Bridge: persist advertised invoke commands
parent
ce1a8d70d9
commit
54830e8401
|
|
@ -431,6 +431,7 @@ describe("node bridge server", () => {
|
||||||
deviceFamily: "iPad",
|
deviceFamily: "iPad",
|
||||||
modelIdentifier: "iPad14,5",
|
modelIdentifier: "iPad14,5",
|
||||||
caps: ["canvas", "camera"],
|
caps: ["canvas", "camera"],
|
||||||
|
commands: ["canvas.eval", "canvas.snapshot", "camera.snap"],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Approve the pending request from the gateway side.
|
// Approve the pending request from the gateway side.
|
||||||
|
|
@ -458,10 +459,20 @@ describe("node bridge server", () => {
|
||||||
expect(node?.deviceFamily).toBe("iPad");
|
expect(node?.deviceFamily).toBe("iPad");
|
||||||
expect(node?.modelIdentifier).toBe("iPad14,5");
|
expect(node?.modelIdentifier).toBe("iPad14,5");
|
||||||
expect(node?.caps).toEqual(["canvas", "camera"]);
|
expect(node?.caps).toEqual(["canvas", "camera"]);
|
||||||
|
expect(node?.commands).toEqual([
|
||||||
|
"canvas.eval",
|
||||||
|
"canvas.snapshot",
|
||||||
|
"camera.snap",
|
||||||
|
]);
|
||||||
|
|
||||||
const after = await listNodePairing(baseDir);
|
const after = await listNodePairing(baseDir);
|
||||||
const paired = after.paired.find((p) => p.nodeId === "n-caps");
|
const paired = after.paired.find((p) => p.nodeId === "n-caps");
|
||||||
expect(paired?.caps).toEqual(["canvas", "camera"]);
|
expect(paired?.caps).toEqual(["canvas", "camera"]);
|
||||||
|
expect(paired?.commands).toEqual([
|
||||||
|
"canvas.eval",
|
||||||
|
"canvas.snapshot",
|
||||||
|
"camera.snap",
|
||||||
|
]);
|
||||||
|
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
await server.close();
|
await server.close();
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ type BridgeHelloFrame = {
|
||||||
deviceFamily?: string;
|
deviceFamily?: string;
|
||||||
modelIdentifier?: string;
|
modelIdentifier?: string;
|
||||||
caps?: string[];
|
caps?: string[];
|
||||||
|
commands?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type BridgePairRequestFrame = {
|
type BridgePairRequestFrame = {
|
||||||
|
|
@ -32,6 +33,7 @@ type BridgePairRequestFrame = {
|
||||||
deviceFamily?: string;
|
deviceFamily?: string;
|
||||||
modelIdentifier?: string;
|
modelIdentifier?: string;
|
||||||
caps?: string[];
|
caps?: string[];
|
||||||
|
commands?: string[];
|
||||||
remoteAddress?: string;
|
remoteAddress?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -119,6 +121,7 @@ export type NodeBridgeClientInfo = {
|
||||||
modelIdentifier?: string;
|
modelIdentifier?: string;
|
||||||
remoteIp?: string;
|
remoteIp?: string;
|
||||||
caps?: string[];
|
caps?: string[];
|
||||||
|
commands?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeBridgeServerOpts = {
|
export type NodeBridgeServerOpts = {
|
||||||
|
|
@ -263,8 +266,12 @@ export async function startNodeBridgeServer(
|
||||||
platform?: string;
|
platform?: string;
|
||||||
deviceFamily?: string;
|
deviceFamily?: string;
|
||||||
}): string[] | undefined => {
|
}): string[] | undefined => {
|
||||||
const platform = String(frame.platform ?? "").trim().toLowerCase();
|
const platform = String(frame.platform ?? "")
|
||||||
const family = String(frame.deviceFamily ?? "").trim().toLowerCase();
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const family = String(frame.deviceFamily ?? "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
if (platform.includes("ios") || platform.includes("ipados")) {
|
if (platform.includes("ios") || platform.includes("ipados")) {
|
||||||
return ["canvas", "camera"];
|
return ["canvas", "camera"];
|
||||||
}
|
}
|
||||||
|
|
@ -287,6 +294,11 @@ export async function startNodeBridgeServer(
|
||||||
verified.node.caps ??
|
verified.node.caps ??
|
||||||
inferCaps(hello);
|
inferCaps(hello);
|
||||||
|
|
||||||
|
const commands =
|
||||||
|
Array.isArray(hello.commands) && hello.commands.length > 0
|
||||||
|
? hello.commands.map((c) => String(c)).filter(Boolean)
|
||||||
|
: verified.node.commands;
|
||||||
|
|
||||||
isAuthenticated = true;
|
isAuthenticated = true;
|
||||||
const existing = connections.get(nodeId);
|
const existing = connections.get(nodeId);
|
||||||
if (existing?.socket && existing.socket !== socket) {
|
if (existing?.socket && existing.socket !== socket) {
|
||||||
|
|
@ -304,6 +316,7 @@ export async function startNodeBridgeServer(
|
||||||
deviceFamily: verified.node.deviceFamily ?? hello.deviceFamily,
|
deviceFamily: verified.node.deviceFamily ?? hello.deviceFamily,
|
||||||
modelIdentifier: verified.node.modelIdentifier ?? hello.modelIdentifier,
|
modelIdentifier: verified.node.modelIdentifier ?? hello.modelIdentifier,
|
||||||
caps,
|
caps,
|
||||||
|
commands,
|
||||||
remoteIp: remoteAddress,
|
remoteIp: remoteAddress,
|
||||||
};
|
};
|
||||||
await updatePairedNodeMetadata(
|
await updatePairedNodeMetadata(
|
||||||
|
|
@ -316,6 +329,7 @@ export async function startNodeBridgeServer(
|
||||||
modelIdentifier: nodeInfo.modelIdentifier,
|
modelIdentifier: nodeInfo.modelIdentifier,
|
||||||
remoteIp: nodeInfo.remoteIp,
|
remoteIp: nodeInfo.remoteIp,
|
||||||
caps: nodeInfo.caps,
|
caps: nodeInfo.caps,
|
||||||
|
commands: nodeInfo.commands,
|
||||||
},
|
},
|
||||||
opts.pairingBaseDir,
|
opts.pairingBaseDir,
|
||||||
);
|
);
|
||||||
|
|
@ -378,6 +392,9 @@ export async function startNodeBridgeServer(
|
||||||
caps: Array.isArray(req.caps)
|
caps: Array.isArray(req.caps)
|
||||||
? req.caps.map((c) => String(c)).filter(Boolean)
|
? req.caps.map((c) => String(c)).filter(Boolean)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
commands: Array.isArray(req.commands)
|
||||||
|
? req.commands.map((c) => String(c)).filter(Boolean)
|
||||||
|
: undefined,
|
||||||
remoteIp: remoteAddress,
|
remoteIp: remoteAddress,
|
||||||
},
|
},
|
||||||
opts.pairingBaseDir,
|
opts.pairingBaseDir,
|
||||||
|
|
@ -411,6 +428,9 @@ export async function startNodeBridgeServer(
|
||||||
caps: Array.isArray(req.caps)
|
caps: Array.isArray(req.caps)
|
||||||
? req.caps.map((c) => String(c)).filter(Boolean)
|
? req.caps.map((c) => String(c)).filter(Boolean)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
commands: Array.isArray(req.commands)
|
||||||
|
? req.commands.map((c) => String(c)).filter(Boolean)
|
||||||
|
: undefined,
|
||||||
remoteIp: remoteAddress,
|
remoteIp: remoteAddress,
|
||||||
};
|
};
|
||||||
connections.set(nodeId, { socket, nodeInfo, invokeWaiters });
|
connections.set(nodeId, { socket, nodeInfo, invokeWaiters });
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export type NodePairingPendingRequest = {
|
||||||
deviceFamily?: string;
|
deviceFamily?: string;
|
||||||
modelIdentifier?: string;
|
modelIdentifier?: string;
|
||||||
caps?: string[];
|
caps?: string[];
|
||||||
|
commands?: string[];
|
||||||
remoteIp?: string;
|
remoteIp?: string;
|
||||||
isRepair?: boolean;
|
isRepair?: boolean;
|
||||||
ts: number;
|
ts: number;
|
||||||
|
|
@ -26,6 +27,7 @@ export type NodePairingPairedNode = {
|
||||||
deviceFamily?: string;
|
deviceFamily?: string;
|
||||||
modelIdentifier?: string;
|
modelIdentifier?: string;
|
||||||
caps?: string[];
|
caps?: string[];
|
||||||
|
commands?: string[];
|
||||||
remoteIp?: string;
|
remoteIp?: string;
|
||||||
createdAtMs: number;
|
createdAtMs: number;
|
||||||
approvedAtMs: number;
|
approvedAtMs: number;
|
||||||
|
|
@ -181,6 +183,7 @@ export async function requestNodePairing(
|
||||||
deviceFamily: req.deviceFamily,
|
deviceFamily: req.deviceFamily,
|
||||||
modelIdentifier: req.modelIdentifier,
|
modelIdentifier: req.modelIdentifier,
|
||||||
caps: req.caps,
|
caps: req.caps,
|
||||||
|
commands: req.commands,
|
||||||
remoteIp: req.remoteIp,
|
remoteIp: req.remoteIp,
|
||||||
isRepair,
|
isRepair,
|
||||||
ts: Date.now(),
|
ts: Date.now(),
|
||||||
|
|
@ -211,6 +214,7 @@ export async function approveNodePairing(
|
||||||
deviceFamily: pending.deviceFamily,
|
deviceFamily: pending.deviceFamily,
|
||||||
modelIdentifier: pending.modelIdentifier,
|
modelIdentifier: pending.modelIdentifier,
|
||||||
caps: pending.caps,
|
caps: pending.caps,
|
||||||
|
commands: pending.commands,
|
||||||
remoteIp: pending.remoteIp,
|
remoteIp: pending.remoteIp,
|
||||||
createdAtMs: existing?.createdAtMs ?? now,
|
createdAtMs: existing?.createdAtMs ?? now,
|
||||||
approvedAtMs: now,
|
approvedAtMs: now,
|
||||||
|
|
@ -251,7 +255,12 @@ export async function verifyNodeToken(
|
||||||
|
|
||||||
export async function updatePairedNodeMetadata(
|
export async function updatePairedNodeMetadata(
|
||||||
nodeId: string,
|
nodeId: string,
|
||||||
patch: Partial<Omit<NodePairingPairedNode, "nodeId" | "token" | "createdAtMs" | "approvedAtMs">>,
|
patch: Partial<
|
||||||
|
Omit<
|
||||||
|
NodePairingPairedNode,
|
||||||
|
"nodeId" | "token" | "createdAtMs" | "approvedAtMs"
|
||||||
|
>
|
||||||
|
>,
|
||||||
baseDir?: string,
|
baseDir?: string,
|
||||||
) {
|
) {
|
||||||
await withLock(async () => {
|
await withLock(async () => {
|
||||||
|
|
@ -269,6 +278,7 @@ export async function updatePairedNodeMetadata(
|
||||||
modelIdentifier: patch.modelIdentifier ?? existing.modelIdentifier,
|
modelIdentifier: patch.modelIdentifier ?? existing.modelIdentifier,
|
||||||
remoteIp: patch.remoteIp ?? existing.remoteIp,
|
remoteIp: patch.remoteIp ?? existing.remoteIp,
|
||||||
caps: patch.caps ?? existing.caps,
|
caps: patch.caps ?? existing.caps,
|
||||||
|
commands: patch.commands ?? existing.commands,
|
||||||
};
|
};
|
||||||
|
|
||||||
state.pairedByNodeId[normalized] = next;
|
state.pairedByNodeId[normalized] = next;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue