@cstar.help/js 0.12.1 → 0.14.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.
@@ -1,12 +1,211 @@
1
+ // src/errors.ts
2
+ var ErrorType = {
3
+ AUTHENTICATION_ERROR: "authentication_error",
4
+ PERMISSION_ERROR: "permission_error",
5
+ INVALID_REQUEST_ERROR: "invalid_request_error",
6
+ NOT_FOUND_ERROR: "not_found_error",
7
+ CONFLICT_ERROR: "conflict_error",
8
+ RATE_LIMIT_ERROR: "rate_limit_error",
9
+ FEATURE_NOT_CONFIGURED: "feature_not_configured",
10
+ API_ERROR: "api_error",
11
+ SERVER_ERROR: "server_error"
12
+ };
13
+ var DEFAULT_DOC_URL = "https://cstar.help/developers/api/errors";
14
+ function normalizeArgs(messageOrStatus, maybeBody, defaults) {
15
+ if (typeof messageOrStatus === "string") {
16
+ return {
17
+ statusCode: defaults?.code === "rate_limit_error" ? 429 : 0,
18
+ body: {
19
+ type: defaults?.type ?? ErrorType.API_ERROR,
20
+ code: defaults?.code ?? "sdk_error",
21
+ message: messageOrStatus,
22
+ doc_url: defaults?.doc_url ?? DEFAULT_DOC_URL,
23
+ ...defaults?.feature ? { feature: defaults.feature } : {},
24
+ ...defaults?.enable_url ? { enable_url: defaults.enable_url } : {}
25
+ }
26
+ };
27
+ }
28
+ return {
29
+ statusCode: messageOrStatus,
30
+ body: maybeBody ?? {
31
+ type: defaults?.type ?? ErrorType.API_ERROR,
32
+ code: defaults?.code ?? "unknown_error",
33
+ message: defaults?.message ?? `Request failed with status ${messageOrStatus}`,
34
+ doc_url: defaults?.doc_url ?? DEFAULT_DOC_URL
35
+ }
36
+ };
37
+ }
38
+ var CStarError = class extends Error {
39
+ constructor(messageOrStatus, body) {
40
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body);
41
+ super(resolved.message);
42
+ this.name = "CStarError";
43
+ this.type = resolved.type;
44
+ this.code = resolved.code;
45
+ this.param = resolved.param;
46
+ this.docUrl = resolved.doc_url;
47
+ this.requestId = resolved.request_id;
48
+ this.statusCode = statusCode;
49
+ }
50
+ };
51
+ var CStarAuthenticationError = class extends CStarError {
52
+ constructor(messageOrStatus, body) {
53
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
54
+ type: ErrorType.AUTHENTICATION_ERROR,
55
+ code: "authentication_failed",
56
+ doc_url: `${DEFAULT_DOC_URL}#authentication-error`
57
+ });
58
+ super(statusCode, resolved);
59
+ this.name = "CStarAuthenticationError";
60
+ }
61
+ };
62
+ var CStarRateLimitError = class extends CStarError {
63
+ constructor(messageOrStatus, body, retryAfter) {
64
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
65
+ type: ErrorType.RATE_LIMIT_ERROR,
66
+ code: "rate_limit_exceeded",
67
+ doc_url: `${DEFAULT_DOC_URL}#rate-limit-error`
68
+ });
69
+ super(statusCode === 0 ? 429 : statusCode, resolved);
70
+ this.name = "CStarRateLimitError";
71
+ this.retryAfter = retryAfter ?? 60;
72
+ }
73
+ };
74
+ var CStarNotFoundError = class extends CStarError {
75
+ constructor(messageOrStatus, body) {
76
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
77
+ type: ErrorType.NOT_FOUND_ERROR,
78
+ code: "resource_not_found",
79
+ doc_url: `${DEFAULT_DOC_URL}#not-found-error`
80
+ });
81
+ super(statusCode, resolved);
82
+ this.name = "CStarNotFoundError";
83
+ }
84
+ };
85
+ var CStarValidationError = class extends CStarError {
86
+ constructor(messageOrStatus, body) {
87
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
88
+ type: ErrorType.INVALID_REQUEST_ERROR,
89
+ code: "validation_error",
90
+ doc_url: `${DEFAULT_DOC_URL}#validation-error`
91
+ });
92
+ super(statusCode, resolved);
93
+ this.name = "CStarValidationError";
94
+ }
95
+ };
96
+ var CStarPermissionError = class extends CStarError {
97
+ constructor(messageOrStatus, body) {
98
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
99
+ type: ErrorType.PERMISSION_ERROR,
100
+ code: "permission_denied",
101
+ doc_url: `${DEFAULT_DOC_URL}#permission-error`
102
+ });
103
+ super(statusCode, resolved);
104
+ this.name = "CStarPermissionError";
105
+ }
106
+ };
107
+ var CStarConflictError = class extends CStarError {
108
+ constructor(messageOrStatus, body) {
109
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
110
+ type: ErrorType.CONFLICT_ERROR,
111
+ code: "conflict",
112
+ doc_url: `${DEFAULT_DOC_URL}#conflict-error`
113
+ });
114
+ super(statusCode, resolved);
115
+ this.name = "CStarConflictError";
116
+ }
117
+ };
118
+ var CStarServerError = class extends CStarError {
119
+ constructor(messageOrStatus, body) {
120
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
121
+ type: ErrorType.SERVER_ERROR,
122
+ code: "server_error",
123
+ doc_url: `${DEFAULT_DOC_URL}#server-error`
124
+ });
125
+ super(statusCode, resolved);
126
+ this.name = "CStarServerError";
127
+ }
128
+ };
129
+ var CStarFeatureDisabledError = class extends CStarError {
130
+ constructor(messageOrStatus, body) {
131
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
132
+ type: ErrorType.FEATURE_NOT_CONFIGURED,
133
+ code: "feature_not_configured",
134
+ doc_url: `${DEFAULT_DOC_URL}#feature-not-configured`
135
+ });
136
+ super(statusCode, resolved);
137
+ this.name = "CStarFeatureDisabledError";
138
+ this.feature = resolved.feature ?? "unknown";
139
+ this.enableUrl = resolved.enable_url ?? "";
140
+ }
141
+ };
142
+ var TYPE_TO_CLASS = {
143
+ [ErrorType.AUTHENTICATION_ERROR]: CStarAuthenticationError,
144
+ [ErrorType.PERMISSION_ERROR]: CStarPermissionError,
145
+ [ErrorType.RATE_LIMIT_ERROR]: CStarRateLimitError,
146
+ [ErrorType.NOT_FOUND_ERROR]: CStarNotFoundError,
147
+ [ErrorType.INVALID_REQUEST_ERROR]: CStarValidationError,
148
+ [ErrorType.CONFLICT_ERROR]: CStarConflictError,
149
+ [ErrorType.SERVER_ERROR]: CStarServerError,
150
+ [ErrorType.FEATURE_NOT_CONFIGURED]: CStarFeatureDisabledError
151
+ };
152
+
1
153
  // src/quickhelp/index.ts
