// // search-ads.ts // AppStoreKit // // Created by Joel Parsons on 24/October/2019 // Copyright (c) 2016 Apple Inc. All rights reserved. // import { isSome } from "@jet/environment"; import * as models from "../../api/models"; import { ads } from "../../api/typings/constants"; import * as serverData from "../../foundation/json-parsing/server-data"; import * as mediaAttributes from "../../foundation/media/attributes"; import { shallowCopyOf } from "../../foundation/util/objects"; import { isAdLocalizationValid } from "../ads/ad-common"; import * as client from "../../foundation/wrappers/client"; import * as contentAttributes from "../content/attributes"; import * as content from "../content/content"; import * as filtering from "../filtering"; import * as lockups from "../lockups/lockups"; import * as metricsHelpersLocation from "../metrics/helpers/location"; import { customCreativeArtworkFromData, customCreativeVideoFromData } from "./custom-creative"; import { platformAttributeAsDictionary } from "../../foundation/media/platform-attributes"; import { searchResultWillUseAppEventDisplay } from "./content/search-results"; /** * Data passed from native for requesting Sponsored Search */ export class SponsoredSearchRequestData { constructor(data, appStoreClientRequestId) { if (!data) { return; } this.appStoreClientRequestId = appStoreClientRequestId; this.iAdId = data["iAdId"]; this.sponsoredSearchRequestData = data["dataBlob"]; this.routingInfo = data["iAdRoutingInfo"]; this.canary = data["canary"]; } validAdRequest() { const hasRequestData = this.sponsoredSearchRequestData && this.sponsoredSearchRequestData.length > 0; const hasRoutingInfo = this.routingInfo && this.routingInfo.length > 0; return hasRequestData && hasRoutingInfo; } } const searchVideoConfiguration = { canPlayFullScreen: false, playbackControls: {}, }; export function adsResultFromSearchResults(objectGraph, advertDatum, resultsDatum, requestMetadata, metricsOptions, installedStates, appStates, searchExperimentsData, personalizationDataContainer) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; const advertsSearchResult = new models.AdvertsSearchResult(); const isNetworkConstrained = (_a = requestMetadata.requestDescriptor.isNetworkConstrained) !== null && _a !== void 0 ? _a : false; const advertMetricsOptions = { id: "ad_container", kind: "iosSoftware", softwareType: null, targetType: null, title: "ad_container", pageInformation: metricsOptions.pageInformation, locationTracker: metricsOptions.locationTracker, idType: "sequential", }; /** * # Why do we push ad_container? * We push an identifier for element that doesn't exist (`ad_container`) to prevent impression indices of organic search results * being affected by having multiple ads built for ad-rotation. * * It is expected that: * - `ad_container` will not impresss, since it doesn't exist * - `parentImpressionId` will not be set expected. */ metricsHelpersLocation.pushContentLocation(objectGraph, advertMetricsOptions, "ad_container"); if (serverData.isNullOrEmpty(advertDatum)) { return { result: advertsSearchResult, }; } const firstSearchResult = resultsDatum[0]; let firstAdComputedStyle; const adIdsString = advertDatum .filter(serverData.isDefinedNonNull) .map((ad) => `[${ad.id}]`) .join(", "); const adString = `Adverts received from ad server: ${adIdsString}`; adLogger(objectGraph, adString); let isFirstAd = true; for (const ad of advertDatum) { if (serverData.isNull(ad)) { continue; } if (filtering.shouldFilter(objectGraph, ad)) { adLogger(objectGraph, `[${ad.id}] filtered by shouldFilter() - app probably not supported on current os or device`); continue; } const isDupe = adIsDupe(ad.id, firstSearchResult === null || firstSearchResult === void 0 ? void 0 : firstSearchResult.id, installedStates); // Extract the iAd data dictionary based on the first organic search result. const adDataType = iAdDataTypeForAdvert(firstSearchResult, isDupe); ad.attributes["iad"] = iadAttributesForType(ad, adDataType); if (serverData.isNullOrEmpty(ad.attributes["iad"])) { adLogger(objectGraph, `[${ad.id}] filtered because no appropriate iAd dictionary was found. (Probably a server issue if hitting this)`); continue; } const adLockupOptions = { metricsOptions: { pageInformation: metricsOptions.pageInformation, locationTracker: metricsOptions.locationTracker, targetType: "card", isAdvert: true, }, hideZeroRatings: true, artworkUseCase: 8 /* content.ArtworkUseCase.SearchIcon */, isNetworkConstrained: isNetworkConstrained, canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "mixedMediaLockup"), }; const iAdData = contentAttributes.contentAttributeAsDictionary(objectGraph, ad, "iad"); const iAdAllowsMedia = serverData.asBooleanOrFalse(iAdData, "format.images"); let creativeHasArtworkToDisplay = false; if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) { const customCreativeData = platformAttributeAsDictionary(ad, contentAttributes.bestAttributePlatformFromData(objectGraph, ad), "creativeAttributes"); const customCreativeArtwork = customCreativeArtworkFromData(objectGraph, ad, customCreativeData); const customCreativeVideo = customCreativeVideoFromData(objectGraph, ad, customCreativeData, searchVideoConfiguration); creativeHasArtworkToDisplay = isSome(customCreativeArtwork) || isSome(customCreativeVideo); } const noPreviousAdStyle = serverData.isNullOrEmpty(firstAdComputedStyle); const temporaryAdLockup = lockups.mixedMediaAdLockupFromData(objectGraph, ad, adLockupOptions, searchVideoConfiguration, searchExperimentsData); const platformLockupMedia = platformMediaForLockup(temporaryAdLockup); // Adverts should only rotate between the same display style. The first advert dictates the display style // for the rest of the adverts. Once we have an ad display style locked in we try to create the next ads in // a style compatible with the first. If it can't satisfy any compatible styles, it gets thrown away. const screenshotsDisplayStyle = creativeHasArtworkToDisplay ? "four-screenshots" : (_b = searchExperimentsData === null || searchExperimentsData === void 0 ? void 0 : searchExperimentsData.displayStyle) === null || _b === void 0 ? void 0 : _b.screenshots; const computedAdDisplayStyle = resolvedAdDisplayStyleForMedia(objectGraph, platformLockupMedia, ad.id, adDataType, iAdAllowsMedia, firstAdComputedStyle, screenshotsDisplayStyle, firstSearchResult, installedStates, appStates, metricsOptions, personalizationDataContainer); if (serverData.isNull(computedAdDisplayStyle)) { adLogger(objectGraph, `[${ad.id}] will not be displayed because we could not create an ad style compatible with ${debugDescriptionForStyle(firstAdComputedStyle)}`); continue; } // Check the localization is valid for the ad with the selected display style. // We do this _before_ storing the style (if this is the first ad) to avoid setting // a style based on an ad that can't be shown. if (!isAdLocalizationValid(objectGraph, ad, null, computedAdDisplayStyle.style)) { adLogger(objectGraph, `[${ad.id}] filtered because localization is not available`); continue; } if (noPreviousAdStyle) { // For the first advert we calculate a display style based on whether it is a dupe of the first organic result // and what media is available for display for the app adLogger(objectGraph, `[${ad.id}] first ad dictates ad display style of: ${debugDescriptionForStyle(computedAdDisplayStyle)}`); firstAdComputedStyle = computedAdDisplayStyle; } else { adLogger(objectGraph, `[${ad.id}] will be displayed because it is compatible with the display style of: ${debugDescriptionForStyle(computedAdDisplayStyle)}, which is the same height as display style: ${debugDescriptionForStyle(firstAdComputedStyle)}`); } metricsOptions.pageInformation.iAdInfo.apply(objectGraph, ad); // Set the template type before creating the lockup to ensure the click event has the right data. (_c = metricsOptions.pageInformation.iAdInfo) === null || _c === void 0 ? void 0 : _c.setTemplateType(computedAdDisplayStyle.style); let adResultLockup = lockups.mixedMediaAdLockupFromData(objectGraph, ad, adLockupOptions, searchVideoConfiguration, searchExperimentsData); adResultLockup = modifyLockupToMatchAdDisplayStyle(adResultLockup, computedAdDisplayStyle, isDupe, isFirstAd); if (objectGraph.props.enabled("advertSlotReporting")) { (_d = adResultLockup.searchAdOpportunity) === null || _d === void 0 ? void 0 : _d.setTemplateType(computedAdDisplayStyle.style); } else { (_e = adResultLockup.searchAd) === null || _e === void 0 ? void 0 : _e.setTemplateType(computedAdDisplayStyle.style); } if (computedAdDisplayStyle.style === "TEXT" /* SearchAdDisplayStyle.TEXT */) { const iAdTextKey = mediaAttributes.attributeAsString(ad, "iad.format.text"); if (iAdTextKey !== "none") { let advertisingText; if (iAdTextKey === "description") { advertisingText = contentAttributes.contentAttributeAsString(objectGraph, ad, "description.standard"); } else { advertisingText = contentAttributes.contentAttributeAsString(objectGraph, ad, iAdTextKey); } const searchAd = (_f = adResultLockup.searchAd) !== null && _f !== void 0 ? _f : (_g = adResultLockup.searchAdOpportunity) === null || _g === void 0 ? void 0 : _g.searchAd; if (serverData.isDefinedNonNull(searchAd) && serverData.isDefinedNonNull(advertisingText)) { searchAd.advertisingText = advertisingText; } } advertsSearchResult.displaysScreenshots = false; } if (serverData.isDefinedNonNullNonEmpty(adResultLockup)) { const duplicatePosition = findOrganicLockupPosition(resultsDatum, adResultLockup.adamId); if (isSome(duplicatePosition) && !creativeHasArtworkToDisplay) { if (objectGraph.props.enabled("advertSlotReporting")) { (_h = adResultLockup.searchAdOpportunity) === null || _h === void 0 ? void 0 : _h.setDuplicatePosition(duplicatePosition); } else { (_j = adResultLockup.searchAd) === null || _j === void 0 ? void 0 : _j.setDuplicatePosition(duplicatePosition); } } advertsSearchResult.lockups.push(adResultLockup); metricsHelpersLocation.nextPosition(metricsOptions.locationTracker); isFirstAd = false; } } // Always pop the `ad_container` location. metricsHelpersLocation.popLocation(metricsOptions.locationTracker); // There are situations where all ads are filtered out - check the results have at least one item prior to modifying metrics data. if (serverData.isDefinedNonNullNonEmpty(advertsSearchResult.lockups)) { // Once all the lockups are built re-set the page information to use the iAd Info of the first advert // This (probably) happens to ensure that the page event accurately reflects what's being presented // to the user at first view. const firstAd = advertDatum[0]; metricsOptions.pageInformation.iAdInfo.apply(objectGraph, firstAd); metricsHelpersLocation.nextPosition(metricsOptions.locationTracker); } if (firstAdComputedStyle) { (_k = metricsOptions.pageInformation.iAdInfo) === null || _k === void 0 ? void 0 : _k.setTemplateType(firstAdComputedStyle.style); } else { (_l = metricsOptions.pageInformation.iAdInfo) === null || _l === void 0 ? void 0 : _l.setTemplateType(null); } advertsSearchResult.condensedBehavior = "never"; return { result: advertsSearchResult, displayStyle: firstAdComputedStyle === null || firstAdComputedStyle === void 0 ? void 0 : firstAdComputedStyle.style, }; } /** * Determines whether or not the ad is considered a dupe of the first organic result. * @param adID The adaimID of the ad result * @param firstResultID The adamID of the first organic result * @param installedState A mapping of adamIDs to device app install state to determine if the ad/first result is on the user's device * @returns whether the ad and result are dupes of the same app and that the first result will not condensed as a condensed first result * will have no bearing on dupe status or display. */ function adIsDupe(adID, firstResultID, installedState) { const isResultInstalled = installedState && installedState[firstResultID]; const isDupeResult = adID && firstResultID && adID === firstResultID; return isDupeResult && !isResultInstalled; } // endregion // region Ad Display Styles /** * Resolves a SearchAdDisplayStyle for the given ad media and the search results context (the first organic search result). * If the ad is a dupe of the first organic search result, we attempt to create two lockups with "full creative" to ensure there is enough * media to show both. * @param media the media from which to calculate the style. * @param adId the id of the ad, for logging purposes. * @param adDataType the ad type, based on the first search result. * @param firstAdComputedStyle a previously computed `SearchAdDisplayStyleContainer`, if any, to maintain compatibility. * @param firstSearchResult The first organic search result which we informs how to handle DUP logic * @param installStates A mapping of adamIDs to their respective install states that is used to determine if the app is currently installed by the user * @param appStates A mapping of adamIDs to their respective app state to determine if the ad/first result has been installed by the user in the past * @param metricsOptions Metrics options for built models. * @param personalizationDataContainer The data container to use for personalizing the data. * @returns a `SearchAdDisplayStyleContainer` with a style compatible with the first search result, if one can be found. */ function resolvedAdDisplayStyleForMedia(objectGraph, media, adId, adDataType, iAdAllowsMedia, firstAdComputedStyle, screenshotsDisplayStyle, firstSearchResult, installStates, appStates, metricsOptions, personalizationDataContainer) { const isFirstAd = serverData.isNullOrEmpty(firstAdComputedStyle); const adDisplayStyle = getAdDisplayStyleForMedia(objectGraph, media, adId, iAdAllowsMedia, firstAdComputedStyle, screenshotsDisplayStyle); if (serverData.isNull(adDisplayStyle)) { return null; } const searchAdDisplayContainer = { platform: media.mediaPlatformUsedForDisplayStyle, style: adDisplayStyle, }; adLogger(objectGraph, `[${adId}] tentatively resolved to: ${debugDescriptionForStyle(searchAdDisplayContainer)}`); if (adDataType === "DUP" /* iAdDataType.DUPE_AD */) { removeUsedMediaForAdDisplayStyle(adDisplayStyle, media); const organicSearchResultDisplayStyle = getAdDisplayStyleForMedia(objectGraph, media, adId, iAdAllowsMedia, null, screenshotsDisplayStyle); const organicHasFullCreative = isDisplayStyleFullCreativeForOrganic(objectGraph, organicSearchResultDisplayStyle, screenshotsDisplayStyle); const organicWillUseAppEvent = searchResultWillUseAppEventDisplay(objectGraph, firstSearchResult, installStates, appStates, metricsOptions, personalizationDataContainer); if ((organicHasFullCreative || organicWillUseAppEvent) && isFirstAd) { adLogger(objectGraph, `[${adId}] Organic Dupe would be full creative as ${organicSearchResultDisplayStyle} so choosing tentative style for ad`); return searchAdDisplayContainer; } else if (organicHasFullCreative && !isFirstAd && isProposedAdStyleCompatible(adDisplayStyle, firstAdComputedStyle)) { adLogger(objectGraph, `[${adId}] Organic Dupe would be a full creative, but ad is not the first so returning compatible style with first ${adDisplayStyle}`); return searchAdDisplayContainer; } else if (isProposedAdStyleCompatible("UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */, firstAdComputedStyle)) { adLogger(objectGraph, `[${adId}] tentative style would not yield full creative for organic result so returning UNRESTRICTED_IMAGE_ONE_ASSET`); return { style: "UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */, }; } else if (isProposedAdStyleCompatible("UV1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_VIDEO_ONE_ASSET */, firstAdComputedStyle)) { adLogger(objectGraph, `[${adId}] tentative style would not yield full creative for organic result so returning UNRESTRICTED_VIDEO_ONE_ASSET`); return { style: "UV1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_VIDEO_ONE_ASSET */, }; } else if (isProposedAdStyleCompatible("TEXT" /* SearchAdDisplayStyle.TEXT */, firstAdComputedStyle)) { adLogger(objectGraph, `[${adId}] tentative style would not yield full creative for organic result so returning TEXT`); return { style: "TEXT" /* SearchAdDisplayStyle.TEXT */, }; } else { adLogger(objectGraph, `[${adId}] tentative style would not yield full creative for organic result and first style is not compatible with TEXT so skipping ad`); return null; } } else if (serverData.isDefinedNonNull(firstAdComputedStyle) && firstAdComputedStyle.style === "TEXT" /* SearchAdDisplayStyle.TEXT */) { adLogger(objectGraph, `[${adId}] tentative style would be filtered since the first ad has style: ${debugDescriptionForStyle(firstAdComputedStyle)}, so returning TEXT`); return { style: "TEXT" /* SearchAdDisplayStyle.TEXT */, }; } return searchAdDisplayContainer; } function allowsFourScreenshots(screenshotsDisplayStyle) { if (!serverData.isDefinedNonNull(screenshotsDisplayStyle)) { return false; } return screenshotsDisplayStyle === "four-screenshots"; } /** * Finds the position of a specific lockup within the organic search results * @param searchResults List of search results * @param adResult The adamId of the app to search for * @returns The 0-indexed position in the list that contains the lockup. If the organic results doesn't contain the lockup, this instead returns null. */ function findOrganicLockupPosition(searchResults, adamId) { const index = searchResults.findIndex((datum) => datum.id === adamId); return index === -1 ? null : index; } /** * Returns a suitable style for a given set of lockup media. * If a firstComputedAdStyle is provided, we check to confirm the proposed style is compatible before selecting that new style. * @param media a set of media from which to calculate a style. * @param adId the id of the ad, for logging purposes. * @param iAdAllowsMedia whether the iAd Data has enabled media for the given ad. * @param firstComputedAdStyle whether the iAd Data has enabled media for the given ad. * @returns the preferred display style from the provided set of media. */ function getAdDisplayStyleForMedia(objectGraph, media, adId, iAdAllowsMedia, firstComputedAdStyle, screenshotsDisplayStyle) { if (!iAdAllowsMedia) { adLogger(objectGraph, `[${adId}] is not allowed to display media because of iAd configuration.`); return "TEXT" /* SearchAdDisplayStyle.TEXT */; } if (media.mediaPlatformUsedForDisplayStyle && firstComputedAdStyle && firstComputedAdStyle.mediaPlatform && !media.mediaPlatformUsedForDisplayStyle.isEqualTo(firstComputedAdStyle.mediaPlatform)) { adLogger(objectGraph, `[${adId}] filtered because media is derived from: ${media.mediaPlatformUsedForDisplayStyle.mediaType}, but first ad media is derived from: ${firstComputedAdStyle.mediaPlatform.mediaType}`); return null; } let displayStyle; let firstVideoPreview = null; if (serverData.isDefinedNonNullNonEmpty(media.videos)) { const firstVideo = media.videos[0]; firstVideoPreview = firstVideo.preview; } if (isSome(media.alignedRegionArtwork) && isProposedAdStyleCompatible("UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */, firstComputedAdStyle)) { displayStyle = "UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */; } else if (isSome(media.alignedRegionVideo) && isProposedAdStyleCompatible("UV1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_VIDEO_ONE_ASSET */, firstComputedAdStyle)) { displayStyle = "UV1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_VIDEO_ONE_ASSET */; } else if (serverData.isDefinedNonNullNonEmpty(firstVideoPreview) && firstVideoPreview.isLandscape() && isProposedAdStyleCompatible("LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */, firstComputedAdStyle)) { // If first trailer's preview is landscape the lockup will render as landscape video displayStyle = "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */; } else if (serverData.isDefinedNonNullNonEmpty(firstVideoPreview) && firstVideoPreview.isPortrait() && allowsFourScreenshots(screenshotsDisplayStyle) && isProposedAdStyleCompatible("PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */, firstComputedAdStyle)) { // The assumption here is that all combinations of portrait video are compatible with each other. // If this changes in future, this logic will need to be revisited. // If first trailer's preview is portrait the lockup will render as portrait with portrait images filled in if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) && media.portraitScreenshots.length >= 3) { displayStyle = "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */; } else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) && media.portraitScreenshots.length >= 2) { displayStyle = "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */; } else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots)) { displayStyle = "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */; } else { displayStyle = "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */; } } else if (serverData.isDefinedNonNullNonEmpty(firstVideoPreview) && firstVideoPreview.isPortrait() && isProposedAdStyleCompatible("PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */, firstComputedAdStyle)) { // The assumption here is that all combinations of portrait video are compatible with each other. // If this changes in future, this logic will need to be revisited. // If first trailer's preview is portrait the lockup will render as portrait with portrait images filled in if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) && media.portraitScreenshots.length >= 2) { displayStyle = "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */; } else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots)) { displayStyle = "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */; } else { displayStyle = "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */; } } else if (serverData.isDefinedNonNullNonEmpty(media.landscapeScreenshots) && isProposedAdStyleCompatible("LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */, firstComputedAdStyle)) { displayStyle = "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */; } else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) && allowsFourScreenshots(screenshotsDisplayStyle) && isProposedAdStyleCompatible("PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */, firstComputedAdStyle)) { // The assumption here is that all combinations of portrait video are compatible with each other. // If this changes in future, this logic will need to be revisited. if (media.portraitScreenshots.length >= 4) { displayStyle = "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */; } else if (media.portraitScreenshots.length >= 3) { displayStyle = "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */; } else if (media.portraitScreenshots.length >= 2) { displayStyle = "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */; } else { displayStyle = "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */; } } else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) && isProposedAdStyleCompatible("PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */, firstComputedAdStyle)) { // The assumption here is that all combinations of portrait video are compatible with each other. // If this changes in future, this logic will need to be revisited. if (media.portraitScreenshots.length >= 3) { displayStyle = "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */; } else if (media.portraitScreenshots.length >= 2) { displayStyle = "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */; } else { displayStyle = "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */; } } else if (isProposedAdStyleCompatible("TEXT" /* SearchAdDisplayStyle.TEXT */, firstComputedAdStyle)) { displayStyle = "TEXT" /* SearchAdDisplayStyle.TEXT */; } else { adLogger(objectGraph, `[${adId}] filtered because we could not create a compatible style for the first style of: ${debugDescriptionForStyle(firstComputedAdStyle)}`); return null; } if (completePortraitMediaCount(objectGraph, screenshotsDisplayStyle) === 2) { if (displayStyle === "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */) { displayStyle = "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */; } else if (displayStyle === "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */) { displayStyle = "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */; } } return displayStyle; } /** * Removes media used by the nominated displayStyle. * Used in the case of a dupe where we need to confirm we have "full creative" for a subsequent presentation of a lockup. * @param lockup the lockup to modify * @param displayStyle the display style previously calculated as suitable for the lockup */ function removeUsedMediaForAdDisplayStyle(displayStyle, media) { // In the case of any video assets being used, as per the implementation in `getAdDisplayStyleForMedia`, // we always pick the first video, so whether it's portrait or landscape just remove the first. switch (displayStyle) { case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */: // We don't want to splice here as we might need these for reuse on dupe ads if (media.portraitScreenshots.length <= 5) { media.portraitScreenshots.splice(0, 4); } break; case "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */: media.portraitScreenshots.splice(0, 3); break; case "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */: media.portraitScreenshots.splice(0, 2); break; case "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */: media.portraitScreenshots.splice(0, 1); break; case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */: // We will keep the video splice as the video is always first, so it will never be reused between the ad and organic media.videos.splice(0, 1); // We don't want to splice here as we might need these for reuse on dupe ads if (media.portraitScreenshots.length <= 4) { media.portraitScreenshots.splice(0, 3); } break; case "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */: media.videos.splice(0, 1); media.portraitScreenshots.splice(0, 2); break; case "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */: media.videos.splice(0, 1); media.portraitScreenshots.splice(0, 1); break; case "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */: media.landscapeScreenshots.splice(0, 1); break; case "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */: case "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */: media.videos.splice(0, 1); break; default: break; } } /** * A function to determine whether a calculated organic search display style is considered "full creative". * @param displayStyle the display style calculated for an organic dupe result to validate. * @returns whether the given display style is considered "full creative" for an organic result. */ function isDisplayStyleFullCreativeForOrganic(objectGraph, displayStyle, screenshotsDisplayStyle) { switch (displayStyle) { case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */: case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */: return true; case "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */: case "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */: return completePortraitMediaCount(objectGraph, screenshotsDisplayStyle) === 3; case "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */: case "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */: return true; case "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */: case "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */: return completePortraitMediaCount(objectGraph) === 2; default: return false; } } /** * Indicates whether the proposed ad style is compatible with a previously computed ad style. * If there is no previously computed style, we return true as it's assumed the new style is compatible. * @param proposedAdStyle * @param firstAdComputedStyle * @returns a boolean indicating whether the styles are compatible. */ function isProposedAdStyleCompatible(proposedAdStyle, firstComputedAdStyle) { if (serverData.isNull(firstComputedAdStyle)) { return true; } let areStylesCompatible = true; switch (proposedAdStyle) { case "TEXT" /* SearchAdDisplayStyle.TEXT */: areStylesCompatible = firstComputedAdStyle.style === "TEXT" /* SearchAdDisplayStyle.TEXT */; break; case "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */: case "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */: areStylesCompatible = firstComputedAdStyle.style === "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */ || firstComputedAdStyle.style === "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */; break; case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */: case "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */: case "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */: case "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */: case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */: case "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */: case "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */: case "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */: areStylesCompatible = firstComputedAdStyle.style === "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */ || firstComputedAdStyle.style === "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */ || firstComputedAdStyle.style === "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */ || firstComputedAdStyle.style === "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */ || firstComputedAdStyle.style === "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */ || firstComputedAdStyle.style === "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */ || firstComputedAdStyle.style === "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */ || firstComputedAdStyle.style === "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */; break; case "UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */: areStylesCompatible = firstComputedAdStyle.style === "UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */; break; default: areStylesCompatible = false; } return areStylesCompatible; } /** * Extract the best media for the platform from the lockup. * @param lockup a lockup created from which we should extract the media. * @returns the best media for the platform. */ function platformMediaForLockup(lockup) { let mediaPlatformUsedForDisplayStyle = null; // This works on the assumption that when a lockup is created screenshots and trailers are sorted // and the first in the array of each is what we attempt to display. const firstTrailer = lockup.trailers[0]; let videos = null; if (serverData.isDefinedNonNullNonEmpty(firstTrailer)) { firstTrailer.videos.sort((a, b) => { return artworkSortingFunction(a.preview, b.preview); }); videos = firstTrailer.videos; mediaPlatformUsedForDisplayStyle = firstTrailer.mediaPlatform; } // Grab the first array of platform screenshots, the one the search ad will display. // Additional elements in this array might be for other platforms we wouldn't // display (e.g. if current device is an iPhone then [1] might be iPad screenshots) const platformScreenshots = lockup.screenshots[0]; // Split the screenshots into portrait and landscape arrays. // Landscape is preferred, so we use those first before portrait images, but we also need to know // of any portrait screenshots so we can fill in any space left after a portrait video, which is // preferred over a landscape screenshot. const portraitScreenshots = []; const landscapeScreenshots = []; if (serverData.isDefinedNonNullNonEmpty(platformScreenshots)) { platformScreenshots.artwork.forEach((artwork) => { if (artwork.isPortrait()) { portraitScreenshots.push(artwork); } else { landscapeScreenshots.push(artwork); } }); mediaPlatformUsedForDisplayStyle = platformScreenshots.mediaPlatform; } return { portraitScreenshots: portraitScreenshots, landscapeScreenshots: landscapeScreenshots, alignedRegionArtwork: lockup.alignedRegionArtwork, alignedRegionVideo: lockup.alignedRegionVideo, videos: videos, mediaPlatformUsedForDisplayStyle: mediaPlatformUsedForDisplayStyle, }; } // endregion function iadAttributesForType(data, type) { let iAdDictionaryToUse = null; const iAdOptions = mediaAttributes.attributeAsDictionary(data, "iads"); const iAdJSONStringToUse = serverData.asString(iAdOptions, type); if (iAdJSONStringToUse && iAdJSONStringToUse.length) { iAdDictionaryToUse = JSON.parse(iAdJSONStringToUse); } return iAdDictionaryToUse; } function iAdDataTypeForAdvert(firstSearchResult, isDupe) { if (serverData.isNullOrEmpty(firstSearchResult)) { return "NOORGANIC" /* iAdDataType.NO_ORGANIC_RESULTS */; } if (isDupe) { return "DUP" /* iAdDataType.DUPE_AD */; } return "NORMAL" /* iAdDataType.NORMAL */; } /** * Removes unused media from the provided ad lockup, matching the selected ad display style. * @param ad the ad lockup from which media should be removed. * @param searchAdDisplayStyle the selected `SearchAdDisplayStyle` to match. * @param isDupe if the given ad is a dupe of the first organic search result. * @param isFirstAd if the given ad is the first search ad. * @returns */ function modifyLockupToMatchAdDisplayStyle(ad, searchAdDisplayStyle, isDupe, isFirstAd) { var _a, _b; // Derive whether the ad lockup has a CPP (custom product page/ppid) that's being used for it's assets. const hasCPP = serverData.isDefinedNonNullNonEmpty((_b = (_a = ad.impressionMetrics) === null || _a === void 0 ? void 0 : _a.fields) === null || _b === void 0 ? void 0 : _b.pageCustomId); // If this ad lockup is a duplicate of the first organic search result and is not the first received ad, // the organic search result gets the "first choice" of media. This flag indicates whether we should keep // the "first set" or "second set" of media for this ad lockup. // This logic does *not* apply if the ad is using a CPP. This is because the CPP from an ad only gets applied // to an organic dupe result where the matching ad is in the first position. If not, the ad and organic are // using a different set of assets, so there's no need to prefer one with the "first" or "second" set of media. const wantsSecondSetOfMedia = isDupe && !isFirstAd && !hasCPP; if (serverData.isDefinedNonNullNonEmpty(ad.trailers)) { const firstTrailers = ad.trailers.shift(); firstTrailers.videos.sort((a, b) => { return artworkSortingFunction(a.preview, b.preview); }); ad.trailers.unshift(firstTrailers); } // Split the screenshots into portrait and landscape arrays. // When removing screenshots from the array below, we can't just assume the first ones match the orientation // we're using. For example, we only use portrait images with a portait video and some landscape images could // be mixed in. let mediaPlatformUsedForDisplayStyle; let portraitScreenshots = []; let landscapeScreenshots = []; if (serverData.isDefinedNonNullNonEmpty(ad.screenshots)) { const firstScreenshots = ad.screenshots.shift(); firstScreenshots.artwork.forEach((artwork) => { if (artwork.isPortrait()) { portraitScreenshots.push(artwork); } else { landscapeScreenshots.push(artwork); } }); mediaPlatformUsedForDisplayStyle = firstScreenshots.mediaPlatform; } switch (searchAdDisplayStyle.style) { case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */: // We've previously determined advert can support this type. Remove videos and landscape images so // advert only displays portrait images. // With 4 portrait assets, we need to make sure that the ads and organic can be full by borrowing some assets from the organic. // However, per product, if the app has 5 or less portrait assets, then it will go to the text ad as today. // So we need to check for 5 assets as the lower bound (exclusive) and 8 (exclusive) as the upper for // the extra work to reuse assets. ad.trailers = null; landscapeScreenshots = null; ad.screenshotsDisplayStyle = "four-screenshots"; if (wantsSecondSetOfMedia) { if (portraitScreenshots.length > 5 && portraitScreenshots.length < 8) { const usedAssets = portraitScreenshots.splice(0, 4); const reuseCount = 4 - portraitScreenshots.length; const reuseAssets = usedAssets.splice(usedAssets.length - reuseCount); portraitScreenshots.unshift(...reuseAssets); } else { portraitScreenshots.splice(0, 4); } } else { portraitScreenshots.splice(4); } break; case "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */: // We've previously determined advert can support this type. Remove videos and landscape images so // advert only displays portrait images. ad.trailers = null; landscapeScreenshots = null; if (wantsSecondSetOfMedia) { portraitScreenshots.splice(0, 3); } else { portraitScreenshots.splice(3); } break; case "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */: // We've previously determined advert can support this type. Remove videos and landscape images so // advert only displays portrait images. ad.trailers = null; landscapeScreenshots = null; if (wantsSecondSetOfMedia) { portraitScreenshots.splice(0, 2); } else { portraitScreenshots.splice(2); } break; case "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */: // We've previously determined advert can support this type. Remove videos and landscape images so // advert only displays portrait images. ad.trailers = null; landscapeScreenshots = null; if (wantsSecondSetOfMedia) { portraitScreenshots.splice(0, 1); } else { portraitScreenshots.splice(1); } break; case "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */: // We've previously determined advert can support this type. Remove videos and portrait images so // advert only displays landscape images. ad.trailers = null; portraitScreenshots = null; if (wantsSecondSetOfMedia) { landscapeScreenshots.splice(0, 1); } else { landscapeScreenshots.splice(1); } break; case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */: // We've previously determined advert can support this type. Remove landscape images so // advert only displays portrait assets. // With 4 portrait assets, we need to make sure that the ads and organic can be full by borrowing some assets from the organic. // However, per product, if the app has 5 or less portrait assets, then it will go to the text ad as today. // Since this style includes a portrait video, we only need to check for 4 assets (exclusive) as the lower bound and 7 (exclusive) as the upper for // the extra work to reuse assets landscapeScreenshots = null; ad.screenshotsDisplayStyle = "four-screenshots"; if (wantsSecondSetOfMedia) { ad.trailers[0].videos.splice(0, 1); if (portraitScreenshots.length > 4 && portraitScreenshots.length < 7) { const usedAssets = portraitScreenshots.splice(0, 3); const reuseCount = 3 - portraitScreenshots.length; const reuseAssets = usedAssets.splice(usedAssets.length - reuseCount); portraitScreenshots.unshift(...reuseAssets); } else { portraitScreenshots.splice(0, 3); } } else { ad.trailers[0].videos.splice(1); portraitScreenshots.splice(3); } break; case "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */: // We've previously determined advert can support this type. Remove landscape images so // advert only displays portrait assets. landscapeScreenshots = null; if (wantsSecondSetOfMedia) { ad.trailers[0].videos.splice(0, 1); portraitScreenshots.splice(0, 2); } else { ad.trailers[0].videos.splice(1); portraitScreenshots.splice(2); } break; case "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */: // We've previously determined advert can support this type. Remove landscape images so // advert only displays portrait assets. landscapeScreenshots = null; if (wantsSecondSetOfMedia) { ad.trailers[0].videos.splice(0, 1); portraitScreenshots.splice(0, 1); } else { ad.trailers[0].videos.splice(1); portraitScreenshots.splice(1); } break; case "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */: case "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */: // We've determined advert can support this type. Remove images so only the single video displays if (wantsSecondSetOfMedia) { ad.trailers[0].videos.splice(0, 1); } else { ad.trailers[0].videos.splice(1); } landscapeScreenshots = null; portraitScreenshots = null; break; case "TEXT" /* SearchAdDisplayStyle.TEXT */: // All adverts can support text only, remove all screenshots and trailers so they display in this style ad.trailers = null; landscapeScreenshots = null; portraitScreenshots = null; break; default: break; } if (serverData.isDefinedNonNullNonEmpty(ad.trailers)) { const firstTrailers = ad.trailers.shift(); ad.trailers = [firstTrailers]; } // Combine the remaining landscape and portrait screenshots, and set them back on the ad as the only values. const allScreenshots = [].concat(...[landscapeScreenshots, portraitScreenshots].filter(serverData.isDefinedNonNull)); if (serverData.isDefinedNonNullNonEmpty(allScreenshots)) { const screenshots = new models.Screenshots(allScreenshots, mediaPlatformUsedForDisplayStyle); ad.screenshots = [screenshots]; } else { ad.screenshots = null; } return ad; } function completePortraitMediaCount(objectGraph, searchExperimentsData = null) { if (objectGraph.client.isPhone) { return allowsFourScreenshots(searchExperimentsData) ? 4 : 3; } else { return 2; } } function debugDescriptionForStyle(styleContainer) { if (serverData.isNullOrEmpty(styleContainer)) { return ""; } let mediaTypeString = ""; if (styleContainer && styleContainer.mediaPlatform) { mediaTypeString = ` derived from ${styleContainer.mediaPlatform.mediaType} media`; } return `${styleContainer.style}${mediaTypeString}`; } /** * Stores a value representing the current setting of the native ad debug logging. * This starts as `null`, and the first time we log we ask native to provide the current value. * Requires a re-bootstrap to update, so we avoid sending a message back to native on every call * if logging is disabled. */ let isNativeAdLoggingEnabled = null; export function adLogger(objectGraph, message) { objectGraph.console.log(`[Ads] ${message}`); if (objectGraph.client.buildType === "internal" && objectGraph.isAvailable(ads) && serverData.isDefinedNonNull(objectGraph.ads.debugLog)) { // If we haven't asked native for whether the debug setting for ad logging is enabled, do it now. if (serverData.isNull(isNativeAdLoggingEnabled) && serverData.isDefinedNonNull(objectGraph.ads.isNativeAdLoggingEnabled)) { isNativeAdLoggingEnabled = objectGraph.ads.isNativeAdLoggingEnabled(); } if (isNativeAdLoggingEnabled) { objectGraph.ads.debugLog(message); } } } const artworkSortingFunction = (a, b) => { const aLandscape = a.isLandscape(); const bLandscape = b.isLandscape(); if (aLandscape === bLandscape) { return 0; } if (aLandscape) { return -1; } return 1; }; /** * A wrapper around the ad search result to include the ad's displayStyle it will present with */ export class SearchAdsDisplayStyleResultContainer { } /** * Removes any media used in a provided ad search result from the organic search result. * @param objectGraph the Object Graph * @param adResult the ad search result * @param searchResult the organic search result * @param searchExperimentsData the metadata for search result experiemnts * @param searchAdDisplayStyle the display style for the ad that is matching with the organic */ export function dedupeAdMediaFromMatchingResult(objectGraph, adResult, searchResult, searchExperimentsData, searchAdDisplayStyle) { var _a; // Run de-duping on `AppSearchResult`s and `AppEventSearchResult`s. // We specifically run this for `AppEventSearchResult`s because they can be presented in the search results // as either a regular search result (ie. MixedMediaLockup) (if the app isn't installed), or as an IAE search // result (if it is installed). This means we need to run de-duping in case it appears as a regular result, // matching behaviour of the `AppSearchResult`. if (!(searchResult instanceof models.AppSearchResult || searchResult instanceof models.AppEventSearchResult)) { return; } const searchLockup = searchResult.lockup; const adLockup = adResult.lockups[0]; if (adLockup.adamId !== searchLockup.adamId) { return; } const usedTemplateUrls = new Set(); if (serverData.isDefinedNonNullNonEmpty(adLockup.screenshots)) { for (const screenshot of adLockup.screenshots[0].artwork) { usedTemplateUrls.add(screenshot.template); } } if (serverData.isDefinedNonNullNonEmpty(adLockup.trailers)) { for (const video of adLockup.trailers[0].videos) { usedTemplateUrls.add(video.preview.template); } } if (serverData.isDefinedNonNullNonEmpty(searchLockup.screenshots)) { const filteredArtwork = searchLockup.screenshots[0].artwork.filter((artwork) => { return !usedTemplateUrls.has(artwork.template); }); searchLockup.screenshots[0] = new models.Screenshots(filteredArtwork, searchLockup.screenshots[0].mediaPlatform); } if (serverData.isDefinedNonNullNonEmpty(searchLockup.trailers)) { const filteredVideos = searchLockup.trailers[0].videos.filter((video) => { return !usedTemplateUrls.has(video.preview.template); }); searchLockup.trailers[0] = new models.Trailers(filteredVideos, searchLockup.trailers[0].mediaPlatform); } /// Exit early if we don't need to do anything special for the 4 screenshots case if (((_a = searchExperimentsData === null || searchExperimentsData === void 0 ? void 0 : searchExperimentsData.displayStyle) === null || _a === void 0 ? void 0 : _a.screenshots) !== "four-screenshots") { return; } // We need to possibly reuse some of the screenshots from the ad to make sure the organic has enough screenshots to show; // This is only if we will show 4 portrait assets const padSearchLockupScreenshotsToPreferredSize = (totalRequiredArtwork) => { const currentScreenshots = searchLockup.screenshots[0].artwork; if (currentScreenshots.length >= totalRequiredArtwork) { return; } let screenshotsStillNeededCount = totalRequiredArtwork - currentScreenshots.length; const adArtworksToReuse = adLockup.screenshots[0].artwork.slice().reverse(); for (const artwork of adArtworksToReuse) { if (screenshotsStillNeededCount <= 0) { return; } searchLockup.screenshots[0].artwork.unshift(artwork); screenshotsStillNeededCount -= 1; } }; switch (searchAdDisplayStyle) { case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */: case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */: padSearchLockupScreenshotsToPreferredSize(4); break; default: break; } } /** * As part of the CPP implementation for Search Results ads, if the first ad result has a CPP applied, and there is a dupe scenario * (ie. the first ad matches the first organic result) the CPP must also be applied to the first organic result. * Here we update the CPP data on the organic result to match the first ad result, if applicable. * @param objectGraph The object graph * @param adData The raw ad data used to build the ad results. * @param adResult The built ad result. * @param searchResultData The data for the first organic search result. * @param installStates A mapping of adamIDs to their respective install states that is used to determine if the app is currently installed by the user * @param appStates A mapping of adamIDs to their respective app state to determine if the ad/first result has been installed by the user in the past * @param metricsOptions Metrics options for built models. * @param personalizationDataContainer The data container to use for personalizing the data. */ export function updateDupeOrganicResultCPPData(objectGraph, adData, adResult, searchResultData, installStates, appStates, metricsOptions, personalizationDataContainer) { var _a, _b; const adLockup = adResult.lockups[0]; if (adLockup.adamId !== searchResultData.id) { return; } // Get the data for the first ad lockup. This *should* just be the first, but // it's possible the first data got dropped when building the ad lockups. const dataForAdLockup = adData.find((data) => data.id === adLockup.adamId); const shouldSkipUpdatingOrganicCPP = searchResultWillUseAppEventDisplay(objectGraph, searchResultData, installStates, appStates, metricsOptions, personalizationDataContainer); if (shouldSkipUpdatingOrganicCPP) { return; } // The first ad lockup matches the first organic result. Apply the Ad cppId to matching organic result. updatePPIDInData((_b = (_a = dataForAdLockup === null || dataForAdLockup === void 0 ? void 0 : dataForAdLockup.meta) === null || _a === void 0 ? void 0 : _a.cppData) === null || _b === void 0 ? void 0 : _b["ppid"], searchResultData); } /** * Update the ppid for the given data. * If provided `ppid` is `null`, the field will be deleted. * If `cppData` does not already exist on `meta`, it will be created. * @param ppid The ppid to update in the data. Can be null, which will delete the field. * @param data The data object to update. */ function updatePPIDInData(ppid, data) { var _a; let meta = shallowCopyOf(data.meta); if (serverData.isNull(ppid)) { (_a = meta === null || meta === void 0 ? void 0 : meta.cppData) === null || _a === void 0 ? true : delete _a["ppid"]; } else { if (serverData.isNull(meta)) { meta = {}; } if (serverData.isNull(meta.cppData)) { meta.cppData = {}; } meta.cppData["ppid"] = ppid; } data.meta = meta; } /** * Whether or not platform supports adverts. */ export function platformSupportsAdverts(objectGraph) { return ((clientIdentifierSupportsAdverts(objectGraph) && objectGraph.host.isiOS) || objectGraph.host.platform === "unknown"); } function clientIdentifierSupportsAdverts(objectGraph) { return (objectGraph.host.clientIdentifier === client.appStoreIdentifier || objectGraph.host.clientIdentifier === client.productPageExtensionIdentifier); } //# sourceMappingURL=search-ads.js.map