@synqauth/react 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,7 +36,9 @@ export default function Settings() {
36
36
  }
37
37
  ```
38
38
 
39
- The hooks are `useSession()`, `useUser()`, `useWallets()`, `useLinkedAccounts()`, `useAccountSessions()` and `useAuth()`. Every call answers `{ data, error }` and never throws, and a change clicked twice while it's under way is sent once.
39
+ To sign people in on your own page (own-UI sign-in, on for the app in the dashboard), render `<SignInForm returnTo="/dashboard" />`, lay out its parts (`PasskeyButton`, `PasswordForm`, `EmailCodeForm`, `VerifyEmailForm`) your own way, or build your own form on `useSignIn()`. It offers a passkey where the browser can use the brand's on your page (Chrome and Edge 128+, Safari 18+, Firefox 152+), then an emailed code or a password. Anything only Synq's hosted page can do, such as a token gate, goes there.
40
+
41
+ The hooks are `useSession()`, `useSignIn()`, `useUser()`, `useWallets()`, `useLinkedAccounts()`, `useAccountSessions()` and `useAuth()`. Every call answers `{ data, error }` and never throws, and a change clicked twice while it's under way is sent once.
40
42
 
41
43
  The components take Synq's look from `@synqauth/react/styles.css`, themed with CSS variables (`--synq-color-primary`, `--synq-radius`...), or none with `unstyled`. `<SynqProvider messages>` puts their words in your app's language.
42
44
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,99 @@
1
1
 
2
- import { ReactNode } from "react";
3
- import { AccountComponents, SynqError } from "@synqauth/js";
2
+ import { InputHTMLAttributes, ReactNode } from "react";
3
+ import { AccountComponents, ChallengeMethod, ChallengeProblem, DeniedReason, RedirectReason, SynqError } from "@synqauth/js";
4
+ //#region src/sign-in.d.ts
5
+ /** Where a sign-in is: what the form shows. */
6
+ type SignInStep = 'start' | 'code' | 'verify-email' | 'done';
7
+ /** What went wrong with the last step: branch on `code`, never on the English. */
8
+ interface SignInProblem {
9
+ /**
10
+ * Synq's problem with the step (`wrong_credentials`, `wrong_code`…), or
11
+ * the sign-in's own: `expired` (it timed out: start again), `refused`
12
+ * (this person can't sign in here: see `reason`), `passkey_cancelled`
13
+ * (the browser's passkey prompt closed with none), `unavailable` (the
14
+ * app's server couldn't be reached, or failed).
15
+ */
16
+ readonly code: ChallengeProblem | 'expired' | 'refused' | 'passkey_cancelled' | 'unavailable';
17
+ /** Why, when `refused`. */
18
+ readonly reason?: DeniedReason;
19
+ /** Synq's words for it, in English: show your own for `code` in another language. */
20
+ readonly message: string;
21
+ /** Seconds to wait first, when Synq said. */
22
+ readonly retryAfter?: number;
23
+ }
24
+ /** What only the hosted page can do for this sign-in, and what to take there. */
25
+ interface SignInHandover {
26
+ readonly reason: RedirectReason;
27
+ readonly method?: ChallengeMethod;
28
+ readonly loginHint?: string;
29
+ }
30
+ /** What `useSignIn()` takes: where to go once signed in, or what to do instead. */
31
+ interface UseSignInOptions {
32
+ /** Where to go once signed in: the page it started on by default. */
33
+ readonly returnTo?: string;
34
+ /** Called once signed in, with where to go, instead of going there. */
35
+ readonly onSignedIn?: (returnTo: string) => void;
36
+ }
37
+ /** What `useSignIn()` answers: the sign-in's step, what went wrong, and each method's steps. */
38
+ interface UseSignIn {
39
+ readonly step: SignInStep;
40
+ /** Whether a step is on its way to Synq. */
41
+ readonly pending: boolean;
42
+ readonly problem: SignInProblem | null;
43
+ /** The address a code went to. */
44
+ readonly email: string | undefined;
45
+ /** Whether a new account's address may be left unverified for now. */
46
+ readonly canSkip: boolean;
47
+ /** Set when only the hosted page can go on: `continueOnHostedPage()` goes there. */
48
+ readonly handover: SignInHandover | null;
49
+ readonly password: {
50
+ readonly signIn: (input: {
51
+ email: string;
52
+ password: string;
53
+ }) => Promise<void>;
54
+ readonly signUp: (input: {
55
+ email: string;
56
+ password: string;
57
+ }) => Promise<void>;
58
+ };
59
+ readonly emailCode: {
60
+ readonly send: (email: string) => Promise<void>;
61
+ readonly verify: (code: string) => Promise<void>;
62
+ readonly resend: () => Promise<void>;
63
+ };
64
+ readonly passkey: {
65
+ /** Whether this browser takes the issuer's passkeys on this page (related origins). */
66
+ readonly supported: boolean;
67
+ /** Whether it offers them in the address field as the person types (conditional UI). */
68
+ readonly autofill: boolean;
69
+ /**
70
+ * On a brand's custom domain, the brand's own Synq host, whose passkeys
71
+ * work too (`signIn({ related: true })`): known after the first passkey
72
+ * prompt.
73
+ */
74
+ readonly relatedDomain: string | null;
75
+ readonly signIn: (options?: {
76
+ related?: boolean;
77
+ }) => Promise<void>;
78
+ /** Offers passkeys in the address field (`autocomplete="username webauthn"`): call once it's on the page. */
79
+ readonly startAutofill: () => Promise<void>;
80
+ };
81
+ readonly verifyEmail: {
82
+ readonly verify: (code: string) => Promise<void>;
83
+ readonly resend: () => Promise<void>;
84
+ readonly skip: () => Promise<void>;
85
+ };
86
+ /** Goes on at the hosted page, with the method and address Synq named, or these. */
87
+ readonly continueOnHostedPage: (options?: {
88
+ method?: ChallengeMethod;
89
+ loginHint?: string;
90
+ }) => void;
91
+ /** Drops the sign-in under way and goes back to the first step. */
92
+ readonly startOver: () => Promise<void>;
93
+ }
94
+ /** One sign-in on the app's own screens, through the route mount's `sign-in/flow`. */
95
+ export declare function useSignIn(options?: UseSignInOptions): UseSignIn;
96
+ //#endregion
4
97
  //#region src/messages.d.ts
5
98
  /** Every word the components show, for `<SynqProvider messages>`; any left out stay English. */
6
99
  interface SynqMessages {
@@ -40,6 +133,66 @@ interface SynqMessages {
40
133
  readonly makeWalletPrimary: (name: string) => string;
41
134
  /** The label of the field naming a wallet. */
42
135
  readonly walletName: (address: string) => string;
136
+ /** API keys for the brand's APIs (AERO-1693). */
137
+ readonly apiKeys: string;
138
+ readonly noApiKeys: string;
139
+ readonly newApiKey: string;
140
+ /** The field picking which of the brand's APIs a key is for. */
141
+ readonly api: string;
142
+ /** The scopes a key may use. */
143
+ readonly keyScopes: string;
144
+ readonly expires: string;
145
+ readonly never: string;
146
+ readonly inDays: (days: number) => string;
147
+ readonly makeKey: string;
148
+ readonly makingKey: string;
149
+ readonly revoke: string;
150
+ /** The label of the button that revokes a key. */
151
+ readonly revokeKey: (name: string) => string;
152
+ readonly revoked: string;
153
+ readonly expired: string;
154
+ readonly lastUsed: (when: string) => string;
155
+ readonly neverUsed: string;
156
+ readonly expiresOn: (when: string) => string;
157
+ /** Over the key just made, the only time it shows. */
158
+ readonly copyKeyNow: string;
159
+ readonly copy: string;
160
+ readonly copied: string;
161
+ readonly done: string;
162
+ /** When making a key needs a sign-in from the last few minutes. */
163
+ readonly keyNeedsFreshSignIn: string;
164
+ readonly signInAgain: string;
165
+ /** `<SignInForm>` (AERO-1411): sign-in on the app's own screens. */
166
+ readonly email: string;
167
+ readonly password: string;
168
+ readonly signInWithPasskey: string;
169
+ /** The button for a passkey made at the brand's own Synq host, on its custom domain. */
170
+ readonly passkeyFor: (domain: string) => string;
171
+ readonly emailMeACode: string;
172
+ /** Between a passkey and the other ways. */
173
+ readonly or: string;
174
+ readonly code: string;
175
+ readonly codeSentTo: (email: string) => string;
176
+ /** Over the code that proves a new account's address. */
177
+ readonly verifyYourEmail: (email: string) => string;
178
+ readonly verify: string;
179
+ readonly sendAnotherCode: string;
180
+ readonly useAnotherWay: string;
181
+ readonly skipForNow: string;
182
+ /** The link to the hosted page's password step, where resetting one lives. */
183
+ readonly forgotPassword: string;
184
+ readonly createAccount: string;
185
+ /** Before the link to the sign-up screen. */
186
+ readonly newHere: string;
187
+ /** Before the link back to the sign-in screen. */
188
+ readonly haveAnAccount: string;
189
+ /** While the browser goes to the hosted page for what only it can do. */
190
+ readonly continuingToSignIn: string;
191
+ readonly tryAgainIn: (seconds: number) => string;
192
+ /** What went wrong with a step, by `SignInProblem.code`; Synq's English for any left out. */
193
+ readonly signInProblems: Readonly<Partial<Record<SignInProblem['code'], string>>>;
194
+ /** Why this person can't sign in here, by `SignInProblem.reason`. */
195
+ readonly signInRefusals: Readonly<Partial<Record<DeniedReason, string>>>;
43
196
  }
44
197
  /** The components' English words, which an app's own messages override one by one. */
45
198
  export declare const DEFAULT_MESSAGES: SynqMessages;
@@ -136,6 +289,14 @@ type Wallet = AccountComponents['schemas']['Wallet'];
136
289
  type LinkedAccount = AccountComponents['schemas']['Identity'];
137
290
  /** One of the person's sessions at the brand, on some device. */
138
291
  type AccountSession = AccountComponents['schemas']['Session'];
292
+ /** One of the person's API keys for the brand's APIs (AERO-1693): never the key itself. */
293
+ type ApiKey = AccountComponents['schemas']['UserApiKey'];
294
+ /** A key just made: `key` is the key itself, in this answer only. */
295
+ type NewApiKey = AccountComponents['schemas']['NewUserApiKey'];
296
+ /** What making an API key takes: the API's identifier, a name, its scopes, an expiry if any. */
297
+ type ApiKeyInput = AccountComponents['schemas']['UserApiKeyCreate'];
298
+ /** One of the brand's APIs the person may make keys for, with the scopes it defines. */
299
+ type KeyableApi = AccountComponents['schemas']['KeyableApi'];
139
300
  //#endregion
140
301
  //#region src/mount.d.ts
141
302
  /** Calls the app's route mount: `request('session')`, `request('account/v1/profile', { method: 'PATCH', ... })`. */
@@ -217,6 +378,10 @@ export declare function useWallets(): UseAccountData<readonly Wallet[]>;
217
378
  export declare function useLinkedAccounts(): UseAccountData<readonly LinkedAccount[]>;
218
379
  /** The person's sessions at the brand, on every device. */
219
380
  export declare function useAccountSessions(): UseAccountData<readonly AccountSession[]>;
381
+ /** The person's API keys for the brand's APIs (AERO-1693), revoked and expired ones too. */
382
+ export declare function useApiKeys(): UseAccountData<readonly ApiKey[]>;
383
+ /** The brand's APIs that let the person make keys, with the scopes each defines. */
384
+ export declare function useKeyableApis(): UseAccountData<readonly KeyableApi[]>;
220
385
  /** What `useUser()` answers: the person's claims, profile and metadata, and `update` for the profile. */
221
386
  interface UseUser {
222
387
  readonly isLoaded: boolean;
@@ -274,6 +439,18 @@ interface UseAuth {
274
439
  /** Signs that device out. */
275
440
  readonly end: (id: string) => Promise<SynqResult<null>>;
276
441
  };
442
+ /** Their API keys for the brand's own APIs (AERO-1693). */
443
+ readonly apiKeys: {
444
+ readonly list: () => Promise<SynqResult<readonly ApiKey[]>>;
445
+ /**
446
+ * Makes a key: its `key` is in this answer only, so show it once. Making
447
+ * one needs a sign-in from the last 5 minutes (`fresh_sign_in_required`:
448
+ * `signIn({ maxAge: 300 })`).
449
+ */
450
+ readonly create: (input: ApiKeyInput) => Promise<SynqResult<NewApiKey>>;
451
+ /** Revokes a key at once. */
452
+ readonly revoke: (id: string) => Promise<SynqResult<null>>;
453
+ };
277
454
  /** Each browser plugin's actions, by its id (`usePlugin` types them). */
278
455
  readonly plugins: Readonly<Record<string, unknown>>;
279
456
  }
@@ -310,6 +487,56 @@ interface SignInButtonProps extends SignInOptions, SynqStyleProps {
310
487
  /** A button that sends the browser to sign in, through the app's route mount. */
311
488
  export declare function SignInButton({ unstyled, className, children, ...options }: SignInButtonProps): ReactNode;
312
489
  //#endregion
490
+ //#region src/components/SignInForm.d.ts
491
+ /** What `SignInForm` takes: which ways to offer, where it starts, and its look. */
492
+ interface SignInFormProps extends UseSignInOptions, SynqStyleProps {
493
+ /** The ways to offer, of those the app has on: all three by default. */
494
+ readonly methods?: readonly ChallengeMethod[];
495
+ /** Which screen it opens on. People can switch, where passwords are on. */
496
+ readonly screen?: 'sign-in' | 'sign-up';
497
+ }
498
+ /** What each part of the form takes: the sign-in it works on, and its look. */
499
+ interface SignInPartProps extends SynqStyleProps {
500
+ /** The sign-in, from `useSignIn()`. */
501
+ readonly signIn: UseSignIn;
502
+ }
503
+ /**
504
+ * Sign-in on the app's own page: a passkey where the browser takes the
505
+ * issuer's, then an emailed code or a password, and the code or the new
506
+ * address's proof after. Whatever only the hosted page can do, it goes
507
+ * there for.
508
+ */
509
+ export declare function SignInForm({ methods, screen: initialScreen, returnTo, onSignedIn, unstyled, className }: SignInFormProps): ReactNode;
510
+ /** The button for a passkey: the brand's, or with `related` its own host's on its custom domain. */
511
+ export declare function PasskeyButton({ signIn, related, children, unstyled, className }: SignInPartProps & {
512
+ readonly related?: boolean;
513
+ readonly children?: ReactNode;
514
+ }): ReactNode;
515
+ /**
516
+ * An email address and a password: signs in, or with `screen="sign-up"`
517
+ * makes an account. With `emailCode`, it offers a code by email for the
518
+ * same address too.
519
+ */
520
+ export declare function PasswordForm({ signIn, screen, emailCode, autofill, unstyled, className }: SignInPartProps & {
521
+ readonly screen?: 'sign-in' | 'sign-up';
522
+ readonly emailCode?: boolean;
523
+ /** Offer passkeys in the address field as the person types, where the browser can. */
524
+ readonly autofill?: boolean;
525
+ }): ReactNode;
526
+ /** An emailed code: the address to send it to, then the code it held. */
527
+ export declare function EmailCodeForm({ signIn, autofill, unstyled, className }: SignInPartProps & {
528
+ /** Offer passkeys in the address field as the person types, where the browser can. */
529
+ readonly autofill?: boolean;
530
+ }): ReactNode;
531
+ /** The code that proves a new account's address, or "Skip for now" where the brand allows. */
532
+ export declare function VerifyEmailForm({ signIn, unstyled, className }: SignInPartProps): ReactNode;
533
+ /** What `OneTimeCodeInput` takes: its label, its look, and any input attribute. */
534
+ interface OneTimeCodeInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className'>, SynqStyleProps {
535
+ readonly label: ReactNode;
536
+ }
537
+ /** The field for an emailed code, named `code`: digits, filled from the message where the device can. */
538
+ export declare function OneTimeCodeInput({ label, unstyled, className, ...input }: OneTimeCodeInputProps): ReactNode;
539
+ //#endregion
313
540
  //#region src/components/UserButton.d.ts
314
541
  /** What `UserButton` takes: where its account link goes, and its look. */
315
542
  interface UserButtonProps extends SynqStyleProps {
@@ -325,11 +552,15 @@ interface UserButtonProps extends SynqStyleProps {
325
552
  export declare function UserButton({ unstyled, className, accountHref }: UserButtonProps): ReactNode;
326
553
  //#endregion
327
554
  //#region src/components/AccountManager.d.ts
328
- /** One part of `AccountManager`: the profile, the ways to sign in, or the sessions. */
329
- type AccountSection = 'profile' | 'accounts' | 'sessions';
555
+ /** One part of `AccountManager`: the profile, the ways to sign in, the sessions, or the API keys. */
556
+ type AccountSection = 'profile' | 'accounts' | 'sessions' | 'apiKeys';
330
557
  /** What `AccountManager` takes: which sections it shows, and its look. */
331
558
  interface AccountManagerProps extends SynqStyleProps {
332
- /** The parts it shows, in this order: all three by default. */
559
+ /**
560
+ * The parts it shows, in this order: all four by default. The API keys
561
+ * show only where one of the brand's APIs lets people make keys, or the
562
+ * person has some.
563
+ */
333
564
  readonly sections?: readonly AccountSection[];
334
565
  }
335
566
  /** Manage the signed-in person's account. Nothing while nobody is signed in. */
@@ -341,4 +572,4 @@ type WalletManagerProps = SynqStyleProps;
341
572
  /** Manage the signed-in person's wallets. Nothing while nobody is signed in. */
342
573
  export declare function WalletManager({ unstyled, className }: WalletManagerProps): ReactNode;
343
574
  //#endregion
344
- export type { AccountManagerProps, AccountSection, AccountSession, LinkedAccount, Profile, ProfileUpdate, SessionStatus, SignInButtonProps, SignInMethod, SignInMethods, SignInOptions, SynqClientPlugin, SynqMessages, SynqMountRequest, SynqProviderProps, SynqResult, SynqSessionInfo, SynqStyleProps, SynqUser, UseAccountData, UseAuth, UseSession, UseUser, UserButtonProps, UserMetadata, Wallet, WalletManagerProps };
575
+ export type { AccountManagerProps, AccountSection, AccountSession, ApiKey, ApiKeyInput, KeyableApi, LinkedAccount, NewApiKey, OneTimeCodeInputProps, Profile, ProfileUpdate, SessionStatus, SignInButtonProps, SignInFormProps, SignInHandover, SignInMethod, SignInMethods, SignInOptions, SignInPartProps, SignInProblem, SignInStep, SynqClientPlugin, SynqMessages, SynqMountRequest, SynqProviderProps, SynqResult, SynqSessionInfo, SynqStyleProps, SynqUser, UseAccountData, UseAuth, UseSession, UseSignIn, UseSignInOptions, UseUser, UserButtonProps, UserMetadata, Wallet, WalletManagerProps };