@cstar.help/js 0.13.0 → 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.
@@ -343,6 +343,7 @@ var ChatClient = class {
343
343
  this.customerId = null;
344
344
  this.authMode = null;
345
345
  this.pollingTimers = /* @__PURE__ */ new Map();
346
+ this.stateListeners = /* @__PURE__ */ new Set();
346
347
  this.realtimeManager = null;
347
348
  this.presenceManager = null;
348
349
  this.realtimeReady = false;
@@ -383,6 +384,7 @@ var ChatClient = class {
383
384
  this.teamId = response.data.teamId;
384
385
  this.customerId = response.data.customer.id;
385
386
  this.authMode = "hmac";
387
+ this.notifyStateChanged();
386
388
  if (this.realtimeEnabled) {
387
389
  this.realtimeInitPromise = this.initRealtime(
388
390
  response.data.customer.id,
@@ -442,11 +444,35 @@ var ChatClient = class {
442
444
  this.teamId = params.teamId;
443
445
  }
444
446
  this.authMode = "session";
447
+ this.notifyStateChanged();
445
448
  if (this.realtimeEnabled) {
446
449
  const customerName = params.customerName ?? "";
447
450
  this.realtimeInitPromise = this.initRealtimeFromSession(customerName);
448
451
  }
449
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
+ }
450
476
  /**
451
477
  * Returns the auth mode the client is currently using, or `null` before
452
478
  * any authenticated call has resolved one. Useful for branching UI
@@ -509,6 +535,7 @@ var ChatClient = class {
509
535
  this.teamId = null;
510
536
  this.customerId = null;
511
537
  this.authMode = null;
538
+ this.notifyStateChanged();
512
539
  }
513
540
  /**
514
541
  * @internal Make an authenticated request to the widget API.
@@ -546,7 +573,9 @@ var ChatClient = class {
546
573
  const responseHeaders = response.headers && typeof response.headers.get === "function" ? response.headers : null;
547
574
  const headerMode = responseHeaders?.get("X-Cstar-Auth-Mode") ?? null;
548
575
  if (headerMode === "hmac" || headerMode === "session") {
576
+ const previous = this.authMode;
549
577
  this.authMode = headerMode;
578
+ if (previous !== headerMode) this.notifyStateChanged();
550
579
  }
551
580
  const requestId = responseHeaders?.get("X-Request-Id") ?? null;
552
581
  if (!response.ok) {
@@ -728,10 +757,16 @@ var TicketsApi = class {
728
757
  cursor: params?.cursor
729
758
  }
730
759
  });
760
+ const data = response.data.tickets ?? response.data.conversations ?? [];
761
+ const cursor = response.data.cursor;
762
+ const hasMore = response.data.hasMore;
731
763
  return {
732
- tickets: response.data.tickets ?? response.data.conversations ?? [],
733
- cursor: response.data.cursor,
734
- 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
735
770
  };
736
771
  }
737
772
  /** Create a new ticket. */
@@ -107,11 +107,34 @@ interface Ticket {
107
107
  */
108
108
  messages?: Message[];
109
109
  }
110
- /** 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
+ */
111
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
+ */
112
132
  tickets: Ticket[];
133
+ /**
134
+ * @deprecated since 0.14.0 — use `pagination.cursor`. Same value.
135
+ * Will be removed in the next major.
136
+ */
113
137
  cursor: string | null;
114
- hasMore: boolean;
115
138
  }
116
139
  /** A message on a ticket. */
117
140
  interface Message {
@@ -345,6 +368,12 @@ declare class ChatClientNetworkError extends ChatClientError {
345
368
  * `ChatClientServerError`, `ChatClientNetworkError`. Each carries
346
369
  * `requestId` for correlating with server-side logs.
347
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;
348
377
  declare class ChatClient {
349
378
  private readonly baseUrl;
350
379
  private readonly teamSlug;
@@ -355,6 +384,7 @@ declare class ChatClient {
355
384
  private customerId;
356
385
  private authMode;
357
386
  private pollingTimers;
387
+ private stateListeners;
358
388
  private realtimeManager;
359
389
  private presenceManager;
360
390
  private realtimeReady;
@@ -397,6 +427,17 @@ declare class ChatClient {
397
427
  * every request — there is no client-side validation here.
398
428
  */
399
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;
400
441
  /**
401
442
  * Returns the auth mode the client is currently using, or `null` before
402
443
  * any authenticated call has resolved one. Useful for branching UI
@@ -509,4 +550,4 @@ declare class MessagesApi {
509
550
  send(ticketId: string, content: string): Promise<Message>;
510
551
  }
511
552
 
512
- 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 };
@@ -107,11 +107,34 @@ interface Ticket {
107
107
  */
108
108
  messages?: Message[];
109
109
  }
110
- /** 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
+ */
111
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
+ */
112
132
  tickets: Ticket[];
133
+ /**
134
+ * @deprecated since 0.14.0 — use `pagination.cursor`. Same value.
135
+ * Will be removed in the next major.
136
+ */
113
137
  cursor: string | null;
114
- hasMore: boolean;
115
138
  }
116
139
  /** A message on a ticket. */
117
140
  interface Message {
@@ -345,6 +368,12 @@ declare class ChatClientNetworkError extends ChatClientError {
345
368
  * `ChatClientServerError`, `ChatClientNetworkError`. Each carries
346
369
  * `requestId` for correlating with server-side logs.
347
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;
348
377
  declare class ChatClient {
349
378
  private readonly baseUrl;
350
379
  private readonly teamSlug;
@@ -355,6 +384,7 @@ declare class ChatClient {
355
384
  private customerId;
356
385
  private authMode;
357
386
  private pollingTimers;
387
+ private stateListeners;
358
388
  private realtimeManager;
359
389
  private presenceManager;
360
390
  private realtimeReady;
@@ -397,6 +427,17 @@ declare class ChatClient {
397
427
  * every request — there is no client-side validation here.
398
428
  */
399
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;
400
441
  /**
401
442
  * Returns the auth mode the client is currently using, or `null` before
402
443
  * any authenticated call has resolved one. Useful for branching UI
@@ -509,4 +550,4 @@ declare class MessagesApi {
509
550
  send(ticketId: string, content: string): Promise<Message>;
510
551
  }
511
552
 
512
- 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 };
@@ -302,6 +302,7 @@ var ChatClient = class {
302
302
  this.customerId = null;
303
303
  this.authMode = null;
304
304
  this.pollingTimers = /* @__PURE__ */ new Map();
305
+ this.stateListeners = /* @__PURE__ */ new Set();
305
306
  this.realtimeManager = null;
306
307
  this.presenceManager = null;
307
308
  this.realtimeReady = false;
@@ -342,6 +343,7 @@ var ChatClient = class {
342
343
  this.teamId = response.data.teamId;
343
344
  this.customerId = response.data.customer.id;
344
345
  this.authMode = "hmac";
346
+ this.notifyStateChanged();
345
347
  if (this.realtimeEnabled) {
346
348
  this.realtimeInitPromise = this.initRealtime(
347
349
  response.data.customer.id,
@@ -401,11 +403,35 @@ var ChatClient = class {
401
403
  this.teamId = params.teamId;
402
404
  }
403
405
  this.authMode = "session";
406
+ this.notifyStateChanged();
404
407
  if (this.realtimeEnabled) {
405
408
  const customerName = params.customerName ?? "";
406
409
  this.realtimeInitPromise = this.initRealtimeFromSession(customerName);
407
410
  }
408
411
  }
412
+ /**
413
+ * Subscribe to client state changes (auth mode flips, identify, disconnect).
414
+ * Fires after the mutation, so listeners reading `getAuthMode()` /
415
+ * `getCustomerId()` see the new value.
416
+ *
417
+ * Returns an unsubscribe function. Closes F-041 — used by `useChat` (React)
418
+ * and `ChatState` (Svelte) to re-render when consumers mutate the client
419
+ * outside of the framework wrappers.
420
+ */
421
+ subscribe(listener) {
422
+ this.stateListeners.add(listener);
423
+ return () => {
424
+ this.stateListeners.delete(listener);
425
+ };
426
+ }
427
+ notifyStateChanged() {
428
+ for (const listener of this.stateListeners) {
429
+ try {
430
+ listener();
431
+ } catch {
432
+ }
433
+ }
434
+ }
409
435
  /**
410
436
  * Returns the auth mode the client is currently using, or `null` before
411
437
  * any authenticated call has resolved one. Useful for branching UI
@@ -468,6 +494,7 @@ var ChatClient = class {
468
494
  this.teamId = null;
469
495
  this.customerId = null;
470
496
  this.authMode = null;
497
+ this.notifyStateChanged();
471
498
  }
472
499
  /**
473
500
  * @internal Make an authenticated request to the widget API.
@@ -505,7 +532,9 @@ var ChatClient = class {
505
532
  const responseHeaders = response.headers && typeof response.headers.get === "function" ? response.headers : null;
506
533
  const headerMode = responseHeaders?.get("X-Cstar-Auth-Mode") ?? null;
507
534
  if (headerMode === "hmac" || headerMode === "session") {
535
+ const previous = this.authMode;
508
536
  this.authMode = headerMode;
537
+ if (previous !== headerMode) this.notifyStateChanged();
509
538
  }
510
539
  const requestId = responseHeaders?.get("X-Request-Id") ?? null;
511
540
  if (!response.ok) {
@@ -687,10 +716,16 @@ var TicketsApi = class {
687
716
  cursor: params?.cursor
688
717
  }
689
718
  });
719
+ const data = response.data.tickets ?? response.data.conversations ?? [];
720
+ const cursor = response.data.cursor;
721
+ const hasMore = response.data.hasMore;
690
722
  return {
691
- tickets: response.data.tickets ?? response.data.conversations ?? [],
692
- cursor: response.data.cursor,
693
- hasMore: response.data.hasMore
723
+ data,
724
+ pagination: { cursor, hasMore },
725
+ hasMore,
726
+ // Deprecated aliases — same references, no extra allocation.
727
+ tickets: data,
728
+ cursor
694
729
  };
695
730
  }
696
731
  /** Create a new ticket. */
package/dist/index.cjs CHANGED
@@ -178,6 +178,18 @@ var CStarFeatureDisabledError = class extends CStarError {
178
178
  this.enableUrl = resolved.enable_url ?? "";
179
179
  }
180
180
  };
181
+ var CStarRealtimeUnavailableError = class extends CStarError {
182
+ constructor(message) {
183
+ const resolved = message ?? "EventSource is not available in this environment. Real-time subscriptions only work in the browser. Move the call to a client-only path (`useEffect` / `onMount`), or pass an `eventSource` polyfill via the realtime options. See https://cstar.help/developers/sdk/realtime#ssr";
184
+ super(0, {
185
+ type: ErrorType.API_ERROR,
186
+ code: "realtime_unavailable",
187
+ message: resolved,
188
+ doc_url: "https://cstar.help/developers/sdk/realtime#ssr"
189
+ });
190
+ this.name = "CStarRealtimeUnavailableError";
191
+ }
192
+ };
181
193
  var STATUS_TO_CLASS = {
182
194
  400: CStarValidationError,
183
195
  401: CStarAuthenticationError,
@@ -220,7 +232,7 @@ function parseError(statusCode, body, retryAfter) {
220
232
  }
221
233
 
222
234
  // src/client.ts
223
- var SDK_VERSION = "0.13.0";
235
+ var SDK_VERSION = "0.14.0";
224
236
  var DEFAULT_BASE_URL = "https://www.cstar.help";
225
237
  var DEFAULT_API_VERSION = "2026-03-01";
226
238
  var DEFAULT_MAX_RETRIES = 3;
@@ -465,6 +477,18 @@ var CStarRealtime = class {
465
477
  this.autoReconnect = options?.autoReconnect ?? true;
466
478
  this.maxRetries = options?.maxRetries ?? 10;
467
479
  this.types = options?.types;
480
+ this.eventSourceCtor = options?.eventSource;
481
+ }
482
+ /**
483
+ * Resolve the EventSource constructor to use for this connection.
484
+ * Returns the explicit polyfill when one was injected, otherwise the
485
+ * global. Returns `undefined` when neither is available — callers must
486
+ * convert to `CStarRealtimeUnavailableError` before touching it.
487
+ */
488
+ resolveEventSource() {
489
+ if (this.eventSourceCtor) return this.eventSourceCtor;
490
+ const fromGlobal = globalThis.EventSource;
491
+ return typeof fromGlobal === "function" ? fromGlobal : void 0;
468
492
  }
469
493
  /** Subscribe to a specific event type. Supports wildcards (e.g., 'ticket.*'). Returns an unsubscribe function. */
470
494
  on(eventType, handler) {
@@ -519,6 +543,9 @@ var CStarRealtime = class {
519
543
  /** Start the SSE connection. Called automatically on first handler registration. */
520
544
  connect() {
521
545
  if (this.eventSource || this.destroyed || this.connecting) return;
546
+ if (!this.resolveEventSource()) {
547
+ throw new CStarRealtimeUnavailableError();
548
+ }
522
549
  this.connecting = true;
523
550
  const headers = this.httpClient.getAuthHeaders();
524
551
  const rawKey = headers.Authorization?.replace("Bearer ", "") ?? "";
@@ -557,7 +584,12 @@ var CStarRealtime = class {
557
584
  }
558
585
  const separator = qs ? "&" : "?";
559
586
  const authenticatedUrl = authToken ? `${url}${separator}token=${authToken}` : url;
560
- this.eventSource = new EventSource(authenticatedUrl);
587
+ const Ctor = this.resolveEventSource();
588
+ if (!Ctor) {
589
+ this.connecting = false;
590
+ throw new CStarRealtimeUnavailableError();
591
+ }
592
+ this.eventSource = new Ctor(authenticatedUrl);
561
593
  this.eventSource.onopen = () => {
562
594
  this.connecting = false;
563
595
  this.retryCount = 0;
@@ -976,6 +1008,14 @@ var BaseResource = class {
976
1008
  async del(id) {
977
1009
  return this.client.request("DELETE", `${this.basePath}/${id}`);
978
1010
  }
1011
+ /**
1012
+ * Delete a resource by ID. Alias for `del()` — kept because IDE
1013
+ * auto-complete and documentation both surface `delete` first. Closes
1014
+ * NEW-v2-06.
1015
+ */
1016
+ async delete(id) {
1017
+ return this.del(id);
1018
+ }
979
1019
  /**
980
1020
  * Auto-paginating async iterator that yields every item across all pages.
981
1021
  *
package/dist/index.d.cts CHANGED
@@ -134,6 +134,13 @@ declare class HttpClient {
134
134
  private sleep;
135
135
  }
136
136
 
137
+ /**
138
+ * The subset of the global `EventSource` constructor surface that the
139
+ * realtime client uses. Allows polyfill injection (e.g. the `eventsource`
140
+ * npm package on Node) without forcing the polyfill to satisfy the full
141
+ * lib.dom.d.ts shape.
142
+ */
143
+ type EventSourceConstructor = new (url: string) => EventSource;
137
144
  /** Options for configuring the realtime connection. */
138
145
  interface RealtimeOptions {
139
146
  /** Event types to subscribe to (e.g., ['ticket.*', 'boss.*']). Omit for all. */
@@ -142,6 +149,13 @@ interface RealtimeOptions {
142
149
  autoReconnect?: boolean;
143
150
  /** Max reconnection attempts before giving up (default: 10). */
144
151
  maxRetries?: number;
152
+ /**
153
+ * EventSource constructor to use. Defaults to `globalThis.EventSource`
154
+ * (browsers and Node 22+). Pass an explicit polyfill (e.g. the
155
+ * `eventsource` npm package) to subscribe from a Node environment that
156
+ * lacks the global. Closes NEW-v2-02.
157
+ */
158
+ eventSource?: EventSourceConstructor;
145
159
  }
146
160
  /** A parsed realtime event from the SSE stream. */
147
161
  interface RealtimeEvent<T = unknown> {
@@ -189,7 +203,15 @@ declare class CStarRealtime {
189
203
  private readonly autoReconnect;
190
204
  private readonly maxRetries;
191
205
  private readonly types;
206
+ private readonly eventSourceCtor;
192
207
  constructor(httpClient: HttpClient, options?: RealtimeOptions);
208
+ /**
209
+ * Resolve the EventSource constructor to use for this connection.
210
+ * Returns the explicit polyfill when one was injected, otherwise the
211
+ * global. Returns `undefined` when neither is available — callers must
212
+ * convert to `CStarRealtimeUnavailableError` before touching it.
213
+ */
214
+ private resolveEventSource;
193
215
  /** Subscribe to a specific event type. Supports wildcards (e.g., 'ticket.*'). Returns an unsubscribe function. */
194
216
  on<T = unknown>(eventType: string, handler: EventHandler<T>): () => void;
195
217
  /** Subscribe to all events. Returns an unsubscribe function. */
@@ -393,6 +415,12 @@ declare abstract class BaseResource<TEntity, TListParams extends PaginationParam
393
415
  update(id: string, params: TUpdateParams, options?: RequestOptions): Promise<TEntity>;
394
416
  /** Delete a resource by ID. */
395
417
  del(id: string): Promise<DeleteResponse>;
418
+ /**
419
+ * Delete a resource by ID. Alias for `del()` — kept because IDE
420
+ * auto-complete and documentation both surface `delete` first. Closes
421
+ * NEW-v2-06.
422
+ */
423
+ delete(id: string): Promise<DeleteResponse>;
396
424
  /**
397
425
  * Auto-paginating async iterator that yields every item across all pages.
398
426
  *
package/dist/index.d.ts CHANGED
@@ -134,6 +134,13 @@ declare class HttpClient {
134
134
  private sleep;
135
135
  }
136
136
 
137
+ /**
138
+ * The subset of the global `EventSource` constructor surface that the
139
+ * realtime client uses. Allows polyfill injection (e.g. the `eventsource`
140
+ * npm package on Node) without forcing the polyfill to satisfy the full
141
+ * lib.dom.d.ts shape.
142
+ */
143
+ type EventSourceConstructor = new (url: string) => EventSource;
137
144
  /** Options for configuring the realtime connection. */
138
145
  interface RealtimeOptions {
139
146
  /** Event types to subscribe to (e.g., ['ticket.*', 'boss.*']). Omit for all. */
@@ -142,6 +149,13 @@ interface RealtimeOptions {
142
149
  autoReconnect?: boolean;
143
150
  /** Max reconnection attempts before giving up (default: 10). */
144
151
  maxRetries?: number;
152
+ /**
153
+ * EventSource constructor to use. Defaults to `globalThis.EventSource`
154
+ * (browsers and Node 22+). Pass an explicit polyfill (e.g. the
155
+ * `eventsource` npm package) to subscribe from a Node environment that
156
+ * lacks the global. Closes NEW-v2-02.
157
+ */
158
+ eventSource?: EventSourceConstructor;
145
159
  }
146
160
  /** A parsed realtime event from the SSE stream. */
147
161
  interface RealtimeEvent<T = unknown> {
@@ -189,7 +203,15 @@ declare class CStarRealtime {
189
203
  private readonly autoReconnect;
190
204
  private readonly maxRetries;
191
205
  private readonly types;
206
+ private readonly eventSourceCtor;
192
207
  constructor(httpClient: HttpClient, options?: RealtimeOptions);
208
+ /**
209
+ * Resolve the EventSource constructor to use for this connection.
210
+ * Returns the explicit polyfill when one was injected, otherwise the
211
+ * global. Returns `undefined` when neither is available — callers must
212
+ * convert to `CStarRealtimeUnavailableError` before touching it.
213
+ */
214
+ private resolveEventSource;
193
215
  /** Subscribe to a specific event type. Supports wildcards (e.g., 'ticket.*'). Returns an unsubscribe function. */
194
216
  on<T = unknown>(eventType: string, handler: EventHandler<T>): () => void;
195
217
  /** Subscribe to all events. Returns an unsubscribe function. */
@@ -393,6 +415,12 @@ declare abstract class BaseResource<TEntity, TListParams extends PaginationParam
393
415
  update(id: string, params: TUpdateParams, options?: RequestOptions): Promise<TEntity>;
394
416
  /** Delete a resource by ID. */
395
417
  del(id: string): Promise<DeleteResponse>;
418
+ /**
419
+ * Delete a resource by ID. Alias for `del()` — kept because IDE
420
+ * auto-complete and documentation both surface `delete` first. Closes
421
+ * NEW-v2-06.
422
+ */
423
+ delete(id: string): Promise<DeleteResponse>;
396
424
  /**
397
425
  * Auto-paginating async iterator that yields every item across all pages.
398
426
  *
package/dist/index.js CHANGED
@@ -139,6 +139,18 @@ var CStarFeatureDisabledError = class extends CStarError {
139
139
  this.enableUrl = resolved.enable_url ?? "";
140
140
  }
141
141
  };
142
+ var CStarRealtimeUnavailableError = class extends CStarError {
143
+ constructor(message) {
144
+ const resolved = message ?? "EventSource is not available in this environment. Real-time subscriptions only work in the browser. Move the call to a client-only path (`useEffect` / `onMount`), or pass an `eventSource` polyfill via the realtime options. See https://cstar.help/developers/sdk/realtime#ssr";
145
+ super(0, {
146
+ type: ErrorType.API_ERROR,
147
+ code: "realtime_unavailable",
148
+ message: resolved,
149
+ doc_url: "https://cstar.help/developers/sdk/realtime#ssr"
150
+ });
151
+ this.name = "CStarRealtimeUnavailableError";
152
+ }
153
+ };
142
154
  var STATUS_TO_CLASS = {
143
155
  400: CStarValidationError,
144
156
  401: CStarAuthenticationError,
@@ -181,7 +193,7 @@ function parseError(statusCode, body, retryAfter) {
181
193
  }
182
194
 
183
195
  // src/client.ts
184
- var SDK_VERSION = "0.13.0";
196
+ var SDK_VERSION = "0.14.0";
185
197
  var DEFAULT_BASE_URL = "https://www.cstar.help";
186
198
  var DEFAULT_API_VERSION = "2026-03-01";
187
199
  var DEFAULT_MAX_RETRIES = 3;
@@ -426,6 +438,18 @@ var CStarRealtime = class {
426
438
  this.autoReconnect = options?.autoReconnect ?? true;
427
439
  this.maxRetries = options?.maxRetries ?? 10;
428
440
  this.types = options?.types;
441
+ this.eventSourceCtor = options?.eventSource;
442
+ }
443
+ /**
444
+ * Resolve the EventSource constructor to use for this connection.
445
+ * Returns the explicit polyfill when one was injected, otherwise the
446
+ * global. Returns `undefined` when neither is available — callers must
447
+ * convert to `CStarRealtimeUnavailableError` before touching it.
448
+ */
449
+ resolveEventSource() {
450
+ if (this.eventSourceCtor) return this.eventSourceCtor;
451
+ const fromGlobal = globalThis.EventSource;
452
+ return typeof fromGlobal === "function" ? fromGlobal : void 0;
429
453
  }
430
454
  /** Subscribe to a specific event type. Supports wildcards (e.g., 'ticket.*'). Returns an unsubscribe function. */
431
455
  on(eventType, handler) {
@@ -480,6 +504,9 @@ var CStarRealtime = class {
480
504
  /** Start the SSE connection. Called automatically on first handler registration. */
481
505
  connect() {
482
506
  if (this.eventSource || this.destroyed || this.connecting) return;
507
+ if (!this.resolveEventSource()) {
508
+ throw new CStarRealtimeUnavailableError();
509
+ }
483
510
  this.connecting = true;
484
511
  const headers = this.httpClient.getAuthHeaders();
485
512
  const rawKey = headers.Authorization?.replace("Bearer ", "") ?? "";
@@ -518,7 +545,12 @@ var CStarRealtime = class {
518
545
  }
519
546
  const separator = qs ? "&" : "?";
520
547
  const authenticatedUrl = authToken ? `${url}${separator}token=${authToken}` : url;
521
- this.eventSource = new EventSource(authenticatedUrl);
548
+ const Ctor = this.resolveEventSource();
549
+ if (!Ctor) {
550
+ this.connecting = false;
551
+ throw new CStarRealtimeUnavailableError();
552
+ }
553
+ this.eventSource = new Ctor(authenticatedUrl);
522
554
  this.eventSource.onopen = () => {
523
555
  this.connecting = false;
524
556
  this.retryCount = 0;
@@ -937,6 +969,14 @@ var BaseResource = class {
937
969
  async del(id) {
938
970
  return this.client.request("DELETE", `${this.basePath}/${id}`);
939
971
  }
972
+ /**
973
+ * Delete a resource by ID. Alias for `del()` — kept because IDE
974
+ * auto-complete and documentation both surface `delete` first. Closes
975
+ * NEW-v2-06.
976
+ */
977
+ async delete(id) {
978
+ return this.del(id);
979
+ }
940
980
  /**
941
981
  * Auto-paginating async iterator that yields every item across all pages.
942
982
  *
@@ -181,11 +181,15 @@ var TYPE_TO_CLASS = {
181
181
  var DEFAULT_BASE_URL = "https://www.cstar.help";
182
182
  var QuickHelpClient = class {
183
183
  constructor(config) {
184
- if (!config?.teamId) {
185
- throw new Error("QuickHelpClient: teamId is required");
184
+ this.resolveTeamIdPromise = null;
185
+ if (!config?.teamId && !config?.teamSlug) {
186
+ throw new Error(
187
+ 'QuickHelpClient: either `teamId` or `teamSlug` is required. Pass `teamSlug` (e.g. "acme") for the easiest setup.'
188
+ );
186
189
  }
187
190
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
188
- this.teamId = config.teamId;
191
+ this.teamId = config.teamId ?? null;
192
+ this.teamSlug = config.teamSlug ?? null;
189
193
  this.publishableKey = config.publishableKey ?? null;
190
194
  this.mode = this.publishableKey?.startsWith("pk_test_") ? "test" : "live";
191
195
  if (this.publishableKey && !/^pk_(test|live)_/.test(this.publishableKey)) {
@@ -197,6 +201,39 @@ var QuickHelpClient = class {
197
201
  authHeaders() {
198
202
  return this.publishableKey ? { Authorization: `Bearer ${this.publishableKey}` } : {};
199
203
  }
204
+ /**
205
+ * Resolve `teamSlug` → `teamId` lazily on the first call that needs it.
206
+ * Cached for the life of the client. NEW-v2-05.
207
+ */
208
+ async resolveTeamId() {
209
+ if (this.teamId) return this.teamId;
210
+ if (!this.teamSlug) throw new Error("QuickHelpClient: no teamId or teamSlug set");
211
+ if (this.resolveTeamIdPromise) return this.resolveTeamIdPromise;
212
+ this.resolveTeamIdPromise = (async () => {
213
+ const url = `${this.baseUrl}/api/widget/team-by-slug?teamSlug=${encodeURIComponent(this.teamSlug)}`;
214
+ const res = await globalThis.fetch(url, { method: "GET", headers: this.authHeaders() });
215
+ if (!res.ok) {
216
+ throw new Error(
217
+ `QuickHelpClient: could not resolve teamSlug "${this.teamSlug}" (HTTP ${res.status}).`
218
+ );
219
+ }
220
+ const body = await res.json();
221
+ const id = body?.data?.teamId;
222
+ if (!id) {
223
+ throw new Error(
224
+ `QuickHelpClient: lookup for teamSlug "${this.teamSlug}" returned no teamId.`
225
+ );
226
+ }
227
+ this.teamId = id;
228
+ return id;
229
+ })();
230
+ try {
231
+ return await this.resolveTeamIdPromise;
232
+ } catch (err) {
233
+ this.resolveTeamIdPromise = null;
234
+ throw err;
235
+ }
236
+ }
200
237
  /**
201
238
  * Fetch the sanitized public config (title, subtitle, placeholder,
202
239
  * theme overrides). Never includes agent id or escalation contact
@@ -205,7 +242,8 @@ var QuickHelpClient = class {
205
242
  * Returns `null` if Quick Help is disabled on the team (404).
206
243
  */
207
244
  async config() {
208
- const url = `${this.baseUrl}/api/quickhelp/config/${encodeURIComponent(this.teamId)}`;
245
+ const teamId = await this.resolveTeamId();
246
+ const url = `${this.baseUrl}/api/quickhelp/config/${encodeURIComponent(teamId)}`;
209
247
  const res = await globalThis.fetch(url, { method: "GET", headers: this.authHeaders() });
210
248
  if (res.status === 404) return null;
211
249
  if (!res.ok) {
@@ -222,12 +260,13 @@ var QuickHelpClient = class {
222
260
  * property set from the response body.
223
261
  */
224
262
  async ask(params) {
263
+ const teamId = await this.resolveTeamId();
225
264
  const url = `${this.baseUrl}/api/quickhelp/ask`;
226
265
  const res = await globalThis.fetch(url, {
227
266
  method: "POST",
228
267
  headers: { "Content-Type": "application/json", ...this.authHeaders() },
229
268
  body: JSON.stringify({
230
- team_id: this.teamId,
269
+ team_id: teamId,
231
270
  session_id: params.sessionId ?? null,
232
271
  question: params.question
233
272
  })
@@ -241,7 +280,7 @@ var QuickHelpClient = class {
241
280
  message: body?.error?.message ?? "Quick Help is not available on this team. Enable an AI Agent (Settings \u2192 AI Agents) and configure Quick Help (Settings \u2192 Quick Help) before calling .ask().",
242
281
  doc_url: "https://cstar.help/developers/api/errors#feature-not-configured",
243
282
  feature: "quickHelp",
244
- enable_url: `${this.baseUrl}/team/${encodeURIComponent(this.teamId)}/settings?section=quickHelp`
283
+ enable_url: `${this.baseUrl}/team/${encodeURIComponent(teamId)}/settings?section=quickHelp`
245
284
  });
246
285
  }
247
286
  const message = body?.error?.message ?? `Quick Help ask failed: ${res.status}`;
@@ -1,7 +1,21 @@
1
- /** Options for the QuickHelpClient. No auth — Quick Help is anonymous by design. */
1
+ /**
2
+ * Options for the QuickHelpClient. No auth — Quick Help is anonymous by
3
+ * design.
4
+ *
5
+ * NEW-v2-05: pass either `teamId` (UUID, fastest — no roundtrip) or the
6
+ * easier-to-discover `teamSlug`. The slug is resolved to a UUID on the
7
+ * first network call and cached for the life of the client. At least one
8
+ * is required.
9
+ */
2
10
  interface QuickHelpConfig {
3
- /** Your team's UUID. Required. */
4
- teamId: string;
11
+ /** Your team's UUID. Either this or `teamSlug` is required. */
12
+ teamId?: string;
13
+ /**
14
+ * Your team's slug (e.g. `acme`). Resolved to `teamId` lazily via the
15
+ * public `/api/widget/team-by-slug` endpoint on the first call. Cached
16
+ * thereafter. Either this or `teamId` is required.
17
+ */
18
+ teamSlug?: string;
5
19
  /**
6
20
  * Optional publishable key (`pk_test_*` or `pk_live_*`). Sent as
7
21
  * `Authorization: Bearer` so test-mode dev sessions can be scoped
@@ -67,12 +81,20 @@ interface QuickHelpAskResult {
67
81
  */
68
82
  declare class QuickHelpClient {
69
83
  private readonly baseUrl;
70
- private readonly teamId;
84
+ /** Cached resolved teamId. `null` until the first lookup completes. */
85
+ private teamId;
86
+ private readonly teamSlug;
87
+ private resolveTeamIdPromise;
71
88
  private readonly publishableKey;
72
89
  /** Resolved mode based on the publishable-key prefix. */
73
90
  readonly mode: 'live' | 'test';
74
91
  constructor(config: QuickHelpConfig);
75
92
  private authHeaders;
93
+ /**
94
+ * Resolve `teamSlug` → `teamId` lazily on the first call that needs it.
95
+ * Cached for the life of the client. NEW-v2-05.
96
+ */
97
+ private resolveTeamId;
76
98
  /**
77
99
  * Fetch the sanitized public config (title, subtitle, placeholder,
78
100
  * theme overrides). Never includes agent id or escalation contact
@@ -1,7 +1,21 @@
1
- /** Options for the QuickHelpClient. No auth — Quick Help is anonymous by design. */
1
+ /**
2
+ * Options for the QuickHelpClient. No auth — Quick Help is anonymous by
3
+ * design.
4
+ *
5
+ * NEW-v2-05: pass either `teamId` (UUID, fastest — no roundtrip) or the
6
+ * easier-to-discover `teamSlug`. The slug is resolved to a UUID on the
7
+ * first network call and cached for the life of the client. At least one
8
+ * is required.
9
+ */
2
10
  interface QuickHelpConfig {
3
- /** Your team's UUID. Required. */
4
- teamId: string;
11
+ /** Your team's UUID. Either this or `teamSlug` is required. */
12
+ teamId?: string;
13
+ /**
14
+ * Your team's slug (e.g. `acme`). Resolved to `teamId` lazily via the
15
+ * public `/api/widget/team-by-slug` endpoint on the first call. Cached
16
+ * thereafter. Either this or `teamId` is required.
17
+ */
18
+ teamSlug?: string;
5
19
  /**
6
20
  * Optional publishable key (`pk_test_*` or `pk_live_*`). Sent as
7
21
  * `Authorization: Bearer` so test-mode dev sessions can be scoped
@@ -67,12 +81,20 @@ interface QuickHelpAskResult {
67
81
  */
68
82
  declare class QuickHelpClient {
69
83
  private readonly baseUrl;
70
- private readonly teamId;
84
+ /** Cached resolved teamId. `null` until the first lookup completes. */
85
+ private teamId;
86
+ private readonly teamSlug;
87
+ private resolveTeamIdPromise;
71
88
  private readonly publishableKey;
72
89
  /** Resolved mode based on the publishable-key prefix. */
73
90
  readonly mode: 'live' | 'test';
74
91
  constructor(config: QuickHelpConfig);
75
92
  private authHeaders;
93
+ /**
94
+ * Resolve `teamSlug` → `teamId` lazily on the first call that needs it.
95
+ * Cached for the life of the client. NEW-v2-05.
96
+ */
97
+ private resolveTeamId;
76
98
  /**
77
99
  * Fetch the sanitized public config (title, subtitle, placeholder,
78
100
  * theme overrides). Never includes agent id or escalation contact
@@ -154,11 +154,15 @@ var TYPE_TO_CLASS = {
154
154
  var DEFAULT_BASE_URL = "https://www.cstar.help";
155
155
  var QuickHelpClient = class {
156
156
  constructor(config) {
157
- if (!config?.teamId) {
158
- throw new Error("QuickHelpClient: teamId is required");
157
+ this.resolveTeamIdPromise = null;
158
+ if (!config?.teamId && !config?.teamSlug) {
159
+ throw new Error(
160
+ 'QuickHelpClient: either `teamId` or `teamSlug` is required. Pass `teamSlug` (e.g. "acme") for the easiest setup.'
161
+ );
159
162
  }
160
163
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
161
- this.teamId = config.teamId;
164
+ this.teamId = config.teamId ?? null;
165
+ this.teamSlug = config.teamSlug ?? null;
162
166
  this.publishableKey = config.publishableKey ?? null;
163
167
  this.mode = this.publishableKey?.startsWith("pk_test_") ? "test" : "live";
164
168
  if (this.publishableKey && !/^pk_(test|live)_/.test(this.publishableKey)) {
@@ -170,6 +174,39 @@ var QuickHelpClient = class {
170
174
  authHeaders() {
171
175
  return this.publishableKey ? { Authorization: `Bearer ${this.publishableKey}` } : {};
172
176
  }
177
+ /**
178
+ * Resolve `teamSlug` → `teamId` lazily on the first call that needs it.
179
+ * Cached for the life of the client. NEW-v2-05.
180
+ */
181
+ async resolveTeamId() {
182
+ if (this.teamId) return this.teamId;
183
+ if (!this.teamSlug) throw new Error("QuickHelpClient: no teamId or teamSlug set");
184
+ if (this.resolveTeamIdPromise) return this.resolveTeamIdPromise;
185
+ this.resolveTeamIdPromise = (async () => {
186
+ const url = `${this.baseUrl}/api/widget/team-by-slug?teamSlug=${encodeURIComponent(this.teamSlug)}`;
187
+ const res = await globalThis.fetch(url, { method: "GET", headers: this.authHeaders() });
188
+ if (!res.ok) {
189
+ throw new Error(
190
+ `QuickHelpClient: could not resolve teamSlug "${this.teamSlug}" (HTTP ${res.status}).`
191
+ );
192
+ }
193
+ const body = await res.json();
194
+ const id = body?.data?.teamId;
195
+ if (!id) {
196
+ throw new Error(
197
+ `QuickHelpClient: lookup for teamSlug "${this.teamSlug}" returned no teamId.`
198
+ );
199
+ }
200
+ this.teamId = id;
201
+ return id;
202
+ })();
203
+ try {
204
+ return await this.resolveTeamIdPromise;
205
+ } catch (err) {
206
+ this.resolveTeamIdPromise = null;
207
+ throw err;
208
+ }
209
+ }
173
210
  /**
174
211
  * Fetch the sanitized public config (title, subtitle, placeholder,
175
212
  * theme overrides). Never includes agent id or escalation contact
@@ -178,7 +215,8 @@ var QuickHelpClient = class {
178
215
  * Returns `null` if Quick Help is disabled on the team (404).
179
216
  */
180
217
  async config() {
181
- const url = `${this.baseUrl}/api/quickhelp/config/${encodeURIComponent(this.teamId)}`;
218
+ const teamId = await this.resolveTeamId();
219
+ const url = `${this.baseUrl}/api/quickhelp/config/${encodeURIComponent(teamId)}`;
182
220
  const res = await globalThis.fetch(url, { method: "GET", headers: this.authHeaders() });
183
221
  if (res.status === 404) return null;
184
222
  if (!res.ok) {
@@ -195,12 +233,13 @@ var QuickHelpClient = class {
195
233
  * property set from the response body.
196
234
  */
197
235
  async ask(params) {
236
+ const teamId = await this.resolveTeamId();
198
237
  const url = `${this.baseUrl}/api/quickhelp/ask`;
199
238
  const res = await globalThis.fetch(url, {
200
239
  method: "POST",
201
240
  headers: { "Content-Type": "application/json", ...this.authHeaders() },
202
241
  body: JSON.stringify({
203
- team_id: this.teamId,
242
+ team_id: teamId,
204
243
  session_id: params.sessionId ?? null,
205
244
  question: params.question
206
245
  })
@@ -214,7 +253,7 @@ var QuickHelpClient = class {
214
253
  message: body?.error?.message ?? "Quick Help is not available on this team. Enable an AI Agent (Settings \u2192 AI Agents) and configure Quick Help (Settings \u2192 Quick Help) before calling .ask().",
215
254
  doc_url: "https://cstar.help/developers/api/errors#feature-not-configured",
216
255
  feature: "quickHelp",
217
- enable_url: `${this.baseUrl}/team/${encodeURIComponent(this.teamId)}/settings?section=quickHelp`
256
+ enable_url: `${this.baseUrl}/team/${encodeURIComponent(teamId)}/settings?section=quickHelp`
218
257
  });
219
258
  }
220
259
  const message = body?.error?.message ?? `Quick Help ask failed: ${res.status}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cstar.help/js",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Official TypeScript SDK for the cStar customer support platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",