herald-auth-web 0.5.1

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.
@@ -0,0 +1,772 @@
1
+ type AuthToken = string | undefined;
2
+ interface Auth {
3
+ /**
4
+ * Which part of the request do we use to send the auth?
5
+ *
6
+ * @default 'header'
7
+ */
8
+ in?: 'header' | 'query' | 'cookie';
9
+ /**
10
+ * Header or query parameter name.
11
+ *
12
+ * @default 'Authorization'
13
+ */
14
+ name?: string;
15
+ scheme?: 'basic' | 'bearer';
16
+ type: 'apiKey' | 'http';
17
+ }
18
+
19
+ interface SerializerOptions<T> {
20
+ /**
21
+ * @default true
22
+ */
23
+ explode: boolean;
24
+ style: T;
25
+ }
26
+ type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited';
27
+ type ObjectStyle = 'form' | 'deepObject';
28
+
29
+ type QuerySerializer = (query: Record<string, unknown>) => string;
30
+ type BodySerializer = (body: any) => any;
31
+ type QuerySerializerOptionsObject = {
32
+ allowReserved?: boolean;
33
+ array?: Partial<SerializerOptions<ArrayStyle>>;
34
+ object?: Partial<SerializerOptions<ObjectStyle>>;
35
+ };
36
+ type QuerySerializerOptions = QuerySerializerOptionsObject & {
37
+ /**
38
+ * Per-parameter serialization overrides. When provided, these settings
39
+ * override the global array/object settings for specific parameter names.
40
+ */
41
+ parameters?: Record<string, QuerySerializerOptionsObject>;
42
+ };
43
+
44
+ type HttpMethod = 'connect' | 'delete' | 'get' | 'head' | 'options' | 'patch' | 'post' | 'put' | 'trace';
45
+ type Client$1<RequestFn = never, Config = unknown, MethodFn = never, BuildUrlFn = never, SseFn = never> = {
46
+ /**
47
+ * Returns the final request URL.
48
+ */
49
+ buildUrl: BuildUrlFn;
50
+ getConfig: () => Config;
51
+ request: RequestFn;
52
+ setConfig: (config: Config) => Config;
53
+ } & {
54
+ [K in HttpMethod]: MethodFn;
55
+ } & ([SseFn] extends [never] ? {
56
+ sse?: never;
57
+ } : {
58
+ sse: {
59
+ [K in HttpMethod]: SseFn;
60
+ };
61
+ });
62
+ interface Config$1 {
63
+ /**
64
+ * Auth token or a function returning auth token. The resolved value will be
65
+ * added to the request payload as defined by its `security` array.
66
+ */
67
+ auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken;
68
+ /**
69
+ * A function for serializing request body parameter. By default,
70
+ * {@link JSON.stringify()} will be used.
71
+ */
72
+ bodySerializer?: BodySerializer | null;
73
+ /**
74
+ * An object containing any HTTP headers that you want to pre-populate your
75
+ * `Headers` object with.
76
+ *
77
+ * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}
78
+ */
79
+ headers?: RequestInit['headers'] | Record<string, string | number | boolean | (string | number | boolean)[] | null | undefined | unknown>;
80
+ /**
81
+ * The request method.
82
+ *
83
+ * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
84
+ */
85
+ method?: Uppercase<HttpMethod>;
86
+ /**
87
+ * A function for serializing request query parameters. By default, arrays
88
+ * will be exploded in form style, objects will be exploded in deepObject
89
+ * style, and reserved characters are percent-encoded.
90
+ *
91
+ * This method will have no effect if the native `paramsSerializer()` Axios
92
+ * API function is used.
93
+ *
94
+ * {@link https://swagger.io/docs/specification/serialization/#query View examples}
95
+ */
96
+ querySerializer?: QuerySerializer | QuerySerializerOptions;
97
+ /**
98
+ * A function validating request data. This is useful if you want to ensure
99
+ * the request conforms to the desired shape, so it can be safely sent to
100
+ * the server.
101
+ */
102
+ requestValidator?: (data: unknown) => Promise<unknown>;
103
+ /**
104
+ * A function transforming response data before it's returned. This is useful
105
+ * for post-processing data, e.g. converting ISO strings into Date objects.
106
+ */
107
+ responseTransformer?: (data: unknown) => Promise<unknown>;
108
+ /**
109
+ * A function validating response data. This is useful if you want to ensure
110
+ * the response conforms to the desired shape, so it can be safely passed to
111
+ * the transformers and returned to the user.
112
+ */
113
+ responseValidator?: (data: unknown) => Promise<unknown>;
114
+ }
115
+
116
+ type ServerSentEventsOptions<TData = unknown> = Omit<RequestInit, 'method'> & Pick<Config$1, 'method' | 'responseTransformer' | 'responseValidator'> & {
117
+ /**
118
+ * Fetch API implementation. You can use this option to provide a custom
119
+ * fetch instance.
120
+ *
121
+ * @default globalThis.fetch
122
+ */
123
+ fetch?: typeof fetch;
124
+ /**
125
+ * Implementing clients can call request interceptors inside this hook.
126
+ */
127
+ onRequest?: (url: string, init: RequestInit) => Promise<Request>;
128
+ /**
129
+ * Callback invoked when a network or parsing error occurs during streaming.
130
+ *
131
+ * This option applies only if the endpoint returns a stream of events.
132
+ *
133
+ * @param error The error that occurred.
134
+ */
135
+ onSseError?: (error: unknown) => void;
136
+ /**
137
+ * Callback invoked when an event is streamed from the server.
138
+ *
139
+ * This option applies only if the endpoint returns a stream of events.
140
+ *
141
+ * @param event Event streamed from the server.
142
+ * @returns Nothing (void).
143
+ */
144
+ onSseEvent?: (event: StreamEvent<TData>) => void;
145
+ serializedBody?: RequestInit['body'];
146
+ /**
147
+ * Default retry delay in milliseconds.
148
+ *
149
+ * This option applies only if the endpoint returns a stream of events.
150
+ *
151
+ * @default 3000
152
+ */
153
+ sseDefaultRetryDelay?: number;
154
+ /**
155
+ * Maximum number of retry attempts before giving up.
156
+ */
157
+ sseMaxRetryAttempts?: number;
158
+ /**
159
+ * Maximum retry delay in milliseconds.
160
+ *
161
+ * Applies only when exponential backoff is used.
162
+ *
163
+ * This option applies only if the endpoint returns a stream of events.
164
+ *
165
+ * @default 30000
166
+ */
167
+ sseMaxRetryDelay?: number;
168
+ /**
169
+ * Optional sleep function for retry backoff.
170
+ *
171
+ * Defaults to using `setTimeout`.
172
+ */
173
+ sseSleepFn?: (ms: number) => Promise<void>;
174
+ url: string;
175
+ };
176
+ interface StreamEvent<TData = unknown> {
177
+ data: TData;
178
+ event?: string;
179
+ id?: string;
180
+ retry?: number;
181
+ }
182
+ type ServerSentEventsResult<TData = unknown, TReturn = void, TNext = unknown> = {
183
+ stream: AsyncGenerator<TData extends Record<string, unknown> ? TData[keyof TData] : TData, TReturn, TNext>;
184
+ };
185
+
186
+ type ErrInterceptor<Err, Res, Req, Options> = (error: Err, response: Res, request: Req, options: Options) => Err | Promise<Err>;
187
+ type ReqInterceptor<Req, Options> = (request: Req, options: Options) => Req | Promise<Req>;
188
+ type ResInterceptor<Res, Req, Options> = (response: Res, request: Req, options: Options) => Res | Promise<Res>;
189
+ declare class Interceptors<Interceptor> {
190
+ fns: Array<Interceptor | null>;
191
+ clear(): void;
192
+ eject(id: number | Interceptor): void;
193
+ exists(id: number | Interceptor): boolean;
194
+ getInterceptorIndex(id: number | Interceptor): number;
195
+ update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false;
196
+ use(fn: Interceptor): number;
197
+ }
198
+ interface Middleware<Req, Res, Err, Options> {
199
+ error: Interceptors<ErrInterceptor<Err, Res, Req, Options>>;
200
+ request: Interceptors<ReqInterceptor<Req, Options>>;
201
+ response: Interceptors<ResInterceptor<Res, Req, Options>>;
202
+ }
203
+
204
+ type ResponseStyle = 'data' | 'fields';
205
+ interface Config<T extends ClientOptions = ClientOptions> extends Omit<RequestInit, 'body' | 'headers' | 'method'>, Config$1 {
206
+ /**
207
+ * Base URL for all requests made by this client.
208
+ */
209
+ baseUrl?: T['baseUrl'];
210
+ /**
211
+ * Fetch API implementation. You can use this option to provide a custom
212
+ * fetch instance.
213
+ *
214
+ * @default globalThis.fetch
215
+ */
216
+ fetch?: typeof fetch;
217
+ /**
218
+ * Please don't use the Fetch client for Next.js applications. The `next`
219
+ * options won't have any effect.
220
+ *
221
+ * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead.
222
+ */
223
+ next?: never;
224
+ /**
225
+ * Return the response data parsed in a specified format. By default, `auto`
226
+ * will infer the appropriate method from the `Content-Type` response header.
227
+ * You can override this behavior with any of the {@link Body} methods.
228
+ * Select `stream` if you don't want to parse response data at all.
229
+ *
230
+ * @default 'auto'
231
+ */
232
+ parseAs?: 'arrayBuffer' | 'auto' | 'blob' | 'formData' | 'json' | 'stream' | 'text';
233
+ /**
234
+ * Should we return only data or multiple fields (data, error, response, etc.)?
235
+ *
236
+ * @default 'fields'
237
+ */
238
+ responseStyle?: ResponseStyle;
239
+ /**
240
+ * Throw an error instead of returning it in the response?
241
+ *
242
+ * @default false
243
+ */
244
+ throwOnError?: T['throwOnError'];
245
+ }
246
+ interface RequestOptions<TData = unknown, TResponseStyle extends ResponseStyle = 'fields', ThrowOnError extends boolean = boolean, Url extends string = string> extends Config<{
247
+ responseStyle: TResponseStyle;
248
+ throwOnError: ThrowOnError;
249
+ }>, Pick<ServerSentEventsOptions<TData>, 'onSseError' | 'onSseEvent' | 'sseDefaultRetryDelay' | 'sseMaxRetryAttempts' | 'sseMaxRetryDelay'> {
250
+ /**
251
+ * Any body that you want to add to your request.
252
+ *
253
+ * {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
254
+ */
255
+ body?: unknown;
256
+ path?: Record<string, unknown>;
257
+ query?: Record<string, unknown>;
258
+ /**
259
+ * Security mechanism(s) to use for the request.
260
+ */
261
+ security?: ReadonlyArray<Auth>;
262
+ url: Url;
263
+ }
264
+ interface ResolvedRequestOptions<TResponseStyle extends ResponseStyle = 'fields', ThrowOnError extends boolean = boolean, Url extends string = string> extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> {
265
+ serializedBody?: string;
266
+ }
267
+ type RequestResult<TData = unknown, TError = unknown, ThrowOnError extends boolean = boolean, TResponseStyle extends ResponseStyle = 'fields'> = ThrowOnError extends true ? Promise<TResponseStyle extends 'data' ? TData extends Record<string, unknown> ? TData[keyof TData] : TData : {
268
+ data: TData extends Record<string, unknown> ? TData[keyof TData] : TData;
269
+ request: Request;
270
+ response: Response;
271
+ }> : Promise<TResponseStyle extends 'data' ? (TData extends Record<string, unknown> ? TData[keyof TData] : TData) | undefined : ({
272
+ data: TData extends Record<string, unknown> ? TData[keyof TData] : TData;
273
+ error: undefined;
274
+ } | {
275
+ data: undefined;
276
+ error: TError extends Record<string, unknown> ? TError[keyof TError] : TError;
277
+ }) & {
278
+ request: Request;
279
+ response: Response;
280
+ }>;
281
+ interface ClientOptions {
282
+ baseUrl?: string;
283
+ responseStyle?: ResponseStyle;
284
+ throwOnError?: boolean;
285
+ }
286
+ type MethodFn = <TData = unknown, TError = unknown, ThrowOnError extends boolean = false, TResponseStyle extends ResponseStyle = 'fields'>(options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
287
+ type SseFn = <TData = unknown, TError = unknown, ThrowOnError extends boolean = false, TResponseStyle extends ResponseStyle = 'fields'>(options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>) => Promise<ServerSentEventsResult<TData, TError>>;
288
+ type RequestFn = <TData = unknown, TError = unknown, ThrowOnError extends boolean = false, TResponseStyle extends ResponseStyle = 'fields'>(options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'> & Pick<Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>, 'method'>) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
289
+ type BuildUrlFn = <TData extends {
290
+ body?: unknown;
291
+ path?: Record<string, unknown>;
292
+ query?: Record<string, unknown>;
293
+ url: string;
294
+ }>(options: TData & Options<TData>) => string;
295
+ type Client = Client$1<RequestFn, Config, MethodFn, BuildUrlFn, SseFn> & {
296
+ interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>;
297
+ };
298
+ interface TDataShape {
299
+ body?: unknown;
300
+ headers?: unknown;
301
+ path?: unknown;
302
+ query?: unknown;
303
+ url: string;
304
+ }
305
+ type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>;
306
+ type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean, TResponse = unknown, TResponseStyle extends ResponseStyle = 'fields'> = OmitKeys<RequestOptions<TResponse, TResponseStyle, ThrowOnError>, 'body' | 'path' | 'query' | 'url'> & ([TData] extends [never] ? unknown : Omit<TData, 'url'>);
307
+
308
+ type BrowserTokenResponse = {
309
+ accessToken: string;
310
+ expiresIn: number;
311
+ refreshExpiresIn: number;
312
+ refreshToken: string;
313
+ tokenType: string;
314
+ };
315
+ type CredentialClass = 'first_party' | 'custom_user_ui';
316
+ type CredentialScope = 'feature_read' | 'profile_read' | 'profile_write_nickname' | 'change_password' | 'delete_account' | 'totp_manage' | 'passkey_manage' | 'logout' | 'points_read' | 'points_transactions_read' | 'purchase_read' | 'purchase_initiate' | 'purchase_status_read' | 'invoice_read' | 'invoice_apply' | 'subscription_read' | 'subscription_cancel';
317
+ type StatusResponse = {
318
+ authenticated: boolean;
319
+ clientAppId: string;
320
+ clientId: string;
321
+ credentialClass: CredentialClass;
322
+ /**
323
+ * Retained in the response shape for clients that display RBAC grants;
324
+ * browser-token authorization itself is governed by `scopes`.
325
+ */
326
+ permissions?: Array<string> | null;
327
+ realmId?: string | null;
328
+ scopes: Array<CredentialScope>;
329
+ userId?: string | null;
330
+ };
331
+
332
+ /**
333
+ * Public SDK types (DEC-js-sdk-010).
334
+ *
335
+ * DTO response types are re-exported from the (internal) generated layer so
336
+ * consumers get a stable, typed surface without depending on generated paths.
337
+ */
338
+
339
+ type HeraldCredentialClass = CredentialClass;
340
+ /** A normalized session view, derived from `/api/auth/status`. */
341
+ interface HeraldSession {
342
+ authenticated: boolean;
343
+ realmId: string | null;
344
+ userId: string | null;
345
+ clientAppId: string | null;
346
+ clientId: string | null;
347
+ credentialClass: HeraldCredentialClass | null;
348
+ permissions: string[];
349
+ scopes: CredentialScope[];
350
+ }
351
+ type SessionEvent = {
352
+ type: 'authenticated';
353
+ session: HeraldSession;
354
+ } | {
355
+ type: 'session-expired';
356
+ reason: 'refresh-failed' | 'family-revoked' | 'client-app-disabled';
357
+ } | {
358
+ type: 'logged-out';
359
+ };
360
+ /** Second factors the backend may request on `POST /login` (DEC-js-sdk-010). */
361
+ type SecondFactor = 'totp' | 'passkey';
362
+ /** Agreement a caller must re-submit (via `agreements`) to pass a consent gate. */
363
+ interface ConsentAgreement {
364
+ agreementType: string;
365
+ versionId: string;
366
+ /**
367
+ * The backend's original agreement summary (snake_case display fields:
368
+ * `title`, `version_no`, `effective_at`, `mode`, ...), passed through for
369
+ * host apps that render the consent list. Optional — the re-submit shape
370
+ * above is the contract; `raw` is display metadata only.
371
+ */
372
+ raw?: Record<string, unknown>;
373
+ }
374
+ interface LoginSuccess {
375
+ kind: 'success';
376
+ session: HeraldSession;
377
+ }
378
+ interface LoginRequiresSecondFactor {
379
+ kind: 'requires-second-factor';
380
+ tempToken: string;
381
+ expiresInSeconds: number;
382
+ secondFactors: SecondFactor[];
383
+ userId: string;
384
+ realmId: string;
385
+ }
386
+ interface LoginConsentRequired {
387
+ kind: 'consent-required';
388
+ /** Agreements the integrator must render + re-submit via `login`/`verify` `agreements`. */
389
+ agreements: ConsentAgreement[];
390
+ }
391
+ interface LoginOauthRedirect {
392
+ kind: 'oauth-redirect';
393
+ redirectTo: string;
394
+ }
395
+ /** A real send: the code was issued and is valid for `expiresInSeconds`. */
396
+ interface EmailOtpSent {
397
+ kind: 'sent';
398
+ message: string;
399
+ expiresInSeconds: number;
400
+ }
401
+ /**
402
+ * A 409 control-flow outcome — NOT an error. `consent_required` carries the
403
+ * agreement list the integrator must render and re-send via `agreements`;
404
+ * `email_not_registered` means auto-register is off for the realm.
405
+ */
406
+ interface EmailOtpConflict {
407
+ kind: 'conflict';
408
+ /** `consent_required` | `email_not_registered` (backend `email_otp.rs`). */
409
+ code: string;
410
+ message: string;
411
+ consentRequired: boolean;
412
+ /** Agreement summaries (with `raw` display passthrough) for the consent gate. */
413
+ agreements: ConsentAgreement[];
414
+ }
415
+ type EmailOtpSendResult = EmailOtpSent | EmailOtpConflict;
416
+ type LoginResult = LoginSuccess | LoginRequiresSecondFactor | LoginConsentRequired | LoginOauthRedirect;
417
+ /** Result of `passkey.loginBegin` (1FA or 2FA). `options` is the WebAuthn
418
+ * `PublicKeyCredentialRequestOptions` JSON returned by the server. */
419
+ interface PasskeyLoginBeginResult {
420
+ authToken: string;
421
+ options: unknown;
422
+ }
423
+
424
+ /**
425
+ * In-memory access-token holder + session state + events.
426
+ *
427
+ * Mirrors the Herald own-frontend pattern (`frontend/src/stores/auth-store.ts`
428
+ * `accessTokenHolder` + persist), with all framework (Zustand/React) coupling
429
+ * removed.
430
+ */
431
+
432
+ /**
433
+ * Non-persisted, module-instance holder for the access token. A page reload
434
+ * clears it; the transport restores it via a silent refresh on the next 401.
435
+ */
436
+ interface AccessTokenHolder {
437
+ get(): string | null;
438
+ set(token: string | null): void;
439
+ clear(): void;
440
+ }
441
+ type SessionListener = (event: SessionEvent) => void;
442
+ interface SessionStore {
443
+ getSession(): HeraldSession;
444
+ setSession(session: HeraldSession | null): void;
445
+ subscribe(listener: SessionListener): () => void;
446
+ emit(event: SessionEvent): void;
447
+ }
448
+
449
+ /**
450
+ * Pluggable refresh-token storage (DEC-js-sdk-006).
451
+ *
452
+ * The access token NEVER passes through `TokenStorage` — it lives only in the
453
+ * in-memory holder (`session.ts`). The default implementation persists the
454
+ * (rotating, reuse-detected) refresh token to `localStorage`, matching the
455
+ * Herald own-frontend risk posture. Non-browser / SSR integrators must inject
456
+ * an adapter or use `memoryStorage()`.
457
+ */
458
+ interface TokenStorage {
459
+ getRefreshToken(): string | null;
460
+ setRefreshToken(token: string | null): void;
461
+ }
462
+ /** In-memory storage: nothing survives a page reload. */
463
+ declare function memoryStorage(): TokenStorage;
464
+ /**
465
+ * `localStorage`-backed storage (browser default). Throws `HeraldError
466
+ * { kind: 'ssr-no-storage' }` when `localStorage` is unavailable so SSR/Node
467
+ * misuse fails fast instead of silently no-op'ing.
468
+ */
469
+ declare function localStorageStorage(key: string): TokenStorage;
470
+
471
+ /**
472
+ * WebAuthn passkey LOGIN assertion helper.
473
+ *
474
+ * Only `navigator.credentials.get` (assertion) — no registration/create. The
475
+ * SDK never manages authenticators (DEC-js-sdk-001). The integrator passes the
476
+ * server-provided options to `performPasskeyAssertion` and submits the returned
477
+ * assertion to `passkey.loginFinish`.
478
+ *
479
+ * base64url encode/decode is implemented on native `ArrayBuffer` ↔ `string`
480
+ * (no dependency).
481
+ */
482
+ /** Server-provided WebAuthn request options, JSON-encoded (base64url fields). */
483
+ interface PublicKeyCredentialRequestOptionsJSON {
484
+ challenge: string;
485
+ rpId?: string;
486
+ timeout?: number;
487
+ userVerification?: UserVerificationRequirement;
488
+ allowCredentials?: Array<{
489
+ type: 'public-key';
490
+ id: string;
491
+ transports?: AuthenticatorTransport[];
492
+ }>;
493
+ }
494
+ /** Assertion result ready to submit to the passkey verify endpoint. */
495
+ interface AssertionResultJSON {
496
+ id: string;
497
+ rawId: string;
498
+ type: 'public-key';
499
+ response: {
500
+ authenticatorData: string;
501
+ clientDataJSON: string;
502
+ signature: string;
503
+ userHandle?: string | null;
504
+ };
505
+ clientExtensionResults?: Record<string, unknown>;
506
+ }
507
+ /**
508
+ * Perform a WebAuthn assertion for passkey login.
509
+ *
510
+ * @throws when WebAuthn is unavailable or the user cancels.
511
+ */
512
+ declare function performPasskeyAssertion(options: PublicKeyCredentialRequestOptionsJSON): Promise<AssertionResultJSON>;
513
+
514
+ /**
515
+ * Authentication orchestration (DEC-js-sdk-008 / DEC-js-sdk-010).
516
+ *
517
+ * Each method calls a generated op through the per-instance transport client
518
+ * (auto Bearer + silent refresh), maps the result to SDK public types, and
519
+ * updates the session/token state. Login-family methods normalize the
520
+ * multi-branch 200 into the `LoginResult` discriminated union.
521
+ */
522
+
523
+ interface RegisterPayload {
524
+ email: string;
525
+ password: string;
526
+ username?: string;
527
+ turnstileToken?: string;
528
+ }
529
+ interface TriggerVerifyEmailPayload {
530
+ email: string;
531
+ turnstileToken?: string;
532
+ }
533
+ interface RequestPasswordResetPayload {
534
+ email: string;
535
+ turnstileToken?: string;
536
+ }
537
+ interface LoginPayload {
538
+ username?: string;
539
+ email?: string;
540
+ password: string;
541
+ turnstileToken?: string;
542
+ /** Agreements to satisfy a prior `consent-required` gate. */
543
+ agreements?: ConsentAgreement[];
544
+ /**
545
+ * Optional OAuth context for host apps that drive an authorization-code flow
546
+ * themselves (e.g. Herald's own frontend with PKCE). When present the backend
547
+ * answers with `redirectTo`, surfaced as `{ kind: 'oauth-redirect' }`; the SDK
548
+ * does NOT perform the token exchange (DEC-js-sdk-008 — that stays with the
549
+ * caller).
550
+ */
551
+ oauthClientId?: string;
552
+ redirectUri?: string;
553
+ state?: string;
554
+ }
555
+ interface VerifyTotpPayload {
556
+ tempToken: string;
557
+ code?: string;
558
+ backupCode?: string;
559
+ agreements?: ConsentAgreement[];
560
+ }
561
+ interface PasskeyLoginBeginPayload {
562
+ /** Present for 2FA (after a `requires-second-factor` login); absent for 1FA. */
563
+ tempToken?: string;
564
+ turnstileToken?: string;
565
+ /**
566
+ * Optional OAuth context for host apps driving an authorization-code flow
567
+ * (first-party passkey logins from an OAuth-linked login page). Passkey
568
+ * verify then answers with `redirectTo` (kind: 'oauth-redirect'); the SDK
569
+ * does NOT perform the exchange (DEC-js-sdk-008).
570
+ */
571
+ oauth?: {
572
+ clientId: string;
573
+ redirectUri: string;
574
+ state: string;
575
+ };
576
+ }
577
+ interface PasskeyLoginFinishPayload {
578
+ authToken: string;
579
+ assertion: AssertionResultJSON;
580
+ /** Present when finishing a 2FA passkey login. */
581
+ tempToken?: string;
582
+ agreements?: ConsentAgreement[];
583
+ }
584
+ interface EmailOtpSendPayload {
585
+ email: string;
586
+ turnstileToken?: string;
587
+ /** Agreements to satisfy a prior `consent_required` conflict on re-send. */
588
+ agreements?: ConsentAgreement[];
589
+ }
590
+ interface EmailOtpVerifyPayload {
591
+ email: string;
592
+ code: string;
593
+ agreements?: ConsentAgreement[];
594
+ }
595
+ interface AuthDeps {
596
+ realmId: string;
597
+ clientId: string;
598
+ /** Per-instance transport client; routes every op through its interceptors. */
599
+ client: Client;
600
+ accessTokenHolder: AccessTokenHolder;
601
+ storage: TokenStorage;
602
+ session: SessionStore;
603
+ /** Shared single-flight refresh core (from the transport). */
604
+ refreshTokens: () => Promise<BrowserTokenResponse | null>;
605
+ }
606
+ declare function createAuth(deps: AuthDeps): {
607
+ register(payload: RegisterPayload): Promise<{
608
+ message: string;
609
+ verificationRequired: boolean;
610
+ }>;
611
+ triggerVerifyEmail(payload: TriggerVerifyEmailPayload): Promise<{
612
+ message: string;
613
+ }>;
614
+ requestPasswordReset(payload: RequestPasswordResetPayload): Promise<{
615
+ message: string;
616
+ }>;
617
+ login(payload: LoginPayload): Promise<LoginResult>;
618
+ verifyTotp(payload: VerifyTotpPayload): Promise<LoginResult>;
619
+ passkey: {
620
+ loginBegin(payload: PasskeyLoginBeginPayload): Promise<PasskeyLoginBeginResult>;
621
+ loginFinish(payload: PasskeyLoginFinishPayload): Promise<LoginResult>;
622
+ };
623
+ loginWithEmailOtp: {
624
+ /**
625
+ * Send a passwordless login code. The two 409 control-flow outcomes
626
+ * (DEC-js-sdk-014) — `consent_required` (auto-register consent gate) and
627
+ * `email_not_registered` (auto-register off) — resolve as
628
+ * `{ kind: 'conflict' }` instead of throwing, mirroring the multi-branch
629
+ * normalization `login()` applies to its 200 bodies. All other HTTP
630
+ * failures throw `HeraldError`.
631
+ */
632
+ send(payload: EmailOtpSendPayload): Promise<EmailOtpSendResult>;
633
+ verify(payload: EmailOtpVerifyPayload): Promise<LoginResult>;
634
+ };
635
+ getStatus(): Promise<StatusResponse>;
636
+ /**
637
+ * Explicitly refresh the Bearer token family (startup restore, proactive
638
+ * refresh). Single-flight: concurrent calls share one HTTP request with the
639
+ * 401 auto-refresh interceptor. On success both the in-memory access token
640
+ * and the stored refresh token are rotated.
641
+ *
642
+ * @throws {HeraldError} `kind: 'session-expired'` when no refresh token is
643
+ * stored or the refresh failed (reuse / expiry / family revocation); a
644
+ * `session-expired` event is emitted either way.
645
+ */
646
+ refresh(): Promise<BrowserTokenResponse>;
647
+ logout(): Promise<{
648
+ message: string;
649
+ }>;
650
+ };
651
+
652
+ /**
653
+ * Client factory (US-JS-001).
654
+ *
655
+ * `createHeraldClient` wires the in-memory access-token holder, the pluggable
656
+ * refresh-token storage, the session store, the transport interceptors, and the
657
+ * auth-orchestration methods into a single client object.
658
+ */
659
+
660
+ interface HeraldClientConfig {
661
+ /** Herald API origin (e.g. `https://auth.example.com`). */
662
+ baseUrl: string;
663
+ /** Realm the integration belongs to. */
664
+ realmId: string;
665
+ /** Client App identifier; injected into request bodies. */
666
+ clientId: string;
667
+ /** Refresh-token storage. Defaults to `localStorage`; inject in SSR. */
668
+ storage?: TokenStorage;
669
+ /** `localStorage` key for the refresh token (default `herald.refreshToken`). */
670
+ storageKey?: string;
671
+ /** Session lifecycle callback (`authenticated` / `session-expired` / `logged-out`). */
672
+ onSessionChange?: (event: SessionEvent) => void;
673
+ }
674
+ type HeraldClient = ReturnType<typeof createAuth> & {
675
+ /** The resolved refresh-token storage. */
676
+ readonly storage: TokenStorage;
677
+ /** Current session snapshot + per-instance event subscription. */
678
+ readonly session: {
679
+ getSession(): HeraldSession;
680
+ subscribe(listener: (event: SessionEvent) => void): () => void;
681
+ };
682
+ /** First-party token bridge: inspect / inject the token family from the host app. */
683
+ readonly tokens: TokenBridge;
684
+ };
685
+ /** Token set obtained outside the SDK (PKCE exchange, switch-client, direct-issue responses). */
686
+ interface SetTokensPayload {
687
+ accessToken: string;
688
+ refreshToken: string;
689
+ /**
690
+ * When provided, rebinds the client's request-body `clientId` (e.g. after a
691
+ * first-party PKCE exchange or a switch-client performed by the host app).
692
+ */
693
+ clientId?: string;
694
+ }
695
+ interface TokenBridge {
696
+ /** Current in-memory access token (null after a reload, until refreshed). */
697
+ getAccessToken(): string | null;
698
+ /**
699
+ * Inject a token set obtained outside the SDK. Pure state update — no session
700
+ * event is emitted; call `getStatus()` afterwards to hydrate the session.
701
+ */
702
+ setTokens(tokens: SetTokensPayload): void;
703
+ /** Clear the access + refresh tokens without calling the server or emitting events. */
704
+ clear(): void;
705
+ /**
706
+ * Rebind the request-body `clientId` without touching tokens. First-party
707
+ * hosts pick between built-in products (e.g. console vs account center) per
708
+ * flow, before any tokens exist.
709
+ */
710
+ bindClientId(clientId: string): void;
711
+ }
712
+ /**
713
+ * Create a Herald browser client. Each client owns its own generated HTTP
714
+ * client instance, so multiple clients are fully isolated.
715
+ *
716
+ * @throws {HeraldError} `kind: 'ssr-no-storage'` when no `storage` is injected
717
+ * and `localStorage` is unavailable (SSR / Node).
718
+ */
719
+ declare function createHeraldClient(config: HeraldClientConfig): HeraldClient;
720
+
721
+ /**
722
+ * Typed, programmatically-discriminable error union (US-JS-008).
723
+ *
724
+ * Every public SDK method rejects with a `HeraldError`. Branch on the stable
725
+ * `kind` field instead of parsing messages.
726
+ */
727
+ type HeraldErrorKind =
728
+ /** Non-browser/SSR use without an injected `TokenStorage` adapter. */
729
+ 'ssr-no-storage'
730
+ /** Cross-origin blocked because the page origin is not on the Client App allow-list. */
731
+ | 'origin-not-allowed'
732
+ /** Network failure or undifferentiated fetch error. */
733
+ | 'network'
734
+ /** 401 — credentials invalid (non-refresh context). */
735
+ | 'unauthorized'
736
+ /** 403. */
737
+ | 'forbidden'
738
+ /** 404. */
739
+ | 'not-found'
740
+ /** Login returned a second-factor challenge where a direct token was expected. */
741
+ | 'requires-second-factor'
742
+ /** Login returned a consent-required gate where a direct token was expected. */
743
+ | 'consent-required'
744
+ /** Refresh failed / token family revoked / Client App disabled. */
745
+ | 'session-expired'
746
+ /** 429. */
747
+ | 'rate-limited'
748
+ /** 400 field validation. */
749
+ | 'validation'
750
+ /** Any other backend error (carries `code`/`requestId`/`details`). */
751
+ | 'api';
752
+ interface HeraldErrorInit {
753
+ kind: HeraldErrorKind;
754
+ message?: string;
755
+ status?: number;
756
+ /** Backend `ApiError.code` (snake_case slug) when available. */
757
+ code?: string;
758
+ /** Backend `ApiError.requestId` when available. */
759
+ requestId?: string;
760
+ /** Backend `ApiError.details` when available. */
761
+ details?: unknown;
762
+ }
763
+ declare class HeraldError extends Error {
764
+ readonly kind: HeraldErrorKind;
765
+ readonly status?: number;
766
+ readonly code?: string;
767
+ readonly requestId?: string;
768
+ readonly details?: unknown;
769
+ constructor(init: HeraldErrorInit);
770
+ }
771
+
772
+ export { type AssertionResultJSON, type BrowserTokenResponse, type ConsentAgreement, type EmailOtpConflict, type EmailOtpSendPayload, type EmailOtpSendResult, type EmailOtpSent, type EmailOtpVerifyPayload, type HeraldClient, type HeraldClientConfig, type HeraldCredentialClass, HeraldError, type HeraldErrorInit, type HeraldErrorKind, type HeraldSession, type LoginConsentRequired, type LoginOauthRedirect, type LoginPayload, type LoginRequiresSecondFactor, type LoginResult, type LoginSuccess, type PasskeyLoginBeginPayload, type PasskeyLoginBeginResult, type PasskeyLoginFinishPayload, type PublicKeyCredentialRequestOptionsJSON, type RegisterPayload, type RequestPasswordResetPayload, type SecondFactor, type SessionEvent, type SetTokensPayload, type StatusResponse, type TokenBridge, type TokenStorage, type TriggerVerifyEmailPayload, type VerifyTotpPayload, createHeraldClient, localStorageStorage, memoryStorage, performPasskeyAssertion };