Skip to content

Commit 9bd7a1c

Browse files
authored
Merge pull request #75 from brionmario/next
2 parents d0b0e03 + 9d5eca7 commit 9bd7a1c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1559
-556
lines changed

.changeset/metal-colts-lose.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@asgardeo/nextjs': patch
3+
---
4+
5+
Stabilize the SDK

packages/javascript/src/AsgardeoJavaScriptClient.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ abstract class AsgardeoJavaScriptClient<T = Config> implements AsgardeoClient<T>
4646

4747
abstract isSignedIn(): Promise<boolean>;
4848

49+
abstract getConfiguration(): T;
50+
4951
abstract signIn(
5052
options?: SignInOptions,
5153
sessionId?: string,
@@ -58,11 +60,11 @@ abstract class AsgardeoJavaScriptClient<T = Config> implements AsgardeoClient<T>
5860
onSignInSuccess?: (afterSignInUrl: string) => void,
5961
): Promise<User>;
6062

61-
abstract signOut(options?: SignOutOptions, afterSignOut?: (redirectUrl: string) => void): Promise<string>;
63+
abstract signOut(options?: SignOutOptions, afterSignOut?: (afterSignOutUrl: string) => void): Promise<string>;
6264
abstract signOut(
6365
options?: SignOutOptions,
6466
sessionId?: string,
65-
afterSignOut?: (redirectUrl: string) => void,
67+
afterSignOut?: (afterSignOutUrl: string) => void,
6668
): Promise<string>;
6769

