diff options
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.js | 381 |
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 |
