diff --git a/packages/background/package.json b/packages/background/package.json index 18206845e..f28fd65f0 100644 --- a/packages/background/package.json +++ b/packages/background/package.json @@ -8,15 +8,15 @@ "@block-wallet/explorer-link": "https://github.com/block-wallet/explorer-link#v2.2.2", "@block-wallet/remote-configs": "https://github.com/block-wallet/remote-configs#v1.1.0", "@ethereumjs/tx": "^3.5.2", + "@metamask/browser-passworder": "https://github.com/block-wallet/browser-passworder#v1.0.1", + "@metamask/eth-keyring-controller": "^10.0.0", "@metamask/eth-sig-util": "^5.0.2", "@unstoppabledomains/resolution": "^8.3.3", "async-mutex": "^0.3.2", "bip39": "^3.0.3", - "browser-passworder": "^2.0.3", "compare-versions": "^3.6.0", "eslint-webpack-plugin": "^3.2.0", "eth-ens-namehash": "^2.0.8", - "eth-keyring-controller": "^6.2.0", "eth-trezor-keyring": "https://github.com/block-wallet/eth-trezor-keyring#v0.10.2", "ethereumjs-util": "^7.0.7", "ethereumjs-wallet": "^1.0.1", diff --git a/packages/background/src/controllers/AppStateController.ts b/packages/background/src/controllers/AppStateController.ts index b393b4fec..aa4b79f9e 100644 --- a/packages/background/src/controllers/AppStateController.ts +++ b/packages/background/src/controllers/AppStateController.ts @@ -17,6 +17,11 @@ export enum AppStateEvents { APP_UNLOCKED = 'APP_UNLOCKED', } +export type SessionToken = { + encryptionKey: string; + encryptionSalt: string; +}; + export default class AppStateController extends BaseController { private _timer: ReturnType | null; @@ -117,19 +122,15 @@ export default class AppStateController extends BaseController => { try { // Unlock vault - const loginToken = await this._keyringController.submitPassword( + const sessionToken = await this._keyringController.submitPassword( password ); if (isManifestV3()) { - // @ts-ignore - chrome.storage.session && - // @ts-ignore - chrome.storage.session - .set({ loginToken }) - .catch((err: any) => { - log.error('error setting loginToken', err); - }); + this._storeSessionToken({ + encryptionKey: sessionToken.encryptionKey, + encryptionSalt: sessionToken.encryptionSalt, + }); } await this._postLoginAction(); @@ -138,24 +139,76 @@ export default class AppStateController extends BaseController { + return new Promise((resolve) => { + // @ts-ignore + if (chrome.storage.session) { + // @ts-ignore + chrome.storage.session.get( + ['sessionToken'], + async ({ sessionToken }: { [key: string]: any }) => { + if (!sessionToken) { + resolve(undefined); + } + resolve(sessionToken as SessionToken); + } + ); + } else { + resolve(undefined); + } + }); + } + + private async _storeSessionToken( + sessionToken: SessionToken + ): Promise { + // @ts-ignore + if (chrome.storage.session) { + // @ts-ignore + await chrome.storage.session + .set({ + sessionToken: { + encryptionKey: sessionToken.encryptionKey, + encryptionSalt: sessionToken.encryptionSalt, + }, + }) + .catch((err: any) => { + log.error('error setting sessionToken', err); + }); + } + + return sessionToken; + } + public autoUnlock = async (): Promise => { if (isManifestV3()) { const { isAppUnlocked } = this.store.getState(); - if (!isAppUnlocked) { - // @ts-ignore - chrome.storage.session && - // @ts-ignore - chrome.storage.session.get( - ['loginToken'], - async ({ loginToken }: { [key: string]: string }) => { - if (loginToken) { - await (this._keyringController as any)[ - 'submitEncryptionKey' - ](loginToken); - await this._postLoginAction(); - } - } - ); + if (isAppUnlocked) { + let forceLock = true; + try { + const sessionToken = await this._getSessionToken(); + if (sessionToken) { + const newSessionToken = + await this._keyringController.submitEncryptionKey( + sessionToken.encryptionKey, + sessionToken.encryptionSalt + ); + await this._storeSessionToken({ + encryptionKey: newSessionToken.encryptionKey, + encryptionSalt: newSessionToken.encryptionSalt, + }); + await this._postLoginAction(); + forceLock = false; + } + } catch (e) { + log.error('Unable to autoUnlock keyring', e); + } finally { + // if we were unable to unlock keyring we should lock the wallet + // the user needs to unlock the keyring and the wallet by his own + if (forceLock) { + this.lock(); + } + } } } }; diff --git a/packages/background/src/controllers/KeyringControllerDerivated.ts b/packages/background/src/controllers/KeyringControllerDerivated.ts index 938c09399..a0e22cd84 100644 --- a/packages/background/src/controllers/KeyringControllerDerivated.ts +++ b/packages/background/src/controllers/KeyringControllerDerivated.ts @@ -1,7 +1,10 @@ -import KeyringController, { +import { + KeyringController, + keyringBuilderFactory, KeyringControllerProps, KeyringControllerState, -} from 'eth-keyring-controller'; +} from '@metamask/eth-keyring-controller'; +import * as customEncryptor from '@metamask/browser-passworder'; import { Hash, Hasheable } from '../utils/hasher'; import { Mutex } from 'async-mutex'; import LedgerBridgeKeyring from '@block-wallet/eth-ledger-bridge-keyring'; @@ -10,6 +13,9 @@ import { Devices } from '../utils/types/hardware'; import log from 'loglevel'; import { HDPaths, BIP44_PATH } from '../utils/types/hardware'; import { TypedTransaction } from '@ethereumjs/tx'; +import { isManifestV3 } from '../utils/manifest'; +import { bufferToHex } from 'ethereumjs-util'; +import { hexToString } from '../utils/signature'; /** * Available keyring types */ @@ -24,7 +30,12 @@ export default class KeyringControllerDerivated extends KeyringController { private readonly _mutex: Mutex; constructor(opts: KeyringControllerProps) { - opts.keyringTypes = [LedgerBridgeKeyring, TrezorKeyring]; + opts.keyringBuilders = [ + keyringBuilderFactory(LedgerBridgeKeyring), + keyringBuilderFactory(TrezorKeyring), + ]; + opts.cacheEncryptionKey = isManifestV3(); + opts.encryptor = customEncryptor; super(opts); this._mutex = new Mutex(); @@ -155,8 +166,7 @@ export default class KeyringControllerDerivated extends KeyringController { KeyringTypes.HD_KEY_TREE )[0]; const serialized = await primaryKeyring.serialize(); - const seedPhrase = serialized.mnemonic; - + const seedPhrase = hexToString(bufferToHex(serialized.mnemonic)); return seedPhrase; } @@ -252,15 +262,20 @@ export default class KeyringControllerDerivated extends KeyringController { // Generate a new keyring const keyringController = new KeyringController({}); - const Keyring = keyringController.getKeyringClassForType( - KeyringTypes.HD_KEY_TREE - ); + const opts = { mnemonic: seedPhrase, numberOfAccounts: createdAccounts.length, }; - const keyring = new Keyring(opts); + const keyring = await keyringController._newKeyring( + KeyringTypes.HD_KEY_TREE, + opts + ); + if (!keyring) { + throw new Error('Unable to generate keyring of type HD_KEY_TREE'); + } + const restoredAccounts = await keyring.getAccounts(); if (restoredAccounts.length !== createdAccounts.length) { diff --git a/packages/background/src/controllers/OnboardingController.ts b/packages/background/src/controllers/OnboardingController.ts index 822a72371..84701da84 100644 --- a/packages/background/src/controllers/OnboardingController.ts +++ b/packages/background/src/controllers/OnboardingController.ts @@ -1,4 +1,4 @@ -import { KeyringControllerState } from 'eth-keyring-controller'; +import { KeyringControllerState } from '@metamask/eth-keyring-controller'; import { BaseController } from '../infrastructure/BaseController'; import KeyringControllerDerivated from './KeyringControllerDerivated'; diff --git a/packages/background/src/index.ts b/packages/background/src/index.ts index c48d3fc51..4adf8d2ce 100644 --- a/packages/background/src/index.ts +++ b/packages/background/src/index.ts @@ -191,6 +191,24 @@ chrome.runtime.onInstalled.addListener(({ reason }) => { } }); +const registerBlankProviderContentScript = async () => { + try { + await (chrome.scripting as any).registerContentScripts([ + { + id: 'blankProvider', + matches: ['file://*/*', 'http://*/*', 'https://*/*'], + js: ['blankProvider.js'], + runAt: 'document_start', + world: 'MAIN', + }, + ]); + } catch (err) { + console.warn( + `Dropped attempt to register blankProvider content script. ${err}` + ); + } +}; + if (isManifestV3()) { // this keeps alive the service worker. // when it goes 'inactive' it is restarted. @@ -198,4 +216,5 @@ if (isManifestV3()) { chrome.alarms.onAlarm.addListener(() => { fetch(chrome.runtime.getURL('keep-alive')); }); + registerBlankProviderContentScript(); } diff --git a/packages/background/src/typings/browser-passworder.d.ts b/packages/background/src/typings/browser-passworder.d.ts index 4abc354ce..0e105873f 100644 --- a/packages/background/src/typings/browser-passworder.d.ts +++ b/packages/background/src/typings/browser-passworder.d.ts @@ -1,4 +1,4 @@ -declare module 'browser-passworder' { +declare module '@metamask/browser-passworder' { declare function encrypt(password: string, dataObj: T): Promise; declare function decrypt( password: string, diff --git a/packages/background/src/typings/eth-keyring-controller.d.ts b/packages/background/src/typings/eth-keyring-controller.d.ts index 0ae5fbee7..e92a30e78 100644 --- a/packages/background/src/typings/eth-keyring-controller.d.ts +++ b/packages/background/src/typings/eth-keyring-controller.d.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/ban-types */ -declare module 'eth-keyring-controller' { + +declare module '@metamask/eth-keyring-controller' { import { TypedTransaction } from '@ethereumjs/tx'; import { IObservableStore } from '../infrastructure/stores/ObservableStore'; import { EventEmitter } from 'events'; @@ -10,6 +11,8 @@ declare module 'eth-keyring-controller' { keyringTypes: any; keyrings: any[]; vault: string; + encryptionKey: string; + encryptionSalt: string; } export interface KeyringControllerMemState { @@ -18,15 +21,27 @@ declare module 'eth-keyring-controller' { keyrings: any[]; } + export type Keyring = any; + + export type KeyringBuilder = { + (): Keyring; + type: KeyringTypes; + }; + + export type KeyringBuilderFactoryType = ( + keyring: Keyring + ) => KeyringBuilder; + + export const keyringBuilderFactory: KeyringBuilderFactoryType; + export interface KeyringControllerProps { initState?: Partial; encryptor?: any; - keyringTypes?: any; + cacheEncryptionKey?: boolean; + keyringBuilders?: KeyringBuilderFactory[]; } - export type Keyring = any; - - export default class KeyringController extends EventEmitter { + export class KeyringController extends EventEmitter { memStore: IObservableStore; store: IObservableStore; @@ -125,6 +140,22 @@ declare module 'eth-keyring-controller' { */ submitPassword(password: string): Promise; + /** + * Submit Encryption Key. + * + * Attempts to decrypt the current vault and load its keyrings + * into memory based on the vault and CryptoKey information. + * + * @fires KeyringController#unlock + * @param {string} encryptionKey - The encrypted key information used to decrypt the vault. + * @param {string} encryptionSalt - The salt used to generate the last key. + * @returns {Promise} A Promise that resolves to the state. + */ + submitEncryptionKey( + encryptionKey: string, + encryptionSalt: string + ): Promise; + /** * Verify Password * @@ -205,19 +236,6 @@ declare module 'eth-keyring-controller' { */ removeAccount(address: string | string[]): Promise; - /** - * Get Keyring Class For Type - * - * Searches the current `keyringTypes` array - * for a Keyring class whose unique `type` property - * matches the provided `type`, - * returning it if it exists. - * - * @param {string} type - The type whose class to get. - * @returns {Keyring|undefined} The class, if it exists. - */ - getKeyringClassForType(type: string): Keyring | undefined; - /** * Sign Ethereum Transaction * @@ -386,6 +404,19 @@ declare module 'eth-keyring-controller' { */ restoreKeyring(serialized: any): Promise; + /** + * Get Keyring Class For Type + * + * Searches the current `keyringBuilders` array + * for a Keyring builder whose unique `type` property + * matches the provided `type`, + * returning it if it exists. + * + * @param {string} type - The type whose class to get. + * @returns {Keyring|undefined} The class, if it exists. + */ + getKeyringBuilderForType(type: string): KeyringBuilder | undefined; + /** * Display For Keyring * @@ -402,5 +433,7 @@ declare module 'eth-keyring-controller' { * Used before initializing a new vault. */ clearKeyrings(): Promise; + + _newKeyring(type: string, data: any): Promise; } } diff --git a/packages/background/src/utils/constants/initialState.ts b/packages/background/src/utils/constants/initialState.ts index 24fc2af0a..72fbe9df7 100644 --- a/packages/background/src/utils/constants/initialState.ts +++ b/packages/background/src/utils/constants/initialState.ts @@ -1,7 +1,7 @@ import { KeyringControllerState, KeyringControllerMemState, -} from 'eth-keyring-controller'; +} from '@metamask/eth-keyring-controller'; import { ValuesOf } from '../types/helpers'; import { IObservableStore } from '../../infrastructure/stores/ObservableStore'; @@ -134,6 +134,8 @@ const initialState: BlankAppState = { keyringTypes: [], keyrings: [], vault: '', + encryptionKey: '', + encryptionSalt: '', }, OnboardingController: { isOnboarded: false, diff --git a/packages/background/test/controllers/KeyringControllerDerivated.test.ts b/packages/background/test/controllers/KeyringControllerDerivated.test.ts index 8ca6a8801..57dcc36a2 100644 --- a/packages/background/test/controllers/KeyringControllerDerivated.test.ts +++ b/packages/background/test/controllers/KeyringControllerDerivated.test.ts @@ -3,7 +3,10 @@ import { expect } from 'chai'; import KeyringControllerDerivated, { KeyringTypes, } from '@block-wallet/background/controllers/KeyringControllerDerivated'; -import KeyringController from 'eth-keyring-controller'; +import { + KeyringBuilder, + KeyringController, +} from '@metamask/eth-keyring-controller'; import mockEncryptor from 'test/mocks/mock-encryptor'; describe('KeyringControllerDerivated', () => { @@ -92,14 +95,19 @@ describe('KeyringControllerDerivated', () => { }); }; } + const mockKeyTreeBuilder: KeyringBuilder = () => mockKeyTree; + mockKeyTreeBuilder.type = KeyringTypes.HD_KEY_TREE; + + const keyTreeBuilder: KeyringBuilder = () => keyTree; + keyTreeBuilder.type = KeyringTypes.HD_KEY_TREE; sinon - .stub(KeyringController.prototype, 'getKeyringsByType') - .returns([keyTree]); + .stub(KeyringController.prototype, 'getKeyringBuilderForType') + .returns(keyTreeBuilder); sinon - .stub(KeyringController.prototype, 'getKeyringClassForType') - .returns(mockKeyTree); + .stub(KeyringController.prototype, 'getKeyringBuilderForType') + .returns(mockKeyTreeBuilder); try { await (keyringControllerDerivated as any)['verifyAccounts'](); @@ -136,13 +144,16 @@ describe('KeyringControllerDerivated', () => { }; } + const mockKeyTreeBuilder: KeyringBuilder = () => mockKeyTree; + mockKeyTreeBuilder.type = KeyringTypes.HD_KEY_TREE; + sinon .stub(KeyringController.prototype, 'getKeyringsByType') .returns([keyTree]); sinon - .stub(KeyringController.prototype, 'getKeyringClassForType') - .returns(mockKeyTree); + .stub(KeyringController.prototype, 'getKeyringBuilderForType') + .returns(mockKeyTreeBuilder); try { await (keyringControllerDerivated as any)['verifyAccounts'](); diff --git a/packages/background/test/infrastructure/stores/migrator/reconciler.test.ts b/packages/background/test/infrastructure/stores/migrator/reconciler.test.ts index 0028de578..de7e332be 100644 --- a/packages/background/test/infrastructure/stores/migrator/reconciler.test.ts +++ b/packages/background/test/infrastructure/stores/migrator/reconciler.test.ts @@ -199,6 +199,8 @@ const initialState: newBlankAppState = { keyringTypes: {}, keyrings: [], vault: '', + encryptionKey: '', + encryptionSalt: '', }, NetworkController: { selectedNetwork: 'mainnet', @@ -425,6 +427,8 @@ describe('State reconciler', () => { keyringTypes: {}, keyrings: [], vault: 'encrypted-vault', + encryptionKey: '', + encryptionSalt: '', }, OnboardingController: { isOnboarded: true, diff --git a/packages/background/yarn.lock b/packages/background/yarn.lock index 8b4a9cd2a..883e67807 100644 --- a/packages/background/yarn.lock +++ b/packages/background/yarn.lock @@ -274,6 +274,15 @@ async "^3.2.4" ethereum-cryptography "^1.1.2" +"@ethereumjs/util@^8.0.2": + version "8.0.3" + resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.0.3.tgz#410c2dc8c6d519b29f1a471aa9b9df9952e41239" + integrity sha512-0apCbwc8xAaie6W7q6QyogfyRS2BMU816a8KwpnpRw9Qrc6Bws+l7J3LfCLMt2iL6Wi8CYb0B29AeIr2N4vHnw== + dependencies: + "@ethereumjs/rlp" "^4.0.0-beta.2" + async "^3.2.4" + ethereum-cryptography "^1.1.2" + "@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.1", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" @@ -707,6 +716,43 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@metamask/browser-passworder@^4.0.2", "@metamask/browser-passworder@https://github.com/block-wallet/browser-passworder", "@metamask/browser-passworder@https://github.com/block-wallet/browser-passworder#v1.0.1": + version "4.0.2" + resolved "https://github.com/block-wallet/browser-passworder#f8fa5a8a9dfdba84afc8b67c481e6c9374e99d13" + +"@metamask/eth-hd-keyring@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-hd-keyring/-/eth-hd-keyring-6.0.0.tgz#a46788d4bbc7aa5d8263ed6e4348101a80519a69" + integrity sha512-dEj/I6Ag9FyCmjPcRXeXCkRXkVJE/uElhDVRcLBU6mT/GsXKgzVWXC/k0dhE8rEDrQbidhl+8wEElSJ2LI1InA== + dependencies: + "@ethereumjs/util" "^8.0.2" + "@metamask/eth-sig-util" "^5.0.2" + "@metamask/scure-bip39" "^2.0.3" + ethereum-cryptography "^1.1.2" + +"@metamask/eth-keyring-controller@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-keyring-controller/-/eth-keyring-controller-10.0.0.tgz#71e8fd84a02fae70e7afe75f09c4551114cad742" + integrity sha512-Ypox0BunIQO6x0E3KUzhw125zoWKwkpZl9xw4rnVNbZOHrsD+/uXGTAZRSPLdoKbq0DYdkQAvcds8FeoJEWEYw== + dependencies: + "@metamask/browser-passworder" "^4.0.2" + "@metamask/eth-hd-keyring" "^6.0.0" + "@metamask/eth-sig-util" "5.0.2" + "@metamask/eth-simple-keyring" "^5.0.0" + obs-store "^4.0.3" + +"@metamask/eth-sig-util@5.0.2", "@metamask/eth-sig-util@^5.0.1", "@metamask/eth-sig-util@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-5.0.2.tgz#c518279a6e17a88135a13d53a0b970f145ff8bce" + integrity sha512-RU6fG/H6/UlBol221uBkq5C7w3TwLK611nEZliO2u+kO0vHKGBXnIPlhI0tzKUigjhUeOd9mhCNbNvhh0LKt9Q== + dependencies: + "@ethereumjs/util" "^8.0.0" + bn.js "^4.11.8" + ethereum-cryptography "^1.1.2" + ethjs-util "^0.1.6" + tweetnacl "^1.0.3" + tweetnacl-util "^0.15.1" + "@metamask/eth-sig-util@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz#3ad61f6ea9ad73ba5b19db780d40d9aae5157088" @@ -718,17 +764,23 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" -"@metamask/eth-sig-util@^5.0.2": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-5.0.2.tgz#c518279a6e17a88135a13d53a0b970f145ff8bce" - integrity sha512-RU6fG/H6/UlBol221uBkq5C7w3TwLK611nEZliO2u+kO0vHKGBXnIPlhI0tzKUigjhUeOd9mhCNbNvhh0LKt9Q== +"@metamask/eth-simple-keyring@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-simple-keyring/-/eth-simple-keyring-5.0.0.tgz#307772d1aa3298e41a2444b428cd8f4522b7bf5c" + integrity sha512-UJfP36Z9g1eeD8mSHWaVqUvkgbgYm3S7YuzlMzQi+WgPnWu81CdbldMMtvreTlu4I1mTyljXLDMjIp65P0bygQ== dependencies: "@ethereumjs/util" "^8.0.0" - bn.js "^4.11.8" + "@metamask/eth-sig-util" "^5.0.1" ethereum-cryptography "^1.1.2" - ethjs-util "^0.1.6" - tweetnacl "^1.0.3" - tweetnacl-util "^0.15.1" + randombytes "^2.1.0" + +"@metamask/scure-bip39@^2.0.3": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@metamask/scure-bip39/-/scure-bip39-2.1.0.tgz#13456884736e56ede15e471bd93c0aa0acdedd0b" + integrity sha512-Ndwdnld0SI6YaftEUUVq20sdoWcWNXsJXxvQkbiY42FKmrA16U6WoSh9Eq+NpugpKKwK6f5uvaTDusjndiEDGQ== + dependencies: + "@noble/hashes" "~1.1.1" + "@scure/base" "~1.1.0" "@noble/hashes@1.1.2": version "1.1.2" @@ -1680,17 +1732,6 @@ bindings@^1.3.0, bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bip39@^2.2.0, bip39@^2.4.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/bip39/-/bip39-2.6.0.tgz#9e3a720b42ec8b3fbe4038f1e445317b6a99321c" - integrity sha512-RrnQRG2EgEoqO24ea+Q/fftuPUZLmrEM3qNhhGsA3PbaXaCW791LTzPuVyx/VprXQcTbPJ3K3UeTna8ZnVl2sg== - dependencies: - create-hash "^1.1.0" - pbkdf2 "^3.0.9" - randombytes "^2.0.1" - safe-buffer "^5.0.1" - unorm "^1.3.3" - bip39@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0" @@ -1727,11 +1768,6 @@ blakejs@^1.1.0: resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== -bluebird@^3.5.0: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.4.0: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" @@ -1774,13 +1810,6 @@ brorand@^1.0.1, brorand@^1.0.5, brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browser-passworder@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/browser-passworder/-/browser-passworder-2.0.3.tgz#6fdd2082e516a176edbcb3dcee0b7f9fce4f7917" - integrity sha512-8mTcGjsVqYkRW0qLmdussBjf/5joBUpvZfR8jUojITBJVVZIS5BL41Qt/xehS+n2ChA2YJHHLPhlkXziK+gvsw== - dependencies: - browserify-unibabel "^3.0.0" - browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" @@ -1840,11 +1869,6 @@ browserify-sign@^4.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" -browserify-unibabel@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/browserify-unibabel/-/browserify-unibabel-3.0.0.tgz#5a6b8f0f704ce388d3927df47337e25830f71dda" - integrity sha512-j3MX0k2dC1/DEo9jSUyj7zpv5wLd1+klpFwYlM0E5mr7MX6LblQOWb+jkBEKV2iRszmhJuLHAtNuqranlGnpNQ== - browserslist@^4.14.5, browserslist@^4.21.3: version "4.21.4" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" @@ -2690,32 +2714,6 @@ eth-ens-namehash@^2.0.8: idna-uts46-hx "^2.3.1" js-sha3 "^0.5.7" -eth-hd-keyring@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/eth-hd-keyring/-/eth-hd-keyring-3.6.0.tgz#6835d30aa411b8d3ef098e82f6427b5325082abb" - integrity sha512-n2CwE9VNXsxLrXQa6suv0Umt4NT6+HtoahKgWx3YviXx4rQFwVT5nDwZfjhwrT31ESuoXYNIeJgz5hKLD96QeQ== - dependencies: - bip39 "^2.2.0" - eth-sig-util "^3.0.1" - eth-simple-keyring "^4.2.0" - ethereumjs-util "^7.0.9" - ethereumjs-wallet "^1.0.1" - -eth-keyring-controller@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/eth-keyring-controller/-/eth-keyring-controller-6.2.1.tgz#61901071fc74059ed37cb5ae93870fdcae6e3781" - integrity sha512-x2gTM1iHp2Kbvdtd9Eslysw0qzVZiqOzpVB3AU/ni2Xiit+rlcv2H80zYKjrEwlfWFDj4YILD3bOqlnEMmRJOA== - dependencies: - bip39 "^2.4.0" - bluebird "^3.5.0" - browser-passworder "^2.0.3" - eth-hd-keyring "^3.6.0" - eth-sig-util "^3.0.1" - eth-simple-keyring "^4.2.0" - ethereumjs-util "^7.0.9" - loglevel "^1.5.0" - obs-store "^4.0.3" - eth-rpc-errors@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz#6ddb6190a4bf360afda82790bb7d9d5e724f423a" @@ -2733,26 +2731,6 @@ eth-sig-util@^2.0.0: tweetnacl "^1.0.3" tweetnacl-util "^0.15.0" -eth-sig-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-3.0.1.tgz#8753297c83a3f58346bd13547b59c4b2cd110c96" - integrity sha512-0Us50HiGGvZgjtWTyAI/+qTzYPMLy5Q451D0Xy68bxq1QMWdoOddDwGvsqcFT27uohKgalM9z/yxplyt+mY2iQ== - dependencies: - ethereumjs-abi "^0.6.8" - ethereumjs-util "^5.1.1" - tweetnacl "^1.0.3" - tweetnacl-util "^0.15.0" - -eth-simple-keyring@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eth-simple-keyring/-/eth-simple-keyring-4.2.0.tgz#c197a4bd4cce7d701b5f3607d0b843112ddb17e3" - integrity sha512-lBxFObXJTBjktDkQszXrqoB317wghEUYATQ3W19vLAjaznrmbdy1lccPhXIRMT9bHIUgNKOJQkLohNZiT9tO8Q== - dependencies: - eth-sig-util "^3.0.1" - ethereumjs-util "^7.0.9" - ethereumjs-wallet "^1.0.1" - events "^1.1.1" - "eth-trezor-keyring@https://github.com/block-wallet/eth-trezor-keyring#v0.10.2": version "0.10.0" resolved "https://github.com/block-wallet/eth-trezor-keyring#ac701bf5c20590e0ba882d4748e578601de74a70" @@ -2897,11 +2875,6 @@ ethjs-util@0.1.6, ethjs-util@^0.1.3, ethjs-util@^0.1.6: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" -events@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" - integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== - events@^3.0.0, events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -3983,7 +3956,7 @@ log-symbols@4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -loglevel@^1.5.0, loglevel@^1.7.1: +loglevel@^1.7.1: version "1.8.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4" integrity sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg== @@ -5631,11 +5604,6 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -unorm@^1.3.3: - version "1.6.0" - resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.6.0.tgz#029b289661fba714f1a9af439eb51d9b16c205af" - integrity sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA== - update-browserslist-db@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" diff --git a/packages/provider/src/content.ts b/packages/provider/src/content.ts index 0cca13fdd..bad4aa40e 100644 --- a/packages/provider/src/content.ts +++ b/packages/provider/src/content.ts @@ -14,22 +14,27 @@ import { checkScriptLoad } from './utils/site'; import blankProvider from '../../../dist/blankProvider.js?raw'; import { isManifestV3 } from '@block-wallet/background/utils/manifest'; +const EXTENSION_CONTEXT_INVALIDATED_CHROMIUM_ERROR = + 'Extension context invalidated.'; + let providerOverridden = false; function injectProvider() { - const injectableScript = blankProvider; - const injectableScriptSourceMapURL = `//# sourceURL=${chrome.runtime.getURL( - 'blankProvider.js' - )}\n`; - const BUNDLE = injectableScript + injectableScriptSourceMapURL; - - const container = document.head || document.documentElement; - const script = document.createElement('script'); - script.type = 'text/javascript'; - script.textContent = BUNDLE; - script.setAttribute('async', 'false'); - container.insertBefore(script, container.children[0]); - container.removeChild(script); + if (!isManifestV3()) { + const injectableScript = blankProvider; + const injectableScriptSourceMapURL = `//# sourceURL=${chrome.runtime.getURL( + 'blankProvider.js' + )}\n`; + const BUNDLE = injectableScript + injectableScriptSourceMapURL; + + const container = document.head || document.documentElement; + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.textContent = BUNDLE; + script.setAttribute('async', 'false'); + container.insertBefore(script, container.children[0]); + container.removeChild(script); + } } window.addEventListener('ethereum#initialized', (e: Event) => { @@ -43,27 +48,56 @@ window.addEventListener('ethereum#initialized', (e: Event) => { injectProvider(); -const SW_KEEP_ALIVE_INTERVAL = 10; +const SW_KEEP_ALIVE_INTERVAL = 1000; let SW_ALIVE = false; +let EXTENSION_CONTEXT_VALID = true; let portReinitialized = false; -let intervalRef: NodeJS.Timer; +let timeoutRef: NodeJS.Timer; -if (isManifestV3()) { - intervalRef = setInterval(() => { - chrome.runtime.sendMessage({ message: CONTENT.SW_KEEP_ALIVE }, () => { - if (chrome.runtime.lastError) { - log.info( - 'Error keeping alive:', - chrome.runtime.lastError.message || chrome.runtime.lastError - ); - const err = chrome.runtime.lastError.message || ''; - SW_ALIVE = !err.includes('Receiving end does not exist'); - portReinitialized = SW_ALIVE; - } else { - SW_ALIVE = true; +function swKeepAlive() { + return new Promise((resolve) => { + try { + chrome.runtime.sendMessage( + { message: CONTENT.SW_KEEP_ALIVE }, + () => { + if (chrome.runtime.lastError) { + log.info( + 'Error keeping alive:', + chrome.runtime.lastError.message || + chrome.runtime.lastError + ); + const err = chrome.runtime.lastError.message || ''; + SW_ALIVE = !err.includes( + 'Receiving end does not exist' + ); + portReinitialized = SW_ALIVE; + } else { + SW_ALIVE = true; + } + resolve(); + } + ); + } catch (e) { + let message = `BlockWallet: ${e}`; + if (e.message === EXTENSION_CONTEXT_INVALIDATED_CHROMIUM_ERROR) { + EXTENSION_CONTEXT_VALID = false; + message = `BlockWallet: Please refresh the page. ${e}`; } - }); - }, SW_KEEP_ALIVE_INTERVAL); + log.error(message); + resolve(); + } + }); +} + +async function keepExtensionAlive() { + await swKeepAlive(); + if (EXTENSION_CONTEXT_VALID) { + timeoutRef = setTimeout(keepExtensionAlive, SW_KEEP_ALIVE_INTERVAL); + } +} + +if (isManifestV3()) { + keepExtensionAlive(); } else { SW_ALIVE = true; } @@ -87,8 +121,8 @@ chrome.runtime.sendMessage( //If provider has been overridden by another wallet, then remove connection. providerOverridden ) { - if (isManifestV3() && intervalRef) { - clearInterval(intervalRef); + if (isManifestV3() && timeoutRef) { + clearTimeout(timeoutRef); } port.disconnect(); window.removeEventListener('message', windowListener); diff --git a/packages/ui/src/components/AppVersion.tsx b/packages/ui/src/components/AppVersion.tsx index 5b05f5f3f..163acd602 100644 --- a/packages/ui/src/components/AppVersion.tsx +++ b/packages/ui/src/components/AppVersion.tsx @@ -1,8 +1,12 @@ -const AppVersion = () => - process.env.VERSION ? ( - Version: v{process.env.VERSION} +const AppVersion = () => { + const { VERSION, VERSION_NAME } = process.env + + return VERSION ? ( + + Version: v{[VERSION, VERSION_NAME].filter(Boolean).join(" - ")} + ) : ( DEVELOPMENT ) - +} export default AppVersion diff --git a/packages/ui/webpack/config/env.js b/packages/ui/webpack/config/env.js index cd39c6bf1..99f7d9793 100644 --- a/packages/ui/webpack/config/env.js +++ b/packages/ui/webpack/config/env.js @@ -91,6 +91,7 @@ function getClientEnvironment(publicUrl) { // It is defined here so it is available in the webpackHotDevClient. FAST_REFRESH: process.env.FAST_REFRESH !== "false", VERSION: require("../../../../public/manifest.json").version, + VERSION_NAME: require("../../../../public/manifest.json").version_name, } ) // Stringify all values so we can feed into webpack DefinePlugin diff --git a/public/manifest.json b/public/manifest.json index 8ba52c852..5627d3593 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,20 +1,12 @@ { "author": "BlockWallet", "background": { - "scripts": [ - "hot-reload.js", - "background.js" - ] + "service_worker": "background.js" }, "content_scripts": [ { - "js": [ - "content.js" - ], - "matches": [ - "http://*/*", - "https://*/*" - ], + "js": ["content.js"], + "matches": ["http://*/*", "https://*/*"], "exclude_matches": [ "https://block-wallet.github.io/eth-ledger-bridge-keyring/*", "https://connect.trezor.io/*" @@ -23,40 +15,40 @@ "all_frames": true }, { - "js": [ - "vendor/trezor/trezor-content.js" - ], - "matches": [ - "*://connect.trezor.io/*/popup.html" - ] + "js": ["vendor/trezor/trezor-content.js"], + "matches": ["*://connect.trezor.io/*/popup.html"] } ], - "browser_action": { + "action": { "default_popup": "popup.html", "default_title": "BlockWallet" }, - "description": "The first crypto wallet protecting you on Web3 without any compromises.", + "description": "The most private, non-custodial cryptocurrency wallet", "homepage_url": "https://www.blockwallet.io/", "icons": { "16": "icons/icon-16.png", "48": "icons/icon-48.png", "128": "icons/icon-128.png" }, - "content_security_policy": "script-src 'self'; object-src 'self'", - "manifest_version": 2, + "content_security_policy": { + "extension_pages": "script-src 'self'; object-src 'self'" + }, + "manifest_version": 3, "name": "BlockWallet", "permissions": [ "activeTab", "storage", "notifications", - "https://*.blockwallet.io/*", - "https://*.etherscan.io/*", - "https://*.bscscan.com/*", - "https://*.polygonscan.com/*" + "alarms", + "scripting" ], + "host_permissions": ["file://*/*", "http://*/*", "https://*/*"], "short_name": "BlockWallet", "version": "0.6.0", "web_accessible_resources": [ - "blankProvider.js" + { + "resources": ["blankProvider.js", "keep-alive"], + "matches": [""] + } ] -} \ No newline at end of file +}