├── test ├── client │ ├── fixtures │ │ ├── noop.js │ │ ├── data.js │ │ └── throw.js │ ├── node-ssl.crt │ ├── node-ssl.key │ └── jsonp-test-browser.js ├── browsers │ ├── edge.json │ ├── ios-9.2.json │ ├── chrome.json │ ├── firefox.json │ ├── ie-11.json │ ├── android-4.3.json │ ├── android-4.4.json │ ├── android-5.1.json │ ├── firefox-38.json │ ├── firefox-45.json │ ├── ios-8.4.json │ ├── safari-8.json │ └── safari-9.json ├── failOnThrow.js ├── curl-config.js ├── run.js ├── util │ ├── base64-test.js │ ├── normalizeHeaderName-test.js │ ├── find-test.js │ ├── attempt-test.js │ ├── mixin-test.js │ ├── lazyPromise-test.js │ ├── delay-test.js │ └── pubsub-test.js ├── buster.js ├── mime │ ├── type │ │ ├── text │ │ │ └── plain-test.js │ │ ├── multipart │ │ │ └── form-data-test-browser.js │ │ └── application │ │ │ ├── json-test.js │ │ │ └── x-www-form-urlencoded-test.js │ └── registry-test.js ├── node-test-node.js ├── browser-test-browser.js ├── version-test-node.js ├── interceptor │ ├── entity-test.js │ ├── errorCode-test.js │ ├── basicAuth-test.js │ ├── jsonp-test.js │ ├── pathPrefix-test.js │ ├── csrf-test.js │ ├── params-test.js │ ├── oAuth-test-browser.js │ ├── location-test.js │ ├── template-test.js │ ├── retry-test.js │ └── defaultRequest-test.js ├── client-test.js ├── rest-test.js └── mime-test.js ├── .jshintignore ├── .gitignore ├── bower.json ├── util ├── Promise.js ├── attempt.js ├── normalizeHeaderName.js ├── delay.js ├── find.js ├── mixin.js ├── lazyPromise.js ├── pubsub.js ├── responsePromise.js ├── base64.js ├── uriEncoder.js └── uriTemplate.js ├── node.js ├── browser.js ├── mime ├── type │ ├── text │ │ └── plain.js │ ├── application │ │ ├── json.js │ │ ├── x-www-form-urlencoded.js │ │ └── hal.js │ └── multipart │ │ └── form-data.js └── registry.js ├── rest.js ├── interceptor ├── entity.js ├── errorCode.js ├── basicAuth.js ├── pathPrefix.js ├── template.js ├── params.js ├── location.js ├── csrf.js ├── retry.js ├── jsonp.js ├── timeout.js ├── defaultRequest.js ├── mime.js ├── hateoas.js └── oAuth.js ├── docs ├── README.md ├── wire.md └── clients.md ├── mime.js ├── LICENSE.txt ├── client.js ├── package.json ├── parsers └── rfc5988.pegjs ├── wire.js ├── .travis.yml ├── client ├── node.js ├── jsonp.js ├── default.js └── xhr.js ├── interceptor.js └── .jshintrc /test/client/fixtures/noop.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | parsers 3 | test/**/fixtures -------------------------------------------------------------------------------- /test/client/fixtures/data.js: -------------------------------------------------------------------------------- 1 | callback({ data: true }); 2 | -------------------------------------------------------------------------------- /test/client/fixtures/throw.js: -------------------------------------------------------------------------------- 1 | notcallback({ data: true }); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .buildpath 3 | .classpath 4 | .idea 5 | .project 6 | .settings 7 | /node_modules 8 | /npm-debug.log 9 | /sauce_connect.log* 10 | -------------------------------------------------------------------------------- /test/browsers/edge.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'MicrosoftEdge', platform: 'Windows 10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/ios-9.2.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'ipad', version: '9.2', platform: 'Mac 10.10' } 4 | ] 5 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rest", 3 | "version": "2.0.0", 4 | "main": "./browser.js", 5 | "moduleType": ["node"], 6 | "dependencies": {}, 7 | "ignore": [ 8 | "docs", 9 | "test" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/browsers/chrome.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'chrome', platform: 'Windows 2008' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/firefox.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'firefox', platform: 'Windows 10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/ie-11.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'internet explorer', version: '11', platform: 'Windows 8.1' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/android-4.3.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'android', version: '4.3', platform: 'Linux' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/android-4.4.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'android', version: '4.4', platform: 'Linux' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/android-5.1.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'android', version: '5.1', platform: 'Linux' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/firefox-38.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'firefox', version: '38', platform: 'Windows 10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/firefox-45.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'firefox', version: '45', platform: 'Windows 10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/ios-8.4.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'ipad', version: '8.4', platform: 'OS X 10.10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/safari-8.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'safari', version: '8', platform: 'Mac 10.10' } 4 | ] 5 | -------------------------------------------------------------------------------- /test/browsers/safari-9.json: -------------------------------------------------------------------------------- 1 | [ 2 | // we don't really care about the platform, but without it the browser may fail to resolve 3 | { browserName: 'safari', version: '9', platform: 'Mac 10.11' } 4 | ] 5 | -------------------------------------------------------------------------------- /util/Promise.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /*global Promise */ 11 | 12 | 13 | module.exports = Promise; 14 | -------------------------------------------------------------------------------- /node.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var rest = require('./client/default'), 11 | node = require('./client/node'); 12 | 13 | rest.setPlatformDefaultClient(node); 14 | 15 | module.exports = rest; 16 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var rest = require('./client/default'), 11 | browser = require('./client/xhr'); 12 | 13 | rest.setPlatformDefaultClient(browser); 14 | 15 | module.exports = rest; 16 | -------------------------------------------------------------------------------- /mime/type/text/plain.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | module.exports = { 11 | 12 | read: function (str) { 13 | return str; 14 | }, 15 | 16 | write: function (obj) { 17 | return obj.toString(); 18 | } 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /rest.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | if (console) { 11 | (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.'); 12 | } 13 | 14 | module.exports = require('./browser'); 15 | -------------------------------------------------------------------------------- /test/failOnThrow.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster) { 9 | 'use strict'; 10 | 11 | var fail; 12 | 13 | fail = buster.assertions.fail; 14 | 15 | buster.assertions.failOnThrow = function failOnThrow(func) { 16 | return function () { 17 | try { 18 | return func.apply(this, arguments); 19 | } 20 | catch (e) { 21 | fail(e); 22 | } 23 | }; 24 | }; 25 | 26 | }( 27 | this.buster || require('buster') 28 | )); 29 | -------------------------------------------------------------------------------- /util/attempt.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Attempt to invoke a function capturing the resulting value as a Promise 12 | * 13 | * If the method throws, the caught value used to reject the Promise. 14 | * 15 | * @param {function} work function to invoke 16 | * @returns {Promise} Promise for the output of the work function 17 | */ 18 | function attempt(work) { 19 | try { 20 | return Promise.resolve(work()); 21 | } 22 | catch (e) { 23 | return Promise.reject(e); 24 | } 25 | } 26 | 27 | module.exports = attempt; 28 | -------------------------------------------------------------------------------- /test/curl-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | window.curl = { 11 | packages: [ 12 | { name: 'rest', location: './', main: 'browser', config: { moduleLoader: 'curl/loader/cjsm11' } }, 13 | { name: 'curl', location: 'node_modules/curl/src/curl', main: 'curl' }, 14 | { name: 'poly', location: 'node_modules/poly', main: 'poly' }, 15 | { name: 'when', location: 'node_modules/when', main: 'when' }, 16 | { name: 'wire', location: 'node_modules/wire', main: 'wire' } 17 | ], 18 | // avoid poly/xhr as we need to test the case without it 19 | preloads: ['poly/object', 'poly/string', 'poly/date', 'poly/array', 'poly/function', 'poly/json'] 20 | }; 21 | -------------------------------------------------------------------------------- /test/client/node-ssl.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIB/zCCAWgCCQDvBizVJkxhzDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDzANBgNVBAoT 4 | BmN1am9KUzAgFw0xNDA2MjQxNjM4MTBaGA8yMTE0MDUzMTE2MzgxMFowQzELMAkG 5 | A1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMQ8w 6 | DQYDVQQKEwZjdWpvSlMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMVyyv6a 7 | I2Hqe4wa+Ae9Iq1ExphrAvw+9oLBZezGzjtk+94CwcBTtDgyEqrDF6uQreKvyR3G 8 | 51ss4/boo9SZg9tPfg0bzIQZq6usK/zL+tyTcGWD7YyV00lCJPsKaNavLEwFv0Ak 9 | /tROBxc/kiUTKVj9rydmlEsj76llId8rONW7AgMBAAEwDQYJKoZIhvcNAQEFBQAD 10 | gYEAInY9rNwVS4fQza1wt+dGSAboss6sbR9cwJSYe/UcNI6C2mKAcYhpjbnJtSaM 11 | PtLGHwdCB5gdqkyDg91vr+SmJBslso+yDtfvTxrs5kzcMoM0ussMsc2OEbW+MzRX 12 | EL+hinrTYPOPIhgHn3UZtnNzTf3fa1VwLVSmWvWM1jzEmSM= 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /test/run.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | define('rest-test/test/run', ['curl/_privileged', 'domReady!'], function (curl) { 12 | 13 | var modules = Object.keys(curl.cache).filter(function (moduleId) { 14 | return moduleId.match(/-test(-browser)?$/); 15 | }); 16 | 17 | buster.testRunner.timeout = 5000; 18 | define('rest-test/test/run-faux', modules, function () { 19 | buster.run(); 20 | }); 21 | 22 | }); 23 | 24 | }( 25 | this.buster || require('buster'), 26 | typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } 27 | // Boilerplate for AMD and Node 28 | )); 29 | -------------------------------------------------------------------------------- /util/normalizeHeaderName.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Normalize HTTP header names using the pseudo camel case. 12 | * 13 | * For example: 14 | * content-type -> Content-Type 15 | * accepts -> Accepts 16 | * x-custom-header-name -> X-Custom-Header-Name 17 | * 18 | * @param {string} name the raw header name 19 | * @return {string} the normalized header name 20 | */ 21 | function normalizeHeaderName(name) { 22 | return name.toLowerCase() 23 | .split('-') 24 | .map(function (chunk) { return chunk.charAt(0).toUpperCase() + chunk.slice(1); }) 25 | .join('-'); 26 | } 27 | 28 | module.exports = normalizeHeaderName; 29 | -------------------------------------------------------------------------------- /util/delay.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Delay the resolution of a promise 12 | * 13 | * Note: if the value is a promise, the delay starts once the promise 14 | * resolves. 15 | * 16 | * @param {number} wait miliseconds to wait 17 | * @param {Promise|*} [promiseOrValue] value to resolve with 18 | * @returns {Promise} delayed Promise containing the value 19 | */ 20 | function delay(wait, promiseOrValue) { 21 | return Promise.resolve(promiseOrValue).then(function (value) { 22 | return new Promise(function (resolve) { 23 | setTimeout(function () { 24 | resolve(value); 25 | }, wait); 26 | }); 27 | }); 28 | } 29 | 30 | module.exports = delay; 31 | -------------------------------------------------------------------------------- /interceptor/entity.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor; 11 | 12 | interceptor = require('../interceptor'); 13 | 14 | if (typeof console !== 'undefined') { 15 | console.log('rest.js: rest/interceptor/entity is deprecated, please use response.entity() instead'); 16 | } 17 | 18 | /** 19 | * @deprecated use response.entity() instead 20 | * 21 | * Returns the response entity as the response, discarding other response 22 | * properties. 23 | * 24 | * @param {Client} [client] client to wrap 25 | * 26 | * @returns {Client} 27 | */ 28 | module.exports = interceptor({ 29 | response: function (response) { 30 | if ('entity' in response) { 31 | return response.entity; 32 | } 33 | return response; 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /util/find.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | module.exports = { 11 | 12 | /** 13 | * Find objects within a graph the contain a property of a certain name. 14 | * 15 | * NOTE: this method will not discover object graph cycles. 16 | * 17 | * @param {*} obj object to search on 18 | * @param {string} prop name of the property to search for 19 | * @param {Function} callback function to receive the found properties and their parent 20 | */ 21 | findProperties: function findProperties(obj, prop, callback) { 22 | if (typeof obj !== 'object' || obj === null) { return; } 23 | if (prop in obj) { 24 | callback(obj[prop], obj, prop); 25 | } 26 | Object.keys(obj).forEach(function (key) { 27 | findProperties(obj[key], prop, callback); 28 | }); 29 | } 30 | 31 | }; 32 | -------------------------------------------------------------------------------- /test/client/node-ssl.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDFcsr+miNh6nuMGvgHvSKtRMaYawL8PvaCwWXsxs47ZPveAsHA 3 | U7Q4MhKqwxerkK3ir8kdxudbLOP26KPUmYPbT34NG8yEGaurrCv8y/rck3Blg+2M 4 | ldNJQiT7CmjWryxMBb9AJP7UTgcXP5IlEylY/a8nZpRLI++pZSHfKzjVuwIDAQAB 5 | AoGBAKH9tB1+SFklD4e6+JMg0Tpmmqih4ykgXw6XrQ+9a6W/DATxLhSkl88jBb2j 6 | od/YK1E390C+10Euazk/VGowKMRsDyyGAnnbo2BdSo1iT+TVDT3wn3yboXgc4hsj 7 | QWiLfSMKkCepFnUhTX8UDRAQXiNzs9GpQo9gNmOQPnaEM0eBAkEA6neMmy6okW/s 8 | +OWtLEYdhcQt8rx3G3sdEIWaHaw+Qa7j1fbRPniqF3pLSILE78r1pO+b0AhrFzhw 9 | rKDB4R2YEwJBANeU6oTjLmLN86RA47sku9SRC4H32TSS0BAhm1Qg1D47VB1oltm8 10 | JXP52Gm6YMxEM81YB8YjZHlR2OTUuhdwULkCQA+hcqUxwhkX/hNFtHq8HeG6B1ok 11 | SEhzt6dPUMvjnK7iStwLvT1N7ADurTAjT5+wxxl8w8VlmkYNBC3t9Z7dSzMCQCEF 12 | CB6uhU5Q6T6BSeBCMhrO1Iplofkxe3jdDOOH27pkb+/JM0HKVZo77G/VQjpHP//1 13 | ucsZZKxIYwY5pv8sDtECQFnAJ8JU97icTib81EiaaXDctngLtTo//z7E73sFKyId 14 | FBr+0OCsHcWq4j94BXRZKdc7g4qlQXe6+onL4+uW8ok= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /interceptor/errorCode.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor; 11 | 12 | interceptor = require('../interceptor'); 13 | 14 | /** 15 | * Rejects the response promise based on the status code. 16 | * 17 | * Codes greater than or equal to the provided value are rejected. Default 18 | * value 400. 19 | * 20 | * @param {Client} [client] client to wrap 21 | * @param {number} [config.code=400] code to indicate a rejection 22 | * 23 | * @returns {Client} 24 | */ 25 | module.exports = interceptor({ 26 | init: function (config) { 27 | config.code = config.code || 400; 28 | return config; 29 | }, 30 | response: function (response, config) { 31 | if (response.status && response.status.code >= config.code) { 32 | return Promise.reject(response); 33 | } 34 | return response; 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # rest.js Documentation 2 | 3 | 4 | ## Core Facilities 5 | 6 | [Interceptors](interceptors.md) 7 | 8 | How interceptors are applied to clients with configuration. Descriptions for all interceptors provided with rest.js including configuration properties. How to create custom interceptors using the interceptor factory with links to real interceptors that demonstrate different interceptor characteristics. 9 | 10 | [Clients](clients.md) 11 | 12 | Description of built in clients including the default client and client specific properties. 13 | 14 | [Common Interfaces](interfaces.md) 15 | 16 | Common methods and properties for request/response objects, clients and interceptors. 17 | 18 | 19 | ## RESTful Concepts 20 | 21 | [Content Negotiation](mime.md) 22 | 23 | Support for serializing/deserializing objects to/from HTTP requests/responses. 24 | 25 | 26 | ## Optional Libraries 27 | 28 | [wire.js](wire.md) 29 | 30 | Declarative client configuration with wire.js. 31 | -------------------------------------------------------------------------------- /mime.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Parse a MIME type into it's constituent parts 12 | * 13 | * @param {string} mime MIME type to parse 14 | * @return {{ 15 | * {string} raw the original MIME type 16 | * {string} type the type and subtype 17 | * {string} [suffix] mime suffix, including the plus, if any 18 | * {Object} params key/value pair of attributes 19 | * }} 20 | */ 21 | function parse(mime) { 22 | var params, type; 23 | 24 | params = mime.split(';'); 25 | type = params[0].trim().split('+'); 26 | 27 | return { 28 | raw: mime, 29 | type: type[0], 30 | suffix: type[1] ? '+' + type[1] : '', 31 | params: params.slice(1).reduce(function (params, pair) { 32 | pair = pair.split('='); 33 | params[pair[0].trim()] = pair[1] ? pair[1].trim() : void 0; 34 | return params; 35 | }, {}) 36 | }; 37 | } 38 | 39 | module.exports = { 40 | parse: parse 41 | }; 42 | -------------------------------------------------------------------------------- /mime/type/application/json.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Create a new JSON converter with custom reviver/replacer. 12 | * 13 | * The extended converter must be published to a MIME registry in order 14 | * to be used. The existing converter will not be modified. 15 | * 16 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON 17 | * 18 | * @param {function} [reviver=undefined] custom JSON.parse reviver 19 | * @param {function|Array} [replacer=undefined] custom JSON.stringify replacer 20 | */ 21 | function createConverter(reviver, replacer) { 22 | return { 23 | 24 | read: function (str) { 25 | return JSON.parse(str, reviver); 26 | }, 27 | 28 | write: function (obj) { 29 | return JSON.stringify(obj, replacer); 30 | }, 31 | 32 | extend: createConverter 33 | 34 | }; 35 | } 36 | 37 | module.exports = createConverter(); 38 | -------------------------------------------------------------------------------- /interceptor/basicAuth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, base64; 11 | 12 | interceptor = require('../interceptor'); 13 | base64 = require('../util/base64'); 14 | 15 | /** 16 | * Authenticates the request using HTTP Basic Authentication (rfc2617) 17 | * 18 | * @param {Client} [client] client to wrap 19 | * @param {string} config.username username 20 | * @param {string} [config.password=''] password for the user 21 | * 22 | * @returns {Client} 23 | */ 24 | module.exports = interceptor({ 25 | request: function handleRequest(request, config) { 26 | var headers, username, password; 27 | 28 | headers = request.headers || (request.headers = {}); 29 | username = request.username || config.username; 30 | password = request.password || config.password || ''; 31 | 32 | if (username) { 33 | headers.Authorization = 'Basic ' + base64.encode(username + ':' + password); 34 | } 35 | 36 | return request; 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /util/mixin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var empty = {}; 11 | 12 | /** 13 | * Mix the properties from the source object into the destination object. 14 | * When the same property occurs in more then one object, the right most 15 | * value wins. 16 | * 17 | * @param {Object} dest the object to copy properties to 18 | * @param {Object} sources the objects to copy properties from. May be 1 to N arguments, but not an Array. 19 | * @return {Object} the destination object 20 | */ 21 | function mixin(dest /*, sources... */) { 22 | var i, l, source, name; 23 | 24 | if (!dest) { dest = {}; } 25 | for (i = 1, l = arguments.length; i < l; i += 1) { 26 | source = arguments[i]; 27 | for (name in source) { 28 | if (!(name in dest) || (dest[name] !== source[name] && (!(name in empty) || empty[name] !== source[name]))) { 29 | dest[name] = source[name]; 30 | } 31 | } 32 | } 33 | 34 | return dest; // Object 35 | } 36 | 37 | module.exports = mixin; 38 | -------------------------------------------------------------------------------- /util/lazyPromise.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var attempt = require('./attempt'); 11 | 12 | /** 13 | * Create a promise whose work is started only when a handler is registered. 14 | * 15 | * The work function will be invoked at most once. Thrown values will result 16 | * in promise rejection. 17 | * 18 | * @param {Function} work function whose ouput is used to resolve the 19 | * returned promise. 20 | * @returns {Promise} a lazy promise 21 | */ 22 | function lazyPromise(work) { 23 | var started, resolver, promise, then; 24 | 25 | started = false; 26 | 27 | promise = new Promise(function (resolve, reject) { 28 | resolver = { 29 | resolve: resolve, 30 | reject: reject 31 | }; 32 | }); 33 | then = promise.then; 34 | 35 | promise.then = function () { 36 | if (!started) { 37 | started = true; 38 | attempt(work).then(resolver.resolve, resolver.reject); 39 | } 40 | return then.apply(promise, arguments); 41 | }; 42 | 43 | return promise; 44 | } 45 | 46 | module.exports = lazyPromise; 47 | -------------------------------------------------------------------------------- /interceptor/pathPrefix.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, UrlBuilder; 11 | 12 | interceptor = require('../interceptor'); 13 | UrlBuilder = require('../UrlBuilder'); 14 | 15 | function startsWith(str, prefix) { 16 | return str.indexOf(prefix) === 0; 17 | } 18 | 19 | function endsWith(str, suffix) { 20 | return str.lastIndexOf(suffix) + suffix.length === str.length; 21 | } 22 | 23 | /** 24 | * Prefixes the request path with a common value. 25 | * 26 | * @param {Client} [client] client to wrap 27 | * @param {number} [config.prefix] path prefix 28 | * 29 | * @returns {Client} 30 | */ 31 | module.exports = interceptor({ 32 | request: function (request, config) { 33 | var path; 34 | 35 | if (config.prefix && !(new UrlBuilder(request.path).isFullyQualified())) { 36 | path = config.prefix; 37 | if (request.path) { 38 | if (!endsWith(path, '/') && !startsWith(request.path, '/')) { 39 | // add missing '/' between path sections 40 | path += '/'; 41 | } 42 | path += request.path; 43 | } 44 | request.path = path; 45 | } 46 | 47 | return request; 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /test/util/base64-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/util/base64-test', function (require) { 17 | 18 | var base64 = require('rest/util/base64'); 19 | 20 | buster.testCase('rest/util/base64', { 21 | 'should base64 encode strings': function () { 22 | assert.equals('Zm9v', base64.encode('foo')); 23 | }, 24 | 'should base64 decode strings': function () { 25 | assert.equals('foo', base64.decode('Zm9v')); 26 | } 27 | }); 28 | 29 | }); 30 | 31 | }( 32 | this.buster || require('buster'), 33 | typeof define === 'function' && define.amd ? define : function (id, factory) { 34 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 35 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 36 | factory(function (moduleId) { 37 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 38 | }); 39 | } 40 | // Boilerplate for AMD and Node 41 | )); 42 | -------------------------------------------------------------------------------- /interceptor/template.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, uriTemplate, mixin; 11 | 12 | interceptor = require('../interceptor'); 13 | uriTemplate = require('../util/uriTemplate'); 14 | mixin = require('../util/mixin'); 15 | 16 | /** 17 | * Applies request params to the path as a URI Template 18 | * 19 | * Params are removed from the request object, as they have been consumed. 20 | * 21 | * @see https://tools.ietf.org/html/rfc6570 22 | * 23 | * @param {Client} [client] client to wrap 24 | * @param {Object} [config.params] default param values 25 | * @param {string} [config.template] default template 26 | * 27 | * @returns {Client} 28 | */ 29 | module.exports = interceptor({ 30 | init: function (config) { 31 | config.params = config.params || {}; 32 | config.template = config.template || ''; 33 | return config; 34 | }, 35 | request: function (request, config) { 36 | var template, params; 37 | 38 | template = request.path || config.template; 39 | params = mixin({}, request.params, config.params); 40 | 41 | request.path = uriTemplate.expand(template, params); 42 | delete request.params; 43 | 44 | return request; 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /util/pubsub.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | // A poor man's pub-sub. A single listener is supported per topic. When 11 | // the topic is published, the listener is unsubscribed. 12 | 13 | var topics = {}; 14 | 15 | /** 16 | * Publishes the message to the topic, invoking the listener. 17 | * 18 | * The listener is unsubscribed from the topic after receiving a message. 19 | * 20 | * @param {string} topic the topic to publish to 21 | * @param {Object} message message to publish 22 | */ 23 | function publish(topic /* , message... */) { 24 | if (!topics[topic]) { return; } 25 | topics[topic].apply({}, Array.prototype.slice.call(arguments, 1)); 26 | // auto cleanup 27 | delete topics[topic]; 28 | } 29 | 30 | /** 31 | * Register a callback function to receive notification of a message published to the topic. 32 | * 33 | * Any existing callback for the topic will be unsubscribed. 34 | * 35 | * @param {string} topic the topic to listen on 36 | * @param {Function} callback the callback to receive the message published to the topic 37 | */ 38 | function subscribe(topic, callback) { 39 | topics[topic] = callback; 40 | } 41 | 42 | module.exports = { 43 | publish: publish, 44 | subscribe: subscribe 45 | }; 46 | -------------------------------------------------------------------------------- /test/buster.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2015 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | var config = exports; 9 | 10 | if (typeof Promise === 'undefined') { 11 | require('when/es6-shim/Promise'); 12 | } 13 | 14 | config['rest:node'] = { 15 | environment: 'node', 16 | rootPath: '../', 17 | tests: [ 18 | 'test/**/*-test.js', 19 | 'test/**/*-test-node.js' 20 | ], 21 | testHelpers: ['test/failOnThrow.js'] 22 | }; 23 | 24 | config['rest:browser'] = { 25 | environment: 'browser', 26 | autoRun: false, 27 | rootPath: '../', 28 | resources: [ 29 | //'**', ** is busted in buster 30 | '*.js', 31 | 'client/**/*.js', 32 | 'interceptor/**/*.js', 33 | 'mime/**/*.js', 34 | 'parsers/**/*.js', 35 | 'util/**/*.js', 36 | 'node_modules/curl/**/*.js', 37 | 'node_modules/poly/**/*.js', 38 | 'node_modules/when/**/*.js', 39 | 'node_modules/wire/**/*.js', 40 | 'test/**/fixtures/**', 41 | { path: '/wait', backend: 'http://example.com' } 42 | ], 43 | libs: [ 44 | 'node_modules/when/es6-shim/Promise.js', 45 | 'test/curl-config.js', 46 | 'node_modules/curl/src/curl.js' 47 | ], 48 | sources: [ 49 | // loaded as resources 50 | ], 51 | tests: [ 52 | 'test/**/*-test.js', 53 | 'test/**/*-test-browser.js', 54 | 'test/run.js' 55 | ], 56 | testHelpers: ['test/failOnThrow.js'] 57 | }; 58 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2015 the original author or authors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | 22 | --- 23 | 24 | Code published by Scott Andrews or Jeremy Grelle prior to 2013-09-21 is 25 | copyright Pivotal and licensed under the above terms. 26 | 27 | GoPivotal, Inc. 28 | 1900 South Norfolk Street, Suite 125 29 | San Mateo, CA 94403 30 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * Add common helper methods to a client impl 12 | * 13 | * @param {function} impl the client implementation 14 | * @param {Client} [target] target of this client, used when wrapping other clients 15 | * @returns {Client} the client impl with additional methods 16 | */ 17 | module.exports = function client(impl, target) { 18 | 19 | if (target) { 20 | 21 | /** 22 | * @returns {Client} the target client 23 | */ 24 | impl.skip = function skip() { 25 | return target; 26 | }; 27 | 28 | } 29 | 30 | /** 31 | * Allow a client to easily be wrapped by an interceptor 32 | * 33 | * @param {Interceptor} interceptor the interceptor to wrap this client with 34 | * @param [config] configuration for the interceptor 35 | * @returns {Client} the newly wrapped client 36 | */ 37 | impl.wrap = function wrap(interceptor, config) { 38 | return interceptor(impl, config); 39 | }; 40 | 41 | /** 42 | * @deprecated 43 | */ 44 | impl.chain = function chain() { 45 | if (typeof console !== 'undefined') { 46 | console.log('rest.js: client.chain() is deprecated, use client.wrap() instead'); 47 | } 48 | 49 | return impl.wrap.apply(this, arguments); 50 | }; 51 | 52 | return impl; 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /interceptor/params.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor, UrlBuilder; 11 | 12 | interceptor = require('../interceptor'); 13 | UrlBuilder = require('../UrlBuilder'); 14 | 15 | /** 16 | * Applies request params to the path by token replacement 17 | * 18 | * Params not applied as a token are appended to the query string. Params 19 | * are removed from the request object, as they have been consumed. 20 | * 21 | * @deprecated The template interceptor `rest/interceptor/template` is a 22 | * much richer way to apply paramters to a template. This interceptor is 23 | * available as a bridge to users who previousled depended on this 24 | * functionality being available directly on clients. 25 | * 26 | * @param {Client} [client] client to wrap 27 | * @param {Object} [config.params] default param values 28 | * 29 | * @returns {Client} 30 | */ 31 | module.exports = interceptor({ 32 | init: function (config) { 33 | config.params = config.params || {}; 34 | return config; 35 | }, 36 | request: function (request, config) { 37 | var path, params; 38 | 39 | path = request.path || ''; 40 | params = request.params || {}; 41 | 42 | request.path = new UrlBuilder(path, config.params).append('', params).build(); 43 | delete request.params; 44 | 45 | return request; 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rest", 3 | "version": "2.0.0", 4 | "description": "RESTful HTTP client library", 5 | "keywords": ["rest", "http", "client", "rest-template", "spring", "cujojs"], 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/cujojs/rest.git" 10 | }, 11 | "bugs": "https://github.com/cujojs/rest/issues", 12 | "maintainers": [ 13 | { 14 | "name": "Scott Andrews", 15 | "email": "scothis@gmail.com", 16 | "web": "http://twitter.com/scothis" 17 | } 18 | ], 19 | "contributors": [ 20 | { 21 | "name": "Jeremy Grelle", 22 | "email": "jeremy.grelle@gmail.com" 23 | }, 24 | { 25 | "name": "John Hann", 26 | "web": "http://unscriptable.com" 27 | }, 28 | { 29 | "name": "Michael Jackson", 30 | "web": "https://github.com/mjackson" 31 | } 32 | ], 33 | "dependencies": {}, 34 | "devDependencies": { 35 | "when": "~3", 36 | "wire": "~0.9", 37 | "test-support": "~0.4", 38 | "curl": "https://github.com/cujojs/curl/tarball/0.7.3", 39 | "poly": "https://github.com/cujojs/poly/tarball/0.5.1" 40 | }, 41 | "main": "./node", 42 | "browser": "./browser", 43 | "jspm": { 44 | "registry": "npm", 45 | "main": "./browser" 46 | }, 47 | "scripts": { 48 | "test": "npm run-script lint && npm run-script buster", 49 | "start": "buster static -e browser", 50 | "tunnel": "sauceme -m", 51 | "lint": "jshint .", 52 | "buster": "buster test --node", 53 | "sauceme": "sauceme" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/mime/type/text/plain-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/mime/type/text/plain-test', function (require) { 17 | 18 | var plain = require('rest/mime/type/text/plain'); 19 | 20 | buster.testCase('rest/mime/type/text/plain', { 21 | 'should not change when writing string values': function () { 22 | assert.equals('7', plain.write('7')); 23 | }, 24 | 'should use the string representation for reading non-string values': function () { 25 | assert.equals('7', plain.write(7)); 26 | }, 27 | 'should not change when reading string values': function () { 28 | assert.equals('7', plain.read('7')); 29 | } 30 | }); 31 | 32 | }); 33 | 34 | }( 35 | this.buster || require('buster'), 36 | typeof define === 'function' && define.amd ? define : function (id, factory) { 37 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 38 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 39 | factory(function (moduleId) { 40 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 41 | }); 42 | } 43 | // Boilerplate for AMD and Node 44 | )); 45 | -------------------------------------------------------------------------------- /test/util/normalizeHeaderName-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/util/normalizeHeaderName-test', function (require) { 17 | 18 | var normalizeHeaderName = require('rest/util/normalizeHeaderName'); 19 | 20 | buster.testCase('rest/util/normalizeHeaderName', { 21 | 'should normalize header names': function () { 22 | assert.equals('Accept', normalizeHeaderName('accept')); 23 | assert.equals('Accept', normalizeHeaderName('ACCEPT')); 24 | assert.equals('Content-Length', normalizeHeaderName('content-length')); 25 | assert.equals('X-Some-Custom-Header', normalizeHeaderName('x-some-custom-header')); 26 | } 27 | }); 28 | 29 | }); 30 | 31 | }( 32 | this.buster || require('buster'), 33 | typeof define === 'function' && define.amd ? define : function (id, factory) { 34 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 35 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 36 | factory(function (moduleId) { 37 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 38 | }); 39 | } 40 | // Boilerplate for AMD and Node 41 | )); 42 | -------------------------------------------------------------------------------- /test/node-test-node.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail, failOnThrow; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | failOnThrow = buster.assertions.failOnThrow; 17 | 18 | define('rest-test/node-test', function (require) { 19 | 20 | var rest, defaultClient, node; 21 | 22 | rest = require('rest/node'); 23 | defaultClient = require('rest/client/default'); 24 | node = require('rest/client/node'); 25 | 26 | buster.testCase('rest/node', { 27 | 'should be the default client': function () { 28 | assert.same(defaultClient, rest); 29 | }, 30 | 'should wrap the node client': function () { 31 | assert.same(node, rest.getDefaultClient()); 32 | } 33 | }); 34 | 35 | }); 36 | 37 | }( 38 | this.buster || require('buster'), 39 | typeof define === 'function' && define.amd ? define : function (id, factory) { 40 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 41 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 42 | factory(function (moduleId) { 43 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 44 | }); 45 | } 46 | // Boilerplate for AMD and Node 47 | )); 48 | -------------------------------------------------------------------------------- /test/browser-test-browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute, fail, failOnThrow; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | fail = buster.assertions.fail; 16 | failOnThrow = buster.assertions.failOnThrow; 17 | 18 | define('rest-test/browser-test', function (require) { 19 | 20 | var rest, defaultClient, xhr; 21 | 22 | rest = require('rest/browser'); 23 | defaultClient = require('rest/client/default'); 24 | xhr = require('rest/client/xhr'); 25 | 26 | buster.testCase('rest/browser', { 27 | 'should be the default client': function () { 28 | assert.same(defaultClient, rest); 29 | }, 30 | 'should wrap the xhr client': function () { 31 | assert.same(xhr, rest.getDefaultClient()); 32 | } 33 | }); 34 | 35 | }); 36 | 37 | }( 38 | this.buster || require('buster'), 39 | typeof define === 'function' && define.amd ? define : function (id, factory) { 40 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 41 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 42 | factory(function (moduleId) { 43 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 44 | }); 45 | } 46 | // Boilerplate for AMD and Node 47 | )); 48 | -------------------------------------------------------------------------------- /interceptor/location.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor; 11 | 12 | interceptor = require('../interceptor'); 13 | 14 | function isRedirect(response, config) { 15 | var matchesRedirectCode = config.code === 0 || (response.status && response.status.code >= config.code); 16 | return response.headers && response.headers.Location && matchesRedirectCode; 17 | } 18 | 19 | /** 20 | * Follows the Location header in a response, if present. The response 21 | * returned is for the subsequent request. 22 | * 23 | * Most browsers will automatically follow HTTP 3xx redirects, however, 24 | * they will not automatically follow 2xx locations. 25 | * 26 | * @param {Client} [client] client to wrap 27 | * @param {Client} [config.client=request.originator] client to use for subsequent request 28 | * 29 | * @returns {Client} 30 | */ 31 | module.exports = interceptor({ 32 | init: function (config) { 33 | config.code = config.code || 0; 34 | return config; 35 | }, 36 | success: function (response, config, client) { 37 | var request; 38 | 39 | if (isRedirect(response, config)) { 40 | request = response.request || {}; 41 | client = (config.client || request.originator || client.skip()); 42 | 43 | return client({ 44 | method: 'GET', 45 | path: response.headers.Location 46 | }); 47 | } 48 | 49 | return response; 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /test/util/find-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | (function (buster, define) { 9 | 'use strict'; 10 | 11 | var assert, refute; 12 | 13 | assert = buster.assertions.assert; 14 | refute = buster.assertions.refute; 15 | 16 | define('rest-test/util/find-test', function (require) { 17 | 18 | var find = require('rest/util/find'); 19 | 20 | buster.testCase('rest/util/find', { 21 | 'should find objects that contain a given property name': function () { 22 | var graph, spy; 23 | 24 | graph = { foo: { foo: {}, bar: { bar: { foo: {} } } } }; 25 | spy = this.spy(); 26 | 27 | find.findProperties(graph, 'foo', spy); 28 | 29 | assert.same(3, spy.callCount); 30 | assert.calledWith(spy, graph.foo, graph, 'foo'); 31 | assert.calledWith(spy, graph.foo.foo, graph.foo, 'foo'); 32 | assert.calledWith(spy, graph.foo.bar.bar.foo, graph.foo.bar.bar, 'foo'); 33 | } 34 | }); 35 | }); 36 | 37 | }( 38 | this.buster || require('buster'), 39 | typeof define === 'function' && define.amd ? define : function (id, factory) { 40 | var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..'); 41 | pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot; 42 | factory(function (moduleId) { 43 | return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId); 44 | }); 45 | } 46 | // Boilerplate for AMD and Node 47 | )); 48 | -------------------------------------------------------------------------------- /interceptor/csrf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Scott Andrews 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var interceptor; 11 | 12 | interceptor = require('../interceptor'); 13 | 14 | /** 15 | * Applies a Cross-Site Request Forgery protection header to a request 16 | * 17 | * CSRF protection helps a server verify that a request came from a 18 | * trusted client and not another client that was able to masquerade 19 | * as an authorized client. Sites that use cookie based authentication 20 | * are particularly vulnerable to request forgeries without extra 21 | * protection. 22 | * 23 | * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery 24 | * 25 | * @param {Client} [client] client to wrap 26 | * @param {string} [config.name='X-Csrf-Token'] name of the request 27 | * header, may be overridden by `request.csrfTokenName` 28 | * @param {string} [config.token] CSRF token, may be overridden by 29 | * `request.csrfToken` 30 | * 31 | * @returns {Client} 32 | */ 33 | module.exports = interceptor({ 34 | init: function (config) { 35 | config.name = config.name || 'X-Csrf-Token'; 36 | return config; 37 | }, 38 | request: function handleRequest(request, config) { 39 | var headers, name, token; 40 | 41 | headers = request.headers || (request.headers = {}); 42 | name = request.csrfTokenName || config.name; 43 | token = request.csrfToken || config.token; 44 | 45 | if (token) { 46 | headers[name] = token; 47 | } 48 | 49 | return request; 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /interceptor/retry.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Jeremy Grelle 6 | * @author Scott Andrews 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var interceptor, delay; 12 | 13 | interceptor = require('../interceptor'); 14 | delay = require('../util/delay'); 15 | 16 | /** 17 | * Retries a rejected request using an exponential backoff. 18 | * 19 | * Defaults to an initial interval of 100ms, a multiplier of 2, and no max interval. 20 | * 21 | * @param {Client} [client] client to wrap 22 | * @param {number} [config.intial=100] initial interval in ms 23 | * @param {number} [config.multiplier=2] interval multiplier 24 | * @param {number} [config.max] max interval in ms 25 | * 26 | * @returns {Client} 27 | */ 28 | module.exports = interceptor({ 29 | init: function (config) { 30 | config.initial = config.initial || 100; 31 | config.multiplier = config.multiplier || 2; 32 | config.max = config.max || Infinity; 33 | return config; 34 | }, 35 | error: function (response, config, meta) { 36 | var request; 37 | 38 | request = response.request; 39 | request.retry = request.retry || config.initial; 40 | 41 | return delay(request.retry, request).then(function (request) { 42 | if (request.canceled) { 43 | // cancel here in case client doesn't check canceled flag 44 | return Promise.reject({ request: request, error: 'precanceled' }); 45 | } 46 | request.retry = Math.min(request.retry * config.multiplier, config.max); 47 | return meta.client(request); 48 | }); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /mime/type/multipart/form-data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 the original author or authors 3 | * @license MIT, see LICENSE.txt for details 4 | * 5 | * @author Michael Jackson 6 | */ 7 | 8 | /* global FormData, File, Blob */ 9 | 10 | 'use strict'; 11 | 12 | function isFormElement(object) { 13 | return object && 14 | object.nodeType === 1 && // Node.ELEMENT_NODE 15 | object.tagName === 'FORM'; 16 | } 17 | 18 | function createFormDataFromObject(object) { 19 | var formData = new FormData(); 20 | 21 | var value; 22 | for (var property in object) { 23 | if (object.hasOwnProperty(property)) { 24 | value = object[property]; 25 | 26 | if (value instanceof File) { 27 | formData.append(property, value, value.name); 28 | } else if (value instanceof Blob) { 29 | formData.append(property, value); 30 | } else { 31 | formData.append(property, String(value)); 32 | } 33 | } 34 | } 35 | 36 | return formData; 37 | } 38 | 39 | module.exports = { 40 | 41 | write: function (object) { 42 | if (typeof FormData === 'undefined') { 43 | throw new Error('The multipart/form-data mime serializer requires FormData support'); 44 | } 45 | 46 | // Support FormData directly. 47 | if (object instanceof FormData) { 48 | return object; 49 | } 50 | 51 | // Support