@cstar.help/js 0.12.1 → 0.14.0

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.
@@ -289,37 +289,45 @@ var PresenceManager = class {
289
289
  };
290
290
 
291
291
  // src/chat/errors.ts
292
+ function normalizeChatArgs(arg, fallbackCode) {
293
+ if (typeof arg === "string") {
294
+ return { message: arg, code: fallbackCode, statusCode: 0, requestId: null };
295
+ }
296
+ return { ...arg, code: arg.code ?? fallbackCode };
297
+ }
292
298
  var ChatClientError = class extends Error {
293
- constructor(message, opts) {
294
- super(message);
299
+ constructor(arg) {
300
+ const opts = normalizeChatArgs(arg, "UNKNOWN");
301
+ super(opts.message);
295
302
  this.name = "ChatClientError";
296
303
  this.code = opts.code ?? "UNKNOWN";
297
- this.statusCode = opts.statusCode;
304
+ this.statusCode = opts.statusCode ?? 0;
298
305
  this.requestId = opts.requestId ?? null;
299
306
  }
300
307
  };
301
308
  var ChatClientAuthError = class extends ChatClientError {
302
- constructor(opts) {
303
- super(opts.message, opts);
309
+ constructor(arg) {
310
+ super(normalizeChatArgs(arg, "AUTH_ERROR"));
304
311
  this.name = "ChatClientAuthError";
305
312
  }
306
313
  };
307
314
  var ChatClientRateLimitError = class extends ChatClientError {
308
- constructor(opts) {
309
- super(opts.message, opts);
315
+ constructor(arg) {
316
+ const opts = typeof arg === "string" ? { message: arg, code: "RATE_LIMIT", statusCode: 429, requestId: null, retryAfter: null } : { ...arg, code: arg.code ?? "RATE_LIMIT" };
317
+ super(opts);
310
318
  this.name = "ChatClientRateLimitError";
311
319
  this.retryAfter = opts.retryAfter ?? null;
312
320
  }
313
321
  };
314
322
  var ChatClientServerError = class extends ChatClientError {
315
- constructor(opts) {
316
- super(opts.message, opts);
323
+ constructor(arg) {
324
+ super(normalizeChatArgs(arg, "SERVER_ERROR"));
317
325
  this.name = "ChatClientServerError";
318
326
  }
319
327
  };
320
328
  var ChatClientNetworkError = class extends ChatClientError {
321
329
  constructor(opts) {
322
- super(opts.message, { code: "NETWORK_ERROR", statusCode: 0, requestId: null });
330
+ super({ message: opts.message, code: "NETWORK_ERROR", statusCode: 0, requestId: null });
323
331
  this.name = "ChatClientNetworkError";
324
332
  this.originalError = opts.originalError;
325
333
  }
@@ -335,6 +343,7 @@ var ChatClient = class {
335
343
  this.customerId = null;
336
344
  this.authMode = null;
337
345
  this.pollingTimers = /* @__PURE__ */ new Map();
346
+ this.stateListeners = /* @__PURE__ */ new Set();
338
347
  this.realtimeManager = null;
339
348
  this.presenceManager = null;
340
349
  this.realtimeReady = false;
@@ -374,6 +383,8 @@ var ChatClient = class {
374
383
  this.sessionToken = response.data.sessionToken;
375
384
  this.teamId = response.data.teamId;
376
385
  this.customerId = response.data.customer.id;
386
+ this.authMode = "hmac";
387
+ this.notifyStateChanged();
377
388
  if (this.realtimeEnabled) {
378
389
  this.realtimeInitPromise = this.initRealtime(
379
390
  response.data.customer.id,
@@ -433,11 +444,35 @@ var ChatClient = class {
433
444
  this.teamId = params.teamId;
434
445
  }
435
446
  this.authMode = "session";
447
+ this.notifyStateChanged();
436
448
  if (this.realtimeEnabled) {
437
449
  const customerName = params.customerName ?? "";
438
450
  this.realtimeInitPromise = this.initRealtimeFromSession(customerName);
439
451
  }
440
452
  }
453
+ /**
454
+ * Subscribe to client state changes (auth mode flips, identify, disconnect).
455
+ * Fires after the mutation, so listeners reading `getAuthMode()` /
456
+ * `getCustomerId()` see the new value.
457
+ *
458
+ * Returns an unsubscribe function. Closes F-041 — used by `useChat` (React)
459
+ * and `ChatState` (Svelte) to re-render when consumers mutate the client
460
+ * outside of the framework wrappers.
461
+ */
462
+ subscribe(listener) {
463
+ this.stateListeners.add(listener);
464
+ return () => {
465
+ this.stateListeners.delete(listener);
466
+ };
467
+ }
468
+ notifyStateChanged() {
469
+ for (const listener of this.stateListeners) {
470
+ try {
471
+ listener();
472
+ } catch {
473
+ }
474
+ }
475
+ }
441
476
  /**
442
477
  * Returns the auth mode the client is currently using, or `null` before
443
478
  * any authenticated call has resolved one. Useful for branching UI
@@ -500,6 +535,7 @@ var ChatClient = class {
500
535
  this.teamId = null;
501
536
  this.customerId = null;
502
537
  this.authMode = null;
538
+ this.notifyStateChanged();
503
539
  }
504
540
  /**
505
541
  * @internal Make an authenticated request to the widget API.
@@ -537,7 +573,9 @@ var ChatClient = class {
537
573
  const responseHeaders = response.headers && typeof response.headers.get === "function" ? response.headers : null;
538
574
  const headerMode = responseHeaders?.get("X-Cstar-Auth-Mode") ?? null;
539
575
  if (headerMode === "hmac" || headerMode === "session") {
576
+ const previous = this.authMode;
540
577
  this.authMode = headerMode;
578
+ if (previous !== headerMode) this.notifyStateChanged();
541
579
  }
542
580
  const requestId = responseHeaders?.get("X-Request-Id") ?? null;
543
581
  if (!response.ok) {
@@ -571,7 +609,8 @@ var ChatClient = class {
571
609
  requestId
572
610
  });
573
611
  }
574
- throw new ChatClientError(message, {
612
+ throw new ChatClientError({
613
+ message,
575
614
  code,
576
615
  statusCode: response.status,
577
616
  requestId
@@ -709,7 +748,7 @@ var TicketsApi = class {
709
748
  /** List tickets for the identified customer. */
710
749
  async list(params) {
711
750
  const teamId = this.chat.getTeamId();
712
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
751
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
713
752
  const response = await this.chat.fetch("/api/widget/conversations", {
714
753
  method: "GET",
715
754
  query: {
@@ -718,16 +757,22 @@ var TicketsApi = class {
718
757
  cursor: params?.cursor
719
758
  }
720
759
  });
760
+ const data = response.data.tickets ?? response.data.conversations ?? [];
761
+ const cursor = response.data.cursor;
762
+ const hasMore = response.data.hasMore;
721
763
  return {
722
- tickets: response.data.tickets ?? response.data.conversations ?? [],
723
- cursor: response.data.cursor,
724
- hasMore: response.data.hasMore
764
+ data,
765
+ pagination: { cursor, hasMore },
766
+ hasMore,
767
+ // Deprecated aliases — same references, no extra allocation.
768
+ tickets: data,
769
+ cursor
725
770
  };
726
771
  }
727
772
  /** Create a new ticket. */
728
773
  async create(params) {
729
774
  const teamId = this.chat.getTeamId();
730
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
775
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
731
776
  const response = await this.chat.fetch("/api/widget/tickets", {
732
777
  method: "POST",
733
778
  body: {
@@ -736,15 +781,25 @@ var TicketsApi = class {
736
781
  message: params.message
737
782
  }
738
783
  });
784
+ const messages = (response.data.messages ?? []).map((m) => ({
785
+ id: m.id,
786
+ content: m.content,
787
+ sender: m.sender,
788
+ sender_name: m.sender_name,
789
+ created_at: m.created_at,
790
+ read_at: m.read_at,
791
+ attachments: m.attachments ?? []
792
+ }));
739
793
  return {
740
794
  id: response.data.id,
741
- ticketNumber: "",
795
+ ticketNumber: null,
742
796
  subject: response.data.subject,
743
797
  status: response.data.status,
744
- messageCount: params.message ? 1 : 0,
798
+ messageCount: messages.length,
745
799
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
746
800
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
747
- closedAt: null
801
+ closedAt: null,
802
+ messages
748
803
  };
749
804
  }
750
805
  };
@@ -755,7 +810,7 @@ var ChatCommunityApi = class {
755
810
  /** Create a new community post as the identified customer. */
756
811
  async createPost(params) {
757
812
  const teamId = this.chat.getTeamId();
758
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
813
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
759
814
  const response = await this.chat.fetch("/api/widget/community", {
760
815
  method: "POST",
761
816
  body: {
@@ -772,7 +827,7 @@ var ChatCommunityApi = class {
772
827
  /** Update a post. Only the original author can edit. */
773
828
  async updatePost(postId, params) {
774
829
  const teamId = this.chat.getTeamId();
775
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
830
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
776
831
  const response = await this.chat.fetch("/api/widget/community", {
777
832
  method: "POST",
778
833
  body: {
@@ -789,7 +844,7 @@ var ChatCommunityApi = class {
789
844
  /** Delete a post (soft delete). Only the original author can delete. */
790
845
  async deletePost(postId) {
791
846
  const teamId = this.chat.getTeamId();
792
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
847
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
793
848
  await this.chat.fetch("/api/widget/community", {
794
849
  method: "POST",
795
850
  body: { teamId, action: "deletePost", postId }
@@ -798,7 +853,7 @@ var ChatCommunityApi = class {
798
853
  /** Update a comment. Only the original author can edit. */
799
854
  async updateComment(postId, commentId, params) {
800
855
  const teamId = this.chat.getTeamId();
801
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
856
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
802
857
  const response = await this.chat.fetch("/api/widget/community", {
803
858
  method: "POST",
804
859
  body: {
@@ -814,7 +869,7 @@ var ChatCommunityApi = class {
814
869
  /** Delete a comment (soft delete). Only the original author can delete. */
815
870
  async deleteComment(postId, commentId) {
816
871
  const teamId = this.chat.getTeamId();
817
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
872
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
818
873
  await this.chat.fetch("/api/widget/community", {
819
874
  method: "POST",
820
875
  body: { teamId, action: "deleteComment", postId, commentId }
@@ -823,7 +878,7 @@ var ChatCommunityApi = class {
823
878
  /** Toggle vote on a post. Returns new vote state and count. */
824
879
  async vote(postId) {
825
880
  const teamId = this.chat.getTeamId();
826
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
881
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
827
882
  const response = await this.chat.fetch("/api/widget/community", {
828
883
  method: "POST",
829
884
  body: { teamId, action: "vote", postId }
@@ -833,7 +888,7 @@ var ChatCommunityApi = class {
833
888
  /** Add a comment to a post. Supports 1-level nesting via parentCommentId. */
834
889
  async comment(postId, params) {
835
890
  const teamId = this.chat.getTeamId();
836
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
891
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
837
892
  const response = await this.chat.fetch("/api/widget/community", {
838
893
  method: "POST",
839
894
  body: {
@@ -849,7 +904,7 @@ var ChatCommunityApi = class {
849
904
  /** Toggle follow on a post. */
850
905
  async follow(postId) {
851
906
  const teamId = this.chat.getTeamId();
852
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
907
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
853
908
  const response = await this.chat.fetch("/api/widget/community", {
854
909
  method: "POST",
855
910
  body: { teamId, action: "follow", postId }
@@ -859,7 +914,7 @@ var ChatCommunityApi = class {
859
914
  /** Check if the current user has voted on a post. */
860
915
  async getVoteStatus(postId) {
861
916
  const teamId = this.chat.getTeamId();
862
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
917
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
863
918
  const response = await this.chat.fetch("/api/widget/community", {
864
919
  method: "GET",
865
920
  query: { teamId, postId }
@@ -870,7 +925,7 @@ var ChatCommunityApi = class {
870
925
  async bulkVoteStatus(postIds) {
871
926
  if (postIds.length === 0) return {};
872
927
  const teamId = this.chat.getTeamId();
873
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
928
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
874
929
  const response = await this.chat.fetch("/api/widget/community", {
875
930
  method: "GET",
876
931
  query: { teamId, postIds: postIds.join(",") }
@@ -88,19 +88,53 @@ interface RealtimeTokenResponse {
88
88
  /** A ticket — a customer's support thread. */
89
89
  interface Ticket {
90
90
  id: string;
91
- ticketNumber: string;
91
+ /**
92
+ * Server-issued display ID (e.g. `CST-1024`). `null` immediately after
93
+ * `tickets.create` (the prefix is assigned asynchronously); populated on
94
+ * subsequent `tickets.list` / single fetches.
95
+ */
96
+ ticketNumber: string | null;
92
97
  subject: string;
93
98
  status: string;
94
99
  messageCount: number;
95
100
  createdAt: string;
96
101
  updatedAt: string;
97
102
  closedAt: string | null;
103
+ /**
104
+ * Set on `tickets.create` responses — contains the first message the
105
+ * widget sent, so callers don't need a round-trip `messages.list(...)`
106
+ * to echo it back. `undefined` on list/get responses.
107
+ */
108
+ messages?: Message[];
98
109
  }
99
- /** Paginated ticket list response. */
110
+ /**
111
+ * Paginated ticket list response.
112
+ *
113
+ * As of `@cstar.help/js@0.14.0` the canonical fields match the admin SDK and
114
+ * library SDK envelopes (`{ data, pagination, hasMore }`). The legacy
115
+ * `tickets` and `cursor` fields are kept as aliases — they reference the
116
+ * same data — and will be removed in the next major. Closes F-037.
117
+ */
100
118
  interface TicketListResponse {
119
+ /** Tickets on this page (canonical). */
120
+ data: Ticket[];
121
+ /** Pagination cursor + a flag for whether more pages exist (canonical). */
122
+ pagination: {
123
+ cursor: string | null;
124
+ hasMore: boolean;
125
+ };
126
+ /** Convenience copy of `pagination.hasMore`. */
127
+ hasMore: boolean;
128
+ /**
129
+ * @deprecated since 0.14.0 — use `data`. Same array reference for now.
130
+ * Will be removed in the next major.
131
+ */
101
132
  tickets: Ticket[];
133
+ /**
134
+ * @deprecated since 0.14.0 — use `pagination.cursor`. Same value.
135
+ * Will be removed in the next major.
136
+ */
102
137
  cursor: string | null;
103
- hasMore: boolean;
104
138
  }
105
139
  /** A message on a ticket. */
106
140
  interface Message {
@@ -244,46 +278,39 @@ interface CommunityFollowResult {
244
278
  * }
245
279
  * }
246
280
  * ```
281
+ *
282
+ * Constructors accept either a full opts object (used by the SDK's HTTP
283
+ * paths) or a bare message string — handy for SDK precondition checks
284
+ * (`throw new ChatClientAuthError('Not identified...')`) and tests.
285
+ * Closes F-032's spirit for the chat-client classes.
247
286
  */
287
+ interface ChatErrorOpts {
288
+ message: string;
289
+ code?: string;
290
+ statusCode?: number;
291
+ requestId?: string | null;
292
+ }
248
293
  /** Base class. Thrown by `ChatClient.fetch()` for any non-2xx response. */
249
294
  declare class ChatClientError extends Error {
250
295
  readonly code: string;
251
296
  readonly statusCode: number;
252
297
  readonly requestId: string | null;
253
- constructor(message: string, opts: {
254
- code?: string;
255
- statusCode: number;
256
- requestId?: string | null;
257
- });
298
+ constructor(arg: ChatErrorOpts | string);
258
299
  }
259
300
  /** 401 / 403 — auth failed (bad/expired token, wrong team, etc.). */
260
301
  declare class ChatClientAuthError extends ChatClientError {
261
- constructor(opts: {
262
- message: string;
263
- code?: string;
264
- statusCode: number;
265
- requestId?: string | null;
266
- });
302
+ constructor(arg: ChatErrorOpts | string);
267
303
  }
268
304
  /** 429 — too many requests. Inspect `retryAfter` (seconds) before retrying. */
269
305
  declare class ChatClientRateLimitError extends ChatClientError {
270
306
  readonly retryAfter: number | null;
271
- constructor(opts: {
272
- message: string;
273
- code?: string;
274
- statusCode: number;
275
- requestId?: string | null;
307
+ constructor(arg: (ChatErrorOpts & {
276
308
  retryAfter?: number | null;
277
- });
309
+ }) | string);
278
310
  }
279
311
  /** 5xx — the cStar server hit an internal error. Usually transient. */
280
312
  declare class ChatClientServerError extends ChatClientError {
281
- constructor(opts: {
282
- message: string;
283
- code?: string;
284
- statusCode: number;
285
- requestId?: string | null;
286
- });
313
+ constructor(arg: ChatErrorOpts | string);
287
314
  }
288
315
  /**
289
316
  * `fetch()` itself failed before a response was received — DNS, TLS,
@@ -341,6 +368,12 @@ declare class ChatClientNetworkError extends ChatClientError {
341
368
  * `ChatClientServerError`, `ChatClientNetworkError`. Each carries
342
369
  * `requestId` for correlating with server-side logs.
343
370
  */
371
+ /**
372
+ * Listener for client-state changes (auth-mode flips, identity assignment,
373
+ * disconnects). Fires once after the underlying state mutates so a UI layer
374
+ * can re-read `getAuthMode()` / `getCustomerId()` and re-render. Closes F-041.
375
+ */
376
+ type ChatStateListener = () => void;
344
377
  declare class ChatClient {
345
378
  private readonly baseUrl;
346
379
  private readonly teamSlug;
@@ -351,6 +384,7 @@ declare class ChatClient {
351
384
  private customerId;
352
385
  private authMode;
353
386
  private pollingTimers;
387
+ private stateListeners;
354
388
  private realtimeManager;
355
389
  private presenceManager;
356
390
  private realtimeReady;
@@ -393,6 +427,17 @@ declare class ChatClient {
393
427
  * every request — there is no client-side validation here.
394
428
  */
395
429
  loginWithSession(params: LoginWithSessionParams): Promise<void>;
430
+ /**
431
+ * Subscribe to client state changes (auth mode flips, identify, disconnect).
432
+ * Fires after the mutation, so listeners reading `getAuthMode()` /
433
+ * `getCustomerId()` see the new value.
434
+ *
435
+ * Returns an unsubscribe function. Closes F-041 — used by `useChat` (React)
436
+ * and `ChatState` (Svelte) to re-render when consumers mutate the client
437
+ * outside of the framework wrappers.
438
+ */
439
+ subscribe(listener: ChatStateListener): () => void;
440
+ private notifyStateChanged;
396
441
  /**
397
442
  * Returns the auth mode the client is currently using, or `null` before
398
443
  * any authenticated call has resolved one. Useful for branching UI
@@ -505,4 +550,4 @@ declare class MessagesApi {
505
550
  send(ticketId: string, content: string): Promise<Message>;
506
551
  }
507
552
 
508
- export { type ChatAuthMode, ChatClient, ChatClientAuthError, ChatClientError, ChatClientNetworkError, ChatClientRateLimitError, ChatClientServerError, type ChatConfig, type IdentifyParams, type IdentifyResult, type LoginWithCredentialsParams, type LoginWithCredentialsResult, type LoginWithSessionParams, type Message, type MessageCallback, type RealtimeTokenResponse, type Ticket, type TicketCreateParams, type TicketListParams, type TicketListResponse, type TypingCallback, type TypingPayload };
553
+ export { type ChatAuthMode, ChatClient, ChatClientAuthError, ChatClientError, ChatClientNetworkError, ChatClientRateLimitError, ChatClientServerError, type ChatConfig, type ChatStateListener, type IdentifyParams, type IdentifyResult, type LoginWithCredentialsParams, type LoginWithCredentialsResult, type LoginWithSessionParams, type Message, type MessageCallback, type RealtimeTokenResponse, type Ticket, type TicketCreateParams, type TicketListParams, type TicketListResponse, type TypingCallback, type TypingPayload };
@@ -88,19 +88,53 @@ interface RealtimeTokenResponse {
88
88
  /** A ticket — a customer's support thread. */
89
89
  interface Ticket {
90
90
  id: string;
91
- ticketNumber: string;
91
+ /**
92
+ * Server-issued display ID (e.g. `CST-1024`). `null` immediately after
93
+ * `tickets.create` (the prefix is assigned asynchronously); populated on
94
+ * subsequent `tickets.list` / single fetches.
95
+ */
96
+ ticketNumber: string | null;
92
97
  subject: string;
93
98
  status: string;
94
99
  messageCount: number;
95
100
  createdAt: string;
96
101
  updatedAt: string;
97
102
  closedAt: string | null;
103
+ /**
104
+ * Set on `tickets.create` responses — contains the first message the
105
+ * widget sent, so callers don't need a round-trip `messages.list(...)`
106
+ * to echo it back. `undefined` on list/get responses.
107
+ */
108
+ messages?: Message[];
98
109
  }
99
- /** Paginated ticket list response. */
110
+ /**
111
+ * Paginated ticket list response.
112
+ *
113
+ * As of `@cstar.help/js@0.14.0` the canonical fields match the admin SDK and
114
+ * library SDK envelopes (`{ data, pagination, hasMore }`). The legacy
115
+ * `tickets` and `cursor` fields are kept as aliases — they reference the
116
+ * same data — and will be removed in the next major. Closes F-037.
117
+ */
100
118
  interface TicketListResponse {
119
+ /** Tickets on this page (canonical). */
120
+ data: Ticket[];
121
+ /** Pagination cursor + a flag for whether more pages exist (canonical). */
122
+ pagination: {
123
+ cursor: string | null;
124
+ hasMore: boolean;
125
+ };
126
+ /** Convenience copy of `pagination.hasMore`. */
127
+ hasMore: boolean;
128
+ /**
129
+ * @deprecated since 0.14.0 — use `data`. Same array reference for now.
130
+ * Will be removed in the next major.
131
+ */
101
132
  tickets: Ticket[];
133
+ /**
134
+ * @deprecated since 0.14.0 — use `pagination.cursor`. Same value.
135
+ * Will be removed in the next major.
136
+ */
102
137
  cursor: string | null;
103
- hasMore: boolean;
104
138
  }
105
139
  /** A message on a ticket. */
106
140
  interface Message {
@@ -244,46 +278,39 @@ interface CommunityFollowResult {
244
278
  * }
245
279
  * }
246
280
  * ```
281
+ *
282
+ * Constructors accept either a full opts object (used by the SDK's HTTP
283
+ * paths) or a bare message string — handy for SDK precondition checks
284
+ * (`throw new ChatClientAuthError('Not identified...')`) and tests.
285
+ * Closes F-032's spirit for the chat-client classes.
247
286
  */
287
+ interface ChatErrorOpts {
288
+ message: string;
289
+ code?: string;
290
+ statusCode?: number;
291
+ requestId?: string | null;
292
+ }
248
293
  /** Base class. Thrown by `ChatClient.fetch()` for any non-2xx response. */
249
294
  declare class ChatClientError extends Error {
250
295
  readonly code: string;
251
296
  readonly statusCode: number;
252
297
  readonly requestId: string | null;
253
- constructor(message: string, opts: {
254
- code?: string;
255
- statusCode: number;
256
- requestId?: string | null;
257
- });
298
+ constructor(arg: ChatErrorOpts | string);
258
299
  }
259
300
  /** 401 / 403 — auth failed (bad/expired token, wrong team, etc.). */
260
301
  declare class ChatClientAuthError extends ChatClientError {
261
- constructor(opts: {
262
- message: string;
263
- code?: string;
264
- statusCode: number;
265
- requestId?: string | null;
266
- });
302
+ constructor(arg: ChatErrorOpts | string);
267
303
  }
268
304
  /** 429 — too many requests. Inspect `retryAfter` (seconds) before retrying. */
269
305
  declare class ChatClientRateLimitError extends ChatClientError {
270
306
  readonly retryAfter: number | null;
271
- constructor(opts: {
272
- message: string;
273
- code?: string;
274
- statusCode: number;
275
- requestId?: string | null;
307
+ constructor(arg: (ChatErrorOpts & {
276
308
  retryAfter?: number | null;
277
- });
309
+ }) | string);
278
310
  }
279
311
  /** 5xx — the cStar server hit an internal error. Usually transient. */
280
312
  declare class ChatClientServerError extends ChatClientError {
281
- constructor(opts: {
282
- message: string;
283
- code?: string;
284
- statusCode: number;
285
- requestId?: string | null;
286
- });
313
+ constructor(arg: ChatErrorOpts | string);
287
314
  }
288
315
  /**
289
316
  * `fetch()` itself failed before a response was received — DNS, TLS,
@@ -341,6 +368,12 @@ declare class ChatClientNetworkError extends ChatClientError {
341
368
  * `ChatClientServerError`, `ChatClientNetworkError`. Each carries
342
369
  * `requestId` for correlating with server-side logs.
343
370
  */
371
+ /**
372
+ * Listener for client-state changes (auth-mode flips, identity assignment,
373
+ * disconnects). Fires once after the underlying state mutates so a UI layer
374
+ * can re-read `getAuthMode()` / `getCustomerId()` and re-render. Closes F-041.
375
+ */
376
+ type ChatStateListener = () => void;
344
377
  declare class ChatClient {
345
378
  private readonly baseUrl;
346
379
  private readonly teamSlug;
@@ -351,6 +384,7 @@ declare class ChatClient {
351
384
  private customerId;
352
385
  private authMode;
353
386
  private pollingTimers;
387
+ private stateListeners;
354
388
  private realtimeManager;
355
389
  private presenceManager;
356
390
  private realtimeReady;
@@ -393,6 +427,17 @@ declare class ChatClient {
393
427
  * every request — there is no client-side validation here.
394
428
  */
395
429
  loginWithSession(params: LoginWithSessionParams): Promise<void>;
430
+ /**
431
+ * Subscribe to client state changes (auth mode flips, identify, disconnect).
432
+ * Fires after the mutation, so listeners reading `getAuthMode()` /
433
+ * `getCustomerId()` see the new value.
434
+ *
435
+ * Returns an unsubscribe function. Closes F-041 — used by `useChat` (React)
436
+ * and `ChatState` (Svelte) to re-render when consumers mutate the client
437
+ * outside of the framework wrappers.
438
+ */
439
+ subscribe(listener: ChatStateListener): () => void;
440
+ private notifyStateChanged;
396
441
  /**
397
442
  * Returns the auth mode the client is currently using, or `null` before
398
443
  * any authenticated call has resolved one. Useful for branching UI
@@ -505,4 +550,4 @@ declare class MessagesApi {
505
550
  send(ticketId: string, content: string): Promise<Message>;
506
551
  }
507
552
 
508
- export { type ChatAuthMode, ChatClient, ChatClientAuthError, ChatClientError, ChatClientNetworkError, ChatClientRateLimitError, ChatClientServerError, type ChatConfig, type IdentifyParams, type IdentifyResult, type LoginWithCredentialsParams, type LoginWithCredentialsResult, type LoginWithSessionParams, type Message, type MessageCallback, type RealtimeTokenResponse, type Ticket, type TicketCreateParams, type TicketListParams, type TicketListResponse, type TypingCallback, type TypingPayload };
553
+ export { type ChatAuthMode, ChatClient, ChatClientAuthError, ChatClientError, ChatClientNetworkError, ChatClientRateLimitError, ChatClientServerError, type ChatConfig, type ChatStateListener, type IdentifyParams, type IdentifyResult, type LoginWithCredentialsParams, type LoginWithCredentialsResult, type LoginWithSessionParams, type Message, type MessageCallback, type RealtimeTokenResponse, type Ticket, type TicketCreateParams, type TicketListParams, type TicketListResponse, type TypingCallback, type TypingPayload };