@ic-reactor/react 3.11.0 → 3.12.1
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 +35 -4
- package/dist/auth/authentication-manager.d.ts +15 -0
- package/dist/auth/authentication-manager.d.ts.map +1 -1
- package/dist/auth/authentication-manager.js +84 -16
- package/dist/auth/authentication-manager.js.map +1 -1
- package/dist/auth/constants.d.ts +7 -1
- package/dist/auth/constants.d.ts.map +1 -1
- package/dist/auth/constants.js +7 -1
- package/dist/auth/constants.js.map +1 -1
- package/dist/auth/identity-attributes-manager.d.ts.map +1 -1
- package/dist/auth/identity-attributes-manager.js +13 -14
- package/dist/auth/identity-attributes-manager.js.map +1 -1
- package/dist/auth/local-ii-probe.d.ts +46 -0
- package/dist/auth/local-ii-probe.d.ts.map +1 -0
- package/dist/auth/local-ii-probe.js +102 -0
- package/dist/auth/local-ii-probe.js.map +1 -0
- package/dist/auth/types.d.ts +3 -6
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/defineReactor.d.ts +2 -1
- package/dist/defineReactor.d.ts.map +1 -1
- package/dist/defineReactor.js +22 -1
- package/dist/defineReactor.js.map +1 -1
- package/llms.txt +7 -1
- package/package.json +12 -12
- package/src/auth/authentication-manager.ts +102 -20
- package/src/auth/constants.ts +9 -2
- package/src/auth/identity-attributes-manager.ts +13 -18
- package/src/auth/local-ii-probe.ts +147 -0
- package/src/auth/types.ts +5 -9
- package/src/defineReactor.ts +30 -1
package/dist/auth/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAExD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,UAAU,CAAA;IAChB,SAAS,EAAE,UAAU,CAAA;CACtB;AAED
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"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"}
|
package/dist/defineReactor.d.ts
CHANGED
|
@@ -70,7 +70,8 @@ export interface DefineReactorSharedParameters extends Omit<ReactorParameters, "
|
|
|
70
70
|
* When omitted, a ClientManager is created from the agent options below.
|
|
71
71
|
*
|
|
72
72
|
* A supplied or adopted manager brings its own agent and QueryClient, so
|
|
73
|
-
* `agentOptions` and `
|
|
73
|
+
* `agentOptions`, `queryClient` and `allowEnvConfig` apply only when this
|
|
74
|
+
* call creates one.
|
|
74
75
|
*/
|
|
75
76
|
clientManager?: ClientManager;
|
|
76
77
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defineReactor.d.ts","sourceRoot":"","sources":["../src/defineReactor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,OAAO,EACL,aAAa,EACb,OAAO,EACP,cAAc,EAEf,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAoB,UAAU,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAA;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAA;AAEjF,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAA;AAEzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AACvE,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,kCAAkC,CAAA;AAEvF,iFAAiF;AACjF,MAAM,WAAW,6BACf,SACE,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,EACxC,IAAI,CAAC,uBAAuB,EAAE,aAAa,CAAC;IAC9C
|
|
1
|
+
{"version":3,"file":"defineReactor.d.ts","sourceRoot":"","sources":["../src/defineReactor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,OAAO,EACL,aAAa,EACb,OAAO,EACP,cAAc,EAEf,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAoB,UAAU,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAA;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAA;AAEjF,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAA;AAEzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AACvE,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,kCAAkC,CAAA;AAEvF,iFAAiF;AACjF,MAAM,WAAW,6BACf,SACE,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,EACxC,IAAI,CAAC,uBAAuB,EAAE,aAAa,CAAC;IAC9C;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,qBAAqB,CAAA;IACtC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC,+BAA+B,EAAE,eAAe,CAAC,CAAA;CAC9D;AAED,4DAA4D;AAC5D,MAAM,WAAW,uBAAwB,SAAQ,6BAA6B;IAC5E,OAAO,CAAC,EAAE,KAAK,CAAA;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,8BAA8B,CAC7C,OAAO,CACP,SAAQ,6BAA6B;IACrC,OAAO,EAAE,IAAI,CAAA;IACb,oEAAoE;IACpE,UAAU,CAAC,EAAE,wBAAwB,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAA;CAC7D;AAED,2EAA2E;AAC3E,MAAM,MAAM,mBAAmB,CAC7B,OAAO,EACP,SAAS,SAAS,YAAY,EAC9B,CAAC,SAAS,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,IACnC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,GAChC,qBAAqB,GAAG;IACtB,OAAO,EAAE,CAAC,CAAA;IACV,aAAa,EAAE,aAAa,CAAA;IAC5B,WAAW,EAAE,WAAW,CAAA;IACxB,2DAA2D;IAC3D,cAAc,EAAE,qBAAqB,CAAA;IACrC,0EAA0E;IAC1E,kBAAkB,EAAE,yBAAyB,CAAA;IAC7C,qBAAqB,EAAE,MAAM,2BAA2B,CAAA;CACzD,CAAA;AAmBH,wBAAgB,aAAa,CAAC,OAAO,GAAG,SAAS,EAC/C,MAAM,EAAE,8BAA8B,CAAC,OAAO,CAAC,GAC9C,mBAAmB,CAAC,OAAO,EAAE,SAAS,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;AAEnE,wBAAgB,aAAa,CAAC,OAAO,GAAG,SAAS,EAC/C,MAAM,EAAE,uBAAuB,GAC9B,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA"}
|
package/dist/defineReactor.js
CHANGED
|
@@ -79,7 +79,7 @@ const createDefaultQueryClient = () => new QueryClient({
|
|
|
79
79
|
export function defineReactor(params
|
|
80
80
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
81
|
) {
|
|
82
|
-
const { clientManager: providedClientManager, queryClient: providedQueryClient, authentication: providedAuthentication, auth, display, agentOptions, name, idlFactory, canisterId, pollingOptions, } = params;
|
|
82
|
+
const { clientManager: providedClientManager, queryClient: providedQueryClient, authentication: providedAuthentication, auth, display, agentOptions, allowEnvConfig, allowEnvRootKey, name, idlFactory, canisterId, pollingOptions, } = params;
|
|
83
83
|
// A shared AuthenticationManager updates the identity on its own
|
|
84
84
|
// ClientManager. Giving this reactor a different one would leave its calls
|
|
85
85
|
// anonymous after sign-in, so adopt the manager's rather than building a new
|
|
@@ -98,11 +98,32 @@ export function defineReactor(params
|
|
|
98
98
|
`The supplied manager is already configured, so \`auth\` (${Object.keys(auth).join(", ")}) would be ignored. ` +
|
|
99
99
|
`Pass those options where that AuthenticationManager is created, or drop \`authentication\` to build one here.`);
|
|
100
100
|
}
|
|
101
|
+
// Same reasoning as `auth`, and it matters more here: an ignored
|
|
102
|
+
// `allowEnvConfig: false` reads as "I locked the cookie out" while the
|
|
103
|
+
// supplied manager carries whatever decision it was built with.
|
|
104
|
+
if ((providedClientManager || providedAuthentication) &&
|
|
105
|
+
(allowEnvConfig !== undefined || allowEnvRootKey !== undefined)) {
|
|
106
|
+
const passed = [
|
|
107
|
+
allowEnvConfig !== undefined && "allowEnvConfig",
|
|
108
|
+
allowEnvRootKey !== undefined && "allowEnvRootKey",
|
|
109
|
+
]
|
|
110
|
+
.filter(Boolean)
|
|
111
|
+
.join(", ");
|
|
112
|
+
throw new Error(`[ic-reactor] defineReactor("${name}") received both a ClientManager and \`${passed}\`. ` +
|
|
113
|
+
`That option is resolved when a ClientManager is constructed, so the supplied one already carries its own ` +
|
|
114
|
+
`decision and this would be ignored — silently changing nothing about whether the ic_env cookie is trusted. ` +
|
|
115
|
+
`Pass it where that ClientManager is created, or drop \`clientManager\` to build one here.`);
|
|
116
|
+
}
|
|
101
117
|
const clientManager = providedClientManager ??
|
|
102
118
|
providedAuthentication?.clientManager ??
|
|
103
119
|
new ClientManager({
|
|
104
120
|
queryClient: providedQueryClient ?? createDefaultQueryClient(),
|
|
105
121
|
agentOptions,
|
|
122
|
+
// Forwarded, not dropped: the type has always accepted these (it extends
|
|
123
|
+
// ClientManagerParameters) while the call ignored them, so the one
|
|
124
|
+
// documented setup path silently discarded the ic_env opt-in it advertised.
|
|
125
|
+
allowEnvConfig,
|
|
126
|
+
allowEnvRootKey,
|
|
106
127
|
});
|
|
107
128
|
// Always report the QueryClient actually in use: when a ClientManager is
|
|
108
129
|
// supplied or adopted, its own QueryClient is the one queries run against.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defineReactor.js","sourceRoot":"","sources":["../src/defineReactor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,OAAO,EACL,aAAa,EACb,OAAO,EACP,cAAc,EACd,YAAY,GACb,MAAM,kBAAkB,CAAA;AAQzB,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAc,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAA;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAA;AACjF,OAAO,EAAE,4BAA4B,EAAE,MAAM,wCAAwC,CAAA;AAErF,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"defineReactor.js","sourceRoot":"","sources":["../src/defineReactor.ts"],"names":[],"mappings":"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"}
|
package/llms.txt
CHANGED
|
@@ -7,9 +7,15 @@ accurate integration guidance.
|
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
9
|
pnpm add @ic-reactor/react @icp-sdk/core @tanstack/react-query
|
|
10
|
-
pnpm add @icp-sdk/auth # optional auth helpers
|
|
10
|
+
pnpm add @icp-sdk/auth@^8 # optional auth helpers; v8 only, v7 is not supported
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
npm cannot resolve this set on its own: every published `@icp-sdk/auth` still
|
|
14
|
+
peers `@icp-sdk/core@^5` while this package needs `^6`, so a strict
|
|
15
|
+
`npm install` fails with `ERESOLVE`. Add
|
|
16
|
+
`{"overrides":{"@icp-sdk/auth":{"@icp-sdk/core":"$@icp-sdk/core"}}}` to
|
|
17
|
+
`package.json`. pnpm and yarn do not need it.
|
|
18
|
+
|
|
13
19
|
## Start Here
|
|
14
20
|
|
|
15
21
|
- Fastest setup: `defineReactor(...)`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ic-reactor/react",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.1",
|
|
4
4
|
"description": "IC Reactor React library for building Internet Computer apps",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -53,30 +53,30 @@
|
|
|
53
53
|
"author": "Behrad Deylami",
|
|
54
54
|
"license": "MIT",
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@ic-reactor/core": "^3.
|
|
56
|
+
"@ic-reactor/core": "^3.12.1"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@icp-sdk/auth": "^
|
|
60
|
-
"@icp-sdk/core": "^6.
|
|
59
|
+
"@icp-sdk/auth": "^8.0.0",
|
|
60
|
+
"@icp-sdk/core": "^6.1.0",
|
|
61
61
|
"@tanstack/react-query": "^5.0.0",
|
|
62
62
|
"react": ">=18.0.0",
|
|
63
63
|
"react-dom": ">=18.0.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@icp-sdk/auth": "^8.0.
|
|
67
|
-
"@icp-sdk/core": "^6.
|
|
68
|
-
"@size-limit/preset-small-lib": "^
|
|
66
|
+
"@icp-sdk/auth": "^8.0.3",
|
|
67
|
+
"@icp-sdk/core": "^6.1.0",
|
|
68
|
+
"@size-limit/preset-small-lib": "^13.0.3",
|
|
69
69
|
"@tanstack/react-query": "^5.101.4",
|
|
70
70
|
"@testing-library/dom": "^10.4.1",
|
|
71
|
-
"@testing-library/jest-dom": "^
|
|
71
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
72
72
|
"@testing-library/react": "^16.3.2",
|
|
73
|
-
"@types/react": "^19.2.
|
|
74
|
-
"@types/react-dom": "^19.2.
|
|
73
|
+
"@types/react": "^19.2.18",
|
|
74
|
+
"@types/react-dom": "^19.2.4",
|
|
75
75
|
"fake-indexeddb": "^6.2.5",
|
|
76
|
-
"jsdom": "^
|
|
76
|
+
"jsdom": "^30.0.1",
|
|
77
77
|
"react": "^19.2.8",
|
|
78
78
|
"react-dom": "^19.2.8",
|
|
79
|
-
"size-limit": "^
|
|
79
|
+
"size-limit": "^13.0.3",
|
|
80
80
|
"vitest": "^4.1.10"
|
|
81
81
|
},
|
|
82
82
|
"size-limit": [
|
|
@@ -6,13 +6,19 @@ import type {
|
|
|
6
6
|
AuthenticationClientOptions,
|
|
7
7
|
AuthenticationSignInOptions,
|
|
8
8
|
} from "./types.js"
|
|
9
|
-
import { ClientManager, isDev
|
|
9
|
+
import { ClientManager, isDev } from "@ic-reactor/core"
|
|
10
10
|
|
|
11
11
|
import { Principal } from "@icp-sdk/core/principal"
|
|
12
12
|
import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"
|
|
13
|
+
import {
|
|
14
|
+
probeLocalInternetIdentity,
|
|
15
|
+
localInternetIdentityUnavailableError,
|
|
16
|
+
type AuthorizePath,
|
|
17
|
+
} from "./local-ii-probe.js"
|
|
13
18
|
import {
|
|
14
19
|
IC_INTERNET_IDENTITY_PROVIDER,
|
|
15
20
|
INTERNET_IDENTITY_PROVIDER_ENV_KEY,
|
|
21
|
+
LOCAL_INTERNET_IDENTITY_CANISTER_ID,
|
|
16
22
|
localInternetIdentityProvider,
|
|
17
23
|
} from "./constants.js"
|
|
18
24
|
|
|
@@ -61,6 +67,13 @@ export class AuthenticationManager {
|
|
|
61
67
|
}
|
|
62
68
|
private readonly identityProvider?: string | URL
|
|
63
69
|
private readonly internetIdentityId?: string
|
|
70
|
+
/**
|
|
71
|
+
* Which authorize path the locally deployed Internet Identity serves, once
|
|
72
|
+
* probed. `undefined` means not probed yet; `null` means it serves no sign-in
|
|
73
|
+
* UI and login should fail with an explanation rather than open a popup onto
|
|
74
|
+
* a gateway error page.
|
|
75
|
+
*/
|
|
76
|
+
private localAuthorizePath?: AuthorizePath | null
|
|
64
77
|
private readonly defaultClientOptions: AuthenticationClientOptions
|
|
65
78
|
public readonly clientManager: ClientManager
|
|
66
79
|
|
|
@@ -86,13 +99,18 @@ export class AuthenticationManager {
|
|
|
86
99
|
canisterEnv?.["PUBLIC_INTERNET_IDENTITY_PROVIDER"],
|
|
87
100
|
clientManager
|
|
88
101
|
)
|
|
102
|
+
// Same cookie, same decision. This one only ever reaches a local provider
|
|
103
|
+
// URL, but `allowEnvConfig: false` has to mean the cookie is not consulted
|
|
104
|
+
// rather than mostly not consulted.
|
|
89
105
|
this.internetIdentityId =
|
|
90
106
|
internetIdentityId ||
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
(clientManager.trustsEnvConfig
|
|
108
|
+
? acceptEnvCanisterId(
|
|
109
|
+
canisterEnv?.["internet_identity"] ||
|
|
110
|
+
canisterEnv?.["PUBLIC_CANISTER_ID:internet_identity"] ||
|
|
111
|
+
canisterEnv?.["CANISTER_ID_INTERNET_IDENTITY"]
|
|
112
|
+
)
|
|
113
|
+
: undefined)
|
|
96
114
|
this.defaultClientOptions = clientOptions
|
|
97
115
|
|
|
98
116
|
if (authClient) {
|
|
@@ -131,6 +149,11 @@ export class AuthenticationManager {
|
|
|
131
149
|
* what browser popup blockers and the ICRC-29 transport require.
|
|
132
150
|
*/
|
|
133
151
|
public async prepareClient(options?: AuthenticationClientOptions) {
|
|
152
|
+
// Before resolving options, because resolving them is what picks the
|
|
153
|
+
// provider URL. This is the last async point before `login()` has to stay
|
|
154
|
+
// inside the user gesture, so the answer has to be cached by now.
|
|
155
|
+
await this.ensureLocalAuthorizePath()
|
|
156
|
+
|
|
134
157
|
const clientOptions = this.resolveClientOptions(options)
|
|
135
158
|
|
|
136
159
|
if (this.authClient && !this.shouldRecreateClient(clientOptions)) {
|
|
@@ -140,6 +163,40 @@ export class AuthenticationManager {
|
|
|
140
163
|
return this.initializeClient(clientOptions)
|
|
141
164
|
}
|
|
142
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Probe the local Internet Identity canister once, and remember what it
|
|
168
|
+
* serves.
|
|
169
|
+
*
|
|
170
|
+
* Only for the derived local provider: an explicitly configured
|
|
171
|
+
* `identityProvider` is the caller's business, and mainnet is fixed.
|
|
172
|
+
*/
|
|
173
|
+
private async ensureLocalAuthorizePath(): Promise<void> {
|
|
174
|
+
if (this.localAuthorizePath !== undefined) return
|
|
175
|
+
if (this.identityProvider) return
|
|
176
|
+
if (!this.clientManager.isLocal) return
|
|
177
|
+
|
|
178
|
+
const canisterId =
|
|
179
|
+
this.internetIdentityId ?? LOCAL_INTERNET_IDENTITY_CANISTER_ID
|
|
180
|
+
|
|
181
|
+
const { path, inconclusive } = await probeLocalInternetIdentity(
|
|
182
|
+
this.clientManager.agent,
|
|
183
|
+
canisterId
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
// An inconclusive probe must not change behaviour: the canister may be
|
|
187
|
+
// fine and merely unreachable from here, and a diagnostic that blocks a
|
|
188
|
+
// working login is worse than the failure it explains.
|
|
189
|
+
this.localAuthorizePath = inconclusive ? "/authorize" : path
|
|
190
|
+
|
|
191
|
+
if (path === "/#authorize" && !inconclusive) {
|
|
192
|
+
console.warn(
|
|
193
|
+
`[ic-reactor] Internet Identity canister ${canisterId} serves its sign-in UI at ` +
|
|
194
|
+
`"/" rather than "/authorize" — using the legacy #authorize flow. This is a ` +
|
|
195
|
+
`pre-2026 build; newer ones through release-2026-03-16 serve /authorize directly.`
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
143
200
|
/**
|
|
144
201
|
* Returns an AuthClient without awaiting, or `undefined` when the auth
|
|
145
202
|
* module has not been loaded yet.
|
|
@@ -467,12 +524,26 @@ export class AuthenticationManager {
|
|
|
467
524
|
if (this.identityProvider) {
|
|
468
525
|
return this.identityProvider
|
|
469
526
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
527
|
+
if (!this.clientManager.isLocal) {
|
|
528
|
+
return IC_INTERNET_IDENTITY_PROVIDER
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const canisterId =
|
|
532
|
+
this.internetIdentityId ?? LOCAL_INTERNET_IDENTITY_CANISTER_ID
|
|
533
|
+
|
|
534
|
+
// `null` is the probe's positive finding that this build serves no sign-in
|
|
535
|
+
// UI. Throwing here surfaces an actionable message where the caller can see
|
|
536
|
+
// it, instead of opening a popup onto the gateway's verification-error page
|
|
537
|
+
// and leaving the app waiting until the user closes it.
|
|
538
|
+
if (this.localAuthorizePath === null) {
|
|
539
|
+
throw localInternetIdentityUnavailableError(canisterId)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return localInternetIdentityProvider(
|
|
543
|
+
Number(this.clientManager.agentHost?.port) || 4943,
|
|
544
|
+
this.internetIdentityId,
|
|
545
|
+
this.localAuthorizePath
|
|
546
|
+
)
|
|
476
547
|
}
|
|
477
548
|
|
|
478
549
|
private updateState(newState: Partial<AuthState>) {
|
|
@@ -651,16 +722,27 @@ function acceptEnvIdentityProvider(
|
|
|
651
722
|
return url.toString()
|
|
652
723
|
}
|
|
653
724
|
|
|
654
|
-
//
|
|
655
|
-
//
|
|
656
|
-
//
|
|
657
|
-
//
|
|
658
|
-
|
|
725
|
+
// Positive allowlist, not `!isMainnetHost(...)`.
|
|
726
|
+
//
|
|
727
|
+
// The old comment here claimed this defaulted to true for an unknown host so
|
|
728
|
+
// that "anything that looks like mainnet fails closed". It did not:
|
|
729
|
+
// isMainnetHost defaults to true only for `undefined`, and returns false for
|
|
730
|
+
// every unrecognised host — so a dapp on a custom domain accepted an identity
|
|
731
|
+
// provider chosen by a cookie, and the ic_env cookie is writable by any
|
|
732
|
+
// sibling subdomain. Redirecting the Internet Identity flow is as severe as
|
|
733
|
+
// substituting the root key, so both now use the same test.
|
|
734
|
+
//
|
|
735
|
+
// That test is the ClientManager's single resolved decision rather than a
|
|
736
|
+
// recomputed host test, which silently ignored a caller
|
|
737
|
+
// who had opted in: `allowEnvConfig: true` accepted the cookie's root key and
|
|
738
|
+
// then discarded its identity provider, from the same cookie, on the same
|
|
739
|
+
// host.
|
|
740
|
+
if (!clientManager.trustsEnvConfig) {
|
|
659
741
|
console.warn(
|
|
660
742
|
`[ic-reactor] Ignoring the Internet Identity provider from the ic_env cookie ` +
|
|
661
|
-
`("${url.origin}") because this reactor
|
|
662
|
-
`is taken from configuration rather than the environment. Pass ` +
|
|
663
|
-
`\`identityProvider\` to AuthenticationManager to use a custom provider
|
|
743
|
+
`("${url.origin}") because this reactor does not target a local replica, where ` +
|
|
744
|
+
`the provider is taken from configuration rather than the environment. Pass ` +
|
|
745
|
+
`\`identityProvider\` to AuthenticationManager to use a custom provider.`
|
|
664
746
|
)
|
|
665
747
|
return undefined
|
|
666
748
|
}
|
package/src/auth/constants.ts
CHANGED
|
@@ -12,11 +12,18 @@ export const LOCAL_INTERNET_IDENTITY_CANISTER_ID = "rdmx6-jaaaa-aaaaa-aaadq-cai"
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Builds the local Internet Identity provider URL for the given replica port.
|
|
15
|
+
*
|
|
16
|
+
* `authorizePath` selects the shape the installed build understands: modern
|
|
17
|
+
* builds serve `/authorize` as a real asset, while pre-2026 ones serve the SPA
|
|
18
|
+
* at `/` and drive the flow from a `#authorize` fragment. `AuthenticationManager`
|
|
19
|
+
* probes the canister and passes the right one; the default keeps the previous
|
|
20
|
+
* behaviour for direct callers.
|
|
15
21
|
*/
|
|
16
22
|
export const localInternetIdentityProvider = (
|
|
17
23
|
port: number,
|
|
18
|
-
canisterId: string = LOCAL_INTERNET_IDENTITY_CANISTER_ID
|
|
19
|
-
|
|
24
|
+
canisterId: string = LOCAL_INTERNET_IDENTITY_CANISTER_ID,
|
|
25
|
+
authorizePath: "/authorize" | "/#authorize" = "/authorize"
|
|
26
|
+
) => `http://${canisterId}.localhost:${port}${authorizePath}`
|
|
20
27
|
|
|
21
28
|
/**
|
|
22
29
|
* Legacy default URL for local Internet Identity, using port `4943`.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
AuthClientLike,
|
|
3
2
|
IdentityAttributeNonce,
|
|
4
3
|
IdentityAttributeResult,
|
|
5
4
|
RequestIdentityAttributesParameters,
|
|
@@ -51,7 +50,7 @@ export class IdentityAttributesManager {
|
|
|
51
50
|
|
|
52
51
|
if (!authClient) {
|
|
53
52
|
throw new Error(
|
|
54
|
-
"Authentication module is missing or failed to initialize. To request identity attributes, please install @icp-sdk/auth
|
|
53
|
+
"Authentication module is missing or failed to initialize. To request identity attributes, please install @icp-sdk/auth v8, or provide a compatible authClient."
|
|
55
54
|
)
|
|
56
55
|
}
|
|
57
56
|
|
|
@@ -68,7 +67,7 @@ export class IdentityAttributesManager {
|
|
|
68
67
|
: Promise.resolve(authClient.getIdentity())
|
|
69
68
|
const requestPromise = authClient.requestAttributes({
|
|
70
69
|
keys,
|
|
71
|
-
nonce: toAuthClientNonce(
|
|
70
|
+
nonce: toAuthClientNonce(nonce),
|
|
72
71
|
})
|
|
73
72
|
|
|
74
73
|
const [signedAttributes, identity] = await Promise.all([
|
|
@@ -114,23 +113,19 @@ export class IdentityAttributesManager {
|
|
|
114
113
|
}
|
|
115
114
|
|
|
116
115
|
/**
|
|
117
|
-
*
|
|
116
|
+
* Adapts our nonce input to the `@icp-sdk/auth` nonce contract.
|
|
118
117
|
*
|
|
119
|
-
* v8 takes `() => Promise<Uint8Array>`
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
118
|
+
* v8 takes `() => Promise<Uint8Array>` — a thunk, so it can journal the value
|
|
119
|
+
* and replay the same bytes through a redirect flow. The thunk form also opens
|
|
120
|
+
* the transport channel synchronously, which is what preserves the user
|
|
121
|
+
* gesture.
|
|
122
|
+
*
|
|
123
|
+
* Until 3.12.0 this probed `typeof authClient.memoize === "function"` to detect
|
|
124
|
+
* v8 and fell back to a bare promise for v7. The peer range is now `^8.0.0`, so
|
|
125
|
+
* the probe would only ever take one branch.
|
|
124
126
|
*/
|
|
125
127
|
function toAuthClientNonce(
|
|
126
|
-
authClient: AuthClientLike,
|
|
127
128
|
nonce: IdentityAttributeNonce
|
|
128
|
-
):
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (supportsNonceThunk) {
|
|
132
|
-
return () => Promise.resolve(typeof nonce === "function" ? nonce() : nonce)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return Promise.resolve(typeof nonce === "function" ? nonce() : nonce)
|
|
129
|
+
): () => Promise<Uint8Array> {
|
|
130
|
+
return () => Promise.resolve(typeof nonce === "function" ? nonce() : nonce)
|
|
136
131
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask a locally deployed Internet Identity canister what it actually serves.
|
|
3
|
+
*
|
|
4
|
+
* `localInternetIdentityProvider` builds `http://<id>.localhost:<port>/authorize`,
|
|
5
|
+
* the id.ai-era path. Whether that works depends on which Internet Identity
|
|
6
|
+
* build is installed, and the window is narrow:
|
|
7
|
+
*
|
|
8
|
+
* - up to `release-2025-03-07`, the SPA lived at `/` and used a `#authorize`
|
|
9
|
+
* fragment; `/authorize` is not an asset
|
|
10
|
+
* - `release-2026-01-05` through `release-2026-03-16` serve `/authorize`
|
|
11
|
+
* - from `release-2026-03-23` the frontend left the canister entirely (the id.ai
|
|
12
|
+
* split), so `/authorize` 404s even on `.raw`
|
|
13
|
+
*
|
|
14
|
+
* Outside that window the login popup renders the gateway's
|
|
15
|
+
* "Response Verification Error" page and the app simply waits until the user
|
|
16
|
+
* closes it. Nothing points at the cause.
|
|
17
|
+
*
|
|
18
|
+
* Asking the canister over the agent avoids the gateway, so the answer is about
|
|
19
|
+
* the canister's assets rather than about how the gateway felt about them.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { Actor, type HttpAgent } from "@icp-sdk/core/agent"
|
|
23
|
+
import { IDL } from "@icp-sdk/core/candid"
|
|
24
|
+
|
|
25
|
+
/** The `http_request` query every asset-serving canister exposes. */
|
|
26
|
+
const httpInterface: IDL.InterfaceFactory = ({ IDL }) => {
|
|
27
|
+
const HeaderField = IDL.Tuple(IDL.Text, IDL.Text)
|
|
28
|
+
return IDL.Service({
|
|
29
|
+
http_request: IDL.Func(
|
|
30
|
+
[
|
|
31
|
+
IDL.Record({
|
|
32
|
+
method: IDL.Text,
|
|
33
|
+
url: IDL.Text,
|
|
34
|
+
headers: IDL.Vec(HeaderField),
|
|
35
|
+
body: IDL.Vec(IDL.Nat8),
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
[
|
|
39
|
+
IDL.Record({
|
|
40
|
+
status_code: IDL.Nat16,
|
|
41
|
+
headers: IDL.Vec(HeaderField),
|
|
42
|
+
body: IDL.Vec(IDL.Nat8),
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
["query"]
|
|
46
|
+
),
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface HttpCapableActor {
|
|
51
|
+
http_request: (request: {
|
|
52
|
+
method: string
|
|
53
|
+
url: string
|
|
54
|
+
headers: [string, string][]
|
|
55
|
+
body: Uint8Array
|
|
56
|
+
}) => Promise<{ status_code: number }>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Where a given Internet Identity build expects the sign-in flow to start. */
|
|
60
|
+
export type AuthorizePath = "/authorize" | "/#authorize"
|
|
61
|
+
|
|
62
|
+
export interface LocalIiProbe {
|
|
63
|
+
/** The path that works, or `null` when the canister serves no sign-in UI. */
|
|
64
|
+
path: AuthorizePath | null
|
|
65
|
+
/**
|
|
66
|
+
* The probe could not reach a conclusion — the canister was unreachable, or
|
|
67
|
+
* it does not expose `http_request` at all. Callers keep their existing
|
|
68
|
+
* behaviour rather than blocking a login that might well work.
|
|
69
|
+
*/
|
|
70
|
+
inconclusive: boolean
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function statusOf(
|
|
74
|
+
actor: HttpCapableActor,
|
|
75
|
+
url: string
|
|
76
|
+
): Promise<number | undefined> {
|
|
77
|
+
try {
|
|
78
|
+
const response = await actor.http_request({
|
|
79
|
+
method: "GET",
|
|
80
|
+
url,
|
|
81
|
+
headers: [],
|
|
82
|
+
body: new Uint8Array(),
|
|
83
|
+
})
|
|
84
|
+
return response.status_code
|
|
85
|
+
} catch {
|
|
86
|
+
return undefined
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Determine which authorize path a locally deployed Internet Identity serves.
|
|
92
|
+
*
|
|
93
|
+
* Never throws. A probe that cannot answer returns `inconclusive`, because a
|
|
94
|
+
* diagnostic that blocks a working setup is worse than the failure it explains.
|
|
95
|
+
*/
|
|
96
|
+
export async function probeLocalInternetIdentity(
|
|
97
|
+
agent: HttpAgent,
|
|
98
|
+
canisterId: string
|
|
99
|
+
): Promise<LocalIiProbe> {
|
|
100
|
+
let actor: HttpCapableActor
|
|
101
|
+
try {
|
|
102
|
+
actor = Actor.createActor<HttpCapableActor>(httpInterface, {
|
|
103
|
+
agent,
|
|
104
|
+
canisterId,
|
|
105
|
+
})
|
|
106
|
+
} catch {
|
|
107
|
+
return { path: "/authorize", inconclusive: true }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const authorize = await statusOf(actor, "/authorize")
|
|
111
|
+
|
|
112
|
+
// The canister does not answer http_request at all — not an asset canister,
|
|
113
|
+
// not deployed, or the replica is down. Not our call to make.
|
|
114
|
+
if (authorize === undefined) {
|
|
115
|
+
return { path: "/authorize", inconclusive: true }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (authorize >= 200 && authorize < 300) {
|
|
119
|
+
return { path: "/authorize", inconclusive: false }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Pre-2026 builds serve the SPA at the root and drive the flow from a
|
|
123
|
+
// `#authorize` fragment, which is a path the gateway never sees.
|
|
124
|
+
const root = await statusOf(actor, "/")
|
|
125
|
+
if (root !== undefined && root >= 200 && root < 300) {
|
|
126
|
+
return { path: "/#authorize", inconclusive: false }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { path: null, inconclusive: false }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The error a login gets when the installed build serves no sign-in UI, in
|
|
134
|
+
* place of a 503 page inside a popup the app cannot see.
|
|
135
|
+
*/
|
|
136
|
+
export function localInternetIdentityUnavailableError(
|
|
137
|
+
canisterId: string
|
|
138
|
+
): Error {
|
|
139
|
+
return new Error(
|
|
140
|
+
`[ic-reactor] The Internet Identity canister ${canisterId} does not serve a sign-in UI: ` +
|
|
141
|
+
`neither /authorize nor / returned a page. Internet Identity builds from ` +
|
|
142
|
+
`release-2026-03-23 onward moved their frontend out of the canister (the id.ai split) ` +
|
|
143
|
+
`and cannot be used locally. Install internet_identity_dev.wasm from release-2026-03-16, ` +
|
|
144
|
+
`the newest build that still self-serves /authorize, or point \`identityProvider\` at a ` +
|
|
145
|
+
`provider you host yourself.`
|
|
146
|
+
)
|
|
147
|
+
}
|
package/src/auth/types.ts
CHANGED
|
@@ -12,9 +12,8 @@ export interface SignedIdentityAttributes {
|
|
|
12
12
|
*
|
|
13
13
|
* A function (or promise) lets the Internet Identity window open while the
|
|
14
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
|
|
16
|
-
*
|
|
17
|
-
* shape the installed client expects.
|
|
15
|
+
* instead of after it. `@icp-sdk/auth` v8 requires the function form, and
|
|
16
|
+
* IC Reactor normalizes whichever of these you pass into that thunk.
|
|
18
17
|
*/
|
|
19
18
|
export type IdentityAttributeNonce =
|
|
20
19
|
Uint8Array | Promise<Uint8Array> | (() => Uint8Array | Promise<Uint8Array>)
|
|
@@ -187,13 +186,10 @@ export interface AuthClientLike {
|
|
|
187
186
|
signOut(options?: { returnTo?: string }): Promise<void>
|
|
188
187
|
requestAttributes(params: {
|
|
189
188
|
keys: string[]
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
nonce: Uint8Array | Promise<Uint8Array> | (() => Promise<Uint8Array>)
|
|
189
|
+
// `@icp-sdk/auth` v8 takes a thunk, so it can journal the value and replay
|
|
190
|
+
// the same bytes through a redirect flow.
|
|
191
|
+
nonce: () => Promise<Uint8Array>
|
|
194
192
|
}): Promise<SignedIdentityAttributes>
|
|
195
|
-
/** Present from `@icp-sdk/auth` v8; used to detect the nonce contract. */
|
|
196
|
-
memoize?<T>(produce: () => T | Promise<T>): Promise<T>
|
|
197
193
|
}
|
|
198
194
|
|
|
199
195
|
export interface AuthState {
|