fix(gateway): resolve iOS node invokes

This commit is contained in:
Peter Steinberger
2025-12-14 00:00:05 +00:00
parent 17e183f5cf
commit aef18b7359
7 changed files with 185 additions and 20 deletions

View File

@@ -51,7 +51,8 @@ export class GatewayClient {
start() {
if (this.closed) return;
const url = this.opts.url ?? "ws://127.0.0.1:18789";
this.ws = new WebSocket(url, { maxPayload: 512 * 1024 });
// Allow node screen snapshots and other large responses.
this.ws = new WebSocket(url, { maxPayload: 10 * 1024 * 1024 });
this.ws.on("open", () => this.sendConnect());
this.ws.on("message", (data) => this.handleMessage(data.toString()));

View File

@@ -38,6 +38,8 @@ import {
HelloOkSchema,
type NodeInvokeParams,
NodeInvokeParamsSchema,
type NodeListParams,
NodeListParamsSchema,
type NodePairApproveParams,
NodePairApproveParamsSchema,
type NodePairListParams,
@@ -105,6 +107,8 @@ export const validateNodePairRejectParams = ajv.compile<NodePairRejectParams>(
export const validateNodePairVerifyParams = ajv.compile<NodePairVerifyParams>(
NodePairVerifyParamsSchema,
);
export const validateNodeListParams =
ajv.compile<NodeListParams>(NodeListParamsSchema);
export const validateNodeInvokeParams = ajv.compile<NodeInvokeParams>(
NodeInvokeParamsSchema,
);
@@ -163,6 +167,7 @@ export {
NodePairApproveParamsSchema,
NodePairRejectParamsSchema,
NodePairVerifyParamsSchema,
NodeListParamsSchema,
NodeInvokeParamsSchema,
SessionsListParamsSchema,
SessionsPatchParamsSchema,
@@ -205,6 +210,7 @@ export type {
NodePairApproveParams,
NodePairRejectParams,
NodePairVerifyParams,
NodeListParams,
NodeInvokeParams,
SessionsListParams,
SessionsPatchParams,

View File

@@ -243,12 +243,18 @@ export const NodePairVerifyParamsSchema = Type.Object(
{ additionalProperties: false },
);
export const NodeListParamsSchema = Type.Object(
{},
{ additionalProperties: false },
);
export const NodeInvokeParamsSchema = Type.Object(
{
nodeId: NonEmptyString,
command: NonEmptyString,
params: Type.Optional(Type.Unknown()),
timeoutMs: Type.Optional(Type.Integer({ minimum: 0 })),
idempotencyKey: NonEmptyString,
},
{ additionalProperties: false },
);
@@ -507,6 +513,7 @@ export const ProtocolSchemas: Record<string, TSchema> = {
NodePairApproveParams: NodePairApproveParamsSchema,
NodePairRejectParams: NodePairRejectParamsSchema,
NodePairVerifyParams: NodePairVerifyParamsSchema,
NodeListParams: NodeListParamsSchema,
NodeInvokeParams: NodeInvokeParamsSchema,
SessionsListParams: SessionsListParamsSchema,
SessionsPatchParams: SessionsPatchParamsSchema,
@@ -545,6 +552,7 @@ export type NodePairListParams = Static<typeof NodePairListParamsSchema>;
export type NodePairApproveParams = Static<typeof NodePairApproveParamsSchema>;
export type NodePairRejectParams = Static<typeof NodePairRejectParamsSchema>;
export type NodePairVerifyParams = Static<typeof NodePairVerifyParamsSchema>;
export type NodeListParams = Static<typeof NodeListParamsSchema>;
export type NodeInvokeParams = Static<typeof NodeInvokeParamsSchema>;
export type SessionsListParams = Static<typeof SessionsListParamsSchema>;
export type SessionsPatchParams = Static<typeof SessionsPatchParamsSchema>;

View File

@@ -399,6 +399,7 @@ describe("gateway server", () => {
command: "screen.eval",
params: { javaScript: "2+2" },
timeoutMs: 123,
idempotencyKey: "idem-1",
});
expect(res.ok).toBe(true);

View File

@@ -101,6 +101,7 @@ import {
validateCronStatusParams,
validateCronUpdateParams,
validateNodeInvokeParams,
validateNodeListParams,
validateNodePairApproveParams,
validateNodePairListParams,
validateNodePairRejectParams,
@@ -177,6 +178,7 @@ const METHODS = [
"node.pair.approve",
"node.pair.reject",
"node.pair.verify",
"node.list",
"node.invoke",
"cron.list",
"cron.status",
@@ -2056,6 +2058,49 @@ export async function startGatewayServer(
}
break;
}
case "node.list": {
const params = (req.params ?? {}) as Record<string, unknown>;
if (!validateNodeListParams(params)) {
respond(
false,
undefined,
errorShape(
ErrorCodes.INVALID_REQUEST,
`invalid node.list params: ${formatValidationErrors(validateNodeListParams.errors)}`,
),
);
break;
}
try {
const list = await listNodePairing();
const connected = bridge?.listConnected?.() ?? [];
const connectedById = new Map(
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),
};
});
respond(true, { ts: Date.now(), nodes }, undefined);
} catch (err) {
respond(
false,
undefined,
errorShape(ErrorCodes.UNAVAILABLE, formatForLog(err)),
);
}
break;
}
case "node.invoke": {
const params = (req.params ?? {}) as Record<string, unknown>;
if (!validateNodeInvokeParams(params)) {
@@ -2082,6 +2127,7 @@ export async function startGatewayServer(
command: string;
params?: unknown;
timeoutMs?: number;
idempotencyKey: string;
};
const nodeId = String(p.nodeId ?? "").trim();
const command = String(p.command ?? "").trim();