import { isNothing, isSome } from "@jet/environment/types/optional"; import * as mediaAPI from "@apple-media-services/media-api"; import * as serverData from "../../foundation/json-parsing/server-data"; import * as mediaDataFetching from "../../foundation/media/data-fetching"; import * as urlBuilder from "../../foundation/media/url-builder"; import { Host, Parameters, Path, previewHosts, ProductPageParameters } from "../../foundation/network/url-constants"; import { configureMediaRequestForGameRecommendations } from "../../gameservicesui/src/foundation/media-api/requests/game-recommendations"; import * as client from "../../foundation/wrappers/client"; import { isAdPlacementEnabled } from "../ads/ad-common"; import * as appPromotionsCommon from "../app-promotions/app-promotions-common"; import * as privacySuppression from "../privacy/privacy-suppression"; import * as productPageVariants from "../product-page/product-page-variants"; import * as mediaRequestUtils from "./url-mapping-utils"; import { addDeveloperRequestProperties, makeDeveloperRequest } from "../developer/developer-request"; import { isReviewSummaryEnabled } from "../product-page/reviews"; import { isProductAccessibilityLabelsEnabled, shouldSuppressAccessibilityLabelsForAdamId, shouldSuppressAccessibilityLabelsForBundleId, } from "../accessibility/accessibility-common"; import { buildArticlePageRequest } from "../today/article-request"; import { makeRoutableArticlePageIntent } from "../../api/intents/routable-article-page-intent"; import { getLocale } from "../locale"; import { shouldUsePrerenderedIconArtwork } from "../content/content"; import { AppEventsAttributes } from "../../gameservicesui/src/foundation/media-api/requests/recommendation-request-types"; import { MediaApiConfiguration } from "../../gameservicesui/src/foundation/media-api/media-api-configuration"; const appsResourceAttributes = [ "description", "fileSizeByDevice", "messagesScreenshots", "minimumOSVersion", "privacyPolicyUrl", "promotionalText", "screenshotsByType", "supportsFunCamera", "supportURLForLanguage", "versionHistory", "videoPreviewsByType", "websiteUrl", "expectedReleaseDateDisplayFormat", "requirementsByDeviceFamily", "remoteControllerRequirement", "backgroundAssetsInfo", "backgroundAssetsInfoWithOptional", "supportsSharePlay", "installSizeByDeviceInBytes", "miniGamesDeepLink", "gameDisplayName", ]; // exporting this will be bad in the long run // adding for Catagories: Games results in cannot connect to app store. // should be removed when we stop handling media api urls in the router export function isMediaUrl(objectGraph, url) { const host = url.host; if (serverData.isNull(host)) { return false; } const mediaApiHosts = [ objectGraph.bag.mediaHost, objectGraph.bag.mediaEdgeHost(objectGraph), objectGraph.bag.mediaEdgeSearchHost, ]; for (const mediaApiHost of mediaApiHosts) { if (serverData.isNull(mediaApiHost)) { continue; } if (host.indexOf(mediaApiHost) !== -1) { return true; } } return false; } export function hrefToRoutableUrl(objectGraph, href, additionalQueryParams) { if (isNothing(href)) { return undefined; } if (preprocessor.GAMES_TARGET) { const request = new mediaAPI.Request(objectGraph, href); if (isSome(additionalQueryParams)) { for (const [key, value] of Object.entries(additionalQueryParams)) { request.addingQuery(key, value); } } return mediaAPI.buildURLFromRequest(new MediaApiConfiguration(objectGraph), request).toString(); } else { const request = new mediaDataFetching.Request(objectGraph, href).addingQueryValues(additionalQueryParams); return urlBuilder.buildURLFromRequest(objectGraph, request).toString(); } } // region Grouping export function mediaApiGroupingURLFromHref(objectGraph, href) { const attributes = [ "editorialArtwork", "editorialVideo", "isAppleWatchSupported", "requiredCapabilities", "minimumOSVersion", "expectedReleaseDateDisplayFormat", ]; if (objectGraph.appleSilicon.isSupportEnabled) { attributes.push("macRequiredCapabilities"); } if (objectGraph.client.isMac) { attributes.push("hasMacIPAPackage"); } if (objectGraph.bag.enableUpdatedAgeRatings) { attributes.push("ageRating"); } if (shouldUsePrerenderedIconArtwork(objectGraph)) { attributes.push("iconArtwork"); } // Catalyst apps have clickable 'get' button on 10.14. // - minimumOSVersion required to filter catalyst apps from groupings. const mediaApiRequest = new mediaDataFetching.Request(objectGraph, href) .includingAttributes(attributes) .includingAgeRestrictions() .includingMacOSCompatibleIOSAppsWhenSupported(); if (appPromotionsCommon.appEventsAreEnabled(objectGraph)) { mediaApiRequest.enablingFeature("appEvents"); mediaApiRequest.includingScopedAttributes("app-events", AppEventsAttributes); mediaApiRequest.includingScopedRelationships("app-events", ["app"]); mediaApiRequest.includingScopedRelationships("editorial-item-shelves", ["app-events"]); } const context = mediaDataFetching.defaultGroupingContextForClient(objectGraph); if (serverData.isDefinedNonNull(context)) { mediaApiRequest.addingQuery("contexts", context); } return mediaApiRequest; } // endregion // region Editorial Stories export function mappedMediaApiEditorialItemURL(objectGraph, url, isIncomingURL) { const pathComponents = url.pathComponents(); if (pathComponents.indexOf("story") < 0 && pathComponents.indexOf("editorialItem") < 0 && pathComponents.indexOf("viewEditorialItem") < 0 && url.host !== Host.spamBlockingExtensions && url.host !== Host.sharePlayApps && url.host !== Host.buddyOnboarding) { throw new Error(`Unable to map ${url.build()} to a media api url`); } let editorialItemId; if (url.host === Host.spamBlockingExtensions) { // Populate editorial item ID for spam blocking extensions. editorialItemId = objectGraph.props.asString("spamBlockingExtensionsEditorialItemID"); } else if (url.host === Host.sharePlayApps) { editorialItemId = objectGraph.bag.sharePlayAppsEditorialItemId; } else if (url.host === Host.buddyOnboarding) { editorialItemId = objectGraph.bag.buddyOnboardingEditorialItemId; } else { // Otherwise extract the ID from the URL. editorialItemId = extractedIdFromURL(url); } if (!serverData.isNumber(editorialItemId)) { throw new Error(`Unable to map ${url.build()} to a media api url`); } if (!serverData.isNumber(editorialItemId)) { throw new Error(`Unable to map ${url} to a media api url`); } const mediaApiRequest = buildArticlePageRequest(objectGraph, makeRoutableArticlePageIntent({ ...getLocale(objectGraph), id: editorialItemId, }), isIncomingURL); const editorialCardId = url.query[Parameters.editorialCardId]; if (serverData.isDefinedNonNullNonEmpty(editorialCardId)) { mediaApiRequest.withFilter("canvas:cardId", editorialCardId); } const previewParam = url.query[Parameters.preview]; if (previewHosts.has(url.host) && (previewParam === null || previewParam === void 0 ? void 0 : previewParam.length) > 0) { mediaApiRequest.addingQuery("preview", previewParam); mediaApiRequest.isStorePreviewRequest = true; } return mediaApiRequest.attributingTo(url.build()); } // endregion // region Product const entityIdRegex = /id([0-9]+)\/?$/i; export function extractedIdFromURL(url) { // 1. Look at the query parameter for the id let extractedId = url.query["id"]; // 2. Consider the last path component /id const pathName = url.pathname; if (serverData.isNull(extractedId) && (pathName === null || pathName === void 0 ? void 0 : pathName.length) > 0) { const match = entityIdRegex.exec(pathName); if (match && match.length > 1) { extractedId = match[1]; } } // 3. At this point, if the ID is not found, try looking for / instead, since this is what the last path // component looks like in QA now. if (!serverData.isNumber(extractedId)) { const pathComponents = url.pathComponents(); if (serverData.isDefinedNonNullNonEmpty(pathComponents)) { extractedId = pathComponents[pathComponents.length - 1]; } } return extractedId; } const productRedirectionTable = { "915249334": "1462947752", // rdar://49929244 (SAD: Redirect from the old 2nd party Shortcuts product page to the new 1st party page on iOS 13) }; export function mappedMediaApiProductURL(objectGraph, url, includeUnlistedApps = false) { var _a, _b; let productId = extractedIdFromURL(url); if (!serverData.isNumber(productId)) { throw new Error(`Unable to map ${url.build()} to a media api url`); } // Redirect to a different product if needed if (productId in productRedirectionTable) { productId = productRedirectionTable[productId]; } let resourceType = "apps"; let attributes = appsResourceAttributes; let relationships = []; if (preprocessor.GAMES_TARGET) { relationships = ["alternate-apps"]; } else { relationships = [ "customers-also-bought-apps", "reviews", "app-bundles", "top-in-apps", "related-editorial-items", "alternate-apps", ]; } const pathComponents = (_a = url.pathname) === null || _a === void 0 ? void 0 : _a.split("/"); const isBundle = isSome(pathComponents) && pathComponents.includes(Path.productBundle); if (isBundle) { resourceType = "app-bundles"; attributes = [ "screenshotsByType", "videoPreviewsByType", "minimumOSVersion", "requirementsByDeviceFamily", "remoteControllerRequirement", ]; relationships = ["apps", "reviews", "related-editorial-items"]; } if (!preprocessor.GAMES_TARGET) { relationships.push("developer-other-apps"); } if (objectGraph.bag.enablePrivacyNutritionLabels && !privacySuppression.shouldSuppressPrivacyInformationForAdamId(objectGraph, productId)) { attributes.push("privacy"); } if (isProductAccessibilityLabelsEnabled(objectGraph) && !shouldSuppressAccessibilityLabelsForAdamId(objectGraph, productId)) { attributes.push("accessibility"); } if (objectGraph.appleSilicon.isSupportEnabled) { attributes.push("macRequiredCapabilities"); } if (objectGraph.client.isMac) { attributes.push("hasMacIPAPackage"); } if (objectGraph.bag.enableUpdatedAgeRatings) { attributes.push("ageRating"); } if (shouldUsePrerenderedIconArtwork(objectGraph)) { attributes.push("iconArtwork"); } if (objectGraph.bag.enableLicenses) { attributes.push("licenses"); } // TODO Product Page should not request developer information if supportsArcade is false // We need to pipe the supportsArcade information from the sidepack, through the lockup, to the productPage action URL // until then, isArcade will always be null. // If no arcade affiliation is specified, or the product definitely isArcade, fetch the developer genre. const isArcade = serverData.asBoolean(url.query[Parameters.isArcade]); if (isArcade === null || isArcade) { attributes.push("minPlayers", "maxPlayers", "editorialVideo"); relationships.push("developer"); } if (objectGraph.client.isVision) { attributes.push("compatibilityControllerRequirement"); attributes.push("isHighMotion"); if (objectGraph.featureFlags.isEnabled("spatial_controllers_2025A")) { attributes.push("spatialControllerRequirement"); } } if (objectGraph.bag.enableSellerInfo) { attributes.push("sellerInfo"); } // Extends the API to include Internet Content Provider Info if (objectGraph.bag.enableSellerICPAnnotation) { attributes.push("internetContentProviderInfo"); } // Extends the API to include supported Game Center features. if (objectGraph.bag.gameCenterExtendSupportedFeatures) { attributes.push("supportedGameCenterFeatures"); } // Extends the API to include Review Summary if (isReviewSummaryEnabled(objectGraph)) { relationships.push("review-summary"); } if (preprocessor.GAMES_TARGET) { attributes.push("gamesUrl"); attributes.push("isEligibleForGamesApp"); } const mediaApiRequest = new mediaDataFetching.Request(objectGraph) .withIdOfType(productId, resourceType) .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph)) .includingAgeRestrictions() .includingRelationships(relationships) .includingAttributes(attributes) .includingMacOSCompatibleIOSAppsWhenSupported(true) .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph)) .includingViews(["categorizations"]); if (!preprocessor.GAMES_TARGET) { mediaApiRequest.addingRelationshipLimit("reviews", mediaDataFetching.defaultProductPageReviewsLimitForClient(objectGraph)); } if (preprocessor.GAMES_TARGET) { mediaApiRequest.includingScopedRelationships("apps", ["activities", "challenges"]); mediaApiRequest.includingViews(["customers-also-bought-games", "developer-other-games"]); mediaApiRequest.enablingFeature("unlistedApps"); if (objectGraph.debugSettings.enableHighlightsFromProductPageResponse) { configureMediaRequestForGameRecommendations(mediaApiRequest); } } if (((_b = url.query[Parameters.productVariantID]) === null || _b === void 0 ? void 0 : _b.length) > 0) { mediaApiRequest.addingQuery(Parameters.productVariantID, url.query[Parameters.productVariantID]); } // If a `minExternalVersionId` is specified, pass this through to the MAPI request const minExternalVersionId = url.query[ProductPageParameters.minExternalVersionId]; if (serverData.isDefinedNonNull(minExternalVersionId)) { mediaApiRequest.addingQuery(ProductPageParameters.minExternalVersionId, minExternalVersionId); } // Disabling for bundles due to rdar://78542145 ([REG] Pre-Prod JS: SkySeed: App bundle product page does not load) if (appPromotionsCommon.appEventsAreEnabled(objectGraph) && !isBundle) { if (!appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) { mediaApiRequest.includingScopedRelationships("apps", ["app-events"]); } mediaApiRequest.includingScopedAttributes("app-events", AppEventsAttributes); mediaApiRequest.includingScopedAvailableIn("app-events", ["future"]); } if (appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) { mediaRequestUtils.configureContingentItemsForMediaRequest(mediaApiRequest); } if (appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) { mediaRequestUtils.configureOfferItemsForMediaRequest(mediaApiRequest); } if (isAdPlacementEnabled(objectGraph, "productPageYMAL")) { mediaApiRequest.enablingFeature("cabAdSupport"); } if (includeUnlistedApps) { mediaApiRequest.enablingFeature("unlistedApps"); } if (!appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) { mediaApiRequest.includingRelationships(["merchandised-in-apps"]); } return mediaApiRequest.attributingTo(url.build()); } // endregion // region Developer export function mediaApiDeveloperURLFromHref(objectGraph, href) { const mediaApiRequest = new mediaDataFetching.Request(objectGraph, href); return addDeveloperRequestProperties(objectGraph, mediaApiRequest); } export function mappedMediaApiDeveloperURL(objectGraph, url) { const developerId = extractedIdFromURL(url); if (!serverData.isNumber(developerId)) { throw new Error(`Unable to map ${url.build()} to a media api url`); } return makeDeveloperRequest(objectGraph, developerId); } // endregion // region Charts /** * Creates a Media API request for one or more charts using provided critera. * @param objectGraph The App Store object graph. * @param genreId The genre for the chart request. * @param charts The list of charts for the request. * @param ageBandId The age band to include in the request. * @param clientIdentifier The identifier of the current client. * @returns A Media API request for one or more charts using provided critera. */ export function mediaApiChartRequestForGenre(objectGraph, genreId, charts, ageBandId = null, clientIdentifier = objectGraph.host.clientIdentifier) { const attributes = ["isAppleWatchSupported", "requiredCapabilities", "minimumOSVersion"]; if (objectGraph.appleSilicon.isSupportEnabled) { attributes.push("macRequiredCapabilities"); } if (objectGraph.client.isMac) { attributes.push("hasMacIPAPackage"); } const mediaApiRequest = new mediaDataFetching.Request(objectGraph) .forType("charts") .addingQuery("genre", genreId) .includingAttributes(attributes) .addingQuery("types", "apps") .includingMacOSCompatibleIOSAppsWhenSupported(true) .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph)) .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph)); if (serverData.isDefinedNonNullNonEmpty(charts)) { mediaApiRequest.addingQuery(Parameters.charts, charts); } if (clientIdentifier === client.watchIdentifier) { mediaApiRequest.addingContext("watch"); } else if (clientIdentifier === client.messagesIdentifier) { mediaApiRequest.addingContext("messages"); } if (objectGraph.appleSilicon.isSupportEnabled) { mediaApiRequest.includingAdditionalPlatforms(["iphone", "ipad"]); } if (isSome(ageBandId)) { mediaApiRequest.addingQuery(Parameters.ages, ageBandId); } return mediaApiRequest; } export function mediaApiChartsRequest(objectGraph, genreId, ageBandId = null) { let request = new mediaDataFetching.Request(objectGraph) .forType("charts") .addingQuery("types", "apps") .addingQuery("genre", genreId) .includingMacOSCompatibleIOSAppsWhenSupported(true); if (ageBandId) { request = request.addingQuery("ages", ageBandId); } if (objectGraph.appleSilicon.isSupportEnabled) { request = request.includingAdditionalPlatforms(["iphone", "ipad"]); } return request; } // endregion // region Lookups export function lookupURLForProductId(objectGraph, productId, isPurchasesProduct, productVariantID, isBundle, includeUnlistedApps) { const attributes = appsResourceAttributes; if (objectGraph.bag.enablePrivacyNutritionLabels && !privacySuppression.shouldSuppressPrivacyInformationForAdamId(objectGraph, productId)) { attributes.push("privacy"); // The "web" client needs the full privacy information in the initial page load; // unlike other platforms, it does not load the full details on a separate page if (objectGraph.client.isWeb) { attributes.push("privacyDetails"); } } if (isProductAccessibilityLabelsEnabled(objectGraph) && !shouldSuppressAccessibilityLabelsForAdamId(objectGraph, productId)) { attributes.push("accessibility"); // The web client requires complete accessibility information on initial page load. // Unlike other platforms that load full details on separate pages, // the web client displays this information in a modal overlay. if (objectGraph.client.isWeb) { attributes.push("accessibilityDetails"); } } if (objectGraph.appleSilicon.isSupportEnabled) { attributes.push("macRequiredCapabilities"); } if (objectGraph.client.isMac) { attributes.push("hasMacIPAPackage"); } if (objectGraph.client.isVision) { attributes.push("compatibilityControllerRequirement"); attributes.push("isHighMotion"); if (objectGraph.featureFlags.isEnabled("spatial_controllers_2025A")) { attributes.push("spatialControllerRequirement"); } } if (objectGraph.bag.enableUpdatedAgeRatings) { attributes.push("ageRating"); } if (shouldUsePrerenderedIconArtwork(objectGraph)) { attributes.push("iconArtwork"); } if (objectGraph.bag.enableLicenses) { attributes.push("licenses"); } // Extends the API to include supported Game Center features. if (objectGraph.bag.gameCenterExtendSupportedFeatures) { attributes.push("supportedGameCenterFeatures"); } const mediaApiRequest = new mediaDataFetching.Request(objectGraph) .withIdOfType(productId, "apps") .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph)) .includingAttributes(attributes) .includingRelationships([ "developer", "customers-also-bought-apps", "developer-other-apps", "reviews", "app-bundles", "top-in-apps", "related-editorial-items", "alternate-apps", ]) .addingRelationshipLimit("reviews", mediaDataFetching.defaultProductPageReviewsLimitForClient(objectGraph)) .includingMacOSCompatibleIOSAppsWhenSupported(true) .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph)); if (objectGraph.client.isWeb) { // Ensure that the "web" client fully hydrates all PDP shelves mediaApiRequest .includingScopedSparseLimit("apps:customers-also-bought-apps", 40) .includingScopedSparseLimit("apps:developer-other-apps", 40) .includingScopedSparseLimit("apps:related-editorial-items", 40); // Ensures web can render the DSA information in the annotation shelf if (objectGraph.bag.enableSellerInfo) { attributes.push("sellerInfo"); } } if (isPurchasesProduct) { mediaApiRequest.addingQuery("availability", "redownload"); } if (isSome(productVariantID)) { mediaApiRequest.addingQuery(Parameters.productVariantID, productVariantID); } if (includeUnlistedApps) { mediaApiRequest.enablingFeature("unlistedApps"); } // Disabling for bundles due to rdar://78542145 ([REG] Pre-Prod JS: SkySeed: App bundle product page does not load) if (appPromotionsCommon.appEventsAreEnabled(objectGraph) && !isBundle) { if (!appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) { mediaApiRequest.includingScopedRelationships("apps", ["app-events"]); } mediaApiRequest.includingScopedAttributes("app-events", AppEventsAttributes); mediaApiRequest.includingScopedAvailableIn("app-events", ["future"]); } if (appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) { mediaRequestUtils.configureContingentItemsForMediaRequest(mediaApiRequest); } if (appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) { mediaRequestUtils.configureOfferItemsForMediaRequest(mediaApiRequest); } if (!appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) { mediaApiRequest.includingRelationships(["merchandised-in-apps"]); } // Extends the API to include Review Summary if (isReviewSummaryEnabled(objectGraph)) { mediaApiRequest.includingRelationships(["review-summary"]); } if (isAdPlacementEnabled(objectGraph, "productPageYMAL")) { mediaApiRequest.enablingFeature("cabAdSupport"); } if (objectGraph.client.isMac) { attributes.push("hasMacIPAPackage"); } return mediaApiRequest; } export function lookupURLForBundleId(objectGraph, bundleId, productVariantID, includeUnlistedApps) { const attributes = appsResourceAttributes; if (objectGraph.bag.enablePrivacyNutritionLabels && !privacySuppression.shouldSuppressPrivacyInformationForBundleId(objectGraph, bundleId)) { attributes.push("privacy"); } if (isProductAccessibilityLabelsEnabled(objectGraph) && !shouldSuppressAccessibilityLabelsForBundleId(objectGraph, bundleId)) { attributes.push("accessibility"); } if (objectGraph.appleSilicon.isSupportEnabled) { attributes.push("macRequiredCapabilities"); } if (objectGraph.client.isMac) { attributes.push("hasMacIPAPackage"); } if (objectGraph.client.isVision) { attributes.push("compatibilityControllerRequirement"); attributes.push("isHighMotion"); if (objectGraph.featureFlags.isEnabled("spatial_controllers_2025A")) { attributes.push("spatialControllerRequirement"); } } if (objectGraph.bag.enableUpdatedAgeRatings) { attributes.push("ageRating"); } if (shouldUsePrerenderedIconArtwork(objectGraph)) { attributes.push("iconArtwork"); } if (objectGraph.bag.enableLicenses) { attributes.push("licenses"); } const mediaApiRequest = new mediaDataFetching.Request(objectGraph) .forType("apps") .withFilter("bundleId", bundleId) .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph)) .includingRelationships([ "developer", "customers-also-bought-apps", "developer-other-apps", "reviews", "app-bundles", "top-in-apps", "related-editorial-items", "alternate-apps", ]) .includingAttributes(attributes) .addingRelationshipLimit("reviews", mediaDataFetching.defaultProductPageReviewsLimitForClient(objectGraph)) .includingMacOSCompatibleIOSAppsWhenSupported(true) .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph)); if (isSome(productVariantID)) { mediaApiRequest.addingQuery(Parameters.productVariantID, productVariantID); } if (isAdPlacementEnabled(objectGraph, "productPageYMAL")) { mediaApiRequest.enablingFeature("cabAdSupport"); } if (includeUnlistedApps) { mediaApiRequest.enablingFeature("unlistedApps"); } if (appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) { mediaApiRequest.includingViews(["standalone-merchandised-in-apps"]); } else { mediaApiRequest.includingRelationships(["merchandised-in-apps"]); } if (objectGraph.client.isMac) { attributes.push("hasMacIPAPackage"); } return mediaApiRequest; } export function mediaApiProductSeeAllRequest(objectGraph, productId, relationship) { const mediaApiRequest = new mediaDataFetching.Request(objectGraph) .withIdOfType(productId, "apps") .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph)) .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph)) .includingRelationships([relationship, "apps", "developer"]) .includingScopedSparseLimit(`apps:${relationship}`, 20); if (appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) { mediaRequestUtils.configureContingentItemsForMediaRequest(mediaApiRequest); } if (appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) { mediaRequestUtils.configureOfferItemsForMediaRequest(mediaApiRequest); } return mediaApiRequest; } // endregion //# sourceMappingURL=url-mapping.js.map