diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js | |
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js')
| -rw-r--r-- | node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js | 1047 |
1 files changed, 1047 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js new file mode 100644 index 0000000..e8377d4 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js @@ -0,0 +1,1047 @@ +// +// 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
\ No newline at end of file |
