@cstar.help/js 0.17.4 → 0.18.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/README.md CHANGED
@@ -26,14 +26,14 @@ cStar has one package with separate entry points for different workflows. Start
26
26
  | `@cstar.help/js/library` | Public knowledge base | Browser or server | No | None |
27
27
  | `@cstar.help/js/community` | Public community forum | Browser or server | No | None |
28
28
  | `@cstar.help/js/webhook` | Webhook signature verification | Server only | No | Signing secret |
29
- | `@cstar.help/js/auth` | Authentication utilities | Server only | Yes | Secret (`sk_*`) |
29
+ | `@cstar.help/js/auth` | Customer signup and login | Browser | No | None (`teamSlug`) |
30
30
 
31
31
  ### The Security Model
32
32
 
33
33
  The server API and customer chat authenticate differently:
34
34
 
35
35
  - **Secret API keys** (`sk_live_*`, `sk_test_*`) authorize team API operations within the key's permissions. Keep them on your server.
36
- - **Publishable API keys** (`pk_live_*`, `pk_test_*`) provide limited read-only access to API endpoints that accept them. They do not identify a customer or authorize customer chat.
36
+ - **Publishable API keys** (`pk_live_*`, `pk_test_*`) are for the public SDKs (`LibraryClient`, `CommunityClient`) and `GET /auth/whoami`. Team API endpoints refuse them with 403: `publishable_key_forbidden` on reads and `publishable_key_write` on writes, so `CStarClient` needs a secret key. They do not identify a customer or authorize customer chat.
37
37
  - **ChatClient** takes a `teamSlug`, then authenticates a customer through HMAC identity verification or a customer session. It has no API-key option. Keep HMAC signing secrets on your server too.
38
38
 
39
39
  > Building a support dashboard? Use `CStarClient` on your backend. Adding a conversation to your website? Use `ChatClient` and one of the customer authentication options below.
@@ -71,19 +71,36 @@ for await (const ticket of cstar.tickets.listAutoPaginating({ status: 'open' }))
71
71
 
72
72
  Created, retrieved and updated resources are returned directly. List methods return `{ data, pagination, hasMore }`. Search returns `{ results, total, query }`. Read `cstar.lastMeta.requestId` immediately after a call when you need its request ID.
73
73
 
74
+ ### Private notes and message history
75
+
76
+ Run message management on your server with a secret key. Internal notes stay inside the support team.
77
+
78
+ ```typescript
79
+ const note = await cstar.tickets.messages.create('tkt_123', {
80
+ content: 'The customer has already tried resetting their password.',
81
+ messageType: 'internal'
82
+ });
83
+ console.log(note.isInternal);
84
+
85
+ const page = await cstar.tickets.messages.list('tkt_123', { page: 2, pageSize: 25 });
86
+ console.log(page.data, page.pagination.hasMore);
87
+ ```
88
+
89
+ The `isInternal` boolean is also accepted when creating a note. If you supply both privacy options, they must agree. Message history is paginated, so request the next page while `hasMore` is true.
90
+
74
91
  ### Full Resource Reference
75
92
 
76
- **Core Resources** — Tickets, customers, and knowledge base.
93
+ **Core Resources**: Tickets, customers, and knowledge base.
77
94
 
78
95
  | Resource | Methods |
79
96
  | ------------------------ | ----------------------------------------------------------------------------------------------- |
80
97
  | `cstar.tickets` | `.list()` `.get(id)` `.create(params)` `.update(id, params)` `.del(id)` `.listAutoPaginating()` |
81
- | `cstar.tickets.messages` | `.list(ticketId)` `.create(ticketId, params)` |
98
+ | `cstar.tickets.messages` | `.list(ticketId, params?)` `.create(ticketId, params)` |
82
99
  | `cstar.customers` | `.list()` `.get(id)` `.create(params)` `.update(id, params)` `.del(id)` `.listAutoPaginating()` |
83
100
  | `cstar.articles` | `.list()` `.get(id)` `.create(params)` `.update(id, params)` `.del(id)` `.listAutoPaginating()` |
