@brandlab-digital/site-core 0.1.0-alpha.8 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/analytics.d.ts +66 -1
  2. package/dist/analytics.d.ts.map +1 -1
  3. package/dist/analytics.js +255 -0
  4. package/dist/analytics.js.map +1 -1
  5. package/dist/blog.d.ts +36 -0
  6. package/dist/blog.d.ts.map +1 -0
  7. package/dist/blog.js +98 -0
  8. package/dist/blog.js.map +1 -0
  9. package/dist/breadcrumbs.d.ts +6 -0
  10. package/dist/breadcrumbs.d.ts.map +1 -1
  11. package/dist/breadcrumbs.js +38 -0
  12. package/dist/breadcrumbs.js.map +1 -1
  13. package/dist/business.d.ts +101 -0
  14. package/dist/business.d.ts.map +1 -0
  15. package/dist/business.js +177 -0
  16. package/dist/business.js.map +1 -0
  17. package/dist/contact.d.ts +109 -30
  18. package/dist/contact.d.ts.map +1 -1
  19. package/dist/contact.js +181 -2
  20. package/dist/contact.js.map +1 -1
  21. package/dist/geography.d.ts +20 -0
  22. package/dist/geography.d.ts.map +1 -0
  23. package/dist/geography.js +6708 -0
  24. package/dist/geography.js.map +1 -0
  25. package/dist/google-reviews.d.ts +91 -0
  26. package/dist/google-reviews.d.ts.map +1 -0
  27. package/dist/google-reviews.js +144 -0
  28. package/dist/google-reviews.js.map +1 -0
  29. package/dist/locations.d.ts +169 -0
  30. package/dist/locations.d.ts.map +1 -0
  31. package/dist/locations.js +198 -0
  32. package/dist/locations.js.map +1 -0
  33. package/dist/metadata.d.ts +16 -4
  34. package/dist/metadata.d.ts.map +1 -1
  35. package/dist/metadata.js +69 -21
  36. package/dist/metadata.js.map +1 -1
  37. package/dist/project.d.ts +62 -0
  38. package/dist/project.d.ts.map +1 -0
  39. package/dist/project.js +130 -0
  40. package/dist/project.js.map +1 -0
  41. package/dist/reviews.d.ts +22 -0
  42. package/dist/reviews.d.ts.map +1 -0
  43. package/dist/reviews.js +36 -0
  44. package/dist/reviews.js.map +1 -0
  45. package/dist/rich-text.d.ts.map +1 -1
  46. package/dist/rich-text.js +8 -2
  47. package/dist/rich-text.js.map +1 -1
  48. package/dist/routes.d.ts +18 -0
  49. package/dist/routes.d.ts.map +1 -0
  50. package/dist/routes.js +88 -0
  51. package/dist/routes.js.map +1 -0
  52. package/dist/schema.d.ts +89 -4
  53. package/dist/schema.d.ts.map +1 -1
  54. package/dist/schema.js +298 -34
  55. package/dist/schema.js.map +1 -1
  56. package/dist/seo-page.d.ts +16 -0
  57. package/dist/seo-page.d.ts.map +1 -0
  58. package/dist/seo-page.js +26 -0
  59. package/dist/seo-page.js.map +1 -0
  60. package/dist/sitemaps.d.ts +9 -31
  61. package/dist/sitemaps.d.ts.map +1 -1
  62. package/dist/sitemaps.js +69 -43
  63. package/dist/sitemaps.js.map +1 -1
  64. package/package.json +37 -1
@@ -1,8 +1,58 @@
1
1
  export declare const CANONICAL_LEAD_EVENTS: readonly ["phone_call", "contact_form"];
2
2
  export type CanonicalLeadEvent = (typeof CANONICAL_LEAD_EVENTS)[number];
