@scalar/client-side-rendering 0.1.13 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @scalar/client-side-rendering
2
2
 
3
+ ## 0.2.1
4
+
5
+ ## 0.2.0
6
+
7
+ ### Minor Changes
8
+
9
+ - [#9422](https://github.com/scalar/scalar/pull/9422): Add a `nonce` option for Content Security Policy support.
10
+
11
+ When you pass a `nonce`, the rendered HTML stamps it onto the inline `<script>` and the CDN `<script>` tag (and Scalar's own `<style>` tags, plus a matching `<meta property="csp-nonce">`). This lets the API Reference run under a strict `script-src` with no `unsafe-inline` and no `unsafe-eval`.
12
+
13
+ ```ts
14
+ ApiReference({
15
+ url: '/openapi.json',
16
+ // Match this value in your `script-src` CSP directive.
17
+ nonce: 'r4nd0m',
18
+ })
19
+ ```
20
+
21
+ Note: `style-src` still needs `'unsafe-inline'`. The reference renders inline `style="…"` attributes, which a CSP nonce can never authorize (nonces only apply to `<script>`, `<style>` and `<link>` elements), so a nonce-only `style-src` is not possible. The win is a fully strict `script-src`.
22
+
3
23
  ## 0.1.13
4
24
 
5
25
  ### Patch Changes
@@ -17,11 +17,26 @@ export declare function renderApiReference(options: {
17
17
  pageTitle?: string;
18
18
  /** CDN URL for the standalone bundle. Defaults to jsDelivr. */
19
19
  cdn?: string;
20
+ /**
21
+ * A Content Security Policy (CSP) nonce to apply to the generated inline `<script>` and `<style>`
22
+ * tags (and the CDN `<script>` tag).
23
+ *
24
+ * When set, a `<meta property="csp-nonce">` tag is also emitted so the standalone bundle can apply
25
+ * the same nonce to the stylesheet it injects at runtime. This lets the API Reference run under a
26
+ * strict `script-src` with no `unsafe-inline` and no `unsafe-eval`.
27
+ *
28
+ * Note: `style-src` still needs `'unsafe-inline'`, because the reference renders inline
29
+ * `style="..."` attributes that a CSP nonce cannot authorize.
30
+ */
31
+ nonce?: string;
20
32
  }, customTheme?: string): string;
21
33
  /**
22
34
  * The script tags to load the @scalar/api-reference package from the CDN.
35
+ *
36
+ * When a `nonce` is provided it is applied to both script tags so they are allowed under a strict
37
+ * `script-src` Content Security Policy.
23
38
  */
24
- export declare function getScriptTags(configuration: Record<string, unknown>, cdn?: string): string;
39
+ export declare function getScriptTags(configuration: Record<string, unknown>, cdn?: string, nonce?: string): string;
25
40
  /**
26
41
  * The configuration to pass to the @scalar/api-reference package.
27
42
  */
@@ -1 +1 @@
1
- {"version":3,"file":"html-rendering.d.ts","sourceRoot":"","sources":["../src/html-rendering.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAA;AAE3G,YAAY,EAAE,4BAA4B,EAAE,0BAA0B,EAAE,CAAA;AAExE,uEAAuE;AACvE,eAAO,MAAM,WAAW,uDAAuD,CAAA;AAsD/E;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE;IACP,uCAAuC;IACvC,MAAM,EAAE,4BAA4B,CAAA;IACpC,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,EACD,WAAW,SAAK,GACf,MAAM,CA0BR;AASD;;GAEG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAwC1F;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAC3B,oBAAoB,OAAO,CAAC,0BAA0B,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAChF,MAAM,CAAC,MAAM,EAAE,OAAO,CAcxB,CAAA"}
1
+ {"version":3,"file":"html-rendering.d.ts","sourceRoot":"","sources":["../src/html-rendering.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAA;AAE3G,YAAY,EAAE,4BAA4B,EAAE,0BAA0B,EAAE,CAAA;AAExE,uEAAuE;AACvE,eAAO,MAAM,WAAW,uDAAuD,CAAA;AAoE/E;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE;IACP,uCAAuC;IACvC,MAAM,EAAE,4BAA4B,CAAA;IACpC,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;;;;;;;OAUG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,EACD,WAAW,SAAK,GACf,MAAM,CA8BR;AASD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CA0C1G;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAC3B,oBAAoB,OAAO,CAAC,0BAA0B,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAChF,MAAM,CAAC,MAAM,EAAE,OAAO,CAcxB,CAAA"}
@@ -6,6 +6,18 @@ export const DEFAULT_CDN = 'https://cdn.jsdelivr.net/npm/@scalar/api-reference';
6
6
  const escapeHtml = (str) => {
7
7
  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
8
8
  };
9
+ /**
10
+ * Escape a value for use inside a double-quoted HTML attribute.
11
+ *
12
+ * On top of the regular HTML escaping we also encode double quotes so the value cannot break out of
13
+ * the attribute. Used for the CSP nonce, which is attacker-influenced in some setups.
14
+ */
15
+ const escapeHtmlAttribute = (str) => escapeHtml(str).replace(/"/g, '&quot;');
16
+ /**
17
+ * Build a ` nonce="..."` attribute (with a leading space) when a nonce is provided, otherwise an
18
+ * empty string. Returned ready to be interpolated into a tag.
19
+ */
20
+ const nonceAttribute = (nonce) => (nonce ? ` nonce="${escapeHtmlAttribute(nonce)}"` : '');
9
21
  /**
10
22
  * Helper function to add consistent indentation to multiline strings
11
23
  * @param str The string to indent
@@ -27,7 +39,7 @@ const addIndent = (str, spaces = 2, initialIndent = false) => {
27
39
  /**
28
40
  * Generate the style tag with custom theme if needed
29
41
  */
30
- const getStyles = (configuration, customTheme) => {
42
+ const getStyles = (configuration, customTheme, nonce) => {
31
43
  const styles = [];
32
44
  if (configuration.customCss) {
33
45
  styles.push('/* Custom CSS */');
@@ -41,7 +53,7 @@ const getStyles = (configuration, customTheme) => {
41
53
  return '';
42
54
  }
43
55
  return `
44
- <style type="text/css">
56
+ <style type="text/css"${nonceAttribute(nonce)}>
45
57
  ${addIndent(styles.join('\n\n'), 6)}
46
58
  </style>`;
47
59
  };
@@ -54,7 +66,7 @@ const getStyles = (configuration, customTheme) => {
54
66
  * For server-side rendering with hydration, use the server module instead.
55
67
  */
56
68
  export function renderApiReference(options, customTheme = '') {
57
- const { config: givenConfig, pageTitle, cdn } = options;
69
+ const { config: givenConfig, pageTitle, cdn, nonce } = options;
58
70
  const title = escapeHtml(pageTitle ?? 'Scalar API Reference');
59
71
  const unwrapped = Array.isArray(givenConfig) ? givenConfig[0] : givenConfig;
60
72
  const { customCss, theme, ...rest } = (unwrapped ?? {});
@@ -63,6 +75,9 @@ export function renderApiReference(options, customTheme = '') {
63
75
  ...(theme ? { theme } : {}),
64
76
  ...(customCss !== undefined ? { customCss } : {}),
65
77
  });
78
+ // Expose the nonce to the standalone bundle so it can apply it to the stylesheet it injects at
79
+ // runtime (the bundle reads `meta[property=csp-nonce]` when built with `useStrictCSP`).
80
+ const cspNonceMeta = nonce ? `\n <meta property="csp-nonce" content="${escapeHtmlAttribute(nonce)}" />` : '';
66
81
  return `<!doctype html>
67
82
  <html>
68
83
  <head>
@@ -70,10 +85,10 @@ export function renderApiReference(options, customTheme = '') {
70
85
  <meta charset="utf-8" />
71
86
  <meta
72
87
  name="viewport"
73
- content="width=device-width, initial-scale=1" />${getStyles(configuration, customTheme)}
88
+ content="width=device-width, initial-scale=1" />${cspNonceMeta}${getStyles(configuration, customTheme, nonce)}
74
89
  </head>
75
90
  <body>
76
- <div id="app"></div>${getScriptTags(configuration, cdn)}
91
+ <div id="app"></div>${getScriptTags(configuration, cdn, nonce)}
77
92
  </body>
78
93
  </html>`;
79
94
  }
@@ -85,8 +100,11 @@ const serializeArrayWithFunctions = (arr) => {
85
100
  };
86
101
  /**
87
102
  * The script tags to load the @scalar/api-reference package from the CDN.
103
+ *
104
+ * When a `nonce` is provided it is applied to both script tags so they are allowed under a strict
105
+ * `script-src` Content Security Policy.
88
106
  */
89
- export function getScriptTags(configuration, cdn) {
107
+ export function getScriptTags(configuration, cdn, nonce) {
90
108
  const restConfig = { ...configuration };
91
109
  const functionProps = [];
92
110
  for (const [key, value] of Object.entries(configuration)) {
@@ -114,12 +132,13 @@ export function getScriptTags(configuration, cdn) {
114
132
  configString = `${jsonWithoutClosingBrace},\n ${functionProps.join(',\n ')}\n }`;
115
133
  }
116
134
  }
135
+ const nonceAttr = nonceAttribute(nonce);
117
136
  return `
118
137
  <!-- Load the Script -->
119
- <script src="${cdn ?? DEFAULT_CDN}"></script>
138
+ <script src="${cdn ?? DEFAULT_CDN}"${nonceAttr}></script>
120
139
 
121
140
  <!-- Initialize the Scalar API Reference -->
122
- <script type="text/javascript">
141
+ <script type="text/javascript"${nonceAttr}>
123
142
  Scalar.createApiReference('#app', ${configString})
124
143
  </script>`;
125
144
  }
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "directory": "packages/client-side-rendering"
12
12
  },
13
13
  "keywords": [],
14
- "version": "0.1.13",
14
+ "version": "0.2.1",
15
15
  "engines": {
16
16
  "node": ">=22"
17
17
  },
@@ -28,8 +28,8 @@
28
28
  "CHANGELOG.md"
29
29
  ],
30
30
  "dependencies": {
31
- "@scalar/schemas": "0.3.3",
32
- "@scalar/types": "0.12.3",
31
+ "@scalar/schemas": "0.4.1",
32
+ "@scalar/types": "0.13.1",
33
33
  "@scalar/validation": "0.6.0"
34
34
  },
35
35
  "devDependencies": {
@@ -38,6 +38,6 @@
38
38
  "scripts": {
39
39
  "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
40
40
  "test": "vitest --run",
41
- "types:check": "tsc --noEmit"
41
+ "types:check": "tsgo --noEmit"
42
42
  }
43
43
  }