@ic-reactor/react 3.6.0 → 3.8.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 (131) hide show
  1. package/README.md +117 -13
  2. package/dist/auth/authentication-manager.d.ts +98 -0
  3. package/dist/auth/authentication-manager.d.ts.map +1 -0
  4. package/dist/auth/authentication-manager.js +532 -0
  5. package/dist/auth/authentication-manager.js.map +1 -0
  6. package/dist/auth/constants.d.ts +18 -0
  7. package/dist/auth/constants.d.ts.map +1 -0
  8. package/dist/auth/constants.js +18 -0
  9. package/dist/auth/constants.js.map +1 -0
  10. package/dist/auth/createIdentityAttributeHooks.d.ts +14 -0
  11. package/dist/auth/createIdentityAttributeHooks.d.ts.map +1 -0
  12. package/dist/auth/createIdentityAttributeHooks.js +54 -0
  13. package/dist/auth/createIdentityAttributeHooks.js.map +1 -0
  14. package/dist/auth/identity-attributes-manager.d.ts +26 -0
  15. package/dist/auth/identity-attributes-manager.d.ts.map +1 -0
  16. package/dist/auth/identity-attributes-manager.js +108 -0
  17. package/dist/auth/identity-attributes-manager.js.map +1 -0
  18. package/dist/auth/identity-attributes.d.ts +19 -0
  19. package/dist/auth/identity-attributes.d.ts.map +1 -0
  20. package/dist/auth/identity-attributes.js +170 -0
  21. package/dist/auth/identity-attributes.js.map +1 -0
  22. package/dist/auth/index.d.ts +8 -0
  23. package/dist/auth/index.d.ts.map +1 -0
  24. package/dist/auth/index.js +8 -0
  25. package/dist/auth/index.js.map +1 -0
  26. package/dist/auth/types.d.ts +182 -0
  27. package/dist/auth/types.d.ts.map +1 -0
  28. package/dist/auth/types.js +2 -0
  29. package/dist/auth/types.js.map +1 -0
  30. package/dist/createActorHooks.d.ts +16 -16
  31. package/dist/createActorHooks.d.ts.map +1 -1
  32. package/dist/createActorHooks.js +6 -6
  33. package/dist/createActorHooks.js.map +1 -1
  34. package/dist/createInfiniteQuery.d.ts +33 -33
  35. package/dist/createInfiniteQuery.d.ts.map +1 -1
  36. package/dist/createInfiniteQuery.js +6 -6
  37. package/dist/createInfiniteQuery.js.map +1 -1
  38. package/dist/createMutation.d.ts +13 -2
  39. package/dist/createMutation.d.ts.map +1 -1
  40. package/dist/createMutation.js +11 -0
  41. package/dist/createMutation.js.map +1 -1
  42. package/dist/createQuery.d.ts +13 -3
  43. package/dist/createQuery.d.ts.map +1 -1
  44. package/dist/createQuery.js +16 -6
  45. package/dist/createQuery.js.map +1 -1
  46. package/dist/createSuspenseInfiniteQuery.d.ts +32 -32
  47. package/dist/createSuspenseInfiniteQuery.d.ts.map +1 -1
  48. package/dist/createSuspenseInfiniteQuery.js +6 -6
  49. package/dist/createSuspenseInfiniteQuery.js.map +1 -1
  50. package/dist/createSuspenseQuery.d.ts +3 -3
  51. package/dist/createSuspenseQuery.d.ts.map +1 -1
  52. package/dist/createSuspenseQuery.js +1 -1
  53. package/dist/createSuspenseQuery.js.map +1 -1
  54. package/dist/defineReactor.d.ts +124 -0
  55. package/dist/defineReactor.d.ts.map +1 -0
  56. package/dist/defineReactor.js +145 -0
  57. package/dist/defineReactor.js.map +1 -0
  58. package/dist/{createAuthHooks.d.ts → hooks/createAuthHooks.d.ts} +6 -13
  59. package/dist/hooks/createAuthHooks.d.ts.map +1 -0
  60. package/dist/{createAuthHooks.js → hooks/createAuthHooks.js} +18 -58
  61. package/dist/hooks/createAuthHooks.js.map +1 -0
  62. package/dist/hooks/index.d.ts +2 -0
  63. package/dist/hooks/index.d.ts.map +1 -1
  64. package/dist/hooks/index.js +2 -0
  65. package/dist/hooks/index.js.map +1 -1
  66. package/dist/hooks/useActorInfiniteQuery.d.ts +9 -9
  67. package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
  68. package/dist/hooks/useActorInfiniteQuery.js +3 -1
  69. package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
  70. package/dist/hooks/useActorMethod.d.ts +17 -17
  71. package/dist/hooks/useActorMethod.d.ts.map +1 -1
  72. package/dist/hooks/useActorMethod.js.map +1 -1
  73. package/dist/hooks/useActorMutation.d.ts +18 -7
  74. package/dist/hooks/useActorMutation.d.ts.map +1 -1
  75. package/dist/hooks/useActorMutation.js +11 -0
  76. package/dist/hooks/useActorMutation.js.map +1 -1
  77. package/dist/hooks/useActorQuery.d.ts +8 -8
  78. package/dist/hooks/useActorQuery.d.ts.map +1 -1
  79. package/dist/hooks/useActorQuery.js.map +1 -1
  80. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +9 -9
  81. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
  82. package/dist/hooks/useActorSuspenseInfiniteQuery.js +3 -1
  83. package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
  84. package/dist/hooks/useActorSuspenseQuery.d.ts +8 -8
  85. package/dist/hooks/useActorSuspenseQuery.d.ts.map +1 -1
  86. package/dist/hooks/useActorSuspenseQuery.js.map +1 -1
  87. package/dist/index.d.ts +11 -10
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +11 -10
  90. package/dist/index.js.map +1 -1
  91. package/dist/types.d.ts +24 -24
  92. package/dist/types.d.ts.map +1 -1
  93. package/dist/utils.d.ts +3 -0
  94. package/dist/utils.d.ts.map +1 -1
  95. package/dist/utils.js +10 -2
  96. package/dist/utils.js.map +1 -1
  97. package/llms.txt +48 -0
  98. package/package.json +34 -23
  99. package/src/auth/authentication-manager.ts +591 -0
  100. package/src/auth/constants.ts +25 -0
  101. package/src/auth/createIdentityAttributeHooks.ts +83 -0
  102. package/src/auth/identity-attributes-manager.ts +136 -0
  103. package/src/auth/identity-attributes.ts +270 -0
  104. package/src/auth/index.ts +7 -0
  105. package/src/auth/types.ts +204 -0
  106. package/src/createActorHooks.ts +90 -58
  107. package/src/createInfiniteQuery.ts +154 -98
  108. package/src/createMutation.ts +35 -19
  109. package/src/createQuery.ts +94 -50
  110. package/src/createSuspenseInfiniteQuery.ts +166 -115
  111. package/src/createSuspenseQuery.ts +69 -43
  112. package/src/defineReactor.ts +272 -0
  113. package/src/{createAuthHooks.ts → hooks/createAuthHooks.ts} +23 -99
  114. package/src/hooks/index.ts +4 -0
  115. package/src/hooks/useActorInfiniteQuery.ts +44 -30
  116. package/src/hooks/useActorMethod.ts +62 -53
  117. package/src/hooks/useActorMutation.ts +47 -30
  118. package/src/hooks/useActorQuery.ts +36 -32
  119. package/src/hooks/useActorSuspenseInfiniteQuery.ts +56 -30
  120. package/src/hooks/useActorSuspenseQuery.ts +42 -32
  121. package/src/index.ts +12 -10
  122. package/src/types.ts +76 -71
  123. package/src/utils.ts +15 -2
  124. package/dist/createAuthHooks.d.ts.map +0 -1
  125. package/dist/createAuthHooks.js.map +0 -1
  126. package/src/hooks/useActorInfiniteQuery.test.tsx +0 -457
  127. package/src/hooks/useActorMethod.test.tsx +0 -798
  128. package/src/hooks/useActorMutation.test.tsx +0 -220
  129. package/src/hooks/useActorQuery.test.tsx +0 -287
  130. package/src/hooks/useActorSuspenseInfiniteQuery.test.tsx +0 -472
  131. package/src/hooks/useActorSuspenseQuery.test.tsx +0 -254
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ic-reactor/react",
3
- "version": "3.6.0",
4
- "description": "IC Reactor React Library",
3
+ "version": "3.8.0",
4
+ "description": "IC Reactor React library for building Internet Computer apps",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -12,12 +12,17 @@
12
12
  "types": "./dist/index.d.ts",
