'use strict'; var Dropbox = require('dropbox'), Alerts = require('./alerts'), Launcher = require('./launcher'), Logger = require('../util/logger'), Locale = require('../util/locale'), UrlUtil = require('../util/url-util'), AppSettingsModel = require('../models/app-settings-model'); var logger = new Logger('dropbox'); var DropboxKeys = { AppFolder: 'qp7ctun6qt5n9d6', FullDropbox: 'eor7hvv6u6oslq9' }; var DropboxCustomErrors = { BadKey: 'bad-key' }; function getKey() { return AppSettingsModel.instance.get('dropboxAppKey') || DropboxKeys.AppFolder; } var DropboxChooser = function(callback) { this.cb = callback; this.onMessage = this.onMessage.bind(this); }; DropboxChooser.prototype.callback = function(err, res) { if (this.cb) { this.cb(err, res); } this.cb = null; }; DropboxChooser.prototype.choose = function() { var windowFeatures = 'width=640,height=552,left=357,top=100,resizable=yes,location=yes'; var url = this.buildUrl(); this.popup = window.open(url, 'dropbox', windowFeatures); if (!this.popup) { return this.callback('Failed to open window'); } window.addEventListener('message', this.onMessage); this.closeInt = setInterval(this.checkClose.bind(this), 200); }; DropboxChooser.prototype.buildUrl = function() { var urlParams = { origin: encodeURIComponent(window.location.protocol + '//' + window.location.host), 'app_key': getKey(), 'link_type': 'direct', trigger: 'js', multiselect: 'false', extensions: '', folderselect: 'false', iframe: 'false', version: 2 }; return 'https://www.dropbox.com/chooser?' + Object.keys(urlParams).map(function(key) { return key + '=' + urlParams[key]; }).join('&'); }; DropboxChooser.prototype.onMessage = function(e) { if (e.source !== this.popup) { return; } var data = JSON.parse(e.data); switch (data.method) { case 'origin_request': e.source.postMessage(JSON.stringify({ method: 'origin' }), 'https://www.dropbox.com'); break; case 'files_selected': this.popup.close(); this.success(data.params); break; case 'close_dialog': this.popup.close(); break; case 'web_session_error': case 'web_session_unlinked': this.callback(data.method); break; case 'resize': this.popup.resize(data.params); break; case 'error': this.callback(data.params); break; } }; DropboxChooser.prototype.checkClose = function() { if (this.popup.closed) { clearInterval(this.closeInt); window.removeEventListener('message', this.onMessage); if (!this.result) { this.callback('closed'); } } }; DropboxChooser.prototype.success = function(params) { /* jshint camelcase:false */ if (!params || !params[0] || !params[0].link || params[0].is_dir) { return this.callback('bad result'); } this.result = params[0]; this.readFile(this.result.link); }; DropboxChooser.prototype.readFile = function(url) { var xhr = new XMLHttpRequest(); xhr.addEventListener('load', (function() { this.callback(null, { name: this.result.name, data: xhr.response }); }).bind(this)); xhr.addEventListener('error', this.callback.bind(this, 'download error')); xhr.addEventListener('abort', this.callback.bind(this, 'download abort')); xhr.open('GET', url); xhr.responseType = 'arraybuffer'; xhr.send(); }; var DropboxLink = { ERROR_CONFLICT: Dropbox.ApiError.CONFLICT, ERROR_NOT_FOUND: Dropbox.ApiError.NOT_FOUND, Keys: DropboxKeys, _getClient: function(complete, overrideAppKey) { if (this._dropboxClient && this._dropboxClient.isAuthenticated()) { complete(null, this._dropboxClient); return; } if (!overrideAppKey && !this.isValidKey()) { return complete(DropboxCustomErrors.BadKey); } var client = new Dropbox.Client({key: overrideAppKey || getKey()}); if (Launcher) { client.authDriver(new Dropbox.AuthDriver.Electron({ receiverUrl: location.href })); } else { client.authDriver(new Dropbox.AuthDriver.Popup({ receiverUrl: location.href })); } client.authenticate((function(error, client) { if (!error) { this._dropboxClient = client; } complete(error, client); }).bind(this)); }, _handleUiError: function(err, alertCallback, callback) { if (!alertCallback) { if (!Alerts.alertDisplayed) { alertCallback = Alerts.error.bind(Alerts); } } logger.error('Dropbox error', err); switch (err.status) { case Dropbox.ApiError.INVALID_TOKEN: if (!Alerts.alertDisplayed) { Alerts.yesno({ icon: 'dropbox', header: Locale.dropboxLogin, body: Locale.dropboxLoginBody, buttons: [{result: 'yes', title: Locale.alertSignIn}, {result: '', title: Locale.alertCancel}], success: (function () { this.authenticate(function (err) { callback(!err); }); }).bind(this), cancel: function () { callback(false); } }); return; } break; case Dropbox.ApiError.NOT_FOUND: alertCallback({ header: Locale.dropboxSyncError, body: Locale.dropboxNotFoundBody }); break; case Dropbox.ApiError.OVER_QUOTA: alertCallback({ header: Locale.dropboxFull, body: Locale.dropboxFullBody }); break; case Dropbox.ApiError.RATE_LIMITED: alertCallback({ header: Locale.dropboxSyncError, body: Locale.dropboxRateLimitedBody }); break; case Dropbox.ApiError.NETWORK_ERROR: alertCallback({ header: Locale.dropboxNetError, body: Locale.dropboxNetErrorBody }); break; case Dropbox.ApiError.INVALID_PARAM: case Dropbox.ApiError.OAUTH_ERROR: case Dropbox.ApiError.INVALID_METHOD: alertCallback({ header: Locale.dropboxSyncError, body: Locale.dropboxErrorBody + err.status }); break; case Dropbox.ApiError.CONFLICT: break; default: alertCallback({ header: Locale.dropboxSyncError, body: Locale.dropboxErrorRepeatBody + err }); break; } callback(false); }, _callAndHandleError: function(callName, args, callback, errorAlertCallback) { var that = this; this._getClient(function(err, client) { if (err) { return callback(err); } var ts = logger.ts(); logger.debug('Call', callName); client[callName].apply(client, args.concat(function(err) { logger.debug('Result', callName, logger.ts(ts), arguments); if (err) { that._handleUiError(err, errorAlertCallback, function(repeat) { if (repeat) { that._callAndHandleError(callName, args, callback, errorAlertCallback); } else { callback(err); } }); } else { callback.apply(null, arguments); } })); }); }, canUseBuiltInKeys: function() { var isSelfHosted = !/^http(s?):\/\/localhost:8085/.test(location.href) && !/http(s?):\/\/(app|beta)\.keeweb\.info/.test(location.href); return !!Launcher || !isSelfHosted; }, getKey: getKey, isValidKey: function() { var key = getKey(); var isBuiltIn = key === DropboxKeys.AppFolder || key === DropboxKeys.FullDropbox; return key && key.indexOf(' ') < 0 && (!isBuiltIn || this.canUseBuiltInKeys()); }, authenticate: function(complete, overrideAppKey) { this._getClient(function(err) { complete(err); }, overrideAppKey); }, logout: function() { if (this._dropboxClient) { try { this._dropboxClient.signOut(); } catch (e) { } finally { this._dropboxClient.reset(); } } }, resetClient: function() { this._dropboxClient = null; }, receive: function() { Dropbox.AuthDriver.Popup.oauthReceiver(); }, saveFile: function(fileName, data, rev, complete, alertCallback) { if (rev) { var opts = typeof rev === 'string' ? { lastVersionTag: rev, noOverwrite: true, noAutoRename: true } : undefined; this._callAndHandleError('writeFile', [fileName, data, opts], complete, alertCallback); } else { var dir = UrlUtil.fileToDir(fileName); this.list(dir, (function(err, files) { if (err) { return complete(err); } var exists = files.some(function(file) { return file.toLowerCase() === fileName.toLowerCase(); }); if (exists) { return complete({ exists: true }); } this._callAndHandleError('writeFile', [fileName, data], complete); }).bind(this)); } }, openFile: function(fileName, complete, errorAlertCallback) { this._callAndHandleError('readFile', [fileName, { arrayBuffer: true }], complete, errorAlertCallback); }, stat: function(fileName, complete, errorAlertCallback) { this._callAndHandleError('stat', [fileName], complete, errorAlertCallback); }, list: function(dir, complete) { this._callAndHandleError('readdir', [dir || ''], function(err, files, dirStat, filesStat) { if (files) { files = files.filter(function(f) { return /\.kdbx$/i.test(f); }); } complete(err, files, dirStat, filesStat); }); }, deleteFile: function(fileName, complete) { this._callAndHandleError('remove', [fileName], complete); }, canChooseFile: function() { return !Launcher; }, chooseFile: function(callback) { new DropboxChooser(callback).choose(); } }; module.exports = DropboxLink;