@icp-sdk/auth 8.0.3 → 10.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 +178 -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 +5 -4
  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 +277 -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,94 @@
1
+ import { type DerEncodedPublicKey, type HttpAgentOptions, type SignIdentity } from '@icp-sdk/core/agent';
2
+ import { DelegationChain } from '@icp-sdk/core/identity';
3
+ import type { Principal } from '@icp-sdk/core/principal';
4
+ import { type AppDelegationSource } from './app-delegation-source.js';
5
+ type AppSessionError = {
6
+ NoSuchSession: null;
7
+ } | {
8
+ NoSuchDelegation: null;
9
+ } | {
10
+ InternalCanisterError: string;
11
+ };
12
+ type Result<T> = {
13
+ Ok: T;
14
+ } | {
15
+ Err: AppSessionError;
16
+ };
17
+ /**
18
+ * Unwraps a canister result, turning the one terminal error into
19
+ * {@link SessionGoneError}, the retryable one into
20
+ * {@link NoSuchDelegationError}, and everything else into a plain failure.
21
+ */
22
+ export declare const unwrapSessionResult: <T>(result: Result<T>) => T;
23
+ export interface SessionMinterOptions {
24
+ /** The key the session chain delegates to. Signs the calls made here. */
25
+ sessionKey: SignIdentity;
26
+ /** The chain proving those calls come from the session. Restricted to II. */
27
+ sessionChain: DelegationChain;
28
+ /** The canister these calls go to. */
29
+ canisterId: Principal;
30
+ /**
31
+ * Options for the agent making them. `identity` is not among them: the agent
32
+ * signs as the session, which is what a mint call rests on.
33
+ */
34
+ agentOptions?: Omit<HttpAgentOptions, 'identity'>;
35
+ }
36
+ /** Mints app delegations by asking the Internet Identity canister for one. */
37
+ export declare class SessionMinter implements AppDelegationSource {
38
+ #private;
39
+ private constructor();
40
+ static create(options: SessionMinterOptions): Promise<SessionMinter>;
41
+ /**
42
+ * Prepares and fetches an app delegation, once more if the fetch finds nothing
43
+ * signed for what the prepare handed out.
44
+ *
45
+ * The pair is two calls, and only the first of them is an update: an expiration
46
+ * the canister will not sign for is one this session no longer holds, and the
47
+ * answer is to prepare again rather than to sign in again. Attempted a second
48
+ * time and no further — a third would be pursuing something other than the race
49
+ * this covers.
50
+ */
51
+ mint(appPublicKey: DerEncodedPublicKey): Promise<DelegationChain>;
52
+ /**
53
+ * Ends the session at the canister.
54
+ *
55
+ * Idempotent at the canister: a session that is already gone is `Ok`, because
56
+ * the caller wanted it gone and it is gone. What comes back as an error is the
57
+ * canister failing to write, and it is raised rather than dropped — an
58
+ * application that told a user it signed them out of their apps should be able
59
+ * to find out that it did not. The call can also fail the way any update call
60
+ * can, and that rejection is the caller's to decide about too.
61
+ */
62
+ revoke(): Promise<void>;
63
+ }
64
+ /**
65
+ * Assembles the one-hop chain an app signs its calls with, rooted at the
66
+ * account's own key.
67
+ */
68
+ export declare function appDelegationChain(userKey: Uint8Array, signed: {
69
+ delegation: {
70
+ pubkey: Uint8Array;
71
+ expiration: bigint;
72
+ targets: [] | [Principal[]];
73
+ permissions: [] | [string];
74
+ };
75
+ signature: Uint8Array;
76
+ }): DelegationChain;
77
+ /**
78
+ * Refuses a chain that is not a session for `canisterId`.
79
+ *
80
+ * Targets restrict what the chain's final key may call, and the effective
81
+ * restriction is the intersection of the hops that name any: a hop with no
82
+ * targets imposes nothing, so one restricting hop is enough to confine the key.
83
+ * Internet Identity names the canister on both of its hops — on the one the
84
+ * canister signs to the provider's own key, and again on the provider's
85
+ * extension to the application's key — and the intersection is what is checked,
86
+ * so a hop that stopped naming it would still leave the chain confined by the
87
+ * other.
88
+ *
89
+ * So what is refused is a chain no hop restricts, which the session key would
90
+ * otherwise be able to sign any call with, and one whose restrictions do not
91
+ * come out as this canister alone.
92
+ */
93
+ export declare function assertChainReaches(chain: DelegationChain, canisterId: Principal): void;
94
+ export {};
@@ -0,0 +1,178 @@
1
+ import { Actor, HttpAgent, } from '@icp-sdk/core/agent';
2
+ import { Delegation, DelegationChain, DelegationIdentity } from '@icp-sdk/core/identity';
3
+ import { SessionGoneError } from './app-delegation-source.js';
4
+ /**
5
+ * Just the three methods an app calls with a session chain. Written out rather
6
+ * than generated because this package carries no Internet Identity declarations,
7
+ * and generating them for three methods would mean carrying the whole interface.
8
+ */
9
+ const idlFactory = ({ IDL }) => {
10
+ const SessionKey = IDL.Vec(IDL.Nat8);
11
+ const Timestamp = IDL.Nat64;
12
+ const AppSessionError = IDL.Variant({
13
+ NoSuchSession: IDL.Null,
14
+ NoSuchDelegation: IDL.Null,
15
+ InternalCanisterError: IDL.Text,
16
+ });
17
+ const CandidDelegation = IDL.Record({
18
+ pubkey: SessionKey,
19
+ expiration: Timestamp,
20
+ targets: IDL.Opt(IDL.Vec(IDL.Principal)),
21
+ permissions: IDL.Opt(IDL.Text),
22
+ });
23
+ return IDL.Service({
24
+ app_prepare_delegation: IDL.Func([IDL.Record({ session_key: SessionKey })], [
25
+ IDL.Variant({
26
+ Ok: IDL.Record({ user_key: SessionKey, expiration: Timestamp }),
27
+ Err: AppSessionError,
28
+ }),
29
+ ], []),
30
+ app_get_delegation: IDL.Func([IDL.Record({ session_key: SessionKey, expiration: Timestamp })], [
31
+ IDL.Variant({
32
+ Ok: IDL.Record({ delegation: CandidDelegation, signature: SessionKey }),
33
+ Err: AppSessionError,
34
+ }),
35
+ ], ['query']),
36
+ app_revoke_session: IDL.Func([], [IDL.Variant({ Ok: IDL.Null, Err: AppSessionError })], []),
37
+ });
38
+ };
39
+ const isGone = (error) => 'NoSuchSession' in error;
40
+ /**
41
+ * Raised when the canister signed nothing for the key and expiration asked for,
42
+ * which means the expiration was not one `app_prepare_delegation` returned.
43
+ *
44
+ * Its own error because the remedy is its own: prepare again and use what comes
45
+ * back. Signing in afresh is not it — the session is live, or the call would have
46
+ * been refused for being no session at all.
47
+ */
48
+ class NoSuchDelegationError extends Error {
49
+ constructor() {
50
+ super('Internet Identity signed no delegation for this key and expiration');
51
+ this.name = 'NoSuchDelegationError';
52
+ }
53
+ }
54
+ /**
55
+ * Unwraps a canister result, turning the one terminal error into
56
+ * {@link SessionGoneError}, the retryable one into
57
+ * {@link NoSuchDelegationError}, and everything else into a plain failure.
58
+ */
59
+ export const unwrapSessionResult = (result) => {
60
+ if ('Ok' in result)
61
+ return result.Ok;
62
+ if (isGone(result.Err))
63
+ throw new SessionGoneError();
64
+ if ('NoSuchDelegation' in result.Err)
65
+ throw new NoSuchDelegationError();
66
+ // A candid variant, so interpolating it directly renders "[object Object]" and
67
+ // throws away the canister's own explanation of what went wrong.
68
+ const detail = 'InternalCanisterError' in result.Err ? result.Err.InternalCanisterError : '';
69
+ throw new Error(`Internet Identity could not mint a delegation${detail === '' ? '' : `: ${detail}`}`);
70
+ };
71
+ /** Mints app delegations by asking the Internet Identity canister for one. */
72
+ export class SessionMinter {
73
+ #service;
74
+ constructor(service) {
75
+ this.#service = service;
76
+ }
77
+ static async create(options) {
78
+ const { canisterId } = options;
79
+ assertChainReaches(options.sessionChain, canisterId);
80
+ const agent = await HttpAgent.create({
81
+ ...options.agentOptions,
82
+ // Last, so no agent option can replace the session these calls are made as.
83
+ identity: DelegationIdentity.fromDelegation(options.sessionKey, options.sessionChain),
84
+ });
85
+ return new SessionMinter(Actor.createActor(idlFactory, { agent, canisterId }));
86
+ }
87
+ /**
88
+ * Prepares and fetches an app delegation, once more if the fetch finds nothing
89
+ * signed for what the prepare handed out.
90
+ *
91
+ * The pair is two calls, and only the first of them is an update: an expiration
92
+ * the canister will not sign for is one this session no longer holds, and the
93
+ * answer is to prepare again rather than to sign in again. Attempted a second
94
+ * time and no further — a third would be pursuing something other than the race
95
+ * this covers.
96
+ */
97
+ async mint(appPublicKey) {
98
+ try {
99
+ return await this.#mintOnce(appPublicKey);
100
+ }
101
+ catch (error) {
102
+ if (!(error instanceof NoSuchDelegationError))
103
+ throw error;
104
+ return await this.#mintOnce(appPublicKey);
105
+ }
106
+ }
107
+ /**
108
+ * Ends the session at the canister.
109
+ *
110
+ * Idempotent at the canister: a session that is already gone is `Ok`, because
111
+ * the caller wanted it gone and it is gone. What comes back as an error is the
112
+ * canister failing to write, and it is raised rather than dropped — an
113
+ * application that told a user it signed them out of their apps should be able
114
+ * to find out that it did not. The call can also fail the way any update call
115
+ * can, and that rejection is the caller's to decide about too.
116
+ */
117
+ async revoke() {
118
+ unwrapSessionResult(await this.#service.app_revoke_session());
119
+ }
120
+ async #mintOnce(appPublicKey) {
121
+ const prepared = unwrapSessionResult(await this.#service.app_prepare_delegation({ session_key: appPublicKey }));
122
+ const signed = unwrapSessionResult(await this.#service.app_get_delegation({
123
+ session_key: appPublicKey,
124
+ // Exactly what was prepared. A different value is a different signature.
125
+ expiration: prepared.expiration,
126
+ }));
127
+ return appDelegationChain(prepared.user_key, signed);
128
+ }
129
+ }
130
+ /**
131
+ * Assembles the one-hop chain an app signs its calls with, rooted at the
132
+ * account's own key.
133
+ */
134
+ export function appDelegationChain(userKey, signed) {
135
+ return DelegationChain.fromDelegations([
136
+ {
137
+ delegation: new Delegation(signed.delegation.pubkey, signed.delegation.expiration, signed.delegation.targets[0], signed.delegation.permissions[0]),
138
+ signature: signed.signature,
139
+ },
140
+ ], userKey);
141
+ }
142
+ /**
143
+ * Refuses a chain that is not a session for `canisterId`.
144
+ *
145
+ * Targets restrict what the chain's final key may call, and the effective
146
+ * restriction is the intersection of the hops that name any: a hop with no
147
+ * targets imposes nothing, so one restricting hop is enough to confine the key.
148
+ * Internet Identity names the canister on both of its hops — on the one the
149
+ * canister signs to the provider's own key, and again on the provider's
150
+ * extension to the application's key — and the intersection is what is checked,
151
+ * so a hop that stopped naming it would still leave the chain confined by the
152
+ * other.
153
+ *
154
+ * So what is refused is a chain no hop restricts, which the session key would
155
+ * otherwise be able to sign any call with, and one whose restrictions do not
156
+ * come out as this canister alone.
157
+ */
158
+ export function assertChainReaches(chain, canisterId) {
159
+ const hops = chain.delegations.map(({ delegation }) => delegation.targets);
160
+ const restricting = hops.filter((hopTargets) => hopTargets !== undefined);
161
+ if (restricting.length === 0) {
162
+ throw new Error(`A session chain must be restricted to ${canisterId.toText()}, but no hop of this one names any canister`);
163
+ }
164
+ // An empty list names nothing rather than naming this canister, and a chain
165
+ // that can call nothing is not a session either.
166
+ const namingNothing = restricting.some((hopTargets) => hopTargets.length === 0);
167
+ if (namingNothing) {
168
+ throw new Error(`A session chain must be restricted to ${canisterId.toText()}, but a hop of this one names nothing`);
169
+ }
170
+ const elsewhere = restricting
171
+ .flat()
172
+ .filter((target) => target.compareTo(canisterId) !== 'eq')
173
+ .map((target) => target.toText());
174
+ if (elsewhere.length > 0) {
175
+ throw new Error(`A session chain must be restricted to ${canisterId.toText()}, but this one also names ${[...new Set(elsewhere)].join(', ')}`);
176
+ }
177
+ }
178
+ //# sourceMappingURL=session-minter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-minter.js","sourceRoot":"","sources":["../../../src/client/session-minter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { ECDSAKeyIdentity } from '@icp-sdk/core/identity';
2
+ import type { Credential, CredentialStorage } from './credential-storage.js';
3
+ /** The channel and lock prefix every instance agrees on by default. */
4
+ export declare const DEFAULT_SHARED_MEMORY_NAME = "ic-credentials";
5
+ /**
6
+ * Credentials in memory, replicated to the other tabs of this origin.
7
+ *
8
+ * For an environment with no durable medium — a browser with IndexedDB and
9
+ * `localStorage` both unavailable, or an application that has decided nothing may
10
+ * reach disk — where every tab minting for itself is still worth avoiding.
11
+ *
12
+ * Every write is broadcast and every instance applies what it receives, so a
13
+ * steady-state read is local and immediate: the medium is a `Map`, and the channel
14
+ * is what keeps the Maps agreeing. A `CryptoKeyPair` survives a structured clone
15
+ * as a handle that signs and cannot be exported, so what crosses is usable
16
+ * without key material having been sent.
17
+ *
18
+ * Nothing survives the document, which is what `durable` says. What that costs is
19
+ * the last tab of an origin closing: the credentials go with it, and the next load
20
+ * signs in again.
21
+ * @see implements {@link CredentialStorage}
22
+ */
23
+ export declare class SharedMemoryCredentialStorage implements CredentialStorage<ECDSAKeyIdentity> {
24
+ #private;
25
+ readonly shared = true;
26
+ readonly durable = false;
27
+ /**
28
+ * @param options - The channel and lock prefix the tabs of this origin share.
29
+ */
30
+ constructor(options?: {
31
+ name?: string;
32
+ });
33
+ /**
34
+ * Stops replicating and lets go of the name a peer waits on.
35
+ *
36
+ * Only needed when you replace a store while the page lives; the document
37
+ * going away does the same thing.
38
+ */
39
+ close(): void;
40
+ create(): Promise<ECDSAKeyIdentity>;
41
+ get(slot: string): Promise<Credential<ECDSAKeyIdentity> | null>;
42
+ set(slot: string, credential: Credential<ECDSAKeyIdentity>): Promise<void>;
43
+ remove(slot: string): Promise<void>;
44
+ }
@@ -0,0 +1,243 @@
1
+ import { DelegationChain, ECDSAKeyIdentity } from '@icp-sdk/core/identity';
2
+ /** The channel and lock prefix every instance agrees on by default. */
3
+ export const DEFAULT_SHARED_MEMORY_NAME = 'ic-credentials';
4
+ // Narrowing, not protection. A channel reaches one origin, so a stranger on the
5
+ // name is our own code through a deploy or an application posting on it, and what
6
+ // keeps those harmless is elsewhere: the switch answers nothing it does not
7
+ // recognise, and rebuilding a record catches its own failures. This is what gives
8
+ // `data` a type, so the switch can read the fields it needs.
9
+ const isMessage = (value) => {
10
+ if (typeof value !== 'object' || value === null || !('kind' in value))
11
+ return false;
12
+ const message = value;
13
+ switch (message.kind) {
14
+ case 'ask':
15
+ return true;
16
+ case 'drop':
17
+ return typeof message.slot === 'string';
18
+ case 'put':
19
+ return typeof message.slot === 'string' && 'keyPair' in message;
20
+ case 'offer':
21
+ return Array.isArray(message.entries);
22
+ default:
23
+ return false;
24
+ }
25
+ };
26
+ /**
27
+ * Credentials in memory, replicated to the other tabs of this origin.
28
+ *
29
+ * For an environment with no durable medium — a browser with IndexedDB and
30
+ * `localStorage` both unavailable, or an application that has decided nothing may
31
+ * reach disk — where every tab minting for itself is still worth avoiding.
32
+ *
33
+ * Every write is broadcast and every instance applies what it receives, so a
34
+ * steady-state read is local and immediate: the medium is a `Map`, and the channel
35
+ * is what keeps the Maps agreeing. A `CryptoKeyPair` survives a structured clone
36
+ * as a handle that signs and cannot be exported, so what crosses is usable
37
+ * without key material having been sent.
38
+ *
39
+ * Nothing survives the document, which is what `durable` says. What that costs is
40
+ * the last tab of an origin closing: the credentials go with it, and the next load
41
+ * signs in again.
42
+ * @see implements {@link CredentialStorage}
43
+ */
44
+ export class SharedMemoryCredentialStorage {
45
+ shared = true;
46
+ durable = false;
47
+ #name;
48
+ #channel;
49
+ // On the instance and never on the module, so two clients on one page keep
50
+ // their own replicas and no state carries between tests in one process.
51
+ #replica = new Map();
52
+ // Held for this instance's life under a name only this instance uses, so a peer
53
+ // can wait for the grant that means this one is gone. The browser releases it
54
+ // when the document is torn down, which is the signal no message could carry.
55
+ #id = crypto.randomUUID();
56
+ #releasePresence;
57
+ #announceOffer;
58
+ // Applying an incoming record is asynchronous, because a key handle and a chain
59
+ // have to be rebuilt from what crossed. Queued so they land in the order they
60
+ // arrived: a put applied after the drop that followed it would resurrect a slot
61
+ // its owner had already cleared.
62
+ #applying = Promise.resolve();
63
+ /**
64
+ * Resolves once there is nothing left to wait for: a peer has answered, or
65
+ * every peer that existed when this instance started has gone.
66
+ *
67
+ * Reads await it, so the first one after a cold start sees what the other tabs
68
+ * hold rather than a miss. There is no deadline in it — waiting ends on a lock
69
+ * grant, and a lone instance waits not at all because there is no peer to hold
70
+ * one.
71
+ */
72
+ #synced;
73
+ /**
74
+ * @param options - The channel and lock prefix the tabs of this origin share.
75
+ */
76
+ constructor(options) {
77
+ this.#name = options?.name ?? DEFAULT_SHARED_MEMORY_NAME;
78
+ this.#channel =
79
+ typeof BroadcastChannel === 'undefined' ? undefined : new BroadcastChannel(this.#name);
80
+ this.#channel?.addEventListener('message', (event) => this.#receive(event.data));
81
+ this.#synced = this.#openReplica();
82
+ }
83
+ /**
84
+ * Stops replicating and lets go of the name a peer waits on.
85
+ *
86
+ * Only needed when you replace a store while the page lives; the document
87
+ * going away does the same thing.
88
+ */
89
+ close() {
90
+ this.#channel?.close();
91
+ this.#releasePresence?.();
92
+ this.#releasePresence = undefined;
93
+ }
94
+ create() {
95
+ return ECDSAKeyIdentity.generate({ extractable: false });
96
+ }
97
+ async get(slot) {
98
+ await this.#synced;
99
+ // A read reflects everything received, not everything that has finished being
100
+ // rebuilt: applying is asynchronous, so a message already in hand would
101
+ // otherwise read as a miss and cost the caller a credential it had.
102
+ await this.#applying;
103
+ return this.#replica.get(slot) ?? null;
104
+ }
105
+ async set(slot, credential) {
106
+ this.#replica.set(slot, credential);
107
+ this.#post({ kind: 'put', ...this.#toWire(slot, credential) });
108
+ }
109
+ async remove(slot) {
110
+ this.#replica.delete(slot);
111
+ this.#post({ kind: 'drop', slot });
112
+ }
113
+ #presenceName(id) {
114
+ return `${this.#name}:alive:${id}`;
115
+ }
116
+ /**
117
+ * Takes this instance's presence lock, then waits for the peers that already
118
+ * had one — asking them for what they hold.
119
+ */
120
+ async #openReplica() {
121
+ const locks = globalThis.navigator?.locks;
122
+ if (locks === undefined || this.#channel === undefined) {
123
+ // Nothing can be enumerated and nothing can answer, so waiting could only
124
+ // cost a load. Every instance is alone, which is calls and not correctness.
125
+ return;
126
+ }
127
+ const peers = await this.#claimPresence(locks);
128
+ if (peers.length === 0)
129
+ return;
130
+ const answered = new Promise((resolve) => {
131
+ this.#announceOffer = resolve;
132
+ });
133
+ this.#post({ kind: 'ask' });
134
+ // A peer that answers ends the wait; a peer that dies without answering ends
135
+ // it too, because its lock is granted the moment the browser releases it. So
136
+ // the wait is bounded by the peers themselves rather than by a number.
137
+ const goneEverywhere = Promise.all(peers.map((name) => locks.request(name, { mode: 'exclusive' }, () => undefined)));
138
+ await Promise.race([answered, goneEverywhere]);
139
+ }
140
+ /**
141
+ * Enumerates the peers holding a presence lock, then takes this instance's own.
142
+ *
143
+ * In that order: a snapshot taken after would include this instance, and it
144
+ * would then wait for a lock it holds itself and never be granted.
145
+ */
146
+ async #claimPresence(locks) {
147
+ const mine = this.#presenceName(this.#id);
148
+ const prefix = `${this.#name}:alive:`;
149
+ let peers = [];
150
+ try {
151
+ const snapshot = await locks.query();
152
+ peers = (snapshot.held ?? [])
153
+ .map((lock) => lock.name)
154
+ .filter((name) => name !== undefined && name !== mine)
155
+ .filter((name) => name.startsWith(prefix));
156
+ }
157
+ catch {
158
+ // query() is not everywhere, and a caller cannot act on its absence. With
159
+ // no snapshot there is no peer to wait for, so this instance starts alone.
160
+ }
161
+ // Not awaited: the request resolves only when the lock is released, which is
162
+ // when this instance is done with it.
163
+ void locks
164
+ .request(mine, { mode: 'exclusive' }, () => {
165
+ return new Promise((resolve) => {
166
+ this.#releasePresence = resolve;
167
+ });
168
+ })
169
+ .catch(() => undefined);
170
+ return peers;
171
+ }
172
+ #receive(data) {
173
+ if (!isMessage(data))
174
+ return;
175
+ if (data.kind === 'ask') {
176
+ // Answered from what is queued as well as what has landed, so an instance
177
+ // asked while still applying an earlier message does not describe a state
178
+ // it is halfway out of.
179
+ this.#enqueue(() => {
180
+ const entries = [...this.#replica].map(([slot, credential]) => this.#toWire(slot, credential));
181
+ // Answered even when empty, because the asker is waiting on an answer and
182
+ // "I have nothing" is one. Staying silent would make it wait for this
183
+ // instance to close.
184
+ this.#post({ kind: 'offer', entries });
185
+ });
186
+ return;
187
+ }
188
+ this.#enqueue(async () => {
189
+ switch (data.kind) {
190
+ case 'offer':
191
+ for (const entry of data.entries)
192
+ await this.#apply(entry, { overwrite: false });
193
+ // Last, so the answer is complete before anything waiting on it wakes.
194
+ this.#announceOffer?.();
195
+ this.#announceOffer = undefined;
196
+ return;
197
+ case 'put':
198
+ await this.#apply(data, { overwrite: true });
199
+ return;
200
+ case 'drop':
201
+ this.#replica.delete(data.slot);
202
+ return;
203
+ }
204
+ });
205
+ }
206
+ #enqueue(work) {
207
+ this.#applying = this.#applying.then(work).catch(() => undefined);
208
+ }
209
+ async #apply(entry, { overwrite }) {
210
+ // An offer answers a question asked before anything local was written, so it
211
+ // fills gaps rather than replacing: a write that happened in the meantime is
212
+ // this instance's own and newer than the snapshot being described.
213
+ if (!overwrite && this.#replica.has(entry.slot))
214
+ return;
215
+ try {
216
+ const identity = await ECDSAKeyIdentity.fromKeyPair(entry.keyPair);
217
+ const chain = entry.chain === undefined ? undefined : DelegationChain.fromJSON(entry.chain);
218
+ this.#replica.set(entry.slot, { identity, chain });
219
+ }
220
+ catch {
221
+ // A record from an incompatible version, or a handle this context cannot
222
+ // adopt. Holding nothing lets the reader acquire again.
223
+ }
224
+ }
225
+ #toWire(slot, credential) {
226
+ return {
227
+ slot,
228
+ keyPair: credential.identity.getKeyPair(),
229
+ chain: credential.chain && JSON.stringify(credential.chain.toJSON()),
230
+ };
231
+ }
232
+ #post(message) {
233
+ try {
234
+ this.#channel?.postMessage(message);
235
+ }
236
+ catch {
237
+ // A closed channel, or a payload this browser will not clone. The write has
238
+ // already landed in this instance's own replica, so the cost is a peer
239
+ // missing it and minting for itself.
240
+ }
241
+ }
242
+ }
243
+ //# sourceMappingURL=shared-memory-credential-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-memory-credential-storage.js","sourceRoot":"","sources":["../../../src/client/shared-memory-credential-storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Every name one client writes under, and the one thing that knows the whole set.
3
+ *
4
+ * Its own module rather than living beside the credential store or the state
5
+ * store, because it belongs to neither: the credentials and the state of a
6
+ * sign-in are named from one namespace, and a list that sat in either file would
7
+ * have to reach across to the other.
8
+ */
9
+ /**
10
+ * The bare names, before a namespace is applied.
11
+ *
12
+ * One list, so a name cannot be added to some of the places that need it and
13
+ * missed in others. It was two lists and a hand-written mapping, and the state
14
+ * record was left out of the namespacing for exactly that reason.
15
+ */
16
+ declare const SLOT_NAMES: {
17
+ /** The session's key and the chain issued to it. */
18
+ readonly session: "session";
19
+ /**
20
+ * Where a sign-in ceremony keeps its key until it returns.
21
+ *
22
+ * Its own name rather than {@link SLOT_NAMES.session}, so a ceremony that is
23
+ * cancelled or never comes back cannot take a working session with it.
24
+ */
25
+ readonly sessionPending: "session-pending";
26
+ /** The key an application signs with, and its delegation. */
27
+ readonly app: "app";
28
+ /**
29
+ * Where a ceremony keeps the credential it minted until its session is stored.
30
+ *
31
+ * The store reaches every tab of the origin, so writing to the app name during
32
+ * a ceremony — or clearing it first — would change what those tabs act with
33
+ * before the sign-in has succeeded. This is promoted instead, once there is a
34
+ * session behind it.
35
+ */
36
+ readonly appPending: "app-pending";
37
+ /**
38
+ * The record of who is signed in here and until when.
39
+ *
40
+ * Not a credential slot, and named here anyway: a namespace that moved the
41
+ * credentials and left this fixed would give two clients their own keys and one
42
+ * shared answer to who is signed in.
43
+ */
44
+ readonly state: "ic-session-state";
45
+ };
46
+ /** Every name a client writes under, with its namespace already applied. */
47
+ export type Slots = Record<keyof typeof SLOT_NAMES, string>;
48
+ /**
49
+ * The names one client writes under, prefixed where a namespace is set.
50
+ *
51
+ * Assigned in one place rather than defaulted by each store: implementations
52
+ * choosing their own is what produced three colliding slots in the arrangement
53
+ * this replaces, and one assigner cannot collide with itself. A namespace moves
54
+ * all of them at once, so an application running two clients under one origin
55
+ * separates them with one string rather than renaming some and missing others.
56
+ * @param namespace - Prefix for every name, or `undefined` for the bare ones.
57
+ */
58
+ export declare function slotsFor(namespace?: string): Slots;
59
+ export {};