3
- export type GtagTransport = (command: 'event', eventName: string, params: Record<string, unknown>) => void;
3
+ export type BrandlabAnalyticsParameterPolicy = {
4
+ key: string;
5
+ type: 'boolean';
6
+ } | {
7
+ key: string;
8
+ type: 'count';
9
+ minimum?: number;
10
+ maximum?: number;
11
+ } | {
12
+ key: string;
13
+ type: 'enum';
14
+ values: readonly string[];
15
+ } | {
16
+ key: string;
17
+ type: 'slug';
18
+ };
19
+ export interface BrandlabAnalyticsEventContract {
20
+ name: string;
21
+ parameters: readonly BrandlabAnalyticsParameterPolicy[];
22
+ }
23
+ export interface BrandlabAnalyticsContract {
24
+ measurementIdEnv: string;
25
+ googleAds?: {
26
+ conversionIdEnv: string;
27
+ contactConversionLabelEnv: string;
28
+ /** Enhanced Conversions require both this project opt-in and live user consent. */
29
+ enhancedConversions?: {
30
+ mode: 'consent-required';
31
+ };
32
+ };
33
+ webVitals: boolean;
34
+ additionalEvents?: readonly BrandlabAnalyticsEventContract[];
35
+ }
36
+ export type GtagTransport = (command: 'event' | 'set', eventName: string, params: Record<string, unknown>) => void;
37
+ export interface EnhancedConversionUserData {
38
+ email?: string | null;
39
+ phone?: string | null;
40
+ }
41
+ export interface GoogleTagEnhancedConversionUserData extends Record<string, unknown> {
42
+ email: string;
43
+ phone_number?: string;
44
+ }
45
+ export interface ContactFormConversionResult {
46
+ ga4Sent: boolean;
47
+ enhancedConversionSent: boolean;
48
+ adsConversionSent: boolean;
49
+ }
50
+ export declare function defineAnalyticsContract<const T extends BrandlabAnalyticsContract>(contract: T): T;
4
51
  export declare function isCanonicalLeadEvent(value: string): value is CanonicalLeadEvent;
5
52
  export declare function normalizeGoogleAdsConversionId(conversionId: string): string;
