summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-common.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-common.js')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-common.js441
1 files changed, 441 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-common.js b/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-common.js
new file mode 100644
index 0000000..440d350
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-common.js
@@ -0,0 +1,441 @@
+/**
+ * Created by ls on 5/15/18.
+ */
+import { Color, isSome } from "@jet/environment";
+import * as models from "../../api/models/index";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { isDefinedNonNull } from "../../foundation/json-parsing/server-data";
+import * as mediaFetching from "../../foundation/media/data-fetching";
+import * as mediaNetwork from "../../foundation/media/network";
+import * as mediaRelationships from "../../foundation/media/relationships";
+import { Path, Protocol } from "../../foundation/network/url-constants";
+import * as urls from "../../foundation/network/urls";
+import * as artworkBuilder from "../content/artwork/artwork";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as productPageVariants from "../product-page/product-page-variants";
+import { MetricsIdentifierType } from "../../foundation/metrics/metrics-identifiers-cache";
+import { named } from "../../foundation/util/color-util";
+import { makeRoutableArcadeSeeAllPageIntent } from "../../api/intents/routable-arcade-see-all-page-intent";
+import { getPlatform } from "../preview-platform";
+import { getLocale } from "../locale";
+import { makeArcadeSeeAllCanonicalUrl } from "./arcade-see-all-routing";
+import { shouldUsePrerenderedIconArtwork } from "../content/content";
+// endregion
+// region Arcade Navigation Actions
+/**
+ * Creates a flow action for going to the page to see all Arcade games.
+ * Defaults to sorting by release date (descending)
+ * @param {ArcadeSeeAllGamesPageSort} sort The order which the games in response will be sorted in.
+ * @param metricsPageInformation
+ * @param metricsLocationTracker
+ * @returns {FlowAction} Flow action to Arcade see all games page.
+ */
+export function seeAllArcadeGamesPageFlowAction(objectGraph, sort = "releaseDate", metricsPageInformation, metricsLocationTracker, title = undefined, id = undefined, idType = undefined, targetType = "button") {
+ const seeAllGamesUrl = urls.URL.fromComponents(Protocol.internal, null, `/${Path.arcadeSeeAllGames}`, {
+ sort: sort,
+ });
+ const flowAction = new models.FlowAction("arcadeSeeAllGames", seeAllGamesUrl.build());
+ flowAction.title = title !== null && title !== void 0 ? title : objectGraph.loc.string("Arcade.SeeAllGames.Button.Title");
+ if (objectGraph.client.isWeb) {
+ const destination = makeRoutableArcadeSeeAllPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ });
+ const pageUrl = makeArcadeSeeAllCanonicalUrl(objectGraph, destination);
+ flowAction.destination = destination;
+ flowAction.pageUrl = pageUrl;
+ }
+ const itemId = id !== null && id !== void 0 ? id : (objectGraph.client.isVision ? "SeeAllGames" : "arcade-see-all-games-button");
+ const seeAllClickOptions = {
+ id: itemId,
+ idType: idType,
+ targetType: targetType,
+ actionType: "navigate",
+ actionContext: "Arcade",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, flowAction, seeAllClickOptions);
+ return flowAction;
+}
+/**
+ * Create a flow action for opening the Arcade Subscribe page with optional parameters
+ * @param context The context in which the Arcade Subscribe page is being opened for, e.g. where the flow action was initiated from.
+ * @param contextualAppId Optional app ID to associate with flow, if any. This is used for flow into contextual upsell sheet.
+ * @returns {FlowAction} Flow action to `arcadeSubscribe` page.
+ */
+export function arcadeSubscribePageFlowAction(objectGraph, context, contextualAppId, purchaseSuccessAction, options) {
+ var _a, _b, _c, _d;
+ const upsellRequestInfo = new models.MarketingItemRequestInfo("arcade", context, objectGraph.bag.metricsTopic, contextualAppId);
+ upsellRequestInfo.purchaseSuccessAction = purchaseSuccessAction;
+ upsellRequestInfo.carrierLinkSuccessAction = purchaseSuccessAction;
+ const action = new models.FlowAction("upsellMarketingItem");
+ if (isSome((_b = (_a = options === null || options === void 0 ? void 0 : options.pageInformation) === null || _a === void 0 ? void 0 : _a.searchTermContext) === null || _b === void 0 ? void 0 : _b.term)) {
+ upsellRequestInfo.metricsOverlay["searchTerm"] = (_c = options.pageInformation.searchTermContext) === null || _c === void 0 ? void 0 : _c.term;
+ }
+ const metricsIdentifierFields = (_d = objectGraph.metricsIdentifiersCache) === null || _d === void 0 ? void 0 : _d.getMetricsFieldsForTypes([
+ MetricsIdentifierType.user,
+ MetricsIdentifierType.client,
+ ]);
+ if (isSome(metricsIdentifierFields)) {
+ upsellRequestInfo.metricsOverlay = {
+ ...upsellRequestInfo.metricsOverlay,
+ ...metricsIdentifierFields,
+ };
+ }
+ action.pageData = upsellRequestInfo;
+ if (serverData.isDefinedNonNull(options)) {
+ metricsHelpersClicks.addClickEventToArcadeBuyInitiateAction(objectGraph, action, options);
+ }
+ return action;
+}
+/**
+ * Action to open the main Arcade page on each platform.
+ * Depending on the platform, this can be a tab change action or open action to separate Arcade app.
+ */
+export function openArcadeMainAction(objectGraph, metricsPageInformation, metricsLocationTracker, popToRoot) {
+ if (objectGraph.client.isTV) {
+ return openTVArcadeAppAction(objectGraph);
+ }
+ else {
+ const arcadeTabChangeAction = new models.TabChangeAction("arcade");
+ if (serverData.isDefinedNonNull(popToRoot)) {
+ arcadeTabChangeAction.popToRoot = popToRoot;
+ }
+ /*
+ * Presidio / Yukon timeframe workaround for <rdar://problem/53600942> Allow deserialized TabChangeAction to have use `title` property from JS instead of always using `nil`
+ * We're wrapping a single `TabChangeAction` within `CompoundAction` since `TabChangeAction` deserialization drops the JS provided title.
+ *
+ * Tracking removing this workaround in:
+ * <rdar://problem/53601182> Arcade: Remove workaround for having a tab change action with a title
+ */
+ return new models.CompoundAction([arcadeTabChangeAction]);
+ }
+}
+/**
+ * Creates an action to open Arcade app on tvOS.
+ */
+export function openTVArcadeAppAction(objectGraph) {
+ const url = "com.apple.Arcade://";
+ return new models.ExternalUrlAction(url);
+}
+// endregion
+/**
+ * Creates an action to open GamesUI.
+ */
+export function openGamesUIAction(objectGraph, target = { playNow: {} }) {
+ return new models.OpenGamesUIAction(target);
+}
+/**
+ * Creates Game Center header.
+ */
+export function makeGameCenterHeader(objectGraph, title = undefined, subtitle = undefined, useTitleArtwork = undefined) {
+ let eyebrowArtwork;
+ if (objectGraph.client.isTV) {
+ eyebrowArtwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://gamecenter.fill", 16, 16);
+ }
+ else {
+ eyebrowArtwork = artworkBuilder.createArtworkForResource(objectGraph, "resource://GameCenterEyebrow", 16, 16);
+ }
+ const isShelfHeaderEnabled = objectGraph.featureFlags.isEnabled("shelf_header");
+ const isGameCenterShelfHeaderEnabled = objectGraph.featureFlags.isEnabled("game_center_shelf_header");
+ const isGSEUIEnabled = objectGraph.featureFlags.isGSEUIEnabled("de7bbd8e");
+ if (isGSEUIEnabled) {
+ const configuration = {
+ eyebrowColor: named("secondaryText"),
+ includeSeparator: !isShelfHeaderEnabled,
+ prefersShelfHeader: isGameCenterShelfHeaderEnabled,
+ };
+ const eyebrow = objectGraph.loc.string("GAME_CENTER");
+ const shelfHeader = {
+ eyebrow: eyebrow,
+ eyebrowArtwork: eyebrowArtwork,
+ eyebrowArtworkType: models.ShelfHeaderArtworkType.Icon,
+ title: title,
+ subtitle: subtitle,
+ configuration: configuration,
+ };
+ return shelfHeader;
+ }
+ else {
+ const configuration = {
+ eyebrowColor: isGSEUIEnabled ? named("systemBlue") : undefined,
+ includeSeparator: !isShelfHeaderEnabled,
+ prefersShelfHeader: isGameCenterShelfHeaderEnabled,
+ };
+ if (isSome(useTitleArtwork) && useTitleArtwork) {
+ const shelfHeader = {
+ title: title,
+ titleArtwork: eyebrowArtwork,
+ titleArtworkType: models.ShelfHeaderArtworkType.Icon,
+ subtitle: subtitle,
+ configuration: configuration,
+ };
+ return shelfHeader;
+ }
+ else {
+ const eyebrow = objectGraph.loc.uppercased(objectGraph.loc.string("GAME_CENTER"));
+ const shelfHeader = {
+ eyebrow: eyebrow,
+ eyebrowArtwork: eyebrowArtwork,
+ eyebrowArtworkType: models.ShelfHeaderArtworkType.Icon,
+ title: title,
+ subtitle: subtitle,
+ configuration: configuration,
+ };
+ return shelfHeader;
+ }
+ }
+}
+// region Arcade Catalog MAPI Requests
+/**
+ * Base request for all MAPI requests fetching a set of Arcade games from catalog.
+ * This request has a server-defined implicit limit value (e.g. 100).
+ * This request should be bare-bones. If additional attributes are needed, it should be chained to this request.
+ *
+ * @returns {mediaFetching.Request} Request object for fetching Arcade apps from MAPI catalog endpoint.
+ */
+export function arcadeAppsRequest(objectGraph, includeComingSoon = false) {
+ let request = new mediaFetching.Request(objectGraph).forType("arcade-apps").includingAgeRestrictions();
+ if (includeComingSoon) {
+ request = request.addingQuery("with", "comingSoonApps");
+ }
+ // For visionOS, we need icons for bincompat games.
+ if (objectGraph.client.isVision) {
+ request = request.includingAdditionalPlatforms(["iphone", "ipad"]);
+ request.attributeIncludes.add("compatibilityControllerRequirement");
+ }
+ return request;
+}
+/**
+ * Request for fetching a set of arcade apps for displaying a set of Arcade Icons, e.g. for Arcade Grouping Footer, Contextual Upsell Icon Grid, and iOS Arcade Showcase.
+ * This request is a very special request with MAPI data containing only `artwork` attribute. Data is meant to be used *as-is* for showing a set of icons only.
+ *
+ * Requirements:
+ * - Icon Artwork for apps only. Additional metadata should be pruned if possible.
+ *
+ * @param limit Limit of apps. This should be configured to fit the view's needs.
+ */
+export function arcadeAppsRequestForIcons(objectGraph, limit) {
+ return arcadeAppsRequest(objectGraph)
+ .withSparseLimit(limit)
+ .asPartialResponseLimitedToFields(["artwork"])
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph));
+}
+// endregion
+// region Arcade Upsell Request
+export function arcadeUpsellRequest(objectGraph, context, contextualAppId) {
+ return arcadeUpsellMarketingItemRequest(objectGraph, context, contextualAppId);
+}
+export function arcadeUpsellMarketingItemRequest(objectGraph, context, contextualAppId) {
+ // We always want `context` to be provided, but some callers will provide this value from an messy source, e.g. extracted from URL param. Fall back to `generic` just in case.
+ if (serverData.isNullOrEmpty(context)) {
+ context = models.marketingItemContextFromString("generic");
+ }
+ const request = new mediaFetching.Request(objectGraph)
+ .forType("upsellMarketingItem")
+ .addingQuery("serviceType", "arcade")
+ .addingQuery("placement", context)
+ .includingMetaKeys("marketing-items", ["metrics"])
+ .includingRelationships(["contents"])
+ .includingAttributes(["marketingArtwork", "marketingVideo"])
+ .includingAgeRestrictions();
+ // Append app id that is promoted, if any.
+ if (serverData.isDefinedNonNull(contextualAppId)) {
+ request.addingQuery("seed", contextualAppId);
+ }
+ return request;
+}
+// endregion
+/**
+ * Grab recently played games.
+ * @param objectGraph The object graph.
+ * @param {number} limit The number of recently played games to return.
+ * @param {boolean} shouldHydrateAppsData Whether the apps data should be fetched from media api.
+ * @param {number} timeout A timeout in seconds.
+ * @returns {DataContainer} The media api data container with the recently played games.
+ */
+export async function getRecentlyPlayedGames(objectGraph, limit = null, shouldHydrateAppsData = false, timeout = null) {
+ return await new Promise((resolve, reject) => {
+ const isRecentlyPlayedGamesSupported = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isTV;
+ // Check if the client supports recently played games.
+ if (!isRecentlyPlayedGamesSupported) {
+ resolve(null);
+ return;
+ }
+ const getRecentlyPlayGamesPromise = objectGraph.arcade.getRecentlyPlayedGamesWithTimeout(timeout);
+ // Get recently played games
+ getRecentlyPlayGamesPromise
+ .then((adamIds) => {
+ // Only perform request when there are recently played games.
+ if (serverData.isNull(adamIds) || adamIds.length === 0) {
+ resolve(null);
+ return;
+ }
+ // Enforce limit (if any).
+ if (serverData.isNumber(limit) && adamIds.length > limit) {
+ adamIds = adamIds.slice(0, limit);
+ }
+ if (shouldHydrateAppsData) {
+ // Fetch data for recently played games.
+ const attributes = [
+ "editorialArtwork",
+ "editorialVideo",
+ "description",
+ "minimumOSVersion",
+ "minPlayers",
+ "maxPlayers",
+ "remoteControllerRequirement",
+ "requiresGameController",
+ "supportsSharePlay",
+ ];
+ if (objectGraph.client.isVision) {
+ attributes.push("compatibilityControllerRequirement");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ const mediaApiRequest = new mediaFetching.Request(objectGraph)
+ .withIdsOfType(adamIds, "apps")
+ .includingAttributes(attributes);
+ const fetchDataPromise = mediaNetwork.fetchData(objectGraph, mediaApiRequest);
+ fetchDataPromise.then((dataContainer) => resolve(dataContainer), (reason) => {
+ objectGraph.console.log(`getRecentlyPlayedGames() failed when calling mediaNetwork.fetchData() with reason: ${reason}`);
+ resolve(null);
+ });
+ }
+ else {
+ // Create an incomplete data container to be fetched later.
+ const dataContainer = {
+ data: [],
+ };
+ adamIds.forEach((adamId) => {
+ dataContainer.data.push({
+ id: adamId,
+ type: "apps",
+ });
+ });
+ resolve(dataContainer);
+ }
+ })
+ .catch((reason) => {
+ objectGraph.console.log(`getRecentlyPlayedGames() failed with: ${reason}`);
+ resolve(null);
+ });
+ });
+}
+/**
+ * Convenience function to build a `ArcadeUpsellData` representation from upsell relationship joined to some data.
+ * @seealso `upsellFromContentsOfUpsellResponse`
+ * @param objectGraph The object graph
+ * @param data Data containing `upsell` relationship to build `ArcadeUpsellData` with
+ */
+export function upsellFromRelationshipOf(objectGraph, data) {
+ // Data to extract.
+ let marketingItemData = null;
+ const upsellDataContainer = objectGraph.client.isVision || preprocessor.GAMES_TARGET
+ ? mediaRelationships.relationship(data, "contents")
+ : mediaRelationships.relationship(data, "upsell") ||
+ mediaRelationships.relationship(data, "marketing-items");
+ if (serverData.isNullOrEmpty(upsellDataContainer) || serverData.isNullOrEmpty(upsellDataContainer.data)) {
+ return null;
+ }
+ // Create marketing items array from data container.
+ const marketingItems = upsellDataContainer.data
+ .map((item) => {
+ if (item.type === "marketing-items") {
+ return item;
+ }
+ else {
+ return null;
+ }
+ })
+ .filter((item) => isDefinedNonNull(item));
+ // Return null if there are NOT any marketing items.
+ if (serverData.isNullOrEmpty(marketingItems)) {
+ return null;
+ }
+ const timeout = objectGraph.bag.marketingItemSelectionTimeout;
+ // If there is only one marketing item or timeout is zero, set this as the data.
+ // Otherwise get a single marketing item by calling AMS.
+ if (marketingItems.length === 1 || timeout === 0) {
+ marketingItemData = marketingItems[0];
+ }
+ else {
+ try {
+ marketingItemData = objectGraph.arcade.getMarketingItemWithTimeout(marketingItems, timeout);
+ }
+ catch {
+ // Default to first item if the call was timed out.
+ marketingItemData = marketingItems[0];
+ }
+ }
+ // Return null if marketing item is null.
+ if (serverData.isNull(marketingItemData)) {
+ return null;
+ }
+ return {
+ marketingItemData: marketingItemData,
+ };
+}
+/**
+ * Convenience function to build a `ArcadeUpsellData` representation from contents of a engagement/upsell response.
+ * This doesn't really belong in
+ * @seealso `upsellFromRelationshipOf`
+ * @param arcadeUpsellResponse Response from the engagement/upsell endpoint.
+ */
+export function upsellFromContentsOfUpsellResponse(objectGraph, arcadeUpsellResponse) {
+ if (!arcadeUpsellResponse) {
+ return null;
+ }
+ let marketingItemData = null;
+ const responseDataArray = serverData.asArrayOrEmpty(arcadeUpsellResponse, "results.data");
+ if (responseDataArray.length > 0) {
+ marketingItemData = responseDataArray[0];
+ }
+ /**
+ * `arcadeUpsellResponse` is expected of form:
+ * {
+ * content: { mediaDataStructure.Data }
+ * }
+ * matching what is provided via the appropriate `action=<usage>` param from engagement/upsell endpoint.
+ */
+ if (!serverData.isDefinedNonNull(marketingItemData)) {
+ return null;
+ }
+ return {
+ marketingItemData: marketingItemData,
+ };
+}
+// endregion
+// region Arcade Games For You Request
+/**
+ * Request for fetching list of recommended Arcade apps from recommendations API.
+ * @param objectGraph The App Store object graph.
+ * @param limit Maximum number of apps to fetch.
+ * @returns {Request} Request object for fetching recommended Arcade apps from
+ * personalized recommendations endpoint.
+ */
+export function arcadeGamesForYouRequest(objectGraph, limit) {
+ let request = new mediaFetching.Request(objectGraph)
+ .forType("personal-recommendations")
+ .addingQuery("sparseLimit[contents]", `${limit}`)
+ .addingQuery("include[personal-recommendations]", "contents")
+ .addingQuery("filter[kind]", "arcadeGamesForYou")
+ .includingAgeRestrictions();
+ // For visionOS, we need to include bincompat apps.
+ if (objectGraph.client.isVision) {
+ request = request.includingAdditionalPlatforms(["iphone", "ipad"]);
+ }
+ return request;
+}
+// endregion
+// The color to use for Arcade content.
+export const arcadeColor = Color.fromRGB(1, 90 / 255, 80 / 255);
+//# sourceMappingURL=arcade-common.js.map \ No newline at end of file