@synqauth/react 0.2.0 → 0.4.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/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
- import { InputHTMLAttributes, ReactNode } from "react";
3
- import { AccountComponents, ChallengeMethod, ChallengeProblem, DeniedReason, RedirectReason, SynqError } from "@synqauth/js";
2
+ import { CSSProperties, InputHTMLAttributes, KeyboardEvent, MouseEvent, PointerEvent, ReactNode, RefObject } from "react";
3
+ import { AccountComponents, AvatarInfo, AvatarPerson, ChallengeMethod, ChallengeProblem, DeniedReason, RedirectReason, SignInMethodInfo, SynqError } from "@synqauth/js";
4
4
  //#region src/sign-in.d.ts
5
5
  /** Where a sign-in is: what the form shows. */
6
6
  type SignInStep = 'start' | 'code' | 'verify-email' | 'done';
@@ -20,6 +20,16 @@ interface SignInProblem {
20
20
  readonly message: string;
21
21
  /** Seconds to wait first, when Synq said. */
22
22
  readonly retryAfter?: number;
23
+ /**
24
+ * For `unavailable` (AERO-2757): what the route mount answered, so the
25
+ * words can tell the person's connection from the app's or Synq's side:
26
+ * `network_error` when nothing answered, else the mount's own code
27
+ * (`forbidden`, `origin_mismatch`, `server_error`…), with its status.
28
+ */
29
+ readonly cause?: string;
30
+ readonly status?: number;
31
+ /** The request's id, to quote to support, when the mount said. */
32
+ readonly requestId?: string;
23
33
  }
24
34
  /** What only the hosted page can do for this sign-in, and what to take there. */
