Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ env:
jobs:
release:
name: 📦 Release
if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')"
if:
"!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')"
runs-on: ubuntu-latest
strategy:
matrix:
Expand Down Expand Up @@ -60,7 +61,7 @@ jobs:
id: changesets
uses: changesets/action@v1
with:
title: "[Release] [GitHub Action] Update package versions"
title: '[Release] [GitHub Action] Update package versions'
publish: pnpm publish:packages
version: pnpm version:packages
commit: "[WSO2 Release] [GitHub Action] [Release] [skip ci] update package versions"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@
"publishConfig": {
"access": "restricted"
}
}
}
73 changes: 40 additions & 33 deletions packages/react/src/AsgardeoReactClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,37 +125,41 @@ class AsgardeoReactClient<T extends AsgardeoReactConfig = AsgardeoReactConfig> e
}

async getDecodedIdToken(sessionId?: string): Promise<IdToken> {
return this.asgardeo.getDecodedIdToken(sessionId);
return this.withLoading(async () => {
return this.asgardeo.getDecodedIdToken(sessionId);
});
}

async getUserProfile(options?: any): Promise<UserProfile> {
try {
let baseUrl = options?.baseUrl;
return this.withLoading(async () => {
try {
let baseUrl = options?.baseUrl;

if (!baseUrl) {
const configData = await this.asgardeo.getConfigData();
baseUrl = configData?.baseUrl;
}
if (!baseUrl) {
const configData = await this.asgardeo.getConfigData();
baseUrl = configData?.baseUrl;
}

const profile = await getScim2Me({baseUrl});
const schemas = await getSchemas({baseUrl});
const profile = await getScim2Me({baseUrl});
const schemas = await getSchemas({baseUrl});

const processedSchemas = flattenUserSchema(schemas);
const processedSchemas = flattenUserSchema(schemas);

const output = {
schemas: processedSchemas,
flattenedProfile: generateFlattenedUserProfile(profile, processedSchemas),
profile,
};
const output = {
schemas: processedSchemas,
flattenedProfile: generateFlattenedUserProfile(profile, processedSchemas),
profile,
};

return output;
} catch (error) {
return {
schemas: [],
flattenedProfile: extractUserClaimsFromIdToken(await this.getDecodedIdToken()),
profile: extractUserClaimsFromIdToken(await this.getDecodedIdToken()),
};
}
return output;
} catch (error) {
return {
schemas: [],
flattenedProfile: extractUserClaimsFromIdToken(await this.getDecodedIdToken()),
profile: extractUserClaimsFromIdToken(await this.getDecodedIdToken()),
};
}
});
}

override async getMyOrganizations(options?: any, sessionId?: string): Promise<Organization[]> {
Expand Down Expand Up @@ -201,13 +205,14 @@ class AsgardeoReactClient<T extends AsgardeoReactConfig = AsgardeoReactConfig> e
}

override async getCurrentOrganization(): Promise<Organization | null> {
const idToken: IdToken = await this.getDecodedIdToken();

return {
orgHandle: idToken?.org_handle,
name: idToken?.org_name,
id: idToken?.org_id,
};
return this.withLoading(async () => {
const idToken: IdToken = await this.getDecodedIdToken();
return {
orgHandle: idToken?.org_handle,
name: idToken?.org_name,
id: idToken?.org_id,
};
});
}

override async switchOrganization(organization: Organization, sessionId?: string): Promise<TokenResponse | Response> {
Expand Down Expand Up @@ -258,8 +263,8 @@ class AsgardeoReactClient<T extends AsgardeoReactConfig = AsgardeoReactConfig> e
return this.asgardeo.isInitialized();
}

override isSignedIn(): Promise<boolean> {
return this.asgardeo.isSignedIn();
override async isSignedIn(): Promise<boolean> {
return await this.asgardeo.isSignedIn();
}

override getConfiguration(): T {
Expand Down Expand Up @@ -355,7 +360,9 @@ class AsgardeoReactClient<T extends AsgardeoReactConfig = AsgardeoReactConfig> e
}

override async getAccessToken(sessionId?: string): Promise<string> {
return this.asgardeo.getAccessToken(sessionId);
return this.withLoading(async () => {
return this.asgardeo.getAccessToken(sessionId);
});
}
}

