ci: improve set up, upgrades

This commit is contained in:
kyranjamie
2023-05-16 17:06:07 +02:00
committed by kyranjamie
parent 3771c7075f
commit 8d45dc8551
39 changed files with 344 additions and 282 deletions

View File

@@ -47,8 +47,6 @@ jobs:
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**/package.json') }} key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**/package.json') }}
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with:
node-version: 14.x
- name: Install packages - name: Install packages
uses: ./.github/actions/provision uses: ./.github/actions/provision
@@ -83,8 +81,6 @@ jobs:
- name: Set Node Version - name: Set Node Version
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with:
node-version: 14.x
- name: Restore cache - name: Restore cache
uses: actions/cache@v3 uses: actions/cache@v3

View File

@@ -33,23 +33,15 @@ jobs:
- name: File name checker - name: File name checker
run: yarn lint:filename run: yarn lint:filename
audit: lint-commit:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: ./.github/actions/provision with:
fetch-depth: 0
- name: Audit - name: Lint commit message
run: yarn audit-ci --high --skip-dev uses: wagoid/commitlint-github-action@v4
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/provision
- name: Typecheck
run: yarn typecheck
lint-deps: lint-deps:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -60,27 +52,6 @@ jobs:
- name: Lint dependency rules - name: Lint dependency rules
run: yarn lint:deps 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: lint-message-schema:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -112,7 +83,34 @@ jobs:
- run: yarn web-ext lint - 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 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -121,6 +119,18 @@ jobs:
- name: Build - name: Build
run: yarn 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 run: sh build-ext.sh
shell: bash shell: bash

View File

@@ -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

View File

