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/lockups/ad-lockups.js | |
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/lockups/ad-lockups.js')
| -rw-r--r-- | node_modules/@jet-app/app-store/tmp/src/common/lockups/ad-lockups.js | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/lockups/ad-lockups.js b/node_modules/@jet-app/app-store/tmp/src/common/lockups/ad-lockups.js new file mode 100644 index 0000000..f668735 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/lockups/ad-lockups.js @@ -0,0 +1,439 @@ +/** + * Builder code specific to Ad Lockups + */ +import { isSome } from "@jet/environment"; +import { AdInteractionAction, AdTransparencyAction, CompoundAction, Screenshots, SearchAd, SearchAdOpportunity, } from "../../api/models"; +import { AdvertActionMetrics, } from "../../api/models/metrics/advert-action-metrics"; +import { isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, asBooleanOrFalse, asArray, } from "../../foundation/json-parsing/server-data"; +import { attributeAsArrayOrEmpty, attributeAsBooleanOrFalse, attributeAsDictionary, attributeAsString, } from "../../foundation/media/attributes"; +import { advertInstanceIdForData } from "../ads/ad-common"; +import * as content from "../content/content"; +import { addClickEventsToAdLockup } from "../metrics/helpers/clicks"; +import { addImpressionsFieldsToAd, impressionOptionsForLockup } from "../metrics/helpers/impressions"; +import { metricsPlatformDisplayStyleFromData } from "../metrics/helpers/util"; +import { appDataHasVariant } from "../product-page/product-page-variants"; +import { adLogger } from "../search/search-ads"; +import { asString } from "@apple-media-services/media-api"; +// region Shared +/** + * Whether or not data is for ad. + * + * Note that this attribute *may not be initially part of the original response*. + * - Sponsored Search: Since Ad Rotation, `iad` attribute is populated from contents of `iads` dictionary. + * - Search Landing: `iad` attribute is populated from contents of `OnDeviceAdvert`. + * + * @param data Data to check if it is an ad. + * @see`iadAttributesForType` and `decorateiAdAttributeFromOnDeviceAd` + */ +export function isAdvert(objectGraph, data) { + const adDictionary = attributeAsDictionary(data, "iad"); + return isDefinedNonNullNonEmpty(adDictionary); +} +// endregion +// region Data Overrides +/** + * Populates Ad-specific customizations for given lockup to a **otherwise fully configured ad lockup** + * @param objectGraph The object graph. + * @param data Data that will provide the ad data. + * @param lockup The lockup to modify. + * @param metricsOptions The lockup metrics options. + * @param applyAdOfferDisplayProperties Whether to apply the default ad OfferDisplayProperties. Some callers of this function want to enforce their own offer styling. + */ +export function performAdOverridesforLockup(objectGraph, data, lockup, metricsOptions, applyAdOfferDisplayProperties = true) { + // If the lockup is not an ad, it's ad eligible. Just apply the metrics modifications and return early. + if (!metricsOptions.isAdvert) { + performMetricsOverridesForLockup(objectGraph, data, lockup, metricsOptions); + return; + } + let searchAd; + if (objectGraph.props.enabled("advertSlotReporting")) { + lockup.searchAdOpportunity = searchAdOpportunityFromData(objectGraph, data, metricsOptions.pageInformation); + searchAd = lockup.searchAdOpportunity.searchAd; + } + else { + lockup.searchAd = searchAdFromData(objectGraph, data, metricsOptions.pageInformation); + searchAd = lockup.searchAd; + } + /** + * Advert Action Metrics (AzulC+) + */ + const advertType = content.isArcadeSupported(objectGraph, data) + ? "arcadeApp" + : "standardApp"; + const reportingDestination = reportingDestinationFromMetricsOptions(objectGraph, metricsOptions.pageInformation); + const bundleId = attributeAsString(data, "platformAttributes.ios.bundleId"); + const isPreorder = attributeAsBooleanOrFalse(data, "isPreorder"); + const purchaseType = isPreorder ? "preorder" : "standard"; + const clickActionAdActionMetrics = new AdvertActionMetrics(searchAd.instanceId, data.id, bundleId, advertType, "advertPressed", purchaseType, reportingDestination); + lockup.clickAction = attachAdActionMetricsToAction(objectGraph, lockup.clickAction, clickActionAdActionMetrics); + const buttonActionAdActionMetrics = new AdvertActionMetrics(searchAd.instanceId, data.id, bundleId, advertType, "offerButtonPress", purchaseType, reportingDestination); + lockup.buttonAction = attachAdActionMetricsToAction(objectGraph, lockup.buttonAction, buttonActionAdActionMetrics); + lockup.itemBackground = objectGraph.props.enabled("insetAdItemBackground") ? "insetAd" : "ad"; + // Offer button style + if (lockup.offerDisplayProperties && applyAdOfferDisplayProperties) { + lockup.offerDisplayProperties = lockup.offerDisplayProperties.newOfferDisplayPropertiesChangingAppearance(false, "colored", "ad"); + } + // Rating + if (!attributeAsBooleanOrFalse(data, "iad.format.userRating")) { + lockup.rating = null; + lockup.ratingCount = null; + } + performMetricsOverridesForLockup(objectGraph, data, lockup, metricsOptions); +} +/** + * Whether or not an advert is eligible to use CPP deeplinks, regardless of whether or not one exists + * @param data Data that will provide the ad data. + */ +export function isCppDeeplinkEnabledForAdvert(data) { + const iAd = attributeAsDictionary(data, "iad"); + const searchAdCppDeepLinkEnabled = asBooleanOrFalse(iAd, "passthroughAdInfo.deepLinkEligible"); + const targetedAdCppDeeplinkingEnabled = asBooleanOrFalse(data, "meta.passthroughAdInfo.deepLinkEligible"); + return searchAdCppDeepLinkEnabled || targetedAdCppDeeplinkingEnabled; +} +/** + * Whether or not a custom creative advert is eligible to use CPP deeplinks, regardless of whether or not one exists + * @param data Data that will provide the ad data. + */ +export function isCustomCreativeDeeplinkEnabledForAdvert(data) { + const extraAdInfo = getExtraAdInfo(data); + if (isSome(extraAdInfo)) { + return asBooleanOrFalse(extraAdInfo, "passthroughAdInfo.deepLinkEligible"); + } + else { + return asBooleanOrFalse(data, "meta.passthroughAdInfo.deepLinkEligible"); + } +} +/** + * The custom creative ad deeplink url, if there is one. + * @param data Data that will provide the ad data. + */ +export function getCustomCreativeDeepLinkUrl(data) { + const extraAdInfo = getExtraAdInfo(data); + const creativeDetails = asArray(extraAdInfo, "creativeDetails"); + if (isSome(creativeDetails)) { + return asString(creativeDetails[0], "deepLink"); + } + else { + return asString(data, "meta.alignedRegionDetails.deepLink"); + } +} +/** + * The tapDestination of the custom creative advert which is also the cppId for the PP. + * @param data Data that will provide the ad data. + */ +export function getTapDestinationIdForAdvert(data) { + const extraAdInfo = getExtraAdInfo(data); + const creativeDetails = asArray(extraAdInfo, "creativeDetails"); + if (isSome(creativeDetails)) { + return asString(creativeDetails[0], "tapDestination"); + } + else { + return asString(data, "meta.alignedRegionDetails.tapDestination"); + } +} +/** + * Local function to extract the extraAdInfo from the meta in the data. + * @param data Data that will provide the ad data. + */ +function getExtraAdInfo(data) { + const extraAdInfoString = asString(data, "meta.extraAdInfo"); + if (isSome(extraAdInfoString)) { + return JSON.parse(extraAdInfoString); + } + return null; +} +/** + * Perform overrides for metrics for Ad Lockups + * @param data Data for ad lockup + * @param lockup Lockup to apply overrides to + * @param baseMetricsOptions Base metrics options to extend. + */ +function performMetricsOverridesForLockup(objectGraph, data, lockup, baseMetricsOptions) { + const platformDisplayStyle = metricsPlatformDisplayStyleFromData(objectGraph, data, lockup.icon, null); + const impressionsMetricOptions = impressionOptionsForLockup(objectGraph, data, lockup, platformDisplayStyle, baseMetricsOptions, true); + addClickEventsToAdLockup(objectGraph, lockup, impressionsMetricOptions); + addImpressionsFieldsToAd(objectGraph, lockup, impressionsMetricOptions, impressionsMetricOptions.pageInformation.iAdInfo); +} +/** + * Creates a template type string for the medium ad format + * @param lockup The lockup whose artwork to use for the template type string + * @returns The native ad template type for the medium format ad + */ +export function getTemplateTypeForMediumAdFromLockupWithScreenshots(screenshots) { + var _a; + const templateString = [ + "MEDRIVER_", + "U", + "I", + ((_a = screenshots === null || screenshots === void 0 ? void 0 : screenshots.artwork) !== null && _a !== void 0 ? _a : []).length, // Count of assets used. + ].join(""); + return templateString; +} +/** + * Creates a template type string for the medium ad format + * @param lockup The lockup whose artwork to use for the template type string + * @returns The native ad template type for the medium format ad + */ +export function getTemplateTypeForMediumAdFromLockupWithCustomCreative() { + const templateString = [ + "MEDRIVER_", + "U", + "I", + 1, + "_2x1", // Aspect Ratio used. + ].join(""); + return templateString; +} +/** + * Create an opportunity for an ad model + * @param objectGraph The object graph. + * @param data Data to build `SearchAd` for. + * @param pageInformation Metrics page information. + * @returns The search ad opportunity represented in the data + */ +export function searchAdOpportunityFromData(objectGraph, data, pageInformation) { + const searchAd = searchAdFromData(objectGraph, data, pageInformation); + const lifecycleEventPayloads = searchAdLifecycleEventPayloads(searchAd.instanceId, pageInformation); + return new SearchAdOpportunity(searchAd.instanceId, lifecycleEventPayloads, searchAd); +} +/** + * Create a missed opportunity for an ad model. This differentiates from `searchAdOpportunityFromData` because the + * opportunity is represented by an organic content that is in an ad eligible position. + * @param objectGraph The object graph. + * @param pageInformation Metrics page information. + * @returns The search ad opportunity for the missed opportunity + */ +export function searchAdMissedOpportunityFromId(objectGraph, pageInformation) { + let adInstanceId; + const placementType = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo.placementType; + if (isDefinedNonNull(placementType) && objectGraph.props.enabled("advertSlotReporting")) { + try { + adInstanceId = objectGraph.ads.getIdentifierForMissedOpportunity(placementType); + } + catch { + adInstanceId = objectGraph.random.nextUUID(); + adLogger(objectGraph, `Error: getIdentifierForMissedOpportunity threw exception. Assigned ${adInstanceId}`); + } + } + else { + adInstanceId = objectGraph.random.nextUUID(); + adLogger(objectGraph, `Error: placementType was null or empty. Assigned ${adInstanceId}`); + } + const lifecycleEventPayloads = searchAdLifecycleEventPayloads(adInstanceId, pageInformation); + return new SearchAdOpportunity(adInstanceId, lifecycleEventPayloads); +} +/** + * Create the backing search ad model for any representation of ad. + * @param objectGraph The object graph. + * @param data Data to build `SearchAd` for. + * @param pageInformation Information of the page hosting the lockup + * @returns The search ad model + */ +function searchAdFromData(objectGraph, data, pageInformation) { + let instanceId = advertInstanceIdForData(objectGraph, data); + if (isNull(instanceId) || instanceId.length === 0) { + instanceId = objectGraph.random.nextUUID(); + adLogger(objectGraph, `Error: instanceId was null or empty. Assigned ${instanceId}`); + } + const iAd = attributeAsDictionary(data, "iad"); + const impressionId = attributeAsString(data, "iad.impressionId"); + const transparencyData = attributeAsString(data, "iad.privacy"); + const bundleId = attributeAsString(data, "platformAttributes.ios.bundleId"); + const transparencyAction = new AdTransparencyAction(transparencyData); + transparencyAction.title = objectGraph.adsLoc.string("IAD_PRIVACY_MARKER_BUTTON_TITLE"); + const advertType = content.isArcadeSupported(objectGraph, data) + ? "arcadeApp" + : "standardApp"; + const isPreorder = attributeAsBooleanOrFalse(data, "isPreorder"); + const purchaseType = isPreorder ? "preorder" : "standard"; + const reportingDestination = reportingDestinationFromMetricsOptions(objectGraph, pageInformation); + const adActionMetrics = new AdvertActionMetrics(instanceId, data.id, bundleId, advertType, "markerPress", purchaseType, reportingDestination); + const lifecycleEventPayloads = searchAdLifecycleEventPayloads(instanceId, pageInformation); + const action = attachAdActionMetricsToAction(objectGraph, transparencyAction, adActionMetrics); + return new SearchAd(instanceId, iAd, lifecycleEventPayloads, impressionId, action); +} +/** + * Create the backing metadata for any representation of ad opportunity. + * @param instanceId The identifier for the ad opportunity we're tracking + * @param pageInformation Information of the page hosting the lockup + * @returns Payloads to be sent to PromotedContent + */ +export function searchAdLifecycleEventPayloads(instanceId, pageInformation) { + const pageIdentifierRaw = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.baseFields.pageId; + const pageIdentifier = typeof pageIdentifierRaw === "string" ? pageIdentifierRaw : "unknown"; + return { + placed: { + adInstanceId: instanceId, + pageIdentifier: pageIdentifier, + }, + pageEnter: { + pageIdentifier: pageIdentifier, + }, + pageExit: { + pageIdentifier: pageIdentifier, + }, + onScreen: { + adInstanceId: instanceId, + }, + offScreen: { + adInstanceId: instanceId, + }, + visible: { + adInstanceId: instanceId, + }, + completed: { + adInstanceId: instanceId, + }, + }; +} +/** + * Populates Ad-specific customizations for given card to an otherwise fully configured Today card + * @param objectGraph The object graph. + * @param data Data that will provide the ad data. + * @param card The Today card to modify. + * @param metricsOptions The card metrics options. + */ +export function performAdOverridesForCard(objectGraph, data, card, metricsOptions) { + let instanceId = advertInstanceIdForData(objectGraph, data); + if (isNull(instanceId) || instanceId.length === 0) { + instanceId = objectGraph.random.nextUUID(); + adLogger(objectGraph, `Error: instanceId was null or empty. Assigned ${instanceId}`); + } + const advertType = content.isArcadeSupported(objectGraph, data) + ? "arcadeApp" + : "standardApp"; + const reportingDestination = reportingDestinationFromMetricsOptions(objectGraph, metricsOptions.pageInformation); + const bundleId = attributeAsString(data, "platformAttributes.ios.bundleId"); + const isPreorder = attributeAsBooleanOrFalse(data, "isPreorder"); + const purchaseType = isPreorder ? "preorder" : "standard"; + const adActionMetrics = new AdvertActionMetrics(instanceId, data.id, bundleId, advertType, "advertPressed", purchaseType, reportingDestination); + card.clickAction = attachAdActionMetricsToAction(objectGraph, card.clickAction, adActionMetrics); +} +/** + * Attaches `AdActionMetrics` to an existing action enabling native ad interaction instrumentation. + * This works by wrapping the existing action, along with a new `AdInteractionAction`, in a `CompoundAction`, which are executed at the same time. + * + * @param objectGraph The Object Graph. + * @param existingAction The existing Action to wrap. + * @param adActionMetrics The adActionMetrics object for the `AdInteractionAction`. + * @returns The new Action containing the existing and new Actions. + */ +export function attachAdActionMetricsToAction(objectGraph, existingAction, adActionMetrics) { + const adInteractionAction = new AdInteractionAction(adActionMetrics); + const action = new CompoundAction([adInteractionAction, existingAction]); + // Re-use the title from the existing action. + action.title = existingAction.title; + return action; +} +// endregion +// region Asset Overrides +/** + * Performs iAd Asset overrides, where a field dictates which assets to use + * @param data The data to source overrides from + * @param lockup The lockup to modify + * @param metricsOptions The metrics options that this operation has side effects on. + */ +export function performAssetOverridesForMixedMediaAdLockupIfNeeded(objectGraph, data, lockup, metricsOptions) { + if (!shouldPerformAssetOverridesForData(objectGraph, data)) { + return; + } + const iAdAssetOverrides = attributeAsArrayOrEmpty(data, "iad.assetOverride"); + if (iAdAssetOverrides.length) { + const didApplyAssetOverrides = applyAssetOverridesToMixedMediaAdLockup(objectGraph, lockup, iAdAssetOverrides); + if (metricsOptions.pageInformation.iAdInfo && metricsOptions.pageInformation.iAdInfo.iAdIsPresent) { + if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) { + metricsOptions.pageInformation.iAdInfo.setSpecifiedAlignedRegionUsed(didApplyAssetOverrides); + } + } + } +} +/** + * Whether or not an asset override should occur. + * This is used to control specific scenarios when asset overrides should not occur, e.g. when it clashes with other features. + * + * @param data The apps resource to determine if asset overrides should be done for. + */ +function shouldPerformAssetOverridesForData(objectGraph, data) { + /** + * If an AB Test is being run on given app, don't accept the override. + */ + const appHasABTest = appDataHasVariant(objectGraph, data, "abExperiment"); + return !appHasABTest; +} +/** + * Apply asset overrides to the ad lock up, based on a given list of overrides + * @param lockup Lockup to modify + * @param iAdAssetOverrides Asset overrides specified + * @return {boolean} - Return whether or not the overrides were applied + */ +function applyAssetOverridesToMixedMediaAdLockup(objectGraph, lockup, iAdAssetOverrides) { + const unusedAssetOverrides = new Set(iAdAssetOverrides); + const videoOverrides = []; + const screenshotOverrides = []; + const isOverrideMedia = function (url, checksum) { + if (unusedAssetOverrides.size === 0) { + return false; + } + for (const assetOverride of iAdAssetOverrides) { + if (assetOverride === checksum || url.indexOf(assetOverride) !== -1) { + unusedAssetOverrides.delete(assetOverride); + return true; + } + } + return false; + }; + if (iAdAssetOverrides.length && (lockup.screenshots.length || lockup.trailers.length)) { + if (lockup.trailers.length) { + for (const video of lockup.trailers[0].videos) { + if (isOverrideMedia(video.videoUrl, video.preview.checksum)) { + videoOverrides.push(video); + } + } + } + if (lockup.screenshots.length) { + for (const screenshot of lockup.screenshots[0].artwork) { + if (isOverrideMedia(screenshot.template, screenshot.checksum)) { + screenshotOverrides.push(screenshot); + } + } + } + } + if (unusedAssetOverrides.size === 0 && (videoOverrides.length || screenshotOverrides.length)) { + if (lockup.trailers.length) { + lockup.trailers[0].videos = videoOverrides; + } + if (lockup.screenshots.length) { + lockup.screenshots[0] = new Screenshots(screenshotOverrides, lockup.screenshots[0].mediaPlatform); + } + return true; + } + else { + return false; + } +} +// endregion +// region Reporting Destionation +/** + * Determine the reporting destination based on metrics options + */ +export function reportingDestinationFromMetricsOptions(objectGraph, pageInformation) { + const iadInfo = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo; + if (isNull(iadInfo)) { + return "undefined"; + } + const placementType = iadInfo.placementType; + switch (placementType) { + case "searchLanding": + return "promotedContent"; // SLP uses PC-backed Journey reporting + case "searchResults": + return "searchAds"; // SRP uses SA-backed Journey reporting + case "today": + case "productPageYMAL": + case "productPageYMALDuringDownload": + return "promotedContent"; // Chainlink uses PC-backed Journey reporting + default: + throw new Error(`This method should never be called with value: ${placementType}`); + } +} +// endregion +//# sourceMappingURL=ad-lockups.js.map
\ No newline at end of file |
