From 59fdd68c7c8ce2b214f5acd9f44a4038981f4419 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 11 Dec 2025 17:21:30 -0800 Subject: [PATCH 1/2] catch construct error and re-report as protocol error --- transport/client.ts | 18 ++++++++++- transport/transport.test.ts | 60 +++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/transport/client.ts b/transport/client.ts index b9820e2e..5927e4f9 100644 --- a/transport/client.ts +++ b/transport/client.ts @@ -480,7 +480,23 @@ export abstract class ClientTransport< let metadata: unknown = undefined; if (this.handshakeExtensions) { - metadata = await this.handshakeExtensions.construct(); + try { + metadata = await this.handshakeExtensions.construct(); + } catch (err) { + const errStr = coerceErrorString(err); + this.log?.error( + `failed to construct handshake metadata for session to ${session.to}: ${errStr}`, + session.loggingMetadata, + ); + + this.protocolError({ + type: ProtocolError.HandshakeFailed, + message: `failed to construct handshake metadata: ${errStr}`, + }); + this.deleteSession(session, { unhealthy: true }); + + return; + } } // double-check to make sure we haven't transitioned the session yet diff --git a/transport/transport.test.ts b/transport/transport.test.ts index 9845da02..8c93f287 100644 --- a/transport/transport.test.ts +++ b/transport/transport.test.ts @@ -1590,6 +1590,66 @@ describe.each(testMatrix())( }); }); + test('construct throwing during handshake results in protocol error', async () => { + const schema = Type.Object({ + foo: Type.String(), + }); + + interface Metadata { + foo: string; + } + + const constructError = new Error('Failed to construct metadata'); + const get = vi.fn(async () => { + throw constructError; + }); + const parse = vi.fn(async (metadata: Metadata) => ({ + foo: metadata.foo, + })); + + const serverTransport = getServerTransport('SERVER', { + schema, + validate: parse, + }); + const clientTransport = getClientTransport('client', { + schema, + construct: get, + }); + const clientHandshakeFailed = vi.fn(); + clientTransport.addEventListener('protocolError', clientHandshakeFailed); + clientTransport.connect(serverTransport.clientId); + + addPostTestCleanup(async () => { + clientTransport.removeEventListener( + 'protocolError', + clientHandshakeFailed, + ); + await cleanupTransports([clientTransport, serverTransport]); + }); + + await waitFor(() => { + expect(get).toHaveBeenCalledTimes(1); + expect(clientHandshakeFailed).toHaveBeenCalledTimes(1); + expect(clientHandshakeFailed).toHaveBeenCalledWith( + expect.objectContaining({ + type: ProtocolError.HandshakeFailed, + message: + 'failed to construct handshake metadata: Failed to construct metadata', + }), + ); + // should never get to the server + expect(parse).toHaveBeenCalledTimes(0); + }); + + expect(numberOfConnections(clientTransport)).toBe(0); + expect(numberOfConnections(serverTransport)).toBe(0); + + await testFinishesCleanly({ + clientTransports: [clientTransport], + serverTransport, + }); + }); + test('server checks request schema on receive', async () => { const clientRequestSchema = Type.Object({ foo: Type.Number(), From 7e11a2967df7fb29f769c8b9e88af5d82e849947 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 11 Dec 2025 17:22:45 -0800 Subject: [PATCH 2/2] 0.212.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9c282b04..a2f98ccb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@replit/river", - "version": "0.212.0", + "version": "0.212.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@replit/river", - "version": "0.212.0", + "version": "0.212.1", "license": "MIT", "dependencies": { "@msgpack/msgpack": "^3.1.2", diff --git a/package.json b/package.json index c12b48f6..0a85e9cf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@replit/river", "description": "It's like tRPC but... with JSON Schema Support, duplex streaming and support for service multiplexing. Transport agnostic!", - "version": "0.212.0", + "version": "0.212.1", "type": "module", "exports": { ".": {