From 00b8a3e7becf70232bd9199a23b5390bfb2385e4 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 14 Nov 2017 11:29:00 -0500 Subject: [PATCH] convert export to ts component --- package.json | 1 + src/popup/app/app.js | 3 - src/popup/app/config.js | 3 +- ...toolsExport.html => export.component.html} | 18 +- src/popup/app/tools/export.component.ts | 163 ++++++++++++++++++ src/popup/app/tools/tools.module.ts | 2 + src/popup/app/tools/toolsExportController.js | 158 ----------------- 7 files changed, 176 insertions(+), 172 deletions(-) rename src/popup/app/tools/{views/toolsExport.html => export.component.html} (57%) create mode 100644 src/popup/app/tools/export.component.ts delete mode 100644 src/popup/app/tools/toolsExportController.js diff --git a/package.json b/package.json index b30a527da41..f30b7af54b7 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@types/chrome": "0.0.51", "@types/jquery": "^3.2.16", "@types/node-forge": "0.6.10", + "@types/papaparse": "4.1.31", "@types/tldjs": "1.7.1", "@types/webcrypto": "^0.0.28", "@uirouter/angularjs": "^1.0.10" diff --git a/src/popup/app/app.js b/src/popup/app/app.js index f1dae2456c2..4e23deda231 100644 --- a/src/popup/app/app.js +++ b/src/popup/app/app.js @@ -1,6 +1,4 @@ -window.Papa = require('papaparse'); require('clipboard'); - require('angular'); require('angular-animate'); @@ -131,7 +129,6 @@ require('./settings/settingsEditFolderController.js'); require('./settings/settingsPremiumController.js'); require('./settings/settingsEnvironmentController.js'); require('./tools/toolsPasswordGeneratorHistoryController.js'); -require('./tools/toolsExportController.js'); // Bootstrap the angular application angular.element(function () { diff --git a/src/popup/app/config.js b/src/popup/app/config.js index 6e41d457cfd..d679a3b1e23 100644 --- a/src/popup/app/config.js +++ b/src/popup/app/config.js @@ -180,8 +180,7 @@ angular }) .state('export', { url: '/export', - template: require('./tools/views/toolsExport.html'), - controller: 'toolsExportController', + component: 'export', data: { authorize: true }, params: { animation: null } }) diff --git a/src/popup/app/tools/views/toolsExport.html b/src/popup/app/tools/export.component.html similarity index 57% rename from src/popup/app/tools/views/toolsExport.html rename to src/popup/app/tools/export.component.html index f05232e6b7e..62a6af853bf 100644 --- a/src/popup/app/tools/views/toolsExport.html +++ b/src/popup/app/tools/export.component.html @@ -1,12 +1,12 @@ -
+
- +
-
{{i18n.exportVault}}
+
{{$ctrl.i18n.exportVault}}
@@ -14,14 +14,14 @@
- - + +
diff --git a/src/popup/app/tools/export.component.ts b/src/popup/app/tools/export.component.ts new file mode 100644 index 00000000000..d100c73512b --- /dev/null +++ b/src/popup/app/tools/export.component.ts @@ -0,0 +1,163 @@ +import * as angular from 'angular'; +import * as papa from 'papaparse'; +import * as template from './export.component.html'; + +import { CipherType } from '../../../enums/cipherType.enum'; + +import { CryptoService } from '../../../services/abstractions/crypto.service'; +import { UtilsService } from '../../../services/abstractions/utils.service'; + +export class ExportController { + i18n: any; + masterPassword: string; + + constructor(private $scope: any, private $state: any, private cryptoService: CryptoService, + private toastr: any, private utilsService: UtilsService, private $analytics: any, + private i18nService: any, private folderService: any, private cipherService: any, + private $window: any, private userService: any) { + this.i18n = i18nService; + this.$scope.submitPromise = null; + } + + $onInit() { + document.getElementById('master-password').focus(); + } + + submit() { + const self = this; + this.$scope.submitPromise = this.checkPassword().then(() => { + return self.getCsv(); + }).then((csv) => { + self.$analytics.eventTrack('Exported Data'); + self.downloadFile(csv); + }, () => { + this.toastr.error(self.i18n.invalidMasterPassword, self.i18n.errorsOccurred); + }); + } + + private async checkPassword() { + const email = await this.userService.getEmail(); + const key = this.cryptoService.makeKey(this.masterPassword, email); + const keyHash = await this.cryptoService.hashPassword(this.masterPassword, key); + const storedKeyHash = await this.cryptoService.getKeyHash(); + if (storedKeyHash == null || keyHash == null || storedKeyHash !== keyHash) { + throw new Error('Invalid password.'); + } + } + + private async getCsv(): Promise { + let decFolders: any[] = []; + let decCiphers: any[] = []; + const promises = []; + + promises.push(this.folderService.getAllDecrypted().then((folders: any[]) => { + decFolders = folders; + })); + + promises.push(this.cipherService.getAllDecrypted().then((ciphers: any[]) => { + decCiphers = ciphers; + })); + + await Promise.all(promises); + + const foldersMap = new Map(); + for (const f of decFolders) { + foldersMap.set(f.id, f); + } + + const exportCiphers = []; + for (const c of decCiphers) { + // only export logins and secure notes + if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) { + continue; + } + + const cipher: any = { + folder: c.folderId && foldersMap.has(c.folderId) ? foldersMap.get(c.folderId).name : null, + favorite: c.favorite ? 1 : null, + type: null, + name: c.name, + notes: c.notes, + fields: null, + // Login props + login_uri: null, + login_username: null, + login_password: null, + login_totp: null, + }; + + if (c.fields) { + for (const f of c.fields) { + if (!cipher.fields) { + cipher.fields = ''; + } else { + cipher.fields += '\n'; + } + + cipher.fields += ((f.name || '') + ': ' + f.value); + } + } + + switch (c.type) { + case CipherType.Login: + cipher.type = 'login'; + cipher.login_uri = c.login.uri; + cipher.login_username = c.login.username; + cipher.login_password = c.login.password; + cipher.login_totp = c.login.totp; + break; + case CipherType.SecureNote: + cipher.type = 'note'; + break; + default: + continue; + } + + exportCiphers.push(cipher); + } + + const csv = papa.unparse(exportCiphers); + return csv; + } + + private downloadFile(csv: string): void { + const csvBlob = new Blob([csv]); + const fileName = this.makeFileName(); + + if (this.$window.navigator.msSaveOrOpenBlob) { + // Currently bugged in Edge. See + // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8178877/ + // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8477778/ + this.$window.navigator.msSaveBlob(csvBlob, fileName); + } else { + const a = this.$window.document.createElement('a'); + a.href = this.$window.URL.createObjectURL(csvBlob, { type: 'text/plain' }); + a.download = fileName; + this.$window.document.body.appendChild(a); + a.click(); + this.$window.document.body.removeChild(a); + } + } + + private makeFileName(): string { + const now = new Date(); + const dateString = + now.getFullYear() + '' + this.padNumber(now.getMonth() + 1, 2) + '' + this.padNumber(now.getDate(), 2) + + this.padNumber(now.getHours(), 2) + '' + this.padNumber(now.getMinutes(), 2) + + this.padNumber(now.getSeconds(), 2); + + return 'bitwarden_export_' + dateString + '.csv'; + } + + private padNumber(num: number, width: number, padCharacter: string = '0'): string { + const numString = num.toString(); + return numString.length >= width ? numString : + new Array(width - numString.length + 1).join(padCharacter) + numString; + } +} + +export const ExportComponent = { + bindings: {}, + controller: ExportController, + template, +}; diff --git a/src/popup/app/tools/tools.module.ts b/src/popup/app/tools/tools.module.ts index c01111438ef..3c9b564a31c 100644 --- a/src/popup/app/tools/tools.module.ts +++ b/src/popup/app/tools/tools.module.ts @@ -1,4 +1,5 @@ import * as angular from 'angular'; +import { ExportComponent } from './export.component'; import { PasswordGeneratorComponent } from './password-generator.component'; import { ToolsComponent } from './tools.component'; @@ -7,5 +8,6 @@ export default angular .component('tools', ToolsComponent) .component('passwordGenerator', PasswordGeneratorComponent) + .component('export', ExportComponent) .name; diff --git a/src/popup/app/tools/toolsExportController.js b/src/popup/app/tools/toolsExportController.js deleted file mode 100644 index ebe36beec4d..00000000000 --- a/src/popup/app/tools/toolsExportController.js +++ /dev/null @@ -1,158 +0,0 @@ -angular - .module('bit.tools') - - .controller('toolsExportController', function ($scope, $state, toastr, $q, $analytics, - i18nService, cryptoService, userService, folderService, cipherService, $window, constantsService) { - $scope.i18n = i18nService; - document.getElementById('master-password').focus(); - - $scope.submitPromise = null; - $scope.submit = function () { - $scope.submitPromise = checkPassword().then(function () { - return getCsv(); - }).then(function (csv) { - $analytics.eventTrack('Exported Data'); - downloadFile(csv); - }, function () { - toastr.error(i18nService.invalidMasterPassword, i18nService.errorsOccurred); - }); - }; - - function checkPassword() { - var deferred = $q.defer(); - - userService.getEmail().then(function (email) { - var key = cryptoService.makeKey($scope.masterPassword, email); - var keyHash; - cryptoService.hashPassword($scope.masterPassword, key).then(function (theKeyHash) { - keyHash = theKeyHash; - return cryptoService.getKeyHash(); - }).then(function (storedKeyHash) { - if (storedKeyHash && keyHash && storedKeyHash === keyHash) { - deferred.resolve(); - } - else { - deferred.reject(); - } - }); - }); - - return deferred.promise; - } - - function getCsv() { - var decFolders = []; - var decCiphers = []; - var promises = []; - - var folderPromise = folderService.getAllDecrypted().then(function (folders) { - decFolders = folders; - }); - promises.push(folderPromise); - - var ciphersPromise = cipherService.getAllDecrypted().then(function (ciphers) { - decCiphers = ciphers; - }); - promises.push(ciphersPromise); - - return $q.all(promises).then(function () { - var foldersDict = {}; - for (var i = 0; i < decFolders.length; i++) { - foldersDict[decFolders[i].id] = decFolders[i]; - } - - var exportCiphers = []; - for (i = 0; i < decCiphers.length; i++) { - // only export logins and secure notes - if (decCiphers[i].type !== constantsService.cipherType.login && - decCiphers[i].type !== constantsService.cipherType.secureNote) { - continue; - } - - var cipher = { - folder: decCiphers[i].folderId && (decCiphers[i].folderId in foldersDict) ? - foldersDict[decCiphers[i].folderId].name : null, - favorite: decCiphers[i].favorite ? 1 : null, - type: null, - name: decCiphers[i].name, - notes: decCiphers[i].notes, - fields: null, - // Login props - login_uri: null, - login_username: null, - login_password: null, - login_totp: null - }; - - if (decCiphers[i].fields) { - for (var j = 0; j < decCiphers[i].fields.length; j++) { - if (!cipher.fields) { - cipher.fields = ''; - } - else { - cipher.fields += '\n'; - } - - cipher.fields += ((decCiphers[i].fields[j].name || '') + ': ' + decCiphers[i].fields[j].value); - } - } - - switch (decCiphers[i].type) { - case constantsService.cipherType.login: - cipher.type = 'login'; - cipher.login_uri = decCiphers[i].login.uri; - cipher.login_username = decCiphers[i].login.username; - cipher.login_password = decCiphers[i].login.password; - cipher.login_totp = decCiphers[i].login.totp; - break; - case constantsService.cipherType.secureNote: - cipher.type = 'note'; - break; - default: - continue; - } - - exportCiphers.push(cipher); - } - - var csv = Papa.unparse(exportCiphers); - return csv; - }); - } - - function downloadFile(csvString) { - var csvBlob = new Blob([csvString]); - var fileName = makeFileName(); - - if ($window.navigator.msSaveOrOpenBlob) { - // Currently bugged in Edge. See - // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8178877/ - // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8477778/ - $window.navigator.msSaveBlob(csvBlob, fileName); - } - else { - var a = $window.document.createElement('a'); - a.href = $window.URL.createObjectURL(csvBlob, { type: 'text/plain' }); - a.download = fileName; - $window.document.body.appendChild(a); - a.click(); - $window.document.body.removeChild(a); - } - } - - function makeFileName() { - var now = new Date(); - var dateString = - now.getFullYear() + '' + padNumber(now.getMonth() + 1, 2) + '' + padNumber(now.getDate(), 2) + - padNumber(now.getHours(), 2) + '' + padNumber(now.getMinutes(), 2) + - padNumber(now.getSeconds(), 2); - - return 'bitwarden_export_' + dateString + '.csv'; - } - - function padNumber(number, width, paddingCharacter) { - paddingCharacter = paddingCharacter || '0'; - number = number + ''; - return number.length >= width ? number : new Array(width - number.length + 1).join(paddingCharacter) + number; - } - });