@icp-sdk/auth 8.0.2 → 9.0.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 (85) hide show
  1. package/dist/esm/client/app-delegation-source.d.ts +65 -0
  2. package/dist/esm/client/app-delegation-source.js +58 -0
  3. package/dist/esm/client/app-delegation-source.js.map +1 -0
  4. package/dist/esm/client/app-lock.d.ts +63 -0
  5. package/dist/esm/client/app-lock.js +92 -0
  6. package/dist/esm/client/app-lock.js.map +1 -0
  7. package/dist/esm/client/auth-client.d.ts +270 -53
  8. package/dist/esm/client/auth-client.js +1017 -414
  9. package/dist/esm/client/auth-client.js.map +1 -1
  10. package/dist/esm/client/base64.d.ts +8 -0
  11. package/dist/esm/client/base64.js +28 -0
  12. package/dist/esm/client/base64.js.map +1 -0
  13. package/dist/esm/client/cookie-state-storage.d.ts +59 -0
  14. package/dist/esm/client/cookie-state-storage.js +245 -0
  15. package/dist/esm/client/cookie-state-storage.js.map +1 -0
  16. package/dist/esm/client/credential-storage.d.ts +48 -0
  17. package/dist/esm/client/credential-storage.js +2 -0
  18. package/dist/esm/client/credential-storage.js.map +1 -0
  19. package/dist/esm/client/db.d.ts +6 -1
  20. package/dist/esm/client/db.js +56 -15
  21. package/dist/esm/client/db.js.map +1 -1
  22. package/dist/esm/client/foreground-refresh.d.ts +35 -0
  23. package/dist/esm/client/foreground-refresh.js +83 -0
  24. package/dist/esm/client/foreground-refresh.js.map +1 -0
  25. package/dist/esm/client/idb-credential-storage.d.ts +25 -0
  26. package/dist/esm/client/idb-credential-storage.js +69 -0
  27. package/dist/esm/client/idb-credential-storage.js.map +1 -0
  28. package/dist/esm/client/index.d.ts +14 -3
  29. package/dist/esm/client/index.js +13 -3
  30. package/dist/esm/client/index.js.map +1 -1
  31. package/dist/esm/client/local-credential-storage.d.ts +26 -0
  32. package/dist/esm/client/local-credential-storage.js +59 -0
  33. package/dist/esm/client/local-credential-storage.js.map +1 -0
  34. package/dist/esm/client/memory-credential-storage.d.ts +22 -0
  35. package/dist/esm/client/memory-credential-storage.js +35 -0
  36. package/dist/esm/client/memory-credential-storage.js.map +1 -0
  37. package/dist/esm/client/session-delegation.d.ts +40 -0
  38. package/dist/esm/client/session-delegation.js +100 -0
  39. package/dist/esm/client/session-delegation.js.map +1 -0
  40. package/dist/esm/client/session-identity.d.ts +97 -0
  41. package/dist/esm/client/session-identity.js +392 -0
  42. package/dist/esm/client/session-identity.js.map +1 -0
  43. package/dist/esm/client/session-minter.d.ts +94 -0
  44. package/dist/esm/client/session-minter.js +185 -0
  45. package/dist/esm/client/session-minter.js.map +1 -0
  46. package/dist/esm/client/shared-memory-credential-storage.d.ts +44 -0
  47. package/dist/esm/client/shared-memory-credential-storage.js +243 -0
  48. package/dist/esm/client/shared-memory-credential-storage.js.map +1 -0
  49. package/dist/esm/client/slots.d.ts +59 -0
  50. package/dist/esm/client/slots.js +67 -0
  51. package/dist/esm/client/slots.js.map +1 -0
  52. package/dist/esm/client/sso.d.ts +33 -0
  53. package/dist/esm/client/sso.js +120 -0
  54. package/dist/esm/client/sso.js.map +1 -0
  55. package/dist/esm/client/state-storage.d.ts +135 -0
  56. package/dist/esm/client/state-storage.js +178 -0
  57. package/dist/esm/client/state-storage.js.map +1 -0
  58. package/package.json +3 -2
  59. package/src/client/app-delegation-source.ts +86 -0
  60. package/src/client/app-lock.ts +120 -0
  61. package/src/client/auth-client.ts +1300 -487
  62. package/src/client/base64.ts +28 -0
  63. package/src/client/cookie-state-storage.ts +277 -0
  64. package/src/client/credential-storage.ts +56 -0
  65. package/src/client/db.ts +56 -12
  66. package/src/client/foreground-refresh.ts +92 -0
  67. package/src/client/idb-credential-storage.ts +82 -0
  68. package/src/client/index.ts +24 -8
  69. package/src/client/local-credential-storage.ts +68 -0
  70. package/src/client/memory-credential-storage.ts +41 -0
  71. package/src/client/session-delegation.ts +138 -0
  72. package/src/client/session-identity.ts +510 -0
  73. package/src/client/session-minter.ts +286 -0
  74. package/src/client/shared-memory-credential-storage.ts +303 -0
  75. package/src/client/slots.ts +78 -0
  76. package/src/client/sso.ts +122 -0
  77. package/src/client/state-storage.ts +284 -0
  78. package/dist/esm/client/idle-manager.d.ts +0 -83
  79. package/dist/esm/client/idle-manager.js +0 -100
  80. package/dist/esm/client/idle-manager.js.map +0 -1
  81. package/dist/esm/client/storage.d.ts +0 -51
  82. package/dist/esm/client/storage.js +0 -90
  83. package/dist/esm/client/storage.js.map +0 -1
  84. package/src/client/idle-manager.ts +0 -162
  85. package/src/client/storage.ts +0 -114
