@cstar.help/js 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +39 -39
- package/dist/auth/index.cjs +127 -0
- package/dist/auth/index.d.cts +123 -0
- package/dist/auth/index.d.ts +123 -0
- package/dist/auth/index.js +102 -0
- package/dist/chat/index.cjs +373 -37
- package/dist/chat/index.d.cts +42 -5
- package/dist/chat/index.d.ts +42 -5
- package/dist/chat/index.js +361 -37
- package/dist/index.cjs +8 -18
- package/dist/index.js +8 -18
- package/dist/library/index.cjs +18 -4
- package/dist/library/index.d.cts +11 -1
- package/dist/library/index.d.ts +11 -1
- package/dist/library/index.js +18 -4
- package/package.json +102 -83
package/dist/chat/index.js
CHANGED
|
@@ -1,3 +1,243 @@
|
|
|
1
|
+
// src/chat/realtime.ts
|
|
2
|
+
var RealtimeManager = class {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.client = null;
|
|
5
|
+
this.initPromise = null;
|
|
6
|
+
this.channels = /* @__PURE__ */ new Map();
|
|
7
|
+
this.refreshTimer = null;
|
|
8
|
+
this.currentToken = null;
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the Supabase client and authenticate with a realtime token.
|
|
13
|
+
* Returns true if successful, false if @supabase/supabase-js is unavailable.
|
|
14
|
+
*/
|
|
15
|
+
init() {
|
|
16
|
+
if (this.initPromise) return this.initPromise;
|
|
17
|
+
this.initPromise = this.doInit();
|
|
18
|
+
return this.initPromise;
|
|
19
|
+
}
|
|
20
|
+
async doInit() {
|
|
21
|
+
try {
|
|
22
|
+
const { createClient } = await import(
|
|
23
|
+
/* webpackIgnore: true */
|
|
24
|
+
"@supabase/supabase-js"
|
|
25
|
+
);
|
|
26
|
+
this.client = createClient(this.options.supabaseUrl, this.options.supabaseAnonKey, {
|
|
27
|
+
auth: { persistSession: false, autoRefreshToken: false },
|
|
28
|
+
realtime: { params: { eventsPerSecond: 10 } }
|
|
29
|
+
});
|
|
30
|
+
const tokenData = await this.options.fetchToken();
|
|
31
|
+
this.setToken(tokenData);
|
|
32
|
+
return true;
|
|
33
|
+
} catch {
|
|
34
|
+
this.initPromise = null;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
setToken(tokenData) {
|
|
39
|
+
this.currentToken = tokenData.token;
|
|
40
|
+
if (this.client) {
|
|
41
|
+
this.client.realtime.setAuth(tokenData.token);
|
|
42
|
+
}
|
|
43
|
+
this.scheduleRefresh(tokenData.refreshAt);
|
|
44
|
+
}
|
|
45
|
+
scheduleRefresh(refreshAt) {
|
|
46
|
+
if (this.refreshTimer) {
|
|
47
|
+
clearTimeout(this.refreshTimer);
|
|
48
|
+
}
|
|
49
|
+
const msUntilRefresh = new Date(refreshAt).getTime() - Date.now();
|
|
50
|
+
const delay = Math.max(msUntilRefresh, 1e4);
|
|
51
|
+
this.refreshTimer = setTimeout(() => this.doRefresh(), delay);
|
|
52
|
+
}
|
|
53
|
+
async doRefresh() {
|
|
54
|
+
try {
|
|
55
|
+
const tokenData = await this.options.fetchToken();
|
|
56
|
+
this.setToken(tokenData);
|
|
57
|
+
} catch {
|
|
58
|
+
this.refreshTimer = setTimeout(() => this.doRefresh(), 3e4);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Subscribe to new messages on a ticket via Postgres Changes.
|
|
63
|
+
* Returns an unsubscribe function.
|
|
64
|
+
*/
|
|
65
|
+
subscribeToMessages(ticketId, callback) {
|
|
66
|
+
if (!this.client) return () => {
|
|
67
|
+
};
|
|
68
|
+
const channelName = `messages:${ticketId}`;
|
|
69
|
+
this.removeChannel(channelName);
|
|
70
|
+
const channel = this.client.channel(channelName).on(
|
|
71
|
+
"postgres_changes",
|
|
72
|
+
{
|
|
73
|
+
event: "INSERT",
|
|
74
|
+
schema: "public",
|
|
75
|
+
table: "messages",
|
|
76
|
+
filter: `ticket_id=eq.${ticketId}`
|
|
77
|
+
},
|
|
78
|
+
(payload) => {
|
|
79
|
+
const row = payload.new;
|
|
80
|
+
if (!row) return;
|
|
81
|
+
const message = {
|
|
82
|
+
id: row.id,
|
|
83
|
+
content: row.content,
|
|
84
|
+
sender: row.sender,
|
|
85
|
+
sender_name: row.sender_name ?? "",
|
|
86
|
+
created_at: row.created_at,
|
|
87
|
+
read_at: row.read_at ?? null,
|
|
88
|
+
attachments: row.attachments ?? []
|
|
89
|
+
};
|
|
90
|
+
callback(message);
|
|
91
|
+
}
|
|
92
|
+
).subscribe();
|
|
93
|
+
this.channels.set(channelName, channel);
|
|
94
|
+
return () => this.removeChannel(channelName);
|
|
95
|
+
}
|
|
96
|
+
/** Get the underlying Supabase client (for sharing with PresenceManager). */
|
|
97
|
+
getClient() {
|
|
98
|
+
return this.client;
|
|
99
|
+
}
|
|
100
|
+
/** Whether the realtime manager is initialized and has a token. */
|
|
101
|
+
get isReady() {
|
|
102
|
+
return this.client !== null && this.currentToken !== null;
|
|
103
|
+
}
|
|
104
|
+
removeChannel(name) {
|
|
105
|
+
const existing = this.channels.get(name);
|
|
106
|
+
if (existing && this.client) {
|
|
107
|
+
this.client.removeChannel(existing);
|
|
108
|
+
this.channels.delete(name);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Clean up all channels, timers, and the client. */
|
|
112
|
+
destroy() {
|
|
113
|
+
if (this.refreshTimer) {
|
|
114
|
+
clearTimeout(this.refreshTimer);
|
|
115
|
+
this.refreshTimer = null;
|
|
116
|
+
}
|
|
117
|
+
for (const [name] of this.channels) {
|
|
118
|
+
this.removeChannel(name);
|
|
119
|
+
}
|
|
120
|
+
this.channels.clear();
|
|
121
|
+
this.client = null;
|
|
122
|
+
this.initPromise = null;
|
|
123
|
+
this.currentToken = null;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// src/chat/presence.ts
|
|
128
|
+
var TYPING_THROTTLE_MS = 2e3;
|
|
129
|
+
var TYPING_CLEAR_DELAY_MS = 3e3;
|
|
130
|
+
var PresenceManager = class {
|
|
131
|
+
constructor(options) {
|
|
132
|
+
this.client = null;
|
|
133
|
+
this.channels = /* @__PURE__ */ new Map();
|
|
134
|
+
this.lastTypingSentAt = /* @__PURE__ */ new Map();
|
|
135
|
+
this.typingClearTimers = /* @__PURE__ */ new Map();
|
|
136
|
+
this.options = options;
|
|
137
|
+
}
|
|
138
|
+
/** Set the Supabase client (shared from RealtimeManager). */
|
|
139
|
+
setClient(client) {
|
|
140
|
+
this.client = client;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Subscribe to agent typing events on a ticket.
|
|
144
|
+
* Only fires callback for agent typing (has `agent_id`), not customer typing echoes.
|
|
145
|
+
*
|
|
146
|
+
* @returns Unsubscribe function
|
|
147
|
+
*/
|
|
148
|
+
subscribeToTyping(ticketId, callback) {
|
|
149
|
+
if (!this.client) return () => {
|
|
150
|
+
};
|
|
151
|
+
const channelName = `typing:${ticketId}`;
|
|
152
|
+
this.removeChannel(channelName);
|
|
153
|
+
const channel = this.client.channel(`ticket:${ticketId}`).on("broadcast", { event: "typing" }, (data) => {
|
|
154
|
+
const payload = data?.payload;
|
|
155
|
+
if (!payload) return;
|
|
156
|
+
if (payload.agent_id) {
|
|
157
|
+
const typingPayload = {
|
|
158
|
+
agent_id: payload.agent_id,
|
|
159
|
+
agent_name: payload.agent_name || "Agent",
|
|
160
|
+
is_typing: !!payload.is_typing
|
|
161
|
+
};
|
|
162
|
+
callback(typingPayload);
|
|
163
|
+
}
|
|
164
|
+
}).subscribe();
|
|
165
|
+
this.channels.set(channelName, channel);
|
|
166
|
+
return () => this.removeChannel(channelName);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Send a customer typing indicator.
|
|
170
|
+
* Throttled: max 1 typing=true per 2s, auto-clears after 3s.
|
|
171
|
+
*/
|
|
172
|
+
async sendTyping(ticketId, isTyping) {
|
|
173
|
+
if (!this.client) return;
|
|
174
|
+
const existingTimer = this.typingClearTimers.get(ticketId);
|
|
175
|
+
if (existingTimer) {
|
|
176
|
+
clearTimeout(existingTimer);
|
|
177
|
+
this.typingClearTimers.delete(ticketId);
|
|
178
|
+
}
|
|
179
|
+
if (isTyping) {
|
|
180
|
+
const lastSent = this.lastTypingSentAt.get(ticketId) || 0;
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
if (now - lastSent < TYPING_THROTTLE_MS) {
|
|
183
|
+
const timer2 = setTimeout(() => {
|
|
184
|
+
this.doSendTyping(ticketId, false);
|
|
185
|
+
}, TYPING_CLEAR_DELAY_MS);
|
|
186
|
+
this.typingClearTimers.set(ticketId, timer2);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
this.lastTypingSentAt.set(ticketId, now);
|
|
190
|
+
const timer = setTimeout(() => {
|
|
191
|
+
this.doSendTyping(ticketId, false);
|
|
192
|
+
}, TYPING_CLEAR_DELAY_MS);
|
|
193
|
+
this.typingClearTimers.set(ticketId, timer);
|
|
194
|
+
}
|
|
195
|
+
await this.doSendTyping(ticketId, isTyping);
|
|
196
|
+
}
|
|
197
|
+
async doSendTyping(ticketId, isTyping) {
|
|
198
|
+
if (!this.client) return;
|
|
199
|
+
try {
|
|
200
|
+
const sendChannelName = `typing-send:${ticketId}`;
|
|
201
|
+
let channel = this.channels.get(sendChannelName);
|
|
202
|
+
if (!channel) {
|
|
203
|
+
channel = this.client.channel(`ticket:${ticketId}`);
|
|
204
|
+
channel.subscribe();
|
|
205
|
+
this.channels.set(sendChannelName, channel);
|
|
206
|
+
}
|
|
207
|
+
await channel.send({
|
|
208
|
+
type: "broadcast",
|
|
209
|
+
event: "typing",
|
|
210
|
+
payload: {
|
|
211
|
+
customer_id: this.options.customerId,
|
|
212
|
+
customer_name: this.options.customerName,
|
|
213
|
+
is_typing: isTyping
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
removeChannel(name) {
|
|
220
|
+
const existing = this.channels.get(name);
|
|
221
|
+
if (existing && this.client) {
|
|
222
|
+
this.client.removeChannel(existing);
|
|
223
|
+
this.channels.delete(name);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/** Clean up all channels and timers. */
|
|
227
|
+
destroy() {
|
|
228
|
+
for (const timer of this.typingClearTimers.values()) {
|
|
229
|
+
clearTimeout(timer);
|
|
230
|
+
}
|
|
231
|
+
this.typingClearTimers.clear();
|
|
232
|
+
this.lastTypingSentAt.clear();
|
|
233
|
+
for (const [name] of this.channels) {
|
|
234
|
+
this.removeChannel(name);
|
|
235
|
+
}
|
|
236
|
+
this.channels.clear();
|
|
237
|
+
this.client = null;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
1
241
|
// src/chat/index.ts
|
|
2
242
|
var DEFAULT_BASE_URL = "https://app.cstar.help";
|
|
3
243
|
var DEFAULT_POLLING_INTERVAL = 3e3;
|
|
@@ -5,10 +245,17 @@ var ChatClient = class {
|
|
|
5
245
|
constructor(config) {
|
|
6
246
|
this.sessionToken = null;
|
|
7
247
|
this.teamId = null;
|
|
248
|
+
this.customerId = null;
|
|
8
249
|
this.pollingTimers = /* @__PURE__ */ new Map();
|
|
250
|
+
this.realtimeManager = null;
|
|
251
|
+
this.presenceManager = null;
|
|
252
|
+
this.realtimeReady = false;
|
|
253
|
+
this.realtimeInitPromise = null;
|
|
9
254
|
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
10
255
|
this.teamSlug = config.teamSlug;
|
|
11
256
|
this.pollingInterval = config.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
|
|
257
|
+
this.supabaseUrl = config.supabaseUrl;
|
|
258
|
+
this.supabaseAnonKey = config.supabaseAnonKey;
|
|
12
259
|
this.conversations = new ConversationsApi(this);
|
|
13
260
|
this.messages = new MessagesApi(this);
|
|
14
261
|
}
|
|
@@ -16,6 +263,9 @@ var ChatClient = class {
|
|
|
16
263
|
* Identify a customer using HMAC signature verification.
|
|
17
264
|
* Must be called before accessing conversations or messages.
|
|
18
265
|
*
|
|
266
|
+
* If `supabaseUrl` and `supabaseAnonKey` were provided in the config,
|
|
267
|
+
* this will also initialize real-time subscriptions (non-blocking).
|
|
268
|
+
*
|
|
19
269
|
* @param customer - Customer identity fields
|
|
20
270
|
* @param signature - HMAC-SHA256 hex signature of the signed fields
|
|
21
271
|
* @param testMode - Whether to use test mode (relaxed timestamp validation)
|
|
@@ -34,40 +284,27 @@ var ChatClient = class {
|
|
|
34
284
|
}
|
|
35
285
|
);
|
|
36
286
|
this.sessionToken = response.data.sessionToken;
|
|
287
|
+
this.teamId = response.data.teamId;
|
|
288
|
+
this.customerId = response.data.customer.id;
|
|
289
|
+
if (this.supabaseUrl && this.supabaseAnonKey) {
|
|
290
|
+
this.realtimeInitPromise = this.initRealtime(
|
|
291
|
+
response.data.customer.id,
|
|
292
|
+
response.data.customer.name || customer.name || customer.email
|
|
293
|
+
);
|
|
294
|
+
}
|
|
37
295
|
return response.data;
|
|
38
296
|
}
|
|
39
297
|
/**
|
|
40
298
|
* Subscribe to new messages on a conversation.
|
|
41
|
-
*
|
|
299
|
+
* Uses Supabase Realtime (Postgres Changes) when available, falls back to polling.
|
|
42
300
|
*
|
|
43
301
|
* @returns Unsubscribe function
|
|
44
302
|
*/
|
|
45
303
|
onMessage(ticketId, callback) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (messages.length > 0) {
|
|
51
|
-
const latest = messages[messages.length - 1];
|
|
52
|
-
if (lastMessageId && latest.id !== lastMessageId) {
|
|
53
|
-
const lastIdx = messages.findIndex((m) => m.id === lastMessageId);
|
|
54
|
-
const newMessages = lastIdx >= 0 ? messages.slice(lastIdx + 1) : [latest];
|
|
55
|
-
for (const msg of newMessages) {
|
|
56
|
-
callback(msg);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
lastMessageId = latest.id;
|
|
60
|
-
}
|
|
61
|
-
} catch {
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
poll();
|
|
65
|
-
const timer = setInterval(poll, this.pollingInterval);
|
|
66
|
-
this.pollingTimers.set(ticketId, timer);
|
|
67
|
-
return () => {
|
|
68
|
-
clearInterval(timer);
|
|
69
|
-
this.pollingTimers.delete(ticketId);
|
|
70
|
-
};
|
|
304
|
+
if (this.realtimeReady && this.realtimeManager) {
|
|
305
|
+
return this.realtimeManager.subscribeToMessages(ticketId, callback);
|
|
306
|
+
}
|
|
307
|
+
return this.pollMessages(ticketId, callback);
|
|
71
308
|
}
|
|
72
309
|
/**
|
|
73
310
|
* Subscribe to typing indicator events.
|
|
@@ -75,17 +312,41 @@ var ChatClient = class {
|
|
|
75
312
|
*
|
|
76
313
|
* @returns Unsubscribe function
|
|
77
314
|
*/
|
|
78
|
-
onTyping(
|
|
315
|
+
onTyping(ticketId, callback) {
|
|
316
|
+
if (this.realtimeReady && this.presenceManager) {
|
|
317
|
+
return this.presenceManager.subscribeToTyping(ticketId, callback);
|
|
318
|
+
}
|
|
79
319
|
return () => {
|
|
80
320
|
};
|
|
81
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Send a customer typing indicator (requires realtime).
|
|
324
|
+
* No-op if realtime is not available.
|
|
325
|
+
*/
|
|
326
|
+
async sendTyping(ticketId, isTyping) {
|
|
327
|
+
if (this.presenceManager) {
|
|
328
|
+
await this.presenceManager.sendTyping(ticketId, isTyping);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
82
331
|
/** Disconnect all subscriptions and clean up. */
|
|
83
332
|
disconnect() {
|
|
333
|
+
if (this.realtimeManager) {
|
|
334
|
+
this.realtimeManager.destroy();
|
|
335
|
+
this.realtimeManager = null;
|
|
336
|
+
}
|
|
337
|
+
if (this.presenceManager) {
|
|
338
|
+
this.presenceManager.destroy();
|
|
339
|
+
this.presenceManager = null;
|
|
340
|
+
}
|
|
341
|
+
this.realtimeReady = false;
|
|
342
|
+
this.realtimeInitPromise = null;
|
|
84
343
|
for (const timer of this.pollingTimers.values()) {
|
|
85
344
|
clearInterval(timer);
|
|
86
345
|
}
|
|
87
346
|
this.pollingTimers.clear();
|
|
88
347
|
this.sessionToken = null;
|
|
348
|
+
this.teamId = null;
|
|
349
|
+
this.customerId = null;
|
|
89
350
|
}
|
|
90
351
|
/** @internal Make an authenticated request to the widget API. */
|
|
91
352
|
async fetch(path, options) {
|
|
@@ -115,6 +376,72 @@ var ChatClient = class {
|
|
|
115
376
|
setTeamId(teamId) {
|
|
116
377
|
this.teamId = teamId;
|
|
117
378
|
}
|
|
379
|
+
/** @internal Wait for realtime to be initialized (for testing). */
|
|
380
|
+
async waitForRealtime() {
|
|
381
|
+
if (this.realtimeInitPromise) {
|
|
382
|
+
await this.realtimeInitPromise;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async initRealtime(customerId, customerName) {
|
|
386
|
+
try {
|
|
387
|
+
this.realtimeManager = new RealtimeManager({
|
|
388
|
+
supabaseUrl: this.supabaseUrl,
|
|
389
|
+
supabaseAnonKey: this.supabaseAnonKey,
|
|
390
|
+
fetchToken: () => this.fetchRealtimeToken()
|
|
391
|
+
});
|
|
392
|
+
const ready = await this.realtimeManager.init();
|
|
393
|
+
if (ready) {
|
|
394
|
+
this.realtimeReady = true;
|
|
395
|
+
this.presenceManager = new PresenceManager({
|
|
396
|
+
customerId,
|
|
397
|
+
customerName
|
|
398
|
+
});
|
|
399
|
+
const client = this.realtimeManager.getClient();
|
|
400
|
+
if (client) {
|
|
401
|
+
this.presenceManager.setClient(client);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} catch {
|
|
405
|
+
this.realtimeReady = false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async fetchRealtimeToken() {
|
|
409
|
+
const response = await this.fetch(
|
|
410
|
+
"/api/widget/realtime-token",
|
|
411
|
+
{
|
|
412
|
+
method: "POST",
|
|
413
|
+
body: { teamId: this.teamId }
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
return response.data;
|
|
417
|
+
}
|
|
418
|
+
pollMessages(ticketId, callback) {
|
|
419
|
+
let lastMessageId = null;
|
|
420
|
+
const poll = async () => {
|
|
421
|
+
try {
|
|
422
|
+
const messages = await this.messages.list(ticketId);
|
|
423
|
+
if (messages.length > 0) {
|
|
424
|
+
const latest = messages[messages.length - 1];
|
|
425
|
+
if (lastMessageId && latest.id !== lastMessageId) {
|
|
426
|
+
const lastIdx = messages.findIndex((m) => m.id === lastMessageId);
|
|
427
|
+
const newMessages = lastIdx >= 0 ? messages.slice(lastIdx + 1) : [latest];
|
|
428
|
+
for (const msg of newMessages) {
|
|
429
|
+
callback(msg);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
lastMessageId = latest.id;
|
|
433
|
+
}
|
|
434
|
+
} catch {
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
poll();
|
|
438
|
+
const timer = setInterval(poll, this.pollingInterval);
|
|
439
|
+
this.pollingTimers.set(ticketId, timer);
|
|
440
|
+
return () => {
|
|
441
|
+
clearInterval(timer);
|
|
442
|
+
this.pollingTimers.delete(ticketId);
|
|
443
|
+
};
|
|
444
|
+
}
|
|
118
445
|
buildUrl(path, query) {
|
|
119
446
|
const base = `${this.baseUrl}${path}`;
|
|
120
447
|
if (!query) return base;
|
|
@@ -149,17 +476,14 @@ var ConversationsApi = class {
|
|
|
149
476
|
/** Create a new conversation (ticket). */
|
|
150
477
|
async create(params) {
|
|
151
478
|
const teamId = this.chat.getTeamId();
|
|
152
|
-
const response = await this.chat.fetch(
|
|
153
|
-
"
|
|
154
|
-
{
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
subject: params.subject,
|
|
159
|
-
message: params.message
|
|
160
|
-
}
|
|
479
|
+
const response = await this.chat.fetch("/api/widget/tickets", {
|
|
480
|
+
method: "POST",
|
|
481
|
+
body: {
|
|
482
|
+
teamId,
|
|
483
|
+
subject: params.subject,
|
|
484
|
+
message: params.message
|
|
161
485
|
}
|
|
162
|
-
);
|
|
486
|
+
});
|
|
163
487
|
return {
|
|
164
488
|
id: response.data.id,
|
|
165
489
|
ticketNumber: "",
|
package/dist/index.cjs
CHANGED
|
@@ -97,7 +97,7 @@ function parseError(statusCode, body, retryAfter) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
// src/client.ts
|
|
100
|
-
var SDK_VERSION = "0.
|
|
100
|
+
var SDK_VERSION = "0.2.0";
|
|
101
101
|
var DEFAULT_BASE_URL = "https://app.cstar.help";
|
|
102
102
|
var DEFAULT_API_VERSION = "2026-03-01";
|
|
103
103
|
var DEFAULT_MAX_RETRIES = 3;
|
|
@@ -179,7 +179,7 @@ var HttpClient = class {
|
|
|
179
179
|
}
|
|
180
180
|
buildHeaders(idempotencyKey) {
|
|
181
181
|
const headers = {
|
|
182
|
-
|
|
182
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
183
183
|
"Content-Type": "application/json",
|
|
184
184
|
"CStar-Version": this.config.version,
|
|
185
185
|
"User-Agent": `@cstar.help/js/${SDK_VERSION}`
|
|
@@ -235,10 +235,7 @@ var BaseResource = class {
|
|
|
235
235
|
}
|
|
236
236
|
/** Delete a resource by ID. */
|
|
237
237
|
async del(id) {
|
|
238
|
-
return this.client.request(
|
|
239
|
-
"DELETE",
|
|
240
|
-
`${this.basePath}/${id}`
|
|
241
|
-
);
|
|
238
|
+
return this.client.request("DELETE", `${this.basePath}/${id}`);
|
|
242
239
|
}
|
|
243
240
|
/**
|
|
244
241
|
* Auto-paginating async iterator that yields every item across all pages.
|
|
@@ -285,21 +282,14 @@ var TicketMessagesResource = class {
|
|
|
285
282
|
}
|
|
286
283
|
/** List all messages on a ticket. */
|
|
287
284
|
async list(ticketId) {
|
|
288
|
-
return this.client.request(
|
|
289
|
-
"GET",
|
|
290
|
-
`/tickets/${ticketId}/messages`
|
|
291
|
-
);
|
|
285
|
+
return this.client.request("GET", `/tickets/${ticketId}/messages`);
|
|
292
286
|
}
|
|
293
287
|
/** Add a message to a ticket. */
|
|
294
288
|
async create(ticketId, params, options) {
|
|
295
|
-
return this.client.request(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
body: params,
|
|
300
|
-
idempotencyKey: options?.idempotencyKey
|
|
301
|
-
}
|
|
302
|
-
);
|
|
289
|
+
return this.client.request("POST", `/tickets/${ticketId}/messages`, {
|
|
290
|
+
body: params,
|
|
291
|
+
idempotencyKey: options?.idempotencyKey
|
|
292
|
+
});
|
|
303
293
|
}
|
|
304
294
|
};
|
|
305
295
|
var TicketsResource = class extends BaseResource {
|
package/dist/index.js
CHANGED
|
@@ -65,7 +65,7 @@ function parseError(statusCode, body, retryAfter) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// src/client.ts
|
|
68
|
-
var SDK_VERSION = "0.
|
|
68
|
+
var SDK_VERSION = "0.2.0";
|
|
69
69
|
var DEFAULT_BASE_URL = "https://app.cstar.help";
|
|
70
70
|
var DEFAULT_API_VERSION = "2026-03-01";
|
|
71
71
|
var DEFAULT_MAX_RETRIES = 3;
|
|
@@ -147,7 +147,7 @@ var HttpClient = class {
|
|
|
147
147
|
}
|
|
148
148
|
buildHeaders(idempotencyKey) {
|
|
149
149
|
const headers = {
|
|
150
|
-
|
|
150
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
151
151
|
"Content-Type": "application/json",
|
|
152
152
|
"CStar-Version": this.config.version,
|
|
153
153
|
"User-Agent": `@cstar.help/js/${SDK_VERSION}`
|
|
@@ -203,10 +203,7 @@ var BaseResource = class {
|
|
|
203
203
|
}
|
|
204
204
|
/** Delete a resource by ID. */
|
|
205
205
|
async del(id) {
|
|
206
|
-
return this.client.request(
|
|
207
|
-
"DELETE",
|
|
208
|
-
`${this.basePath}/${id}`
|
|
209
|
-
);
|
|
206
|
+
return this.client.request("DELETE", `${this.basePath}/${id}`);
|
|
210
207
|
}
|
|
211
208
|
/**
|
|
212
209
|
* Auto-paginating async iterator that yields every item across all pages.
|
|
@@ -253,21 +250,14 @@ var TicketMessagesResource = class {
|
|
|
253
250
|
}
|
|
254
251
|
/** List all messages on a ticket. */
|
|
255
252
|
async list(ticketId) {
|
|
256
|
-
return this.client.request(
|
|
257
|
-
"GET",
|
|
258
|
-
`/tickets/${ticketId}/messages`
|
|
259
|
-
);
|
|
253
|
+
return this.client.request("GET", `/tickets/${ticketId}/messages`);
|
|
260
254
|
}
|
|
261
255
|
/** Add a message to a ticket. */
|
|
262
256
|
async create(ticketId, params, options) {
|
|
263
|
-
return this.client.request(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
body: params,
|
|
268
|
-
idempotencyKey: options?.idempotencyKey
|
|
269
|
-
}
|
|
270
|
-
);
|
|
257
|
+
return this.client.request("POST", `/tickets/${ticketId}/messages`, {
|
|
258
|
+
body: params,
|
|
259
|
+
idempotencyKey: options?.idempotencyKey
|
|
260
|
+
});
|
|
271
261
|
}
|
|
272
262
|
};
|
|
273
263
|
var TicketsResource = class extends BaseResource {
|
package/dist/library/index.cjs
CHANGED
|
@@ -65,10 +65,24 @@ var LibraryClient = class {
|
|
|
65
65
|
q: query
|
|
66
66
|
};
|
|
67
67
|
if (params?.limit) queryParams.limit = String(params.limit);
|
|
68
|
-
return this.fetch(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
return this.fetch(`/api/library/${this.teamSlug}/search`, queryParams);
|
|
69
|
+
}
|
|
70
|
+
/** Get the most viewed articles, sorted by view count descending. */
|
|
71
|
+
async popularArticles(limit = 5) {
|
|
72
|
+
const articles = await this.articles({ limit: 50 });
|
|
73
|
+
return articles.sort((a, b) => (b.view_count ?? 0) - (a.view_count ?? 0)).slice(0, limit);
|
|
74
|
+
}
|
|
75
|
+
/** Get aggregate stats for this knowledge base (article count, category count, total views). */
|
|
76
|
+
async stats() {
|
|
77
|
+
const [categories, articles] = await Promise.all([
|
|
78
|
+
this.categories(),
|
|
79
|
+
this.articles({ limit: 1e3 })
|
|
80
|
+
]);
|
|
81
|
+
return {
|
|
82
|
+
totalArticles: articles.length,
|
|
83
|
+
totalCategories: categories.length,
|
|
84
|
+
totalViews: articles.reduce((sum, a) => sum + (a.view_count ?? 0), 0)
|
|
85
|
+
};
|
|
72
86
|
}
|
|
73
87
|
async fetch(path, query) {
|
|
74
88
|
let url = `${this.baseUrl}${path}`;
|
package/dist/library/index.d.cts
CHANGED
|
@@ -50,6 +50,12 @@ interface LibrarySearchResult {
|
|
|
50
50
|
count: number;
|
|
51
51
|
query: string;
|
|
52
52
|
}
|
|
53
|
+
/** Aggregate stats for a public knowledge base. */
|
|
54
|
+
interface LibraryStats {
|
|
55
|
+
totalArticles: number;
|
|
56
|
+
totalCategories: number;
|
|
57
|
+
totalViews: number;
|
|
58
|
+
}
|
|
53
59
|
|
|
54
60
|
/**
|
|
55
61
|
* Public knowledge base client. No authentication required.
|
|
@@ -76,7 +82,11 @@ declare class LibraryClient {
|
|
|
76
82
|
* Returns matching articles with relevance ranking.
|
|
77
83
|
*/
|
|
78
84
|
search(query: string, params?: LibrarySearchParams): Promise<LibrarySearchResult>;
|
|
85
|
+
/** Get the most viewed articles, sorted by view count descending. */
|
|
86
|
+
popularArticles(limit?: number): Promise<LibraryArticle[]>;
|
|
87
|
+
/** Get aggregate stats for this knowledge base (article count, category count, total views). */
|
|
88
|
+
stats(): Promise<LibraryStats>;
|
|
79
89
|
private fetch;
|
|
80
90
|
}
|
|
81
91
|
|
|
82
|
-
export { type Category, type LibraryArticle, type LibraryArticleListParams, LibraryClient, type LibraryConfig, type LibrarySearchParams, type LibrarySearchResult };
|
|
92
|
+
export { type Category, type LibraryArticle, type LibraryArticleListParams, LibraryClient, type LibraryConfig, type LibrarySearchParams, type LibrarySearchResult, type LibraryStats };
|
package/dist/library/index.d.ts
CHANGED
|
@@ -50,6 +50,12 @@ interface LibrarySearchResult {
|
|
|
50
50
|
count: number;
|
|
51
51
|
query: string;
|
|
52
52
|
}
|
|
53
|
+
/** Aggregate stats for a public knowledge base. */
|
|
54
|
+
interface LibraryStats {
|
|
55
|
+
totalArticles: number;
|
|
56
|
+
totalCategories: number;
|
|
57
|
+
totalViews: number;
|
|
58
|
+
}
|
|
53
59
|
|
|
54
60
|
/**
|
|
55
61
|
* Public knowledge base client. No authentication required.
|
|
@@ -76,7 +82,11 @@ declare class LibraryClient {
|
|
|
76
82
|
* Returns matching articles with relevance ranking.
|
|
77
83
|
*/
|
|
78
84
|
search(query: string, params?: LibrarySearchParams): Promise<LibrarySearchResult>;
|
|
85
|
+
/** Get the most viewed articles, sorted by view count descending. */
|
|
86
|
+
popularArticles(limit?: number): Promise<LibraryArticle[]>;
|
|
87
|
+
/** Get aggregate stats for this knowledge base (article count, category count, total views). */
|
|
88
|
+
stats(): Promise<LibraryStats>;
|
|
79
89
|
private fetch;
|
|
80
90
|
}
|
|
81
91
|
|
|
82
|
-
export { type Category, type LibraryArticle, type LibraryArticleListParams, LibraryClient, type LibraryConfig, type LibrarySearchParams, type LibrarySearchResult };
|
|
92
|
+
export { type Category, type LibraryArticle, type LibraryArticleListParams, LibraryClient, type LibraryConfig, type LibrarySearchParams, type LibrarySearchResult, type LibraryStats };
|
package/dist/library/index.js
CHANGED
|
@@ -41,10 +41,24 @@ var LibraryClient = class {
|
|
|
41
41
|
q: query
|
|
42
42
|
};
|
|
43
43
|
if (params?.limit) queryParams.limit = String(params.limit);
|
|
44
|
-
return this.fetch(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
return this.fetch(`/api/library/${this.teamSlug}/search`, queryParams);
|
|
45
|
+
}
|
|
46
|
+
/** Get the most viewed articles, sorted by view count descending. */
|
|
47
|
+
async popularArticles(limit = 5) {
|
|
48
|
+
const articles = await this.articles({ limit: 50 });
|
|
49
|
+
return articles.sort((a, b) => (b.view_count ?? 0) - (a.view_count ?? 0)).slice(0, limit);
|
|
50
|
+
}
|
|
51
|
+
/** Get aggregate stats for this knowledge base (article count, category count, total views). */
|
|
52
|
+
async stats() {
|
|
53
|
+
const [categories, articles] = await Promise.all([
|
|
54
|
+
this.categories(),
|
|
55
|
+
this.articles({ limit: 1e3 })
|
|
56
|
+
]);
|
|
57
|
+
return {
|
|
58
|
+
totalArticles: articles.length,
|
|
59
|
+
totalCategories: categories.length,
|
|
60
|
+
totalViews: articles.reduce((sum, a) => sum + (a.view_count ?? 0), 0)
|
|
61
|
+
};
|
|
48
62
|
}
|
|
49
63
|
async fetch(path, query) {
|
|
50
64
|
let url = `${this.baseUrl}${path}`;
|