summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js248
1 files changed, 248 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js
new file mode 100644
index 0000000..f6b64c1
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js
@@ -0,0 +1,248 @@
+/**
+ * Builder methods for building a collection of search results models for a series of search result data.
+ */
+import * as validation from "@jet/environment/json/validation";
+import { isNothing } from "@jet/environment/types/optional";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { isDataHydrated } from "../../foundation/media/data-structure";
+import { shouldFilter } from "../filtering";
+import { nextPosition, pushBasicLocation, popLocation } from "../metrics/helpers/location";
+import * as onDevicePersonalization from "../personalization/on-device-personalization";
+import { searchResultFromData } from "./content/search-results";
+import * as searchAds from "./search-ads";
+import * as guidedSearch from "./guided-search/guided-search";
+import { addImpressionFields } from "../metrics/helpers/impressions";
+import { searchAdMissedOpportunityFromId } from "../lockups/ad-lockups";
+/**
+ * Determines where to display guided search, e.g. as mid-scroll module or pinned to top.
+ * @param meta The metadata from the MAPI response for search catalog request.
+ * @param objectGraph The App Store object graph.
+ * @returns The position of the mid-scroll guided search module in search results, or `undefined` for the pinned to top experience.
+ */
+export function guidedSearchPositionFromSearchResponseMeta(meta, objectGraph) {
+ var _a, _b, _c;
+ // Client debug override
+ const guidedSearchPositionOverride = (_a = objectGraph.userDefaults) === null || _a === void 0 ? void 0 : _a.integer("GuidedSearchOverrides.position");
+ if (serverData.isNumber(guidedSearchPositionOverride) && guidedSearchPositionOverride > 1) {
+ return guidedSearchPositionOverride;
+ }
+ // Server controlled by default
+ return (_c = (_b = meta === null || meta === void 0 ? void 0 : meta.displayStyle) === null || _b === void 0 ? void 0 : _b.guidedSearch) === null || _c === void 0 ? void 0 : _c.position;
+}
+/**
+ * Create a `SearchResultsBuildResult` containing set of built and deferred results from the top-level search data container.
+ * This method supports prepending adverts.
+ * @param objectGraph
+ * @param requestMetadata Request metadata for search being performed.
+ * @param searchResponseMetadata Response metadata for search that was performed.
+ * @param metricsOptions Metrics options for built models.
+ * @param resultsData Array of search results data.
+ * @param advertData Data container with advert results.
+ * @param facetData Array of guided search token response data.
+ * @param installStates A mapping of adamIDs to their respective install states that is used to determine if the app is currently installed by the user
+ * @param appStates A mapping of adamIDs to their respective app state to determine if the ad/first result has been installed by the user in the past
+ */
+export async function createSearchResults(objectGraph, requestMetadata, searchResponseMetadata, metricsOptions, resultsData, advertData = undefined, facetData = undefined, installedStates = undefined, appStates = undefined) {
+ var _a, _b, _c, _d, _e, _f;
+ /// Built models
+ const builtResults = [];
+ // Unhydrated items that will be fetched in pagination.
+ const deferredResults = [];
+ // Search Experiments Data
+ const searchExperimentsData = searchResponseMetadata || null;
+ // Generate the personalization data
+ const appIds = resultsData
+ .filter((resultData) => {
+ return resultData.type === "apps";
+ })
+ .map((resultData) => {
+ return resultData.id;
+ });
+ const personalizationDataContainer = onDevicePersonalization.personalizationDataContainerForAppIds(objectGraph, new Set(appIds));
+ // Build Adverts
+ let advertsSearchResult;
+ let advertsDisplayStyle;
+ if (searchAds.platformSupportsAdverts(objectGraph) && serverData.isDefinedNonNullNonEmpty(advertData)) {
+ const adsResultAndDisplayStyle = searchAds.adsResultFromSearchResults(objectGraph, advertData, resultsData, requestMetadata, metricsOptions, installedStates !== null && installedStates !== void 0 ? installedStates : null, appStates !== null && appStates !== void 0 ? appStates : null, searchExperimentsData, personalizationDataContainer);
+ advertsSearchResult = adsResultAndDisplayStyle.result;
+ advertsDisplayStyle = adsResultAndDisplayStyle.displayStyle;
+ if (serverData.isDefinedNonNullNonEmpty(advertsSearchResult === null || advertsSearchResult === void 0 ? void 0 : advertsSearchResult.lockups)) {
+ advertsSearchResult.searchAdOpportunity = advertsSearchResult.lockups[0].searchAdOpportunity;
+ builtResults.push(advertsSearchResult);
+ }
+ }
+ // Flag for Ad Media Deduping
+ let isFirstResult = true;
+ const guidedSearchPosition = guidedSearchPositionFromSearchResponseMeta(searchResponseMetadata, objectGraph);
+ for (const [index, resultData] of resultsData.entries()) {
+ // Inject the mid-scroll guided search module if we've reached the desired position.
+ if (index === guidedSearchPosition) {
+ const tokens = createGuidedSearchTokens(objectGraph, requestMetadata.requestDescriptor, facetData, metricsOptions);
+ if (tokens.length > 0) {
+ const title = (_c = (_b = (_a = searchResponseMetadata === null || searchResponseMetadata === void 0 ? void 0 : searchResponseMetadata.displayStyle) === null || _a === void 0 ? void 0 : _a.guidedSearch) === null || _b === void 0 ? void 0 : _b.title) !== null && _c !== void 0 ? _c : objectGraph.loc.string("Search.Guided.Title.ExploreMore"); // static fallback query context
+ const guidedSearchResult = new models.GuidedSearchResult(title, tokens);
+ const impressionOptions = {
+ ...metricsOptions,
+ id: "midScrollGuidedSearch",
+ kind: "grouping",
+ targetType: "module",
+ title: title,
+ softwareType: null,
+ };
+ addImpressionFields(objectGraph, guidedSearchResult, impressionOptions);
+ builtResults.push(guidedSearchResult);
+ nextPosition(metricsOptions.locationTracker);
+ }
+ }
+ // Deferred items for subsequent pagination.
+ if (!isDataHydrated(resultData)) {
+ // On the first unhydrated item, attach the rest of the queue to `deferredResults` to preserve ordering.
+ deferredResults.push(...resultsData.slice(index));
+ break;
+ }
+ // Filter
+ if (shouldFilter(objectGraph, resultData, 10750 /* Filter.Search */)) {
+ continue;
+ }
+ // Advert: Update CPP data on first organic result.
+ // We must do this *after* the ad results are built, because we need to ensure we're picking the first lockup that will appear,
+ // not just the first data (that may be filtered somehow).
+ if (isFirstResult && serverData.isDefinedNonNullNonEmpty(advertsSearchResult === null || advertsSearchResult === void 0 ? void 0 : advertsSearchResult.lockups)) {
+ searchAds.updateDupeOrganicResultCPPData(objectGraph, advertData !== null && advertData !== void 0 ? advertData : [], advertsSearchResult, resultData, installedStates !== null && installedStates !== void 0 ? installedStates : null, appStates !== null && appStates !== void 0 ? appStates : null, metricsOptions, personalizationDataContainer);
+ }
+ // Build model
+ const searchResult = searchResultFromData(objectGraph, resultData, searchResponseMetadata, personalizationDataContainer, metricsOptions, requestMetadata.requestDescriptor.isNetworkConstrained, requestMetadata.requestDescriptor.searchEntity, searchExperimentsData);
+ if (!searchResult || !platformSupportsResultType(objectGraph, searchResult)) {
+ continue;
+ }
+ /**
+ * Advert: When first advert and result matches, modify media.
+ */
+ if (isFirstResult &&
+ serverData.isDefinedNonNullNonEmpty(advertsSearchResult) &&
+ serverData.isDefinedNonNullNonEmpty(advertsSearchResult.lockups)) {
+ searchAds.dedupeAdMediaFromMatchingResult(objectGraph, advertsSearchResult, searchResult, searchExperimentsData, advertsDisplayStyle);
+ }
+ /**
+ * Advert: When advert isn't available, mark the organic as a missed opportunity slot
+ */
+ if (isFirstResult &&
+ searchAds.platformSupportsAdverts(objectGraph) &&
+ serverData.isDefinedNonNull((_d = metricsOptions.pageInformation) === null || _d === void 0 ? void 0 : _d.iAdInfo) &&
+ (serverData.isNull(advertsSearchResult) || serverData.isNullOrEmpty(advertsSearchResult === null || advertsSearchResult === void 0 ? void 0 : advertsSearchResult.lockups))) {
+ searchResult.searchAdOpportunity = searchAdMissedOpportunityFromId(objectGraph, metricsOptions.pageInformation);
+ (_e = searchResult.searchAdOpportunity) === null || _e === void 0 ? void 0 : _e.setMissedOpportunityReason("NOAD");
+ (_f = searchResult.searchAdOpportunity) === null || _f === void 0 ? void 0 : _f.setTemplateType("APPLOCKUP");
+ }
+ builtResults.push(searchResult);
+ isFirstResult = false;
+ nextPosition(metricsOptions.locationTracker);
+ }
+ return await applyClientFilteringToIAPs(objectGraph, builtResults).then((builtResultsFiltered) => {
+ return {
+ builtSearchResults: builtResultsFiltered,
+ deferredSearchResults: deferredResults,
+ };
+ });
+}
+// endregion
+// region Internals
+/**
+ * Create guided search tokens for the given search request descriptor and facet MAPI response data.
+ * @param objectGraph The App Store object graph.
+ * @param requestDescriptor The search request descriptor.
+ * @param facetData The media API response for guided search facets.
+ * @param metricsOptions The metrics options.
+ * @returns The guided search tokens to display.
+ */
+function createGuidedSearchTokens(objectGraph, requestDescriptor, facetData, metricsOptions) {
+ if (!objectGraph.host.isiOS || isNothing(facetData) || facetData.length === 0) {
+ return [];
+ }
+ pushBasicLocation(objectGraph, {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "SearchRevisions",
+ }, "");
+ // Tokens from facet data
+ const tokens = [];
+ for (const data of facetData) {
+ const token = guidedSearch.createGuidedSearchToken(objectGraph, "rewrite", requestDescriptor, data, metricsOptions);
+ if (token) {
+ tokens.push(token);
+ nextPosition(metricsOptions.locationTracker);
+ }
+ }
+ popLocation(metricsOptions.locationTracker);
+ return tokens;
+}
+/**
+ * Apply client-side iAP filtering to set of results.
+ * @param objectGraph
+ * @param resultsToFilter Results to apply iAP filtering on.
+ */
+async function applyClientFilteringToIAPs(objectGraph, resultsToFilter) {
+ return await validation.context("applyClientFilteringToIAPs", async () => {
+ const iAPProductIDToParentBundleID = {};
+ for (const result of resultsToFilter) {
+ if (result.resultType === "inAppPurchase") {
+ const inAppPurchaseResult = result;
+ const inAppPurchaseLockup = inAppPurchaseResult.lockup;
+ if (inAppPurchaseLockup.parent &&
+ inAppPurchaseLockup.productIdentifier &&
+ inAppPurchaseLockup.parent.bundleId) {
+ iAPProductIDToParentBundleID[inAppPurchaseLockup.productIdentifier] =
+ inAppPurchaseLockup.parent.bundleId;
+ }
+ else {
+ validation.unexpectedNull("ignoredValue", "string", `required fields for ${inAppPurchaseLockup.adamId}`);
+ }
+ }
+ }
+ if (Object.keys(iAPProductIDToParentBundleID).length === 0) {
+ return await Promise.resolve(resultsToFilter);
+ }
+ return await objectGraph.clientOrdering.visibilityForIAPs(iAPProductIDToParentBundleID).then((visibilities) => {
+ const filteredResults = resultsToFilter.filter((result) => {
+ if (result.resultType !== "inAppPurchase") {
+ return true;
+ }
+ const inAppPurchaseResult = result;
+ const inAppPurchaseLockup = inAppPurchaseResult.lockup;
+ if (inAppPurchaseLockup.productIdentifier && visibilities[inAppPurchaseLockup.productIdentifier]) {
+ return true;
+ }
+ else {
+ return inAppPurchaseLockup.isVisibleByDefault;
+ }
+ });
+ return filteredResults;
+ });
+ });
+}
+/**
+ * Whether or not current platform supports displaying given `searchResult`.
+ */
+function platformSupportsResultType(objectGraph, searchResult) {
+ if (objectGraph.host.isTV) {
+ switch (searchResult.resultType) {
+ case "content":
+ case "editorial":
+ return true;
+ default:
+ return false;
+ }
+ }
+ if (!objectGraph.host.isiOS && !objectGraph.client.isWeb) {
+ switch (searchResult.resultType) {
+ case "appEvent":
+ return false;
+ default:
+ break;
+ }
+ }
+ return true;
+}
+// endregion
+//# sourceMappingURL=search-results-pipeline.js.map \ No newline at end of file