fix(sdk): settle connect after observer errors

This commit is contained in:
Vincent Koc
2026-06-19 05:27:52 +02:00
parent f1c0d5f06f
commit ae6e1fa4d2
2 changed files with 54 additions and 12 deletions

View File

@@ -59,4 +59,40 @@ describe("GatewayClientTransport", () => {
await expect(transport.connect()).rejects.toThrow("gateway transport is closed");
expect(gatewayClientMocks.instances).toHaveLength(0);
});
it("resolves connect when a hello observer throws", async () => {
const onHelloOk = vi.fn(() => {
throw new Error("hello observer failed");
});
const transport = new GatewayClientTransport({ onHelloOk });
const connect = transport.connect();
const client = gatewayClientMocks.instances[0];
expect(() => client?.opts.onHelloOk?.({ sessionId: "session-1" })).toThrow(
"hello observer failed",
);
await expect(connect).resolves.toBeUndefined();
expect(onHelloOk).toHaveBeenCalledWith({ sessionId: "session-1" });
});
it("rejects connect when a connect-error observer throws", async () => {
const onConnectError = vi.fn(() => {
throw new Error("connect observer failed");
});
const transport = new GatewayClientTransport({ onConnectError });
const connect = transport.connect();
const connectExpectation = expect(connect).rejects.toThrow("gateway rejected");
const client = gatewayClientMocks.instances[0];
expect(() => client?.opts.onConnectError?.(new Error("gateway rejected"))).toThrow(
"connect observer failed",
);
await connectExpectation;
expect(onConnectError).toHaveBeenCalledOnce();
expect(client?.stopAndWait).toHaveBeenCalledTimes(1);
});
});

View File

@@ -103,21 +103,27 @@ export class GatewayClientTransport implements ConnectableOpenClawTransport {
this.options.onEvent?.(normalized);
},
onHelloOk: (_hello: unknown) => {
this.options.onHelloOk?.(_hello);
this.rejectPendingConnect = null;
resolve();
try {
this.options.onHelloOk?.(_hello);
} finally {
this.rejectPendingConnect = null;
resolve();
}
},
onConnectError: (error: Error) => {
this.options.onConnectError?.(error);
if (this.client === client) {
this.client = null;
try {
this.options.onConnectError?.(error);
} finally {
if (this.client === client) {
this.client = null;
}
if (this.connectPromise) {
this.connectPromise = null;
}
void client.stopAndWait().catch(() => {});
this.rejectPendingConnect = null;
reject(error);
}
if (this.connectPromise) {
this.connectPromise = null;
}
void client.stopAndWait().catch(() => {});
this.rejectPendingConnect = null;
reject(error);
},
onReconnectPaused: this.options.onReconnectPaused,
onClose: this.options.onClose,