@nhost/nhost-js
Version:
Nhost JavaScript SDK
120 lines • 4.61 kB
JavaScript
class DummyLock {
async request(_name, _options,
// biome-ignore lint/suspicious/noExplicitAny: any
callback) {
return callback();
}
}
const lock = typeof navigator !== 'undefined' && navigator.locks
? navigator.locks
: new DummyLock();
/**
* Refreshes the authentication session if needed
*
* This function checks if the current session needs to be refreshed based on
* the access token expiration time. If a refresh is needed, it will attempt to
* refresh the token using the provided auth client.
*
* @param auth - The authentication client to use for token refresh
* @param storage - The session storage implementation
* @param marginSeconds - The number of seconds before the token expiration to refresh the session. If the token is still valid for this duration, it will not be refreshed. Set to 0 to force the refresh.
* @returns A promise that resolves to the current session (refreshed if needed) or null if no session exists
*/
export const refreshSession = async (auth, storage, marginSeconds = 60) => {
try {
return await _refreshSession(auth, storage, marginSeconds);
}
catch (error) {
try {
// we retry the refresh token in case of transient error
// or race conditions
console.warn('error refreshing session, retrying:', error);
return await _refreshSession(auth, storage, marginSeconds);
}
catch (error) {
const errResponse = error;
if (errResponse?.status === 401) {
// this probably means the refresh token is invalid
console.error('session probably expired');
storage.remove();
}
return null;
}
}
};
/**
* Internal implementation of the refresh session logic
*
* @param auth - The authentication client to use for token refresh
* @param storage - The session storage implementation
* @param marginSeconds - How many seconds before expiration to trigger a refresh
* @returns A promise that resolves to the current session (refreshed if needed) or null if no session exists
* @private
*/
const _refreshSession = async (auth, storage, marginSeconds = 60) => {
const { session, needsRefresh, } = await lock.request('nhostSessionLock', { mode: 'shared' }, async () => {
return _needsRefresh(storage, marginSeconds);
});
if (!session) {
return null; // No session found
}
if (!needsRefresh) {
return session; // No need to refresh
}
const refreshedSession = await lock.request('nhostSessionLock', { mode: 'exclusive' }, async () => {
const { session, needsRefresh, sessionExpired } = _needsRefresh(storage, marginSeconds);
if (!session) {
return null; // No session found
}
if (!needsRefresh) {
return session; // No need to refresh
}
try {
const response = await auth.refreshToken({
refreshToken: session.refreshToken,
});
storage.set(response.body);
return response.body;
}
catch (error) {
if (!sessionExpired) {
return session;
}
throw error;
}
});
return refreshedSession;
};
/**
* Checks if the current session needs to be refreshed based on token expiration
*
* @param storage - The session storage implementation
* @param marginSeconds - How many seconds before expiration to trigger a refresh
* @returns An object containing the session, whether it needs refreshing, and whether it has expired
* @private
*/
const _needsRefresh = (storage, marginSeconds = 60) => {
const session = storage.get();
if (!session) {
return { session: null, needsRefresh: false, sessionExpired: false };
}
if (!session.decodedToken?.exp) {
// if the session does not have a valid decoded token, treat it as expired
// as we can't determine its validity
return { session, needsRefresh: true, sessionExpired: true };
}
// Force refresh if marginSeconds is 0
if (marginSeconds === 0) {
return { session, needsRefresh: true, sessionExpired: false };
}
const currentTime = Date.now();
if (session.decodedToken.exp - currentTime > marginSeconds * 1000) {
return { session, needsRefresh: false, sessionExpired: false };
}
return {
session,
needsRefresh: true,
sessionExpired: session.decodedToken.exp < currentTime,
};
};
//# sourceMappingURL=refreshSession.js.map