import * as metricsLocation from "./location"; import * as metricsUtil from "./util"; import { isNothing, isSome } from "@jet/environment/types/optional"; import * as serverData from "../../../foundation/json-parsing/server-data"; import { attributeAsDictionary } from "../../../foundation/media/attributes"; import { shallowCopyOf } from "../../../foundation/util/objects"; import { productVariantDataForData, productVariantDataHasVariant } from "../../product-page/product-page-variants"; import { EventLinter } from "../event-linter"; import { eligibleSlotPositionsForAdPlacement } from "../../ads/ad-common"; import { FlattenedTodayItemType } from "../../today/today-parse-util"; import { getSelectedCustomCreativeId } from "../../search/custom-creative"; export const iAdURLParameterStringToken = "X-AppStore-iAdClickToken"; export const iAdURLLineItemParameterStringToken = "X-AppStore-iAdLineItem"; export const iAdDismissAdActionMetricsParameterStringToken = "X-AppStore-iAdDismissAdActionMetrics"; // User defined type guard for determining if an object conforms to ContentMetricsOptions interface. export function isContentMetricsOptions(object) { return object && Object.prototype.hasOwnProperty.call(object, "id"); } export class IAdSearchInformation { /** * Initialise a new `IAdSearchInformation` * @param objectGraph The Object Graph. * @param placementType The placement type for the ad this object is tracking. * @param baseSlotInformation The initial list of slotInfos for this ad placement * @param iAdId The unique id for the ad instance. * @param appStoreClientRequestId The unique id for the client requesting the ad. * @param wasOdmlSuccessful Whether native ODML processing was successful. * @param positionInfo The position info data describing the requested position of this ad. */ constructor(objectGraph, placementType, baseSlotInformation, iAdId, appStoreClientRequestId, wasOdmlSuccessful, positionInfo) { this.placementType = placementType; this.placementId = placementType === null ? null : this.placementIdFromType(placementType); this.pageFields = {}; this.clickFields = {}; this.impressionsFields = {}; this.fastImpressionFields = {}; this.iAdClickEventFields = {}; this._iAdApplied = false; this._iAdAdamId = undefined; this.positionInfo = positionInfo; this.slotInfo = baseSlotInformation; this.setInitialAdData(objectGraph, iAdId, appStoreClientRequestId); if (serverData.isDefinedNonNull(wasOdmlSuccessful)) { this.pageFields["iAdOdmlSuccess"] = wasOdmlSuccessful; } this.fastImpressionFields["iAdEligible"] = true; } /** * Construct an IAdSearchInformation from the given JSON representation. * This is necessary over and above standard JSON parsing to preserve our ability to call functions on this object. * @param objectGraph The Object Graph. * @param json The JSON representation of the object. * @returns A constructed IAdSearchInformation from the JSON. */ static from(objectGraph, json) { var _a, _b, _c, _d; const iAdInfo = new IAdSearchInformation(objectGraph, serverData.asString(json.placementType), serverData.asArrayOrEmpty(json.slotInfo), (_a = serverData.asString(json.iAdId)) !== null && _a !== void 0 ? _a : undefined, (_b = serverData.asString(json.appStoreClientRequestId)) !== null && _b !== void 0 ? _b : undefined, (_c = serverData.asBoolean(json.wasOdmlSuccessful)) !== null && _c !== void 0 ? _c : undefined, serverData.asInterface(json.positionInfo)); iAdInfo._iAdApplied = serverData.asBooleanOrFalse(json._iAdApplied); iAdInfo._iAdAdamId = (_d = serverData.asString(json._iAdAdamId)) !== null && _d !== void 0 ? _d : undefined; Object.assign(iAdInfo.pageFields, json.pageFields); Object.assign(iAdInfo.clickFields, json.clickFields); Object.assign(iAdInfo.impressionsFields, json.impressionsFields); Object.assign(iAdInfo.fastImpressionFields, json.fastImpressionFields); Object.assign(iAdInfo.iAdClickEventFields, json.iAdClickEventFields); iAdInfo.updateContainerId(serverData.asString(json.containerId)); return iAdInfo; } /** * Create an array of `IAdSlotInformation` objects based on the current ad information available. * This isn't ideal, but we need to understand the available ad slots so we can report * on all slots, whether they have ads in them or not. */ static createInitialSlotInfos(objectGraph, placementType, positionInfo, flattenedTodayFeed) { var _a; switch (placementType) { case "productPageYMAL": case "productPageYMALDuringDownload": const productPageContainerId = IAdSearchInformation.containerIdFromType(placementType); const slotIndex = (_a = positionInfo === null || positionInfo === void 0 ? void 0 : positionInfo.slot) !== null && _a !== void 0 ? _a : 0; const productPageSlot = { slotId: `${productPageContainerId}_${slotIndex}`, slotIndex: slotIndex, hasAdData: false, }; return [ { containerId: productPageContainerId, slots: [productPageSlot], }, ]; break; case "today": const placementEligibleSlotPositions = eligibleSlotPositionsForAdPlacement(objectGraph, placementType); const eligibleAdSlots = isNothing(placementEligibleSlotPositions) ? [] : placementEligibleSlotPositions.map((slotPosition) => slotPosition.slot); const containerSlotInfoMapping = {}; /// iAd provides slots that are not zero based, adjust the slot const adjustedAdSlot = isSome(positionInfo) ? positionInfo.slot - 1 : null; let adPositionEncountered = false; eligibleAdSlots.forEach((eligibleIndex) => { var _a; // If we've reached the ad we need to subtract 1 from the index, to act as if the ad ad was // actually part of the feed. const augmentedFeedIndexIndex = adPositionEncountered ? eligibleIndex - 1 : eligibleIndex; const todayItem = flattenedTodayFeed === null || flattenedTodayFeed === void 0 ? void 0 : flattenedTodayFeed.find((item) => item.containedAdSlots.includes(augmentedFeedIndexIndex)); const isAdSlotIndex = adjustedAdSlot === eligibleIndex; const todayContainerId = iAdContainerIdForSlotInTodayItem(augmentedFeedIndexIndex, isAdSlotIndex, todayItem); const containerSlotInformation = (_a = containerSlotInfoMapping[todayContainerId]) !== null && _a !== void 0 ? _a : { containerId: todayContainerId, slots: [], }; containerSlotInfoMapping[todayContainerId] = containerSlotInformation; const todaySlot = { slotId: `${todayContainerId}_${eligibleIndex}`, slotIndex: eligibleIndex, hasAdData: false, }; containerSlotInformation.slots.push(todaySlot); adPositionEncountered = adPositionEncountered || isAdSlotIndex; }); return Object.values(containerSlotInfoMapping); default: return null; } } get iAdIsPresent() { return this._iAdApplied; } get iAdAdamId() { return this._iAdAdamId; } /** * Update our information with a new ad response. * This is primarily used for asynchronous ad requests, where we need to update the metrics info subsequent to the initial page load. * @param objectGraph The Object Graph. * @param adResponse An ad response to use to update some fields. */ updateForAdResponse(objectGraph, adResponse) { var _a; if (serverData.isNull(adResponse)) { return; } this.placementType = adResponse.placementType; this.placementId = this.placementIdFromType(this.placementType); this.positionInfo = (_a = adResponse.onDeviceAd) === null || _a === void 0 ? void 0 : _a.positionInfo; this.setInitialAdData(objectGraph, adResponse.iAdId, adResponse.clientRequestId); } /** * Set the initial ad data, if available. * This function requires that an ad fetch has been made, and can be called from two paths: * 1. Via the constructor, where the ad fetch is made at page load time, or * 2. Via `updateForAdResponse`, where the ad fetch was made asynchronously after the page load. * @param objectGraph The Object Graph. * @param iAdId The unique id provided for the ad instance. * @param appStoreClientRequestId The unique id representing the App Store ad request client. */ setInitialAdData(objectGraph, iAdId, appStoreClientRequestId) { if (isNothing(appStoreClientRequestId)) { return; } // ToroID Suppression expects this to be replaced with -1 for figaro events const iAdIdValue = isNothing(iAdId) ? "-1" : iAdId; this.pageFields[EventLinter.hasIAdData] = true; switch (this.placementType) { case "today": case "productPageYMAL": case "productPageYMALDuringDownload": // Only set `hasIAdData` on impressions fields initially if it's a Chainlink placement. // This covers the "ad eligible" cases where we still want to check ad metrics when ads are not present. this.impressionsFields[EventLinter.hasIAdData] = true; break; default: break; } this.pageFields["iAdAppStoreClientRequestId"] = appStoreClientRequestId; switch (this.placementType) { case "today": case "productPageYMAL": case "productPageYMALDuringDownload": this.clickFields["iAdAppStoreClientRequestId"] = appStoreClientRequestId; this.impressionsFields["iAdAppStoreClientRequestId"] = appStoreClientRequestId; break; default: break; } this.pageFields["iAdId"] = iAdIdValue; this.impressionsFields["iAdId"] = iAdIdValue; this.clickFields["iAdId"] = iAdIdValue; this.updateContainerId(null); // Update slot info with data this.updateSlotInfo(); if (serverData.isDefinedNonNullNonEmpty(this.slotInfo)) { this.pageFields["iAdSlotInfo"] = this.slotInfo; this.clickFields["iAdSlotInfo"] = this.slotInfo; this.impressionsFields["iAdSlotInfo"] = this.slotInfo; } if (this.placementId !== null && this.placementId.length > 0) { this.pageFields["iAdPlacementId"] = this.placementId; this.clickFields["iAdPlacementId"] = this.placementId; // Attach the `iAdPlacementId` to top-level impressions for v4 impressions. this.impressionsFields["iAdPlacementId"] = this.placementId; // For Chainlink placements, the `iAdPlacementId` sits within the impressions array. // We manually remove `iAdPlacementId` from the top-level fields for v5 impressions in `createMetricsFastImpressionsData`. switch (this.placementType) { case "today": case "productPageYMAL": case "productPageYMALDuringDownload": this.fastImpressionFields["iAdPlacementId"] = this.placementId; break; default: break; } } } /** * Update the containerId based on the current placementType. */ updateContainerId(containerId) { if (this.placementType === "today") { this.containerId = containerId !== null && containerId !== void 0 ? containerId : null; if (serverData.isDefinedNonNull(this.containerId)) { this.clickFields["iAdContainerId"] = this.containerId; this.fastImpressionFields["iAdContainerId"] = this.containerId; } } else { this.containerId = this.placementType === null ? null : IAdSearchInformation.containerIdFromType(this.placementType); if (serverData.isDefinedNonNull(this.containerId)) { this.pageFields["iAdContainerId"] = this.containerId; this.clickFields["iAdContainerId"] = this.containerId; this.fastImpressionFields["iAdContainerId"] = this.containerId; } } } /** * @param slotIndex The slot index to look for a container Id for * @returns The container Id for the given slot index, based off the slot infos */ containerIdForSlotIndex(slotIndex) { if (isNothing(slotIndex) || isNothing(this.slotInfo)) { return null; } for (const slotInfo of this.slotInfo) { for (const slot of slotInfo.slots) { if (slot.slotIndex === slotIndex) { return slotInfo.containerId; } } } return this.containerId; } apply(objectGraph, adData) { if (isNothing(adData) || serverData.isNullOrEmpty(adData)) { return; } const iAdAdamId = adData.id; const iAdConfigurationDictionary = attributeAsDictionary(adData, "iad"); this._iAdAdamId = iAdAdamId; if (iAdConfigurationDictionary) { this.impressionsFields[EventLinter.hasIAdData] = true; this.clickFields[EventLinter.hasIAdData] = true; const impressionId = metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["impressionId"]); this.fastImpressionFields["iAdImpressionId"] = impressionId; this.clickFields["iAdImpressionId"] = impressionId; const metadata = metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["metadata"]); this.clickFields["iAdMetadata"] = metadata; this.fastImpressionFields["iAdMetadata"] = metadata; // Ads boldly populate `adamId` on pages. This is correct. this.pageFields["adamId"] = iAdAdamId; this.pageFields["iAd"] = { iAdFormat: metricsUtil.sanitizedMetricsDictionary(serverData.asInterface(serverData.asJSONValue(iAdConfigurationDictionary), "format")), iAdAlgoId: metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["algoId"]), iAdImpressionId: metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["impressionId"]), iAdMetadata: metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["metadata"]), }; const productVariantData = productVariantDataForData(objectGraph, adData); this.updateIAdMetricsFieldsForProductVariantData(productVariantData, this.clickFields); if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) { if (objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) { const selectedCustomCreativeId = getSelectedCustomCreativeId(adData); if (this.placementType === "today") { this.updateIAdMetricsFieldsForAlignedRegion(selectedCustomCreativeId, this.fastImpressionFields); } else { this.updateIAdMetricsFieldsForAlignedRegion(selectedCustomCreativeId, this.impressionsFields); } this.updateIAdMetricsFieldsForAlignedRegion(selectedCustomCreativeId, this.clickFields); } } Object.assign(this.iAdClickEventFields, iAdConfigurationDictionary); this._iAdApplied = true; // Clear out any prior missed opportunity reason if we have an ad. this.setMissedOpportunity(objectGraph, undefined, this.placementType); } // Update slot info after ad is applied. this.updateSlotInfo(); if (serverData.isDefinedNonNullNonEmpty(this.slotInfo)) { this.pageFields["iAdSlotInfo"] = this.slotInfo; this.clickFields["iAdSlotInfo"] = this.slotInfo; this.impressionsFields["iAdSlotInfo"] = this.slotInfo; } } /** * Apply the click fields that were attached to the page request we're following. * This will generally be following on from click on a lockup with ad data attached, where we want to pull * some of the click fields from ad to be applied to this new page data. * * The goal here is to attach enough metadata into the incoming page that we can attribute any offer action * to the ad that was tapped on to reach this page. Whilst doing this we don't want to attach too much * ad data to the page, as this can start to interfere with metrics for other ad placements. This results in * us manually inserting and removing fields to achieve the right balance. * @param iAdAdamId The adamId for the ad. * @param fields The set of fields to apply to our clickFields. */ applyClickFieldsFromPageRequest(iAdAdamId, fields) { this._iAdApplied = true; this._iAdAdamId = iAdAdamId; Object.assign(this.clickFields, fields); // We don't want to assign any of the fields to the page event, as this looks like an ad is being shown // on the page we're navigating to. Object.keys(this.pageFields).forEach((field) => delete this.pageFields[field]); } setSpecifiedAlignedRegionUsed(didUseSpecifiedCreative) { this.fastImpressionFields["iAdIsSpecifiedCreativeUsed"] = didUseSpecifiedCreative; this.clickFields["iAdIsSpecifiedCreativeUsed"] = didUseSpecifiedCreative; } /** * Set the template type for the page. */ setTemplateType(templateType) { this.pageFields["iAdTemplateType"] = templateType; this.impressionsFields["iAdTemplateType"] = templateType; this.clickFields["iAdTemplateType"] = templateType; } setMissedOpportunity(objectGraph, reason, placementType) { this.missedOpportunityReason = reason; // Only set in page and impressions fields if reason is non-null. if (serverData.isDefinedNonNull(reason)) { this.clickFields["iAdMissedOpportunityReason"] = reason; // Chainlink placements don't expect "iAdMissedOpportunityReason" at the top level of impressions or page events. switch (this.placementType) { case "today": case "productPageYMAL": case "productPageYMALDuringDownload": break; default: this.pageFields["iAdMissedOpportunityReason"] = reason; this.impressionsFields["iAdMissedOpportunityReason"] = reason; break; } } else { delete this.clickFields["iAdMissedOpportunityReason"]; // Generally, we just want to remove the missed opportunity reason if one isn't set. // The 'productPageYMALDuringDownload' placement is a special case where it's placed on the page // subsequent to a previous placement, and metrics is updated via the pageChange metrics. // This means that we need to send `null` as the value for this placement, in order to clear // any possible missed opportunity value from the placement it's replacing. switch (placementType) { case "productPageYMALDuringDownload": this.pageFields["iAdMissedOpportunityReason"] = null; this.impressionsFields["iAdMissedOpportunityReason"] = null; break; default: delete this.pageFields["iAdMissedOpportunityReason"]; delete this.impressionsFields["iAdMissedOpportunityReason"]; break; } } // Only set in page and impressions fields if reason is non-null. if (serverData.isDefinedNonNull(reason)) { this.pageFields["iAdMissedOpportunityReason"] = reason; this.impressionsFields["iAdMissedOpportunityReason"] = reason; } else { delete this.pageFields["iAdMissedOpportunityReason"]; delete this.impressionsFields["iAdMissedOpportunityReason"]; } // Update slotInfo with missed opportunity reason. this.updateSlotInfo(); if (serverData.isDefinedNonNullNonEmpty(this.slotInfo)) { this.pageFields["iAdSlotInfo"] = this.slotInfo; this.clickFields["iAdSlotInfo"] = this.slotInfo; this.impressionsFields["iAdSlotInfo"] = this.slotInfo; } } placementIdFromType(type) { switch (type) { case "searchLanding": return "APPSTORE_SEARCH_LANDING_PAGE"; case "searchResults": return "APPSTORE_SEARCH_RESULT_PAGE"; case "today": return "APPSTORE_TODAY_TAB"; case "productPageYMAL": return "APPSTORE_PRODUCT_PAGE"; case "productPageYMALDuringDownload": return "APPSTORE_PRODUCT_PAGE_DOWNLOAD"; default: throw new Error(`This method should never be called with value: ${type}`); } } static placementTypeFromPlacementId(objectGraph, id) { switch (id) { case "APPSTORE_SEARCH_LANDING_PAGE": return "searchLanding"; case "APPSTORE_SEARCH_RESULT_PAGE": return "searchResults"; case "APPSTORE_TODAY_TAB": return "today"; case "APPSTORE_PRODUCT_PAGE": return "productPageYMAL"; case "APPSTORE_PRODUCT_PAGE_DOWNLOAD": return "productPageYMALDuringDownload"; default: objectGraph.console.log(`Failed to get placementType from placementId ${id}. Defaulting to searchResults`); // For legacy reasons we fall back to `seachResults` when nothing is provided. // Being the first ad slot, in the past this has been assumed as the "default". return "searchResults"; } } /** * Get a containerId value for a given placement type to attach to the ad's metrics. * @param type The AdPlacementType for the current ad. * @returns A string value for containerId, if the placement has one. Otherwise null. */ static containerIdFromType(type) { switch (type) { case "productPageYMAL": return "customers-also-bought-apps"; case "productPageYMALDuringDownload": return "customers-also-bought-apps-download"; case "today": return null; // Today page has variable containerIds that are updated programatically default: return null; } } /** * Return the fast impressions metrics fields for an item using the location tracker. * * `fastImpressionFields` are the items inside the impressions array of metrics events. Although some fields are stable, * these are unique to a given position, so we must use that position information to generate the right fields. * * @param locationTracker A LocationTracker used to identify the relevant information to attach to the metrics fields. * @param adSlotOverride The ad slot to use for the metrics fields. If not provided, the ad slot will be inferred from * the location tracker. This is needed for today where the ad can span different shelves so the position is going to be 0 for multiple ads * @returns Relevant metrics fields for the item in an impressions array. */ fastImpressionsFieldsForCurrentItem(locationTracker, adSlotOverride) { switch (this.placementType) { case "productPageYMAL": case "productPageYMALDuringDownload": case "today": let position; if (isSome(adSlotOverride)) { position = adSlotOverride; } else { const location = metricsLocation.currentLocation(locationTracker); // If the current location is within a todayCard, we want to extract the location of the card, not the location // of this lockup within the card. if (location !== null && location.locationType === "todayCard") { position = metricsLocation.previousPosition(locationTracker); } else { position = metricsLocation.currentPosition(locationTracker); } } const sharedFields = shallowCopyOf(this.fastImpressionFields); sharedFields["iAdSlotId"] = `${this.containerIdForSlotIndex(position)}_${position}`; if (position !== this.adjustedSlotIndex) { // If the current position is not where the ad is, we only want a subset of the tracked fields. const allowedFields = ["iAdEligible", "iAdPlacementId", "iAdContainerId", "iAdSlotId"]; Object.keys(sharedFields).forEach((key) => { if (!allowedFields.includes(key)) { delete sharedFields[key]; } }); } return sharedFields; default: return this.fastImpressionFields; } } /** * Get the adjusted slot index of the ad we're tracking. * This is zero-based - Ad Platforms returns slot info to us one-based but we always adjust it before using it anywhere. */ get adjustedSlotIndex() { var _a; const positionInfoSlotIndex = (_a = this.positionInfo) === null || _a === void 0 ? void 0 : _a.slot; // The slot as provided by ad platforms is one-based - adjust it so we're working with zero-based numbers. if (serverData.isDefinedNonNull(positionInfoSlotIndex)) { return positionInfoSlotIndex - 1; } return null; } /** * Update the existing slot information now that new data has been applied. */ updateSlotInfo() { if (isNothing(this.slotInfo)) { return; } switch (this.placementType) { case "productPageYMAL": case "productPageYMALDuringDownload": for (const containerSlotInfo of this.slotInfo) { for (const slot of containerSlotInfo.slots) { slot.hasAdData = this.iAdIsPresent; if (serverData.isDefinedNonNull(this.missedOpportunityReason)) { slot.missedOpportunityReason = this.missedOpportunityReason; } } } break; case "today": const adjustedSlotIndex = this.adjustedSlotIndex; for (const containerSlotInfo of this.slotInfo) { for (const slot of containerSlotInfo.slots) { // Check whether the current index matches our slot information. // If so, an ad is intended to be placed here. const isAdInCurrentIndex = adjustedSlotIndex === slot.slotIndex; // In order to understand whether a given slot has ad data, we ensure that a usable ad is present, // and the current slot is the one an ad is placed in. const hasAdData = this.iAdIsPresent && isAdInCurrentIndex; // By default, use the missed opportunity reason, if any, stored here. let missedOpportunityReason = this.missedOpportunityReason; // If: // - the ad is not in the current index we're building slot info for, and // - there is a valid slot index // we override the reason to 'NOAD', as it means another eligible slot was filled. if (!isAdInCurrentIndex && serverData.isDefinedNonNull(adjustedSlotIndex)) { missedOpportunityReason = "NOAD"; } slot.hasAdData = hasAdData; if (serverData.isDefinedNonNull(missedOpportunityReason)) { slot.missedOpportunityReason = missedOpportunityReason; } } } break; default: break; } } /** * Modifies the provided MetricsFields for the ProductVariantData specific to fields required for iAds. * @param productVariantData The variant data of impressionable data to apply. * @param metricsFields An existing set of MetricsFields to modify. */ updateIAdMetricsFieldsForProductVariantData(productVariantData, metricsFields) { if (isSome(productVariantData) && productVariantDataHasVariant(productVariantData, "customProductPage")) { metricsFields["iAdPageCustomId"] = productVariantData.productPageId; } else { // If a custom product page doesn't exist, we remove the fields. delete metricsFields["iAdPageCustomId"]; } } /** * Modifies the provided MetricsFields to include the custom id for iAds. * @param selectedCustomCreativeId The id for the custom creative that we want to set as custom id. * @param metricsFields An existing set of MetricsFields to modify. */ updateIAdMetricsFieldsForAlignedRegion(selectedCustomCreativeId, metricsFields) { if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) { if (isSome(selectedCustomCreativeId)) { metricsFields["iAdCustomId"] = selectedCustomCreativeId; } else { delete metricsFields["iAdCustomId"]; } } } /** * Return the event version for fast impressions based on the placement of the ad. * Impressions v5 is being rolled out first for Chainlink. Once all impressions have adopted it, this can be removed. */ get fastImpressionsEventVersion() { switch (this.placementType) { case "productPageYMAL": case "productPageYMALDuringDownload": case "today": return 5; default: return 4; } } /** * Return whether ad rotation fields should be included on metrics events, based on the current placement type. */ get shouldIncludeAdRotationFields() { switch (this.placementType) { case "productPageYMAL": case "productPageYMALDuringDownload": case "today": return false; case "searchLanding": case "searchResults": return true; default: return true; } } } /** * Return the container id to use for a today feed item * @param slotIndex The slot index that we found a TodayItem for * @param isAdSlot Whether this slotIndex is the slot the ad is in * @param todayItem The today item that this card is contained in, this could be a single item or a story group item * @returns The configuration to use when parsing a today card */ export function iAdContainerIdForSlotInTodayItem(slotIndex, isAdSlot, todayItem) { if (isNothing(todayItem)) { return "0"; } switch (todayItem.type) { case FlattenedTodayItemType.EditorialItemGroup: const storyGroupHasMultipleItems = todayItem.containedAdSlots.length > 1; const slotIndexIsFirstOrLastInStoryGroup = !storyGroupHasMultipleItems || slotIndex === todayItem.containedAdSlots[0] || slotIndex === todayItem.containedAdSlots[todayItem.containedAdSlots.length - 1]; if (isAdSlot && slotIndexIsFirstOrLastInStoryGroup) { // If the story group has fewer than 2 items, that means theres not valid slot **in** the actual story group // to place the ad, so its going to be at the top level. Or if there are multiple items, but the slot is the // first or last item there is no reason to pull the ad into the story group, it should be top level. return "0"; } else { // Using the `data` from the todayItem here and not the todayCardData, because // this will be the data for the containing story gorup return "0"; } default: return "0"; } } /** * Create the iAdInformation for a given page if necessary * @param objectGraph The dependency graph for the app store * @param pageId The id of the page this iAd info is being used for * @param response The MAPI response for this page * @param positionInfo The position information on where the ad will attempted to be inserted * @param flattenedTodayFeed The flattened to today feed if this is for a today page * @returns The iAdInformation to be used in a metrics page information object */ export function iAdInformationFromMediaApiResponse(objectGraph, pageId, response, positionInfo = null, flattenedTodayFeed = null) { var _a; /** Toro: iAd is missing AD_OPEN figaro event when tapping on ad and pressing OPEN through the product page */ const iAdClickInfoString = serverData.asString(response, iAdURLParameterStringToken); if (isNothing(iAdClickInfoString)) { return null; } const iAdClickInfo = JSON.parse(iAdClickInfoString); // rdar://72607206 (Gibraltar: Tech Debt: Population of iAd fields in Product Page) const placementType = IAdSearchInformation.placementTypeFromPlacementId(objectGraph, serverData.asString(iAdClickInfo, "iAdPlacementId")); const iAdInfo = new IAdSearchInformation(objectGraph, placementType, IAdSearchInformation.createInitialSlotInfos(objectGraph, placementType, positionInfo, flattenedTodayFeed), (_a = serverData.asString(iAdClickInfo, "iAdId")) !== null && _a !== void 0 ? _a : undefined, undefined, undefined, positionInfo); iAdInfo.applyClickFieldsFromPageRequest(pageId !== null && pageId !== void 0 ? pageId : undefined, iAdClickInfo); return iAdInfo; } /** @public */ export class MetricsPageInformation { constructor(base = {}) { this.baseFields = base; } } //# sourceMappingURL=models.js.map