Source: box-client.ts

  1. /**
  2. * @fileoverview Box API Client
  3. */
  4. import { Promise } from 'bluebird';
  5. // ------------------------------------------------------------------------------
  6. // API Resource Managers
  7. // ------------------------------------------------------------------------------
  8. import AI from './managers/ai.generated';
  9. import CollaborationAllowlist from './managers/collaboration-allowlist';
  10. import Collaborations from './managers/collaborations';
  11. import Collections from './managers/collections';
  12. import Comments from './managers/comments';
  13. import DevicePins from './managers/device-pins';
  14. import Enterprise from './managers/enterprise';
  15. import Events from './managers/events';
  16. import Files from './managers/files';
  17. import Folders from './managers/folders';
  18. import Groups from './managers/groups';
  19. import LegalHoldPolicies from './managers/legal-hold-policies';
  20. import Metadata from './managers/metadata';
  21. import RecentItems from './managers/recent-items';
  22. import RetentionPolicies from './managers/retention-policies';
  23. import Search from './managers/search';
  24. import SharedItems from './managers/shared-items';
  25. import SignRequests from './managers/sign-requests.generated';
  26. import SignTemplates from './managers/sign-templates.generated';
  27. import StoragePolicies from './managers/storage-policies';
  28. import Tasks from './managers/tasks';
  29. import TermsOfService from './managers/terms-of-service';
  30. import Trash from './managers/trash';
  31. import Users from './managers/users';
  32. import WebLinks from './managers/web-links';
  33. import Webhooks from './managers/webhooks';
  34. import FileRequestsManager from "./managers/file-requests-manager";
  35. import ShieldInformationBarriers from "./managers/shield-information-barriers.generated";
  36. import ShieldInformationBarrierSegments from "./managers/shield-information-barrier-segments.generated";
  37. import ShieldInformationBarrierSegmentMembers from "./managers/shield-information-barrier-segment-members.generated";
  38. import ShieldInformationBarrierSegmentRestrictions from "./managers/shield-information-barrier-segment-restrictions.generated";
  39. import ShieldInformationBarrierReports from "./managers/shield-information-barrier-reports.generated";
  40. import IntegrationMappings from "./managers/integration-mappings";
  41. // ------------------------------------------------------------------------------
  42. // Typedefs and Callbacks
  43. // ------------------------------------------------------------------------------
  44. /**
  45. * A collaboration role constant
  46. * @typedef {string} CollaborationRole
  47. */
  48. type CollaborationRole = string;
  49. /**
  50. * A Box file or folder type constant
  51. * @typedef {string} ItemType
  52. */
  53. type ItemType = 'file' | 'folder';
  54. /**
  55. * An access level constant. Used for setting and updating shared links, folder upload, etc.
  56. * @typedef {?Object} AccessLevel
  57. */
  58. type AccessLevel = object | null /* FIXME */;
  59. type APISession = any /* FIXME */;
  60. type APIRequestManager = any /* FIXME */;
  61. // ------------------------------------------------------------------------------
  62. // Requirements
  63. // ------------------------------------------------------------------------------
  64. var util = require('util'),
  65. qs = require('querystring'),
  66. errors = require('./util/errors'),
  67. httpStatusCodes = require('http-status'),
  68. isIP = require('net').isIP,
  69. merge = require('merge-options'),
  70. PagingIterator = require('./util/paging-iterator'),
  71. pkg = require('../package.json');
  72. // ------------------------------------------------------------------------------
  73. // Private
  74. // ------------------------------------------------------------------------------
  75. // The Authorization header label
  76. var HEADER_AUTHORIZATION = 'Authorization',
  77. // Prefix our token with this string in the Authorization header
  78. HEADER_AUTHORIZATION_PREFIX = 'Bearer ',
  79. // The 'BoxApi' header label
  80. HEADER_BOXAPI = 'BoxApi',
  81. // The XFF header label - Used to give the API better information for uploads, rate-limiting, etc.
  82. HEADER_XFF = 'X-Forwarded-For',
  83. // As-User header
  84. HEADER_AS_USER = 'As-User',
  85. // Range of SUCCESS http status codes
  86. HTTP_STATUS_CODE_SUCCESS_BLOCK_RANGE = [200, 299];
  87. /**
  88. * Build the 'Authorization' Header for the API
  89. *
  90. * @param {string} accessToken An OAuth Access Token
  91. * @returns {string} A properly formatted 'Authorization' header
  92. * @private
  93. */
  94. function buildAuthorizationHeader(accessToken: string) {
  95. return HEADER_AUTHORIZATION_PREFIX + accessToken;
  96. }
  97. /**
  98. * Returns true iff the response is a 401 UNAUTHORIZED that is caused by an expired access token.
  99. * @param {APIRequest~ResponseObject} response - The response returned by an APIRequestManager request
  100. * @returns {boolean} - true iff the response is a 401 UNAUTHORIZED caused by an expired access token
  101. * @private
  102. */
  103. function isUnauthorizedDueToExpiredAccessToken(response: any /* FIXME */) {
  104. // There are three cases to consider:
  105. // 1) The response body is a Buffer. This indicates that the request was malformed (i.e. malformed url) so return false.
  106. // 2) The status code is UNAUTHORIZED and the response body is an empty object or null. This indicates that the access tokens are expired, so return true.
  107. // 3) The status code is UNAUTHORIZED and the response body is a non-empty object. This indicates that the 401 was returned for some reason other
  108. // than expired tokens, so return false.
  109. if (Buffer.isBuffer(response.body)) {
  110. return false;
  111. }
  112. var isResponseStatusCodeUnauthorized =
  113. response.statusCode === httpStatusCodes.UNAUTHORIZED,
  114. isResponseBodyEmpty =
  115. !response.body || Object.getOwnPropertyNames(response.body).length === 0;
  116. return isResponseStatusCodeUnauthorized && isResponseBodyEmpty;
  117. }
  118. /**
  119. * Returns a full URL. If the url argument begins with http:// or https://, then url is simply returned.
  120. * Otherwise, the defaultBasePath is prepended to url and returned.
  121. *
  122. * @param {string} defaultBasePath The default root URL that will be prepended if `url` is a partial url
  123. * @param {string} url A full or partial URL that will be used to construct the final URL
  124. * @returns {string} The final URL
  125. * @private
  126. */
  127. function getFullURL(defaultBasePath: string, url: string) {
  128. if (/^https?:\/\//.test(url)) {
  129. return url;
  130. }
  131. return defaultBasePath + url;
  132. }
  133. /**
  134. * Construct the X-Box-UA header to send analytics identifiers
  135. * @param {Object} [client] Analytics client information
  136. * @returns {string} The header value
  137. */
  138. function constructBoxUAHeader(client: any /* FIXME */) {
  139. var analyticsIdentifiers = {
  140. agent: `box-node-sdk/${pkg.version}`,
  141. env: `Node/${process.version.replace('v', '')}`,
  142. } as Record<string, string>;
  143. if (client) {
  144. analyticsIdentifiers.client = `${client.name}/${client.version}`;
  145. }
  146. return Object.keys(analyticsIdentifiers)
  147. .map((k) => `${k}=${analyticsIdentifiers[k]}`)
  148. .join('; ');
  149. }
  150. class BoxClient {
  151. _session: APISession;
  152. _requestManager: APIRequestManager;
  153. _customHeaders: any;
  154. _baseURL: any;
  155. _uploadBaseURL: any;
  156. _uploadRequestTimeoutMS: any;
  157. _useIterators: any;
  158. _analyticsClient: any;
  159. _tokenOptions: any;
  160. ai: AI;
  161. users: any;
  162. files: Files;
  163. fileRequests: FileRequestsManager;
  164. folders: Folders;
  165. comments: any;
  166. collaborations: Collaborations;
  167. groups: any;
  168. sharedItems: any;
  169. metadata: any;
  170. collections: any;
  171. events: Events;
  172. search: any;
  173. tasks: any;
  174. trash: any;
  175. enterprise: any;
  176. legalHoldPolicies: any;
  177. weblinks: any;
  178. retentionPolicies: any;
  179. devicePins: any;
  180. webhooks: Webhooks;
  181. recentItems: any;
  182. collaborationAllowlist: any;
  183. termsOfService: any;
  184. storagePolicies: any;
  185. signRequests: SignRequests;
  186. signTemplates: SignTemplates;
  187. shieldInformationBarriers: ShieldInformationBarriers;
  188. shieldInformationBarrierSegments: ShieldInformationBarrierSegments;
  189. shieldInformationBarrierSegmentMembers: ShieldInformationBarrierSegmentMembers;
  190. shieldInformationBarrierSegmentRestrictions: ShieldInformationBarrierSegmentRestrictions;
  191. shieldInformationBarrierReports: ShieldInformationBarrierReports;
  192. integrationMappings: IntegrationMappings;
  193. /* prototype properties assigned below the class declaration */
  194. collaborationRoles!: Record<string, CollaborationRole>;
  195. itemTypes!: Record<string, ItemType>;
  196. accessLevels!: Record<string, AccessLevel>;
  197. CURRENT_USER_ID!: string;
  198. /**
  199. * The BoxClient can make API calls on behalf of a valid API Session. It is responsible
  200. * for formatting the requests and handling the response. Its goal is to deliver
  201. * sensible results to the user.
  202. *
  203. * @param {APISession} apiSession An initialized API Session, used to get/revoke tokens and handle
  204. * unauthorized responses from the API.
  205. * @param {Config} config The SDK configuration options
  206. * @param {APIRequestManager} requestManager The API Request Manager
  207. * @constructor
  208. */
  209. constructor(
  210. apiSession: APISession,
  211. config: any /* FIXME */,
  212. requestManager: APIRequestManager
  213. ) {
  214. // the API Session used by the client for authentication
  215. this._session = apiSession;
  216. // Attach a request manager instance for making requests
  217. this._requestManager = requestManager;
  218. // An object of custom headers to apply to every request. Modified via BoxClient.setCustomHeader().
  219. this._customHeaders = {};
  220. // Attach the configured properties
  221. this._baseURL = util.format('%s/%s', config.apiRootURL, config.apiVersion);
  222. this._uploadBaseURL = util.format(
  223. '%s/%s',
  224. config.uploadAPIRootURL,
  225. config.apiVersion
  226. );
  227. this._uploadRequestTimeoutMS = config.uploadRequestTimeoutMS;
  228. this._useIterators = config.iterators;
  229. this._analyticsClient = config.analyticsClient;
  230. // Attach API Resource Managers
  231. this.ai = new AI(this);
  232. this.users = new Users(this);
  233. this.files = new Files(this);
  234. this.fileRequests = new FileRequestsManager(this);
  235. this.folders = new Folders(this);
  236. this.comments = new Comments(this);
  237. this.collaborations = new Collaborations(this);
  238. this.groups = new Groups(this);
  239. this.sharedItems = new SharedItems(this);
  240. this.metadata = new Metadata(this);
  241. this.collections = new Collections(this);
  242. this.events = new Events(this);
  243. this.search = new Search(this);
  244. this.tasks = new Tasks(this);
  245. this.trash = new Trash(this);
  246. this.enterprise = new Enterprise(this);
  247. this.legalHoldPolicies = new LegalHoldPolicies(this);
  248. this.weblinks = new WebLinks(this);
  249. this.retentionPolicies = new RetentionPolicies(this);
  250. this.devicePins = new DevicePins(this);
  251. this.webhooks = new Webhooks(this);
  252. this.recentItems = new RecentItems(this);
  253. this.collaborationAllowlist = new CollaborationAllowlist(this);
  254. this.termsOfService = new TermsOfService(this);
  255. this.storagePolicies = new StoragePolicies(this);
  256. this.signRequests = new SignRequests(this);
  257. this.signTemplates = new SignTemplates(this);
  258. this.shieldInformationBarriers = new ShieldInformationBarriers(this);
  259. this.shieldInformationBarrierSegments = new ShieldInformationBarrierSegments(this);
  260. this.shieldInformationBarrierSegmentMembers = new ShieldInformationBarrierSegmentMembers(this);
  261. this.shieldInformationBarrierSegmentRestrictions = new ShieldInformationBarrierSegmentRestrictions(this);
  262. this.shieldInformationBarrierReports = new ShieldInformationBarrierReports(this);
  263. this.integrationMappings = new IntegrationMappings(this);
  264. }
  265. /**
  266. * Returns an object containing the given headers as well as other headers (like the authorization header and
  267. * custom headers) that should be included in a request.
  268. * @param {?Object} callerHeaders - headers that the caller wishes to include in the request. This method will not
  269. * override these headers with its own. Thus, if all the headers that this method was planning to add are already
  270. * specified here, this method will return an object with exactly the same headers.
  271. * @param {string} accessToken - the access token that will be used to make the request
  272. * @returns {Object} - a new object with the headers needed for the request
  273. * @private
  274. */
  275. _createHeadersForRequest(callerHeaders: object | null, accessToken: string) {
  276. var headers: Record<string, string> = {};
  277. // 'Authorization' - contains your valid access token for authorization
  278. headers[HEADER_AUTHORIZATION] = buildAuthorizationHeader(accessToken);
  279. // We copy our own custom headers (XFF, BoxApi, etc.) before copying over the caller-specified headers so that
  280. // the caller-specified headers will take precedence.
  281. Object.assign(headers, this._customHeaders, callerHeaders);
  282. // Add analytics headers last so they cannot be overwritten
  283. Object.assign(headers, {
  284. 'X-Box-UA': constructBoxUAHeader(this._analyticsClient),
  285. });
  286. return headers;
  287. }
  288. /**
  289. * Makes an API request to the Box API on behalf of the client. Before executing
  290. * the request, it first ensures the user has usable tokens. Will be called again
  291. * if the request returns a temporary error. Will propogate error if request returns
  292. * a permanent error, or if usable tokens are not available.
  293. *
  294. * @param {Object} params - Request lib params to configure the request
  295. * @param {Function} [callback] - passed response data
  296. * @returns {Promise} Promise resolving to the response
  297. * @private
  298. */
  299. _makeRequest(params: any /* FIXME */, callback?: Function) {
  300. var promise = this._session
  301. .getAccessToken(this._tokenOptions)
  302. .then((accessToken: string) => {
  303. params.headers = this._createHeadersForRequest(
  304. params.headers,
  305. accessToken
  306. );
  307. if (params.streaming) {
  308. // streaming is specific to the SDK, so delete it from params before continuing
  309. delete params.streaming;
  310. var responseStream =
  311. this._requestManager.makeStreamingRequest(params);
  312. // Listen to 'response' event, so we can cleanup the token store in case when the request is unauthorized
  313. // due to expired access token
  314. responseStream.on('response', (response: any /* FIXME */) => {
  315. if (isUnauthorizedDueToExpiredAccessToken(response)) {
  316. var expiredTokensError = errors.buildAuthError(response);
  317. // Give the session a chance to handle the error (ex: a persistent session will clear the token store)
  318. if (this._session.handleExpiredTokensError) {
  319. this._session.handleExpiredTokensError(expiredTokensError);
  320. }
  321. }
  322. });
  323. return responseStream;
  324. }
  325. // Make the request to Box, and perform standard response handling
  326. return this._requestManager.makeRequest(params);
  327. });
  328. return promise
  329. .then((response: any /* FIXME */) => {
  330. if (!response.statusCode) {
  331. // Response is not yet complete, and is just a stream that will return the response later
  332. // Just return the stream, since it doesn't need further response handling
  333. return response;
  334. }
  335. if (isUnauthorizedDueToExpiredAccessToken(response)) {
  336. var expiredTokensError = errors.buildAuthError(response);
  337. // Give the session a chance to handle the error (ex: a persistent session will clear the token store)
  338. if (this._session.handleExpiredTokensError) {
  339. return this._session.handleExpiredTokensError(expiredTokensError);
  340. }
  341. throw expiredTokensError;
  342. }
  343. return response;
  344. })
  345. .asCallback(callback);
  346. }
  347. /**
  348. * Set a custom header. A custom header is applied to every request for the life of the client. To
  349. * remove a header, set it's value to null.
  350. *
  351. * @param {string} header The name of the custom header to set.
  352. * @param {*} value The value of the custom header. Set to null to remove the given header.
  353. * @returns {void}
  354. */
  355. setCustomHeader(header: string, value: any) {
  356. if (value) {
  357. this._customHeaders[header] = value;
  358. } else {
  359. delete this._customHeaders[header];
  360. }
  361. }
  362. /**
  363. * Sets the list of requesting IP addresses for the X-Forwarded-For header. Used to give the API
  364. * better information for uploads, rate-limiting, etc.
  365. *
  366. * @param {string[]} ips - Array of IP Addresses
  367. * @returns {void}
  368. */
  369. setIPs(ips: string[]) {
  370. var validIPs = ips.filter((ipString: string) => isIP(ipString)).join(', ');
  371. this.setCustomHeader(HEADER_XFF, validIPs);
  372. this._tokenOptions = { ip: validIPs };
  373. }
  374. /**
  375. * Sets the shared item context on the API Session. Overwrites any current context.
  376. *
  377. * @param {string} url The shared link url
  378. * @param {?string} password The shared link password, null if no password exists.
  379. * @returns {void}
  380. */
  381. setSharedContext(url: string, password: string | null) {
  382. var sharedContextAuthHeader = this.buildSharedItemAuthHeader(url, password);
  383. this.setCustomHeader(HEADER_BOXAPI, sharedContextAuthHeader);
  384. }
  385. /**
  386. * Removes any current shared item context from API Session.
  387. *
  388. * @returns {void}
  389. */
  390. revokeSharedContext() {
  391. this.setCustomHeader(HEADER_BOXAPI, null);
  392. }
  393. /**
  394. * Set up the As-User context, which is used by enterprise admins to
  395. * impersonate their managed users and perform actions on their behalf.
  396. *
  397. * @param {string} userID - The ID of the user to impersonate
  398. * @returns {void}
  399. */
  400. asUser(userID: string) {
  401. this.setCustomHeader(HEADER_AS_USER, userID);
  402. }
  403. /**
  404. * Revoke the As-User context and return to making calls on behalf of the user
  405. * who owns the client's access token.
  406. *
  407. * @returns {void}
  408. */
  409. asSelf() {
  410. this.setCustomHeader(HEADER_AS_USER, null);
  411. }
  412. /**
  413. * Revokes the client's access tokens. The client will no longer be tied to a user
  414. * and will be unable to make calls to the API, rendering it effectively useless.
  415. *
  416. * @param {Function} [callback] Called after revoking, with an error if one existed
  417. * @returns {Promise} A promise resolving when the client's access token is revoked
  418. */
  419. revokeTokens(callback: Function) {
  420. return this._session.revokeTokens(this._tokenOptions).asCallback(callback);
  421. }
  422. /**
  423. * Exchange the client access token for one with lower scope
  424. * @param {string|string[]} scopes The scope(s) requested for the new token
  425. * @param {string} [resource] The absolute URL of an API resource to scope the new token to
  426. * @param {Object} [options] - Optional parameters
  427. * @param {ActorParams} [options.actor] - Optional actor parameters for creating annotator tokens with Token Auth client
  428. * @param {SharedLinkParams} [options.sharedLink] - Optional shared link parameters for creating tokens using shared links
  429. * @param {Function} [callback] Called with the new token
  430. * @returns {Promise<TokenInfo>} A promise resolving to the exchanged token info
  431. */
  432. exchangeToken(
  433. scopes: string | string[],
  434. resource?: string,
  435. options?: Function | object,
  436. callback?: Function
  437. ) {
  438. // Shuffle optional parameters
  439. if (typeof options === 'function') {
  440. callback = options;
  441. options = {};
  442. }
  443. var opts = Object.assign(
  444. { tokenRequestOptions: this._tokenOptions || null },
  445. options
  446. );
  447. return this._session
  448. .exchangeToken(scopes, resource, opts)
  449. .asCallback(callback);
  450. }
  451. /**
  452. * Makes GET request to Box API V2 endpoint
  453. *
  454. * @param {string} path - path to a certain API endpoint (ex: /file)
  455. * @param {?Object} params - object containing parameters for the request, such as query strings and headers
  456. * @param {Function} [callback] - passed final API response or err if request failed
  457. * @returns {void}
  458. */
  459. get(path: string, params?: object | null, callback?: Function) {
  460. var newParams = merge({}, params || {});
  461. newParams.method = 'GET';
  462. newParams.url = getFullURL(this._baseURL, path);
  463. return this._makeRequest(newParams, callback);
  464. }
  465. /**
  466. * Makes POST request to Box API V2 endpoint
  467. *
  468. * @param {string} path - path to a certain API endpoint (ex: /file)
  469. * @param {?Object} params - object containing parameters for the request, such as query strings and headers
  470. * @param {Function} [callback] - passed final API response or err if request failed
  471. * @returns {void}
  472. */
  473. post(path: string, params: object | null, callback?: Function) {
  474. var newParams = merge({}, params || {});
  475. newParams.method = 'POST';
  476. newParams.url = getFullURL(this._baseURL, path);
  477. return this._makeRequest(newParams, callback);
  478. }
  479. /**
  480. * Makes PUT request to Box API V2 endpoint
  481. *
  482. * @param {string} path - path to a certain API endpoint (ex: /file)
  483. * @param {?Object} params - object containing parameters for the request, such as query strings and headers
  484. * @param {Function} callback - passed final API response or err if request failed
  485. * @returns {void}
  486. */
  487. put(path: string, params?: object | null, callback?: Function) {
  488. var newParams = merge({}, params || {});
  489. newParams.method = 'PUT';
  490. newParams.url = getFullURL(this._baseURL, path);
  491. return this._makeRequest(newParams, callback);
  492. }
  493. /**
  494. * Makes DELETE request to Box API V2 endpoint
  495. *
  496. * @param {string} path - path to a certain API endpoint (ex: /file)
  497. * @param {?Object} params - object containing parameters for the request, such as query strings and headers
  498. * @param {Function} callback - passed final API response or err if request failed
  499. * @returns {void}
  500. */
  501. del(path: string, params: object | null, callback?: Function) {
  502. var newParams = merge({}, params || {});
  503. newParams.method = 'DELETE';
  504. newParams.url = getFullURL(this._baseURL, path);
  505. return this._makeRequest(newParams, callback);
  506. }
  507. /**
  508. * Makes an OPTIONS call to a Box API V2 endpoint
  509. *
  510. * @param {string} path - Path to an API endpoint (e.g. /files/content)
  511. * @param {?Object} params - An optional object containing request parameters
  512. * @param {Function} callback - Called with API call results, or err if call failed
  513. * @returns {void}
  514. */
  515. options(path: string, params: object | null, callback?: Function) {
  516. var newParams = merge({}, params || {});
  517. newParams.method = 'OPTIONS';
  518. newParams.url = getFullURL(this._baseURL, path);
  519. return this._makeRequest(newParams, callback);
  520. }
  521. /**
  522. * Makes a POST call to a Box API V2 upload endpoint
  523. * @param {string} path - path to an upload API endpoint
  524. * @param {?Object} params - an optional object containing request parameters
  525. * @param {?Object} formData - multipart form data to include in the upload request {@see https://github.com/mikeal/request#multipartform-data-multipart-form-uploads}
  526. * @param {Function} callback - called with API call results, or an error if the call failed
  527. * @returns {void}
  528. */
  529. upload(
  530. path: string,
  531. params: object | null,
  532. formData: object | null,
  533. callback: Function
  534. ) {
  535. var defaults = {
  536. method: 'POST',
  537. };
  538. var newParams = merge(defaults, params || {});
  539. newParams.url = getFullURL(this._uploadBaseURL, path);
  540. newParams.formData = formData;
  541. newParams.timeout = this._uploadRequestTimeoutMS;
  542. return this._makeRequest(newParams, callback);
  543. }
  544. /**
  545. * Build the 'BoxApi' Header used for authenticating access to a shared item
  546. *
  547. * @param {string} url The shared link url
  548. * @param {string} [password] The shared link password
  549. * @returns {string} A properly formatted 'BoxApi' header
  550. */
  551. buildSharedItemAuthHeader(url: string, password: string | null) {
  552. var encodedURL = encodeURIComponent(url),
  553. encodedPassword = encodeURIComponent(password ?? '');
  554. if (password) {
  555. return util.format(
  556. 'shared_link=%s&shared_link_password=%s',
  557. encodedURL,
  558. encodedPassword
  559. );
  560. }
  561. return util.format('shared_link=%s', encodedURL);
  562. }
  563. /**
  564. * Return a callback that properly handles a successful response code by passing the response
  565. * body to the original callback. Any request error or unsuccessful response codes are propagated
  566. * back to the callback as errors. This is the standard behavior of most endpoints.
  567. *
  568. * @param {Function} callback The original callback given by the consumer
  569. * @returns {?Function} A new callback that processes the response before passing it to the callback.
  570. */
  571. defaultResponseHandler(callback: Function) {
  572. var self = this;
  573. if (!callback) {
  574. return null;
  575. }
  576. return function (err: any, response: any /* FIXME */) {
  577. // Error with Request
  578. if (err) {
  579. callback(err);
  580. return;
  581. }
  582. // Successful Response
  583. if (
  584. response.statusCode >= HTTP_STATUS_CODE_SUCCESS_BLOCK_RANGE[0] &&
  585. response.statusCode <= HTTP_STATUS_CODE_SUCCESS_BLOCK_RANGE[1]
  586. ) {
  587. if (self._useIterators && PagingIterator.isIterable(response)) {
  588. callback(null, new PagingIterator(response, self));
  589. return;
  590. }
  591. callback(null, response.body);
  592. return;
  593. }
  594. // Unexpected Response
  595. callback(errors.buildUnexpectedResponseError(response));
  596. };
  597. }
  598. /**
  599. * Wrap a client method with the default handler for both callback and promise styles
  600. * @param {Function} method The client method (e.g. client.get)
  601. * @returns {Function} The wrapped method
  602. */
  603. wrapWithDefaultHandler(method: Function): Function {
  604. var self = this;
  605. return function wrappedClientMethod(/* arguments */) {
  606. // Check if the last argument is a callback
  607. var lastArg = arguments[arguments.length - 1],
  608. callback;
  609. if (typeof lastArg === 'function') {
  610. callback = self.defaultResponseHandler(lastArg);
  611. arguments[arguments.length - 1] = callback;
  612. }
  613. var ret = method.apply(self, arguments);
  614. if (ret instanceof Promise) {
  615. ret = ret.then((response) => {
  616. if (
  617. response.statusCode >= HTTP_STATUS_CODE_SUCCESS_BLOCK_RANGE[0] &&
  618. response.statusCode <= HTTP_STATUS_CODE_SUCCESS_BLOCK_RANGE[1]
  619. ) {
  620. if (self._useIterators && PagingIterator.isIterable(response)) {
  621. return new PagingIterator(response, self);
  622. }
  623. return response.body;
  624. }
  625. throw errors.buildUnexpectedResponseError(response);
  626. });
  627. }
  628. if (callback) {
  629. // If the callback will handle any errors, don't worry about the promise
  630. ret.suppressUnhandledRejections();
  631. }
  632. return ret;
  633. };
  634. }
  635. /**
  636. * Add a SDK plugin. Warning: This will modify the box-client interface and can override existing properties.
  637. * @param {string} name Plugin name. Will be accessible via client.<plugin-name>
  638. * @param {Function} plugin The SDK plugin to add
  639. * @param {Object} [options] Plugin-specific options
  640. * @returns {void}
  641. * @throws Will throw an error if plugin name matches an existing method on box-client
  642. */
  643. plug(name: string, plugin: Function, options: object) {
  644. options = options || {};
  645. if (name in this && typeof (this as any)[name] === 'function') {
  646. throw new Error(
  647. 'You cannot define a plugin that overrides an existing method on the client'
  648. );
  649. }
  650. // Create plugin and export plugin onto client.
  651. (this as any)[name] = plugin(this, options);
  652. }
  653. }
  654. // ------------------------------------------------------------------------------
  655. // Public
  656. // ------------------------------------------------------------------------------
  657. /**
  658. * Enum of valid collaboration roles
  659. *
  660. * @readonly
  661. * @enum {CollaborationRole}
  662. */
  663. BoxClient.prototype.collaborationRoles = {
  664. EDITOR: 'editor',
  665. VIEWER: 'viewer',
  666. PREVIEWER: 'previewer',
  667. UPLOADER: 'uploader',
  668. PREVIEWER_UPLOADER: 'previewer uploader',
  669. VIEWER_UPLOADER: 'viewer uploader',
  670. CO_OWNER: 'co-owner',
  671. OWNER: 'owner',
  672. };
  673. /**
  674. * Enum of Box item types
  675. *
  676. * @readonly
  677. * @enum {ItemType}
  678. */
  679. BoxClient.prototype.itemTypes = {
  680. FILE: 'file',
  681. FOLDER: 'folder',
  682. };
  683. /**
  684. * Enum of valid values for setting different access levels. To be used when
  685. * creating and editting shared links, upload emails, etc.
  686. *
  687. * @readonly
  688. * @type {AccessLevel}
  689. */
  690. BoxClient.prototype.accessLevels = {
  691. OPEN: { access: 'open' },
  692. COLLABORATORS: { access: 'collaborators' },
  693. COMPANY: { access: 'company' },
  694. DEFAULT: {},
  695. DISABLED: null,
  696. };
  697. /** @const {string} */
  698. BoxClient.prototype.CURRENT_USER_ID = Users.prototype.CURRENT_USER_ID;
  699. // ------------------------------------------------------------------------------
  700. // Public
  701. // ------------------------------------------------------------------------------
  702. /**
  703. * @module box-node-sdk/lib/box-client
  704. * @see {@Link BoxClient}
  705. */
  706. export = BoxClient;