fix: route extension sync through configured server

This commit is contained in:
syrup 2026-09-02 09:28:05 +08:00
parent bcbdc321c8
commit d98b672b2e
9 changed files with 115 additions and 19 deletions

View File

@ -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": {

View File

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

View File

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

View File

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

View File

@ -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<ExportExcelPipeline>((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`;

View File

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

View File

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

View File

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

View File

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