PageRenderTime 49ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/app/scripts/comp/dropbox-link.js

https://gitlab.com/mlnkv/keeweb
JavaScript | 330 lines | 296 code | 33 blank | 1 comment | 40 complexity | 110786474cd7704735ad9c6766e5fb3c MD5 | raw file
  1. 'use strict';
  2. var Dropbox = require('dropbox'),
  3. Alerts = require('./alerts'),
  4. Launcher = require('./launcher'),
  5. Logger = require('../util/logger'),
  6. Locale = require('../util/locale'),
  7. UrlUtil = require('../util/url-util'),
  8. AppSettingsModel = require('../models/app-settings-model');
  9. var logger = new Logger('dropbox');
  10. var DropboxKeys = {
  11. AppFolder: 'qp7ctun6qt5n9d6',
  12. FullDropbox: 'eor7hvv6u6oslq9'
  13. };
  14. var DropboxCustomErrors = {
  15. BadKey: 'bad-key'
  16. };
  17. function getKey() {
  18. return AppSettingsModel.instance.get('dropboxAppKey') || DropboxKeys.AppFolder;
  19. }
  20. var DropboxChooser = function(callback) {
  21. this.cb = callback;
  22. this.onMessage = this.onMessage.bind(this);
  23. };
  24. DropboxChooser.prototype.callback = function(err, res) {
  25. if (this.cb) {
  26. this.cb(err, res);
  27. }
  28. this.cb = null;
  29. };
  30. DropboxChooser.prototype.choose = function() {
  31. var windowFeatures = 'width=640,height=552,left=357,top=100,resizable=yes,location=yes';
  32. var url = this.buildUrl();
  33. this.popup = window.open(url, 'dropbox', windowFeatures);
  34. if (!this.popup) {
  35. return this.callback('Failed to open window');
  36. }
  37. window.addEventListener('message', this.onMessage);
  38. this.closeInt = setInterval(this.checkClose.bind(this), 200);
  39. };
  40. DropboxChooser.prototype.buildUrl = function() {
  41. var urlParams = {
  42. origin: encodeURIComponent(window.location.protocol + '//' + window.location.host),
  43. 'app_key': getKey(),
  44. 'link_type': 'direct',
  45. trigger: 'js',
  46. multiselect: 'false',
  47. extensions: '',
  48. folderselect: 'false',
  49. iframe: 'false',
  50. version: 2
  51. };
  52. return 'https://www.dropbox.com/chooser?' + Object.keys(urlParams).map(function(key) {
  53. return key + '=' + urlParams[key];
  54. }).join('&');
  55. };
  56. DropboxChooser.prototype.onMessage = function(e) {
  57. if (e.source !== this.popup) {
  58. return;
  59. }
  60. var data = JSON.parse(e.data);
  61. switch (data.method) {
  62. case 'origin_request':
  63. e.source.postMessage(JSON.stringify({ method: 'origin' }), 'https://www.dropbox.com');
  64. break;
  65. case 'files_selected':
  66. this.popup.close();
  67. this.success(data.params);
  68. break;
  69. case 'close_dialog':
  70. this.popup.close();
  71. break;
  72. case 'web_session_error':
  73. case 'web_session_unlinked':
  74. this.callback(data.method);
  75. break;
  76. case 'resize':
  77. this.popup.resize(data.params);
  78. break;
  79. case 'error':
  80. this.callback(data.params);
  81. break;
  82. }
  83. };
  84. DropboxChooser.prototype.checkClose = function() {
  85. if (this.popup.closed) {
  86. clearInterval(this.closeInt);
  87. window.removeEventListener('message', this.onMessage);
  88. if (!this.result) {
  89. this.callback('closed');
  90. }
  91. }
  92. };
  93. DropboxChooser.prototype.success = function(params) {
  94. /* jshint camelcase:false */
  95. if (!params || !params[0] || !params[0].link || params[0].is_dir) {
  96. return this.callback('bad result');
  97. }
  98. this.result = params[0];
  99. this.readFile(this.result.link);
  100. };
  101. DropboxChooser.prototype.readFile = function(url) {
  102. var xhr = new XMLHttpRequest();
  103. xhr.addEventListener('load', (function() {
  104. this.callback(null, { name: this.result.name, data: xhr.response });
  105. }).bind(this));
  106. xhr.addEventListener('error', this.callback.bind(this, 'download error'));
  107. xhr.addEventListener('abort', this.callback.bind(this, 'download abort'));
  108. xhr.open('GET', url);
  109. xhr.responseType = 'arraybuffer';
  110. xhr.send();
  111. };
  112. var DropboxLink = {
  113. ERROR_CONFLICT: Dropbox.ApiError.CONFLICT,
  114. ERROR_NOT_FOUND: Dropbox.ApiError.NOT_FOUND,
  115. Keys: DropboxKeys,
  116. _getClient: function(complete, overrideAppKey) {
  117. if (this._dropboxClient && this._dropboxClient.isAuthenticated()) {
  118. complete(null, this._dropboxClient);
  119. return;
  120. }
  121. if (!overrideAppKey && !this.isValidKey()) {
  122. return complete(DropboxCustomErrors.BadKey);
  123. }
  124. var client = new Dropbox.Client({key: overrideAppKey || getKey()});
  125. if (Launcher) {
  126. client.authDriver(new Dropbox.AuthDriver.Electron({ receiverUrl: location.href }));
  127. } else {
  128. client.authDriver(new Dropbox.AuthDriver.Popup({ receiverUrl: location.href }));
  129. }
  130. client.authenticate((function(error, client) {
  131. if (!error) {
  132. this._dropboxClient = client;
  133. }
  134. complete(error, client);
  135. }).bind(this));
  136. },
  137. _handleUiError: function(err, alertCallback, callback) {
  138. if (!alertCallback) {
  139. if (!Alerts.alertDisplayed) {
  140. alertCallback = Alerts.error.bind(Alerts);
  141. }
  142. }
  143. logger.error('Dropbox error', err);
  144. switch (err.status) {
  145. case Dropbox.ApiError.INVALID_TOKEN:
  146. if (!Alerts.alertDisplayed) {
  147. Alerts.yesno({
  148. icon: 'dropbox',
  149. header: Locale.dropboxLogin,
  150. body: Locale.dropboxLoginBody,
  151. buttons: [{result: 'yes', title: Locale.alertSignIn}, {result: '', title: Locale.alertCancel}],
  152. success: (function () {
  153. this.authenticate(function (err) { callback(!err); });
  154. }).bind(this),
  155. cancel: function () {
  156. callback(false);
  157. }
  158. });
  159. return;
  160. }
  161. break;
  162. case Dropbox.ApiError.NOT_FOUND:
  163. alertCallback({
  164. header: Locale.dropboxSyncError,
  165. body: Locale.dropboxNotFoundBody
  166. });
  167. break;
  168. case Dropbox.ApiError.OVER_QUOTA:
  169. alertCallback({
  170. header: Locale.dropboxFull,
  171. body: Locale.dropboxFullBody
  172. });
  173. break;
  174. case Dropbox.ApiError.RATE_LIMITED:
  175. alertCallback({
  176. header: Locale.dropboxSyncError,
  177. body: Locale.dropboxRateLimitedBody
  178. });
  179. break;
  180. case Dropbox.ApiError.NETWORK_ERROR:
  181. alertCallback({
  182. header: Locale.dropboxNetError,
  183. body: Locale.dropboxNetErrorBody
  184. });
  185. break;
  186. case Dropbox.ApiError.INVALID_PARAM:
  187. case Dropbox.ApiError.OAUTH_ERROR:
  188. case Dropbox.ApiError.INVALID_METHOD:
  189. alertCallback({
  190. header: Locale.dropboxSyncError,
  191. body: Locale.dropboxErrorBody + err.status
  192. });
  193. break;
  194. case Dropbox.ApiError.CONFLICT:
  195. break;
  196. default:
  197. alertCallback({
  198. header: Locale.dropboxSyncError,
  199. body: Locale.dropboxErrorRepeatBody + err
  200. });
  201. break;
  202. }
  203. callback(false);
  204. },
  205. _callAndHandleError: function(callName, args, callback, errorAlertCallback) {
  206. var that = this;
  207. this._getClient(function(err, client) {
  208. if (err) {
  209. return callback(err);
  210. }
  211. var ts = logger.ts();
  212. logger.debug('Call', callName);
  213. client[callName].apply(client, args.concat(function(err) {
  214. logger.debug('Result', callName, logger.ts(ts), arguments);
  215. if (err) {
  216. that._handleUiError(err, errorAlertCallback, function(repeat) {
  217. if (repeat) {
  218. that._callAndHandleError(callName, args, callback, errorAlertCallback);
  219. } else {
  220. callback(err);
  221. }
  222. });
  223. } else {
  224. callback.apply(null, arguments);
  225. }
  226. }));
  227. });
  228. },
  229. canUseBuiltInKeys: function() {
  230. var isSelfHosted = !/^http(s?):\/\/localhost:8085/.test(location.href) &&
  231. !/http(s?):\/\/(app|beta)\.keeweb\.info/.test(location.href);
  232. return !!Launcher || !isSelfHosted;
  233. },
  234. getKey: getKey,
  235. isValidKey: function() {
  236. var key = getKey();
  237. var isBuiltIn = key === DropboxKeys.AppFolder || key === DropboxKeys.FullDropbox;
  238. return key && key.indexOf(' ') < 0 && (!isBuiltIn || this.canUseBuiltInKeys());
  239. },
  240. authenticate: function(complete, overrideAppKey) {
  241. this._getClient(function(err) { complete(err); }, overrideAppKey);
  242. },
  243. logout: function() {
  244. if (this._dropboxClient) {
  245. try {
  246. this._dropboxClient.signOut();
  247. } catch (e) {
  248. } finally {
  249. this._dropboxClient.reset();
  250. }
  251. }
  252. },
  253. resetClient: function() {
  254. this._dropboxClient = null;
  255. },
  256. receive: function() {
  257. Dropbox.AuthDriver.Popup.oauthReceiver();
  258. },
  259. saveFile: function(fileName, data, rev, complete, alertCallback) {
  260. if (rev) {
  261. var opts = typeof rev === 'string' ? { lastVersionTag: rev, noOverwrite: true, noAutoRename: true } : undefined;
  262. this._callAndHandleError('writeFile', [fileName, data, opts], complete, alertCallback);
  263. } else {
  264. var dir = UrlUtil.fileToDir(fileName);
  265. this.list(dir, (function(err, files) {
  266. if (err) { return complete(err); }
  267. var exists = files.some(function(file) { return file.toLowerCase() === fileName.toLowerCase(); });
  268. if (exists) { return complete({ exists: true }); }
  269. this._callAndHandleError('writeFile', [fileName, data], complete);
  270. }).bind(this));
  271. }
  272. },
  273. openFile: function(fileName, complete, errorAlertCallback) {
  274. this._callAndHandleError('readFile', [fileName, { arrayBuffer: true }], complete, errorAlertCallback);
  275. },
  276. stat: function(fileName, complete, errorAlertCallback) {
  277. this._callAndHandleError('stat', [fileName], complete, errorAlertCallback);
  278. },
  279. list: function(dir, complete) {
  280. this._callAndHandleError('readdir', [dir || ''], function(err, files, dirStat, filesStat) {
  281. if (files) {
  282. files = files.filter(function(f) { return /\.kdbx$/i.test(f); });
  283. }
  284. complete(err, files, dirStat, filesStat);
  285. });
  286. },
  287. deleteFile: function(fileName, complete) {
  288. this._callAndHandleError('remove', [fileName], complete);
  289. },
  290. canChooseFile: function() {
  291. return !Launcher;
  292. },
  293. chooseFile: function(callback) {
  294. new DropboxChooser(callback).choose();
  295. }
  296. };
  297. module.exports = DropboxLink;