@cstar.help/js 0.16.0 → 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 +62 -26
- package/dist/index.d.cts +54 -805
- package/dist/index.d.ts +54 -805
- package/dist/index.js +62 -26
- 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.cjs
CHANGED
|
@@ -47,6 +47,7 @@ var RealtimeManager = class {
|
|
|
47
47
|
this.channels = /* @__PURE__ */ new Map();
|
|
48
48
|
this.refreshTimer = null;
|
|
49
49
|
this.currentToken = null;
|
|
50
|
+
this.destroyed = false;
|
|
50
51
|
this.options = options;
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
@@ -54,6 +55,7 @@ var RealtimeManager = class {
|
|
|
54
55
|
* Returns true if successful, false if the realtime runtime is unavailable.
|
|
55
56
|
*/
|
|
56
57
|
init() {
|
|
58
|
+
if (this.destroyed) return Promise.resolve(false);
|
|
57
59
|
if (this.initPromise) return this.initPromise;
|
|
58
60
|
this.initPromise = this.doInit();
|
|
59
61
|
return this.initPromise;
|
|
@@ -61,10 +63,12 @@ var RealtimeManager = class {
|
|
|
61
63
|
async doInit() {
|
|
62
64
|
try {
|
|
63
65
|
const tokenData = await this.options.fetchToken();
|
|
66
|
+
if (this.destroyed) return false;
|
|
64
67
|
const { createClient } = await import(
|
|
65
68
|
/* webpackIgnore: true */
|
|
66
69
|
"@supabase/supabase-js"
|
|
67
70
|
);
|
|
71
|
+
if (this.destroyed) return false;
|
|
68
72
|
this.client = createClient(tokenData.realtimeUrl, tokenData.realtimeKey, {
|
|
69
73
|
auth: { persistSession: false, autoRefreshToken: false },
|
|
70
74
|
realtime: { params: { eventsPerSecond: 10 } }
|
|
@@ -92,10 +96,13 @@ var RealtimeManager = class {
|
|
|
92
96
|
this.refreshTimer = setTimeout(() => this.doRefresh(), delay);
|
|
93
97
|
}
|
|
94
98
|
async doRefresh() {
|
|
99
|
+
if (this.destroyed) return;
|
|
95
100
|
try {
|
|
96
101
|
const tokenData = await this.options.fetchToken();
|
|
102
|
+
if (this.destroyed) return;
|
|
97
103
|
this.setToken(tokenData);
|
|
98
104
|
} catch {
|
|
105
|
+
if (this.destroyed) return;
|
|
99
106
|
this.refreshTimer = setTimeout(() => this.doRefresh(), 3e4);
|
|
100
107
|
}
|
|
101
108
|
}
|
|
@@ -160,6 +167,7 @@ var RealtimeManager = class {
|
|
|
160
167
|
}
|
|
161
168
|
/** Clean up all channels, timers, and the client. */
|
|
162
169
|
destroy() {
|
|
170
|
+
this.destroyed = true;
|
|
163
171
|
if (this.refreshTimer) {
|
|
164
172
|
clearTimeout(this.refreshTimer);
|
|
165
173
|
this.refreshTimer = null;
|
|
@@ -333,6 +341,94 @@ var ChatClientNetworkError = class extends ChatClientError {
|
|
|
333
341
|
}
|
|
334
342
|
};
|
|
335
343
|
|
|
344
|
+
// src/chat/attachments.ts
|
|
345
|
+
var UUID = /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i;
|
|
346
|
+
function downloadFilename(header) {
|
|
347
|
+
const extended = header?.match(/(?:^|;)\s*filename\*\s*=\s*UTF-8''([^;]*)/i);
|
|
348
|
+
let name = "";
|
|
349
|
+
if (extended) {
|
|
350
|
+
try {
|
|
351
|
+
name = decodeURIComponent(extended[1]);
|
|
352
|
+
} catch {
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (!name) {
|
|
356
|
+
const fallback = header?.match(/(?:^|;)\s*filename\s*=\s*(?:"((?:\\.|[^"\\])*)"|([^;]*))/i);
|
|
357
|
+
name = fallback?.[1]?.replace(/\\(.)/g, "$1") ?? fallback?.[2] ?? "";
|
|
358
|
+
}
|
|
359
|
+
return name.split(/[\\/]/).pop()?.replace(/[\x00-\x1f\x7f]/g, "").replace(/^[\s.]+|[\s.]+$/g, "") || "download";
|
|
360
|
+
}
|
|
361
|
+
var AttachmentsApi = class {
|
|
362
|
+
constructor(chat) {
|
|
363
|
+
this.chat = chat;
|
|
364
|
+
}
|
|
365
|
+
async download(reference, options = {}) {
|
|
366
|
+
const assertCurrent = this.chat.captureSession();
|
|
367
|
+
if (!this.chat.getAuthMode())
|
|
368
|
+
throw new ChatClientAuthError("Sign in before downloading a file.");
|
|
369
|
+
const { ticketId, messageId, attachmentIndex } = reference;
|
|
370
|
+
if (typeof ticketId !== "string" || typeof messageId !== "string" || !UUID.test(ticketId) || !UUID.test(messageId) || !Number.isSafeInteger(attachmentIndex) || attachmentIndex < 0) {
|
|
371
|
+
throw new TypeError("A saved ticket, message and nonnegative attachment index are required.");
|
|
372
|
+
}
|
|
373
|
+
const controller = new AbortController();
|
|
374
|
+
const signal = options.signal;
|
|
375
|
+
const download = options.download === false ? "0" : "1";
|
|
376
|
+
const abort = () => controller.abort();
|
|
377
|
+
const assertOwned = () => {
|
|
378
|
+
assertCurrent();
|
|
379
|
+
controller.signal.throwIfAborted();
|
|
380
|
+
};
|
|
381
|
+
const unsubscribe = this.chat.subscribe(() => {
|
|
382
|
+
try {
|
|
383
|
+
assertCurrent();
|
|
384
|
+
} catch {
|
|
385
|
+
abort();
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
389
|
+
if (signal?.aborted) abort();
|
|
390
|
+
try {
|
|
391
|
+
assertOwned();
|
|
392
|
+
const teamId = await this.chat.ensureTeamId(controller.signal);
|
|
393
|
+
assertOwned();
|
|
394
|
+
const result = await this.chat.fetch(
|
|
395
|
+
"/api/widget/attachments/download",
|
|
396
|
+
{
|
|
397
|
+
method: "GET",
|
|
398
|
+
query: {
|
|
399
|
+
teamId,
|
|
400
|
+
ticketId,
|
|
401
|
+
messageId,
|
|
402
|
+
attachmentIndex: String(attachmentIndex),
|
|
403
|
+
download
|
|
404
|
+
},
|
|
405
|
+
signal: controller.signal,
|
|
406
|
+
cache: "no-store",
|
|
407
|
+
readBody: async (response) => {
|
|
408
|
+
assertOwned();
|
|
409
|
+
const blob = await response.blob();
|
|
410
|
+
assertOwned();
|
|
411
|
+
return {
|
|
412
|
+
blob,
|
|
413
|
+
filename: downloadFilename(response.headers.get("Content-Disposition")),
|
|
414
|
+
contentType: blob.type.split(";")[0] || "application/octet-stream",
|
|
415
|
+
size: blob.size
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
assertOwned();
|
|
421
|
+
return result;
|
|
422
|
+
} catch (error) {
|
|
423
|
+
assertOwned();
|
|
424
|
+
throw error;
|
|
425
|
+
} finally {
|
|
426
|
+
unsubscribe();
|
|
427
|
+
signal?.removeEventListener("abort", abort);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
336
432
|
// src/chat/index.ts
|
|
337
433
|
var DEFAULT_BASE_URL = "https://www.cstar.help";
|
|
338
434
|
var DEFAULT_POLLING_INTERVAL = 3e3;
|
|
@@ -342,7 +438,8 @@ var ChatClient = class {
|
|
|
342
438
|
this.teamId = null;
|
|
343
439
|
this.customerId = null;
|
|
344
440
|
this.authMode = null;
|
|
345
|
-
this.
|
|
441
|
+
this.sessionGeneration = 0;
|
|
442
|
+
this.pollingSubscriptions = /* @__PURE__ */ new Set();
|
|
346
443
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
347
444
|
this.realtimeManager = null;
|
|
348
445
|
this.presenceManager = null;
|
|
@@ -354,6 +451,7 @@ var ChatClient = class {
|
|
|
354
451
|
this.realtimeEnabled = config.realtime !== false;
|
|
355
452
|
this.tickets = new TicketsApi(this);
|
|
356
453
|
this.messages = new MessagesApi(this);
|
|
454
|
+
this.attachments = new AttachmentsApi(this);
|
|
357
455
|
this.community = new ChatCommunityApi(this);
|
|
358
456
|
}
|
|
359
457
|
/**
|
|
@@ -368,6 +466,8 @@ var ChatClient = class {
|
|
|
368
466
|
* @param testMode - Whether to use test mode (relaxed timestamp validation)
|
|
369
467
|
*/
|
|
370
468
|
async identify(customer, signature, testMode = false) {
|
|
469
|
+
const generation = this.retireSession();
|
|
470
|
+
this.assertSession(generation);
|
|
371
471
|
const response = await this.fetch(
|
|
372
472
|
"/api/widget/verify-identity",
|
|
373
473
|
{
|
|
@@ -380,15 +480,18 @@ var ChatClient = class {
|
|
|
380
480
|
}
|
|
381
481
|
}
|
|
382
482
|
);
|
|
483
|
+
this.assertSession(generation);
|
|
383
484
|
this.sessionToken = response.data.sessionToken;
|
|
384
485
|
this.teamId = response.data.teamId;
|
|
385
486
|
this.customerId = response.data.customer.id;
|
|
386
487
|
this.authMode = "hmac";
|
|
387
488
|
this.notifyStateChanged();
|
|
489
|
+
this.assertSession(generation);
|
|
388
490
|
if (this.realtimeEnabled) {
|
|
389
491
|
this.realtimeInitPromise = this.initRealtime(
|
|
390
492
|
response.data.customer.id,
|
|
391
|
-
response.data.customer.name || customer.name || customer.email
|
|
493
|
+
response.data.customer.name || customer.name || customer.email,
|
|
494
|
+
generation
|
|
392
495
|
);
|
|
393
496
|
}
|
|
394
497
|
return response.data;
|
|
@@ -403,11 +506,15 @@ var ChatClient = class {
|
|
|
403
506
|
* Supabase session, use `loginWithSession({ accessToken })` instead.
|
|
404
507
|
*/
|
|
405
508
|
async loginWithCredentials(params) {
|
|
509
|
+
const generation = this.retireSession();
|
|
510
|
+
this.assertSession(generation);
|
|
406
511
|
const teamId = await this.resolveTeamIdFromSlug();
|
|
512
|
+
this.assertSession(generation);
|
|
407
513
|
const response = await this.fetch("/api/widget/auth/login", {
|
|
408
514
|
method: "POST",
|
|
409
515
|
body: { email: params.email, password: params.password, teamId }
|
|
410
516
|
});
|
|
517
|
+
this.assertSession(generation);
|
|
411
518
|
if (!response.accessToken) {
|
|
412
519
|
throw new ChatClientAuthError({
|
|
413
520
|
message: "Login succeeded but no session was issued",
|
|
@@ -416,11 +523,14 @@ var ChatClient = class {
|
|
|
416
523
|
requestId: null
|
|
417
524
|
});
|
|
418
525
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
526
|
+
this.applySession(
|
|
527
|
+
{
|
|
528
|
+
accessToken: response.accessToken,
|
|
529
|
+
teamId,
|
|
530
|
+
customerName: response.customer.name ?? response.customer.email
|
|
531
|
+
},
|
|
532
|
+
generation
|
|
533
|
+
);
|
|
424
534
|
return {
|
|
425
535
|
customer: {
|
|
426
536
|
id: this.customerId ?? response.customer.id,
|
|
@@ -439,15 +549,22 @@ var ChatClient = class {
|
|
|
439
549
|
* every request — there is no client-side validation here.
|
|
440
550
|
*/
|
|
441
551
|
async loginWithSession(params) {
|
|
552
|
+
const teamId = params.teamId ?? this.teamId;
|
|
553
|
+
const generation = this.retireSession();
|
|
554
|
+
this.applySession({ ...params, ...teamId ? { teamId } : {} }, generation);
|
|
555
|
+
}
|
|
556
|
+
applySession(params, generation) {
|
|
557
|
+
this.assertSession(generation);
|
|
442
558
|
this.sessionToken = params.accessToken;
|
|
443
559
|
if (params.teamId) {
|
|
444
560
|
this.teamId = params.teamId;
|
|
445
561
|
}
|
|
446
562
|
this.authMode = "session";
|
|
447
563
|
this.notifyStateChanged();
|
|
564
|
+
this.assertSession(generation);
|
|
448
565
|
if (this.realtimeEnabled) {
|
|
449
566
|
const customerName = params.customerName ?? "";
|
|
450
|
-
this.realtimeInitPromise = this.initRealtimeFromSession(customerName);
|
|
567
|
+
this.realtimeInitPromise = this.initRealtimeFromSession(customerName, generation);
|
|
451
568
|
}
|
|
452
569
|
}
|
|
453
570
|
/**
|
|
@@ -488,8 +605,11 @@ var ChatClient = class {
|
|
|
488
605
|
* @returns Unsubscribe function
|
|
489
606
|
*/
|
|
490
607
|
onMessage(ticketId, callback) {
|
|
608
|
+
const generation = this.sessionGeneration;
|
|
491
609
|
if (this.realtimeReady && this.realtimeManager) {
|
|
492
|
-
return this.realtimeManager.subscribeToMessages(ticketId,
|
|
610
|
+
return this.realtimeManager.subscribeToMessages(ticketId, (message) => {
|
|
611
|
+
if (generation === this.sessionGeneration) callback(message);
|
|
612
|
+
});
|
|
493
613
|
}
|
|
494
614
|
return this.pollMessages(ticketId, callback);
|
|
495
615
|
}
|
|
@@ -500,8 +620,11 @@ var ChatClient = class {
|
|
|
500
620
|
* @returns Unsubscribe function
|
|
501
621
|
*/
|
|
502
622
|
onTyping(ticketId, callback) {
|
|
623
|
+
const generation = this.sessionGeneration;
|
|
503
624
|
if (this.realtimeReady && this.presenceManager) {
|
|
504
|
-
return this.presenceManager.subscribeToTyping(ticketId,
|
|
625
|
+
return this.presenceManager.subscribeToTyping(ticketId, (typing) => {
|
|
626
|
+
if (generation === this.sessionGeneration) callback(typing);
|
|
627
|
+
});
|
|
505
628
|
}
|
|
506
629
|
return () => {
|
|
507
630
|
};
|
|
@@ -517,6 +640,10 @@ var ChatClient = class {
|
|
|
517
640
|
}
|
|
518
641
|
/** Disconnect all subscriptions and clean up. */
|
|
519
642
|
disconnect() {
|
|
643
|
+
this.retireSession();
|
|
644
|
+
}
|
|
645
|
+
retireSession() {
|
|
646
|
+
const generation = ++this.sessionGeneration;
|
|
520
647
|
if (this.realtimeManager) {
|
|
521
648
|
this.realtimeManager.destroy();
|
|
522
649
|
this.realtimeManager = null;
|
|
@@ -527,15 +654,29 @@ var ChatClient = class {
|
|
|
527
654
|
}
|
|
528
655
|
this.realtimeReady = false;
|
|
529
656
|
this.realtimeInitPromise = null;
|
|
530
|
-
for (const
|
|
531
|
-
|
|
657
|
+
for (const unsubscribe of this.pollingSubscriptions) {
|
|
658
|
+
unsubscribe();
|
|
532
659
|
}
|
|
533
|
-
this.
|
|
660
|
+
this.pollingSubscriptions.clear();
|
|
534
661
|
this.sessionToken = null;
|
|
535
662
|
this.teamId = null;
|
|
536
663
|
this.customerId = null;
|
|
537
664
|
this.authMode = null;
|
|
538
665
|
this.notifyStateChanged();
|
|
666
|
+
return generation;
|
|
667
|
+
}
|
|
668
|
+
assertSession(generation) {
|
|
669
|
+
if (generation !== this.sessionGeneration) {
|
|
670
|
+
throw new ChatClientAuthError({
|
|
671
|
+
code: "SESSION_CHANGED",
|
|
672
|
+
message: "Your sign-in session changed. This operation belongs to the previous session."
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/** @internal Keep a public operation owned across its asynchronous prerequisites. */
|
|
677
|
+
captureSession() {
|
|
678
|
+
const generation = this.sessionGeneration;
|
|
679
|
+
return () => this.assertSession(generation);
|
|
539
680
|
}
|
|
540
681
|
/**
|
|
541
682
|
* @internal Make an authenticated request to the widget API.
|
|
@@ -550,6 +691,12 @@ var ChatClient = class {
|
|
|
550
691
|
* server-confirmed mode.
|
|
551
692
|
*/
|
|
552
693
|
async fetch(path, options) {
|
|
694
|
+
const generation = this.sessionGeneration;
|
|
695
|
+
const assertRequest = () => {
|
|
696
|
+
this.assertSession(generation);
|
|
697
|
+
options.signal?.throwIfAborted();
|
|
698
|
+
};
|
|
699
|
+
assertRequest();
|
|
553
700
|
const url = this.buildUrl(path, options.query);
|
|
554
701
|
const headers = {
|
|
555
702
|
"Content-Type": "application/json"
|
|
@@ -562,14 +709,18 @@ var ChatClient = class {
|
|
|
562
709
|
response = await globalThis.fetch(url, {
|
|
563
710
|
method: options.method,
|
|
564
711
|
headers,
|
|
712
|
+
...options.signal ? { signal: options.signal } : {},
|
|
713
|
+
...options.cache ? { cache: options.cache } : {},
|
|
565
714
|
body: options.body ? JSON.stringify(options.body) : void 0
|
|
566
715
|
});
|
|
567
716
|
} catch (networkErr) {
|
|
717
|
+
assertRequest();
|
|
568
718
|
throw new ChatClientNetworkError({
|
|
569
719
|
message: networkErr instanceof Error ? networkErr.message : "Network request failed",
|
|
570
720
|
originalError: networkErr
|
|
571
721
|
});
|
|
572
722
|
}
|
|
723
|
+
assertRequest();
|
|
573
724
|
const responseHeaders = response.headers && typeof response.headers.get === "function" ? response.headers : null;
|
|
574
725
|
const headerMode = responseHeaders?.get("X-Cstar-Auth-Mode") ?? null;
|
|
575
726
|
if (headerMode === "hmac" || headerMode === "session") {
|
|
@@ -577,9 +728,11 @@ var ChatClient = class {
|
|
|
577
728
|
this.authMode = headerMode;
|
|
578
729
|
if (previous !== headerMode) this.notifyStateChanged();
|
|
579
730
|
}
|
|
731
|
+
assertRequest();
|
|
580
732
|
const requestId = responseHeaders?.get("X-Request-Id") ?? null;
|
|
581
733
|
if (!response.ok) {
|
|
582
734
|
const errorBody = await response.json().catch(() => ({ error: { code: "UNKNOWN", message: "Request failed" } }));
|
|
735
|
+
assertRequest();
|
|
583
736
|
const code = errorBody.error?.code ?? "UNKNOWN";
|
|
584
737
|
const message = errorBody.error?.message ?? `Widget API error: ${response.status}`;
|
|
585
738
|
if (response.status === 401 || response.status === 403) {
|
|
@@ -616,7 +769,14 @@ var ChatClient = class {
|
|
|
616
769
|
requestId
|
|
617
770
|
});
|
|
618
771
|
}
|
|
619
|
-
|
|
772
|
+
try {
|
|
773
|
+
const body = options.readBody ? await options.readBody(response) : await response.json();
|
|
774
|
+
assertRequest();
|
|
775
|
+
return body;
|
|
776
|
+
} catch (error) {
|
|
777
|
+
assertRequest();
|
|
778
|
+
throw error;
|
|
779
|
+
}
|
|
620
780
|
}
|
|
621
781
|
/**
|
|
622
782
|
* Look up the team UUID from the configured `teamSlug`. Used by
|
|
@@ -625,37 +785,41 @@ var ChatClient = class {
|
|
|
625
785
|
*
|
|
626
786
|
* @internal
|
|
627
787
|
*/
|
|
628
|
-
async resolveTeamIdFromSlug() {
|
|
788
|
+
async resolveTeamIdFromSlug(signal) {
|
|
629
789
|
const response = await this.fetch(
|
|
630
790
|
"/api/widget/team-by-slug",
|
|
631
791
|
{
|
|
632
792
|
method: "GET",
|
|
793
|
+
signal,
|
|
633
794
|
query: { teamSlug: this.teamSlug }
|
|
634
795
|
}
|
|
635
796
|
);
|
|
636
797
|
return response.data.teamId;
|
|
637
798
|
}
|
|
638
799
|
/** @internal Bootstrap realtime from a session login (no precomputed customerId). */
|
|
639
|
-
async initRealtimeFromSession(customerName) {
|
|
800
|
+
async initRealtimeFromSession(customerName, generation) {
|
|
801
|
+
const manager = new RealtimeManager({ fetchToken: () => this.fetchRealtimeToken(generation) });
|
|
802
|
+
this.realtimeManager = manager;
|
|
640
803
|
try {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
804
|
+
const ready = await manager.init();
|
|
805
|
+
if (generation !== this.sessionGeneration || this.realtimeManager !== manager) {
|
|
806
|
+
manager.destroy();
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
645
809
|
if (!ready) return;
|
|
646
810
|
this.realtimeReady = true;
|
|
647
|
-
const token =
|
|
811
|
+
const token = manager.getCurrentToken();
|
|
648
812
|
const customerId = token ? decodeJwtSub(token) : null;
|
|
649
813
|
if (customerId) {
|
|
650
814
|
this.customerId = customerId;
|
|
651
815
|
this.presenceManager = new PresenceManager({ customerId, customerName });
|
|
652
|
-
const client =
|
|
816
|
+
const client = manager.getClient();
|
|
653
817
|
if (client) {
|
|
654
818
|
this.presenceManager.setClient(client);
|
|
655
819
|
}
|
|
656
820
|
}
|
|
657
821
|
} catch {
|
|
658
|
-
this.realtimeReady = false;
|
|
822
|
+
if (generation === this.sessionGeneration) this.realtimeReady = false;
|
|
659
823
|
}
|
|
660
824
|
}
|
|
661
825
|
/** @internal Get the stored team ID for requests that need it. */
|
|
@@ -673,14 +837,19 @@ var ChatClient = class {
|
|
|
673
837
|
* `"Missing teamId"` when the chat client was teamSlug-only because the
|
|
674
838
|
* tickets sub-resource read `getTeamId()` synchronously and bailed.
|
|
675
839
|
*/
|
|
676
|
-
async ensureTeamId() {
|
|
840
|
+
async ensureTeamId(signal) {
|
|
841
|
+
const assertCurrent = this.captureSession();
|
|
842
|
+
signal?.throwIfAborted();
|
|
677
843
|
if (this.teamId) return this.teamId;
|
|
678
844
|
if (!this.teamSlug) {
|
|
679
845
|
throw new ChatClientAuthError(
|
|
680
846
|
"Missing teamId. Construct ChatClient with `teamSlug` or call `setTeamId()` / `identify()` / `loginWithCredentials()` first."
|
|
681
847
|
);
|
|
682
848
|
}
|
|
683
|
-
|
|
849
|
+
const teamId = await this.resolveTeamIdFromSlug(signal);
|
|
850
|
+
assertCurrent();
|
|
851
|
+
signal?.throwIfAborted();
|
|
852
|
+
this.teamId = teamId;
|
|
684
853
|
return this.teamId;
|
|
685
854
|
}
|
|
686
855
|
/** @internal Set team ID from identify response or other source. */
|
|
@@ -693,28 +862,32 @@ var ChatClient = class {
|
|
|
693
862
|
await this.realtimeInitPromise;
|
|
694
863
|
}
|
|
695
864
|
}
|
|
696
|
-
async initRealtime(customerId, customerName) {
|
|
865
|
+
async initRealtime(customerId, customerName, generation) {
|
|
866
|
+
const manager = new RealtimeManager({ fetchToken: () => this.fetchRealtimeToken(generation) });
|
|
867
|
+
this.realtimeManager = manager;
|
|
697
868
|
try {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
869
|
+
const ready = await manager.init();
|
|
870
|
+
if (generation !== this.sessionGeneration || this.realtimeManager !== manager) {
|
|
871
|
+
manager.destroy();
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
702
874
|
if (ready) {
|
|
703
875
|
this.realtimeReady = true;
|
|
704
876
|
this.presenceManager = new PresenceManager({
|
|
705
877
|
customerId,
|
|
706
878
|
customerName
|
|
707
879
|
});
|
|
708
|
-
const client =
|
|
880
|
+
const client = manager.getClient();
|
|
709
881
|
if (client) {
|
|
710
882
|
this.presenceManager.setClient(client);
|
|
711
883
|
}
|
|
712
884
|
}
|
|
713
885
|
} catch {
|
|
714
|
-
this.realtimeReady = false;
|
|
886
|
+
if (generation === this.sessionGeneration) this.realtimeReady = false;
|
|
715
887
|
}
|
|
716
888
|
}
|
|
717
|
-
async fetchRealtimeToken() {
|
|
889
|
+
async fetchRealtimeToken(generation) {
|
|
890
|
+
this.assertSession(generation);
|
|
718
891
|
const response = await this.fetch(
|
|
719
892
|
"/api/widget/realtime-token",
|
|
720
893
|
{
|
|
@@ -722,34 +895,47 @@ var ChatClient = class {
|
|
|
722
895
|
body: { teamId: this.teamId }
|
|
723
896
|
}
|
|
724
897
|
);
|
|
898
|
+
this.assertSession(generation);
|
|
725
899
|
return response.data;
|
|
726
900
|
}
|
|
727
901
|
pollMessages(ticketId, callback) {
|
|
728
902
|
let lastMessageId = null;
|
|
903
|
+
let initialized = false;
|
|
904
|
+
let active = true;
|
|
905
|
+
let inFlight = false;
|
|
729
906
|
const poll = async () => {
|
|
907
|
+
if (!active || inFlight) return;
|
|
908
|
+
inFlight = true;
|
|
730
909
|
try {
|
|
731
910
|
const messages = await this.messages.list(ticketId);
|
|
911
|
+
if (!active) return;
|
|
732
912
|
if (messages.length > 0) {
|
|
733
913
|
const latest = messages[messages.length - 1];
|
|
734
|
-
if (
|
|
914
|
+
if (initialized && latest.id !== lastMessageId) {
|
|
735
915
|
const lastIdx = messages.findIndex((m) => m.id === lastMessageId);
|
|
736
|
-
const newMessages = lastIdx >= 0 ? messages.slice(lastIdx + 1) : [latest];
|
|
916
|
+
const newMessages = lastIdx >= 0 ? messages.slice(lastIdx + 1) : lastMessageId ? [latest] : messages;
|
|
737
917
|
for (const msg of newMessages) {
|
|
918
|
+
if (!active) return;
|
|
738
919
|
callback(msg);
|
|
739
920
|
}
|
|
740
921
|
}
|
|
741
922
|
lastMessageId = latest.id;
|
|
742
923
|
}
|
|
924
|
+
initialized = true;
|
|
743
925
|
} catch {
|
|
926
|
+
} finally {
|
|
927
|
+
inFlight = false;
|
|
744
928
|
}
|
|
745
929
|
};
|
|
746
930
|
poll();
|
|
747
931
|
const timer = setInterval(poll, this.pollingInterval);
|
|
748
|
-
|
|
749
|
-
|
|
932
|
+
const unsubscribe = () => {
|
|
933
|
+
active = false;
|
|
750
934
|
clearInterval(timer);
|
|
751
|
-
this.
|
|
935
|
+
this.pollingSubscriptions.delete(unsubscribe);
|
|
752
936
|
};
|
|
937
|
+
this.pollingSubscriptions.add(unsubscribe);
|
|
938
|
+
return unsubscribe;
|
|
753
939
|
}
|
|
754
940
|
buildUrl(path, query) {
|
|
755
941
|
const base = `${this.baseUrl}${path}`;
|
|
@@ -768,10 +954,12 @@ var TicketsApi = class {
|
|
|
768
954
|
}
|
|
769
955
|
/** List tickets for the identified customer. */
|
|
770
956
|
async list(params) {
|
|
957
|
+
const assertCurrent = this.chat.captureSession();
|
|
771
958
|
if (this.chat.getAuthMode() === null) {
|
|
772
959
|
throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
|
|
773
960
|
}
|
|
774
961
|
const teamId = await this.chat.ensureTeamId();
|
|
962
|
+
assertCurrent();
|
|
775
963
|
const response = await this.chat.fetch("/api/widget/conversations", {
|
|
776
964
|
method: "GET",
|
|
777
965
|
query: {
|
|
@@ -780,6 +968,7 @@ var TicketsApi = class {
|
|
|
780
968
|
cursor: params?.cursor
|
|
781
969
|
}
|
|
782
970
|
});
|
|
971
|
+
assertCurrent();
|
|
783
972
|
const data = response.data.tickets ?? response.data.conversations ?? [];
|
|
784
973
|
const cursor = response.data.cursor;
|
|
785
974
|
const hasMore = response.data.hasMore;
|
|
@@ -791,10 +980,12 @@ var TicketsApi = class {
|
|
|
791
980
|
}
|
|
792
981
|
/** Create a new ticket. */
|
|
793
982
|
async create(params) {
|
|
983
|
+
const assertCurrent = this.chat.captureSession();
|
|
794
984
|
if (this.chat.getAuthMode() === null) {
|
|
795
985
|
throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
|
|
796
986
|
}
|
|
797
987
|
const teamId = await this.chat.ensureTeamId();
|
|
988
|
+
assertCurrent();
|
|
798
989
|
const response = await this.chat.fetch("/api/widget/tickets", {
|
|
799
990
|
method: "POST",
|
|
800
991
|
body: {
|
|
@@ -803,6 +994,7 @@ var TicketsApi = class {
|
|
|
803
994
|
message: params.message
|
|
804
995
|
}
|
|
805
996
|
});
|
|
997
|
+
assertCurrent();
|
|
806
998
|
const messages = (response.data.messages ?? []).map((m) => ({
|
|
807
999
|
id: m.id,
|
|
808
1000
|
content: m.content,
|
|
@@ -961,6 +1153,7 @@ var MessagesApi = class {
|
|
|
961
1153
|
}
|
|
962
1154
|
/** List messages for a ticket. */
|
|
963
1155
|
async list(ticketId) {
|
|
1156
|
+
const assertCurrent = this.chat.captureSession();
|
|
964
1157
|
const teamId = this.chat.getTeamId();
|
|
965
1158
|
const response = await this.chat.fetch(
|
|
966
1159
|
"/api/widget/messages",
|
|
@@ -972,10 +1165,12 @@ var MessagesApi = class {
|
|
|
972
1165
|
}
|
|
973
1166
|
}
|
|
974
1167
|
);
|
|
1168
|
+
assertCurrent();
|
|
975
1169
|
return response.data.messages;
|
|
976
1170
|
}
|
|
977
1171
|
/** Send a message to a ticket. */
|
|
978
1172
|
async send(ticketId, content) {
|
|
1173
|
+
const assertCurrent = this.chat.captureSession();
|
|
979
1174
|
const teamId = this.chat.getTeamId();
|
|
980
1175
|
const response = await this.chat.fetch(
|
|
981
1176
|
"/api/widget/messages",
|
|
@@ -988,6 +1183,7 @@ var MessagesApi = class {
|
|
|
988
1183
|
}
|
|
989
1184
|
}
|
|
990
1185
|
);
|
|
1186
|
+
assertCurrent();
|
|
991
1187
|
return response.data.message;
|
|
992
1188
|
}
|
|
993
1189
|
};
|