Expand Down
63 changes: 51 additions & 12 deletions packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
getBrandingPreference,
GetBrandingPreferenceConfig,
BrandingPreference,
IdToken,
} from '@asgardeo/browser';
import {FC, RefObject, PropsWithChildren, ReactElement, useEffect, useMemo, useRef, useState, useCallback} from 'react';
import AsgardeoContext from './AsgardeoContext';
Expand Down Expand Up @@ -88,6 +89,8 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
...rest,
});

const [isUpdatingSession, setIsUpdatingSession] = useState<boolean>(false);

// Branding state
const [brandingPreference, setBrandingPreference] = useState<BrandingPreference | null>(null);
const [isBrandingLoading, setIsBrandingLoading] = useState<boolean>(false);
Expand Down Expand Up @@ -129,27 +132,32 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({

(async (): Promise<void> => {
// User is already authenticated. Skip...
if (await asgardeo.isSignedIn()) {
await updateSession();
const isAlreadySignedIn: boolean = await asgardeo.isSignedIn();

if (isAlreadySignedIn) {
await updateSession();
return;
}

if (hasAuthParams(new URL(window.location.href), afterSignInUrl)) {
const currentUrl: URL = new URL(window.location.href);
const hasAuthParamsResult: boolean = hasAuthParams(currentUrl, afterSignInUrl);

if (hasAuthParamsResult) {
try {
await signIn(
{callOnlyOnRedirect: true},
// authParams?.authorizationCode,
// authParams?.sessionState,
// authParams?.state,
);

// setError(null);
} catch (error) {
if (error && Object.prototype.hasOwnProperty.call(error, 'code')) {
// setError(error);
}
}
} else {
// TODO: Add a debug log to indicate that the user is not signed in
}
})();
}, []);
Expand Down Expand Up @@ -177,6 +185,8 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
clearInterval(interval);
}
}, 1000);
} else {
// TODO: Add a debug log to indicate that the user is already signed in.
}
} catch (error) {
setIsSignedInSync(false);
Expand Down Expand Up @@ -207,8 +217,12 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
*/
useEffect(() => {
const checkLoadingState = (): void => {
const loadingState = asgardeo.isLoading();
setIsLoadingSync(loadingState);
// Don't override loading state during critical session updates
if (isUpdatingSession) {
return;
}

setIsLoadingSync(asgardeo.isLoading());
};

// Initial check
Expand All @@ -220,25 +234,44 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
return (): void => {
clearInterval(interval);
};
}, [asgardeo]);
}, [asgardeo, isLoadingSync, isSignedInSync, isUpdatingSession]);

