summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-fetching.js
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /node_modules/@jet-app/app-store/tmp/src/foundation/media/data-fetching.js
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/foundation/media/data-fetching.js')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/data-fetching.js631
1 files changed, 631 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-fetching.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-fetching.js
new file mode 100644
index 0000000..445f42f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-fetching.js
@@ -0,0 +1,631 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as serverData from "../json-parsing/server-data";
+import * as client from "../wrappers/client";
+export function allPlatforms(objectGraph, platformsToExclude) {
+ const platforms = new Set();
+ platforms.add("iphone");
+ platforms.add("ipad");
+ platforms.add("appletv");
+ platforms.add("mac");
+ platforms.add("watch");
+ if (objectGraph.client.isVision || objectGraph.bag.enableVisionPlatform) {
+ platforms.add("realityDevice");
+ }
+ if (isSome(platformsToExclude)) {
+ for (const platform of platformsToExclude) {
+ platforms.delete(platform);
+ }
+ }
+ return Array.from(platforms);
+}
+export function defaultPlatformForClient(objectGraph) {
+ if (objectGraph.client.isCompanionVisionApp) {
+ // The Vision companion app always prefers visionOS assets
+ return "realityDevice";
+ }
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ return "iphone";
+ case "pad":
+ return "ipad";
+ case "tv":
+ return "appletv";
+ case "mac":
+ return "mac";
+ case "watch":
+ return "watch";
+ case "vision":
+ return "realityDevice";
+ case "web":
+ return "web";
+ default:
+ return null;
+ }
+}
+/**
+ * Returns the layout size controlling the number of rows to display.
+ * @param {AppStoreObjectGraph} objectGraph Object graph to get the client device type.
+ * @returns {number} Layout size.
+ */
+export function defaultLayoutSize(objectGraph) {
+ return objectGraph.client.isPhone ? 2 : 1;
+}
+/**
+ * Returns the sparse count controlling the number of shelves to hydrate.
+ * @param {AppStoreObjectGraph} objectGraph Object graph to get the client device type.
+ * @returns {number} Sparse count.
+ */
+export function defaultSparseCountForClient(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ return 4;
+ case "pad":
+ return 5;
+ case "tv":
+ return 6;
+ case "mac":
+ return 5;
+ case "watch":
+ return 10;
+ default:
+ return null;
+ }
+}
+/**
+ * Returns the sparse limit controlling the number of items to hydrate per shelve.
+ * @param {AppStoreObjectGraph} objectGraph Object graph to get the client device type.
+ * @returns {number} Sparse limit.
+ */
+export function defaultSparseLimitForClient(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ return 9;
+ case "pad":
+ return 12;
+ case "tv":
+ return 15;
+ case "mac":
+ return 15;
+ case "watch":
+ return 3;
+ case "web":
+ return 12;
+ default:
+ return null;
+ }
+}
+/**
+ * Get the list of all platforms excluding the current platform
+ * @returns {Platform[]}
+ */
+export function defaultAdditionalPlatformsForClient(objectGraph) {
+ switch (objectGraph.host.clientIdentifier) {
+ case "com.apple.TVAppStore.AppStoreTopShelfExtension":
+ case "com.apple.Arcade.ArcadeTopShelfExtension":
+ case "com.apple.AppStore.Widgets":
+ // Skip additional platforms for top shelf extension due to memory constraint.
+ return [];
+ default: {
+ const currentPlatform = defaultPlatformForClient(objectGraph);
+ return allPlatforms(objectGraph, isSome(currentPlatform) ? new Set([currentPlatform]) : undefined);
+ }
+ }
+}
+/**
+ * Get the product page reviews limit for a client.
+ * @returns {number} The limit to use.
+ */
+export function defaultProductPageReviewsLimitForClient(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ return 6;
+ case "pad":
+ return 10;
+ case "mac":
+ return 12;
+ case "vision":
+ return 10;
+ default:
+ return 8;
+ }
+}
+/// Returns the context param to use for a given grouping request.
+/// This is leveraged for switching grouping tab root in specific configurations.
+export function defaultGroupingContextForClient(objectGraph) {
+ let context = null;
+ if (objectGraph.host.clientIdentifier === client.messagesIdentifier) {
+ // Messages Grouping (iOS)
+ context = "messages";
+ }
+ else if (objectGraph.host.clientIdentifier === client.watchIdentifier) {
+ // Bridge App Store (iOS)
+ context = "watch";
+ }
+ else if (objectGraph.client.isWatch && objectGraph.client.isTinkerWatch) {
+ // Tinker App Store (watchOS)
+ context = "tinker";
+ }
+ return context;
+}
+// Nothing on the App Store is rated 1000+, so a value this high essentially
+// indicates that content restrictions are disabled.
+export const ageRestrictionsDisabledThreshold = 1000;
+export class Request {
+ constructor(objectGraph, param, enableMixedCatalog, supplementaryMetadataAssociations) {
+ var _a;
+ /// The resource types used in a mixed contents call
+ this.contentsResourceTypes = new Set();
+ /// The ID(s) associated with the resource type
+ this.ids = new Set();
+ /// IDs associated with specific resource types, used for mixed catalog requests
+ this.idsByResourceType = new Map();
+ /// The original ordering of the ids in this reqeust if there is a mixed catalog request
+ this.originalOrdering = [];
+ this.relationshipIncludes = new Set();
+ // Contents of `extend` param
+ this.attributeIncludes = new Set();
+ this.platform = null;
+ /// The paths to additional metadata that need to be fetched for mixed media requests
+ this.supplementaryMetadataAssociations = [];
+ this.additionalPlatforms = new Set();
+ this.additionalQuery = {};
+ this.relationshipLimits = {};
+ this.searchTerm = null;
+ this.searchTypes = [];
+ /// Whether we are searching watch or messages store.
+ this.context = null;
+ /// Whether or not to use custom extend attributes, instead of using `attributeIncludes` as-is.
+ this.useCustomAttributes = false;
+ /// An optional country code override for constructing the URL. Used in very specific regulatory scenarios where the bag country code cannot be relied upon.
+ this.countryCodeOverride = undefined;
+ this.objectGraph = objectGraph;
+ this.platform = defaultPlatformForClient(objectGraph);
+ this.isMixedMediaRequest = enableMixedCatalog !== null && enableMixedCatalog !== void 0 ? enableMixedCatalog : false;
+ this.supplementaryMetadataAssociations = supplementaryMetadataAssociations !== null && supplementaryMetadataAssociations !== void 0 ? supplementaryMetadataAssociations : [];
+ this.includeAppBinaryTraitsAttribute = objectGraph.client.isiOS;
+ // By default, the `platform` for web defaults to whatever the `activeIntent` is (e.g. "mac"),
+ // but this can be overridden when calling `setPreviewPlatform` with the request, which
+ // will set the `platform` to `web` and `previewPlatform` will be the `activeIntent` platform.
+ if ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.platform) {
+ this.platform = objectGraph.activeIntent.platform;
+ }
+ if (serverData.isNullOrEmpty(param)) {
+ return;
+ }
+ if (typeof param === "string") {
+ this.href = param;
+ }
+ else if (Array.isArray(param)) {
+ this.withDataItems(param, supplementaryMetadataAssociations, enableMixedCatalog);
+ }
+ }
+ /**
+ * Adds a data object to an idsByResourceType mapping.
+ * @param data The data object
+ * @returns An updated {resource-type: IDs} map, which includes the data object
+ */
+ addDataToIDsByResourceType(data) {
+ const resourceType = data.type;
+ const id = data.id;
+ let ids = this.idsByResourceType.get(resourceType);
+ if (isNothing(ids)) {
+ ids = new Set();
+ }
+ ids.add(id);
+ this.idsByResourceType.set(resourceType, ids);
+ }
+ forType(type) {
+ this.resourceType = type;
+ return this;
+ }
+ /**
+ * This method is used to add data items to the request. It is used to build the request for the mixed catalog / catalog
+ *
+ * @param dataItems The data items to add to the request
+ * @param supplementaryMetadataAssociations The metadata paths to add to the request
+ * @param enableMixedCatalog Whether to allow mixed catalog requests
+ * @returns The modified Request
+ */
+ withDataItems(dataItems, supplementaryMetadataAssociations, enableMixedCatalog) {
+ if (dataItems.length === 0) {
+ return this;
+ }
+ this.isMixedMediaRequest = this.isMixedMediaRequest || (enableMixedCatalog !== null && enableMixedCatalog !== void 0 ? enableMixedCatalog : false);
+ for (const data of dataItems) {
+ // Track all IDs in a single set, for the case where we use the contents endpoint.
+ this.ids.add(data.id);
+ // Track all IDs by resource type, for either the case where we ultimately have just
+ // one resource type, or where we used the mixed catalog endpoint.
+ this.addDataToIDsByResourceType(data);
+ // If we have any additional metadata paths that need fetching, we now add them
+ // as additional resource types for the mixed catalog endpoint.
+ if (isSome(enableMixedCatalog) &&
+ enableMixedCatalog &&
+ isSome(supplementaryMetadataAssociations) &&
+ supplementaryMetadataAssociations.length > 0) {
+ for (const association of supplementaryMetadataAssociations) {
+ const metadataItems = extractMetaAssociationFromData(association, data);
+ if (isSome(metadataItems) && metadataItems.length > 0) {
+ metadataItems.forEach((metadataItem) => {
+ this.addDataToIDsByResourceType(metadataItem);
+ });
+ }
+ }
+ }
+ }
+ // Now that we have collated all IDs and resource types, we can assign them to different
+ // properties depending on the number of resource types we need.
+ if (this.idsByResourceType.size === 1) {
+ // We only have one list of IDs for a single resource type, so we just take the first one
+ this.resourceType = this.idsByResourceType.keys().next().value;
+ this.isMixedMediaRequest = false;
+ }
+ else if (this.idsByResourceType.size > 1 && !this.isMixedMediaRequest) {
+ this.resourceType = "contents";
+ this.contentsResourceTypes = new Set(Array.from(this.idsByResourceType.keys()));
+ }
+ this.originalOrdering.push(...[...dataItems]);
+ return this;
+ }
+ /**
+ * Add a single id to this request, this should only be used when fetching a single resource
+ * @param id The single ID to add to the request
+ * @param type The type of the resource
+ * @returns The modified Request
+ */
+ withIdOfType(id, type) {
+ return this.withDataItems([{ id, type }]);
+ }
+ /**
+ * Add a list of ids to this request, this will add these ids to the mapping of resource types
+ * @param ids The IDs to add to the request
+ * @param type The type of the resource
+ * @returns The modified Request
+ */
+ withIdsOfType(ids, type) {
+ return this.withDataItems(ids.map((id) => ({ id, type })));
+ }
+ includingRelationships(relationships) {
+ for (const relationship of relationships) {
+ this.relationshipIncludes.add(relationship);
+ }
+ return this;
+ }
+ includingScopedRelationships(scopedDataType, relationshipsToAdd) {
+ // Lazy init top-level property
+ if (!this.scopedRelationshipIncludes) {
+ this.scopedRelationshipIncludes = new Map();
+ }
+ // Retrieve existing scoped relationship. Lazy init if needed.
+ let scopedRelationship = this.scopedRelationshipIncludes.get(scopedDataType);
+ if (!scopedRelationship) {
+ scopedRelationship = new Set();
+ }
+ // Update
+ for (const newRelationship of relationshipsToAdd) {
+ scopedRelationship.add(newRelationship);
+ }
+ this.scopedRelationshipIncludes.set(scopedDataType, scopedRelationship);
+ return this;
+ }
+ includingMetaKeys(scopedDataType, keysToAdd) {
+ // Lazy init top-level property
+ if (!this.metaIncludes) {
+ this.metaIncludes = new Map();
+ }
+ // Retrieve existing inclusion. Lazy init if needed.
+ let scopedMeta = this.metaIncludes.get(scopedDataType);
+ if (!scopedMeta) {
+ scopedMeta = new Set();
+ }
+ // Update
+ for (const newKey of keysToAdd) {
+ scopedMeta.add(newKey);
+ }
+ this.metaIncludes.set(scopedDataType, scopedMeta);
+ return this;
+ }
+ includingViews(viewsToAdd) {
+ // Lazy init top-level property
+ if (!this.viewsIncludes) {
+ this.viewsIncludes = new Set();
+ }
+ for (const view of viewsToAdd) {
+ this.viewsIncludes.add(view);
+ }
+ return this;
+ }
+ includingKindsKeys(scopedDataType, keysToAdd) {
+ // Lazy init top-level property
+ if (!this.kindIncludes) {
+ this.kindIncludes = new Map();
+ }
+ // Retrieve existing inclusion. Lazy init if needed.
+ let scopedMeta = this.kindIncludes.get(scopedDataType);
+ if (!scopedMeta) {
+ scopedMeta = new Set();
+ }
+ // Update
+ for (const newKey of keysToAdd) {
+ scopedMeta.add(newKey);
+ }
+ this.kindIncludes.set(scopedDataType, scopedMeta);
+ return this;
+ }
+ includingAssociateKeys(scopedDataType, keysToAdd) {
+ // Lazy init top-level property
+ if (!this.associateIncludes) {
+ this.associateIncludes = new Map();
+ }
+ // Retrieve existing inclusion. Lazy init if needed.
+ let scopedAssociate = this.associateIncludes.get(scopedDataType);
+ if (!scopedAssociate) {
+ scopedAssociate = new Set();
+ }
+ // Update
+ for (const newKey of keysToAdd) {
+ scopedAssociate.add(newKey);
+ }
+ this.associateIncludes.set(scopedDataType, scopedAssociate);
+ return this;
+ }
+ /**
+ * Include the relationships needed for an upsell.
+ * @param requiresScopedInclude A flag indicating whether the include should be "scoped". This will need to be the
+ * case when the relationships we are including is on a request for a separate primary resource type. For example,
+ * if we are looking to have the upsell relationship included in an EI (Today card, for example), this needs to be
+ * scoped. However, if we are fetching a grouping directly and need the upsell included, this should *not* be scoped.
+ */
+ includingRelationshipsForUpsell(requiresScopedRelationshipInclude) {
+ const relationship = "marketing-items";
+ if (requiresScopedRelationshipInclude) {
+ // Lazy init top-level property
+ if (!this.scopedRelationshipIncludes) {
+ this.scopedRelationshipIncludes = new Map();
+ }
+ // Retrieve existing scoped relationship. Lazy init if needed.
+ let scopedRelationship = this.scopedRelationshipIncludes.get("editorial-items");
+ if (!scopedRelationship) {
+ scopedRelationship = new Set();
+ }
+ // Update
+ scopedRelationship.add(relationship);
+ this.scopedRelationshipIncludes.set("editorial-items", scopedRelationship);
+ }
+ else {
+ this.relationshipIncludes.add(relationship);
+ }
+ // In order to get metrics metadata stiched in to the marketing item relationship, we have to include it in our
+ // request.
+ if (relationship === "marketing-items") {
+ if (!this.metaIncludes) {
+ this.metaIncludes = new Map();
+ }
+ // Retrieve existing inclusion. Lazy init if needed.
+ let scopedMeta = this.metaIncludes.get("marketing-items");
+ if (!scopedMeta) {
+ scopedMeta = new Set();
+ }
+ scopedMeta.add("metrics");
+ this.metaIncludes.set("marketing-items", scopedMeta);
+ }
+ return this;
+ }
+ includingAttributes(attributes) {
+ for (const attribute of attributes) {
+ this.attributeIncludes.add(attribute);
+ }
+ return this;
+ }
+ includingScopedAttributes(resourceType, attributesToAdd) {
+ // Lazy init top-level property
+ if (!this.scopedAttributeIncludes) {
+ this.scopedAttributeIncludes = new Map();
+ }
+ // Retrieve existing scoped relationship. Lazy init if needed.
+ let attributesForResourceType = this.scopedAttributeIncludes.get(resourceType);
+ if (!attributesForResourceType) {
+ attributesForResourceType = new Set();
+ }
+ // Update
+ for (const attribute of attributesToAdd) {
+ attributesForResourceType.add(attribute);
+ }
+ this.scopedAttributeIncludes.set(resourceType, attributesForResourceType);
+ return this;
+ }
+ /**
+ * Adds an age restriction to the request. ManagedConfiguration provides
+ * this value on the client, which maps to JRPurpleRating.clientIdentifier.
+ *
+ * @returns {Request} The updated request
+ */
+ includingAgeRestrictions() {
+ const maxAppContentRating = this.objectGraph.client.maxAppContentRating;
+ if (maxAppContentRating < ageRestrictionsDisabledThreshold) {
+ this.ageRestriction = maxAppContentRating;
+ }
+ return this;
+ }
+ includingAdditionalPlatforms(additionalPlatforms) {
+ for (const platform of additionalPlatforms) {
+ this.additionalPlatforms.add(platform);
+ }
+ return this;
+ }
+ includingScopedAvailableIn(resourceType, valuesToAdd) {
+ // Lazy init top-level property
+ if (!this.scopedAvailableInIncludes) {
+ this.scopedAvailableInIncludes = new Map();
+ }
+ // Retrieve existing scoped relationship. Lazy init if needed.
+ let valuesForResourceType = this.scopedAvailableInIncludes.get(resourceType);
+ if (!valuesForResourceType) {
+ valuesForResourceType = new Set();
+ }
+ // Update
+ for (const value of valuesToAdd) {
+ valuesForResourceType.add(value);
+ }
+ this.scopedAvailableInIncludes.set(resourceType, valuesForResourceType);
+ return this;
+ }
+ /**
+ * Include the sparse limit needed for a specific resource type.
+ * @param {media.Type} resourceType Resource type to use for the sparse limit.
+ * @param {number} value Value to set as the sparse limit.
+ * @returns {Request} Updated request.
+ */
+ includingScopedSparseLimit(resourceType, value) {
+ // Lazy init top-level property
+ if (!this.scopedSparseLimit) {
+ this.scopedSparseLimit = new Map();
+ }
+ this.scopedSparseLimit.set(resourceType, value);
+ return this;
+ }
+ addingQuery(key, value) {
+ if (isSome(value)) {
+ this.additionalQuery[key] = value;
+ }
+ else {
+ delete this.addingQuery[key];
+ }
+ return this;
+ }
+ /**
+ * @param query The additional query values to add
+ * @returns The modified Reqeust
+ */
+ addingQueryValues(query) {
+ this.additionalQuery = {
+ ...this.addingQuery,
+ ...query,
+ };
+ return this;
+ }
+ addingRelationshipLimit(relationship, limit) {
+ this.relationshipLimits[relationship] = limit;
+ return this;
+ }
+ withSearchTerm(term) {
+ this.searchTerm = term;
+ return this;
+ }
+ searchingOverTypes(types) {
+ for (const type of types) {
+ this.searchTypes.push(type);
+ }
+ return this;
+ }
+ addingContext(context) {
+ this.context = context;
+ return this;
+ }
+ includingMacOSCompatibleIOSAppsWhenSupported(verifiedBadgeOnly = false) {
+ if (this.objectGraph.appleSilicon.isSupportEnabled) {
+ if (!verifiedBadgeOnly) {
+ this.enablingFeature("macOSCompatibleIOSApps");
+ }
+ this.includingScopedAttributes("apps", ["isVerifiedForAppleSiliconMac"]);
+ }
+ return this;
+ }
+ /// Mark a request to include (or exclude) appBinaryTraits attributes.
+ includingAppBinaryTraitsAttribute(includeAppBinaryTraitsAttribute = true) {
+ this.includeAppBinaryTraitsAttribute = includeAppBinaryTraitsAttribute;
+ return this;
+ }
+ /**
+ * Mark a request to use custom attributes.
+ * This triggers usage of `customXYZ` extend attributes in url-builder for specific extend params
+ * e.g. `artwork`, `customArtwork`.
+ */
+ usingCustomAttributes(useCustomAttributes) {
+ this.useCustomAttributes = useCustomAttributes;
+ return this;
+ }
+ alwaysUseIdsAsQueryParam(value) {
+ this.useIdsAsQueryParam = value;
+ return this;
+ }
+ attributingTo(canonicalUrl) {
+ this.canonicalUrl = canonicalUrl;
+ return this;
+ }
+ withFilter(type, value) {
+ this.filterType = type;
+ this.filterValue = value;
+ return this;
+ }
+ withLimit(limit) {
+ this.limit = limit;
+ return this;
+ }
+ withSparseLimit(sparseLimit) {
+ if (sparseLimit !== null) {
+ this.sparseLimit = sparseLimit;
+ }
+ return this;
+ }
+ withSparseCount(sparseCount) {
+ if (sparseCount !== null) {
+ this.sparseCount = sparseCount;
+ }
+ return this;
+ }
+ enablingFeature(feature) {
+ if (!this.enabledFeatures) {
+ this.enabledFeatures = [];
+ }
+ this.enabledFeatures.push(feature);
+ return this;
+ }
+ enablingFeatures(features) {
+ if (!this.enabledFeatures) {
+ this.enabledFeatures = [];
+ }
+ this.enabledFeatures.push(...features);
+ return this;
+ }
+ /**
+ * Limit the request to include only certain fields.
+ * This should only be used in very select use-cases, where:
+ * - There is no possibility of data being used for other purposes, e.g. sidpack
+ * - All consumer of this data use it in the same narrow scope, e.g. requesting a set of icons for displaying without metadata only.
+ * @param fields Set of fields to limit to
+ */
+ asPartialResponseLimitedToFields(fields) {
+ this.fields = fields;
+ return this;
+ }
+ includesResourceType(resourceType) {
+ if (this.resourceType === resourceType) {
+ return true;
+ }
+ if (serverData.isDefinedNonNull(this.contentsResourceTypes)) {
+ return this.contentsResourceTypes.has(resourceType);
+ }
+ return false;
+ }
+ withCountryCodeOverride(countryCodeOverride) {
+ this.countryCodeOverride = countryCodeOverride;
+ return this;
+ }
+}
+/**
+ * Extracts the metadata association from the data.
+ * @param association The association to extract
+ * @param data The data to extract from
+ */
+export function extractMetaAssociationFromData(association, data) {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ const associationData = serverData.asArrayOrEmpty(data, `meta.associations.${association}.data`);
+ if (isNothing(associationData)) {
+ return null;
+ }
+ return [...associationData];
+}
+//# sourceMappingURL=data-fetching.js.map \ No newline at end of file