53
+ export declare function normalizeEnhancedConversionEmail(email: string | null | undefined): string | undefined;
54
+ export declare function normalizeEnhancedConversionPhone(phone: string | null | undefined): string | undefined;
55
+ export declare function buildEnhancedConversionUserData(userData: EnhancedConversionUserData | null | undefined): GoogleTagEnhancedConversionUserData | undefined;
6
56
  export declare function getGoogleAdsContactSendTo(options: {
7
57
  conversionId?: string | null;
8
58
  conversionLabel?: string | null;
@@ -14,4 +64,19 @@ export declare function sendBrandlabAnalyticsEvent(options: {
14
64
  eventName: string;
15
65
  params: Record<string, unknown>;
16
66
  }): boolean;
67
+ export declare function sanitizeContactFormGa4Params(params: Record<string, unknown>): Record<string, unknown>;
68
+ export declare function sendContactFormConversion(options: {
69
+ enabled: boolean;
70
+ measurementId?: string | null;
71
+ googleAdsConversionId?: string | null;
72
+ googleAdsConversionLabel?: string | null;
73
+ gtag?: GtagTransport;
74
+ pagePath: string;
75
+ submissionId?: string | null;
76
+ enhancedConversion?: EnhancedConversionUserData | null;
77
+ enhancedConversionsEnabled?: boolean;
78
+ adUserDataConsent?: boolean;
79
+ params?: Record<string, unknown>;
80
+ adsParams?: Record<string, unknown>;
81
+ }): ContactFormConversionResult;
17
82
  //# sourceMappingURL=analytics.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,yCAA0C,CAAA;AAE5E,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AACvE,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;AAE1G,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,kBAAkB,CAE/E;AAED,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,MAAM,UAGlE;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE;IACjD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC,sBASA;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE;IAClD,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,IAAI,CAAC,EAAE,aAAa,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC,WAWA"}
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,qBAAqB,yCAA0C,CAAA;AAE5E,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,MAAM,MAAM,gCAAgC,GACxC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAChC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAClE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACxD;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEjC,MAAM,WAAW,8BAA8B;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,SAAS,gCAAgC,EAAE,CAAA;CACxD;AAED,MAAM,WAAW,yBAAyB;IACxC,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE;QACV,eAAe,EAAE,MAAM,CAAA;QACvB,yBAAyB,EAAE,MAAM,CAAA;QACjC,mFAAmF;QACnF,mBAAmB,CAAC,EAAE;YAAE,IAAI,EAAE,kBAAkB,CAAA;SAAE,CAAA;KACnD,CAAA;IACD,SAAS,EAAE,OAAO,CAAA;IAClB,gBAAgB,CAAC,EAAE,SAAS,8BAA8B,EAAE,CAAA;CAC7D;AACD,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,OAAO,GAAG,KAAK,EACxB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,IAAI,CAAA;AAET,MAAM,WAAW,0BAA0B;IACzC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,mCAAoC,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAClF,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,OAAO,CAAA;IAChB,sBAAsB,EAAE,OAAO,CAAA;IAC/B,iBAAiB,EAAE,OAAO,CAAA;CAC3B;AAmDD,wBAAgB,uBAAuB,CAAC,KAAK,CAAC,CAAC,SAAS,yBAAyB,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CA8EjG;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,kBAAkB,CAE/E;AAED,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,MAAM,UAGlE;AAED,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,sBAgBhF;AAED,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,sBAShF;AAED,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,0BAA0B,GAAG,IAAI,GAAG,SAAS,GACtD,mCAAmC,GAAG,SAAS,CAcjD;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE;IACjD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC,sBASA;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE;IAClD,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,IAAI,CAAC,EAAE,aAAa,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC,WAWA;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,2BAmC3E;AA2BD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE;IACjD,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxC,IAAI,CAAC,EAAE,aAAa,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,kBAAkB,CAAC,EAAE,0BAA0B,GAAG,IAAI,CAAA;IACtD,0BAA0B,CAAC,EAAE,OAAO,CAAA;IACpC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC,GAAG,2BAA2B,CA0D9B"}
package/dist/analytics.js CHANGED
@@ -1,4 +1,109 @@
1
+ import { normalizeBrandlabRoutePath } from './routes.js';
1
2
  export const CANONICAL_LEAD_EVENTS = ['phone_call', 'contact_form'];
3
+ const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4
+ const GMAIL_DOMAINS = new Set(['gmail.com', 'googlemail.com']);
5
+ const AUSTRALIAN_E164_DIGITS = /^61[23478]\d{8}$/;
6
+ const GA4_CONTACT_FORM_PARAM_KEYS = new Set([
7
+ 'event_category',
8
+ 'location_slug',
9
+ 'page_path',
10
+ 'page_template',
11
+ 'service_slug',
12
+ 'submission_id',
13
+ ]);
14
+ const GOOGLE_ADS_CONVERSION_PARAM_KEYS = new Set([
15
+ 'currency',
16
+ 'transaction_id',
17
+ 'value',
18
+ ]);
19
+ const ANALYTICS_ENV_KEY_PATTERN = /^[A-Z][A-Z0-9_]*$/;
20
+ const ANALYTICS_EVENT_PATTERN = /^[a-z][a-z0-9_]{0,39}$/;
21
+ const PROHIBITED_ANALYTICS_PARAM_TOKENS = [
22
+ 'address',
23
+ 'email',
24
+ 'first_name',
25
+ 'last_name',
26
+ 'message',
27
+ 'name',
28
+ 'phone',
29
+ ];
30
+ const PRIVACY_SAFE_SUBMISSION_ID_PATTERN = /^[a-f0-9]{64}$/;
31
+ function looksLikePersonalIdentifier(value) {
32
+ const normalized = value.trim().toLowerCase();
33
+ const digits = normalized.replace(/\D/g, '');
34
+ return (normalized.includes('@') ||
35
+ digits.length >= 8 ||
36
+ /^\d{4}$/.test(normalized) ||
37
+ /^[a-f0-9]{24,}$/.test(normalized) ||
38
+ /\b\d+\s+[a-z][a-z -]+\s+(?:street|st|road|rd|avenue|ave|lane|ln|drive|dr|place|pl|court|ct)\b/.test(normalized));
39
+ }
40
+ function assertAnalyticsEnvKey(value, field) {
41
+ if (!ANALYTICS_ENV_KEY_PATTERN.test(value)) {
42
+ throw new Error(`${field} must be an environment variable key.`);
43
+ }
44
+ }
45
+ export function defineAnalyticsContract(contract) {
46
+ assertAnalyticsEnvKey(contract.measurementIdEnv, 'measurementIdEnv');
47
+ if (contract.googleAds) {
48
+ assertAnalyticsEnvKey(contract.googleAds.conversionIdEnv, 'googleAds.conversionIdEnv');
49
+ assertAnalyticsEnvKey(contract.googleAds.contactConversionLabelEnv, 'googleAds.contactConversionLabelEnv');
50
+ if (contract.googleAds.enhancedConversions !== undefined &&
51
+ contract.googleAds.enhancedConversions.mode !== 'consent-required') {
52
+ throw new Error('googleAds.enhancedConversions must require consent.');
53
+ }
54
+ }
55
+ const events = contract.additionalEvents ?? [];
56
+ const duplicate = events.find((event, index) => events.slice(0, index).some((candidate) => candidate.name === event.name));
57
+ if (duplicate)
58
+ throw new Error(`Analytics contract contains duplicate event "${duplicate.name}".`);
59
+ events.forEach((event, eventIndex) => {
60
+ if (!ANALYTICS_EVENT_PATTERN.test(event.name)) {
61
+ throw new Error(`additionalEvents[${eventIndex}].name is invalid.`);
62
+ }
63
+ if (isCanonicalLeadEvent(event.name)) {
64
+ throw new Error(`Canonical lead event "${event.name}" must not be redefined.`);
65
+ }
66
+ if (event.parameters.length === 0 || event.parameters.length > 20) {
67
+ throw new Error(`additionalEvents[${eventIndex}].parameters must contain 1 to 20 policies.`);
68
+ }
69
+ const duplicateParameter = event.parameters.find((parameter, index) => event.parameters.slice(0, index).some((candidate) => candidate.key === parameter.key));
70
+ if (duplicateParameter) {
71
+ throw new Error(`Analytics event "${event.name}" contains duplicate parameter "${duplicateParameter.key}".`);
72
+ }
73
+ event.parameters.forEach((parameter, keyIndex) => {
74
+ const key = parameter.key;
75
+ if (!ANALYTICS_EVENT_PATTERN.test(key)) {
76
+ throw new Error(`additionalEvents[${eventIndex}].parameters[${keyIndex}].key is invalid.`);
77
+ }
78
+ const normalized = key.toLowerCase();
79
+ if (PROHIBITED_ANALYTICS_PARAM_TOKENS.some((token) => normalized.includes(token))) {
80
+ throw new Error(`Analytics parameter "${key}" may contain personal information.`);
81
+ }
82
+ if (parameter.type === 'enum') {
83
+ if (parameter.values.length === 0 ||
84
+ parameter.values.length > 50 ||
85
+ new Set(parameter.values).size !== parameter.values.length ||
86
+ parameter.values.some((value) => !value.trim() ||
87
+ value.length > 80 ||
88
+ looksLikePersonalIdentifier(value))) {
89
+ throw new Error(`Analytics enum parameter "${key}" has an unsafe value policy.`);
90
+ }
91
+ }
92
+ else if (parameter.type === 'count') {
93
+ const minimum = parameter.minimum ?? 0;
94
+ const maximum = parameter.maximum ?? 100_000;
95
+ if (!Number.isInteger(minimum) ||
96
+ !Number.isInteger(maximum) ||
97
+ minimum < 0 ||
98
+ maximum < minimum ||
99
+ maximum > 1_000_000) {
100
+ throw new Error(`Analytics count parameter "${key}" has an invalid range.`);
101
+ }
102
+ }
103
+ });
104
+ });
105
+ return contract;
106
+ }
2
107
  export function isCanonicalLeadEvent(value) {
3
108
  return CANONICAL_LEAD_EVENTS.includes(value);
4
109
  }
@@ -6,6 +111,41 @@ export function normalizeGoogleAdsConversionId(conversionId) {
6
111
  const trimmed = conversionId.trim();
7
112
  return trimmed.startsWith('AW-') ? trimmed : `AW-${trimmed}`;
8
113
  }
114
+ export function normalizeEnhancedConversionEmail(email) {
115
+ const trimmed = email?.trim().toLowerCase();
116
+ if (!trimmed || !EMAIL_PATTERN.test(trimmed)) {
117
+ return undefined;
118
+ }
119
+ const [localPart, domain] = trimmed.split('@');
120
+ if (!localPart || !domain) {
121
+ return undefined;
122
+ }
123
+ const normalizedLocalPart = GMAIL_DOMAINS.has(domain)
124
+ ? localPart.replace(/\./g, '')
125
+ : localPart;
126
+ return `${normalizedLocalPart}@${domain}`;
127
+ }
128
+ export function normalizeEnhancedConversionPhone(phone) {
129
+ const digits = phone?.replace(/[^\d]/g, '') ?? '';
130
+ if (!digits) {
131
+ return undefined;
132
+ }
133
+ const normalizedDigits = digits.startsWith('0') ? `61${digits.slice(1)}` : digits;
134
+ return AUSTRALIAN_E164_DIGITS.test(normalizedDigits) ? `+${normalizedDigits}` : undefined;
135
+ }
136
+ export function buildEnhancedConversionUserData(userData) {
137
+ const email = normalizeEnhancedConversionEmail(userData?.email);
138
+ if (!email) {
139
+ return undefined;
140
+ }
141
+ const phoneNumber = normalizeEnhancedConversionPhone(userData?.phone);
142
+ return phoneNumber
143
+ ? {
144
+ email,
145
+ phone_number: phoneNumber,
146
+ }
147
+ : { email };
148
+ }
9
149
  export function getGoogleAdsContactSendTo(options) {
10
150
  const conversionId = options.conversionId?.trim();
11
151
  const conversionLabel = options.conversionLabel?.trim();
@@ -24,4 +164,119 @@ export function sendBrandlabAnalyticsEvent(options) {
24
164
  });
25
165
  return true;
26
166
  }
167
+ export function sanitizeContactFormGa4Params(params) {
168
+ return Object.entries(params).reduce((sanitized, [key, value]) => {
169
+ const normalizedKey = key.toLowerCase();
170
+ if (!GA4_CONTACT_FORM_PARAM_KEYS.has(normalizedKey))
171
+ return sanitized;
172
+ if (normalizedKey === 'event_category') {
173
+ if (value === 'lead')
174
+ sanitized.event_category = 'lead';
175
+ return sanitized;
176
+ }
177
+ if (normalizedKey === 'page_path') {
178
+ if (typeof value !== 'string')
179
+ return sanitized;
180
+ const path = value.trim().split(/[?#]/, 1)[0];
181
+ if (!path || path.length > 500)
182
+ return sanitized;
183
+ try {
184
+ sanitized.page_path = normalizeBrandlabRoutePath(path);
185
+ }
186
+ catch {
187
+ // Invalid or encoded path separators are not analytics dimensions.
188
+ }
189
+ return sanitized;
190
+ }
191
+ if (normalizedKey === 'submission_id') {
192
+ if (typeof value === 'string' && PRIVACY_SAFE_SUBMISSION_ID_PATTERN.test(value)) {
193
+ sanitized.submission_id = value;
194
+ }
195
+ return sanitized;
196
+ }
197
+ if (typeof value === 'string' &&
198
+ /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(value) &&
199
+ value.length <= 80) {
200
+ sanitized[normalizedKey] = value;
201
+ }
202
+ return sanitized;
203
+ }, {});
204
+ }
205
+ function sanitizeGoogleAdsConversionParams(params) {
206
+ return Object.entries(params).reduce((sanitized, [key, value]) => {
207
+ const normalizedKey = key.toLowerCase();
208
+ if (!GOOGLE_ADS_CONVERSION_PARAM_KEYS.has(normalizedKey))
209
+ return sanitized;
210
+ if (normalizedKey === 'currency' && typeof value === 'string' && /^[A-Z]{3}$/.test(value)) {
211
+ sanitized.currency = value;
212
+ }
213
+ else if (normalizedKey === 'value' &&
214
+ typeof value === 'number' &&
215
+ Number.isFinite(value) &&
216
+ value >= 0) {
217
+ sanitized.value = value;
218
+ }
219
+ else if (normalizedKey === 'transaction_id' &&
220
+ typeof value === 'string' &&
221
+ PRIVACY_SAFE_SUBMISSION_ID_PATTERN.test(value)) {
222
+ sanitized.transaction_id = value;
223
+ }
224
+ return sanitized;
225
+ }, {});
226
+ }
227
+ export function sendContactFormConversion(options) {
228
+ const submissionId = options.submissionId?.trim().toLowerCase();
229
+ const privacySafeSubmissionId = submissionId && PRIVACY_SAFE_SUBMISSION_ID_PATTERN.test(submissionId)
230
+ ? submissionId
231
+ : undefined;
232
+ const baseParams = sanitizeContactFormGa4Params({
233
+ ...options.params,
234
+ page_path: options.pagePath,
235
+ ...(privacySafeSubmissionId ? { submission_id: privacySafeSubmissionId } : {}),
236
+ event_category: 'lead',
237
+ });
238
+ const ga4Sent = sendBrandlabAnalyticsEvent({
239
+ enabled: options.enabled,
240
+ measurementId: options.measurementId,
241
+ gtag: options.gtag,
242
+ eventName: 'contact_form',
243
+ params: baseParams,
244
+ });
245
+ const sendTo = getGoogleAdsContactSendTo({
246
+ conversionId: options.googleAdsConversionId,
247
+ conversionLabel: options.googleAdsConversionLabel,
248
+ });
249
+ const userData = options.enhancedConversionsEnabled && options.adUserDataConsent
250
+ ? buildEnhancedConversionUserData(options.enhancedConversion)
251
+ : undefined;
252
+ const canSendAdsConversion = options.enabled &&
253
+ typeof options.gtag === 'function' &&
254
+ Boolean(sendTo);
255
+ if (!canSendAdsConversion || !sendTo || typeof options.gtag !== 'function') {
256
+ return {
257
+ ga4Sent,
258
+ enhancedConversionSent: false,
259
+ adsConversionSent: false,
260
+ };
261
+ }
262
+ if (userData) {
263
+ options.gtag('set', 'user_data', userData);
264
+ }
265
+ try {
266
+ options.gtag('event', 'conversion', {
267
+ ...sanitizeGoogleAdsConversionParams(options.adsParams ?? {}),
268
+ send_to: sendTo,
269
+ ...(privacySafeSubmissionId ? { transaction_id: privacySafeSubmissionId } : {}),
270
+ });
271
+ }
272
+ finally {
273
+ if (userData)
274
+ options.gtag('set', 'user_data', {});
275
+ }
276
+ return {
277
+ ga4Sent,
278
+ enhancedConversionSent: Boolean(userData),
279
+ adsConversionSent: true,
280
+ };
281
+ }
27
282
  //# sourceMappingURL=analytics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,YAAY,EAAE,cAAc,CAAU,CAAA;AAK5E,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAO,qBAAqB,CAAC,QAAQ,CAAC,KAA2B,CAAC,CAAA;AACpE,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,YAAoB;IACjE,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,CAAA;IACnC,OAAO,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,OAAO,EAAE,CAAA;AAC9D,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAGzC;IACC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,IAAI,EAAE,CAAA;IACjD,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,EAAE,IAAI,EAAE,CAAA;IAEvD,IAAI,CAAC,YAAY,IAAI,CAAC,eAAe,EAAE,CAAC;QACtC,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,OAAO,GAAG,8BAA8B,CAAC,YAAY,CAAC,IAAI,eAAe,EAAE,CAAA;AAC7E,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,OAM1C;IACC,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QACrF,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE;QACvC,GAAG,OAAO,CAAC,MAAM;QACjB,OAAO,EAAE,OAAO,CAAC,aAAa;KAC/B,CAAC,CAAA;IAEF,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
package/dist/blog.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ export type BrandlabBlogAdapter = 'mdx' | 'structured';
2
+ export type BrandlabBlogPostStatus = 'draft' | 'published';
3
+ export interface BrandlabBlogAuthor {
4
+ id: string;
5
+ name: string;
6
+ profileUrl?: string;
7
+ }
8
+ export interface BrandlabBlogPost {
9
+ id: string;
10
+ slug: string;
11
+ path: string;
12
+ adapter: BrandlabBlogAdapter;
13
+ status: BrandlabBlogPostStatus;
14
+ headline: string;
15
+ description: string;
16
+ publishedAt?: string;
17
+ modifiedAt?: string;
18
+ authors: readonly BrandlabBlogAuthor[];
19
+ categories?: readonly string[];
20
+ tags?: readonly string[];
21
+ image?: {
22
+ url: string;
23
+ alt: string;
24
+ };
25
+ contentRef: string;
26
+ }
27
+ export declare function defineBlogPost<const T extends BrandlabBlogPost>(post: T): T;
28
+ export declare function isPublishedBlogPost(post: BrandlabBlogPost, now?: Date): post is BrandlabBlogPost & {
29
+ status: 'published';
30
+ publishedAt: string;
31
+ };
32
+ export declare function getPublishedBlogPosts(posts: readonly BrandlabBlogPost[], now?: Date): (BrandlabBlogPost & {
33
+ status: "published";
34
+ publishedAt: string;
35
+ })[];
36
+ //# sourceMappingURL=blog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog.d.ts","sourceRoot":"","sources":["../src/blog.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,YAAY,CAAA;AACtD,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,WAAW,CAAA;AAE1D,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,mBAAmB,CAAA;IAC5B,MAAM,EAAE,sBAAsB,CAAA;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACtC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,KAAK,CAAC,EAAE;QACN,GAAG,EAAE,MAAM,CAAA;QACX,GAAG,EAAE,MAAM,CAAA;KACZ,CAAA;IACD,UAAU,EAAE,MAAM,CAAA;CACnB;AAaD,wBAAgB,cAAc,CAAC,KAAK,CAAC,CAAC,SAAS,gBAAgB,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CA+D3E;AAcD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,gBAAgB,EACtB,GAAG,GAAE,IAAiB,GACrB,IAAI,IAAI,gBAAgB,GAAG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAMzE;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,gBAAgB,EAAE,EAClC,GAAG,GAAE,IAAiB;YAGiC,WAAW;iBAAe,MAAM;KAIxF"}
package/dist/blog.js ADDED
@@ -0,0 +1,98 @@
1
+ import { normalizeBrandlabRoutePath } from './routes.js';
2
+ function assertNonEmpty(value, field) {
3
+ if (!value.trim())
4
+ throw new Error(`${field} cannot be empty.`);
5
+ }
6
+ function parseDate(value, field) {
7
+ if (!value)
8
+ return undefined;
9
+ const timestamp = Date.parse(value);
10
+ if (Number.isNaN(timestamp))
11
+ throw new Error(`${field} must be a valid date.`);
12
+ return timestamp;
13
+ }
14
+ export function defineBlogPost(post) {
15
+ assertNonEmpty(post.id, 'id');
16
+ assertNonEmpty(post.slug, 'slug');
17
+ assertNonEmpty(post.path, 'path');
18
+ assertNonEmpty(post.headline, 'headline');
19
+ assertNonEmpty(post.description, 'description');
20
+ assertNonEmpty(post.contentRef, 'contentRef');
21
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(post.id)) {
22
+ throw new Error('id must be lowercase kebab-case.');
23
+ }
24
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(post.slug)) {
25
+ throw new Error('slug must be lowercase kebab-case.');
26
+ }
27
+ try {
28
+ normalizeBrandlabRoutePath(post.path);
29
+ }
30
+ catch {
31
+ throw new Error('path must be root-relative without a query string or hash.');
32
+ }
33
+ if (post.authors.length === 0)
34
+ throw new Error('authors cannot be empty.');
35
+ const authorIds = post.authors.map((author) => author.id);
36
+ if (new Set(authorIds).size !== authorIds.length) {
37
+ throw new Error('authors contains duplicate IDs.');
38
+ }
39
+ post.authors.forEach((author, index) => {
40
+ assertNonEmpty(author.id, `authors[${index}].id`);
41
+ assertNonEmpty(author.name, `authors[${index}].name`);
42
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(author.id)) {
43
+ throw new Error(`authors[${index}].id must be lowercase kebab-case.`);
44
+ }
45
+ if (author.profileUrl)
46
+ assertAbsoluteHttpsUrl(author.profileUrl, `authors[${index}].profileUrl`);
47
+ });
48
+ post.categories?.forEach((category, index) => assertNonEmpty(category, `categories[${index}]`));
49
+ post.tags?.forEach((tag, index) => assertNonEmpty(tag, `tags[${index}]`));
50
+ if (post.categories && new Set(post.categories).size !== post.categories.length) {
51
+ throw new Error('categories contains duplicates.');
52
+ }
53
+ if (post.tags && new Set(post.tags).size !== post.tags.length) {
54
+ throw new Error('tags contains duplicates.');
55
+ }
56
+ const publishedAt = parseDate(post.publishedAt, 'publishedAt');
57
+ const modifiedAt = parseDate(post.modifiedAt, 'modifiedAt');
58
+ if (post.status === 'published' && publishedAt === undefined) {
59
+ throw new Error('A published post requires publishedAt.');
60
+ }
61
+ if (publishedAt !== undefined && modifiedAt !== undefined && modifiedAt < publishedAt) {
62
+ throw new Error('modifiedAt cannot be earlier than publishedAt.');
63
+ }
64
+ if (post.image) {
65
+ assertNonEmpty(post.image.url, 'image.url');
66
+ assertNonEmpty(post.image.alt, 'image.alt');
67
+ if (post.image.url.startsWith('/')) {
68
+ normalizeBrandlabRoutePath(post.image.url);
69
+ }
70
+ else {
71
+ assertAbsoluteHttpsUrl(post.image.url, 'image.url');
72
+ }
73
+ }
74
+ return post;
75
+ }
76
+ function assertAbsoluteHttpsUrl(value, field) {
77
+ let url;
78
+ try {
79
+ url = new URL(value);
80
+ }
81
+ catch {
82
+ throw new Error(`${field} must be an absolute HTTPS URL.`);
83
+ }
84
+ if (url.protocol !== 'https:' || url.username || url.password) {
85
+ throw new Error(`${field} must be an absolute HTTPS URL.`);
86
+ }
87
+ }
88
+ export function isPublishedBlogPost(post, now = new Date()) {
89
+ return (post.status === 'published' &&
90
+ Boolean(post.publishedAt) &&
91
+ Date.parse(post.publishedAt ?? '') <= now.getTime());
92
+ }
93
+ export function getPublishedBlogPosts(posts, now = new Date()) {
94
+ return posts
95
+ .filter((post) => isPublishedBlogPost(post, now))
96
+ .sort((left, right) => Date.parse(right.publishedAt) - Date.parse(left.publishedAt));
97
+ }
98
+ //# sourceMappingURL=blog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog.js","sourceRoot":"","sources":["../src/blog.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,16 @@
1
+ import type { BrandlabRouteCatalog } from './routes.js';
1
2
  export interface BreadcrumbItem {
2
3
  name: string;
3
4
  href?: string;
5
+ routeId?: string;
4
6
  }