13
13
  "import": "./dist/index.js",
14
14
  "default": "./dist/index.js"
15
- }
15
+ },
16
+ "./package.json": "./package.json"
16
17
  },
17
18
  "files": [
18
19
  "dist",
19
20
  "src",
20
- "README.md"
21
+ "!src/**/*.test.*",
22
+ "!src/**/*.spec.*",
23
+ "!**/*.tsbuildinfo",
24
+ "README.md",
25
+ "llms.txt"
21
26
  ],
22
27
  "repository": {
23
28
  "type": "git",
@@ -27,7 +32,7 @@
27
32
  "bugs": {
28
33
  "url": "https://github.com/B3Pay/ic-reactor/issues"
29
34
  },
30
- "homepage": "https://ic-reactor.dev",
35
+ "homepage": "https://ic-reactor.b3pay.net/v3/packages/react",
31
36
  "keywords": [
32
37
  "internet-computer",
33
38
  "icp",
@@ -37,40 +42,39 @@
37
42
  "web3",
38
43
  "blockchain",
39
44
  "canister",
40
- "tanstack-query"
45
+ "tanstack-query",
46
+ "ic-reactor",
47
+ "llms",
48
+ "ai-friendly"
41
49
  ],
42
50
  "author": "Behrad Deylami",
43
51
  "license": "MIT",
44
52
  "dependencies": {
45
- "@ic-reactor/core": "^3.6.0"
53
+ "@ic-reactor/core": "^3.8.0"
46
54
  },
47
55
  "peerDependencies": {
48
- "@icp-sdk/auth": "^7.0.0",
56
+ "@icp-sdk/auth": "^7.1.0 || ^8.0.0",
57
+ "@icp-sdk/core": "^6.0.0",
49
58
  "@tanstack/react-query": "^5.0.0",
50
59
  "react": ">=18.0.0",
51
60
  "react-dom": ">=18.0.0"
52
61
  },
53
- "peerDependenciesMeta": {
54
- "@icp-sdk/auth": {
55
- "optional": true
56
- }
57
- },
58
62
  "devDependencies": {
59
- "@icp-sdk/auth": "^7.0.0",
60
- "@icp-sdk/core": "^5.4.0",
63
+ "@icp-sdk/auth": "^8.0.0",
64
+ "@icp-sdk/core": "^6.0.0",
61
65
  "@size-limit/preset-small-lib": "^12.1.0",
62
- "@tanstack/react-query": "^5.100.10",
66
+ "@tanstack/react-query": "^5.101.4",
63
67
  "@testing-library/dom": "^10.4.1",
64
68
  "@testing-library/jest-dom": "^6.9.1",
65
69
  "@testing-library/react": "^16.3.2",
66
- "@types/react": "^19.2.14",
70
+ "@types/react": "^19.2.17",
67
71
  "@types/react-dom": "^19.2.3",
68
72
  "fake-indexeddb": "^6.2.5",
69
73
  "jsdom": "^29.1.1",
70
- "react": "^19.2.6",
71
- "react-dom": "^19.2.6",
74
+ "react": "^19.2.8",
75
+ "react-dom": "^19.2.8",
72
76
  "size-limit": "^12.1.0",
73
- "vitest": "^4.1.6"
77
+ "vitest": "^4.1.10"
74
78
  },
75
79
  "size-limit": [
76
80
  {
@@ -82,14 +86,21 @@
82
86
  "react",
83
87
  "react-dom",
84
88
  "@ic-reactor/core",
85
- "@tanstack/react-query"
89
+ "@tanstack/react-query",
90
+ "@icp-sdk/auth"
86
91
  ]
87
92
  }
88
93
  ],
