From 7709358c8bf79d4211565f0e85e96a3dc1c721a1 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Thu, 16 Jun 2016 13:12:44 -0700 Subject: [PATCH] Drop AMD module support CommonJS Modules are now the only supported format. Any loader that can load CommonJS Modules should be able to load rest.js. curl.js users can use the cjsm11 loader. Bundlers like Browserify and WebPack can also be used to consume rest.js --- README.md | 3 +- UrlBuilder.js | 408 +++++++++--------- bower.json | 2 +- browser.js | 22 +- client.js | 94 ++-- client/default.js | 212 +++++---- client/jsonp.js | 234 +++++----- client/node.js | 249 +++++------ client/xdr.js | 105 +++-- client/xhr.js | 287 ++++++------ interceptor.js | 267 ++++++------ interceptor/basicAuth.js | 62 ++- interceptor/csrf.js | 94 ++-- interceptor/defaultRequest.js | 118 +++-- interceptor/entity.js | 58 ++- interceptor/errorCode.js | 65 ++- interceptor/hateoas.js | 240 +++++------ interceptor/ie/xdomain.js | 91 ++-- interceptor/ie/xhr.js | 95 ++-- interceptor/jsonp.js | 90 ++-- interceptor/location.js | 88 ++-- interceptor/mime.js | 198 ++++----- interceptor/oAuth.js | 236 +++++----- interceptor/params.js | 84 ++-- interceptor/pathPrefix.js | 80 ++-- interceptor/retry.js | 86 ++-- interceptor/template.js | 84 ++-- interceptor/timeout.js | 111 +++-- mime.js | 82 ++-- mime/registry.js | 193 ++++----- mime/type/application/hal.js | 225 +++++----- mime/type/application/json.js | 60 ++- .../type/application/x-www-form-urlencoded.js | 131 +++--- mime/type/multipart/form-data.js | 93 ++-- mime/type/text/plain.js | 29 +- node.js | 22 +- parsers/_template.js | 9 - parsers/rfc5988.js | 160 ++++--- rest.js | 22 +- test/UrlBuilder-test.js | 2 +- test/browser-test-browser.js | 2 +- test/client-test.js | 2 +- test/client/jsonp-test-browser.js | 2 +- test/client/node-test-node.js | 2 +- test/client/xdr-test-browser.js | 4 +- test/client/xhr-test-browser.js | 2 +- test/curl-config.js | 29 +- test/interceptor-test.js | 2 +- test/interceptor/basicAuth-test.js | 2 +- test/interceptor/csrf-test.js | 2 +- test/interceptor/defaultRequest-test.js | 2 +- test/interceptor/entity-test.js | 2 +- test/interceptor/errorCode-test.js | 2 +- test/interceptor/hateoas-test.js | 2 +- test/interceptor/ie/xdomain-test-browser.js | 6 +- test/interceptor/ie/xhr-test-browser.js | 2 +- test/interceptor/jsonp-test.js | 2 +- test/interceptor/location-test.js | 2 +- test/interceptor/mime-test.js | 2 +- .../{oAuth-test.js => oAuth-test-browser.js} | 2 +- test/interceptor/params-test.js | 2 +- test/interceptor/pathPrefix-test.js | 2 +- test/interceptor/retry-test.js | 2 +- test/interceptor/template-test.js | 2 +- test/interceptor/timeout-test.js | 2 +- test/mime-test.js | 2 +- test/mime/registry-test.js | 2 +- test/mime/type/application/hal-test.js | 2 +- test/mime/type/application/json-test.js | 2 +- .../application/x-www-form-urlencoded-test.js | 2 +- .../type/multipart/form-data-test-browser.js | 4 +- test/mime/type/text/plain-test.js | 2 +- test/node-test-node.js | 2 +- test/run.js | 6 +- test/util/attempt-test.js | 2 +- test/util/base64-test.js | 2 +- test/util/delay-test.js | 3 +- test/util/find-test.js | 2 +- test/util/lazyPromise-test.js | 2 +- test/util/mixin-test.js | 2 +- test/util/normalizeHeaderName-test.js | 2 +- test/util/pubsub-test.js | 2 +- test/util/responsePromise-test.js | 5 +- ...{urlEncoder-test.js => uriEncoder-test.js} | 2 +- test/util/uriTemplate-test.js | 2 +- test/version-test-node.js | 2 +- test/wire-test.js | 2 +- util/Promise.js | 21 +- util/attempt.js | 50 +-- util/base64.js | 185 ++++---- util/delay.js | 54 +-- util/find.js | 54 +-- util/lazyPromise.js | 83 ++-- util/mixin.js | 61 ++- util/normalizeHeaderName.js | 50 +-- util/pubsub.js | 82 ++-- util/responsePromise.js | 249 ++++++----- util/uriEncoder.js | 332 +++++++------- util/uriTemplate.js | 288 ++++++------- wire.js | 122 +++--- 100 files changed, 3019 insertions(+), 3540 deletions(-) delete mode 100644 parsers/_template.js rename test/interceptor/{oAuth-test.js => oAuth-test-browser.js} (97%) rename test/util/{urlEncoder-test.js => uriEncoder-test.js} (99%) 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; + } +};