5
7
  export interface BuildBreadcrumbsOptions {
6
8
  labelResolver?: (segment: string, options?: BuildBreadcrumbsOptions) => string;
7
9
  }
8
10
  export declare function formatSegmentTitle(segment: string): string;
9
11
  export declare function buildBreadcrumbs(path: string, options?: BuildBreadcrumbsOptions): BreadcrumbItem[];
12
+ /** Defines an explicit semantic trail shared by visible UI and JSON-LD. */
13
+ export declare function defineBreadcrumbTrail<const T extends readonly BreadcrumbItem[]>(items: T): T;
14
+ /** Builds a semantic trail from explicit route parents and labels. */
15
+ export declare function buildRouteBreadcrumbs(routeId: string, routes: BrandlabRouteCatalog): BreadcrumbItem[];
10
16
  //# sourceMappingURL=breadcrumbs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,KAAK,MAAM,CAAA;CAC/E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,uBAAuB,GAChC,cAAc,EAAE,CAiBlB"}
1
+ {"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAGvD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,KAAK,MAAM,CAAA;CAC/E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,uBAAuB,GAChC,cAAc,EAAE,CAiBlB;AAED,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,KAAK,CAAC,CAAC,SAAS,SAAS,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAY5F;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,oBAAoB,GAC3B,cAAc,EAAE,CAuBlB"}
@@ -1,3 +1,4 @@
1
+ import { getRouteById, normalizeBrandlabRoutePath } from './routes.js';
1
2
  export function formatSegmentTitle(segment) {
2
3
  return segment
3
4
  .split('-')
@@ -21,4 +22,41 @@ export function buildBreadcrumbs(path, options) {
21
22
  }
22
23
  return breadcrumbs;
23
24
  }
