@ic-reactor/core 3.6.0 → 3.7.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.
- package/README.md +4 -4
- package/dist/client.d.ts +17 -79
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +49 -527
- package/dist/client.js.map +1 -1
- package/dist/display/visitor.d.ts.map +1 -1
- package/dist/display/visitor.js +6 -1
- package/dist/display/visitor.js.map +1 -1
- package/dist/display-reactor.d.ts +3 -0
- package/dist/display-reactor.d.ts.map +1 -1
- package/dist/display-reactor.js +3 -0
- package/dist/display-reactor.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/reactor.d.ts +4 -4
- package/dist/reactor.d.ts.map +1 -1
- package/dist/reactor.js +27 -10
- package/dist/reactor.js.map +1 -1
- package/dist/types/client.d.ts +1 -120
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/display-reactor.d.ts.map +1 -1
- package/dist/types/reactor.d.ts +8 -0
- package/dist/types/reactor.d.ts.map +1 -1
- package/dist/types/variant.d.ts.map +1 -1
- package/dist/utils/agent.d.ts +3 -2
- package/dist/utils/agent.d.ts.map +1 -1
- package/dist/utils/agent.js +9 -3
- package/dist/utils/agent.js.map +1 -1
- package/dist/utils/constants.d.ts +0 -2
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +0 -2
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/helper.d.ts +14 -1
- package/dist/utils/helper.d.ts.map +1 -1
- package/dist/utils/helper.js +48 -9
- package/dist/utils/helper.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/polling.d.ts.map +1 -1
- package/dist/utils/polling.js +2 -2
- package/dist/utils/polling.js.map +1 -1
- package/dist/utils/query-data.d.ts +4 -0
- package/dist/utils/query-data.d.ts.map +1 -0
- package/dist/utils/query-data.js +3 -0
- package/dist/utils/query-data.js.map +1 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/llms.txt +43 -0
- package/package.json +15 -17
- package/src/client.ts +53 -626
- package/src/display/visitor.ts +10 -1
- package/src/display-reactor.ts +3 -0
- package/src/index.ts +0 -1
- package/src/reactor.ts +48 -18
- package/src/types/client.ts +1 -138
- package/src/types/display-reactor.ts +1 -2
- package/src/types/reactor.ts +15 -0
- package/src/types/variant.ts +8 -6
- package/src/utils/agent.ts +12 -3
- package/src/utils/constants.ts +0 -5
- package/src/utils/helper.ts +52 -7
- package/src/utils/index.ts +1 -0
- package/src/utils/polling.ts +3 -3
- package/src/utils/query-data.ts +5 -0
- package/src/version.ts +2 -2
- package/dist/identity-attributes.d.ts +0 -19
- package/dist/identity-attributes.d.ts.map +0 -1
- package/dist/identity-attributes.js +0 -167
- package/dist/identity-attributes.js.map +0 -1
- package/src/identity-attributes.ts +0 -263
package/src/client.ts
CHANGED
|
@@ -1,67 +1,50 @@
|
|
|
1
1
|
import type { Identity } from "@icp-sdk/core/agent"
|
|
2
|
-
import type {
|
|
3
|
-
ClientManagerParameters,
|
|
4
|
-
AgentState,
|
|
5
|
-
AuthState,
|
|
6
|
-
AuthClientSignInOptions,
|
|
7
|
-
AuthClientLike,
|
|
8
|
-
ClientManagerAuthClientOptions,
|
|
9
|
-
ClientManagerSignInOptions,
|
|
10
|
-
IdentityAttributeResult,
|
|
11
|
-
RequestIdentityAttributesParameters,
|
|
12
|
-
RequestOpenIdIdentityAttributesParameters,
|
|
13
|
-
} from "./types/client"
|
|
2
|
+
import type { ClientManagerParameters, AgentState } from "./types/client"
|
|
14
3
|
import type { Principal } from "@icp-sdk/core/principal"
|
|
15
|
-
import type { QueryClient } from "@tanstack/
|
|
4
|
+
import type { QueryClient } from "@tanstack/query-core"
|
|
16
5
|
|
|
17
6
|
import { HttpAgent } from "@icp-sdk/core/agent"
|
|
18
7
|
import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"
|
|
19
|
-
import {
|
|
20
|
-
IC_HOST_NETWORK_URI,
|
|
21
|
-
IC_INTERNET_IDENTITY_PROVIDER,
|
|
22
|
-
LOCAL_INTERNET_IDENTITY_PROVIDER,
|
|
23
|
-
} from "./utils/constants"
|
|
8
|
+
import { IC_HOST_NETWORK_URI } from "./utils/constants"
|
|
24
9
|
import {
|
|
25
10
|
getNetworkByHostname,
|
|
26
11
|
getProcessEnvNetwork,
|
|
27
12
|
isDev,
|
|
13
|
+
isMainnetHost,
|
|
28
14
|
} from "./utils/helper"
|
|
29
|
-
import {
|
|
30
|
-
decodeIdentityAttributeValues,
|
|
31
|
-
identityAttributeKeys,
|
|
32
|
-
normalizeSignedIdentityAttributes,
|
|
33
|
-
} from "./identity-attributes"
|
|
34
|
-
|
|
35
|
-
type AuthClientConstructor = {
|
|
36
|
-
new (options?: ClientManagerAuthClientOptions): AuthClientLike
|
|
37
|
-
}
|
|
38
15
|
|
|
39
16
|
/**
|
|
40
|
-
* ClientManager is a central class for managing the Internet Computer (IC) agent
|
|
17
|
+
* ClientManager is a central class for managing the Internet Computer (IC) agent.
|
|
41
18
|
*
|
|
42
|
-
* It initializes the agent (connecting to local or mainnet)
|
|
43
|
-
*
|
|
19
|
+
* It initializes the agent (connecting to local or mainnet) and integrates
|
|
20
|
+
* with TanStack Query's QueryClient for state management.
|
|
21
|
+
* Use this as a singleton shared by all reactors in an app.
|
|
44
22
|
*
|
|
45
23
|
* @example
|
|
46
24
|
* ```typescript
|
|
47
25
|
* import { ClientManager } from "@ic-reactor/core";
|
|
48
|
-
* import { QueryClient } from "@tanstack/
|
|
26
|
+
* import { QueryClient } from "@tanstack/query-core";
|
|
49
27
|
*
|
|
50
28
|
* const queryClient = new QueryClient();
|
|
51
29
|
* const clientManager = new ClientManager({
|
|
52
30
|
* queryClient,
|
|
53
|
-
*
|
|
31
|
+
* agentOptions: { host: "http://127.0.0.1:4943" },
|
|
54
32
|
* });
|
|
55
33
|
*
|
|
56
34
|
* await clientManager.initialize();
|
|
57
35
|
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* // Reuse the same ClientManager across multiple canisters
|
|
40
|
+
* const backend = new Reactor<BackendService>({ clientManager, idlFactory: backendIdl, name: "backend" })
|
|
41
|
+
* const ledger = new Reactor<LedgerService>({ clientManager, idlFactory: ledgerIdl, name: "ledger" })
|
|
42
|
+
* ```
|
|
58
43
|
*/
|
|
59
44
|
export class ClientManager {
|
|
60
45
|
#agent: HttpAgent
|
|
61
|
-
#authClient?: AuthClientLike
|
|
62
46
|
#identitySubscribers: Array<(identity: Identity) => void> = []
|
|
63
47
|
#agentStateSubscribers: Array<(state: AgentState) => void> = []
|
|
64
|
-
#authStateSubscribers: Array<(state: AuthState) => void> = []
|
|
65
48
|
#targetCanisterIds: Set<string> = new Set()
|
|
66
49
|
|
|
67
50
|
/**
|
|
@@ -72,36 +55,14 @@ export class ClientManager {
|
|
|
72
55
|
* Current state of the HttpAgent, including initialization status, network, and error information.
|
|
73
56
|
*/
|
|
74
57
|
public agentState: AgentState
|
|
75
|
-
/**
|
|
76
|
-
* Current authentication state, including the active identity, authentication progress, and errors.
|
|
77
|
-
*/
|
|
78
|
-
public authState: AuthState
|
|
79
|
-
|
|
80
58
|
private initPromise?: Promise<void>
|
|
81
|
-
private authPromise?: Promise<Identity | undefined>
|
|
82
|
-
private authClientConstructor?: AuthClientConstructor
|
|
83
|
-
private authClientConstructorPromise?: Promise<
|
|
84
|
-
AuthClientConstructor | undefined
|
|
85
|
-
>
|
|
86
|
-
private authStateRevision = 0
|
|
87
|
-
private authClientWasProvided = false
|
|
88
|
-
private port: number
|
|
89
|
-
private internetIdentityId?: string
|
|
90
59
|
|
|
91
60
|
/**
|
|
92
61
|
* Creates a new instance of ClientManager.
|
|
93
62
|
*
|
|
94
63
|
* @param parameters - Configuration options for the agent and network environment.
|
|
95
64
|
*/
|
|
96
|
-
constructor({
|
|
97
|
-
port = 4943,
|
|
98
|
-
withLocalEnv,
|
|
99
|
-
withProcessEnv,
|
|
100
|
-
withCanisterEnv,
|
|
101
|
-
agentOptions = {},
|
|
102
|
-
queryClient,
|
|
103
|
-
authClient,
|
|
104
|
-
}: ClientManagerParameters) {
|
|
65
|
+
constructor({ agentOptions = {}, queryClient }: ClientManagerParameters) {
|
|
105
66
|
this.queryClient = queryClient
|
|
106
67
|
|
|
107
68
|
this.agentState = {
|
|
@@ -112,70 +73,49 @@ export class ClientManager {
|
|
|
112
73
|
isLocalhost: false,
|
|
113
74
|
}
|
|
114
75
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
isAuthenticating: false,
|
|
118
|
-
isAuthenticated: false,
|
|
119
|
-
error: undefined,
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
this.port = port
|
|
123
|
-
|
|
124
|
-
// EXPERIMENTAL: Use canister environment from ic_env cookie when enabled
|
|
125
|
-
// ⚠️ This may cause issues with update calls on localhost development
|
|
126
|
-
if (withCanisterEnv) {
|
|
127
|
-
const canisterEnv =
|
|
128
|
-
typeof window !== "undefined" ? safeGetCanisterEnv() : undefined
|
|
129
|
-
|
|
130
|
-
if (canisterEnv) {
|
|
131
|
-
this.internetIdentityId =
|
|
132
|
-
canisterEnv["internet_identity"] ||
|
|
133
|
-
canisterEnv["PUBLIC_CANISTER_ID:internet_identity"] ||
|
|
134
|
-
canisterEnv["CANISTER_ID_INTERNET_IDENTITY"]
|
|
135
|
-
}
|
|
76
|
+
const canisterEnv =
|
|
77
|
+
typeof window !== "undefined" ? safeGetCanisterEnv() : undefined
|
|
136
78
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
agentOptions.verifyQuerySignatures =
|
|
148
|
-
agentOptions.verifyQuerySignatures ?? true
|
|
79
|
+
// Locally deployed asset-canister pages and IC boundary domains can route
|
|
80
|
+
// agent traffic through their serving origin. Ordinary web hosts (Vercel,
|
|
81
|
+
// Cloudflare, etc.) cannot, so they retain the default IC API fallback.
|
|
82
|
+
if (typeof window !== "undefined") {
|
|
83
|
+
const browserOrigin = window.location.origin
|
|
84
|
+
const browserNetwork = getNetworkByHostname(
|
|
85
|
+
new URL(browserOrigin).hostname
|
|
86
|
+
)
|
|
87
|
+
if (browserNetwork === "local" || isMainnetHost(browserOrigin)) {
|
|
88
|
+
agentOptions.host = agentOptions.host ?? browserOrigin
|
|
149
89
|
}
|
|
90
|
+
}
|
|
150
91
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// If a custom root key is required (e.g. local replica), pass it via
|
|
155
|
-
// agentOptions.rootKey in your application code instead.
|
|
156
|
-
if (canisterEnv?.IC_ROOT_KEY) {
|
|
157
|
-
console.error(
|
|
158
|
-
"[ic-reactor] IC_ROOT_KEY in the ic_env cookie is ignored for security reasons. " +
|
|
159
|
-
"Pass agentOptions.rootKey explicitly if you need a custom root key."
|
|
160
|
-
)
|
|
92
|
+
if (isDev() && typeof window !== "undefined") {
|
|
93
|
+
if (agentOptions.verifyQuerySignatures == null) {
|
|
94
|
+
agentOptions.verifyQuerySignatures = false
|
|
161
95
|
}
|
|
96
|
+
} else {
|
|
97
|
+
agentOptions.verifyQuerySignatures =
|
|
98
|
+
agentOptions.verifyQuerySignatures ?? true
|
|
162
99
|
}
|
|
163
100
|
|
|
164
|
-
if (
|
|
101
|
+
if (!agentOptions.host) {
|
|
165
102
|
const processNetwork = getProcessEnvNetwork()
|
|
166
|
-
if (processNetwork === "
|
|
103
|
+
if (processNetwork === "local") {
|
|
104
|
+
const envHost =
|
|
105
|
+
typeof process !== "undefined"
|
|
106
|
+
? process.env.ICP_HOST || process.env.IC_HOST
|
|
107
|
+
: undefined
|
|
108
|
+
agentOptions.host = envHost ?? "http://127.0.0.1:4943"
|
|
109
|
+
} else {
|
|
167
110
|
agentOptions.host = IC_HOST_NETWORK_URI
|
|
168
|
-
} else if (processNetwork === "local") {
|
|
169
|
-
agentOptions.host =
|
|
170
|
-
typeof process !== "undefined" && process.env.IC_HOST
|
|
171
|
-
? process.env.IC_HOST
|
|
172
|
-
: `http://127.0.0.1:${port}`
|
|
173
111
|
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// For security reasons, the root key from the ic_env cookie is only accepted
|
|
115
|
+
// on non-mainnet environments (e.g. local replicas or custom testnets).
|
|
116
|
+
// On the mainnet ("ic"), we must use the pinned root key inside the agent.
|
|
117
|
+
if (!isMainnetHost(agentOptions.host) && canisterEnv?.IC_ROOT_KEY) {
|
|
118
|
+
agentOptions.rootKey = agentOptions.rootKey ?? canisterEnv.IC_ROOT_KEY
|
|
179
119
|
}
|
|
180
120
|
|
|
181
121
|
this.#agent = HttpAgent.createSync(agentOptions)
|
|
@@ -183,52 +123,20 @@ export class ClientManager {
|
|
|
183
123
|
isLocalhost: this.isLocal,
|
|
184
124
|
network: this.network,
|
|
185
125
|
})
|
|
186
|
-
|
|
187
|
-
if (authClient) {
|
|
188
|
-
this.authClientWasProvided = true
|
|
189
|
-
this.#authClient = authClient
|
|
190
|
-
this.syncAuthStateFromClient(this.authStateRevision).catch((error) => {
|
|
191
|
-
this.updateAuthState({ error: error as Error, isAuthenticating: false })
|
|
192
|
-
})
|
|
193
|
-
} else if (typeof window !== "undefined") {
|
|
194
|
-
this.loadAuthClientConstructor().catch(() => {
|
|
195
|
-
// Optional auth support is reported when an auth method is used.
|
|
196
|
-
})
|
|
197
|
-
}
|
|
198
126
|
}
|
|
199
127
|
|
|
200
128
|
/**
|
|
201
129
|
* Orchestrates the complete initialization of the ClientManager.
|
|
202
130
|
* This method awaits the agent's core initialization (e.g., fetching root keys)
|
|
203
|
-
*
|
|
131
|
+
* Authentication session restoration is handled by AuthenticationManager.
|
|
204
132
|
*
|
|
205
133
|
* @returns A promise that resolves to the ClientManager instance when core initialization is complete.
|
|
206
134
|
*/
|
|
207
135
|
public async initialize() {
|
|
208
136
|
await this.initializeAgent()
|
|
209
|
-
this.authenticate()
|
|
210
137
|
return this
|
|
211
138
|
}
|
|
212
139
|
|
|
213
|
-
/**
|
|
214
|
-
* Preloads and creates an AuthClient before a user gesture is needed.
|
|
215
|
-
*
|
|
216
|
-
* Browser signer transports must open their channel directly from a click
|
|
217
|
-
* handler. Apps that pass dynamic auth options, such as OpenID provider
|
|
218
|
-
* aliases, can call this from hover/focus/effect code so the later click path
|
|
219
|
-
* can call signIn/requestAttributes without first awaiting a dynamic import.
|
|
220
|
-
*/
|
|
221
|
-
public async prepareAuthClient(options?: ClientManagerAuthClientOptions) {
|
|
222
|
-
if (
|
|
223
|
-
this.#authClient &&
|
|
224
|
-
(!this.shouldRecreateAuthClient(options) || this.authClientWasProvided)
|
|
225
|
-
) {
|
|
226
|
-
return this.#authClient
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return this.initializeAuthClient(options)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
140
|
/**
|
|
233
141
|
* Specifically initializes the HttpAgent.
|
|
234
142
|
* On local networks, this includes fetching the root key for certificate verification.
|
|
@@ -274,267 +182,6 @@ export class ClientManager {
|
|
|
274
182
|
return this.initPromise
|
|
275
183
|
}
|
|
276
184
|
|
|
277
|
-
private authModuleMissing = false
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Attempts to initialize the authentication client and restore a previous session.
|
|
281
|
-
*
|
|
282
|
-
* If an `AuthClient` is already initialized (passed in constructor or previously created),
|
|
283
|
-
* it uses that instance. Otherwise, it dynamically imports the `@icp-sdk/auth` module
|
|
284
|
-
* and creates a new AuthClient.
|
|
285
|
-
*
|
|
286
|
-
* If the module is missing and no client is provided, it fails gracefully by marking authentication as unavailable.
|
|
287
|
-
*
|
|
288
|
-
* @returns A promise that resolves to the restored Identity, or undefined if auth fails or is unavailable.
|
|
289
|
-
*/
|
|
290
|
-
public authenticate = async (): Promise<Identity | undefined> => {
|
|
291
|
-
if (this.authState.isAuthenticated) {
|
|
292
|
-
return this.authState.identity || undefined
|
|
293
|
-
}
|
|
294
|
-
if (this.authPromise) {
|
|
295
|
-
return this.authPromise
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if (this.authModuleMissing) {
|
|
299
|
-
return undefined
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
this.authPromise = (async () => {
|
|
303
|
-
if (isDev() && typeof window !== "undefined") {
|
|
304
|
-
console.info(
|
|
305
|
-
`%cic-reactor:%c Authenticating...`,
|
|
306
|
-
"color: #3b82f6; font-weight: bold",
|
|
307
|
-
"color: inherit",
|
|
308
|
-
{
|
|
309
|
-
network: this.network,
|
|
310
|
-
authClient: this.#authClient ? "Shared Instance" : "Dynamic Import",
|
|
311
|
-
}
|
|
312
|
-
)
|
|
313
|
-
}
|
|
314
|
-
this.updateAuthState({ isAuthenticating: true })
|
|
315
|
-
try {
|
|
316
|
-
if (!this.#authClient) {
|
|
317
|
-
const authClient = await this.initializeAuthClient()
|
|
318
|
-
if (!authClient) {
|
|
319
|
-
this.updateAuthState({ isAuthenticating: false })
|
|
320
|
-
return undefined
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
const identity = await this.#authClient!.getIdentity()
|
|
324
|
-
const isAuthenticated = await this.#authClient!.isAuthenticated()
|
|
325
|
-
this.updateAgent(identity)
|
|
326
|
-
this.updateAuthState({
|
|
327
|
-
identity,
|
|
328
|
-
isAuthenticated,
|
|
329
|
-
isAuthenticating: false,
|
|
330
|
-
})
|
|
331
|
-
return identity
|
|
332
|
-
} catch (error) {
|
|
333
|
-
this.updateAuthState({ error: error as Error, isAuthenticating: false })
|
|
334
|
-
console.error("Authentication failed:", error)
|
|
335
|
-
throw error
|
|
336
|
-
} finally {
|
|
337
|
-
this.authPromise = undefined
|
|
338
|
-
}
|
|
339
|
-
})()
|
|
340
|
-
|
|
341
|
-
return this.authPromise
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Triggers the login flow using the Internet Identity provider.
|
|
346
|
-
*
|
|
347
|
-
* @param loginOptions - Options for the login flow, including identity provider and callbacks.
|
|
348
|
-
* @throws An error if the authentication module is not installed.
|
|
349
|
-
*/
|
|
350
|
-
public login = async (loginOptions?: ClientManagerSignInOptions) => {
|
|
351
|
-
let didCompleteSignIn = false
|
|
352
|
-
|
|
353
|
-
try {
|
|
354
|
-
const identityProvider =
|
|
355
|
-
loginOptions?.identityProvider || this.getDefaultIdentityProvider()
|
|
356
|
-
const authClientOptions = getAuthClientOptions({
|
|
357
|
-
...loginOptions,
|
|
358
|
-
identityProvider,
|
|
359
|
-
})
|
|
360
|
-
|
|
361
|
-
if (!this.ensurePreparedAuthClient(authClientOptions)) {
|
|
362
|
-
await this.initializeAuthClient(authClientOptions)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (!this.#authClient) {
|
|
366
|
-
await this.authenticate()
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (!this.#authClient) {
|
|
370
|
-
throw new Error(
|
|
371
|
-
"Authentication module is missing or failed to initialize. To use login, please install the auth package: npm install @icp-sdk/auth"
|
|
372
|
-
)
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
this.updateAuthState({ isAuthenticating: true, error: undefined })
|
|
376
|
-
|
|
377
|
-
const identity = await this.signInOrRecoverAuthenticatedIdentity(
|
|
378
|
-
getSignInOptions(loginOptions)
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
if (!this.agentState.isInitialized) {
|
|
382
|
-
await this.initializeAgent()
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
this.updateAgent(identity)
|
|
386
|
-
this.updateAuthState({
|
|
387
|
-
identity,
|
|
388
|
-
isAuthenticated: true,
|
|
389
|
-
isAuthenticating: false,
|
|
390
|
-
})
|
|
391
|
-
didCompleteSignIn = true
|
|
392
|
-
|
|
393
|
-
try {
|
|
394
|
-
await (
|
|
395
|
-
loginOptions?.onSuccess as (() => void | Promise<void>) | undefined
|
|
396
|
-
)?.()
|
|
397
|
-
} catch (callbackError) {
|
|
398
|
-
this.updateAuthState({ error: callbackError as Error })
|
|
399
|
-
await loginOptions?.onError?.((callbackError as Error).message)
|
|
400
|
-
throw callbackError
|
|
401
|
-
}
|
|
402
|
-
} catch (error) {
|
|
403
|
-
if (!didCompleteSignIn) {
|
|
404
|
-
await loginOptions?.onError?.((error as Error).message)
|
|
405
|
-
this.updateAuthState({
|
|
406
|
-
error: error as Error,
|
|
407
|
-
isAuthenticating: false,
|
|
408
|
-
})
|
|
409
|
-
}
|
|
410
|
-
throw error
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* Logs out the user and reverts the agent to an anonymous identity.
|
|
416
|
-
*
|
|
417
|
-
* @throws An error if the authentication module is not installed.
|
|
418
|
-
*/
|
|
419
|
-
public logout = async (options?: { returnTo?: string }) => {
|
|
420
|
-
if (!this.#authClient) {
|
|
421
|
-
throw new Error(
|
|
422
|
-
"Authentication module is missing or failed to initialize. To use logout, please install the auth package: npm install @icp-sdk/auth"
|
|
423
|
-
)
|
|
424
|
-
}
|
|
425
|
-
this.updateAuthState({ isAuthenticating: true, error: undefined })
|
|
426
|
-
await this.#authClient.signOut(options)
|
|
427
|
-
const identity = await this.#authClient.getIdentity()
|
|
428
|
-
this.updateAgent(identity)
|
|
429
|
-
this.updateAuthState({
|
|
430
|
-
identity,
|
|
431
|
-
isAuthenticated: false,
|
|
432
|
-
isAuthenticating: false,
|
|
433
|
-
})
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
public requestIdentityAttributes = async ({
|
|
437
|
-
keys,
|
|
438
|
-
nonce,
|
|
439
|
-
identityProvider,
|
|
440
|
-
openIdProvider,
|
|
441
|
-
windowOpenerFeatures,
|
|
442
|
-
signIn = true,
|
|
443
|
-
maxTimeToLive,
|
|
444
|
-
targets,
|
|
445
|
-
}: RequestIdentityAttributesParameters): Promise<IdentityAttributeResult> => {
|
|
446
|
-
const authClientOptions = getAuthClientOptions({
|
|
447
|
-
identityProvider: identityProvider ?? this.getDefaultIdentityProvider(),
|
|
448
|
-
windowOpenerFeatures,
|
|
449
|
-
openIdProvider,
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
if (!this.ensurePreparedAuthClient(authClientOptions)) {
|
|
453
|
-
await this.initializeAuthClient(authClientOptions)
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (!this.#authClient) {
|
|
457
|
-
await this.authenticate()
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (!this.#authClient) {
|
|
461
|
-
throw new Error(
|
|
462
|
-
"Authentication module is missing or failed to initialize. To request identity attributes, please install @icp-sdk/auth v7 or provide a compatible authClient."
|
|
463
|
-
)
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
this.updateAuthState({ isAuthenticating: true, error: undefined })
|
|
467
|
-
|
|
468
|
-
try {
|
|
469
|
-
const identityPromise = signIn
|
|
470
|
-
? this.signInOrRecoverAuthenticatedIdentity({
|
|
471
|
-
maxTimeToLive,
|
|
472
|
-
targets,
|
|
473
|
-
})
|
|
474
|
-
: Promise.resolve(this.#authClient.getIdentity())
|
|
475
|
-
const requestPromise = this.#authClient.requestAttributes({
|
|
476
|
-
keys,
|
|
477
|
-
nonce,
|
|
478
|
-
})
|
|
479
|
-
|
|
480
|
-
const [signedAttributes, identity] = await Promise.all([
|
|
481
|
-
requestPromise,
|
|
482
|
-
identityPromise,
|
|
483
|
-
])
|
|
484
|
-
|
|
485
|
-
const finalIdentity = identity ?? (await this.#authClient.getIdentity())
|
|
486
|
-
const isAuthenticated = await this.#authClient.isAuthenticated()
|
|
487
|
-
if (!this.agentState.isInitialized) {
|
|
488
|
-
await this.initializeAgent()
|
|
489
|
-
}
|
|
490
|
-
this.updateAgent(finalIdentity)
|
|
491
|
-
this.updateAuthState({
|
|
492
|
-
identity: finalIdentity,
|
|
493
|
-
isAuthenticated,
|
|
494
|
-
isAuthenticating: false,
|
|
495
|
-
})
|
|
496
|
-
|
|
497
|
-
const normalizedSignedAttributes =
|
|
498
|
-
normalizeSignedIdentityAttributes(signedAttributes)
|
|
499
|
-
|
|
500
|
-
return {
|
|
501
|
-
principal: finalIdentity.getPrincipal().toText(),
|
|
502
|
-
requestedKeys: keys,
|
|
503
|
-
signedAttributes: normalizedSignedAttributes,
|
|
504
|
-
decodedAttributes: decodeIdentityAttributeValues(
|
|
505
|
-
normalizedSignedAttributes.data,
|
|
506
|
-
keys
|
|
507
|
-
),
|
|
508
|
-
completedAt: new Date().toISOString(),
|
|
509
|
-
}
|
|
510
|
-
} catch (error) {
|
|
511
|
-
this.updateAuthState({ error: error as Error, isAuthenticating: false })
|
|
512
|
-
throw error
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
public requestOpenIdIdentityAttributes = async ({
|
|
517
|
-
nonce,
|
|
518
|
-
openIdProvider,
|
|
519
|
-
keys,
|
|
520
|
-
identityProvider,
|
|
521
|
-
windowOpenerFeatures,
|
|
522
|
-
signIn,
|
|
523
|
-
maxTimeToLive,
|
|
524
|
-
targets,
|
|
525
|
-
}: RequestOpenIdIdentityAttributesParameters): Promise<IdentityAttributeResult> => {
|
|
526
|
-
return this.requestIdentityAttributes({
|
|
527
|
-
keys: identityAttributeKeys({ openIdProvider, keys }),
|
|
528
|
-
nonce,
|
|
529
|
-
identityProvider,
|
|
530
|
-
openIdProvider,
|
|
531
|
-
windowOpenerFeatures,
|
|
532
|
-
signIn,
|
|
533
|
-
maxTimeToLive,
|
|
534
|
-
targets,
|
|
535
|
-
})
|
|
536
|
-
}
|
|
537
|
-
|
|
538
185
|
/**
|
|
539
186
|
* The underlying HttpAgent managed by this class.
|
|
540
187
|
*/
|
|
@@ -542,143 +189,6 @@ export class ClientManager {
|
|
|
542
189
|
return this.#agent
|
|
543
190
|
}
|
|
544
191
|
|
|
545
|
-
/**
|
|
546
|
-
* The AuthClient instance used for authentication, if available.
|
|
547
|
-
*/
|
|
548
|
-
get authClient() {
|
|
549
|
-
return this.#authClient
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
private async initializeAuthClient(
|
|
553
|
-
options?: ClientManagerAuthClientOptions
|
|
554
|
-
): Promise<AuthClientLike | undefined> {
|
|
555
|
-
const AuthClient = await this.loadAuthClientConstructor()
|
|
556
|
-
|
|
557
|
-
if (!AuthClient) {
|
|
558
|
-
return undefined
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
this.#authClient = this.createAuthClient(AuthClient, options)
|
|
562
|
-
return this.#authClient
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
private async signInOrRecoverAuthenticatedIdentity(
|
|
566
|
-
options?: AuthClientSignInOptions
|
|
567
|
-
): Promise<Identity> {
|
|
568
|
-
if (!this.#authClient) {
|
|
569
|
-
throw new Error(
|
|
570
|
-
"Authentication module is missing or failed to initialize. To use login, please install the auth package: npm install @icp-sdk/auth"
|
|
571
|
-
)
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
try {
|
|
575
|
-
return await this.#authClient.signIn(options)
|
|
576
|
-
} catch (error) {
|
|
577
|
-
const identity = await Promise.resolve(
|
|
578
|
-
this.#authClient.getIdentity()
|
|
579
|
-
).catch(() => null)
|
|
580
|
-
const isAuthenticated = await Promise.resolve(
|
|
581
|
-
this.#authClient.isAuthenticated()
|
|
582
|
-
).catch(() => false)
|
|
583
|
-
|
|
584
|
-
if (identity && isAuthenticated) {
|
|
585
|
-
return identity
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
throw error
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
private async loadAuthClientConstructor(): Promise<
|
|
593
|
-
AuthClientConstructor | undefined
|
|
594
|
-
> {
|
|
595
|
-
if (this.authClientConstructor) {
|
|
596
|
-
return this.authClientConstructor
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
if (!this.authClientConstructorPromise) {
|
|
600
|
-
this.authClientConstructorPromise = import("@icp-sdk/auth/client")
|
|
601
|
-
.then((authModule) => {
|
|
602
|
-
const AuthClient = (
|
|
603
|
-
authModule as { AuthClient?: AuthClientConstructor }
|
|
604
|
-
).AuthClient
|
|
605
|
-
|
|
606
|
-
if (!AuthClient) {
|
|
607
|
-
throw new Error("@icp-sdk/auth/client did not export AuthClient")
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
this.authClientConstructor = AuthClient
|
|
611
|
-
return AuthClient
|
|
612
|
-
})
|
|
613
|
-
.catch((error) => {
|
|
614
|
-
this.authModuleMissing = true
|
|
615
|
-
this.authClientConstructorPromise = undefined
|
|
616
|
-
if (
|
|
617
|
-
error instanceof Error &&
|
|
618
|
-
error.message.includes("did not export AuthClient")
|
|
619
|
-
) {
|
|
620
|
-
throw error
|
|
621
|
-
}
|
|
622
|
-
return undefined
|
|
623
|
-
})
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
return this.authClientConstructorPromise
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
private createAuthClient(
|
|
630
|
-
AuthClient: AuthClientConstructor,
|
|
631
|
-
options?: ClientManagerAuthClientOptions
|
|
632
|
-
): AuthClientLike {
|
|
633
|
-
return new AuthClient(options)
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
private ensurePreparedAuthClient(
|
|
637
|
-
options?: ClientManagerAuthClientOptions
|
|
638
|
-
): AuthClientLike | undefined {
|
|
639
|
-
if (
|
|
640
|
-
this.#authClient &&
|
|
641
|
-
(!this.shouldRecreateAuthClient(options) || this.authClientWasProvided)
|
|
642
|
-
) {
|
|
643
|
-
return this.#authClient
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
if (!this.authClientConstructor || this.authClientWasProvided) {
|
|
647
|
-
return undefined
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
this.#authClient = this.createAuthClient(
|
|
651
|
-
this.authClientConstructor,
|
|
652
|
-
options
|
|
653
|
-
)
|
|
654
|
-
return this.#authClient
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
private shouldRecreateAuthClient(
|
|
658
|
-
options?: ClientManagerAuthClientOptions
|
|
659
|
-
): boolean {
|
|
660
|
-
return !this.authClientWasProvided && hasAuthClientOptions(options)
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
private async syncAuthStateFromClient(revision = this.authStateRevision) {
|
|
664
|
-
if (!this.#authClient) {
|
|
665
|
-
return
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
const identity = await this.#authClient.getIdentity()
|
|
669
|
-
const isAuthenticated = await this.#authClient.isAuthenticated()
|
|
670
|
-
if (revision !== this.authStateRevision) {
|
|
671
|
-
return
|
|
672
|
-
}
|
|
673
|
-
this.updateAgent(identity)
|
|
674
|
-
this.updateAuthState({
|
|
675
|
-
identity,
|
|
676
|
-
isAuthenticated,
|
|
677
|
-
isAuthenticating: false,
|
|
678
|
-
error: undefined,
|
|
679
|
-
})
|
|
680
|
-
}
|
|
681
|
-
|
|
682
192
|
/**
|
|
683
193
|
* The host URL of the current IC agent.
|
|
684
194
|
*/
|
|
@@ -757,17 +267,6 @@ export class ClientManager {
|
|
|
757
267
|
return this.#agent.syncTimeWithSubnet(subnetId)
|
|
758
268
|
}
|
|
759
269
|
|
|
760
|
-
private getDefaultIdentityProvider(): string {
|
|
761
|
-
if (this.isLocal) {
|
|
762
|
-
if (this.internetIdentityId) {
|
|
763
|
-
return `http://${this.internetIdentityId}.localhost:${this.port}/authorize`
|
|
764
|
-
}
|
|
765
|
-
return LOCAL_INTERNET_IDENTITY_PROVIDER
|
|
766
|
-
} else {
|
|
767
|
-
return IC_INTERNET_IDENTITY_PROVIDER
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
270
|
/**
|
|
772
271
|
* Subscribes to identity changes (e.g., after login/logout).
|
|
773
272
|
* @param callback - Function called with the new identity.
|
|
@@ -796,20 +295,6 @@ export class ClientManager {
|
|
|
796
295
|
}
|
|
797
296
|
}
|
|
798
297
|
|
|
799
|
-
/**
|
|
800
|
-
* Subscribes to changes in the authentication state.
|
|
801
|
-
* @param callback - Function called with the updated authentication state.
|
|
802
|
-
* @returns An unsubscribe function.
|
|
803
|
-
*/
|
|
804
|
-
public subscribeAuthState(callback: (state: AuthState) => void) {
|
|
805
|
-
this.#authStateSubscribers.push(callback)
|
|
806
|
-
return () => {
|
|
807
|
-
this.#authStateSubscribers = this.#authStateSubscribers.filter(
|
|
808
|
-
(sub) => sub !== callback
|
|
809
|
-
)
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
298
|
/**
|
|
814
299
|
* Replaces the current agent's identity and invalidates TanStack queries.
|
|
815
300
|
* @param identity - The new identity to use.
|
|
@@ -846,66 +331,8 @@ export class ClientManager {
|
|
|
846
331
|
this.#agentStateSubscribers.forEach((sub) => sub(state))
|
|
847
332
|
}
|
|
848
333
|
|
|
849
|
-
private notifyAuthStateSubscribers(state: AuthState) {
|
|
850
|
-
this.#authStateSubscribers.forEach((sub) => sub(state))
|
|
851
|
-
}
|
|
852
|
-
|
|
853
334
|
private updateAgentState(newState: Partial<AgentState>) {
|
|
854
335
|
this.agentState = { ...this.agentState, ...newState }
|
|
855
336
|
this.notifyAgentStateSubscribers(this.agentState)
|
|
856
337
|
}
|
|
857
|
-
|
|
858
|
-
private updateAuthState(newState: Partial<AuthState>) {
|
|
859
|
-
if (isDev()) console.debug("[ic-reactor] Updating Auth State:", newState)
|
|
860
|
-
this.authStateRevision += 1
|
|
861
|
-
this.authState = { ...this.authState, ...newState }
|
|
862
|
-
this.notifyAuthStateSubscribers(this.authState)
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
function getAuthClientOptions(
|
|
867
|
-
options?: ClientManagerAuthClientOptions
|
|
868
|
-
): ClientManagerAuthClientOptions | undefined {
|
|
869
|
-
if (!options) {
|
|
870
|
-
return undefined
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
return {
|
|
874
|
-
identityProvider: options.identityProvider,
|
|
875
|
-
windowOpenerFeatures: options.windowOpenerFeatures,
|
|
876
|
-
openIdProvider: getAuthClientOpenIdProvider(options.openIdProvider),
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
function getAuthClientOpenIdProvider(
|
|
881
|
-
openIdProvider?: ClientManagerAuthClientOptions["openIdProvider"]
|
|
882
|
-
): ClientManagerAuthClientOptions["openIdProvider"] | undefined {
|
|
883
|
-
return openIdProvider === "google" ||
|
|
884
|
-
openIdProvider === "apple" ||
|
|
885
|
-
openIdProvider === "microsoft"
|
|
886
|
-
? openIdProvider
|
|
887
|
-
: undefined
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
function hasAuthClientOptions(
|
|
891
|
-
options?: ClientManagerAuthClientOptions
|
|
892
|
-
): boolean {
|
|
893
|
-
return Boolean(
|
|
894
|
-
options?.identityProvider ||
|
|
895
|
-
options?.windowOpenerFeatures ||
|
|
896
|
-
options?.openIdProvider
|
|
897
|
-
)
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
function getSignInOptions(
|
|
901
|
-
options?: ClientManagerSignInOptions
|
|
902
|
-
): AuthClientSignInOptions | undefined {
|
|
903
|
-
if (!options) {
|
|
904
|
-
return undefined
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
return {
|
|
908
|
-
maxTimeToLive: options.maxTimeToLive,
|
|
909
|
-
targets: options.targets,
|
|
910
|
-
}
|
|
911
338
|
}
|