diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /node_modules/@jet-app/app-store/tmp/src/common/search/search-results-fetching.js | |
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/search/search-results-fetching.js')
| -rw-r--r-- | node_modules/@jet-app/app-store/tmp/src/common/search/search-results-fetching.js | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-fetching.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-fetching.js new file mode 100644 index 0000000..f04a8e1 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-fetching.js @@ -0,0 +1,496 @@ +/** + * Data Fetching for Search Results for: + * - Initial search requests + * - Content pagination requests + */ +import { isNothing } from "@jet/environment/types/optional"; +import * as models from "../../api/models"; +import { asBooleanOrFalse, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNullOrEmpty, } from "../../foundation/json-parsing/server-data"; +import { defaultAdditionalPlatformsForClient, Request } from "../../foundation/media/data-fetching"; +import { metricsFromMediaApiObject, } from "../../foundation/media/data-structure"; +import { fetchData } from "../../foundation/media/network"; +import { buildURLFromRequest } from "../../foundation/media/url-builder"; +import * as constants from "../../foundation/util/constants"; +import * as client from "../../foundation/wrappers/client"; +import * as appPromotionsCommon from "../app-promotions/app-promotions-common"; +import * as categories from "../categories"; +import { addVariantParametersToRequestForItems, shouldFetchCustomAttributes, } from "../product-page/product-page-variants"; +import { setPreviewPlatform } from "../preview-platform"; +import { SponsoredSearchRequestData } from "./search-ads"; +import { searchMetricsDataSetID } from "./search-common"; +import { fetchSponsoredSearchNativeAdvertData } from "./sponsored-search-fetching"; +import { dateFlooredToHour } from "../../foundation/util/date-util"; +import { shouldUsePrerenderedIconArtwork } from "../content/content"; +import { AppEventsAttributes } from "../../gameservicesui/src/foundation/media-api/requests/recommendation-request-types"; +export async function fetchSegmentedSearchResults(objectGraph, options) { + var _a; + // Primary search request + const searchRequest = (_a = createSearchRequest(objectGraph, options)) === null || _a === void 0 ? void 0 : _a.addingQuery("groupBy[search]", "platform"); + if (isNothing(searchRequest)) { + return null; + } + const fetchSearchResults = fetchData(objectGraph, searchRequest, undefined); + return await fetchSearchResults.then((catalogResponse) => { + renameDataSetIdKey(catalogResponse); + const data = { + catalogResponse: catalogResponse, + requestMetadata: { + requestDescriptor: options, + searchRequestUrl: buildURLFromRequest(objectGraph, searchRequest).toString(), // Searches are attributed to request url + }, + categoriesFilterData: null, + sponsoredSearchRequestData: null, + sponsoredSearchAdvertData: null, + }; + return data; + }); +} +/** + * Fetch a collection of data for displaying a single, sequential set of search results. + * @param options Parameters for the search being performed. + * @returns `Promise` for aggregate search result data, or `null` if `options` was invalid. + */ +export async function fetchSequentialSearchResultsData(objectGraph, options) { + // Advert targeting data from client + const sponsoredSearchRequestData = new SponsoredSearchRequestData(options.targetingData, objectGraph.random.nextUUID()); + // Primary search request + const searchRequest = createSearchRequest(objectGraph, options); + if (searchRequest === null) { + return null; + } + const fetchSearchResults = fetchData(objectGraph, searchRequest, createSearchFetchOptions(objectGraph, sponsoredSearchRequestData)); + // Search History + if (objectGraph.bag.mediaAPISearchFocusEnabled) { + const historyItem = { + term: options.term.trim(), + entity: options.searchEntity, + }; + // ** MAINTAINER'S NOTE ** + // Fire and forget this request to reduce delay showing search results on persisting recent searches that are not visible at this time. + objectGraph.onDeviceSearchHistoryManager.saveRecentSearchWithLimit(historyItem, 30); + } + // Optional requests + const fetchSponsoredSearchAds = fetchSponsoredSearchNativeAdvertData(objectGraph, sponsoredSearchRequestData, options.term, fetchSearchResults); + const fetchCategoriesOrNull = fetchCategoryFiltersDataIfNeeded(objectGraph); + return await Promise.all([fetchSearchResults, fetchSponsoredSearchAds, fetchCategoriesOrNull]).then(([catalogResponse, sponsoredSearchAdvertData, categoriesFilterData]) => { + var _a, _b, _c, _d; + renameDataSetIdKey(catalogResponse); + // Remember the last time a natural language search was performed. + if ((_c = (_b = (_a = catalogResponse.meta) === null || _a === void 0 ? void 0 : _a.results) === null || _b === void 0 ? void 0 : _b.search) === null || _c === void 0 ? void 0 : _c.naturalLanguage) { + const previousNLSQueryDate = objectGraph.storage.retrieveString("lastNLSQueryDate"); + const lastNLSQueryDate = dateFlooredToHour(new Date()).getTime().toString(); + objectGraph.storage.storeString("lastNLSQueryDate", lastNLSQueryDate); + // Notify ODJ on change using AMSEngagement. + (_d = objectGraph.amsEngagement) === null || _d === void 0 ? void 0 : _d.enqueueData({ + eventType: "lastNLSQueryDateChange", + app: "com.apple.AppStore", + oldState: previousNLSQueryDate, + newState: lastNLSQueryDate, + }); + } + const data = { + catalogResponse: catalogResponse, + categoriesFilterData: categoriesFilterData, + sponsoredSearchRequestData: sponsoredSearchRequestData, + sponsoredSearchAdvertData: sponsoredSearchAdvertData, + requestMetadata: { + requestDescriptor: options, + searchRequestUrl: buildURLFromRequest(objectGraph, searchRequest).toString(), // Searches are attributed to request url + }, + }; + return data; + }); +} +/** + * Fetch a collection of data for displaying several, grouped set of search results. + * @param options Parameters for the search being performed. + * @returns `Promise` for aggregate search result data, or `Promise` of `null` if `options` was invalid. + */ +export async function fetchPlatformGroupedSearchResultsData(objectGraph, options) { + // Primary search request + const searchRequest = createPlatformGroupedSearchRequest(objectGraph, options); + if (isNothing(searchRequest)) { + return null; + } + const fetchSearchResults = fetchData(objectGraph, searchRequest, createSearchFetchOptions(objectGraph)); + // Optional requests + const fetchCategoriesOrNull = fetchCategoryFiltersDataIfNeeded(objectGraph); + return await Promise.all([fetchSearchResults, fetchCategoriesOrNull]).then(([catalogResponse, categoriesFilterData]) => { + renameDataSetIdKey(catalogResponse); + const data = { + catalogResponse: catalogResponse, + categoriesFilterData: categoriesFilterData, + sponsoredSearchRequestData: null, + sponsoredSearchAdvertData: null, + requestMetadata: { + requestDescriptor: options, + searchRequestUrl: buildURLFromRequest(objectGraph, searchRequest).toString(), // Searches are attributed to request url + }, + }; + return data; + }); +} +/** + * Fetch a set of items for appearing in search results + * @param items Items to fetch. + */ +export async function fetchSearchResultItems(objectGraph, items) { + const request = createCatalogRequestForItems(objectGraph, items); + return await fetchData(objectGraph, request); +} +// endregion +// region Request Header +/** + * Create fetch options, annotating with `SponsoredSearchRequestData` header fields. + * @param adData Advert data to include in header. + */ +function createSearchFetchOptions(objectGraph, sponsoredSearchRequestData) { + const searchRequestHeader = {}; + if (sponsoredSearchRequestData && sponsoredSearchRequestData.validAdRequest()) { + searchRequestHeader[constants.appStoreClientRequestIdName] = sponsoredSearchRequestData.appStoreClientRequestId; + searchRequestHeader[constants.iAdRequestDataHeaderName] = sponsoredSearchRequestData.sponsoredSearchRequestData; + searchRequestHeader[constants.iAdRoutingInfoHeaderName] = sponsoredSearchRequestData.routingInfo; + } + return { + headers: searchRequestHeader, + }; +} +// endregion +// region Search Results Request +/** + * Set of relationships fetched as relation for search request / catalog requests + */ +const searchIncludeRelationships = ["apps", "top-apps"]; +/** + * Create the `Request` object to execute a search described by `options` + * @param options Options for search being executed. + * @returns `Request` configured for `options`, or `null` if `options` was invalid. + */ +export function createSearchRequest(objectGraph, options) { + var _a; + const term = (_a = options.term) === null || _a === void 0 ? void 0 : _a.trim(); + if (isNullOrEmpty(term)) { + return null; + } + const origin = options.origin; + const source = options.source; + const searchEntityOrNull = options.searchEntity; + const facetsOrNull = options.facets; + const selectedFacetOptionsOrNull = options.selectedFacetOptions; + const spellCheckEnabled = options.spellCheckEnabled; + const excludedTermsOrNull = options.excludedTerms; + const clientIdentifier = objectGraph.host.clientIdentifier; + const searchRequest = new Request(objectGraph) + .withSearchTerm(term) + .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph)) + .includingAttributes(includeAttributesForSearchResults(objectGraph)) + .includingScopedAttributes("editorial-items", ["showLabelInSearch"]) + .includingRelationshipsForUpsell(true) + .includingMacOSCompatibleIOSAppsWhenSupported() + .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph)); + setPreviewPlatform(objectGraph, searchRequest); + if (!objectGraph.client.isWatch && !objectGraph.client.isVision) { + searchRequest.includingRelationships(searchIncludeRelationships); + } + if (objectGraph.client.isVision) { + // Temporarily increase the sparseLimit on Vision to workaround lack of filtering for bincompat. + searchRequest.addingQuery("sparseLimit[developers:top-apps]", "12"); + } + if (appPromotionsCommon.appEventsAreEnabled(objectGraph)) { + searchRequest.includingAssociateKeys("apps", ["app-events"]); + searchRequest.includingScopedAttributes("app-events", AppEventsAttributes); + searchRequest.includingScopedRelationships("editorial-items", ["primary-content"]); + } + /** + * Tinker Filter + */ + if (asBooleanOrFalse(objectGraph.client.isTinkerWatch)) { + searchRequest.withFilter("contexts", "tinker"); + } + /** + * Guided Search + */ + if (objectGraph.host.isiOS) { + // Feature enablers + searchRequest.enablingFeature("guidedSearch"); + searchRequest.enablingFeature("midScrollGuidedSearch"); + // Selected facets, if any. + if (isDefinedNonNullNonEmpty(options.guidedSearchTokens)) { + searchRequest.addingQuery("selectedFacets", options.guidedSearchTokens.join(",")); + } + // Optimization term, if any were on request. + if (isDefinedNonNullNonEmpty(options.guidedSearchOptimizationTerm)) { + searchRequest.addingQuery("finalTerm", options.guidedSearchOptimizationTerm); + } + if (objectGraph.bag.isLLMSearchTagsEnabled) { + searchRequest.includingAssociateKeys("results:apps", ["tags"]); + } + } + if (objectGraph.featureFlags.isEnabled("voyager_bundles_2025A")) { + searchRequest.includingScopedAttributes("apps", ["screenshotsByType"]); + } + /** + * Entities in results + */ + if (searchEntityOrNull === "story") { + searchRequest.searchingOverTypes(["editorial-items"]); + } + else if (searchEntityOrNull === "developer") { + searchRequest.searchingOverTypes(["developers"]); + } + else if (searchEntityOrNull === "watch" || searchEntityOrNull === "arcade") { + searchRequest.searchingOverTypes(["apps"]).withFilter("contexts", searchEntityOrNull); + } + else if (objectGraph.client.isTV) { + searchRequest.searchingOverTypes(["apps", "developers", "groupings", "editorial-items"]); + } + else if (objectGraph.client.isVision) { + searchRequest.searchingOverTypes([ + "apps", + "developers", + "editorial-items", + "app-bundles", + "in-apps", + "editorial-pages", + ]); + } + else { + searchRequest.searchingOverTypes([ + "apps", + "developers", + "groupings", + "editorial-items", + "app-bundles", + "in-apps", + ]); + } + /** + * Signal Rosetta unavailability. + */ + if (objectGraph.appleSilicon.isSupportEnabled && !objectGraph.appleSilicon.isRosettaAvailable) { + searchRequest.addingQuery("restrict", "!requiresRosetta"); + } + /** + * Facets / filters + */ + if (facetsOrNull) { + for (const key of Object.keys(facetsOrNull)) { + searchRequest.addingQuery(key, facetsOrNull[key]); + } + } + if (selectedFacetOptionsOrNull) { + for (const key of Object.keys(selectedFacetOptionsOrNull)) { + const requestValues = models.PageFacets.requestValuesForSelectedFacetOptions(selectedFacetOptionsOrNull[key]); + if (isDefinedNonNullNonEmpty(requestValues)) { + searchRequest.addingQuery(key, requestValues.value); + for (const additionalKey of Object.keys(requestValues.additionalKeyValuePairs)) { + searchRequest.addingQuery(additionalKey, requestValues.additionalKeyValuePairs[additionalKey]); + } + } + } + } + /** + * natural language search availability + */ + const isNaturalLanguageSearchResultsEnabled = objectGraph.bag.isNaturalLanguageSearchEnabled || objectGraph.bag.isNaturalLanguageSearchResultsEnabled; + /** + * source attribution + */ + const sourceKey = isNaturalLanguageSearchResultsEnabled ? "source" : "src"; + if (origin === "hints") { + const hintSource = isNaturalLanguageSearchResultsEnabled && (source === null || source === void 0 ? void 0 : source.length) ? "hint:".concat(source) : "hint"; + searchRequest.addingQuery(sourceKey, hintSource); + } + else if (origin === "recents") { + searchRequest.addingQuery(sourceKey, "recent"); + } + else if (origin === "trending") { + searchRequest.addingQuery(sourceKey, "trending"); + } + else if (origin === "undoSpellCorrection") { + searchRequest.addingQuery(sourceKey, "searchInstead"); + } + else if (origin === "applySpellCorrection") { + searchRequest.addingQuery(sourceKey, "didYouMean"); + } + else if (origin === "guidedToken") { + searchRequest.addingQuery(sourceKey, "facet"); + } + /** + * contexts + */ + switch (clientIdentifier) { + case client.watchIdentifier: + searchRequest.addingContext("watch"); + break; + case client.messagesIdentifier: + searchRequest.addingContext("messages"); + break; + case client.arcadeIdentifier: + searchRequest.addingContext("arcade"); + break; + default: + break; + } + /** + * Advert limit + */ + searchRequest.addingQuery("limit[ads-result]", objectGraph.bag.mediaAdvertRequestLimit.toString()); + /** + * Ads locale metadata. + */ + if (isDefinedNonNullNonEmpty(objectGraph.bag.adsOverrideLanguage)) { + searchRequest.enablingFeature("adsLocaleMetadata"); + } + /** + * Feature: Spellcheck + */ + if (spellCheckEnabled) { + searchRequest.enablingFeature("spellCheck"); + } + /** + * Feature: Natural Language + */ + if (isNaturalLanguageSearchResultsEnabled) { + searchRequest.enablingFeature("naturalLanguage"); + } + /** + * Feature: Organic CPPs + */ + if (objectGraph.host.isiOS) { + searchRequest.enablingFeature("searchResultCpps"); + } + /** + * Excluded terms + */ + if (excludedTermsOrNull) { + searchRequest.addingQuery("excludeTerms", excludedTermsOrNull.join(",")); + } + return searchRequest; +} +/** + * Create the `Request` object to execute a search described by `options`, with results grouped by platform + * @param options Options for search being executed. + * @returns `Request` configured for `options`, or `null` if `options` was invalid. + */ +export function createPlatformGroupedSearchRequest(objectGraph, options) { + var _a; + return (_a = createSearchRequest(objectGraph, options)) === null || _a === void 0 ? void 0 : _a.addingQuery("groupBy[search]", "platform").includingMacOSCompatibleIOSAppsWhenSupported(); +} +/** + * Create a request for fetching set of `items` for appearing in search tab. Used for pagination. + * @param items Items to fetch. + */ +function createCatalogRequestForItems(objectGraph, items) { + const shouldUseMixedCatalogRequest = objectGraph.bag.isLLMSearchTagsEnabled || + asBooleanOrFalse(objectGraph.bag.supportedMixedMediaRequestUsecases["search"]); + const searchRequest = new Request(objectGraph, items, shouldUseMixedCatalogRequest, ["tags"]) + .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph)) + .includingScopedAttributes("editorial-items", ["showLabelInSearch"]) + .includingAttributes(includeAttributesForSearchResults(objectGraph)) + .includingRelationshipsForUpsell(true) + .includingMacOSCompatibleIOSAppsWhenSupported() + .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph)); + addVariantParametersToRequestForItems(objectGraph, searchRequest, items); + if (!objectGraph.client.isWatch) { + searchRequest.includingRelationships(searchIncludeRelationships); + } + if (appPromotionsCommon.appEventsAreEnabled(objectGraph)) { + searchRequest.includingAssociateKeys("apps", ["app-events"]); + searchRequest.includingScopedAttributes("app-events", AppEventsAttributes); + searchRequest.includingScopedRelationships("editorial-items", ["primary-content"]); + } + return searchRequest; +} +/** + * Returns the include attributes used for: + * 1. Initial search request + * 2. Subsequent catalog request for pagination + */ +function includeAttributesForSearchResults(objectGraph) { + const attributes = [ + "screenshotsByType", + "messagesScreenshots", + "videoPreviewsByType", + "requiredCapabilities", + "editorialBadgeInfo", + "supportsFunCamera", + "minimumOSVersion", + "customScreenshotsByTypeForAd", + "customVideoPreviewsByTypeForAd", + "secondaryGenreShortDisplayNames", + "genreShortDisplayName", + "editorialVideo", + ]; + if (objectGraph.appleSilicon.isSupportEnabled) { + attributes.push("macRequiredCapabilities"); + } + if (objectGraph.client.isMac) { + attributes.push("hasMacIPAPackage"); + } + if (objectGraph.client.isVision) { + attributes.push("compatibilityControllerRequirement"); + } + if (objectGraph.bag.enableUpdatedAgeRatings) { + attributes.push("ageRating"); + } + if (shouldUsePrerenderedIconArtwork(objectGraph)) { + attributes.push("iconArtwork"); + } + /// We don't want to unnecessarily ask for clients that don't have metadata ribbon capability + if (objectGraph.host.isOSAtLeast(15, 5, 0)) { + attributes.push("remoteControllerRequirement"); + } + // Keeping custom creatives out of prod. + if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) { + if (objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) { + attributes.push("adCreativeArtwork"); + attributes.push("adCreativeVideo"); + } + } + return attributes; +} +// endregion +// region Categories Filter Request +/** + * Fetches data to for category filters on devices that need them. + */ +async function fetchCategoryFiltersDataIfNeeded(objectGraph) { + const deviceType = objectGraph.client.deviceType; + if (deviceTypeSupportsCategoryFilters(deviceType)) { + const categoriesRequest = categories.createRequest(objectGraph, null, null, defaultAdditionalPlatformsForClient(objectGraph)); + if (categoriesRequest) { + // Failable request. + return await fetchData(objectGraph, categoriesRequest).catch(() => null); + } + } + return null; +} +function deviceTypeSupportsCategoryFilters(deviceType) { + switch (deviceType) { + case "pad": + case "mac": + return true; + default: + return false; + } +} +// endregion +// region Search Result Modification +/** + * The search dataset id is set to the key `dataSetId` on `meta.metrics`, but needs to have `data.search.dataSetId` key. + * <rdar://problem/39920993> Metrics: metrics blob in search response has incorrect field name + */ +function renameDataSetIdKey(searchResponse) { + var _a; + const searchMetrics = metricsFromMediaApiObject(searchResponse); + const oldDataSetId = "dataSetId"; + if (isDefinedNonNull(searchMetrics) && + isDefinedNonNull(searchResponse.meta) && + isDefinedNonNull((_a = searchResponse.meta) === null || _a === void 0 ? void 0 : _a.metrics)) { + searchResponse.meta.metrics[searchMetricsDataSetID] = asString(searchMetrics, oldDataSetId); + delete searchResponse.meta.metrics[oldDataSetId]; + } +} +// endregion +//# sourceMappingURL=search-results-fetching.js.map
\ No newline at end of file |