25
35
  interface SignInHandover {
@@ -94,6 +104,232 @@ interface UseSignIn {
94
104
  /** One sign-in on the app's own screens, through the route mount's `sign-in/flow`. */
95
105
  export declare function useSignIn(options?: UseSignInOptions): UseSignIn;
96
106
  //#endregion
107
+ //#region src/types.d.ts
108
+ /** A call's outcome: its data, or the `SynqError` it failed with (branch on `error.code`). */
109
+ type SynqResult<T> = {
110
+ readonly data: T;
111
+ readonly error: null;
112
+ } | {
113
+ readonly data: null;
114
+ readonly error: SynqError;
115
+ };
116
+ /** A signed-in person, from the ID token Synq issued at sign-in. */
117
+ interface SynqUser {
118
+ /** The user's id at the brand (`usr_…`). */
119
+ readonly sub: string;
120
+ readonly email?: string;
121
+ readonly email_verified?: boolean;
122
+ readonly name?: string;
123
+ readonly preferred_username?: string;
124
+ /** The picture they chose: none, and your app shows its own default, from `avatar_seed`. */
125
+ readonly picture?: string;
126
+ /**
127
+ * Where `picture` came from (AERO-2700): `none`, `upload`, `url` or
128
+ * `linked:<provider>`. Absent from a token issued before Synq said.
129
+ */
130
+ readonly picture_source?: string;
131
+ /**
132
+ * What to draw their default avatar from (AERO-2704): theirs in every app
133
+ * of the brand, for as long as the account lasts. Absent from a token
134
+ * issued before seeds, where `sub` stands in (`useAvatar`).
135
+ */
136
+ readonly avatar_seed?: string;
137
+ /** The brand's own default avatar for them, where it uploaded a set. */
138
+ readonly default_picture?: string;
139
+ /** When they signed in, in seconds since the epoch. */
140
+ readonly auth_time?: number;
141
+ /** How they signed in: `pwd`, `otp`, `wallet`, `hwk`, `fed`... */
142
+ readonly amr?: readonly string[];
143
+ /**
144
+ * The way in of the sign-in this session came from, when the app turned
145
+ * its sign-in claims on (`sign_in_claims`, AERO-2691): `google`, `wallet`,
146
+ * `email`, `password`, `passkey`…
147
+ */
148
+ readonly sign_in_method?: string;
149
+ /** For a wallet sign-in, the address that signed and the wallet app's name, when it said. */
150
+ readonly sign_in_wallet?: {
151
+ readonly address: string;
152
+ readonly app: string | null;
153
+ };
154
+ /** Whether that sign-in made the account: true in its first ID token only. */
155
+ readonly new_user?: boolean;
156
+ readonly [claim: string]: unknown;
157
+ }
158
+ /** The way this browser last signed in to the app (AERO-2691): never who, never an address. */
159
+ interface LastUsedMethod {
160
+ /** The method's id: one of `SignInMethods`, or one Synq adds later. */
161
+ readonly method: SignInMethod | (string & Record<never, never>);
162
+ /** For a wallet, the wallet app's name as it announced itself (`Phantom`). */
163
+ readonly walletApp?: string;
164
+ }
165
+ /** A sign-in that just finished (AERO-2691): what `useSignInEvent()` tells the page once. */
166
+ interface SignInEvent {
167
+ /** This sign-in's own id: two sign-ins the same way can share `at`, never this. */
168
+ readonly id: string;
169
+ /** Whether it made the account: their first sign-in. */
170
+ readonly isNewUser: boolean;
171
+ /** The way in: one of `SignInMethods`, or one Synq adds later. */
172
+ readonly method: SignInMethod | (string & Record<never, never>);
173
+ /** For a wallet sign-in, the address that signed and its wallet app, when it said. */
174
+ readonly wallet: {
175
+ readonly address: string;
176
+ readonly app: string | null;
177
+ } | null;
178
+ /** When they signed in, in seconds since the epoch. */
179
+ readonly at: number;
180
+ }
181
+ /** Who is signed in, as the app's server tells the browser: `await synq.getSession()`, or the route mount's `session`. */
182
+ interface SynqSessionInfo {
183
+ readonly user: SynqUser | null;
184
+ /** When the session's main access token expires, in seconds since the epoch. */
185
+ readonly expiresAt: number | null;
186
+ /**
187
+ * The way this browser last signed in to the app, signed in or out, from
188
+ * the app's sign-in claims (AERO-2691). Absent from a mount that doesn't
189
+ * keep it.
190
+ */
191
+ readonly lastUsed?: LastUsedMethod | null;
192
+ /** The sign-in that just finished, until the page hears of it (AERO-2691). */
193
+ readonly event?: SignInEvent | null;
194
+ }
195
+ /**
196
+ * What the brand keeps for the person (the account API's `metadata`).
197
+ * Augment it to type what the app reads:
198
+ *
199
+ * ```ts
200
+ * declare module '@synqauth/react' {
201
+ * interface UserMetadata {
202
+ * plan?: 'free' | 'pro'
203
+ * }
204
+ * }
205
+ * ```
206
+ */
207
+ interface UserMetadata {
208
+ readonly [field: string]: unknown;
209
+ }
210
+ /**
211
+ * The ways a brand signs people in, as `synq_method` names them: the hosted
212
+ * page goes straight to one. A brand's own connection joins them the same way:
213
+ *
214
+ * ```ts
215
+ * declare module '@synqauth/react' {
216
+ * interface SignInMethods {
217
+ * okta: true
218
+ * }
219
+ * }
220
+ * ```
221
+ */
222
+ interface SignInMethods {
223
+ readonly password: true;
224
+ readonly email: true;
225
+ readonly passkey: true;
226
+ readonly wallet: true;
227
+ readonly google: true;
228
+ readonly github: true;
229
+ readonly microsoft: true;
230
+ readonly apple: true;
231
+ readonly facebook: true;
232
+ readonly x: true;
233
+ readonly discord: true;
234
+ readonly telegram: true;
235
+ readonly matrica: true;
236
+ }
237
+ /** One of the brand's sign-in methods. */
238
+ type SignInMethod = Extract<keyof SignInMethods, string>;
239
+ /** How to send the person to sign in. */
240
+ interface SignInOptions {
241
+ /** Where to come back to: a path on the app, the current page by default. */
242
+ readonly returnTo?: string;
243
+ /** Go straight to one sign-in method. */
244
+ readonly method?: SignInMethod;
245
+ /** Fills in their email address. */
246
+ readonly loginHint?: string;
247
+ /** `create` opens the hosted page on its sign-up screen. */
248
+ readonly prompt?: 'login' | 'none' | 'consent' | 'select_account' | 'create';
249
+ /** Ask them to sign in again when their last sign-in is older than this many seconds. */
250
+ readonly maxAge?: number;
251
+ /** Offer a new passkey once they've signed in again. */
252
+ readonly intent?: 'add-passkey';
253
+ }
254
+ /** The person's profile at the brand. */
255
+ type Profile = AccountComponents['schemas']['Profile'];
256
+ /** Changes to it: only the fields given change, and `null` clears one. */
257
+ type ProfileUpdate = AccountComponents['schemas']['ProfileUpdate'];
258
+ /** A Solana wallet the person signs in with. */
259
+ type Wallet = AccountComponents['schemas']['Wallet'];
260
+ /** A way the person signs in: their email address, a wallet, a Google account... */
261
+ type LinkedAccount = AccountComponents['schemas']['Identity'];
262
+ /** One of the person's passkeys: `synced` when it lives in a password manager across their devices. */
263
+ type Passkey = AccountComponents['schemas']['Passkey'];
264
+ /** One of the person's sessions at the brand, on some device. */
265
+ type AccountSession = AccountComponents['schemas']['Session'];
266
+ /** One of the person's API keys for the brand's APIs (AERO-1693): never the key itself. */
267
+ type ApiKey = AccountComponents['schemas']['UserApiKey'];
268
+ /** A key just made: `key` is the key itself, in this answer only. */
269
+ type NewApiKey = AccountComponents['schemas']['NewUserApiKey'];
270
+ /** What making an API key takes: the API's identifier, a name, its scopes, an expiry if any. */
271
+ type ApiKeyInput = AccountComponents['schemas']['UserApiKeyCreate'];
272
+ /** One of the brand's APIs the person may make keys for, with the scopes it defines. */
273
+ type KeyableApi = AccountComponents['schemas']['KeyableApi'];
274
+ /** A way to sign in the person can add (AERO-2080): what `useAuth().link` takes, with its name. */
275
+ type LinkableMethod = AccountComponents['schemas']['LinkableMethod'];
276
+ /** How `signOut` signs the person out. */
277
+ interface SignOutOptions {
278
+ /**
279
+ * Stay on the page (AERO-2684): the app's server ends the brand's session
280
+ * itself and the browser goes nowhere. For an app whose server holds a
281
+ * secret (`@synqauth/nextjs` does). By default the browser goes to the
282
+ * brand's sign-out page, which asks first.
283
+ */
284
+ readonly stay?: boolean;
285
+ }
286
+ //#endregion
287
+ //#region src/username.d.ts
288
+ /** Where the draft stands: nothing typed, being checked, free, taken, not a username, or theirs already. */
289
+ type UsernameStatus = 'idle' | 'checking' | 'available' | 'taken' | 'format' | 'same';
290
+ /** What to say of the draft: its status, or for `format` which rule it breaks. */
291
+ type UsernameCode = 'same' | 'available' | 'taken' |
292
+ /** Fewer than 3 characters. */
293
+ 'too_short' |
294
+ /** More than 64. */
295
+ 'too_long' |
296
+ /** Something other than a letter, digit, `.`, `_` or `-`. */
297
+ 'characters' |
298
+ /** Starting or ending with `.`, `_` or `-`. */
299
+ 'edges';
300
+ /** A username the brand takes: 3 to 64 of `a-z 0-9 . _ -`, starting and ending with a letter or digit. */
301
+ export declare const USERNAME_FORMAT: RegExp;
302
+ /** The draft as the brand keeps usernames: no spaces around it, no leading at sign, lowercase. */
303
+ export declare function normalizeUsername(value: string): string;
304
+ /** Which format rule `username` breaks, or null when it is one. */
305
+ export declare function usernameFormatCode(username: string): Extract<UsernameCode, 'too_short' | 'too_long' | 'characters' | 'edges'> | null;
306
+ /** What `useUsernameField()` answers. */
307
+ interface UseUsernameField {
308
+ /** The username being typed, as the brand would keep it; their own until they type. */
309
+ readonly draft: string;
310
+ /** Takes what they typed (or a suggestion they tapped), and checks it again. */
311
+ readonly setDraft: (value: string) => void;
312
+ readonly status: UsernameStatus;
313
+ /** What to say of it, by code: `messages.usernameCodes` has the words. */
314
+ readonly code: UsernameCode | null;
315
+ /** That, in the provider's words (`<SynqProvider messages>`), or null when there is nothing to say. */
316
+ readonly message: string | null;
317
+ /** When it is taken: up to three free ones like it, from the brand, to offer. */
318
+ readonly suggestions: readonly string[];
319
+ /**
320
+ * Makes the draft their username. `same` answers the profile as it is; a
321
+ * draft that isn't a username answers `invalid_username`; one taken since
322
+ * the check answers `username_taken`, and the field shows it with its
323
+ * suggestions.
324
+ */
325
+ readonly save: () => Promise<SynqResult<Profile>>;
326
+ readonly saving: boolean;
327
+ /** Why the last check or save failed outright (the network, say). */
328
+ readonly error: SynqError | null;
329
+ }
330
+ /** A username field for the signed-in person (AERO-2693): live availability, suggestions and saving. */
331
+ export declare function useUsernameField(): UseUsernameField;
332
+ //#endregion
97
333
  //#region src/messages.d.ts
98
334
  /** Every word the components show, for `<SynqProvider messages>`; any left out stay English. */
99
335
  interface SynqMessages {
@@ -111,6 +347,14 @@ interface SynqMessages {
111
347
  readonly name: string;
112
348
  readonly username: string;
113
349
  readonly waysToSignIn: string;
350
+ /** Choosing their picture (AERO-2700): uploading one. */
351
+ readonly uploadPicture: string;
352
+ readonly uploadingPicture: string;
353
+ /** Using a linked account's picture: "Use my Google picture". */
354
+ readonly usePictureFrom: (provider: string) => string;
355
+ /** The same, for one of two or more accounts at a provider: "Use my GitHub picture (@ana)". */
356
+ readonly usePictureFromAccount: (provider: string, account: string) => string;
357
+ readonly removePicture: string;
114
358
  readonly remove: string;
115
359
  /** The label of the button that stops a way signing them in. */
116
360
  readonly removeAccount: (provider: string, subject: string) => string;
@@ -193,111 +437,174 @@ interface SynqMessages {
193
437
  readonly signInProblems: Readonly<Partial<Record<SignInProblem['code'], string>>>;
194
438
  /** Why this person can't sign in here, by `SignInProblem.reason`. */
195
439
  readonly signInRefusals: Readonly<Partial<Record<DeniedReason, string>>>;
440
+ /** The button that adds a wallet: Synq asks them to sign in again, then for the wallet. */
441
+ readonly connectWallet: string;
442
+ /**
443
+ * The mark on a way to sign in they proved is theirs: every wallet (by a
444
+ * signature) and provider account, and their email address once verified.
445
+ */
446
+ readonly verified: string;
447
+ /** When a wallet or a way to sign in was added. */
448
+ readonly connectedOn: (date: string) => string;
449
+ readonly copyAddress: (address: string) => string;
450
+ readonly removeWallet: (name: string) => string;
451
+ /** The question before a wallet goes. */
452
+ readonly removeWalletTitle: string;
453
+ readonly removeWalletBody: string;
454
+ /** The question before a way to sign in goes. */
455
+ readonly removeAccountTitle: string;
456
+ readonly removeAccountBody: string;
457
+ /** The heading over the ways they could add. */
458
+ readonly addWayToSignIn: string;
459
+ /** Under that heading: adding one asks them to sign in again first. */
460
+ readonly signInAgainToAdd: string;
461
+ /** The label of the button that adds a way to sign in. */
462
+ readonly linkMethod: (provider: string) => string;
463
+ readonly connect: string;
464
+ readonly passkeys: string;
465
+ /** What passkeys are for, under the heading. */
466
+ readonly passkeysHint: string;
467
+ readonly noPasskeys: string;
468
+ readonly addPasskey: string;
469
+ /** A passkey's name: when it was made. */
470
+ readonly passkeyMade: (date: string) => string;
471
+ /** The mark on a passkey that a password manager keeps on all their devices. */
472
+ readonly synced: string;
473
+ readonly removePasskey: (name: string) => string;
474
+ readonly deleteAccount: string;
475
+ /**
476
+ * What deleting the account does, beside the button and in the question
477
+ * before it. It promises no way back: asking signs them out everywhere,
478
+ * and unless the brand lets a sign-in cancel it, they can't sign in again.
479
+ */
480
+ readonly deleteAccountBody: string;
481
+ /** The word they type to confirm the deletion. */
482
+ readonly deleteConfirmWord: string;
483
+ readonly typeToConfirm: (word: string) => string;
484
+ readonly deletionScheduled: (date: string) => string;
485
+ readonly keepAccount: string;
486
+ /** Why a change needs them to sign in again: their last sign-in isn't recent enough. */
487
+ readonly freshSignInNeeded: string;
488
+ /** What `useUsernameField()` says of the draft, by its `code` (AERO-2693). */
489
+ readonly usernameCodes: Readonly<Record<UsernameCode, (username: string) => string>>;
490
+ /** `<AccountPanel>` (AERO-2696): the trigger's label once signed in, with their first name when known. */
491
+ readonly yourProfile: (name: string | null) => string;
492
+ readonly close: string;
493
+ readonly back: string;
494
+ /** A way to sign in, on its button: "Continue with Google". */
495
+ readonly continueWith: (method: string) => string;
496
+ /** The wallet method with nothing connected. */
497
+ readonly continueWithWallet: string;
498
+ /** Under the connected wallet's button: what signing in with it takes. */
499
+ readonly walletSignInHint: string;
500
+ readonly useAnotherWallet: string;
501
+ readonly pickWallet: string;
502
+ /** Under a wallet in the picker. */
503
+ readonly walletDetected: string;
504
+ readonly noWalletHere: string;
505
+ /** The button under that: reload once a wallet is installed. */
506
+ readonly reload: string;
507
+ /** Signing in on the brand's hosted page instead, where only it can. */
508
+ readonly useSignInPage: string;
509
+ readonly moreOptions: string;
510
+ readonly fewerOptions: string;
511
+ /** The mark on the way this browser last signed in. */
512
+ readonly lastUsedMethod: string;
513
+ /** Where a provider's sign-in can't run: another app's built-in browser. */
514
+ readonly openInBrowser: (method: string) => string;
515
+ /** A sign-in under way: a wallet's prompt, a popup, a redirect, a passkey. */
516
+ readonly approveIn: (walletApp: string | null) => string;
517
+ readonly finishInWindow: (method: string) => string;
518
+ readonly opening: (method: string) => string;
519
+ readonly usePasskeyNow: string;
520
+ readonly signingYouIn: string;
521
+ /** A sign-in that couldn't reach the app's server or Synq. */
522
+ readonly signInOffline: string;
523
+ readonly signInFailed: string;
524
+ /** `<SessionError>`: the app couldn't tell whether anyone is signed in. */
525
+ readonly sessionUnknown: string;
526
+ readonly retry: string;
527
+ /**
528
+ * Sign-in broke on the app's or Synq's side (AERO-2757): a server error, a
529
+ * route mount set up wrong, an app Synq refuses. Never the person's doing.
530
+ */
531
+ readonly signInBroken: string;
532
+ /** Synq's sign-in is down for a moment (`temporarily_unavailable`). */
533
+ readonly signInDown: string;
534
+ /** The sign-in expired, or finished in another tab (`unknown_state`). */
535
+ readonly signInStale: string;
536
+ /** The person left the sign-in before it finished. */
537
+ readonly signInCanceled: string;
538
+ /** This account can't use this app (a refusal that came back from the hosted page). */
539
+ readonly signInRefused: string;
540
+ /** Synq couldn't read the person's wallets for the app's token gates. */
541
+ readonly walletsUnchecked: string;
542
+ /** The app's server takes changes from another address than the page's (`origin_mismatch`). */
543
+ readonly signInOtherOrigin: string;
544
+ readonly tryAgain: string;
545
+ readonly securedBy: string;
546
+ /** A first sign-in's greeting, with their first name when known. */
547
+ readonly youreIn: (name: string | null) => string;
548
+ readonly makeItYours: string;
549
+ /** A returning sign-in's greeting. */
550
+ readonly goodToSeeYou: (name: string | null) => string;
551
+ /** How they signed in: "Signed in with Google", "Signed in with 7xKX…3fQ". */
552
+ readonly signedInWith: (way: string) => string;
553
+ readonly viewProfile: string;
554
+ readonly edit: string;
555
+ readonly changePicture: string;
556
+ readonly yourPicture: string;
557
+ /** Beside the large avatar on the picture screen. */
558
+ readonly pictureHint: string;
559
+ /** The app's own default, drawn for them. */
560
+ readonly defaultPicture: string;
561
+ readonly defaultPictureHint: string;
562
+ readonly uploadPhoto: string;
563
+ readonly uploadAnotherPhoto: string;
564
+ readonly pictureTypes: string;
565
+ readonly uploadedPicture: string;
566
+ /** A linked account's picture: "Your Google picture". */
567
+ readonly pictureFrom: (provider: string) => string;
568
+ /** The mark on the picture showing now. */
569
+ readonly showing: string;
570
+ readonly accountSettings: string;
571
+ readonly signOutFailed: string;
572
+ /** Signing out failed while offline: the person is still signed in. */
573
+ readonly signOutOffline: string;
574
+ readonly usernamePlaceholder: string;
575
+ readonly checking: string;
576
+ /** The connected wallet, when it isn't on their profile. */
577
+ readonly walletNotOnProfile: (address: string) => string;
578
+ readonly add: string;
579
+ readonly connected: string;
580
+ readonly signedInWithThis: string;
581
+ readonly addWallet: string;
582
+ /** Under a wallet in the add picker. */
583
+ readonly connectAndAdd: string;
584
+ readonly walletOptions: (name: string) => string;
585
+ readonly copyAddressShort: string;
586
+ readonly removeFromProfile: string;
587
+ readonly removeWalletQuestion: (name: string) => string;
588
+ readonly keep: string;
589
+ readonly confirmItsYou: string;
590
+ readonly onlyWayIn: string;
591
+ readonly walletInUse: string;
592
+ /** A wallet on another profile that can't move here (its only way in, or the brand doesn't move them). */
593
+ readonly walletOnOtherProfile: string;
594
+ readonly moveHere: string;
595
+ readonly linked: string;
596
+ readonly link: string;
597
+ readonly notLinked: string;
598
+ /** The session ended while the panel was open, and not by its Sign out. */
599
+ readonly signedOutAgain: string;
600
+ /** Confirming it's them, they signed in as someone else, who is never let in. */
601
+ readonly notYourAccount: string;
602
+ /** A change that wants a recent sign-in, before "Confirm it's you". */
603
+ readonly confirmToGoOn: string;
196
604
  }
197
605
  /** The components' English words, which an app's own messages override one by one. */
198
606
  export declare const DEFAULT_MESSAGES: SynqMessages;
199
607
  //#endregion
200
- //#region src/types.d.ts
201
- /** A call's outcome: its data, or the `SynqError` it failed with (branch on `error.code`). */
202
- type SynqResult<T> = {
203
- readonly data: T;
204
- readonly error: null;
205
- } | {
206
- readonly data: null;
207
- readonly error: SynqError;
208
- };
209
- /** A signed-in person, from the ID token Synq issued at sign-in. */
210
- interface SynqUser {
211
- /** The user's id at the brand (`usr_…`). */
212
- readonly sub: string;
213
- readonly email?: string;
214
- readonly email_verified?: boolean;
215
- readonly name?: string;
216
- readonly preferred_username?: string;
217
- readonly picture?: string;
218
- /** When they signed in, in seconds since the epoch. */
219
- readonly auth_time?: number;
220
- /** How they signed in: `pwd`, `otp`, `wallet`, `hwk`, `fed`... */
221
- readonly amr?: readonly string[];
222
- readonly [claim: string]: unknown;
223
- }
224
- /** Who is signed in, as the app's server tells the browser: `await synq.getSession()`, or the route mount's `session`. */
225
- interface SynqSessionInfo {
226
- readonly user: SynqUser | null;
227
- /** When the session's main access token expires, in seconds since the epoch. */
228
- readonly expiresAt: number | null;
229
- }
230
- /**
231
- * What the brand keeps for the person (the account API's `metadata`).
232
- * Augment it to type what the app reads:
233
- *
234
- * ```ts
235
- * declare module '@synqauth/react' {
236
- * interface UserMetadata {
237
- * plan?: 'free' | 'pro'
238
- * }
239
- * }
240
- * ```
241
- */
242
- interface UserMetadata {
243
- readonly [field: string]: unknown;
244
- }
245
- /**
246
- * The ways a brand signs people in, as `synq_method` names them: the hosted
247
- * page goes straight to one. A brand's own connection joins them the same way:
248
- *
249
- * ```ts
250
- * declare module '@synqauth/react' {
251
- * interface SignInMethods {
252
- * okta: true
253
- * }
254
- * }
255
- * ```
256
- */
257
- interface SignInMethods {
258
- readonly password: true;
259
- readonly email: true;
260
- readonly passkey: true;
261
- readonly wallet: true;
262
- readonly google: true;
263
- readonly microsoft: true;
264
- readonly discord: true;
265
- }
266
- /** One of the brand's sign-in methods. */
267
- type SignInMethod = Extract<keyof SignInMethods, string>;
268
- /** How to send the person to sign in. */
269
- interface SignInOptions {
270
- /** Where to come back to: a path on the app, the current page by default. */
271
- readonly returnTo?: string;
272
- /** Go straight to one sign-in method. */
273
- readonly method?: SignInMethod;
274
- /** Fills in their email address. */
275
- readonly loginHint?: string;
276
- readonly prompt?: 'login' | 'none' | 'consent' | 'select_account';
277
- /** Ask them to sign in again when their last sign-in is older than this many seconds. */
278
- readonly maxAge?: number;
279
- /** Offer a new passkey once they've signed in again. */
280
- readonly intent?: 'add-passkey';
281
- }
282
- /** The person's profile at the brand. */
283
- type Profile = AccountComponents['schemas']['Profile'];
284
- /** Changes to it: only the fields given change, and `null` clears one. */
285
- type ProfileUpdate = AccountComponents['schemas']['ProfileUpdate'];
286
- /** A Solana wallet the person signs in with. */
287
- type Wallet = AccountComponents['schemas']['Wallet'];
288
- /** A way the person signs in: their email address, a wallet, a Google account... */
289
- type LinkedAccount = AccountComponents['schemas']['Identity'];
290
- /** One of the person's sessions at the brand, on some device. */
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'];
300
- //#endregion
301
608
  //#region src/mount.d.ts
302
609
  /** Calls the app's route mount: `request('session')`, `request('account/v1/profile', { method: 'PATCH', ... })`. */
303
610
  type SynqMountRequest = (path: string, init?: RequestInit) => Promise<Response>;
@@ -311,10 +618,90 @@ interface SynqClientPlugin<Id extends string = string, Actions = unknown> {
311
618
  actions(request: SynqMountRequest): Actions;
312
619
  }
313
620
  //#endregion
621
+ //#region src/redirect.d.ts
622
+ /** What left the page: a sign-in, a link, or signing the person in again. */
623
+ type RedirectKind = 'sign-in' | 'link' | 'reauthenticate';
624
+ /** The outcome of a sign-in or link that left the page, once the app is back on it. */
625
+ interface RedirectResult {
626
+ /**
627
+ * What left the page: `sign-in` (`useAuth().signIn`), `link` (`useLink`,
628
+ * `useAuth().link`) or `reauthenticate` (`useAuth().reauthenticate`).
629
+ */
630
+ readonly kind: RedirectKind;
631
+ /** The method it went for, if one was named. */
632
+ readonly method: SignInMethod | null;
633
+ /** What the app asked to keep (`keep`), as it gave it. */
634
+ readonly kept: unknown;
635
+ /** Why it didn't finish, as the app's callback said (`synq_error`), or null. */
636
+ readonly error: SynqError | null;
637
+ /** A link's: the ways in the person had before it. */
638
+ readonly before: readonly string[] | null;
639
+ }
640
+ //#endregion
314
641
  //#region src/store.d.ts
315
642
  /** `error` when the app couldn't be asked who is signed in, and nothing is known yet: the provider asks again, waiting longer each time. */
316
643
  type SessionStatus = 'loading' | 'signed-in' | 'signed-out' | 'error';
317
644
  //#endregion
645
+ //#region src/components/styling.d.ts
646
+ /** What every component takes for its look: Synq's classes, or `unstyled`. */
647
+ interface SynqStyleProps {
648
+ /** The same elements, roles and labels, with none of Synq's classes: the app styles them. */
649
+ readonly unstyled?: boolean;
650
+ /** Added to the component's outer element. */
651
+ readonly className?: string;
652
+ }
653
+ //#endregion
654
+ //#region src/components/Avatar.d.ts
655
+ /**
656
+ * Where a picture came from, as `sources` names it: `linked` is any linked
657
+ * account's. A token says `linked:<provider>` only with the
658
+ * `linked_accounts` scope, and `linked` without it; the profile always does.
659
+ */
660
+ type AvatarSource = 'upload' | 'url' | 'linked' | `linked:${string}`;
661
+ /**
662
+ * What shows for someone with no picture to show: Synq's `aura` (the
663
+ * default) or `monogram`, or the app's own, from the person's seed: an
664
+ * image's URL, or an element. Nothing (null) goes on to the brand's default.
665
+ */
666
+ type AvatarFallback = 'aura' | 'monogram' | ((seed: string, user: AvatarPerson) => ReactNode | string);
667
+ /** How an app draws avatars: once for the app (`SynqProvider avatar`), or per component. */
668
+ interface AvatarSettings {
669
+ /** What shows for someone with no picture to show: `aura` by default. */
670
+ readonly fallback?: AvatarFallback;
671
+ /**
672
+ * The pictures that show, by where they came from: all of them by
673
+ * default. `['upload']` shows only pictures people uploaded here, and the
674
+ * app's own art over their linked accounts' pictures.
675
+ */
676
+ readonly sources?: readonly AvatarSource[];
677
+ }
678
+ /** What `useAvatar()` answers: what an avatar is drawn from (`avatarOf` in @synqauth/js). */
679
+ type UseAvatar = AvatarInfo;
680
+ /**
681
+ * What the avatar of `user` is drawn from: the signed-in person by default,
682
+ * with their profile's picture once the account API was read, so a picture
683
+ * they just chose shows at once. `user` is anyone as Synq names them: an ID
684
+ * token's claims, or the management API's user (`id`, `username`).
685
+ */
686
+ export declare function useAvatar(user?: AvatarPerson | null): UseAvatar | null;
687
+ /** What `Avatar` takes: whose, how large, and how it falls back. */
688
+ interface AvatarProps extends SynqStyleProps, AvatarSettings {
689
+ /** Whose: the signed-in person by default, or anyone as Synq names them (`useAvatar`). */
690
+ readonly user?: AvatarPerson | null;
691
+ /**
692
+ * Its width and height in pixels: 32 by default. Styled, it sets
693
+ * `--synq-avatar-size`, which Synq's stylesheet sizes it by; your own CSS
694
+ * still wins.
695
+ */
696
+ readonly size?: number;
697
+ }
698
+ /**
699
+ * A person's avatar: their picture, or the app's default for them. Drawn
700
+ * for the eye alone (`alt=""`, `aria-hidden`): their name beside it says
701
+ * who it is. Nothing while there is nobody.
702
+ */
703
+ export declare function Avatar({ user, size, fallback, sources, unstyled, className }: AvatarProps): ReactNode;
704
+ //#endregion
318
705
  //#region src/provider.d.ts
319
706
  /** What `SynqProvider` takes: the server's session, the route mount, the words and the plugins. */
320
707
  interface SynqProviderProps {
@@ -339,13 +726,56 @@ interface SynqProviderProps {
339
726
  readonly navigate?: (url: string) => void;
340
727
  /** The components' words, in the app's language: any left out are English. */
341
728
  readonly messages?: Partial<SynqMessages>;
729
+ /**
730
+ * How the app draws avatars (AERO-2704), once for every component: its
731
+ * own default for someone with no picture (`fallback`, from their seed),
732
+ * and which pictures show (`sources`). A component's `avatar` overrides it.
733
+ */
734
+ readonly avatar?: AvatarSettings;
342
735
  readonly children?: ReactNode;
343
736
  }
344
737
  /** Synq for the components under it. Its route mount and `fetch` are read once, when it mounts. */
345
- export declare function SynqProvider({ initialSession, basePath, plugins, fetch: fetcher, navigate, messages, children }: SynqProviderProps): ReactNode;
738
+ export declare function SynqProvider({ initialSession, basePath, plugins, fetch: fetcher, navigate, messages, avatar, children }: SynqProviderProps): ReactNode;
346
739
  /** A browser plugin's actions, typed by the plugin, which must be one of the provider's `plugins`. */
347
740
  export declare function usePlugin<Actions>(plugin: SynqClientPlugin<string, Actions>): Actions;
348
741
  //#endregion
742
+ //#region src/popup.d.ts
743
+ /** How a provider's sign-in or link opens: `auto` is a popup where one fits, a redirect elsewhere. */
744
+ type SignInDisplay = 'auto' | 'popup' | 'redirect';
745
+ //#endregion
746
+ //#region src/handoff.d.ts
747
+ /** How a sign-in or a link opens, and the app's own state to have back afterwards. */
748
+ interface HandoffOptions {
749
+ /**
750
+ * `redirect` leaves the page and comes back to `returnTo`; `popup` opens
751
+ * one over it, and the page carries on once the person finishes there;
752
+ * `auto` is a popup where one fits (a fine pointer, from 640px wide, a
753
+ * real browser), else a redirect. A popup the browser blocks becomes a
754
+ * redirect by itself.
755
+ */
756
+ readonly display?: SignInDisplay;
757
+ /**
758
+ * Small state of the app's own (at most 4 KB of JSON), kept in this tab
759
+ * across a redirect and handed back by `useRedirectResult().kept`, or by
760
+ * the call's own answer after a popup: the screen the person was on, say.
761
+ */
762
+ readonly keep?: unknown;
763
+ }
764
+ /** A sign-in that finished on this page, in a popup. */
765
+ interface SignInDone {
766
+ /** The method it went straight to, if one was named. */
767
+ readonly method: SignInMethod | null;
768
+ /** What the app asked to keep (`keep`), as it gave it. */
769
+ readonly kept: unknown;
770
+ }
771
+ /** What `useAuth().reauthenticate()` takes: the way to sign in again with, and how it opens. */
772
+ interface ReauthenticateOptions extends HandoffOptions {
773
+ /** The way to sign in again with (`google`, `x`…): the brand's page shows them all without it. */
774
+ readonly method?: SignInMethod;
775
+ /** Where a redirect comes back to: this page by default. */
776
+ readonly returnTo?: string;
777
+ }
778
+ //#endregion
349
779
  //#region src/hooks.d.ts
350
780
  /** What `useSession()` answers: who is signed in, and whether the app said yet. */
351
781
  interface UseSession {
@@ -363,7 +793,7 @@ interface UseSession {
363
793
  }
364
794
  /** Who is signed in. It reads the session alone, and asks the account API nothing. */
365
795
  export declare function useSession(): UseSession;
366
- /** What the account hooks (`useWallets`, `useLinkedAccounts`, `useAccountSessions`) answer: the data, the error, and whether it loaded. */
796
+ /** What the account hooks (`useWallets`, `useLinkedAccounts`, `useAccountSessions`, `usePasskeys`) answer: the data, the error, and whether it loaded. */
367
797
  interface UseAccountData<T> {
368
798
  readonly data: T | null;
369
799
  readonly error: SynqError | null;
@@ -382,6 +812,60 @@ export declare function useAccountSessions(): UseAccountData<readonly AccountSes
382
812
  export declare function useApiKeys(): UseAccountData<readonly ApiKey[]>;
383
813
  /** The brand's APIs that let the person make keys, with the scopes each defines. */
384
814
  export declare function useKeyableApis(): UseAccountData<readonly KeyableApi[]>;
815
+ /** The person's passkeys. */
816
+ export declare function usePasskeys(): UseAccountData<readonly Passkey[]>;
817
+ /**
818
+ * The ways to sign in the person can add, in the brand's order (AERO-2080):
819
+ * each a `method` for `useAuth().link`, with its name. A passkey is listed
820
+ * only when offered; link('passkey') uses the hosted add-passkey intent.
821
+ */
822
+ export declare function useLinkableMethods(): UseAccountData<readonly LinkableMethod[]>;
823
+ /** What `useSignInMethods()` answers: the app's methods, in the brand's order, and whether the brand shows "Last used". */
824
+ interface UseSignInMethods extends UseAccountData<readonly SignInMethodInfo[]> {
825
+ /** Whether the brand marks the way this browser last signed in: off where people share devices. */
826
+ readonly showLastUsed: boolean;
827
+ }
828
+ /**
829
+ * The ways the app's sign-in offers, in the brand's order (AERO-2690), for a
830
+ * sign-in panel of the app's own: each one's id (what `signIn({ method })`
831
+ * takes), kind, name and artwork, whether it finishes on the app's own
832
+ * screens (`in_place`, through `useSignIn()`) and whether it signs in at
833
+ * another site (`popup`). Read from the route mount's `sign-in/methods`
834
+ * once for every component, signed in or out; a method the brand turns off
835
+ * is gone within a minute.
836
+ */
837
+ export declare function useSignInMethods(): UseSignInMethods;
838
+ /**
839
+ * The way this browser last signed in to the app, for a "Last used" mark on
840
+ * it (AERO-2691), or null: the method and, for a wallet, the wallet app's
841
+ * name, never an address or who it was. The app's server keeps it on the
842
+ * app's own host from its sign-in claims (`sign_in_claims` on the app), so
843
+ * it survives signing out and the issuer's cookies. Null where the brand
844
+ * turned "Last used" off, or no longer offers that method here, once
845
+ * `useSignInMethods()` says so.
846
+ */
847
+ export declare function useLastUsedMethod(): LastUsedMethod | null;
848
+ /** What `useSignInEvent()` answers: the sign-in, what the app kept for it, and `consume` to say the page heard of it. */
849
+ type UseSignInEvent = SignInEvent & {
850
+ /**
851
+ * What the app asked to keep (`keep`) when it started this sign-in in a
852
+ * popup or by a redirect (AERO-2692), as it gave it: the screen to come
853
+ * back to, say. Undefined when it kept nothing.
854
+ */
855
+ readonly kept: unknown;
856
+ /** The page heard of it: it's gone, here and on the app's server, so a reload doesn't tell it again. */
857
+ readonly consume: () => void;
858
+ };
859
+ /**
860
+ * The sign-in that just finished, once (AERO-2691): whether it made the
861
+ * account (`isNewUser`, for a first-profile screen or a welcome), how, with
862
+ * which wallet, and what the app kept for it (`kept`). It stays until
863
+ * `consume()`, or five minutes. Needs the app's sign-in claims on
864
+ * (`sign_in_claims`); null otherwise, and when there is none. Null in the
865
+ * server's render and the browser's first too: it is told just after, once
866
+ * the provider has read what a redirect kept for it.
867
+ */
868
+ export declare function useSignInEvent(): UseSignInEvent | null;
385
869
  /** What `useUser()` answers: the person's claims, profile and metadata, and `update` for the profile. */
386
870
  interface UseUser {
387
871
  readonly isLoaded: boolean;
@@ -396,37 +880,122 @@ interface UseUser {
396
880
  readonly error: SynqError | null;
397
881
  /** Changes their profile: only the fields given, `null` clearing one. */
398
882
  readonly update: (changes: ProfileUpdate) => Promise<SynqResult<Profile>>;
883
+ /**
884
+ * Uploads a picture and makes it theirs (AERO-2700): a PNG, JPEG, WebP or
885
+ * GIF, which Synq keeps as a WebP square. A large one is made smaller in
886
+ * the browser first, to at most 1024 px, so a phone’s photo fits the 5 MiB
887
+ * Synq takes. It goes through the app's route mount
888
+ * (`<base>/account/picture`), whose server sends it on to Synq's storage.
889
+ * Offer it only where the profile's `picture_choices` is true.
890
+ */
891
+ readonly uploadPicture: (picture: Blob) => Promise<SynqResult<Profile>>;
892
+ /**
893
+ * Makes a linked account's picture theirs, by its id (`useLinkedAccounts`,
894
+ * `has_picture`): Synq copies it into the brand's files.
895
+ */
896
+ readonly setPictureFrom: (identity: string) => Promise<SynqResult<Profile>>;
897
+ /** Removes their picture: the app's default shows. */
898
+ readonly removePicture: () => Promise<SynqResult<Profile>>;
399
899
  /** Reads the profile and metadata again. */
400
900
  readonly reload: () => Promise<void>;
401
901
  }
402
902
  /** The signed-in person: their ID token's claims at once, then their profile and metadata. */
403
903
  export declare function useUser(): UseUser;
404
- /** What `useAuth()` answers: `signIn`, `signOut`, `link`, and the account's wallets, ways to sign in and sessions. */
904
+ /** What `useAuth()` answers: `signIn`, `signOut`, `link`, and the account's wallets, ways to sign in, sessions, passkeys and deletion. */
405
905
  interface UseAuth {
406
906
  readonly isLoaded: boolean;
407
907
  readonly isSignedIn: boolean;
408
908
  readonly userId: string | null;
409
- /** Sends the browser to sign in, coming back to `returnTo` (this page by default). */
410
- readonly signIn: (options?: SignInOptions) => void;
909
+ /**
910
+ * Signs the person in: by default sends the browser to sign in, coming
911
+ * back to `returnTo` (this page by default), and answers `null` as the
912
+ * page leaves. With `display: 'popup'` (or `'auto'`, a popup where one
913
+ * fits) it opens one over the page, inside the tap, and answers once the
914
+ * session is the new one's, or the error it ended with (`cancelled` when
915
+ * the person closed it). A blocked popup becomes a redirect by itself.
916
+ */
917
+ readonly signIn: (options?: SignInOptions & HandoffOptions) => Promise<SynqResult<SignInDone | null>>;
411
918
  /** Where `signIn` sends the browser: the route mount's `sign-in`. */
412
919
  readonly signInUrl: (options?: SignInOptions) => string;
413
- /** Ends the session in the app and at the brand, then goes where the brand sends the browser. */
414
- readonly signOut: () => Promise<SynqResult<null>>;
920
+ /**
921
+ * Ends the session in the app and at the brand, then goes where the brand
922
+ * sends the browser. With `stay` (AERO-2684), the app's server ends the
923
+ * brand's session itself and the page stays where it is: show the person
924
+ * they're signed out. A failure leaves them signed in, to try again.
925
+ */
926
+ readonly signOut: (options?: SignOutOptions) => Promise<SynqResult<null>>;
415
927
  /**
416
928
  * Adds a way to sign in to the person's account: Synq asks them to sign in
417
- * again, then for `method`, and the browser comes back to `returnTo` (this
418
- * page by default). Answers only when it can't start: the method isn't
419
- * offered, or they have as many ways in as one may.
929
+ * again (unless they signed in within the brand's `link_reauth_window`),
930
+ * then for `method`, and the browser comes back to `returnTo` (this page
931
+ * by default), answering `null` as it leaves, or the problem when it can't
932
+ * start: the method isn't offered, or they have as many ways in as one
933
+ * may. With `display: 'popup'` or `'auto'` it runs in a popup and answers
934
+ * the way in that joined; `cancelled` when none did (closed, "Not now",
935
+ * or one they already had), and `account_switched` when they signed in to
936
+ * the account that has it instead. `useLink(method)` keeps its state for
937
+ * a button.
420
938
  */
421
939
  readonly link: (method: SignInMethod, options?: {
422
940
  returnTo?: string;
423
- }) => Promise<SynqResult<null>>;
941
+ } & HandoffOptions) => Promise<SynqResult<LinkedAccount | null>>;
942
+ /**
943
+ * Signs the person signed in now in again, for a change that wants a
944
+ * recent sign-in (`fresh_sign_in_required`), through the brand's page:
945
+ * with `method` (`google`, `x`…), straight to it. In a popup where one fits
946
+ * (`display: 'auto'`, the default), answering once the session is the
947
+ * fresh one, so the change can be tried again at once; else by a
948
+ * redirect that comes back to `returnTo` (this page), answering `null` as
949
+ * the page leaves. Call it from a tap's own handler, before anything is
950
+ * awaited: a popup opened later may be blocked, and it runs as a redirect.
951
+ * Only the same person counts: anyone else who signs in there is signed
952
+ * out again at once, at the brand too, the session stays as it was, and
953
+ * it answers `other_account`. `cancelled` when they closed the popup;
954
+ * `not_signed_in` with nobody signed in, or once they were signed out
955
+ * before it finished. With a wallet on the profile,
956
+ * `useWalletSignIn().reauthenticate()` from `@synqauth/solana/react` does
957
+ * it with one signature and no page.
958
+ */
959
+ readonly reauthenticate: (options?: ReauthenticateOptions) => Promise<SynqResult<SignInDone | null>>;
424
960
  readonly wallets: {
425
961
  readonly list: () => Promise<SynqResult<readonly Wallet[]>>;
426
962
  /** Names a wallet; `null` takes its name away. */
427
963
  readonly rename: (id: string, label: string | null) => Promise<SynqResult<Wallet>>;
428
964
  /** Makes it their primary wallet, in place of the one that was. */
429
965
  readonly setPrimary: (id: string) => Promise<SynqResult<Wallet>>;
966
+ /** Stops a wallet signing them in; the last way in can't go (`last_sign_in_method`). */
967
+ readonly remove: (id: string) => Promise<SynqResult<null>>;
968
+ /**
969
+ * The issuer and app the page asks for a wallet's sign-in request with
970
+ * (`POST <issuer>/siws/nonce`), from the route mount's `sign-in/wallet`:
971
+ * what `@synqauth/solana/react` signs with. Read once, then kept.
972
+ */
973
+ readonly requestTarget: () => Promise<SynqResult<WalletRequestTarget>>;
974
+ /**
975
+ * Signs in with a wallet's signature over that request (`siws`, as
976
+ * `@synqauth/solana`'s `encodeSiws` writes it), in place: the route
977
+ * mount trades it for the session with no page (AERO-2681), and the
978
+ * session here follows. `reauthenticate` signs the person signed in now
979
+ * in again, for a route that wants a recent sign-in: Synq signs in only
980
+ * them, and refuses another account's wallet, or one nobody has, before
981
+ * anything changes (`other_account`). A signature that didn't
982
+ * count is `wallet_failed` or `wallet_expired` (ask for another
983
+ * request); what only the hosted page can do is `redirect_to_web`.
984
+ */
985
+ readonly signIn: (siws: string, options?: {
986
+ reauthenticate?: boolean;
987
+ returnTo?: string;
988
+ }) => Promise<SynqResult<WalletSignedIn>>;
989
+ /**
990
+ * Adds the wallet that signed `siws` to their account (AERO-2683): one
991
+ * signature, no hosted page. It needs a recent sign-in
992
+ * (`fresh_sign_in_required`). A wallet another account has is
993
+ * `wallet_in_use`: `walletConflict(error)` from `@synqauth/js` says
994
+ * whether it can move here, and `move` moves it.
995
+ */
996
+ readonly add: (siws: string) => Promise<SynqResult<Wallet>>;
997
+ /** Moves a wallet here with the `move_ticket` of a `wallet_in_use` conflict. */
998
+ readonly move: (ticket: string) => Promise<SynqResult<Wallet>>;
430
999
  };
431
1000
  /** The ways they sign in. */
432
1001
  readonly accounts: {
@@ -451,12 +1020,91 @@ interface UseAuth {
451
1020
  /** Revokes a key at once. */
452
1021
  readonly revoke: (id: string) => Promise<SynqResult<null>>;
453
1022
  };
1023
+ readonly passkeys: {
1024
+ readonly list: () => Promise<SynqResult<readonly Passkey[]>>;
1025
+ /** Sends the browser to Synq, which asks them to sign in again and then makes a passkey, coming back to `returnTo` (this page by default). */
1026
+ readonly add: (options?: {
1027
+ returnTo?: string;
1028
+ }) => void;
1029
+ /** Removes a passkey; the last way in can't go (`last_sign_in_method`). */
1030
+ readonly remove: (id: string) => Promise<SynqResult<null>>;
1031
+ };
1032
+ /** Deleting the account: after a grace period, which the profile's `deletion_scheduled_for` ends. */
1033
+ readonly deletion: {
1034
+ /** Asks for the account to be deleted. Someone who owns an org can't (`conflict`) until they hand it over. */
1035
+ readonly schedule: () => Promise<SynqResult<Profile>>;
1036
+ /** Takes the request back while the grace period lasts. */
1037
+ readonly cancel: () => Promise<SynqResult<Profile>>;
1038
+ };
454
1039
  /** Each browser plugin's actions, by its id (`usePlugin` types them). */
455
1040
  readonly plugins: Readonly<Record<string, unknown>>;
456
1041
  }
1042
+ /** Where the page asks for a wallet's sign-in request: the brand's issuer, as the app's client. */
1043
+ interface WalletRequestTarget {
1044
+ readonly issuer: string;
1045
+ readonly clientId: string;
1046
+ }
1047
+ /** A wallet's sign-in in place: who is signed in now, and where the app meant to go. */
1048
+ interface WalletSignedIn {
1049
+ readonly user: SynqUser | null;
1050
+ /** Where the sign-in was to land: the app decides whether to go there. */
1051
+ readonly returnTo: string;
1052
+ }
457
1053
  /** Everything the signed-in person can do. */
458
1054
  export declare function useAuth(): UseAuth;
459
1055
  //#endregion
1056
+ //#region src/link.d.ts
1057
+ /** Where `useLink` is: nothing yet, under way, linked, or failed. */
1058
+ type LinkStatus = 'idle' | 'pending' | 'linked' | 'error';
1059
+ /** What `useLink(method)` answers. */
1060
+ interface UseLink {
1061
+ /**
1062
+ * Adds `method`'s way in: in a popup where one fits (`display: 'auto'`, the
1063
+ * default), else by a redirect that comes back to `returnTo` (this page).
1064
+ * Call it from the tap itself. Answers the way in that joined, `null` as
1065
+ * the page leaves for a redirect, or the error it ended with: `cancelled`
1066
+ * when the person closed the popup, went on without adding it or proved
1067
+ * one they already had, and `account_switched` when they signed in to the
1068
+ * account that has it instead.
1069
+ */
1070
+ readonly link: (options?: {
1071
+ returnTo?: string;
1072
+ } & HandoffOptions) => Promise<SynqResult<LinkedAccount | null>>;
1073
+ /**
1074
+ * `linked` while the person has a way in of this kind, `pending` while one
1075
+ * is being added (a popup open, or a redirect on its way back), `error`
1076
+ * when the last try failed, else `idle`.
1077
+ */
1078
+ readonly status: LinkStatus;
1079
+ /** Their way in of this kind: the one just added, else their newest. */
1080
+ readonly identity: LinkedAccount | null;
1081
+ /**
1082
+ * Why the last try failed; a closed popup is no failure. A redirect's
1083
+ * error shows until the next try, or until one of this kind joins.
1084
+ */
1085
+ readonly problem: SynqError | null;
1086
+ /** Gives up on a popup still open, closing it where the browser lets the page. */
1087
+ readonly cancel: () => void;
1088
+ }
1089
+ /**
1090
+ * Linking one way in from a button of the app's own (AERO-2695): `link()`
1091
+ * from the tap, and the `status` and `identity` to show. A redirect's return
1092
+ * shows here too: the hook reads the way in that joined, or the error the
1093
+ * app's callback came back with.
1094
+ */
1095
+ export declare function useLink(method: SignInMethod): UseLink;
1096
+ /** What `useRedirectResult()` answers: the result, and `consume()` to forget it once handled. */
1097
+ type UseRedirectResult = (RedirectResult & {
1098
+ readonly consume: () => void;
1099
+ }) | null;
1100
+ /**
1101
+ * A sign-in or link that left the page for a redirect (AERO-2692), once the
1102
+ * app is back: what it was, the state the app kept for it (`keep`), and the
1103
+ * error the app's callback came back with, if any. Null until there is one,
1104
+ * and after `consume()`. Who is signed in now is `useSession()`'s.
1105
+ */
1106
+ export declare function useRedirectResult(): UseRedirectResult;
1107
+ //#endregion
460
1108
  //#region src/components/SignedIn.d.ts
461
1109
  /** Shows its children only while someone is signed in. It renders no element of its own. */
462
1110
  export declare function SignedIn({ children }: {
@@ -469,14 +1117,20 @@ export declare function SignedOut({ children }: {
469
1117
  readonly children?: ReactNode;
470
1118
  }): ReactNode;
471
1119
  //#endregion
472
- //#region src/components/styling.d.ts
473
- /** What every component takes for its look: Synq's classes, or `unstyled`. */
474
- interface SynqStyleProps {
475
- /** The same elements, roles and labels, with none of Synq's classes: the app styles them. */
476
- readonly unstyled?: boolean;
477
- /** Added to the component's outer element. */
478
- readonly className?: string;
1120
+ //#region src/components/SessionError.d.ts
1121
+ /** What `SessionError` takes: its look, and its own words in place of the default. */
1122
+ interface SessionErrorProps extends SynqStyleProps {
1123
+ /** What it says, with its own retry: the default line and a Retry button otherwise. */
1124
+ readonly children?: ReactNode;
479
1125
  }
1126
+ /**
1127
+ * Shown only while the app couldn't tell whether anyone is signed in (the
1128
+ * route mount answered an error, or nothing): `<SignedIn>` and
1129
+ * `<SignedOut>` both show nothing then, so a header built on them would
1130
+ * show neither (AERO-2757). It says so, with a one-tap retry, and tells
1131
+ * the app's developers in the console once.
1132
+ */
1133
+ export declare function SessionError({ unstyled, className, children }: SessionErrorProps): ReactNode;
480
1134
  //#endregion
481
1135
  //#region src/components/SignInButton.d.ts
482
1136
  /** What `SignInButton` takes: the sign-in's options, its look and its words. */
@@ -484,7 +1138,12 @@ interface SignInButtonProps extends SignInOptions, SynqStyleProps {
484
1138
  /** Its label: the provider's `messages.signIn` by default. */
485
1139
  readonly children?: ReactNode;
486
1140
  }
487
- /** A button that sends the browser to sign in, through the app's route mount. */
1141
+ /**
1142
+ * A button that sends the browser to sign in, through the app's route mount.
1143
+ * A press shows at once (AERO-2757): busy, and "Taking you to sign in…" while
1144
+ * the browser leaves; a popup sign-in that fails says why beside it, never
1145
+ * silently, and the person who closed it on purpose hears nothing.
1146
+ */
488
1147
  export declare function SignInButton({ unstyled, className, children, ...options }: SignInButtonProps): ReactNode;
489
1148
  //#endregion
490
1149
  //#region src/components/SignInForm.d.ts
@@ -538,33 +1197,49 @@ interface OneTimeCodeInputProps extends Omit<InputHTMLAttributes<HTMLInputElemen
538
1197
  export declare function OneTimeCodeInput({ label, unstyled, className, ...input }: OneTimeCodeInputProps): ReactNode;
539
1198
  //#endregion
540
1199
  //#region src/components/UserButton.d.ts
541
- /** What `UserButton` takes: where its account link goes, and its look. */
1200
+ /** What `UserButton` takes: where its account link goes, how it draws the avatar, and its look. */
542
1201
  interface UserButtonProps extends SynqStyleProps {
543
1202
  /** The app's page with `<AccountManager>`, linked as "Manage account". */
544
1203
  readonly accountHref?: string;
1204
+ /** How its avatar falls back and which pictures show: the provider's `avatar` by default. */
1205
+ readonly avatar?: AvatarSettings;
545
1206
  }
546
1207
  /**
547
1208
  * The signed-in person's picture and name, showing a panel to manage their
548
1209
  * account or sign out: a disclosure (a button that shows and hides it), so
549
1210
  * its link and button are reached with Tab as anywhere else. Escape or a
550
- * click elsewhere hides it. Nothing while nobody is signed in.
1211
+ * click elsewhere hides it. Its menu opens under the trigger and stays on
1212
+ * screen: on a narrow window it moves sideways to keep 8px clear of each edge
1213
+ * (AERO-2734). Nothing while nobody is signed in.
551
1214
  */
552
- export declare function UserButton({ unstyled, className, accountHref }: UserButtonProps): ReactNode;
1215
+ export declare function UserButton({ unstyled, className, accountHref, avatar }: UserButtonProps): ReactNode;
553
1216
  //#endregion
554
1217
  //#region src/components/AccountManager.d.ts
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';
557
- /** What `AccountManager` takes: which sections it shows, and its look. */
1218
+ /** One part of `AccountManager`: the profile, the ways to sign in, the passkeys, the sessions, the API keys, or deleting the account. */
1219
+ type AccountSection = 'profile' | 'accounts' | 'passkeys' | 'sessions' | 'apiKeys' | 'deletion';
1220
+ /** What `AccountManager` takes: which sections it shows, the ways to sign in it offers to add, and its look. */
558
1221
  interface AccountManagerProps extends SynqStyleProps {
559
1222
  /**
560
- * The parts it shows, in this order: all four by default. The API keys
1223
+ * The parts it shows, in this order: all six by default. The API keys
561
1224
  * show only where one of the brand's APIs lets people make keys, or the
562
1225
  * person has some.
563
1226
  */
564
1227
  readonly sections?: readonly AccountSection[];
1228
+ /**
1229
+ * Limits the ways to sign in it offers to add to these. By default it
1230
+ * offers every way the brand turns on for adding, as the account API lists
1231
+ * them (`useLinkableMethods`), and never one the brand doesn't. A way they
1232
+ * already have is left out, except a wallet or a passkey, of which they may
1233
+ * have several. A passkey is offered in the passkeys section when that
1234
+ * shows, else among the ways to add; either button makes one on Synq's
1235
+ * add-passkey page, and only where the brand offers passkeys.
1236
+ */
1237
+ readonly linkMethods?: readonly SignInMethod[];
1238
+ /** How the profile's avatar falls back and which pictures show: the provider's `avatar` by default. */
1239
+ readonly avatar?: AvatarSettings;
565
1240
  }
566
1241
  /** Manage the signed-in person's account. Nothing while nobody is signed in. */
567
- export declare function AccountManager({ unstyled, className, sections }: AccountManagerProps): ReactNode;
1242
+ export declare function AccountManager({ unstyled, className, sections, linkMethods, avatar }: AccountManagerProps): ReactNode;
568
1243
  //#endregion
569
1244
  //#region src/components/WalletManager.d.ts
570
1245
  /** What `WalletManager` takes: its look. */
@@ -572,4 +1247,428 @@ type WalletManagerProps = SynqStyleProps;
572
1247
  /** Manage the signed-in person's wallets. Nothing while nobody is signed in. */
573
1248
  export declare function WalletManager({ unstyled, className }: WalletManagerProps): ReactNode;
574
1249
  //#endregion
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 };
1250
+ //#region src/account-panel.d.ts
1251
+ /** What the panel shows: the ways to sign in, one under way, a first sign-in's onboarding, or the profile. */
1252
+ type AccountPanelView = 'sign-in' | 'progress' | 'onboarding' | 'profile';
1253
+ /** How the panel sits: a popover anchored under its trigger from 640px wide, a bottom sheet below. */
1254
+ type AccountPanelLayout = 'popover' | 'sheet';
1255
+ /** A sign-in the panel started and waits on. */
1256
+ interface AccountPanelPending {
1257
+ /** The method: `google`, `wallet`… */
1258
+ readonly method: string;
1259
+ /** Its name, as people know it: `Google`. */
1260
+ readonly name: string;
1261
+ /**
1262
+ * How it runs: in a `popup` over the page, a `redirect` that leaves it, or
1263
+ * `in-place` (a wallet's signature, a passkey's prompt) with no page at all.
1264
+ */
1265
+ readonly display: 'popup' | 'redirect' | 'in-place';
1266
+ /** For a wallet's signature: the wallet app it waits on, when known. */
1267
+ readonly walletApp: string | null;
1268
+ }
1269
+ /** What `useAccountPanel()` takes. */
1270
+ interface UseAccountPanelOptions {
1271
+ /**
1272
+ * The query parameter that mirrors the panel being open
1273
+ * (`?<urlParam>=account`): `panel` by default; null mirrors nothing. With
1274
+ * it, Back closes the panel, a reload opens it again, and a provider's
1275
+ * redirect comes back into it.
1276
+ */
1277
+ readonly urlParam?: string | null;
1278
+ /**
1279
+ * Small state of the app's own to have back after a sign-in that leaves
1280
+ * the page (at most 4 KB of JSON; `useRedirectResult().kept`): a value, or
1281
+ * a function the tap calls for it.
1282
+ */
1283
+ readonly keep?: unknown;
1284
+ /** Called once the person signed out from the panel, which stays on the page: the app's toast. */
1285
+ readonly onSignedOut?: () => void;
1286
+ /**
1287
+ * Whether it moves the dialog as it opens and closes, with Synq's own
1288
+ * motion (WAAPI on `panelProps`' element): no by default, so a dialog of
1289
+ * the app's own moves as the app makes it. `<AccountPanel>` says yes.
1290
+ */
1291
+ readonly animate?: boolean;
1292
+ }
1293
+ /** The props of the button that opens the panel. */
1294
+ interface AccountPanelTriggerProps {
1295
+ readonly ref: RefObject<HTMLButtonElement | null>;
1296
+ readonly type: 'button';
1297
+ readonly 'aria-haspopup': 'dialog';
1298
+ readonly 'aria-expanded': boolean;
1299
+ readonly 'aria-controls': string;
1300
+ readonly onClick: () => void;
1301
+ }
1302
+ /** The props of the panel itself: a dialog that keeps the focus inside. */
1303
+ interface AccountPanelDialogProps {
1304
+ readonly ref: RefObject<HTMLDivElement | null>;
1305
+ readonly id: string;
1306
+ readonly role: 'dialog';
1307
+ readonly 'aria-modal': true;
1308
+ readonly 'aria-labelledby': string;
1309
+ readonly tabIndex: -1;
1310
+ readonly 'data-layout': AccountPanelLayout;
1311
+ readonly 'data-view': AccountPanelView;
1312
+ /** Where a popover sits, as `--synq-panel-top`, `--synq-panel-right` and `--synq-panel-max-height`. */
1313
+ readonly style: CSSProperties;
1314
+ readonly onKeyDown: (event: KeyboardEvent<HTMLElement>) => void;
1315
+ /**
1316
+ * Marks a press as inside the panel by React's tree, not the page's: a
1317
+ * popover, select or tooltip a card portals into `<body>` is still inside,
1318
+ * and pressing it leaves the panel open.
1319
+ */
1320
+ readonly onPointerDownCapture: (event: PointerEvent<HTMLElement>) => void;
1321
+ /** A link inside closes the panel as it navigates, without going Back first. */
1322
+ readonly onClickCapture: (event: MouseEvent<HTMLElement>) => void;
1323
+ }
1324
+ /** The props of a sheet's grabber, which a drag down closes. */
1325
+ interface AccountPanelGrabberProps {
1326
+ readonly 'aria-hidden': true;
1327
+ readonly onPointerDown: (event: PointerEvent<HTMLElement>) => void;
1328
+ readonly onPointerMove: (event: PointerEvent<HTMLElement>) => void;
1329
+ readonly onPointerUp: () => void;
1330
+ readonly onPointerCancel: () => void;
1331
+ }
1332
+ /** What `useAccountPanel()` answers. */
1333
+ interface UseAccountPanel {
1334
+ readonly open: boolean;
1335
+ /** Opens or closes it; closing gives the focus back to the trigger and calls off a popup under way. */
1336
+ readonly setOpen: (open: boolean) => void;
1337
+ /**
1338
+ * Closes it. With `navigating`, before the page goes elsewhere (a
1339
+ * `router.push` of the app's): it takes `?panel=account` out of the
1340
+ * address in place instead of going Back, so the navigation lands where
1341
+ * it meant to. A link inside the panel does this by itself.
1342
+ */
1343
+ readonly close: (options?: {
1344
+ readonly navigating?: boolean;
1345
+ }) => void;
1346
+ /** Whether to render the panel: open, or still closing. */
1347
+ readonly present: boolean;
1348
+ readonly view: AccountPanelView;
1349
+ readonly layout: AccountPanelLayout;
1350
+ /** The sign-in under way, started from the panel. */
1351
+ readonly pending: AccountPanelPending | null;
1352
+ /**
1353
+ * Why the last sign-in from the panel failed, in the page or by a redirect
1354
+ * that came back: a closed popup, a declined signature or a way back the
1355
+ * person took (`access_denied`) is none.
1356
+ */
1357
+ readonly problem: SynqError | null;
1358
+ /** The method `problem` is for (`google`, `wallet`…), to show it there. */
1359
+ readonly problemMethod: string | null;
1360
+ /**
1361
+ * The session ended while the panel was open, and not by its own Sign
1362
+ * out (it expired, say): the panel is back on the ways in, and says so.
1363
+ */
1364
+ readonly signedOut: boolean;
1365
+ /** Calls off the sign-in under way: closes its popup, and stops waiting on a wallet. */
1366
+ readonly cancel: () => void;
1367
+ /**
1368
+ * Signs in with `method` from a tap: a provider in a popup where one fits
1369
+ * (else a redirect that comes back into the panel), a wallet in place with
1370
+ * the wallet layer's signature (`walletApp` names one), anything else at
1371
+ * the hosted page. Call it from the tap itself, so a popup can open.
1372
+ */
1373
+ readonly signIn: (method: string, options?: {
1374
+ readonly walletApp?: string;
1375
+ }) => Promise<void>;
1376
+ /**
1377
+ * The sign-in the panel is showing: a new account's (`isNewUser`, the
1378
+ * onboarding) or a returning person's (the greeting), until it closes.
1379
+ */
1380
+ readonly event: SignInEvent | null;
1381
+ /** Shows the profile in place of a returning sign-in's greeting. */
1382
+ readonly showProfile: () => void;
1383
+ /** Signs out and stays on the page (`signOut({ stay: true })`), closing the panel and calling `onSignedOut`. */
1384
+ readonly signOut: () => Promise<SynqResult<null>>;
1385
+ /**
1386
+ * Keeps the panel open on a click outside while `busy`, as while a
1387
+ * wallet's prompt in the page waits: a card inside calls it through
1388
+ * `useAccountPanelSection()`.
1389
+ */
1390
+ readonly hold: (busy: boolean) => () => void;
1391
+ /** The id of the panel's title, which labels the dialog. */
1392
+ readonly titleId: string;
1393
+ readonly triggerProps: AccountPanelTriggerProps;
1394
+ readonly panelProps: AccountPanelDialogProps;
1395
+ readonly grabberProps: AccountPanelGrabberProps;
1396
+ }
1397
+ /**
1398
+ * The account panel's state and behavior, for a panel of the app's own:
1399
+ * spread `triggerProps` on its button and `panelProps` on its dialog.
1400
+ */
1401
+ export declare function useAccountPanel(options?: UseAccountPanelOptions): UseAccountPanel;
1402
+ //#endregion
1403
+ //#region src/components/account-panel-sections.d.ts
1404
+ /** What a card of the app's own inside `<AccountPanel>` reads of it: `useAccountPanelSection()`. */
1405
+ interface AccountPanelSection {
1406
+ /** The onboarding of a first sign-in, which keeps to the essentials, rather than the profile. */
1407
+ readonly firstRun: boolean;
1408
+ readonly view: AccountPanelView;
1409
+ /**
1410
+ * Closes the panel. With `navigating`, before a `router.push` of the
1411
+ * card's: it goes from the address in place rather than Back, so the
1412
+ * navigation lands where it meant to. A link inside does this by itself.
1413
+ */
1414
+ readonly close: (options?: {
1415
+ readonly navigating?: boolean;
1416
+ }) => void;
1417
+ /**
1418
+ * Keeps the panel open on a click outside while `busy` (a prompt of the
1419
+ * card's in the page): `useEffect(() => hold(busy), [hold, busy])`.
1420
+ */
1421
+ readonly hold: (busy: boolean) => () => void;
1422
+ }
1423
+ /** The panel a card of the app's own is in: its view, and closing or holding it. */
1424
+ export declare function useAccountPanelSection(): AccountPanelSection;
1425
+ /** What `AccountPanel.Card` takes. */
1426
+ interface AccountPanelCardProps {
1427
+ /** Its heading, which names the card for screen readers too. */
1428
+ readonly title: ReactNode;
1429
+ /** Beside the heading: a count, say. */
1430
+ readonly badge?: ReactNode;
1431
+ readonly children?: ReactNode;
1432
+ }
1433
+ /** A card of the app's own, drawn as the panel's sections are: a heading, then what the app puts in it. */
1434
+ declare function Card({ title, badge, children }: AccountPanelCardProps): ReactNode;
1435
+ /** What `AccountPanel.Row` takes. */
1436
+ interface AccountPanelRowProps {
1437
+ /**
1438
+ * The provider the row stands for, when it's one: inside
1439
+ * `AccountPanel.Linked`, the profile then lists that provider's linked
1440
+ * account here alone, not again as a read-only row (an app's own
1441
+ * "Event reminders" row for Telegram, say).
1442
+ */
1443
+ readonly provider?: SignInMethod;
1444
+ /** On its tile at the start: an icon, 20px. */
1445
+ readonly icon?: ReactNode;
1446
+ readonly title: ReactNode;
1447
+ /** The line under its title. */
1448
+ readonly line?: ReactNode;
1449
+ /** At its end: a button of the app's (a 44px target). */
1450
+ readonly action?: ReactNode;
1451
+ }
1452
+ /**
1453
+ * A row of the app's own, drawn as a `LinkRow` is: an `<li>`, for inside
1454
+ * `AccountPanel.Linked` or a `<ul>` of the app's own card.
1455
+ */
1456
+ declare function Row({ provider, icon, title, line, action }: AccountPanelRowProps): ReactNode;
1457
+ /** What `AccountPanel.Username` takes. */
1458
+ interface AccountPanelUsernameProps {
1459
+ /** The card's heading: "Username" by default. */
1460
+ readonly title?: ReactNode;
1461
+ /**
1462
+ * A line under the field while there's nothing to say about the name
1463
+ * typed: what the username is for, in the app's words ("Your name on
1464
+ * Acme Guild. Change it any time."). None by default.
1465
+ */
1466
+ readonly description?: ReactNode;
1467
+ }
1468
+ /**
1469
+ * The username card of a first sign-in's onboarding: free names checked as
1470
+ * they type, three suggestions for a taken one, saved with Save or Enter.
1471
+ * In the profile, "Edit" beside their name changes it.
1472
+ */
1473
+ declare function Username({ title, description }: AccountPanelUsernameProps): ReactNode;
1474
+ /** What `AccountPanel.Wallets` takes. */
1475
+ interface AccountPanelWalletsProps {
1476
+ /**
1477
+ * More to show under a wallet, from the app's own data (its rank, its
1478
+ * balance): `firstRun` is a first sign-in's onboarding, which keeps to
1479
+ * the essentials.
1480
+ */
1481
+ readonly extra?: (wallet: Wallet, info: {
1482
+ readonly firstRun: boolean;
1483
+ }) => ReactNode;
1484
+ /** The card's heading: "Wallets" by default. */
1485
+ readonly title?: ReactNode;
1486
+ }
1487
+ /**
1488
+ * The profile's wallets: which one is connected, which signed in, which is
1489
+ * primary; connecting one with no signature, adding one with one, and in
1490
+ * the profile, a menu to make one primary, rename, copy or remove it.
1491
+ * Through the wallet layer (`<SynqWalletProvider>`) where there is one,
1492
+ * else adding one goes through the hosted page.
1493
+ */
1494
+ declare function Wallets({ extra, title }: AccountPanelWalletsProps): ReactNode;
1495
+ /** What `AccountPanel.Linked` takes. */
1496
+ interface AccountPanelLinkedProps {
1497
+ /** The card's heading: "Linked" by default. */
1498
+ readonly title?: ReactNode;
1499
+ /**
1500
+ * Its rows: `AccountPanel.LinkRow`s and the app's own, which render
1501
+ * inside a list, so each is an `<li>` (`AccountPanel.Row` is one). By
1502
+ * default, a LinkRow for each provider the brand offers the app.
1503
+ */
1504
+ readonly children?: ReactNode;
1505
+ }
1506
+ /**
1507
+ * The accounts linked to the profile: the rows the app puts in it, and in
1508
+ * the profile, each other linked account, read only.
1509
+ */
1510
+ declare function Linked({ title, children }: AccountPanelLinkedProps): ReactNode;
1511
+ /** What `AccountPanel.LinkRow` takes. */
1512
+ interface AccountPanelLinkRowProps {
1513
+ /** The provider to link: `telegram`, `x`, `google`… */
1514
+ readonly provider: SignInMethod;
1515
+ /** The row's title: the provider's name by default. */
1516
+ readonly title?: ReactNode;
1517
+ /** The line under it while it isn't linked: "Not linked" by default. */
1518
+ readonly description?: ReactNode;
1519
+ /** What it says once linked, in place of the handle: an app's own status ("Alerts on"). */
1520
+ readonly linkedLine?: (account: LinkedAccount) => ReactNode;
1521
+ }
1522
+ /**
1523
+ * One provider to link: "Link", then "Finish in the X window" in a popup on
1524
+ * a laptop (or a redirect on a phone that comes back into the panel), then
1525
+ * their handle. Signing in again is skipped right after a sign-in.
1526
+ */
1527
+ declare function LinkRow({ provider, title, description, linkedLine }: AccountPanelLinkRowProps): ReactNode;
1528
+ //#endregion
1529
+ //#region src/components/AccountPanel.d.ts
1530
+ /** What `AccountPanel` takes. */
1531
+ interface AccountPanelProps extends SynqStyleProps, Omit<UseAccountPanelOptions, 'animate'> {
1532
+ /**
1533
+ * The ways in that lead the panel, in this order, of those the brand
1534
+ * offers the app: its first four by default. The brand's others go under
1535
+ * "More options", in the brand's order; the one this browser last used is
1536
+ * lifted out of it.
1537
+ */
1538
+ readonly methods?: readonly string[];
1539
+ /** Whether a sign-in plays the avatar's moment, the ring and embers: yes by default. */
1540
+ readonly moment?: boolean;
1541
+ /** How many embers rise in the moment: 6 by default. */
1542
+ readonly sparks?: number;
1543
+ /** A line under the sign-in title, in the app's words. */
1544
+ readonly description?: ReactNode;
1545
+ /** The app's page with `<AccountManager>`, linked as "Account settings" under the profile. */
1546
+ readonly accountHref?: string;
1547
+ /** How avatars fall back (the app's own default, from the person's seed) and which pictures show. */
1548
+ readonly avatar?: AvatarSettings;
1549
+ /**
1550
+ * Where the panel renders: `document.body` by default, so no ancestor's
1551
+ * transform, filter or overflow clips it; null renders it beside its
1552
+ * button. Out of the button's tree, it takes the theme variables set on
1553
+ * `.synq`, which it carries, or on `panelClassName`.
1554
+ */
1555
+ readonly container?: Element | null;
1556
+ /**
1557
+ * Added to the panel's layer, the full-window element that holds the
1558
+ * dialog (and on a phone, its backdrop): where scoped theme variables go.
1559
+ * `className` is the button's wrapper's alone.
1560
+ */
1561
+ readonly panelClassName?: string;
1562
+ /**
1563
+ * What the onboarding and the profile show, in this order: the panel's
1564
+ * sections and the app's own cards between them. Username, Wallets and
1565
+ * Linked by default.
1566
+ */
1567
+ readonly children?: ReactNode;
1568
+ }
1569
+ /**
1570
+ * The account button and panel: sign in, a first sign-in's onboarding, and
1571
+ * the profile, in one popover or sheet. Its sections are `AccountPanel.*`.
1572
+ */
1573
+ export declare function AccountPanel({ methods, moment, sparks, description, accountHref, avatar, container, urlParam, keep, onSignedOut, unstyled, className, panelClassName, children }: AccountPanelProps): ReactNode;
1574
+ export declare namespace AccountPanel {
1575
+ var Username: typeof Username;
1576
+ var Wallets: typeof Wallets;
1577
+ var Linked: typeof Linked;
1578
+ var LinkRow: typeof LinkRow;
1579
+ var Card: typeof Card;
1580
+ var Row: typeof Row;
1581
+ }
1582
+ //#endregion
1583
+ //#region src/components/AvatarMoment.d.ts
1584
+ /** What `AvatarMoment` takes: the sign-in to mark, how large, and how many embers. */
1585
+ interface AvatarMomentProps extends SynqStyleProps, AvatarSettings {
1586
+ /**
1587
+ * The sign-in to mark (`useSignInEvent()`, or the panel's `event`): the
1588
+ * ring and embers play once for its `id`. Null shows the avatar alone.
1589
+ */
1590
+ readonly event: {
1591
+ readonly id: string;
1592
+ } | null;
1593
+ /** Whose avatar: the signed-in person by default. */
1594
+ readonly user?: AvatarPerson | null;
1595
+ /** The avatar's width and height in pixels: 64 by default. */
1596
+ readonly size?: number;
1597
+ /** How many embers rise: 6 by default, 0 for the ring alone. */
1598
+ readonly sparks?: number;
1599
+ }
1600
+ /**
1601
+ * The person's avatar with the moment of a sign-in: a ring that sweeps
1602
+ * around it once and embers that rise off it, by `--synq-moment-1..3`.
1603
+ * Drawn for the eye alone; the greeting beside it says what happened.
1604
+ */
1605
+ export declare function AvatarMoment({ event, user, size, sparks, fallback, sources, unstyled, className }: AvatarMomentProps): ReactNode;
1606
+ //#endregion
1607
+ //#region src/wallet-layer.d.ts
1608
+ /** A wallet app as people see it: its name, and its icon when the browser has it. */
1609
+ interface WalletAppInfo {
1610
+ readonly name: string;
1611
+ /** A data URL, as the wallet announced it; null when the browser hasn't it. */
1612
+ readonly icon: string | null;
1613
+ }
1614
+ /** What a wallet layer lends `<AccountPanel>`: see `WalletLayerProvider`. */
1615
+ interface WalletLayer {
1616
+ /** The account the app uses now, and its wallet app; null when none is connected. */
1617
+ readonly connected: {
1618
+ readonly address: string;
1619
+ readonly app: WalletAppInfo | null;
1620
+ } | null;
1621
+ /** The browser's wallets that can sign someone in, for a picker. */
1622
+ readonly wallets: readonly WalletAppInfo[];
1623
+ /**
1624
+ * Signs in with a wallet, in place: the connected one, or the wallet app
1625
+ * called `walletApp`. One signature; the wallet stays connected. A
1626
+ * declined signature is `wallet_rejected`, and what only the hosted page
1627
+ * can do is `redirect_to_web`.
1628
+ */
1629
+ readonly signIn: (walletApp?: string) => Promise<SynqResult<unknown>>;
1630
+ /**
1631
+ * Adds a wallet to the signed-in profile with one signature: the
1632
+ * connected one, or the wallet app called `walletApp`, which connects it.
1633
+ * A wallet on another profile is `wallet_in_use`; `move()` moves it here
1634
+ * where the brand lets it.
1635
+ */
1636
+ readonly add: (walletApp?: string) => Promise<SynqResult<Wallet>>;
1637
+ /** Moves the wallet of the last add's `wallet_in_use` here, where the brand lets it (`canMove`). */
1638
+ readonly move: () => Promise<SynqResult<Wallet>>;
1639
+ /** Whether the last add's conflict carries a ticket that moves the wallet here. */
1640
+ readonly canMove: boolean;
1641
+ /**
1642
+ * Connects one of the profile's wallets, signing nothing: at `address`,
1643
+ * in the wallet app called `walletApp` when the browser has it. Another
1644
+ * account selected in the wallet app is `account_mismatch`.
1645
+ */
1646
+ readonly connect: (address: string, walletApp: string | null) => Promise<SynqResult<unknown>>;
1647
+ /** The address this session signed in with, when the layer knows it. */
1648
+ readonly signedInWith: string | null;
1649
+ /**
1650
+ * Confirms it's them for a change that wants a recent sign-in, with a
1651
+ * wallet on their profile: one signature, in place, no page.
1652
+ */
1653
+ readonly reauthenticate: () => Promise<SynqResult<unknown>>;
1654
+ /**
1655
+ * Asks for the next signature's request now, and keeps it fresh until the
1656
+ * returned function is called, so a tap on a wallet goes straight to the
1657
+ * wallet (AERO-2679). The account panel calls it while it is open. A
1658
+ * layer without it asks after the tap.
1659
+ */
1660
+ readonly prepare?: () => () => void;
1661
+ }
1662
+ /**
1663
+ * Lends a wallet layer to the components under it. `<SynqWalletProvider>`
1664
+ * from `@synqauth/solana/react` does this itself; a wallet layer of the
1665
+ * app's own (another chain's, say) passes its own `value`.
1666
+ */
1667
+ export declare function WalletLayerProvider({ value, children }: {
1668
+ readonly value: WalletLayer | null;
1669
+ readonly children?: ReactNode;
1670
+ }): ReactNode;
1671
+ /** The wallet layer lent to this component (`WalletLayerProvider`), or null when there is none. */
1672
+ export declare function useWalletLayer(): WalletLayer | null;
1673
+ //#endregion
1674
+ export { type AccountManagerProps, Card as AccountPanelCard, type AccountPanelCardProps, type AccountPanelDialogProps, type AccountPanelGrabberProps, type AccountPanelLayout, LinkRow as AccountPanelLinkRow, type AccountPanelLinkRowProps, Linked as AccountPanelLinked, type AccountPanelLinkedProps, type AccountPanelPending, type AccountPanelProps, Row as AccountPanelRow, type AccountPanelRowProps, type AccountPanelSection, type AccountPanelTriggerProps, Username as AccountPanelUsername, type AccountPanelUsernameProps, type AccountPanelView, Wallets as AccountPanelWallets, type AccountPanelWalletsProps, type AccountSection, type AccountSession, type ApiKey, type ApiKeyInput, type AvatarFallback, type AvatarMomentProps, type AvatarProps, type AvatarSettings, type AvatarSource, type HandoffOptions, type KeyableApi, type LastUsedMethod, type LinkStatus, type LinkableMethod, type LinkedAccount, type NewApiKey, type OneTimeCodeInputProps, type Passkey, type Profile, type ProfileUpdate, type ReauthenticateOptions, type RedirectKind, type RedirectResult, type SessionErrorProps, type SessionStatus, type SignInButtonProps, type SignInDisplay, type SignInDone, type SignInEvent, type SignInFormProps, type SignInHandover, type SignInMethod, type SignInMethodInfo, type SignInMethods, type SignInOptions, type SignInPartProps, type SignInProblem, type SignInStep, type SignOutOptions, type SynqClientPlugin, type SynqMessages, type SynqMountRequest, type SynqProviderProps, type SynqResult, type SynqSessionInfo, type SynqStyleProps, type SynqUser, type UseAccountData, type UseAccountPanel, type UseAccountPanelOptions, type UseAuth, type UseAvatar, type UseLink, type UseRedirectResult, type UseSession, type UseSignIn, type UseSignInEvent, type UseSignInMethods, type UseSignInOptions, type UseUser, type UseUsernameField, type UserButtonProps, type UserMetadata, type UsernameCode, type UsernameStatus, type Wallet, type WalletAppInfo, type WalletLayer, type WalletManagerProps, type WalletRequestTarget, type WalletSignedIn };