Skip to content

Commit 1d02e1f

Browse files
authored
catch construct error and re-report as protocol error (#353)
## Why construct can reject and cause an otherwise uncaught async rejection ## What changed catch construct error and re-report as protocol error ## Versioning - [ ] Breaking protocol change - [ ] Breaking ts/js API change <!-- Kind reminder to add tests and updated documentation if needed -->
1 parent 5828808 commit 1d02e1f

File tree

4 files changed

+80
-4
lines changed

4 files changed

+80
-4
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@replit/river",
33
"description": "It's like tRPC but... with JSON Schema Support, duplex streaming and support for service multiplexing. Transport agnostic!",
4-
"version": "0.212.0",
4+
"version": "0.212.1",
55
"type": "module",
66
"exports": {
77
".": {

transport/client.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,23 @@ export abstract class ClientTransport<
480480
let metadata: unknown = undefined;
481481

482482
if (this.handshakeExtensions) {
483-
metadata = await this.handshakeExtensions.construct();
483+
try {
484+
metadata = await this.handshakeExtensions.construct();
485+
} catch (err) {
486+
const errStr = coerceErrorString(err);
487+
this.log?.error(
488+
`failed to construct handshake metadata for session to ${session.to}: ${errStr}`,
489+
session.loggingMetadata,
490+
);
491+
492+
this.protocolError({
493+
type: ProtocolError.HandshakeFailed,
494+
message: `failed to construct handshake metadata: ${errStr}`,
495+
});
496+
this.deleteSession(session, { unhealthy: true });
497+
498+
return;
499+
}
484500
}
485501

486502
// double-check to make sure we haven't transitioned the session yet

transport/transport.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,6 +1590,66 @@ describe.each(testMatrix())(
15901590
});
15911591
});
15921592

1593+
test('construct throwing during handshake results in protocol error', async () => {
1594+
const schema = Type.Object({
1595+
foo: Type.String(),
1596+
});
1597+
1598+
interface Metadata {
1599+
foo: string;
1600+
}
1601+
1602+
const constructError = new Error('Failed to construct metadata');
1603+
const get = vi.fn(async () => {
1604+
throw constructError;
1605+
});
1606+
const parse = vi.fn(async (metadata: Metadata) => ({
1607+
foo: metadata.foo,
1608+
}));
1609+
1610+
const serverTransport = getServerTransport('SERVER', {
1611+
schema,
1612+
validate: parse,
1613+
});
1614+
const clientTransport = getClientTransport('client', {
1615+
schema,
1616+
construct: get,
1617+
});
1618+
const clientHandshakeFailed = vi.fn();
1619+
clientTransport.addEventListener('protocolError', clientHandshakeFailed);
1620+
clientTransport.connect(serverTransport.clientId);
1621+
1622+
addPostTestCleanup(async () => {
1623+
clientTransport.removeEventListener(
1624+
'protocolError',
1625+
clientHandshakeFailed,
1626+
);
1627+
await cleanupTransports([clientTransport, serverTransport]);
1628+
});
1629+
1630+
await waitFor(() => {
1631+
expect(get).toHaveBeenCalledTimes(1);
1632+
expect(clientHandshakeFailed).toHaveBeenCalledTimes(1);
1633+
expect(clientHandshakeFailed).toHaveBeenCalledWith(
1634+
expect.objectContaining({
1635+
type: ProtocolError.HandshakeFailed,
1636+
message:
1637+
'failed to construct handshake metadata: Failed to construct metadata',
1638+
}),
1639+
);
1640+
// should never get to the server
1641+
expect(parse).toHaveBeenCalledTimes(0);
1642+
});
1643+
1644+
expect(numberOfConnections(clientTransport)).toBe(0);
1645+
expect(numberOfConnections(serverTransport)).toBe(0);
1646+
1647+
await testFinishesCleanly({
1648+
clientTransports: [clientTransport],
1649+
serverTransport,
1650+
});
1651+
});
1652+
15931653
test('server checks request schema on receive', async () => {
15941654
const clientRequestSchema = Type.Object({
15951655
foo: Type.Number(),

0 commit comments

Comments
 (0)