import { DropboxAuth } from 'dropbox';
/**
* This function is meant to run on page load and is in charge
* of dispatching messages from the popup window back to the
* main window.
*/
function dispatchResult() {
const params = window.location.search;
if (window.opener) {
// send them to the opening window
window.opener.postMessage(params);
// close the popup
window.close();
}
}
window.addEventListener('load', dispatchResult);
const windowName = 'Dropbox OAuth';
const defaultWindowOptions = {
toolbar: 'no',
menubar: 'no',
width: 600,
height: 800,
top: 100,
left: 100,
};
const defaultTimeout = 300000; // 5 minutes
/**
* @class DropboxPopup
* @classdesc The DropboxPopup class is to provide a simple popup window to preform OAuth in.
* @param {object} options
* @param {string} options.clientId - [Required] The client id for your app.
* @param {string} [options.clientSecret] - The client secret for your app.
* @param {string} [options.redirectUri] - [Required] The redirect Uri to return to once auth is
* complete.
* @param {string} [options.tokenAccessType] - type of token to request. From the following:
* legacy - creates one long-lived token with no expiration
* online - create one short-lived token with an expiration
* offline - create one short-lived token with an expiration with a refresh token
* @param {Array<string>} [options.scope] - scopes to request for the grant
* @param {string} [options.includeGrantedScopes] - whether or not to include
* previously granted scopes.
* From the following:
* user - include user scopes in the grant
* team - include team scopes in the grant
* Note: if this user has never linked the app, include_granted_scopes must be None
* @param {boolean} [options.usePKCE] - Whether or not to use Sha256 based PKCE.
* PKCE should be only use on client apps which doesn't call your server.
* It is less secure than non-PKCE flow but can be used if you are unable to safely
* retrieve your app secret
* @param {object} [windowOptions]
* @param {number} [windowOptions.width] - The width of the popup window in pixels.
* @param {number} [windowOptions.height] - The height of the popup window in pixels.
* @param {number} [windowOptions.top] - The number of pixels from the top of the screen.
* @param {number} [windowOptions.left] - The number of pixels from the left side of the screen.
* @param {object} [windowOptions.additionalParams] - Any additional parameters desired to be used
* with the window.open() command. Note, by default, we add the parameters toolbar=no and menubar=no
* in order to ensure this opens as a popup.
* @param {number} timeout - The timeout for when to give up on the promise and throw an error
*/
export default class DropboxPopup {
constructor(options, windowOptions, timeout) {
this.clientId = options.clientId;
this.redirectUri = options.redirectUri;
this.clientSecret = options.clientSecret || '';
this.tokenAccessType = options.tokenAccessType || 'offline';
this.scope = options.scope || null;
this.includeGrantedScopes = options.includeGrantedScopes || 'none';
this.usePKCE = options.usePKCE || false;
this.timeout = timeout || defaultTimeout;
this.authObject = new DropboxAuth({
clientId: this.clientId,
clientSecret: this.clientSecret,
});
this.state = Math.random().toString(36).substring(7);
// Set window options with format of key=value,key=value...
const overlayedWindowOptions = Object.assign(defaultWindowOptions, windowOptions);
this.windowOptions = '';
Object.keys(overlayedWindowOptions).forEach((key) => {
if (this.windowOptions === '') {
this.windowOptions = `${key}=${overlayedWindowOptions[key]}`;
} else {
this.windowOptions = this.windowOptions.concat(`, ${key}=${overlayedWindowOptions[key]}`);
}
});
}
/**
* The main function to handle authentication via a popup window.
*
* @returns {Promise<DropboxAuth>} The promise which contains the end auth object
*/
authUser() {
const popup = this;
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Request timed out'));
}, popup.timeout);
/**
* The function in charge of handling the redirect once the popup has completed.
*
* @param {MessageEvent} event - The incoming message from the popup window.
* @returns {void}
*/
function handleRedirect(event) {
if (event.source !== popup) {
return;
}
window.removeEventListener('message', popup.handleRedirect);
const { data } = event;
const urlParams = new URLSearchParams(data);
const code = urlParams.get('code');
popup.authObject.getAccessTokenFromCode(popup.redirectUri, code).then((response) => {
const { result } = response;
popup.authObject.setAccessToken(result.access_token);
popup.authObject.setRefreshToken(result.refresh_token);
popup.authObject.setAccessTokenExpiresAt(
new Date(Date.now() + (result.expires_in * 1000)),
);
resolve(popup.authObject);
})
.catch((error) => {
reject(error);
});
}
popup.authObject.getAuthenticationUrl(popup.redirectUri, popup.state, 'code', popup.tokenAccessType, popup.scope, popup.includeGrantedScopes, popup.usePKCE)
.then((authUrl) => {
const popupWindow = window.open(authUrl, windowName, popup.windowOptions);
popupWindow.focus();
window.addEventListener('message', handleRedirect, false);
});
});
}
}