diff --git a/src/background.html b/src/background.html
index 37a8daa8537..e40241b1da2 100644
--- a/src/background.html
+++ b/src/background.html
@@ -4,7 +4,6 @@
-
diff --git a/src/background.js b/src/background.js
index 5b606eeb270..dc5b3bd3cc2 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 64e3f89a047..ac4808e4950 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 1a5d2833b9a..267520fdb1e 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 cda13449974..99a48a51dd2 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 3e98b0bb945..384193a9cb5 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 086b7358360..774532fc831 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 d429ce4c8bd..25d598f83c5 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 00000000000..93307e73de9
--- /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 2ffb7c33e4e..00000000000
--- 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 });
- };
-}