Add custom headers to direct connections (#2922)
* Add custom headers to direct connections * Address custom header review feedback * Fix direct connection header integration * Fix desktop relay E2E transport setup --------- Co-authored-by: Claude <claude@Claudes-Mac-mini.local> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
co-authored by
Claude
Mohamed Boudra
parent
098122be8d
commit
73ba37a07a
@@ -139,8 +139,123 @@ export async function installDesktopRuntime(
|
||||
let daemonRunning = true;
|
||||
let currentPid: number | null = cfg.daemonPid ?? null;
|
||||
let startCount = 0;
|
||||
let nextTransportSessionId = 0;
|
||||
const transportSessions = new Map<string, WebSocket>();
|
||||
const desktopEventHandlers = new Map<string, Set<(payload: unknown) => void>>();
|
||||
window.__desktopDaemonStartRequested = false;
|
||||
|
||||
function emitDesktopEvent(event: string, payload: unknown) {
|
||||
for (const handler of desktopEventHandlers.get(event) ?? []) {
|
||||
handler(payload);
|
||||
}
|
||||
}
|
||||
|
||||
function bytesToBase64(bytes: Uint8Array): string {
|
||||
let binary = "";
|
||||
for (const byte of bytes) {
|
||||
binary += String.fromCharCode(byte);
|
||||
}
|
||||
return btoa(binary);
|
||||
}
|
||||
|
||||
function base64ToBytes(base64: string): Uint8Array {
|
||||
const binary = atob(base64);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let index = 0; index < binary.length; index += 1) {
|
||||
bytes[index] = binary.charCodeAt(index);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function parseWebSocketTransportTarget(args?: Record<string, unknown>): {
|
||||
url: string;
|
||||
protocols: string[];
|
||||
} {
|
||||
const url = typeof args?.url === "string" ? args.url : "";
|
||||
const protocols = Array.isArray(args?.protocols)
|
||||
? args.protocols.filter((protocol): protocol is string => typeof protocol === "string")
|
||||
: [];
|
||||
const headers =
|
||||
args?.headers && typeof args.headers === "object"
|
||||
? Object.entries(args.headers as Record<string, unknown>)
|
||||
: [];
|
||||
const passwordProtocol = protocols.find((protocol) => protocol.startsWith("paseo.bearer."));
|
||||
const supportsHeaders =
|
||||
headers.length === 0 ||
|
||||
(headers.length === 1 &&
|
||||
headers[0][0].toLowerCase() === "authorization" &&
|
||||
typeof headers[0][1] === "string" &&
|
||||
passwordProtocol !== undefined &&
|
||||
headers[0][1] === `Bearer ${passwordProtocol.slice("paseo.bearer.".length)}`);
|
||||
|
||||
if (!url || !supportsHeaders) {
|
||||
throw new Error("The browser-backed desktop E2E transport cannot attach custom headers.");
|
||||
}
|
||||
return { url, protocols };
|
||||
}
|
||||
|
||||
async function openWebSocketTransport(args?: Record<string, unknown>): Promise<string> {
|
||||
const target = parseWebSocketTransportTarget(args);
|
||||
const sessionId = `e2e-websocket-session-${++nextTransportSessionId}`;
|
||||
const socket = new WebSocket(target.url, target.protocols);
|
||||
socket.binaryType = "arraybuffer";
|
||||
transportSessions.set(sessionId, socket);
|
||||
|
||||
return await new Promise<string>((resolve, reject) => {
|
||||
let opened = false;
|
||||
|
||||
socket.addEventListener("open", () => {
|
||||
opened = true;
|
||||
resolve(sessionId);
|
||||
emitDesktopEvent("local-daemon-transport-event", { sessionId, kind: "open" });
|
||||
});
|
||||
socket.addEventListener("message", async (event) => {
|
||||
if (typeof event.data === "string") {
|
||||
emitDesktopEvent("local-daemon-transport-event", {
|
||||
sessionId,
|
||||
kind: "message",
|
||||
text: event.data,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const buffer =
|
||||
event.data instanceof ArrayBuffer
|
||||
? event.data
|
||||
: await (event.data as Blob).arrayBuffer();
|
||||
emitDesktopEvent("local-daemon-transport-event", {
|
||||
sessionId,
|
||||
kind: "message",
|
||||
binaryBase64: bytesToBase64(new Uint8Array(buffer)),
|
||||
});
|
||||
});
|
||||
socket.addEventListener("close", (event) => {
|
||||
transportSessions.delete(sessionId);
|
||||
if (!opened) {
|
||||
reject(new Error("Desktop E2E WebSocket closed before opening."));
|
||||
return;
|
||||
}
|
||||
emitDesktopEvent("local-daemon-transport-event", {
|
||||
sessionId,
|
||||
kind: "close",
|
||||
code: event.code,
|
||||
reason: event.reason,
|
||||
});
|
||||
});
|
||||
socket.addEventListener("error", () => {
|
||||
if (!opened) {
|
||||
transportSessions.delete(sessionId);
|
||||
reject(new Error("Desktop E2E WebSocket failed to open."));
|
||||
return;
|
||||
}
|
||||
emitDesktopEvent("local-daemon-transport-event", {
|
||||
sessionId,
|
||||
kind: "error",
|
||||
error: "Desktop E2E WebSocket transport error.",
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function buildDaemonStatus() {
|
||||
return {
|
||||
serverId: cfg.serverId,
|
||||
@@ -175,6 +290,55 @@ export async function installDesktopRuntime(
|
||||
}
|
||||
}
|
||||
|
||||
function patchDesktopSettings(args?: Record<string, unknown>) {
|
||||
const daemon = args?.daemon;
|
||||
if (
|
||||
daemon !== null &&
|
||||
typeof daemon === "object" &&
|
||||
"manageBuiltInDaemon" in daemon &&
|
||||
typeof daemon.manageBuiltInDaemon === "boolean"
|
||||
) {
|
||||
manageDaemon = daemon.manageBuiltInDaemon;
|
||||
}
|
||||
return {
|
||||
releaseChannel: "stable",
|
||||
daemon: { manageBuiltInDaemon: manageDaemon, keepRunningAfterQuit: true },
|
||||
};
|
||||
}
|
||||
|
||||
function sendWebSocketTransportMessage(args?: Record<string, unknown>): null {
|
||||
const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
|
||||
const socket = transportSessions.get(sessionId);
|
||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
||||
throw new Error(`Desktop E2E WebSocket session is not open: ${sessionId}`);
|
||||
}
|
||||
if (typeof args?.text === "string") {
|
||||
socket.send(args.text);
|
||||
return null;
|
||||
}
|
||||
if (typeof args?.binaryBase64 === "string") {
|
||||
socket.send(base64ToBytes(args.binaryBase64));
|
||||
return null;
|
||||
}
|
||||
throw new Error("Desktop E2E WebSocket send requires a payload.");
|
||||
}
|
||||
|
||||
function closeWebSocketTransport(args?: Record<string, unknown>): null {
|
||||
const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
|
||||
transportSessions.get(sessionId)?.close();
|
||||
transportSessions.delete(sessionId);
|
||||
return null;
|
||||
}
|
||||
|
||||
const webSocketTransportCommandHandlers: Record<
|
||||
string,
|
||||
(args?: Record<string, unknown>) => unknown
|
||||
> = {
|
||||
open_websocket_daemon_transport: openWebSocketTransport,
|
||||
send_local_daemon_transport_message: sendWebSocketTransportMessage,
|
||||
close_local_daemon_transport: closeWebSocketTransport,
|
||||
};
|
||||
|
||||
const desktopBridge: {
|
||||
platform: string;
|
||||
invoke: (command: string, args?: Record<string, unknown>) => Promise<unknown>;
|
||||
@@ -183,7 +347,10 @@ export async function installDesktopRuntime(
|
||||
open: (options?: Record<string, unknown>) => Promise<string | string[] | null>;
|
||||
};
|
||||
getPendingOpenProject: () => Promise<string | null>;
|
||||
events: { on: () => Promise<() => void> };
|
||||
opener: { openUrl: (url: string) => Promise<void> };
|
||||
events: {
|
||||
on: (event: string, handler: (payload: unknown) => void) => Promise<() => void>;
|
||||
};
|
||||
editor?: {
|
||||
listTargets: () => Promise<DesktopEditorTargetConfig[]>;
|
||||
openTarget: (input: DesktopEditorOpenRecord) => Promise<void>;
|
||||
@@ -239,19 +406,7 @@ export async function installDesktopRuntime(
|
||||
}
|
||||
|
||||
if (command === "patch_desktop_settings") {
|
||||
const daemon = args?.daemon;
|
||||
if (
|
||||
daemon !== null &&
|
||||
typeof daemon === "object" &&
|
||||
"manageBuiltInDaemon" in daemon &&
|
||||
typeof daemon.manageBuiltInDaemon === "boolean"
|
||||
) {
|
||||
manageDaemon = daemon.manageBuiltInDaemon;
|
||||
}
|
||||
return {
|
||||
releaseChannel: "stable",
|
||||
daemon: { manageBuiltInDaemon: manageDaemon, keepRunningAfterQuit: true },
|
||||
};
|
||||
return patchDesktopSettings(args);
|
||||
}
|
||||
|
||||
if (command === "stop_desktop_daemon") {
|
||||
@@ -264,6 +419,11 @@ export async function installDesktopRuntime(
|
||||
return startDesktopDaemon();
|
||||
}
|
||||
|
||||
const transportCommandHandler = webSocketTransportCommandHandlers[command];
|
||||
if (transportCommandHandler) {
|
||||
return await transportCommandHandler(args);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
dialog: {
|
||||
@@ -280,7 +440,24 @@ export async function installDesktopRuntime(
|
||||
},
|
||||
},
|
||||
getPendingOpenProject: async () => null,
|
||||
events: { on: async () => () => undefined },
|
||||
opener: {
|
||||
openUrl: async (url) => {
|
||||
localStorage.setItem("@paseo:e2e-opened-url", url);
|
||||
},
|
||||
},
|
||||
events: {
|
||||
on: async (event, handler) => {
|
||||
const handlers = desktopEventHandlers.get(event) ?? new Set();
|
||||
handlers.add(handler);
|
||||
desktopEventHandlers.set(event, handlers);
|
||||
return () => {
|
||||
handlers.delete(handler);
|
||||
if (handlers.size === 0) {
|
||||
desktopEventHandlers.delete(event);
|
||||
}
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (cfg.editorTargets) {
|
||||
|
||||
Reference in New Issue
Block a user