From 8d45dc85513dae6a27eaa7f2fbc83aa0d3e62334 Mon Sep 17 00:00:00 2001 From: kyranjamie Date: Tue, 16 May 2023 17:06:07 +0200 Subject: [PATCH] ci: improve set up, upgrades --- .github/workflows/build-extension.yml | 4 - .github/workflows/code-checks.yml | 82 +++++++++-------- .github/workflows/commit-lint.yml | 13 --- .github/workflows/integration-tests.yml | 46 +++++----- .github/workflows/playwright.yml | 2 +- package.json | 4 +- scripts/generate-manifest.js | 43 ++++----- src/app/common/hooks/use-key-actions.ts | 15 ++-- src/app/debug.ts | 2 +- src/app/features/container/container.tsx | 1 - .../use-trigger-ledger-request-keys.ts | 6 -- src/app/index.tsx | 17 +--- src/app/routes/account-gate.tsx | 3 + src/app/routes/hooks/use-on-sign-out.ts | 3 +- src/app/routes/hooks/use-on-tab-closed.ts | 7 +- src/app/routes/hooks/use-on-wallet-lock.ts | 3 +- src/app/store/accounts/account.ts | 2 +- src/app/store/chains/stx-chain.selectors.ts | 17 +++- src/app/store/index.ts | 2 +- src/app/store/keys/key.actions.ts | 25 +++--- src/app/store/session-restore.ts | 72 +++++++++++++++ src/background/background.ts | 54 ++++++----- .../internal-methods/message-handler.ts | 31 ++----- src/shared/crypto/generate-encryption-key.ts | 4 +- src/shared/crypto/mnemonic-encryption.ts | 14 +-- .../crypto/stacks/derive-stacks-accounts.ts | 15 ++++ src/shared/messages.ts | 12 +-- .../{storage.ts => storage/redux-pesist.ts} | 2 +- src/shared/utils/analytics.ts | 2 +- src/shared/utils/get-browser-runtime.ts | 11 +++ .../integration/onboarding/onboarding.spec.ts | 6 +- tests-legacy/page-objects/demo.page.ts | 2 +- tests-legacy/page-objects/fund.page.ts | 2 +- tests-legacy/page-objects/network-page.ts | 2 +- .../page-objects/profile-updating.page.ts | 2 +- .../page-objects/transaction-signing.page.ts | 2 +- tests-legacy/page-objects/wallet.page.ts | 2 +- tests/page-object-models/global.page.ts | 4 +- yarn.lock | 90 +++++++++---------- 39 files changed, 344 insertions(+), 282 deletions(-) delete mode 100644 .github/workflows/commit-lint.yml create mode 100644 src/app/store/session-restore.ts create mode 100644 src/shared/crypto/stacks/derive-stacks-accounts.ts rename src/shared/{storage.ts => storage/redux-pesist.ts} (98%) create mode 100644 src/shared/utils/get-browser-runtime.ts diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index 33940dde..355d6b7b 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -47,8 +47,6 @@ jobs: key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**/package.json') }} - uses: actions/setup-node@v3 - with: - node-version: 14.x - name: Install packages uses: ./.github/actions/provision @@ -83,8 +81,6 @@ jobs: - name: Set Node Version uses: actions/setup-node@v3 - with: - node-version: 14.x - name: Restore cache uses: actions/cache@v3 diff --git a/.github/workflows/code-checks.yml b/.github/workflows/code-checks.yml index e7c4cdae..386da85d 100644 --- a/.github/workflows/code-checks.yml +++ b/.github/workflows/code-checks.yml @@ -33,23 +33,15 @@ jobs: - name: File name checker run: yarn lint:filename - audit: + lint-commit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: ./.github/actions/provision + with: + fetch-depth: 0 - - name: Audit - run: yarn audit-ci --high --skip-dev - - typecheck: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: ./.github/actions/provision - - - name: Typecheck - run: yarn typecheck + - name: Lint commit message + uses: wagoid/commitlint-github-action@v4 lint-deps: runs-on: ubuntu-latest @@ -60,27 +52,6 @@ jobs: - name: Lint dependency rules run: yarn lint:deps - locked-versions: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: ./.github/actions/provision - - - name: Check exact versions - uses: ./.github/actions/check-version-lock - - test-unit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: ./.github/actions/provision - - - name: Build - run: yarn build - - - name: Test - run: yarn test:unit - lint-message-schema: runs-on: ubuntu-latest steps: @@ -112,7 +83,34 @@ jobs: - run: yarn web-ext lint - build: + locked-versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/provision + + - name: Check exact versions + uses: ./.github/actions/check-version-lock + + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/provision + + - name: Audit + run: yarn audit-ci --high --skip-dev + + typecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/provision + + - name: Typecheck + run: yarn typecheck + + test-unit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -121,6 +119,18 @@ jobs: - name: Build run: yarn build - - name: Build extension + - name: Test + run: yarn test:unit + + test-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/provision + + - name: Build + run: yarn build + + - name: Package extension run: sh build-ext.sh shell: bash diff --git a/.github/workflows/commit-lint.yml b/.github/workflows/commit-lint.yml deleted file mode 100644 index 873e00d2..00000000 --- a/.github/workflows/commit-lint.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Commit lint - -on: [pull_request] - -jobs: - commitlint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - uses: wagoid/commitlint-github-action@v4 diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 3551e6a5..0b04495f 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -10,12 +10,12 @@ jobs: directories: runs-on: ubuntu-latest outputs: - dir: ${{ steps.set-dirs.outputs.dir }} + dir: ${{ steps.set-dirs.outputs.TEST_DIRECTORIES }} steps: - uses: actions/checkout@v3 - id: set-dirs working-directory: ./tests-legacy/integration - run: echo "::set-output name=dir::$( ls -d */ | xargs -0 | sed 's/\///' | jq -R -s -c 'split("\n")[:-2]')" + run: echo "TEST_DIRECTORIES=$( ls -d */ | xargs -0 | sed 's/\///' | jq -R -s -c 'split("\n")[:-2]')" >> $GITHUB_OUTPUT test-integration: runs-on: ubuntu-latest @@ -28,33 +28,37 @@ jobs: - name: Check out Git repository uses: actions/checkout@v3 - - run: echo ${{ fromJson(needs.directories.outputs.dir) }} - - run: echo ${{ matrix.dir }} + - name: Get installed Playwright version + id: playwright-version + run: echo "PLAYWRIGHT_VERSION=$(node -e "console.log(require('./package.json').devDependencies['@playwright/test'])")" >> $GITHUB_ENV - uses: actions/cache@v3 - name: Cache node_modules - id: cache-node-modules + id: cache with: path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**/package.json') }} + key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**/package.json') }}-${{ env.PLAYWRIGHT_VERSION }} - - uses: actions/cache@v3 - name: Cache playwright - id: cache-playwright-browsers + - name: Install dependencies + if: steps.cache.outputs.cache-hit != 'true' + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + run: yarn --frozen-lockfile + shell: bash + + - name: Cache playwright binaries + uses: actions/cache@v3 + id: playwright-cache with: - path: '~/.cache/ms-playwright' - key: ${{ runner.os }}-playwright-browser + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-cache-${{ env.PLAYWRIGHT_VERSION }} - - uses: actions/setup-node@v3 - with: - node-version: 18 + - name: Install Playwright browsers + run: yarn playwright install chrome + if: steps.playwright-cache.outputs.cache-hit != 'true' - - name: Install packages - uses: ./.github/actions/provision - if: steps.cache-node-modules.outputs.cache-hit != 'true' - - - name: Install Playwright dependencies - run: npx playwright install --with-deps + - name: Install Playwright deps + run: yarn playwright install-deps + if: steps.playwright-cache.outputs.cache-hit != 'true' - name: Build assets run: yarn build:test diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 5374298d..25c2b517 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -52,7 +52,7 @@ jobs: key: ${{ runner.os }}-playwright-cache-${{ env.PLAYWRIGHT_VERSION }} - name: Install Playwright browsers - run: yarn playwright install chrome + run: yarn playwright install chrome chromium if: steps.playwright-cache.outputs.cache-hit != 'true' - name: Install Playwright deps diff --git a/package.json b/package.json index 59260da7..1b385f76 100644 --- a/package.json +++ b/package.json @@ -272,7 +272,7 @@ "@types/webpack": "5.28.1", "@types/zxcvbn": "4.4.1", "@typescript-eslint/parser": "5.59.6", - "@vitest/coverage-istanbul": "0.31.0", + "@vitest/coverage-istanbul": "0.31.1", "audit-ci": "6.6.1", "babel-loader": "9.1.2", "base64-loader": "1.0.0", @@ -317,7 +317,7 @@ "ts-unused-exports": "7.0.3", "tsconfig-paths-webpack-plugin": "4.0.1", "typescript": "5.0.4", - "vitest": "0.31.0", + "vitest": "0.31.1", "vm-browserify": "1.1.2", "web-ext": "7.4.0", "web-ext-submit": "7.4.0", diff --git a/scripts/generate-manifest.js b/scripts/generate-manifest.js index 676945f9..cd834cfd 100644 --- a/scripts/generate-manifest.js +++ b/scripts/generate-manifest.js @@ -32,7 +32,7 @@ const contentSecurityPolicyEnvironment = { development: "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; frame-src 'none'; frame-ancestors 'none';", production: - "default-src 'none'; connect-src *; style-src 'unsafe-inline'; img-src 'self' https:; script-src 'self' 'wasm-unsafe-eval'; object-src 'none'; frame-src 'none'; frame-ancestors 'none';", + "default-src 'none'; connect-src *; style-src 'unsafe-inline'; img-src 'self' data: https:; script-src 'self' 'wasm-unsafe-eval'; object-src 'none'; frame-src 'none'; frame-ancestors 'none';", }; const defaultIconEnvironment = { @@ -42,18 +42,9 @@ const defaultIconEnvironment = { const browserSpecificConfig = { firefox: { - manifest_version: 2, - permissions: ['contextMenus', 'storage', '*://*/*'], background: { - page: 'background.js', + scripts: ['background.js'], }, - web_accessible_resources: ['inpage.js'], - browser_action: { - default_title: 'Stacks', - default_popup: 'popup.html', - default_icon: defaultIconEnvironment[NODE_ENV], - }, - content_security_policy: contentSecurityPolicyEnvironment[NODE_ENV], browser_specific_settings: { gecko: { id: '{e22ae397-03d7-4622-bd8f-ecaca8c9b277}', @@ -61,21 +52,9 @@ const browserSpecificConfig = { }, }, chromium: { - manifest_version: 3, - host_permissions: ['*://*/*'], - permissions: ['contextMenus', 'storage'], background: { service_worker: 'background.js', }, - web_accessible_resources: [{ resources: ['inpage.js'], matches: ['*://*/*'] }], - action: { - default_title: 'Stacks', - default_popup: 'popup.html', - default_icon: defaultIconEnvironment[NODE_ENV], - }, - content_security_policy: { - extension_pages: contentSecurityPolicyEnvironment[NODE_ENV], - }, }, }; @@ -83,9 +62,11 @@ const browserSpecificConfig = { * @type {Manifest} manifest */ const manifest = { + manifest_version: 3, author: 'Hiro PBC', description: 'Hiro Wallet is a safe way to manage your STX, sign into apps, and protect your funds while interacting with Clarity smart contracts.', + permissions: ['contextMenus', 'storage'], commands: { _execute_browser_action: { suggested_key: { @@ -95,6 +76,16 @@ const manifest = { description: 'Opens Stacks App', }, }, + host_permissions: ['*://*/*'], + content_security_policy: { + extension_pages: contentSecurityPolicyEnvironment[NODE_ENV], + }, + web_accessible_resources: [{ resources: ['inpage.js'], matches: ['*://*/*'] }], + action: { + default_title: 'Stacks', + default_popup: 'popup.html', + default_icon: defaultIconEnvironment[NODE_ENV], + }, options_ui: { page: 'index.html', open_in_tab: true, @@ -116,12 +107,8 @@ const name = PREVIEW_RELEASE ? 'Hiro Wallet Preview' : 'Hiro Wallet'; const prodManifest = { name, - // CSP loosened to allow `wasm-eval` per - // https://bugs.chromium.org/p/chromium/issues/detail?id=1268576 - content_security_policy: - "default-src 'none'; connect-src *; style-src 'unsafe-inline'; img-src 'self' data: https:; script-src 'self' 'wasm-eval'; object-src 'none'; frame-src 'none'; frame-ancestors 'none';", icons: generateImageAssetUrlsWithSuffix(PREVIEW_RELEASE ? '-preview' : ''), - browser_action: { + action: { default_icon: `assets/connect-logo/Stacks128w${PREVIEW_RELEASE ? '-preview' : ''}.png`, }, }; diff --git a/src/app/common/hooks/use-key-actions.ts b/src/app/common/hooks/use-key-actions.ts index d8a26585..d7e659cd 100644 --- a/src/app/common/hooks/use-key-actions.ts +++ b/src/app/common/hooks/use-key-actions.ts @@ -3,9 +3,7 @@ import { useMemo } from 'react'; import { generateSecretKey } from '@stacks/wallet-sdk'; import { logger } from '@shared/logger'; -import { InternalMethods } from '@shared/message-types'; -import { sendMessage } from '@shared/messages'; -import { clearChromeStorage } from '@shared/storage'; +import { clearChromeStorage } from '@shared/storage/redux-pesist'; import { queryClient } from '@app/common/persistence'; import { partiallyClearLocalStorage } from '@app/common/store-utils'; @@ -15,6 +13,7 @@ import { useBitcoinClient, useStacksClientAnchored } from '@app/store/common/api import { inMemoryKeyActions } from '@app/store/in-memory-key/in-memory-key.actions'; import { keyActions } from '@app/store/keys/key.actions'; import { useCurrentKeyDetails } from '@app/store/keys/key.selectors'; +import { clearWalletSession } from '@app/store/session-restore'; import { useAnalytics } from './analytics/use-analytics'; @@ -37,10 +36,6 @@ export function useKeyActions() { return; } const secretKey = generateSecretKey(256); - sendMessage({ - method: InternalMethods.ShareInMemoryKeyToBackground, - payload: { secretKey, keyId: 'default' }, - }); return dispatch(inMemoryKeyActions.generateWalletKey(secretKey)); }, @@ -57,7 +52,7 @@ export function useKeyActions() { }, async signOut() { - sendMessage({ method: InternalMethods.RemoveInMemoryKeys, payload: undefined }); + await clearWalletSession(); dispatch(keyActions.signOut()); await clearChromeStorage(); partiallyClearLocalStorage(); @@ -65,8 +60,8 @@ export function useKeyActions() { queryClient.clear(); }, - lockWallet() { - sendMessage({ method: InternalMethods.RemoveInMemoryKeys, payload: undefined }); + async lockWallet() { + await clearWalletSession(); return dispatch(inMemoryKeyActions.lockWallet()); }, }), diff --git a/src/app/debug.ts b/src/app/debug.ts index 4357a1aa..10dceaa5 100644 --- a/src/app/debug.ts +++ b/src/app/debug.ts @@ -3,7 +3,7 @@ import toast from 'react-hot-toast'; import * as reduxPersist from 'redux-persist'; import { getLogsFromBrowserStorage } from '@shared/logger-storage'; -import { persistConfig } from '@shared/storage'; +import { persistConfig } from '@shared/storage/redux-pesist'; import { store } from './store'; import { stxChainSlice } from './store/chains/stx-chain.slice'; diff --git a/src/app/features/container/container.tsx b/src/app/features/container/container.tsx index ab8a39d2..8afe4b00 100644 --- a/src/app/features/container/container.tsx +++ b/src/app/features/container/container.tsx @@ -37,7 +37,6 @@ export function Container() { - diff --git a/src/app/features/ledger/flows/stacks-request-keys/use-trigger-ledger-request-keys.ts b/src/app/features/ledger/flows/stacks-request-keys/use-trigger-ledger-request-keys.ts index 6e1b7cd0..002daac7 100644 --- a/src/app/features/ledger/flows/stacks-request-keys/use-trigger-ledger-request-keys.ts +++ b/src/app/features/ledger/flows/stacks-request-keys/use-trigger-ledger-request-keys.ts @@ -3,8 +3,6 @@ import toast from 'react-hot-toast'; import { useDispatch } from 'react-redux'; import { useNavigate } from 'react-router-dom'; -import { InternalMethods } from '@shared/message-types'; -import { sendMessage } from '@shared/messages'; import { RouteUrls } from '@shared/route-urls'; import { keySlice } from '@app/store/keys/key.slice'; @@ -29,10 +27,6 @@ export function useTriggerLedgerDeviceRequestStacksKeys() { publicKeys, }) ); - // It's possible a user may have first generated a key, then decided - // they wanted to pair with Ledger. Here, we kill all in memory keys when - // a new Ledger wallet is created - sendMessage({ method: InternalMethods.RemoveInMemoryKeys, payload: undefined }); navigate(RouteUrls.Home); }, }), diff --git a/src/app/index.tsx b/src/app/index.tsx index 8959c40a..6708a8fe 100755 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -1,15 +1,13 @@ import { createRoot } from 'react-dom/client'; -import { InternalMethods } from '@shared/message-types'; import { initSentry } from '@shared/utils/analytics'; import { warnUsersAboutDevToolsDangers } from '@shared/utils/dev-tools-warning-log'; import { persistAndRenderApp } from '@app/common/persistence'; +import { restoreWalletSession } from '@app/store/session-restore'; import { App } from './app'; import { setDebugOnGlobal } from './debug'; -import { store } from './store'; -import { inMemoryKeyActions } from './store/in-memory-key/in-memory-key.actions'; initSentry(); warnUsersAboutDevToolsDangers(); @@ -23,19 +21,8 @@ declare global { window.__APP_VERSION__ = VERSION; -async function checkForInMemoryKeys() { - return new Promise(resolve => - chrome.runtime.sendMessage({ method: InternalMethods.RequestInMemoryKeys }, resp => { - if (!resp) return resolve(true); - if (Object.keys(resp).length === 0) return resolve(true); - store.dispatch(inMemoryKeyActions.setKeysInMemory(resp)); - resolve(true); - }) - ); -} - async function renderApp() { - await checkForInMemoryKeys(); + await restoreWalletSession(); const container = document.getElementById('app'); return createRoot(container!).render(); } diff --git a/src/app/routes/account-gate.tsx b/src/app/routes/account-gate.tsx index f5ed8ae0..3de52f7a 100644 --- a/src/app/routes/account-gate.tsx +++ b/src/app/routes/account-gate.tsx @@ -21,6 +21,9 @@ export function AccountGate({ children }: AccountGateProps) { const currentKeyDetails = useCurrentKeyDetails(); const currentInMemorySecretKey = useDefaultWalletSecretKey(); + // console.log('currentKeyDetails', currentKeyDetails); + // console.log('currentInMemorySecretKey', currentInMemorySecretKey); + if (currentKeyDetails?.type === 'ledger') return <>{children}; if (shouldNavigateToOnboardingStartPage(currentKeyDetails)) diff --git a/src/app/routes/hooks/use-on-sign-out.ts b/src/app/routes/hooks/use-on-sign-out.ts index 178a9d97..d4638f49 100644 --- a/src/app/routes/hooks/use-on-sign-out.ts +++ b/src/app/routes/hooks/use-on-sign-out.ts @@ -3,8 +3,9 @@ import { keyActions } from '@app/store/keys/key.actions'; export function useOnSignOut(handler: () => void) { useOnMount(() => { - chrome.runtime.onMessage.addListener(message => { + chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (message?.method === keyActions.signOut.type) handler(); + sendResponse(); }); }); } diff --git a/src/app/routes/hooks/use-on-tab-closed.ts b/src/app/routes/hooks/use-on-tab-closed.ts index a9073ac0..27b12ff3 100644 --- a/src/app/routes/hooks/use-on-tab-closed.ts +++ b/src/app/routes/hooks/use-on-tab-closed.ts @@ -11,12 +11,17 @@ export function useOnOriginTabClose(handler: () => void) { const analytics = useAnalytics(); useEffect(() => { - const messageHandler = (message: BackgroundMessages) => { + const messageHandler = ( + message: BackgroundMessages, + _sender: chrome.runtime.MessageSender, + sendResponse: () => void + ) => { if (message.method !== InternalMethods.OriginatingTabClosed) return; if (message.payload.tabId === tabId) { handler(); void analytics.track('requesting_origin_tab_closed_with_pending_action'); } + sendResponse(); }; chrome.runtime.onMessage.addListener(messageHandler); diff --git a/src/app/routes/hooks/use-on-wallet-lock.ts b/src/app/routes/hooks/use-on-wallet-lock.ts index 568faf37..6509ac68 100644 --- a/src/app/routes/hooks/use-on-wallet-lock.ts +++ b/src/app/routes/hooks/use-on-wallet-lock.ts @@ -3,8 +3,9 @@ import { inMemoryKeyActions } from '@app/store/in-memory-key/in-memory-key.actio export function useOnWalletLock(handler: () => void) { useOnMount(() => { - chrome.runtime.onMessage.addListener(message => { + chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (message?.method === inMemoryKeyActions.lockWallet.type) handler(); + sendResponse(); }); }); } diff --git a/src/app/store/accounts/account.ts b/src/app/store/accounts/account.ts index 8e774b7a..4f75fa62 100644 --- a/src/app/store/accounts/account.ts +++ b/src/app/store/accounts/account.ts @@ -6,7 +6,7 @@ import { selectCurrentAccountIndex } from '../keys/key.selectors'; // This is only used when there is a pending transaction request and // the user switches accounts during the signing process -export const hasSwitchedAccountsState = atom(false); +export const hasSwitchedAccountsState = atom(false); const hasCreatedAccountState = atom(false); diff --git a/src/app/store/chains/stx-chain.selectors.ts b/src/app/store/chains/stx-chain.selectors.ts index f89c4a86..3ab8dff2 100644 --- a/src/app/store/chains/stx-chain.selectors.ts +++ b/src/app/store/chains/stx-chain.selectors.ts @@ -1,19 +1,30 @@ import { Wallet } from '@stacks/wallet-sdk'; import memoize from 'promise-memoize'; +import { deriveStacksAccounts } from '@shared/crypto/stacks/derive-stacks-accounts'; import { InternalMethods } from '@shared/message-types'; import { RequestDerivedStxAccounts } from '@shared/messages'; +import { delay } from '@app/common/utils'; import { RootState } from '@app/store'; export const selectStacksChain = (state: RootState) => state.chains.stx; -export const deriveWalletWithAccounts = memoize( +const requestDerivedStacksAccountFromBackground = memoize(async () => { async (secretKey: string, highestAccountIndex: number): Promise => { const message: RequestDerivedStxAccounts = { method: InternalMethods.RequestDerivedStxAccounts, payload: { secretKey, highestAccountIndex }, }; return new Promise(resolve => chrome.runtime.sendMessage(message, resp => resolve(resp))); - } -); + }; +}); + +export async function deriveWalletWithAccounts(secretKey: string, highestAccountIndex: number) { + const cachedResult = await Promise.race([ + requestDerivedStacksAccountFromBackground(), + delay(1000), + ]); + if (cachedResult) return cachedResult as Wallet; + return deriveStacksAccounts(secretKey, highestAccountIndex); +} diff --git a/src/app/store/index.ts b/src/app/store/index.ts index e376a6cc..3fbe868d 100644 --- a/src/app/store/index.ts +++ b/src/app/store/index.ts @@ -16,7 +16,7 @@ import { import { PersistPartial } from 'redux-persist/es/persistReducer'; import { IS_DEV_ENV } from '@shared/environment'; -import { persistConfig } from '@shared/storage'; +import { persistConfig } from '@shared/storage/redux-pesist'; import { analyticsSlice } from './analytics/analytics.slice'; import { appPermissionsSlice } from './app-permissions/app-permissions.slice'; diff --git a/src/app/store/keys/key.actions.ts b/src/app/store/keys/key.actions.ts index 1facbb15..b396f92e 100644 --- a/src/app/store/keys/key.actions.ts +++ b/src/app/store/keys/key.actions.ts @@ -2,8 +2,6 @@ import { AddressVersion } from '@stacks/transactions'; import { decryptMnemonic, encryptMnemonic } from '@shared/crypto/mnemonic-encryption'; import { logger } from '@shared/logger'; -import { InternalMethods } from '@shared/message-types'; -import { sendMessage } from '@shared/messages'; import { recurseAccountsForActivity } from '@app/common/account-restoration/account-restore'; import { checkForLegacyGaiaConfigWithKnownGeneratedAccountIndex } from '@app/common/account-restoration/legacy-gaia-config-lookup'; @@ -11,6 +9,7 @@ import { BitcoinClient } from '@app/query/bitcoin/bitcoin-client'; import { fetchNamesForAddress } from '@app/query/stacks/bns/bns.utils'; import { StacksClient } from '@app/query/stacks/stacks-client'; import { AppThunk } from '@app/store'; +import { initalizeWalletSession } from '@app/store/session-restore'; import { getNativeSegwitMainnetAddressFromMnemonic } from '../accounts/blockchain/bitcoin/bitcoin-keychain'; import { getStacksAddressByIndex } from '../accounts/blockchain/stacks/stacks-keychain'; @@ -30,7 +29,13 @@ function setWalletEncryptionPassword(args: { return async (dispatch, getState) => { const secretKey = selectDefaultWalletKey(getState()); if (!secretKey) throw new Error('Cannot generate wallet without first having generated a key'); - const { encryptedSecretKey, salt } = await encryptMnemonic({ secretKey, password }); + + const { encryptedSecretKey, salt, encryptionKey } = await encryptMnemonic({ + secretKey, + password, + }); + + await initalizeWalletSession(encryptionKey, secretKey); const legacyAccountActivityLookup = await checkForLegacyGaiaConfigWithKnownGeneratedAccountIndex(secretKey); @@ -76,12 +81,6 @@ function setWalletEncryptionPassword(args: { dispatch(stxChainSlice.actions.restoreAccountIndex(recursiveActivityIndex)); }); - sendMessage({ - method: InternalMethods.ShareInMemoryKeyToBackground, - payload: { secretKey, keyId: defaultKeyId }, - }); - - dispatch(inMemoryKeySlice.actions.setKeysInMemory({ default: secretKey })); dispatch( keySlice.actions.createStacksSoftwareWalletComplete({ type: 'software', @@ -100,11 +99,9 @@ function unlockWalletAction(password: string): AppThunk { const currentKey = selectCurrentKey(getState()); if (!currentKey) return; if (currentKey.type !== 'software') return; - const { secretKey } = await decryptMnemonic({ password, ...currentKey }); - sendMessage({ - method: InternalMethods.ShareInMemoryKeyToBackground, - payload: { secretKey: secretKey, keyId: defaultKeyId }, - }); + const { secretKey, encryptionKey } = await decryptMnemonic({ password, ...currentKey }); + + await initalizeWalletSession(encryptionKey, secretKey); dispatch(inMemoryKeySlice.actions.setKeysInMemory({ default: secretKey })); }; } diff --git a/src/app/store/session-restore.ts b/src/app/store/session-restore.ts new file mode 100644 index 00000000..07ddd059 --- /dev/null +++ b/src/app/store/session-restore.ts @@ -0,0 +1,72 @@ +import { decrypt } from '@stacks/wallet-sdk'; + +import { InternalMethods } from '@shared/message-types'; +import { sendMessage } from '@shared/messages'; +import { whenBrowserRuntime } from '@shared/utils/get-browser-runtime'; + +import { store } from '@app/store'; +import { inMemoryKeyActions } from '@app/store/in-memory-key/in-memory-key.actions'; +import { selectCurrentKey } from '@app/store/keys/key.selectors'; +import { defaultKeyId } from '@app/store/keys/key.slice'; + +export async function initalizeWalletSession(encryptionKey: string, secretKey: string) { + return await whenBrowserRuntime({ + async chromium() { + return chrome.storage.session.set({ encryptionKey }); + }, + async firefox() { + return sendMessage({ + method: InternalMethods.ShareInMemoryKeyToBackground, + payload: { secretKey, keyId: defaultKeyId }, + }); + }, + })(); +} + +export async function clearWalletSession() { + return await whenBrowserRuntime({ + async chromium() { + return chrome.storage.session.remove('encryptionKey'); + }, + async firefox() { + return chrome.runtime.sendMessage({ method: InternalMethods.RemoveInMemoryKeys }); + }, + })(); +} + +export async function restoreWalletSession() { + return whenBrowserRuntime({ + async chromium() { + const key = await chrome.storage.session.get(['encryptionKey']); + if (!key.encryptionKey) return false; + + try { + const currentKey = selectCurrentKey(store.getState()); + + if (currentKey?.type === 'software') { + const secretKey = await decrypt(currentKey.encryptedSecretKey, key.encryptionKey); + store.dispatch(inMemoryKeyActions.setKeysInMemory({ default: secretKey })); + return true; + } + } catch (e) { + return false; + } + + return false; + }, + async firefox() { + return checkForInMemoryKeys(); + }, + })(); +} + +async function checkForInMemoryKeys() { + return new Promise(resolve => + chrome.runtime.sendMessage({ method: InternalMethods.RequestInMemoryKeys }, resp => { + if (!resp) resolve(false); + if (Object.keys(resp).length === 0) return resolve(false); + store.dispatch(inMemoryKeyActions.setKeysInMemory(resp)); + resolve(true); + }) + ); +} diff --git a/src/background/background.ts b/src/background/background.ts index edd7e29c..c83d9a19 100755 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -21,10 +21,6 @@ initSentry(); initContextMenuActions(); warnUsersAboutDevToolsDangers(); -const keepAlive = () => setInterval(async () => await chrome.runtime.getPlatformInfo(), 20_000); -chrome.runtime.onStartup.addListener(keepAlive); -keepAlive(); - const IS_TEST_ENV = process.env.TEST_ENV === 'true'; chrome.runtime.onInstalled.addListener(async details => { @@ -69,34 +65,34 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { return true; }); -const storageArea = chrome.storage.local as chrome.storage.LocalStorageArea; +// const storageArea = chrome.storage.local as chrome.storage.LocalStorageArea; -const testIntervalMs = 10000; -const storageWaitTimeMs = 100; +// const testIntervalMs = 10000; +// const storageWaitTimeMs = 100; // @see https://bugs.chromium.org/p/chromium/issues/detail?id=1316588 -async function hasChromiumIssue1316588() { - return new Promise(resolve => { - let dispatched = false; - const testEventDispatching = () => { - storageArea.onChanged.removeListener(testEventDispatching); - dispatched = true; - }; - storageArea.onChanged.addListener(testEventDispatching); - void storageArea.set({ testEventDispatching: Math.random() }); - setTimeout(() => resolve(!dispatched), storageWaitTimeMs); - }); -} +// async function hasChromiumIssue1316588() { +// return new Promise(resolve => { +// let dispatched = false; +// const testEventDispatching = () => { +// storageArea.onChanged.removeListener(testEventDispatching); +// dispatched = true; +// }; +// storageArea.onChanged.addListener(testEventDispatching); +// void storageArea.set({ testEventDispatching: Math.random() }); +// setTimeout(() => resolve(!dispatched), storageWaitTimeMs); +// }); +// } -function fixChromiumIssue1316588() { - void hasChromiumIssue1316588().then(hasIssue => { - if (hasIssue) { - chrome.runtime.reload(); - return; - } +// function fixChromiumIssue1316588() { +// void hasChromiumIssue1316588().then(hasIssue => { +// if (hasIssue) { +// chrome.runtime.reload(); +// return; +// } - setTimeout(fixChromiumIssue1316588, testIntervalMs); - }); -} +// setTimeout(fixChromiumIssue1316588, testIntervalMs); +// }); +// } -fixChromiumIssue1316588(); +// fixChromiumIssue1316588(); diff --git a/src/background/messaging/internal-methods/message-handler.ts b/src/background/messaging/internal-methods/message-handler.ts index e9b94bc7..2f5bf342 100644 --- a/src/background/messaging/internal-methods/message-handler.ts +++ b/src/background/messaging/internal-methods/message-handler.ts @@ -1,7 +1,4 @@ -// import { StacksMainnet } from '@stacks/network'; -import { generateNewAccount, generateWallet } from '@stacks/wallet-sdk'; -import memoize from 'promise-memoize'; - +import { deriveStacksAccountsMemoized } from '@shared/crypto/stacks/derive-stacks-accounts'; import { logger } from '@shared/logger'; import { InternalMethods } from '@shared/message-types'; import { BackgroundMessages } from '@shared/messages'; @@ -11,18 +8,6 @@ function validateMessagesAreFromExtension(sender: chrome.runtime.MessageSender) return sender.url?.startsWith(chrome.runtime.getURL('')); } -const deriveWalletWithAccounts = memoize(async (secretKey: string, highestAccountIndex: number) => { - // Here we only want the resulting `Wallet` objects, but the API - // requires a password (so it can also return an encrypted key) - const wallet = await generateWallet({ secretKey, password: '' }); - let walWithAccounts = wallet; - for (let i = 0; i < highestAccountIndex; i++) { - walWithAccounts = generateNewAccount(walWithAccounts); - } - return walWithAccounts; -}); - -// Persists keys in memory for the duration of the background scripts life const inMemoryKeys = new Map(); const inMemoryFormState = new Map(); @@ -35,17 +20,19 @@ export async function internalBackgroundMessageHandler( sender: chrome.runtime.MessageSender, sendResponse: (response?: any) => void ) { - // console.info('Internal msg', message, validateMessagesAreFromExtension(sender)); if (!validateMessagesAreFromExtension(sender)) { logger.error('Error: Received background script msg from ' + sender.url); sendResponse(); return; } - // logger.debug('Internal message', message); + logger.debug('Internal message', message); switch (message.method) { case InternalMethods.RequestDerivedStxAccounts: { const { secretKey, highestAccountIndex } = message.payload; - const walletsWithAccounts = await deriveWalletWithAccounts(secretKey, highestAccountIndex); + const walletsWithAccounts = await deriveStacksAccountsMemoized( + secretKey, + highestAccountIndex + ); sendResponse(walletsWithAccounts); break; } @@ -59,7 +46,6 @@ export async function internalBackgroundMessageHandler( case InternalMethods.RequestInMemoryKeys: { sendResponse(Object.fromEntries(inMemoryKeys)); - sendResponse(); break; } @@ -71,11 +57,13 @@ export async function internalBackgroundMessageHandler( case InternalMethods.SetActiveFormState: { const { tabId, ...state } = message.payload; inMemoryFormState.set(tabId, state); + sendResponse(); break; } case InternalMethods.ClearActiveFormState: { inMemoryFormState.delete(message.payload.tabId); + sendResponse(); break; } @@ -85,7 +73,4 @@ export async function internalBackgroundMessageHandler( break; } } - // As browser is instructed that, if there is a response it will be - // asyncronous, we must always return a response, even if empty - sendResponse(); } diff --git a/src/shared/crypto/generate-encryption-key.ts b/src/shared/crypto/generate-encryption-key.ts index f022cf54..ff38783d 100644 --- a/src/shared/crypto/generate-encryption-key.ts +++ b/src/shared/crypto/generate-encryption-key.ts @@ -1,11 +1,11 @@ import { WorkerScript, createWorker } from '../workers'; const worker = createWorker(WorkerScript.DecryptionWorker); -interface GenerateEncryptionKeyArgs { +interface DeriveEncryptionKeyArgs { password: string; salt: string; } -export async function generateEncryptionKey(args: GenerateEncryptionKeyArgs): Promise { +export async function deriveEncryptionKey(args: DeriveEncryptionKeyArgs): Promise { return new Promise(resolve => { const handler = (e: MessageEvent) => resolve(e.data); worker.addEventListener('message', handler); diff --git a/src/shared/crypto/mnemonic-encryption.ts b/src/shared/crypto/mnemonic-encryption.ts index a6ca2b08..7d509f0b 100644 --- a/src/shared/crypto/mnemonic-encryption.ts +++ b/src/shared/crypto/mnemonic-encryption.ts @@ -1,7 +1,7 @@ import { bytesToHex } from '@stacks/common'; import { decrypt, encrypt } from '@stacks/wallet-sdk'; -import { generateEncryptionKey } from './generate-encryption-key'; +import { deriveEncryptionKey } from './generate-encryption-key'; import { generateRandomHexString } from './generate-random-hex'; interface EncryptMnemonicArgs { @@ -10,11 +10,12 @@ interface EncryptMnemonicArgs { } export async function encryptMnemonic({ secretKey, password }: EncryptMnemonicArgs) { const salt = generateRandomHexString(); - const argonHash = await generateEncryptionKey({ password, salt }); - const encryptedBuffer = await encrypt(secretKey, argonHash); + const encryptionKey = await deriveEncryptionKey({ password, salt }); + const encryptedBuffer = await encrypt(secretKey, encryptionKey); return { salt, encryptedSecretKey: bytesToHex(encryptedBuffer), + encryptionKey, }; } @@ -36,14 +37,16 @@ export async function decryptMnemonic({ encryptedSecretKey: string; salt: string; secretKey: string; + encryptionKey: string; }> { if (salt) { - const pw = await generateEncryptionKey({ password, salt }); - const secretKey = await decrypt(Buffer.from(encryptedSecretKey, 'hex'), pw); + const encryptionKey = await deriveEncryptionKey({ password, salt }); + const secretKey = await decrypt(Buffer.from(encryptedSecretKey, 'hex'), encryptionKey); return { secretKey, encryptedSecretKey, salt, + encryptionKey, }; } else { // if there is no salt, decrypt the secret key, then re-encrypt with an argon2 hash @@ -53,6 +56,7 @@ export async function decryptMnemonic({ secretKey, encryptedSecretKey: newEncryptedKey.encryptedSecretKey, salt: newEncryptedKey.salt, + encryptionKey: newEncryptedKey.encryptedSecretKey, }; } } diff --git a/src/shared/crypto/stacks/derive-stacks-accounts.ts b/src/shared/crypto/stacks/derive-stacks-accounts.ts new file mode 100644 index 00000000..5563343c --- /dev/null +++ b/src/shared/crypto/stacks/derive-stacks-accounts.ts @@ -0,0 +1,15 @@ +import { generateNewAccount, generateWallet } from '@stacks/wallet-sdk'; +import memoize from 'promise-memoize'; + +export async function deriveStacksAccounts(secretKey: string, highestAccountIndex: number) { + // Here we only want the resulting `Wallet` objects, but the API + // requires a password (so it can also return an encrypted key) + const wallet = await generateWallet({ secretKey, password: '' }); + let walWithAccounts = wallet; + for (let i = 0; i < highestAccountIndex; i++) { + walWithAccounts = generateNewAccount(walWithAccounts); + } + return walWithAccounts; +} + +export const deriveStacksAccountsMemoized = memoize(deriveStacksAccounts); diff --git a/src/shared/messages.ts b/src/shared/messages.ts index 8ac8091d..44ba5798 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -25,14 +25,14 @@ type ClearActiveFormState = BackgroundMessage< { tabId: number } >; -type ShareInMemoryKeyToBackground = BackgroundMessage< +type FirefoxShareInMemoryKeyToBackground = BackgroundMessage< InternalMethods.ShareInMemoryKeyToBackground, { secretKey: string; keyId: string } >; -type RequestInMemoryKeys = BackgroundMessage; +type FirefoxRequestInMemoryKeys = BackgroundMessage; -type RemoveInMemoryKeys = BackgroundMessage; +type FirefoxRemoveInMemoryKeys = BackgroundMessage; type OriginatingTabClosed = BackgroundMessage< InternalMethods.OriginatingTabClosed, @@ -44,9 +44,9 @@ export type BackgroundMessages = | GetActiveFormState | SetActiveFormState | ClearActiveFormState - | ShareInMemoryKeyToBackground - | RequestInMemoryKeys - | RemoveInMemoryKeys + | FirefoxShareInMemoryKeyToBackground + | FirefoxRequestInMemoryKeys + | FirefoxRemoveInMemoryKeys | OriginatingTabClosed; export function sendMessage(message: BackgroundMessages) { diff --git a/src/shared/storage.ts b/src/shared/storage/redux-pesist.ts similarity index 98% rename from src/shared/storage.ts rename to src/shared/storage/redux-pesist.ts index 8e43bed0..7561ec3c 100644 --- a/src/shared/storage.ts +++ b/src/shared/storage/redux-pesist.ts @@ -2,7 +2,7 @@ import { PersistConfig, createMigrate, getStoredState } from 'redux-persist'; import type { RootState } from '@app/store'; -import { analytics } from './utils/analytics'; +import { analytics } from '../utils/analytics'; export async function clearChromeStorage(): Promise { return new Promise(resolve => chrome.storage.local.clear(resolve)); diff --git a/src/shared/utils/analytics.ts b/src/shared/utils/analytics.ts index 70a75c37..ff3a073d 100644 --- a/src/shared/utils/analytics.ts +++ b/src/shared/utils/analytics.ts @@ -8,7 +8,7 @@ import { SENTRY_DSN, WALLET_ENVIRONMENT, } from '@shared/environment'; -import { persistConfig } from '@shared/storage'; +import { persistConfig } from '@shared/storage/redux-pesist'; import type { RootState } from '@app/store'; diff --git a/src/shared/utils/get-browser-runtime.ts b/src/shared/utils/get-browser-runtime.ts new file mode 100644 index 00000000..98df6829 --- /dev/null +++ b/src/shared/utils/get-browser-runtime.ts @@ -0,0 +1,11 @@ +type BrowserRuntime = 'chromium' | 'firefox'; + +function getBrowserRuntime(): BrowserRuntime { + return chrome.runtime.getURL('').startsWith('moz-extension://') ? 'firefox' : 'chromium'; +} + +type WhenBrowserRuntimeMap = Record; + +export function whenBrowserRuntime(runtimeMap: WhenBrowserRuntimeMap) { + return runtimeMap[getBrowserRuntime()]; +} diff --git a/tests-legacy/integration/onboarding/onboarding.spec.ts b/tests-legacy/integration/onboarding/onboarding.spec.ts index 87d59d7d..a2a83a0a 100644 --- a/tests-legacy/integration/onboarding/onboarding.spec.ts +++ b/tests-legacy/integration/onboarding/onboarding.spec.ts @@ -121,7 +121,11 @@ describe(`Onboarding integration tests`, () => { await wallet.waitForHomePage(); await wallet.waitForsuggestedStepsList(); const stepsToStartBtns = await wallet.page.$$(wallet.$suggestedStepStartBtn); - await stepsToStartBtns[1].click(); + const [newPage] = await Promise.all([ + browser.context.waitForEvent('page'), + stepsToStartBtns[1].click(), + ]); + await newPage.waitForLoadState(); const stepsDone = await wallet.page.$$(wallet.$suggestedStepDoneBadge); expect(stepsDone.length).toEqual(2); }); diff --git a/tests-legacy/page-objects/demo.page.ts b/tests-legacy/page-objects/demo.page.ts index a15cdbef..54e4cfad 100644 --- a/tests-legacy/page-objects/demo.page.ts +++ b/tests-legacy/page-objects/demo.page.ts @@ -1,5 +1,5 @@ +import { BrowserContext, Page } from '@playwright/test'; import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; -import { BrowserContext, Page } from 'playwright'; import { createTestSelector } from '../integration/utils'; diff --git a/tests-legacy/page-objects/fund.page.ts b/tests-legacy/page-objects/fund.page.ts index 2da8b466..e7b8a759 100644 --- a/tests-legacy/page-objects/fund.page.ts +++ b/tests-legacy/page-objects/fund.page.ts @@ -1,5 +1,5 @@ +import { Page } from '@playwright/test'; import { FundPageSelectors } from '@tests-legacy/page-objects/fund.selectors'; -import { Page } from 'playwright'; import { createTestSelector } from '../integration/utils'; diff --git a/tests-legacy/page-objects/network-page.ts b/tests-legacy/page-objects/network-page.ts index 87fc7065..d4d01d4a 100644 --- a/tests-legacy/page-objects/network-page.ts +++ b/tests-legacy/page-objects/network-page.ts @@ -1,5 +1,5 @@ +import { Page } from '@playwright/test'; import { NetworkSelectors } from '@tests-legacy/integration/network.selectors'; -import { Page } from 'playwright'; import { createTestSelector } from '../integration/utils'; diff --git a/tests-legacy/page-objects/profile-updating.page.ts b/tests-legacy/page-objects/profile-updating.page.ts index 136a69cf..e004a61d 100644 --- a/tests-legacy/page-objects/profile-updating.page.ts +++ b/tests-legacy/page-objects/profile-updating.page.ts @@ -1,5 +1,5 @@ +import { Page } from '@playwright/test'; import { ProfileUpdatingSelectors } from '@tests-legacy/integration/profile/profile-updating.selector'; -import { Page } from 'playwright'; import { createTestSelector } from '../integration/utils'; diff --git a/tests-legacy/page-objects/transaction-signing.page.ts b/tests-legacy/page-objects/transaction-signing.page.ts index b1def7f3..92f65e41 100644 --- a/tests-legacy/page-objects/transaction-signing.page.ts +++ b/tests-legacy/page-objects/transaction-signing.page.ts @@ -1,5 +1,5 @@ +import { Page } from '@playwright/test'; import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; -import { Page } from 'playwright'; import { createTestSelector } from '../integration/utils'; import { TransactionSigningSelectors } from './transaction-signing.selectors'; diff --git a/tests-legacy/page-objects/wallet.page.ts b/tests-legacy/page-objects/wallet.page.ts index d1f3f187..49008504 100644 --- a/tests-legacy/page-objects/wallet.page.ts +++ b/tests-legacy/page-objects/wallet.page.ts @@ -1,9 +1,9 @@ +import { Page } from '@playwright/test'; import { SettingsSelectors } from '@tests-legacy/integration/settings.selectors'; import { HomePageSelectorsLegacy } from '@tests-legacy/page-objects/home.selectors'; import { HomePageSelectors } from '@tests/selectors/home.selectors'; import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; import { SettingsMenuSelectors } from '@tests/selectors/settings.selectors'; -import { Page } from 'playwright'; import { RouteUrls } from '@shared/route-urls'; diff --git a/tests/page-object-models/global.page.ts b/tests/page-object-models/global.page.ts index 795b6a18..0dbdc1a4 100644 --- a/tests/page-object-models/global.page.ts +++ b/tests/page-object-models/global.page.ts @@ -1,8 +1,6 @@ import { Page } from '@playwright/test'; import { setupMockApis } from '@tests/mocks/mock-apis'; -// const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); - export class GlobalPage { readonly page: Page; @@ -17,6 +15,7 @@ export class GlobalPage { async setupAndUseApiCalls(extensionId: string) { await this.page.route(/.*/, route => route.continue()); await setupMockApis(this.page); + await this.page.waitForTimeout(600); await this.gotoNakedRoot(extensionId); // I've no idea why this delay is needed. Was required when switching to MV3. Without it, // this error is thrown. Let's try removing with later versions of Playwright. @@ -25,7 +24,6 @@ export class GlobalPage { // =========================== logs =========================== // navigating to "chrome-extension://bcokkkbghbnpbjpkoifjakaofdjfgeaj/index.html", waiting until "load" // ============================================================ - // await delay(600); // await this.page.goto(`chrome-extension://${extensionId}/index.html`); } diff --git a/yarn.lock b/yarn.lock index addb5bf5..3dd667bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5287,7 +5287,7 @@ dependencies: "@types/chai" "*" -"@types/chai@*", "@types/chai@^4.3.4": +"@types/chai@*", "@types/chai@^4.3.5": version "4.3.5" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.5.tgz#ae69bcbb1bebb68c4ac0b11e9d8ed04526b3562b" integrity sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng== @@ -6370,10 +6370,10 @@ "@typescript-eslint/types" "5.59.6" eslint-visitor-keys "^3.3.0" -"@vitest/coverage-istanbul@0.31.0": - version "0.31.0" - resolved "https://registry.yarnpkg.com/@vitest/coverage-istanbul/-/coverage-istanbul-0.31.0.tgz#57d14c5d66685f9fe0d80e5884e940c0872a7fd6" - integrity sha512-SaTI1PSpCRtBhJ5ihBx7Z+jgrFAQlDjuI4MFmKQ/HjyYWzEoaU+I062SquRrOLjJtVOHnwJdjVJXKi0dgFiR9Q== +"@vitest/coverage-istanbul@0.31.1": + version "0.31.1" + resolved "https://registry.yarnpkg.com/@vitest/coverage-istanbul/-/coverage-istanbul-0.31.1.tgz#c9749bf404470c2b12dcb6ad851e89d27f88fc42" + integrity sha512-wLVROukTxWflIub4QUT5TenA2zx2ypUjRp636yiMTidN5hvRhnUNEEkRavJMEpYuWYYMN23E7EmAxo/MzRRmIA== dependencies: istanbul-lib-coverage "^3.2.0" istanbul-lib-instrument "^5.2.1" @@ -6382,45 +6382,45 @@ istanbul-reports "^3.1.5" test-exclude "^6.0.0" -"@vitest/expect@0.31.0": - version "0.31.0" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.31.0.tgz#37ab35d4f75c12826c204f2a0290e0c2e5ef1192" - integrity sha512-Jlm8ZTyp6vMY9iz9Ny9a0BHnCG4fqBa8neCF6Pk/c/6vkUk49Ls6UBlgGAU82QnzzoaUs9E/mUhq/eq9uMOv/g== +"@vitest/expect@0.31.1": + version "0.31.1" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.31.1.tgz#db8cb5a14a91167b948f377b9d29442229c73747" + integrity sha512-BV1LyNvhnX+eNYzJxlHIGPWZpwJFZaCcOIzp2CNG0P+bbetenTupk6EO0LANm4QFt0TTit+yqx7Rxd1qxi/SQA== dependencies: - "@vitest/spy" "0.31.0" - "@vitest/utils" "0.31.0" + "@vitest/spy" "0.31.1" + "@vitest/utils" "0.31.1" chai "^4.3.7" -"@vitest/runner@0.31.0": - version "0.31.0" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.31.0.tgz#ca830405ae4c2744ae5fb7fbe85df81b56430ebc" - integrity sha512-H1OE+Ly7JFeBwnpHTrKyCNm/oZgr+16N4qIlzzqSG/YRQDATBYmJb/KUn3GrZaiQQyL7GwpNHVZxSQd6juLCgw== +"@vitest/runner@0.31.1": + version "0.31.1" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.31.1.tgz#fc06260d4824dde624abaeea1825d6a75bad4583" + integrity sha512-imWuc82ngOtxdCUpXwtEzZIuc1KMr+VlQ3Ondph45VhWoQWit5yvG/fFcldbnCi8DUuFi+NmNx5ehMUw/cGLUw== dependencies: - "@vitest/utils" "0.31.0" + "@vitest/utils" "0.31.1" concordance "^5.0.4" p-limit "^4.0.0" pathe "^1.1.0" -"@vitest/snapshot@0.31.0": - version "0.31.0" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.31.0.tgz#f59c4bcf0d03f1f494ee09286965e60a1e0cab64" - integrity sha512-5dTXhbHnyUMTMOujZPB0wjFjQ6q5x9c8TvAsSPUNKjp1tVU7i9pbqcKPqntyu2oXtmVxKbuHCqrOd+Ft60r4tg== +"@vitest/snapshot@0.31.1": + version "0.31.1" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.31.1.tgz#7fc3f1e48f0c4313e6cb795c17a2c1aa909a7d64" + integrity sha512-L3w5uU9bMe6asrNzJ8WZzN+jUTX4KSgCinEJPXyny0o90fG4FPQMV0OWsq7vrCWfQlAilMjDnOF9nP8lidsJ+g== dependencies: magic-string "^0.30.0" pathe "^1.1.0" pretty-format "^27.5.1" -"@vitest/spy@0.31.0": - version "0.31.0" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.31.0.tgz#98cb19046c0bd2673a73d6c90ee1533d1be82136" - integrity sha512-IzCEQ85RN26GqjQNkYahgVLLkULOxOm5H/t364LG0JYb3Apg0PsYCHLBYGA006+SVRMWhQvHlBBCyuByAMFmkg== +"@vitest/spy@0.31.1": + version "0.31.1" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.31.1.tgz#1c3b6a3eec4ce81b8889e19c7fac6a603b600b14" + integrity sha512-1cTpt2m9mdo3hRLDyCG2hDQvRrePTDgEJBFQQNz1ydHHZy03EiA6EpFxY+7ODaY7vMRCie+WlFZBZ0/dQWyssQ== dependencies: tinyspy "^2.1.0" -"@vitest/utils@0.31.0": - version "0.31.0" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.31.0.tgz#d0aae17150b95ebf7afdf4e5db8952ac21610ffa" - integrity sha512-kahaRyLX7GS1urekRXN2752X4gIgOGVX4Wo8eDUGUkTWlGpXzf5ZS6N9RUUS+Re3XEE8nVGqNyxkSxF5HXlGhQ== +"@vitest/utils@0.31.1": + version "0.31.1" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.31.1.tgz#b810a458b37ef16931ab0d384ce79a9500f34e07" + integrity sha512-yFyRD5ilwojsZfo3E0BnH72pSVSuLg2356cN1tCEe/0RtDzxTPYwOomIC+eQbot7m6DRy4tPZw+09mB7NkbMmA== dependencies: concordance "^5.0.4" loupe "^2.3.6" @@ -16868,7 +16868,7 @@ tiny-warning@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tinybench@^2.4.0: +tinybench@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.0.tgz#4711c99bbf6f3e986f67eb722fed9cddb3a68ba5" integrity sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA== @@ -17488,10 +17488,10 @@ vinyl-buffer@^1.0.1: bl "^1.2.1" through2 "^2.0.3" -vite-node@0.31.0: - version "0.31.0" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.31.0.tgz#8794a98f21b0cf2394bfd2aaa5fc85d2c42be084" - integrity sha512-8x1x1LNuPvE2vIvkSB7c1mApX5oqlgsxzHQesYF7l5n1gKrEmrClIiZuOFbFDQcjLsmcWSwwmrWrcGWm9Fxc/g== +vite-node@0.31.1: + version "0.31.1" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.31.1.tgz#9fea18cbf9552ab262b969068249a8b8e7fb8b38" + integrity sha512-BajE/IsNQ6JyizPzu9zRgHrBwczkAs0erQf/JRpgTIESpKvNj9/Gd0vxX905klLkb0I0SJVCKbdrl5c6FnqYKA== dependencies: cac "^6.7.14" debug "^4.3.4" @@ -17511,19 +17511,19 @@ vite-node@0.31.0: optionalDependencies: fsevents "~2.3.2" -vitest@0.31.0: - version "0.31.0" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.31.0.tgz#133e98f779aa81afbc7ee1fcb385a0c458b8c2c8" - integrity sha512-JwWJS9p3GU9GxkG7eBSmr4Q4x4bvVBSswaCFf1PBNHiPx00obfhHRJfgHcnI0ffn+NMlIh9QGvG75FlaIBdKGA== +vitest@0.31.1: + version "0.31.1" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.31.1.tgz#e3d1b68a44e76e24f142c1156fe9772ef603e52c" + integrity sha512-/dOoOgzoFk/5pTvg1E65WVaobknWREN15+HF+0ucudo3dDG/vCZoXTQrjIfEaWvQXmqScwkRodrTbM/ScMpRcQ== dependencies: - "@types/chai" "^4.3.4" + "@types/chai" "^4.3.5" "@types/chai-subset" "^1.3.3" "@types/node" "*" - "@vitest/expect" "0.31.0" - "@vitest/runner" "0.31.0" - "@vitest/snapshot" "0.31.0" - "@vitest/spy" "0.31.0" - "@vitest/utils" "0.31.0" + "@vitest/expect" "0.31.1" + "@vitest/runner" "0.31.1" + "@vitest/snapshot" "0.31.1" + "@vitest/spy" "0.31.1" + "@vitest/utils" "0.31.1" acorn "^8.8.2" acorn-walk "^8.2.0" cac "^6.7.14" @@ -17536,10 +17536,10 @@ vitest@0.31.0: picocolors "^1.0.0" std-env "^3.3.2" strip-literal "^1.0.1" - tinybench "^2.4.0" + tinybench "^2.5.0" tinypool "^0.5.0" vite "^3.0.0 || ^4.0.0" - vite-node "0.31.0" + vite-node "0.31.1" why-is-node-running "^2.2.2" vm-browserify@1.1.2: