fix(presence): stabilize instance identity

This commit is contained in:
Peter Steinberger
2025-12-12 16:47:07 +00:00
parent 88936b6216
commit debcf19199
7 changed files with 93 additions and 12 deletions

View File

@@ -78,6 +78,15 @@ export type GatewayServer = {
close: () => Promise<void>;
};
function isLoopbackAddress(ip: string | undefined): boolean {
if (!ip) return false;
if (ip === "127.0.0.1") return true;
if (ip.startsWith("127.")) return true;
if (ip === "::1") return true;
if (ip.startsWith("::ffff:127.")) return true;
return false;
}
let presenceVersion = 1;
let healthVersion = 1;
let seq = 0;
@@ -648,7 +657,7 @@ export async function startGatewayServer(
}
upsertPresence(presenceKey, {
host: hello.client.name || os.hostname(),
ip: remoteAddr,
ip: isLoopbackAddress(remoteAddr) ? undefined : remoteAddr,
version: hello.client.version,
mode: hello.client.mode,
instanceId: hello.client.instanceId,

View File

@@ -0,0 +1,40 @@
import { randomUUID } from "node:crypto";
import { describe, expect, it } from "vitest";
import {
listSystemPresence,
updateSystemPresence,
upsertPresence,
} from "./system-presence.js";
describe("system-presence", () => {
it("dedupes entries across sources by case-insensitive instanceId key", () => {
const instanceIdUpper = `AaBb-${randomUUID()}`.toUpperCase();
const instanceIdLower = instanceIdUpper.toLowerCase();
upsertPresence(instanceIdUpper, {
host: "clawdis-mac",
mode: "app",
instanceId: instanceIdUpper,
reason: "connect",
});
updateSystemPresence({
text: "Node: Peter-Mac-Studio (10.0.0.1) · app 2.0.0 · last input 5s ago · mode app · reason beacon",
instanceId: instanceIdLower,
host: "Peter-Mac-Studio",
ip: "10.0.0.1",
mode: "app",
version: "2.0.0",
lastInputSeconds: 5,
reason: "beacon",
});
const matches = listSystemPresence().filter(
(e) => (e.instanceId ?? "").toLowerCase() === instanceIdLower,
);
expect(matches).toHaveLength(1);
expect(matches[0]?.host).toBe("Peter-Mac-Studio");
expect(matches[0]?.ip).toBe("10.0.0.1");
expect(matches[0]?.lastInputSeconds).toBe(5);
});
});

View File

@@ -16,6 +16,13 @@ const entries = new Map<string, SystemPresence>();
const TTL_MS = 5 * 60 * 1000; // 5 minutes
const MAX_ENTRIES = 200;
function normalizePresenceKey(key: string | undefined): string | undefined {
if (!key) return undefined;
const trimmed = key.trim();
if (!trimmed) return undefined;
return trimmed.toLowerCase();
}
function resolvePrimaryIPv4(): string | undefined {
const nets = os.networkInterfaces();
const prefer = ["en0", "eth0"];
@@ -116,9 +123,9 @@ export function updateSystemPresence(payload: SystemPresencePayload) {
ensureSelfPresence();
const parsed = parsePresence(payload.text);
const key =
payload.instanceId?.toLowerCase() ||
parsed.instanceId?.toLowerCase() ||
parsed.host?.toLowerCase() ||
normalizePresenceKey(payload.instanceId) ||
normalizePresenceKey(parsed.instanceId) ||
normalizePresenceKey(parsed.host) ||
parsed.ip ||
parsed.text.slice(0, 64) ||
os.hostname().toLowerCase();
@@ -144,7 +151,9 @@ export function updateSystemPresence(payload: SystemPresencePayload) {
export function upsertPresence(key: string, presence: Partial<SystemPresence>) {
ensureSelfPresence();
const existing = entries.get(key) ?? ({} as SystemPresence);
const normalizedKey =
normalizePresenceKey(key) ?? os.hostname().toLowerCase();
const existing = entries.get(normalizedKey) ?? ({} as SystemPresence);
const merged: SystemPresence = {
...existing,
...presence,
@@ -156,7 +165,7 @@ export function upsertPresence(key: string, presence: Partial<SystemPresence>) {
presence.mode ?? existing.mode ?? "unknown"
}`,
};
entries.set(key, merged);
entries.set(normalizedKey, merged);
}
export function listSystemPresence(): SystemPresence[] {