@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.
@@ -248,37 +248,45 @@ var PresenceManager = class {
248
248
  };
249
249
 
250
250
  // src/chat/errors.ts
251
+ function normalizeChatArgs(arg, fallbackCode) {
252
+ if (typeof arg === "string") {
253
+ return { message: arg, code: fallbackCode, statusCode: 0, requestId: null };
254
+ }
255
+ return { ...arg, code: arg.code ?? fallbackCode };
256
+ }
251
257
  var ChatClientError = class extends Error {
252
- constructor(message, opts) {
253
- super(message);
258
+ constructor(arg) {
259
+ const opts = normalizeChatArgs(arg, "UNKNOWN");
260
+ super(opts.message);
254
261
  this.name = "ChatClientError";
255
262
  this.code = opts.code ?? "UNKNOWN";
256
- this.statusCode = opts.statusCode;
263
+ this.statusCode = opts.statusCode ?? 0;
257
264
  this.requestId = opts.requestId ?? null;
258
265
  }
259
266
  };
260
267
  var ChatClientAuthError = class extends ChatClientError {
261
- constructor(opts) {
262
- super(opts.message, opts);
268
+ constructor(arg) {
269
+ super(normalizeChatArgs(arg, "AUTH_ERROR"));
263
270
  this.name = "ChatClientAuthError";
264
271
  }
265
272
  };
266
273
  var ChatClientRateLimitError = class extends ChatClientError {
267
- constructor(opts) {
268
- super(opts.message, opts);
274
+ constructor(arg) {
275
+ const opts = typeof arg === "string" ? { message: arg, code: "RATE_LIMIT", statusCode: 429, requestId: null, retryAfter: null } : { ...arg, code: arg.code ?? "RATE_LIMIT" };
276
+ super(opts);
269
277
  this.name = "ChatClientRateLimitError";
270
278
  this.retryAfter = opts.retryAfter ?? null;
271
279
  }
272
280
  };
273
281
  var ChatClientServerError = class extends ChatClientError {
274
- constructor(opts) {
275
- super(opts.message, opts);
282
+ constructor(arg) {
283
+ super(normalizeChatArgs(arg, "SERVER_ERROR"));
276
284
  this.name = "ChatClientServerError";
277
285
  }
278
286
  };
279
287
  var ChatClientNetworkError = class extends ChatClientError {
280
288
  constructor(opts) {
281
- super(opts.message, { code: "NETWORK_ERROR", statusCode: 0, requestId: null });
289
+ super({ message: opts.message, code: "NETWORK_ERROR", statusCode: 0, requestId: null });
282
290
  this.name = "ChatClientNetworkError";
283
291
  this.originalError = opts.originalError;
284
292
  }
@@ -294,6 +302,7 @@ var ChatClient = class {
294
302
  this.customerId = null;
295
303
  this.authMode = null;
296
304
  this.pollingTimers = /* @__PURE__ */ new Map();
305
+ this.stateListeners = /* @__PURE__ */ new Set();
297
306
  this.realtimeManager = null;
298
307
  this.presenceManager = null;
299
308
  this.realtimeReady = false;
@@ -333,6 +342,8 @@ var ChatClient = class {
333
342
  this.sessionToken = response.data.sessionToken;
334
343
  this.teamId = response.data.teamId;
335
344
  this.customerId = response.data.customer.id;
345
+ this.authMode = "hmac";
346
+ this.notifyStateChanged();
336
347
  if (this.realtimeEnabled) {
337
348
  this.realtimeInitPromise = this.initRealtime(
338
349
  response.data.customer.id,
@@ -392,11 +403,35 @@ var ChatClient = class {
392
403
  this.teamId = params.teamId;
393
404
  }
394
405
  this.authMode = "session";
406
+ this.notifyStateChanged();
395
407
  if (this.realtimeEnabled) {
396
408
  const customerName = params.customerName ?? "";
397
409
  this.realtimeInitPromise = this.initRealtimeFromSession(customerName);
398
410
  }
399
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
+ }
400
435
  /**
401
436
  * Returns the auth mode the client is currently using, or `null` before
402
437
  * any authenticated call has resolved one. Useful for branching UI
@@ -459,6 +494,7 @@ var ChatClient = class {
459
494
  this.teamId = null;
460
495
  this.customerId = null;
461
496
  this.authMode = null;
497
+ this.notifyStateChanged();
462
498
  }
463
499
  /**
464
500
  * @internal Make an authenticated request to the widget API.
@@ -496,7 +532,9 @@ var ChatClient = class {
496
532
  const responseHeaders = response.headers && typeof response.headers.get === "function" ? response.headers : null;
497
533
  const headerMode = responseHeaders?.get("X-Cstar-Auth-Mode") ?? null;
498
534
  if (headerMode === "hmac" || headerMode === "session") {
535
+ const previous = this.authMode;
499
536
  this.authMode = headerMode;
537
+ if (previous !== headerMode) this.notifyStateChanged();
500
538
  }
501
539
  const requestId = responseHeaders?.get("X-Request-Id") ?? null;
502
540
  if (!response.ok) {
@@ -530,7 +568,8 @@ var ChatClient = class {
530
568
  requestId
531
569
  });
532
570
  }
533
- throw new ChatClientError(message, {
571
+ throw new ChatClientError({
572
+ message,
534
573
  code,
535
574
  statusCode: response.status,
536
575
  requestId
@@ -668,7 +707,7 @@ var TicketsApi = class {
668
707
  /** List tickets for the identified customer. */
669
708
  async list(params) {
670
709
  const teamId = this.chat.getTeamId();
671
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
710
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
672
711
  const response = await this.chat.fetch("/api/widget/conversations", {
673
712
  method: "GET",
674
713
  query: {
@@ -677,16 +716,22 @@ var TicketsApi = class {
677
716
  cursor: params?.cursor
678
717
  }
679
718
  });
719
+ const data = response.data.tickets ?? response.data.conversations ?? [];
720
+ const cursor = response.data.cursor;
721
+ const hasMore = response.data.hasMore;
680
722
  return {
681
- tickets: response.data.tickets ?? response.data.conversations ?? [],
682
- cursor: response.data.cursor,
683
- 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
684
729
  };
685
730
  }
686
731
  /** Create a new ticket. */
687
732
  async create(params) {
688
733
  const teamId = this.chat.getTeamId();
689
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
734
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
690
735
  const response = await this.chat.fetch("/api/widget/tickets", {
691
736
  method: "POST",
692
737
  body: {
@@ -695,15 +740,25 @@ var TicketsApi = class {
695
740
  message: params.message
696
741
  }
697
742
  });
743
+ const messages = (response.data.messages ?? []).map((m) => ({
744
+ id: m.id,
745
+ content: m.content,
746
+ sender: m.sender,
747
+ sender_name: m.sender_name,
748
+ created_at: m.created_at,
749
+ read_at: m.read_at,
750
+ attachments: m.attachments ?? []
751
+ }));
698
752
  return {
699
753
  id: response.data.id,
700
- ticketNumber: "",
754
+ ticketNumber: null,
701
755
  subject: response.data.subject,
702
756
  status: response.data.status,
703
- messageCount: params.message ? 1 : 0,
757
+ messageCount: messages.length,
704
758
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
705
759
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
706
- closedAt: null
760
+ closedAt: null,
761
+ messages
707
762
  };
708
763
  }
709
764
  };
@@ -714,7 +769,7 @@ var ChatCommunityApi = class {
714
769
  /** Create a new community post as the identified customer. */
715
770
  async createPost(params) {
716
771
  const teamId = this.chat.getTeamId();
717
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
772
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
718
773
  const response = await this.chat.fetch("/api/widget/community", {
719
774
  method: "POST",
720
775
  body: {
@@ -731,7 +786,7 @@ var ChatCommunityApi = class {
731
786
  /** Update a post. Only the original author can edit. */
732
787
  async updatePost(postId, params) {
733
788
  const teamId = this.chat.getTeamId();
734
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
789
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
735
790
  const response = await this.chat.fetch("/api/widget/community", {
736
791
  method: "POST",
737
792
  body: {
@@ -748,7 +803,7 @@ var ChatCommunityApi = class {
748
803
  /** Delete a post (soft delete). Only the original author can delete. */
749
804
  async deletePost(postId) {
750
805
  const teamId = this.chat.getTeamId();
751
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
806
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
752
807
  await this.chat.fetch("/api/widget/community", {
753
808
  method: "POST",
754
809
  body: { teamId, action: "deletePost", postId }
@@ -757,7 +812,7 @@ var ChatCommunityApi = class {
757
812
  /** Update a comment. Only the original author can edit. */
758
813
  async updateComment(postId, commentId, params) {
759
814
  const teamId = this.chat.getTeamId();
760
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
815
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
761
816
  const response = await this.chat.fetch("/api/widget/community", {
762
817
  method: "POST",
763
818
  body: {
@@ -773,7 +828,7 @@ var ChatCommunityApi = class {
773
828
  /** Delete a comment (soft delete). Only the original author can delete. */
774
829
  async deleteComment(postId, commentId) {
775
830
  const teamId = this.chat.getTeamId();
776
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
831
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
777
832
  await this.chat.fetch("/api/widget/community", {
778
833
  method: "POST",
779
834
  body: { teamId, action: "deleteComment", postId, commentId }
@@ -782,7 +837,7 @@ var ChatCommunityApi = class {
782
837
  /** Toggle vote on a post. Returns new vote state and count. */
783
838
  async vote(postId) {
784
839
  const teamId = this.chat.getTeamId();
785
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
840
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
786
841
  const response = await this.chat.fetch("/api/widget/community", {
787
842
  method: "POST",
788
843
  body: { teamId, action: "vote", postId }
@@ -792,7 +847,7 @@ var ChatCommunityApi = class {
792
847
  /** Add a comment to a post. Supports 1-level nesting via parentCommentId. */
793
848
  async comment(postId, params) {
794
849
  const teamId = this.chat.getTeamId();
795
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
850
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
796
851
  const response = await this.chat.fetch("/api/widget/community", {
797
852
  method: "POST",
798
853
  body: {
@@ -808,7 +863,7 @@ var ChatCommunityApi = class {
808
863
  /** Toggle follow on a post. */
809
864
  async follow(postId) {
810
865
  const teamId = this.chat.getTeamId();
811
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
866
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
812
867
  const response = await this.chat.fetch("/api/widget/community", {
813
868
  method: "POST",
814
869
  body: { teamId, action: "follow", postId }
@@ -818,7 +873,7 @@ var ChatCommunityApi = class {
818
873
  /** Check if the current user has voted on a post. */
819
874
  async getVoteStatus(postId) {
820
875
  const teamId = this.chat.getTeamId();
821
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
876
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
822
877
  const response = await this.chat.fetch("/api/widget/community", {
823
878
  method: "GET",
824
879
  query: { teamId, postId }
@@ -829,7 +884,7 @@ var ChatCommunityApi = class {
829
884
  async bulkVoteStatus(postIds) {
830
885
  if (postIds.length === 0) return {};
831
886
  const teamId = this.chat.getTeamId();
832
- if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
887
+ if (!teamId) throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
833
888
  const response = await this.chat.fetch("/api/widget/community", {
834
889
  method: "GET",
835
890
  query: { teamId, postIds: postIds.join(",") }
@@ -23,6 +23,221 @@ __export(community_exports, {
23
23
  CommunityClient: () => CommunityClient
24
24
  });
25
25
  module.exports = __toCommonJS(community_exports);
26
+
27
+ // src/errors.ts
28
+ var ErrorType = {
29
+ AUTHENTICATION_ERROR: "authentication_error",
30
+ PERMISSION_ERROR: "permission_error",
31
+ INVALID_REQUEST_ERROR: "invalid_request_error",
32
+ NOT_FOUND_ERROR: "not_found_error",
33
+ CONFLICT_ERROR: "conflict_error",
34
+ RATE_LIMIT_ERROR: "rate_limit_error",
35
+ FEATURE_NOT_CONFIGURED: "feature_not_configured",
36
+ API_ERROR: "api_error",
37
+ SERVER_ERROR: "server_error"
38
+ };
39
+ var DEFAULT_DOC_URL = "https://cstar.help/developers/api/errors";
40
+ function normalizeArgs(messageOrStatus, maybeBody, defaults) {
41
+ if (typeof messageOrStatus === "string") {
42
+ return {
43
+ statusCode: defaults?.code === "rate_limit_error" ? 429 : 0,
44
+ body: {
45
+ type: defaults?.type ?? ErrorType.API_ERROR,
46
+ code: defaults?.code ?? "sdk_error",
47
+ message: messageOrStatus,
48
+ doc_url: defaults?.doc_url ?? DEFAULT_DOC_URL,
49
+ ...defaults?.feature ? { feature: defaults.feature } : {},
50
+ ...defaults?.enable_url ? { enable_url: defaults.enable_url } : {}
51
+ }
52
+ };
53
+ }
54
+ return {
55
+ statusCode: messageOrStatus,
56
+ body: maybeBody ?? {
57
+ type: defaults?.type ?? ErrorType.API_ERROR,
58
+ code: defaults?.code ?? "unknown_error",
59
+ message: defaults?.message ?? `Request failed with status ${messageOrStatus}`,
60
+ doc_url: defaults?.doc_url ?? DEFAULT_DOC_URL
61
+ }
62
+ };
63
+ }
64
+ var CStarError = class extends Error {
65
+ constructor(messageOrStatus, body) {
66
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body);
67
+ super(resolved.message);
68
+ this.name = "CStarError";
69
+ this.type = resolved.type;
70
+ this.code = resolved.code;
71
+ this.param = resolved.param;
72
+ this.docUrl = resolved.doc_url;
73
+ this.requestId = resolved.request_id;
74
+ this.statusCode = statusCode;
75
+ }
76
+ };
77
+ var CStarAuthenticationError = class extends CStarError {
78
+ constructor(messageOrStatus, body) {
79
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
80
+ type: ErrorType.AUTHENTICATION_ERROR,
81
+ code: "authentication_failed",
82
+ doc_url: `${DEFAULT_DOC_URL}#authentication-error`
83
+ });
84
+ super(statusCode, resolved);
85
+ this.name = "CStarAuthenticationError";
86
+ }
87
+ };
88
+ var CStarRateLimitError = class extends CStarError {
89
+ constructor(messageOrStatus, body, retryAfter) {
90
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
91
+ type: ErrorType.RATE_LIMIT_ERROR,
92
+ code: "rate_limit_exceeded",
93
+ doc_url: `${DEFAULT_DOC_URL}#rate-limit-error`
94
+ });
95
+ super(statusCode === 0 ? 429 : statusCode, resolved);
96
+ this.name = "CStarRateLimitError";
97
+ this.retryAfter = retryAfter ?? 60;
98
+ }
99
+ };
100
+ var CStarNotFoundError = class extends CStarError {
101
+ constructor(messageOrStatus, body) {
102
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
103
+ type: ErrorType.NOT_FOUND_ERROR,
104
+ code: "resource_not_found",
105
+ doc_url: `${DEFAULT_DOC_URL}#not-found-error`
106
+ });
107
+ super(statusCode, resolved);
108
+ this.name = "CStarNotFoundError";
109
+ }
110
+ };
111
+ var CStarValidationError = class extends CStarError {
112
+ constructor(messageOrStatus, body) {
113
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
114
+ type: ErrorType.INVALID_REQUEST_ERROR,
115
+ code: "validation_error",
116
+ doc_url: `${DEFAULT_DOC_URL}#validation-error`
117
+ });
118
+ super(statusCode, resolved);
119
+ this.name = "CStarValidationError";
120
+ }
121
+ };
122
+ var CStarPermissionError = class extends CStarError {
123
+ constructor(messageOrStatus, body) {
124
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
125
+ type: ErrorType.PERMISSION_ERROR,
126
+ code: "permission_denied",
127
+ doc_url: `${DEFAULT_DOC_URL}#permission-error`
128
+ });
129
+ super(statusCode, resolved);
130
+ this.name = "CStarPermissionError";
131
+ }
132
+ };
133
+ var CStarConflictError = class extends CStarError {
134
+ constructor(messageOrStatus, body) {
135
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
136
+ type: ErrorType.CONFLICT_ERROR,
137
+ code: "conflict",
138
+ doc_url: `${DEFAULT_DOC_URL}#conflict-error`
139
+ });
140
+ super(statusCode, resolved);
141
+ this.name = "CStarConflictError";
142
+ }
143
+ };
144
+ var CStarServerError = class extends CStarError {
145
+ constructor(messageOrStatus, body) {
146
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
147
+ type: ErrorType.SERVER_ERROR,
148
+ code: "server_error",
149
+ doc_url: `${DEFAULT_DOC_URL}#server-error`
150
+ });
151
+ super(statusCode, resolved);
152
+ this.name = "CStarServerError";
153
+ }
154
+ };
155
+ var CStarFeatureDisabledError = class extends CStarError {
156
+ constructor(messageOrStatus, body) {
157
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
158
+ type: ErrorType.FEATURE_NOT_CONFIGURED,
159
+ code: "feature_not_configured",
160
+ doc_url: `${DEFAULT_DOC_URL}#feature-not-configured`
161
+ });
162
+ super(statusCode, resolved);
163
+ this.name = "CStarFeatureDisabledError";
164
+ this.feature = resolved.feature ?? "unknown";
165
+ this.enableUrl = resolved.enable_url ?? "";
166
+ }
167
+ };
168
+ function parsePublicError(status, body, fallbackMessage) {
169
+ const obj = body && typeof body === "object" ? body : null;
170
+ const errVal = obj?.error;
171
+ let message = fallbackMessage;
172
+ let structured = null;
173
+ if (typeof errVal === "string") {
174
+ message = errVal;
175
+ } else if (errVal && typeof errVal === "object") {
176
+ const e = errVal;
177
+ if (typeof e.message === "string") message = e.message;
178
+ structured = {
179
+ type: typeof e.type === "string" ? e.type : ErrorType.API_ERROR,
180
+ code: typeof e.code === "string" ? e.code : "api_error",
181
+ message,
182
+ param: typeof e.param === "string" ? e.param : void 0,
183
+ doc_url: typeof e.doc_url === "string" ? e.doc_url : DEFAULT_DOC_URL,
184
+ request_id: typeof e.request_id === "string" ? e.request_id : void 0,
185
+ feature: typeof e.feature === "string" ? e.feature : void 0,
186
+ enable_url: typeof e.enable_url === "string" ? e.enable_url : void 0
187
+ };
188
+ } else if (typeof obj?.message === "string") {
189
+ message = obj.message;
190
+ }
191
+ const errorBody = structured ?? {
192
+ type: ErrorType.API_ERROR,
193
+ code: status >= 500 ? "server_error" : "request_failed",
194
+ message,
195
+ doc_url: DEFAULT_DOC_URL
196
+ };
197
+ return parseError(status, { error: errorBody });
198
+ }
199
+ var STATUS_TO_CLASS = {
200
+ 400: CStarValidationError,
201
+ 401: CStarAuthenticationError,
202
+ 403: CStarPermissionError,
203
+ 404: CStarNotFoundError,
204
+ 409: CStarConflictError,
205
+ 422: CStarValidationError,
206
+ 429: CStarRateLimitError
207
+ };
208
+ var TYPE_TO_CLASS = {
209
+ [ErrorType.AUTHENTICATION_ERROR]: CStarAuthenticationError,
210
+ [ErrorType.PERMISSION_ERROR]: CStarPermissionError,
211
+ [ErrorType.RATE_LIMIT_ERROR]: CStarRateLimitError,
212
+ [ErrorType.NOT_FOUND_ERROR]: CStarNotFoundError,
213
+ [ErrorType.INVALID_REQUEST_ERROR]: CStarValidationError,
214
+ [ErrorType.CONFLICT_ERROR]: CStarConflictError,
215
+ [ErrorType.SERVER_ERROR]: CStarServerError,
216
+ [ErrorType.FEATURE_NOT_CONFIGURED]: CStarFeatureDisabledError
217
+ };
218
+ function parseError(statusCode, body, retryAfter) {
219
+ const errorBody = body?.error ?? {
220
+ type: ErrorType.API_ERROR,
221
+ code: "unknown_error",
222
+ message: `Request failed with status ${statusCode}`,
223
+ doc_url: DEFAULT_DOC_URL
224
+ };
225
+ if (statusCode === 429) {
226
+ return new CStarRateLimitError(statusCode, errorBody, retryAfter ?? 60);
227
+ }
228
+ const StatusCls = STATUS_TO_CLASS[statusCode];
229
+ if (StatusCls) {
230
+ return new StatusCls(statusCode, errorBody);
231
+ }
232
+ const TypeCls = TYPE_TO_CLASS[errorBody.type];
233
+ if (TypeCls) {
234
+ return new TypeCls(statusCode, errorBody);
235
+ }
236
+ if (statusCode >= 500) return new CStarServerError(statusCode, errorBody);
237
+ return new CStarError(statusCode, errorBody);
238
+ }
239
+
240
+ // src/community/index.ts
26
241
  var DEFAULT_BASE_URL = "https://www.cstar.help";
27
242
  var CommunityClient = class {
28
243
  constructor(config) {
@@ -30,6 +245,13 @@ var CommunityClient = class {
30
245
  this.teamId = null;
31
246
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
32
247
  this.teamSlug = config.teamSlug;
248
+ this.publishableKey = config.publishableKey ?? null;
249
+ this.mode = this.publishableKey?.startsWith("pk_test_") ? "test" : "live";
250
+ if (this.publishableKey && !/^pk_(test|live)_/.test(this.publishableKey)) {
251
+ throw new Error(
252
+ "[cStar] CommunityClient: publishableKey must start with `pk_test_` or `pk_live_`. Secret keys (`sk_*`) cannot be used in browser/anonymous contexts."
253
+ );
254
+ }
33
255
  }
34
256
  /**
35
257
  * Set a session token from identity verification.
@@ -246,13 +468,13 @@ var CommunityClient = class {
246
468
  */
247
469
  resolveTeamId() {
248
470
  if (this.teamId) return this.teamId;
249
- throw new Error(
471
+ throw new CStarValidationError(
250
472
  "Team ID is required for authenticated operations. Pass it via setSessionToken(token, teamId) or use ChatClient.identify() which provides it automatically."
251
473
  );
252
474
  }
253
475
  async authenticatedFetch(path, options) {
254
476
  if (!this.sessionToken) {
255
- throw new Error(
477
+ throw new CStarValidationError(
256
478
  "Session token required. Call setSessionToken() or use ChatClient.identify() first."
257
479
  );
258
480
  }
@@ -275,9 +497,8 @@ var CommunityClient = class {
275
497
  body: options.body ? JSON.stringify(options.body) : void 0
276
498
  });
277
499
  if (!response.ok) {
278
- const errorBody = await response.json().catch(() => ({ error: { message: "Request failed" } }));
279
- const message = typeof errorBody.error === "string" ? errorBody.error : errorBody.error?.message ?? `Community API error: ${response.status}`;
280
- throw new Error(message);
500
+ const errorBody = await response.json().catch(() => null);
501
+ throw parsePublicError(response.status, errorBody, `Community API error: ${response.status}`);
281
502
  }
282
503
  return response.json();
283
504
  }
@@ -291,14 +512,17 @@ var CommunityClient = class {
291
512
  const qs = params.toString();
292
513
  if (qs) url += `?${qs}`;
293
514
  }
515
+ const headers = { "Content-Type": "application/json" };
516
+ if (this.publishableKey) {
517
+ headers["Authorization"] = `Bearer ${this.publishableKey}`;
518
+ }
294
519
  const response = await globalThis.fetch(url, {
295
520
  method: "GET",
296
- headers: { "Content-Type": "application/json" }
521
+ headers
297
522
  });
298
523
  if (!response.ok) {
299
- const errorBody = await response.json().catch(() => ({ error: { message: "Request failed" } }));
300
- const message = typeof errorBody.error === "string" ? errorBody.error : errorBody.error?.message ?? `Community API error: ${response.status}`;
301
- throw new Error(message);
524
+ const errorBody = await response.json().catch(() => null);
525
+ throw parsePublicError(response.status, errorBody, `Community API error: ${response.status}`);
302
526
  }
303
527
  return response.json();
304
528
  }
@@ -9,6 +9,14 @@ interface CommunityAttachment {
9
9
  interface CommunityConfig {
10
10
  /** Team slug (e.g., 'acme'). Identifies the community to query. */
11
11
  teamSlug: string;
12
+ /**
13
+ * Optional publishable key (`pk_test_*` or `pk_live_*`). When provided,
14
+ * the client switches data scope to match the key's mode (test vs live).
15
+ * Without a key, defaults to live — backwards-compatible with existing
16
+ * widget integrations. See `LibraryConfig.publishableKey` for the full
17
+ * test/live story.
18
+ */
19
+ publishableKey?: string;
12
20
  /** Override the base URL (defaults to https://www.cstar.help). */
13
21
  baseUrl?: string;
14
22
  }
@@ -193,6 +201,9 @@ interface CommunityPostDetail {
193
201
  declare class CommunityClient {
194
202
  private readonly baseUrl;
195
203
  private readonly teamSlug;
204
+ private readonly publishableKey;
205
+ /** Resolved mode based on the publishable-key prefix (`pk_test_*` => `test`). */
206
+ readonly mode: 'live' | 'test';
196
207
  private sessionToken;
197
208
  private teamId;
198
209
  constructor(config: CommunityConfig);
@@ -9,6 +9,14 @@ interface CommunityAttachment {
9
9
  interface CommunityConfig {
10
10
  /** Team slug (e.g., 'acme'). Identifies the community to query. */
11
11
  teamSlug: string;
12
+ /**
13
+ * Optional publishable key (`pk_test_*` or `pk_live_*`). When provided,
14
+ * the client switches data scope to match the key's mode (test vs live).
15
+ * Without a key, defaults to live — backwards-compatible with existing
16
+ * widget integrations. See `LibraryConfig.publishableKey` for the full
17
+ * test/live story.
18
+ */
19
+ publishableKey?: string;
12
20
  /** Override the base URL (defaults to https://www.cstar.help). */
13
21
  baseUrl?: string;
14
22
  }
@@ -193,6 +201,9 @@ interface CommunityPostDetail {
193
201
  declare class CommunityClient {
194
202
  private readonly baseUrl;
195
203
  private readonly teamSlug;
204
+ private readonly publishableKey;
205
+ /** Resolved mode based on the publishable-key prefix (`pk_test_*` => `test`). */
206
+ readonly mode: 'live' | 'test';
196
207
  private sessionToken;
197
208
  private teamId;
198
209
  constructor(config: CommunityConfig);