import * as metricsLocation from "./location"; import * as metricsUtil from "./util"; import { isNothing, isSome } from "@jet/environment"; import * as validation from "@jet/environment/json/validation"; import * as models from "../../../api/models"; import * as serverData from "../../../foundation/json-parsing/server-data"; import * as contentAttributes from "../../content/attributes"; import * as content from "../../content/content"; import * as productVariants from "../../product-page/product-page-variants"; import * as lockups from "../../../common/lockups/lockups"; //* ************************* //* Impression Metrics //* ************************* function generateImpressionFields(objectGraph, options) { /// please note this is not the metrics event itself but the fields for the items that impress /// there is a very strict spec for these items so please do not modify this unless you know /// exactly what you are doing. var _a, _b, _c, _d; let id = options.id; let title = options.title; if (serverData.isDefinedNonNullNonEmpty(options.anonymizationOptions)) { const anonymizationString = (_b = (_a = options.anonymizationOptions) === null || _a === void 0 ? void 0 : _a.anonymizationString) !== null && _b !== void 0 ? _b : "ANONYMOUS"; id = anonymizationString; title = anonymizationString; } else if (id && options.createUniqueImpressionId && !objectGraph.client.isWatch) { id = createUniqueImpressionId(objectGraph, id); } const impressionData = { id: metricsUtil.emptyStringIfNullOrUndefined(id), name: metricsUtil.emptyStringIfNullOrUndefined(title), impressionType: metricsUtil.targetTypeForMetricsOptions(objectGraph, options), }; const idType = metricsUtil.idTypeForMetricsOptions(options); if (isSome(idType)) { impressionData["idType"] = idType; } if (options && options.kind) { impressionData["kind"] = options.kind; } if (options && options.softwareType) { impressionData["softwareType"] = options.softwareType; } if (options && options.recoMetricsData) { Object.assign(impressionData, options.recoMetricsData); } if (options && options.mercuryMetricsData) { Object.assign(impressionData, options.mercuryMetricsData); } if (options && options.lockupDisplayStyle) { impressionData["platformDisplayStyle"] = options.lockupDisplayStyle; } const shouldOmitImpressionIndex = (_c = options.shouldOmitImpressionIndex) !== null && _c !== void 0 ? _c : false; if (options && options.locationTracker && !shouldOmitImpressionIndex) { const currentPosition = metricsLocation.currentPosition(options.locationTracker); impressionData["impressionIndex"] = currentPosition; if (impressionData.id === "") { impressionData.id = currentPosition.toString(); impressionData["idType"] = "sequential"; } } if (options && options.modelSource) { impressionData["modelSource"] = options.modelSource; } // Add offerType if available if (serverData.isDefinedNonNull(options.offerType)) { impressionData["offerType"] = options.offerType; } // Arcade Upsell Tracking if (options && serverData.isDefinedNonNull(options.displaysArcadeUpsell)) { impressionData["displaysArcadeUpsell"] = options.displaysArcadeUpsell; } // Preorder Tracking if (options && serverData.isDefinedNonNull(options.isPreorder)) { impressionData["isPreorder"] = options.isPreorder; } // Add adamId if available if (serverData.isDefinedNonNull(options.adamId) && serverData.isNullOrEmpty(options.anonymizationOptions)) { impressionData["adamId"] = options.adamId; } // Badges if (options && serverData.isDefinedNonNull(options.badges)) { impressionData["badges"] = options.badges; } // In App Event ID if (options && serverData.isDefinedNonNull(options.inAppEventId)) { impressionData["inAppEventId"] = options.inAppEventId; } // Related subject IDs if (options && serverData.isDefinedNonNull(options.relatedSubjectIds)) { impressionData["relatedSubjectIds"] = options.relatedSubjectIds; } /// Hints entity if ((_d = options === null || options === void 0 ? void 0 : options.hintsEntity) === null || _d === void 0 ? void 0 : _d.length) { impressionData["hintsEntity"] = options.hintsEntity; } // autoAdvanceInterval for auto scrolling Views if (options && serverData.isDefinedNonNull(options.autoAdvanceInterval)) { impressionData["autoAdvanceInterval"] = options.autoAdvanceInterval; } // Add fcKind if available if (serverData.isDefinedNonNull(options === null || options === void 0 ? void 0 : options.fcKind)) { impressionData["fcKind"] = options.fcKind; } if (serverData.isDefinedNonNull(options === null || options === void 0 ? void 0 : options.canonicalId)) { impressionData["canonicalId"] = options.canonicalId; } if (serverData.isDefinedNonNull(options === null || options === void 0 ? void 0 : options.displayStyle)) { impressionData["displayStyle"] = options.displayStyle; } // Add product variant fields if available if (serverData.isDefinedNonNull(options.productVariantData)) { Object.assign(impressionData, productVariants.contentFieldsForProductVariantData(options.productVariantData)); } if (isSome(options.contentRating)) { impressionData["contentRating"] = options.contentRating; } if (isSome(options.bundleId)) { impressionData["bundleId"] = options.bundleId; } if (impressionData.id === "") { objectGraph.console.log(`impressionId missing. Tracking broken for ${impressionData.name} of ${impressionData.impressionType}`); } return impressionData; } export function addImpressionFields(objectGraph, impressionable, options) { if (!impressionable) { return; } impressionable.impressionMetrics = new models.ImpressionMetrics(generateImpressionFields(objectGraph, options)); } export function addImpressionFieldsToTagRoomHeader(objectGraph, impressionable, options) { if (!impressionable) { return; } const impressionMetrics = new models.ImpressionMetrics(generateImpressionFields(objectGraph, options)); delete impressionMetrics.fields.impressionIndex; impressionable.impressionMetrics = impressionMetrics; } /** * Adds impression fields to the search metadata ribbon item. * * @param objectGraph - The object graph. * @param metadataRibbonItem - The metadata ribbon item. * @param options - The metrics impression options. */ export function addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, metadataRibbonItem, options) { var _a; if (!metadataRibbonItem) { return; } addImpressionFields(objectGraph, metadataRibbonItem, options); if ((options.isAdvert || options.isAdEligible) && ((_a = options.pageInformation) === null || _a === void 0 ? void 0 : _a.iAdInfo) && metadataRibbonItem.impressionMetrics) { metadataRibbonItem.impressionMetrics = new models.FastImpressionMetrics(metadataRibbonItem.impressionMetrics, true); } } /** * Add impressions fields to a Today card. * @param objectGraph The object graph. * @param card The Today card to apply the impressions fields to. * @param options A set of metrics options to gather data from. * @param franchise The card franchise. * @param cardType The card type. * @param isOnboardingCard Whether the card is an onboarding card. * @param coerceNullToEmptyStrings Whether `null` values for `franchise` and `cardType` should be coerced into empty strings. This * was a behaviour that was always enabled by default, but as of the implementation of Chainlink ads, we don't necessarily want empty strings. * Defaults to true to maintain legacy behaviour for other cards that don't want to opt out. * @returns */ export function addImpressionsFieldsToTodayCard(objectGraph, card, options, franchise, cardType, isOnboardingCard, coerceNullToEmptyStrings = true) { var _a, _b, _c, _d, _e, _f; if (!card) { return; } const impressionData = generateImpressionFields(objectGraph, options); if (coerceNullToEmptyStrings) { impressionData["franchise"] = metricsUtil.emptyStringIfNullOrUndefined(franchise); impressionData["cardType"] = metricsUtil.emptyStringIfNullOrUndefined(cardType); } else { if (franchise) { impressionData["franchise"] = franchise; } if (cardType) { impressionData["cardType"] = cardType; } } if (isOnboardingCard) { impressionData["isOnboardingCard"] = isOnboardingCard; } if (((_b = (_a = options === null || options === void 0 ? void 0 : options.optimizationEntityId) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0) { impressionData["optimizationEntityId"] = options.optimizationEntityId; } if (((_d = (_c = options === null || options === void 0 ? void 0 : options.optimizationId) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0) { impressionData["optimizationId"] = options.optimizationId; } if (isSome(options === null || options === void 0 ? void 0 : options.rowIndex)) { impressionData["rowIndex"] = options.rowIndex; } card.impressionMetrics = new models.ImpressionMetrics(impressionData); if ((options.isAdvert || options.isAdEligible) && ((_e = options.pageInformation) === null || _e === void 0 ? void 0 : _e.iAdInfo)) { const sanitizedIAdData = metricsUtil.sanitizedMetricsDictionary(options.pageInformation.iAdInfo.fastImpressionsFieldsForCurrentItem(options.locationTracker, options.adSlotOverride)); Object.assign(card.impressionMetrics.fields, sanitizedIAdData); card.impressionMetrics = new models.FastImpressionMetrics(card.impressionMetrics, true); if (options.isAdvert) { (_f = card.impressionMetrics) === null || _f === void 0 ? true : delete _f.fields["cardType"]; } } } export function addImpressionsFieldsToAd(objectGraph, impressionable, options, iAdData) { if (!impressionable || !iAdData) { return; } addImpressionFields(objectGraph, impressionable, options); const sanitizedIAdData = metricsUtil.sanitizedMetricsDictionary(iAdData.fastImpressionsFieldsForCurrentItem(options.locationTracker, options.adSlotOverride)); if (isSome(impressionable.impressionMetrics)) { Object.assign(impressionable.impressionMetrics.fields, sanitizedIAdData); const disableFastImpressions = serverData.asBooleanOrFalse(options.disableFastImpressionsForAds); impressionable.impressionMetrics = new models.FastImpressionMetrics(impressionable.impressionMetrics, !disableFastImpressions); } /** * This is an longstanding hack to prevent ad and organic impression showing same content (i.e. AdamId) * to properly have nonclashing identifiers, and have separate impression objects. * Even with impression item ids' hasing on `impressionIndex` - this is still needed in case the Nth rotated ad is the same as Nth organic. * This is orthogonal to "ad_container" in location stack when building ads for ad-rotation, which is a separate metrics trick. */ if (isSome(impressionable.impressionMetrics)) { impressionable.impressionMetrics.fields["parentId"] = "ad_container"; } } export function addImpressionFieldsToInAppPurchaseLockup(objectGraph, lockup, options) { if (!lockup) { return; } // The code that calls this method currently does this indirectly, // this is just to guard against that code changing in the future. if (!lockup.impressionMetrics) { addImpressionFields(objectGraph, lockup, options); } if (lockup.parent && lockup.parent.adamId && isSome(lockup.impressionMetrics)) { lockup.impressionMetrics.fields["parentAdamId"] = metricsUtil.emptyStringIfNullOrUndefined(lockup.parent.adamId); } } export function impressionOptionsForLockup(objectGraph, data, lockup, displayStyle, baseOptions, canDisplayArcadeOfferButton, attributePlatformOverride = undefined) { var _a; const options = impressionOptions(objectGraph, data, lockup.title, baseOptions); options.lockupDisplayStyle = displayStyle; options.contentRating = (_a = lockup.offerDisplayProperties) === null || _a === void 0 ? void 0 : _a.contentRating; options.bundleId = lockup.bundleId; // If no targetType is provided, set the correct value for the platform. if (serverData.isNullOrEmpty(options.targetType)) { options.targetType = objectGraph.client.isVision ? "lockupSmall" : "lockup"; } if (canDisplayArcadeOfferButton && content.isArcadeSupported(objectGraph, data)) { options.displaysArcadeUpsell = true; } // If it has a discounted offer then use the options already set const parentID = baseOptions["id"]; if (serverData.isDefinedNonNullNonEmpty(lockups.discountedOfferFromData(data)) && serverData.isDefinedNonNull(parentID) && parentID.length > 0) { options.id = parentID; } return options; } export function impressionOptions(objectGraph, data, title, baseOptions, attributePlatformOverride = undefined) { return validation.context("impressionOptions", () => { const kind = metricsUtil.metricsKindFromData(objectGraph, data); const softwareType = metricsUtil.softwareTypeForData(objectGraph, data); const metricsOptions = { ...baseOptions, kind: kind, softwareType: softwareType, title: title, id: data.id, }; // Include offerType for pre-order impressions // NOTE: Even though metricsOptions.isPreorder may be true, we don't key off that here because // offerType implies an offer exists, which is not always true (e.g. an Arcade Coming Soon breakout). const containsPreorderOffer = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isPreorder", attributePlatformOverride); if (containsPreorderOffer) { metricsOptions.offerType = "preorder"; } return metricsOptions; }); } /// Returns impression options for Arcade See All Games ribbon item. export function impressionOptionsForArcadeSeeAllGamesRibbonItem(baseOptions) { return validation.context("impressionOptionsForArcadeSeeAllGamesRibbonItem", () => { const metricsOptions = { ...baseOptions, id: "AllGames", idType: "none", kind: null, softwareType: null, title: "All Games", }; return metricsOptions; }); } /// Returns impression options for tag ribbon item on product page. export function impressionOptionsForTagRibbonItem(objectGraph, data, title, baseOptions) { return validation.context("impressionOptions", () => { const kind = metricsUtil.metricsKindFromData(objectGraph, data); const softwareType = metricsUtil.softwareTypeForData(objectGraph, data); const metricsOptions = { ...baseOptions, kind: kind, softwareType: softwareType, title: title, id: data.id, idType: "its_id", displayStyle: "textOnly", }; return metricsOptions; }); } /// Returns impression options for tag ribbon item on product page. export function impressionOptionsForTagHeader(objectGraph, data, title, baseOptions) { return validation.context("impressionOptions", () => { const kind = metricsUtil.metricsKindFromData(objectGraph, data); const softwareType = metricsUtil.softwareTypeForData(objectGraph, data); const metricsOptions = { ...baseOptions, kind: kind, softwareType: softwareType, title: title, id: data.id, idType: "its_contentId", targetType: "tagHeader", }; return metricsOptions; }); } /// Returns impression options for Arcade Choose Your Favorites brick. export function impressionOptionsForArcadeChooseYourFavoritesBrick(baseOptions) { return validation.context("impressionOptionsForArcadeChooseYourFavoritesBrick", () => { const metricsOptions = { ...baseOptions, id: "", kind: null, softwareType: null, title: "choose_your_games_brick", }; return metricsOptions; }); } export function impressionOptionsForMetadataRibbonItem(baseOptions, id, name, idType) { return validation.context("impressionOptionsForMetadataRibbonItem", () => { const metricsOptions = { ...baseOptions, id: id, kind: null, softwareType: null, title: name, idType: idType, targetType: "tag", }; return metricsOptions; }); } // region Hints Impressions export function impressionOptionsForSearchHint(objectGraph, hintTerm, baseOptions, searchEntity, hintSource) { return validation.context("impressionOptionsForSearchHint", () => { const metricsOptions = { ...baseOptions, id: "", kind: null, softwareType: null, title: hintTerm, hintsEntity: searchEntity, modelSource: hintSource, }; return metricsOptions; }); } export function addImpressionMetricsToHintsSearchAction(objectGraph, searchAction, metricsOptions) { const options = impressionOptionsForSearchHint(objectGraph, searchAction.term, metricsOptions, searchAction.entity, searchAction.source); const impressionFields = generateImpressionFields(objectGraph, options); searchAction.impressionMetrics = new models.ImpressionMetrics(impressionFields); } const uniqueIdDelimiter = "::"; function createUniqueImpressionId(objectGraph, baseId) { return `${baseId}${uniqueIdDelimiter}${objectGraph.random.nextUUID()}`; } /** * Strips the unique impression ID from the event fields if necessary. * * @param eventFields - The event fields containing impressions. */ export function stripUniqueImpressionIdsIfNecessary(eventFields) { const impressions = serverData.asArrayOrEmpty(eventFields, "impressions"); for (const impression of impressions) { const impressionId = serverData.asString(impression, "id"); if (isNothing(impressionId)) { continue; } impression["id"] = stripUniqueImpressionIdIfNecessary(impressionId); } } /** * Strips the unique impression ID from the given impression ID if necessary. * If the impression ID contains a unique ID delimiter, it splits the string * and returns the part before the delimiter. Otherwise, it returns the original * impression ID. * * @param impressionId - The impression ID to process. * @returns The processed impression ID. */ function stripUniqueImpressionIdIfNecessary(impressionId) { if (impressionId.includes(uniqueIdDelimiter)) { return impressionId.split(uniqueIdDelimiter)[0]; } return impressionId; } // endregion //# sourceMappingURL=impressions.js.map