1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-03-29 21:21:15 +02:00

All: Dropbox: Handle various error conditions

This commit is contained in:
Laurent Cozic 2018-03-27 00:05:39 +01:00
parent 3c2281dbf9
commit 6e994fd8b9
7 changed files with 60 additions and 33 deletions

View File

@ -92,7 +92,7 @@ class Command extends BaseCommand {
} }
const response = await api.execAuthToken(authCode); const response = await api.execAuthToken(authCode);
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', JSON.stringify(response)); Setting.setValue('sync.' + this.syncTargetId_ + '.auth', response.access_token);
api.setAuthToken(response.access_token); api.setAuthToken(response.access_token);
return true; return true;
} }

View File

@ -37,7 +37,7 @@ class DropboxLoginScreenComponent extends React.Component {
const api = await this.dropboxApi(); const api = await this.dropboxApi();
try { try {
const response = await api.execAuthToken(this.state.authCode); const response = await api.execAuthToken(this.state.authCode);
Setting.setValue('sync.' + this.syncTargetId() + '.auth', JSON.stringify(response)); Setting.setValue('sync.' + this.syncTargetId() + '.auth', response.access_token);
api.setAuthToken(response.access_token); api.setAuthToken(response.access_token);
bridge().showInfoMessageBox(_('The application has been authorised!')); bridge().showInfoMessageBox(_('The application has been authorised!'));
this.props.dispatch({ type: 'NAV_BACK' }); this.props.dispatch({ type: 'NAV_BACK' });

View File

@ -370,7 +370,7 @@ class NoteTextComponent extends React.Component {
webviewReady: true, webviewReady: true,
}); });
if (Setting.value('env') === 'dev') this.webview_.openDevTools(); // if (Setting.value('env') === 'dev') this.webview_.openDevTools();
} }
webview_ref(element) { webview_ref(element) {

View File

@ -3,6 +3,7 @@ const { shim } = require('lib/shim.js');
const JoplinError = require('lib/JoplinError'); const JoplinError = require('lib/JoplinError');
const URL = require('url-parse'); const URL = require('url-parse');
const { time } = require('lib/time-utils'); const { time } = require('lib/time-utils');
const EventDispatcher = require('lib/EventDispatcher');
class DropboxApi { class DropboxApi {
@ -10,6 +11,7 @@ class DropboxApi {
this.logger_ = new Logger(); this.logger_ = new Logger();
this.options_ = options; this.options_ = options;
this.authToken_ = null; this.authToken_ = null;
this.dispatcher_ = new EventDispatcher();
} }
clientId() { clientId() {
@ -32,8 +34,13 @@ class DropboxApi {
return this.authToken_; // Without the "Bearer " prefix return this.authToken_; // Without the "Bearer " prefix
} }
on(eventName, callback) {
return this.dispatcher_.on(eventName, callback);
}
setAuthToken(v) { setAuthToken(v) {
this.authToken_ = v; this.authToken_ = v;
this.dispatcher_.dispatch('authRefreshed', this.authToken());
} }
loginUrl() { loginUrl() {
@ -90,6 +97,12 @@ class DropboxApi {
return JSON.parse(responseText); return JSON.parse(responseText);
} }
isTokenError(status, responseText) {
if (status === 401) return true;
if (responseText.indexOf('OAuth 2 access token is malformed') >= 0) return true;
return false;
}
async exec(method, path = '', body = null, headers = null, options = null) { async exec(method, path = '', body = null, headers = null, options = null) {
if (headers === null) headers = {}; if (headers === null) headers = {};
if (options === null) options = {}; if (options === null) options = {};
@ -124,8 +137,7 @@ class DropboxApi {
// console.info(this.requestToCurl_(url, fetchOptions)); // console.info(this.requestToCurl_(url, fetchOptions));
const now = Date.now(); // console.info(method + ' ' + url);
// console.info(now + ': ' + method + ' ' + url);
if (options.source == 'file' && (method == 'POST' || method == 'PUT')) { if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
response = await shim.uploadBlob(url, fetchOptions); response = await shim.uploadBlob(url, fetchOptions);
@ -137,7 +149,7 @@ class DropboxApi {
const responseText = await response.text(); const responseText = await response.text();
// console.info(now + ': Response: ' + responseText); // console.info('Response: ' + responseText);
let responseJson_ = null; let responseJson_ = null;
const loadResponseJson = () => { const loadResponseJson = () => {
@ -162,10 +174,17 @@ class DropboxApi {
// Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of // Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of
// JSON. That way the error message will still show there's a problem but without filling up the log or screen. // JSON. That way the error message will still show there's a problem but without filling up the log or screen.
const shortResponseText = (responseText + '').substr(0, 1024); const shortResponseText = (responseText + '').substr(0, 1024);
return new JoplinError(method + ' ' + path + ': ' + message + ' (' + response.status + '): ' + shortResponseText, code); const error = new JoplinError(method + ' ' + path + ': ' + message + ' (' + response.status + '): ' + shortResponseText, code);
error.httpStatus = response.status;
return error;
} }
if (!response.ok) { if (!response.ok) {
const json = loadResponseJson();
if (this.isTokenError(response.status, responseText)) {
this.setAuthToken(null);
}
// When using fetchBlob we only get a string (not xml or json) back // When using fetchBlob we only get a string (not xml or json) back
if (options.target === 'file') throw newError('fetchBlob error'); if (options.target === 'file') throw newError('fetchBlob error');

View File

@ -32,4 +32,4 @@ class EventDispatcher {
} }
module.exports = { EventDispatcher }; module.exports = EventDispatcher;

View File

@ -40,10 +40,6 @@ class SyncTargetDropbox extends BaseSyncTarget {
return fileApi.driver().api(); return fileApi.driver().api();
} }
syncTargetId() {
return SyncTargetDropbox.id();
}
async initFileApi() { async initFileApi() {
const params = parameters().dropbox; const params = parameters().dropbox;
@ -52,15 +48,17 @@ class SyncTargetDropbox extends BaseSyncTarget {
secret: params.secret, secret: params.secret,
}); });
const authJson = Setting.value('sync.' + SyncTargetDropbox.id() + '.auth'); api.on('authRefreshed', (auth) => {
if (authJson) { this.logger().info('Saving updated OneDrive auth.');
const auth = JSON.parse(authJson); Setting.setValue('sync.' + SyncTargetDropbox.id() + '.auth', auth ? auth : null);
api.setAuthToken(auth.access_token); });
}
const authToken = Setting.value('sync.' + SyncTargetDropbox.id() + '.auth');
api.setAuthToken(authToken);
const appDir = ''; const appDir = '';
const fileApi = new FileApi(appDir, new FileApiDriverDropbox(api)); const fileApi = new FileApi(appDir, new FileApiDriverDropbox(api));
fileApi.setSyncTargetId(this.syncTargetId()); fileApi.setSyncTargetId(SyncTargetDropbox.id());
fileApi.setLogger(this.logger()); fileApi.setLogger(this.logger());
return fileApi; return fileApi;
} }

View File

@ -66,22 +66,32 @@ class FileApiDriverDropbox {
const context = options ? options.context : null; const context = options ? options.context : null;
let cursor = context ? context.cursor : null; let cursor = context ? context.cursor : null;
const urlPath = cursor ? 'files/list_folder/continue' : 'files/list_folder'; while (true) {
const body = cursor ? { cursor: cursor } : { path: this.makePath_(path), include_deleted: true }; const urlPath = cursor ? 'files/list_folder/continue' : 'files/list_folder';
const response = await this.api().exec('POST', urlPath, body); const body = cursor ? { cursor: cursor } : { path: this.makePath_(path), include_deleted: true };
const output = { try {
items: this.metadataToStats_(response.entries), const response = await this.api().exec('POST', urlPath, body);
hasMore: response.has_more,
context: { cursor: response.cursor }, const output = {
items: this.metadataToStats_(response.entries),
hasMore: response.has_more,
context: { cursor: response.cursor },
}
return output;
} catch (error) {
// If there's an error related to an invalid cursor, clear the cursor and retry.
if (cursor) {
if (error.httpStatus === 400 || error.code.indexOf('reset') >= 0) {
// console.info('Clearing cursor and retrying', error);
cursor = null;
continue;
}
}
throw error;
}
} }
return output;
// TODO: handle error - reset cursor
} }
async list(path, options) { async list(path, options) {