@kerne/react 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -3,25 +3,38 @@ import React, { ReactNode, Component, ErrorInfo } from 'react';
3
3
  import { KerneConfig, JoinWaitlistParams, ValidateTokenResponse, ValidateCodeResponse } from '@kerne/server';
4
4
  export { JoinWaitlistParams, ValidateCodeResponse, ValidateTokenResponse, WaitlistEntry } from '@kerne/server';
5
5
  import * as _kerne_types from '@kerne/types';
6
- import { User, AuthResponse, EntitlementCheck, SubscriptionWithPlan, PublicPlan, Entitlements, UsageRecord, Subscription } from '@kerne/types';
7
- export { AuthResponse, EntitlementCheck, Entitlements, PublicPlan, Subscription, SubscriptionWithPlan, UsageRecord, User } from '@kerne/types';
6
+ import { User, AuthResponse, ActivationContext, AuthConfig, EntitlementCheck, SubscriptionWithPlan, PublicPlan, Entitlements, UsageRecord } from '@kerne/types';
7
+ export { ActivationContext, AuthConfig, AuthResponse, EntitlementCheck, Entitlements, PublicPlan, Subscription, SubscriptionWithPlan, UsageRecord, User } from '@kerne/types';
8
+ import { D as DeepPartial, K as KerneLocalization } from './i18n-BWTT1DWD.cjs';
9
+ export { d as defaultLocalization } from './i18n-BWTT1DWD.cjs';
8
10
 
9
11
  interface KerneReactConfig extends Omit<KerneConfig, 'secretKey'> {
10
12
  storage?: Storage;
11
13
  storageKey?: string;
12
14
  onAuthChange?: (user: User | null) => void;
15
+ /**
16
+ * A cross-origin handoff arrived but could not be exchanged for a session -
17
+ * an expired one-time code, or an origin this tenant does not allow.
18
+ *
19
+ * Worth handling: the SDK cannot render UI, so without this the user
20
+ * completes a magic link, lands on your app, and is simply not signed in
21
+ * with nothing on screen explaining why. Typically a toast plus a link back
22
+ * to sign in.
23
+ */
24
+ onHandoffError?: (error: unknown) => void;
13
25
  /** Auto-refresh session before expiry (default: true) */
14
26
  autoRefresh?: boolean;
15
27
  /** Refresh buffer in seconds before expiry (default: 60) */
16
28
  refreshBuffer?: number;
17
29
  }
18
30
 
