refactor: migrate to manifest version 3

This commit is contained in:
kyranjamie
2021-08-16 13:36:12 +02:00
committed by kyranjamie
parent 78a434954c
commit 03dbb1eb94
15 changed files with 210 additions and 107 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Windows.Window> {
export function popupCenter(options: PopupOptions): Promise<any> {
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<Windows.Window> {
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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -106,6 +106,7 @@ const config = {
},
optimization: {
minimize: false,
usedExports: true,
splitChunks: {
chunks: 'all',
name: 'common',

View File

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