diff --git a/.gitignore b/.gitignore index 7f58f6f1..fb49b642 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .npm -.jshintignore .project .settings .tern-project diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 00000000..518d9045 --- /dev/null +++ b/.jshintignore @@ -0,0 +1 @@ +box/**/*.js diff --git a/box/box.html b/box/box.html index 43622bfa..038e8dd0 100644 --- a/box/box.html +++ b/box/box.html @@ -17,25 +17,51 @@ + + @@ -192,10 +291,19 @@ -
+
+
+ + +
+
+ + +
@@ -203,25 +311,119 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/box/box.js b/box/box.js index 74280442..72fe5370 100644 --- a/box/box.js +++ b/box/box.js @@ -14,538 +14,20 @@ * limitations under the License. **/ -module.exports = function(RED) { - "use strict"; - var crypto = require("crypto"); - var fs = require("fs"); - var request = require("request"); - var url = require("url"); - var minimatch = require("minimatch"); - - function BoxNode(n) { - RED.nodes.createNode(this,n); - } - RED.nodes.registerType("box-credentials", BoxNode, { - credentials: { - displayName: {type:"text"}, - clientId: {type:"text"}, - clientSecret: {type:"password"}, - accessToken: {type:"password"}, - refreshToken: {type:"password"}, - expireTime: {type:"password"} - } - }); - - BoxNode.prototype.refreshToken = function(cb) { - var credentials = this.credentials; - var node = this; - //console.log("refreshing token: " + credentials.refreshToken); - if (!credentials.refreshToken) { - // TODO: add a timeout to make sure we make a request - // every so often (if no flows trigger one) to ensure the - // refresh token does not expire - node.error(RED._("box.error.no-refresh-token")); - return cb(RED._("box.error.no-refresh-token")); - } - request.post({ - url: 'https://api.box.com/oauth2/token', - json: true, - form: { - grant_type: 'refresh_token', - client_id: credentials.clientId, - client_secret: credentials.clientSecret, - refresh_token: credentials.refreshToken, - }, - }, function(err, result, data) { - if (err) { - node.error(RED._("box.error.token-request-error",{err:err})); - return; - } - if (data.error) { - node.error(RED._("box.error.refresh-token-error",{message:data.error.message})); - return; - } - // console.log("refreshed: " + require('util').inspect(data)); - credentials.accessToken = data.access_token; - if (data.refresh_token) { - credentials.refreshToken = data.refresh_token; - } - credentials.expiresIn = data.expires_in; - credentials.expireTime = - data.expires_in + (new Date().getTime()/1000); - credentials.tokenType = data.token_type; - RED.nodes.addCredentials(node.id, credentials); - if (typeof cb !== undefined) { - cb(); - } - }); - }; - - BoxNode.prototype.request = function(req, retries, cb) { - var node = this; - if (typeof retries === 'function') { - cb = retries; - retries = 1; - } - if (typeof req !== 'object') { - req = { url: req }; - } - req.method = req.method || 'GET'; - if (!req.hasOwnProperty("json")) { - req.json = true; - } - // always set access token to the latest ignoring any already present - req.auth = { bearer: this.credentials.accessToken }; - if (!this.credentials.expireTime || - this.credentials.expireTime < (new Date().getTime()/1000)) { - if (retries === 0) { - node.error(RED._("box.error.too-many-refresh-attempts")); - cb(RED._("box.error.too-many-refresh-attempts")); - return; - } - node.warn(RED._("box.warn.refresh-token")); - node.refreshToken(function (err) { - if (err) { - return; - } - node.request(req, 0, cb); - }); - return; - } - return request(req, function(err, result, data) { - if (err) { - // handled in callback - return cb(err, data); - } - if (result.statusCode === 401 && retries > 0) { - retries--; - node.warn(RED._("box.warn.refresh-401")); - node.refreshToken(function (err) { - if (err) { - return cb(err, null); - } - return node.request(req, retries, cb); - }); - } - if (result.statusCode >= 400) { - return cb(result.statusCode + ": " + data.message, data); - } - return cb(err, data); - }); - }; - - BoxNode.prototype.folderInfo = function(parent_id, cb) { - this.request('https://api.box.com/2.0/folders/'+parent_id, cb); - }; - - BoxNode.prototype.resolvePath = function(path, parent_id, cb) { - var node = this; - if (typeof parent_id === 'function') { - cb = parent_id; - parent_id = 0; - } - if (typeof path === "string") { - // split path and remove empty string components - path = path.split("/").filter(function(e) { return e !== ""; }); - // TODO: could also handle '/blah/../' and '/./' perhaps - } else { - path = path.filter(function(e) { return e !== ""; }); - } - if (path.length === 0) { - return cb(null, parent_id); - } - var folder = path.shift(); - node.folderInfo(parent_id, function(err, data) { - if (err) { - return cb(err, -1); - } - var entries = data.item_collection.entries; - for (var i = 0; i < entries.length; i++) { - if (entries[i].type === 'folder' && - entries[i].name === folder) { - // found - return node.resolvePath(path, entries[i].id, cb); - } - } - return cb(RED._("box.error.not-found"), -1); - }); - }; - - BoxNode.prototype.resolveFile = function(path, parent_id, cb) { - var node = this; - if (typeof parent_id === 'function') { - cb = parent_id; - parent_id = 0; - } - if (typeof path === "string") { - // split path and remove empty string components - path = path.split("/").filter(function(e) { return e !== ""; }); - // TODO: could also handle '/blah/../' and '/./' perhaps - } else { - path = path.filter(function(e) { return e !== ""; }); - } - if (path.length === 0) { - return cb(RED._("box.error.missing-filename"), -1); - } - var file = path.pop(); - node.resolvePath(path, function(err, parent_id) { - if (err) { - return cb(err, parent_id); - } - node.folderInfo(parent_id, function(err, data) { - if (err) { - return cb(err, -1); - } - var entries = data.item_collection.entries; - for (var i = 0; i < entries.length; i++) { - if (entries[i].type === 'file' && - entries[i].name === file) { - // found - return cb(null, entries[i].id); - } - } - return cb(RED._("box.error.not-found"), -1); - }); - }); - }; - - function constructFullPath(entry) { - var parentPath = entry.path_collection.entries - .filter(function (e) { return e.id !== "0"; }) - .map(function (e) { return e.name; }) - .join('/'); - return (parentPath !== "" ? parentPath+'/' : "") + entry.name; - } - - RED.httpAdmin.get('/box-credentials/auth', function(req, res) { - if (!req.query.clientId || !req.query.clientSecret || - !req.query.id || !req.query.callback) { - res.send(400); - return; - } - var node_id = req.query.id; - var callback = req.query.callback; - var credentials = { - clientId: req.query.clientId, - clientSecret: req.query.clientSecret - }; - - var csrfToken = crypto.randomBytes(18).toString('base64').replace(/\//g, '-').replace(/\+/g, '_'); - credentials.csrfToken = csrfToken; - credentials.callback = callback; - res.cookie('csrf', csrfToken); - res.redirect(url.format({ - protocol: 'https', - hostname: 'app.box.com', - pathname: '/api/oauth2/authorize', - query: { - response_type: 'code', - client_id: credentials.clientId, - state: node_id + ":" + csrfToken, - redirect_uri: callback - } - })); - RED.nodes.addCredentials(node_id, credentials); - }); - - RED.httpAdmin.get('/box-credentials/auth/callback', function(req, res) { - if (req.query.error) { - return res.send('ERROR: '+ req.query.error + ': ' + req.query.error_description); - } - var state = req.query.state.split(':'); - var node_id = state[0]; - var credentials = RED.nodes.getCredentials(node_id); - if (!credentials || !credentials.clientId || !credentials.clientSecret) { - return res.send(RED._("box.error.no-credentials")); - } - if (state[1] !== credentials.csrfToken) { - return res.status(401).send( - RED._("box.error.token-mismatch") - ); - } - - request.post({ - url: 'https://app.box.com/api/oauth2/token', - json: true, - form: { - grant_type: 'authorization_code', - code: req.query.code, - client_id: credentials.clientId, - client_secret: credentials.clientSecret, - redirect_uri: credentials.callback, - }, - }, function(err, result, data) { - if (err) { - console.log("request error:" + err); - return res.send(RED._("box.error.something-broke")); - } - if (data.error) { - console.log("oauth error: " + data.error); - return res.send(RED._("box.error.something-broke")); - } - //console.log("data: " + require('util').inspect(data)); - credentials.accessToken = data.access_token; - credentials.refreshToken = data.refresh_token; - credentials.expiresIn = data.expires_in; - credentials.expireTime = data.expires_in + (new Date().getTime()/1000); - credentials.tokenType = data.token_type; - delete credentials.csrfToken; - delete credentials.callback; - RED.nodes.addCredentials(node_id, credentials); - request.get({ - url: 'https://api.box.com/2.0/users/me', - json: true, - auth: { bearer: credentials.accessToken }, - }, function(err, result, data) { - if (err) { - console.log('fetching box profile failed: ' + err); - return res.send(RED._("box.error.profile-fetch-failed")); - } - if (result.statusCode >= 400) { - console.log('fetching box profile failed: ' + - result.statusCode + ": " + data.message); - return res.send(RED._("box.error.profile-fetch-failed")); - } - if (!data.name) { - console.log('fetching box profile failed: no name found'); - return res.send(RED._("box.error.profile-fetch-failed")); - } - credentials.displayName = data.name; - RED.nodes.addCredentials(node_id, credentials); - res.send(RED._("box.error.authorized")); - }); - }); - }); - - function BoxInNode(n) { - RED.nodes.createNode(this,n); - this.filepattern = n.filepattern || ""; - this.box = RED.nodes.getNode(n.box); - var node = this; - if (!this.box || !this.box.credentials.accessToken) { - this.warn(RED._("box.warn.missing-credentials")); - return; - } - node.status({fill:"blue",shape:"dot",text:"box.status.initializing"}); - this.box.request({ - url: 'https://api.box.com/2.0/events?stream_position=now&stream_type=changes', - }, function (err, data) { - if (err) { - node.error(RED._("box.error.event-stream-initialize-failed",{err:err.toString()})); - node.status({fill:"red",shape:"ring",text:"box.status.failed"}); - return; - } - node.state = data.next_stream_position; - node.status({}); - node.on("input", function(msg) { - node.status({fill:"blue",shape:"dot",text:"box.status.checking-for-events"}); - node.box.request({ - url: 'https://api.box.com/2.0/events?stream_position='+node.state+'&stream_type=changes', - }, function(err, data) { - if (err) { - node.error(RED._("box.error.events-fetch-failed",{err:err.toString()}),msg); - node.status({}); - return; - } - node.status({}); - node.state = data.next_stream_position; - for (var i = 0; i < data.entries.length; i++) { - // TODO: support other event types - // TODO: suppress duplicate events - // for both of the above see: - // https://developers.box.com/docs/#events - var event; - if (data.entries[i].event_type === 'ITEM_CREATE') { - event = 'add'; - } else if (data.entries[i].event_type === 'ITEM_UPLOAD') { - event = 'add'; - } else if (data.entries[i].event_type === 'ITEM_RENAME') { - event = 'add'; - // TODO: emit delete event? - } else if (data.entries[i].event_type === 'ITEM_TRASH') { - // need to find old path - node.lookupOldPath({}, data.entries[i], 'delete'); - /* strictly speaking the {} argument above should - * be clone(msg) but: - * - it must be {} - * - if there was any possibility of a different - * msg then it should be cloned using the - * node-red/red/nodes/Node.js cloning function - */ - continue; - } else { - event = 'unknown'; - } - //console.log(JSON.stringify(data.entries[i], null, 2)); - node.sendEvent(msg, data.entries[i], event); - } - }); - }); - var interval = setInterval(function() { - node.emit("input", {}); - }, 600000); // 10 minutes - node.on("close", function() { - if (interval !== null) { clearInterval(interval); } - }); - }); - } - RED.nodes.registerType("box in", BoxInNode); - - BoxInNode.prototype.sendEvent = function(msg, entry, event, path) { - var source = entry.source; - if (typeof path === "undefined") { - path = constructFullPath(source); - } - if (this.filepattern && !minimatch(path, this.filepattern)) { - return; - } - msg.file = source.name; - msg.payload = path; - msg.event = event; - msg.data = entry; - this.send(msg); - }; - - BoxInNode.prototype.lookupOldPath = function (msg, entry, event) { - var source = entry.source; - this.status({fill:"blue",shape:"dot",text:"box.status.resolving-path"}); - var node = this; - node.box.folderInfo(source.parent.id, function(err, folder) { - if (err) { - node.warn(RED._("box.warn.old-path-failed",{err:err.toString()})); - node.status({fill:"red",shape:"ring",text:"box.status.failed"}); - return; - } - node.status({}); - // TODO: add folder path_collection to entry.parent? - var parentPath = constructFullPath(folder); - node.sendEvent(msg, entry, event, - (parentPath !== "" ? parentPath + '/' : '') + source.name); - }); - }; - - function BoxQueryNode(n) { - RED.nodes.createNode(this,n); - this.filename = n.filename || ""; - this.box = RED.nodes.getNode(n.box); - var node = this; - if (!this.box || !this.box.credentials.accessToken) { - this.warn(RED._("box.warn.missing-credentials")); - return; - } - - node.on("input", function(msg) { - var filename = node.filename || msg.filename; - if (filename === "") { - node.error(RED._("box.error.no-filename-specified")); - return; - } - msg.filename = filename; - node.status({fill:"blue",shape:"dot",text:"box.status.resolving-path"}); - node.box.resolveFile(filename, function(err, file_id) { - if (err) { - node.error(RED._("box.error.path-resolve-failed",{err:err.toString()}),msg); - node.status({fill:"red",shape:"ring",text:"box.status.failed"}); - return; - } - node.status({fill:"blue",shape:"dot",text:"box.status.downloading"}); - node.box.request({ - url: 'https://api.box.com/2.0/files/'+file_id+'/content', - json: false, - followRedirect: true, - maxRedirects: 1, - encoding: null, - }, function(err, data) { - if (err) { - node.error(RED._("box.error.download-failed",{err:err.toString()}),msg); - node.status({fill:"red",shape:"ring",text:"box.status.failed"}); - } else { - msg.payload = data; - delete msg.error; - node.status({}); - node.send(msg); - } - }); - }); - }); - } - RED.nodes.registerType("box", BoxQueryNode); - - function BoxOutNode(n) { - RED.nodes.createNode(this,n); - this.filename = n.filename || ""; - this.localFilename = n.localFilename || ""; - this.box = RED.nodes.getNode(n.box); - var node = this; - if (!this.box || !this.box.credentials.accessToken) { - this.warn(RED._("box.warn.missing-credentials")); - return; - } - - node.on("input", function(msg) { - var filename = node.filename || msg.filename; - if (filename === "") { - node.error(RED._("box.error.no-filename-specified")); - return; - } - var path = filename.split("/"); - var basename = path.pop(); - node.status({fill:"blue",shape:"dot",text:"box.status.resolving-path"}); - var localFilename = node.localFilename || msg.localFilename; - if (!localFilename && typeof msg.payload === "undefined") { - return; - } - node.box.resolvePath(path, function(err, parent_id) { - if (err) { - node.error(RED._("box.error.path-resolve-failed",{err:err.toString()}),msg); - node.status({fill:"red",shape:"ring",text:"box.status.failed"}); - return; - } - node.status({fill:"blue",shape:"dot",text:"box.status.uploading"}); - var r = node.box.request({ - method: 'POST', - url: 'https://upload.box.com/api/2.0/files/content', - }, function(err, data) { - if (err) { - if (data && data.status === 409 && - data.context_info && data.context_info.conflicts) { - // existing file, attempt to overwrite it - node.status({fill:"blue",shape:"dot",text:"box.status.overwriting"}); - var r = node.box.request({ - method: 'POST', - url: 'https://upload.box.com/api/2.0/files/'+ - data.context_info.conflicts.id+'/content', - }, function(err, data) { - if (err) { - node.error(RED._("box.error.upload-failed",{err:err.toString()}),msg); - node.status({fill:"red",shape:"ring",text:"box.status.failed"}); - return; - } - node.status({}); - }); - var form = r.form(); - if (localFilename) { - form.append('filename', fs.createReadStream(localFilename), { filename: basename }); - } else { - form.append('filename', RED.util.ensureBuffer(msg.payload), { filename: basename }); - } - } else { - node.error(RED._("box.error.upload-failed",{err:err.toString()}),msg); - node.status({fill:"red",shape:"ring",text:"box.status.failed"}); - } - return; - } - node.status({}); - }); - var form = r.form(); - if (localFilename) { - form.append('filename', fs.createReadStream(localFilename), { filename: basename }); - } else { - form.append('filename', RED.util.ensureBuffer(msg.payload), { filename: basename }); - } - form.append('parent_id', parent_id); - }); - }); - } - RED.nodes.registerType("box out",BoxOutNode); +'use strict'; + +const initAPINode = require('./lib/box-api'); +const initDownloadNode = require('./lib/box-download'); +const initEventNode = require('./lib/box-event'); +const initUploadNode = require('./lib/box-upload'); +const initItemsNode = require('./lib/box-items'); +const initFileInfoNode = require('./lib/box-set-file-info'); + +module.exports = RED => { + initAPINode(RED); + initDownloadNode(RED); + initEventNode(RED); + initUploadNode(RED); + initItemsNode(RED); + initFileInfoNode(RED); }; diff --git a/box/lib/box-api.js b/box/lib/box-api.js new file mode 100644 index 00000000..579fb9e3 --- /dev/null +++ b/box/lib/box-api.js @@ -0,0 +1,666 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +'use strict'; + +// note the confusing naming +const BoxSDKNode = require('box-node-sdk'); +const crypto = require('crypto'); +const url = require('url'); +const fs = require('fs'); +const path = require('path'); + +module.exports = RED => { + + /** + * Consumes a readable stream as a UTF-8 string; + * resolves Promise when stream ends. + * @param {ReadableStream} stream + * @returns Promise Stream data + */ + const streamToPromise = stream => { + let content = ''; + return new Promise((resolve, reject) => { + stream.on('data', chunk => { + content += chunk; + }) + .on('end', () => { + resolve(content); + }) + .on('error', err => { + reject(err); + }); + }); + }; + + const normalizeFilepath = filepath => + (typeof filepath === "string" ? filepath.split("/").filter(Boolean) : filepath.filter(Boolean)); + + /** + * Provides an adapter for a Box SDK persistent client to store its tokens + * in Node-RED. + * For our purposes, this is the entirety of the Node's `credentials` object. + * This class is *not* used directly by the Nodes. + */ + class TokenStore { + /** + * Assigns this TokenStore a Node ID + * @param {string} id Node ID to associate TokenStore with + */ + constructor(id) { + this.id = id; + } + + /** + * Reads the token store + * @param {Function} cb Nodeback + */ + read(cb) { + const id = this.id; + // this is only here to force the callback to be called async + process.nextTick(() => { + cb(null, RED.nodes.getCredentials(id)); + }); + } + + /** + * Writes "Token Info" to the token store + * @param {Object} tokenInfo "Token Info" object + * @param {Function} cb Nodeback + */ + write(tokenInfo, cb) { + const id = this.id; + const credentials = RED.nodes.getCredentials(id); + Object.assign(credentials, tokenInfo); + RED.nodes.addCredentials(id, credentials) + .then(() => cb(), cb); + } + + /** + * Annihilates the contents of the token store + * @param {Function} cb Nodeback + */ + clear(cb) { + this.write(null, cb); + } + } + + /** + * Collection of mixins for difft behavior of difft auth strategies. + */ + const AuthModeMixins = { + OAUTH2: { + /** + * `true` if the credentials are in place + * @this BoxAPINode + * @returns {boolean} Credentials OK + */ + _hasCredentials() { + const c = this.credentials; + return c.clientSecret && c.clientId && c.accessToken && + c.refreshToken && c.accessTokenTTLMS && c.acquiredAtMS; + }, + + /** + * A Box SDK client, created as per the auth mode. + * @returns {BoxClient} + */ + _client() { + if (this.__client) { + return this.__client; + } + return this.sdk.getPersistentClient({ + accessToken: this.credentials.accessToken, + refreshToken: this.credentials.refreshToken, + acquiredAtMS: this.credentials.acquiredAtMS, + accessTokenTTLMS: this.credentials.accessTokenTTLMS + }, this.tokenStore); + }, + + /** + * A Box SDK instance, created as per the auth mode. + * @returns {BoxSDKNode} + */ + _sdk() { + if (this.__sdk) { + return this.__sdk; + } + this.__sdk = new BoxSDKNode({ + clientID: this.credentials.clientId, + clientSecret: this.credentials.clientSecret + }); + return this.__sdk; + }, + + /** + * Gets an event stream + * @param {Object} [options={}] Options + * @param {number} [options.interval=0] Fetch interval, in seconds + * @returns {Promise} Readable stream + */ + eventStream(options) { + return this.client.events.getEventStream({ + // this is milliseconds. handy! + fetchInterval: (options.interval || 0) * 1000 + }); + } + }, + APP: { + /** + * `true` if the credentials are in place + * @this BoxAPINode + * @returns {boolean} Credentials OK + */ + _hasCredentials() { + const c = this.credentials; + return c.clientId && c.clientSecret && c.publicKeyId && c.privateKey && + c.passphrase && c.appEnterpriseId; + }, + /** + * A Box SDK client, created as per the app mode. + * If `appUserId` is present, return an "app user" client; otherwise + * an enterprise one. + * @returns {BoxClient} + */ + _client() { + if (this.__client) { + return this.__client; + } + + if (this.credentials.appUserId) { + this.__client = this.sdk.getAppAuthClient('user', this.credentials.appUserId); + this.debug(`Authenticating as app user ${this.credentials.appUserId}`); + } else { + this.__client = this.sdk.getAppAuthClient('enterprise', this.credentials.appEnterpriseId); + this.debug('Authenticating as service user'); + } + + return this.__client; + }, + + /** + * A Box SDK instance, created as per the app mode + * @returns {BoxSDKNode} + */ + _sdk() { + if (this.__sdk) { + return this.__sdk; + } + this.__sdk = new BoxSDKNode({ + clientID: this.credentials.clientId, + clientSecret: this.credentials.clientSecret, + appAuth: { + keyID: this.credentials.publicKeyId, + privateKey: this.credentials.privateKey, + passphrase: this.credentials.passphrase + } + }); + return this.__sdk; + }, + + /** + * Gets an enterprise event stream + * @param {Object} [options={}] Options + * @param {number} [options.interval=0] Polling interval, in seconds + * @returns {Promise} Readable stream + */ + eventStream(options) { + return this.credentials.appUserId ? AuthModeMixins.OAUTH2.eventStream.call(this, options) : + this.client.events.getEnterpriseEventStream({ + // this is seconds + pollingInterval: options.interval + }); + } + }, + DEV: { + /** + * `true` if the credentials are in place + * @this BoxAPINode + * @returns {boolean} Credentials OK + */ + _hasCredentials() { + const c = this.credentials; + return c.clientId && c.clientSecret && c.devToken; + }, + + /** + * @returns {BoxClient} + */ + _client() { + return this.sdk.getBasicClient(this.credentials.devToken); + }, + + /** + * @returns {BoxSDKNode} + */ + _sdk() { + return AuthModeMixins.OAUTH2._sdk.call(this); + }, + + eventStream() { + throw new Error('not implemented'); + } + } + }; + + /** + * Represents an interface into the Box API; contains credentials. + * Requires a mixin applied from `AuthModeMixins` to work properly; + * the `authMode` property determines this. + * @class BoxAPINode + */ + class BoxAPINode { + /** + * Creates an instance of BoxAPINode. + * @param {*} n + * @memberof BoxAPINode + */ + constructor(n) { + RED.nodes.createNode(this, n); + + this.authMode = n.authMode || 'OAUTH2'; + + if (!AuthModeMixins[this.authMode]) { + this.error('Invalid auth mode'); + return; + } + + Object.assign(this, AuthModeMixins[this.authMode]); + } + + /** + * The TokenStore associated with this Node + * @type {TokenStore} + * @memberof BoxAPINode + */ + get tokenStore() { + const tokenStore = this._tokenStore; + if (tokenStore) { + return tokenStore; + } + this._tokenStore = new TokenStore(this.id); + return this.tokenStore; + } + + /** + * @type {BoxSDKNode} + * @memberof BoxAPINode + */ + get sdk() { + return this._sdk(); + } + + /** + * @type {BoxClient} + * @memberof BoxAPINode + */ + get client() { + return this._client(); + } + + /** + * @type {boolean} + * @memberof BoxAPINode + */ + get hasCredentials() { + return this._hasCredentials(); + } + + /** + * Returns the ID of a folder in Box + * @param {string} folderpath A filepath + * @param {string} [folderId=0] Parent folder ID; defaults to root + * @returns {Promise} A folder ID + */ + resolveFolder(folderpath, folderId) { + return Promise.resolve() + .then(() => { + folderId = folderId || '0'; + folderpath = normalizeFilepath(folderpath); + if (!folderpath.length) { + return folderId; + } + const folder = folderpath.shift(); + return this.client.folders.getItems(folderId) + .then(data => { + const entries = data.entries; + for (let i = 0; i < entries.length; i++) { + if (entries[i].type === 'folder' && + entries[i].name === folder) { + // found + return this.resolveFolder(folderpath, entries[i].id); + } + } + return Promise.reject(RED._("box.error.not-found")); + }); + }); + } + + /** + * Attaches a listener function to an event stream + * @param {Function} listener Listener function; receives event object + * @param {Object} [options={}] Options + * @param {number} [options.interval=0] Polling or fetch interval, in seconds + * @returns {Promise} An "unsubscribe" function + */ + subscribe(listener, options) { + options = options || {}; + const errListener = err => { + this.error(RED._('box.error.event-fetch-failed', { + err: err.toString() + })); + }; + return this.eventStream(options) + .then(stream => { + stream.on('error', errListener) + .on('data', listener); + + this.on('close', () => { + stream.removeAllListeners('data') + stream.removeAllListeners('error'); + stream.destroy(); + }); + + return () => { + stream.removeListener('data', listener); + stream.removeListener('error', errListener); + }; + }); + } + + /** + * Finds a file's ID by filename + * @param {string} filename A filename + * @returns {Promise} File ID, if found + */ + resolveFile(filename) { + return Promise.resolve() + .then(() => { + const filepath = normalizeFilepath(filename); + if (!filepath.length) { + return Promise.reject(RED._("box.error.missing-filename")); + } + const file = filepath.pop(); + return this.resolveFolder(path.dirname(filename)) + .then(id => this.client.folders.getItems(id)) + .then(data => { + const entries = data.entries; + for (var i = 0; i < entries.length; i++) { + if (entries[i].type === 'file' && + entries[i].name === file) { + // found + return entries[i].id; + } + } + return Promise.reject(RED._("box.error.not-found")); + }); + }); + } + + /** + * Downloads a file, optionally coerced to a representation type. + * @param {string} filepath A filepath + * @param {FileRepresentationType} [representation] File representation type + * @returns {Promise} A Buffer or string of the file contents (or representation thereof) + */ + download(filepath, representation) { + return Promise.resolve() + .then(() => { + if (representation && this.client.files.representation[representation]) { + return this.client.files.getRepresentationContent( + filepath, this.client.files.representation[representation] + ); + } + // "raw" + return this.client.files.getReadStream(filepath); + }) + .then(streamToPromise); + } + + /** + * Uploads a new version of a file, performing a preflight check to ensure uploading will *likely* work. + * One of `opts.localFilename` or `opts.content` is required. + * @param {Object} opts Options + * @param {string} opts.fileId Box File ID + * @param {number} opts.size File size (bytes) + * @param {string} [opts.localFilename] Path to local (server) filename + * @param {string|Buffer} [opts.content] File content + * @private + * @returns Promise New version info + */ + uploadNewFileVersion(opts) { + return this.client.files.preflightUploadNewFileVersion(opts.fileId, { + size: opts.size + }) + .then(() => { + return this.client.files.uploadNewFileVersion( + opts.fileId, + (opts.localFilename ? fs.createReadStream(opts.localFilename) : opts.content) + ); + }); + + } + + /** + * Uploads a new file, performing a preflight check to ensure uploading will *likely* work. + * One of `opts.localFilename` or `opts.content` is required. + * @param {Object} opts Options + * @param {number} opts.size File size (bytes) + * @param {string} opts.filename Filepath (on Box) + * @param {string} [opts.localFilename] Path to local (server) filename + * @param {string|Buffer} [opts.content] File content + * @private + * @returns Promise New file info + */ + uploadNewFile(opts) { + return Promise.resolve() + .then(() => { + const basename = path.basename(opts.filename); + return this.resolveFolder(path.dirname(opts.filename)) + .then(folderId => this.client.files.preflightUploadFile( + folderId, { + name: basename, + size: opts.size + } + ) + .then(() => this.client.files.uploadFile( + folderId, + basename, + (opts.localFilename ? fs.createReadStream(opts.localFilename) : opts.content) + )) + ); + }); + } + + /** + * Upload a file. If exists, it will be overwritten with a new version. + * @param {string} filename Box complete filepath + * @param {string} [localFilename] Path to local (server) file, if any + * @param {string|Buffer} [content] File content (possibly received via msg) + * @public + */ + upload(filename, localFilename, content) { + content = content || ''; + localFilename = localFilename || ''; + return Promise.resolve() + .then(() => { + // to perform preflight checks, we need to determine the size + // of the file to be uploaded. if it's a local file, + // we can call `fs.stat`; otherwise we just grab the byte length + return new Promise((resolve, reject) => { + if (localFilename) { + return fs.stat(localFilename, (err, stats) => { + if (err) { + return reject(err); + } + resolve(stats); + }); + } + resolve({ + size: Buffer.byteLength(content) + }); + }) + // if the file exists, upload a new version, otherwise + // just a new file. + .then(stats => this.resolveFile(filename) + .then(fileId => this.uploadNewFileVersion({ + fileId: fileId, + size: stats.size, + localFilename: localFilename, + content: content + }) + .catch(err => Promise.reject(RED._('box.error.upload-new-version-failed', { + err: err.toString() + }))), () => { + return this.uploadNewFile({ + filename: filename, + size: stats.size, + localFilename: localFilename, + content: content + }).catch(err => Promise.reject(RED._('box.error.upload-failed', { + err: err.toString() + })));})) + }); + } + } + + RED.nodes.registerType("box-credentials", BoxAPINode, { + credentials: { + displayName: { + type: "text" + }, + clientId: { + type: "text" + }, + clientSecret: { + type: "password" + }, + accessToken: { + type: "password" + }, + refreshToken: { + type: "password" + }, + accessTokenTTLMS: { + type: "text" + }, + acquiredAtMS: { + type: 'text' + }, + publicKeyId: { + type: 'text' + }, + privateKey: { + type: 'password' + }, + passphrase: { + type: 'password' + }, + appEnterpriseId: { + type: 'text' + }, + appUserId: { + type: 'text' + } + } + }); + + RED.httpAdmin.get('/box-credentials/auth', (req, res) => { + if (!req.query.clientId || !req.query.clientSecret || + !req.query.id || !req.query.callback) { + res.send(400); + return; + } + const nodeId = req.query.id; + const callback = req.query.callback; + const credentials = { + clientId: req.query.clientId, + clientSecret: req.query.clientSecret + }; + + const csrfToken = crypto.randomBytes(18) + .toString('base64') + .replace(/\//g, '-') + .replace(/\+/g, '_'); + credentials.csrfToken = csrfToken; + credentials.callback = callback; + res.cookie('csrf', csrfToken); + res.redirect(url.format({ + protocol: 'https', + hostname: 'app.box.com', + pathname: '/api/oauth2/authorize', + query: { + response_type: 'code', + client_id: credentials.clientId, + state: nodeId + ":" + csrfToken, + redirect_uri: callback + } + })); + RED.nodes.addCredentials(nodeId, credentials); + }); + + RED.httpAdmin.get('/box-credentials/auth/callback', (req, res) => { + if (req.query.error) { + return res.send('ERROR: ' + req.query.error + ': ' + req.query.error_description); + } + const state = req.query.state.split(':'); + const nodeId = state[0]; + const credentials = RED.nodes.getCredentials(nodeId); + if (!credentials || !credentials.clientId || !credentials.clientSecret) { + return res.send(RED._("box.error.no-credentials")); + } + if (state[1] !== credentials.csrfToken) { + return res.status(401).send( + RED._("box.error.token-mismatch") + ); + } + + const sdk = new BoxSDKNode({ + clientID: credentials.clientId, + clientSecret: credentials.clientSecret + }); + + sdk.getTokensAuthorizationCodeGrant(req.query.code) + .then(tokenInfo => { + const tokenStore = new TokenStore(nodeId); + return new Promise((resolve, reject) => { + tokenStore.write(tokenInfo, err => { + if (err) { + return reject(err); + } + resolve({ + tokenStore: tokenStore, + tokenInfo: tokenInfo + }); + }); + }); + }) + .then(data => { + const tokenInfo = data.tokenInfo; + const tokenStore = data.tokenStore; + const client = sdk.getPersistentClient(tokenInfo, tokenStore); + return client.users.get(client.CURRENT_USER_ID); + }) + .then(user => { + credentials.displayName = user.name; + RED.nodes.addCredentials(nodeId, credentials); + res.send(RED._("box.error.authorized")); + }) + .catch(err => { + res.status(500).send(err.toString()); + }); + }); +}; diff --git a/box/lib/box-download.js b/box/lib/box-download.js new file mode 100644 index 00000000..a7d880be --- /dev/null +++ b/box/lib/box-download.js @@ -0,0 +1,98 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +'use strict'; + +module.exports = RED => { + + const DOWNLOAD_AS_RAW = 'RAW'; + + class BoxDownloadNode { + constructor(n) { + RED.nodes.createNode(this, n); + + this.filename = n.filename; + this.downloadAs = n.downloadAs || DOWNLOAD_AS_RAW; + this.fileId = n.fileId; + + /** + * @type {BoxAPINode} + */ + this.box = RED.nodes.getNode(n.box); + + if (!this.box || !this.box.hasCredentials) { + this.warn(RED._("box.warn.missing-credentials")); + return; + } + + this.on("input", msg => { + const filename = this.filename || msg.filename; + const downloadAs = this.downloadAs || msg.downloadAs; + const fileId = this.fileId || msg.fileId; + + if (!filename && !fileId) { + this.error(RED._("box.error.no-filename-specified")); + return; + } + msg.filename = filename; + msg.downloadAs = downloadAs; + msg.fileId = fileId; + + return Promise.resolve() + .then(() => { + if (!fileId) { + this.status({ + fill: "blue", + shape: "dot", + text: "box.status.resolving-path" + }); + return this.box.resolveFile(filename) + .catch(err => Promise.reject(RED._('box.error.path-resolve-failed', { + err: err.toString() + }))); + } + return fileId; + }) + .then(fileId => { + this.status({ + fill: "blue", + shape: "dot", + text: "box.status.downloading" + }); + return this.box.download(fileId, downloadAs) + .catch(err => Promise.reject(RED._('box.error.download-failed', { + err: err.toString() + }))); + }) + .then(content => { + msg.payload = content; + delete msg.error; + this.status({}); + this.send(msg); + }) + .catch(err => { + this.error(err, msg); + this.status({ + fill: "red", + shape: "ring", + text: "box.status.failed" + }); + }); + }); + } + } + RED.nodes.registerType("box", BoxDownloadNode); +} diff --git a/box/lib/box-event.js b/box/lib/box-event.js new file mode 100644 index 00000000..7a7ae7af --- /dev/null +++ b/box/lib/box-event.js @@ -0,0 +1,121 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +'use strict'; + +const minimatch = require('minimatch'); + +module.exports = RED => { + + const constructFullPath = entry => { + if (entry.path_collection) { + const parentPath = entry.path_collection.entries + .filter(e => e.id !== "0") + .map(e => e.name) + .join('/'); + return (parentPath ? `${parentPath}/` : "") + entry.name; + } + return entry.name; + } + + class BoxEventNode { + constructor(n) { + RED.nodes.createNode(this, n); + this.filepattern = n.filepattern || ""; + this.interval = n.interval || ""; + /** + * @type {BoxAPINode} + **/ + this.box = RED.nodes.getNode(n.box); + + if (!this.box || !this.box.hasCredentials) { + this.warn(RED._("box.warn.missing-credentials")); + return; + } + + this.status({ + fill: "blue", + shape: "dot", + text: "box.status.initializing" + }); + + this.box.subscribe(event => { + // if there's a "source" property, we can filter + if (event.source) { + event.fullPath = constructFullPath(event.source); + if (this.filepattern && !minimatch(event.fullPath, this.filepattern)) { + this.debug(RED._('box.debug.filtered'), { + fullPath: event.fullPath, + filepattern: this.filepattern + }); + return; + } + } + this.send({ + payload: event + }); + }, { + interval: this.interval + }) + .then(unsubscribe => { + this.status({ + fill: 'green', + shape: 'circle', + text: 'box.status.listening' + }); + this.on('close', unsubscribe); + }) + .catch(err => { + this.error(RED._('box.error.event-stream-initialize-failed', { + err: err.toString() + })); + }); + } + + lookupOldPath(msg, entry, event) { + return Promise.resolve() + .then(() => { + const source = entry.source; + this.status({ + fill: "blue", + shape: "dot", + text: "box.status.resolving-path" + }); + return this.box.folderInfo(source.parent.id); + }) + .then(folder => { + this.status({}); + const parentPath = constructFullPath(folder); + this.sendEvent(msg, entry, event, (parentPath ? `${parentPath}/` : '') + source.name); + }) + .catch(err => { + this.warn(RED._( + "box.warn.old-path-failed", { + err: err.toString() + } + )); + this.status({ + fill: "red", + shape: "ring", + text: "box.status.failed" + }); + }) + // TODO: add folder path_collection to entry.parent? + } + } + RED.nodes.registerType("box in", BoxEventNode); + +}; diff --git a/box/lib/box-items.js b/box/lib/box-items.js new file mode 100644 index 00000000..471735aa --- /dev/null +++ b/box/lib/box-items.js @@ -0,0 +1,81 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +'use strict'; + +module.exports = RED => { + + const DEFAULT_FOLDER_ID = 0; + const DEFAULT_LIMIT = 25; + const DEFAULT_OFFSET = 0; + + class BoxItemsNode { + constructor(n) { + RED.nodes.createNode(this, n); + this.folderId = n.folderId || DEFAULT_FOLDER_ID; + this.limit = n.limit || DEFAULT_LIMIT; + this.offset = n.offset || DEFAULT_OFFSET; + + /** + * @type {BoxAPINode} + */ + this.box = RED.nodes.getNode(n.box); + if (!this.box || !this.box.hasCredentials) { + this.warn(RED._('box.warn.missing-credentials')); + return; + } + + this.on('input', msg => { + const folderId = this.folderId || msg.folderId || DEFAULT_FOLDER_ID; + const limit = this.limit || msg.limit || DEFAULT_LIMIT; + const offset = this.offset || msg.offset || DEFAULT_OFFSET; + + msg.folderId = folderId; + msg.limit = limit; + msg.offset = offset; + + this.status({ + fill: 'blue', + shape: 'dot', + text: 'box.status.listing' + }); + + this.box.client.folders.getItems(folderId, { + limit: parseInt(limit, 10), + offset: parseInt(offset, 10) + }) + .then(result => { + msg.payload = result; + delete msg.error; + this.status({}); + this.send(msg); + }) + .catch(err => { + this.error(RED._('box.error.listing-failed', { + err: err.toString() + }), msg); + this.status({ + fill: 'red', + shape: 'ring', + text: 'box.status.failed' + }); + }); + }); + } + } + + RED.nodes.registerType('box-items', BoxItemsNode); +} diff --git a/box/lib/box-set-file-info.js b/box/lib/box-set-file-info.js new file mode 100644 index 00000000..5fb69264 --- /dev/null +++ b/box/lib/box-set-file-info.js @@ -0,0 +1,88 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +'use strict'; + +module.exports = RED => { + + class BoxSetFileInfoNode { + /** + * Creates an instance of BoxAddMetadataNode. + * @param {*} n + * @memberof BoxAddMetadataNode + */ + constructor(n) { + RED.nodes.createNode(this, n); + + /** + * @type {string|number} + */ + this.fileId = n.fileId; + + /** + * @type {BoxAPINode} + */ + this.box = RED.nodes.getNode(n.box); + + if (!this.box || !this.box.hasCredentials) { + this.warn(RED._("box.warn.missing-credentials")); + return; + } + + this.on("input", msg => { + const fileId = this.fileId || msg.fileId; + const data = this.data || msg.data || msg.payload || {}; + + if (!fileId) { + this.error(RED._("box.error.no-filename-specified")); + return; + } + + msg.fileId = fileId; + msg.payload = msg.data = data; + + this.status({ + fill: "blue", + shape: "dot", + text: "box.status.updating-file" + }); + + this.box.client.files.update( + fileId, + data + ) + .catch(err => Promise.reject(RED._('box.error.update-file-failed', { + err: err.toString() + }))) + .then(newMetadata => { + msg.payload = newMetadata; + delete msg.error; + this.status({}); + this.send(msg); + }) + .catch(err => { + this.error(err, msg); + this.status({ + fill: "red", + shape: "ring", + text: "box.status.failed" + }); + }); + }); + } + } + RED.nodes.registerType("box-set-file-info", BoxSetFileInfoNode); +} diff --git a/box/lib/box-upload.js b/box/lib/box-upload.js new file mode 100644 index 00000000..be509026 --- /dev/null +++ b/box/lib/box-upload.js @@ -0,0 +1,66 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + + 'use strict'; + +const fs = require('fs'); +const path = require('path'); + +module.exports = RED => { + + function BoxOutNode(n) { + RED.nodes.createNode(this, n); + this.filename = n.filename || ""; + this.localFilename = n.localFilename || ""; + this.box = RED.nodes.getNode(n.box); + var node = this; + if (!this.box || !this.box.hasCredentials) { + this.warn(RED._("box.warn.missing-credentials")); + return; + } + + node.on("input", function(msg) { + var filename = node.filename || msg.filename; + if (filename === "") { + node.error(RED._("box.error.no-filename-specified")); + return; + } + node.status({ + fill: "blue", + shape: "dot", + text: "box.status.resolving-path" + }); + const localFilename = node.localFilename || msg.localFilename; + if (!localFilename && typeof msg.payload === "undefined") { + return; + } + return this.box.upload(filename, localFilename, msg.payload) + .catch(err => { + node.status({ + fill: "red", + shape: "ring", + text: "box.status.failed" + }); + node.error(err, msg); + }) + .then(res => { + node.status({}); + node.send(Object.assign(msg, {payload: res})) + }); + }); + } + RED.nodes.registerType("box out", BoxOutNode); +}; diff --git a/box/locales/en-US/box.json b/box/locales/en-US/box.json index d65ab4a5..27641af2 100644 --- a/box/locales/en-US/box.json +++ b/box/locales/en-US/box.json @@ -5,19 +5,40 @@ "pattern": "Filename Pattern", "name": "Name", "filename": "Filename", + "fileId": "File ID", "local": "Local Filename", "create": "Create your own app at", "copy": "Copy the project credentials here", - "clientid": "Client Id", + "clientid": "Client ID", "secret": "Secret", "authenticate": "Authenticate with Box", - "boxuser": "Box User" + "boxuser": "Box User", + "interval": "Polling Interval (seconds)", + "folderId": "Folder ID", + "limit": "Limit", + "offset": "Offset", + "data": "File Data (JSON)", + "authMode": "Auth Mode", + "appEnterpriseId": "App Enterprise ID", + "appUserId": "App User ID", + "publicKeyId": "Public Key ID", + "privateKey": "Private Key", + "passphrase": "Passphrase" }, "placeholder": { "pattern": "Filepattern", "name": "Name", "filename": "Filename", - "local": "Local Filename" + "local": "Local Filename", + "folderId": "0", + "limit": "25", + "offset": "0", + "fileId": "000000000", + "privateKey": "(paste private key)", + "publicKeyId": "(8 chars)", + "appUserId": "(10 chars)", + "appEnterpriseId": "(8 chars)", + "clientId": "(32 chars)" }, "tip": { "redirect": "

Please configure the authorized Redirect URIs of your app to include the following url:

\n__callback__" @@ -30,12 +51,16 @@ "resolving-path": "resolving path", "downloading": "downloading", "uploading": "uploading", - "overwriting": "overwriting" + "overwriting": "overwriting", + "listening": "listening", + "new-event": "new event", + "listing": "listing", + "updating-file": "updating file info" }, "warn": { "refresh-token": "trying to refresh token due to expiry", "refresh-401": "refreshing access token after 401 error", - "missing-credentials": "Missing box credentials", + "missing-credentials": "missing credentials", "old-path-failed": "failed to resolve old path: __err__" }, "error": { @@ -51,11 +76,21 @@ "too-many-refresh-attempts": "too many refresh attempts, giving up", "something-broke": "Something went wrong in the authentication process. Please try again.", "event-stream-initialize-failed": "failed to initialize event stream: __err__", - "events-fetch-failed": "failed to fetch events: __err__", - "no-filename-specified": "No filename specified", + "event-fetch-failed": "failed to fetch event: __err__", + "no-filename-specified": "No filename or file ID specified", "path-resolve-failed": "failed to resolve path: __err__", "download-failed": "download failed: __err__", - "upload-failed": "failed upload: __err__" + "upload-failed": "failed upload: __err__", + "upload-new-version-failed": "failed upload of new version: __err__", + "listing-failed": "failed listing folder: __err__", + "update-file-failed": "updating file data failed: __err__" + }, + "debug": { + "filtered": "skipping file __fullPath__; does not match filter __filepattern__" + }, + "option": { + "enterprise": "Enterprise", + "global": "Global" } } } diff --git a/box/package-lock.json b/box/package-lock.json new file mode 100644 index 00000000..1f115275 --- /dev/null +++ b/box/package-lock.json @@ -0,0 +1,667 @@ +{ + "name": "node-red-node-box", + "version": "0.1.3", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bluebird": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", + "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==", + "dev": true + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "4.x.x" + } + }, + "box-node-sdk": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/box-node-sdk/-/box-node-sdk-1.21.0.tgz", + "integrity": "sha512-i7+35aGMMbCNi64kqEIXCeaybMec61i9DUOd0J4+tIpLt7bnIjE1wR4kSynclu8L7V0ctPyi70kTx3qOCtEuaQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "http-status": "^1.1.0", + "jsonwebtoken": "^8.2.1", + "merge-options": "^1.0.1", + "promise-queue": "^2.2.3", + "request": "^2.87.0", + "url-template": "^2.0.8", + "uuid": "^3.0.0" + }, + "dependencies": { + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "har-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "dev": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "mime-db": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", + "dev": true + }, + "mime-types": { + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "dev": true, + "requires": { + "mime-db": "~1.36.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + } + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "requires": { + "boom": "5.x.x" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.x.x" + } + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "~0.1.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "^5.1.0", + "har-schema": "^2.0.0" + } + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "requires": { + "boom": "4.x.x", + "cryptiles": "3.x.x", + "hoek": "4.x.x", + "sntp": "2.x.x" + } + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "http-status": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-status/-/http-status-1.2.0.tgz", + "integrity": "sha512-cSX+i/g4Kj5lkfOqS9w0SrxQF4hX7gsfikBtSDm5PFrAy+8fjRKk9+JRCG5cEZ40b6q6GOJk3P0QyDB5JpE0Ig==", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonwebtoken": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", + "integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==", + "dev": true, + "requires": { + "jws": "^3.1.5", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jwa": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "dev": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.10", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", + "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "dev": true, + "requires": { + "jwa": "^1.1.5", + "safe-buffer": "^5.0.1" + } + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=", + "dev": true + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=", + "dev": true + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=", + "dev": true + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, + "merge-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-1.0.1.tgz", + "integrity": "sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==", + "dev": true, + "requires": { + "is-plain-obj": "^1.1" + } + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "~1.33.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "promise-queue": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz", + "integrity": "sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=", + "dev": true + }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "request": { + "version": "2.85.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", + "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "hawk": "~6.0.2", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "stringstream": "~0.0.5", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "requires": { + "hoek": "4.x.x" + } + }, + "sshpk": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "requires": { + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=", + "dev": true + }, + "uuid": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.0.tgz", + "integrity": "sha512-ijO9N2xY/YaOqQ5yz5c4sy2ZjWmA6AR6zASb/gdpeKZ8+948CxwfMW9RrKVk5may6ev8c0/Xguu32e2Llelpqw==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + } + } +} diff --git a/box/package.json b/box/package.json index 95f01d1d..4c999da6 100644 --- a/box/package.json +++ b/box/package.json @@ -1,19 +1,23 @@ { - "name" : "node-red-node-box", - "version" : "0.1.3", - "description" : "Node-RED nodes to watch, download and save files to Box", - "dependencies" : { + "name": "node-red-node-box", + "version": "0.1.3", + "description": "Node-RED nodes to watch, download and save files to Box", + "dependencies": { "minimatch": "^3.0.4", - "request":"~2.85.0" + "request": "~2.85.0" }, - "repository" : { - "type":"git", - "url":"https://github.com/node-red/node-red-web-nodes/tree/master/box" + "repository": { + "type": "git", + "url": "https://github.com/node-red/node-red-web-nodes/tree/master/box" }, "license": "Apache-2.0", - "keywords": [ "node-red", "box", "bookmark" ], - "node-red" : { - "nodes" : { + "keywords": [ + "node-red", + "box", + "bookmark" + ], + "node-red": { + "nodes": { "box": "box.js" } }, @@ -21,5 +25,8 @@ "name": "Mark Hindess", "email": "HINDESM@uk.ibm.com", "url": "http://nodered.org" + }, + "devDependencies": { + "box-node-sdk": "^1.21.0" } } diff --git a/package-lock.json b/package-lock.json index b9039523..80dceb17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -177,13 +177,10 @@ "dev": true }, "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true }, "assert-plus": { "version": "1.0.0", @@ -244,12 +241,6 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", "dev": true }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true - }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -316,9 +307,9 @@ } }, "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", "dev": true, "optional": true, "requires": { @@ -376,6 +367,12 @@ "inherits": "~2.0.0" } }, + "bluebird": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", + "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==", + "dev": true + }, "body-parser": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", @@ -408,6 +405,22 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "box-node-sdk": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/box-node-sdk/-/box-node-sdk-1.21.0.tgz", + "integrity": "sha512-i7+35aGMMbCNi64kqEIXCeaybMec61i9DUOd0J4+tIpLt7bnIjE1wR4kSynclu8L7V0ctPyi70kTx3qOCtEuaQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "http-status": "^1.1.0", + "jsonwebtoken": "^8.2.1", + "merge-options": "^1.0.1", + "promise-queue": "^2.2.3", + "request": "^2.87.0", + "url-template": "^2.0.8", + "uuid": "^3.0.0" + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -435,6 +448,12 @@ "isarray": "^1.0.0" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", + "dev": true + }, "buffer-from": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", @@ -1218,14 +1237,22 @@ } }, "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "jsbn": "~0.1.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" } }, "ee-first": { @@ -1439,6 +1466,19 @@ "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", "dev": true }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "dev": true, + "optional": true, + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -1469,6 +1509,16 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "optional": true, + "requires": { + "pend": "~1.2.0" + } + }, "feedparser": { "version": "2.2.9", "resolved": "https://registry.npmjs.org/feedparser/-/feedparser-2.2.9.tgz", @@ -2044,9 +2094,9 @@ } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -2107,16 +2157,6 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", "dev": true }, - "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", - "dev": true, - "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" - } - }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -2151,6 +2191,17 @@ "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=", "dev": true }, + "hasha": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", + "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", + "dev": true, + "optional": true, + "requires": { + "is-stream": "^1.0.1", + "pinkie-promise": "^2.0.0" + } + }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", @@ -2246,6 +2297,12 @@ "sshpk": "^1.7.0" } }, + "http-status": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-status/-/http-status-1.2.0.tgz", + "integrity": "sha512-cSX+i/g4Kj5lkfOqS9w0SrxQF4hX7gsfikBtSDm5PFrAy+8fjRKk9+JRCG5cEZ40b6q6GOJk3P0QyDB5JpE0Ig==", + "dev": true + }, "https-proxy-agent": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", @@ -2447,6 +2504,12 @@ "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", "dev": true }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -2456,6 +2519,13 @@ "is-unc-path": "^1.0.0" } }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "optional": true + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -2633,36 +2703,22 @@ } }, "jshint": { - "version": "2.9.5", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.5.tgz", - "integrity": "sha1-HnJSkVzmgbQIJ+4UJIxG006apiw=", + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.6.tgz", + "integrity": "sha512-KO9SIAKTlJQOM4lE64GQUtGBRpTOuvbrRrSZw3AhUxMNG266nX9hK2cKA4SBhXOj0irJGyNyGSLT62HGOVDEOA==", "dev": true, "requires": { "cli": "~1.0.0", "console-browserify": "1.1.x", "exit": "0.1.x", "htmlparser2": "3.8.x", - "lodash": "3.7.x", + "lodash": "~4.17.10", "minimatch": "~3.0.2", + "phantom": "~4.0.1", + "phantomjs-prebuilt": "~2.1.7", "shelljs": "0.3.x", - "strip-json-comments": "1.0.x" - }, - "dependencies": { - "lodash": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.7.0.tgz", - "integrity": "sha1-Nni9irmVBXwHreg27S7wh9qBHUU=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } + "strip-json-comments": "1.0.x", + "unicode-5.2.0": "^0.7.5" } }, "json-parse-better-errors": { @@ -2735,6 +2791,31 @@ "nomnom": "^1.5.x" } }, + "jsonwebtoken": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", + "integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==", + "dev": true, + "requires": { + "jws": "^3.1.5", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -2748,17 +2829,55 @@ } }, "just-extend": { - "version": "1.1.27", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", - "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-3.0.0.tgz", + "integrity": "sha512-Fu3T6pKBuxjWT/p4DkqGHFRsysc8OauWr4ZRTY9dIx07Y9O0RkoR5jcv28aeD1vuAwhm3nLkDurwLXoALp4DpQ==", "dev": true }, + "jwa": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "dev": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.10", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", + "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "dev": true, + "requires": { + "jwa": "^1.1.5", + "safe-buffer": "^5.0.1" + } + }, + "kew": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", + "dev": true, + "optional": true + }, "keygrip": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.2.tgz", "integrity": "sha1-rTKXxVcGneqLz+ek+kkbdcXd65E=", "dev": true }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, "leven": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/leven/-/leven-1.0.2.tgz", @@ -2889,6 +3008,42 @@ "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=", "dev": true }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=", + "dev": true + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=", + "dev": true + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=", + "dev": true + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -2901,6 +3056,12 @@ "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, "lodash.pick": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", @@ -3044,6 +3205,15 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, + "merge-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-1.0.1.tgz", + "integrity": "sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==", + "dev": true, + "requires": { + "is-plain-obj": "^1.1" + } + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3198,13 +3368,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -3213,7 +3383,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -3314,13 +3484,13 @@ "dev": true }, "nise": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.3.tgz", - "integrity": "sha512-cg44dkGHutAY+VmftgB1gHvLWxFl2vwYdF8WpbceYicQwylESRJiAAKgCRJntdoEbMiUzywkZEUzjoDWH0JwKA==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.4.tgz", + "integrity": "sha512-pxE0c9PzgrUTyhfv5p+5eMIdfU2bLEsq8VQEuE0kxM4zP7SujSar7rk9wpI2F7RyyCEvLyj5O7Is3RER5F36Fg==", "dev": true, "requires": { "@sinonjs/formatio": "^2.0.0", - "just-extend": "^1.1.27", + "just-extend": "^3.0.0", "lolex": "^2.3.2", "path-to-regexp": "^1.7.0", "text-encoding": "^0.6.4" @@ -3477,9 +3647,9 @@ "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "js-yaml": { @@ -3498,8 +3668,8 @@ "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" + "abbrev": "1", + "osenv": "^0.1.4" } }, "semver": { @@ -3817,12 +3987,6 @@ "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE=", "dev": true }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, "oauth2orize": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/oauth2orize/-/oauth2orize-1.11.0.tgz", @@ -4128,12 +4292,104 @@ "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=", "dev": true }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true, + "optional": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "phantom": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/phantom/-/phantom-4.0.12.tgz", + "integrity": "sha512-Tz82XhtPmwCk1FFPmecy7yRGZG2btpzY2KI9fcoPT7zT9det0CcMyfBFPp1S8DqzsnQnm8ZYEfdy528mwVtksA==", + "dev": true, + "optional": true, + "requires": { + "phantomjs-prebuilt": "^2.1.16", + "split": "^1.0.1", + "winston": "^2.4.0" + }, + "dependencies": { + "async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", + "dev": true, + "optional": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true, + "optional": true + }, + "winston": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.4.tgz", + "integrity": "sha512-NBo2Pepn4hK4V01UfcWcDlmiVTs7VTB1h7bgnB0rgP146bYhMxX0ypCz3lBOfNxCO4Zuek7yeT+y/zM1OfMw4Q==", + "dev": true, + "optional": true, + "requires": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + } + } + } + }, + "phantomjs-prebuilt": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", + "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", + "dev": true, + "optional": true, + "requires": { + "es6-promise": "^4.0.3", + "extract-zip": "^1.6.5", + "fs-extra": "^1.0.0", + "hasha": "^2.2.0", + "kew": "^0.7.0", + "progress": "^1.1.8", + "request": "^2.81.0", + "request-progress": "^2.0.1", + "which": "^1.2.10" + }, + "dependencies": { + "fs-extra": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" + } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.6" + } + } + } + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -4188,6 +4444,19 @@ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true, + "optional": true + }, + "promise-queue": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz", + "integrity": "sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=", + "dev": true + }, "prompt": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/prompt/-/prompt-0.2.14.tgz", @@ -4501,33 +4770,71 @@ "uuid": "^3.3.2" }, "dependencies": { + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "har-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "dev": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, "mime-db": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", "dev": true }, "mime-types": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", - "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", "dev": true, "requires": { - "mime-db": "~1.35.0" + "mime-db": "~1.36.0" } }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -4536,6 +4843,16 @@ } } }, + "request-progress": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", + "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", + "dev": true, + "optional": true, + "requires": { + "throttleit": "^1.0.0" + } + }, "reserved-words": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz", @@ -4781,9 +5098,9 @@ "dev": true }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -4870,6 +5187,16 @@ "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", "dev": true }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "optional": true, + "requires": { + "through": "2" + } + }, "split2": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", @@ -4886,9 +5213,9 @@ "dev": true }, "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "dev": true, "requires": { "asn1": "~0.2.3", @@ -4898,7 +5225,6 @@ "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" } }, @@ -5196,6 +5522,20 @@ "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", "dev": true }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true, + "optional": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true, + "optional": true + }, "through2": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", @@ -5270,24 +5610,6 @@ "integrity": "sha1-fMKRUfD18sQZRvEZ9ZMv5VQXASU=", "dev": true }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } - } - }, "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", @@ -5424,6 +5746,12 @@ "util-deprecate": "^1.0.2" } }, + "unicode-5.2.0": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/unicode-5.2.0/-/unicode-5.2.0-0.7.5.tgz", + "integrity": "sha512-KVGLW1Bri30x00yv4HNM8kBxoqFXr0Sbo55735nvrlsx4PYBZol3UtoWgO492fSwmsetzPEZzy73rbU8OGXJcA==", + "dev": true + }, "unique-stream": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", @@ -5456,6 +5784,12 @@ "querystring": "0.2.0" } }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=", + "dev": true + }, "utf7": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz", @@ -5776,6 +6110,16 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "optional": true, + "requires": { + "fd-slicer": "~1.0.1" + } } } } diff --git a/package.json b/package.json index dd23954a..94025e1e 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "devDependencies": { "aws-sdk": "~2.293.0", "backoff": "^2.5.0", + "box-node-sdk": "^1.21.0", "clone": "~2.1.2", "dropbox": "^2.5.10", "fs-extra": "~7.0.0", diff --git a/test/box/box_spec.js b/test/box/box_spec.js index 0fd1ff6a..ffd30c76 100644 --- a/test/box/box_spec.js +++ b/test/box/box_spec.js @@ -33,178 +33,207 @@ describe('box nodes', function() { describe("box credentials", function() { if (!nock) { - return; + return this.skip(); } + it("should complete oauth dance", function(done) { - helper.load(boxNode, [ - {id:"input", type:"helper", wires:[["input"]]}, - {id:"box-config", type:"box-credentials"}, - {id:"box", type:"box out", box:"box-config", wires:[["output"]]}, - {id:"output", type:"helper"}], function() { - var scope = nock('https://app.box.com:443') - .post('/api/oauth2/token', - "grant_type=authorization_code&code=CODE&client_id=CLIENT&client_secret=SECRET&redirect_uri=http%3A%2F%2Flocalhost%3A1880%2Fbox-credentials%2Fauth%2Fcallback") + helper.load(boxNode, [{ + id: "input", + type: "helper", + wires: [ + ["input"] + ] + }, + { + id: "box-config", + type: "box-credentials" + }, + { + id: "box", + type: "box out", + box: "box-config", + wires: [ + ["output"] + ] + }, + { + id: "output", + type: "helper" + } + ], function() { + nock('https://api.box.com') + .post('/oauth2/token', { + grant_type: 'authorization_code', + code: 'CODE', + client_id: 'CLIENT', + client_secret: 'SECRET' + }) .reply(200, { - "access_token":"ACCESS", - "expires_in":3761,"restricted_to":[], - "refresh_token":"REFRESH", - "token_type":"bearer" + "access_token": "ACCESS", + "expires_in": 3761, + "restricted_to": [], + "refresh_token": "REFRESH", + "token_type": "bearer" }, { date: 'Thu, 30 Oct 2014 10:37:29 GMT', 'content-type': 'application/json', 'transfer-encoding': 'chunked' }); - nock('https://api.box.com:443') + nock('https://api.box.com') .get('/2.0/users/me') .reply(200, { - "type":"user", - "id":"123456","name":"Foo Bar", - "login":"foobar@example.com", - "language":"en","timezone":"America/Los_Angeles", - "space_amount":1024,"space_used":512, - "max_upload_size":256,"status":"active" + "type": "user", + "id": "123456", + "name": "Foo Bar", + "login": "foobar@example.com", + "language": "en", + "timezone": "America/Los_Angeles", + "space_amount": 1024, + "space_used": 512, + "max_upload_size": 256, + "status": "active" }, { date: 'Thu, 30 Oct 2014 10:37:30 GMT', 'content-type': 'application/json' }); helper.request() - .get('/box-credentials/auth?id=box-config&clientId=CLIENT&clientSecret=SECRET&callback=http://localhost:1880/box-credentials/auth/callback') + .get( + '/box-credentials/auth?id=box-config&clientId=CLIENT&clientSecret=SECRET&callback=http://localhost:1880/box-credentials/auth/callback' + ) .expect(302) - .expect('Location', /https:\/\/app\.box\.com\/api\/oauth2\/authorize\?response_type=code&client_id=CLIENT&state=([^&]*)&redirect_uri=http%3A%2F%2Flocalhost%3A1880%2Fbox-credentials%2Fauth%2Fcallback/) + .expect('Location', + /https:\/\/app\.box\.com\/api\/oauth2\/authorize\?response_type=code&client_id=CLIENT&state=([^&]*)&redirect_uri=http%3A%2F%2Flocalhost%3A1880%2Fbox-credentials%2Fauth%2Fcallback/ + ) .end(function(err, res) { if (err) { - return done(err); + return done(err); } var location = url.parse(res.headers.location, true); var state = location.query.state; helper.request() - .get('/box-credentials/auth/callback?code=CODE&state='+state) + .get('/box-credentials/auth/callback?code=CODE&state=' + state) .expect(200) .end(function(err, res) { if (err) { - return done(err); + return done(err); } helper.credentials.get("box-config") - .should.have.property('displayName','Foo Bar'); + .should.have.property('displayName', 'Foo Bar'); done(); }); }); - }); + }); }); }); describe("watch node", function() { if (!nock) { - return; + return this.skip(); } - it('should report file add event', function(done) { - nock('https://api.box.com:443') - .get('/2.0/events?stream_position=now&stream_type=changes') - .reply(200, { - "chunk_size":0,"next_stream_position":1000, - "entries":[] - }, { 'content-type': 'application/json' }) - .get('/2.0/events?stream_position=1000&stream_type=changes') - .reply(200, {"entries":[{ - "type":"event","event_id":"1234", - "event_type":"ITEM_UPLOAD", - "session_id":"1234567", - "source":{ - "type":"file","id":"7","name":"foobar.txt", - "path_collection":{"total_count":2,"entries":[ - {"type":"folder","id":"0","name":"All Files"}, - {"type":"folder","id":"2","name":"node-red"} - ]}, - "parent":{"type":"folder","id":"2","name":"node-red"}, - }}],"chunk_size":1,"next_stream_position":2000}, { - 'content-type': 'application/json', - }); - helper.load(boxNode, - [{id:"box-config", type:"box-credentials"}, - {id:"box", type:"box in", box:"box-config", wires:[["output"]]}, - {id:"output", type:"helper"}, - ], { - "box-config": { - clientId: "ID", - clientSecret: "SECRET", - accessToken: "ACCESS", - refreshToken: "REFRESH", - expireTime: 1000+(new Date().getTime()/1000) - }, - }, function() { - var box = helper.getNode("box"); - box.should.have.property('id', 'box'); - var output = helper.getNode("output"); - output.should.have.property('id', 'output'); - output.on("input", function(msg) { - msg.should.have.property('payload', "node-red/foobar.txt"); - msg.should.have.property('file', "foobar.txt"); - msg.should.have.property('event', 'add'); - done(); - }); - // wait for s3.on("input", ...) to be called - var onFunction = box.on; - var onStub = sinon.stub(box,'on').callsFake(function(event, cb) { - var res = onFunction.apply(box, arguments); - onStub.restore(); - box.emit('input', {}); // trigger poll - return res; - }); + beforeEach(function () { + nock('https://api.box.com') + .post('/oauth2/token', { + client_id: 'ID', + client_secret: 'SECRET', + grant_type: 'refresh_token', + refresh_token: 'REFRESH' + }) + .reply(200, { + "access_token": "ACCESS", + "expires_in": 3761, + "restricted_to": [], + "refresh_token": "REFRESH", + "token_type": "bearer" }); }); - it('should report file delete event', function(done) { - nock('https://api.box.com:443') - .get('/2.0/events?stream_position=now&stream_type=changes') - .reply(200, { - "chunk_size":0,"next_stream_position":1000, - "entries":[] - }, { 'content-type': 'application/json' }) - .get('/2.0/events?stream_position=1000&stream_type=changes') - .reply(200, {"entries":[{ - "type":"event","event_id":"1234", - "event_type":"ITEM_TRASH", - "session_id":"1234567", - "source":{ - "type":"file","id":"7","name":"foobar.txt", - "path_collection":{"total_count":1,"entries":[ - {"type":"folder","id":"1","name":"Trash"} - ]}, - "parent":{"type":"folder","id":"2","name":"node-red"} - }}],"chunk_size":1,"next_stream_position":2000}, { - 'content-type': 'application/json' - }) - .get('/2.0/folders/2') + it('should report an event', function(done) { + nock('https://api.box.com') + .get('/2.0/events') + .query({ + stream_position: 'now' + }) .reply(200, { - "type":"folder", "id":"2", "name":"node-red", - "path_collection":{"total_count":1,"entries":[ - {"type":"folder","id":"0","name":"All Files"} - ]}, - "item_collection":{ - "total_count":1,"entries":[ - {"type":"file", "id":"4", "name":"notbar.txt"}, - ],"offset":0,"limit":100,"order":[ - {"by":"type","direction":"ASC"}, - {"by":"name","direction":"ASC"} - ] - } + "chunk_size": 0, + "next_stream_position": 1000, + "entries": [] }, { - date: 'Thu, 30 Oct 2014 10:39:16 GMT', - 'content-type': 'application/json', - }); + 'content-type': 'application/json' + }) + .options('/2.0/events') + .times(2) + .reply(200, { + "chunk_size": 1, + "entries": [{ + "type": "realtime_server", + "url": "http://2.realtime.services.box.net/subscribe?channel=cc807c9c4869ffb1c81a&stream_type=all", + "ttl": "10", + "max_retries": "10", + "retry_timeout": 610 + }] + }) + .get('/2.0/events') + .query({ + stream_position: 1000, + limit: 500 + }) + .reply(200, { + "entries": [{ + "type": "event", + "event_id": "1234", + "event_type": "ITEM_UPLOAD", + "session_id": "1234567", + "source": { + "type": "file", + "id": "7", + "name": "foobar.txt", + "path_collection": { + "total_count": 2, + "entries": [{ + "type": "folder", + "id": "0", + "name": "All Files" + }, + { + "type": "folder", + "id": "2", + "name": "node-red" + } + ] + }, + "parent": { + "type": "folder", + "id": "2", + "name": "node-red" + }, + } + }], + "chunk_size": 1, + "next_stream_position": 2000 + }, { + 'content-type': 'application/json', + }); + nock('http://2.realtime.services.box.net') + .get('/subscribe') + .query({ + channel: 'cc807c9c4869ffb1c81a', + stream_position: 1000, + stream_type: 'all' + }) + .reply(200, {message: 'new_change'}); helper.load(boxNode, - [{id:"box-config", type: "box-credentials"}, - {id:"box", type:"box in", - box: "box-config", wires: [["output"]] }, - {id:"output", type: "helper" }, + [{id:"box-config", type:"box-credentials"}, + {id:"box", type:"box in", box:"box-config", wires:[["output"]]}, + {id:"output", type:"helper"}, ], { "box-config": { clientId: "ID", clientSecret: "SECRET", accessToken: "ACCESS", refreshToken: "REFRESH", - expireTime: 1000+(new Date().getTime()/1000) + acquiredAtMS: Date.now(), + accessTokenTTLMS: 60000 }, }, function() { var box = helper.getNode("box"); @@ -212,21 +241,32 @@ describe('box nodes', function() { var output = helper.getNode("output"); output.should.have.property('id', 'output'); output.on("input", function(msg) { - msg.should.have.property('payload', - "node-red/foobar.txt"); - msg.should.have.property('file', "foobar.txt"); - msg.should.have.property('event', 'delete'); + try { + msg.payload.should.deepEqual({ + event_id: '1234', + event_type: 'ITEM_UPLOAD', + fullPath: 'node-red/foobar.txt', + session_id: '1234567', + source: { + id: '7', + name: 'foobar.txt', + parent: { id: '2', name: 'node-red', type: 'folder' }, + path_collection: { + entries: [ + { id: '0', name: 'All Files', type: 'folder' }, + { id: '2', name: 'node-red', type: 'folder' } + ], + total_count: 2 + }, + type: 'file' + }, + type: 'event' + }); + } catch (err) { + return done(err); + } done(); }); - - // wait for s3.on("input", ...) to be called - var onFunction = box.on; - var onStub = sinon.stub(box, 'on').callsFake(function(event, cb) { - var res = onFunction.apply(box, arguments); - onStub.restore(); - box.emit('input', {}); // trigger poll - return res; - }); }); }); @@ -271,7 +311,8 @@ describe('box nodes', function() { clientSecret: "SECRET", accessToken: "ACCESS", refreshToken: "REFRESH", - expireTime: 1000+(new Date().getTime()/1000) + acquiredAtMS: Date.now(), + accessTokenTTLMS: 60000 }, }, function() { var box = helper.getNode("box"); @@ -337,7 +378,8 @@ describe('box nodes', function() { clientSecret: "SECRET", accessToken: "ACCESS", refreshToken: "REFRESH", - expireTime: 1000+(new Date().getTime()/1000) + acquiredAtMS: Date.now(), + accessTokenTTLMS: 60000 }, }, function() { var box = helper.getNode("box"); @@ -362,109 +404,85 @@ describe('box nodes', function() { }); }); - it.skip('should report file delete event', function(done) { - nock('https://api.box.com:443') - .post('/1/delta') - .reply(200, { - "has_more":false, - "cursor":"AAAA", - "entries":[["/foobar.bak",null]], - "reset":true, - }, { - server: 'nginx', - date: 'Wed Oct 29 05:33:58 GMT 2014', - 'content-type': 'text/javascript', - 'transfer-encoding': 'chunked', - connection: 'keep-alive', - }) - .post('/1/delta', "cursor=AAAA") - .reply(200, { - "has_more":false, - "cursor":"BBBB", - "entries":[], - "reset":false - }, { - server: 'nginx', - date: 'Wed Oct 29 05:34:28 GMT 2014', - 'content-type': 'text/javascript', - 'transfer-encoding': 'chunked', - connection: 'keep-alive', - }); - helper.load(boxNode, - [{id:"box-config", type: "box-credentials"}, - {id:"box", type:"box in", - box: "box-config", wires: [["output"]] }, - {id:"output", type: "helper" }, - ], { - "box-config": { - clientId: "ID", - clientSecret: "SECRET", - accessToken: "ACCESS", - refreshToken: "REFRESH", - expireTime: 1000+(new Date().getTime()/1000) - }, - }, function() { - var box = helper.getNode("box"); - box.should.have.property('id', 'box'); - var output = helper.getNode("output"); - output.should.have.property('id', 'output'); - output.on("input", function(msg) { - should.fail(null,null, - "unexpected message: "+JSON.stringify(msg)); - }); - - // wait for s3.on("input", ...) to be called - var onFunction = box.on; - var onStub = sinon.stub(box, 'on').callsFake(function(event, cb) { - var res = onFunction.apply(box, arguments); - onStub.restore(); - box.emit('input', {}); // trigger poll - // wait to ensure no messages are generated - setTimeout(function () { - done(); - }, 500); - return res; - }); - }); - }); }); describe("query node", function() { if (!nock) { - return; + return this.skip(); } + + beforeEach(function () { + nock('https://api.box.com') + .post('/oauth2/token', { + client_id: 'ID', + client_secret: 'SECRET', + grant_type: 'refresh_token', + refresh_token: 'REFRESH' + }) + .times(4) + .reply(200, { + "access_token": "ACCESS", + "expires_in": 3761, + "restricted_to": [], + "refresh_token": "REFRESH", + "token_type": "bearer" + }); + }); + it('should fetch file', function(done) { - nock('https://api.box.com:443') - .get('/2.0/folders/0') + nock('https://api.box.com') + .get('/2.0/folders/0/items') .reply(200, { - "type":"folder", "id":"0", "name":"All Files", - "path_collection":{"total_count":0,"entries":[]}, - "item_collection":{ - "total_count":2,"entries":[ - {"type":"folder", "id":"1", "name":"not-red"}, - {"type":"folder", "id":"2", "name":"node-red"}, - ],"offset":0,"limit":100,"order":[ - {"by":"type","direction":"ASC"}, - {"by":"name","direction":"ASC"} - ] - } + "entries": [{ + "type": "folder", + "id": "1", + "name": "not-red" + }, + { + "type": "folder", + "id": "2", + "name": "node-red" + }, + ], + "offset": 0, + "limit": 100, + "order": [{ + "by": "type", + "direction": "ASC" + }, + { + "by": "name", + "direction": "ASC" + } + ] }, { date: 'Thu, 30 Oct 2014 10:39:16 GMT', 'content-type': 'application/json', }) - .get('/2.0/folders/2') + .get('/2.0/folders/2/items') .reply(200, { - "type":"folder", "id":"0", "name":"node-red", - "path_collection":{"total_count":0,"entries":[]}, - "item_collection":{ - "total_count":2,"entries":[ - {"type":"file", "id":"4", "name":"notbar.txt"}, - {"type":"file", "id":"5", "name":"foobar.txt"}, - ],"offset":0,"limit":100,"order":[ - {"by":"type","direction":"ASC"}, - {"by":"name","direction":"ASC"} - ] - } + "entries": [{ + "type": "file", + "id": "4", + "name": "notbar.txt" + }, + { + "type": "file", + "id": "5", + "name": "foobar.txt" + }, + ], + "offset": 0, + "limit": 100, + "order": [{ + "by": "type", + "direction": "ASC" + }, + { + "by": "name", + "direction": "ASC" + } + ] }, { date: 'Thu, 30 Oct 2014 10:39:16 GMT', 'content-type': 'application/json', @@ -474,134 +492,355 @@ describe('box nodes', function() { date: 'Thu, 30 Oct 2014 10:42:29 GMT', 'content-length': '0', location: 'https://dl.boxcloud.com/your-file-is-here/' - }); - nock('https://dl.boxcloud.com:443') + }); + nock('https://dl.boxcloud.com') .get('/your-file-is-here/') .reply(200, "hello world", { date: 'Thu, 30 Oct 2014 10:42:29 GMT', 'content-type': 'text/plain; charset=UTF-8', 'content-length': '11', }); - helper.load(boxNode, - [{id:"inject", type: "helper", wires: [["box-instance"]]}, - {id:"box-config", type: "box-credentials"}, - {id:"box-instance", type:"box", - box: "box-config", wires: [["output"]] }, - {id:"output", type: "helper" }, - ], { - "box-config": { - clientId: "ID", - clientSecret: "SECRET", - accessToken: "ACCESS", - refreshToken: "REFRESH", - expireTime: 1000+(new Date().getTime()/1000) - } - }, function() { - var box = helper.getNode("box-instance"); - box.should.have.property('id', 'box-instance'); - var inject = helper.getNode("inject"); - inject.should.have.property('id', 'inject'); - var output = helper.getNode("output"); - output.should.have.property('id', 'output'); - output.on("input", function(msg) { + helper.load(boxNode, [{ + id: "inject", + type: "helper", + wires: [ + ["box-instance"] + ] + }, + { + id: "box-config", + type: "box-credentials" + }, + { + id: "box-instance", + type: "box", + box: "box-config", + wires: [ + ["output"] + ] + }, + { + id: "output", + type: "helper" + }, + ], { + "box-config": { + clientId: "ID", + clientSecret: "SECRET", + accessToken: "ACCESS", + refreshToken: "REFRESH", + acquiredAtMS: Date.now(), + accessTokenTTLMS: 60000 + } + }, function() { + var box = helper.getNode("box-instance"); + box.should.have.property('id', 'box-instance'); + var inject = helper.getNode("inject"); + inject.should.have.property('id', 'inject'); + var output = helper.getNode("output"); + output.should.have.property('id', 'output'); + output.on("input", function(msg) { + try { msg.should.have.property('payload', - new Buffer("hello world")); + "hello world"); msg.should.have.property('filename', 'node-red/foobar.txt'); - done(); - }); - inject.send({ - filename: "node-red/foobar.txt" - }); + } catch (err) { + return done(err); + } + done(); + }); + inject.send({ + filename: "node-red/foobar.txt" }); + }); }); }); describe('out node', function() { if (!nock) { - return; + return this.skip(); } - it('should upload msg.payload', function(done) { - nock('https://api.box.com:443') - .post('/oauth2/token', - "grant_type=refresh_token&client_id=ID&client_secret=SECRET&refresh_token=REFRESH") + + beforeEach(function() { + nock('https://api.box.com') + .post('/oauth2/token', { + client_id: 'ID', + client_secret: 'SECRET', + grant_type: 'refresh_token', + refresh_token: 'REFRESH' + }) + .times(6) + .reply(200, { + "access_token": "ACCESS", + "expires_in": 3761, + "restricted_to": [], + "refresh_token": "REFRESH", + "token_type": "bearer" + }); + }); + + it('should overwrite file w/ msg.payload', function(done) { + nock('https://api.box.com') + .get('/2.0/folders/0/items') .reply(200, { - "access_token":"ACCESS", - "expires_in":3761,"restricted_to":[], - "refresh_token":"REFRESH", - "token_type":"bearer" + "entries": [{ + "type": "folder", + "id": "1", + "name": "not-red" + }, + { + "type": "folder", + "id": "2", + "name": "node-red" + }, + ], + "offset": 0, + "limit": 100, + "order": [{ + "by": "type", + "direction": "ASC" + }, + { + "by": "name", + "direction": "ASC" + } + ] }, { - date: 'Thu, 30 Oct 2014 10:37:29 GMT', + date: 'Thu, 30 Oct 2014 10:39:16 GMT', 'content-type': 'application/json', - 'transfer-encoding': 'chunked' }) - .get('/2.0/folders/0') + .get('/2.0/folders/2/items') .reply(200, { - "type":"folder", "id":"0", "name":"All Files", - "path_collection":{"total_count":0,"entries":[]}, - "item_collection":{ - "total_count":2,"entries":[ - {"type":"folder", "id":"1", "name":"not-red"}, - {"type":"folder", "id":"2", "name":"node-red"}, - ],"offset":0,"limit":100,"order":[ - {"by":"type","direction":"ASC"}, - {"by":"name","direction":"ASC"} - ] - } + "entries": [{ + "type": "file", + "id": "3", + "name": "foobar.txt" + } + ], + "offset": 0, + "limit": 100, + "order": [{ + "by": "type", + "direction": "ASC" + }, + { + "by": "name", + "direction": "ASC" + } + ] }, { date: 'Thu, 30 Oct 2014 10:39:16 GMT', 'content-type': 'application/json', - }); - nock('https://upload.box.com:443') - .filteringRequestBody( // filter variable mime boundary strings - /----------------------------[a-z0-9]*/g, - '----------------------------blah') - .post('/api/2.0/files/content', - "----------------------------blah\r\nContent-Disposition: form-data; name=\"filename\"; filename=\"foobar.txt\"\r\nContent-Type: text/plain\r\n\r\nhello world\r\n----------------------------blah\r\nContent-Disposition: form-data; name=\"parent_id\"\r\n\r\n2\r\n----------------------------blah--\r\n") - .reply(201, { - "total_count":1,"entries":[{ - "type":"file","id":"3","name":"foobar.txt", - "size":11,"path_collection":{ - "total_count":2, - "entries":[ - {"type":"folder","id":"0","name":"All Files"}, - {"type":"folder","id":"2","name":"node-red"} + }) + .options('/2.0/files/3/content') + .reply(200); + nock('https://upload.box.com') + .post('/api/2.0/files/3/content') + .reply(200, { + "total_count": 1, + "entries": [{ + "type": "file", + "id": "3", + "name": "foobar.txt", + "size": 11, + "path_collection": { + "total_count": 2, + "entries": [{ + "type": "folder", + "id": "0", + "name": "All Files" + }, + { + "type": "folder", + "id": "2", + "name": "node-red" + } ] } - }]}, { - date: 'Thu, 30 Oct 2014 10:41:10 GMT', - 'content-type': 'application/json', - }); - helper.load(boxNode, - [ - {id:"inject", type:"helper", wires:[["box"]]}, - {id:"box-config", type: "box-credentials"}, - {id:"box", type:"box out", box: "box-config" }, - ], - { "box-config": { - "clientId":"ID", - "clientSecret":"SECRET", - "accessToken":"ACCESS", - "refreshToken": "REFRESH" - } }, function() { - var box = helper.getNode("box"); - box.should.have.property('id', 'box'); - var inject = helper.getNode("inject"); - inject.should.have.property('id', 'inject'); + }] + }, { + date: 'Thu, 30 Oct 2014 10:41:10 GMT', + 'content-type': 'application/json', + }); + helper.load(boxNode, [{ + id: "inject", + type: "helper", + wires: [ + ["box"] + ] + }, + { + id: "box-config", + type: "box-credentials" + }, + { + id: "box", + type: "box out", + box: "box-config", + wires: [ + ['output'] + ] + }, + { + id: 'output', + type: 'helper' + } + ], { + "box-config": { + clientId: "ID", + clientSecret: "SECRET", + accessToken: "ACCESS", + refreshToken: "REFRESH", + acquiredAtMS: Date.now(), + accessTokenTTLMS: 60000 + } + }, function() { + var box = helper.getNode("box"); + box.should.have.property('id', 'box'); + var inject = helper.getNode("inject"); + inject.should.have.property('id', 'inject'); + var output = helper.getNode("output"); + output.should.have.property('id', 'output'); + output.on("input", function(msg) { + msg.payload.entries[0].should.have.property('name', 'foobar.txt'); + done(); + }); + inject.send({ + filename: 'node-red/foobar.txt', + payload: "hello world" + }); + }); + }); - // stub status call to wait for successful upload - var stub = sinon.stub(box, 'status').callsFake(function(status) { - if (Object.getOwnPropertyNames(status).length === 0) { - stub.restore(); - done(); + it('should upload new file w/ msg.payload', function(done) { + nock('https://api.box.com') + .get('/2.0/folders/0/items') + .times(2) + .reply(200, { + "entries": [{ + "type": "folder", + "id": "1", + "name": "not-red" + }, + { + "type": "folder", + "id": "2", + "name": "node-red" + }, + ], + "offset": 0, + "limit": 100, + "order": [{ + "by": "type", + "direction": "ASC" + }, + { + "by": "name", + "direction": "ASC" } - return; - }); - inject.send({ - filename: 'node-red/foobar.txt', - payload: "hello world" - }); + ] + }, { + date: 'Thu, 30 Oct 2014 10:39:16 GMT', + 'content-type': 'application/json', + }) + .get('/2.0/folders/2/items') + .times(2) + .reply(200, { + "entries": [], + "offset": 0, + "limit": 100, + "order": [{ + "by": "type", + "direction": "ASC" + }, + { + "by": "name", + "direction": "ASC" + } + ] + }, { + date: 'Thu, 30 Oct 2014 10:39:16 GMT', + 'content-type': 'application/json', + }) + .options('/2.0/files/content') + .reply(200); + nock('https://upload.box.com') + .post('/api/2.0/files/content') + .reply(200, { + "total_count": 1, + "entries": [{ + "type": "file", + "id": "3", + "name": "foobar.txt", + "size": 11, + "path_collection": { + "total_count": 2, + "entries": [{ + "type": "folder", + "id": "0", + "name": "All Files" + }, + { + "type": "folder", + "id": "2", + "name": "node-red" + } + ] + } + }] + }, { + date: 'Thu, 30 Oct 2014 10:41:10 GMT', + 'content-type': 'application/json', }); - }); + helper.load(boxNode, [{ + id: "inject", + type: "helper", + wires: [ + ["box"] + ] + }, + { + id: "box-config", + type: "box-credentials" + }, + { + id: "box", + type: "box out", + box: "box-config", + wires: [ + ['output'] + ] + }, + { + id: 'output', + type: 'helper' + } + ], { + "box-config": { + clientId: "ID", + clientSecret: "SECRET", + accessToken: "ACCESS", + refreshToken: "REFRESH", + acquiredAtMS: Date.now(), + accessTokenTTLMS: 60000 + } + }, function() { + var box = helper.getNode("box"); + box.should.have.property('id', 'box'); + var inject = helper.getNode("inject"); + inject.should.have.property('id', 'inject'); + var output = helper.getNode("output"); + output.should.have.property('id', 'output'); + output.on("input", function(msg) { + msg.payload.entries[0].should.have.property('name', 'foobar.txt'); + done(); + }); + inject.send({ + filename: 'node-red/foobar.txt', + payload: "hello world" + }); + }); + }) }); });