Skip to content

Commit 660eb29

Browse files
committed
integrate users/me/meta endpoint into the React and NextJs sdks
1 parent 5d3f8da commit 660eb29

13 files changed

Lines changed: 473 additions & 54 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
import {describe, it, expect, vi} from 'vitest';
20+
import getUsersMeMeta from '../getUsersMeMeta';
21+
import ThunderIDAPIError from '../../errors/ThunderIDAPIError';
22+
23+
describe('getUsersMeMeta', () => {
24+
it('fetches user schema metadata successfully with custom fetcher', async () => {
25+
const mockSchema = {
26+
schema: {
27+
givenName: {
28+
displayName: 'First Name',
29+
type: 'STRING',
30+
required: true,
31+
},
32+
},
33+
};
34+
35+
const mockFetcher = vi.fn().mockResolvedValue({
36+
ok: true,
37+
json: async () => mockSchema,
38+
} as Response);
39+
40+
const result = await getUsersMeMeta({
41+
baseUrl: 'https://api.example.com',
42+
fetcher: mockFetcher,
43+
});
44+
45+
expect(mockFetcher).toHaveBeenCalledWith(
46+
'https://api.example.com/users/me/meta',
47+
expect.objectContaining({
48+
method: 'GET',
49+
}),
50+
);
51+
expect(result).toEqual(mockSchema);
52+
});
53+
54+
it('throws ThunderIDAPIError for invalid URL', async () => {
55+
await expect(
56+
getUsersMeMeta({
57+
baseUrl: 'invalid-url',
58+
fetcher: vi.fn(),
59+
}),
60+
).rejects.toThrow(ThunderIDAPIError);
61+
});
62+
63+
it('throws ThunderIDAPIError when server returns non-ok response', async () => {
64+
const mockFetcher = vi.fn().mockResolvedValue({
65+
ok: false,
66+
status: 500,
67+
statusText: 'Internal Server Error',
68+
text: async () => 'Server error',
69+
} as Response);
70+
71+
await expect(
72+
getUsersMeMeta({
73+
baseUrl: 'https://api.example.com',
74+
fetcher: mockFetcher,
75+
}),
76+
).rejects.toThrow(ThunderIDAPIError);
77+
});
78+
79+
it('handles network failure', async () => {
80+
const mockFetcher = vi.fn().mockRejectedValue(new Error('Network error'));
81+
82+
await expect(
83+
getUsersMeMeta({
84+
baseUrl: 'https://api.example.com',
85+
fetcher: mockFetcher,
86+
}),
87+
).rejects.toThrow(ThunderIDAPIError);
88+
});
89+
});
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
import ThunderIDAPIError from '../errors/ThunderIDAPIError';
20+
21+
/**
22+
* Attribute schema metadata returned by GET /users/me/meta
23+
*/
24+
export interface AttributeSchema {
25+
credential?: boolean;
26+
description?: string;
27+
displayName?: string;
28+
mutability?: string;
29+
readOnly?: boolean;
30+
regex?: string;
31+
required?: boolean;
32+
subAttributes?: AttributeSchema[];
33+
type?: string;
34+
unique?: boolean;
35+
}
36+
37+
/**
38+
* Configuration for the getUsersMeMeta request
39+
*/
40+
export interface GetUsersMeMetaConfig extends Omit<RequestInit, 'method'> {
41+
/**
42+
* The base path of the API endpoint.
43+
*/
44+
baseUrl?: string;
45+
/**
46+
* Optional custom fetcher function.
47+
* If not provided, native fetch will be used.
48+
*/
49+
fetcher?: (url: string, config: RequestInit) => Promise<Response>;
50+
/**
51+
* The absolute API endpoint.
52+
*/
53+
url?: string;
54+
}
55+
56+
/**
57+
* Response structure for GET /users/me/meta
58+
*/
59+
export interface UsersMeMetaResponse {
60+
schema?: Record<string, AttributeSchema>;
61+
}
62+
63+
/**
64+
* Retrieves the user schema metadata from the specified /users/me/meta endpoint.
65+
*
66+
* @param config - Request configuration object.
67+
* @returns A promise that resolves with the user schema metadata.
68+
*/
69+
const getUsersMeMeta = async ({
70+
baseUrl,
71+
fetcher,
72+
url,
73+
...requestConfig
74+
}: GetUsersMeMetaConfig): Promise<UsersMeMetaResponse> => {
75+
try {
76+
// eslint-disable-next-line no-new
77+
new URL((url ?? baseUrl)!);
78+
} catch (error) {
79+
throw new ThunderIDAPIError(
80+
`Invalid URL provided. ${error instanceof Error ? error.message : String(error)}`,
81+
'getUsersMeMeta-ValidationError-001',
82+
'javascript',
83+
400,
84+
'The provided `url` or `baseUrl` path does not adhere to the URL schema.',
85+
);
86+
}
87+
88+
const fetchFn: typeof fetch = fetcher ?? fetch;
89+
const resolvedUrl: string = url ?? `${baseUrl?.replace(/\/$/, '')}/users/me/meta`;
90+
91+
const requestInit: RequestInit = {
92+
...requestConfig,
93+
headers: {
94+
Accept: 'application/json',
95+
'Content-Type': 'application/json',
96+
...requestConfig.headers,
97+
},
98+
method: 'GET',
99+
};
100+
101+
try {
102+
const response: Response = await fetchFn(resolvedUrl, requestInit);
103+
104+
if (!response?.ok) {
105+
const errorText: string = await response.text();
106+
107+
throw new ThunderIDAPIError(
108+
errorText,
109+
'getUsersMeMeta-ResponseError-001',
110+
'javascript',
111+
response.status,
112+
response.statusText,
113+
'Failed to fetch user schema metadata',
114+
);
115+
}
116+
117+
return (await response.json()) as UsersMeMetaResponse;
118+
} catch (error) {
119+
if (error instanceof ThunderIDAPIError) {
120+
throw error;
121+
}
122+
123+
throw new ThunderIDAPIError(
124+
`Network or parsing error: ${error instanceof Error ? error.message : 'Unknown error'}`,
125+
'getUsersMeMeta-NetworkError-001',
126+
'javascript',
127+
0,
128+
'Network Error',
129+
);
130+
}
131+
};
132+
133+
export default getUsersMeMeta;

