From 03dbb1eb944d33dbb0d7754d4ed68ee348632c15 Mon Sep 17 00:00:00 2001 From: kyranjamie Date: Mon, 16 Aug 2021 13:36:12 +0200 Subject: [PATCH] refactor: migrate to manifest version 3 --- .github/workflows/publish-extensions.yml | 4 + scripts/generate-manifest.js | 104 +++++++++++++++--- src/app/index.tsx | 1 + src/background/background.ts | 57 +++++----- src/background/init-context-menus.ts | 12 +- .../internal-methods/message-handler.ts | 6 +- .../legacy/legacy-external-message-handler.ts | 4 +- src/background/popup-center.ts | 14 +-- .../actions/finalize-auth-reaponse-format.ts | 23 ++++ src/shared/actions/finalize-auth-response.ts | 23 +--- .../finalize-message-signature-format.ts | 17 +++ .../actions/finalize-tx-signature-format.ts | 24 ++++ src/shared/actions/finalize-tx-signature.ts | 25 +---- webpack/webpack.config.base.js | 1 + webpack/webpack.config.dev.js | 2 +- 15 files changed, 210 insertions(+), 107 deletions(-) create mode 100644 src/shared/actions/finalize-auth-reaponse-format.ts create mode 100644 src/shared/actions/finalize-message-signature-format.ts create mode 100644 src/shared/actions/finalize-tx-signature-format.ts diff --git a/.github/workflows/publish-extensions.yml b/.github/workflows/publish-extensions.yml index b9b51283..ce09212b 100644 --- a/.github/workflows/publish-extensions.yml +++ b/.github/workflows/publish-extensions.yml @@ -86,6 +86,9 @@ jobs: - create-github-release outputs: publish_status: ${{ steps.publish-chrome.outputs.publish_status }} + env: + MINIFY_PRODUCTION_BUILD: false + TARGET_BROWSER: chromium steps: - name: Check out Git repository uses: actions/checkout@v3 @@ -131,6 +134,7 @@ jobs: runs-on: ubuntu-latest env: MINIFY_PRODUCTION_BUILD: true + TARGET_BROWSER: firefox if: startsWith(github.ref, 'refs/tags/v') needs: - pre-run diff --git a/scripts/generate-manifest.js b/scripts/generate-manifest.js index 788ec17a..81ec20b9 100644 --- a/scripts/generate-manifest.js +++ b/scripts/generate-manifest.js @@ -1,10 +1,17 @@ +/** + * @typedef {import('@schemastore/web-manifest').JSONSchemaForWebApplicationManifestFiles} Manifest + */ const deepMerge = require('deepmerge'); const IS_DEV = process.env.NODE_ENV === 'development'; +console.log(process.env.NODE_ENV); + const PREVIEW_RELEASE = process.env.PREVIEW_RELEASE; -function generateImageAssetUrlsWithSuffix(suffix) { +const TARGET_BROWSER = process.env.TARGET_BROWSER ?? 'chromium'; + +function generateImageAssetUrlsWithSuffix(suffix = '') { return { 128: `assets/connect-logo/Stacks128w${suffix}.png`, 256: `assets/connect-logo/Stacks256w${suffix}.png`, @@ -12,6 +19,68 @@ function generateImageAssetUrlsWithSuffix(suffix) { }; } +const environmentIcons = { + development: { + icons: generateImageAssetUrlsWithSuffix('-dev'), + }, + production: { + icons: generateImageAssetUrlsWithSuffix(PREVIEW_RELEASE ? '-preview' : ''), + }, +}; + +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';", +}; + +const defaultIconEnvironment = { + development: 'assets/connect-logo/Stacks128w-dev.png', + production: 'assets/connect-logo/Stacks128w.png', +}; + +const browserSpecificConfig = { + firefox: { + manifest_version: 2, + permissions: ['contextMenus', 'storage', '*://*/*'], + background: { + page: 'background.js', + }, + browser_action: { + default_title: 'Stacks', + default_popup: 'popup.html', + default_icon: defaultIconEnvironment[process.env.NODE_ENV], + }, + content_security_policy: contentSecurityPolicyEnvironment[process.env.NODE_ENV], + browser_specific_settings: { + gecko: { + id: '{e22ae397-03d7-4622-bd8f-ecaca8c9b277}', + }, + }, + }, + chromium: { + manifest_version: 3, + host_permissions: ['*://*/*'], + permissions: ['contextMenus', 'storage'], + background: { + service_worker: 'background.js', + type: 'module', + }, + action: { + default_title: 'Stacks', + default_popup: 'popup.html', + default_icon: defaultIconEnvironment[process.env.NODE_ENV], + }, + content_security_policy: { + extension_pages: contentSecurityPolicyEnvironment[process.env.NODE_ENV], + }, + }, +}; + +/** + * @type {Manifest} manifest + */ const manifest = { author: 'Hiro PBC', description: 'The most popular and trusted wallet for apps built on Bitcoin', @@ -21,7 +90,7 @@ const manifest = { scripts: ['browser-polyfill.js', 'background.js'], persistent: true, }, - web_accessible_resources: ['inpage.js'], + web_accessible_resources: [{ resources: ['inpage.js'], matches: ['*://*/*'] }], browser_action: { default_title: 'Hiro Wallet', default_popup: 'popup.html', @@ -46,21 +115,10 @@ const manifest = { all_frames: true, }, ], - browser_specific_settings: { - gecko: { - id: '{e22ae397-03d7-4622-bd8f-ecaca8c9b277}', - }, - }, }; const devManifest = { name: 'Hiro Wallet Dev', - content_security_policy: - "script-src 'self' 'unsafe-eval'; object-src 'self'; frame-src 'none'; frame-ancestors 'none';", - icons: generateImageAssetUrlsWithSuffix('-dev'), - browser_action: { - default_icon: 'assets/connect-logo/Stacks128w-dev.png', - }, }; const name = PREVIEW_RELEASE ? 'Hiro Wallet Preview' : 'Hiro Wallet'; @@ -77,9 +135,23 @@ const prodManifest = { }, }; -module.exports = packageVersion => { +function generateManifest(packageVersion) { if (!packageVersion) throw new Error('Version number must be passed to `generateManifest` function'); + const version = packageVersion.includes('-') ? packageVersion.split('-')[0] : packageVersion; - return deepMerge.all([{ version }, manifest, IS_DEV ? devManifest : prodManifest]); -}; + + const releaseEnvironmentConfig = IS_DEV ? devManifest : prodManifest; + + const browserConfig = browserSpecificConfig[TARGET_BROWSER]; + + return deepMerge.all([ + { version }, + manifest, + releaseEnvironmentConfig, + browserConfig, + environmentIcons[process.env.NODE_ENV], + ]); +} + +module.exports = generateManifest; diff --git a/src/app/index.tsx b/src/app/index.tsx index c133e2d7..8959c40a 100755 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -26,6 +26,7 @@ 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); diff --git a/src/background/background.ts b/src/background/background.ts index 7af70ed1..5cbcf08e 100755 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -1,13 +1,19 @@ -import * as Sentry from '@sentry/react'; - +// +// This file is the entrypoint to the extension's background script +// https://developer.chrome.com/docs/extensions/mv3/architecture-overview/#background_script import { logger } from '@shared/logger'; -import { CONTENT_SCRIPT_PORT, LegacyMessageFromContentScript } from '@shared/message-types'; +import { CONTENT_SCRIPT_PORT } from '@shared/message-types'; +import type { LegacyMessageFromContentScript } from '@shared/message-types'; import { RouteUrls } from '@shared/route-urls'; +<<<<<<< HEAD import { WalletRequests } from '@shared/rpc/rpc-methods'; import { initSentry } from '@shared/utils/analytics'; +||||||| parent of 6c42e7042 (refactor: migrate to manifest version 3) +import { initSentry } from '@shared/utils/analytics'; +======= +>>>>>>> 6c42e7042 (refactor: migrate to manifest version 3) import { warnUsersAboutDevToolsDangers } from '@shared/utils/dev-tools-warning-log'; -import { backupOldWalletSalt } from './backup-old-wallet-salt'; import { initContextMenuActions } from './init-context-menus'; import { internalBackgroundMessageHandler } from './messaging/internal-methods/message-handler'; import { @@ -18,36 +24,32 @@ import { rpcMessageHandler } from './messaging/rpc-message-handler'; initSentry(); initContextMenuActions(); -backupOldWalletSalt(); warnUsersAboutDevToolsDangers(); const IS_TEST_ENV = process.env.TEST_ENV === 'true'; -chrome.runtime.onInstalled.addListener(details => { - Sentry.wrap(async () => { - if (details.reason === 'install' && !IS_TEST_ENV) { - await chrome.tabs.create({ - url: chrome.runtime.getURL(`index.html#${RouteUrls.Onboarding}`), - }); - } - }); +chrome.runtime.onInstalled.addListener(async details => { + if (details.reason === 'install' && !IS_TEST_ENV) { + await chrome.tabs.create({ + url: chrome.runtime.getURL(`index.html#${RouteUrls.Onboarding}`), + }); + } }); // // Listen for connection to the content-script - port for two-way communication -chrome.runtime.onConnect.addListener(port => - Sentry.wrap(() => { - if (port.name !== CONTENT_SCRIPT_PORT) return; +chrome.runtime.onConnect.addListener(port => { + if (port.name !== CONTENT_SCRIPT_PORT) return; port.onMessage.addListener((message: LegacyMessageFromContentScript | WalletRequests, port) => { if (!port.sender?.tab?.id) return logger.error('Message reached background script without a corresponding tab'); - // Chromium/Firefox discrepancy - const originUrl = port.sender?.origin ?? port.sender?.url; + // Chromium/Firefox discrepancy + const originUrl = port.sender?.origin ?? port.sender?.url; - if (!originUrl) - return logger.error('Message reached background script without a corresponding origin'); + if (!originUrl) + return logger.error('Message reached background script without a corresponding origin'); // Legacy JWT format messages if (isLegacyMessage(message)) { @@ -65,15 +67,14 @@ chrome.runtime.onConnect.addListener(port => // // Events from the extension frames script -chrome.runtime.onMessage.addListener((message, sender, sendResponse) => - Sentry.wrap(() => { - void internalBackgroundMessageHandler(message, sender, sendResponse); - // Listener fn must return `true` to indicate the response will be async - return true; - }) -); +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + void internalBackgroundMessageHandler(message, sender, sendResponse); + // Listener fn must return `true` to indicate the response will be async + return true; +}); if (IS_TEST_ENV) { // Expose a helper function to open a new tab with the wallet from tests - (window as any).openOptionsPage = (page: string) => chrome.runtime.getURL(`index.html#${page}`); + (globalThis as any).openOptionsPage = (page: string) => + chrome.runtime.getURL(`index.html#${page}`); } diff --git a/src/background/init-context-menus.ts b/src/background/init-context-menus.ts index 3ed732ed..e20b1bfa 100644 --- a/src/background/init-context-menus.ts +++ b/src/background/init-context-menus.ts @@ -6,10 +6,14 @@ export function initContextMenuActions() { chrome.contextMenus.removeAll(); chrome.contextMenus.create({ + id: 'open_in_new_tab', title: 'Open Hiro Wallet in a new tab', - contexts: ['browser_action'], - async onclick() { - await openNewTabWithWallet(); - }, + contexts: ['action'], }); } + +chrome.contextMenus.onClicked.addListener(async args => { + if (args.menuItemId === 'open_in_new_tab') { + await openNewTabWithWallet(); + } +}); diff --git a/src/background/messaging/internal-methods/message-handler.ts b/src/background/messaging/internal-methods/message-handler.ts index e2721143..b08eb50c 100644 --- a/src/background/messaging/internal-methods/message-handler.ts +++ b/src/background/messaging/internal-methods/message-handler.ts @@ -1,3 +1,4 @@ +// import { StacksMainnet } from '@stacks/network'; import { generateNewAccount, generateWallet } from '@stacks/wallet-sdk'; import memoize from 'promise-memoize'; @@ -5,8 +6,6 @@ import { logger } from '@shared/logger'; import { InternalMethods } from '@shared/message-types'; import { BackgroundMessages } from '@shared/messages'; -import { backupWalletSaltForGaia } from '../../backup-old-wallet-salt'; - function validateMessagesAreFromExtension(sender: chrome.runtime.MessageSender) { // Only respond to internal messages from our UI, not content scripts in other applications return sender.url?.startsWith(chrome.runtime.getURL('')); @@ -36,6 +35,7 @@ export async function internalBackgroundMessageHandler( sender: chrome.runtime.MessageSender, sendResponse: (response?: any) => void ) { + logger.info('Internal msg', message); if (!validateMessagesAreFromExtension(sender)) { logger.error('Error: Received background script msg from ' + sender.url); return; @@ -52,7 +52,7 @@ export async function internalBackgroundMessageHandler( case InternalMethods.ShareInMemoryKeyToBackground: { const { keyId, secretKey } = message.payload; inMemoryKeys.set(keyId, secretKey); - await backupWalletSaltForGaia(secretKey); + // await backupWalletSaltForGaia(secretKey); break; } diff --git a/src/background/messaging/legacy/legacy-external-message-handler.ts b/src/background/messaging/legacy/legacy-external-message-handler.ts index 82412330..3c9e548e 100644 --- a/src/background/messaging/legacy/legacy-external-message-handler.ts +++ b/src/background/messaging/legacy/legacy-external-message-handler.ts @@ -1,8 +1,8 @@ -import { formatAuthResponse } from '@shared/actions/finalize-auth-response'; +import { formatAuthResponse } from '@shared/actions/finalize-auth-reaponse-format'; import { formatMessageSigningResponse } from '@shared/actions/finalize-message-signature'; import { formatProfileUpdateResponse } from '@shared/actions/finalize-profile-update'; import { formatPsbtResponse } from '@shared/actions/finalize-psbt'; -import { formatTxSignatureResponse } from '@shared/actions/finalize-tx-signature'; +import { formatTxSignatureResponse } from '@shared/actions/finalize-tx-signature-format'; import { ExternalMethods, LegacyMessageFromContentScript } from '@shared/message-types'; import { RouteUrls } from '@shared/route-urls'; import { getCoreApiUrl, getPayloadFromToken } from '@shared/utils/requests'; diff --git a/src/background/popup-center.ts b/src/background/popup-center.ts index adde9636..5720a460 100644 --- a/src/background/popup-center.ts +++ b/src/background/popup-center.ts @@ -1,5 +1,3 @@ -import type { Windows } from 'webextension-polyfill'; - import { POPUP_CENTER_HEIGHT, POPUP_CENTER_WIDTH } from '@shared/constants'; interface PopupOptions { @@ -9,7 +7,7 @@ interface PopupOptions { h?: number; skipPopupFallback?: boolean; } -export function popupCenter(options: PopupOptions): Promise { +export function popupCenter(options: PopupOptions): Promise { const { url, w = POPUP_CENTER_WIDTH, h = POPUP_CENTER_HEIGHT } = options; return new Promise(resolve => { @@ -17,17 +15,17 @@ export function popupCenter(options: PopupOptions): Promise { chrome.windows.getCurrent(async win => { // these units take into account the distance from // the farthest left/top sides of all displays - const dualScreenLeft = win.left || window.screenLeft; - const dualScreenTop = win.top || window.screenTop; + const dualScreenLeft = win.left ?? 0; + const dualScreenTop = win.top ?? 0; // dimensions of the window that originated the action - const width = win.width || window.innerWidth; - const height = win.height || window.innerHeight; + const width = win.width ?? 0; + const height = win.height ?? 0; const left = Math.floor(width / 2 - w / 2 + dualScreenLeft); const top = Math.floor(height / 2 - h / 2 + dualScreenTop); - const popup = await browser.windows.create({ + const popup = await chrome.windows.create({ url, width: w, height: h, diff --git a/src/shared/actions/finalize-auth-reaponse-format.ts b/src/shared/actions/finalize-auth-reaponse-format.ts new file mode 100644 index 00000000..d86f0517 --- /dev/null +++ b/src/shared/actions/finalize-auth-reaponse-format.ts @@ -0,0 +1,23 @@ +import { + AuthenticationResponseMessage, + ExternalMethods, + MESSAGE_SOURCE, +} from '@shared/message-types'; + +interface FormatAuthResponseArgs { + request: string; + response: string; +} +export function formatAuthResponse({ + request, + response, +}: FormatAuthResponseArgs): AuthenticationResponseMessage { + return { + source: MESSAGE_SOURCE, + payload: { + authenticationRequest: request, + authenticationResponse: response, + }, + method: ExternalMethods.authenticationResponse, + }; +} diff --git a/src/shared/actions/finalize-auth-response.ts b/src/shared/actions/finalize-auth-response.ts index a8ea6570..12f5befb 100644 --- a/src/shared/actions/finalize-auth-response.ts +++ b/src/shared/actions/finalize-auth-response.ts @@ -1,29 +1,8 @@ -import { - AuthenticationResponseMessage, - ExternalMethods, - MESSAGE_SOURCE, -} from '@shared/message-types'; import { DecodedAuthRequest } from '@shared/models/decoded-auth-request'; import { analytics } from '@shared/utils/analytics'; import { isValidUrl } from '@shared/utils/validate-url'; -interface FormatAuthResponseArgs { - request: string; - response: string; -} -export function formatAuthResponse({ - request, - response, -}: FormatAuthResponseArgs): AuthenticationResponseMessage { - return { - source: MESSAGE_SOURCE, - payload: { - authenticationRequest: request, - authenticationResponse: response, - }, - method: ExternalMethods.authenticationResponse, - }; -} +import { formatAuthResponse } from './finalize-auth-reaponse-format'; interface FinalizeAuthParams { decodedAuthRequest: DecodedAuthRequest; diff --git a/src/shared/actions/finalize-message-signature-format.ts b/src/shared/actions/finalize-message-signature-format.ts new file mode 100644 index 00000000..29529a1b --- /dev/null +++ b/src/shared/actions/finalize-message-signature-format.ts @@ -0,0 +1,17 @@ +import { ExternalMethods, MESSAGE_SOURCE, SignatureResponseMessage } from '@shared/message-types'; +import { SignatureData } from '@stacks/connect'; + +interface FormatMessageSigningResponseArgs { + request: string; + response: SignatureData | string; +} +export function formatMessageSigningResponse({ + request, + response, +}: FormatMessageSigningResponseArgs): SignatureResponseMessage { + return { + source: MESSAGE_SOURCE, + method: ExternalMethods.signatureResponse, + payload: { signatureRequest: request, signatureResponse: response }, + }; +} diff --git a/src/shared/actions/finalize-tx-signature-format.ts b/src/shared/actions/finalize-tx-signature-format.ts new file mode 100644 index 00000000..cc582679 --- /dev/null +++ b/src/shared/actions/finalize-tx-signature-format.ts @@ -0,0 +1,24 @@ +import { + ExternalMethods, + MESSAGE_SOURCE, + TransactionResponseMessage, + TxResult, +} from '@shared/message-types'; + +interface FormatTxSignatureResponseArgs { + payload: string; + response: TxResult | 'cancel'; +} +export function formatTxSignatureResponse({ + payload, + response, +}: FormatTxSignatureResponseArgs): TransactionResponseMessage { + return { + source: MESSAGE_SOURCE, + method: ExternalMethods.transactionResponse, + payload: { + transactionRequest: payload, + transactionResponse: response, + }, + }; +} diff --git a/src/shared/actions/finalize-tx-signature.ts b/src/shared/actions/finalize-tx-signature.ts index 8bd1e480..7aa71bcb 100644 --- a/src/shared/actions/finalize-tx-signature.ts +++ b/src/shared/actions/finalize-tx-signature.ts @@ -1,29 +1,8 @@ import { logger } from '@shared/logger'; -import { - ExternalMethods, - MESSAGE_SOURCE, - TransactionResponseMessage, - TxResult, -} from '@shared/message-types'; +import { TxResult } from '@shared/message-types'; import { analytics } from '@shared/utils/analytics'; +import { formatTxSignatureResponse } from './finalize-tx-signature-format'; -interface FormatTxSignatureResponseArgs { - payload: string; - response: TxResult | 'cancel'; -} -export function formatTxSignatureResponse({ - payload, - response, -}: FormatTxSignatureResponseArgs): TransactionResponseMessage { - return { - source: MESSAGE_SOURCE, - method: ExternalMethods.transactionResponse, - payload: { - transactionRequest: payload, - transactionResponse: response, - }, - }; -} interface FinalizeTxSignatureArgs { requestPayload: string; data: TxResult | 'cancel'; diff --git a/webpack/webpack.config.base.js b/webpack/webpack.config.base.js index d2fe1423..84e31c9c 100755 --- a/webpack/webpack.config.base.js +++ b/webpack/webpack.config.base.js @@ -106,6 +106,7 @@ const config = { }, optimization: { minimize: false, + usedExports: true, splitChunks: { chunks: 'all', name: 'common', diff --git a/webpack/webpack.config.dev.js b/webpack/webpack.config.dev.js index 22d248f3..176ce810 100644 --- a/webpack/webpack.config.dev.js +++ b/webpack/webpack.config.dev.js @@ -3,7 +3,7 @@ const baseConfig = require('./webpack.config.base'); const config = { ...baseConfig, - devtool: 'eval-cheap-module-source-map', + devtool: 'inline-source-map', mode: 'development', output: { ...baseConfig.output,