├── .eslintrc.json ├── .gitignore ├── .nvmrc ├── .prettierrc ├── .travis.yml ├── LICENSE ├── README.md ├── lib ├── cache.js ├── cache.test.js ├── client.js ├── client.test.js ├── connection.js ├── connection.test.js └── schema.js ├── package-lock.json └── package.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "node": true 5 | }, 6 | "rules": { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .fwd 2 | node_modules 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v8.9.1 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "bracketSpacing": true, 6 | "semi": true, 7 | "arrowParens": always 8 | } 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.11" 5 | - "0.12" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014-2015 Schema 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Schema API Client for NodeJS [![Build Status](https://travis-ci.org/schemaio/schema-node-client.png?branch=master)](https://travis-ci.org/schemaio/schema-node-client) 2 | 3 | Build and scale ecommerce with Schema. Create your account at 4 | 5 | ## Install 6 | 7 | npm install schema-client 8 | 9 | ## Connect 10 | 11 | ```javascript 12 | const Schema = require('schema-client'); 13 | 14 | const client = new Schema.Client('', ''); 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```javascript 20 | client.get('/products', { active: true }).then(products => { 21 | console.log(products); 22 | }).catch(err => { 23 | // handle error 24 | }); 25 | ``` 26 | 27 | ## Caching 28 | 29 | As of v3, this client provides in-memory caching enabled by default. It uses a version clocking protocol that means you never have to worry about stale cache, and collections that don't change frequently, such as products, will always return from cache when possible. 30 | 31 | To disable caching behavior, initialize the client with `cache: false`. 32 | 33 | ```javascript 34 | new Schema.Client('', '', { 35 | cache: false, 36 | }); 37 | ``` 38 | 39 | ## Documentation 40 | 41 | See for more API docs and usage examples 42 | 43 | ## Contributing 44 | 45 | Pull requests are welcome 46 | 47 | ## License 48 | 49 | MIT 50 | -------------------------------------------------------------------------------- /lib/cache.js: -------------------------------------------------------------------------------- 1 | var inherits = require('util').inherits; 2 | var events = require('events'); 3 | var crypto = require('crypto'); 4 | var fs = require('fs'); 5 | 6 | var DEFAULT_STORAGE = 'memory'; 7 | var DEFAULT_WRITE_PERMS = '0644'; 8 | var DEFAULT_INDEX_LIMIT = 1000; 9 | 10 | /** 11 | * Cache constructor 12 | * 13 | * @param string clientId 14 | * @param array options 15 | */ 16 | var Cache = function(clientId, options) { 17 | events.EventEmitter.call(this); 18 | 19 | this.versions = null; 20 | this.indexes = null; 21 | this.memory = {}; 22 | 23 | options = options || {}; 24 | if (typeof options === 'string') { 25 | options = { path: options }; 26 | } 27 | this.params = { 28 | clientId: clientId, 29 | path: options.path ? String(options.path) : '', 30 | storage: options.storage || DEFAULT_STORAGE, 31 | writePerms: options.writePerms || DEFAULT_WRITE_PERMS, 32 | indexLimit: options.indexLimit || DEFAULT_INDEX_LIMIT, 33 | }; 34 | 35 | if (this.params.storage !== 'memory') { 36 | throw new Error(this.params.storage + ' storage is not currently supported'); 37 | } 38 | }; 39 | 40 | inherits(Cache, events.EventEmitter); 41 | 42 | /** 43 | * Get result from cache by url/data 44 | * 45 | * @param string url 46 | * @param mixed data 47 | */ 48 | Cache.prototype.get = function(url, data) { 49 | data = data || null; 50 | 51 | var cacheKey = this.getKey(url, data); 52 | var result = this.getCache(cacheKey, 'result'); 53 | 54 | if (result) { 55 | // Ensure cache_key exists in index 56 | this.getIndex(); 57 | if (result.$collection !== undefined) { 58 | var collection = result.$collection; 59 | if (this.indexes[collection] && this.indexes[collection][cacheKey]) { 60 | return result; 61 | } 62 | } 63 | 64 | // Not found in proper index, then clear? 65 | var resultCollections = this.resultCollections(result); 66 | for (var i = 0; i < resultCollections.length; i++) { 67 | var collection = resultCollections[i]; 68 | var where = {}; 69 | where[collection] = cacheKey; 70 | this.clearIndexes(where); 71 | } 72 | } 73 | 74 | return null; 75 | }; 76 | 77 | /** 78 | * Get a cache key 79 | * 80 | * @param string url 81 | * @param mixed data 82 | * @return string 83 | */ 84 | Cache.prototype.getKey = function(url, data) { 85 | data = data || null; 86 | var saneUrl = String(url) 87 | .trim() 88 | .replace(/^\/|\/$/g, ''); 89 | var keyData = JSON.stringify([saneUrl, data]); 90 | return crypto 91 | .createHash('md5') 92 | .update(keyData) 93 | .digest('hex'); 94 | }; 95 | 96 | /** 97 | * Get path to a cache file 98 | * 99 | * @return string 100 | */ 101 | Cache.prototype.getPath = function(url, data) { 102 | return ( 103 | this.params.path.replace(/\/$/, '') + 104 | '/client.' + 105 | this.params.clientId + 106 | '.' + 107 | Array.prototype.slice.call(arguments).join('.') 108 | ); 109 | }; 110 | 111 | /** 112 | * Get cache version info 113 | * 114 | * @return array 115 | */ 116 | Cache.prototype.getVersions = function() { 117 | if (!this.versions) { 118 | this.versions = this.getCache('versions') || {}; 119 | } 120 | return this.versions; 121 | }; 122 | 123 | /** 124 | * Get cache index info 125 | * 126 | * @return array 127 | */ 128 | Cache.prototype.getIndex = function() { 129 | if (!this.indexes) { 130 | this.indexes = this.getCache('index') || {}; 131 | } 132 | return this.indexes; 133 | }; 134 | 135 | /** 136 | * Put cache result in storage atomicly 137 | * 138 | * @param string url 139 | * @param mixed data 140 | * @param mixed result 141 | */ 142 | Cache.prototype.put = function(url, data, result) { 143 | if (result.$data === undefined) { 144 | result.$data = null; // Allows for null response 145 | } 146 | 147 | this.getVersions(); 148 | 149 | var cacheContent = {}; 150 | var keys = Object.keys(result); 151 | for (var i = 0; i < keys.length; i++) { 152 | cacheContent[keys[i]] = result[keys[i]]; 153 | } 154 | cacheContent.$cached = true; 155 | 156 | var cacheKey = this.getKey(url, data); 157 | var cachePath = this.getPath(cacheKey, 'result'); 158 | 159 | var size = this.writeCache(cachePath, cacheContent); 160 | 161 | if (size > 0) { 162 | if (result.$cached !== undefined) { 163 | var cached = result.$cached; 164 | var resultCollections = this.resultCollections(result); 165 | for (var i = 0; i < resultCollections.length; i++) { 166 | var collection = resultCollections[i]; 167 | // Collection may not be cacheable 168 | if (cached[collection] === undefined && this.versions[collection] === undefined) { 169 | continue; 170 | } 171 | this.putIndex(collection, cacheKey, size); 172 | if (cached[collection] !== undefined) { 173 | this.putVersion(collection, cached[collection]); 174 | } 175 | } 176 | } 177 | } 178 | }; 179 | 180 | /** 181 | * Update/write the cache index 182 | * 183 | * @param string collection 184 | * @param string key 185 | * @param string size 186 | */ 187 | Cache.prototype.putIndex = function(collection, key, size) { 188 | this.getIndex(); 189 | 190 | // Limit size of index per client/collection 191 | if (this.indexes[collection] !== undefined) { 192 | if (Object.keys(this.indexes[collection]).length >= this.params.indexLimit) { 193 | this.truncateIndex(collection); 194 | } 195 | } 196 | 197 | this.indexes[collection] = this.indexes[collection] || {}; 198 | this.indexes[collection][key] = size; 199 | 200 | var indexPath = this.getPath('index'); 201 | 202 | return this.writeCache(indexPath, this.indexes); 203 | }; 204 | 205 | /** 206 | * Remove an entry from cache base on url and data 207 | * This is mostly used for caching variables as opposed to client results 208 | * 209 | * @param string url 210 | * @param mixed data 211 | */ 212 | Cache.prototype.remove = function(url, data) { 213 | data = data || null; 214 | var cacheKey = this.getKey(url, data); 215 | var cachePath = this.getPath(cacheKey, 'result'); 216 | this.clearCache(cachePath); 217 | }; 218 | 219 | /** 220 | * Truncate the cache index (usually by 1) 221 | * Prefers to eject the smallest cache content first 222 | * 223 | * @param string collection 224 | * @return bool 225 | */ 226 | Cache.prototype.truncateIndex = function(collection) { 227 | this.getIndex(); 228 | if (this.indexes[collection] === undefined) { 229 | return; 230 | } 231 | var keys = Object.keys(this.indexes[collection]); 232 | var lastKey = keys[keys.length - 1]; 233 | var invalid = {}; 234 | invalid[collection] = lastKey; 235 | this.clearIndexes(invalid); 236 | }; 237 | 238 | /** 239 | * Update/write the cache version file 240 | * 241 | * @param string collection 242 | * @param number version 243 | * @return number 244 | */ 245 | Cache.prototype.putVersion = function(collection, version) { 246 | if (!version) { 247 | return; 248 | } 249 | this.getVersions(); 250 | this.versions[collection] = version; 251 | var versionPath = this.getPath('versions'); 252 | this.writeCache(versionPath, this.versions); 253 | }; 254 | 255 | /** 256 | * Clear all cache entries made invalid by result 257 | * 258 | * @param mixed result 259 | */ 260 | Cache.prototype.clear = function(result) { 261 | if (result.$cached === undefined) { 262 | return; 263 | } 264 | 265 | this.getVersions(); 266 | 267 | var invalid = {}; 268 | var cachedCollections = Object.keys(result.$cached); 269 | for (var i = 0; i < cachedCollections.length; i++) { 270 | var collection = cachedCollections[i]; 271 | var version = result.$cached[collection]; 272 | if (this.versions[collection] === undefined || version !== this.versions[collection]) { 273 | this.putVersion(collection, version); 274 | invalid[collection] = true; 275 | // Hack to make admin.settings affect other api.settings 276 | // TODO: figure out how to do this on the server side 277 | if (collection === 'admin.settings') { 278 | var versionCollections = Object.keys(this.versions); 279 | for (var j = 0; j < versionCollections.length; j++) { 280 | var verCollection = versionCollections[j]; 281 | if (String(verCollection).match(/\.settings$/)) { 282 | invalid[verCollection] = true; 283 | } 284 | } 285 | } 286 | } 287 | } 288 | 289 | if (Object.keys(invalid).length > 0) { 290 | this.clearIndexes(invalid); 291 | } 292 | }; 293 | 294 | /** 295 | * Clear cache index for a certain collection 296 | * 297 | * @param array invalid 298 | */ 299 | Cache.prototype.clearIndexes = function(invalid) { 300 | if (!invalid || Object.keys(invalid).length === 0) { 301 | return; 302 | } 303 | 304 | this.getIndex(); 305 | var invalidCollections = Object.keys(invalid); 306 | for (var i = 0; i < invalidCollections.length; i++) { 307 | var collection = invalidCollections[i]; 308 | if (this.indexes[collection] !== undefined) { 309 | if (invalid[collection] === true) { 310 | // Clear all indexes per collection 311 | var cacheKeys = Object.keys(this.indexes[collection]); 312 | for (var j = 0; j < cacheKeys.length; j++) { 313 | var key = cacheKeys[j]; 314 | var cachePath = this.getPath(key, 'result'); 315 | this.clearCache(cachePath); 316 | delete this.indexes[collection][key]; 317 | } 318 | } else if ( 319 | invalid[collection] && 320 | this.indexes[collection][invalid[collection]] !== undefined 321 | ) { 322 | // Clear a single index element by key 323 | var key = invalid[collection]; 324 | var cachePath = this.getPath(key, 'result'); 325 | this.clearCache(cachePath); 326 | delete this.indexes[collection][key]; 327 | } 328 | } 329 | } 330 | 331 | var indexPath = this.getPath('index'); 332 | this.writeCache(indexPath, this.indexes); 333 | }; 334 | 335 | /** 336 | * Get cache content 337 | * 338 | * @return string 339 | */ 340 | Cache.prototype.getCache = function() { 341 | var cachePath = this.getPath.apply(this, arguments); 342 | if (this.memory[cachePath] !== undefined) { 343 | return JSON.parse(this.memory[cachePath]); 344 | } 345 | return null; 346 | }; 347 | 348 | /** 349 | * Write to cache atomically 350 | * 351 | * @param string cachePath 352 | * @param mixed content 353 | * @return number 354 | */ 355 | Cache.prototype.writeCache = function(cachePath, content) { 356 | var cacheContent = JSON.stringify(content); 357 | var cacheSize = cacheContent.length; 358 | 359 | // TODO: file system storage 360 | this.memory[cachePath] = cacheContent; 361 | 362 | return cacheSize; 363 | }; 364 | 365 | /** 366 | * Clear a cache path 367 | * 368 | * @param string cachePath 369 | */ 370 | Cache.prototype.clearCache = function(cachePath) { 371 | delete this.memory[cachePath]; 372 | }; 373 | 374 | /** 375 | * Get array of collections affected by a result 376 | * 377 | * @param array result 378 | * @return array 379 | */ 380 | Cache.prototype.resultCollections = function(result) { 381 | var collections = result.$collection !== undefined ? [result.$collection] : []; 382 | // Combine $collection and $expanded headers 383 | if (result.$expanded !== undefined) { 384 | for (var i = 0; i < result.$expanded.length; i++) { 385 | var expCollection = result.$expanded[i]; 386 | if (collections.indexOf(expCollection) === -1) { 387 | collections.push(expCollection); 388 | } 389 | } 390 | } 391 | return collections; 392 | }; 393 | 394 | // Exports 395 | exports.Cache = Cache; 396 | -------------------------------------------------------------------------------- /lib/cache.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var sinon = require('sinon'); 3 | var Cache = require('./cache').Cache; 4 | 5 | describe('Cache', function() { 6 | describe('#constructor', function() { 7 | it('builds params from defaults', function() { 8 | var cache = new Cache('test', {}); 9 | assert.deepEqual(cache.params, { 10 | clientId: 'test', 11 | path: '', 12 | storage: 'memory', 13 | writePerms: '0644', 14 | indexLimit: 1000, 15 | }); 16 | }); 17 | 18 | it('builds params from options', function() { 19 | var cache = new Cache('test', { 20 | path: '/', 21 | storage: 'memory', 22 | writePerms: '1234', 23 | indexLimit: 1, 24 | }); 25 | assert.deepEqual(cache.params, { 26 | clientId: 'test', 27 | path: '/', 28 | storage: 'memory', 29 | writePerms: '1234', 30 | indexLimit: 1, 31 | }); 32 | }); 33 | 34 | it('throws an error on invalid storage', function() { 35 | try { 36 | var cache = new Cache('test', { 37 | storage: 'fail', 38 | }); 39 | assert.fail('oops'); 40 | } catch (err) { 41 | assert.equal(err.message, 'fail storage is not currently supported'); 42 | } 43 | }); 44 | }); 45 | 46 | describe('#get', function() { 47 | var cache; 48 | 49 | beforeEach(function() { 50 | cache = new Cache('test'); 51 | }); 52 | 53 | it('returns null when cache index not found', function() { 54 | var result = cache.get('/', {}); 55 | assert.isNull(result); 56 | }); 57 | 58 | it('returns response object when cache index found', function() { 59 | cache.put( 60 | '/', 61 | {}, 62 | { 63 | $data: 'foo', 64 | $collection: 'bar', 65 | $cached: { bar: 1 }, 66 | }, 67 | ); 68 | var result = cache.get('/', {}); 69 | assert.deepEqual(result, { 70 | $data: 'foo', 71 | $collection: 'bar', 72 | $cached: true, 73 | }); 74 | }); 75 | }); 76 | 77 | describe('#getKey', function() { 78 | it('returns a predictable key for request args', function() { 79 | var cache = new Cache('test'); 80 | var key1 = cache.getKey('/test1', true); 81 | var key2 = cache.getKey('/test1', true); 82 | var key3 = cache.getKey('/test1', false); 83 | var key4 = cache.getKey('/other'); 84 | var key5 = cache.getKey(' other/ ', null); 85 | assert.ok(key1 && key2 && key3 && key4 && key5); 86 | assert.equal(key1, key2); 87 | assert.notEqual(key1, key3); 88 | assert.notEqual(key1, key4); 89 | assert.equal(key4, key5); 90 | }); 91 | }); 92 | 93 | describe('#getPath', function() { 94 | it('returns a cache path with arg', function() { 95 | var cache = new Cache('test'); 96 | var path = cache.getPath('index'); 97 | assert.equal(path, '/client.test.index'); 98 | }); 99 | 100 | it('returns a cache path with multiple args', function() { 101 | var cache = new Cache('test'); 102 | var path = cache.getPath('result', '12345'); 103 | assert.equal(path, '/client.test.result.12345'); 104 | }); 105 | 106 | it('returns a cache path with path param prepended', function() { 107 | var cache = new Cache('test', { path: '/test' }); 108 | var path = cache.getPath('result', '12345'); 109 | assert.equal(path, '/test/client.test.result.12345'); 110 | }); 111 | }); 112 | 113 | describe('#getVersions', function() { 114 | it('sets and returns version cache', function() { 115 | var cache = new Cache('test'); 116 | assert.isNull(cache.versions); 117 | var versions = cache.getVersions(); 118 | assert.deepEqual(versions, {}); 119 | }); 120 | 121 | it('sets and returns version cached earlier', function() { 122 | var cache = new Cache('test'); 123 | cache.putVersion('test', 1); 124 | var versions = cache.getVersions(); 125 | assert.ok(cache.versions === versions); 126 | assert.deepEqual(versions, { 127 | test: 1, 128 | }); 129 | }); 130 | }); 131 | 132 | describe('#getIndex', function() { 133 | it('sets and returns index cache', function() { 134 | var cache = new Cache('test'); 135 | assert.isNull(cache.indexes); 136 | var indexes = cache.getIndex(); 137 | assert.deepEqual(indexes, {}); 138 | }); 139 | 140 | it('sets and returns index cached earlier', function() { 141 | var cache = new Cache('test'); 142 | cache.putIndex('test', '12345', 100); 143 | var indexes = cache.getIndex(); 144 | assert.ok(cache.indexes === indexes); 145 | assert.deepEqual(indexes, { 146 | test: { '12345': 100 }, 147 | }); 148 | }); 149 | }); 150 | 151 | describe('#put', function() { 152 | var cache, response; 153 | 154 | beforeEach(function() { 155 | cache = new Cache('test'); 156 | response = { 157 | $data: 'foo', 158 | $collection: 'bar', 159 | $cached: { bar: 1 }, 160 | }; 161 | }); 162 | 163 | it('sets index, version and result cache', function() { 164 | cache.put('/', {}, response); 165 | assert.deepEqual(cache.getIndex(), { 166 | bar: { '58cd6550e4fe03ea78ee22cf52c759b7': 50 }, 167 | }); 168 | assert.deepEqual(cache.getVersions(), { 169 | bar: 1, 170 | }); 171 | assert.deepEqual(cache.get('/', {}), { 172 | $data: 'foo', 173 | $collection: 'bar', 174 | $cached: true, 175 | }); 176 | }); 177 | }); 178 | 179 | describe('#putIndex', function() { 180 | it('sets index cache with collection version and size', function() { 181 | var cache = new Cache('test'); 182 | cache.putIndex('bar', '12345', 100); 183 | var indexes = cache.getIndex(); 184 | assert.deepEqual(indexes, { 185 | bar: { '12345': 100 }, 186 | }); 187 | cache.putIndex('bar2', '123456', 1001); 188 | var indexes2 = cache.getIndex(); 189 | assert.deepEqual(indexes2, { 190 | bar: { '12345': 100 }, 191 | bar2: { '123456': 1001 }, 192 | }); 193 | }); 194 | 195 | it('resets existing item in index cache', function() { 196 | var cache = new Cache('test'); 197 | cache.putIndex('bar', '12345', 100); 198 | var indexes = cache.getIndex(); 199 | assert.deepEqual(indexes, { 200 | bar: { '12345': 100 }, 201 | }); 202 | cache.putIndex('bar', '12345', 1001); 203 | var indexes2 = cache.getIndex(); 204 | assert.deepEqual(indexes2, { 205 | bar: { '12345': 1001 }, 206 | }); 207 | }); 208 | }); 209 | 210 | describe('#remove', function() { 211 | it('removes an entry from result cache', function() { 212 | var cache = new Cache('test'); 213 | cache.put( 214 | '/', 215 | {}, 216 | { 217 | $data: 'foo', 218 | $collection: 'bar', 219 | $cached: { bar: 1 }, 220 | }, 221 | ); 222 | assert.deepEqual(cache.get('/', {}), { 223 | $data: 'foo', 224 | $collection: 'bar', 225 | $cached: true, 226 | }); 227 | cache.remove('/', {}); 228 | assert.deepEqual(cache.get('/', {}), null); 229 | }); 230 | }); 231 | }); 232 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | const events = require('events'); 2 | const crypto = require('crypto'); 3 | const inherits = require('util').inherits; 4 | const Promise = require('bluebird').Promise; 5 | 6 | const Schema = require('./schema'); 7 | Schema.Connection = require('./connection').Connection; 8 | Schema.Cache = require('./cache').Cache; 9 | 10 | const DEFAULT_HOST = 'api.schema.io'; 11 | const DEFAULT_PORT = 8443; 12 | const DEFAULT_VERIFY_CERT = true; 13 | const DEFAULT_VERSION = 1; 14 | 15 | const Client = function(clientId, clientKey, options, callback) { 16 | events.EventEmitter.call(this); 17 | 18 | this.server = null; 19 | this.cache = null; 20 | 21 | if (clientId) { 22 | this.init(clientId, clientKey, options); 23 | } 24 | if (callback) { 25 | this.connect(callback); 26 | } 27 | }; 28 | 29 | inherits(Client, events.EventEmitter); 30 | 31 | Client.prototype.init = function(clientId, clientKey, options) { 32 | options = options || {}; 33 | 34 | if (typeof clientKey === 'object') { 35 | options = clientKey; 36 | clientKey = undefined; 37 | } else if (typeof clientId === 'object') { 38 | options = clientId; 39 | clientId = undefined; 40 | } 41 | 42 | this.params = { 43 | clientId: clientId || options.id, 44 | clientKey: clientKey || options.key, 45 | host: options.host || DEFAULT_HOST, 46 | port: options.port || DEFAULT_PORT, 47 | verifyCert: options.verifyCert !== undefined ? options.verifyCert : DEFAULT_VERIFY_CERT, 48 | version: options.version || DEFAULT_VERSION, 49 | session: options.session, 50 | route: options.route, 51 | timeout: options.timeout, 52 | routeClientId: options.route && options.route.client, 53 | cache: typeof options.cache !== 'undefined' ? options.cache : true, 54 | }; 55 | 56 | if (!this.params.clientId) { 57 | throw new Error('Schema client `id` is required to initialize'); 58 | } 59 | if (!this.params.clientKey) { 60 | throw new Error('Schema client `key` is required to initialize'); 61 | } 62 | }; 63 | 64 | Client.prototype.connect = function(callback) { 65 | var self = this; 66 | this.server = new Schema.Connection( 67 | this.params.host, 68 | this.params.port, 69 | { 70 | verifyCert: this.params.verifyCert, 71 | timeout: self.params.timeout, 72 | }, 73 | function() { 74 | callback && callback(self); 75 | self.emit('connect', self); 76 | }, 77 | ); 78 | this.server.on('close', function() { 79 | self.emit('close'); 80 | }); 81 | this.server.on('error', function(err) { 82 | if (events.EventEmitter.listenerCount(self, 'error')) { 83 | self.emit('error', 'Error: ' + err); 84 | } 85 | }); 86 | this.server.on('error.network', function(err) { 87 | if (events.EventEmitter.listenerCount(self, 'error')) { 88 | self.emit('error', 'Network Error: ' + err, 'network'); 89 | } 90 | }); 91 | this.server.on('error.protocol', function(err) { 92 | if (events.EventEmitter.listenerCount(self, 'error')) { 93 | self.emit('error', 'Protocol Error: ' + err, 'protocol'); 94 | } 95 | }); 96 | this.server.on('error.server', function(err) { 97 | if (events.EventEmitter.listenerCount(self, 'error')) { 98 | self.emit('error', 'Server Error: ' + err, 'server'); 99 | } 100 | }); 101 | }; 102 | 103 | Client.prototype.request = function(method, url, data, callback) { 104 | if (typeof data === 'function') { 105 | callback = data; 106 | data = null; 107 | } 108 | 109 | if (!this.cache && this.params.cache) { 110 | var clientId = this.params.routeClientId || this.params.clientId; 111 | this.cache = new Schema.Cache(clientId, this.params.cache); 112 | } 113 | 114 | if (!this.server) { 115 | this.connect(); 116 | } 117 | 118 | // Resolve data as promised 119 | var promises = this.promisifyData(data); 120 | if (promises.length) { 121 | return Promise.all(promises) 122 | .bind(this) 123 | .then(function() { 124 | this.request(method, url, data, callback); 125 | }); 126 | } 127 | 128 | // Prepare url and data for request 129 | url = url && url.toString ? url.toString() : ''; 130 | data = { 131 | $data: data !== undefined ? data : null, 132 | }; 133 | 134 | if (this.authed !== true) { 135 | data.$client = this.params.clientId; 136 | data.$key = this.params.clientKey; 137 | if (this.params.route) { 138 | data.$route = this.params.route; 139 | } 140 | if (this.cache) { 141 | data.$cached = this.cache.getVersions(); 142 | } 143 | } 144 | 145 | var self = this; 146 | return new Promise(function(resolve, reject) { 147 | var responder = function(err, data, response) { 148 | if (callback) { 149 | callback(err, data, response); 150 | } 151 | if (err) { 152 | reject(new Error(err)); 153 | } else { 154 | resolve(data, response); 155 | } 156 | }; 157 | self.server.request(method, url, data, function(response) { 158 | if (response.$auth) { 159 | if (response.$end) { 160 | // Connection ended, retry auth 161 | return self.request(method, url, data.$data, callback); 162 | } else { 163 | self.authed = true; 164 | return self.auth(response.$auth, function(response) { 165 | return self.respond(method, url, data, response, responder); 166 | }); 167 | } 168 | } else { 169 | return self.respond(method, url, data, response, responder); 170 | } 171 | }); 172 | }); 173 | }; 174 | 175 | Client.prototype.promisifyData = function(data) { 176 | if (!data) { 177 | return []; 178 | } 179 | 180 | function thenResolvePromisedValue(data, key) { 181 | data[key].then(function(val) { 182 | data[key] = val; 183 | }); 184 | } 185 | 186 | var promises = []; 187 | if (typeof data === 'object') { 188 | var keys = Object.keys(data); 189 | for (var i = 0; i < keys.length; i++) { 190 | var key = keys[i]; 191 | if (data[key] && data[key].then) { 192 | promises.push(data[key]); 193 | thenResolvePromisedValue(data, key); 194 | } 195 | } 196 | } else if (data instanceof Array) { 197 | for (var i = 0; i < data.length; i++) { 198 | if (data[i] && data[i].then) { 199 | promises.push(data[i]); 200 | thenResolvePromisedValue(data, i); 201 | } 202 | } 203 | } 204 | 205 | return promises; 206 | }; 207 | 208 | Client.prototype.respond = function(method, url, request, response, callback) { 209 | var err = undefined; 210 | var responseData = undefined; 211 | 212 | if (response) { 213 | if (response.$error) { 214 | err = response.$error; 215 | } else { 216 | if (this.cache) { 217 | this.cache.clear(response); 218 | if (method.toLowerCase() === 'get') { 219 | this.cache.put(url, request.$data, response); 220 | } 221 | } 222 | if (response.$data && typeof response.$data === 'object') { 223 | responseData = Client.createResource(response.$url || url, response, this); 224 | } else { 225 | responseData = response.$data; 226 | } 227 | } 228 | } else { 229 | response = { $error: 'Empty response from server', $status: 500 }; 230 | err = response.$error; 231 | } 232 | return callback.call(this, err, responseData, response); 233 | }; 234 | 235 | Client.prototype.get = function(url, data, callback) { 236 | if (this.cache) { 237 | var self = this; 238 | 239 | if (typeof data === 'function') { 240 | callback = data; 241 | data = null; 242 | } 243 | 244 | return this.getCacheResult(url, data).then(function(result) { 245 | if (result) { 246 | if (callback) { 247 | return callback(null, result); 248 | } 249 | return result; 250 | } 251 | return self.request('get', url, data, callback); 252 | }); 253 | } 254 | 255 | return this.request('get', url, data, callback); 256 | }; 257 | 258 | Client.prototype.getCacheResult = function(url, data) { 259 | var self = this; 260 | return new Promise(function(resolve) { 261 | var response = self.cache.get(url, data); 262 | if (!response || response.$data === undefined) { 263 | return resolve(); 264 | } 265 | 266 | if (!self.cacheUpdateTime) { 267 | self.cacheUpdateTime = Date.now(); 268 | } else if (Date.now() - self.cacheUpdateTime > 1000) { 269 | self.cacheUpdateTime = Date.now(); 270 | var $cached = self.cache.getVersions(); 271 | $cached.client = self.params.routeClientId || self.params.clientId; 272 | self.server.request('cached', $cached, function(cachedResponse) { 273 | self.cache.clear(cachedResponse); 274 | resolve(self.getCacheResult(url, data)); 275 | }); 276 | return; 277 | } 278 | 279 | var result; 280 | if (response.$data !== null && typeof response.$data === 'object') { 281 | result = Client.createResource(url, response, self); 282 | } else { 283 | result = response.$data; 284 | } 285 | 286 | resolve(result); 287 | }); 288 | }; 289 | 290 | Client.prototype.put = function(url, data, callback) { 291 | return this.request('put', url, data, callback); 292 | }; 293 | 294 | Client.prototype.post = function(url, data, callback) { 295 | return this.request('post', url, data, callback); 296 | }; 297 | 298 | Client.prototype.delete = function(url, data, callback) { 299 | return this.request('delete', url, data, callback); 300 | }; 301 | 302 | Client.prototype.auth = function(nonce, callback) { 303 | var self = this; 304 | var clientId = this.params.clientId; 305 | var clientKey = this.params.clientKey; 306 | 307 | if (typeof nonce === 'function') { 308 | callback = nonce; 309 | nonce = null; 310 | } 311 | 312 | if (!this.server) { 313 | this.connect(); 314 | } 315 | 316 | // 1) Get nonce 317 | if (!nonce) { 318 | return this.server.request('auth', function(nonce) { 319 | self.auth(nonce, callback); 320 | }); 321 | } 322 | 323 | // 2) Create key hash 324 | var keyHash = crypto 325 | .createHash('md5') 326 | .update(clientId + '::' + clientKey) 327 | .digest('hex'); 328 | 329 | // 3) Create auth key 330 | var authKey = crypto 331 | .createHash('md5') 332 | .update(nonce + clientId + keyHash) 333 | .digest('hex'); 334 | 335 | // 4) Authenticate with client creds and options 336 | var creds = { 337 | client: clientId, 338 | key: authKey, 339 | }; 340 | if (this.params.version) { 341 | creds.$v = this.params.version; 342 | } 343 | if (this.params.session) { 344 | creds.$session = this.params.session; 345 | } 346 | if (this.params.route) { 347 | creds.$route = this.params.route; 348 | } 349 | if (this.cache) { 350 | creds.$cached = this.cache.getVersions(); 351 | } 352 | 353 | // TODO: send local $ip address 354 | 355 | return this.server.request('auth', creds, callback); 356 | }; 357 | 358 | Client.prototype.close = function() { 359 | if (this.server) { 360 | this.server.close(); 361 | } 362 | }; 363 | 364 | Client.create = function(clientId, clientKey, options, callback) { 365 | return new Client(clientId, clientKey, options, callback); 366 | }; 367 | 368 | Client.createResource = function(url, response, client) { 369 | if (response && response.$data && 'count' in response.$data && response.$data.results) { 370 | return new Schema.Collection(url, response, client); 371 | } 372 | return new Schema.Record(url, response, client); 373 | }; 374 | 375 | module.exports = Schema; 376 | module.exports.Client = Client; 377 | module.exports.createClient = Client.create; 378 | -------------------------------------------------------------------------------- /lib/client.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var sinon = require('sinon'); 3 | var Schema = require('./client'); 4 | 5 | describe('Client', function() { 6 | var serverConnectStub; 7 | 8 | before(function() { 9 | serverConnectStub = sinon.stub(Schema.Connection.prototype, 'connect'); 10 | }); 11 | beforeEach(function() { 12 | serverConnectStub.reset(); 13 | }); 14 | after(function() { 15 | serverConnectStub.restore(); 16 | }); 17 | 18 | describe('#constructor', function() { 19 | var initStub; 20 | var connectStub; 21 | 22 | before(function() { 23 | initStub = sinon.stub(Schema.Client.prototype, 'init'); 24 | connectStub = sinon.stub(Schema.Client.prototype, 'connect'); 25 | }); 26 | 27 | beforeEach(function() { 28 | initStub.reset(); 29 | connectStub.reset(); 30 | }); 31 | 32 | after(function() { 33 | initStub.restore(); 34 | connectStub.restore(); 35 | }); 36 | 37 | it('construct without init', function() { 38 | new Schema.Client(); 39 | 40 | assert.strictEqual(initStub.calledOnce, false); 41 | assert.strictEqual(connectStub.called, false); 42 | }); 43 | 44 | it('init with options - callback', function() { 45 | new Schema.Client('id', 'key', {}); 46 | 47 | assert.strictEqual(initStub.calledOnce, true); 48 | assert.strictEqual(initStub.args[0][0], 'id'); 49 | assert.strictEqual(initStub.args[0][1], 'key'); 50 | assert.deepEqual(initStub.args[0][2], {}); 51 | assert.strictEqual(connectStub.called, false); 52 | }); 53 | 54 | it('init with options + callback', function() { 55 | new Schema.Client('id', 'key', {}, function() {}); 56 | 57 | assert.strictEqual(initStub.calledOnce, true); 58 | assert.strictEqual(connectStub.called, true); 59 | }); 60 | }); 61 | 62 | describe('#init', function() { 63 | var client; 64 | var testParams; 65 | 66 | beforeEach(function() { 67 | client = new Schema.Client(); 68 | 69 | testParams = { 70 | clientId: 'id', 71 | clientKey: 'key', 72 | host: 'api.schema.io', 73 | port: 8443, 74 | verifyCert: true, 75 | version: 1, 76 | session: undefined, 77 | timeout: undefined, 78 | route: undefined, 79 | routeClientId: undefined, 80 | cache: true, 81 | }; 82 | }); 83 | 84 | it('initialize params with defaults', function() { 85 | client.init('id', 'key'); 86 | 87 | assert.strictEqual(client.params.clientId, 'id'); 88 | assert.strictEqual(client.params.clientKey, 'key'); 89 | assert.deepEqual(client.params, testParams); 90 | }); 91 | 92 | it('initialize params without cache', function() { 93 | client = new Schema.Client('id', 'key', { 94 | cache: false, 95 | }); 96 | 97 | assert.strictEqual(client.params.cache, false); 98 | }); 99 | 100 | it('initialize params with options', function() { 101 | testParams.clientId = 'testId'; 102 | testParams.clientKey = 'testKey'; 103 | client.init({ id: 'testId', key: 'testKey' }); 104 | 105 | assert.deepEqual(client.params, testParams); 106 | }); 107 | 108 | it('initialize params with credentials + options', function() { 109 | testParams.clientId = 'id2'; 110 | testParams.clientKey = 'key2'; 111 | testParams.host = 'api2'; 112 | client.init(testParams.clientId, testParams.clientKey, { host: testParams.host }); 113 | 114 | assert.strictEqual(client.params.clientId, 'id2'); 115 | assert.strictEqual(client.params.clientKey, 'key2'); 116 | assert.deepEqual(client.params, testParams); 117 | }); 118 | 119 | it('initialize params route', function() { 120 | client.init({ 121 | id: 'id', 122 | key: 'key', 123 | route: { client: 'id2' }, 124 | }); 125 | 126 | assert.deepEqual(client.params.route, { client: 'id2' }); 127 | assert.deepEqual(client.params.routeClientId, 'id2'); 128 | }); 129 | 130 | it('initialize throws without client id', function() { 131 | try { 132 | client.init(); 133 | } catch (err) { 134 | assert(/required/.test(err)); 135 | } 136 | }); 137 | 138 | it('initialize throws without client key', function() { 139 | try { 140 | client.init('id'); 141 | } catch (err) { 142 | assert(/required/.test(err)); 143 | } 144 | }); 145 | }); 146 | 147 | describe('#connect', function() { 148 | var client; 149 | var serverSpy; 150 | 151 | before(function() { 152 | serverSpy = sinon.spy(Schema, 'Connection'); 153 | }); 154 | 155 | beforeEach(function() { 156 | client = new Schema.Client('id', 'key'); 157 | serverSpy.reset(); 158 | }); 159 | 160 | after(function() { 161 | serverSpy.restore(); 162 | }); 163 | 164 | it('connect params', function() { 165 | client.connect(); 166 | 167 | assert.strictEqual(serverSpy.called, true); 168 | assert.strictEqual(serverSpy.args[0][0], client.params.host); 169 | assert.strictEqual(serverSpy.args[0][1], client.params.port); 170 | }); 171 | 172 | it('connect with callback', function() { 173 | client.connect(sinon.stub()); 174 | 175 | assert.strictEqual(serverSpy.called, true); 176 | assert.strictEqual(serverConnectStub.called, true); 177 | assert.strictEqual(typeof serverSpy.args[0][3], 'function'); 178 | }); 179 | 180 | it('proxy connection events', function() { 181 | var onSpy = sinon.spy(Schema.Connection.prototype, 'on'); 182 | client.connect(); 183 | 184 | assert.strictEqual(onSpy.args[0][0], 'close'); 185 | assert.strictEqual(onSpy.args[1][0], 'error'); 186 | assert.strictEqual(onSpy.args[2][0], 'error.network'); 187 | assert.strictEqual(onSpy.args[3][0], 'error.protocol'); 188 | assert.strictEqual(onSpy.args[4][0], 'error.server'); 189 | 190 | onSpy.restore(); 191 | }); 192 | }); 193 | 194 | describe('#request', function() { 195 | var client; 196 | var serverSpy; 197 | var connectSpy; 198 | var respondStub; 199 | var serverRequestStub; 200 | 201 | before(function() { 202 | serverSpy = sinon.spy(Schema, 'Connection'); 203 | connectSpy = sinon.spy(Schema.Client.prototype, 'connect'); 204 | respondStub = sinon.spy(Schema.Client.prototype, 'respond'); 205 | serverRequestStub = sinon.stub(Schema.Connection.prototype, 'request'); 206 | }); 207 | 208 | beforeEach(function() { 209 | client = new Schema.Client('id', 'key'); 210 | serverSpy.reset(); 211 | connectSpy.reset(); 212 | respondStub.reset(); 213 | serverRequestStub.reset(); 214 | }); 215 | 216 | after(function() { 217 | serverSpy.restore(); 218 | connectSpy.restore(); 219 | respondStub.restore(); 220 | serverRequestStub.restore(); 221 | }); 222 | 223 | it('connect on first request', function() { 224 | client.request('get', 'url'); 225 | client.request('get', 'url'); 226 | 227 | assert(!!client.server); 228 | assert.strictEqual(connectSpy.calledOnce, true); 229 | assert.strictEqual(serverRequestStub.calledTwice, true); 230 | }); 231 | 232 | it('init cache', function() { 233 | assert.isNull(client.cache); 234 | client.request('get', 'url'); 235 | assert.ok(client.cache); 236 | }); 237 | 238 | it('init without cache', function() { 239 | client = new Schema.Client('id', 'key', { cache: false }); 240 | assert.isNull(client.cache); 241 | client.request('get', 'url'); 242 | assert.isNull(client.cache); 243 | }); 244 | 245 | it('build request headers - authed', function() { 246 | client.authed = true; 247 | client.request('get', 'url', 'data'); 248 | 249 | assert.strictEqual(serverRequestStub.args[0][0], 'get'); 250 | assert.strictEqual(serverRequestStub.args[0][1], 'url'); 251 | assert.deepEqual(serverRequestStub.args[0][2], { 252 | $data: 'data', 253 | }); 254 | }); 255 | 256 | it('build request headers + authed', function() { 257 | client = new Schema.Client('id', 'key', { 258 | route: { client: 'id2' }, 259 | }); 260 | client.authed = false; 261 | client.request('get', 'url', 'data'); 262 | 263 | assert.strictEqual(serverRequestStub.args[0][0], 'get'); 264 | assert.strictEqual(serverRequestStub.args[0][1], 'url'); 265 | assert.deepEqual(serverRequestStub.args[0][2], { 266 | $client: 'id', 267 | $key: 'key', 268 | $data: 'data', 269 | $route: { 270 | client: 'id2', 271 | }, 272 | $cached: {}, 273 | }); 274 | }); 275 | 276 | it('build request headers with default data', function() { 277 | client.authed = true; 278 | client.request('get', 'url'); 279 | 280 | assert.strictEqual(serverRequestStub.args[0][0], 'get'); 281 | assert.strictEqual(serverRequestStub.args[0][1], 'url'); 282 | assert.deepEqual(serverRequestStub.args[0][2], { 283 | $data: null, 284 | }); 285 | }); 286 | 287 | it('handle result $auth', function() { 288 | var authStub = sinon.stub(Schema.Client.prototype, 'auth'); 289 | serverRequestStub.onCall(0).callsArgWith(3, { 290 | $auth: true, 291 | }); 292 | client.request('get', 'url', 'data'); 293 | 294 | assert.strictEqual(authStub.called, true); 295 | 296 | authStub.restore(); 297 | }); 298 | 299 | it('handle result $auth + $end retry', function() { 300 | var authStub = sinon.stub(Schema.Client.prototype, 'auth'); 301 | var requestSpy = sinon.spy(Schema.Client.prototype, 'request'); 302 | serverRequestStub.onCall(0).callsArgWith(3, { 303 | $auth: true, 304 | $end: true, 305 | }); 306 | client.request('get', 'url', 'data'); 307 | 308 | assert.strictEqual(authStub.called, false); 309 | assert.strictEqual(requestSpy.calledTwice, true); 310 | 311 | authStub.restore(); 312 | requestSpy.restore(); 313 | }); 314 | 315 | it('handle result response', function() { 316 | serverRequestStub.onCall(0).callsArgWith(3, { 317 | $status: 200, 318 | $data: 'success', 319 | }); 320 | client.request('get', 'url', 'data'); 321 | 322 | assert.strictEqual(respondStub.called, true); 323 | assert.deepEqual(respondStub.args[0][3], { 324 | $status: 200, 325 | $data: 'success', 326 | }); 327 | }); 328 | 329 | it('resolves promise', function() { 330 | serverRequestStub.onCall(0).callsArgWith(3, { 331 | $data: 'success', 332 | }); 333 | 334 | return client.request('get', 'url', 'data').then(function(data) { 335 | assert.strictEqual(data, 'success'); 336 | }); 337 | }); 338 | 339 | it('rejects promise with error', function() { 340 | serverRequestStub.onCall(0).callsArgWith(3, { 341 | $error: 'error', 342 | }); 343 | 344 | return client.request('get', 'url', 'data').catch(function(err) { 345 | assert.strictEqual(err.message, 'error'); 346 | }); 347 | }); 348 | 349 | it('calls back', function() { 350 | serverRequestStub.onCall(0).callsArgWith(3, {}); 351 | 352 | var calledBack = false; 353 | return client 354 | .request('get', 'url', 'data', function() { 355 | calledBack = true; 356 | }) 357 | .then(function() { 358 | assert.strictEqual(calledBack, true); 359 | }); 360 | }); 361 | 362 | it('resolves promised data (object)', function() { 363 | var data = { 364 | test1: Promise.resolve('hello'), 365 | test2: Promise.resolve('world'), 366 | test3: 'static', 367 | }; 368 | 369 | return client.request('get', 'url', data).then(function() { 370 | assert.deepEqual(serverRequestStub.args[0][2].$data, { 371 | test1: 'hello', 372 | test2: 'world', 373 | test3: 'static', 374 | }); 375 | }); 376 | }); 377 | 378 | it('resolves promised data (array)', function() { 379 | var data = [Promise.resolve('hello'), Promise.resolve('world'), 'static']; 380 | 381 | return client.request('get', 'url', data).then(function() { 382 | assert.deepEqual(serverRequestStub.args[0][2].$data, ['hello', 'world', 'static']); 383 | }); 384 | }); 385 | }); 386 | 387 | describe('#respond', function() { 388 | var client; 389 | 390 | beforeEach(function() { 391 | client = new Schema.Client(); 392 | }); 393 | 394 | it('respond with resource data', function() { 395 | var response = { 396 | $url: '/resource/foo', 397 | $data: { 398 | id: 1, 399 | name: 'foo', 400 | }, 401 | }; 402 | 403 | client.respond('get', 'url', null, response, function(err, resource, headers) { 404 | assert(resource instanceof Schema.Resource); 405 | assert.strictEqual(resource.toString(), headers.$url); 406 | assert.strictEqual(resource.id, headers.$data.id); 407 | assert.strictEqual(resource.name, headers.$data.name); 408 | assert.strictEqual(err, undefined); 409 | assert.strictEqual(this, client); 410 | }); 411 | }); 412 | 413 | it('respond with null data', function() { 414 | var response = { 415 | $data: null, 416 | }; 417 | 418 | client.respond('get', 'url', null, response, function(err, data, headers) { 419 | assert.strictEqual(data, null); 420 | assert.strictEqual(headers.$data, null); 421 | assert.strictEqual(this, client); 422 | }); 423 | }); 424 | 425 | it('respond with error', function() { 426 | var response = { 427 | $error: 'Internal Server Error', 428 | }; 429 | 430 | client.respond('get', 'url', null, response, function(err, data, headers) { 431 | assert.strictEqual(data, undefined); 432 | assert.strictEqual(err, headers.$error); 433 | assert.strictEqual(err, response.$error); 434 | assert.strictEqual(this, client); 435 | }); 436 | }); 437 | 438 | it('respond with nothing', function() { 439 | var response = null; 440 | 441 | client.respond('get', 'url', null, response, function(err, data, headers) { 442 | assert.strictEqual(err, 'Empty response from server'); 443 | assert.strictEqual(data, undefined); 444 | assert.strictEqual(headers.$status, 500); 445 | assert.strictEqual(this, client); 446 | }); 447 | }); 448 | }); 449 | 450 | describe('#get/put/post/delete', function() { 451 | var client; 452 | var requestStub; 453 | var requestArgs; 454 | 455 | before(function() { 456 | requestStub = sinon.stub(Schema.Client.prototype, 'request'); 457 | requestArgs = ['url', 'data', 'callback']; 458 | client = new Schema.Client(); 459 | }); 460 | 461 | beforeEach(function() { 462 | requestStub.reset(); 463 | }); 464 | 465 | after(function() { 466 | requestStub.restore(); 467 | }); 468 | 469 | it('get request', function() { 470 | client.get.apply(client, requestArgs); 471 | 472 | assert.strictEqual(requestStub.calledOnce, true); 473 | assert.deepEqual(requestStub.args[0][0], 'get'); 474 | assert.deepEqual(requestStub.args[0].slice(1), requestArgs); 475 | }); 476 | 477 | describe('get request caching behaviour', function() { 478 | var returnValue = 'response'; 479 | var getCacheResultStub = sinon 480 | .stub(Schema.Client.prototype, 'getCacheResult') 481 | .returns(Promise.resolve(returnValue)); 482 | 483 | var client = new Schema.Client(); 484 | client.cache = true; 485 | 486 | after(function() { 487 | getCacheResultStub.restore(); 488 | }); 489 | 490 | it('returns (error, response) when retrieving from cache', function() { 491 | var calledBack = false; 492 | 493 | return client 494 | .get('url', 'data', function(error, response) { 495 | assert.strictEqual(error, null); 496 | assert.strictEqual(response, returnValue); 497 | calledBack = true; 498 | }) 499 | .then(function() { 500 | assert.strictEqual(calledBack, true); 501 | }); 502 | }); 503 | 504 | it('fires callback when no request params present (callback 2nd param)', function() { 505 | var calledBack = false; 506 | 507 | return client 508 | .get('url', function() { 509 | calledBack = true; 510 | }) 511 | .then(function() { 512 | assert.strictEqual(calledBack, true); 513 | }); 514 | }); 515 | }); 516 | 517 | it('put request', function() { 518 | client.put.apply(client, requestArgs); 519 | 520 | assert.strictEqual(requestStub.calledOnce, true); 521 | assert.deepEqual(requestStub.args[0][0], 'put'); 522 | assert.deepEqual(requestStub.args[0].slice(1), requestArgs); 523 | }); 524 | 525 | it('post request', function() { 526 | client.post.apply(client, requestArgs); 527 | 528 | assert.strictEqual(requestStub.calledOnce, true); 529 | assert.deepEqual(requestStub.args[0][0], 'post'); 530 | assert.deepEqual(requestStub.args[0].slice(1), requestArgs); 531 | }); 532 | 533 | it('delete request', function() { 534 | client.delete.apply(client, requestArgs); 535 | 536 | assert.strictEqual(requestStub.calledOnce, true); 537 | assert.deepEqual(requestStub.args[0][0], 'delete'); 538 | assert.deepEqual(requestStub.args[0].slice(1), requestArgs); 539 | }); 540 | }); 541 | 542 | describe('#auth', function() { 543 | var client; 544 | var connectSpy; 545 | var serverRequestStub; 546 | 547 | before(function() { 548 | connectSpy = sinon.spy(Schema.Client.prototype, 'connect'); 549 | serverRequestStub = sinon.stub(Schema.Connection.prototype, 'request'); 550 | }); 551 | 552 | beforeEach(function() { 553 | client = new Schema.Client('id', 'key'); 554 | connectSpy.reset(); 555 | serverRequestStub.reset(); 556 | }); 557 | 558 | after(function() { 559 | connectSpy.restore(); 560 | serverRequestStub.restore(); 561 | }); 562 | 563 | it('connect on first request', function() { 564 | client.auth(); 565 | client.auth(); 566 | 567 | assert(!!client.server); 568 | assert.strictEqual(connectSpy.calledOnce, true); 569 | assert.strictEqual(serverRequestStub.calledTwice, true); 570 | }); 571 | 572 | it('request nonce if not provided', function() { 573 | client.auth(); 574 | 575 | assert.strictEqual(serverRequestStub.calledOnce, true); 576 | assert.strictEqual(serverRequestStub.args[0][0], 'auth'); 577 | assert.strictEqual(typeof serverRequestStub.args[0][1], 'function'); 578 | }); 579 | 580 | it('request auth with credentials encrypted by nonce', function() { 581 | client.auth('nonce'); 582 | 583 | assert.strictEqual(serverRequestStub.calledOnce, true); 584 | assert.strictEqual(serverRequestStub.args[0][0], 'auth'); 585 | assert.deepEqual(serverRequestStub.args[0][1], { 586 | $v: 1, 587 | client: 'id', 588 | key: 'db519c8947922ea94bdd541f8612f3fe', 589 | }); 590 | }); 591 | }); 592 | 593 | describe('#close', function() { 594 | it('close server connection', function() { 595 | var closeStub = sinon.stub(Schema.Connection.prototype, 'close'); 596 | var client = new Schema.Client('id', 'key'); 597 | client.connect(); 598 | client.close(); 599 | 600 | assert.strictEqual(closeStub.calledOnce, true); 601 | 602 | closeStub.restore(); 603 | }); 604 | }); 605 | 606 | describe('#create', function() { 607 | it('return a new client instance', function() { 608 | var client = Schema.Client.create('id', 'key'); 609 | 610 | assert(client instanceof Schema.Client); 611 | assert.strictEqual(client.params.clientId, 'id'); 612 | assert.strictEqual(client.params.clientKey, 'key'); 613 | }); 614 | }); 615 | 616 | describe('#createResource', function() { 617 | var client; 618 | 619 | before(function() { 620 | client = new Schema.Client(); 621 | }); 622 | 623 | it('return a new collection resource', function() { 624 | var result = { 625 | $data: { 626 | count: 1, 627 | results: [{}], 628 | }, 629 | }; 630 | var resource = Schema.Client.createResource('url', result, client); 631 | 632 | assert(resource instanceof Schema.Collection); 633 | }); 634 | 635 | it('return a new record resource', function() { 636 | var result = { 637 | $data: {}, 638 | }; 639 | var resource = Schema.Client.createResource('url', result, client); 640 | 641 | assert(resource instanceof Schema.Record); 642 | }); 643 | }); 644 | }); 645 | -------------------------------------------------------------------------------- /lib/connection.js: -------------------------------------------------------------------------------- 1 | const inherits = require('util').inherits; 2 | const events = require('events'); 3 | const tls = require('tls'); 4 | const net = require('net'); 5 | 6 | const DEFAULT_NETWORK_ERROR = 'Server unexpectedly closed network connection.'; 7 | const DEFAULT_TIMEOUT = 60000; 8 | const RETRY_TIME = 3000; 9 | const MAX_CONCURRENT = 10; 10 | 11 | const Connection = function(host, port, options, callback) { 12 | events.EventEmitter.call(this); 13 | 14 | this.stream = null; 15 | this.connected = false; 16 | this.connectingTimeout = null; 17 | this.connectingRetryTimeout = null; 18 | this.buffer = []; 19 | this.requestBuffer = []; 20 | this.requested = 0; 21 | 22 | this.host = host; 23 | this.port = port; 24 | 25 | options = options || {}; 26 | if (typeof options === 'function') { 27 | callback = options; 28 | options = {}; 29 | } 30 | this.options = options; 31 | 32 | if (callback) { 33 | this.connect(callback); 34 | } 35 | }; 36 | 37 | inherits(Connection, events.EventEmitter); 38 | 39 | Connection.prototype.connect = function(callback) { 40 | const proto = this.options.clear ? net : tls; 41 | const timeoutMs = this.options.timeout || DEFAULT_TIMEOUT; 42 | 43 | if (!this.connectingTimeout) { 44 | this.connectingTimeout = setTimeout(() => { 45 | this.stream = null; 46 | this.connected = false; 47 | this.connectingTimeout = null; 48 | this.flushRequestBufferWithError(`Connection timed out (${timeoutMs} ms)`); 49 | }, timeoutMs); 50 | } 51 | 52 | this.stream = proto.connect( 53 | { 54 | host: this.host, 55 | port: this.port, 56 | rejectUnauthorized: this.options.verifyCert === false ? false : true, 57 | }, 58 | () => { 59 | this.connected = true; 60 | clearTimeout(this.connectingTimeout); 61 | this.connectingTimeout = null; 62 | this.flushRequestBuffer(); 63 | callback && callback(this); 64 | this.emit('connect'); 65 | }, 66 | ); 67 | this.stream.on('error', this.error.bind(this)); 68 | this.stream.on('data', this.receive.bind(this)); 69 | this.stream.on('close', this.close.bind(this)); 70 | this.stream.on('timeout', this.timeout.bind(this)); 71 | this.stream.setEncoding('utf8'); 72 | this.stream.setTimeout(timeoutMs); 73 | }; 74 | 75 | Connection.prototype.request = function() { 76 | // Copy args to avoid leaking 77 | const args = new Array(arguments.length); 78 | for (let i = 0; i < arguments.length; i++) { 79 | args[i] = arguments[i]; 80 | } 81 | 82 | this.requestBuffer.push(args); 83 | 84 | if (!this.connected || !this.stream) { 85 | if (!this.stream) { 86 | this.connect(); 87 | } 88 | return; 89 | } 90 | 91 | this.requestNext(); 92 | }; 93 | 94 | Connection.prototype.requestNext = function() { 95 | if (this.requested > MAX_CONCURRENT) { 96 | return; 97 | } 98 | const args = this.requestBuffer[this.requested]; 99 | if (args) { 100 | const request = JSON.stringify(args.slice(0, -1)); 101 | this.stream.write(request + '\n'); 102 | this.requested++; 103 | } 104 | }; 105 | 106 | Connection.prototype.receive = function(buffer) { 107 | // Split buffer data on newline char 108 | for (var i = 0, j = 0; i < buffer.length; i++) { 109 | if (buffer[i] === '\n') { 110 | this.buffer.push(buffer.slice(j, i)); 111 | 112 | const data = this.buffer.join(''); 113 | 114 | this.buffer = []; 115 | this.receiveResponse(data); 116 | if (this.connected && this.stream) { 117 | this.requestNext(); 118 | } 119 | 120 | j = i + 1; 121 | } 122 | } 123 | if (j < buffer.length) { 124 | this.buffer.push(buffer.slice(j, buffer.length)); 125 | } 126 | }; 127 | 128 | Connection.prototype.receiveResponse = function(data) { 129 | let response; 130 | const request = this.requestBuffer.shift(); 131 | const responder = request && request.pop(); 132 | 133 | this.requested--; 134 | 135 | if (responder === undefined) { 136 | return; 137 | } 138 | 139 | try { 140 | response = JSON.parse(data); 141 | } catch (err) { 142 | response = 'Unable to parse response from server (' + data + ')'; 143 | this.emit('error.protocol', response); 144 | return responder({ 145 | $status: 500, 146 | $error: response, 147 | }); 148 | } 149 | 150 | if (!response || typeof response !== 'object') { 151 | response = 'Invalid response from server (' + data + ')'; 152 | this.emit('error.protocol', response); 153 | return responder({ 154 | $status: 500, 155 | $error: response, 156 | }); 157 | } 158 | 159 | if (response.$error) { 160 | this.emit('error.server', response.$error); 161 | } 162 | if (response.$end) { 163 | this.close(); 164 | } 165 | 166 | // Note: response always returns in the same order as request 167 | if (typeof responder === 'function') { 168 | responder(response); 169 | } 170 | }; 171 | 172 | Connection.prototype.error = function(error) { 173 | const shouldReconnect = 174 | !this.connected && this.stream && this.requestBuffer.length > 0 && !this.connectingRetryTimeout; 175 | if (shouldReconnect) { 176 | this.connectingRetryTimeout = setTimeout(() => { 177 | this.connectingRetryTimeout = null; 178 | this.connect(); 179 | }, RETRY_TIME); 180 | } 181 | this.emit('error', error); 182 | }; 183 | 184 | Connection.prototype.close = function() { 185 | if (!this.connected) { 186 | return; 187 | } 188 | 189 | if (this.stream && this.stream.writable) { 190 | this.stream.end(); 191 | } 192 | 193 | this.connected = false; 194 | this.stream = null; 195 | 196 | if (this.requestBuffer.length > 0) { 197 | this.connect(); 198 | } 199 | }; 200 | 201 | // Handle timeout by closing if no requests are pending 202 | Connection.prototype.timeout = function(error) { 203 | if (this.requestBuffer.length) { 204 | return; 205 | } 206 | this.close(); 207 | }; 208 | 209 | // FLush all requests when connected 210 | Connection.prototype.flushRequestBuffer = function() { 211 | if (!this.connected) { 212 | return; 213 | } 214 | const requestBuffer = this.requestBuffer; 215 | this.requestBuffer = []; 216 | this.requested = 0; 217 | while (requestBuffer.length) { 218 | this.request.apply(this, requestBuffer.shift()); 219 | } 220 | }; 221 | 222 | // Flush all requests when a connection error occurs 223 | Connection.prototype.flushRequestBufferWithError = function(error) { 224 | let hasRequests = this.requestBuffer.length; 225 | while (--hasRequests >= 0) { 226 | const request = this.requestBuffer.shift(); 227 | const responder = request && request.pop(); 228 | if (typeof responder === 'function') { 229 | responder({ 230 | $status: 500, 231 | $error: error, 232 | }); 233 | } 234 | } 235 | }; 236 | 237 | exports.Connection = Connection; 238 | -------------------------------------------------------------------------------- /lib/connection.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var sinon = require('sinon'); 3 | var Connection = require('./connection'); 4 | 5 | // TODO 6 | -------------------------------------------------------------------------------- /lib/schema.js: -------------------------------------------------------------------------------- 1 | /*! schema.js (Version 1.1.4) 2015-11-12 */ 2 | 3 | // Promise 6.0.0 (source: https://www.promisejs.org/polyfills/promise-6.0.0.js) 4 | (function e(t, n, r) { 5 | function s(o, u) { 6 | if (!n[o]) { 7 | if (!t[o]) { 8 | var a = typeof require == 'function' && require; 9 | if (!u && a) return a(o, !0); 10 | if (i) return i(o, !0); 11 | var f = new Error("Cannot find module '" + o + "'"); 12 | throw ((f.code = 'MODULE_NOT_FOUND'), f); 13 | } 14 | var l = (n[o] = { 15 | exports: {}, 16 | }); 17 | t[o][0].call( 18 | l.exports, 19 | function(e) { 20 | var n = t[o][1][e]; 21 | return s(n ? n : e); 22 | }, 23 | l, 24 | l.exports, 25 | e, 26 | t, 27 | n, 28 | r, 29 | ); 30 | } 31 | return n[o].exports; 32 | } 33 | var i = typeof require == 'function' && require; 34 | for (var o = 0; o < r.length; o++) s(r[o]); 35 | return s; 36 | })( 37 | { 38 | 1: [ 39 | function(require, module, exports) { 40 | var process = (module.exports = {}); 41 | process.nextTick = (function() { 42 | var canSetImmediate = typeof window !== 'undefined' && window.setImmediate; 43 | var canPost = 44 | typeof window !== 'undefined' && window.postMessage && window.addEventListener; 45 | if (canSetImmediate) { 46 | return function(f) { 47 | return window.setImmediate(f); 48 | }; 49 | } 50 | if (canPost) { 51 | var queue = []; 52 | window.addEventListener( 53 | 'message', 54 | function(ev) { 55 | var source = ev.source; 56 | if ((source === window || source === null) && ev.data === 'process-tick') { 57 | ev.stopPropagation(); 58 | if (queue.length > 0) { 59 | var fn = queue.shift(); 60 | fn(); 61 | } 62 | } 63 | }, 64 | true, 65 | ); 66 | return function nextTick(fn) { 67 | queue.push(fn); 68 | window.postMessage('process-tick', '*'); 69 | }; 70 | } 71 | return function nextTick(fn) { 72 | setTimeout(fn, 0); 73 | }; 74 | })(); 75 | process.title = 'browser'; 76 | process.browser = true; 77 | process.env = {}; 78 | process.argv = []; 79 | function noop() {} 80 | process.on = noop; 81 | process.addListener = noop; 82 | process.once = noop; 83 | process.off = noop; 84 | process.removeListener = noop; 85 | process.removeAllListeners = noop; 86 | process.emit = noop; 87 | process.binding = function(name) { 88 | throw new Error('process.binding is not supported'); 89 | }; 90 | process.cwd = function() { 91 | return '/'; 92 | }; 93 | process.chdir = function(dir) { 94 | throw new Error('process.chdir is not supported'); 95 | }; 96 | }, 97 | {}, 98 | ], 99 | 2: [ 100 | function(require, module, exports) { 101 | 'use strict'; 102 | var asap = require('asap'); 103 | module.exports = Promise; 104 | function Promise(fn) { 105 | if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); 106 | if (typeof fn !== 'function') throw new TypeError('not a function'); 107 | var state = null; 108 | var value = null; 109 | var deferreds = []; 110 | var self = this; 111 | this.then = function(onFulfilled, onRejected) { 112 | return new self.constructor(function(resolve, reject) { 113 | handle(new Handler(onFulfilled, onRejected, resolve, reject)); 114 | }); 115 | }; 116 | function handle(deferred) { 117 | if (state === null) { 118 | deferreds.push(deferred); 119 | return; 120 | } 121 | asap(function() { 122 | var cb = state ? deferred.onFulfilled : deferred.onRejected; 123 | if (cb === null) { 124 | (state ? deferred.resolve : deferred.reject)(value); 125 | return; 126 | } 127 | var ret; 128 | try { 129 | ret = cb(value); 130 | } catch (e) { 131 | deferred.reject(e); 132 | return; 133 | } 134 | deferred.resolve(ret); 135 | }); 136 | } 137 | function resolve(newValue) { 138 | try { 139 | if (newValue === self) 140 | throw new TypeError('A promise cannot be resolved with itself.'); 141 | if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { 142 | var then = newValue.then; 143 | if (typeof then === 'function') { 144 | doResolve(then.bind(newValue), resolve, reject); 145 | return; 146 | } 147 | } 148 | state = true; 149 | value = newValue; 150 | finale(); 151 | } catch (e) { 152 | reject(e); 153 | } 154 | } 155 | function reject(newValue) { 156 | state = false; 157 | value = newValue; 158 | finale(); 159 | } 160 | function finale() { 161 | for (var i = 0, len = deferreds.length; i < len; i++) handle(deferreds[i]); 162 | deferreds = null; 163 | } 164 | doResolve(fn, resolve, reject); 165 | } 166 | function Handler(onFulfilled, onRejected, resolve, reject) { 167 | this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; 168 | this.onRejected = typeof onRejected === 'function' ? onRejected : null; 169 | this.resolve = resolve; 170 | this.reject = reject; 171 | } 172 | function doResolve(fn, onFulfilled, onRejected) { 173 | var done = false; 174 | try { 175 | fn( 176 | function(value) { 177 | if (done) return; 178 | done = true; 179 | onFulfilled(value); 180 | }, 181 | function(reason) { 182 | if (done) return; 183 | done = true; 184 | onRejected(reason); 185 | }, 186 | ); 187 | } catch (ex) { 188 | if (done) return; 189 | done = true; 190 | onRejected(ex); 191 | } 192 | } 193 | }, 194 | { 195 | asap: 4, 196 | }, 197 | ], 198 | 3: [ 199 | function(require, module, exports) { 200 | 'use strict'; 201 | var Promise = require('./core.js'); 202 | var asap = require('asap'); 203 | module.exports = Promise; 204 | function ValuePromise(value) { 205 | this.then = function(onFulfilled) { 206 | if (typeof onFulfilled !== 'function') return this; 207 | return new Promise(function(resolve, reject) { 208 | asap(function() { 209 | try { 210 | resolve(onFulfilled(value)); 211 | } catch (ex) { 212 | reject(ex); 213 | } 214 | }); 215 | }); 216 | }; 217 | } 218 | ValuePromise.prototype = Promise.prototype; 219 | var TRUE = new ValuePromise(true); 220 | var FALSE = new ValuePromise(false); 221 | var NULL = new ValuePromise(null); 222 | var UNDEFINED = new ValuePromise(undefined); 223 | var ZERO = new ValuePromise(0); 224 | var EMPTYSTRING = new ValuePromise(''); 225 | Promise.resolve = function(value) { 226 | if (value instanceof Promise) return value; 227 | if (value === null) return NULL; 228 | if (value === undefined) return UNDEFINED; 229 | if (value === true) return TRUE; 230 | if (value === false) return FALSE; 231 | if (value === 0) return ZERO; 232 | if (value === '') return EMPTYSTRING; 233 | if (typeof value === 'object' || typeof value === 'function') { 234 | try { 235 | var then = value.then; 236 | if (typeof then === 'function') { 237 | return new Promise(then.bind(value)); 238 | } 239 | } catch (ex) { 240 | return new Promise(function(resolve, reject) { 241 | reject(ex); 242 | }); 243 | } 244 | } 245 | return new ValuePromise(value); 246 | }; 247 | Promise.all = function(arr) { 248 | var args = Array.prototype.slice.call(arr); 249 | return new Promise(function(resolve, reject) { 250 | if (args.length === 0) return resolve([]); 251 | var remaining = args.length; 252 | function res(i, val) { 253 | try { 254 | if (val && (typeof val === 'object' || typeof val === 'function')) { 255 | var then = val.then; 256 | if (typeof then === 'function') { 257 | then.call( 258 | val, 259 | function(val) { 260 | res(i, val); 261 | }, 262 | reject, 263 | ); 264 | return; 265 | } 266 | } 267 | args[i] = val; 268 | if (--remaining === 0) { 269 | resolve(args); 270 | } 271 | } catch (ex) { 272 | reject(ex); 273 | } 274 | } 275 | for (var i = 0; i < args.length; i++) { 276 | res(i, args[i]); 277 | } 278 | }); 279 | }; 280 | Promise.reject = function(value) { 281 | return new Promise(function(resolve, reject) { 282 | reject(value); 283 | }); 284 | }; 285 | Promise.race = function(values) { 286 | return new Promise(function(resolve, reject) { 287 | values.forEach(function(value) { 288 | Promise.resolve(value).then(resolve, reject); 289 | }); 290 | }); 291 | }; 292 | Promise.prototype['catch'] = function(onRejected) { 293 | return this.then(null, onRejected); 294 | }; 295 | }, 296 | { 297 | './core.js': 2, 298 | asap: 4, 299 | }, 300 | ], 301 | 4: [ 302 | function(require, module, exports) { 303 | (function(process) { 304 | var head = { 305 | task: void 0, 306 | next: null, 307 | }; 308 | var tail = head; 309 | var flushing = false; 310 | var requestFlush = void 0; 311 | var isNodeJS = false; 312 | function flush() { 313 | while (head.next) { 314 | head = head.next; 315 | var task = head.task; 316 | head.task = void 0; 317 | var domain = head.domain; 318 | if (domain) { 319 | head.domain = void 0; 320 | domain.enter(); 321 | } 322 | try { 323 | task(); 324 | } catch (e) { 325 | if (isNodeJS) { 326 | if (domain) { 327 | domain.exit(); 328 | } 329 | setTimeout(flush, 0); 330 | if (domain) { 331 | domain.enter(); 332 | } 333 | throw e; 334 | } else { 335 | setTimeout(function() { 336 | throw e; 337 | }, 0); 338 | } 339 | } 340 | if (domain) { 341 | domain.exit(); 342 | } 343 | } 344 | flushing = false; 345 | } 346 | if (typeof process !== 'undefined' && process.nextTick) { 347 | isNodeJS = true; 348 | requestFlush = function() { 349 | process.nextTick(flush); 350 | }; 351 | } else if (typeof setImmediate === 'function') { 352 | if (typeof window !== 'undefined') { 353 | requestFlush = setImmediate.bind(window, flush); 354 | } else { 355 | requestFlush = function() { 356 | setImmediate(flush); 357 | }; 358 | } 359 | } else if (typeof MessageChannel !== 'undefined') { 360 | var channel = new MessageChannel(); 361 | channel.port1.onmessage = flush; 362 | requestFlush = function() { 363 | channel.port2.postMessage(0); 364 | }; 365 | } else { 366 | requestFlush = function() { 367 | setTimeout(flush, 0); 368 | }; 369 | } 370 | function asap(task) { 371 | tail = tail.next = { 372 | task: task, 373 | domain: isNodeJS && process.domain, 374 | next: null, 375 | }; 376 | if (!flushing) { 377 | flushing = true; 378 | requestFlush(); 379 | } 380 | } 381 | module.exports = asap; 382 | }.call(this, require('_process'))); 383 | }, 384 | { 385 | _process: 1, 386 | }, 387 | ], 388 | 5: [ 389 | function(require, module, exports) { 390 | if (typeof Promise.prototype.done !== 'function') { 391 | Promise.prototype.done = function(onFulfilled, onRejected) { 392 | var self = arguments.length ? this.then.apply(this, arguments) : this; 393 | self.then(null, function(err) { 394 | setTimeout(function() { 395 | throw err; 396 | }, 0); 397 | }); 398 | }; 399 | } 400 | }, 401 | {}, 402 | ], 403 | 6: [ 404 | function(require, module, exports) { 405 | var asap = require('asap'); 406 | if (typeof Promise === 'undefined') { 407 | Promise = require('./lib/core.js'); 408 | require('./lib/es6-extensions.js'); 409 | } 410 | require('./polyfill-done.js'); 411 | }, 412 | { 413 | './lib/core.js': 2, 414 | './lib/es6-extensions.js': 3, 415 | './polyfill-done.js': 5, 416 | asap: 4, 417 | }, 418 | ], 419 | }, 420 | {}, 421 | [6], 422 | ); 423 | /** 424 | * Client library, card tokenizer, and drop-in replacement for Stripe.js 425 | */ 426 | (function() { 427 | var Schema = (this.Schema = { 428 | publicUrl: 'https://api.schema.io', 429 | vaultUrl: 'https://vault.schema.io', 430 | publicKey: null, 431 | }); 432 | 433 | /** 434 | * Set public key used to identify client in API calls 435 | * 436 | * @param string key 437 | */ 438 | Schema.setPublicKey = function(key) { 439 | this.publicKey = key; 440 | delete this._vaultClient; 441 | delete this._publicClient; 442 | }; 443 | 444 | /** 445 | * Alias for stripe.js compatibility 446 | * 447 | * @param string key 448 | */ 449 | Schema.setPublishableKey = function(key) { 450 | return Schema.setPublicKey(key); 451 | }; 452 | 453 | /** 454 | * Alias card namespace 455 | */ 456 | Schema.card = {}; 457 | 458 | /** 459 | * Create a token from card details 460 | * 461 | * @param object card 462 | * @param function callback 463 | * @return void 464 | */ 465 | Schema.createToken = function(card, callback) { 466 | var error = null; 467 | var param = null; 468 | if (!card) { 469 | error = 'Card details are missing in `Schema.createToken(card, callback)`'; 470 | param = ''; 471 | } 472 | if (!callback) { 473 | error = 'Callback function missing in `Schema.createToken(card, callback)`'; 474 | param = ''; 475 | } 476 | if (!Schema.card.validateCardNumber(card.number)) { 477 | error = 'Card number appears to be invalid'; 478 | param = 'number'; 479 | } 480 | if (card.exp) { 481 | var exp = Schema.cardExpiry(card.exp); 482 | card.exp_month = exp.month; 483 | card.exp_year = exp.year; 484 | } 485 | if (!Schema.card.validateExpiry(card.exp_month, card.exp_year)) { 486 | error = 'Card expiry appears to be invalid'; 487 | param = 'exp_month'; 488 | } 489 | if (!Schema.card.validateCVC(card.cvc)) { 490 | error = 'Card CVC code appears to be invalid'; 491 | param = 'exp_cvc'; 492 | } 493 | if (error) { 494 | setTimeout(function() { 495 | callback(402, { error: { message: error, param: param } }); 496 | }, 1); 497 | return; 498 | } 499 | 500 | if (!card.billing) { 501 | card.billing = {}; 502 | } 503 | if (card.address_line1) { 504 | card.billing.address1 = card.address_line1; 505 | } 506 | if (card.address_line2) { 507 | card.billing.address2 = card.address_line2; 508 | } 509 | if (card.address_city) { 510 | card.billing.city = card.address_city; 511 | } 512 | if (card.address_state) { 513 | card.billing.state = card.address_state; 514 | } 515 | if (card.address_zip) { 516 | card.billing.zip = card.address_zip; 517 | } 518 | if (card.address_country) { 519 | card.billing.country = card.address_country; 520 | } 521 | 522 | // Get a token from Schema Vault 523 | Schema.vault().post('/tokens', card, function(result, headers) { 524 | var response = result || {}; 525 | if (headers.$error) { 526 | response.error = { message: headers.$error }; 527 | } else if (response.errors) { 528 | var param = Object.keys(result.errors)[0]; 529 | response.error = result.errors[param]; 530 | response.error.param = param; 531 | headers.$status = 402; 532 | } else if (result.toObject) { 533 | response = result.toObject(); 534 | } 535 | return callback(headers.$status, response); 536 | }); 537 | }; 538 | Schema.card.createToken = function() { 539 | return Schema.createToken.apply(this, arguments); 540 | }; 541 | 542 | /** 543 | * Parse card expiry from a string value 544 | * 545 | * @param string value 546 | * @return object {month: int, year: int} 547 | */ 548 | Schema.cardExpiry = function(value) { 549 | if (value && value.month && value.year) { 550 | return value; 551 | } 552 | 553 | var parts = new String(value).split(/[\s\/\-]+/, 2); 554 | var month = parts[0]; 555 | var year = parts[1]; 556 | 557 | // Convert 2 digit year 558 | if (year && year.length === 2 && /^\d+$/.test(year)) { 559 | var prefix = new Date() 560 | .getFullYear() 561 | .toString() 562 | .substring(0, 2); 563 | year = prefix + year; 564 | } 565 | 566 | return { 567 | month: ~~month, 568 | year: ~~year, 569 | }; 570 | }; 571 | 572 | /** 573 | * Determine card type 574 | */ 575 | Schema.cardType = function() { 576 | return Schema.Stripe.card.cardType.apply(Schema.Stripe, arguments); 577 | }; 578 | Schema.card.cardType = function() { 579 | return Schema.cardType.apply(this, arguments); 580 | }; 581 | 582 | /** 583 | * Validate card number 584 | */ 585 | Schema.validateCardNumber = function() { 586 | return Schema.Stripe.card.validateCardNumber.apply(Schema.Stripe, arguments); 587 | }; 588 | Schema.card.validateCardNumber = function() { 589 | return Schema.validateCardNumber.apply(this, arguments); 590 | }; 591 | 592 | /** 593 | * Validate card expiry 594 | */ 595 | Schema.validateExpiry = function() { 596 | return Schema.Stripe.card.validateExpiry.apply(Schema.Stripe, arguments); 597 | }; 598 | Schema.card.validateExpiry = function() { 599 | return Schema.validateExpiry.apply(this, arguments); 600 | }; 601 | 602 | /** 603 | * Validate card CVC code 604 | */ 605 | Schema.validateCVC = function() { 606 | return Schema.Stripe.card.validateCVC.apply(Schema.Stripe, arguments); 607 | }; 608 | Schema.card.validateCVC = function() { 609 | return Schema.validateCVC.apply(this, arguments); 610 | }; 611 | 612 | /** 613 | * Get a public client instance if public key is defined 614 | * 615 | * @return Schema.Client 616 | */ 617 | Schema.client = function() { 618 | if (this._publicClient) { 619 | return this._publicClient; 620 | } 621 | if (!this.publicKey) { 622 | throw 'Error: Public key must be set Schema.setPublicKey()'; 623 | } 624 | 625 | this._publicClient = new Schema.Client(this.publicKey, { hostUrl: this.publicUrl }); 626 | 627 | return this._publicClient; 628 | }; 629 | 630 | /** 631 | * Get a vault client instance if public key is defined 632 | * 633 | * @return Schema.Client 634 | */ 635 | Schema.vault = function() { 636 | if (this._vaultClient) { 637 | return this._vaultClient; 638 | } 639 | if (!this.publicKey) { 640 | throw 'Error: Public key must be set Schema.setPublicKey()'; 641 | } 642 | 643 | this._vaultClient = new Schema.Client(this.publicKey, { hostUrl: this.vaultUrl }); 644 | 645 | return this._vaultClient; 646 | }; 647 | 648 | /** 649 | * Include Stripe (v2) 650 | * Use for validation and Stripe specific tokenization 651 | */ 652 | (function() { 653 | var e, 654 | t, 655 | n, 656 | r, 657 | i, 658 | s = {}.hasOwnProperty, 659 | o = function(e, t) { 660 | function r() { 661 | this.constructor = e; 662 | } 663 | for (var n in t) s.call(t, n) && (e[n] = t[n]); 664 | return (r.prototype = t.prototype), (e.prototype = new r()), (e.__super__ = t.prototype), e; 665 | }, 666 | u = this; 667 | (this.Stripe = function() { 668 | function e() {} 669 | return ( 670 | (e.version = 2), 671 | (e.endpoint = 'https://api.stripe.com/v1'), 672 | (e.setPublishableKey = function(t) { 673 | e.key = t; 674 | }), 675 | (e.complete = function(t, n) { 676 | return function(r, i, s) { 677 | var o; 678 | if (r !== 'success') 679 | return ( 680 | (o = Math.round(new Date().getTime() / 1e3)), 681 | (new Image().src = 682 | 'https://q.stripe.com?event=stripejs-error&type=' + 683 | r + 684 | '&key=' + 685 | e.key + 686 | '×tamp=' + 687 | o), 688 | typeof t == 'function' 689 | ? t(500, { 690 | error: { 691 | code: r, 692 | type: r, 693 | message: n, 694 | }, 695 | }) 696 | : void 0 697 | ); 698 | }; 699 | }), 700 | e 701 | ); 702 | }.call(this)), 703 | (e = this.Stripe), 704 | (this.Stripe.token = function() { 705 | function t() {} 706 | return ( 707 | (t.validate = function(e, t) { 708 | if (!e) throw t + ' required'; 709 | if (typeof e != 'object') throw t + ' invalid'; 710 | }), 711 | (t.formatData = function(t, n) { 712 | return ( 713 | e.utils.isElement(t) && (t = e.utils.paramsFromForm(t, n)), 714 | e.utils.underscoreKeys(t), 715 | t 716 | ); 717 | }), 718 | (t.create = function(t, n) { 719 | return ( 720 | t.key || (t.key = e.key || e.publishableKey), 721 | e.utils.validateKey(t.key), 722 | e.ajaxJSONP({ 723 | url: '' + e.endpoint + '/tokens', 724 | data: t, 725 | method: 'POST', 726 | success: function(e, t) { 727 | return typeof n == 'function' ? n(t, e) : void 0; 728 | }, 729 | complete: e.complete( 730 | n, 731 | 'A network error has occurred, and you have not been charged. Please try again.', 732 | ), 733 | timeout: 4e4, 734 | }) 735 | ); 736 | }), 737 | (t.get = function(t, n) { 738 | if (!t) throw 'token required'; 739 | return ( 740 | e.utils.validateKey(e.key), 741 | e.ajaxJSONP({ 742 | url: '' + e.endpoint + '/tokens/' + t, 743 | data: { 744 | key: e.key, 745 | }, 746 | success: function(e, t) { 747 | return typeof n == 'function' ? n(t, e) : void 0; 748 | }, 749 | complete: e.complete( 750 | n, 751 | 'A network error has occurred loading data from Stripe. Please try again.', 752 | ), 753 | timeout: 4e4, 754 | }) 755 | ); 756 | }), 757 | t 758 | ); 759 | }.call(this)), 760 | (this.Stripe.card = function(t) { 761 | function n() { 762 | return n.__super__.constructor.apply(this, arguments); 763 | } 764 | return ( 765 | o(n, t), 766 | (n.tokenName = 'card'), 767 | (n.whitelistedAttrs = [ 768 | 'number', 769 | 'cvc', 770 | 'exp_month', 771 | 'exp_year', 772 | 'name', 773 | 'address_line1', 774 | 'address_line2', 775 | 'address_city', 776 | 'address_state', 777 | 'address_zip', 778 | 'address_country', 779 | ]), 780 | (n.createToken = function(t, r, i) { 781 | var s; 782 | return ( 783 | r == null && (r = {}), 784 | e.token.validate(t, 'card'), 785 | typeof r == 'function' 786 | ? ((i = r), (r = {})) 787 | : typeof r != 'object' && 788 | ((s = parseInt(r, 10)), (r = {}), s > 0 && (r.amount = s)), 789 | (r[n.tokenName] = e.token.formatData(t, n.whitelistedAttrs)), 790 | e.token.create(r, i) 791 | ); 792 | }), 793 | (n.getToken = function(t, n) { 794 | return e.token.get(t, n); 795 | }), 796 | (n.validateCardNumber = function(e) { 797 | return ( 798 | (e = (e + '').replace(/\s+|-/g, '')), 799 | e.length >= 10 && e.length <= 16 && n.luhnCheck(e) 800 | ); 801 | }), 802 | (n.validateCVC = function(t) { 803 | return (t = e.utils.trim(t)), /^\d+$/.test(t) && t.length >= 3 && t.length <= 4; 804 | }), 805 | (n.validateExpiry = function(t, n) { 806 | var r, i; 807 | return ( 808 | (t = e.utils.trim(t)), 809 | (n = e.utils.trim(n)), 810 | /^\d+$/.test(t) 811 | ? /^\d+$/.test(n) 812 | ? parseInt(t, 10) <= 12 813 | ? ((i = new Date(n, t)), 814 | (r = new Date()), 815 | i.setMonth(i.getMonth() - 1), 816 | i.setMonth(i.getMonth() + 1, 1), 817 | i > r) 818 | : !1 819 | : !1 820 | : !1 821 | ); 822 | }), 823 | (n.luhnCheck = function(e) { 824 | var t, n, r, i, s, o; 825 | (r = !0), (i = 0), (n = (e + '').split('').reverse()); 826 | for (s = 0, o = n.length; s < o; s++) { 827 | (t = n[s]), (t = parseInt(t, 10)); 828 | if ((r = !r)) t *= 2; 829 | t > 9 && (t -= 9), (i += t); 830 | } 831 | return i % 10 === 0; 832 | }), 833 | (n.cardType = function(e) { 834 | return n.cardTypes[e.slice(0, 2)] || 'Unknown'; 835 | }), 836 | (n.cardTypes = (function() { 837 | var e, t, n, r; 838 | t = {}; 839 | for (e = n = 40; n <= 49; e = ++n) t[e] = 'Visa'; 840 | for (e = r = 50; r <= 59; e = ++r) t[e] = 'MasterCard'; 841 | return ( 842 | (t[34] = t[37] = 'American Express'), 843 | (t[60] = t[62] = t[64] = t[65] = 'Discover'), 844 | (t[35] = 'JCB'), 845 | (t[30] = t[36] = t[38] = t[39] = 'Diners Club'), 846 | t 847 | ); 848 | })()), 849 | n 850 | ); 851 | }.call(this, this.Stripe.token)), 852 | (this.Stripe.bankAccount = function(t) { 853 | function n() { 854 | return n.__super__.constructor.apply(this, arguments); 855 | } 856 | return ( 857 | o(n, t), 858 | (n.tokenName = 'bank_account'), 859 | (n.whitelistedAttrs = ['country', 'routing_number', 'account_number']), 860 | (n.createToken = function(t, r, i) { 861 | return ( 862 | r == null && (r = {}), 863 | e.token.validate(t, 'bank account'), 864 | typeof r == 'function' && ((i = r), (r = {})), 865 | (r[n.tokenName] = e.token.formatData(t, n.whitelistedAttrs)), 866 | e.token.create(r, i) 867 | ); 868 | }), 869 | (n.getToken = function(t, n) { 870 | return e.token.get(t, n); 871 | }), 872 | (n.validateRoutingNumber = function(t, r) { 873 | t = e.utils.trim(t); 874 | switch (r) { 875 | case 'US': 876 | return /^\d+$/.test(t) && t.length === 9 && n.routingChecksum(t); 877 | case 'CA': 878 | return /\d{5}\-\d{3}/.test(t) && t.length === 9; 879 | default: 880 | return !0; 881 | } 882 | }), 883 | (n.validateAccountNumber = function(t, n) { 884 | t = e.utils.trim(t); 885 | switch (n) { 886 | case 'US': 887 | return /^\d+$/.test(t) && t.length >= 1 && t.length <= 17; 888 | default: 889 | return !0; 890 | } 891 | }), 892 | (n.routingChecksum = function(e) { 893 | var t, n, r, i, s, o; 894 | (r = 0), (t = (e + '').split('')), (o = [0, 3, 6]); 895 | for (i = 0, s = o.length; i < s; i++) 896 | (n = o[i]), 897 | (r += parseInt(t[n]) * 3), 898 | (r += parseInt(t[n + 1]) * 7), 899 | (r += parseInt(t[n + 2])); 900 | return r !== 0 && r % 10 === 0; 901 | }), 902 | n 903 | ); 904 | }.call(this, this.Stripe.token)), 905 | (this.Stripe.bitcoinReceiver = function() { 906 | function t() {} 907 | return ( 908 | (t._whitelistedAttrs = ['amount', 'currency', 'email', 'description']), 909 | (t.createReceiver = function(t, n) { 910 | var r; 911 | return ( 912 | e.token.validate(t, 'bitcoin_receiver data'), 913 | (r = e.token.formatData(t, this._whitelistedAttrs)), 914 | (r.key = e.key || e.publishableKey), 915 | e.utils.validateKey(r.key), 916 | e.ajaxJSONP({ 917 | url: '' + e.endpoint + '/bitcoin/receivers', 918 | data: r, 919 | method: 'POST', 920 | success: function(e, t) { 921 | return typeof n == 'function' ? n(t, e) : void 0; 922 | }, 923 | complete: e.complete( 924 | n, 925 | 'A network error has occurred while creating a Bitcoin address. Please try again.', 926 | ), 927 | timeout: 4e4, 928 | }) 929 | ); 930 | }), 931 | (t.getReceiver = function(t, n) { 932 | var r; 933 | if (!t) throw 'receiver id required'; 934 | return ( 935 | (r = e.key || e.publishableKey), 936 | e.utils.validateKey(r), 937 | e.ajaxJSONP({ 938 | url: '' + e.endpoint + '/bitcoin/receivers/' + t, 939 | data: { 940 | key: r, 941 | }, 942 | success: function(e, t) { 943 | return typeof n == 'function' ? n(t, e) : void 0; 944 | }, 945 | complete: e.complete( 946 | n, 947 | 'A network error has occurred loading data from Stripe. Please try again.', 948 | ), 949 | timeout: 4e4, 950 | }) 951 | ); 952 | }), 953 | (t._activeReceiverPolls = {}), 954 | (t._clearReceiverPoll = function(e) { 955 | return delete t._activeReceiverPolls[e]; 956 | }), 957 | (t._pollInterval = 1500), 958 | (t.pollReceiver = function(e, t) { 959 | if (this._activeReceiverPolls[e] != null) 960 | throw 'You are already polling receiver ' + 961 | e + 962 | '. Please cancel that poll before polling it again.'; 963 | return (this._activeReceiverPolls[e] = {}), this._pollReceiver(e, t); 964 | }), 965 | (t._pollReceiver = function(e, n) { 966 | t.getReceiver(e, function(r, i) { 967 | var s, o; 968 | if (t._activeReceiverPolls[e] == null) return; 969 | return r === 200 && i.filled 970 | ? (t._clearReceiverPoll(e), typeof n == 'function' ? n(r, i) : void 0) 971 | : r >= 400 && r < 500 972 | ? (t._clearReceiverPoll(e), typeof n == 'function' ? n(r, i) : void 0) 973 | : ((s = r === 500 ? 5e3 : t._pollInterval), 974 | (o = setTimeout(function() { 975 | return t._pollReceiver(e, n); 976 | }, s)), 977 | (t._activeReceiverPolls[e].timeoutId = o)); 978 | }); 979 | }), 980 | (t.cancelReceiverPoll = function(e) { 981 | var n; 982 | n = t._activeReceiverPolls[e]; 983 | if (n == null) throw 'You are not polling receiver ' + e + '.'; 984 | n['timeoutId'] != null && clearTimeout(n.timeoutId), t._clearReceiverPoll(e); 985 | }), 986 | t 987 | ); 988 | }.call(this)), 989 | (t = [ 990 | 'createToken', 991 | 'getToken', 992 | 'cardType', 993 | 'validateExpiry', 994 | 'validateCVC', 995 | 'validateCardNumber', 996 | ]); 997 | for (r = 0, i = t.length; r < i; r++) (n = t[r]), (this.Stripe[n] = this.Stripe.card[n]); 998 | typeof module != 'undefined' && module !== null && (module.exports = this.Stripe), 999 | typeof define == 'function' && 1000 | define('stripe', [], function() { 1001 | return u.Stripe; 1002 | }); 1003 | }.call(this), 1004 | function() { 1005 | var e, 1006 | t, 1007 | n, 1008 | r = [].slice; 1009 | (e = encodeURIComponent), 1010 | (t = new Date().getTime()), 1011 | (n = function(t, r, i) { 1012 | var s, o; 1013 | r == null && (r = []); 1014 | for (s in t) 1015 | (o = t[s]), 1016 | i && (s = '' + i + '[' + s + ']'), 1017 | typeof o == 'object' ? n(o, r, s) : r.push('' + s + '=' + e(o)); 1018 | return r.join('&').replace(/%20/g, '+'); 1019 | }), 1020 | (this.Stripe.ajaxJSONP = function(e) { 1021 | var i, s, o, u, a, f; 1022 | return ( 1023 | e == null && (e = {}), 1024 | (o = 'sjsonp' + ++t), 1025 | (a = document.createElement('script')), 1026 | (s = null), 1027 | (i = function(t) { 1028 | var n; 1029 | return ( 1030 | t == null && (t = 'abort'), 1031 | clearTimeout(s), 1032 | (n = a.parentNode) != null && n.removeChild(a), 1033 | o in window && (window[o] = function() {}), 1034 | typeof e.complete == 'function' ? e.complete(t, f, e) : void 0 1035 | ); 1036 | }), 1037 | (f = { 1038 | abort: i, 1039 | }), 1040 | (a.onerror = function() { 1041 | return f.abort(), typeof e.error == 'function' ? e.error(f, e) : void 0; 1042 | }), 1043 | (window[o] = function() { 1044 | var t; 1045 | (t = 1 <= arguments.length ? r.call(arguments, 0) : []), 1046 | clearTimeout(s), 1047 | a.parentNode.removeChild(a); 1048 | try { 1049 | delete window[o]; 1050 | } catch (n) { 1051 | window[o] = void 0; 1052 | } 1053 | return ( 1054 | typeof e.success == 'function' && e.success.apply(e, t), 1055 | typeof e.complete == 'function' ? e.complete('success', f, e) : void 0 1056 | ); 1057 | }), 1058 | e.data || (e.data = {}), 1059 | (e.data.callback = o), 1060 | e.method && (e.data._method = e.method), 1061 | (a.src = e.url + '?' + n(e.data)), 1062 | (u = document.getElementsByTagName('head')[0]), 1063 | u.appendChild(a), 1064 | e.timeout > 0 && 1065 | (s = setTimeout(function() { 1066 | return f.abort('timeout'); 1067 | }, e.timeout)), 1068 | f 1069 | ); 1070 | }); 1071 | }.call(this), 1072 | function() { 1073 | var e = 1074 | [].indexOf || 1075 | function(e) { 1076 | for (var t = 0, n = this.length; t < n; t++) if (t in this && this[t] === e) return t; 1077 | return -1; 1078 | }; 1079 | this.Stripe.utils = (function() { 1080 | function t() {} 1081 | return ( 1082 | (t.trim = function(e) { 1083 | return (e + '').replace(/^\s+|\s+$/g, ''); 1084 | }), 1085 | (t.underscore = function(e) { 1086 | return (e + '') 1087 | .replace(/([A-Z])/g, function(e) { 1088 | return '_' + e.toLowerCase(); 1089 | }) 1090 | .replace(/-/g, '_'); 1091 | }), 1092 | (t.underscoreKeys = function(e) { 1093 | var t, n, r; 1094 | r = []; 1095 | for (t in e) (n = e[t]), delete e[t], r.push((e[this.underscore(t)] = n)); 1096 | return r; 1097 | }), 1098 | (t.isElement = function(e) { 1099 | return typeof e != 'object' 1100 | ? !1 1101 | : typeof jQuery != 'undefined' && jQuery !== null && e instanceof jQuery 1102 | ? !0 1103 | : e.nodeType === 1; 1104 | }), 1105 | (t.paramsFromForm = function(t, n) { 1106 | var r, i, s, o, u, a, f, l, c, h; 1107 | n == null && (n = []), 1108 | typeof jQuery != 'undefined' && jQuery !== null && t instanceof jQuery && (t = t[0]), 1109 | (s = t.getElementsByTagName('input')), 1110 | (u = t.getElementsByTagName('select')), 1111 | (a = {}); 1112 | for (f = 0, c = s.length; f < c; f++) { 1113 | (i = s[f]), (r = this.underscore(i.getAttribute('data-stripe'))); 1114 | if (e.call(n, r) < 0) continue; 1115 | a[r] = i.value; 1116 | } 1117 | for (l = 0, h = u.length; l < h; l++) { 1118 | (o = u[l]), (r = this.underscore(o.getAttribute('data-stripe'))); 1119 | if (e.call(n, r) < 0) continue; 1120 | o.selectedIndex != null && (a[r] = o.options[o.selectedIndex].value); 1121 | } 1122 | return a; 1123 | }), 1124 | (t.validateKey = function(e) { 1125 | if (!e || typeof e != 'string') 1126 | throw new Error( 1127 | 'You did not set a valid publishable key. Call Stripe.setPublishableKey() with your publishable key. For more info, see https://stripe.com/docs/stripe.js', 1128 | ); 1129 | if (/\s/g.test(e)) 1130 | throw new Error( 1131 | 'Your key is invalid, as it contains whitespace. For more info, see https://stripe.com/docs/stripe.js', 1132 | ); 1133 | if (/^sk_/.test(e)) 1134 | throw new Error( 1135 | 'You are using a secret key with Stripe.js, instead of the publishable one. For more info, see https://stripe.com/docs/stripe.js', 1136 | ); 1137 | }), 1138 | t 1139 | ); 1140 | })(); 1141 | }.call(this), 1142 | function() { 1143 | var e = 1144 | [].indexOf || 1145 | function(e) { 1146 | for (var t = 0, n = this.length; t < n; t++) if (t in this && this[t] === e) return t; 1147 | return -1; 1148 | }; 1149 | this.Stripe.validator = { 1150 | boolean: function(e, t) { 1151 | if (t !== 'true' && t !== 'false') return 'Enter a boolean string (true or false)'; 1152 | }, 1153 | integer: function(e, t) { 1154 | if (!/^\d+$/.test(t)) return 'Enter an integer'; 1155 | }, 1156 | positive: function(e, t) { 1157 | if (!(!this.integer(e, t) && parseInt(t, 10) > 0)) return 'Enter a positive value'; 1158 | }, 1159 | range: function(t, n) { 1160 | var r; 1161 | if (((r = parseInt(n, 10)), e.call(t, r) < 0)) 1162 | return 'Needs to be between ' + t[0] + ' and ' + t[t.length - 1]; 1163 | }, 1164 | required: function(e, t) { 1165 | if (e && (t == null || t === '')) return 'Required'; 1166 | }, 1167 | year: function(e, t) { 1168 | if (!/^\d{4}$/.test(t)) return 'Enter a 4-digit year'; 1169 | }, 1170 | birthYear: function(e, t) { 1171 | var n; 1172 | n = this.year(e, t); 1173 | if (n) return n; 1174 | if (parseInt(t, 10) > 2e3) return 'You must be over 18'; 1175 | if (parseInt(t, 10) < 1900) return 'Enter your birth year'; 1176 | }, 1177 | month: function(e, t) { 1178 | if (this.integer(e, t)) return 'Please enter a month'; 1179 | if (this.range([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], t)) 1180 | return 'Needs to be between 1 and 12'; 1181 | }, 1182 | choices: function(t, n) { 1183 | if (e.call(t, n) < 0) return 'Not an acceptable value for this field'; 1184 | }, 1185 | email: function(e, t) { 1186 | if (!/^[^@<\s>]+@[^@<\s>]+$/.test(t)) return "That doesn't look like an email address"; 1187 | }, 1188 | url: function(e, t) { 1189 | if (!/^https?:\/\/.+\..+/.test(t)) return 'Not a valid url'; 1190 | }, 1191 | usTaxID: function(e, t) { 1192 | if (!/^\d{2}-?\d{1}-?\d{2}-?\d{4}$/.test(t)) return 'Not a valid tax ID'; 1193 | }, 1194 | ein: function(e, t) { 1195 | if (!/^\d{2}-?\d{7}$/.test(t)) return 'Not a valid EIN'; 1196 | }, 1197 | ssnLast4: function(e, t) { 1198 | if (!/^\d{4}$/.test(t)) return 'Not a valid last 4 digits for an SSN'; 1199 | }, 1200 | ownerPersonalID: function(e, t) { 1201 | var n; 1202 | n = (function() { 1203 | switch (e) { 1204 | case 'CA': 1205 | return /^\d{3}-?\d{3}-?\d{3}$/.test(t); 1206 | case 'US': 1207 | return !0; 1208 | } 1209 | })(); 1210 | if (!n) return 'Not a valid ID'; 1211 | }, 1212 | bizTaxID: function(e, t) { 1213 | var n, r, i, s, o, u, a, f; 1214 | (u = { 1215 | CA: ['Tax ID', [/^\d{9}$/]], 1216 | US: ['EIN', [/^\d{2}-?\d{7}$/]], 1217 | }), 1218 | (o = u[e]); 1219 | if (o != null) { 1220 | (n = o[0]), (s = o[1]), (r = !1); 1221 | for (a = 0, f = s.length; a < f; a++) { 1222 | i = s[a]; 1223 | if (i.test(t)) { 1224 | r = !0; 1225 | break; 1226 | } 1227 | } 1228 | if (!r) return 'Not a valid ' + n; 1229 | } 1230 | }, 1231 | zip: function(e, t) { 1232 | var n; 1233 | n = (function() { 1234 | switch (e.toUpperCase()) { 1235 | case 'CA': 1236 | return /^[\d\w]{6}$/.test(t != null ? t.replace(/\s+/g, '') : void 0); 1237 | case 'US': 1238 | return /^\d{5}$/.test(t) || /^\d{9}$/.test(t); 1239 | } 1240 | })(); 1241 | if (!n) return 'Not a valid zip'; 1242 | }, 1243 | bankAccountNumber: function(e, t) { 1244 | if (!/^\d{1,17}$/.test(t)) return 'Invalid bank account number'; 1245 | }, 1246 | usRoutingNumber: function(e) { 1247 | var t, n, r, i, s, o, u; 1248 | if (!/^\d{9}$/.test(e)) return 'Routing number must have 9 digits'; 1249 | s = 0; 1250 | for (t = o = 0, u = e.length - 1; o <= u; t = o += 3) 1251 | (n = parseInt(e.charAt(t), 10) * 3), 1252 | (r = parseInt(e.charAt(t + 1), 10) * 7), 1253 | (i = parseInt(e.charAt(t + 2), 10)), 1254 | (s += n + r + i); 1255 | if (s === 0 || s % 10 !== 0) return 'Invalid routing number'; 1256 | }, 1257 | caRoutingNumber: function(e) { 1258 | if (!/^\d{5}\-\d{3}$/.test(e)) return 'Invalid transit number'; 1259 | }, 1260 | routingNumber: function(e, t) { 1261 | switch (e.toUpperCase()) { 1262 | case 'CA': 1263 | return this.caRoutingNumber(t); 1264 | case 'US': 1265 | return this.usRoutingNumber(t); 1266 | } 1267 | }, 1268 | phoneNumber: function(e, t) { 1269 | var n; 1270 | n = t.replace(/[^0-9]/g, ''); 1271 | if (n.length !== 10) return 'Invalid phone number'; 1272 | }, 1273 | bizDBA: function(e, t) { 1274 | if (!/^.{1,23}$/.test(t)) 1275 | return 'Statement descriptors can only have up to 23 characters'; 1276 | }, 1277 | nameLength: function(e, t) { 1278 | if (t.length === 1) return 'Names need to be longer than one character'; 1279 | }, 1280 | }; 1281 | }.call(this)); 1282 | 1283 | // Alias Stripe for node compatibility 1284 | this.Schema.Stripe = this.Stripe; 1285 | 1286 | /** 1287 | * Aliases for compatibility between Stripe v1 and v2 1288 | */ 1289 | this.Stripe.createToken = function() { 1290 | return this.card.createToken.apply(arguments); 1291 | }; 1292 | this.Stripe.cardType = function() { 1293 | return this.card.cardType.apply(arguments); 1294 | }; 1295 | this.Stripe.validateCardNumber = function() { 1296 | return this.card.validateCardNumber.apply(arguments); 1297 | }; 1298 | this.Stripe.validateExpiry = function() { 1299 | return this.card.validateExpiry.apply(arguments); 1300 | }; 1301 | this.Stripe.validateCVC = function() { 1302 | return this.card.validateCVC.apply(arguments); 1303 | }; 1304 | 1305 | // Exports 1306 | if (typeof module !== 'undefined') { 1307 | module.exports = this.Schema; 1308 | } 1309 | }.call(this)); 1310 | 1311 | /** 1312 | * Utils 1313 | */ 1314 | (function() { 1315 | var Promise = this.Promise; 1316 | 1317 | /** 1318 | * Static utils 1319 | */ 1320 | var util = (this.Schema.util = {}); 1321 | 1322 | /** 1323 | * Inheritance helper 1324 | * 1325 | * @param object baseObj 1326 | * @param object superObj 1327 | */ 1328 | util.inherits = function(baseObj, superObj) { 1329 | baseObj._base = superObj; 1330 | var tempObj = function() {}; 1331 | tempObj.prototype = superObj.prototype; 1332 | baseObj.prototype = new tempObj(); 1333 | baseObj.prototype.constructor = baseObj; 1334 | }; 1335 | 1336 | /** 1337 | * Inspect a variable and output to console 1338 | */ 1339 | util.inspect = function(arg, options) { 1340 | if (require !== undefined) { 1341 | // NodeJS 1342 | return require('util').inspect(arg, options); 1343 | } else { 1344 | // Browser 1345 | return console.log(arg); 1346 | } 1347 | }; 1348 | 1349 | // Exports 1350 | if (typeof exports !== 'undefined') { 1351 | exports.util = this.Schema.util; 1352 | } 1353 | }.call(this)); 1354 | /** 1355 | * Resource 1356 | */ 1357 | (function() { 1358 | var util = this.Schema.util; 1359 | 1360 | /** 1361 | * Resource constructor 1362 | * 1363 | * @param string url 1364 | * @param mixed result 1365 | * @param Client client 1366 | */ 1367 | var Resource = (this.Schema.Resource = function(url, result, client) { 1368 | this.__url = url; 1369 | this.__client = client; 1370 | this.__data = null; 1371 | 1372 | if (result) { 1373 | if (result.$links) { 1374 | this.$links = Resource.$links[url] = result.$links; 1375 | } else if (Resource.$links[url]) { 1376 | this.$links = Resource.$links[url]; 1377 | } 1378 | if (result.$data) { 1379 | this.__initData(result.$data); 1380 | } 1381 | } 1382 | }); 1383 | 1384 | /** 1385 | * Resource links by url 1386 | * @var object 1387 | */ 1388 | Resource.$links = {}; 1389 | 1390 | /** 1391 | * Initialize resource data 1392 | * 1393 | * @param object data 1394 | */ 1395 | Resource.prototype.__initData = function(data) { 1396 | if (!data || typeof data !== 'object') { 1397 | return; 1398 | } 1399 | this.__data = data; 1400 | var keys = Object.keys(data); 1401 | for (var i = 0; i < keys.length; i++) { 1402 | var key = keys[i]; 1403 | if (!data.hasOwnProperty(key)) { 1404 | continue; 1405 | } 1406 | this[key] = data[key]; 1407 | } 1408 | }; 1409 | 1410 | /** 1411 | * Get raw resource data 1412 | * 1413 | * @return object 1414 | */ 1415 | Resource.prototype.toObject = function() { 1416 | var data; 1417 | if (this.__data) { 1418 | var keys = Object.keys(this.__data); 1419 | for (var i = 0; i < keys.length; i++) { 1420 | var key = keys[i]; 1421 | if (this.__data[key]) { 1422 | data = data || {}; 1423 | data[key] = this.__data[key]; 1424 | } 1425 | } 1426 | } 1427 | return data; 1428 | }; 1429 | 1430 | /** 1431 | * Get raw resource data 1432 | * 1433 | * @return object 1434 | */ 1435 | Resource.prototype.toJSON = function() { 1436 | return JSON.stringify(this.toObject()); 1437 | }; 1438 | 1439 | /** 1440 | * Resource as a string representing request url 1441 | * 1442 | * @return string 1443 | */ 1444 | Resource.prototype.toString = function() { 1445 | return this.__url; 1446 | }; 1447 | 1448 | /** 1449 | * Resource as inspected 1450 | * 1451 | * @return object 1452 | */ 1453 | Resource.prototype.inspect = function(depth) { 1454 | var props = this.toObject(); 1455 | if (this.$links) { 1456 | if (!props) { 1457 | props = {}; 1458 | } 1459 | props.$links = this.$links; 1460 | } 1461 | return util.inspect(props, { depth: depth, colors: true }); 1462 | }; 1463 | 1464 | // Exports 1465 | if (typeof exports !== 'undefined') { 1466 | exports.Resource = this.Schema.Resource; 1467 | } 1468 | }.call(this)); 1469 | 1470 | /** 1471 | * Record 1472 | */ 1473 | (function() { 1474 | var util = this.Schema.util; 1475 | var Resource = this.Schema.Resource; 1476 | 1477 | /** 1478 | * Record constructor 1479 | * 1480 | * @param string url 1481 | * @param mixed result 1482 | * @param Client client 1483 | * @param Collection Collection 1484 | */ 1485 | var Record = (this.Schema.Record = function(url, result, client, collection) { 1486 | Resource.call(this, url, result, client); 1487 | 1488 | if (this.$links) { 1489 | this.__initLinks(this.$links); 1490 | } 1491 | 1492 | this.collection = collection; 1493 | }); 1494 | 1495 | util.inherits(Record, Resource); 1496 | 1497 | /** 1498 | * Initialize record links 1499 | * 1500 | * @param links 1501 | */ 1502 | Record.prototype.__initLinks = function(links) { 1503 | var self = this; 1504 | this.__forEachLink(links, function(link, key, res, path) { 1505 | var url = self.__linkUrl(path); 1506 | // expanded? 1507 | if (res[key]) { 1508 | return; 1509 | } 1510 | // record.link(...) 1511 | res[key] = function(callback) { 1512 | if (typeof callback !== 'function') return; 1513 | res.__client.get(url, null, callback); 1514 | return this; 1515 | }; 1516 | if (res[key] === undefined) { 1517 | return; 1518 | } 1519 | // record.link.each(...) 1520 | res[key].each = function(callback, then) { 1521 | if (typeof callback !== 'function') return; 1522 | res[key](function(result) { 1523 | if (result && result.results) { 1524 | for (var i = 0; i < result.results.length; i++) { 1525 | callback(result.results[i]); 1526 | } 1527 | } else { 1528 | callback(result); 1529 | } 1530 | if (typeof then === 'function') { 1531 | then(result); 1532 | } 1533 | }); 1534 | }; 1535 | // record.link.get(...) 1536 | res[key].get = self.__linkRequest.bind(self, 'get', url); 1537 | // record.link.put(...) 1538 | res[key].put = self.__linkRequest.bind(self, 'put', url); 1539 | // record.link.post(...) 1540 | res[key].post = self.__linkRequest.bind(self, 'post', url); 1541 | // record.link.delete(...) 1542 | res[key].post = self.__linkRequest.bind(self, 'delete', url); 1543 | // api.put(record.link, ...) 1544 | res[key].toString = function() { 1545 | return url; 1546 | }; 1547 | }); 1548 | }; 1549 | 1550 | /** 1551 | * Iterate over link fields 1552 | * 1553 | * @param function callback (link, key, res) 1554 | */ 1555 | Record.prototype.__forEachLink = function(links, res, path, callback) { 1556 | if (typeof res === 'function') { 1557 | callback = res; 1558 | res = this; 1559 | path = ''; 1560 | } else if (typeof path === 'function') { 1561 | callback = path; 1562 | path = ''; 1563 | } 1564 | if (!res) { 1565 | return; 1566 | } 1567 | var keys = Object.keys(links); 1568 | for (var i = 0; i < keys.length; i++) { 1569 | var key = keys[i]; 1570 | var link = links[key]; 1571 | var keyPath = path + '/' + key; 1572 | if (link.url) { 1573 | callback(link, key, res, keyPath); 1574 | } 1575 | if (link.links) { 1576 | if (link.links['*']) { 1577 | if (!(res[key] instanceof Array)) continue; 1578 | for (var j = 0; j < res[key].length; j++) { 1579 | if (!res[key][j]) continue; 1580 | keyPath += '/' + (res[key][j].id || j); 1581 | this.__forEachLink(link.links['*'], res[key][j], keyPath, callback); 1582 | } 1583 | } else { 1584 | this.__forEachLink(link.links, res[key], keyPath, callback); 1585 | } 1586 | } 1587 | } 1588 | }; 1589 | 1590 | /** 1591 | * Build a link url for client request 1592 | * 1593 | * @param string field 1594 | * @return string 1595 | */ 1596 | Record.prototype.__linkRequest = function(method, url, relUrl, relData, callback) { 1597 | if (typeof relUrl === 'function') { 1598 | callback = relUrl; 1599 | relUrl = null; 1600 | } else if (typeof relData === 'function') { 1601 | callback = relData; 1602 | relData = null; 1603 | } 1604 | if (typeof relUrl === 'object') { 1605 | relData = relUrl; 1606 | relUrl = null; 1607 | if (typeof relData === 'function') { 1608 | callback = relData; 1609 | relData = null; 1610 | } 1611 | } 1612 | if (typeof callback !== 'function') return; 1613 | if (relUrl) { 1614 | url = url + '/' + relUrl.replace(/^\//, ''); 1615 | } 1616 | return this.__client[method](url, relData, callback); 1617 | }; 1618 | 1619 | /** 1620 | * Build a link url for client request 1621 | * 1622 | * @param string field 1623 | * @return string 1624 | */ 1625 | Record.prototype.__linkUrl = function(path) { 1626 | var url = this.__url; 1627 | var qpos = this.__url.indexOf('?'); 1628 | if (qpos !== -1) { 1629 | url = url.substring(0, qpos); 1630 | } 1631 | return url.replace(/\/$/, '') + '/' + path.replace(/^\//, ''); 1632 | }; 1633 | 1634 | /** 1635 | * Record as inspected 1636 | * 1637 | * @return object 1638 | */ 1639 | Record.prototype.inspect = function(depth) { 1640 | var props = this.toObject(); 1641 | if (this.$links) { 1642 | if (!this.collection) { 1643 | if (!props) { 1644 | props = {}; 1645 | } 1646 | props.$links = this.$links; 1647 | } 1648 | } 1649 | return util.inspect(props, { depth: depth, colors: true }); 1650 | }; 1651 | 1652 | // Exports 1653 | if (typeof exports !== 'undefined') { 1654 | exports.Record = this.Schema.Record; 1655 | } 1656 | }.call(this)); 1657 | /** 1658 | * Collection 1659 | */ 1660 | (function() { 1661 | var util = this.Schema.util; 1662 | var Resource = this.Schema.Resource; 1663 | var Record = this.Schema.Record; 1664 | 1665 | function filterNil(data) { 1666 | if (!data || !Array.isArray(data)) { 1667 | return data; 1668 | } 1669 | 1670 | return data.filter(function(item) { 1671 | return item !== undefined && item !== null; 1672 | }); 1673 | } 1674 | 1675 | /** 1676 | * Collection constructor 1677 | * 1678 | * @param string url 1679 | * @param object result 1680 | * @param Client client 1681 | */ 1682 | var Collection = (this.Schema.Collection = function(url, result, client) { 1683 | this.count = 0; 1684 | this.page = 0; 1685 | this.pages = {}; 1686 | this.length = 0; 1687 | 1688 | if (result && result.$data) { 1689 | result.$data.results = filterNil(result.$data.results); 1690 | 1691 | this.count = result.$data.count; 1692 | this.page = result.$data.page; 1693 | this.pages = result.$data.pages || {}; 1694 | this.length = result.$data.results ? result.$data.results.length : 0; 1695 | } 1696 | 1697 | result.$data = result.$data.results; 1698 | result = this.__buildRecords(url, result, client); 1699 | 1700 | Resource.call(this, url, result, client); 1701 | 1702 | this.results = []; 1703 | for (var i = 0; i < this.length; i++) { 1704 | this.results[i] = this[i]; 1705 | } 1706 | }); 1707 | 1708 | util.inherits(Collection, Resource); 1709 | 1710 | /** 1711 | * Build collection result data into Record resources 1712 | * 1713 | * @param string url 1714 | * @param object result 1715 | * @param Client client 1716 | * @return array 1717 | */ 1718 | Collection.prototype.__buildRecords = function(url, result, client) { 1719 | if (!(result.$data instanceof Array)) { 1720 | return null; 1721 | } 1722 | 1723 | var parentUrl = url; 1724 | var qpos = url.indexOf('?'); 1725 | if (qpos !== -1) { 1726 | url = url.substring(0, qpos); 1727 | } 1728 | 1729 | url = '/' + url.replace(/^\//, '').replace(/\/$/, ''); 1730 | for (var i = 0; i < result.$data.length; i++) { 1731 | var record = result.$data[i]; 1732 | var recordUrl = url + '/' + ((record && record.id) || i); 1733 | result.$data[i] = new Record( 1734 | recordUrl, 1735 | { $data: record, $links: result.$links }, 1736 | client, 1737 | this, 1738 | ); 1739 | } 1740 | 1741 | return result; 1742 | }; 1743 | 1744 | /** 1745 | * Iterate over results array style 1746 | * 1747 | * @param function callback 1748 | */ 1749 | Collection.prototype.each = function(callback) { 1750 | for (var i = 0; i < this.length; i++) { 1751 | callback.call(this, this[i]); 1752 | } 1753 | }; 1754 | 1755 | /** 1756 | * Get raw collection data 1757 | * 1758 | * @return object 1759 | */ 1760 | Collection.prototype.toObject = function() { 1761 | var results = []; 1762 | if (this.results) { 1763 | for (var i = 0; i < this.results.length; i++) { 1764 | if (this.results[i]) { 1765 | if (this.results[i].toObject) { 1766 | results[i] = this.results[i].toObject(); 1767 | } else { 1768 | results[i] = this.results[i]; 1769 | } 1770 | } 1771 | } 1772 | } 1773 | return { 1774 | count: this.count, 1775 | results: results, 1776 | page: this.page, 1777 | pages: this.pages, 1778 | }; 1779 | }; 1780 | 1781 | /** 1782 | * Collection as inspected 1783 | * 1784 | * @return object 1785 | */ 1786 | Collection.prototype.inspect = function(depth) { 1787 | var props = this.toObject(); 1788 | if (this.$links) { 1789 | if (!props) { 1790 | props = {}; 1791 | } 1792 | props.$links = this.$links; 1793 | } 1794 | return util.inspect(props, { depth: depth, colors: true }); 1795 | }; 1796 | 1797 | // Exports for Node 1798 | if (typeof exports !== 'undefined') { 1799 | exports.Collection = this.Schema.Collection; 1800 | } 1801 | }.call(this)); 1802 | /** 1803 | * Schema API Client for JS 1804 | */ 1805 | (function() { 1806 | // Not relevant for Node 1807 | if (typeof module !== 'undefined' && module.exports) { 1808 | return; 1809 | } 1810 | 1811 | var util = this.Schema.util; 1812 | 1813 | /** 1814 | * @param string clientId 1815 | * @param string publicKey 1816 | * @param object options 1817 | */ 1818 | var Client = (this.Schema.Client = function(publicKey, options) { 1819 | if (typeof publicKey === 'object') { 1820 | options = publicKey; 1821 | publicKey = undefined; 1822 | } else { 1823 | options = options || {}; 1824 | } 1825 | this.options = { 1826 | publicKey: publicKey || options.publicKey || this.Schema.publicKey, 1827 | hostUrl: options.hostUrl || this.Schema.publicUrl, 1828 | timeout: options.timeout || 15000, 1829 | version: options.version, 1830 | session: options.session, 1831 | api: options.api, 1832 | }; 1833 | }); 1834 | 1835 | /** 1836 | * Execute a client request using JSONP 1837 | * 1838 | * @param string method 1839 | * @param string url 1840 | * @param mixed data 1841 | * @param function callback 1842 | */ 1843 | Client.prototype.request = function(method, url, data, callback) { 1844 | if (typeof data === 'function') { 1845 | callback = data; 1846 | data = null; 1847 | } 1848 | 1849 | // TODO: implement $cached 1850 | 1851 | var requestId = Client.generateRequestId(); 1852 | 1853 | url = url && url.toString ? url.toString() : ''; 1854 | data = { 1855 | $jsonp: { 1856 | method: method, 1857 | callback: 'Schema.Client.response' + requestId, 1858 | }, 1859 | $data: data, 1860 | $key: this.options.publicKey, 1861 | }; 1862 | 1863 | var script = document.createElement('script'); 1864 | script.type = 'text/javascript'; 1865 | script.src = 1866 | this.options.hostUrl + '/' + url.replace(/^\//, '') + '?' + Client.serializeData(data); 1867 | 1868 | var self = this; 1869 | var errorTimeout = setTimeout(function() { 1870 | Schema.Client['response' + requestId]({ 1871 | $error: 'Request timed out after ' + self.options.timeout / 1000 + ' seconds', 1872 | $status: 500, 1873 | }); 1874 | }, self.options.timeout); 1875 | 1876 | Schema.Client['response' + requestId] = function(response) { 1877 | clearTimeout(errorTimeout); 1878 | self.response(method, url, response, callback); 1879 | delete Schema.Client['response' + requestId]; 1880 | script.parentNode.removeChild(script); 1881 | }; 1882 | 1883 | document.getElementsByTagName('head')[0].appendChild(script); 1884 | }; 1885 | 1886 | /** 1887 | * Client response handler 1888 | * 1889 | * @param string method 1890 | * @param string url 1891 | * @param mixed result 1892 | * @param function callback 1893 | */ 1894 | Client.prototype.response = function(method, url, result, callback) { 1895 | var actualResult = null; 1896 | 1897 | if (result && result.$data && typeof result.$data === 'object') { 1898 | actualResult = Client.createResource(result.$url, result, this); 1899 | } else { 1900 | actualResult = result.$data; 1901 | } 1902 | return callback && callback.call(this, actualResult, result); 1903 | }; 1904 | 1905 | /** 1906 | * GET a resource 1907 | * 1908 | * @param string url 1909 | * @param mixed data 1910 | * @param function callback 1911 | */ 1912 | Client.prototype.get = function(url, data, callback) { 1913 | return this.request('get', url, data, callback); 1914 | }; 1915 | 1916 | /** 1917 | * PUT a resource 1918 | * 1919 | * @param string url 1920 | * @param mixed data 1921 | * @param function callback 1922 | */ 1923 | Client.prototype.put = function(url, data, callback) { 1924 | return this.request('put', url, data, callback); 1925 | }; 1926 | 1927 | /** 1928 | * POST a resource 1929 | * 1930 | * @param string url 1931 | * @param mixed data 1932 | * @param function callback 1933 | */ 1934 | Client.prototype.post = function(url, data, callback) { 1935 | return this.request('post', url, data, callback); 1936 | }; 1937 | 1938 | /** 1939 | * DELETE a resource 1940 | * 1941 | * @param string url 1942 | * @param mixed data 1943 | * @param function callback 1944 | */ 1945 | Client.prototype.delete = function(url, data, callback) { 1946 | return this.request('delete', url, data, callback); 1947 | }; 1948 | 1949 | /** 1950 | * Generate a unique request ID for response callbacks 1951 | * 1952 | * @return number 1953 | */ 1954 | Client.generateRequestId = function() { 1955 | window.__schema_client_request_id = window.__schema_client_request_id || 0; 1956 | window.__schema_client_request_id++; 1957 | return window.__schema_client_request_id; 1958 | }; 1959 | 1960 | /** 1961 | * Serialize data into a query string 1962 | * Mostly copied from jQuery.param 1963 | * 1964 | * @param mixed data 1965 | * @return string 1966 | */ 1967 | Client.serializeData = function(data) { 1968 | var key; 1969 | var s = []; 1970 | var add = function(key, value) { 1971 | // If value is a function, invoke it and return its value 1972 | if (typeof value === 'function') { 1973 | value = value(); 1974 | } else if (value == null) { 1975 | value = ''; 1976 | } 1977 | s[s.length] = encodeURIComponent(key) + '=' + encodeURIComponent(value); 1978 | }; 1979 | for (key in data) { 1980 | buildParams(key, data[key], add); 1981 | } 1982 | return s.join('&').replace(' ', '+'); 1983 | }; 1984 | var rbracket = /\[\]$/; 1985 | function buildParams(key, obj, add) { 1986 | var name; 1987 | if (obj instanceof Array) { 1988 | for (var i = 0; i < obj.length; i++) { 1989 | if (rbracket.test(key)) { 1990 | // Treat each array item as a scalar. 1991 | add(key, v); 1992 | } else { 1993 | // Item is non-scalar (array or object), encode its numeric index. 1994 | buildParams(key + '[' + (typeof v === 'object' && v != null ? i : '') + ']', v, add); 1995 | } 1996 | } 1997 | } else if (obj && typeof obj === 'object') { 1998 | // Serialize object item. 1999 | for (name in obj) { 2000 | buildParams(key + '[' + name + ']', obj[name], add); 2001 | } 2002 | } else { 2003 | // Serialize scalar item. 2004 | add(key, obj); 2005 | } 2006 | } 2007 | 2008 | /** 2009 | * Client create/init helper 2010 | * 2011 | * @param string publicKey 2012 | * @param object options 2013 | * @return Client 2014 | */ 2015 | Client.create = function(publicKey, options) { 2016 | return new Client(publicKey, options); 2017 | }; 2018 | 2019 | /** 2020 | * Create a resource from result data 2021 | * 2022 | * @param string url 2023 | * @param mixed result 2024 | * @param Client client 2025 | * @return Resource 2026 | */ 2027 | Client.createResource = function(url, result, client) { 2028 | if (result && result.$data && 'count' in result.$data && result.$data.results) { 2029 | return new Schema.Collection(url, result, client); 2030 | } 2031 | return new Schema.Record(url, result, client); 2032 | }; 2033 | }.call(this)); 2034 | 2035 | /** 2036 | * Form 2037 | */ 2038 | (function() { 2039 | var Schema = this.Schema; 2040 | var Form = (Schema.Form = Schema.form = {}); 2041 | 2042 | /** 2043 | * Validate and tokenize card data for payment forms 2044 | * 2045 | * @param object params 2046 | * string publicKey (Required) 2047 | * - Public/publishable key for tokenization 2048 | * HTMLFormElement form (Optional) (Default: first form element) 2049 | * - Represents the form to capture data 2050 | * string name (Optional) (Default: 'card') 2051 | * - Name of the card submission parameter 2052 | * - Id of the payment gateway to use for tokenization 2053 | * function onError (Optional) 2054 | * - Handler for card errors triggered on submit 2055 | */ 2056 | Form.onSubmitCard = function(params) { 2057 | Form._validateCardParams(params); 2058 | 2059 | Form._addEventListener(params.form, 'submit', Form._onSubmitCard.bind(this, params)); 2060 | }; 2061 | 2062 | /** 2063 | * Handle card form submission event 2064 | */ 2065 | Form._onSubmitCard = function(params, event) { 2066 | // Card expiry is { month: 00, year: 0000 } 2067 | var cardExpiry; 2068 | if (params.fields.cardExp) { 2069 | cardExpiry = Schema.cardExpiry(params.fields.cardExp.value); 2070 | } else { 2071 | cardExpiry = { 2072 | month: params.fields.cardExpMonth.value, 2073 | year: params.fields.cardExpYear.value, 2074 | }; 2075 | } 2076 | 2077 | // Assemble card info 2078 | var data = { 2079 | number: params.fields.cardNumber.value, 2080 | cvc: params.fields.cardCVC.value, 2081 | exp_month: cardExpiry.month, 2082 | exp_year: cardExpiry.year, 2083 | billing: {}, 2084 | }; 2085 | // Billing info is optional 2086 | if (params.fields.billing) { 2087 | data.billing = { 2088 | name: params.fields.billingName && params.fields.billingName.value, 2089 | address1: params.fields.billingAddress1 && params.fields.billingAddress1.value, 2090 | address2: params.fields.billingAddress2 && params.fields.billingAddress2.value, 2091 | city: params.fields.billingCity && params.fields.billingCity.value, 2092 | state: params.fields.billingState && params.fields.billingState.value, 2093 | zip: params.fields.billingZip && params.fields.billingZip.value, 2094 | country: params.fields.billingCountry && params.fields.billingCountry.value, 2095 | }; 2096 | } 2097 | 2098 | // Trigger submit handler 2099 | if (typeof params.onSubmit === 'function') { 2100 | // Return false to prevent default 2101 | if (params.onSubmit(data) === false) { 2102 | return; 2103 | } 2104 | } 2105 | 2106 | // Card values are serialized and validated on change 2107 | var dataSerialized = JSON.stringify(data); 2108 | 2109 | // Return if card data is not changed 2110 | if (params.form.__cardData === dataSerialized) { 2111 | return; 2112 | } 2113 | 2114 | // Prevent form submission 2115 | Form._preventDefault(event); 2116 | 2117 | // Validate card data 2118 | var fieldsValid = true; 2119 | if (!Schema.validateCVC(data.cvc)) { 2120 | fieldsValid = false; 2121 | Form._triggerFieldError(params.onError, params.fields.cardCVC, ''); 2122 | } 2123 | if (!Schema.validateExpiry(data.exp_month, data.exp_year)) { 2124 | fieldsValid = false; 2125 | if (params.fields.cardExp) { 2126 | Form._triggerFieldError(params.onError, params.fields.cardExp, ''); 2127 | } 2128 | if (params.fields.cardExpMonth) { 2129 | Form._triggerFieldError(params.onError, params.fields.cardExpMonth, ''); 2130 | } 2131 | if (params.fields.cardExpYear) { 2132 | Form._triggerFieldError(params.onError, params.fields.cardExpYear, ''); 2133 | } 2134 | } 2135 | if (!Schema.validateCardNumber(data.number) || !data.number) { 2136 | fieldsValid = false; 2137 | Form._triggerFieldError(params.onError, params.fields.cardNumber, ''); 2138 | } 2139 | if (!fieldsValid) { 2140 | return; 2141 | } 2142 | 2143 | // Card data is valid, continue to process 2144 | params.form.__cardData = dataSerialized; 2145 | 2146 | Schema.setPublicKey(params.publicKey); 2147 | Schema.card.createToken(data, function(status, response) { 2148 | if (response.errors) { 2149 | params.form.__cardData = null; 2150 | for (var key in response.errors) { 2151 | var field; 2152 | switch (key) { 2153 | case 'exp_month': 2154 | field = params.fields.cardExp || params.fields.cardExpMonth; 2155 | break; 2156 | case 'exp_year': 2157 | field = params.fields.cardExp || params.fields.cardExpYear; 2158 | break; 2159 | case 'cvc': 2160 | field = params.fields.cardCVC; 2161 | break; 2162 | case 'number': 2163 | default: 2164 | field = params.fields.cardNumber; 2165 | break; 2166 | } 2167 | Form._triggerFieldError(params.onError, field, response.errors[key].message); 2168 | } 2169 | } else if (status > 200) { 2170 | Form._triggerFieldError( 2171 | params.onError, 2172 | params.fields.cardNumber, 2173 | 'Unknown gateway error, please try again', 2174 | ); 2175 | } else { 2176 | // Clear previous data fields first 2177 | var els = document.getElementsByClassName('x-card-response-data'); 2178 | for (var i = 0; i < els.length; i++) { 2179 | els[i].parentNode.removeChild(els[i]); 2180 | } 2181 | // Append card response fields to form 2182 | var fieldName = params.name; 2183 | for (var key in response) { 2184 | if (typeof response[key] === 'object') { 2185 | continue; 2186 | } 2187 | var el = document.createElement('input'); 2188 | el.type = 'hidden'; 2189 | el.className = 'x-card-response-data'; 2190 | el.name = params.name + '[' + key + ']'; 2191 | el.value = response[key]; 2192 | params.form.appendChild(el); 2193 | } 2194 | params.form.submit(); 2195 | } 2196 | }); 2197 | }; 2198 | 2199 | /** 2200 | * Make sure params are valid 2201 | * 2202 | * @return object 2203 | */ 2204 | Form._validateCardParams = function(params) { 2205 | params || {}; 2206 | 2207 | params.publicKey = params.publicKey || Schema.publicKey; 2208 | if (!params.publicKey) { 2209 | throw 'Form.onSubmitCard(): publicKey required'; 2210 | } 2211 | 2212 | // Ensure valid form element 2213 | if (!params.form) { 2214 | params.form = Form._findDefaultFormElement(); 2215 | } else { 2216 | params.form = Form._sanitizeElement(params.form); 2217 | } 2218 | if (params.form === null) { 2219 | throw 'Form.onSubmitCard(): form not found with .card-number field'; 2220 | } 2221 | 2222 | // Get valid card input fields 2223 | var fields = params.fields || {}; 2224 | fields.cardNumber = Form._sanitizeElement(fields.cardNumber || '.card-number'); 2225 | if (!fields.cardNumber) { 2226 | throw 'Form.onSubmitCard(): card number field not found'; 2227 | } 2228 | fields.cardExp = Form._sanitizeElement(fields.cardExp || '.card-exp'); 2229 | fields.cardExpMonth = Form._sanitizeElement(fields.cardExpMonth || '.card-exp-month'); 2230 | if (!fields.cardExp && !fields.cardExpMonth) { 2231 | throw 'Form.onSubmitCard(): card expiration field not found'; 2232 | } 2233 | fields.cardExpYear = Form._sanitizeElement(fields.cardExpYear || '.card-exp-year'); 2234 | if (!fields.cardExp && !fields.cardExpYear) { 2235 | throw 'Form.onSubmitCard(): card expiration year field not found'; 2236 | } 2237 | fields.cardCVC = Form._sanitizeElement(fields.cardCvc || fields.cardCVC || '.card-cvc'); 2238 | if (!fields.cardCVC) { 2239 | throw 'Form.onSubmitCard(): card cvc field not found'; 2240 | } 2241 | fields.billingName = Form._sanitizeElement(fields.billingName || '.billing-name'); 2242 | fields.billingAddress1 = Form._sanitizeElement(fields.billingAddress2 || '.billing-address1'); 2243 | fields.billingAddress2 = Form._sanitizeElement(fields.billingAddress2 || '.billing-address2'); 2244 | fields.billingCity = Form._sanitizeElement(fields.billingCity || '.billing-city'); 2245 | fields.billingState = Form._sanitizeElement(fields.billingState || '.billing-state'); 2246 | fields.billingZip = Form._sanitizeElement(fields.billingZip || '.billing-zip'); 2247 | fields.billingCountry = Form._sanitizeElement(fields.billingCountry || '.billing-country'); 2248 | fields.billing = !!( 2249 | fields.billingName || 2250 | fields.billingAddress1 || 2251 | fields.billingAddress2 || 2252 | fields.billingCity || 2253 | fields.billingState || 2254 | fields.billingZip || 2255 | fields.billingCountry 2256 | ); 2257 | 2258 | params.fields = fields; 2259 | 2260 | // Default submit parameter name 2261 | params.name = params.name || 'card'; 2262 | }; 2263 | 2264 | /** 2265 | * Get a clean HTMLElement reference 2266 | * May be passed by jQuery, ID, or class name 2267 | * 2268 | * @param mixed el 2269 | * @return HTMLElement 2270 | */ 2271 | Form._sanitizeElement = function(el) { 2272 | if (jQuery) { 2273 | // By jQuery reference 2274 | return jQuery(el).get(0); 2275 | } else if (typeof el === 'string') { 2276 | if (el[0] === '.') { 2277 | // By class name 2278 | return document.getElementsByClassName(el.substring(1))[0]; 2279 | } else { 2280 | // By ID 2281 | return document.getElementById(el); 2282 | } 2283 | } else if (typeof el === 'object' && el !== null) { 2284 | if (el.toString().indexOf('[object HTML') === 0) { 2285 | // By direct reference 2286 | return el; 2287 | } 2288 | } 2289 | // Not valid 2290 | return null; 2291 | }; 2292 | 2293 | /** 2294 | * Find the first form element with card-number input 2295 | * 2296 | * @return HTMLFormElement 2297 | */ 2298 | Form._findDefaultFormElement = function() { 2299 | var field = document.getElementsByClassName('card-number')[0]; 2300 | if (field) { 2301 | while (true) { 2302 | if ((field = field.parentNode)) { 2303 | if (field.tagName === 'FORM') { 2304 | return field; 2305 | } 2306 | } else { 2307 | break; 2308 | } 2309 | } 2310 | } 2311 | return null; 2312 | }; 2313 | 2314 | /** 2315 | * 2316 | */ 2317 | Form._triggerFieldError = function(handler, field, message) { 2318 | if (typeof handler === 'function') { 2319 | return handler(field, message); 2320 | } 2321 | if (field && field.className.indexOf('error') === -1) { 2322 | field.className = field.className + ' error'; 2323 | } 2324 | }; 2325 | 2326 | /** 2327 | * Add a DOM event listener, cross browser 2328 | * 2329 | * @param HTMLElement el 2330 | * @param string event 2331 | * @param function handler 2332 | */ 2333 | Form._addEventListener = function(el, event, handler) { 2334 | if (el.addEventListener) { 2335 | el.addEventListener(event, handler, false); 2336 | } else { 2337 | el.attachEvent('on' + event, function() { 2338 | // set the this pointer same as addEventListener when fn is called 2339 | return handler.call(el, window.event); 2340 | }); 2341 | } 2342 | }; 2343 | 2344 | /** 2345 | * Prevent default event behavior, cross browser 2346 | * 2347 | * @param HTMLEvent event 2348 | */ 2349 | Form._preventDefault = function(event) { 2350 | if (event.preventDefault) { 2351 | event.preventDefault(); 2352 | } else { 2353 | event.returnValue = false; 2354 | } 2355 | return; 2356 | }; 2357 | }.call(this)); 2358 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "schema-client", 3 | "version": "3.0.20", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "align-text": { 8 | "version": "0.1.4", 9 | "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", 10 | "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", 11 | "dev": true, 12 | "requires": { 13 | "kind-of": "3.2.2", 14 | "longest": "1.0.1", 15 | "repeat-string": "1.6.1" 16 | } 17 | }, 18 | "amdefine": { 19 | "version": "1.0.1", 20 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 21 | "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", 22 | "dev": true 23 | }, 24 | "ansi-escapes": { 25 | "version": "1.4.0", 26 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", 27 | "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", 28 | "dev": true 29 | }, 30 | "ansi-regex": { 31 | "version": "2.1.1", 32 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 33 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 34 | "dev": true 35 | }, 36 | "ansi-styles": { 37 | "version": "2.2.1", 38 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 39 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 40 | "dev": true 41 | }, 42 | "argparse": { 43 | "version": "1.0.9", 44 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", 45 | "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", 46 | "dev": true, 47 | "requires": { 48 | "sprintf-js": "1.0.3" 49 | } 50 | }, 51 | "array-union": { 52 | "version": "1.0.2", 53 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", 54 | "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", 55 | "dev": true, 56 | "requires": { 57 | "array-uniq": "1.0.3" 58 | } 59 | }, 60 | "array-uniq": { 61 | "version": "1.0.3", 62 | "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", 63 | "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", 64 | "dev": true 65 | }, 66 | "arrify": { 67 | "version": "1.0.1", 68 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 69 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", 70 | "dev": true 71 | }, 72 | "assertion-error": { 73 | "version": "1.1.0", 74 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 75 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 76 | "dev": true 77 | }, 78 | "async": { 79 | "version": "1.5.2", 80 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 81 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", 82 | "dev": true 83 | }, 84 | "balanced-match": { 85 | "version": "1.0.0", 86 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 87 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 88 | "dev": true 89 | }, 90 | "bluebird": { 91 | "version": "3.5.1", 92 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 93 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 94 | }, 95 | "brace-expansion": { 96 | "version": "1.1.8", 97 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 98 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 99 | "dev": true, 100 | "requires": { 101 | "balanced-match": "1.0.0", 102 | "concat-map": "0.0.1" 103 | } 104 | }, 105 | "camelcase": { 106 | "version": "1.2.1", 107 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", 108 | "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", 109 | "dev": true, 110 | "optional": true 111 | }, 112 | "center-align": { 113 | "version": "0.1.3", 114 | "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", 115 | "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", 116 | "dev": true, 117 | "optional": true, 118 | "requires": { 119 | "align-text": "0.1.4", 120 | "lazy-cache": "1.0.4" 121 | } 122 | }, 123 | "chai": { 124 | "version": "3.4.1", 125 | "resolved": "https://registry.npmjs.org/chai/-/chai-3.4.1.tgz", 126 | "integrity": "sha1-Mwri+BkSTCYYIDb6XkOojqThvYU=", 127 | "dev": true, 128 | "requires": { 129 | "assertion-error": "1.1.0", 130 | "deep-eql": "0.1.3", 131 | "type-detect": "1.0.0" 132 | } 133 | }, 134 | "chalk": { 135 | "version": "1.1.3", 136 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 137 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 138 | "dev": true, 139 | "requires": { 140 | "ansi-styles": "2.2.1", 141 | "escape-string-regexp": "1.0.5", 142 | "has-ansi": "2.0.0", 143 | "strip-ansi": "3.0.1", 144 | "supports-color": "2.0.0" 145 | } 146 | }, 147 | "circular-json": { 148 | "version": "0.3.3", 149 | "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", 150 | "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", 151 | "dev": true 152 | }, 153 | "cli-cursor": { 154 | "version": "1.0.2", 155 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", 156 | "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", 157 | "dev": true, 158 | "requires": { 159 | "restore-cursor": "1.0.1" 160 | } 161 | }, 162 | "cli-width": { 163 | "version": "1.1.1", 164 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz", 165 | "integrity": "sha1-pNKT72frt7iNSk1CwMzwDE0eNm0=", 166 | "dev": true 167 | }, 168 | "cliui": { 169 | "version": "2.1.0", 170 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", 171 | "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", 172 | "dev": true, 173 | "optional": true, 174 | "requires": { 175 | "center-align": "0.1.3", 176 | "right-align": "0.1.3", 177 | "wordwrap": "0.0.2" 178 | }, 179 | "dependencies": { 180 | "wordwrap": { 181 | "version": "0.0.2", 182 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", 183 | "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", 184 | "dev": true, 185 | "optional": true 186 | } 187 | } 188 | }, 189 | "code-point-at": { 190 | "version": "1.1.0", 191 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 192 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", 193 | "dev": true 194 | }, 195 | "commander": { 196 | "version": "2.3.0", 197 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", 198 | "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", 199 | "dev": true 200 | }, 201 | "concat-map": { 202 | "version": "0.0.1", 203 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 204 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 205 | "dev": true 206 | }, 207 | "concat-stream": { 208 | "version": "1.6.0", 209 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", 210 | "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", 211 | "dev": true, 212 | "requires": { 213 | "inherits": "2.0.3", 214 | "readable-stream": "2.3.3", 215 | "typedarray": "0.0.6" 216 | } 217 | }, 218 | "core-util-is": { 219 | "version": "1.0.2", 220 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 221 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 222 | "dev": true 223 | }, 224 | "d": { 225 | "version": "1.0.0", 226 | "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", 227 | "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", 228 | "dev": true, 229 | "requires": { 230 | "es5-ext": "0.10.38" 231 | } 232 | }, 233 | "debug": { 234 | "version": "2.6.9", 235 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 236 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 237 | "dev": true, 238 | "requires": { 239 | "ms": "2.0.0" 240 | } 241 | }, 242 | "decamelize": { 243 | "version": "1.2.0", 244 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 245 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 246 | "dev": true, 247 | "optional": true 248 | }, 249 | "deep-eql": { 250 | "version": "0.1.3", 251 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 252 | "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", 253 | "dev": true, 254 | "requires": { 255 | "type-detect": "0.1.1" 256 | }, 257 | "dependencies": { 258 | "type-detect": { 259 | "version": "0.1.1", 260 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 261 | "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", 262 | "dev": true 263 | } 264 | } 265 | }, 266 | "deep-is": { 267 | "version": "0.1.3", 268 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 269 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 270 | "dev": true 271 | }, 272 | "del": { 273 | "version": "2.2.2", 274 | "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", 275 | "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", 276 | "dev": true, 277 | "requires": { 278 | "globby": "5.0.0", 279 | "is-path-cwd": "1.0.0", 280 | "is-path-in-cwd": "1.0.0", 281 | "object-assign": "4.1.1", 282 | "pify": "2.3.0", 283 | "pinkie-promise": "2.0.1", 284 | "rimraf": "2.6.2" 285 | } 286 | }, 287 | "diff": { 288 | "version": "1.4.0", 289 | "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", 290 | "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", 291 | "dev": true 292 | }, 293 | "doctrine": { 294 | "version": "0.7.2", 295 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", 296 | "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=", 297 | "dev": true, 298 | "requires": { 299 | "esutils": "1.1.6", 300 | "isarray": "0.0.1" 301 | }, 302 | "dependencies": { 303 | "esutils": { 304 | "version": "1.1.6", 305 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz", 306 | "integrity": "sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=", 307 | "dev": true 308 | }, 309 | "isarray": { 310 | "version": "0.0.1", 311 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 312 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 313 | "dev": true 314 | } 315 | } 316 | }, 317 | "es5-ext": { 318 | "version": "0.10.38", 319 | "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.38.tgz", 320 | "integrity": "sha512-jCMyePo7AXbUESwbl8Qi01VSH2piY9s/a3rSU/5w/MlTIx8HPL1xn2InGN8ejt/xulcJgnTO7vqNtOAxzYd2Kg==", 321 | "dev": true, 322 | "requires": { 323 | "es6-iterator": "2.0.3", 324 | "es6-symbol": "3.1.1" 325 | } 326 | }, 327 | "es6-iterator": { 328 | "version": "2.0.3", 329 | "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", 330 | "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", 331 | "dev": true, 332 | "requires": { 333 | "d": "1.0.0", 334 | "es5-ext": "0.10.38", 335 | "es6-symbol": "3.1.1" 336 | } 337 | }, 338 | "es6-map": { 339 | "version": "0.1.5", 340 | "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", 341 | "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", 342 | "dev": true, 343 | "requires": { 344 | "d": "1.0.0", 345 | "es5-ext": "0.10.38", 346 | "es6-iterator": "2.0.3", 347 | "es6-set": "0.1.5", 348 | "es6-symbol": "3.1.1", 349 | "event-emitter": "0.3.5" 350 | } 351 | }, 352 | "es6-set": { 353 | "version": "0.1.5", 354 | "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", 355 | "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", 356 | "dev": true, 357 | "requires": { 358 | "d": "1.0.0", 359 | "es5-ext": "0.10.38", 360 | "es6-iterator": "2.0.3", 361 | "es6-symbol": "3.1.1", 362 | "event-emitter": "0.3.5" 363 | } 364 | }, 365 | "es6-symbol": { 366 | "version": "3.1.1", 367 | "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", 368 | "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", 369 | "dev": true, 370 | "requires": { 371 | "d": "1.0.0", 372 | "es5-ext": "0.10.38" 373 | } 374 | }, 375 | "es6-weak-map": { 376 | "version": "2.0.2", 377 | "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", 378 | "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", 379 | "dev": true, 380 | "requires": { 381 | "d": "1.0.0", 382 | "es5-ext": "0.10.38", 383 | "es6-iterator": "2.0.3", 384 | "es6-symbol": "3.1.1" 385 | } 386 | }, 387 | "escape-string-regexp": { 388 | "version": "1.0.5", 389 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 390 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 391 | "dev": true 392 | }, 393 | "escope": { 394 | "version": "3.6.0", 395 | "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", 396 | "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", 397 | "dev": true, 398 | "requires": { 399 | "es6-map": "0.1.5", 400 | "es6-weak-map": "2.0.2", 401 | "esrecurse": "4.2.0", 402 | "estraverse": "4.2.0" 403 | } 404 | }, 405 | "eslint": { 406 | "version": "1.10.3", 407 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-1.10.3.tgz", 408 | "integrity": "sha1-+xmpGxPBWAgrvKKUsX2Xm8g1Ogo=", 409 | "dev": true, 410 | "requires": { 411 | "chalk": "1.1.3", 412 | "concat-stream": "1.6.0", 413 | "debug": "2.6.9", 414 | "doctrine": "0.7.2", 415 | "escape-string-regexp": "1.0.5", 416 | "escope": "3.6.0", 417 | "espree": "2.2.5", 418 | "estraverse": "4.2.0", 419 | "estraverse-fb": "1.3.2", 420 | "esutils": "2.0.2", 421 | "file-entry-cache": "1.3.1", 422 | "glob": "5.0.15", 423 | "globals": "8.18.0", 424 | "handlebars": "4.0.11", 425 | "inquirer": "0.11.4", 426 | "is-my-json-valid": "2.17.1", 427 | "is-resolvable": "1.1.0", 428 | "js-yaml": "3.4.5", 429 | "json-stable-stringify": "1.0.1", 430 | "lodash.clonedeep": "3.0.2", 431 | "lodash.merge": "3.3.2", 432 | "lodash.omit": "3.1.0", 433 | "minimatch": "3.0.4", 434 | "mkdirp": "0.5.1", 435 | "object-assign": "4.1.1", 436 | "optionator": "0.6.0", 437 | "path-is-absolute": "1.0.1", 438 | "path-is-inside": "1.0.2", 439 | "shelljs": "0.5.3", 440 | "strip-json-comments": "1.0.4", 441 | "text-table": "0.2.0", 442 | "user-home": "2.0.0", 443 | "xml-escape": "1.0.0" 444 | } 445 | }, 446 | "espree": { 447 | "version": "2.2.5", 448 | "resolved": "https://registry.npmjs.org/espree/-/espree-2.2.5.tgz", 449 | "integrity": "sha1-32kbkxCIlAKuspzAZnCMVmkLhUs=", 450 | "dev": true 451 | }, 452 | "esrecurse": { 453 | "version": "4.2.0", 454 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", 455 | "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", 456 | "dev": true, 457 | "requires": { 458 | "estraverse": "4.2.0", 459 | "object-assign": "4.1.1" 460 | } 461 | }, 462 | "estraverse": { 463 | "version": "4.2.0", 464 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", 465 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", 466 | "dev": true 467 | }, 468 | "estraverse-fb": { 469 | "version": "1.3.2", 470 | "resolved": "https://registry.npmjs.org/estraverse-fb/-/estraverse-fb-1.3.2.tgz", 471 | "integrity": "sha1-0yOky15awzHOoDNBOpJT4WQ+B8Q=", 472 | "dev": true 473 | }, 474 | "esutils": { 475 | "version": "2.0.2", 476 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 477 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 478 | "dev": true 479 | }, 480 | "event-emitter": { 481 | "version": "0.3.5", 482 | "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", 483 | "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", 484 | "dev": true, 485 | "requires": { 486 | "d": "1.0.0", 487 | "es5-ext": "0.10.38" 488 | } 489 | }, 490 | "exit-hook": { 491 | "version": "1.1.1", 492 | "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", 493 | "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", 494 | "dev": true 495 | }, 496 | "fast-levenshtein": { 497 | "version": "1.0.7", 498 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz", 499 | "integrity": "sha1-AXjc3uAjuSkFGTrwlZ6KdjnP3Lk=", 500 | "dev": true 501 | }, 502 | "figures": { 503 | "version": "1.7.0", 504 | "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", 505 | "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", 506 | "dev": true, 507 | "requires": { 508 | "escape-string-regexp": "1.0.5", 509 | "object-assign": "4.1.1" 510 | } 511 | }, 512 | "file-entry-cache": { 513 | "version": "1.3.1", 514 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.3.1.tgz", 515 | "integrity": "sha1-RMYepgeuS+nBQC9B9EJwy/4zT/g=", 516 | "dev": true, 517 | "requires": { 518 | "flat-cache": "1.3.0", 519 | "object-assign": "4.1.1" 520 | } 521 | }, 522 | "flat-cache": { 523 | "version": "1.3.0", 524 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", 525 | "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", 526 | "dev": true, 527 | "requires": { 528 | "circular-json": "0.3.3", 529 | "del": "2.2.2", 530 | "graceful-fs": "4.1.11", 531 | "write": "0.2.1" 532 | } 533 | }, 534 | "formatio": { 535 | "version": "1.1.1", 536 | "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", 537 | "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", 538 | "dev": true, 539 | "requires": { 540 | "samsam": "1.1.2" 541 | } 542 | }, 543 | "fs.realpath": { 544 | "version": "1.0.0", 545 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 546 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 547 | "dev": true 548 | }, 549 | "generate-function": { 550 | "version": "2.0.0", 551 | "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", 552 | "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", 553 | "dev": true 554 | }, 555 | "generate-object-property": { 556 | "version": "1.2.0", 557 | "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", 558 | "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", 559 | "dev": true, 560 | "requires": { 561 | "is-property": "1.0.2" 562 | } 563 | }, 564 | "glob": { 565 | "version": "5.0.15", 566 | "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", 567 | "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", 568 | "dev": true, 569 | "requires": { 570 | "inflight": "1.0.6", 571 | "inherits": "2.0.3", 572 | "minimatch": "3.0.4", 573 | "once": "1.4.0", 574 | "path-is-absolute": "1.0.1" 575 | } 576 | }, 577 | "globals": { 578 | "version": "8.18.0", 579 | "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz", 580 | "integrity": "sha1-k9SmK9ysOM+vr8R9awNHaMsP/LQ=", 581 | "dev": true 582 | }, 583 | "globby": { 584 | "version": "5.0.0", 585 | "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", 586 | "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", 587 | "dev": true, 588 | "requires": { 589 | "array-union": "1.0.2", 590 | "arrify": "1.0.1", 591 | "glob": "7.1.2", 592 | "object-assign": "4.1.1", 593 | "pify": "2.3.0", 594 | "pinkie-promise": "2.0.1" 595 | }, 596 | "dependencies": { 597 | "glob": { 598 | "version": "7.1.2", 599 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 600 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 601 | "dev": true, 602 | "requires": { 603 | "fs.realpath": "1.0.0", 604 | "inflight": "1.0.6", 605 | "inherits": "2.0.3", 606 | "minimatch": "3.0.4", 607 | "once": "1.4.0", 608 | "path-is-absolute": "1.0.1" 609 | } 610 | } 611 | } 612 | }, 613 | "graceful-fs": { 614 | "version": "4.1.11", 615 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 616 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", 617 | "dev": true 618 | }, 619 | "growl": { 620 | "version": "1.8.1", 621 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.8.1.tgz", 622 | "integrity": "sha1-Sy3sjZB+k9szZiTc7AGDUC+MlCg=", 623 | "dev": true 624 | }, 625 | "handlebars": { 626 | "version": "4.0.11", 627 | "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", 628 | "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", 629 | "dev": true, 630 | "requires": { 631 | "async": "1.5.2", 632 | "optimist": "0.6.1", 633 | "source-map": "0.4.4", 634 | "uglify-js": "2.8.29" 635 | } 636 | }, 637 | "has-ansi": { 638 | "version": "2.0.0", 639 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 640 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 641 | "dev": true, 642 | "requires": { 643 | "ansi-regex": "2.1.1" 644 | } 645 | }, 646 | "inflight": { 647 | "version": "1.0.6", 648 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 649 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 650 | "dev": true, 651 | "requires": { 652 | "once": "1.4.0", 653 | "wrappy": "1.0.2" 654 | } 655 | }, 656 | "inherits": { 657 | "version": "2.0.3", 658 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 659 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 660 | "dev": true 661 | }, 662 | "inquirer": { 663 | "version": "0.11.4", 664 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.11.4.tgz", 665 | "integrity": "sha1-geM3ToNhvq/y2XAWIG01nQsy+k0=", 666 | "dev": true, 667 | "requires": { 668 | "ansi-escapes": "1.4.0", 669 | "ansi-regex": "2.1.1", 670 | "chalk": "1.1.3", 671 | "cli-cursor": "1.0.2", 672 | "cli-width": "1.1.1", 673 | "figures": "1.7.0", 674 | "lodash": "3.10.1", 675 | "readline2": "1.0.1", 676 | "run-async": "0.1.0", 677 | "rx-lite": "3.1.2", 678 | "string-width": "1.0.2", 679 | "strip-ansi": "3.0.1", 680 | "through": "2.3.8" 681 | } 682 | }, 683 | "is-buffer": { 684 | "version": "1.1.6", 685 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 686 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", 687 | "dev": true 688 | }, 689 | "is-fullwidth-code-point": { 690 | "version": "1.0.0", 691 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 692 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 693 | "dev": true, 694 | "requires": { 695 | "number-is-nan": "1.0.1" 696 | } 697 | }, 698 | "is-my-json-valid": { 699 | "version": "2.17.1", 700 | "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz", 701 | "integrity": "sha512-Q2khNw+oBlWuaYvEEHtKSw/pCxD2L5Rc1C+UQme9X6JdRDh7m5D7HkozA0qa3DUkQ6VzCnEm8mVIQPyIRkI5sQ==", 702 | "dev": true, 703 | "requires": { 704 | "generate-function": "2.0.0", 705 | "generate-object-property": "1.2.0", 706 | "jsonpointer": "4.0.1", 707 | "xtend": "4.0.1" 708 | } 709 | }, 710 | "is-path-cwd": { 711 | "version": "1.0.0", 712 | "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", 713 | "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", 714 | "dev": true 715 | }, 716 | "is-path-in-cwd": { 717 | "version": "1.0.0", 718 | "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", 719 | "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", 720 | "dev": true, 721 | "requires": { 722 | "is-path-inside": "1.0.1" 723 | } 724 | }, 725 | "is-path-inside": { 726 | "version": "1.0.1", 727 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", 728 | "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", 729 | "dev": true, 730 | "requires": { 731 | "path-is-inside": "1.0.2" 732 | } 733 | }, 734 | "is-property": { 735 | "version": "1.0.2", 736 | "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", 737 | "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", 738 | "dev": true 739 | }, 740 | "is-resolvable": { 741 | "version": "1.1.0", 742 | "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", 743 | "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", 744 | "dev": true 745 | }, 746 | "isarray": { 747 | "version": "1.0.0", 748 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 749 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 750 | "dev": true 751 | }, 752 | "jade": { 753 | "version": "0.26.3", 754 | "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", 755 | "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", 756 | "dev": true, 757 | "requires": { 758 | "commander": "0.6.1", 759 | "mkdirp": "0.3.0" 760 | }, 761 | "dependencies": { 762 | "commander": { 763 | "version": "0.6.1", 764 | "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", 765 | "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", 766 | "dev": true 767 | }, 768 | "mkdirp": { 769 | "version": "0.3.0", 770 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", 771 | "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", 772 | "dev": true 773 | } 774 | } 775 | }, 776 | "js-yaml": { 777 | "version": "3.4.5", 778 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.4.5.tgz", 779 | "integrity": "sha1-w0A3l98SuRhmV08t4jZG/oyvtE0=", 780 | "dev": true, 781 | "requires": { 782 | "argparse": "1.0.9", 783 | "esprima": "2.7.3" 784 | }, 785 | "dependencies": { 786 | "esprima": { 787 | "version": "2.7.3", 788 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", 789 | "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", 790 | "dev": true 791 | } 792 | } 793 | }, 794 | "json-stable-stringify": { 795 | "version": "1.0.1", 796 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", 797 | "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", 798 | "dev": true, 799 | "requires": { 800 | "jsonify": "0.0.0" 801 | } 802 | }, 803 | "jsonify": { 804 | "version": "0.0.0", 805 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 806 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", 807 | "dev": true 808 | }, 809 | "jsonpointer": { 810 | "version": "4.0.1", 811 | "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", 812 | "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", 813 | "dev": true 814 | }, 815 | "kind-of": { 816 | "version": "3.2.2", 817 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 818 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 819 | "dev": true, 820 | "requires": { 821 | "is-buffer": "1.1.6" 822 | } 823 | }, 824 | "lazy-cache": { 825 | "version": "1.0.4", 826 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", 827 | "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", 828 | "dev": true, 829 | "optional": true 830 | }, 831 | "levn": { 832 | "version": "0.2.5", 833 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz", 834 | "integrity": "sha1-uo0znQykphDjo/FFucr0iAcVUFQ=", 835 | "dev": true, 836 | "requires": { 837 | "prelude-ls": "1.1.2", 838 | "type-check": "0.3.2" 839 | } 840 | }, 841 | "lodash": { 842 | "version": "3.10.1", 843 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", 844 | "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", 845 | "dev": true 846 | }, 847 | "lodash._arraycopy": { 848 | "version": "3.0.0", 849 | "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", 850 | "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=", 851 | "dev": true 852 | }, 853 | "lodash._arrayeach": { 854 | "version": "3.0.0", 855 | "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", 856 | "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=", 857 | "dev": true 858 | }, 859 | "lodash._arraymap": { 860 | "version": "3.0.0", 861 | "resolved": "https://registry.npmjs.org/lodash._arraymap/-/lodash._arraymap-3.0.0.tgz", 862 | "integrity": "sha1-Go/Q9MDfS2HeoHbXF83Jfwo8PmY=", 863 | "dev": true 864 | }, 865 | "lodash._baseassign": { 866 | "version": "3.2.0", 867 | "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", 868 | "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", 869 | "dev": true, 870 | "requires": { 871 | "lodash._basecopy": "3.0.1", 872 | "lodash.keys": "3.1.2" 873 | } 874 | }, 875 | "lodash._baseclone": { 876 | "version": "3.3.0", 877 | "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", 878 | "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", 879 | "dev": true, 880 | "requires": { 881 | "lodash._arraycopy": "3.0.0", 882 | "lodash._arrayeach": "3.0.0", 883 | "lodash._baseassign": "3.2.0", 884 | "lodash._basefor": "3.0.3", 885 | "lodash.isarray": "3.0.4", 886 | "lodash.keys": "3.1.2" 887 | } 888 | }, 889 | "lodash._basecopy": { 890 | "version": "3.0.1", 891 | "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", 892 | "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", 893 | "dev": true 894 | }, 895 | "lodash._basedifference": { 896 | "version": "3.0.3", 897 | "resolved": "https://registry.npmjs.org/lodash._basedifference/-/lodash._basedifference-3.0.3.tgz", 898 | "integrity": "sha1-8sIEKWwqeOArOJCBtu3KyTPPYpw=", 899 | "dev": true, 900 | "requires": { 901 | "lodash._baseindexof": "3.1.0", 902 | "lodash._cacheindexof": "3.0.2", 903 | "lodash._createcache": "3.1.2" 904 | } 905 | }, 906 | "lodash._baseflatten": { 907 | "version": "3.1.4", 908 | "resolved": "https://registry.npmjs.org/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz", 909 | "integrity": "sha1-B3D/gBMa9uNPO1EXlqe6UhTmX/c=", 910 | "dev": true, 911 | "requires": { 912 | "lodash.isarguments": "3.1.0", 913 | "lodash.isarray": "3.0.4" 914 | } 915 | }, 916 | "lodash._basefor": { 917 | "version": "3.0.3", 918 | "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", 919 | "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=", 920 | "dev": true 921 | }, 922 | "lodash._baseindexof": { 923 | "version": "3.1.0", 924 | "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", 925 | "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=", 926 | "dev": true 927 | }, 928 | "lodash._bindcallback": { 929 | "version": "3.0.1", 930 | "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", 931 | "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", 932 | "dev": true 933 | }, 934 | "lodash._cacheindexof": { 935 | "version": "3.0.2", 936 | "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", 937 | "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=", 938 | "dev": true 939 | }, 940 | "lodash._createassigner": { 941 | "version": "3.1.1", 942 | "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", 943 | "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", 944 | "dev": true, 945 | "requires": { 946 | "lodash._bindcallback": "3.0.1", 947 | "lodash._isiterateecall": "3.0.9", 948 | "lodash.restparam": "3.6.1" 949 | } 950 | }, 951 | "lodash._createcache": { 952 | "version": "3.1.2", 953 | "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", 954 | "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", 955 | "dev": true, 956 | "requires": { 957 | "lodash._getnative": "3.9.1" 958 | } 959 | }, 960 | "lodash._getnative": { 961 | "version": "3.9.1", 962 | "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", 963 | "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", 964 | "dev": true 965 | }, 966 | "lodash._isiterateecall": { 967 | "version": "3.0.9", 968 | "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", 969 | "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", 970 | "dev": true 971 | }, 972 | "lodash._pickbyarray": { 973 | "version": "3.0.2", 974 | "resolved": "https://registry.npmjs.org/lodash._pickbyarray/-/lodash._pickbyarray-3.0.2.tgz", 975 | "integrity": "sha1-H4mNlgfrVgsOFnOEt3x8bRCKpMU=", 976 | "dev": true 977 | }, 978 | "lodash._pickbycallback": { 979 | "version": "3.0.0", 980 | "resolved": "https://registry.npmjs.org/lodash._pickbycallback/-/lodash._pickbycallback-3.0.0.tgz", 981 | "integrity": "sha1-/2G5oBens699MObFPeKK+hm4dQo=", 982 | "dev": true, 983 | "requires": { 984 | "lodash._basefor": "3.0.3", 985 | "lodash.keysin": "3.0.8" 986 | } 987 | }, 988 | "lodash.clonedeep": { 989 | "version": "3.0.2", 990 | "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz", 991 | "integrity": "sha1-oKHkDYKl6on/WxR7hETtY9koJ9s=", 992 | "dev": true, 993 | "requires": { 994 | "lodash._baseclone": "3.3.0", 995 | "lodash._bindcallback": "3.0.1" 996 | } 997 | }, 998 | "lodash.isarguments": { 999 | "version": "3.1.0", 1000 | "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", 1001 | "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", 1002 | "dev": true 1003 | }, 1004 | "lodash.isarray": { 1005 | "version": "3.0.4", 1006 | "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", 1007 | "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", 1008 | "dev": true 1009 | }, 1010 | "lodash.isplainobject": { 1011 | "version": "3.2.0", 1012 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz", 1013 | "integrity": "sha1-moI4rhayAEMpYM1zRlEtASP79MU=", 1014 | "dev": true, 1015 | "requires": { 1016 | "lodash._basefor": "3.0.3", 1017 | "lodash.isarguments": "3.1.0", 1018 | "lodash.keysin": "3.0.8" 1019 | } 1020 | }, 1021 | "lodash.istypedarray": { 1022 | "version": "3.0.6", 1023 | "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", 1024 | "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=", 1025 | "dev": true 1026 | }, 1027 | "lodash.keys": { 1028 | "version": "3.1.2", 1029 | "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", 1030 | "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", 1031 | "dev": true, 1032 | "requires": { 1033 | "lodash._getnative": "3.9.1", 1034 | "lodash.isarguments": "3.1.0", 1035 | "lodash.isarray": "3.0.4" 1036 | } 1037 | }, 1038 | "lodash.keysin": { 1039 | "version": "3.0.8", 1040 | "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz", 1041 | "integrity": "sha1-IsRJPrvtsUJ5YqVLRFssinZ/tH8=", 1042 | "dev": true, 1043 | "requires": { 1044 | "lodash.isarguments": "3.1.0", 1045 | "lodash.isarray": "3.0.4" 1046 | } 1047 | }, 1048 | "lodash.merge": { 1049 | "version": "3.3.2", 1050 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-3.3.2.tgz", 1051 | "integrity": "sha1-DZDZPtY3sYeEN7s+IWASYNev6ZQ=", 1052 | "dev": true, 1053 | "requires": { 1054 | "lodash._arraycopy": "3.0.0", 1055 | "lodash._arrayeach": "3.0.0", 1056 | "lodash._createassigner": "3.1.1", 1057 | "lodash._getnative": "3.9.1", 1058 | "lodash.isarguments": "3.1.0", 1059 | "lodash.isarray": "3.0.4", 1060 | "lodash.isplainobject": "3.2.0", 1061 | "lodash.istypedarray": "3.0.6", 1062 | "lodash.keys": "3.1.2", 1063 | "lodash.keysin": "3.0.8", 1064 | "lodash.toplainobject": "3.0.0" 1065 | } 1066 | }, 1067 | "lodash.omit": { 1068 | "version": "3.1.0", 1069 | "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-3.1.0.tgz", 1070 | "integrity": "sha1-iX/jguZBPZrJfGH3jtHgV6AK+fM=", 1071 | "dev": true, 1072 | "requires": { 1073 | "lodash._arraymap": "3.0.0", 1074 | "lodash._basedifference": "3.0.3", 1075 | "lodash._baseflatten": "3.1.4", 1076 | "lodash._bindcallback": "3.0.1", 1077 | "lodash._pickbyarray": "3.0.2", 1078 | "lodash._pickbycallback": "3.0.0", 1079 | "lodash.keysin": "3.0.8", 1080 | "lodash.restparam": "3.6.1" 1081 | } 1082 | }, 1083 | "lodash.restparam": { 1084 | "version": "3.6.1", 1085 | "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", 1086 | "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", 1087 | "dev": true 1088 | }, 1089 | "lodash.toplainobject": { 1090 | "version": "3.0.0", 1091 | "resolved": "https://registry.npmjs.org/lodash.toplainobject/-/lodash.toplainobject-3.0.0.tgz", 1092 | "integrity": "sha1-KHkK2ULSk9eKpmOgfs9/UsoEGY0=", 1093 | "dev": true, 1094 | "requires": { 1095 | "lodash._basecopy": "3.0.1", 1096 | "lodash.keysin": "3.0.8" 1097 | } 1098 | }, 1099 | "lolex": { 1100 | "version": "1.3.2", 1101 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", 1102 | "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", 1103 | "dev": true 1104 | }, 1105 | "longest": { 1106 | "version": "1.0.1", 1107 | "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", 1108 | "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", 1109 | "dev": true 1110 | }, 1111 | "lru-cache": { 1112 | "version": "2.7.3", 1113 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", 1114 | "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", 1115 | "dev": true 1116 | }, 1117 | "minimatch": { 1118 | "version": "3.0.4", 1119 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1120 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1121 | "dev": true, 1122 | "requires": { 1123 | "brace-expansion": "1.1.8" 1124 | } 1125 | }, 1126 | "minimist": { 1127 | "version": "0.0.8", 1128 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 1129 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 1130 | "dev": true 1131 | }, 1132 | "mkdirp": { 1133 | "version": "0.5.1", 1134 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 1135 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 1136 | "dev": true, 1137 | "requires": { 1138 | "minimist": "0.0.8" 1139 | } 1140 | }, 1141 | "mocha": { 1142 | "version": "2.3.4", 1143 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.3.4.tgz", 1144 | "integrity": "sha1-himm+wRPLSJapLgaKuLQAWmesmY=", 1145 | "dev": true, 1146 | "requires": { 1147 | "commander": "2.3.0", 1148 | "debug": "2.2.0", 1149 | "diff": "1.4.0", 1150 | "escape-string-regexp": "1.0.2", 1151 | "glob": "3.2.3", 1152 | "growl": "1.8.1", 1153 | "jade": "0.26.3", 1154 | "mkdirp": "0.5.0", 1155 | "supports-color": "1.2.0" 1156 | }, 1157 | "dependencies": { 1158 | "debug": { 1159 | "version": "2.2.0", 1160 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 1161 | "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 1162 | "dev": true, 1163 | "requires": { 1164 | "ms": "0.7.1" 1165 | } 1166 | }, 1167 | "escape-string-regexp": { 1168 | "version": "1.0.2", 1169 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", 1170 | "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", 1171 | "dev": true 1172 | }, 1173 | "glob": { 1174 | "version": "3.2.3", 1175 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", 1176 | "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", 1177 | "dev": true, 1178 | "requires": { 1179 | "graceful-fs": "2.0.3", 1180 | "inherits": "2.0.3", 1181 | "minimatch": "0.2.14" 1182 | } 1183 | }, 1184 | "graceful-fs": { 1185 | "version": "2.0.3", 1186 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", 1187 | "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", 1188 | "dev": true 1189 | }, 1190 | "minimatch": { 1191 | "version": "0.2.14", 1192 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", 1193 | "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", 1194 | "dev": true, 1195 | "requires": { 1196 | "lru-cache": "2.7.3", 1197 | "sigmund": "1.0.1" 1198 | } 1199 | }, 1200 | "mkdirp": { 1201 | "version": "0.5.0", 1202 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", 1203 | "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", 1204 | "dev": true, 1205 | "requires": { 1206 | "minimist": "0.0.8" 1207 | } 1208 | }, 1209 | "ms": { 1210 | "version": "0.7.1", 1211 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 1212 | "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", 1213 | "dev": true 1214 | }, 1215 | "supports-color": { 1216 | "version": "1.2.0", 1217 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", 1218 | "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", 1219 | "dev": true 1220 | } 1221 | } 1222 | }, 1223 | "ms": { 1224 | "version": "2.0.0", 1225 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1226 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 1227 | "dev": true 1228 | }, 1229 | "mute-stream": { 1230 | "version": "0.0.5", 1231 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", 1232 | "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", 1233 | "dev": true 1234 | }, 1235 | "number-is-nan": { 1236 | "version": "1.0.1", 1237 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1238 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 1239 | "dev": true 1240 | }, 1241 | "object-assign": { 1242 | "version": "4.1.1", 1243 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1244 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 1245 | "dev": true 1246 | }, 1247 | "once": { 1248 | "version": "1.4.0", 1249 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1250 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1251 | "dev": true, 1252 | "requires": { 1253 | "wrappy": "1.0.2" 1254 | } 1255 | }, 1256 | "onetime": { 1257 | "version": "1.1.0", 1258 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", 1259 | "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", 1260 | "dev": true 1261 | }, 1262 | "optimist": { 1263 | "version": "0.6.1", 1264 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 1265 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 1266 | "dev": true, 1267 | "requires": { 1268 | "minimist": "0.0.8", 1269 | "wordwrap": "0.0.3" 1270 | } 1271 | }, 1272 | "optionator": { 1273 | "version": "0.6.0", 1274 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.6.0.tgz", 1275 | "integrity": "sha1-tj7Lvw4xX61LyYJ7Rdx7pFKE/LY=", 1276 | "dev": true, 1277 | "requires": { 1278 | "deep-is": "0.1.3", 1279 | "fast-levenshtein": "1.0.7", 1280 | "levn": "0.2.5", 1281 | "prelude-ls": "1.1.2", 1282 | "type-check": "0.3.2", 1283 | "wordwrap": "0.0.3" 1284 | } 1285 | }, 1286 | "os-homedir": { 1287 | "version": "1.0.2", 1288 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 1289 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", 1290 | "dev": true 1291 | }, 1292 | "path-is-absolute": { 1293 | "version": "1.0.1", 1294 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1295 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1296 | "dev": true 1297 | }, 1298 | "path-is-inside": { 1299 | "version": "1.0.2", 1300 | "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", 1301 | "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", 1302 | "dev": true 1303 | }, 1304 | "pify": { 1305 | "version": "2.3.0", 1306 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 1307 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", 1308 | "dev": true 1309 | }, 1310 | "pinkie": { 1311 | "version": "2.0.4", 1312 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 1313 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", 1314 | "dev": true 1315 | }, 1316 | "pinkie-promise": { 1317 | "version": "2.0.1", 1318 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 1319 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 1320 | "dev": true, 1321 | "requires": { 1322 | "pinkie": "2.0.4" 1323 | } 1324 | }, 1325 | "prelude-ls": { 1326 | "version": "1.1.2", 1327 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 1328 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", 1329 | "dev": true 1330 | }, 1331 | "process-nextick-args": { 1332 | "version": "1.0.7", 1333 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 1334 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", 1335 | "dev": true 1336 | }, 1337 | "readable-stream": { 1338 | "version": "2.3.3", 1339 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", 1340 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", 1341 | "dev": true, 1342 | "requires": { 1343 | "core-util-is": "1.0.2", 1344 | "inherits": "2.0.3", 1345 | "isarray": "1.0.0", 1346 | "process-nextick-args": "1.0.7", 1347 | "safe-buffer": "5.1.1", 1348 | "string_decoder": "1.0.3", 1349 | "util-deprecate": "1.0.2" 1350 | } 1351 | }, 1352 | "readline2": { 1353 | "version": "1.0.1", 1354 | "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", 1355 | "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", 1356 | "dev": true, 1357 | "requires": { 1358 | "code-point-at": "1.1.0", 1359 | "is-fullwidth-code-point": "1.0.0", 1360 | "mute-stream": "0.0.5" 1361 | } 1362 | }, 1363 | "repeat-string": { 1364 | "version": "1.6.1", 1365 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 1366 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", 1367 | "dev": true 1368 | }, 1369 | "restore-cursor": { 1370 | "version": "1.0.1", 1371 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", 1372 | "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", 1373 | "dev": true, 1374 | "requires": { 1375 | "exit-hook": "1.1.1", 1376 | "onetime": "1.1.0" 1377 | } 1378 | }, 1379 | "right-align": { 1380 | "version": "0.1.3", 1381 | "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", 1382 | "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", 1383 | "dev": true, 1384 | "optional": true, 1385 | "requires": { 1386 | "align-text": "0.1.4" 1387 | } 1388 | }, 1389 | "rimraf": { 1390 | "version": "2.6.2", 1391 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 1392 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 1393 | "dev": true, 1394 | "requires": { 1395 | "glob": "7.1.2" 1396 | }, 1397 | "dependencies": { 1398 | "glob": { 1399 | "version": "7.1.2", 1400 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 1401 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 1402 | "dev": true, 1403 | "requires": { 1404 | "fs.realpath": "1.0.0", 1405 | "inflight": "1.0.6", 1406 | "inherits": "2.0.3", 1407 | "minimatch": "3.0.4", 1408 | "once": "1.4.0", 1409 | "path-is-absolute": "1.0.1" 1410 | } 1411 | } 1412 | } 1413 | }, 1414 | "run-async": { 1415 | "version": "0.1.0", 1416 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", 1417 | "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", 1418 | "dev": true, 1419 | "requires": { 1420 | "once": "1.4.0" 1421 | } 1422 | }, 1423 | "rx-lite": { 1424 | "version": "3.1.2", 1425 | "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", 1426 | "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", 1427 | "dev": true 1428 | }, 1429 | "safe-buffer": { 1430 | "version": "5.1.1", 1431 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 1432 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", 1433 | "dev": true 1434 | }, 1435 | "samsam": { 1436 | "version": "1.1.2", 1437 | "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", 1438 | "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", 1439 | "dev": true 1440 | }, 1441 | "shelljs": { 1442 | "version": "0.5.3", 1443 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", 1444 | "integrity": "sha1-xUmCuZbHbvDB5rWfvcWCX1txMRM=", 1445 | "dev": true 1446 | }, 1447 | "sigmund": { 1448 | "version": "1.0.1", 1449 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", 1450 | "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", 1451 | "dev": true 1452 | }, 1453 | "sinon": { 1454 | "version": "1.17.7", 1455 | "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", 1456 | "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", 1457 | "dev": true, 1458 | "requires": { 1459 | "formatio": "1.1.1", 1460 | "lolex": "1.3.2", 1461 | "samsam": "1.1.2", 1462 | "util": "0.10.3" 1463 | } 1464 | }, 1465 | "source-map": { 1466 | "version": "0.4.4", 1467 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", 1468 | "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", 1469 | "dev": true, 1470 | "requires": { 1471 | "amdefine": "1.0.1" 1472 | } 1473 | }, 1474 | "sprintf-js": { 1475 | "version": "1.0.3", 1476 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1477 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1478 | "dev": true 1479 | }, 1480 | "string-width": { 1481 | "version": "1.0.2", 1482 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1483 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1484 | "dev": true, 1485 | "requires": { 1486 | "code-point-at": "1.1.0", 1487 | "is-fullwidth-code-point": "1.0.0", 1488 | "strip-ansi": "3.0.1" 1489 | } 1490 | }, 1491 | "string_decoder": { 1492 | "version": "1.0.3", 1493 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 1494 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 1495 | "dev": true, 1496 | "requires": { 1497 | "safe-buffer": "5.1.1" 1498 | } 1499 | }, 1500 | "strip-ansi": { 1501 | "version": "3.0.1", 1502 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1503 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1504 | "dev": true, 1505 | "requires": { 1506 | "ansi-regex": "2.1.1" 1507 | } 1508 | }, 1509 | "strip-json-comments": { 1510 | "version": "1.0.4", 1511 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", 1512 | "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", 1513 | "dev": true 1514 | }, 1515 | "supports-color": { 1516 | "version": "2.0.0", 1517 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1518 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 1519 | "dev": true 1520 | }, 1521 | "text-table": { 1522 | "version": "0.2.0", 1523 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1524 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 1525 | "dev": true 1526 | }, 1527 | "through": { 1528 | "version": "2.3.8", 1529 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1530 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 1531 | "dev": true 1532 | }, 1533 | "type-check": { 1534 | "version": "0.3.2", 1535 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 1536 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 1537 | "dev": true, 1538 | "requires": { 1539 | "prelude-ls": "1.1.2" 1540 | } 1541 | }, 1542 | "type-detect": { 1543 | "version": "1.0.0", 1544 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", 1545 | "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", 1546 | "dev": true 1547 | }, 1548 | "typedarray": { 1549 | "version": "0.0.6", 1550 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1551 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", 1552 | "dev": true 1553 | }, 1554 | "uglify-js": { 1555 | "version": "2.8.29", 1556 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", 1557 | "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", 1558 | "dev": true, 1559 | "optional": true, 1560 | "requires": { 1561 | "source-map": "0.5.7", 1562 | "uglify-to-browserify": "1.0.2", 1563 | "yargs": "3.10.0" 1564 | }, 1565 | "dependencies": { 1566 | "source-map": { 1567 | "version": "0.5.7", 1568 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 1569 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 1570 | "dev": true, 1571 | "optional": true 1572 | } 1573 | } 1574 | }, 1575 | "uglify-to-browserify": { 1576 | "version": "1.0.2", 1577 | "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", 1578 | "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", 1579 | "dev": true, 1580 | "optional": true 1581 | }, 1582 | "user-home": { 1583 | "version": "2.0.0", 1584 | "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", 1585 | "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", 1586 | "dev": true, 1587 | "requires": { 1588 | "os-homedir": "1.0.2" 1589 | } 1590 | }, 1591 | "util": { 1592 | "version": "0.10.3", 1593 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", 1594 | "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", 1595 | "dev": true, 1596 | "requires": { 1597 | "inherits": "2.0.1" 1598 | }, 1599 | "dependencies": { 1600 | "inherits": { 1601 | "version": "2.0.1", 1602 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", 1603 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", 1604 | "dev": true 1605 | } 1606 | } 1607 | }, 1608 | "util-deprecate": { 1609 | "version": "1.0.2", 1610 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1611 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 1612 | "dev": true 1613 | }, 1614 | "window-size": { 1615 | "version": "0.1.0", 1616 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", 1617 | "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", 1618 | "dev": true, 1619 | "optional": true 1620 | }, 1621 | "wordwrap": { 1622 | "version": "0.0.3", 1623 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 1624 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", 1625 | "dev": true 1626 | }, 1627 | "wrappy": { 1628 | "version": "1.0.2", 1629 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1630 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1631 | "dev": true 1632 | }, 1633 | "write": { 1634 | "version": "0.2.1", 1635 | "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", 1636 | "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", 1637 | "dev": true, 1638 | "requires": { 1639 | "mkdirp": "0.5.1" 1640 | } 1641 | }, 1642 | "xml-escape": { 1643 | "version": "1.0.0", 1644 | "resolved": "https://registry.npmjs.org/xml-escape/-/xml-escape-1.0.0.tgz", 1645 | "integrity": "sha1-AJY9aXsq3wwYXE4E5zF0upsojrI=", 1646 | "dev": true 1647 | }, 1648 | "xtend": { 1649 | "version": "4.0.1", 1650 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 1651 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", 1652 | "dev": true 1653 | }, 1654 | "yargs": { 1655 | "version": "3.10.0", 1656 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", 1657 | "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", 1658 | "dev": true, 1659 | "optional": true, 1660 | "requires": { 1661 | "camelcase": "1.2.1", 1662 | "cliui": "2.1.0", 1663 | "decamelize": "1.2.0", 1664 | "window-size": "0.1.0" 1665 | } 1666 | } 1667 | } 1668 | } 1669 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "schema-client", 3 | "version": "3.0.20", 4 | "description": "Schema API Client for NodeJS", 5 | "keywords": [ 6 | "schema", 7 | "ecommerce", 8 | "payments", 9 | "api" 10 | ], 11 | "homepage": "https://github.com/schemaio/schema-node-client", 12 | "author": "Schema", 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/schemaio/schema-node-client.git" 16 | }, 17 | "bugs:": "https://github.com/schemaio/schema-node-client/issues", 18 | "engines": { 19 | "node": ">= v8.0.0" 20 | }, 21 | "main": "./lib/client.js", 22 | "devDependencies": { 23 | "chai": "~3.4.1", 24 | "eslint": "^1.10.3", 25 | "mocha": "~2.3.4", 26 | "sinon": "^1.17.2" 27 | }, 28 | "dependencies": { 29 | "bluebird": "^3.1.1" 30 | }, 31 | "license": "MIT", 32 | "scripts": { 33 | "test": "mocha lib/*.test.js" 34 | } 35 | } 36 | --------------------------------------------------------------------------------