94
+ "peerDependenciesMeta": {
95
+ "@icp-sdk/auth": {
96
+ "optional": true
97
+ }
98
+ },
89
99
  "scripts": {
90
- "build": "tsc",
100
+ "build": "rm -rf dist tsconfig.tsbuildinfo && tsc",
91
101
  "test": "vitest run",
92
102
  "size": "size-limit",
93
- "analyze": "size-limit"
103
+ "analyze": "size-limit",
104
+ "typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
94
105
  }
95
106
  }
@@ -0,0 +1,591 @@
1
+ import type { Identity } from "@icp-sdk/core/agent"
2
+ import type {
3
+ AuthClientLike,
4
+ AuthClientSignInOptions,
5
+ AuthState,
6
+ AuthenticationClientOptions,
7
+ AuthenticationSignInOptions,
8
+ } from "./types.js"
9
+ import { ClientManager, isDev } from "@ic-reactor/core"
10
+
11
+ import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"
12
+ import {
13
+ IC_INTERNET_IDENTITY_PROVIDER,
14
+ INTERNET_IDENTITY_PROVIDER_ENV_KEY,
15
+ localInternetIdentityProvider,
16
+ } from "./constants.js"
17
+
18
+ export interface AuthenticationManagerParameters extends AuthenticationClientOptions {
19
+ clientManager: ClientManager
20
+ /**
21
+ * Bring your own client instance. When provided, IC Reactor never constructs
22
+ * one and never applies the options below to it.
23
+ */
24
+ authClient?: AuthClientLike
25
+ /** Canister ID of a locally deployed Internet Identity. */
26
+ internetIdentityId?: string
27
+ }
28
+
29
+ type AuthClientConstructor = {
30
+ new (options?: AuthenticationClientOptions): AuthClientLike
31
+ }
32
+
33
+ /**
34
+ * Manages Internet Identity sign-in, session restoration, and authentication
35
+ * state for a {@link ClientManager}.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const authentication = new AuthenticationManager({ clientManager })
40
+ * const identity = await authentication.authenticate()
41
+ * ```
42
+ */
43
+ export class AuthenticationManager {
44
+ private authClient?: AuthClientLike
45
+ private authPromise?: Promise<Identity | undefined>
46
+ private authClientWasProvided = false
47
+ private authStateRevision = 0
48
+ private authStateSubscribers: Array<(state: AuthState) => void> = []
49
+ private authClientConstructor?: AuthClientConstructor
50
+ private authClientConstructorPromise?: Promise<
51
+ AuthClientConstructor | undefined
52
+ >
53
+ private authModuleMissing = false
54
+ private authClientOptions?: AuthenticationClientOptions
55
+ private authStateValue: AuthState = {
56
+ identity: null,
57
+ isAuthenticating: false,
58
+ isAuthenticated: false,
59
+ error: undefined,
60
+ }
61
+ private readonly identityProvider?: string | URL
62
+ private readonly internetIdentityId?: string
63
+ private readonly defaultClientOptions: AuthenticationClientOptions
64
+ public readonly clientManager: ClientManager
65
+
66
+ /** The current authentication state. */
67
+ public get authState(): AuthState {
68
+ return this.authStateValue
69
+ }
70
+
71
+ constructor({
72
+ clientManager,
73
+ authClient,
74
+ identityProvider,
75
+ internetIdentityId,
76
+ ...clientOptions
77
+ }: AuthenticationManagerParameters) {
78
+ this.clientManager = clientManager
79
+ const canisterEnv =
80
+ typeof window !== "undefined" ? getAuthenticationCanisterEnv() : undefined
81
+ this.identityProvider =
82
+ identityProvider ||
83
+ canisterEnv?.[INTERNET_IDENTITY_PROVIDER_ENV_KEY] ||
84
+ canisterEnv?.["PUBLIC_INTERNET_IDENTITY_PROVIDER"]
85
+ this.internetIdentityId =
86
+ internetIdentityId ||
87
+ canisterEnv?.["internet_identity"] ||
88
+ canisterEnv?.["PUBLIC_CANISTER_ID:internet_identity"] ||
89
+ canisterEnv?.["CANISTER_ID_INTERNET_IDENTITY"]
90
+ this.defaultClientOptions = clientOptions
91
+
92
+ if (authClient) {
93
+ this.authClientWasProvided = true
94
+ this.authClient = authClient
95
+ this.syncStateFromClient(this.authStateRevision).catch((error) => {
96
+ this.updateState({ error: error as Error, isAuthenticating: false })
97
+ })
98
+ } else if (typeof window !== "undefined") {
99
+ this.loadAuthClientConstructor().catch(() => {
100
+ // Optional auth support is reported when an auth method is used.
101
+ })
102
+ }
103
+ }
104
+
105
+ /** @internal Used by IdentityAttributesManager. */
106
+ public get client() {
107
+ return this.authClient
108
+ }
109
+
110
+ public subscribeAuthState(callback: (state: AuthState) => void) {
111
+ this.authStateSubscribers.push(callback)
112
+ return () => {
113
+ this.authStateSubscribers = this.authStateSubscribers.filter(
114
+ (subscriber) => subscriber !== callback
115
+ )
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Preloads the auth module and creates an AuthClient ahead of time.
121
+ *
122
+ * Call (and await) this before wiring up a login button: it makes
123
+ * {@link login} and {@link IdentityAttributesManager.request} able to open
124
+ * the identity provider synchronously inside the click handler, which is
125
+ * what browser popup blockers and the ICRC-29 transport require.
126
+ */
127
+ public async prepareClient(options?: AuthenticationClientOptions) {
128
+ const clientOptions = this.resolveClientOptions(options)
129
+
130
+ if (this.authClient && !this.shouldRecreateClient(clientOptions)) {
131
+ return this.authClient
132
+ }
133
+
134
+ return this.initializeClient(clientOptions)
135
+ }
136
+
137
+ /**
138
+ * Returns an AuthClient without awaiting, or `undefined` when the auth
139
+ * module has not been loaded yet.
140
+ *
141
+ * Callers that must stay inside a user gesture use this instead of
142
+ * {@link ensureClient}; awaiting anything before opening the identity
143
+ * provider window loses the gesture.
144
+ */
145
+ public getPreparedClient(
146
+ options?: AuthenticationClientOptions
147
+ ): AuthClientLike | undefined {
148
+ return this.ensurePreparedClient(this.resolveClientOptions(options))
149
+ }
150
+
151
+ public authenticate = async (): Promise<Identity | undefined> => {
152
+ if (this.authState.isAuthenticated) {
153
+ return this.authState.identity || undefined
154
+ }
155
+ if (this.authPromise) {
156
+ return this.authPromise
157
+ }
158
+ if (this.authModuleMissing) {
159
+ return undefined
160
+ }
161
+
162
+ this.authPromise = (async () => {
163
+ if (isDev() && typeof window !== "undefined") {
164
+ console.info(
165
+ `%cic-reactor:%c Authenticating...`,
166
+ "color: #3b82f6; font-weight: bold",
167
+ "color: inherit",
168
+ {
169
+ network: this.clientManager.network,
170
+ authClient: this.authClient ? "Shared Instance" : "Dynamic Import",
171
+ }
172
+ )
173
+ }
174
+ this.updateState({ isAuthenticating: true })
175
+ try {
176
+ if (!this.authClient) {
177
+ const authClient = await this.initializeClient(
178
+ this.resolveClientOptions()
179
+ )
180
+ if (!authClient) {
181
+ this.updateState({ isAuthenticating: false })
182
+ return undefined
183
+ }
184
+ }
185
+ const identity = await this.authClient!.getIdentity()
186
+ const isAuthenticated = await this.authClient!.isAuthenticated()
187
+ // Restoring an anonymous session is the common first-load case; pushing
188
+ // it through updateAgent would invalidate the whole query cache on
189
+ // every mount for nothing.
190
+ if (isAuthenticated || !identity.getPrincipal().isAnonymous()) {
191
+ this.clientManager.updateAgent(identity)
192
+ }
193
+ this.updateState({
194
+ identity,
195
+ isAuthenticated,
196
+ isAuthenticating: false,
197
+ })
198
+ return identity
199
+ } catch (error) {
200
+ this.updateState({ error: error as Error, isAuthenticating: false })
201
+ console.error("Authentication failed:", error)
202
+ throw error
203
+ } finally {
204
+ this.authPromise = undefined
205
+ }
206
+ })()
207
+
208
+ return this.authPromise
209
+ }
210
+
211
+ public login = async (loginOptions?: AuthenticationSignInOptions) => {
212
+ let didCompleteSignIn = false
213
+
214
+ try {
215
+ const authClientOptions = this.resolveClientOptions(loginOptions)
216
+
217
+ // Stays synchronous when `prepareClient()` has already loaded the auth
218
+ // module, so `signIn()` still runs inside the caller's click handler.
219
+ if (!this.ensurePreparedClient(authClientOptions)) {
220
+ await this.initializeClient(authClientOptions)
221
+ }
222
+
223
+ if (!this.authClient) {
224
+ await this.authenticate()
225
+ }
226
+
227
+ if (!this.authClient) {
228
+ throw new Error(
229
+ "Authentication module is missing or failed to initialize. To use login, install the optional auth peer: npm install @icp-sdk/auth. If it is already installed and your bundler could not resolve it, pass a pre-constructed client instead: new AuthenticationManager({ clientManager, authClient: new AuthClient(...) })"
230
+ )
231
+ }
232
+
233
+ this.updateState({ isAuthenticating: true, error: undefined })
234
+ const identity = await this.signInOrRecoverIdentity(
235
+ getSignInOptions(loginOptions)
236
+ )
237
+
238
+ if (!this.clientManager.agentState.isInitialized) {
239
+ await this.clientManager.initializeAgent()
240
+ }
241
+
242
+ this.clientManager.updateAgent(identity)
243
+ this.updateState({
244
+ identity,
245
+ isAuthenticated: true,
246
+ isAuthenticating: false,
247
+ })
248
+ didCompleteSignIn = true
249
+
250
+ try {
251
+ await loginOptions?.onSuccess?.()
252
+ } catch (callbackError) {
253
+ this.updateState({ error: callbackError as Error })
254
+ await loginOptions?.onError?.((callbackError as Error).message)
255
+ throw callbackError
256
+ }
257
+ } catch (error) {
258
+ if (!didCompleteSignIn) {
259
+ await loginOptions?.onError?.((error as Error).message)
260
+ this.updateState({
261
+ error: error as Error,
262
+ isAuthenticating: false,
263
+ })
264
+ }
265
+ throw error
266
+ }
267
+ }
268
+
269
+ public logout = async (options?: { returnTo?: string }) => {
270
+ if (!this.authClient) {
271
+ throw new Error(
272
+ "Authentication module is missing or failed to initialize. To use logout, install the optional auth peer: npm install @icp-sdk/auth. If it is already installed and your bundler could not resolve it, pass a pre-constructed client instead: new AuthenticationManager({ clientManager, authClient: new AuthClient(...) })"
273
+ )
274
+ }
275
+ this.updateState({ isAuthenticating: true, error: undefined })
276
+ await this.authClient.signOut(options)
277
+ const identity = await this.authClient.getIdentity()
278
+ this.clientManager.updateAgent(identity)
279
+ this.updateState({
280
+ identity,
281
+ isAuthenticated: false,
282
+ isAuthenticating: false,
283
+ })
284
+ }
285
+
286
+ private async initializeClient(
287
+ options?: AuthenticationClientOptions
288
+ ): Promise<AuthClientLike | undefined> {
289
+ const AuthClient = await this.loadAuthClientConstructor()
290
+
291
+ if (!AuthClient) {
292
+ return undefined
293
+ }
294
+
295
+ this.authClient = new AuthClient(options)
296
+ this.authClientOptions = options
297
+ return this.authClient
298
+ }
299
+
300
+ /** @internal Used by IdentityAttributesManager. */
301
+ public async signInOrRecoverIdentity(
302
+ options?: AuthClientSignInOptions
303
+ ): Promise<Identity> {
304
+ if (!this.authClient) {
305
+ throw new Error(
306
+ "Authentication module is missing or failed to initialize. To use login, install the optional auth peer: npm install @icp-sdk/auth. If it is already installed and your bundler could not resolve it, pass a pre-constructed client instead: new AuthenticationManager({ clientManager, authClient: new AuthClient(...) })"
307
+ )
308
+ }
309
+
310
+ try {
311
+ return await this.authClient.signIn(options)
312
+ } catch (error) {
313
+ const identity = await Promise.resolve(
314
+ this.authClient.getIdentity()
315
+ ).catch(() => null)
316
+ const isAuthenticated = await Promise.resolve(
317
+ this.authClient.isAuthenticated()
318
+ ).catch(() => false)
319
+
320
+ if (identity && isAuthenticated) {
321
+ return identity
322
+ }
323
+
324
+ throw error
325
+ }
326
+ }
327
+
328
+ private ensurePreparedClient(
329
+ options?: AuthenticationClientOptions
330
+ ): AuthClientLike | undefined {
331
+ if (this.authClient && !this.shouldRecreateClient(options)) {
332
+ return this.authClient
333
+ }
334
+
335
+ const AuthClient = this.authClientConstructor
336
+ if (!AuthClient || this.authClientWasProvided) {
337
+ return undefined
338
+ }
339
+
340
+ this.authClient = new AuthClient(options)
341
+ this.authClientOptions = options
342
+ return this.authClient
343
+ }
344
+
345
+ /**
346
+ * Only rebuild the client when the effective options actually changed.
347
+ *
348
+ * Recreating on every call would discard the client warmed up by
349
+ * `prepareClient()` and register a duplicate sign-out callback on the
350
+ * shared IdleManager singleton each time.
351
+ */
352
+ private shouldRecreateClient(options?: AuthenticationClientOptions): boolean {
353
+ if (this.authClientWasProvided) {
354
+ return false
355
+ }
356
+ return !isSameAuthClientOptions(this.authClientOptions, options)
357
+ }
358
+
359
+ /**
360
+ * Merges constructor-level defaults with per-call overrides and fills in the
361
+ * network-appropriate identity provider.
362
+ */
363
+ private resolveClientOptions(
364
+ options?: AuthenticationClientOptions
365
+ ): AuthenticationClientOptions {
366
+ const merged = getAuthClientOptions({
367
+ ...this.defaultClientOptions,
368
+ ...options,
369
+ })
370
+
371
+ return {
372
+ ...merged,
373
+ identityProvider:
374
+ merged?.identityProvider ?? this.getDefaultIdentityProvider(),
375
+ }
376
+ }
377
+
378
+ private async syncStateFromClient(revision = this.authStateRevision) {
379
+ if (!this.authClient) {
380
+ return
381
+ }
382
+
383
+ const identity = await this.authClient.getIdentity()
384
+ const isAuthenticated = await this.authClient.isAuthenticated()
385
+ if (revision !== this.authStateRevision) {
386
+ return
387
+ }
388
+ this.clientManager.updateAgent(identity)
389
+ this.updateState({
390
+ identity,
391
+ isAuthenticated,
392
+ isAuthenticating: false,
393
+ error: undefined,
394
+ })
395
+ }
396
+
397
+ /** @internal Used by IdentityAttributesManager. */
398
+ public async ensureClient(options?: AuthenticationClientOptions) {
399
+ const clientOptions = this.resolveClientOptions(options)
400
+ if (!this.ensurePreparedClient(clientOptions)) {
401
+ await this.initializeClient(clientOptions)
402
+ }
403
+ return this.authClient
404
+ }
405
+
406
+ /** @internal Used by IdentityAttributesManager. */
407
+ public async commitIdentity(identity: Identity, isAuthenticated: boolean) {
408
+ if (!this.clientManager.agentState.isInitialized) {
409
+ await this.clientManager.initializeAgent()
410
+ }
411
+ this.clientManager.updateAgent(identity)
412
+ this.updateState({ identity, isAuthenticated, isAuthenticating: false })
413
+ }
414
+
415
+ /** @internal Used by IdentityAttributesManager. */
416
+ public setAuthenticating() {
417
+ this.updateState({ isAuthenticating: true, error: undefined })
418
+ }
419
+
420
+ /** @internal Used by IdentityAttributesManager. */
421
+ public setAuthenticationError(error: Error) {
422
+ this.updateState({ error, isAuthenticating: false })
423
+ }
424
+
425
+ private getDefaultIdentityProvider(): string | URL {
426
+ if (this.identityProvider) {
427
+ return this.identityProvider
428
+ }
429
+ return this.clientManager.isLocal
430
+ ? localInternetIdentityProvider(
431
+ Number(this.clientManager.agentHost?.port) || 4943,
432
+ this.internetIdentityId
433
+ )
434
+ : IC_INTERNET_IDENTITY_PROVIDER
435
+ }
436
+
437
+ private updateState(newState: Partial<AuthState>) {
438
+ if (isDev()) console.debug("[ic-reactor] Updating Auth State:", newState)
439
+ this.authStateRevision += 1
440
+ this.authStateValue = { ...this.authStateValue, ...newState }
441
+ this.authStateSubscribers.forEach((subscriber) =>
442
+ subscriber(this.authStateValue)
443
+ )
444
+ }
445
+
446
+ private async loadAuthClientConstructor() {
447
+ if (this.authClientConstructor) {
448
+ return this.authClientConstructor
449
+ }
450
+
451
+ if (!this.authClientConstructorPromise) {
452
+ // The specifier must stay a literal: a variable specifier (or /* @vite-ignore */)
453
+ // makes bundlers skip this import entirely, so the bare specifier survives into
454
+ // the browser, where it cannot be resolved without an import map and every login
455
+ // path fails. A literal lets Vite/Rollup/webpack code-split the optional peer,
456
+ // and lets them tree-shake it away for apps that never reference this class.
457
+ this.authClientConstructorPromise = import("@icp-sdk/auth/client")
458
+ .then((authModule) => {
459
+ const AuthClient = (
460
+ authModule as { AuthClient?: AuthClientConstructor }
461
+ ).AuthClient
462
+
463
+ if (!AuthClient) {
464
+ throw new Error("@icp-sdk/auth/client did not export AuthClient")
465
+ }
466
+
467
+ this.authClientConstructor = AuthClient
468
+ return AuthClient
469
+ })
470
+ .catch((error) => {
471
+ this.authModuleMissing = true
472
+ this.authClientConstructorPromise = undefined
473
+ if (
474
+ error instanceof Error &&
475
+ error.message.includes("did not export AuthClient")
476
+ ) {
477
+ throw error
478
+ }
479
+ return undefined
480
+ })
481
+ }
482
+
483
+ return this.authClientConstructorPromise
484
+ }
485
+ }
486
+
487
+ function getAuthClientOptions(
488
+ options?: AuthenticationClientOptions
489
+ ): AuthenticationClientOptions | undefined {
490
+ if (!options) {
491
+ return undefined
492
+ }
493
+
494
+ return {
495
+ identityProvider: options.identityProvider,
496
+ windowOpenerFeatures: options.windowOpenerFeatures,
497
+ openIdProvider: getAuthClientOpenIdProvider(options.openIdProvider),
498
+ derivationOrigin: options.derivationOrigin,
499
+ storage: options.storage,
500
+ keyType: options.keyType,
501
+ idleOptions: options.idleOptions,
502
+ identity: options.identity,
503
+ transport: options.transport,
504
+ }
505
+ }
506
+
507
+ function getAuthClientOpenIdProvider(
508
+ openIdProvider?: AuthenticationClientOptions["openIdProvider"]
509
+ ): AuthenticationClientOptions["openIdProvider"] | undefined {
510
+ return openIdProvider === "google" ||
511
+ openIdProvider === "apple" ||
512
+ openIdProvider === "microsoft"
513
+ ? openIdProvider
514
+ : undefined
515
+ }
516
+
517
+ /**
518
+ * Compares the options two AuthClients would be built from. Object-valued
519
+ * options (`storage`, `identity`, `idleOptions`) are compared by reference,
520
+ * which is what module-scoped configuration produces.
521
+ */
522
+ function isSameAuthClientOptions(
523
+ current?: AuthenticationClientOptions,
524
+ next?: AuthenticationClientOptions
525
+ ): boolean {
526
+ if (current === next) {
527
+ return true
528
+ }
529
+ if (!current || !next) {
530
+ return false
531
+ }
532
+
533
+ return (
534
+ String(current.identityProvider ?? "") ===
535
+ String(next.identityProvider ?? "") &&
536
+ current.windowOpenerFeatures === next.windowOpenerFeatures &&
537
+ current.openIdProvider === next.openIdProvider &&
538
+ String(current.derivationOrigin ?? "") ===
539
+ String(next.derivationOrigin ?? "") &&
540
+ current.storage === next.storage &&
541
+ current.keyType === next.keyType &&
542
+ current.idleOptions === next.idleOptions &&
543
+ current.identity === next.identity &&
544
+ current.transport === next.transport
545
+ )
546
+ }
547
+
548
+ function getAuthenticationCanisterEnv(): Record<string, string> | undefined {
549
+ const canisterEnv = safeGetCanisterEnv<Record<string, string>>()
550
+ if (canisterEnv) {
551
+ return canisterEnv as unknown as Record<string, string>
552
+ }
553
+
554
+ if (typeof document === "undefined") {
555
+ return undefined
556
+ }
557
+
558
+ const cookie = document.cookie
559
+ .split(";")
560
+ .find((part) => part.trim().startsWith("ic_env="))
561
+ const encodedValue = cookie?.split("=").slice(1).join("=")?.trim()
562
+ if (!encodedValue) {
563
+ return undefined
564
+ }
565
+
566
+ const env = Object.fromEntries(
567
+ decodeURIComponent(encodedValue)
568
+ .split("&")
569
+ .map((entry) => {
570
+ const separatorIndex = entry.indexOf("=")
571
+ return separatorIndex === -1
572
+ ? [entry, ""]
573
+ : [entry.slice(0, separatorIndex), entry.slice(separatorIndex + 1)]
574
+ })
575
+ )
576
+
577
+ return Object.keys(env).length ? env : undefined
578
+ }
579
+
580
+ function getSignInOptions(
581
+ options?: AuthenticationSignInOptions
582
+ ): AuthClientSignInOptions | undefined {
583
+ if (!options) {
584
+ return undefined
585
+ }
586
+
587
+ return {
588
+ maxTimeToLive: options.maxTimeToLive,
589
+ targets: options.targets,
590
+ }
591
+ }