Skip to content

Commit 65d5a91

Browse files
steipetepandego
andcommitted
fix(browser): land PR #22571 with safe extension handshake handling
Bind relay WS message handling before onopen and add non-blocking connect.challenge response support without forcing handshake waits on current relay protocol. Landed from contributor @pandego (PR #22571). Co-authored-by: pandego <[email protected]>
1 parent ce833cd commit 65d5a91

File tree

2 files changed

+67
-4
lines changed

2 files changed

+67
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
1616
- Browser/Extension relay CORS: handle `/json*` `OPTIONS` preflight before auth checks, allow Chrome extension origins, and return extension-origin CORS headers on relay HTTP responses so extension token validation no longer fails cross-origin. Landed from contributor PR #23962 by @miloudbelarebia. (#23842)
1717
- 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)
1818
- 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.
19+
- 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)
1920
- 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.
2021
- 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)
2122
- 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.

assets/chrome-extension/background.js

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const BADGE = {
1313
let relayWs = null
1414
/** @type {Promise<void>|null} */
1515
let relayConnectPromise = null
16+
let relayGatewayToken = ''
17+
/** @type {string|null} */
18+
let relayConnectRequestId = null
1619

1720
let nextSession = 1
1821

@@ -143,6 +146,13 @@ async function ensureRelayConnection() {
143146

144147
const ws = new WebSocket(wsUrl)
145148
relayWs = ws
149+
relayGatewayToken = gatewayToken
150+
// Bind message handler before open so an immediate first frame (for example
151+
// gateway connect.challenge) cannot be missed.
152+
ws.onmessage = (event) => {
153+
if (ws !== relayWs) return
154+
void whenReady(() => onRelayMessage(String(event.data || '')))
155+
}
146156

147157
await new Promise((resolve, reject) => {
148158
const t = setTimeout(() => reject(new Error('WebSocket connect timeout')), 5000)
@@ -162,10 +172,6 @@ async function ensureRelayConnection() {
162172

163173
// Bind permanent handlers. Guard against stale socket: if this WS was
164174
// replaced before its close fires, the handler is a no-op.
165-
ws.onmessage = (event) => {
166-
if (ws !== relayWs) return
167-
void whenReady(() => onRelayMessage(String(event.data || '')))
168-
}
169175
ws.onclose = () => {
170176
if (ws !== relayWs) return
171177
onRelayClosed('closed')
@@ -188,6 +194,8 @@ async function ensureRelayConnection() {
188194
// Debugger sessions are kept alive so they survive transient WS drops.
189195
function onRelayClosed(reason) {
190196
relayWs = null
197+
relayGatewayToken = ''
198+
relayConnectRequestId = null
191199

192200
for (const [id, p] of pending.entries()) {
193201
pending.delete(id)
@@ -308,6 +316,33 @@ function sendToRelay(payload) {
308316
ws.send(JSON.stringify(payload))
309317
}
310318

319+
function ensureGatewayHandshakeStarted(payload) {
320+
if (relayConnectRequestId) return
321+
const nonce = typeof payload?.nonce === 'string' ? payload.nonce.trim() : ''
322+
relayConnectRequestId = `ext-connect-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`
323+
sendToRelay({
324+
type: 'req',
325+
id: relayConnectRequestId,
326+
method: 'connect',
327+
params: {
328+
minProtocol: 3,
329+
maxProtocol: 3,
330+
client: {
331+
id: 'chrome-relay-extension',
332+
version: '1.0.0',
333+
platform: 'chrome-extension',
334+
mode: 'webchat',
335+
},
336+
role: 'operator',
337+
scopes: ['operator.read', 'operator.write'],
338+
caps: [],
339+
commands: [],
340+
nonce: nonce || undefined,
341+
auth: relayGatewayToken ? { token: relayGatewayToken } : undefined,
342+
},
343+
})
344+
}
345+
311346
async function maybeOpenHelpOnce() {
312347
try {
313348
const stored = await chrome.storage.local.get(['helpOnErrorShown'])
@@ -349,6 +384,33 @@ async function onRelayMessage(text) {
349384
return
350385
}
351386

387+
if (msg && msg.type === 'event' && msg.event === 'connect.challenge') {
388+
try {
389+
ensureGatewayHandshakeStarted(msg.payload)
390+
} catch (err) {
391+
console.warn('gateway connect handshake start failed', err instanceof Error ? err.message : String(err))
392+
relayConnectRequestId = null
393+
const ws = relayWs
394+
if (ws && ws.readyState === WebSocket.OPEN) {
395+
ws.close(1008, 'gateway connect failed')
396+
}
397+
}
398+
return
399+
}
400+
401+
if (msg && msg.type === 'res' && relayConnectRequestId && msg.id === relayConnectRequestId) {
402+
relayConnectRequestId = null
403+
if (!msg.ok) {
404+
const detail = msg?.error?.message || msg?.error || 'gateway connect failed'
405+
console.warn('gateway connect handshake rejected', String(detail))
406+
const ws = relayWs
407+
if (ws && ws.readyState === WebSocket.OPEN) {
408+
ws.close(1008, 'gateway connect failed')
409+
}
410+
}
411+
return
412+
}
413+
352414
if (msg && msg.method === 'ping') {
353415
try {
354416
sendToRelay({ method: 'pong' })

0 commit comments

Comments
 (0)