diff --git a/README.md b/README.md index b7e055e..6a66712 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ From source: $ npm install -rest.js is designed to run in a browser environment, utilizing [AMD modules](https://github.com/amdjs/amdjs-api/wiki/AMD), or within [Node.js](http://nodejs.org/) as CommonJS modules. Any module loader capable of loading either AMD or CommonJS modules should be able to load rest.js. cujoJS [curl.js](https://github.com/cujojs/curl) is actively tested. +rest.js is designed to run in a browser environment, utilizing a CommonJS Module loader, a transformer such as Browserify or WebPack, or within [Node.js](http://nodejs.org/). Any module loader capable of loading either CommonJS modules should be able to load rest.js. cujoJS [curl.js](https://github.com/cujojs/curl) is actively tested with the cjsm11 loader. An ECMAScript 5 compatible environment is assumed. Older browsers, ::cough:: IE, that do not support ES5 natively can be shimmed. Any shim should work, although we test with cujoJS [poly.js](https://github.com/cujojs/poly) @@ -236,6 +236,7 @@ Change Log .next - MAJOR: Drop hard when.js dependency in favor of ES6 Promise API. See https://github.com/cujojs/when/blob/master/docs/es6-promise-shim.md to use when.js as an ES6 Promise polyfill. +- MAJOR: AMD modules are no longer supported. curl.js users can use the cjsm11 loader, see https://github.com/cujojs/curl#api-at-a-glance - Moved path token param replacement from the clients into the `rest/interceptor/params` interceptor, which is also deprecated. The behavior will no longer be applied automatically in the client. Using the `rest/interceptor/template` interceptor is far more powerful and preferred. - Fixed an issue preventing uri template exploded values from expanding correctly. - Update tested environments: diff --git a/UrlBuilder.js b/UrlBuilder.js index 0735c82..f3a4497 100644 --- a/UrlBuilder.js +++ b/UrlBuilder.js @@ -1,228 +1,216 @@ /* - * Copyright 2012-2013 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define, location) { - 'use strict'; - - var undef; - - define(function (require) { - - var mixin, xWWWFormURLEncoder, origin, urlRE, absoluteUrlRE, fullyQualifiedUrlRE; - - mixin = require('./util/mixin'); - xWWWFormURLEncoder = require('./mime/type/application/x-www-form-urlencoded'); - - urlRE = /([a-z][a-z0-9\+\-\.]*:)\/\/([^@]+@)?(([^:\/]+)(:([0-9]+))?)?(\/[^?#]*)?(\?[^#]*)?(#\S*)?/i; - absoluteUrlRE = /^([a-z][a-z0-9\-\+\.]*:\/\/|\/)/i; - fullyQualifiedUrlRE = /([a-z][a-z0-9\+\-\.]*:)\/\/([^@]+@)?(([^:\/]+)(:([0-9]+))?)?\//i; - - /** - * Apply params to the template to create a URL. - * - * Parameters that are not applied directly to the template, are appended - * to the URL as query string parameters. - * - * @param {string} template the URI template - * @param {Object} params parameters to apply to the template - * @return {string} the resulting URL - */ - function buildUrl(template, params) { - // internal builder to convert template with params. - var url, name, queryStringParams, queryString, re; - - url = template; - queryStringParams = {}; - - if (params) { - for (name in params) { - /*jshint forin:false */ - re = new RegExp('\\{' + name + '\\}'); - if (re.test(url)) { - url = url.replace(re, encodeURIComponent(params[name]), 'g'); - } - else { - queryStringParams[name] = params[name]; - } - } - - queryString = xWWWFormURLEncoder.write(queryStringParams); - if (queryString) { - url += url.indexOf('?') === -1 ? '?' : '&'; - url += queryString; - } - } - return url; - } +'use strict'; - function startsWith(str, test) { - return str.indexOf(test) === 0; - } +var mixin, xWWWFormURLEncoder, origin, urlRE, absoluteUrlRE, fullyQualifiedUrlRE; - /** - * Create a new URL Builder - * - * @param {string|UrlBuilder} template the base template to build from, may be another UrlBuilder - * @param {Object} [params] base parameters - * @constructor - */ - function UrlBuilder(template, params) { - if (!(this instanceof UrlBuilder)) { - // invoke as a constructor - return new UrlBuilder(template, params); - } +mixin = require('./util/mixin'); +xWWWFormURLEncoder = require('./mime/type/application/x-www-form-urlencoded'); + +urlRE = /([a-z][a-z0-9\+\-\.]*:)\/\/([^@]+@)?(([^:\/]+)(:([0-9]+))?)?(\/[^?#]*)?(\?[^#]*)?(#\S*)?/i; +absoluteUrlRE = /^([a-z][a-z0-9\-\+\.]*:\/\/|\/)/i; +fullyQualifiedUrlRE = /([a-z][a-z0-9\+\-\.]*:)\/\/([^@]+@)?(([^:\/]+)(:([0-9]+))?)?\//i; - if (template instanceof UrlBuilder) { - this._template = template.template; - this._params = mixin({}, this._params, params); +/** + * Apply params to the template to create a URL. + * + * Parameters that are not applied directly to the template, are appended + * to the URL as query string parameters. + * + * @param {string} template the URI template + * @param {Object} params parameters to apply to the template + * @return {string} the resulting URL + */ +function buildUrl(template, params) { + // internal builder to convert template with params. + var url, name, queryStringParams, queryString, re; + + url = template; + queryStringParams = {}; + + if (params) { + for (name in params) { + /*jshint forin:false */ + re = new RegExp('\\{' + name + '\\}'); + if (re.test(url)) { + url = url.replace(re, encodeURIComponent(params[name]), 'g'); } else { - this._template = (template || '').toString(); - this._params = params || {}; + queryStringParams[name] = params[name]; } } - UrlBuilder.prototype = { - - /** - * Create a new UrlBuilder instance that extends the current builder. - * The current builder is unmodified. - * - * @param {string} [template] URL template to append to the current template - * @param {Object} [params] params to combine with current params. New params override existing params - * @return {UrlBuilder} the new builder - */ - append: function (template, params) { - // TODO consider query strings and fragments - return new UrlBuilder(this._template + template, mixin({}, this._params, params)); - }, - - /** - * Create a new UrlBuilder with a fully qualified URL based on the - * window's location or base href and the current templates relative URL. - * - * Path variables are preserved. - * - * *Browser only* - * - * @return {UrlBuilder} the fully qualified URL template - */ - fullyQualify: function () { - if (!location) { return this; } - if (this.isFullyQualified()) { return this; } - - var template = this._template; - - if (startsWith(template, '//')) { - template = origin.protocol + template; - } - else if (startsWith(template, '/')) { - template = origin.origin + template; - } - else if (!this.isAbsolute()) { - template = origin.origin + origin.pathname.substring(0, origin.pathname.lastIndexOf('/') + 1); - } - - if (template.indexOf('/', 8) === -1) { - // default the pathname to '/' - template = template + '/'; - } - - return new UrlBuilder(template, this._params); - }, - - /** - * True if the URL is absolute - * - * @return {boolean} - */ - isAbsolute: function () { - return absoluteUrlRE.test(this.build()); - }, - - /** - * True if the URL is fully qualified - * - * @return {boolean} - */ - isFullyQualified: function () { - return fullyQualifiedUrlRE.test(this.build()); - }, - - /** - * True if the URL is cross origin. The protocol, host and port must not be - * the same in order to be cross origin, - * - * @return {boolean} - */ - isCrossOrigin: function () { - if (!origin) { - return true; - } - var url = this.parts(); - return url.protocol !== origin.protocol || - url.hostname !== origin.hostname || - url.port !== origin.port; - }, - - /** - * Split a URL into its consituent parts following the naming convention of - * 'window.location'. One difference is that the port will contain the - * protocol default if not specified. - * - * @see https://developer.mozilla.org/en-US/docs/DOM/window.location - * - * @returns {Object} a 'window.location'-like object - */ - parts: function () { - /*jshint maxcomplexity:20 */ - var url, parts; - url = this.fullyQualify().build().match(urlRE); - parts = { - href: url[0], - protocol: url[1], - host: url[3] || '', - hostname: url[4] || '', - port: url[6], - pathname: url[7] || '', - search: url[8] || '', - hash: url[9] || '' - }; - parts.origin = parts.protocol + '//' + parts.host; - parts.port = parts.port || (parts.protocol === 'https:' ? '443' : parts.protocol === 'http:' ? '80' : ''); - return parts; - }, - - /** - * Expand the template replacing path variables with parameters - * - * @param {Object} [params] params to combine with current params. New params override existing params - * @return {string} the expanded URL - */ - build: function (params) { - return buildUrl(this._template, mixin({}, this._params, params)); - }, - - /** - * @see build - */ - toString: function () { - return this.build(); - } + queryString = xWWWFormURLEncoder.write(queryStringParams); + if (queryString) { + url += url.indexOf('?') === -1 ? '?' : '&'; + url += queryString; + } + } + return url; +} - }; +function startsWith(str, test) { + return str.indexOf(test) === 0; +} - origin = location ? new UrlBuilder(location.href).parts() : undef; +/** + * Create a new URL Builder + * + * @param {string|UrlBuilder} template the base template to build from, may be another UrlBuilder + * @param {Object} [params] base parameters + * @constructor + */ +function UrlBuilder(template, params) { + if (!(this instanceof UrlBuilder)) { + // invoke as a constructor + return new UrlBuilder(template, params); + } + + if (template instanceof UrlBuilder) { + this._template = template.template; + this._params = mixin({}, this._params, params); + } + else { + this._template = (template || '').toString(); + this._params = params || {}; + } +} + +UrlBuilder.prototype = { + + /** + * Create a new UrlBuilder instance that extends the current builder. + * The current builder is unmodified. + * + * @param {string} [template] URL template to append to the current template + * @param {Object} [params] params to combine with current params. New params override existing params + * @return {UrlBuilder} the new builder + */ + append: function (template, params) { + // TODO consider query strings and fragments + return new UrlBuilder(this._template + template, mixin({}, this._params, params)); + }, + + /** + * Create a new UrlBuilder with a fully qualified URL based on the + * window's location or base href and the current templates relative URL. + * + * Path variables are preserved. + * + * *Browser only* + * + * @return {UrlBuilder} the fully qualified URL template + */ + fullyQualify: function () { + if (typeof location === 'undefined') { return this; } + if (this.isFullyQualified()) { return this; } + + var template = this._template; + + if (startsWith(template, '//')) { + template = origin.protocol + template; + } + else if (startsWith(template, '/')) { + template = origin.origin + template; + } + else if (!this.isAbsolute()) { + template = origin.origin + origin.pathname.substring(0, origin.pathname.lastIndexOf('/') + 1); + } - return UrlBuilder; - }); + if (template.indexOf('/', 8) === -1) { + // default the pathname to '/' + template = template + '/'; + } -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }, - typeof window !== 'undefined' ? window.location : void 0 - // Boilerplate for AMD and Node -)); + return new UrlBuilder(template, this._params); + }, + + /** + * True if the URL is absolute + * + * @return {boolean} + */ + isAbsolute: function () { + return absoluteUrlRE.test(this.build()); + }, + + /** + * True if the URL is fully qualified + * + * @return {boolean} + */ + isFullyQualified: function () { + return fullyQualifiedUrlRE.test(this.build()); + }, + + /** + * True if the URL is cross origin. The protocol, host and port must not be + * the same in order to be cross origin, + * + * @return {boolean} + */ + isCrossOrigin: function () { + if (!origin) { + return true; + } + var url = this.parts(); + return url.protocol !== origin.protocol || + url.hostname !== origin.hostname || + url.port !== origin.port; + }, + + /** + * Split a URL into its consituent parts following the naming convention of + * 'window.location'. One difference is that the port will contain the + * protocol default if not specified. + * + * @see https://developer.mozilla.org/en-US/docs/DOM/window.location + * + * @returns {Object} a 'window.location'-like object + */ + parts: function () { + /*jshint maxcomplexity:20 */ + var url, parts; + url = this.fullyQualify().build().match(urlRE); + parts = { + href: url[0], + protocol: url[1], + host: url[3] || '', + hostname: url[4] || '', + port: url[6], + pathname: url[7] || '', + search: url[8] || '', + hash: url[9] || '' + }; + parts.origin = parts.protocol + '//' + parts.host; + parts.port = parts.port || (parts.protocol === 'https:' ? '443' : parts.protocol === 'http:' ? '80' : ''); + return parts; + }, + + /** + * Expand the template replacing path variables with parameters + * + * @param {Object} [params] params to combine with current params. New params override existing params + * @return {string} the expanded URL + */ + build: function (params) { + return buildUrl(this._template, mixin({}, this._params, params)); + }, + + /** + * @see build + */ + toString: function () { + return this.build(); + } + +}; + +origin = typeof location !== 'undefined' ? new UrlBuilder(location.href).parts() : void 0; + +module.exports = UrlBuilder; diff --git a/bower.json b/bower.json index 013ac2b..bf818d4 100644 --- a/bower.json +++ b/bower.json @@ -2,7 +2,7 @@ "name": "rest", "version": "2.0.0-pre", "main": "./browser.js", - "moduleType": ["amd", "node"], + "moduleType": ["node"], "dependencies": {}, "ignore": [ "docs", diff --git a/browser.js b/browser.js index b031bac..103c466 100644 --- a/browser.js +++ b/browser.js @@ -1,25 +1,15 @@ /* - * Copyright 2014 the original author or authors + * Copyright 2014-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var rest = require('./client/default'), + browser = require('./client/xhr'); - var rest = require('./client/default'), - browser = require('./client/xhr'); +rest.setPlatformDefaultClient(browser); - rest.setPlatformDefaultClient(browser); - - return rest; - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = rest; diff --git a/client.js b/client.js index 3c8f9c3..575dff1 100644 --- a/client.js +++ b/client.js @@ -1,64 +1,54 @@ /* - * Copyright 2014 the original author or authors + * Copyright 2014-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (/* require */) { +/** + * Add common helper methods to a client impl + * + * @param {function} impl the client implementation + * @param {Client} [target] target of this client, used when wrapping other clients + * @returns {Client} the client impl with additional methods + */ +module.exports = function client(impl, target) { + + if (target) { /** - * Add common helper methods to a client impl - * - * @param {function} impl the client implementation - * @param {Client} [target] target of this client, used when wrapping other clients - * @returns {Client} the client impl with additional methods + * @returns {Client} the target client */ - return function client(impl, target) { - - if (target) { - - /** - * @returns {Client} the target client - */ - impl.skip = function skip() { - return target; - }; - - } - - /** - * Allow a client to easily be wrapped by an interceptor - * - * @param {Interceptor} interceptor the interceptor to wrap this client with - * @param [config] configuration for the interceptor - * @returns {Client} the newly wrapped client - */ - impl.wrap = function wrap(interceptor, config) { - return interceptor(impl, config); - }; - - /** - * @deprecated - */ - impl.chain = function chain() { - if (typeof console !== 'undefined') { - console.log('rest.js: client.chain() is deprecated, use client.wrap() instead'); - } - - return impl.wrap.apply(this, arguments); - }; - - return impl; - + impl.skip = function skip() { + return target; }; - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); + } + + /** + * Allow a client to easily be wrapped by an interceptor + * + * @param {Interceptor} interceptor the interceptor to wrap this client with + * @param [config] configuration for the interceptor + * @returns {Client} the newly wrapped client + */ + impl.wrap = function wrap(interceptor, config) { + return interceptor(impl, config); + }; + + /** + * @deprecated + */ + impl.chain = function chain() { + if (typeof console !== 'undefined') { + console.log('rest.js: client.chain() is deprecated, use client.wrap() instead'); + } + + return impl.wrap.apply(this, arguments); + }; + + return impl; + +}; diff --git a/client/default.js b/client/default.js index ef5e57b..68b9bcc 100644 --- a/client/default.js +++ b/client/default.js @@ -1,124 +1,116 @@ /* - * Copyright 2014 the original author or authors + * Copyright 2014-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - var undef; - - define(function (require) { - - /** - * Plain JS Object containing properties that represent an HTTP request. - * - * Depending on the capabilities of the underlying client, a request - * may be cancelable. If a request may be canceled, the client will add - * a canceled flag and cancel function to the request object. Canceling - * the request will put the response into an error state. - * - * @field {string} [method='GET'] HTTP method, commonly GET, POST, PUT, DELETE or HEAD - * @field {string|UrlBuilder} [path=''] path template with optional path variables - * @field {Object} [params] parameters for the path template and query string - * @field {Object} [headers] custom HTTP headers to send, in addition to the clients default headers - * @field [entity] the HTTP entity, common for POST or PUT requests - * @field {boolean} [canceled] true if the request has been canceled, set by the client - * @field {Function} [cancel] cancels the request if invoked, provided by the client - * @field {Client} [originator] the client that first handled this request, provided by the interceptor - * - * @class Request - */ - - /** - * Plain JS Object containing properties that represent an HTTP response - * - * @field {Object} [request] the request object as received by the root client - * @field {Object} [raw] the underlying request object, like XmlHttpRequest in a browser - * @field {number} [status.code] status code of the response (i.e. 200, 404) - * @field {string} [status.text] status phrase of the response - * @field {Object] [headers] response headers hash of normalized name, value pairs - * @field [entity] the response body - * - * @class Response - */ - - /** - * HTTP client particularly suited for RESTful operations. - * - * @field {function} wrap wraps this client with a new interceptor returning the wrapped client - * - * @param {Request} the HTTP request - * @returns {ResponsePromise} a promise the resolves to the HTTP response - * - * @class Client - */ - - /** - * Extended when.js Promises/A+ promise with HTTP specific helpers - *q - * @method entity promise for the HTTP entity - * @method status promise for the HTTP status code - * @method headers promise for the HTTP response headers - * @method header promise for a specific HTTP response header - * - * @class ResponsePromise - * @extends Promise - */ - - var client, target, platformDefault; - - client = require('../client'); - - /** - * Make a request with the default client - * @param {Request} the HTTP request - * @returns {Promise} a promise the resolves to the HTTP response - */ - function defaultClient() { - return target.apply(undef, arguments); - } +/** + * Plain JS Object containing properties that represent an HTTP request. + * + * Depending on the capabilities of the underlying client, a request + * may be cancelable. If a request may be canceled, the client will add + * a canceled flag and cancel function to the request object. Canceling + * the request will put the response into an error state. + * + * @field {string} [method='GET'] HTTP method, commonly GET, POST, PUT, DELETE or HEAD + * @field {string|UrlBuilder} [path=''] path template with optional path variables + * @field {Object} [params] parameters for the path template and query string + * @field {Object} [headers] custom HTTP headers to send, in addition to the clients default headers + * @field [entity] the HTTP entity, common for POST or PUT requests + * @field {boolean} [canceled] true if the request has been canceled, set by the client + * @field {Function} [cancel] cancels the request if invoked, provided by the client + * @field {Client} [originator] the client that first handled this request, provided by the interceptor + * + * @class Request + */ - /** - * Change the default client - * @param {Client} client the new default client - */ - defaultClient.setDefaultClient = function setDefaultClient(client) { - target = client; - }; +/** + * Plain JS Object containing properties that represent an HTTP response + * + * @field {Object} [request] the request object as received by the root client + * @field {Object} [raw] the underlying request object, like XmlHttpRequest in a browser + * @field {number} [status.code] status code of the response (i.e. 200, 404) + * @field {string} [status.text] status phrase of the response + * @field {Object] [headers] response headers hash of normalized name, value pairs + * @field [entity] the response body + * + * @class Response + */ - /** - * Obtain a direct reference to the current default client - * @returns {Client} the default client - */ - defaultClient.getDefaultClient = function getDefaultClient() { - return target; - }; +/** + * HTTP client particularly suited for RESTful operations. + * + * @field {function} wrap wraps this client with a new interceptor returning the wrapped client + * + * @param {Request} the HTTP request + * @returns {ResponsePromise} a promise the resolves to the HTTP response + * + * @class Client + */ - /** - * Reset the default client to the platform default - */ - defaultClient.resetDefaultClient = function resetDefaultClient() { - target = platformDefault; - }; + /** + * Extended when.js Promises/A+ promise with HTTP specific helpers + *q + * @method entity promise for the HTTP entity + * @method status promise for the HTTP status code + * @method headers promise for the HTTP response headers + * @method header promise for a specific HTTP response header + * + * @class ResponsePromise + * @extends Promise + */ + +var client, target, platformDefault; + +client = require('../client'); + +if (typeof Promise !== 'function' && console && console.log) { + console.log('An ES6 Promise implementation is required to use rest.js. See https://github.com/cujojs/when/blob/master/docs/es6-promise-shim.md for using when.js as a Promise polyfill.'); +} + +/** + * Make a request with the default client + * @param {Request} the HTTP request + * @returns {Promise} a promise the resolves to the HTTP response + */ +function defaultClient() { + return target.apply(void 0, arguments); +} - /** - * @private - */ - defaultClient.setPlatformDefaultClient = function setPlatformDefaultClient(client) { - if (platformDefault) { - throw new Error('Unable to redefine platformDefaultClient'); - } - target = platformDefault = client; - }; +/** + * Change the default client + * @param {Client} client the new default client + */ +defaultClient.setDefaultClient = function setDefaultClient(client) { + target = client; +}; - return client(defaultClient); +/** + * Obtain a direct reference to the current default client + * @returns {Client} the default client + */ +defaultClient.getDefaultClient = function getDefaultClient() { + return target; +}; - }); +/** + * Reset the default client to the platform default + */ +defaultClient.resetDefaultClient = function resetDefaultClient() { + target = platformDefault; +}; -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +/** + * @private + */ +defaultClient.setPlatformDefaultClient = function setPlatformDefaultClient(client) { + if (platformDefault) { + throw new Error('Unable to redefine platformDefaultClient'); + } + target = platformDefault = client; +}; + +module.exports = client(defaultClient); diff --git a/client/jsonp.js b/client/jsonp.js index 3f719a1..0ef86af 100644 --- a/client/jsonp.js +++ b/client/jsonp.js @@ -5,140 +5,126 @@ * @author Scott Andrews */ -(function (define, global, document) { - 'use strict'; +'use strict'; + +var UrlBuilder, responsePromise, client; + +UrlBuilder = require('../UrlBuilder'); +responsePromise = require('../util/responsePromise'); +client = require('../client'); + +// consider abstracting this into a util module +function clearProperty(scope, propertyName) { + try { + delete scope[propertyName]; + } + catch (e) { + // IE doesn't like to delete properties on the window object + if (propertyName in scope) { + scope[propertyName] = void 0; + } + } +} - var undef; +function cleanupScriptNode(response) { + try { + if (response.raw && response.raw.parentNode) { + response.raw.parentNode.removeChild(response.raw); + } + } catch (e) { + // ignore + } +} + +function registerCallback(prefix, resolve, response, name) { + if (!name) { + do { + name = prefix + Math.floor(new Date().getTime() * Math.random()); + } + while (name in window); + } + + window[name] = function jsonpCallback(data) { + response.entity = data; + clearProperty(window, name); + cleanupScriptNode(response); + if (!response.request.canceled) { + resolve(response); + } + }; - define(function (require) { + return name; +} - var UrlBuilder, responsePromise, client; +/** + * Executes the request as JSONP. + * + * @param {string} request.path the URL to load + * @param {Object} [request.params] parameters to bind to the path + * @param {string} [request.callback.param='callback'] the parameter name for + * which the callback function name is the value + * @param {string} [request.callback.prefix='jsonp'] prefix for the callback + * function, as the callback is attached to the window object, a unique, + * unobtrusive prefix is desired + * @param {string} [request.callback.name=] pins the name of the + * callback function, useful for cases where the server doesn't allow + * custom callback names. Generally not recommended. + * + * @returns {Promise} + */ +module.exports = client(function jsonp(request) { + return responsePromise.promise(function (resolve, reject) { - UrlBuilder = require('../UrlBuilder'); - responsePromise = require('../util/responsePromise'); - client = require('../client'); + var callbackName, callbackParams, script, firstScript, response; - // consider abstracting this into a util module - function clearProperty(scope, propertyName) { - try { - delete scope[propertyName]; - } - catch (e) { - // IE doesn't like to delete properties on the window object - if (propertyName in scope) { - scope[propertyName] = undef; - } - } - } + request = typeof request === 'string' ? { path: request } : request || {}; + response = { request: request }; - function cleanupScriptNode(response) { - try { - if (response.raw && response.raw.parentNode) { - response.raw.parentNode.removeChild(response.raw); - } - } catch (e) { - // ignore - } + if (request.canceled) { + response.error = 'precanceled'; + reject(response); + return; } - function registerCallback(prefix, resolve, response, name) { - if (!name) { - do { - name = prefix + Math.floor(new Date().getTime() * Math.random()); - } - while (name in global); - } - - global[name] = function jsonpCallback(data) { - response.entity = data; - clearProperty(global, name); + request.callback = request.callback || {}; + callbackName = registerCallback(request.callback.prefix || 'jsonp', resolve, response, request.callback.name); + callbackParams = {}; + callbackParams[request.callback.param || 'callback'] = callbackName; + + request.canceled = false; + request.cancel = function cancel() { + request.canceled = true; + cleanupScriptNode(response); + reject(response); + }; + + script = document.createElement('script'); + script.type = 'text/javascript'; + script.async = true; + script.src = response.url = new UrlBuilder(request.path, callbackParams).build(); + + function handlePossibleError() { + if (typeof window[callbackName] === 'function') { + response.error = 'loaderror'; + clearProperty(window, callbackName); cleanupScriptNode(response); - if (!response.request.canceled) { - resolve(response); - } - }; - - return name; + reject(response); + } } + script.onerror = function () { + handlePossibleError(); + }; + script.onload = script.onreadystatechange = function (e) { + // script tag load callbacks are completely non-standard + // handle case where onreadystatechange is fired for an error instead of onerror + if ((e && (e.type === 'load' || e.type === 'error')) || script.readyState === 'loaded') { + handlePossibleError(); + } + }; - /** - * Executes the request as JSONP. - * - * @param {string} request.path the URL to load - * @param {Object} [request.params] parameters to bind to the path - * @param {string} [request.callback.param='callback'] the parameter name for - * which the callback function name is the value - * @param {string} [request.callback.prefix='jsonp'] prefix for the callback - * function, as the callback is attached to the window object, a unique, - * unobtrusive prefix is desired - * @param {string} [request.callback.name=] pins the name of the - * callback function, useful for cases where the server doesn't allow - * custom callback names. Generally not recommended. - * - * @returns {Promise} - */ - return client(function jsonp(request) { - return responsePromise.promise(function (resolve, reject) { - - var callbackName, callbackParams, script, firstScript, response; - - request = typeof request === 'string' ? { path: request } : request || {}; - response = { request: request }; - - if (request.canceled) { - response.error = 'precanceled'; - reject(response); - return; - } - - request.callback = request.callback || {}; - callbackName = registerCallback(request.callback.prefix || 'jsonp', resolve, response, request.callback.name); - callbackParams = {}; - callbackParams[request.callback.param || 'callback'] = callbackName; - - request.canceled = false; - request.cancel = function cancel() { - request.canceled = true; - cleanupScriptNode(response); - reject(response); - }; - - script = document.createElement('script'); - script.type = 'text/javascript'; - script.async = true; - script.src = response.url = new UrlBuilder(request.path, callbackParams).build(); - - function handlePossibleError() { - if (typeof global[callbackName] === 'function') { - response.error = 'loaderror'; - clearProperty(global, callbackName); - cleanupScriptNode(response); - reject(response); - } - } - script.onerror = function () { - handlePossibleError(); - }; - script.onload = script.onreadystatechange = function (e) { - // script tag load callbacks are completely non-standard - // handle case where onreadystatechange is fired for an error instead of onerror - if ((e && (e.type === 'load' || e.type === 'error')) || script.readyState === 'loaded') { - handlePossibleError(); - } - }; - - response.raw = script; - firstScript = document.getElementsByTagName('script')[0]; - firstScript.parentNode.insertBefore(script, firstScript); - - }); - }); + response.raw = script; + firstScript = document.getElementsByTagName('script')[0]; + firstScript.parentNode.insertBefore(script, firstScript); }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }, - typeof window !== 'undefined' ? window : void 0, - typeof document !== 'undefined' ? document : void 0 - // Boilerplate for AMD and Node -)); +}); diff --git a/client/node.js b/client/node.js index 6dcf891..1ee6419 100644 --- a/client/node.js +++ b/client/node.js @@ -6,140 +6,129 @@ * @author Scott Andrews */ -(function (define, envRequire) { - 'use strict'; - - define(function (require) { - - var parser, http, https, mixin, normalizeHeaderName, responsePromise, client, httpsExp; - - parser = envRequire('url'); - http = envRequire('http'); - https = envRequire('https'); - mixin = require('../util/mixin'); - normalizeHeaderName = require('../util/normalizeHeaderName'); - responsePromise = require('../util/responsePromise'); - client = require('../client'); - - httpsExp = /^https/i; - - // TODO remove once Node 0.6 is no longer supported - Buffer.concat = Buffer.concat || function (list, length) { - /*jshint plusplus:false, shadow:true */ - // from https://github.com/joyent/node/blob/v0.8.21/lib/buffer.js - if (!Array.isArray(list)) { - throw new Error('Usage: Buffer.concat(list, [length])'); - } - - if (list.length === 0) { - return new Buffer(0); - } else if (list.length === 1) { - return list[0]; - } - - if (typeof length !== 'number') { - length = 0; - for (var i = 0; i < list.length; i++) { - var buf = list[i]; - length += buf.length; - } - } - - var buffer = new Buffer(length); - var pos = 0; - for (var i = 0; i < list.length; i++) { - var buf = list[i]; - buf.copy(buffer, pos); - pos += buf.length; - } - return buffer; +'use strict'; + +var parser, http, https, mixin, normalizeHeaderName, responsePromise, client, httpsExp; + +parser = require('url'); +http = require('http'); +https = require('https'); +mixin = require('../util/mixin'); +normalizeHeaderName = require('../util/normalizeHeaderName'); +responsePromise = require('../util/responsePromise'); +client = require('../client'); + +httpsExp = /^https/i; + +// TODO remove once Node 0.6 is no longer supported +Buffer.concat = Buffer.concat || function (list, length) { + /*jshint plusplus:false, shadow:true */ + // from https://github.com/joyent/node/blob/v0.8.21/lib/buffer.js + if (!Array.isArray(list)) { + throw new Error('Usage: Buffer.concat(list, [length])'); + } + + if (list.length === 0) { + return new Buffer(0); + } else if (list.length === 1) { + return list[0]; + } + + if (typeof length !== 'number') { + length = 0; + for (var i = 0; i < list.length; i++) { + var buf = list[i]; + length += buf.length; + } + } + + var buffer = new Buffer(length); + var pos = 0; + for (var i = 0; i < list.length; i++) { + var buf = list[i]; + buf.copy(buffer, pos); + pos += buf.length; + } + return buffer; +}; + +module.exports = client(function node(request) { + /*jshint maxcomplexity:20 */ + return responsePromise.promise(function (resolve, reject) { + + var options, clientRequest, client, url, headers, entity, response; + + request = typeof request === 'string' ? { path: request } : request || {}; + response = { request: request }; + + if (request.canceled) { + response.error = 'precanceled'; + reject(response); + return; + } + + url = response.url = request.path || ''; + client = url.match(httpsExp) ? https : http; + + options = mixin({}, request.mixin, parser.parse(url)); + + entity = request.entity; + request.method = request.method || (entity ? 'POST' : 'GET'); + options.method = request.method; + headers = options.headers = {}; + Object.keys(request.headers || {}).forEach(function (name) { + headers[normalizeHeaderName(name)] = request.headers[name]; + }); + if (!headers['Content-Length']) { + headers['Content-Length'] = entity ? Buffer.byteLength(entity, 'utf8') : 0; + } + + request.canceled = false; + request.cancel = function cancel() { + request.canceled = true; + clientRequest.abort(); }; - return client(function node(request) { - /*jshint maxcomplexity:20 */ - return responsePromise.promise(function (resolve, reject) { - - var options, clientRequest, client, url, headers, entity, response; - - request = typeof request === 'string' ? { path: request } : request || {}; - response = { request: request }; - - if (request.canceled) { - response.error = 'precanceled'; - reject(response); - return; - } - - url = response.url = request.path || ''; - client = url.match(httpsExp) ? https : http; - - options = mixin({}, request.mixin, parser.parse(url)); - - entity = request.entity; - request.method = request.method || (entity ? 'POST' : 'GET'); - options.method = request.method; - headers = options.headers = {}; - Object.keys(request.headers || {}).forEach(function (name) { - headers[normalizeHeaderName(name)] = request.headers[name]; - }); - if (!headers['Content-Length']) { - headers['Content-Length'] = entity ? Buffer.byteLength(entity, 'utf8') : 0; - } - - request.canceled = false; - request.cancel = function cancel() { - request.canceled = true; - clientRequest.abort(); - }; - - clientRequest = client.request(options, function (clientResponse) { - // Array of Buffers to collect response chunks - var buffers = []; - - response.raw = { - request: clientRequest, - response: clientResponse - }; - response.status = { - code: clientResponse.statusCode - // node doesn't provide access to the status text - }; - response.headers = {}; - Object.keys(clientResponse.headers).forEach(function (name) { - response.headers[normalizeHeaderName(name)] = clientResponse.headers[name]; - }); - - clientResponse.on('data', function (data) { - // Collect the next Buffer chunk - buffers.push(data); - }); - - clientResponse.on('end', function () { - // Create the final response entity - response.entity = buffers.length > 0 ? Buffer.concat(buffers).toString() : ''; - buffers = null; - - resolve(response); - }); - }); - - clientRequest.on('error', function (e) { - response.error = e; - reject(response); - }); - - if (entity) { - clientRequest.write(entity); - } - clientRequest.end(); + clientRequest = client.request(options, function (clientResponse) { + // Array of Buffers to collect response chunks + var buffers = []; + + response.raw = { + request: clientRequest, + response: clientResponse + }; + response.status = { + code: clientResponse.statusCode + // node doesn't provide access to the status text + }; + response.headers = {}; + Object.keys(clientResponse.headers).forEach(function (name) { + response.headers[normalizeHeaderName(name)] = clientResponse.headers[name]; + }); + + clientResponse.on('data', function (data) { + // Collect the next Buffer chunk + buffers.push(data); + }); + + clientResponse.on('end', function () { + // Create the final response entity + response.entity = buffers.length > 0 ? Buffer.concat(buffers).toString() : ''; + buffers = null; + resolve(response); }); }); - }); + clientRequest.on('error', function (e) { + response.error = e; + reject(response); + }); -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }, - typeof require === 'function' && require - // Boilerplate for AMD and Node -)); + if (entity) { + clientRequest.write(entity); + } + clientRequest.end(); + + }); +}); diff --git a/client/xdr.js b/client/xdr.js index ab7686d..352943e 100644 --- a/client/xdr.js +++ b/client/xdr.js @@ -5,77 +5,68 @@ * @author Scott Andrews */ -(function (define, XDomainRequest) { - 'use strict'; +'use strict'; - define(function (require) { +/*globals XDomainRequest */ - var responsePromise, client; +var responsePromise, client; - responsePromise = require('../util/responsePromise'); - client = require('../client'); +responsePromise = require('../util/responsePromise'); +client = require('../client'); - return client(function xdr(request) { - return responsePromise.promise(function (resolve, reject) { +module.exports = client(function xdr(request) { + return responsePromise.promise(function (resolve, reject) { - var client, method, url, entity, response; + var client, method, url, entity, response; - request = typeof request === 'string' ? { path: request } : request || {}; - response = { request: request }; + request = typeof request === 'string' ? { path: request } : request || {}; + response = { request: request }; - if (request.canceled) { - response.error = 'precanceled'; - reject(response); - return; - } + if (request.canceled) { + response.error = 'precanceled'; + reject(response); + return; + } - client = response.raw = new XDomainRequest(); + client = response.raw = new XDomainRequest(); - entity = request.entity; - request.method = request.method || (entity ? 'POST' : 'GET'); - method = request.method; - url = response.url = request.path || ''; + entity = request.entity; + request.method = request.method || (entity ? 'POST' : 'GET'); + method = request.method; + url = response.url = request.path || ''; - try { - client.open(method, url); + try { + client.open(method, url); - request.canceled = false; - request.cancel = function cancel() { - request.canceled = true; - client.abort(); - reject(response); - }; + request.canceled = false; + request.cancel = function cancel() { + request.canceled = true; + client.abort(); + reject(response); + }; - client.onload = function () { - if (request.canceled) { return; } - // this is all we have access to on the XDR object :( - response.headers = { 'Content-Type': client.contentType }; - response.entity = client.responseText; - resolve(response); - }; + client.onload = function () { + if (request.canceled) { return; } + // this is all we have access to on the XDR object :( + response.headers = { 'Content-Type': client.contentType }; + response.entity = client.responseText; + resolve(response); + }; - client.onerror = function () { - response.error = 'loaderror'; - reject(response); - }; + client.onerror = function () { + response.error = 'loaderror'; + reject(response); + }; - // onprogress must be defined - client.onprogress = function () {}; + // onprogress must be defined + client.onprogress = function () {}; - client.send(entity); - } - catch (e) { - response.error = 'loaderror'; - reject(response); - } - - }); - }); + client.send(entity); + } + catch (e) { + response.error = 'loaderror'; + reject(response); + } }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }, - typeof window !== 'undefined' ? window.XDomainRequest : void 0 - // Boilerplate for AMD and Node -)); +}); diff --git a/client/xhr.js b/client/xhr.js index 15c7bc1..ba27c75 100644 --- a/client/xhr.js +++ b/client/xhr.js @@ -1,178 +1,167 @@ /* - * Copyright 2012-2015 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define, global) { - 'use strict'; - - define(function (require) { +'use strict'; + +var normalizeHeaderName, responsePromise, client, headerSplitRE; + +normalizeHeaderName = require('../util/normalizeHeaderName'); +responsePromise = require('../util/responsePromise'); +client = require('../client'); + +// according to the spec, the line break is '\r\n', but doesn't hold true in practice +headerSplitRE = /[\r|\n]+/; + +function parseHeaders(raw) { + // Note: Set-Cookie will be removed by the browser + var headers = {}; + + if (!raw) { return headers; } + + raw.trim().split(headerSplitRE).forEach(function (header) { + var boundary, name, value; + boundary = header.indexOf(':'); + name = normalizeHeaderName(header.substring(0, boundary).trim()); + value = header.substring(boundary + 1).trim(); + if (headers[name]) { + if (Array.isArray(headers[name])) { + // add to an existing array + headers[name].push(value); + } + else { + // convert single value to array + headers[name] = [headers[name], value]; + } + } + else { + // new, single value + headers[name] = value; + } + }); - var normalizeHeaderName, responsePromise, client, headerSplitRE; + return headers; +} + +function safeMixin(target, source) { + Object.keys(source || {}).forEach(function (prop) { + // make sure the property already exists as + // IE 6 will blow up if we add a new prop + if (source.hasOwnProperty(prop) && prop in target) { + try { + target[prop] = source[prop]; + } + catch (e) { + // ignore, expected for some properties at some points in the request lifecycle + } + } + }); - normalizeHeaderName = require('../util/normalizeHeaderName'); - responsePromise = require('../util/responsePromise'); - client = require('../client'); + return target; +} - // according to the spec, the line break is '\r\n', but doesn't hold true in practice - headerSplitRE = /[\r|\n]+/; +module.exports = client(function xhr(request) { + return responsePromise.promise(function (resolve, reject) { + /*jshint maxcomplexity:20 */ - function parseHeaders(raw) { - // Note: Set-Cookie will be removed by the browser - var headers = {}; + var client, method, url, headers, entity, headerName, response, XHR; - if (!raw) { return headers; } + request = typeof request === 'string' ? { path: request } : request || {}; + response = { request: request }; - raw.trim().split(headerSplitRE).forEach(function (header) { - var boundary, name, value; - boundary = header.indexOf(':'); - name = normalizeHeaderName(header.substring(0, boundary).trim()); - value = header.substring(boundary + 1).trim(); - if (headers[name]) { - if (Array.isArray(headers[name])) { - // add to an existing array - headers[name].push(value); - } - else { - // convert single value to array - headers[name] = [headers[name], value]; - } - } - else { - // new, single value - headers[name] = value; - } - }); - - return headers; + if (request.canceled) { + response.error = 'precanceled'; + reject(response); + return; } - function safeMixin(target, source) { - Object.keys(source || {}).forEach(function (prop) { - // make sure the property already exists as - // IE 6 will blow up if we add a new prop - if (source.hasOwnProperty(prop) && prop in target) { - try { - target[prop] = source[prop]; - } - catch (e) { - // ignore, expected for some properties at some points in the request lifecycle - } - } - }); - - return target; + XHR = request.engine || XMLHttpRequest; + if (!XHR) { + reject({ request: request, error: 'xhr-not-available' }); + return; } - return client(function xhr(request) { - return responsePromise.promise(function (resolve, reject) { - /*jshint maxcomplexity:20 */ - - var client, method, url, headers, entity, headerName, response, XMLHttpRequest; - - request = typeof request === 'string' ? { path: request } : request || {}; - response = { request: request }; - - if (request.canceled) { - response.error = 'precanceled'; - reject(response); - return; + entity = request.entity; + request.method = request.method || (entity ? 'POST' : 'GET'); + method = request.method; + url = response.url = request.path || ''; + + try { + client = response.raw = new XHR(); + + // mixin extra request properties before and after opening the request as some properties require being set at different phases of the request + safeMixin(client, request.mixin); + client.open(method, url, true); + safeMixin(client, request.mixin); + + headers = request.headers; + for (headerName in headers) { + /*jshint forin:false */ + if (headerName === 'Content-Type' && headers[headerName] === 'multipart/form-data') { + // XMLHttpRequest generates its own Content-Type header with the + // appropriate multipart boundary when sending multipart/form-data. + continue; } - entity = request.entity; - request.method = request.method || (entity ? 'POST' : 'GET'); - method = request.method; - url = response.url = request.path || ''; - - XMLHttpRequest = request.engine || global.XMLHttpRequest; - if (!XMLHttpRequest) { - reject({ request: request, url: url, error: 'xhr-not-available' }); - return; - } - - try { - client = response.raw = new XMLHttpRequest(); - - // mixin extra request properties before and after opening the request as some properties require being set at different phases of the request - safeMixin(client, request.mixin); - client.open(method, url, true); - safeMixin(client, request.mixin); - - headers = request.headers; - for (headerName in headers) { - /*jshint forin:false */ - if (headerName === 'Content-Type' && headers[headerName] === 'multipart/form-data') { - // XMLHttpRequest generates its own Content-Type header with the - // appropriate multipart boundary when sending multipart/form-data. - continue; - } - - client.setRequestHeader(headerName, headers[headerName]); - } - - request.canceled = false; - request.cancel = function cancel() { - request.canceled = true; - client.abort(); - reject(response); + client.setRequestHeader(headerName, headers[headerName]); + } + + request.canceled = false; + request.cancel = function cancel() { + request.canceled = true; + client.abort(); + reject(response); + }; + + client.onreadystatechange = function (/* e */) { + if (request.canceled) { return; } + if (client.readyState === (XHR.DONE || 4)) { + response.status = { + code: client.status, + text: client.statusText }; + response.headers = parseHeaders(client.getAllResponseHeaders()); + response.entity = client.responseText; - client.onreadystatechange = function (/* e */) { - if (request.canceled) { return; } - if (client.readyState === (XMLHttpRequest.DONE || 4)) { - response.status = { - code: client.status, - text: client.statusText - }; - response.headers = parseHeaders(client.getAllResponseHeaders()); - response.entity = client.responseText; - - // #125 -- Sometimes IE8-9 uses 1223 instead of 204 - // http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request - if (response.status.code === 1223) { - response.status.code = 204; - } - - if (response.status.code > 0) { - // check status code as readystatechange fires before error event - resolve(response); - } - else { - // give the error callback a chance to fire before resolving - // requests for file:// URLs do not have a status code - setTimeout(function () { - resolve(response); - }, 0); - } - } - }; + // #125 -- Sometimes IE8-9 uses 1223 instead of 204 + // http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request + if (response.status.code === 1223) { + response.status.code = 204; + } - try { - client.onerror = function (/* e */) { - response.error = 'loaderror'; - reject(response); - }; + if (response.status.code > 0) { + // check status code as readystatechange fires before error event + resolve(response); } - catch (e) { - // IE 6 will not support error handling + else { + // give the error callback a chance to fire before resolving + // requests for file:// URLs do not have a status code + setTimeout(function () { + resolve(response); + }, 0); } - - client.send(entity); } - catch (e) { + }; + + try { + client.onerror = function (/* e */) { response.error = 'loaderror'; reject(response); - } + }; + } + catch (e) { + // IE 6 will not support error handling + } - }); - }); + client.send(entity); + } + catch (e) { + response.error = 'loaderror'; + reject(response); + } }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }, - typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : void 0) - // Boilerplate for AMD and Node -)); +}); diff --git a/interceptor.js b/interceptor.js index a26eb55..d8c5d4c 100644 --- a/interceptor.js +++ b/interceptor.js @@ -1,156 +1,145 @@ /* - * Copyright 2012-2015 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; - - define(function (require) { - - var defaultClient, mixin, responsePromise, client, Promise; - - defaultClient = require('./client/default'); - mixin = require('./util/mixin'); - responsePromise = require('./util/responsePromise'); - client = require('./client'); - Promise = require('./util/Promise'); - - /** - * Interceptors have the ability to intercept the request and/org response - * objects. They may augment, prune, transform or replace the - * request/response as needed. Clients may be composed by wrapping - * together multiple interceptors. - * - * Configured interceptors are functional in nature. Wrapping a client in - * an interceptor will not affect the client, merely the data that flows in - * and out of that client. A common configuration can be created once and - * shared; specialization can be created by further wrapping that client - * with custom interceptors. - * - * @param {Client} [target] client to wrap - * @param {Object} [config] configuration for the interceptor, properties will be specific to the interceptor implementation - * @returns {Client} A client wrapped with the interceptor - * - * @class Interceptor - */ - - function defaultInitHandler(config) { - return config; - } +'use strict'; - function defaultRequestHandler(request /*, config, meta */) { - return request; - } +var defaultClient, mixin, responsePromise, client; - function defaultResponseHandler(response /*, config, meta */) { - return response; - } +defaultClient = require('./client/default'); +mixin = require('./util/mixin'); +responsePromise = require('./util/responsePromise'); +client = require('./client'); - /** - * Alternate return type for the request handler that allows for more complex interactions. - * - * @param properties.request the traditional request return object - * @param {Promise} [properties.abort] promise that resolves if/when the request is aborted - * @param {Client} [properties.client] override the defined client with an alternate client - * @param [properties.response] response for the request, short circuit the request - */ - function ComplexRequest(properties) { - if (!(this instanceof ComplexRequest)) { - // in case users forget the 'new' don't mix into the interceptor - return new ComplexRequest(properties); - } - mixin(this, properties); - } +/** + * Interceptors have the ability to intercept the request and/org response + * objects. They may augment, prune, transform or replace the + * request/response as needed. Clients may be composed by wrapping + * together multiple interceptors. + * + * Configured interceptors are functional in nature. Wrapping a client in + * an interceptor will not affect the client, merely the data that flows in + * and out of that client. A common configuration can be created once and + * shared; specialization can be created by further wrapping that client + * with custom interceptors. + * + * @param {Client} [target] client to wrap + * @param {Object} [config] configuration for the interceptor, properties will be specific to the interceptor implementation + * @returns {Client} A client wrapped with the interceptor + * + * @class Interceptor + */ - /** - * Create a new interceptor for the provided handlers. - * - * @param {Function} [handlers.init] one time intialization, must return the config object - * @param {Function} [handlers.request] request handler - * @param {Function} [handlers.response] response handler regardless of error state - * @param {Function} [handlers.success] response handler when the request is not in error - * @param {Function} [handlers.error] response handler when the request is in error, may be used to 'unreject' an error state - * @param {Function} [handlers.client] the client to use if otherwise not specified, defaults to platform default client - * - * @returns {Interceptor} - */ - function interceptor(handlers) { - - var initHandler, requestHandler, successResponseHandler, errorResponseHandler; - - handlers = handlers || {}; - - initHandler = handlers.init || defaultInitHandler; - requestHandler = handlers.request || defaultRequestHandler; - successResponseHandler = handlers.success || handlers.response || defaultResponseHandler; - errorResponseHandler = handlers.error || function () { - // Propagate the rejection, with the result of the handler - return Promise.resolve((handlers.response || defaultResponseHandler).apply(this, arguments)) - .then(Promise.reject.bind(Promise)); - }; - - return function (target, config) { - - if (typeof target === 'object') { - config = target; - } - if (typeof target !== 'function') { - target = handlers.client || defaultClient; - } +function defaultInitHandler(config) { + return config; +} - config = initHandler(config || {}); - - function interceptedClient(request) { - var context, meta; - context = {}; - meta = { 'arguments': Array.prototype.slice.call(arguments), client: interceptedClient }; - request = typeof request === 'string' ? { path: request } : request || {}; - request.originator = request.originator || interceptedClient; - return responsePromise( - requestHandler.call(context, request, config, meta), - function (request) { - var response, abort, next; - next = target; - if (request instanceof ComplexRequest) { - // unpack request - abort = request.abort; - next = request.client || next; - response = request.response; - // normalize request, must be last - request = request.request; - } - response = response || Promise.resolve(request).then(function (request) { - return Promise.resolve(next(request)).then( - function (response) { - return successResponseHandler.call(context, response, config, meta); - }, - function (response) { - return errorResponseHandler.call(context, response, config, meta); - } - ); - }); - return abort ? Promise.race([response, abort]) : response; - }, - function (error) { - return Promise.reject({ request: request, error: error }); - } - ); - } +function defaultRequestHandler(request /*, config, meta */) { + return request; +} + +function defaultResponseHandler(response /*, config, meta */) { + return response; +} - return client(interceptedClient, target); - }; +/** + * Alternate return type for the request handler that allows for more complex interactions. + * + * @param properties.request the traditional request return object + * @param {Promise} [properties.abort] promise that resolves if/when the request is aborted + * @param {Client} [properties.client] override the defined client with an alternate client + * @param [properties.response] response for the request, short circuit the request + */ +function ComplexRequest(properties) { + if (!(this instanceof ComplexRequest)) { + // in case users forget the 'new' don't mix into the interceptor + return new ComplexRequest(properties); + } + mixin(this, properties); +} + +/** + * Create a new interceptor for the provided handlers. + * + * @param {Function} [handlers.init] one time intialization, must return the config object + * @param {Function} [handlers.request] request handler + * @param {Function} [handlers.response] response handler regardless of error state + * @param {Function} [handlers.success] response handler when the request is not in error + * @param {Function} [handlers.error] response handler when the request is in error, may be used to 'unreject' an error state + * @param {Function} [handlers.client] the client to use if otherwise not specified, defaults to platform default client + * + * @returns {Interceptor} + */ +function interceptor(handlers) { + + var initHandler, requestHandler, successResponseHandler, errorResponseHandler; + + handlers = handlers || {}; + + initHandler = handlers.init || defaultInitHandler; + requestHandler = handlers.request || defaultRequestHandler; + successResponseHandler = handlers.success || handlers.response || defaultResponseHandler; + errorResponseHandler = handlers.error || function () { + // Propagate the rejection, with the result of the handler + return Promise.resolve((handlers.response || defaultResponseHandler).apply(this, arguments)) + .then(Promise.reject.bind(Promise)); + }; + + return function (target, config) { + + if (typeof target === 'object') { + config = target; + } + if (typeof target !== 'function') { + target = handlers.client || defaultClient; } - interceptor.ComplexRequest = ComplexRequest; + config = initHandler(config || {}); + + function interceptedClient(request) { + var context, meta; + context = {}; + meta = { 'arguments': Array.prototype.slice.call(arguments), client: interceptedClient }; + request = typeof request === 'string' ? { path: request } : request || {}; + request.originator = request.originator || interceptedClient; + return responsePromise( + requestHandler.call(context, request, config, meta), + function (request) { + var response, abort, next; + next = target; + if (request instanceof ComplexRequest) { + // unpack request + abort = request.abort; + next = request.client || next; + response = request.response; + // normalize request, must be last + request = request.request; + } + response = response || Promise.resolve(request).then(function (request) { + return Promise.resolve(next(request)).then( + function (response) { + return successResponseHandler.call(context, response, config, meta); + }, + function (response) { + return errorResponseHandler.call(context, response, config, meta); + } + ); + }); + return abort ? Promise.race([response, abort]) : response; + }, + function (error) { + return Promise.reject({ request: request, error: error }); + } + ); + } - return interceptor; + return client(interceptedClient, target); + }; +} - }); +interceptor.ComplexRequest = ComplexRequest; -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = interceptor; diff --git a/interceptor/basicAuth.js b/interceptor/basicAuth.js index 6eff739..5dc2b28 100644 --- a/interceptor/basicAuth.js +++ b/interceptor/basicAuth.js @@ -1,48 +1,38 @@ /* - * Copyright 2012-2013 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor, base64; - var interceptor, base64; +interceptor = require('../interceptor'); +base64 = require('../util/base64'); - interceptor = require('../interceptor'); - base64 = require('../util/base64'); - - /** - * Authenticates the request using HTTP Basic Authentication (rfc2617) - * - * @param {Client} [client] client to wrap - * @param {string} config.username username - * @param {string} [config.password=''] password for the user - * - * @returns {Client} - */ - return interceptor({ - request: function handleRequest(request, config) { - var headers, username, password; - - headers = request.headers || (request.headers = {}); - username = request.username || config.username; - password = request.password || config.password || ''; - - if (username) { - headers.Authorization = 'Basic ' + base64.encode(username + ':' + password); - } +/** + * Authenticates the request using HTTP Basic Authentication (rfc2617) + * + * @param {Client} [client] client to wrap + * @param {string} config.username username + * @param {string} [config.password=''] password for the user + * + * @returns {Client} + */ +module.exports = interceptor({ + request: function handleRequest(request, config) { + var headers, username, password; - return request; - } - }); + headers = request.headers || (request.headers = {}); + username = request.username || config.username; + password = request.password || config.password || ''; - }); + if (username) { + headers.Authorization = 'Basic ' + base64.encode(username + ':' + password); + } -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); + return request; + } +}); diff --git a/interceptor/csrf.js b/interceptor/csrf.js index 637fe5f..c1fb675 100644 --- a/interceptor/csrf.js +++ b/interceptor/csrf.js @@ -1,61 +1,51 @@ /* - * Copyright 2013 the original author or authors + * Copyright 2013-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor; - var interceptor; +interceptor = require('../interceptor'); - interceptor = require('../interceptor'); - - /** - * Applies a Cross-Site Request Forgery protection header to a request - * - * CSRF protection helps a server verify that a request came from a - * trusted client and not another client that was able to masquerade - * as an authorized client. Sites that use cookie based authentication - * are particularly vulnerable to request forgeries without extra - * protection. - * - * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery - * - * @param {Client} [client] client to wrap - * @param {string} [config.name='X-Csrf-Token'] name of the request - * header, may be overridden by `request.csrfTokenName` - * @param {string} [config.token] CSRF token, may be overridden by - * `request.csrfToken` - * - * @returns {Client} - */ - return interceptor({ - init: function (config) { - config.name = config.name || 'X-Csrf-Token'; - return config; - }, - request: function handleRequest(request, config) { - var headers, name, token; - - headers = request.headers || (request.headers = {}); - name = request.csrfTokenName || config.name; - token = request.csrfToken || config.token; - - if (token) { - headers[name] = token; - } - - return request; - } - }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +/** + * Applies a Cross-Site Request Forgery protection header to a request + * + * CSRF protection helps a server verify that a request came from a + * trusted client and not another client that was able to masquerade + * as an authorized client. Sites that use cookie based authentication + * are particularly vulnerable to request forgeries without extra + * protection. + * + * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery + * + * @param {Client} [client] client to wrap + * @param {string} [config.name='X-Csrf-Token'] name of the request + * header, may be overridden by `request.csrfTokenName` + * @param {string} [config.token] CSRF token, may be overridden by + * `request.csrfToken` + * + * @returns {Client} + */ +module.exports = interceptor({ + init: function (config) { + config.name = config.name || 'X-Csrf-Token'; + return config; + }, + request: function handleRequest(request, config) { + var headers, name, token; + + headers = request.headers || (request.headers = {}); + name = request.csrfTokenName || config.name; + token = request.csrfToken || config.token; + + if (token) { + headers[name] = token; + } + + return request; + } +}); diff --git a/interceptor/defaultRequest.js b/interceptor/defaultRequest.js index 5f69470..26c1aeb 100644 --- a/interceptor/defaultRequest.js +++ b/interceptor/defaultRequest.js @@ -1,79 +1,69 @@ /* - * Copyright 2013 the original author or authors + * Copyright 2013-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor, mixinUtil, defaulter; - var interceptor, mixinUtil, defaulter; +interceptor = require('../interceptor'); +mixinUtil = require('../util/mixin'); - interceptor = require('../interceptor'); - mixinUtil = require('../util/mixin'); +defaulter = (function () { - defaulter = (function () { + function mixin(prop, target, defaults) { + if (prop in target || prop in defaults) { + target[prop] = mixinUtil({}, defaults[prop], target[prop]); + } + } - function mixin(prop, target, defaults) { - if (prop in target || prop in defaults) { - target[prop] = mixinUtil({}, defaults[prop], target[prop]); - } - } + function copy(prop, target, defaults) { + if (prop in defaults && !(prop in target)) { + target[prop] = defaults[prop]; + } + } - function copy(prop, target, defaults) { - if (prop in defaults && !(prop in target)) { - target[prop] = defaults[prop]; - } - } + var mappings = { + method: copy, + path: copy, + params: mixin, + headers: mixin, + entity: copy, + mixin: mixin + }; - var mappings = { - method: copy, - path: copy, - params: mixin, - headers: mixin, - entity: copy, - mixin: mixin - }; + return function (target, defaults) { + for (var prop in mappings) { + /*jshint forin: false */ + mappings[prop](prop, target, defaults); + } + return target; + }; - return function (target, defaults) { - for (var prop in mappings) { - /*jshint forin: false */ - mappings[prop](prop, target, defaults); - } - return target; - }; +}()); - }()); - - /** - * Provide default values for a request. These values will be applied to the - * request if the request object does not already contain an explicit value. - * - * For 'params', 'headers', and 'mixin', individual values are mixed in with the - * request's values. The result is a new object representiing the combined - * request and config values. Neither input object is mutated. - * - * @param {Client} [client] client to wrap - * @param {string} [config.method] the default method - * @param {string} [config.path] the default path - * @param {Object} [config.params] the default params, mixed with the request's existing params - * @param {Object} [config.headers] the default headers, mixed with the request's existing headers - * @param {Object} [config.mixin] the default "mixins" (http/https options), mixed with the request's existing "mixins" - * - * @returns {Client} - */ - return interceptor({ - request: function handleRequest(request, config) { - return defaulter(request, config); - } - }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +/** + * Provide default values for a request. These values will be applied to the + * request if the request object does not already contain an explicit value. + * + * For 'params', 'headers', and 'mixin', individual values are mixed in with the + * request's values. The result is a new object representiing the combined + * request and config values. Neither input object is mutated. + * + * @param {Client} [client] client to wrap + * @param {string} [config.method] the default method + * @param {string} [config.path] the default path + * @param {Object} [config.params] the default params, mixed with the request's existing params + * @param {Object} [config.headers] the default headers, mixed with the request's existing headers + * @param {Object} [config.mixin] the default "mixins" (http/https options), mixed with the request's existing "mixins" + * + * @returns {Client} + */ +module.exports = interceptor({ + request: function handleRequest(request, config) { + return defaulter(request, config); + } +}); diff --git a/interceptor/entity.js b/interceptor/entity.js index 9b75d63..6b7a7bd 100644 --- a/interceptor/entity.js +++ b/interceptor/entity.js @@ -1,45 +1,35 @@ /* - * Copyright 2012-2014 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor; - var interceptor; +interceptor = require('../interceptor'); - interceptor = require('../interceptor'); +if (typeof console !== 'undefined') { + console.log('rest.js: rest/interceptor/entity is deprecated, please use response.entity() instead'); +} - if (typeof console !== 'undefined') { - console.log('rest.js: rest/interceptor/entity is deprecated, please use response.entity() instead'); +/** + * @deprecated use response.entity() instead + * + * Returns the response entity as the response, discarding other response + * properties. + * + * @param {Client} [client] client to wrap + * + * @returns {Client} + */ +module.exports = interceptor({ + response: function (response) { + if ('entity' in response) { + return response.entity; } - - /** - * @deprecated use response.entity() instead - * - * Returns the response entity as the response, discarding other response - * properties. - * - * @param {Client} [client] client to wrap - * - * @returns {Client} - */ - return interceptor({ - response: function (response) { - if ('entity' in response) { - return response.entity; - } - return response; - } - }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); + return response; + } +}); diff --git a/interceptor/errorCode.js b/interceptor/errorCode.js index a753e52..69437e0 100644 --- a/interceptor/errorCode.js +++ b/interceptor/errorCode.js @@ -1,47 +1,36 @@ /* - * Copyright 2012-2015 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor; - var interceptor, Promise; +interceptor = require('../interceptor'); - interceptor = require('../interceptor'); - Promise = require('../util/Promise'); - - /** - * Rejects the response promise based on the status code. - * - * Codes greater than or equal to the provided value are rejected. Default - * value 400. - * - * @param {Client} [client] client to wrap - * @param {number} [config.code=400] code to indicate a rejection - * - * @returns {Client} - */ - return interceptor({ - init: function (config) { - config.code = config.code || 400; - return config; - }, - response: function (response, config) { - if (response.status && response.status.code >= config.code) { - return Promise.reject(response); - } - return response; - } - }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +/** + * Rejects the response promise based on the status code. + * + * Codes greater than or equal to the provided value are rejected. Default + * value 400. + * + * @param {Client} [client] client to wrap + * @param {number} [config.code=400] code to indicate a rejection + * + * @returns {Client} + */ +module.exports = interceptor({ + init: function (config) { + config.code = config.code || 400; + return config; + }, + response: function (response, config) { + if (response.status && response.status.code >= config.code) { + return Promise.reject(response); + } + return response; + } +}); diff --git a/interceptor/hateoas.js b/interceptor/hateoas.js index e0d7b75..f6ed337 100644 --- a/interceptor/hateoas.js +++ b/interceptor/hateoas.js @@ -1,148 +1,138 @@ /* - * Copyright 2012-2013 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor, pathPrefix, rfc5988LinkParser, find; - var interceptor, pathPrefix, rfc5988LinkParser, find; +interceptor = require('../interceptor'); +pathPrefix = require('./pathPrefix'); +rfc5988LinkParser = require('../parsers/rfc5988'); +find = require('../util/find'); - interceptor = require('../interceptor'); - pathPrefix = require('./pathPrefix'); - rfc5988LinkParser = require('../parsers/rfc5988'); - find = require('../util/find'); - - /** - * [Experimental] - * - * Supports 'Hypertext As The Engine Of Application State' style - * services by indexing the 'links' property from the entity to make - * accessing links via the 'rel' attribute easier. - * - * Links are index in two ways: - * 1. as link's 'rel' which when accessed issues a request for the - * linked resource. A promise for the related resourse is expected - * to be returned. - * 2. as link's 'rel' with 'Link' appended, as a reference to the link - * object - * - * The 'Link' response header is also parsed for related resources - * following rfc5988. The values parsed from the headers are indexed - * into the response.links object. - * - * Also defines a 'clientFor' factory function that creates a new - * client configured to communicate with a related resource. - * - * The client for the resoruce reference and the 'clientFor' function - * can be provided by the 'client' config property. - * - * Index links are exposed by default on the entity. A child object may be - * configed by the 'target' config property. - * - * @param {Client} [client] client to wrap - * @param {string} [config.target=''] property to create on the entity and - * parse links into. If empty, the response entity is used directly. - * @param {Client} [config.client=request.originator] the parent client to - * use when creating clients for a linked resources. Defaults to the - * request's originator if available, otherwise the current interceptor's - * client - * - * @returns {Client} - */ - return interceptor({ - init: function (config) { - config.target = config.target || ''; - return config; - }, - response: function (response, config, meta) { - var client; - - client = config.client || (response.request && response.request.originator) || meta.client; - - function apply(target, links) { - links.forEach(function (link) { - Object.defineProperty(target, link.rel + 'Link', { - enumerable: false, - configurable: true, - value: link - }); +/** + * [Experimental] + * + * Supports 'Hypertext As The Engine Of Application State' style + * services by indexing the 'links' property from the entity to make + * accessing links via the 'rel' attribute easier. + * + * Links are index in two ways: + * 1. as link's 'rel' which when accessed issues a request for the + * linked resource. A promise for the related resourse is expected + * to be returned. + * 2. as link's 'rel' with 'Link' appended, as a reference to the link + * object + * + * The 'Link' response header is also parsed for related resources + * following rfc5988. The values parsed from the headers are indexed + * into the response.links object. + * + * Also defines a 'clientFor' factory function that creates a new + * client configured to communicate with a related resource. + * + * The client for the resoruce reference and the 'clientFor' function + * can be provided by the 'client' config property. + * + * Index links are exposed by default on the entity. A child object may be + * configed by the 'target' config property. + * + * @param {Client} [client] client to wrap + * @param {string} [config.target=''] property to create on the entity and + * parse links into. If empty, the response entity is used directly. + * @param {Client} [config.client=request.originator] the parent client to + * use when creating clients for a linked resources. Defaults to the + * request's originator if available, otherwise the current interceptor's + * client + * + * @returns {Client} + */ +module.exports = interceptor({ + init: function (config) { + config.target = config.target || ''; + return config; + }, + response: function (response, config, meta) { + var client; + + client = config.client || (response.request && response.request.originator) || meta.client; + + function apply(target, links) { + links.forEach(function (link) { + Object.defineProperty(target, link.rel + 'Link', { + enumerable: false, + configurable: true, + value: link + }); + Object.defineProperty(target, link.rel, { + enumerable: false, + configurable: true, + get: function () { + var response = client({ path: link.href }); Object.defineProperty(target, link.rel, { enumerable: false, configurable: true, - get: function () { - var response = client({ path: link.href }); - Object.defineProperty(target, link.rel, { - enumerable: false, - configurable: true, - value: response - }); - return response; - } + value: response }); - }); - - // if only Proxy was well supported... - Object.defineProperty(target, 'clientFor', { - enumerable: false, - value: function clientFor(rel, parentClient) { - return pathPrefix( - parentClient || client, - { prefix: target[rel + 'Link'].href } - ); - } - }); + return response; + } + }); + }); + + // if only Proxy was well supported... + Object.defineProperty(target, 'clientFor', { + enumerable: false, + value: function clientFor(rel, parentClient) { + return pathPrefix( + parentClient || client, + { prefix: target[rel + 'Link'].href } + ); } - - function parseLinkHeaders(headers) { - var links = []; - [].concat(headers).forEach(function (header) { - try { - links = links.concat(rfc5988LinkParser.parse(header)); - } - catch (e) { - // ignore - // TODO consider a debug mode that logs - } - }); - return links; + }); + } + + function parseLinkHeaders(headers) { + var links = []; + [].concat(headers).forEach(function (header) { + try { + links = links.concat(rfc5988LinkParser.parse(header)); } - - if (response.headers && response.headers.Link) { - response.links = response.links || {}; - apply(response.links, parseLinkHeaders(response.headers.Link)); + catch (e) { + // ignore + // TODO consider a debug mode that logs } + }); + return links; + } - find.findProperties(response.entity, 'links', function (obj, host) { - var target; + if (response.headers && response.headers.Link) { + response.links = response.links || {}; + apply(response.links, parseLinkHeaders(response.headers.Link)); + } - if (Array.isArray(host.links)) { - if (config.target === '') { - target = host; - } - else { - target = {}; - Object.defineProperty(host, config.target, { - enumerable: false, - value: target - }); - } + find.findProperties(response.entity, 'links', function (obj, host) { + var target; - apply(target, host.links); - } - }); + if (Array.isArray(host.links)) { + if (config.target === '') { + target = host; + } + else { + target = {}; + Object.defineProperty(host, config.target, { + enumerable: false, + value: target + }); + } - return response; + apply(target, host.links); } }); - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); + return response; + } +}); diff --git a/interceptor/ie/xdomain.js b/interceptor/ie/xdomain.js index 97872ee..98ba170 100644 --- a/interceptor/ie/xdomain.js +++ b/interceptor/ie/xdomain.js @@ -1,61 +1,50 @@ /* - * Copyright 2013 the original author or authors + * Copyright 2013-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define, global) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor, xdrClient, UrlBuilder, hasXdr, hasXhrCors; - var interceptor, xdrClient, UrlBuilder, hasXdr, hasXhrCors; +interceptor = require('../../interceptor'); +xdrClient = require('../../client/xdr'); +UrlBuilder = require('../../UrlBuilder'); - interceptor = require('../../interceptor'); - xdrClient = require('../../client/xdr'); - UrlBuilder = require('../../UrlBuilder'); +hasXdr = typeof XDomainRequest !== 'undefined'; +hasXhrCors = typeof XMLHttpRequest !== 'undefined' && 'withCredentials' in new XMLHttpRequest(); - hasXdr = 'XDomainRequest' in global; - hasXhrCors = global.XMLHttpRequest && 'withCredentials' in new global.XMLHttpRequest(); - - /** - * Apply IE 8 and 9's cross domain support if needed and available. - * - * XDR enabled cross-origin requests, but with sever restrictions. Please - * understand these restrictions before using this interceptor. For example: - * only GET and POST are supported, there is no response status code, there - * are no request or response headers except for the response Content-Type, - * the remote server must use the same scheme as the origin http-to-http - * https-to-https. - * - * http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx - * - * If needed, this interceptor should be installed as close to the - * interceptor chain root as possible. When the XDR client is needed, any - * other interceptors in the primary chain are skipped. It is possible to - * mimick the primary interceptor chain, by wrapping the XDR client in the - * same interceptors and providing the resulting client as the 'xdrClient' - * config property. - * - * @param {Client} [client] client to wrap - * @param {Client} [config.xdrClient] the client to use when XDR is needed, defaults to 'rest/client/xdr' - * - * @returns {Client} - */ - return interceptor({ - request: function handleRequest(request, config) { - if (hasXdr && !hasXhrCors && new UrlBuilder(request.path, request.params).isCrossOrigin()) { - return new interceptor.ComplexRequest({ request: request, client: config.xdrClient || xdrClient }); - } - return request; - } - }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }, - typeof window !== 'undefined' ? window : void 0 - // Boilerplate for AMD and Node -)); +/** + * Apply IE 8 and 9's cross domain support if needed and available. + * + * XDR enabled cross-origin requests, but with sever restrictions. Please + * understand these restrictions before using this interceptor. For example: + * only GET and POST are supported, there is no response status code, there + * are no request or response headers except for the response Content-Type, + * the remote server must use the same scheme as the origin http-to-http + * https-to-https. + * + * http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx + * + * If needed, this interceptor should be installed as close to the + * interceptor chain root as possible. When the XDR client is needed, any + * other interceptors in the primary chain are skipped. It is possible to + * mimick the primary interceptor chain, by wrapping the XDR client in the + * same interceptors and providing the resulting client as the 'xdrClient' + * config property. + * + * @param {Client} [client] client to wrap + * @param {Client} [config.xdrClient] the client to use when XDR is needed, defaults to 'rest/client/xdr' + * + * @returns {Client} + */ +module.exports = interceptor({ + request: function handleRequest(request, config) { + if (hasXdr && !hasXhrCors && new UrlBuilder(request.path, request.params).isCrossOrigin()) { + return new interceptor.ComplexRequest({ request: request, client: config.xdrClient || xdrClient }); + } + return request; + } +}); diff --git a/interceptor/ie/xhr.js b/interceptor/ie/xhr.js index 318e0b0..5a00992 100644 --- a/interceptor/ie/xhr.js +++ b/interceptor/ie/xhr.js @@ -1,68 +1,59 @@ /* - * Copyright 2013 the original author or authors + * Copyright 2013-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define, global) { - 'use strict'; +'use strict'; - define(function (require) { +/*global ActiveXObject */ - var interceptor, XMLHttpRequest; +var interceptor, XHR; - interceptor = require('../../interceptor'); +interceptor = require('../../interceptor'); - XMLHttpRequest = (function () { - // derived from https://github.com/cujojs/poly/blob/0.5.1/xhr.js - if (global.XMLHttpRequest) { - return global.XMLHttpRequest; - } +XHR = (function () { + // derived from https://github.com/cujojs/poly/blob/0.5.1/xhr.js + if (XMLHttpRequest) { + return XMLHttpRequest; + } - var progIds, xhr; + var progIds, xhr; - progIds = [ - 'Msxml2.XMLHTTP', - 'Microsoft.XMLHTTP', - 'Msxml2.XMLHTTP.4.0' - ]; + progIds = [ + 'Msxml2.XMLHTTP', + 'Microsoft.XMLHTTP', + 'Msxml2.XMLHTTP.4.0' + ]; - function tryCtor(progId) { - try { - /*jshint nonew:false */ - new global.ActiveXObject(progId); - xhr = function () { return new global.ActiveXObject(progId); }; - } - catch (ex) {} - } + function tryCtor(progId) { + try { + /*jshint nonew:false */ + new ActiveXObject(progId); + xhr = function () { return new ActiveXObject(progId); }; + } + catch (ex) {} + } - while (!xhr && progIds.length) { - tryCtor(progIds.shift()); - } + while (!xhr && progIds.length) { + tryCtor(progIds.shift()); + } - return xhr; - }()); + return xhr; +}()); - /** - * Defaults request.engine to XMLHttpRequest, or an appropriate ActiveX fall - * back - * - * @param {Client} [client] client to wrap - * - * @returns {Client} - */ - return interceptor({ - request: function handleRequest(request) { - request.engine = request.engine || XMLHttpRequest; - return request; - } - }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }, - typeof window !== 'undefined' ? window : void 0 - // Boilerplate for AMD and Node -)); +/** + * Defaults request.engine to XMLHttpRequest, or an appropriate ActiveX fall + * back + * + * @param {Client} [client] client to wrap + * + * @returns {Client} + */ +module.exports = interceptor({ + request: function handleRequest(request) { + request.engine = request.engine || XHR; + return request; + } +}); diff --git a/interceptor/jsonp.js b/interceptor/jsonp.js index 901ab54..9b38d5d 100644 --- a/interceptor/jsonp.js +++ b/interceptor/jsonp.js @@ -1,59 +1,49 @@ /* - * Copyright 2012-2013 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor, jsonpClient; - var interceptor, jsonpClient; +interceptor = require('../interceptor'); +jsonpClient = require('../client/jsonp'); - interceptor = require('../interceptor'); - jsonpClient = require('../client/jsonp'); - - /** - * Allows common configuration of JSONP clients. - * - * Values provided to this interceptor are added to the request, if the - * request dose not already contain the property. - * - * The rest/client/jsonp client is used by default instead of the - * common default client for the platform. - * - * @param {Client} [client=rest/client/jsonp] custom client to wrap - * @param {string} [config.callback.param] the parameter name for which the - * callback function name is the value - * @param {string} [config.callback.prefix] prefix for the callback function, - * as the callback is attached to the window object, a unique, unobtrusive - * prefix is desired - * @param {string} [request.callback.name=] pins the name of the - * callback function, useful for cases where the server doesn't allow - * custom callback names. Generally not recommended. - * - * @returns {Client} - */ - return interceptor({ - client: jsonpClient, - init: function (config) { - config.callback = config.callback || {}; - return config; - }, - request: function (request, config) { - request.callback = request.callback || {}; - request.callback.param = request.callback.param || config.callback.param; - request.callback.prefix = request.callback.prefix || config.callback.prefix; - request.callback.name = request.callback.name || config.callback.name; - return request; - } - }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +/** + * Allows common configuration of JSONP clients. + * + * Values provided to this interceptor are added to the request, if the + * request dose not already contain the property. + * + * The rest/client/jsonp client is used by default instead of the + * common default client for the platform. + * + * @param {Client} [client=rest/client/jsonp] custom client to wrap + * @param {string} [config.callback.param] the parameter name for which the + * callback function name is the value + * @param {string} [config.callback.prefix] prefix for the callback function, + * as the callback is attached to the window object, a unique, unobtrusive + * prefix is desired + * @param {string} [request.callback.name=] pins the name of the + * callback function, useful for cases where the server doesn't allow + * custom callback names. Generally not recommended. + * + * @returns {Client} + */ +module.exports = interceptor({ + client: jsonpClient, + init: function (config) { + config.callback = config.callback || {}; + return config; + }, + request: function (request, config) { + request.callback = request.callback || {}; + request.callback.param = request.callback.param || config.callback.param; + request.callback.prefix = request.callback.prefix || config.callback.prefix; + request.callback.name = request.callback.name || config.callback.name; + return request; + } +}); diff --git a/interceptor/location.js b/interceptor/location.js index ca9b9d7..6815fa5 100644 --- a/interceptor/location.js +++ b/interceptor/location.js @@ -1,61 +1,51 @@ /* - * Copyright 2012-2013 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor; - var interceptor; +interceptor = require('../interceptor'); - interceptor = require('../interceptor'); +function isRedirect(response, config) { + var matchesRedirectCode = config.code === 0 || (response.status && response.status.code >= config.code); + return response.headers && response.headers.Location && matchesRedirectCode; +} - function isRedirect(response, config) { - var matchesRedirectCode = config.code === 0 || (response.status && response.status.code >= config.code); - return response.headers && response.headers.Location && matchesRedirectCode; +/** + * Follows the Location header in a response, if present. The response + * returned is for the subsequent request. + * + * Most browsers will automatically follow HTTP 3xx redirects, however, + * they will not automatically follow 2xx locations. + * + * @param {Client} [client] client to wrap + * @param {Client} [config.client=request.originator] client to use for subsequent request + * + * @returns {Client} + */ +module.exports = interceptor({ + init: function (config) { + config.code = config.code || 0; + return config; + }, + success: function (response, config, client) { + var request; + + if (isRedirect(response, config)) { + request = response.request || {}; + client = (config.client || request.originator || client.skip()); + + return client({ + method: 'GET', + path: response.headers.Location + }); } - /** - * Follows the Location header in a response, if present. The response - * returned is for the subsequent request. - * - * Most browsers will automatically follow HTTP 3xx redirects, however, - * they will not automatically follow 2xx locations. - * - * @param {Client} [client] client to wrap - * @param {Client} [config.client=request.originator] client to use for subsequent request - * - * @returns {Client} - */ - return interceptor({ - init: function (config) { - config.code = config.code || 0; - return config; - }, - success: function (response, config, client) { - var request; - - if (isRedirect(response, config)) { - request = response.request || {}; - client = (config.client || request.originator || client.skip()); - - return client({ - method: 'GET', - path: response.headers.Location - }); - } - - return response; - } - }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); + return response; + } +}); diff --git a/interceptor/mime.js b/interceptor/mime.js index 5bd309b..73a8b0f 100644 --- a/interceptor/mime.js +++ b/interceptor/mime.js @@ -1,121 +1,109 @@ /* - * Copyright 2012-2015 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - var undef; +var interceptor, mime, registry, noopConverter, missingConverter, attempt; - define(function (require) { +interceptor = require('../interceptor'); +mime = require('../mime'); +registry = require('../mime/registry'); +attempt = require('../util/attempt'); - var interceptor, mime, registry, noopConverter, missingConverter, attempt; +noopConverter = { + read: function (obj) { return obj; }, + write: function (obj) { return obj; } +}; - interceptor = require('../interceptor'); - mime = require('../mime'); - registry = require('../mime/registry'); - attempt = require('../util/attempt'); +missingConverter = { + read: function () { throw 'No read method found on converter'; }, + write: function () { throw 'No write method found on converter'; } +}; - noopConverter = { - read: function (obj) { return obj; }, - write: function (obj) { return obj; } - }; - - missingConverter = { - read: function () { throw 'No read method found on converter'; }, - write: function () { throw 'No write method found on converter'; } - }; - - /** - * MIME type support for request and response entities. Entities are - * (de)serialized using the converter for the MIME type. - * - * Request entities are converted using the desired converter and the - * 'Accept' request header prefers this MIME. - * - * Response entities are converted based on the Content-Type response header. - * - * @param {Client} [client] client to wrap - * @param {string} [config.mime='text/plain'] MIME type to encode the request - * entity - * @param {string} [config.accept] Accept header for the request - * @param {Client} [config.client=] client passed to the - * converter, defaults to the client originating the request - * @param {Registry} [config.registry] MIME registry, defaults to the root - * registry - * @param {boolean} [config.permissive] Allow an unkown request MIME type - * - * @returns {Client} - */ - return interceptor({ - init: function (config) { - config.registry = config.registry || registry; - return config; - }, - request: function (request, config) { - var type, headers; - - headers = request.headers || (request.headers = {}); - type = mime.parse(headers['Content-Type'] || config.mime || 'text/plain'); - headers.Accept = headers.Accept || config.accept || type.raw + ', application/json;q=0.8, text/plain;q=0.5, */*;q=0.2'; - - if (!('entity' in request)) { +/** + * MIME type support for request and response entities. Entities are + * (de)serialized using the converter for the MIME type. + * + * Request entities are converted using the desired converter and the + * 'Accept' request header prefers this MIME. + * + * Response entities are converted based on the Content-Type response header. + * + * @param {Client} [client] client to wrap + * @param {string} [config.mime='text/plain'] MIME type to encode the request + * entity + * @param {string} [config.accept] Accept header for the request + * @param {Client} [config.client=] client passed to the + * converter, defaults to the client originating the request + * @param {Registry} [config.registry] MIME registry, defaults to the root + * registry + * @param {boolean} [config.permissive] Allow an unkown request MIME type + * + * @returns {Client} + */ +module.exports = interceptor({ + init: function (config) { + config.registry = config.registry || registry; + return config; + }, + request: function (request, config) { + var type, headers; + + headers = request.headers || (request.headers = {}); + type = mime.parse(headers['Content-Type'] || config.mime || 'text/plain'); + headers.Accept = headers.Accept || config.accept || type.raw + ', application/json;q=0.8, text/plain;q=0.5, */*;q=0.2'; + + if (!('entity' in request)) { + return request; + } + + headers['Content-Type'] = type.raw; + + return config.registry.lookup(type)['catch'](function () { + // failed to resolve converter + if (config.permissive) { + return noopConverter; + } + throw 'mime-unknown'; + }).then(function (converter) { + var client = config.client || request.originator, + write = converter.write || missingConverter.write; + + return attempt(write.bind(void 0, request.entity, { client: client, request: request, mime: type, registry: config.registry })) + ['catch'](function() { + throw 'mime-serialization'; + }) + .then(function(entity) { + request.entity = entity; return request; - } - - headers['Content-Type'] = type.raw; - - return config.registry.lookup(type)['catch'](function () { - // failed to resolve converter - if (config.permissive) { - return noopConverter; - } - throw 'mime-unknown'; - }).then(function (converter) { - var client = config.client || request.originator, - write = converter.write || missingConverter.write; - - return attempt(write.bind(undef, request.entity, { client: client, request: request, mime: type, registry: config.registry })) - ['catch'](function() { - throw 'mime-serialization'; - }) - .then(function(entity) { - request.entity = entity; - return request; - }); }); - }, - response: function (response, config) { - if (!(response.headers && response.headers['Content-Type'] && response.entity)) { + }); + }, + response: function (response, config) { + if (!(response.headers && response.headers['Content-Type'] && response.entity)) { + return response; + } + + var type = mime.parse(response.headers['Content-Type']); + + return config.registry.lookup(type)['catch'](function () { return noopConverter; }).then(function (converter) { + var client = config.client || response.request && response.request.originator, + read = converter.read || missingConverter.read; + + return attempt(read.bind(void 0, response.entity, { client: client, response: response, mime: type, registry: config.registry })) + ['catch'](function (e) { + response.error = 'mime-deserialization'; + response.cause = e; + throw response; + }) + .then(function (entity) { + response.entity = entity; return response; - } - - var type = mime.parse(response.headers['Content-Type']); - - return config.registry.lookup(type)['catch'](function () { return noopConverter; }).then(function (converter) { - var client = config.client || response.request && response.request.originator, - read = converter.read || missingConverter.read; - - return attempt(read.bind(undef, response.entity, { client: client, response: response, mime: type, registry: config.registry })) - ['catch'](function (e) { - response.error = 'mime-deserialization'; - response.cause = e; - throw response; - }) - .then(function (entity) { - response.entity = entity; - return response; - }); }); - } }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); + } +}); diff --git a/interceptor/oAuth.js b/interceptor/oAuth.js index 04e72fc..d461bf7 100644 --- a/interceptor/oAuth.js +++ b/interceptor/oAuth.js @@ -1,147 +1,135 @@ /* - * Copyright 2012-2015 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define, global) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor, UrlBuilder, pubsub; - var interceptor, UrlBuilder, pubsub, Promise; +interceptor = require('../interceptor'); +UrlBuilder = require('../UrlBuilder'); +pubsub = require('../util/pubsub'); - interceptor = require('../interceptor'); - UrlBuilder = require('../UrlBuilder'); - pubsub = require('../util/pubsub'); - Promise = require('../util/Promise'); +function defaultOAuthCallback(hash) { + var params, queryString, regex, m; - function defaultOAuthCallback(hash) { - var params, queryString, regex, m; + queryString = hash.indexOf('#') === 0 ? hash.substring(1) : hash; + params = {}; + regex = /([^&=]+)=([^&]*)/g; - queryString = hash.indexOf('#') === 0 ? hash.substring(1) : hash; - params = {}; - regex = /([^&=]+)=([^&]*)/g; + m = regex.exec(queryString); + do { + params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); + m = regex.exec(queryString); + } while (m); - m = regex.exec(queryString); - do { - params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); - m = regex.exec(queryString); - } while (m); + /*jshint camelcase:false */ + pubsub.publish(params.state, params.token_type + ' ' + params.access_token); +} - /*jshint camelcase:false */ - pubsub.publish(params.state, params.token_type + ' ' + params.access_token); - } +function defaultWindowStrategy(url) { + var w = window.open(url, '_blank', 'width=500,height=400'); + return function () { + w.close(); + }; +} - function defaultWindowStrategy(url) { - var w = window.open(url, '_blank', 'width=500,height=400'); - return function () { - w.close(); - }; - } +function authorize(config) { + var state, url, dismissWindow; - function authorize(config) { - var state, url, dismissWindow; + return new Promise(function (resolve) { - return new Promise(function (resolve) { + state = Math.random() * new Date().getTime(); + url = new UrlBuilder(config.authorizationUrlBase).build({ + 'response_type': 'token', + 'redirect_uri': config.redirectUrl, + 'client_id': config.clientId, + 'scope': config.scope, + 'state': state + }); - state = Math.random() * new Date().getTime(); - url = new UrlBuilder(config.authorizationUrlBase).build({ - 'response_type': 'token', - 'redirect_uri': config.redirectUrl, - 'client_id': config.clientId, - 'scope': config.scope, - 'state': state - }); + dismissWindow = config.windowStrategy(url); - dismissWindow = config.windowStrategy(url); + pubsub.subscribe(state, function (authorization) { + dismissWindow(); + resolve(authorization); + }); - pubsub.subscribe(state, function (authorization) { - dismissWindow(); - resolve(authorization); - }); + }); +} +/** + * OAuth implicit flow support + * + * Authorizes request with the OAuth authorization token. Tokens are + * requested from the authorization server as needed if there isn't a + * token, or the token is expired. + * + * A custom window strategy can be provided to replace the default popup + * window. The window strategy is a function that must accept a URL as an + * argument and returns a function to close and cleanup the window. A + * common custom strategy would be to use an iframe in a dialog. + * + * The callback function must be invoked when the authorization server + * redirects the browser back to the application. + * + * NOTE: Registering a handler to receive the redirect is required and + * outside the scope of this interceptor. The implementer must collect the + * URL fragment and pass it to the callback function on the 'opener', or + * 'parent' window. + * + * @param {Client} [target] client to wrap + * @param {string} [config.token] pre-configured authentication token + * @param {string} config.clientId OAuth clientId + * @param {string} config.scope OAuth scope + * @param {string} config.authorizationUrlBase URL of the authorization server + * @param {string} [config.redirectUrl] callback URL from the authorization server. Will be converted to a fully qualified, absolute URL, if needed. Default's to the window's location or base href. + * @param {Function} [config.windowStrategy] strategy for opening the authorization window, defaults to window.open + * @param {string} [config.oAuthCallbackName='oAuthCallback'] name to register the callback as in global scope + * @param {Function} [config.oAuthCallback] callback function to receive OAuth URL fragment + * + * @returns {Client} + */ +module.exports = interceptor({ + init: function (config) { + config.redirectUrl = new UrlBuilder(config.redirectUrl).fullyQualify().build(); + config.windowStrategy = config.windowStrategy || defaultWindowStrategy; + config.oAuthCallback = config.oAuthCallback || defaultOAuthCallback; + config.oAuthCallbackName = config.oAuthCallbackName || 'oAuthCallback'; + + window[config.oAuthCallbackName] = config.oAuthCallback; + + return config; + }, + request: function (request, config) { + request.headers = request.headers || {}; + + if (config.token) { + request.headers.Authorization = config.token; + return request; + } + else { + return authorize(config).then(function (authorization) { + request.headers.Authorization = config.token = authorization; + return request; }); } + }, + response: function (response, config, meta) { + if (response.status.code === 401) { + // token probably expired, reauthorize + return authorize(config).then(function (authorization) { + config.token = authorization; + return meta.client(response.request); + }); + } + else if (response.status.code === 403) { + return Promise.reject(response); + } - /** - * OAuth implicit flow support - * - * Authorizes request with the OAuth authorization token. Tokens are - * requested from the authorization server as needed if there isn't a - * token, or the token is expired. - * - * A custom window strategy can be provided to replace the default popup - * window. The window strategy is a function that must accept a URL as an - * argument and returns a function to close and cleanup the window. A - * common custom strategy would be to use an iframe in a dialog. - * - * The callback function must be invoked when the authorization server - * redirects the browser back to the application. - * - * NOTE: Registering a handler to receive the redirect is required and - * outside the scope of this interceptor. The implementer must collect the - * URL fragment and pass it to the callback function on the 'opener', or - * 'parent' window. - * - * @param {Client} [target] client to wrap - * @param {string} [config.token] pre-configured authentication token - * @param {string} config.clientId OAuth clientId - * @param {string} config.scope OAuth scope - * @param {string} config.authorizationUrlBase URL of the authorization server - * @param {string} [config.redirectUrl] callback URL from the authorization server. Will be converted to a fully qualified, absolute URL, if needed. Default's to the window's location or base href. - * @param {Function} [config.windowStrategy] strategy for opening the authorization window, defaults to window.open - * @param {string} [config.oAuthCallbackName='oAuthCallback'] name to register the callback as in global scope - * @param {Function} [config.oAuthCallback] callback function to receive OAuth URL fragment - * - * @returns {Client} - */ - return interceptor({ - init: function (config) { - config.redirectUrl = new UrlBuilder(config.redirectUrl).fullyQualify().build(); - config.windowStrategy = config.windowStrategy || defaultWindowStrategy; - config.oAuthCallback = config.oAuthCallback || defaultOAuthCallback; - config.oAuthCallbackName = config.oAuthCallbackName || 'oAuthCallback'; - - global[config.oAuthCallbackName] = config.oAuthCallback; - - return config; - }, - request: function (request, config) { - request.headers = request.headers || {}; - - if (config.token) { - request.headers.Authorization = config.token; - return request; - } - else { - return authorize(config).then(function (authorization) { - request.headers.Authorization = config.token = authorization; - return request; - }); - } - }, - response: function (response, config, meta) { - if (response.status.code === 401) { - // token probably expired, reauthorize - return authorize(config).then(function (authorization) { - config.token = authorization; - return meta.client(response.request); - }); - } - else if (response.status.code === 403) { - return Promise.reject(response); - } - - return response; - } - }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }, - typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : void 0 - // Boilerplate for AMD and Node -)); + return response; + } +}); diff --git a/interceptor/params.js b/interceptor/params.js index 898506f..82d69b6 100644 --- a/interceptor/params.js +++ b/interceptor/params.js @@ -5,53 +5,43 @@ * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor, UrlBuilder; - var interceptor, UrlBuilder; +interceptor = require('../interceptor'); +UrlBuilder = require('../UrlBuilder'); - interceptor = require('../interceptor'); - UrlBuilder = require('../UrlBuilder'); - - /** - * Applies request params to the path by token replacement - * - * Params not applied as a token are appended to the query string. Params - * are removed from the request object, as they have been consumed. - * - * @deprecated The template interceptor `rest/interceptor/template` is a - * much richer way to apply paramters to a template. This interceptor is - * available as a bridge to users who previousled depended on this - * functionality being available directly on clients. - * - * @param {Client} [client] client to wrap - * @param {Object} [config.params] default param values - * - * @returns {Client} - */ - return interceptor({ - init: function (config) { - config.params = config.params || {}; - return config; - }, - request: function (request, config) { - var path, params; - - path = request.path || ''; - params = request.params || {}; - - request.path = new UrlBuilder(path, config.params).append('', params).build(); - delete request.params; - - return request; - } - }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +/** + * Applies request params to the path by token replacement + * + * Params not applied as a token are appended to the query string. Params + * are removed from the request object, as they have been consumed. + * + * @deprecated The template interceptor `rest/interceptor/template` is a + * much richer way to apply paramters to a template. This interceptor is + * available as a bridge to users who previousled depended on this + * functionality being available directly on clients. + * + * @param {Client} [client] client to wrap + * @param {Object} [config.params] default param values + * + * @returns {Client} + */ +module.exports = interceptor({ + init: function (config) { + config.params = config.params || {}; + return config; + }, + request: function (request, config) { + var path, params; + + path = request.path || ''; + params = request.params || {}; + + request.path = new UrlBuilder(path, config.params).append('', params).build(); + delete request.params; + + return request; + } +}); diff --git a/interceptor/pathPrefix.js b/interceptor/pathPrefix.js index ac60d04..71f5247 100644 --- a/interceptor/pathPrefix.js +++ b/interceptor/pathPrefix.js @@ -1,59 +1,49 @@ /* - * Copyright 2012-2013 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor, UrlBuilder; - var interceptor, UrlBuilder; +interceptor = require('../interceptor'); +UrlBuilder = require('../UrlBuilder'); - interceptor = require('../interceptor'); - UrlBuilder = require('../UrlBuilder'); +function startsWith(str, prefix) { + return str.indexOf(prefix) === 0; +} - function startsWith(str, prefix) { - return str.indexOf(prefix) === 0; - } - - function endsWith(str, suffix) { - return str.lastIndexOf(suffix) + suffix.length === str.length; - } +function endsWith(str, suffix) { + return str.lastIndexOf(suffix) + suffix.length === str.length; +} - /** - * Prefixes the request path with a common value. - * - * @param {Client} [client] client to wrap - * @param {number} [config.prefix] path prefix - * - * @returns {Client} - */ - return interceptor({ - request: function (request, config) { - var path; - - if (config.prefix && !(new UrlBuilder(request.path).isFullyQualified())) { - path = config.prefix; - if (request.path) { - if (!endsWith(path, '/') && !startsWith(request.path, '/')) { - // add missing '/' between path sections - path += '/'; - } - path += request.path; - } - request.path = path; +/** + * Prefixes the request path with a common value. + * + * @param {Client} [client] client to wrap + * @param {number} [config.prefix] path prefix + * + * @returns {Client} + */ +module.exports = interceptor({ + request: function (request, config) { + var path; + + if (config.prefix && !(new UrlBuilder(request.path).isFullyQualified())) { + path = config.prefix; + if (request.path) { + if (!endsWith(path, '/') && !startsWith(request.path, '/')) { + // add missing '/' between path sections + path += '/'; } - - return request; + path += request.path; } - }); - - }); + request.path = path; + } -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); + return request; + } +}); diff --git a/interceptor/retry.js b/interceptor/retry.js index a56a762..90d47c6 100644 --- a/interceptor/retry.js +++ b/interceptor/retry.js @@ -1,60 +1,50 @@ /* - * Copyright 2012-2015 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Jeremy Grelle * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor, delay; - var interceptor, delay, Promise; +interceptor = require('../interceptor'); +delay = require('../util/delay'); - interceptor = require('../interceptor'); - delay = require('../util/delay'); - Promise = require('../util/Promise'); - - /** - * Retries a rejected request using an exponential backoff. - * - * Defaults to an initial interval of 100ms, a multiplier of 2, and no max interval. - * - * @param {Client} [client] client to wrap - * @param {number} [config.intial=100] initial interval in ms - * @param {number} [config.multiplier=2] interval multiplier - * @param {number} [config.max] max interval in ms - * - * @returns {Client} - */ - return interceptor({ - init: function (config) { - config.initial = config.initial || 100; - config.multiplier = config.multiplier || 2; - config.max = config.max || Infinity; - return config; - }, - error: function (response, config, meta) { - var request; - - request = response.request; - request.retry = request.retry || config.initial; - - return delay(request.retry, request).then(function (request) { - if (request.canceled) { - // cancel here in case client doesn't check canceled flag - return Promise.reject({ request: request, error: 'precanceled' }); - } - request.retry = Math.min(request.retry * config.multiplier, config.max); - return meta.client(request); - }); +/** + * Retries a rejected request using an exponential backoff. + * + * Defaults to an initial interval of 100ms, a multiplier of 2, and no max interval. + * + * @param {Client} [client] client to wrap + * @param {number} [config.intial=100] initial interval in ms + * @param {number} [config.multiplier=2] interval multiplier + * @param {number} [config.max] max interval in ms + * + * @returns {Client} + */ +module.exports = interceptor({ + init: function (config) { + config.initial = config.initial || 100; + config.multiplier = config.multiplier || 2; + config.max = config.max || Infinity; + return config; + }, + error: function (response, config, meta) { + var request; + + request = response.request; + request.retry = request.retry || config.initial; + + return delay(request.retry, request).then(function (request) { + if (request.canceled) { + // cancel here in case client doesn't check canceled flag + return Promise.reject({ request: request, error: 'precanceled' }); } + request.retry = Math.min(request.retry * config.multiplier, config.max); + return meta.client(request); }); - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); + } +}); diff --git a/interceptor/template.js b/interceptor/template.js index 6c5ee43..fad414b 100644 --- a/interceptor/template.js +++ b/interceptor/template.js @@ -1,56 +1,46 @@ /* - * Copyright 2015 the original author or authors + * Copyright 2015-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor, uriTemplate, mixin; - var interceptor, uriTemplate, mixin; +interceptor = require('../interceptor'); +uriTemplate = require('../util/uriTemplate'); +mixin = require('../util/mixin'); - interceptor = require('../interceptor'); - uriTemplate = require('../util/uriTemplate'); - mixin = require('../util/mixin'); - - /** - * Applies request params to the path as a URI Template - * - * Params are removed from the request object, as they have been consumed. - * - * @see https://tools.ietf.org/html/rfc6570 - * - * @param {Client} [client] client to wrap - * @param {Object} [config.params] default param values - * @param {string} [config.template] default template - * - * @returns {Client} - */ - return interceptor({ - init: function (config) { - config.params = config.params || {}; - config.template = config.template || ''; - return config; - }, - request: function (request, config) { - var template, params; - - template = request.path || config.template; - params = mixin({}, request.params, config.params); - - request.path = uriTemplate.expand(template, params); - delete request.params; - - return request; - } - }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +/** + * Applies request params to the path as a URI Template + * + * Params are removed from the request object, as they have been consumed. + * + * @see https://tools.ietf.org/html/rfc6570 + * + * @param {Client} [client] client to wrap + * @param {Object} [config.params] default param values + * @param {string} [config.template] default template + * + * @returns {Client} + */ +module.exports = interceptor({ + init: function (config) { + config.params = config.params || {}; + config.template = config.template || ''; + return config; + }, + request: function (request, config) { + var template, params; + + template = request.path || config.template; + params = mixin({}, request.params, config.params); + + request.path = uriTemplate.expand(template, params); + delete request.params; + + return request; + } +}); diff --git a/interceptor/timeout.js b/interceptor/timeout.js index 48083e2..85e4337 100644 --- a/interceptor/timeout.js +++ b/interceptor/timeout.js @@ -1,73 +1,62 @@ /* - * Copyright 2012-2015 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Jeremy Grelle * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var interceptor; - var interceptor, Promise; +interceptor = require('../interceptor'); - interceptor = require('../interceptor'); - Promise = require('../util/Promise'); - - /** - * Cancels a request if it takes longer then the timeout value. - * - * @param {Client} [client] client to wrap - * @param {number} [config.timeout=0] duration in milliseconds before canceling the request. Non-positive values disable the timeout - * @param {boolean} [config.transient=false] if true, timed out requests will not be marked as canceled so that it may be retried - * - * @returns {Client} - */ - return interceptor({ - init: function (config) { - config.timeout = config.timeout || 0; - config.transient = !!config.transient; - return config; - }, - request: function (request, config) { - var timeout, abort, triggerAbort, transient; - timeout = 'timeout' in request ? request.timeout : config.timeout; - transient = 'transient' in request ? request.transient : config.transient; - if (timeout <= 0) { - return request; - } - abort = new Promise(function (resolve, reject) { - triggerAbort = reject; - }); - this.timeout = setTimeout(function () { - triggerAbort({ request: request, error: 'timeout' }); - if (request.cancel) { - request.cancel(); - if (transient) { - // unmark request as canceled for future requests - request.canceled = false; - } - } - else if (!transient) { - request.canceled = true; - } - }, timeout); - return new interceptor.ComplexRequest({ request: request, abort: abort }); - }, - response: function (response) { - if (this.timeout) { - clearTimeout(this.timeout); - delete this.timeout; +/** + * Cancels a request if it takes longer then the timeout value. + * + * @param {Client} [client] client to wrap + * @param {number} [config.timeout=0] duration in milliseconds before canceling the request. Non-positive values disable the timeout + * @param {boolean} [config.transient=false] if true, timed out requests will not be marked as canceled so that it may be retried + * + * @returns {Client} + */ +module.exports = interceptor({ + init: function (config) { + config.timeout = config.timeout || 0; + config.transient = !!config.transient; + return config; + }, + request: function (request, config) { + var timeout, abort, triggerAbort, transient; + timeout = 'timeout' in request ? request.timeout : config.timeout; + transient = 'transient' in request ? request.transient : config.transient; + if (timeout <= 0) { + return request; + } + abort = new Promise(function (resolve, reject) { + triggerAbort = reject; + }); + this.timeout = setTimeout(function () { + triggerAbort({ request: request, error: 'timeout' }); + if (request.cancel) { + request.cancel(); + if (transient) { + // unmark request as canceled for future requests + request.canceled = false; } - return response; } - }); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); + else if (!transient) { + request.canceled = true; + } + }, timeout); + return new interceptor.ComplexRequest({ request: request, abort: abort }); + }, + response: function (response) { + if (this.timeout) { + clearTimeout(this.timeout); + delete this.timeout; + } + return response; + } +}); diff --git a/mime.js b/mime.js index 5f70588..fadd72c 100644 --- a/mime.js +++ b/mime.js @@ -1,53 +1,41 @@ /* -* Copyright 2014 the original author or authors +* Copyright 2014-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; - - var undef; - - define(function (/* require */) { - - /** - * Parse a MIME type into it's constituent parts - * - * @param {string} mime MIME type to parse - * @return {{ - * {string} raw the original MIME type - * {string} type the type and subtype - * {string} [suffix] mime suffix, including the plus, if any - * {Object} params key/value pair of attributes - * }} - */ - function parse(mime) { - var params, type; - - params = mime.split(';'); - type = params[0].trim().split('+'); - - return { - raw: mime, - type: type[0], - suffix: type[1] ? '+' + type[1] : '', - params: params.slice(1).reduce(function (params, pair) { - pair = pair.split('='); - params[pair[0].trim()] = pair[1] ? pair[1].trim() : undef; - return params; - }, {}) - }; - } - - return { - parse: parse - }; - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +'use strict'; + +/** + * Parse a MIME type into it's constituent parts + * + * @param {string} mime MIME type to parse + * @return {{ + * {string} raw the original MIME type + * {string} type the type and subtype + * {string} [suffix] mime suffix, including the plus, if any + * {Object} params key/value pair of attributes + * }} + */ +function parse(mime) { + var params, type; + + params = mime.split(';'); + type = params[0].trim().split('+'); + + return { + raw: mime, + type: type[0], + suffix: type[1] ? '+' + type[1] : '', + params: params.slice(1).reduce(function (params, pair) { + pair = pair.split('='); + params[pair[0].trim()] = pair[1] ? pair[1].trim() : void 0; + return params; + }, {}) + }; +} + +module.exports = { + parse: parse +}; diff --git a/mime/registry.js b/mime/registry.js index 72292dc..a87e8f0 100644 --- a/mime/registry.js +++ b/mime/registry.js @@ -1,115 +1,104 @@ /* - * Copyright 2012-2015 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; - - define(function (require) { - - var mime, Promise, registry; - - mime = require('../mime'); - Promise = require('../util/Promise'); - - function Registry(mimes) { - - /** - * Lookup the converter for a MIME type - * - * @param {string} type the MIME type - * @return a promise for the converter - */ - this.lookup = function lookup(type) { - var parsed; - - parsed = typeof type === 'string' ? mime.parse(type) : type; - - if (mimes[parsed.raw]) { - return mimes[parsed.raw]; - } - if (mimes[parsed.type + parsed.suffix]) { - return mimes[parsed.type + parsed.suffix]; - } - if (mimes[parsed.type]) { - return mimes[parsed.type]; - } - if (mimes[parsed.suffix]) { - return mimes[parsed.suffix]; - } - - return Promise.reject(new Error('Unable to locate converter for mime "' + parsed.raw + '"')); - }; - - /** - * Create a late dispatched proxy to the target converter. - * - * Common when a converter is registered under multiple names and - * should be kept in sync if updated. - * - * @param {string} type mime converter to dispatch to - * @returns converter whose read/write methods target the desired mime converter - */ - this.delegate = function delegate(type) { - return { - read: function () { - var args = arguments; - return this.lookup(type).then(function (converter) { - return converter.read.apply(this, args); - }.bind(this)); - }.bind(this), - write: function () { - var args = arguments; - return this.lookup(type).then(function (converter) { - return converter.write.apply(this, args); - }.bind(this)); - }.bind(this) - }; - }; - - /** - * Register a custom converter for a MIME type - * - * @param {string} type the MIME type - * @param converter the converter for the MIME type - * @return a promise for the converter - */ - this.register = function register(type, converter) { - mimes[type] = Promise.resolve(converter); - return mimes[type]; - }; - - /** - * Create a child registry whoes registered converters remain local, while - * able to lookup converters from its parent. - * - * @returns child MIME registry - */ - this.child = function child() { - return new Registry(Object.create(mimes)); - }; +'use strict'; - } +var mime, registry; - registry = new Registry({}); +mime = require('../mime'); - // include provided serializers - registry.register('application/hal', require('./type/application/hal')); - registry.register('application/json', require('./type/application/json')); - registry.register('application/x-www-form-urlencoded', require('./type/application/x-www-form-urlencoded')); - registry.register('multipart/form-data', require('./type/multipart/form-data')); - registry.register('text/plain', require('./type/text/plain')); +function Registry(mimes) { - registry.register('+json', registry.delegate('application/json')); + /** + * Lookup the converter for a MIME type + * + * @param {string} type the MIME type + * @return a promise for the converter + */ + this.lookup = function lookup(type) { + var parsed; - return registry; + parsed = typeof type === 'string' ? mime.parse(type) : type; - }); + if (mimes[parsed.raw]) { + return mimes[parsed.raw]; + } + if (mimes[parsed.type + parsed.suffix]) { + return mimes[parsed.type + parsed.suffix]; + } + if (mimes[parsed.type]) { + return mimes[parsed.type]; + } + if (mimes[parsed.suffix]) { + return mimes[parsed.suffix]; + } -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); + return Promise.reject(new Error('Unable to locate converter for mime "' + parsed.raw + '"')); + }; + + /** + * Create a late dispatched proxy to the target converter. + * + * Common when a converter is registered under multiple names and + * should be kept in sync if updated. + * + * @param {string} type mime converter to dispatch to + * @returns converter whose read/write methods target the desired mime converter + */ + this.delegate = function delegate(type) { + return { + read: function () { + var args = arguments; + return this.lookup(type).then(function (converter) { + return converter.read.apply(this, args); + }.bind(this)); + }.bind(this), + write: function () { + var args = arguments; + return this.lookup(type).then(function (converter) { + return converter.write.apply(this, args); + }.bind(this)); + }.bind(this) + }; + }; + + /** + * Register a custom converter for a MIME type + * + * @param {string} type the MIME type + * @param converter the converter for the MIME type + * @return a promise for the converter + */ + this.register = function register(type, converter) { + mimes[type] = Promise.resolve(converter); + return mimes[type]; + }; + + /** + * Create a child registry whoes registered converters remain local, while + * able to lookup converters from its parent. + * + * @returns child MIME registry + */ + this.child = function child() { + return new Registry(Object.create(mimes)); + }; + +} + +registry = new Registry({}); + +// include provided serializers +registry.register('application/hal', require('./type/application/hal')); +registry.register('application/json', require('./type/application/json')); +registry.register('application/x-www-form-urlencoded', require('./type/application/x-www-form-urlencoded')); +registry.register('multipart/form-data', require('./type/multipart/form-data')); +registry.register('text/plain', require('./type/text/plain')); + +registry.register('+json', registry.delegate('application/json')); + +module.exports = registry; diff --git a/mime/type/application/hal.js b/mime/type/application/hal.js index 08b55eb..4e11145 100644 --- a/mime/type/application/hal.js +++ b/mime/type/application/hal.js @@ -1,137 +1,128 @@ /* - * Copyright 2013-2015 the original author or authors + * Copyright 2013-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var pathPrefix, template, find, lazyPromise, responsePromise; - var pathPrefix, template, find, lazyPromise, responsePromise; +pathPrefix = require('../../../interceptor/pathPrefix'); +template = require('../../../interceptor/template'); +find = require('../../../util/find'); +lazyPromise = require('../../../util/lazyPromise'); +responsePromise = require('../../../util/responsePromise'); - pathPrefix = require('../../../interceptor/pathPrefix'); - template = require('../../../interceptor/template'); - find = require('../../../util/find'); - lazyPromise = require('../../../util/lazyPromise'); - responsePromise = require('../../../util/responsePromise'); - - function defineProperty(obj, name, value) { - Object.defineProperty(obj, name, { - value: value, - configurable: true, - enumerable: false, - writeable: true - }); - } +function defineProperty(obj, name, value) { + Object.defineProperty(obj, name, { + value: value, + configurable: true, + enumerable: false, + writeable: true + }); +} - /** - * Hypertext Application Language serializer - * - * Implemented to https://tools.ietf.org/html/draft-kelly-json-hal-06 - * - * As the spec is still a draft, this implementation will be updated as the - * spec evolves - * - * Objects are read as HAL indexing links and embedded objects on to the - * resource. Objects are written as plain JSON. - * - * Embedded relationships are indexed onto the resource by the relationship - * as a promise for the related resource. - * - * Links are indexed onto the resource as a lazy promise that will GET the - * resource when a handler is first registered on the promise. - * - * A `requestFor` method is added to the entity to make a request for the - * relationship. - * - * A `clientFor` method is added to the entity to get a full Client for a - * relationship. - * - * The `_links` and `_embedded` properties on the resource are made - * non-enumerable. - */ - return { +/** + * Hypertext Application Language serializer + * + * Implemented to https://tools.ietf.org/html/draft-kelly-json-hal-06 + * + * As the spec is still a draft, this implementation will be updated as the + * spec evolves + * + * Objects are read as HAL indexing links and embedded objects on to the + * resource. Objects are written as plain JSON. + * + * Embedded relationships are indexed onto the resource by the relationship + * as a promise for the related resource. + * + * Links are indexed onto the resource as a lazy promise that will GET the + * resource when a handler is first registered on the promise. + * + * A `requestFor` method is added to the entity to make a request for the + * relationship. + * + * A `clientFor` method is added to the entity to get a full Client for a + * relationship. + * + * The `_links` and `_embedded` properties on the resource are made + * non-enumerable. + */ +module.exports = { - read: function (str, opts) { - var client, console; + read: function (str, opts) { + var client, console; - opts = opts || {}; - client = opts.client; - console = opts.console || console; + opts = opts || {}; + client = opts.client; + console = opts.console || console; - function deprecationWarning(relationship, deprecation) { - if (deprecation && console && console.warn || console.log) { - (console.warn || console.log).call(console, 'Relationship \'' + relationship + '\' is deprecated, see ' + deprecation); - } - } + function deprecationWarning(relationship, deprecation) { + if (deprecation && console && console.warn || console.log) { + (console.warn || console.log).call(console, 'Relationship \'' + relationship + '\' is deprecated, see ' + deprecation); + } + } - return opts.registry.lookup(opts.mime.suffix).then(function (converter) { - return converter.read(str, opts); - }).then(function (root) { - find.findProperties(root, '_embedded', function (embedded, resource, name) { - Object.keys(embedded).forEach(function (relationship) { - if (relationship in resource) { return; } - var related = responsePromise({ - entity: embedded[relationship] - }); - defineProperty(resource, relationship, related); - }); - defineProperty(resource, name, embedded); + return opts.registry.lookup(opts.mime.suffix).then(function (converter) { + return converter.read(str, opts); + }).then(function (root) { + find.findProperties(root, '_embedded', function (embedded, resource, name) { + Object.keys(embedded).forEach(function (relationship) { + if (relationship in resource) { return; } + var related = responsePromise({ + entity: embedded[relationship] }); - find.findProperties(root, '_links', function (links, resource, name) { - Object.keys(links).forEach(function (relationship) { - var link = links[relationship]; - if (relationship in resource) { return; } - defineProperty(resource, relationship, responsePromise.make(lazyPromise(function () { - if (link.deprecation) { deprecationWarning(relationship, link.deprecation); } - if (link.templated === true) { - return template(client)({ path: link.href }); - } - return client({ path: link.href }); - }))); - }); - defineProperty(resource, name, links); - defineProperty(resource, 'clientFor', function (relationship, clientOverride) { - var link = links[relationship]; - if (!link) { - throw new Error('Unknown relationship: ' + relationship); - } - if (link.deprecation) { deprecationWarning(relationship, link.deprecation); } - if (link.templated === true) { - return template( - clientOverride || client, - { template: link.href } - ); - } - return pathPrefix( - clientOverride || client, - { prefix: link.href } - ); - }); - defineProperty(resource, 'requestFor', function (relationship, request, clientOverride) { - var client = this.clientFor(relationship, clientOverride); - return client(request); - }); - }); - - return root; + defineProperty(resource, relationship, related); + }); + defineProperty(resource, name, embedded); + }); + find.findProperties(root, '_links', function (links, resource, name) { + Object.keys(links).forEach(function (relationship) { + var link = links[relationship]; + if (relationship in resource) { return; } + defineProperty(resource, relationship, responsePromise.make(lazyPromise(function () { + if (link.deprecation) { deprecationWarning(relationship, link.deprecation); } + if (link.templated === true) { + return template(client)({ path: link.href }); + } + return client({ path: link.href }); + }))); + }); + defineProperty(resource, name, links); + defineProperty(resource, 'clientFor', function (relationship, clientOverride) { + var link = links[relationship]; + if (!link) { + throw new Error('Unknown relationship: ' + relationship); + } + if (link.deprecation) { deprecationWarning(relationship, link.deprecation); } + if (link.templated === true) { + return template( + clientOverride || client, + { template: link.href } + ); + } + return pathPrefix( + clientOverride || client, + { prefix: link.href } + ); }); + defineProperty(resource, 'requestFor', function (relationship, request, clientOverride) { + var client = this.clientFor(relationship, clientOverride); + return client(request); + }); + }); - }, + return root; + }); - write: function (obj, opts) { - return opts.registry.lookup(opts.mime.suffix).then(function (converter) { - return converter.write(obj, opts); - }); - } + }, - }; - }); + write: function (obj, opts) { + return opts.registry.lookup(opts.mime.suffix).then(function (converter) { + return converter.write(obj, opts); + }); + } -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +}; diff --git a/mime/type/application/json.js b/mime/type/application/json.js index 3f84136..e5eb32c 100644 --- a/mime/type/application/json.js +++ b/mime/type/application/json.js @@ -1,47 +1,37 @@ /* - * Copyright 2012-2015 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (/* require */) { - - /** - * Create a new JSON converter with custom reviver/replacer. - * - * The extended converter must be published to a MIME registry in order - * to be used. The existing converter will not be modified. - * - * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON - * - * @param {function} [reviver=undefined] custom JSON.parse reviver - * @param {function|Array} [replacer=undefined] custom JSON.stringify replacer - */ - function createConverter(reviver, replacer) { - return { - - read: function (str) { - return JSON.parse(str, reviver); - }, - - write: function (obj) { - return JSON.stringify(obj, replacer); - }, +/** + * Create a new JSON converter with custom reviver/replacer. + * + * The extended converter must be published to a MIME registry in order + * to be used. The existing converter will not be modified. + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON + * + * @param {function} [reviver=undefined] custom JSON.parse reviver + * @param {function|Array} [replacer=undefined] custom JSON.stringify replacer + */ +function createConverter(reviver, replacer) { + return { - extend: createConverter + read: function (str) { + return JSON.parse(str, reviver); + }, - }; - } + write: function (obj) { + return JSON.stringify(obj, replacer); + }, - return createConverter(); + extend: createConverter - }); + }; +} -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = createConverter(); diff --git a/mime/type/application/x-www-form-urlencoded.js b/mime/type/application/x-www-form-urlencoded.js index 382a766..96355f5 100644 --- a/mime/type/application/x-www-form-urlencoded.js +++ b/mime/type/application/x-www-form-urlencoded.js @@ -1,90 +1,81 @@ /* - * Copyright 2012 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (/* require */) { +var encodedSpaceRE, urlEncodedSpaceRE; - var encodedSpaceRE, urlEncodedSpaceRE; +encodedSpaceRE = /%20/g; +urlEncodedSpaceRE = /\+/g; - encodedSpaceRE = /%20/g; - urlEncodedSpaceRE = /\+/g; +function urlEncode(str) { + str = encodeURIComponent(str); + // spec says space should be encoded as '+' + return str.replace(encodedSpaceRE, '+'); +} - function urlEncode(str) { - str = encodeURIComponent(str); - // spec says space should be encoded as '+' - return str.replace(encodedSpaceRE, '+'); - } +function urlDecode(str) { + // spec says space should be encoded as '+' + str = str.replace(urlEncodedSpaceRE, ' '); + return decodeURIComponent(str); +} - function urlDecode(str) { - // spec says space should be encoded as '+' - str = str.replace(urlEncodedSpaceRE, ' '); - return decodeURIComponent(str); +function append(str, name, value) { + if (Array.isArray(value)) { + value.forEach(function (value) { + str = append(str, name, value); + }); + } + else { + if (str.length > 0) { + str += '&'; + } + str += urlEncode(name); + if (value !== undefined && value !== null) { + str += '=' + urlEncode(value); } + } + return str; +} + +module.exports = { - function append(str, name, value) { - if (Array.isArray(value)) { - value.forEach(function (value) { - str = append(str, name, value); - }); + read: function (str) { + var obj = {}; + str.split('&').forEach(function (entry) { + var pair, name, value; + pair = entry.split('='); + name = urlDecode(pair[0]); + if (pair.length === 2) { + value = urlDecode(pair[1]); } else { - if (str.length > 0) { - str += '&'; - } - str += urlEncode(name); - if (value !== undefined && value !== null) { - str += '=' + urlEncode(value); + value = null; + } + if (name in obj) { + if (!Array.isArray(obj[name])) { + // convert to an array, perserving currnent value + obj[name] = [obj[name]]; } + obj[name].push(value); } - return str; - } - - return { - - read: function (str) { - var obj = {}; - str.split('&').forEach(function (entry) { - var pair, name, value; - pair = entry.split('='); - name = urlDecode(pair[0]); - if (pair.length === 2) { - value = urlDecode(pair[1]); - } - else { - value = null; - } - if (name in obj) { - if (!Array.isArray(obj[name])) { - // convert to an array, perserving currnent value - obj[name] = [obj[name]]; - } - obj[name].push(value); - } - else { - obj[name] = value; - } - }); - return obj; - }, - - write: function (obj) { - var str = ''; - Object.keys(obj).forEach(function (name) { - str = append(str, name, obj[name]); - }); - return str; + else { + obj[name] = value; } + }); + return obj; + }, - }; - }); + write: function (obj) { + var str = ''; + Object.keys(obj).forEach(function (name) { + str = append(str, name, obj[name]); + }); + return str; + } -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +}; diff --git a/mime/type/multipart/form-data.js b/mime/type/multipart/form-data.js index f12302f..e0dcfbb 100644 --- a/mime/type/multipart/form-data.js +++ b/mime/type/multipart/form-data.js @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors + * Copyright 2014-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Michael Jackson @@ -7,67 +7,58 @@ /* global FormData, File, Blob */ -(function (define) { - 'use strict'; +'use strict'; - define(function (/* require */) { +function isFormElement(object) { + return object && + object.nodeType === 1 && // Node.ELEMENT_NODE + object.tagName === 'FORM'; +} - function isFormElement(object) { - return object && - object.nodeType === 1 && // Node.ELEMENT_NODE - object.tagName === 'FORM'; - } - - function createFormDataFromObject(object) { - var formData = new FormData(); +function createFormDataFromObject(object) { + var formData = new FormData(); - var value; - for (var property in object) { - if (object.hasOwnProperty(property)) { - value = object[property]; + var value; + for (var property in object) { + if (object.hasOwnProperty(property)) { + value = object[property]; - if (value instanceof File) { - formData.append(property, value, value.name); - } else if (value instanceof Blob) { - formData.append(property, value); - } else { - formData.append(property, String(value)); - } - } + if (value instanceof File) { + formData.append(property, value, value.name); + } else if (value instanceof Blob) { + formData.append(property, value); + } else { + formData.append(property, String(value)); } - - return formData; } + } - return { + return formData; +} - write: function (object) { - if (typeof FormData === 'undefined') { - throw new Error('The multipart/form-data mime serializer requires FormData support'); - } +module.exports = { - // Support FormData directly. - if (object instanceof FormData) { - return object; - } + write: function (object) { + if (typeof FormData === 'undefined') { + throw new Error('The multipart/form-data mime serializer requires FormData support'); + } - // Support
elements. - if (isFormElement(object)) { - return new FormData(object); - } + // Support FormData directly. + if (object instanceof FormData) { + return object; + } - // Support plain objects, may contain File/Blob as value. - if (typeof object === 'object' && object !== null) { - return createFormDataFromObject(object); - } + // Support elements. + if (isFormElement(object)) { + return new FormData(object); + } - throw new Error('Unable to create FormData from object ' + object); - } + // Support plain objects, may contain File/Blob as value. + if (typeof object === 'object' && object !== null) { + return createFormDataFromObject(object); + } - }; - }); + throw new Error('Unable to create FormData from object ' + object); + } -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +}; diff --git a/mime/type/text/plain.js b/mime/type/text/plain.js index 20c8cc0..6d41078 100644 --- a/mime/type/text/plain.js +++ b/mime/type/text/plain.js @@ -1,29 +1,20 @@ /* - * Copyright 2012 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (/* require */) { +module.exports = { - return { + read: function (str) { + return str; + }, - read: function (str) { - return str; - }, + write: function (obj) { + return obj.toString(); + } - write: function (obj) { - return obj.toString(); - } - - }; - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +}; diff --git a/node.js b/node.js index 6be8db7..047cd3a 100644 --- a/node.js +++ b/node.js @@ -1,25 +1,15 @@ /* - * Copyright 2014 the original author or authors + * Copyright 2014-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var rest = require('./client/default'), + node = require('./client/node'); - var rest = require('./client/default'), - node = require('./client/node'); +rest.setPlatformDefaultClient(node); - rest.setPlatformDefaultClient(node); - - return rest; - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = rest; diff --git a/parsers/_template.js b/parsers/_template.js deleted file mode 100644 index ce3f684..0000000 --- a/parsers/_template.js +++ /dev/null @@ -1,9 +0,0 @@ -(function (define) { - define(function (require, exports, module) { - -// pegjs output goes here - - }); -}( - typeof define === 'function' && define.amd ? define : function (factory) { factory(require, module.exports, module); } -)); diff --git a/parsers/rfc5988.js b/parsers/rfc5988.js index f3c5bae..e25d1de 100644 --- a/parsers/rfc5988.js +++ b/parsers/rfc5988.js @@ -1,13 +1,10 @@ -(function (define) { - define(function (require, exports, module) { - module.exports = (function(){ /* * Generated by PEG.js 0.7.0. * * http://pegjs.majda.cz/ */ - + function quote(s) { /* * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a @@ -30,7 +27,7 @@ module.exports = (function(){ .replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g, escape) + '"'; } - + var result = { /* * Parses the input with a generated parser. If the parsing is successfull, @@ -62,7 +59,7 @@ module.exports = (function(){ "QDText": parse_QDText, "QuotedPair": parse_QuotedPair }; - + if (startRule !== undefined) { if (parseFunctions[startRule] === undefined) { throw new Error("Invalid rule name: " + quote(startRule) + "."); @@ -70,28 +67,28 @@ module.exports = (function(){ } else { startRule = "start"; } - + var pos = 0; var reportFailures = 0; var rightmostFailuresPos = 0; var rightmostFailuresExpected = []; - + function padLeft(input, padding, length) { var result = input; - + var padLength = length - input.length; for (var i = 0; i < padLength; i++) { result = padding + result; } - + return result; } - + function escape(ch) { var charCode = ch.charCodeAt(0); var escapeChar; var length; - + if (charCode <= 0xFF) { escapeChar = 'x'; length = 2; @@ -99,27 +96,27 @@ module.exports = (function(){ escapeChar = 'u'; length = 4; } - + return '\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length); } - + function matchFailed(failure) { if (pos < rightmostFailuresPos) { return; } - + if (pos > rightmostFailuresPos) { rightmostFailuresPos = pos; rightmostFailuresExpected = []; } - + rightmostFailuresExpected.push(failure); } - + function parse_start() { var result0, result1, result2, result3, result4; var pos0, pos1, pos2, pos3; - + pos0 = pos; pos1 = pos; result0 = []; @@ -228,11 +225,11 @@ module.exports = (function(){ } return result0; } - + function parse_LinkValue() { var result0, result1, result2, result3, result4, result5; var pos0, pos1; - + pos0 = pos; pos1 = pos; if (input.charCodeAt(pos) === 60) { @@ -302,11 +299,11 @@ module.exports = (function(){ } return result0; } - + function parse_LinkParams() { var result0, result1, result2, result3; var pos0, pos1; - + pos0 = pos; pos1 = pos; if (input.charCodeAt(pos) === 59) { @@ -350,11 +347,11 @@ module.exports = (function(){ } return result0; } - + function parse_URIReference() { var result0, result1; var pos0; - + pos0 = pos; if (/^[^>]/.test(input.charAt(pos))) { result1 = input.charAt(pos); @@ -390,11 +387,11 @@ module.exports = (function(){ } return result0; } - + function parse_LinkParam() { var result0, result1; var pos0, pos1; - + pos0 = pos; pos1 = pos; result0 = parse_LinkParamName(); @@ -419,11 +416,11 @@ module.exports = (function(){ } return result0; } - + function parse_LinkParamName() { var result0, result1; var pos0; - + pos0 = pos; if (/^[a-z]/.test(input.charAt(pos))) { result1 = input.charAt(pos); @@ -459,11 +456,11 @@ module.exports = (function(){ } return result0; } - + function parse_LinkParamValue() { var result0, result1; var pos0, pos1; - + pos0 = pos; pos1 = pos; if (input.charCodeAt(pos) === 61) { @@ -498,11 +495,11 @@ module.exports = (function(){ } return result0; } - + function parse_PToken() { var result0, result1; var pos0; - + pos0 = pos; result1 = parse_PTokenChar(); if (result1 !== null) { @@ -522,10 +519,10 @@ module.exports = (function(){ } return result0; } - + function parse_PTokenChar() { var result0; - + if (input.charCodeAt(pos) === 33) { result0 = "!"; pos++; @@ -840,10 +837,10 @@ module.exports = (function(){ } return result0; } - + function parse_OptionalSP() { var result0, result1; - + result0 = []; result1 = parse_SP(); while (result1 !== null) { @@ -852,11 +849,11 @@ module.exports = (function(){ } return result0; } - + function parse_QuotedString() { var result0, result1, result2; var pos0, pos1; - + pos0 = pos; pos1 = pos; result0 = parse_DQ(); @@ -886,11 +883,11 @@ module.exports = (function(){ } return result0; } - + function parse_QuotedStringInternal() { var result0, result1; var pos0; - + pos0 = pos; result0 = []; result1 = parse_QDText(); @@ -912,10 +909,10 @@ module.exports = (function(){ } return result0; } - + function parse_Char() { var result0; - + if (/^[\0-]/.test(input.charAt(pos))) { result0 = input.charAt(pos); pos++; @@ -927,10 +924,10 @@ module.exports = (function(){ } return result0; } - + function parse_UpAlpha() { var result0; - + if (/^[A-Z]/.test(input.charAt(pos))) { result0 = input.charAt(pos); pos++; @@ -942,10 +939,10 @@ module.exports = (function(){ } return result0; } - + function parse_LoAlpha() { var result0; - + if (/^[a-z]/.test(input.charAt(pos))) { result0 = input.charAt(pos); pos++; @@ -957,20 +954,20 @@ module.exports = (function(){ } return result0; } - + function parse_Alpha() { var result0; - + result0 = parse_UpAlpha(); if (result0 === null) { result0 = parse_LoAlpha(); } return result0; } - + function parse_Digit() { var result0; - + if (/^[0-9]/.test(input.charAt(pos))) { result0 = input.charAt(pos); pos++; @@ -982,10 +979,10 @@ module.exports = (function(){ } return result0; } - + function parse_SP() { var result0; - + if (/^[ ]/.test(input.charAt(pos))) { result0 = input.charAt(pos); pos++; @@ -997,10 +994,10 @@ module.exports = (function(){ } return result0; } - + function parse_DQ() { var result0; - + if (/^["]/.test(input.charAt(pos))) { result0 = input.charAt(pos); pos++; @@ -1012,10 +1009,10 @@ module.exports = (function(){ } return result0; } - + function parse_QDText() { var result0; - + if (/^[^"]/.test(input.charAt(pos))) { result0 = input.charAt(pos); pos++; @@ -1027,11 +1024,11 @@ module.exports = (function(){ } return result0; } - + function parse_QuotedPair() { var result0, result1; var pos0; - + pos0 = pos; if (/^[\\]/.test(input.charAt(pos))) { result0 = input.charAt(pos); @@ -1056,11 +1053,11 @@ module.exports = (function(){ } return result0; } - - + + function cleanupExpected(expected) { expected.sort(); - + var lastExpected = null; var cleanExpected = []; for (var i = 0; i < expected.length; i++) { @@ -1071,7 +1068,7 @@ module.exports = (function(){ } return cleanExpected; } - + function computeErrorPosition() { /* * The first idea was to use |String.split| to break the input up to the @@ -1079,11 +1076,11 @@ module.exports = (function(){ * there. However IE's |split| implementation is so broken that it was * enough to prevent it. */ - + var line = 1; var column = 1; var seenCR = false; - + for (var i = 0; i < Math.max(pos, rightmostFailuresPos); i++) { var ch = input.charAt(i); if (ch === "\n") { @@ -1099,13 +1096,13 @@ module.exports = (function(){ seenCR = false; } } - + return { line: line, column: column }; } - - + + var result = parseFunctions[startRule](); - + /* * The parser is now in one of the following three states: * @@ -1134,7 +1131,7 @@ module.exports = (function(){ var offset = Math.max(pos, rightmostFailuresPos); var found = offset < input.length ? input.charAt(offset) : null; var errorPosition = computeErrorPosition(); - + throw new this.SyntaxError( cleanupExpected(rightmostFailuresExpected), found, @@ -1143,20 +1140,20 @@ module.exports = (function(){ errorPosition.column ); } - + return result; }, - + /* Returns the parser source code. */ toSource: function() { return this._source; } }; - + /* Thrown when a parser encounters a syntax error. */ - + result.SyntaxError = function(expected, found, offset, line, column) { function buildMessage(expected, found) { var expectedHumanized, foundHumanized; - + switch (expected.length) { case 0: expectedHumanized = "end of input"; @@ -1169,12 +1166,12 @@ module.exports = (function(){ + " or " + expected[expected.length - 1]; } - + foundHumanized = found ? quote(found) : "end of input"; - + return "Expected " + expectedHumanized + " but " + foundHumanized + " found."; } - + this.name = "SyntaxError"; this.expected = expected; this.found = found; @@ -1183,13 +1180,8 @@ module.exports = (function(){ this.line = line; this.column = column; }; - + result.SyntaxError.prototype = Error.prototype; - + return result; })(); - - }); -}( - typeof define === 'function' && define.amd ? define : function (factory) { factory(require, module.exports, module); } -)); diff --git a/rest.js b/rest.js index 7e9d098..defd7cf 100644 --- a/rest.js +++ b/rest.js @@ -1,24 +1,14 @@ /* - * Copyright 2012-2014 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +if (console) { + (console.warn || console.log).call(console, 'rest.js: The main module has moved, please switch your configuration to use \'rest/browser\' as the main module for browser applications.'); +} - if (console) { - (console.warn || console.log).call(console, 'rest.js: The main module has moved, please switch your configuration to use \'rest/browser\' as the main module for browser applications.'); - } - - return require('./browser'); - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = require('./browser'); diff --git a/test/UrlBuilder-test.js b/test/UrlBuilder-test.js index 53d229c..2dc549c 100644 --- a/test/UrlBuilder-test.js +++ b/test/UrlBuilder-test.js @@ -13,7 +13,7 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/UrlBuilder-test', function (require) { + define('rest-test/UrlBuilder-test', function (require) { var UrlBuilder = require('rest/UrlBuilder'); diff --git a/test/browser-test-browser.js b/test/browser-test-browser.js index 067d8d5..9689506 100644 --- a/test/browser-test-browser.js +++ b/test/browser-test-browser.js @@ -15,7 +15,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/browser-test', function (require) { + define('rest-test/browser-test', function (require) { var rest, defaultClient, xhr; diff --git a/test/client-test.js b/test/client-test.js index 7242eed..6fc16a3 100644 --- a/test/client-test.js +++ b/test/client-test.js @@ -15,7 +15,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/client-test', function (require) { + define('rest-test/client-test', function (require) { var client, rest, interceptor, defaultClient, skippableClient, defaultInterceptor; diff --git a/test/client/jsonp-test-browser.js b/test/client/jsonp-test-browser.js index 3ed4bca..0318f8e 100644 --- a/test/client/jsonp-test-browser.js +++ b/test/client/jsonp-test-browser.js @@ -15,7 +15,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/client/jsonp-test', function (require) { + define('rest-test/client/jsonp-test', function (require) { var client, jsonpInterceptor, rest; diff --git a/test/client/node-test-node.js b/test/client/node-test-node.js index f6ec54b..d1285dd 100644 --- a/test/client/node-test-node.js +++ b/test/client/node-test-node.js @@ -16,7 +16,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/client/node-test', function (require) { + define('rest-test/client/node-test', function (require) { var rest, client, http, https, fs, serverHttp, serverHttps; diff --git a/test/client/xdr-test-browser.js b/test/client/xdr-test-browser.js index 54503f0..b95fef4 100644 --- a/test/client/xdr-test-browser.js +++ b/test/client/xdr-test-browser.js @@ -15,7 +15,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/client/xdr-test', function (require) { + define('rest-test/client/xdr-test', function (require) { var client, rest, flickrUrl; @@ -26,7 +26,7 @@ buster.testCase('rest/client/xdr', { '': { - requiresSupportFor: { xdr: 'XDomainRequest' in window }, + requiresSupportFor: { xdr: typeof XDomainRequest !== 'undefined' }, 'should make a GET by default': function () { var request = { path: flickrUrl }; return client(request).then(function (response) { diff --git a/test/client/xhr-test-browser.js b/test/client/xhr-test-browser.js index 16c3316..a95f12c 100644 --- a/test/client/xhr-test-browser.js +++ b/test/client/xhr-test-browser.js @@ -15,7 +15,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/client/xhr-test', function (require) { + define('rest-test/client/xhr-test', function (require) { var xhr, rest, xhrFallback, when, client; diff --git a/test/curl-config.js b/test/curl-config.js index 4c0099d..3e7b82f 100644 --- a/test/curl-config.js +++ b/test/curl-config.js @@ -1,23 +1,20 @@ /* - * Copyright 2012-2014 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (global) { - 'use strict'; +'use strict'; - global.curl = { - packages: [ - { name: 'rest', location: './', main: 'browser' }, - { name: 'curl', location: 'node_modules/curl/src/curl', main: 'curl' }, - { name: 'poly', location: 'node_modules/poly', main: 'poly' }, - { name: 'when', location: 'node_modules/when', main: 'when' }, - { name: 'wire', location: 'node_modules/wire', main: 'wire' } - ], - // avoid poly/xhr as we need to test the case without it - preloads: ['poly/object', 'poly/string', 'poly/date', 'poly/array', 'poly/function', 'poly/json'] - }; - -}(this)); +window.curl = { + packages: [ + { name: 'rest', location: './', main: 'browser', config: { moduleLoader: 'curl/loader/cjsm11' } }, + { name: 'curl', location: 'node_modules/curl/src/curl', main: 'curl' }, + { name: 'poly', location: 'node_modules/poly', main: 'poly' }, + { name: 'when', location: 'node_modules/when', main: 'when' }, + { name: 'wire', location: 'node_modules/wire', main: 'wire' } + ], + // avoid poly/xhr as we need to test the case without it + preloads: ['poly/object', 'poly/string', 'poly/date', 'poly/array', 'poly/function', 'poly/json'] +}; diff --git a/test/interceptor-test.js b/test/interceptor-test.js index 0989e81..080e489 100644 --- a/test/interceptor-test.js +++ b/test/interceptor-test.js @@ -15,7 +15,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/interceptor-test', function (require) { + define('rest-test/interceptor-test', function (require) { var interceptor, rest, when; diff --git a/test/interceptor/basicAuth-test.js b/test/interceptor/basicAuth-test.js index 52b19e7..60a1188 100644 --- a/test/interceptor/basicAuth-test.js +++ b/test/interceptor/basicAuth-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/basicAuth-test', function (require) { + define('rest-test/interceptor/basicAuth-test', function (require) { var basicAuth, rest; diff --git a/test/interceptor/csrf-test.js b/test/interceptor/csrf-test.js index 5652b72..f37691c 100644 --- a/test/interceptor/csrf-test.js +++ b/test/interceptor/csrf-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/csrf-test', function (require) { + define('rest-test/interceptor/csrf-test', function (require) { var csrf, rest; diff --git a/test/interceptor/defaultRequest-test.js b/test/interceptor/defaultRequest-test.js index 6599982..2a53b8a 100644 --- a/test/interceptor/defaultRequest-test.js +++ b/test/interceptor/defaultRequest-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/defaultRequest-test', function (require) { + define('rest-test/interceptor/defaultRequest-test', function (require) { var defaultRequest, rest; diff --git a/test/interceptor/entity-test.js b/test/interceptor/entity-test.js index 4b75c21..ecc2b38 100644 --- a/test/interceptor/entity-test.js +++ b/test/interceptor/entity-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/entity-test', function (require) { + define('rest-test/interceptor/entity-test', function (require) { var entity, rest; diff --git a/test/interceptor/errorCode-test.js b/test/interceptor/errorCode-test.js index 40bd8ed..bddf733 100644 --- a/test/interceptor/errorCode-test.js +++ b/test/interceptor/errorCode-test.js @@ -15,7 +15,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/interceptor/errorCode-test', function (require) { + define('rest-test/interceptor/errorCode-test', function (require) { var errorCode, rest; diff --git a/test/interceptor/hateoas-test.js b/test/interceptor/hateoas-test.js index 67c192e..4c39e8b 100644 --- a/test/interceptor/hateoas-test.js +++ b/test/interceptor/hateoas-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/hateoas-test', function (require) { + define('rest-test/interceptor/hateoas-test', function (require) { var hateoas, rest, when, supports; diff --git a/test/interceptor/ie/xdomain-test-browser.js b/test/interceptor/ie/xdomain-test-browser.js index 5a2c1e0..649d420 100644 --- a/test/interceptor/ie/xdomain-test-browser.js +++ b/test/interceptor/ie/xdomain-test-browser.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/ie/xdomain-test', function (require) { + define('rest-test/interceptor/ie/xdomain-test', function (require) { var xdomain, rest, client, xdr, xhrCors; @@ -31,8 +31,8 @@ client = xdomain(defaultClient, { xdrClient: xdrClient }); - xdr = 'XDomainRequest' in window; - xhrCors = window.XMLHttpRequest && 'withCredentials' in new window.XMLHttpRequest(); + xdr = typeof XDomainRequest !== 'undefined'; + xhrCors = typeof XMLHttpRequest !== 'undefined' && 'withCredentials' in new XMLHttpRequest(); buster.testCase('rest/interceptor/ie/xdomain', { 'for XDomainRequest enabled browsers': { diff --git a/test/interceptor/ie/xhr-test-browser.js b/test/interceptor/ie/xhr-test-browser.js index c8701c9..e56d821 100644 --- a/test/interceptor/ie/xhr-test-browser.js +++ b/test/interceptor/ie/xhr-test-browser.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/ie/xhr-test', function (require) { + define('rest-test/interceptor/ie/xhr-test', function (require) { var xhr, rest; diff --git a/test/interceptor/jsonp-test.js b/test/interceptor/jsonp-test.js index 85b69ff..1c7a336 100644 --- a/test/interceptor/jsonp-test.js +++ b/test/interceptor/jsonp-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/jsonp-test', function (require) { + define('rest-test/interceptor/jsonp-test', function (require) { var jsonp, rest, jsonpClient, when; diff --git a/test/interceptor/location-test.js b/test/interceptor/location-test.js index 30ee06c..4da1f17 100644 --- a/test/interceptor/location-test.js +++ b/test/interceptor/location-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/location-test', function (require) { + define('rest-test/interceptor/location-test', function (require) { var location, rest; diff --git a/test/interceptor/mime-test.js b/test/interceptor/mime-test.js index c874df1..036b8de 100644 --- a/test/interceptor/mime-test.js +++ b/test/interceptor/mime-test.js @@ -15,7 +15,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/interceptor/mime-test', function (require) { + define('rest-test/interceptor/mime-test', function (require) { var mime, registry, rest, when; diff --git a/test/interceptor/oAuth-test.js b/test/interceptor/oAuth-test-browser.js similarity index 97% rename from test/interceptor/oAuth-test.js rename to test/interceptor/oAuth-test-browser.js index 6b679ad..aaf3131 100644 --- a/test/interceptor/oAuth-test.js +++ b/test/interceptor/oAuth-test-browser.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/oAuth-test', function (require) { + define('rest-test/interceptor/oAuth-test', function (require) { var oAuth, rest, pubsub; diff --git a/test/interceptor/params-test.js b/test/interceptor/params-test.js index 3198c99..2cc1d4b 100644 --- a/test/interceptor/params-test.js +++ b/test/interceptor/params-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/params-test', function (require) { + define('rest-test/interceptor/params-test', function (require) { var params, rest; diff --git a/test/interceptor/pathPrefix-test.js b/test/interceptor/pathPrefix-test.js index 36654bc..d6dee51 100644 --- a/test/interceptor/pathPrefix-test.js +++ b/test/interceptor/pathPrefix-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/pathPrefix-test', function (require) { + define('rest-test/interceptor/pathPrefix-test', function (require) { var pathPrefix, rest; diff --git a/test/interceptor/retry-test.js b/test/interceptor/retry-test.js index afbca3d..6dd071c 100644 --- a/test/interceptor/retry-test.js +++ b/test/interceptor/retry-test.js @@ -15,7 +15,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/interceptor/retry-test', function (require) { + define('rest-test/interceptor/retry-test', function (require) { var interceptor, retry, rest, when; diff --git a/test/interceptor/template-test.js b/test/interceptor/template-test.js index 6f70fd7..8796b7d 100644 --- a/test/interceptor/template-test.js +++ b/test/interceptor/template-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/interceptor/template-test', function (require) { + define('rest-test/interceptor/template-test', function (require) { var template, rest; diff --git a/test/interceptor/timeout-test.js b/test/interceptor/timeout-test.js index 8660fb7..61dc208 100644 --- a/test/interceptor/timeout-test.js +++ b/test/interceptor/timeout-test.js @@ -16,7 +16,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/interceptor/timeout-test', function (require) { + define('rest-test/interceptor/timeout-test', function (require) { var timeout, rest, when; diff --git a/test/mime-test.js b/test/mime-test.js index d3adc86..1d1e652 100644 --- a/test/mime-test.js +++ b/test/mime-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/mime-test', function (require) { + define('rest-test/mime-test', function (require) { var mime; diff --git a/test/mime/registry-test.js b/test/mime/registry-test.js index 66e4164..4607847 100644 --- a/test/mime/registry-test.js +++ b/test/mime/registry-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/mime/registry-test', function (require) { + define('rest-test/mime/registry-test', function (require) { var mimeRegistry, when, registry; diff --git a/test/mime/type/application/hal-test.js b/test/mime/type/application/hal-test.js index 0136301..72c6e07 100644 --- a/test/mime/type/application/hal-test.js +++ b/test/mime/type/application/hal-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/mime/type/application/hal-test', function (require) { + define('rest-test/mime/type/application/hal-test', function (require) { var hal, mime, registry, halMime, supports; diff --git a/test/mime/type/application/json-test.js b/test/mime/type/application/json-test.js index c1f6a74..a48218a 100644 --- a/test/mime/type/application/json-test.js +++ b/test/mime/type/application/json-test.js @@ -13,7 +13,7 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/mime/type/application/json-test', function (require) { + define('rest-test/mime/type/application/json-test', function (require) { var json = require('rest/mime/type/application/json'); diff --git a/test/mime/type/application/x-www-form-urlencoded-test.js b/test/mime/type/application/x-www-form-urlencoded-test.js index bdffcaa..67c1d5e 100644 --- a/test/mime/type/application/x-www-form-urlencoded-test.js +++ b/test/mime/type/application/x-www-form-urlencoded-test.js @@ -13,7 +13,7 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/mime/type/application/x-www-form-urlencoded-test', function (require) { + define('rest-test/mime/type/application/x-www-form-urlencoded-test', function (require) { var encodeder = require('rest/mime/type/application/x-www-form-urlencoded'); diff --git a/test/mime/type/multipart/form-data-test-browser.js b/test/mime/type/multipart/form-data-test-browser.js index f995898..6eedacc 100644 --- a/test/mime/type/multipart/form-data-test-browser.js +++ b/test/mime/type/multipart/form-data-test-browser.js @@ -13,12 +13,12 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/mime/type/multipart/form-data-test', function (require) { + define('rest-test/mime/type/multipart/form-data-test', function (require) { var encoder = require('rest/mime/type/multipart/form-data'); buster.testCase('rest/mime/type/multipart/form-data', { - requiresSupportFor: { FormData: 'FormData' in window }, + requiresSupportFor: { FormData: typeof FormData !== 'undefined' }, 'should pass a FormData object through unmodified': function () { var data = new FormData(); assert.same(encoder.write(data), data); diff --git a/test/mime/type/text/plain-test.js b/test/mime/type/text/plain-test.js index 6ed4da0..f4ce0f7 100644 --- a/test/mime/type/text/plain-test.js +++ b/test/mime/type/text/plain-test.js @@ -13,7 +13,7 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/mime/type/text/plain-test', function (require) { + define('rest-test/mime/type/text/plain-test', function (require) { var plain = require('rest/mime/type/text/plain'); diff --git a/test/node-test-node.js b/test/node-test-node.js index 7d07c27..acf53ef 100644 --- a/test/node-test-node.js +++ b/test/node-test-node.js @@ -15,7 +15,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/node-test', function (require) { + define('rest-test/node-test', function (require) { var rest, defaultClient, node; diff --git a/test/run.js b/test/run.js index d28787f..7e3d033 100644 --- a/test/run.js +++ b/test/run.js @@ -8,14 +8,14 @@ (function (buster, define) { 'use strict'; - define('rest/test/run', ['curl/_privileged', 'domReady!'], function (curl) { + define('rest-test/test/run', ['curl/_privileged', 'domReady!'], function (curl) { var modules = Object.keys(curl.cache).filter(function (moduleId) { - return moduleId.indexOf('-test') > 0; + return moduleId.match(/-test(-browser)?$/); }); buster.testRunner.timeout = 5000; - define('rest/test/run-faux', modules, function () { + define('rest-test/test/run-faux', modules, function () { buster.run(); }); diff --git a/test/util/attempt-test.js b/test/util/attempt-test.js index 28f4abd..28b8284 100644 --- a/test/util/attempt-test.js +++ b/test/util/attempt-test.js @@ -15,7 +15,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/util/attempt-test', function (require) { + define('rest-test/util/attempt-test', function (require) { var attempt = require('rest/util/attempt'); diff --git a/test/util/base64-test.js b/test/util/base64-test.js index 87cf884..6e1e345 100644 --- a/test/util/base64-test.js +++ b/test/util/base64-test.js @@ -13,7 +13,7 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/util/base64-test', function (require) { + define('rest-test/util/base64-test', function (require) { var base64 = require('rest/util/base64'); diff --git a/test/util/delay-test.js b/test/util/delay-test.js index 668af2e..ec5e8f4 100644 --- a/test/util/delay-test.js +++ b/test/util/delay-test.js @@ -15,10 +15,9 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/util/delay-test', function (require) { + define('rest-test/util/delay-test', function (require) { var delay = require('rest/util/delay'); - var Promise = require('rest/util/Promise'); buster.testCase('rest/util/delay', { 'delays promise resolution': function () { diff --git a/test/util/find-test.js b/test/util/find-test.js index 252f802..72da7d1 100644 --- a/test/util/find-test.js +++ b/test/util/find-test.js @@ -13,7 +13,7 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/util/find-test', function (require) { + define('rest-test/util/find-test', function (require) { var find = require('rest/util/find'); diff --git a/test/util/lazyPromise-test.js b/test/util/lazyPromise-test.js index e68de83..52889bb 100644 --- a/test/util/lazyPromise-test.js +++ b/test/util/lazyPromise-test.js @@ -14,7 +14,7 @@ refute = buster.assertions.refute; fail = buster.assertions.fail; - define('rest/util/lazyPromise-test', function (require) { + define('rest-test/util/lazyPromise-test', function (require) { var lazyPromise = require('rest/util/lazyPromise'); diff --git a/test/util/mixin-test.js b/test/util/mixin-test.js index 4d16da8..ee3d8d3 100644 --- a/test/util/mixin-test.js +++ b/test/util/mixin-test.js @@ -13,7 +13,7 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/util/mixin-test', function (require) { + define('rest-test/util/mixin-test', function (require) { var mixin = require('rest/util/mixin'); diff --git a/test/util/normalizeHeaderName-test.js b/test/util/normalizeHeaderName-test.js index 9f1004b..d5d0667 100644 --- a/test/util/normalizeHeaderName-test.js +++ b/test/util/normalizeHeaderName-test.js @@ -13,7 +13,7 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/util/normalizeHeaderName-test', function (require) { + define('rest-test/util/normalizeHeaderName-test', function (require) { var normalizeHeaderName = require('rest/util/normalizeHeaderName'); diff --git a/test/util/pubsub-test.js b/test/util/pubsub-test.js index 2eb89d7..49a28f6 100644 --- a/test/util/pubsub-test.js +++ b/test/util/pubsub-test.js @@ -13,7 +13,7 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/util/pubsub-test', function (require) { + define('rest-test/util/pubsub-test', function (require) { var pubsub = require('rest/util/pubsub'); diff --git a/test/util/responsePromise-test.js b/test/util/responsePromise-test.js index b2ef882..6aa2b1b 100644 --- a/test/util/responsePromise-test.js +++ b/test/util/responsePromise-test.js @@ -15,12 +15,11 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/util/responsePromise-test', function (require) { + define('rest-test/util/responsePromise-test', function (require) { - var responsePromise, Promise, mime, when, client; + var responsePromise, mime, when, client; responsePromise = require('rest/util/responsePromise'); - Promise = require('rest/util/Promise'); mime = require('rest/interceptor/mime'); when = require('when'); diff --git a/test/util/urlEncoder-test.js b/test/util/uriEncoder-test.js similarity index 99% rename from test/util/urlEncoder-test.js rename to test/util/uriEncoder-test.js index a3bd064..4c0dd67 100644 --- a/test/util/urlEncoder-test.js +++ b/test/util/uriEncoder-test.js @@ -13,7 +13,7 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/util/uriEncoder-test', function (require) { + define('rest-test/util/uriEncoder-test', function (require) { var uriEncoder, strings; diff --git a/test/util/uriTemplate-test.js b/test/util/uriTemplate-test.js index ea9195f..b4c358f 100644 --- a/test/util/uriTemplate-test.js +++ b/test/util/uriTemplate-test.js @@ -13,7 +13,7 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/util/uriTemplate-test', function (require) { + define('rest-test/util/uriTemplate-test', function (require) { var uriTemplate, params; diff --git a/test/version-test-node.js b/test/version-test-node.js index 5e0c813..fe63cdb 100644 --- a/test/version-test-node.js +++ b/test/version-test-node.js @@ -13,7 +13,7 @@ assert = buster.assertions.assert; refute = buster.assertions.refute; - define('rest/version-test', function (require) { + define('rest-test/version-test', function (require) { var bowerJson, packageJson; diff --git a/test/wire-test.js b/test/wire-test.js index bdcc8c1..fb56222 100644 --- a/test/wire-test.js +++ b/test/wire-test.js @@ -15,7 +15,7 @@ fail = buster.assertions.fail; failOnThrow = buster.assertions.failOnThrow; - define('rest/wire-test', function (require) { + define('rest-test/wire-test', function (require) { var rest, pathPrefixInterceptor, wire; diff --git a/util/Promise.js b/util/Promise.js index f1539f7..7955d6e 100644 --- a/util/Promise.js +++ b/util/Promise.js @@ -1,26 +1,13 @@ /* - * Copyright 2015 the original author or authors + * Copyright 2015-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - /*global Promise */ +/*global Promise */ - define(function (/* require */) { - if (typeof Promise !== 'function' && console && console.log) { - console.log('An ES6 Promise implementation is required to use rest.js. See https://github.com/cujojs/when/blob/master/docs/es6-promise-shim.md for using when.js as a Promise polyfill.'); - } - - return Promise; - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = Promise; diff --git a/util/attempt.js b/util/attempt.js index fb8ce63..56bb8c0 100644 --- a/util/attempt.js +++ b/util/attempt.js @@ -1,39 +1,27 @@ /* - * Copyright 2015 the original author or authors + * Copyright 2015-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { - - var Promise = require('./Promise'); - - /** - * Attempt to invoke a function capturing the resulting value as a Promise - * - * If the method throws, the caught value used to reject the Promise. - * - * @param {function} work function to invoke - * @returns {Promise} Promise for the output of the work function - */ - function attempt(work) { - try { - return Promise.resolve(work()); - } - catch (e) { - return Promise.reject(e); - } - } - - return attempt; - - }); +/** + * Attempt to invoke a function capturing the resulting value as a Promise + * + * If the method throws, the caught value used to reject the Promise. + * + * @param {function} work function to invoke + * @returns {Promise} Promise for the output of the work function + */ +function attempt(work) { + try { + return Promise.resolve(work()); + } + catch (e) { + return Promise.reject(e); + } +} -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = attempt; diff --git a/util/base64.js b/util/base64.js index 837161a..100b4ba 100644 --- a/util/base64.js +++ b/util/base64.js @@ -24,132 +24,123 @@ * Base 64 implementation in JavaScript * Original source available at https://raw.github.com/nzakas/computer-science-in-javascript/02a2745b4aa8214f2cae1bf0b15b447ca1a91b23/encodings/base64/base64.js * - * Converted to AMD and linter refinement by Scott Andrews + * Linter refinement by Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - /*jshint bitwise: false */ - define(function (/* require */) { +/*jshint bitwise: false */ - var digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; +var digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - /** - * Base64-encodes a string of text. - * - * @param {string} text The text to encode. - * @return {string} The base64-encoded string. - */ - function base64Encode(text) { - - if (/([^\u0000-\u00ff])/.test(text)) { - throw new Error('Can\'t base64 encode non-ASCII characters.'); - } - - var i = 0, - cur, prev, byteNum, - result = []; - - while (i < text.length) { +/** + * Base64-encodes a string of text. + * + * @param {string} text The text to encode. + * @return {string} The base64-encoded string. + */ +function base64Encode(text) { - cur = text.charCodeAt(i); - byteNum = i % 3; + if (/([^\u0000-\u00ff])/.test(text)) { + throw new Error('Can\'t base64 encode non-ASCII characters.'); + } - switch (byteNum) { - case 0: //first byte - result.push(digits.charAt(cur >> 2)); - break; + var i = 0, + cur, prev, byteNum, + result = []; - case 1: //second byte - result.push(digits.charAt((prev & 3) << 4 | (cur >> 4))); - break; + while (i < text.length) { - case 2: //third byte - result.push(digits.charAt((prev & 0x0f) << 2 | (cur >> 6))); - result.push(digits.charAt(cur & 0x3f)); - break; - } + cur = text.charCodeAt(i); + byteNum = i % 3; - prev = cur; - i += 1; - } + switch (byteNum) { + case 0: //first byte + result.push(digits.charAt(cur >> 2)); + break; - if (byteNum === 0) { - result.push(digits.charAt((prev & 3) << 4)); - result.push('=='); - } else if (byteNum === 1) { - result.push(digits.charAt((prev & 0x0f) << 2)); - result.push('='); - } + case 1: //second byte + result.push(digits.charAt((prev & 3) << 4 | (cur >> 4))); + break; - return result.join(''); + case 2: //third byte + result.push(digits.charAt((prev & 0x0f) << 2 | (cur >> 6))); + result.push(digits.charAt(cur & 0x3f)); + break; } - /** - * Base64-decodes a string of text. - * - * @param {string} text The text to decode. - * @return {string} The base64-decoded string. - */ - function base64Decode(text) { + prev = cur; + i += 1; + } - //ignore white space - text = text.replace(/\s/g, ''); + if (byteNum === 0) { + result.push(digits.charAt((prev & 3) << 4)); + result.push('=='); + } else if (byteNum === 1) { + result.push(digits.charAt((prev & 0x0f) << 2)); + result.push('='); + } - //first check for any unexpected input - if (!(/^[a-z0-9\+\/\s]+\={0,2}$/i.test(text)) || text.length % 4 > 0) { - throw new Error('Not a base64-encoded string.'); - } + return result.join(''); +} - //local variables - var cur, prev, digitNum, - i = 0, - result = []; +/** + * Base64-decodes a string of text. + * + * @param {string} text The text to decode. + * @return {string} The base64-decoded string. + */ +function base64Decode(text) { - //remove any equals signs - text = text.replace(/\=/g, ''); + //ignore white space + text = text.replace(/\s/g, ''); - //loop over each character - while (i < text.length) { + //first check for any unexpected input + if (!(/^[a-z0-9\+\/\s]+\={0,2}$/i.test(text)) || text.length % 4 > 0) { + throw new Error('Not a base64-encoded string.'); + } - cur = digits.indexOf(text.charAt(i)); - digitNum = i % 4; + //local variables + var cur, prev, digitNum, + i = 0, + result = []; - switch (digitNum) { + //remove any equals signs + text = text.replace(/\=/g, ''); - //case 0: first digit - do nothing, not enough info to work with + //loop over each character + while (i < text.length) { - case 1: //second digit - result.push(String.fromCharCode(prev << 2 | cur >> 4)); - break; + cur = digits.indexOf(text.charAt(i)); + digitNum = i % 4; - case 2: //third digit - result.push(String.fromCharCode((prev & 0x0f) << 4 | cur >> 2)); - break; + switch (digitNum) { - case 3: //fourth digit - result.push(String.fromCharCode((prev & 3) << 6 | cur)); - break; - } + //case 0: first digit - do nothing, not enough info to work with - prev = cur; - i += 1; - } + case 1: //second digit + result.push(String.fromCharCode(prev << 2 | cur >> 4)); + break; - //return a string - return result.join(''); + case 2: //third digit + result.push(String.fromCharCode((prev & 0x0f) << 4 | cur >> 2)); + break; + case 3: //fourth digit + result.push(String.fromCharCode((prev & 3) << 6 | cur)); + break; } - return { - encode: base64Encode, - decode: base64Decode - }; + prev = cur; + i += 1; + } + + //return a string + return result.join(''); - }); +} -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = { + encode: base64Encode, + decode: base64Decode +}; diff --git a/util/delay.js b/util/delay.js index 80662b4..51e734a 100644 --- a/util/delay.js +++ b/util/delay.js @@ -1,42 +1,30 @@ /* - * Copyright 2015 the original author or authors + * Copyright 2015-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; - - define(function (require) { - - var Promise = require('./Promise'); - - /** - * Delay the resolution of a promise - * - * Note: if the value is a promise, the delay starts once the promise - * resolves. - * - * @param {number} wait miliseconds to wait - * @param {Promise|*} [promiseOrValue] value to resolve with - * @returns {Promise} delayed Promise containing the value - */ - function delay(wait, promiseOrValue) { - return Promise.resolve(promiseOrValue).then(function (value) { - return new Promise(function (resolve) { - setTimeout(function () { - resolve(value); - }, wait); - }); - }); - } - - return delay; +'use strict'; +/** + * Delay the resolution of a promise + * + * Note: if the value is a promise, the delay starts once the promise + * resolves. + * + * @param {number} wait miliseconds to wait + * @param {Promise|*} [promiseOrValue] value to resolve with + * @returns {Promise} delayed Promise containing the value + */ +function delay(wait, promiseOrValue) { + return Promise.resolve(promiseOrValue).then(function (value) { + return new Promise(function (resolve) { + setTimeout(function () { + resolve(value); + }, wait); + }); }); +} -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = delay; diff --git a/util/find.js b/util/find.js index 83a1e2f..e403b71 100644 --- a/util/find.js +++ b/util/find.js @@ -1,41 +1,31 @@ /* - * Copyright 2013 the original author or authors + * Copyright 2013-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (/* require */) { +module.exports = { - return { + /** + * Find objects within a graph the contain a property of a certain name. + * + * NOTE: this method will not discover object graph cycles. + * + * @param {*} obj object to search on + * @param {string} prop name of the property to search for + * @param {Function} callback function to receive the found properties and their parent + */ + findProperties: function findProperties(obj, prop, callback) { + if (typeof obj !== 'object' || obj === null) { return; } + if (prop in obj) { + callback(obj[prop], obj, prop); + } + Object.keys(obj).forEach(function (key) { + findProperties(obj[key], prop, callback); + }); + } - /** - * Find objects within a graph the contain a property of a certain name. - * - * NOTE: this method will not discover object graph cycles. - * - * @param {*} obj object to search on - * @param {string} prop name of the property to search for - * @param {Function} callback function to receive the found properties and their parent - */ - findProperties: function findProperties(obj, prop, callback) { - if (typeof obj !== 'object' || obj === null) { return; } - if (prop in obj) { - callback(obj[prop], obj, prop); - } - Object.keys(obj).forEach(function (key) { - findProperties(obj[key], prop, callback); - }); - } - - }; - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +}; diff --git a/util/lazyPromise.js b/util/lazyPromise.js index 917b5e3..1f1cbed 100644 --- a/util/lazyPromise.js +++ b/util/lazyPromise.js @@ -1,57 +1,46 @@ /* - * Copyright 2013-2015 the original author or authors + * Copyright 2013-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; - - define(function (require) { - - var Promise = require('./Promise'), - attempt = require('./attempt'); - - /** - * Create a promise whose work is started only when a handler is registered. - * - * The work function will be invoked at most once. Thrown values will result - * in promise rejection. - * - * @param {Function} work function whose ouput is used to resolve the - * returned promise. - * @returns {Promise} a lazy promise - */ - function lazyPromise(work) { - var started, resolver, promise, then; - - started = false; - - promise = new Promise(function (resolve, reject) { - resolver = { - resolve: resolve, - reject: reject - }; - }); - then = promise.then; - - promise.then = function () { - if (!started) { - started = true; - attempt(work).then(resolver.resolve, resolver.reject); - } - return then.apply(promise, arguments); - }; - - return promise; - } +'use strict'; + +var attempt = require('./attempt'); - return lazyPromise; +/** + * Create a promise whose work is started only when a handler is registered. + * + * The work function will be invoked at most once. Thrown values will result + * in promise rejection. + * + * @param {Function} work function whose ouput is used to resolve the + * returned promise. + * @returns {Promise} a lazy promise + */ +function lazyPromise(work) { + var started, resolver, promise, then; + started = false; + + promise = new Promise(function (resolve, reject) { + resolver = { + resolve: resolve, + reject: reject + }; }); + then = promise.then; + + promise.then = function () { + if (!started) { + started = true; + attempt(work).then(resolver.resolve, resolver.reject); + } + return then.apply(promise, arguments); + }; + + return promise; +} -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = lazyPromise; diff --git a/util/mixin.js b/util/mixin.js index 6bf5588..3c76da0 100644 --- a/util/mixin.js +++ b/util/mixin.js @@ -1,48 +1,37 @@ /* - * Copyright 2012-2013 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - // derived from dojo.mixin - define(function (/* require */) { +var empty = {}; - var empty = {}; - - /** - * Mix the properties from the source object into the destination object. - * When the same property occurs in more then one object, the right most - * value wins. - * - * @param {Object} dest the object to copy properties to - * @param {Object} sources the objects to copy properties from. May be 1 to N arguments, but not an Array. - * @return {Object} the destination object - */ - function mixin(dest /*, sources... */) { - var i, l, source, name; - - if (!dest) { dest = {}; } - for (i = 1, l = arguments.length; i < l; i += 1) { - source = arguments[i]; - for (name in source) { - if (!(name in dest) || (dest[name] !== source[name] && (!(name in empty) || empty[name] !== source[name]))) { - dest[name] = source[name]; - } - } +/** + * Mix the properties from the source object into the destination object. + * When the same property occurs in more then one object, the right most + * value wins. + * + * @param {Object} dest the object to copy properties to + * @param {Object} sources the objects to copy properties from. May be 1 to N arguments, but not an Array. + * @return {Object} the destination object + */ +function mixin(dest /*, sources... */) { + var i, l, source, name; + + if (!dest) { dest = {}; } + for (i = 1, l = arguments.length; i < l; i += 1) { + source = arguments[i]; + for (name in source) { + if (!(name in dest) || (dest[name] !== source[name] && (!(name in empty) || empty[name] !== source[name]))) { + dest[name] = source[name]; } - - return dest; // Object } + } - return mixin; - - }); + return dest; // Object +} -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = mixin; diff --git a/util/normalizeHeaderName.js b/util/normalizeHeaderName.js index edacf16..dbc918a 100644 --- a/util/normalizeHeaderName.js +++ b/util/normalizeHeaderName.js @@ -1,38 +1,28 @@ /* - * Copyright 2012 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (/* require */) { - - /** - * Normalize HTTP header names using the pseudo camel case. - * - * For example: - * content-type -> Content-Type - * accepts -> Accepts - * x-custom-header-name -> X-Custom-Header-Name - * - * @param {string} name the raw header name - * @return {string} the normalized header name - */ - function normalizeHeaderName(name) { - return name.toLowerCase() - .split('-') - .map(function (chunk) { return chunk.charAt(0).toUpperCase() + chunk.slice(1); }) - .join('-'); - } - - return normalizeHeaderName; - - }); +/** + * Normalize HTTP header names using the pseudo camel case. + * + * For example: + * content-type -> Content-Type + * accepts -> Accepts + * x-custom-header-name -> X-Custom-Header-Name + * + * @param {string} name the raw header name + * @return {string} the normalized header name + */ +function normalizeHeaderName(name) { + return name.toLowerCase() + .split('-') + .map(function (chunk) { return chunk.charAt(0).toUpperCase() + chunk.slice(1); }) + .join('-'); +} -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = normalizeHeaderName; diff --git a/util/pubsub.js b/util/pubsub.js index b8a2793..06bb91b 100644 --- a/util/pubsub.js +++ b/util/pubsub.js @@ -1,55 +1,45 @@ /* - * Copyright 2012 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (/* require */) { +// A poor man's pub-sub. A single listener is supported per topic. When +// the topic is published, the listener is unsubscribed. - // A poor man's pub-sub. A single listener is supported per topic. When - // the topic is published, the listener is unsubscribed. +var topics = {}; - var topics = {}; - - /** - * Publishes the message to the topic, invoking the listener. - * - * The listener is unsubscribed from the topic after receiving a message. - * - * @param {string} topic the topic to publish to - * @param {Object} message message to publish - */ - function publish(topic /* , message... */) { - if (!topics[topic]) { return; } - topics[topic].apply({}, Array.prototype.slice.call(arguments, 1)); - // auto cleanup - delete topics[topic]; - } - - /** - * Register a callback function to receive notification of a message published to the topic. - * - * Any existing callback for the topic will be unsubscribed. - * - * @param {string} topic the topic to listen on - * @param {Function} callback the callback to receive the message published to the topic - */ - function subscribe(topic, callback) { - topics[topic] = callback; - } - - return { - publish: publish, - subscribe: subscribe - }; - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +/** + * Publishes the message to the topic, invoking the listener. + * + * The listener is unsubscribed from the topic after receiving a message. + * + * @param {string} topic the topic to publish to + * @param {Object} message message to publish + */ +function publish(topic /* , message... */) { + if (!topics[topic]) { return; } + topics[topic].apply({}, Array.prototype.slice.call(arguments, 1)); + // auto cleanup + delete topics[topic]; +} + +/** + * Register a callback function to receive notification of a message published to the topic. + * + * Any existing callback for the topic will be unsubscribed. + * + * @param {string} topic the topic to listen on + * @param {Function} callback the callback to receive the message published to the topic + */ +function subscribe(topic, callback) { + topics[topic] = callback; +} + +module.exports = { + publish: publish, + subscribe: subscribe +}; diff --git a/util/responsePromise.js b/util/responsePromise.js index 95aa4bc..766e180 100644 --- a/util/responsePromise.js +++ b/util/responsePromise.js @@ -1,143 +1,134 @@ /* - * Copyright 2014-2015 the original author or authors + * Copyright 2014-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +/*jshint latedef: nofunc */ - var Promise = require('./Promise'), - normalizeHeaderName = require('./normalizeHeaderName'); +var normalizeHeaderName = require('./normalizeHeaderName'); - function property(promise, name) { - return promise.then( - function (value) { - return value && value[name]; - }, - function (value) { - return Promise.reject(value && value[name]); - } - ); +function property(promise, name) { + return promise.then( + function (value) { + return value && value[name]; + }, + function (value) { + return Promise.reject(value && value[name]); } + ); +} - /** - * Obtain the response entity - * - * @returns {Promise} for the response entity - */ - function entity() { - /*jshint validthis:true */ - return property(this, 'entity'); - } - - /** - * Obtain the response status - * - * @returns {Promise} for the response status - */ - function status() { - /*jshint validthis:true */ - return property(property(this, 'status'), 'code'); - } - - /** - * Obtain the response headers map - * - * @returns {Promise} for the response headers map - */ - function headers() { - /*jshint validthis:true */ - return property(this, 'headers'); - } - - /** - * Obtain a specific response header - * - * @param {String} headerName the header to retrieve - * @returns {Promise} for the response header's value - */ - function header(headerName) { - /*jshint validthis:true */ - headerName = normalizeHeaderName(headerName); - return property(this.headers(), headerName); - } - - /** - * Follow a related resource - * - * The relationship to follow may be define as a plain string, an object - * with the rel and params, or an array containing one or more entries - * with the previous forms. - * - * Examples: - * response.follow('next') - * - * response.follow({ rel: 'next', params: { pageSize: 100 } }) - * - * response.follow([ - * { rel: 'items', params: { projection: 'noImages' } }, - * 'search', - * { rel: 'findByGalleryIsNull', params: { projection: 'noImages' } }, - * 'items' - * ]) - * - * @param {String|Object|Array} rels one, or more, relationships to follow - * @returns ResponsePromise related resource - */ - function follow(rels) { - /*jshint validthis:true */ - rels = [].concat(rels); - - return make(rels.reduce(function (response, rel) { - return response.then(function (response) { - if (typeof rel === 'string') { - rel = { rel: rel }; - } - if (typeof response.entity.clientFor !== 'function') { - throw new Error('Hypermedia response expected'); - } - var client = response.entity.clientFor(rel.rel); - return client({ params: rel.params }); - }); - }, this)); - } - - /** - * Wrap a Promise as an ResponsePromise - * - * @param {Promise} promise the promise for an HTTP Response - * @returns {ResponsePromise} wrapped promise for Response with additional helper methods - */ - function make(promise) { - promise.status = status; - promise.headers = headers; - promise.header = header; - promise.entity = entity; - promise.follow = follow; - return promise; - } - - function responsePromise(obj, callback, errback) { - return make(Promise.resolve(obj).then(callback, errback)); - } - - responsePromise.make = make; - responsePromise.reject = function (val) { - return make(Promise.reject(val)); - }; - responsePromise.promise = function (func) { - return make(new Promise(func)); - }; +/** + * Obtain the response entity + * + * @returns {Promise} for the response entity + */ +function entity() { + /*jshint validthis:true */ + return property(this, 'entity'); +} - return responsePromise; +/** + * Obtain the response status + * + * @returns {Promise} for the response status + */ +function status() { + /*jshint validthis:true */ + return property(property(this, 'status'), 'code'); +} - }); +/** + * Obtain the response headers map + * + * @returns {Promise} for the response headers map + */ +function headers() { + /*jshint validthis:true */ + return property(this, 'headers'); +} -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +/** + * Obtain a specific response header + * + * @param {String} headerName the header to retrieve + * @returns {Promise} for the response header's value + */ +function header(headerName) { + /*jshint validthis:true */ + headerName = normalizeHeaderName(headerName); + return property(this.headers(), headerName); +} + +/** + * Follow a related resource + * + * The relationship to follow may be define as a plain string, an object + * with the rel and params, or an array containing one or more entries + * with the previous forms. + * + * Examples: + * response.follow('next') + * + * response.follow({ rel: 'next', params: { pageSize: 100 } }) + * + * response.follow([ + * { rel: 'items', params: { projection: 'noImages' } }, + * 'search', + * { rel: 'findByGalleryIsNull', params: { projection: 'noImages' } }, + * 'items' + * ]) + * + * @param {String|Object|Array} rels one, or more, relationships to follow + * @returns ResponsePromise related resource + */ +function follow(rels) { + /*jshint validthis:true */ + rels = [].concat(rels); + + return make(rels.reduce(function (response, rel) { + return response.then(function (response) { + if (typeof rel === 'string') { + rel = { rel: rel }; + } + if (typeof response.entity.clientFor !== 'function') { + throw new Error('Hypermedia response expected'); + } + var client = response.entity.clientFor(rel.rel); + return client({ params: rel.params }); + }); + }, this)); +} + +/** + * Wrap a Promise as an ResponsePromise + * + * @param {Promise} promise the promise for an HTTP Response + * @returns {ResponsePromise} wrapped promise for Response with additional helper methods + */ +function make(promise) { + promise.status = status; + promise.headers = headers; + promise.header = header; + promise.entity = entity; + promise.follow = follow; + return promise; +} + +function responsePromise(obj, callback, errback) { + return make(Promise.resolve(obj).then(callback, errback)); +} + +responsePromise.make = make; +responsePromise.reject = function (val) { + return make(Promise.reject(val)); +}; +responsePromise.promise = function (func) { + return make(new Promise(func)); +}; + +module.exports = responsePromise; diff --git a/util/uriEncoder.js b/util/uriEncoder.js index 650fb64..c5b3e43 100644 --- a/util/uriEncoder.js +++ b/util/uriEncoder.js @@ -1,180 +1,170 @@ /* - * Copyright 2015 the original author or authors + * Copyright 2015-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; - - define(function (/* require */) { - - var charMap; - - charMap = (function () { - var strings = { - alpha: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', - digit: '0123456789' - }; - - strings.genDelims = ':/?#[]@'; - strings.subDelims = '!$&\'()*+,;='; - strings.reserved = strings.genDelims + strings.subDelims; - strings.unreserved = strings.alpha + strings.digit + '-._~'; - strings.url = strings.reserved + strings.unreserved; - strings.scheme = strings.alpha + strings.digit + '+-.'; - strings.userinfo = strings.unreserved + strings.subDelims + ':'; - strings.host = strings.unreserved + strings.subDelims; - strings.port = strings.digit; - strings.pchar = strings.unreserved + strings.subDelims + ':@'; - strings.segment = strings.pchar; - strings.path = strings.segment + '/'; - strings.query = strings.pchar + '/?'; - strings.fragment = strings.pchar + '/?'; - - return Object.keys(strings).reduce(function (charMap, set) { - charMap[set] = strings[set].split('').reduce(function (chars, myChar) { - chars[myChar] = true; - return chars; - }, {}); - return charMap; - }, {}); - }()); - - function encode(str, allowed) { - if (typeof str !== 'string') { - throw new Error('String required for URL encoding'); - } - return str.split('').map(function (myChar) { - if (allowed.hasOwnProperty(myChar)) { - return myChar; - } - var code = myChar.charCodeAt(0); - if (code <= 127) { - var encoded = code.toString(16).toUpperCase(); - return '%' + (encoded.length % 2 === 1 ? '0' : '') + encoded; - } - else { - return encodeURIComponent(myChar).toUpperCase(); - } - }).join(''); +'use strict'; + +var charMap; + +charMap = (function () { + var strings = { + alpha: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', + digit: '0123456789' + }; + + strings.genDelims = ':/?#[]@'; + strings.subDelims = '!$&\'()*+,;='; + strings.reserved = strings.genDelims + strings.subDelims; + strings.unreserved = strings.alpha + strings.digit + '-._~'; + strings.url = strings.reserved + strings.unreserved; + strings.scheme = strings.alpha + strings.digit + '+-.'; + strings.userinfo = strings.unreserved + strings.subDelims + ':'; + strings.host = strings.unreserved + strings.subDelims; + strings.port = strings.digit; + strings.pchar = strings.unreserved + strings.subDelims + ':@'; + strings.segment = strings.pchar; + strings.path = strings.segment + '/'; + strings.query = strings.pchar + '/?'; + strings.fragment = strings.pchar + '/?'; + + return Object.keys(strings).reduce(function (charMap, set) { + charMap[set] = strings[set].split('').reduce(function (chars, myChar) { + chars[myChar] = true; + return chars; + }, {}); + return charMap; + }, {}); +}()); + +function encode(str, allowed) { + if (typeof str !== 'string') { + throw new Error('String required for URL encoding'); + } + return str.split('').map(function (myChar) { + if (allowed.hasOwnProperty(myChar)) { + return myChar; } - - function makeEncoder(allowed) { - allowed = allowed || charMap.unreserved; - return function (str) { - return encode(str, allowed); - }; + var code = myChar.charCodeAt(0); + if (code <= 127) { + var encoded = code.toString(16).toUpperCase(); + return '%' + (encoded.length % 2 === 1 ? '0' : '') + encoded; } - - function decode(str) { - return decodeURIComponent(str); + else { + return encodeURIComponent(myChar).toUpperCase(); } - - return { - - /* - * Decode URL encoded strings - * - * @param {string} URL encoded string - * @returns {string} URL decoded string - */ - decode: decode, - - /* - * URL encode a string - * - * All but alpha-numerics and a very limited set of punctuation - . _ ~ are - * encoded. - * - * @param {string} string to encode - * @returns {string} URL encoded string - */ - encode: makeEncoder(), - - /* - * URL encode a URL - * - * All character permitted anywhere in a URL are left unencoded even - * if that character is not permitted in that portion of a URL. - * - * Note: This method is typically not what you want. - * - * @param {string} string to encode - * @returns {string} URL encoded string - */ - encodeURL: makeEncoder(charMap.url), - - /* - * URL encode the scheme portion of a URL - * - * @param {string} string to encode - * @returns {string} URL encoded string - */ - encodeScheme: makeEncoder(charMap.scheme), - - /* - * URL encode the user info portion of a URL - * - * @param {string} string to encode - * @returns {string} URL encoded string - */ - encodeUserInfo: makeEncoder(charMap.userinfo), - - /* - * URL encode the host portion of a URL - * - * @param {string} string to encode - * @returns {string} URL encoded string - */ - encodeHost: makeEncoder(charMap.host), - - /* - * URL encode the port portion of a URL - * - * @param {string} string to encode - * @returns {string} URL encoded string - */ - encodePort: makeEncoder(charMap.port), - - /* - * URL encode a path segment portion of a URL - * - * @param {string} string to encode - * @returns {string} URL encoded string - */ - encodePathSegment: makeEncoder(charMap.segment), - - /* - * URL encode the path portion of a URL - * - * @param {string} string to encode - * @returns {string} URL encoded string - */ - encodePath: makeEncoder(charMap.path), - - /* - * URL encode the query portion of a URL - * - * @param {string} string to encode - * @returns {string} URL encoded string - */ - encodeQuery: makeEncoder(charMap.query), - - /* - * URL encode the fragment portion of a URL - * - * @param {string} string to encode - * @returns {string} URL encoded string - */ - encodeFragment: makeEncoder(charMap.fragment) - - }; - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); + }).join(''); +} + +function makeEncoder(allowed) { + allowed = allowed || charMap.unreserved; + return function (str) { + return encode(str, allowed); + }; +} + +function decode(str) { + return decodeURIComponent(str); +} + +module.exports = { + + /* + * Decode URL encoded strings + * + * @param {string} URL encoded string + * @returns {string} URL decoded string + */ + decode: decode, + + /* + * URL encode a string + * + * All but alpha-numerics and a very limited set of punctuation - . _ ~ are + * encoded. + * + * @param {string} string to encode + * @returns {string} URL encoded string + */ + encode: makeEncoder(), + + /* + * URL encode a URL + * + * All character permitted anywhere in a URL are left unencoded even + * if that character is not permitted in that portion of a URL. + * + * Note: This method is typically not what you want. + * + * @param {string} string to encode + * @returns {string} URL encoded string + */ + encodeURL: makeEncoder(charMap.url), + + /* + * URL encode the scheme portion of a URL + * + * @param {string} string to encode + * @returns {string} URL encoded string + */ + encodeScheme: makeEncoder(charMap.scheme), + + /* + * URL encode the user info portion of a URL + * + * @param {string} string to encode + * @returns {string} URL encoded string + */ + encodeUserInfo: makeEncoder(charMap.userinfo), + + /* + * URL encode the host portion of a URL + * + * @param {string} string to encode + * @returns {string} URL encoded string + */ + encodeHost: makeEncoder(charMap.host), + + /* + * URL encode the port portion of a URL + * + * @param {string} string to encode + * @returns {string} URL encoded string + */ + encodePort: makeEncoder(charMap.port), + + /* + * URL encode a path segment portion of a URL + * + * @param {string} string to encode + * @returns {string} URL encoded string + */ + encodePathSegment: makeEncoder(charMap.segment), + + /* + * URL encode the path portion of a URL + * + * @param {string} string to encode + * @returns {string} URL encoded string + */ + encodePath: makeEncoder(charMap.path), + + /* + * URL encode the query portion of a URL + * + * @param {string} string to encode + * @returns {string} URL encoded string + */ + encodeQuery: makeEncoder(charMap.query), + + /* + * URL encode the fragment portion of a URL + * + * @param {string} string to encode + * @returns {string} URL encoded string + */ + encodeFragment: makeEncoder(charMap.fragment) + +}; diff --git a/util/uriTemplate.js b/util/uriTemplate.js index b7c99ac..d59298f 100644 --- a/util/uriTemplate.js +++ b/util/uriTemplate.js @@ -1,172 +1,160 @@ /* - * Copyright 2015 the original author or authors + * Copyright 2015-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; - - var undef; - - define(function (require) { - - var uriEncoder, operations, prefixRE; - - uriEncoder = require('./uriEncoder'); - - prefixRE = /^([^:]*):([0-9]+)$/; - operations = { - '': { first: '', separator: ',', named: false, empty: '', encoder: uriEncoder.encode }, - '+': { first: '', separator: ',', named: false, empty: '', encoder: uriEncoder.encodeURL }, - '#': { first: '#', separator: ',', named: false, empty: '', encoder: uriEncoder.encodeURL }, - '.': { first: '.', separator: '.', named: false, empty: '', encoder: uriEncoder.encode }, - '/': { first: '/', separator: '/', named: false, empty: '', encoder: uriEncoder.encode }, - ';': { first: ';', separator: ';', named: true, empty: '', encoder: uriEncoder.encode }, - '?': { first: '?', separator: '&', named: true, empty: '=', encoder: uriEncoder.encode }, - '&': { first: '&', separator: '&', named: true, empty: '=', encoder: uriEncoder.encode }, - '=': { reserved: true }, - ',': { reserved: true }, - '!': { reserved: true }, - '@': { reserved: true }, - '|': { reserved: true } - }; - - function apply(operation, expression, params) { - /*jshint maxcomplexity:11 */ - return expression.split(',').reduce(function (result, variable) { - var opts, value; - - opts = {}; - if (variable.slice(-1) === '*') { - variable = variable.slice(0, -1); - opts.explode = true; - } - if (prefixRE.test(variable)) { - var prefix = prefixRE.exec(variable); - variable = prefix[1]; - opts.maxLength = parseInt(prefix[2]); - } +'use strict'; + +var uriEncoder, operations, prefixRE; + +uriEncoder = require('./uriEncoder'); + +prefixRE = /^([^:]*):([0-9]+)$/; +operations = { + '': { first: '', separator: ',', named: false, empty: '', encoder: uriEncoder.encode }, + '+': { first: '', separator: ',', named: false, empty: '', encoder: uriEncoder.encodeURL }, + '#': { first: '#', separator: ',', named: false, empty: '', encoder: uriEncoder.encodeURL }, + '.': { first: '.', separator: '.', named: false, empty: '', encoder: uriEncoder.encode }, + '/': { first: '/', separator: '/', named: false, empty: '', encoder: uriEncoder.encode }, + ';': { first: ';', separator: ';', named: true, empty: '', encoder: uriEncoder.encode }, + '?': { first: '?', separator: '&', named: true, empty: '=', encoder: uriEncoder.encode }, + '&': { first: '&', separator: '&', named: true, empty: '=', encoder: uriEncoder.encode }, + '=': { reserved: true }, + ',': { reserved: true }, + '!': { reserved: true }, + '@': { reserved: true }, + '|': { reserved: true } +}; + +function apply(operation, expression, params) { + /*jshint maxcomplexity:11 */ + return expression.split(',').reduce(function (result, variable) { + var opts, value; + + opts = {}; + if (variable.slice(-1) === '*') { + variable = variable.slice(0, -1); + opts.explode = true; + } + if (prefixRE.test(variable)) { + var prefix = prefixRE.exec(variable); + variable = prefix[1]; + opts.maxLength = parseInt(prefix[2]); + } - variable = uriEncoder.decode(variable); - value = params[variable]; + variable = uriEncoder.decode(variable); + value = params[variable]; - if (value === undef || value === null) { - return result; - } - if (Array.isArray(value)) { - result = value.reduce(function (result, value) { - if (result.length) { - result += opts.explode ? operation.separator : ','; - if (operation.named && opts.explode) { - result += operation.encoder(variable); - result += value.length ? '=' : operation.empty; - } - } - else { - result += operation.first; - if (operation.named) { - result += operation.encoder(variable); - result += value.length ? '=' : operation.empty; - } - } - result += operation.encoder(value); - return result; - }, result); - } - else if (typeof value === 'object') { - result = Object.keys(value).reduce(function (result, name) { - if (result.length) { - result += opts.explode ? operation.separator : ','; - } - else { - result += operation.first; - if (operation.named && !opts.explode) { - result += operation.encoder(variable); - result += value[name].length ? '=' : operation.empty; - } - } - result += operation.encoder(name); - result += opts.explode ? '=' : ','; - result += operation.encoder(value[name]); - return result; - }, result); + if (value === void 0 || value === null) { + return result; + } + if (Array.isArray(value)) { + result = value.reduce(function (result, value) { + if (result.length) { + result += opts.explode ? operation.separator : ','; + if (operation.named && opts.explode) { + result += operation.encoder(variable); + result += value.length ? '=' : operation.empty; + } } else { - value = String(value); - if (opts.maxLength) { - value = value.slice(0, opts.maxLength); - } - result += result.length ? operation.separator : operation.first; + result += operation.first; if (operation.named) { result += operation.encoder(variable); result += value.length ? '=' : operation.empty; } - result += operation.encoder(value); } - + result += operation.encoder(value); return result; - }, ''); + }, result); } - - function expandExpression(expression, params) { - var operation; - - operation = operations[expression.slice(0,1)]; - if (operation) { - expression = expression.slice(1); - } - else { - operation = operations['']; + else if (typeof value === 'object') { + result = Object.keys(value).reduce(function (result, name) { + if (result.length) { + result += opts.explode ? operation.separator : ','; + } + else { + result += operation.first; + if (operation.named && !opts.explode) { + result += operation.encoder(variable); + result += value[name].length ? '=' : operation.empty; + } + } + result += operation.encoder(name); + result += opts.explode ? '=' : ','; + result += operation.encoder(value[name]); + return result; + }, result); + } + else { + value = String(value); + if (opts.maxLength) { + value = value.slice(0, opts.maxLength); } - - if (operation.reserved) { - throw new Error('Reserved expression operations are not supported'); + result += result.length ? operation.separator : operation.first; + if (operation.named) { + result += operation.encoder(variable); + result += value.length ? '=' : operation.empty; } - - return apply(operation, expression, params); + result += operation.encoder(value); } - function expandTemplate(template, params) { - var start, end, uri; - - uri = ''; - end = 0; - while (true) { - start = template.indexOf('{', end); - if (start === -1) { - // no more expressions - uri += template.slice(end); - break; - } - uri += template.slice(end, start); - end = template.indexOf('}', start) + 1; - uri += expandExpression(template.slice(start + 1, end - 1), params); - } - - return uri; + return result; + }, ''); +} + +function expandExpression(expression, params) { + var operation; + + operation = operations[expression.slice(0,1)]; + if (operation) { + expression = expression.slice(1); + } + else { + operation = operations['']; + } + + if (operation.reserved) { + throw new Error('Reserved expression operations are not supported'); + } + + return apply(operation, expression, params); +} + +function expandTemplate(template, params) { + var start, end, uri; + + uri = ''; + end = 0; + while (true) { + start = template.indexOf('{', end); + if (start === -1) { + // no more expressions + uri += template.slice(end); + break; } - - return { - - /** - * Expand a URI Template with parameters to form a URI. - * - * Full implementation (level 4) of rfc6570. - * @see https://tools.ietf.org/html/rfc6570 - * - * @param {string} template URI template - * @param {Object} [params] params to apply to the template durring expantion - * @returns {string} expanded URI - */ - expand: expandTemplate - - }; - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); + uri += template.slice(end, start); + end = template.indexOf('}', start) + 1; + uri += expandExpression(template.slice(start + 1, end - 1), params); + } + + return uri; +} + +module.exports = { + + /** + * Expand a URI Template with parameters to form a URI. + * + * Full implementation (level 4) of rfc6570. + * @see https://tools.ietf.org/html/rfc6570 + * + * @param {string} template URI template + * @param {Object} [params] params to apply to the template durring expantion + * @returns {string} expanded URI + */ + expand: expandTemplate + +}; diff --git a/wire.js b/wire.js index 3ee803e..8dccf8e 100644 --- a/wire.js +++ b/wire.js @@ -1,81 +1,71 @@ /* - * Copyright 2012-2015 the original author or authors + * Copyright 2012-2016 the original author or authors * @license MIT, see LICENSE.txt for details * * @author Scott Andrews */ -(function (define) { - 'use strict'; +'use strict'; - define(function (require) { +var client, when, pipeline, plugin; - var client, when, pipeline, plugin; +client = require('./client/default'); +when = require('when'); +pipeline = require('when/pipeline'); - client = require('./client/default'); - when = require('when'); - pipeline = require('when/pipeline'); +function normalizeRestFactoryConfig(spec, wire) { + var config = {}; - function normalizeRestFactoryConfig(spec, wire) { - var config = {}; + config.parent = wire(spec.parent || client); + config.interceptors = when.all((Array.isArray(spec) ? spec : spec.interceptors || []).map(function (interceptorDef) { + var interceptorConfig = interceptorDef.config; + delete interceptorDef.config; + return when.all([ + wire(typeof interceptorDef === 'string' ? { module: interceptorDef } : interceptorDef), + wire(interceptorConfig) + ]).spread(function (interceptor, config) { + return { interceptor: interceptor, config: config }; + }); + })); - config.parent = wire(spec.parent || client); - config.interceptors = when.all((Array.isArray(spec) ? spec : spec.interceptors || []).map(function (interceptorDef) { - var interceptorConfig = interceptorDef.config; - delete interceptorDef.config; - return when.all([ - wire(typeof interceptorDef === 'string' ? { module: interceptorDef } : interceptorDef), - wire(interceptorConfig) - ]).spread(function (interceptor, config) { - return { interceptor: interceptor, config: config }; - }); - })); + return config; +} - return config; - } +/** + * Creates a rest client for the "rest" factory. + * @param resolver + * @param spec + * @param wire + */ +function restFactory(resolver, spec, wire) { + var config = normalizeRestFactoryConfig(spec.rest || spec.options, wire); + return config.parent.then(function (parent) { + return config.interceptors.then(function (interceptorDefs) { + pipeline(interceptorDefs.map(function (interceptorDef) { + return function (parent) { + return interceptorDef.interceptor(parent, interceptorDef.config); + }; + }), parent).then(resolver.resolve, resolver.reject); + }); + }); +} - /** - * Creates a rest client for the "rest" factory. - * @param resolver - * @param spec - * @param wire - */ - function restFactory(resolver, spec, wire) { - var config = normalizeRestFactoryConfig(spec.rest || spec.options, wire); - return config.parent.then(function (parent) { - return config.interceptors.then(function (interceptorDefs) { - pipeline(interceptorDefs.map(function (interceptorDef) { - return function (parent) { - return interceptorDef.interceptor(parent, interceptorDef.config); - }; - }), parent).then(resolver.resolve, resolver.reject); - }); - }); +/** + * The plugin instance. Can be the same for all wiring runs + */ +plugin = { + resolvers: { + client: function () { + throw new Error('rest.js: client! wire reference resolved is deprecated, use \'rest\' facotry instead'); } + }, + factories: { + rest: restFactory + } +}; - /** - * The plugin instance. Can be the same for all wiring runs - */ - plugin = { - resolvers: { - client: function () { - throw new Error('rest.js: client! wire reference resolved is deprecated, use \'rest\' facotry instead'); - } - }, - factories: { - rest: restFactory - } - }; - - return { - wire$plugin: function restPlugin(/* ready, destroyed, options */) { - return plugin; - } - }; - - }); - -}( - typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } - // Boilerplate for AMD and Node -)); +module.exports = { + wire$plugin: function restPlugin(/* ready, destroyed, options */) { + return plugin; + } +};