19
- type Listener$1 = () => void;
31
+ type Listener = () => void;
20
32
  declare class KerneClient {
21
33
  private kerne;
22
34
  private storage;
23
35
  private storageKey;
24
36
  private onAuthChange?;
37
+ private onHandoffError?;
25
38
  private autoRefresh;
26
39
  private refreshBuffer;
27
40
  private refreshTimer;
@@ -31,16 +44,43 @@ declare class KerneClient {
31
44
  private _refreshToken;
32
45
  private _expiresAt;
33
46
  private _isLoading;
47
+ private _authConfigPromise;
34
48
  readonly baseUrl: string;
35
49
  readonly appId: string;
36
50
  constructor(config: KerneReactConfig);
37
- subscribe(listener: Listener$1): () => void;
51
+ subscribe(listener: Listener): () => void;
38
52
  private notify;
39
53
  get user(): User | null;
40
54
  get token(): string | null;
41
55
  get isAuthenticated(): boolean;
42
56
  get isLoading(): boolean;
43
57
  private loadSession;
58
+ private getHandoffCodeFromUrl;
59
+ /**
60
+ * Strips only `kerne_handoff` from the URL, leaving the rest of the
61
+ * path/query (e.g. a `return_to` the host app added) untouched - and
62
+ * without adding a history entry, so back-navigation doesn't resurrect it.
63
+ */
64
+ private stripHandoffParamFromUrl;
65
+ /**
66
+ * Same shape as `refreshOnLoad`: a bootstrap-only path invoked once from
67
+ * `loadSession()` when a `kerne_handoff` code is present in the URL,
68
+ * entirely invisible to the host app - no code on their end required.
69
+ *
70
+ * Works no matter which page the browser lands on: `redirectUrl` is a hard
71
+ * navigation (`window.location.href`), so the destination reloads the SDK
72
+ * fresh and `loadSession()` reads the code straight off that page's own
73
+ * URL - there is no dependency on a specific "callback route", as long as
74
+ * `KerneProvider` wraps whatever the host renders there (normally true when
75
+ * it is mounted once at the app root).
76
+ *
77
+ * One retry on a transient failure (network blip, 5xx) - worth it because
78
+ * the code was never reached by the server in that case, so it is still
79
+ * unspent. A 4xx (invalid/expired/already-consumed) is not retried: the
80
+ * code is gone, retrying only delays the "logged out" outcome.
81
+ */
82
+ private exchangeHandoffOnLoad;
83
+ private refreshOnLoad;
44
84
  /**
45
85
  * `AuthResponse.user` is the minimal `{id, email, role}` claim set the
46
86
  * token endpoint returns, not the full profile (`User`) - so this always
@@ -76,10 +116,45 @@ declare class KerneClient {
76
116
  currentPassword: string;
77
117
  newPassword: string;
78
118
  }): Promise<void>;
119
+ /** Soft-deletes (deactivates) the caller's own account, then clears the local session. */
120
+ requestAccountDeletion(): Promise<void>;
79
121
  /** Forgot-password flow (logged out) - sends the reset email. */
80
- requestPasswordReset(email: string): Promise<void>;
122
+ requestPasswordReset(email: string, callbackUrl?: string): Promise<void>;
81
123
  /** Confirms a password reset with the token from the email. */
82
124
  confirmPasswordReset(token: string, password: string): Promise<void>;
125
+ /**
126
+ * Sends a magic-link email (logged out). Anti-enumeration: always resolves,
127
+ * regardless of whether the email is registered.
128
+ */
129
+ startPasswordless(email: string, callbackUrl?: string, invitation?: {
130
+ invitationToken?: string;
131
+ invitationCode?: string;
132
+ }): Promise<void>;
133
+ /** Completes a magic-link login using the `token` query param from the emailed link. */
134
+ loginWithMagicLink(token: string): Promise<AuthResponse>;
135
+ /** Read-only: which UI to render for an activation token, without consuming it. */
136
+ getActivationContext(token: string): Promise<ActivationContext>;
137
+ /** Completes activation (sets a password or confirms magic-link mode), then logs the user in. */
138
+ completeActivation(token: string, password?: string): Promise<AuthResponse>;
139
+ /**
140
+ * Mints a handoff code for the current session and redirects the browser
141
+ * to `url` with it attached (`?kerne_handoff=...`) - never the real
142
+ * token/refresh_token, which would otherwise leak into server logs,
143
+ * browser history, and third-party `Referer` headers on the landing page.
144
+ */
145
+ redirectWithSession(url: string): Promise<void>;
146
+ /**
147
+ * The tenant's public auth config - which sign-in methods to render, whether
148
+ * registration is open, whether to show Kerne branding.
149
+ *
150
+ * Deduped on the in-flight promise rather than the resolved value: the
151
+ * prebuilt forms all read this on mount, and two of them mounted together
152
+ * (a login screen with a register link prefetching) would otherwise fire two
153
+ * identical requests before either resolved. Cached for the client's
154
+ * lifetime - this config changes on the order of "the owner edited their
155
+ * settings", not per render.
156
+ */
157
+ getAuthConfig(): Promise<AuthConfig>;
83
158
  /**
84
159
  * Errors are not swallowed here (unlike the old implementation, which
85
160
  * caught everything and returned `false`) - a 401/500 must not be
@@ -115,7 +190,7 @@ declare class KerneClient {
115
190
  validateInvitationToken(token: string): Promise<ValidateTokenResponse>;
116
191
  /** Validate an invitation code before showing the registration form. */
117
192
  validateInvitationCode(code: string): Promise<ValidateCodeResponse>;
118
- sendVerificationEmail(verificationType?: 'code' | 'link'): Promise<void>;
193
+ sendVerificationEmail(verificationType?: 'code' | 'link', callbackUrl?: string): Promise<void>;
119
194
  verifyEmailWithCode(code: string): Promise<{
120
195
  success: boolean;
121
196
  needsTokenRefresh: boolean;
@@ -133,8 +208,16 @@ declare class KerneClient {
133
208
  declare const KerneContext: React.Context<KerneClient | null>;
134
209
  interface KerneProviderProps extends KerneReactConfig {
135
210
  children: React.ReactNode;
211
+ /**
212
+ * Overrides for the prebuilt components' copy, including the error-code
213
+ * messages. Set once here rather than per component - the strings are shared,
214
+ * and a per-component prop would mean re-passing the same dictionary to every
215
+ * screen. Merged group-by-group over the English defaults, so a partial
216
+ * override keeps everything it does not mention.
217
+ */
218
+ localization?: DeepPartial<KerneLocalization>;
136
219
  }
137
- declare function KerneProvider({ children, ...config }: KerneProviderProps): react_jsx_runtime.JSX.Element;
220
+ declare function KerneProvider({ children, localization, ...config }: KerneProviderProps): react_jsx_runtime.JSX.Element;
138
221
 
139
222
  /**
140
223
  * Access the Kerne client instance
@@ -149,7 +232,7 @@ declare function useClient(): KerneClient;
149
232
  * ```
150
233
  */
151
234
  declare function useAuth(): {
152
- user: User | null;
235
+ user: _kerne_types.User | null;
153
236
  token: string | null;
154
237
  isAuthenticated: boolean;
155
238
  isLoading: boolean;
@@ -165,20 +248,26 @@ declare function useAuth(): {
165
248
  invitationCode?: string;
166
249
  }) => Promise<_kerne_types.AuthResponse>;
167
250
  logout: () => void;
168
- refreshUser: () => Promise<User | null>;
251
+ refreshUser: () => Promise<_kerne_types.User | null>;
169
252
  refreshToken: () => Promise<_kerne_types.AuthResponse | null>;
170
253
  updateProfile: (params: {
171
254
  first_name?: string;
172
255
  last_name?: string;
173
256
  avatar?: string;
174
- }) => Promise<User>;
257
+ }) => Promise<_kerne_types.User>;
175
258
  updatePassword: (params: {
176
259
  currentPassword: string;
177
260
  newPassword: string;
178
261
  }) => Promise<void>;
179
- requestPasswordReset: (email: string) => Promise<void>;
262
+ requestAccountDeletion: () => Promise<void>;
263
+ requestPasswordReset: (email: string, callbackUrl?: string) => Promise<void>;
180
264
  confirmPasswordReset: (token: string, password: string) => Promise<void>;
181
- sendVerificationEmail: (verificationType?: "code" | "link") => Promise<void>;
265
+ startPasswordless: (email: string, callbackUrl?: string, invitation?: {
266
+ invitationToken?: string;
267
+ invitationCode?: string;
268
+ }) => Promise<void>;
269
+ loginWithMagicLink: (token: string) => Promise<_kerne_types.AuthResponse>;
270
+ sendVerificationEmail: (verificationType?: "code" | "link", callbackUrl?: string) => Promise<void>;
182
271
  verifyEmailWithCode: (code: string) => Promise<{
183
272
  success: boolean;
184
273
  needsTokenRefresh: boolean;
@@ -192,6 +281,9 @@ declare function useAuth(): {
192
281
  needsVerification: boolean;
193
282
  email: string;
194
283
  }>;
284
+ getActivationContext: (token: string) => Promise<_kerne_types.ActivationContext>;
285
+ completeActivation: (token: string, password?: string) => Promise<_kerne_types.AuthResponse>;
286
+ redirectWithSession: (url: string) => Promise<void>;
195
287
  };
196
288
  /**
197
289
  * Checkout hook - starts a Stripe/Polar checkout session for a plan price.
@@ -226,30 +318,33 @@ declare function usePortal(): {
226
318
  openPortal: (returnUrl?: string) => Promise<void>;
227
319
  };
228
320
  /**
229
- * Hook for a plain allowed/denied entitlement check.
321
+ * Whether the current scope has access to one feature - the single entry
322
+ * point for a live, per-feature decision. Deliberately not named after what
323
+ * it checks TODAY: this gates entitlements for now, but the same shape is
324
+ * meant to answer for a future access-rule engine (custom RBAC, feature
325
+ * flags) without a rename - "access" is the word that survives that.
326
+ * `details` (not `entitlement`) follows the same logic on the return value.
230
327
  *
231
- * @example
232
- * ```tsx
233
- * const { allowed, isLoading } = useAllows('advanced_analytics');
234
- * ```
235
- */
236
- declare function useAllows(featureKey: string, requested?: number): {
237
- allowed: boolean;
238
- isLoading: boolean;
239
- error: Error | null;
240
- };
241
- /**
242
- * Hook for the full entitlement detail (limit/used/remaining/overage) -
243
- * use `useAllows` when a plain boolean is all you need.
328
+ * Not `useCheck` either - too close to `useCheckout` to skim safely, and
329
+ * unrelated to it in every other way. Not folded into `useEntitlements()`
330
+ * (the cached list, for display): the two hit different endpoints with
331
+ * different consistency guarantees, and merging them would make the network
332
+ * behavior branch silently on whether a key was passed.
333
+ *
334
+ * Replaces the former `useAllows`/`useCheck` pair, which were two names for
335
+ * one request: `client.allows()` is literally `check().allowed`, hitting the
336
+ * same endpoint and discarding the rest.
244
337
  *
245
338
  * @example
246
339
  * ```tsx
247
- * const { check, isLoading } = useCheck('api_calls');
248
- * // check?.remaining, check?.limit, check?.overage_behavior
340
+ * const { allowed } = useAccess('advanced_analytics');
341
+ * const { details } = useAccess('api_calls');
342
+ * // details?.remaining, details?.limit, details?.overage_behavior
249
343
  * ```
250
344
  */
251
- declare function useCheck(featureKey: string, requested?: number): {
252
- check: EntitlementCheck | null;
345
+ declare function useAccess(featureKey: string, requested?: number): {
346
+ details: EntitlementCheck | null;
347
+ allowed: boolean;
253
348
  isLoading: boolean;
254
349
  error: Error | null;
255
350
  };
@@ -263,8 +358,8 @@ declare function useCheck(featureKey: string, requested?: number): {
263
358
  */
264
359
  declare function useSubscription(productSlug?: string): {
265
360
  isActive: boolean;
266
- planSlug: any;
267
- subscription: any | null;
361
+ planSlug: string | undefined;
362
+ subscription: SubscriptionWithPlan | null;
268
363
  isLoading: boolean;
269
364
  error: Error | null;
270
365
  };
@@ -282,11 +377,27 @@ declare function usePlans(productIdOrSlug?: string): {
282
377
  isLoading: boolean;
283
378
  error: Error | null;
284
379
  };
380
+ /**
381
+ * The tenant's public auth config - which sign-in methods are enabled, whether
382
+ * registration is open, whether to show Kerne branding. Drives what the
383
+ * prebuilt forms render, so a tenant that turns magic link on gets the button
384
+ * without shipping any code.
385
+ *
386
+ * @example
387
+ * ```tsx
388
+ * const { config, isLoading } = useAuthConfig();
389
+ * ```
390
+ */
391
+ declare function useAuthConfig(): {
392
+ config: AuthConfig | null;
393
+ isLoading: boolean;
394
+ error: Error | null;
395
+ };
285
396
  /**
286
397
  * Every entitlement for the current user in one call - for a "your plan"
287
- * screen. Use `useAllows`/`useCheck` instead for a single feature - this
288
- * response never carries `allowed` (see docs: the list is cached, a stale
289
- * verdict would be unsafe).
398
+ * screen. Use `useAccess` instead for a single feature - this response
399
+ * never carries `allowed` (see docs: the list is cached, a stale verdict
400
+ * would be unsafe).
290
401
  *
291
402
  * @example
292
403
  * ```tsx
@@ -353,13 +464,13 @@ declare function useInvitation(params: {
353
464
  * ```
354
465
  */
355
466
  declare function useUser(): {
356
- user: User | null;
467
+ user: _kerne_types.User | null;
357
468
  isLoading: boolean;
358
469
  update: (params: {
359
470
  first_name?: string;
360
471
  last_name?: string;
361
472
  avatar?: string;
362
- }) => Promise<User>;
473
+ }) => Promise<_kerne_types.User>;
363
474
  fullName: string | null;
364
475
  initials: string | null;
365
476
  email: string | null;
@@ -431,8 +542,16 @@ interface SubscriptionGuardProps {
431
542
  declare function HasSubscription({ children, fallback }: SubscriptionGuardProps): ReactNode;
432
543
  interface EntitlementGuardProps {
433
544
  children: ReactNode;
434
- /** Capability key to check */
435
- key: string;
545
+ /**
546
+ * Capability key to check.
547
+ *
548
+ * NOT named `key`: React reserves that prop for reconciliation and strips it
549
+ * before the component sees it, so the previous `key="..."` signature could
550
+ * only ever read `undefined` - the check then failed and the guard rendered
551
+ * its fallback forever. Same name as `useAccess(featureKey)` and
552
+ * `UpgradePrompt`, so the whole entitlement surface reads alike.
553
+ */
554
+ featureKey: string;
436
555
  /** Minimum value required (for limit features) */
437
556
  minimum?: number;
438
557
  /** Fallback content when entitlement is not met */
@@ -445,18 +564,18 @@ interface EntitlementGuardProps {
445
564
  *
446
565
  * @example
447
566
  * ```tsx
448
- * <Allows key="advanced_analytics" fallback={<UpgradePrompt feature="analytics" />}>
567
+ * <Allows featureKey="advanced_analytics" fallback={<UpgradePrompt featureKey="advanced_analytics" />}>
449
568
  * <AnalyticsDashboard />
450
569
  * </Allows>
451
570
  * ```
452
571
  */
453
- declare function Allows({ children, key, minimum, fallback, }: EntitlementGuardProps): ReactNode;
572
+ declare function Allows({ children, featureKey, minimum, fallback, }: EntitlementGuardProps): ReactNode;
454
573
  interface ProtectedProps {
455
574
  children: ReactNode;
456
575
  /** Require authentication */
457
576
  auth?: boolean;
458
- /** Require specific capability key */
459
- key?: string;
577
+ /** Require a specific capability. Not `key` - React reserves that prop name. */
578
+ featureKey?: string;
460
579
  /** Fallback for unauthenticated */
461
580
  authFallback?: ReactNode;
462
581
  /** Fallback for missing feature */
@@ -467,12 +586,12 @@ interface ProtectedProps {
467
586
  *
468
587
  * @example
469
588
  * ```tsx
470
- * <Protected auth key="advanced_analytics" authFallback={<Login />} billingFallback={<Upgrade />}>
589
+ * <Protected auth featureKey="advanced_analytics" authFallback={<Login />} billingFallback={<Upgrade />}>
471
590
  * <ProFeature />
472
591
  * </Protected>
473
592
  * ```
474
593
  */
475
- declare function Protected({ children, auth, key, authFallback, billingFallback, }: ProtectedProps): ReactNode;
594
+ declare function Protected({ children, auth, featureKey, authFallback, billingFallback, }: ProtectedProps): ReactNode;
476
595
 
477
596
  /**
478
597
  * Kerne Error Boundary
@@ -533,35 +652,4 @@ declare function useKerneError(): {
533
652
  clearError: () => void;
534
653
  };
535
654
 
536
- /**
537
- * KerneStore - External store for React 18+ useSyncExternalStore
538
- *
539
- * Optimized for minimal re-renders:
540
- * - Subscribe to specific slices of state
541
- * - Only notifies when subscribed values change
542
- */
543
-
544
- interface KerneState {
545
- user: User | null;
546
- isAuthenticated: boolean;
547
- isLoading: boolean;
548
- subscription: Subscription | null;
549
- error: Error | null;
550
- }
551
- type Listener = () => void;
552
- type Selector<T> = (state: KerneState) => T;
553
- declare class KerneStore {
554
- private state;
555
- private listeners;
556
- constructor(initialState?: Partial<KerneState>);
557
- getState(): KerneState;
558
- getSnapshot(): KerneState;
559
- getServerSnapshot(): KerneState;
560
- setState(partial: Partial<KerneState>): void;
561
- private hasChanged;
562
- subscribe(listener: Listener): () => void;
563
- private notify;
564
- select<T>(selector: Selector<T>): T;
565
- }
566
-
567
- export { Allows, AuthLoading, Authenticated, HasSubscription, KerneClient, KerneContext, type KerneError, KerneErrorBoundary, KerneProvider, type KerneProviderProps, type KerneReactConfig, type KerneState, KerneStore, Protected, Unauthenticated, useAllows, useAuth, useCheck, useCheckout, useClient, useEntitlements, useInvitation, useKerneError, usePlans, usePortal, useSubscription, useUsage, useUser, useWaitlist };
655
+ export { Allows, AuthLoading, Authenticated, HasSubscription, KerneClient, KerneContext, type KerneError, KerneErrorBoundary, KerneLocalization, KerneProvider, type KerneProviderProps, type KerneReactConfig, Protected, Unauthenticated, useAccess, useAuth, useAuthConfig, useCheckout, useClient, useEntitlements, useInvitation, useKerneError, usePlans, usePortal, useSubscription, useUsage, useUser, useWaitlist };