box-node-sdk 1.32.0 → 1.34.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,27 @@
1
1
  # Changelog
2
2
 
3
- ## 1.32.0 [2020-03-30]
3
+ ## 1.34.2 [2020-08-20]
4
4
 
5
- - Temporarily removed Node 4 and Node 5 builds from Travis, due to tests not passing. Will investigate, going forward.
6
- - Fixed an issue where an error is thrown during a retry when a response is not returned by the previous call ([#476](https://github.com/box/box-node-sdk/pull/76)).
5
+ - Make iterator bug fix for uploading files non breaking ([#534](https://github.com/box/box-node-sdk/pull/534))
6
+
7
+ ## 1.34.1 [2020-08-17]
8
+
9
+ - Fix iterator bug for uploading new file versions ([#531](https://github.com/box/box-node-sdk/pull/531))
10
+
11
+ ## 1.34.0 [2020-08-04]
12
+
13
+ - Add zip functionality ([#525](https://github.com/box/box-node-sdk/pull/525))
14
+ - Add proxy support for `http`, `https`, `socks` and `pac` protocols ([#529](https://github.com/box/box-node-sdk/pull/529))
15
+
16
+ ## 1.33.0 [2020-06-25]
17
+
18
+ - Add path parameter sanitization ([#505](https://github.com/box/box-node-sdk/pull/505))
19
+ - Add support for all streams for uploading files ([#519](https://github.com/box/box-node-sdk/pull/519))
20
+
21
+ ## 1.32.0 [2020-04-01]
22
+
23
+ - Temporarily removed Node 4 and Node 5 builds from Travis, due to tests not passing. Will investigate, going forward ([#495](https://github.com/box/box-node-sdk/pull/495)).
24
+ - Fixed an issue where an error is thrown during a retry when a response is not returned by the previous call ([#477](https://github.com/box/box-node-sdk/pull/477)).
7
25
  - Added the ability to [query](./docs/metadata.md#query) Box items based on their metadata ([#487](https://github.com/box/box-node-sdk/pull/487)).
8
26
 
9
27
  ## 1.31.0 [2020-02-13]
@@ -44,7 +44,8 @@ var urlPath = require('../util/url-path'),
44
44
  var BASE_PATH = '/files',
45
45
  VERSIONS_SUBRESOURCE = '/versions',
46
46
  WATERMARK_SUBRESOURCE = '/watermark',
47
- UPLOAD_SESSION_SUBRESOURCE = '/upload_sessions';
47
+ UPLOAD_SESSION_SUBRESOURCE = '/upload_sessions',
48
+ ZIP_DOWNLOAD_PATH = '/zip_downloads';
48
49
 
49
50
  // Enum of valid lock types
50
51
  var lockTypes = {
@@ -78,10 +79,11 @@ function createFileMetadataFormData(parentFolderID, filename, options) {
78
79
  /**
79
80
  * Returns the multipart form value for file upload content.
80
81
  * @param {string|Buffer|Stream} content - the content of the file being uploaded
82
+ * @param {Object} options - options for the content
81
83
  * @returns {Object} - the form value expected by the API for the 'content' key
82
84
  * @private
83
85
  */
84
- function createFileContentFormData(content) {
86
+ function createFileContentFormData(content, options) {
85
87
  // The upload API appears to look for a form field that contains a filename
86
88
  // property and assume that this form field contains the file content. Thus,
87
89
  // the value of name does not actually matter (as long as it does not conflict
@@ -90,7 +92,7 @@ function createFileContentFormData(content) {
90
92
  // filename specified in the metadata form field instead.
91
93
  return {
92
94
  value: content,
93
- options: { filename: 'unused' }
95
+ options: Object.assign({ filename: 'unused' }, options)
94
96
  };
95
97
  }
96
98
 
@@ -601,6 +603,7 @@ Files.prototype.promoteVersion = function(fileID, versionID, callback) {
601
603
  * @param {Object} [options] - Optional parameters
602
604
  * @param {string} [options.content_created_at] - RFC 3339 timestamp when the file was created
603
605
  * @param {string} [options.content_modified_at] - RFC 3339 timestamp when the file was last modified
606
+ * @param {int} [options.content_length] - Optional length of the content. Required if content is a read stream of any type other than fs stream.
604
607
  * @param {Function} [callback] - called with data about the upload if successful, or an error if the
605
608
  * upload failed
606
609
  * @returns {Promise<Object>} A promise resolving to the uploaded file
@@ -613,10 +616,17 @@ Files.prototype.uploadFile = function(parentFolderID, filename, content, options
613
616
  options = {};
614
617
  }
615
618
 
619
+ var formOptions = {};
620
+ if (options && options.hasOwnProperty('content_length')) {
621
+ formOptions.knownLength = options.content_length;
622
+ // Delete content_length from options so it's not added to the attributes of the form
623
+ delete options.content_length;
624
+ }
625
+
616
626
  var apiPath = urlPath(BASE_PATH, '/content'),
617
627
  multipartFormData = {
618
628
  attributes: createFileMetadataFormData(parentFolderID, filename, options),
619
- content: createFileContentFormData(content)
629
+ content: createFileContentFormData(content, formOptions)
620
630
  };
621
631
 
622
632
  return this.client.wrapWithDefaultHandler(this.client.upload)(apiPath, null, multipartFormData, callback);
@@ -635,6 +645,7 @@ Files.prototype.uploadFile = function(parentFolderID, filename, content, options
635
645
  * @param {Object} [options] - Optional parameters
636
646
  * @param {string} [options.content_modified_at] - RFC 3339 timestamp when the file was last modified
637
647
  * @param {string} [options.name] - A new name for the file
648
+ * @param {int} [options.content_length] - Optional length of the content. Required if content is a read stream of any type other than fs stream.
638
649
  * @param {Function} [callback] - called with data about the upload if successful, or an error if the
639
650
  * upload failed
640
651
  * @returns {Promise<Object>} A promise resolving to the uploaded file
@@ -650,11 +661,18 @@ Files.prototype.uploadNewFileVersion = function(fileID, content, options, callba
650
661
  var apiPath = urlPath(BASE_PATH, fileID, '/content'),
651
662
  multipartFormData = {};
652
663
 
664
+
665
+ var formOptions = {};
653
666
  if (options) {
667
+ if (options.hasOwnProperty('content_length')) {
668
+ formOptions.knownLength = options.content_length;
669
+ // Delete content_length from options so it's not added to the attributes of the form
670
+ delete options.content_length;
671
+ }
654
672
  multipartFormData.attributes = JSON.stringify(options);
655
673
  }
656
674
 
657
- multipartFormData.content = createFileContentFormData(content);
675
+ multipartFormData.content = createFileContentFormData(content, formOptions);
658
676
 
659
677
  return this.client.wrapWithDefaultHandler(this.client.upload)(apiPath, null, multipartFormData, callback);
660
678
  };
@@ -1574,6 +1592,67 @@ Files.prototype.getRepresentationContent = function(fileID, representationType,
1574
1592
  .asCallback(callback);
1575
1593
  };
1576
1594
 
1595
+ /**
1596
+ * Creates a zip of multiple files and folders.
1597
+ *
1598
+ * API Endpoint: '/zip_downloads'
1599
+ * Method: POST
1600
+ *
1601
+ * @param {name} name - The name of the zip file to be created
1602
+ * @param {Array} items - Array of files or folders to be part of the created zip
1603
+ * @param {Function} [callback] Passed a stream over the representation contents if successful
1604
+ * @returns {Promise<string>} A promise resolving to the file's download URL
1605
+ */
1606
+ Files.prototype.createZip = function(name, items, callback) {
1607
+ var params = {
1608
+ body: {
1609
+ download_file_name: name,
1610
+ items
1611
+ }
1612
+ };
1613
+
1614
+ return this.client.wrapWithDefaultHandler(this.client.post)(ZIP_DOWNLOAD_PATH, params, callback);
1615
+ };
1616
+
1617
+ /**
1618
+ * Creates a zip of multiple files and folders and downloads it.
1619
+ *
1620
+ * API Endpoint: '/zip_downloads'
1621
+ * Method: GET
1622
+ *
1623
+ * @param {name} name - The name of the zip file to be created
1624
+ * @param {Array} items - Array of files or folders to be part of the created zip
1625
+ * @param {Stream} stream - Stream to pipe the readable stream of the zip file
1626
+ * @param {Function} [callback] - Passed a status response object
1627
+ * @returns {Promise<Readable>} A promise resolving for the file stream
1628
+ */
1629
+ Files.prototype.downloadZip = function(name, items, stream, callback) {
1630
+ var downloadStreamOptions = {
1631
+ streaming: true,
1632
+ headers: {}
1633
+ };
1634
+
1635
+ var params = {
1636
+ body: {
1637
+ download_file_name: name,
1638
+ items
1639
+ }
1640
+ };
1641
+
1642
+ return this.client.post(ZIP_DOWNLOAD_PATH, params)
1643
+ .then(response => this.client.get(response.body.download_url, downloadStreamOptions)
1644
+ .then(responseStream => {
1645
+ responseStream.pipe(stream);
1646
+ // eslint-disable-next-line promise/avoid-new
1647
+ return new Promise((resolve, reject) => {
1648
+ responseStream.on('end', () => resolve('Done downloading'));
1649
+ responseStream.on('error', error => reject(error));
1650
+ }).then(() => this.client.get(response.body.status_url).then(responseStatus => responseStatus.body));
1651
+ })
1652
+ )
1653
+ .asCallback(callback);
1654
+ };
1655
+
1577
1656
  /**
1578
1657
  * @module box-node-sdk/lib/managers/files
1579
1658
  * @see {@Link Files}
@@ -325,7 +325,12 @@ Metadata.prototype = {
325
325
  *
326
326
  * @param {string} from - The template used in the query. Must be in the form scope.templateKey
327
327
  * @param {string} ancestorFolderId - The folder_id to which to restrain the query
328
- * @param {Object} options - Query options
328
+ * @param {Object} options - Optional parameters
329
+ * @param {string} [options.query] - The logical expression of the query
330
+ * @param {Object} [options.query_parameters] - Required if query present. The arguments for the query
331
+ * @param {string} [options.index_name] - The name of the Index to use
332
+ * @param {Object} [options.order_by] - The field_key(s) to order on and the corresponding direction(s)
333
+ * @param {Array} [options.fields] - An array of fields to return
329
334
  * @param {Function} [callback] - Passed a collection of items and their associated metadata
330
335
  * @returns {Promise<void>} Promise resolving to a collection of items and their associated metadata
331
336
  */
@@ -10,7 +10,9 @@
10
10
  var assert = require('assert'),
11
11
  https = require('https'),
12
12
  merge = require('merge-options'),
13
- sdkVersion = require('../../package.json').version;
13
+ sdkVersion = require('../../package.json').version,
14
+ ProxyAgent = require('proxy-agent'),
15
+ url = require('url');
14
16
 
15
17
  // ------------------------------------------------------------------------------
16
18
  // Private
@@ -64,6 +66,11 @@ var defaults = {
64
66
  iterators: false,
65
67
  enterpriseID: undefined,
66
68
  analyticsClient: null,
69
+ proxy: {
70
+ url: null,
71
+ username: null,
72
+ password: null,
73
+ },
67
74
  request: {
68
75
  // By default, require API SSL cert to be valid
69
76
  strictSSL: true,
@@ -130,6 +137,22 @@ function validateAppAuthParams(appAuth) {
130
137
  }
131
138
  }
132
139
 
140
+ /**
141
+ * Update the agentClass based on the proxy config values passed in by the user
142
+ * @param {UserConfigurationOptions} params The current Config values
143
+ * @returns {void}
144
+ * @private
145
+ */
146
+ function updateRequestAgent(params) {
147
+ if (params.proxy.url) {
148
+ params.request.agentClass = ProxyAgent;
149
+ params.request.agentOptions = Object.assign({}, params.request.agentOptions, url.parse(params.proxy.url));
150
+ if (params.proxy.username && params.proxy.password) {
151
+ Object.assign(params.request.agentOptions, {auth: `${params.proxy.username}:${params.proxy.password}`});
152
+ }
153
+ }
154
+ }
155
+
133
156
  // ------------------------------------------------------------------------------
134
157
  // Public
135
158
  // ------------------------------------------------------------------------------
@@ -157,8 +180,8 @@ function Config(params) {
157
180
 
158
181
  // Set the given params or default value if params property is missing
159
182
  this._params = merge(defaults, params);
183
+ updateRequestAgent(this._params);
160
184
  Object.assign(this, this._params);
161
-
162
185
  // Freeze the object so that configuration options cannot be modified
163
186
  Object.freeze(this);
164
187
  }
@@ -58,7 +58,11 @@ class PagingIterator {
58
58
  * @returns {boolean} Whether the response is iterable
59
59
  */
60
60
  static isIterable(response) {
61
- var isGetOrPostRequest = (response.request && (response.request.method === 'GET' || response.request.method === 'POST')),
61
+ // POST responses for uploading a file are explicitly excluded here because, while the response is iterable,
62
+ // it always contains only a single entry and historically has not been handled as iterable in the SDK.
63
+ // This behavior is being preserved here to avoid a breaking change.
64
+ let UPLOAD_PATTERN = /.*upload\.box\.com.*\/content/;
65
+ var isGetOrPostRequest = (response.request && (response.request.method === 'GET' || (response.request.method === 'POST' && !UPLOAD_PATTERN.test(response.request.uri.href)))),
62
66
  hasEntries = (response.body && Array.isArray(response.body.entries)),
63
67
  notEventStream = (response.body && !response.body.next_stream_position);
64
68
 
@@ -160,6 +164,9 @@ class PagingIterator {
160
164
  if (response.request.method === 'GET') {
161
165
  this.options.qs[this.nextField] = this.nextValue;
162
166
  } else if (response.request.method === 'POST') {
167
+ if (!this.options.body) {
168
+ this.options.body = {};
169
+ }
163
170
  this.options.body[this.nextField] = this.nextValue;
164
171
  let bodyString = JSON.stringify(this.options.body);
165
172
  this.options.headers['content-length'] = bodyString.length;
@@ -8,6 +8,8 @@
8
8
  // Private
9
9
  // ------------------------------------------------------------------------------
10
10
 
11
+ // Pattern to check for relative paths
12
+ var PATTERN = /\/\.+/;
11
13
  /**
12
14
  * remove leading & trailing slashes from some string. This is useful for
13
15
  * removing slashes from the path segments that are actually a part of the
@@ -39,7 +41,14 @@ function trimSlashes(segment) {
39
41
  */
40
42
  module.exports = function urlPath(/* arguments*/) {
41
43
  var args = Array.prototype.slice.call(arguments);
42
- var path = args.map(x => String(x)).map(x => trimSlashes(x))
44
+ var path = args.map(x => String(x))
45
+ .map(x => {
46
+ var trimmedX = trimSlashes(x);
47
+ if (PATTERN.test(trimmedX)) {
48
+ throw new Error(`An invalid path parameter exists in ${trimmedX}. Relative path parameters cannot be passed.`);
49
+ }
50
+ return trimmedX;
51
+ })
43
52
  .map(x => encodeURIComponent(x))
44
53
  .join('/');
45
54
  return `/${path}`;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "box-node-sdk",
3
3
  "author": "Box <oss@box.com>",
4
- "version": "1.32.0",
4
+ "version": "1.34.2",
5
5
  "description": "Official SDK for Box Plaform APIs",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -36,8 +36,8 @@
36
36
  "http-status": "^1.4.1",
37
37
  "jsonwebtoken": "^8.5.1",
38
38
  "merge-options": "^1.0.1",
39
- "npm-upgrade": "^2.0.2",
40
39
  "promise-queue": "^2.2.3",
40
+ "proxy-agent": "^3.1.1",
41
41
  "request": "^2.88.0",
42
42
  "url-template": "^2.0.8",
43
43
  "uuid": "^3.3.3"
@@ -57,6 +57,7 @@
57
57
  "mockery": "^2.1.0",
58
58
  "nock": "^9.6.1",
59
59
  "np": "^5.1.3",
60
+ "npm-upgrade": "^2.0.3",
60
61
  "nyc": "^11.9.0",
61
62
  "shelljs": "^0.8.3",
62
63
  "shelljs-nodecli": "^0.1.1",