2
154
  var DEFAULT_BASE_URL = "https://www.cstar.help";
3
155
  var QuickHelpClient = class {
4
156
  constructor(config) {
5
- if (!config?.teamId) {
6
- throw new Error("QuickHelpClient: teamId is required");
157
+ this.resolveTeamIdPromise = null;
158
+ if (!config?.teamId && !config?.teamSlug) {
159
+ throw new Error(
160
+ 'QuickHelpClient: either `teamId` or `teamSlug` is required. Pass `teamSlug` (e.g. "acme") for the easiest setup.'
161
+ );
7
162
  }
8
163
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
9
- this.teamId = config.teamId;
164
+ this.teamId = config.teamId ?? null;
165
+ this.teamSlug = config.teamSlug ?? null;
166
+ this.publishableKey = config.publishableKey ?? null;
167
+ this.mode = this.publishableKey?.startsWith("pk_test_") ? "test" : "live";
168
+ if (this.publishableKey && !/^pk_(test|live)_/.test(this.publishableKey)) {
169
+ throw new Error(
170
+ "[cStar] QuickHelpClient: publishableKey must start with `pk_test_` or `pk_live_`. Secret keys (`sk_*`) cannot be used in browser/anonymous contexts."
171
+ );
172
+ }
173
+ }
174
+ authHeaders() {
175
+ return this.publishableKey ? { Authorization: `Bearer ${this.publishableKey}` } : {};
176
+ }
177
+ /**
178
+ * Resolve `teamSlug` → `teamId` lazily on the first call that needs it.
179
+ * Cached for the life of the client. NEW-v2-05.
180
+ */
181
+ async resolveTeamId() {
182
+ if (this.teamId) return this.teamId;
183
+ if (!this.teamSlug) throw new Error("QuickHelpClient: no teamId or teamSlug set");
184
+ if (this.resolveTeamIdPromise) return this.resolveTeamIdPromise;
185
+ this.resolveTeamIdPromise = (async () => {
186
+ const url = `${this.baseUrl}/api/widget/team-by-slug?teamSlug=${encodeURIComponent(this.teamSlug)}`;
187
+ const res = await globalThis.fetch(url, { method: "GET", headers: this.authHeaders() });
188
+ if (!res.ok) {
189
+ throw new Error(
190
+ `QuickHelpClient: could not resolve teamSlug "${this.teamSlug}" (HTTP ${res.status}).`
191
+ );
192
+ }
193
+ const body = await res.json();
194
+ const id = body?.data?.teamId;
195
+ if (!id) {
196
+ throw new Error(
197
+ `QuickHelpClient: lookup for teamSlug "${this.teamSlug}" returned no teamId.`
198
+ );
199
+ }
200
+ this.teamId = id;
201
+ return id;
202
+ })();
203
+ try {
204
+ return await this.resolveTeamIdPromise;
205
+ } catch (err) {
206
+ this.resolveTeamIdPromise = null;
207
+ throw err;
208
+ }
10
209
  }
11
210
  /**
12
211
  * Fetch the sanitized public config (title, subtitle, placeholder,
@@ -16,8 +215,9 @@ var QuickHelpClient = class {
16
215
  * Returns `null` if Quick Help is disabled on the team (404).
17
216
  */
18
217
  async config() {
19
- const url = `${this.baseUrl}/api/quickhelp/config/${encodeURIComponent(this.teamId)}`;
20
- const res = await globalThis.fetch(url, { method: "GET" });
218
+ const teamId = await this.resolveTeamId();
219
+ const url = `${this.baseUrl}/api/quickhelp/config/${encodeURIComponent(teamId)}`;
220
+ const res = await globalThis.fetch(url, { method: "GET", headers: this.authHeaders() });
21
221
  if (res.status === 404) return null;
22
222
  if (!res.ok) {
23
223
  const body2 = await res.json().catch(() => null);
@@ -33,18 +233,29 @@ var QuickHelpClient = class {
33
233
  * property set from the response body.
34
234
  */
35
235
  async ask(params) {
236
+ const teamId = await this.resolveTeamId();
36
237
  const url = `${this.baseUrl}/api/quickhelp/ask`;
37
238
  const res = await globalThis.fetch(url, {
38
239
  method: "POST",
39
- headers: { "Content-Type": "application/json" },
240
+ headers: { "Content-Type": "application/json", ...this.authHeaders() },
40
241
  body: JSON.stringify({
41
- team_id: this.teamId,
242
+ team_id: teamId,
42
243
  session_id: params.sessionId ?? null,
43
244
  question: params.question
44
245
  })
45
246
  });
46
247
  const body = await res.json().catch(() => null);
47
248
  if (!res.ok || !body?.success || !body.data) {
249
+ if (res.status === 503 || body?.error?.code === "feature_not_configured") {
250
+ throw new CStarFeatureDisabledError(res.status, {
251
+ type: "feature_not_configured",
252
+ code: "feature_not_configured",
253
+ message: body?.error?.message ?? "Quick Help is not available on this team. Enable an AI Agent (Settings \u2192 AI Agents) and configure Quick Help (Settings \u2192 Quick Help) before calling .ask().",
254
+ doc_url: "https://cstar.help/developers/api/errors#feature-not-configured",
255
+ feature: "quickHelp",
256
+ enable_url: `${this.baseUrl}/team/${encodeURIComponent(teamId)}/settings?section=quickHelp`
257
+ });
258
+ }
48
259
  const message = body?.error?.message ?? `Quick Help ask failed: ${res.status}`;
49
260
  const err = new QuickHelpError(message, {
50
261
  status: res.status,
@@ -21,9 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var webhook_exports = {};
22
22
  __export(webhook_exports, {
23
23
  WebhookSignatureVerificationError: () => WebhookSignatureVerificationError,
24
- constructEvent: () => constructEvent,
25
24
  constructEventAsync: () => constructEventAsync,
26
- verifySignature: () => verifySignature,
27
25
  verifySignatureAsync: () => verifySignatureAsync
28
26
  });
29
27
  module.exports = __toCommonJS(webhook_exports);
@@ -33,14 +31,6 @@ var WebhookSignatureVerificationError = class extends Error {
33
31
  this.name = "WebhookSignatureVerificationError";
34
32
  }
35
33
  };
36
- function constructEvent(rawBody, signature, secret) {
37
- if (!verifySignature(rawBody, signature, secret)) {
38
- throw new WebhookSignatureVerificationError(
39
- "Webhook signature verification failed. Ensure you are using the raw request body and the correct endpoint secret."
40
- );
41
- }
42
- return JSON.parse(rawBody);
43
- }
44
34
  async function constructEventAsync(rawBody, signature, secret) {
45
35
  const valid = await verifySignatureAsync(rawBody, signature, secret);
46
36
  if (!valid) {
@@ -50,12 +40,6 @@ async function constructEventAsync(rawBody, signature, secret) {
50
40
  }
51
41
  return JSON.parse(rawBody);
52
42
  }
53
- function verifySignature(rawBody, signature, secret) {
54
- const crypto2 = require("crypto");
55
- const expected = "sha256=" + crypto2.createHmac("sha256", secret).update(rawBody).digest("hex");
56
- if (signature.length !== expected.length) return false;
57
- return crypto2.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
58
- }
59
43
  async function verifySignatureAsync(rawBody, signature, secret) {
60
44
  const encoder = new TextEncoder();
61
45
  const key = await crypto.subtle.importKey(
@@ -77,8 +61,6 @@ async function verifySignatureAsync(rawBody, signature, secret) {
77
61
  // Annotate the CommonJS export names for ESM import in node:
78
62
  0 && (module.exports = {
79
63
  WebhookSignatureVerificationError,
80
- constructEvent,
81
64
  constructEventAsync,
82
- verifySignature,
83
65
  verifySignatureAsync
84
66
  });