Skip to content

Commit 931dc86

Browse files
committed
fix(server): avoid crashing server when using ws adapter by logging error
fixes #1615
1 parent 528e42a commit 931dc86

5 files changed

Lines changed: 54 additions & 5 deletions

File tree

packages/server/src/adapters/ws/handler.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { StandardHandler } from '../standard'
55
import type {
66
HandleStandardServerPeerMessageOptions,
77
} from '../standard-peer'
8-
import { readAsBuffer, resolveMaybeOptionalOptions } from '@orpc/shared'
8+
import { logError, readAsBuffer, resolveMaybeOptionalOptions } from '@orpc/shared'
99
import { ServerPeer } from '@orpc/standard-server-peer'
1010
import { createServerPeerHandleRequestFn } from '../standard-peer'
1111

@@ -26,10 +26,19 @@ export class WsHandler<T extends Context> {
2626
? await readAsBuffer(new Blob(event.data))
2727
: event.data
2828

29-
await peer.message(
30-
message,
31-
createServerPeerHandleRequestFn(this.standardHandler, resolveMaybeOptionalOptions(rest)),
32-
)
29+
try {
30+
await peer.message(
31+
message,
32+
createServerPeerHandleRequestFn(this.standardHandler, resolveMaybeOptionalOptions(rest)),
33+
)
34+
}
35+
catch (error) {
36+
/**
37+
* Users cannot catch errors thrown by this `peer.message`, and node.js may
38+
* crash on unhandled rejections, so we log the error here to prevent that.
39+
*/
40+
logError(error)
41+
}
3342
})
3443

3544
ws.addEventListener('close', () => {

packages/server/src/adapters/ws/rpc-handler.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import * as Shared from '@orpc/shared'
12
import { encodeRequestMessage, MessageType } from '@orpc/standard-server-peer'
23
import { os } from '../../builder'
34
import { RPCHandler } from './rpc-handler'
45

6+
const logErrorSpy = vi.spyOn(Shared, 'logError').mockImplementation(() => {})
7+
58
describe('rpcHandler', async () => {
69
let signal: AbortSignal
710

@@ -70,4 +73,14 @@ describe('rpcHandler', async () => {
7073
await vi.waitFor(() => expect(signal.aborted).toBe(true))
7174
expect(wss.send).not.toHaveBeenCalled()
7275
})
76+
77+
it('logs error on message handler error instead of throwing', async () => {
78+
onMessage({
79+
data: 'invalid message that will cause an error',
80+
})
81+
82+
await vi.waitFor(() => {
83+
expect(logErrorSpy).toHaveBeenCalledWith(expect.any(Error))
84+
})
85+
})
7386
})

packages/shared/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export * from './id'
1010
export * from './interceptor'
1111
export * from './iterator'
1212
export * from './json'
13+
export * from './log'
1314
export * from './object'
1415
export * from './otel'
1516
export * from './proxy'

packages/shared/src/log.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { logError } from './log'
2+
3+
describe('logError', () => {
4+
it('should log error to console.error', () => {
5+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
6+
const error = new Error('Test error')
7+
logError(error)
8+
expect(consoleErrorSpy).toHaveBeenCalledWith(error)
9+
consoleErrorSpy.mockRestore()
10+
})
11+
12+
it('should do nothing if console.error is not available', () => {
13+
const originalConsoleError = console.error
14+
// @ts-expect-error: Temporarily override console.error for testing
15+
console.error = undefined
16+
const error = new Error('Test error')
17+
expect(() => logError(error)).not.toThrow()
18+
// Restore the original console.error function
19+
console.error = originalConsoleError
20+
})
21+
})

packages/shared/src/log.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function logError(error: unknown): void {
2+
if (typeof console === 'object' && typeof console.error === 'function') {
3+
console.error(error)
4+
}
5+
}

0 commit comments

Comments
 (0)