summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /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.js1047
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