@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.
- package/README.md +117 -13
- package/dist/auth/authentication-manager.d.ts +98 -0
- package/dist/auth/authentication-manager.d.ts.map +1 -0
- package/dist/auth/authentication-manager.js +532 -0
- package/dist/auth/authentication-manager.js.map +1 -0
- package/dist/auth/constants.d.ts +18 -0
- package/dist/auth/constants.d.ts.map +1 -0
- package/dist/auth/constants.js +18 -0
- package/dist/auth/constants.js.map +1 -0
- package/dist/auth/createIdentityAttributeHooks.d.ts +14 -0
- package/dist/auth/createIdentityAttributeHooks.d.ts.map +1 -0
- package/dist/auth/createIdentityAttributeHooks.js +54 -0
- package/dist/auth/createIdentityAttributeHooks.js.map +1 -0
- package/dist/auth/identity-attributes-manager.d.ts +26 -0
- package/dist/auth/identity-attributes-manager.d.ts.map +1 -0
- package/dist/auth/identity-attributes-manager.js +108 -0
- package/dist/auth/identity-attributes-manager.js.map +1 -0
- package/dist/auth/identity-attributes.d.ts +19 -0
- package/dist/auth/identity-attributes.d.ts.map +1 -0
- package/dist/auth/identity-attributes.js +170 -0
- package/dist/auth/identity-attributes.js.map +1 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/types.d.ts +182 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/createActorHooks.d.ts +16 -16
- package/dist/createActorHooks.d.ts.map +1 -1
- package/dist/createActorHooks.js +6 -6
- package/dist/createActorHooks.js.map +1 -1
- package/dist/createInfiniteQuery.d.ts +33 -33
- package/dist/createInfiniteQuery.d.ts.map +1 -1
- package/dist/createInfiniteQuery.js +6 -6
- package/dist/createInfiniteQuery.js.map +1 -1
- package/dist/createMutation.d.ts +13 -2
- package/dist/createMutation.d.ts.map +1 -1
- package/dist/createMutation.js +11 -0
- package/dist/createMutation.js.map +1 -1
- package/dist/createQuery.d.ts +13 -3
- package/dist/createQuery.d.ts.map +1 -1
- package/dist/createQuery.js +16 -6
- package/dist/createQuery.js.map +1 -1
- package/dist/createSuspenseInfiniteQuery.d.ts +32 -32
- package/dist/createSuspenseInfiniteQuery.d.ts.map +1 -1
- package/dist/createSuspenseInfiniteQuery.js +6 -6
- package/dist/createSuspenseInfiniteQuery.js.map +1 -1
- package/dist/createSuspenseQuery.d.ts +3 -3
- package/dist/createSuspenseQuery.d.ts.map +1 -1
- package/dist/createSuspenseQuery.js +1 -1
- package/dist/createSuspenseQuery.js.map +1 -1
- package/dist/defineReactor.d.ts +124 -0
- package/dist/defineReactor.d.ts.map +1 -0
- package/dist/defineReactor.js +145 -0
- package/dist/defineReactor.js.map +1 -0
- package/dist/{createAuthHooks.d.ts → hooks/createAuthHooks.d.ts} +6 -13
- package/dist/hooks/createAuthHooks.d.ts.map +1 -0
- package/dist/{createAuthHooks.js → hooks/createAuthHooks.js} +18 -58
- package/dist/hooks/createAuthHooks.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.d.ts +9 -9
- package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.js +3 -1
- package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
- package/dist/hooks/useActorMethod.d.ts +17 -17
- package/dist/hooks/useActorMethod.d.ts.map +1 -1
- package/dist/hooks/useActorMethod.js.map +1 -1
- package/dist/hooks/useActorMutation.d.ts +18 -7
- package/dist/hooks/useActorMutation.d.ts.map +1 -1
- package/dist/hooks/useActorMutation.js +11 -0
- package/dist/hooks/useActorMutation.js.map +1 -1
- package/dist/hooks/useActorQuery.d.ts +8 -8
- package/dist/hooks/useActorQuery.d.ts.map +1 -1
- package/dist/hooks/useActorQuery.js.map +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +9 -9
- package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.js +3 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
- package/dist/hooks/useActorSuspenseQuery.d.ts +8 -8
- package/dist/hooks/useActorSuspenseQuery.d.ts.map +1 -1
- package/dist/hooks/useActorSuspenseQuery.js.map +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -10
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +24 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +10 -2
- package/dist/utils.js.map +1 -1
- package/llms.txt +48 -0
- package/package.json +34 -23
- package/src/auth/authentication-manager.ts +591 -0
- package/src/auth/constants.ts +25 -0
- package/src/auth/createIdentityAttributeHooks.ts +83 -0
- package/src/auth/identity-attributes-manager.ts +136 -0
- package/src/auth/identity-attributes.ts +270 -0
- package/src/auth/index.ts +7 -0
- package/src/auth/types.ts +204 -0
- package/src/createActorHooks.ts +90 -58
- package/src/createInfiniteQuery.ts +154 -98
- package/src/createMutation.ts +35 -19
- package/src/createQuery.ts +94 -50
- package/src/createSuspenseInfiniteQuery.ts +166 -115
- package/src/createSuspenseQuery.ts +69 -43
- package/src/defineReactor.ts +272 -0
- package/src/{createAuthHooks.ts → hooks/createAuthHooks.ts} +23 -99
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useActorInfiniteQuery.ts +44 -30
- package/src/hooks/useActorMethod.ts +62 -53
- package/src/hooks/useActorMutation.ts +47 -30
- package/src/hooks/useActorQuery.ts +36 -32
- package/src/hooks/useActorSuspenseInfiniteQuery.ts +56 -30
- package/src/hooks/useActorSuspenseQuery.ts +42 -32
- package/src/index.ts +12 -10
- package/src/types.ts +76 -71
- package/src/utils.ts +15 -2
- package/dist/createAuthHooks.d.ts.map +0 -1
- package/dist/createAuthHooks.js.map +0 -1
- package/src/hooks/useActorInfiniteQuery.test.tsx +0 -457
- package/src/hooks/useActorMethod.test.tsx +0 -798
- package/src/hooks/useActorMutation.test.tsx +0 -220
- package/src/hooks/useActorQuery.test.tsx +0 -287
- package/src/hooks/useActorSuspenseInfiniteQuery.test.tsx +0 -472
- 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.
|
|
4
|
-
"description": "IC Reactor React
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
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": "^
|
|
60
|
-
"@icp-sdk/core": "^
|
|
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.
|
|
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.
|
|
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.
|
|
71
|
-
"react-dom": "^19.2.
|
|
74
|
+
"react": "^19.2.8",
|
|
75
|
+
"react-dom": "^19.2.8",
|
|
72
76
|
"size-limit": "^12.1.0",
|
|
73
|
-
"vitest": "^4.1.
|
|
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
|
+
}
|