Skip to content

Commit

Permalink
Publish errors through the web socket channel for clean errors other …
Browse files Browse the repository at this point in the history
…than ‘normal closure’ (#3375)
  • Loading branch information
steveluscher authored Oct 12, 2024
1 parent 3cfc8c3 commit 3ebd33d
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,21 +122,48 @@ describe('a websocket channel', () => {
ws.send('message');
expect(messageListener).not.toHaveBeenCalled();
});
it('publishes errors to error listeners when the connection closes', () => {
it('publishes errors to error listeners when the connection closes in an unclean manner', () => {
const errorListener = jest.fn();
channel.on('error', errorListener);
const errorDetails = {
const closeOptions = {
code: 1006 /* abnormal closure */,
reason: 'o no',
wasClean: false,
};
ws.error(errorDetails);
ws.close(closeOptions);
expect(errorListener).toHaveBeenCalledWith(
new SolanaError(SOLANA_ERROR__RPC_SUBSCRIPTIONS__CHANNEL_CONNECTION_CLOSED, {
cause: expect.objectContaining(closeOptions),
}),
);
expect(errorListener.mock.lastCall[0].cause).toMatchObject(closeOptions);
});
it('publishes errors to error listeners when the connection closes cleanly with a non-1000 code', () => {
const errorListener = jest.fn();
channel.on('error', errorListener);
const closeOptions = {
code: 1011 /* internal error */,
reason: 'o no',
wasClean: true,
};
ws.server.close(closeOptions);
expect(errorListener).toHaveBeenCalledWith(
new SolanaError(SOLANA_ERROR__RPC_SUBSCRIPTIONS__CHANNEL_CONNECTION_CLOSED, {
cause: expect.objectContaining(errorDetails),
cause: expect.objectContaining(closeOptions),
}),
);
expect(errorListener.mock.lastCall[0].cause).toMatchObject(errorDetails);
expect(errorListener.mock.lastCall[0].cause).toMatchObject(closeOptions);
});
it('does not publish errors to error listeners when the connection closes cleanly with a 1000 code', () => {
const errorListener = jest.fn();
channel.on('error', errorListener);
const errorDetails = {
code: 1000 /* normal closure */,
reason: 'I enjoyed our little chat',
wasClean: true,
};
ws.error(errorDetails);
expect(errorListener).not.toHaveBeenCalled();
});
it('does not publish errors received between the time the channel is aborted and the time it closes', () => {
const errorListener = jest.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export type Config = Readonly<{

type WebSocketMessage = ArrayBufferLike | ArrayBufferView | Blob | string;

const NORMAL_CLOSURE_CODE = 1000; // https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1

export function createWebSocketChannel({
sendBufferHighWatermark,
signal,
Expand All @@ -39,7 +41,7 @@ export function createWebSocketChannel({
rejectOpen(signal.reason);
}
if (webSocket.readyState !== WebSocket.CLOSED && webSocket.readyState !== WebSocket.CLOSING) {
webSocket.close(1000);
webSocket.close(NORMAL_CLOSURE_CODE);
}
}
function handleClose(ev: CloseEvent) {
Expand All @@ -50,7 +52,7 @@ export function createWebSocketChannel({
webSocket.removeEventListener('error', handleError);
webSocket.removeEventListener('message', handleMessage);
webSocket.removeEventListener('open', handleOpen);
if (!signal.aborted && !ev.wasClean) {
if (!signal.aborted && !(ev.wasClean && ev.code === NORMAL_CLOSURE_CODE)) {
eventTarget.dispatchEvent(
new CustomEvent('error', {
detail: new SolanaError(SOLANA_ERROR__RPC_SUBSCRIPTIONS__CHANNEL_CONNECTION_CLOSED, {
Expand Down

0 comments on commit 3ebd33d

Please sign in to comment.