fix(presence): hide cli sessions; use numeric mac build

This commit is contained in:
Peter Steinberger
2025-12-12 17:27:11 +00:00
parent c417517f43
commit 7dab927260
5 changed files with 71 additions and 17 deletions

View File

@@ -1,3 +1,4 @@
import { randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import { type AddressInfo, createServer } from "node:net";
import os from "node:os";
@@ -792,6 +793,47 @@ describe("gateway server", () => {
await server.close();
});
test("cli connections are not tracked as instances", async () => {
const { server, ws } = await startServerWithClient();
const cliId = `cli-${randomUUID()}`;
ws.send(
JSON.stringify({
type: "hello",
minProtocol: 1,
maxProtocol: 1,
client: {
name: "cli",
version: "dev",
platform: "test",
mode: "cli",
instanceId: cliId,
},
caps: [],
}),
);
await onceMessage(ws, (o) => o.type === "hello-ok");
const presenceP = onceMessage(
ws,
(o) => o.type === "res" && o.id === "cli-presence",
4000,
);
ws.send(
JSON.stringify({
type: "req",
id: "cli-presence",
method: "system-presence",
}),
);
const presenceRes = await presenceP;
const entries = presenceRes.payload as Array<Record<string, unknown>>;
expect(entries.some((e) => e.instanceId === cliId)).toBe(false);
ws.close();
await server.close();
});
test("refuses to start when port already bound", async () => {
const { server: blocker, port } = await occupyPort();
await expect(startGatewayServer(port)).rejects.toBeInstanceOf(

View File

@@ -58,6 +58,7 @@ type Client = {
socket: WebSocket;
hello: Hello;
connId: string;
presenceKey?: string;
};
const METHODS = [
@@ -545,10 +546,9 @@ export async function startGatewayServer(
`webchat disconnected code=${code} reason=${reason?.toString() || "n/a"} conn=${connId}`,
);
}
if (client) {
if (client?.presenceKey) {
// mark presence as disconnected
const key = client.hello.client.instanceId || connId;
upsertPresence(key, {
upsertPresence(client.presenceKey, {
reason: "disconnect",
});
presenceVersion += 1;
@@ -639,8 +639,11 @@ export async function startGatewayServer(
return;
}
const shouldTrackPresence = hello.client.mode !== "cli";
// synthesize presence entry for this connection (client fingerprint)
const presenceKey = hello.client.instanceId || connId;
const presenceKey = shouldTrackPresence
? hello.client.instanceId || connId
: undefined;
logWs("in", "hello", {
connId,
client: hello.client.name,
@@ -655,15 +658,17 @@ export async function startGatewayServer(
`webchat connected conn=${connId} remote=${remoteAddr ?? "?"} client=${describeHello(hello)}`,
);
}
upsertPresence(presenceKey, {
host: hello.client.name || os.hostname(),
ip: isLoopbackAddress(remoteAddr) ? undefined : remoteAddr,
version: hello.client.version,
mode: hello.client.mode,
instanceId: hello.client.instanceId,
reason: "connect",
});
presenceVersion += 1;
if (presenceKey) {
upsertPresence(presenceKey, {
host: hello.client.name || os.hostname(),
ip: isLoopbackAddress(remoteAddr) ? undefined : remoteAddr,
version: hello.client.version,
mode: hello.client.mode,
instanceId: hello.client.instanceId,
reason: "connect",
});
presenceVersion += 1;
}
const snapshot = buildSnapshot();
if (healthCache) {
snapshot.health = healthCache;
@@ -692,7 +697,7 @@ export async function startGatewayServer(
clearTimeout(handshakeTimer);
// Add the client only after the hello response is ready so no tick/presence
// events reach it before the handshake completes.
client = { socket, hello, connId };
client = { socket, hello, connId, presenceKey };
logWs("out", "hello-ok", {
connId,
methods: METHODS.length,