From 942d98d7985a31372544c0be14edc88c246056cd Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Mon, 6 Nov 2017 11:55:17 -0500 Subject: [PATCH] convert sync service to ts --- src/background.html | 1 - src/background.js | 43 ++-- .../app/settings/settingsSyncController.js | 6 +- src/popup/app/vault/vaultController.js | 4 +- src/services/cipher.service.ts | 2 +- src/services/crypto.service.ts | 4 +- src/services/folder.service.ts | 2 +- src/services/settings.service.ts | 2 +- src/services/sync.service.ts | 168 ++++++++++++++ src/services/syncService.js | 210 ------------------ 10 files changed, 199 insertions(+), 243 deletions(-) create mode 100644 src/services/sync.service.ts delete mode 100644 src/services/syncService.js diff --git a/src/background.html b/src/background.html index 37a8daa853..e40241b1da 100644 --- a/src/background.html +++ b/src/background.html @@ -4,7 +4,6 @@ - diff --git a/src/background.js b/src/background.js index 5b606eeb27..dc5b3bd3cc 100644 --- a/src/background.js +++ b/src/background.js @@ -10,6 +10,7 @@ import i18nService from './services/i18nService.js'; import LockService from './services/lockService.js'; import PasswordGenerationService from './services/passwordGeneration.service'; import SettingsService from './services/settings.service'; +import SyncService from './services/sync.service'; import TokenService from './services/token.service'; import TotpService from './services/totp.service'; import UserService from './services/user.service'; @@ -103,8 +104,7 @@ var bg_isBackground = true, window.bg_folderService = bg_folderService = new FolderService(bg_cryptoService, bg_userService, bg_i18nService, bg_apiService); window.bg_lockService = bg_lockService = new LockService(bg_constantsService, bg_cryptoService, bg_folderService, bg_cipherService, bg_utilsService, setIcon, refreshBadgeAndMenu); - window.bg_syncService = bg_syncService = new SyncService(bg_cipherService, bg_folderService, bg_userService, bg_apiService, bg_settingsService, - bg_cryptoService, logout); + window.bg_syncService = bg_syncService = new SyncService(bg_userService, bg_apiService, bg_settingsService, bg_folderService, bg_cipherService, bg_cryptoService, logout); window.bg_passwordGenerationService = bg_passwordGenerationService = new PasswordGenerationService(bg_cryptoService); window.bg_totpService = bg_totpService = new TotpService(); window.bg_autofillService = bg_autofillService = new AutofillService(bg_utilsService, bg_totpService, bg_tokenService, bg_cipherService, @@ -909,26 +909,25 @@ var bg_isBackground = true, } function logout(expired, callback) { - bg_syncService.setLastSync(new Date(0), function () { - bg_userService.getUserId().then(function (userId) { - return Q.all([ - bg_tokenService.clearToken(), - bg_cryptoService.clearKeys(), - bg_userService.clear(), - bg_settingsService.clear(userId), - bg_cipherService.clear(userId), - bg_folderService.clear(userId), - bg_passwordGenerationService.clear() - ]).then(function () { - chrome.runtime.sendMessage({ - command: 'doneLoggingOut', expired: expired - }); - setIcon(); - refreshBadgeAndMenu(); - if (callback) { - callback(); - } + bg_userService.getUserId().then(function (userId) { + return Q.all([ + bg_syncService.setLastSync(new Date(0)), + bg_tokenService.clearToken(), + bg_cryptoService.clearKeys(), + bg_userService.clear(), + bg_settingsService.clear(userId), + bg_cipherService.clear(userId), + bg_folderService.clear(userId), + bg_passwordGenerationService.clear() + ]).then(function () { + chrome.runtime.sendMessage({ + command: 'doneLoggingOut', expired: expired }); + setIcon(); + refreshBadgeAndMenu(); + if (callback) { + callback(); + } }); }); } @@ -939,7 +938,7 @@ var bg_isBackground = true, var syncInternal = 6 * 60 * 60 * 1000; // 6 hours var lastSyncAgo = new Date() - lastSync; if (override || !lastSync || lastSyncAgo >= syncInternal) { - bg_syncService.fullSync(override || false, function () { + bg_syncService.fullSync(override || false).then(function () { scheduleNextSync(); }); } diff --git a/src/popup/app/settings/settingsSyncController.js b/src/popup/app/settings/settingsSyncController.js index 64e3f89a04..ac4808e495 100644 --- a/src/popup/app/settings/settingsSyncController.js +++ b/src/popup/app/settings/settingsSyncController.js @@ -1,4 +1,4 @@ -angular +angular .module('bit.settings') .controller('settingsSyncController', function ($scope, syncService, toastr, $analytics, i18nService) { @@ -9,7 +9,7 @@ $scope.sync = function () { $scope.loading = true; - syncService.fullSync(true, function (success) { + syncService.fullSync(true).then(function (success) { $scope.loading = false; setLastSync(); if (success) { @@ -23,7 +23,7 @@ }; function setLastSync() { - syncService.getLastSync(function (lastSync) { + syncService.getLastSync().then(function (lastSync) { if (lastSync) { $scope.lastSync = lastSync.toLocaleDateString() + ' ' + lastSync.toLocaleTimeString(); } diff --git a/src/popup/app/vault/vaultController.js b/src/popup/app/vault/vaultController.js index 1a5d2833b9..267520fdb1 100644 --- a/src/popup/app/vault/vaultController.js +++ b/src/popup/app/vault/vaultController.js @@ -1,4 +1,4 @@ -angular +angular .module('bit.vault') .controller('vaultController', function ($scope, $rootScope, cipherService, folderService, $q, $state, $stateParams, toastr, @@ -15,7 +15,7 @@ if (syncOnLoad) { $scope.$on('$viewContentLoaded', function () { $timeout(function () { - syncService.fullSync(true, function () { }); + syncService.fullSync(true); }, 0); }); } diff --git a/src/services/cipher.service.ts b/src/services/cipher.service.ts index cda1344997..99a48a51dd 100644 --- a/src/services/cipher.service.ts +++ b/src/services/cipher.service.ts @@ -347,7 +347,7 @@ export default class CipherService { this.decryptedCipherCache = null; } - async replace(ciphers: CipherData[]): Promise { + async replace(ciphers: { [id: string]: CipherData; }): Promise { const userId = await this.userService.getUserId(); await UtilsService.saveObjToStorage(Keys.ciphersPrefix + userId, ciphers); this.decryptedCipherCache = null; diff --git a/src/services/crypto.service.ts b/src/services/crypto.service.ts index 3e98b0bb94..384193a9cb 100644 --- a/src/services/crypto.service.ts +++ b/src/services/crypto.service.ts @@ -3,6 +3,7 @@ import { EncryptionType } from '../enums/encryptionType.enum'; import { CipherString } from '../models/domain/cipherString'; import EncryptedObject from '../models/domain/encryptedObject'; import SymmetricCryptoKey from '../models/domain/symmetricCryptoKey'; +import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse'; import ConstantsService from './constants.service'; import UtilsService from './utils.service'; @@ -69,8 +70,7 @@ export default class CryptoService { this.privateKey = null; } - // TODO: proper response model type for orgs - setOrgKeys(orgs: any): Promise<{}> { + setOrgKeys(orgs: ProfileOrganizationResponse[]): Promise<{}> { const orgKeys: any = {}; for (const org of orgs) { orgKeys[org.id] = org.key; diff --git a/src/services/folder.service.ts b/src/services/folder.service.ts index 086b735836..774532fc83 100644 --- a/src/services/folder.service.ts +++ b/src/services/folder.service.ts @@ -123,7 +123,7 @@ export default class FolderService { this.decryptedFolderCache = null; } - async replace(folders: FolderData[]): Promise { + async replace(folders: { [id: string]: FolderData; }): Promise { const userId = await this.userService.getUserId(); await UtilsService.saveObjToStorage(Keys.foldersPrefix + userId, folders); this.decryptedFolderCache = null; diff --git a/src/services/settings.service.ts b/src/services/settings.service.ts index d429ce4c8b..25d598f83c 100644 --- a/src/services/settings.service.ts +++ b/src/services/settings.service.ts @@ -20,7 +20,7 @@ export default class SettingsService { return this.getSettingsKey(Keys.equivalentDomains); } - async setEquivalentDomains(equivalentDomains: any) { + async setEquivalentDomains(equivalentDomains: string[][]) { await this.setSettingsKey(Keys.equivalentDomains, equivalentDomains); } diff --git a/src/services/sync.service.ts b/src/services/sync.service.ts new file mode 100644 index 0000000000..93307e73de --- /dev/null +++ b/src/services/sync.service.ts @@ -0,0 +1,168 @@ +import { CipherData } from '../models/data/cipherData'; +import { FolderData } from '../models/data/folderData'; + +import { CipherResponse } from '../models/response/cipherResponse'; +import { DomainsResponse } from '../models/response/domainsResponse'; +import { FolderResponse } from '../models/response/folderResponse'; +import { ProfileResponse } from '../models/response/profileResponse'; +import { SyncResponse } from '../models/response/syncResponse'; + +import ApiService from './api.service'; +import CipherService from './cipher.service'; +import CryptoService from './crypto.service'; +import FolderService from './folder.service'; +import SettingsService from './settings.service'; +import UserService from './user.service'; +import UtilsService from './utils.service'; + +const Keys = { + lastSyncPrefix: 'lastSync_', +}; + +export default class SyncService { + syncInProgress: boolean = false; + + constructor(private userService: UserService, private apiService: ApiService, + private settingsService: SettingsService, private folderService: FolderService, + private cipherService: CipherService, private cryptoService: CryptoService, + private logoutCallback: Function) { + } + + async getLastSync() { + const userId = await this.userService.getUserId(); + const lastSync = await UtilsService.getObjFromStorage(Keys.lastSyncPrefix + userId); + if (lastSync) { + return new Date(lastSync); + } + + return null; + } + + async setLastSync(date: Date) { + const userId = await this.userService.getUserId(); + await UtilsService.saveObjToStorage(Keys.lastSyncPrefix + userId, date.toJSON()); + } + + syncStarted() { + this.syncInProgress = true; + chrome.runtime.sendMessage({ command: 'syncStarted' }); + } + + syncCompleted(successfully: boolean) { + this.syncInProgress = false; + // tslint:disable-next-line + chrome.runtime.sendMessage({ command: 'syncCompleted', successfully: successfully }); + } + + async fullSync(forceSync: boolean) { + this.syncStarted(); + const isAuthenticated = await this.userService.isAuthenticated(); + if (!isAuthenticated) { + this.syncCompleted(false); + return false; + } + + const now = new Date(); + const needsSyncResult = await this.needsSyncing(forceSync); + const needsSync = needsSyncResult[0]; + const skipped = needsSyncResult[1]; + + if (skipped) { + this.syncCompleted(false); + return false; + } + + if (!needsSync) { + await this.setLastSync(now); + this.syncCompleted(false); + return false; + } + + const userId = await this.userService.getUserId(); + try { + const response = await this.apiService.getSync(); + + await this.syncProfile(response.profile); + await this.syncFolders(userId, response.folders); + await this.syncCiphers(userId, response.ciphers); + await this.syncSettings(userId, response.domains); + + await this.setLastSync(now); + this.syncCompleted(true); + return true; + } catch (e) { + this.syncCompleted(false); + return false; + } + } + + // Helpers + + private async needsSyncing(forceSync: boolean) { + if (forceSync) { + return [true, false]; + } + + try { + const response = await this.apiService.getAccountRevisionDate(); + const accountRevisionDate = new Date(response); + const lastSync = await this.getLastSync(); + if (lastSync != null && accountRevisionDate <= lastSync) { + return [false, false]; + } + + return [true, false]; + } catch (e) { + return [false, true]; + } + } + + private async syncProfile(response: ProfileResponse) { + const stamp = await this.userService.getSecurityStamp(); + if (stamp != null && stamp !== response.securityStamp) { + if (this.logoutCallback != null) { + this.logoutCallback(true); + } + + throw new Error('Stamp has changed'); + } + + await this.cryptoService.setEncKey(response.key); + await this.cryptoService.setEncPrivateKey(response.privateKey); + await this.cryptoService.setOrgKeys(response.organizations); + await this.userService.setSecurityStamp(response.securityStamp); + } + + private async syncFolders(userId: string, response: FolderResponse[]) { + const folders: { [id: string]: FolderData; } = {}; + for (const f of response) { + folders[f.id] = new FolderData(f, userId); + } + return await this.folderService.replace(folders); + } + + private async syncCiphers(userId: string, response: CipherResponse[]) { + const ciphers: { [id: string]: CipherData; } = {}; + for (const c of response) { + ciphers[c.id] = new CipherData(c, userId); + } + return await this.cipherService.replace(ciphers); + } + + private async syncSettings(userId: string, response: DomainsResponse) { + let eqDomains: string[][] = []; + if (response != null && response.equivalentDomains != null) { + eqDomains = eqDomains.concat(response.equivalentDomains); + } + + if (response != null && response.globalEquivalentDomains != null) { + for (const global of response.globalEquivalentDomains) { + if (global.domains.length > 0) { + eqDomains.push(global.domains); + } + } + } + + return this.settingsService.setEquivalentDomains(eqDomains); + } +} diff --git a/src/services/syncService.js b/src/services/syncService.js deleted file mode 100644 index 2ffb7c33e4..0000000000 --- a/src/services/syncService.js +++ /dev/null @@ -1,210 +0,0 @@ -function SyncService(cipherService, folderService, userService, apiService, settingsService, - cryptoService, logoutCallback) { - this.cipherService = cipherService; - this.folderService = folderService; - this.userService = userService; - this.apiService = apiService; - this.settingsService = settingsService; - this.cryptoService = cryptoService; - this.syncInProgress = false; - this.logoutCallback = logoutCallback; - - initSyncService(); -} - -function initSyncService() { - SyncService.prototype.fullSync = function (forceSync, callback) { - if (!callback || typeof callback !== 'function') { - throw 'callback function required'; - } - - var self = this; - - self.syncStarted(); - self.userService.isAuthenticated().then(function (isAuthenticated) { - if (!isAuthenticated) { - self.syncCompleted(false); - callback(false); - return; - } - - var now = new Date(); - needsSyncing(self, forceSync, function (needsSync, skipped) { - if (skipped) { - self.syncCompleted(false); - callback(false); - return; - } - - if (!needsSync) { - self.setLastSync(now, function () { - self.syncCompleted(false); - callback(false); - }); - return; - } - - self.userService.getUserId().then(function (userId) { - self.apiService.getSync().then(function (response) { - syncProfile(self, response.profile).then(function () { - return syncFolders(self, userId, response.folders); - }).then(function () { - return syncCiphers(self, userId, response.ciphers); - }).then(function () { - return syncSettings(self, userId, response.domains); - }).then(function () { - self.setLastSync(now, function () { - self.syncCompleted(true); - callback(true); - }); - }, function () { - self.syncCompleted(false); - callback(false); - }); - }, function () { - self.syncCompleted(false); - callback(false); - }); - }); - }); - }); - }; - - function needsSyncing(self, forceSync, callback) { - if (!callback || typeof callback !== 'function') { - throw 'callback function required'; - } - - if (forceSync) { - callback(true, false); - return; - } - - self.apiService.getAccountRevisionDate().then(function (response) { - var accountRevisionDate = new Date(response); - self.getLastSync(function (lastSync) { - if (lastSync && accountRevisionDate <= lastSync) { - callback(false, false); - return; - } - - callback(true, false); - }); - }, function (error) { - callback(false, true); - }); - } - - function syncProfile(self, response) { - var deferred = Q.defer(); - - self.userService.getSecurityStamp().then(function (stamp) { - if (stamp && stamp !== response.securityStamp) { - if (self.logoutCallback) { - self.logoutCallback(true, function () { }); - } - - deferred.reject(); - return; - } - - return self.cryptoService.setEncKey(response.key); - }).then(function () { - return self.cryptoService.setEncPrivateKey(response.privateKey); - }, function () { - deferred.reject(); - }).then(function () { - return self.cryptoService.setOrgKeys(response.organizations); - }, function () { - deferred.reject(); - }).then(function () { - return self.userService.setSecurityStamp(response.securityStamp); - }, function () { - deferred.reject(); - }).then(function () { - deferred.resolve(); - }, function () { - deferred.reject(); - }); - - return deferred.promise; - } - - function syncFolders(self, userId, response) { - var folders = {}; - for (var i = 0; i < response.length; i++) { - folders[response[i].id] = new FolderData(response[i], userId); - } - return self.folderService.replace(folders); - } - - function syncCiphers(self, userId, response) { - var ciphers = {}; - for (var i = 0; i < response.length; i++) { - ciphers[response[i].id] = new CipherData(response[i], userId); - } - return self.cipherService.replace(ciphers); - } - - function syncSettings(self, userId, response) { - var eqDomains = []; - if (response && response.equivalentDomains) { - eqDomains = eqDomains.concat(response.equivalentDomains); - } - if (response && response.globalEquivalentDomains) { - for (var i = 0; i < response.globalEquivalentDomains.length; i++) { - if (response.globalEquivalentDomains[i].domains.length) { - eqDomains.push(response.globalEquivalentDomains[i].domains); - } - } - } - - return self.settingsService.setEquivalentDomains(eqDomains); - } - - SyncService.prototype.getLastSync = function (callback) { - if (!callback || typeof callback !== 'function') { - throw 'callback function required'; - } - - this.userService.getUserId().then(function (userId) { - var lastSyncKey = 'lastSync_' + userId; - chrome.storage.local.get(lastSyncKey, function (obj) { - var lastSync = obj[lastSyncKey]; - if (lastSync) { - callback(new Date(lastSync)); - } - else { - callback(null); - } - }); - }); - }; - - SyncService.prototype.setLastSync = function (date, callback) { - if (!callback || typeof callback !== 'function') { - throw 'callback function required'; - } - - this.userService.getUserId().then(function (userId) { - var lastSyncKey = 'lastSync_' + userId; - - var obj = {}; - obj[lastSyncKey] = date.toJSON(); - - chrome.storage.local.set(obj, function () { - callback(); - }); - }); - }; - - SyncService.prototype.syncStarted = function () { - this.syncInProgress = true; - chrome.runtime.sendMessage({ command: 'syncStarted' }); - }; - - SyncService.prototype.syncCompleted = function (successfully) { - this.syncInProgress = false; - chrome.runtime.sendMessage({ command: 'syncCompleted', successfully: successfully }); - }; -}