mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-24 03:14:27 +00:00
The extension relay server authenticates using an HMAC-SHA256 derived
token (`openclaw-extension-relay-v1:<port>`), but the Chrome extension
was sending the raw gateway token. This caused both the WebSocket
connection and the options page validation to fail with 401 Unauthorized.
Additionally, the options page validation request triggered a CORS
preflight (due to the custom `x-openclaw-relay-token` header) which the
relay rejects because OPTIONS requests lack auth headers. The options
page now delegates the check to the background service worker which has
host_permissions and bypasses CORS preflight.
Fixes #23842
Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit bbc654b9f0)
90 lines
3.0 KiB
JavaScript
90 lines
3.0 KiB
JavaScript
const DEFAULT_PORT = 18792
|
|
|
|
function clampPort(value) {
|
|
const n = Number.parseInt(String(value || ''), 10)
|
|
if (!Number.isFinite(n)) return DEFAULT_PORT
|
|
if (n <= 0 || n > 65535) return DEFAULT_PORT
|
|
return n
|
|
}
|
|
|
|
function updateRelayUrl(port) {
|
|
const el = document.getElementById('relay-url')
|
|
if (!el) return
|
|
el.textContent = `http://127.0.0.1:${port}/`
|
|
}
|
|
|
|
async function deriveRelayToken(gatewayToken, port) {
|
|
const enc = new TextEncoder()
|
|
const key = await crypto.subtle.importKey(
|
|
'raw', enc.encode(gatewayToken), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'],
|
|
)
|
|
const sig = await crypto.subtle.sign(
|
|
'HMAC', key, enc.encode(`openclaw-extension-relay-v1:${port}`),
|
|
)
|
|
return [...new Uint8Array(sig)].map((b) => b.toString(16).padStart(2, '0')).join('')
|
|
}
|
|
|
|
function setStatus(kind, message) {
|
|
const status = document.getElementById('status')
|
|
if (!status) return
|
|
status.dataset.kind = kind || ''
|
|
status.textContent = message || ''
|
|
}
|
|
|
|
async function checkRelayReachable(port, token) {
|
|
const url = `http://127.0.0.1:${port}/json/version`
|
|
const trimmedToken = String(token || '').trim()
|
|
if (!trimmedToken) {
|
|
setStatus('error', 'Gateway token required. Save your gateway token to connect.')
|
|
return
|
|
}
|
|
try {
|
|
const relayToken = await deriveRelayToken(trimmedToken, port)
|
|
// Delegate the fetch to the background service worker to bypass
|
|
// CORS preflight on the custom x-openclaw-relay-token header.
|
|
const res = await chrome.runtime.sendMessage({
|
|
type: 'relayCheck',
|
|
url,
|
|
token: relayToken,
|
|
})
|
|
if (!res) throw new Error('No response from service worker')
|
|
if (res.status === 401) {
|
|
setStatus('error', 'Gateway token rejected. Check token and save again.')
|
|
return
|
|
}
|
|
if (res.error) throw new Error(res.error)
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
setStatus('ok', `Relay reachable and authenticated at http://127.0.0.1:${port}/`)
|
|
} catch {
|
|
setStatus(
|
|
'error',
|
|
`Relay not reachable/authenticated at http://127.0.0.1:${port}/. Start OpenClaw browser relay and verify token.`,
|
|
)
|
|
}
|
|
}
|
|
|
|
async function load() {
|
|
const stored = await chrome.storage.local.get(['relayPort', 'gatewayToken'])
|
|
const port = clampPort(stored.relayPort)
|
|
const token = String(stored.gatewayToken || '').trim()
|
|
document.getElementById('port').value = String(port)
|
|
document.getElementById('token').value = token
|
|
updateRelayUrl(port)
|
|
await checkRelayReachable(port, token)
|
|
}
|
|
|
|
async function save() {
|
|
const portInput = document.getElementById('port')
|
|
const tokenInput = document.getElementById('token')
|
|
const port = clampPort(portInput.value)
|
|
const token = String(tokenInput.value || '').trim()
|
|
await chrome.storage.local.set({ relayPort: port, gatewayToken: token })
|
|
portInput.value = String(port)
|
|
tokenInput.value = token
|
|
updateRelayUrl(port)
|
|
await checkRelayReachable(port, token)
|
|
}
|
|
|
|
document.getElementById('save').addEventListener('click', () => void save())
|
|
void load()
|