mirror of
https://github.com/zhigang1992/wallet.git
synced 2026-01-12 17:53:19 +08:00
refactor: migrate to manifest version 3
This commit is contained in:
4
.github/workflows/publish-extensions.yml
vendored
4
.github/workflows/publish-extensions.yml
vendored
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
23
src/shared/actions/finalize-auth-reaponse-format.ts
Normal file
23
src/shared/actions/finalize-auth-reaponse-format.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
17
src/shared/actions/finalize-message-signature-format.ts
Normal file
17
src/shared/actions/finalize-message-signature-format.ts
Normal 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 },
|
||||
};
|
||||
}
|
||||
24
src/shared/actions/finalize-tx-signature-format.ts
Normal file
24
src/shared/actions/finalize-tx-signature-format.ts
Normal 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -106,6 +106,7 @@ const config = {
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
usedExports: true,
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
name: 'common',
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user