const updateSession = async (): Promise<void> => {
try {
// Set flag to prevent loading state tracking from interfering
setIsUpdatingSession(true);
setIsLoadingSync(true);
let _baseUrl: string = baseUrl;

const decodedToken: IdToken = await asgardeo.getDecodedIdToken();

// If there's a `user_org` claim in the ID token,
// Treat this login as a organization login.
if ((await asgardeo.getDecodedIdToken())?.['user_org']) {
if (decodedToken?.['user_org']) {
_baseUrl = `${(await asgardeo.getConfiguration()).baseUrl}/o`;
setBaseUrl(_baseUrl);
}

setUser(await asgardeo.getUser({baseUrl: _baseUrl}));
setUserProfile(await asgardeo.getUserProfile({baseUrl: _baseUrl}));
setCurrentOrganization(await asgardeo.getCurrentOrganization());
setMyOrganizations(await asgardeo.getMyOrganizations());
const user: User = await asgardeo.getUser({baseUrl: _baseUrl});
const userProfile: UserProfile = await asgardeo.getUserProfile({baseUrl: _baseUrl});
const currentOrganization: Organization = await asgardeo.getCurrentOrganization();
const myOrganizations: Organization[] = await asgardeo.getMyOrganizations();

// Update user data first
setUser(user);
setUserProfile(userProfile);
setCurrentOrganization(currentOrganization);
setMyOrganizations(myOrganizations);

// CRITICAL: Update sign-in status BEFORE setting loading to false
// This prevents the race condition where ProtectedRoute sees isLoading=false but isSignedIn=false
const currentSignInStatus = await asgardeo.isSignedIn();
setIsSignedInSync(await asgardeo.isSignedIn());
} catch (error) {
// TODO: Add an error log.
} finally {
// Clear the flag and set final loading state
setIsUpdatingSession(false);
setIsLoadingSync(asgardeo.isLoading());
}
};
Expand Down Expand Up @@ -302,6 +335,7 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({

const signIn = async (...args: any): Promise<User> => {
try {
setIsUpdatingSession(true);
setIsLoadingSync(true);
const response: User = await asgardeo.signIn(...args);

Expand All @@ -313,12 +347,14 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
} catch (error) {
throw new Error(`Error while signing in: ${error}`);
} finally {
setIsUpdatingSession(false);
setIsLoadingSync(asgardeo.isLoading());
}
};

const signInSilently = async (options?: SignInOptions): Promise<User | boolean> => {
try {
setIsUpdatingSession(true);
setIsLoadingSync(true);
const response: User | boolean = await asgardeo.signInSilently(options);

Expand All @@ -335,12 +371,14 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
'An error occurred while trying to sign in silently.',
);
} finally {
setIsUpdatingSession(false);
setIsLoadingSync(asgardeo.isLoading());
}
};

const switchOrganization = async (organization: Organization): Promise<void> => {
try {
setIsUpdatingSession(true);
setIsLoadingSync(true);
await asgardeo.switchOrganization(organization);

Expand All @@ -355,6 +393,7 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
'An error occurred while switching to the specified organization.',
);
} finally {
setIsUpdatingSession(false);
setIsLoadingSync(asgardeo.isLoading());
}
};
Expand Down
31 changes: 7 additions & 24 deletions scripts/aggregate-changelogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,35 +26,19 @@ const path = require('path');
const rootDir = process.cwd();
const outputFile = path.join(rootDir, 'CHANGELOG.md');


// List of package or directory names to skip when aggregating changelogs
const SKIP_PACKAGES = [
'__legacy__',
'node_modules',
'dist',
'build',
'coverage',
'scripts',
'docs',
];
const SKIP_PACKAGES = ['__legacy__', 'node_modules', 'dist', 'build', 'coverage', 'scripts', 'docs'];

const findChangelogs = (dir) => {
const findChangelogs = dir => {
let changelogs = [];
const entries = fs.readdirSync(dir, { withFileTypes: true });
const entries = fs.readdirSync(dir, {withFileTypes: true});

for (const entry of entries) {
const fullPath = path.join(dir, entry.name);

if (
entry.isDirectory() &&
!SKIP_PACKAGES.includes(entry.name)
) {
if (entry.isDirectory() && !SKIP_PACKAGES.includes(entry.name)) {
changelogs = changelogs.concat(findChangelogs(fullPath));
} else if (
entry.isFile() &&
entry.name === 'CHANGELOG.md' &&
fullPath !== outputFile
) {
} else if (entry.isFile() && entry.name === 'CHANGELOG.md' && fullPath !== outputFile) {
// Check if the changelog is inside a skipped package
const relPath = path.relative(rootDir, fullPath);
const parts = relPath.split(path.sep);
Expand All @@ -65,8 +49,7 @@ const findChangelogs = (dir) => {
}

return changelogs;
}

};

const aggregate = () => {
const changelogFiles = findChangelogs(rootDir);
Expand All @@ -93,6 +76,6 @@ const aggregate = () => {
fs.writeFileSync(outputFile, toc + output);

console.log(`Aggregated ${changelogFiles.length} changelogs into ${outputFile}`);
}
};

aggregate();
Loading