dropbox
Version:
The Dropbox JavaScript SDK is a lightweight, promise based interface to the Dropbox v2 API that works in both nodejs and browser environments.
397 lines (348 loc) • 12.1 kB
JavaScript
import { UPLOAD, DOWNLOAD, RPC } from './constants';
import { downloadRequest } from './download-request';
import { uploadRequest } from './upload-request';
import { rpcRequest } from './rpc-request';
/* eslint-disable */
// Polyfill object.assign for legacy browsers
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
if (typeof Object.assign !== 'function') {
(function () {
Object.assign = function (target) {
'use strict';
var output;
var index;
var source;
var nextKey;
if (target === undefined || target === null) {
throw new TypeError('Cannot convert undefined or null to object');
}
output = Object(target);
for (index = 1; index < arguments.length; index++) {
source = arguments[index];
if (source !== undefined && source !== null) {
for (nextKey in source) {
if (source.hasOwnProperty(nextKey)) {
output[nextKey] = source[nextKey];
}
}
}
}
return output;
};
}());
}
// Polyfill Array.includes for legacy browsers
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
value: function(searchElement, fromIndex) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
// 1. Let O be ? ToObject(this value).
var o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;
// 3. If len is 0, return false.
if (len === 0) {
return false;
}
// 4. Let n be ? ToInteger(fromIndex).
// (If fromIndex is undefined, this step produces the value 0.)
var n = fromIndex | 0;
// 5. If n ≥ 0, then
// a. Let k be n.
// 6. Else n < 0,
// a. Let k be len + n.
// b. If k < 0, let k be 0.
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
function sameValueZero(x, y) {
return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
}
// 7. Repeat, while k < len
while (k < len) {
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
// b. If SameValueZero(searchElement, elementK) is true, return true.
if (sameValueZero(o[k], searchElement)) {
return true;
}
// c. Increase k by 1.
k++;
}
// 8. Return false
return false;
}
});
}
/* eslint-enable */
/**
* @private
* @class DropboxBase
* @classdesc The main Dropbox SDK class. This contains the methods that are
* shared between Dropbox and DropboxTeam classes. It is marked as private so
* that it doesn't show up in the docs because it is never used directly.
* @arg {Object} options
* @arg {Function} [options.fetch] - fetch library for making requests.
* @arg {String} [options.accessToken] - An access token for making authenticated
* requests.
* @arg {String} [options.clientId] - The client id for your app. Used to create
* authentication URL.
* @arg {String} [options.clientSecret] - The client secret for your app.
* @arg {Number} [options.selectUser] - User that the team access token would like
* to act as.
* @arg {String} [options.selectAdmin] - Team admin that the team access token would like
* to act as.
* @arg {String} [options.pathRoot] - root pass to access other namespaces
* Use to access team folders for example
*/
function parseBodyToType(res) {
const clone = res.clone();
return new Promise((resolve) => {
res.json()
.then(data => resolve(data))
.catch(() => clone.text().then(data => resolve(data)));
}).then(data => [res, data]);
}
export class DropboxBase {
constructor(options) {
options = options || {};
this.accessToken = options.accessToken;
this.clientId = options.clientId;
this.clientSecret = options.clientSecret;
this.selectUser = options.selectUser;
this.selectAdmin = options.selectAdmin;
this.fetch = options.fetch || fetch;
this.pathRoot = options.pathRoot;
if (!options.fetch) { console.warn('Global fetch is deprecated and will be unsupported in a future version. Please pass fetch function as option when instantiating dropbox instance: new Dropbox({fetch})'); } // eslint-disable-line no-console
}
/**
* Set the access token used to authenticate requests to the API.
* @arg {String} accessToken - An access token
* @returns {undefined}
*/
setAccessToken(accessToken) {
this.accessToken = accessToken;
}
/**
* Get the access token
* @returns {String} Access token
*/
getAccessToken() {
return this.accessToken;
}
/**
* Set the client id, which is used to help gain an access token.
* @arg {String} clientId - Your apps client id
* @returns {undefined}
*/
setClientId(clientId) {
this.clientId = clientId;
}
/**
* Get the client id
* @returns {String} Client id
*/
getClientId() {
return this.clientId;
}
/**
* Set the client secret
* @arg {String} clientSecret - Your app's client secret
* @returns {undefined}
*/
setClientSecret(clientSecret) {
this.clientSecret = clientSecret;
}
/**
* Get the client secret
* @returns {String} Client secret
*/
getClientSecret() {
return this.clientSecret;
}
/**
* Get a URL that can be used to authenticate users for the Dropbox API.
* @arg {String} redirectUri - A URL to redirect the user to after
* authenticating. This must be added to your app through the admin interface.
* @arg {String} [state] - State that will be returned in the redirect URL to help
* prevent cross site scripting attacks.
* @arg {String} [authType] - auth type, defaults to 'token', other option is 'code'
* @returns {String} Url to send user to for Dropbox API authentication
*/
getAuthenticationUrl(redirectUri, state, authType = 'token') {
const clientId = this.getClientId();
const baseUrl = 'https://www.dropbox.com/oauth2/authorize';
if (!clientId) {
throw new Error('A client id is required. You can set the client id using .setClientId().');
}
if (authType !== 'code' && !redirectUri) {
throw new Error('A redirect uri is required.');
}
if (!['code', 'token'].includes(authType)) {
throw new Error('Authorization type must be code or token');
}
let authUrl;
if (authType === 'code') {
authUrl = `${baseUrl}?response_type=code&client_id=${clientId}`;
} else {
authUrl = `${baseUrl}?response_type=token&client_id=${clientId}`;
}
if (redirectUri) {
authUrl += `&redirect_uri=${redirectUri}`;
}
if (state) {
authUrl += `&state=${state}`;
}
return authUrl;
}
/**
* Get an OAuth2 access token from an OAuth2 Code.
* @arg {String} redirectUri - A URL to redirect the user to after
* authenticating. This must be added to your app through the admin interface.
* @arg {String} code - An OAuth2 code.
*/
getAccessTokenFromCode(redirectUri, code) {
const clientId = this.getClientId();
const clientSecret = this.getClientSecret();
if (!clientId) {
throw new Error('A client id is required. You can set the client id using .setClientId().');
}
if (!clientSecret) {
throw new Error('A client secret is required. You can set the client id using .setClientSecret().');
}
const path = `https://api.dropboxapi.com/oauth2/token?code=${code}&\
grant_type=authorization_code&redirect_uri=${redirectUri}&\
client_id=${clientId}&client_secret=${clientSecret}`;
const fetchOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
};
return this.fetch(path, fetchOptions)
.then(res => parseBodyToType(res))
.then(([res, data]) => {
// maintaining existing API for error codes not equal to 200 range
if (!res.ok) {
// eslint-disable-next-line no-throw-literal
throw {
error: data,
response: res,
status: res.status,
};
}
return data.access_token;
});
}
/**
* Called when the authentication succeed
* @callback successCallback
* @param {string} access_token The application's access token
*/
/**
* Called when the authentication failed.
* @callback errorCallback
*/
/**
* An authentication process that works with cordova applications.
* @param {successCallback} successCallback
* @param {errorCallback} errorCallback
*/
authenticateWithCordova(successCallback, errorCallback) {
const redirectUrl = 'https://www.dropbox.com/1/oauth2/redirect_receiver';
const url = this.getAuthenticationUrl(redirectUrl);
let removed = false;
const browser = window.open(url, '_blank');
function onLoadError(event) {
if (event.code !== -999) { // Workaround to fix wrong behavior on cordova-plugin-inappbrowser
// Try to avoid a browser crash on browser.close().
window.setTimeout(() => { browser.close(); }, 10);
errorCallback();
}
}
function onLoadStop(event) {
const errorLabel = '&error=';
const errorIndex = event.url.indexOf(errorLabel);
if (errorIndex > -1) {
// Try to avoid a browser crash on browser.close().
window.setTimeout(() => { browser.close(); }, 10);
errorCallback();
} else {
const tokenLabel = '#access_token=';
let tokenIndex = event.url.indexOf(tokenLabel);
const tokenTypeIndex = event.url.indexOf('&token_type=');
if (tokenIndex > -1) {
tokenIndex += tokenLabel.length;
// Try to avoid a browser crash on browser.close().
window.setTimeout(() => { browser.close(); }, 10);
const accessToken = event.url.substring(tokenIndex, tokenTypeIndex);
successCallback(accessToken);
}
}
}
function onExit() {
if (removed) {
return;
}
browser.removeEventListener('loaderror', onLoadError);
browser.removeEventListener('loadstop', onLoadStop);
browser.removeEventListener('exit', onExit);
removed = true;
}
browser.addEventListener('loaderror', onLoadError);
browser.addEventListener('loadstop', onLoadStop);
browser.addEventListener('exit', onExit);
}
request(path, args, auth, host, style) {
let request = null;
switch (style) {
case RPC:
request = this.getRpcRequest();
break;
case DOWNLOAD:
request = this.getDownloadRequest();
break;
case UPLOAD:
request = this.getUploadRequest();
break;
default:
throw new Error(`Invalid request style: ${style}`);
}
const options = {
selectUser: this.selectUser,
selectAdmin: this.selectAdmin,
clientId: this.getClientId(),
clientSecret: this.getClientSecret(),
pathRoot: this.pathRoot,
};
return request(path, args, auth, host, this.getAccessToken(), options);
}
setRpcRequest(newRpcRequest) {
this.rpcRequest = newRpcRequest;
}
getRpcRequest() {
if (this.rpcRequest === undefined) {
this.rpcRequest = rpcRequest(this.fetch);
}
return this.rpcRequest;
}
setDownloadRequest(newDownloadRequest) {
this.downloadRequest = newDownloadRequest;
}
getDownloadRequest() {
if (this.downloadRequest === undefined) {
this.downloadRequest = downloadRequest(this.fetch);
}
return this.downloadRequest;
}
setUploadRequest(newUploadRequest) {
this.uploadRequest = newUploadRequest;
}
getUploadRequest() {
if (this.uploadRequest === undefined) {
this.uploadRequest = uploadRequest(this.fetch);
}
return this.uploadRequest;
}
}