@pulsepairs/sdk 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/ws.js +42 -1
  2. package/package.json +3 -3
package/dist/ws.js CHANGED
@@ -212,7 +212,48 @@ export class UpDownWsClient {
212
212
  const delay = attempt > 12 ? 30_000 : Math.min(30_000, 1000 * 2 ** Math.min(attempt, 5));
213
213
  this.timer = setTimeout(() => this.openSocket(), delay);
214
214
  };
215
- ws.onerror = () => ws.close();
215
+ // NEVER call close() synchronously from here.
216
+ //
217
+ // `onerror = () => ws.close()` stood here through 0.8.0 and took
218
+ // LedgerCore's process down twice against our 502s:
219
+ // "RangeError: Maximum call stack size exceeded" at dist/ws.js:215. On a
220
+ // WebSocket implementation that fails a CONNECTING socket SYNCHRONOUSLY,
221
+ // close() dispatches `error` before it returns, which re-enters this
222
+ // handler, which calls close() again — unbounded recursion on the stack
223
+ // rather than a loop that backoff could ever slow down. A handshake that
224
+ // fails fast, like a 502 from a load balancer, is exactly the trigger.
225
+ //
226
+ // Node's built-in WebSocket and the `ws` package both defer that error to a
227
+ // later tick and so survive it, which is why this went unnoticed here: the
228
+ // bug is invisible on the runtimes we test on and fatal on the ones we
229
+ // don't. The guard below therefore does not depend on knowing which
230
+ // implementation is underneath.
231
+ //
232
+ // The close is also redundant on every conforming implementation: a socket
233
+ // that emits `error` emits `close` afterwards, and `onclose` already owns
234
+ // reconnect. It is kept only for an implementation that errors without ever
235
+ // closing, deferred to a later tick where re-entry cannot grow the stack.
236
+ let errorHandled = false;
237
+ ws.onerror = () => {
238
+ if (errorHandled)
239
+ return; // re-entry is the bug; make it impossible
240
+ errorHandled = true;
241
+ if (this.closed || this.ws !== ws)
242
+ return;
243
+ setTimeout(() => {
244
+ // `onclose` normally fires on its own and has already scheduled the
245
+ // reconnect. Only force it if this socket errored and then just sat
246
+ // there, which would otherwise strand the client with no retry.
247
+ if (ws.readyState === 3 /* CLOSED */)
248
+ return;
249
+ try {
250
+ ws.close();
251
+ }
252
+ catch {
253
+ /* ignore */
254
+ }
255
+ }, 0);
256
+ };
216
257
  }
217
258
  async startHandshake() {
218
259
  if (!this.mode || !this.ws)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pulsepairs/sdk",
3
- "version": "0.8.0",
4
- "description": "Standalone SDK for the UpDown (PulsePairs) up/down prediction markets — matcher REST/WS client, EIP-712 order signing, trade-math, and Alchemy Account Kit (smart-account) order signing for rain.trade integration.",
3
+ "version": "0.8.1",
4
+ "description": "Standalone SDK for the UpDown (PulsePairs) up/down prediction markets \u2014 matcher REST/WS client, EIP-712 order signing, trade-math, and Alchemy Account Kit (smart-account) order signing for rain.trade integration.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -34,7 +34,7 @@
34
34
  "scripts": {
35
35
  "clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\"",
36
36
  "build": "npm run clean && tsc",
37
- "test": "node scripts/eip712-golden.test.mjs && node scripts/rawtx-tier.test.mjs && node scripts/hot-key-safety.test.mjs && node scripts/reconcile.test.mjs && node scripts/pnl.test.mjs && node scripts/ws-subscribe.test.mjs",
37
+ "test": "node scripts/eip712-golden.test.mjs && node scripts/rawtx-tier.test.mjs && node scripts/hot-key-safety.test.mjs && node scripts/reconcile.test.mjs && node scripts/pnl.test.mjs && node scripts/ws-subscribe.test.mjs && node scripts/ws-onerror.test.mjs",
38
38
  "prepublishOnly": "npm run build && npm test",
39
39
  "example:taker": "npx tsx examples/simple-taker.ts",
40
40
  "example:maker": "npx tsx examples/simple-maker.ts",