Source: box-client.ts

/**
 * @fileoverview Box API Client
 */

import { Promise } from 'bluebird';
// ------------------------------------------------------------------------------
// API Resource Managers
// ------------------------------------------------------------------------------
import AI from './managers/ai.generated';
import CollaborationAllowlist from './managers/collaboration-allowlist';
import Collaborations from './managers/collaborations';
import Collections from './managers/collections';
import Comments from './managers/comments';
import DevicePins from './managers/device-pins';
import Enterprise from './managers/enterprise';
import Events from './managers/events';
import Files from './managers/files';
import Folders from './managers/folders';
import Groups from './managers/groups';
import LegalHoldPolicies from './managers/legal-hold-policies';
import Metadata from './managers/metadata';
import RecentItems from './managers/recent-items';
import RetentionPolicies from './managers/retention-policies';
import Search from './managers/search';
import SharedItems from './managers/shared-items';
import SignRequests from './managers/sign-requests.generated';
import SignTemplates from './managers/sign-templates.generated';
import StoragePolicies from './managers/storage-policies';
import Tasks from './managers/tasks';
import TermsOfService from './managers/terms-of-service';
import Trash from './managers/trash';
import Users from './managers/users';
import WebLinks from './managers/web-links';
import Webhooks from './managers/webhooks';
import FileRequestsManager from './managers/file-requests-manager';
import ShieldInformationBarriers from './managers/shield-information-barriers.generated';
import ShieldInformationBarrierSegments from './managers/shield-information-barrier-segments.generated';
import ShieldInformationBarrierSegmentMembers from './managers/shield-information-barrier-segment-members.generated';
import ShieldInformationBarrierSegmentRestrictions from './managers/shield-information-barrier-segment-restrictions.generated';
import ShieldInformationBarrierReports from './managers/shield-information-barrier-reports.generated';
import IntegrationMappings from './managers/integration-mappings';

// ------------------------------------------------------------------------------
// Typedefs and Callbacks
// ------------------------------------------------------------------------------

/**
 * A collaboration role constant
 * @typedef {string} CollaborationRole
 */
type CollaborationRole = string;

/**
 * A Box file or folder type constant
 * @typedef {string} ItemType
 */
type ItemType = 'file' | 'folder';

/**
 * An access level constant. Used for setting and updating shared links, folder upload, etc.
 * @typedef {?Object} AccessLevel
 */
type AccessLevel = object | null /* FIXME */;

type APISession = any /* FIXME */;
type APIRequestManager = any /* FIXME */;

// ------------------------------------------------------------------------------
// 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: string) {
  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: any /* 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: string, url: string) {
  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: any /* FIXME */) {
  var analyticsIdentifiers = {
    agent: `box-node-sdk/${pkg.version}`,
    env: `Node/${process.version.replace('v', '')}`,
  } as Record<string, string>;

  if (client) {
    analyticsIdentifiers.client = `${client.name}/${client.version}`;
  }

  return Object.keys(analyticsIdentifiers)
    .map((k) => `${k}=${analyticsIdentifiers[k]}`)
    .join('; ');
}

class BoxClient {
  _session: APISession;
  _requestManager: APIRequestManager;
  _customHeaders: any;

  _baseURL: any;
  _uploadBaseURL: any;
  _uploadRequestTimeoutMS: any;
  _useIterators: any;
  _analyticsClient: any;
  _tokenOptions: any;

  ai: AI;
  users: any;
  files: Files;
  fileRequests: FileRequestsManager;
  folders: Folders;
  comments: any;
  collaborations: Collaborations;
  groups: any;
  sharedItems: any;
  metadata: any;
  collections: any;
  events: Events;
  search: any;
  tasks: any;
  trash: any;
  enterprise: any;
  legalHoldPolicies: any;
  weblinks: any;
  retentionPolicies: any;
  devicePins: any;
  webhooks: Webhooks;
  recentItems: any;
  collaborationAllowlist: any;
  termsOfService: any;
  storagePolicies: any;
  signRequests: SignRequests;
  signTemplates: SignTemplates;
  shieldInformationBarriers: ShieldInformationBarriers;
  shieldInformationBarrierSegments: ShieldInformationBarrierSegments;
  shieldInformationBarrierSegmentMembers: ShieldInformationBarrierSegmentMembers;
  shieldInformationBarrierSegmentRestrictions: ShieldInformationBarrierSegmentRestrictions;
  shieldInformationBarrierReports: ShieldInformationBarrierReports;
  integrationMappings: IntegrationMappings;

  /* prototype properties assigned below the class declaration */
  collaborationRoles!: Record<string, CollaborationRole>;
  itemTypes!: Record<string, ItemType>;
  accessLevels!: Record<string, AccessLevel>;
  CURRENT_USER_ID!: string;

