diff --git a/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts b/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts index 7383725ef1..1354a7e72d 100644 --- a/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts +++ b/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts @@ -96,7 +96,12 @@ export class WebSocketTransport implements ITransport { webSocket.onmessage = (message: MessageEvent) => { this.logger.log(LogLevel.Trace, `(WebSockets transport) data received. ${getDataDetail(message.data, this.logMessageContent)}.`); if (this.onreceive) { - this.onreceive(message.data); + try { + this.onreceive(message.data); + } catch (error) { + this.close(error); + return; + } } }; @@ -131,15 +136,21 @@ export class WebSocketTransport implements ITransport { return Promise.resolve(); } - private close(event?: CloseEvent): void { + private close(event?: CloseEvent | Error): void { // webSocket will be null if the transport did not start successfully this.logger.log(LogLevel.Trace, "(WebSockets transport) socket closed."); if (this.onclose) { - if (event && (event.wasClean === false || event.code !== 1000)) { + if (this.isCloseEvent(event) && (event.wasClean === false || event.code !== 1000)) { this.onclose(new Error(`WebSocket closed with status code: ${event.code} (${event.reason}).`)); + } else if (event instanceof Error) { + this.onclose(event); } else { this.onclose(); } } } + + private isCloseEvent(event?: any): event is CloseEvent { + return event && typeof event.wasClean === "boolean" && typeof event.code === "number"; + } } diff --git a/src/SignalR/clients/ts/signalr/tests/WebSocketTransport.test.ts b/src/SignalR/clients/ts/signalr/tests/WebSocketTransport.test.ts index 4ac43fe966..7fae4c65ae 100644 --- a/src/SignalR/clients/ts/signalr/tests/WebSocketTransport.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/WebSocketTransport.test.ts @@ -229,6 +229,36 @@ describe("WebSocketTransport", () => { .toBe("WebSocket is not in the OPEN state"); }); }); + + it("is closed from 'onreceive' callback throwing", async () => { + await VerifyLogger.run(async (logger) => { + (global as any).ErrorEvent = TestEvent; + const webSocket = await createAndStartWebSocket(logger); + + let closeCalled: boolean = false; + let error: Error; + webSocket.onclose = (e) => { + closeCalled = true; + error = e!; + }; + + const receiveError = new Error("callback error"); + webSocket.onreceive = (data) => { + throw receiveError; + }; + + const message = new TestMessageEvent(); + message.data = "receive data"; + TestWebSocket.webSocket.onmessage(message); + + expect(closeCalled).toBe(true); + expect(error!).toBe(receiveError); + + await expect(webSocket.send("")) + .rejects + .toBe("WebSocket is not in the OPEN state"); + }); + }); }); async function createAndStartWebSocket(logger: ILogger, url?: string, accessTokenFactory?: (() => string | Promise), format?: TransferFormat): Promise {