summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/foundation/media
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/foundation/media')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/associations.js17
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/attributes.js149
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/data-fetching.js631
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/data-structure.js84
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/network.js304
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/platform-attributes.js143
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/relationships.js43
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/url-builder.js381
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/util.js185
9 files changed, 1937 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/associations.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/associations.js
new file mode 100644
index 0000000..7e2b088
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/associations.js
@@ -0,0 +1,17 @@
+import * as serverData from "../json-parsing/server-data";
+/**
+ * @param data The media api data to find teh card in
+ * @returns The editorial-card data for this media api item, this will return the first associated card
+ */
+export function editorialCardsFromData(data) {
+ const editorialCards = serverData.asArrayOrEmpty(data, "meta.associations.editorial-cards.data");
+ return editorialCards;
+}
+/**
+ * @param data The media api data to find teh card in
+ * @returns The editorial-card data for this media api item, this will return the first associated card
+ */
+export function editorialCardFromData(data) {
+ return serverData.asInterface(editorialCardsFromData(data)[0]);
+}
+//# sourceMappingURL=associations.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/attributes.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/attributes.js
new file mode 100644
index 0000000..992a56e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/attributes.js
@@ -0,0 +1,149 @@
+import { isNothing } from "@jet/environment/types/optional";
+import * as serverData from "../json-parsing/server-data";
+// region Generic Attribute retrieval
+// region Attribute retrieval
+/**
+ * Retrieve the specified attribute from the data, coercing it to a JSONData dictionary
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param defaultValue The object to return if the path search fails.
+ * @returns The dictionary of data
+ */
+export function attributeAsDictionary(data, attributePath, defaultValue) {
+ if (isNothing(data)) {
+ return null;
+ }
+ return serverData.asDictionary(data.attributes, attributePath, defaultValue);
+}
+/**
+ * Retrieve the specified attribute from the data, coercing it to an Interface
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param defaultValue The object to return if the path search fails.
+ * @returns The dictionary of data as an interface
+ */
+export function attributeAsInterface(data, attributePath, defaultValue) {
+ return attributeAsDictionary(data, attributePath, defaultValue);
+}
+/**
+ * Retrieve the specified attribute from the data as an array, coercing to a JSONValue array
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @returns {any[]} The attribute value as an array.
+ */
+export function attributeAsArray(data, attributePath) {
+ if (serverData.isNull(data)) {
+ return [];
+ }
+ return serverData.asArray(data.attributes, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as an array, coercing to an empty array if the object is not an array.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @returns {any[]} The attribute value as an array.
+ */
+export function attributeAsArrayOrEmpty(data, attributePath) {
+ var _a;
+ return (_a = attributeAsArray(data, attributePath)) !== null && _a !== void 0 ? _a : [];
+}
+/**
+ * Retrieve the specified attribute from the data as a string.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The object path for the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {string} The attribute value as a string.
+ */
+export function attributeAsString(data, attributePath, policy = "coercible") {
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ return serverData.asString(data.attributes, attributePath, policy);
+}
+/**
+ * Retrieve the specified attribute from the data as a boolean.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {boolean} The attribute value as a boolean.
+ */
+export function attributeAsBoolean(data, attributePath, policy = "coercible") {
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ return serverData.asBoolean(data.attributes, attributePath, policy);
+}
+/**
+ * Retrieve the specified attribute from the data as a boolean, which will be `false` if the attribute does not exist.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @returns {boolean} The attribute value as a boolean, coercing to `false` if the value is not present..
+ */
+export function attributeAsBooleanOrFalse(data, attributePath) {
+ if (serverData.isNull(data)) {
+ return false;
+ }
+ return serverData.asBooleanOrFalse(data.attributes, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as a number.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {boolean} The attribute value as a number.
+ */
+export function attributeAsNumber(data, attributePath, policy = "coercible") {
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ return serverData.asNumber(data.attributes, attributePath, policy);
+}
+export function hasAttributes(data) {
+ return !serverData.isNull(serverData.asDictionary(data, "attributes"));
+}
+/**
+ * The canonical way to detect if an item from Media API is hydrated or not.
+ *
+ * @param data The data from which to retrieve the attributes.
+ */
+export function isNotHydrated(data) {
+ return !hasAttributes(data);
+}
+// region Custom Attributes
+/**
+ * Performs conversion for a custom variant of given attribute, if any are available.
+ * @param attribute Attribute to get custom attribute key for, if any.
+ */
+export function attributeKeyAsCustomAttributeKey(attribute) {
+ return customAttributeMapping[attribute];
+}
+/**
+ * Whether or not given custom attributes key allows fallback to default page with AB testing treatment within a nondefault page.
+ * This is to allow AB testing to affect only icons within custom product pages.
+ */
+export function attributeAllowsNonDefaultTreatmentInNonDefaultPage(customAttribute) {
+ return customAttribute === "customArtwork" || customAttribute === "customIconArtwork"; // Only the icon artwork.
+}
+/**
+ * Defines mapping of attribute to custom attribute.
+ */
+const customAttributeMapping = {
+ artwork: "customArtwork",
+ iconArtwork: "customIconArtwork",
+ screenshotsByType: "customScreenshotsByType",
+ promotionalText: "customPromotionalText",
+ videoPreviewsByType: "customVideoPreviewsByType",
+ customScreenshotsByTypeForAd: "customScreenshotsByTypeForAd",
+ customVideoPreviewsByTypeForAd: "customVideoPreviewsByTypeForAd",
+ customDeepLink: "customDeepLink",
+};
+// endregion
+//# sourceMappingURL=attributes.js.map \ No newline at end of file
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
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-structure.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-structure.js
new file mode 100644
index 0000000..cafcce8
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-structure.js
@@ -0,0 +1,84 @@
+/**
+ * Created by joel on 1/25/18.
+ */
+import * as serverData from "../json-parsing/server-data";
+import { ResponseMetadata } from "../network/network";
+export function dataFromDataContainer(objectGraph, dataContainer) {
+ const dataArray = serverData.asArrayOrEmpty(dataContainer, "data");
+ if (dataArray.length > 1) {
+ objectGraph.console.warn("tried to extract data from container but more than one member present");
+ }
+ if (dataArray.length !== 1) {
+ return null;
+ }
+ return dataArray[0];
+}
+export function dataCollectionFromDataContainer(dataContainer) {
+ return serverData.asArrayOrEmpty(dataContainer, "data");
+}
+/**
+ * Check whether or not a server vended `Data` object is hydrated or not.
+ * @param {Data} data to check if hydrated.
+ */
+export function isDataHydrated(data) {
+ return serverData.isDefinedNonNull(data.attributes);
+}
+/**
+ * Check whether or not a entire data collection contains elements that are fully hydrated.
+ * @param {Data[]} dataArray Data array to check.
+ */
+export function isDataCollectionHydrated(dataCollection) {
+ // Iterate from the back to determine if fully hydrated faster - unhydrated elements tend to be latter elements.
+ const lastIndex = dataCollection.length - 1;
+ for (let index = lastIndex; index >= 0; index--) {
+ const data = dataCollection[index];
+ if (!isDataHydrated(data)) {
+ return false;
+ }
+ }
+ return true;
+}
+/**
+ * Check whether or not a entire data collection at least has 1 element that is fully hydrated.
+ * @param {Data[]} dataArray Data array to check.
+ */
+export function isDataCollectionPartiallyHydrated(dataCollection) {
+ for (const data of dataCollection) {
+ if (isDataHydrated(data)) {
+ return true;
+ }
+ }
+ return false;
+}
+/**
+ * Check whether or not a today data module represents a today page on the heuristic that:
+ * - The "Today" modules have labels with value 'TodayForApps' as opposed to `WhatYouMissed`
+ * - Date marker "Today" modules have a 'date' field.
+ */
+export function isModuleTodayForApps(todayModule) {
+ const todayModuleId = "TodayForApps";
+ const date = serverData.traverse(todayModule, "date");
+ return todayModule.label === todayModuleId || serverData.isDefinedNonNull(date);
+}
+/**
+ * Get chart results from a server response. Always returns chart segments with valid data
+ */
+export function chartResultsFromServerResponse(response) {
+ const resultsArray = serverData.asArrayOrEmpty(response, "results.apps");
+ return resultsArray.filter((segment) => {
+ return !serverData.isNull(segment.data);
+ });
+}
+export function dataCollectionFromResultsListContainer(resultsListContainer) {
+ return serverData.asArrayOrEmpty(resultsListContainer, "results.contents");
+}
+/**
+ * Get the metrics dictionary included in the meta data of a mediaApi Data object.
+ * @param {MetaDataProviding} metaDataProvidingObject
+ * @returns {MapLike<string> | null}
+ */
+export function metricsFromMediaApiObject(metaDataProvidingObject) {
+ return serverData.asDictionary(metaDataProvidingObject, "meta.metrics");
+}
+// endregion
+//# sourceMappingURL=data-structure.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/network.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/network.js
new file mode 100644
index 0000000..2b01d40
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/network.js
@@ -0,0 +1,304 @@
+/**
+ * Created by joel on 20/2/2018.
+ *
+ * This `network.ts` is the Media API arm of the netwok fetch requests.
+ * It is built on `Network` object and provides standard functionality specific for MAPI.
+ *
+ * @see `src/network.ts` for fetching from non-Media API endpoints
+ */
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as serverData from "../json-parsing/server-data";
+import { MetricsIdentifierType } from "../metrics/metrics-identifiers-cache";
+import { ResponseMetadata } from "../network/network";
+import * as urls from "../network/urls";
+import { extractMetaAssociationFromData } from "./data-fetching";
+import * as urlBuilder from "./url-builder";
+const secondaryUserIdHeaderName = "X-Apple-AppStore-UserId-Secondary";
+/**
+ * Implements the MAPI fetch, building URL from MAPI Request and opaquely managing initial token request and refreshes.
+ *
+ * @param {Request} request MAPI Request to fetch with.
+ * @param {FetchOptions} options? FetchOptions for the MAPI request.
+ * @returns {Promise<Type>} Promise resolving to some type for given MAPI request.
+ */
+export async function fetchData(objectGraph, request, options) {
+ var _a;
+ const startTime = Date.now();
+ const token = await objectGraph.mediaToken.refreshToken();
+ const fetchTimingMetricsBuilder = objectGraph.fetchTimingMetricsBuilder;
+ const requestOptions = options !== null && options !== void 0 ? options : {};
+ const personalizationIdentifier = (_a = objectGraph.personalizationMetricsIdentifiersCache) === null || _a === void 0 ? void 0 : _a.getMetricsIdForType(MetricsIdentifierType.user);
+ if (isSome(personalizationIdentifier)) {
+ if (isSome(requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.headers)) {
+ requestOptions.headers[secondaryUserIdHeaderName] = personalizationIdentifier;
+ }
+ else {
+ requestOptions.headers = {
+ [secondaryUserIdHeaderName]: personalizationIdentifier,
+ };
+ }
+ }
+ const response = await fetchWithToken(objectGraph, request, token, requestOptions, false, fetchTimingMetricsBuilder);
+ const endTime = Date.now();
+ if (request.canonicalUrl) {
+ response[ResponseMetadata.requestedUrl] = request.canonicalUrl;
+ }
+ const roundTripTimeIncludingWaiting = endTime - startTime;
+ if (roundTripTimeIncludingWaiting > 500) {
+ const longFetchUrl = urlBuilder.buildURLFromRequest(objectGraph, request).toString();
+ objectGraph.console.warn("Fetch took too long (" + roundTripTimeIncludingWaiting.toString() + "ms) " + longFetchUrl);
+ }
+ return response;
+}
+export function redirectParametersInUrl(objectGraph, url) {
+ const redirectURLParams = objectGraph.bag.redirectUrlWhitelistedQueryParams;
+ return redirectURLParams.filter((param) => { var _a; return serverData.isDefinedNonNull((_a = url.query) === null || _a === void 0 ? void 0 : _a[param]); });
+}
+/**
+ * Given a built URL, token, and options, calls into native networking APIs to fetch content.
+ *
+ * @param {string} url URL to fetch data from.
+ * @param {string} request The original request used to build this url.
+ * @param {string} token MAPI token key.
+ * @param {FetchOptions} options Fetch options for MAPI requests.
+ * @param {boolean} isRetry flag indicating whether this is a fetch retry following a 401 request, and media token was refreshed.
+ * @returns {Promise<Type>} Promise resolving to some type for given MAPI request.
+ */
+async function fetchWithToken(objectGraph, request, token, options = {}, isRetry = false, fetchTimingMetricsBuilder) {
+ var _a, _b;
+ const originalUrl = urlBuilder.buildURLFromRequest(objectGraph, request).toString();
+ // Removes all affiliate/redirect params for caching (https://connectme.apple.com/docs/DOC-577671)
+ const filteredURL = new urls.URL(originalUrl);
+ const redirectParameters = redirectParametersInUrl(objectGraph, filteredURL);
+ for (const param of redirectParameters) {
+ filteredURL.removeParam(param);
+ }
+ const filteredUrlString = filteredURL.toString();
+ let headers = options.headers;
+ if (!headers) {
+ headers = {};
+ }
+ headers["Authorization"] = "Bearer " + token;
+ const response = await objectGraph.network.fetch({
+ url: filteredUrlString,
+ headers: headers,
+ method: options.method,
+ body: options.requestBodyString,
+ timeout: options.timeout,
+ });
+ try {
+ if (response.status === 401 || response.status === 403) {
+ if (isRetry) {
+ throw Error("We refreshed the token but we still get 401 from the API");
+ }
+ objectGraph.mediaToken.resetToken();
+ return await objectGraph.mediaToken.refreshToken().then(async (newToken) => {
+ // Explicitly re-fetch with the original request so logging and metrics are correct
+ return await fetchWithToken(objectGraph, request, newToken, options, true, fetchTimingMetricsBuilder);
+ });
+ }
+ else if (response.status === 404) {
+ // item is not available in this storefront or perhaps not at all
+ throw noContentError();
+ }
+ else if (!response.ok) {
+ const error = new NetworkError(`Bad Status code ${response.status} for ${filteredUrlString}, original ${originalUrl}`);
+ error.statusCode = response.status;
+ throw error;
+ }
+ const parser = (resp) => {
+ var _a;
+ const parseStartTime = Date.now();
+ let result;
+ if (serverData.isNull(resp.body) || resp.body === "") {
+ if (resp.status === 204) {
+ // 204 indicates a success, but the response will typically be empty
+ // Create a fake result so that we don't throw an error when JSON parsing
+ const emptyData = {};
+ result = emptyData;
+ }
+ else {
+ throw noContentError();
+ }
+ }
+ else {
+ try {
+ result = JSON.parse(resp.body);
+ }
+ catch (e) {
+ let errorMessage = e.message;
+ if (["debug", "internal"].includes(objectGraph.client.buildType)) {
+ errorMessage = `${e.message}, body: ${resp.body}`;
+ }
+ throw new JSONParseError(errorMessage);
+ }
+ }
+ const parseEndTime = Date.now();
+ if (result) {
+ result[ResponseMetadata.pageInformation] = serverData.asJSONData(getPageInformationFromResponse(objectGraph, resp));
+ if (resp.metrics.length > 0) {
+ const metrics = {
+ ...resp.metrics[0],
+ parseStartTime: parseStartTime,
+ parseEndTime: parseEndTime,
+ };
+ result[ResponseMetadata.timingValues] = metrics;
+ }
+ else {
+ const fallbackMetrics = {
+ pageURL: resp.url,
+ parseStartTime,
+ parseEndTime,
+ };
+ result[ResponseMetadata.timingValues] = fallbackMetrics;
+ }
+ result[ResponseMetadata.contentMaxAge] = getContentTimeToLiveFromResponse(objectGraph, resp);
+ // If we have an empty data object, throw a 204 (No Content).
+ if (Array.isArray(result.data) &&
+ serverData.isArrayDefinedNonNullAndEmpty(result.data) &&
+ !serverData.asBooleanOrFalse(options.allowEmptyDataResponse)) {
+ throw noContentError();
+ }
+ if (Array.isArray(result.data) && request.originalOrdering.length > 1) {
+ result.data = buildHydratedDataListResponse(objectGraph, request.originalOrdering, (_a = result.data) !== null && _a !== void 0 ? _a : [], request.supplementaryMetadataAssociations);
+ }
+ result[ResponseMetadata.requestedUrl] = originalUrl;
+ }
+ return result;
+ };
+ if (isSome(fetchTimingMetricsBuilder)) {
+ return fetchTimingMetricsBuilder.measureParsing(response, parser);
+ }
+ else {
+ return parser(response);
+ }
+ }
+ catch (e) {
+ if (e instanceof NetworkError) {
+ throw e;
+ }
+ const correlationKey = (_a = response.headers["x-apple-jingle-correlation-key"]) !== null && _a !== void 0 ? _a : (_b = response.metrics[0]) === null || _b === void 0 ? void 0 : _b.clientCorrelationKey;
+ throw new Error(`Error Fetching - filtered: ${filteredUrlString}, original: ${originalUrl}, correlationKey: ${correlationKey !== null && correlationKey !== void 0 ? correlationKey : "N/A"}, ${e.name}, ${e.message}`);
+ }
+}
+export class NetworkError extends Error {
+}
+class JSONParseError extends Error {
+}
+export function noContentError() {
+ const error = new NetworkError(`No content`);
+ error.statusCode = 204;
+ return error;
+}
+export function notFoundError() {
+ const error = new NetworkError("Not found");
+ error.statusCode = 404;
+ return error;
+}
+const serverInstanceHeader = "x-apple-application-instance";
+const environmentDataCenterHeader = "x-apple-application-site";
+function getPageInformationFromResponse(objectGraph, response) {
+ const storeFrontHeader = objectGraph.client.storefrontIdentifier;
+ let storeFront = null;
+ if ((storeFrontHeader === null || storeFrontHeader === void 0 ? void 0 : storeFrontHeader.length) > 0) {
+ const storeFrontHeaderComponents = storeFrontHeader.split("-");
+ if (serverData.isDefinedNonNullNonEmpty(storeFrontHeaderComponents)) {
+ storeFront = storeFrontHeaderComponents[0];
+ }
+ }
+ return {
+ serverInstance: response.headers[serverInstanceHeader],
+ storeFrontHeader: storeFrontHeader,
+ language: objectGraph.bag.language,
+ storeFront: storeFront,
+ environmentDataCenter: response.headers[environmentDataCenterHeader],
+ };
+}
+function getContentTimeToLiveFromResponse(objectGraph, response) {
+ const cacheControlHeaderKey = Object.keys(response.headers).find((key) => key.toLowerCase() === "cache-control");
+ if (serverData.isNull(cacheControlHeaderKey) || cacheControlHeaderKey === "") {
+ return null;
+ }
+ const headerValue = response.headers[cacheControlHeaderKey];
+ if (serverData.isNullOrEmpty(headerValue)) {
+ return null;
+ }
+ const matches = headerValue.match(/max-age=(\d+)/);
+ if (serverData.isNull(matches) || matches.length < 2) {
+ return null;
+ }
+ return serverData.asNumber(matches[1]);
+}
+/**
+ * Builds the hydrated list of shelf items, based on the data in the response, and the original unhydated items.
+ */
+function buildHydratedDataListResponse(objectGraph, unhydratedData, hydratedDataCollection, associations = []) {
+ // Create a map of all the hydrated data items from the response, using the resource type and ID as the key
+ const hydratedDataMap = {};
+ for (const dataItem of hydratedDataCollection) {
+ const key = dataMapKey(objectGraph, dataItem.type, dataItem.id);
+ hydratedDataMap[key] = dataItem;
+ }
+ // Next iterate through the unhyrated items in their original order, and swap in the hydrated item.
+ const hydratedItems = [];
+ for (const unhydratedItem of unhydratedData) {
+ const key = dataMapKey(objectGraph, unhydratedItem.type, unhydratedItem.id);
+ const hydratedItem = hydratedDataMap[key];
+ if (isSome(hydratedItem)) {
+ /// Start with a base of what was there already, this is to ensure we don't lose any existing meta data
+ /// if some of the requested associations are not present in the response.
+ if (serverData.isDefinedNonNullNonEmpty(associations)) {
+ hydratedItem.meta = {
+ ...unhydratedItem.meta,
+ };
+ for (const association of associations) {
+ hydrateMetaAssociationIfNecessary(objectGraph, association, hydratedItem, unhydratedItem, hydratedDataMap);
+ }
+ }
+ hydratedItems.push(hydratedItem);
+ }
+ }
+ return hydratedItems;
+}
+/**
+ * Creates a key to use in a mapping of data items.
+ * @param objectGraph Current object graph
+ * @param data The data item
+ * @returns A string composed from the data item ID and resource type
+ */
+function dataMapKey(objectGraph, resourceType, id) {
+ return `${resourceType}_${id}`;
+}
+/**
+ * @param objectGraph The current object graph
+ * @param association The association we'd like to hydrate in the meta object
+ * @param unhydratedItem The original MAPI data item to look for editorial card, and fill in with a fetched card if there is one
+ * @param hydratedDataMap The data map of all the fetched items keyed by id / type
+ */
+function hydrateMetaAssociationIfNecessary(objectGraph, association, hydratedItem, unhydratedItem, hydratedDataMap) {
+ var _a;
+ if (isNothing(hydratedItem.meta)) {
+ hydratedItem.meta = {
+ associations: {},
+ };
+ }
+ else if (isNothing(hydratedItem.meta.associations)) {
+ hydratedItem.meta.associations = {};
+ }
+ const unhydratedAssociationsData = extractMetaAssociationFromData(association, unhydratedItem);
+ if (serverData.isDefinedNonNullNonEmpty(unhydratedAssociationsData)) {
+ const hydratedAssociationData = [];
+ for (const unhydratedAssociation of unhydratedAssociationsData) {
+ const associationDataKey = dataMapKey(objectGraph, unhydratedAssociation.type, unhydratedAssociation.id);
+ const associationData = hydratedDataMap[associationDataKey];
+ if (isSome(associationData)) {
+ hydratedAssociationData.push(associationData);
+ }
+ }
+ const hydratedAssociations = (_a = serverData.asDictionary(hydratedItem.meta.associations)) !== null && _a !== void 0 ? _a : {};
+ hydratedAssociations[association] = {
+ data: hydratedAssociationData,
+ };
+ }
+}
+//# sourceMappingURL=network.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/platform-attributes.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/platform-attributes.js
new file mode 100644
index 0000000..3849624
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/platform-attributes.js
@@ -0,0 +1,143 @@
+import { isNothing } from "@jet/environment/types/optional";
+import * as serverData from "../json-parsing/server-data";
+import * as attributes from "./attributes";
+/**
+ * There are nested attributes that are platform-specific. If provided with a platform for which the response data has
+ * platform-specific attributes, this function will return those attributes.
+ * @param {Data} data The data for which to determine attributes.
+ * @param {AttributePlatform} platform The platform to fetch the attributes for
+ * @returns {any} If a platform is provided, returns `true` exactly when platform-specific attributes exist for that
+ * platform. If no platform is provided, it simply returns the data's top-level attributes.
+ */
+function attributesForPlatform(data, platform) {
+ const allPlatformAttributes = attributes.attributeAsDictionary(data, "platformAttributes");
+ return serverData.traverse(allPlatformAttributes, platform !== null && platform !== void 0 ? platform : undefined);
+}
+/**
+ * Determines if attributes exist for a given platform
+ * @param {Data} data The data to check
+ * @param {AttributePlatform} platform The platform to check
+ * @returns {boolean} True if the platform exists in the data's platform attributes. False if not.
+ */
+export function hasPlatformAttribute(data, platform) {
+ const platformAttributes = attributesForPlatform(data, platform);
+ return serverData.isDefinedNonNullNonEmpty(platformAttributes);
+}
+/**
+ * Retrieve the specified attribute from the data as a dictionary
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The path of the attribute.
+ * @returns The value for the requested attribute
+ */
+export function platformAttributeAsDictionary(data, platform, attributePath) {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (serverData.isNull(platformAttributes)) {
+ return null;
+ }
+ return serverData.asDictionary(platformAttributes, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as an array.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The path of the attribute.
+ * @returns {any[]} The attribute value as an array.
+ */
+export function platformAttributeAsArray(data, platform, attributePath) {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (isNothing(platformAttributes)) {
+ return null;
+ }
+ return serverData.asArray(platformAttributes, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as an array, coercing to an empty array if the object is not an array.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The path of the attribute.
+ * @returns {any[]} The attribute value as an array.
+ */
+export function platformAttributeAsArrayOrEmpty(data, platform, attributePath) {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (serverData.isNull(platformAttributes)) {
+ return [];
+ }
+ return serverData.asArrayOrEmpty(platformAttributes, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as a string.
+ *
+ * If the attribute lives under the platform-specific attributes, then a platform may be provided to properly call in to
+ * the nested structure.
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The object path for the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {string} The attribute value as a string.
+ */
+export function platformAttributeAsString(data, platform, attributePath, policy = "coercible") {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (serverData.isNull(platformAttributes)) {
+ return null;
+ }
+ return serverData.asString(platformAttributes, attributePath, policy);
+}
+/**
+ * Retrieve the specified attribute from the data as a boolean.
+ *
+ * If the attribute lives under the platform-specific attributes, then a platform may be provided to properly call in to
+ * the nested structure.
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The path of the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {boolean} The attribute value as a boolean.
+ */
+export function platformAttributeAsBoolean(data, platform, attributePath, policy = "coercible") {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (serverData.isNull(platformAttributes)) {
+ return null;
+ }
+ return serverData.asBoolean(platformAttributes, attributePath, policy);
+}
+/**
+ * Retrieve the specified attribute from the data as a boolean, which will be `false` if the attribute does not exist.
+ *
+ * If the attribute lives under the platform-specific attributes, then a platform may be provided to properly call in to
+ * the nested structure.
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The path of the attribute.
+ * @returns {boolean} The attribute value as a boolean, coercing to `false` if the value is not present..
+ */
+export function platformAttributeAsBooleanOrFalse(data, platform, attributePath) {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (serverData.isNull(platformAttributes)) {
+ return false;
+ }
+ return serverData.asBooleanOrFalse(platformAttributes, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as a number.
+ *
+ * If the attribute lives under the platform-specific attributes, then a platform may be provided to properly call in to
+ * the nested structure.
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The path of the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {boolean} The attribute value as a number.
+ */
+export function platformAttributeAsNumber(data, platform, attributePath, policy = "coercible") {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (serverData.isNull(platformAttributes)) {
+ return null;
+ }
+ return serverData.asNumber(platformAttributes, attributePath, policy);
+}
+// endregion
+//# sourceMappingURL=platform-attributes.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/relationships.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/relationships.js
new file mode 100644
index 0000000..0f96574
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/relationships.js
@@ -0,0 +1,43 @@
+import * as serverData from "../json-parsing/server-data";
+export function hasRelationship(data, relationshipType, checkForContent = true) {
+ const relationshipDataContainer = relationship(data, relationshipType);
+ if (!relationshipDataContainer) {
+ return false;
+ }
+ if (!relationshipDataContainer.data || (checkForContent && relationshipDataContainer.data.length === 0)) {
+ return false;
+ }
+ return true;
+}
+export function relationship(data, relationshipType) {
+ if (serverData.isDefinedNonNull(data)) {
+ return serverData.asInterface(data.relationships, relationshipType);
+ }
+ return null;
+}
+export function relationshipViewsContainer(data, relationshipType) {
+ return serverData.asInterface(data.views, relationshipType);
+}
+export function relationshipData(objectGraph, data, relationshipType) {
+ const relationshipDataArray = serverData.asArrayOrEmpty(data.relationships, [
+ relationshipType,
+ "data",
+ ]);
+ if (relationshipDataArray.length === 0) {
+ return null;
+ }
+ if (relationshipDataArray.length > 1) {
+ objectGraph.console.warn(`there was an array of relationships when only the first was asked for in relationship ${relationshipType}`);
+ }
+ return relationshipDataArray[0];
+}
+export function relationshipCollection(data, relationshipType, allowNulls = false) {
+ if (!hasRelationship(data, relationshipType, false) && allowNulls) {
+ return null;
+ }
+ return serverData.asArrayOrEmpty(data.relationships, [relationshipType, "data"]);
+}
+export function relationshipViewsCollection(data, relationshipType) {
+ return serverData.asArrayOrEmpty(data.views, [relationshipType, "data"]);
+}
+//# sourceMappingURL=relationships.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/url-builder.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/url-builder.js
new file mode 100644
index 0000000..955529c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/url-builder.js
@@ -0,0 +1,381 @@
+/**
+ * Created by joel on 11/4/2018.
+ */
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as serverData from "../json-parsing/server-data";
+import * as urls from "../network/urls";
+import * as attributes from "./attributes";
+/// this is exposed for compatibility. If you find yourself needing to use this outside of the media api module you
+/// probably have code smell. DO NOT USE.
+export function buildURLFromRequest(objectGraph, request) {
+ var _a, _b;
+ const baseURL = request.href && request.href.length > 0
+ ? baseURLForHref(request.href)
+ : baseURLForResourceType(objectGraph, request.isMixedMediaRequest, request.resourceType, request.countryCodeOverride);
+ const mediaApiURL = new urls.URL(baseURL);
+ if (serverData.isDefinedNonNullNonEmpty(request.resourceType)) {
+ for (const pathComponent of pathComponentsForRequest(request.resourceType, request.targetResourceType)) {
+ mediaApiURL.append("pathname", pathComponent);
+ }
+ }
+ if (request.isMixedMediaRequest) {
+ for (const [resourceType, ids] of request.idsByResourceType.entries()) {
+ mediaApiURL.param(`ids[${resourceType}]`, Array.from(ids).sort().join(","));
+ }
+ }
+ else if (request.ids.size > 1 || request.useIdsAsQueryParam) {
+ mediaApiURL.param("ids", Array.from(request.ids).sort().join(","));
+ }
+ else if (request.ids.size === 1) {
+ const id = request.ids.values().next().value;
+ mediaApiURL.append("pathname", id);
+ }
+ if (request.resourceType !== undefined) {
+ const trailingPathComponent = trailingPathComponentForResourceType(request.resourceType);
+ if (serverData.isDefinedNonNullNonEmpty(trailingPathComponent)) {
+ mediaApiURL.append("pathname", trailingPathComponent);
+ }
+ }
+ mediaApiURL.param("platform", (_a = request.platform) !== null && _a !== void 0 ? _a : undefined);
+ if (request.additionalPlatforms.size > 0) {
+ mediaApiURL.param("additionalPlatforms", Array.from(request.additionalPlatforms).sort().join(","));
+ }
+ /**
+ * Add `extend` attributes.
+ * Note that when `useCustomAttributes` is true, there is `customArtwork` param even when `attributeIncludes` is initially empty.
+ * This due MAPI auto-extend for `artwork`, and lack of auto-extend for `customArtwork`
+ */
+ if (request.attributeIncludes.size > 0 || request.useCustomAttributes) {
+ let extendAttributes = Array.from(request.attributeIncludes);
+ if (request.useCustomAttributes) {
+ extendAttributes = convertRequestAttributesToCustomAttributes(objectGraph, extendAttributes);
+ }
+ extendAttributes.sort();
+ mediaApiURL.param("extend", extendAttributes.join(","));
+ }
+ // Add age restriction if present.
+ if (serverData.isDefinedNonNull(request.ageRestriction) && objectGraph.bag.enableAgeRatingFilter) {
+ mediaApiURL.param("restrict[ageRestriction]", request.ageRestriction.toString());
+ }
+ // Automatically extend iOS catalog requests for apps to include appBinaryTraits.
+ if (request.includeAppBinaryTraitsAttribute) {
+ request.includingScopedAttributes("apps", ["appBinaryTraits"]);
+ }
+ if (serverData.isDefinedNonNull(request.scopedAttributeIncludes)) {
+ for (const [dataType, scopedIncludes] of request.scopedAttributeIncludes.entries()) {
+ mediaApiURL.param(`extend[${dataType}]`, Array.from(scopedIncludes).sort().join(","));
+ }
+ }
+ if (request.relationshipIncludes.size > 0) {
+ mediaApiURL.param("include", Array.from(request.relationshipIncludes).sort().join(","));
+ }
+ if (serverData.isDefinedNonNull(request.scopedRelationshipIncludes)) {
+ for (const [dataType, scopedIncludes] of request.scopedRelationshipIncludes.entries()) {
+ mediaApiURL.param(`include[${dataType}]`, Array.from(scopedIncludes).sort().join(","));
+ }
+ }
+ if (serverData.isDefinedNonNull(request.metaIncludes)) {
+ for (const [dataType, scopedMeta] of request.metaIncludes.entries()) {
+ mediaApiURL.param(`meta[${dataType}]`, Array.from(scopedMeta).sort().join(","));
+ }
+ }
+ if (serverData.isSetDefinedNonNullNonEmpty(request.viewsIncludes)) {
+ mediaApiURL.param("views", Array.from(request.viewsIncludes).sort().join(","));
+ }
+ if (serverData.isDefinedNonNull(request.kindIncludes)) {
+ for (const [dataType, scopedMeta] of request.kindIncludes.entries()) {
+ mediaApiURL.param(`kinds[${dataType}]`, Array.from(scopedMeta).sort().join(","));
+ }
+ }
+ if (serverData.isDefinedNonNull(request.associateIncludes)) {
+ for (const [dataType, scopedAssociate] of request.associateIncludes.entries()) {
+ mediaApiURL.param(`associate[${dataType}]`, Array.from(scopedAssociate).sort().join(","));
+ }
+ }
+ if (serverData.isDefinedNonNull(request.scopedAvailableInIncludes)) {
+ for (const [dataType, scopedAvailableIn] of request.scopedAvailableInIncludes.entries()) {
+ mediaApiURL.param(`availableIn[${dataType}]`, Array.from(scopedAvailableIn).sort().join(","));
+ }
+ }
+ if (serverData.isDefinedNonNullNonEmpty(request.fields)) {
+ let extendedFields = Array.from(request.fields);
+ if (request.useCustomAttributes) {
+ extendedFields = convertRequestFieldsToCustomFields(extendedFields);
+ }
+ request.fields.sort();
+ mediaApiURL.param("fields", extendedFields.join(","));
+ }
+ if (serverData.isDefinedNonNull(request.limit) && request.limit > 0) {
+ mediaApiURL.param(`limit`, `${request.limit}`);
+ }
+ if (serverData.isDefinedNonNull(request.sparseLimit)) {
+ mediaApiURL.param(`sparseLimit`, `${request.sparseLimit}`);
+ }
+ if (serverData.isDefinedNonNull(request.scopedSparseLimit)) {
+ for (const [dataType, scopedLimit] of request.scopedSparseLimit.entries()) {
+ mediaApiURL.param(`sparseLimit[${dataType}]`, String(scopedLimit));
+ }
+ }
+ if (serverData.isDefinedNonNull(request.sparseCount)) {
+ mediaApiURL.param(`sparseCount`, `${request.sparseCount}`);
+ }
+ for (const relationshipID of Object.keys(request.relationshipLimits).sort()) {
+ const limit = request.relationshipLimits[relationshipID];
+ mediaApiURL.param(`limit[${relationshipID}]`, `${limit}`);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(request.additionalQuery)) {
+ mediaApiURL.append("query", request.additionalQuery);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(request.searchTerm)) {
+ // Search hints shouldn't add `search` to the end of the path name as the correct final path
+ // is `v1/catalog/us/search/suggestions`, which is handled by `trailingPathComponentForResourceType()`
+ // Search hints also shouldn't have the bubble param
+ if (isNothing(request.resourceType) || request.resourceType !== "search-hints") {
+ mediaApiURL.append("pathname", "search");
+ mediaApiURL.param("bubble[search]", request.searchTypes.join(","));
+ }
+ mediaApiURL.param("term", request.searchTerm);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(request.enabledFeatures)) {
+ mediaApiURL.param("with", request.enabledFeatures.join(","));
+ }
+ if (serverData.isDefinedNonNullNonEmpty(request.context)) {
+ mediaApiURL.param("contexts", request.context);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(request.filterType) &&
+ serverData.isDefinedNonNullNonEmpty(request.filterValue)) {
+ mediaApiURL.param(`filter[${request.filterType}]`, request.filterValue);
+ }
+ const language = objectGraph.bag.mediaApiLanguage;
+ // Only attach the language query param if:
+ // - there is a language available in the bag, and
+ // - a language has not been manually attached to the request. This is used for special situations to override the language for particular content,
+ // so it should take precedence over the default.
+ if (serverData.isDefinedNonNull(language) && serverData.isNull(request.additionalQuery["l"])) {
+ mediaApiURL.param("l", language);
+ }
+ mediaApiURL.host = (_b = hostForUrl(objectGraph, mediaApiURL, request)) !== null && _b !== void 0 ? _b : undefined;
+ mediaApiURL.protocol = "https";
+ return mediaApiURL;
+}
+/**
+ * Get the media api base url for all requests.
+ * @param objectGraph Current object graph
+ * @param isMixedCatalogRequest Whether the request intends to use mixed catalog
+ * @param type The request resource type
+ * @param overrideCountryCode A country code to override the bag value.
+ * @returns A built base URL string
+ */
+function baseURLForResourceType(objectGraph, isMixedCatalogRequest, type, overrideCountryCode) {
+ switch (type) {
+ case "personalization-data":
+ case "reviews":
+ case "app-distribution":
+ return `/v1/${endpointTypeForResourceType(type)}/`;
+ default:
+ const countryCode = isSome(overrideCountryCode) && overrideCountryCode.length > 0
+ ? overrideCountryCode
+ : objectGraph.bag.mediaCountryCode;
+ const baseURL = `/v1/${endpointTypeForResourceType(type)}/${countryCode}`;
+ return isMixedCatalogRequest ? baseURL : `${baseURL}/`;
+ }
+}
+/**
+ * Get the media api base url for all requests that already have an href.
+ * @return {string}
+ */
+function baseURLForHref(href) {
+ return href;
+}
+function endpointTypeForResourceType(type) {
+ switch (type) {
+ case "apps":
+ case "app-events":
+ case "arcade-apps":
+ case "app-bundles":
+ case "charts":
+ case "contents":
+ case "developers":
+ case "eula":
+ case "in-apps":
+ case "multiple-system-operators":
+ case "user-reviews":
+ case "customers-also-bought-apps-with-download-intent":
+ return "catalog";
+ case "categories":
+ case "editorial-pages":
+ case "editorial-items":
+ case "editorial-item-groups":
+ case "editorial-elements":
+ case "groupings":
+ case "multiplex":
+ case "multirooms":
+ case "rooms":
+ case "today":
+ case "collections":
+ return "editorial";
+ case "ratings":
+ return "ratings";
+ case "personalization-data":
+ case "reviews":
+ return "me";
+ case "upsellMarketingItem":
+ case "landing":
+ return "engagement";
+ case "landing:new-protocol":
+ return "recommendations";
+ case "personal-recommendations":
+ return "recommendations";
+ case "engagement-data":
+ return "engagement";
+ case "app-distribution":
+ return "listing";
+ default:
+ return "catalog";
+ }
+}
+/**
+ * The path component to add for the given resource
+ */
+function pathComponentsForRequest(resourceType, targetResourceType) {
+ switch (resourceType) {
+ case "eula":
+ if (targetResourceType === undefined) {
+ return [resourceType]; // Might be modelled better as an error.
+ }
+ else {
+ return [resourceType, targetResourceType];
+ }
+ case "landing:new-protocol":
+ return [];
+ case "landing":
+ if (targetResourceType === undefined) {
+ return ["search", resourceType]; // Might be modelled better as an error.
+ }
+ else {
+ return ["search", resourceType, targetResourceType];
+ }
+ case "user-reviews":
+ return ["apps"];
+ case "reviews":
+ return ["reviews", "apps"];
+ case "multiplex":
+ return ["multiplex"];
+ case "upsellMarketingItem":
+ return ["upsell", "marketing-items"];
+ case "trending-contents":
+ return ["search", resourceType];
+ case "customers-also-bought-apps-with-download-intent":
+ return ["apps"];
+ case "searchLanding:see-all":
+ return [];
+ case "search-hints":
+ return [];
+ case "app-distribution":
+ return ["apps"];
+ default:
+ return [resourceType];
+ }
+}
+/**
+ * Add a component to the end of the path for the given resource
+ */
+function trailingPathComponentForResourceType(type) {
+ switch (type) {
+ case "user-reviews":
+ return "reviews";
+ case "customers-also-bought-apps-with-download-intent":
+ return "view/customers-also-bought-apps-with-download-intent";
+ case "collections":
+ return "contents";
+ case "searchLanding:see-all":
+ return "view/see-all";
+ case "search-hints":
+ return "search/suggestions";
+ default:
+ return null;
+ }
+}
+function hostForUrl(objectGraph, url, request) {
+ var _a;
+ const path = (_a = url.pathname) !== null && _a !== void 0 ? _a : "";
+ let host = null;
+ if (request.isStorePreviewRequest) {
+ host = objectGraph.bag.mediaPreviewHost;
+ }
+ else if (request.isMediaRealmRequest) {
+ host = objectGraph.bag.mediaRealmHost;
+ }
+ else if (path.includes("search/landing")) {
+ // Special case <rdar://problem/50185140> RFW3: Use bag key "apps-media-api-edge-end-points" for "search/landing" end-point
+ // until we figure out a better way to test the paths
+ const useEdgeForSearchLanding = objectGraph.bag.edgeEndpoints.indexOf("landing") !== -1;
+ host = useEdgeForSearchLanding ? objectGraph.bag.mediaEdgeHost(objectGraph) : objectGraph.bag.mediaHost;
+ }
+ else if (request.resourceType === "app-distribution" && isSome(objectGraph.bag.appDistributionMediaAPIHost)) {
+ host = objectGraph.bag.appDistributionMediaAPIHost;
+ }
+ else if (request.isMixedMediaRequest && objectGraph.bag.mediaAPICatalogMixedShouldUseEdge) {
+ // CatalogMixed endpoint should be routed to edge when the bag is enabled.
+ host = objectGraph.bag.mediaEdgeHost(objectGraph);
+ }
+ else if (objectGraph.bag.edgeEndpoints.map((endpoint) => path.includes(endpoint)).reduce(truthReducer, false)) {
+ if (path.includes("search") && !path.includes("view/see-all")) {
+ host = objectGraph.bag.mediaEdgeSearchHost;
+ }
+ else {
+ host = objectGraph.bag.mediaEdgeHost(objectGraph);
+ }
+ }
+ else {
+ host = objectGraph.bag.mediaHost;
+ }
+ if (serverData.isNull(host)) {
+ host = "api.apps.apple.com";
+ }
+ return host;
+}
+const truthReducer = (accumulator, current) => accumulator || current;
+/**
+ * Performs a conversion for given attribute to fetch the customAttribute variant of it.
+ * @param objectGraph The object graph
+ * @param attribute Attribute to convert if needed, e.g. `artwork`
+ * @returns `string` attribute that is custom equivalent of `attribute`, or `attribute` unmodified.
+ */
+function convertRequestAttributesToCustomAttributes(objectGraph, requestAttributes) {
+ const convertedAttributes = requestAttributes.map((attribute) => {
+ var _a;
+ return (_a = attributes.attributeKeyAsCustomAttributeKey(attribute)) !== null && _a !== void 0 ? _a : attribute;
+ });
+ /**
+ * `artwork` is an autoincluded resources, so `attributes` usually doesn't contain this explicitly :(
+ * Per MAPI contract, we "autoinclude" `customArtwork` explicitly for requests with custon attributes.
+ */
+ convertedAttributes.push("customArtwork");
+ /**
+ * `iconArtwork` is not autoincluded, but we need to ensure it is always requested even alongside its
+ * custom counterpart, `customIconArtwork`. This is because we might be viewing a macOS only app on iOS,
+ * where custom attributes are supported, but not available for macOS apps.
+ */
+ if (requestAttributes.includes("iconArtwork")) {
+ convertedAttributes.push("iconArtwork");
+ }
+ /**
+ * `customDeepLink` is always desired as an included resource in case an app decides to use a custom tap destination.
+ * Per MAPI contract, we "autoinclude" `customDeepLink` explicitly for all requests with custom attributes.
+ */
+ convertedAttributes.push("customDeepLink");
+ return convertedAttributes;
+}
+/**
+ * Performs the conversion for given field value (which may specify `attributes` keys) to customAttribute variant of it.
+ */
+function convertRequestFieldsToCustomFields(requestFields) {
+ const convertedFields = requestFields.map((fieldName) => {
+ var _a;
+ return (_a = attributes.attributeKeyAsCustomAttributeKey(fieldName)) !== null && _a !== void 0 ? _a : fieldName;
+ });
+ // DON'T include `customArtwork` for request `field` conversion. Only specify if `artwork` was initially in `requestFields`.
+ return convertedFields;
+}
+//# sourceMappingURL=url-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/util.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/util.js
new file mode 100644
index 0000000..77ed5c2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/util.js
@@ -0,0 +1,185 @@
+import { noContentError, notFoundError } from "./network";
+/**
+ * Validate an untrusted adam ID without contacting the server.
+ *
+ * This allows avoiding the work of calling the server with completely bogus
+ * IDs. It's scoped to web since ID formats can change and web can be updated
+ * easily (whereas backporting to older native clients is trickier).
+ *
+ * @param {AppStoreObjectGraph} objectGraph
+ * @param {string} id - the Adam ID to validate
+ * @returns {void} undefined if valid, throws a `NetworkError` (204) otherwise
+ */
+export function validateAdamId(objectGraph, id) {
+ if (objectGraph.client.isWeb && !isAdamId(id)) {
+ throw noContentError();
+ }
+}
+/**
+ * Check if an ID is a valid Adam ID.
+ *
+ * @param {string} id - string to validate
+ * @return {boolean} true if valid, false otherwise
+ */
+function isAdamId(id) {
+ // Media API actually validates if the number <= 2^63-1. But doubles in
+ // JavaScript cannot precisely represent this (the closest number is >2^63)
+ // so checking this requires BigInt, which might not be available. At the
+ // end of the day, checking this is likely not worth the complexity. 2^63-1
+ // is 9223372036854775807 so we just restrict the number of digits.
+ //
+ // See: https://github.pie.apple.com/its/amp-enums/blob/f75500b44f871f35ba3ce459a5ff4c9f225e71b0/src/main/java/com/apple/jingle/store/IdSpace.java#L131-L140
+ // See: https://github.com/google/guava/blob/869a75a1e3ff85d36672a3cd154772dc90c7b3d2/guava/src/com/google/common/primitives/Longs.java#L400-L440
+ // See: https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/Long.html#MAX_VALUE
+ return /^\d{1,19}$/.test(id);
+}
+/**
+ * Validate an untrusted Featured Content ID without contacting the server.
+ *
+ * This allows avoiding the work of calling the server with completely bogus
+ * IDs. It's scoped to web since ID formats can change and web can be updated
+ * easily (whereas backporting to older native clients is trickier).
+ *
+ * @param {AppStoreObjectGraph} objectGraph
+ * @param {string} id the FC ID to validate
+ * @returns {void} undefined if valid, throws a `NetworkError` (204) otherwise
+ */
+export function validateFcId(objectGraph, id) {
+ if (objectGraph.client.isWeb && !isFcId(id)) {
+ throw noContentError();
+ }
+}
+/**
+ * Check if an ID is a valid Featured Content (FC) ID.
+ *
+ * @param {string} id - string to validate
+ * @return {boolean} true if valid, false otherwise
+ */
+function isFcId(id) {
+ // FcIds are actually Adam IDs under the hood.
+ // See: https://github.pie.apple.com/its/Jingle/blob/d2d051c9ef2891f72d5f02f5bbbf2d7748afa7b9/MZStoreComponents/src/main/java/com/apple/jingle/app/store/editorial/SFEditorialHelper.java#L293
+ return isAdamId(id);
+}
+/**
+ * Validate an untrusted (Chart) Genre ID without contacting the server.
+ *
+ * This allows avoiding the work of calling the server with completely bogus
+ * IDs. It's scoped to web since ID formats can change and web can be updated
+ * easily (whereas backporting to older native clients is trickier).
+ *
+ * @param {AppStoreObjectGraph} objectGraph
+ * @param {string} id the genre ID to validate
+ * @returns {void} undefined if valid, throws a `NetworkError` (204) otherwise
+ */
+export function validateGenreId(objectGraph, id) {
+ if (objectGraph.client.isWeb && !isGenreId(id)) {
+ throw noContentError();
+ }
+}
+/**
+ * Check if an ID is a valid (Chart) Genre ID.
+ *
+ * @param {string} id - string to validate
+ * @return {boolean} true if valid, false otherwise
+ */
+function isGenreId(id) {
+ // Genre IDs are Java int (signed 32-bit).
+ //
+ // Technically negative is not excluded, but all charts are positive, so
+ // filter out negative.
+ //
+ // See: https://github.pie.apple.com/its/Jingle/blob/main/shared/reference-data-logic/MZReferenceDataLogic/src/main/java/com/apple/jingle/eo/MZGenreService.java#L555
+ return isPositiveJavaInt(id);
+}
+/**
+ * Validate an untrusted Grouping ID without contacting the server.
+ *
+ * This allows avoiding the work of calling the server with completely bogus
+ * IDs. It's scoped to web since ID formats can change and web can be updated
+ * easily (whereas backporting to older native clients is trickier).
+ *
+ * @param {AppStoreObjectGraph} objectGraph
+ * @param {string} id the grouping ID to validate
+ * @returns {void} undefined if valid, throws a `NetworkError` (204) otherwise
+ */
+export function validateGroupingId(objectGraph, id) {
+ if (objectGraph.client.isWeb && !isGroupingId(id)) {
+ throw noContentError();
+ }
+}
+/**
+ * Check if an ID is a valid Grouping ID.
+ *
+ * @param {string} id - string to validate
+ * @return {boolean} true if valid, false otherwise
+ */
+function isGroupingId(id) {
+ // Grouping IDs are Java int (signed 32-bit)
+ //
+ // Technically negative does not fail to parse, but they're all positive in
+ // practice.
+ //
+ // See: https://github.pie.apple.com/its/Jingle/blob/aaccec936f1feed227fd171ae66bb160cf38e497/MZStorePlatform/src/main/java/com/apple/jingle/store/mediaapi/util/SFMediaAPIEditorialUtil.java#L634
+ return isPositiveJavaInt(id);
+}
+/**
+ * Test if a string contains a Java int.
+ *
+ * @param {string} s - the string to test
+ * @returns {boolean} true if the string is a stringified Java int, false otherwise
+ */
+function isPositiveJavaInt(s) {
+ // Java int is 32-bit signed. We can check bounds since signed integers are
+ // exactly representable as doubles (actually up to 2^53 is representable
+ // exactly).
+ //
+ // See: https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/Integer.html#MAX_VALUE
+ return /^\d+$/.test(s) && parseInt(s, 10) <= 2147483647;
+}
+/**
+ * Validate an untrusted Editorial Shelf Collection ID without contacting the server.
+ *
+ * This allows avoiding the work of calling the server with completely bogus
+ * IDs. It's scoped to web since ID formats can change and web can be updated
+ * easily (whereas backporting to older native clients is trickier).
+ *
+ * @param {AppStoreObjectGraph} objectGraph
+ * @param {string} id the editorial shelf collection ID to validate
+ * @returns {void} undefined if valid, throws a `NetworkError` (204) otherwise
+ */
+export function validateEditorialShelfCollectionId(objectGraph, id) {
+ if (objectGraph.client.isWeb && !isEditorialShelfCollectionId(id)) {
+ throw noContentError();
+ }
+}
+/**
+ * Check if an ID is a valid editorial shelf collection ID.
+ *
+ * @param {string} id - string to validate
+ * @return {boolean} true if valid, false otherwise
+ */
+function isEditorialShelfCollectionId(id) {
+ // Ids are prefixed with "eds.". Beyond that they do have a UUID-type
+ // identifier that follows, but that seems more liable to change.
+ //
+ // See: https://github.pie.apple.com/its/amp-enums/blob/f75500b44f871f35ba3ce459a5ff4c9f225e71b0/src/main/java/com/apple/jingle/store/IdSpace.java#L43C5-L43C20
+ // See: https://github.pie.apple.com/its/amp-enums/blob/f75500b44f871f35ba3ce459a5ff4c9f225e71b0/src/main/java/com/apple/jingle/store/IdSpace.java#L250-L252
+ return id.startsWith("eds.");
+}
+/**
+ * Validates if a request can be performed for `vision` platform content, based on
+ * the feature flag is disabled.
+ *
+ * @param {AppStoreObjectGraph} objectGraph The application's state graph.
+ * @throws {NetworkError} Throws a 404 error if access is restricted.
+ * @returns {void}
+ */
+export function validateNeedsVisionRestriction(objectGraph) {
+ var _a;
+ if (objectGraph.client.isWeb &&
+ ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.previewPlatform) === "vision" &&
+ !objectGraph.bag.enableVisionPlatform) {
+ throw notFoundError();
+ }
+}
+//# sourceMappingURL=util.js.map \ No newline at end of file