@scalar/api-client 3.21.4 → 3.21.5
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/CHANGELOG.md +11 -0
- package/dist/plugins/posthog/sanitize-event-payload.d.ts.map +1 -1
- package/dist/plugins/posthog/sanitize-event-payload.js +1 -0
- package/dist/plugins/posthog/sanitize-event-payload.js.map +1 -1
- package/dist/style.css +59 -49
- package/dist/v2/blocks/operation-block/OperationBlock.vue.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/OperationBlock.vue.js.map +1 -1
- package/dist/v2/blocks/operation-block/OperationBlock.vue.script.js +1 -1
- package/dist/v2/blocks/operation-block/OperationBlock.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/RequestBlock.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestBody.vue.d.ts.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestBody.vue.js +1 -1
- package/dist/v2/blocks/request-block/components/RequestBody.vue.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestBody.vue.script.js +1 -1
- package/dist/v2/blocks/request-block/components/RequestBody.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue.js +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue.js.map +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue.script.js +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue.script.js +53 -22
- package/dist/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthDataTableInput.vue.d.ts +4 -0
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthDataTableInput.vue.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthDataTableInput.vue.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthDataTableInput.vue.script.js +23 -12
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthDataTableInput.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.script.js +30 -6
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth-device-authorization.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth-device-authorization.js +3 -1
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth-device-authorization.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.js +9 -3
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.js.map +1 -1
- package/dist/v2/components/code-input/CodeInput.vue.d.ts.map +1 -1
- package/dist/v2/components/code-input/CodeInput.vue.js +1 -1
- package/dist/v2/components/code-input/CodeInput.vue.js.map +1 -1
- package/dist/v2/components/code-input/CodeInput.vue.script.js.map +1 -1
- package/dist/v2/components/data-table/DataTableInput.vue.d.ts.map +1 -1
- package/dist/v2/components/data-table/DataTableInput.vue.js +1 -1
- package/dist/v2/components/data-table/DataTableInput.vue.js.map +1 -1
- package/dist/v2/components/data-table/DataTableInput.vue.script.js +15 -11
- package/dist/v2/components/data-table/DataTableInput.vue.script.js.map +1 -1
- package/dist/v2/components/server/ServerDropdown.vue.d.ts.map +1 -1
- package/dist/v2/components/server/ServerDropdown.vue.js.map +1 -1
- package/dist/v2/components/server/ServerDropdown.vue.script.js +1 -1
- package/dist/v2/components/server/ServerDropdown.vue.script.js.map +1 -1
- package/dist/v2/constants.js +1 -1
- package/dist/v2/features/localization/locales/ar.d.ts +3 -0
- package/dist/v2/features/localization/locales/ar.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/ar.js +4 -1
- package/dist/v2/features/localization/locales/ar.js.map +1 -1
- package/dist/v2/features/localization/locales/de.d.ts +3 -0
- package/dist/v2/features/localization/locales/de.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/de.js +4 -1
- package/dist/v2/features/localization/locales/de.js.map +1 -1
- package/dist/v2/features/localization/locales/es.d.ts +3 -0
- package/dist/v2/features/localization/locales/es.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/es.js +4 -1
- package/dist/v2/features/localization/locales/es.js.map +1 -1
- package/dist/v2/features/localization/locales/fr.d.ts +3 -0
- package/dist/v2/features/localization/locales/fr.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/fr.js +4 -1
- package/dist/v2/features/localization/locales/fr.js.map +1 -1
- package/dist/v2/features/localization/locales/pt.d.ts +3 -0
- package/dist/v2/features/localization/locales/pt.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/pt.js +4 -1
- package/dist/v2/features/localization/locales/pt.js.map +1 -1
- package/dist/v2/features/localization/locales/ru.d.ts +3 -0
- package/dist/v2/features/localization/locales/ru.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/ru.js +4 -1
- package/dist/v2/features/localization/locales/ru.js.map +1 -1
- package/dist/v2/features/localization/locales/zh-cn.d.ts +3 -0
- package/dist/v2/features/localization/locales/zh-cn.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/zh-cn.js +4 -1
- package/dist/v2/features/localization/locales/zh-cn.js.map +1 -1
- package/dist/v2/features/localization/translations.d.ts +3 -0
- package/dist/v2/features/localization/translations.d.ts.map +1 -1
- package/dist/v2/features/localization/translations.js +4 -1
- package/dist/v2/features/localization/translations.js.map +1 -1
- package/dist/v2/workspace-events.d.ts.map +1 -1
- package/dist/v2/workspace-events.js +1 -0
- package/dist/v2/workspace-events.js.map +1 -1
- package/dist/vue-styles.css +29 -31
- package/package.json +11 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestBody.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/request-block/components/RequestBody.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components/button'\nimport { ScalarCodeBlockCopy } from '@scalar/components/code-block'\nimport { ScalarCopyBackdrop } from '@scalar/components/copy'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { ScalarIconButton } from '@scalar/components/icon-button'\nimport { ScalarListbox } from '@scalar/components/listbox'\nimport { CONTENT_TYPES } from '@scalar/helpers/http/content-types'\nimport { isXmlMediaType } from '@scalar/helpers/http/is-xml-media-type'\nimport { parseMimeType } from '@scalar/helpers/http/mime-type'\nimport { isObject } from '@scalar/helpers/object/is-object'\nimport { objectEntries } from '@scalar/helpers/object/object-entries'\nimport { ScalarIconMagicWand } from '@scalar/icons'\nimport type { ApiReferenceEvents } from '@scalar/workspace-store/events'\nimport {\n getExampleValue,\n getExplicitExampleText,\n} from '@scalar/workspace-store/helpers/get-example-value'\nimport { unpackProxyObject } from '@scalar/workspace-store/helpers/unpack-proxy'\nimport {\n getExampleFromBody,\n getSchemaExampleFromBody,\n getSelectedBodyContentType,\n} from '@scalar/workspace-store/request-example'\nimport { resolve } from '@scalar/workspace-store/resolve'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type {\n RequestBodyObject,\n SchemaObject,\n} from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { isObjectSchema } from '@scalar/workspace-store/schemas/v3.2/strict/type-guards'\nimport { computed, ref, watch } from 'vue'\n\nimport { useFileDialog } from '@/hooks/use-file-dialog'\nimport RequestBodyForm from '@/v2/blocks/request-block/components/RequestBodyForm.vue'\nimport RequestBodyStructured from '@/v2/blocks/request-block/components/RequestBodyStructured.vue'\nimport RequestBodyViewToggle from '@/v2/blocks/request-block/components/RequestBodyViewToggle.vue'\nimport { getFileName } from '@/v2/blocks/request-block/helpers/files'\nimport { getStructuredBodyCodec } from '@/v2/blocks/request-block/helpers/structured-body-codec'\nimport { CodeInput } from '@/v2/components/code-input'\nimport {\n DataTable,\n DataTableHeader,\n DataTableRow,\n} from '@/v2/components/data-table'\nimport { CollapsibleSection } from '@/v2/components/layout'\nimport { useLocalization } from '@/v2/features/localization'\n\nconst {\n requestBody,\n exampleKey,\n environment,\n requestBodyCompositionSelection,\n openapiVersion,\n title,\n defaultView = 'raw',\n} = defineProps<{\n /** Request body */\n requestBody?: RequestBodyObject\n /** Currently selected example key for the current operation */\n exampleKey: string\n /** Display title */\n title: string\n /** Selected environment */\n environment: XScalarEnvironment\n /** Originating OpenAPI version, used for XML mapping rules. */\n openapiVersion?: string\n /** Selected anyOf/oneOf request-body variants keyed by schema path */\n requestBodyCompositionSelection?: Record<string, number>\n /**\n * Initial view for structured (JSON/YAML) bodies. Comes from the\n * `x-scalar-default-request-body-view` document extension and falls back to `raw`\n * whenever the body cannot be shown as a form.\n */\n defaultView?: 'form' | 'raw'\n}>()\n\nconst emits = defineEmits<{\n (e: 'update:contentType', payload: { value: string }): void\n /** We use this event to update raw values */\n (\n e: 'update:value',\n payload: Pick<\n ApiReferenceEvents['operation:update:requestBody:value'],\n 'payload' | 'contentType'\n >,\n ): void\n /** We use this event when updating form data only */\n (\n e: 'update:formValue',\n payload: Pick<\n ApiReferenceEvents['operation:update:requestBody:formValue'],\n 'payload' | 'contentType'\n >,\n ): void\n /**\n * Create a new example seeded with values generated from the request body schema.\n *\n * The block itself owns the document and operation identity needed to create an example, so we\n * only report the content type the user is currently looking at.\n */\n (e: 'generate:example', payload: { contentType: string }): void\n}>()\n\nconst { translate } = useLocalization()\n\n// Map a content type to a language for the code editor\nconst contentTypeToLanguageMap = {\n 'application/json': 'json',\n 'application/xml': 'xml',\n 'application/yaml': 'yaml',\n} as const\n\n/** Selected content type with default */\nconst selectedContentType = computed(\n () => getSelectedBodyContentType(requestBody, exampleKey) ?? 'none',\n)\n\n/** Keep the editor and copy label aligned with the selected content type. */\nconst selectedLanguage = computed(() =>\n isXmlMediaType(selectedContentType.value)\n ? 'xml'\n : (contentTypeToLanguageMap[\n selectedContentType.value as keyof typeof contentTypeToLanguageMap\n ] ?? 'plaintext'),\n)\n\n/**\n * Strips MIME parameters (such as `charset=utf-8`) for dropdown labels, aligned with how we treat\n * media types elsewhere (see api-reference `normalizeMimeType` / WHATWG essence).\n */\nconst contentTypeLabel = (raw: string): string => parseMimeType(raw).essence\n\n/**\n * Build the dropdown options for the content type selector.\n *\n * The list is composed of two groups:\n * 1. The built-in content types we ship friendly labels for (JSON, XML, Multipart Form, etc.).\n * 2. Any additional content types defined on the OpenAPI request body that are not in the built-in list\n * (for example `text/csv`, `application/pdf`, vendor types like `application/vnd.api+json`, ...).\n *\n * The OpenAPI-defined extras are appended at the bottom so the well-known options stay on top, and\n * users can always pick the exact content type the operation actually accepts.\n */\n/** MIME values remain stable while their human-readable labels follow the locale. */\nconst localizedContentTypes = computed(() => ({\n ...CONTENT_TYPES,\n 'multipart/form-data': translate('apiClient.requestBody.multipartForm'),\n 'application/x-www-form-urlencoded': translate(\n 'apiClient.requestBody.formUrlEncoded',\n ),\n 'application/octet-stream': translate('apiClient.requestBody.binaryFile'),\n 'other': translate('apiClient.requestBody.other'),\n 'none': translate('apiClient.requestBody.none'),\n}))\n\nconst contentTypeOptions = computed<{ id: string; label: string }[]>(() => {\n const builtIn = objectEntries(localizedContentTypes.value).map(\n ([id, label]) => ({\n id,\n label,\n }),\n )\n\n const extras = Object.keys(requestBody?.content ?? {})\n .filter((type) => {\n if (type in CONTENT_TYPES) {\n return false\n }\n // Same essence as a built-in (e.g. `application/json; charset=utf-8`) — do not duplicate the row.\n return !(contentTypeLabel(type) in CONTENT_TYPES)\n })\n .map((type) => ({ id: type, label: contentTypeLabel(type) }))\n\n return [...builtIn, ...extras]\n})\n\nconst selectedContentTypeModel = computed<{ id: string; label: string }>({\n get: () => {\n const found = contentTypeOptions.value.find(\n (it) => it.id === selectedContentType.value,\n )\n if (found) {\n return found\n }\n\n const essence = contentTypeLabel(selectedContentType.value)\n const friendly =\n localizedContentTypes.value[essence as keyof typeof CONTENT_TYPES] ??\n essence\n\n return {\n id: selectedContentType.value,\n label: friendly,\n }\n },\n set: (v) => {\n emits('update:contentType', { value: v.id })\n },\n})\n\nfunction handleFileUpload(callback: (file: File) => void) {\n const { open } = useFileDialog({\n onChange: (files) => {\n const file = files?.[0]\n if (file) {\n callback(file)\n }\n },\n multiple: false,\n accept: '*/*',\n })\n open()\n}\n\n/** Dereferenced example */\nconst example = computed(\n () =>\n requestBody &&\n getExampleFromBody(\n requestBody,\n selectedContentType.value,\n exampleKey,\n requestBodyCompositionSelection,\n openapiVersion,\n ),\n)\n\n/** Convert the example value to a string for the code editor */\nconst bodyValue = computed(() => {\n if (!example.value) {\n return ''\n }\n\n const selected = getExampleValue(example.value)\n const explicitText = getExplicitExampleText(\n selected,\n selectedContentType.value,\n 2,\n )\n if (explicitText !== undefined) {\n return explicitText\n }\n const value = selected?.value\n if (typeof value === 'string') {\n return value\n }\n\n return JSON.stringify(value, null, 2)\n})\n\n/** Resolved schema for the request body */\nconst bodySchema = computed<SchemaObject | undefined>(() => {\n return resolve.schema(\n requestBody?.content?.[selectedContentType.value]?.schema ??\n requestBody?.content?.[selectedContentType.value]?.itemSchema,\n )\n})\n\n/** Codec for structured (JSON/YAML) bodies, undefined for everything else */\nconst structuredCodec = computed(() =>\n selectedContentType.value === 'none'\n ? undefined\n : getStructuredBodyCodec(selectedContentType.value),\n)\n\n/** Parsed body value for the form view — `ok: false` when the raw text is not parseable */\nconst parsedBody = computed<{ ok: boolean; value?: unknown }>(() => {\n const codec = structuredCodec.value\n if (!codec) {\n return { ok: false }\n }\n\n const selected = getExampleValue(example.value ?? undefined)\n // Structured data is already parsed; parsing strings again changes the payload type.\n if (example.value?.dataValue !== undefined) {\n return { ok: true, value: example.value.dataValue }\n }\n const raw = selected?.value\n // An empty body is still form-editable: rows come from the schema.\n if (raw === undefined || raw === null || raw === '') {\n return { ok: true, value: {} }\n }\n if (typeof raw === 'string') {\n try {\n return { ok: true, value: codec.parse(raw) }\n } catch {\n return { ok: false }\n }\n }\n // Spec-provided examples can be stored as objects directly.\n return { ok: true, value: raw }\n})\n\n/**\n * An edited example normally takes precedence over schema-generated data. When a different\n * composition branch is selected, however, keeping that example leaves the editor showing the\n * previous branch. Track the selection itself instead of inspecting discriminator fields so this\n * also works for compositions whose members are distinguished only by their shape.\n */\nconst compositionSelectionKey = computed(() =>\n JSON.stringify(requestBodyCompositionSelection ?? {}),\n)\n\n// Switching operations changes the body (and often the selection) at once, which is not a branch\n// change, so we only regenerate the body when the selection changes while the operation and example\n// stay the same. Watching all three together and comparing against the watcher's own previous values\n// keeps this correct regardless of the order in which props update or watchers flush.\nwatch(\n [() => requestBody, () => exampleKey, compositionSelectionKey],\n (\n [, , selection],\n [previousRequestBody, previousExampleKey, previousSelection],\n ) => {\n const operationChanged =\n requestBody !== previousRequestBody || exampleKey !== previousExampleKey\n const selectionChanged = selection !== previousSelection\n\n // Going from no selection to a populated one is the modal applying the reference's selection as\n // the panel opens, not the user switching branches. Resetting here would discard the body's named\n // example on the very first open (issue #10075), so only a change between two populated selections\n // counts as a real branch switch.\n const hadNoPreviousSelection = previousSelection === '{}'\n\n // Only a genuine branch switch within the same operation should reset the edited body. An empty\n // selection means there is no composition to switch between (or the selection was cleared on an\n // operation change), so there is nothing to reset.\n if (\n operationChanged ||\n !selectionChanged ||\n hadNoPreviousSelection ||\n Object.keys(requestBodyCompositionSelection ?? {}).length === 0\n ) {\n return\n }\n\n const codec = structuredCodec.value\n const isXml = isXmlMediaType(selectedContentType.value)\n if (!requestBody || (!codec && !isXml)) {\n return\n }\n\n // Regenerate through the same helper the initial example uses so a reset produces exactly the\n // value a fresh render of this branch would, rather than a hand-copied second code path.\n const selectedValue = getSchemaExampleFromBody(\n requestBody,\n selectedContentType.value,\n requestBodyCompositionSelection,\n openapiVersion,\n )\n\n emits('update:value', {\n // A branch with no writable content generates `null`/`undefined`; clear the editor rather than\n // writing the literal text `null` or leaving the previously selected branch's body behind.\n payload:\n selectedValue == null\n ? ''\n : isXml\n ? String(selectedValue)\n : codec!.stringify(selectedValue),\n contentType: selectedContentType.value,\n })\n },\n)\n\n/** The form view only works on an object-shaped body root */\nconst isFormViewAvailable = computed(\n () => parsedBody.value.ok && isObject(parsedBody.value.value),\n)\n\n/**\n * Show the Form/Raw toggle for structured bodies that are (or per schema should be)\n * an object at the root. Non-object roots (arrays, primitives) stay raw-only.\n */\nconst showBodyViewToggle = computed(\n () =>\n Boolean(structuredCodec.value) &&\n (isFormViewAvailable.value ||\n Boolean(bodySchema.value && isObjectSchema(bodySchema.value))),\n)\n\n/** Selected body view, seeded from the document default (raw unless configured) */\nconst bodyView = ref<'form' | 'raw'>(defaultView)\n\n// Fall back to raw when the form view stops being available (e.g. the content type\n// changed to a non-structured one, or an external edit made the body unparseable).\nwatch(isFormViewAvailable, (ok) => {\n if (!ok) {\n bodyView.value = 'raw'\n }\n})\n\n/**\n * Whether we can offer to generate a fresh example from the schema.\n *\n * Only structured (JSON/YAML) bodies qualify: they have a schema to generate from and a text\n * representation we can seed the new example with. Binary and form bodies have their own editors,\n * so the button would have nothing meaningful to write.\n */\nconst canGenerateExample = computed(() =>\n Boolean(structuredCodec.value && bodySchema.value),\n)\n</script>\n<template>\n <CollapsibleSection>\n <template #title>{{ title }}</template>\n <DataTable\n :columns=\"['']\"\n presentational>\n <DataTableHeader\n class=\"relative col-span-full flex h-8 cursor-pointer items-center justify-between border-r-0 !p-0\">\n <ScalarListbox\n v-model=\"selectedContentTypeModel\"\n :options=\"contentTypeOptions\"\n teleport>\n <ScalarButton\n class=\"text-c-2 hover:text-c-1 flex h-full w-fit gap-1.5 px-3 font-normal\"\n fullWidth\n variant=\"ghost\">\n <span>{{ selectedContentTypeModel.label }}</span>\n <ScalarIcon\n icon=\"ChevronDown\"\n size=\"md\" />\n </ScalarButton>\n </ScalarListbox>\n <div class=\"flex items-center gap-1\">\n <!--\n Generates a new example from the schema so the auto-generated fields stay reachable\n even once the document ships custom examples.\n -->\n <ScalarIconButton\n v-if=\"canGenerateExample\"\n aria-label=\"Generate example from schema\"\n :icon=\"ScalarIconMagicWand\"\n label=\"Generate example from schema\"\n size=\"sm\"\n tooltip\n @click.stop=\"\n emits('generate:example', { contentType: selectedContentType })\n \" />\n <RequestBodyViewToggle\n v-if=\"showBodyViewToggle\"\n :disabled=\"!isFormViewAvailable\"\n :modelValue=\"bodyView\"\n @update:modelValue=\"(v) => (bodyView = v)\" />\n </div>\n </DataTableHeader>\n <DataTableRow>\n <!-- No Body -->\n <template v-if=\"selectedContentType === 'none'\">\n <div\n class=\"text-c-3 flex min-h-10 w-full items-center justify-center border-t p-2 text-sm\">\n <span>{{ translate('apiClient.requestBody.noBody') }}</span>\n </div>\n </template>\n\n <!-- Binary File -->\n <template\n v-else-if=\"selectedContentType === 'application/octet-stream'\">\n <div\n class=\"flex items-center justify-center overflow-hidden border-t p-1.5\">\n <template\n v-if=\"\n getFileName(unpackProxyObject(example?.value)) !== undefined\n \">\n <span\n class=\"text-c-2 w-full max-w-full overflow-hidden rounded border px-1.5 py-1 text-xs whitespace-nowrap\">\n {{ getFileName(unpackProxyObject(example?.value)) }}\n </span>\n <ScalarButton\n class=\"bg-b-2 hover:bg-b-3 text-c-2 ml-1 border-0 shadow-none\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"\n emits('update:value', {\n payload: undefined,\n contentType: selectedContentType,\n })\n \">\n {{ translate('apiClient.requestBody.delete') }}\n </ScalarButton>\n </template>\n <template v-else>\n <ScalarButton\n class=\"bg-b-2 hover:bg-b-3 text-c-2 border-0 shadow-none\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"\n () =>\n handleFileUpload((file) =>\n emits('update:value', {\n payload: file,\n contentType: selectedContentType,\n }),\n )\n \">\n <span>{{ translate('apiClient.requestBody.selectFile') }}</span>\n <ScalarIcon\n class=\"ml-1\"\n icon=\"Upload\"\n size=\"xs\"\n thickness=\"2.5\" />\n </ScalarButton>\n </template>\n </div>\n </template>\n\n <!-- Form Data / URL Encoded -->\n <template\n v-else-if=\"\n selectedContentType === 'multipart/form-data' ||\n selectedContentType === 'application/x-www-form-urlencoded'\n \">\n <RequestBodyForm\n :bodySchema\n :environment\n :example\n :selectedContentType\n @update:formValue=\"\n (payload) =>\n emits('update:formValue', {\n payload,\n contentType: selectedContentType,\n })\n \" />\n </template>\n\n <!-- Code/Other -->\n <template v-else>\n <!-- Schema-driven form view for structured (JSON/YAML) bodies -->\n <RequestBodyStructured\n v-if=\"bodyView === 'form' && isFormViewAvailable\"\n :bodySchema\n :contentType=\"selectedContentType\"\n :environment\n :parsedValue=\"parsedBody.value\"\n @update:value=\"\n (value) =>\n emits('update:value', {\n payload: value,\n contentType: selectedContentType,\n })\n \" />\n <!-- Editable raw body with a copy button revealed on hover/focus -->\n <div\n v-else\n class=\"group/code-block relative border-t\">\n <CodeInput\n class=\"px-3\"\n content=\"\"\n :environment=\"environment\"\n :language=\"selectedLanguage\"\n lineNumbers\n lint\n :modelValue=\"bodyValue\"\n withFakeData\n @update:modelValue=\"\n (value) =>\n emits('update:value', {\n payload: value,\n contentType: selectedContentType,\n })\n \" />\n <ScalarCodeBlockCopy\n v-if=\"bodyValue\"\n class=\"absolute top-1.5 right-1.5\"\n :content=\"bodyValue\"\n :lang=\"selectedLanguage\"\n showLang>\n <template #backdrop>\n <ScalarCopyBackdrop class=\"-top-1 -right-1.5\" />\n </template>\n </ScalarCodeBlockCopy>\n </div>\n </template>\n </DataTableRow>\n </DataTable>\n </CollapsibleSection>\n</template>\n<style scoped>\n:deep(.cm-content) {\n font-size: var(--scalar-small);\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RequestBody.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/request-block/components/RequestBody.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components/button'\nimport { ScalarCodeBlockCopy } from '@scalar/components/code-block'\nimport { ScalarCopyBackdrop } from '@scalar/components/copy'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { ScalarIconButton } from '@scalar/components/icon-button'\nimport { ScalarListbox } from '@scalar/components/listbox'\nimport { CONTENT_TYPES } from '@scalar/helpers/http/content-types'\nimport { isXmlMediaType } from '@scalar/helpers/http/is-xml-media-type'\nimport { parseMimeType } from '@scalar/helpers/http/mime-type'\nimport { isObject } from '@scalar/helpers/object/is-object'\nimport { objectEntries } from '@scalar/helpers/object/object-entries'\nimport { ScalarIconMagicWand } from '@scalar/icons'\nimport type { ApiReferenceEvents } from '@scalar/workspace-store/events'\nimport {\n getExampleValue,\n getExplicitExampleText,\n} from '@scalar/workspace-store/helpers/get-example-value'\nimport { unpackProxyObject } from '@scalar/workspace-store/helpers/unpack-proxy'\nimport {\n getExampleFromBody,\n getSchemaExampleFromBody,\n getSelectedBodyContentType,\n} from '@scalar/workspace-store/request-example'\nimport { resolve } from '@scalar/workspace-store/resolve'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type {\n RequestBodyObject,\n SchemaObject,\n} from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { isObjectSchema } from '@scalar/workspace-store/schemas/v3.2/strict/type-guards'\nimport { computed, ref, watch } from 'vue'\n\nimport { useFileDialog } from '@/hooks/use-file-dialog'\nimport RequestBodyForm from '@/v2/blocks/request-block/components/RequestBodyForm.vue'\nimport RequestBodyStructured from '@/v2/blocks/request-block/components/RequestBodyStructured.vue'\nimport RequestBodyViewToggle from '@/v2/blocks/request-block/components/RequestBodyViewToggle.vue'\nimport { getFileName } from '@/v2/blocks/request-block/helpers/files'\nimport { getStructuredBodyCodec } from '@/v2/blocks/request-block/helpers/structured-body-codec'\nimport { CodeInput } from '@/v2/components/code-input'\nimport {\n DataTable,\n DataTableHeader,\n DataTableRow,\n} from '@/v2/components/data-table'\nimport { CollapsibleSection } from '@/v2/components/layout'\nimport { useLocalization } from '@/v2/features/localization'\n\nconst {\n requestBody,\n exampleKey,\n environment,\n requestBodyCompositionSelection,\n openapiVersion,\n title,\n defaultView = 'raw',\n} = defineProps<{\n /** Request body */\n requestBody?: RequestBodyObject\n /** Currently selected example key for the current operation */\n exampleKey: string\n /** Display title */\n title: string\n /** Selected environment */\n environment: XScalarEnvironment\n /** Originating OpenAPI version, used for XML mapping rules. */\n openapiVersion?: string\n /** Selected anyOf/oneOf request-body variants keyed by schema path */\n requestBodyCompositionSelection?: Record<string, number>\n /**\n * Initial view for structured (JSON/YAML) bodies. Comes from the\n * `x-scalar-default-request-body-view` document extension and falls back to `raw`\n * whenever the body cannot be shown as a form.\n */\n defaultView?: 'form' | 'raw'\n}>()\n\nconst emits = defineEmits<{\n (e: 'update:contentType', payload: { value: string }): void\n /** We use this event to update raw values */\n (\n e: 'update:value',\n payload: Pick<\n ApiReferenceEvents['operation:update:requestBody:value'],\n 'payload' | 'contentType'\n >,\n ): void\n /** We use this event when updating form data only */\n (\n e: 'update:formValue',\n payload: Pick<\n ApiReferenceEvents['operation:update:requestBody:formValue'],\n 'payload' | 'contentType'\n >,\n ): void\n /**\n * Create a new example seeded with values generated from the request body schema.\n *\n * The block itself owns the document and operation identity needed to create an example, so we\n * only report the content type the user is currently looking at.\n */\n (e: 'generate:example', payload: { contentType: string }): void\n}>()\n\nconst { translate } = useLocalization()\n\n// Map a content type to a language for the code editor\nconst contentTypeToLanguageMap = {\n 'application/json': 'json',\n 'application/xml': 'xml',\n 'application/yaml': 'yaml',\n} as const\n\n/** Selected content type with default */\nconst selectedContentType = computed(\n () => getSelectedBodyContentType(requestBody, exampleKey) ?? 'none',\n)\n\n/** Keep the editor and copy label aligned with the selected content type. */\nconst selectedLanguage = computed(() =>\n isXmlMediaType(selectedContentType.value)\n ? 'xml'\n : (contentTypeToLanguageMap[\n selectedContentType.value as keyof typeof contentTypeToLanguageMap\n ] ?? 'plaintext'),\n)\n\n/**\n * Strips MIME parameters (such as `charset=utf-8`) for dropdown labels, aligned with how we treat\n * media types elsewhere (see api-reference `normalizeMimeType` / WHATWG essence).\n */\nconst contentTypeLabel = (raw: string): string => parseMimeType(raw).essence\n\n/**\n * Build the dropdown options for the content type selector.\n *\n * The list is composed of two groups:\n * 1. The built-in content types we ship friendly labels for (JSON, XML, Multipart Form, etc.).\n * 2. Any additional content types defined on the OpenAPI request body that are not in the built-in list\n * (for example `text/csv`, `application/pdf`, vendor types like `application/vnd.api+json`, ...).\n *\n * The OpenAPI-defined extras are appended at the bottom so the well-known options stay on top, and\n * users can always pick the exact content type the operation actually accepts.\n */\n/** MIME values remain stable while their human-readable labels follow the locale. */\nconst localizedContentTypes = computed(() => ({\n ...CONTENT_TYPES,\n 'multipart/form-data': translate('apiClient.requestBody.multipartForm'),\n 'application/x-www-form-urlencoded': translate(\n 'apiClient.requestBody.formUrlEncoded',\n ),\n 'application/octet-stream': translate('apiClient.requestBody.binaryFile'),\n 'other': translate('apiClient.requestBody.other'),\n 'none': translate('apiClient.requestBody.none'),\n}))\n\nconst contentTypeOptions = computed<{ id: string; label: string }[]>(() => {\n const builtIn = objectEntries(localizedContentTypes.value).map(\n ([id, label]) => ({\n id,\n label,\n }),\n )\n\n const extras = Object.keys(requestBody?.content ?? {})\n .filter((type) => {\n if (type in CONTENT_TYPES) {\n return false\n }\n // Same essence as a built-in (e.g. `application/json; charset=utf-8`) — do not duplicate the row.\n return !(contentTypeLabel(type) in CONTENT_TYPES)\n })\n .map((type) => ({ id: type, label: contentTypeLabel(type) }))\n\n return [...builtIn, ...extras]\n})\n\nconst selectedContentTypeModel = computed<{ id: string; label: string }>({\n get: () => {\n const found = contentTypeOptions.value.find(\n (it) => it.id === selectedContentType.value,\n )\n if (found) {\n return found\n }\n\n const essence = contentTypeLabel(selectedContentType.value)\n const friendly =\n localizedContentTypes.value[essence as keyof typeof CONTENT_TYPES] ??\n essence\n\n return {\n id: selectedContentType.value,\n label: friendly,\n }\n },\n set: (v) => {\n emits('update:contentType', { value: v.id })\n },\n})\n\nfunction handleFileUpload(callback: (file: File) => void) {\n const { open } = useFileDialog({\n onChange: (files) => {\n const file = files?.[0]\n if (file) {\n callback(file)\n }\n },\n multiple: false,\n accept: '*/*',\n })\n open()\n}\n\n/** Dereferenced example */\nconst example = computed(\n () =>\n requestBody &&\n getExampleFromBody(\n requestBody,\n selectedContentType.value,\n exampleKey,\n requestBodyCompositionSelection,\n openapiVersion,\n ),\n)\n\n/** Convert the example value to a string for the code editor */\nconst bodyValue = computed(() => {\n if (!example.value) {\n return ''\n }\n\n const selected = getExampleValue(example.value)\n const explicitText = getExplicitExampleText(\n selected,\n selectedContentType.value,\n 2,\n )\n if (explicitText !== undefined) {\n return explicitText\n }\n const value = selected?.value\n if (typeof value === 'string') {\n return value\n }\n\n return JSON.stringify(value, null, 2)\n})\n\n/** Resolved schema for the request body */\nconst bodySchema = computed<SchemaObject | undefined>(() => {\n return resolve.schema(\n requestBody?.content?.[selectedContentType.value]?.schema ??\n requestBody?.content?.[selectedContentType.value]?.itemSchema,\n )\n})\n\n/** Codec for structured (JSON/YAML) bodies, undefined for everything else */\nconst structuredCodec = computed(() =>\n selectedContentType.value === 'none'\n ? undefined\n : getStructuredBodyCodec(selectedContentType.value),\n)\n\n/** Parsed body value for the form view — `ok: false` when the raw text is not parseable */\nconst parsedBody = computed<{ ok: boolean; value?: unknown }>(() => {\n const codec = structuredCodec.value\n if (!codec) {\n return { ok: false }\n }\n\n const selected = getExampleValue(example.value ?? undefined)\n // Structured data is already parsed; parsing strings again changes the payload type.\n if (example.value?.dataValue !== undefined) {\n return { ok: true, value: example.value.dataValue }\n }\n const raw = selected?.value\n // An empty body is still form-editable: rows come from the schema.\n if (raw === undefined || raw === null || raw === '') {\n return { ok: true, value: {} }\n }\n if (typeof raw === 'string') {\n try {\n return { ok: true, value: codec.parse(raw) }\n } catch {\n return { ok: false }\n }\n }\n // Spec-provided examples can be stored as objects directly.\n return { ok: true, value: raw }\n})\n\n/**\n * An edited example normally takes precedence over schema-generated data. When a different\n * composition branch is selected, however, keeping that example leaves the editor showing the\n * previous branch. Track the selection itself instead of inspecting discriminator fields so this\n * also works for compositions whose members are distinguished only by their shape.\n */\nconst compositionSelectionKey = computed(() =>\n JSON.stringify(requestBodyCompositionSelection ?? {}),\n)\n\n// Switching operations changes the body (and often the selection) at once, which is not a branch\n// change, so we only regenerate the body when the selection changes while the operation and example\n// stay the same. Watching all three together and comparing against the watcher's own previous values\n// keeps this correct regardless of the order in which props update or watchers flush.\nwatch(\n [() => requestBody, () => exampleKey, compositionSelectionKey],\n (\n [, , selection],\n [previousRequestBody, previousExampleKey, previousSelection],\n ) => {\n const operationChanged =\n requestBody !== previousRequestBody || exampleKey !== previousExampleKey\n const selectionChanged = selection !== previousSelection\n\n // Going from no selection to a populated one is the modal applying the reference's selection as\n // the panel opens, not the user switching branches. Resetting here would discard the body's named\n // example on the very first open (issue #10075), so only a change between two populated selections\n // counts as a real branch switch.\n const hadNoPreviousSelection = previousSelection === '{}'\n\n // Only a genuine branch switch within the same operation should reset the edited body. An empty\n // selection means there is no composition to switch between (or the selection was cleared on an\n // operation change), so there is nothing to reset.\n if (\n operationChanged ||\n !selectionChanged ||\n hadNoPreviousSelection ||\n Object.keys(requestBodyCompositionSelection ?? {}).length === 0\n ) {\n return\n }\n\n const codec = structuredCodec.value\n const isXml = isXmlMediaType(selectedContentType.value)\n if (!requestBody || (!codec && !isXml)) {\n return\n }\n\n // Regenerate through the same helper the initial example uses so a reset produces exactly the\n // value a fresh render of this branch would, rather than a hand-copied second code path.\n const selectedValue = getSchemaExampleFromBody(\n requestBody,\n selectedContentType.value,\n requestBodyCompositionSelection,\n openapiVersion,\n )\n\n emits('update:value', {\n // A branch with no writable content generates `null`/`undefined`; clear the editor rather than\n // writing the literal text `null` or leaving the previously selected branch's body behind.\n payload:\n selectedValue == null\n ? ''\n : isXml\n ? String(selectedValue)\n : codec!.stringify(selectedValue),\n contentType: selectedContentType.value,\n })\n },\n)\n\n/** The form view only works on an object-shaped body root */\nconst isFormViewAvailable = computed(\n () => parsedBody.value.ok && isObject(parsedBody.value.value),\n)\n\n/**\n * Show the Form/Raw toggle for structured bodies that are (or per schema should be)\n * an object at the root. Non-object roots (arrays, primitives) stay raw-only.\n */\nconst showBodyViewToggle = computed(\n () =>\n Boolean(structuredCodec.value) &&\n (isFormViewAvailable.value ||\n Boolean(bodySchema.value && isObjectSchema(bodySchema.value))),\n)\n\n/** Selected body view, seeded from the document default (raw unless configured) */\nconst bodyView = ref<'form' | 'raw'>(defaultView)\n\n// Fall back to raw when the form view stops being available (e.g. the content type\n// changed to a non-structured one, or an external edit made the body unparseable).\nwatch(isFormViewAvailable, (ok) => {\n if (!ok) {\n bodyView.value = 'raw'\n }\n})\n\n/**\n * Whether we can offer to generate a fresh example from the schema.\n *\n * Only structured (JSON/YAML) bodies qualify: they have a schema to generate from and a text\n * representation we can seed the new example with. Binary and form bodies have their own editors,\n * so the button would have nothing meaningful to write.\n */\nconst canGenerateExample = computed(() =>\n Boolean(structuredCodec.value && bodySchema.value),\n)\n</script>\n<template>\n <CollapsibleSection>\n <template #title>{{ title }}</template>\n <DataTable\n :columns=\"['']\"\n presentational>\n <DataTableHeader\n class=\"relative col-span-full flex h-8 cursor-pointer items-center justify-between border-r-0 !p-0\">\n <ScalarListbox\n v-model=\"selectedContentTypeModel\"\n :options=\"contentTypeOptions\"\n teleport>\n <ScalarButton\n class=\"text-c-2 hover:text-c-1 flex h-full w-fit gap-1.5 px-3 font-normal\"\n fullWidth\n variant=\"ghost\">\n <span>{{ selectedContentTypeModel.label }}</span>\n <ScalarIcon\n icon=\"ChevronDown\"\n size=\"md\" />\n </ScalarButton>\n </ScalarListbox>\n <div class=\"flex items-center gap-1\">\n <!--\n Generates a new example from the schema so the auto-generated fields stay reachable\n even once the document ships custom examples.\n -->\n <ScalarIconButton\n v-if=\"canGenerateExample\"\n aria-label=\"Generate example from schema\"\n :icon=\"ScalarIconMagicWand\"\n label=\"Generate example from schema\"\n size=\"sm\"\n tooltip\n @click.stop=\"\n emits('generate:example', { contentType: selectedContentType })\n \" />\n <RequestBodyViewToggle\n v-if=\"showBodyViewToggle\"\n :disabled=\"!isFormViewAvailable\"\n :modelValue=\"bodyView\"\n @update:modelValue=\"(v) => (bodyView = v)\" />\n </div>\n </DataTableHeader>\n <DataTableRow>\n <!-- No Body -->\n <template v-if=\"selectedContentType === 'none'\">\n <div\n class=\"text-c-3 flex min-h-10 w-full items-center justify-center border-t p-2 text-sm\">\n <span>{{ translate('apiClient.requestBody.noBody') }}</span>\n </div>\n </template>\n\n <!-- Binary File -->\n <template\n v-else-if=\"selectedContentType === 'application/octet-stream'\">\n <div\n class=\"flex items-center justify-center overflow-hidden border-t p-1.5\">\n <template\n v-if=\"\n getFileName(unpackProxyObject(example?.value)) !== undefined\n \">\n <span\n class=\"text-c-2 w-full max-w-full overflow-hidden rounded border px-1.5 py-1 text-xs whitespace-nowrap\">\n {{ getFileName(unpackProxyObject(example?.value)) }}\n </span>\n <ScalarButton\n class=\"bg-b-2 hover:bg-b-3 text-c-2 ml-1 border-0 shadow-none\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"\n emits('update:value', {\n payload: undefined,\n contentType: selectedContentType,\n })\n \">\n {{ translate('apiClient.requestBody.delete') }}\n </ScalarButton>\n </template>\n <template v-else>\n <ScalarButton\n class=\"bg-b-2 hover:bg-b-3 text-c-2 border-0 shadow-none\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"\n () =>\n handleFileUpload((file) =>\n emits('update:value', {\n payload: file,\n contentType: selectedContentType,\n }),\n )\n \">\n <span>{{ translate('apiClient.requestBody.selectFile') }}</span>\n <ScalarIcon\n class=\"ml-1\"\n icon=\"Upload\"\n size=\"xs\"\n thickness=\"2.5\" />\n </ScalarButton>\n </template>\n </div>\n </template>\n\n <!-- Form Data / URL Encoded -->\n <template\n v-else-if=\"\n selectedContentType === 'multipart/form-data' ||\n selectedContentType === 'application/x-www-form-urlencoded'\n \">\n <RequestBodyForm\n :bodySchema\n :environment\n :example\n :selectedContentType\n @update:formValue=\"\n (payload) =>\n emits('update:formValue', {\n payload,\n contentType: selectedContentType,\n })\n \" />\n </template>\n\n <!-- Code/Other -->\n <template v-else>\n <!-- Schema-driven form view for structured (JSON/YAML) bodies -->\n <RequestBodyStructured\n v-if=\"bodyView === 'form' && isFormViewAvailable\"\n :bodySchema\n :contentType=\"selectedContentType\"\n :environment\n :parsedValue=\"parsedBody.value\"\n @update:value=\"\n (value) =>\n emits('update:value', {\n payload: value,\n contentType: selectedContentType,\n })\n \" />\n <!-- Editable raw body with a copy button revealed on hover/focus -->\n <div\n v-else\n class=\"group/code-block relative min-w-0 border-t\">\n <CodeInput\n class=\"px-3\"\n content=\"\"\n :environment=\"environment\"\n :language=\"selectedLanguage\"\n lineNumbers\n lint\n :modelValue=\"bodyValue\"\n withFakeData\n @update:modelValue=\"\n (value) =>\n emits('update:value', {\n payload: value,\n contentType: selectedContentType,\n })\n \" />\n <ScalarCodeBlockCopy\n v-if=\"bodyValue\"\n class=\"absolute top-1.5 right-1.5\"\n :content=\"bodyValue\"\n :lang=\"selectedLanguage\"\n showLang>\n <template #backdrop>\n <ScalarCopyBackdrop class=\"-top-1 -right-1.5\" />\n </template>\n </ScalarCodeBlockCopy>\n </div>\n </template>\n </DataTableRow>\n </DataTable>\n </CollapsibleSection>\n</template>\n<style scoped>\n:deep(.cm-content) {\n font-size: var(--scalar-small);\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddressBarHistory.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AddressBarHistory.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue"],"names":[],"mappings":"AAkIA,KAAK,WAAW,GAAG;IACjB,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd,wCAAwC;IACxC,OAAO,EAAE,OAAO,EAAE,CAAA;CACnB,CAAC;AAcF,MAAM,MAAM,OAAO,GAAG;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAwRD,QAAA,MAAM,YAAY;;;eArS6B,MAAM;;;;;eAAN,MAAM;;kFAwSnD,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -2,7 +2,7 @@ import _plugin_vue_export_helper_default from "../../../../_virtual/_plugin-vue_
|
|
|
2
2
|
import AddressBarHistory_vue_vue_type_script_setup_true_lang_default from "./AddressBarHistory.vue.script.js";
|
|
3
3
|
/* empty css */
|
|
4
4
|
//#region src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue
|
|
5
|
-
var AddressBarHistory_default = /*#__PURE__*/ _plugin_vue_export_helper_default(AddressBarHistory_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-
|
|
5
|
+
var AddressBarHistory_default = /*#__PURE__*/ _plugin_vue_export_helper_default(AddressBarHistory_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-8704eaef"]]);
|
|
6
6
|
//#endregion
|
|
7
7
|
export { AddressBarHistory_default as default };
|
|
8
8
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddressBarHistory.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'\nimport {\n ScalarFloating,\n ScalarFloatingBackdrop,\n} from '@scalar/components/floating'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { formatMilliseconds } from '@scalar/helpers/formatters/format-milliseconds'\nimport { httpStatusCodes } from '@scalar/helpers/http/http-status-codes'\n\nimport { HttpMethod } from '@/components/HttpMethod'\nimport ValueEmitter from '@/v2/components/layout/ValueEmitter.vue'\nimport { useLocalization } from '@/v2/features/localization'\n\nimport { getStatusCodeColor } from './httpStatusCodeColors'\n\nconst { target } = defineProps<{\n /** The id of the target to use for the popover (e.g. address bar) */\n target: string\n /** List of all request history items */\n history: History[]\n}>()\n\nconst emits = defineEmits<{\n /** Select a request history item by index */\n (e: 'select:history:item', payload: { index: number }): void\n /** Update the open state of the history popover */\n (e: 'update:open', value: boolean): void\n}>()\n\nconst { translate } = useLocalization()\n\nexport type History = {\n method: string\n path: string\n duration: number\n status: number\n}\n</script>\n<template>\n <Menu\n v-slot=\"{ open }\"\n as=\"div\">\n <!-- Emit the slot value back out the parent -->\n <ValueEmitter\n :value=\"open\"\n @change=\"(value) => emits('update:open', value)\"\n @unmount=\"emits('update:open', false)\" />\n\n <ScalarFloating\n :offset=\"0\"\n resize\n :target=\"target\">\n <!-- History -->\n <MenuButton\n v-if=\"history.length\"\n class=\"address-bar-history-button text-c-3 focus:text-c-1 relative mr-1 rounded-lg p-1.5\">\n <ScalarIcon\n icon=\"History\"\n size=\"sm\"\n thickness=\"2.25\" />\n <span class=\"sr-only\">{{\n translate('apiClient.addressBarHistory.requestHistory')\n }}</span>\n </MenuButton>\n <!-- History shadow and placement-->\n <template\n v-if=\"open\"\n #floating=\"{ width }\">\n <!-- History Item -->\n <MenuItems\n class=\"custom-scroll grid max-h-[inherit] grid-cols-[44px_1fr_repeat(3,auto)] items-center p-0.75\"\n static\n :style=\"{ width }\">\n <MenuItem\n v-for=\"(entry, index) in history\"\n :key=\"index\"\n as=\"button\"\n class=\"font-code ui-active:*:bg-b-2 text-c-2 contents text-sm font-medium *:flex *:h-8 *:cursor-pointer *:items-center *:rounded-none *:px-1.5 *:first:rounded-l *:last:rounded-r\"\n :value=\"index\"\n @click=\"emits('select:history:item', { index })\">\n <HttpMethod\n class=\"text-[11px]\"\n :method=\"entry.method\" />\n <div class=\"min-w-0\">\n <div class=\"text-c-1 min-w-0 truncate\">\n {{ entry.path }}\n </div>\n </div>\n <div>{{ formatMilliseconds(entry.duration) }}</div>\n <div :class=\"[getStatusCodeColor(entry.status).color]\">\n {{ entry.status }}\n </div>\n <div>\n {{ httpStatusCodes[entry.status]?.name }}\n </div>\n </MenuItem>\n </MenuItems>\n <ScalarFloatingBackdrop
|
|
1
|
+
{"version":3,"file":"AddressBarHistory.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'\nimport {\n ScalarFloating,\n ScalarFloatingBackdrop,\n} from '@scalar/components/floating'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { formatMilliseconds } from '@scalar/helpers/formatters/format-milliseconds'\nimport { httpStatusCodes } from '@scalar/helpers/http/http-status-codes'\n\nimport { HttpMethod } from '@/components/HttpMethod'\nimport ValueEmitter from '@/v2/components/layout/ValueEmitter.vue'\nimport { useLocalization } from '@/v2/features/localization'\n\nimport { getStatusCodeColor } from './httpStatusCodeColors'\n\nconst { target } = defineProps<{\n /** The id of the target to use for the popover (e.g. address bar) */\n target: string\n /** List of all request history items */\n history: History[]\n}>()\n\nconst emits = defineEmits<{\n /** Select a request history item by index */\n (e: 'select:history:item', payload: { index: number }): void\n /** Update the open state of the history popover */\n (e: 'update:open', value: boolean): void\n}>()\n\nconst { translate } = useLocalization()\n\nexport type History = {\n method: string\n path: string\n duration: number\n status: number\n}\n</script>\n<template>\n <Menu\n v-slot=\"{ open }\"\n as=\"div\">\n <!-- Emit the slot value back out the parent -->\n <ValueEmitter\n :value=\"open\"\n @change=\"(value) => emits('update:open', value)\"\n @unmount=\"emits('update:open', false)\" />\n\n <ScalarFloating\n :offset=\"0\"\n resize\n :target=\"target\">\n <!-- History -->\n <MenuButton\n v-if=\"history.length\"\n class=\"address-bar-history-button text-c-3 focus:text-c-1 relative mr-1 rounded-lg p-1.5\">\n <ScalarIcon\n icon=\"History\"\n size=\"sm\"\n thickness=\"2.25\" />\n <span class=\"sr-only\">{{\n translate('apiClient.addressBarHistory.requestHistory')\n }}</span>\n </MenuButton>\n <!-- History shadow and placement-->\n <template\n v-if=\"open\"\n #floating=\"{ width }\">\n <!-- History Item -->\n <MenuItems\n class=\"custom-scroll grid max-h-[inherit] grid-cols-[44px_1fr_repeat(3,auto)] items-center p-0.75\"\n static\n :style=\"{ width }\">\n <MenuItem\n v-for=\"(entry, index) in history\"\n :key=\"index\"\n as=\"button\"\n class=\"font-code ui-active:*:bg-b-2 text-c-2 contents text-sm font-medium *:flex *:h-8 *:cursor-pointer *:items-center *:rounded-none *:px-1.5 *:first:rounded-l *:last:rounded-r\"\n :value=\"index\"\n @click=\"emits('select:history:item', { index })\">\n <HttpMethod\n class=\"text-[11px]\"\n :method=\"entry.method\" />\n <div class=\"min-w-0\">\n <div class=\"text-c-1 min-w-0 truncate\">\n {{ entry.path }}\n </div>\n </div>\n <div>{{ formatMilliseconds(entry.duration) }}</div>\n <div :class=\"[getStatusCodeColor(entry.status).color]\">\n {{ entry.status }}\n </div>\n <div>\n {{ httpStatusCodes[entry.status]?.name }}\n </div>\n </MenuItem>\n </MenuItems>\n <ScalarFloatingBackdrop\n class=\"rounded-none rounded-b-lg border border-t-0 after:hidden\" />\n </template>\n </ScalarFloating>\n </Menu>\n</template>\n<style scoped>\n.address-bar-history-button:hover {\n background: var(--scalar-background-3);\n}\n.address-bar-history-button:focus-within {\n background: var(--scalar-background-2);\n}\n</style>\n"],"mappings":""}
|
|
@@ -74,7 +74,7 @@ var AddressBarHistory_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
|
|
|
74
74
|
}, 1032, ["value", "onClick"]);
|
|
75
75
|
}), 128))]),
|
|
76
76
|
_: 1
|
|
77
|
-
}, 8, ["style"]), createVNode(unref(ScalarFloatingBackdrop), { class: "
|
|
77
|
+
}, 8, ["style"]), createVNode(unref(ScalarFloatingBackdrop), { class: "rounded-none rounded-b-lg border border-t-0 after:hidden" })]),
|
|
78
78
|
key: "0"
|
|
79
79
|
} : void 0]), 1032, ["target"])]),
|
|
80
80
|
_: 1
|
package/dist/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue.script.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddressBarHistory.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'\nimport {\n ScalarFloating,\n ScalarFloatingBackdrop,\n} from '@scalar/components/floating'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { formatMilliseconds } from '@scalar/helpers/formatters/format-milliseconds'\nimport { httpStatusCodes } from '@scalar/helpers/http/http-status-codes'\n\nimport { HttpMethod } from '@/components/HttpMethod'\nimport ValueEmitter from '@/v2/components/layout/ValueEmitter.vue'\nimport { useLocalization } from '@/v2/features/localization'\n\nimport { getStatusCodeColor } from './httpStatusCodeColors'\n\nconst { target } = defineProps<{\n /** The id of the target to use for the popover (e.g. address bar) */\n target: string\n /** List of all request history items */\n history: History[]\n}>()\n\nconst emits = defineEmits<{\n /** Select a request history item by index */\n (e: 'select:history:item', payload: { index: number }): void\n /** Update the open state of the history popover */\n (e: 'update:open', value: boolean): void\n}>()\n\nconst { translate } = useLocalization()\n\nexport type History = {\n method: string\n path: string\n duration: number\n status: number\n}\n</script>\n<template>\n <Menu\n v-slot=\"{ open }\"\n as=\"div\">\n <!-- Emit the slot value back out the parent -->\n <ValueEmitter\n :value=\"open\"\n @change=\"(value) => emits('update:open', value)\"\n @unmount=\"emits('update:open', false)\" />\n\n <ScalarFloating\n :offset=\"0\"\n resize\n :target=\"target\">\n <!-- History -->\n <MenuButton\n v-if=\"history.length\"\n class=\"address-bar-history-button text-c-3 focus:text-c-1 relative mr-1 rounded-lg p-1.5\">\n <ScalarIcon\n icon=\"History\"\n size=\"sm\"\n thickness=\"2.25\" />\n <span class=\"sr-only\">{{\n translate('apiClient.addressBarHistory.requestHistory')\n }}</span>\n </MenuButton>\n <!-- History shadow and placement-->\n <template\n v-if=\"open\"\n #floating=\"{ width }\">\n <!-- History Item -->\n <MenuItems\n class=\"custom-scroll grid max-h-[inherit] grid-cols-[44px_1fr_repeat(3,auto)] items-center p-0.75\"\n static\n :style=\"{ width }\">\n <MenuItem\n v-for=\"(entry, index) in history\"\n :key=\"index\"\n as=\"button\"\n class=\"font-code ui-active:*:bg-b-2 text-c-2 contents text-sm font-medium *:flex *:h-8 *:cursor-pointer *:items-center *:rounded-none *:px-1.5 *:first:rounded-l *:last:rounded-r\"\n :value=\"index\"\n @click=\"emits('select:history:item', { index })\">\n <HttpMethod\n class=\"text-[11px]\"\n :method=\"entry.method\" />\n <div class=\"min-w-0\">\n <div class=\"text-c-1 min-w-0 truncate\">\n {{ entry.path }}\n </div>\n </div>\n <div>{{ formatMilliseconds(entry.duration) }}</div>\n <div :class=\"[getStatusCodeColor(entry.status).color]\">\n {{ entry.status }}\n </div>\n <div>\n {{ httpStatusCodes[entry.status]?.name }}\n </div>\n </MenuItem>\n </MenuItems>\n <ScalarFloatingBackdrop
|
|
1
|
+
{"version":3,"file":"AddressBarHistory.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'\nimport {\n ScalarFloating,\n ScalarFloatingBackdrop,\n} from '@scalar/components/floating'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { formatMilliseconds } from '@scalar/helpers/formatters/format-milliseconds'\nimport { httpStatusCodes } from '@scalar/helpers/http/http-status-codes'\n\nimport { HttpMethod } from '@/components/HttpMethod'\nimport ValueEmitter from '@/v2/components/layout/ValueEmitter.vue'\nimport { useLocalization } from '@/v2/features/localization'\n\nimport { getStatusCodeColor } from './httpStatusCodeColors'\n\nconst { target } = defineProps<{\n /** The id of the target to use for the popover (e.g. address bar) */\n target: string\n /** List of all request history items */\n history: History[]\n}>()\n\nconst emits = defineEmits<{\n /** Select a request history item by index */\n (e: 'select:history:item', payload: { index: number }): void\n /** Update the open state of the history popover */\n (e: 'update:open', value: boolean): void\n}>()\n\nconst { translate } = useLocalization()\n\nexport type History = {\n method: string\n path: string\n duration: number\n status: number\n}\n</script>\n<template>\n <Menu\n v-slot=\"{ open }\"\n as=\"div\">\n <!-- Emit the slot value back out the parent -->\n <ValueEmitter\n :value=\"open\"\n @change=\"(value) => emits('update:open', value)\"\n @unmount=\"emits('update:open', false)\" />\n\n <ScalarFloating\n :offset=\"0\"\n resize\n :target=\"target\">\n <!-- History -->\n <MenuButton\n v-if=\"history.length\"\n class=\"address-bar-history-button text-c-3 focus:text-c-1 relative mr-1 rounded-lg p-1.5\">\n <ScalarIcon\n icon=\"History\"\n size=\"sm\"\n thickness=\"2.25\" />\n <span class=\"sr-only\">{{\n translate('apiClient.addressBarHistory.requestHistory')\n }}</span>\n </MenuButton>\n <!-- History shadow and placement-->\n <template\n v-if=\"open\"\n #floating=\"{ width }\">\n <!-- History Item -->\n <MenuItems\n class=\"custom-scroll grid max-h-[inherit] grid-cols-[44px_1fr_repeat(3,auto)] items-center p-0.75\"\n static\n :style=\"{ width }\">\n <MenuItem\n v-for=\"(entry, index) in history\"\n :key=\"index\"\n as=\"button\"\n class=\"font-code ui-active:*:bg-b-2 text-c-2 contents text-sm font-medium *:flex *:h-8 *:cursor-pointer *:items-center *:rounded-none *:px-1.5 *:first:rounded-l *:last:rounded-r\"\n :value=\"index\"\n @click=\"emits('select:history:item', { index })\">\n <HttpMethod\n class=\"text-[11px]\"\n :method=\"entry.method\" />\n <div class=\"min-w-0\">\n <div class=\"text-c-1 min-w-0 truncate\">\n {{ entry.path }}\n </div>\n </div>\n <div>{{ formatMilliseconds(entry.duration) }}</div>\n <div :class=\"[getStatusCodeColor(entry.status).color]\">\n {{ entry.status }}\n </div>\n <div>\n {{ httpStatusCodes[entry.status]?.name }}\n </div>\n </MenuItem>\n </MenuItems>\n <ScalarFloatingBackdrop\n class=\"rounded-none rounded-b-lg border border-t-0 after:hidden\" />\n </template>\n </ScalarFloating>\n </Menu>\n</template>\n<style scoped>\n.address-bar-history-button:hover {\n background: var(--scalar-background-3);\n}\n.address-bar-history-button:focus-within {\n background: var(--scalar-background-2);\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;EAuBA,MAAM,QAAQ;EAOd,MAAM,EAAE,cAAc,gBAAgB;;uBAUpC,YA8DO,MAAA,IAAA,GAAA,EA5DL,IAAG,MAAK,GAAA;sBAKmC,EANjC,WAAI,CAGd,YAG2C,sBAAA;KAFxC,OAAO;KACP,UAAM,OAAA,OAAA,OAAA,MAAG,UAAU,MAAK,eAAgB,KAAK;KAC7C,WAAO,OAAA,OAAA,OAAA,MAAA,WAAE,MAAK,eAAA,KAAA;4BAEjB,YAoDiB,MAAA,cAAA,GAAA;KAnDd,QAAQ;KACT,QAAA;KACC,QAAQ,QAAA;;4BAYI,CATL,QAAA,QAAQ,UAAA,UAAA,GADhB,YAUa,MAAA,UAAA,GAAA;;MARX,OAAM;;6BAIe,CAHrB,YAGqB,MAAA,UAAA,GAAA;OAFnB,MAAK;OACL,MAAK;OACL,WAAU;UACZ,mBAES,QAFT,YAES,gBADP,MAAA,SAAA,CAAS,CAAA,4CAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;;;QAKL,OAAA;WACL;kBA6BW,EA7BC,YAAK,CAElB,YA2BY,MAAA,SAAA,GAAA;MA1BV,OAAM;MACN,QAAA;MACC,OAAK,eAAA,EAAI,MAAK,CAAA;;6BAEoB,EAAA,UAAA,IAAA,GADnC,mBAsBW,UAAA,MAAA,WArBgB,QAAA,UAAjB,OAAO,UAAK;2BADtB,YAsBW,MAAA,QAAA,GAAA;QApBR,KAAK;QACN,IAAG;QACH,OAAM;QACL,OAAO;QACP,UAAK,WAAE,MAAK,uBAAA,EAA0B,MAAK,CAAA;;+BAGjB;SAF3B,YAE2B,MAAA,kBAAA,GAAA;UADzB,OAAM;UACL,QAAQ,MAAM;;SACjB,mBAIM,OAJN,YAIM,CAHJ,mBAEM,OAFN,YAEM,gBADD,MAAM,IAAI,GAAA,CAAA,CAAA,CAAA;SAGjB,mBAAmD,OAAA,MAAA,gBAA3C,MAAA,kBAAA,CAAkB,CAAC,MAAM,QAAQ,CAAA,GAAA,CAAA;SACzC,mBAEM,OAAA,EAFA,OAAK,eAAA,CAAG,MAAA,kBAAA,CAAkB,CAAC,MAAM,MAAM,CAAA,CAAE,KAAK,CAAA,EAAA,GAAA,gBAC/C,MAAM,MAAM,GAAA,CAAA;SAEjB,mBAEM,OAAA,MAAA,gBADD,MAAA,eAAA,CAAe,CAAC,MAAM,OAAM,EAAG,IAAI,GAAA,CAAA;;;;;;uBAI5C,YACqE,MAAA,sBAAA,GAAA,EAAnE,OAAM,2DAA0D,CAAA,CAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthSelector.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ScalarButton,\n type ScalarButton as ScalarButtonType,\n} from '@scalar/components/button'\nimport { ScalarComboboxMultiselect } from '@scalar/components/combobox'\nimport type { Icon } from '@scalar/components/icon'\nimport { ScalarIconButton } from '@scalar/components/icon-button'\nimport { ScalarListboxCheckbox } from '@scalar/components/listbox'\nimport { useModal } from '@scalar/components/modal'\nimport { ScalarIconCaretDown, ScalarIconTrash } from '@scalar/icons'\nimport type { SelectedSecurity } from '@scalar/workspace-store/entities/auth'\nimport type {\n AuthMeta,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref'\nimport { unpackProxyObject } from '@scalar/workspace-store/helpers/unpack-proxy'\nimport {\n isAuthOptional,\n type MergedSecuritySchemes,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type {\n OpenApiDocument,\n SecurityRequirementObject,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { computed, ref, useId } from 'vue'\n\nimport DeleteRequestAuthModal from '@/v2/blocks/scalar-auth-selector-block/components/DeleteRequestAuthModal.vue'\nimport type { OAuth2Options } from '@/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue'\nimport {\n formatComplexScheme,\n formatScheme,\n getSecuritySchemeOptions,\n type SecuritySchemeOption,\n} from '@/v2/blocks/scalar-auth-selector-block/helpers/security-scheme'\nimport { CollapsibleSection } from '@/v2/components/layout'\nimport { useLocalization } from '@/v2/features/localization'\n\nimport RequestAuthDataTable from './RequestAuthDataTable.vue'\n\nconst {\n environment,\n eventBus,\n createAnySecurityScheme = false,\n canDeleteSchemes = true,\n defaultOpen = true,\n heading = true,\n hideSingleRequiredScheme = false,\n isStatic = false,\n meta,\n proxyUrl,\n securityRequirements,\n securitySchemes,\n selectedSecurity,\n server,\n title,\n options,\n documentType,\n} = defineProps<{\n environment: XScalarEnvironment\n eventBus: WorkspaceEventBus\n /** Allows adding authentication which is not in the document */\n createAnySecurityScheme?: boolean\n /**\n * Whether schemes can be deleted from the selector. Enabled for the editable client, disabled in\n * the read-only reference where the schemes come from the document and cannot be removed.\n */\n canDeleteSchemes?: boolean\n /** Whether the authentication disclosure should start expanded */\n defaultOpen?: boolean\n /**\n * Whether the title belongs in the document outline. The client renders the\n * selector as one section of a request, so it is a heading there. A surface\n * that only offers the schemes as a card of controls, such as the reference,\n * turns it off and the card is named as a group instead.\n */\n heading?: boolean\n /** Hides a sole active required choice in the reference while preserving client controls. */\n hideSingleRequiredScheme?: boolean\n /** Creates a static disclosure that cannot be collapsed */\n isStatic?: boolean\n meta: AuthMeta\n proxyUrl: string\n securityRequirements: OpenApiDocument['security']\n securitySchemes: MergedSecuritySchemes\n selectedSecurity: SelectedSecurity | undefined\n server: ServerObject | null\n title: string\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n /** Type of the document the schemes belong to, used to label the missing-type warning */\n documentType?: 'openapi' | 'asyncapi'\n}>()\n\nconst { translate, translations } = useLocalization()\n\nconst titleId = useId()\nconst comboboxButtonRef = ref<typeof ScalarButtonType | null>(null)\nconst isDisclosureOpen = ref(false)\n\nconst deleteModal = useModal()\nconst schemeToDelete = ref<{\n label: string\n payload: SecurityRequirementObject\n} | null>(null)\n\n/**\n * Determines if authentication is required or optional\n *\n * Auth is optional when there is an empty security requirement and no complex requirements.\n * Complex requirements have multiple auth schemes combined (e.g., API key + OAuth).\n */\nconst authIndicator = computed<{ icon: Icon; text: string } | null>(() => {\n if (!securityRequirements?.length) {\n return null\n }\n\n const isOptional = isAuthOptional(securityRequirements)\n\n return {\n icon: isOptional ? 'Unlock' : 'Lock',\n text: isOptional\n ? translate('apiClient.authSelector.optional')\n : translate('apiClient.authSelector.required'),\n }\n})\n\n/** All available auth scheme options for the dropdown */\nconst availableSchemeOptions = computed(() =>\n getSecuritySchemeOptions(\n securityRequirements ?? [],\n securitySchemes ?? {},\n selectedSecurity?.selectedSchemes ?? [],\n createAnySecurityScheme,\n translations.value.apiClient,\n ),\n)\n\n/** Currently active auth schemes selected for this operation or collection */\nconst activeSchemeOptions = computed<SecuritySchemeOption[]>(() => {\n const schemes = selectedSecurity?.selectedSchemes\n if (!schemes?.length) {\n return []\n }\n\n return schemes.flatMap((requirement) => {\n const schemeNames = Object.keys(requirement)\n\n if (schemeNames.length === 0) {\n return []\n }\n\n // Complex auth requirement with multiple schemes\n if (schemeNames.length > 1) {\n return formatComplexScheme(requirement)\n }\n\n // Simple auth requirement with single scheme\n const schemeName = schemeNames[0]\n if (!schemeName) {\n return []\n }\n\n const scheme = getResolvedRef(securitySchemes?.[schemeName])\n if (!scheme) {\n return []\n }\n\n return formatScheme({\n name: schemeName,\n value: requirement,\n })\n })\n})\n\n/** Keep selection available whenever it can change auth or recover an empty saved selection. */\nconst showSchemeSelector = computed<boolean>(() => {\n if (\n !hideSingleRequiredScheme ||\n createAnySecurityScheme ||\n securityRequirements?.length !== 1 ||\n Object.keys(securityRequirements[0] ?? {}).length === 0\n ) {\n return true\n }\n\n const schemeOptions = availableSchemeOptions.value.flatMap((option) =>\n 'options' in option ? option.options : [option],\n )\n\n return (\n schemeOptions.length !== 1 ||\n activeSchemeOptions.value.length !== 1 ||\n activeSchemeOptions.value[0]?.id !== schemeOptions[0]?.id\n )\n})\n\n/**\n * Opens the combobox dropdown when clicking the auth indicator badge.\n * Prevents the disclosure from toggling if it is already open.\n */\nconst handleAuthIndicatorClick = (event: Event): void => {\n if (!showSchemeSelector.value) {\n return\n }\n\n if (isDisclosureOpen.value) {\n event.stopPropagation()\n }\n comboboxButtonRef.value?.$el.click()\n}\n\n/**\n * Updates the selected auth schemes.\n * Separates existing schemes from newly created ones for the event payload.\n */\nconst handleSchemeSelection = (selected: SecuritySchemeOption[]): void => {\n const existingSchemes = selected\n .filter((option) => option.payload === undefined)\n .map((option) => unpackProxyObject(option.value, { depth: 2 }))\n\n const newSchemes = selected\n .filter((option) => option.payload !== undefined)\n .map((option) => ({\n name: option.label,\n scheme: option.payload!,\n }))\n\n eventBus.emit('auth:update:selected-security-schemes', {\n selectedRequirements: existingSchemes,\n newSchemes,\n meta,\n })\n}\n\n/** Shows the delete confirmation modal for the selected scheme */\nconst handleDeleteRequest = (option: {\n label: string\n value: SecurityRequirementObject\n}): void => {\n schemeToDelete.value = { label: option.label, payload: option.value }\n deleteModal.show()\n}\n\n/** Deletes the selected auth scheme after confirmation */\nconst handleDeleteConfirm = (): void => {\n if (!schemeToDelete.value) {\n return\n }\n\n eventBus.emit('auth:delete:security-scheme', {\n names: Object.keys(schemeToDelete.value.payload),\n })\n\n schemeToDelete.value = null\n deleteModal.hide()\n}\n\ndefineExpose({\n authIndicator,\n selectedSchemeOptions: activeSchemeOptions,\n schemeOptions: availableSchemeOptions,\n})\n</script>\n<template>\n <CollapsibleSection\n class=\"group/params relative\"\n :defaultOpen\n :heading\n :isStatic=\"isStatic\"\n :itemCount=\"activeSchemeOptions.length\"\n @update:modelValue=\"(open) => (isDisclosureOpen = open)\">\n <template #title>\n <div\n :id=\"titleId\"\n class=\"inline-flex items-center gap-0.5 leading-[20px]\">\n <span>{{ title }}</span>\n\n <span\n v-if=\"authIndicator\"\n class=\"text-c-3 -my-0.5 -mr-1 rounded px-1 py-0.5 leading-[normal] font-normal\"\n :class=\"{\n 'text-c-1': authIndicator.icon === 'Lock',\n 'hover:bg-b-3 hover:text-c-1 cursor-pointer': showSchemeSelector,\n }\"\n data-testid=\"auth-indicator\"\n @click=\"handleAuthIndicatorClick\">\n {{ authIndicator.text }}\n </span>\n </div>\n </template>\n\n <!-- The client keeps its selection and deletion controls. -->\n <template\n v-if=\"showSchemeSelector\"\n #actions>\n <ScalarComboboxMultiselect\n class=\"w-72 text-xs\"\n :modelValue=\"activeSchemeOptions\"\n multiple\n :options=\"availableSchemeOptions\"\n placement=\"bottom-end\"\n teleport\n @delete=\"handleDeleteRequest\"\n @update:modelValue=\"handleSchemeSelection\">\n <ScalarButton\n ref=\"comboboxButtonRef\"\n :aria-describedby=\"titleId\"\n class=\"group/combobox-button hover:text-c-1 text-c-2 flex h-fit w-full items-center gap-1 px-0.75 py-0.25 text-base font-normal\"\n variant=\"ghost\">\n <!-- Single auth scheme selected -->\n <template v-if=\"activeSchemeOptions.length === 1\">\n {{\n translate('apiClient.authSelector.selectedType', {\n type: activeSchemeOptions[0]?.label ?? '',\n })\n }}\n </template>\n\n <!-- Multiple auth schemes selected -->\n <template v-else-if=\"activeSchemeOptions.length > 1\">\n {{ translate('apiClient.authSelector.multipleTypes') }}\n </template>\n\n <!-- No auth schemes selected -->\n <template v-else>\n {{ translate('apiClient.authSelector.selectType') }}\n </template>\n\n <ScalarIconCaretDown\n class=\"size-3 shrink-0 transition-transform duration-100 group-aria-expanded/combobox-button:rotate-180\"\n weight=\"bold\" />\n </ScalarButton>\n\n <template #option=\"{ option, selected }\">\n <ScalarListboxCheckbox\n multiselect\n :selected=\"selected\" />\n <div class=\"min-w-0 flex-1 truncate\">\n {{ option.label }}\n </div>\n <ScalarIconButton\n v-if=\"option.isDeletable && canDeleteSchemes\"\n class=\"-m-0.5 shrink-0 p-0.5 opacity-0 group-hover/item:opacity-100\"\n :icon=\"ScalarIconTrash\"\n :label=\"\n translate('apiClient.authSelector.deleteScheme', {\n name: option.label,\n })\n \"\n size=\"xs\"\n @click.stop=\"handleDeleteRequest(option)\" />\n </template>\n </ScalarComboboxMultiselect>\n </template>\n\n <!-- Auth Table -->\n <RequestAuthDataTable\n :activeAuthIndex=\"selectedSecurity?.selectedIndex ?? 0\"\n :documentType\n :environment\n :eventBus\n :isStatic\n :meta\n :options\n :proxyUrl\n :securitySchemes\n :selectedSchemeOptions=\"activeSchemeOptions\"\n :server />\n\n <!-- Delete Auth Modal -->\n <DeleteRequestAuthModal\n v-if=\"schemeToDelete\"\n :label=\"schemeToDelete.label\"\n :scheme=\"schemeToDelete\"\n :state=\"deleteModal\"\n @close=\"deleteModal.hide()\"\n @delete=\"handleDeleteConfirm\" />\n </CollapsibleSection>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AuthSelector.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ScalarButton,\n type ScalarButton as ScalarButtonType,\n} from '@scalar/components/button'\nimport { ScalarComboboxMultiselect } from '@scalar/components/combobox'\nimport type { Icon } from '@scalar/components/icon'\nimport { ScalarIconButton } from '@scalar/components/icon-button'\nimport { ScalarListboxCheckbox } from '@scalar/components/listbox'\nimport { useModal } from '@scalar/components/modal'\nimport { ScalarIconCaretDown, ScalarIconTrash } from '@scalar/icons'\nimport type { SelectedSecurity } from '@scalar/workspace-store/entities/auth'\nimport type {\n AuthMeta,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref'\nimport { unpackProxyObject } from '@scalar/workspace-store/helpers/unpack-proxy'\nimport {\n isAuthOptional,\n type MergedSecuritySchemes,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type {\n OpenApiDocument,\n SecurityRequirementObject,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { computed, ref, useId } from 'vue'\n\nimport DeleteRequestAuthModal from '@/v2/blocks/scalar-auth-selector-block/components/DeleteRequestAuthModal.vue'\nimport type { OAuth2Options } from '@/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue'\nimport {\n formatComplexScheme,\n formatScheme,\n getSecuritySchemeOptions,\n type SecuritySchemeOption,\n} from '@/v2/blocks/scalar-auth-selector-block/helpers/security-scheme'\nimport { CollapsibleSection } from '@/v2/components/layout'\nimport { useLocalization } from '@/v2/features/localization'\n\nimport RequestAuthDataTable from './RequestAuthDataTable.vue'\n\nconst {\n environment,\n eventBus,\n createAnySecurityScheme = false,\n canDeleteSchemes = true,\n defaultOpen = true,\n heading = true,\n hideSingleRequiredScheme = false,\n isStatic = false,\n meta,\n proxyUrl,\n securityRequirements,\n securitySchemes,\n selectedSecurity,\n server,\n title,\n options,\n documentType,\n} = defineProps<{\n environment: XScalarEnvironment\n eventBus: WorkspaceEventBus\n /** Allows adding authentication which is not in the document */\n createAnySecurityScheme?: boolean\n /**\n * Whether schemes can be deleted from the selector. Enabled for the editable client, disabled in\n * the read-only reference where the schemes come from the document and cannot be removed.\n */\n canDeleteSchemes?: boolean\n /** Whether the authentication disclosure should start expanded */\n defaultOpen?: boolean\n /**\n * Whether the title belongs in the document outline. The client renders the\n * selector as one section of a request, so it is a heading there. A surface\n * that only offers the schemes as a card of controls, such as the reference,\n * turns it off and the card is named as a group instead.\n */\n heading?: boolean\n /** Hides a sole active required choice in the reference while preserving client controls. */\n hideSingleRequiredScheme?: boolean\n /** Creates a static disclosure that cannot be collapsed */\n isStatic?: boolean\n meta: AuthMeta\n proxyUrl: string\n securityRequirements: OpenApiDocument['security']\n securitySchemes: MergedSecuritySchemes\n selectedSecurity: SelectedSecurity | undefined\n server: ServerObject | null\n title: string\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n /** Type of the document the schemes belong to, used to label the missing-type warning */\n documentType?: 'openapi' | 'asyncapi'\n}>()\n\nconst { translate, translations } = useLocalization()\n\nconst titleId = useId()\nconst comboboxButtonRef = ref<typeof ScalarButtonType | null>(null)\nconst isDisclosureOpen = ref(false)\n\nconst deleteModal = useModal()\nconst schemeToDelete = ref<{\n label: string\n payload: SecurityRequirementObject\n} | null>(null)\n\n/**\n * Determines if authentication is required or optional\n *\n * Auth is optional when there is an empty security requirement and no complex requirements.\n * Complex requirements have multiple auth schemes combined (e.g., API key + OAuth).\n */\nconst authIndicator = computed<{ icon: Icon; text: string } | null>(() => {\n if (!securityRequirements?.length) {\n return null\n }\n\n const isOptional = isAuthOptional(securityRequirements)\n\n return {\n icon: isOptional ? 'Unlock' : 'Lock',\n text: isOptional\n ? translate('apiClient.authSelector.optional')\n : translate('apiClient.authSelector.required'),\n }\n})\n\n/** All available auth scheme options for the dropdown */\nconst availableSchemeOptions = computed(() =>\n getSecuritySchemeOptions(\n securityRequirements ?? [],\n securitySchemes ?? {},\n selectedSecurity?.selectedSchemes ?? [],\n createAnySecurityScheme,\n translations.value.apiClient,\n ),\n)\n\n/** Currently active auth schemes selected for this operation or collection */\nconst activeSchemeOptions = computed<SecuritySchemeOption[]>(() => {\n const schemes = selectedSecurity?.selectedSchemes\n if (!schemes?.length) {\n return []\n }\n\n return schemes.flatMap((requirement) => {\n const schemeNames = Object.keys(requirement)\n\n if (schemeNames.length === 0) {\n return []\n }\n\n // Complex auth requirement with multiple schemes\n if (schemeNames.length > 1) {\n return formatComplexScheme(requirement)\n }\n\n // Simple auth requirement with single scheme\n const schemeName = schemeNames[0]\n if (!schemeName) {\n return []\n }\n\n const scheme = getResolvedRef(securitySchemes?.[schemeName])\n if (!scheme) {\n return []\n }\n\n return formatScheme({\n name: schemeName,\n value: requirement,\n })\n })\n})\n\n/** Keep selection available whenever it can change auth or recover an empty saved selection. */\nconst showSchemeSelector = computed<boolean>(() => {\n if (\n !hideSingleRequiredScheme ||\n createAnySecurityScheme ||\n securityRequirements?.length !== 1 ||\n Object.keys(securityRequirements[0] ?? {}).length === 0\n ) {\n return true\n }\n\n const schemeOptions = availableSchemeOptions.value.flatMap((option) =>\n 'options' in option ? option.options : [option],\n )\n\n return (\n schemeOptions.length !== 1 ||\n activeSchemeOptions.value.length !== 1 ||\n activeSchemeOptions.value[0]?.id !== schemeOptions[0]?.id\n )\n})\n\n/**\n * Opens the combobox dropdown when clicking the auth indicator badge.\n * Prevents the disclosure from toggling if it is already open.\n */\nconst handleAuthIndicatorClick = (event: Event): void => {\n if (!showSchemeSelector.value) {\n return\n }\n\n if (isDisclosureOpen.value) {\n event.stopPropagation()\n }\n comboboxButtonRef.value?.$el.click()\n}\n\n/**\n * Updates the selected auth schemes.\n * Separates existing schemes from newly created ones for the event payload.\n */\nconst handleSchemeSelection = (selected: SecuritySchemeOption[]): void => {\n const existingSchemes = selected\n .filter((option) => option.payload === undefined)\n .map((option) => unpackProxyObject(option.value, { depth: 2 }))\n\n const newSchemes = selected\n .filter((option) => option.payload !== undefined)\n .map((option) => ({\n name: option.label,\n scheme: option.payload!,\n }))\n\n eventBus.emit('auth:update:selected-security-schemes', {\n selectedRequirements: existingSchemes,\n newSchemes,\n meta,\n })\n}\n\n/** Shows the delete confirmation modal for the selected scheme */\nconst handleDeleteRequest = (option: {\n label: string\n value: SecurityRequirementObject\n}): void => {\n schemeToDelete.value = { label: option.label, payload: option.value }\n deleteModal.show()\n}\n\n/** Deletes the selected auth scheme after confirmation */\nconst handleDeleteConfirm = (): void => {\n if (!schemeToDelete.value) {\n return\n }\n\n eventBus.emit('auth:delete:security-scheme', {\n names: Object.keys(schemeToDelete.value.payload),\n })\n\n schemeToDelete.value = null\n deleteModal.hide()\n}\n\ndefineExpose({\n authIndicator,\n selectedSchemeOptions: activeSchemeOptions,\n schemeOptions: availableSchemeOptions,\n})\n</script>\n<template>\n <CollapsibleSection\n class=\"group/params relative\"\n :defaultOpen\n :heading\n :isStatic=\"isStatic\"\n :itemCount=\"activeSchemeOptions.length\"\n @update:modelValue=\"(open) => (isDisclosureOpen = open)\">\n <template #title>\n <div\n :id=\"titleId\"\n class=\"inline-flex items-center gap-0.5 leading-[20px]\">\n <span>{{ title }}</span>\n\n <span\n v-if=\"authIndicator\"\n class=\"text-c-3 -my-0.5 -mr-1 rounded px-1 py-0.5 leading-[normal] font-normal\"\n :class=\"{\n 'text-c-1': authIndicator.icon === 'Lock',\n 'hover:bg-b-3 hover:text-c-1 cursor-pointer': showSchemeSelector,\n }\"\n data-testid=\"auth-indicator\"\n @click=\"handleAuthIndicatorClick\">\n {{ authIndicator.text }}\n </span>\n </div>\n </template>\n\n <!-- The client keeps its selection and deletion controls. -->\n <template\n v-if=\"showSchemeSelector\"\n #actions>\n <ScalarComboboxMultiselect\n class=\"w-72 text-xs\"\n :modelValue=\"activeSchemeOptions\"\n multiple\n :options=\"availableSchemeOptions\"\n placement=\"bottom-end\"\n teleport\n @delete=\"handleDeleteRequest\"\n @update:modelValue=\"handleSchemeSelection\">\n <ScalarButton\n ref=\"comboboxButtonRef\"\n :aria-describedby=\"titleId\"\n class=\"group/combobox-button hover:text-c-1 text-c-2 flex h-fit w-full items-center gap-1 px-0.75 py-0.25 text-base font-normal\"\n variant=\"ghost\">\n <!-- Single auth scheme selected -->\n <template v-if=\"activeSchemeOptions.length === 1\">\n {{\n translate('apiClient.authSelector.selectedType', {\n type: activeSchemeOptions[0]?.label ?? '',\n })\n }}\n </template>\n\n <!-- Multiple auth schemes selected -->\n <template v-else-if=\"activeSchemeOptions.length > 1\">\n {{ translate('apiClient.authSelector.multipleTypes') }}\n </template>\n\n <!-- No auth schemes selected -->\n <template v-else>\n {{ translate('apiClient.authSelector.selectType') }}\n </template>\n\n <ScalarIconCaretDown\n class=\"size-3 shrink-0 transition-transform duration-100 group-aria-expanded/combobox-button:rotate-180\"\n weight=\"bold\" />\n </ScalarButton>\n\n <template #option=\"{ option, selected }\">\n <ScalarListboxCheckbox\n multiselect\n :selected=\"selected\" />\n <div class=\"min-w-0 flex-1 truncate\">\n {{ option.label }}\n </div>\n <ScalarIconButton\n v-if=\"option.isDeletable && canDeleteSchemes\"\n class=\"-m-0.5 shrink-0 p-0.5 opacity-0 group-hover/item:opacity-100\"\n :icon=\"ScalarIconTrash\"\n :label=\"\n translate('apiClient.authSelector.deleteScheme', {\n name: option.label,\n })\n \"\n size=\"xs\"\n @click.stop=\"handleDeleteRequest(option)\" />\n </template>\n </ScalarComboboxMultiselect>\n </template>\n\n <!-- Auth Table -->\n <RequestAuthDataTable\n :activeAuthIndex=\"selectedSecurity?.selectedIndex ?? 0\"\n :documentType\n :environment\n :eventBus\n :isStatic\n :meta\n :options\n :proxyUrl\n :securitySchemes\n :selectedSchemeOptions=\"activeSchemeOptions\"\n :server />\n\n <!-- Delete Auth Modal -->\n <DeleteRequestAuthModal\n v-if=\"schemeToDelete\"\n :label=\"schemeToDelete.label\"\n :scheme=\"schemeToDelete\"\n :state=\"deleteModal\"\n @close=\"deleteModal.hide()\"\n @delete=\"handleDeleteConfirm\" />\n </CollapsibleSection>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuth2.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OAuth2.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue"],"names":[],"mappings":"AA0tBA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAIzE,OAAO,KAAK,EAEV,iBAAiB,EAClB,MAAM,gCAAgC,CAAA;AAKvC,OAAO,EAKL,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAChC,MAAM,yCAAyC,CAAA;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2EAA2E,CAAA;AAEnH,OAAO,KAAK,EAEV,YAAY,EACb,MAAM,8DAA8D,CAAA;AAIrE,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,sDAAsD,CAAA;AAS7D,uDAAuD;AACvD,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,sBAAsB,EACtB,mBAAmB,CACpB,GAAG;IACF,2GAA2G;IAC3G,WAAW,CAAC,EAAE,OAAO,KAAK,CAAA;IAC1B,0FAA0F;IAC1F,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;CAC9C,CAAA;wBACoB,OAAO,YAAY;AAAxC,wBAAyC;AACzC,QAAA,MAAM,YAAY;IAEhB,wCAAwC;iBAC3B,kBAAkB;IAC/B,kBAAkB;WACX,sBAAsB;IAC7B,6BAA6B;UACvB,MAAM,sBAAsB;IAClC,sBAAsB;oBACN,MAAM,EAAE;IACxB,sBAAsB;YACd,0BAA0B;IAClC,mCAAmC;YAC3B,YAAY,GAAG,IAAI;IAC3B,gBAAgB;cACN,MAAM;IAChB,kCAAkC;UAC5B,MAAM;IACZ,2CAA2C;cACjC,iBAAiB;IAC3B,uDAAuD;cAC7C,aAAa;IACvB;;;;OAIG;kBACW,OAAO;;;iBAmBC,MAAM,EAAE;gBAAU,MAAM;mBAAa,OAAO;;;;;;;;;;;;;;;;IA5ClE,wCAAwC;iBAC3B,kBAAkB;IAC/B,kBAAkB;WACX,sBAAsB;IAC7B,6BAA6B;UACvB,MAAM,sBAAsB;IAClC,sBAAsB;oBACN,MAAM,EAAE;IACxB,sBAAsB;YACd,0BAA0B;IAClC,mCAAmC;YAC3B,YAAY,GAAG,IAAI;IAC3B,gBAAgB;cACN,MAAM;IAChB,kCAAkC;UAC5B,MAAM;IACZ,2CAA2C;cACjC,iBAAiB;IAC3B,uDAAuD;cAC7C,aAAa;IACvB;;;;OAIG;kBACW,OAAO;;;iBAmBC,MAAM,EAAE;gBAAU,MAAM;mBAAa,OAAO;;;;;;;;;;;;;;;kFAu2ChE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuth2.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue"],"sourcesContent":["<script lang=\"ts\">\n/** Any config options required for the OAuth2 flow */\nexport type OAuth2Options = Pick<\n ApiClientConfiguration,\n 'oauth2RedirectUri'\n> & {\n /** Optional fetch override (IPC-backed on desktop) used for token exchange, refresh, and OIDC discovery */\n customFetch?: typeof fetch\n /** Optional redirect capture (loopback-backed on desktop) for interactive OAuth2 flows */\n captureOAuth2Callback?: CaptureOAuth2Callback\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components/button'\nimport { useLoadingState } from '@scalar/components/loading'\nimport type { ApiClientConfiguration } from '@scalar/types/api-reference'\nimport { pkceOptions } from '@scalar/types/entities'\nimport { useToasts } from '@scalar/use-toasts'\nimport type { SecretsOAuthFlows } from '@scalar/workspace-store/entities/auth'\nimport type {\n ApiReferenceEvents,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport {\n getEnvironmentVariables,\n type OAuthFlowAuthorizationCodeSecret,\n type OAuthFlowClientCredentialsSecret,\n type OAuthFlowPasswordSecret,\n type OAuthFlowsObjectSecret,\n type SecuritySchemeObjectSecret,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { XScalarCredentialsLocation } from '@scalar/workspace-store/schemas/extensions/security/x-scalar-credentials-location'\nimport { type XusePkce } from '@scalar/workspace-store/schemas/extensions/security/x-use-pkce'\nimport type {\n OAuthFlow,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\n\nimport OAuthScopesInput from '@/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue'\nimport {\n authorizeOauth2,\n refreshOauth2Token,\n type CaptureOAuth2Callback,\n} from '@/v2/blocks/scalar-auth-selector-block/helpers/oauth'\nimport { resolveDefaultOAuth2RedirectUri } from '@/v2/blocks/scalar-auth-selector-block/helpers/resolve-default-oauth2-redirect-url'\nimport { DataTableRow } from '@/v2/components/data-table'\nimport { useLocalization } from '@/v2/features/localization'\n\nimport type { DeviceAuthorizationPrompt } from '../helpers/oauth-device-authorization'\nimport RequestAuthDataTableInput from './RequestAuthDataTableInput.vue'\n\nconst {\n environment,\n flows,\n type,\n scheme,\n selectedScopes: selectedScopesProp,\n server,\n proxyUrl,\n eventBus,\n name,\n options = {},\n} = defineProps<{\n /** Current environment configuration */\n environment: XScalarEnvironment\n /** OAuth flows */\n flows: OAuthFlowsObjectSecret\n /** Type of the OAuth flow */\n type: keyof OAuthFlowsObjectSecret\n /** Selected scopes */\n selectedScopes: string[]\n /** Security scheme */\n scheme: SecuritySchemeObjectSecret\n /** Current server configuration */\n server: ServerObject | null\n /** Proxy URL */\n proxyUrl: string\n /** Name of the security scheme */\n name: string\n /** Event bus for authentication updates */\n eventBus: WorkspaceEventBus\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n /**\n * Hides the Authorize / Refresh / Clear actions. Used when the form is embedded as a\n * configuration-only view (e.g. behind a gear icon), so the host component owns the\n * authorize action and the tokens are routed to the scheme it chooses.\n */\n hideActions?: boolean\n}>()\n\nconst emits = defineEmits<{\n (\n e: 'update:selectedScopes',\n payload: { scopes?: string[]; scope?: string; selected?: boolean },\n ): void\n (\n e: 'upsert:scope',\n payload: Omit<ApiReferenceEvents['auth:upsert:scopes'], 'name'>,\n ): void\n (\n e: 'delete:scope',\n payload: Omit<ApiReferenceEvents['auth:delete:scopes'], 'name'>,\n ): void\n}>()\n\nconst devicePrompt = ref<DeviceAuthorizationPrompt | null>(null)\nlet deviceController: AbortController | undefined\nconst cancelDeviceAuthorization = (): void => {\n deviceController?.abort()\n devicePrompt.value = null\n}\nonBeforeUnmount(cancelDeviceAuthorization)\nwatch(() => [type, name, flows], cancelDeviceAuthorization)\n\nconst { translate } = useLocalization()\n\nconst loader = useLoadingState()\nconst { toast } = useToasts()\n\n/** The current OAuth flow based on the selected type */\nconst flow = computed(() => flows[type]!)\ntype NonImplicitFlow =\n | OAuthFlowPasswordSecret\n | OAuthFlowClientCredentialsSecret\n | OAuthFlowAuthorizationCodeSecret\n\n/** We filter selected scopes to only include scopes that are defined on this flow (own keys only). */\nconst selectedScopes = computed(() => {\n const definedScopes = flow.value.scopes\n if (!definedScopes) {\n return []\n }\n return selectedScopesProp.filter((scope) =>\n Object.hasOwn(definedScopes, scope),\n )\n})\n\n/**\n * Show the client secret whenever the flow supports one. PKCE and a client\n * secret are independent, so confidential clients can use both together\n * (see RFC 9700 Section 2.1.1).\n */\nconst showClientSecret = computed(\n () => 'x-scalar-secret-client-secret' in flow.value,\n)\n\nconst clientSecretValue = computed((): string => {\n const f = flow.value as { 'x-scalar-secret-client-secret'?: string }\n return f['x-scalar-secret-client-secret'] ?? ''\n})\n\n/** Updates the security scheme base */\nconst handleOauth2Update = (\n payload: Partial<OAuthFlow & XScalarCredentialsLocation> & {\n deviceAuthorizationUrl?: string\n },\n): void => {\n // OpenIdConnect uses the secrets update for all\n if (scheme.type === 'openIdConnect') {\n return handleOauth2SecretsUpdate(payload)\n }\n\n // Only OAuth2 schemes carry flows on the document; this component is never\n // rendered for the other scheme types.\n if (scheme.type !== 'oauth2') {\n return\n }\n\n eventBus.emit('auth:update:security-scheme', {\n payload: {\n type: scheme.type,\n flows: {\n [type]: payload,\n },\n },\n name,\n })\n}\n\n/** Updates the flow secrets */\nconst handleOauth2SecretsUpdate = (\n payload: Omit<Partial<SecretsOAuthFlows[keyof SecretsOAuthFlows]>, 'type'>,\n): void => {\n // This component only renders for oauth2/openIdConnect. Other scheme types (e.g. mutualTLS)\n // carry no flow secrets, so there is nothing to persist and no matching secrets payload type.\n if (scheme.type !== 'oauth2' && scheme.type !== 'openIdConnect') {\n return\n }\n\n eventBus.emit('auth:update:security-scheme-secrets', {\n payload: {\n type: scheme.type,\n [type]: payload,\n },\n name,\n })\n}\n\n/** Clears the flow secrets */\nconst clearOauth2Secrets = (): void => {\n if (loader.isLoading) {\n return\n }\n eventBus.emit('auth:clear:security-scheme-secrets', {\n name,\n })\n}\n\n/** Clears stored access and refresh tokens (authorized state) */\nconst handleClearAccessTokens = (): void => {\n if (loader.isLoading) {\n return\n }\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': '',\n 'x-scalar-secret-refresh-token': '',\n })\n}\n\n/** Track redirect URI prefill per flow instance to support document switching */\nconst prefilledFlowIdentity = ref<string | null>(null)\nconst hasHandledRedirectPrefill = ref(false)\n\nconst resolveFlowIdentity = (\n currentFlow: OAuthFlowsObjectSecret[keyof OAuthFlowsObjectSecret] | undefined,\n): string =>\n JSON.stringify({\n type,\n authorizationUrl:\n currentFlow && 'authorizationUrl' in currentFlow\n ? currentFlow.authorizationUrl\n : '',\n tokenUrl:\n currentFlow && 'tokenUrl' in currentFlow ? currentFlow.tokenUrl : '',\n refreshUrl: currentFlow?.refreshUrl ?? '',\n scopes: Object.keys(currentFlow?.scopes ?? {}),\n })\n\n/** Default the redirect-uri to the current origin if we have access to window */\nwatch(\n () => flow.value,\n (currentFlow) => {\n if (!currentFlow || !('x-scalar-secret-redirect-uri' in currentFlow)) {\n return\n }\n\n const flowIdentity = resolveFlowIdentity(currentFlow)\n if (prefilledFlowIdentity.value !== flowIdentity) {\n prefilledFlowIdentity.value = flowIdentity\n hasHandledRedirectPrefill.value = false\n }\n\n if (hasHandledRedirectPrefill.value) {\n return\n }\n\n const newRedirectUri = (currentFlow as OAuthFlowAuthorizationCodeSecret)[\n 'x-scalar-secret-redirect-uri'\n ]\n const defaultRedirectUri = resolveDefaultOAuth2RedirectUri(options)\n\n hasHandledRedirectPrefill.value = true\n\n // The desktop loopback path computes the redirect URI at authorize time (an\n // ephemeral 127.0.0.1 port), so persisting a default into the document would\n // only bake in a stale, unused value. Leave it empty and show a hint instead.\n if (\n newRedirectUri ||\n !defaultRedirectUri ||\n options.captureOAuth2Callback\n ) {\n return\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-redirect-uri': defaultRedirectUri,\n })\n },\n { immediate: true },\n)\n\n/**\n * Authorizes the user using the specified OAuth flow.\n * Opens the appropriate OAuth dialog and/or performs the token exchange.\n */\nconst handleAuthorize = async (): Promise<void> => {\n if (loader.isLoading) {\n return\n }\n\n loader.start()\n const controller = new AbortController()\n deviceController = controller\n\n const [error, tokens] = await authorizeOauth2(\n flows,\n type,\n selectedScopes.value,\n server,\n proxyUrl,\n getEnvironmentVariables(environment),\n options.customFetch,\n options.captureOAuth2Callback,\n {\n signal: controller.signal,\n onPrompt: (prompt) => {\n devicePrompt.value = prompt\n },\n },\n )\n devicePrompt.value = null\n\n await loader.clear()\n if (controller.signal.aborted) {\n return\n }\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(\n error?.message ?? translate('apiClient.oauth2.failedToauthorize'),\n 'error',\n )\n }\n}\n\n/** Refreshing requires a token issued by the provider, not just a compatible flow. */\nconst canRefreshToken = computed<boolean>(\n (): boolean =>\n type !== 'implicit' && Boolean(flow.value['x-scalar-secret-refresh-token']),\n)\n\n/**\n * Refresh URL placeholder, shows tokenUrl as hint if refreshUrl is not specified.\n * This helps users understand that tokenUrl will be used as fallback.\n */\nconst refreshUrlPlaceholder = computed(() => {\n if ('tokenUrl' in flow.value && flow.value.tokenUrl) {\n return flow.value.tokenUrl\n }\n return 'https://galaxy.scalar.com/oauth/refresh'\n})\n\n/**\n * Uses the stored refresh token to obtain a new access token\n * via grant_type=refresh_token.\n */\nconst handleRefresh = async (): Promise<void> => {\n // The explicit flow check narrows the type for refreshOauth2Token; the computed boolean cannot.\n if (loader.isLoading || type === 'implicit' || !canRefreshToken.value) {\n return\n }\n\n loader.start()\n\n const [error, tokens] = await refreshOauth2Token(\n flows,\n type,\n proxyUrl,\n server,\n getEnvironmentVariables(environment),\n options.customFetch,\n )\n\n await loader.clear()\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(\n error?.message ?? translate('apiClient.oauth2.failedToRefreshToken'),\n 'error',\n )\n }\n}\n\n/** Updates the secret location */\nconst handleSecretLocationUpdate = (value: string): void => {\n const credentialsLocation = value === 'body' ? 'body' : 'header'\n\n if (scheme.type !== 'openIdConnect') {\n handleOauth2Update({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n}\n</script>\n\n<template>\n <!-- Access Token Display: Shows when user is already authorized -->\n <template v-if=\"Boolean(flow['x-scalar-secret-token'])\">\n <DataTableRow>\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token']\"\n placeholder=\"QUxMIFlPVVIgQkFTRSBBUkUgQkVMT05HIFRPIFVT\"\n type=\"password\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-token': v })\n \">\n {{ translate('apiClient.oauth2.accessToken') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"canRefreshToken\">\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow.refreshUrl ?? ''\"\n :placeholder=\"refreshUrlPlaceholder\"\n @update:modelValue=\"(v) => handleOauth2Update({ refreshUrl: v })\">\n {{ translate('apiClient.oauth2.refreshUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow\n v-if=\"!hideActions\"\n class=\"min-w-full\">\n <div class=\"flex h-8 items-center justify-end gap-2 border-t\">\n <ScalarButton\n v-if=\"canRefreshToken\"\n class=\"p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleRefresh\">\n {{ translate('apiClient.oauth2.refresh') }}\n </ScalarButton>\n <ScalarButton\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleClearAccessTokens\">\n {{ translate('apiClient.oauth2.clear') }}\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n\n <!-- Authorization Form: Shows when user needs to authorize -->\n <template v-else>\n <DataTableRow v-if=\"'deviceAuthorizationUrl' in flow\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow.deviceAuthorizationUrl\"\n @update:modelValue=\"\n (v) => handleOauth2Update({ deviceAuthorizationUrl: v })\n \">\n {{ translate('apiClient.oauth2.deviceAuthorizationUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n <DataTableRow v-if=\"devicePrompt\">\n <div class=\"w-full p-2\">\n <div\n class=\"bg-b-2 flex flex-col gap-3 rounded-lg border p-3 text-sm\"\n role=\"status\">\n <p class=\"text-c-2 text-xs leading-normal\">\n {{ translate('apiClient.oauth2.deviceVerificationPrompt') }}\n </p>\n <strong\n class=\"bg-b-1 font-code w-fit max-w-full rounded border px-3 py-2 text-base font-medium tracking-widest break-all select-all\">\n {{ devicePrompt.userCode }}\n </strong>\n <a\n class=\"text-c-accent w-fit max-w-full text-xs break-all underline underline-offset-2\"\n :href=\"\n devicePrompt.verificationUriComplete ||\n devicePrompt.verificationUri\n \"\n rel=\"noopener noreferrer\"\n target=\"_blank\">\n {{ devicePrompt.verificationUri }}\n </a>\n <div class=\"flex items-center justify-between gap-3\">\n <p class=\"text-c-2 text-xs\">\n {{ translate('apiClient.oauth2.waitingForAuthorization') }}\n </p>\n <ScalarButton\n size=\"xs\"\n variant=\"outlined\"\n @click=\"cancelDeviceAuthorization\">\n {{ translate('apiClient.oauth2.cancelDeviceAuthorization') }}\n </ScalarButton>\n </div>\n </div>\n </div>\n </DataTableRow>\n <DataTableRow>\n <RequestAuthDataTableInput\n v-if=\"'authorizationUrl' in flow\"\n containerClass=\"border-r-0\"\n :environment\n :modelValue=\"flow['x-scalar-secret-auth-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/authorize\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-auth-url': v })\n handleOauth2Update({ authorizationUrl: v })\n }\n \">\n {{ translate('apiClient.oauth2.authUrl') }}\n </RequestAuthDataTableInput>\n\n <RequestAuthDataTableInput\n v-if=\"'tokenUrl' in flow\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/token\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-token-url': v })\n handleOauth2Update({ tokenUrl: v })\n }\n \">\n {{ translate('apiClient.oauth2.tokenUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-scalar-secret-redirect-uri' in flow\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-redirect-uri']\"\n :placeholder=\"\n options.captureOAuth2Callback\n ? `${resolveDefaultOAuth2RedirectUri(options) || 'http://127.0.0.1'} (handled automatically)`\n : translate('apiClient.oauth2.optionalRedirectUrl')\n \"\n @update:modelValue=\"\n (v) => {\n hasHandledRedirectPrefill = true\n handleOauth2SecretsUpdate({ 'x-scalar-secret-redirect-uri': v })\n }\n \">\n {{ translate('apiClient.oauth2.redirectUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <template\n v-if=\"\n 'x-scalar-secret-username' in flow && 'x-scalar-secret-password' in flow\n \">\n <DataTableRow>\n <RequestAuthDataTableInput\n class=\"text-c-2\"\n :environment\n :modelValue=\"flow['x-scalar-secret-username']\"\n placeholder=\"janedoe\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-username': v })\n \">\n {{ translate('apiClient.oauth2.username') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-password']\"\n placeholder=\"********\"\n type=\"password\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-password': v })\n \">\n {{ translate('apiClient.oauth2.password') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n </template>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-client-id']\"\n placeholder=\"12345\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-client-id': v })\n \">\n {{ translate('apiClient.oauth2.clientID') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"showClientSecret\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"clientSecretValue\"\n placeholder=\"XYZ123\"\n type=\"password\"\n @update:modelValue=\"\n (v) =>\n handleOauth2SecretsUpdate({ 'x-scalar-secret-client-secret': v })\n \">\n {{ translate('apiClient.oauth2.clientSecret') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-usePkce' in flow\">\n <RequestAuthDataTableInput\n :enum=\"pkceOptions\"\n :environment\n :modelValue=\"flow['x-usePkce']\"\n readOnly\n @update:modelValue=\"\n (v) =>\n handleOauth2Update({\n 'x-usePkce': v as XusePkce['x-usePkce'],\n })\n \">\n {{ translate('apiClient.oauth2.usePKCE') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Credentials Location -->\n <DataTableRow v-if=\"type !== 'implicit'\">\n <RequestAuthDataTableInput\n :enum=\"['header', 'body']\"\n :environment\n :modelValue=\"\n (flow as NonImplicitFlow)['x-scalar-credentials-location'] || 'header'\n \"\n placeholder=\"header\"\n readOnly\n @update:modelValue=\"(v) => handleSecretLocationUpdate(v)\">\n {{ translate('apiClient.oauth2.credentialsLocation') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Scopes -->\n <DataTableRow>\n <OAuthScopesInput\n :flow\n :flowType=\"type\"\n :selectedScopes\n @delete:scope=\"(v) => emits('delete:scope', v)\"\n @update:selectedScopes=\"(v) => emits('update:selectedScopes', v)\"\n @upsert:scope=\"(v) => emits('upsert:scope', v)\" />\n </DataTableRow>\n\n <DataTableRow\n v-if=\"!hideActions\"\n class=\"min-w-full\">\n <div class=\"flex h-8 w-full items-center justify-end border-t\">\n <!-- Allow clearing the oauth section and going back to discovery -->\n <ScalarButton\n v-if=\"scheme.type === 'openIdConnect'\"\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"clearOauth2Secrets\">\n {{ translate('apiClient.oauth2.clear') }}\n </ScalarButton>\n\n <ScalarButton\n class=\"mr-0.75 p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleAuthorize\">\n {{ translate('apiClient.oauth2.authorize') }}\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"OAuth2.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue"],"sourcesContent":["<script lang=\"ts\">\n/** Any config options required for the OAuth2 flow */\nexport type OAuth2Options = Pick<\n ApiClientConfiguration,\n 'oauth2RedirectUri'\n> & {\n /** Optional fetch override (IPC-backed on desktop) used for token exchange, refresh, and OIDC discovery */\n customFetch?: typeof fetch\n /** Optional redirect capture (loopback-backed on desktop) for interactive OAuth2 flows */\n captureOAuth2Callback?: CaptureOAuth2Callback\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components/button'\nimport { useLoadingState } from '@scalar/components/loading'\nimport type { ApiClientConfiguration } from '@scalar/types/api-reference'\nimport { pkceOptions } from '@scalar/types/entities'\nimport { useToasts } from '@scalar/use-toasts'\nimport type { SecretsOAuthFlows } from '@scalar/workspace-store/entities/auth'\nimport type {\n ApiReferenceEvents,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport {\n isSecretFieldCleared,\n type AuthSecretField,\n} from '@scalar/workspace-store/helpers/auth-secret-fields'\nimport {\n getEnvironmentVariables,\n type OAuthFlowAuthorizationCodeSecret,\n type OAuthFlowClientCredentialsSecret,\n type OAuthFlowPasswordSecret,\n type OAuthFlowsObjectSecret,\n type SecuritySchemeObjectSecret,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { XScalarCredentialsLocation } from '@scalar/workspace-store/schemas/extensions/security/x-scalar-credentials-location'\nimport type {\n OAuthFlow,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\n\nimport OAuthScopesInput from '@/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue'\nimport {\n authorizeOauth2,\n refreshOauth2Token,\n type CaptureOAuth2Callback,\n} from '@/v2/blocks/scalar-auth-selector-block/helpers/oauth'\nimport { resolveDefaultOAuth2RedirectUri } from '@/v2/blocks/scalar-auth-selector-block/helpers/resolve-default-oauth2-redirect-url'\nimport { DataTableRow } from '@/v2/components/data-table'\nimport { useLocalization } from '@/v2/features/localization'\n\nimport type { DeviceAuthorizationPrompt } from '../helpers/oauth-device-authorization'\nimport RequestAuthDataTableInput from './RequestAuthDataTableInput.vue'\n\nconst {\n environment,\n flows,\n type,\n scheme,\n selectedScopes: selectedScopesProp,\n server,\n proxyUrl,\n eventBus,\n name,\n options = {},\n} = defineProps<{\n /** Current environment configuration */\n environment: XScalarEnvironment\n /** OAuth flows */\n flows: OAuthFlowsObjectSecret\n /** Type of the OAuth flow */\n type: keyof OAuthFlowsObjectSecret\n /** Selected scopes */\n selectedScopes: string[]\n /** Security scheme */\n scheme: SecuritySchemeObjectSecret\n /** Current server configuration */\n server: ServerObject | null\n /** Proxy URL */\n proxyUrl: string\n /** Name of the security scheme */\n name: string\n /** Event bus for authentication updates */\n eventBus: WorkspaceEventBus\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n /**\n * Hides the Authorize / Refresh / Clear actions. Used when the form is embedded as a\n * configuration-only view (e.g. behind a gear icon), so the host component owns the\n * authorize action and the tokens are routed to the scheme it chooses.\n */\n hideActions?: boolean\n}>()\n\nconst emits = defineEmits<{\n (\n e: 'update:selectedScopes',\n payload: { scopes?: string[]; scope?: string; selected?: boolean },\n ): void\n (\n e: 'upsert:scope',\n payload: Omit<ApiReferenceEvents['auth:upsert:scopes'], 'name'>,\n ): void\n (\n e: 'delete:scope',\n payload: Omit<ApiReferenceEvents['auth:delete:scopes'], 'name'>,\n ): void\n}>()\n\nconst devicePrompt = ref<DeviceAuthorizationPrompt | null>(null)\nlet deviceController: AbortController | undefined\nconst cancelDeviceAuthorization = (): void => {\n deviceController?.abort()\n devicePrompt.value = null\n}\nonBeforeUnmount(cancelDeviceAuthorization)\nwatch(() => [type, name, flows], cancelDeviceAuthorization)\n\nconst { translate } = useLocalization()\n\nconst loader = useLoadingState()\nconst { toast } = useToasts()\n\n/** The current OAuth flow based on the selected type */\nconst flow = computed(() => flows[type]!)\ntype NonImplicitFlow =\n | OAuthFlowPasswordSecret\n | OAuthFlowClientCredentialsSecret\n | OAuthFlowAuthorizationCodeSecret\n\n/** We filter selected scopes to only include scopes that are defined on this flow (own keys only). */\nconst selectedScopes = computed(() => {\n const definedScopes = flow.value.scopes\n if (!definedScopes) {\n return []\n }\n return selectedScopesProp.filter((scope) =>\n Object.hasOwn(definedScopes, scope),\n )\n})\n\n/**\n * Show the client secret whenever the flow supports one. PKCE and a client\n * secret are independent, so confidential clients can use both together\n * (see RFC 9700 Section 2.1.1).\n */\nconst showClientSecret = computed(\n () => 'x-scalar-secret-client-secret' in flow.value,\n)\n\nconst clientSecretValue = computed((): string => {\n const f = flow.value as { 'x-scalar-secret-client-secret'?: string }\n return f['x-scalar-secret-client-secret'] ?? ''\n})\n\n/** Updates the security scheme base */\nconst handleOauth2Update = (\n payload: Partial<OAuthFlow & XScalarCredentialsLocation> & {\n deviceAuthorizationUrl?: string\n },\n): void => {\n // OpenIdConnect uses the secrets update for all\n if (scheme.type === 'openIdConnect') {\n return handleOauth2SecretsUpdate(payload)\n }\n\n // Only OAuth2 schemes carry flows on the document; this component is never\n // rendered for the other scheme types.\n if (scheme.type !== 'oauth2') {\n return\n }\n\n eventBus.emit('auth:update:security-scheme', {\n payload: {\n type: scheme.type,\n flows: {\n [type]: payload,\n },\n },\n name,\n })\n}\n\n/** Updates the flow secrets */\nconst handleOauth2SecretsUpdate = (\n payload: Omit<Partial<SecretsOAuthFlows[keyof SecretsOAuthFlows]>, 'type'>,\n): void => {\n // This component only renders for oauth2/openIdConnect. Other scheme types (e.g. mutualTLS)\n // carry no flow secrets, so there is nothing to persist and no matching secrets payload type.\n if (scheme.type !== 'oauth2' && scheme.type !== 'openIdConnect') {\n return\n }\n\n eventBus.emit('auth:update:security-scheme-secrets', {\n payload: {\n type: scheme.type,\n [type]: payload,\n },\n name,\n })\n}\n\n/** Reset only this field; redirect URI may need its browser default prefilled again. */\nconst handleResetSecret = (field: AuthSecretField): void => {\n if (field === 'x-scalar-secret-redirect-uri') {\n hasHandledRedirectPrefill.value = false\n }\n eventBus.emit('auth:reset:security-scheme-secret', {\n name,\n flow: type,\n field,\n })\n}\n\n/** Fixed-choice fields must never write an empty string into the document. */\nconst handlePkceUpdate = (value: string): void => {\n if (value === 'SHA-256' || value === 'plain' || value === 'no') {\n handleOauth2Update({ 'x-usePkce': value })\n }\n}\n\n/** Clears the flow secrets */\nconst clearOauth2Secrets = (): void => {\n if (loader.isLoading) {\n return\n }\n eventBus.emit('auth:clear:security-scheme-secrets', {\n name,\n })\n}\n\n/** Clears stored access and refresh tokens (authorized state) */\nconst handleClearAccessTokens = (): void => {\n if (loader.isLoading) {\n return\n }\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': '',\n 'x-scalar-secret-refresh-token': '',\n })\n}\n\n/** Track redirect URI prefill per flow instance to support document switching */\nconst prefilledFlowIdentity = ref<string | null>(null)\nconst hasHandledRedirectPrefill = ref(false)\n\nconst resolveFlowIdentity = (\n currentFlow: OAuthFlowsObjectSecret[keyof OAuthFlowsObjectSecret] | undefined,\n): string =>\n JSON.stringify({\n type,\n authorizationUrl:\n currentFlow && 'authorizationUrl' in currentFlow\n ? currentFlow.authorizationUrl\n : '',\n tokenUrl:\n currentFlow && 'tokenUrl' in currentFlow ? currentFlow.tokenUrl : '',\n refreshUrl: currentFlow?.refreshUrl ?? '',\n scopes: Object.keys(currentFlow?.scopes ?? {}),\n })\n\n/** Default the redirect-uri to the current origin if we have access to window */\nwatch(\n () => flow.value,\n (currentFlow) => {\n if (!currentFlow || !('x-scalar-secret-redirect-uri' in currentFlow)) {\n return\n }\n\n const flowIdentity = resolveFlowIdentity(currentFlow)\n if (prefilledFlowIdentity.value !== flowIdentity) {\n prefilledFlowIdentity.value = flowIdentity\n hasHandledRedirectPrefill.value = false\n }\n\n if (hasHandledRedirectPrefill.value) {\n return\n }\n\n const newRedirectUri = (currentFlow as OAuthFlowAuthorizationCodeSecret)[\n 'x-scalar-secret-redirect-uri'\n ]\n const defaultRedirectUri = resolveDefaultOAuth2RedirectUri(options)\n\n hasHandledRedirectPrefill.value = true\n\n // The desktop loopback path computes the redirect URI at authorize time (an\n // ephemeral 127.0.0.1 port), so persisting a default into the document would\n // only bake in a stale, unused value. Leave it empty and show a hint instead.\n if (\n isSecretFieldCleared(currentFlow, 'x-scalar-secret-redirect-uri') ||\n newRedirectUri ||\n !defaultRedirectUri ||\n options.captureOAuth2Callback\n ) {\n return\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-redirect-uri': defaultRedirectUri,\n })\n },\n { immediate: true },\n)\n\n/**\n * Authorizes the user using the specified OAuth flow.\n * Opens the appropriate OAuth dialog and/or performs the token exchange.\n */\nconst handleAuthorize = async (): Promise<void> => {\n if (loader.isLoading) {\n return\n }\n\n loader.start()\n const controller = new AbortController()\n deviceController = controller\n\n const [error, tokens] = await authorizeOauth2(\n flows,\n type,\n selectedScopes.value,\n server,\n proxyUrl,\n getEnvironmentVariables(environment),\n options.customFetch,\n options.captureOAuth2Callback,\n {\n signal: controller.signal,\n onPrompt: (prompt) => {\n devicePrompt.value = prompt\n },\n },\n )\n devicePrompt.value = null\n\n await loader.clear()\n if (controller.signal.aborted) {\n return\n }\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(\n error?.message ?? translate('apiClient.oauth2.failedToauthorize'),\n 'error',\n )\n }\n}\n\n/** Refreshing requires a token issued by the provider, not just a compatible flow. */\nconst canRefreshToken = computed<boolean>(\n (): boolean =>\n type !== 'implicit' && Boolean(flow.value['x-scalar-secret-refresh-token']),\n)\n\n/**\n * Refresh URL placeholder, shows tokenUrl as hint if refreshUrl is not specified.\n * This helps users understand that tokenUrl will be used as fallback.\n */\nconst refreshUrlPlaceholder = computed(() => {\n if ('tokenUrl' in flow.value && flow.value.tokenUrl) {\n return flow.value.tokenUrl\n }\n return 'https://galaxy.scalar.com/oauth/refresh'\n})\n\n/**\n * Uses the stored refresh token to obtain a new access token\n * via grant_type=refresh_token.\n */\nconst handleRefresh = async (): Promise<void> => {\n // The explicit flow check narrows the type for refreshOauth2Token; the computed boolean cannot.\n if (loader.isLoading || type === 'implicit' || !canRefreshToken.value) {\n return\n }\n\n loader.start()\n\n const [error, tokens] = await refreshOauth2Token(\n flows,\n type,\n proxyUrl,\n server,\n getEnvironmentVariables(environment),\n options.customFetch,\n )\n\n await loader.clear()\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(\n error?.message ?? translate('apiClient.oauth2.failedToRefreshToken'),\n 'error',\n )\n }\n}\n\n/** Updates the secret location */\nconst handleSecretLocationUpdate = (value: string): void => {\n if (value !== 'body' && value !== 'header') return\n const credentialsLocation = value\n\n if (scheme.type !== 'openIdConnect') {\n handleOauth2Update({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n}\n</script>\n\n<template>\n <!-- Access Token Display: Shows when user is already authorized -->\n <template\n v-if=\"\n Boolean(flow['x-scalar-secret-token']) ||\n isSecretFieldCleared(flow, 'x-scalar-secret-token')\n \">\n <DataTableRow>\n <RequestAuthDataTableInput\n canReset\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token']\"\n placeholder=\"QUxMIFlPVVIgQkFTRSBBUkUgQkVMT05HIFRPIFVT\"\n type=\"password\"\n @reset=\"handleResetSecret('x-scalar-secret-token')\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-token': v })\n \">\n {{ translate('apiClient.oauth2.accessToken') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"canRefreshToken\">\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow.refreshUrl ?? ''\"\n :placeholder=\"refreshUrlPlaceholder\"\n @update:modelValue=\"(v) => handleOauth2Update({ refreshUrl: v })\">\n {{ translate('apiClient.oauth2.refreshUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow\n v-if=\"!hideActions\"\n class=\"min-w-full\">\n <div class=\"flex h-8 items-center justify-end gap-2 border-t\">\n <ScalarButton\n v-if=\"canRefreshToken\"\n class=\"p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleRefresh\">\n {{ translate('apiClient.oauth2.refresh') }}\n </ScalarButton>\n <ScalarButton\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleClearAccessTokens\">\n {{ translate('apiClient.oauth2.clearTokens') }}\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n\n <!-- Authorization Form: Shows when user needs to authorize -->\n <template v-else>\n <DataTableRow v-if=\"'deviceAuthorizationUrl' in flow\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow.deviceAuthorizationUrl\"\n @update:modelValue=\"\n (v) => handleOauth2Update({ deviceAuthorizationUrl: v })\n \">\n {{ translate('apiClient.oauth2.deviceAuthorizationUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n <DataTableRow v-if=\"devicePrompt\">\n <div class=\"w-full p-2\">\n <div\n class=\"bg-b-2 flex flex-col gap-3 rounded-lg border p-3 text-sm\"\n role=\"status\">\n <p class=\"text-c-2 text-xs leading-normal\">\n {{ translate('apiClient.oauth2.deviceVerificationPrompt') }}\n </p>\n <strong\n class=\"bg-b-1 font-code w-fit max-w-full rounded border px-3 py-2 text-base font-medium tracking-widest break-all select-all\">\n {{ devicePrompt.userCode }}\n </strong>\n <a\n class=\"text-c-accent w-fit max-w-full text-xs break-all underline underline-offset-2\"\n :href=\"\n devicePrompt.verificationUriComplete ||\n devicePrompt.verificationUri\n \"\n rel=\"noopener noreferrer\"\n target=\"_blank\">\n {{ devicePrompt.verificationUri }}\n </a>\n <div class=\"flex items-center justify-between gap-3\">\n <p class=\"text-c-2 text-xs\">\n {{ translate('apiClient.oauth2.waitingForAuthorization') }}\n </p>\n <ScalarButton\n size=\"xs\"\n variant=\"outlined\"\n @click=\"cancelDeviceAuthorization\">\n {{ translate('apiClient.oauth2.cancelDeviceAuthorization') }}\n </ScalarButton>\n </div>\n </div>\n </div>\n </DataTableRow>\n <DataTableRow>\n <RequestAuthDataTableInput\n v-if=\"'authorizationUrl' in flow\"\n canReset\n containerClass=\"border-r-0\"\n :environment\n :modelValue=\"flow['x-scalar-secret-auth-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/authorize\"\n @reset=\"handleResetSecret('x-scalar-secret-auth-url')\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-auth-url': v })\n if (v) handleOauth2Update({ authorizationUrl: v })\n }\n \">\n {{ translate('apiClient.oauth2.authUrl') }}\n </RequestAuthDataTableInput>\n\n <RequestAuthDataTableInput\n v-if=\"'tokenUrl' in flow\"\n canReset\n :environment\n :modelValue=\"flow['x-scalar-secret-token-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/token\"\n @reset=\"handleResetSecret('x-scalar-secret-token-url')\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-token-url': v })\n if (v) handleOauth2Update({ tokenUrl: v })\n }\n \">\n {{ translate('apiClient.oauth2.tokenUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-scalar-secret-redirect-uri' in flow\">\n <RequestAuthDataTableInput\n canReset\n :environment\n :modelValue=\"flow['x-scalar-secret-redirect-uri']\"\n :placeholder=\"\n options.captureOAuth2Callback\n ? `${resolveDefaultOAuth2RedirectUri(options) || 'http://127.0.0.1'} (handled automatically)`\n : translate('apiClient.oauth2.optionalRedirectUrl')\n \"\n @reset=\"handleResetSecret('x-scalar-secret-redirect-uri')\"\n @update:modelValue=\"\n (v) => {\n hasHandledRedirectPrefill = true\n handleOauth2SecretsUpdate({ 'x-scalar-secret-redirect-uri': v })\n }\n \">\n {{ translate('apiClient.oauth2.redirectUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <template\n v-if=\"\n 'x-scalar-secret-username' in flow && 'x-scalar-secret-password' in flow\n \">\n <DataTableRow>\n <RequestAuthDataTableInput\n canReset\n class=\"text-c-2\"\n :environment\n :modelValue=\"flow['x-scalar-secret-username']\"\n placeholder=\"janedoe\"\n @reset=\"handleResetSecret('x-scalar-secret-username')\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-username': v })\n \">\n {{ translate('apiClient.oauth2.username') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n canReset\n :environment\n :modelValue=\"flow['x-scalar-secret-password']\"\n placeholder=\"********\"\n type=\"password\"\n @reset=\"handleResetSecret('x-scalar-secret-password')\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-password': v })\n \">\n {{ translate('apiClient.oauth2.password') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n </template>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n canReset\n :environment\n :modelValue=\"flow['x-scalar-secret-client-id']\"\n placeholder=\"12345\"\n @reset=\"handleResetSecret('x-scalar-secret-client-id')\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-client-id': v })\n \">\n {{ translate('apiClient.oauth2.clientID') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"showClientSecret\">\n <RequestAuthDataTableInput\n canReset\n :environment\n :modelValue=\"clientSecretValue\"\n placeholder=\"XYZ123\"\n type=\"password\"\n @reset=\"handleResetSecret('x-scalar-secret-client-secret')\"\n @update:modelValue=\"\n (v) =>\n handleOauth2SecretsUpdate({ 'x-scalar-secret-client-secret': v })\n \">\n {{ translate('apiClient.oauth2.clientSecret') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-usePkce' in flow\">\n <RequestAuthDataTableInput\n :enum=\"pkceOptions\"\n :environment\n :modelValue=\"flow['x-usePkce']\"\n readOnly\n @update:modelValue=\"handlePkceUpdate\">\n {{ translate('apiClient.oauth2.usePKCE') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Credentials Location -->\n <DataTableRow v-if=\"type !== 'implicit'\">\n <RequestAuthDataTableInput\n :enum=\"['header', 'body']\"\n :environment\n :modelValue=\"\n (flow as NonImplicitFlow)['x-scalar-credentials-location'] || 'header'\n \"\n placeholder=\"header\"\n readOnly\n @update:modelValue=\"(v) => handleSecretLocationUpdate(v)\">\n {{ translate('apiClient.oauth2.credentialsLocation') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Scopes -->\n <DataTableRow>\n <OAuthScopesInput\n :flow\n :flowType=\"type\"\n :selectedScopes\n @delete:scope=\"(v) => emits('delete:scope', v)\"\n @update:selectedScopes=\"(v) => emits('update:selectedScopes', v)\"\n @upsert:scope=\"(v) => emits('upsert:scope', v)\" />\n </DataTableRow>\n\n <DataTableRow\n v-if=\"!hideActions\"\n class=\"min-w-full\">\n <div class=\"flex h-8 w-full items-center justify-end border-t\">\n <!-- Allow clearing the oauth section and going back to discovery -->\n <ScalarButton\n v-if=\"scheme.type === 'openIdConnect'\"\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"clearOauth2Secrets\">\n {{ translate('apiClient.oauth2.resetDiscovery') }}\n </ScalarButton>\n\n <ScalarButton\n class=\"mr-0.75 p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleAuthorize\">\n {{ translate('apiClient.oauth2.authorize') }}\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n</template>\n"],"mappings":""}
|