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