feat: serve plugin http and websockets from one mount instead of ports

This commit is contained in:
LukeGus committed 2026-09-22 16:39:41 -05:00
1 parent c496a093ca
commit 8431570ff7
102 files changed
+2894 -2308

No files matched your search

@@ -114,7 +114,7 @@ describe("SharedSessionView", () => {
api.resolveShareLink.mockResolvedValue({
protocol: "rdp",
permissionLevel: "read-only",
wsPath: "/guacamole/websocket/",
wsPath: "/plugin-ws/remote-desktop/display",
connectParams: { token: "guac-join-token" },
});
+24 -23
View File
@@ -127,14 +127,15 @@ describe("resolveConnectionOrigin", () => {
});
/**
* The embedded backend authenticates a local WebSocket from `?token=`, because
* the browser WebSocket API cannot set an Authorization header. Electron's main
* process does inject a JWT cookie, but only on an exact origin match, and the
* remembered cookie belongs to the API origin (`localhost:30001`) — so nothing
* is attached to a `127.0.0.1:30009` connection.
* The embedded backend authenticates a local WebSocket from its subprotocol,
* because the browser WebSocket API cannot set an Authorization header.
* Electron's main process does inject a JWT cookie, but only on an exact
* origin match, so a socket has to carry the credential itself.
*
* The Docker console opted out of the query token and had no other credential
* left, so its handshake was closed with 1008 while logs and stats kept working.
* This caught a real bug: the Docker console opted out of the token and had no
* other credential left, so its handshake was closed with 1008 while logs and
* stats kept working. The fixtures below use the plugin socket routes these
* channels actually use now; every plugin shares the backend port.
*/
describe("buildOriginWsUrl", () => {
const store: Record<string, string> = {};
@@ -154,13 +155,13 @@ describe("buildOriginWsUrl", () => {
// Every interactive channel on the embedded backend relies on this.
const target = await buildOriginWsUrl({
origin: "local",
localPort: 30009,
localPath: "/docker/console/",
remotePath: "/docker/console/",
localPort: 30001,
localPath: "/plugin-ws/docker/console",
remotePath: "/plugin-ws/docker/console",
});
expect(target).toEqual({
url: "ws://127.0.0.1:30009/docker/console/",
url: "ws://127.0.0.1:30001/plugin-ws/docker/console",
protocols: ["termix.jwt.local-jwt"],
});
});
@@ -168,14 +169,14 @@ describe("buildOriginWsUrl", () => {
it("omits it only when a caller asks", async () => {
const target = await buildOriginWsUrl({
origin: "local",
localPort: 30009,
localPath: "/docker/console/",
remotePath: "/docker/console/",
localPort: 30001,
localPath: "/plugin-ws/docker/console",
remotePath: "/plugin-ws/docker/console",
includeJwt: false,
});
expect(target).toEqual({
url: "ws://127.0.0.1:30009/docker/console/",
url: "ws://127.0.0.1:30001/plugin-ws/docker/console",
protocols: [],
});
});
@@ -193,14 +194,14 @@ describe("buildOriginWsUrl", () => {
const target = await buildOriginWsUrl({
origin: "remote",
localPort: 30008,
localPath: "/guacamole/websocket/",
remotePath: "/guacamole/websocket/",
localPort: 30001,
localPath: "/plugin-ws/remote-desktop/display",
remotePath: "/plugin-ws/remote-desktop/display",
includeJwt: false,
});
expect(target).toEqual({
url: "wss://termix.example/guacamole/websocket/",
url: "wss://termix.example/plugin-ws/remote-desktop/display",
protocols: [],
});
});
@@ -210,13 +211,13 @@ describe("buildOriginWsUrl", () => {
const target = await buildOriginWsUrl({
origin: "local",
localPort: 30002,
localPath: "",
remotePath: "/ssh/websocket/",
localPort: 30001,
localPath: "/plugin-ws/ssh-terminal/terminal",
remotePath: "/plugin-ws/ssh-terminal/terminal",
});
expect(target).toEqual({
url: "ws://127.0.0.1:30002",
url: "ws://127.0.0.1:30001/plugin-ws/ssh-terminal/terminal",
protocols: [],
});
});
+1 -1
View File
@@ -5,7 +5,7 @@ const isElectron = vi.hoisted(() => vi.fn());
const remoteApi = vi.hoisted(() => ({ get: vi.fn() }));
vi.mock("@/lib/electron", () => ({ isElectron }));
vi.mock("@/main-axios", () => ({ getRemoteStatsApi: () => remoteApi }));
vi.mock("@/main-axios", () => ({ getRemoteCoreApi: () => remoteApi }));
import {
getConnectedRemoteApi,