Skip to content

Commit

Permalink
Remplace prOidc by getOidc, fix bug in authRequiredGlobally mode, ena…
Browse files Browse the repository at this point in the history
…ble to initialize lasyly oidc in react api
  • Loading branch information
garronej committed Aug 2, 2024
1 parent 504349d commit 8805e8c
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 15 deletions.
8 changes: 7 additions & 1 deletion examples/react-router/src/oidc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@ export const {
* to the above OidcProvider.
*/
useOidc,
prOidc
/**
* This is useful to use the oidc API outside of React.
*/
getOidc
} = createReactOidc({
// If you don't have the parameters right away, it's the case for example
// if you get the oidc parameters from an API you can pass a promise that
// resolves to the parameters. `createReactOidc(prParams)`.
// You can also pass an async function that returns the parameters.
// `createReactOidc(async () => params)`. It will be called when the <OidcProvider />
// is first mounted or when getOidc() is called.

issuerUri: import.meta.env.VITE_OIDC_ISSUER,
clientId: import.meta.env.VITE_OIDC_CLIENT_ID,
Expand Down
4 changes: 2 additions & 2 deletions examples/react-router/src/router/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createBrowserRouter, type LoaderFunctionArgs } from "react-router-dom";
import { Layout } from "./Layout";
import { ProtectedPage } from "../pages/ProtectedPage";
import { PublicPage } from "../pages/PublicPage";
import { prOidc } from "oidc";
import { getOidc } from "oidc";

export const router = createBrowserRouter([
{
Expand All @@ -23,7 +23,7 @@ export const router = createBrowserRouter([
]);

async function protectedRouteLoader({ request }: LoaderFunctionArgs) {
const oidc = await prOidc;
const oidc = await getOidc();

if (!oidc.isUserLoggedIn) {
// Replace the href without reloading the page.
Expand Down
8 changes: 7 additions & 1 deletion examples/tanstack-router-file-based/src/oidc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@ export const {
* to the above OidcProvider.
*/
useOidc,
prOidc
/**
* This is useful to use the oidc API outside of React.
*/
getOidc
} = createReactOidc({
// If you don't have the parameters right away, it's the case for example
// if you get the oidc parameters from an API you can pass a promise that
// resolves to the parameters. `createReactOidc(prParams)`.
// You can also pass an async function that returns the parameters.
// `createReactOidc(async () => params)`. It will be called when the <OidcProvider />
// is first mounted or when getOidc() is called.

issuerUri: import.meta.env.VITE_OIDC_ISSUER,
clientId: import.meta.env.VITE_OIDC_CLIENT_ID,
Expand Down
17 changes: 15 additions & 2 deletions examples/tanstack-router-file-based/src/routes/protected.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// NOTE: Absolute imports are possible due to the following configuration:
// - tsconfig.json: "baseUrl": "./src"
// - vite.config.ts: usage of the "vite-tsconfig-paths" plugin
import { prOidc, useOidc } from "oidc";
import { getOidc, useOidc } from "oidc";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/protected")({
Expand Down Expand Up @@ -40,12 +40,25 @@ function ProtectedPage() {
>
Update profile
</button>
{/*
<br />
<br />
<button
onClick={() =>
goToAuthServer({
extraQueryParams: { kc_action: "delete_account" }
})
}
>
Delete account
</button>
*/}
</h4>
);
}

async function protectedRouteLoader() {
const oidc = await prOidc;
const oidc = await getOidc();

if (oidc.isUserLoggedIn) {
return null;
Expand Down
8 changes: 7 additions & 1 deletion examples/tanstack-router/src/oidc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@ export const {
* to the above OidcProvider.
*/
useOidc,
prOidc
/**
* This is useful to use the oidc API outside of React.
*/
getOidc
} = createReactOidc({
// If you don't have the parameters right away, it's the case for example
// if you get the oidc parameters from an API you can pass a promise that
// resolves to the parameters. `createReactOidc(prParams)`.
// You can also pass an async function that returns the parameters.
// `createReactOidc(async () => params)`. It will be called when the <OidcProvider />
// is first mounted or when getOidc() is called.

issuerUri: import.meta.env.VITE_OIDC_ISSUER,
clientId: import.meta.env.VITE_OIDC_CLIENT_ID,
Expand Down
4 changes: 2 additions & 2 deletions examples/tanstack-router/src/router/router.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Layout } from "./Layout";
import { ProtectedPage } from "../pages/ProtectedPage";
import { PublicPage } from "../pages/PublicPage";
import { prOidc } from "oidc";
import { getOidc } from "oidc";

import { createRootRoute, createRoute, createRouter } from "@tanstack/react-router";

Expand All @@ -25,7 +25,7 @@ declare module "@tanstack/react-router" {
}

async function protectedRouteLoader() {
const oidc = await prOidc;
const oidc = await getOidc();

if (oidc.isUserLoggedIn) {
return null;
Expand Down
53 changes: 47 additions & 6 deletions src/react/react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { assert } from "../vendor/tsafe";
import { id } from "../vendor/tsafe";
import { useGuaranteedMemo } from "../tools/powerhooks/useGuaranteedMemo";
import type { PromiseOrNot } from "../tools/PromiseOrNot";
import type { ValueOrPromiseOrAsyncGetter } from "../tools/ValueOrPromiseOrAsyncGetter";
import { Deferred } from "../tools/Deferred";

export type OidcReact<DecodedIdToken extends Record<string, unknown>> =
| OidcReact.NotLoggedIn
Expand Down Expand Up @@ -70,9 +72,13 @@ type OidcReactApi<
(params?: { assertUserLoggedIn: false }): OidcReact<DecodedIdToken>;
(params: { assertUserLoggedIn: true }): OidcReact.LoggedIn<DecodedIdToken>;
};
/** @deprecated: Use getOidc instead */
prOidc: Promise<
IsAuthGloballyRequired extends true ? Oidc.LoggedIn<DecodedIdToken> : Oidc<DecodedIdToken>
>;
getOidc: () => Promise<
IsAuthGloballyRequired extends true ? Oidc.LoggedIn<DecodedIdToken> : Oidc<DecodedIdToken>
>;
};

export function createOidcReactApi_dependencyInjection<
Expand All @@ -86,14 +92,33 @@ export function createOidcReactApi_dependencyInjection<
| {}
)
>(
params: PromiseOrNot<ParamsOfCreateOidc>,
params: ValueOrPromiseOrAsyncGetter<ParamsOfCreateOidc>,
createOidc: (params: ParamsOfCreateOidc) => PromiseOrNot<Oidc<DecodedIdToken>>
): OidcReactApi<
DecodedIdToken,
ParamsOfCreateOidc extends { isAuthGloballyRequired?: true | undefined } ? true : false
> {
const prOidc = Promise.resolve(params)
.then(params => createOidc(params))
const dReadyToCreate = new Deferred<void>();

// NOTE: It can be InitializationError only if isAuthGloballyRequired is true
const prOidcOrInitializationError = Promise.resolve(params)
.then(async paramsOrGetParams => {
const params = await (async () => {
if (typeof paramsOrGetParams === "function") {
const getParams = paramsOrGetParams;

await dReadyToCreate.pr;

const params = getParams();

return params;
}

return paramsOrGetParams;
})();

return createOidc(params);
})
.catch(error => {
if (!(error instanceof OidcInitializationError)) {
throw error;
Expand All @@ -117,7 +142,8 @@ export function createOidcReactApi_dependencyInjection<
>(undefined);

useEffect(() => {
prOidc.then(setOidcOrInitializationError);
dReadyToCreate.resolve();
prOidcOrInitializationError.then(setOidcOrInitializationError);
}, []);

if (oidcOrInitializationError === undefined) {
Expand Down Expand Up @@ -256,19 +282,34 @@ export function createOidcReactApi_dependencyInjection<
});
}

const prOidc = prOidcOrInitializationError.then(oidcOrInitializationError => {
if (oidcOrInitializationError instanceof OidcInitializationError) {
return new Promise<never>(() => {});
}

const oidc = oidcOrInitializationError;

return oidc;
});

return {
OidcProvider,
// @ts-expect-error: We know what we are doing
useOidc,
// @ts-expect-error: We know what we are doing
prOidc
prOidc,
// @ts-expect-error: We know what we are doing
getOidc: () => {
dReadyToCreate.resolve();
return prOidc;
}
};
}

/** @see: https://docs.oidc-spa.dev/documentation/usage#react-api */
export function createReactOidc<
DecodedIdToken extends Record<string, unknown> = Record<string, unknown>,
IsAuthGloballyRequired extends boolean = false
>(params: PromiseOrNot<ParamsOfCreateOidc<DecodedIdToken, IsAuthGloballyRequired>>) {
>(params: ValueOrPromiseOrAsyncGetter<ParamsOfCreateOidc<DecodedIdToken, IsAuthGloballyRequired>>) {
return createOidcReactApi_dependencyInjection(params, createOidc);
}
1 change: 1 addition & 0 deletions src/tools/ValueOrPromiseOrAsyncGetter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type ValueOrPromiseOrAsyncGetter<T> = T | Promise<T> | (() => Promise<T>);

0 comments on commit 8805e8c

Please sign in to comment.