@blinkk/root 1.0.0-rc.35 → 1.0.0-rc.36
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/bin/root.js +5 -32
- package/dist/{chunk-MJCIAH6K.js → chunk-7IDJ3PBT.js} +1 -1
- package/dist/{chunk-MJCIAH6K.js.map → chunk-7IDJ3PBT.js.map} +1 -1
- package/dist/{chunk-JH33OQEK.js → chunk-L2UBQKLP.js} +7 -8
- package/dist/{chunk-JH33OQEK.js.map → chunk-L2UBQKLP.js.map} +1 -1
- package/dist/{chunk-3XHMMHV7.js → chunk-TZAHHHA4.js} +6 -8
- package/dist/{chunk-3XHMMHV7.js.map → chunk-TZAHHHA4.js.map} +1 -1
- package/dist/{chunk-5ZBCP3XZ.js → chunk-VDORN4UU.js} +89 -50
- package/dist/chunk-VDORN4UU.js.map +1 -0
- package/dist/cli.d.ts +10 -3
- package/dist/cli.js +5 -3
- package/dist/core.d.ts +4 -2
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/functions.js +6 -6
- package/dist/functions.js.map +1 -1
- package/dist/middleware.d.ts +2 -2
- package/dist/middleware.js +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +1 -1
- package/dist/render.d.ts +1 -1
- package/dist/render.js +138 -31
- package/dist/render.js.map +1 -1
- package/dist/{types-a9rD7sef.d.ts → types-403nR8i5.d.ts} +58 -1
- package/package.json +2 -2
- package/dist/chunk-5ZBCP3XZ.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CliRunner,
|
|
2
3
|
build,
|
|
3
4
|
createDevServer,
|
|
4
5
|
createPreviewServer,
|
|
@@ -6,12 +7,13 @@ import {
|
|
|
6
7
|
dev,
|
|
7
8
|
preview,
|
|
8
9
|
start
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-VDORN4UU.js";
|
|
11
|
+
import "./chunk-TZAHHHA4.js";
|
|
12
|
+
import "./chunk-L2UBQKLP.js";
|
|
12
13
|
import "./chunk-QKBMWK5B.js";
|
|
13
14
|
import "./chunk-IUQLRDFW.js";
|
|
14
15
|
export {
|
|
16
|
+
CliRunner,
|
|
15
17
|
build,
|
|
16
18
|
createDevServer,
|
|
17
19
|
createPreviewServer,
|
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as Route } from './types-
|
|
2
|
-
export {
|
|
1
|
+
import { R as Route } from './types-403nR8i5.js';
|
|
2
|
+
export { i as ConfigureServerHook, j as ConfigureServerOptions, C as ContentSecurityPolicyConfig, n as GetStaticPaths, G as GetStaticProps, r as Handler, H as HandlerContext, u as HandlerRenderFn, t as HandlerRenderOptions, L as LocaleGroup, M as MultipartFile, N as NextFunction, P as Plugin, p as Request, o as RequestMiddleware, q as Response, b as RootConfig, e as RootHeaderConfig, c as RootI18nConfig, d as RootRedirectConfig, f as RootSecurityConfig, g as RootServerConfig, a as RootUserConfig, s as RouteModule, m as RouteParams, S as Server, X as XFrameOptionsConfig, k as configureServerPlugins, h as defineConfig, l as getVitePlugins } from './types-403nR8i5.js';
|
|
3
3
|
import * as preact$1 from 'preact';
|
|
4
4
|
import { FunctionalComponent, ComponentChildren } from 'preact';
|
|
5
5
|
import 'express';
|
|
@@ -116,6 +116,8 @@ interface RequestContext {
|
|
|
116
116
|
locale: string;
|
|
117
117
|
/** Translations map for the current locale. */
|
|
118
118
|
translations: Record<string, string>;
|
|
119
|
+
/** CSP nonce value. */
|
|
120
|
+
nonce?: string;
|
|
119
121
|
}
|
|
120
122
|
/**
|
|
121
123
|
* A hook that returns information about the current route.
|
package/dist/core.js
CHANGED
package/dist/core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/config.ts","../src/core/components/Body.tsx","../src/core/components/Head.ts","../src/core/components/Script.ts","../src/core/hooks/useTranslations.ts"],"sourcesContent":["import {UserConfig as ViteUserConfig} from 'vite';\n\nimport {HtmlMinifyOptions} from '../render/html-minify';\nimport {HtmlPrettyOptions} from '../render/html-pretty';\n\nimport {Plugin} from './plugin';\nimport {RequestMiddleware} from './types';\n\nexport interface RootUserConfig {\n /**\n * Canonical domain the website will serve on. Useful for things like the\n * sitemap, SEO tags, etc.\n */\n domain?: string;\n\n /**\n * The base URL path that the site will serve on. Defaults to `/`;\n */\n base?: string;\n\n /**\n * Config for auto-injecting custom element dependencies.\n */\n elements?: {\n /**\n * A list of directories to use to look for custom elements. The dir path\n * should be relative to the project dir, e.g. \"path/to/elements\".\n */\n include?: string[];\n\n /**\n * A list of RegEx patterns to exclude. The string passed to the RegEx is\n * the file URL relative to the project root, e.g. \"/elements/foo/foo.ts\".\n */\n exclude?: RegExp[];\n };\n\n /**\n * Config options for localization and internationalization.\n */\n i18n?: RootI18nConfig;\n\n /**\n * Config options for the Root.js express server.\n */\n server?: RootServerConfig;\n\n /**\n * Vite config.\n * @see {@link https://vitejs.dev/config/} for more information.\n */\n vite?: ViteUserConfig;\n\n /**\n * Whether to automatically minify HTML output. This is enabled by default,\n * in order to disable, pass `minifyHtml: false` to root.config.ts.\n */\n minifyHtml?: boolean;\n\n /**\n * Options to pass to html-minifier-terser.\n */\n minifyHtmlOptions?: HtmlMinifyOptions;\n\n /**\n * Whether to pretty print HTML output.\n */\n prettyHtml?: boolean;\n\n /**\n * Options to pass to js-beautify.\n */\n prettyHtmlOptions?: HtmlPrettyOptions;\n\n /**\n * Whether to include a sitemap.xml file to the build output.\n */\n sitemap?: boolean;\n\n /**\n * Plugins.\n */\n plugins?: Plugin[];\n}\n\nexport type RootConfig = RootUserConfig & {\n rootDir: string;\n};\n\nexport interface LocaleGroup {\n label?: string;\n locales: string[];\n}\n\nexport interface RootI18nConfig {\n /**\n * Locales enabled for the site.\n */\n locales?: string[];\n\n /**\n * The default locale to use. Defaults is `en`.\n */\n defaultLocale?: string;\n\n /**\n * URL format for localized content. Default is `/[locale]/[base]/[path]`.\n */\n urlFormat?: string;\n\n /**\n * Localization groups, to help UIs (like Root.js CMS) logically group\n * locales.\n */\n groups?: Record<string, LocaleGroup>;\n}\n\nexport interface RootRedirectConfig {\n source: string;\n destination: string;\n type?: number;\n}\n\nexport interface RootHeaderConfig {\n /** A glob pattern match (regex not supported yet). */\n source: string;\n headers: Array<{\n key: string;\n value: string;\n }>;\n}\n\nexport interface RootServerConfig {\n /**\n * An array of middleware to add to the express server. These middleware are\n * added to the beginning of the express app.\n */\n middlewares?: RequestMiddleware[];\n\n /**\n * The `trailingSlash` config allows you to control how the server handles\n * trailing slashes. This config only affects URLs that do not have a file\n * extension (i.e. HTML paths).\n *\n * - When `true`, the server redirects URLs to add a trailing slash\n * - When `false`, the server redirects URLs to remove a trailing slash\n * - When unspecified, the server allows URLs with and without trailing slash\n */\n trailingSlash?: boolean;\n\n /**\n * Cookie secret for the session middleware.\n */\n sessionCookieSecret?: string | string[];\n\n /**\n * List of redirects.\n */\n redirects?: RootRedirectConfig[];\n\n /**\n * HTTP headers to add to a response.\n */\n headers?: RootHeaderConfig[];\n}\n\nexport function defineConfig(config: RootUserConfig): RootUserConfig {\n return config;\n}\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type BodyProps = preact.JSX.HTMLAttributes<HTMLBodyElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The `<Body>` component can be used to update attrs in the `<body>` tag.\n *\n * Usage:\n *\n * ```tsx\n * <Body className=\"body\">\n * <h1>Hello world</h1>\n * </Body>\n * ```\n *\n * Output:\n *\n * ```html\n * <body class=\"body\">\n * <h1>Hello world</h1>\n * </body>\n */\nexport const Body: FunctionalComponent<BodyProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Body> component'\n );\n }\n context.bodyAttrs = attrs;\n return <>{children}</>;\n};\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type HeadProps = preact.JSX.HTMLAttributes<HTMLHeadElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The <Head> component can be used for injecting elements into the HTML head\n * tag from any part of a page. The <Head> can be added via any component or\n * sub-component and will automatically be hoisted to the `<head>` element.\n *\n * Usage:\n *\n * ```tsx\n * <Head>\n * <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n * </Head>\n * ```\n */\nexport const Head: FunctionalComponent<HeadProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Head> component'\n );\n }\n context.headComponents.push(children);\n context.headAttrs = attrs;\n return null;\n};\n","import {FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type ScriptProps = preact.JSX.HTMLAttributes<HTMLScriptElement>;\n\n/**\n * The <Script> component is used for rendering any custom script modules. At\n * the moment, the system only pre-renders and bundles files that are in the\n * `/bundles` folder at the root of the project.\n *\n * Usage:\n *\n * ```tsx\n * <Script src=\"/bundles/main.ts\" />\n * ```\n */\nexport const Script: FunctionalComponent<ScriptProps> = (props) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Script> component'\n );\n }\n context.scriptDeps.push(props);\n return null;\n};\n","import {useI18nContext} from './useI18nContext';\n\n/**\n * A hook that returns a function that can be used to translate a string, and\n * optionally replace any parameterized values that are surrounded in curly\n * braces.\n *\n * Usage:\n *\n * ```ts\n * const t = useTranslations();\n * t('Hello {name}', {name: 'Bob'});\n * // => 'Bounjour Bob'\n */\nexport function useTranslations() {\n const context = useI18nContext();\n const translations = context.translations || {};\n const t = (str: string, params?: Record<string, string | number>) => {\n const key = normalizeString(str);\n let translation = translations[key] ?? key ?? '';\n if (params) {\n for (const param of Object.keys(params)) {\n const val = String(params[param] ?? '');\n translation = translation.replaceAll(`{${param}}`, val);\n }\n }\n return translation;\n };\n return t;\n}\n\n/**\n * Cleans a string that's used for translations. Performs the following:\n * - Removes any leading/trailing whitespace\n * - Removes spaces at the end of any line\n */\nexport function normalizeString(str: string) {\n const lines = String(str)\n .trim()\n .split('\\n')\n .map((line) => line.trimEnd());\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;;AAsKO,SAAS,aAAa,QAAwC;AACnE,SAAO;AACT;;;ACvKA,SAAQ,kBAAiB;AAkChB;AARF,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAU,WAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,YAAY;AACpB,SAAO,gCAAG,UAAS;AACrB;;;ACnCA,SAAQ,cAAAA,mBAAiB;AAqBlB,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,eAAe,KAAK,QAAQ;AACpC,UAAQ,YAAY;AACpB,SAAO;AACT;;;AC/BA,SAAQ,cAAAC,mBAAiB;AAiBlB,IAAM,SAA2C,CAAC,UAAU;AACjE,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,WAAW,KAAK,KAAK;AAC7B,SAAO;AACT;;;ACbO,SAAS,kBAAkB;AAChC,QAAM,UAAU,eAAe;AAC/B,QAAM,eAAe,QAAQ,gBAAgB,CAAC;AAC9C,QAAM,IAAI,CAAC,KAAa,WAA6C;AACnE,UAAM,MAAM,gBAAgB,GAAG;AAC/B,QAAI,cAAc,aAAa,GAAG,KAAK,OAAO;AAC9C,QAAI,QAAQ;AACV,iBAAW,SAAS,OAAO,KAAK,MAAM,GAAG;AACvC,cAAM,MAAM,OAAO,OAAO,KAAK,KAAK,EAAE;AACtC,sBAAc,YAAY,WAAW,IAAI,KAAK,KAAK,GAAG;AAAA,MACxD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,gBAAgB,KAAa;AAC3C,QAAM,QAAQ,OAAO,GAAG,EACrB,KAAK,EACL,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC;AAC/B,SAAO,MAAM,KAAK,IAAI;AACxB;","names":["useContext","useContext","useContext","useContext"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/config.ts","../src/core/components/Body.tsx","../src/core/components/Head.ts","../src/core/components/Script.ts","../src/core/hooks/useTranslations.ts"],"sourcesContent":["import {UserConfig as ViteUserConfig} from 'vite';\n\nimport {HtmlMinifyOptions} from '../render/html-minify';\nimport {HtmlPrettyOptions} from '../render/html-pretty';\n\nimport {Plugin} from './plugin';\nimport {RequestMiddleware} from './types';\n\nexport interface RootUserConfig {\n /**\n * Canonical domain the website will serve on. Useful for things like the\n * sitemap, SEO tags, etc.\n */\n domain?: string;\n\n /**\n * The base URL path that the site will serve on. Defaults to `/`;\n */\n base?: string;\n\n /**\n * Config for auto-injecting custom element dependencies.\n */\n elements?: {\n /**\n * A list of directories to use to look for custom elements. The dir path\n * should be relative to the project dir, e.g. \"path/to/elements\".\n */\n include?: string[];\n\n /**\n * A list of RegEx patterns to exclude. The string passed to the RegEx is\n * the file URL relative to the project root, e.g. \"/elements/foo/foo.ts\".\n */\n exclude?: RegExp[];\n };\n\n /**\n * Config options for localization and internationalization.\n */\n i18n?: RootI18nConfig;\n\n /**\n * Config options for the Root.js express server.\n */\n server?: RootServerConfig;\n\n /**\n * Vite config.\n * @see {@link https://vitejs.dev/config/} for more information.\n */\n vite?: ViteUserConfig;\n\n /**\n * Whether to automatically minify HTML output. This is enabled by default,\n * in order to disable, pass `minifyHtml: false` to root.config.ts.\n */\n minifyHtml?: boolean;\n\n /**\n * Options to pass to html-minifier-terser.\n */\n minifyHtmlOptions?: HtmlMinifyOptions;\n\n /**\n * Whether to pretty print HTML output.\n */\n prettyHtml?: boolean;\n\n /**\n * Options to pass to js-beautify.\n */\n prettyHtmlOptions?: HtmlPrettyOptions;\n\n /**\n * Whether to include a sitemap.xml file to the build output.\n */\n sitemap?: boolean;\n\n /**\n * Plugins.\n */\n plugins?: Plugin[];\n}\n\nexport type RootConfig = RootUserConfig & {\n rootDir: string;\n};\n\nexport interface LocaleGroup {\n label?: string;\n locales: string[];\n}\n\nexport interface RootI18nConfig {\n /**\n * Locales enabled for the site.\n */\n locales?: string[];\n\n /**\n * The default locale to use. Defaults is `en`.\n */\n defaultLocale?: string;\n\n /**\n * URL format for localized content. Default is `/[locale]/[base]/[path]`.\n */\n urlFormat?: string;\n\n /**\n * Localization groups, to help UIs (like Root.js CMS) logically group\n * locales.\n */\n groups?: Record<string, LocaleGroup>;\n}\n\nexport interface RootRedirectConfig {\n source: string;\n destination: string;\n type?: number;\n}\n\nexport interface RootHeaderConfig {\n /** A glob pattern match (regex not supported yet). */\n source: string;\n headers: Array<{\n key: string;\n value: string;\n }>;\n}\n\nexport interface ContentSecurityPolicyConfig {\n directives?: Record<string, string[]>;\n reportOnly?: boolean;\n}\n\nexport interface XFrameOptionsConfig {\n action: 'DENY' | 'SAMEORIGIN';\n}\n\nexport interface RootSecurityConfig {\n /**\n * Content-Security-Policy config. If enabled, a nonce is auto-generated\n * for every request and appended to script and stylesheet tags. You can\n * validate your CSP headers using a tool like {@link https://csp-evaluator.withgoogle.com/}.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP}\n */\n contentSecurityPolicy?: ContentSecurityPolicyConfig | boolean;\n\n /**\n * Strict-Transport-Security config. When enabled, the header value is set\n * to `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload`.\n */\n strictTransportSecurity?: boolean;\n\n /**\n * X-Content-Type-Options config. When enabled, the header value is set to\n * `X-Content-Type-Options: nosniff`.\n */\n xContentTypeOptions?: boolean;\n\n /**\n * X-Frame-Options config. Setting this value to `true` will default the\n * header value to `X-Frame-Options: SAMEORIGIN`.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options}\n */\n xFrameOptions?: 'DENY' | 'SAMEORIGIN' | boolean;\n\n /**\n * X-XSS-Protection config. When enabled, the header value is set to\n * `X-XSS-Protection: 1; mode=block`.\n */\n xXssProtection?: boolean;\n}\n\nexport interface RootServerConfig {\n /**\n * An array of middleware to add to the express server. These middleware are\n * added to the beginning of the express app.\n */\n middlewares?: RequestMiddleware[];\n\n /**\n * The `trailingSlash` config allows you to control how the server handles\n * trailing slashes. This config only affects URLs that do not have a file\n * extension (i.e. HTML paths).\n *\n * - When `true`, the server redirects URLs to add a trailing slash\n * - When `false`, the server redirects URLs to remove a trailing slash\n * - When unspecified, the server allows URLs with and without trailing slash\n */\n trailingSlash?: boolean;\n\n /**\n * Cookie secret for the session middleware.\n */\n sessionCookieSecret?: string | string[];\n\n /**\n * List of redirects.\n */\n redirects?: RootRedirectConfig[];\n\n /**\n * HTTP headers to add to a response.\n */\n headers?: RootHeaderConfig[];\n\n /**\n * HTTP security settings. By default, all security settings are enabled with\n * commonly used default values.\n */\n security?: RootSecurityConfig;\n}\n\nexport function defineConfig(config: RootUserConfig): RootUserConfig {\n return config;\n}\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type BodyProps = preact.JSX.HTMLAttributes<HTMLBodyElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The `<Body>` component can be used to update attrs in the `<body>` tag.\n *\n * Usage:\n *\n * ```tsx\n * <Body className=\"body\">\n * <h1>Hello world</h1>\n * </Body>\n * ```\n *\n * Output:\n *\n * ```html\n * <body class=\"body\">\n * <h1>Hello world</h1>\n * </body>\n */\nexport const Body: FunctionalComponent<BodyProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Body> component'\n );\n }\n context.bodyAttrs = attrs;\n return <>{children}</>;\n};\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type HeadProps = preact.JSX.HTMLAttributes<HTMLHeadElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The <Head> component can be used for injecting elements into the HTML head\n * tag from any part of a page. The <Head> can be added via any component or\n * sub-component and will automatically be hoisted to the `<head>` element.\n *\n * Usage:\n *\n * ```tsx\n * <Head>\n * <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n * </Head>\n * ```\n */\nexport const Head: FunctionalComponent<HeadProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Head> component'\n );\n }\n context.headComponents.push(children);\n context.headAttrs = attrs;\n return null;\n};\n","import {FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type ScriptProps = preact.JSX.HTMLAttributes<HTMLScriptElement>;\n\n/**\n * The <Script> component is used for rendering any custom script modules. At\n * the moment, the system only pre-renders and bundles files that are in the\n * `/bundles` folder at the root of the project.\n *\n * Usage:\n *\n * ```tsx\n * <Script src=\"/bundles/main.ts\" />\n * ```\n */\nexport const Script: FunctionalComponent<ScriptProps> = (props) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Script> component'\n );\n }\n context.scriptDeps.push(props);\n return null;\n};\n","import {useI18nContext} from './useI18nContext';\n\n/**\n * A hook that returns a function that can be used to translate a string, and\n * optionally replace any parameterized values that are surrounded in curly\n * braces.\n *\n * Usage:\n *\n * ```ts\n * const t = useTranslations();\n * t('Hello {name}', {name: 'Bob'});\n * // => 'Bounjour Bob'\n */\nexport function useTranslations() {\n const context = useI18nContext();\n const translations = context.translations || {};\n const t = (str: string, params?: Record<string, string | number>) => {\n const key = normalizeString(str);\n let translation = translations[key] ?? key ?? '';\n if (params) {\n for (const param of Object.keys(params)) {\n const val = String(params[param] ?? '');\n translation = translation.replaceAll(`{${param}}`, val);\n }\n }\n return translation;\n };\n return t;\n}\n\n/**\n * Cleans a string that's used for translations. Performs the following:\n * - Removes any leading/trailing whitespace\n * - Removes spaces at the end of any line\n */\nexport function normalizeString(str: string) {\n const lines = String(str)\n .trim()\n .split('\\n')\n .map((line) => line.trimEnd());\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;;AA0NO,SAAS,aAAa,QAAwC;AACnE,SAAO;AACT;;;AC3NA,SAAQ,kBAAiB;AAkChB;AARF,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAU,WAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,YAAY;AACpB,SAAO,gCAAG,UAAS;AACrB;;;ACnCA,SAAQ,cAAAA,mBAAiB;AAqBlB,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,eAAe,KAAK,QAAQ;AACpC,UAAQ,YAAY;AACpB,SAAO;AACT;;;AC/BA,SAAQ,cAAAC,mBAAiB;AAiBlB,IAAM,SAA2C,CAAC,UAAU;AACjE,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,WAAW,KAAK,KAAK;AAC7B,SAAO;AACT;;;ACbO,SAAS,kBAAkB;AAChC,QAAM,UAAU,eAAe;AAC/B,QAAM,eAAe,QAAQ,gBAAgB,CAAC;AAC9C,QAAM,IAAI,CAAC,KAAa,WAA6C;AACnE,UAAM,MAAM,gBAAgB,GAAG;AAC/B,QAAI,cAAc,aAAa,GAAG,KAAK,OAAO;AAC9C,QAAI,QAAQ;AACV,iBAAW,SAAS,OAAO,KAAK,MAAM,GAAG;AACvC,cAAM,MAAM,OAAO,OAAO,KAAK,KAAK,EAAE;AACtC,sBAAc,YAAY,WAAW,IAAI,KAAK,KAAK,GAAG;AAAA,MACxD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,gBAAgB,KAAa;AAC3C,QAAM,QAAQ,OAAO,GAAG,EACrB,KAAK,EACL,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC;AAC/B,SAAO,MAAM,KAAK,IAAI;AACxB;","names":["useContext","useContext","useContext","useContext"]}
|
package/dist/functions.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createPreviewServer,
|
|
3
3
|
createProdServer
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-VDORN4UU.js";
|
|
5
|
+
import "./chunk-TZAHHHA4.js";
|
|
6
|
+
import "./chunk-L2UBQKLP.js";
|
|
7
7
|
import "./chunk-QKBMWK5B.js";
|
|
8
8
|
import "./chunk-IUQLRDFW.js";
|
|
9
9
|
|
|
@@ -12,10 +12,10 @@ import path from "node:path";
|
|
|
12
12
|
import { onRequest } from "firebase-functions/v2/https";
|
|
13
13
|
function server(options) {
|
|
14
14
|
let rootServer;
|
|
15
|
-
const rootDir = path.resolve(
|
|
16
|
-
return onRequest(
|
|
15
|
+
const rootDir = path.resolve(options?.rootDir || process.cwd());
|
|
16
|
+
return onRequest(options?.httpsOptions || {}, async (req, res) => {
|
|
17
17
|
if (!rootServer) {
|
|
18
|
-
if (
|
|
18
|
+
if (options?.mode === "preview") {
|
|
19
19
|
rootServer = await createPreviewServer({ rootDir });
|
|
20
20
|
} else {
|
|
21
21
|
rootServer = await createProdServer({ rootDir });
|
package/dist/functions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/functions/server.ts"],"sourcesContent":["import path from 'node:path';\nimport {HttpsOptions, onRequest} from 'firebase-functions/v2/https';\nimport {createPreviewServer, createProdServer} from '../cli/cli';\nimport {Server} from '../core/types';\n\nexport interface ProdServerOptions {\n rootDir?: string;\n mode?: 'preview' | 'production';\n httpsOptions?: HttpsOptions;\n}\n\n/**\n * Firebase Function that runs a Root.js server running in SSR mode.\n */\nexport function server(options?: ProdServerOptions) {\n let rootServer: Server;\n const rootDir = path.resolve(options?.rootDir || process.cwd());\n return onRequest(options?.httpsOptions || {}, async (req, res) => {\n if (!rootServer) {\n if (options?.mode === 'preview') {\n rootServer = await createPreviewServer({rootDir});\n } else {\n rootServer = await createProdServer({rootDir});\n }\n }\n await rootServer(req, res);\n });\n}\n"],"mappings":";;;;;;;;;;AAAA,OAAO,UAAU;AACjB,SAAsB,iBAAgB;AAa/B,SAAS,OAAO,SAA6B;AAClD,MAAI;AACJ,QAAM,UAAU,KAAK,
|
|
1
|
+
{"version":3,"sources":["../src/functions/server.ts"],"sourcesContent":["import path from 'node:path';\nimport {HttpsOptions, onRequest} from 'firebase-functions/v2/https';\nimport {createPreviewServer, createProdServer} from '../cli/cli';\nimport {Server} from '../core/types';\n\nexport interface ProdServerOptions {\n rootDir?: string;\n mode?: 'preview' | 'production';\n httpsOptions?: HttpsOptions;\n}\n\n/**\n * Firebase Function that runs a Root.js server running in SSR mode.\n */\nexport function server(options?: ProdServerOptions) {\n let rootServer: Server;\n const rootDir = path.resolve(options?.rootDir || process.cwd());\n return onRequest(options?.httpsOptions || {}, async (req, res) => {\n if (!rootServer) {\n if (options?.mode === 'preview') {\n rootServer = await createPreviewServer({rootDir});\n } else {\n rootServer = await createProdServer({rootDir});\n }\n }\n await rootServer(req, res);\n });\n}\n"],"mappings":";;;;;;;;;;AAAA,OAAO,UAAU;AACjB,SAAsB,iBAAgB;AAa/B,SAAS,OAAO,SAA6B;AAClD,MAAI;AACJ,QAAM,UAAU,KAAK,QAAQ,SAAS,WAAW,QAAQ,IAAI,CAAC;AAC9D,SAAO,UAAU,SAAS,gBAAgB,CAAC,GAAG,OAAO,KAAK,QAAQ;AAChE,QAAI,CAAC,YAAY;AACf,UAAI,SAAS,SAAS,WAAW;AAC/B,qBAAa,MAAM,oBAAoB,EAAC,QAAO,CAAC;AAAA,MAClD,OAAO;AACL,qBAAa,MAAM,iBAAiB,EAAC,QAAO,CAAC;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,WAAW,KAAK,GAAG;AAAA,EAC3B,CAAC;AACH;","names":[]}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as RootConfig,
|
|
2
|
-
export {
|
|
1
|
+
import { b as RootConfig, p as Request, q as Response, N as NextFunction } from './types-403nR8i5.js';
|
|
2
|
+
export { v as SESSION_COOKIE, x as SaveSessionOptions, z as Session, w as SessionMiddlewareOptions, y as sessionMiddleware } from './types-403nR8i5.js';
|
|
3
3
|
import { RequestHandler } from 'express';
|
|
4
4
|
import 'preact';
|
|
5
5
|
import 'vite';
|
package/dist/middleware.js
CHANGED
package/dist/node.d.ts
CHANGED
package/dist/node.js
CHANGED
package/dist/render.d.ts
CHANGED
package/dist/render.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
I18N_CONTEXT,
|
|
4
4
|
REQUEST_CONTEXT,
|
|
5
5
|
getTranslations
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7IDJ3PBT.js";
|
|
7
7
|
import {
|
|
8
8
|
RouteTrie,
|
|
9
9
|
htmlMinify,
|
|
@@ -12,6 +12,10 @@ import {
|
|
|
12
12
|
} from "./chunk-IUQLRDFW.js";
|
|
13
13
|
|
|
14
14
|
// src/render/render.tsx
|
|
15
|
+
import crypto from "node:crypto";
|
|
16
|
+
import {
|
|
17
|
+
options as preactOptions
|
|
18
|
+
} from "preact";
|
|
15
19
|
import renderToString from "preact-render-to-string";
|
|
16
20
|
|
|
17
21
|
// src/core/pages/ErrorPage.tsx
|
|
@@ -92,7 +96,6 @@ function ErrorPage(props) {
|
|
|
92
96
|
// src/core/pages/DevErrorPage.tsx
|
|
93
97
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
94
98
|
function DevErrorPage(props) {
|
|
95
|
-
var _a;
|
|
96
99
|
const req = props.req;
|
|
97
100
|
const err = props.error;
|
|
98
101
|
const route = props.route;
|
|
@@ -100,7 +103,7 @@ function DevErrorPage(props) {
|
|
|
100
103
|
let errMsg = String(err);
|
|
101
104
|
if (err && err.stack) {
|
|
102
105
|
errMsg = err.stack.replace(/\(.*node_modules/g, "(node_modules").replace(/at \/.*node_modules/g, "at node_modules");
|
|
103
|
-
if (
|
|
106
|
+
if (req.rootConfig?.rootDir) {
|
|
104
107
|
errMsg = errMsg.replaceAll(req.rootConfig.rootDir, "<root>");
|
|
105
108
|
}
|
|
106
109
|
if (process.env.HOME) {
|
|
@@ -114,7 +117,7 @@ function DevErrorPage(props) {
|
|
|
114
117
|
] }),
|
|
115
118
|
/* @__PURE__ */ jsx2("h2", { children: "Debug Info" }),
|
|
116
119
|
/* @__PURE__ */ jsx2("pre", { className: "box", children: /* @__PURE__ */ jsx2("code", { children: `url: ${req.originalUrl}
|
|
117
|
-
route: ${
|
|
120
|
+
route: ${route?.src || "null"}
|
|
118
121
|
routeParams: ${routeParams && JSON.stringify(routeParams) || "null"}` }) })
|
|
119
122
|
] });
|
|
120
123
|
}
|
|
@@ -245,11 +248,10 @@ var ES_419_COUNTRIES = [
|
|
|
245
248
|
// Venezuela
|
|
246
249
|
];
|
|
247
250
|
function getFallbackLocales(req) {
|
|
248
|
-
var _a, _b;
|
|
249
251
|
const hl = getFirstQueryParam(req, "hl");
|
|
250
252
|
const countryCode = getCountry(req);
|
|
251
253
|
if (isWebCrawler(req)) {
|
|
252
|
-
const defaultLocale =
|
|
254
|
+
const defaultLocale = req.rootConfig?.i18n?.defaultLocale || "en";
|
|
253
255
|
if (hl && hl !== defaultLocale) {
|
|
254
256
|
return [hl, defaultLocale];
|
|
255
257
|
}
|
|
@@ -350,13 +352,11 @@ var Router = class {
|
|
|
350
352
|
await this.routeTrie.walk(cb);
|
|
351
353
|
}
|
|
352
354
|
initRouteTrie() {
|
|
353
|
-
|
|
354
|
-
const locales = ((_a = this.rootConfig.i18n) == null ? void 0 : _a.locales) || [];
|
|
355
|
+
const locales = this.rootConfig.i18n?.locales || [];
|
|
355
356
|
const basePath = this.rootConfig.base || "/";
|
|
356
|
-
const defaultLocale =
|
|
357
|
+
const defaultLocale = this.rootConfig.i18n?.defaultLocale || "en";
|
|
357
358
|
const trie = new RouteTrie();
|
|
358
359
|
Object.keys(ROUTES_FILES).forEach((modulePath) => {
|
|
359
|
-
var _a2;
|
|
360
360
|
const src = modulePath.slice(1);
|
|
361
361
|
let relativeRoutePath = modulePath.replace(/^\/routes/, "");
|
|
362
362
|
const parts = path.parse(relativeRoutePath);
|
|
@@ -370,17 +370,16 @@ var Router = class {
|
|
|
370
370
|
}
|
|
371
371
|
const urlFormat = "/[base]/[path]";
|
|
372
372
|
const i18nUrlFormat = toSquareBrackets(
|
|
373
|
-
|
|
373
|
+
this.rootConfig.i18n?.urlFormat || "/[locale]/[base]/[path]"
|
|
374
374
|
);
|
|
375
375
|
const placeholders = {
|
|
376
376
|
base: removeSlashes(basePath),
|
|
377
377
|
path: removeSlashes(relativeRoutePath)
|
|
378
378
|
};
|
|
379
379
|
const formatUrl = (format) => {
|
|
380
|
-
var _a3;
|
|
381
380
|
const url = format.replaceAll("[base]", placeholders.base).replaceAll("[path]", placeholders.path);
|
|
382
381
|
return normalizeUrlPath(url, {
|
|
383
|
-
trailingSlash:
|
|
382
|
+
trailingSlash: this.rootConfig.server?.trailingSlash
|
|
384
383
|
});
|
|
385
384
|
};
|
|
386
385
|
const routePath = formatUrl(urlFormat);
|
|
@@ -472,7 +471,7 @@ function replaceParams(urlPathFormat, params) {
|
|
|
472
471
|
}
|
|
473
472
|
function normalizeUrlPath(urlPath, options) {
|
|
474
473
|
urlPath = urlPath.replace(/\/+/g, "/");
|
|
475
|
-
if (
|
|
474
|
+
if (options?.trailingSlash === false && urlPath !== "/" && urlPath.endsWith("/")) {
|
|
476
475
|
urlPath = urlPath.replace(/\/*$/g, "");
|
|
477
476
|
}
|
|
478
477
|
if (urlPath.endsWith("/index")) {
|
|
@@ -522,14 +521,13 @@ var Renderer = class {
|
|
|
522
521
|
}
|
|
523
522
|
const fallbackLocales = route.isDefaultLocale ? getFallbackLocales(req) : [route.locale];
|
|
524
523
|
const getPreferredLocale = (availableLocales) => {
|
|
525
|
-
var _a, _b;
|
|
526
524
|
const lowerLocales = availableLocales.map((l) => l.toLowerCase());
|
|
527
525
|
for (const fallbackLocale of fallbackLocales) {
|
|
528
526
|
if (lowerLocales.includes(fallbackLocale.toLowerCase())) {
|
|
529
527
|
return fallbackLocale;
|
|
530
528
|
}
|
|
531
529
|
}
|
|
532
|
-
return
|
|
530
|
+
return req.rootConfig?.i18n?.defaultLocale || "en";
|
|
533
531
|
};
|
|
534
532
|
const render404 = async () => {
|
|
535
533
|
next();
|
|
@@ -540,15 +538,19 @@ var Renderer = class {
|
|
|
540
538
|
render404();
|
|
541
539
|
return;
|
|
542
540
|
}
|
|
541
|
+
const securityConfig = this.getSecurityConfig();
|
|
542
|
+
const cspEnabled = !!securityConfig.contentSecurityPolicy;
|
|
543
543
|
const currentPath = req.path;
|
|
544
|
-
const locale =
|
|
545
|
-
const translations = options
|
|
544
|
+
const locale = options?.locale || route.locale;
|
|
545
|
+
const translations = options?.translations;
|
|
546
|
+
const nonce = cspEnabled ? this.generateNonce() : void 0;
|
|
546
547
|
const output = await this.renderComponent(route.module.default, props2, {
|
|
547
548
|
currentPath,
|
|
548
549
|
route,
|
|
549
550
|
routeParams,
|
|
550
551
|
locale,
|
|
551
|
-
translations
|
|
552
|
+
translations,
|
|
553
|
+
nonce
|
|
552
554
|
});
|
|
553
555
|
let html = output.html;
|
|
554
556
|
if (this.rootConfig.prettyHtml) {
|
|
@@ -566,7 +568,13 @@ var Renderer = class {
|
|
|
566
568
|
statusCode = 500;
|
|
567
569
|
}
|
|
568
570
|
req.hooks.trigger("preRender");
|
|
569
|
-
res.status(statusCode)
|
|
571
|
+
res.status(statusCode);
|
|
572
|
+
res.set({ "Content-Type": "text/html" });
|
|
573
|
+
this.setSecurityHeaders(res, {
|
|
574
|
+
securityConfig,
|
|
575
|
+
nonce
|
|
576
|
+
});
|
|
577
|
+
res.end(html);
|
|
570
578
|
};
|
|
571
579
|
if (route.module.handle) {
|
|
572
580
|
const handlerContext = {
|
|
@@ -596,7 +604,7 @@ var Renderer = class {
|
|
|
596
604
|
await render(props);
|
|
597
605
|
}
|
|
598
606
|
async renderComponent(Component, props, options) {
|
|
599
|
-
const { currentPath, route, routeParams } = options;
|
|
607
|
+
const { currentPath, route, routeParams, nonce } = options;
|
|
600
608
|
const locale = options.locale;
|
|
601
609
|
const translations = {
|
|
602
610
|
...getTranslations(locale),
|
|
@@ -608,7 +616,8 @@ var Renderer = class {
|
|
|
608
616
|
props,
|
|
609
617
|
routeParams,
|
|
610
618
|
locale,
|
|
611
|
-
translations
|
|
619
|
+
translations,
|
|
620
|
+
nonce
|
|
612
621
|
};
|
|
613
622
|
const htmlContext = {
|
|
614
623
|
htmlAttrs: {},
|
|
@@ -618,7 +627,26 @@ var Renderer = class {
|
|
|
618
627
|
scriptDeps: []
|
|
619
628
|
};
|
|
620
629
|
const vdom = /* @__PURE__ */ jsx4(REQUEST_CONTEXT.Provider, { value: ctx, children: /* @__PURE__ */ jsx4(I18N_CONTEXT.Provider, { value: { locale, translations }, children: /* @__PURE__ */ jsx4(HTML_CONTEXT.Provider, { value: htmlContext, children: /* @__PURE__ */ jsx4(Component, { ...props }) }) }) });
|
|
621
|
-
const
|
|
630
|
+
const preactHook = preactOptions.vnode;
|
|
631
|
+
let mainHtml;
|
|
632
|
+
try {
|
|
633
|
+
preactOptions.vnode = (vnode) => {
|
|
634
|
+
if (vnode && vnode.type === "script") {
|
|
635
|
+
vnode.props.nonce = nonce;
|
|
636
|
+
}
|
|
637
|
+
if (vnode && vnode.type === "link" && vnode.props.rel === "stylesheet") {
|
|
638
|
+
vnode.props.nonce = nonce;
|
|
639
|
+
}
|
|
640
|
+
if (preactHook) {
|
|
641
|
+
preactHook(vnode);
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
mainHtml = renderToString(vdom);
|
|
645
|
+
preactOptions.vnode = preactHook;
|
|
646
|
+
} catch (err) {
|
|
647
|
+
preactOptions.vnode = preactHook;
|
|
648
|
+
throw err;
|
|
649
|
+
}
|
|
622
650
|
const jsDeps = /* @__PURE__ */ new Set();
|
|
623
651
|
const cssDeps = /* @__PURE__ */ new Set();
|
|
624
652
|
const routeAsset = await this.assetMap.get(route.src);
|
|
@@ -647,10 +675,10 @@ var Renderer = class {
|
|
|
647
675
|
})
|
|
648
676
|
);
|
|
649
677
|
const styleTags = Array.from(cssDeps).map((cssUrl) => {
|
|
650
|
-
return /* @__PURE__ */ jsx4("link", { rel: "stylesheet", href: cssUrl });
|
|
678
|
+
return /* @__PURE__ */ jsx4("link", { rel: "stylesheet", href: cssUrl, nonce });
|
|
651
679
|
});
|
|
652
680
|
const scriptTags = Array.from(jsDeps).map((jsUrls) => {
|
|
653
|
-
return /* @__PURE__ */ jsx4("script", { type: "module", src: jsUrls });
|
|
681
|
+
return /* @__PURE__ */ jsx4("script", { type: "module", src: jsUrls, nonce });
|
|
654
682
|
});
|
|
655
683
|
const html = await this.renderHtml(mainHtml, {
|
|
656
684
|
htmlAttrs: htmlContext.htmlAttrs,
|
|
@@ -724,13 +752,13 @@ var Renderer = class {
|
|
|
724
752
|
return sitemap;
|
|
725
753
|
}
|
|
726
754
|
async renderHtml(html, options) {
|
|
727
|
-
const htmlAttrs =
|
|
728
|
-
const headAttrs =
|
|
729
|
-
const bodyAttrs =
|
|
755
|
+
const htmlAttrs = options?.htmlAttrs || {};
|
|
756
|
+
const headAttrs = options?.headAttrs || {};
|
|
757
|
+
const bodyAttrs = options?.bodyAttrs || {};
|
|
730
758
|
const page = /* @__PURE__ */ jsxs4("html", { ...htmlAttrs, children: [
|
|
731
759
|
/* @__PURE__ */ jsxs4("head", { ...headAttrs, children: [
|
|
732
760
|
/* @__PURE__ */ jsx4("meta", { charSet: "utf-8" }),
|
|
733
|
-
options
|
|
761
|
+
options?.headComponents
|
|
734
762
|
] }),
|
|
735
763
|
/* @__PURE__ */ jsx4("body", { ...bodyAttrs, dangerouslySetInnerHTML: { __html: html } })
|
|
736
764
|
] });
|
|
@@ -739,7 +767,7 @@ ${renderToString(page)}
|
|
|
739
767
|
`;
|
|
740
768
|
}
|
|
741
769
|
async render404(options) {
|
|
742
|
-
const currentPath =
|
|
770
|
+
const currentPath = options?.currentPath || "/404";
|
|
743
771
|
const [route, routeParams] = this.router.get("/404");
|
|
744
772
|
if (route && route.src === "routes/404.tsx" && route.module.default) {
|
|
745
773
|
const Component = route.module.default;
|
|
@@ -775,7 +803,7 @@ ${renderToString(page)}
|
|
|
775
803
|
return { html };
|
|
776
804
|
}
|
|
777
805
|
async renderError(err, options) {
|
|
778
|
-
const currentPath =
|
|
806
|
+
const currentPath = options?.currentPath || "/500";
|
|
779
807
|
const [route, routeParams] = this.router.get("/500");
|
|
780
808
|
if (route && route.src === "routes/500.tsx" && route.module.default) {
|
|
781
809
|
const Component = route.module.default;
|
|
@@ -874,7 +902,86 @@ ${renderToString(page)}
|
|
|
874
902
|
);
|
|
875
903
|
return { jsDeps, cssDeps };
|
|
876
904
|
}
|
|
905
|
+
/**
|
|
906
|
+
* Returns the `security` config value with default values inserted wherever
|
|
907
|
+
* a user config value is blank or set to `true`.
|
|
908
|
+
*/
|
|
909
|
+
getSecurityConfig() {
|
|
910
|
+
const userConfig = this.rootConfig.server?.security || {};
|
|
911
|
+
const securityConfig = {};
|
|
912
|
+
if (isTrueOrUndefined(userConfig.contentSecurityPolicy)) {
|
|
913
|
+
securityConfig.contentSecurityPolicy = {
|
|
914
|
+
directives: {
|
|
915
|
+
"base-uri": ["'none'"],
|
|
916
|
+
"object-src": ["'none'"],
|
|
917
|
+
"script-src": ["'self'"]
|
|
918
|
+
},
|
|
919
|
+
reportOnly: true
|
|
920
|
+
};
|
|
921
|
+
} else {
|
|
922
|
+
securityConfig.contentSecurityPolicy = userConfig.contentSecurityPolicy;
|
|
923
|
+
}
|
|
924
|
+
if (isTrueOrUndefined(userConfig.xFrameOptions)) {
|
|
925
|
+
securityConfig.xFrameOptions = "SAMEORIGIN";
|
|
926
|
+
} else {
|
|
927
|
+
securityConfig.xFrameOptions = userConfig.xFrameOptions;
|
|
928
|
+
}
|
|
929
|
+
securityConfig.strictTransportSecurity = userConfig.strictTransportSecurity ?? true;
|
|
930
|
+
securityConfig.xContentTypeOptions = userConfig.xContentTypeOptions ?? true;
|
|
931
|
+
securityConfig.xXssProtection = userConfig.xXssProtection ?? true;
|
|
932
|
+
return securityConfig;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Generates a random string that can be used as the "nonce" value for CSP.
|
|
936
|
+
*/
|
|
937
|
+
generateNonce() {
|
|
938
|
+
return crypto.randomBytes(16).toString("base64");
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Sets security-related HTTP headers.
|
|
942
|
+
*/
|
|
943
|
+
setSecurityHeaders(res, options) {
|
|
944
|
+
const securityConfig = options.securityConfig;
|
|
945
|
+
const contentSecurityPolicy = securityConfig.contentSecurityPolicy;
|
|
946
|
+
if (typeof contentSecurityPolicy === "object") {
|
|
947
|
+
const directives = contentSecurityPolicy.directives || {};
|
|
948
|
+
if (options.nonce) {
|
|
949
|
+
if (!directives["script-src"]) {
|
|
950
|
+
directives["script-src"] = ["'self'"];
|
|
951
|
+
}
|
|
952
|
+
directives["script-src"].push(`'nonce-${options.nonce}'`);
|
|
953
|
+
}
|
|
954
|
+
const headerSegments = [];
|
|
955
|
+
Object.entries(directives).forEach(([key, values]) => {
|
|
956
|
+
headerSegments.push([key, ...values].join(" "));
|
|
957
|
+
});
|
|
958
|
+
const csp = headerSegments.join("; ");
|
|
959
|
+
if (contentSecurityPolicy.reportOnly === false) {
|
|
960
|
+
res.setHeader("content-security-policy", csp);
|
|
961
|
+
} else {
|
|
962
|
+
res.setHeader("content-security-policy-report-only", csp);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (typeof securityConfig.xFrameOptions === "string") {
|
|
966
|
+
res.setHeader("x-frame-options", securityConfig.xFrameOptions);
|
|
967
|
+
}
|
|
968
|
+
if (securityConfig.strictTransportSecurity) {
|
|
969
|
+
res.setHeader(
|
|
970
|
+
"strict-transport-security",
|
|
971
|
+
"max-age=63072000; includeSubdomains; preload"
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
if (securityConfig.xContentTypeOptions) {
|
|
975
|
+
res.setHeader("x-content-type-options", "nosniff");
|
|
976
|
+
}
|
|
977
|
+
if (securityConfig.xXssProtection) {
|
|
978
|
+
res.setHeader("x-xss-protection", "1; mode=block");
|
|
979
|
+
}
|
|
980
|
+
}
|
|
877
981
|
};
|
|
982
|
+
function isTrueOrUndefined(value) {
|
|
983
|
+
return value === true || value === void 0;
|
|
984
|
+
}
|
|
878
985
|
export {
|
|
879
986
|
Renderer
|
|
880
987
|
};
|