keeweb/app/scripts/storage/storage-dropbox.js

370 lines
12 KiB
JavaScript
Raw Normal View History

2017-01-31 07:50:28 +01:00
const StorageBase = require('./storage-base');
const UrlUtil = require('../util/url-util');
2017-05-28 17:30:00 +02:00
const FeatureDetector = require('../util/feature-detector');
2017-04-16 17:00:35 +02:00
const DropboxKeys = {
AppFolder: 'qp7ctun6qt5n9d6',
FullDropbox: 'eor7hvv6u6oslq9'
};
const DropboxCustomErrors = {
BadKey: 'bad-key'
};
2015-12-12 09:53:50 +01:00
2017-01-31 07:50:28 +01:00
const StorageDropbox = StorageBase.extend({
2015-12-02 21:39:40 +01:00
name: 'dropbox',
2016-03-12 12:22:35 +01:00
icon: 'dropbox',
2015-12-02 21:39:40 +01:00
enabled: true,
2016-03-13 17:08:25 +01:00
uipos: 20,
2016-08-21 18:07:59 +02:00
backup: true,
2015-12-02 21:39:40 +01:00
2016-03-14 06:04:55 +01:00
_toFullPath: function(path) {
2017-01-31 07:50:28 +01:00
const rootFolder = this.appSettings.get('dropboxFolder');
2016-03-14 06:04:55 +01:00
if (rootFolder) {
2016-04-03 14:56:38 +02:00
path = UrlUtil.fixSlashes('/' + rootFolder + '/' + path);
2016-03-14 06:04:55 +01:00
}
return path;
},
_toRelPath: function(path) {
2017-01-31 07:50:28 +01:00
const rootFolder = this.appSettings.get('dropboxFolder');
2016-03-14 06:04:55 +01:00
if (rootFolder) {
2017-01-31 07:50:28 +01:00
const ix = path.toLowerCase().indexOf(rootFolder.toLowerCase());
2016-03-14 06:04:55 +01:00
if (ix === 0) {
path = path.substr(rootFolder.length);
} else if (ix === 1) {
path = path.substr(rootFolder.length + 1);
}
2016-04-03 14:56:38 +02:00
path = UrlUtil.fixSlashes('/' + path);
2016-03-14 06:04:55 +01:00
}
return path;
},
_fixConfigFolder: function(folder) {
folder = folder.replace(/\\/g, '/').trim();
if (folder[0] === '/') {
folder = folder.substr(1);
}
return folder;
},
2017-04-16 17:00:35 +02:00
_getKey: function() {
return this.appSettings.get('dropboxAppKey') || DropboxKeys.AppFolder;
},
_isValidKey: function() {
const key = this._getKey();
const isBuiltIn = key === DropboxKeys.AppFolder || key === DropboxKeys.FullDropbox;
return key && key.indexOf(' ') < 0 && (!isBuiltIn || this._canUseBuiltInKeys());
},
_canUseBuiltInKeys: function() {
2017-05-28 17:30:00 +02:00
return !FeatureDetector.isSelfHosted;
2017-04-16 17:00:35 +02:00
},
_getOAuthConfig: function() {
return {
scope: '',
url: 'https://www.dropbox.com/oauth2/authorize',
clientId: this._getKey(),
width: 600,
height: 400
};
},
2016-03-13 17:08:25 +01:00
needShowOpenConfig: function() {
2017-04-16 17:00:35 +02:00
return !this._isValidKey();
2016-03-13 17:45:55 +01:00
},
getOpenConfig: function() {
return {
desc: 'dropboxSetupDesc',
fields: [
2019-08-16 23:05:39 +02:00
{
id: 'key',
title: 'dropboxAppKey',
desc: 'dropboxAppKeyDesc',
type: 'text',
required: true,
pattern: '\\w+'
},
{
id: 'folder',
title: 'dropboxFolder',
desc: 'dropboxFolderDesc',
type: 'text',
placeholder: 'dropboxFolderPlaceholder'
}
2016-03-13 17:45:55 +01:00
]
};
},
getSettingsConfig: function() {
2017-01-31 07:50:28 +01:00
const fields = [];
2017-04-16 17:00:35 +02:00
const appKey = this._getKey();
2019-08-16 23:05:39 +02:00
const linkField = {
id: 'link',
title: 'dropboxLink',
type: 'select',
value: 'custom',
options: { app: 'dropboxLinkApp', full: 'dropboxLinkFull', custom: 'dropboxLinkCustom' }
};
const keyField = {
id: 'key',
title: 'dropboxAppKey',
desc: 'dropboxAppKeyDesc',
type: 'text',
required: true,
pattern: '\\w+',
value: appKey
};
const folderField = {
id: 'folder',
title: 'dropboxFolder',
desc: 'dropboxFolderSettingsDesc',
type: 'text',
value: this.appSettings.get('dropboxFolder') || ''
};
2017-04-16 17:00:35 +02:00
const canUseBuiltInKeys = this._canUseBuiltInKeys();
if (canUseBuiltInKeys) {
fields.push(linkField);
2017-04-16 17:00:35 +02:00
if (appKey === DropboxKeys.AppFolder) {
linkField.value = 'app';
2017-04-16 17:00:35 +02:00
} else if (appKey === DropboxKeys.FullDropbox) {
linkField.value = 'full';
fields.push(folderField);
} else {
fields.push(keyField);
fields.push(folderField);
}
} else {
fields.push(keyField);
fields.push(folderField);
}
return { fields: fields };
},
2016-03-13 17:45:55 +01:00
applyConfig: function(config, callback) {
2017-04-16 17:00:35 +02:00
if (config.key === DropboxKeys.AppFolder || config.key === DropboxKeys.FullDropbox) {
return callback(DropboxCustomErrors.BadKey);
}
// TODO: try to connect using new key
if (config.folder) {
config.folder = this._fixConfigFolder(config.folder);
}
this.appSettings.set({
dropboxAppKey: config.key,
dropboxFolder: config.folder
});
callback();
2016-03-13 17:08:25 +01:00
},
applySetting: function(key, value) {
switch (key) {
case 'link':
key = 'dropboxAppKey';
switch (value) {
case 'app':
2017-04-16 17:00:35 +02:00
value = DropboxKeys.AppFolder;
break;
case 'full':
2017-04-16 17:00:35 +02:00
value = DropboxKeys.FullDropbox;
break;
case 'custom':
value = '(your app key)';
break;
2016-03-17 22:49:39 +01:00
default:
return;
}
2017-04-16 17:00:35 +02:00
this._oauthRevokeToken();
break;
case 'key':
key = 'dropboxAppKey';
2017-04-16 17:00:35 +02:00
this._oauthRevokeToken();
break;
case 'folder':
key = 'dropboxFolder';
value = this._fixConfigFolder(value);
break;
default:
return;
}
2016-03-27 09:06:23 +02:00
this.appSettings.set(key, value);
},
2015-12-12 16:43:43 +01:00
getPathForName: function(fileName) {
return '/' + fileName + '.kdbx';
},
_encodeJsonHttpHeader(json) {
return json.replace(/[\u007f-\uffff]/g, c => '\\u' + ('000' + c.charCodeAt(0).toString(16)).slice(-4));
},
2017-04-16 17:00:35 +02:00
_apiCall: function(args) {
this._oauthAuthorize(err => {
if (err) {
return args.error(err);
}
const host = args.host || 'api';
let headers;
let data = args.data;
if (args.apiArg) {
headers = { 'Dropbox-API-Arg': this._encodeJsonHttpHeader(JSON.stringify(args.apiArg)) };
2017-04-16 17:00:35 +02:00
if (args.data) {
headers['Content-Type'] = 'application/octet-stream';
}
} else if (args.data) {
data = JSON.stringify(data);
headers = {
'Content-Type': 'application/json'
};
}
this._xhr({
url: `https://${host}.dropboxapi.com/2/${args.method}`,
method: 'POST',
responseType: args.responseType || 'json',
headers: headers,
data: data,
statuses: args.statuses || undefined,
success: args.success,
error: (e, xhr) => {
2019-08-16 23:05:39 +02:00
let err = (xhr.response && xhr.response.error) || new Error('Network error');
2017-04-16 17:00:35 +02:00
if (err && err.path && err.path['.tag'] === 'not_found') {
err = new Error('File removed');
err.notFound = true;
this.logger.debug('File not found', args.method);
} else {
this.logger.error('API error', args.method, xhr.status, err);
}
err.status = xhr.status;
args.error(err);
}
});
});
},
2016-03-12 17:49:52 +01:00
load: function(path, opts, callback) {
2016-07-17 13:30:38 +02:00
this.logger.debug('Load', path);
2017-01-31 07:50:28 +01:00
const ts = this.logger.ts();
2016-07-17 13:30:38 +02:00
path = this._toFullPath(path);
2017-04-16 17:00:35 +02:00
this._apiCall({
method: 'files/download',
host: 'content',
apiArg: { path },
responseType: 'arraybuffer',
success: (response, xhr) => {
const stat = JSON.parse(xhr.getResponseHeader('dropbox-api-result'));
this.logger.debug('Loaded', path, stat.rev, this.logger.ts(ts));
callback(null, response, { rev: stat.rev });
},
error: callback
});
2015-12-08 20:18:35 +01:00
},
2016-03-12 17:49:52 +01:00
stat: function(path, opts, callback) {
2016-07-17 13:30:38 +02:00
this.logger.debug('Stat', path);
2017-01-31 07:50:28 +01:00
const ts = this.logger.ts();
2016-07-17 13:30:38 +02:00
path = this._toFullPath(path);
2017-04-16 17:00:35 +02:00
this._apiCall({
method: 'files/get_metadata',
data: { path },
success: stat => {
if (stat['.tag'] === 'file') {
stat = { rev: stat.rev };
} else if (stat['.tag'] === 'folder') {
stat = { folder: true };
}
this.logger.debug('Stated', path, stat.folder ? 'folder' : stat.rev, this.logger.ts(ts));
2019-08-16 23:05:39 +02:00
if (callback) {
callback(null, stat);
}
2017-04-16 17:00:35 +02:00
},
error: callback
});
2015-12-02 21:39:40 +01:00
},
2016-03-12 17:49:52 +01:00
save: function(path, opts, data, callback, rev) {
2016-07-17 13:30:38 +02:00
this.logger.debug('Save', path, rev);
2017-01-31 07:50:28 +01:00
const ts = this.logger.ts();
2016-07-17 13:30:38 +02:00
path = this._toFullPath(path);
2017-04-16 17:00:35 +02:00
const arg = {
path,
mode: rev ? { '.tag': 'update', update: rev } : { '.tag': 'overwrite' }
};
this._apiCall({
method: 'files/upload',
host: 'content',
apiArg: arg,
data: data,
responseType: 'json',
success: stat => {
this.logger.debug('Saved', path, stat.rev, this.logger.ts(ts));
callback(null, { rev: stat.rev });
},
error: callback
});
2016-03-13 17:08:25 +01:00
},
2017-11-26 17:26:58 +01:00
list: function(dir, callback) {
2017-04-16 17:00:35 +02:00
this.logger.debug('List');
const ts = this.logger.ts();
this._apiCall({
method: 'files/list_folder',
data: {
2017-11-26 17:26:58 +01:00
path: this._toFullPath(dir || ''),
2017-04-16 17:00:35 +02:00
recursive: false
},
success: data => {
this.logger.debug('Listed', this.logger.ts(ts));
2019-08-16 23:05:39 +02:00
const fileList = data.entries.map(f => ({
name: f.name,
path: this._toRelPath(f['path_display']),
rev: f.rev,
dir: f['.tag'] !== 'file'
}));
2017-04-16 17:00:35 +02:00
callback(null, fileList);
},
error: callback
2016-03-13 17:08:25 +01:00
});
2016-03-27 18:38:33 +02:00
},
remove: function(path, callback) {
2016-07-17 13:30:38 +02:00
this.logger.debug('Remove', path);
2017-01-31 07:50:28 +01:00
const ts = this.logger.ts();
2016-07-17 13:30:38 +02:00
path = this._toFullPath(path);
2017-04-16 17:00:35 +02:00
this._apiCall({
method: 'files/delete',
data: { path },
success: () => {
this.logger.debug('Removed', path, this.logger.ts(ts));
callback();
},
error: callback
});
},
2016-08-21 17:43:59 +02:00
mkdir: function(path, callback) {
2017-04-16 17:00:35 +02:00
this.logger.debug('Make dir', path);
const ts = this.logger.ts();
path = this._toFullPath(path);
this._apiCall({
method: 'files/create_folder',
data: { path },
success: () => {
2016-08-21 17:43:59 +02:00
this.logger.debug('Made dir', path, this.logger.ts(ts));
2017-04-16 17:00:35 +02:00
callback();
},
error: callback
2016-08-21 17:43:59 +02:00
});
},
setEnabled: function(enabled) {
if (!enabled) {
2017-04-16 17:00:35 +02:00
this._oauthRevokeToken();
}
StorageBase.prototype.setEnabled.call(this, enabled);
2015-12-02 21:39:40 +01:00
}
2016-03-27 09:06:23 +02:00
});
2015-12-02 21:39:40 +01:00
2016-03-27 09:46:43 +02:00
module.exports = new StorageDropbox();