fumadocs-openapi 12.0.4 → 12.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/css/generated/shared.css +1 -1
- package/dist/.translations/index.d.ts +1 -0
- package/dist/.translations/keys.js +1 -0
- package/dist/operation.js +5 -7
- package/dist/playground/auth.d.ts +13 -4
- package/dist/playground/auth.js +84 -100
- package/dist/playground/fetcher.js +1 -1
- package/dist/requests/generators/curl.js +3 -2
- package/dist/requests/index.d.ts +2 -2
- package/dist/requests/index.js +2 -2
- package/dist/requests/media/adapter.d.ts +1 -1
- package/dist/requests/media/adapter.js +2 -2
- package/dist/requests/media/resolve-adapter.d.ts +0 -8
- package/dist/requests/media/resolve-adapter.js +2 -12
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/oauth.d.ts +6 -0
- package/dist/server/oauth.js +17 -0
- package/dist/ui/playground/client.js +35 -40
- package/dist/ui/playground/components/oauth-dialog.js +78 -76
- package/dist/ui/playground/components/result-display.js +39 -16
- package/dist/utils/create-page.d.ts +2 -0
- package/dist/utils/create-page.js +5 -3
- package/dist/utils/get-example-requests.js +2 -2
- package/dist/utils/schema.js +7 -3
- package/package.json +3 -3
package/css/generated/shared.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@source inline("!isDefined *:data-[collapsible=true]:order-last *:min-w-0 --- --accordion-panel-height --anchor-width --available-height --available-width --collapsible-panel-height --fd-docs-row-1 --min-height --spacing --transform-origin -mode -ms-2 -mx-3 -translate-x-1/2 -translate-y-1/2 @4xl:flex-row @4xl:items-start @4xl:sticky @4xl:top-[calc(var(--fd-docs-row-1,2rem)+1rem)] @4xl:w-[400px] @container @default @fuma-translate/react @fumadocs/json-schema @fumari/stf @md:grid-cols-2 @param @private @returns @scalar/api-client-react @see @typescript-eslint/no-explicit-any [&[hidden]:not([hidden='until-found'])]:hidden [&_svg]:size-3.5 a absolute accept access access_token accordion actual adapter adapters additionalProperties again against alias align allFlows allOf allow already alt always an anchor anchorIdStartsWith anchorSegments and animate-spin anyFields anyOf apiExample apiKey apiPlayground applicable application/javascript application/json application/x-javascript application/x-www-form-urlencoded application/xml apply are aria-label aria-live arkdown array as asSchema async at auth auth-specific authCodeQuery authNode authSchemes authorization authorizationCode authorization_code authorize authorizes autoComplete avoid await back backdrop-blur-lg backdrop-blur-sm badgeVariants base baseDir basic be because before behavior belong bg-black/30 bg-fd-background/50 bg-fd-card bg-fd-muted bg-fd-popover bg-fd-popover/60 bg-fd-primary bg-fd-primary/10 bg-fd-secondary binary blob block blocks blue body bodyMediaType bodyNode boolean border border-b border-current border-fd-primary/20 border-s border-t border-y bottom break-all buffer built-in bundled but button buttonVariants by byteLength bytes cache cached cachedDocFiles callbacks callbacksNode can cannot case catch center change charset check children class-variance-authority className cleanup client clientAuth clientAuthMethods clientCredentials clientHeight clientId clientSecret client_auth client_credentials client_error client_id client_secret cloned closed cn code codeProps codeUsages codeblock col-span-full collapsible color compiler components configuration const content contentType contentTypes cookie cookies copying core could createContext createOpenAPIPageBase createProxy credentials css csv ctx current currentValue custom customise customization cva dark dark:text-blue-400 dark:text-green-400 dark:text-orange-400 dark:text-red-400 dark:text-yellow-400 data data-[closed]:animate-fd-popover-out data-[disabled]:opacity-50 data-[disabled]:pointer-events-none data-[highlighted]:bg-fd-accent data-[highlighted]:text-fd-accent-foreground data-[open]:animate-fd-popover-in data-[open]:border-b-0 data-[popup-open]:text-fd-accent-foreground data-closed:animate-fd-dialog-out data-closed:animate-fd-fade-out data-collapsible data-ending-style:h-0 data-ending-style:opacity-0 data-object-search-input data-open:animate-fd-dialog-in data-open:animate-fd-fade-in data-placeholder:text-fd-muted-foreground data-starting-style:h-0 data-starting-style:opacity-0 data-type dataEngine decoded default defaultMdxComponents defaultOpen defaultShikiFactory defaultShikiOptions defaultValue defaultValues defaults deferredValue define definitions delete deprecated depth dereference dereferenced description descriptionNode development deviceAuthorization dialog didn disable disableCache disabled disabled:cursor-not-allowed disabled:opacity-50 display displayNames div do doc docCache docFilesCache docRoot docs document documents domain downloaded drives duplicated duration-200 during dynamic dynamicSource e eading ease-out element else empty empty:hidden enabled encode encodeRequestData encoded end end-2 endpoint enforce engine ensure entries entry enum error eslint-disable eslint-disable-next-line example exampleId examples export exported extends fallback false fast-content-type-parse fd-page-tree-item-name fd-scroll-container fetch fetcher field fieldData fieldLabelVariants fieldName fields file filePath files filtered fine first first:border-t-0 firstItemRef fixed flex flex-1 flex-col flex-col-reverse flex-row flex-wrap flow flows focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-fd-ring focus-within:[&_svg]:text-fd-primary focus-within:ring-2 focus-within:ring-fd-ring focus:outline-none focus:ring focus:ring-fd-ring folder folderStyle font-medium font-mono font-normal font-semibold for form formData format from fs full fumadocs-core/source fumadocs-core/toc fumadocs-ui/mdx fumadocs:openapi fumadocs_openapi_requests fumadocs_openapi_responses function gap-0.5 gap-1 gap-1.5 gap-2 gap-2.5 gap-24 gap-3 gap-4 gap-6 gap-x-4 gap-x-6 gap-y-1 gap-y-4 generate generateDefault generateSchemaUI generated get getOpenAPIPageProps getSchema getSchemas getWatchPaths ghost github-dark github-light given go grant_type gray green grid grid-cols-1 grid-cols-[auto_1fr] group group-data-[panel-open]/accordion:rotate-90 group-data-[panel-open]:rotate-180 group/accordion groupId h-(--accordion-panel-height) h-(--collapsible-panel-height) h-10 h-9 h-[240px] h-px h1 hash hast-util-to-jsx-runtime headNode header headers heading headingLevel headless hidden hiddenProperties highlighted highlighter holding hook hover:bg-fd-accent hover:text-fd-accent-foreground hover:underline how html htmlFor http hydration i icon icon-sm icon-xs id idx if ignore img implicit import imports in in-popover index indexItemMatch info infoTags init initial inline-flex input inputs inset-0 inside installed instanceof instead integer interface internal into invalid is isDefined isDuplicated isDynamic isLazy isLoading isMounted isNumber isRequired issues it item items items-center items-start its j js json json-schema-typed justify-between justify-center key keyed keyof kind label labelClassName lang last last:border-b-0 last:mb-0 lazy leading-none leave left-1/2 length let light like line-through link links list list-disc list-inside listed load loadDocument loaderPlugin localStorage location lucide-react map mapValues marker:text-fd-muted-foreground matching max-age max-h-(--available-height) max-h-[460px] max-w-(--available-width) max-w-[98vw] max-w-full max-w-lg may mb-1 mb-2 mb-auto md mdxComponents me-auto media mediaType member merge mergeRefs message meta metaData method methods mime min-h-(--min-height,200px) min-w-0 min-w-[240px] min-w-[calc(var(--anchor-width)+--spacing(2))] modify mounted ms-auto mt-0! mt-10 mt-2 mt-3 multipart/form-data multiple must mx-1 my-0! my-1 my-1.5 my-2 my-2! my-4 name name! named namespace navigating navigation need needs nested never new no no-cache node node:fs node:path noop normalizes not not-last:pb-0 not-prose note null number oauth2 object objectSearchOverrides objectSet objects of off omit on onChange onClick onCopy onKeyDown onOpenChange onScrollEnd onSubmit onValueChange one oneOf ones only opacity-80 open openApiData openIdConnect openapi openapiMeta opened opens operation options or orange order-last origin origin-(--transform-origin) other out outline outline-none output over overflow-auto overflow-hidden overflow-x-auto overflow-y-auto overflow-y-hidden p-1 p-1.5 p-2 p-3 p-4 packages page pageData pages panelVariants param parameter parameterLabel parameterNode parameters params parent parentPathIndex parents parsed parsedState part parts party pass password path pathIndex pathItem pathName pathnameFromRequest pattern patternProperties payload pb-0 pb-2 pe-2 pe-3 peer-disabled:cursor-not-allowed peer-disabled:opacity-70 persist persisted picked place placeholder placeholder:text-fd-muted-foreground playground playgrounds please plugins polite popover popoverRef positions pre preloadOpenAPIPage preloaded preventScroll previous primitive processor properties property props prose prose-no-margin prose-sm proxy proxyForwardCookie proxyUrl ps-1 ps-2 ps-3 ps-4.5 ps-6 pt-0 pt-1 pt-2 pt-2.5 px px-1 px-2 px-3 px-4 py-0 py-0.5 py-1 py-1.5 py-2 py-4 query querySelector quiet raw rawSearch re-triggering react react-hooks/exhaustive-deps react/jsx-runtime read readOnly readonly record red redirect_uri ref reference refs regenerated rehype-react relativePath remark remark-rehype remarkGfm remarkRehype remove removeItem removing render renderBodyField renderCodeblock renderMarkdown renderParameterField renderPopoverTrigger renderTrigger rendered renderers renders replace represents request request-body requestBody requestData requestInit requestTimeout requestUrl requests required requirement requirements res reset resize-none resolve resolveMediaAdapter resolveUrl resolved resolvedInput resolvedTheme response response-body responseNode responseTabs response_type responses rest restored result return returning returns role root rootId roots rotate-90 rounded-2xl rounded-lg rounded-md rounded-sm rounded-xl route s-highlight safeParse same satisfies scalar scan schema schemaId schemaPropKeys schemaUI scheme schemeId schemes scope scopes scroll scroll-m-20 scrollTop scrollTops search secondary seconds secret section sections security segments segs select select-none selected selectedType selections selector sends sep separator server server-to-server serverSchema services set setClientAuth setError setInfo setIsJson setNextName setObjectUrl setOpen setPath setSearch setSelected setServerVariables setShow setStore setToken setType setValue shadow-lg shadow-md shadow-sm shared shiki shikiOptions show showDescription showExample showResponseSchema showSelect showTitle showing shrink-0 side sideOffset signal single size size-3.5 size-4 skipNonRequired skipReadOnly slot slotType slots sm sm:flex-row sm:gap-2 sm:justify-end sm:text-start smooth so some source specify sr-only src stable start state staticSource status statusInfo step stf stfSync sticky still storageKey store stored string stringify stringifyFieldKey structuredData styles submit such supported svg switch t tab tabDepth tabValues tabs tag target testQuery text text-[0.8125rem] text-blue-600 text-center text-end text-fd-card-foreground text-fd-foreground text-fd-muted-foreground text-fd-muted-foreground/70 text-fd-popover-foreground text-fd-primary text-fd-primary-foreground text-fd-secondary-foreground text-green-500 text-green-600 text-lg text-nowrap text-orange-400 text-orange-600 text-red-400 text-red-400/80 text-red-500 text-red-600 text-sm text-start text-xs text-yellow-600 text/css text/csv text/html text/plain that the their them themes they this through throw timeout timerRef tips title to toJsxRuntime toStaticData toc token tokenInfo tokenUrl token_type toolbar top top-0 top-1/2 top-10 top-2 tracking-tight transform transformPageTree transition-[height,opacity] transition-[height] transition-colors transition-transform translations tree trigger true truncate try ts type typeVariants typeof types unchanged undefined under underline unexpected union unionField unions unknown unlisted unselected up update updateInfo updated updatedSchemeId updating url usageTabs use useAnchorId useApiClient useComponents useCopyButton useFieldValue useListener useMemo useNamespace useObject useOnChange useOpenAPI useOperation usePlaygroundAuth useQuery useRef useRenderContext useResponseExamples useServer useState useStatusInfo useStf useSyncExternalStore useTheme useTranslations useTypeScriptDefinitions used username using utf-8 v value valueSchema values variable variables variant variants via view virtual void w-0 w-14 w-[600px] w-fit w-full warn was we webhook webhooks when where whitespace-pre-wrap will with without won wrap-break-word wrapping write writeOnly x-exclusiveCodeSample x-playground-lazy xml yellow you your z-20 z-50");
|
|
1
|
+
@source inline("!isDefined *:data-[collapsible=true]:order-last *:min-w-0 --- --accordion-panel-height --anchor-width --available-height --available-width --collapsible-panel-height --fd-docs-row-1 --min-height --spacing --transform-origin -mode -ms-2 -mx-3 -translate-x-1/2 -translate-y-1/2 @4xl:flex-row @4xl:items-start @4xl:sticky @4xl:top-[calc(var(--fd-docs-row-1,2rem)+1rem)] @4xl:w-[400px] @container @default @fuma-translate/react @fumadocs/json-schema @fumari/stf @md:grid-cols-2 @param @private @returns @scalar/api-client-react @see @typescript-eslint/no-explicit-any [&[hidden]:not([hidden='until-found'])]:hidden [&_svg]:size-3.5 a absolute accept access access_token accordion actual adapter adapters additionalProperties again against alias align allFlows allOf allow already alt always an anchor anchorIdStartsWith anchorSegments and animate-spin anyFields anyOf apiExample apiKey apiPlayground applicable application/javascript application/json application/x-javascript application/x-www-form-urlencoded application/xml applies apply are aria-label aria-live arkdown array as asSchema async at audience auth auth-specific authCodeQuery authNode authSchemes authorization authorizationCode authorizationUrl authorization_code authorize authorizes autoComplete avoid await back backdrop-blur-lg backdrop-blur-sm badgeVariants base baseDir basic be because before behavior belong bg-black/30 bg-fd-background/50 bg-fd-card bg-fd-muted bg-fd-popover bg-fd-popover/60 bg-fd-primary bg-fd-primary/10 bg-fd-secondary bg-transparent binary block blocks blue body bodyMediaType bodyNode boolean border border-0 border-b border-current border-fd-primary/20 border-s border-t border-y bottom break-all built-in bundled button buttonVariants by byteLength bytes cache cached cachedDocFiles callbacks callbacksNode can cannot case catch center change charset children class-variance-authority className cleanup client clientAuth clientAuthMethods clientCredentials clientHeight clientId clientSecret client_auth client_credentials client_error client_id client_secret cloned closed cn code codeProps codeUsages codeblock col-span-full collapsible color compiler components configuration const content contentType contentTypes cookie cookies copying core createContext createOAuthHandler createOpenAPIPageBase createProxy credentials css csv ctx current currentValue custom customise customization cva dark dark:text-blue-400 dark:text-green-400 dark:text-orange-400 dark:text-red-400 dark:text-yellow-400 data data-[closed]:animate-fd-popover-out data-[disabled]:opacity-50 data-[disabled]:pointer-events-none data-[highlighted]:bg-fd-accent data-[highlighted]:text-fd-accent-foreground data-[open]:animate-fd-popover-in data-[open]:border-b-0 data-[popup-open]:text-fd-accent-foreground data-closed:animate-fd-dialog-out data-closed:animate-fd-fade-out data-collapsible data-ending-style:h-0 data-ending-style:opacity-0 data-object-search-input data-open:animate-fd-dialog-in data-open:animate-fd-fade-in data-placeholder:text-fd-muted-foreground data-starting-style:h-0 data-starting-style:opacity-0 data-type dataEngine decoded default defaultMdxComponents defaultOpen defaultShikiFactory defaultShikiOptions defaultValue defaultValues defaults deferredValue define definitions delete deprecated depth dereference dereferenced description descriptionNode development deviceAuthorization dialog didn disable disableCache disabled disabled:cursor-not-allowed disabled:opacity-50 display displayNames disposition div do doc docCache docFilesCache docRoot docs document documents domain download downloaded drives duplicated duration-200 during dynamic dynamicSource e eading ease-out element else empty empty:hidden enabled encode encodeRequestData encoded end end-2 endpoint enforce engine ensure entries entry enum error eslint-disable eslint-disable-next-line example exampleId examples export exported extended extends fallback false fast-content-type-parse fd-page-tree-item-name fd-scroll-container fetch fetcher field fieldData fieldLabelVariants fieldName fields file filePath files filtered fine first first:border-t-0 firstItemRef fixed flex flex-1 flex-col flex-col-reverse flex-row flex-wrap flow flows focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-fd-ring focus-within:[&_svg]:text-fd-primary focus-within:ring focus-within:ring-2 focus-within:ring-fd-ring focus:outline-none focus:ring focus:ring-0 focus:ring-fd-ring folder folderStyle font-medium font-mono font-normal font-semibold for form form-encoded formData format fragment from fs full fumadocs-core/source fumadocs-core/toc fumadocs-openapi-oauth fumadocs-ui/mdx fumadocs:openapi fumadocs_openapi_requests fumadocs_openapi_responses function gap-0.5 gap-1 gap-1.5 gap-2 gap-2.5 gap-24 gap-3 gap-4 gap-6 gap-x-4 gap-x-6 gap-y-1 gap-y-4 generate generateDefault generateSchemaUI generated get getOpenAPIPageProps getSchema getSchemas getWatchPaths ghost github-dark github-light given go goes grant_type gray green grid grid-cols-1 grid-cols-[auto_1fr] group group-data-[panel-open]/accordion:rotate-90 group-data-[panel-open]:rotate-180 group/accordion groupId h-(--accordion-panel-height) h-(--collapsible-panel-height) h-10 h-9 h-[240px] h-px h1 hash hast-util-to-jsx-runtime have headNode header headers heading headingLevel headless hidden hiddenProperties highlighted highlighter holding hook hover:bg-fd-accent hover:text-fd-accent-foreground hover:underline how href html htmlFor http hydration i icon icon-sm icon-xs id idx if ignore img implicit import imports in in-popover index indexItemMatch info infoTags init initial inline-flex input inputs inset-0 inside installed instanceof instead integer interface internal into invalid is isDefined isDuplicated isDynamic isLazy isLoading isMounted isNumber isRequired issues it item items items-center items-start its j js json json-schema-typed justify-between justify-center keep key keyed keyof kind label labelClassName lang last last:border-b-0 last:mb-0 lazy leading-none leave left left-1/2 length let light like line-through link links list list-disc list-inside listed listen listener listeners load loadDocument loaderPlugin localStorage location lucide-react map mapValues marker:text-fd-muted-foreground match matching max-age max-h-(--available-height) max-h-[460px] max-w-(--available-width) max-w-[98vw] max-w-full max-w-lg may mb-1 mb-2 mb-auto md mdxComponents me-auto media mediaAdapters mediaType member merge mergeRefs message meta metaData method methods mime min-h-(--min-height,200px) min-w-0 min-w-[240px] min-w-[calc(var(--anchor-width)+--spacing(2))] modify mounted ms-auto mt-0! mt-10 mt-2 mt-3 multipart/form-data multiple must mx-1 my-0! my-1 my-1.5 my-2 my-2! my-4 name name! named namespace navigating navigation needs nested never new no no-cache node node:fs node:path noop normalizes not not-last:pb-0 not-prose note null number oauth2 oauthRedirectUrl object objectSearchOverrides objectSet objectUrl objects of off omit on onChange onClick onCopy onKeyDown onOpenChange onScrollEnd onSubmit onToken onValueChange one oneOf ones only opacity-80 open openApiData openIdConnect openapi openapiMeta opened opens operation options or orange order-last origin origin-(--transform-origin) other otherwise out outline outline-none output over overflow-auto overflow-hidden overflow-x-auto overflow-y-auto overflow-y-hidden p-1 p-1.5 p-2 p-3 p-4 packages page pageData pages panelVariants param parameter parameterLabel parameterNode parameters params parent parentPathIndex parents parsed part parts party pass password path pathIndex pathItem pathName pathnameFromRequest pattern patternProperties payload pb-0 pb-2 pe-2 pe-3 peer-disabled:cursor-not-allowed peer-disabled:opacity-70 pending pendingFlowKey persist persisted picked place placeholder placeholder:text-fd-muted-foreground playground playgrounds please plugins polite popover popoverRef positions pre preferring preloadOpenAPIPage preloaded preventScroll previous primitive processor properties property props prose prose-no-margin prose-sm proxy proxyForwardCookie proxyUrl ps-1 ps-2 ps-3 ps-4.5 ps-6 pt-0 pt-1 pt-2 pt-2.5 px px-1 px-2 px-3 px-4 py-0 py-0.5 py-1 py-1.5 py-2 py-4 query querySelector quiet random raw rawSearch re-triggering react react-hooks/exhaustive-deps react/jsx-runtime read readOnly readonly record red redirect redirectUrl redirect_uri ref reference refs regenerated rehype-react relative relativePath remark remark-rehype remarkGfm remarkRehype remove removeItem removing render renderBodyField renderCodeblock renderMarkdown renderParameterField renderPopoverTrigger renderTrigger rendered renderers renders replace represents request request-body requestBody requestData requestInit requestTimeout requestUrl requests required requirement requirements res reset resize-none resolve resolveMediaAdapter resolveUrl resolved resolvedInput resolvedTheme response response-body responseNode responseTabs response_type responses rest restored result return returning returns role root rootId roots rotate-90 rounded-2xl rounded-b-none rounded-lg rounded-md rounded-sm rounded-t-md rounded-xl route s-highlight safeParse same satisfies scalar scan schema schemaId schemaPropKeys schemaUI scheme schemeId schemes scope scopes scroll scroll-m-20 scrollTop scrollTops search secondary seconds secret section sections security see segments segs select select-none selected selectedType selections selector send sends sep separator server server-to-server serverSchema serverUrl services sessionStorage set setClientAuth setError setInfo setIsJson setNextName setObjectUrl setOpen setPath setSearch setSelected setServerVariables setShow setStore setToken setType setValue shadow-lg shadow-md shadow-sm shared shiki shikiOptions show showDescription showExample showResponseSchema showSelect showTitle showing shrink-0 side sideOffset signal single size size-3.5 size-4 skipNonRequired skipReadOnly slot slotType slots sm sm:flex-row sm:gap-2 sm:justify-end sm:text-start smooth so some sr-only src stable start started state staticSource status statusInfo step stf stfSync sticky still storageKey store stored string stringify stringifyFieldKey structuredData styles submit subscribe such supported svg switch t tab tabDepth tabValues tabs tag target testQuery text text-[0.8125rem] text-blue-600 text-center text-end text-fd-card-foreground text-fd-foreground text-fd-muted-foreground text-fd-muted-foreground/70 text-fd-popover-foreground text-fd-primary text-fd-primary-foreground text-fd-secondary-foreground text-green-500 text-green-600 text-lg text-nowrap text-orange-400 text-orange-600 text-red-400 text-red-400/80 text-red-500 text-red-600 text-sm text-start text-xs text-yellow-600 text/css text/csv text/html text/plain that the their them themes they this through throw timeout timerRef tips title to toJsxRuntime toStaticData toc token tokenInfo tokenUrl token_type token_url tokens toolbar top top-0 top-1/2 top-10 top-2 tracking-tight transform transformPageTree transition-[height,opacity] transition-[height] transition-colors transition-transform translations tree trigger true truncate try ts type typeVariants typeof types unchanged undefined under underline unexpected union unionField unions unknown unlisted unselected up update updateInfo updated updating url usageTabs use useAnchorId useApiClient useComponents useCopyButton useFieldValue useListener useMemo useNamespace useObject useOnChange useOpenAPI useOperation usePlaygroundAuth useQuery useRef useRenderContext useResponseExamples useServer useState useStatusInfo useStf useSyncExternalStore useTheme useTranslations useTypeScriptDefinitions used username users using utf-8 v value valueSchema values variable variables variant variants via view virtual void w-0 w-14 w-[600px] w-fit w-full warn was we webhook webhooks when where will with without won wrap-break-word wrapping write writeOnly x-exclusiveCodeSample x-playground-lazy xml yellow you your z-20 z-50");
|
|
@@ -30,6 +30,7 @@ export type Translations = {
|
|
|
30
30
|
"Deprecated(operation page)": string;
|
|
31
31
|
"Deprecated(security scheme)": string;
|
|
32
32
|
"Device Authorization(OAuth dialog)": string;
|
|
33
|
+
"Download(playground result display)": string;
|
|
33
34
|
"Empty(operation page)": string;
|
|
34
35
|
"Enter Value(playground server select)": string;
|
|
35
36
|
"Enter value(OAuth dialog)": string;
|
|
@@ -30,6 +30,7 @@ var keys_default = [
|
|
|
30
30
|
"Deprecated(operation page)",
|
|
31
31
|
"Deprecated(security scheme)",
|
|
32
32
|
"Device Authorization(OAuth dialog)",
|
|
33
|
+
"Download(playground result display)",
|
|
33
34
|
"Empty(operation page)",
|
|
34
35
|
"Enter Value(playground server select)",
|
|
35
36
|
"Enter value(OAuth dialog)",
|
package/dist/operation.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { isMediaTypeSupported } from "./requests/media/resolve-adapter.js";
|
|
3
2
|
import { createCodeUsageGeneratorRegistry, pathnameFromRequest } from "./requests/generators/index.js";
|
|
4
3
|
import { joinURL } from "./packages/shared-api/dist/utils/url.js";
|
|
5
4
|
import { ServerProvider, useServer } from "./utils/use-server.js";
|
|
6
5
|
import { useOpenAPI } from "./utils/create-page.js";
|
|
7
6
|
import { idToTitle } from "./packages/shared-api/dist/utils/id-to-title.js";
|
|
8
|
-
import { getPreferredType, methodKeys } from "./utils/schema.js";
|
|
7
|
+
import { getExampleValue, getPreferredType, methodKeys } from "./utils/schema.js";
|
|
9
8
|
import { encodeRequestData } from "./requests/media/encode.js";
|
|
10
9
|
import { getExampleRequests } from "./utils/get-example-requests.js";
|
|
11
10
|
import { createContext, use, useMemo, useSyncExternalStore } from "react";
|
|
@@ -29,10 +28,7 @@ function OperationProvider({ type = "operation", path, method, operation, pathIt
|
|
|
29
28
|
let requestBody;
|
|
30
29
|
if (body?.content && Object.keys(body.content).length > 0) {
|
|
31
30
|
const content = {};
|
|
32
|
-
for (const [mediaType, item] of Object.entries(body.content))
|
|
33
|
-
if (!isMediaTypeSupported(mediaType, runtime.mediaAdapters)) throw new Error(`Media type ${mediaType} is not supported (in ${path})`);
|
|
34
|
-
content[mediaType] = resolve(item);
|
|
35
|
-
}
|
|
31
|
+
for (const [mediaType, item] of Object.entries(body.content)) content[mediaType] = resolve(item);
|
|
36
32
|
requestBody = {
|
|
37
33
|
description: body.description,
|
|
38
34
|
required: body.required,
|
|
@@ -269,9 +265,11 @@ function useResponseExamples() {
|
|
|
269
265
|
tab.examples = [];
|
|
270
266
|
for (const [key, item] of Object.entries(responseOfType.examples)) {
|
|
271
267
|
const example = resolve(item);
|
|
268
|
+
const sample = getExampleValue(example);
|
|
269
|
+
if (sample === void 0) continue;
|
|
272
270
|
tab.examples.push({
|
|
273
271
|
label: example.summary ?? t("Example {key}", { variables: { key } }),
|
|
274
|
-
sample
|
|
272
|
+
sample,
|
|
275
273
|
description: example.description
|
|
276
274
|
});
|
|
277
275
|
}
|
|
@@ -11,8 +11,15 @@ export interface OAuthFlowInput {
|
|
|
11
11
|
clientSecret: string;
|
|
12
12
|
username: string;
|
|
13
13
|
password: string;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* where token requests send the client credentials,
|
|
16
|
+
* `header` (HTTP Basic) applies only with a client secret, otherwise the `client_id` goes in the body
|
|
17
|
+
*/
|
|
15
18
|
clientAuth: 'body' | 'header';
|
|
19
|
+
/** URL of the selected server, relative URLs of the flow resolve against it */
|
|
20
|
+
serverUrl: string;
|
|
21
|
+
/** URL of the `createOAuthHandler()` route, defaults to the page */
|
|
22
|
+
redirectUrl?: string;
|
|
16
23
|
}
|
|
17
24
|
/**
|
|
18
25
|
* Run an OAuth flow and resolve its token.
|
|
@@ -20,25 +27,27 @@ export interface OAuthFlowInput {
|
|
|
20
27
|
* `implicit` and `authorizationCode` leave the page instead: the token is picked up on return,
|
|
21
28
|
* in `usePlaygroundAuth().store`.
|
|
22
29
|
*/
|
|
23
|
-
export declare function requestOAuthToken(scheme: OAuth2SecurityScheme, type: OAuthFlowType, { schemeId, scopes, clientId, clientSecret, username, password, clientAuth }: OAuthFlowInput): Promise<string | undefined>;
|
|
30
|
+
export declare function requestOAuthToken(scheme: OAuth2SecurityScheme, type: OAuthFlowType, { schemeId, scopes, clientId, clientSecret, username, password, clientAuth, serverUrl, redirectUrl }: OAuthFlowInput): Promise<string | undefined>;
|
|
24
31
|
/** scheme name -> token info */
|
|
25
32
|
type TokenStore = Record<string, TokenInfo | undefined>;
|
|
26
33
|
type TokenInfo = {
|
|
27
34
|
type: 'authorization_code';
|
|
28
|
-
redirect_uri: string;
|
|
29
35
|
client_id: string;
|
|
30
36
|
client_secret: string;
|
|
37
|
+
client_auth: OAuthFlowInput['clientAuth'];
|
|
31
38
|
token: string;
|
|
32
39
|
} | {
|
|
33
40
|
type: 'implicit';
|
|
34
41
|
client_id: string;
|
|
35
42
|
token: string;
|
|
36
43
|
};
|
|
44
|
+
type TokenListener = (schemeId: string, token: string) => void;
|
|
37
45
|
interface AuthContextType {
|
|
38
46
|
store: TokenStore;
|
|
39
|
-
updatedSchemeId: string | null;
|
|
40
47
|
isLoading: boolean;
|
|
41
48
|
error?: unknown;
|
|
49
|
+
/** listen to the tokens of flows returning to the page, returns a cleanup */
|
|
50
|
+
subscribe: (listener: TokenListener) => () => void;
|
|
42
51
|
}
|
|
43
52
|
export declare function usePlaygroundAuth(): AuthContextType;
|
|
44
53
|
export interface AuthRequirement {
|
package/dist/playground/auth.js
CHANGED
|
@@ -1,72 +1,69 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useQuery } from "../packages/shared-api/dist/utils/use-query.js";
|
|
3
3
|
import { useOpenAPI } from "../utils/create-page.js";
|
|
4
|
-
import { createContext, use, useCallback, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { createContext, use, useCallback, useEffect, useEffectEvent, useMemo, useState } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
import { useListener } from "@fumari/stf";
|
|
7
7
|
import { arrayStartsWith, objectGet, objectSet } from "@fumari/stf/lib/utils";
|
|
8
|
-
import { useOnChange } from "fumadocs-core/utils/use-on-change";
|
|
9
8
|
//#region src/playground/auth.tsx
|
|
9
|
+
const pendingFlowKey = "fumadocs-openapi-oauth";
|
|
10
10
|
/**
|
|
11
11
|
* Run an OAuth flow and resolve its token.
|
|
12
12
|
*
|
|
13
13
|
* `implicit` and `authorizationCode` leave the page instead: the token is picked up on return,
|
|
14
14
|
* in `usePlaygroundAuth().store`.
|
|
15
15
|
*/
|
|
16
|
-
async function requestOAuthToken(scheme, type, { schemeId, scopes, clientId, clientSecret, username, password, clientAuth }) {
|
|
16
|
+
async function requestOAuthToken(scheme, type, { schemeId, scopes, clientId, clientSecret, username, password, clientAuth, serverUrl, redirectUrl }) {
|
|
17
17
|
const flows = scheme.flows ?? {};
|
|
18
|
-
const scope = scopes.join("
|
|
19
|
-
const redirect_uri = window.location.href;
|
|
18
|
+
const scope = scopes.join(" ");
|
|
20
19
|
if (type === "implicit" || type === "authorizationCode") {
|
|
21
20
|
const flow = flows[type];
|
|
22
21
|
if (!flow) return;
|
|
23
|
-
const
|
|
24
|
-
|
|
22
|
+
const pending = {
|
|
23
|
+
state: crypto.getRandomValues(/* @__PURE__ */ new Uint32Array(4)).join(""),
|
|
24
|
+
scheme: schemeId,
|
|
25
25
|
client_id: clientId,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
window.location.replace(
|
|
26
|
+
client_secret: clientSecret,
|
|
27
|
+
client_auth: clientAuth,
|
|
28
|
+
redirect_uri: new URL(redirectUrl ?? window.location.pathname, window.location.origin).href,
|
|
29
|
+
token_url: "tokenUrl" in flow ? new URL(flow.tokenUrl, serverUrl).href : void 0
|
|
30
|
+
};
|
|
31
|
+
sessionStorage.setItem(pendingFlowKey, JSON.stringify(pending));
|
|
32
|
+
if (redirectUrl) document.cookie = `fumadocs-openapi-oauth=${encodeURIComponent(window.location.pathname)}; path=/`;
|
|
33
|
+
const url = new URL(flow.authorizationUrl, serverUrl);
|
|
34
|
+
url.searchParams.set("response_type", type === "implicit" ? "token" : "code");
|
|
35
|
+
url.searchParams.set("client_id", clientId);
|
|
36
|
+
url.searchParams.set("redirect_uri", pending.redirect_uri);
|
|
37
|
+
url.searchParams.set("scope", scope);
|
|
38
|
+
url.searchParams.set("state", pending.state);
|
|
39
|
+
window.location.replace(url);
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
if (type !== "password" && type !== "clientCredentials") return;
|
|
43
43
|
const flow = flows[type];
|
|
44
44
|
if (!flow) return;
|
|
45
|
-
const headers = {};
|
|
46
45
|
const body = new URLSearchParams({ scope });
|
|
47
46
|
if (type === "password") {
|
|
48
47
|
body.set("grant_type", "password");
|
|
49
48
|
body.set("username", username);
|
|
50
49
|
body.set("password", password);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
body.set("grant_type", "client_credentials");
|
|
58
|
-
body.set("client_id", clientId);
|
|
59
|
-
body.set("client_secret", clientSecret);
|
|
60
|
-
}
|
|
61
|
-
return fetchToken(flow.tokenUrl, body, headers);
|
|
50
|
+
} else body.set("grant_type", "client_credentials");
|
|
51
|
+
return fetchToken(new URL(flow.tokenUrl, serverUrl).href, body, {
|
|
52
|
+
client_id: clientId,
|
|
53
|
+
client_secret: clientSecret,
|
|
54
|
+
client_auth: clientAuth
|
|
55
|
+
});
|
|
62
56
|
}
|
|
63
|
-
async function fetchToken(tokenUrl, body,
|
|
57
|
+
async function fetchToken(tokenUrl, body, { client_id, client_secret, client_auth }) {
|
|
58
|
+
const headers = { "Content-Type": "application/x-www-form-urlencoded" };
|
|
59
|
+
if (client_auth === "header" && client_secret) headers.Authorization = `Basic ${btoa(`${encodeURIComponent(client_id)}:${encodeURIComponent(client_secret)}`)}`;
|
|
60
|
+
else {
|
|
61
|
+
if (client_id) body.set("client_id", client_id);
|
|
62
|
+
if (client_secret) body.set("client_secret", client_secret);
|
|
63
|
+
}
|
|
64
64
|
const res = await fetch(tokenUrl, {
|
|
65
65
|
method: "POST",
|
|
66
|
-
headers
|
|
67
|
-
...headers,
|
|
68
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
69
|
-
},
|
|
66
|
+
headers,
|
|
70
67
|
body
|
|
71
68
|
});
|
|
72
69
|
if (!res.ok) throw new Error(await res.text());
|
|
@@ -80,75 +77,64 @@ function usePlaygroundAuth() {
|
|
|
80
77
|
return ctx;
|
|
81
78
|
}
|
|
82
79
|
function AuthProvider({ children }) {
|
|
83
|
-
const { dereferenced, resolve } = useOpenAPI().doc;
|
|
84
|
-
const schemes = dereferenced.components?.securitySchemes;
|
|
85
80
|
const [store, setStore] = useState({});
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const info = {
|
|
92
|
-
type: "authorization_code",
|
|
93
|
-
...state,
|
|
94
|
-
token: await fetchToken(value.tokenUrl, new URLSearchParams({
|
|
95
|
-
grant_type: "authorization_code",
|
|
96
|
-
code,
|
|
97
|
-
redirect_uri: state.redirect_uri,
|
|
98
|
-
client_id: state.client_id,
|
|
99
|
-
client_secret: state.client_secret
|
|
100
|
-
}))
|
|
81
|
+
const listeners = useMemo(() => /* @__PURE__ */ new Set(), []);
|
|
82
|
+
const subscribe = useCallback((listener) => {
|
|
83
|
+
listeners.add(listener);
|
|
84
|
+
return () => {
|
|
85
|
+
listeners.delete(listener);
|
|
101
86
|
};
|
|
87
|
+
}, [listeners]);
|
|
88
|
+
function save(schemeId, info) {
|
|
102
89
|
setStore((s) => ({
|
|
103
90
|
...s,
|
|
104
|
-
[
|
|
91
|
+
[schemeId]: info
|
|
105
92
|
}));
|
|
93
|
+
for (const listener of listeners) listener(schemeId, info.token);
|
|
94
|
+
}
|
|
95
|
+
const authCodeQuery = useQuery(async (code, flow) => {
|
|
96
|
+
const token = await fetchToken(flow.token_url, new URLSearchParams({
|
|
97
|
+
grant_type: "authorization_code",
|
|
98
|
+
code,
|
|
99
|
+
redirect_uri: flow.redirect_uri
|
|
100
|
+
}), flow);
|
|
101
|
+
save(flow.scheme, {
|
|
102
|
+
type: "authorization_code",
|
|
103
|
+
client_id: flow.client_id,
|
|
104
|
+
client_secret: flow.client_secret,
|
|
105
|
+
client_auth: flow.client_auth,
|
|
106
|
+
token
|
|
107
|
+
});
|
|
106
108
|
});
|
|
107
109
|
useEffect(() => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const scheme = resolve(schemes?.[parsedState.scheme]);
|
|
125
|
-
if (!scheme || scheme.type !== "oauth2" || !scheme.flows?.implicit) return;
|
|
126
|
-
const info = {
|
|
127
|
-
type: "implicit",
|
|
128
|
-
client_id: parsedState.client_id,
|
|
129
|
-
token: `${type} ${token}`
|
|
130
|
-
};
|
|
131
|
-
setStore((s) => ({
|
|
132
|
-
...s,
|
|
133
|
-
[parsedState.scheme]: info
|
|
134
|
-
}));
|
|
135
|
-
window.history.replaceState(null, "", window.location.pathname);
|
|
136
|
-
}
|
|
137
|
-
try {
|
|
138
|
-
if (window.location.search.length > 0) onQuery();
|
|
139
|
-
if (window.location.hash.length > 1) onHash();
|
|
140
|
-
} catch {}
|
|
110
|
+
const stored = sessionStorage.getItem(pendingFlowKey);
|
|
111
|
+
if (!stored) return;
|
|
112
|
+
const flow = JSON.parse(stored);
|
|
113
|
+
const query = new URLSearchParams(window.location.search);
|
|
114
|
+
const hash = new URLSearchParams(window.location.hash.slice(1));
|
|
115
|
+
const code = query.get("code");
|
|
116
|
+
const token = hash.get("access_token");
|
|
117
|
+
if (code && query.get("state") === flow.state) authCodeQuery.start(code, flow);
|
|
118
|
+
else if (token && hash.get("state") === flow.state) save(flow.scheme, {
|
|
119
|
+
type: "implicit",
|
|
120
|
+
client_id: flow.client_id,
|
|
121
|
+
token: `${hash.get("token_type") ?? "Bearer"} ${token}`
|
|
122
|
+
});
|
|
123
|
+
else return;
|
|
124
|
+
sessionStorage.removeItem(pendingFlowKey);
|
|
125
|
+
window.history.replaceState(null, "", window.location.pathname);
|
|
141
126
|
}, []);
|
|
142
127
|
return /* @__PURE__ */ jsx(AuthContext, {
|
|
143
128
|
value: useMemo(() => ({
|
|
144
129
|
store,
|
|
145
|
-
updatedSchemeId: Object.keys(store)[0] ?? null,
|
|
146
130
|
isLoading: authCodeQuery.isLoading,
|
|
147
|
-
error: authCodeQuery.error
|
|
131
|
+
error: authCodeQuery.error,
|
|
132
|
+
subscribe
|
|
148
133
|
}), [
|
|
149
134
|
store,
|
|
150
135
|
authCodeQuery.isLoading,
|
|
151
|
-
authCodeQuery.error
|
|
136
|
+
authCodeQuery.error,
|
|
137
|
+
subscribe
|
|
152
138
|
]),
|
|
153
139
|
children
|
|
154
140
|
});
|
|
@@ -157,7 +143,7 @@ function AuthProvider({ children }) {
|
|
|
157
143
|
* The auth fields of an API playground: the values it persists, and the request data they encode into.
|
|
158
144
|
*/
|
|
159
145
|
function useAuthFields(engine, { operation, transform } = {}) {
|
|
160
|
-
const
|
|
146
|
+
const { subscribe } = usePlaygroundAuth();
|
|
161
147
|
const { storageKeyPrefix, doc } = useOpenAPI();
|
|
162
148
|
const { dereferenced, resolve } = doc;
|
|
163
149
|
const schemes = dereferenced.components?.securitySchemes;
|
|
@@ -238,21 +224,19 @@ function useAuthFields(engine, { operation, transform } = {}) {
|
|
|
238
224
|
}
|
|
239
225
|
}
|
|
240
226
|
});
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
if (!updatedSchemeId) return;
|
|
244
|
-
const { token } = auth.store[updatedSchemeId];
|
|
245
|
-
const field = fields.find((field) => field.schemeId === updatedSchemeId);
|
|
227
|
+
const onToken = useEffectEvent((schemeId, token) => {
|
|
228
|
+
const field = fields.find((field) => field.schemeId === schemeId);
|
|
246
229
|
if (field) {
|
|
247
230
|
engine.update(field.fieldName, token);
|
|
248
231
|
return;
|
|
249
232
|
}
|
|
250
|
-
const idx = requirements.findIndex((req) => req.some((item) => item.id ===
|
|
233
|
+
const idx = requirements.findIndex((req) => req.some((item) => item.id === schemeId));
|
|
251
234
|
if (idx !== -1) {
|
|
252
|
-
localStorage.setItem(`${storageKeyPrefix}auth-${
|
|
235
|
+
localStorage.setItem(`${storageKeyPrefix}auth-${schemeId}`, JSON.stringify(token));
|
|
253
236
|
select(idx);
|
|
254
237
|
}
|
|
255
238
|
});
|
|
239
|
+
useEffect(() => subscribe(onToken), [subscribe]);
|
|
256
240
|
return {
|
|
257
241
|
requirements,
|
|
258
242
|
selected,
|
|
@@ -22,7 +22,7 @@ function createBrowserFetcher(adapters, { proxyUrl, proxyForwardCookie = true, r
|
|
|
22
22
|
if (!adapter) return {
|
|
23
23
|
type: "client_error",
|
|
24
24
|
url,
|
|
25
|
-
message: `[Fumadocs] No adapter for ${data.bodyMediaType},
|
|
25
|
+
message: `[Fumadocs] No media adapter for ${data.bodyMediaType}, pass one to \`mediaAdapters\` of \`createOpenAPIPage()\`.`
|
|
26
26
|
};
|
|
27
27
|
if (data.bodyMediaType !== "multipart/form-data") headers.append("Content-Type", data.bodyMediaType);
|
|
28
28
|
requestInit.body = adapter.encode(data);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { doubleQuote, indent, inputToString, singleQuote } from "../string-utils.js";
|
|
2
|
+
import { resolveMediaAdapter } from "../media/resolve-adapter.js";
|
|
2
3
|
//#region src/requests/generators/curl.ts
|
|
3
4
|
const curl = {
|
|
4
5
|
label: "cURL",
|
|
5
6
|
lang: "bash",
|
|
6
|
-
generate(data) {
|
|
7
|
+
generate(data, { mediaAdapters }) {
|
|
7
8
|
const s = [];
|
|
8
9
|
s.push(`curl -X ${data.method.toUpperCase()} "${data.url}"`);
|
|
9
10
|
for (const header in data.header) {
|
|
@@ -17,7 +18,7 @@ const curl = {
|
|
|
17
18
|
if (data.body && data.bodyMediaType === "multipart/form-data") {
|
|
18
19
|
if (typeof data.body !== "object") throw new Error("[CURL] request body must be an object.");
|
|
19
20
|
for (const [key, value] of Object.entries(data.body)) s.push(`-F ${key}=${doubleQuote(inputToString(value))}`);
|
|
20
|
-
} else if (data.body && data.bodyMediaType) {
|
|
21
|
+
} else if (data.body && data.bodyMediaType && resolveMediaAdapter(data.bodyMediaType, mediaAdapters)) {
|
|
21
22
|
const escaped = singleQuote(inputToString(data.body, data.bodyMediaType));
|
|
22
23
|
s.push(`-H "Content-Type: ${data.bodyMediaType}"`);
|
|
23
24
|
s.push(`-d ${escaped}`);
|
package/dist/requests/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveMediaAdapter } from "./media/resolve-adapter.js";
|
|
2
2
|
import { EncodedParameter, EncodedParameterMultiple, encodeRequestData } from "./media/encode.js";
|
|
3
3
|
import { RawRequestData, RequestData } from "./types.js";
|
|
4
|
-
export { type EncodedParameter, type EncodedParameterMultiple, type RawRequestData, type RequestData, encodeRequestData,
|
|
4
|
+
export { type EncodedParameter, type EncodedParameterMultiple, type RawRequestData, type RequestData, encodeRequestData, resolveMediaAdapter };
|
package/dist/requests/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveMediaAdapter } from "./media/resolve-adapter.js";
|
|
2
2
|
import { encodeRequestData } from "./media/encode.js";
|
|
3
|
-
export { encodeRequestData,
|
|
3
|
+
export { encodeRequestData, resolveMediaAdapter };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { __toESM } from "../../_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { require_js2xml } from "../../node_modules/.pnpm/xml-js@1.6.11/node_modules/xml-js/lib/js2xml.js";
|
|
3
3
|
import { backtickQuote, inputToString, rustRawStringLiteral, tripleDoubleQuote } from "../string-utils.js";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveMediaAdapter } from "./resolve-adapter.js";
|
|
5
5
|
//#region src/requests/media/adapter.ts
|
|
6
6
|
var import_js2xml = /* @__PURE__ */ __toESM(require_js2xml(), 1);
|
|
7
7
|
const defaultAdapters = {
|
|
@@ -125,4 +125,4 @@ function str(init, mediaType, ctx) {
|
|
|
125
125
|
if (ctx.lang === "rust") return `let body = ${rustRawStringLiteral(inputToString(init, mediaType))};`;
|
|
126
126
|
}
|
|
127
127
|
//#endregion
|
|
128
|
-
export { defaultAdapters,
|
|
128
|
+
export { defaultAdapters, resolveMediaAdapter };
|
|
@@ -9,12 +9,4 @@ import { MediaAdapter } from "./adapter.js";
|
|
|
9
9
|
* @returns The resolved adapter or undefined if not found
|
|
10
10
|
*/
|
|
11
11
|
export declare function resolveMediaAdapter(mediaType: string, adapters: Record<string, MediaAdapter>): MediaAdapter | undefined;
|
|
12
|
-
/**
|
|
13
|
-
* Check if a media type is supported by the given adapters.
|
|
14
|
-
*
|
|
15
|
-
* @param mediaType - The media type to check
|
|
16
|
-
* @param adapters - Record of media adapters
|
|
17
|
-
* @returns true if the media type is supported
|
|
18
|
-
*/
|
|
19
|
-
export declare function isMediaTypeSupported(mediaType: string, adapters: Record<string, MediaAdapter>): boolean;
|
|
20
12
|
//#endregion
|
|
@@ -16,20 +16,10 @@ function resolveMediaAdapter(mediaType, adapters) {
|
|
|
16
16
|
const baseType = `${match.groups.dir}/${match.groups.variant}`;
|
|
17
17
|
if (baseType in adapters) return adapters[baseType];
|
|
18
18
|
}
|
|
19
|
-
if (
|
|
19
|
+
if (normalized === "text/plain") {
|
|
20
20
|
console.warn("there is no defined behaviour for encoding form values into \"text/plain\", using JSON encoder for now.");
|
|
21
21
|
return adapters["application/json"];
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Check if a media type is supported by the given adapters.
|
|
26
|
-
*
|
|
27
|
-
* @param mediaType - The media type to check
|
|
28
|
-
* @param adapters - Record of media adapters
|
|
29
|
-
* @returns true if the media type is supported
|
|
30
|
-
*/
|
|
31
|
-
function isMediaTypeSupported(mediaType, adapters) {
|
|
32
|
-
return resolveMediaAdapter(mediaType, adapters) !== void 0;
|
|
33
|
-
}
|
|
34
24
|
//#endregion
|
|
35
|
-
export {
|
|
25
|
+
export { resolveMediaAdapter };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Awaitable, Document } from "../types.js";
|
|
|
2
2
|
import { OpenAPIPageProps_Preloaded, OpenAPIPageProps_Spec } from "../utils/pages/builder.js";
|
|
3
3
|
import { SchemaToPagesOptions } from "../utils/pages/preset-auto.js";
|
|
4
4
|
import { CreateProxyOptions, Proxy, createProxy } from "./proxy.js";
|
|
5
|
+
import { createOAuthHandler } from "./oauth.js";
|
|
5
6
|
import "../ui/index.js";
|
|
6
7
|
import { DynamicSource, LoaderPlugin, MetaData, Page, PageData, Source } from "fumadocs-core/source";
|
|
7
8
|
import { StructuredData } from "fumadocs-core/mdx-plugins/remark-structure";
|
|
@@ -30,6 +31,7 @@ export interface OpenAPIOptions {
|
|
|
30
31
|
}
|
|
31
32
|
export interface OpenAPIServer {
|
|
32
33
|
createProxy: typeof createProxy;
|
|
34
|
+
createOAuthHandler: typeof createOAuthHandler;
|
|
33
35
|
getSchemas: () => Promise<Record<string, LoadedDocument>>;
|
|
34
36
|
getSchema: (document: string) => Promise<LoadedDocument>;
|
|
35
37
|
readonly options: OpenAPIOptions;
|
package/dist/server/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { fromSchema, getPageProps } from "../utils/pages/builder.js";
|
|
|
2
2
|
import { toStaticData } from "../utils/pages/to-static-data.js";
|
|
3
3
|
import { createAutoPreset } from "../utils/pages/preset-auto.js";
|
|
4
4
|
import { createProxy } from "./proxy.js";
|
|
5
|
+
import { createOAuthHandler } from "./oauth.js";
|
|
5
6
|
import { loadDocument } from "../utils/document/load.js";
|
|
6
7
|
import { MethodLabel } from "../ui/components/method-label.js";
|
|
7
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -113,6 +114,7 @@ function createOpenAPI(options = {}) {
|
|
|
113
114
|
return {
|
|
114
115
|
options,
|
|
115
116
|
createProxy,
|
|
117
|
+
createOAuthHandler,
|
|
116
118
|
_getWatchPaths() {
|
|
117
119
|
return Object.keys(resolvedInput).filter((key) => !URL.canParse(key) && fs.existsSync(key));
|
|
118
120
|
},
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/server/oauth.ts
|
|
2
|
+
/**
|
|
3
|
+
* A route handler for the redirect URI of OAuth flows, it sends users back to the page that started the flow.
|
|
4
|
+
*/
|
|
5
|
+
function createOAuthHandler() {
|
|
6
|
+
return (req) => {
|
|
7
|
+
const page = /(?:^|;\s*)fumadocs-openapi-oauth=([^;]+)/.exec(req.headers.get("cookie") ?? "");
|
|
8
|
+
const target = page && URL.parse(decodeURIComponent(page[1]), "http://localhost");
|
|
9
|
+
if (!target || target.origin !== "http://localhost") return new Response(null, { status: 400 });
|
|
10
|
+
return new Response(null, {
|
|
11
|
+
status: 302,
|
|
12
|
+
headers: { Location: target.pathname + new URL(req.url).search }
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { createOAuthHandler };
|
|
@@ -8,6 +8,7 @@ import { encodeRequestData } from "../../requests/media/encode.js";
|
|
|
8
8
|
import { useExampleRequests, useOperation } from "../../operation.js";
|
|
9
9
|
import { MethodLabel } from "../components/method-label.js";
|
|
10
10
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../packages/shared-api/dist/components/select.js";
|
|
11
|
+
import { Markdown } from "../components/markdown.js";
|
|
11
12
|
import { cn } from "../../utils/cn.js";
|
|
12
13
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../packages/shared-api/dist/components/collapsible.js";
|
|
13
14
|
import { DefaultResultDisplay } from "./components/result-display.js";
|
|
@@ -21,10 +22,10 @@ import { Fragment, createContext, use, useEffect, useMemo, useRef, useState } fr
|
|
|
21
22
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
22
23
|
import { StfProvider, useDataEngine, useFieldValue, useListener, useStf } from "@fumari/stf";
|
|
23
24
|
import { stringifyFieldKey } from "@fumari/stf/lib/utils";
|
|
24
|
-
import { useOnChange } from "fumadocs-core/utils/use-on-change";
|
|
25
25
|
import { useTranslations } from "@fuma-translate/react";
|
|
26
26
|
import { ChevronDown, LoaderCircle, PlusIcon } from "lucide-react";
|
|
27
27
|
import { buttonVariants } from "fumadocs-ui/components/ui/button";
|
|
28
|
+
import { useOnChange } from "fumadocs-core/utils/use-on-change";
|
|
28
29
|
//#region src/ui/playground/client.tsx
|
|
29
30
|
const OptionsContext = createContext({});
|
|
30
31
|
function usePlaygroundOptions() {
|
|
@@ -185,37 +186,16 @@ function SecurityRequirements({ requirements, select, selected, children }) {
|
|
|
185
186
|
useOnChange(defaultOpen, () => {
|
|
186
187
|
if (defaultOpen) setOpen(true);
|
|
187
188
|
});
|
|
188
|
-
const items = requirements.map((requirement, i) => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
children: scheme.description
|
|
199
|
-
})] })
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
return {
|
|
203
|
-
value: i,
|
|
204
|
-
label: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
205
|
-
className: "inline-flex items-center gap-1 font-mono font-medium",
|
|
206
|
-
children: requirement.map((item, i) => /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 && /* @__PURE__ */ jsx(PlusIcon, { className: "text-fd-muted-foreground size-3.5" }), /* @__PURE__ */ jsx("span", {
|
|
207
|
-
className: cn(item.scheme.deprecated && "text-fd-muted-foreground line-through"),
|
|
208
|
-
children: item.id
|
|
209
|
-
})] }, i))
|
|
210
|
-
}), /* @__PURE__ */ jsx("ul", {
|
|
211
|
-
className: "text-fd-muted-foreground whitespace-pre-wrap list-disc list-inside",
|
|
212
|
-
children: requirement.map((item, i) => /* @__PURE__ */ jsx("li", {
|
|
213
|
-
className: "empty:hidden",
|
|
214
|
-
children: item.scheme.description
|
|
215
|
-
}, i))
|
|
216
|
-
})] })
|
|
217
|
-
};
|
|
218
|
-
});
|
|
189
|
+
const items = requirements.map((requirement, i) => ({
|
|
190
|
+
value: i,
|
|
191
|
+
label: /* @__PURE__ */ jsx("span", {
|
|
192
|
+
className: "inline-flex items-center gap-1 font-mono font-medium",
|
|
193
|
+
children: requirement.map((item, i) => /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 && /* @__PURE__ */ jsx(PlusIcon, { className: "text-fd-muted-foreground size-3.5" }), /* @__PURE__ */ jsx("span", {
|
|
194
|
+
className: cn(item.scheme.deprecated && "text-fd-muted-foreground line-through"),
|
|
195
|
+
children: item.id
|
|
196
|
+
})] }, i))
|
|
197
|
+
})
|
|
198
|
+
}));
|
|
219
199
|
return /* @__PURE__ */ jsxs(CollapsiblePanel, {
|
|
220
200
|
title: /* @__PURE__ */ jsxs(Fragment$1, { children: [t("Authorization"), isLoading && /* @__PURE__ */ jsxs("span", {
|
|
221
201
|
className: "border-s ps-2 inline-flex items-center gap-1.5 text-fd-muted-foreground text-xs font-mono",
|
|
@@ -236,14 +216,29 @@ function SecurityRequirements({ requirements, select, selected, children }) {
|
|
|
236
216
|
children: t("Failed to fetch token")
|
|
237
217
|
}), /* @__PURE__ */ jsx("p", { children: String(error) })]
|
|
238
218
|
}),
|
|
239
|
-
/* @__PURE__ */ jsxs(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
children:
|
|
246
|
-
|
|
219
|
+
/* @__PURE__ */ jsxs("div", {
|
|
220
|
+
className: "overflow-hidden rounded-md border bg-fd-secondary focus-within:ring focus-within:ring-fd-ring",
|
|
221
|
+
children: [/* @__PURE__ */ jsxs(Select, {
|
|
222
|
+
items,
|
|
223
|
+
value: selected,
|
|
224
|
+
onValueChange: (v) => v !== null && select(v),
|
|
225
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
226
|
+
className: "rounded-t-md rounded-b-none border-0 bg-transparent focus:ring-0",
|
|
227
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
228
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: items.map(({ value, label }) => /* @__PURE__ */ jsx(SelectItem, {
|
|
229
|
+
value,
|
|
230
|
+
children: label
|
|
231
|
+
}, value)) })]
|
|
232
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
233
|
+
className: "grid grid-cols-[auto_1fr] gap-2 border-t px-2 py-1.5 text-xs text-fd-muted-foreground empty:hidden",
|
|
234
|
+
children: requirements[selected]?.map(({ id, scheme }, _, arr) => {
|
|
235
|
+
if (!scheme.description) return;
|
|
236
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [arr.length > 1 && /* @__PURE__ */ jsx("span", {
|
|
237
|
+
className: "font-medium font-mono text-fd-primary",
|
|
238
|
+
children: id
|
|
239
|
+
}), /* @__PURE__ */ jsx(Markdown, { md: scheme.description })] }, id);
|
|
240
|
+
})
|
|
241
|
+
})]
|
|
247
242
|
}),
|
|
248
243
|
children
|
|
249
244
|
]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useQuery } from "../../../packages/shared-api/dist/utils/use-query.js";
|
|
2
2
|
import { requestOAuthToken, usePlaygroundAuth } from "../../../playground/auth.js";
|
|
3
|
+
import { useServer } from "../../../utils/use-server.js";
|
|
3
4
|
import { useOpenAPI } from "../../../utils/create-page.js";
|
|
4
5
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../packages/shared-api/dist/components/select.js";
|
|
5
6
|
import { cn } from "../../../utils/cn.js";
|
|
@@ -17,24 +18,26 @@ function OAuthDialogContent(props) {
|
|
|
17
18
|
return /* @__PURE__ */ jsxs(DialogContent, { children: [/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: t("Authorization") }), /* @__PURE__ */ jsx(DialogDescription, { children: t("Obtain the access token for API.") })] }), /* @__PURE__ */ jsx(Content, { ...props })] });
|
|
18
19
|
}
|
|
19
20
|
function Content({ schemeId, scopes, setToken, setOpen }) {
|
|
20
|
-
const {
|
|
21
|
+
const { doc, oauthRedirectUrl } = useOpenAPI();
|
|
22
|
+
const { dereferenced, resolve } = doc;
|
|
21
23
|
const schemes = dereferenced.components?.securitySchemes;
|
|
22
24
|
const tokenInfo = usePlaygroundAuth().store[schemeId];
|
|
25
|
+
const { resolveUrl } = useServer();
|
|
23
26
|
const scheme = resolve(schemes?.[schemeId]);
|
|
24
27
|
if (!scheme || scheme.type !== "oauth2") throw new Error("unexpected schemaId: must be type oauth2");
|
|
25
28
|
const [type, setType] = useState(() => {
|
|
26
29
|
return Object.keys(scheme.flows)[0];
|
|
27
30
|
});
|
|
28
|
-
const [clientAuth, setClientAuth] = useState("
|
|
31
|
+
const [clientAuth, setClientAuth] = useState(() => tokenInfo?.type === "authorization_code" ? tokenInfo.client_auth : "header");
|
|
29
32
|
const t = useTranslations({ note: "OAuth dialog" });
|
|
30
33
|
const clientAuthMethods = {
|
|
31
|
-
body: {
|
|
32
|
-
name: t("Send client credentials in body"),
|
|
33
|
-
description: t("Include the client ID and secret in the token request body.")
|
|
34
|
-
},
|
|
35
34
|
header: {
|
|
36
35
|
name: t("Send as Basic Auth header"),
|
|
37
36
|
description: t("Send the client ID and secret in the Authorization header.")
|
|
37
|
+
},
|
|
38
|
+
body: {
|
|
39
|
+
name: t("Send client credentials in body"),
|
|
40
|
+
description: t("Include the client ID and secret in the token request body.")
|
|
38
41
|
}
|
|
39
42
|
};
|
|
40
43
|
const allFlows = useMemo(() => ({
|
|
@@ -78,7 +81,9 @@ function Content({ schemeId, scopes, setToken, setOpen }) {
|
|
|
78
81
|
...values,
|
|
79
82
|
schemeId,
|
|
80
83
|
scopes,
|
|
81
|
-
clientAuth
|
|
84
|
+
clientAuth,
|
|
85
|
+
serverUrl: resolveUrl(),
|
|
86
|
+
redirectUrl: oauthRedirectUrl
|
|
82
87
|
});
|
|
83
88
|
if (!token) return;
|
|
84
89
|
setToken(token);
|
|
@@ -143,7 +148,7 @@ function Content({ schemeId, scopes, setToken, setOpen }) {
|
|
|
143
148
|
autoComplete: "off",
|
|
144
149
|
disabled: isLoading,
|
|
145
150
|
defaultValue: defaultValues.clientId,
|
|
146
|
-
required: type !== "password"
|
|
151
|
+
required: type !== "password"
|
|
147
152
|
})
|
|
148
153
|
]
|
|
149
154
|
}),
|
|
@@ -166,78 +171,75 @@ function Content({ schemeId, scopes, setToken, setOpen }) {
|
|
|
166
171
|
autoComplete: "off",
|
|
167
172
|
disabled: isLoading,
|
|
168
173
|
defaultValue: defaultValues.clientSecret,
|
|
169
|
-
required: type !== "password"
|
|
174
|
+
required: type !== "password"
|
|
170
175
|
})
|
|
171
176
|
]
|
|
172
177
|
}),
|
|
173
|
-
type === "password" && /* @__PURE__ */ jsxs(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
id: "client_auth",
|
|
194
|
-
disabled: isLoading,
|
|
195
|
-
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
196
|
-
}), /* @__PURE__ */ jsx(SelectContent, { children: Object.entries(clientAuthMethods).map(([key, method]) => /* @__PURE__ */ jsxs(SelectItem, {
|
|
197
|
-
value: key,
|
|
198
|
-
children: [/* @__PURE__ */ jsx("p", {
|
|
199
|
-
className: "font-medium",
|
|
200
|
-
children: method.name
|
|
201
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
202
|
-
className: "text-fd-muted-foreground",
|
|
203
|
-
children: method.description
|
|
204
|
-
})]
|
|
205
|
-
}, key)) })]
|
|
206
|
-
})]
|
|
207
|
-
}),
|
|
208
|
-
/* @__PURE__ */ jsxs("fieldset", {
|
|
209
|
-
className: "flex flex-col gap-1.5",
|
|
210
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
211
|
-
htmlFor: "username",
|
|
212
|
-
children: t("Username")
|
|
213
|
-
}), /* @__PURE__ */ jsx(Input, {
|
|
214
|
-
id: "username",
|
|
215
|
-
name: "username",
|
|
216
|
-
placeholder: t("Enter value"),
|
|
217
|
-
type: "text",
|
|
218
|
-
autoComplete: "off",
|
|
219
|
-
disabled: isLoading,
|
|
220
|
-
defaultValue: defaultValues.username,
|
|
221
|
-
required: true
|
|
222
|
-
})]
|
|
223
|
-
}),
|
|
224
|
-
/* @__PURE__ */ jsxs("fieldset", {
|
|
225
|
-
className: "flex flex-col gap-1.5",
|
|
226
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
227
|
-
htmlFor: "password",
|
|
228
|
-
children: t("Password")
|
|
229
|
-
}), /* @__PURE__ */ jsx(Input, {
|
|
230
|
-
id: "password",
|
|
231
|
-
name: "password",
|
|
232
|
-
placeholder: t("Enter value"),
|
|
233
|
-
type: "password",
|
|
234
|
-
autoComplete: "off",
|
|
178
|
+
(type === "authorizationCode" || type === "clientCredentials" || type === "password") && /* @__PURE__ */ jsxs("fieldset", {
|
|
179
|
+
className: "flex flex-col gap-1.5",
|
|
180
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
181
|
+
htmlFor: "client_auth",
|
|
182
|
+
children: t("Client Authentication")
|
|
183
|
+
}), /* @__PURE__ */ jsxs(Select, {
|
|
184
|
+
items: Object.entries(clientAuthMethods).map(([key, method]) => ({
|
|
185
|
+
label: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("p", {
|
|
186
|
+
className: "font-medium",
|
|
187
|
+
children: method.name
|
|
188
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
189
|
+
className: "text-fd-muted-foreground",
|
|
190
|
+
children: method.description
|
|
191
|
+
})] }),
|
|
192
|
+
value: key
|
|
193
|
+
})),
|
|
194
|
+
value: clientAuth,
|
|
195
|
+
onValueChange: (v) => v !== null && setClientAuth(v),
|
|
196
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
197
|
+
id: "client_auth",
|
|
235
198
|
disabled: isLoading,
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
199
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
200
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: Object.entries(clientAuthMethods).map(([key, method]) => /* @__PURE__ */ jsxs(SelectItem, {
|
|
201
|
+
value: key,
|
|
202
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
203
|
+
className: "font-medium",
|
|
204
|
+
children: method.name
|
|
205
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
206
|
+
className: "text-fd-muted-foreground",
|
|
207
|
+
children: method.description
|
|
208
|
+
})]
|
|
209
|
+
}, key)) })]
|
|
210
|
+
})]
|
|
211
|
+
}),
|
|
212
|
+
type === "password" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("fieldset", {
|
|
213
|
+
className: "flex flex-col gap-1.5",
|
|
214
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
215
|
+
htmlFor: "username",
|
|
216
|
+
children: t("Username")
|
|
217
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
218
|
+
id: "username",
|
|
219
|
+
name: "username",
|
|
220
|
+
placeholder: t("Enter value"),
|
|
221
|
+
type: "text",
|
|
222
|
+
autoComplete: "off",
|
|
223
|
+
disabled: isLoading,
|
|
224
|
+
defaultValue: defaultValues.username,
|
|
225
|
+
required: true
|
|
226
|
+
})]
|
|
227
|
+
}), /* @__PURE__ */ jsxs("fieldset", {
|
|
228
|
+
className: "flex flex-col gap-1.5",
|
|
229
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
230
|
+
htmlFor: "password",
|
|
231
|
+
children: t("Password")
|
|
232
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
233
|
+
id: "password",
|
|
234
|
+
name: "password",
|
|
235
|
+
placeholder: t("Enter value"),
|
|
236
|
+
type: "password",
|
|
237
|
+
autoComplete: "off",
|
|
238
|
+
disabled: isLoading,
|
|
239
|
+
defaultValue: defaultValues.password,
|
|
240
|
+
required: true
|
|
241
|
+
})]
|
|
242
|
+
})] }),
|
|
241
243
|
type && allFlows[type].supported ? /* @__PURE__ */ jsxs(Fragment$1, { children: [authorize.error ? /* @__PURE__ */ jsx("p", {
|
|
242
244
|
className: "text-red-400 font-medium text-sm",
|
|
243
245
|
children: String(authorize.error)
|
|
@@ -8,7 +8,7 @@ import { Fragment, useEffect, useMemo, useState } from "react";
|
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { useTranslations } from "@fuma-translate/react";
|
|
10
10
|
import { cva } from "class-variance-authority";
|
|
11
|
-
import { ChevronDown, CircleX, SignpostIcon } from "lucide-react";
|
|
11
|
+
import { ChevronDown, CircleX, DownloadIcon, SignpostIcon } from "lucide-react";
|
|
12
12
|
import { buttonVariants } from "fumadocs-ui/components/ui/button";
|
|
13
13
|
//#region src/ui/playground/components/result-display.tsx
|
|
14
14
|
var import_fast_content_type_parse = require_fast_content_type_parse();
|
|
@@ -69,11 +69,17 @@ function getTextFormat(mime) {
|
|
|
69
69
|
function ResponseResult({ data, reset, ...rest }) {
|
|
70
70
|
const t = useTranslations({ note: "playground result display" });
|
|
71
71
|
const statusInfo = useStatusInfo(data.status);
|
|
72
|
+
const [objectUrl, setObjectUrl] = useState();
|
|
72
73
|
const { parameters, type } = useMemo(() => (0, import_fast_content_type_parse.safeParse)(data.headers.get("Content-Type") ?? "text/plain"), [data.headers]);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const objectUrl = URL.createObjectURL(new Blob([data.body], { type }));
|
|
76
|
+
setObjectUrl(objectUrl);
|
|
77
|
+
return () => URL.revokeObjectURL(objectUrl);
|
|
78
|
+
}, [data.body, type]);
|
|
73
79
|
let body;
|
|
74
|
-
if (type.startsWith("image/")) body = /* @__PURE__ */ jsx(ImageResult, {
|
|
80
|
+
if (type.startsWith("image/")) body = objectUrl && /* @__PURE__ */ jsx(ImageResult, {
|
|
75
81
|
mime: type,
|
|
76
|
-
|
|
82
|
+
src: objectUrl
|
|
77
83
|
});
|
|
78
84
|
else if (data.body.byteLength > 0) {
|
|
79
85
|
const lang = getTextFormat(type);
|
|
@@ -103,19 +109,28 @@ function ResponseResult({ data, reset, ...rest }) {
|
|
|
103
109
|
children: [
|
|
104
110
|
/* @__PURE__ */ jsx(statusInfo.icon, { className: cn("size-4 shrink-0", statusInfo.color) }),
|
|
105
111
|
/* @__PURE__ */ jsxs("p", {
|
|
106
|
-
className: "text-sm font-medium
|
|
112
|
+
className: "min-w-0 me-auto text-sm font-medium truncate",
|
|
107
113
|
children: [
|
|
108
114
|
data.status,
|
|
109
115
|
" ",
|
|
110
116
|
statusInfo.description
|
|
111
117
|
]
|
|
112
118
|
}),
|
|
119
|
+
data.body.byteLength > 0 && /* @__PURE__ */ jsxs("a", {
|
|
120
|
+
href: objectUrl,
|
|
121
|
+
download: getFileName(data.headers),
|
|
122
|
+
className: cn(buttonVariants({
|
|
123
|
+
size: "sm",
|
|
124
|
+
variant: "outline"
|
|
125
|
+
}), "gap-2"),
|
|
126
|
+
children: [/* @__PURE__ */ jsx(DownloadIcon, { className: "size-3.5 text-fd-muted-foreground" }), t("Download")]
|
|
127
|
+
}),
|
|
113
128
|
/* @__PURE__ */ jsx("button", {
|
|
114
129
|
type: "button",
|
|
115
|
-
className:
|
|
130
|
+
className: buttonVariants({
|
|
116
131
|
size: "sm",
|
|
117
132
|
variant: "outline"
|
|
118
|
-
}),
|
|
133
|
+
}),
|
|
119
134
|
onClick: () => reset(),
|
|
120
135
|
children: t("Close")
|
|
121
136
|
})
|
|
@@ -184,26 +199,34 @@ function TextResult({ lang, charset, data, contentType }) {
|
|
|
184
199
|
}) }
|
|
185
200
|
});
|
|
186
201
|
}
|
|
187
|
-
function ImageResult({ mime,
|
|
188
|
-
const [objectUrl, setObjectUrl] = useState(null);
|
|
189
|
-
useEffect(() => {
|
|
190
|
-
const blob = new Blob([buffer], { type: mime });
|
|
191
|
-
const url = URL.createObjectURL(blob);
|
|
192
|
-
setObjectUrl(url);
|
|
193
|
-
return () => URL.revokeObjectURL(url);
|
|
194
|
-
}, [mime, buffer]);
|
|
195
|
-
if (!objectUrl) return;
|
|
202
|
+
function ImageResult({ mime, src }) {
|
|
196
203
|
return /* @__PURE__ */ jsxs("figure", {
|
|
197
204
|
className: "w-full p-1 bg-fd-card border rounded-lg shadow-sm",
|
|
198
205
|
children: [/* @__PURE__ */ jsx("figcaption", {
|
|
199
206
|
className: "text-xs text-fd-muted-foreground font-mono p-1 pb-2",
|
|
200
207
|
children: mime
|
|
201
208
|
}), /* @__PURE__ */ jsx("img", {
|
|
202
|
-
src
|
|
209
|
+
src,
|
|
203
210
|
alt: "",
|
|
204
211
|
className: "w-full rounded-md"
|
|
205
212
|
})]
|
|
206
213
|
});
|
|
207
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* The file name from `Content-Disposition`, preferring `filename*` (RFC 6266), without directories.
|
|
217
|
+
*/
|
|
218
|
+
function getFileName(headers) {
|
|
219
|
+
const disposition = headers.get("Content-Disposition") ?? "";
|
|
220
|
+
const extended = /(?:^|;)\s*filename\*\s*=\s*[^']*'[^']*'([^\s;"]+)/i.exec(disposition);
|
|
221
|
+
let name;
|
|
222
|
+
if (extended) try {
|
|
223
|
+
name = decodeURIComponent(extended[1]);
|
|
224
|
+
} catch {}
|
|
225
|
+
if (!name) {
|
|
226
|
+
const match = /(?:^|;)\s*filename\s*=\s*(?:"((?:\\.|[^"\\])*)"|([^\s;]+))/i.exec(disposition);
|
|
227
|
+
name = match?.[1]?.replace(/\\(.)/g, "$1") ?? match?.[2];
|
|
228
|
+
}
|
|
229
|
+
return name?.replace(/^.*[/\\]/, "") || "response";
|
|
230
|
+
}
|
|
208
231
|
//#endregion
|
|
209
232
|
export { DefaultResultDisplay };
|
|
@@ -34,6 +34,8 @@ export interface OpenAPIRuntimeOptions {
|
|
|
34
34
|
* @defaultValue `fumadocs-openapi-`
|
|
35
35
|
*/
|
|
36
36
|
storageKeyPrefix?: string;
|
|
37
|
+
/** URL of the `createOAuthHandler()` route, the redirect URI of OAuth flows instead of the page */
|
|
38
|
+
oauthRedirectUrl?: string;
|
|
37
39
|
/**
|
|
38
40
|
* Generate example code usage for all endpoints.
|
|
39
41
|
*/
|
|
@@ -59,7 +59,7 @@ function useTypeScriptDefinitions(schema, options) {
|
|
|
59
59
|
]);
|
|
60
60
|
return result instanceof Promise ? use(result) : result;
|
|
61
61
|
}
|
|
62
|
-
function OpenAPIProvider({ document, mediaAdapters, codeUsages, generateCodeSamples, generateTypeScriptDefinitions, proxyUrl, storageKeyPrefix = "fumadocs-openapi-", shiki, shikiOptions = defaultShikiOptions, showResponseSchema, content, schemaUI, playground, components, children }) {
|
|
62
|
+
function OpenAPIProvider({ document, mediaAdapters, codeUsages, generateCodeSamples, generateTypeScriptDefinitions, proxyUrl, storageKeyPrefix = "fumadocs-openapi-", oauthRedirectUrl, shiki, shikiOptions = defaultShikiOptions, showResponseSchema, content, schemaUI, playground, components, children }) {
|
|
63
63
|
const runtime = useMemo(() => ({
|
|
64
64
|
doc: dereferenceBundledDocument(document),
|
|
65
65
|
mediaAdapters: {
|
|
@@ -70,7 +70,8 @@ function OpenAPIProvider({ document, mediaAdapters, codeUsages, generateCodeSamp
|
|
|
70
70
|
generateCodeSamples,
|
|
71
71
|
generateTypeScriptDefinitions,
|
|
72
72
|
proxyUrl,
|
|
73
|
-
storageKeyPrefix
|
|
73
|
+
storageKeyPrefix,
|
|
74
|
+
oauthRedirectUrl
|
|
74
75
|
}), [
|
|
75
76
|
document,
|
|
76
77
|
mediaAdapters,
|
|
@@ -78,7 +79,8 @@ function OpenAPIProvider({ document, mediaAdapters, codeUsages, generateCodeSamp
|
|
|
78
79
|
generateCodeSamples,
|
|
79
80
|
generateTypeScriptDefinitions,
|
|
80
81
|
proxyUrl,
|
|
81
|
-
storageKeyPrefix
|
|
82
|
+
storageKeyPrefix,
|
|
83
|
+
oauthRedirectUrl
|
|
82
84
|
]);
|
|
83
85
|
const render = useMemo(() => ({
|
|
84
86
|
shiki,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getPreferredType, pickExample } from "./schema.js";
|
|
1
|
+
import { getExampleValue, getPreferredType, pickExample } from "./schema.js";
|
|
2
2
|
import { encodeRequestData } from "../requests/media/encode.js";
|
|
3
3
|
import { getRaw } from "@scalar/json-magic/magic-proxy";
|
|
4
4
|
import { dereference, sample } from "@fumadocs/json-schema";
|
|
@@ -80,7 +80,7 @@ function getRequestData({ method, path, parameters, sampleKey, body }) {
|
|
|
80
80
|
if (!type) throw new Error(`Cannot find body schema for ${path} ${method}: missing media type`);
|
|
81
81
|
result.bodyMediaType = type;
|
|
82
82
|
const bodyOfType = dereference(body.content[type]);
|
|
83
|
-
if (bodyOfType.examples && sampleKey) result.body =
|
|
83
|
+
if (bodyOfType.examples && sampleKey) result.body = getExampleValue(dereference(bodyOfType.examples[sampleKey]));
|
|
84
84
|
else if (bodyOfType.example) result.body = getRaw(bodyOfType.example);
|
|
85
85
|
else result.body = sample(bodyOfType?.schema ?? {}, {
|
|
86
86
|
skipReadOnly: method !== "get",
|
package/dist/utils/schema.js
CHANGED
|
@@ -26,14 +26,18 @@ function pickExample(value) {
|
|
|
26
26
|
const content = type ? dereference(value.content[type]) : void 0;
|
|
27
27
|
if (type && content) {
|
|
28
28
|
const example = value.examples?.[type];
|
|
29
|
-
const out = (example !== void 0 ?
|
|
29
|
+
const out = (example !== void 0 ? getExampleValue(dereference(example)) : void 0) ?? pickExample(content);
|
|
30
30
|
if (out !== void 0) return out;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
if (value.examples) {
|
|
34
34
|
const examples = Object.values(value.examples);
|
|
35
|
-
if (examples.length > 0) return
|
|
35
|
+
if (examples.length > 0) return getExampleValue(dereference(examples[0]));
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
/** the value of an Example Object, `dataValue` since OpenAPI 3.2 */
|
|
39
|
+
function getExampleValue(example) {
|
|
40
|
+
return getRaw(example.dataValue ?? example.value);
|
|
41
|
+
}
|
|
38
42
|
//#endregion
|
|
39
|
-
export { getPreferredType, getTagDisplayName, methodKeys, pickExample };
|
|
43
|
+
export { getExampleValue, getPreferredType, getTagDisplayName, methodKeys, pickExample };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fumadocs-openapi",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.1.1",
|
|
4
4
|
"description": "Generate MDX docs for your OpenAPI spec",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Docs",
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
"@types/react-dom": "^19.3.0",
|
|
79
79
|
"fast-content-type-parse": "^4.0.0",
|
|
80
80
|
"fuma-cli": "^0.3.1",
|
|
81
|
-
"fumadocs-core": "16.15.
|
|
82
|
-
"fumadocs-ui": "npm:@fumadocs/base-ui@16.15.
|
|
81
|
+
"fumadocs-core": "16.15.18",
|
|
82
|
+
"fumadocs-ui": "npm:@fumadocs/base-ui@16.15.18",
|
|
83
83
|
"json-schema-typed": "^8.0.2",
|
|
84
84
|
"shared-api": "0.2.10",
|
|
85
85
|
"tsconfig": "0.0.0",
|