@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.
Files changed (131) hide show
  1. package/README.md +117 -13
  2. package/dist/auth/authentication-manager.d.ts +98 -0
  3. package/dist/auth/authentication-manager.d.ts.map +1 -0
  4. package/dist/auth/authentication-manager.js +532 -0
  5. package/dist/auth/authentication-manager.js.map +1 -0
  6. package/dist/auth/constants.d.ts +18 -0
  7. package/dist/auth/constants.d.ts.map +1 -0
  8. package/dist/auth/constants.js +18 -0
  9. package/dist/auth/constants.js.map +1 -0
  10. package/dist/auth/createIdentityAttributeHooks.d.ts +14 -0
  11. package/dist/auth/createIdentityAttributeHooks.d.ts.map +1 -0
  12. package/dist/auth/createIdentityAttributeHooks.js +54 -0
  13. package/dist/auth/createIdentityAttributeHooks.js.map +1 -0
  14. package/dist/auth/identity-attributes-manager.d.ts +26 -0
  15. package/dist/auth/identity-attributes-manager.d.ts.map +1 -0
  16. package/dist/auth/identity-attributes-manager.js +108 -0
  17. package/dist/auth/identity-attributes-manager.js.map +1 -0
  18. package/dist/auth/identity-attributes.d.ts +19 -0
  19. package/dist/auth/identity-attributes.d.ts.map +1 -0
  20. package/dist/auth/identity-attributes.js +170 -0
  21. package/dist/auth/identity-attributes.js.map +1 -0
  22. package/dist/auth/index.d.ts +8 -0
  23. package/dist/auth/index.d.ts.map +1 -0
  24. package/dist/auth/index.js +8 -0
  25. package/dist/auth/index.js.map +1 -0
  26. package/dist/auth/types.d.ts +182 -0
  27. package/dist/auth/types.d.ts.map +1 -0
  28. package/dist/auth/types.js +2 -0
  29. package/dist/auth/types.js.map +1 -0
  30. package/dist/createActorHooks.d.ts +16 -16
  31. package/dist/createActorHooks.d.ts.map +1 -1
  32. package/dist/createActorHooks.js +6 -6
  33. package/dist/createActorHooks.js.map +1 -1
  34. package/dist/createInfiniteQuery.d.ts +33 -33
  35. package/dist/createInfiniteQuery.d.ts.map +1 -1
  36. package/dist/createInfiniteQuery.js +6 -6
  37. package/dist/createInfiniteQuery.js.map +1 -1
  38. package/dist/createMutation.d.ts +13 -2
  39. package/dist/createMutation.d.ts.map +1 -1
  40. package/dist/createMutation.js +11 -0
  41. package/dist/createMutation.js.map +1 -1
  42. package/dist/createQuery.d.ts +13 -3
  43. package/dist/createQuery.d.ts.map +1 -1
  44. package/dist/createQuery.js +16 -6
  45. package/dist/createQuery.js.map +1 -1
  46. package/dist/createSuspenseInfiniteQuery.d.ts +32 -32
  47. package/dist/createSuspenseInfiniteQuery.d.ts.map +1 -1
  48. package/dist/createSuspenseInfiniteQuery.js +6 -6
  49. package/dist/createSuspenseInfiniteQuery.js.map +1 -1
  50. package/dist/createSuspenseQuery.d.ts +3 -3
  51. package/dist/createSuspenseQuery.d.ts.map +1 -1
  52. package/dist/createSuspenseQuery.js +1 -1
  53. package/dist/createSuspenseQuery.js.map +1 -1
  54. package/dist/defineReactor.d.ts +124 -0
  55. package/dist/defineReactor.d.ts.map +1 -0
  56. package/dist/defineReactor.js +145 -0
  57. package/dist/defineReactor.js.map +1 -0
  58. package/dist/{createAuthHooks.d.ts → hooks/createAuthHooks.d.ts} +6 -13
  59. package/dist/hooks/createAuthHooks.d.ts.map +1 -0
  60. package/dist/{createAuthHooks.js → hooks/createAuthHooks.js} +18 -58
  61. package/dist/hooks/createAuthHooks.js.map +1 -0
  62. package/dist/hooks/index.d.ts +2 -0
  63. package/dist/hooks/index.d.ts.map +1 -1
  64. package/dist/hooks/index.js +2 -0
  65. package/dist/hooks/index.js.map +1 -1
  66. package/dist/hooks/useActorInfiniteQuery.d.ts +9 -9
  67. package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
  68. package/dist/hooks/useActorInfiniteQuery.js +3 -1
  69. package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
  70. package/dist/hooks/useActorMethod.d.ts +17 -17
  71. package/dist/hooks/useActorMethod.d.ts.map +1 -1
  72. package/dist/hooks/useActorMethod.js.map +1 -1
  73. package/dist/hooks/useActorMutation.d.ts +18 -7
  74. package/dist/hooks/useActorMutation.d.ts.map +1 -1
  75. package/dist/hooks/useActorMutation.js +11 -0
  76. package/dist/hooks/useActorMutation.js.map +1 -1
  77. package/dist/hooks/useActorQuery.d.ts +8 -8
  78. package/dist/hooks/useActorQuery.d.ts.map +1 -1
  79. package/dist/hooks/useActorQuery.js.map +1 -1
  80. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +9 -9
  81. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
  82. package/dist/hooks/useActorSuspenseInfiniteQuery.js +3 -1
  83. package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
  84. package/dist/hooks/useActorSuspenseQuery.d.ts +8 -8
  85. package/dist/hooks/useActorSuspenseQuery.d.ts.map +1 -1
  86. package/dist/hooks/useActorSuspenseQuery.js.map +1 -1
  87. package/dist/index.d.ts +11 -10
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +11 -10
  90. package/dist/index.js.map +1 -1
  91. package/dist/types.d.ts +24 -24
  92. package/dist/types.d.ts.map +1 -1
  93. package/dist/utils.d.ts +3 -0
  94. package/dist/utils.d.ts.map +1 -1
  95. package/dist/utils.js +10 -2
  96. package/dist/utils.js.map +1 -1
  97. package/llms.txt +48 -0
  98. package/package.json +34 -23
  99. package/src/auth/authentication-manager.ts +591 -0
  100. package/src/auth/constants.ts +25 -0
  101. package/src/auth/createIdentityAttributeHooks.ts +83 -0
  102. package/src/auth/identity-attributes-manager.ts +136 -0
  103. package/src/auth/identity-attributes.ts +270 -0
  104. package/src/auth/index.ts +7 -0
  105. package/src/auth/types.ts +204 -0
  106. package/src/createActorHooks.ts +90 -58
  107. package/src/createInfiniteQuery.ts +154 -98
  108. package/src/createMutation.ts +35 -19
  109. package/src/createQuery.ts +94 -50
  110. package/src/createSuspenseInfiniteQuery.ts +166 -115
  111. package/src/createSuspenseQuery.ts +69 -43
  112. package/src/defineReactor.ts +272 -0
  113. package/src/{createAuthHooks.ts → hooks/createAuthHooks.ts} +23 -99
  114. package/src/hooks/index.ts +4 -0
  115. package/src/hooks/useActorInfiniteQuery.ts +44 -30
  116. package/src/hooks/useActorMethod.ts +62 -53
  117. package/src/hooks/useActorMutation.ts +47 -30
  118. package/src/hooks/useActorQuery.ts +36 -32
  119. package/src/hooks/useActorSuspenseInfiniteQuery.ts +56 -30
  120. package/src/hooks/useActorSuspenseQuery.ts +42 -32
  121. package/src/index.ts +12 -10
  122. package/src/types.ts +76 -71
  123. package/src/utils.ts +15 -2
  124. package/dist/createAuthHooks.d.ts.map +0 -1
  125. package/dist/createAuthHooks.js.map +0 -1
  126. package/src/hooks/useActorInfiniteQuery.test.tsx +0 -457
  127. package/src/hooks/useActorMethod.test.tsx +0 -798
  128. package/src/hooks/useActorMutation.test.tsx +0 -220
  129. package/src/hooks/useActorQuery.test.tsx +0 -287
  130. package/src/hooks/useActorSuspenseInfiniteQuery.test.tsx +0 -472
  131. package/src/hooks/useActorSuspenseQuery.test.tsx +0 -254
