@cstar.help/js 0.16.1 → 0.17.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 +100 -60
- package/dist/auth/index.d.cts +3 -398
- package/dist/auth/index.d.ts +3 -398
- package/dist/chat/index.cjs +234 -38
- package/dist/chat/index.d.cts +40 -84
- package/dist/chat/index.d.ts +40 -84
- package/dist/chat/index.js +234 -38
- package/dist/community/index.d.cts +4 -586
- package/dist/community/index.d.ts +4 -586
- package/dist/errors-b5A4wfAg.d.cts +398 -0
- package/dist/errors-b5A4wfAg.d.ts +398 -0
- package/dist/index.cjs +41 -22
- package/dist/index.d.cts +24 -802
- package/dist/index.d.ts +24 -802
- package/dist/index.js +41 -22
- package/dist/library/index.d.cts +3 -398
- package/dist/library/index.d.ts +3 -398
- package/dist/quickhelp/index.d.cts +3 -398
- package/dist/quickhelp/index.d.ts +3 -398
- package/dist/types-DF7GprCV.d.cts +190 -0
- package/dist/types-DF7GprCV.d.ts +190 -0
- package/dist/webhook/index.d.cts +2 -434
- package/dist/webhook/index.d.ts +2 -434
- package/dist/webhook/node.d.cts +4 -450
- package/dist/webhook/node.d.ts +4 -450
- package/dist/webhookEvents-1N2i2GcQ.d.cts +449 -0
- package/dist/webhookEvents-1N2i2GcQ.d.ts +449 -0
- package/package.json +4 -4
package/dist/chat/index.js
CHANGED
|
@@ -6,6 +6,7 @@ var RealtimeManager = class {
|
|
|
6
6
|
this.channels = /* @__PURE__ */ new Map();
|
|
7
7
|
this.refreshTimer = null;
|
|
8
8
|
this.currentToken = null;
|
|
9
|
+
this.destroyed = false;
|
|
9
10
|
this.options = options;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
@@ -13,6 +14,7 @@ var RealtimeManager = class {
|
|
|
13
14
|
* Returns true if successful, false if the realtime runtime is unavailable.
|
|
14
15
|
*/
|
|
15
16
|
init() {
|
|
17
|
+
if (this.destroyed) return Promise.resolve(false);
|
|
16
18
|
if (this.initPromise) return this.initPromise;
|
|
17
19
|
this.initPromise = this.doInit();
|
|
18
20
|
return this.initPromise;
|
|
@@ -20,10 +22,12 @@ var RealtimeManager = class {
|
|
|
20
22
|
async doInit() {
|
|
21
23
|
try {
|
|
22
24
|
const tokenData = await this.options.fetchToken();
|
|
25
|
+
if (this.destroyed) return false;
|
|
23
26
|
const { createClient } = await import(
|
|
24
27
|
/* webpackIgnore: true */
|
|
25
28
|
"@supabase/supabase-js"
|
|
26
29
|
);
|
|
30
|
+
if (this.destroyed) return false;
|
|
27
31
|
this.client = createClient(tokenData.realtimeUrl, tokenData.realtimeKey, {
|
|
28
32
|
auth: { persistSession: false, autoRefreshToken: false },
|
|
29
33
|
realtime: { params: { eventsPerSecond: 10 } }
|
|
@@ -51,10 +55,13 @@ var RealtimeManager = class {
|
|
|
51
55
|
this.refreshTimer = setTimeout(() => this.doRefresh(), delay);
|
|
52
56
|
}
|
|
53
57
|
async doRefresh() {
|
|
58
|
+
if (this.destroyed) return;
|
|
54
59
|
try {
|
|
55
60
|
const tokenData = await this.options.fetchToken();
|
|
61
|
+
if (this.destroyed) return;
|
|
56
62
|
this.setToken(tokenData);
|
|
57
63
|
} catch {
|
|
64
|
+
if (this.destroyed) return;
|
|
58
65
|
this.refreshTimer = setTimeout(() => this.doRefresh(), 3e4);
|
|
59
66
|
}
|
|
60
67
|
}
|
|
@@ -119,6 +126,7 @@ var RealtimeManager = class {
|
|
|
119
126
|
}
|
|
120
127
|
/** Clean up all channels, timers, and the client. */
|
|
121
128
|
destroy() {
|
|
129
|
+
this.destroyed = true;
|
|
122
130
|
if (this.refreshTimer) {
|
|
123
131
|
clearTimeout(this.refreshTimer);
|
|
124
132
|
this.refreshTimer = null;
|
|
@@ -292,6 +300,94 @@ var ChatClientNetworkError = class extends ChatClientError {
|
|
|
292
300
|
}
|
|
293
301
|
};
|
|
294
302
|
|
|
303
|
+
// src/chat/attachments.ts
|
|
304
|
+
var UUID = /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i;
|
|
305
|
+
function downloadFilename(header) {
|
|
306
|
+
const extended = header?.match(/(?:^|;)\s*filename\*\s*=\s*UTF-8''([^;]*)/i);
|
|
307
|
+
let name = "";
|
|
308
|
+
if (extended) {
|
|
309
|
+
try {
|
|
310
|
+
name = decodeURIComponent(extended[1]);
|
|
311
|
+
} catch {
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (!name) {
|
|
315
|
+
const fallback = header?.match(/(?:^|;)\s*filename\s*=\s*(?:"((?:\\.|[^"\\])*)"|([^;]*))/i);
|
|
316
|
+
name = fallback?.[1]?.replace(/\\(.)/g, "$1") ?? fallback?.[2] ?? "";
|
|
317
|
+
}
|
|
318
|
+
return name.split(/[\\/]/).pop()?.replace(/[\x00-\x1f\x7f]/g, "").replace(/^[\s.]+|[\s.]+$/g, "") || "download";
|
|
319
|
+
}
|
|
320
|
+
var AttachmentsApi = class {
|
|
321
|
+
constructor(chat) {
|
|
322
|
+
this.chat = chat;
|
|
323
|
+
}
|
|
324
|
+
async download(reference, options = {}) {
|
|
325
|
+
const assertCurrent = this.chat.captureSession();
|
|
326
|
+
if (!this.chat.getAuthMode())
|
|
327
|
+
throw new ChatClientAuthError("Sign in before downloading a file.");
|
|
328
|
+
const { ticketId, messageId, attachmentIndex } = reference;
|
|
329
|
+
if (typeof ticketId !== "string" || typeof messageId !== "string" || !UUID.test(ticketId) || !UUID.test(messageId) || !Number.isSafeInteger(attachmentIndex) || attachmentIndex < 0) {
|
|
330
|
+
throw new TypeError("A saved ticket, message and nonnegative attachment index are required.");
|
|
331
|
+
}
|
|
332
|
+
const controller = new AbortController();
|
|
333
|
+
const signal = options.signal;
|
|
334
|
+
const download = options.download === false ? "0" : "1";
|
|
335
|
+
const abort = () => controller.abort();
|
|
336
|
+
const assertOwned = () => {
|
|
337
|
+
assertCurrent();
|
|
338
|
+
controller.signal.throwIfAborted();
|
|
339
|
+
};
|
|
340
|
+
const unsubscribe = this.chat.subscribe(() => {
|
|
341
|
+
try {
|
|
342
|
+
assertCurrent();
|
|
343
|
+
} catch {
|
|
344
|
+
abort();
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
348
|
+
if (signal?.aborted) abort();
|
|
349
|
+
try {
|
|
350
|
+
assertOwned();
|
|
351
|
+
const teamId = await this.chat.ensureTeamId(controller.signal);
|
|
352
|
+
assertOwned();
|
|
353
|
+
const result = await this.chat.fetch(
|
|
354
|
+
"/api/widget/attachments/download",
|
|
355
|
+
{
|
|
356
|
+
method: "GET",
|
|
357
|
+
query: {
|
|
358
|
+
teamId,
|
|
359
|
+
ticketId,
|
|
360
|
+
messageId,
|
|
361
|
+
attachmentIndex: String(attachmentIndex),
|
|
362
|
+
download
|
|
363
|
+
},
|
|
364
|
+
signal: controller.signal,
|
|
365
|
+
cache: "no-store",
|
|
366
|
+
readBody: async (response) => {
|
|
367
|
+
assertOwned();
|
|
368
|
+
const blob = await response.blob();
|
|
369
|
+
assertOwned();
|
|
370
|
+
return {
|
|
371
|
+
blob,
|
|
372
|
+
filename: downloadFilename(response.headers.get("Content-Disposition")),
|
|
373
|
+
contentType: blob.type.split(";")[0] || "application/octet-stream",
|
|
374
|
+
size: blob.size
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
);
|
|
379
|
+
assertOwned();
|
|
380
|
+
return result;
|
|
381
|
+
} catch (error) {
|
|
382
|
+
assertOwned();
|
|
383
|
+
throw error;
|
|
384
|
+
} finally {
|
|
385
|
+
unsubscribe();
|
|
386
|
+
signal?.removeEventListener("abort", abort);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
295
391
|
// src/chat/index.ts
|
|
296
392
|
var DEFAULT_BASE_URL = "https://www.cstar.help";
|
|
297
393
|
var DEFAULT_POLLING_INTERVAL = 3e3;
|
|
@@ -301,7 +397,8 @@ var ChatClient = class {
|
|
|
301
397
|
this.teamId = null;
|
|
302
398
|
this.customerId = null;
|
|
303
399
|
this.authMode = null;
|
|
304
|
-
this.
|
|
400
|
+
this.sessionGeneration = 0;
|
|
401
|
+
this.pollingSubscriptions = /* @__PURE__ */ new Set();
|
|
305
402
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
306
403
|
this.realtimeManager = null;
|
|
307
404
|
this.presenceManager = null;
|
|
@@ -313,6 +410,7 @@ var ChatClient = class {
|
|
|
313
410
|
this.realtimeEnabled = config.realtime !== false;
|
|
314
411
|
this.tickets = new TicketsApi(this);
|
|
315
412
|
this.messages = new MessagesApi(this);
|
|
413
|
+
this.attachments = new AttachmentsApi(this);
|
|
316
414
|
this.community = new ChatCommunityApi(this);
|
|
317
415
|
}
|
|
318
416
|
/**
|
|
@@ -327,6 +425,8 @@ var ChatClient = class {
|
|
|
327
425
|
* @param testMode - Whether to use test mode (relaxed timestamp validation)
|
|
328
426
|
*/
|
|
329
427
|
async identify(customer, signature, testMode = false) {
|
|
428
|
+
const generation = this.retireSession();
|
|
429
|
+
this.assertSession(generation);
|
|
330
430
|
const response = await this.fetch(
|
|
331
431
|
"/api/widget/verify-identity",
|
|
332
432
|
{
|
|
@@ -339,15 +439,18 @@ var ChatClient = class {
|
|
|
339
439
|
}
|
|
340
440
|
}
|
|
341
441
|
);
|
|
442
|
+
this.assertSession(generation);
|
|
342
443
|
this.sessionToken = response.data.sessionToken;
|
|
343
444
|
this.teamId = response.data.teamId;
|
|
344
445
|
this.customerId = response.data.customer.id;
|
|
345
446
|
this.authMode = "hmac";
|
|
346
447
|
this.notifyStateChanged();
|
|
448
|
+
this.assertSession(generation);
|
|
347
449
|
if (this.realtimeEnabled) {
|
|
348
450
|
this.realtimeInitPromise = this.initRealtime(
|
|
349
451
|
response.data.customer.id,
|
|
350
|
-
response.data.customer.name || customer.name || customer.email
|
|
452
|
+
response.data.customer.name || customer.name || customer.email,
|
|
453
|
+
generation
|
|
351
454
|
);
|
|
352
455
|
}
|
|
353
456
|
return response.data;
|
|
@@ -362,11 +465,15 @@ var ChatClient = class {
|
|
|
362
465
|
* Supabase session, use `loginWithSession({ accessToken })` instead.
|
|
363
466
|
*/
|
|
364
467
|
async loginWithCredentials(params) {
|
|
468
|
+
const generation = this.retireSession();
|
|
469
|
+
this.assertSession(generation);
|
|
365
470
|
const teamId = await this.resolveTeamIdFromSlug();
|
|
471
|
+
this.assertSession(generation);
|
|
366
472
|
const response = await this.fetch("/api/widget/auth/login", {
|
|
367
473
|
method: "POST",
|
|
368
474
|
body: { email: params.email, password: params.password, teamId }
|
|
369
475
|
});
|
|
476
|
+
this.assertSession(generation);
|
|
370
477
|
if (!response.accessToken) {
|
|
371
478
|
throw new ChatClientAuthError({
|
|
372
479
|
message: "Login succeeded but no session was issued",
|
|
@@ -375,11 +482,14 @@ var ChatClient = class {
|
|
|
375
482
|
requestId: null
|
|
376
483
|
});
|
|
377
484
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
485
|
+
this.applySession(
|
|
486
|
+
{
|
|
487
|
+
accessToken: response.accessToken,
|
|
488
|
+
teamId,
|
|
489
|
+
customerName: response.customer.name ?? response.customer.email
|
|
490
|
+
},
|
|
491
|
+
generation
|
|
492
|
+
);
|
|
383
493
|
return {
|
|
384
494
|
customer: {
|
|
385
495
|
id: this.customerId ?? response.customer.id,
|
|
@@ -398,15 +508,22 @@ var ChatClient = class {
|
|
|
398
508
|
* every request — there is no client-side validation here.
|
|
399
509
|
*/
|
|
400
510
|
async loginWithSession(params) {
|
|
511
|
+
const teamId = params.teamId ?? this.teamId;
|
|
512
|
+
const generation = this.retireSession();
|
|
513
|
+
this.applySession({ ...params, ...teamId ? { teamId } : {} }, generation);
|
|
514
|
+
}
|
|
515
|
+
applySession(params, generation) {
|
|
516
|
+
this.assertSession(generation);
|
|
401
517
|
this.sessionToken = params.accessToken;
|
|
402
518
|
if (params.teamId) {
|
|
403
519
|
this.teamId = params.teamId;
|
|
404
520
|
}
|
|
405
521
|
this.authMode = "session";
|
|
406
522
|
this.notifyStateChanged();
|
|
523
|
+
this.assertSession(generation);
|
|
407
524
|
if (this.realtimeEnabled) {
|
|
408
525
|
const customerName = params.customerName ?? "";
|
|
409
|
-
this.realtimeInitPromise = this.initRealtimeFromSession(customerName);
|
|
526
|
+
this.realtimeInitPromise = this.initRealtimeFromSession(customerName, generation);
|
|
410
527
|
}
|
|
411
528
|
}
|
|
412
529
|
/**
|
|
@@ -447,8 +564,11 @@ var ChatClient = class {
|
|
|
447
564
|
* @returns Unsubscribe function
|
|
448
565
|
*/
|
|
449
566
|
onMessage(ticketId, callback) {
|
|
567
|
+
const generation = this.sessionGeneration;
|
|
450
568
|
if (this.realtimeReady && this.realtimeManager) {
|
|
451
|
-
return this.realtimeManager.subscribeToMessages(ticketId,
|
|
569
|
+
return this.realtimeManager.subscribeToMessages(ticketId, (message) => {
|
|
570
|
+
if (generation === this.sessionGeneration) callback(message);
|
|
571
|
+
});
|
|
452
572
|
}
|
|
453
573
|
return this.pollMessages(ticketId, callback);
|
|
454
574
|
}
|
|
@@ -459,8 +579,11 @@ var ChatClient = class {
|
|
|
459
579
|
* @returns Unsubscribe function
|
|
460
580
|
*/
|
|
461
581
|
onTyping(ticketId, callback) {
|
|
582
|
+
const generation = this.sessionGeneration;
|
|
462
583
|
if (this.realtimeReady && this.presenceManager) {
|
|
463
|
-
return this.presenceManager.subscribeToTyping(ticketId,
|
|
584
|
+
return this.presenceManager.subscribeToTyping(ticketId, (typing) => {
|
|
585
|
+
if (generation === this.sessionGeneration) callback(typing);
|
|
586
|
+
});
|
|
464
587
|
}
|
|
465
588
|
return () => {
|
|
466
589
|
};
|
|
@@ -476,6 +599,10 @@ var ChatClient = class {
|
|
|
476
599
|
}
|
|
477
600
|
/** Disconnect all subscriptions and clean up. */
|
|
478
601
|
disconnect() {
|
|
602
|
+
this.retireSession();
|
|
603
|
+
}
|
|
604
|
+
retireSession() {
|
|
605
|
+
const generation = ++this.sessionGeneration;
|
|
479
606
|
if (this.realtimeManager) {
|
|
480
607
|
this.realtimeManager.destroy();
|
|
481
608
|
this.realtimeManager = null;
|
|
@@ -486,15 +613,29 @@ var ChatClient = class {
|
|
|
486
613
|
}
|
|
487
614
|
this.realtimeReady = false;
|
|
488
615
|
this.realtimeInitPromise = null;
|
|
489
|
-
for (const
|
|
490
|
-
|
|
616
|
+
for (const unsubscribe of this.pollingSubscriptions) {
|
|
617
|
+
unsubscribe();
|
|
491
618
|
}
|
|
492
|
-
this.
|
|
619
|
+
this.pollingSubscriptions.clear();
|
|
493
620
|
this.sessionToken = null;
|
|
494
621
|
this.teamId = null;
|
|
495
622
|
this.customerId = null;
|
|
496
623
|
this.authMode = null;
|
|
497
624
|
this.notifyStateChanged();
|
|
625
|
+
return generation;
|
|
626
|
+
}
|
|
627
|
+
assertSession(generation) {
|
|
628
|
+
if (generation !== this.sessionGeneration) {
|
|
629
|
+
throw new ChatClientAuthError({
|
|
630
|
+
code: "SESSION_CHANGED",
|
|
631
|
+
message: "Your sign-in session changed. This operation belongs to the previous session."
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/** @internal Keep a public operation owned across its asynchronous prerequisites. */
|
|
636
|
+
captureSession() {
|
|
637
|
+
const generation = this.sessionGeneration;
|
|
638
|
+
return () => this.assertSession(generation);
|
|
498
639
|
}
|
|
499
640
|
/**
|
|
500
641
|
* @internal Make an authenticated request to the widget API.
|
|
@@ -509,6 +650,12 @@ var ChatClient = class {
|
|
|
509
650
|
* server-confirmed mode.
|
|
510
651
|
*/
|
|
511
652
|
async fetch(path, options) {
|
|
653
|
+
const generation = this.sessionGeneration;
|
|
654
|
+
const assertRequest = () => {
|
|
655
|
+
this.assertSession(generation);
|
|
656
|
+
options.signal?.throwIfAborted();
|
|
657
|
+
};
|
|
658
|
+
assertRequest();
|
|
512
659
|
const url = this.buildUrl(path, options.query);
|
|
513
660
|
const headers = {
|
|
514
661
|
"Content-Type": "application/json"
|
|
@@ -521,14 +668,18 @@ var ChatClient = class {
|
|
|
521
668
|
response = await globalThis.fetch(url, {
|
|
522
669
|
method: options.method,
|
|
523
670
|
headers,
|
|
671
|
+
...options.signal ? { signal: options.signal } : {},
|
|
672
|
+
...options.cache ? { cache: options.cache } : {},
|
|
524
673
|
body: options.body ? JSON.stringify(options.body) : void 0
|
|
525
674
|
});
|
|
526
675
|
} catch (networkErr) {
|
|
676
|
+
assertRequest();
|
|
527
677
|
throw new ChatClientNetworkError({
|
|
528
678
|
message: networkErr instanceof Error ? networkErr.message : "Network request failed",
|
|
529
679
|
originalError: networkErr
|
|
530
680
|
});
|
|
531
681
|
}
|
|
682
|
+
assertRequest();
|
|
532
683
|
const responseHeaders = response.headers && typeof response.headers.get === "function" ? response.headers : null;
|
|
533
684
|
const headerMode = responseHeaders?.get("X-Cstar-Auth-Mode") ?? null;
|
|
534
685
|
if (headerMode === "hmac" || headerMode === "session") {
|
|
@@ -536,9 +687,11 @@ var ChatClient = class {
|
|
|
536
687
|
this.authMode = headerMode;
|
|
537
688
|
if (previous !== headerMode) this.notifyStateChanged();
|
|
538
689
|
}
|
|
690
|
+
assertRequest();
|
|
539
691
|
const requestId = responseHeaders?.get("X-Request-Id") ?? null;
|
|
540
692
|
if (!response.ok) {
|
|
541
693
|
const errorBody = await response.json().catch(() => ({ error: { code: "UNKNOWN", message: "Request failed" } }));
|
|
694
|
+
assertRequest();
|
|
542
695
|
const code = errorBody.error?.code ?? "UNKNOWN";
|
|
543
696
|
const message = errorBody.error?.message ?? `Widget API error: ${response.status}`;
|
|
544
697
|
if (response.status === 401 || response.status === 403) {
|
|
@@ -575,7 +728,14 @@ var ChatClient = class {
|
|
|
575
728
|
requestId
|
|
576
729
|
});
|
|
577
730
|
}
|
|
578
|
-
|
|
731
|
+
try {
|
|
732
|
+
const body = options.readBody ? await options.readBody(response) : await response.json();
|
|
733
|
+
assertRequest();
|
|
734
|
+
return body;
|
|
735
|
+
} catch (error) {
|
|
736
|
+
assertRequest();
|
|
737
|
+
throw error;
|
|
738
|
+
}
|
|
579
739
|
}
|
|
580
740
|
/**
|
|
581
741
|
* Look up the team UUID from the configured `teamSlug`. Used by
|
|
@@ -584,37 +744,41 @@ var ChatClient = class {
|
|
|
584
744
|
*
|
|
585
745
|
* @internal
|
|
586
746
|
*/
|
|
587
|
-
async resolveTeamIdFromSlug() {
|
|
747
|
+
async resolveTeamIdFromSlug(signal) {
|
|
588
748
|
const response = await this.fetch(
|
|
589
749
|
"/api/widget/team-by-slug",
|
|
590
750
|
{
|
|
591
751
|
method: "GET",
|
|
752
|
+
signal,
|
|
592
753
|
query: { teamSlug: this.teamSlug }
|
|
593
754
|
}
|
|
594
755
|
);
|
|
595
756
|
return response.data.teamId;
|
|
596
757
|
}
|
|
597
758
|
/** @internal Bootstrap realtime from a session login (no precomputed customerId). */
|
|
598
|
-
async initRealtimeFromSession(customerName) {
|
|
759
|
+
async initRealtimeFromSession(customerName, generation) {
|
|
760
|
+
const manager = new RealtimeManager({ fetchToken: () => this.fetchRealtimeToken(generation) });
|
|
761
|
+
this.realtimeManager = manager;
|
|
599
762
|
try {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
763
|
+
const ready = await manager.init();
|
|
764
|
+
if (generation !== this.sessionGeneration || this.realtimeManager !== manager) {
|
|
765
|
+
manager.destroy();
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
604
768
|
if (!ready) return;
|
|
605
769
|
this.realtimeReady = true;
|
|
606
|
-
const token =
|
|
770
|
+
const token = manager.getCurrentToken();
|
|
607
771
|
const customerId = token ? decodeJwtSub(token) : null;
|
|
608
772
|
if (customerId) {
|
|
609
773
|
this.customerId = customerId;
|
|
610
774
|
this.presenceManager = new PresenceManager({ customerId, customerName });
|
|
611
|
-
const client =
|
|
775
|
+
const client = manager.getClient();
|
|
612
776
|
if (client) {
|
|
613
777
|
this.presenceManager.setClient(client);
|
|
614
778
|
}
|
|
615
779
|
}
|
|
616
780
|
} catch {
|
|
617
|
-
this.realtimeReady = false;
|
|
781
|
+
if (generation === this.sessionGeneration) this.realtimeReady = false;
|
|
618
782
|
}
|
|
619
783
|
}
|
|
620
784
|
/** @internal Get the stored team ID for requests that need it. */
|
|
@@ -632,14 +796,19 @@ var ChatClient = class {
|
|
|
632
796
|
* `"Missing teamId"` when the chat client was teamSlug-only because the
|
|
633
797
|
* tickets sub-resource read `getTeamId()` synchronously and bailed.
|
|
634
798
|
*/
|
|
635
|
-
async ensureTeamId() {
|
|
799
|
+
async ensureTeamId(signal) {
|
|
800
|
+
const assertCurrent = this.captureSession();
|
|
801
|
+
signal?.throwIfAborted();
|
|
636
802
|
if (this.teamId) return this.teamId;
|
|
637
803
|
if (!this.teamSlug) {
|
|
638
804
|
throw new ChatClientAuthError(
|
|
639
805
|
"Missing teamId. Construct ChatClient with `teamSlug` or call `setTeamId()` / `identify()` / `loginWithCredentials()` first."
|
|
640
806
|
);
|
|
641
807
|
}
|
|
642
|
-
|
|
808
|
+
const teamId = await this.resolveTeamIdFromSlug(signal);
|
|
809
|
+
assertCurrent();
|
|
810
|
+
signal?.throwIfAborted();
|
|
811
|
+
this.teamId = teamId;
|
|
643
812
|
return this.teamId;
|
|
644
813
|
}
|
|
645
814
|
/** @internal Set team ID from identify response or other source. */
|
|
@@ -652,28 +821,32 @@ var ChatClient = class {
|
|
|
652
821
|
await this.realtimeInitPromise;
|
|
653
822
|
}
|
|
654
823
|
}
|
|
655
|
-
async initRealtime(customerId, customerName) {
|
|
824
|
+
async initRealtime(customerId, customerName, generation) {
|
|
825
|
+
const manager = new RealtimeManager({ fetchToken: () => this.fetchRealtimeToken(generation) });
|
|
826
|
+
this.realtimeManager = manager;
|
|
656
827
|
try {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
828
|
+
const ready = await manager.init();
|
|
829
|
+
if (generation !== this.sessionGeneration || this.realtimeManager !== manager) {
|
|
830
|
+
manager.destroy();
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
661
833
|
if (ready) {
|
|
662
834
|
this.realtimeReady = true;
|
|
663
835
|
this.presenceManager = new PresenceManager({
|
|
664
836
|
customerId,
|
|
665
837
|
customerName
|
|
666
838
|
});
|
|
667
|
-
const client =
|
|
839
|
+
const client = manager.getClient();
|
|
668
840
|
if (client) {
|
|
669
841
|
this.presenceManager.setClient(client);
|
|
670
842
|
}
|
|
671
843
|
}
|
|
672
844
|
} catch {
|
|
673
|
-
this.realtimeReady = false;
|
|
845
|
+
if (generation === this.sessionGeneration) this.realtimeReady = false;
|
|
674
846
|
}
|
|
675
847
|
}
|
|
676
|
-
async fetchRealtimeToken() {
|
|
848
|
+
async fetchRealtimeToken(generation) {
|
|
849
|
+
this.assertSession(generation);
|
|
677
850
|
const response = await this.fetch(
|
|
678
851
|
"/api/widget/realtime-token",
|
|
679
852
|
{
|
|
@@ -681,34 +854,47 @@ var ChatClient = class {
|
|
|
681
854
|
body: { teamId: this.teamId }
|
|
682
855
|
}
|
|
683
856
|
);
|
|
857
|
+
this.assertSession(generation);
|
|
684
858
|
return response.data;
|
|
685
859
|
}
|
|
686
860
|
pollMessages(ticketId, callback) {
|
|
687
861
|
let lastMessageId = null;
|
|
862
|
+
let initialized = false;
|
|
863
|
+
let active = true;
|
|
864
|
+
let inFlight = false;
|
|
688
865
|
const poll = async () => {
|
|
866
|
+
if (!active || inFlight) return;
|
|
867
|
+
inFlight = true;
|
|
689
868
|
try {
|
|
690
869
|
const messages = await this.messages.list(ticketId);
|
|
870
|
+
if (!active) return;
|
|
691
871
|
if (messages.length > 0) {
|
|
692
872
|
const latest = messages[messages.length - 1];
|
|
693
|
-
if (
|
|
873
|
+
if (initialized && latest.id !== lastMessageId) {
|
|
694
874
|
const lastIdx = messages.findIndex((m) => m.id === lastMessageId);
|
|
695
|
-
const newMessages = lastIdx >= 0 ? messages.slice(lastIdx + 1) : [latest];
|
|
875
|
+
const newMessages = lastIdx >= 0 ? messages.slice(lastIdx + 1) : lastMessageId ? [latest] : messages;
|
|
696
876
|
for (const msg of newMessages) {
|
|
877
|
+
if (!active) return;
|
|
697
878
|
callback(msg);
|
|
698
879
|
}
|
|
699
880
|
}
|
|
700
881
|
lastMessageId = latest.id;
|
|
701
882
|
}
|
|
883
|
+
initialized = true;
|
|
702
884
|
} catch {
|
|
885
|
+
} finally {
|
|
886
|
+
inFlight = false;
|
|
703
887
|
}
|
|
704
888
|
};
|
|
705
889
|
poll();
|
|
706
890
|
const timer = setInterval(poll, this.pollingInterval);
|
|
707
|
-
|
|
708
|
-
|
|
891
|
+
const unsubscribe = () => {
|
|
892
|
+
active = false;
|
|
709
893
|
clearInterval(timer);
|
|
710
|
-
this.
|
|
894
|
+
this.pollingSubscriptions.delete(unsubscribe);
|
|
711
895
|
};
|
|
896
|
+
this.pollingSubscriptions.add(unsubscribe);
|
|
897
|
+
return unsubscribe;
|
|
712
898
|
}
|
|
713
899
|
buildUrl(path, query) {
|
|
714
900
|
const base = `${this.baseUrl}${path}`;
|
|
@@ -727,10 +913,12 @@ var TicketsApi = class {
|
|
|
727
913
|
}
|
|
728
914
|
/** List tickets for the identified customer. */
|
|
729
915
|
async list(params) {
|
|
916
|
+
const assertCurrent = this.chat.captureSession();
|
|
730
917
|
if (this.chat.getAuthMode() === null) {
|
|
731
918
|
throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
|
|
732
919
|
}
|
|
733
920
|
const teamId = await this.chat.ensureTeamId();
|
|
921
|
+
assertCurrent();
|
|
734
922
|
const response = await this.chat.fetch("/api/widget/conversations", {
|
|
735
923
|
method: "GET",
|
|
736
924
|
query: {
|
|
@@ -739,6 +927,7 @@ var TicketsApi = class {
|
|
|
739
927
|
cursor: params?.cursor
|
|
740
928
|
}
|
|
741
929
|
});
|
|
930
|
+
assertCurrent();
|
|
742
931
|
const data = response.data.tickets ?? response.data.conversations ?? [];
|
|
743
932
|
const cursor = response.data.cursor;
|
|
744
933
|
const hasMore = response.data.hasMore;
|
|
@@ -750,10 +939,12 @@ var TicketsApi = class {
|
|
|
750
939
|
}
|
|
751
940
|
/** Create a new ticket. */
|
|
752
941
|
async create(params) {
|
|
942
|
+
const assertCurrent = this.chat.captureSession();
|
|
753
943
|
if (this.chat.getAuthMode() === null) {
|
|
754
944
|
throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
|
|
755
945
|
}
|
|
756
946
|
const teamId = await this.chat.ensureTeamId();
|
|
947
|
+
assertCurrent();
|
|
757
948
|
const response = await this.chat.fetch("/api/widget/tickets", {
|
|
758
949
|
method: "POST",
|
|
759
950
|
body: {
|
|
@@ -762,6 +953,7 @@ var TicketsApi = class {
|
|
|
762
953
|
message: params.message
|
|
763
954
|
}
|
|
764
955
|
});
|
|
956
|
+
assertCurrent();
|
|
765
957
|
const messages = (response.data.messages ?? []).map((m) => ({
|
|
766
958
|
id: m.id,
|
|
767
959
|
content: m.content,
|
|
@@ -920,6 +1112,7 @@ var MessagesApi = class {
|
|
|
920
1112
|
}
|
|
921
1113
|
/** List messages for a ticket. */
|
|
922
1114
|
async list(ticketId) {
|
|
1115
|
+
const assertCurrent = this.chat.captureSession();
|
|
923
1116
|
const teamId = this.chat.getTeamId();
|
|
924
1117
|
const response = await this.chat.fetch(
|
|
925
1118
|
"/api/widget/messages",
|
|
@@ -931,10 +1124,12 @@ var MessagesApi = class {
|
|
|
931
1124
|
}
|
|
932
1125
|
}
|
|
933
1126
|
);
|
|
1127
|
+
assertCurrent();
|
|
934
1128
|
return response.data.messages;
|
|
935
1129
|
}
|
|
936
1130
|
/** Send a message to a ticket. */
|
|
937
1131
|
async send(ticketId, content) {
|
|
1132
|
+
const assertCurrent = this.chat.captureSession();
|
|
938
1133
|
const teamId = this.chat.getTeamId();
|
|
939
1134
|
const response = await this.chat.fetch(
|
|
940
1135
|
"/api/widget/messages",
|
|
@@ -947,6 +1142,7 @@ var MessagesApi = class {
|
|
|
947
1142
|
}
|
|
948
1143
|
}
|
|
949
1144
|
);
|
|
1145
|
+
assertCurrent();
|
|
950
1146
|
return response.data.message;
|
|
951
1147
|
}
|
|
952
1148
|
};
|