@brandlab-digital/site-core 1.0.13 → 1.2.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 +4 -1
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +7 -2
- package/dist/analytics.js.map +1 -1
- package/dist/contact.d.ts +9 -7
- package/dist/contact.d.ts.map +1 -1
- package/dist/contact.js +8 -25
- package/dist/contact.js.map +1 -1
- package/dist/project.d.ts +1 -1
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +1 -0
- package/dist/project.js.map +1 -1
- package/package.json +1 -1
package/dist/analytics.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export declare const CANONICAL_LEAD_EVENTS: readonly ["
|
|
1
|
+
export declare const CANONICAL_LEAD_EVENTS: readonly ["contact_form"];
|
|
2
|
+
export declare const CANONICAL_PHONE_INTERACTION_EVENTS: readonly ["phone_link_click"];
|
|
2
3
|
export declare const BRANDLAB_LEAD_DIAGNOSTIC_EVENTS: readonly ["lead_form_start", "lead_form_error", "lead_cta_click"];
|
|
3
4
|
export type CanonicalLeadEvent = (typeof CANONICAL_LEAD_EVENTS)[number];
|
|
5
|
+
export type CanonicalPhoneInteractionEvent = (typeof CANONICAL_PHONE_INTERACTION_EVENTS)[number];
|
|
4
6
|
export type BrandlabLeadDiagnosticEvent = (typeof BRANDLAB_LEAD_DIAGNOSTIC_EVENTS)[number];
|
|
5
7
|
/**
|
|
6
8
|
* Analytics and other production-only browser behaviour must use the actual
|
|
@@ -67,6 +69,7 @@ export interface ContactFormConversionResult {
|
|
|
67
69
|
}
|
|
68
70
|
export declare function defineAnalyticsContract<const T extends BrandlabAnalyticsContract>(contract: T): T;
|
|
69
71
|
export declare function isCanonicalLeadEvent(value: string): value is CanonicalLeadEvent;
|
|
72
|
+
export declare function isCanonicalPhoneInteractionEvent(value: string): value is CanonicalPhoneInteractionEvent;
|
|
70
73
|
export declare function normalizeGoogleAdsConversionId(conversionId: string): string;
|
|
71
74
|
export declare function normalizeEnhancedConversionEmail(email: string | null | undefined): string | undefined;
|
|
72
75
|
export declare function normalizeEnhancedConversionPhone(phone: string | null | undefined): string | undefined;
|
package/dist/analytics.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,qBAAqB,
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
|
package/dist/analytics.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { normalizeBrandlabRoutePath } from './routes.js';
|
|
2
2
|
import { assertBrandlabCanonicalEntityId } from './entity-id.js';
|
|
3
|
-
export const CANONICAL_LEAD_EVENTS = ['
|
|
3
|
+
export const CANONICAL_LEAD_EVENTS = ['contact_form'];
|
|
4
|
+
export const CANONICAL_PHONE_INTERACTION_EVENTS = ['phone_link_click'];
|
|
4
5
|
export const BRANDLAB_LEAD_DIAGNOSTIC_EVENTS = [
|
|
5
6
|
'lead_form_start',
|
|
6
7
|
'lead_form_error',
|
|
@@ -74,7 +75,8 @@ export function defineAnalyticsContract(contract) {
|
|
|
74
75
|
const duplicate = events.find((event, index) => events.slice(0, index).some((candidate) => candidate.name === event.name));
|
|
75
76
|
if (duplicate)
|
|
76
77
|
throw new Error(`Analytics contract contains duplicate event "${duplicate.name}".`);
|
|
77
|
-
const reservedDiagnostic = events.find((event) => BRANDLAB_LEAD_DIAGNOSTIC_EVENTS.includes(event.name)
|
|
78
|
+
const reservedDiagnostic = events.find((event) => BRANDLAB_LEAD_DIAGNOSTIC_EVENTS.includes(event.name) ||
|
|
79
|
+
CANONICAL_PHONE_INTERACTION_EVENTS.includes(event.name));
|
|
78
80
|
if (reservedDiagnostic) {
|
|
79
81
|
throw new Error(`Shared lead diagnostic event "${reservedDiagnostic.name}" must not be redefined.`);
|
|
80
82
|
}
|
|
@@ -129,6 +131,9 @@ export function defineAnalyticsContract(contract) {
|
|
|
129
131
|
export function isCanonicalLeadEvent(value) {
|
|
130
132
|
return CANONICAL_LEAD_EVENTS.includes(value);
|
|
131
133
|
}
|
|
134
|
+
export function isCanonicalPhoneInteractionEvent(value) {
|
|
135
|
+
return CANONICAL_PHONE_INTERACTION_EVENTS.includes(value);
|
|
136
|
+
}
|
|
132
137
|
export function normalizeGoogleAdsConversionId(conversionId) {
|
|
133
138
|
const trimmed = conversionId.trim();
|
|
134
139
|
return trimmed.startsWith('AW-') ? trimmed : `AW-${trimmed}`;
|
package/dist/analytics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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","sourcesContent":["import { normalizeBrandlabRoutePath } from './routes.js'\nimport { assertBrandlabCanonicalEntityId } from './entity-id.js'\n\nexport const CANONICAL_LEAD_EVENTS = ['phone_call', 'contact_form'] as const\nexport const BRANDLAB_LEAD_DIAGNOSTIC_EVENTS = [\n 'lead_form_start',\n 'lead_form_error',\n 'lead_cta_click',\n] as const\n\nexport type CanonicalLeadEvent = (typeof CANONICAL_LEAD_EVENTS)[number]\nexport type BrandlabLeadDiagnosticEvent =\n (typeof BRANDLAB_LEAD_DIAGNOSTIC_EVENTS)[number]\n\n/**\n * Analytics and other production-only browser behaviour must use the actual\n * Vercel deployment environment. Vercel preview builds also use\n * NODE_ENV=production, so NODE_ENV is not a safe production gate.\n */\nexport function isBrandlabProductionDeployment(\n environment: string | null | undefined,\n) {\n return environment === 'production'\n}\n\nexport type BrandlabAnalyticsParameterPolicy =\n | { key: string; type: 'boolean' }\n | { key: string; type: 'count'; minimum?: number; maximum?: number }\n | { key: string; type: 'enum'; values: readonly string[] }\n | { key: string; type: 'slug' }\n\nexport interface BrandlabAnalyticsEventContract {\n name: string\n parameters: readonly BrandlabAnalyticsParameterPolicy[]\n}\n\nexport interface BrandlabAnalyticsContract {\n /** Canonical entity binding prevents analytics config being reused by the wrong site. */\n projectId: string\n measurementIdEnv: string\n googleAds?: {\n conversionIdEnv: string\n contactConversionLabelEnv: string\n /** Google Ads forwarding-number label for genuine calls from the website. */\n websiteCalls?: {\n conversionLabelEnv: string\n }\n /** Enhanced Conversions require both this project opt-in and live user consent. */\n enhancedConversions?: { mode: 'consent-required' }\n }\n /** Privacy-safe GA4 diagnostics. These never become canonical lead events. */\n leadDiagnostics?: { enabled: true }\n webVitals: boolean\n additionalEvents?: readonly BrandlabAnalyticsEventContract[]\n}\nexport type GtagTransport = (...args:\n | ['event', string, Record<string, unknown>]\n | ['set', string, Record<string, unknown>]\n | ['config', string, Record<string, unknown>?]\n | ['js', Date]\n) => void\n\nexport interface EnhancedConversionUserData {\n email?: string | null\n phone?: string | null\n}\n\nexport interface GoogleTagEnhancedConversionUserData extends Record<string, unknown> {\n email: string\n phone_number?: string\n}\n\nexport interface ContactFormConversionResult {\n ga4Sent: boolean\n enhancedConversionSent: boolean\n adsConversionSent: boolean\n}\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst GMAIL_DOMAINS = new Set(['gmail.com', 'googlemail.com'])\nconst AUSTRALIAN_E164_DIGITS = /^61[23478]\\d{8}$/\nconst GA4_CONTACT_FORM_PARAM_KEYS = new Set([\n 'event_category',\n 'location_slug',\n 'page_path',\n 'page_template',\n 'service_slug',\n 'submission_id',\n])\nconst GOOGLE_ADS_CONVERSION_PARAM_KEYS = new Set([\n 'currency',\n 'transaction_id',\n 'value',\n])\nconst ANALYTICS_ENV_KEY_PATTERN = /^[A-Z][A-Z0-9_]*$/\nconst ANALYTICS_EVENT_PATTERN = /^[a-z][a-z0-9_]{0,39}$/\nconst PROHIBITED_ANALYTICS_PARAM_TOKENS = [\n 'address',\n 'email',\n 'first_name',\n 'last_name',\n 'message',\n 'name',\n 'phone',\n] as const\nconst PRIVACY_SAFE_SUBMISSION_ID_PATTERN = /^[a-f0-9]{64}$/\n\nfunction looksLikePersonalIdentifier(value: string) {\n const normalized = value.trim().toLowerCase()\n const digits = normalized.replace(/\\D/g, '')\n return (\n normalized.includes('@') ||\n digits.length >= 8 ||\n /^\\d{4}$/.test(normalized) ||\n /^[a-f0-9]{24,}$/.test(normalized) ||\n /\\b\\d+\\s+[a-z][a-z -]+\\s+(?:street|st|road|rd|avenue|ave|lane|ln|drive|dr|place|pl|court|ct)\\b/.test(\n normalized,\n )\n )\n}\n\nfunction assertAnalyticsEnvKey(value: string, field: string) {\n if (!ANALYTICS_ENV_KEY_PATTERN.test(value)) {\n throw new Error(`${field} must be an environment variable key.`)\n }\n}\n\nexport function defineAnalyticsContract<const T extends BrandlabAnalyticsContract>(contract: T): T {\n assertBrandlabCanonicalEntityId(contract.projectId)\n assertAnalyticsEnvKey(contract.measurementIdEnv, 'measurementIdEnv')\n if (contract.googleAds) {\n assertAnalyticsEnvKey(contract.googleAds.conversionIdEnv, 'googleAds.conversionIdEnv')\n assertAnalyticsEnvKey(\n contract.googleAds.contactConversionLabelEnv,\n 'googleAds.contactConversionLabelEnv',\n )\n if (contract.googleAds.websiteCalls) {\n assertAnalyticsEnvKey(\n contract.googleAds.websiteCalls.conversionLabelEnv,\n 'googleAds.websiteCalls.conversionLabelEnv',\n )\n }\n if (\n contract.googleAds.enhancedConversions !== undefined &&\n contract.googleAds.enhancedConversions.mode !== 'consent-required'\n ) {\n throw new Error('googleAds.enhancedConversions must require consent.')\n }\n }\n\n const events = contract.additionalEvents ?? []\n const duplicate = events.find((event, index) =>\n events.slice(0, index).some((candidate) => candidate.name === event.name),\n )\n if (duplicate) throw new Error(`Analytics contract contains duplicate event \"${duplicate.name}\".`)\n\n const reservedDiagnostic = events.find((event) =>\n BRANDLAB_LEAD_DIAGNOSTIC_EVENTS.includes(event.name as BrandlabLeadDiagnosticEvent),\n )\n if (reservedDiagnostic) {\n throw new Error(\n `Shared lead diagnostic event \"${reservedDiagnostic.name}\" must not be redefined.`,\n )\n }\n\n events.forEach((event, eventIndex) => {\n if (!ANALYTICS_EVENT_PATTERN.test(event.name)) {\n throw new Error(`additionalEvents[${eventIndex}].name is invalid.`)\n }\n if (isCanonicalLeadEvent(event.name)) {\n throw new Error(`Canonical lead event \"${event.name}\" must not be redefined.`)\n }\n if (event.parameters.length === 0 || event.parameters.length > 20) {\n throw new Error(`additionalEvents[${eventIndex}].parameters must contain 1 to 20 policies.`)\n }\n const duplicateParameter = event.parameters.find((parameter, index) =>\n event.parameters.slice(0, index).some((candidate) => candidate.key === parameter.key),\n )\n if (duplicateParameter) {\n throw new Error(`Analytics event \"${event.name}\" contains duplicate parameter \"${duplicateParameter.key}\".`)\n }\n event.parameters.forEach((parameter, keyIndex) => {\n const key = parameter.key\n if (!ANALYTICS_EVENT_PATTERN.test(key)) {\n throw new Error(`additionalEvents[${eventIndex}].parameters[${keyIndex}].key is invalid.`)\n }\n const normalized = key.toLowerCase()\n if (PROHIBITED_ANALYTICS_PARAM_TOKENS.some((token) => normalized.includes(token))) {\n throw new Error(`Analytics parameter \"${key}\" may contain personal information.`)\n }\n if (parameter.type === 'enum') {\n if (\n parameter.values.length === 0 ||\n parameter.values.length > 50 ||\n new Set(parameter.values).size !== parameter.values.length ||\n parameter.values.some(\n (value) =>\n !value.trim() ||\n value.length > 80 ||\n looksLikePersonalIdentifier(value),\n )\n ) {\n throw new Error(`Analytics enum parameter \"${key}\" has an unsafe value policy.`)\n }\n } else if (parameter.type === 'count') {\n const minimum = parameter.minimum ?? 0\n const maximum = parameter.maximum ?? 100_000\n if (\n !Number.isInteger(minimum) ||\n !Number.isInteger(maximum) ||\n minimum < 0 ||\n maximum < minimum ||\n maximum > 1_000_000\n ) {\n throw new Error(`Analytics count parameter \"${key}\" has an invalid range.`)\n }\n }\n })\n })\n\n return contract\n}\n\nexport function isCanonicalLeadEvent(value: string): value is CanonicalLeadEvent {\n return CANONICAL_LEAD_EVENTS.includes(value as CanonicalLeadEvent)\n}\n\nexport function normalizeGoogleAdsConversionId(conversionId: string) {\n const trimmed = conversionId.trim()\n return trimmed.startsWith('AW-') ? trimmed : `AW-${trimmed}`\n}\n\nfunction isGoogleAdsConversionLabel(value: string) {\n return /^[A-Za-z0-9_-]{1,100}$/.test(value)\n}\n\nexport function normalizeEnhancedConversionEmail(email: string | null | undefined) {\n const trimmed = email?.trim().toLowerCase()\n if (!trimmed || !EMAIL_PATTERN.test(trimmed)) {\n return undefined\n }\n\n const [localPart, domain] = trimmed.split('@')\n if (!localPart || !domain) {\n return undefined\n }\n\n const normalizedLocalPart = GMAIL_DOMAINS.has(domain)\n ? localPart.replace(/\\./g, '')\n : localPart\n\n return `${normalizedLocalPart}@${domain}`\n}\n\nexport function normalizeEnhancedConversionPhone(phone: string | null | undefined) {\n const digits = phone?.replace(/[^\\d]/g, '') ?? ''\n if (!digits) {\n return undefined\n }\n\n const normalizedDigits = digits.startsWith('0') ? `61${digits.slice(1)}` : digits\n\n return AUSTRALIAN_E164_DIGITS.test(normalizedDigits) ? `+${normalizedDigits}` : undefined\n}\n\nexport function buildEnhancedConversionUserData(\n userData: EnhancedConversionUserData | null | undefined,\n): GoogleTagEnhancedConversionUserData | undefined {\n const email = normalizeEnhancedConversionEmail(userData?.email)\n if (!email) {\n return undefined\n }\n\n const phoneNumber = normalizeEnhancedConversionPhone(userData?.phone)\n\n return phoneNumber\n ? {\n email,\n phone_number: phoneNumber,\n }\n : { email }\n}\n\nexport function getGoogleAdsContactSendTo(options: {\n conversionId?: string | null\n conversionLabel?: string | null\n}) {\n const conversionId = options.conversionId?.trim()\n const conversionLabel = options.conversionLabel?.trim()\n\n if (!conversionId || !conversionLabel || !isGoogleAdsConversionLabel(conversionLabel)) {\n return undefined\n }\n\n return `${normalizeGoogleAdsConversionId(conversionId)}/${conversionLabel}`\n}\n\nexport function getGoogleAdsWebsiteCallSendTo(options: {\n conversionId?: string | null\n conversionLabel?: string | null\n}) {\n return getGoogleAdsContactSendTo(options)\n}\n\nexport function sendBrandlabAnalyticsEvent(options: {\n enabled: boolean\n measurementId?: string | null\n gtag?: GtagTransport\n eventName: string\n params: Record<string, unknown>\n}) {\n if (!options.enabled || !options.measurementId || typeof options.gtag !== 'function') {\n return false\n }\n\n options.gtag('event', options.eventName, {\n ...options.params,\n send_to: options.measurementId,\n })\n\n return true\n}\n\nexport function sanitizeContactFormGa4Params(params: Record<string, unknown>) {\n return Object.entries(params).reduce<Record<string, unknown>>((sanitized, [key, value]) => {\n const normalizedKey = key.toLowerCase()\n if (!GA4_CONTACT_FORM_PARAM_KEYS.has(normalizedKey)) return sanitized\n if (normalizedKey === 'event_category') {\n if (value === 'lead') sanitized.event_category = 'lead'\n return sanitized\n }\n if (normalizedKey === 'page_path') {\n if (typeof value !== 'string') return sanitized\n const path = value.trim().split(/[?#]/, 1)[0]\n if (!path || path.length > 500) return sanitized\n try {\n sanitized.page_path = normalizeBrandlabRoutePath(path)\n } catch {\n // Invalid or encoded path separators are not analytics dimensions.\n }\n return sanitized\n }\n if (normalizedKey === 'submission_id') {\n if (typeof value === 'string' && PRIVACY_SAFE_SUBMISSION_ID_PATTERN.test(value)) {\n sanitized.submission_id = value\n }\n return sanitized\n }\n if (\n typeof value === 'string' &&\n /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(value) &&\n value.length <= 80\n ) {\n sanitized[normalizedKey] = value\n }\n\n return sanitized\n }, {})\n}\n\nfunction sanitizeGoogleAdsConversionParams(params: Record<string, unknown>) {\n return Object.entries(params).reduce<Record<string, unknown>>((sanitized, [key, value]) => {\n const normalizedKey = key.toLowerCase()\n if (!GOOGLE_ADS_CONVERSION_PARAM_KEYS.has(normalizedKey)) return sanitized\n if (normalizedKey === 'currency' && typeof value === 'string' && /^[A-Z]{3}$/.test(value)) {\n sanitized.currency = value\n } else if (\n normalizedKey === 'value' &&\n typeof value === 'number' &&\n Number.isFinite(value) &&\n value >= 0\n ) {\n sanitized.value = value\n } else if (\n normalizedKey === 'transaction_id' &&\n typeof value === 'string' &&\n PRIVACY_SAFE_SUBMISSION_ID_PATTERN.test(value)\n ) {\n sanitized.transaction_id = value\n }\n\n return sanitized\n }, {})\n}\n\nexport function sendContactFormConversion(options: {\n enabled: boolean\n measurementId?: string | null\n googleAdsConversionId?: string | null\n googleAdsConversionLabel?: string | null\n gtag?: GtagTransport\n pagePath: string\n submissionId?: string | null\n enhancedConversion?: EnhancedConversionUserData | null\n enhancedConversionsEnabled?: boolean\n adUserDataConsent?: boolean\n params?: Record<string, unknown>\n adsParams?: Record<string, unknown>\n}): ContactFormConversionResult {\n const submissionId = options.submissionId?.trim().toLowerCase()\n const privacySafeSubmissionId =\n submissionId && PRIVACY_SAFE_SUBMISSION_ID_PATTERN.test(submissionId)\n ? submissionId\n : undefined\n const baseParams = sanitizeContactFormGa4Params({\n ...options.params,\n page_path: options.pagePath,\n ...(privacySafeSubmissionId ? { submission_id: privacySafeSubmissionId } : {}),\n event_category: 'lead',\n })\n const ga4Sent = sendBrandlabAnalyticsEvent({\n enabled: options.enabled,\n measurementId: options.measurementId,\n gtag: options.gtag,\n eventName: 'contact_form',\n params: baseParams,\n })\n const sendTo = getGoogleAdsContactSendTo({\n conversionId: options.googleAdsConversionId,\n conversionLabel: options.googleAdsConversionLabel,\n })\n const userData =\n options.enhancedConversionsEnabled && options.adUserDataConsent\n ? buildEnhancedConversionUserData(options.enhancedConversion)\n : undefined\n const canSendAdsConversion =\n options.enabled &&\n typeof options.gtag === 'function' &&\n Boolean(sendTo)\n\n if (!canSendAdsConversion || !sendTo || typeof options.gtag !== 'function') {\n return {\n ga4Sent,\n enhancedConversionSent: false,\n adsConversionSent: false,\n }\n }\n\n if (userData) {\n options.gtag('set', 'user_data', userData)\n }\n try {\n options.gtag('event', 'conversion', {\n ...sanitizeGoogleAdsConversionParams(options.adsParams ?? {}),\n send_to: sendTo,\n ...(privacySafeSubmissionId ? { transaction_id: privacySafeSubmissionId } : {}),\n })\n } finally {\n if (userData) options.gtag('set', 'user_data', {})\n }\n\n return {\n ga4Sent,\n enhancedConversionSent: Boolean(userData),\n adsConversionSent: true,\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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","sourcesContent":["import { normalizeBrandlabRoutePath } from './routes.js'\nimport { assertBrandlabCanonicalEntityId } from './entity-id.js'\n\nexport const CANONICAL_LEAD_EVENTS = ['contact_form'] as const\nexport const CANONICAL_PHONE_INTERACTION_EVENTS = ['phone_link_click'] as const\nexport const BRANDLAB_LEAD_DIAGNOSTIC_EVENTS = [\n 'lead_form_start',\n 'lead_form_error',\n 'lead_cta_click',\n] as const\n\nexport type CanonicalLeadEvent = (typeof CANONICAL_LEAD_EVENTS)[number]\nexport type CanonicalPhoneInteractionEvent =\n (typeof CANONICAL_PHONE_INTERACTION_EVENTS)[number]\nexport type BrandlabLeadDiagnosticEvent =\n (typeof BRANDLAB_LEAD_DIAGNOSTIC_EVENTS)[number]\n\n/**\n * Analytics and other production-only browser behaviour must use the actual\n * Vercel deployment environment. Vercel preview builds also use\n * NODE_ENV=production, so NODE_ENV is not a safe production gate.\n */\nexport function isBrandlabProductionDeployment(\n environment: string | null | undefined,\n) {\n return environment === 'production'\n}\n\nexport type BrandlabAnalyticsParameterPolicy =\n | { key: string; type: 'boolean' }\n | { key: string; type: 'count'; minimum?: number; maximum?: number }\n | { key: string; type: 'enum'; values: readonly string[] }\n | { key: string; type: 'slug' }\n\nexport interface BrandlabAnalyticsEventContract {\n name: string\n parameters: readonly BrandlabAnalyticsParameterPolicy[]\n}\n\nexport interface BrandlabAnalyticsContract {\n /** Canonical entity binding prevents analytics config being reused by the wrong site. */\n projectId: string\n measurementIdEnv: string\n googleAds?: {\n conversionIdEnv: string\n contactConversionLabelEnv: string\n /** Google Ads forwarding-number label for genuine calls from the website. */\n websiteCalls?: {\n conversionLabelEnv: string\n }\n /** Enhanced Conversions require both this project opt-in and live user consent. */\n enhancedConversions?: { mode: 'consent-required' }\n }\n /** Privacy-safe GA4 diagnostics. These never become canonical lead events. */\n leadDiagnostics?: { enabled: true }\n webVitals: boolean\n additionalEvents?: readonly BrandlabAnalyticsEventContract[]\n}\nexport type GtagTransport = (...args:\n | ['event', string, Record<string, unknown>]\n | ['set', string, Record<string, unknown>]\n | ['config', string, Record<string, unknown>?]\n | ['js', Date]\n) => void\n\nexport interface EnhancedConversionUserData {\n email?: string | null\n phone?: string | null\n}\n\nexport interface GoogleTagEnhancedConversionUserData extends Record<string, unknown> {\n email: string\n phone_number?: string\n}\n\nexport interface ContactFormConversionResult {\n ga4Sent: boolean\n enhancedConversionSent: boolean\n adsConversionSent: boolean\n}\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst GMAIL_DOMAINS = new Set(['gmail.com', 'googlemail.com'])\nconst AUSTRALIAN_E164_DIGITS = /^61[23478]\\d{8}$/\nconst GA4_CONTACT_FORM_PARAM_KEYS = new Set([\n 'event_category',\n 'location_slug',\n 'page_path',\n 'page_template',\n 'service_slug',\n 'submission_id',\n])\nconst GOOGLE_ADS_CONVERSION_PARAM_KEYS = new Set([\n 'currency',\n 'transaction_id',\n 'value',\n])\nconst ANALYTICS_ENV_KEY_PATTERN = /^[A-Z][A-Z0-9_]*$/\nconst ANALYTICS_EVENT_PATTERN = /^[a-z][a-z0-9_]{0,39}$/\nconst PROHIBITED_ANALYTICS_PARAM_TOKENS = [\n 'address',\n 'email',\n 'first_name',\n 'last_name',\n 'message',\n 'name',\n 'phone',\n] as const\nconst PRIVACY_SAFE_SUBMISSION_ID_PATTERN = /^[a-f0-9]{64}$/\n\nfunction looksLikePersonalIdentifier(value: string) {\n const normalized = value.trim().toLowerCase()\n const digits = normalized.replace(/\\D/g, '')\n return (\n normalized.includes('@') ||\n digits.length >= 8 ||\n /^\\d{4}$/.test(normalized) ||\n /^[a-f0-9]{24,}$/.test(normalized) ||\n /\\b\\d+\\s+[a-z][a-z -]+\\s+(?:street|st|road|rd|avenue|ave|lane|ln|drive|dr|place|pl|court|ct)\\b/.test(\n normalized,\n )\n )\n}\n\nfunction assertAnalyticsEnvKey(value: string, field: string) {\n if (!ANALYTICS_ENV_KEY_PATTERN.test(value)) {\n throw new Error(`${field} must be an environment variable key.`)\n }\n}\n\nexport function defineAnalyticsContract<const T extends BrandlabAnalyticsContract>(contract: T): T {\n assertBrandlabCanonicalEntityId(contract.projectId)\n assertAnalyticsEnvKey(contract.measurementIdEnv, 'measurementIdEnv')\n if (contract.googleAds) {\n assertAnalyticsEnvKey(contract.googleAds.conversionIdEnv, 'googleAds.conversionIdEnv')\n assertAnalyticsEnvKey(\n contract.googleAds.contactConversionLabelEnv,\n 'googleAds.contactConversionLabelEnv',\n )\n if (contract.googleAds.websiteCalls) {\n assertAnalyticsEnvKey(\n contract.googleAds.websiteCalls.conversionLabelEnv,\n 'googleAds.websiteCalls.conversionLabelEnv',\n )\n }\n if (\n contract.googleAds.enhancedConversions !== undefined &&\n contract.googleAds.enhancedConversions.mode !== 'consent-required'\n ) {\n throw new Error('googleAds.enhancedConversions must require consent.')\n }\n }\n\n const events = contract.additionalEvents ?? []\n const duplicate = events.find((event, index) =>\n events.slice(0, index).some((candidate) => candidate.name === event.name),\n )\n if (duplicate) throw new Error(`Analytics contract contains duplicate event \"${duplicate.name}\".`)\n\n const reservedDiagnostic = events.find((event) =>\n BRANDLAB_LEAD_DIAGNOSTIC_EVENTS.includes(event.name as BrandlabLeadDiagnosticEvent) ||\n CANONICAL_PHONE_INTERACTION_EVENTS.includes(\n event.name as CanonicalPhoneInteractionEvent,\n ),\n )\n if (reservedDiagnostic) {\n throw new Error(\n `Shared lead diagnostic event \"${reservedDiagnostic.name}\" must not be redefined.`,\n )\n }\n\n events.forEach((event, eventIndex) => {\n if (!ANALYTICS_EVENT_PATTERN.test(event.name)) {\n throw new Error(`additionalEvents[${eventIndex}].name is invalid.`)\n }\n if (isCanonicalLeadEvent(event.name)) {\n throw new Error(`Canonical lead event \"${event.name}\" must not be redefined.`)\n }\n if (event.parameters.length === 0 || event.parameters.length > 20) {\n throw new Error(`additionalEvents[${eventIndex}].parameters must contain 1 to 20 policies.`)\n }\n const duplicateParameter = event.parameters.find((parameter, index) =>\n event.parameters.slice(0, index).some((candidate) => candidate.key === parameter.key),\n )\n if (duplicateParameter) {\n throw new Error(`Analytics event \"${event.name}\" contains duplicate parameter \"${duplicateParameter.key}\".`)\n }\n event.parameters.forEach((parameter, keyIndex) => {\n const key = parameter.key\n if (!ANALYTICS_EVENT_PATTERN.test(key)) {\n throw new Error(`additionalEvents[${eventIndex}].parameters[${keyIndex}].key is invalid.`)\n }\n const normalized = key.toLowerCase()\n if (PROHIBITED_ANALYTICS_PARAM_TOKENS.some((token) => normalized.includes(token))) {\n throw new Error(`Analytics parameter \"${key}\" may contain personal information.`)\n }\n if (parameter.type === 'enum') {\n if (\n parameter.values.length === 0 ||\n parameter.values.length > 50 ||\n new Set(parameter.values).size !== parameter.values.length ||\n parameter.values.some(\n (value) =>\n !value.trim() ||\n value.length > 80 ||\n looksLikePersonalIdentifier(value),\n )\n ) {\n throw new Error(`Analytics enum parameter \"${key}\" has an unsafe value policy.`)\n }\n } else if (parameter.type === 'count') {\n const minimum = parameter.minimum ?? 0\n const maximum = parameter.maximum ?? 100_000\n if (\n !Number.isInteger(minimum) ||\n !Number.isInteger(maximum) ||\n minimum < 0 ||\n maximum < minimum ||\n maximum > 1_000_000\n ) {\n throw new Error(`Analytics count parameter \"${key}\" has an invalid range.`)\n }\n }\n })\n })\n\n return contract\n}\n\nexport function isCanonicalLeadEvent(value: string): value is CanonicalLeadEvent {\n return CANONICAL_LEAD_EVENTS.includes(value as CanonicalLeadEvent)\n}\n\nexport function isCanonicalPhoneInteractionEvent(\n value: string,\n): value is CanonicalPhoneInteractionEvent {\n return CANONICAL_PHONE_INTERACTION_EVENTS.includes(\n value as CanonicalPhoneInteractionEvent,\n )\n}\n\nexport function normalizeGoogleAdsConversionId(conversionId: string) {\n const trimmed = conversionId.trim()\n return trimmed.startsWith('AW-') ? trimmed : `AW-${trimmed}`\n}\n\nfunction isGoogleAdsConversionLabel(value: string) {\n return /^[A-Za-z0-9_-]{1,100}$/.test(value)\n}\n\nexport function normalizeEnhancedConversionEmail(email: string | null | undefined) {\n const trimmed = email?.trim().toLowerCase()\n if (!trimmed || !EMAIL_PATTERN.test(trimmed)) {\n return undefined\n }\n\n const [localPart, domain] = trimmed.split('@')\n if (!localPart || !domain) {\n return undefined\n }\n\n const normalizedLocalPart = GMAIL_DOMAINS.has(domain)\n ? localPart.replace(/\\./g, '')\n : localPart\n\n return `${normalizedLocalPart}@${domain}`\n}\n\nexport function normalizeEnhancedConversionPhone(phone: string | null | undefined) {\n const digits = phone?.replace(/[^\\d]/g, '') ?? ''\n if (!digits) {\n return undefined\n }\n\n const normalizedDigits = digits.startsWith('0') ? `61${digits.slice(1)}` : digits\n\n return AUSTRALIAN_E164_DIGITS.test(normalizedDigits) ? `+${normalizedDigits}` : undefined\n}\n\nexport function buildEnhancedConversionUserData(\n userData: EnhancedConversionUserData | null | undefined,\n): GoogleTagEnhancedConversionUserData | undefined {\n const email = normalizeEnhancedConversionEmail(userData?.email)\n if (!email) {\n return undefined\n }\n\n const phoneNumber = normalizeEnhancedConversionPhone(userData?.phone)\n\n return phoneNumber\n ? {\n email,\n phone_number: phoneNumber,\n }\n : { email }\n}\n\nexport function getGoogleAdsContactSendTo(options: {\n conversionId?: string | null\n conversionLabel?: string | null\n}) {\n const conversionId = options.conversionId?.trim()\n const conversionLabel = options.conversionLabel?.trim()\n\n if (!conversionId || !conversionLabel || !isGoogleAdsConversionLabel(conversionLabel)) {\n return undefined\n }\n\n return `${normalizeGoogleAdsConversionId(conversionId)}/${conversionLabel}`\n}\n\nexport function getGoogleAdsWebsiteCallSendTo(options: {\n conversionId?: string | null\n conversionLabel?: string | null\n}) {\n return getGoogleAdsContactSendTo(options)\n}\n\nexport function sendBrandlabAnalyticsEvent(options: {\n enabled: boolean\n measurementId?: string | null\n gtag?: GtagTransport\n eventName: string\n params: Record<string, unknown>\n}) {\n if (!options.enabled || !options.measurementId || typeof options.gtag !== 'function') {\n return false\n }\n\n options.gtag('event', options.eventName, {\n ...options.params,\n send_to: options.measurementId,\n })\n\n return true\n}\n\nexport function sanitizeContactFormGa4Params(params: Record<string, unknown>) {\n return Object.entries(params).reduce<Record<string, unknown>>((sanitized, [key, value]) => {\n const normalizedKey = key.toLowerCase()\n if (!GA4_CONTACT_FORM_PARAM_KEYS.has(normalizedKey)) return sanitized\n if (normalizedKey === 'event_category') {\n if (value === 'lead') sanitized.event_category = 'lead'\n return sanitized\n }\n if (normalizedKey === 'page_path') {\n if (typeof value !== 'string') return sanitized\n const path = value.trim().split(/[?#]/, 1)[0]\n if (!path || path.length > 500) return sanitized\n try {\n sanitized.page_path = normalizeBrandlabRoutePath(path)\n } catch {\n // Invalid or encoded path separators are not analytics dimensions.\n }\n return sanitized\n }\n if (normalizedKey === 'submission_id') {\n if (typeof value === 'string' && PRIVACY_SAFE_SUBMISSION_ID_PATTERN.test(value)) {\n sanitized.submission_id = value\n }\n return sanitized\n }\n if (\n typeof value === 'string' &&\n /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(value) &&\n value.length <= 80\n ) {\n sanitized[normalizedKey] = value\n }\n\n return sanitized\n }, {})\n}\n\nfunction sanitizeGoogleAdsConversionParams(params: Record<string, unknown>) {\n return Object.entries(params).reduce<Record<string, unknown>>((sanitized, [key, value]) => {\n const normalizedKey = key.toLowerCase()\n if (!GOOGLE_ADS_CONVERSION_PARAM_KEYS.has(normalizedKey)) return sanitized\n if (normalizedKey === 'currency' && typeof value === 'string' && /^[A-Z]{3}$/.test(value)) {\n sanitized.currency = value\n } else if (\n normalizedKey === 'value' &&\n typeof value === 'number' &&\n Number.isFinite(value) &&\n value >= 0\n ) {\n sanitized.value = value\n } else if (\n normalizedKey === 'transaction_id' &&\n typeof value === 'string' &&\n PRIVACY_SAFE_SUBMISSION_ID_PATTERN.test(value)\n ) {\n sanitized.transaction_id = value\n }\n\n return sanitized\n }, {})\n}\n\nexport function sendContactFormConversion(options: {\n enabled: boolean\n measurementId?: string | null\n googleAdsConversionId?: string | null\n googleAdsConversionLabel?: string | null\n gtag?: GtagTransport\n pagePath: string\n submissionId?: string | null\n enhancedConversion?: EnhancedConversionUserData | null\n enhancedConversionsEnabled?: boolean\n adUserDataConsent?: boolean\n params?: Record<string, unknown>\n adsParams?: Record<string, unknown>\n}): ContactFormConversionResult {\n const submissionId = options.submissionId?.trim().toLowerCase()\n const privacySafeSubmissionId =\n submissionId && PRIVACY_SAFE_SUBMISSION_ID_PATTERN.test(submissionId)\n ? submissionId\n : undefined\n const baseParams = sanitizeContactFormGa4Params({\n ...options.params,\n page_path: options.pagePath,\n ...(privacySafeSubmissionId ? { submission_id: privacySafeSubmissionId } : {}),\n event_category: 'lead',\n })\n const ga4Sent = sendBrandlabAnalyticsEvent({\n enabled: options.enabled,\n measurementId: options.measurementId,\n gtag: options.gtag,\n eventName: 'contact_form',\n params: baseParams,\n })\n const sendTo = getGoogleAdsContactSendTo({\n conversionId: options.googleAdsConversionId,\n conversionLabel: options.googleAdsConversionLabel,\n })\n const userData =\n options.enhancedConversionsEnabled && options.adUserDataConsent\n ? buildEnhancedConversionUserData(options.enhancedConversion)\n : undefined\n const canSendAdsConversion =\n options.enabled &&\n typeof options.gtag === 'function' &&\n Boolean(sendTo)\n\n if (!canSendAdsConversion || !sendTo || typeof options.gtag !== 'function') {\n return {\n ga4Sent,\n enhancedConversionSent: false,\n adsConversionSent: false,\n }\n }\n\n if (userData) {\n options.gtag('set', 'user_data', userData)\n }\n try {\n options.gtag('event', 'conversion', {\n ...sanitizeGoogleAdsConversionParams(options.adsParams ?? {}),\n send_to: sendTo,\n ...(privacySafeSubmissionId ? { transaction_id: privacySafeSubmissionId } : {}),\n })\n } finally {\n if (userData) options.gtag('set', 'user_data', {})\n }\n\n return {\n ga4Sent,\n enhancedConversionSent: Boolean(userData),\n adsConversionSent: true,\n }\n}\n"]}
|
package/dist/contact.d.ts
CHANGED
|
@@ -4,24 +4,26 @@ export interface ContactAttachmentPolicy {
|
|
|
4
4
|
maxTotalSizeBytes: number;
|
|
5
5
|
allowedTypes: readonly ContactAttachmentMimeType[];
|
|
6
6
|
}
|
|
7
|
-
export type ContactAttachmentMimeType = 'image/jpeg' | 'image/jpg' | 'image/png' | 'image/webp' | 'image/gif' | 'image/heic' | 'image/heif';
|
|
7
|
+
export type ContactAttachmentMimeType = 'application/pdf' | 'image/jpeg' | 'image/jpg' | 'image/png' | 'image/webp' | 'image/gif' | 'image/heic' | 'image/heif';
|
|
8
8
|
export declare const DEFAULT_CONTACT_ATTACHMENT_POLICY: {
|
|
9
9
|
readonly maxFiles: 5;
|
|
10
10
|
readonly maxFileSizeBytes: 4000000;
|
|
11
11
|
readonly maxTotalSizeBytes: 4000000;
|
|
12
12
|
readonly allowedTypes: readonly ["image/jpeg", "image/jpg", "image/png", "image/webp", "image/gif", "image/heic", "image/heif"];
|
|
13
13
|
};
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
export declare const DOCUMENT_CONTACT_ATTACHMENT_POLICY: {
|
|
15
|
+
readonly allowedTypes: readonly ["image/jpeg", "image/jpg", "image/png", "image/webp", "image/gif", "image/heic", "image/heif", "application/pdf"];
|
|
16
|
+
readonly maxFiles: 5;
|
|
17
|
+
readonly maxFileSizeBytes: 4000000;
|
|
18
|
+
readonly maxTotalSizeBytes: 4000000;
|
|
19
|
+
};
|
|
19
20
|
export declare function contactAttachmentAccept(policy?: ContactAttachmentPolicy): string;
|
|
20
21
|
export declare function contactAttachmentTypeLabel(policy?: ContactAttachmentPolicy): string;
|
|
21
22
|
export declare function isAllowedContactAttachmentType(value: string, policy?: ContactAttachmentPolicy): value is ContactAttachmentMimeType;
|
|
22
23
|
export declare function resolveContactAttachmentType(filename: string, fileType?: string, policy?: ContactAttachmentPolicy): ContactAttachmentMimeType | null;
|
|
23
24
|
export declare function createContactAttachmentFilename(options: {
|
|
24
25
|
index: number;
|
|
26
|
+
contentType?: string;
|
|
25
27
|
}): string;
|
|
26
28
|
export declare function validateContactAttachment(file: {
|
|
27
29
|
filename: string;
|
|
@@ -134,7 +136,7 @@ export interface BrandlabContactContract {
|
|
|
134
136
|
fieldProfile: string;
|
|
135
137
|
fields: readonly ContactFieldContract[];
|
|
136
138
|
attachments: {
|
|
137
|
-
mode: 'none' | 'images-optional' | 'images-required';
|
|
139
|
+
mode: 'none' | 'images-optional' | 'images-required' | 'images-and-pdfs-optional';
|
|
138
140
|
};
|
|
139
141
|
delivery: {
|
|
140
142
|
recipientEnv: string;
|
package/dist/contact.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contact.d.ts","sourceRoot":"","sources":["../src/contact.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,MAAM,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,YAAY,EAAE,SAAS,yBAAyB,EAAE,CAAA;CACnD;AAED,MAAM,MAAM,yBAAyB,GACjC,YAAY,GACZ,WAAW,GACX,WAAW,GACX,YAAY,GACZ,WAAW,GACX,YAAY,GACZ,YAAY,CAAA;AAEhB,eAAO,MAAM,iCAAiC;;;;;CAaF,CAAA;
|
|
1
|
+
{"version":3,"file":"contact.d.ts","sourceRoot":"","sources":["../src/contact.ts"],"names":[],"mappings":"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"}
|
package/dist/contact.js
CHANGED
|
@@ -14,7 +14,12 @@ export const DEFAULT_CONTACT_ATTACHMENT_POLICY = {
|
|
|
14
14
|
'image/heif',
|
|
15
15
|
],
|
|
16
16
|
};
|
|
17
|
+
export const DOCUMENT_CONTACT_ATTACHMENT_POLICY = {
|
|
18
|
+
...DEFAULT_CONTACT_ATTACHMENT_POLICY,
|
|
19
|
+
allowedTypes: [...DEFAULT_CONTACT_ATTACHMENT_POLICY.allowedTypes, 'application/pdf'],
|
|
20
|
+
};
|
|
17
21
|
const EXTENSION_TO_MIME = {
|
|
22
|
+
pdf: 'application/pdf',
|
|
18
23
|
jpg: 'image/jpeg',
|
|
19
24
|
jpeg: 'image/jpeg',
|
|
20
25
|
png: 'image/png',
|
|
@@ -23,30 +28,6 @@ const EXTENSION_TO_MIME = {
|
|
|
23
28
|
heic: 'image/heic',
|
|
24
29
|
heif: 'image/heif',
|
|
25
30
|
};
|
|
26
|
-
/** Selection stays compatible; HEIC/HEIF must be prepared as JPEG in the browser. */
|
|
27
|
-
export function deriveContactAttachmentTransportPolicy(selection = DEFAULT_CONTACT_ATTACHMENT_POLICY) {
|
|
28
|
-
const allowedTypes = selection.allowedTypes.flatMap((type) => {
|
|
29
|
-
if (['image/jpg', 'image/heic', 'image/heif'].includes(type))
|
|
30
|
-
return ['image/jpeg'];
|
|
31
|
-
return ['image/jpeg', 'image/png', 'image/webp', 'image/gif'].includes(type)
|
|
32
|
-
? [type] : [];
|
|
33
|
-
});
|
|
34
|
-
return { ...selection, allowedTypes: [...new Set(allowedTypes)] };
|
|
35
|
-
}
|
|
36
|
-
/** Positive raw-byte allowlist. No ISO-BMFF/HEIF-family input reaches native decoding. */
|
|
37
|
-
export function detectContactTransportAttachmentType(bytes) {
|
|
38
|
-
const starts = (signature) => signature.every((byte, index) => bytes[index] === byte);
|
|
39
|
-
const ascii = (start, length) => String.fromCharCode(...bytes.slice(start, start + length));
|
|
40
|
-
if (starts([0xff, 0xd8, 0xff]))
|
|
41
|
-
return 'image/jpeg';
|
|
42
|
-
if (starts([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
|
|
43
|
-
return 'image/png';
|
|
44
|
-
if (['GIF87a', 'GIF89a'].includes(ascii(0, 6)))
|
|
45
|
-
return 'image/gif';
|
|
46
|
-
if (ascii(0, 4) === 'RIFF' && ascii(8, 4) === 'WEBP')
|
|
47
|
-
return 'image/webp';
|
|
48
|
-
return undefined;
|
|
49
|
-
}
|
|
50
31
|
export function contactAttachmentAccept(policy = DEFAULT_CONTACT_ATTACHMENT_POLICY) {
|
|
51
32
|
return [...new Set(policy.allowedTypes)].join(',');
|
|
52
33
|
}
|
|
@@ -80,6 +61,8 @@ export function resolveContactAttachmentType(filename, fileType, policy = DEFAUL
|
|
|
80
61
|
return mappedType && isAllowedContactAttachmentType(mappedType, policy) ? mappedType : null;
|
|
81
62
|
}
|
|
82
63
|
export function createContactAttachmentFilename(options) {
|
|
64
|
+
if (options.contentType === 'application/pdf')
|
|
65
|
+
return `document-${options.index + 1}.pdf`;
|
|
83
66
|
return `image-${options.index + 1}.jpg`;
|
|
84
67
|
}
|
|
85
68
|
export function validateContactAttachment(file, policy = DEFAULT_CONTACT_ATTACHMENT_POLICY) {
|
|
@@ -362,7 +345,7 @@ export function defineContactContract(contract) {
|
|
|
362
345
|
assertContactContractKeys(contract, ['projectId', 'fieldProfile', 'fields', 'attachments', 'delivery'], 'Contact contract');
|
|
363
346
|
if (!contract.attachments ||
|
|
364
347
|
typeof contract.attachments !== 'object' ||
|
|
365
|
-
!['none', 'images-optional', 'images-required'].includes(contract.attachments.mode)) {
|
|
348
|
+
!['none', 'images-optional', 'images-required', 'images-and-pdfs-optional'].includes(contract.attachments.mode)) {
|
|
366
349
|
throw new Error('Contact contract attachment mode is invalid.');
|
|
367
350
|
}
|
|
368
351
|
assertContactContractKeys(contract.attachments, ['mode'], 'Contact contract attachments');
|
package/dist/contact.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contact.js","sourceRoot":"","sources":["../src/contact.ts"],"names":[],"mappings":"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","sourcesContent":["import { normalizeBrandlabRoutePath } from './routes.js'\nimport { assertBrandlabCanonicalEntityId } from './entity-id.js'\n\nexport interface ContactAttachmentPolicy {\n maxFiles: number\n maxFileSizeBytes: number\n maxTotalSizeBytes: number\n allowedTypes: readonly ContactAttachmentMimeType[]\n}\n\nexport type ContactAttachmentMimeType =\n | 'image/jpeg'\n | 'image/jpg'\n | 'image/png'\n | 'image/webp'\n | 'image/gif'\n | 'image/heic'\n | 'image/heif'\n\nexport const DEFAULT_CONTACT_ATTACHMENT_POLICY = {\n maxFiles: 5,\n maxFileSizeBytes: 4_000_000,\n maxTotalSizeBytes: 4_000_000,\n allowedTypes: [\n 'image/jpeg',\n 'image/jpg',\n 'image/png',\n 'image/webp',\n 'image/gif',\n 'image/heic',\n 'image/heif',\n ],\n} as const satisfies ContactAttachmentPolicy\n\nconst EXTENSION_TO_MIME: Record<string, ContactAttachmentMimeType> = {\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n heic: 'image/heic',\n heif: 'image/heif',\n}\n\nexport type ContactTransportAttachmentMimeType =\n | 'image/jpeg' | 'image/png' | 'image/webp' | 'image/gif'\n\n/** Selection stays compatible; HEIC/HEIF must be prepared as JPEG in the browser. */\nexport function deriveContactAttachmentTransportPolicy(\n selection: ContactAttachmentPolicy = DEFAULT_CONTACT_ATTACHMENT_POLICY,\n): ContactAttachmentPolicy {\n const allowedTypes = selection.allowedTypes.flatMap((type) => {\n if (['image/jpg', 'image/heic', 'image/heif'].includes(type)) return ['image/jpeg' as const]\n return ['image/jpeg', 'image/png', 'image/webp', 'image/gif'].includes(type)\n ? [type] : []\n })\n return { ...selection, allowedTypes: [...new Set(allowedTypes)] }\n}\n\n/** Positive raw-byte allowlist. No ISO-BMFF/HEIF-family input reaches native decoding. */\nexport function detectContactTransportAttachmentType(\n bytes: Uint8Array,\n): ContactTransportAttachmentMimeType | undefined {\n const starts = (signature: readonly number[]) =>\n signature.every((byte, index) => bytes[index] === byte)\n const ascii = (start: number, length: number) =>\n String.fromCharCode(...bytes.slice(start, start + length))\n if (starts([0xff, 0xd8, 0xff])) return 'image/jpeg'\n if (starts([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) return 'image/png'\n if (['GIF87a', 'GIF89a'].includes(ascii(0, 6))) return 'image/gif'\n if (ascii(0, 4) === 'RIFF' && ascii(8, 4) === 'WEBP') return 'image/webp'\n return undefined\n}\n\nexport function contactAttachmentAccept(\n policy: ContactAttachmentPolicy = DEFAULT_CONTACT_ATTACHMENT_POLICY,\n) {\n return [...new Set(policy.allowedTypes)].join(',')\n}\n\nexport function contactAttachmentTypeLabel(\n policy: ContactAttachmentPolicy = DEFAULT_CONTACT_ATTACHMENT_POLICY,\n) {\n const labels = policy.allowedTypes\n .map((type) => {\n const subtype = type.split('/')[1] ?? type\n if (subtype === 'jpeg' || subtype === 'jpg') return 'JPG'\n if (subtype === 'webp') return 'WebP'\n return subtype.toUpperCase()\n })\n .filter((label, index, labels) => labels.indexOf(label) === index)\n\n if (labels.length <= 1) return labels[0] ?? ''\n return `${labels.slice(0, -1).join(', ')}, or ${labels[labels.length - 1]}`\n}\n\nexport function isAllowedContactAttachmentType(\n value: string,\n policy: ContactAttachmentPolicy = DEFAULT_CONTACT_ATTACHMENT_POLICY,\n): value is ContactAttachmentMimeType {\n return policy.allowedTypes.includes(value as ContactAttachmentMimeType)\n}\n\nexport function resolveContactAttachmentType(\n filename: string,\n fileType?: string,\n policy: ContactAttachmentPolicy = DEFAULT_CONTACT_ATTACHMENT_POLICY,\n): ContactAttachmentMimeType | null {\n const normalizedType = fileType?.trim().toLowerCase()\n\n if (normalizedType && isAllowedContactAttachmentType(normalizedType, policy)) {\n return normalizedType\n }\n\n const extension = filename.toLowerCase().split('.').pop()\n if (!extension) return null\n\n const mappedType = EXTENSION_TO_MIME[extension]\n return mappedType && isAllowedContactAttachmentType(mappedType, policy) ? mappedType : null\n}\n\nexport function createContactAttachmentFilename(options: {\n index: number\n}) {\n return `image-${options.index + 1}.jpg`\n}\n\nexport function validateContactAttachment(\n file: { filename: string; size: number; type?: string },\n policy: ContactAttachmentPolicy = DEFAULT_CONTACT_ATTACHMENT_POLICY,\n):\n | { ok: true; contentType: ContactAttachmentMimeType }\n | { ok: false; message: string } {\n const contentType = resolveContactAttachmentType(file.filename, file.type, policy)\n\n if (!contentType) {\n return {\n ok: false,\n message: `\"${file.filename}\" is not a supported image type. Please upload ${contactAttachmentTypeLabel(\n policy,\n )} files.`,\n }\n }\n\n if (file.size > policy.maxFileSizeBytes) {\n const maximumMegabytes = Math.round((policy.maxFileSizeBytes / (1024 * 1024)) * 10) / 10\n return {\n ok: false,\n message: `Image \"${file.filename}\" is too large. Maximum size is ${maximumMegabytes} MB per image.`,\n }\n }\n\n return { ok: true, contentType }\n}\n\nexport type AdClickIds = {\n gclid?: string\n gbraid?: string\n wbraid?: string\n gadParams?: Record<string, string>\n}\n\nconst AD_CLICK_ID_FIELDS = [\n ['gclid', 'Google Click ID'],\n ['gbraid', 'GBRAID'],\n ['wbraid', 'WBRAID'],\n] as const\n\nexport function resolveTrackingSearchParams(queryParams: string, pageUrl = '') {\n const trimmedQueryParams = queryParams.trim()\n\n if (trimmedQueryParams) {\n return new URLSearchParams(\n trimmedQueryParams.startsWith('?') ? trimmedQueryParams : `?${trimmedQueryParams}`,\n )\n }\n\n if (pageUrl) {\n try {\n return new URL(pageUrl).searchParams\n } catch {\n return new URLSearchParams()\n }\n }\n\n return new URLSearchParams()\n}\n\nexport function resolveAdClickIds(queryParams: string, pageUrl = ''): AdClickIds {\n const params = resolveTrackingSearchParams(queryParams, pageUrl)\n const clickIds = AD_CLICK_ID_FIELDS.reduce<AdClickIds>((ids, [field]) => {\n const value = params.get(field)?.trim()\n\n if (value) {\n ids[field] = value\n }\n\n return ids\n }, {})\n const gadParams: Record<string, string> = {}\n\n params.forEach((value, key) => {\n const trimmedValue = value.trim()\n const normalizedKey = key.toLowerCase()\n\n if (trimmedValue && (normalizedKey === 'gad_source' || normalizedKey.startsWith('gad_'))) {\n gadParams[key] = trimmedValue\n }\n })\n\n return Object.keys(gadParams).length > 0 ? { ...clickIds, gadParams } : clickIds\n}\n\nexport function hasAdClickIds(adClickIds: AdClickIds) {\n return (\n AD_CLICK_ID_FIELDS.some(([field]) => Boolean(adClickIds[field])) ||\n Object.keys(adClickIds.gadParams ?? {}).length > 0\n )\n}\n\nexport function hasGoogleAdsClickId(queryParams: string, pageUrl = '') {\n return hasAdClickIds(resolveAdClickIds(queryParams, pageUrl))\n}\n\nfunction formatGadLabel(key: string) {\n return key\n .replace(/^gad_/, 'GAD ')\n .replace(/_/g, ' ')\n .replace(/campaignid\\b/i, 'campaign ID')\n .replace(/\\b\\w/g, (letter) => letter.toUpperCase())\n}\n\nexport function getAdClickIdRows(adClickIds: AdClickIds | undefined) {\n if (!adClickIds) return []\n\n const directRows = AD_CLICK_ID_FIELDS.flatMap(([field, label]) => {\n const value = adClickIds[field]\n return value ? [{ field, label, value }] : []\n })\n\n const gadRows = Object.entries(adClickIds.gadParams ?? {})\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([field, value]) => ({\n field,\n label: formatGadLabel(field),\n value,\n }))\n\n return [...directRows, ...gadRows]\n}\n\nexport function formatAdClickIdsForEmailText(adClickIds: AdClickIds) {\n return getAdClickIdRows(adClickIds)\n .map(({ label, value }) => `${label}: ${value}`)\n .join('\\n')\n}\n\nexport function parseContactEmailRecipients(value: string | undefined) {\n const recipients = value\n ?.split(',')\n .map((item) => item.trim())\n .filter(Boolean) ?? []\n const unique = recipients.filter((recipient, index) => {\n const address = recipient.match(/<([^<>]+)>$/)?.[1]?.trim() ?? recipient\n return recipients.findIndex((candidate) => {\n const candidateAddress = candidate.match(/<([^<>]+)>$/)?.[1]?.trim() ?? candidate\n return candidateAddress.toLowerCase() === address.toLowerCase()\n }) === index\n })\n if (unique.length > 5) {\n throw new Error('Contact delivery supports no more than five unique recipients.')\n }\n return unique\n}\n\nexport interface ContactFormEmailBaseData {\n name: string\n email: string\n phone: string\n message: string\n submittedAt: string\n submissionId: string\n pageLabel: string\n pagePath: string\n pageUrl: string\n trafficSource?: string\n initialLandingPath?: string\n initialLandingUrl?: string\n adClickIds?: AdClickIds\n attachmentCount?: number\n attachments?: readonly ContactFormEmailAttachmentSummary[]\n}\n\nexport type ContactFormEmailData = ContactFormEmailBaseData &\n ({ suburb: string; location?: string } | { location: string; suburb?: string })\n\nexport interface ContactFormEmailAttachmentSummary {\n filename: string\n contentType?: string\n sizeBytes?: number\n}\n\nexport interface ContactFormEmailRow {\n field: string\n label: string\n value: string\n}\n\nexport interface ContactFormEmailDataIssue {\n field: string\n message: string\n}\n\ntype ContactFormEmailValidationData = Partial<\n ContactFormEmailBaseData & {\n suburb: string\n location: string\n }\n>\n\nconst REQUIRED_CONTACT_FORM_EMAIL_LEAD_FIELDS = [\n 'name',\n 'email',\n 'phone',\n] as const\n\nconst REQUIRED_CONTACT_FORM_EMAIL_CONTEXT_FIELDS = [\n 'message',\n 'submittedAt',\n 'submissionId',\n 'pageLabel',\n 'pagePath',\n 'pageUrl',\n] as const\n\nfunction hasContactEmailValue(value: unknown) {\n return typeof value === 'string' ? value.trim().length > 0 : value != null\n}\n\nfunction contactEmailRow(field: string, label: string, value: unknown): ContactFormEmailRow[] {\n return hasContactEmailValue(value) ? [{ field, label, value: String(value) }] : []\n}\n\nexport function getContactFormEmailLocationValue(\n data: Pick<ContactFormEmailValidationData, 'location' | 'suburb'>,\n) {\n return data.suburb?.trim() || data.location?.trim() || ''\n}\n\nexport function validateContactFormEmailData(data: ContactFormEmailValidationData) {\n const issues: ContactFormEmailDataIssue[] = []\n\n REQUIRED_CONTACT_FORM_EMAIL_LEAD_FIELDS.forEach((field) => {\n if (!hasContactEmailValue(data[field])) {\n issues.push({ field, message: `Contact email data requires ${field}.` })\n }\n })\n\n if (!getContactFormEmailLocationValue(data)) {\n issues.push({\n field: 'suburbOrLocation',\n message: 'Contact email data requires a suburb or location.',\n })\n }\n\n REQUIRED_CONTACT_FORM_EMAIL_CONTEXT_FIELDS.forEach((field) => {\n if (!hasContactEmailValue(data[field])) {\n issues.push({ field, message: `Contact email data requires ${field}.` })\n }\n })\n\n return issues\n}\n\nexport function getContactFormEmailLeadRows(data: ContactFormEmailData) {\n const locationField = data.suburb ? 'suburb' : 'location'\n\n return [\n ...contactEmailRow('name', 'Name', data.name),\n ...contactEmailRow('email', 'Email', data.email),\n ...contactEmailRow('phone', 'Phone', data.phone),\n ...contactEmailRow(locationField, 'Location', getContactFormEmailLocationValue(data)),\n ...contactEmailRow('message', 'Message', data.message),\n ...contactEmailRow('submittedAt', 'Submitted at', data.submittedAt),\n ...contactEmailRow('submissionId', 'Submission ID', data.submissionId),\n ]\n}\n\nexport function formatContactFormEmailAttachmentSummary(\n data: Pick<ContactFormEmailBaseData, 'attachmentCount' | 'attachments'>,\n) {\n const attachments = data.attachments ?? []\n const attachmentCount = data.attachmentCount ?? attachments.length\n\n if (!attachmentCount) {\n return undefined\n }\n\n const countLabel = attachmentCount === 1 ? '1 file' : `${attachmentCount} files`\n // Original filenames may contain a person's name, address or device data.\n // Shared context rows retain only the operationally useful count.\n return countLabel\n}\n\nfunction privacySafeContextLabel(value: string | undefined) {\n const normalized = value?.trim()\n return normalized && normalized.length <= 160 && !/[\\u0000-\\u001f\\u007f]/.test(normalized)\n ? normalized\n : undefined\n}\n\nfunction privacySafeContextPath(value: string | undefined) {\n if (!value) return undefined\n try {\n return normalizeBrandlabRoutePath(value.split(/[?#]/, 1)[0] ?? '')\n } catch {\n return undefined\n }\n}\n\nfunction privacySafeContextUrl(value: string | undefined) {\n if (!value) return undefined\n try {\n const parsed = new URL(value)\n if (parsed.protocol !== 'https:' || parsed.username || parsed.password) return undefined\n const path = privacySafeContextPath(parsed.pathname)\n return path ? new URL(path, parsed.origin).toString() : undefined\n } catch {\n return undefined\n }\n}\n\nexport function getContactFormEmailContextRows(data: ContactFormEmailData) {\n const attachmentSummary = formatContactFormEmailAttachmentSummary(data)\n\n return [\n ...contactEmailRow('pageLabel', 'Page', privacySafeContextLabel(data.pageLabel)),\n ...contactEmailRow('pagePath', 'Page path', privacySafeContextPath(data.pagePath)),\n ...contactEmailRow('pageUrl', 'Page URL', privacySafeContextUrl(data.pageUrl)),\n ...contactEmailRow('attachments', 'Attachments', attachmentSummary),\n ]\n}\n\nconst AUSTRALIAN_PHONE_DIGITS = /^(?:0[23478]\\d{8}|61[23478]\\d{8})$/\n\nexport function normalizeAustralianPhoneDigits(phone: string) {\n return phone.replace(/[^\\d]/g, '')\n}\n\nexport function isValidAustralianPhoneNumber(phone: string) {\n return AUSTRALIAN_PHONE_DIGITS.test(normalizeAustralianPhoneDigits(phone))\n}\n\nexport const BRANDLAB_CONTACT_CONTRACT_VERSION = '4' as const\n\nexport const CONTACT_VISIT_SOURCES = [\n 'google-ads',\n 'google-business-profile',\n 'organic-search',\n 'referral',\n 'social',\n 'email-campaign',\n 'direct-or-unknown',\n] as const\n\nexport type ContactVisitSource = (typeof CONTACT_VISIT_SOURCES)[number]\n\nexport interface ContactAttributionSummary {\n initialLandingPath: string\n visitSource: ContactVisitSource\n visitSourceDetail?: string\n}\n\nexport function isContactVisitSource(value: unknown): value is ContactVisitSource {\n return CONTACT_VISIT_SOURCES.includes(value as ContactVisitSource)\n}\n\nexport function contactVisitSourceLabel(source: ContactVisitSource, detail?: string) {\n const labels: Record<ContactVisitSource, string> = {\n 'google-ads': 'Google Ads',\n 'google-business-profile': 'Google Business Profile',\n 'organic-search': 'Organic search',\n referral: 'Referral',\n social: 'Social',\n 'email-campaign': 'Email campaign',\n 'direct-or-unknown': 'Direct or unknown',\n }\n const label = labels[source]\n return detail && (source === 'referral' || source === 'social')\n ? `${label} — ${detail}`\n : label\n}\n\nexport type ContactFieldRole =\n | 'name'\n | 'email'\n | 'phone'\n | 'location'\n | 'message'\n | 'custom'\n\nexport type ContactFieldKind =\n | 'text'\n | 'email'\n | 'tel'\n | 'textarea'\n | 'select'\n | 'checkbox'\n\nexport type ContactFieldNormalization =\n | 'lowercase'\n | 'australian-phone-digits'\n\nexport type ContactPhoneRule = 'australian'\n\nexport interface ContactFieldContract {\n key: string\n role: ContactFieldRole\n kind: ContactFieldKind\n required: boolean\n /** Declarative limits remain shared instead of being recreated in projects. */\n maxLength?: number\n /** Required for select fields and prohibited for other field kinds. */\n options?: readonly string[]\n normalization?: ContactFieldNormalization\n /** Required for telephone fields so server validation has explicit semantics. */\n phoneRule?: ContactPhoneRule\n /** Required for checkbox fields; browsers submit this exact value when checked. */\n checkedValue?: string\n}\n\nexport interface BrandlabContactContract {\n projectId: string\n fieldProfile: string\n fields: readonly ContactFieldContract[]\n attachments: {\n mode: 'none' | 'images-optional' | 'images-required'\n }\n delivery: {\n recipientEnv: string\n /** Reviewed non-secret sender identity, for example Brand <noreply@example.com>. */\n sender: string\n }\n}\n\nexport type ContactActionResult =\n | {\n ok: true\n accepted: true\n trackConversion: true\n confirmationId: string\n }\n | {\n ok: true\n accepted: false\n trackConversion: false\n }\n | {\n ok: false\n code: 'validation' | 'attachment_rejected' | 'delivery_unavailable'\n retryable: boolean\n fieldErrors?: Record<string, string[]>\n }\n\nconst CONTACT_REQUIRED_ROLES = ['name', 'email', 'phone', 'message'] as const\nconst CONTACT_SINGLETON_ROLES = ['location'] as const\nconst ENV_KEY_PATTERN = /^[A-Z][A-Z0-9_]*$/\nconst CONTACT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\nconst CONTACT_FIELD_KEY_PATTERN = /^[a-z][A-Za-z0-9_-]{0,63}$/\nconst CONTACT_SENDER_PATTERN =\n /^(?:[^<>\\r\\n]{1,120}<)?[^\\s<>,@]+@[^\\s<>,@]+\\.[^\\s<>,@]+>?$/\nconst CONTACT_FIELD_ROLES = new Set<ContactFieldRole>([\n 'name', 'email', 'phone', 'location', 'message', 'custom',\n])\nconst CONTACT_FIELD_KINDS = new Set<ContactFieldKind>([\n 'text', 'email', 'tel', 'textarea', 'select', 'checkbox',\n])\nfunction assertContractText(value: string, field: string) {\n if (!value.trim()) throw new Error(`${field} cannot be empty.`)\n}\n\nfunction assertContactContractKeys(\n value: object,\n allowedKeys: readonly string[],\n field: string,\n) {\n const allowed = new Set(allowedKeys)\n const unsupported = Object.keys(value).filter((key) => !allowed.has(key))\n if (unsupported.length > 0) {\n throw new Error(`${field} contains unsupported keys: ${unsupported.join(', ')}.`)\n }\n}\n\nexport function defineContactContract<const T extends BrandlabContactContract>(contract: T): T {\n if (!contract || typeof contract !== 'object' || Array.isArray(contract)) {\n throw new Error('Contact contract must be an object.')\n }\n if (!Array.isArray(contract.fields)) {\n throw new Error('Contact contract fields must be an array.')\n }\n assertContactContractKeys(\n contract,\n ['projectId', 'fieldProfile', 'fields', 'attachments', 'delivery'],\n 'Contact contract',\n )\n if (\n !contract.attachments ||\n typeof contract.attachments !== 'object' ||\n !['none', 'images-optional', 'images-required'].includes(contract.attachments.mode)\n ) {\n throw new Error('Contact contract attachment mode is invalid.')\n }\n assertContactContractKeys(contract.attachments, ['mode'], 'Contact contract attachments')\n if (\n !contract.delivery ||\n typeof contract.delivery !== 'object' ||\n Array.isArray(contract.delivery)\n ) {\n throw new Error('Contact contract delivery environment is invalid.')\n }\n assertContactContractKeys(\n contract.delivery,\n ['recipientEnv', 'sender'],\n 'Contact contract delivery',\n )\n assertContractText(contract.projectId, 'projectId')\n assertContractText(contract.fieldProfile, 'fieldProfile')\n assertBrandlabCanonicalEntityId(contract.projectId)\n if (!CONTACT_ID_PATTERN.test(contract.fieldProfile)) {\n throw new Error('fieldProfile must be lowercase kebab-case.')\n }\n if (contract.fields.length < CONTACT_REQUIRED_ROLES.length || contract.fields.length > 20) {\n throw new Error('Contact contract must define between 4 and 20 fields.')\n }\n\n const duplicateKey = contract.fields.find((field, index) =>\n contract.fields.slice(0, index).some((candidate) => candidate.key === field.key),\n )\n if (duplicateKey) throw new Error(`Contact contract contains duplicate field key \"${duplicateKey.key}\".`)\n\n contract.fields.forEach((field, index) => {\n assertContactContractKeys(\n field,\n [\n 'key',\n 'role',\n 'kind',\n 'required',\n 'maxLength',\n 'options',\n 'normalization',\n 'phoneRule',\n 'checkedValue',\n ],\n `fields[${index}]`,\n )\n assertContractText(field.key, `fields[${index}].key`)\n if (!CONTACT_FIELD_KEY_PATTERN.test(field.key)) {\n throw new Error(`fields[${index}].key is invalid.`)\n }\n if (!CONTACT_FIELD_ROLES.has(field.role)) {\n throw new Error(`fields[${index}].role is invalid.`)\n }\n if (!CONTACT_FIELD_KINDS.has(field.kind)) {\n throw new Error(`fields[${index}].kind is invalid.`)\n }\n if (typeof field.required !== 'boolean') {\n throw new Error(`fields[${index}].required must be boolean.`)\n }\n if (\n field.maxLength !== undefined &&\n (!Number.isSafeInteger(field.maxLength) || field.maxLength < 1 || field.maxLength > 10_000)\n ) {\n throw new Error(`fields[${index}].maxLength is invalid.`)\n }\n if (field.kind === 'select') {\n if (\n !Array.isArray(field.options) ||\n field.options.length === 0 ||\n field.options.length > 100 ||\n field.options.some(\n (option: unknown) =>\n typeof option !== 'string' ||\n option.trim() !== option ||\n !option ||\n option.length > 200,\n ) ||\n new Set(field.options).size !== field.options.length\n ) {\n throw new Error(`fields[${index}].options must be a bounded unique select allowlist.`)\n }\n } else if (field.options !== undefined) {\n throw new Error(`fields[${index}].options is supported only for select fields.`)\n }\n if (\n field.normalization !== undefined &&\n !['lowercase', 'australian-phone-digits'].includes(field.normalization)\n ) {\n throw new Error(`fields[${index}].normalization is unsupported.`)\n }\n if (\n field.normalization === 'australian-phone-digits' &&\n field.kind !== 'tel'\n ) {\n throw new Error(`fields[${index}] uses phone normalization without a tel kind.`)\n }\n if (field.kind === 'tel') {\n if (field.phoneRule !== 'australian') {\n throw new Error(`fields[${index}].phoneRule must declare Australian phone semantics.`)\n }\n if (field.normalization !== 'australian-phone-digits') {\n throw new Error(`fields[${index}].normalization must use Australian phone digits.`)\n }\n } else if (field.phoneRule !== undefined) {\n throw new Error(`fields[${index}].phoneRule is supported only for tel fields.`)\n }\n if (field.kind === 'email' && field.normalization !== 'lowercase') {\n throw new Error(`fields[${index}].normalization must lowercase email values.`)\n }\n if (field.kind === 'checkbox') {\n if (\n typeof field.checkedValue !== 'string' ||\n !field.checkedValue ||\n field.checkedValue.trim() !== field.checkedValue ||\n field.checkedValue.length > 100\n ) {\n throw new Error(`fields[${index}].checkedValue is required for checkbox fields.`)\n }\n } else if (field.checkedValue !== undefined) {\n throw new Error(`fields[${index}].checkedValue is supported only for checkbox fields.`)\n }\n })\n\n CONTACT_REQUIRED_ROLES.forEach((role) => {\n const matches = contract.fields.filter((field) => field.role === role)\n if (matches.length !== 1 || !matches[0]?.required) {\n throw new Error(`Contact contract requires exactly one required \"${role}\" field.`)\n }\n })\n CONTACT_SINGLETON_ROLES.forEach((role) => {\n if (contract.fields.filter((field) => field.role === role).length > 1) {\n throw new Error(`Contact contract allows at most one \"${role}\" field.`)\n }\n })\n\n const roleKinds: Partial<Record<ContactFieldRole, readonly ContactFieldKind[]>> = {\n name: ['text'],\n email: ['email'],\n phone: ['tel'],\n location: ['text', 'select'],\n message: ['textarea'],\n }\n contract.fields.forEach((field) => {\n const role = field.role as ContactFieldRole\n const kind = field.kind as ContactFieldKind\n const allowedKinds = roleKinds[role]\n if (allowedKinds && !allowedKinds.includes(kind)) {\n throw new Error(`Contact field \"${field.key}\" has an invalid kind for role \"${field.role}\".`)\n }\n })\n\n if (!ENV_KEY_PATTERN.test(contract.delivery.recipientEnv)) {\n throw new Error('delivery.recipientEnv must be an environment variable key.')\n }\n if (\n !CONTACT_SENDER_PATTERN.test(contract.delivery.sender) ||\n contract.delivery.sender.length > 320 ||\n /[\\u0000-\\u001f\\u007f]/.test(contract.delivery.sender)\n ) {\n throw new Error('delivery.sender must be a reviewed email sender.')\n }\n\n return contract\n}\n"]}
|
|
1
|
+
{"version":3,"file":"contact.js","sourceRoot":"","sources":["../src/contact.ts"],"names":[],"mappings":"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","sourcesContent":["import { normalizeBrandlabRoutePath } from './routes.js'\nimport { assertBrandlabCanonicalEntityId } from './entity-id.js'\n\nexport interface ContactAttachmentPolicy {\n maxFiles: number\n maxFileSizeBytes: number\n maxTotalSizeBytes: number\n allowedTypes: readonly ContactAttachmentMimeType[]\n}\n\nexport type ContactAttachmentMimeType =\n | 'application/pdf'\n | 'image/jpeg'\n | 'image/jpg'\n | 'image/png'\n | 'image/webp'\n | 'image/gif'\n | 'image/heic'\n | 'image/heif'\n\nexport const DEFAULT_CONTACT_ATTACHMENT_POLICY = {\n maxFiles: 5,\n maxFileSizeBytes: 4_000_000,\n maxTotalSizeBytes: 4_000_000,\n allowedTypes: [\n 'image/jpeg',\n 'image/jpg',\n 'image/png',\n 'image/webp',\n 'image/gif',\n 'image/heic',\n 'image/heif',\n ],\n} as const satisfies ContactAttachmentPolicy\n\nexport const DOCUMENT_CONTACT_ATTACHMENT_POLICY = {\n ...DEFAULT_CONTACT_ATTACHMENT_POLICY,\n allowedTypes: [...DEFAULT_CONTACT_ATTACHMENT_POLICY.allowedTypes, 'application/pdf'],\n} as const satisfies ContactAttachmentPolicy\n\nconst EXTENSION_TO_MIME: Record<string, ContactAttachmentMimeType> = {\n pdf: 'application/pdf',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n heic: 'image/heic',\n heif: 'image/heif',\n}\n\nexport function contactAttachmentAccept(\n policy: ContactAttachmentPolicy = DEFAULT_CONTACT_ATTACHMENT_POLICY,\n) {\n return [...new Set(policy.allowedTypes)].join(',')\n}\n\nexport function contactAttachmentTypeLabel(\n policy: ContactAttachmentPolicy = DEFAULT_CONTACT_ATTACHMENT_POLICY,\n) {\n const labels = policy.allowedTypes\n .map((type) => {\n const subtype = type.split('/')[1] ?? type\n if (subtype === 'jpeg' || subtype === 'jpg') return 'JPG'\n if (subtype === 'webp') return 'WebP'\n return subtype.toUpperCase()\n })\n .filter((label, index, labels) => labels.indexOf(label) === index)\n\n if (labels.length <= 1) return labels[0] ?? ''\n return `${labels.slice(0, -1).join(', ')}, or ${labels[labels.length - 1]}`\n}\n\nexport function isAllowedContactAttachmentType(\n value: string,\n policy: ContactAttachmentPolicy = DEFAULT_CONTACT_ATTACHMENT_POLICY,\n): value is ContactAttachmentMimeType {\n return policy.allowedTypes.includes(value as ContactAttachmentMimeType)\n}\n\nexport function resolveContactAttachmentType(\n filename: string,\n fileType?: string,\n policy: ContactAttachmentPolicy = DEFAULT_CONTACT_ATTACHMENT_POLICY,\n): ContactAttachmentMimeType | null {\n const normalizedType = fileType?.trim().toLowerCase()\n\n if (normalizedType && isAllowedContactAttachmentType(normalizedType, policy)) {\n return normalizedType\n }\n\n const extension = filename.toLowerCase().split('.').pop()\n if (!extension) return null\n\n const mappedType = EXTENSION_TO_MIME[extension]\n return mappedType && isAllowedContactAttachmentType(mappedType, policy) ? mappedType : null\n}\n\nexport function createContactAttachmentFilename(options: {\n index: number\n contentType?: string\n}) {\n if (options.contentType === 'application/pdf') return `document-${options.index + 1}.pdf`\n return `image-${options.index + 1}.jpg`\n}\n\nexport function validateContactAttachment(\n file: { filename: string; size: number; type?: string },\n policy: ContactAttachmentPolicy = DEFAULT_CONTACT_ATTACHMENT_POLICY,\n):\n | { ok: true; contentType: ContactAttachmentMimeType }\n | { ok: false; message: string } {\n const contentType = resolveContactAttachmentType(file.filename, file.type, policy)\n\n if (!contentType) {\n return {\n ok: false,\n message: `\"${file.filename}\" is not a supported image type. Please upload ${contactAttachmentTypeLabel(\n policy,\n )} files.`,\n }\n }\n\n if (file.size > policy.maxFileSizeBytes) {\n const maximumMegabytes = Math.round((policy.maxFileSizeBytes / (1024 * 1024)) * 10) / 10\n return {\n ok: false,\n message: `Image \"${file.filename}\" is too large. Maximum size is ${maximumMegabytes} MB per image.`,\n }\n }\n\n return { ok: true, contentType }\n}\n\nexport type AdClickIds = {\n gclid?: string\n gbraid?: string\n wbraid?: string\n gadParams?: Record<string, string>\n}\n\nconst AD_CLICK_ID_FIELDS = [\n ['gclid', 'Google Click ID'],\n ['gbraid', 'GBRAID'],\n ['wbraid', 'WBRAID'],\n] as const\n\nexport function resolveTrackingSearchParams(queryParams: string, pageUrl = '') {\n const trimmedQueryParams = queryParams.trim()\n\n if (trimmedQueryParams) {\n return new URLSearchParams(\n trimmedQueryParams.startsWith('?') ? trimmedQueryParams : `?${trimmedQueryParams}`,\n )\n }\n\n if (pageUrl) {\n try {\n return new URL(pageUrl).searchParams\n } catch {\n return new URLSearchParams()\n }\n }\n\n return new URLSearchParams()\n}\n\nexport function resolveAdClickIds(queryParams: string, pageUrl = ''): AdClickIds {\n const params = resolveTrackingSearchParams(queryParams, pageUrl)\n const clickIds = AD_CLICK_ID_FIELDS.reduce<AdClickIds>((ids, [field]) => {\n const value = params.get(field)?.trim()\n\n if (value) {\n ids[field] = value\n }\n\n return ids\n }, {})\n const gadParams: Record<string, string> = {}\n\n params.forEach((value, key) => {\n const trimmedValue = value.trim()\n const normalizedKey = key.toLowerCase()\n\n if (trimmedValue && (normalizedKey === 'gad_source' || normalizedKey.startsWith('gad_'))) {\n gadParams[key] = trimmedValue\n }\n })\n\n return Object.keys(gadParams).length > 0 ? { ...clickIds, gadParams } : clickIds\n}\n\nexport function hasAdClickIds(adClickIds: AdClickIds) {\n return (\n AD_CLICK_ID_FIELDS.some(([field]) => Boolean(adClickIds[field])) ||\n Object.keys(adClickIds.gadParams ?? {}).length > 0\n )\n}\n\nexport function hasGoogleAdsClickId(queryParams: string, pageUrl = '') {\n return hasAdClickIds(resolveAdClickIds(queryParams, pageUrl))\n}\n\nfunction formatGadLabel(key: string) {\n return key\n .replace(/^gad_/, 'GAD ')\n .replace(/_/g, ' ')\n .replace(/campaignid\\b/i, 'campaign ID')\n .replace(/\\b\\w/g, (letter) => letter.toUpperCase())\n}\n\nexport function getAdClickIdRows(adClickIds: AdClickIds | undefined) {\n if (!adClickIds) return []\n\n const directRows = AD_CLICK_ID_FIELDS.flatMap(([field, label]) => {\n const value = adClickIds[field]\n return value ? [{ field, label, value }] : []\n })\n\n const gadRows = Object.entries(adClickIds.gadParams ?? {})\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([field, value]) => ({\n field,\n label: formatGadLabel(field),\n value,\n }))\n\n return [...directRows, ...gadRows]\n}\n\nexport function formatAdClickIdsForEmailText(adClickIds: AdClickIds) {\n return getAdClickIdRows(adClickIds)\n .map(({ label, value }) => `${label}: ${value}`)\n .join('\\n')\n}\n\nexport function parseContactEmailRecipients(value: string | undefined) {\n const recipients = value\n ?.split(',')\n .map((item) => item.trim())\n .filter(Boolean) ?? []\n const unique = recipients.filter((recipient, index) => {\n const address = recipient.match(/<([^<>]+)>$/)?.[1]?.trim() ?? recipient\n return recipients.findIndex((candidate) => {\n const candidateAddress = candidate.match(/<([^<>]+)>$/)?.[1]?.trim() ?? candidate\n return candidateAddress.toLowerCase() === address.toLowerCase()\n }) === index\n })\n if (unique.length > 5) {\n throw new Error('Contact delivery supports no more than five unique recipients.')\n }\n return unique\n}\n\nexport interface ContactFormEmailBaseData {\n name: string\n email: string\n phone: string\n message: string\n submittedAt: string\n submissionId: string\n pageLabel: string\n pagePath: string\n pageUrl: string\n trafficSource?: string\n initialLandingPath?: string\n initialLandingUrl?: string\n adClickIds?: AdClickIds\n attachmentCount?: number\n attachments?: readonly ContactFormEmailAttachmentSummary[]\n}\n\nexport type ContactFormEmailData = ContactFormEmailBaseData &\n ({ suburb: string; location?: string } | { location: string; suburb?: string })\n\nexport interface ContactFormEmailAttachmentSummary {\n filename: string\n contentType?: string\n sizeBytes?: number\n}\n\nexport interface ContactFormEmailRow {\n field: string\n label: string\n value: string\n}\n\nexport interface ContactFormEmailDataIssue {\n field: string\n message: string\n}\n\ntype ContactFormEmailValidationData = Partial<\n ContactFormEmailBaseData & {\n suburb: string\n location: string\n }\n>\n\nconst REQUIRED_CONTACT_FORM_EMAIL_LEAD_FIELDS = [\n 'name',\n 'email',\n 'phone',\n] as const\n\nconst REQUIRED_CONTACT_FORM_EMAIL_CONTEXT_FIELDS = [\n 'message',\n 'submittedAt',\n 'submissionId',\n 'pageLabel',\n 'pagePath',\n 'pageUrl',\n] as const\n\nfunction hasContactEmailValue(value: unknown) {\n return typeof value === 'string' ? value.trim().length > 0 : value != null\n}\n\nfunction contactEmailRow(field: string, label: string, value: unknown): ContactFormEmailRow[] {\n return hasContactEmailValue(value) ? [{ field, label, value: String(value) }] : []\n}\n\nexport function getContactFormEmailLocationValue(\n data: Pick<ContactFormEmailValidationData, 'location' | 'suburb'>,\n) {\n return data.suburb?.trim() || data.location?.trim() || ''\n}\n\nexport function validateContactFormEmailData(data: ContactFormEmailValidationData) {\n const issues: ContactFormEmailDataIssue[] = []\n\n REQUIRED_CONTACT_FORM_EMAIL_LEAD_FIELDS.forEach((field) => {\n if (!hasContactEmailValue(data[field])) {\n issues.push({ field, message: `Contact email data requires ${field}.` })\n }\n })\n\n if (!getContactFormEmailLocationValue(data)) {\n issues.push({\n field: 'suburbOrLocation',\n message: 'Contact email data requires a suburb or location.',\n })\n }\n\n REQUIRED_CONTACT_FORM_EMAIL_CONTEXT_FIELDS.forEach((field) => {\n if (!hasContactEmailValue(data[field])) {\n issues.push({ field, message: `Contact email data requires ${field}.` })\n }\n })\n\n return issues\n}\n\nexport function getContactFormEmailLeadRows(data: ContactFormEmailData) {\n const locationField = data.suburb ? 'suburb' : 'location'\n\n return [\n ...contactEmailRow('name', 'Name', data.name),\n ...contactEmailRow('email', 'Email', data.email),\n ...contactEmailRow('phone', 'Phone', data.phone),\n ...contactEmailRow(locationField, 'Location', getContactFormEmailLocationValue(data)),\n ...contactEmailRow('message', 'Message', data.message),\n ...contactEmailRow('submittedAt', 'Submitted at', data.submittedAt),\n ...contactEmailRow('submissionId', 'Submission ID', data.submissionId),\n ]\n}\n\nexport function formatContactFormEmailAttachmentSummary(\n data: Pick<ContactFormEmailBaseData, 'attachmentCount' | 'attachments'>,\n) {\n const attachments = data.attachments ?? []\n const attachmentCount = data.attachmentCount ?? attachments.length\n\n if (!attachmentCount) {\n return undefined\n }\n\n const countLabel = attachmentCount === 1 ? '1 file' : `${attachmentCount} files`\n // Original filenames may contain a person's name, address or device data.\n // Shared context rows retain only the operationally useful count.\n return countLabel\n}\n\nfunction privacySafeContextLabel(value: string | undefined) {\n const normalized = value?.trim()\n return normalized && normalized.length <= 160 && !/[\\u0000-\\u001f\\u007f]/.test(normalized)\n ? normalized\n : undefined\n}\n\nfunction privacySafeContextPath(value: string | undefined) {\n if (!value) return undefined\n try {\n return normalizeBrandlabRoutePath(value.split(/[?#]/, 1)[0] ?? '')\n } catch {\n return undefined\n }\n}\n\nfunction privacySafeContextUrl(value: string | undefined) {\n if (!value) return undefined\n try {\n const parsed = new URL(value)\n if (parsed.protocol !== 'https:' || parsed.username || parsed.password) return undefined\n const path = privacySafeContextPath(parsed.pathname)\n return path ? new URL(path, parsed.origin).toString() : undefined\n } catch {\n return undefined\n }\n}\n\nexport function getContactFormEmailContextRows(data: ContactFormEmailData) {\n const attachmentSummary = formatContactFormEmailAttachmentSummary(data)\n\n return [\n ...contactEmailRow('pageLabel', 'Page', privacySafeContextLabel(data.pageLabel)),\n ...contactEmailRow('pagePath', 'Page path', privacySafeContextPath(data.pagePath)),\n ...contactEmailRow('pageUrl', 'Page URL', privacySafeContextUrl(data.pageUrl)),\n ...contactEmailRow('attachments', 'Attachments', attachmentSummary),\n ]\n}\n\nconst AUSTRALIAN_PHONE_DIGITS = /^(?:0[23478]\\d{8}|61[23478]\\d{8})$/\n\nexport function normalizeAustralianPhoneDigits(phone: string) {\n return phone.replace(/[^\\d]/g, '')\n}\n\nexport function isValidAustralianPhoneNumber(phone: string) {\n return AUSTRALIAN_PHONE_DIGITS.test(normalizeAustralianPhoneDigits(phone))\n}\n\nexport const BRANDLAB_CONTACT_CONTRACT_VERSION = '4' as const\n\nexport const CONTACT_VISIT_SOURCES = [\n 'google-ads',\n 'google-business-profile',\n 'organic-search',\n 'referral',\n 'social',\n 'email-campaign',\n 'direct-or-unknown',\n] as const\n\nexport type ContactVisitSource = (typeof CONTACT_VISIT_SOURCES)[number]\n\nexport interface ContactAttributionSummary {\n initialLandingPath: string\n visitSource: ContactVisitSource\n visitSourceDetail?: string\n}\n\nexport function isContactVisitSource(value: unknown): value is ContactVisitSource {\n return CONTACT_VISIT_SOURCES.includes(value as ContactVisitSource)\n}\n\nexport function contactVisitSourceLabel(source: ContactVisitSource, detail?: string) {\n const labels: Record<ContactVisitSource, string> = {\n 'google-ads': 'Google Ads',\n 'google-business-profile': 'Google Business Profile',\n 'organic-search': 'Organic search',\n referral: 'Referral',\n social: 'Social',\n 'email-campaign': 'Email campaign',\n 'direct-or-unknown': 'Direct or unknown',\n }\n const label = labels[source]\n return detail && (source === 'referral' || source === 'social')\n ? `${label} — ${detail}`\n : label\n}\n\nexport type ContactFieldRole =\n | 'name'\n | 'email'\n | 'phone'\n | 'location'\n | 'message'\n | 'custom'\n\nexport type ContactFieldKind =\n | 'text'\n | 'email'\n | 'tel'\n | 'textarea'\n | 'select'\n | 'checkbox'\n\nexport type ContactFieldNormalization =\n | 'lowercase'\n | 'australian-phone-digits'\n\nexport type ContactPhoneRule = 'australian'\n\nexport interface ContactFieldContract {\n key: string\n role: ContactFieldRole\n kind: ContactFieldKind\n required: boolean\n /** Declarative limits remain shared instead of being recreated in projects. */\n maxLength?: number\n /** Required for select fields and prohibited for other field kinds. */\n options?: readonly string[]\n normalization?: ContactFieldNormalization\n /** Required for telephone fields so server validation has explicit semantics. */\n phoneRule?: ContactPhoneRule\n /** Required for checkbox fields; browsers submit this exact value when checked. */\n checkedValue?: string\n}\n\nexport interface BrandlabContactContract {\n projectId: string\n fieldProfile: string\n fields: readonly ContactFieldContract[]\n attachments: {\n mode: 'none' | 'images-optional' | 'images-required' | 'images-and-pdfs-optional'\n }\n delivery: {\n recipientEnv: string\n /** Reviewed non-secret sender identity, for example Brand <noreply@example.com>. */\n sender: string\n }\n}\n\nexport type ContactActionResult =\n | {\n ok: true\n accepted: true\n trackConversion: true\n confirmationId: string\n }\n | {\n ok: true\n accepted: false\n trackConversion: false\n }\n | {\n ok: false\n code: 'validation' | 'attachment_rejected' | 'delivery_unavailable'\n retryable: boolean\n fieldErrors?: Record<string, string[]>\n }\n\nconst CONTACT_REQUIRED_ROLES = ['name', 'email', 'phone', 'message'] as const\nconst CONTACT_SINGLETON_ROLES = ['location'] as const\nconst ENV_KEY_PATTERN = /^[A-Z][A-Z0-9_]*$/\nconst CONTACT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\nconst CONTACT_FIELD_KEY_PATTERN = /^[a-z][A-Za-z0-9_-]{0,63}$/\nconst CONTACT_SENDER_PATTERN =\n /^(?:[^<>\\r\\n]{1,120}<)?[^\\s<>,@]+@[^\\s<>,@]+\\.[^\\s<>,@]+>?$/\nconst CONTACT_FIELD_ROLES = new Set<ContactFieldRole>([\n 'name', 'email', 'phone', 'location', 'message', 'custom',\n])\nconst CONTACT_FIELD_KINDS = new Set<ContactFieldKind>([\n 'text', 'email', 'tel', 'textarea', 'select', 'checkbox',\n])\nfunction assertContractText(value: string, field: string) {\n if (!value.trim()) throw new Error(`${field} cannot be empty.`)\n}\n\nfunction assertContactContractKeys(\n value: object,\n allowedKeys: readonly string[],\n field: string,\n) {\n const allowed = new Set(allowedKeys)\n const unsupported = Object.keys(value).filter((key) => !allowed.has(key))\n if (unsupported.length > 0) {\n throw new Error(`${field} contains unsupported keys: ${unsupported.join(', ')}.`)\n }\n}\n\nexport function defineContactContract<const T extends BrandlabContactContract>(contract: T): T {\n if (!contract || typeof contract !== 'object' || Array.isArray(contract)) {\n throw new Error('Contact contract must be an object.')\n }\n if (!Array.isArray(contract.fields)) {\n throw new Error('Contact contract fields must be an array.')\n }\n assertContactContractKeys(\n contract,\n ['projectId', 'fieldProfile', 'fields', 'attachments', 'delivery'],\n 'Contact contract',\n )\n if (\n !contract.attachments ||\n typeof contract.attachments !== 'object' ||\n !['none', 'images-optional', 'images-required', 'images-and-pdfs-optional'].includes(contract.attachments.mode)\n ) {\n throw new Error('Contact contract attachment mode is invalid.')\n }\n assertContactContractKeys(contract.attachments, ['mode'], 'Contact contract attachments')\n if (\n !contract.delivery ||\n typeof contract.delivery !== 'object' ||\n Array.isArray(contract.delivery)\n ) {\n throw new Error('Contact contract delivery environment is invalid.')\n }\n assertContactContractKeys(\n contract.delivery,\n ['recipientEnv', 'sender'],\n 'Contact contract delivery',\n )\n assertContractText(contract.projectId, 'projectId')\n assertContractText(contract.fieldProfile, 'fieldProfile')\n assertBrandlabCanonicalEntityId(contract.projectId)\n if (!CONTACT_ID_PATTERN.test(contract.fieldProfile)) {\n throw new Error('fieldProfile must be lowercase kebab-case.')\n }\n if (contract.fields.length < CONTACT_REQUIRED_ROLES.length || contract.fields.length > 20) {\n throw new Error('Contact contract must define between 4 and 20 fields.')\n }\n\n const duplicateKey = contract.fields.find((field, index) =>\n contract.fields.slice(0, index).some((candidate) => candidate.key === field.key),\n )\n if (duplicateKey) throw new Error(`Contact contract contains duplicate field key \"${duplicateKey.key}\".`)\n\n contract.fields.forEach((field, index) => {\n assertContactContractKeys(\n field,\n [\n 'key',\n 'role',\n 'kind',\n 'required',\n 'maxLength',\n 'options',\n 'normalization',\n 'phoneRule',\n 'checkedValue',\n ],\n `fields[${index}]`,\n )\n assertContractText(field.key, `fields[${index}].key`)\n if (!CONTACT_FIELD_KEY_PATTERN.test(field.key)) {\n throw new Error(`fields[${index}].key is invalid.`)\n }\n if (!CONTACT_FIELD_ROLES.has(field.role)) {\n throw new Error(`fields[${index}].role is invalid.`)\n }\n if (!CONTACT_FIELD_KINDS.has(field.kind)) {\n throw new Error(`fields[${index}].kind is invalid.`)\n }\n if (typeof field.required !== 'boolean') {\n throw new Error(`fields[${index}].required must be boolean.`)\n }\n if (\n field.maxLength !== undefined &&\n (!Number.isSafeInteger(field.maxLength) || field.maxLength < 1 || field.maxLength > 10_000)\n ) {\n throw new Error(`fields[${index}].maxLength is invalid.`)\n }\n if (field.kind === 'select') {\n if (\n !Array.isArray(field.options) ||\n field.options.length === 0 ||\n field.options.length > 100 ||\n field.options.some(\n (option: unknown) =>\n typeof option !== 'string' ||\n option.trim() !== option ||\n !option ||\n option.length > 200,\n ) ||\n new Set(field.options).size !== field.options.length\n ) {\n throw new Error(`fields[${index}].options must be a bounded unique select allowlist.`)\n }\n } else if (field.options !== undefined) {\n throw new Error(`fields[${index}].options is supported only for select fields.`)\n }\n if (\n field.normalization !== undefined &&\n !['lowercase', 'australian-phone-digits'].includes(field.normalization)\n ) {\n throw new Error(`fields[${index}].normalization is unsupported.`)\n }\n if (\n field.normalization === 'australian-phone-digits' &&\n field.kind !== 'tel'\n ) {\n throw new Error(`fields[${index}] uses phone normalization without a tel kind.`)\n }\n if (field.kind === 'tel') {\n if (field.phoneRule !== 'australian') {\n throw new Error(`fields[${index}].phoneRule must declare Australian phone semantics.`)\n }\n if (field.normalization !== 'australian-phone-digits') {\n throw new Error(`fields[${index}].normalization must use Australian phone digits.`)\n }\n } else if (field.phoneRule !== undefined) {\n throw new Error(`fields[${index}].phoneRule is supported only for tel fields.`)\n }\n if (field.kind === 'email' && field.normalization !== 'lowercase') {\n throw new Error(`fields[${index}].normalization must lowercase email values.`)\n }\n if (field.kind === 'checkbox') {\n if (\n typeof field.checkedValue !== 'string' ||\n !field.checkedValue ||\n field.checkedValue.trim() !== field.checkedValue ||\n field.checkedValue.length > 100\n ) {\n throw new Error(`fields[${index}].checkedValue is required for checkbox fields.`)\n }\n } else if (field.checkedValue !== undefined) {\n throw new Error(`fields[${index}].checkedValue is supported only for checkbox fields.`)\n }\n })\n\n CONTACT_REQUIRED_ROLES.forEach((role) => {\n const matches = contract.fields.filter((field) => field.role === role)\n if (matches.length !== 1 || !matches[0]?.required) {\n throw new Error(`Contact contract requires exactly one required \"${role}\" field.`)\n }\n })\n CONTACT_SINGLETON_ROLES.forEach((role) => {\n if (contract.fields.filter((field) => field.role === role).length > 1) {\n throw new Error(`Contact contract allows at most one \"${role}\" field.`)\n }\n })\n\n const roleKinds: Partial<Record<ContactFieldRole, readonly ContactFieldKind[]>> = {\n name: ['text'],\n email: ['email'],\n phone: ['tel'],\n location: ['text', 'select'],\n message: ['textarea'],\n }\n contract.fields.forEach((field) => {\n const role = field.role as ContactFieldRole\n const kind = field.kind as ContactFieldKind\n const allowedKinds = roleKinds[role]\n if (allowedKinds && !allowedKinds.includes(kind)) {\n throw new Error(`Contact field \"${field.key}\" has an invalid kind for role \"${field.role}\".`)\n }\n })\n\n if (!ENV_KEY_PATTERN.test(contract.delivery.recipientEnv)) {\n throw new Error('delivery.recipientEnv must be an environment variable key.')\n }\n if (\n !CONTACT_SENDER_PATTERN.test(contract.delivery.sender) ||\n contract.delivery.sender.length > 320 ||\n /[\\u0000-\\u001f\\u007f]/.test(contract.delivery.sender)\n ) {\n throw new Error('delivery.sender must be a reviewed email sender.')\n }\n\n return contract\n}\n"]}
|
package/dist/project.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { GooglePlaceReviewsSelection } from './google-reviews.js';
|
|
|
2
2
|
export type BrandlabProjectProfile = 'next-local-service' | 'next-root-level';
|
|
3
3
|
export type BrandlabRouteFamily = 'services' | 'locations' | 'service-locations' | 'blog' | 'projects' | 'legal';
|
|
4
4
|
export type BrandlabBlogAdapter = 'none' | 'mdx' | 'structured';
|
|
5
|
-
export type BrandlabContactAttachmentMode = 'none' | 'images-optional' | 'images-required';
|
|
5
|
+
export type BrandlabContactAttachmentMode = 'none' | 'images-optional' | 'images-required' | 'images-and-pdfs-optional';
|
|
6
6
|
export type BrandlabLocationStrategy = 'none' | 'approved-service-area' | 'physical-location' | 'multi-location';
|
|
7
7
|
export type BrandlabReviewsProvider = 'google-places' | 'project-owned';
|
|
8
8
|
export type BrandlabGoogleReviewsMode = 'link-only' | 'summary' | 'full';
|
package/dist/project.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAA;AAEtE,MAAM,MAAM,sBAAsB,GAC9B,oBAAoB,GACpB,iBAAiB,CAAA;AAErB,MAAM,MAAM,mBAAmB,GAC3B,UAAU,GACV,WAAW,GACX,mBAAmB,GACnB,MAAM,GACN,UAAU,GACV,OAAO,CAAA;AAEX,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,KAAK,GAAG,YAAY,CAAA;AAC/D,MAAM,MAAM,6BAA6B,GACrC,MAAM,GACN,iBAAiB,GACjB,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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"}
|
package/dist/project.js
CHANGED
package/dist/project.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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","sourcesContent":["import { normalizeBrandlabRoutePath } from './routes.js'\nimport { assertBrandlabCanonicalEntityId } from './entity-id.js'\nimport type { GooglePlaceReviewsSelection } from './google-reviews.js'\n\nexport type BrandlabProjectProfile =\n | 'next-local-service'\n | 'next-root-level'\n\nexport type BrandlabRouteFamily =\n | 'services'\n | 'locations'\n | 'service-locations'\n | 'blog'\n | 'projects'\n | 'legal'\n\nexport type BrandlabBlogAdapter = 'none' | 'mdx' | 'structured'\nexport type BrandlabContactAttachmentMode =\n | 'none'\n | 'images-optional'\n | 'images-required'\nexport type BrandlabLocationStrategy =\n | 'none'\n | 'approved-service-area'\n | 'physical-location'\n | 'multi-location'\nexport type BrandlabReviewsProvider = 'google-places' | 'project-owned'\nexport type BrandlabGoogleReviewsMode = 'link-only' | 'summary' | 'full'\n\nexport type BrandlabReviewsCapability =\n | { provider: 'project-owned' }\n | {\n provider: 'google-places'\n mode: BrandlabGoogleReviewsMode\n /** Optional presentation policy. Five-star selection is valid only in full mode. */\n selection?: GooglePlaceReviewsSelection\n /** Stable route IDs where review proof is intentionally presented. */\n routeIds: readonly string[]\n }\n\nexport interface BrandlabRouteAlias {\n from: string\n to: string\n status?: 301 | 308\n}\n\nexport interface BrandlabException {\n ruleId: string\n rationale: string\n owner: 'Romie'\n evidence: string\n expiresAt?: string\n reviewTrigger: string\n}\n\nexport type BrandlabSitemapConfig =\n | { mode: 'single' }\n | {\n mode: 'split'\n families: readonly string[]\n reason: string\n }\n\nexport interface BrandlabProjectConfig {\n projectId: string\n profile: BrandlabProjectProfile\n routes: {\n families: readonly BrandlabRouteFamily[]\n aliases?: readonly BrandlabRouteAlias[]\n }\n capabilities?: {\n blog?: { adapter: BrandlabBlogAdapter }\n contact?: {\n fieldProfile: string\n attachments: BrandlabContactAttachmentMode\n }\n locations?: { strategy: BrandlabLocationStrategy }\n reviews?: BrandlabReviewsCapability\n }\n sitemap?: BrandlabSitemapConfig\n searchCertification: {\n representativeRoutes: {\n home: string\n contact?: string\n service?: string\n location?: string\n blog?: string\n }\n }\n exceptions?: readonly BrandlabException[]\n}\n\nconst PROJECT_PROFILES = new Set<BrandlabProjectProfile>([\n 'next-local-service',\n 'next-root-level',\n])\nconst ROUTE_FAMILIES = new Set<BrandlabRouteFamily>([\n 'services',\n 'locations',\n 'service-locations',\n 'blog',\n 'projects',\n 'legal',\n])\nconst BLOG_ADAPTERS = new Set<BrandlabBlogAdapter>(['none', 'mdx', 'structured'])\nconst ATTACHMENT_MODES = new Set<BrandlabContactAttachmentMode>([\n 'none',\n 'images-optional',\n 'images-required',\n])\nconst LOCATION_STRATEGIES = new Set<BrandlabLocationStrategy>([\n 'none',\n 'approved-service-area',\n 'physical-location',\n 'multi-location',\n])\nconst REVIEWS_PROVIDERS = new Set<BrandlabReviewsProvider>([\n 'google-places',\n 'project-owned',\n])\nconst GOOGLE_REVIEWS_MODES = new Set<BrandlabGoogleReviewsMode>([\n 'link-only',\n 'summary',\n 'full',\n])\nconst GOOGLE_REVIEWS_SELECTIONS = new Set<GooglePlaceReviewsSelection>([\n 'relevance',\n 'five-star-featured',\n])\n\nfunction assertNonEmpty(value: string, field: string) {\n if (!value.trim()) {\n throw new Error(`${field} cannot be empty.`)\n }\n}\n\nfunction assertRootRelativePath(path: string, field: string) {\n try {\n normalizeBrandlabRoutePath(path)\n } catch {\n throw new Error(`${field} must be a root-relative path without a query string or hash.`)\n }\n}\n\nfunction assertUnique(values: readonly string[], field: string) {\n const duplicate = values.find((value, index) => values.indexOf(value) !== index)\n if (duplicate) {\n throw new Error(`${field} contains duplicate value \"${duplicate}\".`)\n }\n}\n\n/**\n * Defines project-owned variation only. Runtime versions and global Brandlab\n * policy deliberately do not belong in this object.\n */\nexport function defineBrandlabProject<const T extends BrandlabProjectConfig>(config: T): T {\n assertNonEmpty(config.projectId, 'projectId')\n\n assertBrandlabCanonicalEntityId(config.projectId)\n if (!PROJECT_PROFILES.has(config.profile)) {\n throw new Error(`Unsupported project profile \"${String(config.profile)}\".`)\n }\n\n assertUnique(config.routes.families, 'routes.families')\n config.routes.families.forEach((family, index) => {\n if (!ROUTE_FAMILIES.has(family)) {\n throw new Error(`routes.families[${index}] is unsupported.`)\n }\n })\n\n const aliases = config.routes.aliases ?? []\n const normalizedAliases = aliases.map((alias) => ({\n ...alias,\n from: normalizeBrandlabRoutePath(alias.from),\n to: normalizeBrandlabRoutePath(alias.to),\n }))\n assertUnique(\n normalizedAliases.map((alias) => alias.from),\n 'routes.aliases.from',\n )\n\n aliases.forEach((alias, index) => {\n assertRootRelativePath(alias.from, `routes.aliases[${index}].from`)\n assertRootRelativePath(alias.to, `routes.aliases[${index}].to`)\n if (alias.status !== undefined && alias.status !== 301 && alias.status !== 308) {\n throw new Error(`routes.aliases[${index}].status must be 301 or 308.`)\n }\n if (normalizedAliases[index]!.from === normalizedAliases[index]!.to) {\n throw new Error(`routes.aliases[${index}] cannot redirect a path to itself.`)\n }\n })\n\n const aliasTargets = new Set(normalizedAliases.map((alias) => alias.to))\n const chainedAlias = normalizedAliases.find((alias) => aliasTargets.has(alias.from))\n if (chainedAlias) {\n throw new Error(`Route alias \"${chainedAlias.from}\" creates a redirect chain or cycle.`)\n }\n\n const representativeRoutes = config.searchCertification.representativeRoutes\n Object.entries(representativeRoutes).forEach(([name, path]) => {\n if (path) assertRootRelativePath(path, `searchCertification.representativeRoutes.${name}`)\n })\n if (representativeRoutes.home !== '/') {\n throw new Error('searchCertification.representativeRoutes.home must be \"/\".')\n }\n\n if (config.capabilities?.contact) {\n assertNonEmpty(config.capabilities.contact.fieldProfile, 'contact.fieldProfile')\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(config.capabilities.contact.fieldProfile)) {\n throw new Error('contact.fieldProfile must be lowercase kebab-case.')\n }\n if (!ATTACHMENT_MODES.has(config.capabilities.contact.attachments)) {\n throw new Error('contact.attachments is unsupported.')\n }\n }\n if (\n config.capabilities?.blog &&\n !BLOG_ADAPTERS.has(config.capabilities.blog.adapter)\n ) {\n throw new Error('blog.adapter is unsupported.')\n }\n if (\n config.capabilities?.locations &&\n !LOCATION_STRATEGIES.has(config.capabilities.locations.strategy)\n ) {\n throw new Error('locations.strategy is unsupported.')\n }\n if (\n config.capabilities?.reviews &&\n !REVIEWS_PROVIDERS.has(config.capabilities.reviews.provider)\n ) {\n throw new Error('reviews.provider is unsupported.')\n }\n if (\n config.capabilities?.reviews?.provider === 'google-places' &&\n !config.routes.families.includes('legal')\n ) {\n throw new Error('Google Places reviews require the legal route family.')\n }\n if (config.capabilities?.reviews?.provider === 'google-places') {\n const reviews = config.capabilities.reviews\n if (!GOOGLE_REVIEWS_MODES.has(reviews.mode)) {\n throw new Error('reviews.mode is unsupported.')\n }\n if (reviews.selection && !GOOGLE_REVIEWS_SELECTIONS.has(reviews.selection)) {\n throw new Error('reviews.selection is unsupported.')\n }\n if (reviews.selection === 'five-star-featured' && reviews.mode !== 'full') {\n throw new Error('Five-star featured reviews require full mode.')\n }\n if (!Array.isArray(reviews.routeIds) || reviews.routeIds.length === 0) {\n throw new Error('Google Places reviews require at least one approved route ID.')\n }\n assertUnique(reviews.routeIds, 'reviews.routeIds')\n reviews.routeIds.forEach((routeId, index) => {\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(routeId)) {\n throw new Error(`reviews.routeIds[${index}] must be lowercase kebab-case.`)\n }\n })\n }\n\n if (config.sitemap?.mode === 'split') {\n if (config.sitemap.families.length === 0) {\n throw new Error('Split sitemap configuration requires at least one family.')\n }\n assertUnique(config.sitemap.families, 'sitemap.families')\n config.sitemap.families.forEach((path, index) =>\n assertRootRelativePath(path, `sitemap.families[${index}]`),\n )\n assertNonEmpty(config.sitemap.reason, 'sitemap.reason')\n }\n\n assertUnique(\n (config.exceptions ?? []).map((exception) => exception.ruleId),\n 'exceptions.ruleId',\n )\n\n ;(config.exceptions ?? []).forEach((exception, index) => {\n assertNonEmpty(exception.ruleId, `exceptions[${index}].ruleId`)\n assertNonEmpty(exception.rationale, `exceptions[${index}].rationale`)\n assertNonEmpty(exception.evidence, `exceptions[${index}].evidence`)\n assertNonEmpty(exception.reviewTrigger, `exceptions[${index}].reviewTrigger`)\n if (exception.owner !== 'Romie') {\n throw new Error(`exceptions[${index}].owner must be \"Romie\".`)\n }\n\n if (exception.expiresAt && Number.isNaN(Date.parse(exception.expiresAt))) {\n throw new Error(`exceptions[${index}].expiresAt must be a valid date.`)\n }\n })\n\n return config\n}\n"]}
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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","sourcesContent":["import { normalizeBrandlabRoutePath } from './routes.js'\nimport { assertBrandlabCanonicalEntityId } from './entity-id.js'\nimport type { GooglePlaceReviewsSelection } from './google-reviews.js'\n\nexport type BrandlabProjectProfile =\n | 'next-local-service'\n | 'next-root-level'\n\nexport type BrandlabRouteFamily =\n | 'services'\n | 'locations'\n | 'service-locations'\n | 'blog'\n | 'projects'\n | 'legal'\n\nexport type BrandlabBlogAdapter = 'none' | 'mdx' | 'structured'\nexport type BrandlabContactAttachmentMode =\n | 'none'\n | 'images-optional'\n | 'images-required'\n | 'images-and-pdfs-optional'\nexport type BrandlabLocationStrategy =\n | 'none'\n | 'approved-service-area'\n | 'physical-location'\n | 'multi-location'\nexport type BrandlabReviewsProvider = 'google-places' | 'project-owned'\nexport type BrandlabGoogleReviewsMode = 'link-only' | 'summary' | 'full'\n\nexport type BrandlabReviewsCapability =\n | { provider: 'project-owned' }\n | {\n provider: 'google-places'\n mode: BrandlabGoogleReviewsMode\n /** Optional presentation policy. Five-star selection is valid only in full mode. */\n selection?: GooglePlaceReviewsSelection\n /** Stable route IDs where review proof is intentionally presented. */\n routeIds: readonly string[]\n }\n\nexport interface BrandlabRouteAlias {\n from: string\n to: string\n status?: 301 | 308\n}\n\nexport interface BrandlabException {\n ruleId: string\n rationale: string\n owner: 'Romie'\n evidence: string\n expiresAt?: string\n reviewTrigger: string\n}\n\nexport type BrandlabSitemapConfig =\n | { mode: 'single' }\n | {\n mode: 'split'\n families: readonly string[]\n reason: string\n }\n\nexport interface BrandlabProjectConfig {\n projectId: string\n profile: BrandlabProjectProfile\n routes: {\n families: readonly BrandlabRouteFamily[]\n aliases?: readonly BrandlabRouteAlias[]\n }\n capabilities?: {\n blog?: { adapter: BrandlabBlogAdapter }\n contact?: {\n fieldProfile: string\n attachments: BrandlabContactAttachmentMode\n }\n locations?: { strategy: BrandlabLocationStrategy }\n reviews?: BrandlabReviewsCapability\n }\n sitemap?: BrandlabSitemapConfig\n searchCertification: {\n representativeRoutes: {\n home: string\n contact?: string\n service?: string\n location?: string\n blog?: string\n }\n }\n exceptions?: readonly BrandlabException[]\n}\n\nconst PROJECT_PROFILES = new Set<BrandlabProjectProfile>([\n 'next-local-service',\n 'next-root-level',\n])\nconst ROUTE_FAMILIES = new Set<BrandlabRouteFamily>([\n 'services',\n 'locations',\n 'service-locations',\n 'blog',\n 'projects',\n 'legal',\n])\nconst BLOG_ADAPTERS = new Set<BrandlabBlogAdapter>(['none', 'mdx', 'structured'])\nconst ATTACHMENT_MODES = new Set<BrandlabContactAttachmentMode>([\n 'none',\n 'images-optional',\n 'images-required',\n 'images-and-pdfs-optional',\n])\nconst LOCATION_STRATEGIES = new Set<BrandlabLocationStrategy>([\n 'none',\n 'approved-service-area',\n 'physical-location',\n 'multi-location',\n])\nconst REVIEWS_PROVIDERS = new Set<BrandlabReviewsProvider>([\n 'google-places',\n 'project-owned',\n])\nconst GOOGLE_REVIEWS_MODES = new Set<BrandlabGoogleReviewsMode>([\n 'link-only',\n 'summary',\n 'full',\n])\nconst GOOGLE_REVIEWS_SELECTIONS = new Set<GooglePlaceReviewsSelection>([\n 'relevance',\n 'five-star-featured',\n])\n\nfunction assertNonEmpty(value: string, field: string) {\n if (!value.trim()) {\n throw new Error(`${field} cannot be empty.`)\n }\n}\n\nfunction assertRootRelativePath(path: string, field: string) {\n try {\n normalizeBrandlabRoutePath(path)\n } catch {\n throw new Error(`${field} must be a root-relative path without a query string or hash.`)\n }\n}\n\nfunction assertUnique(values: readonly string[], field: string) {\n const duplicate = values.find((value, index) => values.indexOf(value) !== index)\n if (duplicate) {\n throw new Error(`${field} contains duplicate value \"${duplicate}\".`)\n }\n}\n\n/**\n * Defines project-owned variation only. Runtime versions and global Brandlab\n * policy deliberately do not belong in this object.\n */\nexport function defineBrandlabProject<const T extends BrandlabProjectConfig>(config: T): T {\n assertNonEmpty(config.projectId, 'projectId')\n\n assertBrandlabCanonicalEntityId(config.projectId)\n if (!PROJECT_PROFILES.has(config.profile)) {\n throw new Error(`Unsupported project profile \"${String(config.profile)}\".`)\n }\n\n assertUnique(config.routes.families, 'routes.families')\n config.routes.families.forEach((family, index) => {\n if (!ROUTE_FAMILIES.has(family)) {\n throw new Error(`routes.families[${index}] is unsupported.`)\n }\n })\n\n const aliases = config.routes.aliases ?? []\n const normalizedAliases = aliases.map((alias) => ({\n ...alias,\n from: normalizeBrandlabRoutePath(alias.from),\n to: normalizeBrandlabRoutePath(alias.to),\n }))\n assertUnique(\n normalizedAliases.map((alias) => alias.from),\n 'routes.aliases.from',\n )\n\n aliases.forEach((alias, index) => {\n assertRootRelativePath(alias.from, `routes.aliases[${index}].from`)\n assertRootRelativePath(alias.to, `routes.aliases[${index}].to`)\n if (alias.status !== undefined && alias.status !== 301 && alias.status !== 308) {\n throw new Error(`routes.aliases[${index}].status must be 301 or 308.`)\n }\n if (normalizedAliases[index]!.from === normalizedAliases[index]!.to) {\n throw new Error(`routes.aliases[${index}] cannot redirect a path to itself.`)\n }\n })\n\n const aliasTargets = new Set(normalizedAliases.map((alias) => alias.to))\n const chainedAlias = normalizedAliases.find((alias) => aliasTargets.has(alias.from))\n if (chainedAlias) {\n throw new Error(`Route alias \"${chainedAlias.from}\" creates a redirect chain or cycle.`)\n }\n\n const representativeRoutes = config.searchCertification.representativeRoutes\n Object.entries(representativeRoutes).forEach(([name, path]) => {\n if (path) assertRootRelativePath(path, `searchCertification.representativeRoutes.${name}`)\n })\n if (representativeRoutes.home !== '/') {\n throw new Error('searchCertification.representativeRoutes.home must be \"/\".')\n }\n\n if (config.capabilities?.contact) {\n assertNonEmpty(config.capabilities.contact.fieldProfile, 'contact.fieldProfile')\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(config.capabilities.contact.fieldProfile)) {\n throw new Error('contact.fieldProfile must be lowercase kebab-case.')\n }\n if (!ATTACHMENT_MODES.has(config.capabilities.contact.attachments)) {\n throw new Error('contact.attachments is unsupported.')\n }\n }\n if (\n config.capabilities?.blog &&\n !BLOG_ADAPTERS.has(config.capabilities.blog.adapter)\n ) {\n throw new Error('blog.adapter is unsupported.')\n }\n if (\n config.capabilities?.locations &&\n !LOCATION_STRATEGIES.has(config.capabilities.locations.strategy)\n ) {\n throw new Error('locations.strategy is unsupported.')\n }\n if (\n config.capabilities?.reviews &&\n !REVIEWS_PROVIDERS.has(config.capabilities.reviews.provider)\n ) {\n throw new Error('reviews.provider is unsupported.')\n }\n if (\n config.capabilities?.reviews?.provider === 'google-places' &&\n !config.routes.families.includes('legal')\n ) {\n throw new Error('Google Places reviews require the legal route family.')\n }\n if (config.capabilities?.reviews?.provider === 'google-places') {\n const reviews = config.capabilities.reviews\n if (!GOOGLE_REVIEWS_MODES.has(reviews.mode)) {\n throw new Error('reviews.mode is unsupported.')\n }\n if (reviews.selection && !GOOGLE_REVIEWS_SELECTIONS.has(reviews.selection)) {\n throw new Error('reviews.selection is unsupported.')\n }\n if (reviews.selection === 'five-star-featured' && reviews.mode !== 'full') {\n throw new Error('Five-star featured reviews require full mode.')\n }\n if (!Array.isArray(reviews.routeIds) || reviews.routeIds.length === 0) {\n throw new Error('Google Places reviews require at least one approved route ID.')\n }\n assertUnique(reviews.routeIds, 'reviews.routeIds')\n reviews.routeIds.forEach((routeId, index) => {\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(routeId)) {\n throw new Error(`reviews.routeIds[${index}] must be lowercase kebab-case.`)\n }\n })\n }\n\n if (config.sitemap?.mode === 'split') {\n if (config.sitemap.families.length === 0) {\n throw new Error('Split sitemap configuration requires at least one family.')\n }\n assertUnique(config.sitemap.families, 'sitemap.families')\n config.sitemap.families.forEach((path, index) =>\n assertRootRelativePath(path, `sitemap.families[${index}]`),\n )\n assertNonEmpty(config.sitemap.reason, 'sitemap.reason')\n }\n\n assertUnique(\n (config.exceptions ?? []).map((exception) => exception.ruleId),\n 'exceptions.ruleId',\n )\n\n ;(config.exceptions ?? []).forEach((exception, index) => {\n assertNonEmpty(exception.ruleId, `exceptions[${index}].ruleId`)\n assertNonEmpty(exception.rationale, `exceptions[${index}].rationale`)\n assertNonEmpty(exception.evidence, `exceptions[${index}].evidence`)\n assertNonEmpty(exception.reviewTrigger, `exceptions[${index}].reviewTrigger`)\n if (exception.owner !== 'Romie') {\n throw new Error(`exceptions[${index}].owner must be \"Romie\".`)\n }\n\n if (exception.expiresAt && Number.isNaN(Date.parse(exception.expiresAt))) {\n throw new Error(`exceptions[${index}].expiresAt must be a valid date.`)\n }\n })\n\n return config\n}\n"]}
|