Gateway: include node caps + hardware in node.list

This commit is contained in:
Peter Steinberger
2025-12-18 00:12:02 +00:00
parent 99310a5bbb
commit 9f73131621
3 changed files with 73 additions and 11 deletions

View File

@@ -2965,17 +2965,20 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
connected.map((n) => [n.nodeId, n]),
);
const nodes = list.paired.map((n) => {
const live = connectedById.get(n.nodeId);
return {
nodeId: n.nodeId,
displayName: live?.displayName ?? n.displayName,
platform: live?.platform ?? n.platform,
version: live?.version ?? n.version,
remoteIp: live?.remoteIp ?? n.remoteIp,
connected: Boolean(live),
};
});
const nodes = list.paired.map((n) => {
const live = connectedById.get(n.nodeId);
return {
nodeId: n.nodeId,
displayName: live?.displayName ?? n.displayName,
platform: live?.platform ?? n.platform,
version: live?.version ?? n.version,
deviceFamily: live?.deviceFamily ?? n.deviceFamily,
modelIdentifier: live?.modelIdentifier ?? n.modelIdentifier,
remoteIp: live?.remoteIp ?? n.remoteIp,
caps: live?.caps,
connected: Boolean(live),
};
});
respond(true, { ts: Date.now(), nodes }, undefined);
} catch (err) {

View File

@@ -412,4 +412,54 @@ describe("node bridge server", () => {
await server.close();
});
it("tracks connected node caps and hardware identifiers", async () => {
const server = await startNodeBridgeServer({
host: "127.0.0.1",
port: 0,
pairingBaseDir: baseDir,
});
const socket = net.connect({ host: "127.0.0.1", port: server.port });
const readLine = createLineReader(socket);
sendLine(socket, {
type: "pair-request",
nodeId: "n-caps",
displayName: "Iris",
platform: "ios",
version: "1.0",
deviceFamily: "iPad",
modelIdentifier: "iPad14,5",
caps: ["canvas", "camera"],
});
// Approve the pending request from the gateway side.
let reqId: string | undefined;
for (let i = 0; i < 40; i += 1) {
const list = await listNodePairing(baseDir);
const req = list.pending.find((p) => p.nodeId === "n-caps");
if (req) {
reqId = req.requestId;
break;
}
await new Promise((r) => setTimeout(r, 25));
}
expect(reqId).toBeTruthy();
if (!reqId) throw new Error("expected a pending requestId");
await approveNodePairing(reqId, baseDir);
const pairOk = JSON.parse(await readLine()) as { type: string };
expect(pairOk.type).toBe("pair-ok");
const helloOk = JSON.parse(await readLine()) as { type: string };
expect(helloOk.type).toBe("hello-ok");
const connected = server.listConnected();
const node = connected.find((n) => n.nodeId === "n-caps");
expect(node?.deviceFamily).toBe("iPad");
expect(node?.modelIdentifier).toBe("iPad14,5");
expect(node?.caps).toEqual(["canvas", "camera"]);
socket.destroy();
await server.close();
});
});

View File

@@ -19,6 +19,7 @@ type BridgeHelloFrame = {
version?: string;
deviceFamily?: string;
modelIdentifier?: string;
caps?: string[];
};
type BridgePairRequestFrame = {
@@ -29,6 +30,7 @@ type BridgePairRequestFrame = {
version?: string;
deviceFamily?: string;
modelIdentifier?: string;
caps?: string[];
remoteAddress?: string;
};
@@ -115,6 +117,7 @@ export type NodeBridgeClientInfo = {
deviceFamily?: string;
modelIdentifier?: string;
remoteIp?: string;
caps?: string[];
};
export type NodeBridgeServerOpts = {
@@ -271,6 +274,9 @@ export async function startNodeBridgeServer(
version: verified.node.version ?? hello.version,
deviceFamily: verified.node.deviceFamily ?? hello.deviceFamily,
modelIdentifier: verified.node.modelIdentifier ?? hello.modelIdentifier,
caps: Array.isArray(hello.caps)
? hello.caps.map((c) => String(c)).filter(Boolean)
: undefined,
remoteIp: remoteAddress,
};
connections.set(nodeId, { socket, nodeInfo, invokeWaiters });
@@ -359,6 +365,9 @@ export async function startNodeBridgeServer(
version: req.version,
deviceFamily: req.deviceFamily,
modelIdentifier: req.modelIdentifier,
caps: Array.isArray(req.caps)
? req.caps.map((c) => String(c)).filter(Boolean)
: undefined,
remoteIp: remoteAddress,
};
connections.set(nodeId, { socket, nodeInfo, invokeWaiters });