From aeec7ffada970a70714ba8c75fb94c70c30c5a51 Mon Sep 17 00:00:00 2001 From: Cesare Naldi <3353250+cesarenaldi@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:47:56 +0200 Subject: [PATCH] fix(client): clear RFQ session on disconnect --- .changeset/rfq-close-reconnect.md | 5 ++ packages/client/src/websockets/rfq/quoter.ts | 10 +++- packages/client/tests/integration/rfq.test.ts | 58 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 .changeset/rfq-close-reconnect.md diff --git a/.changeset/rfq-close-reconnect.md b/.changeset/rfq-close-reconnect.md new file mode 100644 index 0000000..c3b3d6c --- /dev/null +++ b/.changeset/rfq-close-reconnect.md @@ -0,0 +1,5 @@ +--- +"@polymarket/client": patch +--- + +Clear cached RFQ quoter sessions immediately after unexpected websocket disconnects. diff --git a/packages/client/src/websockets/rfq/quoter.ts b/packages/client/src/websockets/rfq/quoter.ts index 7ce18dd..f45af04 100644 --- a/packages/client/src/websockets/rfq/quoter.ts +++ b/packages/client/src/websockets/rfq/quoter.ts @@ -83,7 +83,10 @@ export class RfqQuoterWebSocketManager { } async connect(): Promise { - if (this.#session !== undefined) return this.#session; + if (this.#session !== undefined) { + if (!this.#session.closed) return this.#session; + this.#session = undefined; + } if (this.#connecting !== undefined) return this.#connecting; const session = new RfqWebSocketSession({ @@ -184,6 +187,10 @@ class RfqWebSocketSession implements RfqSession, RfqEventController { this.#url = options.url; } + get closed(): boolean { + return this.#closing !== undefined; + } + async connect(): Promise { const auth = createPending(); this.#auth = auth; @@ -310,6 +317,7 @@ class RfqWebSocketSession implements RfqSession, RfqEventController { } #handleClose(): void { + this.#onClose(); void this.close(); } diff --git a/packages/client/tests/integration/rfq.test.ts b/packages/client/tests/integration/rfq.test.ts index 6bd39ae..4bd5eb6 100644 --- a/packages/client/tests/integration/rfq.test.ts +++ b/packages/client/tests/integration/rfq.test.ts @@ -1216,6 +1216,64 @@ describe('RFQ sessions', () => { }); }); + describe('when reopening immediately after an unexpected close', () => { + beforeEach(() => { + server.resetHandlers(); + server.use( + rfq.addEventListener('connection', ({ client: socket }) => { + connectionCount += 1; + + socket.addEventListener('message', (event) => { + const frame = recordOutboundFrame(event.data, outboundFrames); + + if (frame.type === 'auth') { + socket.send(authAckMessage()); + socket.send(quoteRequestMessage()); + return; + } + + if (frame.type === 'RFQ_QUOTE') { + quoteAmounts(frame); + socket.close(); + } + }); + }), + ); + }); + + it('opens a fresh session instead of returning the closed session', async ({ + secureClientWithDepositWallet, + }) => { + const session = await secureClientWithDepositWallet.openRfqSession(); + + try { + const iterator = session[Symbol.asyncIterator](); + const next = await iterator.next(); + + if (next.done === true || next.value.type !== 'quote_request') { + throw new Error('Expected RFQ quote request.'); + } + + const quote = next.value.quote({ price: 0.45 }); + const reopened = quote.then( + () => { + throw new Error('Expected RFQ quote rejection.'); + }, + () => secureClientWithDepositWallet.openRfqSession(), + ); + + const nextSession = await reopened; + + expect(nextSession).not.toBe(session); + expect(connectionCount).toBe(2); + + await nextSession.close(); + } finally { + await secureClientWithDepositWallet.closeSubscriptions(); + } + }); + }); + describe('when the connection closes before confirmation acknowledgement', () => { beforeEach(() => { server.resetHandlers();