Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/cli/src/__tests__/app-request-headers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('CLI App request headers', () => {

expect(headers).toMatchObject({
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) OneKeyWallet/6.3.0 Chrome/142.0.7444.265 Electron/39.8.9 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) OneKeyWallet/6.3.0 Chrome/150.0.7871.114 Electron/43.1.1 Safari/537.36',
'x-onekey-hide-asset-details': 'false',
'x-onekey-instance-id': '45304ac0-2271-4b57-b7d8-ba111a627a6d',
'x-onekey-request-build-number': '2026043075',
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/infra/app-request-headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function buildCliAppUserAgent(): string {
return (
`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ` +
`AppleWebKit/537.36 (KHTML, like Gecko) OneKeyWallet/${version} ` +
`Chrome/142.0.7444.265 Electron/39.8.9 Safari/537.36`
`Chrome/150.0.7871.114 Electron/43.1.1 Safari/537.36`
);
}

Expand Down
24 changes: 9 additions & 15 deletions apps/desktop/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
import { ipcMessageKeys } from './config';
import { ElectronTranslations, i18nText, initLocale } from './i18n';
import { scheduleCrashDumpCleanup } from './libs/crashDumpCleanup';
import { findAllowedDeepLinkArg, isAllowedDeepLinkUrl } from './libs/deepLink';
import {
applyDesktopNetworkThrottleToKnownSessions,
applyDesktopNetworkThrottleToWebContents,
Expand Down Expand Up @@ -596,17 +597,9 @@ function handleDeepLinkUrl(
isColdStartup?: boolean,
) {
// Validate deep link scheme before forwarding to renderer
if (url) {
const allowedSchemes = [
`${ONEKEY_APP_DEEP_LINK_NAME}:`,
`${WALLET_CONNECT_DEEP_LINK_NAME}:`,
'ethereum:',
];
const isAllowed = allowedSchemes.some((scheme) => url.startsWith(scheme));
if (!isAllowed) {
logger.warn('[DeepLink] Rejected URL with unknown scheme:', url);
return;
}
if (url && !isAllowedDeepLinkUrl(url)) {
logger.warn('[DeepLink] Rejected URL with unknown scheme:', url);
return;
}

const eventData: IDesktopOpenUrlEventData = {
Expand Down Expand Up @@ -913,9 +906,10 @@ async function createMainWindow(opts?: { isSoftRestart?: boolean }) {
// original cold-start URL (e.g. a WalletConnect pairing or send screen) after
// every bundle update. Only a genuine cold boot should consume argv.
if ((isWin || isMac) && !isSoftRestart) {
// Keep only command line / deep linked arguments
const deeplinkingUrl = process.argv[1];
handleDeepLinkUrl(null, deeplinkingUrl, process.argv, true);
const deeplinkingUrl = findAllowedDeepLinkArg(process.argv);
if (deeplinkingUrl) {
handleDeepLinkUrl(null, deeplinkingUrl, process.argv, true);
}
}

browserWindow.webContents.on('unresponsive', () => {
Expand Down Expand Up @@ -1730,7 +1724,7 @@ if (!singleInstance && !process.mas) {

// Handle deep link arguments for all platforms
// argv: An array of the second instance's (command line / deep linked) arguments
const deeplinkingUrl = argv[1];
const deeplinkingUrl = findAllowedDeepLinkArg(argv);
if (deeplinkingUrl) {
// handleDeepLinkUrl internally calls showMainWindow(), so we don't need to call it separately
handleDeepLinkUrl(null, deeplinkingUrl, argv, false); // isColdStartup=false for second instance
Expand Down
32 changes: 32 additions & 0 deletions apps/desktop/app/libs/deepLink.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { findAllowedDeepLinkArg, isAllowedDeepLinkUrl } from './deepLink';

describe('desktop deep link arguments', () => {
test('finds the URL after Electron command-line switches', () => {
expect(
findAllowedDeepLinkArg([
'OneKey.exe',
'--allow-file-access-from-files',
'onekey-wallet://search/list?q=onekey',
]),
).toBe('onekey-wallet://search/list?q=onekey');
});

test.each([
'onekey-wallet://search/list?q=onekey',
'wc://pairing',
'ethereum:0x0000000000000000000000000000000000000000',
])('allows a registered deep link scheme: %s', (url) => {
expect(isAllowedDeepLinkUrl(url)).toBe(true);
});

test('does not treat Electron switches or unregistered URLs as deep links', () => {
expect(
findAllowedDeepLinkArg([
'OneKey.exe',
'--allow-file-access-from-files',
'https://example.com',
]),
).toBeUndefined();
expect(isAllowedDeepLinkUrl('https://example.com')).toBe(false);
});
});
20 changes: 20 additions & 0 deletions apps/desktop/app/libs/deepLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
ONEKEY_APP_DEEP_LINK_NAME,
WALLET_CONNECT_DEEP_LINK_NAME,
} from '@onekeyhq/shared/src/consts/deeplinkConsts';

const ALLOWED_DEEP_LINK_SCHEMES = [
`${ONEKEY_APP_DEEP_LINK_NAME}:`,
`${WALLET_CONNECT_DEEP_LINK_NAME}:`,
'ethereum:',
] as const;

export function isAllowedDeepLinkUrl(url: string): boolean {
return ALLOWED_DEEP_LINK_SCHEMES.some((scheme) => url.startsWith(scheme));
}

export function findAllowedDeepLinkArg(
argv: readonly string[],
): string | undefined {
return argv.find((arg) => isAllowedDeepLinkUrl(arg));
}
1 change: 0 additions & 1 deletion apps/desktop/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"passport-desktop": "0.1.2",
"passport-desktop-win32-x64-msvc": "0.1.2",
"systeminformation": "5.31.6",
"tr46": "0.0.3",
"validator": "13.15.23"
},
"resolutions": {
Expand Down
25 changes: 23 additions & 2 deletions apps/desktop/app/process/Bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ jest.mock('electron-log/main', () => ({
debug: jest.fn(),
}));

jest.mock('node-fetch', () => jest.fn());

jest.mock('../resoucePath', () => ({
getAppStaticResourcesPath: jest.fn(() => '/mock/resources'),
}));
Expand All @@ -25,6 +23,7 @@ import BridgeProcess, { BRIDGE_SUPPORTED_SYSTEMS } from './Bridge';

const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
const originalArch = Object.getOwnPropertyDescriptor(process, 'arch');
const originalFetch = globalThis.fetch;

type ILoggerMock = {
info: jest.Mock;
Expand Down Expand Up @@ -59,6 +58,7 @@ describe('BridgeProcess', () => {
afterEach(() => {
jest.clearAllMocks();
restorePlatform();
globalThis.fetch = originalFetch;
});

test('only supports Linux bridge systems', () => {
Expand Down Expand Up @@ -96,4 +96,25 @@ describe('BridgeProcess', () => {

expect(bridge.isCurrentSystemSupported()).toBe(true);
});

test('checks bridge status with the Node runtime fetch implementation', async () => {
const fetchMock = jest.fn().mockResolvedValue({
status: 200,
json: jest.fn().mockResolvedValue({ version: '2.0.0' }),
});
globalThis.fetch = fetchMock as unknown as typeof fetch;

const bridge = new BridgeProcess();

await expect(bridge.getStatus()).resolves.toEqual({
service: true,
process: true,
});
expect(fetchMock).toHaveBeenCalledWith('http://127.0.0.1:21320/', {
method: 'POST',
headers: {
Origin: 'https://electron.onekey.so',
},
});
});
});
20 changes: 10 additions & 10 deletions apps/desktop/app/process/Bridge.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import logger from 'electron-log/main';
import fetch from 'node-fetch';

import BaseProcess from './BaseProcess';

Expand All @@ -27,7 +26,7 @@ class BridgeProcess extends BaseProcess {
});
logger.debug(`Checking status (${resp.status})`);
if (resp.status === 200) {
const data = await resp.json();
const data = (await resp.json()) as { version?: unknown };
if (data?.version) {
return {
service: true,
Expand All @@ -52,17 +51,18 @@ async function fetchWithTimeout(
url: string,
options: RequestInit & { timeout: number },
) {
const { timeout = 3000 } = options;
const { timeout = 3000, ...requestOptions } = options;

const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
// @ts-expect-error
const response = await fetch(url, {
...options,
signal: controller.signal as any,
});
clearTimeout(id);
return response;
try {
return await fetch(url, {
...requestOptions,
signal: controller.signal,
});
} finally {
clearTimeout(id);
}
}

export const BridgeHeart = {
Expand Down
8 changes: 0 additions & 8 deletions apps/desktop/app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ __metadata:
passport-desktop: "npm:0.1.2"
passport-desktop-win32-x64-msvc: "npm:0.1.2"
systeminformation: "npm:5.31.6"
tr46: "npm:0.0.3"
validator: "npm:13.15.23"
languageName: unknown
linkType: soft
Expand Down Expand Up @@ -2049,13 +2048,6 @@ __metadata:
languageName: node
linkType: hard

"tr46@npm:0.0.3":
version: 0.0.3
resolution: "tr46@npm:0.0.3"
checksum: 10/8f1f5aa6cb232f9e1bdc86f485f916b7aa38caee8a778b378ffec0b70d9307873f253f5cbadbe2955ece2ac5c83d0dc14a77513166ccd0a0c7fe197e21396695
languageName: node
linkType: hard

"undici-types@npm:~8.3.0":
version: 8.3.0
resolution: "undici-types@npm:8.3.0"
Expand Down
6 changes: 2 additions & 4 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,9 @@
"electron-check-biometric-auth-changed": "1.0.0",
"electron-context-menu": "^3.5.0",
"electron-is-dev": "^2.0.0",
"electron-log": "5.2.0",
"electron-log": "5.4.4",
"electron-store": "^8.2.0",
"electron-updater": "6.8.9",
"node-fetch": "^2.6.7",
"passport-desktop": "0.1.2",
"passport-desktop-win32-x64-msvc": "0.1.2",
"semver": "7.5.4"
Expand All @@ -60,10 +59,9 @@
"@electron/remote": "^2.0.1",
"@types/adm-zip": "^0",
"@types/electron-localshortcut": "^3.1.0",
"@types/node-fetch": "^2.6.1",
"concurrently": "9.2.1",
"cross-env": "7.0.3",
"electron": "39.8.9",
"electron": "43.1.1",
"electron-builder": "26.4.0",
"esbuild": "0.27.2",
"glob": "^7.2.0",
Expand Down
55 changes: 55 additions & 0 deletions apps/desktop/scripts/apply-runtime-patches.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const path = require('path');
const {
electronUpdaterRuntimePatchFiles,
} = require('./electron-updater-runtime-patch-files');
const {
thirdPartyRuntimePatchPackages,
} = require('./third-party-runtime-patch-files');

function failPatch(message) {
process.stderr.write(`${message}\n`);
Expand Down Expand Up @@ -72,3 +75,55 @@ for (const relativePath of electronUpdaterRuntimePatchFiles) {
process.stdout.write(
`Applied electron-updater ${runtimePackage.version} runtime patch.\n`,
);

for (const { packageName, relativePaths } of thirdPartyRuntimePatchPackages) {
const runtimeRoot = path.join(
desktopPackageRoot,
'app/node_modules',
packageName,
);
const runtimeJsonPath = path.join(runtimeRoot, 'package.json');
let workspaceJsonPath;
try {
workspaceJsonPath = path.join(
path.dirname(
require.resolve(packageName, {
paths: [desktopPackageRoot],
}),
),
'package.json',
);
} catch {
failPatch(
`Workspace ${packageName} dependency cannot be resolved from ${desktopPackageRoot}. Run yarn install first.`,
);
}
const workspaceRoot = path.dirname(workspaceJsonPath);
const workspaceMetadata = readPackageMetadata(
workspaceJsonPath,
`Workspace ${packageName} package metadata`,
);
const runtimeMetadata = readPackageMetadata(
runtimeJsonPath,
`Runtime ${packageName} package metadata`,
);
if (runtimeMetadata.version !== workspaceMetadata.version) {
failPatch(
`${packageName} version mismatch: runtime=${runtimeMetadata.version}, workspace=${workspaceMetadata.version}.`,
);
}
for (const relativePath of relativePaths) {
const sourcePath = path.join(workspaceRoot, relativePath);
const destinationPath = path.join(runtimeRoot, relativePath);
if (!fs.existsSync(sourcePath)) {
failPatch(`Workspace ${packageName} file is missing: ${sourcePath}.`);
}
if (!fs.existsSync(destinationPath)) {
failPatch(`Runtime ${packageName} file is missing: ${destinationPath}.`);
}
fs.copyFileSync(sourcePath, destinationPath);
}
process.stdout.write(
`Applied ${packageName} ${runtimeMetadata.version} runtime patch.\n`,
);
}
13 changes: 6 additions & 7 deletions apps/desktop/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,14 @@ build({
// XML stack, js-yaml) out of app.js parse.
'electron-updater',
'adm-zip',
// Tier 2: large lookup-table deps reached transitively via node-fetch /
// whatwg-url (tr46 IDNA table) and the local HTTP server (mime-db, validator).
// FOOTGUN: these three are *transitive* — no app code imports them directly.
// Tier 2: large lookup-table deps reached transitively via the local HTTP
// server (mime-db, validator).
// FOOTGUN: these are *transitive* — no app code imports them directly.
// esbuild leaves a bare `require('<name>')` and only ONE copy ships in the
// asar, so the shipped version is whatever is pinned in app/package.json, NOT
// what yarn.lock resolves for the real consumers. When bumping node-fetch /
// whatwg-url / the http stack, re-check that these pins still match the
// resolved transitive versions, or the asar will ship a mismatched copy.
'tr46',
// what yarn.lock resolves for the real consumers. When bumping the HTTP
// stack, re-check that these pins still match the resolved transitive
// versions, or the asar will ship a mismatched copy.
'mime-db',
'validator',
...Object.keys(pkg.dependencies),
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/scripts/node-runtime-harness-summary.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ const createPassingReport = () => ({
driftsAfterAppInit: [],
driftsAfterRepair: [],
driftsBeforeRepair: [],
electron: '39.8.9',
electron: '43.1.1',
isPackaged: true,
node: '22.22.1',
node: '24.18.0',
nodeRuntimeCheckNames: ['global.Buffer', 'node:fs.readFile'],
platform: 'linux',
processType: 'browser',
Expand Down
14 changes: 14 additions & 0 deletions apps/desktop/scripts/third-party-runtime-patch-files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const thirdPartyRuntimePatchPackages = [
{
packageName: '@sentry/electron',
relativePaths: [
'esm/main/integrations/preload-injection.js',
'main/integrations/preload-injection.js',
],
requiredMarkers: ['registerPreloadScript'],
},
];

module.exports = {
thirdPartyRuntimePatchPackages,
};
Loading
Loading