@ic-reactor/react 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 +28 -8
- package/dist/auth/authentication-manager.d.ts +66 -0
- package/dist/auth/authentication-manager.d.ts.map +1 -0
- package/dist/auth/authentication-manager.js +458 -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 +19 -0
- package/dist/auth/identity-attributes-manager.d.ts.map +1 -0
- package/dist/auth/identity-attributes-manager.js +94 -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 +75 -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 +12 -12
- package/dist/createActorHooks.d.ts.map +1 -1
- package/dist/createActorHooks.js.map +1 -1
- package/dist/createInfiniteQuery.d.ts +32 -32
- 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 +12 -1
- 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 +12 -2
- package/dist/createQuery.d.ts.map +1 -1
- package/dist/createQuery.js +15 -5
- package/dist/createQuery.js.map +1 -1
- package/dist/createSuspenseInfiniteQuery.d.ts +31 -31
- 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 +2 -2
- package/dist/createSuspenseQuery.d.ts.map +1 -1
- package/dist/createSuspenseQuery.js.map +1 -1
- package/dist/defineReactor.d.ts +74 -0
- package/dist/defineReactor.d.ts.map +1 -0
- package/dist/defineReactor.js +75 -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} +12 -57
- 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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- 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 +27 -21
- package/src/auth/authentication-manager.ts +519 -0
- package/src/auth/constants.ts +25 -0
- package/src/auth/createIdentityAttributeHooks.ts +83 -0
- package/src/auth/identity-attributes-manager.ts +127 -0
- package/src/auth/identity-attributes.ts +270 -0
- package/src/auth/index.ts +7 -0
- package/src/auth/types.ts +88 -0
- package/src/createActorHooks.ts +81 -49
- package/src/createInfiniteQuery.ts +153 -97
- package/src/createMutation.ts +34 -18
- package/src/createQuery.ts +92 -48
- package/src/createSuspenseInfiniteQuery.ts +165 -114
- package/src/createSuspenseQuery.ts +67 -41
- package/src/defineReactor.test.ts +70 -0
- package/src/defineReactor.ts +150 -0
- package/src/{createAuthHooks.ts → hooks/createAuthHooks.ts} +17 -98
- 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 +3 -1
- package/src/types.ts +76 -71
- package/src/utils.test.ts +14 -0
- package/src/utils.ts +15 -2
- package/dist/createAuthHooks.d.ts.map +0 -1
- package/dist/createAuthHooks.js.map +0 -1
|
@@ -0,0 +1,519 @@
|
|
|
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"
|
|
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"
|
|
17
|
+
|
|
18
|
+
export interface AuthenticationManagerParameters {
|
|
19
|
+
clientManager: ClientManager
|
|
20
|
+
authClient?: AuthClientLike
|
|
21
|
+
identityProvider?: string | URL
|
|
22
|
+
internetIdentityId?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type AuthClientConstructor = {
|
|
26
|
+
new (options?: AuthenticationClientOptions): AuthClientLike
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const AUTH_CLIENT_MODULE = "@icp-sdk/auth/client"
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Manages Internet Identity sign-in, session restoration, and authentication
|
|
33
|
+
* state for a {@link ClientManager}.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const authentication = new AuthenticationManager({ clientManager })
|
|
38
|
+
* const identity = await authentication.authenticate()
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export class AuthenticationManager {
|
|
42
|
+
private authClient?: AuthClientLike
|
|
43
|
+
private authPromise?: Promise<Identity | undefined>
|
|
44
|
+
private authClientWasProvided = false
|
|
45
|
+
private authStateRevision = 0
|
|
46
|
+
private authStateSubscribers: Array<(state: AuthState) => void> = []
|
|
47
|
+
private authClientConstructor?: AuthClientConstructor
|
|
48
|
+
private authClientConstructorPromise?: Promise<
|
|
49
|
+
AuthClientConstructor | undefined
|
|
50
|
+
>
|
|
51
|
+
private authModuleMissing = false
|
|
52
|
+
private authStateValue: AuthState = {
|
|
53
|
+
identity: null,
|
|
54
|
+
isAuthenticating: false,
|
|
55
|
+
isAuthenticated: false,
|
|
56
|
+
error: undefined,
|
|
57
|
+
}
|
|
58
|
+
private readonly identityProvider?: string | URL
|
|
59
|
+
private readonly internetIdentityId?: string
|
|
60
|
+
public readonly clientManager: ClientManager
|
|
61
|
+
|
|
62
|
+
/** The current authentication state. */
|
|
63
|
+
public get authState(): AuthState {
|
|
64
|
+
return this.authStateValue
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
constructor({
|
|
68
|
+
clientManager,
|
|
69
|
+
authClient,
|
|
70
|
+
identityProvider,
|
|
71
|
+
internetIdentityId,
|
|
72
|
+
}: AuthenticationManagerParameters) {
|
|
73
|
+
this.clientManager = clientManager
|
|
74
|
+
const canisterEnv =
|
|
75
|
+
typeof window !== "undefined" ? getAuthenticationCanisterEnv() : undefined
|
|
76
|
+
this.identityProvider =
|
|
77
|
+
identityProvider ||
|
|
78
|
+
canisterEnv?.[INTERNET_IDENTITY_PROVIDER_ENV_KEY] ||
|
|
79
|
+
canisterEnv?.["PUBLIC_INTERNET_IDENTITY_PROVIDER"]
|
|
80
|
+
this.internetIdentityId =
|
|
81
|
+
internetIdentityId ||
|
|
82
|
+
canisterEnv?.["internet_identity"] ||
|
|
83
|
+
canisterEnv?.["PUBLIC_CANISTER_ID:internet_identity"] ||
|
|
84
|
+
canisterEnv?.["CANISTER_ID_INTERNET_IDENTITY"]
|
|
85
|
+
|
|
86
|
+
if (authClient) {
|
|
87
|
+
this.authClientWasProvided = true
|
|
88
|
+
this.authClient = authClient
|
|
89
|
+
this.syncStateFromClient(this.authStateRevision).catch((error) => {
|
|
90
|
+
this.updateState({ error: error as Error, isAuthenticating: false })
|
|
91
|
+
})
|
|
92
|
+
} else if (typeof window !== "undefined") {
|
|
93
|
+
this.loadAuthClientConstructor().catch(() => {
|
|
94
|
+
// Optional auth support is reported when an auth method is used.
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** @internal Used by IdentityAttributesManager. */
|
|
100
|
+
public get client() {
|
|
101
|
+
return this.authClient
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public subscribeAuthState(callback: (state: AuthState) => void) {
|
|
105
|
+
this.authStateSubscribers.push(callback)
|
|
106
|
+
return () => {
|
|
107
|
+
this.authStateSubscribers = this.authStateSubscribers.filter(
|
|
108
|
+
(subscriber) => subscriber !== callback
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Preloads and creates an AuthClient before a user gesture is needed.
|
|
115
|
+
*/
|
|
116
|
+
public async prepareClient(options?: AuthenticationClientOptions) {
|
|
117
|
+
const clientOptions = getAuthClientOptions({
|
|
118
|
+
...options,
|
|
119
|
+
identityProvider:
|
|
120
|
+
options?.identityProvider ?? this.getDefaultIdentityProvider(),
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
if (
|
|
124
|
+
this.authClient &&
|
|
125
|
+
(!this.shouldRecreateClient(clientOptions) || this.authClientWasProvided)
|
|
126
|
+
) {
|
|
127
|
+
return this.authClient
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return this.initializeClient(clientOptions)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public authenticate = async (): Promise<Identity | undefined> => {
|
|
134
|
+
if (this.authState.isAuthenticated) {
|
|
135
|
+
return this.authState.identity || undefined
|
|
136
|
+
}
|
|
137
|
+
if (this.authPromise) {
|
|
138
|
+
return this.authPromise
|
|
139
|
+
}
|
|
140
|
+
if (this.authModuleMissing) {
|
|
141
|
+
return undefined
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.authPromise = (async () => {
|
|
145
|
+
if (isDev() && typeof window !== "undefined") {
|
|
146
|
+
console.info(
|
|
147
|
+
`%cic-reactor:%c Authenticating...`,
|
|
148
|
+
"color: #3b82f6; font-weight: bold",
|
|
149
|
+
"color: inherit",
|
|
150
|
+
{
|
|
151
|
+
network: this.clientManager.network,
|
|
152
|
+
authClient: this.authClient ? "Shared Instance" : "Dynamic Import",
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
this.updateState({ isAuthenticating: true })
|
|
157
|
+
try {
|
|
158
|
+
if (!this.authClient) {
|
|
159
|
+
const authClient = await this.initializeClient(
|
|
160
|
+
getAuthClientOptions({
|
|
161
|
+
identityProvider: this.getDefaultIdentityProvider(),
|
|
162
|
+
})
|
|
163
|
+
)
|
|
164
|
+
if (!authClient) {
|
|
165
|
+
this.updateState({ isAuthenticating: false })
|
|
166
|
+
return undefined
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const identity = await this.authClient!.getIdentity()
|
|
170
|
+
const isAuthenticated = await this.authClient!.isAuthenticated()
|
|
171
|
+
this.clientManager.updateAgent(identity)
|
|
172
|
+
this.updateState({
|
|
173
|
+
identity,
|
|
174
|
+
isAuthenticated,
|
|
175
|
+
isAuthenticating: false,
|
|
176
|
+
})
|
|
177
|
+
return identity
|
|
178
|
+
} catch (error) {
|
|
179
|
+
this.updateState({ error: error as Error, isAuthenticating: false })
|
|
180
|
+
console.error("Authentication failed:", error)
|
|
181
|
+
throw error
|
|
182
|
+
} finally {
|
|
183
|
+
this.authPromise = undefined
|
|
184
|
+
}
|
|
185
|
+
})()
|
|
186
|
+
|
|
187
|
+
return this.authPromise
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
public login = async (loginOptions?: AuthenticationSignInOptions) => {
|
|
191
|
+
let didCompleteSignIn = false
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const identityProvider =
|
|
195
|
+
loginOptions?.identityProvider || this.getDefaultIdentityProvider()
|
|
196
|
+
const authClientOptions = getAuthClientOptions({
|
|
197
|
+
...loginOptions,
|
|
198
|
+
identityProvider,
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
if (!this.ensurePreparedClient(authClientOptions)) {
|
|
202
|
+
await this.initializeClient(authClientOptions)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!this.authClient) {
|
|
206
|
+
await this.authenticate()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!this.authClient) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
"Authentication module is missing or failed to initialize. To use login, please install the auth package: npm install @icp-sdk/auth"
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this.updateState({ isAuthenticating: true, error: undefined })
|
|
216
|
+
const identity = await this.signInOrRecoverIdentity(
|
|
217
|
+
getSignInOptions(loginOptions)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if (!this.clientManager.agentState.isInitialized) {
|
|
221
|
+
await this.clientManager.initializeAgent()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.clientManager.updateAgent(identity)
|
|
225
|
+
this.updateState({
|
|
226
|
+
identity,
|
|
227
|
+
isAuthenticated: true,
|
|
228
|
+
isAuthenticating: false,
|
|
229
|
+
})
|
|
230
|
+
didCompleteSignIn = true
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
await loginOptions?.onSuccess?.()
|
|
234
|
+
} catch (callbackError) {
|
|
235
|
+
this.updateState({ error: callbackError as Error })
|
|
236
|
+
await loginOptions?.onError?.((callbackError as Error).message)
|
|
237
|
+
throw callbackError
|
|
238
|
+
}
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (!didCompleteSignIn) {
|
|
241
|
+
await loginOptions?.onError?.((error as Error).message)
|
|
242
|
+
this.updateState({
|
|
243
|
+
error: error as Error,
|
|
244
|
+
isAuthenticating: false,
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
throw error
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public logout = async (options?: { returnTo?: string }) => {
|
|
252
|
+
if (!this.authClient) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
"Authentication module is missing or failed to initialize. To use logout, please install the auth package: npm install @icp-sdk/auth"
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
this.updateState({ isAuthenticating: true, error: undefined })
|
|
258
|
+
await this.authClient.signOut(options)
|
|
259
|
+
const identity = await this.authClient.getIdentity()
|
|
260
|
+
this.clientManager.updateAgent(identity)
|
|
261
|
+
this.updateState({
|
|
262
|
+
identity,
|
|
263
|
+
isAuthenticated: false,
|
|
264
|
+
isAuthenticating: false,
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private async initializeClient(
|
|
269
|
+
options?: AuthenticationClientOptions
|
|
270
|
+
): Promise<AuthClientLike | undefined> {
|
|
271
|
+
const AuthClient = await this.loadAuthClientConstructor()
|
|
272
|
+
|
|
273
|
+
if (!AuthClient) {
|
|
274
|
+
return undefined
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.authClient = new AuthClient(options)
|
|
278
|
+
return this.authClient
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** @internal Used by IdentityAttributesManager. */
|
|
282
|
+
public async signInOrRecoverIdentity(
|
|
283
|
+
options?: AuthClientSignInOptions
|
|
284
|
+
): Promise<Identity> {
|
|
285
|
+
if (!this.authClient) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
"Authentication module is missing or failed to initialize. To use login, please install the auth package: npm install @icp-sdk/auth"
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
return await this.authClient.signIn(options)
|
|
293
|
+
} catch (error) {
|
|
294
|
+
const identity = await Promise.resolve(
|
|
295
|
+
this.authClient.getIdentity()
|
|
296
|
+
).catch(() => null)
|
|
297
|
+
const isAuthenticated = await Promise.resolve(
|
|
298
|
+
this.authClient.isAuthenticated()
|
|
299
|
+
).catch(() => false)
|
|
300
|
+
|
|
301
|
+
if (identity && isAuthenticated) {
|
|
302
|
+
return identity
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
throw error
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private ensurePreparedClient(
|
|
310
|
+
options?: AuthenticationClientOptions
|
|
311
|
+
): AuthClientLike | undefined {
|
|
312
|
+
if (
|
|
313
|
+
this.authClient &&
|
|
314
|
+
(!this.shouldRecreateClient(options) || this.authClientWasProvided)
|
|
315
|
+
) {
|
|
316
|
+
return this.authClient
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const AuthClient = this.authClientConstructor
|
|
320
|
+
if (!AuthClient || this.authClientWasProvided) {
|
|
321
|
+
return undefined
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
this.authClient = new AuthClient(options)
|
|
325
|
+
return this.authClient
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private shouldRecreateClient(options?: AuthenticationClientOptions): boolean {
|
|
329
|
+
return !this.authClientWasProvided && hasAuthClientOptions(options)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private async syncStateFromClient(revision = this.authStateRevision) {
|
|
333
|
+
if (!this.authClient) {
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const identity = await this.authClient.getIdentity()
|
|
338
|
+
const isAuthenticated = await this.authClient.isAuthenticated()
|
|
339
|
+
if (revision !== this.authStateRevision) {
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
this.clientManager.updateAgent(identity)
|
|
343
|
+
this.updateState({
|
|
344
|
+
identity,
|
|
345
|
+
isAuthenticated,
|
|
346
|
+
isAuthenticating: false,
|
|
347
|
+
error: undefined,
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** @internal Used by IdentityAttributesManager. */
|
|
352
|
+
public async ensureClient(options?: AuthenticationClientOptions) {
|
|
353
|
+
const clientOptions = getAuthClientOptions({
|
|
354
|
+
...options,
|
|
355
|
+
identityProvider:
|
|
356
|
+
options?.identityProvider ?? this.getDefaultIdentityProvider(),
|
|
357
|
+
})
|
|
358
|
+
if (!this.ensurePreparedClient(clientOptions)) {
|
|
359
|
+
await this.initializeClient(clientOptions)
|
|
360
|
+
}
|
|
361
|
+
return this.authClient
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** @internal Used by IdentityAttributesManager. */
|
|
365
|
+
public async commitIdentity(identity: Identity, isAuthenticated: boolean) {
|
|
366
|
+
if (!this.clientManager.agentState.isInitialized) {
|
|
367
|
+
await this.clientManager.initializeAgent()
|
|
368
|
+
}
|
|
369
|
+
this.clientManager.updateAgent(identity)
|
|
370
|
+
this.updateState({ identity, isAuthenticated, isAuthenticating: false })
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** @internal Used by IdentityAttributesManager. */
|
|
374
|
+
public setAuthenticating() {
|
|
375
|
+
this.updateState({ isAuthenticating: true, error: undefined })
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** @internal Used by IdentityAttributesManager. */
|
|
379
|
+
public setAuthenticationError(error: Error) {
|
|
380
|
+
this.updateState({ error, isAuthenticating: false })
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private getDefaultIdentityProvider(): string | URL {
|
|
384
|
+
if (this.identityProvider) {
|
|
385
|
+
return this.identityProvider
|
|
386
|
+
}
|
|
387
|
+
return this.clientManager.isLocal
|
|
388
|
+
? localInternetIdentityProvider(
|
|
389
|
+
Number(this.clientManager.agentHost?.port) || 4943,
|
|
390
|
+
this.internetIdentityId
|
|
391
|
+
)
|
|
392
|
+
: IC_INTERNET_IDENTITY_PROVIDER
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private updateState(newState: Partial<AuthState>) {
|
|
396
|
+
if (isDev()) console.debug("[ic-reactor] Updating Auth State:", newState)
|
|
397
|
+
this.authStateRevision += 1
|
|
398
|
+
this.authStateValue = { ...this.authStateValue, ...newState }
|
|
399
|
+
this.authStateSubscribers.forEach((subscriber) =>
|
|
400
|
+
subscriber(this.authStateValue)
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private async loadAuthClientConstructor() {
|
|
405
|
+
if (this.authClientConstructor) {
|
|
406
|
+
return this.authClientConstructor
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (!this.authClientConstructorPromise) {
|
|
410
|
+
// Keep the optional auth peer out of non-auth bundles. Vite/Rollup/esbuild
|
|
411
|
+
// can otherwise try to resolve this package even when auth APIs are unused.
|
|
412
|
+
this.authClientConstructorPromise = import(
|
|
413
|
+
/* @vite-ignore */ AUTH_CLIENT_MODULE
|
|
414
|
+
)
|
|
415
|
+
.then((authModule) => {
|
|
416
|
+
const AuthClient = (
|
|
417
|
+
authModule as { AuthClient?: AuthClientConstructor }
|
|
418
|
+
).AuthClient
|
|
419
|
+
|
|
420
|
+
if (!AuthClient) {
|
|
421
|
+
throw new Error("@icp-sdk/auth/client did not export AuthClient")
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
this.authClientConstructor = AuthClient
|
|
425
|
+
return AuthClient
|
|
426
|
+
})
|
|
427
|
+
.catch((error) => {
|
|
428
|
+
this.authModuleMissing = true
|
|
429
|
+
this.authClientConstructorPromise = undefined
|
|
430
|
+
if (
|
|
431
|
+
error instanceof Error &&
|
|
432
|
+
error.message.includes("did not export AuthClient")
|
|
433
|
+
) {
|
|
434
|
+
throw error
|
|
435
|
+
}
|
|
436
|
+
return undefined
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return this.authClientConstructorPromise
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function getAuthClientOptions(
|
|
445
|
+
options?: AuthenticationClientOptions
|
|
446
|
+
): AuthenticationClientOptions | undefined {
|
|
447
|
+
if (!options) {
|
|
448
|
+
return undefined
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
identityProvider: options.identityProvider,
|
|
453
|
+
windowOpenerFeatures: options.windowOpenerFeatures,
|
|
454
|
+
openIdProvider: getAuthClientOpenIdProvider(options.openIdProvider),
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function getAuthClientOpenIdProvider(
|
|
459
|
+
openIdProvider?: AuthenticationClientOptions["openIdProvider"]
|
|
460
|
+
): AuthenticationClientOptions["openIdProvider"] | undefined {
|
|
461
|
+
return openIdProvider === "google" ||
|
|
462
|
+
openIdProvider === "apple" ||
|
|
463
|
+
openIdProvider === "microsoft"
|
|
464
|
+
? openIdProvider
|
|
465
|
+
: undefined
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function hasAuthClientOptions(options?: AuthenticationClientOptions): boolean {
|
|
469
|
+
return Boolean(
|
|
470
|
+
options?.identityProvider ||
|
|
471
|
+
options?.windowOpenerFeatures ||
|
|
472
|
+
options?.openIdProvider
|
|
473
|
+
)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function getAuthenticationCanisterEnv(): Record<string, string> | undefined {
|
|
477
|
+
const canisterEnv = safeGetCanisterEnv<Record<string, string>>()
|
|
478
|
+
if (canisterEnv) {
|
|
479
|
+
return canisterEnv as unknown as Record<string, string>
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (typeof document === "undefined") {
|
|
483
|
+
return undefined
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const cookie = document.cookie
|
|
487
|
+
.split(";")
|
|
488
|
+
.find((part) => part.trim().startsWith("ic_env="))
|
|
489
|
+
const encodedValue = cookie?.split("=").slice(1).join("=")?.trim()
|
|
490
|
+
if (!encodedValue) {
|
|
491
|
+
return undefined
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const env = Object.fromEntries(
|
|
495
|
+
decodeURIComponent(encodedValue)
|
|
496
|
+
.split("&")
|
|
497
|
+
.map((entry) => {
|
|
498
|
+
const separatorIndex = entry.indexOf("=")
|
|
499
|
+
return separatorIndex === -1
|
|
500
|
+
? [entry, ""]
|
|
501
|
+
: [entry.slice(0, separatorIndex), entry.slice(separatorIndex + 1)]
|
|
502
|
+
})
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
return Object.keys(env).length ? env : undefined
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function getSignInOptions(
|
|
509
|
+
options?: AuthenticationSignInOptions
|
|
510
|
+
): AuthClientSignInOptions | undefined {
|
|
511
|
+
if (!options) {
|
|
512
|
+
return undefined
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return {
|
|
516
|
+
maxTimeToLive: options.maxTimeToLive,
|
|
517
|
+
targets: options.targets,
|
|
518
|
+
}
|
|
519
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const IC_INTERNET_IDENTITY_PROVIDER = "https://id.ai/authorize"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `ic_env` key used to provide a fully resolved Internet Identity URL.
|
|
5
|
+
*/
|
|
6
|
+
export const INTERNET_IDENTITY_PROVIDER_ENV_KEY = "INTERNET_IDENTITY_PROVIDER"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Well-known Internet Identity canister ID used when deploying II locally.
|
|
10
|
+
*/
|
|
11
|
+
export const LOCAL_INTERNET_IDENTITY_CANISTER_ID = "rdmx6-jaaaa-aaaaa-aaadq-cai"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Builds the local Internet Identity provider URL for the given replica port.
|
|
15
|
+
*/
|
|
16
|
+
export const localInternetIdentityProvider = (
|
|
17
|
+
port: number,
|
|
18
|
+
canisterId: string = LOCAL_INTERNET_IDENTITY_CANISTER_ID
|
|
19
|
+
) => `http://${canisterId}.localhost:${port}/authorize`
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Legacy default URL for local Internet Identity, using port `4943`.
|
|
23
|
+
*/
|
|
24
|
+
export const LOCAL_INTERNET_IDENTITY_PROVIDER =
|
|
25
|
+
localInternetIdentityProvider(4943)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useCallback, useState } from "react"
|
|
2
|
+
import type { IdentityAttributesManager } from "./identity-attributes-manager"
|
|
3
|
+
import type {
|
|
4
|
+
IdentityAttributeResult,
|
|
5
|
+
RequestIdentityAttributesParameters,
|
|
6
|
+
RequestOpenIdIdentityAttributesParameters,
|
|
7
|
+
} from "./types"
|
|
8
|
+
|
|
9
|
+
export interface UseIdentityAttributesReturn {
|
|
10
|
+
requestAttributes: (
|
|
11
|
+
params: RequestIdentityAttributesParameters
|
|
12
|
+
) => Promise<IdentityAttributeResult>
|
|
13
|
+
requestOpenIdAttributes: (
|
|
14
|
+
params: RequestOpenIdIdentityAttributesParameters
|
|
15
|
+
) => Promise<IdentityAttributeResult>
|
|
16
|
+
attributes: IdentityAttributeResult | null
|
|
17
|
+
isRequestingAttributes: boolean
|
|
18
|
+
attributeError: Error | null
|
|
19
|
+
clearAttributes: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createIdentityAttributeHooks(
|
|
23
|
+
identityAttributes: IdentityAttributesManager
|
|
24
|
+
) {
|
|
25
|
+
const useIdentityAttributes = (): UseIdentityAttributesReturn => {
|
|
26
|
+
const [attributes, setAttributes] =
|
|
27
|
+
useState<IdentityAttributeResult | null>(null)
|
|
28
|
+
const [isRequestingAttributes, setIsRequestingAttributes] = useState(false)
|
|
29
|
+
const [attributeError, setAttributeError] = useState<Error | null>(null)
|
|
30
|
+
|
|
31
|
+
const requestAttributes = useCallback(
|
|
32
|
+
async (params: RequestIdentityAttributesParameters) => {
|
|
33
|
+
setIsRequestingAttributes(true)
|
|
34
|
+
setAttributeError(null)
|
|
35
|
+
try {
|
|
36
|
+
const result = await identityAttributes.request(params)
|
|
37
|
+
setAttributes(result)
|
|
38
|
+
return result
|
|
39
|
+
} catch (error) {
|
|
40
|
+
setAttributeError(error as Error)
|
|
41
|
+
throw error
|
|
42
|
+
} finally {
|
|
43
|
+
setIsRequestingAttributes(false)
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const requestOpenIdAttributes = useCallback(
|
|
50
|
+
async (params: RequestOpenIdIdentityAttributesParameters) => {
|
|
51
|
+
setIsRequestingAttributes(true)
|
|
52
|
+
setAttributeError(null)
|
|
53
|
+
try {
|
|
54
|
+
const result = await identityAttributes.requestOpenId(params)
|
|
55
|
+
setAttributes(result)
|
|
56
|
+
return result
|
|
57
|
+
} catch (error) {
|
|
58
|
+
setAttributeError(error as Error)
|
|
59
|
+
throw error
|
|
60
|
+
} finally {
|
|
61
|
+
setIsRequestingAttributes(false)
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
[]
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const clearAttributes = useCallback(() => {
|
|
68
|
+
setAttributes(null)
|
|
69
|
+
setAttributeError(null)
|
|
70
|
+
}, [])
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
requestAttributes,
|
|
74
|
+
requestOpenIdAttributes,
|
|
75
|
+
attributes,
|
|
76
|
+
isRequestingAttributes,
|
|
77
|
+
attributeError,
|
|
78
|
+
clearAttributes,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { useIdentityAttributes }
|
|
83
|
+
}
|