@shuttle.technology/authorization 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/README.md +1082 -666
  3. package/dist/accountLists.d.ts +28 -0
  4. package/dist/accountLists.js +27 -0
  5. package/dist/auth.d.ts +11 -11
  6. package/dist/auth.js +28 -54
  7. package/dist/client.d.ts +62 -174
  8. package/dist/client.js +381 -354
  9. package/dist/clientTypes.d.ts +56 -0
  10. package/dist/clientTypes.js +5 -0
  11. package/dist/configuration.d.ts +48 -0
  12. package/dist/configuration.js +72 -0
  13. package/dist/consentDebit.d.ts +36 -0
  14. package/dist/consentDebit.js +95 -0
  15. package/dist/credentials.d.ts +116 -0
  16. package/dist/credentials.js +315 -0
  17. package/dist/debitPermissionWire.d.ts +90 -0
  18. package/dist/debitPermissionWire.js +118 -0
  19. package/dist/debitPermissions.d.ts +39 -0
  20. package/dist/debitPermissions.js +117 -0
  21. package/dist/deviceRecovery.d.ts +40 -0
  22. package/dist/deviceRecovery.js +143 -0
  23. package/dist/deviceShares.d.ts +91 -0
  24. package/dist/deviceShares.js +204 -0
  25. package/dist/errors.d.ts +280 -0
  26. package/dist/errors.js +85 -0
  27. package/dist/linkedRecovery.d.ts +40 -0
  28. package/dist/linkedRecovery.js +49 -0
  29. package/dist/linkedRegistrations.d.ts +11 -0
  30. package/dist/linkedRegistrations.js +109 -0
  31. package/dist/linkedWallets.d.ts +46 -18
  32. package/dist/linkedWallets.js +85 -120
  33. package/dist/me.d.ts +27 -0
  34. package/dist/me.js +30 -0
  35. package/dist/mobile.d.ts +64 -0
  36. package/dist/mobile.js +69 -0
  37. package/dist/redirects.d.ts +45 -0
  38. package/dist/redirects.js +108 -0
  39. package/dist/serviceGrants.d.ts +52 -0
  40. package/dist/serviceGrants.js +46 -0
  41. package/dist/slice4.d.ts +20 -6
  42. package/dist/slice4.js +22 -8
  43. package/dist/streams.js +1 -1
  44. package/dist/walletCreateGrant.d.ts +62 -0
  45. package/dist/walletCreateGrant.js +89 -0
  46. package/dist/walletDisplayParts.d.ts +2 -0
  47. package/dist/walletDisplayParts.js +4 -0
  48. package/dist/walletOperations.d.ts +23 -6
  49. package/dist/walletOperations.js +31 -22
  50. package/dist/webRenewal.d.ts +92 -0
  51. package/dist/webRenewal.js +179 -0
  52. package/package.json +25 -23
