summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/lockups/ad-lockups.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/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.js439
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