UNPKG

box-node-sdk

Version:

Official SDK for Box Plaform APIs

596 lines 29.5 kB
"use strict"; /** * @fileoverview Box API Client */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var bluebird_1 = require("bluebird"); // ------------------------------------------------------------------------------ // API Resource Managers // ------------------------------------------------------------------------------ var ai_generated_1 = __importDefault(require("./managers/ai.generated")); var collaboration_allowlist_1 = __importDefault(require("./managers/collaboration-allowlist")); var collaborations_1 = __importDefault(require("./managers/collaborations")); var collections_1 = __importDefault(require("./managers/collections")); var comments_1 = __importDefault(require("./managers/comments")); var device_pins_1 = __importDefault(require("./managers/device-pins")); var enterprise_1 = __importDefault(require("./managers/enterprise")); var events_1 = __importDefault(require("./managers/events")); var files_1 = __importDefault(require("./managers/files")); var folders_1 = __importDefault(require("./managers/folders")); var groups_1 = __importDefault(require("./managers/groups")); var legal_hold_policies_1 = __importDefault(require("./managers/legal-hold-policies")); var metadata_1 = __importDefault(require("./managers/metadata")); var recent_items_1 = __importDefault(require("./managers/recent-items")); var retention_policies_1 = __importDefault(require("./managers/retention-policies")); var search_1 = __importDefault(require("./managers/search")); var shared_items_1 = __importDefault(require("./managers/shared-items")); var sign_requests_generated_1 = __importDefault(require("./managers/sign-requests.generated")); var sign_templates_generated_1 = __importDefault(require("./managers/sign-templates.generated")); var storage_policies_1 = __importDefault(require("./managers/storage-policies")); var tasks_1 = __importDefault(require("./managers/tasks")); var terms_of_service_1 = __importDefault(require("./managers/terms-of-service")); var trash_1 = __importDefault(require("./managers/trash")); var users_1 = __importDefault(require("./managers/users")); var web_links_1 = __importDefault(require("./managers/web-links")); var webhooks_1 = __importDefault(require("./managers/webhooks")); var file_requests_manager_1 = __importDefault(require("./managers/file-requests-manager")); var shield_information_barriers_generated_1 = __importDefault(require("./managers/shield-information-barriers.generated")); var shield_information_barrier_segments_generated_1 = __importDefault(require("./managers/shield-information-barrier-segments.generated")); var shield_information_barrier_segment_members_generated_1 = __importDefault(require("./managers/shield-information-barrier-segment-members.generated")); var shield_information_barrier_segment_restrictions_generated_1 = __importDefault(require("./managers/shield-information-barrier-segment-restrictions.generated")); var shield_information_barrier_reports_generated_1 = __importDefault(require("./managers/shield-information-barrier-reports.generated")); var integration_mappings_1 = __importDefault(require("./managers/integration-mappings")); // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ var util = require('util'), qs = require('querystring'), errors = require('./util/errors'), httpStatusCodes = require('http-status'), isIP = require('net').isIP, merge = require('merge-options'), PagingIterator = require('./util/paging-iterator'), pkg = require('../package.json'); // ------------------------------------------------------------------------------ // Private // ------------------------------------------------------------------------------ // The Authorization header label var HEADER_AUTHORIZATION = 'Authorization', // Prefix our token with this string in the Authorization header HEADER_AUTHORIZATION_PREFIX = 'Bearer ', // The 'BoxApi' header label HEADER_BOXAPI = 'BoxApi', // The XFF header label - Used to give the API better information for uploads, rate-limiting, etc. HEADER_XFF = 'X-Forwarded-For', // As-User header HEADER_AS_USER = 'As-User', // Range of SUCCESS http status codes HTTP_STATUS_CODE_SUCCESS_BLOCK_RANGE = [200, 299]; /** * Build the 'Authorization' Header for the API * * @param {string} accessToken An OAuth Access Token * @returns {string} A properly formatted 'Authorization' header * @private */ function buildAuthorizationHeader(accessToken) { return HEADER_AUTHORIZATION_PREFIX + accessToken; } /** * Returns true iff the response is a 401 UNAUTHORIZED that is caused by an expired access token. * @param {APIRequest~ResponseObject} response - The response returned by an APIRequestManager request * @returns {boolean} - true iff the response is a 401 UNAUTHORIZED caused by an expired access token * @private */ function isUnauthorizedDueToExpiredAccessToken(response /* FIXME */) { // There are three cases to consider: // 1) The response body is a Buffer. This indicates that the request was malformed (i.e. malformed url) so return false. // 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. // 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 // than expired tokens, so return false. if (Buffer.isBuffer(response.body)) { return false; } var isResponseStatusCodeUnauthorized = response.statusCode === httpStatusCodes.UNAUTHORIZED, isResponseBodyEmpty = !response.body || Object.getOwnPropertyNames(response.body).length === 0; return isResponseStatusCodeUnauthorized && isResponseBodyEmpty; } /** * Returns a full URL. If the url argument begins with http:// or https://, then url is simply returned. * Otherwise, the defaultBasePath is prepended to url and returned. * * @param {string} defaultBasePath The default root URL that will be prepended if `url` is a partial url * @param {string} url A full or partial URL that will be used to construct the final URL * @returns {string} The final URL * @private */ function getFullURL(defaultBasePath, url) { if (/^https?:\/\//.test(url)) { return url; } return defaultBasePath + url; } /** * Construct the X-Box-UA header to send analytics identifiers * @param {Object} [client] Analytics client information * @returns {string} The header value */ function constructBoxUAHeader(client /* FIXME */) { var analyticsIdentifiers = { agent: "box-node-sdk/".concat(pkg.version), env: "Node/".concat(process.version.replace('v', '')), }; if (client) { analyticsIdentifiers.client = "".concat(client.name, "/").concat(client.version); } return Object.keys(analyticsIdentifiers) .map(function (k) { return "".concat(k, "=").concat(analyticsIdentifiers[k]); }) .join('; '); } var BoxClient = /** @class */ (function () { /** * The BoxClient can make API calls on behalf of a valid API Session. It is responsible * for formatting the requests and handling the response. Its goal is to deliver * sensible results to the user. * * @param {APISession} apiSession An initialized API Session, used to get/revoke tokens and handle * unauthorized responses from the API. * @param {Config} config The SDK configuration options * @param {APIRequestManager} requestManager The API Request Manager * @constructor */ function BoxClient(apiSession, config /* FIXME */, requestManager) { // the API Session used by the client for authentication this._session = apiSession; // Attach a request manager instance for making requests this._requestManager = requestManager; // An object of custom headers to apply to every request. Modified via BoxClient.setCustomHeader(). this._customHeaders = {}; // Attach the configured properties this._baseURL = util.format('%s/%s', config.apiRootURL, config.apiVersion); this._uploadBaseURL = util.format('%s/%s', config.uploadAPIRootURL, config.apiVersion); this._uploadRequestTimeoutMS = config.uploadRequestTimeoutMS; this._useIterators = config.iterators; this._analyticsClient = config.analyticsClient; // Attach API Resource Managers this.ai = new ai_generated_1.default(this); this.users = new users_1.default(this); this.files = new files_1.default(this); this.fileRequests = new file_requests_manager_1.default(this); this.folders = new folders_1.default(this); this.comments = new comments_1.default(this); this.collaborations = new collaborations_1.default(this); this.groups = new groups_1.default(this); this.sharedItems = new shared_items_1.default(this); this.metadata = new metadata_1.default(this); this.collections = new collections_1.default(this); this.events = new events_1.default(this); this.search = new search_1.default(this); this.tasks = new tasks_1.default(this); this.trash = new trash_1.default(this); this.enterprise = new enterprise_1.default(this); this.legalHoldPolicies = new legal_hold_policies_1.default(this); this.weblinks = new web_links_1.default(this); this.retentionPolicies = new retention_policies_1.default(this); this.devicePins = new device_pins_1.default(this); this.webhooks = new webhooks_1.default(this); this.recentItems = new recent_items_1.default(this); this.collaborationAllowlist = new collaboration_allowlist_1.default(this); this.termsOfService = new terms_of_service_1.default(this); this.storagePolicies = new storage_policies_1.default(this); this.signRequests = new sign_requests_generated_1.default(this); this.signTemplates = new sign_templates_generated_1.default(this); this.shieldInformationBarriers = new shield_information_barriers_generated_1.default(this); this.shieldInformationBarrierSegments = new shield_information_barrier_segments_generated_1.default(this); this.shieldInformationBarrierSegmentMembers = new shield_information_barrier_segment_members_generated_1.default(this); this.shieldInformationBarrierSegmentRestrictions = new shield_information_barrier_segment_restrictions_generated_1.default(this); this.shieldInformationBarrierReports = new shield_information_barrier_reports_generated_1.default(this); this.integrationMappings = new integration_mappings_1.default(this); } /** * Returns an object containing the given headers as well as other headers (like the authorization header and * custom headers) that should be included in a request. * @param {?Object} callerHeaders - headers that the caller wishes to include in the request. This method will not * override these headers with its own. Thus, if all the headers that this method was planning to add are already * specified here, this method will return an object with exactly the same headers. * @param {string} accessToken - the access token that will be used to make the request * @returns {Object} - a new object with the headers needed for the request * @private */ BoxClient.prototype._createHeadersForRequest = function (callerHeaders, accessToken) { var headers = {}; // 'Authorization' - contains your valid access token for authorization headers[HEADER_AUTHORIZATION] = buildAuthorizationHeader(accessToken); // We copy our own custom headers (XFF, BoxApi, etc.) before copying over the caller-specified headers so that // the caller-specified headers will take precedence. Object.assign(headers, this._customHeaders, callerHeaders); // Add analytics headers last so they cannot be overwritten Object.assign(headers, { 'X-Box-UA': constructBoxUAHeader(this._analyticsClient), }); return headers; }; /** * Makes an API request to the Box API on behalf of the client. Before executing * the request, it first ensures the user has usable tokens. Will be called again * if the request returns a temporary error. Will propogate error if request returns * a permanent error, or if usable tokens are not available. * * @param {Object} params - Request lib params to configure the request * @param {Function} [callback] - passed response data * @returns {Promise} Promise resolving to the response * @private */ BoxClient.prototype._makeRequest = function (params /* FIXME */, callback) { var _this = this; var promise = this._session .getAccessToken(this._tokenOptions) .then(function (accessToken) { params.headers = _this._createHeadersForRequest(params.headers, accessToken); if (params.streaming) { // streaming is specific to the SDK, so delete it from params before continuing delete params.streaming; var responseStream = _this._requestManager.makeStreamingRequest(params); // Listen to 'response' event, so we can cleanup the token store in case when the request is unauthorized // due to expired access token responseStream.on('response', function (response /* FIXME */) { if (isUnauthorizedDueToExpiredAccessToken(response)) { var expiredTokensError = errors.buildAuthError(response); // Give the session a chance to handle the error (ex: a persistent session will clear the token store) if (_this._session.handleExpiredTokensError) { _this._session.handleExpiredTokensError(expiredTokensError); } } }); return responseStream; } // Make the request to Box, and perform standard response handling return _this._requestManager.makeRequest(params); }); return promise .then(function (response /* FIXME */) { if (!response.statusCode) { // Response is not yet complete, and is just a stream that will return the response later // Just return the stream, since it doesn't need further response handling return response; } if (isUnauthorizedDueToExpiredAccessToken(response)) { var expiredTokensError = errors.buildAuthError(response); // Give the session a chance to handle the error (ex: a persistent session will clear the token store) if (_this._session.handleExpiredTokensError) { return _this._session.handleExpiredTokensError(expiredTokensError); } throw expiredTokensError; } return response; }) .asCallback(callback); }; /** * Set a custom header. A custom header is applied to every request for the life of the client. To * remove a header, set it's value to null. * * @param {string} header The name of the custom header to set. * @param {*} value The value of the custom header. Set to null to remove the given header. * @returns {void} */ BoxClient.prototype.setCustomHeader = function (header, value) { if (value) { this._customHeaders[header] = value; } else { delete this._customHeaders[header]; } }; /** * Sets the list of requesting IP addresses for the X-Forwarded-For header. Used to give the API * better information for uploads, rate-limiting, etc. * * @param {string[]} ips - Array of IP Addresses * @returns {void} */ BoxClient.prototype.setIPs = function (ips) { var validIPs = ips.filter(function (ipString) { return isIP(ipString); }).join(', '); this.setCustomHeader(HEADER_XFF, validIPs); this._tokenOptions = { ip: validIPs }; }; /** * Sets the shared item context on the API Session. Overwrites any current context. * * @param {string} url The shared link url * @param {?string} password The shared link password, null if no password exists. * @returns {void} */ BoxClient.prototype.setSharedContext = function (url, password) { var sharedContextAuthHeader = this.buildSharedItemAuthHeader(url, password); this.setCustomHeader(HEADER_BOXAPI, sharedContextAuthHeader); }; /** * Removes any current shared item context from API Session. * * @returns {void} */ BoxClient.prototype.revokeSharedContext = function () { this.setCustomHeader(HEADER_BOXAPI, null); }; /** * Set up the As-User context, which is used by enterprise admins to * impersonate their managed users and perform actions on their behalf. * * @param {string} userID - The ID of the user to impersonate * @returns {void} */ BoxClient.prototype.asUser = function (userID) { this.setCustomHeader(HEADER_AS_USER, userID); }; /** * Revoke the As-User context and return to making calls on behalf of the user * who owns the client's access token. * * @returns {void} */ BoxClient.prototype.asSelf = function () { this.setCustomHeader(HEADER_AS_USER, null); }; /** * Revokes the client's access tokens. The client will no longer be tied to a user * and will be unable to make calls to the API, rendering it effectively useless. * * @param {Function} [callback] Called after revoking, with an error if one existed * @returns {Promise} A promise resolving when the client's access token is revoked */ BoxClient.prototype.revokeTokens = function (callback) { return this._session.revokeTokens(this._tokenOptions).asCallback(callback); }; /** * Exchange the client access token for one with lower scope * @param {string|string[]} scopes The scope(s) requested for the new token * @param {string} [resource] The absolute URL of an API resource to scope the new token to * @param {Object} [options] - Optional parameters * @param {ActorParams} [options.actor] - Optional actor parameters for creating annotator tokens with Token Auth client * @param {SharedLinkParams} [options.sharedLink] - Optional shared link parameters for creating tokens using shared links * @param {Function} [callback] Called with the new token * @returns {Promise<TokenInfo>} A promise resolving to the exchanged token info */ BoxClient.prototype.exchangeToken = function (scopes, resource, options, callback) { // Shuffle optional parameters if (typeof options === 'function') { callback = options; options = {}; } var opts = Object.assign({ tokenRequestOptions: this._tokenOptions || null }, options); return this._session .exchangeToken(scopes, resource, opts) .asCallback(callback); }; /** * Makes GET request to Box API V2 endpoint * * @param {string} path - path to a certain API endpoint (ex: /file) * @param {?Object} params - object containing parameters for the request, such as query strings and headers * @param {Function} [callback] - passed final API response or err if request failed * @returns {void} */ BoxClient.prototype.get = function (path, params, callback) { var newParams = merge({}, params || {}); newParams.method = 'GET'; newParams.url = getFullURL(this._baseURL, path); return this._makeRequest(newParams, callback); }; /** * Makes POST request to Box API V2 endpoint * * @param {string} path - path to a certain API endpoint (ex: /file) * @param {?Object} params - object containing parameters for the request, such as query strings and headers * @param {Function} [callback] - passed final API response or err if request failed * @returns {void} */ BoxClient.prototype.post = function (path, params, callback) { var newParams = merge({}, params || {}); newParams.method = 'POST'; newParams.url = getFullURL(this._baseURL, path); return this._makeRequest(newParams, callback); }; /** * Makes PUT request to Box API V2 endpoint * * @param {string} path - path to a certain API endpoint (ex: /file) * @param {?Object} params - object containing parameters for the request, such as query strings and headers * @param {Function} callback - passed final API response or err if request failed * @returns {void} */ BoxClient.prototype.put = function (path, params, callback) { var newParams = merge({}, params || {}); newParams.method = 'PUT'; newParams.url = getFullURL(this._baseURL, path); return this._makeRequest(newParams, callback); }; /** * Makes DELETE request to Box API V2 endpoint * * @param {string} path - path to a certain API endpoint (ex: /file) * @param {?Object} params - object containing parameters for the request, such as query strings and headers * @param {Function} callback - passed final API response or err if request failed * @returns {void} */ BoxClient.prototype.del = function (path, params, callback) { var newParams = merge({}, params || {}); newParams.method = 'DELETE'; newParams.url = getFullURL(this._baseURL, path); return this._makeRequest(newParams, callback); }; /** * Makes an OPTIONS call to a Box API V2 endpoint * * @param {string} path - Path to an API endpoint (e.g. /files/content) * @param {?Object} params - An optional object containing request parameters * @param {Function} callback - Called with API call results, or err if call failed * @returns {void} */ BoxClient.prototype.options = function (path, params, callback) { var newParams = merge({}, params || {}); newParams.method = 'OPTIONS'; newParams.url = getFullURL(this._baseURL, path); return this._makeRequest(newParams, callback); }; /** * Makes a POST call to a Box API V2 upload endpoint * @param {string} path - path to an upload API endpoint * @param {?Object} params - an optional object containing request parameters * @param {?Object} formData - multipart form data to include in the upload request {@see https://github.com/mikeal/request#multipartform-data-multipart-form-uploads} * @param {Function} callback - called with API call results, or an error if the call failed * @returns {void} */ BoxClient.prototype.upload = function (path, params, formData, callback) { var defaults = { method: 'POST', }; var newParams = merge(defaults, params || {}); newParams.url = getFullURL(this._uploadBaseURL, path); newParams.formData = formData; newParams.timeout = this._uploadRequestTimeoutMS; return this._makeRequest(newParams, callback); }; /** * Build the 'BoxApi' Header used for authenticating access to a shared item * * @param {string} url The shared link url * @param {string} [password] The shared link password * @returns {string} A properly formatted 'BoxApi' header */ BoxClient.prototype.buildSharedItemAuthHeader = function (url, password) { var encodedURL = encodeURIComponent(url), encodedPassword = encodeURIComponent(password !== null && password !== void 0 ? password : ''); if (password) { return util.format('shared_link=%s&shared_link_password=%s', encodedURL, encodedPassword); } return util.format('shared_link=%s', encodedURL); }; /** * Return a callback that properly handles a successful response code by passing the response * body to the original callback. Any request error or unsuccessful response codes are propagated * back to the callback as errors. This is the standard behavior of most endpoints. * * @param {Function} callback The original callback given by the consumer * @returns {?Function} A new callback that processes the response before passing it to the callback. */ BoxClient.prototype.defaultResponseHandler = function (callback) { var self = this; if (!callback) { return null; } return function (err, response /* FIXME */) { // Error with Request if (err) { callback(err); return; } // Successful Response if (response.statusCode >= HTTP_STATUS_CODE_SUCCESS_BLOCK_RANGE[0] && response.statusCode <= HTTP_STATUS_CODE_SUCCESS_BLOCK_RANGE[1]) { if (self._useIterators && PagingIterator.isIterable(response)) { callback(null, new PagingIterator(response, self)); return; } callback(null, response.body); return; } // Unexpected Response callback(errors.buildUnexpectedResponseError(response)); }; }; /** * Wrap a client method with the default handler for both callback and promise styles * @param {Function} method The client method (e.g. client.get) * @returns {Function} The wrapped method */ BoxClient.prototype.wrapWithDefaultHandler = function (method) { var self = this; return function wrappedClientMethod( /* arguments */) { // Check if the last argument is a callback var lastArg = arguments[arguments.length - 1], callback; if (typeof lastArg === 'function') { callback = self.defaultResponseHandler(lastArg); arguments[arguments.length - 1] = callback; } var ret = method.apply(self, arguments); if (ret instanceof bluebird_1.Promise) { ret = ret.then(function (response) { if (response.statusCode >= HTTP_STATUS_CODE_SUCCESS_BLOCK_RANGE[0] && response.statusCode <= HTTP_STATUS_CODE_SUCCESS_BLOCK_RANGE[1]) { if (self._useIterators && PagingIterator.isIterable(response)) { return new PagingIterator(response, self); } return response.body; } throw errors.buildUnexpectedResponseError(response); }); } if (callback) { // If the callback will handle any errors, don't worry about the promise ret.suppressUnhandledRejections(); } return ret; }; }; /** * Add a SDK plugin. Warning: This will modify the box-client interface and can override existing properties. * @param {string} name Plugin name. Will be accessible via client.<plugin-name> * @param {Function} plugin The SDK plugin to add * @param {Object} [options] Plugin-specific options * @returns {void} * @throws Will throw an error if plugin name matches an existing method on box-client */ BoxClient.prototype.plug = function (name, plugin, options) { options = options || {}; if (name in this && typeof this[name] === 'function') { throw new Error('You cannot define a plugin that overrides an existing method on the client'); } // Create plugin and export plugin onto client. this[name] = plugin(this, options); }; return BoxClient; }()); // ------------------------------------------------------------------------------ // Public // ------------------------------------------------------------------------------ /** * Enum of valid collaboration roles * * @readonly * @enum {CollaborationRole} */ BoxClient.prototype.collaborationRoles = { EDITOR: 'editor', VIEWER: 'viewer', PREVIEWER: 'previewer', UPLOADER: 'uploader', PREVIEWER_UPLOADER: 'previewer uploader', VIEWER_UPLOADER: 'viewer uploader', CO_OWNER: 'co-owner', OWNER: 'owner', }; /** * Enum of Box item types * * @readonly * @enum {ItemType} */ BoxClient.prototype.itemTypes = { FILE: 'file', FOLDER: 'folder', }; /** * Enum of valid values for setting different access levels. To be used when * creating and editting shared links, upload emails, etc. * * @readonly * @type {AccessLevel} */ BoxClient.prototype.accessLevels = { OPEN: { access: 'open' }, COLLABORATORS: { access: 'collaborators' }, COMPANY: { access: 'company' }, DEFAULT: {}, DISABLED: null, }; /** @const {string} */ BoxClient.prototype.CURRENT_USER_ID = users_1.default.prototype.CURRENT_USER_ID; module.exports = BoxClient; //# sourceMappingURL=box-client.js.map