84
101
  | `cstar.categories` | `.list()` `.get(id)` `.create(params)` `.update(id, params)` `.del(id)` |
85
102
 
86
- **Team & Configuration** — Members, settings, and customization.
103
+ **Team & Configuration**: Members, settings, and customization.
87
104
 
88
105
  | Resource | Methods |
89
106
  | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
@@ -93,9 +110,9 @@ Created, retrieved and updated resources are returned directly. List methods ret
93
110
  | `cstar.settings.statuses` | `.list()` `.create(params)` `.update(key, params)` `.del(key)` `.reorder(keys)` |
94
111
  | `cstar.settings.quickReplies` | `.list()` `.create(params)` `.update(id, params)` `.del(id)` `.reorder(ids)` |
95
112
  | `cstar.customFields` | `.list()` `.get(id)` `.create(params)` `.update(id, params)` `.del(id)` `.reorder(ids)` |
96
- | `cstar.notifications` | `.list()` `.unreadCount()` `.markRead(id)` `.markAllRead()` |
113
+ | `cstar.notifications` | `.list({ userId })` `.unreadCount(userId)` `.markRead(id, userId)` `.markAllRead(userId)` |
97
114
 
98
- **Operations** — Analytics, bulk actions, and data management.
115
+ **Operations**: Analytics, bulk actions, and data management.
99
116
 
100
117
  | Resource | Methods |
101
118
  | ---------------------- | --------------------------------------------------------------------------------------- |
@@ -110,7 +127,7 @@ Created, retrieved and updated resources are returned directly. List methods ret
110
127
  | `cstar.auditLog` | `.list(params?)` |
111
128
  | `cstar.customerGroups` | `.list()` `.get(id)` `.create(params)` `.update(id, params)` `.del(id)` |
112
129
 
113
- **Gamification** — The full game system.
130
+ **Gamification**: The full game system.
114
131
 
115
132
  | Resource | Methods |
116
133
  | ------------------------- | --------------------------------------------------------------------------------------------------- |
@@ -127,9 +144,9 @@ Created, retrieved and updated resources are returned directly. List methods ret
127
144
 
128
145
  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`.
129
146
 
130
- 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.
147
+ 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, resetsAt }` (each quest pays its own `xpReward` and `goldReward`; there is no all-quests bonus). Single resources return bare objects.
131
148
 
132
- **Advanced** — AI, automations, views, billing, webhooks.
149
+ **Advanced**: AI, automations, views, billing, webhooks.
133
150
 
134
151
  | Resource | Methods |
135
152
  | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -182,9 +199,9 @@ const csv = await cstar.export.create({ resources: ['tickets'], format: 'csv' })
182
199
 
183
200
  // Saved views
184
201
  const view = await cstar.views.create({
185
- name: 'Urgent Unresolved',
202
+ name: 'Critical Unresolved',
186
203
  entityType: 'tickets',
187
- filters: { priority: 'urgent', status: 'open' }
204
+ filters: { priority: 'critical', status: 'open' }
188
205
  });
189
206
  const { data: results } = await cstar.views.execute(view.id);
190
207
 
