1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-11-29 22:48:10 +02:00

Adding Dropbox sync to Electron app

This commit is contained in:
Laurent Cozic
2018-03-26 18:33:55 +01:00
parent 0f4324c2f8
commit ac07bf784d
17 changed files with 235 additions and 24 deletions

View File

@@ -29,6 +29,7 @@ const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js');
const SyncTargetOneDriveDev = require('lib/SyncTargetOneDriveDev.js');
const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js');
const SyncTargetWebDAV = require('lib/SyncTargetWebDAV.js');
const SyncTargetDropbox = require('lib/SyncTargetDropbox.js');
const EncryptionService = require('lib/services/EncryptionService');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const BaseService = require('lib/services/BaseService');
@@ -38,6 +39,7 @@ SyncTargetRegistry.addClass(SyncTargetOneDrive);
SyncTargetRegistry.addClass(SyncTargetOneDriveDev);
SyncTargetRegistry.addClass(SyncTargetNextcloud);
SyncTargetRegistry.addClass(SyncTargetWebDAV);
SyncTargetRegistry.addClass(SyncTargetDropbox);
class BaseApplication {
@@ -421,8 +423,14 @@ class BaseApplication {
if (Setting.value('firstStart')) {
const locale = shim.detectAndSetLocale(Setting);
reg.logger().info('First start: detected locale as ' + locale);
if (Setting.value('env') === 'dev') Setting.setValue('sync.target', SyncTargetRegistry.nameToId('onedrive_dev'));
Setting.setValue('firstStart', 0)
if (Setting.value('env') === 'dev') {
Setting.setValue('showTrayIcon', 0);
Setting.setValue('autoUpdateEnabled', 0);
Setting.setValue('sync.interval', 3600);
}
Setting.setValue('firstStart', 0);
} else {
setLocale(Setting.value('locale'));
}

View File

@@ -30,7 +30,7 @@ class BaseSyncTarget {
return this.db_;
}
isAuthenticated() {
async isAuthenticated() {
return false;
}
@@ -113,7 +113,7 @@ class BaseSyncTarget {
async syncStarted() {
if (!this.synchronizer_) return false;
if (!this.isAuthenticated()) return false;
if (!await this.isAuthenticated()) return false;
const sync = await this.synchronizer();
return sync.state() != 'idle';
}

View File

@@ -12,6 +12,14 @@ class DropboxApi {
this.authToken_ = null;
}
clientId() {
return this.options_.id;
}
clientSecret() {
return this.options_.secret;
}
setLogger(l) {
this.logger_ = l;
}
@@ -21,13 +29,17 @@ class DropboxApi {
}
authToken() {
return this.authToken_; // Must be "Bearer XXXXXXXXXXXXXXXXXX"
return this.authToken_; // Without the "Bearer " prefix
}
setAuthToken(v) {
this.authToken_ = v;
}
loginUrl() {
return 'https://www.dropbox.com/oauth2/authorize?response_type=code&client_id=' + this.clientId();
}
baseUrl(endPointFormat) {
if (['content', 'api'].indexOf(endPointFormat) < 0) throw new Error('Invalid end point format: ' + endPointFormat);
return 'https://' + endPointFormat + '.dropboxapi.com/2';
@@ -49,6 +61,35 @@ class DropboxApi {
return output.join(' ');
}
async execAuthToken(authCode) {
const postData = {
code: authCode,
grant_type: 'authorization_code',
client_id: this.clientId(),
client_secret: this.clientSecret(),
};
var formBody = [];
for (var property in postData) {
var encodedKey = encodeURIComponent(property);
var encodedValue = encodeURIComponent(postData[property]);
formBody.push(encodedKey + "=" + encodedValue);
}
formBody = formBody.join("&");
const response = await shim.fetch('https://api.dropboxapi.com/oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
},
body: formBody
});
const responseText = await response.text();
if (!response.ok) throw new Error(responseText);
return JSON.parse(responseText);
}
async exec(method, path = '', body = null, headers = null, options = null) {
if (headers === null) headers = {};
if (options === null) options = {};
@@ -56,7 +97,7 @@ class DropboxApi {
const authToken = this.authToken();
if (authToken) headers['Authorization'] = authToken;
if (authToken) headers['Authorization'] = 'Bearer ' + authToken;
const endPointFormat = ['files/upload', 'files/download'].indexOf(path) >= 0 ? 'content' : 'api';
@@ -73,7 +114,7 @@ class DropboxApi {
if (options.path) fetchOptions.path = options.path;
if (body) fetchOptions.body = body;
const url = this.baseUrl(endPointFormat) + '/' + path;
const url = path.indexOf('https://') === 0 ? path : this.baseUrl(endPointFormat) + '/' + path;
let tryCount = 0;

View File

@@ -26,9 +26,18 @@ class SyncTargetDropbox extends BaseSyncTarget {
return _('Dropbox');
}
isAuthenticated() {
const f = this.fileApiSync();
return f && f.driver().api().authToken();
authRouteName() {
return 'DropboxLogin';
}
async isAuthenticated() {
const f = await this.fileApi();
return !!f.driver().api().authToken();
}
async api() {
const fileApi = await this.fileApi();
return fileApi.driver().api();
}
syncTargetId() {
@@ -36,7 +45,19 @@ class SyncTargetDropbox extends BaseSyncTarget {
}
async initFileApi() {
const api = new DropboxApi();
const params = parameters().dropbox;
const api = new DropboxApi({
id: params.id,
secret: params.secret,
});
const authJson = Setting.value('sync.' + SyncTargetDropbox.id() + '.auth');
if (authJson) {
const auth = JSON.parse(authJson);
api.setAuthToken(auth.access_token);
}
const appDir = '';
const fileApi = new FileApi(appDir, new FileApiDriverDropbox(api));
fileApi.setSyncTargetId(this.syncTargetId());
@@ -45,7 +66,7 @@ class SyncTargetDropbox extends BaseSyncTarget {
}
async initSynchronizer() {
if (!this.isAuthenticated()) throw new Error('User is not authentified');
if (!(await this.isAuthenticated())) throw new Error('User is not authentified');
return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType'));
}

View File

@@ -19,7 +19,7 @@ class SyncTargetFilesystem extends BaseSyncTarget {
return _('File system');
}
isAuthenticated() {
async isAuthenticated() {
return true;
}

View File

@@ -19,7 +19,7 @@ class SyncTargetMemory extends BaseSyncTarget {
return 'Memory';
}
isAuthenticated() {
async isAuthenticated() {
return true;
}

View File

@@ -28,7 +28,7 @@ class SyncTargetNextcloud extends BaseSyncTarget {
return _('Nextcloud');
}
isAuthenticated() {
async isAuthenticated() {
return true;
}

View File

@@ -26,7 +26,7 @@ class SyncTargetOneDrive extends BaseSyncTarget {
return _('OneDrive');
}
isAuthenticated() {
async isAuthenticated() {
return this.api().auth();
}
@@ -80,7 +80,7 @@ class SyncTargetOneDrive extends BaseSyncTarget {
}
async initSynchronizer() {
if (!this.isAuthenticated()) throw new Error('User is not authentified');
if (!await this.isAuthenticated()) throw new Error('User is not authentified');
return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType'));
}

View File

@@ -24,7 +24,7 @@ class SyncTargetWebDAV extends BaseSyncTarget {
return _('WebDAV');
}
isAuthenticated() {
async isAuthenticated() {
return true;
}

View File

@@ -36,7 +36,7 @@ shared.synchronize_press = async function(comp) {
const action = comp.props.syncStarted ? 'cancel' : 'start';
if (!reg.syncTarget().isAuthenticated()) {
if (!await reg.syncTarget().isAuthenticated()) {
if (reg.syncTarget().authRouteName()) {
comp.props.dispatch({
type: 'NAV_GO',

View File

@@ -99,7 +99,7 @@ class Setting extends BaseModel {
}},
'noteVisiblePanes': { value: ['editor', 'viewer'], type: Setting.TYPE_ARRAY, public: false, appTypes: ['desktop'] },
'showAdvancedOptions': { value: false, type: Setting.TYPE_BOOL, public: true, appTypes: ['mobile' ], label: () => _('Show advanced options') },
'sync.target': { value: SyncTargetRegistry.nameToId('onedrive'), type: Setting.TYPE_INT, isEnum: true, public: true, label: () => _('Synchronisation target'), description: (appType) => { return appType !== 'cli' ? null : _('The target to synchonise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below).') }, options: () => {
'sync.target': { value: SyncTargetRegistry.nameToId('dropbox'), type: Setting.TYPE_INT, isEnum: true, public: true, label: () => _('Synchronisation target'), description: (appType) => { return appType !== 'cli' ? null : _('The target to synchonise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below).') }, options: () => {
return SyncTargetRegistry.idAndLabelPlainObject();
}},
@@ -121,12 +121,14 @@ class Setting extends BaseModel {
'sync.3.auth': { value: '', type: Setting.TYPE_STRING, public: false },
'sync.4.auth': { value: '', type: Setting.TYPE_STRING, public: false },
'sync.7.auth': { value: '', type: Setting.TYPE_STRING, public: false },
'sync.1.context': { value: '', type: Setting.TYPE_STRING, public: false },
'sync.2.context': { value: '', type: Setting.TYPE_STRING, public: false },
'sync.3.context': { value: '', type: Setting.TYPE_STRING, public: false },
'sync.4.context': { value: '', type: Setting.TYPE_STRING, public: false },
'sync.5.context': { value: '', type: Setting.TYPE_STRING, public: false },
'sync.6.context': { value: '', type: Setting.TYPE_STRING, public: false },
'sync.7.context': { value: '', type: Setting.TYPE_STRING, public: false },
};
return this.metadata_;

View File

@@ -11,6 +11,10 @@ parameters_.dev = {
id: '606fd4d7-4dfb-4310-b8b7-a47d96aa22b6',
secret: 'qabchuPYL7931$ePDEQ3~_$',
},
dropbox: {
id: 'cx9li9ur8taq1z7',
secret: 'i8f9a1mvx3bijrt',
},
};
parameters_.prod = {
@@ -22,6 +26,10 @@ parameters_.prod = {
id: '606fd4d7-4dfb-4310-b8b7-a47d96aa22b6',
secret: 'qabchuPYL7931$ePDEQ3~_$',
},
dropbox: {
id: 'm044w3cvmxhzvop',
secret: 'r298deqisz0od56',
},
};
function parameters(env = null) {

View File

@@ -70,7 +70,7 @@ reg.scheduleSync = async (delay = null, syncOptions = null) => {
const syncTargetId = Setting.value('sync.target');
if (!reg.syncTarget(syncTargetId).isAuthenticated()) {
if (!await reg.syncTarget(syncTargetId).isAuthenticated()) {
reg.logger().info('Synchroniser is missing credentials - manual sync required to authenticate.');
promiseResolve();
return;