‎packages/javascript/src/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export type {
3333
export {default as getUserInfo} from './api/getUserInfo';
3434
export {default as getUsersMe} from './api/getUsersMe';
3535
export type {GetUsersMeConfig} from './api/getUsersMe';
36+
export {default as getUsersMeMeta} from './api/getUsersMeMeta';
37+
export type {GetUsersMeMetaConfig, UsersMeMetaResponse, AttributeSchema} from './api/getUsersMeMeta';
3638
export {default as updateMeProfile} from './api/updateMeProfile';
3739
export type {UpdateMeProfileConfig} from './api/updateMeProfile';
3840

‎packages/nextjs/src/client/contexts/ThunderID/ThunderIDProvider.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ const ThunderIDClientProvider: FC<PropsWithChildren<ThunderIDClientProviderProps
374374
signUp: handleSignUp,
375375
signUpUrl,
376376
user,
377+
userSchema: null,
377378
vendor: getVendorPrefix(vendor),
378379
}),
379380
[
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
import {
20+
FetchHttpClient,
21+
HttpRequestConfig,
22+
HttpResponse,
23+
getUsersMeMeta as baseGetUsersMeMeta,
24+
GetUsersMeMetaConfig as BaseGetUsersMeMetaConfig,
25+
UsersMeMetaResponse,
26+
AttributeSchema,
27+
} from '@thunderid/browser';
28+
29+
export type {AttributeSchema, UsersMeMetaResponse};
30+
31+
/**
32+
* Configuration for the getUsersMeMeta request (React-specific)
33+
*/
34+
export interface GetUsersMeMetaConfig extends Omit<BaseGetUsersMeMetaConfig, 'fetcher'> {
35+
/**
36+
* Optional custom fetcher function. If not provided, the ThunderID SPA client's httpClient will be used
37+
*/
38+
fetcher?: (url: string, config: RequestInit) => Promise<Response>;
39+
/**
40+
* Optional instance ID for multi-instance support. Defaults to 0.
41+
*/
42+
instanceId?: number;
43+
}
44+
45+
/**
46+
* Retrieves the user schema metadata from the specified /users/me/meta endpoint.
47+
* Uses ThunderID SPA client FetchHttpClient by default with multi-instance support.
48+
*/
49+
const getUsersMeMeta = async ({
50+
fetcher,
51+
instanceId = 0,
52+
...requestConfig
53+
}: GetUsersMeMetaConfig): Promise<UsersMeMetaResponse> => {
54+
const defaultFetcher = async (url: string, config: RequestInit): Promise<Response> => {
55+
const httpClient: FetchHttpClient = FetchHttpClient.getInstance(instanceId);
56+
const response: HttpResponse<UsersMeMetaResponse> = await httpClient.request({
57+
headers: config.headers as Record<string, string>,
58+
method: config.method ?? 'GET',
59+
url,
60+
} as HttpRequestConfig);
61+
62+
return {
63+
json: () => Promise.resolve(response.data),
64+
ok: response.status >= 200 && response.status < 300,
65+
status: response.status,
66+
statusText: response.statusText || '',
67+
text: () => Promise.resolve(typeof response.data === 'string' ? response.data : JSON.stringify(response.data)),
68+
} as Response;
69+
};
70+
71+
return baseGetUsersMeMeta({
72+
...requestConfig,
73+
fetcher: fetcher ?? defaultFetcher,
74+
});
75+
};
76+
77+
export default getUsersMeMeta;

‎packages/react/src/components/auth/Callback/__tests__/TokenCallback.test.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const thunderIDContext: ThunderIDContextProps = {
4242
isLoading: false,
4343
signIn: mockSignIn,
4444
signUp: mockSignUp,
45+
vendor: 'thunderid',
4546
} as unknown as ThunderIDContextProps;
4647

4748
describe('TokenCallback', () => {

0 commit comments

Comments
 (0)