From 9d90bab6d1ebca70e07052a59b3ef9e5eb336b8c Mon Sep 17 00:00:00 2001 From: David Taylor <114563+tayles@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:15:53 -0500 Subject: [PATCH 1/5] feat: Implement `authPersistence` in `ThemeConfig` --- .../theme/ApiExplorer/Authorization/slice.ts | 2 +- .../ApiExplorer/persistanceMiddleware.ts | 19 ++++++++++++------- .../src/theme/ApiItem/index.tsx | 5 ++++- .../src/types.ts | 2 +- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/Authorization/slice.ts b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/Authorization/slice.ts index 8449dcf79..a07da6158 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/Authorization/slice.ts +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/Authorization/slice.ts @@ -58,7 +58,7 @@ export function createAuth({ }; options?: ThemeConfig["api"]; }): AuthState { - const storage = createStorage("sessionStorage"); + const storage = createStorage(opts?.authPersistence ?? "sessionStorage"); let data: AuthState["data"] = {}; let options: AuthState["options"] = {}; diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistanceMiddleware.ts b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistanceMiddleware.ts index 823f8e786..239587c7d 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistanceMiddleware.ts +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistanceMiddleware.ts @@ -11,8 +11,8 @@ import { setSelectedAuth, } from "@theme/ApiExplorer/Authorization/slice"; import { AppDispatch, RootState } from "@theme/ApiItem/store"; -/* eslint-disable import/no-extraneous-dependencies*/ -import { ThemeConfig } from "docusaurus-theme-openapi-docs/src/types"; +import type { ServerObject } from "docusaurus-plugin-openapi-docs/src/openapi/types"; +import type { ThemeConfig } from "docusaurus-theme-openapi-docs/src/types"; import { createStorage, hashArray } from "./storage-utils"; @@ -25,7 +25,9 @@ export function createPersistanceMiddleware(options: ThemeConfig["api"]) { const state = storeAPI.getState(); - const storage = createStorage("sessionStorage"); + const storage = createStorage( + options?.authPersistence ?? "sessionStorage" + ); if (action.type === setAuthData.type) { for (const [key, value] of Object.entries(state.auth.data)) { @@ -60,11 +62,14 @@ export function createPersistanceMiddleware(options: ThemeConfig["api"]) { } if (action.type === "server/setServerVariable") { - const server = storage.getItem("server") ?? "{}"; + const server = storage.getItem("server"); const variables = JSON.parse(action.payload); - let serverObject = JSON.parse(server); - serverObject.variables[variables.key].default = variables.value; - storage.setItem("server", JSON.stringify(serverObject)); + + let serverObject = (JSON.parse(server!) as ServerObject) ?? {}; + if (serverObject?.variables?.[variables.key]) { + serverObject.variables[variables.key].default = variables.value; + storage.setItem("server", JSON.stringify(serverObject)); + } } return result; diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx index 13f2f17d8..dc6c4d956 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx @@ -15,6 +15,7 @@ import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; import useIsBrowser from "@docusaurus/useIsBrowser"; import { createAuth } from "@theme/ApiExplorer/Authorization/slice"; import { createPersistanceMiddleware } from "@theme/ApiExplorer/persistanceMiddleware"; +import { createStorage } from "@theme/ApiExplorer/storage-utils"; import DocItemLayout from "@theme/ApiItem/Layout"; import CodeBlock from "@theme/CodeBlock"; import type { Props } from "@theme/DocItem"; @@ -129,10 +130,12 @@ export default function ApiItem(props: Props): JSX.Element { securitySchemes: api?.securitySchemes, options, }); + + const storage = createStorage(options?.authPersistence ?? "sessionStorage"); // TODO: determine way to rehydrate without flashing // const acceptValue = window?.sessionStorage.getItem("accept"); // const contentTypeValue = window?.sessionStorage.getItem("contentType"); - const server = window?.sessionStorage.getItem("server"); + const server = storage.getItem("server"); const serverObject = (JSON.parse(server!) as ServerObject) ?? {}; store2 = createStoreWithState( diff --git a/packages/docusaurus-theme-openapi-docs/src/types.ts b/packages/docusaurus-theme-openapi-docs/src/types.ts index e65019ea5..053ddb278 100644 --- a/packages/docusaurus-theme-openapi-docs/src/types.ts +++ b/packages/docusaurus-theme-openapi-docs/src/types.ts @@ -11,7 +11,7 @@ import type { JSONSchema4, JSONSchema6, JSONSchema7 } from "json-schema"; export interface ThemeConfig { api?: { proxy?: string; - authPersistance?: false | "localStorage" | "sessionStorage"; + authPersistence?: false | "localStorage" | "sessionStorage"; }; } From 375bb4b73e338a4ccc80649c1d36c98ba450f4cb Mon Sep 17 00:00:00 2001 From: David Taylor <114563+tayles@users.noreply.github.com> Date: Sat, 15 Nov 2025 19:38:41 -0500 Subject: [PATCH 2/5] chore: Rename persistance -> persistence --- ...{persistanceMiddleware.ts => persistenceMiddleware.ts} | 6 +++--- .../src/theme/ApiExplorer/storage-utils.ts | 8 ++++---- .../src/theme/ApiItem/index.tsx | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) rename packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/{persistanceMiddleware.ts => persistenceMiddleware.ts} (93%) diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistanceMiddleware.ts b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistenceMiddleware.ts similarity index 93% rename from packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistanceMiddleware.ts rename to packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistenceMiddleware.ts index 239587c7d..c4cb22527 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistanceMiddleware.ts +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistenceMiddleware.ts @@ -16,8 +16,8 @@ import type { ThemeConfig } from "docusaurus-theme-openapi-docs/src/types"; import { createStorage, hashArray } from "./storage-utils"; -export function createPersistanceMiddleware(options: ThemeConfig["api"]) { - const persistanceMiddleware: Middleware<{}, RootState, AppDispatch> = +export function createPersistenceMiddleware(options: ThemeConfig["api"]) { + const persistenceMiddleware: Middleware<{}, RootState, AppDispatch> = (storeAPI) => (next) => (action: ReturnType | any) => { @@ -74,5 +74,5 @@ export function createPersistanceMiddleware(options: ThemeConfig["api"]) { return result; }; - return persistanceMiddleware; + return persistenceMiddleware; } diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/storage-utils.ts b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/storage-utils.ts index 2203ba3f9..0c566613b 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/storage-utils.ts +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/storage-utils.ts @@ -17,10 +17,10 @@ export function hashArray(arr: string[]) { return hash(res); } -type Persistance = false | "localStorage" | "sessionStorage" | undefined; +type Persistence = false | "localStorage" | "sessionStorage" | undefined; -export function createStorage(persistance: Persistance): Storage { - if (persistance === false) { +export function createStorage(persistence: Persistence): Storage { + if (persistence === false) { return { getItem: () => null, setItem: () => {}, @@ -31,7 +31,7 @@ export function createStorage(persistance: Persistance): Storage { }; } - if (persistance === "sessionStorage") { + if (persistence === "sessionStorage") { return sessionStorage; } diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx index dc6c4d956..be148f71f 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx @@ -14,7 +14,7 @@ import { HtmlClassNameProvider } from "@docusaurus/theme-common"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; import useIsBrowser from "@docusaurus/useIsBrowser"; import { createAuth } from "@theme/ApiExplorer/Authorization/slice"; -import { createPersistanceMiddleware } from "@theme/ApiExplorer/persistanceMiddleware"; +import { createPersistenceMiddleware } from "@theme/ApiExplorer/persistenceMiddleware"; import { createStorage } from "@theme/ApiExplorer/storage-utils"; import DocItemLayout from "@theme/ApiItem/Layout"; import CodeBlock from "@theme/CodeBlock"; @@ -91,11 +91,11 @@ export default function ApiItem(props: Props): JSX.Element { // Define store2 let store2: any = {}; - const persistanceMiddleware = createPersistanceMiddleware(options); + const persistenceMiddleware = createPersistenceMiddleware(options); // Init store for SSR if (!isBrowser) { - store2 = createStoreWithoutState({}, [persistanceMiddleware]); + store2 = createStoreWithoutState({}, [persistenceMiddleware]); } // Init store for CSR to hydrate components @@ -157,7 +157,7 @@ export default function ApiItem(props: Props): JSX.Element { params, auth, }, - [persistanceMiddleware] + [persistenceMiddleware] ); } From 3cc9657075d8a376d95da5dfbc7384e97a0887af Mon Sep 17 00:00:00 2001 From: David Taylor <114563+tayles@users.noreply.github.com> Date: Sat, 15 Nov 2025 20:05:49 -0500 Subject: [PATCH 3/5] docs: Add jsdoc for authPersistence --- packages/docusaurus-theme-openapi-docs/src/types.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/docusaurus-theme-openapi-docs/src/types.ts b/packages/docusaurus-theme-openapi-docs/src/types.ts index 053ddb278..8caa91690 100644 --- a/packages/docusaurus-theme-openapi-docs/src/types.ts +++ b/packages/docusaurus-theme-openapi-docs/src/types.ts @@ -11,7 +11,13 @@ import type { JSONSchema4, JSONSchema6, JSONSchema7 } from "json-schema"; export interface ThemeConfig { api?: { proxy?: string; - authPersistence?: false | "localStorage" | "sessionStorage"; + /** + * Controls how authentication credentials are persisted in the API explorer. + * - `false`: No persistence (in-memory only) + * - `"sessionStorage"`: Persist for the browser session (default) + * - `"localStorage"`: Persist across browser sessions + */ + authPersistence?: false | "sessionStorage" | "localStorage"; }; } From 856873a5e5441494fe60ae7822fb050cd473dcb3 Mon Sep 17 00:00:00 2001 From: David Taylor <114563+tayles@users.noreply.github.com> Date: Sat, 15 Nov 2025 20:13:17 -0500 Subject: [PATCH 4/5] fix: Add runtime null check --- .../src/theme/ApiExplorer/persistenceMiddleware.ts | 7 +++++-- .../src/theme/ApiItem/index.tsx | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistenceMiddleware.ts b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistenceMiddleware.ts index c4cb22527..a90127168 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistenceMiddleware.ts +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistenceMiddleware.ts @@ -63,10 +63,13 @@ export function createPersistenceMiddleware(options: ThemeConfig["api"]) { if (action.type === "server/setServerVariable") { const server = storage.getItem("server"); + if (!server) { + return result; + } const variables = JSON.parse(action.payload); - let serverObject = (JSON.parse(server!) as ServerObject) ?? {}; - if (serverObject?.variables?.[variables.key]) { + const serverObject = (JSON.parse(server) as ServerObject) ?? {}; + if (serverObject.variables?.[variables.key]) { serverObject.variables[variables.key].default = variables.value; storage.setItem("server", JSON.stringify(serverObject)); } diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx index be148f71f..0e5916d11 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx @@ -136,7 +136,9 @@ export default function ApiItem(props: Props): JSX.Element { // const acceptValue = window?.sessionStorage.getItem("accept"); // const contentTypeValue = window?.sessionStorage.getItem("contentType"); const server = storage.getItem("server"); - const serverObject = (JSON.parse(server!) as ServerObject) ?? {}; + const serverObject = server + ? (JSON.parse(server) as ServerObject) + : undefined; store2 = createStoreWithState( { @@ -149,7 +151,7 @@ export default function ApiItem(props: Props): JSX.Element { options: contentTypeArray, }, server: { - value: serverObject.url ? serverObject : undefined, + value: serverObject?.url ? serverObject : undefined, options: servers, }, response: { value: undefined }, From 3e043100f1580f74d1aa6f7eae1f776345708268 Mon Sep 17 00:00:00 2001 From: David Taylor <114563+tayles@users.noreply.github.com> Date: Sat, 15 Nov 2025 20:21:57 -0500 Subject: [PATCH 5/5] chore: Add type imports --- .../src/theme/ApiExplorer/persistenceMiddleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistenceMiddleware.ts b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistenceMiddleware.ts index a90127168..60808f340 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistenceMiddleware.ts +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/persistenceMiddleware.ts @@ -5,12 +5,12 @@ * LICENSE file in the root directory of this source tree. * ========================================================================== */ -import { Middleware } from "@reduxjs/toolkit"; +import type { Middleware } from "@reduxjs/toolkit"; import { setAuthData, setSelectedAuth, } from "@theme/ApiExplorer/Authorization/slice"; -import { AppDispatch, RootState } from "@theme/ApiItem/store"; +import type { AppDispatch, RootState } from "@theme/ApiItem/store"; import type { ServerObject } from "docusaurus-plugin-openapi-docs/src/openapi/types"; import type { ThemeConfig } from "docusaurus-theme-openapi-docs/src/types";