@@ -34,28 +34,28 @@ 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
42
42
  // ============================================================================
43
43
 
44
44
  const createSuspenseQueryImpl = <
45
- A,
46
- M extends FunctionName<A> = FunctionName<A>,
47
- T extends TransformKey = "candid",
48
- TSelected = QueryFnData<A, M, T>,
45
+ Service,
46
+ Method extends FunctionName<Service> = FunctionName<Service>,
47
+ Transform extends TransformKey = "candid",
48
+ Selected = QueryFnData<Service, Method, Transform>,
49
49
  >(
50
- reactor: Reactor<A, T>,
51
- config: SuspenseQueryConfig<A, M, T, TSelected>
50
+ reactor: Reactor<Service, Transform>,
51
+ config: SuspenseQueryConfig<Service, Method, Transform, Selected>
52
52
  ): SuspenseQueryResult<
53
- QueryFnData<A, M, T>,
54
- TSelected,
55
- QueryError<A, M, T>
53
+ QueryFnData<Service, Method, Transform>,
54
+ Selected,
55
+ QueryError<Service, Method, Transform>
56
56
  > => {
57
- type TData = QueryFnData<A, M, T>
58
- type TError = QueryError<A, M, T>
57
+ type TData = QueryFnData<Service, Method, Transform>
58
+ type TError = QueryError<Service, Method, Transform>
59
59
 
60
60
  const {
61
61
  functionName,
@@ -70,11 +70,11 @@ const createSuspenseQueryImpl = <
70
70
 
71
71
  const getQueryKey = () => reactor.generateQueryKey(params)
72
72
 
73
- const applySelect = (raw: TData): TSelected =>
74
- select ? select(raw) : (raw as unknown as TSelected)
73
+ const applySelect = (raw: TData): Selected =>
74
+ select ? select(raw) : (raw as unknown as Selected)
75
75
 
76
76
  /** Cache-first fetch for use in loaders / route preloading. */
77
- const fetch = async (): Promise<TSelected> => {
77
+ const fetch = async (): Promise<Selected> => {
78
78
  const result = await reactor.fetchQuery(params)
79
79
  return applySelect(result)
80
80
  }
@@ -91,7 +91,7 @@ const createSuspenseQueryImpl = <
91
91
 
92
92
  const useSuspenseQueryHook: UseSuspenseQueryWithSelect<
93
93
  TData,
94
- TSelected,
94
+ Selected,
95
95
  TError
96
96
  > = (options: any): any => {
97
97
  const baseOptions = reactor.getQueryOptions(params)
@@ -114,21 +114,20 @@ const createSuspenseQueryImpl = <
114
114
 
115
115
  const getCacheData: SuspenseQueryResult<
116
116
  TData,
117
- TSelected,
117
+ Selected,
118
118
  TError
119
- >["getCacheData"] = (selectFn?: (data: TSelected) => unknown): any => {
119
+ >["getCacheData"] = (selectFn?: (data: Selected) => unknown): any => {
120
120
  const raw = reactor.getQueryData(params)
121
121
  if (raw === undefined) return undefined
122
122
  const selected = applySelect(raw)
123
123
  return selectFn ? selectFn(selected) : selected
124
124
  }
125
125
 
126
- const setData: SuspenseQueryResult<TData, TSelected, TError>["setData"] = (
126
+ const setData: SuspenseQueryResult<TData, Selected, TError>["setData"] = (
127
127
  updater
128
128
  ) => {
129
129
  return reactor.queryClient.setQueryData(getQueryKey(), updater as any) as
130
- | TData
131
- | undefined
130
+ TData | undefined
132
131
  }
133
132
 
134
133
  return {
@@ -147,17 +146,21 @@ const createSuspenseQueryImpl = <
147
146
  // ============================================================================
148
147
 
149
148
  export function createSuspenseQuery<
150
- A,
151
- T extends TransformKey,
152
- M extends FunctionName<A> = FunctionName<A>,
153
- TSelected = QueryFnData<A, M, T>,
149
+ Service,
150
+ Transform extends TransformKey,
151
+ Method extends FunctionName<Service> = FunctionName<Service>,
152
+ Selected = QueryFnData<Service, Method, Transform>,
154
153
  >(
155
- reactor: Reactor<A, T>,
156
- config: SuspenseQueryConfig<NoInfer<A>, M, T, TSelected>
157
- ): SuspenseQueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>> {
154
+ reactor: Reactor<Service, Transform>,
155
+ config: SuspenseQueryConfig<NoInfer<Service>, Method, Transform, Selected>
156
+ ): SuspenseQueryResult<
157
+ QueryFnData<Service, Method, Transform>,
158
+ Selected,
159
+ QueryError<Service, Method, Transform>
160
+ > {
158
161
  return createSuspenseQueryImpl(
159
162
  reactor,
160
- config as SuspenseQueryConfig<A, M, T, TSelected>
163
+ config as SuspenseQueryConfig<Service, Method, Transform, Selected>
161
164
  )
162
165
  }
163
166
 
@@ -166,24 +169,37 @@ export function createSuspenseQuery<
166
169
  // ============================================================================
167
170
 
168
171
  export function createSuspenseQueryFactory<
169
- A,
170
- T extends TransformKey,
171
- M extends FunctionName<A> = FunctionName<A>,
172
- TSelected = QueryFnData<A, M, T>,
172
+ Service,
173
+ Transform extends TransformKey,
174
+ Method extends FunctionName<Service> = FunctionName<Service>,
175
+ Selected = QueryFnData<Service, Method, Transform>,
173
176
  >(
174
- reactor: Reactor<A, T>,
175
- config: SuspenseQueryFactoryConfig<NoInfer<A>, M, T, TSelected>
177
+ reactor: Reactor<Service, Transform>,
178
+ config: SuspenseQueryFactoryConfig<
179
+ NoInfer<Service>,
180
+ Method,
181
+ Transform,
182
+ Selected
183
+ >
176
184
  ): (
177
- args: ReactorArgs<A, M, T>
178
- ) => SuspenseQueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>> {
185
+ args: ReactorArgs<Service, Method, Transform>
186
+ ) => SuspenseQueryResult<
187
+ QueryFnData<Service, Method, Transform>,
188
+ Selected,
189
+ QueryError<Service, Method, Transform>
190
+ > {
179
191
  const cache = new Map<
180
192
  string,
181
- SuspenseQueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>>
193
+ SuspenseQueryResult<
194
+ QueryFnData<Service, Method, Transform>,
195
+ Selected,
196
+ QueryError<Service, Method, Transform>
197
+ >
182
198
  >()
183
199
 
184
- return (args: ReactorArgs<A, M, T>) => {
200
+ return (args: ReactorArgs<Service, Method, Transform>) => {
185
201
  const key = reactor.generateQueryKey({
186
- functionName: config.functionName as M,
202
+ functionName: config.functionName as Method,
187
203
  args,
188
204
  })
189
205
  const cacheKey = JSON.stringify(key)
@@ -191,8 +207,18 @@ export function createSuspenseQueryFactory<
191
207
  const existing = cache.get(cacheKey)
192
208
  if (existing) return existing
193
209
 
194
- const result = createSuspenseQueryImpl<A, M, T, TSelected>(reactor, {
195
- ...(config as SuspenseQueryFactoryConfig<A, M, T, TSelected>),
210
+ const result = createSuspenseQueryImpl<
211
+ Service,
212
+ Method,
213
+ Transform,
214
+ Selected
215
+ >(reactor, {
216
+ ...(config as SuspenseQueryFactoryConfig<
217
+ Service,
218
+ Method,
219
+ Transform,
220
+ Selected
221
+ >),
196
222
  args,
197
223
  })
198
224
  cache.set(cacheKey, result)
@@ -0,0 +1,272 @@
1
+ /**
2
+ * defineReactor - One-call bootstrap for a canister reactor + its React hooks.
3
+ *
4
+ * Collapses the four manual steps (QueryClient → ClientManager → Reactor →
5
+ * createActorHooks) into a single call, and turns the Reactor / DisplayReactor
6
+ * choice into a `display` flag.
7
+ * Use this when one-call bootstrap is enough. If you need reusable
8
+ * method-specific operations outside React, compose with query/mutation
9
+ * factories after setup.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { defineReactor } from "@ic-reactor/react"
14
+ * import { idlFactory, type _SERVICE } from "./declarations/backend"
15
+ *
16
+ * const { reactor, useActorQuery, useActorMutation } = defineReactor<_SERVICE>({
17
+ * name: "backend",
18
+ * idlFactory,
19
+ * canisterId: "rrkah-fqaaa-aaaaa-aaaaq-cai",
20
+ * display: true, // ⇒ DisplayReactor (string principals/bigints)
21
+ * })
22
+ *
23
+ * function Profile() {
24
+ * const { data } = useActorQuery({ functionName: "get_profile" })
25
+ * return <div>{data?.name}</div>
26
+ * }
27
+ * ```
28
+ *
29
+ * @example Reuse an existing ClientManager (multiple canisters share one agent)
30
+ * ```typescript
31
+ * const ledger = defineReactor<_LEDGER>({ name: "ledger", idlFactory: ledgerIdl })
32
+ * const index = defineReactor<_INDEX>({
33
+ * name: "index",
34
+ * idlFactory: indexIdl,
35
+ * clientManager: ledger.clientManager,
36
+ * authentication: ledger.authentication, // one Internet Identity session
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
+ * ```
56
+ */
57
+ import { ClientManager, Reactor, DisplayReactor } from "@ic-reactor/core"
58
+ import type {
59
+ BaseActor,
60
+ TransformKey,
61
+ ReactorParameters,
62
+ ClientManagerParameters,
63
+ DisplayReactorParameters,
64
+ } from "@ic-reactor/core"
65
+ import { QueryClient } from "@tanstack/react-query"
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"
74
+
75
+ /** Options shared by both the standard and display variants of defineReactor. */
76
+ export interface DefineReactorSharedParameters
77
+ extends
78
+ Omit<ReactorParameters, "clientManager">,
79
+ Omit<ClientManagerParameters, "queryClient"> {
80
+ /**
81
+ * Reuse an existing ClientManager (e.g. to share one agent across canisters).
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.
86
+ */
87
+ clientManager?: ClientManager
88
+ /**
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.
93
+ */
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">
112
+ }
113
+
114
+ /** Parameters for a standard (raw Candid types) reactor. */
115
+ export interface DefineReactorParameters extends DefineReactorSharedParameters {
116
+ display?: false
117
+ }
118
+
119
+ /** Parameters for a DisplayReactor (UI-friendly string principals/bigints). */
120
+ export interface DefineDisplayReactorParameters<
121
+ Service,
122
+ > extends DefineReactorSharedParameters {
123
+ display: true
124
+ /** Optional initial argument validators (receive display types). */
125
+ validators?: DisplayReactorParameters<Service>["validators"]
126
+ }
127
+
128
+ /** The reactor instance plus its bound hooks and shared infrastructure. */
129
+ export type DefineReactorResult<
130
+ Service,
131
+ Transform extends TransformKey,
132
+ R extends Reactor<Service, Transform>,
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
+ }
144
+
145
+ export function defineReactor<Service = BaseActor>(
146
+ params: DefineDisplayReactorParameters<Service>
147
+ ): DefineReactorResult<Service, "display", DisplayReactor<Service>>
148
+
149
+ export function defineReactor<Service = BaseActor>(
150
+ params: DefineReactorParameters
151
+ ): DefineReactorResult<Service, "candid", Reactor<Service, "candid">>
152
+
153
+ export function defineReactor<Service = BaseActor>(
154
+ params: DefineReactorParameters | DefineDisplayReactorParameters<Service>
155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
+ ): DefineReactorResult<Service, any, any> {
157
+ const {
158
+ clientManager: providedClientManager,
159
+ queryClient: providedQueryClient,
160
+ authentication: providedAuthentication,
161
+ auth,
162
+ display,
163
+ agentOptions,
164
+ name,
165
+ idlFactory,
166
+ canisterId,
167
+ pollingOptions,
168
+ } = params as DefineDisplayReactorParameters<Service>
169
+
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
+ }
195
+
196
+ const clientManager =
197
+ providedClientManager ??
198
+ providedAuthentication?.clientManager ??
199
+ new ClientManager({
200
+ queryClient: providedQueryClient ?? new QueryClient(),
201
+ agentOptions,
202
+ })
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
+
208
+ const reactorConfig = {
209
+ clientManager,
210
+ name,
211
+ idlFactory,
212
+ canisterId,
213
+ pollingOptions,
214
+ }
215
+
216
+ // The Reactor / DisplayReactor union cannot be expressed through the shared
217
+ // implementation signature, so the body is intentionally untyped here; the
218
+ // public overloads above carry the precise types for callers.
219
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
220
+ const reactor: any = display
221
+ ? new DisplayReactor<Service>({
222
+ ...reactorConfig,
223
+ validators: (params as DefineDisplayReactorParameters<Service>)
224
+ .validators,
225
+ })
226
+ : new Reactor<Service>(reactorConfig)
227
+
228
+ const hooks = createActorHooks(reactor)
229
+
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
+ }
272
+ }
@@ -1,26 +1,13 @@
1
- import {
2
- useSyncExternalStore,
3
- useEffect,
4
- useRef,
5
- useMemo,
6
- useState,
7
- useCallback,
8
- } from "react"
9
- import {
10
- ClientManager,
11
- AgentState,
12
- AuthState,
13
- ClientManagerSignInOptions,
14
- IdentityAttributeResult,
15
- RequestIdentityAttributesParameters,
16
- RequestOpenIdIdentityAttributesParameters,
17
- } from "@ic-reactor/core"
1
+ import { useSyncExternalStore, useEffect, useRef, useMemo } from "react"
2
+ import type { AuthenticationManager } from "../auth/authentication-manager.js"
3
+ import type { AuthState, AuthenticationSignInOptions } from "../auth/types.js"
4
+ import type { AgentState } from "@ic-reactor/core"
18
5
  import type { Principal } from "@icp-sdk/core/principal"
19
6
  import type { Identity } from "@icp-sdk/core/agent"
20
7
 
21
8
  export interface UseAuthReturn {
22
9
  authenticate: () => Promise<Identity | undefined>
23
- login: (options?: ClientManagerSignInOptions) => Promise<void>
10
+ login: (options?: AuthenticationSignInOptions) => Promise<void>
24
11
  logout: (options?: { returnTo?: string }) => Promise<void>
25
12
  isAuthenticated: boolean
26
13
  isAuthenticating: boolean
@@ -33,27 +20,13 @@ export interface CreateAuthHooksReturn {
33
20
  useAgentState: () => AgentState
34
21
  useUserPrincipal: () => Principal | null
35
22
  useAuth: () => UseAuthReturn
36
- useIdentityAttributes: () => UseIdentityAttributesReturn
37
- }
38
-
39
- export interface UseIdentityAttributesReturn {
40
- requestAttributes: (
41
- params: RequestIdentityAttributesParameters
42
- ) => Promise<IdentityAttributeResult>
43
- requestOpenIdAttributes: (
44
- params: RequestOpenIdIdentityAttributesParameters
45
- ) => Promise<IdentityAttributeResult>
46
- attributes: IdentityAttributeResult | null
47
- isRequestingAttributes: boolean
48
- attributeError: Error | null
49
- clearAttributes: () => void
50
23
  }
51
24
 
52
25
  /**
53
26
  * Create authentication hooks for managing user sessions with Internet Identity.
54
27
  *
55
28
  * @example
56
- * const { useAuth, useUserPrincipal, useAgentState } = createAuthHooks(clientManager)
29
+ * const { useAuth, useUserPrincipal, useAgentState } = createAuthHooks(authentication)
57
30
  *
58
31
  * function App() {
59
32
  * const { login, logout, principal, isAuthenticated } = useAuth()
@@ -64,8 +37,9 @@ export interface UseIdentityAttributesReturn {
64
37
  * }
65
38
  */
66
39
  export const createAuthHooks = (
67
- clientManager: ClientManager
40
+ authentication: AuthenticationManager
68
41
  ): CreateAuthHooksReturn => {
42
+ const { clientManager } = authentication
69
43
  /**
70
44
  * Subscribe to agent state changes.
71
45
  * Returns the current agent state (agent, isInitialized, etc.)
@@ -84,10 +58,10 @@ export const createAuthHooks = (
84
58
  */
85
59
  const useAuthState = (): AuthState =>
86
60
  useSyncExternalStore(
87
- (callback) => clientManager.subscribeAuthState(callback),
88
- () => clientManager.authState,
61
+ (callback) => authentication.subscribeAuthState(callback),
62
+ () => authentication.authState,
89
63
  // Server snapshot - provide initial state for SSR
90
- () => clientManager.authState
64
+ () => authentication.authState
91
65
  )
92
66
 
93
67
  /**
@@ -109,18 +83,27 @@ export const createAuthHooks = (
109
83
  * }
110
84
  */
111
85
  const useAuth = (): UseAuthReturn => {
112
- const { login, logout, authenticate } = clientManager
86
+ const { login, logout, authenticate } = authentication
113
87
  const { isAuthenticated, isAuthenticating, identity, error } =
114
88
  useAuthState()
115
89
 
116
90
  // Track if we've already initialized to avoid duplicate calls
117
91
  const initializedRef = useRef(false)
118
92
 
119
- // 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.
120
96
  useEffect(() => {
121
97
  if (!initializedRef.current) {
122
98
  initializedRef.current = true
123
- clientManager.initialize()
99
+ authentication
100
+ .prepareClient()
101
+ .catch(() => undefined)
102
+ .then(() => clientManager.initialize())
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)
124
107
  }
125
108
  }, [])
126
109
 
@@ -157,68 +140,9 @@ export const createAuthHooks = (
157
140
  return identity ? identity.getPrincipal() : null
158
141
  }
159
142
 
160
- const useIdentityAttributes = (): UseIdentityAttributesReturn => {
161
- const [attributes, setAttributes] =
162
- useState<IdentityAttributeResult | null>(null)
163
- const [isRequestingAttributes, setIsRequestingAttributes] = useState(false)
164
- const [attributeError, setAttributeError] = useState<Error | null>(null)
165
-
166
- const requestAttributes = useCallback(
167
- async (params: RequestIdentityAttributesParameters) => {
168
- setIsRequestingAttributes(true)
169
- setAttributeError(null)
170
- try {
171
- const result = await clientManager.requestIdentityAttributes(params)
172
- setAttributes(result)
173
- return result
174
- } catch (error) {
175
- setAttributeError(error as Error)
176
- throw error
177
- } finally {
178
- setIsRequestingAttributes(false)
179
- }
180
- },
181
- []
182
- )
183
-
184
- const requestOpenIdAttributes = useCallback(
185
- async (params: RequestOpenIdIdentityAttributesParameters) => {
186
- setIsRequestingAttributes(true)
187
- setAttributeError(null)
188
- try {
189
- const result =
190
- await clientManager.requestOpenIdIdentityAttributes(params)
191
- setAttributes(result)
192
- return result
193
- } catch (error) {
194
- setAttributeError(error as Error)
195
- throw error
196
- } finally {
197
- setIsRequestingAttributes(false)
198
- }
199
- },
200
- []
201
- )
202
-
203
- const clearAttributes = useCallback(() => {
204
- setAttributes(null)
205
- setAttributeError(null)
206
- }, [])
207
-
208
- return {
209
- requestAttributes,
210
- requestOpenIdAttributes,
211
- attributes,
212
- isRequestingAttributes,
213
- attributeError,
214
- clearAttributes,
215
- }
216
- }
217
-
218
143
  return {
219
144
  useAuth,
220
145
  useAgentState,
221
146
  useUserPrincipal,
222
- useIdentityAttributes,
223
147
  }
224
148
  }
@@ -24,6 +24,10 @@ export {
24
24
  createActorMethodHooks,
25
25
  } from "./useActorMethod.js"
26
26
 
27
+ // createAuthHooks
28
+ export { createAuthHooks } from "./createAuthHooks.js"
29
+ export type { CreateAuthHooksReturn, UseAuthReturn } from "./createAuthHooks.js"
30
+
27
31
  // ============================================================================
28
32
  // Types - UseReactor* (with reactor parameter)
29
33
  // ============================================================================