6870
abstract signUp(options?: SignUpOptions): Promise<void>;
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* Copyright (c) 2025, 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 getScim2Me from '../getScim2Me';
21+
import AsgardeoAPIError from '../../../errors/AsgardeoAPIError';
22+
23+
// Mock user data
24+
const mockUser = {
25+
id: '123',
26+
username: 'testuser',
27+
email: 'test@example.com',
28+
givenName: 'Test',
29+
familyName: 'User',
30+
};
31+
32+
describe('getScim2Me', () => {
33+
it('should fetch user profile successfully with default fetch', async () => {
34+
// Mock fetch
35+
const mockFetch = vi.fn().mockResolvedValue({
36+
ok: true,
37+
status: 200,
38+
statusText: 'OK',
39+
json: () => Promise.resolve(mockUser),
40+
text: () => Promise.resolve(JSON.stringify(mockUser)),
41+
});
42+
43+
// Replace global fetch
44+
global.fetch = mockFetch;
45+
46+
const result = await getScim2Me({
47+
url: 'https://api.asgardeo.io/t/test/scim2/Me',
48+
});
49+
50+
expect(result).toEqual(mockUser);
51+
expect(mockFetch).toHaveBeenCalledWith('https://api.asgardeo.io/t/test/scim2/Me', {
52+
method: 'GET',
53+
headers: {
54+
'Content-Type': 'application/scim+json',
55+
Accept: 'application/json',
56+
},
57+
});
58+
});
59+
60+
it('should use custom fetcher when provided', async () => {
61+
const customFetcher = vi.fn().mockResolvedValue({
62+
ok: true,
63+
status: 200,
64+
statusText: 'OK',
65+
json: () => Promise.resolve(mockUser),
66+
text: () => Promise.resolve(JSON.stringify(mockUser)),
67+
});
68+
69+
const result = await getScim2Me({
70+
url: 'https://api.asgardeo.io/t/test/scim2/Me',
71+
fetcher: customFetcher,
72+
});
73+
74+
expect(result).toEqual(mockUser);
75+
expect(customFetcher).toHaveBeenCalledWith('https://api.asgardeo.io/t/test/scim2/Me', {
76+
method: 'GET',
77+
headers: {
78+
'Content-Type': 'application/scim+json',
79+
Accept: 'application/json',
80+
},
81+
});
82+
});
83+
84+
it('should throw AsgardeoAPIError for invalid URL', async () => {
85+
await expect(
86+
getScim2Me({
87+
url: 'invalid-url',
88+
}),
89+
).rejects.toThrow(AsgardeoAPIError);
90+
});
91+
92+
it('should throw AsgardeoAPIError for failed response', async () => {
93+
const mockFetch = vi.fn().mockResolvedValue({
94+
ok: false,
95+
status: 404,
96+
statusText: 'Not Found',
97+
text: () => Promise.resolve('User not found'),
98+
});
99+
100+
global.fetch = mockFetch;
101+
102+
await expect(
103+
getScim2Me({
104+
url: 'https://api.asgardeo.io/t/test/scim2/Me',
105+
}),
106+
).rejects.toThrow(AsgardeoAPIError);
107+
});
108+
109+
it('should handle network errors', async () => {
110+
const mockFetch = vi.fn().mockRejectedValue(new Error('Network error'));
111+
112+
global.fetch = mockFetch;
113+
114+
await expect(
115+
getScim2Me({
116+
url: 'https://api.asgardeo.io/t/test/scim2/Me',
117+
}),
118+
).rejects.toThrow(AsgardeoAPIError);
119+
});
120+
});
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/**
2+
* Copyright (c) 2025, 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 {User} from '../models/user';
20+
import AsgardeoAPIError from '../errors/AsgardeoAPIError';
21+
22+
/**
23+
* Configuration for the getScim2Me request
24+
*/
25+
export interface GetScim2MeConfig extends Omit<RequestInit, 'method'> {
26+
/**
27+
* The absolute API endpoint.
28+
*/
29+
url?: string;
30+
/**
31+
* The base path of the API endpoint.
32+
*/
33+
baseUrl?: string;
34+
/**
35+
* Optional custom fetcher function.
36+
* If not provided, native fetch will be used
37+
*/
38+
fetcher?: (url: string, config: RequestInit) => Promise<Response>;
39+
}
40+
41+
/**
42+
* Retrieves the user profile information from the specified SCIM2 /Me endpoint.
43+
*
44+
* @param config - Request configuration object.
45+
* @returns A promise that resolves with the user profile information.
46+
* @example
47+
* ```typescript
48+
* // Using default fetch
49+
* try {
50+
* const userProfile = await getScim2Me({
51+
* url: "https://api.asgardeo.io/t/<ORGANIZATION>/scim2/Me",
52+
* });
53+
* console.log(userProfile);
54+
* } catch (error) {
55+
* if (error instanceof AsgardeoAPIError) {
56+
* console.error('Failed to get user profile:', error.message);
57+
* }
58+
* }
59+
* ```
60+
*
61+
* @example
62+
* ```typescript
63+
* // Using custom fetcher (e.g., axios-based httpClient)
64+
* try {
65+
* const userProfile = await getScim2Me({
66+
* url: "https://api.asgardeo.io/t/<ORGANIZATION>/scim2/Me",
67+
* fetcher: async (url, config) => {
68+
* const response = await httpClient({
69+
* url,
70+
* method: config.method,
71+
* headers: config.headers,
72+
* ...config
73+
* });
74+
* // Convert axios-like response to fetch-like Response
75+
* return {
76+
* ok: response.status >= 200 && response.status < 300,
77+
* status: response.status,
78+
* statusText: response.statusText,
79+
* json: () => Promise.resolve(response.data),
80+
* text: () => Promise.resolve(typeof response.data === 'string' ? response.data : JSON.stringify(response.data))
81+
* } as Response;
82+
* }
83+
* });
84+
* console.log(userProfile);
85+
* } catch (error) {
86+
* if (error instanceof AsgardeoAPIError) {
87+
* console.error('Failed to get user profile:', error.message);
88+
* }
89+
* }
90+
* ```
91+
*/
92+
const getScim2Me = async ({url, baseUrl, fetcher, ...requestConfig}: GetScim2MeConfig): Promise<User> => {
93+
try {
94+
new URL(url ?? baseUrl);
95+
} catch (error) {
96+
throw new AsgardeoAPIError(
97+
`Invalid URL provided. ${error?.toString()}`,
98+
'getScim2Me-ValidationError-001',
99+
'javascript',
100+
400,
101+
'The provided `url` or `baseUrl` path does not adhere to the URL schema.',
102+
);
103+
}
104+
105+
const fetchFn = fetcher || fetch;
106+
const resolvedUrl: string = url ?? `${baseUrl}/scim2/Me`
107+
108+
const requestInit: RequestInit = {
109+
method: 'GET',
110+
headers: {
111+
'Content-Type': 'application/scim+json',
112+
Accept: 'application/json',
113+
...requestConfig.headers,
114+
},
115+
...requestConfig,
116+
};
117+
118+
try {
119+
const response: Response = await fetchFn(resolvedUrl, requestInit);
120+
121+
if (!response?.ok) {
122+
const errorText = await response.text();
123+
124+
throw new AsgardeoAPIError(
125+
`Failed to fetch user profile: ${errorText}`,
126+
'getScim2Me-ResponseError-001',
127+
'javascript',
128+
response.status,
129+
response.statusText,
130+
);
131+
}
132+
133+
return (await response.json()) as User;
134+
} catch (error) {
135+
if (error instanceof AsgardeoAPIError) {
136+
throw error;
137+
}
138+
139+
throw new AsgardeoAPIError(
140+
`Network or parsing error: ${error instanceof Error ? error.message : 'Unknown error'}`,
141+
'getScim2Me-NetworkError-001',
142+
'javascript',
143+
0,
144+
'Network Error',
145+
);
146+
}
147+
};
148+
149+
export default getScim2Me;

