@ic-reactor/react 3.6.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -13
- package/dist/auth/authentication-manager.d.ts +98 -0
- package/dist/auth/authentication-manager.d.ts.map +1 -0
- package/dist/auth/authentication-manager.js +532 -0
- package/dist/auth/authentication-manager.js.map +1 -0
- package/dist/auth/constants.d.ts +18 -0
- package/dist/auth/constants.d.ts.map +1 -0
- package/dist/auth/constants.js +18 -0
- package/dist/auth/constants.js.map +1 -0
- package/dist/auth/createIdentityAttributeHooks.d.ts +14 -0
- package/dist/auth/createIdentityAttributeHooks.d.ts.map +1 -0
- package/dist/auth/createIdentityAttributeHooks.js +54 -0
- package/dist/auth/createIdentityAttributeHooks.js.map +1 -0
- package/dist/auth/identity-attributes-manager.d.ts +26 -0
- package/dist/auth/identity-attributes-manager.d.ts.map +1 -0
- package/dist/auth/identity-attributes-manager.js +108 -0
- package/dist/auth/identity-attributes-manager.js.map +1 -0
- package/dist/auth/identity-attributes.d.ts +19 -0
- package/dist/auth/identity-attributes.d.ts.map +1 -0
- package/dist/auth/identity-attributes.js +170 -0
- package/dist/auth/identity-attributes.js.map +1 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/types.d.ts +182 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/createActorHooks.d.ts +16 -16
- package/dist/createActorHooks.d.ts.map +1 -1
- package/dist/createActorHooks.js +6 -6
- package/dist/createActorHooks.js.map +1 -1
- package/dist/createInfiniteQuery.d.ts +33 -33
- package/dist/createInfiniteQuery.d.ts.map +1 -1
- package/dist/createInfiniteQuery.js +6 -6
- package/dist/createInfiniteQuery.js.map +1 -1
- package/dist/createMutation.d.ts +13 -2
- package/dist/createMutation.d.ts.map +1 -1
- package/dist/createMutation.js +11 -0
- package/dist/createMutation.js.map +1 -1
- package/dist/createQuery.d.ts +13 -3
- package/dist/createQuery.d.ts.map +1 -1
- package/dist/createQuery.js +16 -6
- package/dist/createQuery.js.map +1 -1
- package/dist/createSuspenseInfiniteQuery.d.ts +32 -32
- package/dist/createSuspenseInfiniteQuery.d.ts.map +1 -1
- package/dist/createSuspenseInfiniteQuery.js +6 -6
- package/dist/createSuspenseInfiniteQuery.js.map +1 -1
- package/dist/createSuspenseQuery.d.ts +3 -3
- package/dist/createSuspenseQuery.d.ts.map +1 -1
- package/dist/createSuspenseQuery.js +1 -1
- package/dist/createSuspenseQuery.js.map +1 -1
- package/dist/defineReactor.d.ts +124 -0
- package/dist/defineReactor.d.ts.map +1 -0
- package/dist/defineReactor.js +145 -0
- package/dist/defineReactor.js.map +1 -0
- package/dist/{createAuthHooks.d.ts → hooks/createAuthHooks.d.ts} +6 -13
- package/dist/hooks/createAuthHooks.d.ts.map +1 -0
- package/dist/{createAuthHooks.js → hooks/createAuthHooks.js} +18 -58
- package/dist/hooks/createAuthHooks.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.d.ts +9 -9
- package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.js +3 -1
- package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
- package/dist/hooks/useActorMethod.d.ts +17 -17
- package/dist/hooks/useActorMethod.d.ts.map +1 -1
- package/dist/hooks/useActorMethod.js.map +1 -1
- package/dist/hooks/useActorMutation.d.ts +18 -7
- package/dist/hooks/useActorMutation.d.ts.map +1 -1
- package/dist/hooks/useActorMutation.js +11 -0
- package/dist/hooks/useActorMutation.js.map +1 -1
- package/dist/hooks/useActorQuery.d.ts +8 -8
- package/dist/hooks/useActorQuery.d.ts.map +1 -1
- package/dist/hooks/useActorQuery.js.map +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +9 -9
- package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.js +3 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
- package/dist/hooks/useActorSuspenseQuery.d.ts +8 -8
- package/dist/hooks/useActorSuspenseQuery.d.ts.map +1 -1
- package/dist/hooks/useActorSuspenseQuery.js.map +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -10
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +24 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +10 -2
- package/dist/utils.js.map +1 -1
- package/llms.txt +48 -0
- package/package.json +34 -23
- package/src/auth/authentication-manager.ts +591 -0
- package/src/auth/constants.ts +25 -0
- package/src/auth/createIdentityAttributeHooks.ts +83 -0
- package/src/auth/identity-attributes-manager.ts +136 -0
- package/src/auth/identity-attributes.ts +270 -0
- package/src/auth/index.ts +7 -0
- package/src/auth/types.ts +204 -0
- package/src/createActorHooks.ts +90 -58
- package/src/createInfiniteQuery.ts +154 -98
- package/src/createMutation.ts +35 -19
- package/src/createQuery.ts +94 -50
- package/src/createSuspenseInfiniteQuery.ts +166 -115
- package/src/createSuspenseQuery.ts +69 -43
- package/src/defineReactor.ts +272 -0
- package/src/{createAuthHooks.ts → hooks/createAuthHooks.ts} +23 -99
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useActorInfiniteQuery.ts +44 -30
- package/src/hooks/useActorMethod.ts +62 -53
- package/src/hooks/useActorMutation.ts +47 -30
- package/src/hooks/useActorQuery.ts +36 -32
- package/src/hooks/useActorSuspenseInfiniteQuery.ts +56 -30
- package/src/hooks/useActorSuspenseQuery.ts +42 -32
- package/src/index.ts +12 -10
- package/src/types.ts +76 -71
- package/src/utils.ts +15 -2
- package/dist/createAuthHooks.d.ts.map +0 -1
- package/dist/createAuthHooks.js.map +0 -1
- package/src/hooks/useActorInfiniteQuery.test.tsx +0 -457
- package/src/hooks/useActorMethod.test.tsx +0 -798
- package/src/hooks/useActorMutation.test.tsx +0 -220
- package/src/hooks/useActorQuery.test.tsx +0 -287
- package/src/hooks/useActorSuspenseInfiniteQuery.test.tsx +0 -472
- package/src/hooks/useActorSuspenseQuery.test.tsx +0 -254
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const IC_INTERNET_IDENTITY_PROVIDER = "https://id.ai/authorize"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `ic_env` key used to provide a fully resolved Internet Identity URL.
|
|
5
|
+
*/
|
|
6
|
+
export const INTERNET_IDENTITY_PROVIDER_ENV_KEY = "INTERNET_IDENTITY_PROVIDER"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Well-known Internet Identity canister ID used when deploying II locally.
|
|
10
|
+
*/
|
|
11
|
+
export const LOCAL_INTERNET_IDENTITY_CANISTER_ID = "rdmx6-jaaaa-aaaaa-aaadq-cai"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Builds the local Internet Identity provider URL for the given replica port.
|
|
15
|
+
*/
|
|
16
|
+
export const localInternetIdentityProvider = (
|
|
17
|
+
port: number,
|
|
18
|
+
canisterId: string = LOCAL_INTERNET_IDENTITY_CANISTER_ID
|
|
19
|
+
) => `http://${canisterId}.localhost:${port}/authorize`
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Legacy default URL for local Internet Identity, using port `4943`.
|
|
23
|
+
*/
|
|
24
|
+
export const LOCAL_INTERNET_IDENTITY_PROVIDER =
|
|
25
|
+
localInternetIdentityProvider(4943)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useCallback, useState } from "react"
|
|
2
|
+
import type { IdentityAttributesManager } from "./identity-attributes-manager.js"
|
|
3
|
+
import type {
|
|
4
|
+
IdentityAttributeResult,
|
|
5
|
+
RequestIdentityAttributesParameters,
|
|
6
|
+
RequestOpenIdIdentityAttributesParameters,
|
|
7
|
+
} from "./types.js"
|
|
8
|
+
|
|
9
|
+
export interface UseIdentityAttributesReturn {
|
|
10
|
+
requestAttributes: (
|
|
11
|
+
params: RequestIdentityAttributesParameters
|
|
12
|
+
) => Promise<IdentityAttributeResult>
|
|
13
|
+
requestOpenIdAttributes: (
|
|
14
|
+
params: RequestOpenIdIdentityAttributesParameters
|
|
15
|
+
) => Promise<IdentityAttributeResult>
|
|
16
|
+
attributes: IdentityAttributeResult | null
|
|
17
|
+
isRequestingAttributes: boolean
|
|
18
|
+
attributeError: Error | null
|
|
19
|
+
clearAttributes: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createIdentityAttributeHooks(
|
|
23
|
+
identityAttributes: IdentityAttributesManager
|
|
24
|
+
) {
|
|
25
|
+
const useIdentityAttributes = (): UseIdentityAttributesReturn => {
|
|
26
|
+
const [attributes, setAttributes] =
|
|
27
|
+
useState<IdentityAttributeResult | null>(null)
|
|
28
|
+
const [isRequestingAttributes, setIsRequestingAttributes] = useState(false)
|
|
29
|
+
const [attributeError, setAttributeError] = useState<Error | null>(null)
|
|
30
|
+
|
|
31
|
+
const requestAttributes = useCallback(
|
|
32
|
+
async (params: RequestIdentityAttributesParameters) => {
|
|
33
|
+
setIsRequestingAttributes(true)
|
|
34
|
+
setAttributeError(null)
|
|
35
|
+
try {
|
|
36
|
+
const result = await identityAttributes.request(params)
|
|
37
|
+
setAttributes(result)
|
|
38
|
+
return result
|
|
39
|
+
} catch (error) {
|
|
40
|
+
setAttributeError(error as Error)
|
|
41
|
+
throw error
|
|
42
|
+
} finally {
|
|
43
|
+
setIsRequestingAttributes(false)
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const requestOpenIdAttributes = useCallback(
|
|
50
|
+
async (params: RequestOpenIdIdentityAttributesParameters) => {
|
|
51
|
+
setIsRequestingAttributes(true)
|
|
52
|
+
setAttributeError(null)
|
|
53
|
+
try {
|
|
54
|
+
const result = await identityAttributes.requestOpenId(params)
|
|
55
|
+
setAttributes(result)
|
|
56
|
+
return result
|
|
57
|
+
} catch (error) {
|
|
58
|
+
setAttributeError(error as Error)
|
|
59
|
+
throw error
|
|
60
|
+
} finally {
|
|
61
|
+
setIsRequestingAttributes(false)
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
[]
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const clearAttributes = useCallback(() => {
|
|
68
|
+
setAttributes(null)
|
|
69
|
+
setAttributeError(null)
|
|
70
|
+
}, [])
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
requestAttributes,
|
|
74
|
+
requestOpenIdAttributes,
|
|
75
|
+
attributes,
|
|
76
|
+
isRequestingAttributes,
|
|
77
|
+
attributeError,
|
|
78
|
+
clearAttributes,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { useIdentityAttributes }
|
|
83
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AuthClientLike,
|
|
3
|
+
IdentityAttributeNonce,
|
|
4
|
+
IdentityAttributeResult,
|
|
5
|
+
RequestIdentityAttributesParameters,
|
|
6
|
+
RequestOpenIdIdentityAttributesParameters,
|
|
7
|
+
} from "./types.js"
|
|
8
|
+
|
|
9
|
+
import { AuthenticationManager } from "./authentication-manager.js"
|
|
10
|
+
import {
|
|
11
|
+
decodeIdentityAttributeValues,
|
|
12
|
+
identityAttributeKeys,
|
|
13
|
+
normalizeSignedIdentityAttributes,
|
|
14
|
+
} from "./identity-attributes.js"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Requests and decodes signed identity attributes for an authenticated
|
|
18
|
+
* Internet Identity session.
|
|
19
|
+
*
|
|
20
|
+
* Await `authentication.prepareClient()` once during startup (the `useAuth`
|
|
21
|
+
* hook already does) so that {@link request} can open the identity provider
|
|
22
|
+
* window synchronously from a click handler.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const attributes = new IdentityAttributesManager(authentication)
|
|
27
|
+
* const result = await attributes.request({
|
|
28
|
+
* keys: ["name"],
|
|
29
|
+
* nonce: () => fetchNonceFromCanister(),
|
|
30
|
+
* })
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export class IdentityAttributesManager {
|
|
34
|
+
constructor(public readonly authentication: AuthenticationManager) {}
|
|
35
|
+
|
|
36
|
+
public request = async ({
|
|
37
|
+
keys,
|
|
38
|
+
nonce,
|
|
39
|
+
signIn = true,
|
|
40
|
+
maxTimeToLive,
|
|
41
|
+
targets,
|
|
42
|
+
...clientOptions
|
|
43
|
+
}: RequestIdentityAttributesParameters): Promise<IdentityAttributeResult> => {
|
|
44
|
+
// Nothing may be awaited before `requestAttributes` / `signIn` are invoked:
|
|
45
|
+
// the ICRC-29 transport only opens the identity provider window while the
|
|
46
|
+
// browser still considers us inside the user gesture. `getPreparedClient`
|
|
47
|
+
// is synchronous whenever `prepareClient()` has run.
|
|
48
|
+
const preparedClient = this.authentication.getPreparedClient(clientOptions)
|
|
49
|
+
const authClient =
|
|
50
|
+
preparedClient ?? (await this.authentication.ensureClient(clientOptions))
|
|
51
|
+
|
|
52
|
+
if (!authClient) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
"Authentication module is missing or failed to initialize. To request identity attributes, please install @icp-sdk/auth v7 or later, or provide a compatible authClient."
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.authentication.setAuthenticating()
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// `signIn` first: it opens the transport channel synchronously, and the
|
|
62
|
+
// attribute request then reuses that same channel and window.
|
|
63
|
+
const identityPromise = signIn
|
|
64
|
+
? this.authentication.signInOrRecoverIdentity({
|
|
65
|
+
maxTimeToLive,
|
|
66
|
+
targets,
|
|
67
|
+
})
|
|
68
|
+
: Promise.resolve(authClient.getIdentity())
|
|
69
|
+
const requestPromise = authClient.requestAttributes({
|
|
70
|
+
keys,
|
|
71
|
+
nonce: toAuthClientNonce(authClient, nonce),
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const [signedAttributes, identity] = await Promise.all([
|
|
75
|
+
requestPromise,
|
|
76
|
+
identityPromise,
|
|
77
|
+
])
|
|
78
|
+
|
|
79
|
+
const finalIdentity = identity ?? (await authClient.getIdentity())
|
|
80
|
+
const isAuthenticated = await authClient.isAuthenticated()
|
|
81
|
+
await this.authentication.commitIdentity(finalIdentity, isAuthenticated)
|
|
82
|
+
|
|
83
|
+
const normalizedSignedAttributes =
|
|
84
|
+
normalizeSignedIdentityAttributes(signedAttributes)
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
principal: finalIdentity.getPrincipal().toText(),
|
|
88
|
+
requestedKeys: keys,
|
|
89
|
+
signedAttributes: normalizedSignedAttributes,
|
|
90
|
+
decodedAttributes: decodeIdentityAttributeValues(
|
|
91
|
+
normalizedSignedAttributes.data,
|
|
92
|
+
keys
|
|
93
|
+
),
|
|
94
|
+
completedAt: new Date().toISOString(),
|
|
95
|
+
}
|
|
96
|
+
} catch (error) {
|
|
97
|
+
this.authentication.setAuthenticationError(error as Error)
|
|
98
|
+
throw error
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public requestOpenId = ({
|
|
103
|
+
keys,
|
|
104
|
+
openIdProvider,
|
|
105
|
+
...rest
|
|
106
|
+
}: RequestOpenIdIdentityAttributesParameters): Promise<IdentityAttributeResult> => {
|
|
107
|
+
// Called (not awaited) synchronously so the user gesture is preserved.
|
|
108
|
+
return this.request({
|
|
109
|
+
...rest,
|
|
110
|
+
openIdProvider,
|
|
111
|
+
keys: identityAttributeKeys({ openIdProvider, keys }),
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Bridges the two `@icp-sdk/auth` nonce contracts.
|
|
118
|
+
*
|
|
119
|
+
* v8 takes `() => Promise<Uint8Array>` (it journals the value so a redirect
|
|
120
|
+
* flow replays the same bytes); v7 takes `Uint8Array | Promise<Uint8Array>`.
|
|
121
|
+
* Both open the transport channel synchronously for the deferred forms, so we
|
|
122
|
+
* always hand over the deferred shape the installed client understands.
|
|
123
|
+
* `memoize` only exists from v8 onwards, which makes it a reliable marker.
|
|
124
|
+
*/
|
|
125
|
+
function toAuthClientNonce(
|
|
126
|
+
authClient: AuthClientLike,
|
|
127
|
+
nonce: IdentityAttributeNonce
|
|
128
|
+
): Uint8Array | Promise<Uint8Array> | (() => Promise<Uint8Array>) {
|
|
129
|
+
const supportsNonceThunk = typeof authClient.memoize === "function"
|
|
130
|
+
|
|
131
|
+
if (supportsNonceThunk) {
|
|
132
|
+
return () => Promise.resolve(typeof nonce === "function" ? nonce() : nonce)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return Promise.resolve(typeof nonce === "function" ? nonce() : nonce)
|
|
136
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { IDL } from "@icp-sdk/core/candid"
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
IdentityAttributeOpenIdProvider,
|
|
5
|
+
IdentityAttributeValues,
|
|
6
|
+
SignedIdentityAttributes,
|
|
7
|
+
} from "./types.js"
|
|
8
|
+
import { IC_INTERNET_IDENTITY_PROVIDER } from "./constants.js"
|
|
9
|
+
|
|
10
|
+
export const IDENTITY_ATTRIBUTES_PROVIDER = IC_INTERNET_IDENTITY_PROVIDER
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @deprecated Use the default ClientManager identity provider or
|
|
14
|
+
* `IDENTITY_ATTRIBUTES_PROVIDER` instead.
|
|
15
|
+
*/
|
|
16
|
+
export const IDENTITY_ATTRIBUTES_BETA_PROVIDER = IDENTITY_ATTRIBUTES_PROVIDER
|
|
17
|
+
|
|
18
|
+
const OPEN_ID_PROVIDER_URLS = {
|
|
19
|
+
apple: "https://appleid.apple.com",
|
|
20
|
+
google: "https://accounts.google.com",
|
|
21
|
+
microsoft: "https://login.microsoftonline.com/{tid}/v2.0",
|
|
22
|
+
} as const
|
|
23
|
+
|
|
24
|
+
export function identityAttributeKeys({
|
|
25
|
+
openIdProvider,
|
|
26
|
+
keys,
|
|
27
|
+
}: {
|
|
28
|
+
openIdProvider: IdentityAttributeOpenIdProvider
|
|
29
|
+
keys: string[]
|
|
30
|
+
}): string[] {
|
|
31
|
+
const provider = normalizeOpenIdProvider(openIdProvider)
|
|
32
|
+
return keys.map((key) => `openid:${provider}:${key}`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function normalizeOpenIdProvider(
|
|
36
|
+
openIdProvider: IdentityAttributeOpenIdProvider
|
|
37
|
+
): string {
|
|
38
|
+
return (
|
|
39
|
+
OPEN_ID_PROVIDER_URLS[
|
|
40
|
+
openIdProvider as keyof typeof OPEN_ID_PROVIDER_URLS
|
|
41
|
+
] ?? openIdProvider
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function resolveIdentityAttributeKeys({
|
|
46
|
+
openIdProvider,
|
|
47
|
+
keys,
|
|
48
|
+
}: {
|
|
49
|
+
openIdProvider: IdentityAttributeOpenIdProvider
|
|
50
|
+
keys: string[]
|
|
51
|
+
}): Promise<string[]> {
|
|
52
|
+
return identityAttributeKeys({ openIdProvider, keys })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function decodeIdentityAttributeValues(
|
|
56
|
+
data: Uint8Array,
|
|
57
|
+
requestedKeys: string[]
|
|
58
|
+
): IdentityAttributeValues {
|
|
59
|
+
const requestedKeyMap = requestedKeys.reduce<Record<string, string>>(
|
|
60
|
+
(acc, key) => {
|
|
61
|
+
acc[key] = identityAttributeDisplayKey(key)
|
|
62
|
+
acc[identityAttributeDisplayKey(key)] = identityAttributeDisplayKey(key)
|
|
63
|
+
return acc
|
|
64
|
+
},
|
|
65
|
+
{}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
const decodedValues = decodeCandidAttributeValues(data, requestedKeyMap)
|
|
69
|
+
if (Object.keys(decodedValues).length > 0) {
|
|
70
|
+
return decodedValues
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return extractPrintableAttributeValues(data, requestedKeys)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function decodeCandidAttributeValues(
|
|
77
|
+
data: Uint8Array,
|
|
78
|
+
requestedKeyMap: Record<string, string>
|
|
79
|
+
): IdentityAttributeValues {
|
|
80
|
+
const textPairs = IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text))
|
|
81
|
+
const recordPairs = IDL.Vec(
|
|
82
|
+
IDL.Record({
|
|
83
|
+
key: IDL.Text,
|
|
84
|
+
value: IDL.Text,
|
|
85
|
+
})
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const candidates = [
|
|
89
|
+
[textPairs],
|
|
90
|
+
[recordPairs],
|
|
91
|
+
[IDL.Record({ attributes: textPairs })],
|
|
92
|
+
[IDL.Record({ values: textPairs })],
|
|
93
|
+
[IDL.Record({ attributes: recordPairs })],
|
|
94
|
+
[IDL.Record({ values: recordPairs })],
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
for (const candidate of candidates) {
|
|
98
|
+
try {
|
|
99
|
+
const decoded = IDL.decode(candidate, data)
|
|
100
|
+
const values = collectDecodedAttributeValues(decoded, requestedKeyMap)
|
|
101
|
+
if (Object.keys(values).length > 0) {
|
|
102
|
+
return values
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// Try the next known beta payload shape, then fall back to text extraction.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function collectDecodedAttributeValues(
|
|
113
|
+
value: unknown,
|
|
114
|
+
requestedKeyMap: Record<string, string>,
|
|
115
|
+
activeKey?: string
|
|
116
|
+
): IdentityAttributeValues {
|
|
117
|
+
const values: IdentityAttributeValues = {}
|
|
118
|
+
|
|
119
|
+
if (typeof value === "string") {
|
|
120
|
+
const displayKey = requestedKeyMap[value]
|
|
121
|
+
if (displayKey) {
|
|
122
|
+
return values
|
|
123
|
+
}
|
|
124
|
+
if (activeKey && isPrintableTextValue(value)) {
|
|
125
|
+
values[activeKey] = value
|
|
126
|
+
}
|
|
127
|
+
return values
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (Array.isArray(value)) {
|
|
131
|
+
if (value.length === 2 && typeof value[0] === "string") {
|
|
132
|
+
const displayKey = requestedKeyMap[value[0]]
|
|
133
|
+
if (
|
|
134
|
+
displayKey &&
|
|
135
|
+
typeof value[1] === "string" &&
|
|
136
|
+
isPrintableTextValue(value[1])
|
|
137
|
+
) {
|
|
138
|
+
values[displayKey] = value[1]
|
|
139
|
+
return values
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const item of value) {
|
|
144
|
+
Object.assign(
|
|
145
|
+
values,
|
|
146
|
+
collectDecodedAttributeValues(item, requestedKeyMap, activeKey)
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
return values
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (value && typeof value === "object") {
|
|
153
|
+
const record = value as Record<string, unknown>
|
|
154
|
+
if (typeof record.key === "string" && typeof record.value === "string") {
|
|
155
|
+
const displayKey = requestedKeyMap[record.key]
|
|
156
|
+
if (displayKey && isPrintableTextValue(record.value)) {
|
|
157
|
+
values[displayKey] = record.value
|
|
158
|
+
return values
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const [key, nested] of Object.entries(record)) {
|
|
163
|
+
const displayKey = requestedKeyMap[key] ?? activeKey
|
|
164
|
+
if (
|
|
165
|
+
displayKey &&
|
|
166
|
+
typeof nested === "string" &&
|
|
167
|
+
isPrintableTextValue(nested)
|
|
168
|
+
) {
|
|
169
|
+
values[displayKey] = nested
|
|
170
|
+
continue
|
|
171
|
+
}
|
|
172
|
+
Object.assign(
|
|
173
|
+
values,
|
|
174
|
+
collectDecodedAttributeValues(nested, requestedKeyMap, displayKey)
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return values
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function extractPrintableAttributeValues(
|
|
183
|
+
data: Uint8Array,
|
|
184
|
+
requestedKeys: string[]
|
|
185
|
+
): IdentityAttributeValues {
|
|
186
|
+
const text = new TextDecoder().decode(data)
|
|
187
|
+
const values: IdentityAttributeValues = {}
|
|
188
|
+
|
|
189
|
+
for (const requestedKey of requestedKeys) {
|
|
190
|
+
const start = text.indexOf(requestedKey)
|
|
191
|
+
if (start === -1) {
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const tail = text.slice(start + requestedKey.length)
|
|
196
|
+
const printableRuns = tail.match(/[\x20-\x7E]{2,512}/g) ?? []
|
|
197
|
+
const value = printableRuns
|
|
198
|
+
.map((candidate) =>
|
|
199
|
+
cleanPrintableAttributeValue(candidate, requestedKeys)
|
|
200
|
+
)
|
|
201
|
+
.find(
|
|
202
|
+
(candidate) =>
|
|
203
|
+
isPrintableTextValue(candidate) &&
|
|
204
|
+
candidate !== requestedKey &&
|
|
205
|
+
!candidate.startsWith("openid:")
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if (value) {
|
|
209
|
+
values[identityAttributeDisplayKey(requestedKey)] = value.trim()
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return values
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function cleanPrintableAttributeValue(
|
|
217
|
+
value: string,
|
|
218
|
+
requestedKeys: string[]
|
|
219
|
+
): string {
|
|
220
|
+
let cleaned = value
|
|
221
|
+
|
|
222
|
+
for (const requestedKey of requestedKeys) {
|
|
223
|
+
const nextKeyIndex = cleaned.indexOf(requestedKey)
|
|
224
|
+
if (nextKeyIndex > 0) {
|
|
225
|
+
cleaned = cleaned.slice(
|
|
226
|
+
0,
|
|
227
|
+
trimCandidTextLengthPrefix(cleaned, nextKeyIndex)
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const nextOpenIdKeyIndex = cleaned.indexOf("openid:")
|
|
233
|
+
if (nextOpenIdKeyIndex > 0) {
|
|
234
|
+
cleaned = cleaned.slice(
|
|
235
|
+
0,
|
|
236
|
+
trimCandidTextLengthPrefix(cleaned, nextOpenIdKeyIndex)
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return cleaned.trim()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function trimCandidTextLengthPrefix(value: string, index: number): number {
|
|
244
|
+
let trimmedIndex = index
|
|
245
|
+
while (trimmedIndex > 0 && isAsciiDigit(value.charCodeAt(trimmedIndex - 1))) {
|
|
246
|
+
trimmedIndex -= 1
|
|
247
|
+
}
|
|
248
|
+
return trimmedIndex
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function isAsciiDigit(charCode: number): boolean {
|
|
252
|
+
return charCode >= 48 && charCode <= 57
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function identityAttributeDisplayKey(key: string): string {
|
|
256
|
+
return key.split(":").pop() || key
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function isPrintableTextValue(value: string): boolean {
|
|
260
|
+
return value.length > 0 && value.length <= 512 && /^[\x20-\x7E]+$/.test(value)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function normalizeSignedIdentityAttributes(
|
|
264
|
+
attributes: SignedIdentityAttributes
|
|
265
|
+
): SignedIdentityAttributes {
|
|
266
|
+
return {
|
|
267
|
+
data: new Uint8Array(attributes.data),
|
|
268
|
+
signature: new Uint8Array(attributes.signature),
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./authentication-manager.js"
|
|
2
|
+
export * from "./identity-attributes-manager.js"
|
|
3
|
+
export * from "./identity-attributes.js"
|
|
4
|
+
export * from "./constants.js"
|
|
5
|
+
export * from "./types.js"
|
|
6
|
+
export * from "../hooks/createAuthHooks.js"
|
|
7
|
+
export * from "./createIdentityAttributeHooks.js"
|