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

203 lines
8.2 KiB
JavaScript
Raw Normal View History

2016-03-13 17:08:25 +01:00
'use strict';
2017-01-31 07:50:28 +01:00
const StorageBase = require('./storage-base');
2016-03-13 17:08:25 +01:00
2017-01-31 07:50:28 +01:00
const GDriveClientId = '847548101761-koqkji474gp3i2gn3k5omipbfju7pbt1.apps.googleusercontent.com';
const NewFileIdPrefix = 'NewFile:';
2016-03-27 11:08:54 +02:00
2017-01-31 07:50:28 +01:00
const StorageGDrive = StorageBase.extend({
2016-03-13 17:08:25 +01:00
name: 'gdrive',
2016-03-26 23:04:34 +01:00
enabled: true,
2016-03-13 17:08:25 +01:00
uipos: 30,
iconSvg: '<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" version="1.1">' +
'<path d="M120.76421 71.989219 84.87226 9.6679848l-41.828196 0 35.899791 62.3212342zM58.014073 56.294956 37.107816 19.986746 1.2237094 82.284404 ' +
'22.137808 118.59261Zm-21.415974 63.012814 69.180421 0 20.9141-39.459631-67.635587 0z"/></svg>',
2016-03-27 14:57:22 +02:00
_baseUrl: 'https://www.googleapis.com/drive/v3',
2016-03-27 11:08:54 +02:00
2016-03-27 20:14:31 +02:00
getPathForName: function(fileName) {
return NewFileIdPrefix + fileName;
},
2016-03-13 17:08:25 +01:00
load: function(path, opts, callback) {
this.stat(path, opts, (err, stat) => {
2016-03-26 21:12:56 +01:00
if (err) { return callback && callback(err); }
this.logger.debug('Load', path);
2017-01-31 07:50:28 +01:00
const ts = this.logger.ts();
const url = this._baseUrl + '/files/{id}/revisions/{rev}?alt=media'
2016-03-26 21:12:56 +01:00
.replace('{id}', path)
.replace('{rev}', stat.rev);
this._xhr({
2016-03-27 09:42:48 +02:00
url: url,
responseType: 'arraybuffer',
success: (response) => {
this.logger.debug('Loaded', path, stat.rev, this.logger.ts(ts));
2016-03-27 09:42:48 +02:00
return callback && callback(null, response, { rev: stat.rev });
},
error: (err) => {
this.logger.error('Load error', path, err, this.logger.ts(ts));
2016-03-27 09:42:48 +02:00
return callback && callback(err);
2016-03-26 21:12:56 +01:00
}
});
});
2016-03-13 17:08:25 +01:00
},
stat: function(path, opts, callback) {
2016-03-27 20:14:31 +02:00
if (path.lastIndexOf(NewFileIdPrefix, 0) === 0) {
return callback && callback({ notFound: true });
}
2016-07-17 13:30:38 +02:00
this._oauthAuthorize(err => {
2016-03-26 21:12:56 +01:00
if (err) {
return callback && callback(err);
}
this.logger.debug('Stat', path);
2017-01-31 07:50:28 +01:00
const ts = this.logger.ts();
const url = this._baseUrl + '/files/{id}?fields=headRevisionId'
2016-03-27 09:42:48 +02:00
.replace('{id}', path);
this._xhr({
2016-03-27 09:42:48 +02:00
url: url,
responseType: 'json',
success: (response) => {
2017-01-31 07:50:28 +01:00
const rev = response.headRevisionId;
this.logger.debug('Stated', path, rev, this.logger.ts(ts));
2016-03-27 09:42:48 +02:00
return callback && callback(null, { rev: rev });
},
error: (err) => {
this.logger.error('Stat error', this.logger.ts(ts), err);
2016-03-27 09:42:48 +02:00
return callback && callback(err);
}
2016-03-26 21:12:56 +01:00
});
});
},
save: function(path, opts, data, callback, rev) {
2016-08-21 17:43:59 +02:00
this._oauthAuthorize(err => {
if (err) {
return callback && callback(err);
2016-03-27 20:14:31 +02:00
}
2016-08-21 17:43:59 +02:00
this.stat(path, opts, (err, stat) => {
if (rev) {
if (err) {
return callback && callback(err);
}
if (stat.rev !== rev) {
return callback && callback({ revConflict: true }, stat);
2016-03-27 09:42:48 +02:00
}
2016-03-26 21:12:56 +01:00
}
2016-08-21 17:43:59 +02:00
this.logger.debug('Save', path);
2017-01-31 07:50:28 +01:00
const ts = this.logger.ts();
const isNew = path.lastIndexOf(NewFileIdPrefix, 0) === 0;
let url;
2016-08-21 17:43:59 +02:00
if (isNew) {
url = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,headRevisionId';
2017-01-31 07:50:28 +01:00
const fileName = path.replace(NewFileIdPrefix, '') + '.kdbx';
const boundry = 'b' + Date.now() + 'x' + Math.round(Math.random() * 1000000);
2016-08-21 17:43:59 +02:00
data = new Blob([
'--', boundry, '\r\n',
'Content-Type: application/json; charset=UTF-8', '\r\n\r\n',
JSON.stringify({ name: fileName }), '\r\n',
'--', boundry, '\r\n',
'Content-Type: application/octet-stream', '\r\n\r\n',
data, '\r\n',
'--', boundry, '--', '\r\n'
], { type: 'multipart/related; boundary="' + boundry + '"' });
} else {
url = 'https://www.googleapis.com/upload/drive/v3/files/{id}?uploadType=media&fields=headRevisionId'
.replace('{id}', path);
data = new Blob([data], { type: 'application/octet-stream' });
}
this._xhr({
url: url,
method: isNew ? 'POST' : 'PATCH',
responseType: 'json',
data: data,
success: (response) => {
this.logger.debug('Saved', path, this.logger.ts(ts));
2017-01-31 07:50:28 +01:00
const newRev = response.headRevisionId;
2016-08-21 17:43:59 +02:00
if (!newRev) {
return callback && callback('save error: no rev');
}
return callback && callback(null, { rev: newRev, path: isNew ? response.id : null });
},
error: (err) => {
this.logger.error('Save error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
});
2016-03-26 21:12:56 +01:00
});
});
},
list: function(callback) {
2016-07-17 13:30:38 +02:00
this._oauthAuthorize((err) => {
2016-03-26 21:12:56 +01:00
if (err) { return callback && callback(err); }
this.logger.debug('List');
2017-01-31 07:50:28 +01:00
const url = this._baseUrl + '/files?fields={fields}&q={q}'
2016-03-27 09:42:48 +02:00
.replace('{fields}', encodeURIComponent('files'))
2016-04-13 22:28:36 +02:00
.replace('{q}', encodeURIComponent('fileExtension="kdbx" and trashed=false'));
2017-01-31 07:50:28 +01:00
const ts = this.logger.ts();
this._xhr({
2016-03-27 09:42:48 +02:00
url: url,
responseType: 'json',
success: (response) => {
2016-03-27 09:42:48 +02:00
if (!response) {
this.logger.error('List error', this.logger.ts(ts));
2016-03-27 09:42:48 +02:00
return callback && callback('list error');
}
this.logger.debug('Listed', this.logger.ts(ts));
2017-01-31 07:50:28 +01:00
const fileList = response.files.map(f => ({
2016-07-17 13:30:38 +02:00
name: f.name,
path: f.id,
rev: f.headRevisionId
}));
2016-03-27 09:42:48 +02:00
return callback && callback(null, fileList);
},
error: (err) => {
this.logger.error('List error', this.logger.ts(ts), err);
2016-03-27 09:42:48 +02:00
return callback && callback(err);
2016-03-26 21:12:56 +01:00
}
});
});
},
2016-03-27 20:14:31 +02:00
remove: function(path, callback) {
this.logger.debug('Remove', path);
2017-01-31 07:50:28 +01:00
const ts = this.logger.ts();
const url = this._baseUrl + '/files/{id}'.replace('{id}', path);
this._xhr({
2016-03-27 20:14:31 +02:00
url: url,
method: 'DELETE',
responseType: 'json',
statuses: [200, 204],
success: () => {
this.logger.debug('Removed', path, this.logger.ts(ts));
2016-03-27 20:14:31 +02:00
return callback && callback();
},
error: (err) => {
this.logger.error('Remove error', path, err, this.logger.ts(ts));
2016-03-27 20:14:31 +02:00
return callback && callback(err);
}
});
},
setEnabled: function(enabled) {
if (!enabled) {
this._oauthRevokeToken('https://accounts.google.com/o/oauth2/revoke?token={token}');
}
StorageBase.prototype.setEnabled.call(this, enabled);
},
2016-03-27 15:18:05 +02:00
_getOAuthConfig: function() {
2017-01-31 07:50:28 +01:00
const clientId = this.appSettings.get('gdriveClientId') || GDriveClientId;
2016-03-27 15:18:05 +02:00
return {
2016-03-27 16:47:29 +02:00
scope: 'https://www.googleapis.com/auth/drive',
url: 'https://accounts.google.com/o/oauth2/v2/auth',
clientId: clientId,
2016-03-27 14:57:22 +02:00
width: 600,
height: 400
2016-03-27 15:18:05 +02:00
};
2016-03-13 17:08:25 +01:00
}
2016-03-27 09:06:23 +02:00
});
2016-03-13 17:08:25 +01:00
2016-03-27 09:46:43 +02:00
module.exports = new StorageGDrive();