Skip to content

Commit

Permalink
Properly memoize for HMR
Browse files Browse the repository at this point in the history
  • Loading branch information
garronej committed Oct 14, 2024
1 parent 4469f10 commit 026bc92
Showing 1 changed file with 80 additions and 51 deletions.
131 changes: 80 additions & 51 deletions src/oidc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,37 +202,15 @@ export type ParamsOfCreateOidc<
doEnableDebugLogs?: boolean;
};

// NOTE: This is not arbitrary, it matches what oidc-client-ts uses.
const SESSION_STORAGE_PREFIX = "oidc.";

let $isUserActive: StatefulObservable<boolean> | undefined = undefined;
const prOidcByConfigHash = new Map<string, Promise<Oidc<any>>>();

const URL_real = window.URL;

/** @see: https://docs.oidc-spa.dev/v/v5/documentation/usage */
export async function createOidc<
DecodedIdToken extends Record<string, unknown> = Record<string, unknown>,
IsAuthGloballyRequired extends boolean = false
>(
params: ParamsOfCreateOidc<DecodedIdToken, IsAuthGloballyRequired>
): Promise<IsAuthGloballyRequired extends true ? Oidc.LoggedIn<DecodedIdToken> : Oidc<DecodedIdToken>> {
const {
issuerUri,
clientId,
scopes = ["profile"],
transformUrlBeforeRedirect,
extraQueryParams: extraQueryParamsOrGetter,
extraTokenParams: extraTokenParamsOrGetter,
publicUrl: publicUrl_params,
decodedIdTokenSchema,
__unsafe_ssoSessionIdleSeconds,
autoLogoutParams = { "redirectTo": "current page" },
isAuthGloballyRequired = false,
postLoginRedirectUrl,
doEnableDebugLogs = false
} = params;

for (const name of ["issuerUri", "clientId"] as const) {
const value = params[name];
if (typeof value !== "string") {
Expand All @@ -242,6 +220,8 @@ export async function createOidc<
}
}

const { issuerUri, clientId, scopes = ["profile"], doEnableDebugLogs, ...rest } = params;

const log = (() => {
if (!doEnableDebugLogs) {
return undefined;
Expand All @@ -258,6 +238,84 @@ export async function createOidc<
});
})();

const configHash = fnv1aHashToHex(`${issuerUri} ${clientId} ${scopes.join(" ")}`);

use_previous_instance: {
const prOidc = prOidcByConfigHash.get(configHash);

if (prOidc === undefined) {
break use_previous_instance;
}

log?.(
[
`createOidc was called again with the same config (${JSON.stringify({
issuerUri,
clientId,
scopes
})})`,
`probably due to a hot module replacement. Returning the previous instance.`
].join(" ")
);

// @ts-expect-error: We know what we're doing
return prOidc;
}

const dOidc = new Deferred<Oidc<any>>();

prOidcByConfigHash.set(configHash, dOidc.pr);

const oidc = await createOidc_nonMemoized(rest, {
issuerUri,
clientId,
scopes,
configHash,
log
});

dOidc.resolve(oidc);

return oidc;
}

// NOTE: This is not arbitrary, it matches what oidc-client-ts uses.
const SESSION_STORAGE_PREFIX = "oidc.";

let $isUserActive: StatefulObservable<boolean> | undefined = undefined;

const URL_real = window.URL;

export async function createOidc_nonMemoized<
DecodedIdToken extends Record<string, unknown> = Record<string, unknown>,
IsAuthGloballyRequired extends boolean = false
>(
params: Omit<
ParamsOfCreateOidc<DecodedIdToken, IsAuthGloballyRequired>,
"issuerUri" | "clientId" | "scopes" | "doEnableDebugLogs"
>,
preProcessedParams: {
issuerUri: string;
clientId: string;
scopes: string[];
configHash: string;
log: typeof console.log | undefined;
}
): Promise<IsAuthGloballyRequired extends true ? Oidc.LoggedIn<DecodedIdToken> : Oidc<DecodedIdToken>> {
const {
transformUrlBeforeRedirect,
extraQueryParams: extraQueryParamsOrGetter,
extraTokenParams: extraTokenParamsOrGetter,
publicUrl: publicUrl_params,
decodedIdTokenSchema,
__unsafe_ssoSessionIdleSeconds,
autoLogoutParams = { "redirectTo": "current page" },
isAuthGloballyRequired = false,
postLoginRedirectUrl
} = params;

const { issuerUri, clientId, scopes, configHash, log } = preProcessedParams;

const [getExtraQueryParams, getExtraTokenParams] = (
[extraQueryParamsOrGetter, extraTokenParamsOrGetter] as const
).map(valueOrGetter => {
Expand All @@ -284,34 +342,8 @@ export async function createOidc<
).replace(/\/$/, "");
})();

const configHash = fnv1aHashToHex(`${issuerUri} ${clientId} ${scopes.join(" ")}`);

log?.(`Calling createOidc v${VERSION}`, { params, publicUrl, configHash });

use_previous_instance: {
const prOidc = prOidcByConfigHash.get(configHash);

if (prOidc === undefined) {
break use_previous_instance;
}

log?.("Using previous instance of oidc-spa");

console.warn(
[
`oidc-spa has been instantiated more than once with the same configuration.`,
`If you are in development mode with hot module replacement this is expected you can ignore this warning.`,
`In production however this is something that should be addressed.`
].join(" ")
);

return prOidc as any;
}

const dOidc = new Deferred<Oidc<any>>();

prOidcByConfigHash.set(configHash, dOidc.pr);

const silentSso =
publicUrl === undefined
? {
Expand Down Expand Up @@ -1316,7 +1348,6 @@ export async function createOidc<
initializationError
});

dOidc.resolve(oidc);
// @ts-expect-error: We know what we are doing.
return oidc;
}
Expand All @@ -1343,8 +1374,6 @@ export async function createOidc<
"initializationError": undefined
});

dOidc.resolve(oidc);

// @ts-expect-error: We know what we are doing.
return oidc;
}
Expand Down

0 comments on commit 026bc92

Please sign in to comment.