@contentful/experiences-client 0.5.2 → 0.7.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/CHANGELOG.md +24 -0
- package/README.md +70 -22
- package/dist/contentful-experiences.d.ts +17 -10
- package/dist/contentful-experiences.js +25 -12
- package/dist/contentful-experiences.js.map +1 -1
- package/dist/create-delivery-client.d.ts +29 -0
- package/dist/create-delivery-client.js +27 -0
- package/dist/create-delivery-client.js.map +1 -0
- package/dist/create-optimization-client.d.ts +20 -0
- package/dist/create-optimization-client.js +30 -0
- package/dist/create-optimization-client.js.map +1 -0
- package/dist/event-builder.js +35 -27
- package/dist/event-builder.js.map +1 -1
- package/dist/fetch-experience.d.ts +12 -9
- package/dist/fetch-experience.js +32 -18
- package/dist/fetch-experience.js.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime-event-handoff.d.ts +33 -0
- package/dist/runtime-event-handoff.js +68 -0
- package/dist/runtime-event-handoff.js.map +1 -0
- package/dist/runtime-event-methods.d.ts +52 -0
- package/dist/runtime-event-methods.js +105 -0
- package/dist/runtime-event-methods.js.map +1 -0
- package/dist/sdk-info.js +1 -1
- package/package.json +2 -2
- package/dist/create-client.d.ts +0 -14
- package/dist/create-client.js +0 -13
- package/dist/create-client.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/event-builder.ts"],"sourcesContent":["/* eslint-disable no-redeclare */\n\nimport {\n type App,\n Campaign,\n type Channel,\n type ExoClickEvent,\n ExoEntityKind,\n type ExoHoverEvent,\n type ExoViewEvent,\n GeoLocation,\n type IdentifyEvent,\n type Library,\n Page,\n PageEventContext,\n type PageViewEvent,\n parseWithFriendlyError,\n Properties,\n Screen,\n type TrackEvent,\n Traits,\n UniversalEventProperties,\n type ViewEvent,\n} from '@contentful/optimization-api-client/api-schemas';\nimport { createScopedLogger } from '@contentful/optimization-api-client/logger';\nimport { merge } from 'es-toolkit/object';\nimport * as z from 'zod/mini';\n\nconst eventBuilderLogger = createScopedLogger('Experiences:EventBuilder');\n\nconst UTM_CAMPAIGN_PARAMETERS = [\n ['utm_campaign', 'name'],\n ['utm_source', 'source'],\n ['utm_medium', 'medium'],\n ['utm_term', 'term'],\n ['utm_content', 'content'],\n] as const;\n\nfunction extractCampaignFromUrl(url: string | undefined): Campaign | undefined {\n if (url === undefined) return undefined;\n\n try {\n const { searchParams } = new URL(url);\n const campaign: Campaign = {};\n let hasCampaignParameter = false;\n\n for (const [parameter, property] of UTM_CAMPAIGN_PARAMETERS) {\n if (!searchParams.has(parameter)) continue;\n\n campaign[property] = searchParams.get(parameter) ?? '';\n hasCampaignParameter = true;\n }\n\n return hasCampaignParameter ? campaign : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface EventBuilderConfig {\n app?: App;\n channel: Channel;\n library: Library;\n getLocale?: () => string | undefined;\n getPageProperties?: () => Page;\n getUserAgent?: () => string | undefined;\n getConsent?: () => boolean | undefined;\n}\n\nexport const UniversalEventBuilderArgs = z.object({\n campaign: z.optional(Campaign),\n locale: z.optional(z.string()),\n location: z.optional(GeoLocation),\n page: z.optional(Page),\n screen: z.optional(Screen),\n userAgent: z.optional(z.string()),\n});\n\nexport type UniversalEventBuilderArgs = z.infer<typeof UniversalEventBuilderArgs>;\n\nexport const InteractionBuilderArgsBase = z.extend(UniversalEventBuilderArgs, {\n entityId: z.string(),\n entityKind: ExoEntityKind,\n entityKindId: z.optional(z.string()),\n entryIds: z.optional(z.array(z.string())),\n optimizationId: z.optional(z.string()),\n parameters: z.optional(z.record(z.string(), z.json())),\n parentExperienceId: z.optional(z.string()),\n variantId: z.optional(z.string()),\n variantIndex: z.optional(z.number()),\n});\n\nexport type InteractionBuilderArgsBase = z.infer<typeof InteractionBuilderArgsBase>;\n\nexport const ViewBuilderArgs = z.extend(InteractionBuilderArgsBase, {\n viewId: z.string(),\n viewDurationMs: z.int().check(z.minimum(0)),\n});\n\nexport type ViewBuilderArgs = z.infer<typeof ViewBuilderArgs>;\n\nexport const ClickBuilderArgs = InteractionBuilderArgsBase;\n\nexport type ClickBuilderArgs = z.infer<typeof ClickBuilderArgs>;\n\nexport const HoverBuilderArgs = z.extend(InteractionBuilderArgsBase, {\n hoverId: z.string(),\n hoverDurationMs: z.int().check(z.minimum(0)),\n});\n\nexport type HoverBuilderArgs = z.infer<typeof HoverBuilderArgs>;\n\nconst FlagInteractionBuilderArgsBase = z.extend(UniversalEventBuilderArgs, {\n componentId: z.string(),\n experienceId: z.optional(z.string()),\n variantIndex: z.optional(z.number()),\n});\n\nexport const FlagViewBuilderArgs = z.extend(FlagInteractionBuilderArgsBase, {\n viewId: z.optional(z.string()),\n viewDurationMs: z.optional(z.number()),\n});\n\nexport type FlagViewBuilderArgs = z.infer<typeof FlagViewBuilderArgs>;\n\nexport const IdentifyBuilderArgs = z.extend(UniversalEventBuilderArgs, {\n traits: z.optional(Traits),\n userId: z.string(),\n});\n\nexport type IdentifyBuilderArgs = z.infer<typeof IdentifyBuilderArgs>;\n\nexport const PageViewBuilderArgs = z.extend(UniversalEventBuilderArgs, {\n properties: z.optional(z.partial(Page)),\n});\n\nexport type PageViewBuilderArgs = z.infer<typeof PageViewBuilderArgs>;\n\nexport const TrackBuilderArgs = z.extend(UniversalEventBuilderArgs, {\n event: z.string(),\n properties: z.optional(z.prefault(Properties, {})),\n});\n\nexport type TrackBuilderArgs = z.infer<typeof TrackBuilderArgs>;\n\nconst PAGE_CONTEXT_CONFLICT_KEYS = ['path', 'query', 'search', 'url'] as const;\n\nfunction warnIfPageContextConflicts(explicitPage: Page | undefined, properties: Page): void {\n if (!explicitPage) return;\n\n const conflictingKeys = PAGE_CONTEXT_CONFLICT_KEYS.filter((key) => {\n const { [key]: explicitValue } = explicitPage;\n const { [key]: propertyValue } = properties;\n return JSON.stringify(explicitValue) !== JSON.stringify(propertyValue);\n });\n\n if (conflictingKeys.length === 0) return;\n\n eventBuilderLogger.warn(\n `Explicit page context differs from page event properties for: ${conflictingKeys.join(', ')}`\n );\n}\n\nexport const DEFAULT_PAGE_PROPERTIES: Page = {\n path: '',\n query: {},\n referrer: '',\n search: '',\n title: '',\n url: '',\n};\n\nclass EventBuilder {\n app?: App;\n channel: Channel;\n library: Library;\n getLocale: () => string | undefined;\n getPageProperties: () => Page;\n getUserAgent: () => string | undefined;\n getConsent: () => boolean | undefined;\n\n constructor(config: EventBuilderConfig) {\n const { app, channel, library, getLocale, getPageProperties, getUserAgent, getConsent } =\n config;\n this.app = app;\n this.channel = channel;\n this.library = library;\n this.getLocale = getLocale ?? (() => 'en-US');\n this.getPageProperties = getPageProperties ?? (() => DEFAULT_PAGE_PROPERTIES);\n this.getUserAgent = getUserAgent ?? (() => undefined);\n this.getConsent = getConsent ?? (() => undefined);\n }\n\n protected buildUniversalEventProperties({\n campaign,\n locale,\n location,\n page,\n screen,\n userAgent,\n }: UniversalEventBuilderArgs): UniversalEventProperties {\n const timestamp = new Date().toISOString();\n const resolvedPage = page ?? this.getPageProperties();\n\n return {\n channel: this.channel,\n context: {\n app: this.app,\n campaign: campaign ?? extractCampaignFromUrl(resolvedPage.url) ?? {},\n gdpr: { isConsentGiven: this.getConsent() === true },\n library: this.library,\n locale: locale ?? this.getLocale() ?? 'en-US',\n location,\n page: resolvedPage,\n screen,\n userAgent: userAgent ?? this.getUserAgent(),\n },\n messageId: crypto.randomUUID(),\n originalTimestamp: timestamp,\n sentAt: timestamp,\n timestamp,\n };\n }\n\n private buildInteractionBase(\n args: InteractionBuilderArgsBase\n ): UniversalEventProperties & InteractionBuilderArgsBase {\n const { campaign, locale, location, page, screen, userAgent, ...interactionProperties } = args;\n\n return {\n ...this.buildUniversalEventProperties({\n campaign,\n locale,\n location,\n page,\n screen,\n userAgent,\n }),\n ...interactionProperties,\n };\n }\n\n buildView(args: ViewBuilderArgs): ExoViewEvent {\n const { viewId, viewDurationMs, ...interaction } = parseWithFriendlyError(\n ViewBuilderArgs,\n args\n );\n\n return {\n ...this.buildInteractionBase(interaction),\n type: 'exo_node_view',\n viewId,\n viewDurationMs,\n };\n }\n\n buildClick(args: ClickBuilderArgs): ExoClickEvent {\n const interaction = parseWithFriendlyError(ClickBuilderArgs, args);\n\n return {\n ...this.buildInteractionBase(interaction),\n type: 'exo_node_click',\n };\n }\n\n buildHover(args: HoverBuilderArgs): ExoHoverEvent {\n const { hoverId, hoverDurationMs, ...interaction } = parseWithFriendlyError(\n HoverBuilderArgs,\n args\n );\n\n return {\n ...this.buildInteractionBase(interaction),\n type: 'exo_node_hover',\n hoverId,\n hoverDurationMs,\n };\n }\n\n buildFlagView(args: FlagViewBuilderArgs): ViewEvent {\n const { componentId, experienceId, variantIndex, viewId, viewDurationMs, ...universal } =\n parseWithFriendlyError(FlagViewBuilderArgs, args);\n\n return {\n ...this.buildUniversalEventProperties(universal),\n ...(viewDurationMs === undefined ? {} : { viewDurationMs }),\n ...(viewId === undefined ? {} : { viewId }),\n type: 'component',\n componentType: 'Variable',\n componentId,\n experienceId,\n variantIndex: variantIndex ?? 0,\n };\n }\n\n buildIdentify(args: IdentifyBuilderArgs): IdentifyEvent {\n const { traits = {}, userId, ...universal } = parseWithFriendlyError(IdentifyBuilderArgs, args);\n\n return {\n ...this.buildUniversalEventProperties(universal),\n type: 'identify',\n traits,\n userId,\n };\n }\n\n buildPageView(args: PageViewBuilderArgs = {}): PageViewEvent {\n const { properties = {}, ...universal } = parseWithFriendlyError(PageViewBuilderArgs, args);\n const propertiesCampaign = extractCampaignFromUrl(properties.url);\n const pageProperties = this.getPageProperties();\n const merged = merge(\n {\n ...pageProperties,\n title: pageProperties.title ?? DEFAULT_PAGE_PROPERTIES.title,\n },\n properties\n );\n\n warnIfPageContextConflicts(universal.page, merged);\n\n const {\n context: { screen: _, ...universalContext },\n ...universalProperties\n } = this.buildUniversalEventProperties({\n ...universal,\n campaign: universal.campaign ?? propertiesCampaign,\n page: universal.page ?? merged,\n });\n\n const context = parseWithFriendlyError(PageEventContext, universalContext);\n\n return {\n ...universalProperties,\n context,\n type: 'page',\n properties: merged,\n };\n }\n\n buildTrack(args: TrackBuilderArgs): TrackEvent {\n const { event, properties = {}, ...universal } = parseWithFriendlyError(TrackBuilderArgs, args);\n\n return {\n ...this.buildUniversalEventProperties(universal),\n type: 'track',\n event,\n properties,\n };\n }\n}\n\nexport default EventBuilder;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/event-builder.ts"],"sourcesContent":["/* eslint-disable no-redeclare */\n\nimport {\n type App as OptimizationApp,\n Campaign as OptimizationCampaign,\n type Channel as OptimizationChannel,\n type ExoClickEvent as OptimizationExoClickEvent,\n ExoEntityKind as OptimizationExoEntityKind,\n type ExoHoverEvent as OptimizationExoHoverEvent,\n type ExoViewEvent as OptimizationExoViewEvent,\n GeoLocation as OptimizationGeoLocation,\n type IdentifyEvent as OptimizationIdentifyEvent,\n type Library as OptimizationLibrary,\n Page as OptimizationPage,\n PageEventContext as OptimizationPageEventContext,\n type PageViewEvent as OptimizationPageViewEvent,\n parseWithFriendlyError as parseOptimizationSchema,\n Properties as OptimizationProperties,\n Screen as OptimizationScreen,\n type TrackEvent as OptimizationTrackEvent,\n Traits as OptimizationTraits,\n UniversalEventProperties as OptimizationUniversalEventProperties,\n type ViewEvent as OptimizationViewEvent,\n} from '@contentful/optimization-api-client/api-schemas';\nimport { createScopedLogger as createOptimizationScopedLogger } from '@contentful/optimization-api-client/logger';\nimport { merge } from 'es-toolkit/object';\nimport * as z from 'zod/mini';\n\nconst eventBuilderLogger = createOptimizationScopedLogger('Experiences:EventBuilder');\n\nconst UTM_CAMPAIGN_PARAMETERS = [\n ['utm_campaign', 'name'],\n ['utm_source', 'source'],\n ['utm_medium', 'medium'],\n ['utm_term', 'term'],\n ['utm_content', 'content'],\n] as const;\n\nfunction extractCampaignFromUrl(url: string | undefined): OptimizationCampaign | undefined {\n if (url === undefined) return undefined;\n\n try {\n const { searchParams } = new URL(url);\n const campaign: OptimizationCampaign = {};\n let hasCampaignParameter = false;\n\n for (const [parameter, property] of UTM_CAMPAIGN_PARAMETERS) {\n if (!searchParams.has(parameter)) continue;\n\n campaign[property] = searchParams.get(parameter) ?? '';\n hasCampaignParameter = true;\n }\n\n return hasCampaignParameter ? campaign : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface EventBuilderConfig {\n app?: OptimizationApp;\n channel: OptimizationChannel;\n library: OptimizationLibrary;\n getLocale?: () => string | undefined;\n getPageProperties?: () => OptimizationPage;\n getUserAgent?: () => string | undefined;\n getConsent?: () => boolean | undefined;\n}\n\nexport const UniversalEventBuilderArgs = z.object({\n campaign: z.optional(OptimizationCampaign),\n locale: z.optional(z.string()),\n location: z.optional(OptimizationGeoLocation),\n page: z.optional(OptimizationPage),\n screen: z.optional(OptimizationScreen),\n userAgent: z.optional(z.string()),\n});\n\nexport type UniversalEventBuilderArgs = z.infer<typeof UniversalEventBuilderArgs>;\n\nexport const InteractionBuilderArgsBase = z.extend(UniversalEventBuilderArgs, {\n entityId: z.string(),\n entityKind: OptimizationExoEntityKind,\n entityKindId: z.optional(z.string()),\n entryIds: z.optional(z.array(z.string())),\n optimizationId: z.optional(z.string()),\n parameters: z.optional(z.record(z.string(), z.json())),\n parentExperienceId: z.optional(z.string()),\n variantId: z.optional(z.string()),\n variantIndex: z.optional(z.number()),\n});\n\nexport type InteractionBuilderArgsBase = z.infer<typeof InteractionBuilderArgsBase>;\n\nexport const ViewBuilderArgs = z.extend(InteractionBuilderArgsBase, {\n viewId: z.string(),\n viewDurationMs: z.int().check(z.minimum(0)),\n});\n\nexport type ViewBuilderArgs = z.infer<typeof ViewBuilderArgs>;\n\nexport const ClickBuilderArgs = InteractionBuilderArgsBase;\n\nexport type ClickBuilderArgs = z.infer<typeof ClickBuilderArgs>;\n\nexport const HoverBuilderArgs = z.extend(InteractionBuilderArgsBase, {\n hoverId: z.string(),\n hoverDurationMs: z.int().check(z.minimum(0)),\n});\n\nexport type HoverBuilderArgs = z.infer<typeof HoverBuilderArgs>;\n\nconst FlagInteractionBuilderArgsBase = z.extend(UniversalEventBuilderArgs, {\n componentId: z.string(),\n experienceId: z.optional(z.string()),\n variantIndex: z.optional(z.number()),\n});\n\nexport const FlagViewBuilderArgs = z.extend(FlagInteractionBuilderArgsBase, {\n viewId: z.optional(z.string()),\n viewDurationMs: z.optional(z.number()),\n});\n\nexport type FlagViewBuilderArgs = z.infer<typeof FlagViewBuilderArgs>;\n\nexport const IdentifyBuilderArgs = z.extend(UniversalEventBuilderArgs, {\n traits: z.optional(OptimizationTraits),\n userId: z.string(),\n});\n\nexport type IdentifyBuilderArgs = z.infer<typeof IdentifyBuilderArgs>;\n\nexport const PageViewBuilderArgs = z.extend(UniversalEventBuilderArgs, {\n properties: z.optional(z.partial(OptimizationPage)),\n});\n\nexport type PageViewBuilderArgs = z.infer<typeof PageViewBuilderArgs>;\n\nexport const TrackBuilderArgs = z.extend(UniversalEventBuilderArgs, {\n event: z.string(),\n properties: z.optional(z.prefault(OptimizationProperties, {})),\n});\n\nexport type TrackBuilderArgs = z.infer<typeof TrackBuilderArgs>;\n\nconst PAGE_CONTEXT_CONFLICT_KEYS = ['path', 'query', 'search', 'url'] as const;\n\nfunction warnIfPageContextConflicts(\n explicitPage: OptimizationPage | undefined,\n properties: OptimizationPage\n): void {\n if (!explicitPage) return;\n\n const conflictingKeys = PAGE_CONTEXT_CONFLICT_KEYS.filter((key) => {\n const { [key]: explicitValue } = explicitPage;\n const { [key]: propertyValue } = properties;\n return JSON.stringify(explicitValue) !== JSON.stringify(propertyValue);\n });\n\n if (conflictingKeys.length === 0) return;\n\n eventBuilderLogger.warn(\n `Explicit page context differs from page event properties for: ${conflictingKeys.join(', ')}`\n );\n}\n\nexport const DEFAULT_PAGE_PROPERTIES: OptimizationPage = {\n path: '',\n query: {},\n referrer: '',\n search: '',\n title: '',\n url: '',\n};\n\nclass EventBuilder {\n app?: OptimizationApp;\n channel: OptimizationChannel;\n library: OptimizationLibrary;\n getLocale: () => string | undefined;\n getPageProperties: () => OptimizationPage;\n getUserAgent: () => string | undefined;\n getConsent: () => boolean | undefined;\n\n constructor(config: EventBuilderConfig) {\n const { app, channel, library, getLocale, getPageProperties, getUserAgent, getConsent } =\n config;\n this.app = app;\n this.channel = channel;\n this.library = library;\n this.getLocale = getLocale ?? (() => 'en-US');\n this.getPageProperties = getPageProperties ?? (() => DEFAULT_PAGE_PROPERTIES);\n this.getUserAgent = getUserAgent ?? (() => undefined);\n this.getConsent = getConsent ?? (() => undefined);\n }\n\n protected buildUniversalEventProperties({\n campaign,\n locale,\n location,\n page,\n screen,\n userAgent,\n }: UniversalEventBuilderArgs): OptimizationUniversalEventProperties {\n const timestamp = new Date().toISOString();\n const resolvedPage = page ?? this.getPageProperties();\n\n return {\n channel: this.channel,\n context: {\n app: this.app,\n campaign: campaign ?? extractCampaignFromUrl(resolvedPage.url) ?? {},\n gdpr: { isConsentGiven: this.getConsent() === true },\n library: this.library,\n locale: locale ?? this.getLocale() ?? 'en-US',\n location,\n page: resolvedPage,\n screen,\n userAgent: userAgent ?? this.getUserAgent(),\n },\n messageId: crypto.randomUUID(),\n originalTimestamp: timestamp,\n sentAt: timestamp,\n timestamp,\n };\n }\n\n private buildInteractionBase(\n args: InteractionBuilderArgsBase\n ): OptimizationUniversalEventProperties & InteractionBuilderArgsBase {\n const { campaign, locale, location, page, screen, userAgent, ...interactionProperties } = args;\n\n return {\n ...this.buildUniversalEventProperties({\n campaign,\n locale,\n location,\n page,\n screen,\n userAgent,\n }),\n ...interactionProperties,\n };\n }\n\n buildView(args: ViewBuilderArgs): OptimizationExoViewEvent {\n const { viewId, viewDurationMs, ...interaction } = parseOptimizationSchema(\n ViewBuilderArgs,\n args\n );\n\n return {\n ...this.buildInteractionBase(interaction),\n type: 'exo_node_view',\n viewId,\n viewDurationMs,\n };\n }\n\n buildClick(args: ClickBuilderArgs): OptimizationExoClickEvent {\n const interaction = parseOptimizationSchema(ClickBuilderArgs, args);\n\n return {\n ...this.buildInteractionBase(interaction),\n type: 'exo_node_click',\n };\n }\n\n buildHover(args: HoverBuilderArgs): OptimizationExoHoverEvent {\n const { hoverId, hoverDurationMs, ...interaction } = parseOptimizationSchema(\n HoverBuilderArgs,\n args\n );\n\n return {\n ...this.buildInteractionBase(interaction),\n type: 'exo_node_hover',\n hoverId,\n hoverDurationMs,\n };\n }\n\n buildFlagView(args: FlagViewBuilderArgs): OptimizationViewEvent {\n const { componentId, experienceId, variantIndex, viewId, viewDurationMs, ...universal } =\n parseOptimizationSchema(FlagViewBuilderArgs, args);\n\n return {\n ...this.buildUniversalEventProperties(universal),\n ...(viewDurationMs === undefined ? {} : { viewDurationMs }),\n ...(viewId === undefined ? {} : { viewId }),\n type: 'component',\n componentType: 'Variable',\n componentId,\n experienceId,\n variantIndex: variantIndex ?? 0,\n };\n }\n\n buildIdentify(args: IdentifyBuilderArgs): OptimizationIdentifyEvent {\n const {\n traits = {},\n userId,\n ...universal\n } = parseOptimizationSchema(IdentifyBuilderArgs, args);\n\n return {\n ...this.buildUniversalEventProperties(universal),\n type: 'identify',\n traits,\n userId,\n };\n }\n\n buildPageView(args: PageViewBuilderArgs = {}): OptimizationPageViewEvent {\n const { properties = {}, ...universal } = parseOptimizationSchema(PageViewBuilderArgs, args);\n const propertiesCampaign = extractCampaignFromUrl(properties.url);\n const pageProperties = this.getPageProperties();\n const merged = merge(\n {\n ...pageProperties,\n title: pageProperties.title ?? DEFAULT_PAGE_PROPERTIES.title,\n },\n properties\n );\n\n warnIfPageContextConflicts(universal.page, merged);\n\n const {\n context: { screen: _, ...universalContext },\n ...universalProperties\n } = this.buildUniversalEventProperties({\n ...universal,\n campaign: universal.campaign ?? propertiesCampaign,\n page: universal.page ?? merged,\n });\n\n const context = parseOptimizationSchema(OptimizationPageEventContext, universalContext);\n\n return {\n ...universalProperties,\n context,\n type: 'page',\n properties: merged,\n };\n }\n\n buildTrack(args: TrackBuilderArgs): OptimizationTrackEvent {\n const {\n event,\n properties = {},\n ...universal\n } = parseOptimizationSchema(TrackBuilderArgs, args);\n\n return {\n ...this.buildUniversalEventProperties(universal),\n type: 'track',\n event,\n properties,\n };\n }\n}\n\nexport default EventBuilder;\n"],"mappings":"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","names":[]}
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
import { ContentfulViewDeliveryClient } from '@contentful/experience-delivery';
|
|
1
|
+
import { ContentfulViewDelivery, ContentfulViewDeliveryClient } from '@contentful/experience-delivery';
|
|
2
2
|
import { ResolverConfig, PortableRenderPlan } from '@contentful/experiences-sdk-core';
|
|
3
3
|
|
|
4
|
+
type DeliveryExperienceRequestExtensions = ContentfulViewDelivery.GetWithOverridesExperienceRequestExtensions;
|
|
5
|
+
type PersonalizationOptions = NonNullable<DeliveryExperienceRequestExtensions['personalization']>;
|
|
6
|
+
type ExperienceRequestExtensions = Omit<DeliveryExperienceRequestExtensions, 'personalization'>;
|
|
4
7
|
type ByIdExperienceOptions = {
|
|
5
8
|
spaceId: string;
|
|
6
9
|
environmentId: string;
|
|
7
10
|
experienceId: string;
|
|
8
11
|
locale?: string;
|
|
9
12
|
/**
|
|
10
|
-
*
|
|
11
|
-
* `PortableRenderPlan.sourceMap`. Defaults to `false` because the map is large.
|
|
12
|
-
*
|
|
13
|
-
* Switches the request from `GET` to `POST` (the opt-in is a request-body
|
|
14
|
-
* field, only accepted by `getWithOverrides`), so it is not CDN-cacheable.
|
|
15
|
-
* Query params, auth, and the response shape are unchanged.
|
|
13
|
+
* Profile and events used by XDA to personalize the resolved Experience.
|
|
16
14
|
*/
|
|
17
|
-
|
|
15
|
+
personalization?: PersonalizationOptions;
|
|
16
|
+
/**
|
|
17
|
+
* Additional XDA request extensions such as source maps. Supplying a defined
|
|
18
|
+
* extension switches the request to POST.
|
|
19
|
+
*/
|
|
20
|
+
extensions?: ExperienceRequestExtensions;
|
|
18
21
|
};
|
|
19
22
|
/**
|
|
20
23
|
* Resolve an Experience by the Destination Node instead of `experienceId`
|
|
@@ -98,4 +101,4 @@ declare function fetchExperience(experienceOptions: ByIdExperienceOptions, clien
|
|
|
98
101
|
declare function fetchExperience(experienceOptions: ByDestinationNodeIdExperienceOptions, clientOptions: ClientOptions, resolveOptions: ResolveOptions): Promise<PortableRenderPlan | DestinationRedirectResult>;
|
|
99
102
|
declare function fetchExperience(experienceOptions: ByDestinationPathExperienceOptions, clientOptions: ClientOptions, resolveOptions: ResolveOptions): Promise<PortableRenderPlan | DestinationRedirectResult>;
|
|
100
103
|
|
|
101
|
-
export { type ByDestinationNodeIdExperienceOptions, type ByDestinationPathExperienceOptions, type ByIdExperienceOptions, type ClientOptions, type DestinationRedirectResult, type ExperienceOptions, type ResolveOptions, fetchExperience };
|
|
104
|
+
export { type ByDestinationNodeIdExperienceOptions, type ByDestinationPathExperienceOptions, type ByIdExperienceOptions, type ClientOptions, type DestinationRedirectResult, type ExperienceOptions, type ExperienceRequestExtensions, type PersonalizationOptions, type ResolveOptions, fetchExperience };
|
package/dist/fetch-experience.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ContentfulViewDelivery } from "@contentful/experience-delivery";
|
|
2
2
|
import { createDebugLogger, resolveExperience } from "@contentful/experiences-sdk-core";
|
|
3
|
-
import { createClient } from "./create-client.js";
|
|
3
|
+
import { createClient } from "./create-delivery-client.js";
|
|
4
4
|
import { DestinationPreviewNotSupportedError, ExperienceFetchError } from "./errors.js";
|
|
5
5
|
import { PREVIEW_HOST } from "./hosts.js";
|
|
6
6
|
import {
|
|
@@ -55,20 +55,41 @@ async function fetchExperience(experienceOptions, clientOptions, resolveOptions)
|
|
|
55
55
|
{ config, metadata, debug, initialViewportId }
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
|
-
const { spaceId, environmentId, experienceId, locale,
|
|
58
|
+
const { spaceId, environmentId, experienceId, locale, personalization, extensions } = experienceOptions;
|
|
59
59
|
log.log("fetching experience", {
|
|
60
60
|
spaceId,
|
|
61
61
|
environmentId,
|
|
62
62
|
experienceId,
|
|
63
63
|
locale,
|
|
64
|
-
|
|
64
|
+
// avoiding log any personalization sensitive data
|
|
65
|
+
personalization: personalization !== void 0,
|
|
66
|
+
extensions
|
|
65
67
|
});
|
|
66
|
-
|
|
68
|
+
const response = await fetchByExperienceId(client, experienceOptions);
|
|
69
|
+
const payload = toExperiencePayload(response);
|
|
70
|
+
const sourceMap = extensions?.sourceMap !== void 0 ? readSourceMap(response) : void 0;
|
|
71
|
+
log.lazy("received raw payload", () => payload);
|
|
72
|
+
return resolveExperience(payload, config, {
|
|
73
|
+
metadata,
|
|
74
|
+
debug,
|
|
75
|
+
initialViewportId,
|
|
76
|
+
sourceMap
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async function fetchByExperienceId(client, options) {
|
|
80
|
+
const { spaceId, environmentId, experienceId, locale, personalization, extensions } = options;
|
|
81
|
+
const requestExtensions = {
|
|
82
|
+
...extensions,
|
|
83
|
+
...personalization === void 0 ? {} : { personalization }
|
|
84
|
+
};
|
|
67
85
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
if (hasRequestExtensions(requestExtensions)) {
|
|
87
|
+
return await client.experience.getWithOverrides(spaceId, environmentId, experienceId, {
|
|
88
|
+
locale,
|
|
89
|
+
extensions: requestExtensions
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return await client.experience.get(spaceId, environmentId, experienceId, { locale });
|
|
72
93
|
} catch (err) {
|
|
73
94
|
if (err instanceof ContentfulViewDelivery.NotFoundError) {
|
|
74
95
|
throw err;
|
|
@@ -79,16 +100,6 @@ async function fetchExperience(experienceOptions, clientOptions, resolveOptions)
|
|
|
79
100
|
{ spaceId, environmentId, experienceId, cause: err }
|
|
80
101
|
);
|
|
81
102
|
}
|
|
82
|
-
const payload = toExperiencePayload(response);
|
|
83
|
-
const sourceMap = withSourceMap ? readSourceMap(response) : void 0;
|
|
84
|
-
log.lazy("received raw payload", () => payload);
|
|
85
|
-
if (withSourceMap && !sourceMap) log.log("source map requested but not returned");
|
|
86
|
-
return resolveExperience(payload, config, {
|
|
87
|
-
metadata,
|
|
88
|
-
debug,
|
|
89
|
-
initialViewportId,
|
|
90
|
-
sourceMap
|
|
91
|
-
});
|
|
92
103
|
}
|
|
93
104
|
async function fetchByDestination(identity, resolve, log, resolveOptions) {
|
|
94
105
|
const { spaceId, destinationId, locator } = identity;
|
|
@@ -126,6 +137,9 @@ async function fetchByDestination(identity, resolve, log, resolveOptions) {
|
|
|
126
137
|
initialViewportId
|
|
127
138
|
});
|
|
128
139
|
}
|
|
140
|
+
function hasRequestExtensions(extensions) {
|
|
141
|
+
return extensions !== void 0 && Object.values(extensions).some((extension) => extension !== void 0);
|
|
142
|
+
}
|
|
129
143
|
export {
|
|
130
144
|
fetchExperience
|
|
131
145
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fetch-experience.ts"],"sourcesContent":["import type { ContentfulViewDeliveryClient } from '@contentful/experience-delivery';\nimport { ContentfulViewDelivery } from '@contentful/experience-delivery';\nimport { createDebugLogger, resolveExperience } from '@contentful/experiences-sdk-core';\nimport type { PortableRenderPlan, ResolverConfig } from '@contentful/experiences-sdk-core';\nimport { createClient } from './create-client.js';\nimport { DestinationPreviewNotSupportedError, ExperienceFetchError } from './errors.js';\nimport { PREVIEW_HOST } from './hosts.js';\nimport {\n readSourceMap,\n toExperiencePayload,\n toExperiencePayloadFromDestination,\n type ExperienceResponse,\n} from './to-experience-payload.js';\n\nexport type ByIdExperienceOptions = {\n spaceId: string;\n environmentId: string;\n experienceId: string;\n locale?: string;\n /**\n * Fetch the content source map alongside the experience, onto\n * `PortableRenderPlan.sourceMap`. Defaults to `false` because the map is large.\n *\n * Switches the request from `GET` to `POST` (the opt-in is a request-body\n * field, only accepted by `getWithOverrides`), so it is not CDN-cacheable.\n * Query params, auth, and the response shape are unchanged.\n */\n withSourceMap?: boolean;\n};\n\n/**\n * Resolve an Experience by the Destination Node instead of `experienceId`\n * The destinations delivery endpoints are space-scoped only and have no locale\n * param, unlike the by-id path.\n */\nexport type ByDestinationNodeIdExperienceOptions = {\n spaceId: string;\n destinationId: string;\n nodeId: string;\n};\n\n/**\n * Resolve an Experience by the absolute path wired up to a\n * Destination Node, instead of a node id directly. Same scoping as\n * `ByDestinationNodeIdExperienceOptions` — no `environmentId` or `locale`.\n */\nexport type ByDestinationPathExperienceOptions = {\n spaceId: string;\n destinationId: string;\n /** Absolute path to resolve. Must start with `/`. */\n path: string;\n};\n\nexport type ExperienceOptions =\n ByIdExperienceOptions | ByDestinationNodeIdExperienceOptions | ByDestinationPathExperienceOptions;\n\n/**\n * A Destination resolution that the app must act on as control flow — honor\n * it by navigating or re-resolving at the new path — rather than a hydrated\n * Experience to render. Modeled as a normal return value, not a thrown error,\n * per the Destinations delivery API's own framing of a redirect response.\n */\nexport interface DestinationRedirectResult {\n redirect: { path: string };\n}\n\nexport type ClientOptions =\n | {\n accessToken: string;\n /**\n * Preview access token. Required when calling with `preview: true`.\n */\n previewToken?: string;\n /**\n * Flip between delivery (default) and preview at request time. When\n * `true`, `fetchExperience` uses `previewToken` and the preview host;\n * when `false` or unset, it uses `accessToken` and the delivery host.\n * Ignored when a pre-made `client` is passed instead of inline creds.\n */\n preview?: boolean;\n /**\n * Custom base URL for the delivery client (staging, proxy, per-region).\n * Wins over the `preview`-derived default host — combine `host` with\n * `preview: true` to point preview mode at a non-prod endpoint.\n * Omit for the standard delivery / preview hosts.\n */\n host?: string;\n }\n | { client: ContentfulViewDeliveryClient };\n\nexport type ResolveOptions = {\n config: ResolverConfig;\n /**\n * Arbitrary per-render metadata exposed to every resolver as\n * `ctx.experience.metadata` and readable at render time via the framework\n * adapter's `useExperience()` / `getExperience()`.\n */\n metadata?: Record<string, unknown>;\n /**\n * Observability switch. When on: logs the fetch (host, ids) and the raw\n * payload here, and threads through to `resolveExperience` (resolution steps\n * + `resolveData` timings) and the renderer (visible missing-component box,\n * debug fallback). A single boolean across fetch + resolve + render.\n */\n debug?: boolean;\n /**\n * Per-request fallback viewport for server-side design pre-resolution. Pass\n * the same id you seed the renderer's `initialViewportId` with (e.g. a\n * User-Agent-derived viewport) so SSR paints correct design on first render.\n */\n initialViewportId?: string;\n};\n\n// TS overload signatures, not real redeclarations — base ESLint's no-redeclare doesn't know the\n// overload form (@typescript-eslint's own version does), so each repeated signature needs a\n// disable comment.\nexport async function fetchExperience(\n experienceOptions: ByIdExperienceOptions,\n clientOptions: ClientOptions,\n resolveOptions: ResolveOptions\n): Promise<PortableRenderPlan>;\n// eslint-disable-next-line no-redeclare -- see above\nexport async function fetchExperience(\n experienceOptions: ByDestinationNodeIdExperienceOptions,\n clientOptions: ClientOptions,\n resolveOptions: ResolveOptions\n): Promise<PortableRenderPlan | DestinationRedirectResult>;\n// eslint-disable-next-line no-redeclare -- see above\nexport async function fetchExperience(\n experienceOptions: ByDestinationPathExperienceOptions,\n clientOptions: ClientOptions,\n resolveOptions: ResolveOptions\n): Promise<PortableRenderPlan | DestinationRedirectResult>;\n// eslint-disable-next-line no-redeclare -- see above\nexport async function fetchExperience(\n experienceOptions: ExperienceOptions,\n clientOptions: ClientOptions,\n resolveOptions: ResolveOptions\n): Promise<PortableRenderPlan | DestinationRedirectResult> {\n const { config, metadata, debug, initialViewportId } = resolveOptions;\n const log = createDebugLogger(debug, 'client');\n\n if (\n !('client' in clientOptions) &&\n clientOptions.preview &&\n 'destinationId' in experienceOptions\n ) {\n const { spaceId, destinationId } = experienceOptions;\n const locator =\n 'nodeId' in experienceOptions\n ? { nodeId: experienceOptions.nodeId }\n : { path: experienceOptions.path };\n throw new DestinationPreviewNotSupportedError(\n `fetchExperience() called with preview: true and a destination-shaped experienceOptions ` +\n `(destinationId \"${destinationId}\"). The Destinations Delivery API does not support ` +\n `preview mode yet — pass preview: false (or omit it) for destination-based fetches.`,\n { spaceId, destinationId, ...locator }\n );\n }\n\n let client: ContentfulViewDeliveryClient;\n if ('client' in clientOptions) {\n client = clientOptions.client;\n log.log('using caller-supplied delivery client');\n } else {\n const { accessToken, previewToken, preview, host } = clientOptions;\n if (preview && !previewToken) {\n throw new Error(\n 'fetchExperience() called with preview: true but no previewToken was provided'\n );\n }\n const resolvedHost = host ?? (preview ? PREVIEW_HOST : undefined);\n client = createClient({\n accessToken: preview ? (previewToken as string) : accessToken,\n host: resolvedHost,\n });\n log.log('created delivery client', { preview: Boolean(preview), host: resolvedHost });\n }\n\n if ('nodeId' in experienceOptions) {\n const { spaceId, destinationId, nodeId } = experienceOptions;\n return fetchByDestination(\n { spaceId, destinationId, locator: nodeId },\n () => client.destination.resolveByNodeId(spaceId, destinationId, nodeId),\n log,\n { config, metadata, debug, initialViewportId }\n );\n }\n\n if ('path' in experienceOptions) {\n const { spaceId, destinationId, path } = experienceOptions;\n return fetchByDestination(\n { spaceId, destinationId, locator: path },\n () => client.destination.resolveByPath(spaceId, destinationId, { path }),\n log,\n { config, metadata, debug, initialViewportId }\n );\n }\n\n const { spaceId, environmentId, experienceId, locale, withSourceMap } = experienceOptions;\n\n log.log('fetching experience', {\n spaceId,\n environmentId,\n experienceId,\n locale,\n withSourceMap: Boolean(withSourceMap),\n });\n\n // Typed as the union of both operations' responses — they declare the same\n // union, but which one runs depends on `withSourceMap` below.\n let response: ExperienceResponse;\n try {\n // Both methods hit the same endpoint; only the POST accepts a body, and\n // `extensions` (the source-map opt-in) lives there. Otherwise identical.\n // The alpha-feature header is sent by the delivery client itself since\n // 1.0.0-dev.7, so a caller-supplied `{ client }` is covered too.\n response = withSourceMap\n ? await client.experience.getWithOverrides(spaceId, environmentId, experienceId, {\n locale,\n extensions: { sourceMap: {} },\n })\n : await client.experience.get(spaceId, environmentId, experienceId, { locale });\n } catch (err) {\n // `NotFoundError` is a distinguishable, expected outcome (draft/unpublished/\n // wrong id) — callers already route it to their framework's 404 idiom, so\n // it passes through as-is. Everything else (network failure, bad/expired\n // token, a 5xx) is unexpected and gets wrapped in an actionable error\n // instead of leaking whatever shape the delivery client happened to throw.\n if (err instanceof ContentfulViewDelivery.NotFoundError) {\n throw err;\n }\n const reason = err instanceof Error ? err.message : String(err);\n throw new ExperienceFetchError(\n `Failed to fetch Experience \"${experienceId}\" (space \"${spaceId}\", environment ` +\n `\"${environmentId}\"): ${reason}. Check network connectivity, the access token, and ` +\n `that the space/environment/experience ids are correct.`,\n { spaceId, environmentId, experienceId, cause: err }\n );\n }\n\n const payload = toExperiencePayload(response);\n const sourceMap = withSourceMap ? readSourceMap(response) : undefined;\n\n log.lazy('received raw payload', () => payload);\n if (withSourceMap && !sourceMap) log.log('source map requested but not returned');\n\n return resolveExperience(payload, config, {\n metadata,\n debug,\n initialViewportId,\n sourceMap,\n });\n}\n\n/**\n * Shared response-handling for both destination-shaped `fetchExperience`\n * branches (by node id, by path).\n */\nasync function fetchByDestination(\n identity: { spaceId: string; destinationId: string; locator: string },\n resolve: () => Promise<ContentfulViewDelivery.DestinationExperienceResolutionResponse>,\n log: ReturnType<typeof createDebugLogger>,\n resolveOptions: {\n config: ResolverConfig;\n metadata: Record<string, unknown> | undefined;\n debug: boolean | undefined;\n initialViewportId: string | undefined;\n }\n): Promise<PortableRenderPlan | DestinationRedirectResult> {\n const { spaceId, destinationId, locator } = identity;\n const { config, metadata, debug, initialViewportId } = resolveOptions;\n\n log.log('resolving destination experience', { spaceId, destinationId, locator });\n\n let response: ContentfulViewDelivery.DestinationExperienceResolutionResponse;\n try {\n response = await resolve();\n } catch (err) {\n if (err instanceof ContentfulViewDelivery.NotFoundError) {\n throw err;\n }\n const reason = err instanceof Error ? err.message : String(err);\n throw new ExperienceFetchError(\n `Failed to resolve Destination \"${destinationId}\" at \"${locator}\" (space \"${spaceId}\"): ` +\n `${reason}. Check network connectivity, the access token, and that the space/` +\n `destination/node-or-path values are correct.`,\n { spaceId, experienceId: locator, cause: err }\n );\n }\n\n if ('redirect' in response) {\n log.log('destination resolved to a redirect', response.redirect);\n return { redirect: response.redirect };\n }\n\n const [firstExperience] = response.experiences;\n if (!firstExperience) {\n throw new ExperienceFetchError(\n `Destination \"${destinationId}\" resolved \"${locator}\" to zero Experiences ` +\n `(space \"${spaceId}\"). Expected exactly one hydrated Experience or a redirect.`,\n { spaceId, experienceId: locator }\n );\n }\n\n const payload = toExperiencePayloadFromDestination(firstExperience.experience);\n log.lazy('received raw payload', () => payload);\n\n return resolveExperience(payload, config, {\n metadata,\n debug,\n initialViewportId,\n });\n}\n"],"mappings":"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","names":["spaceId"]}
|
|
1
|
+
{"version":3,"sources":["../src/fetch-experience.ts"],"sourcesContent":["import type { ContentfulViewDeliveryClient } from '@contentful/experience-delivery';\nimport { ContentfulViewDelivery } from '@contentful/experience-delivery';\nimport { createDebugLogger, resolveExperience } from '@contentful/experiences-sdk-core';\nimport type { PortableRenderPlan, ResolverConfig } from '@contentful/experiences-sdk-core';\nimport { createClient } from './create-delivery-client.js';\nimport { DestinationPreviewNotSupportedError, ExperienceFetchError } from './errors.js';\nimport { PREVIEW_HOST } from './hosts.js';\nimport {\n readSourceMap,\n toExperiencePayload,\n toExperiencePayloadFromDestination,\n type ExperienceResponse,\n} from './to-experience-payload.js';\n\ntype DeliveryExperienceRequestExtensions =\n ContentfulViewDelivery.GetWithOverridesExperienceRequestExtensions;\n\nexport type PersonalizationOptions = NonNullable<\n DeliveryExperienceRequestExtensions['personalization']\n>;\n\nexport type ExperienceRequestExtensions = Omit<\n DeliveryExperienceRequestExtensions,\n 'personalization'\n>;\n\nexport type ByIdExperienceOptions = {\n spaceId: string;\n environmentId: string;\n experienceId: string;\n locale?: string;\n /**\n * Profile and events used by XDA to personalize the resolved Experience.\n */\n personalization?: PersonalizationOptions;\n /**\n * Additional XDA request extensions such as source maps. Supplying a defined\n * extension switches the request to POST.\n */\n extensions?: ExperienceRequestExtensions;\n};\n\n/**\n * Resolve an Experience by the Destination Node instead of `experienceId`\n * The destinations delivery endpoints are space-scoped only and have no locale\n * param, unlike the by-id path.\n */\nexport type ByDestinationNodeIdExperienceOptions = {\n spaceId: string;\n destinationId: string;\n nodeId: string;\n};\n\n/**\n * Resolve an Experience by the absolute path wired up to a\n * Destination Node, instead of a node id directly. Same scoping as\n * `ByDestinationNodeIdExperienceOptions` — no `environmentId` or `locale`.\n */\nexport type ByDestinationPathExperienceOptions = {\n spaceId: string;\n destinationId: string;\n /** Absolute path to resolve. Must start with `/`. */\n path: string;\n};\n\nexport type ExperienceOptions =\n ByIdExperienceOptions | ByDestinationNodeIdExperienceOptions | ByDestinationPathExperienceOptions;\n\n/**\n * A Destination resolution that the app must act on as control flow — honor\n * it by navigating or re-resolving at the new path — rather than a hydrated\n * Experience to render. Modeled as a normal return value, not a thrown error,\n * per the Destinations delivery API's own framing of a redirect response.\n */\nexport interface DestinationRedirectResult {\n redirect: { path: string };\n}\n\nexport type ClientOptions =\n | {\n accessToken: string;\n /**\n * Preview access token. Required when calling with `preview: true`.\n */\n previewToken?: string;\n /**\n * Flip between delivery (default) and preview at request time. When\n * `true`, `fetchExperience` uses `previewToken` and the preview host;\n * when `false` or unset, it uses `accessToken` and the delivery host.\n * Ignored when a pre-made `client` is passed instead of inline creds.\n */\n preview?: boolean;\n /**\n * Custom base URL for the delivery client (staging, proxy, per-region).\n * Wins over the `preview`-derived default host — combine `host` with\n * `preview: true` to point preview mode at a non-prod endpoint.\n * Omit for the standard delivery / preview hosts.\n */\n host?: string;\n }\n | { client: ContentfulViewDeliveryClient };\n\nexport type ResolveOptions = {\n config: ResolverConfig;\n /**\n * Arbitrary per-render metadata exposed to every resolver as\n * `ctx.experience.metadata` and readable at render time via the framework\n * adapter's `useExperience()` / `getExperience()`.\n */\n metadata?: Record<string, unknown>;\n /**\n * Observability switch. When on: logs the fetch (host, ids) and the raw\n * payload here, and threads through to `resolveExperience` (resolution steps\n * + `resolveData` timings) and the renderer (visible missing-component box,\n * debug fallback). A single boolean across fetch + resolve + render.\n */\n debug?: boolean;\n /**\n * Per-request fallback viewport for server-side design pre-resolution. Pass\n * the same id you seed the renderer's `initialViewportId` with (e.g. a\n * User-Agent-derived viewport) so SSR paints correct design on first render.\n */\n initialViewportId?: string;\n};\n\n// TS overload signatures, not real redeclarations — base ESLint's no-redeclare doesn't know the\n// overload form (@typescript-eslint's own version does), so each repeated signature needs a\n// disable comment.\nexport async function fetchExperience(\n experienceOptions: ByIdExperienceOptions,\n clientOptions: ClientOptions,\n resolveOptions: ResolveOptions\n): Promise<PortableRenderPlan>;\n// eslint-disable-next-line no-redeclare -- see above\nexport async function fetchExperience(\n experienceOptions: ByDestinationNodeIdExperienceOptions,\n clientOptions: ClientOptions,\n resolveOptions: ResolveOptions\n): Promise<PortableRenderPlan | DestinationRedirectResult>;\n// eslint-disable-next-line no-redeclare -- see above\nexport async function fetchExperience(\n experienceOptions: ByDestinationPathExperienceOptions,\n clientOptions: ClientOptions,\n resolveOptions: ResolveOptions\n): Promise<PortableRenderPlan | DestinationRedirectResult>;\n// eslint-disable-next-line no-redeclare -- see above\nexport async function fetchExperience(\n experienceOptions: ExperienceOptions,\n clientOptions: ClientOptions,\n resolveOptions: ResolveOptions\n): Promise<PortableRenderPlan | DestinationRedirectResult> {\n const { config, metadata, debug, initialViewportId } = resolveOptions;\n const log = createDebugLogger(debug, 'client');\n\n if (\n !('client' in clientOptions) &&\n clientOptions.preview &&\n 'destinationId' in experienceOptions\n ) {\n const { spaceId, destinationId } = experienceOptions;\n const locator =\n 'nodeId' in experienceOptions\n ? { nodeId: experienceOptions.nodeId }\n : { path: experienceOptions.path };\n throw new DestinationPreviewNotSupportedError(\n `fetchExperience() called with preview: true and a destination-shaped experienceOptions ` +\n `(destinationId \"${destinationId}\"). The Destinations Delivery API does not support ` +\n `preview mode yet — pass preview: false (or omit it) for destination-based fetches.`,\n { spaceId, destinationId, ...locator }\n );\n }\n\n let client: ContentfulViewDeliveryClient;\n if ('client' in clientOptions) {\n client = clientOptions.client;\n log.log('using caller-supplied delivery client');\n } else {\n const { accessToken, previewToken, preview, host } = clientOptions;\n if (preview && !previewToken) {\n throw new Error(\n 'fetchExperience() called with preview: true but no previewToken was provided'\n );\n }\n const resolvedHost = host ?? (preview ? PREVIEW_HOST : undefined);\n client = createClient({\n accessToken: preview ? (previewToken as string) : accessToken,\n host: resolvedHost,\n });\n log.log('created delivery client', { preview: Boolean(preview), host: resolvedHost });\n }\n\n if ('nodeId' in experienceOptions) {\n const { spaceId, destinationId, nodeId } = experienceOptions;\n return fetchByDestination(\n { spaceId, destinationId, locator: nodeId },\n () => client.destination.resolveByNodeId(spaceId, destinationId, nodeId),\n log,\n { config, metadata, debug, initialViewportId }\n );\n }\n\n if ('path' in experienceOptions) {\n const { spaceId, destinationId, path } = experienceOptions;\n return fetchByDestination(\n { spaceId, destinationId, locator: path },\n () => client.destination.resolveByPath(spaceId, destinationId, { path }),\n log,\n { config, metadata, debug, initialViewportId }\n );\n }\n\n const { spaceId, environmentId, experienceId, locale, personalization, extensions } =\n experienceOptions;\n\n log.log('fetching experience', {\n spaceId,\n environmentId,\n experienceId,\n locale,\n // avoiding log any personalization sensitive data\n personalization: personalization !== undefined,\n extensions,\n });\n\n const response = await fetchByExperienceId(client, experienceOptions);\n\n const payload = toExperiencePayload(response);\n const sourceMap = extensions?.sourceMap !== undefined ? readSourceMap(response) : undefined;\n\n log.lazy('received raw payload', () => payload);\n\n return resolveExperience(payload, config, {\n metadata,\n debug,\n initialViewportId,\n sourceMap,\n });\n}\n\nasync function fetchByExperienceId(\n client: ContentfulViewDeliveryClient,\n options: ByIdExperienceOptions\n): Promise<ExperienceResponse> {\n const { spaceId, environmentId, experienceId, locale, personalization, extensions } = options;\n const requestExtensions: DeliveryExperienceRequestExtensions = {\n ...extensions,\n ...(personalization === undefined ? {} : { personalization }),\n };\n\n try {\n // Both methods hit the same endpoint, but only the POST operation accepts\n // extensions in its request body. Keep the cacheable GET when there are no\n // extensions to evaluate.\n // The generated delivery client owns the alpha-feature header on both\n // paths, including when the caller supplied the client instance.\n //\n // Await inside the try so rejected requests are normalized below.\n if (hasRequestExtensions(requestExtensions)) {\n return await client.experience.getWithOverrides(spaceId, environmentId, experienceId, {\n locale,\n extensions: requestExtensions,\n });\n }\n\n return await client.experience.get(spaceId, environmentId, experienceId, { locale });\n } catch (err) {\n // Preserve expected 404 control flow for framework routers. Other failures\n // are wrapped with stable SDK context instead of exposing transport-specific\n // error shapes.\n if (err instanceof ContentfulViewDelivery.NotFoundError) {\n throw err;\n }\n const reason = err instanceof Error ? err.message : String(err);\n throw new ExperienceFetchError(\n `Failed to fetch Experience \"${experienceId}\" (space \"${spaceId}\", environment ` +\n `\"${environmentId}\"): ${reason}. Check network connectivity, the access token, and ` +\n `that the space/environment/experience ids are correct.`,\n { spaceId, environmentId, experienceId, cause: err }\n );\n }\n}\n\n/**\n * Shared response-handling for both destination-shaped `fetchExperience`\n * branches (by node id, by path).\n */\nasync function fetchByDestination(\n identity: { spaceId: string; destinationId: string; locator: string },\n resolve: () => Promise<ContentfulViewDelivery.DestinationExperienceResolutionResponse>,\n log: ReturnType<typeof createDebugLogger>,\n resolveOptions: {\n config: ResolverConfig;\n metadata: Record<string, unknown> | undefined;\n debug: boolean | undefined;\n initialViewportId: string | undefined;\n }\n): Promise<PortableRenderPlan | DestinationRedirectResult> {\n const { spaceId, destinationId, locator } = identity;\n const { config, metadata, debug, initialViewportId } = resolveOptions;\n\n log.log('resolving destination experience', { spaceId, destinationId, locator });\n\n let response: ContentfulViewDelivery.DestinationExperienceResolutionResponse;\n try {\n response = await resolve();\n } catch (err) {\n if (err instanceof ContentfulViewDelivery.NotFoundError) {\n throw err;\n }\n const reason = err instanceof Error ? err.message : String(err);\n throw new ExperienceFetchError(\n `Failed to resolve Destination \"${destinationId}\" at \"${locator}\" (space \"${spaceId}\"): ` +\n `${reason}. Check network connectivity, the access token, and that the space/` +\n `destination/node-or-path values are correct.`,\n { spaceId, experienceId: locator, cause: err }\n );\n }\n\n if ('redirect' in response) {\n log.log('destination resolved to a redirect', response.redirect);\n return { redirect: response.redirect };\n }\n\n const [firstExperience] = response.experiences;\n if (!firstExperience) {\n throw new ExperienceFetchError(\n `Destination \"${destinationId}\" resolved \"${locator}\" to zero Experiences ` +\n `(space \"${spaceId}\"). Expected exactly one hydrated Experience or a redirect.`,\n { spaceId, experienceId: locator }\n );\n }\n\n const payload = toExperiencePayloadFromDestination(firstExperience.experience);\n log.lazy('received raw payload', () => payload);\n\n return resolveExperience(payload, config, {\n metadata,\n debug,\n initialViewportId,\n });\n}\n\nfunction hasRequestExtensions(\n extensions: DeliveryExperienceRequestExtensions | undefined\n): extensions is DeliveryExperienceRequestExtensions {\n return (\n extensions !== undefined &&\n Object.values(extensions).some((extension) => extension !== undefined)\n );\n}\n"],"mappings":"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","names":["spaceId"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { ContentfulViewDelivery } from '@contentful/experience-delivery';
|
|
2
2
|
export { ContentfulViewDelivery, ContentfulViewDeliveryClient } from '@contentful/experience-delivery';
|
|
3
3
|
export { DestinationPreviewNotSupportedError, ExperienceFetchError } from './errors.js';
|
|
4
|
-
export { CreateClientOptions, createClient } from './create-client.js';
|
|
4
|
+
export { CreateClientOptions, RuntimeDeliveryClientOptions, createClient } from './create-delivery-client.js';
|
|
5
|
+
export { RuntimeOptimizationConfig } from './create-optimization-client.js';
|
|
5
6
|
export { ClickBuilderArgs, DEFAULT_PAGE_PROPERTIES, default as EventBuilder, EventBuilderConfig, FlagViewBuilderArgs, HoverBuilderArgs, IdentifyBuilderArgs, InteractionBuilderArgsBase, PageViewBuilderArgs, TrackBuilderArgs, UniversalEventBuilderArgs, ViewBuilderArgs } from './event-builder.js';
|
|
6
|
-
export { ByDestinationNodeIdExperienceOptions, ByDestinationPathExperienceOptions, ByIdExperienceOptions, ClientOptions, DestinationRedirectResult, ExperienceOptions, ResolveOptions, fetchExperience } from './fetch-experience.js';
|
|
7
|
-
export { ContentfulExperiences, ContentfulExperiencesConfig, ExperienceRuntime,
|
|
7
|
+
export { ByDestinationNodeIdExperienceOptions, ByDestinationPathExperienceOptions, ByIdExperienceOptions, ClientOptions, DestinationRedirectResult, ExperienceOptions, ExperienceRequestExtensions, PersonalizationOptions, ResolveOptions, fetchExperience } from './fetch-experience.js';
|
|
8
|
+
export { ContentfulExperiences, ContentfulExperiencesConfig, ExperienceRuntime, RuntimeEventBuilderConfig, RuntimeFetchByDestinationNodeOptions, RuntimeFetchByDestinationPathOptions, RuntimeFetchExperienceOptions, RuntimeResolveOptions } from './contentful-experiences.js';
|
|
9
|
+
export { AnalyticsEventMethod, EventProfileRequiredError, PersonalizationEventMethod, RuntimeEventBindings, RuntimeEventMethods } from './runtime-event-methods.js';
|
|
10
|
+
export { RUNTIME_EVENT_HANDOFF_VERSION, RuntimeEventHandoff, RuntimeEventHandoffEvent, RuntimeEventHandoffReceipt, RuntimeServerEventDelivery, assertRuntimeEventHandoffSize, parseRuntimeEventHandoff } from './runtime-event-handoff.js';
|
|
8
11
|
export { ExperienceResponse, readSourceMap, toExperiencePayload, toExperiencePayloadFromDestination } from './to-experience-payload.js';
|
|
9
12
|
export { DELIVERY_HOST, PREVIEW_HOST, PREVIEW_WEBSOCKET_HOST } from './hosts.js';
|
|
10
|
-
|
|
13
|
+
export { OptimizationData as EventOptimizationData, PartialProfile as EventProfile } from '@contentful/optimization-api-client/api-schemas';
|
|
14
|
+
import '@contentful/optimization-api-client';
|
|
11
15
|
import 'zod/mini';
|
|
12
16
|
import '@contentful/experiences-sdk-core';
|
|
13
17
|
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,17 @@ import {
|
|
|
5
5
|
} from "@contentful/experience-delivery";
|
|
6
6
|
const NotFoundError = ContentfulViewDelivery.NotFoundError;
|
|
7
7
|
import { ExperienceFetchError, DestinationPreviewNotSupportedError } from "./errors.js";
|
|
8
|
-
import { createClient } from "./create-client.js";
|
|
8
|
+
import { createClient } from "./create-delivery-client.js";
|
|
9
9
|
import { default as default2 } from "./event-builder.js";
|
|
10
10
|
export * from "./event-builder.js";
|
|
11
11
|
import { fetchExperience } from "./fetch-experience.js";
|
|
12
12
|
import { ContentfulExperiences } from "./contentful-experiences.js";
|
|
13
|
+
import { EventProfileRequiredError } from "./runtime-event-methods.js";
|
|
14
|
+
import {
|
|
15
|
+
assertRuntimeEventHandoffSize,
|
|
16
|
+
parseRuntimeEventHandoff,
|
|
17
|
+
RUNTIME_EVENT_HANDOFF_VERSION
|
|
18
|
+
} from "./runtime-event-handoff.js";
|
|
13
19
|
import {
|
|
14
20
|
readSourceMap,
|
|
15
21
|
toExperiencePayload,
|
|
@@ -23,12 +29,16 @@ export {
|
|
|
23
29
|
DELIVERY_HOST,
|
|
24
30
|
DestinationPreviewNotSupportedError,
|
|
25
31
|
default2 as EventBuilder,
|
|
32
|
+
EventProfileRequiredError,
|
|
26
33
|
ExperienceFetchError,
|
|
27
34
|
NotFoundError,
|
|
28
35
|
PREVIEW_HOST,
|
|
29
36
|
PREVIEW_WEBSOCKET_HOST,
|
|
37
|
+
RUNTIME_EVENT_HANDOFF_VERSION,
|
|
38
|
+
assertRuntimeEventHandoffSize,
|
|
30
39
|
createClient,
|
|
31
40
|
fetchExperience,
|
|
41
|
+
parseRuntimeEventHandoff,
|
|
32
42
|
readSourceMap,
|
|
33
43
|
toExperiencePayload,
|
|
34
44
|
toExperiencePayloadFromDestination
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { ContentfulViewDelivery } from '@contentful/experience-delivery';\n\nexport {\n ContentfulViewDelivery,\n ContentfulViewDeliveryClient,\n} from '@contentful/experience-delivery';\nexport const NotFoundError = ContentfulViewDelivery.NotFoundError;\n// eslint-disable-next-line no-redeclare -- value + type share a name across separate TS namespaces, not a real redeclaration\nexport type NotFoundError = InstanceType<typeof ContentfulViewDelivery.NotFoundError>;\nexport { ExperienceFetchError, DestinationPreviewNotSupportedError } from './errors.js';\nexport { createClient } from './create-client.js';\nexport type {
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { ContentfulViewDelivery } from '@contentful/experience-delivery';\n\nexport {\n ContentfulViewDelivery,\n ContentfulViewDeliveryClient,\n} from '@contentful/experience-delivery';\nexport const NotFoundError = ContentfulViewDelivery.NotFoundError;\n// eslint-disable-next-line no-redeclare -- value + type share a name across separate TS namespaces, not a real redeclaration\nexport type NotFoundError = InstanceType<typeof ContentfulViewDelivery.NotFoundError>;\nexport { ExperienceFetchError, DestinationPreviewNotSupportedError } from './errors.js';\nexport { createClient } from './create-delivery-client.js';\nexport type {\n CreateClientOptions,\n RuntimeDeliveryClientOptions,\n} from './create-delivery-client.js';\nexport type { RuntimeOptimizationConfig } from './create-optimization-client.js';\nexport { default as EventBuilder } from './event-builder.js';\nexport * from './event-builder.js';\nexport { fetchExperience } from './fetch-experience.js';\nexport { ContentfulExperiences } from './contentful-experiences.js';\nexport type {\n ContentfulExperiencesConfig,\n ExperienceRuntime,\n RuntimeFetchExperienceOptions,\n RuntimeFetchByDestinationNodeOptions,\n RuntimeFetchByDestinationPathOptions,\n RuntimeEventBuilderConfig,\n RuntimeResolveOptions,\n} from './contentful-experiences.js';\nexport { EventProfileRequiredError } from './runtime-event-methods.js';\nexport type {\n EventOptimizationData,\n EventProfile,\n AnalyticsEventMethod,\n PersonalizationEventMethod,\n RuntimeEventBindings,\n RuntimeEventMethods,\n} from './runtime-event-methods.js';\nexport {\n assertRuntimeEventHandoffSize,\n parseRuntimeEventHandoff,\n RUNTIME_EVENT_HANDOFF_VERSION,\n} from './runtime-event-handoff.js';\nexport type {\n RuntimeEventHandoff,\n RuntimeEventHandoffEvent,\n RuntimeEventHandoffReceipt,\n RuntimeServerEventDelivery,\n} from './runtime-event-handoff.js';\nexport {\n readSourceMap,\n toExperiencePayload,\n toExperiencePayloadFromDestination,\n} from './to-experience-payload.js';\nexport type { ExperienceResponse } from './to-experience-payload.js';\nexport type {\n ExperienceOptions,\n ExperienceRequestExtensions,\n PersonalizationOptions,\n ByIdExperienceOptions,\n ByDestinationNodeIdExperienceOptions,\n ByDestinationPathExperienceOptions,\n ClientOptions,\n ResolveOptions,\n DestinationRedirectResult,\n} from './fetch-experience.js';\nexport { DELIVERY_HOST, PREVIEW_HOST, PREVIEW_WEBSOCKET_HOST } from './hosts.js';\n"],"mappings":"AAAA,SAAS,8BAA8B;AAEvC;AAAA,EACE,0BAAAA;AAAA,EACA;AAAA,OACK;AACA,MAAM,gBAAgB,uBAAuB;AAGpD,SAAS,sBAAsB,2CAA2C;AAC1E,SAAS,oBAAoB;AAM7B,SAAoB,WAAXC,gBAA+B;AACxC,cAAc;AACd,SAAS,uBAAuB;AAChC,SAAS,6BAA6B;AAUtC,SAAS,iCAAiC;AAS1C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAOP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAaP,SAAS,eAAe,cAAc,8BAA8B;","names":["ContentfulViewDelivery","default"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ExperienceEvent, InsightsEvent } from '@contentful/optimization-api-client/api-schemas';
|
|
2
|
+
|
|
3
|
+
declare const RUNTIME_EVENT_HANDOFF_VERSION: 1;
|
|
4
|
+
/** Server-only delivery choice. Browser runtimes always commit received handoffs. */
|
|
5
|
+
type RuntimeServerEventDelivery = 'commit' | 'handoff';
|
|
6
|
+
type RuntimeEventHandoffEvent = {
|
|
7
|
+
readonly transport: 'personalization';
|
|
8
|
+
readonly event: ExperienceEvent;
|
|
9
|
+
} | {
|
|
10
|
+
readonly transport: 'analytics';
|
|
11
|
+
readonly event: InsightsEvent;
|
|
12
|
+
};
|
|
13
|
+
/** Plain JSON passed from one Node request to its paired Web runtime. */
|
|
14
|
+
interface RuntimeEventHandoff {
|
|
15
|
+
readonly version: typeof RUNTIME_EVENT_HANDOFF_VERSION;
|
|
16
|
+
readonly spaceId: string;
|
|
17
|
+
readonly environmentId: string;
|
|
18
|
+
/** Profile known before any preflight event was evaluated. */
|
|
19
|
+
readonly initialProfileId?: string;
|
|
20
|
+
readonly events: readonly RuntimeEventHandoffEvent[];
|
|
21
|
+
/** App-defined route identity for a successfully staged initial page event. */
|
|
22
|
+
readonly initialPageRouteKey?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Returned only after the complete Web replay succeeds. */
|
|
25
|
+
interface RuntimeEventHandoffReceipt {
|
|
26
|
+
readonly initialPageRouteKey?: string;
|
|
27
|
+
}
|
|
28
|
+
/** Enforces the private response payload cap without parsing trusted Node data. */
|
|
29
|
+
declare function assertRuntimeEventHandoffSize(input: unknown): void;
|
|
30
|
+
/** Validates an untrusted serialized handoff before Web starts any transport. */
|
|
31
|
+
declare function parseRuntimeEventHandoff(input: unknown): RuntimeEventHandoff;
|
|
32
|
+
|
|
33
|
+
export { RUNTIME_EVENT_HANDOFF_VERSION, type RuntimeEventHandoff, type RuntimeEventHandoffEvent, type RuntimeEventHandoffReceipt, type RuntimeServerEventDelivery, assertRuntimeEventHandoffSize, parseRuntimeEventHandoff };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExperienceEvent as PersonalizationEventSchema,
|
|
3
|
+
InsightsEvent as AnalyticsEventSchema,
|
|
4
|
+
parseWithFriendlyError as parseOptimizationSchema
|
|
5
|
+
} from "@contentful/optimization-api-client/api-schemas";
|
|
6
|
+
import * as z from "zod/mini";
|
|
7
|
+
const RUNTIME_EVENT_HANDOFF_VERSION = 1;
|
|
8
|
+
const RUNTIME_EVENT_HANDOFF_MAX_BYTES = 64 * 1024;
|
|
9
|
+
const encoder = new globalThis.TextEncoder();
|
|
10
|
+
const RuntimeEventHandoffSchema = z.object({
|
|
11
|
+
version: z.literal(RUNTIME_EVENT_HANDOFF_VERSION),
|
|
12
|
+
spaceId: z.string(),
|
|
13
|
+
environmentId: z.string(),
|
|
14
|
+
initialProfileId: z.optional(z.string()),
|
|
15
|
+
events: z.array(
|
|
16
|
+
z.discriminatedUnion("transport", [
|
|
17
|
+
z.object({
|
|
18
|
+
transport: z.literal("personalization"),
|
|
19
|
+
event: PersonalizationEventSchema
|
|
20
|
+
}),
|
|
21
|
+
z.object({
|
|
22
|
+
transport: z.literal("analytics"),
|
|
23
|
+
event: AnalyticsEventSchema
|
|
24
|
+
})
|
|
25
|
+
])
|
|
26
|
+
),
|
|
27
|
+
initialPageRouteKey: z.optional(z.string())
|
|
28
|
+
});
|
|
29
|
+
function assertRuntimeEventHandoffSize(input) {
|
|
30
|
+
let serialized;
|
|
31
|
+
try {
|
|
32
|
+
serialized = JSON.stringify(input);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new TypeError("Runtime event handoff must be JSON serializable", { cause: error });
|
|
35
|
+
}
|
|
36
|
+
if (serialized === void 0) throw new TypeError("Runtime event handoff is not serializable");
|
|
37
|
+
if (encoder.encode(serialized).byteLength > RUNTIME_EVENT_HANDOFF_MAX_BYTES) {
|
|
38
|
+
throw new TypeError(
|
|
39
|
+
`Runtime event handoff exceeds ${RUNTIME_EVENT_HANDOFF_MAX_BYTES} serialized bytes`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function parseRuntimeEventHandoff(input) {
|
|
44
|
+
assertRuntimeEventHandoffSize(input);
|
|
45
|
+
const handoff = parseOptimizationSchema(RuntimeEventHandoffSchema, input);
|
|
46
|
+
if (handoff.initialPageRouteKey !== void 0 && !handoff.events.some(
|
|
47
|
+
(entry) => entry.transport === "personalization" && entry.event.type === "page"
|
|
48
|
+
)) {
|
|
49
|
+
throw new TypeError("Runtime event handoff initial page requires a staged page event");
|
|
50
|
+
}
|
|
51
|
+
let hasProfile = handoff.initialProfileId !== void 0;
|
|
52
|
+
for (const entry of handoff.events) {
|
|
53
|
+
if (entry.transport === "personalization") {
|
|
54
|
+
hasProfile = true;
|
|
55
|
+
} else if (!hasProfile) {
|
|
56
|
+
throw new TypeError(
|
|
57
|
+
"Runtime event handoff Analytics events require an initial or preceding Personalization profile"
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return handoff;
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
RUNTIME_EVENT_HANDOFF_VERSION,
|
|
65
|
+
assertRuntimeEventHandoffSize,
|
|
66
|
+
parseRuntimeEventHandoff
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=runtime-event-handoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/runtime-event-handoff.ts"],"sourcesContent":["import {\n ExperienceEvent as PersonalizationEventSchema,\n InsightsEvent as AnalyticsEventSchema,\n parseWithFriendlyError as parseOptimizationSchema,\n type ExperienceEvent as PersonalizationEvent,\n type InsightsEvent as AnalyticsEvent,\n} from '@contentful/optimization-api-client/api-schemas';\nimport * as z from 'zod/mini';\n\nexport const RUNTIME_EVENT_HANDOFF_VERSION = 1 as const;\n\nconst RUNTIME_EVENT_HANDOFF_MAX_BYTES = 64 * 1024;\nconst encoder = new globalThis.TextEncoder();\nconst RuntimeEventHandoffSchema = z.object({\n version: z.literal(RUNTIME_EVENT_HANDOFF_VERSION),\n spaceId: z.string(),\n environmentId: z.string(),\n initialProfileId: z.optional(z.string()),\n events: z.array(\n z.discriminatedUnion('transport', [\n z.object({\n transport: z.literal('personalization'),\n event: PersonalizationEventSchema,\n }),\n z.object({\n transport: z.literal('analytics'),\n event: AnalyticsEventSchema,\n }),\n ])\n ),\n initialPageRouteKey: z.optional(z.string()),\n});\n\n/** Server-only delivery choice. Browser runtimes always commit received handoffs. */\nexport type RuntimeServerEventDelivery = 'commit' | 'handoff';\n\nexport type RuntimeEventHandoffEvent =\n | { readonly transport: 'personalization'; readonly event: PersonalizationEvent }\n | { readonly transport: 'analytics'; readonly event: AnalyticsEvent };\n\n/** Plain JSON passed from one Node request to its paired Web runtime. */\nexport interface RuntimeEventHandoff {\n readonly version: typeof RUNTIME_EVENT_HANDOFF_VERSION;\n readonly spaceId: string;\n readonly environmentId: string;\n /** Profile known before any preflight event was evaluated. */\n readonly initialProfileId?: string;\n readonly events: readonly RuntimeEventHandoffEvent[];\n /** App-defined route identity for a successfully staged initial page event. */\n readonly initialPageRouteKey?: string;\n}\n\n/** Returned only after the complete Web replay succeeds. */\nexport interface RuntimeEventHandoffReceipt {\n readonly initialPageRouteKey?: string;\n}\n\n/** Enforces the private response payload cap without parsing trusted Node data. */\nexport function assertRuntimeEventHandoffSize(input: unknown): void {\n let serialized: string | undefined;\n try {\n serialized = JSON.stringify(input);\n } catch (error) {\n throw new TypeError('Runtime event handoff must be JSON serializable', { cause: error });\n }\n if (serialized === undefined) throw new TypeError('Runtime event handoff is not serializable');\n if (encoder.encode(serialized).byteLength > RUNTIME_EVENT_HANDOFF_MAX_BYTES) {\n throw new TypeError(\n `Runtime event handoff exceeds ${RUNTIME_EVENT_HANDOFF_MAX_BYTES} serialized bytes`\n );\n }\n}\n\n/** Validates an untrusted serialized handoff before Web starts any transport. */\nexport function parseRuntimeEventHandoff(input: unknown): RuntimeEventHandoff {\n assertRuntimeEventHandoffSize(input);\n const handoff = parseOptimizationSchema(RuntimeEventHandoffSchema, input);\n\n if (\n handoff.initialPageRouteKey !== undefined &&\n !handoff.events.some(\n (entry) => entry.transport === 'personalization' && entry.event.type === 'page'\n )\n ) {\n throw new TypeError('Runtime event handoff initial page requires a staged page event');\n }\n\n let hasProfile = handoff.initialProfileId !== undefined;\n for (const entry of handoff.events) {\n if (entry.transport === 'personalization') {\n hasProfile = true;\n } else if (!hasProfile) {\n throw new TypeError(\n 'Runtime event handoff Analytics events require an initial or preceding Personalization profile'\n );\n }\n }\n\n return handoff;\n}\n"],"mappings":"AAAA;AAAA,EACE,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,0BAA0B;AAAA,OAGrB;AACP,YAAY,OAAO;AAEZ,MAAM,gCAAgC;AAE7C,MAAM,kCAAkC,KAAK;AAC7C,MAAM,UAAU,IAAI,WAAW,YAAY;AAC3C,MAAM,4BAA4B,EAAE,OAAO;AAAA,EACzC,SAAS,EAAE,QAAQ,6BAA6B;AAAA,EAChD,SAAS,EAAE,OAAO;AAAA,EAClB,eAAe,EAAE,OAAO;AAAA,EACxB,kBAAkB,EAAE,SAAS,EAAE,OAAO,CAAC;AAAA,EACvC,QAAQ,EAAE;AAAA,IACR,EAAE,mBAAmB,aAAa;AAAA,MAChC,EAAE,OAAO;AAAA,QACP,WAAW,EAAE,QAAQ,iBAAiB;AAAA,QACtC,OAAO;AAAA,MACT,CAAC;AAAA,MACD,EAAE,OAAO;AAAA,QACP,WAAW,EAAE,QAAQ,WAAW;AAAA,QAChC,OAAO;AAAA,MACT,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EACA,qBAAqB,EAAE,SAAS,EAAE,OAAO,CAAC;AAC5C,CAAC;AA2BM,SAAS,8BAA8B,OAAsB;AAClE,MAAI;AACJ,MAAI;AACF,iBAAa,KAAK,UAAU,KAAK;AAAA,EACnC,SAAS,OAAO;AACd,UAAM,IAAI,UAAU,mDAAmD,EAAE,OAAO,MAAM,CAAC;AAAA,EACzF;AACA,MAAI,eAAe,OAAW,OAAM,IAAI,UAAU,2CAA2C;AAC7F,MAAI,QAAQ,OAAO,UAAU,EAAE,aAAa,iCAAiC;AAC3E,UAAM,IAAI;AAAA,MACR,iCAAiC,+BAA+B;AAAA,IAClE;AAAA,EACF;AACF;AAGO,SAAS,yBAAyB,OAAqC;AAC5E,gCAA8B,KAAK;AACnC,QAAM,UAAU,wBAAwB,2BAA2B,KAAK;AAExE,MACE,QAAQ,wBAAwB,UAChC,CAAC,QAAQ,OAAO;AAAA,IACd,CAAC,UAAU,MAAM,cAAc,qBAAqB,MAAM,MAAM,SAAS;AAAA,EAC3E,GACA;AACA,UAAM,IAAI,UAAU,iEAAiE;AAAA,EACvF;AAEA,MAAI,aAAa,QAAQ,qBAAqB;AAC9C,aAAW,SAAS,QAAQ,QAAQ;AAClC,QAAI,MAAM,cAAc,mBAAmB;AACzC,mBAAa;AAAA,IACf,WAAW,CAAC,YAAY;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ExperienceApiClient, InsightsApiClient } from '@contentful/optimization-api-client';
|
|
2
|
+
import { PartialProfile, ExperienceEvent, OptimizationData, InsightsEvent } from '@contentful/optimization-api-client/api-schemas';
|
|
3
|
+
export { OptimizationData as EventOptimizationData, PartialProfile as EventProfile } from '@contentful/optimization-api-client/api-schemas';
|
|
4
|
+
import EventBuilder, { UniversalEventBuilderArgs, IdentifyBuilderArgs, PageViewBuilderArgs, TrackBuilderArgs, ViewBuilderArgs, ClickBuilderArgs, HoverBuilderArgs, FlagViewBuilderArgs } from './event-builder.js';
|
|
5
|
+
import 'zod/mini';
|
|
6
|
+
|
|
7
|
+
type PersonalizationEventMethod = 'identify' | 'page' | 'track';
|
|
8
|
+
type AnalyticsEventMethod = 'trackView' | 'trackClick' | 'trackHover' | 'trackFlagView';
|
|
9
|
+
/** The Optimization API surface used by the shared runtime. */
|
|
10
|
+
interface RuntimeOptimizationApiClient {
|
|
11
|
+
readonly personalization: Pick<ExperienceApiClient, 'upsertProfile'>;
|
|
12
|
+
readonly analytics: Pick<InsightsApiClient, 'sendBatchEvents'>;
|
|
13
|
+
}
|
|
14
|
+
/** Internal strategy used by a runtime to commit or stage built events. */
|
|
15
|
+
interface RuntimeEventDispatch {
|
|
16
|
+
personalization(event: ExperienceEvent, profile: PartialProfile | undefined): Promise<OptimizationData>;
|
|
17
|
+
analytics(event: InsightsEvent, profile: PartialProfile): Promise<boolean>;
|
|
18
|
+
}
|
|
19
|
+
/** Runtime-owned state and context used to bind otherwise stateless event methods. */
|
|
20
|
+
interface RuntimeEventBindings {
|
|
21
|
+
getProfile: () => PartialProfile | undefined;
|
|
22
|
+
setProfile: (profile: PartialProfile) => void;
|
|
23
|
+
/** Changes when an external session boundary must invalidate an in-flight profile response. */
|
|
24
|
+
getProfileRevision?: () => number;
|
|
25
|
+
getEventContext?: () => UniversalEventBuilderArgs;
|
|
26
|
+
getConsent?: () => boolean | undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Event-triggering surface shared by the public Node and Web runtimes.
|
|
30
|
+
* Profile-producing calls are not serialized; await them before another
|
|
31
|
+
* profile-producing or Analytics call on the same bound runtime. In Node
|
|
32
|
+
* handoff mode an Analytics `true` means accepted into the handoff journal,
|
|
33
|
+
* not delivered to the Analytics transport.
|
|
34
|
+
*/
|
|
35
|
+
interface RuntimeEventMethods {
|
|
36
|
+
readonly profile: PartialProfile | undefined;
|
|
37
|
+
identify(args: IdentifyBuilderArgs): Promise<OptimizationData>;
|
|
38
|
+
page(args?: PageViewBuilderArgs): Promise<OptimizationData>;
|
|
39
|
+
track(args: TrackBuilderArgs): Promise<OptimizationData>;
|
|
40
|
+
trackView(args: ViewBuilderArgs): Promise<boolean>;
|
|
41
|
+
trackClick(args: ClickBuilderArgs): Promise<boolean>;
|
|
42
|
+
trackHover(args: HoverBuilderArgs): Promise<boolean>;
|
|
43
|
+
trackFlagView(args: FlagViewBuilderArgs): Promise<boolean>;
|
|
44
|
+
}
|
|
45
|
+
/** Thrown when an Analytics event is triggered before a profile is available. */
|
|
46
|
+
declare class EventProfileRequiredError extends Error {
|
|
47
|
+
readonly method: AnalyticsEventMethod;
|
|
48
|
+
constructor(method: AnalyticsEventMethod);
|
|
49
|
+
}
|
|
50
|
+
declare function createRuntimeEventMethods(api: RuntimeOptimizationApiClient, eventBuilder: EventBuilder, bindings: RuntimeEventBindings, dispatch?: RuntimeEventDispatch): RuntimeEventMethods;
|
|
51
|
+
|
|
52
|
+
export { type AnalyticsEventMethod, EventProfileRequiredError, type PersonalizationEventMethod, type RuntimeEventBindings, type RuntimeEventDispatch, type RuntimeEventMethods, type RuntimeOptimizationApiClient, createRuntimeEventMethods };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
class EventProfileRequiredError extends Error {
|
|
2
|
+
method;
|
|
3
|
+
constructor(method) {
|
|
4
|
+
super(
|
|
5
|
+
`${method}() requires a current event profile. Supply an initial profile or await identify(), page(), or track() first.`
|
|
6
|
+
);
|
|
7
|
+
this.name = "EventProfileRequiredError";
|
|
8
|
+
this.method = method;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
class BoundRuntimeEventMethods {
|
|
12
|
+
constructor(eventBuilder, bindings, dispatch) {
|
|
13
|
+
this.eventBuilder = eventBuilder;
|
|
14
|
+
this.bindings = bindings;
|
|
15
|
+
this.dispatch = dispatch;
|
|
16
|
+
}
|
|
17
|
+
eventBuilder;
|
|
18
|
+
bindings;
|
|
19
|
+
dispatch;
|
|
20
|
+
get profile() {
|
|
21
|
+
return this.bindings.getProfile();
|
|
22
|
+
}
|
|
23
|
+
async identify(args) {
|
|
24
|
+
return this.sendPersonalizationEvent(
|
|
25
|
+
this.eventBuilder.buildIdentify(this.withEventContext(args))
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
async page(args = {}) {
|
|
29
|
+
return this.sendPersonalizationEvent(
|
|
30
|
+
this.eventBuilder.buildPageView(this.withEventContext(args))
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
async track(args) {
|
|
34
|
+
return this.sendPersonalizationEvent(this.eventBuilder.buildTrack(this.withEventContext(args)));
|
|
35
|
+
}
|
|
36
|
+
async trackView(args) {
|
|
37
|
+
return this.sendAnalyticsEvent(
|
|
38
|
+
"trackView",
|
|
39
|
+
this.eventBuilder.buildView(this.withEventContext(args))
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
async trackClick(args) {
|
|
43
|
+
return this.sendAnalyticsEvent(
|
|
44
|
+
"trackClick",
|
|
45
|
+
this.eventBuilder.buildClick(this.withEventContext(args))
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
async trackHover(args) {
|
|
49
|
+
return this.sendAnalyticsEvent(
|
|
50
|
+
"trackHover",
|
|
51
|
+
this.eventBuilder.buildHover(this.withEventContext(args))
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
async trackFlagView(args) {
|
|
55
|
+
return this.sendAnalyticsEvent(
|
|
56
|
+
"trackFlagView",
|
|
57
|
+
this.eventBuilder.buildFlagView(this.withEventContext(args))
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
withEventContext(args) {
|
|
61
|
+
return { ...this.bindings.getEventContext?.(), ...args };
|
|
62
|
+
}
|
|
63
|
+
withConsent(event) {
|
|
64
|
+
const consent = this.bindings.getConsent?.();
|
|
65
|
+
if (consent === void 0) return event;
|
|
66
|
+
return {
|
|
67
|
+
...event,
|
|
68
|
+
context: {
|
|
69
|
+
...event.context,
|
|
70
|
+
gdpr: { ...event.context.gdpr, isConsentGiven: consent }
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async sendPersonalizationEvent(event) {
|
|
75
|
+
const eventWithConsent = this.withConsent(event);
|
|
76
|
+
const profileRevision = this.bindings.getProfileRevision?.();
|
|
77
|
+
const data = await this.dispatch.personalization(eventWithConsent, this.profile);
|
|
78
|
+
if (profileRevision === void 0 || this.bindings.getProfileRevision?.() === profileRevision) {
|
|
79
|
+
this.bindings.setProfile(data.profile);
|
|
80
|
+
}
|
|
81
|
+
return data;
|
|
82
|
+
}
|
|
83
|
+
async sendAnalyticsEvent(method, event) {
|
|
84
|
+
const profile = this.profile;
|
|
85
|
+
if (profile === void 0) throw new EventProfileRequiredError(method);
|
|
86
|
+
return this.dispatch.analytics(this.withConsent(event), profile);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function createDirectEventDispatch(api) {
|
|
90
|
+
return {
|
|
91
|
+
personalization: (event, profile) => api.personalization.upsertProfile(
|
|
92
|
+
{ profileId: profile?.id, events: [event] },
|
|
93
|
+
{ locale: event.context.locale }
|
|
94
|
+
),
|
|
95
|
+
analytics: (event, profile) => api.analytics.sendBatchEvents([{ profile, events: [event] }])
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function createRuntimeEventMethods(api, eventBuilder, bindings, dispatch = createDirectEventDispatch(api)) {
|
|
99
|
+
return new BoundRuntimeEventMethods(eventBuilder, bindings, dispatch);
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
EventProfileRequiredError,
|
|
103
|
+
createRuntimeEventMethods
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=runtime-event-methods.js.map
|