@cstar.help/js 0.16.1 → 0.17.2
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 +427 -101
- package/dist/chat/index.d.cts +44 -92
- package/dist/chat/index.d.ts +44 -92
- package/dist/chat/index.js +427 -101
- 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 +42 -23
- package/dist/index.d.cts +24 -802
- package/dist/index.d.ts +24 -802
- package/dist/index.js +42 -23
- 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
|
@@ -40,6 +40,28 @@ __export(chat_exports, {
|
|
|
40
40
|
module.exports = __toCommonJS(chat_exports);
|
|
41
41
|
|
|
42
42
|
// src/chat/realtime.ts
|
|
43
|
+
var channelRemovals = /* @__PURE__ */ new WeakMap();
|
|
44
|
+
var CHANNEL_DRAIN_TIMEOUT_MS = 5e3;
|
|
45
|
+
function removeRealtimeChannel(client, channel) {
|
|
46
|
+
const pending = channelRemovals.get(client) ?? /* @__PURE__ */ new Set();
|
|
47
|
+
channelRemovals.set(client, pending);
|
|
48
|
+
const removal = client.removeChannel(channel);
|
|
49
|
+
pending.add(removal);
|
|
50
|
+
const settled = () => pending.delete(removal);
|
|
51
|
+
void removal.then(settled, settled);
|
|
52
|
+
}
|
|
53
|
+
async function closeRealtimeClient(client) {
|
|
54
|
+
const pending = [...channelRemovals.get(client) ?? []];
|
|
55
|
+
if (pending.length) {
|
|
56
|
+
let timeout;
|
|
57
|
+
const deadline = new Promise((resolve) => {
|
|
58
|
+
timeout = setTimeout(resolve, CHANNEL_DRAIN_TIMEOUT_MS);
|
|
59
|
+
});
|
|
60
|
+
await Promise.race([Promise.allSettled(pending), deadline]);
|
|
61
|
+
clearTimeout(timeout);
|
|
62
|
+
}
|
|
63
|
+
await client.realtime.disconnect();
|
|
64
|
+
}
|
|
43
65
|
var RealtimeManager = class {
|
|
44
66
|
constructor(options) {
|
|
45
67
|
this.client = null;
|
|
@@ -47,6 +69,8 @@ var RealtimeManager = class {
|
|
|
47
69
|
this.channels = /* @__PURE__ */ new Map();
|
|
48
70
|
this.refreshTimer = null;
|
|
49
71
|
this.currentToken = null;
|
|
72
|
+
this.destroyed = false;
|
|
73
|
+
this.cleanupPromise = null;
|
|
50
74
|
this.options = options;
|
|
51
75
|
}
|
|
52
76
|
/**
|
|
@@ -54,6 +78,7 @@ var RealtimeManager = class {
|
|
|
54
78
|
* Returns true if successful, false if the realtime runtime is unavailable.
|
|
55
79
|
*/
|
|
56
80
|
init() {
|
|
81
|
+
if (this.destroyed) return Promise.resolve(false);
|
|
57
82
|
if (this.initPromise) return this.initPromise;
|
|
58
83
|
this.initPromise = this.doInit();
|
|
59
84
|
return this.initPromise;
|
|
@@ -61,11 +86,17 @@ var RealtimeManager = class {
|
|
|
61
86
|
async doInit() {
|
|
62
87
|
try {
|
|
63
88
|
const tokenData = await this.options.fetchToken();
|
|
89
|
+
if (this.destroyed) return false;
|
|
64
90
|
const { createClient } = await import(
|
|
65
91
|
/* webpackIgnore: true */
|
|
66
92
|
"@supabase/supabase-js"
|
|
67
93
|
);
|
|
94
|
+
if (this.destroyed) return false;
|
|
95
|
+
this.currentToken = tokenData.token;
|
|
68
96
|
this.client = createClient(tokenData.realtimeUrl, tokenData.realtimeKey, {
|
|
97
|
+
// Reconnects and heartbeats must refresh from cStar's customer JWT,
|
|
98
|
+
// rather than the dedicated client's empty hosted-auth session.
|
|
99
|
+
accessToken: async () => this.currentToken,
|
|
69
100
|
auth: { persistSession: false, autoRefreshToken: false },
|
|
70
101
|
realtime: { params: { eventsPerSecond: 10 } }
|
|
71
102
|
});
|
|
@@ -92,10 +123,13 @@ var RealtimeManager = class {
|
|
|
92
123
|
this.refreshTimer = setTimeout(() => this.doRefresh(), delay);
|
|
93
124
|
}
|
|
94
125
|
async doRefresh() {
|
|
126
|
+
if (this.destroyed) return;
|
|
95
127
|
try {
|
|
96
128
|
const tokenData = await this.options.fetchToken();
|
|
129
|
+
if (this.destroyed) return;
|
|
97
130
|
this.setToken(tokenData);
|
|
98
131
|
} catch {
|
|
132
|
+
if (this.destroyed) return;
|
|
99
133
|
this.refreshTimer = setTimeout(() => this.doRefresh(), 3e4);
|
|
100
134
|
}
|
|
101
135
|
}
|
|
@@ -103,12 +137,14 @@ var RealtimeManager = class {
|
|
|
103
137
|
* Subscribe to new messages on a ticket via Postgres Changes.
|
|
104
138
|
* Returns an unsubscribe function.
|
|
105
139
|
*/
|
|
106
|
-
subscribeToMessages(ticketId, callback) {
|
|
140
|
+
subscribeToMessages(ticketId, callback, onStatus) {
|
|
107
141
|
if (!this.client) return () => {
|
|
108
142
|
};
|
|
109
143
|
const channelName = `messages:${ticketId}`;
|
|
110
144
|
this.removeChannel(channelName);
|
|
111
|
-
const channel = this.client.channel(channelName)
|
|
145
|
+
const channel = this.client.channel(channelName);
|
|
146
|
+
this.channels.set(channelName, channel);
|
|
147
|
+
channel.on(
|
|
112
148
|
"postgres_changes",
|
|
113
149
|
{
|
|
114
150
|
event: "INSERT",
|
|
@@ -117,6 +153,7 @@ var RealtimeManager = class {
|
|
|
117
153
|
filter: `ticket_id=eq.${ticketId}`
|
|
118
154
|
},
|
|
119
155
|
(payload) => {
|
|
156
|
+
if (this.channels.get(channelName) !== channel) return;
|
|
120
157
|
const row = payload.new;
|
|
121
158
|
if (!row) return;
|
|
122
159
|
const message = {
|
|
@@ -131,9 +168,10 @@ var RealtimeManager = class {
|
|
|
131
168
|
};
|
|
132
169
|
callback(message);
|
|
133
170
|
}
|
|
134
|
-
).subscribe()
|
|
135
|
-
|
|
136
|
-
|
|
171
|
+
).subscribe((status) => {
|
|
172
|
+
if (this.channels.get(channelName) === channel) onStatus?.(status);
|
|
173
|
+
});
|
|
174
|
+
return () => this.removeChannel(channelName, channel);
|
|
137
175
|
}
|
|
138
176
|
/** Get the underlying realtime client (for sharing with PresenceManager). */
|
|
139
177
|
getClient() {
|
|
@@ -151,15 +189,18 @@ var RealtimeManager = class {
|
|
|
151
189
|
get isReady() {
|
|
152
190
|
return this.client !== null && this.currentToken !== null;
|
|
153
191
|
}
|
|
154
|
-
removeChannel(name) {
|
|
192
|
+
removeChannel(name, expected) {
|
|
155
193
|
const existing = this.channels.get(name);
|
|
194
|
+
if (expected && existing !== expected) return;
|
|
156
195
|
if (existing && this.client) {
|
|
157
|
-
this.client.removeChannel(existing);
|
|
158
196
|
this.channels.delete(name);
|
|
197
|
+
removeRealtimeChannel(this.client, existing);
|
|
159
198
|
}
|
|
160
199
|
}
|
|
161
200
|
/** Clean up all channels, timers, and the client. */
|
|
162
201
|
destroy() {
|
|
202
|
+
if (this.destroyed) return this.cleanupPromise ?? Promise.resolve();
|
|
203
|
+
this.destroyed = true;
|
|
163
204
|
if (this.refreshTimer) {
|
|
164
205
|
clearTimeout(this.refreshTimer);
|
|
165
206
|
this.refreshTimer = null;
|
|
@@ -168,9 +209,13 @@ var RealtimeManager = class {
|
|
|
168
209
|
this.removeChannel(name);
|
|
169
210
|
}
|
|
170
211
|
this.channels.clear();
|
|
212
|
+
const client = this.client;
|
|
171
213
|
this.client = null;
|
|
172
214
|
this.initPromise = null;
|
|
173
215
|
this.currentToken = null;
|
|
216
|
+
this.cleanupPromise = client ? closeRealtimeClient(client).catch(() => {
|
|
217
|
+
}) : Promise.resolve();
|
|
218
|
+
return this.cleanupPromise;
|
|
174
219
|
}
|
|
175
220
|
};
|
|
176
221
|
|
|
@@ -200,7 +245,10 @@ var PresenceManager = class {
|
|
|
200
245
|
};
|
|
201
246
|
const channelName = `typing:${ticketId}`;
|
|
202
247
|
this.removeChannel(channelName);
|
|
203
|
-
const channel = this.client.channel(`ticket:${ticketId}`)
|
|
248
|
+
const channel = this.client.channel(`ticket:${ticketId}`);
|
|
249
|
+
this.channels.set(channelName, channel);
|
|
250
|
+
channel.on("broadcast", { event: "typing" }, (data) => {
|
|
251
|
+
if (this.channels.get(channelName) !== channel) return;
|
|
204
252
|
const payload = data?.payload;
|
|
205
253
|
if (!payload) return;
|
|
206
254
|
if (payload.agent_id) {
|
|
@@ -212,8 +260,7 @@ var PresenceManager = class {
|
|
|
212
260
|
callback(typingPayload);
|
|
213
261
|
}
|
|
214
262
|
}).subscribe();
|
|
215
|
-
this.
|
|
216
|
-
return () => this.removeChannel(channelName);
|
|
263
|
+
return () => this.removeChannel(channelName, channel);
|
|
217
264
|
}
|
|
218
265
|
/**
|
|
219
266
|
* Send a customer typing indicator.
|
|
@@ -266,11 +313,12 @@ var PresenceManager = class {
|
|
|
266
313
|
} catch {
|
|
267
314
|
}
|
|
268
315
|
}
|
|
269
|
-
removeChannel(name) {
|
|
316
|
+
removeChannel(name, expected) {
|
|
270
317
|
const existing = this.channels.get(name);
|
|
318
|
+
if (expected && existing !== expected) return;
|
|
271
319
|
if (existing && this.client) {
|
|
272
|
-
this.client.removeChannel(existing);
|
|
273
320
|
this.channels.delete(name);
|
|
321
|
+
removeRealtimeChannel(this.client, existing);
|
|
274
322
|
}
|
|
275
323
|
}
|
|
276
324
|
/** Clean up all channels and timers. */
|
|
@@ -333,6 +381,94 @@ var ChatClientNetworkError = class extends ChatClientError {
|
|
|
333
381
|
}
|
|
334
382
|
};
|
|
335
383
|
|
|
384
|
+
// src/chat/attachments.ts
|
|
385
|
+
var UUID = /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i;
|
|
386
|
+
function downloadFilename(header) {
|
|
387
|
+
const extended = header?.match(/(?:^|;)\s*filename\*\s*=\s*UTF-8''([^;]*)/i);
|
|
388
|
+
let name = "";
|
|
389
|
+
if (extended) {
|
|
390
|
+
try {
|
|
391
|
+
name = decodeURIComponent(extended[1]);
|
|
392
|
+
} catch {
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (!name) {
|
|
396
|
+
const fallback = header?.match(/(?:^|;)\s*filename\s*=\s*(?:"((?:\\.|[^"\\])*)"|([^;]*))/i);
|
|
397
|
+
name = fallback?.[1]?.replace(/\\(.)/g, "$1") ?? fallback?.[2] ?? "";
|
|
398
|
+
}
|
|
399
|
+
return name.split(/[\\/]/).pop()?.replace(/[\x00-\x1f\x7f]/g, "").replace(/^[\s.]+|[\s.]+$/g, "") || "download";
|
|
400
|
+
}
|
|
401
|
+
var AttachmentsApi = class {
|
|
402
|
+
constructor(chat) {
|
|
403
|
+
this.chat = chat;
|
|
404
|
+
}
|
|
405
|
+
async download(reference, options = {}) {
|
|
406
|
+
const assertCurrent = this.chat.captureSession();
|
|
407
|
+
if (!this.chat.getAuthMode())
|
|
408
|
+
throw new ChatClientAuthError("Sign in before downloading a file.");
|
|
409
|
+
const { ticketId, messageId, attachmentIndex } = reference;
|
|
410
|
+
if (typeof ticketId !== "string" || typeof messageId !== "string" || !UUID.test(ticketId) || !UUID.test(messageId) || !Number.isSafeInteger(attachmentIndex) || attachmentIndex < 0) {
|
|
411
|
+
throw new TypeError("A saved ticket, message and nonnegative attachment index are required.");
|
|
412
|
+
}
|
|
413
|
+
const controller = new AbortController();
|
|
414
|
+
const signal = options.signal;
|
|
415
|
+
const download = options.download === false ? "0" : "1";
|
|
416
|
+
const abort = () => controller.abort();
|
|
417
|
+
const assertOwned = () => {
|
|
418
|
+
assertCurrent();
|
|
419
|
+
controller.signal.throwIfAborted();
|
|
420
|
+
};
|
|
421
|
+
const unsubscribe = this.chat.subscribe(() => {
|
|
422
|
+
try {
|
|
423
|
+
assertCurrent();
|
|
424
|
+
} catch {
|
|
425
|
+
abort();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
429
|
+
if (signal?.aborted) abort();
|
|
430
|
+
try {
|
|
431
|
+
assertOwned();
|
|
432
|
+
const teamId = await this.chat.ensureTeamId(controller.signal);
|
|
433
|
+
assertOwned();
|
|
434
|
+
const result = await this.chat.fetch(
|
|
435
|
+
"/api/widget/attachments/download",
|
|
436
|
+
{
|
|
437
|
+
method: "GET",
|
|
438
|
+
query: {
|
|
439
|
+
teamId,
|
|
440
|
+
ticketId,
|
|
441
|
+
messageId,
|
|
442
|
+
attachmentIndex: String(attachmentIndex),
|
|
443
|
+
download
|
|
444
|
+
},
|
|
445
|
+
signal: controller.signal,
|
|
446
|
+
cache: "no-store",
|
|
447
|
+
readBody: async (response) => {
|
|
448
|
+
assertOwned();
|
|
449
|
+
const blob = await response.blob();
|
|
450
|
+
assertOwned();
|
|
451
|
+
return {
|
|
452
|
+
blob,
|
|
453
|
+
filename: downloadFilename(response.headers.get("Content-Disposition")),
|
|
454
|
+
contentType: blob.type.split(";")[0] || "application/octet-stream",
|
|
455
|
+
size: blob.size
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
assertOwned();
|
|
461
|
+
return result;
|
|
462
|
+
} catch (error) {
|
|
463
|
+
assertOwned();
|
|
464
|
+
throw error;
|
|
465
|
+
} finally {
|
|
466
|
+
unsubscribe();
|
|
467
|
+
signal?.removeEventListener("abort", abort);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
|
|
336
472
|
// src/chat/index.ts
|
|
337
473
|
var DEFAULT_BASE_URL = "https://www.cstar.help";
|
|
338
474
|
var DEFAULT_POLLING_INTERVAL = 3e3;
|
|
@@ -342,7 +478,9 @@ var ChatClient = class {
|
|
|
342
478
|
this.teamId = null;
|
|
343
479
|
this.customerId = null;
|
|
344
480
|
this.authMode = null;
|
|
345
|
-
this.
|
|
481
|
+
this.sessionGeneration = 0;
|
|
482
|
+
this.pollingSubscriptions = /* @__PURE__ */ new Set();
|
|
483
|
+
this.realtimeMessageSubscriptions = /* @__PURE__ */ new Map();
|
|
346
484
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
347
485
|
this.realtimeManager = null;
|
|
348
486
|
this.presenceManager = null;
|
|
@@ -354,6 +492,7 @@ var ChatClient = class {
|
|
|
354
492
|
this.realtimeEnabled = config.realtime !== false;
|
|
355
493
|
this.tickets = new TicketsApi(this);
|
|
356
494
|
this.messages = new MessagesApi(this);
|
|
495
|
+
this.attachments = new AttachmentsApi(this);
|
|
357
496
|
this.community = new ChatCommunityApi(this);
|
|
358
497
|
}
|
|
359
498
|
/**
|
|
@@ -368,6 +507,8 @@ var ChatClient = class {
|
|
|
368
507
|
* @param testMode - Whether to use test mode (relaxed timestamp validation)
|
|
369
508
|
*/
|
|
370
509
|
async identify(customer, signature, testMode = false) {
|
|
510
|
+
const generation = this.retireSession();
|
|
511
|
+
this.assertSession(generation);
|
|
371
512
|
const response = await this.fetch(
|
|
372
513
|
"/api/widget/verify-identity",
|
|
373
514
|
{
|
|
@@ -380,15 +521,18 @@ var ChatClient = class {
|
|
|
380
521
|
}
|
|
381
522
|
}
|
|
382
523
|
);
|
|
524
|
+
this.assertSession(generation);
|
|
383
525
|
this.sessionToken = response.data.sessionToken;
|
|
384
526
|
this.teamId = response.data.teamId;
|
|
385
527
|
this.customerId = response.data.customer.id;
|
|
386
528
|
this.authMode = "hmac";
|
|
387
529
|
this.notifyStateChanged();
|
|
530
|
+
this.assertSession(generation);
|
|
388
531
|
if (this.realtimeEnabled) {
|
|
389
532
|
this.realtimeInitPromise = this.initRealtime(
|
|
390
533
|
response.data.customer.id,
|
|
391
|
-
response.data.customer.name || customer.name || customer.email
|
|
534
|
+
response.data.customer.name || customer.name || customer.email,
|
|
535
|
+
generation
|
|
392
536
|
);
|
|
393
537
|
}
|
|
394
538
|
return response.data;
|
|
@@ -403,11 +547,15 @@ var ChatClient = class {
|
|
|
403
547
|
* Supabase session, use `loginWithSession({ accessToken })` instead.
|
|
404
548
|
*/
|
|
405
549
|
async loginWithCredentials(params) {
|
|
550
|
+
const generation = this.retireSession();
|
|
551
|
+
this.assertSession(generation);
|
|
406
552
|
const teamId = await this.resolveTeamIdFromSlug();
|
|
553
|
+
this.assertSession(generation);
|
|
407
554
|
const response = await this.fetch("/api/widget/auth/login", {
|
|
408
555
|
method: "POST",
|
|
409
556
|
body: { email: params.email, password: params.password, teamId }
|
|
410
557
|
});
|
|
558
|
+
this.assertSession(generation);
|
|
411
559
|
if (!response.accessToken) {
|
|
412
560
|
throw new ChatClientAuthError({
|
|
413
561
|
message: "Login succeeded but no session was issued",
|
|
@@ -416,11 +564,14 @@ var ChatClient = class {
|
|
|
416
564
|
requestId: null
|
|
417
565
|
});
|
|
418
566
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
567
|
+
this.applySession(
|
|
568
|
+
{
|
|
569
|
+
accessToken: response.accessToken,
|
|
570
|
+
teamId,
|
|
571
|
+
customerName: response.customer.name ?? response.customer.email
|
|
572
|
+
},
|
|
573
|
+
generation
|
|
574
|
+
);
|
|
424
575
|
return {
|
|
425
576
|
customer: {
|
|
426
577
|
id: this.customerId ?? response.customer.id,
|
|
@@ -439,15 +590,22 @@ var ChatClient = class {
|
|
|
439
590
|
* every request — there is no client-side validation here.
|
|
440
591
|
*/
|
|
441
592
|
async loginWithSession(params) {
|
|
593
|
+
const teamId = params.teamId ?? this.teamId;
|
|
594
|
+
const generation = this.retireSession();
|
|
595
|
+
this.applySession({ ...params, ...teamId ? { teamId } : {} }, generation);
|
|
596
|
+
}
|
|
597
|
+
applySession(params, generation) {
|
|
598
|
+
this.assertSession(generation);
|
|
442
599
|
this.sessionToken = params.accessToken;
|
|
443
600
|
if (params.teamId) {
|
|
444
601
|
this.teamId = params.teamId;
|
|
445
602
|
}
|
|
446
603
|
this.authMode = "session";
|
|
447
604
|
this.notifyStateChanged();
|
|
605
|
+
this.assertSession(generation);
|
|
448
606
|
if (this.realtimeEnabled) {
|
|
449
607
|
const customerName = params.customerName ?? "";
|
|
450
|
-
this.realtimeInitPromise = this.initRealtimeFromSession(customerName);
|
|
608
|
+
this.realtimeInitPromise = this.initRealtimeFromSession(customerName, generation);
|
|
451
609
|
}
|
|
452
610
|
}
|
|
453
611
|
/**
|
|
@@ -489,7 +647,15 @@ var ChatClient = class {
|
|
|
489
647
|
*/
|
|
490
648
|
onMessage(ticketId, callback) {
|
|
491
649
|
if (this.realtimeReady && this.realtimeManager) {
|
|
492
|
-
|
|
650
|
+
this.realtimeMessageSubscriptions.get(ticketId)?.();
|
|
651
|
+
const unsubscribe = this.pollMessages(ticketId, callback, this.realtimeManager);
|
|
652
|
+
this.realtimeMessageSubscriptions.set(ticketId, unsubscribe);
|
|
653
|
+
return () => {
|
|
654
|
+
unsubscribe();
|
|
655
|
+
if (this.realtimeMessageSubscriptions.get(ticketId) === unsubscribe) {
|
|
656
|
+
this.realtimeMessageSubscriptions.delete(ticketId);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
493
659
|
}
|
|
494
660
|
return this.pollMessages(ticketId, callback);
|
|
495
661
|
}
|
|
@@ -500,8 +666,11 @@ var ChatClient = class {
|
|
|
500
666
|
* @returns Unsubscribe function
|
|
501
667
|
*/
|
|
502
668
|
onTyping(ticketId, callback) {
|
|
669
|
+
const generation = this.sessionGeneration;
|
|
503
670
|
if (this.realtimeReady && this.presenceManager) {
|
|
504
|
-
return this.presenceManager.subscribeToTyping(ticketId,
|
|
671
|
+
return this.presenceManager.subscribeToTyping(ticketId, (typing) => {
|
|
672
|
+
if (generation === this.sessionGeneration) callback(typing);
|
|
673
|
+
});
|
|
505
674
|
}
|
|
506
675
|
return () => {
|
|
507
676
|
};
|
|
@@ -517,25 +686,44 @@ var ChatClient = class {
|
|
|
517
686
|
}
|
|
518
687
|
/** Disconnect all subscriptions and clean up. */
|
|
519
688
|
disconnect() {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
689
|
+
this.retireSession();
|
|
690
|
+
}
|
|
691
|
+
retireSession() {
|
|
692
|
+
const generation = ++this.sessionGeneration;
|
|
524
693
|
if (this.presenceManager) {
|
|
525
694
|
this.presenceManager.destroy();
|
|
526
695
|
this.presenceManager = null;
|
|
527
696
|
}
|
|
697
|
+
if (this.realtimeManager) {
|
|
698
|
+
this.realtimeManager.destroy();
|
|
699
|
+
this.realtimeManager = null;
|
|
700
|
+
}
|
|
528
701
|
this.realtimeReady = false;
|
|
529
702
|
this.realtimeInitPromise = null;
|
|
530
|
-
for (const
|
|
531
|
-
|
|
703
|
+
for (const unsubscribe of this.pollingSubscriptions) {
|
|
704
|
+
unsubscribe();
|
|
532
705
|
}
|
|
533
|
-
this.
|
|
706
|
+
this.pollingSubscriptions.clear();
|
|
707
|
+
this.realtimeMessageSubscriptions.clear();
|
|
534
708
|
this.sessionToken = null;
|
|
535
709
|
this.teamId = null;
|
|
536
710
|
this.customerId = null;
|
|
537
711
|
this.authMode = null;
|
|
538
712
|
this.notifyStateChanged();
|
|
713
|
+
return generation;
|
|
714
|
+
}
|
|
715
|
+
assertSession(generation) {
|
|
716
|
+
if (generation !== this.sessionGeneration) {
|
|
717
|
+
throw new ChatClientAuthError({
|
|
718
|
+
code: "SESSION_CHANGED",
|
|
719
|
+
message: "Your sign-in session changed. This operation belongs to the previous session."
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/** @internal Keep a public operation owned across its asynchronous prerequisites. */
|
|
724
|
+
captureSession() {
|
|
725
|
+
const generation = this.sessionGeneration;
|
|
726
|
+
return () => this.assertSession(generation);
|
|
539
727
|
}
|
|
540
728
|
/**
|
|
541
729
|
* @internal Make an authenticated request to the widget API.
|
|
@@ -550,6 +738,12 @@ var ChatClient = class {
|
|
|
550
738
|
* server-confirmed mode.
|
|
551
739
|
*/
|
|
552
740
|
async fetch(path, options) {
|
|
741
|
+
const generation = this.sessionGeneration;
|
|
742
|
+
const assertRequest = () => {
|
|
743
|
+
this.assertSession(generation);
|
|
744
|
+
options.signal?.throwIfAborted();
|
|
745
|
+
};
|
|
746
|
+
assertRequest();
|
|
553
747
|
const url = this.buildUrl(path, options.query);
|
|
554
748
|
const headers = {
|
|
555
749
|
"Content-Type": "application/json"
|
|
@@ -562,14 +756,18 @@ var ChatClient = class {
|
|
|
562
756
|
response = await globalThis.fetch(url, {
|
|
563
757
|
method: options.method,
|
|
564
758
|
headers,
|
|
759
|
+
...options.signal ? { signal: options.signal } : {},
|
|
760
|
+
...options.cache ? { cache: options.cache } : {},
|
|
565
761
|
body: options.body ? JSON.stringify(options.body) : void 0
|
|
566
762
|
});
|
|
567
763
|
} catch (networkErr) {
|
|
764
|
+
assertRequest();
|
|
568
765
|
throw new ChatClientNetworkError({
|
|
569
766
|
message: networkErr instanceof Error ? networkErr.message : "Network request failed",
|
|
570
767
|
originalError: networkErr
|
|
571
768
|
});
|
|
572
769
|
}
|
|
770
|
+
assertRequest();
|
|
573
771
|
const responseHeaders = response.headers && typeof response.headers.get === "function" ? response.headers : null;
|
|
574
772
|
const headerMode = responseHeaders?.get("X-Cstar-Auth-Mode") ?? null;
|
|
575
773
|
if (headerMode === "hmac" || headerMode === "session") {
|
|
@@ -577,9 +775,11 @@ var ChatClient = class {
|
|
|
577
775
|
this.authMode = headerMode;
|
|
578
776
|
if (previous !== headerMode) this.notifyStateChanged();
|
|
579
777
|
}
|
|
778
|
+
assertRequest();
|
|
580
779
|
const requestId = responseHeaders?.get("X-Request-Id") ?? null;
|
|
581
780
|
if (!response.ok) {
|
|
582
781
|
const errorBody = await response.json().catch(() => ({ error: { code: "UNKNOWN", message: "Request failed" } }));
|
|
782
|
+
assertRequest();
|
|
583
783
|
const code = errorBody.error?.code ?? "UNKNOWN";
|
|
584
784
|
const message = errorBody.error?.message ?? `Widget API error: ${response.status}`;
|
|
585
785
|
if (response.status === 401 || response.status === 403) {
|
|
@@ -616,7 +816,14 @@ var ChatClient = class {
|
|
|
616
816
|
requestId
|
|
617
817
|
});
|
|
618
818
|
}
|
|
619
|
-
|
|
819
|
+
try {
|
|
820
|
+
const body = options.readBody ? await options.readBody(response) : await response.json();
|
|
821
|
+
assertRequest();
|
|
822
|
+
return body;
|
|
823
|
+
} catch (error) {
|
|
824
|
+
assertRequest();
|
|
825
|
+
throw error;
|
|
826
|
+
}
|
|
620
827
|
}
|
|
621
828
|
/**
|
|
622
829
|
* Look up the team UUID from the configured `teamSlug`. Used by
|
|
@@ -625,37 +832,41 @@ var ChatClient = class {
|
|
|
625
832
|
*
|
|
626
833
|
* @internal
|
|
627
834
|
*/
|
|
628
|
-
async resolveTeamIdFromSlug() {
|
|
835
|
+
async resolveTeamIdFromSlug(signal) {
|
|
629
836
|
const response = await this.fetch(
|
|
630
837
|
"/api/widget/team-by-slug",
|
|
631
838
|
{
|
|
632
839
|
method: "GET",
|
|
840
|
+
signal,
|
|
633
841
|
query: { teamSlug: this.teamSlug }
|
|
634
842
|
}
|
|
635
843
|
);
|
|
636
844
|
return response.data.teamId;
|
|
637
845
|
}
|
|
638
846
|
/** @internal Bootstrap realtime from a session login (no precomputed customerId). */
|
|
639
|
-
async initRealtimeFromSession(customerName) {
|
|
847
|
+
async initRealtimeFromSession(customerName, generation) {
|
|
848
|
+
const manager = new RealtimeManager({ fetchToken: () => this.fetchRealtimeToken(generation) });
|
|
849
|
+
this.realtimeManager = manager;
|
|
640
850
|
try {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
851
|
+
const ready = await manager.init();
|
|
852
|
+
if (generation !== this.sessionGeneration || this.realtimeManager !== manager) {
|
|
853
|
+
manager.destroy();
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
645
856
|
if (!ready) return;
|
|
646
857
|
this.realtimeReady = true;
|
|
647
|
-
const token =
|
|
858
|
+
const token = manager.getCurrentToken();
|
|
648
859
|
const customerId = token ? decodeJwtSub(token) : null;
|
|
649
860
|
if (customerId) {
|
|
650
861
|
this.customerId = customerId;
|
|
651
862
|
this.presenceManager = new PresenceManager({ customerId, customerName });
|
|
652
|
-
const client =
|
|
863
|
+
const client = manager.getClient();
|
|
653
864
|
if (client) {
|
|
654
865
|
this.presenceManager.setClient(client);
|
|
655
866
|
}
|
|
656
867
|
}
|
|
657
868
|
} catch {
|
|
658
|
-
this.realtimeReady = false;
|
|
869
|
+
if (generation === this.sessionGeneration) this.realtimeReady = false;
|
|
659
870
|
}
|
|
660
871
|
}
|
|
661
872
|
/** @internal Get the stored team ID for requests that need it. */
|
|
@@ -664,23 +875,23 @@ var ChatClient = class {
|
|
|
664
875
|
}
|
|
665
876
|
/**
|
|
666
877
|
* Resolve a team ID, lazily looking it up from `teamSlug` if no team ID
|
|
667
|
-
* has been cached yet.
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
* public data.
|
|
671
|
-
*
|
|
672
|
-
* Closes NEW-v3-05 — `TicketsState(chatClient)` previously errored
|
|
673
|
-
* `"Missing teamId"` when the chat client was teamSlug-only because the
|
|
674
|
-
* tickets sub-resource read `getTeamId()` synchronously and bailed.
|
|
878
|
+
* has been cached yet. Authenticated resources and realtime bootstrap
|
|
879
|
+
* use this after session login so callers do not need to list tickets
|
|
880
|
+
* or set a UUID before opening an existing conversation.
|
|
675
881
|
*/
|
|
676
|
-
async ensureTeamId() {
|
|
882
|
+
async ensureTeamId(signal) {
|
|
883
|
+
const assertCurrent = this.captureSession();
|
|
884
|
+
signal?.throwIfAborted();
|
|
677
885
|
if (this.teamId) return this.teamId;
|
|
678
886
|
if (!this.teamSlug) {
|
|
679
887
|
throw new ChatClientAuthError(
|
|
680
888
|
"Missing teamId. Construct ChatClient with `teamSlug` or call `setTeamId()` / `identify()` / `loginWithCredentials()` first."
|
|
681
889
|
);
|
|
682
890
|
}
|
|
683
|
-
|
|
891
|
+
const teamId = await this.resolveTeamIdFromSlug(signal);
|
|
892
|
+
assertCurrent();
|
|
893
|
+
signal?.throwIfAborted();
|
|
894
|
+
this.teamId = teamId;
|
|
684
895
|
return this.teamId;
|
|
685
896
|
}
|
|
686
897
|
/** @internal Set team ID from identify response or other source. */
|
|
@@ -693,63 +904,116 @@ var ChatClient = class {
|
|
|
693
904
|
await this.realtimeInitPromise;
|
|
694
905
|
}
|
|
695
906
|
}
|
|
696
|
-
async initRealtime(customerId, customerName) {
|
|
907
|
+
async initRealtime(customerId, customerName, generation) {
|
|
908
|
+
const manager = new RealtimeManager({ fetchToken: () => this.fetchRealtimeToken(generation) });
|
|
909
|
+
this.realtimeManager = manager;
|
|
697
910
|
try {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
911
|
+
const ready = await manager.init();
|
|
912
|
+
if (generation !== this.sessionGeneration || this.realtimeManager !== manager) {
|
|
913
|
+
manager.destroy();
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
702
916
|
if (ready) {
|
|
703
917
|
this.realtimeReady = true;
|
|
704
918
|
this.presenceManager = new PresenceManager({
|
|
705
919
|
customerId,
|
|
706
920
|
customerName
|
|
707
921
|
});
|
|
708
|
-
const client =
|
|
922
|
+
const client = manager.getClient();
|
|
709
923
|
if (client) {
|
|
710
924
|
this.presenceManager.setClient(client);
|
|
711
925
|
}
|
|
712
926
|
}
|
|
713
927
|
} catch {
|
|
714
|
-
this.realtimeReady = false;
|
|
928
|
+
if (generation === this.sessionGeneration) this.realtimeReady = false;
|
|
715
929
|
}
|
|
716
930
|
}
|
|
717
|
-
async fetchRealtimeToken() {
|
|
931
|
+
async fetchRealtimeToken(generation) {
|
|
932
|
+
this.assertSession(generation);
|
|
933
|
+
const teamId = await this.ensureTeamId();
|
|
934
|
+
this.assertSession(generation);
|
|
718
935
|
const response = await this.fetch(
|
|
719
936
|
"/api/widget/realtime-token",
|
|
720
937
|
{
|
|
721
938
|
method: "POST",
|
|
722
|
-
body: { teamId
|
|
939
|
+
body: { teamId }
|
|
723
940
|
}
|
|
724
941
|
);
|
|
942
|
+
this.assertSession(generation);
|
|
725
943
|
return response.data;
|
|
726
944
|
}
|
|
727
|
-
pollMessages(ticketId, callback) {
|
|
728
|
-
|
|
729
|
-
|
|
945
|
+
pollMessages(ticketId, callback, manager) {
|
|
946
|
+
const seen = /* @__PURE__ */ new Set();
|
|
947
|
+
let initialized = false;
|
|
948
|
+
let active = true;
|
|
949
|
+
let inFlight = false;
|
|
950
|
+
let catchUpQueued = false;
|
|
951
|
+
let connected = false;
|
|
952
|
+
let timer;
|
|
953
|
+
let stopRealtime;
|
|
954
|
+
const deliver = (message) => {
|
|
955
|
+
if (!active || seen.has(message.id)) return;
|
|
956
|
+
seen.add(message.id);
|
|
957
|
+
callback(message);
|
|
958
|
+
};
|
|
959
|
+
const stopPolling = () => {
|
|
960
|
+
clearInterval(timer);
|
|
961
|
+
timer = void 0;
|
|
962
|
+
};
|
|
963
|
+
const startPolling = () => {
|
|
964
|
+
if (active && timer === void 0) {
|
|
965
|
+
timer = setInterval(() => void poll(), this.pollingInterval);
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
const poll = async (catchUp = false) => {
|
|
969
|
+
if (!active) return;
|
|
970
|
+
if (inFlight) {
|
|
971
|
+
if (catchUp) catchUpQueued = true;
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
inFlight = true;
|
|
730
975
|
try {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
976
|
+
do {
|
|
977
|
+
catchUpQueued = false;
|
|
978
|
+
try {
|
|
979
|
+
const messages = await this.messages.list(ticketId);
|
|
980
|
+
if (!active) return;
|
|
981
|
+
for (const message of messages) {
|
|
982
|
+
if (!active) return;
|
|
983
|
+
if (initialized) deliver(message);
|
|
984
|
+
else seen.add(message.id);
|
|
739
985
|
}
|
|
986
|
+
initialized = true;
|
|
987
|
+
if (connected) stopPolling();
|
|
988
|
+
} catch {
|
|
989
|
+
startPolling();
|
|
740
990
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
991
|
+
} while (active && catchUpQueued);
|
|
992
|
+
} finally {
|
|
993
|
+
inFlight = false;
|
|
744
994
|
}
|
|
745
995
|
};
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
this.pollingTimers.delete(ticketId);
|
|
996
|
+
const unsubscribe = () => {
|
|
997
|
+
active = false;
|
|
998
|
+
stopPolling();
|
|
999
|
+
stopRealtime?.();
|
|
1000
|
+
this.pollingSubscriptions.delete(unsubscribe);
|
|
752
1001
|
};
|
|
1002
|
+
this.pollingSubscriptions.add(unsubscribe);
|
|
1003
|
+
void poll();
|
|
1004
|
+
startPolling();
|
|
1005
|
+
stopRealtime = manager?.subscribeToMessages(ticketId, deliver, (status) => {
|
|
1006
|
+
if (!active) return;
|
|
1007
|
+
if (status === "SUBSCRIBED") {
|
|
1008
|
+
connected = true;
|
|
1009
|
+
stopPolling();
|
|
1010
|
+
} else if (["CHANNEL_ERROR", "TIMED_OUT", "CLOSED"].includes(status)) {
|
|
1011
|
+
connected = false;
|
|
1012
|
+
startPolling();
|
|
1013
|
+
} else return;
|
|
1014
|
+
void poll(true);
|
|
1015
|
+
});
|
|
1016
|
+
return unsubscribe;
|
|
753
1017
|
}
|
|
754
1018
|
buildUrl(path, query) {
|
|
755
1019
|
const base = `${this.baseUrl}${path}`;
|
|
@@ -768,10 +1032,12 @@ var TicketsApi = class {
|
|
|
768
1032
|
}
|
|
769
1033
|
/** List tickets for the identified customer. */
|
|
770
1034
|
async list(params) {
|
|
1035
|
+
const assertCurrent = this.chat.captureSession();
|
|
771
1036
|
if (this.chat.getAuthMode() === null) {
|
|
772
1037
|
throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
|
|
773
1038
|
}
|
|
774
1039
|
const teamId = await this.chat.ensureTeamId();
|
|
1040
|
+
assertCurrent();
|
|
775
1041
|
const response = await this.chat.fetch("/api/widget/conversations", {
|
|
776
1042
|
method: "GET",
|
|
777
1043
|
query: {
|
|
@@ -780,6 +1046,7 @@ var TicketsApi = class {
|
|
|
780
1046
|
cursor: params?.cursor
|
|
781
1047
|
}
|
|
782
1048
|
});
|
|
1049
|
+
assertCurrent();
|
|
783
1050
|
const data = response.data.tickets ?? response.data.conversations ?? [];
|
|
784
1051
|
const cursor = response.data.cursor;
|
|
785
1052
|
const hasMore = response.data.hasMore;
|
|
@@ -791,10 +1058,12 @@ var TicketsApi = class {
|
|
|
791
1058
|
}
|
|
792
1059
|
/** Create a new ticket. */
|
|
793
1060
|
async create(params) {
|
|
1061
|
+
const assertCurrent = this.chat.captureSession();
|
|
794
1062
|
if (this.chat.getAuthMode() === null) {
|
|
795
1063
|
throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
|
|
796
1064
|
}
|
|
797
1065
|
const teamId = await this.chat.ensureTeamId();
|
|
1066
|
+
assertCurrent();
|
|
798
1067
|
const response = await this.chat.fetch("/api/widget/tickets", {
|
|
799
1068
|
method: "POST",
|
|
800
1069
|
body: {
|
|
@@ -803,6 +1072,7 @@ var TicketsApi = class {
|
|
|
803
1072
|
message: params.message
|
|
804
1073
|
}
|
|
805
1074
|
});
|
|
1075
|
+
assertCurrent();
|
|
806
1076
|
const messages = (response.data.messages ?? []).map((m) => ({
|
|
807
1077
|
id: m.id,
|
|
808
1078
|
content: m.content,
|
|
@@ -831,8 +1101,12 @@ var ChatCommunityApi = class {
|
|
|
831
1101
|
}
|
|
832
1102
|
/** Create a new community post as the identified customer. */
|
|
833
1103
|
async createPost(params) {
|
|
834
|
-
const
|
|
835
|
-
if (
|
|
1104
|
+
const assertCurrent = this.chat.captureSession();
|
|
1105
|
+
if (this.chat.getAuthMode() === null) {
|
|
1106
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1107
|
+
}
|
|
1108
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1109
|
+
assertCurrent();
|
|
836
1110
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
837
1111
|
method: "POST",
|
|
838
1112
|
body: {
|
|
@@ -844,12 +1118,17 @@ var ChatCommunityApi = class {
|
|
|
844
1118
|
customFields: params.customFields
|
|
845
1119
|
}
|
|
846
1120
|
});
|
|
1121
|
+
assertCurrent();
|
|
847
1122
|
return response.data.post;
|
|
848
1123
|
}
|
|
849
1124
|
/** Update a post. Only the original author can edit. */
|
|
850
1125
|
async updatePost(postId, params) {
|
|
851
|
-
const
|
|
852
|
-
if (
|
|
1126
|
+
const assertCurrent = this.chat.captureSession();
|
|
1127
|
+
if (this.chat.getAuthMode() === null) {
|
|
1128
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1129
|
+
}
|
|
1130
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1131
|
+
assertCurrent();
|
|
853
1132
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
854
1133
|
method: "POST",
|
|
855
1134
|
body: {
|
|
@@ -861,21 +1140,31 @@ var ChatCommunityApi = class {
|
|
|
861
1140
|
customFields: params.customFields
|
|
862
1141
|
}
|
|
863
1142
|
});
|
|
1143
|
+
assertCurrent();
|
|
864
1144
|
return response.data.post;
|
|
865
1145
|
}
|
|
866
1146
|
/** Delete a post (soft delete). Only the original author can delete. */
|
|
867
1147
|
async deletePost(postId) {
|
|
868
|
-
const
|
|
869
|
-
if (
|
|
1148
|
+
const assertCurrent = this.chat.captureSession();
|
|
1149
|
+
if (this.chat.getAuthMode() === null) {
|
|
1150
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1151
|
+
}
|
|
1152
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1153
|
+
assertCurrent();
|
|
870
1154
|
await this.chat.fetch("/api/widget/community", {
|
|
871
1155
|
method: "POST",
|
|
872
1156
|
body: { teamId, action: "deletePost", postId }
|
|
873
1157
|
});
|
|
1158
|
+
assertCurrent();
|
|
874
1159
|
}
|
|
875
1160
|
/** Update a comment. Only the original author can edit. */
|
|
876
1161
|
async updateComment(postId, commentId, params) {
|
|
877
|
-
const
|
|
878
|
-
if (
|
|
1162
|
+
const assertCurrent = this.chat.captureSession();
|
|
1163
|
+
if (this.chat.getAuthMode() === null) {
|
|
1164
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1165
|
+
}
|
|
1166
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1167
|
+
assertCurrent();
|
|
879
1168
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
880
1169
|
method: "POST",
|
|
881
1170
|
body: {
|
|
@@ -886,31 +1175,46 @@ var ChatCommunityApi = class {
|
|
|
886
1175
|
commentBody: params.body
|
|
887
1176
|
}
|
|
888
1177
|
});
|
|
1178
|
+
assertCurrent();
|
|
889
1179
|
return response.data.comment;
|
|
890
1180
|
}
|
|
891
1181
|
/** Delete a comment (soft delete). Only the original author can delete. */
|
|
892
1182
|
async deleteComment(postId, commentId) {
|
|
893
|
-
const
|
|
894
|
-
if (
|
|
1183
|
+
const assertCurrent = this.chat.captureSession();
|
|
1184
|
+
if (this.chat.getAuthMode() === null) {
|
|
1185
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1186
|
+
}
|
|
1187
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1188
|
+
assertCurrent();
|
|
895
1189
|
await this.chat.fetch("/api/widget/community", {
|
|
896
1190
|
method: "POST",
|
|
897
1191
|
body: { teamId, action: "deleteComment", postId, commentId }
|
|
898
1192
|
});
|
|
1193
|
+
assertCurrent();
|
|
899
1194
|
}
|
|
900
1195
|
/** Toggle vote on a post. Returns new vote state and count. */
|
|
901
1196
|
async vote(postId) {
|
|
902
|
-
const
|
|
903
|
-
if (
|
|
1197
|
+
const assertCurrent = this.chat.captureSession();
|
|
1198
|
+
if (this.chat.getAuthMode() === null) {
|
|
1199
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1200
|
+
}
|
|
1201
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1202
|
+
assertCurrent();
|
|
904
1203
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
905
1204
|
method: "POST",
|
|
906
1205
|
body: { teamId, action: "vote", postId }
|
|
907
1206
|
});
|
|
1207
|
+
assertCurrent();
|
|
908
1208
|
return response.data;
|
|
909
1209
|
}
|
|
910
1210
|
/** Add a comment to a post. Supports 1-level nesting via parentCommentId. */
|
|
911
1211
|
async comment(postId, params) {
|
|
912
|
-
const
|
|
913
|
-
if (
|
|
1212
|
+
const assertCurrent = this.chat.captureSession();
|
|
1213
|
+
if (this.chat.getAuthMode() === null) {
|
|
1214
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1215
|
+
}
|
|
1216
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1217
|
+
assertCurrent();
|
|
914
1218
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
915
1219
|
method: "POST",
|
|
916
1220
|
body: {
|
|
@@ -921,37 +1225,53 @@ var ChatCommunityApi = class {
|
|
|
921
1225
|
parentCommentId: params.parentCommentId ?? null
|
|
922
1226
|
}
|
|
923
1227
|
});
|
|
1228
|
+
assertCurrent();
|
|
924
1229
|
return response.data.comment;
|
|
925
1230
|
}
|
|
926
1231
|
/** Toggle follow on a post. */
|
|
927
1232
|
async follow(postId) {
|
|
928
|
-
const
|
|
929
|
-
if (
|
|
1233
|
+
const assertCurrent = this.chat.captureSession();
|
|
1234
|
+
if (this.chat.getAuthMode() === null) {
|
|
1235
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1236
|
+
}
|
|
1237
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1238
|
+
assertCurrent();
|
|
930
1239
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
931
1240
|
method: "POST",
|
|
932
1241
|
body: { teamId, action: "follow", postId }
|
|
933
1242
|
});
|
|
1243
|
+
assertCurrent();
|
|
934
1244
|
return response.data;
|
|
935
1245
|
}
|
|
936
1246
|
/** Check if the current user has voted on a post. */
|
|
937
1247
|
async getVoteStatus(postId) {
|
|
938
|
-
const
|
|
939
|
-
if (
|
|
1248
|
+
const assertCurrent = this.chat.captureSession();
|
|
1249
|
+
if (this.chat.getAuthMode() === null) {
|
|
1250
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1251
|
+
}
|
|
1252
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1253
|
+
assertCurrent();
|
|
940
1254
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
941
1255
|
method: "GET",
|
|
942
1256
|
query: { teamId, postId }
|
|
943
1257
|
});
|
|
1258
|
+
assertCurrent();
|
|
944
1259
|
return response.data.voted;
|
|
945
1260
|
}
|
|
946
1261
|
/** Check vote status for multiple posts in one request. */
|
|
947
1262
|
async bulkVoteStatus(postIds) {
|
|
948
1263
|
if (postIds.length === 0) return {};
|
|
949
|
-
const
|
|
950
|
-
if (
|
|
1264
|
+
const assertCurrent = this.chat.captureSession();
|
|
1265
|
+
if (this.chat.getAuthMode() === null) {
|
|
1266
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1267
|
+
}
|
|
1268
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1269
|
+
assertCurrent();
|
|
951
1270
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
952
1271
|
method: "GET",
|
|
953
1272
|
query: { teamId, postIds: postIds.join(",") }
|
|
954
1273
|
});
|
|
1274
|
+
assertCurrent();
|
|
955
1275
|
return response.data;
|
|
956
1276
|
}
|
|
957
1277
|
};
|
|
@@ -961,22 +1281,27 @@ var MessagesApi = class {
|
|
|
961
1281
|
}
|
|
962
1282
|
/** List messages for a ticket. */
|
|
963
1283
|
async list(ticketId) {
|
|
964
|
-
const
|
|
1284
|
+
const assertCurrent = this.chat.captureSession();
|
|
1285
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1286
|
+
assertCurrent();
|
|
965
1287
|
const response = await this.chat.fetch(
|
|
966
1288
|
"/api/widget/messages",
|
|
967
1289
|
{
|
|
968
1290
|
method: "GET",
|
|
969
1291
|
query: {
|
|
970
|
-
teamId
|
|
1292
|
+
teamId,
|
|
971
1293
|
ticketId
|
|
972
1294
|
}
|
|
973
1295
|
}
|
|
974
1296
|
);
|
|
1297
|
+
assertCurrent();
|
|
975
1298
|
return response.data.messages;
|
|
976
1299
|
}
|
|
977
1300
|
/** Send a message to a ticket. */
|
|
978
1301
|
async send(ticketId, content) {
|
|
979
|
-
const
|
|
1302
|
+
const assertCurrent = this.chat.captureSession();
|
|
1303
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1304
|
+
assertCurrent();
|
|
980
1305
|
const response = await this.chat.fetch(
|
|
981
1306
|
"/api/widget/messages",
|
|
982
1307
|
{
|
|
@@ -988,6 +1313,7 @@ var MessagesApi = class {
|
|
|
988
1313
|
}
|
|
989
1314
|
}
|
|
990
1315
|
);
|
|
1316
|
+
assertCurrent();
|
|
991
1317
|
return response.data.message;
|
|
992
1318
|
}
|
|
993
1319
|
};
|