@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.js
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
// src/chat/realtime.ts
|
|
2
|
+
var channelRemovals = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
var CHANNEL_DRAIN_TIMEOUT_MS = 5e3;
|
|
4
|
+
function removeRealtimeChannel(client, channel) {
|
|
5
|
+
const pending = channelRemovals.get(client) ?? /* @__PURE__ */ new Set();
|
|
6
|
+
channelRemovals.set(client, pending);
|
|
7
|
+
const removal = client.removeChannel(channel);
|
|
8
|
+
pending.add(removal);
|
|
9
|
+
const settled = () => pending.delete(removal);
|
|
10
|
+
void removal.then(settled, settled);
|
|
11
|
+
}
|
|
12
|
+
async function closeRealtimeClient(client) {
|
|
13
|
+
const pending = [...channelRemovals.get(client) ?? []];
|
|
14
|
+
if (pending.length) {
|
|
15
|
+
let timeout;
|
|
16
|
+
const deadline = new Promise((resolve) => {
|
|
17
|
+
timeout = setTimeout(resolve, CHANNEL_DRAIN_TIMEOUT_MS);
|
|
18
|
+
});
|
|
19
|
+
await Promise.race([Promise.allSettled(pending), deadline]);
|
|
20
|
+
clearTimeout(timeout);
|
|
21
|
+
}
|
|
22
|
+
await client.realtime.disconnect();
|
|
23
|
+
}
|
|
2
24
|
var RealtimeManager = class {
|
|
3
25
|
constructor(options) {
|
|
4
26
|
this.client = null;
|
|
@@ -6,6 +28,8 @@ var RealtimeManager = class {
|
|
|
6
28
|
this.channels = /* @__PURE__ */ new Map();
|
|
7
29
|
this.refreshTimer = null;
|
|
8
30
|
this.currentToken = null;
|
|
31
|
+
this.destroyed = false;
|
|
32
|
+
this.cleanupPromise = null;
|
|
9
33
|
this.options = options;
|
|
10
34
|
}
|
|
11
35
|
/**
|
|
@@ -13,6 +37,7 @@ var RealtimeManager = class {
|
|
|
13
37
|
* Returns true if successful, false if the realtime runtime is unavailable.
|
|
14
38
|
*/
|
|
15
39
|
init() {
|
|
40
|
+
if (this.destroyed) return Promise.resolve(false);
|
|
16
41
|
if (this.initPromise) return this.initPromise;
|
|
17
42
|
this.initPromise = this.doInit();
|
|
18
43
|
return this.initPromise;
|
|
@@ -20,11 +45,17 @@ var RealtimeManager = class {
|
|
|
20
45
|
async doInit() {
|
|
21
46
|
try {
|
|
22
47
|
const tokenData = await this.options.fetchToken();
|
|
48
|
+
if (this.destroyed) return false;
|
|
23
49
|
const { createClient } = await import(
|
|
24
50
|
/* webpackIgnore: true */
|
|
25
51
|
"@supabase/supabase-js"
|
|
26
52
|
);
|
|
53
|
+
if (this.destroyed) return false;
|
|
54
|
+
this.currentToken = tokenData.token;
|
|
27
55
|
this.client = createClient(tokenData.realtimeUrl, tokenData.realtimeKey, {
|
|
56
|
+
// Reconnects and heartbeats must refresh from cStar's customer JWT,
|
|
57
|
+
// rather than the dedicated client's empty hosted-auth session.
|
|
58
|
+
accessToken: async () => this.currentToken,
|
|
28
59
|
auth: { persistSession: false, autoRefreshToken: false },
|
|
29
60
|
realtime: { params: { eventsPerSecond: 10 } }
|
|
30
61
|
});
|
|
@@ -51,10 +82,13 @@ var RealtimeManager = class {
|
|
|
51
82
|
this.refreshTimer = setTimeout(() => this.doRefresh(), delay);
|
|
52
83
|
}
|
|
53
84
|
async doRefresh() {
|
|
85
|
+
if (this.destroyed) return;
|
|
54
86
|
try {
|
|
55
87
|
const tokenData = await this.options.fetchToken();
|
|
88
|
+
if (this.destroyed) return;
|
|
56
89
|
this.setToken(tokenData);
|
|
57
90
|
} catch {
|
|
91
|
+
if (this.destroyed) return;
|
|
58
92
|
this.refreshTimer = setTimeout(() => this.doRefresh(), 3e4);
|
|
59
93
|
}
|
|
60
94
|
}
|
|
@@ -62,12 +96,14 @@ var RealtimeManager = class {
|
|
|
62
96
|
* Subscribe to new messages on a ticket via Postgres Changes.
|
|
63
97
|
* Returns an unsubscribe function.
|
|
64
98
|
*/
|
|
65
|
-
subscribeToMessages(ticketId, callback) {
|
|
99
|
+
subscribeToMessages(ticketId, callback, onStatus) {
|
|
66
100
|
if (!this.client) return () => {
|
|
67
101
|
};
|
|
68
102
|
const channelName = `messages:${ticketId}`;
|
|
69
103
|
this.removeChannel(channelName);
|
|
70
|
-
const channel = this.client.channel(channelName)
|
|
104
|
+
const channel = this.client.channel(channelName);
|
|
105
|
+
this.channels.set(channelName, channel);
|
|
106
|
+
channel.on(
|
|
71
107
|
"postgres_changes",
|
|
72
108
|
{
|
|
73
109
|
event: "INSERT",
|
|
@@ -76,6 +112,7 @@ var RealtimeManager = class {
|
|
|
76
112
|
filter: `ticket_id=eq.${ticketId}`
|
|
77
113
|
},
|
|
78
114
|
(payload) => {
|
|
115
|
+
if (this.channels.get(channelName) !== channel) return;
|
|
79
116
|
const row = payload.new;
|
|
80
117
|
if (!row) return;
|
|
81
118
|
const message = {
|
|
@@ -90,9 +127,10 @@ var RealtimeManager = class {
|
|
|
90
127
|
};
|
|
91
128
|
callback(message);
|
|
92
129
|
}
|
|
93
|
-
).subscribe()
|
|
94
|
-
|
|
95
|
-
|
|
130
|
+
).subscribe((status) => {
|
|
131
|
+
if (this.channels.get(channelName) === channel) onStatus?.(status);
|
|
132
|
+
});
|
|
133
|
+
return () => this.removeChannel(channelName, channel);
|
|
96
134
|
}
|
|
97
135
|
/** Get the underlying realtime client (for sharing with PresenceManager). */
|
|
98
136
|
getClient() {
|
|
@@ -110,15 +148,18 @@ var RealtimeManager = class {
|
|
|
110
148
|
get isReady() {
|
|
111
149
|
return this.client !== null && this.currentToken !== null;
|
|
112
150
|
}
|
|
113
|
-
removeChannel(name) {
|
|
151
|
+
removeChannel(name, expected) {
|
|
114
152
|
const existing = this.channels.get(name);
|
|
153
|
+
if (expected && existing !== expected) return;
|
|
115
154
|
if (existing && this.client) {
|
|
116
|
-
this.client.removeChannel(existing);
|
|
117
155
|
this.channels.delete(name);
|
|
156
|
+
removeRealtimeChannel(this.client, existing);
|
|
118
157
|
}
|
|
119
158
|
}
|
|
120
159
|
/** Clean up all channels, timers, and the client. */
|
|
121
160
|
destroy() {
|
|
161
|
+
if (this.destroyed) return this.cleanupPromise ?? Promise.resolve();
|
|
162
|
+
this.destroyed = true;
|
|
122
163
|
if (this.refreshTimer) {
|
|
123
164
|
clearTimeout(this.refreshTimer);
|
|
124
165
|
this.refreshTimer = null;
|
|
@@ -127,9 +168,13 @@ var RealtimeManager = class {
|
|
|
127
168
|
this.removeChannel(name);
|
|
128
169
|
}
|
|
129
170
|
this.channels.clear();
|
|
171
|
+
const client = this.client;
|
|
130
172
|
this.client = null;
|
|
131
173
|
this.initPromise = null;
|
|
132
174
|
this.currentToken = null;
|
|
175
|
+
this.cleanupPromise = client ? closeRealtimeClient(client).catch(() => {
|
|
176
|
+
}) : Promise.resolve();
|
|
177
|
+
return this.cleanupPromise;
|
|
133
178
|
}
|
|
134
179
|
};
|
|
135
180
|
|
|
@@ -159,7 +204,10 @@ var PresenceManager = class {
|
|
|
159
204
|
};
|
|
160
205
|
const channelName = `typing:${ticketId}`;
|
|
161
206
|
this.removeChannel(channelName);
|
|
162
|
-
const channel = this.client.channel(`ticket:${ticketId}`)
|
|
207
|
+
const channel = this.client.channel(`ticket:${ticketId}`);
|
|
208
|
+
this.channels.set(channelName, channel);
|
|
209
|
+
channel.on("broadcast", { event: "typing" }, (data) => {
|
|
210
|
+
if (this.channels.get(channelName) !== channel) return;
|
|
163
211
|
const payload = data?.payload;
|
|
164
212
|
if (!payload) return;
|
|
165
213
|
if (payload.agent_id) {
|
|
@@ -171,8 +219,7 @@ var PresenceManager = class {
|
|
|
171
219
|
callback(typingPayload);
|
|
172
220
|
}
|
|
173
221
|
}).subscribe();
|
|
174
|
-
this.
|
|
175
|
-
return () => this.removeChannel(channelName);
|
|
222
|
+
return () => this.removeChannel(channelName, channel);
|
|
176
223
|
}
|
|
177
224
|
/**
|
|
178
225
|
* Send a customer typing indicator.
|
|
@@ -225,11 +272,12 @@ var PresenceManager = class {
|
|
|
225
272
|
} catch {
|
|
226
273
|
}
|
|
227
274
|
}
|
|
228
|
-
removeChannel(name) {
|
|
275
|
+
removeChannel(name, expected) {
|
|
229
276
|
const existing = this.channels.get(name);
|
|
277
|
+
if (expected && existing !== expected) return;
|
|
230
278
|
if (existing && this.client) {
|
|
231
|
-
this.client.removeChannel(existing);
|
|
232
279
|
this.channels.delete(name);
|
|
280
|
+
removeRealtimeChannel(this.client, existing);
|
|
233
281
|
}
|
|
234
282
|
}
|
|
235
283
|
/** Clean up all channels and timers. */
|
|
@@ -292,6 +340,94 @@ var ChatClientNetworkError = class extends ChatClientError {
|
|
|
292
340
|
}
|
|
293
341
|
};
|
|
294
342
|
|
|
343
|
+
// src/chat/attachments.ts
|
|
344
|
+
var UUID = /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i;
|
|
345
|
+
function downloadFilename(header) {
|
|
346
|
+
const extended = header?.match(/(?:^|;)\s*filename\*\s*=\s*UTF-8''([^;]*)/i);
|
|
347
|
+
let name = "";
|
|
348
|
+
if (extended) {
|
|
349
|
+
try {
|
|
350
|
+
name = decodeURIComponent(extended[1]);
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (!name) {
|
|
355
|
+
const fallback = header?.match(/(?:^|;)\s*filename\s*=\s*(?:"((?:\\.|[^"\\])*)"|([^;]*))/i);
|
|
356
|
+
name = fallback?.[1]?.replace(/\\(.)/g, "$1") ?? fallback?.[2] ?? "";
|
|
357
|
+
}
|
|
358
|
+
return name.split(/[\\/]/).pop()?.replace(/[\x00-\x1f\x7f]/g, "").replace(/^[\s.]+|[\s.]+$/g, "") || "download";
|
|
359
|
+
}
|
|
360
|
+
var AttachmentsApi = class {
|
|
361
|
+
constructor(chat) {
|
|
362
|
+
this.chat = chat;
|
|
363
|
+
}
|
|
364
|
+
async download(reference, options = {}) {
|
|
365
|
+
const assertCurrent = this.chat.captureSession();
|
|
366
|
+
if (!this.chat.getAuthMode())
|
|
367
|
+
throw new ChatClientAuthError("Sign in before downloading a file.");
|
|
368
|
+
const { ticketId, messageId, attachmentIndex } = reference;
|
|
369
|
+
if (typeof ticketId !== "string" || typeof messageId !== "string" || !UUID.test(ticketId) || !UUID.test(messageId) || !Number.isSafeInteger(attachmentIndex) || attachmentIndex < 0) {
|
|
370
|
+
throw new TypeError("A saved ticket, message and nonnegative attachment index are required.");
|
|
371
|
+
}
|
|
372
|
+
const controller = new AbortController();
|
|
373
|
+
const signal = options.signal;
|
|
374
|
+
const download = options.download === false ? "0" : "1";
|
|
375
|
+
const abort = () => controller.abort();
|
|
376
|
+
const assertOwned = () => {
|
|
377
|
+
assertCurrent();
|
|
378
|
+
controller.signal.throwIfAborted();
|
|
379
|
+
};
|
|
380
|
+
const unsubscribe = this.chat.subscribe(() => {
|
|
381
|
+
try {
|
|
382
|
+
assertCurrent();
|
|
383
|
+
} catch {
|
|
384
|
+
abort();
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
388
|
+
if (signal?.aborted) abort();
|
|
389
|
+
try {
|
|
390
|
+
assertOwned();
|
|
391
|
+
const teamId = await this.chat.ensureTeamId(controller.signal);
|
|
392
|
+
assertOwned();
|
|
393
|
+
const result = await this.chat.fetch(
|
|
394
|
+
"/api/widget/attachments/download",
|
|
395
|
+
{
|
|
396
|
+
method: "GET",
|
|
397
|
+
query: {
|
|
398
|
+
teamId,
|
|
399
|
+
ticketId,
|
|
400
|
+
messageId,
|
|
401
|
+
attachmentIndex: String(attachmentIndex),
|
|
402
|
+
download
|
|
403
|
+
},
|
|
404
|
+
signal: controller.signal,
|
|
405
|
+
cache: "no-store",
|
|
406
|
+
readBody: async (response) => {
|
|
407
|
+
assertOwned();
|
|
408
|
+
const blob = await response.blob();
|
|
409
|
+
assertOwned();
|
|
410
|
+
return {
|
|
411
|
+
blob,
|
|
412
|
+
filename: downloadFilename(response.headers.get("Content-Disposition")),
|
|
413
|
+
contentType: blob.type.split(";")[0] || "application/octet-stream",
|
|
414
|
+
size: blob.size
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
assertOwned();
|
|
420
|
+
return result;
|
|
421
|
+
} catch (error) {
|
|
422
|
+
assertOwned();
|
|
423
|
+
throw error;
|
|
424
|
+
} finally {
|
|
425
|
+
unsubscribe();
|
|
426
|
+
signal?.removeEventListener("abort", abort);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
295
431
|
// src/chat/index.ts
|
|
296
432
|
var DEFAULT_BASE_URL = "https://www.cstar.help";
|
|
297
433
|
var DEFAULT_POLLING_INTERVAL = 3e3;
|
|
@@ -301,7 +437,9 @@ var ChatClient = class {
|
|
|
301
437
|
this.teamId = null;
|
|
302
438
|
this.customerId = null;
|
|
303
439
|
this.authMode = null;
|
|
304
|
-
this.
|
|
440
|
+
this.sessionGeneration = 0;
|
|
441
|
+
this.pollingSubscriptions = /* @__PURE__ */ new Set();
|
|
442
|
+
this.realtimeMessageSubscriptions = /* @__PURE__ */ new Map();
|
|
305
443
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
306
444
|
this.realtimeManager = null;
|
|
307
445
|
this.presenceManager = null;
|
|
@@ -313,6 +451,7 @@ var ChatClient = class {
|
|
|
313
451
|
this.realtimeEnabled = config.realtime !== false;
|
|
314
452
|
this.tickets = new TicketsApi(this);
|
|
315
453
|
this.messages = new MessagesApi(this);
|
|
454
|
+
this.attachments = new AttachmentsApi(this);
|
|
316
455
|
this.community = new ChatCommunityApi(this);
|
|
317
456
|
}
|
|
318
457
|
/**
|
|
@@ -327,6 +466,8 @@ var ChatClient = class {
|
|
|
327
466
|
* @param testMode - Whether to use test mode (relaxed timestamp validation)
|
|
328
467
|
*/
|
|
329
468
|
async identify(customer, signature, testMode = false) {
|
|
469
|
+
const generation = this.retireSession();
|
|
470
|
+
this.assertSession(generation);
|
|
330
471
|
const response = await this.fetch(
|
|
331
472
|
"/api/widget/verify-identity",
|
|
332
473
|
{
|
|
@@ -339,15 +480,18 @@ var ChatClient = class {
|
|
|
339
480
|
}
|
|
340
481
|
}
|
|
341
482
|
);
|
|
483
|
+
this.assertSession(generation);
|
|
342
484
|
this.sessionToken = response.data.sessionToken;
|
|
343
485
|
this.teamId = response.data.teamId;
|
|
344
486
|
this.customerId = response.data.customer.id;
|
|
345
487
|
this.authMode = "hmac";
|
|
346
488
|
this.notifyStateChanged();
|
|
489
|
+
this.assertSession(generation);
|
|
347
490
|
if (this.realtimeEnabled) {
|
|
348
491
|
this.realtimeInitPromise = this.initRealtime(
|
|
349
492
|
response.data.customer.id,
|
|
350
|
-
response.data.customer.name || customer.name || customer.email
|
|
493
|
+
response.data.customer.name || customer.name || customer.email,
|
|
494
|
+
generation
|
|
351
495
|
);
|
|
352
496
|
}
|
|
353
497
|
return response.data;
|
|
@@ -362,11 +506,15 @@ var ChatClient = class {
|
|
|
362
506
|
* Supabase session, use `loginWithSession({ accessToken })` instead.
|
|
363
507
|
*/
|
|
364
508
|
async loginWithCredentials(params) {
|
|
509
|
+
const generation = this.retireSession();
|
|
510
|
+
this.assertSession(generation);
|
|
365
511
|
const teamId = await this.resolveTeamIdFromSlug();
|
|
512
|
+
this.assertSession(generation);
|
|
366
513
|
const response = await this.fetch("/api/widget/auth/login", {
|
|
367
514
|
method: "POST",
|
|
368
515
|
body: { email: params.email, password: params.password, teamId }
|
|
369
516
|
});
|
|
517
|
+
this.assertSession(generation);
|
|
370
518
|
if (!response.accessToken) {
|
|
371
519
|
throw new ChatClientAuthError({
|
|
372
520
|
message: "Login succeeded but no session was issued",
|
|
@@ -375,11 +523,14 @@ var ChatClient = class {
|
|
|
375
523
|
requestId: null
|
|
376
524
|
});
|
|
377
525
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
526
|
+
this.applySession(
|
|
527
|
+
{
|
|
528
|
+
accessToken: response.accessToken,
|
|
529
|
+
teamId,
|
|
530
|
+
customerName: response.customer.name ?? response.customer.email
|
|
531
|
+
},
|
|
532
|
+
generation
|
|
533
|
+
);
|
|
383
534
|
return {
|
|
384
535
|
customer: {
|
|
385
536
|
id: this.customerId ?? response.customer.id,
|
|
@@ -398,15 +549,22 @@ var ChatClient = class {
|
|
|
398
549
|
* every request — there is no client-side validation here.
|
|
399
550
|
*/
|
|
400
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);
|
|
401
558
|
this.sessionToken = params.accessToken;
|
|
402
559
|
if (params.teamId) {
|
|
403
560
|
this.teamId = params.teamId;
|
|
404
561
|
}
|
|
405
562
|
this.authMode = "session";
|
|
406
563
|
this.notifyStateChanged();
|
|
564
|
+
this.assertSession(generation);
|
|
407
565
|
if (this.realtimeEnabled) {
|
|
408
566
|
const customerName = params.customerName ?? "";
|
|
409
|
-
this.realtimeInitPromise = this.initRealtimeFromSession(customerName);
|
|
567
|
+
this.realtimeInitPromise = this.initRealtimeFromSession(customerName, generation);
|
|
410
568
|
}
|
|
411
569
|
}
|
|
412
570
|
/**
|
|
@@ -448,7 +606,15 @@ var ChatClient = class {
|
|
|
448
606
|
*/
|
|
449
607
|
onMessage(ticketId, callback) {
|
|
450
608
|
if (this.realtimeReady && this.realtimeManager) {
|
|
451
|
-
|
|
609
|
+
this.realtimeMessageSubscriptions.get(ticketId)?.();
|
|
610
|
+
const unsubscribe = this.pollMessages(ticketId, callback, this.realtimeManager);
|
|
611
|
+
this.realtimeMessageSubscriptions.set(ticketId, unsubscribe);
|
|
612
|
+
return () => {
|
|
613
|
+
unsubscribe();
|
|
614
|
+
if (this.realtimeMessageSubscriptions.get(ticketId) === unsubscribe) {
|
|
615
|
+
this.realtimeMessageSubscriptions.delete(ticketId);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
452
618
|
}
|
|
453
619
|
return this.pollMessages(ticketId, callback);
|
|
454
620
|
}
|
|
@@ -459,8 +625,11 @@ var ChatClient = class {
|
|
|
459
625
|
* @returns Unsubscribe function
|
|
460
626
|
*/
|
|
461
627
|
onTyping(ticketId, callback) {
|
|
628
|
+
const generation = this.sessionGeneration;
|
|
462
629
|
if (this.realtimeReady && this.presenceManager) {
|
|
463
|
-
return this.presenceManager.subscribeToTyping(ticketId,
|
|
630
|
+
return this.presenceManager.subscribeToTyping(ticketId, (typing) => {
|
|
631
|
+
if (generation === this.sessionGeneration) callback(typing);
|
|
632
|
+
});
|
|
464
633
|
}
|
|
465
634
|
return () => {
|
|
466
635
|
};
|
|
@@ -476,25 +645,44 @@ var ChatClient = class {
|
|
|
476
645
|
}
|
|
477
646
|
/** Disconnect all subscriptions and clean up. */
|
|
478
647
|
disconnect() {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
648
|
+
this.retireSession();
|
|
649
|
+
}
|
|
650
|
+
retireSession() {
|
|
651
|
+
const generation = ++this.sessionGeneration;
|
|
483
652
|
if (this.presenceManager) {
|
|
484
653
|
this.presenceManager.destroy();
|
|
485
654
|
this.presenceManager = null;
|
|
486
655
|
}
|
|
656
|
+
if (this.realtimeManager) {
|
|
657
|
+
this.realtimeManager.destroy();
|
|
658
|
+
this.realtimeManager = null;
|
|
659
|
+
}
|
|
487
660
|
this.realtimeReady = false;
|
|
488
661
|
this.realtimeInitPromise = null;
|
|
489
|
-
for (const
|
|
490
|
-
|
|
662
|
+
for (const unsubscribe of this.pollingSubscriptions) {
|
|
663
|
+
unsubscribe();
|
|
491
664
|
}
|
|
492
|
-
this.
|
|
665
|
+
this.pollingSubscriptions.clear();
|
|
666
|
+
this.realtimeMessageSubscriptions.clear();
|
|
493
667
|
this.sessionToken = null;
|
|
494
668
|
this.teamId = null;
|
|
495
669
|
this.customerId = null;
|
|
496
670
|
this.authMode = null;
|
|
497
671
|
this.notifyStateChanged();
|
|
672
|
+
return generation;
|
|
673
|
+
}
|
|
674
|
+
assertSession(generation) {
|
|
675
|
+
if (generation !== this.sessionGeneration) {
|
|
676
|
+
throw new ChatClientAuthError({
|
|
677
|
+
code: "SESSION_CHANGED",
|
|
678
|
+
message: "Your sign-in session changed. This operation belongs to the previous session."
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/** @internal Keep a public operation owned across its asynchronous prerequisites. */
|
|
683
|
+
captureSession() {
|
|
684
|
+
const generation = this.sessionGeneration;
|
|
685
|
+
return () => this.assertSession(generation);
|
|
498
686
|
}
|
|
499
687
|
/**
|
|
500
688
|
* @internal Make an authenticated request to the widget API.
|
|
@@ -509,6 +697,12 @@ var ChatClient = class {
|
|
|
509
697
|
* server-confirmed mode.
|
|
510
698
|
*/
|
|
511
699
|
async fetch(path, options) {
|
|
700
|
+
const generation = this.sessionGeneration;
|
|
701
|
+
const assertRequest = () => {
|
|
702
|
+
this.assertSession(generation);
|
|
703
|
+
options.signal?.throwIfAborted();
|
|
704
|
+
};
|
|
705
|
+
assertRequest();
|
|
512
706
|
const url = this.buildUrl(path, options.query);
|
|
513
707
|
const headers = {
|
|
514
708
|
"Content-Type": "application/json"
|
|
@@ -521,14 +715,18 @@ var ChatClient = class {
|
|
|
521
715
|
response = await globalThis.fetch(url, {
|
|
522
716
|
method: options.method,
|
|
523
717
|
headers,
|
|
718
|
+
...options.signal ? { signal: options.signal } : {},
|
|
719
|
+
...options.cache ? { cache: options.cache } : {},
|
|
524
720
|
body: options.body ? JSON.stringify(options.body) : void 0
|
|
525
721
|
});
|
|
526
722
|
} catch (networkErr) {
|
|
723
|
+
assertRequest();
|
|
527
724
|
throw new ChatClientNetworkError({
|
|
528
725
|
message: networkErr instanceof Error ? networkErr.message : "Network request failed",
|
|
529
726
|
originalError: networkErr
|
|
530
727
|
});
|
|
531
728
|
}
|
|
729
|
+
assertRequest();
|
|
532
730
|
const responseHeaders = response.headers && typeof response.headers.get === "function" ? response.headers : null;
|
|
533
731
|
const headerMode = responseHeaders?.get("X-Cstar-Auth-Mode") ?? null;
|
|
534
732
|
if (headerMode === "hmac" || headerMode === "session") {
|
|
@@ -536,9 +734,11 @@ var ChatClient = class {
|
|
|
536
734
|
this.authMode = headerMode;
|
|
537
735
|
if (previous !== headerMode) this.notifyStateChanged();
|
|
538
736
|
}
|
|
737
|
+
assertRequest();
|
|
539
738
|
const requestId = responseHeaders?.get("X-Request-Id") ?? null;
|
|
540
739
|
if (!response.ok) {
|
|
541
740
|
const errorBody = await response.json().catch(() => ({ error: { code: "UNKNOWN", message: "Request failed" } }));
|
|
741
|
+
assertRequest();
|
|
542
742
|
const code = errorBody.error?.code ?? "UNKNOWN";
|
|
543
743
|
const message = errorBody.error?.message ?? `Widget API error: ${response.status}`;
|
|
544
744
|
if (response.status === 401 || response.status === 403) {
|
|
@@ -575,7 +775,14 @@ var ChatClient = class {
|
|
|
575
775
|
requestId
|
|
576
776
|
});
|
|
577
777
|
}
|
|
578
|
-
|
|
778
|
+
try {
|
|
779
|
+
const body = options.readBody ? await options.readBody(response) : await response.json();
|
|
780
|
+
assertRequest();
|
|
781
|
+
return body;
|
|
782
|
+
} catch (error) {
|
|
783
|
+
assertRequest();
|
|
784
|
+
throw error;
|
|
785
|
+
}
|
|
579
786
|
}
|
|
580
787
|
/**
|
|
581
788
|
* Look up the team UUID from the configured `teamSlug`. Used by
|
|
@@ -584,37 +791,41 @@ var ChatClient = class {
|
|
|
584
791
|
*
|
|
585
792
|
* @internal
|
|
586
793
|
*/
|
|
587
|
-
async resolveTeamIdFromSlug() {
|
|
794
|
+
async resolveTeamIdFromSlug(signal) {
|
|
588
795
|
const response = await this.fetch(
|
|
589
796
|
"/api/widget/team-by-slug",
|
|
590
797
|
{
|
|
591
798
|
method: "GET",
|
|
799
|
+
signal,
|
|
592
800
|
query: { teamSlug: this.teamSlug }
|
|
593
801
|
}
|
|
594
802
|
);
|
|
595
803
|
return response.data.teamId;
|
|
596
804
|
}
|
|
597
805
|
/** @internal Bootstrap realtime from a session login (no precomputed customerId). */
|
|
598
|
-
async initRealtimeFromSession(customerName) {
|
|
806
|
+
async initRealtimeFromSession(customerName, generation) {
|
|
807
|
+
const manager = new RealtimeManager({ fetchToken: () => this.fetchRealtimeToken(generation) });
|
|
808
|
+
this.realtimeManager = manager;
|
|
599
809
|
try {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
810
|
+
const ready = await manager.init();
|
|
811
|
+
if (generation !== this.sessionGeneration || this.realtimeManager !== manager) {
|
|
812
|
+
manager.destroy();
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
604
815
|
if (!ready) return;
|
|
605
816
|
this.realtimeReady = true;
|
|
606
|
-
const token =
|
|
817
|
+
const token = manager.getCurrentToken();
|
|
607
818
|
const customerId = token ? decodeJwtSub(token) : null;
|
|
608
819
|
if (customerId) {
|
|
609
820
|
this.customerId = customerId;
|
|
610
821
|
this.presenceManager = new PresenceManager({ customerId, customerName });
|
|
611
|
-
const client =
|
|
822
|
+
const client = manager.getClient();
|
|
612
823
|
if (client) {
|
|
613
824
|
this.presenceManager.setClient(client);
|
|
614
825
|
}
|
|
615
826
|
}
|
|
616
827
|
} catch {
|
|
617
|
-
this.realtimeReady = false;
|
|
828
|
+
if (generation === this.sessionGeneration) this.realtimeReady = false;
|
|
618
829
|
}
|
|
619
830
|
}
|
|
620
831
|
/** @internal Get the stored team ID for requests that need it. */
|
|
@@ -623,23 +834,23 @@ var ChatClient = class {
|
|
|
623
834
|
}
|
|
624
835
|
/**
|
|
625
836
|
* Resolve a team ID, lazily looking it up from `teamSlug` if no team ID
|
|
626
|
-
* has been cached yet.
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
* public data.
|
|
630
|
-
*
|
|
631
|
-
* Closes NEW-v3-05 — `TicketsState(chatClient)` previously errored
|
|
632
|
-
* `"Missing teamId"` when the chat client was teamSlug-only because the
|
|
633
|
-
* tickets sub-resource read `getTeamId()` synchronously and bailed.
|
|
837
|
+
* has been cached yet. Authenticated resources and realtime bootstrap
|
|
838
|
+
* use this after session login so callers do not need to list tickets
|
|
839
|
+
* or set a UUID before opening an existing conversation.
|
|
634
840
|
*/
|
|
635
|
-
async ensureTeamId() {
|
|
841
|
+
async ensureTeamId(signal) {
|
|
842
|
+
const assertCurrent = this.captureSession();
|
|
843
|
+
signal?.throwIfAborted();
|
|
636
844
|
if (this.teamId) return this.teamId;
|
|
637
845
|
if (!this.teamSlug) {
|
|
638
846
|
throw new ChatClientAuthError(
|
|
639
847
|
"Missing teamId. Construct ChatClient with `teamSlug` or call `setTeamId()` / `identify()` / `loginWithCredentials()` first."
|
|
640
848
|
);
|
|
641
849
|
}
|
|
642
|
-
|
|
850
|
+
const teamId = await this.resolveTeamIdFromSlug(signal);
|
|
851
|
+
assertCurrent();
|
|
852
|
+
signal?.throwIfAborted();
|
|
853
|
+
this.teamId = teamId;
|
|
643
854
|
return this.teamId;
|
|
644
855
|
}
|
|
645
856
|
/** @internal Set team ID from identify response or other source. */
|
|
@@ -652,63 +863,116 @@ var ChatClient = class {
|
|
|
652
863
|
await this.realtimeInitPromise;
|
|
653
864
|
}
|
|
654
865
|
}
|
|
655
|
-
async initRealtime(customerId, customerName) {
|
|
866
|
+
async initRealtime(customerId, customerName, generation) {
|
|
867
|
+
const manager = new RealtimeManager({ fetchToken: () => this.fetchRealtimeToken(generation) });
|
|
868
|
+
this.realtimeManager = manager;
|
|
656
869
|
try {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
870
|
+
const ready = await manager.init();
|
|
871
|
+
if (generation !== this.sessionGeneration || this.realtimeManager !== manager) {
|
|
872
|
+
manager.destroy();
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
661
875
|
if (ready) {
|
|
662
876
|
this.realtimeReady = true;
|
|
663
877
|
this.presenceManager = new PresenceManager({
|
|
664
878
|
customerId,
|
|
665
879
|
customerName
|
|
666
880
|
});
|
|
667
|
-
const client =
|
|
881
|
+
const client = manager.getClient();
|
|
668
882
|
if (client) {
|
|
669
883
|
this.presenceManager.setClient(client);
|
|
670
884
|
}
|
|
671
885
|
}
|
|
672
886
|
} catch {
|
|
673
|
-
this.realtimeReady = false;
|
|
887
|
+
if (generation === this.sessionGeneration) this.realtimeReady = false;
|
|
674
888
|
}
|
|
675
889
|
}
|
|
676
|
-
async fetchRealtimeToken() {
|
|
890
|
+
async fetchRealtimeToken(generation) {
|
|
891
|
+
this.assertSession(generation);
|
|
892
|
+
const teamId = await this.ensureTeamId();
|
|
893
|
+
this.assertSession(generation);
|
|
677
894
|
const response = await this.fetch(
|
|
678
895
|
"/api/widget/realtime-token",
|
|
679
896
|
{
|
|
680
897
|
method: "POST",
|
|
681
|
-
body: { teamId
|
|
898
|
+
body: { teamId }
|
|
682
899
|
}
|
|
683
900
|
);
|
|
901
|
+
this.assertSession(generation);
|
|
684
902
|
return response.data;
|
|
685
903
|
}
|
|
686
|
-
pollMessages(ticketId, callback) {
|
|
687
|
-
|
|
688
|
-
|
|
904
|
+
pollMessages(ticketId, callback, manager) {
|
|
905
|
+
const seen = /* @__PURE__ */ new Set();
|
|
906
|
+
let initialized = false;
|
|
907
|
+
let active = true;
|
|
908
|
+
let inFlight = false;
|
|
909
|
+
let catchUpQueued = false;
|
|
910
|
+
let connected = false;
|
|
911
|
+
let timer;
|
|
912
|
+
let stopRealtime;
|
|
913
|
+
const deliver = (message) => {
|
|
914
|
+
if (!active || seen.has(message.id)) return;
|
|
915
|
+
seen.add(message.id);
|
|
916
|
+
callback(message);
|
|
917
|
+
};
|
|
918
|
+
const stopPolling = () => {
|
|
919
|
+
clearInterval(timer);
|
|
920
|
+
timer = void 0;
|
|
921
|
+
};
|
|
922
|
+
const startPolling = () => {
|
|
923
|
+
if (active && timer === void 0) {
|
|
924
|
+
timer = setInterval(() => void poll(), this.pollingInterval);
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
const poll = async (catchUp = false) => {
|
|
928
|
+
if (!active) return;
|
|
929
|
+
if (inFlight) {
|
|
930
|
+
if (catchUp) catchUpQueued = true;
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
inFlight = true;
|
|
689
934
|
try {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
935
|
+
do {
|
|
936
|
+
catchUpQueued = false;
|
|
937
|
+
try {
|
|
938
|
+
const messages = await this.messages.list(ticketId);
|
|
939
|
+
if (!active) return;
|
|
940
|
+
for (const message of messages) {
|
|
941
|
+
if (!active) return;
|
|
942
|
+
if (initialized) deliver(message);
|
|
943
|
+
else seen.add(message.id);
|
|
698
944
|
}
|
|
945
|
+
initialized = true;
|
|
946
|
+
if (connected) stopPolling();
|
|
947
|
+
} catch {
|
|
948
|
+
startPolling();
|
|
699
949
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
950
|
+
} while (active && catchUpQueued);
|
|
951
|
+
} finally {
|
|
952
|
+
inFlight = false;
|
|
703
953
|
}
|
|
704
954
|
};
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
this.pollingTimers.delete(ticketId);
|
|
955
|
+
const unsubscribe = () => {
|
|
956
|
+
active = false;
|
|
957
|
+
stopPolling();
|
|
958
|
+
stopRealtime?.();
|
|
959
|
+
this.pollingSubscriptions.delete(unsubscribe);
|
|
711
960
|
};
|
|
961
|
+
this.pollingSubscriptions.add(unsubscribe);
|
|
962
|
+
void poll();
|
|
963
|
+
startPolling();
|
|
964
|
+
stopRealtime = manager?.subscribeToMessages(ticketId, deliver, (status) => {
|
|
965
|
+
if (!active) return;
|
|
966
|
+
if (status === "SUBSCRIBED") {
|
|
967
|
+
connected = true;
|
|
968
|
+
stopPolling();
|
|
969
|
+
} else if (["CHANNEL_ERROR", "TIMED_OUT", "CLOSED"].includes(status)) {
|
|
970
|
+
connected = false;
|
|
971
|
+
startPolling();
|
|
972
|
+
} else return;
|
|
973
|
+
void poll(true);
|
|
974
|
+
});
|
|
975
|
+
return unsubscribe;
|
|
712
976
|
}
|
|
713
977
|
buildUrl(path, query) {
|
|
714
978
|
const base = `${this.baseUrl}${path}`;
|
|
@@ -727,10 +991,12 @@ var TicketsApi = class {
|
|
|
727
991
|
}
|
|
728
992
|
/** List tickets for the identified customer. */
|
|
729
993
|
async list(params) {
|
|
994
|
+
const assertCurrent = this.chat.captureSession();
|
|
730
995
|
if (this.chat.getAuthMode() === null) {
|
|
731
996
|
throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
|
|
732
997
|
}
|
|
733
998
|
const teamId = await this.chat.ensureTeamId();
|
|
999
|
+
assertCurrent();
|
|
734
1000
|
const response = await this.chat.fetch("/api/widget/conversations", {
|
|
735
1001
|
method: "GET",
|
|
736
1002
|
query: {
|
|
@@ -739,6 +1005,7 @@ var TicketsApi = class {
|
|
|
739
1005
|
cursor: params?.cursor
|
|
740
1006
|
}
|
|
741
1007
|
});
|
|
1008
|
+
assertCurrent();
|
|
742
1009
|
const data = response.data.tickets ?? response.data.conversations ?? [];
|
|
743
1010
|
const cursor = response.data.cursor;
|
|
744
1011
|
const hasMore = response.data.hasMore;
|
|
@@ -750,10 +1017,12 @@ var TicketsApi = class {
|
|
|
750
1017
|
}
|
|
751
1018
|
/** Create a new ticket. */
|
|
752
1019
|
async create(params) {
|
|
1020
|
+
const assertCurrent = this.chat.captureSession();
|
|
753
1021
|
if (this.chat.getAuthMode() === null) {
|
|
754
1022
|
throw new ChatClientAuthError("Not identified. Call chat.identify() first.");
|
|
755
1023
|
}
|
|
756
1024
|
const teamId = await this.chat.ensureTeamId();
|
|
1025
|
+
assertCurrent();
|
|
757
1026
|
const response = await this.chat.fetch("/api/widget/tickets", {
|
|
758
1027
|
method: "POST",
|
|
759
1028
|
body: {
|
|
@@ -762,6 +1031,7 @@ var TicketsApi = class {
|
|
|
762
1031
|
message: params.message
|
|
763
1032
|
}
|
|
764
1033
|
});
|
|
1034
|
+
assertCurrent();
|
|
765
1035
|
const messages = (response.data.messages ?? []).map((m) => ({
|
|
766
1036
|
id: m.id,
|
|
767
1037
|
content: m.content,
|
|
@@ -790,8 +1060,12 @@ var ChatCommunityApi = class {
|
|
|
790
1060
|
}
|
|
791
1061
|
/** Create a new community post as the identified customer. */
|
|
792
1062
|
async createPost(params) {
|
|
793
|
-
const
|
|
794
|
-
if (
|
|
1063
|
+
const assertCurrent = this.chat.captureSession();
|
|
1064
|
+
if (this.chat.getAuthMode() === null) {
|
|
1065
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1066
|
+
}
|
|
1067
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1068
|
+
assertCurrent();
|
|
795
1069
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
796
1070
|
method: "POST",
|
|
797
1071
|
body: {
|
|
@@ -803,12 +1077,17 @@ var ChatCommunityApi = class {
|
|
|
803
1077
|
customFields: params.customFields
|
|
804
1078
|
}
|
|
805
1079
|
});
|
|
1080
|
+
assertCurrent();
|
|
806
1081
|
return response.data.post;
|
|
807
1082
|
}
|
|
808
1083
|
/** Update a post. Only the original author can edit. */
|
|
809
1084
|
async updatePost(postId, params) {
|
|
810
|
-
const
|
|
811
|
-
if (
|
|
1085
|
+
const assertCurrent = this.chat.captureSession();
|
|
1086
|
+
if (this.chat.getAuthMode() === null) {
|
|
1087
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1088
|
+
}
|
|
1089
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1090
|
+
assertCurrent();
|
|
812
1091
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
813
1092
|
method: "POST",
|
|
814
1093
|
body: {
|
|
@@ -820,21 +1099,31 @@ var ChatCommunityApi = class {
|
|
|
820
1099
|
customFields: params.customFields
|
|
821
1100
|
}
|
|
822
1101
|
});
|
|
1102
|
+
assertCurrent();
|
|
823
1103
|
return response.data.post;
|
|
824
1104
|
}
|
|
825
1105
|
/** Delete a post (soft delete). Only the original author can delete. */
|
|
826
1106
|
async deletePost(postId) {
|
|
827
|
-
const
|
|
828
|
-
if (
|
|
1107
|
+
const assertCurrent = this.chat.captureSession();
|
|
1108
|
+
if (this.chat.getAuthMode() === null) {
|
|
1109
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1110
|
+
}
|
|
1111
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1112
|
+
assertCurrent();
|
|
829
1113
|
await this.chat.fetch("/api/widget/community", {
|
|
830
1114
|
method: "POST",
|
|
831
1115
|
body: { teamId, action: "deletePost", postId }
|
|
832
1116
|
});
|
|
1117
|
+
assertCurrent();
|
|
833
1118
|
}
|
|
834
1119
|
/** Update a comment. Only the original author can edit. */
|
|
835
1120
|
async updateComment(postId, commentId, params) {
|
|
836
|
-
const
|
|
837
|
-
if (
|
|
1121
|
+
const assertCurrent = this.chat.captureSession();
|
|
1122
|
+
if (this.chat.getAuthMode() === null) {
|
|
1123
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1124
|
+
}
|
|
1125
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1126
|
+
assertCurrent();
|
|
838
1127
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
839
1128
|
method: "POST",
|
|
840
1129
|
body: {
|
|
@@ -845,31 +1134,46 @@ var ChatCommunityApi = class {
|
|
|
845
1134
|
commentBody: params.body
|
|
846
1135
|
}
|
|
847
1136
|
});
|
|
1137
|
+
assertCurrent();
|
|
848
1138
|
return response.data.comment;
|
|
849
1139
|
}
|
|
850
1140
|
/** Delete a comment (soft delete). Only the original author can delete. */
|
|
851
1141
|
async deleteComment(postId, commentId) {
|
|
852
|
-
const
|
|
853
|
-
if (
|
|
1142
|
+
const assertCurrent = this.chat.captureSession();
|
|
1143
|
+
if (this.chat.getAuthMode() === null) {
|
|
1144
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1145
|
+
}
|
|
1146
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1147
|
+
assertCurrent();
|
|
854
1148
|
await this.chat.fetch("/api/widget/community", {
|
|
855
1149
|
method: "POST",
|
|
856
1150
|
body: { teamId, action: "deleteComment", postId, commentId }
|
|
857
1151
|
});
|
|
1152
|
+
assertCurrent();
|
|
858
1153
|
}
|
|
859
1154
|
/** Toggle vote on a post. Returns new vote state and count. */
|
|
860
1155
|
async vote(postId) {
|
|
861
|
-
const
|
|
862
|
-
if (
|
|
1156
|
+
const assertCurrent = this.chat.captureSession();
|
|
1157
|
+
if (this.chat.getAuthMode() === null) {
|
|
1158
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1159
|
+
}
|
|
1160
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1161
|
+
assertCurrent();
|
|
863
1162
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
864
1163
|
method: "POST",
|
|
865
1164
|
body: { teamId, action: "vote", postId }
|
|
866
1165
|
});
|
|
1166
|
+
assertCurrent();
|
|
867
1167
|
return response.data;
|
|
868
1168
|
}
|
|
869
1169
|
/** Add a comment to a post. Supports 1-level nesting via parentCommentId. */
|
|
870
1170
|
async comment(postId, params) {
|
|
871
|
-
const
|
|
872
|
-
if (
|
|
1171
|
+
const assertCurrent = this.chat.captureSession();
|
|
1172
|
+
if (this.chat.getAuthMode() === null) {
|
|
1173
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1174
|
+
}
|
|
1175
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1176
|
+
assertCurrent();
|
|
873
1177
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
874
1178
|
method: "POST",
|
|
875
1179
|
body: {
|
|
@@ -880,37 +1184,53 @@ var ChatCommunityApi = class {
|
|
|
880
1184
|
parentCommentId: params.parentCommentId ?? null
|
|
881
1185
|
}
|
|
882
1186
|
});
|
|
1187
|
+
assertCurrent();
|
|
883
1188
|
return response.data.comment;
|
|
884
1189
|
}
|
|
885
1190
|
/** Toggle follow on a post. */
|
|
886
1191
|
async follow(postId) {
|
|
887
|
-
const
|
|
888
|
-
if (
|
|
1192
|
+
const assertCurrent = this.chat.captureSession();
|
|
1193
|
+
if (this.chat.getAuthMode() === null) {
|
|
1194
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1195
|
+
}
|
|
1196
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1197
|
+
assertCurrent();
|
|
889
1198
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
890
1199
|
method: "POST",
|
|
891
1200
|
body: { teamId, action: "follow", postId }
|
|
892
1201
|
});
|
|
1202
|
+
assertCurrent();
|
|
893
1203
|
return response.data;
|
|
894
1204
|
}
|
|
895
1205
|
/** Check if the current user has voted on a post. */
|
|
896
1206
|
async getVoteStatus(postId) {
|
|
897
|
-
const
|
|
898
|
-
if (
|
|
1207
|
+
const assertCurrent = this.chat.captureSession();
|
|
1208
|
+
if (this.chat.getAuthMode() === null) {
|
|
1209
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1210
|
+
}
|
|
1211
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1212
|
+
assertCurrent();
|
|
899
1213
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
900
1214
|
method: "GET",
|
|
901
1215
|
query: { teamId, postId }
|
|
902
1216
|
});
|
|
1217
|
+
assertCurrent();
|
|
903
1218
|
return response.data.voted;
|
|
904
1219
|
}
|
|
905
1220
|
/** Check vote status for multiple posts in one request. */
|
|
906
1221
|
async bulkVoteStatus(postIds) {
|
|
907
1222
|
if (postIds.length === 0) return {};
|
|
908
|
-
const
|
|
909
|
-
if (
|
|
1223
|
+
const assertCurrent = this.chat.captureSession();
|
|
1224
|
+
if (this.chat.getAuthMode() === null) {
|
|
1225
|
+
throw new ChatClientAuthError("Sign in or identify a customer before accessing community.");
|
|
1226
|
+
}
|
|
1227
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1228
|
+
assertCurrent();
|
|
910
1229
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
911
1230
|
method: "GET",
|
|
912
1231
|
query: { teamId, postIds: postIds.join(",") }
|
|
913
1232
|
});
|
|
1233
|
+
assertCurrent();
|
|
914
1234
|
return response.data;
|
|
915
1235
|
}
|
|
916
1236
|
};
|
|
@@ -920,22 +1240,27 @@ var MessagesApi = class {
|
|
|
920
1240
|
}
|
|
921
1241
|
/** List messages for a ticket. */
|
|
922
1242
|
async list(ticketId) {
|
|
923
|
-
const
|
|
1243
|
+
const assertCurrent = this.chat.captureSession();
|
|
1244
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1245
|
+
assertCurrent();
|
|
924
1246
|
const response = await this.chat.fetch(
|
|
925
1247
|
"/api/widget/messages",
|
|
926
1248
|
{
|
|
927
1249
|
method: "GET",
|
|
928
1250
|
query: {
|
|
929
|
-
teamId
|
|
1251
|
+
teamId,
|
|
930
1252
|
ticketId
|
|
931
1253
|
}
|
|
932
1254
|
}
|
|
933
1255
|
);
|
|
1256
|
+
assertCurrent();
|
|
934
1257
|
return response.data.messages;
|
|
935
1258
|
}
|
|
936
1259
|
/** Send a message to a ticket. */
|
|
937
1260
|
async send(ticketId, content) {
|
|
938
|
-
const
|
|
1261
|
+
const assertCurrent = this.chat.captureSession();
|
|
1262
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1263
|
+
assertCurrent();
|
|
939
1264
|
const response = await this.chat.fetch(
|
|
940
1265
|
"/api/widget/messages",
|
|
941
1266
|
{
|
|
@@ -947,6 +1272,7 @@ var MessagesApi = class {
|
|
|
947
1272
|
}
|
|
948
1273
|
}
|
|
949
1274
|
);
|
|
1275
|
+
assertCurrent();
|
|
950
1276
|
return response.data.message;
|
|
951
1277
|
}
|
|
952
1278
|
};
|