25
+ /** Defines an explicit semantic trail shared by visible UI and JSON-LD. */
26
+ export function defineBreadcrumbTrail(items) {
27
+ if (items.length === 0)
28
+ throw new Error('A breadcrumb trail requires at least one item.');
29
+ items.forEach((item, index) => {
30
+ if (!item.name.trim())
31
+ throw new Error(`Breadcrumb item ${index + 1} requires a name.`);
32
+ if (item.href)
33
+ normalizeBrandlabRoutePath(item.href);
34
+ if (index < items.length - 1 && !item.href) {
35
+ throw new Error('Only the final breadcrumb item may omit its href.');
36
+ }
37
+ });
38
+ return items;
39
+ }
40
+ /** Builds a semantic trail from explicit route parents and labels. */
41
+ export function buildRouteBreadcrumbs(routeId, routes) {
42
+ const route = getRouteById(routes, routeId);
43
+ if (!route)
44
+ throw new Error(`Unknown route ID "${routeId}".`);
45
+ const lineage = [route];
46
+ let parentId = route.parentId;
47
+ while (parentId) {
48
+ const parent = getRouteById(routes, parentId);
49
+ if (!parent)
50
+ throw new Error(`Route "${lineage[0]?.id}" references unknown parent "${parentId}".`);
51
+ lineage.unshift(parent);
52
+ parentId = parent.parentId;
53
+ }
54
+ return defineBreadcrumbTrail(lineage.map((item, index) => ({
55
+ name: item.breadcrumbLabel,
56
+ routeId: item.id,
57
+ ...(index < lineage.length - 1
58
+ ? { href: normalizeBrandlabRoutePath(item.canonicalPath ?? item.path) }
59
+ : {}),
60
+ })));
61
+ }
24
62
  //# sourceMappingURL=breadcrumbs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,OAAO,OAAO;SACX,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAC3D,IAAI,CAAC,GAAG,CAAC,CAAA;AACd,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,OAAiC;IAEjC,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAA;IAEtD,MAAM,YAAY,GAAG,OAAO,EAAE,aAAa,IAAI,kBAAkB,CAAA;IACjE,MAAM,WAAW,GAAqB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAA;IACnE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAA;IACrE,IAAI,WAAW,GAAG,EAAE,CAAA;IAEpB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,WAAW,IAAI,IAAI,OAAO,EAAE,CAAA;QAC5B,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC;YACpC,IAAI,EAAE,WAAW;SAClB,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,WAAW,CAAA;AACpB,CAAC"}
1
+ {"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"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"}