Skip to content

Commit b99c672

Browse files
authored
fix: socket-io reconnection bug (#2174)
* fix: socket-io reconnection bug * test: add socket-io reconnection test
1 parent d344a79 commit b99c672

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

client/src/socket-io/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class StacksApiSocketClient {
7272

7373
handleSubscription(topic: Topic, subscribe = false, listener?: (...args: any[]) => void) {
7474
const subsQuery = this.socket.io.opts.query?.subscriptions as string | undefined;
75-
const subscriptions = new Set(subsQuery?.split(',') ?? []);
75+
const subscriptions = new Set(subsQuery ? subsQuery.split(',') : []);
7676
if (subscribe) {
7777
this.socket.emit('subscribe', topic, error => {
7878
if (error) console.error(`Error subscribing: ${error}`);

src/api/routes/ws/channels/socket-io-channel.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ export class SocketIOChannel extends WebSocketChannel {
9090
io.use((socket, next) => {
9191
const subscriptions = socket.handshake.query['subscriptions'];
9292
if (subscriptions) {
93-
const topics = [...[subscriptions]].flat().flatMap(r => r.split(','));
93+
const topics = [...[subscriptions]]
94+
.flat()
95+
.flatMap(r => r.split(','))
96+
.filter(r => !!r);
9497
const invalidSubs = this.getInvalidSubscriptionTopics(topics as Topic[]);
9598
if (invalidSubs) {
9699
const error = new Error(`Invalid topic: ${invalidSubs.join(', ')}`);

tests/api/socket-io.test.ts

+57
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
NftEvent,
2121
Transaction,
2222
} from 'client/src/types';
23+
import { Socket } from 'node:net';
2324

2425
describe('socket-io', () => {
2526
let apiServer: ApiServer;
@@ -40,6 +41,62 @@ describe('socket-io', () => {
4041
await migrate('down');
4142
});
4243

44+
test('socket-io-client > reconnect', async () => {
45+
const serverSocketConnectWaiter = waiter<Socket>();
46+
apiServer.server.once('upgrade', (_req, socket: Socket) => {
47+
serverSocketConnectWaiter.finish(socket);
48+
});
49+
50+
const client = new StacksApiSocketClient({
51+
url: `http://${apiServer.address}`,
52+
// socketOpts: { reconnection: false },
53+
});
54+
55+
const updateWaiter: Waiter<Block> = waiter();
56+
const subResult = client.subscribeBlocks(block => updateWaiter.finish(block));
57+
58+
// subscriptions should be saved in the client query obj
59+
expect(client.socket.io.opts.query).toMatchObject({ subscriptions: 'block' });
60+
61+
// wait for initial client connection
62+
await new Promise<void>(resolve => client.socket.once('connect', resolve));
63+
64+
const connectAttempt = waiter();
65+
client.socket.io.once('reconnect_attempt', attempt => {
66+
// subscriptions should be saved in the client query obj
67+
expect(client.socket.io.opts.query).toMatchObject({ subscriptions: 'block' });
68+
connectAttempt.finish();
69+
});
70+
71+
const reconnectWaiter = waiter();
72+
client.socket.io.once('reconnect', () => reconnectWaiter.finish());
73+
74+
// force kill client connection on the server to trigger reconnect
75+
const serverSocket = await serverSocketConnectWaiter;
76+
serverSocket.resetAndDestroy();
77+
78+
await connectAttempt;
79+
await reconnectWaiter;
80+
81+
// ensure client still waiting for block update
82+
expect(updateWaiter.isFinished).toBe(false);
83+
84+
const block = new TestBlockBuilder({ block_hash: '0x1234', burn_block_hash: '0x5454' })
85+
.addTx({ tx_id: '0x4321' })
86+
.build();
87+
await db.update(block);
88+
89+
const result = await updateWaiter;
90+
try {
91+
expect(result.hash).toEqual('0x1234');
92+
expect(result.burn_block_hash).toEqual('0x5454');
93+
expect(result.txs[0]).toEqual('0x4321');
94+
} finally {
95+
subResult.unsubscribe();
96+
client.socket.close();
97+
}
98+
});
99+
43100
test('socket-io-client > block updates', async () => {
44101
const client = new StacksApiSocketClient({
45102
url: `http://${apiServer.address}`,

0 commit comments

Comments
 (0)