@@ -287,7 +304,9 @@ function closeChat() {
287
304
 
288
305
  HTTP and network failures use typed subclasses of `ChatClientError`. A server response may provide a `requestId` through the `X-Request-Id` header; it is `null` when unavailable. Other failures, such as malformed response JSON, can still throw ordinary errors.
289
306
 
290
- A lost response may follow a successful send. Keep the unsent text available and check the conversation before offering another send. Do not automatically resend a message because a network or server error occurred.
307
+ Customer message sends generate a new retry key for each intentional reply and retry network or server failures at most twice. Each retry uses the same key and returns the original saved reply. A terminal `ChatClientError` retains that key in `error.idempotencyKey`. Reuse it with `chat.messages.send(ticketId, content, { idempotencyKey })` for a manual retry of the same reply. Keep the draft and key together; use a new key when the message or ticket changes. Receipts last 24 hours and stay scoped to the customer, team and live/test mode.
308
+
309
+ Pass `{ signal }` to cancel a send or its retry delay. For a cancellable UI, generate and retain your own `idempotencyKey` before sending. A cancelled request may already have been saved. Conversation history includes `client_request_key` so your UI can reconcile a pending bubble with its confirmed reply. Separate sends with identical text remain separate messages. These message retries do not apply to ticket creation or other customer writes.
291
310
 
292
311
  ```typescript
293
312
  import {
@@ -321,7 +340,7 @@ try {
321
340
 
322
341
  ## Client-Side: Public Knowledge Base
323
342
 
324
- Search and browse your public help center — **no authentication required**.
343
+ Search and browse your public help center. **No authentication required**.
325
344
 
326
345
  ```typescript
327
346
  import { LibraryClient } from '@cstar.help/js/library';
@@ -337,7 +356,7 @@ const article = await library.article('how-to-reset-password');
337
356
 
338
357
  ## Client-Side: Public Community
339
358
 
340
- Browse your public community forum — **no authentication required**.
359
+ Browse your public community forum. **No authentication required**.
341
360
 
342
361
  ```typescript
343
362
  import { CommunityClient } from '@cstar.help/js/community';
@@ -356,17 +375,17 @@ const { post, comments } = await community.post('dark-mode-support');
356
375
  Verify incoming webhook signatures on your server.
357
376
 
358
377
  ```typescript
359
- import { constructEvent } from '@cstar.help/js/webhook';
378
+ import { constructEvent } from '@cstar.help/js/webhook/node';
360
379
 
361
- // In your webhook handler
380
+ // In your webhook handler (Node). It throws if the signature is invalid.
362
381
  const event = constructEvent(rawBody, signature, 'whsec_your_secret');
363
382
 
364
383
  switch (event.type) {
365
384
  case 'ticket.created':
366
- console.log('New ticket:', event.data.title);
385
+ console.log('New ticket:', event.data.ticket.title);
367
386
  break;
368
387
  case 'customer.created':
369
- console.log('New customer:', event.data.email);
388
+ console.log('New customer:', event.data.customer.email);
370
389
  break;
371
390
  }
372
391
  ```
@@ -384,12 +403,12 @@ const event = await constructEventAsync(rawBody, signature, secret);
384
403
 
385
404
  ```typescript
386
405
  const cstar = new CStarClient({
387
- apiKey: 'sk_live_...', // Required — must be a secret key (sk_*)
388
- teamId: 'tm_...', // Required — your team ID
389
- baseUrl: 'https://...', // Optional — defaults to https://www.cstar.help
390
- version: '2026-03-01', // Optional — API version header
391
- maxRetries: 3, // Optional — retry attempts for retry-safe failures and 429
392
- timeout: 30000 // Optional — request timeout in ms
406
+ apiKey: 'sk_live_...', // Required: must be a secret key (sk_*)
407
+ teamId: 'tm_...', // Required: your team ID
408
+ baseUrl: 'https://...', // Optional: defaults to https://www.cstar.help
409
+ version: '2026-03-01', // Optional: API version header
410
+ maxRetries: 3, // Optional: retry attempts for retry-safe failures and 429
411
+ timeout: 30000 // Optional: request timeout in ms
393
412
  });
394
413
  ```
395
414
 
@@ -417,7 +436,7 @@ const cstar = new CStarClient({
417
436
  });
418
437
 
419
438
  console.log(cstar.isTestMode); // true
420
- // All operations use test data — no real customers affected
439
+ // All operations use test data, so no real customers are affected
421
440
  ```
422
441
 
423
442
  ## Error Handling
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/auth/index.ts
@@ -308,45 +318,196 @@ function parseError(statusCode, body, retryAfter) {
308
318
 
309
319
  // src/auth/index.ts
310
320
  var DEFAULT_BASE_URL = "https://www.cstar.help";
321
+ var PRODUCTION_ORIGINS = /* @__PURE__ */ new Set(["https://www.cstar.help", "https://cstar.help"]);
322
+ var PRODUCTION_SUPABASE_URL = "https://kmorvgcubqthkfrasbny.supabase.co";
323
+ var PRODUCTION_SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imttb3J2Z2N1YnF0aGtmcmFzYm55Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjUzMzczNjIsImV4cCI6MjA4MDkxMzM2Mn0.e0RJbMpZr-8FbJmAGhdw86Ym9M4MCfvX9Qm6Va3d_N8";
324
+ var PKCE_STORAGE_KEY = "cstar_widget_pkce";
325
+ var MIN_PASSWORD_LENGTH = 8;
326
+ var CONFIRMATION_TYPES = /* @__PURE__ */ new Set(["magiclink", "signup", "recovery"]);
311
327
  var AuthClient = class {
312
328
  constructor(config) {
313
329
  this.session = null;
330
+ this.pkceClient = null;
314
331
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
315
332
  this.teamSlug = config.teamSlug;
333
+ const isProduction = PRODUCTION_ORIGINS.has(this.baseUrl);
334
+ this.supabaseUrl = config.supabaseUrl ?? (isProduction ? PRODUCTION_SUPABASE_URL : null);
335
+ this.supabaseKey = config.supabaseKey ?? (isProduction ? PRODUCTION_SUPABASE_KEY : null);
316
336
  }
317
337
  /**
318
- * Create a new customer account.
338
+ * Create a password-less customer account and email a sign-in link.
319
339
  *
320
- * If the team requires email confirmation, returns a `SignupConfirmationResult`
321
- * instead of tokens. Check for `requiresConfirmation` in the response.
340
+ * The response is the same whether or not the email already has an
341
+ * account. Nothing is signed in yet: the customer clicks the link, and the
342
+ * page it returns to calls `completeSignIn()`. Signup takes no password,
343
+ * because one chosen before the link proves the address could belong to
344
+ * someone else. Once signed in, the customer can call `setPassword()` to
345
+ * enable `login()`.
322
346
  *
323
- * @throws Error on validation failure, duplicate email, or server error
347
+ * Runs in a browser: the PKCE verifier is stored in this page's
348
+ * localStorage so `completeSignIn()` can finish the exchange.
349
+ *
350
+ * @throws CStarRateLimitError when too many signups were attempted
324
351
  */
325
352
  async signup(params) {
326
- const response = await this.fetch(
327
- "/api/widget/auth/signup",
328
- {
329
- method: "POST",
330
- body: {
331
- teamId: this.teamSlug,
332
- email: params.email,
333
- password: params.password,
334
- name: params.name
335
- }
353
+ const returnUrl = params.returnUrl ?? (typeof window !== "undefined" && window.location ? window.location.href : void 0);
354
+ const prepared = await this.fetch("/api/widget/auth/signup", {
355
+ method: "POST",
356
+ body: {
357
+ teamId: this.teamSlug,
358
+ email: params.email,
359
+ name: params.name,
360
+ returnUrl
336
361
  }
337
- );
338
- if ("accessToken" in response && response.accessToken && response.customer) {
362
+ });
363
+ if (!prepared?.emailRedirectTo) {
364
+ throw parsePublicError(500, null, "Sign-up prepare returned no redirect URL.");
365
+ }
366
+ const client = await this.getPkceClient();
367
+ const { error } = await client.auth.signInWithOtp({
368
+ email: params.email,
369
+ options: { emailRedirectTo: prepared.emailRedirectTo, shouldCreateUser: false }
370
+ });
371
+ if (error) {
372
+ if (error.status === 429) {
373
+ throw parsePublicError(
374
+ 429,
375
+ { error: "Too many sign-up requests. Please wait a few minutes and try again." },
376
+ "Too many sign-up requests"
377
+ );
378
+ }
379
+ throw parsePublicError(
380
+ 502,
381
+ {
382
+ error: "Couldn't send a confirmation email. Try again, or sign in if you already have an account."
383
+ },
384
+ "Confirmation email failed"
385
+ );
386
+ }
387
+ return {
388
+ success: true,
389
+ message: prepared.message ?? "Check your inbox to finish signing up.",
390
+ requiresConfirmation: true
391
+ };
392
+ }
393
+ /**
394
+ * Finish sign-in on the page the confirmation link returns to.
395
+ *
396
+ * Reads `?code=` and `?type=` from the URL, removes them from the address
397
+ * bar, exchanges the code, and stores the session. Returns null when the URL
398
+ * holds no cStar confirmation code or the exchange fails.
399
+ *
400
+ * @param url Defaults to `window.location.href`.
401
+ */
402
+ async completeSignIn(url) {
403
+ const href = url ?? (typeof window !== "undefined" && window.location ? window.location.href : null);
404
+ if (!href) return null;
405
+ const parsed = new URL(href);
406
+ const code = parsed.searchParams.get("code");
407
+ const type = parsed.searchParams.get("type");
408
+ if (!code || !type || !CONFIRMATION_TYPES.has(type)) return null;
409
+ if (url === void 0 && typeof window !== "undefined" && window.history?.replaceState) {
410
+ parsed.searchParams.delete("code");
411
+ parsed.searchParams.delete("type");
412
+ try {
413
+ window.history.replaceState(null, "", parsed.pathname + parsed.search + parsed.hash);
414
+ } catch {
415
+ }
416
+ }
417
+ const client = await this.getPkceClient();
418
+ const exchanged = await client.auth.exchangeCodeForSession(code);
419
+ const supabaseSession = exchanged?.data?.session;
420
+ if (exchanged?.error || !supabaseSession) return null;
421
+ const result = await this.fetch("/api/widget/auth/magic-link/exchange", {
422
+ method: "POST",
423
+ body: {
424
+ teamId: this.teamSlug,
425
+ accessToken: supabaseSession.access_token,
426
+ refreshToken: supabaseSession.refresh_token
427
+ }
428
+ });
429
+ if (!result.customer || !result.accessToken || !result.refreshToken) return null;
430
+ this.session = {
431
+ customer: result.customer,
432
+ accessToken: result.accessToken,
433
+ refreshToken: result.refreshToken
434
+ };
435
+ return this.session;
436
+ }
437
+ /**
438
+ * Choose a password for the signed-in customer, so later visits can use
439
+ * `login()` instead of an emailed link.
440
+ *
441
+ * Needs a session from `completeSignIn()` or `login()`. This is the same
442
+ * Supabase Auth password update the hosted widget makes after a first
443
+ * email sign-in, and it clears the widget's "choose a password" prompt.
444
+ * The customer stays signed in.
445
+ *
446
+ * @throws CStarValidationError when the password is under 8 characters or is rejected
447
+ * @throws CStarAuthenticationError when there is no session or it has expired
448
+ */
449
+ async setPassword(password) {
450
+ if (!password || password.length < MIN_PASSWORD_LENGTH) {
451
+ throw parsePublicError(
452
+ 400,
453
+ { error: `Password must be at least ${MIN_PASSWORD_LENGTH} characters` },
454
+ "Invalid password"
455
+ );
456
+ }
457
+ const current = this.session;
458
+ if (!current) {
459
+ throw parsePublicError(
460
+ 401,
461
+ { error: "Sign in before choosing a password." },
462
+ "Not signed in"
463
+ );
464
+ }
465
+ const client = await this.getPkceClient();
466
+ const restored = await client.auth.setSession({
467
+ access_token: current.accessToken,
468
+ refresh_token: current.refreshToken
469
+ });
470
+ const restoredSession = restored?.data?.session;
471
+ if (restored?.error || !restoredSession) {
472
+ throw parsePublicError(
473
+ 401,
474
+ { error: "Your session has expired. Sign in again to choose a password." },
475
+ "Session expired"
476
+ );
477
+ }
478
+ if (this.session === current) {
339
479
  this.session = {
340
- customer: response.customer,
341
- accessToken: response.accessToken,
342
- refreshToken: response.refreshToken
480
+ ...current,
481
+ accessToken: restoredSession.access_token,
482
+ refreshToken: restoredSession.refresh_token
343
483
  };
344
484
  }
345
- return response;
485
+ const { error } = await client.auth.updateUser({
486
+ password,
487
+ data: { widget_password_pending: false }
488
+ });
489
+ if (error) {
490
+ if (error.status === 429) {
491
+ throw parsePublicError(
492
+ 429,
493
+ { error: "Too many password changes. Please wait a few minutes and try again." },
494
+ "Too many password changes"
495
+ );
496
+ }
497
+ const rejected = error.status === 400 || error.status === 422;
498
+ throw parsePublicError(
499
+ rejected ? 400 : 502,
500
+ { error: "That password could not be saved. Please try another one." },
501
+ "Password not saved"
502
+ );
503
+ }
346
504
  }
347
505
  /**
348
506
  * Log in an existing customer with email and password.
349
507
  *
508
+ * Signup is password-less, so this works only after the customer has
509
+ * chosen a password with `setPassword()` or in the hosted widget.
510
+ *
350
511
  * On success, stores the session internally so subsequent
351
512
  * ChatClient requests can use `auth.getSession()` for tokens.
352
513
  *
@@ -370,9 +531,18 @@ var AuthClient = class {
370
531
  }
371
532
  return response;
372
533
  }
373
- /** Clear the stored session. */
534
+ /**
535
+ * Clear the session: the in-memory one and, when a sign-in link or
536
+ * setPassword() restored it through Supabase Auth, the copy that client
537
+ * keeps in browser storage, so the next person on the device is signed out.
538
+ */
374
539
  logout() {
375
540
  this.session = null;
541
+ const pkceClient = this.pkceClient;
542
+ if (pkceClient) {
543
+ void pkceClient.then((client) => client.auth.signOut({ scope: "local" })).catch(() => {
544
+ });
545
+ }
376
546
  }
377
547
  /** Get the current session, or null if not authenticated. */
378
548
  getSession() {
@@ -390,6 +560,35 @@ var AuthClient = class {
390
560
  get accessToken() {
391
561
  return this.session?.accessToken ?? null;
392
562
  }
563
+ getPkceClient() {
564
+ const { supabaseUrl, supabaseKey } = this;
565
+ if (!supabaseUrl || !supabaseKey) {
566
+ return Promise.reject(
567
+ parsePublicError(
568
+ 400,
569
+ {
570
+ error: "AuthClient needs supabaseUrl and supabaseKey when baseUrl is not https://www.cstar.help."
571
+ },
572
+ "Missing Supabase config"
573
+ )
574
+ );
575
+ }
576
+ this.pkceClient ?? (this.pkceClient = import(
577
+ /* webpackIgnore: true */
578
+ "@supabase/supabase-js"
579
+ ).then(
580
+ ({ createClient }) => createClient(supabaseUrl, supabaseKey, {
581
+ auth: {
582
+ flowType: "pkce",
583
+ persistSession: true,
584
+ autoRefreshToken: false,
585
+ storageKey: PKCE_STORAGE_KEY,
586
+ detectSessionInUrl: false
587
+ }
588
+ })
589
+ ));
590
+ return this.pkceClient;
591
+ }
393
592
  async fetch(path, options) {
394
593
  const url = `${this.baseUrl}${path}`;
395
594
  const response = await globalThis.fetch(url, {
@@ -1,4 +1,4 @@
1
- export { A as ApiErrorBody, C as CStarAuthenticationError, a as CStarConflictError, b as CStarError, c as CStarFeatureDisabledError, d as CStarNotFoundError, e as CStarPermissionError, f as CStarRateLimitError, g as CStarRealtimeUnavailableError, h as CStarServerError, i as CStarValidationError, E as ErrorType, p as parseError, j as parsePublicError } from '../errors-b5A4wfAg.cjs';
1
+ export { A as ApiErrorBody, C as CStarAuthenticationError, a as CStarConflictError, b as CStarError, c as CStarFeatureDisabledError, d as CStarNotFoundError, e as CStarPermissionError, f as CStarRateLimitError, g as CStarRealtimeUnavailableError, h as CStarServerError, i as CStarValidationError, E as ErrorType, p as parseError, j as parsePublicError } from '../errors-bW8CwTn4.cjs';
2
2
 
3
3
  /** Configuration for the AuthClient. */
4
4
  interface AuthConfig {
@@ -6,6 +6,14 @@ interface AuthConfig {
6
6
  teamSlug: string;
7
7
  /** Override the base URL (defaults to https://www.cstar.help). */
8
8
  baseUrl?: string;
9
+ /**
10
+ * Supabase project URL used to send the signup confirmation email.
11
+ * Defaults to cStar's production project. Required when `baseUrl` points
12
+ * anywhere other than production, so the email comes from the same project.
13
+ */
14
+ supabaseUrl?: string;
15
+ /** Supabase publishable (anon) key that pairs with `supabaseUrl`. */
16
+ supabaseKey?: string;
9
17
  }
10
18
  /** A customer identity returned from auth endpoints. */
11
19
  interface AuthCustomer {
@@ -17,26 +25,33 @@ interface AuthCustomer {
17
25
  interface SignupParams {
18
26
  /** Customer email address. */
19
27
  email: string;
20
- /** Password (minimum 6 characters). */
21
- password: string;
22
28
  /** Customer display name. */
23
29
  name?: string;
30
+ /**
31
+ * Page the confirmation link returns to. Defaults to the current page in a
32
+ * browser. Its origin must be on the team's allowed redirect list, or cStar
33
+ * falls back to the hosted widget page.
34
+ */
35
+ returnUrl?: string;
24
36
  }
25
- /** Response from a successful signup. */
37
+ /**
38
+ * Response from signup. It is identical for new and existing emails, so a
39
+ * signup form cannot be used to discover who already has an account. The
40
+ * customer confirms through the emailed link; call `completeSignIn()` on the
41
+ * page the link returns to.
42
+ */
26
43
  interface SignupResult {
27
- customer: AuthCustomer;
28
- /** Access token for authenticated requests. Null if email confirmation is required. */
29
- accessToken: string | null;
30
- /** Refresh token for renewing the session. Null if email confirmation is required. */
31
- refreshToken: string | null;
32
- }
33
- /** Response when email confirmation is required after signup. */
34
- interface SignupConfirmationResult {
35
44
  success: true;
36
45
  message: string;
37
46
  requiresConfirmation: true;
38
47
  }
39
- /** Parameters for logging in an existing customer. */
48
+ /** @deprecated Signup always requires confirmation now. Use `SignupResult`. */
49
+ type SignupConfirmationResult = SignupResult;
50
+ /**
51
+ * Parameters for logging in an existing customer. Signup is password-less,
52
+ * so this works only after the customer has chosen a password (see
53
+ * `AuthClient.setPassword()`).
54
+ */
40
55
  interface LoginParams {
41
56
  /** Customer email address. */
42
57
  email: string;
@@ -69,47 +84,83 @@ interface AuthSession {
69
84
  *
70
85
  * const auth = new AuthClient({ teamSlug: 'acme' });
71
86
  *
72
- * // Sign up a new customer
73
- * const result = await auth.signup({
74
- * email: 'jane@example.com',
75
- * password: 'securepass123',
76
- * name: 'Jane Doe',
77
- * });
87
+ * // Sign up: cStar emails a sign-in link. No password yet.
88
+ * await auth.signup({ email: 'jane@example.com', name: 'Jane Doe' });
78
89
  *
79
- * // Log in an existing customer
80
- * const session = await auth.login({
81
- * email: 'jane@example.com',
82
- * password: 'securepass123',
83
- * });
90
+ * // On the page the link returns to
91
+ * const session = await auth.completeSignIn();
84
92
  *
85
- * // Check current session
86
- * const current = auth.getSession();
93
+ * // Optional: choose a password for faster sign-in next time
94
+ * await auth.setPassword('securepass123');
95
+ *
96
+ * // Later visits: log in with that password
97
+ * await auth.login({ email: 'jane@example.com', password: 'securepass123' });
87
98
  * ```
88
99
  */
89
100
  declare class AuthClient {
90
101
  private readonly baseUrl;
91
102
  private readonly teamSlug;
103
+ private readonly supabaseUrl;
104
+ private readonly supabaseKey;
92
105
  private session;
106
+ private pkceClient;
93
107
  constructor(config: AuthConfig);
94
108
  /**
95
- * Create a new customer account.
109
+ * Create a password-less customer account and email a sign-in link.
110
+ *
111
+ * The response is the same whether or not the email already has an
112
+ * account. Nothing is signed in yet: the customer clicks the link, and the
113
+ * page it returns to calls `completeSignIn()`. Signup takes no password,
114
+ * because one chosen before the link proves the address could belong to
115
+ * someone else. Once signed in, the customer can call `setPassword()` to
116
+ * enable `login()`.
96
117
  *
97
- * If the team requires email confirmation, returns a `SignupConfirmationResult`
98
- * instead of tokens. Check for `requiresConfirmation` in the response.
118
+ * Runs in a browser: the PKCE verifier is stored in this page's
119
+ * localStorage so `completeSignIn()` can finish the exchange.
99
120
  *
100
- * @throws Error on validation failure, duplicate email, or server error
121
+ * @throws CStarRateLimitError when too many signups were attempted
101
122
  */
102
- signup(params: SignupParams): Promise<SignupResult | SignupConfirmationResult>;
123
+ signup(params: SignupParams): Promise<SignupResult>;
124
+ /**
125
+ * Finish sign-in on the page the confirmation link returns to.
126
+ *
127
+ * Reads `?code=` and `?type=` from the URL, removes them from the address
128
+ * bar, exchanges the code, and stores the session. Returns null when the URL
129
+ * holds no cStar confirmation code or the exchange fails.
130
+ *
131
+ * @param url Defaults to `window.location.href`.
132
+ */
133
+ completeSignIn(url?: string): Promise<AuthSession | null>;
134
+ /**
135
+ * Choose a password for the signed-in customer, so later visits can use
136
+ * `login()` instead of an emailed link.
137
+ *
138
+ * Needs a session from `completeSignIn()` or `login()`. This is the same
139
+ * Supabase Auth password update the hosted widget makes after a first
140
+ * email sign-in, and it clears the widget's "choose a password" prompt.
141
+ * The customer stays signed in.
142
+ *
143
+ * @throws CStarValidationError when the password is under 8 characters or is rejected
144
+ * @throws CStarAuthenticationError when there is no session or it has expired
145
+ */
146
+ setPassword(password: string): Promise<void>;
103
147
  /**
104
148
  * Log in an existing customer with email and password.
105
149
  *
150
+ * Signup is password-less, so this works only after the customer has
151
+ * chosen a password with `setPassword()` or in the hosted widget.
152
+ *
106
153
  * On success, stores the session internally so subsequent
107
154
  * ChatClient requests can use `auth.getSession()` for tokens.
108
155
  *
109
156
  * @throws Error on invalid credentials, unconfirmed email, or server error
110
157
  */
111
158
  login(params: LoginParams): Promise<LoginResult>;
112
- /** Clear the stored session. */
159
+ /**
160
+ * Clear the session: the in-memory one and, when a sign-in link or
161
+ * setPassword() restored it through Supabase Auth, the copy that client
162
+ * keeps in browser storage, so the next person on the device is signed out.
163
+ */
113
164
  logout(): void;
114
165
  /** Get the current session, or null if not authenticated. */
115
166
  getSession(): AuthSession | null;
@@ -119,6 +170,7 @@ declare class AuthClient {
119
170
  get isAuthenticated(): boolean;
120
171
  /** Get the current access token, or null. Useful for passing to ChatClient. */
121
172
  get accessToken(): string | null;
173
+ private getPkceClient;
122
174
  private fetch;
123
175
  }
124
176