mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-04 21:41:44 +00:00
* feat(browser): add copilot security contracts * fix(gateway): expose verified client identity to handlers * feat(browser): add secure per-tab copilot panel Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * refactor(browser): separate copilot gateway hint custody * fix(browser): preserve legacy pairing parse shape * fix(browser): harden copilot lifecycle custody Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * fix(browser): enforce copilot lifecycle boundaries Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * style(browser): format copilot sources Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * fix(browser): preserve copilot consent revocation Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * refactor(browser): split copilot custody owners Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * test(browser): normalize websocket array buffers Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * chore(protocol): regenerate Swift gateway models Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * refactor(browser): model copilot runtime entrypoints Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * fix(browser): honor extension build boundaries Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * test(gateway): assert targeted chat delivery Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * test(gateway): cover targeted delivery calls Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * fix(browser): declare copilot build dependencies Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * fix(ci): clear browser copilot gate failures Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * test(ci): cover copilot lint exclusion Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * fix(browser): gate copilot on relay custody Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * test(browser): bound copilot relay frames Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> --------- Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>
105 lines
3.2 KiB
JavaScript
105 lines
3.2 KiB
JavaScript
// Pure helpers for the OpenClaw extension: pairing-string parsing, reconnect
|
|
// backoff, and Chrome tab-group color mapping. No chrome.* usage here so the
|
|
// repo's vitest suite can exercise the logic directly.
|
|
|
|
/** Tab group shown to the user; membership == what the agent may touch. */
|
|
export const OPENCLAW_TAB_GROUP_TITLE = "OpenClaw";
|
|
const EXTENSION_RELAY_PROTOCOL = "openclaw-extension-relay";
|
|
const EXTENSION_RELAY_TOKEN_PROTOCOL_PREFIX = "openclaw-extension-token.";
|
|
|
|
const CHROME_GROUP_COLORS = {
|
|
grey: [128, 128, 128],
|
|
blue: [66, 133, 244],
|
|
red: [219, 68, 55],
|
|
yellow: [244, 180, 0],
|
|
green: [15, 157, 88],
|
|
pink: [233, 30, 99],
|
|
purple: [156, 39, 176],
|
|
cyan: [0, 188, 212],
|
|
orange: [255, 112, 32],
|
|
};
|
|
|
|
/**
|
|
* Parse a pairing string printed by `openclaw browser extension pair`.
|
|
* Shape: ws://127.0.0.1:<port>/extension?gateway=<url>#<token>
|
|
* The additive gateway hint is not a credential; old extensions safely pass
|
|
* it through to the relay while new extensions remove it before connecting.
|
|
*/
|
|
export function parsePairingString(raw) {
|
|
const trimmed = String(raw ?? "").trim();
|
|
const hashIndex = trimmed.indexOf("#");
|
|
if (hashIndex <= 0) {
|
|
return null;
|
|
}
|
|
const relayUrl = trimmed.slice(0, hashIndex);
|
|
const token = trimmed.slice(hashIndex + 1).trim();
|
|
if (!token) {
|
|
return null;
|
|
}
|
|
let parsed;
|
|
try {
|
|
parsed = new URL(relayUrl);
|
|
} catch {
|
|
return null;
|
|
}
|
|
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
|
|
return null;
|
|
}
|
|
if (!parsed.pathname.endsWith("/extension")) {
|
|
return null;
|
|
}
|
|
const gatewayUrl = parsed.searchParams.get("gateway")?.trim() || undefined;
|
|
parsed.searchParams.delete("gateway");
|
|
if ([...parsed.searchParams].length > 0) {
|
|
return null;
|
|
}
|
|
return {
|
|
relayUrl: parsed.toString(),
|
|
token,
|
|
...(gatewayUrl ? { gatewayUrl } : {}),
|
|
};
|
|
}
|
|
|
|
/** Build WebSocket subprotocols without putting the relay secret in the request URL. */
|
|
export function buildRelayWsProtocols(token) {
|
|
return [EXTENSION_RELAY_PROTOCOL, `${EXTENSION_RELAY_TOKEN_PROTOCOL_PREFIX}${token}`];
|
|
}
|
|
|
|
/** Exponential reconnect backoff: 1s, 2s, 4s ... capped at 30s. */
|
|
export function reconnectDelayMs(attempt) {
|
|
const capped = Math.min(Math.max(0, attempt), 5);
|
|
return Math.min(1000 * 2 ** capped, 30_000);
|
|
}
|
|
|
|
/** Map a hex color to the closest Chrome tab-group color name. */
|
|
export function nearestGroupColor(hex) {
|
|
const match = /^#?([0-9a-f]{6})$/i.exec(String(hex ?? "").trim());
|
|
if (!match) {
|
|
return "orange";
|
|
}
|
|
const value = Number.parseInt(match[1], 16);
|
|
const r = (value >> 16) & 0xff;
|
|
const g = (value >> 8) & 0xff;
|
|
const b = value & 0xff;
|
|
let best = "orange";
|
|
let bestDistance = Number.POSITIVE_INFINITY;
|
|
for (const [name, [cr, cg, cb]] of Object.entries(CHROME_GROUP_COLORS)) {
|
|
const distance = (r - cr) ** 2 + (g - cg) ** 2 + (b - cb) ** 2;
|
|
if (distance < bestDistance) {
|
|
bestDistance = distance;
|
|
best = name;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
/** Normalize a chrome.tabs.Tab into the relay's tab info shape. */
|
|
export function toRelayTabInfo(tab) {
|
|
return {
|
|
tabId: tab.id,
|
|
url: tab.url ?? "",
|
|
title: tab.title ?? "",
|
|
active: tab.active === true,
|
|
};
|
|
}
|