mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 20:14:30 +00:00
fix(browser): land PR #21277 dedupe concurrent relay init
Add shared per-port relay initialization dedupe so concurrent callers await a single startup lifecycle, with regression coverage and changelog entry. Landed from contributor @HOYALIM (PR #21277). Co-authored-by: Ho Lim <subhoya@gmail.com>
This commit is contained in:
@@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Browser/Extension relay auth: allow `?token=` query-param auth on relay `/json*` endpoints (consistent with relay WebSocket auth) so curl/devtools-style `/json/version` and `/json/list` probes work without requiring custom headers. Landed from contributor PR #26015 by @Sid-Qin. (#25928)
|
- Browser/Extension relay auth: allow `?token=` query-param auth on relay `/json*` endpoints (consistent with relay WebSocket auth) so curl/devtools-style `/json/version` and `/json/list` probes work without requiring custom headers. Landed from contributor PR #26015 by @Sid-Qin. (#25928)
|
||||||
- Browser/Extension relay shutdown: flush pending extension-request timers/rejections during relay `stop()` before socket/server teardown so in-flight extension waits do not survive shutdown windows. Landed from contributor PR #24142 by @kevinWangSheng.
|
- Browser/Extension relay shutdown: flush pending extension-request timers/rejections during relay `stop()` before socket/server teardown so in-flight extension waits do not survive shutdown windows. Landed from contributor PR #24142 by @kevinWangSheng.
|
||||||
- Browser/Chrome extension handshake: bind relay WS message handling before `onopen` and add non-blocking `connect.challenge` response handling for gateway-style handshake frames, avoiding stuck `…` badge states when challenge frames arrive immediately on connect. Landed from contributor PR #22571 by @pandego. (#22553)
|
- Browser/Chrome extension handshake: bind relay WS message handling before `onopen` and add non-blocking `connect.challenge` response handling for gateway-style handshake frames, avoiding stuck `…` badge states when challenge frames arrive immediately on connect. Landed from contributor PR #22571 by @pandego. (#22553)
|
||||||
|
- Browser/Extension relay init: dedupe concurrent same-port relay startup with shared in-flight initialization promises so callers await one startup lifecycle and receive consistent success/failure results. Landed from contributor PR #21277 by @HOYALIM. (Related #20688)
|
||||||
- Auth/Auth profiles: normalize `auth-profiles.json` alias fields (`mode -> type`, `apiKey -> key`) before credential validation so entries copied from `openclaw.json` auth examples are no longer silently dropped. (#26950) thanks @byungsker.
|
- Auth/Auth profiles: normalize `auth-profiles.json` alias fields (`mode -> type`, `apiKey -> key`) before credential validation so entries copied from `openclaw.json` auth examples are no longer silently dropped. (#26950) thanks @byungsker.
|
||||||
- Cron/Hooks isolated routing: preserve canonical `agent:*` session keys in isolated runs so already-qualified keys are not double-prefixed (for example `agent:main:main` no longer becomes `agent:main:agent:main:main`). Landed from contributor PR #27333 by @MaheshBhushan. (#27289, #27282)
|
- Cron/Hooks isolated routing: preserve canonical `agent:*` session keys in isolated runs so already-qualified keys are not double-prefixed (for example `agent:main:main` no longer becomes `agent:main:agent:main:main`). Landed from contributor PR #27333 by @MaheshBhushan. (#27289, #27282)
|
||||||
- iOS/Talk mode: stop injecting the voice directive hint into iOS Talk prompts and remove the Voice Directive Hint setting, reducing model bias toward tool-style TTS directives and keeping relay responses text-first by default. (#27543) thanks @ngutman.
|
- iOS/Talk mode: stop injecting the voice directive hint into iOS Talk prompts and remove the Voice Directive Hint setting, reducing model bias toward tool-style TTS directives and keeping relay responses text-first by default. (#27543) thanks @ngutman.
|
||||||
|
|||||||
@@ -208,6 +208,17 @@ describe("chrome extension relay server", () => {
|
|||||||
expect(err.message).toContain("401");
|
expect(err.message).toContain("401");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("deduplicates concurrent relay starts for the same requested port", async () => {
|
||||||
|
const port = await getFreePort();
|
||||||
|
cdpUrl = `http://127.0.0.1:${port}`;
|
||||||
|
const [first, second] = await Promise.all([
|
||||||
|
ensureChromeExtensionRelayServer({ cdpUrl }),
|
||||||
|
ensureChromeExtensionRelayServer({ cdpUrl }),
|
||||||
|
]);
|
||||||
|
expect(first).toBe(second);
|
||||||
|
expect(first.port).toBe(port);
|
||||||
|
});
|
||||||
|
|
||||||
it("allows CORS preflight from chrome-extension origins", async () => {
|
it("allows CORS preflight from chrome-extension origins", async () => {
|
||||||
const port = await getFreePort();
|
const port = await getFreePort();
|
||||||
cdpUrl = `http://127.0.0.1:${port}`;
|
cdpUrl = `http://127.0.0.1:${port}`;
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ function rejectUpgrade(socket: Duplex, status: number, bodyText: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const relayRuntimeByPort = new Map<number, RelayRuntime>();
|
const relayRuntimeByPort = new Map<number, RelayRuntime>();
|
||||||
|
const relayInitByPort = new Map<number, Promise<ChromeExtensionRelayServer>>();
|
||||||
|
|
||||||
function isAddrInUseError(err: unknown): boolean {
|
function isAddrInUseError(err: unknown): boolean {
|
||||||
return (
|
return (
|
||||||
@@ -219,6 +220,12 @@ export async function ensureChromeExtensionRelayServer(opts: {
|
|||||||
return existing.server;
|
return existing.server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inFlight = relayInitByPort.get(info.port);
|
||||||
|
if (inFlight) {
|
||||||
|
return await inFlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initPromise = (async (): Promise<ChromeExtensionRelayServer> => {
|
||||||
const relayAuthToken = resolveRelayAuthTokenForPort(info.port);
|
const relayAuthToken = resolveRelayAuthTokenForPort(info.port);
|
||||||
const relayAuthTokens = new Set(resolveRelayAcceptedTokensForPort(info.port));
|
const relayAuthTokens = new Set(resolveRelayAcceptedTokensForPort(info.port));
|
||||||
|
|
||||||
@@ -491,7 +498,9 @@ export async function ensureChromeExtensionRelayServer(opts: {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (handleTargetActionRoute(path.match(/^\/json\/activate\/(.+)$/), "Target.activateTarget")) {
|
if (
|
||||||
|
handleTargetActionRoute(path.match(/^\/json\/activate\/(.+)$/), "Target.activateTarget")
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (handleTargetActionRoute(path.match(/^\/json\/close\/(.+)$/), "Target.closeTarget")) {
|
if (handleTargetActionRoute(path.match(/^\/json\/close\/(.+)$/), "Target.closeTarget")) {
|
||||||
@@ -586,7 +595,12 @@ export async function ensureChromeExtensionRelayServer(opts: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsed && typeof parsed === "object" && "id" in parsed && typeof parsed.id === "number") {
|
if (
|
||||||
|
parsed &&
|
||||||
|
typeof parsed === "object" &&
|
||||||
|
"id" in parsed &&
|
||||||
|
typeof parsed.id === "number"
|
||||||
|
) {
|
||||||
const pending = pendingExtension.get(parsed.id);
|
const pending = pendingExtension.get(parsed.id);
|
||||||
if (!pending) {
|
if (!pending) {
|
||||||
return;
|
return;
|
||||||
@@ -847,6 +861,13 @@ export async function ensureChromeExtensionRelayServer(opts: {
|
|||||||
|
|
||||||
relayRuntimeByPort.set(port, { server: relay, relayAuthToken });
|
relayRuntimeByPort.set(port, { server: relay, relayAuthToken });
|
||||||
return relay;
|
return relay;
|
||||||
|
})();
|
||||||
|
relayInitByPort.set(info.port, initPromise);
|
||||||
|
try {
|
||||||
|
return await initPromise;
|
||||||
|
} finally {
|
||||||
|
relayInitByPort.delete(info.port);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stopChromeExtensionRelayServer(opts: { cdpUrl: string }): Promise<boolean> {
|
export async function stopChromeExtensionRelayServer(opts: { cdpUrl: string }): Promise<boolean> {
|
||||||
|
|||||||
Reference in New Issue
Block a user