@@ -0,0 +1,100 @@
1
+ import { Delegation, DelegationChain } from '@icp-sdk/core/identity';
2
+ import { Principal } from '@icp-sdk/core/principal';
3
+ import { fromBase64, toBase64 } from './base64.js';
4
+ /**
5
+ * The method Internet Identity answers with a session rather than a long-lived
6
+ * delegation. Named for its provider because a JSON-RPC method travels over a
7
+ * transport shared with other signers, unlike a canister method.
8
+ */
9
+ const SESSION_DELEGATION_METHOD = 'ii_session_delegation';
10
+ /**
11
+ * The code Internet Identity denies a `prompt: 'none'` request with.
12
+ *
13
+ * In ICRC-25's 3xxx user-action range, which is what lets a caller tell a request
14
+ * that needs a ceremony apart from a transport or protocol failure.
15
+ */
16
+ const INTERACTION_REQUIRED_CODE = 3002;
17
+ /**
18
+ * Internet Identity had nothing to answer a silent request from.
19
+ *
20
+ * The one denial that is not a failure: it says a ceremony is needed, so the
21
+ * caller signs in interactively rather than retrying. It is also the only
22
+ * dependable evidence that a shared record is stale rather than replaced, which
23
+ * is what lets an origin retract a record it did not write.
24
+ *
25
+ * Thrown only for a request that asked not to render anything. An interactive
26
+ * sign-in that the user abandons is a different outcome and is not this.
27
+ */
28
+ export class InteractionRequiredError extends Error {
29
+ /**
30
+ * Why, where Internet Identity said: `login_required` where it holds no
31
+ * session, `account_selection_required` where it holds more than one and the
32
+ * request named none.
33
+ *
34
+ * A caller may word its prompt from this and does not have to branch on it, so
35
+ * it is deliberately a string rather than a union — a value this library does
36
+ * not know is not a reason to fail.
37
+ */
38
+ reason;
39
+ constructor(message, reason) {
40
+ super(message);
41
+ this.name = 'InteractionRequiredError';
42
+ this.reason = reason;
43
+ }
44
+ }
45
+ const looksLikeResult = (value) => {
46
+ const result = value;
47
+ return (typeof result?.publicKey === 'string' &&
48
+ Array.isArray(result?.signerDelegation) &&
49
+ // An empty list builds a chain whose earliest expiry is 0, so it reads as a
50
+ // session that expired at the epoch rather than as a bad response.
51
+ result.signerDelegation.length > 0);
52
+ };
53
+ /**
54
+ * Asks the identity provider for a session, and returns the chain it signed.
55
+ *
56
+ * The chain is restricted to the Internet Identity canister, so it is not
57
+ * something an application can call its own canisters with. It is what app
58
+ * delegations are minted from.
59
+ */
60
+ export async function requestSessionDelegation(signer, params) {
61
+ const response = await signer.sendRequest({
62
+ jsonrpc: '2.0',
63
+ id: globalThis.crypto.randomUUID(),
64
+ method: SESSION_DELEGATION_METHOD,
65
+ params: {
66
+ sessionPublicKey: toBase64(params.sessionPublicKey),
67
+ ...(params.maxTimeToLive === undefined
68
+ ? {}
69
+ : { maxTimeToLive: params.maxTimeToLive.toString() }),
70
+ ...(params.maxTimeToIdle === undefined
71
+ ? {}
72
+ : { maxTimeToIdle: params.maxTimeToIdle.toString() }),
73
+ ...(params.derivationOrigin === undefined
74
+ ? {}
75
+ : { icrc95DerivationOrigin: params.derivationOrigin }),
76
+ },
77
+ });
78
+ if ('error' in response) {
79
+ // The code carries the only thing a caller can act on, and dropping it left
80
+ // `interaction_required` indistinguishable from a transport failure except by
81
+ // matching a message this library does not define.
82
+ if (response.error.code === INTERACTION_REQUIRED_CODE) {
83
+ const data = response.error.data;
84
+ const reason = typeof data === 'object' && data !== null && 'reason' in data
85
+ ? String(data.reason)
86
+ : undefined;
87
+ throw new InteractionRequiredError(response.error.message, reason);
88
+ }
89
+ throw new Error(response.error.message);
90
+ }
91
+ const result = response.result;
92
+ if (!looksLikeResult(result)) {
93
+ throw new Error('Invalid session response: missing publicKey, or missing or empty signerDelegation');
94
+ }
95
+ return DelegationChain.fromDelegations(result.signerDelegation.map(({ delegation, signature }) => ({
96
+ delegation: new Delegation(fromBase64(delegation.pubkey), BigInt(delegation.expiration), delegation.targets?.map((target) => Principal.fromText(target))),
97
+ signature: fromBase64(signature),
98
+ })), fromBase64(result.publicKey));
99
+ }
100
+ //# sourceMappingURL=session-delegation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-delegation.js","sourceRoot":"","sources":["../../../src/client/session-delegation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ import type { DerEncodedPublicKey, HttpAgentRequest } from '@icp-sdk/core/agent';
2
+ import { DelegationChain, DelegationIdentity } from '@icp-sdk/core/identity';
3
+ import { Principal } from '@icp-sdk/core/principal';
4
+ import { type AppDelegationSource } from './app-delegation-source.js';
5
+ import type { CredentialStorage } from './credential-storage.js';
6
+ export interface SessionIdentityOptions {
7
+ /** The account's key, which every app delegation is rooted at. */
8
+ accountKey: DerEncodedPublicKey;
9
+ /** When the session itself ends. Nothing is minted past this. */
10
+ sessionExpiresAtMs: number;
11
+ source: AppDelegationSource;
12
+ /**
13
+ * Where the app credential is kept, and what makes the key a mint gets a
14
+ * delegation for.
15
+ *
16
+ * The store is what every tab of the origin reads, so a delegation minted in
17
+ * one is the delegation the others use.
18
+ */
19
+ storage: CredentialStorage;
20
+ /**
21
+ * The slot the app credential lives under, and the name of the lock that
22
+ * serialises minting into it.
23
+ *
24
+ * Taken rather than assumed, because the client assigns every name from one
25
+ * namespace: reaching for the bare constant here would read and write a slot
26
+ * the client never promotes into, so a namespaced client would mint again on
27
+ * every load and its sign-out would clear a slot nothing was using.
28
+ */
29
+ slot: string;
30
+ /**
31
+ * Called once when the session turns out to be gone, so the client can drop
32
+ * what it holds and tell its subscribers. Ending a session is the client's to
33
+ * do; this only reports it.
34
+ */
35
+ onSessionGone: () => void;
36
+ }
37
+ /**
38
+ * An identity whose credential is short-lived and replaced under the caller.
39
+ *
40
+ * It extends {@link DelegationIdentity} so anything already accepting one accepts
41
+ * this, and what it presents is an ordinary app delegation. What differs is that
42
+ * the key and delegation it signs with are replaced as they age, which is why an
43
+ * agent can hold this object for hours: an agent keeps whatever identity it was
44
+ * given, so a snapshot of one credential would sign with it until it expired.
45
+ *
46
+ * A rotation is invisible to the holder. The principal comes from the account's
47
+ * key rather than from whatever is current, and a request is signed and its
48
+ * delegation attached in the same act, so what is already in flight carries a
49
+ * matching pair however soon the next one arrives.
50
+ */
51
+ export declare class SessionIdentity extends DelegationIdentity {
52
+ #private;
53
+ /**
54
+ * Builds an identity for a session, resolving the account key it needs.
55
+ *
56
+ * The account key cannot be worked out from the session — the session chain is
57
+ * rooted at the session's own key, and the state carries the account's
58
+ * principal, which is a hash of the account key rather than the key. So the
59
+ * only two places one can come from are a credential the store already holds
60
+ * and a mint, and a caller with neither has to wait for the mint before an
61
+ * identity exists at all.
62
+ *
63
+ * That resolution goes through the same locked read-or-mint every rotation
64
+ * uses, which is what keeps the app slot to one writer: doing it here, outside
65
+ * the lock, is how a page load could overwrite a credential a peer tab had just
66
+ * minted, and it would skip the read that lets this tab adopt that credential
67
+ * instead of paying for its own.
68
+ *
69
+ * The identity comes back already holding what was resolved, so a caller does
70
+ * not have to refresh it before use.
71
+ * @param options - As the constructor takes them, less the account key.
72
+ */
73
+ static create(options: Omit<SessionIdentityOptions, 'accountKey'> & {
74
+ expectedAccount?: Principal;
75
+ }): Promise<SessionIdentity>;
76
+ constructor(options: SessionIdentityOptions);
77
+ getDelegation(): DelegationChain;
78
+ transformRequest(request: HttpAgentRequest): Promise<unknown>;
79
+ /**
80
+ * Says somebody is here, and mints now if one is due.
81
+ *
82
+ * For a caller that knows the moment is a good one, such as a page load, a tab
83
+ * coming back to the foreground, or a pointer moving. The caller says when;
84
+ * this still decides whether, so a credential with plenty of life left costs
85
+ * no call.
86
+ *
87
+ * It is never a no-op, though: being here counts as use, exactly as signing a
88
+ * request does, so the credential held afterwards has earned the next
89
+ * rotation. Without that a session bounded by how long it goes unminted would
90
+ * end under a user who is reading rather than clicking — the delegation would
91
+ * only ever be replaced in the last {@link PRE_MINT_THRESHOLD_MS} of its life,
92
+ * and only if a pointer happened to move inside that window.
93
+ */
94
+ refresh(): Promise<void>;
95
+ /** Drops the scheduled refresh. */
96
+ dispose(): void;
97
+ }
@@ -0,0 +1,392 @@
1
+ import { DelegationChain, DelegationIdentity } from '@icp-sdk/core/identity';
2
+ import { Principal } from '@icp-sdk/core/principal';
3
+ import { AccountMismatchError, chainAuthorisesKey, SessionGoneError, SessionUnsupportedError, } from './app-delegation-source.js';
4
+ import { withMintLock } from './app-lock.js';
5
+ /**
6
+ * How much life a delegation needs left to be handed to a caller. Covers a
7
+ * request's flight time, because the delegation has to still be valid when the
8
+ * replica verifies the request it was attached to.
9
+ */
10
+ const BLOCK_MARGIN_MS = 10_000;
11
+ /**
12
+ * When a replacement is minted. Covers one mint and nothing more: minting early
13
+ * discards the rest of a delegation's life, so an active session consumes
14
+ * lifetime at `ttl / (ttl - threshold)` and every second here costs update calls
15
+ * on every active session. A refresh is scheduled for this moment rather than
16
+ * waiting for a request to arrive inside the window, which is what keeps it small.
17
+ */
18
+ const PRE_MINT_THRESHOLD_MS = 15_000;
19
+ const expiresAtMs = (chain) => Number(chain.delegations.reduce((soonest, { delegation }) => delegation.expiration < soonest ? delegation.expiration : soonest, chain.delegations[0]?.delegation.expiration ?? 0n) / 1000000n);
20
+ const msLeftOf = (chain) => expiresAtMs(chain) - Date.now();
21
+ /** The account a chain is rooted at. */
22
+ const accountOf = (chain) => Principal.selfAuthenticating(new Uint8Array(chain.publicKey));
23
+ /**
24
+ * Whether a credential may be used for `expected`.
25
+ *
26
+ * Two checks at opposite ends of the chain. The leaf must be the key stored
27
+ * beside it, which is true of any whole record whoever it belongs to. And the
28
+ * root must be the account the caller names — a credential left by another
29
+ * account would otherwise decide who this identity is, and requests would go
30
+ * out as that account and succeed until its delegation expired.
31
+ *
32
+ * `expected` is absent only where the caller has no account to name, which is
33
+ * why a read that cannot check it does not adopt.
34
+ */
35
+ const isForAccount = (held, expected) => {
36
+ if (!chainAuthorisesKey(held.chain, held.identity.getPublicKey().toDer()))
37
+ return false;
38
+ return expected === undefined || accountOf(held.chain).compareTo(expected) === 'eq';
39
+ };
40
+ /**
41
+ * Reads what the slot holds, or `undefined` where it holds nothing usable.
42
+ *
43
+ * @param evictSpent - Whether a spent record may be removed, which only a caller
44
+ * holding the mint lock may do. The store reaches every tab of the origin, so a
45
+ * lock-free read that removed what it declined could delete a credential
46
+ * another tab minted between the read and the removal.
47
+ * @param verify - Rejects a credential not rooted at the account already
48
+ * established. Absent on the first read of all, where there is no account to
49
+ * compare against and the first mint is what establishes one.
50
+ */
51
+ async function readSlot({ storage, slot, evictSpent, verify, }) {
52
+ const stored = await storage.get(slot).catch(() => null);
53
+ if (!stored?.chain)
54
+ return undefined;
55
+ // A spent credential goes rather than merely being declined. The state is the
56
+ // only thing meant to outlive a delegation, and it records who is signed in on
57
+ // its own, so nothing here is worth keeping once it can no longer sign. This is
58
+ // what bounds a stored credential at one delegation lifetime with nothing
59
+ // having to fire — there is no dependable signal for a browser closing.
60
+ //
61
+ // Both halves together, because they are one record: the key is the thing that
62
+ // signs, and a chain without it authorises nothing.
63
+ if (msLeftOf(stored.chain) <= 0) {
64
+ if (evictSpent) {
65
+ // Best effort. A credential that cannot be removed is still one this tab
66
+ // refuses, so a failure costs tidiness and not correctness.
67
+ await storage.remove(slot).catch(() => undefined);
68
+ }
69
+ return undefined;
70
+ }
71
+ const credential = { identity: stored.identity, chain: stored.chain };
72
+ if (verify !== undefined && !verify(credential))
73
+ return undefined;
74
+ return credential;
75
+ }
76
+ /**
77
+ * The one place an app credential is minted and written.
78
+ *
79
+ * Reads the slot under the lock and mints only where what it finds cannot be
80
+ * used. The lock alone would merely serialise: five tabs waking together would
81
+ * queue politely and then make five calls one after another. The read inside it
82
+ * is what turns serialising into suppressing, and it is why the floor is one mint
83
+ * per origin rather than one per tab.
84
+ *
85
+ * Shared by the first acquisition and by every rotation, so both go through the
86
+ * same lock and the same read. Having a second reader-and-writer outside the lock
87
+ * is how a load could land on top of a peer's freshly minted credential.
88
+ */
89
+ async function acquireCredential({ storage, slot, source, lockName, verify, adopt, onGone, }) {
90
+ return withMintLock(lockName, async (stolen) => {
91
+ const stored = await readSlot({ storage, slot, evictSpent: true, verify });
92
+ // Read either way, so a spent record is evicted; adopted only where the
93
+ // caller could say which account it expects.
94
+ if (adopt && stored && msLeftOf(stored.chain) > PRE_MINT_THRESHOLD_MS)
95
+ return stored;
96
+ const identity = await storage.create();
97
+ let chain;
98
+ try {
99
+ chain = await source.mint(identity.getPublicKey().toDer());
100
+ }
101
+ catch (error) {
102
+ if (error instanceof SessionGoneError)
103
+ onGone?.();
104
+ throw error;
105
+ }
106
+ const credential = { identity, chain };
107
+ if (verify !== undefined && !verify(credential)) {
108
+ throw new AccountMismatchError();
109
+ }
110
+ // Checked here, after the calls have returned and before anything is written:
111
+ // a sign-out elsewhere took the lock away while this was in flight, and it
112
+ // has already cleared the slot this would write to. Storing now would put a
113
+ // credential back for a session that has ended.
114
+ if (stolen.aborted) {
115
+ onGone?.();
116
+ throw new SessionGoneError('The session ended while this mint was in flight');
117
+ }
118
+ // A credential that cannot be written is still one this tab can use. The cost
119
+ // of a failed write is that other tabs mint for themselves, which is what
120
+ // they do without a lock at all — it is not a reason to fail the request that
121
+ // is waiting on this.
122
+ await storage.set(slot, credential).catch(() => undefined);
123
+ return credential;
124
+ });
125
+ }
126
+ /** Whether a session has enough left that minting against it returns something usable. */
127
+ const sessionHasLifeLeft = (sessionExpiresAtMs) => sessionExpiresAtMs - Date.now() > BLOCK_MARGIN_MS;
128
+ /** The lock a mint is serialised on, or `null` where none is worth taking. */
129
+ const lockNameFor = (storage, slot) =>
130
+ // A store no other tab can read has nothing another tab could adopt, so
131
+ // serialising would spread the mints without preventing any of them.
132
+ storage.shared ? slot : null;
133
+ /**
134
+ * An identity whose credential is short-lived and replaced under the caller.
135
+ *
136
+ * It extends {@link DelegationIdentity} so anything already accepting one accepts
137
+ * this, and what it presents is an ordinary app delegation. What differs is that
138
+ * the key and delegation it signs with are replaced as they age, which is why an
139
+ * agent can hold this object for hours: an agent keeps whatever identity it was
140
+ * given, so a snapshot of one credential would sign with it until it expired.
141
+ *
142
+ * A rotation is invisible to the holder. The principal comes from the account's
143
+ * key rather than from whatever is current, and a request is signed and its
144
+ * delegation attached in the same act, so what is already in flight carries a
145
+ * matching pair however soon the next one arrives.
146
+ */
147
+ export class SessionIdentity extends DelegationIdentity {
148
+ #source;
149
+ #storage;
150
+ #slot;
151
+ #onSessionGone;
152
+ #sessionExpiresAtMs;
153
+ /** The account every credential must be rooted at, derived once. */
154
+ #account;
155
+ /** Reached by the base class's `sign`, so rotating does not change the object. */
156
+ #held;
157
+ #inFlight;
158
+ #currentWasUsed = false;
159
+ #scheduled;
160
+ #reportedGone = false;
161
+ #unsupported;
162
+ /**
163
+ * Builds an identity for a session, resolving the account key it needs.
164
+ *
165
+ * The account key cannot be worked out from the session — the session chain is
166
+ * rooted at the session's own key, and the state carries the account's
167
+ * principal, which is a hash of the account key rather than the key. So the
168
+ * only two places one can come from are a credential the store already holds
169
+ * and a mint, and a caller with neither has to wait for the mint before an
170
+ * identity exists at all.
171
+ *
172
+ * That resolution goes through the same locked read-or-mint every rotation
173
+ * uses, which is what keeps the app slot to one writer: doing it here, outside
174
+ * the lock, is how a page load could overwrite a credential a peer tab had just
175
+ * minted, and it would skip the read that lets this tab adopt that credential
176
+ * instead of paying for its own.
177
+ *
178
+ * The identity comes back already holding what was resolved, so a caller does
179
+ * not have to refresh it before use.
180
+ * @param options - As the constructor takes them, less the account key.
181
+ */
182
+ static async create(options) {
183
+ if (!sessionHasLifeLeft(options.sessionExpiresAtMs)) {
184
+ options.onSessionGone();
185
+ throw new SessionGoneError('The session has expired');
186
+ }
187
+ const credential = await acquireCredential({
188
+ storage: options.storage,
189
+ slot: options.slot,
190
+ source: options.source,
191
+ lockName: lockNameFor(options.storage, options.slot),
192
+ verify: (candidate) => isForAccount(candidate, options.expectedAccount),
193
+ // A caller that cannot name the account has nothing to check the slot
194
+ // against, so it mints rather than trusting what is there: a mint reports
195
+ // the account, and is the only thing that can establish one.
196
+ adopt: options.expectedAccount !== undefined,
197
+ onGone: options.onSessionGone,
198
+ });
199
+ const identity = new SessionIdentity({
200
+ ...options,
201
+ accountKey: credential.chain.publicKey,
202
+ });
203
+ identity.#adopt(credential);
204
+ return identity;
205
+ }
206
+ constructor(options) {
207
+ const held = {};
208
+ // The base signs through this, so what it holds can be replaced without the
209
+ // object an application is holding changing. The account's key is the root of
210
+ // every app delegation, so an empty chain rooted at it answers getPublicKey()
211
+ // and getPrincipal() before anything is minted.
212
+ super({
213
+ sign: (blob) => {
214
+ const key = held.current?.identity;
215
+ if (!key)
216
+ throw new Error('This identity holds no delegation yet');
217
+ return key.sign(blob);
218
+ },
219
+ }, DelegationChain.fromDelegations([], options.accountKey));
220
+ this.#held = held;
221
+ this.#source = options.source;
222
+ this.#storage = options.storage;
223
+ this.#slot = options.slot;
224
+ this.#onSessionGone = options.onSessionGone;
225
+ this.#sessionExpiresAtMs = options.sessionExpiresAtMs;
226
+ this.#account = Principal.selfAuthenticating(new Uint8Array(options.accountKey));
227
+ }
228
+ getDelegation() {
229
+ return this.#held.current?.chain ?? super.getDelegation();
230
+ }
231
+ async transformRequest(request) {
232
+ const credential = await this.#usable();
233
+ this.#currentWasUsed = true;
234
+ return DelegationIdentity.fromDelegation(credential.identity, credential.chain).transformRequest(request);
235
+ }
236
+ /**
237
+ * Says somebody is here, and mints now if one is due.
238
+ *
239
+ * For a caller that knows the moment is a good one, such as a page load, a tab
240
+ * coming back to the foreground, or a pointer moving. The caller says when;
241
+ * this still decides whether, so a credential with plenty of life left costs
242
+ * no call.
243
+ *
244
+ * It is never a no-op, though: being here counts as use, exactly as signing a
245
+ * request does, so the credential held afterwards has earned the next
246
+ * rotation. Without that a session bounded by how long it goes unminted would
247
+ * end under a user who is reading rather than clicking — the delegation would
248
+ * only ever be replaced in the last {@link PRE_MINT_THRESHOLD_MS} of its life,
249
+ * and only if a pointer happened to move inside that window.
250
+ */
251
+ async refresh() {
252
+ await this.#refreshIfDue();
253
+ // After, not before: both branches below can replace what is held, and
254
+ // adopting resets this.
255
+ this.#currentWasUsed = true;
256
+ }
257
+ async #refreshIfDue() {
258
+ const current = this.#held.current;
259
+ if (current && this.#msLeft(current.chain) > PRE_MINT_THRESHOLD_MS)
260
+ return;
261
+ if (current === undefined) {
262
+ // Nothing held yet, so what another tab left is worth having before minting.
263
+ const stored = await this.#readStored();
264
+ if (stored && this.#msLeft(stored.chain) > PRE_MINT_THRESHOLD_MS) {
265
+ this.#adopt(stored);
266
+ return;
267
+ }
268
+ }
269
+ await this.#mint().catch(() => undefined);
270
+ }
271
+ /** Drops the scheduled refresh. */
272
+ dispose() {
273
+ clearTimeout(this.#scheduled);
274
+ this.#scheduled = undefined;
275
+ }
276
+ get #lockName() {
277
+ return lockNameFor(this.#storage, this.#slot);
278
+ }
279
+ #isForThisSession(held) {
280
+ return isForAccount(held, this.#account);
281
+ }
282
+ #msLeft(chain) {
283
+ return expiresAtMs(chain) - Date.now();
284
+ }
285
+ /**
286
+ * The stored credential, where there is one this session may use.
287
+ *
288
+ * Whatever another tab minted arrives this way: sharing is a read of the store
289
+ * rather than a conversation, which works whether the other tabs are running,
290
+ * frozen, or gone.
291
+ *
292
+ * Takes no lock, so it declines a spent record rather than evicting it. The
293
+ * mint that follows evicts under the lock, per {@link readSlot}.
294
+ */
295
+ #readStored() {
296
+ return readSlot({
297
+ storage: this.#storage,
298
+ slot: this.#slot,
299
+ evictSpent: false,
300
+ verify: (candidate) => this.#isForThisSession(candidate),
301
+ });
302
+ }
303
+ async #usable() {
304
+ const current = this.#held.current;
305
+ if (current) {
306
+ const left = this.#msLeft(current.chain);
307
+ if (left > PRE_MINT_THRESHOLD_MS)
308
+ return current;
309
+ if (left > BLOCK_MARGIN_MS) {
310
+ // Enough life to serve this request while a replacement is on its way.
311
+ void this.#mint().catch(() => undefined);
312
+ return current;
313
+ }
314
+ }
315
+ return this.#mint();
316
+ }
317
+ #mint() {
318
+ this.#inFlight ??= this.#mintOnce().finally(() => {
319
+ this.#inFlight = undefined;
320
+ });
321
+ return this.#inFlight;
322
+ }
323
+ async #mintOnce() {
324
+ // A session this library cannot act for will answer the same way every time,
325
+ // so nothing is gained by asking again: the rotation stops and the error
326
+ // reaches the caller. The record is left alone — the sign-in is alive, and
327
+ // retracting it would sign every tab on the domain out of it.
328
+ if (this.#unsupported)
329
+ throw this.#unsupported;
330
+ // A delegation lasts min(its ttl, what remains of the session), so minting
331
+ // against an almost finished session returns one already too short to use,
332
+ // and refreshing on the delegation alone would mint without end.
333
+ if (!sessionHasLifeLeft(this.#sessionExpiresAtMs)) {
334
+ this.#reportGone();
335
+ throw new SessionGoneError('The session has expired');
336
+ }
337
+ let credential;
338
+ try {
339
+ credential = await acquireCredential({
340
+ storage: this.#storage,
341
+ slot: this.#slot,
342
+ source: this.#source,
343
+ lockName: this.#lockName,
344
+ // Every mint after the first must be rooted at the account already
345
+ // established: one that is not is a failed mint, never a new principal.
346
+ verify: (candidate) => this.#isForThisSession(candidate),
347
+ adopt: true,
348
+ onGone: () => this.#reportGone(),
349
+ });
350
+ }
351
+ catch (error) {
352
+ // Latched, and the rotation dropped: asking again would return the same
353
+ // answer, and a timer that keeps firing for it is a timer that never stops.
354
+ if (error instanceof SessionUnsupportedError) {
355
+ this.#unsupported = error;
356
+ this.dispose();
357
+ }
358
+ throw error;
359
+ }
360
+ this.#adopt(credential);
361
+ return credential;
362
+ }
363
+ #adopt(credential) {
364
+ this.#held.current = credential;
365
+ this.#currentWasUsed = false;
366
+ this.#schedule(credential.chain);
367
+ }
368
+ #schedule(chain) {
369
+ clearTimeout(this.#scheduled);
370
+ const delay = this.#msLeft(chain) - PRE_MINT_THRESHOLD_MS;
371
+ if (delay <= 0)
372
+ return;
373
+ this.#scheduled = setTimeout(() => {
374
+ // Only refresh a credential something used — signing a request, or a
375
+ // caller saying somebody is here. Each one earns the next refresh and no
376
+ // more, so an application nobody is using lets its delegation lapse rather
377
+ // than refreshing for as long as a tab is open.
378
+ if (this.#currentWasUsed)
379
+ void this.#mint().catch(() => undefined);
380
+ }, delay);
381
+ // Never hold a Node process open for a refresh nobody is waiting on.
382
+ this.#scheduled.unref?.();
383
+ }
384
+ #reportGone() {
385
+ if (this.#reportedGone)
386
+ return;
387
+ this.#reportedGone = true;
388
+ this.dispose();
389
+ this.#onSessionGone();
390
+ }
391
+ }
392
+ //# sourceMappingURL=session-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-identity.js","sourceRoot":"","sources":["../../../src/client/session-identity.ts"],"names":[],"mappings":"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"}