summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/foundation/media/url-builder.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/foundation/media/url-builder.js')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/url-builder.js381
1 files changed, 381 insertions, 0 deletions
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