packages/javascript/src/api/scim2/__tests__/getMeProfile.test.ts

Whitespace-only changes.

packages/javascript/src/api/scim2/getMeProfile.ts

Whitespace-only changes.

packages/javascript/src/api/scim2/index.ts

Whitespace-only changes.

packages/javascript/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export {default as initializeEmbeddedSignInFlow} from './api/initializeEmbeddedS
2525
export {default as executeEmbeddedSignInFlow} from './api/executeEmbeddedSignInFlow';
2626
export {default as executeEmbeddedSignUpFlow} from './api/executeEmbeddedSignUpFlow';
2727
export {default as getUserInfo} from './api/getUserInfo';
28+
export {default as getScim2Me, GetScim2MeConfig} from './api/getScim2Me';
2829

2930
export {default as ApplicationNativeAuthenticationConstants} from './constants/ApplicationNativeAuthenticationConstants';
3031
export {default as TokenConstants} from './constants/TokenConstants';

packages/javascript/src/models/client.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ export interface AsgardeoClient<T> {
5757
*/
5858
switchOrganization(organization: Organization): Promise<void>;
5959

60+
getConfiguration(): T;
61+
6062
/**
6163
* Gets user information from the session.
6264
*
@@ -132,7 +134,7 @@ export interface AsgardeoClient<T> {
132134
* @param afterSignOut - Callback function to be executed after sign-out is complete.
133135
* @returns A promise that resolves to true if sign-out is successful
134136
*/
135-
signOut(options?: SignOutOptions, afterSignOut?: (redirectUrl: string) => void): Promise<string>;
137+
signOut(options?: SignOutOptions, afterSignOut?: (afterSignOutUrl: string) => void): Promise<string>;
136138

137139
/**
138140
* Signs out the currently signed-in user with an optional session ID.
@@ -143,7 +145,7 @@ export interface AsgardeoClient<T> {
143145
* @param afterSignOut - Callback function to be executed after sign-out is complete.
144146
* @returns A promise that resolves to true if sign-out is successful
145147
*/
146-
signOut(options?: SignOutOptions, sessionId?: string, afterSignOut?: (redirectUrl: string) => void): Promise<string>;
148+
signOut(options?: SignOutOptions, sessionId?: string, afterSignOut?: (afterSignOutUrl: string) => void): Promise<string>;
147149

148150
/**
149151
* Initiates a redirection-based sign-up process for the user.

packages/javascript/src/models/config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ export interface BaseConfig<T = unknown> extends WithPreferences {
7676
* scopes: ["openid", "profile", "email"]
7777
*/
7878
scopes?: string | string[] | undefined;
79+
80+
/**
81+
* Optional URL to redirect the user to sign-in.
82+
* By default, this will be the sign-in page of Asgardeo.
83+
* If you want to use a custom sign-in page, you can provide the URL here and use the `SignIn` component to render it.
84+
*/
85+
signInUrl?: string | undefined;
86+
87+
/**
88+
* Optional URL to redirect the user to sign-up.
89+
* By default, this will be the sign-up page of Asgardeo.
90+
* If you want to use a custom sign-up page, you can provide the URL here
91+
* and use the `SignUp` component to render it.
92+
*/
93+
signUpUrl?: string | undefined;
7994
}
8095

8196
export interface WithPreferences {

0 commit comments

Comments
 (0)