@cstar.help/js 0.11.0 → 0.12.1
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 +58 -6
- package/dist/auth/index.cjs +10 -10
- package/dist/auth/index.d.cts +4 -4
- package/dist/auth/index.d.ts +4 -4
- package/dist/auth/index.js +10 -10
- package/dist/chat/index.cjs +243 -10
- package/dist/chat/index.d.cts +188 -6
- package/dist/chat/index.d.ts +188 -6
- package/dist/chat/index.js +237 -9
- package/dist/quickhelp/index.cjs +102 -0
- package/dist/quickhelp/index.d.cts +97 -0
- package/dist/quickhelp/index.d.ts +97 -0
- package/dist/quickhelp/index.js +76 -0
- package/package.json +11 -1
- package/dist/index.d.cts +0 -2732
- package/dist/index.d.ts +0 -2732
- package/dist/webhook/index.d.cts +0 -163
- package/dist/webhook/index.d.ts +0 -163
package/dist/chat/index.js
CHANGED
|
@@ -98,6 +98,14 @@ var RealtimeManager = class {
|
|
|
98
98
|
getClient() {
|
|
99
99
|
return this.client;
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* The current Supabase JWT (set by init/refresh). Returned so callers can
|
|
103
|
+
* decode the `sub` claim to learn the customerId — required after a
|
|
104
|
+
* session-mode login where the SDK doesn't have customerId at hand.
|
|
105
|
+
*/
|
|
106
|
+
getCurrentToken() {
|
|
107
|
+
return this.currentToken;
|
|
108
|
+
}
|
|
101
109
|
/** Whether the realtime manager is initialized and has a token. */
|
|
102
110
|
get isReady() {
|
|
103
111
|
return this.client !== null && this.currentToken !== null;
|
|
@@ -239,6 +247,43 @@ var PresenceManager = class {
|
|
|
239
247
|
}
|
|
240
248
|
};
|
|
241
249
|
|
|
250
|
+
// src/chat/errors.ts
|
|
251
|
+
var ChatClientError = class extends Error {
|
|
252
|
+
constructor(message, opts) {
|
|
253
|
+
super(message);
|
|
254
|
+
this.name = "ChatClientError";
|
|
255
|
+
this.code = opts.code ?? "UNKNOWN";
|
|
256
|
+
this.statusCode = opts.statusCode;
|
|
257
|
+
this.requestId = opts.requestId ?? null;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
var ChatClientAuthError = class extends ChatClientError {
|
|
261
|
+
constructor(opts) {
|
|
262
|
+
super(opts.message, opts);
|
|
263
|
+
this.name = "ChatClientAuthError";
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
var ChatClientRateLimitError = class extends ChatClientError {
|
|
267
|
+
constructor(opts) {
|
|
268
|
+
super(opts.message, opts);
|
|
269
|
+
this.name = "ChatClientRateLimitError";
|
|
270
|
+
this.retryAfter = opts.retryAfter ?? null;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
var ChatClientServerError = class extends ChatClientError {
|
|
274
|
+
constructor(opts) {
|
|
275
|
+
super(opts.message, opts);
|
|
276
|
+
this.name = "ChatClientServerError";
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
var ChatClientNetworkError = class extends ChatClientError {
|
|
280
|
+
constructor(opts) {
|
|
281
|
+
super(opts.message, { code: "NETWORK_ERROR", statusCode: 0, requestId: null });
|
|
282
|
+
this.name = "ChatClientNetworkError";
|
|
283
|
+
this.originalError = opts.originalError;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
242
287
|
// src/chat/index.ts
|
|
243
288
|
var DEFAULT_BASE_URL = "https://www.cstar.help";
|
|
244
289
|
var DEFAULT_POLLING_INTERVAL = 3e3;
|
|
@@ -247,6 +292,7 @@ var ChatClient = class {
|
|
|
247
292
|
this.sessionToken = null;
|
|
248
293
|
this.teamId = null;
|
|
249
294
|
this.customerId = null;
|
|
295
|
+
this.authMode = null;
|
|
250
296
|
this.pollingTimers = /* @__PURE__ */ new Map();
|
|
251
297
|
this.realtimeManager = null;
|
|
252
298
|
this.presenceManager = null;
|
|
@@ -295,6 +341,70 @@ var ChatClient = class {
|
|
|
295
341
|
}
|
|
296
342
|
return response.data;
|
|
297
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* Sign in with cStar's hosted password auth. The SDK keeps the resulting
|
|
346
|
+
* Supabase access token and uses it as Bearer for every subsequent call.
|
|
347
|
+
* Real-time bootstraps automatically (same flow as `identify()`).
|
|
348
|
+
*
|
|
349
|
+
* Use this when your customers don't have a session in your app and you
|
|
350
|
+
* want cStar to handle login. For the case where your app already has a
|
|
351
|
+
* Supabase session, use `loginWithSession({ accessToken })` instead.
|
|
352
|
+
*/
|
|
353
|
+
async loginWithCredentials(params) {
|
|
354
|
+
const teamId = await this.resolveTeamIdFromSlug();
|
|
355
|
+
const response = await this.fetch("/api/widget/auth/login", {
|
|
356
|
+
method: "POST",
|
|
357
|
+
body: { email: params.email, password: params.password, teamId }
|
|
358
|
+
});
|
|
359
|
+
if (!response.accessToken) {
|
|
360
|
+
throw new ChatClientAuthError({
|
|
361
|
+
message: "Login succeeded but no session was issued",
|
|
362
|
+
code: "NO_SESSION",
|
|
363
|
+
statusCode: 200,
|
|
364
|
+
requestId: null
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
await this.loginWithSession({
|
|
368
|
+
accessToken: response.accessToken,
|
|
369
|
+
teamId,
|
|
370
|
+
customerName: response.customer.name ?? response.customer.email
|
|
371
|
+
});
|
|
372
|
+
return {
|
|
373
|
+
customer: {
|
|
374
|
+
id: this.customerId ?? response.customer.id,
|
|
375
|
+
email: response.customer.email,
|
|
376
|
+
name: response.customer.name
|
|
377
|
+
},
|
|
378
|
+
teamId
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Use an existing Supabase access token for widget calls. Skip the cStar
|
|
383
|
+
* login flow entirely; the SDK uses the supplied token as Bearer and
|
|
384
|
+
* bootstraps real-time the same way `identify()` does.
|
|
385
|
+
*
|
|
386
|
+
* The widget API verifies the token via the `widget-session` strategy on
|
|
387
|
+
* every request — there is no client-side validation here.
|
|
388
|
+
*/
|
|
389
|
+
async loginWithSession(params) {
|
|
390
|
+
this.sessionToken = params.accessToken;
|
|
391
|
+
if (params.teamId) {
|
|
392
|
+
this.teamId = params.teamId;
|
|
393
|
+
}
|
|
394
|
+
this.authMode = "session";
|
|
395
|
+
if (this.realtimeEnabled) {
|
|
396
|
+
const customerName = params.customerName ?? "";
|
|
397
|
+
this.realtimeInitPromise = this.initRealtimeFromSession(customerName);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Returns the auth mode the client is currently using, or `null` before
|
|
402
|
+
* any authenticated call has resolved one. Useful for branching UI
|
|
403
|
+
* (e.g. to show a "Sign out" button only in session mode).
|
|
404
|
+
*/
|
|
405
|
+
getAuthMode() {
|
|
406
|
+
return this.authMode;
|
|
407
|
+
}
|
|
298
408
|
/**
|
|
299
409
|
* Subscribe to new messages on a ticket.
|
|
300
410
|
* Uses real-time delivery when available, falls back to polling.
|
|
@@ -348,8 +458,20 @@ var ChatClient = class {
|
|
|
348
458
|
this.sessionToken = null;
|
|
349
459
|
this.teamId = null;
|
|
350
460
|
this.customerId = null;
|
|
461
|
+
this.authMode = null;
|
|
351
462
|
}
|
|
352
|
-
/**
|
|
463
|
+
/**
|
|
464
|
+
* @internal Make an authenticated request to the widget API.
|
|
465
|
+
*
|
|
466
|
+
* On any non-2xx response, throws a typed subclass of `ChatClientError`
|
|
467
|
+
* (`ChatClientAuthError`, `ChatClientRateLimitError`, etc.) so consumers
|
|
468
|
+
* can branch on error class instead of parsing message strings.
|
|
469
|
+
*
|
|
470
|
+
* On the way out, reads the `X-Cstar-Auth-Mode` response header (set by
|
|
471
|
+
* widgetHandler on every response) and caches it on the client. After
|
|
472
|
+
* the first authenticated round-trip, `getAuthMode()` returns the
|
|
473
|
+
* server-confirmed mode.
|
|
474
|
+
*/
|
|
353
475
|
async fetch(path, options) {
|
|
354
476
|
const url = this.buildUrl(path, options.query);
|
|
355
477
|
const headers = {
|
|
@@ -358,17 +480,104 @@ var ChatClient = class {
|
|
|
358
480
|
if (this.sessionToken) {
|
|
359
481
|
headers["Authorization"] = `Bearer ${this.sessionToken}`;
|
|
360
482
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
483
|
+
let response;
|
|
484
|
+
try {
|
|
485
|
+
response = await globalThis.fetch(url, {
|
|
486
|
+
method: options.method,
|
|
487
|
+
headers,
|
|
488
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
489
|
+
});
|
|
490
|
+
} catch (networkErr) {
|
|
491
|
+
throw new ChatClientNetworkError({
|
|
492
|
+
message: networkErr instanceof Error ? networkErr.message : "Network request failed",
|
|
493
|
+
originalError: networkErr
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
const responseHeaders = response.headers && typeof response.headers.get === "function" ? response.headers : null;
|
|
497
|
+
const headerMode = responseHeaders?.get("X-Cstar-Auth-Mode") ?? null;
|
|
498
|
+
if (headerMode === "hmac" || headerMode === "session") {
|
|
499
|
+
this.authMode = headerMode;
|
|
500
|
+
}
|
|
501
|
+
const requestId = responseHeaders?.get("X-Request-Id") ?? null;
|
|
366
502
|
if (!response.ok) {
|
|
367
|
-
const errorBody = await response.json().catch(() => ({ error: { message: "Request failed" } }));
|
|
368
|
-
|
|
503
|
+
const errorBody = await response.json().catch(() => ({ error: { code: "UNKNOWN", message: "Request failed" } }));
|
|
504
|
+
const code = errorBody.error?.code ?? "UNKNOWN";
|
|
505
|
+
const message = errorBody.error?.message ?? `Widget API error: ${response.status}`;
|
|
506
|
+
if (response.status === 401 || response.status === 403) {
|
|
507
|
+
throw new ChatClientAuthError({
|
|
508
|
+
message,
|
|
509
|
+
code,
|
|
510
|
+
statusCode: response.status,
|
|
511
|
+
requestId
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
if (response.status === 429) {
|
|
515
|
+
const retryAfterHeader = responseHeaders?.get("Retry-After") ?? null;
|
|
516
|
+
const retryAfter = retryAfterHeader ? parseInt(retryAfterHeader, 10) : null;
|
|
517
|
+
throw new ChatClientRateLimitError({
|
|
518
|
+
message,
|
|
519
|
+
code,
|
|
520
|
+
statusCode: response.status,
|
|
521
|
+
requestId,
|
|
522
|
+
retryAfter: Number.isFinite(retryAfter ?? NaN) ? retryAfter : null
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
if (response.status >= 500) {
|
|
526
|
+
throw new ChatClientServerError({
|
|
527
|
+
message,
|
|
528
|
+
code,
|
|
529
|
+
statusCode: response.status,
|
|
530
|
+
requestId
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
throw new ChatClientError(message, {
|
|
534
|
+
code,
|
|
535
|
+
statusCode: response.status,
|
|
536
|
+
requestId
|
|
537
|
+
});
|
|
369
538
|
}
|
|
370
539
|
return response.json();
|
|
371
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* Look up the team UUID from the configured `teamSlug`. Used by
|
|
543
|
+
* `loginWithCredentials`. HMAC integrators (`identify()`) never hit
|
|
544
|
+
* this path — verify-identity accepts the slug directly.
|
|
545
|
+
*
|
|
546
|
+
* @internal
|
|
547
|
+
*/
|
|
548
|
+
async resolveTeamIdFromSlug() {
|
|
549
|
+
const response = await this.fetch(
|
|
550
|
+
"/api/widget/team-by-slug",
|
|
551
|
+
{
|
|
552
|
+
method: "GET",
|
|
553
|
+
query: { teamSlug: this.teamSlug }
|
|
554
|
+
}
|
|
555
|
+
);
|
|
556
|
+
return response.data.teamId;
|
|
557
|
+
}
|
|
558
|
+
/** @internal Bootstrap realtime from a session login (no precomputed customerId). */
|
|
559
|
+
async initRealtimeFromSession(customerName) {
|
|
560
|
+
try {
|
|
561
|
+
this.realtimeManager = new RealtimeManager({
|
|
562
|
+
fetchToken: () => this.fetchRealtimeToken()
|
|
563
|
+
});
|
|
564
|
+
const ready = await this.realtimeManager.init();
|
|
565
|
+
if (!ready) return;
|
|
566
|
+
this.realtimeReady = true;
|
|
567
|
+
const token = this.realtimeManager.getCurrentToken();
|
|
568
|
+
const customerId = token ? decodeJwtSub(token) : null;
|
|
569
|
+
if (customerId) {
|
|
570
|
+
this.customerId = customerId;
|
|
571
|
+
this.presenceManager = new PresenceManager({ customerId, customerName });
|
|
572
|
+
const client = this.realtimeManager.getClient();
|
|
573
|
+
if (client) {
|
|
574
|
+
this.presenceManager.setClient(client);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
} catch {
|
|
578
|
+
this.realtimeReady = false;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
372
581
|
/** @internal Get the stored team ID for requests that need it. */
|
|
373
582
|
getTeamId() {
|
|
374
583
|
return this.teamId;
|
|
@@ -664,6 +873,25 @@ var MessagesApi = class {
|
|
|
664
873
|
return response.data.message;
|
|
665
874
|
}
|
|
666
875
|
};
|
|
876
|
+
function decodeJwtSub(jwt) {
|
|
877
|
+
try {
|
|
878
|
+
const parts = jwt.split(".");
|
|
879
|
+
if (parts.length !== 3) return null;
|
|
880
|
+
const payloadB64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
881
|
+
const padded = payloadB64 + "=".repeat((4 - payloadB64.length % 4) % 4);
|
|
882
|
+
const payload = JSON.parse(
|
|
883
|
+
typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf-8")
|
|
884
|
+
);
|
|
885
|
+
return typeof payload.sub === "string" ? payload.sub : null;
|
|
886
|
+
} catch {
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
667
890
|
export {
|
|
668
|
-
ChatClient
|
|
891
|
+
ChatClient,
|
|
892
|
+
ChatClientAuthError,
|
|
893
|
+
ChatClientError,
|
|
894
|
+
ChatClientNetworkError,
|
|
895
|
+
ChatClientRateLimitError,
|
|
896
|
+
ChatClientServerError
|
|
669
897
|
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/quickhelp/index.ts
|
|
21
|
+
var quickhelp_exports = {};
|
|
22
|
+
__export(quickhelp_exports, {
|
|
23
|
+
QuickHelpClient: () => QuickHelpClient,
|
|
24
|
+
QuickHelpError: () => QuickHelpError
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(quickhelp_exports);
|
|
27
|
+
var DEFAULT_BASE_URL = "https://www.cstar.help";
|
|
28
|
+
var QuickHelpClient = class {
|
|
29
|
+
constructor(config) {
|
|
30
|
+
if (!config?.teamId) {
|
|
31
|
+
throw new Error("QuickHelpClient: teamId is required");
|
|
32
|
+
}
|
|
33
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
34
|
+
this.teamId = config.teamId;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Fetch the sanitized public config (title, subtitle, placeholder,
|
|
38
|
+
* theme overrides). Never includes agent id or escalation contact
|
|
39
|
+
* info — those leak only through the agent's own replies.
|
|
40
|
+
*
|
|
41
|
+
* Returns `null` if Quick Help is disabled on the team (404).
|
|
42
|
+
*/
|
|
43
|
+
async config() {
|
|
44
|
+
const url = `${this.baseUrl}/api/quickhelp/config/${encodeURIComponent(this.teamId)}`;
|
|
45
|
+
const res = await globalThis.fetch(url, { method: "GET" });
|
|
46
|
+
if (res.status === 404) return null;
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const body2 = await res.json().catch(() => null);
|
|
49
|
+
throw new Error(body2?.error?.message ?? `Quick Help config failed: ${res.status}`);
|
|
50
|
+
}
|
|
51
|
+
const body = await res.json();
|
|
52
|
+
return body.data;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Ask a question and get an answer. On first turn, omit `sessionId`;
|
|
56
|
+
* the server mints one and you MUST pass it back on follow-ups. On
|
|
57
|
+
* rate-limit (429) the returned Error has a `retryAfterSeconds`
|
|
58
|
+
* property set from the response body.
|
|
59
|
+
*/
|
|
60
|
+
async ask(params) {
|
|
61
|
+
const url = `${this.baseUrl}/api/quickhelp/ask`;
|
|
62
|
+
const res = await globalThis.fetch(url, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
team_id: this.teamId,
|
|
67
|
+
session_id: params.sessionId ?? null,
|
|
68
|
+
question: params.question
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
const body = await res.json().catch(() => null);
|
|
72
|
+
if (!res.ok || !body?.success || !body.data) {
|
|
73
|
+
const message = body?.error?.message ?? `Quick Help ask failed: ${res.status}`;
|
|
74
|
+
const err = new QuickHelpError(message, {
|
|
75
|
+
status: res.status,
|
|
76
|
+
code: body?.error?.code,
|
|
77
|
+
retryAfterSeconds: parseRetryAfter(res.headers.get("retry-after"))
|
|
78
|
+
});
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
return body.data;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var QuickHelpError = class extends Error {
|
|
85
|
+
constructor(message, opts) {
|
|
86
|
+
super(message);
|
|
87
|
+
this.name = "QuickHelpError";
|
|
88
|
+
this.status = opts.status;
|
|
89
|
+
this.code = opts.code;
|
|
90
|
+
this.retryAfterSeconds = opts.retryAfterSeconds;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function parseRetryAfter(header) {
|
|
94
|
+
if (!header) return void 0;
|
|
95
|
+
const n = Number(header);
|
|
96
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
97
|
+
}
|
|
98
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
99
|
+
0 && (module.exports = {
|
|
100
|
+
QuickHelpClient,
|
|
101
|
+
QuickHelpError
|
|
102
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** Options for the QuickHelpClient. No auth — Quick Help is anonymous by design. */
|
|
2
|
+
interface QuickHelpConfig {
|
|
3
|
+
/** Your team's UUID. Required. */
|
|
4
|
+
teamId: string;
|
|
5
|
+
/** Override the base URL (defaults to https://www.cstar.help). */
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Sanitized config the server returns to anonymous visitors. */
|
|
9
|
+
interface QuickHelpPublicConfig {
|
|
10
|
+
headerTitle: string;
|
|
11
|
+
headerSubtitle: string;
|
|
12
|
+
placeholderText: string;
|
|
13
|
+
theme: 'light' | 'dark' | null;
|
|
14
|
+
primaryColor: string | null;
|
|
15
|
+
fontHeading: string | null;
|
|
16
|
+
fontBody: string | null;
|
|
17
|
+
borderRadius: string | null;
|
|
18
|
+
}
|
|
19
|
+
type QuickHelpTurnOutcome = 'answered' | 'escalated' | 'needs_clarification';
|
|
20
|
+
interface QuickHelpAskParams {
|
|
21
|
+
/** The visitor's question. Max 2000 chars. */
|
|
22
|
+
question: string;
|
|
23
|
+
/**
|
|
24
|
+
* The session id from a previous `ask()` call, if this is a follow-up
|
|
25
|
+
* turn. Omit or pass null on the first turn; the server mints a new
|
|
26
|
+
* id and returns it in `sessionId`.
|
|
27
|
+
*/
|
|
28
|
+
sessionId?: string | null;
|
|
29
|
+
}
|
|
30
|
+
interface QuickHelpAskResult {
|
|
31
|
+
sessionId: string;
|
|
32
|
+
answer: string;
|
|
33
|
+
turnOutcome: QuickHelpTurnOutcome;
|
|
34
|
+
turnNumber: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Quick Help — anonymous AI Q&A client.
|
|
39
|
+
*
|
|
40
|
+
* No API key. No login. The Quick Help widget is the exact same product
|
|
41
|
+
* cStar's marketing site dogfoods on its homepage, just driven
|
|
42
|
+
* programmatically. Sessions live entirely in memory on the caller side;
|
|
43
|
+
* the server hands back a `sessionId` on the first turn and expects you
|
|
44
|
+
* to pass it back on follow-ups so the orchestrator can see prior
|
|
45
|
+
* transcript.
|
|
46
|
+
*
|
|
47
|
+
* ```typescript
|
|
48
|
+
* import { QuickHelpClient } from '@cstar.help/js/quickhelp';
|
|
49
|
+
*
|
|
50
|
+
* const qh = new QuickHelpClient({ teamId: 'your-team-uuid' });
|
|
51
|
+
*
|
|
52
|
+
* const turn1 = await qh.ask({ question: 'How do I reset my password?' });
|
|
53
|
+
* console.log(turn1.answer);
|
|
54
|
+
*
|
|
55
|
+
* const turn2 = await qh.ask({
|
|
56
|
+
* question: 'Can you walk me through that?',
|
|
57
|
+
* sessionId: turn1.sessionId
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare class QuickHelpClient {
|
|
62
|
+
private readonly baseUrl;
|
|
63
|
+
private readonly teamId;
|
|
64
|
+
constructor(config: QuickHelpConfig);
|
|
65
|
+
/**
|
|
66
|
+
* Fetch the sanitized public config (title, subtitle, placeholder,
|
|
67
|
+
* theme overrides). Never includes agent id or escalation contact
|
|
68
|
+
* info — those leak only through the agent's own replies.
|
|
69
|
+
*
|
|
70
|
+
* Returns `null` if Quick Help is disabled on the team (404).
|
|
71
|
+
*/
|
|
72
|
+
config(): Promise<QuickHelpPublicConfig | null>;
|
|
73
|
+
/**
|
|
74
|
+
* Ask a question and get an answer. On first turn, omit `sessionId`;
|
|
75
|
+
* the server mints one and you MUST pass it back on follow-ups. On
|
|
76
|
+
* rate-limit (429) the returned Error has a `retryAfterSeconds`
|
|
77
|
+
* property set from the response body.
|
|
78
|
+
*/
|
|
79
|
+
ask(params: QuickHelpAskParams): Promise<QuickHelpAskResult>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Thrown by `QuickHelpClient.ask` when the request fails. Carries the
|
|
83
|
+
* HTTP status + error code + Retry-After (on 429) so callers can back
|
|
84
|
+
* off intelligently.
|
|
85
|
+
*/
|
|
86
|
+
declare class QuickHelpError extends Error {
|
|
87
|
+
readonly status: number;
|
|
88
|
+
readonly code?: string;
|
|
89
|
+
readonly retryAfterSeconds?: number;
|
|
90
|
+
constructor(message: string, opts: {
|
|
91
|
+
status: number;
|
|
92
|
+
code?: string;
|
|
93
|
+
retryAfterSeconds?: number;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { type QuickHelpAskParams, type QuickHelpAskResult, QuickHelpClient, type QuickHelpConfig, QuickHelpError, type QuickHelpPublicConfig, type QuickHelpTurnOutcome };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** Options for the QuickHelpClient. No auth — Quick Help is anonymous by design. */
|
|
2
|
+
interface QuickHelpConfig {
|
|
3
|
+
/** Your team's UUID. Required. */
|
|
4
|
+
teamId: string;
|
|
5
|
+
/** Override the base URL (defaults to https://www.cstar.help). */
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Sanitized config the server returns to anonymous visitors. */
|
|
9
|
+
interface QuickHelpPublicConfig {
|
|
10
|
+
headerTitle: string;
|
|
11
|
+
headerSubtitle: string;
|
|
12
|
+
placeholderText: string;
|
|
13
|
+
theme: 'light' | 'dark' | null;
|
|
14
|
+
primaryColor: string | null;
|
|
15
|
+
fontHeading: string | null;
|
|
16
|
+
fontBody: string | null;
|
|
17
|
+
borderRadius: string | null;
|
|
18
|
+
}
|
|
19
|
+
type QuickHelpTurnOutcome = 'answered' | 'escalated' | 'needs_clarification';
|
|
20
|
+
interface QuickHelpAskParams {
|
|
21
|
+
/** The visitor's question. Max 2000 chars. */
|
|
22
|
+
question: string;
|
|
23
|
+
/**
|
|
24
|
+
* The session id from a previous `ask()` call, if this is a follow-up
|
|
25
|
+
* turn. Omit or pass null on the first turn; the server mints a new
|
|
26
|
+
* id and returns it in `sessionId`.
|
|
27
|
+
*/
|
|
28
|
+
sessionId?: string | null;
|
|
29
|
+
}
|
|
30
|
+
interface QuickHelpAskResult {
|
|
31
|
+
sessionId: string;
|
|
32
|
+
answer: string;
|
|
33
|
+
turnOutcome: QuickHelpTurnOutcome;
|
|
34
|
+
turnNumber: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Quick Help — anonymous AI Q&A client.
|
|
39
|
+
*
|
|
40
|
+
* No API key. No login. The Quick Help widget is the exact same product
|
|
41
|
+
* cStar's marketing site dogfoods on its homepage, just driven
|
|
42
|
+
* programmatically. Sessions live entirely in memory on the caller side;
|
|
43
|
+
* the server hands back a `sessionId` on the first turn and expects you
|
|
44
|
+
* to pass it back on follow-ups so the orchestrator can see prior
|
|
45
|
+
* transcript.
|
|
46
|
+
*
|
|
47
|
+
* ```typescript
|
|
48
|
+
* import { QuickHelpClient } from '@cstar.help/js/quickhelp';
|
|
49
|
+
*
|
|
50
|
+
* const qh = new QuickHelpClient({ teamId: 'your-team-uuid' });
|
|
51
|
+
*
|
|
52
|
+
* const turn1 = await qh.ask({ question: 'How do I reset my password?' });
|
|
53
|
+
* console.log(turn1.answer);
|
|
54
|
+
*
|
|
55
|
+
* const turn2 = await qh.ask({
|
|
56
|
+
* question: 'Can you walk me through that?',
|
|
57
|
+
* sessionId: turn1.sessionId
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare class QuickHelpClient {
|
|
62
|
+
private readonly baseUrl;
|
|
63
|
+
private readonly teamId;
|
|
64
|
+
constructor(config: QuickHelpConfig);
|
|
65
|
+
/**
|
|
66
|
+
* Fetch the sanitized public config (title, subtitle, placeholder,
|
|
67
|
+
* theme overrides). Never includes agent id or escalation contact
|
|
68
|
+
* info — those leak only through the agent's own replies.
|
|
69
|
+
*
|
|
70
|
+
* Returns `null` if Quick Help is disabled on the team (404).
|
|
71
|
+
*/
|
|
72
|
+
config(): Promise<QuickHelpPublicConfig | null>;
|
|
73
|
+
/**
|
|
74
|
+
* Ask a question and get an answer. On first turn, omit `sessionId`;
|
|
75
|
+
* the server mints one and you MUST pass it back on follow-ups. On
|
|
76
|
+
* rate-limit (429) the returned Error has a `retryAfterSeconds`
|
|
77
|
+
* property set from the response body.
|
|
78
|
+
*/
|
|
79
|
+
ask(params: QuickHelpAskParams): Promise<QuickHelpAskResult>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Thrown by `QuickHelpClient.ask` when the request fails. Carries the
|
|
83
|
+
* HTTP status + error code + Retry-After (on 429) so callers can back
|
|
84
|
+
* off intelligently.
|
|
85
|
+
*/
|
|
86
|
+
declare class QuickHelpError extends Error {
|
|
87
|
+
readonly status: number;
|
|
88
|
+
readonly code?: string;
|
|
89
|
+
readonly retryAfterSeconds?: number;
|
|
90
|
+
constructor(message: string, opts: {
|
|
91
|
+
status: number;
|
|
92
|
+
code?: string;
|
|
93
|
+
retryAfterSeconds?: number;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { type QuickHelpAskParams, type QuickHelpAskResult, QuickHelpClient, type QuickHelpConfig, QuickHelpError, type QuickHelpPublicConfig, type QuickHelpTurnOutcome };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/quickhelp/index.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://www.cstar.help";
|
|
3
|
+
var QuickHelpClient = class {
|
|
4
|
+
constructor(config) {
|
|
5
|
+
if (!config?.teamId) {
|
|
6
|
+
throw new Error("QuickHelpClient: teamId is required");
|
|
7
|
+
}
|
|
8
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
9
|
+
this.teamId = config.teamId;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Fetch the sanitized public config (title, subtitle, placeholder,
|
|
13
|
+
* theme overrides). Never includes agent id or escalation contact
|
|
14
|
+
* info — those leak only through the agent's own replies.
|
|
15
|
+
*
|
|
16
|
+
* Returns `null` if Quick Help is disabled on the team (404).
|
|
17
|
+
*/
|
|
18
|
+
async config() {
|
|
19
|
+
const url = `${this.baseUrl}/api/quickhelp/config/${encodeURIComponent(this.teamId)}`;
|
|
20
|
+
const res = await globalThis.fetch(url, { method: "GET" });
|
|
21
|
+
if (res.status === 404) return null;
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
const body2 = await res.json().catch(() => null);
|
|
24
|
+
throw new Error(body2?.error?.message ?? `Quick Help config failed: ${res.status}`);
|
|
25
|
+
}
|
|
26
|
+
const body = await res.json();
|
|
27
|
+
return body.data;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Ask a question and get an answer. On first turn, omit `sessionId`;
|
|
31
|
+
* the server mints one and you MUST pass it back on follow-ups. On
|
|
32
|
+
* rate-limit (429) the returned Error has a `retryAfterSeconds`
|
|
33
|
+
* property set from the response body.
|
|
34
|
+
*/
|
|
35
|
+
async ask(params) {
|
|
36
|
+
const url = `${this.baseUrl}/api/quickhelp/ask`;
|
|
37
|
+
const res = await globalThis.fetch(url, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "Content-Type": "application/json" },
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
team_id: this.teamId,
|
|
42
|
+
session_id: params.sessionId ?? null,
|
|
43
|
+
question: params.question
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
const body = await res.json().catch(() => null);
|
|
47
|
+
if (!res.ok || !body?.success || !body.data) {
|
|
48
|
+
const message = body?.error?.message ?? `Quick Help ask failed: ${res.status}`;
|
|
49
|
+
const err = new QuickHelpError(message, {
|
|
50
|
+
status: res.status,
|
|
51
|
+
code: body?.error?.code,
|
|
52
|
+
retryAfterSeconds: parseRetryAfter(res.headers.get("retry-after"))
|
|
53
|
+
});
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
return body.data;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var QuickHelpError = class extends Error {
|
|
60
|
+
constructor(message, opts) {
|
|
61
|
+
super(message);
|
|
62
|
+
this.name = "QuickHelpError";
|
|
63
|
+
this.status = opts.status;
|
|
64
|
+
this.code = opts.code;
|
|
65
|
+
this.retryAfterSeconds = opts.retryAfterSeconds;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
function parseRetryAfter(header) {
|
|
69
|
+
if (!header) return void 0;
|
|
70
|
+
const n = Number(header);
|
|
71
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
QuickHelpClient,
|
|
75
|
+
QuickHelpError
|
|
76
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cstar.help/js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Official TypeScript SDK for the cStar customer support platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -76,6 +76,16 @@
|
|
|
76
76
|
"types": "./dist/proactive/index.d.cts",
|
|
77
77
|
"default": "./dist/proactive/index.cjs"
|
|
78
78
|
}
|
|
79
|
+
},
|
|
80
|
+
"./quickhelp": {
|
|
81
|
+
"import": {
|
|
82
|
+
"types": "./dist/quickhelp/index.d.ts",
|
|
83
|
+
"default": "./dist/quickhelp/index.js"
|
|
84
|
+
},
|
|
85
|
+
"require": {
|
|
86
|
+
"types": "./dist/quickhelp/index.d.cts",
|
|
87
|
+
"default": "./dist/quickhelp/index.cjs"
|
|
88
|
+
}
|
|
79
89
|
}
|
|
80
90
|
},
|
|
81
91
|
"files": [
|