@@ -10,12 +10,12 @@ jobs:
directories: directories:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
dir: ${{ steps.set-dirs.outputs.dir }} dir: ${{ steps.set-dirs.outputs.TEST_DIRECTORIES }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- id: set-dirs - id: set-dirs
working-directory: ./tests-legacy/integration 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: test-integration:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -28,33 +28,37 @@ jobs:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v3 uses: actions/checkout@v3
- run: echo ${{ fromJson(needs.directories.outputs.dir) }} - name: Get installed Playwright version
- run: echo ${{ matrix.dir }} id: playwright-version
run: echo "PLAYWRIGHT_VERSION=$(node -e "console.log(require('./package.json').devDependencies['@playwright/test'])")" >> $GITHUB_ENV
- uses: actions/cache@v3 - uses: actions/cache@v3
name: Cache node_modules id: cache
id: cache-node-modules
with: with:
path: '**/node_modules' 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: Install dependencies
name: Cache playwright if: steps.cache.outputs.cache-hit != 'true'
id: cache-playwright-browsers env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
run: yarn --frozen-lockfile
shell: bash
- name: Cache playwright binaries
uses: actions/cache@v3
id: playwright-cache
with: with:
path: '~/.cache/ms-playwright' path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-browser key: ${{ runner.os }}-playwright-cache-${{ env.PLAYWRIGHT_VERSION }}
- uses: actions/setup-node@v3 - name: Install Playwright browsers
with: run: yarn playwright install chrome
node-version: 18 if: steps.playwright-cache.outputs.cache-hit != 'true'
- name: Install packages - name: Install Playwright deps
uses: ./.github/actions/provision run: yarn playwright install-deps
if: steps.cache-node-modules.outputs.cache-hit != 'true' if: steps.playwright-cache.outputs.cache-hit != 'true'
- name: Install Playwright dependencies
run: npx playwright install --with-deps
- name: Build assets - name: Build assets
run: yarn build:test run: yarn build:test

View File

@@ -52,7 +52,7 @@ jobs:
key: ${{ runner.os }}-playwright-cache-${{ env.PLAYWRIGHT_VERSION }} key: ${{ runner.os }}-playwright-cache-${{ env.PLAYWRIGHT_VERSION }}
- name: Install Playwright browsers - name: Install Playwright browsers
run: yarn playwright install chrome run: yarn playwright install chrome chromium
if: steps.playwright-cache.outputs.cache-hit != 'true' if: steps.playwright-cache.outputs.cache-hit != 'true'
- name: Install Playwright deps - name: Install Playwright deps

View File

@@ -272,7 +272,7 @@
"@types/webpack": "5.28.1", "@types/webpack": "5.28.1",
"@types/zxcvbn": "4.4.1", "@types/zxcvbn": "4.4.1",
"@typescript-eslint/parser": "5.59.6", "@typescript-eslint/parser": "5.59.6",
"@vitest/coverage-istanbul": "0.31.0", "@vitest/coverage-istanbul": "0.31.1",
"audit-ci": "6.6.1", "audit-ci": "6.6.1",
"babel-loader": "9.1.2", "babel-loader": "9.1.2",
"base64-loader": "1.0.0", "base64-loader": "1.0.0",
@@ -317,7 +317,7 @@
"ts-unused-exports": "7.0.3", "ts-unused-exports": "7.0.3",
"tsconfig-paths-webpack-plugin": "4.0.1", "tsconfig-paths-webpack-plugin": "4.0.1",
"typescript": "5.0.4", "typescript": "5.0.4",
"vitest": "0.31.0", "vitest": "0.31.1",
"vm-browserify": "1.1.2", "vm-browserify": "1.1.2",
"web-ext": "7.4.0", "web-ext": "7.4.0",
"web-ext-submit": "7.4.0", "web-ext-submit": "7.4.0",

View File

@@ -32,7 +32,7 @@ const contentSecurityPolicyEnvironment = {
development: development:
"script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; frame-src 'none'; frame-ancestors 'none';", "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; frame-src 'none'; frame-ancestors 'none';",
production: 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 = { const defaultIconEnvironment = {
@@ -42,18 +42,9 @@ const defaultIconEnvironment = {
const browserSpecificConfig = { const browserSpecificConfig = {
firefox: { firefox: {
manifest_version: 2,
permissions: ['contextMenus', 'storage', '*://*/*'],
background: { 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: { browser_specific_settings: {
gecko: { gecko: {
id: '{e22ae397-03d7-4622-bd8f-ecaca8c9b277}', id: '{e22ae397-03d7-4622-bd8f-ecaca8c9b277}',
@@ -61,21 +52,9 @@ const browserSpecificConfig = {
}, },
}, },
chromium: { chromium: {
manifest_version: 3,
host_permissions: ['*://*/*'],
permissions: ['contextMenus', 'storage'],
background: { background: {
service_worker: 'background.js', 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 * @type {Manifest} manifest
*/ */
const manifest = { const manifest = {
manifest_version: 3,
author: 'Hiro PBC', author: 'Hiro PBC',
description: description:
'Hiro Wallet is a safe way to manage your STX, sign into apps, and protect your funds while interacting with Clarity smart contracts.', '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: { commands: {
_execute_browser_action: { _execute_browser_action: {
suggested_key: { suggested_key: {
@@ -95,6 +76,16 @@ const manifest = {
description: 'Opens Stacks App', 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: { options_ui: {
page: 'index.html', page: 'index.html',
open_in_tab: true, open_in_tab: true,
@@ -116,12 +107,8 @@ const name = PREVIEW_RELEASE ? 'Hiro Wallet Preview' : 'Hiro Wallet';
const prodManifest = { const prodManifest = {
name, 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' : ''), icons: generateImageAssetUrlsWithSuffix(PREVIEW_RELEASE ? '-preview' : ''),
browser_action: { action: {
default_icon: `assets/connect-logo/Stacks128w${PREVIEW_RELEASE ? '-preview' : ''}.png`, default_icon: `assets/connect-logo/Stacks128w${PREVIEW_RELEASE ? '-preview' : ''}.png`,
}, },
}; };

View File

@@ -3,9 +3,7 @@ import { useMemo } from 'react';
import { generateSecretKey } from '@stacks/wallet-sdk'; import { generateSecretKey } from '@stacks/wallet-sdk';
import { logger } from '@shared/logger'; import { logger } from '@shared/logger';
import { InternalMethods } from '@shared/message-types'; import { clearChromeStorage } from '@shared/storage/redux-pesist';
import { sendMessage } from '@shared/messages';
import { clearChromeStorage } from '@shared/storage';
import { queryClient } from '@app/common/persistence'; import { queryClient } from '@app/common/persistence';
import { partiallyClearLocalStorage } from '@app/common/store-utils'; 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 { inMemoryKeyActions } from '@app/store/in-memory-key/in-memory-key.actions';
import { keyActions } from '@app/store/keys/key.actions'; import { keyActions } from '@app/store/keys/key.actions';
import { useCurrentKeyDetails } from '@app/store/keys/key.selectors'; import { useCurrentKeyDetails } from '@app/store/keys/key.selectors';
import { clearWalletSession } from '@app/store/session-restore';
import { useAnalytics } from './analytics/use-analytics'; import { useAnalytics } from './analytics/use-analytics';
@@ -37,10 +36,6 @@ export function useKeyActions() {
return; return;
} }
const secretKey = generateSecretKey(256); const secretKey = generateSecretKey(256);
sendMessage({
method: InternalMethods.ShareInMemoryKeyToBackground,
payload: { secretKey, keyId: 'default' },
});
return dispatch(inMemoryKeyActions.generateWalletKey(secretKey)); return dispatch(inMemoryKeyActions.generateWalletKey(secretKey));
}, },
@@ -57,7 +52,7 @@ export function useKeyActions() {
}, },
async signOut() { async signOut() {
sendMessage({ method: InternalMethods.RemoveInMemoryKeys, payload: undefined }); await clearWalletSession();
dispatch(keyActions.signOut()); dispatch(keyActions.signOut());
await clearChromeStorage(); await clearChromeStorage();
partiallyClearLocalStorage(); partiallyClearLocalStorage();
@@ -65,8 +60,8 @@ export function useKeyActions() {
queryClient.clear(); queryClient.clear();
}, },
lockWallet() { async lockWallet() {
sendMessage({ method: InternalMethods.RemoveInMemoryKeys, payload: undefined }); await clearWalletSession();
return dispatch(inMemoryKeyActions.lockWallet()); return dispatch(inMemoryKeyActions.lockWallet());
}, },
}), }),

View File

@@ -3,7 +3,7 @@ import toast from 'react-hot-toast';
import * as reduxPersist from 'redux-persist'; import * as reduxPersist from 'redux-persist';
import { getLogsFromBrowserStorage } from '@shared/logger-storage'; import { getLogsFromBrowserStorage } from '@shared/logger-storage';
import { persistConfig } from '@shared/storage'; import { persistConfig } from '@shared/storage/redux-pesist';
import { store } from './store'; import { store } from './store';
import { stxChainSlice } from './store/chains/stx-chain.slice'; import { stxChainSlice } from './store/chains/stx-chain.slice';

View File

@@ -37,7 +37,6 @@ export function Container() {
<SwitchAccountDrawer /> <SwitchAccountDrawer />
<SettingsDropdown /> <SettingsDropdown />
<Toaster position="bottom-center" toastOptions={{ style: { fontSize: '14px' } }} /> <Toaster position="bottom-center" toastOptions={{ style: { fontSize: '14px' } }} />
<ContainerLayout header={routeHeader}> <ContainerLayout header={routeHeader}>
<Outlet /> <Outlet />
</ContainerLayout> </ContainerLayout>

View File

@@ -3,8 +3,6 @@ import toast from 'react-hot-toast';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { InternalMethods } from '@shared/message-types';
import { sendMessage } from '@shared/messages';
import { RouteUrls } from '@shared/route-urls'; import { RouteUrls } from '@shared/route-urls';
import { keySlice } from '@app/store/keys/key.slice'; import { keySlice } from '@app/store/keys/key.slice';
@@ -29,10 +27,6 @@ export function useTriggerLedgerDeviceRequestStacksKeys() {
publicKeys, 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); navigate(RouteUrls.Home);
}, },
}), }),

View File

@@ -1,15 +1,13 @@
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import { InternalMethods } from '@shared/message-types';
import { initSentry } from '@shared/utils/analytics'; import { initSentry } from '@shared/utils/analytics';
import { warnUsersAboutDevToolsDangers } from '@shared/utils/dev-tools-warning-log'; import { warnUsersAboutDevToolsDangers } from '@shared/utils/dev-tools-warning-log';
import { persistAndRenderApp } from '@app/common/persistence'; import { persistAndRenderApp } from '@app/common/persistence';
import { restoreWalletSession } from '@app/store/session-restore';
import { App } from './app'; import { App } from './app';
import { setDebugOnGlobal } from './debug'; import { setDebugOnGlobal } from './debug';
import { store } from './store';
import { inMemoryKeyActions } from './store/in-memory-key/in-memory-key.actions';
initSentry(); initSentry();
warnUsersAboutDevToolsDangers(); warnUsersAboutDevToolsDangers();
@@ -23,19 +21,8 @@ declare global {
window.__APP_VERSION__ = VERSION; 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() { async function renderApp() {
await checkForInMemoryKeys(); await restoreWalletSession();
const container = document.getElementById('app'); const container = document.getElementById('app');
return createRoot(container!).render(<App />); return createRoot(container!).render(<App />);
} }

View File

@@ -21,6 +21,9 @@ export function AccountGate({ children }: AccountGateProps) {
const currentKeyDetails = useCurrentKeyDetails(); const currentKeyDetails = useCurrentKeyDetails();
const currentInMemorySecretKey = useDefaultWalletSecretKey(); const currentInMemorySecretKey = useDefaultWalletSecretKey();
// console.log('currentKeyDetails', currentKeyDetails);
// console.log('currentInMemorySecretKey', currentInMemorySecretKey);
if (currentKeyDetails?.type === 'ledger') return <>{children}</>; if (currentKeyDetails?.type === 'ledger') return <>{children}</>;
if (shouldNavigateToOnboardingStartPage(currentKeyDetails)) if (shouldNavigateToOnboardingStartPage(currentKeyDetails))

View File

@@ -3,8 +3,9 @@ import { keyActions } from '@app/store/keys/key.actions';
export function useOnSignOut(handler: () => void) { export function useOnSignOut(handler: () => void) {
useOnMount(() => { useOnMount(() => {
chrome.runtime.onMessage.addListener(message => { chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message?.method === keyActions.signOut.type) handler(); if (message?.method === keyActions.signOut.type) handler();
sendResponse();
}); });
}); });
} }

View File

@@ -11,12 +11,17 @@ export function useOnOriginTabClose(handler: () => void) {
const analytics = useAnalytics(); const analytics = useAnalytics();
useEffect(() => { useEffect(() => {
const messageHandler = (message: BackgroundMessages) => { const messageHandler = (
message: BackgroundMessages,
_sender: chrome.runtime.MessageSender,
sendResponse: () => void
) => {
if (message.method !== InternalMethods.OriginatingTabClosed) return; if (message.method !== InternalMethods.OriginatingTabClosed) return;
if (message.payload.tabId === tabId) { if (message.payload.tabId === tabId) {
handler(); handler();
void analytics.track('requesting_origin_tab_closed_with_pending_action'); void analytics.track('requesting_origin_tab_closed_with_pending_action');
} }
sendResponse();
}; };
chrome.runtime.onMessage.addListener(messageHandler); chrome.runtime.onMessage.addListener(messageHandler);

View File

@@ -3,8 +3,9 @@ import { inMemoryKeyActions } from '@app/store/in-memory-key/in-memory-key.actio
export function useOnWalletLock(handler: () => void) { export function useOnWalletLock(handler: () => void) {
useOnMount(() => { useOnMount(() => {
chrome.runtime.onMessage.addListener(message => { chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message?.method === inMemoryKeyActions.lockWallet.type) handler(); if (message?.method === inMemoryKeyActions.lockWallet.type) handler();
sendResponse();
}); });
}); });
} }

View File

@@ -6,7 +6,7 @@ import { selectCurrentAccountIndex } from '../keys/key.selectors';
// This is only used when there is a pending transaction request and // This is only used when there is a pending transaction request and
// the user switches accounts during the signing process // the user switches accounts during the signing process
export const hasSwitchedAccountsState = atom<boolean>(false); export const hasSwitchedAccountsState = atom(false);
const hasCreatedAccountState = atom<boolean>(false); const hasCreatedAccountState = atom<boolean>(false);

View File

@@ -1,19 +1,30 @@
import { Wallet } from '@stacks/wallet-sdk'; import { Wallet } from '@stacks/wallet-sdk';
import memoize from 'promise-memoize'; import memoize from 'promise-memoize';
import { deriveStacksAccounts } from '@shared/crypto/stacks/derive-stacks-accounts';
import { InternalMethods } from '@shared/message-types'; import { InternalMethods } from '@shared/message-types';
import { RequestDerivedStxAccounts } from '@shared/messages'; import { RequestDerivedStxAccounts } from '@shared/messages';
import { delay } from '@app/common/utils';
import { RootState } from '@app/store'; import { RootState } from '@app/store';
export const selectStacksChain = (state: RootState) => state.chains.stx; export const selectStacksChain = (state: RootState) => state.chains.stx;
export const deriveWalletWithAccounts = memoize( const requestDerivedStacksAccountFromBackground = memoize(async () => {
async (secretKey: string, highestAccountIndex: number): Promise<Wallet> => { async (secretKey: string, highestAccountIndex: number): Promise<Wallet> => {
const message: RequestDerivedStxAccounts = { const message: RequestDerivedStxAccounts = {
method: InternalMethods.RequestDerivedStxAccounts, method: InternalMethods.RequestDerivedStxAccounts,
payload: { secretKey, highestAccountIndex }, payload: { secretKey, highestAccountIndex },
}; };
return new Promise(resolve => chrome.runtime.sendMessage(message, resp => resolve(resp))); 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);
}

View File

@@ -16,7 +16,7 @@ import {
import { PersistPartial } from 'redux-persist/es/persistReducer'; import { PersistPartial } from 'redux-persist/es/persistReducer';
import { IS_DEV_ENV } from '@shared/environment'; 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 { analyticsSlice } from './analytics/analytics.slice';
import { appPermissionsSlice } from './app-permissions/app-permissions.slice'; import { appPermissionsSlice } from './app-permissions/app-permissions.slice';

View File

@@ -2,8 +2,6 @@ import { AddressVersion } from '@stacks/transactions';
import { decryptMnemonic, encryptMnemonic } from '@shared/crypto/mnemonic-encryption'; import { decryptMnemonic, encryptMnemonic } from '@shared/crypto/mnemonic-encryption';
import { logger } from '@shared/logger'; 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 { recurseAccountsForActivity } from '@app/common/account-restoration/account-restore';
import { checkForLegacyGaiaConfigWithKnownGeneratedAccountIndex } from '@app/common/account-restoration/legacy-gaia-config-lookup'; 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 { fetchNamesForAddress } from '@app/query/stacks/bns/bns.utils';
import { StacksClient } from '@app/query/stacks/stacks-client'; import { StacksClient } from '@app/query/stacks/stacks-client';
import { AppThunk } from '@app/store'; import { AppThunk } from '@app/store';
import { initalizeWalletSession } from '@app/store/session-restore';
import { getNativeSegwitMainnetAddressFromMnemonic } from '../accounts/blockchain/bitcoin/bitcoin-keychain'; import { getNativeSegwitMainnetAddressFromMnemonic } from '../accounts/blockchain/bitcoin/bitcoin-keychain';
import { getStacksAddressByIndex } from '../accounts/blockchain/stacks/stacks-keychain'; import { getStacksAddressByIndex } from '../accounts/blockchain/stacks/stacks-keychain';
@@ -30,7 +29,13 @@ function setWalletEncryptionPassword(args: {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const secretKey = selectDefaultWalletKey(getState()); const secretKey = selectDefaultWalletKey(getState());
if (!secretKey) throw new Error('Cannot generate wallet without first having generated a key'); 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 = const legacyAccountActivityLookup =
await checkForLegacyGaiaConfigWithKnownGeneratedAccountIndex(secretKey); await checkForLegacyGaiaConfigWithKnownGeneratedAccountIndex(secretKey);
@@ -76,12 +81,6 @@ function setWalletEncryptionPassword(args: {
dispatch(stxChainSlice.actions.restoreAccountIndex(recursiveActivityIndex)); dispatch(stxChainSlice.actions.restoreAccountIndex(recursiveActivityIndex));
}); });
sendMessage({
method: InternalMethods.ShareInMemoryKeyToBackground,
payload: { secretKey, keyId: defaultKeyId },
});
dispatch(inMemoryKeySlice.actions.setKeysInMemory({ default: secretKey }));
dispatch( dispatch(
keySlice.actions.createStacksSoftwareWalletComplete({ keySlice.actions.createStacksSoftwareWalletComplete({
type: 'software', type: 'software',
@@ -100,11 +99,9 @@ function unlockWalletAction(password: string): AppThunk {
const currentKey = selectCurrentKey(getState()); const currentKey = selectCurrentKey(getState());
if (!currentKey) return; if (!currentKey) return;
if (currentKey.type !== 'software') return; if (currentKey.type !== 'software') return;
const { secretKey } = await decryptMnemonic({ password, ...currentKey }); const { secretKey, encryptionKey } = await decryptMnemonic({ password, ...currentKey });
sendMessage({
method: InternalMethods.ShareInMemoryKeyToBackground, await initalizeWalletSession(encryptionKey, secretKey);
payload: { secretKey: secretKey, keyId: defaultKeyId },
});
dispatch(inMemoryKeySlice.actions.setKeysInMemory({ default: secretKey })); dispatch(inMemoryKeySlice.actions.setKeysInMemory({ default: secretKey }));
}; };
} }

View File

@@ -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);
})
);
}

View File

@@ -21,10 +21,6 @@ initSentry();
initContextMenuActions(); initContextMenuActions();
warnUsersAboutDevToolsDangers(); 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'; const IS_TEST_ENV = process.env.TEST_ENV === 'true';
chrome.runtime.onInstalled.addListener(async details => { chrome.runtime.onInstalled.addListener(async details => {
@@ -69,34 +65,34 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
return true; return true;
}); });
const storageArea = chrome.storage.local as chrome.storage.LocalStorageArea; // const storageArea = chrome.storage.local as chrome.storage.LocalStorageArea;
const testIntervalMs = 10000; // const testIntervalMs = 10000;
const storageWaitTimeMs = 100; // const storageWaitTimeMs = 100;
// @see https://bugs.chromium.org/p/chromium/issues/detail?id=1316588 // @see https://bugs.chromium.org/p/chromium/issues/detail?id=1316588
async function hasChromiumIssue1316588() { // async function hasChromiumIssue1316588() {
return new Promise(resolve => { // return new Promise(resolve => {
let dispatched = false; // let dispatched = false;
const testEventDispatching = () => { // const testEventDispatching = () => {
storageArea.onChanged.removeListener(testEventDispatching); // storageArea.onChanged.removeListener(testEventDispatching);
dispatched = true; // dispatched = true;
}; // };
storageArea.onChanged.addListener(testEventDispatching); // storageArea.onChanged.addListener(testEventDispatching);
void storageArea.set({ testEventDispatching: Math.random() }); // void storageArea.set({ testEventDispatching: Math.random() });
setTimeout(() => resolve(!dispatched), storageWaitTimeMs); // setTimeout(() => resolve(!dispatched), storageWaitTimeMs);
}); // });
} // }
function fixChromiumIssue1316588() { // function fixChromiumIssue1316588() {
void hasChromiumIssue1316588().then(hasIssue => { // void hasChromiumIssue1316588().then(hasIssue => {
if (hasIssue) { // if (hasIssue) {
chrome.runtime.reload(); // chrome.runtime.reload();
return; // return;
} // }
setTimeout(fixChromiumIssue1316588, testIntervalMs); // setTimeout(fixChromiumIssue1316588, testIntervalMs);
}); // });
} // }
fixChromiumIssue1316588(); // fixChromiumIssue1316588();

View File

@@ -1,7 +1,4 @@
// import { StacksMainnet } from '@stacks/network'; import { deriveStacksAccountsMemoized } from '@shared/crypto/stacks/derive-stacks-accounts';
import { generateNewAccount, generateWallet } from '@stacks/wallet-sdk';
import memoize from 'promise-memoize';
import { logger } from '@shared/logger'; import { logger } from '@shared/logger';
import { InternalMethods } from '@shared/message-types'; import { InternalMethods } from '@shared/message-types';
import { BackgroundMessages } from '@shared/messages'; import { BackgroundMessages } from '@shared/messages';
@@ -11,18 +8,6 @@ function validateMessagesAreFromExtension(sender: chrome.runtime.MessageSender)
return sender.url?.startsWith(chrome.runtime.getURL('')); 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 inMemoryKeys = new Map();
const inMemoryFormState = new Map<number, object>(); const inMemoryFormState = new Map<number, object>();
@@ -35,17 +20,19 @@ export async function internalBackgroundMessageHandler(
sender: chrome.runtime.MessageSender, sender: chrome.runtime.MessageSender,
sendResponse: (response?: any) => void sendResponse: (response?: any) => void
) { ) {
// console.info('Internal msg', message, validateMessagesAreFromExtension(sender));
if (!validateMessagesAreFromExtension(sender)) { if (!validateMessagesAreFromExtension(sender)) {
logger.error('Error: Received background script msg from ' + sender.url); logger.error('Error: Received background script msg from ' + sender.url);
sendResponse(); sendResponse();
return; return;
} }
// logger.debug('Internal message', message); logger.debug('Internal message', message);
switch (message.method) { switch (message.method) {
case InternalMethods.RequestDerivedStxAccounts: { case InternalMethods.RequestDerivedStxAccounts: {
const { secretKey, highestAccountIndex } = message.payload; const { secretKey, highestAccountIndex } = message.payload;
const walletsWithAccounts = await deriveWalletWithAccounts(secretKey, highestAccountIndex); const walletsWithAccounts = await deriveStacksAccountsMemoized(
secretKey,
highestAccountIndex
);
sendResponse(walletsWithAccounts); sendResponse(walletsWithAccounts);
break; break;
} }
@@ -59,7 +46,6 @@ export async function internalBackgroundMessageHandler(
case InternalMethods.RequestInMemoryKeys: { case InternalMethods.RequestInMemoryKeys: {
sendResponse(Object.fromEntries(inMemoryKeys)); sendResponse(Object.fromEntries(inMemoryKeys));
sendResponse();
break; break;
} }
@@ -71,11 +57,13 @@ export async function internalBackgroundMessageHandler(
case InternalMethods.SetActiveFormState: { case InternalMethods.SetActiveFormState: {
const { tabId, ...state } = message.payload; const { tabId, ...state } = message.payload;
inMemoryFormState.set(tabId, state); inMemoryFormState.set(tabId, state);
sendResponse();
break; break;
} }
case InternalMethods.ClearActiveFormState: { case InternalMethods.ClearActiveFormState: {
inMemoryFormState.delete(message.payload.tabId); inMemoryFormState.delete(message.payload.tabId);
sendResponse();
break; break;
} }
@@ -85,7 +73,4 @@ export async function internalBackgroundMessageHandler(
break; 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();
} }

View File

@@ -1,11 +1,11 @@
import { WorkerScript, createWorker } from '../workers'; import { WorkerScript, createWorker } from '../workers';
const worker = createWorker(WorkerScript.DecryptionWorker); const worker = createWorker(WorkerScript.DecryptionWorker);
interface GenerateEncryptionKeyArgs { interface DeriveEncryptionKeyArgs {
password: string; password: string;
salt: string; salt: string;
} }
export async function generateEncryptionKey(args: GenerateEncryptionKeyArgs): Promise<string> { export async function deriveEncryptionKey(args: DeriveEncryptionKeyArgs): Promise<string> {
return new Promise(resolve => { return new Promise(resolve => {
const handler = (e: MessageEvent<string>) => resolve(e.data); const handler = (e: MessageEvent<string>) => resolve(e.data);
worker.addEventListener('message', handler); worker.addEventListener('message', handler);

View File

@@ -1,7 +1,7 @@
import { bytesToHex } from '@stacks/common'; import { bytesToHex } from '@stacks/common';
import { decrypt, encrypt } from '@stacks/wallet-sdk'; 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'; import { generateRandomHexString } from './generate-random-hex';
interface EncryptMnemonicArgs { interface EncryptMnemonicArgs {
@@ -10,11 +10,12 @@ interface EncryptMnemonicArgs {
} }
export async function encryptMnemonic({ secretKey, password }: EncryptMnemonicArgs) { export async function encryptMnemonic({ secretKey, password }: EncryptMnemonicArgs) {
const salt = generateRandomHexString(); const salt = generateRandomHexString();
const argonHash = await generateEncryptionKey({ password, salt }); const encryptionKey = await deriveEncryptionKey({ password, salt });
const encryptedBuffer = await encrypt(secretKey, argonHash); const encryptedBuffer = await encrypt(secretKey, encryptionKey);
return { return {
salt, salt,
encryptedSecretKey: bytesToHex(encryptedBuffer), encryptedSecretKey: bytesToHex(encryptedBuffer),
encryptionKey,
}; };
} }
@@ -36,14 +37,16 @@ export async function decryptMnemonic({
encryptedSecretKey: string; encryptedSecretKey: string;
salt: string; salt: string;
secretKey: string; secretKey: string;
encryptionKey: string;
}> { }> {
if (salt) { if (salt) {
const pw = await generateEncryptionKey({ password, salt }); const encryptionKey = await deriveEncryptionKey({ password, salt });
const secretKey = await decrypt(Buffer.from(encryptedSecretKey, 'hex'), pw); const secretKey = await decrypt(Buffer.from(encryptedSecretKey, 'hex'), encryptionKey);
return { return {
secretKey, secretKey,
encryptedSecretKey, encryptedSecretKey,
salt, salt,
encryptionKey,
}; };
} else { } else {
// if there is no salt, decrypt the secret key, then re-encrypt with an argon2 hash // 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, secretKey,
encryptedSecretKey: newEncryptedKey.encryptedSecretKey, encryptedSecretKey: newEncryptedKey.encryptedSecretKey,
salt: newEncryptedKey.salt, salt: newEncryptedKey.salt,
encryptionKey: newEncryptedKey.encryptedSecretKey,
}; };
} }
} }

View File

@@ -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);

View File

@@ -25,14 +25,14 @@ type ClearActiveFormState = BackgroundMessage<
{ tabId: number } { tabId: number }
>; >;
type ShareInMemoryKeyToBackground = BackgroundMessage< type FirefoxShareInMemoryKeyToBackground = BackgroundMessage<
InternalMethods.ShareInMemoryKeyToBackground, InternalMethods.ShareInMemoryKeyToBackground,
{ secretKey: string; keyId: string } { secretKey: string; keyId: string }
>; >;
type RequestInMemoryKeys = BackgroundMessage<InternalMethods.RequestInMemoryKeys>; type FirefoxRequestInMemoryKeys = BackgroundMessage<InternalMethods.RequestInMemoryKeys>;
type RemoveInMemoryKeys = BackgroundMessage<InternalMethods.RemoveInMemoryKeys>; type FirefoxRemoveInMemoryKeys = BackgroundMessage<InternalMethods.RemoveInMemoryKeys>;
type OriginatingTabClosed = BackgroundMessage< type OriginatingTabClosed = BackgroundMessage<
InternalMethods.OriginatingTabClosed, InternalMethods.OriginatingTabClosed,
@@ -44,9 +44,9 @@ export type BackgroundMessages =
| GetActiveFormState | GetActiveFormState
| SetActiveFormState | SetActiveFormState
| ClearActiveFormState | ClearActiveFormState
| ShareInMemoryKeyToBackground | FirefoxShareInMemoryKeyToBackground
| RequestInMemoryKeys | FirefoxRequestInMemoryKeys
| RemoveInMemoryKeys | FirefoxRemoveInMemoryKeys
| OriginatingTabClosed; | OriginatingTabClosed;
export function sendMessage(message: BackgroundMessages) { export function sendMessage(message: BackgroundMessages) {

View File

@@ -2,7 +2,7 @@ import { PersistConfig, createMigrate, getStoredState } from 'redux-persist';
import type { RootState } from '@app/store'; import type { RootState } from '@app/store';
import { analytics } from './utils/analytics'; import { analytics } from '../utils/analytics';
export async function clearChromeStorage(): Promise<void> { export async function clearChromeStorage(): Promise<void> {
return new Promise(resolve => chrome.storage.local.clear(resolve)); return new Promise(resolve => chrome.storage.local.clear(resolve));

View File

@@ -8,7 +8,7 @@ import {
SENTRY_DSN, SENTRY_DSN,
WALLET_ENVIRONMENT, WALLET_ENVIRONMENT,
} from '@shared/environment'; } from '@shared/environment';
import { persistConfig } from '@shared/storage'; import { persistConfig } from '@shared/storage/redux-pesist';
import type { RootState } from '@app/store'; import type { RootState } from '@app/store';

View File

@@ -0,0 +1,11 @@
type BrowserRuntime = 'chromium' | 'firefox';
function getBrowserRuntime(): BrowserRuntime {
return chrome.runtime.getURL('').startsWith('moz-extension://') ? 'firefox' : 'chromium';
}
type WhenBrowserRuntimeMap<T> = Record<BrowserRuntime, T>;
export function whenBrowserRuntime<T>(runtimeMap: WhenBrowserRuntimeMap<T>) {
return runtimeMap[getBrowserRuntime()];
}

View File

@@ -121,7 +121,11 @@ describe(`Onboarding integration tests`, () => {
await wallet.waitForHomePage(); await wallet.waitForHomePage();
await wallet.waitForsuggestedStepsList(); await wallet.waitForsuggestedStepsList();
const stepsToStartBtns = await wallet.page.$$(wallet.$suggestedStepStartBtn); 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); const stepsDone = await wallet.page.$$(wallet.$suggestedStepDoneBadge);
expect(stepsDone.length).toEqual(2); expect(stepsDone.length).toEqual(2);
}); });

View File

@@ -1,5 +1,5 @@
import { BrowserContext, Page } from '@playwright/test';
import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors';
import { BrowserContext, Page } from 'playwright';
import { createTestSelector } from '../integration/utils'; import { createTestSelector } from '../integration/utils';

View File

@@ -1,5 +1,5 @@
import { Page } from '@playwright/test';
import { FundPageSelectors } from '@tests-legacy/page-objects/fund.selectors'; import { FundPageSelectors } from '@tests-legacy/page-objects/fund.selectors';
import { Page } from 'playwright';
import { createTestSelector } from '../integration/utils'; import { createTestSelector } from '../integration/utils';

View File

@@ -1,5 +1,5 @@
import { Page } from '@playwright/test';
import { NetworkSelectors } from '@tests-legacy/integration/network.selectors'; import { NetworkSelectors } from '@tests-legacy/integration/network.selectors';
import { Page } from 'playwright';
import { createTestSelector } from '../integration/utils'; import { createTestSelector } from '../integration/utils';

View File

@@ -1,5 +1,5 @@
import { Page } from '@playwright/test';
import { ProfileUpdatingSelectors } from '@tests-legacy/integration/profile/profile-updating.selector'; import { ProfileUpdatingSelectors } from '@tests-legacy/integration/profile/profile-updating.selector';
import { Page } from 'playwright';
import { createTestSelector } from '../integration/utils'; import { createTestSelector } from '../integration/utils';

View File

@@ -1,5 +1,5 @@
import { Page } from '@playwright/test';
import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors';
import { Page } from 'playwright';
import { createTestSelector } from '../integration/utils'; import { createTestSelector } from '../integration/utils';
import { TransactionSigningSelectors } from './transaction-signing.selectors'; import { TransactionSigningSelectors } from './transaction-signing.selectors';

View File

@@ -1,9 +1,9 @@
import { Page } from '@playwright/test';
import { SettingsSelectors } from '@tests-legacy/integration/settings.selectors'; import { SettingsSelectors } from '@tests-legacy/integration/settings.selectors';
import { HomePageSelectorsLegacy } from '@tests-legacy/page-objects/home.selectors'; import { HomePageSelectorsLegacy } from '@tests-legacy/page-objects/home.selectors';
import { HomePageSelectors } from '@tests/selectors/home.selectors'; import { HomePageSelectors } from '@tests/selectors/home.selectors';
import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors';
import { SettingsMenuSelectors } from '@tests/selectors/settings.selectors'; import { SettingsMenuSelectors } from '@tests/selectors/settings.selectors';
import { Page } from 'playwright';
import { RouteUrls } from '@shared/route-urls'; import { RouteUrls } from '@shared/route-urls';

View File

@@ -1,8 +1,6 @@
import { Page } from '@playwright/test'; import { Page } from '@playwright/test';
import { setupMockApis } from '@tests/mocks/mock-apis'; import { setupMockApis } from '@tests/mocks/mock-apis';
// const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
export class GlobalPage { export class GlobalPage {
readonly page: Page; readonly page: Page;
@@ -17,6 +15,7 @@ export class GlobalPage {
async setupAndUseApiCalls(extensionId: string) { async setupAndUseApiCalls(extensionId: string) {
await this.page.route(/.*/, route => route.continue()); await this.page.route(/.*/, route => route.continue());
await setupMockApis(this.page); await setupMockApis(this.page);
await this.page.waitForTimeout(600);
await this.gotoNakedRoot(extensionId); await this.gotoNakedRoot(extensionId);
// I've no idea why this delay is needed. Was required when switching to MV3. Without it, // 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. // this error is thrown. Let's try removing with later versions of Playwright.
@@ -25,7 +24,6 @@ export class GlobalPage {
// =========================== logs =========================== // =========================== logs ===========================
// navigating to "chrome-extension://bcokkkbghbnpbjpkoifjakaofdjfgeaj/index.html", waiting until "load" // navigating to "chrome-extension://bcokkkbghbnpbjpkoifjakaofdjfgeaj/index.html", waiting until "load"
// ============================================================ // ============================================================
// await delay(600);
// await this.page.goto(`chrome-extension://${extensionId}/index.html`); // await this.page.goto(`chrome-extension://${extensionId}/index.html`);
} }

View File

@@ -5287,7 +5287,7 @@
dependencies: dependencies:
"@types/chai" "*" "@types/chai" "*"
"@types/chai@*", "@types/chai@^4.3.4": "@types/chai@*", "@types/chai@^4.3.5":
version "4.3.5" version "4.3.5"
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.5.tgz#ae69bcbb1bebb68c4ac0b11e9d8ed04526b3562b" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.5.tgz#ae69bcbb1bebb68c4ac0b11e9d8ed04526b3562b"
integrity sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng== integrity sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==
@@ -6370,10 +6370,10 @@
"@typescript-eslint/types" "5.59.6" "@typescript-eslint/types" "5.59.6"
eslint-visitor-keys "^3.3.0" eslint-visitor-keys "^3.3.0"
"@vitest/coverage-istanbul@0.31.0": "@vitest/coverage-istanbul@0.31.1":
version "0.31.0" version "0.31.1"
resolved "https://registry.yarnpkg.com/@vitest/coverage-istanbul/-/coverage-istanbul-0.31.0.tgz#57d14c5d66685f9fe0d80e5884e940c0872a7fd6" resolved "https://registry.yarnpkg.com/@vitest/coverage-istanbul/-/coverage-istanbul-0.31.1.tgz#c9749bf404470c2b12dcb6ad851e89d27f88fc42"
integrity sha512-SaTI1PSpCRtBhJ5ihBx7Z+jgrFAQlDjuI4MFmKQ/HjyYWzEoaU+I062SquRrOLjJtVOHnwJdjVJXKi0dgFiR9Q== integrity sha512-wLVROukTxWflIub4QUT5TenA2zx2ypUjRp636yiMTidN5hvRhnUNEEkRavJMEpYuWYYMN23E7EmAxo/MzRRmIA==
dependencies: dependencies:
istanbul-lib-coverage "^3.2.0" istanbul-lib-coverage "^3.2.0"
istanbul-lib-instrument "^5.2.1" istanbul-lib-instrument "^5.2.1"
@@ -6382,45 +6382,45 @@
istanbul-reports "^3.1.5" istanbul-reports "^3.1.5"
test-exclude "^6.0.0" test-exclude "^6.0.0"
"@vitest/expect@0.31.0": "@vitest/expect@0.31.1":
version "0.31.0" version "0.31.1"
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.31.0.tgz#37ab35d4f75c12826c204f2a0290e0c2e5ef1192" resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.31.1.tgz#db8cb5a14a91167b948f377b9d29442229c73747"
integrity sha512-Jlm8ZTyp6vMY9iz9Ny9a0BHnCG4fqBa8neCF6Pk/c/6vkUk49Ls6UBlgGAU82QnzzoaUs9E/mUhq/eq9uMOv/g== integrity sha512-BV1LyNvhnX+eNYzJxlHIGPWZpwJFZaCcOIzp2CNG0P+bbetenTupk6EO0LANm4QFt0TTit+yqx7Rxd1qxi/SQA==
dependencies: dependencies:
"@vitest/spy" "0.31.0" "@vitest/spy" "0.31.1"
"@vitest/utils" "0.31.0" "@vitest/utils" "0.31.1"
chai "^4.3.7" chai "^4.3.7"
"@vitest/runner@0.31.0": "@vitest/runner@0.31.1":
version "0.31.0" version "0.31.1"
resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.31.0.tgz#ca830405ae4c2744ae5fb7fbe85df81b56430ebc" resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.31.1.tgz#fc06260d4824dde624abaeea1825d6a75bad4583"
integrity sha512-H1OE+Ly7JFeBwnpHTrKyCNm/oZgr+16N4qIlzzqSG/YRQDATBYmJb/KUn3GrZaiQQyL7GwpNHVZxSQd6juLCgw== integrity sha512-imWuc82ngOtxdCUpXwtEzZIuc1KMr+VlQ3Ondph45VhWoQWit5yvG/fFcldbnCi8DUuFi+NmNx5ehMUw/cGLUw==
dependencies: dependencies:
"@vitest/utils" "0.31.0" "@vitest/utils" "0.31.1"
concordance "^5.0.4" concordance "^5.0.4"
p-limit "^4.0.0" p-limit "^4.0.0"
pathe "^1.1.0" pathe "^1.1.0"
"@vitest/snapshot@0.31.0": "@vitest/snapshot@0.31.1":
version "0.31.0" version "0.31.1"
resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.31.0.tgz#f59c4bcf0d03f1f494ee09286965e60a1e0cab64" resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.31.1.tgz#7fc3f1e48f0c4313e6cb795c17a2c1aa909a7d64"
integrity sha512-5dTXhbHnyUMTMOujZPB0wjFjQ6q5x9c8TvAsSPUNKjp1tVU7i9pbqcKPqntyu2oXtmVxKbuHCqrOd+Ft60r4tg== integrity sha512-L3w5uU9bMe6asrNzJ8WZzN+jUTX4KSgCinEJPXyny0o90fG4FPQMV0OWsq7vrCWfQlAilMjDnOF9nP8lidsJ+g==
dependencies: dependencies:
magic-string "^0.30.0" magic-string "^0.30.0"
pathe "^1.1.0" pathe "^1.1.0"
pretty-format "^27.5.1" pretty-format "^27.5.1"
"@vitest/spy@0.31.0": "@vitest/spy@0.31.1":
version "0.31.0" version "0.31.1"
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.31.0.tgz#98cb19046c0bd2673a73d6c90ee1533d1be82136" resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.31.1.tgz#1c3b6a3eec4ce81b8889e19c7fac6a603b600b14"
integrity sha512-IzCEQ85RN26GqjQNkYahgVLLkULOxOm5H/t364LG0JYb3Apg0PsYCHLBYGA006+SVRMWhQvHlBBCyuByAMFmkg== integrity sha512-1cTpt2m9mdo3hRLDyCG2hDQvRrePTDgEJBFQQNz1ydHHZy03EiA6EpFxY+7ODaY7vMRCie+WlFZBZ0/dQWyssQ==
dependencies: dependencies:
tinyspy "^2.1.0" tinyspy "^2.1.0"
"@vitest/utils@0.31.0": "@vitest/utils@0.31.1":
version "0.31.0" version "0.31.1"
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.31.0.tgz#d0aae17150b95ebf7afdf4e5db8952ac21610ffa" resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.31.1.tgz#b810a458b37ef16931ab0d384ce79a9500f34e07"
integrity sha512-kahaRyLX7GS1urekRXN2752X4gIgOGVX4Wo8eDUGUkTWlGpXzf5ZS6N9RUUS+Re3XEE8nVGqNyxkSxF5HXlGhQ== integrity sha512-yFyRD5ilwojsZfo3E0BnH72pSVSuLg2356cN1tCEe/0RtDzxTPYwOomIC+eQbot7m6DRy4tPZw+09mB7NkbMmA==
dependencies: dependencies:
concordance "^5.0.4" concordance "^5.0.4"
loupe "^2.3.6" 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" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
tinybench@^2.4.0: tinybench@^2.5.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.0.tgz#4711c99bbf6f3e986f67eb722fed9cddb3a68ba5" resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.0.tgz#4711c99bbf6f3e986f67eb722fed9cddb3a68ba5"
integrity sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA== integrity sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==
@@ -17488,10 +17488,10 @@ vinyl-buffer@^1.0.1:
bl "^1.2.1" bl "^1.2.1"
through2 "^2.0.3" through2 "^2.0.3"
vite-node@0.31.0: vite-node@0.31.1:
version "0.31.0" version "0.31.1"
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.31.0.tgz#8794a98f21b0cf2394bfd2aaa5fc85d2c42be084" resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.31.1.tgz#9fea18cbf9552ab262b969068249a8b8e7fb8b38"
integrity sha512-8x1x1LNuPvE2vIvkSB7c1mApX5oqlgsxzHQesYF7l5n1gKrEmrClIiZuOFbFDQcjLsmcWSwwmrWrcGWm9Fxc/g== integrity sha512-BajE/IsNQ6JyizPzu9zRgHrBwczkAs0erQf/JRpgTIESpKvNj9/Gd0vxX905klLkb0I0SJVCKbdrl5c6FnqYKA==
dependencies: dependencies:
cac "^6.7.14" cac "^6.7.14"
debug "^4.3.4" debug "^4.3.4"
@@ -17511,19 +17511,19 @@ vite-node@0.31.0:
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
vitest@0.31.0: vitest@0.31.1:
version "0.31.0" version "0.31.1"
resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.31.0.tgz#133e98f779aa81afbc7ee1fcb385a0c458b8c2c8" resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.31.1.tgz#e3d1b68a44e76e24f142c1156fe9772ef603e52c"
integrity sha512-JwWJS9p3GU9GxkG7eBSmr4Q4x4bvVBSswaCFf1PBNHiPx00obfhHRJfgHcnI0ffn+NMlIh9QGvG75FlaIBdKGA== integrity sha512-/dOoOgzoFk/5pTvg1E65WVaobknWREN15+HF+0ucudo3dDG/vCZoXTQrjIfEaWvQXmqScwkRodrTbM/ScMpRcQ==
dependencies: dependencies:
"@types/chai" "^4.3.4" "@types/chai" "^4.3.5"
"@types/chai-subset" "^1.3.3" "@types/chai-subset" "^1.3.3"
"@types/node" "*" "@types/node" "*"
"@vitest/expect" "0.31.0" "@vitest/expect" "0.31.1"
"@vitest/runner" "0.31.0" "@vitest/runner" "0.31.1"
"@vitest/snapshot" "0.31.0" "@vitest/snapshot" "0.31.1"
"@vitest/spy" "0.31.0" "@vitest/spy" "0.31.1"
"@vitest/utils" "0.31.0" "@vitest/utils" "0.31.1"
acorn "^8.8.2" acorn "^8.8.2"
acorn-walk "^8.2.0" acorn-walk "^8.2.0"
cac "^6.7.14" cac "^6.7.14"
@@ -17536,10 +17536,10 @@ vitest@0.31.0:
picocolors "^1.0.0" picocolors "^1.0.0"
std-env "^3.3.2" std-env "^3.3.2"
strip-literal "^1.0.1" strip-literal "^1.0.1"
tinybench "^2.4.0" tinybench "^2.5.0"
tinypool "^0.5.0" tinypool "^0.5.0"
vite "^3.0.0 || ^4.0.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" why-is-node-running "^2.2.2"
vm-browserify@1.1.2: vm-browserify@1.1.2: