@acta-markets/ts-sdk 0.0.4-beta → 0.0.6-beta

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.
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const wirePolicy_1 = require("./wirePolicy");
4
+ describe("validateQuantityBySizeRule", () => {
5
+ const rule = {
6
+ min_size: 2_000_000_000,
7
+ max_size: 10_000_000_000,
8
+ step: 1_000_000_000,
9
+ };
10
+ it("accepts a valid quantity", () => {
11
+ expect(() => (0, wirePolicy_1.validateQuantityBySizeRule)(6_000_000_000, rule)).not.toThrow();
12
+ });
13
+ it("rejects quantity below min", () => {
14
+ expect(() => (0, wirePolicy_1.validateQuantityBySizeRule)(1_000_000_000, rule)).toThrow("must be within");
15
+ });
16
+ it("rejects quantity above max", () => {
17
+ expect(() => (0, wirePolicy_1.validateQuantityBySizeRule)(11_000_000_000, rule)).toThrow("must be within");
18
+ });
19
+ it("rejects quantity with invalid step", () => {
20
+ expect(() => (0, wirePolicy_1.validateQuantityBySizeRule)(2_500_000_000, rule)).toThrow("(quantity - min_size) % step == 0");
21
+ });
22
+ });
@@ -182,6 +182,8 @@ describe("events parsing", () => {
182
182
  Buffer.from(pk(3)),
183
183
  u64(777),
184
184
  Buffer.from([1]), // is_put
185
+ Buffer.from([6]), // underlying_decimals
186
+ Buffer.from([9]), // quote_decimals
185
187
  ]));
186
188
  const d = decodeActaEventLine(line);
187
189
  expect(d.kind).toBe(EventKind.CreateMarket);
@@ -192,6 +194,8 @@ describe("events parsing", () => {
192
194
  expect(d.event.quoteMint).toBe(addr(3));
193
195
  expect(d.event.expiryTs).toBe(777n);
194
196
  expect(d.event.isPut).toBe(1);
197
+ expect(d.event.underlyingDecimals).toBe(6);
198
+ expect(d.event.quoteDecimals).toBe(9);
195
199
  }
196
200
  }
197
201
  // 9: FinalizeMarket
@@ -213,6 +217,7 @@ describe("events parsing", () => {
213
217
  u64(222),
214
218
  Buffer.from([1]),
215
219
  Buffer.from(pk(3)),
220
+ Buffer.from(pk(9)),
216
221
  ]));
217
222
  const d = decodeActaEventLine(line);
218
223
  expect(d.kind).toBe(EventKind.CreateOracle);
@@ -223,6 +228,7 @@ describe("events parsing", () => {
223
228
  expect(d.event.expiryTs).toBe(222n);
224
229
  expect(d.event.oracleType).toBe(1);
225
230
  expect(d.event.authority).toBe(addr(3));
231
+ expect(Buffer.from(d.event.feedId)).toEqual(Buffer.from(pk(9)));
226
232
  }
227
233
  }
228
234
  // 15: UpdateOraclePrice
@@ -55,6 +55,8 @@ export type ActaEvent = {
55
55
  quoteMint: Address;
56
56
  expiryTs: bigint;
57
57
  isPut: number;
58
+ underlyingDecimals: number;
59
+ quoteDecimals: number;
58
60
  } | {
59
61
  __kind: "FinalizeMarket";
60
62
  marketPda: Address;
@@ -95,6 +97,7 @@ export type ActaEvent = {
95
97
  expiryTs: bigint;
96
98
  oracleType: number;
97
99
  authority: Address;
100
+ feedId: ReadonlyUint8Array;
98
101
  } | {
99
102
  __kind: "UpdateOraclePrice";
100
103
  oraclePda: Address;
@@ -163,6 +166,8 @@ export type ActaEventArgs = {
163
166
  quoteMint: Address;
164
167
  expiryTs: number | bigint;
165
168
  isPut: number;
169
+ underlyingDecimals: number;
170
+ quoteDecimals: number;
166
171
  } | {
167
172
  __kind: "FinalizeMarket";
168
173
  marketPda: Address;
@@ -203,6 +208,7 @@ export type ActaEventArgs = {
203
208
  expiryTs: number | bigint;
204
209
  oracleType: number;
205
210
  authority: Address;
211
+ feedId: ReadonlyUint8Array;
206
212
  } | {
207
213
  __kind: "UpdateOraclePrice";
208
214
  oraclePda: Address;
@@ -82,6 +82,8 @@ export function getActaEventEncoder() {
82
82
  ["quoteMint", getAddressEncoder()],
83
83
  ["expiryTs", getU64Encoder()],
84
84
  ["isPut", getU8Encoder()],
85
+ ["underlyingDecimals", getU8Encoder()],
86
+ ["quoteDecimals", getU8Encoder()],
85
87
  ]),
86
88
  ],
87
89
  [
@@ -135,6 +137,7 @@ export function getActaEventEncoder() {
135
137
  ["expiryTs", getU64Encoder()],
136
138
  ["oracleType", getU8Encoder()],
137
139
  ["authority", getAddressEncoder()],
140
+ ["feedId", fixEncoderSize(getBytesEncoder(), 32)],
138
141
  ]),
139
142
  ],
140
143
  [
@@ -240,6 +243,8 @@ export function getActaEventDecoder() {
240
243
  ["quoteMint", getAddressDecoder()],
241
244
  ["expiryTs", getU64Decoder()],
242
245
  ["isPut", getU8Decoder()],
246
+ ["underlyingDecimals", getU8Decoder()],
247
+ ["quoteDecimals", getU8Decoder()],
243
248
  ]),
244
249
  ],
245
250
  [
@@ -293,6 +298,7 @@ export function getActaEventDecoder() {
293
298
  ["expiryTs", getU64Decoder()],
294
299
  ["oracleType", getU8Decoder()],
295
300
  ["authority", getAddressDecoder()],
301
+ ["feedId", fixDecoderSize(getBytesDecoder(), 32)],
296
302
  ]),
297
303
  ],
298
304
  [
@@ -1883,6 +1883,14 @@
1883
1883
  {
1884
1884
  "name": "is_put",
1885
1885
  "type": "u8"
1886
+ },
1887
+ {
1888
+ "name": "underlying_decimals",
1889
+ "type": "u8"
1890
+ },
1891
+ {
1892
+ "name": "quote_decimals",
1893
+ "type": "u8"
1886
1894
  }
1887
1895
  ]
1888
1896
  },
@@ -2030,6 +2038,15 @@
2030
2038
  {
2031
2039
  "name": "authority",
2032
2040
  "type": "publicKey"
2041
+ },
2042
+ {
2043
+ "name": "feed_id",
2044
+ "type": {
2045
+ "array": [
2046
+ "u8",
2047
+ 32
2048
+ ]
2049
+ }
2033
2050
  }
2034
2051
  ]
2035
2052
  },
@@ -1 +1 @@
1
- export declare const ACTA_IDL_SHA256 = "1466fbb647b4c33af315eefbb61e5e572ce14f22f16d050ac2894f8e017aaf66";
1
+ export declare const ACTA_IDL_SHA256 = "704677f7071ecbe98f442fb62468d882329b906950a707fc47aad0f38683636f";
package/dist/idl/hash.js CHANGED
@@ -1 +1 @@
1
- export const ACTA_IDL_SHA256 = "1466fbb647b4c33af315eefbb61e5e572ce14f22f16d050ac2894f8e017aaf66";
1
+ export const ACTA_IDL_SHA256 = "704677f7071ecbe98f442fb62468d882329b906950a707fc47aad0f38683636f";
@@ -2,7 +2,7 @@
2
2
  import type { AuthProvider } from "./auth";
3
3
  import type { SignerLike } from "../chain/orders";
4
4
  import type { Address } from "@solana/addresses";
