From 026bc92d1c7b3728b2bf8181af0991733d99ce6a Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Mon, 14 Oct 2024 15:58:25 +0200 Subject: [PATCH] Properly memoize for HMR --- src/oidc.ts | 131 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 80 insertions(+), 51 deletions(-) diff --git a/src/oidc.ts b/src/oidc.ts index 4c5f608..c6d6073 100644 --- a/src/oidc.ts +++ b/src/oidc.ts @@ -202,14 +202,8 @@ 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 | undefined = undefined; const prOidcByConfigHash = new Map>>(); -const URL_real = window.URL; - /** @see: https://docs.oidc-spa.dev/v/v5/documentation/usage */ export async function createOidc< DecodedIdToken extends Record = Record, @@ -217,22 +211,6 @@ export async function createOidc< >( params: ParamsOfCreateOidc ): Promise : Oidc> { - 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") { @@ -242,6 +220,8 @@ export async function createOidc< } } + const { issuerUri, clientId, scopes = ["profile"], doEnableDebugLogs, ...rest } = params; + const log = (() => { if (!doEnableDebugLogs) { return undefined; @@ -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>(); + + 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 | undefined = undefined; + +const URL_real = window.URL; + +export async function createOidc_nonMemoized< + DecodedIdToken extends Record = Record, + IsAuthGloballyRequired extends boolean = false +>( + params: Omit< + ParamsOfCreateOidc, + "issuerUri" | "clientId" | "scopes" | "doEnableDebugLogs" + >, + preProcessedParams: { + issuerUri: string; + clientId: string; + scopes: string[]; + configHash: string; + log: typeof console.log | undefined; + } +): Promise : Oidc> { + 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 => { @@ -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>(); - - prOidcByConfigHash.set(configHash, dOidc.pr); - const silentSso = publicUrl === undefined ? { @@ -1316,7 +1348,6 @@ export async function createOidc< initializationError }); - dOidc.resolve(oidc); // @ts-expect-error: We know what we are doing. return oidc; } @@ -1343,8 +1374,6 @@ export async function createOidc< "initializationError": undefined }); - dOidc.resolve(oidc); - // @ts-expect-error: We know what we are doing. return oidc; }