  /**
   * 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
   */
  constructor(
    apiSession: APISession,
    config: any /* FIXME */,
    requestManager: APIRequestManager
  ) {
    // 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(this);
    this.users = new Users(this);
    this.files = new Files(this);
    this.fileRequests = new FileRequestsManager(this);
    this.folders = new Folders(this);
    this.comments = new Comments(this);
    this.collaborations = new Collaborations(this);
    this.groups = new Groups(this);
    this.sharedItems = new SharedItems(this);
    this.metadata = new Metadata(this);
    this.collections = new Collections(this);
    this.events = new Events(this);
    this.search = new Search(this);
    this.tasks = new Tasks(this);
    this.trash = new Trash(this);
    this.enterprise = new Enterprise(this);
    this.legalHoldPolicies = new LegalHoldPolicies(this);
    this.weblinks = new WebLinks(this);
    this.retentionPolicies = new RetentionPolicies(this);
    this.devicePins = new DevicePins(this);
    this.webhooks = new Webhooks(this);
    this.recentItems = new RecentItems(this);
    this.collaborationAllowlist = new CollaborationAllowlist(this);
    this.termsOfService = new TermsOfService(this);
    this.storagePolicies = new StoragePolicies(this);
    this.signRequests = new SignRequests(this);
    this.signTemplates = new SignTemplates(this);
    this.shieldInformationBarriers = new ShieldInformationBarriers(this);
    this.shieldInformationBarrierSegments =
      new ShieldInformationBarrierSegments(this);
    this.shieldInformationBarrierSegmentMembers =
      new ShieldInformationBarrierSegmentMembers(this);
    this.shieldInformationBarrierSegmentRestrictions =
      new ShieldInformationBarrierSegmentRestrictions(this);
    this.shieldInformationBarrierReports = new ShieldInformationBarrierReports(
      this
    );
    this.integrationMappings = new IntegrationMappings(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
   */
  _createHeadersForRequest(callerHeaders: object | null, accessToken: string) {
    var headers: Record<string, string> = {};

    // '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
   */
  _makeRequest(params: any /* FIXME */, callback?: Function) {
    var promise = this._session
      .getAccessToken(this._tokenOptions)
      .then((accessToken: string) => {
        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', (response: any /* 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((response: any /* 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}
   */
  setCustomHeader(header: string, value: any) {
    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}
   */
  setIPs(ips: string[]) {
    var validIPs = ips.filter((ipString: string) => 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}
   */
  setSharedContext(url: string, password: string | null) {
    var sharedContextAuthHeader = this.buildSharedItemAuthHeader(url, password);
    this.setCustomHeader(HEADER_BOXAPI, sharedContextAuthHeader);
  }

  /**
   * Removes any current shared item context from API Session.
   *
   * @returns {void}
   */
  revokeSharedContext() {
    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}
   */
  asUser(userID: string) {
    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}
   */
  asSelf() {
    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
   */
  revokeTokens(callback: Function) {
    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
   */
  exchangeToken(
    scopes: string | string[],
    resource?: string,
    options?: Function | object,
    callback?: Function
  ) {
    // 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}
   */
  get(path: string, params?: object | null, callback?: Function) {
    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}
   */
  post(path: string, params: object | null, callback?: Function) {
    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}
   */
  put(path: string, params?: object | null, callback?: Function) {
    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}
   */
  del(path: string, params: object | null, callback?: Function) {
    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}
   */
  options(path: string, params: object | null, callback?: Function) {
    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}
   */
  upload(
    path: string,
    params: object | null,
    formData: object | null,
    callback: Function
  ) {
    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
   */
  buildSharedItemAuthHeader(url: string, password: string | null) {
    var encodedURL = encodeURIComponent(url),
      encodedPassword = encodeURIComponent(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.
   */
  defaultResponseHandler(callback: Function) {
    var self = this;

    if (!callback) {
      return null;
    }

    return function (err: any, response: any /* 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
   */
  wrapWithDefaultHandler(method: Function): Function {
    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 Promise) {
        ret = ret.then((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
   */
  plug(name: string, plugin: Function, options: object) {
    options = options || {};

    if (name in this && typeof (this as any)[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 as any)[name] = plugin(this, options);
  }
}

// ------------------------------------------------------------------------------
// 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.prototype.CURRENT_USER_ID;

// ------------------------------------------------------------------------------
// Public
// ------------------------------------------------------------------------------

/**
 * @module box-node-sdk/lib/box-client
 * @see {@Link BoxClient}
 */
export = BoxClient;