5
- import type { ActiveRfqInfo, ChainEventMessage, GlobalStats, MarketDescriptorInfo, MarketInfo, MyActiveRfqInfo, MyActiveRfqsMessage, OrderStatusMessage, PositionInfo, QuoteAcknowledgedMessage, QuoteBestStatusMessage, QuoteCancelledMessage, QuoteMessage, QuoteRefreshRequestedMessage, QuoteOutbidMessage, QuoteReceivedMessage, QuoteSelectedMessage, QuotesUpdateMessage, RfqBroadcastMessage, RfqClosedMessage, RfqCreatedMessage, RfqRequestMessage, RfqAvailableAgainMessage, QuoteExpiredMessage, QuoteFilledMessage, IndicativePricesMessage, IndicativePricesRequestMessage, IndicativePricesResponseMessage, GetIndicativePricesMessage, ServerMessage, SnapshotMessage, StatsDelta, SubscriptionsMessage, TokenInfo, TradeInfo, UuidString, VersionMismatchMessage, WelcomeMessage, WsChannel } from "./types";
5
+ import type { ActiveRfqInfo, ChainEventMessage, GlobalStats, MarketDescriptorInfo, MarketInfo, MyActiveRfqInfo, MyActiveRfqsMessage, OrderStatusMessage, PositionInfo, QuoteAcknowledgedMessage, QuoteBestStatusMessage, QuoteCancelledMessage, QuoteMessage, QuoteRefreshRequestedMessage, QuoteOutbidMessage, QuoteReceivedMessage, QuoteSelectedMessage, QuotesUpdateMessage, RfqBroadcastMessage, RfqClosedMessage, RfqCreatedMessage, RfqRequestMessage, RfqAvailableAgainMessage, QuoteExpiredMessage, QuoteFilledMessage, IndicativePricesMessage, IndicativePricesRequestMessage, IndicativePricesResponseMessage, GetIndicativePricesMessage, RequestId, ServerMessage, SnapshotMessage, StatsDelta, SubscriptionsMessage, TokenInfo, TradeInfo, UuidString, VersionMismatchMessage, WelcomeMessage, WsChannel } from "./types";
6
6
  export type ConnectionState = "disconnected" | "connecting" | "authenticating" | "authenticated" | "error";
7
7
  export type ClientRole = "taker" | "maker";
8
8
  export type PendingMessagesOverflowPolicy = "drop_oldest" | "drop_newest" | "throw";
@@ -57,7 +57,8 @@ export type ActaWsClientEvents = {
57
57
  welcome: (msg: WelcomeMessage) => void;
58
58
  versionMismatch: (msg: VersionMismatchMessage) => void;
59
59
  authenticated: (sessionId: string, expiresAt: number | null) => void;
60
- authError: (reason: string) => void;
60
+ authError: (reason: string, message?: string) => void;
61
+ logoutSuccess: () => void;
61
62
  disconnected: (code: number, reason: string) => void;
62
63
  error: (error: Error) => void;
63
64
  stateChange: (state: ConnectionState) => void;
@@ -151,6 +152,7 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
151
152
  private pendingResumeSessionId;
152
153
  private connectionState;
153
154
  private sessionId;
155
+ private lastAuthSessionId;
154
156
  private helloSent;
155
157
  private welcomeReceived;
156
158
  private pendingMessages;
@@ -161,6 +163,7 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
161
163
  private subscribedChannels;
162
164
  private subscribedMarkets;
163
165
  private hasMarketScope;
166
+ private marketDescriptorsByMarket;
164
167
  readonly state: ClientState;
165
168
  constructor(options: ActaWsClientOptions);
166
169
  connectAnonymous(): void;
@@ -195,23 +198,24 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
195
198
  orderIdHex: string;
196
199
  txBase64: string;
197
200
  }): Promise<void>;
198
- getPositions(): void;
199
- getMarkets(): void;
201
+ getPositions(): RequestId;
202
+ getMarkets(): RequestId;
200
203
  getMarketDescriptors(args?: {
201
204
  active_only?: boolean;
202
- }): void;
205
+ }): RequestId;
203
206
  getExpiries(args?: {
204
207
  underlying_mint?: Address<string>;
205
208
  quote_mint?: Address<string>;
206
209
  is_put?: boolean | null;
207
- }): void;
210
+ }): RequestId;
208
211
  getTokens(args?: {
209
212
  active_only?: boolean;
210
- }): void;
211
- getMyActiveRfqs(): void;
212
- getActiveRfqs(): void;
213
- getOrderStatus(orderIdHex: string): void;
214
- cancelRfq(rfqId: string): void;
213
+ }): RequestId;
214
+ getMyActiveRfqs(): RequestId;
215
+ getActiveRfqs(): RequestId;
216
+ logout(): void;
217
+ getOrderStatus(orderIdHex: string): RequestId;
218
+ cancelRfq(rfqId: string): RequestId;
215
219
  submitQuote(quote: QuoteMessage): void;
216
220
  /**
217
221
  * Convenience: sign 32-byte `orderId` and send `Quote`.
@@ -228,7 +232,7 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
228
232
  orderId: Uint8Array;
229
233
  makerSigner: SignerLike;
230
234
  }): Promise<void>;
231
- cancelQuote(rfqId: string): void;
235
+ cancelQuote(rfqId: string): RequestId;
232
236
  subscribe(channels: WsChannel[], markets?: string[]): void;
233
237
  unsubscribe(channels: WsChannel[]): void;
234
238
  ping(): void;
@@ -236,7 +240,7 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
236
240
  private doConnect;
237
241
  private handleMessage;
238
242
  /** Taker-only: request current indicative prices for a market + position_type. */
239
- getIndicativePrices(req: GetIndicativePricesMessage): void;
243
+ getIndicativePrices(req: Omit<GetIndicativePricesMessage, "request_id">): RequestId;
240
244
  /** Maker-only: respond to an indicative request (unsigned, non-binding). */
241
245
  sendIndicativePricesResponse(resp: IndicativePricesResponseMessage): void;
242
246
  private handleAuthRequest;
@@ -257,6 +261,7 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
257
261
  private handlePositionUpdated;
258
262
  private handleChainEvent;
259
263
  private send;
264
+ private nextRequestId;
260
265
  private ensureAuthenticated;
261
266
  private setConnectionState;
262
267
  private startPingInterval;
package/dist/ws/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Acta WebSocket client (rfq-server). */
2
2
  import { buildSignedQuoteMessage, buildAcceptQuoteMessage } from "./flows";
3
- import { assertWsU64Safe } from "./wirePolicy";
3
+ import { assertWsU64Safe, validateQuantityBySizeRule } from "./wirePolicy";
4
4
  function formatServerError(error) {
5
5
  if (error.type === "generic") {
6
6
  return `${error.data.code}: ${error.data.message}`;
@@ -70,6 +70,21 @@ function getCloseInfo(ev) {
70
70
  reason: typeof rec.reason === "string" ? rec.reason : undefined,
71
71
  };
72
72
  }
73
+ function generateRequestId() {
74
+ const cryptoApi = globalThis.crypto;
75
+ if (cryptoApi?.randomUUID) {
76
+ return cryptoApi.randomUUID();
77
+ }
78
+ if (cryptoApi?.getRandomValues) {
79
+ const bytes = cryptoApi.getRandomValues(new Uint8Array(16));
80
+ // RFC4122 v4 bits: version=0100, variant=10xx.
81
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
82
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
83
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
84
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
85
+ }
86
+ return `req-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
87
+ }
73
88
  export class ActaWsClient extends TypedEventEmitter {
74
89
  ws = null;
75
90
  options;
@@ -79,6 +94,7 @@ export class ActaWsClient extends TypedEventEmitter {
79
94
  pendingResumeSessionId = null;
80
95
  connectionState = "disconnected";
81
96
  sessionId = null;
97
+ lastAuthSessionId = null;
82
98
  helloSent = false;
83
99
  welcomeReceived = false;
84
100
  pendingMessages = [];
@@ -89,6 +105,7 @@ export class ActaWsClient extends TypedEventEmitter {
89
105
  subscribedChannels = new Set(["rfqs"]);
90
106
  subscribedMarkets = new Set();
91
107
  hasMarketScope = false;
108
+ marketDescriptorsByMarket = new Map();
92
109
  state = {
93
110
  stats: null,
94
111
  activeRfqs: new Map(),
@@ -121,6 +138,7 @@ export class ActaWsClient extends TypedEventEmitter {
121
138
  this.authProvider = null;
122
139
  this.authRequested = false;
123
140
  this.pendingResumeSessionId = null;
141
+ this.lastAuthSessionId = null;
124
142
  this.shouldReconnect = this.options.autoReconnect;
125
143
  this.doConnect();
126
144
  }
@@ -176,6 +194,13 @@ export class ActaWsClient extends TypedEventEmitter {
176
194
  }
177
195
  async createRfq(request) {
178
196
  this.ensureAuthenticated();
197
+ assertWsU64Safe(request.strike, "strike");
198
+ assertWsU64Safe(request.quantity, "quantity");
199
+ const marketDescriptor = this.marketDescriptorsByMarket.get(request.market);
200
+ if (!marketDescriptor) {
201
+ throw new Error(`Missing market descriptor for market=${request.market}; call getMarketDescriptors() before createRfq().`);
202
+ }
203
+ validateQuantityBySizeRule(request.quantity, marketDescriptor.size_rule);
179
204
  this.send({
180
205
  type: "RfqRequest",
181
206
  data: {
@@ -220,47 +245,89 @@ export class ActaWsClient extends TypedEventEmitter {
220
245
  }
221
246
  getPositions() {
222
247
  this.ensureAuthenticated();
223
- this.send({ type: "GetPositions" });
248
+ const requestId = this.nextRequestId();
249
+ this.send({
250
+ type: "GetPositions",
251
+ data: { request_id: requestId },
252
+ });
253
+ return requestId;
224
254
  }
225
255
  getMarkets() {
226
- this.send({ type: "GetMarkets" });
256
+ const requestId = this.nextRequestId();
257
+ this.send({
258
+ type: "GetMarkets",
259
+ data: { request_id: requestId },
260
+ });
261
+ return requestId;
227
262
  }
228
263
  getMarketDescriptors(args) {
264
+ const requestId = this.nextRequestId();
229
265
  const data = {
266
+ request_id: requestId,
230
267
  active_only: args?.active_only ?? true,
231
268
  };
232
269
  this.send({ type: "GetMarketDescriptors", data });
270
+ return requestId;
233
271
  }
234
272
  getExpiries(args) {
273
+ const requestId = this.nextRequestId();
235
274
  this.send({
236
275
  type: "GetExpiries",
237
276
  data: {
277
+ request_id: requestId,
238
278
  underlying_mint: args?.underlying_mint,
239
279
  quote_mint: args?.quote_mint,
240
280
  is_put: args?.is_put ?? null,
241
281
  },
242
282
  });
283
+ return requestId;
243
284
  }
244
285
  getTokens(args) {
286
+ const requestId = this.nextRequestId();
245
287
  const data = {
288
+ request_id: requestId,
246
289
  active_only: args?.active_only ?? true,
247
290
  };
248
291
  this.send({ type: "GetTokens", data });
292
+ return requestId;
249
293
  }
250
294
  getMyActiveRfqs() {
251
295
  this.ensureAuthenticated();
252
- this.send({ type: "GetMyActiveRfqs" });
296
+ const requestId = this.nextRequestId();
297
+ this.send({
298
+ type: "GetMyActiveRfqs",
299
+ data: { request_id: requestId },
300
+ });
301
+ return requestId;
253
302
  }
254
303
  getActiveRfqs() {
255
- this.send({ type: "GetActiveRfqs" });
304
+ const requestId = this.nextRequestId();
305
+ this.send({
306
+ type: "GetActiveRfqs",
307
+ data: { request_id: requestId },
308
+ });
309
+ return requestId;
310
+ }
311
+ logout() {
312
+ this.send({ type: "Logout" });
256
313
  }
257
314
  getOrderStatus(orderIdHex) {
258
315
  this.ensureAuthenticated();
259
- this.send({ type: "GetOrderStatus", data: { order_id: orderIdHex } });
316
+ const requestId = this.nextRequestId();
317
+ this.send({
318
+ type: "GetOrderStatus",
319
+ data: { request_id: requestId, order_id: orderIdHex },
320
+ });
321
+ return requestId;
260
322
  }
261
323
  cancelRfq(rfqId) {
262
324
  this.ensureAuthenticated();
263
- this.send({ type: "CancelRfq", data: { rfq_id: rfqId } });
325
+ const requestId = this.nextRequestId();
326
+ this.send({
327
+ type: "CancelRfq",
328
+ data: { rfq_id: rfqId, request_id: requestId },
329
+ });
330
+ return requestId;
264
331
  }
265
332
  submitQuote(quote) {
266
333
  this.ensureAuthenticated();
@@ -290,7 +357,12 @@ export class ActaWsClient extends TypedEventEmitter {
290
357
  }
291
358
  cancelQuote(rfqId) {
292
359
  this.ensureAuthenticated();
293
- this.send({ type: "CancelQuote", data: { rfq_id: rfqId } });
360
+ const requestId = this.nextRequestId();
361
+ this.send({
362
+ type: "CancelQuote",
363
+ data: { rfq_id: rfqId, request_id: requestId },
364
+ });
365
+ return requestId;
294
366
  }
295
367
  subscribe(channels, markets) {
296
368
  this.ensureAuthenticated();
@@ -415,6 +487,14 @@ export class ActaWsClient extends TypedEventEmitter {
415
487
  case "AuthError":
416
488
  this.handleAuthError(message.data.reason, message.data.message);
417
489
  break;
490
+ case "LogoutSuccess":
491
+ this.sessionId = null;
492
+ this.pendingResumeSessionId = null;
493
+ this.lastAuthSessionId = null;
494
+ this.startAuthSent = false;
495
+ this.setConnectionState("connecting");
496
+ this.emit("logoutSuccess");
497
+ break;
418
498
  case "Snapshot":
419
499
  this.handleSnapshot(message.data);
420
500
  break;
@@ -425,7 +505,13 @@ export class ActaWsClient extends TypedEventEmitter {
425
505
  this.handleMarkets(message.data.markets ?? []);
426
506
  break;
427
507
  case "MarketDescriptors":
428
- this.emit("marketDescriptors", message.data.markets ?? []);
508
+ {
509
+ const marketDescriptors = message.data.markets ?? [];
510
+ for (const marketDescriptor of marketDescriptors) {
511
+ this.marketDescriptorsByMarket.set(marketDescriptor.market.market_pda, marketDescriptor);
512
+ }
513
+ this.emit("marketDescriptors", marketDescriptors);
514
+ }
429
515
  break;
430
516
  case "Expiries":
431
517
  this.emit("expiries", message.data.expiries_ts ?? []);
@@ -584,10 +670,12 @@ export class ActaWsClient extends TypedEventEmitter {
584
670
  }
585
671
  /** Taker-only: request current indicative prices for a market + position_type. */
586
672
  getIndicativePrices(req) {
673
+ const requestId = this.nextRequestId();
587
674
  this.send({
588
675
  type: "GetIndicativePrices",
589
- data: req,
676
+ data: { ...req, request_id: requestId },
590
677
  });
678
+ return requestId;
591
679
  }
592
680
  /** Maker-only: respond to an indicative request (unsigned, non-binding). */
593
681
  sendIndicativePricesResponse(resp) {
@@ -616,8 +704,9 @@ export class ActaWsClient extends TypedEventEmitter {
616
704
  }
617
705
  }
618
706
  async beginAuthHandshake() {
619
- if (this.pendingResumeSessionId) {
620
- this.sendResumeAuth(this.pendingResumeSessionId);
707
+ const resumeSessionId = this.pendingResumeSessionId ?? this.lastAuthSessionId;
708
+ if (resumeSessionId) {
709
+ this.sendResumeAuth(resumeSessionId);
621
710
  return;
622
711
  }
623
712
  await this.sendStartAuth();
@@ -654,6 +743,9 @@ export class ActaWsClient extends TypedEventEmitter {
654
743
  handleAuthSuccess(sessionId, expiresAt) {
655
744
  this.sessionId = sessionId;
656
745
  this.pendingResumeSessionId = null;
746
+ if (expiresAt !== null) {
747
+ this.lastAuthSessionId = sessionId;
748
+ }
657
749
  this.setConnectionState("authenticated");
658
750
  this.emit("authenticated", sessionId, expiresAt);
659
751
  if (this.subscribedChannels.size > 0) {
@@ -665,12 +757,13 @@ export class ActaWsClient extends TypedEventEmitter {
665
757
  }
666
758
  }
667
759
  handleAuthError(reason, message) {
668
- this.emit("authError", reason);
760
+ this.emit("authError", reason, message);
669
761
  if (reason === "session_expired" &&
670
762
  this.authRequested &&
671
763
  this.authProvider &&
672
- this.pendingResumeSessionId) {
764
+ (this.pendingResumeSessionId || this.lastAuthSessionId)) {
673
765
  this.pendingResumeSessionId = null;
766
+ this.lastAuthSessionId = null;
674
767
  this.startAuthSent = false;
675
768
  void this.sendStartAuth().catch((err) => {
676
769
  this.emit("error", err);
@@ -786,6 +879,9 @@ export class ActaWsClient extends TypedEventEmitter {
786
879
  this.log("Cannot send, WebSocket not open");
787
880
  }
788
881
  }
882
+ nextRequestId() {
883
+ return generateRequestId();
884
+ }
789
885
  ensureAuthenticated() {
790
886
  if (this.connectionState !== "authenticated") {
791
887
  throw new Error("Client is not authenticated");
@@ -880,7 +976,6 @@ export class ActaWsClient extends TypedEventEmitter {
880
976
  this.helloSent = false;
881
977
  this.welcomeReceived = false;
882
978
  this.startAuthSent = false;
883
- this.pendingResumeSessionId = null;
884
979
  this.pendingMessages = [];
885
980
  this.setConnectionState("disconnected");
886
981
  }