@brandlab-digital/site-core 3.2.0 → 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/business.d.ts +40 -0
- package/dist/business.d.ts.map +1 -1
- package/dist/business.js +175 -79
- package/dist/business.js.map +1 -1
- package/dist/geography.d.ts +2 -0
- package/dist/geography.d.ts.map +1 -1
- package/dist/geography.js +0 -18
- package/dist/geography.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/dist/site.d.ts +11 -1
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +24 -15
- package/dist/site.js.map +1 -1
- package/package.json +1 -1
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"]}
|
package/dist/site.d.ts
CHANGED
|
@@ -38,8 +38,12 @@ export interface BrandlabSiteDefinition {
|
|
|
38
38
|
/**
|
|
39
39
|
* Hosts that permanently redirect every path to the canonical origin, such as
|
|
40
40
|
* the `www` host, the Vercel project host or a retired domain.
|
|
41
|
+
*
|
|
42
|
+
* Optional from 3.2.1: declare them in next.config with
|
|
43
|
+
* `defineBrandlabNextConfig({ aliasHosts })` instead, so they stay out of the
|
|
44
|
+
* browser bundles of every client component that imports the site identity.
|
|
41
45
|
*/
|
|
42
|
-
aliasHosts
|
|
46
|
+
aliasHosts?: readonly string[];
|
|
43
47
|
}
|
|
44
48
|
export interface BrandlabSite {
|
|
45
49
|
readonly name: string;
|
|
@@ -57,4 +61,10 @@ export declare function isBrandlabHostname(value: unknown): value is string;
|
|
|
57
61
|
* feeds metadata, robots, sitemaps, redirects and next.config.
|
|
58
62
|
*/
|
|
59
63
|
export declare function defineBrandlabSite(definition: BrandlabSiteDefinition): BrandlabSite;
|
|
64
|
+
/**
|
|
65
|
+
* Checks a list of alias hosts: lowercase hostnames without a scheme, port or
|
|
66
|
+
* path, never the canonical host, each listed once. `defineBrandlabSite` and
|
|
67
|
+
* `defineBrandlabNextConfig({ aliasHosts })` share it.
|
|
68
|
+
*/
|
|
69
|
+
export declare function assertBrandlabAliasHosts(hosts: unknown, canonicalHost: string, field?: string): asserts hosts is readonly string[];
|
|
60
70
|
//# sourceMappingURL=site.d.ts.map
|
package/dist/site.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,eAAe,CAAA;AAEtB;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG,YAAY,GAAG,SAAS,GAAG,aAAa,CAAA;AAEpF,MAAM,WAAW,kBAAkB;IACjC,2GAA2G;IAC3G,QAAQ,CAAC,WAAW,EAAE,6BAA6B,CAAA;IACnD;;;;OAIG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAClC;AAED,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;AAE9E;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,mBAAmB,GAAG,kBAAkB,CAe9F;AAED,MAAM,WAAW,sBAAsB;IACrC,wFAAwF;IACxF,IAAI,EAAE,MAAM,CAAA;IACZ,4FAA4F;IAC5F,YAAY,EAAE,MAAM,CAAA;IACpB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,sFAAsF;IACtF,aAAa,EAAE,MAAM,CAAA;IACrB,kBAAkB,EAAE,mBAAmB,CAAA;IACvC
|
|
1
|
+
{"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,eAAe,CAAA;AAEtB;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG,YAAY,GAAG,SAAS,GAAG,aAAa,CAAA;AAEpF,MAAM,WAAW,kBAAkB;IACjC,2GAA2G;IAC3G,QAAQ,CAAC,WAAW,EAAE,6BAA6B,CAAA;IACnD;;;;OAIG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAClC;AAED,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;AAE9E;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,mBAAmB,GAAG,kBAAkB,CAe9F;AAED,MAAM,WAAW,sBAAsB;IACrC,wFAAwF;IACxF,IAAI,EAAE,MAAM,CAAA;IACZ,4FAA4F;IAC5F,YAAY,EAAE,MAAM,CAAA;IACpB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,sFAAsF;IACtF,aAAa,EAAE,MAAM,CAAA;IACrB,kBAAkB,EAAE,mBAAmB,CAAA;IACvC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,uFAAuF;IACvF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,kBAAkB,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAA;IAC1D,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;CACvC;AAyBD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAElE;AA0BD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,sBAAsB,GAAG,YAAY,CA+BnF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,aAAa,EAAE,MAAM,EACrB,KAAK,SAAe,GACnB,OAAO,CAAC,KAAK,IAAI,SAAS,MAAM,EAAE,CAcpC"}
|
package/dist/site.js
CHANGED
|
@@ -89,20 +89,8 @@ export function defineBrandlabSite(definition) {
|
|
|
89
89
|
throw new Error('titleTemplate must contain exactly one %s placeholder.');
|
|
90
90
|
}
|
|
91
91
|
assertBrandlabSocialImage(definition.defaultSocialImage, 'defaultSocialImage');
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
definition.aliasHosts.forEach((host, index) => {
|
|
96
|
-
if (!isBrandlabHostname(host)) {
|
|
97
|
-
throw new Error(`aliasHosts[${index}] must be a lowercase hostname without a scheme, port or path.`);
|
|
98
|
-
}
|
|
99
|
-
if (host === origin.hostname) {
|
|
100
|
-
throw new Error(`aliasHosts[${index}] cannot be the canonical host.`);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
const duplicate = definition.aliasHosts.find((host, index) => definition.aliasHosts.indexOf(host) !== index);
|
|
104
|
-
if (duplicate)
|
|
105
|
-
throw new Error(`aliasHosts contains duplicate host "${duplicate}".`);
|
|
92
|
+
const aliasHosts = definition.aliasHosts ?? [];
|
|
93
|
+
assertBrandlabAliasHosts(aliasHosts, origin.hostname);
|
|
106
94
|
return Object.freeze({
|
|
107
95
|
name: definition.name,
|
|
108
96
|
canonicalUrl: origin.origin,
|
|
@@ -110,7 +98,28 @@ export function defineBrandlabSite(definition) {
|
|
|
110
98
|
locale: definition.locale,
|
|
111
99
|
titleTemplate: definition.titleTemplate,
|
|
112
100
|
defaultSocialImage: Object.freeze({ ...definition.defaultSocialImage }),
|
|
113
|
-
aliasHosts: Object.freeze([...
|
|
101
|
+
aliasHosts: Object.freeze([...aliasHosts]),
|
|
114
102
|
});
|
|
115
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Checks a list of alias hosts: lowercase hostnames without a scheme, port or
|
|
106
|
+
* path, never the canonical host, each listed once. `defineBrandlabSite` and
|
|
107
|
+
* `defineBrandlabNextConfig({ aliasHosts })` share it.
|
|
108
|
+
*/
|
|
109
|
+
export function assertBrandlabAliasHosts(hosts, canonicalHost, field = 'aliasHosts') {
|
|
110
|
+
if (!Array.isArray(hosts)) {
|
|
111
|
+
throw new Error(`${field} must list every host that redirects to the canonical origin.`);
|
|
112
|
+
}
|
|
113
|
+
hosts.forEach((host, index) => {
|
|
114
|
+
if (!isBrandlabHostname(host)) {
|
|
115
|
+
throw new Error(`${field}[${index}] must be a lowercase hostname without a scheme, port or path.`);
|
|
116
|
+
}
|
|
117
|
+
if (host === canonicalHost) {
|
|
118
|
+
throw new Error(`${field}[${index}] cannot be the canonical host.`);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
const duplicate = hosts.find((host, index) => hosts.indexOf(host) !== index);
|
|
122
|
+
if (duplicate)
|
|
123
|
+
throw new Error(`${field} contains duplicate host "${duplicate}".`);
|
|
124
|
+
}
|
|
116
125
|
//# sourceMappingURL=site.js.map
|
package/dist/site.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site.js","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n assertBrandlabSocialImage,\n type BrandlabSocialImage,\n} from './metadata.js'\n\n/**\n * `production` and `preview` are real Vercel deployments. Everything else —\n * `next dev`, a local `next start`, CI and tests — is `development`.\n */\nexport type BrandlabDeploymentEnvironment = 'production' | 'preview' | 'development'\n\nexport interface BrandlabDeployment {\n /** The single gate for production-only behaviour: analytics, contact delivery and Preview restrictions. */\n readonly environment: BrandlabDeploymentEnvironment\n /**\n * An optimised build (`NODE_ENV=production`), on Vercel or locally. Security\n * headers and crawler access follow the build so a local `next start` serves\n * exactly the production headers.\n */\n readonly productionBuild: boolean\n}\n\nexport type BrandlabEnvironment = Readonly<Record<string, string | undefined>>\n\n/**\n * Resolves the deployment once for every consumer (next.config headers,\n * robots, analytics and contact delivery). Server code passes `process.env`;\n * browser code passes `{ NEXT_PUBLIC_VERCEL_ENV: process.env.NEXT_PUBLIC_VERCEL_ENV }`,\n * which `defineBrandlabNextConfig` inlines at build time.\n *\n * `VERCEL_ENV` wins over `NEXT_PUBLIC_VERCEL_ENV`. An unrecognised declared\n * environment fails closed to `preview`: never analytics, never indexable.\n */\nexport function resolveBrandlabDeployment(environment: BrandlabEnvironment): BrandlabDeployment {\n if (!environment || typeof environment !== 'object') {\n throw new TypeError('resolveBrandlabDeployment requires an environment record.')\n }\n const declared = (environment.VERCEL_ENV ?? environment.NEXT_PUBLIC_VERCEL_ENV)?.trim()\n const deploymentEnvironment: BrandlabDeploymentEnvironment =\n !declared || declared === 'development'\n ? 'development'\n : declared === 'production'\n ? 'production'\n : 'preview'\n return Object.freeze({\n environment: deploymentEnvironment,\n productionBuild: environment.NODE_ENV === 'production',\n })\n}\n\nexport interface BrandlabSiteDefinition {\n /** Public site name: Open Graph `siteName` and the brand guard for templated titles. */\n name: string\n /** Canonical HTTPS origin written as a source constant, never read from the environment. */\n canonicalUrl: string\n /** Open Graph locale, for example `en_AU`. */\n locale: string\n /** Page title template with exactly one `%s`, for example `%s | Example Services`. */\n titleTemplate: string\n defaultSocialImage: BrandlabSocialImage\n /**\n * Hosts that permanently redirect every path to the canonical origin, such as\n * the `www` host, the Vercel project host or a retired domain.\n */\n aliasHosts: readonly string[]\n}\n\nexport interface BrandlabSite {\n readonly name: string\n /** Canonical origin without a trailing slash, for example `https://example.com.au`. */\n readonly canonicalUrl: string\n readonly canonicalHost: string\n readonly locale: string\n readonly titleTemplate: string\n readonly defaultSocialImage: Readonly<BrandlabSocialImage>\n readonly aliasHosts: readonly string[]\n}\n\nconst SITE_KEYS = new Set([\n 'name',\n 'canonicalUrl',\n 'locale',\n 'titleTemplate',\n 'defaultSocialImage',\n 'aliasHosts',\n])\nconst HOSTNAME_PATTERN =\n /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$/\n\nfunction assertSiteText(value: unknown, field: string, maximum: number): asserts value is string {\n if (\n typeof value !== 'string' ||\n !value.trim() ||\n value.trim() !== value ||\n value.length > maximum ||\n /[\\u0000-\\u001f\\u007f]/.test(value)\n ) {\n throw new Error(`${field} must be bounded, trimmed, single-line text.`)\n }\n}\n\nexport function isBrandlabHostname(value: unknown): value is string {\n return typeof value === 'string' && HOSTNAME_PATTERN.test(value)\n}\n\nfunction canonicalOrigin(value: unknown) {\n assertSiteText(value, 'canonicalUrl', 2_048)\n let url: URL\n try {\n url = new URL(value)\n } catch {\n throw new Error(`canonicalUrl must be an absolute HTTPS origin: \"${value}\".`)\n }\n if (\n url.protocol !== 'https:' ||\n url.username ||\n url.password ||\n url.port ||\n url.search ||\n url.hash ||\n (url.pathname !== '/' && url.pathname !== '') ||\n value.replace(/\\/$/, '') !== url.origin ||\n !isBrandlabHostname(url.hostname)\n ) {\n throw new Error(`canonicalUrl must be an absolute HTTPS origin: \"${value}\".`)\n }\n return url\n}\n\n/**\n * Declares the site-wide SEO identity once (`src/config/site.ts`). The result\n * feeds metadata, robots, sitemaps, redirects and next.config.\n */\nexport function defineBrandlabSite(definition: BrandlabSiteDefinition): BrandlabSite {\n if (!definition || typeof definition !== 'object' || Array.isArray(definition)) {\n throw new TypeError('defineBrandlabSite requires a site definition.')\n }\n const unsupported = Object.keys(definition).filter((key) => !SITE_KEYS.has(key))\n if (unsupported.length > 0) {\n throw new Error(`Site definition contains unsupported keys: ${unsupported.join(', ')}.`)\n }\n assertSiteText(definition.name, 'name', 160)\n const origin = canonicalOrigin(definition.canonicalUrl)\n assertSiteText(definition.locale, 'locale', 20)\n if (!/^[a-z]{2}(?:_[A-Z]{2})?$/.test(definition.locale)) {\n throw new Error('locale must use a language or language_REGION identifier.')\n }\n assertSiteText(definition.titleTemplate, 'titleTemplate', 240)\n if ((definition.titleTemplate.match(/%s/g) ?? []).length !== 1) {\n throw new Error('titleTemplate must contain exactly one %s placeholder.')\n }\n assertBrandlabSocialImage(definition.defaultSocialImage, 'defaultSocialImage')\n if (!Array.isArray(definition.aliasHosts)) {\n throw new Error('aliasHosts must list every host that redirects to the canonical origin.')\n }\n definition.aliasHosts.forEach((host, index) => {\n if (!isBrandlabHostname(host)) {\n throw new Error(`aliasHosts[${index}] must be a lowercase hostname without a scheme, port or path.`)\n }\n if (host === origin.hostname) {\n throw new Error(`aliasHosts[${index}] cannot be the canonical host.`)\n }\n })\n const duplicate = definition.aliasHosts.find(\n (host, index) => definition.aliasHosts.indexOf(host) !== index,\n )\n if (duplicate) throw new Error(`aliasHosts contains duplicate host \"${duplicate}\".`)\n\n return Object.freeze({\n name: definition.name,\n canonicalUrl: origin.origin,\n canonicalHost: origin.hostname,\n locale: definition.locale,\n titleTemplate: definition.titleTemplate,\n defaultSocialImage: Object.freeze({ ...definition.defaultSocialImage }),\n aliasHosts: Object.freeze([...definition.aliasHosts]),\n })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"site.js","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n assertBrandlabSocialImage,\n type BrandlabSocialImage,\n} from './metadata.js'\n\n/**\n * `production` and `preview` are real Vercel deployments. Everything else —\n * `next dev`, a local `next start`, CI and tests — is `development`.\n */\nexport type BrandlabDeploymentEnvironment = 'production' | 'preview' | 'development'\n\nexport interface BrandlabDeployment {\n /** The single gate for production-only behaviour: analytics, contact delivery and Preview restrictions. */\n readonly environment: BrandlabDeploymentEnvironment\n /**\n * An optimised build (`NODE_ENV=production`), on Vercel or locally. Security\n * headers and crawler access follow the build so a local `next start` serves\n * exactly the production headers.\n */\n readonly productionBuild: boolean\n}\n\nexport type BrandlabEnvironment = Readonly<Record<string, string | undefined>>\n\n/**\n * Resolves the deployment once for every consumer (next.config headers,\n * robots, analytics and contact delivery). Server code passes `process.env`;\n * browser code passes `{ NEXT_PUBLIC_VERCEL_ENV: process.env.NEXT_PUBLIC_VERCEL_ENV }`,\n * which `defineBrandlabNextConfig` inlines at build time.\n *\n * `VERCEL_ENV` wins over `NEXT_PUBLIC_VERCEL_ENV`. An unrecognised declared\n * environment fails closed to `preview`: never analytics, never indexable.\n */\nexport function resolveBrandlabDeployment(environment: BrandlabEnvironment): BrandlabDeployment {\n if (!environment || typeof environment !== 'object') {\n throw new TypeError('resolveBrandlabDeployment requires an environment record.')\n }\n const declared = (environment.VERCEL_ENV ?? environment.NEXT_PUBLIC_VERCEL_ENV)?.trim()\n const deploymentEnvironment: BrandlabDeploymentEnvironment =\n !declared || declared === 'development'\n ? 'development'\n : declared === 'production'\n ? 'production'\n : 'preview'\n return Object.freeze({\n environment: deploymentEnvironment,\n productionBuild: environment.NODE_ENV === 'production',\n })\n}\n\nexport interface BrandlabSiteDefinition {\n /** Public site name: Open Graph `siteName` and the brand guard for templated titles. */\n name: string\n /** Canonical HTTPS origin written as a source constant, never read from the environment. */\n canonicalUrl: string\n /** Open Graph locale, for example `en_AU`. */\n locale: string\n /** Page title template with exactly one `%s`, for example `%s | Example Services`. */\n titleTemplate: string\n defaultSocialImage: BrandlabSocialImage\n /**\n * Hosts that permanently redirect every path to the canonical origin, such as\n * the `www` host, the Vercel project host or a retired domain.\n *\n * Optional from 3.2.1: declare them in next.config with\n * `defineBrandlabNextConfig({ aliasHosts })` instead, so they stay out of the\n * browser bundles of every client component that imports the site identity.\n */\n aliasHosts?: readonly string[]\n}\n\nexport interface BrandlabSite {\n readonly name: string\n /** Canonical origin without a trailing slash, for example `https://example.com.au`. */\n readonly canonicalUrl: string\n readonly canonicalHost: string\n readonly locale: string\n readonly titleTemplate: string\n readonly defaultSocialImage: Readonly<BrandlabSocialImage>\n readonly aliasHosts: readonly string[]\n}\n\nconst SITE_KEYS = new Set([\n 'name',\n 'canonicalUrl',\n 'locale',\n 'titleTemplate',\n 'defaultSocialImage',\n 'aliasHosts',\n])\nconst HOSTNAME_PATTERN =\n /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$/\n\nfunction assertSiteText(value: unknown, field: string, maximum: number): asserts value is string {\n if (\n typeof value !== 'string' ||\n !value.trim() ||\n value.trim() !== value ||\n value.length > maximum ||\n /[\\u0000-\\u001f\\u007f]/.test(value)\n ) {\n throw new Error(`${field} must be bounded, trimmed, single-line text.`)\n }\n}\n\nexport function isBrandlabHostname(value: unknown): value is string {\n return typeof value === 'string' && HOSTNAME_PATTERN.test(value)\n}\n\nfunction canonicalOrigin(value: unknown) {\n assertSiteText(value, 'canonicalUrl', 2_048)\n let url: URL\n try {\n url = new URL(value)\n } catch {\n throw new Error(`canonicalUrl must be an absolute HTTPS origin: \"${value}\".`)\n }\n if (\n url.protocol !== 'https:' ||\n url.username ||\n url.password ||\n url.port ||\n url.search ||\n url.hash ||\n (url.pathname !== '/' && url.pathname !== '') ||\n value.replace(/\\/$/, '') !== url.origin ||\n !isBrandlabHostname(url.hostname)\n ) {\n throw new Error(`canonicalUrl must be an absolute HTTPS origin: \"${value}\".`)\n }\n return url\n}\n\n/**\n * Declares the site-wide SEO identity once (`src/config/site.ts`). The result\n * feeds metadata, robots, sitemaps, redirects and next.config.\n */\nexport function defineBrandlabSite(definition: BrandlabSiteDefinition): BrandlabSite {\n if (!definition || typeof definition !== 'object' || Array.isArray(definition)) {\n throw new TypeError('defineBrandlabSite requires a site definition.')\n }\n const unsupported = Object.keys(definition).filter((key) => !SITE_KEYS.has(key))\n if (unsupported.length > 0) {\n throw new Error(`Site definition contains unsupported keys: ${unsupported.join(', ')}.`)\n }\n assertSiteText(definition.name, 'name', 160)\n const origin = canonicalOrigin(definition.canonicalUrl)\n assertSiteText(definition.locale, 'locale', 20)\n if (!/^[a-z]{2}(?:_[A-Z]{2})?$/.test(definition.locale)) {\n throw new Error('locale must use a language or language_REGION identifier.')\n }\n assertSiteText(definition.titleTemplate, 'titleTemplate', 240)\n if ((definition.titleTemplate.match(/%s/g) ?? []).length !== 1) {\n throw new Error('titleTemplate must contain exactly one %s placeholder.')\n }\n assertBrandlabSocialImage(definition.defaultSocialImage, 'defaultSocialImage')\n const aliasHosts = definition.aliasHosts ?? []\n assertBrandlabAliasHosts(aliasHosts, origin.hostname)\n\n return Object.freeze({\n name: definition.name,\n canonicalUrl: origin.origin,\n canonicalHost: origin.hostname,\n locale: definition.locale,\n titleTemplate: definition.titleTemplate,\n defaultSocialImage: Object.freeze({ ...definition.defaultSocialImage }),\n aliasHosts: Object.freeze([...aliasHosts]),\n })\n}\n\n/**\n * Checks a list of alias hosts: lowercase hostnames without a scheme, port or\n * path, never the canonical host, each listed once. `defineBrandlabSite` and\n * `defineBrandlabNextConfig({ aliasHosts })` share it.\n */\nexport function assertBrandlabAliasHosts(\n hosts: unknown,\n canonicalHost: string,\n field = 'aliasHosts',\n): asserts hosts is readonly string[] {\n if (!Array.isArray(hosts)) {\n throw new Error(`${field} must list every host that redirects to the canonical origin.`)\n }\n hosts.forEach((host, index) => {\n if (!isBrandlabHostname(host)) {\n throw new Error(`${field}[${index}] must be a lowercase hostname without a scheme, port or path.`)\n }\n if (host === canonicalHost) {\n throw new Error(`${field}[${index}] cannot be the canonical host.`)\n }\n })\n const duplicate = hosts.find((host, index) => hosts.indexOf(host) !== index)\n if (duplicate) throw new Error(`${field} contains duplicate host \"${duplicate}\".`)\n}\n"]}
|