From d98b672b2e0fe24ca66b9cb69ba273511fb289d5 Mon Sep 17 00:00:00 2001 From: syrup <1032771964@qq.com> Date: Wed, 2 Sep 2026 09:28:05 +0800 Subject: [PATCH] fix: route extension sync through configured server --- package.json | 2 ++ scripts/test-remote-endpoint.ts | 34 ++++++++++++++++++++++++++ scripts/verify-production-endpoint.mjs | 29 ++++++++++++++++++++++ src/background/amazon-sync.ts | 4 +-- src/composables/useCloudExporter.ts | 12 +++------ src/env.ts | 14 ++++++++--- src/logic/remote-endpoint.ts | 28 +++++++++++++++++++++ src/logic/upload-image.ts | 7 +++--- src/page-worker/services/base.ts | 4 +-- 9 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 scripts/test-remote-endpoint.ts create mode 100644 scripts/verify-production-endpoint.mjs create mode 100644 src/logic/remote-endpoint.ts diff --git a/package.json b/package.json index b05926a..7483fe7 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,8 @@ "test:amazon-page-navigation": "esno scripts/test-amazon-page-navigation.ts", "test:execute-script": "esno scripts/test-execute-script.ts", "test:extension-runtime": "node scripts/verify-extension-runtime.mjs", + "test:remote-endpoint": "esno scripts/test-remote-endpoint.ts", + "test:production-endpoint": "node scripts/verify-production-endpoint.mjs", "prepare": "husky" }, "devDependencies": { diff --git a/scripts/test-remote-endpoint.ts b/scripts/test-remote-endpoint.ts new file mode 100644 index 0000000..36a8b15 --- /dev/null +++ b/scripts/test-remote-endpoint.ts @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict'; +import { resolveRemoteEndpoint } from '../src/logic/remote-endpoint'; + +assert.deepEqual(resolveRemoteEndpoint({ isDev: true }), { + host: '127.0.0.1:18000', + httpBaseUrl: 'http://127.0.0.1:18000', + webSocketBaseUrl: 'ws://127.0.0.1:18000', +}); + +assert.deepEqual(resolveRemoteEndpoint({ isDev: false }), { + host: 'amzplugin.rekeymed.com', + httpBaseUrl: 'https://amzplugin.rekeymed.com', + webSocketBaseUrl: 'wss://amzplugin.rekeymed.com', +}); + +assert.deepEqual( + resolveRemoteEndpoint({ + isDev: true, + host: 'staging.example.test:8443', + protocol: 'https', + }), + { + host: 'staging.example.test:8443', + httpBaseUrl: 'https://staging.example.test:8443', + webSocketBaseUrl: 'wss://staging.example.test:8443', + }, +); + +assert.throws( + () => resolveRemoteEndpoint({ isDev: true, protocol: 'ftp' }), + /Unsupported remote protocol/, +); + +console.log('Remote endpoint tests passed.'); diff --git a/scripts/verify-production-endpoint.mjs b/scripts/verify-production-endpoint.mjs new file mode 100644 index 0000000..083f9ef --- /dev/null +++ b/scripts/verify-production-endpoint.mjs @@ -0,0 +1,29 @@ +import { readdir, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +const background = await readFile('extension/dist/background/index.mjs', 'utf8'); +async function readJavaScriptFiles(directory) { + const entries = await readdir(directory, { withFileTypes: true }); + const contents = await Promise.all( + entries.map(async (entry) => { + const path = join(directory, entry.name); + if (entry.isDirectory()) return readJavaScriptFiles(path); + if (!/\.(?:m?js)$/.test(entry.name)) return ''; + return readFile(path, 'utf8'); + }), + ); + return contents.flat().join('\n'); +} +const allJavaScript = await readJavaScriptFiles('extension/dist'); + +if (!background.includes('amzplugin.rekeymed.com')) { + throw new Error('Production background bundle does not target the public server.'); +} +if (background.includes('127.0.0.1:18000')) { + throw new Error('Production background bundle still contains the local API endpoint.'); +} +if (!allJavaScript.includes('wss://') || !background.includes('https://')) { + throw new Error('Production bundle is missing secure HTTP/WebSocket protocols.'); +} + +console.log('Production endpoint verification passed.'); diff --git a/src/background/amazon-sync.ts b/src/background/amazon-sync.ts index 1bf7b7e..1f51e80 100644 --- a/src/background/amazon-sync.ts +++ b/src/background/amazon-sync.ts @@ -1,7 +1,7 @@ import { onMessage } from 'webext-bridge/background'; import { flattenObject } from '~/logic/convert'; import { exportKey, generateKeyPair, signData } from '~/logic/crypto'; -import { remoteHost } from '~/env'; +import { remoteBaseUrl } from '~/env'; import { AMAZON_SYNC_ALARM, AMAZON_SYNC_QUEUE_KEY, @@ -56,7 +56,7 @@ async function signedPost(path: string, data: unknown[]) { if (data.length === 0) return; const body = JSON.stringify(data); const { privateKey } = await keyPairPromise; - const response = await fetch(`https://${remoteHost}${path}`, { + const response = await fetch(`${remoteBaseUrl}${path}`, { method: 'POST', headers: { 'content-type': 'application/json', diff --git a/src/composables/useCloudExporter.ts b/src/composables/useCloudExporter.ts index 2e6f985..dd18f3d 100644 --- a/src/composables/useCloudExporter.ts +++ b/src/composables/useCloudExporter.ts @@ -1,4 +1,4 @@ -import { remoteHost } from '~/env'; +import { remoteBaseUrl, remoteWebSocketBaseUrl } from '~/env'; import { useLongTask } from './useLongTask'; type AddDataFragmentsCommand = { @@ -16,16 +16,12 @@ type WebSocketResponse = | { type: 'progress'; current: number; total: number } | { type: 'result'; result: string }; -// 使用 wss:// 和 https:// -const protocol = __DEV__ ? 'ws' : 'wss'; -const httpProtocol = __DEV__ ? 'http' : 'https'; - class ExportExcelPipeline { private socket: WebSocket; constructor() { this.socket = new WebSocket( - `${protocol}://${remoteHost}/ws/daa0b9f1-4e4a-4e7c-9269-f5f0e86ae271`, + `${remoteWebSocketBaseUrl}/ws/daa0b9f1-4e4a-4e7c-9269-f5f0e86ae271`, ); } @@ -39,7 +35,7 @@ class ExportExcelPipeline { case WebSocket.CLOSED: case WebSocket.CLOSING: this.socket = new WebSocket( - `${protocol}://${remoteHost}/ws/daa0b9f1-4e4a-4e7c-9269-f5f0e86ae271`, + `${remoteWebSocketBaseUrl}/ws/daa0b9f1-4e4a-4e7c-9269-f5f0e86ae271`, ); case WebSocket.CONNECTING: return new Promise((resolve) => { @@ -126,7 +122,7 @@ export const useCloudExporter = () => { await pipeline.close(); if (file) { - const url = `${httpProtocol}://${remoteHost}${file}`; + const url = `${remoteBaseUrl}${file}`; const link = document.createElement('a'); link.href = url; link.download = `${dayjs().format('YYYY-MM-DD')}.xlsx`; diff --git a/src/env.ts b/src/env.ts index ae171ac..66eeb81 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,3 +1,5 @@ +import { resolveRemoteEndpoint } from '~/logic/remote-endpoint'; + const forbiddenProtocols = [ 'chrome-extension://', 'chrome-search://', @@ -14,6 +16,12 @@ export function isForbiddenUrl(url: string): boolean { export const isFirefox = navigator.userAgent.includes('Firefox'); -export const remoteHost = - import.meta.env.VITE_REMOTE_HOST || - (__DEV__ ? '127.0.0.1:18000' : 'amzplugin.rekeymed.com'); +const remoteEndpoint = resolveRemoteEndpoint({ + isDev: __DEV__, + host: import.meta.env.VITE_REMOTE_HOST, + protocol: import.meta.env.VITE_REMOTE_PROTOCOL, +}); + +export const remoteHost = remoteEndpoint.host; +export const remoteBaseUrl = remoteEndpoint.httpBaseUrl; +export const remoteWebSocketBaseUrl = remoteEndpoint.webSocketBaseUrl; diff --git a/src/logic/remote-endpoint.ts b/src/logic/remote-endpoint.ts new file mode 100644 index 0000000..b5f59c2 --- /dev/null +++ b/src/logic/remote-endpoint.ts @@ -0,0 +1,28 @@ +type RemoteEndpointOptions = { + isDev: boolean; + host?: string; + protocol?: string; +}; + +export type RemoteEndpoint = { + host: string; + httpBaseUrl: string; + webSocketBaseUrl: string; +}; + +export function resolveRemoteEndpoint({ + isDev, + host, + protocol, +}: RemoteEndpointOptions): RemoteEndpoint { + const resolvedHost = host || (isDev ? '127.0.0.1:18000' : 'amzplugin.rekeymed.com'); + const resolvedProtocol = protocol || (isDev ? 'http' : 'https'); + if (resolvedProtocol !== 'http' && resolvedProtocol !== 'https') { + throw new Error(`Unsupported remote protocol: ${resolvedProtocol}`); + } + return { + host: resolvedHost, + httpBaseUrl: `${resolvedProtocol}://${resolvedHost}`, + webSocketBaseUrl: `${resolvedProtocol === 'https' ? 'wss' : 'ws'}://${resolvedHost}`, + }; +} diff --git a/src/logic/upload-image.ts b/src/logic/upload-image.ts index 8ede900..eea9e73 100644 --- a/src/logic/upload-image.ts +++ b/src/logic/upload-image.ts @@ -1,5 +1,5 @@ /** 上传图片工具类 */ -import { remoteHost } from '~/env'; +import { remoteBaseUrl } from '~/env'; export async function uploadImage( base64String: string, @@ -23,8 +23,7 @@ export async function uploadImage( const formData = new FormData(); formData.append('file', blob, filename); - const httpProtocol = __DEV__ ? 'http' : 'https'; - const url = `${httpProtocol}://${remoteHost}/upload/image/${encodeURIComponent(filename)}`; + const url = `${remoteBaseUrl}/upload/image/${encodeURIComponent(filename)}`; const resp = (await fetch(url, { method: 'POST', body: formData, @@ -33,5 +32,5 @@ export async function uploadImage( return undefined; } const data = await resp.json(); - return `${httpProtocol}://${remoteHost}${data.file}`; + return `${remoteBaseUrl}${data.file}`; } diff --git a/src/page-worker/services/base.ts b/src/page-worker/services/base.ts index 0ead32c..9e487b8 100644 --- a/src/page-worker/services/base.ts +++ b/src/page-worker/services/base.ts @@ -1,6 +1,6 @@ import { createAlova } from 'alova'; import adapterFetch from 'alova/fetch'; -import { remoteHost } from '~/env'; +import { remoteBaseUrl } from '~/env'; import VueHook from 'alova/vue'; import { exportKey, generateKeyPair, signData } from '~/logic/crypto'; @@ -8,7 +8,7 @@ const keyPairPromise = generateKeyPair(); const publicKeyBase64Promise = keyPairPromise.then(async ({ publicKey }) => exportKey(publicKey)); const httpClient = createAlova({ - baseURL: `${__DEV__ ? 'http' : 'https'}://${remoteHost}`, + baseURL: remoteBaseUrl, requestAdapter: adapterFetch(), timeout: 10000, statesHook: VueHook,