@cstar.help/js 0.17.0 → 0.17.3
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 +17 -12
- package/dist/chat/index.cjs +202 -72
- package/dist/chat/index.d.cts +4 -8
- package/dist/chat/index.d.ts +4 -8
- package/dist/chat/index.js +202 -72
- package/dist/index.cjs +29 -27
- package/dist/index.d.cts +187 -60
- package/dist/index.d.ts +187 -60
- package/dist/index.js +29 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -116,13 +116,17 @@ for await (const ticket of cstar.tickets.listAutoPaginating({ status: 'open' }))
|
|
|
116
116
|
| `cstar.game.boss` | `.current()` `.isActive()` `.spawn(params?)` `.damage(params)` `.defeats(params?)` `.getDefeat(id)` |
|
|
117
117
|
| `cstar.game.achievements` | `.list(params?)` `.unlocked(params)` `.get(id, params?)` |
|
|
118
118
|
| `cstar.game.quests` | `.today(params?)` `.history(params?)` |
|
|
119
|
-
| `cstar.game.puzzles` | `.today(params?)` `.complete(params)` `.streak(params)` `.leaderboard(params
|
|
119
|
+
| `cstar.game.puzzles` | `.today(params?)` `.complete(params)` `.streak(params)` `.leaderboard(params)` |
|
|
120
120
|
| `cstar.game.oneUps` | `.list(params?)` `.send(params)` `.remaining(params)` |
|
|
121
121
|
| `cstar.game.highFives` | `.list(params?)` `.send(params)` `.canSend(teamId, params)` |
|
|
122
|
-
| `cstar.game.cosmetics` | `.list(params
|
|
123
|
-
| `cstar.game.seasons` | `.list()` `.current()` `.stats(seasonId, params)`
|
|
122
|
+
| `cstar.game.cosmetics` | `.list(params)` `.getPreferences(params)` `.updatePreferences(params)` |
|
|
123
|
+
| `cstar.game.seasons` | `.list(params?)` `.current()` `.stats(seasonId, params)` |
|
|
124
124
|
| `cstar.game.skills` | `.get(params)` `.allocate(params)` `.reset(params)` |
|
|
125
125
|
|
|
126
|
+
Game writes belong on your server with a secret API key. Use user and team UUIDs from cStar. One-ups and high fives default their sender to the key creator. Puzzle scoreboards require a `puzzleId`; streak boards use `{ type: 'streaks' }`. Cosmetic lists require a `userId`.
|
|
127
|
+
|
|
128
|
+
Cosmetic and season lists return `{ data, pagination, hasMore }`. One-up and high-five lists return `{ object: 'list', data }`. Today's quests return `{ quests, completedToday, totalQuests, allComplete, completionBonus, resetsAt }`. Single resources return bare objects.
|
|
129
|
+
|
|
126
130
|
**Advanced** — AI, automations, views, billing, webhooks.
|
|
127
131
|
|
|
128
132
|
| Resource | Methods |
|
|
@@ -140,20 +144,21 @@ for await (const ticket of cstar.tickets.listAutoPaginating({ status: 'open' }))
|
|
|
140
144
|
|
|
141
145
|
```typescript
|
|
142
146
|
// Team analytics
|
|
143
|
-
const
|
|
144
|
-
console.log(`${overview.tickets
|
|
147
|
+
const overview = await cstar.analytics.overview({ period: 'week' });
|
|
148
|
+
console.log(`${overview.tickets?.open ?? 0} open tickets this week`);
|
|
145
149
|
|
|
146
150
|
// SLA rules
|
|
147
151
|
const { data: rules } = await cstar.sla.list();
|
|
148
152
|
|
|
149
|
-
//
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
153
|
+
// Cheer on another team. The sender defaults to your API key's creator.
|
|
154
|
+
const highFive = await cstar.game.highFives.send({
|
|
155
|
+
recipientTeamId: '550e8400-e29b-41d4-a716-446655440003',
|
|
156
|
+
message: 'Amazing work!'
|
|
157
|
+
});
|
|
158
|
+
console.log(`A high five from ${highFive.senderTeam.name}`);
|
|
154
159
|
|
|
155
160
|
// AI-powered reply suggestion
|
|
156
|
-
const
|
|
161
|
+
const suggestion = await cstar.ai.suggestReply('tkt_abc123');
|
|
157
162
|
|
|
158
163
|
// Bulk close resolved tickets
|
|
159
164
|
await cstar.bulk.update({
|
|
@@ -233,7 +238,7 @@ The client keeps the access token internally. The public result contains `custom
|
|
|
233
238
|
|
|
234
239
|
### Session (existing customer token)
|
|
235
240
|
|
|
236
|
-
Pass an existing cStar customer token as an object. A token from an unrelated Supabase project does not establish a cStar customer session. Include the cStar team UUID when your application has it.
|
|
241
|
+
Pass an existing cStar customer token as an object. A token from an unrelated Supabase project does not establish a cStar customer session. Include the cStar team UUID when your application has it. Otherwise the client resolves its configured team slug before message, community and realtime requests.
|
|
237
242
|
|
|
238
243
|
```typescript
|
|
239
244
|
import { ChatClient, type LoginWithSessionParams } from '@cstar.help/js/chat';
|
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;
|
|
@@ -48,6 +70,7 @@ var RealtimeManager = class {
|
|
|
48
70
|
this.refreshTimer = null;
|
|
49
71
|
this.currentToken = null;
|
|
50
72
|
this.destroyed = false;
|
|
73
|
+
this.cleanupPromise = null;
|
|
51
74
|
this.options = options;
|
|
52
75
|
}
|
|
53
76
|
/**
|
|
@@ -69,7 +92,11 @@ var RealtimeManager = class {
|
|
|
69
92
|
"@supabase/supabase-js"
|
|
70
93
|
);
|
|
71
94
|
if (this.destroyed) return false;
|
|
95
|
+
this.currentToken = tokenData.token;
|
|
72
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,
|
|
73
100
|
auth: { persistSession: false, autoRefreshToken: false },
|
|
74
101
|
realtime: { params: { eventsPerSecond: 10 } }
|
|
75
102
|
});
|
|
@@ -110,12 +137,14 @@ var RealtimeManager = class {
|
|
|
110
137
|
* Subscribe to new messages on a ticket via Postgres Changes.
|
|
111
138
|
* Returns an unsubscribe function.
|
|
112
139
|
*/
|
|
113
|
-
subscribeToMessages(ticketId, callback) {
|
|
140
|
+
subscribeToMessages(ticketId, callback, onStatus) {
|
|
114
141
|
if (!this.client) return () => {
|
|
115
142
|
};
|
|
116
143
|
const channelName = `messages:${ticketId}`;
|
|
117
144
|
this.removeChannel(channelName);
|
|
118
|
-
const channel = this.client.channel(channelName)
|
|
145
|
+
const channel = this.client.channel(channelName);
|
|
146
|
+
this.channels.set(channelName, channel);
|
|
147
|
+
channel.on(
|
|
119
148
|
"postgres_changes",
|
|
120
149
|
{
|
|
121
150
|
event: "INSERT",
|
|
@@ -124,6 +153,7 @@ var RealtimeManager = class {
|
|
|
124
153
|
filter: `ticket_id=eq.${ticketId}`
|
|
125
154
|
},
|
|
126
155
|
(payload) => {
|
|
156
|
+
if (this.channels.get(channelName) !== channel) return;
|
|
127
157
|
const row = payload.new;
|
|
128
158
|
if (!row) return;
|
|
129
159
|
const message = {
|
|
@@ -138,9 +168,10 @@ var RealtimeManager = class {
|
|
|
138
168
|
};
|
|
139
169
|
callback(message);
|
|
140
170
|
}
|
|
141
|
-
).subscribe()
|
|
142
|
-
|
|
143
|
-
|
|
171
|
+
).subscribe((status) => {
|
|
172
|
+
if (this.channels.get(channelName) === channel) onStatus?.(status);
|
|
173
|
+
});
|
|
174
|
+
return () => this.removeChannel(channelName, channel);
|
|
144
175
|
}
|
|
145
176
|
/** Get the underlying realtime client (for sharing with PresenceManager). */
|
|
146
177
|
getClient() {
|
|
@@ -158,15 +189,17 @@ var RealtimeManager = class {
|
|
|
158
189
|
get isReady() {
|
|
159
190
|
return this.client !== null && this.currentToken !== null;
|
|
160
191
|
}
|
|
161
|
-
removeChannel(name) {
|
|
192
|
+
removeChannel(name, expected) {
|
|
162
193
|
const existing = this.channels.get(name);
|
|
194
|
+
if (expected && existing !== expected) return;
|
|
163
195
|
if (existing && this.client) {
|
|
164
|
-
this.client.removeChannel(existing);
|
|
165
196
|
this.channels.delete(name);
|
|
197
|
+
removeRealtimeChannel(this.client, existing);
|
|
166
198
|
}
|
|
167
199
|
}
|
|
168
200
|
/** Clean up all channels, timers, and the client. */
|
|
169
201
|
destroy() {
|
|
202
|
+
if (this.destroyed) return this.cleanupPromise ?? Promise.resolve();
|
|
170
203
|
this.destroyed = true;
|
|
171
204
|
if (this.refreshTimer) {
|
|
172
205
|
clearTimeout(this.refreshTimer);
|
|
@@ -176,9 +209,13 @@ var RealtimeManager = class {
|
|
|
176
209
|
this.removeChannel(name);
|
|
177
210
|
}
|
|
178
211
|
this.channels.clear();
|
|
212
|
+
const client = this.client;
|
|
179
213
|
this.client = null;
|
|
180
214
|
this.initPromise = null;
|
|
181
215
|
this.currentToken = null;
|
|
216
|
+
this.cleanupPromise = client ? closeRealtimeClient(client).catch(() => {
|
|
217
|
+
}) : Promise.resolve();
|
|
218
|
+
return this.cleanupPromise;
|
|
182
219
|
}
|
|
183
220
|
};
|
|
184
221
|
|
|
@@ -208,7 +245,10 @@ var PresenceManager = class {
|
|
|
208
245
|
};
|
|
209
246
|
const channelName = `typing:${ticketId}`;
|
|
210
247
|
this.removeChannel(channelName);
|
|
211
|
-
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;
|
|
212
252
|
const payload = data?.payload;
|
|
213
253
|
if (!payload) return;
|
|
214
254
|
if (payload.agent_id) {
|
|
@@ -220,8 +260,7 @@ var PresenceManager = class {
|
|
|
220
260
|
callback(typingPayload);
|
|
221
261
|
}
|
|
222
262
|
}).subscribe();
|
|
223
|
-
this.
|
|
224
|
-
return () => this.removeChannel(channelName);
|
|
263
|
+
return () => this.removeChannel(channelName, channel);
|
|
225
264
|
}
|
|
226
265
|
/**
|
|
227
266
|
* Send a customer typing indicator.
|
|
@@ -274,11 +313,12 @@ var PresenceManager = class {
|
|
|
274
313
|
} catch {
|
|
275
314
|
}
|
|
276
315
|
}
|
|
277
|
-
removeChannel(name) {
|
|
316
|
+
removeChannel(name, expected) {
|
|
278
317
|
const existing = this.channels.get(name);
|
|
318
|
+
if (expected && existing !== expected) return;
|
|
279
319
|
if (existing && this.client) {
|
|
280
|
-
this.client.removeChannel(existing);
|
|
281
320
|
this.channels.delete(name);
|
|
321
|
+
removeRealtimeChannel(this.client, existing);
|
|
282
322
|
}
|
|
283
323
|
}
|
|
284
324
|
/** Clean up all channels and timers. */
|
|
@@ -440,6 +480,7 @@ var ChatClient = class {
|
|
|
440
480
|
this.authMode = null;
|
|
441
481
|
this.sessionGeneration = 0;
|
|
442
482
|
this.pollingSubscriptions = /* @__PURE__ */ new Set();
|
|
483
|
+
this.realtimeMessageSubscriptions = /* @__PURE__ */ new Map();
|
|
443
484
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
444
485
|
this.realtimeManager = null;
|
|
445
486
|
this.presenceManager = null;
|
|
@@ -605,11 +646,16 @@ var ChatClient = class {
|
|
|
605
646
|
* @returns Unsubscribe function
|
|
606
647
|
*/
|
|
607
648
|
onMessage(ticketId, callback) {
|
|
608
|
-
const generation = this.sessionGeneration;
|
|
609
649
|
if (this.realtimeReady && this.realtimeManager) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
+
};
|
|
613
659
|
}
|
|
614
660
|
return this.pollMessages(ticketId, callback);
|
|
615
661
|
}
|
|
@@ -644,20 +690,21 @@ var ChatClient = class {
|
|
|
644
690
|
}
|
|
645
691
|
retireSession() {
|
|
646
692
|
const generation = ++this.sessionGeneration;
|
|
647
|
-
if (this.realtimeManager) {
|
|
648
|
-
this.realtimeManager.destroy();
|
|
649
|
-
this.realtimeManager = null;
|
|
650
|
-
}
|
|
651
693
|
if (this.presenceManager) {
|
|
652
694
|
this.presenceManager.destroy();
|
|
653
695
|
this.presenceManager = null;
|
|
654
696
|
}
|
|
697
|
+
if (this.realtimeManager) {
|
|
698
|
+
this.realtimeManager.destroy();
|
|
699
|
+
this.realtimeManager = null;
|
|
700
|
+
}
|
|
655
701
|
this.realtimeReady = false;
|
|
656
702
|
this.realtimeInitPromise = null;
|
|
657
703
|
for (const unsubscribe of this.pollingSubscriptions) {
|
|
658
704
|
unsubscribe();
|
|
659
705
|
}
|
|
660
706
|
this.pollingSubscriptions.clear();
|
|
707
|
+
this.realtimeMessageSubscriptions.clear();
|
|
661
708
|
this.sessionToken = null;
|
|
662
709
|
this.teamId = null;
|
|
663
710
|
this.customerId = null;
|
|
@@ -828,14 +875,9 @@ var ChatClient = class {
|
|
|
828
875
|
}
|
|
829
876
|
/**
|
|
830
877
|
* Resolve a team ID, lazily looking it up from `teamSlug` if no team ID
|
|
831
|
-
* has been cached yet.
|
|
832
|
-
*
|
|
833
|
-
*
|
|
834
|
-
* public data.
|
|
835
|
-
*
|
|
836
|
-
* Closes NEW-v3-05 — `TicketsState(chatClient)` previously errored
|
|
837
|
-
* `"Missing teamId"` when the chat client was teamSlug-only because the
|
|
838
|
-
* 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.
|
|
839
881
|
*/
|
|
840
882
|
async ensureTeamId(signal) {
|
|
841
883
|
const assertCurrent = this.captureSession();
|
|
@@ -887,54 +929,90 @@ var ChatClient = class {
|
|
|
887
929
|
}
|
|
888
930
|
}
|
|
889
931
|
async fetchRealtimeToken(generation) {
|
|
932
|
+
this.assertSession(generation);
|
|
933
|
+
const teamId = await this.ensureTeamId();
|
|
890
934
|
this.assertSession(generation);
|
|
891
935
|
const response = await this.fetch(
|
|
892
936
|
"/api/widget/realtime-token",
|
|
893
937
|
{
|
|
894
938
|
method: "POST",
|
|
895
|
-
body: { teamId
|
|
939
|
+
body: { teamId }
|
|
896
940
|
}
|
|
897
941
|
);
|
|
898
942
|
this.assertSession(generation);
|
|
899
943
|
return response.data;
|
|
900
944
|
}
|
|
901
|
-
pollMessages(ticketId, callback) {
|
|
902
|
-
|
|
945
|
+
pollMessages(ticketId, callback, manager) {
|
|
946
|
+
const seen = /* @__PURE__ */ new Set();
|
|
903
947
|
let initialized = false;
|
|
904
948
|
let active = true;
|
|
905
949
|
let inFlight = false;
|
|
906
|
-
|
|
907
|
-
|
|
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
|
+
}
|
|
908
974
|
inFlight = true;
|
|
909
975
|
try {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
const
|
|
916
|
-
const newMessages = lastIdx >= 0 ? messages.slice(lastIdx + 1) : lastMessageId ? [latest] : messages;
|
|
917
|
-
for (const msg of newMessages) {
|
|
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) {
|
|
918
982
|
if (!active) return;
|
|
919
|
-
|
|
983
|
+
if (initialized) deliver(message);
|
|
984
|
+
else seen.add(message.id);
|
|
920
985
|
}
|
|
986
|
+
initialized = true;
|
|
987
|
+
if (connected) stopPolling();
|
|
988
|
+
} catch {
|
|
989
|
+
startPolling();
|
|
921
990
|
}
|
|
922
|
-
|
|
923
|
-
}
|
|
924
|
-
initialized = true;
|
|
925
|
-
} catch {
|
|
991
|
+
} while (active && catchUpQueued);
|
|
926
992
|
} finally {
|
|
927
993
|
inFlight = false;
|
|
928
994
|
}
|
|
929
995
|
};
|
|
930
|
-
poll();
|
|
931
|
-
const timer = setInterval(poll, this.pollingInterval);
|
|
932
996
|
const unsubscribe = () => {
|
|
933
997
|
active = false;
|
|
934
|
-
|
|
998
|
+
stopPolling();
|
|
999
|
+
stopRealtime?.();
|
|
935
1000
|
this.pollingSubscriptions.delete(unsubscribe);
|
|
936
1001
|
};
|
|
937
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
|
+
});
|
|
938
1016
|
return unsubscribe;
|
|
939
1017
|
}
|
|
940
1018
|
buildUrl(path, query) {
|
|
@@ -1023,8 +1101,12 @@ var ChatCommunityApi = class {
|
|
|
1023
1101
|
}
|
|
1024
1102
|
/** Create a new community post as the identified customer. */
|
|
1025
1103
|
async createPost(params) {
|
|
1026
|
-
const
|
|
1027
|
-
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();
|
|
1028
1110
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
1029
1111
|
method: "POST",
|
|
1030
1112
|
body: {
|
|
@@ -1036,12 +1118,17 @@ var ChatCommunityApi = class {
|
|
|
1036
1118
|
customFields: params.customFields
|
|
1037
1119
|
}
|
|
1038
1120
|
});
|
|
1121
|
+
assertCurrent();
|
|
1039
1122
|
return response.data.post;
|
|
1040
1123
|
}
|
|
1041
1124
|
/** Update a post. Only the original author can edit. */
|
|
1042
1125
|
async updatePost(postId, params) {
|
|
1043
|
-
const
|
|
1044
|
-
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();
|
|
1045
1132
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
1046
1133
|
method: "POST",
|
|
1047
1134
|
body: {
|
|
@@ -1053,21 +1140,31 @@ var ChatCommunityApi = class {
|
|
|
1053
1140
|
customFields: params.customFields
|
|
1054
1141
|
}
|
|
1055
1142
|
});
|
|
1143
|
+
assertCurrent();
|
|
1056
1144
|
return response.data.post;
|
|
1057
1145
|
}
|
|
1058
1146
|
/** Delete a post (soft delete). Only the original author can delete. */
|
|
1059
1147
|
async deletePost(postId) {
|
|
1060
|
-
const
|
|
1061
|
-
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();
|
|
1062
1154
|
await this.chat.fetch("/api/widget/community", {
|
|
1063
1155
|
method: "POST",
|
|
1064
1156
|
body: { teamId, action: "deletePost", postId }
|
|
1065
1157
|
});
|
|
1158
|
+
assertCurrent();
|
|
1066
1159
|
}
|
|
1067
1160
|
/** Update a comment. Only the original author can edit. */
|
|
1068
1161
|
async updateComment(postId, commentId, params) {
|
|
1069
|
-
const
|
|
1070
|
-
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();
|
|
1071
1168
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
1072
1169
|
method: "POST",
|
|
1073
1170
|
body: {
|
|
@@ -1078,31 +1175,46 @@ var ChatCommunityApi = class {
|
|
|
1078
1175
|
commentBody: params.body
|
|
1079
1176
|
}
|
|
1080
1177
|
});
|
|
1178
|
+
assertCurrent();
|
|
1081
1179
|
return response.data.comment;
|
|
1082
1180
|
}
|
|
1083
1181
|
/** Delete a comment (soft delete). Only the original author can delete. */
|
|
1084
1182
|
async deleteComment(postId, commentId) {
|
|
1085
|
-
const
|
|
1086
|
-
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();
|
|
1087
1189
|
await this.chat.fetch("/api/widget/community", {
|
|
1088
1190
|
method: "POST",
|
|
1089
1191
|
body: { teamId, action: "deleteComment", postId, commentId }
|
|
1090
1192
|
});
|
|
1193
|
+
assertCurrent();
|
|
1091
1194
|
}
|
|
1092
1195
|
/** Toggle vote on a post. Returns new vote state and count. */
|
|
1093
1196
|
async vote(postId) {
|
|
1094
|
-
const
|
|
1095
|
-
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();
|
|
1096
1203
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
1097
1204
|
method: "POST",
|
|
1098
1205
|
body: { teamId, action: "vote", postId }
|
|
1099
1206
|
});
|
|
1207
|
+
assertCurrent();
|
|
1100
1208
|
return response.data;
|
|
1101
1209
|
}
|
|
1102
1210
|
/** Add a comment to a post. Supports 1-level nesting via parentCommentId. */
|
|
1103
1211
|
async comment(postId, params) {
|
|
1104
|
-
const
|
|
1105
|
-
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();
|
|
1106
1218
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
1107
1219
|
method: "POST",
|
|
1108
1220
|
body: {
|
|
@@ -1113,37 +1225,53 @@ var ChatCommunityApi = class {
|
|
|
1113
1225
|
parentCommentId: params.parentCommentId ?? null
|
|
1114
1226
|
}
|
|
1115
1227
|
});
|
|
1228
|
+
assertCurrent();
|
|
1116
1229
|
return response.data.comment;
|
|
1117
1230
|
}
|
|
1118
1231
|
/** Toggle follow on a post. */
|
|
1119
1232
|
async follow(postId) {
|
|
1120
|
-
const
|
|
1121
|
-
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();
|
|
1122
1239
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
1123
1240
|
method: "POST",
|
|
1124
1241
|
body: { teamId, action: "follow", postId }
|
|
1125
1242
|
});
|
|
1243
|
+
assertCurrent();
|
|
1126
1244
|
return response.data;
|
|
1127
1245
|
}
|
|
1128
1246
|
/** Check if the current user has voted on a post. */
|
|
1129
1247
|
async getVoteStatus(postId) {
|
|
1130
|
-
const
|
|
1131
|
-
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();
|
|
1132
1254
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
1133
1255
|
method: "GET",
|
|
1134
1256
|
query: { teamId, postId }
|
|
1135
1257
|
});
|
|
1258
|
+
assertCurrent();
|
|
1136
1259
|
return response.data.voted;
|
|
1137
1260
|
}
|
|
1138
1261
|
/** Check vote status for multiple posts in one request. */
|
|
1139
1262
|
async bulkVoteStatus(postIds) {
|
|
1140
1263
|
if (postIds.length === 0) return {};
|
|
1141
|
-
const
|
|
1142
|
-
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();
|
|
1143
1270
|
const response = await this.chat.fetch("/api/widget/community", {
|
|
1144
1271
|
method: "GET",
|
|
1145
1272
|
query: { teamId, postIds: postIds.join(",") }
|
|
1146
1273
|
});
|
|
1274
|
+
assertCurrent();
|
|
1147
1275
|
return response.data;
|
|
1148
1276
|
}
|
|
1149
1277
|
};
|
|
@@ -1154,13 +1282,14 @@ var MessagesApi = class {
|
|
|
1154
1282
|
/** List messages for a ticket. */
|
|
1155
1283
|
async list(ticketId) {
|
|
1156
1284
|
const assertCurrent = this.chat.captureSession();
|
|
1157
|
-
const teamId = this.chat.
|
|
1285
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1286
|
+
assertCurrent();
|
|
1158
1287
|
const response = await this.chat.fetch(
|
|
1159
1288
|
"/api/widget/messages",
|
|
1160
1289
|
{
|
|
1161
1290
|
method: "GET",
|
|
1162
1291
|
query: {
|
|
1163
|
-
teamId
|
|
1292
|
+
teamId,
|
|
1164
1293
|
ticketId
|
|
1165
1294
|
}
|
|
1166
1295
|
}
|
|
@@ -1171,7 +1300,8 @@ var MessagesApi = class {
|
|
|
1171
1300
|
/** Send a message to a ticket. */
|
|
1172
1301
|
async send(ticketId, content) {
|
|
1173
1302
|
const assertCurrent = this.chat.captureSession();
|
|
1174
|
-
const teamId = this.chat.
|
|
1303
|
+
const teamId = await this.chat.ensureTeamId();
|
|
1304
|
+
assertCurrent();
|
|
1175
1305
|
const response = await this.chat.fetch(
|
|
1176
1306
|
"/api/widget/messages",
|
|
1177
1307
|
{
|
package/dist/chat/index.d.cts
CHANGED
|
@@ -321,6 +321,7 @@ declare class ChatClient {
|
|
|
321
321
|
private authMode;
|
|
322
322
|
private sessionGeneration;
|
|
323
323
|
private pollingSubscriptions;
|
|
324
|
+
private realtimeMessageSubscriptions;
|
|
324
325
|
private stateListeners;
|
|
325
326
|
private realtimeManager;
|
|
326
327
|
private presenceManager;
|
|
@@ -443,14 +444,9 @@ declare class ChatClient {
|
|
|
443
444
|
getTeamId(): string | null;
|
|
444
445
|
/**
|
|
445
446
|
* Resolve a team ID, lazily looking it up from `teamSlug` if no team ID
|
|
446
|
-
* has been cached yet.
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
* public data.
|
|
450
|
-
*
|
|
451
|
-
* Closes NEW-v3-05 — `TicketsState(chatClient)` previously errored
|
|
452
|
-
* `"Missing teamId"` when the chat client was teamSlug-only because the
|
|
453
|
-
* tickets sub-resource read `getTeamId()` synchronously and bailed.
|
|
447
|
+
* has been cached yet. Authenticated resources and realtime bootstrap
|
|
448
|
+
* use this after session login so callers do not need to list tickets
|
|
449
|
+
* or set a UUID before opening an existing conversation.
|
|
454
450
|
*/
|
|
455
451
|
ensureTeamId(signal?: AbortSignal): Promise<string>;
|
|
456
452
|
/** @internal Set team ID from identify response or other source. */
|
package/dist/chat/index.d.ts
CHANGED
|
@@ -321,6 +321,7 @@ declare class ChatClient {
|
|
|
321
321
|
private authMode;
|
|
322
322
|
private sessionGeneration;
|
|
323
323
|
private pollingSubscriptions;
|
|
324
|
+
private realtimeMessageSubscriptions;
|
|
324
325
|
private stateListeners;
|
|
325
326
|
private realtimeManager;
|
|
326
327
|
private presenceManager;
|
|
@@ -443,14 +444,9 @@ declare class ChatClient {
|
|
|
443
444
|
getTeamId(): string | null;
|
|
444
445
|
/**
|
|
445
446
|
* Resolve a team ID, lazily looking it up from `teamSlug` if no team ID
|
|
446
|
-
* has been cached yet.
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
* public data.
|
|
450
|
-
*
|
|
451
|
-
* Closes NEW-v3-05 — `TicketsState(chatClient)` previously errored
|
|
452
|
-
* `"Missing teamId"` when the chat client was teamSlug-only because the
|
|
453
|
-
* tickets sub-resource read `getTeamId()` synchronously and bailed.
|
|
447
|
+
* has been cached yet. Authenticated resources and realtime bootstrap
|
|
448
|
+
* use this after session login so callers do not need to list tickets
|
|
449
|
+
* or set a UUID before opening an existing conversation.
|
|
454
450
|
*/
|
|
455
451
|
ensureTeamId(signal?: AbortSignal): Promise<string>;
|
|
456
452
|
/** @internal Set team ID from identify response or other source. */
|