mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
adfb59c19b
* feat(mac): add dashboard gateway switching * feat(ui): add dashboard gateway picker * docs(mac): document dashboard gateway picker * fix(mac): harden gateway switching after review * fix(ui): refresh gateway picker snapshots * fix(mac): carry TLS pins through promotion and serialize gateway switches * style(ui): satisfy lint rules in gateway picker files * fix(ui): tolerate absent context in gateway picker pane props * chore(ci): refresh generated inventories * style(mac): satisfy Swift CI checks * refactor(mac): drop dead GatewayEndpointStore.requireConfig * perf(ui): own gateway capability in the chat chunk * chore(ui): keep gateway capability factory module-private
90 lines
3.0 KiB
TypeScript
90 lines
3.0 KiB
TypeScript
export type NativeGateway = {
|
|
id: string;
|
|
name: string;
|
|
kind: "local" | "remote";
|
|
isPrimary: boolean;
|
|
canPromote: boolean;
|
|
health: "ok" | "error" | "unknown";
|
|
};
|
|
|
|
export type NativeGatewaysSnapshot = { gateways: NativeGateway[]; currentId: string };
|
|
type NativeGatewaysMessage =
|
|
| { type: "select" | "open-window" | "set-primary"; id: string }
|
|
| { type: "open-settings" };
|
|
type NativeGatewaysWindow = Window & {
|
|
__OPENCLAW_NATIVE_GATEWAYS__?: unknown;
|
|
webkit?: {
|
|
messageHandlers?: { openclawGateways?: { postMessage(message: NativeGatewaysMessage): void } };
|
|
};
|
|
};
|
|
|
|
const NATIVE_GATEWAYS_CHANGED_EVENT = "openclaw:native-gateways-changed";
|
|
|
|
export type NativeGatewaysCapability = {
|
|
readonly snapshot: NativeGatewaysSnapshot | null;
|
|
subscribe(listener: (snapshot: NativeGatewaysSnapshot) => void): () => void;
|
|
select(id: string): void;
|
|
openWindow(id: string): void;
|
|
setPrimary(id: string): void;
|
|
openSettings(): void;
|
|
};
|
|
|
|
function snapshotFrom(value: unknown): NativeGatewaysSnapshot | null {
|
|
if (!value || typeof value !== "object") {
|
|
return null;
|
|
}
|
|
const snapshot = value as Partial<NativeGatewaysSnapshot>;
|
|
// The embedder owns this payload; deep validation only defends the Mac app from itself.
|
|
return Array.isArray(snapshot.gateways) && typeof snapshot.currentId === "string"
|
|
? (snapshot as NativeGatewaysSnapshot)
|
|
: null;
|
|
}
|
|
|
|
function createNativeGatewaysCapability(): NativeGatewaysCapability | null {
|
|
if (typeof window === "undefined") {
|
|
return null;
|
|
}
|
|
const nativeWindow = window as NativeGatewaysWindow;
|
|
const handler = nativeWindow.webkit?.messageHandlers?.openclawGateways;
|
|
if (!handler?.postMessage) {
|
|
return null;
|
|
}
|
|
const post = handler.postMessage.bind(handler);
|
|
const postWithId = (type: "select" | "open-window" | "set-primary", id: string) =>
|
|
post({ type, id });
|
|
let snapshot = snapshotFrom(nativeWindow["__OPENCLAW_NATIVE_GATEWAYS__"]);
|
|
const listeners = new Set<(snapshot: NativeGatewaysSnapshot) => void>();
|
|
const onChange = (event: Event) => {
|
|
const next = snapshotFrom((event as CustomEvent<unknown>).detail);
|
|
if (!next) {
|
|
return;
|
|
}
|
|
snapshot = next;
|
|
listeners.forEach((listener) => listener(next));
|
|
};
|
|
window.addEventListener(NATIVE_GATEWAYS_CHANGED_EVENT, onChange);
|
|
return {
|
|
get snapshot() {
|
|
return snapshot;
|
|
},
|
|
subscribe(listener) {
|
|
listeners.add(listener);
|
|
return () => listeners.delete(listener);
|
|
},
|
|
select: (id) => postWithId("select", id),
|
|
openWindow: (id) => postWithId("open-window", id),
|
|
setPrimary: (id) => postWithId("set-primary", id),
|
|
openSettings: () => post({ type: "open-settings" }),
|
|
};
|
|
}
|
|
|
|
let singleton: NativeGatewaysCapability | null | undefined;
|
|
|
|
// Chat-chunk-owned so this capability never enters the QA-smoke startup bundle.
|
|
export function nativeGatewaysCapability(): NativeGatewaysCapability | null {
|
|
if (singleton === undefined) {
|
|
singleton = createNativeGatewaysCapability();
|
|
}
|
|
return singleton;
|
|
}
|