Skip to content

Commit b5d66ff

Browse files
authored
Merge pull request #149 from brionmario/next-sign-up-button
chore(react): add support for redirection based `SignUpButton` usage
2 parents 80ae17f + d2b69ae commit b5d66ff

28 files changed

+2266
-198
lines changed

.changeset/plenty-suits-repair.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@asgardeo/javascript': patch
3+
'@asgardeo/browser': patch
4+
'@asgardeo/react': patch
5+
---
6+
7+
Add support for redirection based `SignUpButton` usage

packages/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export * from './__legacy__/worker/worker-receiver';
4646
export {AsgardeoBrowserConfig} from './models/config';
4747

4848
export {default as hasAuthParamsInUrl} from './utils/hasAuthParamsInUrl';
49+
export {default as navigate} from './utils/navigate';
4950

5051
export {default as AsgardeoBrowserClient} from './AsgardeoBrowserClient';
5152

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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 {vi} from 'vitest';
20+
import navigate from '../navigate';
21+
22+
describe('navigate', () => {
23+
const originalLocation = window.location;
24+
25+
beforeEach(() => {
26+
// @ts-ignore
27+
window.history.pushState = vi.fn();
28+
// @ts-ignore
29+
window.dispatchEvent = vi.fn();
30+
// @ts-ignore
31+
window.location.assign = vi.fn();
32+
// @ts-ignore
33+
delete window.location;
34+
// @ts-ignore
35+
window.location = {
36+
...originalLocation,
37+
assign: vi.fn(),
38+
origin: 'https://localhost:5173',
39+
href: 'https://localhost:5173/',
40+
};
41+
});
42+
43+
afterEach(() => {
44+
vi.clearAllMocks();
45+
// @ts-ignore
46+
window.location = originalLocation;
47+
});
48+
49+
it('should call window.history.pushState with the correct arguments for same-origin', () => {
50+
navigate('/test-url');
51+
expect(window.history.pushState).toHaveBeenCalledWith(null, '', '/test-url');
52+
expect(window.location.assign).not.toHaveBeenCalled();
53+
});
54+
55+
it('should dispatch a PopStateEvent with state null for same-origin', () => {
56+
navigate('/test-url');
57+
expect(window.dispatchEvent).toHaveBeenCalledWith(
58+
expect.objectContaining({
59+
type: 'popstate',
60+
state: null,
61+
}),
62+
);
63+
expect(window.location.assign).not.toHaveBeenCalled();
64+
});
65+
66+
it('should use window.location.assign for cross-origin URLs', () => {
67+
const crossOriginUrl = 'https://accounts.asgardeo.io/t/dxlab/accountrecoveryendpoint/register.do';
68+
navigate(crossOriginUrl);
69+
expect(window.location.assign).toHaveBeenCalledWith(crossOriginUrl);
70+
expect(window.history.pushState).not.toHaveBeenCalled();
71+
expect(window.dispatchEvent).not.toHaveBeenCalled();
72+
});
73+
74+
it('should use window.location.assign for malformed URLs', () => {
75+
const malformedUrl = 'http://[::1'; // Invalid URL
76+
navigate(malformedUrl);
77+
expect(window.location.assign).toHaveBeenCalledWith(malformedUrl);
78+
expect(window.history.pushState).not.toHaveBeenCalled();
79+
expect(window.dispatchEvent).not.toHaveBeenCalled();
80+
});
81+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
/**
20+
* Navigates to a new URL within the browser.
21+
*
22+
* - For same-origin URLs (relative paths or absolute URLs with the same origin),
23+
* uses the History API and dispatches a `popstate` event (SPA navigation).
24+
* - For cross-origin URLs, performs a full page load using `window.location.assign`.
25+
*
26+
* This allows seamless navigation for both SPA routes and external links.
27+
*
28+
* @param url - The target URL to navigate to. Can be a path, query, or absolute URL.
29+
*
30+
* @example
31+
* ```typescript
32+
* // SPA navigation (same origin)
33+
* navigate('/dashboard');
34+
*
35+
* // SPA navigation with query
36+
* navigate('/search?q=asgardeo');
37+
*
38+
* // Cross-origin navigation (full page load)
39+
* navigate('https://accounts.asgardeo.io/t/dxlab/accountrecoveryendpoint/register.do');
40+
* ```
41+
*/
42+
const navigate = (url: string): void => {
43+
try {
44+
const targetUrl = new URL(url, window.location.origin);
45+
if (targetUrl.origin === window.location.origin) {
46+
window.history.pushState(null, '', targetUrl.pathname + targetUrl.search + targetUrl.hash);
47+
window.dispatchEvent(new PopStateEvent('popstate', {state: null}));
48+
} else {
49+
window.location.assign(targetUrl.href);
50+
}
51+
} catch {
52+
// If URL constructor fails (e.g., malformed URL), fallback to location.assign
53+
window.location.assign(url);
54+
}
55+
};
56+
57+
export default navigate;

packages/browser/tsconfig.spec.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
"compilerOptions": {
44
"outDir": "dist",
55
"module": "commonjs",
6-
"types": ["jest", "node"]
6+
"types": ["vitest/globals"]
77
},
88
"include": [
99
"test-configs",
10-
"jest.config.js",
10+
"vitest.config.ts",
1111
"**/*.test.ts",
1212
"**/*.spec.ts",
1313
"**/*.test.js",

packages/browser/vitest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ export default defineConfig({
2626
instances: [{browser: 'chromium'}],
2727
provider: 'playwright',
2828
},
29+
globals: true,
2930
},
3031
});

packages/javascript/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"typecheck": "tsc -p tsconfig.lib.json"
4343
},
4444
"devDependencies": {
45-
"@types/node": "^22.15.3",
45+
"@types/node": "^22.15.30",
4646
"@wso2/eslint-plugin": "catalog:",
4747
"@wso2/prettier-config": "catalog:",
4848
"esbuild": "^0.25.9",

packages/javascript/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Platform } from './models/platforms';
12
/**
23
* Copyright (c) 2020, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
34
*
@@ -57,6 +58,7 @@ export {default as AsgardeoRuntimeError} from './errors/AsgardeoRuntimeError';
5758
export {AsgardeoAuthException} from './errors/exception';
5859

5960
export {AllOrganizationsApiResponse} from './models/organization';
61+
export {Platform} from './models/platforms';
6062
export {
6163
EmbeddedSignInFlowInitiateResponse,
6264
EmbeddedSignInFlowStatus,
@@ -135,12 +137,15 @@ export {default as processUsername} from './utils/processUsername';
135137
export {default as deepMerge} from './utils/deepMerge';
136138
export {default as deriveOrganizationHandleFromBaseUrl} from './utils/deriveOrganizationHandleFromBaseUrl';
137139
export {default as extractUserClaimsFromIdToken} from './utils/extractUserClaimsFromIdToken';
140+
export {default as isRecognizedBaseUrlPattern} from './utils/isRecognizedBaseUrlPattern';
138141
export {default as extractPkceStorageKeyFromState} from './utils/extractPkceStorageKeyFromState';
139142
export {default as flattenUserSchema} from './utils/flattenUserSchema';
140143
export {default as generateUserProfile} from './utils/generateUserProfile';
141144
export {default as getLatestStateParam} from './utils/getLatestStateParam';
142145
export {default as generateFlattenedUserProfile} from './utils/generateFlattenedUserProfile';
146+
export {default as getRedirectBasedSignUpUrl} from './utils/getRedirectBasedSignUpUrl';
143147
export {default as getI18nBundles} from './utils/getI18nBundles';
148+
export {default as identifyPlatform} from './utils/identifyPlatform';
144149
export {default as isEmpty} from './utils/isEmpty';
145150
export {default as set} from './utils/set';
146151
export {default as get} from './utils/get';
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
/**
20+
* Enumeration of supported identity platforms.
21+
*
22+
* - `Asgardeo`: Represents the Asgardeo cloud identity platform (asgardeo.io domains).
23+
* - `IdentityServer`: Represents WSO2 Identity Server (on-prem or custom domains).
24+
* - `Unknown`: Used when the platform cannot be determined from the configuration.
25+
*
26+
* This enum is used to distinguish between different identity providers and to
27+
* enable platform-specific logic throughout the SDK.
28+
*/
29+
export enum Platform {
30+
/** Asgardeo cloud identity platform (asgardeo.io domains) */
31+
Asgardeo = 'ASGARDEO',
32+
/** WSO2 Identity Server (on-prem or custom domains) */
33+
IdentityServer = 'IDENTITY_SERVER',
34+
/** Unknown or unsupported platform */
35+
Unknown = 'UNKNOWN',
36+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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, afterEach} from 'vitest';
20+
import {Config} from '../../models/config';
21+
import isRecognizedBaseUrlPattern from '../isRecognizedBaseUrlPattern';
22+
import getRedirectBasedSignUpUrl from '../getRedirectBasedSignUpUrl';
23+
24+
vi.mock('../isRecognizedBaseUrlPattern', () => ({default: vi.fn()}));
25+
26+
describe('getRedirectBasedSignUpUrl', () => {
27+
const baseUrl: string = 'https://api.asgardeo.io/t/org';
28+
const expectedBaseUrl: string = 'https://accounts.asgardeo.io/t/org';
29+
const clientId: string = 'client123';
30+
const applicationId: string = 'app456';
31+
32+
afterEach(() => {
33+
vi.clearAllMocks();
34+
});
35+
36+
it('returns the correct sign-up URL if baseUrl is recognized and both params are present', () => {
37+
(isRecognizedBaseUrlPattern as unknown as ReturnType<typeof vi.fn>).mockReturnValue(true);
38+
const config: Config = {baseUrl, clientId, applicationId};
39+
const url: URL = new URL(expectedBaseUrl + '/accountrecoveryendpoint/register.do');
40+
url.searchParams.set('client_id', clientId);
41+
url.searchParams.set('spId', applicationId);
42+
expect(getRedirectBasedSignUpUrl(config)).toBe(url.toString());
43+
});
44+
45+
it('returns the correct sign-up URL if only clientId is present', () => {
46+
(isRecognizedBaseUrlPattern as unknown as ReturnType<typeof vi.fn>).mockReturnValue(true);
47+
const config: Config = {baseUrl, clientId};
48+
const url: URL = new URL(expectedBaseUrl + '/accountrecoveryendpoint/register.do');
49+
url.searchParams.set('client_id', clientId);
50+
expect(getRedirectBasedSignUpUrl(config)).toBe(url.toString());
51+
});
52+
53+
it('returns the correct sign-up URL if only applicationId is present', () => {
54+
(isRecognizedBaseUrlPattern as unknown as ReturnType<typeof vi.fn>).mockReturnValue(true);
55+
const config: Config = {baseUrl, applicationId, clientId: ''};
56+
const url: URL = new URL(expectedBaseUrl + '/accountrecoveryendpoint/register.do');
57+
url.searchParams.set('spId', applicationId);
58+
expect(getRedirectBasedSignUpUrl(config)).toBe(url.toString());
59+
});
60+
61+
it('returns the correct sign-up URL if neither param is present', () => {
62+
(isRecognizedBaseUrlPattern as unknown as ReturnType<typeof vi.fn>).mockReturnValue(true);
63+
const config: Config = {baseUrl, clientId: ''};
64+
const url: URL = new URL(expectedBaseUrl + '/accountrecoveryendpoint/register.do');
65+
expect(getRedirectBasedSignUpUrl(config)).toBe(url.toString());
66+
});
67+
68+
it('returns empty string if baseUrl is not recognized', () => {
69+
(isRecognizedBaseUrlPattern as unknown as ReturnType<typeof vi.fn>).mockReturnValue(false);
70+
const config: Config = {baseUrl, clientId, applicationId};
71+
expect(getRedirectBasedSignUpUrl(config)).toBe('');
72+
});
73+
});

0 commit comments

Comments
 (0)