@ic-reactor/react 3.7.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 +93 -9
- package/dist/auth/authentication-manager.d.ts +37 -5
- package/dist/auth/authentication-manager.d.ts.map +1 -1
- package/dist/auth/authentication-manager.js +111 -37
- package/dist/auth/authentication-manager.js.map +1 -1
- package/dist/auth/createIdentityAttributeHooks.d.ts +2 -2
- package/dist/auth/createIdentityAttributeHooks.d.ts.map +1 -1
- package/dist/auth/identity-attributes-manager.d.ts +12 -5
- package/dist/auth/identity-attributes-manager.d.ts.map +1 -1
- package/dist/auth/identity-attributes-manager.js +42 -28
- package/dist/auth/identity-attributes-manager.js.map +1 -1
- package/dist/auth/identity-attributes.d.ts +1 -1
- package/dist/auth/identity-attributes.d.ts.map +1 -1
- package/dist/auth/identity-attributes.js +1 -1
- package/dist/auth/identity-attributes.js.map +1 -1
- package/dist/auth/index.d.ts +7 -7
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +7 -7
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/types.d.ts +123 -16
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/createActorHooks.d.ts +4 -4
- 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 +1 -1
- package/dist/createInfiniteQuery.d.ts.map +1 -1
- package/dist/createInfiniteQuery.js +1 -1
- package/dist/createInfiniteQuery.js.map +1 -1
- package/dist/createMutation.d.ts +1 -1
- package/dist/createMutation.d.ts.map +1 -1
- package/dist/createQuery.d.ts +1 -1
- package/dist/createQuery.d.ts.map +1 -1
- package/dist/createQuery.js +1 -1
- package/dist/createQuery.js.map +1 -1
- package/dist/createSuspenseInfiniteQuery.d.ts +1 -1
- package/dist/createSuspenseInfiniteQuery.d.ts.map +1 -1
- package/dist/createSuspenseInfiniteQuery.js +1 -1
- package/dist/createSuspenseInfiniteQuery.js.map +1 -1
- package/dist/createSuspenseQuery.d.ts +1 -1
- 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 +54 -4
- package/dist/defineReactor.d.ts.map +1 -1
- package/dist/defineReactor.js +77 -7
- package/dist/defineReactor.js.map +1 -1
- package/dist/hooks/createAuthHooks.d.ts +2 -2
- package/dist/hooks/createAuthHooks.d.ts.map +1 -1
- package/dist/hooks/createAuthHooks.js +7 -2
- package/dist/hooks/createAuthHooks.js.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.js +1 -1
- package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.js +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -11
- package/dist/index.js.map +1 -1
- package/llms.txt +1 -1
- package/package.json +14 -9
- package/src/auth/authentication-manager.ts +121 -49
- package/src/auth/createIdentityAttributeHooks.ts +2 -2
- package/src/auth/identity-attributes-manager.ts +53 -44
- package/src/auth/identity-attributes.ts +2 -2
- package/src/auth/index.ts +7 -7
- package/src/auth/types.ts +134 -18
- package/src/createActorHooks.ts +9 -9
- package/src/createInfiniteQuery.ts +2 -2
- package/src/createMutation.ts +1 -1
- package/src/createQuery.ts +2 -2
- package/src/createSuspenseInfiniteQuery.ts +2 -2
- package/src/createSuspenseQuery.ts +2 -2
- package/src/defineReactor.ts +136 -14
- package/src/hooks/createAuthHooks.ts +8 -3
- package/src/hooks/useActorInfiniteQuery.ts +1 -1
- package/src/hooks/useActorSuspenseInfiniteQuery.ts +1 -1
- package/src/index.ts +11 -11
- package/src/defineReactor.test.ts +0 -70
- 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/src/utils.test.ts +0 -14
package/src/auth/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Identity } from "@icp-sdk/core/agent"
|
|
1
|
+
import type { Identity, SignIdentity } from "@icp-sdk/core/agent"
|
|
2
|
+
import type { PartialIdentity } from "@icp-sdk/core/identity"
|
|
2
3
|
import type { Principal } from "@icp-sdk/core/principal"
|
|
3
4
|
|
|
4
5
|
export interface SignedIdentityAttributes {
|
|
@@ -6,9 +7,21 @@ export interface SignedIdentityAttributes {
|
|
|
6
7
|
signature: Uint8Array
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* The 32-byte nonce issued by the relying-party canister.
|
|
12
|
+
*
|
|
13
|
+
* A function (or promise) lets the Internet Identity window open while the
|
|
14
|
+
* nonce is still being fetched, so the popup is opened inside the user gesture
|
|
15
|
+
* instead of after it. `@icp-sdk/auth` v8 requires the function form; v7
|
|
16
|
+
* accepts the promise form. IC Reactor normalizes whichever you pass to the
|
|
17
|
+
* shape the installed client expects.
|
|
18
|
+
*/
|
|
19
|
+
export type IdentityAttributeNonce =
|
|
20
|
+
Uint8Array | Promise<Uint8Array> | (() => Uint8Array | Promise<Uint8Array>)
|
|
21
|
+
|
|
9
22
|
export interface IdentityAttributeRequest {
|
|
10
23
|
keys: string[]
|
|
11
|
-
nonce:
|
|
24
|
+
nonce: IdentityAttributeNonce
|
|
12
25
|
}
|
|
13
26
|
|
|
14
27
|
export interface IdentityAttributeValues {
|
|
@@ -26,15 +39,102 @@ export interface IdentityAttributeResult {
|
|
|
26
39
|
completedAt: string
|
|
27
40
|
}
|
|
28
41
|
|
|
42
|
+
/**
|
|
43
|
+
* The providers that Internet Identity supports for one-click sign-in. Only
|
|
44
|
+
* these three reach the underlying auth client.
|
|
45
|
+
*/
|
|
29
46
|
type IdentityAttributeOpenIdProviderAlias = "google" | "apple" | "microsoft"
|
|
30
47
|
|
|
48
|
+
/**
|
|
49
|
+
* An OpenID provider, either as a well-known alias or as a raw issuer URL.
|
|
50
|
+
*
|
|
51
|
+
* The two forms are **not** interchangeable, and what each one does depends on
|
|
52
|
+
* where it is passed:
|
|
53
|
+
*
|
|
54
|
+
* - An alias (`"google" | "apple" | "microsoft"`) is forwarded to the auth
|
|
55
|
+
* client, which adds an `openid` search param to the identity provider URL so
|
|
56
|
+
* the user signs in through that provider directly.
|
|
57
|
+
* - Any other string is treated as a raw issuer URL and is **never** forwarded
|
|
58
|
+
* to the auth client, so it never affects how the user signs in. It is only
|
|
59
|
+
* meaningful when passed to {@link IdentityAttributesManager.requestOpenId}
|
|
60
|
+
* (or `identityAttributeKeys`), which scopes the requested keys to
|
|
61
|
+
* `openid:<issuer>:<key>`. Passed anywhere else it has no effect at all.
|
|
62
|
+
*/
|
|
31
63
|
export type IdentityAttributeOpenIdProvider =
|
|
32
64
|
IdentityAttributeOpenIdProviderAlias | (string & {})
|
|
33
65
|
|
|
66
|
+
/** Session key algorithm used for each sign-in. */
|
|
67
|
+
export type AuthClientKeyType = "ECDSA" | "Ed25519"
|
|
68
|
+
|
|
69
|
+
/** Structural shape of an `@icp-sdk/auth` `AuthClientStorage`. */
|
|
70
|
+
export interface AuthClientStorageLike {
|
|
71
|
+
get(key: string): Promise<string | CryptoKeyPair | null>
|
|
72
|
+
set(key: string, value: string | CryptoKeyPair): Promise<void>
|
|
73
|
+
remove(key: string): Promise<void>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Idle-session configuration forwarded to the underlying AuthClient. */
|
|
77
|
+
export interface AuthClientIdleOptions {
|
|
78
|
+
/** Called once the user has been idle for `idleTimeout` ms. */
|
|
79
|
+
onIdle?: () => unknown
|
|
80
|
+
/** Idle timeout in ms. @default 600_000 (10 minutes) */
|
|
81
|
+
idleTimeout?: number
|
|
82
|
+
/** Treat scroll events as activity. @default false */
|
|
83
|
+
captureScroll?: boolean
|
|
84
|
+
/** Scroll debounce in ms. @default 100 */
|
|
85
|
+
scrollDebounce?: number
|
|
86
|
+
/** Disable idle detection entirely. @default false */
|
|
87
|
+
disableIdle?: boolean
|
|
88
|
+
/**
|
|
89
|
+
* Disable the built-in idle callback, which signs the user out and reloads
|
|
90
|
+
* the page. @default false
|
|
91
|
+
*/
|
|
92
|
+
disableDefaultIdleCallback?: boolean
|
|
93
|
+
}
|
|
94
|
+
|
|
34
95
|
export interface AuthenticationClientOptions {
|
|
96
|
+
/** Identity provider URL. @default "https://id.ai/authorize" */
|
|
35
97
|
identityProvider?: string | URL
|
|
98
|
+
/** `window.open` features string for the identity provider popup. */
|
|
36
99
|
windowOpenerFeatures?: string
|
|
100
|
+
/**
|
|
101
|
+
* Provider for one-click sign-in.
|
|
102
|
+
*
|
|
103
|
+
* Only the `"google" | "apple" | "microsoft"` aliases reach the auth client.
|
|
104
|
+
* Any other value is dropped here and has **no effect** — raw issuer URLs are
|
|
105
|
+
* only meaningful on `requestOpenId`, where they scope the requested keys.
|
|
106
|
+
* @see {@link IdentityAttributeOpenIdProvider}
|
|
107
|
+
*/
|
|
37
108
|
openIdProvider?: IdentityAttributeOpenIdProvider
|
|
109
|
+
/**
|
|
110
|
+
* Derivation origin for the identity provider.
|
|
111
|
+
*
|
|
112
|
+
* Required when the app is served from more than one origin (e.g. a custom
|
|
113
|
+
* domain plus `<canisterId>.icp0.io`) and every origin must resolve to the
|
|
114
|
+
* same principal.
|
|
115
|
+
* @see https://github.com/dfinity/internet-identity/blob/main/docs/internet-identity-spec.adoc
|
|
116
|
+
*/
|
|
117
|
+
derivationOrigin?: string | URL
|
|
118
|
+
/** Persistent storage backend. @default IndexedDB */
|
|
119
|
+
storage?: AuthClientStorageLike
|
|
120
|
+
/**
|
|
121
|
+
* Session key algorithm. Use `"Ed25519"` when the storage backend cannot
|
|
122
|
+
* hold a `CryptoKey`. @default "ECDSA"
|
|
123
|
+
*/
|
|
124
|
+
keyType?: AuthClientKeyType
|
|
125
|
+
/**
|
|
126
|
+
* Idle timeout configuration. Pass `{ disableIdle: true }` to opt out of the
|
|
127
|
+
* default behaviour, which signs the user out and reloads after 10 minutes.
|
|
128
|
+
*/
|
|
129
|
+
idleOptions?: AuthClientIdleOptions
|
|
130
|
+
/** An existing identity to authenticate via delegation. */
|
|
131
|
+
identity?: SignIdentity | PartialIdentity
|
|
132
|
+
/**
|
|
133
|
+
* How the client talks to the identity provider. `"redirect"` requires
|
|
134
|
+
* `@icp-sdk/auth` v8 or later and is ignored by older versions.
|
|
135
|
+
* @default "window"
|
|
136
|
+
*/
|
|
137
|
+
transport?: "window" | "redirect"
|
|
38
138
|
}
|
|
39
139
|
|
|
40
140
|
export interface AuthClientSignInOptions {
|
|
@@ -48,36 +148,52 @@ export interface AuthenticationSignInOptions
|
|
|
48
148
|
onError?: (error?: string) => void | Promise<void>
|
|
49
149
|
}
|
|
50
150
|
|
|
51
|
-
export interface RequestIdentityAttributesParameters {
|
|
151
|
+
export interface RequestIdentityAttributesParameters extends AuthenticationClientOptions {
|
|
52
152
|
keys: string[]
|
|
53
|
-
nonce:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
153
|
+
nonce: IdentityAttributeNonce
|
|
154
|
+
/**
|
|
155
|
+
* Sign in as part of the same identity-provider interaction.
|
|
156
|
+
* @default true
|
|
157
|
+
*/
|
|
57
158
|
signIn?: boolean
|
|
58
159
|
maxTimeToLive?: bigint
|
|
59
160
|
targets?: Principal[]
|
|
60
161
|
}
|
|
61
162
|
|
|
62
|
-
export interface RequestOpenIdIdentityAttributesParameters
|
|
63
|
-
|
|
163
|
+
export interface RequestOpenIdIdentityAttributesParameters extends Omit<
|
|
164
|
+
RequestIdentityAttributesParameters,
|
|
165
|
+
"openIdProvider"
|
|
166
|
+
> {
|
|
167
|
+
/**
|
|
168
|
+
* Provider the requested `keys` are scoped to, producing
|
|
169
|
+
* `openid:<issuer>:<key>`.
|
|
170
|
+
*
|
|
171
|
+
* Passing one of the `"google" | "apple" | "microsoft"` aliases additionally
|
|
172
|
+
* routes sign-in through that provider, so the user grants attribute access
|
|
173
|
+
* in the same step. A raw issuer URL only scopes the keys.
|
|
174
|
+
* @see {@link IdentityAttributeOpenIdProvider}
|
|
175
|
+
*/
|
|
64
176
|
openIdProvider: IdentityAttributeOpenIdProvider
|
|
65
|
-
keys: string[]
|
|
66
|
-
identityProvider?: string | URL
|
|
67
|
-
windowOpenerFeatures?: string
|
|
68
|
-
signIn?: boolean
|
|
69
|
-
maxTimeToLive?: bigint
|
|
70
|
-
targets?: Principal[]
|
|
71
177
|
}
|
|
72
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Structural subset of `@icp-sdk/auth`'s `AuthClient` that IC Reactor relies
|
|
181
|
+
* on. Declared locally so `@icp-sdk/auth` stays an optional peer dependency.
|
|
182
|
+
*/
|
|
73
183
|
export interface AuthClientLike {
|
|
74
184
|
getIdentity(): Promise<Identity> | Identity
|
|
75
185
|
isAuthenticated(): Promise<boolean> | boolean
|
|
76
186
|
signIn(options?: AuthClientSignInOptions): Promise<Identity>
|
|
77
187
|
signOut(options?: { returnTo?: string }): Promise<void>
|
|
78
|
-
requestAttributes(
|
|
79
|
-
|
|
80
|
-
|
|
188
|
+
requestAttributes(params: {
|
|
189
|
+
keys: string[]
|
|
190
|
+
// Widened across `@icp-sdk/auth` v7 (value or promise) and v8 (thunk).
|
|
191
|
+
// Method-shorthand parameters are bivariant, so both real signatures are
|
|
192
|
+
// assignable to this one.
|
|
193
|
+
nonce: Uint8Array | Promise<Uint8Array> | (() => Promise<Uint8Array>)
|
|
194
|
+
}): Promise<SignedIdentityAttributes>
|
|
195
|
+
/** Present from `@icp-sdk/auth` v8; used to detect the nonce contract. */
|
|
196
|
+
memoize?<T>(produce: () => T | Promise<T>): Promise<T>
|
|
81
197
|
}
|
|
82
198
|
|
|
83
199
|
export interface AuthState {
|
package/src/createActorHooks.ts
CHANGED
|
@@ -34,18 +34,18 @@ import {
|
|
|
34
34
|
UseMutationResult,
|
|
35
35
|
InfiniteData,
|
|
36
36
|
} from "@tanstack/react-query"
|
|
37
|
-
import { useActorQuery } from "./hooks/useActorQuery"
|
|
38
|
-
import { useActorSuspenseQuery } from "./hooks/useActorSuspenseQuery"
|
|
39
|
-
import { useActorInfiniteQuery } from "./hooks/useActorInfiniteQuery"
|
|
40
|
-
import { useActorSuspenseInfiniteQuery } from "./hooks/useActorSuspenseInfiniteQuery"
|
|
41
|
-
import { useActorMutation } from "./hooks/useActorMutation"
|
|
37
|
+
import { useActorQuery } from "./hooks/useActorQuery.js"
|
|
38
|
+
import { useActorSuspenseQuery } from "./hooks/useActorSuspenseQuery.js"
|
|
39
|
+
import { useActorInfiniteQuery } from "./hooks/useActorInfiniteQuery.js"
|
|
40
|
+
import { useActorSuspenseInfiniteQuery } from "./hooks/useActorSuspenseInfiniteQuery.js"
|
|
41
|
+
import { useActorMutation } from "./hooks/useActorMutation.js"
|
|
42
42
|
import {
|
|
43
43
|
useActorMethod,
|
|
44
44
|
UseActorMethodParameters,
|
|
45
|
-
} from "./hooks/useActorMethod"
|
|
46
|
-
import { InfiniteQueryConfig } from "./createInfiniteQuery"
|
|
47
|
-
import { SuspenseInfiniteQueryConfig } from "./createSuspenseInfiniteQuery"
|
|
48
|
-
import { QueryConfig, SuspenseQueryConfig, MutationConfig } from "./types"
|
|
45
|
+
} from "./hooks/useActorMethod.js"
|
|
46
|
+
import { InfiniteQueryConfig } from "./createInfiniteQuery.js"
|
|
47
|
+
import { SuspenseInfiniteQueryConfig } from "./createSuspenseInfiniteQuery.js"
|
|
48
|
+
import { QueryConfig, SuspenseQueryConfig, MutationConfig } from "./types.js"
|
|
49
49
|
|
|
50
50
|
export type ActorHooks<Service, Transform extends TransformKey> = {
|
|
51
51
|
useActorQuery: {
|
|
@@ -43,8 +43,8 @@ import {
|
|
|
43
43
|
InfiniteQueryObserverOptions,
|
|
44
44
|
} from "@tanstack/react-query"
|
|
45
45
|
import { CallConfig } from "@icp-sdk/core/agent"
|
|
46
|
-
import { NoInfer } from "./types"
|
|
47
|
-
import { mergeFactoryQueryKey, normalizeQueryData } from "./utils"
|
|
46
|
+
import { NoInfer } from "./types.js"
|
|
47
|
+
import { mergeFactoryQueryKey, normalizeQueryData } from "./utils.js"
|
|
48
48
|
|
|
49
49
|
type InfiniteQueryFactoryFn<
|
|
50
50
|
Service,
|
package/src/createMutation.ts
CHANGED
package/src/createQuery.ts
CHANGED
|
@@ -44,8 +44,8 @@ import type {
|
|
|
44
44
|
QueryResult,
|
|
45
45
|
QueryFactoryConfig,
|
|
46
46
|
NoInfer,
|
|
47
|
-
} from "./types"
|
|
48
|
-
import { buildChainedSelect } from "./utils"
|
|
47
|
+
} from "./types.js"
|
|
48
|
+
import { buildChainedSelect } from "./utils.js"
|
|
49
49
|
|
|
50
50
|
// ============================================================================
|
|
51
51
|
// Internal Implementation
|
|
@@ -45,8 +45,8 @@ import {
|
|
|
45
45
|
InfiniteQueryObserverOptions,
|
|
46
46
|
} from "@tanstack/react-query"
|
|
47
47
|
import { CallConfig } from "@icp-sdk/core/agent"
|
|
48
|
-
import { NoInfer } from "./types"
|
|
49
|
-
import { mergeFactoryQueryKey, normalizeQueryData } from "./utils"
|
|
48
|
+
import { NoInfer } from "./types.js"
|
|
49
|
+
import { mergeFactoryQueryKey, normalizeQueryData } from "./utils.js"
|
|
50
50
|
|
|
51
51
|
type SuspenseInfiniteFactoryCallOptions = {
|
|
52
52
|
queryKey?: QueryKey
|
|
@@ -34,8 +34,8 @@ import type {
|
|
|
34
34
|
SuspenseQueryResult,
|
|
35
35
|
SuspenseQueryFactoryConfig,
|
|
36
36
|
NoInfer,
|
|
37
|
-
} from "./types"
|
|
38
|
-
import { buildChainedSelect } from "./utils"
|
|
37
|
+
} from "./types.js"
|
|
38
|
+
import { buildChainedSelect } from "./utils.js"
|
|
39
39
|
|
|
40
40
|
// ============================================================================
|
|
41
41
|
// Internal Implementation
|
package/src/defineReactor.ts
CHANGED
|
@@ -33,8 +33,26 @@
|
|
|
33
33
|
* name: "index",
|
|
34
34
|
* idlFactory: indexIdl,
|
|
35
35
|
* clientManager: ledger.clientManager,
|
|
36
|
+
* authentication: ledger.authentication, // one Internet Identity session
|
|
36
37
|
* })
|
|
37
38
|
* ```
|
|
39
|
+
*
|
|
40
|
+
* @example Internet Identity is wired up out of the box
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const { useAuth, useIdentityAttributes } = defineReactor<_SERVICE>({
|
|
43
|
+
* name: "backend",
|
|
44
|
+
* idlFactory,
|
|
45
|
+
* // Needed when the app is served from more than one origin.
|
|
46
|
+
* auth: { derivationOrigin: "https://app.example.com" },
|
|
47
|
+
* })
|
|
48
|
+
*
|
|
49
|
+
* function LoginButton() {
|
|
50
|
+
* const { login, logout, isAuthenticated, principal } = useAuth()
|
|
51
|
+
* return isAuthenticated
|
|
52
|
+
* ? <button onClick={() => logout()}>{principal?.toText()}</button>
|
|
53
|
+
* : <button onClick={() => login()}>Sign in</button>
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
38
56
|
*/
|
|
39
57
|
import { ClientManager, Reactor, DisplayReactor } from "@ic-reactor/core"
|
|
40
58
|
import type {
|
|
@@ -45,7 +63,14 @@ import type {
|
|
|
45
63
|
DisplayReactorParameters,
|
|
46
64
|
} from "@ic-reactor/core"
|
|
47
65
|
import { QueryClient } from "@tanstack/react-query"
|
|
48
|
-
import { createActorHooks, ActorHooks } from "./createActorHooks"
|
|
66
|
+
import { createActorHooks, ActorHooks } from "./createActorHooks.js"
|
|
67
|
+
import { AuthenticationManager } from "./auth/authentication-manager.js"
|
|
68
|
+
import { IdentityAttributesManager } from "./auth/identity-attributes-manager.js"
|
|
69
|
+
import { createIdentityAttributeHooks } from "./auth/createIdentityAttributeHooks.js"
|
|
70
|
+
import type { UseIdentityAttributesReturn } from "./auth/createIdentityAttributeHooks.js"
|
|
71
|
+
import { createAuthHooks } from "./hooks/createAuthHooks.js"
|
|
72
|
+
import type { CreateAuthHooksReturn } from "./hooks/createAuthHooks.js"
|
|
73
|
+
import type { AuthenticationManagerParameters } from "./auth/authentication-manager.js"
|
|
49
74
|
|
|
50
75
|
/** Options shared by both the standard and display variants of defineReactor. */
|
|
51
76
|
export interface DefineReactorSharedParameters
|
|
@@ -55,13 +80,35 @@ export interface DefineReactorSharedParameters
|
|
|
55
80
|
/**
|
|
56
81
|
* Reuse an existing ClientManager (e.g. to share one agent across canisters).
|
|
57
82
|
* When omitted, a ClientManager is created from the agent options below.
|
|
83
|
+
*
|
|
84
|
+
* A supplied or adopted manager brings its own agent and QueryClient, so
|
|
85
|
+
* `agentOptions` and `queryClient` apply only when this call creates one.
|
|
58
86
|
*/
|
|
59
87
|
clientManager?: ClientManager
|
|
60
88
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
89
|
+
* QueryClient for a ClientManager created by this call.
|
|
90
|
+
*
|
|
91
|
+
* Ignored when `clientManager` or `authentication` is supplied — queries run
|
|
92
|
+
* against that manager's own QueryClient, which is what is returned.
|
|
63
93
|
*/
|
|
64
94
|
queryClient?: QueryClient
|
|
95
|
+
/**
|
|
96
|
+
* Reuse an existing AuthenticationManager, so several reactors share one
|
|
97
|
+
* Internet Identity session. When omitted, one is created for this reactor.
|
|
98
|
+
*
|
|
99
|
+
* Its `clientManager` is adopted for this reactor, so sign-in updates the
|
|
100
|
+
* same agent the reactor calls through. Supplying a different `clientManager`
|
|
101
|
+
* alongside it is rejected.
|
|
102
|
+
*/
|
|
103
|
+
authentication?: AuthenticationManager
|
|
104
|
+
/**
|
|
105
|
+
* Internet Identity options forwarded to the AuthenticationManager
|
|
106
|
+
* (`identityProvider`, `derivationOrigin`, `idleOptions`, `storage`, …).
|
|
107
|
+
*
|
|
108
|
+
* Mutually exclusive with `authentication`: a manager built elsewhere is
|
|
109
|
+
* already configured, so these could not be applied to it.
|
|
110
|
+
*/
|
|
111
|
+
auth?: Omit<AuthenticationManagerParameters, "clientManager">
|
|
65
112
|
}
|
|
66
113
|
|
|
67
114
|
/** Parameters for a standard (raw Candid types) reactor. */
|
|
@@ -83,11 +130,17 @@ export type DefineReactorResult<
|
|
|
83
130
|
Service,
|
|
84
131
|
Transform extends TransformKey,
|
|
85
132
|
R extends Reactor<Service, Transform>,
|
|
86
|
-
> = ActorHooks<Service, Transform> &
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
133
|
+
> = ActorHooks<Service, Transform> &
|
|
134
|
+
CreateAuthHooksReturn & {
|
|
135
|
+
reactor: R
|
|
136
|
+
clientManager: ClientManager
|
|
137
|
+
queryClient: QueryClient
|
|
138
|
+
/** Internet Identity session manager backing `useAuth`. */
|
|
139
|
+
authentication: AuthenticationManager
|
|
140
|
+
/** Signed identity attribute requests backing `useIdentityAttributes`. */
|
|
141
|
+
identityAttributes: IdentityAttributesManager
|
|
142
|
+
useIdentityAttributes: () => UseIdentityAttributesReturn
|
|
143
|
+
}
|
|
91
144
|
|
|
92
145
|
export function defineReactor<Service = BaseActor>(
|
|
93
146
|
params: DefineDisplayReactorParameters<Service>
|
|
@@ -104,6 +157,8 @@ export function defineReactor<Service = BaseActor>(
|
|
|
104
157
|
const {
|
|
105
158
|
clientManager: providedClientManager,
|
|
106
159
|
queryClient: providedQueryClient,
|
|
160
|
+
authentication: providedAuthentication,
|
|
161
|
+
auth,
|
|
107
162
|
display,
|
|
108
163
|
agentOptions,
|
|
109
164
|
name,
|
|
@@ -112,18 +167,44 @@ export function defineReactor<Service = BaseActor>(
|
|
|
112
167
|
pollingOptions,
|
|
113
168
|
} = params as DefineDisplayReactorParameters<Service>
|
|
114
169
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
170
|
+
// A shared AuthenticationManager updates the identity on its own
|
|
171
|
+
// ClientManager. Giving this reactor a different one would leave its calls
|
|
172
|
+
// anonymous after sign-in, so adopt the manager's rather than building a new
|
|
173
|
+
// one, and refuse an explicit mismatch instead of splitting them silently.
|
|
174
|
+
if (
|
|
175
|
+
providedClientManager &&
|
|
176
|
+
providedAuthentication &&
|
|
177
|
+
providedAuthentication.clientManager !== providedClientManager
|
|
178
|
+
) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`[ic-reactor] defineReactor("${name}") received an \`authentication\` manager bound to a different \`clientManager\`. ` +
|
|
181
|
+
`Sign-in would update the authentication manager's agent while this reactor calls through another one, ` +
|
|
182
|
+
`leaving its calls anonymous. Pass \`clientManager: authentication.clientManager\`, or omit \`clientManager\` to adopt it.`
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// `auth` configures a manager this call would build; an existing one is
|
|
187
|
+
// already constructed, so these options could only be dropped on the floor.
|
|
188
|
+
if (providedAuthentication && auth) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`[ic-reactor] defineReactor("${name}") received both \`authentication\` and \`auth\`. ` +
|
|
191
|
+
`The supplied manager is already configured, so \`auth\` (${Object.keys(auth).join(", ")}) would be ignored. ` +
|
|
192
|
+
`Pass those options where that AuthenticationManager is created, or drop \`authentication\` to build one here.`
|
|
193
|
+
)
|
|
194
|
+
}
|
|
119
195
|
|
|
120
196
|
const clientManager =
|
|
121
197
|
providedClientManager ??
|
|
198
|
+
providedAuthentication?.clientManager ??
|
|
122
199
|
new ClientManager({
|
|
123
|
-
queryClient,
|
|
200
|
+
queryClient: providedQueryClient ?? new QueryClient(),
|
|
124
201
|
agentOptions,
|
|
125
202
|
})
|
|
126
203
|
|
|
204
|
+
// Always report the QueryClient actually in use: when a ClientManager is
|
|
205
|
+
// supplied or adopted, its own QueryClient is the one queries run against.
|
|
206
|
+
const queryClient = clientManager.queryClient
|
|
207
|
+
|
|
127
208
|
const reactorConfig = {
|
|
128
209
|
clientManager,
|
|
129
210
|
name,
|
|
@@ -146,5 +227,46 @@ export function defineReactor<Service = BaseActor>(
|
|
|
146
227
|
|
|
147
228
|
const hooks = createActorHooks(reactor)
|
|
148
229
|
|
|
149
|
-
|
|
230
|
+
// Auth is built on first use. `AuthenticationManager` dynamically imports the
|
|
231
|
+
// optional `@icp-sdk/auth` peer as soon as it is constructed, and reactors
|
|
232
|
+
// that never touch authentication should not pay for that.
|
|
233
|
+
let authenticationInstance: AuthenticationManager | undefined
|
|
234
|
+
const getAuthentication = () =>
|
|
235
|
+
(authenticationInstance ??=
|
|
236
|
+
providedAuthentication ??
|
|
237
|
+
new AuthenticationManager({ ...auth, clientManager }))
|
|
238
|
+
|
|
239
|
+
let identityAttributesInstance: IdentityAttributesManager | undefined
|
|
240
|
+
const getIdentityAttributes = () =>
|
|
241
|
+
(identityAttributesInstance ??= new IdentityAttributesManager(
|
|
242
|
+
getAuthentication()
|
|
243
|
+
))
|
|
244
|
+
|
|
245
|
+
let authHooks: CreateAuthHooksReturn | undefined
|
|
246
|
+
const getAuthHooks = () =>
|
|
247
|
+
(authHooks ??= createAuthHooks(getAuthentication()))
|
|
248
|
+
|
|
249
|
+
let attributeHooks:
|
|
250
|
+
ReturnType<typeof createIdentityAttributeHooks> | undefined
|
|
251
|
+
const getAttributeHooks = () =>
|
|
252
|
+
(attributeHooks ??= createIdentityAttributeHooks(getIdentityAttributes()))
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
...hooks,
|
|
256
|
+
reactor,
|
|
257
|
+
clientManager,
|
|
258
|
+
queryClient,
|
|
259
|
+
// Stable wrappers: the hook call order inside them never changes, so the
|
|
260
|
+
// rules of hooks still hold.
|
|
261
|
+
useAuth: () => getAuthHooks().useAuth(),
|
|
262
|
+
useAgentState: () => getAuthHooks().useAgentState(),
|
|
263
|
+
useUserPrincipal: () => getAuthHooks().useUserPrincipal(),
|
|
264
|
+
useIdentityAttributes: () => getAttributeHooks().useIdentityAttributes(),
|
|
265
|
+
get authentication() {
|
|
266
|
+
return getAuthentication()
|
|
267
|
+
},
|
|
268
|
+
get identityAttributes() {
|
|
269
|
+
return getIdentityAttributes()
|
|
270
|
+
},
|
|
271
|
+
}
|
|
150
272
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useSyncExternalStore, useEffect, useRef, useMemo } from "react"
|
|
2
|
-
import type { AuthenticationManager } from "../auth/authentication-manager"
|
|
3
|
-
import type { AuthState, AuthenticationSignInOptions } from "../auth/types"
|
|
2
|
+
import type { AuthenticationManager } from "../auth/authentication-manager.js"
|
|
3
|
+
import type { AuthState, AuthenticationSignInOptions } from "../auth/types.js"
|
|
4
4
|
import type { AgentState } from "@ic-reactor/core"
|
|
5
5
|
import type { Principal } from "@icp-sdk/core/principal"
|
|
6
6
|
import type { Identity } from "@icp-sdk/core/agent"
|
|
@@ -90,7 +90,9 @@ export const createAuthHooks = (
|
|
|
90
90
|
// Track if we've already initialized to avoid duplicate calls
|
|
91
91
|
const initializedRef = useRef(false)
|
|
92
92
|
|
|
93
|
-
// Auto-initialize on first mount to restore previous session
|
|
93
|
+
// Auto-initialize on first mount to restore previous session.
|
|
94
|
+
// `prepareClient` also warms up the AuthClient so a later `login()` can
|
|
95
|
+
// open the identity provider window inside the click handler.
|
|
94
96
|
useEffect(() => {
|
|
95
97
|
if (!initializedRef.current) {
|
|
96
98
|
initializedRef.current = true
|
|
@@ -99,6 +101,9 @@ export const createAuthHooks = (
|
|
|
99
101
|
.catch(() => undefined)
|
|
100
102
|
.then(() => clientManager.initialize())
|
|
101
103
|
.then(() => authentication.authenticate())
|
|
104
|
+
// Failures are already reflected in authState/agentState; without
|
|
105
|
+
// this the rejection escapes as an unhandled promise rejection.
|
|
106
|
+
.catch(() => undefined)
|
|
102
107
|
}
|
|
103
108
|
}, [])
|
|
104
109
|
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
ReactorReturnErr,
|
|
17
17
|
} from "@ic-reactor/core"
|
|
18
18
|
import { CallConfig } from "@icp-sdk/core/agent"
|
|
19
|
-
import { normalizeQueryData } from "../utils"
|
|
19
|
+
import { normalizeQueryData } from "../utils.js"
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Parameters for useActorInfiniteQuery hook.
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
ReactorReturnErr,
|
|
17
17
|
} from "@ic-reactor/core"
|
|
18
18
|
import { CallConfig } from "@icp-sdk/core/agent"
|
|
19
|
-
import { normalizeQueryData } from "../utils"
|
|
19
|
+
import { normalizeQueryData } from "../utils.js"
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Parameters for useActorSuspenseInfiniteQuery hook.
|
package/src/index.ts
CHANGED
|
@@ -3,21 +3,21 @@
|
|
|
3
3
|
export * from "@ic-reactor/core"
|
|
4
4
|
|
|
5
5
|
// Re-export hooks
|
|
6
|
-
export * from "./hooks"
|
|
6
|
+
export * from "./hooks/index.js"
|
|
7
7
|
|
|
8
8
|
// Validation utilities for React
|
|
9
|
-
export * from "./validation"
|
|
9
|
+
export * from "./validation.js"
|
|
10
10
|
|
|
11
11
|
// React-specific exports
|
|
12
|
-
export * from "./createActorHooks"
|
|
13
|
-
export * from "./defineReactor"
|
|
12
|
+
export * from "./createActorHooks.js"
|
|
13
|
+
export * from "./defineReactor.js"
|
|
14
14
|
|
|
15
|
-
export * from "./createQuery"
|
|
16
|
-
export * from "./createSuspenseQuery"
|
|
17
|
-
export * from "./createInfiniteQuery"
|
|
18
|
-
export * from "./createSuspenseInfiniteQuery"
|
|
19
|
-
export * from "./createMutation"
|
|
15
|
+
export * from "./createQuery.js"
|
|
16
|
+
export * from "./createSuspenseQuery.js"
|
|
17
|
+
export * from "./createInfiniteQuery.js"
|
|
18
|
+
export * from "./createSuspenseInfiniteQuery.js"
|
|
19
|
+
export * from "./createMutation.js"
|
|
20
20
|
|
|
21
|
-
export * from "./auth"
|
|
21
|
+
export * from "./auth/index.js"
|
|
22
22
|
|
|
23
|
-
export * from "./types"
|
|
23
|
+
export * from "./types.js"
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest"
|
|
2
|
-
import { ClientManager, Reactor, DisplayReactor } from "@ic-reactor/core"
|
|
3
|
-
import { QueryClient } from "@tanstack/react-query"
|
|
4
|
-
import { defineReactor } from "./defineReactor"
|
|
5
|
-
import { ActorMethod } from "@icp-sdk/core/agent"
|
|
6
|
-
|
|
7
|
-
const idlFactory = ({ IDL }: any) =>
|
|
8
|
-
IDL.Service({
|
|
9
|
-
greet: IDL.Func([IDL.Text], [IDL.Text], ["query"]),
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
interface TestActor {
|
|
13
|
-
greet: ActorMethod<[string], string>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const baseConfig = {
|
|
17
|
-
name: "test",
|
|
18
|
-
idlFactory,
|
|
19
|
-
canisterId: "rrkah-fqaaa-aaaaa-aaaaq-cai",
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe("defineReactor", () => {
|
|
23
|
-
it("bootstraps a standard Reactor with hooks and infrastructure", () => {
|
|
24
|
-
const result = defineReactor<TestActor>(baseConfig)
|
|
25
|
-
|
|
26
|
-
expect(result.reactor).toBeInstanceOf(Reactor)
|
|
27
|
-
expect(result.reactor).not.toBeInstanceOf(DisplayReactor)
|
|
28
|
-
expect(result.clientManager).toBeInstanceOf(ClientManager)
|
|
29
|
-
expect(result.queryClient).toBeInstanceOf(QueryClient)
|
|
30
|
-
expect(typeof result.useActorQuery).toBe("function")
|
|
31
|
-
expect(typeof result.useActorMutation).toBe("function")
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it("creates a DisplayReactor when display is true", () => {
|
|
35
|
-
const result = defineReactor<TestActor>({ ...baseConfig, display: true })
|
|
36
|
-
|
|
37
|
-
expect(result.reactor).toBeInstanceOf(DisplayReactor)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it("wires the reactor to the created ClientManager and QueryClient", () => {
|
|
41
|
-
const { reactor, clientManager, queryClient } =
|
|
42
|
-
defineReactor<TestActor>(baseConfig)
|
|
43
|
-
|
|
44
|
-
expect(reactor.clientManager).toBe(clientManager)
|
|
45
|
-
expect(clientManager.queryClient).toBe(queryClient)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it("reuses an existing ClientManager and its QueryClient", () => {
|
|
49
|
-
const queryClient = new QueryClient()
|
|
50
|
-
const clientManager = new ClientManager({ queryClient })
|
|
51
|
-
|
|
52
|
-
const result = defineReactor<TestActor>({ ...baseConfig, clientManager })
|
|
53
|
-
|
|
54
|
-
expect(result.clientManager).toBe(clientManager)
|
|
55
|
-
expect(result.queryClient).toBe(queryClient)
|
|
56
|
-
expect(result.reactor.clientManager).toBe(clientManager)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it("lets multiple reactors share one ClientManager", () => {
|
|
60
|
-
const ledger = defineReactor<TestActor>({ ...baseConfig, name: "ledger" })
|
|
61
|
-
const index = defineReactor<TestActor>({
|
|
62
|
-
...baseConfig,
|
|
63
|
-
name: "index",
|
|
64
|
-
clientManager: ledger.clientManager,
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
expect(index.clientManager).toBe(ledger.clientManager)
|
|
68
|
-
expect(index.reactor).not.toBe(ledger.reactor)
|
|
69
|
-
})
|
|
70
|
-
})
|