summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-fetch.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-fetch.js')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-fetch.js286
1 files changed, 286 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-fetch.js b/node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-fetch.js
new file mode 100644
index 0000000..04f3e01
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-fetch.js
@@ -0,0 +1,286 @@
+/**
+ * Handles fetching Ads for all on-device ad placements
+ * This is currently SLP, Today, Product Page.
+ *
+ * # Ad fetching for on-device placements
+ * On-Device placements are unpersonalized and rely on CDN caching. To show ads, we:
+ * 1. Fetch from an on-device cache of ads (managed by PromotedContent framework) for the specific placement
+ * 2. Fetching ad data from MAPI
+ * 2. Stitch ad onto page data...
+ */
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { Request } from "../../foundation/media/data-fetching";
+import { dataFromDataContainer } from "../../foundation/media/data-structure";
+import { fetchData } from "../../foundation/media/network";
+import { buildURLFromRequest } from "../../foundation/media/url-builder";
+import { Parameters } from "../../foundation/network/url-constants";
+import { offerDataFromData } from "../offers/offers";
+import { productVariantDataForData, shouldFetchCustomAttributes } from "../product-page/product-page-variants";
+import { todayTabODPTimeoutUseCase } from "../personalization/on-device-recommendations-today";
+import { setTimeoutForRequestKey } from "../util/timeout-manager-util";
+import { adLogger } from "../search/search-ads";
+import * as adCommon from "./ad-common";
+import { getSelectedCustomCreativeId } from "../search/custom-creative";
+import { isSome } from "@jet/environment";
+// region exports
+/**
+ * Fetch ads for the given placement type leveraging on-device ads cache.
+ * @param objectGraph the object graph.
+ * @param placementType the placement type to fetch the ad for.
+ * @param adamId the adamId of the app for which the product page is being viewed, to provide a relevant ad. Only required for product page placements.
+ * @returns a promise containing an ad.
+ */
+export async function fetchAds(objectGraph, placementType, adamId) {
+ const timeout = adCommon.adFetchTimeoutForPlacement(objectGraph, placementType, false);
+ const request = new Request(objectGraph);
+ switch (placementType) {
+ case "today":
+ request.usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
+ switch (adCommon.todayAdStyle(objectGraph)) {
+ case "mediumLockup":
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ if (objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) {
+ request.includingAttributes(["customScreenshotsByTypeForAd", "adCreativeArtwork"]);
+ }
+ else {
+ request.includingAttributes(["customScreenshotsByTypeForAd"]);
+ }
+ }
+ else {
+ request.includingAttributes(["customScreenshotsByTypeForAd"]);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ request.usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
+ break;
+ default:
+ break;
+ }
+ /**
+ * Ad not available content filtering
+ */
+ const adsOverrideLanguage = objectGraph.bag.adsOverrideLanguage;
+ if (serverData.isDefinedNonNullNonEmpty(adsOverrideLanguage)) {
+ request.enablingFeature("adsLocaleMetadata").addingQuery("l", adsOverrideLanguage);
+ }
+ const requestMetaFields = buildURLFromRequest(objectGraph, request).query;
+ try {
+ const onDeviceResponse = await objectGraph.ads.fetchOnDeviceAdPlacement(placementType, timeout, requestMetaFields, adamId);
+ return await handleAdResponse(objectGraph, onDeviceResponse, placementType);
+ }
+ catch {
+ return null;
+ }
+}
+/**
+ * Handle the response from an on device ad request.
+ * @param objectGraph The App Store Object Graph.
+ * @param onDeviceResponse The response from the on device ad fetcher.
+ * @param placementType The placement this request was for.
+ * @returns A promise containing an ad.
+ */
+async function handleAdResponse(objectGraph, onDeviceResponse, placementType) {
+ var _a, _b, _c, _d, _e, _f;
+ if (serverData.isNullOrEmpty(onDeviceResponse.clientRequestId)) {
+ onDeviceResponse.clientRequestId = objectGraph.random.nextUUID();
+ adLogger(objectGraph, `clientRequestId was nil. Assigned ${onDeviceResponse.clientRequestId}`);
+ }
+ const aggregateResponse = {
+ clientRequestId: onDeviceResponse.clientRequestId,
+ iAdId: onDeviceResponse.iAdId,
+ placementType: (_b = (_a = onDeviceResponse === null || onDeviceResponse === void 0 ? void 0 : onDeviceResponse.ad) === null || _a === void 0 ? void 0 : _a.placementType) !== null && _b !== void 0 ? _b : placementType,
+ };
+ // Failed w/o Ad from device.
+ if (onDeviceResponse.failureReason) {
+ aggregateResponse.failureReason = onDeviceResponse.failureReason;
+ return aggregateResponse;
+ }
+ // Set the basic ad info received on the response.
+ aggregateResponse.onDeviceAd = onDeviceResponse.ad;
+ // Ad requests should return with at least some basic app metadata.
+ // Note: On pre-SydneyC builds, this is expected to be null for the SLP placement.
+ let mediaResponse = (_c = onDeviceResponse.ad) === null || _c === void 0 ? void 0 : _c.appMetadata;
+ // Get the currently available app data from the data container.
+ const appData = dataFromDataContainer(objectGraph, mediaResponse);
+ // We should only attempt to fetch the full app data if the app data provided to us via
+ // Promoted Content is incomplete. This should only be the case for SLP ads - Chainlink
+ // placements should arrive with complete app data.
+ // We check a couple of attributes here as a way to be sure it's hydrated. Sometimes
+ // a single attribute can be misleading.
+ if (serverData.isNullOrEmpty((_d = appData === null || appData === void 0 ? void 0 : appData.attributes) === null || _d === void 0 ? void 0 : _d.name) ||
+ serverData.isNullOrEmpty((_e = appData === null || appData === void 0 ? void 0 : appData.attributes) === null || _e === void 0 ? void 0 : _e.platformAttributes) ||
+ serverData.isNullOrEmpty(offerDataFromData(objectGraph, appData))) {
+ try {
+ const adRequest = createRequestForOnDeviceAd(objectGraph, onDeviceResponse.ad);
+ mediaResponse = await fetchData(objectGraph, adRequest);
+ }
+ catch (e) {
+ adLogger(objectGraph, `fetchAds request failed - ${e}`);
+ aggregateResponse.failureReason = "mapiFetchFail";
+ }
+ }
+ // The app data should now be complete, set it on the response.
+ if (serverData.isDefinedNonNullNonEmpty((_f = dataFromDataContainer(objectGraph, mediaResponse)) === null || _f === void 0 ? void 0 : _f.attributes)) {
+ aggregateResponse.mediaResponse = decorateiAdAttributeFromOnDeviceAdResponse(objectGraph, mediaResponse, onDeviceResponse);
+ // Check the localization is valid for the ad.
+ if (!adCommon.isAdLocalizationValid(objectGraph, dataFromDataContainer(objectGraph, mediaResponse), aggregateResponse.onDeviceAd)) {
+ adLogger(objectGraph, `fetchAds request failed - localization not available`);
+ aggregateResponse.failureReason = "localizationNotAvailable";
+ }
+ const metadataFailReason = checkAppMetadataIsValidForPlacement(objectGraph, aggregateResponse, placementType);
+ if (serverData.isDefinedNonNull(metadataFailReason)) {
+ adLogger(objectGraph, `fetchAds request failed - ${metadataFailReason}`);
+ aggregateResponse.failureReason = metadataFailReason;
+ }
+ }
+ return aggregateResponse;
+}
+/**
+ * Indicates that an organic request kicked off parallel to an ad fetch has completed.
+ * This gives us an opportunity to enforce a timeout on the ad request for the time beyond the organic request.
+ * @param objectGraph The object graph.
+ * @param placementType The placement for which the parallel organic request finished.
+ */
+export function parallelOrganicRequestDidFinish(objectGraph, placementType) {
+ const timeout = adCommon.adFetchTimeoutForPlacement(objectGraph, placementType, true);
+ if (serverData.isNull(timeout)) {
+ return;
+ }
+ objectGraph.ads.setTimeoutForCurrentOnDeviceAdFetch(placementType, timeout);
+ setTimeoutForRequestKey(objectGraph, timeout, todayTabODPTimeoutUseCase);
+}
+// endregion
+// region internals
+/**
+ * Create an request for on-device adverts
+ * @param ad The on device ad to fetch MAPI data for.
+ */
+function createRequestForOnDeviceAd(objectGraph, ad) {
+ const request = new Request(objectGraph)
+ .withIdOfType(ad.adamId, "apps")
+ .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph))
+ .includingAttributes(["customScreenshotsByTypeForAd"]);
+ if (serverData.isDefinedNonNullNonEmpty(ad.cppIds)) {
+ request.addingQuery(Parameters.productVariantID, ad.cppIds[0]);
+ }
+ // If there is an `adsOverrideLanguage`, attach it to this request too.
+ const adsOverrideLanguage = objectGraph.bag.adsOverrideLanguage;
+ if (serverData.isDefinedNonNullNonEmpty(adsOverrideLanguage)) {
+ request.addingQuery("l", adsOverrideLanguage);
+ }
+ return request;
+}
+/**
+ * Decorate `iad` attribute with contents of `OnDeviceAdvert`.
+ *
+ * @param mediaResponse The data from of MAPI request
+ * @param ad Ad data that was fetched independent of response.
+ */
+function decorateiAdAttributeFromOnDeviceAdResponse(objectGraph, mediaResponse, adResponse) {
+ const adData = dataFromDataContainer(objectGraph, mediaResponse);
+ if (serverData.isNullOrEmpty(adData) || serverData.isNull(adData.attributes)) {
+ adLogger(objectGraph, "decorateiAdAttributeFromOnDeviceAd cannot decorate for malformed response");
+ return null; // The data is incompatible with `iad` decoration. Return `null` to let builder report error.
+ }
+ const onDeviceAd = adResponse.ad;
+ const lineItem = `${onDeviceAd.adamId}|${onDeviceAd.metadata}`;
+ // Create `IAdAttributes` and stitch onto `mediaResponse`
+ const iadAttributes = {
+ clientRequestId: adResponse.clientRequestId,
+ impressionId: onDeviceAd.impressionId,
+ metadata: onDeviceAd.metadata,
+ privacy: onDeviceAd.privacy,
+ lineItem: lineItem,
+ };
+ const metaContainer = dataFromDataContainer(objectGraph, onDeviceAd.appMetadata);
+ if (serverData.isDefinedNonNullNonEmpty(adData.meta) &&
+ serverData.isDefinedNonNullNonEmpty(metaContainer) &&
+ serverData.isDefinedNonNullNonEmpty(metaContainer.meta)) {
+ adData.meta.passthroughAdInfo = metaContainer.meta.passthroughAdInfo;
+ if (isSome(onDeviceAd === null || onDeviceAd === void 0 ? void 0 : onDeviceAd.alignedRegionDetails)) {
+ adData.meta.alignedRegionDetails = onDeviceAd === null || onDeviceAd === void 0 ? void 0 : onDeviceAd.alignedRegionDetails[0];
+ }
+ }
+ switch (onDeviceAd.placementType) {
+ case "today":
+ // Enable images for specific placements.
+ const imagesEnabled = adCommon.todayAdStyle(objectGraph) === "mediumLockup";
+ iadAttributes.format = {
+ images: imagesEnabled,
+ text: "",
+ userRating: false,
+ };
+ break;
+ case "searchLanding":
+ iadAttributes.format = {
+ images: true,
+ text: "",
+ userRating: false,
+ };
+ break;
+ default:
+ break;
+ }
+ adData.attributes["iad"] = iadAttributes;
+ adCommon.decorateAdInstanceIdOnData(objectGraph, adData, onDeviceAd.instanceId);
+ return mediaResponse;
+}
+/**
+ * Checks whether the metadata returned for the app is considered valid for the given placement.
+ * Some ad placements have special rules to be shown. These rules are validated here.
+ * @param objectGraph The App Store object graph.
+ * @param adResponse The ad response.
+ * @param placementType The placement the given ad data is to be placed in.
+ * @returns A fail reason if there is one. Otherwise null.
+ */
+function checkAppMetadataIsValidForPlacement(objectGraph, adResponse, placementType) {
+ switch (placementType) {
+ case "today":
+ return checkAppMetadataIsValidForToday(objectGraph, adResponse);
+ default:
+ return null;
+ }
+}
+/**
+ * Checks the metadata returned for the app is considered valid **specifically** for the Today placement.
+ *
+ * Today ad placements must:
+ * - Include a valid cppId in the `meta` field of the data that matches the cppId in the ad result, and
+ * - Have enough assets to fulfill the template requirement:
+ * - Portrait: at least 4 assets, one of which can be a video.
+ * - Landscape: at least 5 assets, one of which can be a video.
+ *
+ * @param objectGraph The App Store object graph.
+ * @param adResponse The ad response.
+ * @returns A fail reason if there is one. Otherwise null.
+ */
+function checkAppMetadataIsValidForToday(objectGraph, adResponse) {
+ var _a, _b, _c;
+ const adData = dataFromDataContainer(objectGraph, adResponse.mediaResponse);
+ const productVariantData = productVariantDataForData(objectGraph, adData);
+ const hasCPP = (_b = (_a = adResponse.onDeviceAd) === null || _a === void 0 ? void 0 : _a.cppIds) === null || _b === void 0 ? void 0 : _b.includes(productVariantData.productPageId);
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ const alignedRegionDetails = (_c = adResponse.onDeviceAd) === null || _c === void 0 ? void 0 : _c.alignedRegionDetails;
+ const creativeID = serverData.asString(alignedRegionDetails === null || alignedRegionDetails === void 0 ? void 0 : alignedRegionDetails[0], "apAssetId");
+ const selectedCustomCreativeId = getSelectedCustomCreativeId(adData);
+ const hasCreative = creativeID === selectedCustomCreativeId;
+ // First check there is a cppId for the ad that matches the `meta`.
+ if (!hasCPP && !hasCreative) {
+ // Then check if there is appMetadata for the custom creative ad.
+ return "cppAssetsMissing";
+ }
+ }
+ else {
+ if (!hasCPP) {
+ return "cppAssetsMissing";
+ }
+ }
+ return null;
+}
+// endregion
+//# sourceMappingURL=on-device-ad-fetch.js.map \ No newline at end of file