@cstar.help/js 0.11.0 → 0.12.1
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 +58 -6
- package/dist/auth/index.cjs +10 -10
- package/dist/auth/index.d.cts +4 -4
- package/dist/auth/index.d.ts +4 -4
- package/dist/auth/index.js +10 -10
- package/dist/chat/index.cjs +243 -10
- package/dist/chat/index.d.cts +188 -6
- package/dist/chat/index.d.ts +188 -6
- package/dist/chat/index.js +237 -9
- package/dist/quickhelp/index.cjs +102 -0
- package/dist/quickhelp/index.d.cts +97 -0
- package/dist/quickhelp/index.d.ts +97 -0
- package/dist/quickhelp/index.js +76 -0
- package/package.json +11 -1
- package/dist/index.d.cts +0 -2732
- package/dist/index.d.ts +0 -2732
- package/dist/webhook/index.d.cts +0 -163
- package/dist/webhook/index.d.ts +0 -163
package/README.md
CHANGED
|
@@ -200,32 +200,84 @@ const { data: searchResults } = await cstar.search.query({
|
|
|
200
200
|
|
|
201
201
|
Build custom chat experiences in the browser. Uses a publishable key — safe for frontend code.
|
|
202
202
|
|
|
203
|
+
The chat client supports two authentication modes:
|
|
204
|
+
|
|
205
|
+
- **HMAC** — your server signs the customer payload with the team's signing secret. Best for sites where users are already authenticated server-side.
|
|
206
|
+
- **Session** — the customer signs in directly with email/password (or magic link). Best for standalone widgets and customer portals where there's no host-app session to bridge.
|
|
207
|
+
|
|
208
|
+
You don't have to pick the mode up-front. The first response from any cstar endpoint includes an `X-Cstar-Auth-Mode` header, which the SDK caches; `chat.getAuthMode()` returns `'hmac'`, `'session'`, or `null` after the first call.
|
|
209
|
+
|
|
210
|
+
### HMAC
|
|
211
|
+
|
|
203
212
|
```typescript
|
|
204
213
|
import { ChatClient } from '@cstar.help/js/chat';
|
|
205
214
|
|
|
206
215
|
const chat = new ChatClient({ teamSlug: 'acme' });
|
|
207
216
|
|
|
208
|
-
// Identify the customer (HMAC signature from your server)
|
|
209
217
|
await chat.identify(
|
|
210
218
|
{ externalId: 'usr_123', email: 'jane@example.com', name: 'Jane', timestamp: Date.now() / 1000 },
|
|
211
219
|
hmacSignature
|
|
212
220
|
);
|
|
221
|
+
```
|
|
213
222
|
|
|
214
|
-
|
|
215
|
-
|
|
223
|
+
### Session (email/password)
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const chat = new ChatClient({ teamSlug: 'acme' });
|
|
227
|
+
|
|
228
|
+
// Login with credentials — returns the customer + tokens
|
|
229
|
+
const { customer, accessToken, refreshToken } = await chat.loginWithCredentials(
|
|
230
|
+
'jane@example.com',
|
|
231
|
+
'hunter2'
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// Or rehydrate from previously stored tokens
|
|
235
|
+
await chat.loginWithSession(accessToken, refreshToken);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Once authenticated (either mode)
|
|
216
239
|
|
|
217
|
-
|
|
240
|
+
```typescript
|
|
241
|
+
const { tickets } = await chat.tickets.list();
|
|
218
242
|
await chat.messages.send('tkt_abc123', 'Thanks for the help!');
|
|
219
243
|
|
|
220
|
-
// Subscribe to new messages
|
|
221
244
|
const unsubscribe = chat.onMessage('tkt_abc123', (message) => {
|
|
222
245
|
console.log(`${message.sender_name}: ${message.content}`);
|
|
223
246
|
});
|
|
224
247
|
|
|
225
|
-
// Clean up
|
|
226
248
|
chat.disconnect();
|
|
227
249
|
```
|
|
228
250
|
|
|
251
|
+
### Typed errors
|
|
252
|
+
|
|
253
|
+
Every `ChatClient` request throws a typed subclass of `ChatClientError` on failure. Each carries a `requestId` (from the `X-Request-Id` response header) so customer support can correlate against server logs.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import {
|
|
257
|
+
ChatClientError,
|
|
258
|
+
ChatClientAuthError,
|
|
259
|
+
ChatClientRateLimitError,
|
|
260
|
+
ChatClientServerError,
|
|
261
|
+
ChatClientNetworkError
|
|
262
|
+
} from '@cstar.help/js/chat';
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
await chat.identify(customer, signature);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
if (err instanceof ChatClientAuthError) {
|
|
268
|
+
// 401 / 403 — bad signature, expired token, wrong team
|
|
269
|
+
} else if (err instanceof ChatClientRateLimitError) {
|
|
270
|
+
// 429 — back off for err.retryAfter seconds
|
|
271
|
+
} else if (err instanceof ChatClientServerError) {
|
|
272
|
+
// 5xx — usually transient, retry with backoff
|
|
273
|
+
} else if (err instanceof ChatClientNetworkError) {
|
|
274
|
+
// fetch() itself failed — DNS, offline, CORS — see err.originalError
|
|
275
|
+
} else if (err instanceof ChatClientError) {
|
|
276
|
+
console.log('Error', err.code, err.statusCode, 'requestId:', err.requestId);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
229
281
|
---
|
|
230
282
|
|
|
231
283
|
## Client-Side: Public Knowledge Base
|
package/dist/auth/index.cjs
CHANGED
|
@@ -40,22 +40,22 @@ var AuthClient = class {
|
|
|
40
40
|
*/
|
|
41
41
|
async signup(params) {
|
|
42
42
|
const response = await this.fetch(
|
|
43
|
-
"/widget/
|
|
43
|
+
"/api/widget/auth/signup",
|
|
44
44
|
{
|
|
45
45
|
method: "POST",
|
|
46
46
|
body: {
|
|
47
|
-
|
|
47
|
+
teamId: this.teamSlug,
|
|
48
48
|
email: params.email,
|
|
49
49
|
password: params.password,
|
|
50
50
|
name: params.name
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
);
|
|
54
|
-
if ("
|
|
54
|
+
if ("accessToken" in response && response.accessToken && response.customer) {
|
|
55
55
|
this.session = {
|
|
56
56
|
customer: response.customer,
|
|
57
|
-
accessToken: response.
|
|
58
|
-
refreshToken: response.
|
|
57
|
+
accessToken: response.accessToken,
|
|
58
|
+
refreshToken: response.refreshToken
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
return response;
|
|
@@ -69,19 +69,19 @@ var AuthClient = class {
|
|
|
69
69
|
* @throws Error on invalid credentials, unconfirmed email, or server error
|
|
70
70
|
*/
|
|
71
71
|
async login(params) {
|
|
72
|
-
const response = await this.fetch("/widget/
|
|
72
|
+
const response = await this.fetch("/api/widget/auth/login", {
|
|
73
73
|
method: "POST",
|
|
74
74
|
body: {
|
|
75
|
-
|
|
75
|
+
teamId: this.teamSlug,
|
|
76
76
|
email: params.email,
|
|
77
77
|
password: params.password
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
|
-
if (response.
|
|
80
|
+
if (response.accessToken && response.customer) {
|
|
81
81
|
this.session = {
|
|
82
82
|
customer: response.customer,
|
|
83
|
-
accessToken: response.
|
|
84
|
-
refreshToken: response.
|
|
83
|
+
accessToken: response.accessToken,
|
|
84
|
+
refreshToken: response.refreshToken
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
return response;
|
package/dist/auth/index.d.cts
CHANGED
|
@@ -24,9 +24,9 @@ interface SignupParams {
|
|
|
24
24
|
interface SignupResult {
|
|
25
25
|
customer: AuthCustomer;
|
|
26
26
|
/** Access token for authenticated requests. Null if email confirmation is required. */
|
|
27
|
-
|
|
27
|
+
accessToken: string | null;
|
|
28
28
|
/** Refresh token for renewing the session. Null if email confirmation is required. */
|
|
29
|
-
|
|
29
|
+
refreshToken: string | null;
|
|
30
30
|
}
|
|
31
31
|
/** Response when email confirmation is required after signup. */
|
|
32
32
|
interface SignupConfirmationResult {
|
|
@@ -45,9 +45,9 @@ interface LoginParams {
|
|
|
45
45
|
interface LoginResult {
|
|
46
46
|
customer: AuthCustomer;
|
|
47
47
|
/** Access token for authenticated requests. */
|
|
48
|
-
|
|
48
|
+
accessToken: string | null;
|
|
49
49
|
/** Refresh token for renewing the session. */
|
|
50
|
-
|
|
50
|
+
refreshToken: string | null;
|
|
51
51
|
}
|
|
52
52
|
/** Stored session with tokens and customer identity. */
|
|
53
53
|
interface AuthSession {
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -24,9 +24,9 @@ interface SignupParams {
|
|
|
24
24
|
interface SignupResult {
|
|
25
25
|
customer: AuthCustomer;
|
|
26
26
|
/** Access token for authenticated requests. Null if email confirmation is required. */
|
|
27
|
-
|
|
27
|
+
accessToken: string | null;
|
|
28
28
|
/** Refresh token for renewing the session. Null if email confirmation is required. */
|
|
29
|
-
|
|
29
|
+
refreshToken: string | null;
|
|
30
30
|
}
|
|
31
31
|
/** Response when email confirmation is required after signup. */
|
|
32
32
|
interface SignupConfirmationResult {
|
|
@@ -45,9 +45,9 @@ interface LoginParams {
|
|
|
45
45
|
interface LoginResult {
|
|
46
46
|
customer: AuthCustomer;
|
|
47
47
|
/** Access token for authenticated requests. */
|
|
48
|
-
|
|
48
|
+
accessToken: string | null;
|
|
49
49
|
/** Refresh token for renewing the session. */
|
|
50
|
-
|
|
50
|
+
refreshToken: string | null;
|
|
51
51
|
}
|
|
52
52
|
/** Stored session with tokens and customer identity. */
|
|
53
53
|
interface AuthSession {
|
package/dist/auth/index.js
CHANGED
|
@@ -16,22 +16,22 @@ var AuthClient = class {
|
|
|
16
16
|
*/
|
|
17
17
|
async signup(params) {
|
|
18
18
|
const response = await this.fetch(
|
|
19
|
-
"/widget/
|
|
19
|
+
"/api/widget/auth/signup",
|
|
20
20
|
{
|
|
21
21
|
method: "POST",
|
|
22
22
|
body: {
|
|
23
|
-
|
|
23
|
+
teamId: this.teamSlug,
|
|
24
24
|
email: params.email,
|
|
25
25
|
password: params.password,
|
|
26
26
|
name: params.name
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
);
|
|
30
|
-
if ("
|
|
30
|
+
if ("accessToken" in response && response.accessToken && response.customer) {
|
|
31
31
|
this.session = {
|
|
32
32
|
customer: response.customer,
|
|
33
|
-
accessToken: response.
|
|
34
|
-
refreshToken: response.
|
|
33
|
+
accessToken: response.accessToken,
|
|
34
|
+
refreshToken: response.refreshToken
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
return response;
|
|
@@ -45,19 +45,19 @@ var AuthClient = class {
|
|
|
45
45
|
* @throws Error on invalid credentials, unconfirmed email, or server error
|
|
46
46
|
*/
|
|
47
47
|
async login(params) {
|
|
48
|
-
const response = await this.fetch("/widget/
|
|
48
|
+
const response = await this.fetch("/api/widget/auth/login", {
|
|
49
49
|
method: "POST",
|
|
50
50
|
body: {
|
|
51
|
-
|
|
51
|
+
teamId: this.teamSlug,
|
|
52
52
|
email: params.email,
|
|
53
53
|
password: params.password
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
|
-
if (response.
|
|
56
|
+
if (response.accessToken && response.customer) {
|
|
57
57
|
this.session = {
|
|
58
58
|
customer: response.customer,
|
|
59
|
-
accessToken: response.
|
|
60
|
-
refreshToken: response.
|
|
59
|
+
accessToken: response.accessToken,
|
|
60
|
+
refreshToken: response.refreshToken
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
return response;
|
package/dist/chat/index.cjs
CHANGED
|
@@ -30,7 +30,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/chat/index.ts
|
|
31
31
|
var chat_exports = {};
|
|
32
32
|
__export(chat_exports, {
|
|
33
|
-
ChatClient: () => ChatClient
|
|
33
|
+
ChatClient: () => ChatClient,
|
|
34
|
+
ChatClientAuthError: () => ChatClientAuthError,
|
|
35
|
+
ChatClientError: () => ChatClientError,
|
|
36
|
+
ChatClientNetworkError: () => ChatClientNetworkError,
|
|
37
|
+
ChatClientRateLimitError: () => ChatClientRateLimitError,
|
|
38
|
+
ChatClientServerError: () => ChatClientServerError
|
|
34
39
|
});
|
|
35
40
|
module.exports = __toCommonJS(chat_exports);
|
|
36
41
|
|
|
@@ -134,6 +139,14 @@ var RealtimeManager = class {
|
|
|
134
139
|
getClient() {
|
|
135
140
|
return this.client;
|
|
136
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* The current Supabase JWT (set by init/refresh). Returned so callers can
|
|
144
|
+
* decode the `sub` claim to learn the customerId — required after a
|
|
145
|
+
* session-mode login where the SDK doesn't have customerId at hand.
|
|
146
|
+
*/
|
|
147
|
+
getCurrentToken() {
|
|
148
|
+
return this.currentToken;
|
|
149
|
+
}
|
|
137
150
|
/** Whether the realtime manager is initialized and has a token. */
|
|
138
151
|
get isReady() {
|
|
139
152
|
return this.client !== null && this.currentToken !== null;
|
|
@@ -275,6 +288,43 @@ var PresenceManager = class {
|
|
|
275
288
|
}
|
|
276
289
|
};
|
|
277
290
|
|
|
291
|
+
// src/chat/errors.ts
|
|
292
|
+
var ChatClientError = class extends Error {
|
|
293
|
+
constructor(message, opts) {
|
|
294
|
+
super(message);
|
|
295
|
+
this.name = "ChatClientError";
|
|
296
|
+
this.code = opts.code ?? "UNKNOWN";
|
|
297
|
+
this.statusCode = opts.statusCode;
|
|
298
|
+
this.requestId = opts.requestId ?? null;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
var ChatClientAuthError = class extends ChatClientError {
|
|
302
|
+
constructor(opts) {
|
|
303
|
+
super(opts.message, opts);
|
|
304
|
+
this.name = "ChatClientAuthError";
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
var ChatClientRateLimitError = class extends ChatClientError {
|
|
308
|
+
constructor(opts) {
|
|
309
|
+
super(opts.message, opts);
|
|
310
|
+
this.name = "ChatClientRateLimitError";
|
|
311
|
+
this.retryAfter = opts.retryAfter ?? null;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
var ChatClientServerError = class extends ChatClientError {
|
|
315
|
+
constructor(opts) {
|
|
316
|
+
super(opts.message, opts);
|
|
317
|
+
this.name = "ChatClientServerError";
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
var ChatClientNetworkError = class extends ChatClientError {
|
|
321
|
+
constructor(opts) {
|
|
322
|
+
super(opts.message, { code: "NETWORK_ERROR", statusCode: 0, requestId: null });
|
|
323
|
+
this.name = "ChatClientNetworkError";
|
|
324
|
+
this.originalError = opts.originalError;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
278
328
|
// src/chat/index.ts
|
|
279
329
|
var DEFAULT_BASE_URL = "https://www.cstar.help";
|
|
280
330
|
var DEFAULT_POLLING_INTERVAL = 3e3;
|
|
@@ -283,6 +333,7 @@ var ChatClient = class {
|
|
|
283
333
|
this.sessionToken = null;
|
|
284
334
|
this.teamId = null;
|
|
285
335
|
this.customerId = null;
|
|
336
|
+
this.authMode = null;
|
|
286
337
|
this.pollingTimers = /* @__PURE__ */ new Map();
|
|
287
338
|
this.realtimeManager = null;
|
|
288
339
|
this.presenceManager = null;
|
|
@@ -331,6 +382,70 @@ var ChatClient = class {
|
|
|
331
382
|
}
|
|
332
383
|
return response.data;
|
|
333
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* Sign in with cStar's hosted password auth. The SDK keeps the resulting
|
|
387
|
+
* Supabase access token and uses it as Bearer for every subsequent call.
|
|
388
|
+
* Real-time bootstraps automatically (same flow as `identify()`).
|
|
389
|
+
*
|
|
390
|
+
* Use this when your customers don't have a session in your app and you
|
|
391
|
+
* want cStar to handle login. For the case where your app already has a
|
|
392
|
+
* Supabase session, use `loginWithSession({ accessToken })` instead.
|
|
393
|
+
*/
|
|
394
|
+
async loginWithCredentials(params) {
|
|
395
|
+
const teamId = await this.resolveTeamIdFromSlug();
|
|
396
|
+
const response = await this.fetch("/api/widget/auth/login", {
|
|
397
|
+
method: "POST",
|
|
398
|
+
body: { email: params.email, password: params.password, teamId }
|
|
399
|
+
});
|
|
400
|
+
if (!response.accessToken) {
|
|
401
|
+
throw new ChatClientAuthError({
|
|
402
|
+
message: "Login succeeded but no session was issued",
|
|
403
|
+
code: "NO_SESSION",
|
|
404
|
+
statusCode: 200,
|
|
405
|
+
requestId: null
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
await this.loginWithSession({
|
|
409
|
+
accessToken: response.accessToken,
|
|
410
|
+
teamId,
|
|
411
|
+
customerName: response.customer.name ?? response.customer.email
|
|
412
|
+
});
|
|
413
|
+
return {
|
|
414
|
+
customer: {
|
|
415
|
+
id: this.customerId ?? response.customer.id,
|
|
416
|
+
email: response.customer.email,
|
|
417
|
+
name: response.customer.name
|
|
418
|
+
},
|
|
419
|
+
teamId
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Use an existing Supabase access token for widget calls. Skip the cStar
|
|
424
|
+
* login flow entirely; the SDK uses the supplied token as Bearer and
|
|
425
|
+
* bootstraps real-time the same way `identify()` does.
|
|
426
|
+
*
|
|
427
|
+
* The widget API verifies the token via the `widget-session` strategy on
|
|
428
|
+
* every request — there is no client-side validation here.
|
|
429
|
+
*/
|
|
430
|
+
async loginWithSession(params) {
|
|
431
|
+
this.sessionToken = params.accessToken;
|
|
432
|
+
if (params.teamId) {
|
|
433
|
+
this.teamId = params.teamId;
|
|
434
|
+
}
|
|
435
|
+
this.authMode = "session";
|
|
436
|
+
if (this.realtimeEnabled) {
|
|
437
|
+
const customerName = params.customerName ?? "";
|
|
438
|
+
this.realtimeInitPromise = this.initRealtimeFromSession(customerName);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Returns the auth mode the client is currently using, or `null` before
|
|
443
|
+
* any authenticated call has resolved one. Useful for branching UI
|
|
444
|
+
* (e.g. to show a "Sign out" button only in session mode).
|
|
445
|
+
*/
|
|
446
|
+
getAuthMode() {
|
|
447
|
+
return this.authMode;
|
|
448
|
+
}
|
|
334
449
|
/**
|
|
335
450
|
* Subscribe to new messages on a ticket.
|
|
336
451
|
* Uses real-time delivery when available, falls back to polling.
|
|
@@ -384,8 +499,20 @@ var ChatClient = class {
|
|
|
384
499
|
this.sessionToken = null;
|
|
385
500
|
this.teamId = null;
|
|
386
501
|
this.customerId = null;
|
|
502
|
+
this.authMode = null;
|
|
387
503
|
}
|
|
388
|
-
/**
|
|
504
|
+
/**
|
|
505
|
+
* @internal Make an authenticated request to the widget API.
|
|
506
|
+
*
|
|
507
|
+
* On any non-2xx response, throws a typed subclass of `ChatClientError`
|
|
508
|
+
* (`ChatClientAuthError`, `ChatClientRateLimitError`, etc.) so consumers
|
|
509
|
+
* can branch on error class instead of parsing message strings.
|
|
510
|
+
*
|
|
511
|
+
* On the way out, reads the `X-Cstar-Auth-Mode` response header (set by
|
|
512
|
+
* widgetHandler on every response) and caches it on the client. After
|
|
513
|
+
* the first authenticated round-trip, `getAuthMode()` returns the
|
|
514
|
+
* server-confirmed mode.
|
|
515
|
+
*/
|
|
389
516
|
async fetch(path, options) {
|
|
390
517
|
const url = this.buildUrl(path, options.query);
|
|
391
518
|
const headers = {
|
|
@@ -394,17 +521,104 @@ var ChatClient = class {
|
|
|
394
521
|
if (this.sessionToken) {
|
|
395
522
|
headers["Authorization"] = `Bearer ${this.sessionToken}`;
|
|
396
523
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
524
|
+
let response;
|
|
525
|
+
try {
|
|
526
|
+
response = await globalThis.fetch(url, {
|
|
527
|
+
method: options.method,
|
|
528
|
+
headers,
|
|
529
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
530
|
+
});
|
|
531
|
+
} catch (networkErr) {
|
|
532
|
+
throw new ChatClientNetworkError({
|
|
533
|
+
message: networkErr instanceof Error ? networkErr.message : "Network request failed",
|
|
534
|
+
originalError: networkErr
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
const responseHeaders = response.headers && typeof response.headers.get === "function" ? response.headers : null;
|
|
538
|
+
const headerMode = responseHeaders?.get("X-Cstar-Auth-Mode") ?? null;
|
|
539
|
+
if (headerMode === "hmac" || headerMode === "session") {
|
|
540
|
+
this.authMode = headerMode;
|
|
541
|
+
}
|
|
542
|
+
const requestId = responseHeaders?.get("X-Request-Id") ?? null;
|
|
402
543
|
if (!response.ok) {
|
|
403
|
-
const errorBody = await response.json().catch(() => ({ error: { message: "Request failed" } }));
|
|
404
|
-
|
|
544
|
+
const errorBody = await response.json().catch(() => ({ error: { code: "UNKNOWN", message: "Request failed" } }));
|
|
545
|
+
const code = errorBody.error?.code ?? "UNKNOWN";
|
|
546
|
+
const message = errorBody.error?.message ?? `Widget API error: ${response.status}`;
|
|
547
|
+
if (response.status === 401 || response.status === 403) {
|
|
548
|
+
throw new ChatClientAuthError({
|
|
549
|
+
message,
|
|
550
|
+
code,
|
|
551
|
+
statusCode: response.status,
|
|
552
|
+
requestId
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
if (response.status === 429) {
|
|
556
|
+
const retryAfterHeader = responseHeaders?.get("Retry-After") ?? null;
|
|
557
|
+
const retryAfter = retryAfterHeader ? parseInt(retryAfterHeader, 10) : null;
|
|
558
|
+
throw new ChatClientRateLimitError({
|
|
559
|
+
message,
|
|
560
|
+
code,
|
|
561
|
+
statusCode: response.status,
|
|
562
|
+
requestId,
|
|
563
|
+
retryAfter: Number.isFinite(retryAfter ?? NaN) ? retryAfter : null
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
if (response.status >= 500) {
|
|
567
|
+
throw new ChatClientServerError({
|
|
568
|
+
message,
|
|
569
|
+
code,
|
|
570
|
+
statusCode: response.status,
|
|
571
|
+
requestId
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
throw new ChatClientError(message, {
|
|
575
|
+
code,
|
|
576
|
+
statusCode: response.status,
|
|
577
|
+
requestId
|
|
578
|
+
});
|
|
405
579
|
}
|
|
406
580
|
return response.json();
|
|
407
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* Look up the team UUID from the configured `teamSlug`. Used by
|
|
584
|
+
* `loginWithCredentials`. HMAC integrators (`identify()`) never hit
|
|
585
|
+
* this path — verify-identity accepts the slug directly.
|
|
586
|
+
*
|
|
587
|
+
* @internal
|
|
588
|
+
*/
|
|
589
|
+
async resolveTeamIdFromSlug() {
|
|
590
|
+
const response = await this.fetch(
|
|
591
|
+
"/api/widget/team-by-slug",
|
|
592
|
+
{
|
|
593
|
+
method: "GET",
|
|
594
|
+
query: { teamSlug: this.teamSlug }
|
|
595
|
+
}
|
|
596
|
+
);
|
|
597
|
+
return response.data.teamId;
|
|
598
|
+
}
|
|
599
|
+
/** @internal Bootstrap realtime from a session login (no precomputed customerId). */
|
|
600
|
+
async initRealtimeFromSession(customerName) {
|
|
601
|
+
try {
|
|
602
|
+
this.realtimeManager = new RealtimeManager({
|
|
603
|
+
fetchToken: () => this.fetchRealtimeToken()
|
|
604
|
+
});
|
|
605
|
+
const ready = await this.realtimeManager.init();
|
|
606
|
+
if (!ready) return;
|
|
607
|
+
this.realtimeReady = true;
|
|
608
|
+
const token = this.realtimeManager.getCurrentToken();
|
|
609
|
+
const customerId = token ? decodeJwtSub(token) : null;
|
|
610
|
+
if (customerId) {
|
|
611
|
+
this.customerId = customerId;
|
|
612
|
+
this.presenceManager = new PresenceManager({ customerId, customerName });
|
|
613
|
+
const client = this.realtimeManager.getClient();
|
|
614
|
+
if (client) {
|
|
615
|
+
this.presenceManager.setClient(client);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
} catch {
|
|
619
|
+
this.realtimeReady = false;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
408
622
|
/** @internal Get the stored team ID for requests that need it. */
|
|
409
623
|
getTeamId() {
|
|
410
624
|
return this.teamId;
|
|
@@ -700,7 +914,26 @@ var MessagesApi = class {
|
|
|
700
914
|
return response.data.message;
|
|
701
915
|
}
|
|
702
916
|
};
|
|
917
|
+
function decodeJwtSub(jwt) {
|
|
918
|
+
try {
|
|
919
|
+
const parts = jwt.split(".");
|
|
920
|
+
if (parts.length !== 3) return null;
|
|
921
|
+
const payloadB64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
922
|
+
const padded = payloadB64 + "=".repeat((4 - payloadB64.length % 4) % 4);
|
|
923
|
+
const payload = JSON.parse(
|
|
924
|
+
typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf-8")
|
|
925
|
+
);
|
|
926
|
+
return typeof payload.sub === "string" ? payload.sub : null;
|
|
927
|
+
} catch {
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
703
931
|
// Annotate the CommonJS export names for ESM import in node:
|
|
704
932
|
0 && (module.exports = {
|
|
705
|
-
ChatClient
|
|
933
|
+
ChatClient,
|
|
934
|
+
ChatClientAuthError,
|
|
935
|
+
ChatClientError,
|
|
936
|
+
ChatClientNetworkError,
|
|
937
|
+
ChatClientRateLimitError,
|
|
938
|
+
ChatClientServerError
|
|
706
939
|
});
|