summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/builders
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/builders')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/builders/additional-page-requirement-util.js36
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/builders/pagination.js51
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/builders/routing.js321
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping-utils.js42
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping.js627
5 files changed, 1077 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/builders/additional-page-requirement-util.js b/node_modules/@jet-app/app-store/tmp/src/common/builders/additional-page-requirement-util.js
new file mode 100644
index 0000000..242d28c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/builders/additional-page-requirement-util.js
@@ -0,0 +1,36 @@
+import { isNothing } from "@jet/environment";
+import { allMixed } from "../../foundation/util/promise-util";
+export async function fetchPageWithAdditionalPageRequirements(objectGraph, primaryPageRequirement, additionalRequirements) {
+ var _a;
+ // Data Frankensteining Pt 1. - Additional Page Requirements:
+ // Additional requests that are fired *IN PARALLEL* with page request to fetch additional data needed to render page.
+ // Why are these not included in a singular page fetch? I don't know.
+ // This builder API is used to request data that will *definitely* be used, regardless of the content of initial page fetch's response.
+ const additionalRequirementKeys = Object.keys(additionalRequirements);
+ const additionalPageRequirementPromises = additionalRequirementKeys.map(async (key) => {
+ return await additionalRequirements[key];
+ });
+ const allPageRequirements = await allMixed([primaryPageRequirement, ...additionalPageRequirementPromises]);
+ const primaryPageDataResult = allPageRequirements[0];
+ const primaryPageData = primaryPageDataResult.value;
+ if (!primaryPageDataResult.success || isNothing(primaryPageData)) {
+ throw (_a = primaryPageDataResult.error) !== null && _a !== void 0 ? _a : new Error("Unknown primaryPageData error");
+ }
+ const additionalData = {};
+ const additionalPageRequirements = allPageRequirements.slice(1);
+ for (const [index, additionalPageRequirement] of additionalPageRequirements.entries()) {
+ const additionalRequirementKey = additionalRequirementKeys[index];
+ if (additionalPageRequirement.success) {
+ additionalData[additionalRequirementKey] = additionalPageRequirement.value;
+ }
+ else {
+ additionalData[additionalRequirementKey] = null;
+ objectGraph.console.log(`Builder - failed to fetch additionalPageRequirement ${additionalRequirementKey}, reason: ${additionalPageRequirement.error}`);
+ }
+ }
+ return {
+ primaryPageData,
+ additionalData,
+ };
+}
+//# sourceMappingURL=additional-page-requirement-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/builders/pagination.js b/node_modules/@jet-app/app-store/tmp/src/common/builders/pagination.js
new file mode 100644
index 0000000..5f11fc7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/builders/pagination.js
@@ -0,0 +1,51 @@
+/**
+ * Created by km on 10/5/16.
+ */
+/// The suggested maximum items to load per page.
+export const suggestedMaxPerPage = 20;
+/**
+ * Returns a single page of lookup data from a page token.
+ * @param pageToken The page token to extract the ids from.
+ * @returns An array of zero or more ids to lookup.
+ */
+export function getDataForLookup(objectGraph, pageToken) {
+ if (!pageToken || !pageToken.remainingContent) {
+ return [];
+ }
+ return pageToken.remainingContent.slice(0, pageToken.maxPerPage);
+}
+/**
+ * Returns a page token containing the ids to fetch for the next page of data.
+ * @param pageToken The page token to advance.
+ * @param count The number of items to get, or all items if not specified
+ * @returns A new page token.
+ */
+export function nextPageToken(objectGraph, pageToken, count) {
+ const nextToken = { ...pageToken };
+ if (pageToken && pageToken.remainingContent) {
+ const nextContent = nextDataRange(objectGraph, pageToken.remainingContent, pageToken.maxPerPage, count);
+ nextToken.remainingContent = nextContent;
+ }
+ return nextToken;
+}
+/**
+ * Returns a subset of the content from start to the end of the array
+ * @param ids The array of IDs from which to get the sub array
+ * @param start The starting index
+ * @param count The number of items to get, or all items if not specified
+ * @returns An array of all ids from start until the end of the array
+ */
+export function nextDataRange(objectGraph, dataArray, start, count) {
+ return dataArray.slice(start, count !== null && count !== void 0 ? count : dataArray.length);
+}
+export function createMediaPageToken(objectGraph, remaining, url, nextHref = null, pageInformation, locationTracker) {
+ return {
+ remainingContent: remaining,
+ maxPerPage: suggestedMaxPerPage,
+ highestOrdinal: 0,
+ url: url,
+ metricsPageInformation: pageInformation,
+ metricsLocationTracker: locationTracker,
+ };
+}
+//# sourceMappingURL=pagination.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/builders/routing.js b/node_modules/@jet-app/app-store/tmp/src/common/builders/routing.js
new file mode 100644
index 0000000..1655d6d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/builders/routing.js
@@ -0,0 +1,321 @@
+/**
+ * Created by km on 11/17/16.
+ */
+/* This file DOES NOT target nodejs usage. */
+import { isNothing, isSome } from "@jet/environment";
+import { makeMetatype } from "@jet/environment/util/metatype";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { Parameters } from "../../foundation/network/url-constants";
+import * as routingComponents from "../../foundation/routing/routing-components";
+import * as objects from "../../foundation/util/objects";
+import { allOptional, tryAwait } from "../../foundation/util/promise-util";
+import * as productPageCommon from "../product-page/product-page-common";
+// region Routers
+export const pageRouter = makeMetatype("app-store:page-router");
+export class PageRouter {
+ constructor() {
+ this.registeredBuilders = new Set();
+ this.pageRouter = new routingComponents.UrlRouter();
+ this.shelfRouter = new routingComponents.UrlRouter();
+ this.paginationRouter = new routingComponents.UrlRouter();
+ // endregion
+ }
+ /**
+ * Register a page builder containing routes and related handlers.
+ * @param objectGraph
+ * @param {Builder} pageBuilder to register.
+ *
+ * @note Although one router can serve the page, shelf, and pagination routes, we maintain
+ * three instances dedicated to each instead to avoid iterating through each other's routes
+ * during URL matching.
+ */
+ registerPageBuilder(objectGraph, pageBuilder) {
+ if (this.registeredBuilders.has(pageBuilder.builderClass)) {
+ throw new Error(`routing: Registering duplicate builderClass ${pageBuilder.builderClass}`);
+ }
+ this.registeredBuilders.add(pageBuilder.builderClass);
+ this.pageRouter.associate(pageBuilder.pageRoute(objectGraph), pageBuilder);
+ this.shelfRouter.associate(pageBuilder.shelfRoute(objectGraph), pageBuilder);
+ this.paginationRouter.associate(pageBuilder.paginationRoute(objectGraph), pageBuilder);
+ }
+ /**
+ * Register a shelf builder containing routes and related handlers.
+ * @param objectGraph
+ * @param {Builder} shelfBuilder to register.
+ *
+ * @note Although one router can serve the page, shelf, and pagination routes, we maintain
+ * three instances dedicated to each instead to avoid iterating through each other's routes
+ * during URL matching.
+ */
+ registerShelfBuilder(objectGraph, shelfBuilder) {
+ if (this.registeredBuilders.has(shelfBuilder.builderClass)) {
+ throw new Error(`routing: Registering duplicate builderClass ${shelfBuilder.builderClass}`);
+ }
+ this.registeredBuilders.add(shelfBuilder.builderClass);
+ this.shelfRouter.associate(shelfBuilder.shelfRoute(objectGraph), shelfBuilder);
+ }
+ /**
+ * Register a pagination builder containing routes and related handlers.
+ * @param objectGraph
+ * @param {Builder} paginationBuilder to register.
+ *
+ * @note Although one router can serve the page, shelf, and pagination routes, we maintain
+ * three instances dedicated to each instead to avoid iterating through each other's routes
+ * during URL matching.
+ */
+ registerPaginationBuilder(objectGraph, paginationBuilder) {
+ if (this.registeredBuilders.has(paginationBuilder.builderClass)) {
+ throw new Error(`routing: Registering duplicate builderClass ${paginationBuilder.builderClass}`);
+ }
+ this.registeredBuilders.add(paginationBuilder.builderClass);
+ this.paginationRouter.associate(paginationBuilder.paginationRoute(objectGraph), paginationBuilder);
+ }
+ // endregion
+ // region Exported API
+ /**
+ * Determine the type of destination for given `url`.
+ * @param {string} url The url to fetch `flowPage` for.
+ * @returns {FlowPage} The FlowPage for given `url`
+ */
+ fetchFlowPage(url) {
+ const routerResult = this.pageRouter.routedObjectForUrl(url);
+ if (!routerResult.object) {
+ return `unknown`;
+ }
+ const builder = routerResult.object;
+ // Product URLs can go to `writeReview` via deep links in iOS, so the page type may not necessary be `product`
+ // So check that they are not routed to the reviews sections
+ if (builder.builderClass === "ProductBuilder" &&
+ routerResult.parameters[Parameters.action] !== productPageCommon.reviewsAction &&
+ routerResult.parameters[Parameters.action] !== productPageCommon.writeReviewAction) {
+ return "product";
+ }
+ return builder.pageType();
+ }
+ /**
+ * Fetch the contents of a page.
+ * @param objectGraph
+ * @param url The URL to determine what kind of page to load.
+ * @param pageType The meta type of the expected page, e.g. `models.ProductPage`.
+ * This is used to perform a runtime type check to prevent hard to track bugs.
+ * @returns A promise which will resolve into a page of `pageType`.
+ */
+ async fetchPage(objectGraph, url, pageType) {
+ return await this.fetchAction(objectGraph, url, null, false).then(async (pageAction) => {
+ return await new Promise((resolve, reject) => {
+ if (!pageAction) {
+ throw new Error(`Promise resolved to null action for: ${url}`);
+ }
+ if (pageAction.actionClass === "FlowAction") {
+ const pageData = pageAction.pageData;
+ if (!objects.isTypeOf(pageData, pageType)) {
+ // As we have no data of the correct type, check if we have a redirect
+ const pageUrl = pageAction.pageUrl;
+ const isRedirectingToSelf = pageUrl === url;
+ if (pageUrl && !isRedirectingToSelf) {
+ // Re-process this fetch with the new URL.
+ this.fetchPage(objectGraph, pageUrl, pageType)
+ .then((page) => {
+ resolve(page);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ return;
+ }
+ reject(new Error(`pageData is not expected type ${pageType.name}, ${JSON.stringify(pageData)}`));
+ return;
+ }
+ resolve(pageData);
+ return;
+ }
+ else if (pageAction.actionClass === "TabChangeAction") {
+ const tabChangeAction = pageAction;
+ if (tabChangeAction.actions.length === 1 &&
+ tabChangeAction.actions[0].actionClass === "FlowAction") {
+ const pageData = tabChangeAction.actions[0].pageData;
+ if (!objects.isTypeOf(pageData, pageType)) {
+ reject(new Error(`pageData is not expected type ${pageType.name}, ${JSON.stringify(pageData)}`));
+ return;
+ }
+ resolve(pageData);
+ return;
+ }
+ }
+ reject(new Error("Action is not a flowAction or a tabChangeAction that contains a single flowAction."));
+ });
+ });
+ }
+ /**
+ * Fetch an action for a given url, including page data if there is any to return.
+ * @param url The URL to determine which actions to take.
+ * @param referrerData Optional incoming deep link referrer data.
+ * @param isIncomingURL Whether the fetch is for deep link.
+ * @param visitedUrls Optional set of URLs already visited in this redirect chain to prevent cycles.
+ * @returns A promise that will resolve to an Action.
+ */
+ async fetchAction(objectGraph, url, referrerData, isIncomingURL, visitedUrls = new Set()) {
+ var _a;
+ const routerResult = this.pageRouter.routedObjectForUrl(url);
+ if (!routerResult.object) {
+ // Urls fed into `fetchAction` can redirect to supported routes, thus we attempt to
+ // chain the redirect and pipe it back into `fetchAction`.
+ return await this.redirectAndRefetchActionIfPossible(objectGraph, routerResult.normalizedUrl, visitedUrls);
+ }
+ const builder = routerResult.object;
+ return await builder.handlePage(objectGraph, routerResult.normalizedUrl, (_a = routerResult.parameters) !== null && _a !== void 0 ? _a : {}, routerResult.matchedRuleIdentifier, referrerData, isIncomingURL);
+ }
+ /**
+ * Fetch the next page of a page. The returned page will have its `shelves`, `nextPage` properties set.
+ * @param pageToken The page token to use.
+ * @returns A promise that will resolve to a page.
+ */
+ async fetchMoreOfPage(objectGraph, pageToken) {
+ const url = pageToken.url;
+ if (!url) {
+ throw new Error("Page token missing required `url` property");
+ }
+ const routerResult = this.paginationRouter.routedObjectForUrl(url);
+ if (!routerResult.object) {
+ throw new Error(`fetchMoreOfPage: Unhandled pagination url: ${url}`);
+ }
+ const builder = routerResult.object;
+ return await builder.handlePagination(objectGraph, routerResult.normalizedUrl, routerResult.parameters, routerResult.matchedRuleIdentifier, pageToken);
+ }
+ /**
+ * Fetch the contents of multiple shelves, producing a shelf batch object.
+ * @param requests A hash of request keys to URL strings.
+ * @returns A promise that will resolve into multiple shelves.
+ */
+ async fetchShelves(objectGraph, requests) {
+ // eslint-disable-next-line promise/param-names
+ const shelfRequestKeys = Object.keys(requests);
+ const shelfUrls = shelfRequestKeys.map((key) => requests[key]);
+ // Guaranteed never to throw an error
+ const shelfForUrl = async (shelfUrl) => {
+ if (isNothing(shelfUrl)) {
+ throw new Error(`fetchShelves: Null shelf url`);
+ }
+ const routerResult = this.shelfRouter.routedObjectForUrl(shelfUrl);
+ if (isNothing(routerResult.object)) {
+ throw new Error(`fetchShelves: Unhandled shelf url: ${shelfUrl}`);
+ }
+ const builder = routerResult.object;
+ const shelf = await builder.handleShelf(objectGraph, routerResult.normalizedUrl, routerResult.parameters, routerResult.matchedRuleIdentifier);
+ return shelf;
+ };
+ // Map each URL to a promise and make them all optional
+ const resolvedShelves = await allOptional(shelfUrls.map(shelfForUrl));
+ const batch = {
+ shelves: {},
+ errors: {},
+ };
+ for (const [index, resolvedShelf] of resolvedShelves.entries()) {
+ const requestKey = shelfRequestKeys[index];
+ if (isNothing(requestKey)) {
+ // This should never happen as we create a shelf request for each key
+ continue;
+ }
+ if (resolvedShelf.success) {
+ batch.shelves[requestKey] = resolvedShelf.value;
+ }
+ else {
+ // For rejected promises, access the error property
+ batch.errors[requestKey] = resolvedShelf.error;
+ }
+ }
+ const hasResults = Object.keys(batch.shelves).length > 0;
+ if (hasResults) {
+ return batch;
+ }
+ else {
+ const messages = Object.keys(batch.errors).map((key) => batch.errors[key].message);
+ if (messages.length === 0) {
+ throw new Error(`Could not load any shelves: ${JSON.stringify(requests)}`);
+ }
+ else {
+ throw new Error(messages.join("\n"));
+ }
+ }
+ }
+ // endregion
+ // region Redirect
+ /**
+ * Given an `url`, it will fire a GET request and then try to refetch the action for url if
+ * it was redirected. This is used for urls that are unknown, but redirects to a supported url.
+ *
+ * @param objectGraph
+ * @param {string} url to attempt to redirect with. This is expected to be normalized.
+ * @param {PromiseResolveFunction<models.Action>} resolve function to resolve promise to an `Action`.
+ * @param {PromiseRejectFunction} reject function to reject promise to `Error`.
+ * @param {Set<string>} visitedUrls Set of URLs already visited in this redirect chain to prevent cycles.
+ */
+ async redirectAndRefetchActionIfPossible(objectGraph, url, visitedUrls = new Set()) {
+ // Check for redirect cycle
+ const urlString = url.toString();
+ if (visitedUrls.has(urlString)) {
+ throw new Error(`redirectAndRefetchActionIfPossible: Redirect cycle detected for URL: ${urlString}`);
+ }
+ // Add current URL to visited set
+ visitedUrls.add(urlString);
+ const fetchResult = await tryAwait(objectGraph.network.fetch({
+ url: urlString,
+ method: "GET",
+ }));
+ if (!fetchResult.success) {
+ throw new Error(`redirectAndRefetchActionIfPossible: Failed to fetch page at url: ${url}`);
+ }
+ const response = fetchResult.value;
+ if (this.hasGotoURLInResponse(objectGraph, response)) {
+ // If the response body contains a goto url fail silently as we will natively try to load the URL.
+ return new models.BlankAction();
+ }
+ else if (response.status === 200 && response.redirected && response.url) {
+ // Only route if this was a redirect; otherwise we don't know how to handle this page
+ return await this.fetchAction(objectGraph, response.url, null, false, visitedUrls);
+ }
+ else {
+ throw new Error(`redirectAndRefetchActionIfPossible: Unhandled page url: ${url}`);
+ }
+ }
+ /**
+ * Given a `response`, it will check to see if the response body contains a goto URL
+ * A goto URL is returned by the server in a plist as a way of navigating to a new page
+ * AMSURLSession which is the native response handler looks for these URLs and tries to load them natively
+ * @param objectGraph
+ * @param {FetchResponse} the response body from the server
+ */
+ hasGotoURLInResponse(objectGraph, response) {
+ if (serverData.isNullOrEmpty(response.body)) {
+ return false;
+ }
+ try {
+ const responseData = serverData.asJSONValue(objectGraph.plist.parse(response.body));
+ const responseActionKind = serverData.asString(responseData, "action.kind");
+ const responseActionUrl = serverData.asString(responseData, "action.url");
+ if (responseActionKind === "Goto" && isSome(responseActionUrl)) {
+ return true;
+ }
+ return false;
+ }
+ catch {
+ return false;
+ }
+ }
+ // endregion
+ // region Testing
+ /**
+ * Fetch the builder for a given url.
+ * @param url The URL that the builder is associated with.
+ * @returns A builder if any was associated, or null if no match was found.
+ * @note FOR TESTING ONLY.
+ */
+ fetchBuilder(url) {
+ const routerResult = this.pageRouter.routedObjectForUrl(url) ||
+ this.shelfRouter.routedObjectForUrl(url) ||
+ this.paginationRouter.routedObjectForUrl(url);
+ return routerResult ? routerResult.object : null;
+ }
+}
+//# sourceMappingURL=routing.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping-utils.js b/node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping-utils.js
new file mode 100644
index 0000000..5d8e1ac
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping-utils.js
@@ -0,0 +1,42 @@
+/**
+ * Adds the required url query params to a media API request for Contingent Items
+ */
+export function configureContingentItemsForMediaRequest(mediaApiRequest) {
+ mediaApiRequest.includingScopedRelationships("contingent-items", ["trunk-app", "branch-app", "branch"]);
+ mediaApiRequest.includingViews(["events-and-offers", "standalone-merchandised-in-apps"]);
+ mediaApiRequest.includingScopedAttributes("contingent-items", ["name", "subtitle", "additionalTerms"]);
+ mediaApiRequest.includingMetaKeys("contingent-items:branch", ["contingentItemOffer"]);
+ mediaApiRequest.includingMetaKeys("contingent-items:association.trunks", ["useTrunkArtwork"]);
+ mediaApiRequest.includingAssociateKeys("contingent-items", ["trunks"]);
+}
+/**
+ * Adds the required url query params to a groupings API request for Contingent Items
+ */
+export function configureContingentItemsForGroupingRequest(mediaApiRequest) {
+ mediaApiRequest.includingScopedRelationships("contingent-items", ["trunk-app", "branch-app", "branch"]);
+ mediaApiRequest.includingScopedAttributes("contingent-items", ["name", "subtitle", "additionalTerms"]);
+ mediaApiRequest.includingMetaKeys("contingent-items:branch", ["contingentItemOffer"]);
+ mediaApiRequest.includingMetaKeys("contingent-items:association.trunks", ["useTrunkArtwork"]);
+ mediaApiRequest.includingAssociateKeys("contingent-items", ["trunks"]);
+}
+export function configureTagsForMediaRequest(mediaApiRequest) {
+ mediaApiRequest.enablingFeature("tags");
+ mediaApiRequest.includingScopedAttributes("tags", ["editorialArtwork"]);
+ mediaApiRequest.addingQuery("associate[tags]", "apps");
+ mediaApiRequest.includingScopedRelationships("editorial-elements", ["contents", "see-all-contents"]);
+}
+/**
+ * Adds the required url query params to a media API request for Winback Offer items
+ */
+export function configureOfferItemsForMediaRequest(mediaApiRequest) {
+ mediaApiRequest.includingScopedRelationships("offer-items", ["salables"]);
+ mediaApiRequest.includingScopedAttributes("offer-items", [
+ "title",
+ "subtitle",
+ "additionalTerms",
+ "redemptionExpirationDate",
+ ]);
+ mediaApiRequest.includingMetaKeys("offer-items:salables", ["discountOffer"]);
+ mediaApiRequest.includingKindsKeys("offer-items", ["winback"]);
+}
+//# sourceMappingURL=url-mapping-utils.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping.js b/node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping.js
new file mode 100644
index 0000000..fdaa85b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping.js
@@ -0,0 +1,627 @@
+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 <rdar://problem/39578622> 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");
+ }
+ // <rdar://problem/56198088> 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<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 /<id> 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 <rdar://problem/49763626> 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 \ No newline at end of file