@cstar.help/js 0.17.5 → 0.18.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.
- package/README.md +27 -25
- package/dist/auth/index.cjs +220 -21
- package/dist/auth/index.d.cts +83 -31
- package/dist/auth/index.d.ts +83 -31
- package/dist/auth/index.js +210 -21
- package/dist/chat/index.cjs +131 -20
- package/dist/chat/index.d.cts +32 -4
- package/dist/chat/index.d.ts +32 -4
- package/dist/chat/index.js +131 -20
- package/dist/index.cjs +89 -38
- package/dist/index.d.cts +443 -150
- package/dist/index.d.ts +443 -150
- package/dist/index.js +89 -38
- package/dist/proactive/index.cjs +46 -33
- package/dist/proactive/index.js +45 -34
- package/dist/webhook/index.d.cts +1 -1
- package/dist/webhook/index.d.ts +1 -1
- package/dist/webhook/node.d.cts +1 -1
- package/dist/webhook/node.d.ts +1 -1
- package/dist/{webhookEvents-1N2i2GcQ.d.cts → webhookEvents-Lqpsj935.d.cts} +0 -1
- package/dist/{webhookEvents-1N2i2GcQ.d.ts → webhookEvents-Lqpsj935.d.ts} +0 -1
- package/package.json +1 -1
package/dist/chat/index.d.cts
CHANGED
|
@@ -28,6 +28,11 @@ type ChatAuthMode = 'hmac' | 'session';
|
|
|
28
28
|
interface LoginWithSessionParams {
|
|
29
29
|
/** Supabase access token (JWT). Used as Bearer for every subsequent call. */
|
|
30
30
|
accessToken: string;
|
|
31
|
+
/**
|
|
32
|
+
* Optional: the matching refresh token. When supplied, the client renews
|
|
33
|
+
* an expired access token through `/api/widget/auth/refresh` on a 401.
|
|
34
|
+
*/
|
|
35
|
+
refreshToken?: string;
|
|
31
36
|
/** Optional: the team UUID. If omitted, derived server-side from the token. */
|
|
32
37
|
teamId?: string;
|
|
33
38
|
/** Optional: a hint for the customer name (UI only — server stays authoritative). */
|
|
@@ -132,6 +137,8 @@ interface TicketListResponse {
|
|
|
132
137
|
/** A message on a ticket. */
|
|
133
138
|
interface Message {
|
|
134
139
|
id: string;
|
|
140
|
+
/** Opaque send identity, used to reconcile a reply after an uncertain response. */
|
|
141
|
+
client_request_key?: string | null;
|
|
135
142
|
content: string;
|
|
136
143
|
sender: 'customer' | 'agent';
|
|
137
144
|
sender_name: string;
|
|
@@ -144,6 +151,12 @@ interface Message {
|
|
|
144
151
|
/** Whether this message was sent by an AI agent. */
|
|
145
152
|
is_bot?: boolean;
|
|
146
153
|
}
|
|
154
|
+
/** Options for one logical customer reply, including any retries. */
|
|
155
|
+
interface MessageSendOptions {
|
|
156
|
+
/** Retain for a manual retry of the same ticket, content and attachments. Use a new key for each intentional send. Receipts last 24 hours. */
|
|
157
|
+
idempotencyKey?: string;
|
|
158
|
+
signal?: AbortSignal;
|
|
159
|
+
}
|
|
147
160
|
/** A confirmed customer's message attachment. Never a Storage path or URL. */
|
|
148
161
|
interface CustomerAttachmentReference {
|
|
149
162
|
readonly ticketId: string;
|
|
@@ -170,6 +183,7 @@ interface TicketListParams {
|
|
|
170
183
|
}
|
|
171
184
|
/** Parameters for creating a new ticket. */
|
|
172
185
|
interface TicketCreateParams {
|
|
186
|
+
/** Conversation subject. Subjects over 255 characters are shortened with an ellipsis, never rejected. */
|
|
173
187
|
subject: string;
|
|
174
188
|
message?: string;
|
|
175
189
|
}
|
|
@@ -231,6 +245,8 @@ declare class ChatClientError extends Error {
|
|
|
231
245
|
readonly code: string;
|
|
232
246
|
readonly statusCode: number;
|
|
233
247
|
readonly requestId: string | null;
|
|
248
|
+
/** Customer message send key retained for a manual retry after an uncertain outcome. */
|
|
249
|
+
idempotencyKey?: string;
|
|
234
250
|
constructor(arg: ChatErrorOpts | string);
|
|
235
251
|
}
|
|
236
252
|
/** 401 / 403 — auth failed (bad/expired token, wrong team, etc.). */
|
|
@@ -316,6 +332,8 @@ declare class ChatClient {
|
|
|
316
332
|
private readonly pollingInterval;
|
|
317
333
|
private readonly realtimeEnabled;
|
|
318
334
|
private sessionToken;
|
|
335
|
+
private refreshToken;
|
|
336
|
+
private refreshPromise;
|
|
319
337
|
private teamId;
|
|
320
338
|
private customerId;
|
|
321
339
|
private authMode;
|
|
@@ -429,7 +447,14 @@ declare class ChatClient {
|
|
|
429
447
|
signal?: AbortSignal;
|
|
430
448
|
cache?: RequestCache;
|
|
431
449
|
readBody?: (response: Response) => Promise<T>;
|
|
432
|
-
|
|
450
|
+
idempotencyKey?: string;
|
|
451
|
+
}, retriedAfterRefresh?: boolean): Promise<T>;
|
|
452
|
+
/**
|
|
453
|
+
* Renew the access token with the stored refresh token. Concurrent 401s
|
|
454
|
+
* share one refresh. Resolves true when a newer token is in place.
|
|
455
|
+
*/
|
|
456
|
+
private refreshSession;
|
|
457
|
+
private requestRefresh;
|
|
433
458
|
/**
|
|
434
459
|
* Look up the team UUID from the configured `teamSlug`. Used by
|
|
435
460
|
* `loginWithCredentials`. HMAC integrators (`identify()`) never hit
|
|
@@ -501,8 +526,11 @@ declare class MessagesApi {
|
|
|
501
526
|
constructor(chat: ChatClient);
|
|
502
527
|
/** List messages for a ticket. */
|
|
503
528
|
list(ticketId: string): Promise<Message[]>;
|
|
504
|
-
/**
|
|
505
|
-
|
|
529
|
+
/**
|
|
530
|
+
* Send one logical reply. Network and server failures retry twice with the
|
|
531
|
+
* same key. A separate send call is a new intent unless a retained key is supplied.
|
|
532
|
+
*/
|
|
533
|
+
send(ticketId: string, content: string, options?: MessageSendOptions): Promise<Message>;
|
|
506
534
|
}
|
|
507
535
|
|
|
508
|
-
export { type AttachmentDownloadOptions, type ChatAuthMode, ChatClient, ChatClientAuthError, ChatClientError, ChatClientNetworkError, ChatClientRateLimitError, ChatClientServerError, type ChatConfig, type ChatStateListener, type CustomerAttachmentReference, type DownloadedAttachment, 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 };
|
|
536
|
+
export { type AttachmentDownloadOptions, type ChatAuthMode, ChatClient, ChatClientAuthError, ChatClientError, ChatClientNetworkError, ChatClientRateLimitError, ChatClientServerError, type ChatConfig, type ChatStateListener, type CustomerAttachmentReference, type DownloadedAttachment, type IdentifyParams, type IdentifyResult, type LoginWithCredentialsParams, type LoginWithCredentialsResult, type LoginWithSessionParams, type Message, type MessageCallback, type MessageSendOptions, type RealtimeTokenResponse, type Ticket, type TicketCreateParams, type TicketListParams, type TicketListResponse, type TypingCallback, type TypingPayload };
|
package/dist/chat/index.d.ts
CHANGED
|
@@ -28,6 +28,11 @@ type ChatAuthMode = 'hmac' | 'session';
|
|
|
28
28
|
interface LoginWithSessionParams {
|
|
29
29
|
/** Supabase access token (JWT). Used as Bearer for every subsequent call. */
|
|
30
30
|
accessToken: string;
|
|
31
|
+
/**
|
|
32
|
+
* Optional: the matching refresh token. When supplied, the client renews
|
|
33
|
+
* an expired access token through `/api/widget/auth/refresh` on a 401.
|
|
34
|
+
*/
|
|
35
|
+
refreshToken?: string;
|
|
31
36
|
/** Optional: the team UUID. If omitted, derived server-side from the token. */
|
|
32
37
|
teamId?: string;
|
|
33
38
|
/** Optional: a hint for the customer name (UI only — server stays authoritative). */
|
|
@@ -132,6 +137,8 @@ interface TicketListResponse {
|
|
|
132
137
|
/** A message on a ticket. */
|
|
133
138
|
interface Message {
|
|
134
139
|
id: string;
|
|
140
|
+
/** Opaque send identity, used to reconcile a reply after an uncertain response. */
|
|
141
|
+
client_request_key?: string | null;
|
|
135
142
|
content: string;
|
|
136
143
|
sender: 'customer' | 'agent';
|
|
137
144
|
sender_name: string;
|
|
@@ -144,6 +151,12 @@ interface Message {
|
|
|
144
151
|
/** Whether this message was sent by an AI agent. */
|
|
145
152
|
is_bot?: boolean;
|
|
146
153
|
}
|
|
154
|
+
/** Options for one logical customer reply, including any retries. */
|
|
155
|
+
interface MessageSendOptions {
|
|
156
|
+
/** Retain for a manual retry of the same ticket, content and attachments. Use a new key for each intentional send. Receipts last 24 hours. */
|
|
157
|
+
idempotencyKey?: string;
|
|
158
|
+
signal?: AbortSignal;
|
|
159
|
+
}
|
|
147
160
|
/** A confirmed customer's message attachment. Never a Storage path or URL. */
|
|
148
161
|
interface CustomerAttachmentReference {
|
|
149
162
|
readonly ticketId: string;
|
|
@@ -170,6 +183,7 @@ interface TicketListParams {
|
|
|
170
183
|
}
|
|
171
184
|
/** Parameters for creating a new ticket. */
|
|
172
185
|
interface TicketCreateParams {
|
|
186
|
+
/** Conversation subject. Subjects over 255 characters are shortened with an ellipsis, never rejected. */
|
|
173
187
|
subject: string;
|
|
174
188
|
message?: string;
|
|
175
189
|
}
|
|
@@ -231,6 +245,8 @@ declare class ChatClientError extends Error {
|
|
|
231
245
|
readonly code: string;
|
|
232
246
|
readonly statusCode: number;
|
|
233
247
|
readonly requestId: string | null;
|
|
248
|
+
/** Customer message send key retained for a manual retry after an uncertain outcome. */
|
|
249
|
+
idempotencyKey?: string;
|
|
234
250
|
constructor(arg: ChatErrorOpts | string);
|
|
235
251
|
}
|
|
236
252
|
/** 401 / 403 — auth failed (bad/expired token, wrong team, etc.). */
|
|
@@ -316,6 +332,8 @@ declare class ChatClient {
|
|
|
316
332
|
private readonly pollingInterval;
|
|
317
333
|
private readonly realtimeEnabled;
|
|
318
334
|
private sessionToken;
|
|
335
|
+
private refreshToken;
|
|
336
|
+
private refreshPromise;
|
|
319
337
|
private teamId;
|
|
320
338
|
private customerId;
|
|
321
339
|
private authMode;
|
|
@@ -429,7 +447,14 @@ declare class ChatClient {
|
|
|
429
447
|
signal?: AbortSignal;
|
|
430
448
|
cache?: RequestCache;
|
|
431
449
|
readBody?: (response: Response) => Promise<T>;
|
|
432
|
-
|
|
450
|
+
idempotencyKey?: string;
|
|
451
|
+
}, retriedAfterRefresh?: boolean): Promise<T>;
|
|
452
|
+
/**
|
|
453
|
+
* Renew the access token with the stored refresh token. Concurrent 401s
|
|
454
|
+
* share one refresh. Resolves true when a newer token is in place.
|
|
455
|
+
*/
|
|
456
|
+
private refreshSession;
|
|
457
|
+
private requestRefresh;
|
|
433
458
|
/**
|
|
434
459
|
* Look up the team UUID from the configured `teamSlug`. Used by
|
|
435
460
|
* `loginWithCredentials`. HMAC integrators (`identify()`) never hit
|
|
@@ -501,8 +526,11 @@ declare class MessagesApi {
|
|
|
501
526
|
constructor(chat: ChatClient);
|
|
502
527
|
/** List messages for a ticket. */
|
|
503
528
|
list(ticketId: string): Promise<Message[]>;
|
|
504
|
-
/**
|
|
505
|
-
|
|
529
|
+
/**
|
|
530
|
+
* Send one logical reply. Network and server failures retry twice with the
|
|
531
|
+
* same key. A separate send call is a new intent unless a retained key is supplied.
|
|
532
|
+
*/
|
|
533
|
+
send(ticketId: string, content: string, options?: MessageSendOptions): Promise<Message>;
|
|
506
534
|
}
|
|
507
535
|
|
|
508
|
-
export { type AttachmentDownloadOptions, type ChatAuthMode, ChatClient, ChatClientAuthError, ChatClientError, ChatClientNetworkError, ChatClientRateLimitError, ChatClientServerError, type ChatConfig, type ChatStateListener, type CustomerAttachmentReference, type DownloadedAttachment, 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 };
|
|
536
|
+
export { type AttachmentDownloadOptions, type ChatAuthMode, ChatClient, ChatClientAuthError, ChatClientError, ChatClientNetworkError, ChatClientRateLimitError, ChatClientServerError, type ChatConfig, type ChatStateListener, type CustomerAttachmentReference, type DownloadedAttachment, type IdentifyParams, type IdentifyResult, type LoginWithCredentialsParams, type LoginWithCredentialsResult, type LoginWithSessionParams, type Message, type MessageCallback, type MessageSendOptions, type RealtimeTokenResponse, type Ticket, type TicketCreateParams, type TicketListParams, type TicketListResponse, type TypingCallback, type TypingPayload };
|
package/dist/chat/index.js
CHANGED
|
@@ -434,6 +434,8 @@ var DEFAULT_POLLING_INTERVAL = 3e3;
|
|
|
434
434
|
var ChatClient = class {
|
|
435
435
|
constructor(config) {
|
|
436
436
|
this.sessionToken = null;
|
|
437
|
+
this.refreshToken = null;
|
|
438
|
+
this.refreshPromise = null;
|
|
437
439
|
this.teamId = null;
|
|
438
440
|
this.customerId = null;
|
|
439
441
|
this.authMode = null;
|
|
@@ -526,6 +528,7 @@ var ChatClient = class {
|
|
|
526
528
|
this.applySession(
|
|
527
529
|
{
|
|
528
530
|
accessToken: response.accessToken,
|
|
531
|
+
refreshToken: response.refreshToken ?? void 0,
|
|
529
532
|
teamId,
|
|
530
533
|
customerName: response.customer.name ?? response.customer.email
|
|
531
534
|
},
|
|
@@ -556,6 +559,7 @@ var ChatClient = class {
|
|
|
556
559
|
applySession(params, generation) {
|
|
557
560
|
this.assertSession(generation);
|
|
558
561
|
this.sessionToken = params.accessToken;
|
|
562
|
+
this.refreshToken = params.refreshToken ?? null;
|
|
559
563
|
if (params.teamId) {
|
|
560
564
|
this.teamId = params.teamId;
|
|
561
565
|
}
|
|
@@ -665,6 +669,8 @@ var ChatClient = class {
|
|
|
665
669
|
this.pollingSubscriptions.clear();
|
|
666
670
|
this.realtimeMessageSubscriptions.clear();
|
|
667
671
|
this.sessionToken = null;
|
|
672
|
+
this.refreshToken = null;
|
|
673
|
+
this.refreshPromise = null;
|
|
668
674
|
this.teamId = null;
|
|
669
675
|
this.customerId = null;
|
|
670
676
|
this.authMode = null;
|
|
@@ -696,7 +702,7 @@ var ChatClient = class {
|
|
|
696
702
|
* the first authenticated round-trip, `getAuthMode()` returns the
|
|
697
703
|
* server-confirmed mode.
|
|
698
704
|
*/
|
|
699
|
-
async fetch(path, options) {
|
|
705
|
+
async fetch(path, options, retriedAfterRefresh = false) {
|
|
700
706
|
const generation = this.sessionGeneration;
|
|
701
707
|
const assertRequest = () => {
|
|
702
708
|
this.assertSession(generation);
|
|
@@ -707,8 +713,10 @@ var ChatClient = class {
|
|
|
707
713
|
const headers = {
|
|
708
714
|
"Content-Type": "application/json"
|
|
709
715
|
};
|
|
710
|
-
if (
|
|
711
|
-
|
|
716
|
+
if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
|
|
717
|
+
const sentToken = this.sessionToken;
|
|
718
|
+
if (sentToken) {
|
|
719
|
+
headers["Authorization"] = `Bearer ${sentToken}`;
|
|
712
720
|
}
|
|
713
721
|
let response;
|
|
714
722
|
try {
|
|
@@ -736,11 +744,17 @@ var ChatClient = class {
|
|
|
736
744
|
}
|
|
737
745
|
assertRequest();
|
|
738
746
|
const requestId = responseHeaders?.get("X-Request-Id") ?? null;
|
|
747
|
+
if (response.status === 401 && sentToken && this.refreshToken && !retriedAfterRefresh) {
|
|
748
|
+
const refreshed = await this.refreshSession(sentToken);
|
|
749
|
+
assertRequest();
|
|
750
|
+
if (refreshed) return this.fetch(path, options, true);
|
|
751
|
+
}
|
|
739
752
|
if (!response.ok) {
|
|
740
|
-
const errorBody = await response.json().catch(() =>
|
|
753
|
+
const errorBody = await response.json().catch(() => null);
|
|
741
754
|
assertRequest();
|
|
742
|
-
const
|
|
743
|
-
const
|
|
755
|
+
const structured = errorBody?.error && typeof errorBody.error === "object" ? errorBody.error : null;
|
|
756
|
+
const code = structured?.code ?? "UNKNOWN";
|
|
757
|
+
const message = structured?.message ?? (typeof errorBody?.error === "string" ? errorBody.error : null) ?? (typeof errorBody?.message === "string" ? errorBody.message : null) ?? `Widget API error: ${response.status}`;
|
|
744
758
|
if (response.status === 401 || response.status === 403) {
|
|
745
759
|
throw new ChatClientAuthError({
|
|
746
760
|
message,
|
|
@@ -781,9 +795,52 @@ var ChatClient = class {
|
|
|
781
795
|
return body;
|
|
782
796
|
} catch (error) {
|
|
783
797
|
assertRequest();
|
|
798
|
+
if (options.idempotencyKey)
|
|
799
|
+
throw new ChatClientNetworkError({
|
|
800
|
+
message: "Could not read the message acknowledgment",
|
|
801
|
+
originalError: error
|
|
802
|
+
});
|
|
784
803
|
throw error;
|
|
785
804
|
}
|
|
786
805
|
}
|
|
806
|
+
/**
|
|
807
|
+
* Renew the access token with the stored refresh token. Concurrent 401s
|
|
808
|
+
* share one refresh. Resolves true when a newer token is in place.
|
|
809
|
+
*/
|
|
810
|
+
refreshSession(rejectedToken) {
|
|
811
|
+
if (this.sessionToken && this.sessionToken !== rejectedToken) return Promise.resolve(true);
|
|
812
|
+
if (!this.refreshPromise) {
|
|
813
|
+
const pending = this.requestRefresh().finally(() => {
|
|
814
|
+
if (this.refreshPromise === pending) this.refreshPromise = null;
|
|
815
|
+
});
|
|
816
|
+
this.refreshPromise = pending;
|
|
817
|
+
}
|
|
818
|
+
return this.refreshPromise;
|
|
819
|
+
}
|
|
820
|
+
async requestRefresh() {
|
|
821
|
+
const generation = this.sessionGeneration;
|
|
822
|
+
const refreshToken = this.refreshToken;
|
|
823
|
+
if (!refreshToken) return false;
|
|
824
|
+
try {
|
|
825
|
+
const response = await globalThis.fetch(`${this.baseUrl}/api/widget/auth/refresh`, {
|
|
826
|
+
method: "POST",
|
|
827
|
+
headers: { "Content-Type": "application/json" },
|
|
828
|
+
body: JSON.stringify({ refreshToken })
|
|
829
|
+
});
|
|
830
|
+
if (generation !== this.sessionGeneration) return false;
|
|
831
|
+
if (!response.ok) {
|
|
832
|
+
if (response.status === 401) this.refreshToken = null;
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
const body = await response.json();
|
|
836
|
+
if (generation !== this.sessionGeneration || !body.accessToken) return false;
|
|
837
|
+
this.sessionToken = body.accessToken;
|
|
838
|
+
this.refreshToken = body.refreshToken ?? refreshToken;
|
|
839
|
+
return true;
|
|
840
|
+
} catch {
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
787
844
|
/**
|
|
788
845
|
* Look up the team UUID from the configured `teamSlug`. Used by
|
|
789
846
|
* `loginWithCredentials`. HMAC integrators (`identify()`) never hit
|
|
@@ -1256,26 +1313,80 @@ var MessagesApi = class {
|
|
|
1256
1313
|
assertCurrent();
|
|
1257
1314
|
return response.data.messages;
|
|
1258
1315
|
}
|
|
1259
|
-
/**
|
|
1260
|
-
|
|
1316
|
+
/**
|
|
1317
|
+
* Send one logical reply. Network and server failures retry twice with the
|
|
1318
|
+
* same key. A separate send call is a new intent unless a retained key is supplied.
|
|
1319
|
+
*/
|
|
1320
|
+
async send(ticketId, content, options = {}) {
|
|
1261
1321
|
const assertCurrent = this.chat.captureSession();
|
|
1322
|
+
options.signal?.throwIfAborted();
|
|
1323
|
+
const key = options.idempotencyKey ?? createMessageRequestKey();
|
|
1324
|
+
if (!key || key.length > 250 || key.trim() !== key || /[^\x20-\x7e]/.test(key))
|
|
1325
|
+
throw new ChatClientError({
|
|
1326
|
+
code: "INVALID_IDEMPOTENCY_KEY",
|
|
1327
|
+
message: "Idempotency-Key must contain 1 to 250 printable characters, without surrounding whitespace."
|
|
1328
|
+
});
|
|
1262
1329
|
const teamId = await this.chat.ensureTeamId();
|
|
1263
1330
|
assertCurrent();
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1331
|
+
for (let attempt = 0; ; attempt++) {
|
|
1332
|
+
assertCurrent();
|
|
1333
|
+
options.signal?.throwIfAborted();
|
|
1334
|
+
try {
|
|
1335
|
+
const response = await this.chat.fetch(
|
|
1336
|
+
"/api/widget/messages",
|
|
1337
|
+
{
|
|
1338
|
+
method: "POST",
|
|
1339
|
+
body: { teamId, ticketId, content },
|
|
1340
|
+
idempotencyKey: key,
|
|
1341
|
+
signal: options.signal
|
|
1342
|
+
}
|
|
1343
|
+
);
|
|
1344
|
+
assertCurrent();
|
|
1345
|
+
if (typeof response?.data?.message?.id !== "string" || !response.data.message.id)
|
|
1346
|
+
throw new ChatClientNetworkError({
|
|
1347
|
+
message: "Message acknowledgment was incomplete",
|
|
1348
|
+
originalError: new Error(
|
|
1349
|
+
"The reply may already be saved. Retry with the same send identity."
|
|
1350
|
+
)
|
|
1351
|
+
});
|
|
1352
|
+
return response.data.message;
|
|
1353
|
+
} catch (error) {
|
|
1354
|
+
assertCurrent();
|
|
1355
|
+
options.signal?.throwIfAborted();
|
|
1356
|
+
if (error instanceof ChatClientError) error.idempotencyKey = key;
|
|
1357
|
+
if (attempt >= 2 || !(error instanceof ChatClientNetworkError || error instanceof ChatClientServerError))
|
|
1358
|
+
throw error;
|
|
1359
|
+
await waitForMessageRetry(250 * 2 ** attempt, options.signal);
|
|
1273
1360
|
}
|
|
1274
|
-
|
|
1275
|
-
assertCurrent();
|
|
1276
|
-
return response.data.message;
|
|
1361
|
+
}
|
|
1277
1362
|
}
|
|
1278
1363
|
};
|
|
1364
|
+
function createMessageRequestKey() {
|
|
1365
|
+
if (typeof globalThis.crypto?.randomUUID === "function") return globalThis.crypto.randomUUID();
|
|
1366
|
+
if (typeof globalThis.crypto?.getRandomValues !== "function")
|
|
1367
|
+
throw new ChatClientError({
|
|
1368
|
+
code: "RANDOM_UNAVAILABLE",
|
|
1369
|
+
message: "Secure random values are required to send a message safely."
|
|
1370
|
+
});
|
|
1371
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
1372
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1373
|
+
}
|
|
1374
|
+
function waitForMessageRetry(delay, signal) {
|
|
1375
|
+
signal?.throwIfAborted();
|
|
1376
|
+
return new Promise((resolve, reject) => {
|
|
1377
|
+
const finish = () => {
|
|
1378
|
+
signal?.removeEventListener("abort", abort);
|
|
1379
|
+
resolve();
|
|
1380
|
+
};
|
|
1381
|
+
const timer = setTimeout(finish, delay);
|
|
1382
|
+
const abort = () => {
|
|
1383
|
+
clearTimeout(timer);
|
|
1384
|
+
signal?.removeEventListener("abort", abort);
|
|
1385
|
+
reject(signal?.reason);
|
|
1386
|
+
};
|
|
1387
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1279
1390
|
function decodeJwtSub(jwt) {
|
|
1280
1391
|
try {
|
|
1281
1392
|
const parts = jwt.split(".");
|
package/dist/index.cjs
CHANGED
|
@@ -278,7 +278,7 @@ function parseError(statusCode, body, retryAfter) {
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
// src/client.ts
|
|
281
|
-
var SDK_VERSION = "0.
|
|
281
|
+
var SDK_VERSION = "0.18.0";
|
|
282
282
|
var DEFAULT_BASE_URL = "https://www.cstar.help";
|
|
283
283
|
var DEFAULT_API_VERSION = "2026-03-01";
|
|
284
284
|
var DEFAULT_MAX_RETRIES = 3;
|
|
@@ -298,7 +298,7 @@ var HttpClient = class {
|
|
|
298
298
|
this.lastMeta = { requestId: null, timestamp: null, pagination: null };
|
|
299
299
|
if (!config || typeof config.apiKey !== "string" || config.apiKey.length === 0) {
|
|
300
300
|
throw new Error(
|
|
301
|
-
"[cStar] CStarClient: apiKey is required. Pass a secret key (sk_live_... / sk_test_...)
|
|
301
|
+
"[cStar] CStarClient: apiKey is required. Pass a secret key (sk_live_... / sk_test_...). Publishable keys (pk_...) cannot read team endpoints; use them with LibraryClient or CommunityClient. See https://cstar.help/developers/quickstart"
|
|
302
302
|
);
|
|
303
303
|
}
|
|
304
304
|
if (typeof config.teamId !== "string" || config.teamId.length === 0) {
|
|
@@ -530,6 +530,8 @@ var CStarRealtime = class {
|
|
|
530
530
|
this.retryTimer = null;
|
|
531
531
|
this._connected = false;
|
|
532
532
|
this.destroyed = false;
|
|
533
|
+
/** The server closed the stream because the API key was revoked or expired. */
|
|
534
|
+
this.revoked = false;
|
|
533
535
|
this.connecting = false;
|
|
534
536
|
/** Short-lived SSE token (exchanged from secret key). */
|
|
535
537
|
this.sseToken = null;
|
|
@@ -605,7 +607,7 @@ var CStarRealtime = class {
|
|
|
605
607
|
}
|
|
606
608
|
/** Start the SSE connection. Called automatically on first handler registration. */
|
|
607
609
|
connect() {
|
|
608
|
-
if (this.eventSource || this.destroyed || this.connecting) return;
|
|
610
|
+
if (this.eventSource || this.destroyed || this.revoked || this.connecting) return;
|
|
609
611
|
if (!this.resolveEventSource()) {
|
|
610
612
|
throw new CStarRealtimeUnavailableError();
|
|
611
613
|
}
|
|
@@ -665,6 +667,7 @@ var CStarRealtime = class {
|
|
|
665
667
|
this.eventSource.close();
|
|
666
668
|
this.eventSource = null;
|
|
667
669
|
}
|
|
670
|
+
if (this.revoked) return;
|
|
668
671
|
if (this.autoReconnect && !this.destroyed && this.retryCount < this.maxRetries) {
|
|
669
672
|
this.scheduleReconnect();
|
|
670
673
|
} else if (this.retryCount >= this.maxRetries) {
|
|
@@ -674,6 +677,9 @@ var CStarRealtime = class {
|
|
|
674
677
|
this.eventSource.onmessage = (event) => {
|
|
675
678
|
this.handleMessage(event);
|
|
676
679
|
};
|
|
680
|
+
this.eventSource.addEventListener("revoked", (event) => {
|
|
681
|
+
this.handleRevoked(event);
|
|
682
|
+
});
|
|
677
683
|
const knownTypes = [
|
|
678
684
|
"ticket.created",
|
|
679
685
|
"ticket.updated",
|
|
@@ -802,6 +808,40 @@ var CStarRealtime = class {
|
|
|
802
808
|
for (const handler of this.connectionHandlers) handler(value);
|
|
803
809
|
}
|
|
804
810
|
}
|
|
811
|
+
handleRevoked(event) {
|
|
812
|
+
if (this.revoked || this.destroyed) return;
|
|
813
|
+
this.revoked = true;
|
|
814
|
+
let body = {};
|
|
815
|
+
try {
|
|
816
|
+
body = JSON.parse(event.data).error ?? {};
|
|
817
|
+
} catch {
|
|
818
|
+
}
|
|
819
|
+
if (this.retryTimer) {
|
|
820
|
+
clearTimeout(this.retryTimer);
|
|
821
|
+
this.retryTimer = null;
|
|
822
|
+
}
|
|
823
|
+
if (this.tokenRefreshTimer) {
|
|
824
|
+
clearTimeout(this.tokenRefreshTimer);
|
|
825
|
+
this.tokenRefreshTimer = null;
|
|
826
|
+
}
|
|
827
|
+
if (this.eventSource) {
|
|
828
|
+
this.eventSource.close();
|
|
829
|
+
this.eventSource = null;
|
|
830
|
+
}
|
|
831
|
+
this.sseToken = null;
|
|
832
|
+
this.sseTokenExpiresAt = 0;
|
|
833
|
+
this.connecting = false;
|
|
834
|
+
this.setConnected(false);
|
|
835
|
+
this.emitError(
|
|
836
|
+
new CStarAuthenticationError(401, {
|
|
837
|
+
type: body.type ?? "authentication_error",
|
|
838
|
+
code: body.code ?? "invalid_api_key",
|
|
839
|
+
message: body.message ?? "This API key was revoked or has expired, so the stream is closed.",
|
|
840
|
+
doc_url: body.doc_url ?? "https://www.cstar.help/developers/errors#invalid_api_key",
|
|
841
|
+
...body.request_id ? { request_id: body.request_id } : {}
|
|
842
|
+
})
|
|
843
|
+
);
|
|
844
|
+
}
|
|
805
845
|
emitError(error) {
|
|
806
846
|
for (const handler of this.errorHandlers) handler(error);
|
|
807
847
|
}
|
|
@@ -1521,11 +1561,17 @@ var SettingsResource = class {
|
|
|
1521
1561
|
async update(params) {
|
|
1522
1562
|
return this.client.request("PATCH", "/settings", { body: params });
|
|
1523
1563
|
}
|
|
1524
|
-
/**
|
|
1564
|
+
/**
|
|
1565
|
+
* Get the chat widget's online hours (outside them the widget shows its
|
|
1566
|
+
* offline message). Not the SLA working hours that SLA deadlines use.
|
|
1567
|
+
*/
|
|
1525
1568
|
async getBusinessHours() {
|
|
1526
1569
|
return this.client.request("GET", "/settings/business-hours");
|
|
1527
1570
|
}
|
|
1528
|
-
/**
|
|
1571
|
+
/**
|
|
1572
|
+
* Update the chat widget's online hours. SLA deadlines don't change:
|
|
1573
|
+
* they follow the SLA working hours in Settings > Business Hours.
|
|
1574
|
+
*/
|
|
1529
1575
|
async updateBusinessHours(params) {
|
|
1530
1576
|
return this.client.request("PATCH", "/settings/business-hours", {
|
|
1531
1577
|
body: params
|
|
@@ -1550,8 +1596,10 @@ var NotificationsResource = class {
|
|
|
1550
1596
|
}
|
|
1551
1597
|
/** List notifications for a user. */
|
|
1552
1598
|
async list(params) {
|
|
1599
|
+
const { unreadOnly, ...query } = params;
|
|
1600
|
+
if (unreadOnly && query.isRead === void 0) query.isRead = false;
|
|
1553
1601
|
return this.client.request("GET", "/notifications", {
|
|
1554
|
-
query
|
|
1602
|
+
query
|
|
1555
1603
|
});
|
|
1556
1604
|
}
|
|
1557
1605
|
/** Get unread notification count for a user. */
|
|
@@ -1625,15 +1673,22 @@ var AttachmentsResource = class {
|
|
|
1625
1673
|
constructor(client) {
|
|
1626
1674
|
this.client = client;
|
|
1627
1675
|
}
|
|
1628
|
-
/** List
|
|
1676
|
+
/** List message files, each with a short-lived signed `url`. */
|
|
1629
1677
|
async list(params) {
|
|
1630
1678
|
return this.client.request("GET", "/attachments", {
|
|
1631
1679
|
query: params
|
|
1632
1680
|
});
|
|
1633
1681
|
}
|
|
1634
|
-
/**
|
|
1635
|
-
|
|
1636
|
-
|
|
1682
|
+
/**
|
|
1683
|
+
* Get a signed download URL, valid for `expiresIn` seconds, for a stored
|
|
1684
|
+
* file. Pass `storagePath` and `bucket` from `list()`.
|
|
1685
|
+
*/
|
|
1686
|
+
async get(storagePath, params) {
|
|
1687
|
+
return this.client.request(
|
|
1688
|
+
"GET",
|
|
1689
|
+
`/attachments/${storagePath}`,
|
|
1690
|
+
params?.bucket ? { query: { bucket: params.bucket } } : void 0
|
|
1691
|
+
);
|
|
1637
1692
|
}
|
|
1638
1693
|
/** Delete an attachment. */
|
|
1639
1694
|
async del(attachmentId) {
|
|
@@ -1649,16 +1704,12 @@ var CustomFieldsResource = class {
|
|
|
1649
1704
|
constructor(client) {
|
|
1650
1705
|
this.client = client;
|
|
1651
1706
|
}
|
|
1652
|
-
/** List custom
|
|
1707
|
+
/** List custom field definitions, optionally for one record type. There is no single-field GET. */
|
|
1653
1708
|
async list(params) {
|
|
1654
1709
|
return this.client.request("GET", "/custom-fields", {
|
|
1655
1710
|
query: params
|
|
1656
1711
|
});
|
|
1657
1712
|
}
|
|
1658
|
-
/** Get a single custom field. */
|
|
1659
|
-
async get(fieldId) {
|
|
1660
|
-
return this.client.request("GET", `/custom-fields/${fieldId}`);
|
|
1661
|
-
}
|
|
1662
1713
|
/** Create a custom field. */
|
|
1663
1714
|
async create(params, options) {
|
|
1664
1715
|
return this.client.request("POST", "/custom-fields", {
|
|
@@ -1693,11 +1744,9 @@ var SlaResource = class {
|
|
|
1693
1744
|
constructor(client) {
|
|
1694
1745
|
this.client = client;
|
|
1695
1746
|
}
|
|
1696
|
-
/** List SLA rules. */
|
|
1697
|
-
async list(
|
|
1698
|
-
return this.client.request("GET", "/sla/rules"
|
|
1699
|
-
query: params
|
|
1700
|
-
});
|
|
1747
|
+
/** List SLA rules in evaluation order. */
|
|
1748
|
+
async list() {
|
|
1749
|
+
return this.client.request("GET", "/sla/rules");
|
|
1701
1750
|
}
|
|
1702
1751
|
/** Create an SLA rule. */
|
|
1703
1752
|
async create(params, options) {
|
|
@@ -1886,25 +1935,28 @@ var TagsResource = class {
|
|
|
1886
1935
|
constructor(client) {
|
|
1887
1936
|
this.client = client;
|
|
1888
1937
|
}
|
|
1889
|
-
/** List
|
|
1938
|
+
/** List tags with usage counts, one row per tag and entity type. */
|
|
1890
1939
|
async list(params) {
|
|
1891
1940
|
return this.client.request("GET", "/tags", {
|
|
1892
1941
|
query: params
|
|
1893
1942
|
});
|
|
1894
1943
|
}
|
|
1895
|
-
/**
|
|
1896
|
-
async
|
|
1944
|
+
/** Replace `fromTag` with `toTag` on every record that carries it. */
|
|
1945
|
+
async merge(params, options) {
|
|
1897
1946
|
return this.client.request("POST", "/tags", {
|
|
1898
|
-
body: {
|
|
1947
|
+
body: { action: "merge", ...params },
|
|
1899
1948
|
idempotencyKey: options?.idempotencyKey
|
|
1900
1949
|
});
|
|
1901
1950
|
}
|
|
1902
|
-
/**
|
|
1903
|
-
async
|
|
1904
|
-
return this.client.request(
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1951
|
+
/** Remove a tag from every record that carries it. */
|
|
1952
|
+
async remove(tag, params) {
|
|
1953
|
+
return this.client.request("DELETE", "/tags", {
|
|
1954
|
+
body: { tag, ...params }
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
/** @deprecated Use `remove()`. */
|
|
1958
|
+
async del(tag, params) {
|
|
1959
|
+
return this.remove(tag, params);
|
|
1908
1960
|
}
|
|
1909
1961
|
};
|
|
1910
1962
|
|
|
@@ -2350,7 +2402,7 @@ var ViewsResource = class extends BaseResource {
|
|
|
2350
2402
|
constructor(client) {
|
|
2351
2403
|
super(client, "/views");
|
|
2352
2404
|
}
|
|
2353
|
-
/** Execute a view
|
|
2405
|
+
/** Execute a view and return the filtered results. The server uses the view's entity_type to determine which table to query. */
|
|
2354
2406
|
async execute(viewId, params) {
|
|
2355
2407
|
return this.client.request("GET", `/views/${viewId}/execute`, {
|
|
2356
2408
|
query: params
|
|
@@ -2586,7 +2638,7 @@ var WEBHOOK_EVENT_SAMPLES = {
|
|
|
2586
2638
|
"comment": {
|
|
2587
2639
|
"id": "cmt_01H8X9YZ7E0J3K4M5N6X",
|
|
2588
2640
|
"post_id": "post_01H8X9YZ7E0J3K4M5N6W",
|
|
2589
|
-
"content": "I would love this
|
|
2641
|
+
"content": "I would love this. Bumping for visibility.",
|
|
2590
2642
|
"author_id": "cus_01H8X9YZ7E0J3K4M5N6Q",
|
|
2591
2643
|
"created_at": "2026-04-27T18:55:02.000Z"
|
|
2592
2644
|
}
|
|
@@ -2806,15 +2858,14 @@ var WEBHOOK_EVENT_SAMPLES = {
|
|
|
2806
2858
|
"id": "tkt_01H8X9YZ7E0J3K4M5N6P",
|
|
2807
2859
|
"prefixed_id": "CST-1024",
|
|
2808
2860
|
"title": "Cannot reset password",
|
|
2809
|
-
"status": "
|
|
2861
|
+
"status": "closed",
|
|
2810
2862
|
"priority": "normal",
|
|
2811
|
-
"channel": "widget"
|
|
2812
|
-
"resolved_at": "2026-04-27T19:08:43.000Z"
|
|
2863
|
+
"channel": "widget"
|
|
2813
2864
|
},
|
|
2814
2865
|
"changes": {
|
|
2815
2866
|
"status": {
|
|
2816
2867
|
"from": "open",
|
|
2817
|
-
"to": "
|
|
2868
|
+
"to": "closed"
|
|
2818
2869
|
}
|
|
2819
2870
|
}
|
|
2820
2871
|
},
|
|
@@ -2847,7 +2898,7 @@ var WEBHOOK_EVENT_SAMPLES = {
|
|
|
2847
2898
|
"id": "msg_01H8X9YZ7E0J3K4M5N6R",
|
|
2848
2899
|
"sender": "agent",
|
|
2849
2900
|
"sender_name": "Riley",
|
|
2850
|
-
"content": "I just sent a reset link to your inbox
|
|
2901
|
+
"content": "I just sent a reset link to your inbox. Let me know if it lands.",
|
|
2851
2902
|
"created_at": "2026-04-27T18:55:02.000Z"
|
|
2852
2903
|
},
|
|
2853
2904
|
"customer": {
|
|
@@ -2913,7 +2964,7 @@ var WEBHOOK_EVENT_CATALOG = {
|
|
|
2913
2964
|
"survey.submitted": { label: "Customer Feedback Received", description: "Fired when a customer submits a CSAT survey response", category: "csat" },
|
|
2914
2965
|
"team.preferences_updated": { label: "Team Preferences Updated", description: "Fired when team-wide UX preferences change (e.g., bossBattlesEnabled toggled)", category: "team" },
|
|
2915
2966
|
"ticket.assigned": { label: "Ticket Assigned", description: "Fired when a ticket is assigned to an agent", category: "tickets" },
|
|
2916
|
-
"ticket.closed": { label: "Ticket Closed
|
|
2967
|
+
"ticket.closed": { label: "Ticket Closed", description: "Fired when a ticket's status changes to Closed. Moving a ticket to Resolved sends ticket.updated instead.", category: "tickets" },
|
|
2917
2968
|
"ticket.created": { label: "New Ticket Created", description: "Fired when a new ticket is created via app, widget, API, or email", category: "tickets" },
|
|
2918
2969
|
"ticket.deleted": { label: "Ticket Deleted", description: "Fired when a ticket is deleted via app or API", category: "tickets" },
|
|
2919
2970
|
"ticket.message_added": { label: "New Message on Ticket", description: "Fired when a new message is added to a ticket", category: "tickets" },
|