Restore macOS direct LAN connections (#3071)

* fix(desktop): restore direct LAN connections

Desktop 0.3.0 routed remote WebSockets through the Electron main process, causing previously working macOS LAN connections to fail with EHOSTUNREACH. Revert custom direct headers and restore the renderer WebSocket path used before the regression.

* fix(app): replace host connections by identity

Keep one connection per stable ID when TLS or password settings change. This preserves the current-main upsert invariant while the custom-header rollback removes the unrelated transport feature.
This commit is contained in:
Mohamed Boudra
2026-08-09 15:32:31 +08:00
committed by GitHub
parent 7761d122d8
commit fd9cc5d693
36 changed files with 153 additions and 1131 deletions
@@ -1,8 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import {
createDesktopLocalDaemonTransportFactory,
createDesktopWebSocketTransportFactory,
} from "./desktop-daemon-transport";
import { createDesktopLocalDaemonTransportFactory } from "./desktop-daemon-transport";
import { createFakeLocalDaemonTransportRpc } from "./test-local-daemon-transport-rpc";
const LOCAL_URL = "paseo+local://socket?path=%2Ftmp%2Fpaseo.sock";
@@ -46,25 +43,4 @@ describe("desktop-daemon-transport", () => {
expect(rpc.closedSessions).toEqual(["local-session-2"]);
expect(cleanup).toHaveBeenCalledTimes(1);
});
it("forwards remote WebSocket headers and protocols through the desktop bridge", () => {
const rpc = createFakeLocalDaemonTransportRpc();
const transportFactory = createDesktopWebSocketTransportFactory(rpc);
expect(transportFactory).not.toBeNull();
transportFactory!({
url: "wss://daemon.example/ws",
headers: { "X-Tenant": "acme", Authorization: "Bearer secret" },
protocols: ["paseo.bearer.secret"],
});
expect(rpc.openCalls).toEqual([
{
transportType: "websocket",
url: "wss://daemon.example/ws",
headers: { "X-Tenant": "acme", Authorization: "Bearer secret" },
protocols: ["paseo.bearer.secret"],
},
]);
});
});
@@ -2,13 +2,11 @@ import type {
DaemonTransport,
DaemonTransportFactory,
} from "@getpaseo/client/internal/daemon-client";
import type { DesktopDaemonTransportTarget, LocalTransportTarget } from "./desktop-daemon";
import type { LocalTransportTarget } from "./desktop-daemon";
import {
defaultLocalDaemonTransportRpc,
defaultWebSocketDaemonTransportRpc,
type LocalDaemonTransportRpc,
} from "./local-daemon-transport-rpc";
import { isElectronRuntime } from "@/desktop/host";
const LOCAL_TRANSPORT_SCHEME = "paseo+local:";
@@ -52,12 +50,11 @@ function parseLocalDaemonTransportUrl(url: string): LocalTransportTarget {
};
}
function createDesktopDaemonTransportFactory(
resolveTarget: (options: Parameters<DaemonTransportFactory>[0]) => DesktopDaemonTransportTarget,
rpc: LocalDaemonTransportRpc,
): DaemonTransportFactory {
return (options) => {
const target = resolveTarget(options);
export function createDesktopLocalDaemonTransportFactory(
rpc: LocalDaemonTransportRpc = defaultLocalDaemonTransportRpc,
): DaemonTransportFactory | null {
return ({ url }) => {
const target = parseLocalDaemonTransportUrl(url);
let sessionId: string | null = null;
let unlisten: (() => void) | null = null;
let disposed = false;
@@ -190,26 +187,3 @@ function createDesktopDaemonTransportFactory(
return transport;
};
}
export function createDesktopLocalDaemonTransportFactory(
rpc: LocalDaemonTransportRpc = defaultLocalDaemonTransportRpc,
): DaemonTransportFactory | null {
return createDesktopDaemonTransportFactory(({ url }) => parseLocalDaemonTransportUrl(url), rpc);
}
export function createDesktopWebSocketTransportFactory(
rpc?: LocalDaemonTransportRpc,
): DaemonTransportFactory | null {
if (!rpc && !isElectronRuntime()) {
return null;
}
return createDesktopDaemonTransportFactory(
({ url, headers, protocols }) => ({
transportType: "websocket" as const,
url,
...(headers && Object.keys(headers).length > 0 ? { headers } : {}),
...(protocols && protocols.length > 0 ? { protocols } : {}),
}),
rpc ?? defaultWebSocketDaemonTransportRpc,
);
}
@@ -46,16 +46,6 @@ export interface LocalTransportTarget {
transportPath: string;
}
export interface WebSocketTransportTarget {
[key: string]: unknown;
transportType: "websocket";
url: string;
headers?: Record<string, string>;
protocols?: string[];
}
export type DesktopDaemonTransportTarget = LocalTransportTarget | WebSocketTransportTarget;
interface LocalTransportEventPayload {
sessionId: string;
kind: "open" | "message" | "close" | "error";
@@ -203,16 +193,6 @@ export async function openLocalTransportSession(target: LocalTransportTarget): P
return raw;
}
export async function openWebSocketTransportSession(
target: WebSocketTransportTarget,
): Promise<string> {
const raw = await invokeDesktopCommand<unknown>("open_websocket_daemon_transport", target);
if (typeof raw !== "string" || raw.trim().length === 0) {
throw new Error("Unexpected WebSocket transport session response.");
}
return raw;
}
export async function sendLocalTransportMessage(input: {
sessionId: string;
text?: string;
@@ -2,10 +2,8 @@ import {
closeLocalTransportSession,
listenToLocalTransportEvents,
openLocalTransportSession,
openWebSocketTransportSession,
sendLocalTransportMessage,
type DesktopDaemonTransportTarget,
type WebSocketTransportTarget,
type LocalTransportTarget,
} from "./desktop-daemon";
export interface LocalDaemonTransportEvent {
@@ -19,7 +17,7 @@ export interface LocalDaemonTransportEvent {
}
export interface LocalDaemonTransportRpc {
openSession(target: DesktopDaemonTransportTarget): Promise<string>;
openSession(target: LocalTransportTarget): Promise<string>;
listenToEvents(handler: (event: LocalDaemonTransportEvent) => void): Promise<() => void>;
sendMessage(input: { sessionId: string; text?: string; binaryBase64?: string }): Promise<void>;
closeSession(sessionId: string): Promise<void>;
@@ -31,10 +29,3 @@ export const defaultLocalDaemonTransportRpc: LocalDaemonTransportRpc = {
sendMessage: sendLocalTransportMessage,
closeSession: closeLocalTransportSession,
};
export const defaultWebSocketDaemonTransportRpc: LocalDaemonTransportRpc = {
openSession: (target) => openWebSocketTransportSession(target as WebSocketTransportTarget),
listenToEvents: listenToLocalTransportEvents,
sendMessage: sendLocalTransportMessage,
closeSession: closeLocalTransportSession,
};
@@ -1,4 +1,4 @@
import type { DesktopDaemonTransportTarget } from "./desktop-daemon";
import type { LocalTransportTarget } from "./desktop-daemon";
import type {
LocalDaemonTransportEvent,
LocalDaemonTransportRpc,
@@ -11,7 +11,7 @@ export interface RecordedSend {
}
export interface FakeLocalDaemonTransportRpc extends LocalDaemonTransportRpc {
readonly openCalls: DesktopDaemonTransportTarget[];
readonly openCalls: LocalTransportTarget[];
readonly recordedSends: RecordedSend[];
readonly closedSessions: string[];
resolveOpen(sessionId: string): void;
@@ -22,7 +22,7 @@ export interface FakeLocalDaemonTransportRpc extends LocalDaemonTransportRpc {
}
export function createFakeLocalDaemonTransportRpc(): FakeLocalDaemonTransportRpc {
const openCalls: DesktopDaemonTransportTarget[] = [];
const openCalls: LocalTransportTarget[] = [];
const recordedSends: RecordedSend[] = [];
const closedSessions: string[] = [];
let eventHandler: ((event: LocalDaemonTransportEvent) => void) | null = null;