@brandlab-digital/site-next 2.1.0 → 3.0.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/dist/analytics.d.ts +111 -52
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +281 -153
- package/dist/analytics.js.map +1 -1
- package/dist/blog.d.ts +4 -2
- package/dist/blog.d.ts.map +1 -1
- package/dist/blog.js +7 -7
- package/dist/blog.js.map +1 -1
- package/dist/contact/client-entry.d.ts +2 -0
- package/dist/contact/client-entry.d.ts.map +1 -1
- package/dist/contact/client-entry.js +1 -0
- package/dist/contact/client-entry.js.map +1 -1
- package/dist/contact/client.d.ts +12 -22
- package/dist/contact/client.d.ts.map +1 -1
- package/dist/contact/client.js +28 -21
- package/dist/contact/client.js.map +1 -1
- package/dist/contact/form-client.d.ts +148 -0
- package/dist/contact/form-client.d.ts.map +1 -0
- package/dist/contact/form-client.js +234 -0
- package/dist/contact/form-client.js.map +1 -0
- package/dist/contact/index.d.ts +1 -1
- package/dist/contact/index.d.ts.map +1 -1
- package/dist/contact/index.js +1 -1
- package/dist/contact/index.js.map +1 -1
- package/dist/contact/route-handler.d.ts +32 -0
- package/dist/contact/route-handler.d.ts.map +1 -0
- package/dist/contact/route-handler.js +44 -0
- package/dist/contact/route-handler.js.map +1 -0
- package/dist/contact/server-entry.d.ts +1 -0
- package/dist/contact/server-entry.d.ts.map +1 -1
- package/dist/contact/server-entry.js +1 -0
- package/dist/contact/server-entry.js.map +1 -1
- package/dist/contact/server.d.ts +5 -0
- package/dist/contact/server.d.ts.map +1 -1
- package/dist/contact/server.js +13 -7
- package/dist/contact/server.js.map +1 -1
- package/dist/contact/shared.d.ts +4 -0
- package/dist/contact/shared.d.ts.map +1 -1
- package/dist/contact/shared.js.map +1 -1
- package/dist/contact/testing.d.ts +29 -0
- package/dist/contact/testing.d.ts.map +1 -1
- package/dist/contact/testing.js +27 -0
- package/dist/contact/testing.js.map +1 -1
- package/dist/google-tag-plan.d.ts +1 -4
- package/dist/google-tag-plan.d.ts.map +1 -1
- package/dist/google-tag-plan.js +1 -14
- package/dist/google-tag-plan.js.map +1 -1
- package/dist/google-tag.d.ts +5 -4
- package/dist/google-tag.d.ts.map +1 -1
- package/dist/google-tag.js +5 -4
- package/dist/google-tag.js.map +1 -1
- package/dist/headers.d.ts +21 -28
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +62 -51
- package/dist/headers.js.map +1 -1
- package/dist/instrumentation-client.d.ts +21 -1
- package/dist/instrumentation-client.d.ts.map +1 -1
- package/dist/instrumentation-client.js +62 -7
- package/dist/instrumentation-client.js.map +1 -1
- package/dist/lead-registry.d.ts +34 -0
- package/dist/lead-registry.d.ts.map +1 -0
- package/dist/lead-registry.js +30 -0
- package/dist/lead-registry.js.map +1 -0
- package/dist/lead-reporting.d.ts +37 -0
- package/dist/lead-reporting.d.ts.map +1 -0
- package/dist/lead-reporting.js +74 -0
- package/dist/lead-reporting.js.map +1 -0
- package/dist/metadata.d.ts +10 -11
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +17 -19
- package/dist/metadata.js.map +1 -1
- package/dist/next-config.d.ts +102 -0
- package/dist/next-config.d.ts.map +1 -0
- package/dist/next-config.js +140 -0
- package/dist/next-config.js.map +1 -0
- package/dist/robots.d.ts +14 -6
- package/dist/robots.d.ts.map +1 -1
- package/dist/robots.js +26 -17
- package/dist/robots.js.map +1 -1
- package/dist/sitemaps.d.ts +34 -0
- package/dist/sitemaps.d.ts.map +1 -1
- package/dist/sitemaps.js +66 -1
- package/dist/sitemaps.js.map +1 -1
- package/dist/testing/server-only.d.ts +8 -0
- package/dist/testing/server-only.d.ts.map +1 -0
- package/dist/testing/server-only.js +8 -0
- package/dist/testing/server-only.js.map +1 -0
- package/dist/ui-client.d.ts +1 -12
- package/dist/ui-client.d.ts.map +1 -1
- package/dist/ui-client.js +0 -17
- package/dist/ui-client.js.map +1 -1
- package/dist/ui.d.ts +22 -2
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +67 -6
- package/dist/ui.js.map +1 -1
- package/package.json +16 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/** Supplied by `defineBrandlabAnalytics(...).googleTag`. */
|
|
2
2
|
export interface BrandlabGoogleTagConfig {
|
|
3
3
|
enabled: boolean;
|
|
4
4
|
measurementId?: string | null;
|
|
@@ -12,8 +12,5 @@ export interface BrandlabGoogleTagPlan {
|
|
|
12
12
|
destinations: readonly string[];
|
|
13
13
|
websiteCallDestination: string | null;
|
|
14
14
|
}
|
|
15
|
-
export declare function resolveBrandlabGoogleTagConfig(contract: BrandlabAnalyticsContract, environment: Readonly<Record<string, string | undefined>>, options: {
|
|
16
|
-
enabled: boolean;
|
|
17
|
-
}): BrandlabGoogleTagConfig;
|
|
18
15
|
export declare function createBrandlabGoogleTagPlan(config: BrandlabGoogleTagConfig, phoneNumber?: string | null): BrandlabGoogleTagPlan | null;
|
|
19
16
|
//# sourceMappingURL=google-tag-plan.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-tag-plan.d.ts","sourceRoot":"","sources":["../src/google-tag-plan.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"google-tag-plan.d.ts","sourceRoot":"","sources":["../src/google-tag-plan.ts"],"names":[],"mappings":"AAEA,4DAA4D;AAC5D,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,mCAAmC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACpD;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAA;CACtC;AAKD,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,uBAAuB,EAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAC1B,qBAAqB,GAAG,IAAI,CA2C9B"}
|
package/dist/google-tag-plan.js
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getGoogleAdsWebsiteCallSendTo } from '@brandlab-digital/site-core/analytics';
|
|
2
2
|
const GA4_MEASUREMENT_ID_PATTERN = /^G-[A-Z0-9]{4,20}$/i;
|
|
3
3
|
const GOOGLE_ADS_CONVERSION_ID_PATTERN = /^(?:AW-)?\d{6,20}$/;
|
|
4
|
-
export function resolveBrandlabGoogleTagConfig(contract, environment, options) {
|
|
5
|
-
defineAnalyticsContract(contract);
|
|
6
|
-
return {
|
|
7
|
-
enabled: options.enabled,
|
|
8
|
-
measurementId: environment[contract.measurementIdEnv],
|
|
9
|
-
googleAdsConversionId: contract.googleAds
|
|
10
|
-
? environment[contract.googleAds.conversionIdEnv]
|
|
11
|
-
: undefined,
|
|
12
|
-
googleAdsWebsiteCallConversionLabel: contract.googleAds?.websiteCalls
|
|
13
|
-
? environment[contract.googleAds.websiteCalls.conversionLabelEnv]
|
|
14
|
-
: undefined,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
4
|
export function createBrandlabGoogleTagPlan(config, phoneNumber) {
|
|
18
5
|
if (!config.enabled)
|
|
19
6
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-tag-plan.js","sourceRoot":"","sources":["../src/google-tag-plan.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"google-tag-plan.js","sourceRoot":"","sources":["../src/google-tag-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { getGoogleAdsWebsiteCallSendTo } from '@brandlab-digital/site-core/analytics'\n\n/** Supplied by `defineBrandlabAnalytics(...).googleTag`. */\nexport interface BrandlabGoogleTagConfig {\n enabled: boolean\n measurementId?: string | null\n googleAdsConversionId?: string | null\n googleAdsWebsiteCallConversionLabel?: string | null\n}\n\nexport interface BrandlabGoogleTagPlan {\n loaderId: string\n source: string\n inlineScript: string\n destinations: readonly string[]\n websiteCallDestination: string | null\n}\n\nconst GA4_MEASUREMENT_ID_PATTERN = /^G-[A-Z0-9]{4,20}$/i\nconst GOOGLE_ADS_CONVERSION_ID_PATTERN = /^(?:AW-)?\\d{6,20}$/\n\nexport function createBrandlabGoogleTagPlan(\n config: BrandlabGoogleTagConfig,\n phoneNumber?: string | null,\n): BrandlabGoogleTagPlan | null {\n if (!config.enabled) return null\n const measurementId = validMeasurementId(config.measurementId)\n const googleAdsConversionId = validGoogleAdsConversionId(config.googleAdsConversionId)\n const loaderId = measurementId ?? googleAdsConversionId\n if (!loaderId) return null\n\n const destinations = [...new Set([\n ...(measurementId ? [measurementId] : []),\n ...(googleAdsConversionId ? [googleAdsConversionId] : []),\n ])]\n const displayPhone = safeDisplayedBusinessPhone(phoneNumber)\n const websiteCallDestination = displayPhone\n ? getGoogleAdsWebsiteCallSendTo({\n conversionId: googleAdsConversionId,\n conversionLabel: config.googleAdsWebsiteCallConversionLabel,\n }) ?? null\n : null\n const configCommands = destinations.map(\n (destination) => `window.gtag('config', ${safeJson(destination)});`,\n )\n if (websiteCallDestination && displayPhone) {\n configCommands.push(\n `window.gtag('config', ${safeJson(websiteCallDestination)}, ${safeJson({\n phone_conversion_number: displayPhone,\n })});`,\n )\n }\n\n return {\n loaderId,\n source: `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(loaderId)}`,\n inlineScript: [\n 'window.dataLayer = window.dataLayer || [];',\n 'window.gtag = window.gtag || function(){window.dataLayer.push(arguments);};',\n \"window.gtag('consent', 'default', {ad_user_data:'denied',ad_personalization:'denied'});\",\n \"window.gtag('set', 'user_data', {});\",\n \"window.gtag('js', new Date());\",\n ...configCommands,\n ].join('\\n'),\n destinations,\n websiteCallDestination,\n }\n}\n\nfunction validMeasurementId(value: string | null | undefined) {\n const normalized = value?.trim()\n return normalized && GA4_MEASUREMENT_ID_PATTERN.test(normalized)\n ? normalized.toUpperCase()\n : undefined\n}\n\nfunction validGoogleAdsConversionId(value: string | null | undefined) {\n const normalized = value?.trim()\n if (!normalized || !GOOGLE_ADS_CONVERSION_ID_PATTERN.test(normalized)) return undefined\n return normalized.startsWith('AW-') ? normalized : `AW-${normalized}`\n}\n\nfunction safeDisplayedBusinessPhone(value: string | null | undefined) {\n const normalized = value?.trim()\n const digits = normalized?.replace(/\\D/g, '') ?? ''\n return normalized &&\n normalized.length <= 32 &&\n digits.length >= 6 &&\n /^[0-9+().\\s-]+$/.test(normalized)\n ? normalized\n : undefined\n}\n\nfunction safeJson(value: unknown) {\n return JSON.stringify(value)\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029')\n}\n"]}
|
package/dist/google-tag.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type BrandlabGoogleTagConfig } from './google-tag-plan.js';
|
|
2
|
-
export { createBrandlabGoogleTagPlan
|
|
2
|
+
export { createBrandlabGoogleTagPlan } from './google-tag-plan.js';
|
|
3
3
|
export type { BrandlabGoogleTagConfig, BrandlabGoogleTagPlan, } from './google-tag-plan.js';
|
|
4
4
|
export interface BrandlabGoogleTagProps extends BrandlabGoogleTagConfig {
|
|
5
5
|
/** Must match the verified public number exactly as it is rendered on the page. */
|
|
@@ -9,9 +9,10 @@ export interface BrandlabGoogleTagProps extends BrandlabGoogleTagConfig {
|
|
|
9
9
|
/**
|
|
10
10
|
* Shared Google tag and website-call forwarding-number snippet.
|
|
11
11
|
*
|
|
12
|
-
* Render this exactly once from the root layout
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* Render this exactly once from the root layout:
|
|
13
|
+
* `<BrandlabGoogleTag {...analytics.googleTag} phoneNumber={displayedPhone} />`.
|
|
14
|
+
* `analytics.googleTag.enabled` is true only on the production deployment;
|
|
15
|
+
* account-side conversion settings remain outside website runtime.
|
|
15
16
|
*/
|
|
16
17
|
export declare function BrandlabGoogleTag(props: BrandlabGoogleTagProps): import("react/jsx-runtime").JSX.Element | null;
|
|
17
18
|
//# sourceMappingURL=google-tag.d.ts.map
|
package/dist/google-tag.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-tag.d.ts","sourceRoot":"","sources":["../src/google-tag.tsx"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAA;AAE7B,OAAO,
|
|
1
|
+
{"version":3,"file":"google-tag.d.ts","sourceRoot":"","sources":["../src/google-tag.tsx"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAA;AAClE,YAAY,EACV,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,sBAAsB,CAAA;AAE7B,MAAM,WAAW,sBAAuB,SAAQ,uBAAuB;IACrE,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,kDAqB9D"}
|
package/dist/google-tag.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import Script from 'next/script';
|
|
3
3
|
import { createBrandlabGoogleTagPlan, } from './google-tag-plan.js';
|
|
4
|
-
export { createBrandlabGoogleTagPlan
|
|
4
|
+
export { createBrandlabGoogleTagPlan } from './google-tag-plan.js';
|
|
5
5
|
/**
|
|
6
6
|
* Shared Google tag and website-call forwarding-number snippet.
|
|
7
7
|
*
|
|
8
|
-
* Render this exactly once from the root layout
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Render this exactly once from the root layout:
|
|
9
|
+
* `<BrandlabGoogleTag {...analytics.googleTag} phoneNumber={displayedPhone} />`.
|
|
10
|
+
* `analytics.googleTag.enabled` is true only on the production deployment;
|
|
11
|
+
* account-side conversion settings remain outside website runtime.
|
|
11
12
|
*/
|
|
12
13
|
export function BrandlabGoogleTag(props) {
|
|
13
14
|
const plan = createBrandlabGoogleTagPlan(props, props.phoneNumber);
|
package/dist/google-tag.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-tag.js","sourceRoot":"","sources":["../src/google-tag.tsx"],"names":[],"mappings":";AAAA,OAAO,MAAM,MAAM,aAAa,CAAA;AAChC,OAAO,EACL,2BAA2B,GAE5B,MAAM,sBAAsB,CAAA;AAE7B,OAAO,
|
|
1
|
+
{"version":3,"file":"google-tag.js","sourceRoot":"","sources":["../src/google-tag.tsx"],"names":[],"mappings":";AAAA,OAAO,MAAM,MAAM,aAAa,CAAA;AAChC,OAAO,EACL,2BAA2B,GAE5B,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAA;AAYlE;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAA6B;IAC7D,MAAM,IAAI,GAAG,2BAA2B,CAAC,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;IAClE,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,OAAO,CACL,8BACE,KAAC,MAAM,IACL,EAAE,EAAC,4BAA4B,8BACN,QAAQ,EACjC,GAAG,EAAE,IAAI,CAAC,MAAM,EAChB,QAAQ,EAAC,kBAAkB,EAC3B,KAAK,EAAE,KAAK,CAAC,KAAK,GAClB,EACF,KAAC,MAAM,IACL,EAAE,EAAC,4BAA4B,8BACN,QAAQ,EACjC,QAAQ,EAAC,kBAAkB,EAC3B,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,uBAAuB,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,GACtD,IACD,CACJ,CAAA;AACH,CAAC","sourcesContent":["import Script from 'next/script'\nimport {\n createBrandlabGoogleTagPlan,\n type BrandlabGoogleTagConfig,\n} from './google-tag-plan.js'\n\nexport { createBrandlabGoogleTagPlan } from './google-tag-plan.js'\nexport type {\n BrandlabGoogleTagConfig,\n BrandlabGoogleTagPlan,\n} from './google-tag-plan.js'\n\nexport interface BrandlabGoogleTagProps extends BrandlabGoogleTagConfig {\n /** Must match the verified public number exactly as it is rendered on the page. */\n phoneNumber?: string | null\n nonce?: string\n}\n\n/**\n * Shared Google tag and website-call forwarding-number snippet.\n *\n * Render this exactly once from the root layout:\n * `<BrandlabGoogleTag {...analytics.googleTag} phoneNumber={displayedPhone} />`.\n * `analytics.googleTag.enabled` is true only on the production deployment;\n * account-side conversion settings remain outside website runtime.\n */\nexport function BrandlabGoogleTag(props: BrandlabGoogleTagProps) {\n const plan = createBrandlabGoogleTagPlan(props, props.phoneNumber)\n if (!plan) return null\n return (\n <>\n <Script\n id=\"brandlab-google-tag-loader\"\n data-brandlab-google-tag=\"loader\"\n src={plan.source}\n strategy=\"afterInteractive\"\n nonce={props.nonce}\n />\n <Script\n id=\"brandlab-google-tag-config\"\n data-brandlab-google-tag=\"config\"\n strategy=\"afterInteractive\"\n nonce={props.nonce}\n dangerouslySetInnerHTML={{ __html: plan.inlineScript }}\n />\n </>\n )\n}\n"]}
|
package/dist/headers.d.ts
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Brandlab security/response headers
|
|
2
|
+
* Brandlab security/response headers: one canonical header set for every
|
|
3
|
+
* client site, emitted by `defineBrandlabNextConfig`.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
* `next
|
|
5
|
+
* The deployment decides everything environment-specific:
|
|
6
|
+
* - a production build (`next build`, on Vercel or locally) enforces the
|
|
7
|
+
* production Content Security Policy and sends HSTS;
|
|
8
|
+
* - `next dev` sends a report-only development policy without HSTS;
|
|
9
|
+
* - a Vercel Preview additionally sends `noindex, nofollow, noarchive` and
|
|
10
|
+
* `private, no-store`, so previews never reach search results or caches.
|
|
6
11
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* isProduction,
|
|
10
|
-
* profile: 'next-local-service',
|
|
11
|
-
* })
|
|
12
|
-
* }
|
|
13
|
-
*
|
|
14
|
-
* The X-Robots-Tag here carries snippet-display controls only; per-page
|
|
15
|
-
* indexation stays owned by Metadata.robots (noindex pages override via
|
|
16
|
-
* their <meta name="robots">).
|
|
12
|
+
* The production X-Robots-Tag carries snippet-display controls only; per-page
|
|
13
|
+
* indexation stays owned by Metadata.robots.
|
|
17
14
|
*/
|
|
15
|
+
import type { BrandlabDeployment } from '@brandlab-digital/site-core/site';
|
|
18
16
|
export interface BrandlabHeader {
|
|
19
17
|
key: string;
|
|
20
18
|
value: string;
|
|
@@ -23,10 +21,10 @@ export interface BrandlabHeaderRule {
|
|
|
23
21
|
source: string;
|
|
24
22
|
headers: BrandlabHeader[];
|
|
25
23
|
}
|
|
26
|
-
export declare const BRANDLAB_NEXT_HEADER_PROFILES: readonly ["next-local-service", "next-root-level"];
|
|
27
|
-
export type BrandlabNextHeaderProfile = (typeof BRANDLAB_NEXT_HEADER_PROFILES)[number];
|
|
28
24
|
export declare const BRANDLAB_DEFAULT_PERMISSIONS_POLICY = "camera=(), microphone=(), geolocation=()";
|
|
29
25
|
export declare const BRANDLAB_DEFAULT_X_ROBOTS_TAG = "max-snippet:-1, max-image-preview:large, max-video-preview:-1";
|
|
26
|
+
export declare const BRANDLAB_PREVIEW_X_ROBOTS_TAG = "noindex, nofollow, noarchive";
|
|
27
|
+
export declare const BRANDLAB_PREVIEW_CACHE_CONTROL = "private, no-store, max-age=0";
|
|
30
28
|
export declare const BRANDLAB_HSTS_VALUE = "max-age=31536000";
|
|
31
29
|
export interface BrandlabHstsExpansion {
|
|
32
30
|
includeSubDomains: true;
|
|
@@ -39,26 +37,21 @@ export interface BrandlabContentSecurityPolicyExtensions {
|
|
|
39
37
|
connectSrc?: readonly string[];
|
|
40
38
|
frameSrc?: readonly string[];
|
|
41
39
|
}
|
|
42
|
-
export type BrandlabContentSecurityPolicyMode = 'enforce' | 'report-only';
|
|
43
40
|
export interface BrandlabSecurityHeaderOptions {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
permissionsPolicy?: string;
|
|
47
|
-
/** Pass null to omit the snippet-control header entirely. */
|
|
48
|
-
xRobotsTag?: string | null;
|
|
49
|
-
extraHeaders?: readonly BrandlabHeader[];
|
|
41
|
+
/** From resolveBrandlabDeployment(process.env). */
|
|
42
|
+
deployment: BrandlabDeployment;
|
|
50
43
|
hsts?: BrandlabHstsExpansion;
|
|
51
|
-
contentSecurityPolicyMode?: BrandlabContentSecurityPolicyMode;
|
|
52
44
|
/** Reviewed HTTPS origins needed by an approved project integration. */
|
|
53
45
|
contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions;
|
|
46
|
+
/** Reviewed project exception, for example `geolocation=(self)` for a location feature. */
|
|
47
|
+
permissionsPolicy?: string;
|
|
48
|
+
/** Reviewed project headers; they cannot replace a protected header. */
|
|
49
|
+
extraHeaders?: readonly BrandlabHeader[];
|
|
54
50
|
}
|
|
55
51
|
export declare function buildBrandlabContentSecurityPolicy(options: {
|
|
56
|
-
|
|
57
|
-
profile: BrandlabNextHeaderProfile;
|
|
52
|
+
deployment: BrandlabDeployment;
|
|
58
53
|
contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions;
|
|
59
54
|
}): string;
|
|
60
55
|
export declare function buildBrandlabSecurityHeaders(options: BrandlabSecurityHeaderOptions): BrandlabHeader[];
|
|
61
|
-
export declare function buildBrandlabHeaderRules(options: BrandlabSecurityHeaderOptions
|
|
62
|
-
source?: string;
|
|
63
|
-
}): BrandlabHeaderRule[];
|
|
56
|
+
export declare function buildBrandlabHeaderRules(options: BrandlabSecurityHeaderOptions): BrandlabHeaderRule[];
|
|
64
57
|
//# sourceMappingURL=headers.d.ts.map
|
package/dist/headers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAEnB,MAAM,kCAAkC,CAAA;AAEzC,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,cAAc,EAAE,CAAA;CAC1B;AAED,eAAO,MAAM,mCAAmC,6CACJ,CAAA;AAE5C,eAAO,MAAM,6BAA6B,kEACuB,CAAA;AAEjE,eAAO,MAAM,6BAA6B,iCAAiC,CAAA;AAE3E,eAAO,MAAM,8BAA8B,iCAAiC,CAAA;AAE5E,eAAO,MAAM,mBAAmB,qBACZ,CAAA;AAEpB,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,IAAI,CAAA;IACvB,OAAO,CAAC,EAAE,IAAI,CAAA;IACd,mEAAmE;IACnE,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAoBD,MAAM,WAAW,uCAAuC;IACtD,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC7B;AAED,MAAM,WAAW,6BAA6B;IAC5C,mDAAmD;IACnD,UAAU,EAAE,kBAAkB,CAAA;IAC9B,IAAI,CAAC,EAAE,qBAAqB,CAAA;IAC5B,wEAAwE;IACxE,+BAA+B,CAAC,EAAE,uCAAuC,CAAA;IACzE,2FAA2F;IAC3F,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,wEAAwE;IACxE,YAAY,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;CACzC;AAcD,wBAAgB,kCAAkC,CAAC,OAAO,EAAE;IAC1D,UAAU,EAAE,kBAAkB,CAAA;IAC9B,+BAA+B,CAAC,EAAE,uCAAuC,CAAA;CAC1E,UAsDA;AAiDD,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,6BAA6B,GACrC,cAAc,EAAE,CAmDlB;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,6BAA6B,GAAG,kBAAkB,EAAE,CAOrG"}
|
package/dist/headers.js
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Brandlab security/response headers
|
|
2
|
+
* Brandlab security/response headers: one canonical header set for every
|
|
3
|
+
* client site, emitted by `defineBrandlabNextConfig`.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
* `next
|
|
5
|
+
* The deployment decides everything environment-specific:
|
|
6
|
+
* - a production build (`next build`, on Vercel or locally) enforces the
|
|
7
|
+
* production Content Security Policy and sends HSTS;
|
|
8
|
+
* - `next dev` sends a report-only development policy without HSTS;
|
|
9
|
+
* - a Vercel Preview additionally sends `noindex, nofollow, noarchive` and
|
|
10
|
+
* `private, no-store`, so previews never reach search results or caches.
|
|
6
11
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* isProduction,
|
|
10
|
-
* profile: 'next-local-service',
|
|
11
|
-
* })
|
|
12
|
-
* }
|
|
13
|
-
*
|
|
14
|
-
* The X-Robots-Tag here carries snippet-display controls only; per-page
|
|
15
|
-
* indexation stays owned by Metadata.robots (noindex pages override via
|
|
16
|
-
* their <meta name="robots">).
|
|
12
|
+
* The production X-Robots-Tag carries snippet-display controls only; per-page
|
|
13
|
+
* indexation stays owned by Metadata.robots.
|
|
17
14
|
*/
|
|
18
|
-
export const BRANDLAB_NEXT_HEADER_PROFILES = [
|
|
19
|
-
'next-local-service',
|
|
20
|
-
'next-root-level',
|
|
21
|
-
];
|
|
22
15
|
export const BRANDLAB_DEFAULT_PERMISSIONS_POLICY = 'camera=(), microphone=(), geolocation=()';
|
|
23
16
|
export const BRANDLAB_DEFAULT_X_ROBOTS_TAG = 'max-snippet:-1, max-image-preview:large, max-video-preview:-1';
|
|
17
|
+
export const BRANDLAB_PREVIEW_X_ROBOTS_TAG = 'noindex, nofollow, noarchive';
|
|
18
|
+
export const BRANDLAB_PREVIEW_CACHE_CONTROL = 'private, no-store, max-age=0';
|
|
24
19
|
export const BRANDLAB_HSTS_VALUE = 'max-age=31536000';
|
|
25
20
|
const HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
26
21
|
const PROTECTED_HEADER_NAMES = new Set([
|
|
22
|
+
'cache-control',
|
|
27
23
|
'content-security-policy',
|
|
28
24
|
'content-security-policy-report-only',
|
|
29
25
|
'x-frame-options',
|
|
@@ -33,11 +29,23 @@ const PROTECTED_HEADER_NAMES = new Set([
|
|
|
33
29
|
'x-robots-tag',
|
|
34
30
|
'strict-transport-security',
|
|
35
31
|
]);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
const DEPLOYMENT_ENVIRONMENTS = new Set([
|
|
33
|
+
'production',
|
|
34
|
+
'preview',
|
|
35
|
+
'development',
|
|
36
|
+
]);
|
|
37
|
+
function assertDeployment(deployment) {
|
|
38
|
+
const candidate = deployment;
|
|
39
|
+
if (!candidate ||
|
|
40
|
+
typeof candidate !== 'object' ||
|
|
41
|
+
!DEPLOYMENT_ENVIRONMENTS.has(candidate.environment) ||
|
|
42
|
+
typeof candidate.productionBuild !== 'boolean') {
|
|
43
|
+
throw new Error('Header rules require the deployment from resolveBrandlabDeployment(process.env).');
|
|
40
44
|
}
|
|
45
|
+
}
|
|
46
|
+
export function buildBrandlabContentSecurityPolicy(options) {
|
|
47
|
+
assertDeployment(options.deployment);
|
|
48
|
+
const productionBuild = options.deployment.productionBuild;
|
|
41
49
|
const scriptSources = [
|
|
42
50
|
"'self'",
|
|
43
51
|
"'unsafe-inline'",
|
|
@@ -49,7 +57,7 @@ export function buildBrandlabContentSecurityPolicy(options) {
|
|
|
49
57
|
'https://googleads.g.doubleclick.net',
|
|
50
58
|
'https://challenges.cloudflare.com',
|
|
51
59
|
...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.scriptSrc, 'scriptSrc'),
|
|
52
|
-
...(
|
|
60
|
+
...(productionBuild ? [] : ["'unsafe-eval'", 'http://localhost:*']),
|
|
53
61
|
];
|
|
54
62
|
const connectSources = [
|
|
55
63
|
"'self'",
|
|
@@ -66,7 +74,7 @@ export function buildBrandlabContentSecurityPolicy(options) {
|
|
|
66
74
|
'https://*.analytics.google.com',
|
|
67
75
|
'https://challenges.cloudflare.com',
|
|
68
76
|
...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.connectSrc, 'connectSrc'),
|
|
69
|
-
...(
|
|
77
|
+
...(productionBuild ? [] : ['http://localhost:*', 'ws://localhost:*']),
|
|
70
78
|
];
|
|
71
79
|
const directives = [
|
|
72
80
|
"default-src 'self'",
|
|
@@ -82,7 +90,7 @@ export function buildBrandlabContentSecurityPolicy(options) {
|
|
|
82
90
|
`frame-src 'self' https://challenges.cloudflare.com https://www.googletagmanager.com https://www.google.com ${normalizeCspExtensions(options.contentSecurityPolicyExtensions?.frameSrc, 'frameSrc').join(' ')}`.trim(),
|
|
83
91
|
"manifest-src 'self'",
|
|
84
92
|
"worker-src 'self' blob:",
|
|
85
|
-
...(
|
|
93
|
+
...(productionBuild ? ['upgrade-insecure-requests'] : []),
|
|
86
94
|
];
|
|
87
95
|
return directives.join('; ');
|
|
88
96
|
}
|
|
@@ -120,15 +128,25 @@ function validateExtraHeaders(headers) {
|
|
|
120
128
|
assertHeaderValue(header.value, `extraHeaders[${index}].value`);
|
|
121
129
|
});
|
|
122
130
|
}
|
|
131
|
+
const HEADER_OPTION_KEYS = new Set([
|
|
132
|
+
'deployment',
|
|
133
|
+
'hsts',
|
|
134
|
+
'contentSecurityPolicyExtensions',
|
|
135
|
+
'permissionsPolicy',
|
|
136
|
+
'extraHeaders',
|
|
137
|
+
]);
|
|
123
138
|
export function buildBrandlabSecurityHeaders(options) {
|
|
124
|
-
if (typeof options
|
|
125
|
-
throw new
|
|
139
|
+
if (!options || typeof options !== 'object') {
|
|
140
|
+
throw new TypeError('Header options are required.');
|
|
141
|
+
}
|
|
142
|
+
const unsupported = Object.keys(options).filter((key) => !HEADER_OPTION_KEYS.has(key));
|
|
143
|
+
if (unsupported.length > 0) {
|
|
144
|
+
throw new Error(`Header options contain unsupported keys: ${unsupported.join(', ')}. The deployment decides CSP, HSTS and X-Robots-Tag.`);
|
|
126
145
|
}
|
|
127
|
-
|
|
146
|
+
assertDeployment(options.deployment);
|
|
147
|
+
const { environment, productionBuild } = options.deployment;
|
|
128
148
|
const permissionsPolicy = options.permissionsPolicy ?? BRANDLAB_DEFAULT_PERMISSIONS_POLICY;
|
|
129
149
|
assertHeaderValue(permissionsPolicy, 'permissionsPolicy');
|
|
130
|
-
if (xRobotsTag !== null)
|
|
131
|
-
assertHeaderValue(xRobotsTag, 'xRobotsTag');
|
|
132
150
|
validateExtraHeaders(options.extraHeaders ?? []);
|
|
133
151
|
if (options.hsts) {
|
|
134
152
|
assertHeaderValue(options.hsts.readinessEvidence, 'hsts.readinessEvidence');
|
|
@@ -139,19 +157,7 @@ export function buildBrandlabSecurityHeaders(options) {
|
|
|
139
157
|
const hstsValue = options.hsts
|
|
140
158
|
? `${BRANDLAB_HSTS_VALUE}; includeSubDomains${options.hsts.preload ? '; preload' : ''}`
|
|
141
159
|
: BRANDLAB_HSTS_VALUE;
|
|
142
|
-
const
|
|
143
|
-
if (!new Set(['enforce', 'report-only']).has(cspMode)) {
|
|
144
|
-
throw new Error(`Unsupported contentSecurityPolicyMode "${cspMode}".`);
|
|
145
|
-
}
|
|
146
|
-
if (options.isProduction && cspMode !== 'enforce') {
|
|
147
|
-
throw new Error('Production Content Security Policy must use enforce mode.');
|
|
148
|
-
}
|
|
149
|
-
const cspHeader = [{
|
|
150
|
-
key: cspMode === 'enforce'
|
|
151
|
-
? 'Content-Security-Policy'
|
|
152
|
-
: 'Content-Security-Policy-Report-Only',
|
|
153
|
-
value: buildBrandlabContentSecurityPolicy(options),
|
|
154
|
-
}];
|
|
160
|
+
const preview = environment === 'preview';
|
|
155
161
|
return [
|
|
156
162
|
{ key: 'X-Frame-Options', value: 'DENY' },
|
|
157
163
|
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
|
@@ -160,22 +166,27 @@ export function buildBrandlabSecurityHeaders(options) {
|
|
|
160
166
|
key: 'Permissions-Policy',
|
|
161
167
|
value: permissionsPolicy,
|
|
162
168
|
},
|
|
163
|
-
|
|
164
|
-
|
|
169
|
+
{
|
|
170
|
+
key: 'X-Robots-Tag',
|
|
171
|
+
value: preview ? BRANDLAB_PREVIEW_X_ROBOTS_TAG : BRANDLAB_DEFAULT_X_ROBOTS_TAG,
|
|
172
|
+
},
|
|
173
|
+
...(productionBuild
|
|
165
174
|
? [{ key: 'Strict-Transport-Security', value: hstsValue }]
|
|
166
175
|
: []),
|
|
167
|
-
|
|
168
|
-
|
|
176
|
+
{
|
|
177
|
+
key: productionBuild
|
|
178
|
+
? 'Content-Security-Policy'
|
|
179
|
+
: 'Content-Security-Policy-Report-Only',
|
|
180
|
+
value: buildBrandlabContentSecurityPolicy(options),
|
|
181
|
+
},
|
|
182
|
+
...(options.extraHeaders ?? []).map((header) => ({ ...header })),
|
|
183
|
+
...(preview ? [{ key: 'Cache-Control', value: BRANDLAB_PREVIEW_CACHE_CONTROL }] : []),
|
|
169
184
|
];
|
|
170
185
|
}
|
|
171
186
|
export function buildBrandlabHeaderRules(options) {
|
|
172
|
-
const source = options.source ?? '/:path*';
|
|
173
|
-
if (!source.startsWith('/') || /[\u0000-\u001F\u007F]/.test(source)) {
|
|
174
|
-
throw new Error('Header-rule source must be a root-relative single-line pattern.');
|
|
175
|
-
}
|
|
176
187
|
return [
|
|
177
188
|
{
|
|
178
|
-
source,
|
|
189
|
+
source: '/:path*',
|
|
179
190
|
headers: buildBrandlabSecurityHeaders(options),
|
|
180
191
|
},
|
|
181
192
|
];
|
package/dist/headers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Brandlab security/response headers (Brandlab Project Standard §2).\n *\n * One canonical header set for every client site, composed into\n * `next.config.ts`:\n *\n * async headers() {\n * return buildBrandlabHeaderRules({\n * isProduction,\n * profile: 'next-local-service',\n * })\n * }\n *\n * The X-Robots-Tag here carries snippet-display controls only; per-page\n * indexation stays owned by Metadata.robots (noindex pages override via\n * their <meta name=\"robots\">).\n */\n\nexport interface BrandlabHeader {\n key: string\n value: string\n}\n\nexport interface BrandlabHeaderRule {\n source: string\n headers: BrandlabHeader[]\n}\n\nexport const BRANDLAB_NEXT_HEADER_PROFILES = [\n 'next-local-service',\n 'next-root-level',\n] as const\n\nexport type BrandlabNextHeaderProfile = (typeof BRANDLAB_NEXT_HEADER_PROFILES)[number]\n\nexport const BRANDLAB_DEFAULT_PERMISSIONS_POLICY =\n 'camera=(), microphone=(), geolocation=()'\n\nexport const BRANDLAB_DEFAULT_X_ROBOTS_TAG =\n 'max-snippet:-1, max-image-preview:large, max-video-preview:-1'\n\nexport const BRANDLAB_HSTS_VALUE =\n 'max-age=31536000'\n\nexport interface BrandlabHstsExpansion {\n includeSubDomains: true\n preload?: true\n /** Reviewed proof that every affected subdomain is HTTPS-ready. */\n readinessEvidence: string\n}\n\nconst HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/\nconst PROTECTED_HEADER_NAMES = new Set([\n 'content-security-policy',\n 'content-security-policy-report-only',\n 'x-frame-options',\n 'x-content-type-options',\n 'referrer-policy',\n 'permissions-policy',\n 'x-robots-tag',\n 'strict-transport-security',\n])\n\nexport interface BrandlabContentSecurityPolicyExtensions {\n scriptSrc?: readonly string[]\n connectSrc?: readonly string[]\n frameSrc?: readonly string[]\n}\n\nexport type BrandlabContentSecurityPolicyMode = 'enforce' | 'report-only'\n\nexport interface BrandlabSecurityHeaderOptions {\n isProduction: boolean\n profile: BrandlabNextHeaderProfile\n permissionsPolicy?: string\n /** Pass null to omit the snippet-control header entirely. */\n xRobotsTag?: string | null\n extraHeaders?: readonly BrandlabHeader[]\n hsts?: BrandlabHstsExpansion\n contentSecurityPolicyMode?: BrandlabContentSecurityPolicyMode\n /** Reviewed HTTPS origins needed by an approved project integration. */\n contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions\n}\n\nexport function buildBrandlabContentSecurityPolicy(options: {\n isProduction: boolean\n profile: BrandlabNextHeaderProfile\n contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions\n}) {\n const profile = options.profile\n if (!BRANDLAB_NEXT_HEADER_PROFILES.includes(profile)) {\n throw new Error(`Unsupported Brandlab Next header profile \"${profile}\".`)\n }\n\n const scriptSources = [\n \"'self'\",\n \"'unsafe-inline'\",\n 'https://www.googletagmanager.com',\n 'https://www.googleadservices.com',\n 'https://www.google.com',\n 'https://www.gstatic.com',\n 'https://pagead2.googlesyndication.com',\n 'https://googleads.g.doubleclick.net',\n 'https://challenges.cloudflare.com',\n ...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.scriptSrc, 'scriptSrc'),\n ...(options.isProduction ? [] : [\"'unsafe-eval'\", 'http://localhost:*']),\n ]\n const connectSources = [\n \"'self'\",\n 'https://www.google-analytics.com',\n 'https://*.google-analytics.com',\n 'https://www.googletagmanager.com',\n 'https://www.googleadservices.com',\n 'https://*.doubleclick.net',\n 'https://*.google.com',\n 'https://google.com',\n 'https://www.google.com.au',\n 'https://pagead2.googlesyndication.com',\n 'https://*.googletagmanager.com',\n 'https://*.analytics.google.com',\n 'https://challenges.cloudflare.com',\n ...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.connectSrc, 'connectSrc'),\n ...(options.isProduction ? [] : ['http://localhost:*', 'ws://localhost:*']),\n ]\n const directives = [\n \"default-src 'self'\",\n \"base-uri 'self'\",\n \"object-src 'none'\",\n \"frame-ancestors 'none'\",\n \"form-action 'self'\",\n `script-src ${scriptSources.join(' ')}`,\n \"style-src 'self' 'unsafe-inline'\",\n \"font-src 'self' data:\",\n \"img-src 'self' data: blob: https:\",\n `connect-src ${connectSources.join(' ')}`,\n `frame-src 'self' https://challenges.cloudflare.com https://www.googletagmanager.com https://www.google.com ${normalizeCspExtensions(\n options.contentSecurityPolicyExtensions?.frameSrc,\n 'frameSrc',\n ).join(' ')}`.trim(),\n \"manifest-src 'self'\",\n \"worker-src 'self' blob:\",\n ...(options.isProduction ? ['upgrade-insecure-requests'] : []),\n ]\n return directives.join('; ')\n}\n\nfunction normalizeCspExtensions(\n sources: readonly string[] | undefined,\n field: keyof BrandlabContentSecurityPolicyExtensions,\n) {\n if (!sources) return []\n const normalized = sources.map((source) => {\n const value = source.trim().toLowerCase()\n if (!/^https:\\/\\/(?:\\*\\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?::\\d{1,5})?$/.test(value)) {\n throw new Error(`${field} entries must be HTTPS origins without paths or CSP keywords.`)\n }\n return value\n })\n if (new Set(normalized).size !== normalized.length) {\n throw new Error(`${field} entries must be unique.`)\n }\n return normalized\n}\n\nfunction assertHeaderValue(value: string, field: string) {\n if (!value || value.length > 4_096 || /[\\u0000-\\u001F\\u007F]/.test(value)) {\n throw new Error(`${field} must be a bounded single-line header value.`)\n }\n}\n\nfunction validateExtraHeaders(headers: readonly BrandlabHeader[]) {\n const names = new Set<string>()\n headers.forEach((header, index) => {\n if (!HEADER_NAME_PATTERN.test(header.key)) {\n throw new Error(`extraHeaders[${index}].key is invalid.`)\n }\n const name = header.key.toLowerCase()\n if (PROTECTED_HEADER_NAMES.has(name) || names.has(name)) {\n throw new Error(`extraHeaders[${index}] duplicates a protected or existing header.`)\n }\n names.add(name)\n assertHeaderValue(header.value, `extraHeaders[${index}].value`)\n })\n}\n\nexport function buildBrandlabSecurityHeaders(\n options: BrandlabSecurityHeaderOptions,\n): BrandlabHeader[] {\n if (typeof options.isProduction !== 'boolean') {\n throw new Error('isProduction must be boolean.')\n }\n const xRobotsTag =\n options.xRobotsTag === undefined ? BRANDLAB_DEFAULT_X_ROBOTS_TAG : options.xRobotsTag\n const permissionsPolicy =\n options.permissionsPolicy ?? BRANDLAB_DEFAULT_PERMISSIONS_POLICY\n assertHeaderValue(permissionsPolicy, 'permissionsPolicy')\n if (xRobotsTag !== null) assertHeaderValue(xRobotsTag, 'xRobotsTag')\n validateExtraHeaders(options.extraHeaders ?? [])\n if (options.hsts) {\n assertHeaderValue(options.hsts.readinessEvidence, 'hsts.readinessEvidence')\n if (options.hsts.preload && !options.hsts.includeSubDomains) {\n throw new Error('HSTS preload requires includeSubDomains readiness.')\n }\n }\n const hstsValue = options.hsts\n ? `${BRANDLAB_HSTS_VALUE}; includeSubDomains${options.hsts.preload ? '; preload' : ''}`\n : BRANDLAB_HSTS_VALUE\n const cspMode = options.contentSecurityPolicyMode ?? (\n options.isProduction ? 'enforce' : 'report-only'\n )\n if (!new Set<BrandlabContentSecurityPolicyMode>(['enforce', 'report-only']).has(cspMode)) {\n throw new Error(`Unsupported contentSecurityPolicyMode \"${cspMode}\".`)\n }\n if (options.isProduction && cspMode !== 'enforce') {\n throw new Error('Production Content Security Policy must use enforce mode.')\n }\n const cspHeader = [{\n key: cspMode === 'enforce'\n ? 'Content-Security-Policy'\n : 'Content-Security-Policy-Report-Only',\n value: buildBrandlabContentSecurityPolicy(options),\n }]\n\n return [\n { key: 'X-Frame-Options', value: 'DENY' },\n { key: 'X-Content-Type-Options', value: 'nosniff' },\n { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },\n {\n key: 'Permissions-Policy',\n value: permissionsPolicy,\n },\n ...(xRobotsTag ? [{ key: 'X-Robots-Tag', value: xRobotsTag }] : []),\n ...(options.isProduction\n ? [{ key: 'Strict-Transport-Security', value: hstsValue }]\n : []),\n ...cspHeader,\n ...(options.extraHeaders ?? []),\n ]\n}\n\nexport function buildBrandlabHeaderRules(options: BrandlabSecurityHeaderOptions & {\n source?: string\n}): BrandlabHeaderRule[] {\n const source = options.source ?? '/:path*'\n if (!source.startsWith('/') || /[\\u0000-\\u001F\\u007F]/.test(source)) {\n throw new Error('Header-rule source must be a root-relative single-line pattern.')\n }\n return [\n {\n source,\n headers: buildBrandlabSecurityHeaders(options),\n },\n ]\n}\n"]}
|
|
1
|
+
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAiBH,MAAM,CAAC,MAAM,mCAAmC,GAC9C,0CAA0C,CAAA;AAE5C,MAAM,CAAC,MAAM,6BAA6B,GACxC,+DAA+D,CAAA;AAEjE,MAAM,CAAC,MAAM,6BAA6B,GAAG,8BAA8B,CAAA;AAE3E,MAAM,CAAC,MAAM,8BAA8B,GAAG,8BAA8B,CAAA;AAE5E,MAAM,CAAC,MAAM,mBAAmB,GAC9B,kBAAkB,CAAA;AASpB,MAAM,mBAAmB,GAAG,+BAA+B,CAAA;AAC3D,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IACrC,eAAe;IACf,yBAAyB;IACzB,qCAAqC;IACrC,iBAAiB;IACjB,wBAAwB;IACxB,iBAAiB;IACjB,oBAAoB;IACpB,cAAc;IACd,2BAA2B;CAC5B,CAAC,CAAA;AACF,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAgC;IACrE,YAAY;IACZ,SAAS;IACT,aAAa;CACd,CAAC,CAAA;AAoBF,SAAS,gBAAgB,CAAC,UAAmB;IAC3C,MAAM,SAAS,GAAG,UAAqD,CAAA;IACvE,IACE,CAAC,SAAS;QACV,OAAO,SAAS,KAAK,QAAQ;QAC7B,CAAC,uBAAuB,CAAC,GAAG,CAAC,SAAS,CAAC,WAA4C,CAAC;QACpF,OAAO,SAAS,CAAC,eAAe,KAAK,SAAS,EAC9C,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,kFAAkF,CAAC,CAAA;IACrG,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kCAAkC,CAAC,OAGlD;IACC,gBAAgB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IACpC,MAAM,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAA;IAE1D,MAAM,aAAa,GAAG;QACpB,QAAQ;QACR,iBAAiB;QACjB,kCAAkC;QAClC,kCAAkC;QAClC,wBAAwB;QACxB,yBAAyB;QACzB,uCAAuC;QACvC,qCAAqC;QACrC,mCAAmC;QACnC,GAAG,sBAAsB,CAAC,OAAO,CAAC,+BAA+B,EAAE,SAAS,EAAE,WAAW,CAAC;QAC1F,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,eAAe,EAAE,oBAAoB,CAAC,CAAC;KACpE,CAAA;IACD,MAAM,cAAc,GAAG;QACrB,QAAQ;QACR,kCAAkC;QAClC,gCAAgC;QAChC,kCAAkC;QAClC,kCAAkC;QAClC,2BAA2B;QAC3B,sBAAsB;QACtB,oBAAoB;QACpB,2BAA2B;QAC3B,uCAAuC;QACvC,gCAAgC;QAChC,gCAAgC;QAChC,mCAAmC;QACnC,GAAG,sBAAsB,CAAC,OAAO,CAAC,+BAA+B,EAAE,UAAU,EAAE,YAAY,CAAC;QAC5F,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,oBAAoB,EAAE,kBAAkB,CAAC,CAAC;KACvE,CAAA;IACD,MAAM,UAAU,GAAG;QACjB,oBAAoB;QACpB,iBAAiB;QACjB,mBAAmB;QACnB,wBAAwB;QACxB,oBAAoB;QACpB,cAAc,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACvC,kCAAkC;QAClC,uBAAuB;QACvB,mCAAmC;QACnC,eAAe,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACzC,8GAA8G,sBAAsB,CAClI,OAAO,CAAC,+BAA+B,EAAE,QAAQ,EACjD,UAAU,CACX,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE;QACpB,qBAAqB;QACrB,yBAAyB;QACzB,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1D,CAAA;IACD,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC9B,CAAC;AAED,SAAS,sBAAsB,CAC7B,OAAsC,EACtC,KAAoD;IAEpD,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAA;IACvB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACzC,IAAI,CAAC,oEAAoE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACtF,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,+DAA+D,CAAC,CAAA;QAC1F,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC,CAAC,CAAA;IACF,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,MAAM,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,0BAA0B,CAAC,CAAA;IACrD,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAa,EAAE,KAAa;IACrD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,KAAK,IAAI,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1E,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,8CAA8C,CAAC,CAAA;IACzE,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,OAAkC;IAC9D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAA;IAC/B,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;QAChC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,gBAAgB,KAAK,mBAAmB,CAAC,CAAA;QAC3D,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAA;QACrC,IAAI,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACxD,MAAM,IAAI,KAAK,CAAC,gBAAgB,KAAK,8CAA8C,CAAC,CAAA;QACtF,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACf,iBAAiB,CAAC,MAAM,CAAC,KAAK,EAAE,gBAAgB,KAAK,SAAS,CAAC,CAAA;IACjE,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,YAAY;IACZ,MAAM;IACN,iCAAiC;IACjC,mBAAmB;IACnB,cAAc;CACf,CAAC,CAAA;AAEF,MAAM,UAAU,4BAA4B,CAC1C,OAAsC;IAEtC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,MAAM,IAAI,SAAS,CAAC,8BAA8B,CAAC,CAAA;IACrD,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IACtF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,4CAA4C,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,sDAAsD,CACzH,CAAA;IACH,CAAC;IACD,gBAAgB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IACpC,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,UAAU,CAAA;IAC3D,MAAM,iBAAiB,GACrB,OAAO,CAAC,iBAAiB,IAAI,mCAAmC,CAAA;IAClE,iBAAiB,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAAA;IACzD,oBAAoB,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAA;IAChD,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,wBAAwB,CAAC,CAAA;QAC3E,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC5D,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAA;QACvE,CAAC;IACH,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI;QAC5B,CAAC,CAAC,GAAG,mBAAmB,sBAAsB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE;QACvF,CAAC,CAAC,mBAAmB,CAAA;IACvB,MAAM,OAAO,GAAG,WAAW,KAAK,SAAS,CAAA;IAEzC,OAAO;QACL,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE;QACzC,EAAE,GAAG,EAAE,wBAAwB,EAAE,KAAK,EAAE,SAAS,EAAE;QACnD,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,iCAAiC,EAAE;QACpE;YACE,GAAG,EAAE,oBAAoB;YACzB,KAAK,EAAE,iBAAiB;SACzB;QACD;YACE,GAAG,EAAE,cAAc;YACnB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,6BAA6B;SAC/E;QACD,GAAG,CAAC,eAAe;YACjB,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,2BAA2B,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;YAC1D,CAAC,CAAC,EAAE,CAAC;QACP;YACE,GAAG,EAAE,eAAe;gBAClB,CAAC,CAAC,yBAAyB;gBAC3B,CAAC,CAAC,qCAAqC;YACzC,KAAK,EAAE,kCAAkC,CAAC,OAAO,CAAC;SACnD;QACD,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QAChE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KACtF,CAAA;AACH,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,OAAsC;IAC7E,OAAO;QACL;YACE,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,4BAA4B,CAAC,OAAO,CAAC;SAC/C;KACF,CAAA;AACH,CAAC","sourcesContent":["/**\n * Brandlab security/response headers: one canonical header set for every\n * client site, emitted by `defineBrandlabNextConfig`.\n *\n * The deployment decides everything environment-specific:\n * - a production build (`next build`, on Vercel or locally) enforces the\n * production Content Security Policy and sends HSTS;\n * - `next dev` sends a report-only development policy without HSTS;\n * - a Vercel Preview additionally sends `noindex, nofollow, noarchive` and\n * `private, no-store`, so previews never reach search results or caches.\n *\n * The production X-Robots-Tag carries snippet-display controls only; per-page\n * indexation stays owned by Metadata.robots.\n */\n\nimport type {\n BrandlabDeployment,\n BrandlabDeploymentEnvironment,\n} from '@brandlab-digital/site-core/site'\n\nexport interface BrandlabHeader {\n key: string\n value: string\n}\n\nexport interface BrandlabHeaderRule {\n source: string\n headers: BrandlabHeader[]\n}\n\nexport const BRANDLAB_DEFAULT_PERMISSIONS_POLICY =\n 'camera=(), microphone=(), geolocation=()'\n\nexport const BRANDLAB_DEFAULT_X_ROBOTS_TAG =\n 'max-snippet:-1, max-image-preview:large, max-video-preview:-1'\n\nexport const BRANDLAB_PREVIEW_X_ROBOTS_TAG = 'noindex, nofollow, noarchive'\n\nexport const BRANDLAB_PREVIEW_CACHE_CONTROL = 'private, no-store, max-age=0'\n\nexport const BRANDLAB_HSTS_VALUE =\n 'max-age=31536000'\n\nexport interface BrandlabHstsExpansion {\n includeSubDomains: true\n preload?: true\n /** Reviewed proof that every affected subdomain is HTTPS-ready. */\n readinessEvidence: string\n}\n\nconst HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/\nconst PROTECTED_HEADER_NAMES = new Set([\n 'cache-control',\n 'content-security-policy',\n 'content-security-policy-report-only',\n 'x-frame-options',\n 'x-content-type-options',\n 'referrer-policy',\n 'permissions-policy',\n 'x-robots-tag',\n 'strict-transport-security',\n])\nconst DEPLOYMENT_ENVIRONMENTS = new Set<BrandlabDeploymentEnvironment>([\n 'production',\n 'preview',\n 'development',\n])\n\nexport interface BrandlabContentSecurityPolicyExtensions {\n scriptSrc?: readonly string[]\n connectSrc?: readonly string[]\n frameSrc?: readonly string[]\n}\n\nexport interface BrandlabSecurityHeaderOptions {\n /** From resolveBrandlabDeployment(process.env). */\n deployment: BrandlabDeployment\n hsts?: BrandlabHstsExpansion\n /** Reviewed HTTPS origins needed by an approved project integration. */\n contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions\n /** Reviewed project exception, for example `geolocation=(self)` for a location feature. */\n permissionsPolicy?: string\n /** Reviewed project headers; they cannot replace a protected header. */\n extraHeaders?: readonly BrandlabHeader[]\n}\n\nfunction assertDeployment(deployment: unknown): asserts deployment is BrandlabDeployment {\n const candidate = deployment as Partial<BrandlabDeployment> | undefined\n if (\n !candidate ||\n typeof candidate !== 'object' ||\n !DEPLOYMENT_ENVIRONMENTS.has(candidate.environment as BrandlabDeploymentEnvironment) ||\n typeof candidate.productionBuild !== 'boolean'\n ) {\n throw new Error('Header rules require the deployment from resolveBrandlabDeployment(process.env).')\n }\n}\n\nexport function buildBrandlabContentSecurityPolicy(options: {\n deployment: BrandlabDeployment\n contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions\n}) {\n assertDeployment(options.deployment)\n const productionBuild = options.deployment.productionBuild\n\n const scriptSources = [\n \"'self'\",\n \"'unsafe-inline'\",\n 'https://www.googletagmanager.com',\n 'https://www.googleadservices.com',\n 'https://www.google.com',\n 'https://www.gstatic.com',\n 'https://pagead2.googlesyndication.com',\n 'https://googleads.g.doubleclick.net',\n 'https://challenges.cloudflare.com',\n ...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.scriptSrc, 'scriptSrc'),\n ...(productionBuild ? [] : [\"'unsafe-eval'\", 'http://localhost:*']),\n ]\n const connectSources = [\n \"'self'\",\n 'https://www.google-analytics.com',\n 'https://*.google-analytics.com',\n 'https://www.googletagmanager.com',\n 'https://www.googleadservices.com',\n 'https://*.doubleclick.net',\n 'https://*.google.com',\n 'https://google.com',\n 'https://www.google.com.au',\n 'https://pagead2.googlesyndication.com',\n 'https://*.googletagmanager.com',\n 'https://*.analytics.google.com',\n 'https://challenges.cloudflare.com',\n ...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.connectSrc, 'connectSrc'),\n ...(productionBuild ? [] : ['http://localhost:*', 'ws://localhost:*']),\n ]\n const directives = [\n \"default-src 'self'\",\n \"base-uri 'self'\",\n \"object-src 'none'\",\n \"frame-ancestors 'none'\",\n \"form-action 'self'\",\n `script-src ${scriptSources.join(' ')}`,\n \"style-src 'self' 'unsafe-inline'\",\n \"font-src 'self' data:\",\n \"img-src 'self' data: blob: https:\",\n `connect-src ${connectSources.join(' ')}`,\n `frame-src 'self' https://challenges.cloudflare.com https://www.googletagmanager.com https://www.google.com ${normalizeCspExtensions(\n options.contentSecurityPolicyExtensions?.frameSrc,\n 'frameSrc',\n ).join(' ')}`.trim(),\n \"manifest-src 'self'\",\n \"worker-src 'self' blob:\",\n ...(productionBuild ? ['upgrade-insecure-requests'] : []),\n ]\n return directives.join('; ')\n}\n\nfunction normalizeCspExtensions(\n sources: readonly string[] | undefined,\n field: keyof BrandlabContentSecurityPolicyExtensions,\n) {\n if (!sources) return []\n const normalized = sources.map((source) => {\n const value = source.trim().toLowerCase()\n if (!/^https:\\/\\/(?:\\*\\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?::\\d{1,5})?$/.test(value)) {\n throw new Error(`${field} entries must be HTTPS origins without paths or CSP keywords.`)\n }\n return value\n })\n if (new Set(normalized).size !== normalized.length) {\n throw new Error(`${field} entries must be unique.`)\n }\n return normalized\n}\n\nfunction assertHeaderValue(value: string, field: string) {\n if (!value || value.length > 4_096 || /[\\u0000-\\u001F\\u007F]/.test(value)) {\n throw new Error(`${field} must be a bounded single-line header value.`)\n }\n}\n\nfunction validateExtraHeaders(headers: readonly BrandlabHeader[]) {\n const names = new Set<string>()\n headers.forEach((header, index) => {\n if (!HEADER_NAME_PATTERN.test(header.key)) {\n throw new Error(`extraHeaders[${index}].key is invalid.`)\n }\n const name = header.key.toLowerCase()\n if (PROTECTED_HEADER_NAMES.has(name) || names.has(name)) {\n throw new Error(`extraHeaders[${index}] duplicates a protected or existing header.`)\n }\n names.add(name)\n assertHeaderValue(header.value, `extraHeaders[${index}].value`)\n })\n}\n\nconst HEADER_OPTION_KEYS = new Set([\n 'deployment',\n 'hsts',\n 'contentSecurityPolicyExtensions',\n 'permissionsPolicy',\n 'extraHeaders',\n])\n\nexport function buildBrandlabSecurityHeaders(\n options: BrandlabSecurityHeaderOptions,\n): BrandlabHeader[] {\n if (!options || typeof options !== 'object') {\n throw new TypeError('Header options are required.')\n }\n const unsupported = Object.keys(options).filter((key) => !HEADER_OPTION_KEYS.has(key))\n if (unsupported.length > 0) {\n throw new Error(\n `Header options contain unsupported keys: ${unsupported.join(', ')}. The deployment decides CSP, HSTS and X-Robots-Tag.`,\n )\n }\n assertDeployment(options.deployment)\n const { environment, productionBuild } = options.deployment\n const permissionsPolicy =\n options.permissionsPolicy ?? BRANDLAB_DEFAULT_PERMISSIONS_POLICY\n assertHeaderValue(permissionsPolicy, 'permissionsPolicy')\n validateExtraHeaders(options.extraHeaders ?? [])\n if (options.hsts) {\n assertHeaderValue(options.hsts.readinessEvidence, 'hsts.readinessEvidence')\n if (options.hsts.preload && !options.hsts.includeSubDomains) {\n throw new Error('HSTS preload requires includeSubDomains readiness.')\n }\n }\n const hstsValue = options.hsts\n ? `${BRANDLAB_HSTS_VALUE}; includeSubDomains${options.hsts.preload ? '; preload' : ''}`\n : BRANDLAB_HSTS_VALUE\n const preview = environment === 'preview'\n\n return [\n { key: 'X-Frame-Options', value: 'DENY' },\n { key: 'X-Content-Type-Options', value: 'nosniff' },\n { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },\n {\n key: 'Permissions-Policy',\n value: permissionsPolicy,\n },\n {\n key: 'X-Robots-Tag',\n value: preview ? BRANDLAB_PREVIEW_X_ROBOTS_TAG : BRANDLAB_DEFAULT_X_ROBOTS_TAG,\n },\n ...(productionBuild\n ? [{ key: 'Strict-Transport-Security', value: hstsValue }]\n : []),\n {\n key: productionBuild\n ? 'Content-Security-Policy'\n : 'Content-Security-Policy-Report-Only',\n value: buildBrandlabContentSecurityPolicy(options),\n },\n ...(options.extraHeaders ?? []).map((header) => ({ ...header })),\n ...(preview ? [{ key: 'Cache-Control', value: BRANDLAB_PREVIEW_CACHE_CONTROL }] : []),\n ]\n}\n\nexport function buildBrandlabHeaderRules(options: BrandlabSecurityHeaderOptions): BrandlabHeaderRule[] {\n return [\n {\n source: '/:path*',\n headers: buildBrandlabSecurityHeaders(options),\n },\n ]\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ContactAttributionSummary } from '@brandlab-digital/site-core/contact';
|
|
2
|
-
|
|
2
|
+
import { type BrandlabAnalytics, type PhoneCallPlacement } from './analytics.js';
|
|
3
|
+
export type PhoneCallLocation = PhoneCallPlacement;
|
|
3
4
|
export interface LastPhoneCallEvent {
|
|
4
5
|
key: string;
|
|
5
6
|
at: number;
|
|
@@ -17,6 +18,13 @@ export declare function shouldTrackPhoneCallClick(options: {
|
|
|
17
18
|
export declare function persistInitialLanding(win?: Window): void;
|
|
18
19
|
export declare function getInitialLandingPath(win?: Window): string | undefined;
|
|
19
20
|
export declare function getInitialContactAttribution(win?: Window): ContactAttributionSummary;
|
|
21
|
+
/**
|
|
22
|
+
* Placement of a telephone link: its own `data-phone-location`, then a
|
|
23
|
+
* `<header>` or `<footer>` ancestor, then the nearest ancestor declaring a
|
|
24
|
+
* valid `data-phone-location` (for example a hero or call-to-action section),
|
|
25
|
+
* otherwise `page`. Unrecognised values are ignored, so every tap still
|
|
26
|
+
* produces exactly one event.
|
|
27
|
+
*/
|
|
20
28
|
export declare function getPhoneCallLocation(link: HTMLAnchorElement): PhoneCallLocation;
|
|
21
29
|
export declare function installPhoneClickListener(options: {
|
|
22
30
|
trackPhoneLinkClick: (context: {
|
|
@@ -27,4 +35,16 @@ export declare function installPhoneClickListener(options: {
|
|
|
27
35
|
win?: Window;
|
|
28
36
|
dedupeWindowMs?: number;
|
|
29
37
|
}): () => void;
|
|
38
|
+
/**
|
|
39
|
+
* The whole of `src/instrumentation-client.ts`:
|
|
40
|
+
* `installBrandlabInstrumentation(analytics)`. Stores the privacy-safe landing
|
|
41
|
+
* path and visit source for contact attribution, and installs the single
|
|
42
|
+
* telephone-link listener that sends one `phone_link_click` per tap (rapid
|
|
43
|
+
* repeats of the same link are de-duplicated). Installing twice in one window
|
|
44
|
+
* is a no-op; the returned function removes the listener.
|
|
45
|
+
*/
|
|
46
|
+
export declare function installBrandlabInstrumentation(analytics: Pick<BrandlabAnalytics, 'trackPhoneLinkClick'>, options?: {
|
|
47
|
+
win?: Window;
|
|
48
|
+
dedupeWindowMs?: number;
|
|
49
|
+
}): () => void;
|
|
30
50
|
//# sourceMappingURL=instrumentation-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation-client.d.ts","sourceRoot":"","sources":["../src/instrumentation-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,qCAAqC,CAAA;
|
|
1
|
+
{"version":3,"file":"instrumentation-client.d.ts","sourceRoot":"","sources":["../src/instrumentation-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAA;AAMvB,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAA;AAElD,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,EAAE,EAAE,MAAM,CAAA;CACX;AAED,eAAO,MAAM,8BAA8B,6BAA6B,CAAA;AACxE,eAAO,MAAM,mCAAmC,OAAO,CAAA;AAEvD,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,UAe9E;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAEtE;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE;IACjD,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,kBAAkB,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,WAOA;AAED,wBAAgB,qBAAqB,CAAC,GAAG,GAAE,MAAe,QAgCzD;AAED,wBAAgB,qBAAqB,CAAC,GAAG,GAAE,MAAe,sBAOzD;AAED,wBAAgB,4BAA4B,CAAC,GAAG,GAAE,MAAe,GAAG,yBAAyB,CAkB5F;AASD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,CAc/E;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE;IACjD,mBAAmB,EAAE,CAAC,OAAO,EAAE;QAC7B,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,iBAAiB,CAAA;QAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAA;KAC5B,KAAK,IAAI,CAAA;IACV,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,cA4CA;AAID;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,IAAI,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,EACzD,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,MAAM,IAAI,CA2BZ"}
|