From bce557cc2dc767628bed6aac87301a1be7c5431b Mon Sep 17 00:00:00 2001 From: rxliuli Date: Tue, 4 Nov 2025 05:03:50 +0800 Subject: init commit --- .../tmp/src/common/ads/on-device-ad-fetch.js | 286 +++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-fetch.js (limited to 'node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-fetch.js') 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 -- cgit v1.2.3