Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dropbox: Retrieves & uses refresh token to obtain new access token #1274

Merged
merged 11 commits into from
Oct 27, 2022
Merged
75 changes: 38 additions & 37 deletions src/dropbox.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import EventHandling from './eventhandling';
import WireClient from './wireclient';
import BaseClient from './baseclient';
import RevisionCache from './revisioncache';
import SyncError from './sync-error';
Expand All @@ -13,6 +12,7 @@ import {
getTextFromArrayBuffer,
localStorageAvailable
} from './util';
import {requestWithTimeout, RequestOptions, isArrayBufferView} from "./requests";

/**
* WORK IN PROGRESS, NOT RECOMMENDED FOR PRODUCTION USE
Expand Down Expand Up @@ -83,12 +83,12 @@ function httpHeaderSafeJson(obj) {
);
}

function compareApiError (response, expect) {
function compareApiError (response: {error_summary: string}, expect: string[]): boolean {
return new RegExp('^' + expect.join('\\/') + '(\\/|$)').test(response.error_summary);
}

function isBinaryData (data) {
return data instanceof ArrayBuffer || WireClient.isArrayBufferView(data);
function isBinaryData (data): boolean {
return data instanceof ArrayBuffer || isArrayBufferView(data);
}

/**
Expand Down Expand Up @@ -228,7 +228,7 @@ class Dropbox {
*
* @private
*/
_getFolder (path) {
_getFolder (path: string) {
const url = FOLDER_URL;
const revCache = this._revCache;

Expand Down Expand Up @@ -312,7 +312,7 @@ class Dropbox {
*
* @protected
*/
get (path, options) {
get (path: string, options: { ifNoneMatch?: string } = {}): Promise<unknown> {
if (! this.connected) { return Promise.reject("not connected (path: " + path + ")"); }
const url = DOWNLOAD_URL;
DougReeder marked this conversation as resolved.
Show resolved Hide resolved

Expand Down Expand Up @@ -382,7 +382,7 @@ class Dropbox {
mime = resp.getResponseHeader('Content-Type');
rev = meta.rev;
this._revCache.set(path, rev);
this._shareIfNeeded(path);
this._shareIfNeeded(path); // It's not necessary to wait for this promise

if (shouldBeTreatedAsBinary(responseText, mime)) {
// return unprocessed response
Expand Down Expand Up @@ -416,14 +416,16 @@ class Dropbox {
* Calls ``Dropbox.share`` afterwards to fill ``_itemRefs``.
*
* @param {string} path - path of the folder to put, with leading slash
* @param {XMLHttpRequestBodyInit} body - Blob | BufferSource | FormData | URLSearchParams | string
* @param {string} contentType - MIME type of body
* @param {Object} options
* @param {string} options.ifNoneMatch - When *, only create or update the file if it doesn't yet exist
* @param {string} options.ifMatch - Only saves if this matches current revision
* @returns {Promise} Resolves with an object containing the status code,
* content-type and revision
* @protected
*/
async put (path, body, contentType, options) {
async put (path: string, body, contentType: string, options: { ifMatch?: string; ifNoneMatch?: string } = {}) {
DougReeder marked this conversation as resolved.
Show resolved Hide resolved
if (!this.connected) {
throw new Error("not connected (path: " + path + ")");
}
Expand Down Expand Up @@ -490,7 +492,7 @@ class Dropbox {
*
* @protected
*/
async 'delete' (path, options) {
async 'delete' (path: string, options: { ifMatch?: string } = {}) {
if (!this.connected) {
throw new Error("not connected (path: " + path + ")");
}
Expand All @@ -516,12 +518,11 @@ class Dropbox {

/**
* Calls share, if the provided path resides in a public folder.
*
* @private
*/
_shareIfNeeded (path) {
_shareIfNeeded (path: string): Promise<any> {
if (path.match(/^\/public\/.*[^/]$/) && this._itemRefs[path] === undefined) {
this.share(path);
return this.share(path);
}
}

Expand All @@ -533,7 +534,7 @@ class Dropbox {
*
* @private
*/
share (path) {
share (path: string): Promise<any> {
const url = CREATE_SHARED_URL;
const options = {
body: {path: getDropboxPath(path)}
Expand Down Expand Up @@ -582,26 +583,27 @@ class Dropbox {
*
* @protected
*/
info () {
info (): Promise<{email: string}> {
const url = ACCOUNT_URL;

return this._request('POST', url, {}).then(function (response) {
let info = response.responseText;
let email;

try {
info = JSON.parse(info);
const info = JSON.parse(response.responseText);
email = info?.email;
} catch (e) {
return Promise.reject(new Error('Could not query current account info: Invalid API response: ' + info));
return Promise.reject(new Error('Could not query current account info: Invalid API response: ' + response.responseText));
}

return Promise.resolve({
email: info.email
email: email
});
});
}

/**
* Make a network request.
* Makes a network request.
*
* @param {string} method - Request method
* @param {string} url - Target URL
Expand All @@ -610,7 +612,7 @@ class Dropbox {
*
* @private
*/
_request (method, url, options) {
_request (method: string, url: string, options): Promise<any> {
if (!options.headers) { options.headers = {}; }
options.headers['Authorization'] = 'Bearer ' + this.token;

Expand All @@ -624,14 +626,15 @@ class Dropbox {
isFolder: isFolder(url)
});

return WireClient.request.call(this, method, url, options).then(xhr => {
return requestWithTimeout(method, url, options).then(xhr => {
// 503 means retry this later
if (xhr && xhr.status === 503) {
if (this.online) {
this.online = false;
this.rs._emit('network-offline');
}
return setTimeout(this._request(method, url, options), 3210);
// TODO: refactor so the request can be re-run after a delay
return Promise.reject(new ProgressEvent("server is busy"));
} else {
if (!this.online) {
this.online = true;
Expand Down Expand Up @@ -816,12 +819,11 @@ class Dropbox {
* Upload a simple file (the size is no more than 150MB).
*
* @param {Object} params
* @param {string} options.ifMatch - Only update the file if its ETag
* @param {string} params.ifMatch - Only update the file if its ETag
* matches this string
* @param {string} options.path - path of the file
* @param {string} options.body - contents of the file to upload
* @param {string} options.contentType - mime type of the file
*
* @param {string} params.path - path of the file
* @param {string} params.body - contents of the file to upload
* @param {string} params.contentType - mime type of the file *
* @return {Promise} A promise for an object with the following structure:
* statusCode - HTTP status code
* revision - revision of the newly-created file, if any
Expand Down Expand Up @@ -851,12 +853,12 @@ class Dropbox {
return Promise.resolve({statusCode: response.status});
}

let body = response.responseText;
let body;

try {
body = JSON.parse(body);
body = JSON.parse(response.responseText);
} catch (e) {
return Promise.reject(new Error('Invalid API result: ' + body));
return Promise.reject(new Error('Invalid API result: ' + response.responseText));
}

if (response.status === 409) {
Expand Down Expand Up @@ -896,12 +898,12 @@ class Dropbox {
return Promise.resolve({statusCode: response.status});
}

let responseBody = response.responseText;
let responseBody;

try {
responseBody = JSON.parse(responseBody);
responseBody = JSON.parse(response.responseText);
} catch (e) {
return Promise.reject(new Error('Invalid response body: ' + responseBody));
return Promise.reject(new Error('Invalid response body: ' + response.responseText));
}

if (response.status === 409) {
Expand Down Expand Up @@ -956,7 +958,7 @@ class Dropbox {
}

if (response.status === 409) {
return Promise.reject(new Error('API error: ' + response.error_summary));
return Promise.reject(new Error('API error: ' + body?.error_summary || response.responseText));
}

if (!body.links.length) {
Expand All @@ -973,7 +975,7 @@ class Dropbox {
/**
* Initialize the Dropbox backend.
*
* @param {object} remoteStorage - RemoteStorage instance
* @param {object} rs - RemoteStorage instance
*
* @protected
*/
Expand All @@ -990,7 +992,6 @@ class Dropbox {
/**
* Inform about the availability of the Dropbox backend.
*
* @param {object} rs - RemoteStorage instance
* @returns {Boolean}
*
* @protected
Expand All @@ -1002,7 +1003,7 @@ class Dropbox {
/**
* Remove Dropbox as a backend.
*
* @param {object} remoteStorage - RemoteStorage instance
* @param {object} rs - RemoteStorage instance
*
* @protected
*/
Expand Down
18 changes: 8 additions & 10 deletions src/googledrive.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import BaseClient from './baseclient';
import WireClient from './wireclient';
import EventHandling from './eventhandling';
import {
applyMixins,
Expand All @@ -10,6 +9,7 @@ import {
getTextFromArrayBuffer,
localStorageAvailable
} from './util';
import {requestWithTimeout, RequestOptions} from "./requests";

const BASE_URL = 'https://www.googleapis.com';
const AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';
Expand Down Expand Up @@ -277,7 +277,7 @@ class GoogleDrive {
*
* @protected
*/
get (path, options) {
get (path: string, options: { ifNoneMatch?: string } = {}): Promise<unknown> {
if (isFolder(path)) {
return this._getFolder(googleDrivePath(path));
} else {
Expand All @@ -299,7 +299,7 @@ class GoogleDrive {
*
* @protected
*/
put (path, body, contentType, options) {
put (path: string, body: XMLHttpRequestBodyInit, contentType: string, options: { ifMatch?: string; ifNoneMatch?: string } = {}) {
const fullPath = googleDrivePath(path);

function putDone(response) {
Expand Down Expand Up @@ -337,7 +337,7 @@ class GoogleDrive {
*
* @protected
*/
delete (path, options) {
delete (path: string, options: { ifMatch?: string } = {}) {
const fullPath = googleDrivePath(path);

return this._getFileId(fullPath).then((id) => {
Expand Down Expand Up @@ -496,7 +496,7 @@ class GoogleDrive {
}
}

const params = {
const params: RequestOptions = {
responseType: 'arraybuffer'
};
return this._request('GET', meta.downloadUrl, params).then((response) => {
Expand Down Expand Up @@ -531,13 +531,12 @@ class GoogleDrive {
* Request a directory.
*
* @param {string} path - Directory path
* @param {Object} options
* @returns {Promise} Resolves with an object containing the status code,
* body and content-type
*
* @private
*/
_getFolder (path) {
_getFolder (path: string) {
return this._getFileId(path).then((id) => {
let data, etagWithoutQuotes, itemsMap;
if (! id) {
Expand Down Expand Up @@ -704,7 +703,7 @@ class GoogleDrive {
*
* @private
*/
_request (method, url, options) {
_request (method: string, url: string, options: RequestOptions) {
if (! options.headers) { options.headers = {}; }
options.headers['Authorization'] = 'Bearer ' + this.token;

Expand All @@ -713,7 +712,7 @@ class GoogleDrive {
isFolder: isFolder(url)
});

return WireClient.request.call(this, method, url, options).then((xhr) => {
return requestWithTimeout(method, url, options).then((xhr) => {
// Google tokens expire from time to time...
if (xhr && xhr.status === 401) {
this.connect();
Expand Down Expand Up @@ -769,7 +768,6 @@ class GoogleDrive {
/**
* Inform about the availability of the Google Drive backend.
*
* @param {Object} rs - RemoteStorage instance
* @returns {Boolean}
*
* @protected
Expand Down