@@ -0,0 +1,56 @@
1
+ import type { MobileBrowserBridge, MobileSecureStorage } from "./mobile.ts";
2
+ import type { DeviceShareStore } from "./deviceShares.ts";
3
+ export interface FetchRequest {
4
+ url: string;
5
+ method?: string;
6
+ headers?: Record<string, string>;
7
+ body?: string;
8
+ credentials?: "include" | "omit";
9
+ cache?: "no-store";
10
+ signal?: AbortSignal;
11
+ }
12
+ export interface FetchResponse {
13
+ status: number;
14
+ ok: boolean;
15
+ json(): Promise<unknown>;
16
+ /** The answer as it arrives and its headers; only a signal stream reads them. */
17
+ body?: ReadableStream<Uint8Array> | null;
18
+ headers?: {
19
+ get(name: string): string | null;
20
+ };
21
+ }
22
+ export type FetchLike = (url: string, init?: Omit<FetchRequest, "url">) => Promise<FetchResponse>;
23
+ interface CommonDependencies {
24
+ fetch?: FetchLike;
25
+ crypto?: Crypto;
26
+ now?: () => number;
27
+ /**
28
+ * Where the SDK keeps the device key and the device share of each wallet it
29
+ * made the key for, and of each wallet it recovered onto this device. On the
30
+ * web it is the origin's IndexedDB when left out; on mobile there is none
31
+ * unless the app supplies one, and every call that asks the SDK for a share
32
+ * then refuses with device_share_unavailable.
33
+ */
34
+ deviceShares?: DeviceShareStore;
35
+ }
36
+ export interface WebDependencies extends CommonDependencies {
37
+ currentOrigin: () => string;
38
+ /**
39
+ * Opens a separate top-level browsing context; restore needs the final registered redirect back, the verifier staying in memory.
40
+ * A restore passes a signal that aborts once no caller waits for it any more: the host then closes the context it opened.
41
+ * A confirmation the SDK runs by itself (the recovery of a device share) needs the redirect back too.
42
+ */
43
+ openHosted: (url: string, signal?: AbortSignal) => Promise<string | void> | string | void;
44
+ }
45
+ export interface MobileDependencies extends CommonDependencies {
46
+ browserBridge: MobileBrowserBridge;
47
+ secureStorage: MobileSecureStorage;
48
+ }
49
+ export type ClientDependencies = WebDependencies | MobileDependencies;
50
+ export interface AuthAttempt {
51
+ requestId: string;
52
+ attemptId: string;
53
+ launchUrl: string;
54
+ expiresAt: number;
55
+ }
56
+ export {};
@@ -0,0 +1,5 @@
1
+ // The ports the host hands initializeAuthorizationClient: its fetch, its
2
+ // WebCrypto and clock, how a provider page is opened (a top-level window on
3
+ // the web, the system browser on mobile), the mobile secure storage, and where
4
+ // the device shares this SDK keeps live (deviceShares.ts).
5
+ export {};
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The public configuration a client is initialized with, checked before any
3
+ * request, and the provider's public description of a web client, read and
4
+ * checked against it at initialization. Neither holds any authority.
5
+ */
6
+ import { type ClientChannel, type IdentityMethod } from "./auth.ts";
7
+ import type { FetchLike } from "./client.ts";
8
+ export interface ClientConfiguration {
9
+ mode: "mode_b";
10
+ channel: ClientChannel;
11
+ /** The provider's API origin, such as https://demo.shuttle.technology; exact, HTTPS, no path. */
12
+ authorizationOrigin: string;
13
+ /** The web application's own exact origin; web only. */
14
+ applicationOrigin?: string;
15
+ /** Where the provider's pages return to: on the web, the page that starts the sign-in also completes it. */
16
+ redirectUri: string;
17
+ publishableClientId: string;
18
+ tenantId: string;
19
+ applicationId: string;
20
+ environmentId: string;
21
+ methods: readonly IdentityMethod[];
22
+ /** The app's bundle or package identifier; mobile only. */
23
+ mobileApplicationIdentifier?: string;
24
+ }
25
+ /**
26
+ * What an app passes. On the web, applicationOrigin and redirectUri may be left out: they are then
27
+ * the current page's origin and the current page (origin and path), so the page that starts a
28
+ * sign-in also completes it. Given, they are checked as they are. Mobile apps give both.
29
+ */
30
+ export type ClientSettings = Omit<ClientConfiguration, "applicationOrigin" | "redirectUri"> & {
31
+ applicationOrigin?: string;
32
+ redirectUri?: string;
33
+ };
34
+ /** The settings with the web defaults filled from the current page; nothing is filled on mobile. */
35
+ export declare function resolveConfiguration(settings: ClientSettings, currentOrigin: (() => string) | undefined): ClientConfiguration;
36
+ export interface PublicClientConfiguration {
37
+ requestId: string;
38
+ id: string;
39
+ name: string;
40
+ environmentId: string;
41
+ methods: readonly IdentityMethod[];
42
+ providerOrigin: string;
43
+ rpId: string;
44
+ redirects: readonly string[];
45
+ platform: ClientChannel;
46
+ }
47
+ export declare function validateConfiguration(configuration: ClientConfiguration): void;
48
+ export declare function readPublicConfiguration(configuration: ClientConfiguration, fetch: FetchLike): Promise<PublicClientConfiguration>;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The public configuration a client is initialized with, checked before any
3
+ * request, and the provider's public description of a web client, read and
4
+ * checked against it at initialization. Neither holds any authority.
5
+ */
6
+ import { ClientFlowError, identityMethods, opaqueId, parseAuthorizationError, record } from "./auth.js";
7
+ import { exactAppOrigin, exactAppRedirect, exactHttpsOrigin } from "./redirects.js";
8
+ import { mobileApplicationIdentifier } from "./mobile.js";
9
+ /** The settings with the web defaults filled from the current page; nothing is filled on mobile. */
10
+ export function resolveConfiguration(settings, currentOrigin) {
11
+ if (settings.channel !== "web")
12
+ return { ...settings };
13
+ const origin = settings.applicationOrigin ?? (typeof currentOrigin === "function" ? currentOrigin() : undefined);
14
+ const page = globalThis.location;
15
+ const redirect = settings.redirectUri ?? (origin !== undefined && page?.origin === origin && typeof page.pathname === "string" ? origin + page.pathname : undefined);
16
+ return { ...settings, applicationOrigin: origin, redirectUri: redirect };
17
+ }
18
+ export function validateConfiguration(configuration) {
19
+ const allowedFields = ["applicationId", "applicationOrigin", "authorizationOrigin", "channel", "environmentId", "methods", "mobileApplicationIdentifier", "mode", "publishableClientId", "redirectUri", "tenantId"];
20
+ if (Object.keys(configuration).some((field) => !allowedFields.includes(field)))
21
+ throw new ClientFlowError("invalid_public_configuration");
22
+ if (configuration.mode !== "mode_b" || (configuration.channel !== "web" && configuration.channel !== "mobile") || !exactHttpsOrigin(configuration.authorizationOrigin) || typeof configuration.redirectUri !== "string" || !exactAppRedirect(configuration.redirectUri) || !opaqueId(configuration.publishableClientId) || !opaqueId(configuration.tenantId) || !opaqueId(configuration.applicationId) || !opaqueId(configuration.environmentId) || configuration.methods.length === 0 || new Set(configuration.methods).size !== configuration.methods.length || configuration.methods.some((method) => !identityMethods.includes(method))) {
23
+ throw new ClientFlowError("invalid_public_configuration");
24
+ }
25
+ if (configuration.channel === "web" && (!configuration.applicationOrigin || !exactAppOrigin(configuration.applicationOrigin) || configuration.mobileApplicationIdentifier !== undefined))
26
+ throw new ClientFlowError("invalid_public_configuration");
27
+ if (configuration.channel === "mobile" && (configuration.applicationOrigin !== undefined || !mobileApplicationIdentifier(configuration.mobileApplicationIdentifier)))
28
+ throw new ClientFlowError("invalid_public_configuration");
29
+ }
30
+ export async function readPublicConfiguration(configuration, fetch) {
31
+ const response = await fetch(`${configuration.authorizationOrigin}/v1/public/clients/${encodeURIComponent(configuration.publishableClientId)}`, {
32
+ method: "GET",
33
+ headers: { Accept: "application/json" },
34
+ credentials: "omit",
35
+ cache: "no-store",
36
+ });
37
+ const payload = await response.json();
38
+ if (!response.ok)
39
+ throw parseAuthorizationError(payload, response.status);
40
+ const root = record(payload, ["request_id", "client"]);
41
+ const client = record(root.client, ["id", "name", "environment_id", "methods", "provider_origin", "rp_id", "redirects", "platform"]);
42
+ if (!opaqueId(root.request_id) || client.id !== configuration.publishableClientId || typeof client.name !== "string" || client.name.length < 1 || client.name.length > 128 || client.environment_id !== configuration.environmentId || !Array.isArray(client.methods) || client.methods.length < 1 || client.methods.length > identityMethods.length || !client.methods.every((method) => typeof method === "string" && identityMethods.includes(method)) || new Set(client.methods).size !== client.methods.length || !sameStrings(client.methods, configuration.methods) || client.provider_origin !== configuration.authorizationOrigin || typeof client.rp_id !== "string" || client.rp_id.length === 0 || !knownRedirects(client.redirects, configuration.redirectUri) || client.platform !== "web") {
43
+ throw new ClientFlowError("invalid_public_configuration");
44
+ }
45
+ return Object.freeze({
46
+ requestId: root.request_id,
47
+ id: client.id,
48
+ name: client.name,
49
+ environmentId: client.environment_id,
50
+ methods: Object.freeze([...client.methods]),
51
+ providerOrigin: client.provider_origin,
52
+ rpId: client.rp_id,
53
+ redirects: Object.freeze([...client.redirects]),
54
+ platform: client.platform,
55
+ });
56
+ }
57
+ // A client registered for any origin lists its redirects as exactly ["*"]: any redirect the app's
58
+ // own checks accept. Otherwise the app's redirect is one of the client's exact ones.
59
+ function knownRedirects(redirects, redirectUri) {
60
+ if (!Array.isArray(redirects))
61
+ return false;
62
+ if (redirects.length === 1 && redirects[0] === "*")
63
+ return true;
64
+ return redirects.length >= 1 && redirects.length <= 20 && redirects.every((redirect) => typeof redirect === "string" && exactAppRedirect(redirect)) &&
65
+ new Set(redirects).size === redirects.length && redirects.includes(redirectUri);
66
+ }
67
+ function sameStrings(left, right) {
68
+ if (left.length !== right.length)
69
+ return false;
70
+ const expected = new Set(right);
71
+ return left.every((value) => typeof value === "string" && expected.has(value));
72
+ }
@@ -0,0 +1,36 @@
1
+ import { type DebitAssetBound } from './debitPermissionWire.ts';
2
+ import type { DebitPermissions } from './debitPermissions.ts';
3
+ import type { LocalOperation, LocalOperationResult } from './slice4.ts';
4
+ import type { UserWalletOperation, WalletResult } from './walletOperations.ts';
5
+ /** The debit terms a grant froze from its service: per asset the most one debit and 24 hours may move, and the recipients (empty: any). */
6
+ export interface DebitTerms {
7
+ readonly assets: readonly DebitAssetBound[];
8
+ readonly recipients: readonly string[];
9
+ }
10
+ /**
11
+ * What became of the debit permission a consent creates. succeeded: the permission is active, operationId is the
12
+ * debit_permission.create operation and the permission's ID. not_set: it was not created, for the reason code says;
13
+ * the person then lets the service debit alone (createDebitPermission, with its own passkey) or opens the wallet to
14
+ * the service again. authorized is the provider's answer before the SDK executes it, and never reaches the app.
15
+ */
16
+ export interface ConsentDebitOutcome {
17
+ state: 'succeeded' | 'not_set' | 'authorized';
18
+ operationId?: string;
19
+ code?: string;
20
+ }
21
+ /** parseDebitTerms reads the terms a display names, in their closed form. */
22
+ export declare function parseDebitTerms(value: unknown): DebitTerms;
23
+ /** parseConsentDebitOutcome reads the debit member of a creation's or a grant's result: a code exactly for not_set. */
24
+ export declare function parseConsentDebitOutcome(value: unknown): ConsentDebitOutcome;
25
+ /** What settling needs of the client: its reads and its linked-wallet facade. */
26
+ export interface ConsentDebitClient {
27
+ walletOperation(operationId: string, signal?: AbortSignal): Promise<UserWalletOperation>;
28
+ operation(operationId: string, signal?: AbortSignal): Promise<LocalOperation>;
29
+ linkedWallets(): {
30
+ debitPermissions(): DebitPermissions;
31
+ };
32
+ }
33
+ /** settleCreationDebit settles the debit permission a creation's consent created beside its grant. */
34
+ export declare function settleCreationDebit(client: ConsentDebitClient, released: WalletResult, signal?: AbortSignal): Promise<WalletResult>;
35
+ /** settleGrantDebit settles the debit permission a grant's consent created. */
36
+ export declare function settleGrantDebit(client: ConsentDebitClient, grantOperationId: string, released: LocalOperationResult, signal?: AbortSignal): Promise<LocalOperationResult>;
@@ -0,0 +1,95 @@
1
+ // The debit permission that rides on a grant (owner decision of 28 October
2
+ // 2026). A service that holds wallet:debit:user carries the debit terms the
3
+ // platform's operator set for it. Whenever the person opens a wallet to such a
4
+ // service, by createWallet({ grant }) or by createServiceGrant(), the
5
+ // provider's page names those terms and the one passkey confirmation also
6
+ // creates the service's debit permission on the wallet. Nothing is for the app
7
+ // to pass: once the creation's or the grant's result is read, the provider
8
+ // has authorized the permission by that consent, and the SDK executes it at
9
+ // once, handing the wallet's device share to the platform share keeper sealed
10
+ // to the keeper's key, and only for the confirmed operation at its version.
11
+ // The result then says succeeded, or not_set with the code.
12
+ import { AuthorizationError, ClientFlowError, opaqueId, record } from './auth.js';
13
+ import { isAuthorizationErrorCode } from './errors.js';
14
+ import { recipients, wireBounds } from './debitPermissionWire.js';
15
+ import { lowerUUID } from './walletDisplayParts.js';
16
+ /** parseDebitTerms reads the terms a display names, in their closed form. */
17
+ export function parseDebitTerms(value) {
18
+ let terms;
19
+ try {
20
+ terms = record(value, ['assets', 'recipients']);
21
+ }
22
+ catch {
23
+ throw new ClientFlowError('invalid_server_response');
24
+ }
25
+ if (!recipients(terms.recipients))
26
+ throw new ClientFlowError('invalid_server_response');
27
+ return Object.freeze({ assets: wireBounds(terms.assets), recipients: Object.freeze([...terms.recipients]) });
28
+ }
29
+ /** parseConsentDebitOutcome reads the debit member of a creation's or a grant's result: a code exactly for not_set. */
30
+ export function parseConsentDebitOutcome(value) {
31
+ let outcome;
32
+ try {
33
+ outcome = record(value, ['state'], ['operation_id', 'code']);
34
+ }
35
+ catch {
36
+ throw new ClientFlowError('invalid_server_response');
37
+ }
38
+ const state = outcome.state;
39
+ if ((state !== 'authorized' && state !== 'succeeded' && state !== 'not_set') || (outcome.operation_id !== undefined && !lowerUUID(outcome.operation_id)) ||
40
+ (state === 'not_set') !== (outcome.code !== undefined) || (outcome.code !== undefined && !isAuthorizationErrorCode(outcome.code)) || (state !== 'not_set' && outcome.operation_id === undefined)) {
41
+ throw new ClientFlowError('invalid_server_response');
42
+ }
43
+ const parsed = { state };
44
+ if (outcome.operation_id !== undefined)
45
+ parsed.operationId = outcome.operation_id;
46
+ if (outcome.code !== undefined)
47
+ parsed.code = outcome.code;
48
+ return parsed;
49
+ }
50
+ /**
51
+ * settle executes a permission the consent authorized, for the wallet and service the consent was about, and answers
52
+ * succeeded or not_set. It hands the share this browser keeps and never recovers one: a browser without a current
53
+ * share answers not_set with device_share_unavailable. The execute's key is derived from the operation, so reading the
54
+ * result again repeats it safely.
55
+ */
56
+ async function settle(client, outcome, linkedWalletId, servicePrincipalId, signal) {
57
+ if (outcome.state !== 'authorized' || outcome.operationId === undefined)
58
+ return outcome;
59
+ const operationId = outcome.operationId;
60
+ try {
61
+ const read = await client.operation(operationId, signal);
62
+ if (read.display.kind !== 'debit_permission.create' || linkedWalletId === undefined || read.display.linkedWalletId !== linkedWalletId ||
63
+ servicePrincipalId === undefined || read.display.servicePrincipalId !== servicePrincipalId) {
64
+ return { state: 'not_set', operationId, code: 'invalid_server_response' };
65
+ }
66
+ if (read.state === 'succeeded')
67
+ return { state: 'succeeded', operationId };
68
+ // Reading a result never asks for a passkey: without a current share kept here, nothing is recovered or sent.
69
+ const executed = await client.linkedWallets().debitPermissions().executeConsentDebitPermission(operationId, read.version, `consent-debit-${operationId}`, signal);
70
+ return executed.state === 'succeeded' ? { state: 'succeeded', operationId } : { state: 'not_set', operationId, code: 'AUTHORIZATION_STALE' };
71
+ }
72
+ catch (error) {
73
+ if (error instanceof AuthorizationError || error instanceof ClientFlowError)
74
+ return { state: 'not_set', operationId, code: error.code };
75
+ throw error;
76
+ }
77
+ }
78
+ /** settleCreationDebit settles the debit permission a creation's consent created beside its grant. */
79
+ export async function settleCreationDebit(client, released, signal) {
80
+ const debit = released.result.debit;
81
+ if (debit === undefined || debit.state !== 'authorized')
82
+ return released;
83
+ const creation = await client.walletOperation(released.result.operationId, signal);
84
+ const service = creation.display.kind === 'wallet.create' && !('materialPurged' in creation.display) ? creation.display.grant?.servicePrincipalId : undefined;
85
+ return { ...released, result: { ...released.result, debit: await settle(client, debit, released.result.grant?.linkedWalletId, service, signal) } };
86
+ }
87
+ /** settleGrantDebit settles the debit permission a grant's consent created. */
88
+ export async function settleGrantDebit(client, grantOperationId, released, signal) {
89
+ const debit = released.result.debit;
90
+ if (debit === undefined || debit.state !== 'authorized' || !opaqueId(grantOperationId))
91
+ return released;
92
+ const grant = await client.operation(grantOperationId, signal);
93
+ const display = grant.display.kind === 'service_grant.set' ? grant.display : undefined;
94
+ return { ...released, result: { ...released.result, debit: await settle(client, debit, display?.walletId, display?.servicePrincipalId, signal) } };
95
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The session's credentials and every change to them, in one place.
3
+ *
4
+ * Every attempt takes a generation when it starts, before anything is
5
+ * awaited: a sign-in (code exchange), a renewal or restore, a logout. A
6
+ * session's lineage is the generation of the sign-in (or first restore) it
7
+ * comes from; its renewals carry it on, and one lineage is installed at a
8
+ * time. A sign-in installs while no newer sign-in or logout began. A renewal
9
+ * installs while the installed lineage did not change since it began (an
10
+ * epoch counts every change, to none included; a rotation within the lineage
11
+ * is no change) and it answers the same session of the same account: on the
12
+ * web the cookie is shared by every tab, and a sign-in in another tab puts its
13
+ * own session there. A renewal or restore of an installed session that
14
+ * answers another one installs nothing and ends the installed session
15
+ * (session_replaced); a client with no session takes up what the cookie holds.
16
+ * A definitive refusal ends the lineage of the token it refused, also after a
17
+ * rotation, and nothing else. A logout ends everything older.
18
+ *
19
+ * Every change of memory and of secure storage runs in one queue, one after
20
+ * another, failure cleanup included, and installs only while its attempt is
21
+ * current at that moment. A renewal reads the stored refresh token only in
22
+ * the queue and is checked again after the read and before it asks the
23
+ * provider. A superseded attempt undoes its own storage write and nothing
24
+ * else. A stored token whose cleanup failed stays marked ended, so no restore
25
+ * takes it up again. On the web nothing is stored: the refresh credential is
26
+ * the provider's cookie, and a web renewal or restore is checked against its
27
+ * attempt under the tabs' lock (webRenewal.ts).
28
+ */
29
+ import { type ClientChannel } from "./auth.ts";
30
+ import type { MobileSecureStorage } from "./mobile.ts";
31
+ export interface Session {
32
+ requestId: string;
33
+ accessExpiresAt: number;
34
+ accountId: string;
35
+ sessionId: string;
36
+ setupRequired?: boolean;
37
+ }
38
+ /**
39
+ * One sign-in, renewal or restore: its generation, the epoch of the installed
40
+ * lineage when it began, whether it founds a lineage (a restore with no
41
+ * session), the lineage it installs into (a founding restore takes up the
42
+ * lineage of a stored token it reads), and for a renewal of an installed
43
+ * session the session and account it must answer.
44
+ */
45
+ export interface Attempt {
46
+ readonly generation: number;
47
+ readonly kind: "sign_in" | "renewal";
48
+ readonly epoch: number;
49
+ readonly founds: boolean;
50
+ lineage: number;
51
+ readonly renews?: {
52
+ readonly sessionId: string;
53
+ readonly accountId: string;
54
+ };
55
+ }
56
+ /** The credentials held now. */
57
+ export interface Held {
58
+ readonly token: string;
59
+ readonly csrf?: string;
60
+ readonly session: Session;
61
+ readonly lineage: number;
62
+ }
63
+ /** A renewal that changed hands before it asked the provider anything. */
64
+ export declare class Superseded extends Error {
65
+ constructor();
66
+ }
67
+ export declare class Credentials {
68
+ #private;
69
+ constructor(channel: ClientChannel, storage: MobileSecureStorage | undefined, key: string);
70
+ /** current is the credentials held, while their lineage is the one installed. */
71
+ current(): Held | undefined;
72
+ get hasSession(): boolean;
73
+ /** usable is the token to send now: none while there is no session or a read was refused with it. */
74
+ usable(): string;
75
+ /** dispatchable says a request may still go out with token: of the lineage installed, and not refused by a read. */
76
+ dispatchable(token: string): boolean;
77
+ /** due says the token is to be renewed first: it lives less than marginSeconds more, or a read was refused with it. */
78
+ due(nowSeconds: number, marginSeconds: number): boolean;
79
+ /**
80
+ * markRefused marks a token a read was refused with, held now or rotated
81
+ * away, so no call waiting with it sends it; it says whether it is the
82
+ * token held now, which only a renewal replaces.
83
+ */
84
+ markRefused(token: string): boolean;
85
+ /** signIn starts a sign-in: no older sign-in installs after it. */
86
+ signIn(): Attempt;
87
+ /** renewal starts a renewal of the lineage installed now, or a restore that founds one when none is. */
88
+ renewal(): Attempt;
89
+ /** check throws Superseded once the renewal is no longer current, right before it asks the provider. */
90
+ check(attempt: Attempt): void;
91
+ /** superseded answers for an overtaken renewal: the session held now, or none. */
92
+ superseded(): Session;
93
+ /** refreshToken reads the stored refresh token for a mobile renewal, in the queue, and checks the renewal again after the read. */
94
+ refreshToken(attempt: Attempt): Promise<string>;
95
+ /**
96
+ * install keeps what an exchange or a renewal answered, in the queue, only
97
+ * while its attempt is current: in secure storage first on mobile, then in
98
+ * memory. A current attempt whose write fails ends its lineage and clears
99
+ * the held and stored credentials; an attempt overtaken during its write
100
+ * puts back what was stored before and installs nothing.
101
+ */
102
+ install(attempt: Attempt, payload: unknown, exchange: boolean): Promise<Session>;
103
+ /** refuse ends the lineage of a token the provider refused for good, whichever of its tokens it was. */
104
+ refuse(token: string): void;
105
+ /** renewalFailed answers for a failed renewal: overtaken, it is the session now; refused for good, its lineage ends. */
106
+ renewalFailed(attempt: Attempt, error: unknown): Session;
107
+ /** end ends one lineage at once, and clears its held and stored credentials in the queue. */
108
+ end(lineage: number): void;
109
+ /** logout ends the session held at once and every older attempt, clears memory and storage in the queue, and answers what it ended. */
110
+ logout(): {
111
+ token?: string;
112
+ csrf?: string;
113
+ sessionId?: string;
114
+ cleared: Promise<void>;
115
+ };
116
+ }