@brandlab-digital/site-core 3.2.1 → 3.3.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 +9 -2
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +3 -2
- package/dist/analytics.js.map +1 -1
- package/dist/google-reviews.d.ts +3 -1
- package/dist/google-reviews.d.ts.map +1 -1
- package/dist/google-reviews.js +1 -0
- package/dist/google-reviews.js.map +1 -1
- package/package.json +1 -1
package/dist/analytics.d.ts
CHANGED
|
@@ -35,13 +35,20 @@ export interface BrandlabAnalyticsContract {
|
|
|
35
35
|
websiteCalls?: {
|
|
36
36
|
conversionLabelEnv: string;
|
|
37
37
|
};
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Enhanced Conversions send the enquirer's email and phone (hashed by the
|
|
40
|
+
* Google tag) with the Ads form conversion.
|
|
41
|
+
* - `consent-required`: only when the site passes a live ad_user_data grant.
|
|
42
|
+
* - `disclosed`: the site's privacy policy discloses this measurement use, so
|
|
43
|
+
* ad_user_data defaults to granted. ad_personalization stays denied.
|
|
44
|
+
*/
|
|
39
45
|
enhancedConversions?: {
|
|
40
|
-
mode:
|
|
46
|
+
mode: BrandlabEnhancedConversionsMode;
|
|
41
47
|
};
|
|
42
48
|
};
|
|
43
49
|
additionalEvents?: readonly BrandlabAnalyticsEventContract[];
|
|
44
50
|
}
|
|
51
|
+
export type BrandlabEnhancedConversionsMode = 'consent-required' | 'disclosed';
|
|
45
52
|
export type GtagTransport = (...args: ['event', string, Record<string, unknown>] | ['set', string, Record<string, unknown>] | ['config', string, Record<string, unknown>?] | ['js', Date]) => void;
|
|
46
53
|
export interface EnhancedConversionUserData {
|
|
47
54
|
email?: string | null;
|
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,2BAA4B,CAAA;AAC9D,eAAO,MAAM,kCAAkC,+BAAgC,CAAA;AAC/E,eAAO,MAAM,+BAA+B,mEAIlC,CAAA;AAEV,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AACvE,MAAM,MAAM,8BAA8B,GACxC,CAAC,OAAO,kCAAkC,CAAC,CAAC,MAAM,CAAC,CAAA;AACrD,MAAM,MAAM,2BAA2B,GACrC,CAAC,OAAO,+BAA+B,CAAC,CAAC,MAAM,CAAC,CAAA;AAElD,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,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE;QACV,eAAe,EAAE,MAAM,CAAA;QACvB,yBAAyB,EAAE,MAAM,CAAA;QACjC,6EAA6E;QAC7E,YAAY,CAAC,EAAE;YACb,kBAAkB,EAAE,MAAM,CAAA;SAC3B,CAAA;QACD
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
|
package/dist/analytics.js
CHANGED
|
@@ -75,8 +75,9 @@ export function defineAnalyticsContract(contract) {
|
|
|
75
75
|
assertAnalyticsEnvKey(contract.googleAds.websiteCalls.conversionLabelEnv, 'googleAds.websiteCalls.conversionLabelEnv');
|
|
76
76
|
}
|
|
77
77
|
if (contract.googleAds.enhancedConversions !== undefined &&
|
|
78
|
-
contract.googleAds.enhancedConversions.mode !== 'consent-required'
|
|
79
|
-
|
|
78
|
+
contract.googleAds.enhancedConversions.mode !== 'consent-required' &&
|
|
79
|
+
contract.googleAds.enhancedConversions.mode !== 'disclosed') {
|
|
80
|
+
throw new Error("googleAds.enhancedConversions.mode must be 'consent-required' or 'disclosed'.");
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
const events = contract.additionalEvents ?? [];
|
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 = ['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\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 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\nconst CONTRACT_KEYS = ['projectId', 'measurementIdEnv', 'googleAds', 'additionalEvents']\nconst GOOGLE_ADS_KEYS = ['conversionIdEnv', 'contactConversionLabelEnv', 'websiteCalls', 'enhancedConversions']\n\nfunction assertContractKeys(value: object, allowed: readonly string[], field: string) {\n const unsupported = Object.keys(value).filter((key) => !allowed.includes(key))\n if (unsupported.length > 0) {\n throw new Error(`${field} contains unsupported keys: ${unsupported.join(', ')}.`)\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 if (!contract || typeof contract !== 'object' || Array.isArray(contract)) {\n throw new Error('Analytics contract must be an object.')\n }\n // Retired 3.0 flags fail here instead of being silently ignored: web vitals\n // were removed (page speed comes from Search Console) and the privacy-safe\n // lead diagnostics are always on.\n assertContractKeys(contract, CONTRACT_KEYS, 'Analytics contract')\n assertBrandlabCanonicalEntityId(contract.projectId)\n assertAnalyticsEnvKey(contract.measurementIdEnv, 'measurementIdEnv')\n if (contract.googleAds) {\n assertContractKeys(contract.googleAds, GOOGLE_ADS_KEYS, '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"]}
|
|
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\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 /**\n * Enhanced Conversions send the enquirer's email and phone (hashed by the\n * Google tag) with the Ads form conversion.\n * - `consent-required`: only when the site passes a live ad_user_data grant.\n * - `disclosed`: the site's privacy policy discloses this measurement use, so\n * ad_user_data defaults to granted. ad_personalization stays denied.\n */\n enhancedConversions?: { mode: BrandlabEnhancedConversionsMode }\n }\n additionalEvents?: readonly BrandlabAnalyticsEventContract[]\n}\nexport type BrandlabEnhancedConversionsMode = 'consent-required' | 'disclosed'\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\nconst CONTRACT_KEYS = ['projectId', 'measurementIdEnv', 'googleAds', 'additionalEvents']\nconst GOOGLE_ADS_KEYS = ['conversionIdEnv', 'contactConversionLabelEnv', 'websiteCalls', 'enhancedConversions']\n\nfunction assertContractKeys(value: object, allowed: readonly string[], field: string) {\n const unsupported = Object.keys(value).filter((key) => !allowed.includes(key))\n if (unsupported.length > 0) {\n throw new Error(`${field} contains unsupported keys: ${unsupported.join(', ')}.`)\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 if (!contract || typeof contract !== 'object' || Array.isArray(contract)) {\n throw new Error('Analytics contract must be an object.')\n }\n // Retired 3.0 flags fail here instead of being silently ignored: web vitals\n // were removed (page speed comes from Search Console) and the privacy-safe\n // lead diagnostics are always on.\n assertContractKeys(contract, CONTRACT_KEYS, 'Analytics contract')\n assertBrandlabCanonicalEntityId(contract.projectId)\n assertAnalyticsEnvKey(contract.measurementIdEnv, 'measurementIdEnv')\n if (contract.googleAds) {\n assertContractKeys(contract.googleAds, GOOGLE_ADS_KEYS, '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 contract.googleAds.enhancedConversions.mode !== 'disclosed'\n ) {\n throw new Error(\"googleAds.enhancedConversions.mode must be 'consent-required' or 'disclosed'.\")\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/google-reviews.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ export declare const GOOGLE_PLACE_REVIEWS_MAX_REVIEWS = 5;
|
|
|
7
7
|
export declare const GOOGLE_PLACE_FEATURED_REVIEWS_MAX_REVIEWS = 3;
|
|
8
8
|
export type GooglePlaceReviewsMode = 'summary' | 'full';
|
|
9
9
|
export type GooglePlaceReviewsSelection = 'relevance' | 'five-star-featured';
|
|
10
|
-
export type GooglePlaceReviewsUnavailableReason = 'missing-api-key' | 'missing-place-id' | 'api-error' | 'invalid-response'
|
|
10
|
+
export type GooglePlaceReviewsUnavailableReason = 'missing-api-key' | 'missing-place-id' | 'api-error' | 'invalid-response'
|
|
11
|
+
/** The request did not come from the site's own page (no `Sec-Fetch-Site: same-origin`), so no Places call was made. */
|
|
12
|
+
| 'not-same-origin';
|
|
11
13
|
export interface GooglePlaceText {
|
|
12
14
|
text?: string;
|
|
13
15
|
languageCode?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-reviews.d.ts","sourceRoot":"","sources":["../src/google-reviews.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,6BAA6B,4CAA4C,CAAA;AACtF,eAAO,MAAM,uCAAuC,qEACgB,CAAA;AACpE,eAAO,MAAM,oCAAoC,wPACsM,CAAA;AACvP,eAAO,MAAM,6BAA6B,gBAAgB,CAAA;AAC1D,eAAO,MAAM,4BAA4B,gBAAgB,CAAA;AACzD,eAAO,MAAM,gCAAgC,IAAI,CAAA;AACjD,eAAO,MAAM,yCAAyC,IAAI,CAAA;AAE1D,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,MAAM,CAAA;AACvD,MAAM,MAAM,2BAA2B,GAAG,WAAW,GAAG,oBAAoB,CAAA;AAE5E,MAAM,MAAM,mCAAmC,GAC3C,iBAAiB,GACjB,kBAAkB,GAClB,WAAW,GACX,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"google-reviews.d.ts","sourceRoot":"","sources":["../src/google-reviews.ts"],"names":[],"mappings":"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"}
|
package/dist/google-reviews.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-reviews.js","sourceRoot":"","sources":["../src/google-reviews.ts"],"names":[],"mappings":"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","sourcesContent":["export const GOOGLE_PLACE_DETAILS_ENDPOINT = 'https://places.googleapis.com/v1/places'\nexport const GOOGLE_PLACE_REVIEWS_SUMMARY_FIELD_MASK =\n 'id,displayName,googleMapsUri,rating,userRatingCount,attributions'\nexport const GOOGLE_PLACE_REVIEWS_FULL_FIELD_MASK =\n 'id,displayName,googleMapsUri,rating,userRatingCount,attributions,reviews.authorAttribution,reviews.relativePublishTimeDescription,reviews.rating,reviews.text,reviews.originalText,reviews.publishTime,reviews.googleMapsUri,reviews.flagContentUri'\nexport const GOOGLE_MAPS_ATTRIBUTION_LABEL = 'Google Maps'\nexport const GOOGLE_MAPS_ATTRIBUTION_TEXT = 'Google Maps'\nexport const GOOGLE_PLACE_REVIEWS_MAX_REVIEWS = 5\nexport const GOOGLE_PLACE_FEATURED_REVIEWS_MAX_REVIEWS = 3\n\nexport type GooglePlaceReviewsMode = 'summary' | 'full'\nexport type GooglePlaceReviewsSelection = 'relevance' | 'five-star-featured'\n\nexport type GooglePlaceReviewsUnavailableReason =\n | 'missing-api-key'\n | 'missing-place-id'\n | 'api-error'\n | 'invalid-response'\n\nexport interface GooglePlaceText {\n text?: string\n languageCode?: string\n}\n\nexport interface GooglePlaceDisplayName {\n text?: string\n languageCode?: string\n}\n\nexport interface GooglePlaceReviewAuthorAttribution {\n displayName?: string\n uri?: string\n photoUri?: string\n}\n\nexport interface GooglePlaceReviewApiReview {\n name?: string\n relativePublishTimeDescription?: string\n rating?: number\n text?: GooglePlaceText\n originalText?: GooglePlaceText\n authorAttribution?: GooglePlaceReviewAuthorAttribution\n publishTime?: string\n googleMapsUri?: string\n flagContentUri?: string\n}\n\nexport interface GooglePlaceDetailsApiResponse {\n id?: string\n displayName?: GooglePlaceDisplayName\n googleMapsUri?: string\n rating?: number\n userRatingCount?: number\n attributions?: GooglePlaceApiAttribution[]\n reviews?: GooglePlaceReviewApiReview[]\n}\n\nexport interface GooglePlaceApiAttribution {\n provider?: string\n providerUri?: string\n}\n\nexport interface GooglePlaceAttribution {\n provider: string\n providerUri?: string\n}\n\nexport interface GooglePlaceReview {\n authorName: string\n authorUri?: string\n authorPhotoUri?: string\n rating?: number\n text: string\n relativePublishTimeDescription?: string\n publishTime?: string\n googleMapsUri: string\n flagContentUri?: string\n}\n\nexport interface GooglePlaceReviewsAttribution {\n label: typeof GOOGLE_MAPS_ATTRIBUTION_LABEL\n text: typeof GOOGLE_MAPS_ATTRIBUTION_TEXT\n}\n\nexport interface GooglePlaceReviewsAvailableSnapshot {\n status: 'available'\n mode: GooglePlaceReviewsMode\n businessName: string\n googleMapsUri: string\n rating?: number\n userRatingCount?: number\n reviews: GooglePlaceReview[]\n thirdPartyAttributions: GooglePlaceAttribution[]\n attribution: GooglePlaceReviewsAttribution\n}\n\nexport interface GooglePlaceReviewsUnavailableSnapshot {\n status: 'unavailable'\n mode: GooglePlaceReviewsMode\n reason: GooglePlaceReviewsUnavailableReason\n googleMapsUri?: string\n attribution: GooglePlaceReviewsAttribution\n}\n\nexport type GooglePlaceReviewsSnapshot =\n | GooglePlaceReviewsAvailableSnapshot\n | GooglePlaceReviewsUnavailableSnapshot\n\nconst GOOGLE_PLACE_REVIEWS_UNAVAILABLE_REASONS = new Set<GooglePlaceReviewsUnavailableReason>([\n 'missing-api-key',\n 'missing-place-id',\n 'api-error',\n 'invalid-response',\n])\n\nexport interface GooglePlaceDetailsRequest {\n url: string\n headers: Record<string, string>\n}\n\nexport interface FetchGooglePlaceReviewsOptions {\n apiKey?: string | null\n placeId?: string | null\n /** Summary avoids the higher-priced reviews field; full returns up to five reviews. */\n mode?: GooglePlaceReviewsMode\n maxReviews?: number\n fallbackGoogleMapsUri?: string | null\n fetcher?: typeof fetch\n /** Bounded upstream timeout; defaults to five seconds. */\n timeoutMs?: number\n}\n\nconst attribution: GooglePlaceReviewsAttribution = {\n label: GOOGLE_MAPS_ATTRIBUTION_LABEL,\n text: GOOGLE_MAPS_ATTRIBUTION_TEXT,\n}\n\nexport function createGooglePlaceReviewsUnavailableSnapshot(\n reason: GooglePlaceReviewsUnavailableReason,\n googleMapsUri?: string | null,\n mode: GooglePlaceReviewsMode = 'full',\n): GooglePlaceReviewsUnavailableSnapshot {\n const safeGoogleMapsUri = normalizeGooglePlacePublicUrl(googleMapsUri)\n return {\n status: 'unavailable',\n mode,\n reason,\n ...(safeGoogleMapsUri ? { googleMapsUri: safeGoogleMapsUri } : {}),\n attribution,\n }\n}\n\nfunction trimToUndefined(value: string | null | undefined) {\n const trimmed = value?.trim()\n return trimmed ? trimmed : undefined\n}\n\nexport function normalizeGooglePlacePublicUrl(value: string | null | undefined) {\n const trimmed = trimToUndefined(value)\n if (!trimmed) return undefined\n\n try {\n const url = new URL(trimmed)\n return url.protocol === 'https:' && !url.username && !url.password ? url.href : undefined\n } catch {\n return undefined\n }\n}\n\nfunction normalizeRating(value: number | null | undefined) {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 && value <= 5\n ? value\n : undefined\n}\n\nfunction normalizeCount(value: number | null | undefined) {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : undefined\n}\n\nfunction clampReviewLimit(value: number | null | undefined) {\n if (typeof value !== 'number' || !Number.isFinite(value)) return GOOGLE_PLACE_REVIEWS_MAX_REVIEWS\n return Math.max(0, Math.min(GOOGLE_PLACE_REVIEWS_MAX_REVIEWS, Math.floor(value)))\n}\n\nexport function normalizeGooglePlaceReviewsMode(\n value: GooglePlaceReviewsMode | null | undefined,\n): GooglePlaceReviewsMode {\n return value === 'summary' ? 'summary' : 'full'\n}\n\nexport function normalizeGooglePlaceReviewsSelection(\n value: GooglePlaceReviewsSelection | null | undefined,\n): GooglePlaceReviewsSelection {\n return value === 'five-star-featured' ? 'five-star-featured' : 'relevance'\n}\n\n/**\n * Selects the approved presentation subset without altering Google's live\n * aggregate rating, review total, or relevance order.\n */\nexport function selectGooglePlaceReviewsSnapshot(\n snapshot: GooglePlaceReviewsAvailableSnapshot,\n selection: GooglePlaceReviewsSelection = 'relevance',\n): GooglePlaceReviewsAvailableSnapshot {\n if (\n snapshot.mode !== 'full' ||\n normalizeGooglePlaceReviewsSelection(selection) === 'relevance'\n ) {\n return snapshot\n }\n\n return {\n ...snapshot,\n reviews: snapshot.reviews\n .filter((review) => review.rating === 5)\n .slice(0, GOOGLE_PLACE_FEATURED_REVIEWS_MAX_REVIEWS),\n }\n}\n\nexport function normalizeGooglePlaceId(placeId: string | null | undefined) {\n const trimmed = trimToUndefined(placeId)\n if (!trimmed) return undefined\n\n return trimmed.replace(/^places\\//, '')\n}\n\nexport function buildGooglePlaceDetailsRequest(options: {\n apiKey: string\n placeId: string\n mode?: GooglePlaceReviewsMode\n}): GooglePlaceDetailsRequest {\n const normalizedPlaceId = normalizeGooglePlaceId(options.placeId)\n const apiKey = trimToUndefined(options.apiKey)\n\n if (!apiKey) {\n throw new Error('GOOGLE_MAPS_PLATFORM_API_KEY is required')\n }\n\n if (!normalizedPlaceId) {\n throw new Error('Google Place ID is required')\n }\n\n return {\n url: `${GOOGLE_PLACE_DETAILS_ENDPOINT}/${encodeURIComponent(normalizedPlaceId)}`,\n headers: {\n 'X-Goog-Api-Key': apiKey,\n // The only supported masks are platform-owned. Full mode keeps review,\n // author, source, report and provider attribution inseparable; summary\n // mode avoids requesting the higher-priced reviews field entirely.\n 'X-Goog-FieldMask': normalizeGooglePlaceReviewsMode(options.mode) === 'summary'\n ? GOOGLE_PLACE_REVIEWS_SUMMARY_FIELD_MASK\n : GOOGLE_PLACE_REVIEWS_FULL_FIELD_MASK,\n },\n }\n}\n\nexport function normalizeGooglePlaceReview(\n review: GooglePlaceReviewApiReview,\n): GooglePlaceReview | null {\n const authorName = trimToUndefined(review.authorAttribution?.displayName)\n const authorUri = normalizeGooglePlacePublicUrl(review.authorAttribution?.uri)\n const authorPhotoUri = normalizeGooglePlacePublicUrl(review.authorAttribution?.photoUri)\n const rating = normalizeRating(review.rating)\n const relativePublishTimeDescription = trimToUndefined(review.relativePublishTimeDescription)\n const publishTime = trimToUndefined(review.publishTime)\n const googleMapsUri = normalizeGooglePlacePublicUrl(review.googleMapsUri)\n const flagContentUri = normalizeGooglePlacePublicUrl(review.flagContentUri)\n const text = trimToUndefined(review.text?.text)\n\n if (!authorName || !text || !googleMapsUri) {\n return null\n }\n\n return {\n authorName,\n ...(authorUri ? { authorUri } : {}),\n ...(authorPhotoUri ? { authorPhotoUri } : {}),\n ...(rating ? { rating } : {}),\n text,\n ...(relativePublishTimeDescription ? { relativePublishTimeDescription } : {}),\n ...(publishTime ? { publishTime } : {}),\n googleMapsUri,\n ...(flagContentUri ? { flagContentUri } : {}),\n }\n}\n\nexport function normalizeGooglePlaceReviewsResponse(\n response: GooglePlaceDetailsApiResponse,\n options: {\n mode?: GooglePlaceReviewsMode\n maxReviews?: number\n fallbackGoogleMapsUri?: string | null\n } = {},\n): GooglePlaceReviewsSnapshot {\n const businessName = trimToUndefined(response.displayName?.text)\n const mode = normalizeGooglePlaceReviewsMode(options.mode)\n const googleMapsUri = normalizeGooglePlacePublicUrl(response.googleMapsUri) ??\n normalizeGooglePlacePublicUrl(options.fallbackGoogleMapsUri)\n const rating = normalizeRating(response.rating)\n const userRatingCount = normalizeCount(response.userRatingCount)\n\n if (!businessName || !googleMapsUri) {\n return createGooglePlaceReviewsUnavailableSnapshot('invalid-response', googleMapsUri, mode)\n }\n\n const reviews = (mode === 'full' ? response.reviews ?? [] : [])\n .map((review) => normalizeGooglePlaceReview(review))\n .filter((review): review is GooglePlaceReview => Boolean(review))\n .slice(0, clampReviewLimit(options.maxReviews))\n const thirdPartyAttributions = (response.attributions ?? [])\n .map((item): GooglePlaceAttribution | null => {\n const provider = trimToUndefined(item.provider)\n if (!provider) return null\n const providerUri = normalizeGooglePlacePublicUrl(item.providerUri)\n return { provider, ...(providerUri ? { providerUri } : {}) }\n })\n .filter((item): item is GooglePlaceAttribution => Boolean(item))\n\n return {\n status: 'available',\n mode,\n businessName,\n googleMapsUri,\n ...(rating ? { rating } : {}),\n ...(userRatingCount !== undefined ? { userRatingCount } : {}),\n reviews,\n thirdPartyAttributions,\n attribution,\n }\n}\n\n/**\n * Treats the public route response as untrusted input before it reaches React.\n * Unknown fields are discarded and incomplete review attribution is omitted.\n */\nexport function parseGooglePlaceReviewsSnapshot(\n value: unknown,\n expectedMode?: GooglePlaceReviewsMode,\n): GooglePlaceReviewsSnapshot | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null\n const record = value as Record<string, unknown>\n const mode = record.mode === 'summary' || record.mode === 'full'\n ? record.mode\n : null\n if (!mode || (expectedMode && mode !== expectedMode)) return null\n if (\n !record.attribution ||\n typeof record.attribution !== 'object' ||\n Array.isArray(record.attribution)\n ) {\n return null\n }\n const rawAttribution = record.attribution as Record<string, unknown>\n if (\n rawAttribution.label !== GOOGLE_MAPS_ATTRIBUTION_LABEL ||\n rawAttribution.text !== GOOGLE_MAPS_ATTRIBUTION_TEXT\n ) {\n return null\n }\n\n if (record.status === 'unavailable') {\n if (\n typeof record.reason !== 'string' ||\n !GOOGLE_PLACE_REVIEWS_UNAVAILABLE_REASONS.has(\n record.reason as GooglePlaceReviewsUnavailableReason,\n )\n ) {\n return null\n }\n return createGooglePlaceReviewsUnavailableSnapshot(\n record.reason as GooglePlaceReviewsUnavailableReason,\n typeof record.googleMapsUri === 'string' ? record.googleMapsUri : undefined,\n mode,\n )\n }\n if (record.status !== 'available') return null\n\n const businessName = typeof record.businessName === 'string'\n ? trimToUndefined(record.businessName)\n : undefined\n const googleMapsUri = typeof record.googleMapsUri === 'string'\n ? normalizeGooglePlacePublicUrl(record.googleMapsUri)\n : undefined\n if (!businessName || !googleMapsUri) return null\n const rating = typeof record.rating === 'number' ? normalizeRating(record.rating) : undefined\n const userRatingCount = typeof record.userRatingCount === 'number'\n ? normalizeCount(record.userRatingCount)\n : undefined\n const rawReviews = Array.isArray(record.reviews) ? record.reviews : null\n const rawAttributions = Array.isArray(record.thirdPartyAttributions)\n ? record.thirdPartyAttributions\n : null\n if (!rawReviews || !rawAttributions || (mode === 'summary' && rawReviews.length > 0)) {\n return null\n }\n\n const reviews = rawReviews\n .map((item): GooglePlaceReview | null => {\n if (!item || typeof item !== 'object' || Array.isArray(item)) return null\n const review = item as Record<string, unknown>\n return normalizeGooglePlaceReview({\n authorAttribution: {\n displayName: typeof review.authorName === 'string' ? review.authorName : undefined,\n uri: typeof review.authorUri === 'string' ? review.authorUri : undefined,\n photoUri: typeof review.authorPhotoUri === 'string' ? review.authorPhotoUri : undefined,\n },\n rating: typeof review.rating === 'number' ? review.rating : undefined,\n text: { text: typeof review.text === 'string' ? review.text : undefined },\n relativePublishTimeDescription:\n typeof review.relativePublishTimeDescription === 'string'\n ? review.relativePublishTimeDescription\n : undefined,\n publishTime: typeof review.publishTime === 'string' ? review.publishTime : undefined,\n googleMapsUri:\n typeof review.googleMapsUri === 'string' ? review.googleMapsUri : undefined,\n flagContentUri:\n typeof review.flagContentUri === 'string' ? review.flagContentUri : undefined,\n })\n })\n .filter((review): review is GooglePlaceReview => Boolean(review))\n .slice(0, GOOGLE_PLACE_REVIEWS_MAX_REVIEWS)\n if (mode === 'full' && reviews.length !== rawReviews.length) return null\n\n const thirdPartyAttributions = rawAttributions\n .map((item): GooglePlaceAttribution | null => {\n if (!item || typeof item !== 'object' || Array.isArray(item)) return null\n const raw = item as Record<string, unknown>\n const provider = typeof raw.provider === 'string' ? trimToUndefined(raw.provider) : undefined\n if (!provider) return null\n const providerUri = typeof raw.providerUri === 'string'\n ? normalizeGooglePlacePublicUrl(raw.providerUri)\n : undefined\n return { provider, ...(providerUri ? { providerUri } : {}) }\n })\n .filter((item): item is GooglePlaceAttribution => Boolean(item))\n if (thirdPartyAttributions.length !== rawAttributions.length) return null\n\n return {\n status: 'available',\n mode,\n businessName,\n googleMapsUri,\n ...(rating ? { rating } : {}),\n ...(userRatingCount !== undefined ? { userRatingCount } : {}),\n reviews,\n thirdPartyAttributions,\n attribution,\n }\n}\n\nexport async function fetchGooglePlaceReviewsSnapshot(\n options: FetchGooglePlaceReviewsOptions,\n): Promise<GooglePlaceReviewsSnapshot> {\n const apiKey = trimToUndefined(options.apiKey)\n const placeId = normalizeGooglePlaceId(options.placeId)\n const mode = normalizeGooglePlaceReviewsMode(options.mode)\n\n if (!apiKey) {\n return createGooglePlaceReviewsUnavailableSnapshot(\n 'missing-api-key',\n options.fallbackGoogleMapsUri,\n mode,\n )\n }\n\n if (!placeId) {\n return createGooglePlaceReviewsUnavailableSnapshot(\n 'missing-place-id',\n options.fallbackGoogleMapsUri,\n mode,\n )\n }\n\n try {\n const request = buildGooglePlaceDetailsRequest({\n apiKey,\n placeId,\n mode,\n })\n const fetcher = options.fetcher ?? fetch\n const timeoutMs = options.timeoutMs ?? 5_000\n if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 20_000) {\n throw new Error('Google Places timeout must be between 100 and 20000 milliseconds.')\n }\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n let response: Response\n try {\n response = await fetcher(request.url, {\n cache: 'no-store',\n headers: request.headers,\n signal: controller.signal,\n })\n } finally {\n clearTimeout(timeout)\n }\n\n if (!response.ok) {\n return createGooglePlaceReviewsUnavailableSnapshot(\n 'api-error',\n options.fallbackGoogleMapsUri,\n mode,\n )\n }\n\n const json = (await response.json()) as GooglePlaceDetailsApiResponse\n\n return normalizeGooglePlaceReviewsResponse(json, {\n mode,\n maxReviews: options.maxReviews,\n fallbackGoogleMapsUri: options.fallbackGoogleMapsUri,\n })\n } catch {\n return createGooglePlaceReviewsUnavailableSnapshot(\n 'api-error',\n options.fallbackGoogleMapsUri,\n mode,\n )\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"google-reviews.js","sourceRoot":"","sources":["../src/google-reviews.ts"],"names":[],"mappings":"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","sourcesContent":["export const GOOGLE_PLACE_DETAILS_ENDPOINT = 'https://places.googleapis.com/v1/places'\nexport const GOOGLE_PLACE_REVIEWS_SUMMARY_FIELD_MASK =\n 'id,displayName,googleMapsUri,rating,userRatingCount,attributions'\nexport const GOOGLE_PLACE_REVIEWS_FULL_FIELD_MASK =\n 'id,displayName,googleMapsUri,rating,userRatingCount,attributions,reviews.authorAttribution,reviews.relativePublishTimeDescription,reviews.rating,reviews.text,reviews.originalText,reviews.publishTime,reviews.googleMapsUri,reviews.flagContentUri'\nexport const GOOGLE_MAPS_ATTRIBUTION_LABEL = 'Google Maps'\nexport const GOOGLE_MAPS_ATTRIBUTION_TEXT = 'Google Maps'\nexport const GOOGLE_PLACE_REVIEWS_MAX_REVIEWS = 5\nexport const GOOGLE_PLACE_FEATURED_REVIEWS_MAX_REVIEWS = 3\n\nexport type GooglePlaceReviewsMode = 'summary' | 'full'\nexport type GooglePlaceReviewsSelection = 'relevance' | 'five-star-featured'\n\nexport type GooglePlaceReviewsUnavailableReason =\n | 'missing-api-key'\n | 'missing-place-id'\n | 'api-error'\n | 'invalid-response'\n /** The request did not come from the site's own page (no `Sec-Fetch-Site: same-origin`), so no Places call was made. */\n | 'not-same-origin'\n\nexport interface GooglePlaceText {\n text?: string\n languageCode?: string\n}\n\nexport interface GooglePlaceDisplayName {\n text?: string\n languageCode?: string\n}\n\nexport interface GooglePlaceReviewAuthorAttribution {\n displayName?: string\n uri?: string\n photoUri?: string\n}\n\nexport interface GooglePlaceReviewApiReview {\n name?: string\n relativePublishTimeDescription?: string\n rating?: number\n text?: GooglePlaceText\n originalText?: GooglePlaceText\n authorAttribution?: GooglePlaceReviewAuthorAttribution\n publishTime?: string\n googleMapsUri?: string\n flagContentUri?: string\n}\n\nexport interface GooglePlaceDetailsApiResponse {\n id?: string\n displayName?: GooglePlaceDisplayName\n googleMapsUri?: string\n rating?: number\n userRatingCount?: number\n attributions?: GooglePlaceApiAttribution[]\n reviews?: GooglePlaceReviewApiReview[]\n}\n\nexport interface GooglePlaceApiAttribution {\n provider?: string\n providerUri?: string\n}\n\nexport interface GooglePlaceAttribution {\n provider: string\n providerUri?: string\n}\n\nexport interface GooglePlaceReview {\n authorName: string\n authorUri?: string\n authorPhotoUri?: string\n rating?: number\n text: string\n relativePublishTimeDescription?: string\n publishTime?: string\n googleMapsUri: string\n flagContentUri?: string\n}\n\nexport interface GooglePlaceReviewsAttribution {\n label: typeof GOOGLE_MAPS_ATTRIBUTION_LABEL\n text: typeof GOOGLE_MAPS_ATTRIBUTION_TEXT\n}\n\nexport interface GooglePlaceReviewsAvailableSnapshot {\n status: 'available'\n mode: GooglePlaceReviewsMode\n businessName: string\n googleMapsUri: string\n rating?: number\n userRatingCount?: number\n reviews: GooglePlaceReview[]\n thirdPartyAttributions: GooglePlaceAttribution[]\n attribution: GooglePlaceReviewsAttribution\n}\n\nexport interface GooglePlaceReviewsUnavailableSnapshot {\n status: 'unavailable'\n mode: GooglePlaceReviewsMode\n reason: GooglePlaceReviewsUnavailableReason\n googleMapsUri?: string\n attribution: GooglePlaceReviewsAttribution\n}\n\nexport type GooglePlaceReviewsSnapshot =\n | GooglePlaceReviewsAvailableSnapshot\n | GooglePlaceReviewsUnavailableSnapshot\n\nconst GOOGLE_PLACE_REVIEWS_UNAVAILABLE_REASONS = new Set<GooglePlaceReviewsUnavailableReason>([\n 'missing-api-key',\n 'missing-place-id',\n 'api-error',\n 'invalid-response',\n 'not-same-origin',\n])\n\nexport interface GooglePlaceDetailsRequest {\n url: string\n headers: Record<string, string>\n}\n\nexport interface FetchGooglePlaceReviewsOptions {\n apiKey?: string | null\n placeId?: string | null\n /** Summary avoids the higher-priced reviews field; full returns up to five reviews. */\n mode?: GooglePlaceReviewsMode\n maxReviews?: number\n fallbackGoogleMapsUri?: string | null\n fetcher?: typeof fetch\n /** Bounded upstream timeout; defaults to five seconds. */\n timeoutMs?: number\n}\n\nconst attribution: GooglePlaceReviewsAttribution = {\n label: GOOGLE_MAPS_ATTRIBUTION_LABEL,\n text: GOOGLE_MAPS_ATTRIBUTION_TEXT,\n}\n\nexport function createGooglePlaceReviewsUnavailableSnapshot(\n reason: GooglePlaceReviewsUnavailableReason,\n googleMapsUri?: string | null,\n mode: GooglePlaceReviewsMode = 'full',\n): GooglePlaceReviewsUnavailableSnapshot {\n const safeGoogleMapsUri = normalizeGooglePlacePublicUrl(googleMapsUri)\n return {\n status: 'unavailable',\n mode,\n reason,\n ...(safeGoogleMapsUri ? { googleMapsUri: safeGoogleMapsUri } : {}),\n attribution,\n }\n}\n\nfunction trimToUndefined(value: string | null | undefined) {\n const trimmed = value?.trim()\n return trimmed ? trimmed : undefined\n}\n\nexport function normalizeGooglePlacePublicUrl(value: string | null | undefined) {\n const trimmed = trimToUndefined(value)\n if (!trimmed) return undefined\n\n try {\n const url = new URL(trimmed)\n return url.protocol === 'https:' && !url.username && !url.password ? url.href : undefined\n } catch {\n return undefined\n }\n}\n\nfunction normalizeRating(value: number | null | undefined) {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 && value <= 5\n ? value\n : undefined\n}\n\nfunction normalizeCount(value: number | null | undefined) {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : undefined\n}\n\nfunction clampReviewLimit(value: number | null | undefined) {\n if (typeof value !== 'number' || !Number.isFinite(value)) return GOOGLE_PLACE_REVIEWS_MAX_REVIEWS\n return Math.max(0, Math.min(GOOGLE_PLACE_REVIEWS_MAX_REVIEWS, Math.floor(value)))\n}\n\nexport function normalizeGooglePlaceReviewsMode(\n value: GooglePlaceReviewsMode | null | undefined,\n): GooglePlaceReviewsMode {\n return value === 'summary' ? 'summary' : 'full'\n}\n\nexport function normalizeGooglePlaceReviewsSelection(\n value: GooglePlaceReviewsSelection | null | undefined,\n): GooglePlaceReviewsSelection {\n return value === 'five-star-featured' ? 'five-star-featured' : 'relevance'\n}\n\n/**\n * Selects the approved presentation subset without altering Google's live\n * aggregate rating, review total, or relevance order.\n */\nexport function selectGooglePlaceReviewsSnapshot(\n snapshot: GooglePlaceReviewsAvailableSnapshot,\n selection: GooglePlaceReviewsSelection = 'relevance',\n): GooglePlaceReviewsAvailableSnapshot {\n if (\n snapshot.mode !== 'full' ||\n normalizeGooglePlaceReviewsSelection(selection) === 'relevance'\n ) {\n return snapshot\n }\n\n return {\n ...snapshot,\n reviews: snapshot.reviews\n .filter((review) => review.rating === 5)\n .slice(0, GOOGLE_PLACE_FEATURED_REVIEWS_MAX_REVIEWS),\n }\n}\n\nexport function normalizeGooglePlaceId(placeId: string | null | undefined) {\n const trimmed = trimToUndefined(placeId)\n if (!trimmed) return undefined\n\n return trimmed.replace(/^places\\//, '')\n}\n\nexport function buildGooglePlaceDetailsRequest(options: {\n apiKey: string\n placeId: string\n mode?: GooglePlaceReviewsMode\n}): GooglePlaceDetailsRequest {\n const normalizedPlaceId = normalizeGooglePlaceId(options.placeId)\n const apiKey = trimToUndefined(options.apiKey)\n\n if (!apiKey) {\n throw new Error('GOOGLE_MAPS_PLATFORM_API_KEY is required')\n }\n\n if (!normalizedPlaceId) {\n throw new Error('Google Place ID is required')\n }\n\n return {\n url: `${GOOGLE_PLACE_DETAILS_ENDPOINT}/${encodeURIComponent(normalizedPlaceId)}`,\n headers: {\n 'X-Goog-Api-Key': apiKey,\n // The only supported masks are platform-owned. Full mode keeps review,\n // author, source, report and provider attribution inseparable; summary\n // mode avoids requesting the higher-priced reviews field entirely.\n 'X-Goog-FieldMask': normalizeGooglePlaceReviewsMode(options.mode) === 'summary'\n ? GOOGLE_PLACE_REVIEWS_SUMMARY_FIELD_MASK\n : GOOGLE_PLACE_REVIEWS_FULL_FIELD_MASK,\n },\n }\n}\n\nexport function normalizeGooglePlaceReview(\n review: GooglePlaceReviewApiReview,\n): GooglePlaceReview | null {\n const authorName = trimToUndefined(review.authorAttribution?.displayName)\n const authorUri = normalizeGooglePlacePublicUrl(review.authorAttribution?.uri)\n const authorPhotoUri = normalizeGooglePlacePublicUrl(review.authorAttribution?.photoUri)\n const rating = normalizeRating(review.rating)\n const relativePublishTimeDescription = trimToUndefined(review.relativePublishTimeDescription)\n const publishTime = trimToUndefined(review.publishTime)\n const googleMapsUri = normalizeGooglePlacePublicUrl(review.googleMapsUri)\n const flagContentUri = normalizeGooglePlacePublicUrl(review.flagContentUri)\n const text = trimToUndefined(review.text?.text)\n\n if (!authorName || !text || !googleMapsUri) {\n return null\n }\n\n return {\n authorName,\n ...(authorUri ? { authorUri } : {}),\n ...(authorPhotoUri ? { authorPhotoUri } : {}),\n ...(rating ? { rating } : {}),\n text,\n ...(relativePublishTimeDescription ? { relativePublishTimeDescription } : {}),\n ...(publishTime ? { publishTime } : {}),\n googleMapsUri,\n ...(flagContentUri ? { flagContentUri } : {}),\n }\n}\n\nexport function normalizeGooglePlaceReviewsResponse(\n response: GooglePlaceDetailsApiResponse,\n options: {\n mode?: GooglePlaceReviewsMode\n maxReviews?: number\n fallbackGoogleMapsUri?: string | null\n } = {},\n): GooglePlaceReviewsSnapshot {\n const businessName = trimToUndefined(response.displayName?.text)\n const mode = normalizeGooglePlaceReviewsMode(options.mode)\n const googleMapsUri = normalizeGooglePlacePublicUrl(response.googleMapsUri) ??\n normalizeGooglePlacePublicUrl(options.fallbackGoogleMapsUri)\n const rating = normalizeRating(response.rating)\n const userRatingCount = normalizeCount(response.userRatingCount)\n\n if (!businessName || !googleMapsUri) {\n return createGooglePlaceReviewsUnavailableSnapshot('invalid-response', googleMapsUri, mode)\n }\n\n const reviews = (mode === 'full' ? response.reviews ?? [] : [])\n .map((review) => normalizeGooglePlaceReview(review))\n .filter((review): review is GooglePlaceReview => Boolean(review))\n .slice(0, clampReviewLimit(options.maxReviews))\n const thirdPartyAttributions = (response.attributions ?? [])\n .map((item): GooglePlaceAttribution | null => {\n const provider = trimToUndefined(item.provider)\n if (!provider) return null\n const providerUri = normalizeGooglePlacePublicUrl(item.providerUri)\n return { provider, ...(providerUri ? { providerUri } : {}) }\n })\n .filter((item): item is GooglePlaceAttribution => Boolean(item))\n\n return {\n status: 'available',\n mode,\n businessName,\n googleMapsUri,\n ...(rating ? { rating } : {}),\n ...(userRatingCount !== undefined ? { userRatingCount } : {}),\n reviews,\n thirdPartyAttributions,\n attribution,\n }\n}\n\n/**\n * Treats the public route response as untrusted input before it reaches React.\n * Unknown fields are discarded and incomplete review attribution is omitted.\n */\nexport function parseGooglePlaceReviewsSnapshot(\n value: unknown,\n expectedMode?: GooglePlaceReviewsMode,\n): GooglePlaceReviewsSnapshot | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null\n const record = value as Record<string, unknown>\n const mode = record.mode === 'summary' || record.mode === 'full'\n ? record.mode\n : null\n if (!mode || (expectedMode && mode !== expectedMode)) return null\n if (\n !record.attribution ||\n typeof record.attribution !== 'object' ||\n Array.isArray(record.attribution)\n ) {\n return null\n }\n const rawAttribution = record.attribution as Record<string, unknown>\n if (\n rawAttribution.label !== GOOGLE_MAPS_ATTRIBUTION_LABEL ||\n rawAttribution.text !== GOOGLE_MAPS_ATTRIBUTION_TEXT\n ) {\n return null\n }\n\n if (record.status === 'unavailable') {\n if (\n typeof record.reason !== 'string' ||\n !GOOGLE_PLACE_REVIEWS_UNAVAILABLE_REASONS.has(\n record.reason as GooglePlaceReviewsUnavailableReason,\n )\n ) {\n return null\n }\n return createGooglePlaceReviewsUnavailableSnapshot(\n record.reason as GooglePlaceReviewsUnavailableReason,\n typeof record.googleMapsUri === 'string' ? record.googleMapsUri : undefined,\n mode,\n )\n }\n if (record.status !== 'available') return null\n\n const businessName = typeof record.businessName === 'string'\n ? trimToUndefined(record.businessName)\n : undefined\n const googleMapsUri = typeof record.googleMapsUri === 'string'\n ? normalizeGooglePlacePublicUrl(record.googleMapsUri)\n : undefined\n if (!businessName || !googleMapsUri) return null\n const rating = typeof record.rating === 'number' ? normalizeRating(record.rating) : undefined\n const userRatingCount = typeof record.userRatingCount === 'number'\n ? normalizeCount(record.userRatingCount)\n : undefined\n const rawReviews = Array.isArray(record.reviews) ? record.reviews : null\n const rawAttributions = Array.isArray(record.thirdPartyAttributions)\n ? record.thirdPartyAttributions\n : null\n if (!rawReviews || !rawAttributions || (mode === 'summary' && rawReviews.length > 0)) {\n return null\n }\n\n const reviews = rawReviews\n .map((item): GooglePlaceReview | null => {\n if (!item || typeof item !== 'object' || Array.isArray(item)) return null\n const review = item as Record<string, unknown>\n return normalizeGooglePlaceReview({\n authorAttribution: {\n displayName: typeof review.authorName === 'string' ? review.authorName : undefined,\n uri: typeof review.authorUri === 'string' ? review.authorUri : undefined,\n photoUri: typeof review.authorPhotoUri === 'string' ? review.authorPhotoUri : undefined,\n },\n rating: typeof review.rating === 'number' ? review.rating : undefined,\n text: { text: typeof review.text === 'string' ? review.text : undefined },\n relativePublishTimeDescription:\n typeof review.relativePublishTimeDescription === 'string'\n ? review.relativePublishTimeDescription\n : undefined,\n publishTime: typeof review.publishTime === 'string' ? review.publishTime : undefined,\n googleMapsUri:\n typeof review.googleMapsUri === 'string' ? review.googleMapsUri : undefined,\n flagContentUri:\n typeof review.flagContentUri === 'string' ? review.flagContentUri : undefined,\n })\n })\n .filter((review): review is GooglePlaceReview => Boolean(review))\n .slice(0, GOOGLE_PLACE_REVIEWS_MAX_REVIEWS)\n if (mode === 'full' && reviews.length !== rawReviews.length) return null\n\n const thirdPartyAttributions = rawAttributions\n .map((item): GooglePlaceAttribution | null => {\n if (!item || typeof item !== 'object' || Array.isArray(item)) return null\n const raw = item as Record<string, unknown>\n const provider = typeof raw.provider === 'string' ? trimToUndefined(raw.provider) : undefined\n if (!provider) return null\n const providerUri = typeof raw.providerUri === 'string'\n ? normalizeGooglePlacePublicUrl(raw.providerUri)\n : undefined\n return { provider, ...(providerUri ? { providerUri } : {}) }\n })\n .filter((item): item is GooglePlaceAttribution => Boolean(item))\n if (thirdPartyAttributions.length !== rawAttributions.length) return null\n\n return {\n status: 'available',\n mode,\n businessName,\n googleMapsUri,\n ...(rating ? { rating } : {}),\n ...(userRatingCount !== undefined ? { userRatingCount } : {}),\n reviews,\n thirdPartyAttributions,\n attribution,\n }\n}\n\nexport async function fetchGooglePlaceReviewsSnapshot(\n options: FetchGooglePlaceReviewsOptions,\n): Promise<GooglePlaceReviewsSnapshot> {\n const apiKey = trimToUndefined(options.apiKey)\n const placeId = normalizeGooglePlaceId(options.placeId)\n const mode = normalizeGooglePlaceReviewsMode(options.mode)\n\n if (!apiKey) {\n return createGooglePlaceReviewsUnavailableSnapshot(\n 'missing-api-key',\n options.fallbackGoogleMapsUri,\n mode,\n )\n }\n\n if (!placeId) {\n return createGooglePlaceReviewsUnavailableSnapshot(\n 'missing-place-id',\n options.fallbackGoogleMapsUri,\n mode,\n )\n }\n\n try {\n const request = buildGooglePlaceDetailsRequest({\n apiKey,\n placeId,\n mode,\n })\n const fetcher = options.fetcher ?? fetch\n const timeoutMs = options.timeoutMs ?? 5_000\n if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 20_000) {\n throw new Error('Google Places timeout must be between 100 and 20000 milliseconds.')\n }\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n let response: Response\n try {\n response = await fetcher(request.url, {\n cache: 'no-store',\n headers: request.headers,\n signal: controller.signal,\n })\n } finally {\n clearTimeout(timeout)\n }\n\n if (!response.ok) {\n return createGooglePlaceReviewsUnavailableSnapshot(\n 'api-error',\n options.fallbackGoogleMapsUri,\n mode,\n )\n }\n\n const json = (await response.json()) as GooglePlaceDetailsApiResponse\n\n return normalizeGooglePlaceReviewsResponse(json, {\n mode,\n maxReviews: options.maxReviews,\n fallbackGoogleMapsUri: options.fallbackGoogleMapsUri,\n })\n } catch {\n return createGooglePlaceReviewsUnavailableSnapshot(\n 'api-error',\n options.fallbackGoogleMapsUri,\n mode,\n )\n }\n}\n"]}
|