From a62d7f61483f071fa44a871cca88910d8412699f Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Fri, 7 Jun 2019 15:43:29 -0500 Subject: [PATCH 01/44] change compile options --- tsconfig.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 15280d2ed..29b158b78 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,14 +2,16 @@ "extends": "./node_modules/gts/tsconfig-google.json", "compilerOptions": { "lib": ["es6", "dom"], - "target": "es6", + "target": "es5", "strict": true, + "module": "es2015", "rootDir": "src", + "moduleResolution": "node", "outDir": "build" }, "include": [ "src/*.ts", - "src/**/*.ts" + "src/**/*" ], "exclude": [ "node_modules" From c6bde6ce2ac40cc65baf5f2befbd4537bf847daa Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Fri, 7 Jun 2019 16:33:44 -0500 Subject: [PATCH 02/44] move interfaces to .d.ts files and add an interface also gts fix --- src/background.ts | 5 +- src/content.ts | 6 +-- src/definitions/i18n.d.ts | 3 ++ src/definitions/otp.d.ts | 44 ++++++++++++++++++ src/definitions/shims-vue.d.ts | 5 ++ src/definitions/uiconfig.d.ts | 19 ++++++++ src/models/backup.ts | 8 ++-- src/models/credentials.ts | 6 +-- src/models/encryption.ts | 2 +- src/models/interface.ts | 63 ------------------------- src/models/key-utilities.ts | 2 - src/models/otp.ts | 3 +- src/models/storage.ts | 3 +- src/test/test.ts | 84 +++++++++++++++++----------------- src/ui/add-account.ts | 10 ++-- src/ui/backup.ts | 12 ++--- src/ui/class.ts | 7 ++- src/ui/entry.ts | 11 ++--- src/ui/i18n.ts | 3 +- src/ui/info.ts | 9 ++-- src/ui/menu.ts | 10 ++-- src/ui/message.ts | 6 +-- src/ui/passphrase.ts | 6 +-- src/ui/qr.ts | 6 +-- src/ui/ui.ts | 1 - 25 files changed, 162 insertions(+), 172 deletions(-) create mode 100644 src/definitions/i18n.d.ts create mode 100644 src/definitions/otp.d.ts create mode 100644 src/definitions/shims-vue.d.ts create mode 100644 src/definitions/uiconfig.d.ts delete mode 100644 src/models/interface.ts diff --git a/src/background.ts b/src/background.ts index 5ae658572..1eeba170a 100644 --- a/src/background.ts +++ b/src/background.ts @@ -4,7 +4,6 @@ import QRCode from 'qrcode-reader'; import {getCredentials} from './models/credentials'; import {Encryption} from './models/encryption'; -import {OTPStorage} from './models/interface'; import {EntryStorage, ManagedStorage} from './models/storage'; let cachedPassphrase = ''; @@ -161,7 +160,7 @@ async function getTotp(text: string) { type, encrypted: false, index: 0, - counter: 0 + counter: 0, }; if (period) { entryData[hash].period = period; @@ -179,7 +178,7 @@ function getBackupToken(service: string) { chrome.identity.getAuthToken( { 'interactive': true, - 'scopes': ['https://www.googleapis.com/auth/drive.file'] + 'scopes': ['https://www.googleapis.com/auth/drive.file'], }, (value) => { localStorage.driveToken = value; diff --git a/src/content.ts b/src/content.ts index 27fa58b8e..70c618bbe 100644 --- a/src/content.ts +++ b/src/content.ts @@ -158,7 +158,7 @@ function sendPosition( left: number, top: number, width: number, height: number) { chrome.runtime.sendMessage({ action: 'position', - info: {left, top, width, height, windowWidth: window.innerWidth} + info: {left, top, width, height, windowWidth: window.innerWidth}, }); } @@ -186,7 +186,7 @@ function pasteCode(code: string) { return; } const identities = [ - '2fa', 'otp', 'authenticator', 'factor', 'code', 'totp', 'twoFactorCode' + '2fa', 'otp', 'authenticator', 'factor', 'code', 'totp', 'twoFactorCode', ]; for (const inputBox of inputBoxes) { for (const identity of identities) { @@ -228,7 +228,7 @@ function fireInputEvents(inputBox: HTMLInputElement) { const events = [ new KeyboardEvent('keydown'), new KeyboardEvent('keyup'), new KeyboardEvent('keypress'), new Event('input', {'bubbles': true}), - new Event('change', {'bubbles': true}) + new Event('change', {'bubbles': true}), ]; for (const event of events) { inputBox.dispatchEvent(event); diff --git a/src/definitions/i18n.d.ts b/src/definitions/i18n.d.ts new file mode 100644 index 000000000..1b17ca9d4 --- /dev/null +++ b/src/definitions/i18n.d.ts @@ -0,0 +1,3 @@ +interface I18nMessage { + [key: string]: { message: string, description: string }; +} diff --git a/src/definitions/otp.d.ts b/src/definitions/otp.d.ts new file mode 100644 index 000000000..bf2baf106 --- /dev/null +++ b/src/definitions/otp.d.ts @@ -0,0 +1,44 @@ +declare enum OTPType { + totp = 1, + hotp, + battle, + steam, + hex, + hhex +} + +interface IOTPEntry { + type: OTPType; + index: number; + issuer: string; + secret: string; + account: string; + hash: string; + counter: number; + code: string; + period: number; + create(encryption: IEncryption): Promise; + update(encryption: IEncryption): Promise; + next(encryption: IEncryption): Promise; + delete(): Promise; + generate(): void; +} + +interface IEncryption { + getEncryptedSecret(secret: string): string; + getDecryptedSecret(secret: string, hash: string): string; + getEncryptionStatus(): boolean; + updateEncryptionPassword(password: string): void; +} + +interface OTPStorage { + account: string; + encrypted: boolean; + hash: string; + index: number; + issuer: string; + secret: string; + type: string; + counter: number; + period?: number; +} diff --git a/src/definitions/shims-vue.d.ts b/src/definitions/shims-vue.d.ts new file mode 100644 index 000000000..8a0de0a61 --- /dev/null +++ b/src/definitions/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import Vue from 'vue' + export default Vue + } + \ No newline at end of file diff --git a/src/definitions/uiconfig.d.ts b/src/definitions/uiconfig.d.ts new file mode 100644 index 000000000..fd961587b --- /dev/null +++ b/src/definitions/uiconfig.d.ts @@ -0,0 +1,19 @@ +interface UIConfig { + el?: string; + data?: { + /* tslint:disable-next-line:no-any */ + [name: string]: any + }; + methods?: { + /* tslint:disable-next-line:no-any */ + [name: string]: (...arg: any[]) => any + }; + /* tslint:disable-next-line:no-any */ + mounted?: (...arg: any[]) => any; + /* tslint:disable-next-line:no-any */ + beforeCreate?: (...arg: any[]) => any; + /* tslint:disable-next-line:no-any */ + render?: (h: any) => any; + /* tslint:disable-next-line:no-any */ + } + \ No newline at end of file diff --git a/src/models/backup.ts b/src/models/backup.ts index 2ea681476..221d808cb 100644 --- a/src/models/backup.ts +++ b/src/models/backup.ts @@ -31,7 +31,7 @@ export class Dropbox { const apiArg = { path: `/${now}.json`, mode: 'add', - autorename: true + autorename: true, }; xhr.open('POST', url); xhr.setRequestHeader('Authorization', 'Bearer ' + token); @@ -115,7 +115,7 @@ export class Drive { return chrome.identity.getAuthToken( { 'interactive': false, - 'scopes': ['https://www.googleapis.com/auth/drive.file'] + 'scopes': ['https://www.googleapis.com/auth/drive.file'], }, (token) => { localStorage.driveToken = token; @@ -254,7 +254,7 @@ export class Drive { }; xhr.send(JSON.stringify({ 'name': 'Authenticator Backups', - 'mimeType': 'application/vnd.google-apps.folder' + 'mimeType': 'application/vnd.google-apps.folder', })); }); } @@ -318,7 +318,7 @@ export class Drive { JSON.stringify( {name: `${now}.json`, parents: [localStorage.driveFolder]}), '', '--segment_marker', 'Content-Type: application/octet-stream', - '', backup, '--segment_marker--' + '', backup, '--segment_marker--', ]; let requestData = ''; requestDataPrototype.forEach((line) => { diff --git a/src/models/credentials.ts b/src/models/credentials.ts index 3ba39d3ca..ca8cae4da 100644 --- a/src/models/credentials.ts +++ b/src/models/credentials.ts @@ -2,10 +2,10 @@ export function getCredentials() { return { drive: { client_id: '', // Google client ID - client_secret: '' // Google client secret + client_secret: '', // Google client secret }, dropbox: { - client_id: '' // Dropbox client ID - } + client_id: '', // Dropbox client ID + }, }; } diff --git a/src/models/encryption.ts b/src/models/encryption.ts index f955f57ed..e94005112 100644 --- a/src/models/encryption.ts +++ b/src/models/encryption.ts @@ -1,6 +1,6 @@ import * as CryptoJS from 'crypto-js'; -export class Encryption { +export class Encryption implements IEncryption { private password: string; constructor(password: string) { diff --git a/src/models/interface.ts b/src/models/interface.ts deleted file mode 100644 index b12ad4035..000000000 --- a/src/models/interface.ts +++ /dev/null @@ -1,63 +0,0 @@ -import {Encryption} from './encryption'; - -export enum OTPType { - totp = 1, - hotp, - battle, - steam, - hex, - hhex -} - -export interface OTP { - type: OTPType; - index: number; - issuer: string; - secret: string; - account: string; - hash: string; - counter: number; - code: string; - period: number; - create(encryption: Encryption): Promise; - update(encryption: Encryption): Promise; - next(encryption: Encryption): Promise; - delete(): Promise; - generate(): void; -} - -export interface OTPStorage { - account: string; - encrypted: boolean; - hash: string; - index: number; - issuer: string; - secret: string; - type: string; - counter: number; - period?: number; -} - -/* tslint:disable-next-line:interface-name */ -export interface I18nMessage { - [key: string]: {message: string, description: string}; -} - -export interface UIConfig { - el?: string; - data?: { - /* tslint:disable-next-line:no-any */ - [name: string]: any - }; - methods?: { - /* tslint:disable-next-line:no-any */ - [name: string]: (...arg: any[]) => any - }; - /* tslint:disable-next-line:no-any */ - mounted?: (...arg: any[]) => any; - /* tslint:disable-next-line:no-any */ - beforeCreate?: (...arg: any[]) => any; - /* tslint:disable-next-line:no-any */ - render?: (h: any) => any; - /* tslint:disable-next-line:no-any */ -} diff --git a/src/models/key-utilities.ts b/src/models/key-utilities.ts index bd786b3c7..625ba256b 100644 --- a/src/models/key-utilities.ts +++ b/src/models/key-utilities.ts @@ -1,7 +1,5 @@ import * as jsSHA from 'jssha'; -import {OTPType} from './interface'; - // Originally based on the JavaScript implementation as provided by Russell // Sayers on his Tin Isles blog: // http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/ diff --git a/src/models/otp.ts b/src/models/otp.ts index fb4b00011..48ca6d895 100644 --- a/src/models/otp.ts +++ b/src/models/otp.ts @@ -1,11 +1,10 @@ import * as CryptoJS from 'crypto-js'; import {Encryption} from './encryption'; -import {OTP, OTPType} from './interface'; import {KeyUtilities} from './key-utilities'; import {EntryStorage} from './storage'; -export class OTPEntry implements OTP { +export class OTPEntry implements IOTPEntry { type: OTPType; index: number; issuer: string; diff --git a/src/models/storage.ts b/src/models/storage.ts index f74a6c877..d7d1ce078 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -1,7 +1,6 @@ import * as CryptoJS from 'crypto-js'; import {Encryption} from './encryption'; -import {OTPStorage, OTPType} from './interface'; import {OTPEntry} from './otp'; export class BrowserStorage { @@ -101,7 +100,7 @@ export class EntryStorage { type: OTPType[entry.type], counter: entry.counter, secret: encryption.getEncryptedSecret(entry.secret), - encrypted: encryption.getEncryptionStatus() + encrypted: encryption.getEncryptionStatus(), }; if (entry.period && entry.period !== 30) { storageItem.period = entry.period; diff --git a/src/test/test.ts b/src/test/test.ts index 5c73dc916..5741863a0 100644 --- a/src/test/test.ts +++ b/src/test/test.ts @@ -12,7 +12,7 @@ interface TestCase { const cases: TestCase[] = [ { name: 'Missing fields', - data: {'7733be61632fa6af88d31218e6c4afb2': {'secret': 'abcd2345'}} + data: {'7733be61632fa6af88d31218e6c4afb2': {'secret': 'abcd2345'}}, }, { name: 'Bad hash in key', @@ -25,9 +25,9 @@ const cases: TestCase[] = [ 'index': 0, 'issuer': '', 'secret': 'abcd2345', - 'type': 'totp' - } - } + 'type': 'totp', + }, + }, }, { name: 'Bad hash', @@ -40,9 +40,9 @@ const cases: TestCase[] = [ 'index': 0, 'issuer': '', 'secret': 'abcd2345', - 'type': 'totp' - } - } + 'type': 'totp', + }, + }, }, { name: 'Bad type for HEX', @@ -55,9 +55,9 @@ const cases: TestCase[] = [ 'index': 0, 'issuer': '', 'secret': 'abcd1234', - 'type': 'totp' - } - } + 'type': 'totp', + }, + }, }, { name: 'Unicode in issuer', @@ -70,9 +70,9 @@ const cases: TestCase[] = [ 'index': 0, 'issuer': '✓ à la mode', 'secret': 'abcd2345', - 'type': 'totp' - } - } + 'type': 'totp', + }, + }, }, { name: 'Battle migrate', @@ -85,9 +85,9 @@ const cases: TestCase[] = [ 'index': 0, 'issuer': '', 'secret': 'blz-abcd2345', - 'type': 'totp' - } - } + 'type': 'totp', + }, + }, }, { name: 'Steam migrate', @@ -100,13 +100,13 @@ const cases: TestCase[] = [ 'index': 0, 'issuer': '', 'secret': 'stm-abcd2345', - 'type': 'totp' - } - } + 'type': 'totp', + }, + }, }, { name: 'Missing field with HEX secret', - data: {'e19d5cd5af0378da05f63f891c7467af': {'secret': 'abcd1234'}} + data: {'e19d5cd5af0378da05f63f891c7467af': {'secret': 'abcd1234'}}, }, { name: 'Mess index', @@ -119,7 +119,7 @@ const cases: TestCase[] = [ 'index': 6, 'issuer': '', 'secret': 'abcd2345', - 'type': 'totp' + 'type': 'totp', }, '770f51f23603ddae810e446630c2f673': { 'account': 'test', @@ -129,9 +129,9 @@ const cases: TestCase[] = [ 'index': 6, 'issuer': '', 'secret': 'abcd2346', - 'type': 'totp' - } - } + 'type': 'totp', + }, + }, }, { name: 'Base32 with padding', @@ -144,9 +144,9 @@ const cases: TestCase[] = [ 'index': 0, 'issuer': '', 'secret': 'DKCE3SQPHJRJQGBGI322QA7Z5E======', - 'type': 'totp' - } - } + 'type': 'totp', + }, + }, }, { name: 'Incorrect but valid hash', @@ -159,9 +159,9 @@ const cases: TestCase[] = [ 'index': 0, 'issuer': '', 'secret': 'abcd2345', - 'type': 'totp' - } - } + 'type': 'totp', + }, + }, }, { name: 'HOTP with HEX secret', @@ -174,9 +174,9 @@ const cases: TestCase[] = [ 'index': 0, 'issuer': '', 'secret': '2c52e8fcfac34091da63ef7b118f1cc50b925a42', - 'type': 'hhex' - } - } + 'type': 'hhex', + }, + }, }, { name: 'Amazon 2FA', @@ -189,9 +189,9 @@ const cases: TestCase[] = [ 'index': 0, 'issuer': '', 'secret': 'QLGNXJ2KLSOACXOEKJ47X6VA6ZPGT5HE2GBO5NPXTLD7FJAKD4JQ', - 'type': 'totp' - } - } + 'type': 'totp', + }, + }, }, { name: 'Secret contains spaces', @@ -204,10 +204,10 @@ const cases: TestCase[] = [ 'index': 0, 'issuer': '', 'secret': 'p5s7 k2in z3mj oqfg', - 'type': 'totp' - } - } - } + 'type': 'totp', + }, + }, + }, ]; let testCaseIndex = 0; @@ -328,13 +328,13 @@ async function test() { const item = data[hash]; const keys = [ 'issuer', 'account', 'secret', 'hash', 'index', 'type', 'counter', - 'encrypted' + 'encrypted', ]; for (const key of keys) { if (item[key] === undefined) { testRes[testCaseIndex] = { pass: false, - error: `Missing key<${key}>: ${JSON.stringify(item)}` + error: `Missing key<${key}>: ${JSON.stringify(item)}`, }; break; } diff --git a/src/ui/add-account.ts b/src/ui/add-account.ts index 995aadc2c..93c38b1d5 100644 --- a/src/ui/add-account.ts +++ b/src/ui/add-account.ts @@ -1,7 +1,5 @@ -import {OTPType, UIConfig} from '../models/interface'; -import {OTPEntry} from '../models/otp'; - import {UI} from './ui'; +import { OTPEntry } from '../models/otp'; export async function insertContentScript() { return new Promise((resolve: () => void, reject: (reason: Error) => void) => { @@ -19,7 +17,7 @@ export async function addAccount(_ui: UI) { const ui: UIConfig = { data: { newAccount: {show: false, account: '', secret: '', type: OTPType.totp}, - newPassphrase: {phrase: '', confirm: ''} + newPassphrase: {phrase: '', confirm: ''}, }, methods: { addNewAccount: async () => { @@ -112,8 +110,8 @@ export async function addAccount(_ui: UI) { } _ui.instance.newAccount.show = true; - } - } + }, + }, }; _ui.update(ui); diff --git a/src/ui/backup.ts b/src/ui/backup.ts index 49c9ff824..604c6dd27 100644 --- a/src/ui/backup.ts +++ b/src/ui/backup.ts @@ -1,6 +1,4 @@ import {Drive, Dropbox} from '../models/backup'; -import {UIConfig} from '../models/interface'; - import {UI} from './ui'; export async function backup(_ui: UI) { @@ -9,7 +7,7 @@ export async function backup(_ui: UI) { dropboxEncrypted: localStorage.dropboxEncrypted, driveEncrypted: localStorage.driveEncrypted, dropboxToken: localStorage.dropboxToken || '', - driveToken: localStorage.driveToken || '' + driveToken: localStorage.driveToken || '', }, methods: { backupUpload: async (service: string) => { @@ -126,8 +124,8 @@ export async function backup(_ui: UI) { { origins: [ 'https://www.googleapis.com/*', - 'https://accounts.google.com/o/oauth2/revoke' - ] + 'https://accounts.google.com/o/oauth2/revoke', + ], }, async (hasPermission) => { if (hasPermission) { @@ -154,8 +152,8 @@ export async function backup(_ui: UI) { _ui.instance.alert(_ui.instance.i18n.remind_backup); localStorage.lastRemindingBackupTime = clientTime; } - } - } + }, + }, }; _ui.update(ui); diff --git a/src/ui/class.ts b/src/ui/class.ts index de9c3ea31..edb5045b4 100644 --- a/src/ui/class.ts +++ b/src/ui/class.ts @@ -1,4 +1,3 @@ -import {UIConfig} from '../models/interface'; import {UI} from './ui'; @@ -16,9 +15,9 @@ export async function className(_ui: UI) { qrfadeout: false, notificationFadein: false, notificationFadeout: false, - hotpDiabled: false - } - } + hotpDiabled: false, + }, + }, }; _ui.update(ui); diff --git a/src/ui/entry.ts b/src/ui/entry.ts index 5190e8158..7adde2ee3 100644 --- a/src/ui/entry.ts +++ b/src/ui/entry.ts @@ -1,7 +1,6 @@ import * as CryptoJS from 'crypto-js'; import {Encryption} from '../models/encryption'; -import {OTP, OTPStorage, OTPType, UIConfig} from '../models/interface'; import {OTPEntry} from '../models/otp'; import {EntryStorage} from '../models/storage'; @@ -49,7 +48,7 @@ async function updateCode(app: any) { // } // } // } - const entries = app.entries as OTP[]; + const entries = app.entries as IOTPEntry[]; for (let i = 0; i < entries.length; i++) { if (entries[i].type !== OTPType.hotp && entries[i].type !== OTPType.hhex) { entries[i].generate(); @@ -317,7 +316,7 @@ function getEntryDataFromOTPAuthPerLine(importCode: string) { type, encrypted: false, index: 0, - counter: 0 + counter: 0, }; if (period) { exportData[hash].period = period; @@ -394,7 +393,7 @@ export async function entry(_ui: UI) { importPassphrase: '', importFilePassphrase: '', unsupportedAccounts, - searchText: '' + searchText: '', }, methods: { isMatchedEntry: (entry: OTPEntry) => { @@ -444,7 +443,7 @@ export async function entry(_ui: UI) { } }, updateCode: async () => { - return await updateCode(_ui.instance); + return updateCode(_ui.instance); }, decryptBackupData: (backupData: {[hash: string]: OTPStorage}, @@ -749,7 +748,7 @@ export async function entry(_ui: UI) { } return; }, - } + }, }; _ui.update(ui); diff --git a/src/ui/i18n.ts b/src/ui/i18n.ts index abd116bd0..587720ea1 100644 --- a/src/ui/i18n.ts +++ b/src/ui/i18n.ts @@ -1,4 +1,3 @@ -import {I18nMessage, UIConfig} from '../models/interface'; import {UI} from './ui'; @@ -38,5 +37,5 @@ export async function i18n(_ui: UI) { } export async function ri18n() { - return await loadI18nMessages(); + return loadI18nMessages(); } diff --git a/src/ui/info.ts b/src/ui/info.ts index 3bfaf1bf1..118d4783e 100644 --- a/src/ui/info.ts +++ b/src/ui/info.ts @@ -1,4 +1,3 @@ -import {UIConfig} from '../models/interface'; import {OTPEntry} from '../models/otp'; import {UI} from './ui'; @@ -47,8 +46,8 @@ export async function info(_ui: UI) { { origins: [ 'https://www.googleapis.com/*', - 'https://accounts.google.com/o/oauth2/revoke' - ] + 'https://accounts.google.com/o/oauth2/revoke', + ], }, async (granted) => { if (granted) { @@ -80,8 +79,8 @@ export async function info(_ui: UI) { _ui.instance.newAccount.show = false; }, 200); return; - } - } + }, + }, }; _ui.update(ui); diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 3d6fd48df..12cc1e1a6 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -1,4 +1,4 @@ -import {UIConfig} from '../models/interface'; + import {ManagedStorage} from '../models/storage'; import {UI} from './ui'; @@ -96,7 +96,7 @@ export async function menu(_ui: UI) { useHighContrast, newStorageLocation: localStorage.storageLocation, backupDisabled, - storageArea + storageArea, }, methods: { openLink: (url: string) => { @@ -195,7 +195,7 @@ export async function menu(_ui: UI) { url: chrome.extension.getURL('view/popup.html?popup=true'), type: windowType, height: window.innerHeight, - width: window.innerWidth + width: window.innerWidth, }); }, isPopup: () => { @@ -278,8 +278,8 @@ export async function menu(_ui: UI) { } else { return; } - } - } + }, + }, }; _ui.update(ui); diff --git a/src/ui/message.ts b/src/ui/message.ts index 84778a8c8..00d650576 100644 --- a/src/ui/message.ts +++ b/src/ui/message.ts @@ -1,4 +1,4 @@ -import {UIConfig} from '../models/interface'; + import {UI} from './ui'; @@ -44,8 +44,8 @@ export async function message(_ui: UI) { const confirmEvent = new CustomEvent('confirm', {detail: false}); window.dispatchEvent(confirmEvent); return; - } - } + }, + }, }; _ui.update(ui); diff --git a/src/ui/passphrase.ts b/src/ui/passphrase.ts index 70c4dc629..3235a1130 100644 --- a/src/ui/passphrase.ts +++ b/src/ui/passphrase.ts @@ -1,5 +1,3 @@ -import {UIConfig} from '../models/interface'; - import {getSiteName, hasMatchedEntry} from './entry'; import {UI} from './ui'; @@ -50,8 +48,8 @@ export async function passphrase(_ui: UI) { // remove cached passphrase in old version localStorage.removeItem('encodedPhrase'); return; - } - } + }, + }, }; _ui.update(ui); diff --git a/src/ui/qr.ts b/src/ui/qr.ts index 530abb304..9882f76e4 100644 --- a/src/ui/qr.ts +++ b/src/ui/qr.ts @@ -1,6 +1,4 @@ import * as QRGen from 'qrcode-generator'; - -import {OTPType, UIConfig} from '../models/interface'; import {OTPEntry} from '../models/otp'; import {UI} from './ui'; @@ -53,8 +51,8 @@ export async function qr(_ui: UI) { _ui.instance.currentClass.qrfadeout = false; }, 200); return; - } - } + }, + }, }; _ui.update(ui); diff --git a/src/ui/ui.ts b/src/ui/ui.ts index d3cff043e..8e4e5e38e 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -1,7 +1,6 @@ import Vue, {Component} from 'vue'; // @ts-ignore import {Vue2Dragula} from 'vue2-dragula'; -import {UIConfig} from '../models/interface'; import {OTPEntry} from '../models/otp'; export class UI { From 61bd7b921655e60dd088c4c23135d0e9cfffd5da Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Fri, 7 Jun 2019 20:51:04 -0500 Subject: [PATCH 03/44] - updated all dependincies to latest - fiddled with webpack until SFCs started working properly --- package-lock.json | 590 ++++++++++++++++++++++++++++++---------------- package.json | 19 +- sass/_ui.scss | 2 + webpack.config.js | 28 ++- 4 files changed, 426 insertions(+), 213 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e1b1c7fb..d00be142c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,25 +4,45 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, "@types/chrome": { - "version": "0.0.59", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.59.tgz", - "integrity": "sha512-wbzlTyfybnPgKDgc6krdbZW+8QPTYedWptjB4szZU4mWmhm9+Gn8yjDJQe2rM+HiYp3iTdP6yOa0kBDURStjEg==", + "version": "0.0.86", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.86.tgz", + "integrity": "sha512-7ehebPf/5IR64SYdD2Vig0q0oUCNbWMQ29kASlNJaHVVtA5/J/DnrxnYVPCID70o7LgHdYsav6I4/XdqAxjTLQ==", "dev": true, "requires": { "@types/filesystem": "*" } }, "@types/crypto-js": { - "version": "3.1.38", - "resolved": "http://registry.npm.taobao.org/@types/crypto-js/download/@types/crypto-js-3.1.38.tgz", - "integrity": "sha1-4TZ/dz7d4phrqa6+478c3z6Bil0=", + "version": "3.1.43", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-3.1.43.tgz", + "integrity": "sha512-EHe/YKctU3IYNBsDmSOPX/7jLHPRlx8WaiDKSY9JCTnJ8XJeM4c0ZJvx+9Gxmr2s2ihI92R+3U/gNL1sq5oRuQ==", "dev": true }, "@types/filesystem": { - "version": "0.0.28", - "resolved": "http://registry.npm.taobao.org/@types/filesystem/download/@types/filesystem-0.0.28.tgz", - "integrity": "sha1-P9dzWDDyx0E8taxFeAvEWQRpew4=", + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.29.tgz", + "integrity": "sha512-85/1KfRedmfPGsbK8YzeaQUyV1FQAvMPMTuWFQ5EkLd2w7szhNO96bk3Rh/SKmOfd9co2rCLf0Voy4o7ECBOvw==", "dev": true, "requires": { "@types/filewriter": "*" @@ -35,9 +55,9 @@ "dev": true }, "@types/jssha": { - "version": "0.0.29", - "resolved": "http://registry.npmjs.org/@types/jssha/-/jssha-0.0.29.tgz", - "integrity": "sha1-leg9uph4f/eW0tXzehklq/Qbycs=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/jssha/-/jssha-2.0.0.tgz", + "integrity": "sha512-oBnY3csYnXfqZXDRBJwP1nDDJCW/+VMJ88UHT4DCy0deSXpJIQvMCwYlnmdW4M+u7PiSfQc44LmiFcUbJ8hLEw==", "dev": true }, "@vue/component-compiler-utils": { @@ -299,9 +319,9 @@ } }, "ansi-escapes": { - "version": "3.1.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true }, "ansi-regex": { @@ -327,6 +347,17 @@ "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, "aproba": { @@ -392,11 +423,12 @@ } }, "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", "dev": true, "requires": { + "object-assign": "^4.1.1", "util": "0.10.3" }, "dependencies": { @@ -423,16 +455,10 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, - "async": { - "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "dev": true }, "atoa": { @@ -471,7 +497,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -482,9 +508,15 @@ "supports-color": "^2.0.0" } }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -573,9 +605,9 @@ "dev": true }, "binary-extensions": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", - "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, "bluebird": { @@ -859,24 +891,23 @@ "dev": true }, "chokidar": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", "dev": true, "requires": { "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", "glob-parent": "^3.1.0", - "inherits": "^2.0.1", + "inherits": "^2.0.3", "is-binary-path": "^1.0.0", "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", + "normalize-path": "^3.0.0", "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "readdirp": "^2.2.1", + "upath": "^1.1.1" } }, "chownr": { @@ -886,9 +917,9 @@ "dev": true }, "chrome-trace-event": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz", - "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -910,17 +941,6 @@ "safe-buffer": "^5.0.1" } }, - "clang-format": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.2.3.tgz", - "integrity": "sha512-x90Hac4ERacGDcZSvHKK58Ga0STuMD+Doi5g0iG2zf7wlJef5Huvhs/3BvMRFxwRYyYSdl6mpQNrtfMxE8MQzw==", - "dev": true, - "requires": { - "async": "^1.5.2", - "glob": "^7.0.0", - "resolve": "^1.1.6" - } - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -1020,9 +1040,9 @@ "dev": true }, "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "dev": true }, "concat-map": { @@ -1385,9 +1405,9 @@ "dev": true }, "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", "dev": true }, "diffie-hellman": { @@ -1484,6 +1504,12 @@ "tapable": "^1.0.0" } }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -1850,6 +1876,30 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, + "fork-ts-checker-webpack-plugin": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.3.5.tgz", + "integrity": "sha512-LpjSKaEVM17pst4MZeZWoYxwozZm4AB+bz4fR5oY9ksSwea86Dhj7J2dDEiHLlb+HtEwarh741kgMi25i+V6iw==", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "chalk": "^2.4.1", + "chokidar": "^2.0.4", + "micromatch": "^3.1.10", + "minimatch": "^3.0.4", + "semver": "^5.6.0", + "tapable": "^1.0.0", + "worker-rpc": "^0.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -1888,14 +1938,14 @@ "dev": true }, "fsevents": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", - "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", "dev": true, "optional": true, "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" }, "dependencies": { "abbrev": { @@ -1967,12 +2017,12 @@ "optional": true }, "debug": { - "version": "2.6.9", + "version": "4.1.1", "bundled": true, "dev": true, "optional": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "deep-extend": { @@ -2137,24 +2187,24 @@ } }, "ms": { - "version": "2.0.0", + "version": "2.1.1", "bundled": true, "dev": true, "optional": true }, "needle": { - "version": "2.2.4", + "version": "2.3.0", "bundled": true, "dev": true, "optional": true, "requires": { - "debug": "^2.1.2", + "debug": "^4.1.0", "iconv-lite": "^0.4.4", "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.10.3", + "version": "0.12.0", "bundled": true, "dev": true, "optional": true, @@ -2182,13 +2232,13 @@ } }, "npm-bundled": { - "version": "1.0.5", + "version": "1.0.6", "bundled": true, "dev": true, "optional": true }, "npm-packlist": { - "version": "1.2.0", + "version": "1.4.1", "bundled": true, "dev": true, "optional": true, @@ -2324,7 +2374,7 @@ "optional": true }, "semver": { - "version": "5.6.0", + "version": "5.7.0", "bundled": true, "dev": true, "optional": true @@ -2528,18 +2578,20 @@ "dev": true }, "gts": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/gts/-/gts-0.8.0.tgz", - "integrity": "sha512-VB9LQLFR+10cJhDBLYu9i2t7vTkewTXeBJbvw5+M2LqGgjiaKIUTIFbVBLjIknDpuaRpAzVcvhiHWy/30c09jg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gts/-/gts-1.0.0.tgz", + "integrity": "sha512-/CRhAi0/xkH1l9UveIlTxLLBcTzUNa2PHyBrllLuYtVXZc8NPh/hZmxO6JwxF6OD1GxJyAwyMt5+RjnBncI5jw==", "dev": true, "requires": { "chalk": "^2.4.1", - "clang-format": "1.2.3", + "diff": "^4.0.1", + "entities": "^1.1.1", "inquirer": "^6.0.0", "meow": "^5.0.0", - "pify": "^3.0.0", + "pify": "^4.0.0", + "prettier": "^1.15.3", "rimraf": "^2.6.2", - "tslint": "^5.9.1", + "tslint": "^5.12.0", "update-notifier": "^2.5.0", "write-file-atomic": "^2.3.0" } @@ -2762,24 +2814,52 @@ "dev": true }, "inquirer": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", - "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz", + "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^3.0.0", + "external-editor": "^3.0.3", "figures": "^2.0.0", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rxjs": "^6.1.0", + "rxjs": "^6.4.0", "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "strip-ansi": "^5.1.0", "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } } }, "interpret": { @@ -2835,15 +2915,6 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } - }, "is-ci": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", @@ -2911,9 +2982,9 @@ "dev": true }, "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -3015,6 +3086,12 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3034,9 +3111,9 @@ "dev": true }, "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { @@ -3109,6 +3186,14 @@ "parse-json": "^4.0.0", "pify": "^3.0.0", "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "loader-runner": { @@ -3144,12 +3229,6 @@ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true - }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -3183,6 +3262,14 @@ "dev": true, "requires": { "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "mamacro": { @@ -3295,6 +3382,12 @@ } } }, + "microevent.ts": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", + "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==", + "dev": true + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -3452,9 +3545,9 @@ "dev": true }, "nan": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", - "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "dev": true, "optional": true }, @@ -3478,9 +3571,9 @@ } }, "neo-async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", - "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, "nice-try": { @@ -3529,25 +3622,22 @@ } }, "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", + "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "npm-run-path": { "version": "2.0.2", @@ -3564,6 +3654,12 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -3849,6 +3945,14 @@ "dev": true, "requires": { "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "pbkdf2": { @@ -3864,10 +3968,16 @@ "sha.js": "^2.4.8" } }, + "picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "dev": true + }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, "pkg-dir": { @@ -4297,9 +4407,9 @@ } }, "registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "dev": true, "requires": { "rc": "^1.1.6", @@ -4346,12 +4456,12 @@ "dev": true }, "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", + "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", "dev": true, "requires": { - "path-parse": "^1.0.5" + "path-parse": "^1.0.6" } }, "resolve-cwd": { @@ -4439,9 +4549,9 @@ } }, "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -4469,9 +4579,9 @@ "dev": true }, "sass": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.16.1.tgz", - "integrity": "sha512-lKoiOI/zsAHrdYAdcWBM0pynYCmK0t7N9OAVjxAoYvo0mDBQmlhM6w+zNuFQYeS6d3VF+7KVWwkX6oWNMJxVag==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.21.0.tgz", + "integrity": "sha512-67hIIOZZtarbhI2aSgKBPDUgn+VqetduKoD+ZSYeIWg+ksNioTzeX+R2gUdebDoolvKNsQ/GY9NDxctbXluTNA==", "dev": true, "requires": { "chokidar": "^2.0.0" @@ -4504,9 +4614,9 @@ } }, "serialize-javascript": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.6.1.tgz", - "integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz", + "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==", "dev": true }, "set-blocking": { @@ -4732,9 +4842,9 @@ "dev": true }, "spdx-correct": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.2.tgz", - "integrity": "sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -4758,9 +4868,9 @@ } }, "spdx-license-ids": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", - "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", "dev": true }, "split-string": { @@ -4924,9 +5034,9 @@ } }, "terser": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", - "integrity": "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.0.0.tgz", + "integrity": "sha512-dOapGTU0hETFl1tCo4t56FN+2jffoKyER9qBGoUFyZ6y7WLoKT0bF+lAYi6B6YsILcGF3q1C2FBh8QcKSCgkgA==", "dev": true, "requires": { "commander": "^2.19.0", @@ -4943,19 +5053,21 @@ } }, "terser-webpack-plugin": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz", - "integrity": "sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg==", "dev": true, "requires": { - "cacache": "^11.0.2", + "cacache": "^11.3.2", "find-cache-dir": "^2.0.0", + "is-wsl": "^1.1.0", + "loader-utils": "^1.2.3", "schema-utils": "^1.0.0", - "serialize-javascript": "^1.4.0", + "serialize-javascript": "^1.7.0", "source-map": "^0.6.1", - "terser": "^3.16.1", - "webpack-sources": "^1.1.0", - "worker-farm": "^1.5.2" + "terser": "^4.0.0", + "webpack-sources": "^1.3.0", + "worker-farm": "^1.7.0" }, "dependencies": { "source-map": { @@ -5066,16 +5178,67 @@ "dev": true }, "ts-loader": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.3.3.tgz", - "integrity": "sha512-KwF1SplmOJepnoZ4eRIloH/zXL195F51skt7reEsS6jvDqzgc/YSbz9b8E07GxIUwLXdcD4ssrJu6v8CwaTafA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.0.2.tgz", + "integrity": "sha512-kkF3sGf3oBUehlvXI9fkbItbFTnNgGkYAz91vtWnsKAU4m+LAmQjuby7uTZNo3As+/zHLuyB052SkQDY6vLXtg==", "dev": true, "requires": { "chalk": "^2.3.0", "enhanced-resolve": "^4.0.0", "loader-utils": "^1.0.2", - "micromatch": "^3.1.4", - "semver": "^5.0.1" + "micromatch": "^4.0.0", + "semver": "^6.0.0" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } } }, "tslib": { @@ -5085,23 +5248,32 @@ "dev": true }, "tslint": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", - "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.17.0.tgz", + "integrity": "sha512-pflx87WfVoYepTet3xLfDOLDm9Jqi61UXIKePOuca0qoAZyrGWonDG9VTbji58Fy+8gciUn8Bt7y69+KEVjc/w==", "dev": true, "requires": { - "babel-code-frame": "^6.22.0", + "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", "chalk": "^2.3.0", "commander": "^2.12.1", "diff": "^3.2.0", "glob": "^7.1.1", - "js-yaml": "^3.7.0", + "js-yaml": "^3.13.1", "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.27.2" + "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + } } }, "tsutils": { @@ -5126,9 +5298,9 @@ "dev": true }, "typescript": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", - "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", + "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", "dev": true }, "union-value": { @@ -5246,9 +5418,9 @@ "dev": true }, "upath": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", "dev": true }, "update-notifier": { @@ -5333,9 +5505,9 @@ "dev": true }, "v8-compile-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz", - "integrity": "sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", + "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", "dev": true }, "validate-npm-package-license": { @@ -5427,9 +5599,9 @@ } }, "webpack": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.30.0.tgz", - "integrity": "sha512-4hgvO2YbAFUhyTdlR4FNyt2+YaYBYHavyzjCMbZzgglo02rlKi/pcsEzwCuCpsn1ryzIl1cq/u8ArIKu8JBYMg==", + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.33.0.tgz", + "integrity": "sha512-ggWMb0B2QUuYso6FPZKUohOgfm+Z0sVFs8WwWuSH1IAvkWs428VDNmOlAxvHGTB9Dm/qOB/qtE5cRx5y01clxw==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", @@ -5459,9 +5631,9 @@ } }, "webpack-cli": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.0.tgz", - "integrity": "sha512-t1M7G4z5FhHKJ92WRKwZ1rtvi7rHc0NZoZRbSkol0YKl4HvcC8+DsmGDmK7MmZxHSAetHagiOsjOB6MmzC2TUw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.3.tgz", + "integrity": "sha512-/qBxTvsxZ7bIFQtSa08QRY5BZuiJb27cbJM/nzmgXg9NEaudP20D7BruKKIuWfABqWoMEJQcNYYq/OxxSbPHlg==", "dev": true, "requires": { "chalk": "^2.4.1", @@ -5472,6 +5644,7 @@ "import-local": "^2.0.0", "interpret": "^1.1.0", "loader-utils": "^1.1.0", + "prettier": "^1.17.0", "supports-color": "^5.5.0", "v8-compile-cache": "^2.0.2", "yargs": "^12.0.5" @@ -5489,6 +5662,12 @@ "shebang-command": "^1.2.0", "which": "^1.2.9" } + }, + "prettier": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", + "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", + "dev": true } } }, @@ -5535,23 +5714,32 @@ "dev": true }, "widest-line": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", - "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "dev": true, "requires": { "string-width": "^2.1.1" } }, "worker-farm": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", - "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", "dev": true, "requires": { "errno": "~0.1.7" } }, + "worker-rpc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", + "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", + "dev": true, + "requires": { + "microevent.ts": "~0.1.1" + } + }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", @@ -5606,9 +5794,9 @@ "dev": true }, "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "dev": true, "requires": { "graceful-fs": "^4.1.11", diff --git a/package.json b/package.json index 5918d0297..a447be9b7 100644 --- a/package.json +++ b/package.json @@ -28,18 +28,19 @@ }, "homepage": "https://github.com/Authenticator-Extension/Authenticator#readme", "devDependencies": { - "@types/chrome": "^0.0.59", - "@types/crypto-js": "^3.1.38", - "@types/jssha": "0.0.29", + "@types/chrome": "^0.0.86", + "@types/crypto-js": "^3.1.43", + "@types/jssha": "2.0.0", "css-loader": "^2.1.1", - "gts": "^0.8.0", - "sass": "^1.16.1", - "ts-loader": "^5.3.3", - "typescript": "^2.9.2", + "fork-ts-checker-webpack-plugin": "^1.3.5", + "gts": "^1.0.0", + "sass": "^1.21.0", + "ts-loader": "^6.0.2", + "typescript": "^3.5.1", "vue-loader": "^15.7.0", "vue-template-compiler": "^2.6.10", - "webpack": "^4.30.0", - "webpack-cli": "^3.3.0", + "webpack": "^4.33.0", + "webpack-cli": "^3.3.3", "webpack-merge": "^4.2.1" }, "dependencies": { diff --git a/sass/_ui.scss b/sass/_ui.scss index 87217d862..23177ab47 100644 --- a/sass/_ui.scss +++ b/sass/_ui.scss @@ -56,6 +56,8 @@ $themes: ( ), ); +$theme-map: null; + @mixin themify($themes: $themes) { @each $theme, $map in $themes { .theme-#{$theme} & { diff --git a/webpack.config.js b/webpack.config.js index b4657d92b..94d080c93 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,6 @@ const path = require('path'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); module.exports = { mode: 'development', @@ -17,7 +18,11 @@ module.exports = { rules: [ { test: /\.tsx?$/, - use: 'ts-loader', + loader: 'ts-loader', + options: { + appendTsSuffixTo: [/\.vue$/], + transpileOnly: true + }, exclude: /node_modules/ }, { @@ -27,10 +32,27 @@ module.exports = { ] }, plugins: [ - new VueLoaderPlugin() + new VueLoaderPlugin(), + new ForkTsCheckerWebpackPlugin( + { + vue: true + } + ) ], resolve: { - extensions: ['.vue', '.tsx', '.ts', '.js'], + extensions: [ + '.mjs', + '.js', + '.jsx', + '.vue', + '.json', + '.wasm', + '.ts', + '.tsx' + ], + modules: [ + 'node_modules' + ] }, output: { path: path.resolve(__dirname, 'dist') From 36d1ad958bdbf73d90cff20106e6a7b805ec4b9b Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Fri, 7 Jun 2019 20:51:25 -0500 Subject: [PATCH 04/44] gts fix --- src/background.ts | 297 +++++++++------- src/content.ts | 132 ++++--- src/models/backup.ts | 535 +++++++++++++++------------- src/models/credentials.ts | 6 +- src/models/encryption.ts | 17 +- src/models/key-utilities.ts | 12 +- src/models/otp.ts | 30 +- src/models/storage.ts | 683 +++++++++++++++++++----------------- src/qrdebug.ts | 67 ++-- src/test/test.ts | 300 ++++++++-------- src/ui/add-account.ts | 54 +-- src/ui/backup.ts | 125 ++++--- src/ui/class.ts | 3 +- src/ui/entry.ts | 522 +++++++++++++++------------ src/ui/i18n.ts | 51 +-- src/ui/info.ts | 71 ++-- src/ui/menu.ts | 178 +++++----- src/ui/message.ts | 35 +- src/ui/passphrase.ts | 28 +- src/ui/qr.ts | 66 ++-- src/ui/ui.ts | 7 +- 21 files changed, 1789 insertions(+), 1430 deletions(-) diff --git a/src/background.ts b/src/background.ts index 1eeba170a..80e79624c 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,10 +1,11 @@ import * as CryptoJS from 'crypto-js'; +// tslint:disable-next-line:ban-ts-ignore // @ts-ignore import QRCode from 'qrcode-reader'; -import {getCredentials} from './models/credentials'; -import {Encryption} from './models/encryption'; -import {EntryStorage, ManagedStorage} from './models/storage'; +import { getCredentials } from './models/credentials'; +import { Encryption } from './models/encryption'; +import { EntryStorage, ManagedStorage } from './models/storage'; let cachedPassphrase = ''; chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { @@ -13,8 +14,13 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { return; } getQr( - sender.tab, message.info.left, message.info.top, message.info.width, - message.info.height, message.info.windowWidth); + sender.tab, + message.info.left, + message.info.top, + message.info.width, + message.info.height, + message.info.windowWidth + ); } else if (message.action === 'cachePassphrase') { cachedPassphrase = message.value; } else if (message.action === 'passphrase') { @@ -29,9 +35,14 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { let contentTab: chrome.tabs.Tab; function getQr( - tab: chrome.tabs.Tab, left: number, top: number, width: number, - height: number, windowWidth: number) { - chrome.tabs.captureVisibleTab(tab.windowId, {format: 'png'}, (dataUrl) => { + tab: chrome.tabs.Tab, + left: number, + top: number, + width: number, + height: number, + windowWidth: number +) { + chrome.tabs.captureVisibleTab(tab.windowId, { format: 'png' }, dataUrl => { contentTab = tab; const qr = new Image(); qr.src = dataUrl; @@ -45,27 +56,37 @@ function getQr( return; } ctx.drawImage( - qr, left * devicePixelRatio, top * devicePixelRatio, - width * devicePixelRatio, height * devicePixelRatio, 0, 0, - width * devicePixelRatio, height * devicePixelRatio); + qr, + left * devicePixelRatio, + top * devicePixelRatio, + width * devicePixelRatio, + height * devicePixelRatio, + 0, + 0, + width * devicePixelRatio, + height * devicePixelRatio + ); const url = captureCanvas.toDataURL(); const qrReader = new QRCode(); - qrReader.callback = (error: string, text: { - result: string, - points: Array<{ - x: number, - y: number, - count: number, - estimatedModuleSize: number - }> - }) => { + qrReader.callback = ( + error: string, + text: { + result: string; + points: Array<{ + x: number; + y: number; + count: number; + estimatedModuleSize: number; + }>; + } + ) => { if (error) { console.error(error); const id = contentTab.id; if (!id) { return; } - chrome.tabs.sendMessage(id, {action: 'errorqr'}); + chrome.tabs.sendMessage(id, { action: 'errorqr' }); } else { getTotp(text.result); } @@ -83,9 +104,9 @@ async function getTotp(text: string) { if (text.indexOf('otpauth://') !== 0) { if (text === 'error decoding QR Code') { - chrome.tabs.sendMessage(id, {action: 'errorqr'}); + chrome.tabs.sendMessage(id, { action: 'errorqr' }); } else { - chrome.tabs.sendMessage(id, {action: 'text', text}); + chrome.tabs.sendMessage(id, { action: 'text', text }); } } else { let uri = text.split('otpauth://')[1]; @@ -94,12 +115,12 @@ async function getTotp(text: string) { let label = uri.split('?')[0]; const parameterPart = uri.split('?')[1]; if (!label || !parameterPart) { - chrome.tabs.sendMessage(id, {action: 'errorqr'}); + chrome.tabs.sendMessage(id, { action: 'errorqr' }); } else { let account = ''; let secret = ''; let issuer = ''; - let period: number|undefined = undefined; + let period: number | undefined = undefined; try { label = decodeURIComponent(label); @@ -113,7 +134,7 @@ async function getTotp(text: string) { account = label; } const parameters = parameterPart.split('&'); - parameters.forEach((item) => { + parameters.forEach(item => { const parameter = item.split('='); if (parameter[0].toLowerCase() === 'secret') { secret = parameter[1]; @@ -125,33 +146,40 @@ async function getTotp(text: string) { } } else if (parameter[0].toLowerCase() === 'counter') { let counter = Number(parameter[1]); - counter = (isNaN(counter) || counter < 0) ? 0 : counter; + counter = isNaN(counter) || counter < 0 ? 0 : counter; } else if (parameter[0].toLowerCase() === 'period') { period = Number(parameter[1]); - period = (isNaN(period) || period < 0 || period > 60 || - 60 % period !== 0) ? - undefined : - period; + period = + isNaN(period) || period < 0 || period > 60 || 60 % period !== 0 + ? undefined + : period; } }); if (!secret) { - chrome.tabs.sendMessage(id, {action: 'errorqr'}); + chrome.tabs.sendMessage(id, { action: 'errorqr' }); } else if ( - !/^[0-9a-f]+$/i.test(secret) && !/^[2-7a-z]+=*$/i.test(secret)) { - chrome.tabs.sendMessage(id, {action: 'secretqr', secret}); + !/^[0-9a-f]+$/i.test(secret) && + !/^[2-7a-z]+=*$/i.test(secret) + ) { + chrome.tabs.sendMessage(id, { action: 'secretqr', secret }); } else { const encryption = new Encryption(cachedPassphrase); const hash = CryptoJS.MD5(secret).toString(); - if (!/^[2-7a-z]+=*$/i.test(secret) && /^[0-9a-f]+$/i.test(secret) && - type === 'totp') { + if ( + !/^[2-7a-z]+=*$/i.test(secret) && + /^[0-9a-f]+$/i.test(secret) && + type === 'totp' + ) { type = 'hex'; } else if ( - !/^[2-7a-z]+=*$/i.test(secret) && /^[0-9a-f]+$/i.test(secret) && - type === 'hotp') { + !/^[2-7a-z]+=*$/i.test(secret) && + /^[0-9a-f]+$/i.test(secret) && + type === 'hotp' + ) { type = 'hhex'; } - const entryData: {[hash: string]: OTPStorage} = {}; + const entryData: { [hash: string]: OTPStorage } = {}; entryData[hash] = { account, hash, @@ -166,7 +194,7 @@ async function getTotp(text: string) { entryData[hash].period = period; } await EntryStorage.import(encryption, entryData); - chrome.tabs.sendMessage(id, {action: 'added', account}); + chrome.tabs.sendMessage(id, { action: 'added', account }); } } } @@ -176,121 +204,130 @@ async function getTotp(text: string) { function getBackupToken(service: string) { if (navigator.userAgent.indexOf('Chrome') !== -1 && service === 'drive') { chrome.identity.getAuthToken( - { - 'interactive': true, - 'scopes': ['https://www.googleapis.com/auth/drive.file'], - }, - (value) => { - localStorage.driveToken = value; - chrome.runtime.sendMessage({action: 'drivetoken', value}); - return true; - }); + { + interactive: true, + scopes: ['https://www.googleapis.com/auth/drive.file'], + }, + value => { + localStorage.driveToken = value; + chrome.runtime.sendMessage({ action: 'drivetoken', value }); + return true; + } + ); } else { let authUrl = ''; if (service === 'dropbox') { authUrl = - 'https://www.dropbox.com/oauth2/authorize?response_type=token&client_id=' + - getCredentials().dropbox.client_id + '&redirect_uri=' + - encodeURIComponent(chrome.identity.getRedirectURL()); + 'https://www.dropbox.com/oauth2/authorize?response_type=token&client_id=' + + getCredentials().dropbox.client_id + + '&redirect_uri=' + + encodeURIComponent(chrome.identity.getRedirectURL()); } else if (service === 'drive') { authUrl = - 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&access_type=offline&client_id=' + - getCredentials().drive.client_id + - '&scope=https%3A//www.googleapis.com/auth/drive.file&prompt=consent&redirect_uri=' + - encodeURIComponent('https://authenticator.cc/oauth'); + 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&access_type=offline&client_id=' + + getCredentials().drive.client_id + + '&scope=https%3A//www.googleapis.com/auth/drive.file&prompt=consent&redirect_uri=' + + encodeURIComponent('https://authenticator.cc/oauth'); } chrome.identity.launchWebAuthFlow( - {url: authUrl, interactive: true}, async (url) => { - if (!url) { - return; - } - let hashMatches = url.split('#'); - if (service === 'drive') { - hashMatches = url.slice(0, -1).split('?'); - } - if (hashMatches.length < 2) { - return; - } + { url: authUrl, interactive: true }, + async url => { + if (!url) { + return; + } + let hashMatches = url.split('#'); + if (service === 'drive') { + hashMatches = url.slice(0, -1).split('?'); + } + if (hashMatches.length < 2) { + return; + } - const hash = hashMatches[1]; + const hash = hashMatches[1]; - const resData = hash.split('&'); - for (let i = 0; i < resData.length; i++) { - const kv = resData[i]; - if (/^(.*?)=(.*?)$/.test(kv)) { - const kvMatches = kv.match(/^(.*?)=(.*?)$/); - if (!kvMatches) { - continue; + const resData = hash.split('&'); + for (let i = 0; i < resData.length; i++) { + const kv = resData[i]; + if (/^(.*?)=(.*?)$/.test(kv)) { + const kvMatches = kv.match(/^(.*?)=(.*?)$/); + if (!kvMatches) { + continue; + } + const key = kvMatches[1]; + const value = kvMatches[2]; + if (key === 'access_token') { + if (service === 'dropbox') { + localStorage.dropboxToken = value; + chrome.runtime.sendMessage({ action: 'dropboxtoken', value }); + return; } - const key = kvMatches[1]; - const value = kvMatches[2]; - if (key === 'access_token') { - if (service === 'dropbox') { - localStorage.dropboxToken = value; - chrome.runtime.sendMessage({action: 'dropboxtoken', value}); - return; - } - } else if (key === 'code') { - if (service === 'drive') { - const xhr = new XMLHttpRequest(); - // Need to trade code we got from launchWebAuthFlow for a - // token & refresh token - await new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - xhr.open( - 'POST', - 'https://www.googleapis.com/oauth2/v4/token?client_id=' + - getCredentials().drive.client_id + - '&client_secret=' + - getCredentials().drive.client_secret + - '&code=' + value + - '&redirect_uri=https://authenticator.cc/oauth&grant_type=authorization_code'); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.setRequestHeader( - 'Content-Type', - 'application/x-www-form-urlencoded'); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - try { - const res = JSON.parse(xhr.responseText); - if (res.error) { - console.error(res.error_description); - resolve(false); - } else { - localStorage.driveToken = res.access_token; - localStorage.driveRefreshToken = - res.refresh_token; - resolve(true); - } - } catch (error) { - console.error(error); - reject(error); - } + } else if (key === 'code') { + if (service === 'drive') { + const xhr = new XMLHttpRequest(); + // Need to trade code we got from launchWebAuthFlow for a + // token & refresh token + await new Promise( + ( + resolve: (value: boolean) => void, + reject: (reason: Error) => void + ) => { + xhr.open( + 'POST', + 'https://www.googleapis.com/oauth2/v4/token?client_id=' + + getCredentials().drive.client_id + + '&client_secret=' + + getCredentials().drive.client_secret + + '&code=' + + value + + '&redirect_uri=https://authenticator.cc/oauth&grant_type=authorization_code' + ); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.setRequestHeader( + 'Content-Type', + 'application/x-www-form-urlencoded' + ); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + console.error(res.error_description); + resolve(false); + } else { + localStorage.driveToken = res.access_token; + localStorage.driveRefreshToken = res.refresh_token; + resolve(true); } - return; - }; - xhr.send(); - }); - chrome.runtime.sendMessage({action: 'drivetoken', value}); - } + } catch (error) { + console.error(error); + reject(error); + } + } + return; + }; + xhr.send(); + } + ); + chrome.runtime.sendMessage({ action: 'drivetoken', value }); } } } - return; - }); + } + return; + } + ); } } // Show issue page after first install -chrome.runtime.onInstalled.addListener(async (details) => { +chrome.runtime.onInstalled.addListener(async details => { if (details.reason !== 'install') { return; } else if (await ManagedStorage.get('disableInstallHelp')) { return; } - let url: string|null = null; + let url: string | null = null; if (navigator.userAgent.indexOf('Chrome') !== -1) { url = 'https://authenticator.cc/docs/en/chrome-issues'; diff --git a/src/content.ts b/src/content.ts index 70c618bbe..70a16b288 100644 --- a/src/content.ts +++ b/src/content.ts @@ -38,18 +38,20 @@ function showGrayLayout() { document.body.appendChild(grayLayout); const scan = document.createElement('div'); scan.className = 'scan'; - scan.style.background = 'url(' + - chrome.extension.getURL('images/scan.gif') + ') no-repeat center'; + scan.style.background = + 'url(' + + chrome.extension.getURL('images/scan.gif') + + ') no-repeat center'; grayLayout.appendChild(scan); const captureBox = document.createElement('div'); captureBox.id = '__ga_captureBox__'; grayLayout.appendChild(captureBox); grayLayout.onmousedown = grayLayoutDown; grayLayout.onmousemove = grayLayoutMove; - grayLayout.onmouseup = (event) => { + grayLayout.onmouseup = event => { grayLayoutUp(event); }; - grayLayout.oncontextmenu = (event) => { + grayLayout.oncontextmenu = event => { event.preventDefault(); return; }; @@ -88,19 +90,21 @@ function grayLayoutMove(event: MouseEvent) { } const captureBoxLeft = Math.min( - Number(sessionStorage.getItem('captureBoxPositionLeft')), event.clientX); + Number(sessionStorage.getItem('captureBoxPositionLeft')), + event.clientX + ); const captureBoxTop = Math.min( - Number(sessionStorage.getItem('captureBoxPositionTop')), event.clientY); + Number(sessionStorage.getItem('captureBoxPositionTop')), + event.clientY + ); const captureBoxWidth = - Math.abs( - Number(sessionStorage.getItem('captureBoxPositionLeft')) - - event.clientX) - - 1; + Math.abs( + Number(sessionStorage.getItem('captureBoxPositionLeft')) - event.clientX + ) - 1; const captureBoxHeight = - Math.abs( - Number(sessionStorage.getItem('captureBoxPositionTop')) - - event.clientY) - - 1; + Math.abs( + Number(sessionStorage.getItem('captureBoxPositionTop')) - event.clientY + ) - 1; captureBox.style.left = captureBoxLeft + 'px'; captureBox.style.top = captureBoxTop + 'px'; captureBox.style.width = captureBoxWidth + 'px'; @@ -126,59 +130,73 @@ function grayLayoutUp(event: MouseEvent) { } const captureBoxLeft = - Math.min( - Number(sessionStorage.getItem('captureBoxPositionLeft')), - event.clientX) + - 1; + Math.min( + Number(sessionStorage.getItem('captureBoxPositionLeft')), + event.clientX + ) + 1; const captureBoxTop = - Math.min( - Number(sessionStorage.getItem('captureBoxPositionTop')), - event.clientY) + - 1; + Math.min( + Number(sessionStorage.getItem('captureBoxPositionTop')), + event.clientY + ) + 1; const captureBoxWidth = - Math.abs( - Number(sessionStorage.getItem('captureBoxPositionLeft')) - - event.clientX) - - 1; + Math.abs( + Number(sessionStorage.getItem('captureBoxPositionLeft')) - event.clientX + ) - 1; const captureBoxHeight = - Math.abs( - Number(sessionStorage.getItem('captureBoxPositionTop')) - - event.clientY) - - 1; + Math.abs( + Number(sessionStorage.getItem('captureBoxPositionTop')) - event.clientY + ) - 1; // make sure captureBox and grayLayout is hidden setTimeout(() => { sendPosition( - captureBoxLeft, captureBoxTop, captureBoxWidth, captureBoxHeight); + captureBoxLeft, + captureBoxTop, + captureBoxWidth, + captureBoxHeight + ); }, 200); return false; } function sendPosition( - left: number, top: number, width: number, height: number) { + left: number, + top: number, + width: number, + height: number +) { chrome.runtime.sendMessage({ action: 'position', - info: {left, top, width, height, windowWidth: window.innerWidth}, + info: { left, top, width, height, windowWidth: window.innerWidth }, }); } function showQrCode(msg: string) { - const left = (screen.width / 2) - 200; - const top = (screen.height / 2) - 100; + const left = screen.width / 2 - 200; + const top = screen.height / 2 - 100; const url = - chrome.extension.getURL('view/qr.html') + '?' + encodeURIComponent(msg); + chrome.extension.getURL('view/qr.html') + '?' + encodeURIComponent(msg); window.open( - url, '_blank', - 'toolbar=no, location=no, status=no, menubar=no, scrollbars=yes, copyhistory=no, width=400, height=200, left=' + - left + ',top=' + top); + url, + '_blank', + 'toolbar=no, location=no, status=no, menubar=no, scrollbars=yes, copyhistory=no, width=400, height=200, left=' + + left + + ',top=' + + top + ); } function pasteCode(code: string) { const _inputBoxes = document.getElementsByTagName('input'); const inputBoxes: HTMLInputElement[] = []; for (let i = 0; i < _inputBoxes.length; i++) { - if (_inputBoxes[i].type === 'text' || _inputBoxes[i].type === 'number' || - _inputBoxes[i].type === 'tel' || _inputBoxes[i].type === 'password') { + if ( + _inputBoxes[i].type === 'text' || + _inputBoxes[i].type === 'number' || + _inputBoxes[i].type === 'tel' || + _inputBoxes[i].type === 'password' + ) { inputBoxes.push(_inputBoxes[i]); } } @@ -186,12 +204,20 @@ function pasteCode(code: string) { return; } const identities = [ - '2fa', 'otp', 'authenticator', 'factor', 'code', 'totp', 'twoFactorCode', + '2fa', + 'otp', + 'authenticator', + 'factor', + 'code', + 'totp', + 'twoFactorCode', ]; for (const inputBox of inputBoxes) { for (const identity of identities) { - if (inputBox.name.toLowerCase().indexOf(identity) >= 0 || - inputBox.id.toLowerCase().indexOf(identity) >= 0) { + if ( + inputBox.name.toLowerCase().indexOf(identity) >= 0 || + inputBox.id.toLowerCase().indexOf(identity) >= 0 + ) { if (!inputBox.value) { inputBox.value = code; fireInputEvents(inputBox); @@ -202,9 +228,9 @@ function pasteCode(code: string) { } const activeInputBox = - document.activeElement && document.activeElement.tagName === 'INPUT' ? - document.activeElement : - null; + document.activeElement && document.activeElement.tagName === 'INPUT' + ? document.activeElement + : null; if (activeInputBox) { const inputBox = activeInputBox as HTMLInputElement; if (!inputBox.value) { @@ -226,9 +252,11 @@ function pasteCode(code: string) { function fireInputEvents(inputBox: HTMLInputElement) { const events = [ - new KeyboardEvent('keydown'), new KeyboardEvent('keyup'), - new KeyboardEvent('keypress'), new Event('input', {'bubbles': true}), - new Event('change', {'bubbles': true}), + new KeyboardEvent('keydown'), + new KeyboardEvent('keyup'), + new KeyboardEvent('keypress'), + new Event('input', { bubbles: true }), + new Event('change', { bubbles: true }), ]; for (const event of events) { inputBox.dispatchEvent(event); @@ -236,8 +264,8 @@ function fireInputEvents(inputBox: HTMLInputElement) { return; } -window.onkeydown = (event) => { - if (event.keyCode === 27) { +window.onkeydown = event => { + if (event.key === 'Escape') { event.preventDefault(); const grayLayout = document.getElementById('__ga_grayLayout__'); const captureBox = document.getElementById('__ga_captureBox__'); diff --git a/src/models/backup.ts b/src/models/backup.ts index 221d808cb..e382e9c95 100644 --- a/src/models/backup.ts +++ b/src/models/backup.ts @@ -1,6 +1,6 @@ -import {getCredentials} from './credentials'; -import {Encryption} from './encryption'; -import {EntryStorage} from './storage'; +import { getCredentials } from './credentials'; +import { Encryption } from './encryption'; +import { EntryStorage } from './storage'; export class Dropbox { private async getToken() { @@ -13,160 +13,183 @@ export class Dropbox { localStorage.dropboxEncrypted = 'true'; } const exportData = await EntryStorage.getExport( - encryption, (localStorage.dropboxEncrypted === 'true')); + encryption, + localStorage.dropboxEncrypted === 'true' + ); const backup = JSON.stringify(exportData, null, 2); const url = 'https://content.dropboxapi.com/2/files/upload'; const token = await this.getToken(); return new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - if (!token) { - resolve(false); - } - try { - const xhr = new XMLHttpRequest(); - const now = - (new Date()).toISOString().slice(0, 10).replace(/-/g, ''); - const apiArg = { - path: `/${now}.json`, - mode: 'add', - autorename: true, - }; - xhr.open('POST', url); - xhr.setRequestHeader('Authorization', 'Bearer ' + token); - xhr.setRequestHeader('Content-type', 'application/octet-stream'); - xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify(apiArg)); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 401) { - localStorage.removeItem('dropboxToken'); - localStorage.dropboxRevoked = true; + (resolve: (value: boolean) => void, reject: (reason: Error) => void) => { + if (!token) { + resolve(false); + } + try { + const xhr = new XMLHttpRequest(); + const now = new Date() + .toISOString() + .slice(0, 10) + .replace(/-/g, ''); + const apiArg = { + path: `/${now}.json`, + mode: 'add', + autorename: true, + }; + xhr.open('POST', url); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader('Content-type', 'application/octet-stream'); + xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify(apiArg)); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('dropboxToken'); + localStorage.dropboxRevoked = true; + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (res.name) { + resolve(true); + } else { resolve(false); } - try { - const res = JSON.parse(xhr.responseText); - if (res.name) { - resolve(true); - } else { - resolve(false); - } - } catch (error) { - reject(error); - } + } catch (error) { + reject(error); } - return; - }; - xhr.send(backup); - } catch (error) { - return reject(error); - } - }); + } + return; + }; + xhr.send(backup); + } catch (error) { + return reject(error); + } + } + ); } } export class Drive { private async getToken() { - if (!localStorage.driveToken || - await new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - const xhr = new XMLHttpRequest(); - xhr.open('GET', 'https://www.googleapis.com/drive/v3/files'); - xhr.setRequestHeader( - 'Authorization', 'Bearer ' + localStorage.driveToken); - xhr.onreadystatechange = async () => { - if (xhr.readyState === 4) { - try { - const res = JSON.parse(xhr.responseText); - if (res.error) { - if (res.error.code === 401) { - if (navigator.userAgent.indexOf('Chrome') !== -1 && - navigator.userAgent.indexOf('OPR') === -1) { - // Clear invalid token from - // chrome://identity-internals/ - await chrome.identity.removeCachedAuthToken( - {'token': localStorage.driveToken}); - } - localStorage.driveToken = ''; - resolve(true); - } - } else { - resolve(false); + if ( + !localStorage.driveToken || + (await new Promise( + ( + resolve: (value: boolean) => void, + reject: (reason: Error) => void + ) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', 'https://www.googleapis.com/drive/v3/files'); + xhr.setRequestHeader( + 'Authorization', + 'Bearer ' + localStorage.driveToken + ); + xhr.onreadystatechange = async () => { + if (xhr.readyState === 4) { + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + if (res.error.code === 401) { + if ( + navigator.userAgent.indexOf('Chrome') !== -1 && + navigator.userAgent.indexOf('OPR') === -1 + ) { + // Clear invalid token from + // chrome://identity-internals/ + await chrome.identity.removeCachedAuthToken({ + token: localStorage.driveToken, + }); } - } catch (error) { - console.error(error); - reject(error); + localStorage.driveToken = ''; + resolve(true); } + } else { + resolve(false); } - return; - }; - xhr.send(); - })) { + } catch (error) { + console.error(error); + reject(error); + } + } + return; + }; + xhr.send(); + } + )) + ) { await this.refreshToken(); } return localStorage.driveToken; } async refreshToken() { - if (navigator.userAgent.indexOf('Chrome') !== -1 && - navigator.userAgent.indexOf('OPR') === -1) { + if ( + navigator.userAgent.indexOf('Chrome') !== -1 && + navigator.userAgent.indexOf('OPR') === -1 + ) { return new Promise((resolve: (value: boolean) => void) => { return chrome.identity.getAuthToken( - { - 'interactive': false, - 'scopes': ['https://www.googleapis.com/auth/drive.file'], - }, - (token) => { - localStorage.driveToken = token; - if (!Boolean(token)) { - localStorage.driveRevoked = true; - } - resolve(Boolean(token)); - }); + { + interactive: false, + scopes: ['https://www.googleapis.com/auth/drive.file'], + }, + token => { + localStorage.driveToken = token; + if (!Boolean(token)) { + localStorage.driveRevoked = true; + } + resolve(Boolean(token)); + } + ); }); } else { return new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - const xhr = new XMLHttpRequest(); - xhr.open( - 'POST', - 'https://www.googleapis.com/oauth2/v4/token?client_id=' + - getCredentials().drive.client_id + - '&client_secret=' + getCredentials().drive.client_secret + - '&refresh_token=' + localStorage.driveRefreshToken + - '&grant_type=refresh_token'); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 401) { - localStorage.removeItem('driveRefreshToken'); - localStorage.driveRevoked = true; - resolve(false); - } - try { - const res = JSON.parse(xhr.responseText); - if (res.error) { - if (res.error === 'invalid_grant') { - localStorage.removeItem('driveRefreshToken'); - localStorage.driveRevoked = true; - } - console.error(res.error_description); - resolve(false); - } else { - localStorage.driveToken = res.access_token; - resolve(true); + ( + resolve: (value: boolean) => void, + reject: (reason: Error) => void + ) => { + const xhr = new XMLHttpRequest(); + xhr.open( + 'POST', + 'https://www.googleapis.com/oauth2/v4/token?client_id=' + + getCredentials().drive.client_id + + '&client_secret=' + + getCredentials().drive.client_secret + + '&refresh_token=' + + localStorage.driveRefreshToken + + '&grant_type=refresh_token' + ); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveRefreshToken'); + localStorage.driveRevoked = true; + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + if (res.error === 'invalid_grant') { + localStorage.removeItem('driveRefreshToken'); + localStorage.driveRevoked = true; } - } catch (error) { - console.error(error); - reject(error); + console.error(res.error_description); + resolve(false); + } else { + localStorage.driveToken = res.access_token; + resolve(true); } + } catch (error) { + console.error(error); + reject(error); } - return; - }; - xhr.send(); - }); + } + return; + }; + xhr.send(); + } + ); } } @@ -179,84 +202,94 @@ export class Drive { } if (localStorage.driveFolder) { await new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - const xhr = new XMLHttpRequest(); - xhr.open( - 'GET', - 'https://www.googleapis.com/drive/v3/files/' + - localStorage.driveFolder + '?fields=trashed'); - xhr.setRequestHeader('Authorization', 'Bearer ' + token); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 401) { - localStorage.removeItem('driveToken'); - resolve(false); - } - try { - const res = JSON.parse(xhr.responseText); - if (res.error) { - if (res.error.code === 404) { - localStorage.driveFolder = ''; - resolve(true); - } - } else if (res.trashed) { + ( + resolve: (value: boolean) => void, + reject: (reason: Error) => void + ) => { + const xhr = new XMLHttpRequest(); + xhr.open( + 'GET', + 'https://www.googleapis.com/drive/v3/files/' + + localStorage.driveFolder + + '?fields=trashed' + ); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + if (res.error.code === 404) { localStorage.driveFolder = ''; resolve(true); - } else if (res.error) { - console.error(res.error.message); - resolve(false); - } else { - resolve(true); } - } catch (error) { - console.error(error); - reject(error); + } else if (res.trashed) { + localStorage.driveFolder = ''; + resolve(true); + } else if (res.error) { + console.error(res.error.message); + resolve(false); + } else { + resolve(true); } + } catch (error) { + console.error(error); + reject(error); } - return; - }; - xhr.send(); - }); + } + return; + }; + xhr.send(); + } + ); } if (!localStorage.driveFolder) { await new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - // create folder - const xhr = new XMLHttpRequest(); - xhr.open('POST', 'https://www.googleapis.com/drive/v3/files/'); - xhr.setRequestHeader('Authorization', 'Bearer ' + token); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.setRequestHeader('Content-Type', 'application/json'); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 401) { - localStorage.removeItem('driveToken'); + ( + resolve: (value: boolean) => void, + reject: (reason: Error) => void + ) => { + // create folder + const xhr = new XMLHttpRequest(); + xhr.open('POST', 'https://www.googleapis.com/drive/v3/files/'); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (!res.error) { + localStorage.driveFolder = res.id; + resolve(true); + } else { + console.error(res.error.message); resolve(false); } - try { - const res = JSON.parse(xhr.responseText); - if (!res.error) { - localStorage.driveFolder = res.id; - resolve(true); - } else { - console.error(res.error.message); - resolve(false); - } - } catch (error) { - console.error(error); - reject(error); - } + } catch (error) { + console.error(error); + reject(error); } - return; - }; - xhr.send(JSON.stringify({ - 'name': 'Authenticator Backups', - 'mimeType': 'application/vnd.google-apps.folder', - })); - }); + } + return; + }; + xhr.send( + JSON.stringify({ + name: 'Authenticator Backups', + mimeType: 'application/vnd.google-apps.folder', + }) + ); + } + ); } return localStorage.driveFolder; } @@ -266,7 +299,9 @@ export class Drive { localStorage.driveEncrypted = 'true'; } const exportData = await EntryStorage.getExport( - encryption, (localStorage.driveEncrypted === 'true')); + encryption, + localStorage.driveEncrypted === 'true' + ); const backup = JSON.stringify(exportData, null, 2); const token = await this.getToken(); @@ -277,57 +312,69 @@ export class Drive { } const folderId = await this.getFolder(); return new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - if (!token || !folderId) { - resolve(false); - } - try { - const xhr = new XMLHttpRequest(); - const now = - (new Date()).toISOString().slice(0, 10).replace(/-/g, ''); - xhr.open( - 'POST', - 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart'); - xhr.setRequestHeader('Authorization', 'Bearer ' + token); - xhr.setRequestHeader( - 'Content-type', 'multipart/related; boundary=segment_marker'); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 401) { - localStorage.removeItem('driveToken'); + (resolve: (value: boolean) => void, reject: (reason: Error) => void) => { + if (!token || !folderId) { + resolve(false); + } + try { + const xhr = new XMLHttpRequest(); + const now = new Date() + .toISOString() + .slice(0, 10) + .replace(/-/g, ''); + xhr.open( + 'POST', + 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart' + ); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader( + 'Content-type', + 'multipart/related; boundary=segment_marker' + ); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (!res.error) { + resolve(true); + } else { + console.error(res.error.message); resolve(false); } - try { - const res = JSON.parse(xhr.responseText); - if (!res.error) { - resolve(true); - } else { - console.error(res.error.message); - resolve(false); - } - } catch (error) { - reject(error); - } + } catch (error) { + reject(error); } - return; - }; - const requestDataPrototype = [ - '--segment_marker', - 'Content-Type: application/json; charset=UTF-8', '', - JSON.stringify( - {name: `${now}.json`, parents: [localStorage.driveFolder]}), - '', '--segment_marker', 'Content-Type: application/octet-stream', - '', backup, '--segment_marker--', - ]; - let requestData = ''; - requestDataPrototype.forEach((line) => { - requestData = requestData + line + '\n'; - }); - xhr.send(requestData); - } catch (error) { - return reject(error); - } - }); + } + return; + }; + const requestDataPrototype = [ + '--segment_marker', + 'Content-Type: application/json; charset=UTF-8', + '', + JSON.stringify({ + name: `${now}.json`, + parents: [localStorage.driveFolder], + }), + '', + '--segment_marker', + 'Content-Type: application/octet-stream', + '', + backup, + '--segment_marker--', + ]; + let requestData = ''; + requestDataPrototype.forEach(line => { + requestData = requestData + line + '\n'; + }); + xhr.send(requestData); + } catch (error) { + return reject(error); + } + } + ); } } diff --git a/src/models/credentials.ts b/src/models/credentials.ts index ca8cae4da..dab30c1b7 100644 --- a/src/models/credentials.ts +++ b/src/models/credentials.ts @@ -1,11 +1,11 @@ export function getCredentials() { return { drive: { - client_id: '', // Google client ID - client_secret: '', // Google client secret + client_id: '', // Google client ID + client_secret: '', // Google client secret }, dropbox: { - client_id: '', // Dropbox client ID + client_id: '', // Dropbox client ID }, }; } diff --git a/src/models/encryption.ts b/src/models/encryption.ts index e94005112..c07ce22fe 100644 --- a/src/models/encryption.ts +++ b/src/models/encryption.ts @@ -20,8 +20,10 @@ export class Encryption implements IEncryption { } try { - let decryptedSecret = CryptoJS.AES.decrypt(secret, this.password) - .toString(CryptoJS.enc.Utf8); + let decryptedSecret = CryptoJS.AES.decrypt( + secret, + this.password + ).toString(CryptoJS.enc.Utf8); if (!decryptedSecret) { return 'Encrypted'; @@ -37,10 +39,13 @@ export class Encryption implements IEncryption { decryptedSecret = decryptedSecret.replace(/ /g, ''); - if (!/^[a-z2-7]+=*$/i.test(decryptedSecret) && - !/^[0-9a-f]+$/i.test(decryptedSecret) && - !/^blz\-/.test(decryptedSecret) && !/^bliz\-/.test(decryptedSecret) && - !/^stm\-/.test(decryptedSecret)) { + if ( + !/^[a-z2-7]+=*$/i.test(decryptedSecret) && + !/^[0-9a-f]+$/i.test(decryptedSecret) && + !/^blz\-/.test(decryptedSecret) && + !/^bliz\-/.test(decryptedSecret) && + !/^stm\-/.test(decryptedSecret) + ) { return 'Encrypted'; } diff --git a/src/models/key-utilities.ts b/src/models/key-utilities.ts index 625ba256b..6f566e97c 100644 --- a/src/models/key-utilities.ts +++ b/src/models/key-utilities.ts @@ -91,7 +91,11 @@ export class KeyUtilities { } static generate( - type: OTPType, secret: string, counter: number, period: number) { + type: OTPType, + secret: string, + counter: number, + period: number + ) { secret = secret.replace(/\s/g, ''); let len = 6; let b26 = false; @@ -152,8 +156,8 @@ export class KeyUtilities { } let otp = - (this.hex2dec(hmac.substr(offset * 2, 8)) & this.hex2dec('7fffffff')) + - ''; + (this.hex2dec(hmac.substr(offset * 2, 8)) & this.hex2dec('7fffffff')) + + ''; if (b26) { return this.base26(Number(otp)); @@ -162,6 +166,6 @@ export class KeyUtilities { if (otp.length < len) { otp = new Array(len - otp.length + 1).join('0') + otp; } - return (otp).substr(otp.length - len, len).toString(); + return otp.substr(otp.length - len, len).toString(); } } diff --git a/src/models/otp.ts b/src/models/otp.ts index 48ca6d895..5140bcbdc 100644 --- a/src/models/otp.ts +++ b/src/models/otp.ts @@ -1,8 +1,8 @@ import * as CryptoJS from 'crypto-js'; -import {Encryption} from './encryption'; -import {KeyUtilities} from './key-utilities'; -import {EntryStorage} from './storage'; +import { Encryption } from './encryption'; +import { KeyUtilities } from './key-utilities'; +import { EntryStorage } from './storage'; export class OTPEntry implements IOTPEntry { type: OTPType; @@ -16,16 +16,24 @@ export class OTPEntry implements IOTPEntry { code = '••••••'; constructor( - type: OTPType, issuer: string, secret: string, account: string, - index: number, counter: number, period?: number, hash?: string) { + type: OTPType, + issuer: string, + secret: string, + account: string, + index: number, + counter: number, + period?: number, + hash?: string + ) { this.type = type; this.index = index; this.issuer = issuer; this.secret = secret; this.account = account; - this.hash = hash && /^[0-9a-f]{32}$/.test(hash) ? - hash : - CryptoJS.MD5(secret).toString(); + this.hash = + hash && /^[0-9a-f]{32}$/.test(hash) + ? hash + : CryptoJS.MD5(secret).toString(); this.counter = counter; if (this.type === OTPType.totp && period) { this.period = period; @@ -68,7 +76,11 @@ export class OTPEntry implements IOTPEntry { } else { try { this.code = KeyUtilities.generate( - this.type, this.secret, this.counter, this.period); + this.type, + this.secret, + this.counter, + this.period + ); } catch (error) { this.code = 'Invalid'; if (parent) { diff --git a/src/models/storage.ts b/src/models/storage.ts index d7d1ce078..6db2531ba 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -1,13 +1,13 @@ import * as CryptoJS from 'crypto-js'; -import {Encryption} from './encryption'; -import {OTPEntry} from './otp'; +import { Encryption } from './encryption'; +import { OTPEntry } from './otp'; export class BrowserStorage { private static async getStorageLocation() { const managedLocation = await ManagedStorage.get('storageArea'); if (managedLocation === 'sync' || managedLocation === 'local') { - return new Promise((resolve) => { + return new Promise(resolve => { if (localStorage.storageLocation !== managedLocation) { localStorage.storageLocation = managedLocation; } @@ -15,15 +15,16 @@ export class BrowserStorage { return; }); } else if ( - localStorage.storageLocation !== 'sync' && - localStorage.storageLocation !== 'local') { + localStorage.storageLocation !== 'sync' && + localStorage.storageLocation !== 'local' + ) { return new Promise((resolve, reject) => { let amountSync: number; let amountLocal: number; - chrome.storage.local.get((local) => { + chrome.storage.local.get(local => { amountLocal = Object.keys(local).length; try { - chrome.storage.sync.get((sync) => { + chrome.storage.sync.get(sync => { amountSync = Object.keys(sync).length; // If storage location can't be found try to auto-detect storage // location @@ -49,7 +50,7 @@ export class BrowserStorage { }); }); } else { - return new Promise((resolve) => { + return new Promise(resolve => { resolve(localStorage.storageLocation); return; }); @@ -57,7 +58,7 @@ export class BrowserStorage { } /* tslint:disable-next-line:no-any */ - static async get(callback: (items: {[key: string]: any;}) => void) { + static async get(callback: (items: { [key: string]: any }) => void) { const storageLocation = await this.getStorageLocation(); if (storageLocation === 'local') { chrome.storage.local.get(callback); @@ -67,7 +68,7 @@ export class BrowserStorage { return; } - static async set(data: object, callback?: (() => void)|undefined) { + static async set(data: object, callback?: (() => void) | undefined) { const storageLocation = await this.getStorageLocation(); if (storageLocation === 'local') { chrome.storage.local.set(data, callback); @@ -78,7 +79,9 @@ export class BrowserStorage { } static async remove( - data: string|string[], callback?: (() => void)|undefined) { + data: string | string[], + callback?: (() => void) | undefined + ) { const storageLocation = await this.getStorageLocation(); if (storageLocation === 'local') { chrome.storage.local.remove(data, callback); @@ -91,7 +94,9 @@ export class BrowserStorage { export class EntryStorage { private static getOTPStorageFromEntry( - encryption: Encryption, entry: OTPEntry): OTPStorage { + encryption: Encryption, + entry: OTPEntry + ): OTPStorage { const storageItem: OTPStorage = { account: entry.account, hash: entry.hash, @@ -108,7 +113,7 @@ export class EntryStorage { return storageItem; } - private static ensureUniqueIndex(_data: {[hash: string]: OTPStorage}) { + private static ensureUniqueIndex(_data: { [hash: string]: OTPStorage }) { const tempEntryArray: OTPStorage[] = []; for (const hash of Object.keys(_data)) { @@ -122,7 +127,7 @@ export class EntryStorage { return a.index - b.index; }); - const newData: {[hash: string]: OTPStorage} = {}; + const newData: { [hash: string]: OTPStorage } = {}; for (let i = 0; i < tempEntryArray.length; i++) { tempEntryArray[i].index = i; newData[tempEntryArray[i].hash] = tempEntryArray[i]; @@ -141,7 +146,9 @@ export class EntryStorage { } private static isValidEntry( - _data: {[hash: string]: OTPStorage}, hash: string) { + _data: { [hash: string]: OTPStorage }, + hash: string + ) { if (typeof _data[hash] !== 'object') { console.log('Key "' + hash + '" is not an object'); return false; @@ -156,383 +163,425 @@ export class EntryStorage { static hasEncryptedEntry() { return new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - for (const hash of Object.keys(_data)) { - if (!this.isValidEntry(_data, hash)) { - continue; - } - if (_data[hash].encrypted) { - return resolve(true); - } + (resolve: (value: boolean) => void, reject: (reason: Error) => void) => { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + for (const hash of Object.keys(_data)) { + if (!this.isValidEntry(_data, hash)) { + continue; } - return resolve(false); - }); - return; + if (_data[hash].encrypted) { + return resolve(true); + } + } + return resolve(false); }); + return; + } + ); } static getExport(encryption: Encryption, encrypted?: boolean) { return new Promise( - (resolve: (value: {[hash: string]: OTPStorage}) => void, - reject: (reason: Error) => void) => { - try { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - for (const hash of Object.keys(_data)) { - if (!this.isValidEntry(_data, hash)) { - delete _data[hash]; - continue; - } - if (!encrypted) { - // decrypt the data to export - if (_data[hash].encrypted) { - const decryptedSecret = - encryption.getDecryptedSecret(_data[hash].secret, hash); - if (decryptedSecret !== _data[hash].secret && - decryptedSecret !== 'Encrypted') { - _data[hash].secret = decryptedSecret; - _data[hash].encrypted = false; - } - } - // we need correct hash - if (hash !== _data[hash].hash) { - _data[_data[hash].hash] = _data[hash]; - delete _data[hash]; + ( + resolve: (value: { [hash: string]: OTPStorage }) => void, + reject: (reason: Error) => void + ) => { + try { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + for (const hash of Object.keys(_data)) { + if (!this.isValidEntry(_data, hash)) { + delete _data[hash]; + continue; + } + if (!encrypted) { + // decrypt the data to export + if (_data[hash].encrypted) { + const decryptedSecret = encryption.getDecryptedSecret( + _data[hash].secret, + hash + ); + if ( + decryptedSecret !== _data[hash].secret && + decryptedSecret !== 'Encrypted' + ) { + _data[hash].secret = decryptedSecret; + _data[hash].encrypted = false; } } + // we need correct hash + if (hash !== _data[hash].hash) { + _data[_data[hash].hash] = _data[hash]; + delete _data[hash]; + } } - return resolve(_data); - }); - return; - } catch (error) { - return reject(error); - } - }); + } + return resolve(_data); + }); + return; + } catch (error) { + return reject(error); + } + } + ); } - static import(encryption: Encryption, data: {[hash: string]: OTPStorage}) { + static import(encryption: Encryption, data: { [hash: string]: OTPStorage }) { return new Promise( - (resolve: () => void, reject: (reason: Error) => void) => { - try { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - for (const hash of Object.keys(data)) { - // never trust data import from user - // we do not support encrypted data import any longer - if (!data[hash].secret || data[hash].encrypted) { - // we need give a failed warning - continue; - } + (resolve: () => void, reject: (reason: Error) => void) => { + try { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + for (const hash of Object.keys(data)) { + // never trust data import from user + // we do not support encrypted data import any longer + if (!data[hash].secret || data[hash].encrypted) { + // we need give a failed warning + continue; + } - data[hash].hash = data[hash].hash || hash; - data[hash].account = data[hash].account || ''; - data[hash].encrypted = encryption.getEncryptionStatus(); - data[hash].index = data[hash].index || 0; - data[hash].issuer = data[hash].issuer || ''; - data[hash].type = data[hash].type || OTPType[OTPType.totp]; - data[hash].counter = data[hash].counter || 0; - const period = data[hash].period; - if (data[hash].type !== OTPType[OTPType.totp] || - period && (isNaN(period) || period <= 0)) { - delete data[hash].period; - } + data[hash].hash = data[hash].hash || hash; + data[hash].account = data[hash].account || ''; + data[hash].encrypted = encryption.getEncryptionStatus(); + data[hash].index = data[hash].index || 0; + data[hash].issuer = data[hash].issuer || ''; + data[hash].type = data[hash].type || OTPType[OTPType.totp]; + data[hash].counter = data[hash].counter || 0; + const period = data[hash].period; + if ( + data[hash].type !== OTPType[OTPType.totp] || + (period && (isNaN(period) || period <= 0)) + ) { + delete data[hash].period; + } - if (/^(blz\-|bliz\-)/.test(data[hash].secret)) { - const secretMatches = - data[hash].secret.match(/^(blz\-|bliz\-)(.*)/); - if (secretMatches && secretMatches.length >= 3) { - data[hash].secret = secretMatches[2]; - data[hash].type = OTPType[OTPType.battle]; - } + if (/^(blz\-|bliz\-)/.test(data[hash].secret)) { + const secretMatches = data[hash].secret.match( + /^(blz\-|bliz\-)(.*)/ + ); + if (secretMatches && secretMatches.length >= 3) { + data[hash].secret = secretMatches[2]; + data[hash].type = OTPType[OTPType.battle]; } + } - if (/^stm\-/.test(data[hash].secret)) { - const secretMatches = data[hash].secret.match(/^stm\-(.*)/); - if (secretMatches && secretMatches.length >= 2) { - data[hash].secret = secretMatches[1]; - data[hash].type = OTPType[OTPType.steam]; - } + if (/^stm\-/.test(data[hash].secret)) { + const secretMatches = data[hash].secret.match(/^stm\-(.*)/); + if (secretMatches && secretMatches.length >= 2) { + data[hash].secret = secretMatches[1]; + data[hash].type = OTPType[OTPType.steam]; } + } - if (!/^[a-z2-7]+=*$/i.test(data[hash].secret) && - /^[0-9a-f]+$/i.test(data[hash].secret) && - data[hash].type === OTPType[OTPType.totp]) { - data[hash].type = OTPType[OTPType.hex]; - } + if ( + !/^[a-z2-7]+=*$/i.test(data[hash].secret) && + /^[0-9a-f]+$/i.test(data[hash].secret) && + data[hash].type === OTPType[OTPType.totp] + ) { + data[hash].type = OTPType[OTPType.hex]; + } - if (!/^[a-z2-7]+=*$/i.test(data[hash].secret) && - /^[0-9a-f]+$/i.test(data[hash].secret) && - data[hash].type === OTPType[OTPType.hotp]) { - data[hash].type = OTPType[OTPType.hhex]; - } + if ( + !/^[a-z2-7]+=*$/i.test(data[hash].secret) && + /^[0-9a-f]+$/i.test(data[hash].secret) && + data[hash].type === OTPType[OTPType.hotp] + ) { + data[hash].type = OTPType[OTPType.hhex]; + } - const _hash = CryptoJS.MD5(data[hash].secret).toString(); - // not a valid hash - if (!/^[0-9a-f]{32}$/.test(hash)) { - data[_hash] = data[hash]; - data[_hash].hash = _hash; - delete data[hash]; - } + const _hash = CryptoJS.MD5(data[hash].secret).toString(); + // not a valid hash + if (!/^[0-9a-f]{32}$/.test(hash)) { + data[_hash] = data[hash]; + data[_hash].hash = _hash; + delete data[hash]; + } - if (data[_hash]) { - data[_hash].secret = - encryption.getEncryptedSecret(data[_hash].secret); - _data[_hash] = data[_hash]; - } else { - data[hash].secret = - encryption.getEncryptedSecret(data[hash].secret); - _data[hash] = data[hash]; - } + if (data[_hash]) { + data[_hash].secret = encryption.getEncryptedSecret( + data[_hash].secret + ); + _data[_hash] = data[_hash]; + } else { + data[hash].secret = encryption.getEncryptedSecret( + data[hash].secret + ); + _data[hash] = data[hash]; } - _data = this.ensureUniqueIndex(_data); - BrowserStorage.set(_data, resolve); - }); - return; - } catch (error) { - return reject(error); - } - }); + } + _data = this.ensureUniqueIndex(_data); + BrowserStorage.set(_data, resolve); + }); + return; + } catch (error) { + return reject(error); + } + } + ); } static add(encryption: Encryption, entry: OTPEntry) { return new Promise( - (resolve: () => void, reject: (reason: Error) => void) => { - try { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - if (_data.hasOwnProperty(entry.hash)) { - throw new Error('The specific entry has already existed.'); - } - const storageItem = - this.getOTPStorageFromEntry(encryption, entry); - _data[entry.hash] = storageItem; - _data = this.ensureUniqueIndex(_data); - BrowserStorage.set(_data, resolve); - }); - return; - } catch (error) { - return reject(error); - } - }); + (resolve: () => void, reject: (reason: Error) => void) => { + try { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + if (_data.hasOwnProperty(entry.hash)) { + throw new Error('The specific entry has already existed.'); + } + const storageItem = this.getOTPStorageFromEntry(encryption, entry); + _data[entry.hash] = storageItem; + _data = this.ensureUniqueIndex(_data); + BrowserStorage.set(_data, resolve); + }); + return; + } catch (error) { + return reject(error); + } + } + ); } static update(encryption: Encryption, entry: OTPEntry) { return new Promise( - (resolve: () => void, reject: (reason: Error) => void) => { - try { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - if (!_data.hasOwnProperty(entry.hash)) { - throw new Error('The specific entry is not existing.'); - } - const storageItem = - this.getOTPStorageFromEntry(encryption, entry); - _data[entry.hash] = storageItem; - _data = this.ensureUniqueIndex(_data); - BrowserStorage.set(_data, resolve); - }); - return; - } catch (error) { - return reject(error); - } - }); + (resolve: () => void, reject: (reason: Error) => void) => { + try { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + if (!_data.hasOwnProperty(entry.hash)) { + throw new Error('The specific entry is not existing.'); + } + const storageItem = this.getOTPStorageFromEntry(encryption, entry); + _data[entry.hash] = storageItem; + _data = this.ensureUniqueIndex(_data); + BrowserStorage.set(_data, resolve); + }); + return; + } catch (error) { + return reject(error); + } + } + ); } static set(encryption: Encryption, entries: OTPEntry[]) { return new Promise( - (resolve: () => void, reject: (reason: Error) => void) => { - try { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - entries.forEach(entry => { - const storageItem = - this.getOTPStorageFromEntry(encryption, entry); - _data[entry.hash] = storageItem; - }); - _data = this.ensureUniqueIndex(_data); - BrowserStorage.set(_data, resolve); + (resolve: () => void, reject: (reason: Error) => void) => { + try { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + entries.forEach(entry => { + const storageItem = this.getOTPStorageFromEntry( + encryption, + entry + ); + _data[entry.hash] = storageItem; }); - return; - } catch (error) { - reject(error); - } - }); + _data = this.ensureUniqueIndex(_data); + BrowserStorage.set(_data, resolve); + }); + return; + } catch (error) { + reject(error); + } + } + ); } static get(encryption: Encryption) { return new Promise( - (resolve: (value: OTPEntry[]) => void, - reject: (reason: Error) => void) => { - try { - BrowserStorage.get(async (_data: {[hash: string]: OTPStorage}) => { - const data: OTPEntry[] = []; - for (const hash of Object.keys(_data)) { - if (!this.isValidEntry(_data, hash)) { - continue; - } - const entryData = _data[hash]; - let needMigrate = false; + ( + resolve: (value: OTPEntry[]) => void, + reject: (reason: Error) => void + ) => { + try { + BrowserStorage.get(async (_data: { [hash: string]: OTPStorage }) => { + const data: OTPEntry[] = []; + for (const hash of Object.keys(_data)) { + if (!this.isValidEntry(_data, hash)) { + continue; + } + const entryData = _data[hash]; + let needMigrate = false; - if (!entryData.hash) { - entryData.hash = hash; - needMigrate = true; - } + if (!entryData.hash) { + entryData.hash = hash; + needMigrate = true; + } - if (!entryData.type) { + if (!entryData.type) { + entryData.type = OTPType[OTPType.totp]; + needMigrate = true; + } + + let type: OTPType; + switch (entryData.type) { + case 'totp': + case 'hotp': + case 'battle': + case 'steam': + case 'hex': + case 'hhex': + type = OTPType[entryData.type]; + break; + default: + // we need correct the type here + // and save it + type = OTPType.totp; entryData.type = OTPType[OTPType.totp]; needMigrate = true; - } - - let type: OTPType; - switch (entryData.type) { - case 'totp': - case 'hotp': - case 'battle': - case 'steam': - case 'hex': - case 'hhex': - type = OTPType[entryData.type]; - break; - default: - // we need correct the type here - // and save it - type = OTPType.totp; - entryData.type = OTPType[OTPType.totp]; - needMigrate = true; - } - - let period = 30; - if (entryData.type === OTPType[OTPType.totp] && - entryData.period && entryData.period > 0) { - period = entryData.period; - } - - entryData.secret = entryData.encrypted ? - encryption.getDecryptedSecret(entryData.secret, hash) : - entryData.secret; - - // we need migrate secret in old format here - if (/^(blz\-|bliz\-)/.test(entryData.secret)) { - const secretMatches = - entryData.secret.match(/^(blz\-|bliz\-)(.*)/); - if (secretMatches && secretMatches.length >= 3) { - entryData.secret = secretMatches[2]; - entryData.type = OTPType[OTPType.battle]; - entryData.hash = CryptoJS.MD5(entryData.secret).toString(); - await this.remove(hash); - needMigrate = true; - } - } + } - if (/^stm\-/.test(entryData.secret)) { - const secretMatches = entryData.secret.match(/^stm\-(.*)/); - if (secretMatches && secretMatches.length >= 2) { - entryData.secret = secretMatches[1]; - entryData.type = OTPType[OTPType.steam]; - entryData.hash = CryptoJS.MD5(entryData.secret).toString(); - await this.remove(hash); - needMigrate = true; - } - } + let period = 30; + if ( + entryData.type === OTPType[OTPType.totp] && + entryData.period && + entryData.period > 0 + ) { + period = entryData.period; + } - if (!/^[a-z2-7]+=*$/i.test(entryData.secret) && - /^[0-9a-f]+$/i.test(entryData.secret) && - entryData.type === OTPType[OTPType.totp]) { - entryData.type = OTPType[OTPType.hex]; + entryData.secret = entryData.encrypted + ? encryption.getDecryptedSecret(entryData.secret, hash) + : entryData.secret; + + // we need migrate secret in old format here + if (/^(blz\-|bliz\-)/.test(entryData.secret)) { + const secretMatches = entryData.secret.match( + /^(blz\-|bliz\-)(.*)/ + ); + if (secretMatches && secretMatches.length >= 3) { + entryData.secret = secretMatches[2]; + entryData.type = OTPType[OTPType.battle]; + entryData.hash = CryptoJS.MD5(entryData.secret).toString(); + await this.remove(hash); needMigrate = true; } + } - if (!/^[a-z2-7]+=*$/i.test(entryData.secret) && - /^[0-9a-f]+$/i.test(entryData.secret) && - entryData.type === OTPType[OTPType.hotp]) { - entryData.type = OTPType[OTPType.hhex]; + if (/^stm\-/.test(entryData.secret)) { + const secretMatches = entryData.secret.match(/^stm\-(.*)/); + if (secretMatches && secretMatches.length >= 2) { + entryData.secret = secretMatches[1]; + entryData.type = OTPType[OTPType.steam]; + entryData.hash = CryptoJS.MD5(entryData.secret).toString(); + await this.remove(hash); needMigrate = true; } + } - const entry = new OTPEntry( - type, entryData.issuer, entryData.secret, entryData.account, - entryData.index, entryData.counter, period, entryData.hash); - data.push(entry); - - // we need correct the hash - - // Do not correct hash, wrong password - // may not only 'Encrypted', but also - // other wrong secret. We cannot know - // if the hash doesn't match the correct - // secret + if ( + !/^[a-z2-7]+=*$/i.test(entryData.secret) && + /^[0-9a-f]+$/i.test(entryData.secret) && + entryData.type === OTPType[OTPType.totp] + ) { + entryData.type = OTPType[OTPType.hex]; + needMigrate = true; + } - // Only correct invalid hash here + if ( + !/^[a-z2-7]+=*$/i.test(entryData.secret) && + /^[0-9a-f]+$/i.test(entryData.secret) && + entryData.type === OTPType[OTPType.hotp] + ) { + entryData.type = OTPType[OTPType.hhex]; + needMigrate = true; + } - if (entry.secret !== 'Encrypted' && - !/^[0-9a-f]{32}$/.test(hash)) { - const _hash = CryptoJS.MD5(entryData.secret).toString(); - if (hash !== _hash) { - await this.remove(hash); - entryData.hash = _hash; - needMigrate = true; - } + const entry = new OTPEntry( + type, + entryData.issuer, + entryData.secret, + entryData.account, + entryData.index, + entryData.counter, + period, + entryData.hash + ); + data.push(entry); + + // we need correct the hash + + // Do not correct hash, wrong password + // may not only 'Encrypted', but also + // other wrong secret. We cannot know + // if the hash doesn't match the correct + // secret + + // Only correct invalid hash here + + if ( + entry.secret !== 'Encrypted' && + !/^[0-9a-f]{32}$/.test(hash) + ) { + const _hash = CryptoJS.MD5(entryData.secret).toString(); + if (hash !== _hash) { + await this.remove(hash); + entryData.hash = _hash; + needMigrate = true; } + } - if (needMigrate) { - const _entry: {[hash: string]: OTPStorage} = {}; - _entry[entryData.hash] = entryData; - _entry[entryData.hash].encrypted = false; - this.import(encryption, _entry); - } + if (needMigrate) { + const _entry: { [hash: string]: OTPStorage } = {}; + _entry[entryData.hash] = entryData; + _entry[entryData.hash].encrypted = false; + this.import(encryption, _entry); } + } - data.sort((a, b) => { - return a.index - b.index; - }); + data.sort((a, b) => { + return a.index - b.index; + }); - for (let i = 0; i < data.length; i++) { - if (data[i].index !== i) { - const exportData = await this.getExport(encryption); - await this.import(encryption, exportData); - break; - } + for (let i = 0; i < data.length; i++) { + if (data[i].index !== i) { + const exportData = await this.getExport(encryption); + await this.import(encryption, exportData); + break; } + } - return resolve(data); - }); - return; - } catch (error) { - return reject(error); - } - }); + return resolve(data); + }); + return; + } catch (error) { + return reject(error); + } + } + ); } static remove(hash: string) { return new Promise( - (resolve: () => void, reject: (reason: Error) => void) => { - return BrowserStorage.remove(hash, resolve); - }); + (resolve: () => void, reject: (reason: Error) => void) => { + return BrowserStorage.remove(hash, resolve); + } + ); } static delete(entry: OTPEntry) { return new Promise( - (resolve: () => void, reject: (reason: Error) => void) => { - try { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - if (_data.hasOwnProperty(entry.hash)) { - delete _data[entry.hash]; - } - _data = this.ensureUniqueIndex(_data); - BrowserStorage.remove(entry.hash, () => { - BrowserStorage.set(_data, resolve); - }); - return; + (resolve: () => void, reject: (reason: Error) => void) => { + try { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + if (_data.hasOwnProperty(entry.hash)) { + delete _data[entry.hash]; + } + _data = this.ensureUniqueIndex(_data); + BrowserStorage.remove(entry.hash, () => { + BrowserStorage.set(_data, resolve); }); return; - } catch (error) { - return reject(error); - } - }); + }); + return; + } catch (error) { + return reject(error); + } + } + ); } } export class ManagedStorage { static get(key: string) { - return new Promise((resolve: (result: boolean|string) => void) => { - chrome.storage.managed.get((data) => { + return new Promise((resolve: (result: boolean | string) => void) => { + chrome.storage.managed.get(data => { if (chrome.runtime.lastError) { resolve(false); } diff --git a/src/qrdebug.ts b/src/qrdebug.ts index e445e8334..3ebb8bed0 100644 --- a/src/qrdebug.ts +++ b/src/qrdebug.ts @@ -4,15 +4,25 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { return; } getQrDebug( - sender.tab, message.info.left, message.info.top, message.info.width, - message.info.height, message.info.windowWidth); + sender.tab, + message.info.left, + message.info.top, + message.info.width, + message.info.height, + message.info.windowWidth + ); } }); function getQrDebug( - tab: chrome.tabs.Tab, left: number, top: number, width: number, - height: number, windowWidth: number) { - chrome.tabs.captureVisibleTab(tab.windowId, {format: 'png'}, (dataUrl) => { + tab: chrome.tabs.Tab, + left: number, + top: number, + width: number, + height: number, + windowWidth: number +) { + chrome.tabs.captureVisibleTab(tab.windowId, { format: 'png' }, dataUrl => { const qr = new Image(); qr.src = dataUrl; qr.onload = () => { @@ -25,28 +35,37 @@ function getQrDebug( return; } ctx.drawImage( - qr, left * devicePixelRatio, top * devicePixelRatio, - width * devicePixelRatio, height * devicePixelRatio, 0, 0, - width * devicePixelRatio, height * devicePixelRatio); + qr, + left * devicePixelRatio, + top * devicePixelRatio, + width * devicePixelRatio, + height * devicePixelRatio, + 0, + 0, + width * devicePixelRatio, + height * devicePixelRatio + ); const url = captureCanvas.toDataURL(); const infoDom = document.getElementById('info'); if (infoDom) { - infoDom.innerHTML = 'Scan Data:
' + - `
` + - `Window Inner Width: ${windowWidth}
` + - `Width: ${width}
` + - `Height: ${height}
` + - `Left: ${left}
` + - `Top: ${top}
` + - `Screen Width: ${window.screen.width}
` + - `Screen Height: ${window.screen.height}
` + - `Capture Width: ${qr.width}
` + - `Capture Height: ${qr.height}
` + - `Device Pixel Ratio: ${devicePixelRatio} / ${ - window.devicePixelRatio}
` + - `Tab ID: ${tab.id}
` + - '
' + - 'Captured Screenshot:'; + infoDom.innerHTML = + 'Scan Data:
' + + `
` + + `Window Inner Width: ${windowWidth}
` + + `Width: ${width}
` + + `Height: ${height}
` + + `Left: ${left}
` + + `Top: ${top}
` + + `Screen Width: ${window.screen.width}
` + + `Screen Height: ${window.screen.height}
` + + `Capture Width: ${qr.width}
` + + `Capture Height: ${qr.height}
` + + `Device Pixel Ratio: ${devicePixelRatio} / ${ + window.devicePixelRatio + }
` + + `Tab ID: ${tab.id}
` + + '
' + + 'Captured Screenshot:'; } const qrDom = document.getElementById('qr') as HTMLImageElement; diff --git a/src/test/test.ts b/src/test/test.ts index 5741863a0..05e24a675 100644 --- a/src/test/test.ts +++ b/src/test/test.ts @@ -4,7 +4,7 @@ interface TestCase { /* tslint:disable-next-line:no-any */ [hash: string]: { /* tslint:disable-next-line:no-any */ - [key: string]: any + [key: string]: any; }; }; } @@ -12,50 +12,50 @@ interface TestCase { const cases: TestCase[] = [ { name: 'Missing fields', - data: {'7733be61632fa6af88d31218e6c4afb2': {'secret': 'abcd2345'}}, + data: { '7733be61632fa6af88d31218e6c4afb2': { secret: 'abcd2345' } }, }, { name: 'Bad hash in key', data: { - 'badhash': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '7733be61632fa6af88d31218e6c4afb2', - 'index': 0, - 'issuer': '', - 'secret': 'abcd2345', - 'type': 'totp', + badhash: { + account: 'test', + counter: 0, + encrypted: false, + hash: '7733be61632fa6af88d31218e6c4afb2', + index: 0, + issuer: '', + secret: 'abcd2345', + type: 'totp', }, }, }, { name: 'Bad hash', data: { - 'badhash': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': 'badhash', - 'index': 0, - 'issuer': '', - 'secret': 'abcd2345', - 'type': 'totp', + badhash: { + account: 'test', + counter: 0, + encrypted: false, + hash: 'badhash', + index: 0, + issuer: '', + secret: 'abcd2345', + type: 'totp', }, }, }, { name: 'Bad type for HEX', data: { - 'e19d5cd5af0378da05f63f891c7467af': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': 'e19d5cd5af0378da05f63f891c7467af', - 'index': 0, - 'issuer': '', - 'secret': 'abcd1234', - 'type': 'totp', + e19d5cd5af0378da05f63f891c7467af: { + account: 'test', + counter: 0, + encrypted: false, + hash: 'e19d5cd5af0378da05f63f891c7467af', + index: 0, + issuer: '', + secret: 'abcd1234', + type: 'totp', }, }, }, @@ -63,14 +63,14 @@ const cases: TestCase[] = [ name: 'Unicode in issuer', data: { '7733be61632fa6af88d31218e6c4afb2': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '7733be61632fa6af88d31218e6c4afb2', - 'index': 0, - 'issuer': '✓ à la mode', - 'secret': 'abcd2345', - 'type': 'totp', + account: 'test', + counter: 0, + encrypted: false, + hash: '7733be61632fa6af88d31218e6c4afb2', + index: 0, + issuer: '✓ à la mode', + secret: 'abcd2345', + type: 'totp', }, }, }, @@ -78,14 +78,14 @@ const cases: TestCase[] = [ name: 'Battle migrate', data: { '95c869de1221960c7f7e6892f78d7062': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '95c869de1221960c7f7e6892f78d7062', - 'index': 0, - 'issuer': '', - 'secret': 'blz-abcd2345', - 'type': 'totp', + account: 'test', + counter: 0, + encrypted: false, + hash: '95c869de1221960c7f7e6892f78d7062', + index: 0, + issuer: '', + secret: 'blz-abcd2345', + type: 'totp', }, }, }, @@ -93,73 +93,73 @@ const cases: TestCase[] = [ name: 'Steam migrate', data: { '95c869de1221960c7f7e6892f78d7062': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '95c869de1221960c7f7e6892f78d7062', - 'index': 0, - 'issuer': '', - 'secret': 'stm-abcd2345', - 'type': 'totp', + account: 'test', + counter: 0, + encrypted: false, + hash: '95c869de1221960c7f7e6892f78d7062', + index: 0, + issuer: '', + secret: 'stm-abcd2345', + type: 'totp', }, }, }, { name: 'Missing field with HEX secret', - data: {'e19d5cd5af0378da05f63f891c7467af': {'secret': 'abcd1234'}}, + data: { e19d5cd5af0378da05f63f891c7467af: { secret: 'abcd1234' } }, }, { name: 'Mess index', data: { '7733be61632fa6af88d31218e6c4afb2': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '7733be61632fa6af88d31218e6c4afb2', - 'index': 6, - 'issuer': '', - 'secret': 'abcd2345', - 'type': 'totp', + account: 'test', + counter: 0, + encrypted: false, + hash: '7733be61632fa6af88d31218e6c4afb2', + index: 6, + issuer: '', + secret: 'abcd2345', + type: 'totp', }, '770f51f23603ddae810e446630c2f673': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '770f51f23603ddae810e446630c2f673', - 'index': 6, - 'issuer': '', - 'secret': 'abcd2346', - 'type': 'totp', + account: 'test', + counter: 0, + encrypted: false, + hash: '770f51f23603ddae810e446630c2f673', + index: 6, + issuer: '', + secret: 'abcd2346', + type: 'totp', }, }, }, { name: 'Base32 with padding', data: { - 'b905232a977347a0a113a7d1c924fb8d': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': 'b905232a977347a0a113a7d1c924fb8d', - 'index': 0, - 'issuer': '', - 'secret': 'DKCE3SQPHJRJQGBGI322QA7Z5E======', - 'type': 'totp', + b905232a977347a0a113a7d1c924fb8d: { + account: 'test', + counter: 0, + encrypted: false, + hash: 'b905232a977347a0a113a7d1c924fb8d', + index: 0, + issuer: '', + secret: 'DKCE3SQPHJRJQGBGI322QA7Z5E======', + type: 'totp', }, }, }, { name: 'Incorrect but valid hash', data: { - 'ffffffffffffffffffffffffffffffff': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': 'ffffffffffffffffffffffffffffffff', - 'index': 0, - 'issuer': '', - 'secret': 'abcd2345', - 'type': 'totp', + ffffffffffffffffffffffffffffffff: { + account: 'test', + counter: 0, + encrypted: false, + hash: 'ffffffffffffffffffffffffffffffff', + index: 0, + issuer: '', + secret: 'abcd2345', + type: 'totp', }, }, }, @@ -167,14 +167,14 @@ const cases: TestCase[] = [ name: 'HOTP with HEX secret', data: { '7c117a118e015b6232ff359958b9e270': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '7c117a118e015b6232ff359958b9e270', - 'index': 0, - 'issuer': '', - 'secret': '2c52e8fcfac34091da63ef7b118f1cc50b925a42', - 'type': 'hhex', + account: 'test', + counter: 0, + encrypted: false, + hash: '7c117a118e015b6232ff359958b9e270', + index: 0, + issuer: '', + secret: '2c52e8fcfac34091da63ef7b118f1cc50b925a42', + type: 'hhex', }, }, }, @@ -182,14 +182,14 @@ const cases: TestCase[] = [ name: 'Amazon 2FA', data: { '0e00b601f60a4d7154d54ba94c429afb': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '0e00b601f60a4d7154d54ba94c429afb', - 'index': 0, - 'issuer': '', - 'secret': 'QLGNXJ2KLSOACXOEKJ47X6VA6ZPGT5HE2GBO5NPXTLD7FJAKD4JQ', - 'type': 'totp', + account: 'test', + counter: 0, + encrypted: false, + hash: '0e00b601f60a4d7154d54ba94c429afb', + index: 0, + issuer: '', + secret: 'QLGNXJ2KLSOACXOEKJ47X6VA6ZPGT5HE2GBO5NPXTLD7FJAKD4JQ', + type: 'totp', }, }, }, @@ -197,21 +197,21 @@ const cases: TestCase[] = [ name: 'Secret contains spaces', data: { '1b0c21ad1ec44264f665708ef82dae84': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '1b0c21ad1ec44264f665708ef82dae84', - 'index': 0, - 'issuer': '', - 'secret': 'p5s7 k2in z3mj oqfg', - 'type': 'totp', + account: 'test', + counter: 0, + encrypted: false, + hash: '1b0c21ad1ec44264f665708ef82dae84', + index: 0, + issuer: '', + secret: 'p5s7 k2in z3mj oqfg', + type: 'totp', }, }, }, ]; let testCaseIndex = 0; -let testRes: Array<{pass: boolean, error: string | Event}> = []; +let testRes: Array<{ pass: boolean; error: string | Event }> = []; let testResData: string[] = []; function testStart() { @@ -255,17 +255,20 @@ async function clear() { async function get() { return new Promise( - (resolve: (items: {[key: string]: T}) => void, - reject: (reason: Error) => void) => { - try { - chrome.storage.sync.get(resolve); - } catch (error) { - reject(error); - } - }); + ( + resolve: (items: { [key: string]: T }) => void, + reject: (reason: Error) => void + ) => { + try { + chrome.storage.sync.get(resolve); + } catch (error) { + reject(error); + } + } + ); } -async function set(items: {[key: string]: {}}) { +async function set(items: { [key: string]: {} }) { /* tslint:disable-next-line:no-any */ return new Promise((resolve: () => void, reject: (reason: Error) => void) => { try { @@ -283,8 +286,9 @@ async function test() { } console.log( - cases[Math.floor(testCaseIndex / 2)].name, - testCaseIndex % 2 ? 'Reopen' : ''); + cases[Math.floor(testCaseIndex / 2)].name, + testCaseIndex % 2 ? 'Reopen' : '' + ); if (testCaseIndex % 2 === 0) { clear(); @@ -293,7 +297,7 @@ async function test() { const iframe = document.getElementsByTagName('iframe')[0]; if (iframe) { - testRes[testCaseIndex] = {pass: true, error: ''}; + testRes[testCaseIndex] = { pass: true, error: '' }; iframe.src = 'popup.html'; iframe.onload = () => { @@ -302,11 +306,11 @@ async function test() { } iframe.contentWindow.addEventListener('unhandledrejection', event => { const rejectionEvent = event as PromiseRejectionEvent; - testRes[testCaseIndex] = {pass: false, error: rejectionEvent.reason}; + testRes[testCaseIndex] = { pass: false, error: rejectionEvent.reason }; }); iframe.contentWindow.onerror = error => { - testRes[testCaseIndex] = {pass: false, error}; + testRes[testCaseIndex] = { pass: false, error }; }; }; } @@ -314,20 +318,28 @@ async function test() { setTimeout(async () => { const data = await get<{ /* tslint:disable-next-line:no-any */ - [key: string]: any + [key: string]: any; }>(); testResData[testCaseIndex] = JSON.stringify(data, null, 2); if (testRes[testCaseIndex].pass) { - if (Object.keys(data).length !== - Object.keys(cases[Math.floor(testCaseIndex / 2)].data).length) { - testRes[testCaseIndex] = {pass: false, error: `Missing data`}; + if ( + Object.keys(data).length !== + Object.keys(cases[Math.floor(testCaseIndex / 2)].data).length + ) { + testRes[testCaseIndex] = { pass: false, error: `Missing data` }; } else { for (const hash of Object.keys(data)) { const item = data[hash]; const keys = [ - 'issuer', 'account', 'secret', 'hash', 'index', 'type', 'counter', + 'issuer', + 'account', + 'secret', + 'hash', + 'index', + 'type', + 'counter', 'encrypted', ]; for (const key of keys) { @@ -364,13 +376,13 @@ function showTestResult() { for (let i = 0; i < testRes.length; i++) { const el = document.createElement('tr'); el.innerHTML = `[${testRes[i].pass ? 'Pass' : 'Fail'}]`; - el.innerHTML += - `

${cases[Math.floor(i / 2)].name}${ - i % 2 === 1 ? ' (Reopen)' : - ''}

${testRes[i].error}
${
-            testResData[i]}

`; + testRes[i].pass ? 'green' : 'red' + }">[${testRes[i].pass ? 'Pass' : 'Fail'}]`; + el.innerHTML += `

${ + cases[Math.floor(i / 2)].name + }${i % 2 === 1 ? ' (Reopen)' : ''}

${testRes[i].error}
${
+      testResData[i]
+    }

`; testResultContainer.appendChild(el); } @@ -381,6 +393,10 @@ if (startBtn) { startBtn.onclick = testStart; } -window.addEventListener('message', (event) => { - testRes[testCaseIndex] = {pass: false, error: event.data}; -}, false); +window.addEventListener( + 'message', + event => { + testRes[testCaseIndex] = { pass: false, error: event.data }; + }, + false +); diff --git a/src/ui/add-account.ts b/src/ui/add-account.ts index 93c38b1d5..b76d0bc7f 100644 --- a/src/ui/add-account.ts +++ b/src/ui/add-account.ts @@ -1,11 +1,12 @@ -import {UI} from './ui'; import { OTPEntry } from '../models/otp'; +import { UI } from './ui'; + export async function insertContentScript() { return new Promise((resolve: () => void, reject: (reason: Error) => void) => { try { - return chrome.tabs.executeScript({file: '/dist/content.js'}, () => { - chrome.tabs.insertCSS({file: '/css/content.css'}, resolve); + return chrome.tabs.executeScript({ file: '/dist/content.js' }, () => { + chrome.tabs.insertCSS({ file: '/css/content.css' }, resolve); }); } catch (error) { return reject(error); @@ -16,38 +17,51 @@ export async function insertContentScript() { export async function addAccount(_ui: UI) { const ui: UIConfig = { data: { - newAccount: {show: false, account: '', secret: '', type: OTPType.totp}, - newPassphrase: {phrase: '', confirm: ''}, + newAccount: { show: false, account: '', secret: '', type: OTPType.totp }, + newPassphrase: { phrase: '', confirm: '' }, }, methods: { addNewAccount: async () => { - _ui.instance.newAccount.secret = - _ui.instance.newAccount.secret.replace(/ /g, ''); + _ui.instance.newAccount.secret = _ui.instance.newAccount.secret.replace( + / /g, + '' + ); - if (!/^[a-z2-7]+=*$/i.test(_ui.instance.newAccount.secret) && - !/^[0-9a-f]+$/i.test(_ui.instance.newAccount.secret)) { + if ( + !/^[a-z2-7]+=*$/i.test(_ui.instance.newAccount.secret) && + !/^[0-9a-f]+$/i.test(_ui.instance.newAccount.secret) + ) { _ui.instance.alert( - _ui.instance.i18n.errorsecret + _ui.instance.newAccount.secret); + _ui.instance.i18n.errorsecret + _ui.instance.newAccount.secret + ); return; } let type: OTPType; - if (!/^[a-z2-7]+=*$/i.test(_ui.instance.newAccount.secret) && - /^[0-9a-f]+$/i.test(_ui.instance.newAccount.secret) && - _ui.instance.newAccount.type === 'totp') { + if ( + !/^[a-z2-7]+=*$/i.test(_ui.instance.newAccount.secret) && + /^[0-9a-f]+$/i.test(_ui.instance.newAccount.secret) && + _ui.instance.newAccount.type === 'totp' + ) { type = OTPType.hex; } else if ( - !/^[a-z2-7]+=*$/i.test(_ui.instance.newAccount.secret) && - /^[0-9a-f]+$/i.test(_ui.instance.newAccount.secret) && - _ui.instance.newAccount.type === 'hotp') { + !/^[a-z2-7]+=*$/i.test(_ui.instance.newAccount.secret) && + /^[0-9a-f]+$/i.test(_ui.instance.newAccount.secret) && + _ui.instance.newAccount.type === 'hotp' + ) { type = OTPType.hhex; } else { type = _ui.instance.newAccount.type; } const entry = new OTPEntry( - type, '', _ui.instance.newAccount.secret, - _ui.instance.newAccount.account, 0, 0); + type, + '', + _ui.instance.newAccount.secret, + _ui.instance.newAccount.account, + 0, + 0 + ); await entry.create(_ui.instance.encryption); await _ui.instance.updateEntries(); _ui.instance.newAccount.type = OTPType.totp; @@ -81,12 +95,12 @@ export async function addAccount(_ui: UI) { } } - chrome.tabs.query({active: true, lastFocusedWindow: true}, (tabs) => { + chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { const tab = tabs[0]; if (!tab || !tab.id) { return; } - chrome.tabs.sendMessage(tab.id, {action: 'capture'}, (result) => { + chrome.tabs.sendMessage(tab.id, { action: 'capture' }, result => { if (result !== 'beginCapture') { _ui.instance.alert(_ui.instance.i18n.capture_failed); } else { diff --git a/src/ui/backup.ts b/src/ui/backup.ts index 604c6dd27..935e309c9 100644 --- a/src/ui/backup.ts +++ b/src/ui/backup.ts @@ -1,5 +1,5 @@ -import {Drive, Dropbox} from '../models/backup'; -import {UI} from './ui'; +import { Drive, Dropbox } from '../models/backup'; +import { UI } from './ui'; export async function backup(_ui: UI) { const ui: UIConfig = { @@ -18,7 +18,8 @@ export async function backup(_ui: UI) { _ui.instance.alert(_ui.instance.i18n.updateSuccess); } else if (localStorage.dropboxRevoked === 'true') { _ui.instance.alert( - chrome.i18n.getMessage('token_revoked', ['Dropbox'])); + chrome.i18n.getMessage('token_revoked', ['Dropbox']) + ); localStorage.removeItem('dropboxRevoked'); _ui.instance.dropboxToken = ''; } else { @@ -31,7 +32,8 @@ export async function backup(_ui: UI) { _ui.instance.alert(_ui.instance.i18n.updateSuccess); } else if (localStorage.driveRevoked === 'true') { _ui.instance.alert( - chrome.i18n.getMessage('token_revoked', ['Google Drive'])); + chrome.i18n.getMessage('token_revoked', ['Google Drive']) + ); localStorage.removeItem('driveRevoked'); _ui.instance.driveToken = ''; } else { @@ -53,7 +55,9 @@ export async function backup(_ui: UI) { const xhr = new XMLHttpRequest(); xhr.open('POST', 'https://api.dropboxapi.com/2/auth/token/revoke'); xhr.setRequestHeader( - 'Authorization', 'Bearer ' + localStorage.dropboxToken); + 'Authorization', + 'Bearer ' + localStorage.dropboxToken + ); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { resolve(true); @@ -67,16 +71,19 @@ export async function backup(_ui: UI) { await new Promise((resolve: (value: boolean) => void) => { const xhr = new XMLHttpRequest(); xhr.open( - 'POST', - 'https://accounts.google.com/o/oauth2/revoke?token=' + - localStorage.driveToken); + 'POST', + 'https://accounts.google.com/o/oauth2/revoke?token=' + + localStorage.driveToken + ); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (navigator.userAgent.indexOf('Chrome') !== -1) { chrome.identity.removeCachedAuthToken( - {token: localStorage.driveToken}, () => { - resolve(true); - }); + { token: localStorage.driveToken }, + () => { + resolve(true); + } + ); } else { resolve(true); } @@ -90,63 +97,67 @@ export async function backup(_ui: UI) { setTimeout(_ui.instance.closeInfo, 500); }, getBackupToken: (service: string) => { - chrome.runtime.sendMessage({action: service}); + chrome.runtime.sendMessage({ action: service }); }, runScheduledBackup: (clientTime: number) => { if (_ui.instance.dropboxToken) { chrome.permissions.contains( - {origins: ['https://*.dropboxapi.com/*']}, - async (hasPermission) => { - if (hasPermission) { - try { - const dropbox = new Dropbox(); - const res = await dropbox.upload(_ui.instance.encryption); - if (res) { - // we have uploaded backup to Dropbox - // no need to remind - localStorage.lastRemindingBackupTime = clientTime; - return; - } else if (localStorage.dropboxRevoked === 'true') { - _ui.instance.alert( - chrome.i18n.getMessage('token_revoked', ['Dropbox'])); - localStorage.removeItem('dropboxRevoked'); - } - } catch (error) { - // ignore + { origins: ['https://*.dropboxapi.com/*'] }, + async hasPermission => { + if (hasPermission) { + try { + const dropbox = new Dropbox(); + const res = await dropbox.upload(_ui.instance.encryption); + if (res) { + // we have uploaded backup to Dropbox + // no need to remind + localStorage.lastRemindingBackupTime = clientTime; + return; + } else if (localStorage.dropboxRevoked === 'true') { + _ui.instance.alert( + chrome.i18n.getMessage('token_revoked', ['Dropbox']) + ); + localStorage.removeItem('dropboxRevoked'); } + } catch (error) { + // ignore } - _ui.instance.alert(_ui.instance.i18n.remind_backup); - localStorage.lastRemindingBackupTime = clientTime; - }); + } + _ui.instance.alert(_ui.instance.i18n.remind_backup); + localStorage.lastRemindingBackupTime = clientTime; + } + ); } if (_ui.instance.driveToken) { chrome.permissions.contains( - { - origins: [ - 'https://www.googleapis.com/*', - 'https://accounts.google.com/o/oauth2/revoke', - ], - }, - async (hasPermission) => { - if (hasPermission) { - try { - const drive = new Drive(); - const res = await drive.upload(_ui.instance.encryption); - if (res) { - localStorage.lastRemindingBackupTime = clientTime; - return; - } else if (localStorage.driveRevoked === 'true') { - _ui.instance.alert(chrome.i18n.getMessage( - 'token_revoked', ['Google Drive'])); - localStorage.removeItem('driveRevoked'); - } - } catch (error) { - // ignore + { + origins: [ + 'https://www.googleapis.com/*', + 'https://accounts.google.com/o/oauth2/revoke', + ], + }, + async hasPermission => { + if (hasPermission) { + try { + const drive = new Drive(); + const res = await drive.upload(_ui.instance.encryption); + if (res) { + localStorage.lastRemindingBackupTime = clientTime; + return; + } else if (localStorage.driveRevoked === 'true') { + _ui.instance.alert( + chrome.i18n.getMessage('token_revoked', ['Google Drive']) + ); + localStorage.removeItem('driveRevoked'); } + } catch (error) { + // ignore } - _ui.instance.alert(_ui.instance.i18n.remind_backup); - localStorage.lastRemindingBackupTime = clientTime; - }); + } + _ui.instance.alert(_ui.instance.i18n.remind_backup); + localStorage.lastRemindingBackupTime = clientTime; + } + ); } if (!_ui.instance.driveToken && !_ui.instance.dropboxToken) { _ui.instance.alert(_ui.instance.i18n.remind_backup); diff --git a/src/ui/class.ts b/src/ui/class.ts index edb5045b4..add6718b5 100644 --- a/src/ui/class.ts +++ b/src/ui/class.ts @@ -1,5 +1,4 @@ - -import {UI} from './ui'; +import { UI } from './ui'; export async function className(_ui: UI) { const ui: UIConfig = { diff --git a/src/ui/entry.ts b/src/ui/entry.ts index 7adde2ee3..27b6e3e7d 100644 --- a/src/ui/entry.ts +++ b/src/ui/entry.ts @@ -1,11 +1,11 @@ import * as CryptoJS from 'crypto-js'; -import {Encryption} from '../models/encryption'; -import {OTPEntry} from '../models/otp'; -import {EntryStorage} from '../models/storage'; +import { Encryption } from '../models/encryption'; +import { OTPEntry } from '../models/otp'; +import { EntryStorage } from '../models/storage'; -import {insertContentScript} from './add-account'; -import {UI} from './ui'; +import { insertContentScript } from './add-account'; +import { UI } from './ui'; async function getEntries(encryption: Encryption) { const otpEntries: OTPEntry[] = await EntryStorage.get(encryption); @@ -27,9 +27,11 @@ async function updateCode(app: any) { // passphrase box should not be shown (no passphrase set) or // there are entiries shown and passphrase box isn't shown (the user has // already provided password) - if (!app.sectorStart && - (!app.shouldShowPassphrase || - app.entries.length > 0 && app.info !== 'passphrase')) { + if ( + !app.sectorStart && + (!app.shouldShowPassphrase || + (app.entries.length > 0 && app.info !== 'passphrase')) + ) { app.sectorStart = true; app.sectorOffset = -second; } @@ -56,12 +58,13 @@ async function updateCode(app: any) { } } -function getBackupFile(entryData: {[hash: string]: OTPStorage}) { +function getBackupFile(entryData: { [hash: string]: OTPStorage }) { let json = JSON.stringify(entryData, null, 2); // for windows notepad json = json.replace(/\n/g, '\r\n'); - const base64Data = - CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(json)); + const base64Data = CryptoJS.enc.Base64.stringify( + CryptoJS.enc.Utf8.parse(json) + ); return `data:application/octet-stream;base64,${base64Data}`; } @@ -69,15 +72,15 @@ function removeUnsafeData(data: string) { return encodeURIComponent(data.split('::')[0].replace(/:/g, '')); } -function getOneLineOtpBackupFile(entryData: {[hash: string]: OTPStorage}) { +function getOneLineOtpBackupFile(entryData: { [hash: string]: OTPStorage }) { const otpAuthLines: string[] = []; for (const hash of Object.keys(entryData)) { const otpStorage = entryData[hash]; otpStorage.issuer = removeUnsafeData(otpStorage.issuer); otpStorage.account = removeUnsafeData(otpStorage.account); - const label = otpStorage.issuer ? - (otpStorage.issuer + ':' + otpStorage.account) : - otpStorage.account; + const label = otpStorage.issuer + ? otpStorage.issuer + ':' + otpStorage.account + : otpStorage.account; let type = ''; if (otpStorage.type === 'totp' || otpStorage.type === 'hex') { type = 'totp'; @@ -87,86 +90,101 @@ function getOneLineOtpBackupFile(entryData: {[hash: string]: OTPStorage}) { continue; } - const otpAuthLine = 'otpauth://' + type + '/' + label + - '?secret=' + otpStorage.secret + - (otpStorage.issuer ? ('&issuer=' + otpStorage.issuer) : '') + - (type === 'hotp' ? ('&counter=' + otpStorage.counter) : '') + - (type === 'totp' && otpStorage.period ? - ('&period=' + otpStorage.period) : - ''); + const otpAuthLine = + 'otpauth://' + + type + + '/' + + label + + '?secret=' + + otpStorage.secret + + (otpStorage.issuer ? '&issuer=' + otpStorage.issuer : '') + + (type === 'hotp' ? '&counter=' + otpStorage.counter : '') + + (type === 'totp' && otpStorage.period + ? '&period=' + otpStorage.period + : ''); otpAuthLines.push(otpAuthLine); } const base64Data = CryptoJS.enc.Base64.stringify( - CryptoJS.enc.Utf8.parse(otpAuthLines.join('\r\n'))); + CryptoJS.enc.Utf8.parse(otpAuthLines.join('\r\n')) + ); return `data:application/octet-stream;base64,${base64Data}`; } export async function getSiteName() { return new Promise( - (resolve: (value: Array) => void, - reject: (reason: Error) => void) => { - chrome.tabs.query({active: true, lastFocusedWindow: true}, (tabs) => { - const tab = tabs[0]; - if (!tab) { - return resolve([null, null]); - } + ( + resolve: (value: Array) => void, + reject: (reason: Error) => void + ) => { + chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { + const tab = tabs[0]; + if (!tab) { + return resolve([null, null]); + } - const title = tab.title ? - tab.title.replace(/[^a-z0-9]/ig, '').toLowerCase() : - null; + const title = tab.title + ? tab.title.replace(/[^a-z0-9]/gi, '').toLowerCase() + : null; - if (!tab.url) { - return resolve([title, null]); - } + if (!tab.url) { + return resolve([title, null]); + } - const urlParser = document.createElement('a'); - urlParser.href = tab.url; - const hostname = urlParser.hostname.toLowerCase(); + const urlParser = document.createElement('a'); + urlParser.href = tab.url; + const hostname = urlParser.hostname.toLowerCase(); - // try to parse name from hostname - // i.e. hostname is www.example.com - // name should be example - let nameFromDomain = ''; + // try to parse name from hostname + // i.e. hostname is www.example.com + // name should be example + let nameFromDomain = ''; - // ip address - if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) { - nameFromDomain = hostname; - } + // ip address + if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) { + nameFromDomain = hostname; + } - // local network - if (hostname.indexOf('.') === -1) { - nameFromDomain = hostname; - } + // local network + if (hostname.indexOf('.') === -1) { + nameFromDomain = hostname; + } - const hostLevelUnits = hostname.split('.'); + const hostLevelUnits = hostname.split('.'); - if (hostLevelUnits.length === 2) { - nameFromDomain = hostLevelUnits[0]; - } + if (hostLevelUnits.length === 2) { + nameFromDomain = hostLevelUnits[0]; + } - // www.example.com + // www.example.com + // example.com.cn + if (hostLevelUnits.length > 2) { // example.com.cn - if (hostLevelUnits.length > 2) { - // example.com.cn - if (['com', 'net', 'org', 'edu', 'gov', 'co'].indexOf( - hostLevelUnits[hostLevelUnits.length - 2]) !== -1) { - nameFromDomain = hostLevelUnits[hostLevelUnits.length - 3]; - } else { // www.example.com - nameFromDomain = hostLevelUnits[hostLevelUnits.length - 2]; - } + if ( + ['com', 'net', 'org', 'edu', 'gov', 'co'].indexOf( + hostLevelUnits[hostLevelUnits.length - 2] + ) !== -1 + ) { + nameFromDomain = hostLevelUnits[hostLevelUnits.length - 3]; + } else { + // www.example.com + nameFromDomain = hostLevelUnits[hostLevelUnits.length - 2]; } + } - nameFromDomain = nameFromDomain.replace(/-/g, '').toLowerCase(); + nameFromDomain = nameFromDomain.replace(/-/g, '').toLowerCase(); - return resolve([title, nameFromDomain, hostname]); - }); + return resolve([title, nameFromDomain, hostname]); }); + } + ); } export function hasMatchedEntry( - siteName: Array, entries: OTPEntry[]) { + siteName: Array, + entries: OTPEntry[] +) { if (siteName.length < 2) { return false; } @@ -179,13 +197,13 @@ export function hasMatchedEntry( return false; } -function isMatchedEntry(siteName: Array, entry: OTPEntry) { +function isMatchedEntry(siteName: Array, entry: OTPEntry) { if (!entry.issuer) { return false; } const issuerHostMatches = entry.issuer.split('::'); - const issuer = issuerHostMatches[0].replace(/[^0-9a-z]/ig, '').toLowerCase(); + const issuer = issuerHostMatches[0].replace(/[^0-9a-z]/gi, '').toLowerCase(); if (!issuer) { return false; @@ -215,30 +233,35 @@ function isMatchedEntry(siteName: Array, entry: OTPEntry) { async function getCachedPassphrase() { return new Promise( - (resolve: (value: string) => void, reject: (reason: Error) => void) => { - const cookie = document.cookie; - const cookieMatch = - cookie ? document.cookie.match(/passphrase=([^;]*)/) : null; - const cachedPassphrase = - cookieMatch && cookieMatch.length > 1 ? cookieMatch[1] : null; - const cachedPassphraseLocalStorage = localStorage.encodedPhrase ? - CryptoJS.AES.decrypt(localStorage.encodedPhrase, '') - .toString(CryptoJS.enc.Utf8) : - ''; - if (cachedPassphrase || cachedPassphraseLocalStorage) { - return resolve(cachedPassphrase || cachedPassphraseLocalStorage); - } + (resolve: (value: string) => void, reject: (reason: Error) => void) => { + const cookie = document.cookie; + const cookieMatch = cookie + ? document.cookie.match(/passphrase=([^;]*)/) + : null; + const cachedPassphrase = + cookieMatch && cookieMatch.length > 1 ? cookieMatch[1] : null; + const cachedPassphraseLocalStorage = localStorage.encodedPhrase + ? CryptoJS.AES.decrypt(localStorage.encodedPhrase, '').toString( + CryptoJS.enc.Utf8 + ) + : ''; + if (cachedPassphrase || cachedPassphraseLocalStorage) { + return resolve(cachedPassphrase || cachedPassphraseLocalStorage); + } - chrome.runtime.sendMessage( - {action: 'passphrase'}, (passphrase: string) => { - return resolve(passphrase); - }); - }); + chrome.runtime.sendMessage( + { action: 'passphrase' }, + (passphrase: string) => { + return resolve(passphrase); + } + ); + } + ); } function getEntryDataFromOTPAuthPerLine(importCode: string) { const lines = importCode.split('\n'); - const exportData: {[hash: string]: OTPStorage} = {}; + const exportData: { [hash: string]: OTPStorage } = {}; for (let item of lines) { item = item.trim(); if (!item.startsWith('otpauth:')) { @@ -256,7 +279,7 @@ function getEntryDataFromOTPAuthPerLine(importCode: string) { let account = ''; let secret = ''; let issuer = ''; - let period: number|undefined = undefined; + let period: number | undefined = undefined; try { label = decodeURIComponent(label); @@ -270,7 +293,7 @@ function getEntryDataFromOTPAuthPerLine(importCode: string) { account = label; } const parameters = parameterPart.split('&'); - parameters.forEach((item) => { + parameters.forEach(item => { const parameter = item.split('='); if (parameter[0].toLowerCase() === 'secret') { secret = parameter[1]; @@ -282,29 +305,36 @@ function getEntryDataFromOTPAuthPerLine(importCode: string) { } } else if (parameter[0].toLowerCase() === 'counter') { let counter = Number(parameter[1]); - counter = (isNaN(counter) || counter < 0) ? 0 : counter; + counter = isNaN(counter) || counter < 0 ? 0 : counter; } else if (parameter[0].toLowerCase() === 'period') { period = Number(parameter[1]); - period = (isNaN(period) || period < 0 || period > 60 || - 60 % period !== 0) ? - undefined : - period; + period = + isNaN(period) || period < 0 || period > 60 || 60 % period !== 0 + ? undefined + : period; } }); if (!secret) { continue; } else if ( - !/^[0-9a-f]+$/i.test(secret) && !/^[2-7a-z]+=*$/i.test(secret)) { + !/^[0-9a-f]+$/i.test(secret) && + !/^[2-7a-z]+=*$/i.test(secret) + ) { continue; } else { const hash = CryptoJS.MD5(secret).toString(); - if (!/^[2-7a-z]+=*$/i.test(secret) && /^[0-9a-f]+$/i.test(secret) && - type === 'totp') { + if ( + !/^[2-7a-z]+=*$/i.test(secret) && + /^[0-9a-f]+$/i.test(secret) && + type === 'totp' + ) { type = 'hex'; } else if ( - !/^[2-7a-z]+=*$/i.test(secret) && /^[0-9a-f]+$/i.test(secret) && - type === 'hotp') { + !/^[2-7a-z]+=*$/i.test(secret) && + /^[0-9a-f]+$/i.test(secret) && + type === 'hotp' + ) { type = 'hhex'; } @@ -330,13 +360,15 @@ function getEntryDataFromOTPAuthPerLine(importCode: string) { export async function entry(_ui: UI) { const cachedPassphrase = await getCachedPassphrase(); const encryption: Encryption = new Encryption(cachedPassphrase); - let shouldShowPassphrase = - cachedPassphrase ? false : await EntryStorage.hasEncryptedEntry(); - const exportData = - shouldShowPassphrase ? {} : await EntryStorage.getExport(encryption); - const exportEncData = shouldShowPassphrase ? - {} : - await EntryStorage.getExport(encryption, true); + let shouldShowPassphrase = cachedPassphrase + ? false + : await EntryStorage.hasEncryptedEntry(); + const exportData = shouldShowPassphrase + ? {} + : await EntryStorage.getExport(encryption); + const exportEncData = shouldShowPassphrase + ? {} + : await EntryStorage.getExport(encryption, true); const entries = shouldShowPassphrase ? [] : await getEntries(encryption); for (let i = 0; i < entries.length; i++) { @@ -399,7 +431,7 @@ export async function entry(_ui: UI) { isMatchedEntry: (entry: OTPEntry) => { return isMatchedEntry(siteName, entry); }, - searchListener: (e) => { + searchListener: e => { if (e.keyCode === 191) { if (_ui.instance.info !== '') { return; @@ -433,10 +465,14 @@ export async function entry(_ui: UI) { return true; } - if (entry.issuer.toLowerCase().includes( - _ui.instance.searchText.toLowerCase()) || - entry.account.toLowerCase().includes( - _ui.instance.searchText.toLowerCase())) { + if ( + entry.issuer + .toLowerCase() + .includes(_ui.instance.searchText.toLowerCase()) || + entry.account + .toLowerCase() + .includes(_ui.instance.searchText.toLowerCase()) + ) { return true; } else { return false; @@ -445,59 +481,63 @@ export async function entry(_ui: UI) { updateCode: async () => { return updateCode(_ui.instance); }, - decryptBackupData: - (backupData: {[hash: string]: OTPStorage}, - passphrase: string|null) => { - const decryptedbackupData: {[hash: string]: OTPStorage} = {}; - for (const hash of Object.keys(backupData)) { - if (typeof backupData[hash] !== 'object') { - continue; - } - if (!backupData[hash].secret) { - continue; - } - if (backupData[hash].encrypted && !passphrase) { - continue; - } - if (backupData[hash].encrypted && passphrase) { - try { - backupData[hash].secret = - CryptoJS.AES.decrypt(backupData[hash].secret, passphrase) - .toString(CryptoJS.enc.Utf8); - backupData[hash].encrypted = false; - } catch (error) { - continue; - } - } - // backupData[hash].secret may be empty after decrypt with wrong - // passphrase - if (!backupData[hash].secret) { - continue; - } - decryptedbackupData[hash] = backupData[hash]; + decryptBackupData: ( + backupData: { [hash: string]: OTPStorage }, + passphrase: string | null + ) => { + const decryptedbackupData: { [hash: string]: OTPStorage } = {}; + for (const hash of Object.keys(backupData)) { + if (typeof backupData[hash] !== 'object') { + continue; + } + if (!backupData[hash].secret) { + continue; + } + if (backupData[hash].encrypted && !passphrase) { + continue; + } + if (backupData[hash].encrypted && passphrase) { + try { + backupData[hash].secret = CryptoJS.AES.decrypt( + backupData[hash].secret, + passphrase + ).toString(CryptoJS.enc.Utf8); + backupData[hash].encrypted = false; + } catch (error) { + continue; } - return decryptedbackupData; - }, + } + // backupData[hash].secret may be empty after decrypt with wrong + // passphrase + if (!backupData[hash].secret) { + continue; + } + decryptedbackupData[hash] = backupData[hash]; + } + return decryptedbackupData; + }, importBackupCode: async () => { - let exportData: {[hash: string]: OTPStorage} = {}; + let exportData: { [hash: string]: OTPStorage } = {}; try { exportData = JSON.parse(_ui.instance.importCode); - } catch (error) { // Maybe one-otpauth-per line text exportData = getEntryDataFromOTPAuthPerLine(_ui.instance.importCode); } try { - const passphrase: string|null = - _ui.instance.importEncrypted && _ui.instance.importPassphrase ? - _ui.instance.importPassphrase : - null; - const decryptedbackupData: {[hash: string]: OTPStorage} = - _ui.instance.decryptBackupData(exportData, passphrase); + const passphrase: string | null = + _ui.instance.importEncrypted && _ui.instance.importPassphrase + ? _ui.instance.importPassphrase + : null; + const decryptedbackupData: { + [hash: string]: OTPStorage; + } = _ui.instance.decryptBackupData(exportData, passphrase); if (Object.keys(decryptedbackupData).length) { await EntryStorage.import( - _ui.instance.encryption, decryptedbackupData); + _ui.instance.encryption, + decryptedbackupData + ); await _ui.instance.updateEntries(); alert(_ui.instance.i18n.updateSuccess); window.close(); @@ -510,8 +550,11 @@ export async function entry(_ui: UI) { } }, noCopy: (code: string) => { - return code === 'Encrypted' || code === 'Invalid' || - code.startsWith('•'); + return ( + code === 'Encrypted' || + code === 'Invalid' || + code.startsWith('•') + ); }, updateStorage: async () => { await EntryStorage.set(_ui.instance.encryption, _ui.instance.entries); @@ -525,22 +568,28 @@ export async function entry(_ui: UI) { }, importEntries: async () => { await EntryStorage.import( - _ui.instance.encryption, JSON.parse(_ui.instance.exportData)); + _ui.instance.encryption, + JSON.parse(_ui.instance.exportData) + ); await _ui.instance.updateEntries(); _ui.instance.alert(_ui.instance.i18n.updateSuccess); return; }, updateEntries: async () => { - const exportData = - await EntryStorage.getExport(_ui.instance.encryption); - const exportEncData = - await EntryStorage.getExport(_ui.instance.encryption, true); + const exportData = await EntryStorage.getExport( + _ui.instance.encryption + ); + const exportEncData = await EntryStorage.getExport( + _ui.instance.encryption, + true + ); _ui.instance.exportData = JSON.stringify(exportData, null, 2); _ui.instance.entries = await getEntries(_ui.instance.encryption); _ui.instance.exportFile = getBackupFile(exportData); _ui.instance.exportEncryptedFile = getBackupFile(exportEncData); - _ui.instance.exportOneLineOtpAuthFile = - getOneLineOtpBackupFile(exportData); + _ui.instance.exportOneLineOtpAuthFile = getOneLineOtpBackupFile( + exportData + ); await _ui.instance.updateCode(); return; }, @@ -566,14 +615,15 @@ export async function entry(_ui: UI) { } if (target.files[0]) { const reader = new FileReader(); - let decryptedFileData: {[hash: string]: OTPStorage} = {}; + let decryptedFileData: { [hash: string]: OTPStorage } = {}; reader.onload = async () => { - let importData: {[hash: string]: OTPStorage} = {}; + let importData: { [hash: string]: OTPStorage } = {}; try { importData = JSON.parse(reader.result as string); } catch (e) { - importData = - getEntryDataFromOTPAuthPerLine(reader.result as string); + importData = getEntryDataFromOTPAuthPerLine( + reader.result as string + ); } let encrypted = false; @@ -581,10 +631,13 @@ export async function entry(_ui: UI) { if (importData[hash].encrypted) { encrypted = true; try { - const oldPassphrase: string|null = - await _ui.instance.getOldPassphrase(); - decryptedFileData = - _ui.instance.decryptBackupData(importData, oldPassphrase); + const oldPassphrase: + | string + | null = await _ui.instance.getOldPassphrase(); + decryptedFileData = _ui.instance.decryptBackupData( + importData, + oldPassphrase + ); break; } catch { break; @@ -596,7 +649,9 @@ export async function entry(_ui: UI) { } if (Object.keys(decryptedFileData).length) { await EntryStorage.import( - _ui.instance.encryption, decryptedFileData); + _ui.instance.encryption, + decryptedFileData + ); await _ui.instance.updateEntries(); alert(_ui.instance.i18n.updateSuccess); if (closeWindow) { @@ -637,8 +692,9 @@ export async function entry(_ui: UI) { if (codes) { // wait vue apply changes to dom setTimeout(() => { - codes.scrollTop = - _ui.instance.currentClass.edit ? codes.scrollHeight : 0; + codes.scrollTop = _ui.instance.currentClass.edit + ? codes.scrollHeight + : 0; }, 0); } return; @@ -655,8 +711,11 @@ export async function entry(_ui: UI) { return; }, copyCode: async (entry: OTPEntry) => { - if (_ui.instance.currentClass.edit || entry.code === 'Invalid' || - entry.code.startsWith('•')) { + if ( + _ui.instance.currentClass.edit || + entry.code === 'Invalid' || + entry.code.startsWith('•') + ) { return; } @@ -666,8 +725,9 @@ export async function entry(_ui: UI) { } if (navigator.userAgent.indexOf('Edge') !== -1) { - const codeClipboard = - document.getElementById('codeClipboard') as HTMLInputElement; + const codeClipboard = document.getElementById( + 'codeClipboard' + ) as HTMLInputElement; if (!codeClipboard) { return; } @@ -676,15 +736,19 @@ export async function entry(_ui: UI) { await insertContentScript(); chrome.tabs.query( - {active: true, lastFocusedWindow: true}, (tabs) => { - const tab = tabs[0]; - if (!tab || !tab.id) { - return; - } - - chrome.tabs.sendMessage( - tab.id, {action: 'pastecode', code: entry.code}); + { active: true, lastFocusedWindow: true }, + tabs => { + const tab = tabs[0]; + if (!tab || !tab.id) { + return; + } + + chrome.tabs.sendMessage(tab.id, { + action: 'pastecode', + code: entry.code, }); + } + ); } codeClipboard.value = entry.code; @@ -704,47 +768,53 @@ export async function entry(_ui: UI) { }, 1000); } else { chrome.permissions.request( - {permissions: ['clipboardWrite']}, async (granted) => { - if (granted) { - const codeClipboard = - document.getElementById('codeClipboard') as - HTMLInputElement; - if (!codeClipboard) { - return; - } - - if (_ui.instance.useAutofill) { - await insertContentScript(); - - chrome.tabs.query( - {active: true, lastFocusedWindow: true}, (tabs) => { - const tab = tabs[0]; - if (!tab || !tab.id) { - return; - } - - chrome.tabs.sendMessage( - tab.id, {action: 'pastecode', code: entry.code}); - }); - } - - codeClipboard.value = entry.code; - codeClipboard.focus(); - codeClipboard.select(); - document.execCommand('Copy'); - _ui.instance.notification = _ui.instance.i18n.copied; - clearTimeout(_ui.instance.notificationTimeout); - _ui.instance.currentClass.notificationFadein = true; - _ui.instance.currentClass.notificationFadeout = false; - _ui.instance.notificationTimeout = setTimeout(() => { - _ui.instance.currentClass.notificationFadein = false; - _ui.instance.currentClass.notificationFadeout = true; - setTimeout(() => { - _ui.instance.currentClass.notificationFadeout = false; - }, 200); - }, 1000); + { permissions: ['clipboardWrite'] }, + async granted => { + if (granted) { + const codeClipboard = document.getElementById( + 'codeClipboard' + ) as HTMLInputElement; + if (!codeClipboard) { + return; } - }); + + if (_ui.instance.useAutofill) { + await insertContentScript(); + + chrome.tabs.query( + { active: true, lastFocusedWindow: true }, + tabs => { + const tab = tabs[0]; + if (!tab || !tab.id) { + return; + } + + chrome.tabs.sendMessage(tab.id, { + action: 'pastecode', + code: entry.code, + }); + } + ); + } + + codeClipboard.value = entry.code; + codeClipboard.focus(); + codeClipboard.select(); + document.execCommand('Copy'); + _ui.instance.notification = _ui.instance.i18n.copied; + clearTimeout(_ui.instance.notificationTimeout); + _ui.instance.currentClass.notificationFadein = true; + _ui.instance.currentClass.notificationFadeout = false; + _ui.instance.notificationTimeout = setTimeout(() => { + _ui.instance.currentClass.notificationFadein = false; + _ui.instance.currentClass.notificationFadeout = true; + setTimeout(() => { + _ui.instance.currentClass.notificationFadeout = false; + }, 200); + }, 1000); + } + } + ); } return; }, diff --git a/src/ui/i18n.ts b/src/ui/i18n.ts index 587720ea1..3d66e84c1 100644 --- a/src/ui/i18n.ts +++ b/src/ui/i18n.ts @@ -1,37 +1,38 @@ - -import {UI} from './ui'; +import { UI } from './ui'; export async function loadI18nMessages() { return new Promise( - (resolve: (value: {[key: string]: string}) => void, - reject: (reason: Error) => void) => { - try { - const xhr = new XMLHttpRequest(); - xhr.overrideMimeType('application/json'); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - const i18nMessage: I18nMessage = JSON.parse(xhr.responseText); - const i18nData: {[key: string]: string} = {}; - for (const key of Object.keys(i18nMessage)) { - i18nData[key] = chrome.i18n.getMessage(key); - } - return resolve(i18nData); + ( + resolve: (value: { [key: string]: string }) => void, + reject: (reason: Error) => void + ) => { + try { + const xhr = new XMLHttpRequest(); + xhr.overrideMimeType('application/json'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + const i18nMessage: I18nMessage = JSON.parse(xhr.responseText); + const i18nData: { [key: string]: string } = {}; + for (const key of Object.keys(i18nMessage)) { + i18nData[key] = chrome.i18n.getMessage(key); } - return; - }; - xhr.open( - 'GET', chrome.extension.getURL('/_locales/en/messages.json')); - xhr.send(); - } catch (error) { - return reject(error); - } - }); + return resolve(i18nData); + } + return; + }; + xhr.open('GET', chrome.extension.getURL('/_locales/en/messages.json')); + xhr.send(); + } catch (error) { + return reject(error); + } + } + ); } export async function i18n(_ui: UI) { const i18n = await loadI18nMessages(); - const ui: UIConfig = {data: {i18n}}; + const ui: UIConfig = { data: { i18n } }; _ui.update(ui); } diff --git a/src/ui/info.ts b/src/ui/info.ts index 118d4783e..b1799c82c 100644 --- a/src/ui/info.ts +++ b/src/ui/info.ts @@ -1,10 +1,10 @@ -import {OTPEntry} from '../models/otp'; +import { OTPEntry } from '../models/otp'; -import {UI} from './ui'; +import { UI } from './ui'; export async function info(_ui: UI) { const ui: UIConfig = { - data: {info: ''}, + data: { info: '' }, methods: { showInfo: (tab: string) => { if (tab === 'export' || tab === 'security') { @@ -20,47 +20,56 @@ export async function info(_ui: UI) { } } } else if (tab === 'dropbox') { - if (localStorage.dropboxEncrypted !== 'true' && - localStorage.dropboxEncrypted !== 'false') { + if ( + localStorage.dropboxEncrypted !== 'true' && + localStorage.dropboxEncrypted !== 'false' + ) { localStorage.dropboxEncrypted = 'true'; _ui.instance.dropboxEncrypted = localStorage.dropboxEncrypted; } chrome.permissions.request( - {origins: ['https://*.dropboxapi.com/*']}, async (granted) => { - if (granted) { - _ui.instance.currentClass.fadein = true; - _ui.instance.currentClass.fadeout = false; - _ui.instance.info = tab; - } - return; - }); + { origins: ['https://*.dropboxapi.com/*'] }, + async granted => { + if (granted) { + _ui.instance.currentClass.fadein = true; + _ui.instance.currentClass.fadeout = false; + _ui.instance.info = tab; + } + return; + } + ); return; } else if (tab === 'drive') { - if (localStorage.driveEncrypted !== 'true' && - localStorage.driveEncrypted !== 'false') { + if ( + localStorage.driveEncrypted !== 'true' && + localStorage.driveEncrypted !== 'false' + ) { localStorage.driveEncrypted = 'true'; _ui.instance.driveEncrypted = localStorage.driveEncrypted; } chrome.permissions.request( - { - origins: [ - 'https://www.googleapis.com/*', - 'https://accounts.google.com/o/oauth2/revoke', - ], - }, - async (granted) => { - if (granted) { - _ui.instance.currentClass.fadein = true; - _ui.instance.currentClass.fadeout = false; - _ui.instance.info = tab; - } - return; - }); + { + origins: [ + 'https://www.googleapis.com/*', + 'https://accounts.google.com/o/oauth2/revoke', + ], + }, + async granted => { + if (granted) { + _ui.instance.currentClass.fadein = true; + _ui.instance.currentClass.fadeout = false; + _ui.instance.info = tab; + } + return; + } + ); return; } else if (tab === 'storage') { - if (_ui.instance.newStorageLocation !== 'sync' && - _ui.instance.newStorageLocation !== 'local') { + if ( + _ui.instance.newStorageLocation !== 'sync' && + _ui.instance.newStorageLocation !== 'local' + ) { _ui.instance.newStorageLocation = localStorage.storageLocation; } } diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 12cc1e1a6..52904e82c 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -1,7 +1,6 @@ +import { ManagedStorage } from '../models/storage'; -import {ManagedStorage} from '../models/storage'; - -import {UI} from './ui'; +import { UI } from './ui'; function getVersion() { return chrome.runtime.getManifest().version; @@ -9,47 +8,51 @@ function getVersion() { export async function syncTimeWithGoogle() { return new Promise( - (resolve: (value: string) => void, reject: (reason: Error) => void) => { - try { - // @ts-ignore - const xhr = new XMLHttpRequest({'mozAnon': true}); - xhr.open('HEAD', 'https://www.google.com/generate_204'); - const xhrAbort = setTimeout(() => { - xhr.abort(); - return resolve('updateFailure'); - }, 5000); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - clearTimeout(xhrAbort); - const date = xhr.getResponseHeader('date'); - if (!date) { - return resolve('updateFailure'); - } - const serverTime = new Date(date).getTime(); - const clientTime = new Date().getTime(); - const offset = Math.round((serverTime - clientTime) / 1000); + (resolve: (value: string) => void, reject: (reason: Error) => void) => { + try { + // tslint:disable-next-line:ban-ts-ignore + // @ts-ignore + const xhr = new XMLHttpRequest({ mozAnon: true }); + xhr.open('HEAD', 'https://www.google.com/generate_204'); + const xhrAbort = setTimeout(() => { + xhr.abort(); + return resolve('updateFailure'); + }, 5000); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + clearTimeout(xhrAbort); + const date = xhr.getResponseHeader('date'); + if (!date) { + return resolve('updateFailure'); + } + const serverTime = new Date(date).getTime(); + const clientTime = new Date().getTime(); + const offset = Math.round((serverTime - clientTime) / 1000); - if (Math.abs(offset) <= 300) { // within 5 minutes - localStorage.offset = - Math.round((serverTime - clientTime) / 1000); - return resolve('updateSuccess'); - } else { - return resolve('clock_too_far_off'); - } + if (Math.abs(offset) <= 300) { + // within 5 minutes + localStorage.offset = Math.round( + (serverTime - clientTime) / 1000 + ); + return resolve('updateSuccess'); + } else { + return resolve('clock_too_far_off'); } - }; - xhr.send(); - } catch (error) { - return reject(error); - } - }); + } + }; + xhr.send(); + } catch (error) { + return reject(error); + } + } + ); } function resize(zoom: number) { if (zoom !== 100) { document.body.style.marginBottom = 480 * (zoom / 100 - 1) + 'px'; document.body.style.marginRight = 320 * (zoom / 100 - 1) + 'px'; - document.body.style.transform = 'scale(' + (zoom / 100) + ')'; + document.body.style.transform = 'scale(' + zoom / 100 + ')'; } } @@ -67,17 +70,17 @@ async function openHelp() { url = feedbackURL; } - chrome.tabs.create({url}); + chrome.tabs.create({ url }); } -let backupDisabled: boolean|string; -let storageArea: boolean|string; +let backupDisabled: boolean | string; +let storageArea: boolean | string; -ManagedStorage.get('disableBackup').then((value) => { +ManagedStorage.get('disableBackup').then(value => { backupDisabled = value; }); -ManagedStorage.get('storageArea').then((value) => { +ManagedStorage.get('storageArea').then(value => { storageArea = value; }); @@ -85,8 +88,8 @@ export async function menu(_ui: UI) { const version = getVersion(); const zoom = Number(localStorage.zoom) || 100; resize(zoom); - let useAutofill = (localStorage.autofill === 'true'); - let useHighContrast = (localStorage.highContrast === 'true'); + let useAutofill = localStorage.autofill === 'true'; + let useHighContrast = localStorage.highContrast === 'true'; const ui: UIConfig = { data: { @@ -104,7 +107,7 @@ export async function menu(_ui: UI) { return; }, createWindow: (url: string) => { - chrome.windows.create({type: 'normal', url}); + chrome.windows.create({ type: 'normal', url }); return; }, showMenu: () => { @@ -147,18 +150,18 @@ export async function menu(_ui: UI) { }, showEdgeBugWarning: () => { _ui.instance.alert( - 'Due to a bug in Edge, downloading backups is not supported at this time. More info on feedback page.'); + 'Due to a bug in Edge, downloading backups is not supported at this time. More info on feedback page.' + ); }, saveAutofill: () => { localStorage.autofill = _ui.instance.useAutofill; - useAutofill = - (localStorage.autofill === 'true') ? true : false || false; + useAutofill = localStorage.autofill === 'true' ? true : false || false; return; }, saveHighContrast: () => { localStorage.highContrast = _ui.instance.useHighContrast; useHighContrast = - (localStorage.highContrast === 'true') ? true : false || false; + localStorage.highContrast === 'true' ? true : false || false; return; }, saveZoom: () => { @@ -172,13 +175,15 @@ export async function menu(_ui: UI) { _ui.instance.alert(_ui.instance.i18n[message]); } else { chrome.permissions.request( - {origins: ['https://www.google.com/']}, async (granted) => { - if (granted) { - const message = await syncTimeWithGoogle(); - _ui.instance.alert(_ui.instance.i18n[message]); - } - return; - }); + { origins: ['https://www.google.com/'] }, + async granted => { + if (granted) { + const message = await syncTimeWithGoogle(); + _ui.instance.alert(_ui.instance.i18n[message]); + } + return; + } + ); } return; }, @@ -199,38 +204,46 @@ export async function menu(_ui: UI) { }); }, isPopup: () => { - const params = - new URLSearchParams(document.location.search.substring(1)); + const params = new URLSearchParams( + document.location.search.substring(1) + ); return params.get('popup'); }, fixPopupSize: () => { const zoom = Number(localStorage.zoom) / 100 || 1; const correctHeight = 480 * zoom; const correctWidth = 320 * zoom; - if (window.innerHeight !== correctHeight || - window.innerWidth !== correctWidth) { + if ( + window.innerHeight !== correctHeight || + window.innerWidth !== correctWidth + ) { // window update to correct size const adjustedHeight = - correctHeight + (window.outerHeight - window.innerHeight); + correctHeight + (window.outerHeight - window.innerHeight); const adjustedWidth = - correctWidth + (window.outerWidth - window.innerWidth); - chrome.windows.update( - chrome.windows.WINDOW_ID_CURRENT, - {height: adjustedHeight, width: adjustedWidth}); + correctWidth + (window.outerWidth - window.innerWidth); + chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, { + height: adjustedHeight, + width: adjustedWidth, + }); } }, migrateStorage: async () => { // sync => local - if (localStorage.storageLocation === 'sync' && - _ui.instance.newStorageLocation === 'local') { + if ( + localStorage.storageLocation === 'sync' && + _ui.instance.newStorageLocation === 'local' + ) { return new Promise((resolve, reject) => { chrome.storage.sync.get(syncData => { chrome.storage.local.set(syncData, () => { - chrome.storage.local.get((localData) => { + chrome.storage.local.get(localData => { // Double check if data was set - if (Object.keys(syncData).every( - (value) => - Object.keys(localData).indexOf(value) >= 0)) { + if ( + Object.keys(syncData).every( + value => Object.keys(localData).indexOf(value) >= 0 + ) + ) { localStorage.storageLocation = 'local'; chrome.storage.sync.clear(); _ui.instance.alert(_ui.instance.i18n.updateSuccess); @@ -238,8 +251,9 @@ export async function menu(_ui: UI) { return; } else { _ui.instance.alert( - _ui.instance.i18n.updateFailure + - ' All data not transferred successfully.'); + _ui.instance.i18n.updateFailure + + ' All data not transferred successfully.' + ); reject('Transfer failure'); return; } @@ -249,16 +263,19 @@ export async function menu(_ui: UI) { }); // local => sync } else if ( - localStorage.storageLocation === 'local' && - _ui.instance.newStorageLocation === 'sync') { + localStorage.storageLocation === 'local' && + _ui.instance.newStorageLocation === 'sync' + ) { return new Promise((resolve, reject) => { chrome.storage.local.get(localData => { chrome.storage.sync.set(localData, () => { - chrome.storage.sync.get((syncData) => { + chrome.storage.sync.get(syncData => { // Double check if data was set - if (Object.keys(localData).every( - (value) => - Object.keys(syncData).indexOf(value) >= 0)) { + if ( + Object.keys(localData).every( + value => Object.keys(syncData).indexOf(value) >= 0 + ) + ) { localStorage.storageLocation = 'sync'; chrome.storage.local.clear(); _ui.instance.alert(_ui.instance.i18n.updateSuccess); @@ -266,8 +283,9 @@ export async function menu(_ui: UI) { return; } else { _ui.instance.alert( - _ui.instance.i18n.updateFailure + - ' All data not transferred successfully.'); + _ui.instance.i18n.updateFailure + + ' All data not transferred successfully.' + ); reject('Transfer failure'); return; } diff --git a/src/ui/message.ts b/src/ui/message.ts index 00d650576..324560600 100644 --- a/src/ui/message.ts +++ b/src/ui/message.ts @@ -1,6 +1,4 @@ - - -import {UI} from './ui'; +import { UI } from './ui'; function isCustomEvent(event: Event): event is CustomEvent { return 'detail' in event; @@ -8,7 +6,7 @@ function isCustomEvent(event: Event): event is CustomEvent { export async function message(_ui: UI) { const ui: UIConfig = { - data: {message: [], messageIdle: true, confirmMessage: ''}, + data: { message: [], messageIdle: true, confirmMessage: '' }, methods: { alert: (message: string) => { _ui.instance.message.unshift(message); @@ -22,26 +20,29 @@ export async function message(_ui: UI) { }, confirm: async (message: string) => { return new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - _ui.instance.confirmMessage = message; - window.addEventListener('confirm', (event) => { - _ui.instance.confirmMessage = ''; - if (!isCustomEvent(event)) { - return resolve(false); - } - return resolve(event.detail); - }); - return; + ( + resolve: (value: boolean) => void, + reject: (reason: Error) => void + ) => { + _ui.instance.confirmMessage = message; + window.addEventListener('confirm', event => { + _ui.instance.confirmMessage = ''; + if (!isCustomEvent(event)) { + return resolve(false); + } + return resolve(event.detail); }); + return; + } + ); }, confirmOK: () => { - const confirmEvent = new CustomEvent('confirm', {detail: true}); + const confirmEvent = new CustomEvent('confirm', { detail: true }); window.dispatchEvent(confirmEvent); return; }, confirmCancel: () => { - const confirmEvent = new CustomEvent('confirm', {detail: false}); + const confirmEvent = new CustomEvent('confirm', { detail: false }); window.dispatchEvent(confirmEvent); return; }, diff --git a/src/ui/passphrase.ts b/src/ui/passphrase.ts index 3235a1130..c9f7d68cb 100644 --- a/src/ui/passphrase.ts +++ b/src/ui/passphrase.ts @@ -1,18 +1,18 @@ -import {getSiteName, hasMatchedEntry} from './entry'; -import {UI} from './ui'; +import { getSiteName, hasMatchedEntry } from './entry'; +import { UI } from './ui'; function cachePassword(password: string) { document.cookie = 'passphrase=' + password; - chrome.runtime.sendMessage({action: 'cachePassphrase', value: password}); + chrome.runtime.sendMessage({ action: 'cachePassphrase', value: password }); } export async function passphrase(_ui: UI) { const ui: UIConfig = { - data: {passphrase: ''}, + data: { passphrase: '' }, methods: { lock: () => { document.cookie = 'passphrase=";expires=Thu, 01 Jan 1970 00:00:00 GMT"'; - chrome.runtime.sendMessage({action: 'lock'}, window.close); + chrome.runtime.sendMessage({ action: 'lock' }, window.close); return; }, removePassphrase: async () => { @@ -26,23 +26,29 @@ export async function passphrase(_ui: UI) { return; } _ui.instance.encryption.updateEncryptionPassword( - _ui.instance.passphrase); + _ui.instance.passphrase + ); await _ui.instance.updateEntries(); const siteName = await getSiteName(); - _ui.instance.shouldFilter = - hasMatchedEntry(siteName, _ui.instance.entries); + _ui.instance.shouldFilter = hasMatchedEntry( + siteName, + _ui.instance.entries + ); _ui.instance.closeInfo(); cachePassword(_ui.instance.passphrase); return; }, changePassphrase: async () => { - if (_ui.instance.newPassphrase.phrase !== - _ui.instance.newPassphrase.confirm) { + if ( + _ui.instance.newPassphrase.phrase !== + _ui.instance.newPassphrase.confirm + ) { _ui.instance.alert(_ui.instance.i18n.phrase_not_match); return; } _ui.instance.encryption.updateEncryptionPassword( - _ui.instance.newPassphrase.phrase); + _ui.instance.newPassphrase.phrase + ); cachePassword(_ui.instance.newPassphrase.phrase); await _ui.instance.importEntries(); // remove cached passphrase in old version diff --git a/src/ui/qr.ts b/src/ui/qr.ts index 9882f76e4..87481e0dd 100644 --- a/src/ui/qr.ts +++ b/src/ui/qr.ts @@ -1,41 +1,53 @@ import * as QRGen from 'qrcode-generator'; -import {OTPEntry} from '../models/otp'; +import { OTPEntry } from '../models/otp'; -import {UI} from './ui'; +import { UI } from './ui'; async function getQrUrl(entry: OTPEntry) { return new Promise( - (resolve: (value: string) => void, reject: (reason: Error) => void) => { - const label = - entry.issuer ? (entry.issuer + ':' + entry.account) : entry.account; - const type = entry.type === OTPType.hex ? - OTPType[OTPType.totp] : - (entry.type === OTPType.hhex ? OTPType[OTPType.hotp] : - OTPType[entry.type]); - const otpauth = 'otpauth://' + type + '/' + label + - '?secret=' + entry.secret + - (entry.issuer ? ('&issuer=' + entry.issuer.split('::')[0]) : '') + - ((entry.type === OTPType.hotp || entry.type === OTPType.hhex) ? - ('&counter=' + entry.counter) : - '') + - (entry.type === OTPType.totp && entry.period ? - ('&period=' + entry.period) : - ''); - const qr = QRGen(0, 'L'); - qr.addData(otpauth); - qr.make(); - resolve(qr.createDataURL(5)); - return; - }); + (resolve: (value: string) => void, reject: (reason: Error) => void) => { + const label = entry.issuer + ? entry.issuer + ':' + entry.account + : entry.account; + const type = + entry.type === OTPType.hex + ? OTPType[OTPType.totp] + : entry.type === OTPType.hhex + ? OTPType[OTPType.hotp] + : OTPType[entry.type]; + const otpauth = + 'otpauth://' + + type + + '/' + + label + + '?secret=' + + entry.secret + + (entry.issuer ? '&issuer=' + entry.issuer.split('::')[0] : '') + + (entry.type === OTPType.hotp || entry.type === OTPType.hhex + ? '&counter=' + entry.counter + : '') + + (entry.type === OTPType.totp && entry.period + ? '&period=' + entry.period + : ''); + const qr = QRGen(0, 'L'); + qr.addData(otpauth); + qr.make(); + resolve(qr.createDataURL(5)); + return; + } + ); } export async function qr(_ui: UI) { const ui: UIConfig = { - data: {qr: ''}, + data: { qr: '' }, methods: { shouldShowQrIcon: (entry: OTPEntry) => { - return entry.secret !== 'Encrypted' && entry.type !== OTPType.battle && - entry.type !== OTPType.steam; + return ( + entry.secret !== 'Encrypted' && + entry.type !== OTPType.battle && + entry.type !== OTPType.steam + ); }, showQr: async (entry: OTPEntry) => { const qrUrl = await getQrUrl(entry); diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 8e4e5e38e..70c4bb4d6 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -1,7 +1,8 @@ -import Vue, {Component} from 'vue'; +/* tslint:disable:ban-ts-ignore */ +import Vue, { Component } from 'vue'; // @ts-ignore -import {Vue2Dragula} from 'vue2-dragula'; -import {OTPEntry} from '../models/otp'; +import { Vue2Dragula } from 'vue2-dragula'; +import { OTPEntry } from '../models/otp'; export class UI { private ui: UIConfig; From 391dc962b7fb4a6d8bd5a8681d0c5d60cd89022a Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Sat, 8 Jun 2019 11:16:59 -0500 Subject: [PATCH 05/44] vuex --- package-lock.json | 5 + package.json | 3 +- src/{view => components}/import.vue | 6 +- src/{view => components}/popup.vue | 17 ++++ src/definitions/shims-vue.d.ts | 7 +- src/definitions/vue2-dragula.d.ts | 6 ++ src/definitions/vuex.d.ts | 8 ++ src/import.ts | 22 ++--- src/popup.ts | 142 ++++++++++++++++++++-------- src/ui/i18n.ts | 12 --- src/ui/ui.ts | 3 +- 11 files changed, 157 insertions(+), 74 deletions(-) rename src/{view => components}/import.vue (94%) rename src/{view => components}/popup.vue (98%) create mode 100644 src/definitions/vue2-dragula.d.ts create mode 100644 src/definitions/vuex.d.ts diff --git a/package-lock.json b/package-lock.json index d00be142c..7c505e448 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5587,6 +5587,11 @@ "dragula": "3.7.2" } }, + "vuex": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.1.tgz", + "integrity": "sha512-ER5moSbLZuNSMBFnEBVGhQ1uCBNJslH9W/Dw2W7GZN23UQA69uapP5GTT9Vm8Trc0PzBSVt6LzF3hGjmv41xcg==" + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", diff --git a/package.json b/package.json index a447be9b7..d1ed868a6 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "qrcode-generator": "^1.4.3", "qrcode-reader": "^1.0.4", "vue": "^2.6.10", - "vue2-dragula": "^2.5.4" + "vue2-dragula": "^2.5.4", + "vuex": "^3.1.1" } } diff --git a/src/view/import.vue b/src/components/import.vue similarity index 94% rename from src/view/import.vue rename to src/components/import.vue index 79d2319e4..fdc1eeb45 100644 --- a/src/view/import.vue +++ b/src/components/import.vue @@ -16,7 +16,7 @@
+ accept="application/json, text/plain" />

{{ i18n.passphrase_info }}

@@ -48,3 +48,7 @@
{{ i18n.import_error_password }}
+ diff --git a/src/view/popup.vue b/src/components/popup.vue similarity index 98% rename from src/view/popup.vue rename to src/components/popup.vue index a8047f784..d4021efb9 100644 --- a/src/view/popup.vue +++ b/src/components/popup.vue @@ -255,3 +255,20 @@ + diff --git a/src/definitions/shims-vue.d.ts b/src/definitions/shims-vue.d.ts index 8a0de0a61..d9f24faa4 100644 --- a/src/definitions/shims-vue.d.ts +++ b/src/definitions/shims-vue.d.ts @@ -1,5 +1,4 @@ declare module '*.vue' { - import Vue from 'vue' - export default Vue - } - \ No newline at end of file + import Vue from 'vue' + export default Vue +} diff --git a/src/definitions/vue2-dragula.d.ts b/src/definitions/vue2-dragula.d.ts new file mode 100644 index 000000000..79c442b28 --- /dev/null +++ b/src/definitions/vue2-dragula.d.ts @@ -0,0 +1,6 @@ + +declare module 'vue2-dragula' { + import { PluginFunction, VueConstructor } from "vue"; + + const Vue2Dragula: PluginFunction; +} diff --git a/src/definitions/vuex.d.ts b/src/definitions/vuex.d.ts new file mode 100644 index 000000000..cc50adc1d --- /dev/null +++ b/src/definitions/vuex.d.ts @@ -0,0 +1,8 @@ +import Vue from 'vue' +import { Store } from 'vuex'; + +declare module 'vue/types/vue' { + interface Vue { + $store: Store; + } +} diff --git a/src/import.ts b/src/import.ts index 414d8ba2e..679ca2fa7 100644 --- a/src/import.ts +++ b/src/import.ts @@ -1,19 +1,15 @@ -import {entry} from './ui/entry'; -import {i18n} from './ui/i18n'; -import {UI} from './ui/ui'; -// @ts-ignore -import ImportView from './view/import'; +import { entry } from './ui/entry'; +import Vue from 'vue'; +import ImportView from './components/import.vue'; async function init() { - const ui = new UI(ImportView, {el: '#import'}); + new Vue({ render: h => h(ImportView) }).$mount('#import'); - const vm = await ui.load(i18n).load(entry).render(); - - try { - document.title = ui.instance.i18n.extName; - } catch (e) { - console.error(e); - } + // try { + // document.title = ui..i18n.extName; + // } catch (e) { + // console.error(e); + // } } init(); diff --git a/src/popup.ts b/src/popup.ts index 4cec6880f..4c812d8c8 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -1,31 +1,32 @@ -import {addAccount} from './ui/add-account'; -import {backup} from './ui/backup'; -import {className} from './ui/class'; -import {entry} from './ui/entry'; -import {i18n} from './ui/i18n'; -import {info} from './ui/info'; -import {menu, syncTimeWithGoogle} from './ui/menu'; -import {message} from './ui/message'; -import {passphrase} from './ui/passphrase'; -import {qr} from './ui/qr'; -import {UI} from './ui/ui'; -// @ts-ignore -import Authenticator from './view/popup'; +/* +import { addAccount } from './ui/add-account'; +import { backup } from './ui/backup'; +import { className } from './ui/class'; +import { entry } from './ui/entry'; +import { i18n } from './ui/i18n'; +import { info } from './ui/info'; +import { menu, syncTimeWithGoogle } from './ui/menu'; +import { message } from './ui/message'; +import { passphrase } from './ui/passphrase'; +import { qr } from './ui/qr'; +import { UI } from './ui/ui'; +import Authenticator from './view/popup.vue'; async function init() { - const ui = new UI(Authenticator, {el: '#authenticator'}); - - const authenticator = await ui.load(className) - .load(i18n) - .load(menu) - .load(info) - .load(passphrase) - .load(entry) - .load(qr) - .load(message) - .load(addAccount) - .load(backup) - .render(); + const ui = new UI(Authenticator, { el: '#authenticator' }); + + const authenticator = await ui + .load(className) + .load(i18n) + .load(menu) + .load(info) + .load(passphrase) + .load(entry) + .load(qr) + .load(message) + .load(addAccount) + .load(backup) + .render(); try { document.title = ui.instance.i18n.extName; @@ -60,20 +61,27 @@ async function init() { if (!localStorage.lastRemindingBackupTime) { localStorage.lastRemindingBackupTime = clientTime; } else if ( - clientTime - localStorage.lastRemindingBackupTime >= 30 || - clientTime - localStorage.lastRemindingBackupTime < 0) { + clientTime - localStorage.lastRemindingBackupTime >= 30 || + clientTime - localStorage.lastRemindingBackupTime < 0 + ) { // backup to cloud authenticator.runScheduledBackup(clientTime); } return; }, 1000); - document.addEventListener('keyup', (e) => { - ui.instance.searchListener(e); - }, false); - - if (ui.instance.entries.length >= 10 && - !(ui.instance.shouldFilter && ui.instance.filter)) { + document.addEventListener( + 'keyup', + e => { + ui.instance.searchListener(e); + }, + false + ); + + if ( + ui.instance.entries.length >= 10 && + !(ui.instance.shouldFilter && ui.instance.filter) + ) { ui.instance.showSearch = true; } @@ -85,8 +93,11 @@ async function init() { authenticator.driveToken = message.value; } authenticator.backupUpload( - String(message.action) - .substring(0, String(message.action).indexOf('token'))); + String(message.action).substring( + 0, + String(message.action).indexOf('token') + ) + ); if (['dropbox', 'drive'].indexOf(authenticator.info) > -1) { setTimeout(authenticator.closeInfo, 500); } @@ -104,11 +115,60 @@ if (navigator.userAgent.indexOf('Edge') !== -1) { syncTimeWithGoogle(); } else { chrome.permissions.contains( - {origins: ['https://www.google.com/']}, (hasPermission) => { - if (hasPermission) { - syncTimeWithGoogle(); - } - }); + { origins: ['https://www.google.com/'] }, + hasPermission => { + if (hasPermission) { + syncTimeWithGoogle(); + } + } + ); +} + +init(); +*/ + +// Vue +import Vue from 'vue'; +import Vuex from 'vuex'; +import { Vue2Dragula } from 'vue2-dragula'; + +// Components +import Popup from './components/popup.vue'; + +// Other +import { loadI18nMessages } from './ui/i18n'; + +async function init() { + // Add globals + Vue.prototype.i18n = await loadI18nMessages(); + + // Load modules + Vue.use(Vuex); + Vue.use(Vue2Dragula); + + // State store + const store = new Vuex.Store({ + state: { + useHighContrast: + typeof localStorage.useHighContrast === undefined + ? false + : localStorage.useHighContrast, + }, + mutations: { + toggleHighContrast(state) { + state.useHighContrast = !state.useHighContrast; + }, + }, + }); + + // Render + const instance = new Vue({ + render: h => h(Popup), + store, + mounted: () => { + // Dragula stuff here + }, + }).$mount('#authenticator'); } init(); diff --git a/src/ui/i18n.ts b/src/ui/i18n.ts index 3d66e84c1..6543cb122 100644 --- a/src/ui/i18n.ts +++ b/src/ui/i18n.ts @@ -28,15 +28,3 @@ export async function loadI18nMessages() { } ); } - -export async function i18n(_ui: UI) { - const i18n = await loadI18nMessages(); - - const ui: UIConfig = { data: { i18n } }; - - _ui.update(ui); -} - -export async function ri18n() { - return loadI18nMessages(); -} diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 70c4bb4d6..04450cff0 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -1,7 +1,6 @@ /* tslint:disable:ban-ts-ignore */ import Vue, { Component } from 'vue'; // @ts-ignore -import { Vue2Dragula } from 'vue2-dragula'; import { OTPEntry } from '../models/otp'; export class UI { @@ -44,7 +43,7 @@ export class UI { for (let i = 0; i < this.modules.length; i++) { await this.modules[i](this); } - Vue.use(Vue2Dragula); + this.ui.mounted = () => { // @ts-ignore Vue.$dragula.$service.eventBus.$on('drop', async () => { From 0161e72c88da6d6f0c1ed15e1930cb5f4e8da9b5 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Sun, 9 Jun 2019 17:27:20 -0500 Subject: [PATCH 06/44] wip --- src/components/popup.vue | 28 +++++++++++++++------------ src/definitions/module-interface.d.ts | 22 +++++++++++++++++++++ src/popup.ts | 21 +++++++++++++------- 3 files changed, 52 insertions(+), 19 deletions(-) create mode 100644 src/definitions/module-interface.d.ts diff --git a/src/components/popup.vue b/src/components/popup.vue index d4021efb9..46ac06400 100644 --- a/src/components/popup.vue +++ b/src/components/popup.vue @@ -3,15 +3,15 @@
{{ i18n.extName }}
-
cog
-
lock
-
Alternate Sync
-
-
Alternate Pencil
+
cog
+
lock
+
Alternate Sync
+
+
Alternate Pencil
Check
-
+
{{ i18n.show_all_entries }}