@zudojs/openapi 1.2.0 → 1.3.0
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/README.md +42 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/openApiErrors/openApiError.base.d.ts +8 -15
- package/dist/openApiErrors/openApiError.base.js +8 -16
- package/dist/openApiHttp/openApiHttpAdapter.core.js +10 -0
- package/dist/openApiSchema/schemaConverter.core.d.ts +1 -0
- package/dist/openApiSchema/schemaConverter.core.js +3 -4
- package/dist/openApiUi/index.d.ts +2 -0
- package/dist/openApiUi/index.js +2 -0
- package/dist/openApiUi/openApiUi.assets.d.ts +41 -0
- package/dist/openApiUi/openApiUi.assets.js +48 -0
- package/dist/openApiUi/openApiUi.brand.d.ts +1 -1
- package/dist/openApiUi/openApiUi.brand.js +1 -1
- package/dist/openApiUi/openApiUi.core.d.ts +24 -5
- package/dist/openApiUi/openApiUi.core.js +18 -43
- package/dist/openApiUi/openApiUi.csp.d.ts +25 -0
- package/dist/openApiUi/openApiUi.csp.js +92 -0
- package/dist/openApiUi/openApiUiPage/index.d.ts +9 -0
- package/dist/openApiUi/openApiUiPage/index.js +9 -0
- package/dist/openApiUi/openApiUiPage/openApiUiPage.script.d.ts +14 -0
- package/dist/openApiUi/openApiUiPage/openApiUiPage.script.js +24 -0
- package/dist/openApiUi/openApiUiPage/openApiUiPage.theme.d.ts +6 -0
- package/dist/openApiUi/openApiUiPage/openApiUiPage.theme.js +25 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
OpenAPI 3.0 and 3.1 specification generation, validation, and serialization for Zudojs applications.
|
|
4
4
|
|
|
5
|
+
<!-- zudo-docs:start -->
|
|
6
|
+
|
|
7
|
+
**Documentation:** [zudojs.oyinlola.site/docs/packages-openapi](https://zudojs.oyinlola.site/docs/packages-openapi) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-openapi.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
|
|
8
|
+
|
|
9
|
+
<!-- zudo-docs:end -->
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -88,13 +94,33 @@ manager. Both accept the same options:
|
|
|
88
94
|
| `logo` | Header logo, or `false` for none | Zudo wordmark |
|
|
89
95
|
| `favicon` | Favicon URL or data URI, or `false` | Zudo favicon |
|
|
90
96
|
| `customCss` | CSS appended after the built-in theme | — |
|
|
91
|
-
| `assetsBaseUrl` | Where the viewer's own JS and CSS load from |
|
|
97
|
+
| `assetsBaseUrl` | Where the viewer's own JS and CSS load from | pinned jsDelivr |
|
|
98
|
+
| `assetIntegrity` | SRI hashes `{ script?, stylesheet? }`, or `false` | pinned hashes |
|
|
99
|
+
| `contentSecurityPolicy` | CSP header from `toUIResponse`, or `false` | restrictive policy |
|
|
100
|
+
| `connectSources` | Extra origins "Try it out" may call | — |
|
|
92
101
|
| `swaggerOptions` | Forwarded to `SwaggerUIBundle`; ignored by ReDoc | — |
|
|
93
102
|
|
|
103
|
+
By default the viewer loads from exact, pinned versions on jsDelivr
|
|
104
|
+
(`swagger-ui-dist@5.33.0`, `redoc@2.5.4`; exported as `SWAGGER_UI_VERSION`
|
|
105
|
+
and `REDOC_VERSION`) with Subresource Integrity hashes, so a changed or
|
|
106
|
+
compromised CDN file is refused by the browser instead of running on your
|
|
107
|
+
API's origin.
|
|
108
|
+
|
|
109
|
+
`toUIResponse` also sends `x-content-type-options: nosniff` and a
|
|
110
|
+
`content-security-policy` built by `buildOpenAPIUIContentSecurityPolicy`:
|
|
111
|
+
scripts only from the asset origin plus the hash of the page's one inline
|
|
112
|
+
bootstrap script, `default-src 'none'`, no plugins, no `<base>`, framing
|
|
113
|
+
only by the same origin, and `connect-src` limited to the page's origin, the
|
|
114
|
+
spec URL and every absolute `servers[].url` in the document (the servers
|
|
115
|
+
"Try it out" calls). Add more with `connectSources`, replace the policy with
|
|
116
|
+
a string, or pass `contentSecurityPolicy: false` to send none.
|
|
117
|
+
|
|
94
118
|
`assetsBaseUrl` points the viewer's assets at a self-hosted copy, which is what
|
|
95
119
|
an air-gapped deployment needs — the default CDN renders a blank page with no
|
|
96
120
|
egress. Swagger UI loads `swagger-ui.css` and `swagger-ui-bundle.js` from that
|
|
97
|
-
base; ReDoc loads `redoc.standalone.js`.
|
|
121
|
+
base; ReDoc loads `redoc.standalone.js`. A self-hosted copy gets no
|
|
122
|
+
`integrity` attribute unless you pass `assetIntegrity`, since it may be a
|
|
123
|
+
different build.
|
|
98
124
|
|
|
99
125
|
```typescript
|
|
100
126
|
manager.toUIResponse({ specUrl: "/openapi.json", assetsBaseUrl: "/vendor/swagger" });
|
|
@@ -116,7 +142,7 @@ default, so a spec opened in one of them shows a logo rather than nothing.
|
|
|
116
142
|
|
|
117
143
|
```typescript
|
|
118
144
|
new OpenAPIManager({ info }).generate().info["x-logo"];
|
|
119
|
-
// { url: "data:image/svg+xml;…", href: "https://
|
|
145
|
+
// { url: "data:image/svg+xml;…", href: "https://zudojs.oyinlola.site", altText: "Zudo", … }
|
|
120
146
|
```
|
|
121
147
|
|
|
122
148
|
The `branding` option controls it, and the same value is used by
|
|
@@ -142,7 +168,8 @@ can show them without a network request: `ZUDO_MARK_SVG`, `ZUDO_MARK_DARK_SVG`,
|
|
|
142
168
|
`ZUDO_WORDMARK_SVG`, `ZUDO_WORDMARK_DARK_SVG`, `ZUDO_FAVICON_SVG`, a
|
|
143
169
|
`*_DATA_URI` counterpart for each, plus `ZUDO_SITE_URL`, `zudoLogo(overrides?)`
|
|
144
170
|
and `svgToDataUri(svg)`. The types are `OpenAPIUIOptions`,
|
|
145
|
-
`OpenAPIUIRenderer`, `OpenAPIUIResponse` and
|
|
171
|
+
`OpenAPIUIRenderer`, `OpenAPIUIResponse`, `OpenAPIUIAssetIntegrity` and
|
|
172
|
+
`OpenAPILogo`.
|
|
146
173
|
|
|
147
174
|
## Schemas
|
|
148
175
|
|
|
@@ -173,9 +200,9 @@ produces
|
|
|
173
200
|
{
|
|
174
201
|
"type": "object",
|
|
175
202
|
"properties": {
|
|
176
|
-
"id": { "type": "string", "format": "uuid" },
|
|
203
|
+
"id": { "type": "string", "format": "uuid", "maxLength": 255 },
|
|
177
204
|
"age": { "type": "integer", "minimum": 0 },
|
|
178
|
-
"nickname": { "type": "string" }
|
|
205
|
+
"nickname": { "type": "string", "maxLength": 255 }
|
|
179
206
|
},
|
|
180
207
|
"required": ["id", "age"]
|
|
181
208
|
}
|
|
@@ -188,6 +215,13 @@ all converted, along with string and number constraints (`min`, `max`,
|
|
|
188
215
|
`length`, `pattern`, `format`, `int`, `multipleOf`, `gt`, `lt`). Constraints
|
|
189
216
|
on a coercing schema (`coerce.number().int().min(1)`) are carried through.
|
|
190
217
|
|
|
218
|
+
`@zudojs/schema` caps every string at 255 characters and every array at 1000
|
|
219
|
+
items unless the schema sets its own `.max()`. The emitted `maxLength` /
|
|
220
|
+
`maxItems` carry that effective limit (read from `@zudojs/constants`'
|
|
221
|
+
`SCHEMA_DEFAULT_MAX_STRING_LENGTH` / `SCHEMA_DEFAULT_MAX_ARRAY_LENGTH`, the same
|
|
222
|
+
constants the parser uses), so a client generated from the document is never
|
|
223
|
+
told it may send a payload the server rejects.
|
|
224
|
+
|
|
191
225
|
A property is listed in `required` exactly when the object parser rejects
|
|
192
226
|
its absence: fields wrapped in `optional`, fields with a `default`, and
|
|
193
227
|
`any` / `unknown` fields are left out, and `.required()` forces every key
|
|
@@ -344,7 +378,8 @@ still produces a pointer that resolves.
|
|
|
344
378
|
|
|
345
379
|
## Errors
|
|
346
380
|
|
|
347
|
-
All errors extend `OpenAPIError
|
|
381
|
+
All errors extend `OpenAPIError`, which is the `@zudojs/errors` class
|
|
382
|
+
re-exported (a `BaseError`), and
|
|
348
383
|
default to status 500, not exposed — these are failures while a service builds
|
|
349
384
|
or validates its own specification, not responses to a client request:
|
|
350
385
|
|
package/dist/index.d.ts
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* const yaml = manager.toYAML();
|
|
38
38
|
* ```
|
|
39
39
|
*/
|
|
40
|
-
export { renderOpenAPIUI, zudoLogo, svgToDataUri, ZUDO_MARK_SVG, ZUDO_MARK_DARK_SVG, ZUDO_WORDMARK_SVG, ZUDO_WORDMARK_DARK_SVG, ZUDO_FAVICON_SVG, ZUDO_MARK_DATA_URI, ZUDO_MARK_DARK_DATA_URI, ZUDO_WORDMARK_DATA_URI, ZUDO_WORDMARK_DARK_DATA_URI, ZUDO_FAVICON_DATA_URI, ZUDO_SITE_URL, type OpenAPIUIOptions, type OpenAPIUIRenderer, } from "./openApiUi/index.js";
|
|
40
|
+
export { renderOpenAPIUI, zudoLogo, svgToDataUri, ZUDO_MARK_SVG, ZUDO_MARK_DARK_SVG, ZUDO_WORDMARK_SVG, ZUDO_WORDMARK_DARK_SVG, ZUDO_FAVICON_SVG, ZUDO_MARK_DATA_URI, ZUDO_MARK_DARK_DATA_URI, ZUDO_WORDMARK_DATA_URI, ZUDO_WORDMARK_DARK_DATA_URI, ZUDO_FAVICON_DATA_URI, ZUDO_SITE_URL, SWAGGER_UI_VERSION, REDOC_VERSION, buildOpenAPIUIContentSecurityPolicy, type OpenAPIUIAssetIntegrity, type OpenAPIUIOptions, type OpenAPIUIRenderer, } from "./openApiUi/index.js";
|
|
41
41
|
export { OpenAPIDocumentBuilder, createOpenAPIDocumentBuilder, type OpenAPIDocumentOptions, } from "./openApiDocument/index.js";
|
|
42
42
|
export { OpenAPIRegistryImpl } from "./openApiRegistry/index.js";
|
|
43
43
|
export type { OpenAPIRegistry, OpenAPIRoute, OpenAPIComponentRegistration, } from "./openApiRegistry/index.js";
|
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* ```
|
|
39
39
|
*/
|
|
40
40
|
/* ─── Documentation UI & branding ──────────────────────────────────────── */
|
|
41
|
-
export { renderOpenAPIUI, zudoLogo, svgToDataUri, ZUDO_MARK_SVG, ZUDO_MARK_DARK_SVG, ZUDO_WORDMARK_SVG, ZUDO_WORDMARK_DARK_SVG, ZUDO_FAVICON_SVG, ZUDO_MARK_DATA_URI, ZUDO_MARK_DARK_DATA_URI, ZUDO_WORDMARK_DATA_URI, ZUDO_WORDMARK_DARK_DATA_URI, ZUDO_FAVICON_DATA_URI, ZUDO_SITE_URL, } from "./openApiUi/index.js";
|
|
41
|
+
export { renderOpenAPIUI, zudoLogo, svgToDataUri, ZUDO_MARK_SVG, ZUDO_MARK_DARK_SVG, ZUDO_WORDMARK_SVG, ZUDO_WORDMARK_DARK_SVG, ZUDO_FAVICON_SVG, ZUDO_MARK_DATA_URI, ZUDO_MARK_DARK_DATA_URI, ZUDO_WORDMARK_DATA_URI, ZUDO_WORDMARK_DARK_DATA_URI, ZUDO_FAVICON_DATA_URI, ZUDO_SITE_URL, SWAGGER_UI_VERSION, REDOC_VERSION, buildOpenAPIUIContentSecurityPolicy, } from "./openApiUi/index.js";
|
|
42
42
|
/* ─── Document builder ──────────────────────────────────────────────────── */
|
|
43
43
|
export { OpenAPIDocumentBuilder, createOpenAPIDocumentBuilder, } from "./openApiDocument/index.js";
|
|
44
44
|
/* ─── Registry ──────────────────────────────────────────────────────────── */
|
|
@@ -8,21 +8,14 @@
|
|
|
8
8
|
* version mismatches into a client-visible body tells an attacker about the
|
|
9
9
|
* shape of the API rather than telling the operator anything.
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
readonly cause?: unknown;
|
|
20
|
-
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
21
|
-
}
|
|
22
|
-
/** Base error for all OpenAPI subsystem failures. */
|
|
23
|
-
export declare class OpenAPIError extends BaseError {
|
|
24
|
-
constructor(message: string, options?: OpenAPIErrorOptions);
|
|
25
|
-
}
|
|
11
|
+
import { OpenAPIError, type OpenAPIErrorOptions } from "@zudojs/errors";
|
|
12
|
+
/**
|
|
13
|
+
* `OpenAPIError` (the base of every class in this package) and
|
|
14
|
+
* `OpenAPIErrorOptions` live in `@zudojs/errors` (code `OPENAPI_DOCUMENT`,
|
|
15
|
+
* category `openapi`, 500, not exposed); they are re-exported here so
|
|
16
|
+
* existing imports keep working.
|
|
17
|
+
*/
|
|
18
|
+
export { OpenAPIError, type OpenAPIErrorOptions };
|
|
26
19
|
/** Creates an OpenAPI error. */
|
|
27
20
|
export declare function createOpenAPIError(message: string, options?: OpenAPIErrorOptions): OpenAPIError;
|
|
28
21
|
/** Determines whether an unknown value is an OpenAPI error. */
|
|
@@ -8,22 +8,14 @@
|
|
|
8
8
|
* version mismatches into a client-visible body tells an attacker about the
|
|
9
9
|
* shape of the API rather than telling the operator anything.
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
statusCode: options.statusCode ?? 500,
|
|
20
|
-
expose: options.expose ?? false,
|
|
21
|
-
cause: options.cause,
|
|
22
|
-
metadata: options.metadata,
|
|
23
|
-
});
|
|
24
|
-
this.name = "OpenAPIError";
|
|
25
|
-
}
|
|
26
|
-
}
|
|
11
|
+
import { OpenAPIError } from "@zudojs/errors";
|
|
12
|
+
/**
|
|
13
|
+
* `OpenAPIError` (the base of every class in this package) and
|
|
14
|
+
* `OpenAPIErrorOptions` live in `@zudojs/errors` (code `OPENAPI_DOCUMENT`,
|
|
15
|
+
* category `openapi`, 500, not exposed); they are re-exported here so
|
|
16
|
+
* existing imports keep working.
|
|
17
|
+
*/
|
|
18
|
+
export { OpenAPIError };
|
|
27
19
|
/** Creates an OpenAPI error. */
|
|
28
20
|
export function createOpenAPIError(message, options = {}) {
|
|
29
21
|
return new OpenAPIError(message, options);
|
|
@@ -5,6 +5,7 @@ import { SchemaRegistryImpl } from "../openApiSchema/schemaRegistry.core.js";
|
|
|
5
5
|
import { toOpenAPIJSON, toOpenAPIYAML, } from "../openApiSerialization/openApiSerializer.core.js";
|
|
6
6
|
import { DEFAULT_MEDIA_TYPE, DEFAULT_OPENAPI_VERSION, DOCUMENT_CACHE_TTL_MS, } from "../openApiConstants/openApiConstants.core.js";
|
|
7
7
|
import { renderOpenAPIUI, zudoLogo, } from "../openApiUi/openApiUi.core.js";
|
|
8
|
+
import { buildOpenAPIUIContentSecurityPolicy, documentServerUrls, } from "../openApiUi/openApiUi.csp.js";
|
|
8
9
|
/**
|
|
9
10
|
* High-level OpenAPI manager that coordinates generation, validation, and
|
|
10
11
|
* serving.
|
|
@@ -254,11 +255,20 @@ export class OpenAPIManager {
|
|
|
254
255
|
? this.logo
|
|
255
256
|
: undefined,
|
|
256
257
|
});
|
|
258
|
+
const csp = options.contentSecurityPolicy === false
|
|
259
|
+
? undefined
|
|
260
|
+
: (options.contentSecurityPolicy ??
|
|
261
|
+
buildOpenAPIUIContentSecurityPolicy(options, [
|
|
262
|
+
...documentServerUrls(this.getDocument()),
|
|
263
|
+
...(options.connectSources ?? []),
|
|
264
|
+
]));
|
|
257
265
|
return Object.freeze({
|
|
258
266
|
status: 200,
|
|
259
267
|
headers: Object.freeze({
|
|
260
268
|
"content-type": "text/html; charset=utf-8",
|
|
261
269
|
"cache-control": "public, max-age=300",
|
|
270
|
+
"x-content-type-options": "nosniff",
|
|
271
|
+
...(csp === undefined ? {} : { "content-security-policy": csp }),
|
|
262
272
|
}),
|
|
263
273
|
body,
|
|
264
274
|
});
|
|
@@ -12,6 +12,7 @@ import type { OpenAPISchema } from "../openApiTypes/openApiTypes.core.js";
|
|
|
12
12
|
* Field names as of `@zudojs/schema@0.1.0`:
|
|
13
13
|
* object `_config.shape`, `_config.requiredKeys` (a Set), `_config.unknownKeys`
|
|
14
14
|
* array `_config.itemSchema`, `_config.min`, `_config.max`, `_config.length`
|
|
15
|
+
* (no `max` means the parser's implicit ceiling, which is emitted)
|
|
15
16
|
* string `_config.min|max|length|pattern|format`
|
|
16
17
|
* number `_config.min|max|int|gt|lt|multipleOf`
|
|
17
18
|
* coerce.string / coerce.number
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SCHEMA_DEFAULT_MAX_ARRAY_LENGTH, SCHEMA_DEFAULT_MAX_STRING_LENGTH, } from "@zudojs/constants";
|
|
1
2
|
import { DEFAULT_OPENAPI_VERSION } from "../openApiConstants/openApiConstants.core.js";
|
|
2
3
|
/** Maps `@zudojs/schema` string formats onto OpenAPI `format` values. */
|
|
3
4
|
const STRING_FORMATS = {
|
|
@@ -155,7 +156,7 @@ function convertString(schema, state) {
|
|
|
155
156
|
? { minLength: exact, maxLength: exact }
|
|
156
157
|
: {
|
|
157
158
|
...(num(c["min"]) !== undefined ? { minLength: num(c["min"]) } : {}),
|
|
158
|
-
|
|
159
|
+
maxLength: num(c["max"]) ?? SCHEMA_DEFAULT_MAX_STRING_LENGTH,
|
|
159
160
|
}),
|
|
160
161
|
...(pattern instanceof RegExp
|
|
161
162
|
? { pattern: pattern.source }
|
|
@@ -337,9 +338,7 @@ function convertSchemaNode(schema, state) {
|
|
|
337
338
|
...(num(c["min"]) !== undefined
|
|
338
339
|
? { minItems: num(c["min"]) }
|
|
339
340
|
: {}),
|
|
340
|
-
|
|
341
|
-
? { maxItems: num(c["max"]) }
|
|
342
|
-
: {}),
|
|
341
|
+
maxItems: num(c["max"]) ?? SCHEMA_DEFAULT_MAX_ARRAY_LENGTH,
|
|
343
342
|
}),
|
|
344
343
|
};
|
|
345
344
|
}
|
|
@@ -5,4 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { renderOpenAPIUI, zudoLogo, escapeHtml, type OpenAPIUIOptions, type OpenAPIUIRenderer, } from "./openApiUi.core.js";
|
|
7
7
|
export { ZUDO_MARK_SVG, ZUDO_MARK_DARK_SVG, ZUDO_WORDMARK_SVG, ZUDO_WORDMARK_DARK_SVG, ZUDO_FAVICON_SVG, ZUDO_MARK_DATA_URI, ZUDO_MARK_DARK_DATA_URI, ZUDO_WORDMARK_DATA_URI, ZUDO_WORDMARK_DARK_DATA_URI, ZUDO_FAVICON_DATA_URI, ZUDO_SITE_URL, svgToDataUri, } from "./openApiUi.brand.js";
|
|
8
|
+
export { SWAGGER_UI_VERSION, REDOC_VERSION, DEFAULT_SWAGGER_ASSETS, DEFAULT_REDOC_ASSETS, SWAGGER_UI_INTEGRITY, REDOC_INTEGRITY, type OpenAPIUIAssetIntegrity, } from "./openApiUi.assets.js";
|
|
9
|
+
export { buildOpenAPIUIContentSecurityPolicy } from "./openApiUi.csp.js";
|
|
8
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/openApiUi/index.js
CHANGED
|
@@ -5,4 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { renderOpenAPIUI, zudoLogo, escapeHtml, } from "./openApiUi.core.js";
|
|
7
7
|
export { ZUDO_MARK_SVG, ZUDO_MARK_DARK_SVG, ZUDO_WORDMARK_SVG, ZUDO_WORDMARK_DARK_SVG, ZUDO_FAVICON_SVG, ZUDO_MARK_DATA_URI, ZUDO_MARK_DARK_DATA_URI, ZUDO_WORDMARK_DATA_URI, ZUDO_WORDMARK_DARK_DATA_URI, ZUDO_FAVICON_DATA_URI, ZUDO_SITE_URL, svgToDataUri, } from "./openApiUi.brand.js";
|
|
8
|
+
export { SWAGGER_UI_VERSION, REDOC_VERSION, DEFAULT_SWAGGER_ASSETS, DEFAULT_REDOC_ASSETS, SWAGGER_UI_INTEGRITY, REDOC_INTEGRITY, } from "./openApiUi.assets.js";
|
|
9
|
+
export { buildOpenAPIUIContentSecurityPolicy } from "./openApiUi.csp.js";
|
|
8
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pinned viewer assets for the documentation page.
|
|
3
|
+
*
|
|
4
|
+
* Every default asset URL names an exact package version, and every file
|
|
5
|
+
* carries a Subresource Integrity hash. A floating tag such as
|
|
6
|
+
* `swagger-ui-dist@5` or `redoc/latest` runs whatever the CDN serves today on
|
|
7
|
+
* the API's own origin, where "Try it out" holds bearer tokens and cookies;
|
|
8
|
+
* a pinned, hashed file cannot change underneath the page.
|
|
9
|
+
*
|
|
10
|
+
* The hashes were computed from the files inside the npm tarballs
|
|
11
|
+
* (`swagger-ui-dist-5.33.0.tgz`, `redoc-2.5.4.tgz`) and checked against the
|
|
12
|
+
* copies served by cdn.jsdelivr.net and unpkg.com. When bumping a version,
|
|
13
|
+
* recompute them with
|
|
14
|
+
* `openssl dgst -sha384 -binary <file> | base64 -w0`.
|
|
15
|
+
*/
|
|
16
|
+
/** Swagger UI version served by default. */
|
|
17
|
+
export declare const SWAGGER_UI_VERSION = "5.33.0";
|
|
18
|
+
/** ReDoc version served by default. */
|
|
19
|
+
export declare const REDOC_VERSION = "2.5.4";
|
|
20
|
+
/** Default base URL for Swagger UI assets. */
|
|
21
|
+
export declare const DEFAULT_SWAGGER_ASSETS = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.0";
|
|
22
|
+
/** Default base URL for ReDoc assets. */
|
|
23
|
+
export declare const DEFAULT_REDOC_ASSETS = "https://cdn.jsdelivr.net/npm/redoc@2.5.4/bundles";
|
|
24
|
+
/** Integrity hashes for a viewer's script and (Swagger UI only) stylesheet. */
|
|
25
|
+
export interface OpenAPIUIAssetIntegrity {
|
|
26
|
+
/** SRI hash for the viewer script, e.g. `sha384-…`. */
|
|
27
|
+
readonly script?: string;
|
|
28
|
+
/** SRI hash for the viewer stylesheet (Swagger UI only). */
|
|
29
|
+
readonly stylesheet?: string;
|
|
30
|
+
}
|
|
31
|
+
/** SRI hashes for the default Swagger UI assets. */
|
|
32
|
+
export declare const SWAGGER_UI_INTEGRITY: OpenAPIUIAssetIntegrity;
|
|
33
|
+
/** SRI hashes for the default ReDoc assets. */
|
|
34
|
+
export declare const REDOC_INTEGRITY: OpenAPIUIAssetIntegrity;
|
|
35
|
+
/**
|
|
36
|
+
* Renders ` integrity="…" crossorigin="anonymous"` for an asset tag, or just
|
|
37
|
+
* ` crossorigin="anonymous"` when there is no hash. Rejects anything that is
|
|
38
|
+
* not a well-formed SRI token, since it is interpolated into an attribute.
|
|
39
|
+
*/
|
|
40
|
+
export declare function integrityAttrs(hash: string | undefined): string;
|
|
41
|
+
//# sourceMappingURL=openApiUi.assets.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pinned viewer assets for the documentation page.
|
|
3
|
+
*
|
|
4
|
+
* Every default asset URL names an exact package version, and every file
|
|
5
|
+
* carries a Subresource Integrity hash. A floating tag such as
|
|
6
|
+
* `swagger-ui-dist@5` or `redoc/latest` runs whatever the CDN serves today on
|
|
7
|
+
* the API's own origin, where "Try it out" holds bearer tokens and cookies;
|
|
8
|
+
* a pinned, hashed file cannot change underneath the page.
|
|
9
|
+
*
|
|
10
|
+
* The hashes were computed from the files inside the npm tarballs
|
|
11
|
+
* (`swagger-ui-dist-5.33.0.tgz`, `redoc-2.5.4.tgz`) and checked against the
|
|
12
|
+
* copies served by cdn.jsdelivr.net and unpkg.com. When bumping a version,
|
|
13
|
+
* recompute them with
|
|
14
|
+
* `openssl dgst -sha384 -binary <file> | base64 -w0`.
|
|
15
|
+
*/
|
|
16
|
+
/** Swagger UI version served by default. */
|
|
17
|
+
export const SWAGGER_UI_VERSION = "5.33.0";
|
|
18
|
+
/** ReDoc version served by default. */
|
|
19
|
+
export const REDOC_VERSION = "2.5.4";
|
|
20
|
+
/** Default base URL for Swagger UI assets. */
|
|
21
|
+
export const DEFAULT_SWAGGER_ASSETS = `https://cdn.jsdelivr.net/npm/swagger-ui-dist@${SWAGGER_UI_VERSION}`;
|
|
22
|
+
/** Default base URL for ReDoc assets. */
|
|
23
|
+
export const DEFAULT_REDOC_ASSETS = `https://cdn.jsdelivr.net/npm/redoc@${REDOC_VERSION}/bundles`;
|
|
24
|
+
/** SRI hashes for the default Swagger UI assets. */
|
|
25
|
+
export const SWAGGER_UI_INTEGRITY = Object.freeze({
|
|
26
|
+
script: "sha384-YDALVcy8kj8yltLBVi1vBiBAUqdxvus673gM8XKwiy6aDUJFXivF/KCufekjYbVf",
|
|
27
|
+
stylesheet: "sha384-Ov4/wv3j2bmct8cDc5X4ngJZohVPzEmc6uDPH8WeljUxO5vtoykvMEfbu9Vh6RaW",
|
|
28
|
+
});
|
|
29
|
+
/** SRI hashes for the default ReDoc assets. */
|
|
30
|
+
export const REDOC_INTEGRITY = Object.freeze({
|
|
31
|
+
script: "sha384-w447zOpYfw/1Tv/5AK9NfHTlQIqE3RVR6KY62jCyy9zNDgO64cMwGGP1Fj0zJVf5",
|
|
32
|
+
});
|
|
33
|
+
const SRI = /^(sha256|sha384|sha512)-[A-Za-z0-9+/]+={0,2}$/;
|
|
34
|
+
/**
|
|
35
|
+
* Renders ` integrity="…" crossorigin="anonymous"` for an asset tag, or just
|
|
36
|
+
* ` crossorigin="anonymous"` when there is no hash. Rejects anything that is
|
|
37
|
+
* not a well-formed SRI token, since it is interpolated into an attribute.
|
|
38
|
+
*/
|
|
39
|
+
export function integrityAttrs(hash) {
|
|
40
|
+
if (hash === undefined)
|
|
41
|
+
return ` crossorigin="anonymous"`;
|
|
42
|
+
const tokens = hash.trim().split(/\s+/);
|
|
43
|
+
if (tokens.length === 0 || !tokens.every((t) => SRI.test(t))) {
|
|
44
|
+
throw new TypeError(`Invalid Subresource Integrity value: "${hash}"`);
|
|
45
|
+
}
|
|
46
|
+
return ` integrity="${tokens.join(" ")}" crossorigin="anonymous"`;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=openApiUi.assets.js.map
|
|
@@ -22,5 +22,5 @@ export declare const ZUDO_WORDMARK_DATA_URI: string;
|
|
|
22
22
|
export declare const ZUDO_WORDMARK_DARK_DATA_URI: string;
|
|
23
23
|
export declare const ZUDO_FAVICON_DATA_URI: string;
|
|
24
24
|
/** Where the logo links by default. */
|
|
25
|
-
export declare const ZUDO_SITE_URL = "https://
|
|
25
|
+
export declare const ZUDO_SITE_URL = "https://zudojs.oyinlola.site";
|
|
26
26
|
//# sourceMappingURL=openApiUi.brand.d.ts.map
|
|
@@ -40,5 +40,5 @@ export const ZUDO_WORDMARK_DATA_URI = svgToDataUri(ZUDO_WORDMARK_SVG);
|
|
|
40
40
|
export const ZUDO_WORDMARK_DARK_DATA_URI = svgToDataUri(ZUDO_WORDMARK_DARK_SVG);
|
|
41
41
|
export const ZUDO_FAVICON_DATA_URI = svgToDataUri(ZUDO_FAVICON_SVG);
|
|
42
42
|
/** Where the logo links by default. */
|
|
43
|
-
export const ZUDO_SITE_URL = "https://
|
|
43
|
+
export const ZUDO_SITE_URL = "https://zudojs.oyinlola.site";
|
|
44
44
|
//# sourceMappingURL=openApiUi.brand.js.map
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* `content-type: text/html`. {@link OpenAPIManager.toUIResponse} does that.
|
|
10
10
|
*/
|
|
11
11
|
import type { OpenAPILogo } from "../openApiTypes/openApiTypes.core.js";
|
|
12
|
+
import { type OpenAPIUIAssetIntegrity } from "./openApiUi.assets.js";
|
|
12
13
|
/** Which viewer to render. */
|
|
13
14
|
export type OpenAPIUIRenderer = "swagger" | "redoc";
|
|
14
15
|
/** Options for {@link renderOpenAPIUI}. */
|
|
@@ -21,7 +22,7 @@ export interface OpenAPIUIOptions {
|
|
|
21
22
|
readonly renderer?: OpenAPIUIRenderer;
|
|
22
23
|
/**
|
|
23
24
|
* Logo shown in the page header. Default: the Zudo wordmark linking to
|
|
24
|
-
*
|
|
25
|
+
* zudojs.oyinlola.site. Pass `false` to render no logo at all.
|
|
25
26
|
*/
|
|
26
27
|
readonly logo?: OpenAPILogo | false;
|
|
27
28
|
/** Favicon URL or data URI. Default: the Zudo favicon. */
|
|
@@ -29,12 +30,30 @@ export interface OpenAPIUIOptions {
|
|
|
29
30
|
/** Extra CSS appended after the built-in theme. */
|
|
30
31
|
readonly customCss?: string;
|
|
31
32
|
/**
|
|
32
|
-
* Base URL the viewer's own assets load from. Defaults to
|
|
33
|
-
* `
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* Base URL the viewer's own assets load from. Defaults to exact, pinned
|
|
34
|
+
* versions on cdn.jsdelivr.net (`swagger-ui-dist@5.33.0`, `redoc@2.5.4`),
|
|
35
|
+
* loaded with Subresource Integrity. Point it at a self-hosted copy for
|
|
36
|
+
* air-gapped deployments; pass `assetIntegrity` to keep SRI on that copy.
|
|
36
37
|
*/
|
|
37
38
|
readonly assetsBaseUrl?: string;
|
|
39
|
+
/**
|
|
40
|
+
* SRI hashes for the viewer assets. Default: the pinned hashes when
|
|
41
|
+
* `assetsBaseUrl` is not set, and none when it is (a self-hosted copy may
|
|
42
|
+
* be a different build). Pass `false` to omit `integrity` entirely.
|
|
43
|
+
*/
|
|
44
|
+
readonly assetIntegrity?: OpenAPIUIAssetIntegrity | false;
|
|
45
|
+
/**
|
|
46
|
+
* Value for the `content-security-policy` header sent by
|
|
47
|
+
* `OpenAPIManager.toUIResponse`. Default: a restrictive policy built by
|
|
48
|
+
* `buildOpenAPIUIContentSecurityPolicy`. Pass `false` to send none.
|
|
49
|
+
* Ignored by `renderOpenAPIUI`, which returns only the HTML.
|
|
50
|
+
*/
|
|
51
|
+
readonly contentSecurityPolicy?: string | false;
|
|
52
|
+
/**
|
|
53
|
+
* Extra origins "Try it out" may call, added to the default policy's
|
|
54
|
+
* `connect-src`. The document's absolute `servers` are added automatically.
|
|
55
|
+
*/
|
|
56
|
+
readonly connectSources?: readonly string[];
|
|
38
57
|
/** Options forwarded to `SwaggerUIBundle(...)`; ignored by ReDoc. */
|
|
39
58
|
readonly swaggerOptions?: Readonly<Record<string, unknown>>;
|
|
40
59
|
}
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* `content-type: text/html`. {@link OpenAPIManager.toUIResponse} does that.
|
|
10
10
|
*/
|
|
11
11
|
import { ZUDO_FAVICON_DATA_URI, ZUDO_MARK_DATA_URI, ZUDO_SITE_URL, ZUDO_WORDMARK_DARK_DATA_URI, } from "./openApiUi.brand.js";
|
|
12
|
+
import { DEFAULT_REDOC_ASSETS, DEFAULT_SWAGGER_ASSETS, REDOC_INTEGRITY, SWAGGER_UI_INTEGRITY, integrityAttrs, } from "./openApiUi.assets.js";
|
|
13
|
+
import { swaggerInitScript, THEME_CSS } from "./openApiUiPage/index.js";
|
|
12
14
|
const DEFAULT_TITLE = "API reference";
|
|
13
|
-
const SWAGGER_ASSETS = "https://unpkg.com/swagger-ui-dist@5";
|
|
14
|
-
const REDOC_ASSETS = "https://cdn.redoc.ly/redoc/latest/bundles";
|
|
15
15
|
/** The default logo used by every branded page and by `info["x-logo"]`. */
|
|
16
16
|
export function zudoLogo(overrides) {
|
|
17
17
|
return Object.freeze({
|
|
@@ -31,13 +31,6 @@ export function escapeHtml(value) {
|
|
|
31
31
|
.replace(/"/g, """)
|
|
32
32
|
.replace(/'/g, "'");
|
|
33
33
|
}
|
|
34
|
-
/** Serialises a value for an inline `<script>` without letting `</script>` through. */
|
|
35
|
-
function jsLiteral(value) {
|
|
36
|
-
return JSON.stringify(value)
|
|
37
|
-
.replace(/</g, "\\u003c")
|
|
38
|
-
.replace(/\u2028/g, "\\u2028")
|
|
39
|
-
.replace(/\u2029/g, "\\u2029");
|
|
40
|
-
}
|
|
41
34
|
/**
|
|
42
35
|
* Rejects URLs that could execute script when placed in `src`/`href`.
|
|
43
36
|
*
|
|
@@ -83,26 +76,6 @@ function safeCss(css) {
|
|
|
83
76
|
}
|
|
84
77
|
return css;
|
|
85
78
|
}
|
|
86
|
-
const THEME_CSS = `
|
|
87
|
-
:root{--zd-ink:#1A1A2E;--zd-red:#C0392B;--zd-bg:#FAFAF9;--zd-border:#E5E7EB}
|
|
88
|
-
*{border-radius:0!important}
|
|
89
|
-
html,body{margin:0;background:var(--zd-bg);font-family:Inter,system-ui,-apple-system,"Segoe UI",sans-serif}
|
|
90
|
-
.zudo-bar{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:14px;height:56px;padding:0 20px;background:var(--zd-ink);color:var(--zd-bg);border-bottom:3px solid var(--zd-red)}
|
|
91
|
-
.zudo-bar a{display:inline-flex;align-items:center;color:inherit;text-decoration:none}
|
|
92
|
-
.zudo-bar img{height:22px;width:auto;display:block}
|
|
93
|
-
.zudo-bar .zudo-sep{width:1px;height:22px;background:rgba(250,250,249,.25)}
|
|
94
|
-
.zudo-bar .zudo-title{font-weight:800;font-size:14px;letter-spacing:.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
95
|
-
.zudo-bar .zudo-spec{margin-left:auto;font:700 11px/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.06em;text-transform:uppercase;color:rgba(250,250,249,.75);border:2px solid rgba(250,250,249,.3);padding:6px 10px}
|
|
96
|
-
.zudo-bar .zudo-spec:hover{border-color:var(--zd-bg);color:var(--zd-bg)}
|
|
97
|
-
.zudo-foot{padding:16px 20px;border-top:1px solid var(--zd-border);color:#6B7280;font-size:12px;display:flex;gap:8px;align-items:center}
|
|
98
|
-
.zudo-foot img{height:14px;width:auto}
|
|
99
|
-
.swagger-ui .topbar{display:none}
|
|
100
|
-
.swagger-ui .info .title{font-weight:900;letter-spacing:-.01em;color:var(--zd-ink)}
|
|
101
|
-
.swagger-ui .opblock{border-width:2px;box-shadow:none}
|
|
102
|
-
.swagger-ui .btn{border-width:2px;font-weight:700}
|
|
103
|
-
.swagger-ui .btn.execute{background:var(--zd-red);border-color:var(--zd-red)}
|
|
104
|
-
.swagger-ui .scheme-container{box-shadow:none;border-bottom:1px solid var(--zd-border);background:#fff}
|
|
105
|
-
`;
|
|
106
79
|
function header(options, title) {
|
|
107
80
|
const logo = options.logo === undefined ? zudoLogo({ url: ZUDO_WORDMARK_DARK_DATA_URI }) : options.logo;
|
|
108
81
|
const logoHtml = logo === false
|
|
@@ -131,27 +104,29 @@ export function renderOpenAPIUI(options) {
|
|
|
131
104
|
}
|
|
132
105
|
const title = options.title ?? DEFAULT_TITLE;
|
|
133
106
|
const renderer = options.renderer ?? "swagger";
|
|
107
|
+
const integrity = resolveIntegrity(options, renderer);
|
|
134
108
|
if (renderer === "redoc") {
|
|
135
|
-
const base = (options.assetsBaseUrl ??
|
|
109
|
+
const base = (options.assetsBaseUrl ?? DEFAULT_REDOC_ASSETS).replace(/(?<!\/)\/+$/, "");
|
|
136
110
|
return (head(options, title, "") +
|
|
137
111
|
`<body>${header(options, title)}` +
|
|
138
112
|
`<redoc spec-url="${safeUrl(options.specUrl)}" hide-hostname></redoc>` +
|
|
139
|
-
`<script src="${safeUrl(base)}/redoc.standalone.js"></script>` +
|
|
113
|
+
`<script src="${safeUrl(base)}/redoc.standalone.js"${integrityAttrs(integrity.script)}></script>` +
|
|
140
114
|
`${footer()}</body></html>`);
|
|
141
115
|
}
|
|
142
|
-
const base = (options.assetsBaseUrl ??
|
|
143
|
-
|
|
144
|
-
url: options.specUrl,
|
|
145
|
-
dom_id: "#zudo-openapi",
|
|
146
|
-
deepLinking: true,
|
|
147
|
-
displayRequestDuration: true,
|
|
148
|
-
tryItOutEnabled: true,
|
|
149
|
-
...options.swaggerOptions,
|
|
150
|
-
};
|
|
151
|
-
return (head(options, title, `<link rel="stylesheet" href="${safeUrl(base)}/swagger-ui.css">`) +
|
|
116
|
+
const base = (options.assetsBaseUrl ?? DEFAULT_SWAGGER_ASSETS).replace(/(?<!\/)\/+$/, "");
|
|
117
|
+
return (head(options, title, `<link rel="stylesheet" href="${safeUrl(base)}/swagger-ui.css"${integrityAttrs(integrity.stylesheet)}>`) +
|
|
152
118
|
`<body>${header(options, title)}<div id="zudo-openapi"></div>` +
|
|
153
|
-
`<script src="${safeUrl(base)}/swagger-ui-bundle.js"
|
|
154
|
-
`<script
|
|
119
|
+
`<script src="${safeUrl(base)}/swagger-ui-bundle.js"${integrityAttrs(integrity.script)}></script>` +
|
|
120
|
+
`<script>${swaggerInitScript(options)}</script>` +
|
|
155
121
|
`${footer()}</body></html>`);
|
|
156
122
|
}
|
|
123
|
+
function resolveIntegrity(options, renderer) {
|
|
124
|
+
if (options.assetIntegrity === false)
|
|
125
|
+
return {};
|
|
126
|
+
if (options.assetIntegrity !== undefined)
|
|
127
|
+
return options.assetIntegrity;
|
|
128
|
+
if (options.assetsBaseUrl !== undefined)
|
|
129
|
+
return {};
|
|
130
|
+
return renderer === "redoc" ? REDOC_INTEGRITY : SWAGGER_UI_INTEGRITY;
|
|
131
|
+
}
|
|
157
132
|
//# sourceMappingURL=openApiUi.core.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-Security-Policy for the documentation page.
|
|
3
|
+
*
|
|
4
|
+
* The page runs third-party viewer code on the API's own origin, so the
|
|
5
|
+
* policy limits scripts to the viewer's asset origin plus the hash of the
|
|
6
|
+
* page's single inline bootstrap script, forbids plugins, framing by other
|
|
7
|
+
* origins and `<base>` rewrites, and limits `fetch` to the page's origin, the
|
|
8
|
+
* spec URL and the servers the document declares ("Try it out" calls them).
|
|
9
|
+
*/
|
|
10
|
+
import type { OpenAPIDocument } from "../openApiTypes/openApiTypes.core.js";
|
|
11
|
+
import type { OpenAPIUIOptions } from "./openApiUi.core.js";
|
|
12
|
+
/** Returns `'self'` for relative URLs, the origin for absolute http(s) ones. */
|
|
13
|
+
export declare function cspSourceFor(url: string): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Builds the `content-security-policy` header value for a page rendered by
|
|
16
|
+
* `renderOpenAPIUI(options)`.
|
|
17
|
+
*
|
|
18
|
+
* @param options - The same options the page was rendered with.
|
|
19
|
+
* @param connectSources - Extra URLs or origins "Try it out" may call,
|
|
20
|
+
* typically the document's `servers`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildOpenAPIUIContentSecurityPolicy(options: OpenAPIUIOptions, connectSources?: readonly string[]): string;
|
|
23
|
+
/** Every `servers[].url` in a document: top level, path items and operations. */
|
|
24
|
+
export declare function documentServerUrls(document: OpenAPIDocument): string[];
|
|
25
|
+
//# sourceMappingURL=openApiUi.csp.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-Security-Policy for the documentation page.
|
|
3
|
+
*
|
|
4
|
+
* The page runs third-party viewer code on the API's own origin, so the
|
|
5
|
+
* policy limits scripts to the viewer's asset origin plus the hash of the
|
|
6
|
+
* page's single inline bootstrap script, forbids plugins, framing by other
|
|
7
|
+
* origins and `<base>` rewrites, and limits `fetch` to the page's origin, the
|
|
8
|
+
* spec URL and the servers the document declares ("Try it out" calls them).
|
|
9
|
+
*/
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { DEFAULT_REDOC_ASSETS, DEFAULT_SWAGGER_ASSETS } from "./openApiUi.assets.js";
|
|
12
|
+
import { swaggerInitScript } from "./openApiUiPage/index.js";
|
|
13
|
+
/** Returns `'self'` for relative URLs, the origin for absolute http(s) ones. */
|
|
14
|
+
export function cspSourceFor(url) {
|
|
15
|
+
const trimmed = url.trim();
|
|
16
|
+
if (!/^[a-z][a-z0-9+.-]*:/i.test(trimmed) && !trimmed.startsWith("//")) {
|
|
17
|
+
return "'self'";
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const parsed = new URL(trimmed.startsWith("//") ? `https:${trimmed}` : trimmed);
|
|
21
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:")
|
|
22
|
+
return undefined;
|
|
23
|
+
return parsed.origin;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function unique(values) {
|
|
30
|
+
return [...new Set(values.filter((v) => v !== undefined))].join(" ");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Builds the `content-security-policy` header value for a page rendered by
|
|
34
|
+
* `renderOpenAPIUI(options)`.
|
|
35
|
+
*
|
|
36
|
+
* @param options - The same options the page was rendered with.
|
|
37
|
+
* @param connectSources - Extra URLs or origins "Try it out" may call,
|
|
38
|
+
* typically the document's `servers`.
|
|
39
|
+
*/
|
|
40
|
+
export function buildOpenAPIUIContentSecurityPolicy(options, connectSources = []) {
|
|
41
|
+
const renderer = options.renderer ?? "swagger";
|
|
42
|
+
const assets = options.assetsBaseUrl ??
|
|
43
|
+
(renderer === "redoc" ? DEFAULT_REDOC_ASSETS : DEFAULT_SWAGGER_ASSETS);
|
|
44
|
+
const assetSource = cspSourceFor(assets) ?? "'self'";
|
|
45
|
+
const scriptSources = renderer === "redoc"
|
|
46
|
+
? [assetSource]
|
|
47
|
+
: [
|
|
48
|
+
assetSource,
|
|
49
|
+
`'sha256-${createHash("sha256")
|
|
50
|
+
.update(swaggerInitScript(options))
|
|
51
|
+
.digest("base64")}'`,
|
|
52
|
+
];
|
|
53
|
+
const connect = unique([
|
|
54
|
+
"'self'",
|
|
55
|
+
cspSourceFor(options.specUrl),
|
|
56
|
+
...connectSources.map(cspSourceFor),
|
|
57
|
+
]);
|
|
58
|
+
return [
|
|
59
|
+
"default-src 'none'",
|
|
60
|
+
`script-src ${unique(scriptSources)}`,
|
|
61
|
+
`style-src ${unique([assetSource, "'unsafe-inline'"])}`,
|
|
62
|
+
"img-src 'self' data: https:",
|
|
63
|
+
`font-src ${unique([assetSource, "data:"])}`,
|
|
64
|
+
`connect-src ${connect}`,
|
|
65
|
+
"worker-src 'self' blob:",
|
|
66
|
+
"object-src 'none'",
|
|
67
|
+
"base-uri 'none'",
|
|
68
|
+
"form-action 'self'",
|
|
69
|
+
"frame-ancestors 'self'",
|
|
70
|
+
].join("; ");
|
|
71
|
+
}
|
|
72
|
+
/** Every `servers[].url` in a document: top level, path items and operations. */
|
|
73
|
+
export function documentServerUrls(document) {
|
|
74
|
+
const urls = (document.servers ?? []).map((s) => s.url);
|
|
75
|
+
for (const item of Object.values(document.paths ?? {})) {
|
|
76
|
+
if (item === undefined || item === null)
|
|
77
|
+
continue;
|
|
78
|
+
for (const s of item.servers ?? [])
|
|
79
|
+
urls.push(s.url);
|
|
80
|
+
for (const value of Object.values(item)) {
|
|
81
|
+
const servers = value?.servers;
|
|
82
|
+
if (!Array.isArray(servers))
|
|
83
|
+
continue;
|
|
84
|
+
for (const s of servers) {
|
|
85
|
+
if (typeof s?.url === "string")
|
|
86
|
+
urls.push(s.url);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return urls;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=openApiUi.csp.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/openapi/openApiUi/openApiUiPage
|
|
3
|
+
*
|
|
4
|
+
* Internal page parts shared by the renderer and its Content-Security-Policy:
|
|
5
|
+
* the Swagger UI bootstrap script and the Zudo theme CSS.
|
|
6
|
+
*/
|
|
7
|
+
export { jsLiteral, swaggerInitScript, type SwaggerInitInput, } from "./openApiUiPage.script.js";
|
|
8
|
+
export { THEME_CSS } from "./openApiUiPage.theme.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/openapi/openApiUi/openApiUiPage
|
|
3
|
+
*
|
|
4
|
+
* Internal page parts shared by the renderer and its Content-Security-Policy:
|
|
5
|
+
* the Swagger UI bootstrap script and the Zudo theme CSS.
|
|
6
|
+
*/
|
|
7
|
+
export { jsLiteral, swaggerInitScript, } from "./openApiUiPage.script.js";
|
|
8
|
+
export { THEME_CSS } from "./openApiUiPage.theme.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one inline script the Swagger UI page runs, built in one place so the
|
|
3
|
+
* page and its Content-Security-Policy hash agree byte for byte.
|
|
4
|
+
*/
|
|
5
|
+
/** Serialises a value for an inline `<script>` without letting `</script>` through. */
|
|
6
|
+
export declare function jsLiteral(value: unknown): string;
|
|
7
|
+
/** Inputs that shape the Swagger UI bootstrap script. */
|
|
8
|
+
export interface SwaggerInitInput {
|
|
9
|
+
readonly specUrl: string;
|
|
10
|
+
readonly swaggerOptions?: Readonly<Record<string, unknown>>;
|
|
11
|
+
}
|
|
12
|
+
/** Returns the body of the inline `<script>` that boots Swagger UI. */
|
|
13
|
+
export declare function swaggerInitScript(input: SwaggerInitInput): string;
|
|
14
|
+
//# sourceMappingURL=openApiUiPage.script.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one inline script the Swagger UI page runs, built in one place so the
|
|
3
|
+
* page and its Content-Security-Policy hash agree byte for byte.
|
|
4
|
+
*/
|
|
5
|
+
/** Serialises a value for an inline `<script>` without letting `</script>` through. */
|
|
6
|
+
export function jsLiteral(value) {
|
|
7
|
+
return JSON.stringify(value)
|
|
8
|
+
.replace(/</g, "\\u003c")
|
|
9
|
+
.replace(/\u2028/g, "\\u2028")
|
|
10
|
+
.replace(/\u2029/g, "\\u2029");
|
|
11
|
+
}
|
|
12
|
+
/** Returns the body of the inline `<script>` that boots Swagger UI. */
|
|
13
|
+
export function swaggerInitScript(input) {
|
|
14
|
+
const config = {
|
|
15
|
+
url: input.specUrl,
|
|
16
|
+
dom_id: "#zudo-openapi",
|
|
17
|
+
deepLinking: true,
|
|
18
|
+
displayRequestDuration: true,
|
|
19
|
+
tryItOutEnabled: true,
|
|
20
|
+
...input.swaggerOptions,
|
|
21
|
+
};
|
|
22
|
+
return `window.addEventListener("load",function(){window.ui=SwaggerUIBundle(${jsLiteral(config)});});`;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=openApiUiPage.script.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Zudo theme applied on top of the viewer's own stylesheet.
|
|
3
|
+
*/
|
|
4
|
+
/** Built-in page CSS; `customCss` is appended after it. */
|
|
5
|
+
export declare const THEME_CSS = "\n:root{--zd-ink:#1A1A2E;--zd-red:#C0392B;--zd-bg:#FAFAF9;--zd-border:#E5E7EB}\n*{border-radius:0!important}\nhtml,body{margin:0;background:var(--zd-bg);font-family:Inter,system-ui,-apple-system,\"Segoe UI\",sans-serif}\n.zudo-bar{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:14px;height:56px;padding:0 20px;background:var(--zd-ink);color:var(--zd-bg);border-bottom:3px solid var(--zd-red)}\n.zudo-bar a{display:inline-flex;align-items:center;color:inherit;text-decoration:none}\n.zudo-bar img{height:22px;width:auto;display:block}\n.zudo-bar .zudo-sep{width:1px;height:22px;background:rgba(250,250,249,.25)}\n.zudo-bar .zudo-title{font-weight:800;font-size:14px;letter-spacing:.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.zudo-bar .zudo-spec{margin-left:auto;font:700 11px/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.06em;text-transform:uppercase;color:rgba(250,250,249,.75);border:2px solid rgba(250,250,249,.3);padding:6px 10px}\n.zudo-bar .zudo-spec:hover{border-color:var(--zd-bg);color:var(--zd-bg)}\n.zudo-foot{padding:16px 20px;border-top:1px solid var(--zd-border);color:#6B7280;font-size:12px;display:flex;gap:8px;align-items:center}\n.zudo-foot img{height:14px;width:auto}\n.swagger-ui .topbar{display:none}\n.swagger-ui .info .title{font-weight:900;letter-spacing:-.01em;color:var(--zd-ink)}\n.swagger-ui .opblock{border-width:2px;box-shadow:none}\n.swagger-ui .btn{border-width:2px;font-weight:700}\n.swagger-ui .btn.execute{background:var(--zd-red);border-color:var(--zd-red)}\n.swagger-ui .scheme-container{box-shadow:none;border-bottom:1px solid var(--zd-border);background:#fff}\n";
|
|
6
|
+
//# sourceMappingURL=openApiUiPage.theme.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Zudo theme applied on top of the viewer's own stylesheet.
|
|
3
|
+
*/
|
|
4
|
+
/** Built-in page CSS; `customCss` is appended after it. */
|
|
5
|
+
export const THEME_CSS = `
|
|
6
|
+
:root{--zd-ink:#1A1A2E;--zd-red:#C0392B;--zd-bg:#FAFAF9;--zd-border:#E5E7EB}
|
|
7
|
+
*{border-radius:0!important}
|
|
8
|
+
html,body{margin:0;background:var(--zd-bg);font-family:Inter,system-ui,-apple-system,"Segoe UI",sans-serif}
|
|
9
|
+
.zudo-bar{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:14px;height:56px;padding:0 20px;background:var(--zd-ink);color:var(--zd-bg);border-bottom:3px solid var(--zd-red)}
|
|
10
|
+
.zudo-bar a{display:inline-flex;align-items:center;color:inherit;text-decoration:none}
|
|
11
|
+
.zudo-bar img{height:22px;width:auto;display:block}
|
|
12
|
+
.zudo-bar .zudo-sep{width:1px;height:22px;background:rgba(250,250,249,.25)}
|
|
13
|
+
.zudo-bar .zudo-title{font-weight:800;font-size:14px;letter-spacing:.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
14
|
+
.zudo-bar .zudo-spec{margin-left:auto;font:700 11px/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.06em;text-transform:uppercase;color:rgba(250,250,249,.75);border:2px solid rgba(250,250,249,.3);padding:6px 10px}
|
|
15
|
+
.zudo-bar .zudo-spec:hover{border-color:var(--zd-bg);color:var(--zd-bg)}
|
|
16
|
+
.zudo-foot{padding:16px 20px;border-top:1px solid var(--zd-border);color:#6B7280;font-size:12px;display:flex;gap:8px;align-items:center}
|
|
17
|
+
.zudo-foot img{height:14px;width:auto}
|
|
18
|
+
.swagger-ui .topbar{display:none}
|
|
19
|
+
.swagger-ui .info .title{font-weight:900;letter-spacing:-.01em;color:var(--zd-ink)}
|
|
20
|
+
.swagger-ui .opblock{border-width:2px;box-shadow:none}
|
|
21
|
+
.swagger-ui .btn{border-width:2px;font-weight:700}
|
|
22
|
+
.swagger-ui .btn.execute{background:var(--zd-red);border-color:var(--zd-red)}
|
|
23
|
+
.swagger-ui .scheme-container{box-shadow:none;border-bottom:1px solid var(--zd-border);background:#fff}
|
|
24
|
+
`;
|
|
25
|
+
//# sourceMappingURL=openApiUiPage.theme.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/openapi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "OpenAPI 3.0 and 3.1 specification generation, validation, and serialization for Zudojs applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"!dist/.tsbuildinfo"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@zudojs/
|
|
29
|
+
"@zudojs/constants": "1.1.0",
|
|
30
|
+
"@zudojs/errors": "1.1.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@types/node": "^26.4.1",
|