summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/search
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
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/search')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/content/search-categories.js334
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/content/search-content-common.js31
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/content/search-lockup-collection.js171
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/content/search-results.js723
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/content/search-shelves.js53
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/custom-creative.js71
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search-metrics.js132
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search.js105
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-cohort.js84
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-shelf-controller.js836
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/category-metadata-ribbon-item.js38
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/chart-metadata-ribbon-item.js75
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/developer-metadata-ribbon-item.js37
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/divider-metadata-ribbon-item.js8
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/editors-choice-metadata-ribbon-item.js21
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/game-controller-metadata-ribbon-item.js36
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon-item-factory.js24
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon.js27
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/ranked-secondary-category-metadata-ribbon-item.js22
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/search-tags-ribbon.js72
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/secondary-short-categories-metadata-ribbon-item.js27
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/short-category-metadata-ribbon-item.js36
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/star-rating-metadata-ribbon-item.js20
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/tag-metadata-ribbon-item.js18
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-ads-odml.js87
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js1047
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-common.js59
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-facets.js146
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-landing-page-utils.js386
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-page-url.js5
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-results-fetching.js496
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-results-learn-more-notice.js46
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js248
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-spell-correction.js98
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-token.js57
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search.js1024
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/shelves/search-history-shelf.js148
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/sponsored-search-fetching.js122
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/web-search-action.js23
39 files changed, 6993 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-categories.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-categories.js
new file mode 100644
index 0000000..927de7c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-categories.js
@@ -0,0 +1,334 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaRelationships from "../../../foundation/media/relationships";
+import { pageRouter } from "../../builders/routing";
+import { hrefToRoutableUrl } from "../../builders/url-mapping";
+import * as content from "../../content/content";
+import { addClickEventToAction, addClickEventToSeeAllAction } from "../../metrics/helpers/clicks";
+import { addImpressionFields } from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { addMetricsEventsToPageWithInformation, metricsPageInformationFromMediaApiResponse, } from "../../metrics/helpers/page";
+import { createClickMetricsOptionsForChartOrCategory, createMetricsOptionsForChartOrCategory, } from "../../metrics/helpers/search/search-shelves";
+import { combinedRecoMetricsDataFromMetricsData } from "../../metrics/helpers/util";
+import * as onDevicePersonalization from "../../personalization/on-device-personalization";
+import * as searchShelves from "./search-shelves";
+import * as groupingShelfControllerCommon from "../../grouping/shelf-controllers/grouping-shelf-controller-common";
+/**
+ * Takes the raw page data and creates a charts and categories page
+ * @param objectGraph The App Store Object Graph
+ * @param resultData The data representing the charts and categories page
+ * @returns A charts and categories page
+ */
+export function searchChartsAndCategoriesPageFromData(objectGraph, resultData) {
+ var _a, _b;
+ /// Get the page's metadata
+ const pageTitle = mediaAttributes.attributeAsString(resultData, "title");
+ const selectedTabId = serverData.asString(resultData, "meta.autoSelectedTabId");
+ const sourceShelfCanonicalId = serverData.asString(resultData, "meta.sourceShelfCanonicalId");
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "SearchLanding", sourceShelfCanonicalId, resultData);
+ const onDevicePersonalizationMetricsData = onDevicePersonalization.metricsData(objectGraph);
+ pageInformation.recoMetricsData = combinedRecoMetricsDataFromMetricsData(pageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData);
+ /// Generate a new SearchPageContext (mainly for making shelf contexts)
+ const searchPageContext = {
+ shelves: [],
+ metricsLocationTracker: metricsHelpersLocation.newLocationTracker(),
+ metricsPageInformation: pageInformation,
+ adStitcher: null,
+ adIncidentRecorder: null,
+ pageType: searchShelves.SearchPageType.ChartsAndCategories,
+ };
+ /// Prep the shelves data, mappings, orderings, and page tabs list
+ const shelvesData = serverData.asArrayOrEmpty(resultData, "data");
+ const shelvesMapping = {};
+ const shelfOrdering = [];
+ const pageTabsCollector = [];
+ const pageTabsLocationTracker = metricsHelpersLocation.newLocationTracker();
+ for (const shelfData of shelvesData) {
+ /// Generate the shelf attributes for the shelf
+ const shelfAttributes = searchShelves.shelfAttributesFromData(objectGraph, shelfData, models.SearchLandingPageContentKind.CategoriesAndCharts, models.SearchPageKind.CategoriesAndCharts);
+ /// Since the shelves act as their own page, we need to set a new location tracker for each shelf
+ const shelfPageContext = {
+ ...searchPageContext,
+ metricsLocationTracker: metricsHelpersLocation.newLocationTracker(),
+ };
+ /// Generate the shelf context
+ const shelfContext = searchShelves.baseShelfContext(objectGraph, shelfData, shelfAttributes, shelfPageContext);
+ /// Push the shelf content location so each shelf has the correct parent and starting index
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfContext.metricsOptions, shelfAttributes.title);
+ /// Make the shelf
+ const shelf = createChartsCategoryShelf(objectGraph, shelfData, true, shelfAttributes, shelfPageContext, shelfContext);
+ metricsHelpersLocation.popLocation(shelfContext.metricsOptions.locationTracker);
+ if (isNothing(shelf)) {
+ continue;
+ }
+ /// Add the shelf to the shelves mapping for the shelf id
+ shelvesMapping[shelf.id] = shelf;
+ /// Add the shelf id to the shelf ordering (to maintain ordered shelves)
+ shelfOrdering.push(shelf.id);
+ /// Make a pageTab metadata model from the shelf and action
+ const pageTab = new models.PageTab();
+ const action = new models.PageTabChangeAction(shelfData.id, shelfAttributes.title);
+ /// Generate the click actions
+ const pageTabClickActionOptions = {
+ id: shelfAttributes.title,
+ canonicalId: (_a = serverData.asString(shelfData.meta, "canonicalId")) !== null && _a !== void 0 ? _a : undefined,
+ actionType: "navigate",
+ targetType: "button",
+ pageInformation: searchPageContext.metricsPageInformation,
+ locationTracker: pageTabsLocationTracker,
+ };
+ addClickEventToAction(objectGraph, action, pageTabClickActionOptions);
+ pageTab.action = action;
+ pageTab.id = shelf.id;
+ pageTab.title = `${(_b = shelf.title) !== null && _b !== void 0 ? _b : ""}`; /// We need a deep copy of the string as we will remove the reference possibly later.
+ pageTabsCollector.push(pageTab);
+ metricsHelpersLocation.nextPosition(pageTabsLocationTracker);
+ }
+ /// Return an empty page if there are no real shelves to show
+ if (!serverData.isDefinedNonNullNonEmpty(shelfOrdering)) {
+ return new models.SearchChartsAndCategoriesPage();
+ }
+ /// Create the page tabs
+ const pageTabs = new models.PageTabs();
+ /// The id needs to be static but it isn't tied to the payload
+ pageTabs.id = objectGraph.random.nextUUID();
+ /// Add the pageTabs as its own shelf so it can be independent of the chart and category shelves
+ /// as well as to control the segmented control for swapping between category shelf orderings (e.g. category containers)
+ const pageTabsShelf = new models.Shelf("pageTabs");
+ pageTabsShelf.items = [pageTabs];
+ shelvesMapping[pageTabs.id] = pageTabsShelf;
+ /// Make the page and add the shelf mappings (which includes our pageTabs shelf)
+ const page = new models.SearchChartsAndCategoriesPage();
+ page.shelfMapping = shelvesMapping;
+ const shelfOrderings = {};
+ /** Normally, we would have the shelf orderings be based on some identifier in the payload on a container which contains
+ an array of shelves. E.g.
+ {
+ Container {
+ containerId: string
+ shelves: [
+ {
+ shelfId1: string,
+ ...
+ },
+ ...
+ ]
+ }
+ }
+ shelf orderings = { containerId: [shelfId1, ...]}
+
+ However, the current protocol has no container, just a loose array of shelves.
+ {
+ shelves: [
+ {
+ shelfId1: string,
+ ...
+ },
+ ...
+ ]
+ }
+
+ Therefore, we have to fake our ordering by having the ordering be the shelves' own identifiers.
+
+ shelf orderings = { shelfId1: [shelfId1], shelfId2: [shelfId2], ...}
+
+ We also will append the pageTabs shelf if we have multiple shelves representing multiple tabs on the page.
+ */
+ for (const shelfOrderingElement of shelfOrdering) {
+ if (pageTabsCollector.length > 1) {
+ shelfOrderings[shelfOrderingElement] = [pageTabs.id, shelfOrderingElement];
+ }
+ else {
+ shelfOrderings[shelfOrderingElement] = [shelfOrderingElement];
+ }
+ }
+ for (const shelf of Object.values(shelvesMapping)) {
+ shelf.title = undefined;
+ }
+ /// Set the data on the page
+ page.title = pageTitle;
+ page.pageTabs = pageTabs;
+ page.columnCount = 2;
+ page.shelfOrderings = shelfOrderings;
+ /// If we don't have a reco selected default tab, just default to the first one
+ page.defaultShelfOrdering = shelfOrdering.includes(selectedTabId) ? selectedTabId : shelfOrdering[0];
+ /// Set the data on the page tabs
+ pageTabs.tabs = pageTabsCollector;
+ pageTabs.selectedTabId = page.defaultShelfOrdering;
+ /// Add the page metrics
+ addMetricsEventsToPageWithInformation(objectGraph, page, searchPageContext.metricsPageInformation);
+ return page;
+}
+/**
+ * Creates a charts and categories shelf from the shelf data
+ * @param objectGraph The App Store Object Graph
+ * @param data The shelf data object
+ * @param isForSeeAllPage Whether or not this shelf will be displayed on the see-all page
+ * @param shelfAttributes The shelf's attributes
+ * @param searchPageContext The context for the page this shelf belongs to
+ * @param searchShelfContext The shelf's context
+ * @returns A charts and categories shelf
+ */
+export function createChartsCategoryShelf(objectGraph, data, isForSeeAllPage, shelfAttributes, searchPageContext, searchShelfContext) {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
+ const items = [];
+ const chartsAndCategoriesData = mediaRelationships.relationshipCollection(data, "contents");
+ const shelf = new models.Shelf("searchChartsAndCategories");
+ shelf.isHorizontal = false;
+ shelf.id = data.id;
+ shelf.title = shelfAttributes.title;
+ shelf.presentationHints = { isWidthConstrained: true };
+ if (serverData.isNumber((_a = shelfAttributes.displayStyle) === null || _a === void 0 ? void 0 : _a.layoutSize)) {
+ shelf.contentsMetadata = {
+ type: "searchLandingChartsAndCategoriesSection",
+ numberOfColumns: shelfAttributes.displayStyle.layoutSize,
+ };
+ }
+ if (shelfAttributes.hasSeeAll) {
+ const action = new models.FlowAction("searchChartsAndCategories");
+ action.pageUrl = shelfAttributes.seeAllLink;
+ action.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ const seeAllMetricsOptions = {
+ ...searchShelfContext.metricsOptions,
+ targetType: "button",
+ };
+ addClickEventToSeeAllAction(objectGraph, action, action.pageUrl, seeAllMetricsOptions);
+ shelf.seeAllAction = action;
+ }
+ addImpressionFields(objectGraph, shelf, searchShelfContext.metricsOptions);
+ const brickUseCase = isForSeeAllPage
+ ? content.SearchChartOrCategoryBrickUseCase.seeAllPage
+ : content.SearchChartOrCategoryBrickUseCase.other;
+ for (const chartOrCategory of chartsAndCategoriesData) {
+ let name = null;
+ let badge = null;
+ if (chartOrCategory.type === "tags") {
+ const brickName = (_b = mediaAttributes.attributeAsString(chartOrCategory, "name")) !== null && _b !== void 0 ? _b : "tagbrick";
+ name = brickName;
+ }
+ else {
+ const editorialNotes = mediaAttributes.attributeAsDictionary(chartOrCategory, "editorialNotes");
+ if (serverData.isDefinedNonNullNonEmpty(editorialNotes)) {
+ name = serverData.asString(editorialNotes, "name");
+ badge = serverData.asString(editorialNotes, "badge");
+ }
+ }
+ const artwork = content.searchChartOrCategoryArtworkFromData(objectGraph, chartOrCategory, brickUseCase, (_c = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _c === void 0 ? void 0 : _c.layoutDensity);
+ const kind = mediaAttributes.attributeAsString(chartOrCategory, "kind");
+ const linkData = mediaAttributes.attributeAsDictionary(chartOrCategory, "link");
+ const linkUrl = serverData.asString(linkData, "url");
+ const dataCollection = mediaRelationships.relationshipCollection(chartOrCategory, "primary-content");
+ let isTitleRequired = true;
+ let action = null;
+ if (chartOrCategory.type === "tags") {
+ const href = serverData.asString(chartOrCategory, "href");
+ const url = hrefToRoutableUrl(objectGraph, href);
+ const flowPage = objectGraph.required(pageRouter).fetchFlowPage(url);
+ const flowAction = new models.FlowAction(flowPage);
+ flowAction.pageUrl = url;
+ flowAction.title = name;
+ action = flowAction;
+ }
+ else if (isSome(linkUrl)) {
+ switch (kind) {
+ case "CategoryChart":
+ const topChartAction = new models.FlowAction("topCharts");
+ topChartAction.pageUrl = linkUrl;
+ topChartAction.title = name;
+ action = topChartAction;
+ break;
+ case "External":
+ // For external links, we don't require a title we can just show an image
+ isTitleRequired = false;
+ const target = serverData.asString(linkData, "target");
+ if (target === "external") {
+ action = new models.ExternalUrlAction(linkUrl);
+ action.title = name !== null && name !== void 0 ? name : "";
+ }
+ else {
+ const flowPage = objectGraph.required(pageRouter).fetchFlowPage(linkUrl);
+ const linkAction = new models.FlowAction(flowPage);
+ linkAction.pageUrl = linkUrl;
+ linkAction.title = name !== null && name !== void 0 ? name : "";
+ action = linkAction;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(dataCollection)) {
+ const chartMetadata = dataCollection[0];
+ const chartHref = chartMetadata.href;
+ const url = hrefToRoutableUrl(objectGraph, chartHref);
+ if ((url === null || url === void 0 ? void 0 : url.length) > 0) {
+ const flowAction = new models.FlowAction("page");
+ flowAction.pageUrl = url;
+ flowAction.title = name;
+ action = flowAction;
+ }
+ else {
+ continue;
+ }
+ }
+ else {
+ continue;
+ }
+ const chartClickOptions = createClickMetricsOptionsForChartOrCategory(objectGraph, (_d = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _d === void 0 ? void 0 : _d.layoutDensity, chartOrCategory, searchShelfContext);
+ addClickEventToAction(objectGraph, action, chartClickOptions);
+ if (isTitleRequired && serverData.isNullOrEmpty(name)) {
+ continue;
+ }
+ let chartOrCategoryModel = new models.SearchChartOrCategory(name, artwork, null, null, badge, action, (_e = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _e === void 0 ? void 0 : _e.layoutDensity, artworkSafeArea((_f = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _f === void 0 ? void 0 : _f.layoutDensity), textSafeArea((_g = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _g === void 0 ? void 0 : _g.layoutDensity));
+ let chartModelMetricsOptions = createMetricsOptionsForChartOrCategory(objectGraph, chartOrCategoryModel, chartOrCategory, searchShelfContext);
+ const artworkOptions = {
+ useCase: 18 /* content.ArtworkUseCase.GroupingBrick */,
+ };
+ const collectionIcons = groupingShelfControllerCommon.artworkForTags(objectGraph, chartOrCategory, 1060, 520, artworkOptions, chartModelMetricsOptions);
+ if (isSome(collectionIcons) && collectionIcons.length > 0) {
+ const collectionIconBackgroundColor = collectionIcons[0].backgroundColor;
+ if (isSome(collectionIconBackgroundColor) && (collectionIconBackgroundColor === null || collectionIconBackgroundColor === void 0 ? void 0 : collectionIconBackgroundColor.type) === "rgb") {
+ chartOrCategoryModel = new models.SearchChartOrCategory(name, null, collectionIcons, (_h = content.closestTagBackgroundColorForIcon(collectionIconBackgroundColor)) !== null && _h !== void 0 ? _h : undefined, badge, action, (_j = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _j === void 0 ? void 0 : _j.layoutDensity, artworkSafeArea((_k = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _k === void 0 ? void 0 : _k.layoutDensity), textSafeArea((_l = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _l === void 0 ? void 0 : _l.layoutDensity));
+ chartModelMetricsOptions = createMetricsOptionsForChartOrCategory(objectGraph, chartOrCategoryModel, chartOrCategory, searchShelfContext);
+ }
+ }
+ addImpressionFields(objectGraph, chartOrCategoryModel, chartModelMetricsOptions);
+ items.push(chartOrCategoryModel);
+ metricsHelpersLocation.nextPosition(searchShelfContext.metricsOptions.locationTracker);
+ }
+ if (serverData.isNullOrEmpty(items)) {
+ return null;
+ }
+ shelf.items = items;
+ return shelf;
+}
+function artworkSafeArea(density) {
+ switch (density) {
+ // Tile
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density1:
+ return models.ChartOrCategorySafeArea.defaultTileArtworkSafeArea;
+ // Pill
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density2:
+ return models.ChartOrCategorySafeArea.defaultPillArtworkSafeArea;
+ // Round
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density3:
+ return undefined;
+ default:
+ return undefined;
+ }
+}
+function textSafeArea(density) {
+ switch (density) {
+ // Tile
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density1:
+ return models.ChartOrCategorySafeArea.defaultTileTextSafeArea;
+ // Pill
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density2:
+ return models.ChartOrCategorySafeArea.defaultPillTextSafeArea;
+ default:
+ return undefined;
+ }
+}
+//# sourceMappingURL=search-categories.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-content-common.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-content-common.js
new file mode 100644
index 0000000..59b2fe5
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-content-common.js
@@ -0,0 +1,31 @@
+/**
+ * Common functions for search results content.
+ */
+import { isDefinedNonNullNonEmpty } from "../../../foundation/json-parsing/server-data";
+import { attributeAsBooleanOrFalse, attributeAsString } from "../../../foundation/media/attributes";
+// region Editorial Content
+/**
+ * Returns the headline / tagline for Editorial Search Results
+ * @param resultData Data to determine tagline for.
+ */
+export function editorialSearchResultTagline(objectGraph, resultData) {
+ // Flag to disable showing specific headings, e.g. "App of the Day" that may be unnatural in search result
+ const showLabelInSearch = attributeAsBooleanOrFalse(resultData, "showLabelInSearch");
+ if (!showLabelInSearch) {
+ return null;
+ }
+ // <rdar://problem/40468686> LOC: AOTD & GOTD badges in Search result for the Editorial Item Card AOTD & GOTD stories do not show up correctly for JA-JP and TH-TH
+ // Always use alternative label, if one is provided.
+ const alternateLabel = attributeAsString(resultData, "alternateLabel");
+ if (isDefinedNonNullNonEmpty(alternateLabel)) {
+ return alternateLabel; // No newline flattening needed for alternativeLabel
+ }
+ // Otherwise, fallback to franchise label, if any.
+ const label = attributeAsString(resultData, "label");
+ if (isDefinedNonNullNonEmpty(label)) {
+ return label.replace(/\n/g, " ");
+ }
+ return null;
+}
+// endregion
+//# sourceMappingURL=search-content-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-lockup-collection.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-lockup-collection.js
new file mode 100644
index 0000000..c5c85e7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-lockup-collection.js
@@ -0,0 +1,171 @@
+import { TodayCardDisplayStyle } from "./../../today/today-types";
+/**
+ * Builder for SearchLockupCollection.
+ */
+import { SearchLockupCollection } from "../../../api/models";
+import { isDefinedNonNullNonEmpty, isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { attributeAsArrayOrEmpty, attributeAsString } from "../../../foundation/media/attributes";
+import { relationshipCollection } from "../../../foundation/media/relationships";
+import { createArtworkForResource } from "../../content/artwork/artwork";
+import { notesFromData } from "../../content/content";
+import { currentEditorialCollectionTreatment, } from "../../../foundation/experimentation/search-results-experiments";
+import { actionFromData, lockupsFromData } from "../../lockups/lockups";
+import { addImpressionFields, impressionOptions } from "../../metrics/helpers/impressions";
+import { popLocation, pushContentLocation } from "../../metrics/helpers/location";
+import { cardDisplayStyleFromData } from "../../today/today-card-util";
+import { editorialSearchResultTagline } from "./search-content-common";
+/**
+ * Whether or not a given result data should be rendered as `SearchLockupCollection` model.
+ * Semantically, every `collection` should be rendered as a lockup collection, but collections are currently backed by many different types of articles.
+ * @param resultData Data from results endpoint.
+ * @param searchResponseMetadata Meta blob from initial search request.
+ */
+export function resultDataShouldRenderLockupCollection(objectGraph, resultData, searchResponseMetadata) {
+ // Filter supported platforms
+ if (!(objectGraph.host.isiOS || objectGraph.host.isVision)) {
+ return false;
+ }
+ if (currentEditorialCollectionTreatment(objectGraph, searchResponseMetadata) !==
+ 1 /* EditorialCollectionExperimentType.Swoosh */) {
+ return false;
+ }
+ switch (resultData.type) {
+ case "groupings":
+ // Groupings can only be rendered if `contentIds` is present
+ const collectionAdamIds = attributeAsArrayOrEmpty(resultData, "contentIds");
+ return isDefinedNonNullNonEmpty(collectionAdamIds);
+ case "rooms":
+ case "multirooms":
+ // Rooms, and multirooms always render as collection.
+ return true;
+ case "editorial-items":
+ // Some subtypes of editorial items render as collection.
+ const cardDisplayStyle = cardDisplayStyleFromData(resultData);
+ switch (cardDisplayStyle) {
+ case TodayCardDisplayStyle.List:
+ case TodayCardDisplayStyle.NumberedList:
+ case TodayCardDisplayStyle.Grid:
+ case TodayCardDisplayStyle.River:
+ return true;
+ default:
+ return false;
+ }
+ default:
+ return false;
+ }
+}
+/**
+ * Create a `SearchLockupCollection` from search results.
+ * @param resultData Data from results endpoint.
+ * @param lockupOptions Options for lockups shared throughout search results page.
+ * @param metricsOptions Metrics context.
+ */
+export function lockupCollectionFromResultData(objectGraph, resultData, metricsOptions) {
+ // Text
+ const heading = lockupCollectionHeadingFromResultData(objectGraph, resultData);
+ const headingArtwork = lockupCollectionHeadingArtworkFromResultData(objectGraph, resultData);
+ const title = lockupCollectionTitleFromResultData(objectGraph, resultData);
+ // Metrics for impression + location
+ const lockupCollectionMetrics = impressionOptions(objectGraph, resultData, title, {
+ targetType: "card",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ });
+ pushContentLocation(objectGraph, lockupCollectionMetrics, title);
+ // Click Action ("See All")
+ const clickMetrics = {
+ actionType: "navigate",
+ targetType: "button",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ id: "See All",
+ idType: "sequential",
+ };
+ const seeAllAction = actionFromData(objectGraph, resultData, clickMetrics, objectGraph.host.clientIdentifier);
+ seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ // Lockups
+ const listOptions = {
+ lockupOptions: {
+ metricsOptions: {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "lockup",
+ },
+ skipDefaultClickAction: objectGraph.client.isVision,
+ artworkUseCase: 8 /* ArtworkUseCase.SearchIcon */,
+ hideCompatibilityBadge: objectGraph.client.isVision,
+ },
+ filter: 128 /* Filter.UnsupportedPlatform */,
+ };
+ let lockupData = relationshipCollection(resultData, "card-contents");
+ if (isNullOrEmpty(lockupData)) {
+ lockupData = relationshipCollection(resultData, "top-apps");
+ }
+ const items = lockupsFromData(objectGraph, lockupData, listOptions);
+ popLocation(metricsOptions.locationTracker);
+ // Lockup Collection
+ const lockupCollection = new SearchLockupCollection(heading, title, items, seeAllAction, headingArtwork);
+ addImpressionFields(objectGraph, lockupCollection, lockupCollectionMetrics);
+ if (isNullOrEmpty(items)) {
+ return null;
+ }
+ return lockupCollection;
+}
+// endregion
+// region Heading
+/**
+ * Returns the title for given editorial results search results data.
+ * @param resultData The result data to get a title for.
+ */
+function lockupCollectionTitleFromResultData(objectGraph, resultData) {
+ const resultType = resultData.type;
+ switch (resultType) {
+ case "developers":
+ return attributeAsString(resultData, "name");
+ default:
+ return notesFromData(objectGraph, resultData, "name");
+ }
+}
+/**
+ * Returns the heading for given editorial results search results data.
+ * This builds on `editorialSearchResultTagline` with some additional search-specific logic
+ * @param resultData The result data to get a heading for.
+ */
+function lockupCollectionHeadingFromResultData(objectGraph, resultData) {
+ const resultType = resultData.type;
+ if (resultType === "developers") {
+ return objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_DEVELOPER_TITLE_CASE");
+ }
+ const editorialTagline = editorialSearchResultTagline(objectGraph, resultData);
+ if (isDefinedNonNullNonEmpty(editorialTagline)) {
+ return editorialTagline;
+ }
+ if (objectGraph.client.isVision) {
+ return objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_COLLECTION_TITLE_CASE");
+ }
+ else {
+ return objectGraph.loc.string("Search.EditorialSearchResultType.Heading.Collection");
+ }
+}
+/**
+ * Returns the heading artwork to use for a given editorial search results data.
+ * @param resultData The result data to get heading artwork for.
+ */
+function lockupCollectionHeadingArtworkFromResultData(objectGraph, resultData) {
+ if (!objectGraph.client.isVision) {
+ return null;
+ }
+ const resultType = resultData.type;
+ let imageName;
+ switch (resultType) {
+ case "developers":
+ imageName = "person.crop.square";
+ break;
+ default:
+ imageName = "appstore";
+ break;
+ }
+ return createArtworkForResource(objectGraph, `systemimage://${imageName}`);
+}
+// endregion
+//# sourceMappingURL=search-lockup-collection.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-results.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-results.js
new file mode 100644
index 0000000..5f363b3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-results.js
@@ -0,0 +1,723 @@
+/**
+ * A builder for all SearchResultsContainers variants except for `SearchLockupCollection`.
+ * To be cleaned in the future.
+ */
+import * as validation from "@jet/environment/json/validation";
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import { EditorialSearchResult } from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as errors from "../../../foundation/util/errors";
+import * as client from "../../../foundation/wrappers/client";
+import * as appEvents from "../../app-promotions/app-event";
+import * as appPromotionsCommon from "../../app-promotions/app-promotions-common";
+import { createArtworkForResource } from "../../content/artwork/artwork";
+import * as content from "../../content/content";
+import { editorialDisplayOptionsFromClientParams, extractEditorialClientParams, } from "../../editorial-pages/editorial-data-util";
+import * as filtering from "../../filtering";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersUtil from "../../metrics/helpers/util";
+import * as onDevicePersonalization from "../../personalization/on-device-personalization";
+import { defaultTodayCardConfiguration, todayCardFromData } from "../../today/today-card-util";
+import { TodayCardDisplayStyle, TodayParseContext } from "../../today/today-types";
+import * as common from "./search-content-common";
+import * as searchLockupCollection from "./search-lockup-collection";
+export function searchResultFromData(objectGraph, resultData, searchResponseMetadata, personalizationDataContainer, metricsOptions, isNetworkConstrained, searchEntity, searchExperimentsData) {
+ return validation.context("searchResultFromData", () => {
+ let searchResult = null;
+ const resultType = resultData.type;
+ const standardLockupOptions = {
+ metricsOptions: {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "card",
+ createUniqueImpressionId: true,
+ },
+ hideZeroRatings: true,
+ artworkUseCase: 8 /* content.ArtworkUseCase.SearchIcon */,
+ isNetworkConstrained: isNetworkConstrained,
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "mixedMediaLockup"),
+ clientIdentifierOverride: clientIdentifierOverrideForSearchEntity(objectGraph, searchEntity),
+ isMultilineTertiaryTitleAllowed: false,
+ };
+ const condensedBehavior = condensedBehaviorForData(objectGraph, resultData, searchExperimentsData);
+ switch (resultType) {
+ case "rooms":
+ case "multirooms":
+ case "developers":
+ case "editorial-items":
+ case "groupings":
+ if (resultType !== "editorial-items" && resultType !== "developers" && objectGraph.client.isVision) {
+ return null;
+ }
+ const todayCardConfig = defaultTodayCardConfiguration(objectGraph);
+ todayCardConfig.isSearchContext = true;
+ const todayCard = todayCardFromData(objectGraph, resultData, todayCardConfig, new TodayParseContext(metricsOptions.pageInformation, metricsOptions.locationTracker));
+ if (todayCard && todayCard.media && todayCard.media.kind === "inAppPurchase") {
+ if (objectGraph.client.isVision) {
+ return null;
+ }
+ const iapMedia = todayCard.media;
+ const todayCardInAppPurchaseLockup = iapMedia.lockup;
+ todayCardInAppPurchaseLockup.theme = "dark";
+ searchResult = new models.InAppPurchaseSearchResult(todayCardInAppPurchaseLockup);
+ }
+ else if (searchLockupCollection.resultDataShouldRenderLockupCollection(objectGraph, resultData, searchResponseMetadata)) {
+ const lockupCollection = searchLockupCollection.lockupCollectionFromResultData(objectGraph, resultData, metricsOptions);
+ if (lockupCollection) {
+ searchResult = lockupCollection;
+ }
+ }
+ else {
+ const editorialSearchResult = editorialSearchResultFromData(objectGraph, resultData, standardLockupOptions.metricsOptions, condensedBehavior);
+ if (editorialSearchResult) {
+ if (editorialSearchResult.title) {
+ editorialSearchResult.title = editorialSearchResult.title.replace(/\n/g, " ");
+ }
+ if (editorialSearchResult instanceof EditorialSearchResult && editorialSearchResult.subtitle) {
+ editorialSearchResult.subtitle = editorialSearchResult.subtitle.replace(/\n/g, " ");
+ }
+ searchResult = editorialSearchResult;
+ }
+ }
+ break;
+ case "in-apps":
+ if (objectGraph.client.isVision || objectGraph.client.isWeb) {
+ return null;
+ }
+ // Ensure the parent app data is available before proceeding with building the lockup.
+ const parentData = lockups.parentDataFromInAppData(objectGraph, resultData);
+ if (serverData.isNullOrEmpty(parentData)) {
+ return null;
+ }
+ const inAppPurchaseLockup = lockups.inAppPurchaseLockupFromData(objectGraph, resultData, standardLockupOptions);
+ inAppPurchaseLockup.theme = "dark";
+ modifyMetadataBadgeForSearchExperiment(objectGraph, inAppPurchaseLockup, searchExperimentsData);
+ searchResult = new models.InAppPurchaseSearchResult(inAppPurchaseLockup);
+ break;
+ case "apps":
+ case "app-bundles":
+ default:
+ // There should never be an iad in the non-ad search results, so remove it here
+ // before creating the lockups.
+ delete resultData.attributes["iad"];
+ if (resultType === "app-bundles") {
+ if (objectGraph.client.isVision) {
+ return null;
+ }
+ standardLockupOptions.shouldIncludeScreenshotsForChildren =
+ objectGraph.featureFlags.isEnabled("voyager_bundles_2025A");
+ const bundleLockup = lockups.lockupFromData(objectGraph, resultData, standardLockupOptions);
+ bundleLockup.showMetadataInformationInLockup = true;
+ modifyMetadataBadgeForSearchExperiment(objectGraph, bundleLockup, searchExperimentsData);
+ searchResult = new models.BundleSearchResult(bundleLockup);
+ }
+ else {
+ const lockup = lockups.mixedMediaLockupFromData(objectGraph, resultData, standardLockupOptions, {
+ canPlayFullScreen: false,
+ playbackControls: {},
+ }, searchExperimentsData);
+ modifyMetadataBadgeForSearchExperiment(objectGraph, lockup, searchExperimentsData);
+ // Extract out any associated app event from meta
+ const appEventSearchResult = appEventSearchResultFromData(objectGraph, resultData, lockup, standardLockupOptions, personalizationDataContainer, metricsOptions);
+ if (serverData.isDefinedNonNull(appEventSearchResult)) {
+ searchResult = appEventSearchResult;
+ searchResult.condensedBehavior = "never";
+ }
+ else {
+ // If no app event in meta, fallback to a regular mixed media lockup
+ searchResult = new models.AppSearchResult(lockup);
+ }
+ }
+ break;
+ }
+ if (serverData.isDefinedNonNull(searchResult) && serverData.isNull(searchResult.condensedBehavior)) {
+ searchResult.condensedBehavior = condensedBehavior;
+ }
+ return searchResult;
+ });
+}
+/**
+ * Indicates whether a search result will display an app event
+ * @param objectGraph The object graph
+ * @param searchResultData The data for the search result
+ * @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 states that is used to determine if the app has been installed by the user in the past
+ * @param metricsOptions Metrics options for built lockups
+ * @param personalizationDataContainer Personalization criteria used for sorting and filtering app events
+ * @returns a boolean result
+ */
+export function searchResultWillUseAppEventDisplay(objectGraph, searchResultData, installStates, appStates, metricsOptions, personalizationDataContainer) {
+ const appEventEligibleToDisplay = searchResultIsEligibleToDisplayAppEvent(objectGraph, searchResultData);
+ if (!appEventEligibleToDisplay) {
+ return false;
+ }
+ const { dataItems } = selectedAppEventDataItems(objectGraph, searchResultData, personalizationDataContainer);
+ let appEvent;
+ for (const appEventDataItem of dataItems) {
+ appEvent = sanitizedAppEvent(objectGraph, appEventDataItem, searchResultData, metricsOptions, undefined, undefined);
+ if (serverData.isDefinedNonNull(appEvent)) {
+ break;
+ }
+ }
+ const appIsInstalled = serverData.isDefinedNonNull(installStates) && serverData.isDefinedNonNull(installStates[searchResultData.id])
+ ? installStates[searchResultData.id]
+ : false;
+ const appHasBeenInstalled = serverData.isDefinedNonNull(appStates) && serverData.isDefinedNonNull(appStates[searchResultData.id])
+ ? ["downloadable"].includes(appStates[searchResultData.id])
+ : false;
+ // App Events are only displayed in native when the user has installed the app previously and there is a valid app event
+ return (appIsInstalled || appHasBeenInstalled) && serverData.isDefinedNonNull(appEvent);
+}
+/**
+ * Derive the condensed behavior from a search result
+ * @param objectGraph The global object graph instance
+ * @param resultData The data blob for this specific search result
+ * @param pageSearchExperimentsData The page level search experiments data object
+ */
+function condensedBehaviorForData(objectGraph, resultData, pageSearchExperimentsData) {
+ var _a, _b;
+ /// Guard early against incompatible client devices
+ if (!canHaveCondensedBehaviorForClient(objectGraph)) {
+ return "never";
+ }
+ const itemSearchExperimentData = resultData.meta;
+ const itemCondensedStyle = (_a = itemSearchExperimentData === null || itemSearchExperimentData === void 0 ? void 0 : itemSearchExperimentData.displayStyle) === null || _a === void 0 ? void 0 : _a.condensed;
+ if (serverData.isDefinedNonNull(itemCondensedStyle)) {
+ return condensedBehaviorFromStyle(objectGraph, itemCondensedStyle);
+ }
+ const pageCondensedStyle = (_b = pageSearchExperimentsData === null || pageSearchExperimentsData === void 0 ? void 0 : pageSearchExperimentsData.displayStyle) === null || _b === void 0 ? void 0 : _b.condensed;
+ if (serverData.isDefinedNonNull(pageCondensedStyle)) {
+ return condensedBehaviorFromStyle(objectGraph, pageCondensedStyle);
+ }
+ return defaultCondensedBehaviorForClient(objectGraph);
+}
+/**
+ * Gets the condensed behavior from a given condensed style, or a default behavior based on the client type
+ * @param objectGraph The global object graph instance
+ * @param condensedStyle The condensed style on the data model
+ * @returns The condensed behavior for the native search result view
+ */
+function condensedBehaviorFromStyle(objectGraph, condensedStyle) {
+ switch (condensedStyle) {
+ case "always":
+ return "always";
+ case "never":
+ return "never";
+ case "when-installed":
+ return "whenInstalled";
+ default:
+ return defaultCondensedBehaviorForClient(objectGraph);
+ }
+}
+/**
+ * Determines the default condensed behavior based for a given client type
+ * @param objectGraph The global object graph instance
+ * @param condensedStyle The condensed style on the data model
+ * @returns The condensed behavior for the native search result view
+ */
+function defaultCondensedBehaviorForClient(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ return "whenInstalled";
+ default:
+ return "never";
+ }
+}
+/**
+ * Determines if the current client type supports condensed behavior
+ * @param objectGraph The global object graph instance
+ * @returns Whether condensed behavior is allowed
+ */
+function canHaveCondensedBehaviorForClient(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ return true;
+ default:
+ return false;
+ }
+}
+/**
+ * Modifies the editor's choice badge based on whether the search experiment overrides the metadataPrecendence
+ * for the badge
+ * @param objectGraph App Store ObjectGraph
+ * @param lockup The lockup we need to modify for the experiment
+ * @param searchExperimentData The experiment data to pull the metadata info from
+ */
+function modifyMetadataBadgeForSearchExperiment(objectGraph, lockup, searchExperimentData) {
+ const shouldShowEditorsChoice = metadataPrecedenceTypePreceedsType(objectGraph, searchExperimentData, "editorialBadgeInfo", "userRating");
+ if (serverData.isDefinedNonNull(shouldShowEditorsChoice)) {
+ lockup.isEditorsChoice = lockup.isEditorsChoice && shouldShowEditorsChoice;
+ }
+}
+/**
+ * Determines whether the first metadata type should precede the second type for the given experiment data
+ * @param objectGraph App Store ObjectGraph
+ * @param experimentData The experiment data to pull the metadata info from
+ * @param firstType Does this precede the second type in the experiment order
+ * @param secondType Does this succeed the second type in the experiment order
+ * @returns whether the first type precedes the second type
+ */
+function metadataPrecedenceTypePreceedsType(objectGraph, experimentData, firstType, secondType) {
+ var _a;
+ if (serverData.isNull(experimentData) || !objectGraph.client.isPhone) {
+ return null;
+ }
+ const order = (_a = experimentData === null || experimentData === void 0 ? void 0 : experimentData.displayStyle) === null || _a === void 0 ? void 0 : _a.metadataPrecedenceOrder;
+ if (!serverData.isDefinedNonNullNonEmpty(order)) {
+ return null;
+ }
+ const firstIndex = order.indexOf(firstType);
+ const secondIndex = order.indexOf(secondType);
+ if (firstIndex === -1 && secondIndex === -1) {
+ return null;
+ }
+ if (firstIndex === -1) {
+ return false;
+ }
+ if (secondIndex === -1) {
+ return true;
+ }
+ return firstIndex < secondIndex;
+}
+function editorialSearchResultFromData(objectGraph, resultData, metricsOptions, resultCondensedBehavior) {
+ return validation.context("editorialSearchResultFromData", () => {
+ let searchResult;
+ const title = mediaAttributes.attributeAsString(resultData, "name");
+ const resultType = resultData.type;
+ switch (resultType) {
+ case "groupings": {
+ const editorialSearchResult = new models.EditorialSearchResult(title);
+ const collectionAdamIds = mediaAttributes.attributeAsArrayOrEmpty(resultData, "contentIds");
+ if (serverData.isDefinedNonNullNonEmpty(collectionAdamIds)) {
+ editorialSearchResult.collectionAdamIds = collectionAdamIds;
+ }
+ else {
+ const iconArtwork = content.artworkFromApiArtwork(objectGraph, mediaAttributes.attributeAsDictionary(resultData, "artwork"), {
+ useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */,
+ allowingTransparency: true,
+ });
+ editorialSearchResult.iconArtwork = iconArtwork;
+ }
+ editorialSearchResult.type = "category";
+ searchResult = editorialSearchResult;
+ break;
+ }
+ case "rooms":
+ case "multirooms": {
+ const editorialSearchResult = new models.EditorialSearchResult(title);
+ editorialSearchResult.artwork = content.artworkFromApiArtwork(objectGraph, mediaAttributes.attributeAsDictionary(resultData, "artwork"), {
+ useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */,
+ cropCode: "sr",
+ });
+ editorialSearchResult.collectionAdamIds = mediaAttributes.attributeAsArrayOrEmpty(resultData, "contentIds");
+ editorialSearchResult.type = "collection";
+ searchResult = editorialSearchResult;
+ break;
+ }
+ case "editorial-items": {
+ if (objectGraph.bag.searchFilterEditorialItemIds.has(resultData.id)) {
+ return null;
+ }
+ // Bridge objects to Today builder.
+ const todayParseContext = new TodayParseContext(metricsOptions.pageInformation, metricsOptions.locationTracker);
+ const shouldResultBeCondensed = resultCondensedBehavior === "always";
+ const editorialSearchResult = editorialSearchResultFromTodayCardData(objectGraph, resultData, todayParseContext, shouldResultBeCondensed);
+ searchResult = editorialSearchResult;
+ break;
+ }
+ case "developers": {
+ const editorialSearchResult = new models.EditorialSearchResult(title);
+ editorialSearchResult.artwork = content.artworkFromApiArtwork(objectGraph, mediaAttributes.attributeAsDictionary(resultData, "editorialArtwork.bannerUber"), {
+ useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */,
+ cropCode: "sr",
+ });
+ editorialSearchResult.type = "developer";
+ if (isSome(editorialSearchResult.artwork)) {
+ searchResult = editorialSearchResult;
+ }
+ else {
+ let topApps = mediaRelationship.relationshipCollection(resultData, "top-apps");
+ topApps = topApps.filter((topApp) => {
+ return !filtering.shouldFilter(objectGraph, topApp, 76532 /* filtering.Filter.DeveloperPage */);
+ });
+ const topAppIds = [];
+ const topAppArtwork = [];
+ topApps.forEach((app) => {
+ topAppIds.push(app.id);
+ const appIcon = content.iconFromData(objectGraph, app, {
+ useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */,
+ });
+ if (serverData.isDefinedNonNull(appIcon)) {
+ topAppArtwork.push(appIcon);
+ }
+ });
+ editorialSearchResult.collectionAdamIds = topAppIds;
+ editorialSearchResult.collectionAppIcons = topAppArtwork;
+ // On visionOS, the developer result falls back to a lockup collection if no art is available.
+ if (objectGraph.client.isVision) {
+ const lockupCollection = searchLockupCollection.lockupCollectionFromResultData(objectGraph, resultData, metricsOptions);
+ searchResult = lockupCollection;
+ }
+ else {
+ searchResult = editorialSearchResult;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ if (serverData.isNull(searchResult)) {
+ return null;
+ }
+ if (searchResult instanceof EditorialSearchResult) {
+ if (searchResult.collectionAdamIds != null && searchResult.collectionAdamIds.length) {
+ const lockupCount = searchResult.collectionAdamIds.length;
+ if (lockupCount <= 5) {
+ searchResult.artworkGridType = "extraLarge";
+ }
+ else if (lockupCount <= 8) {
+ searchResult.artworkGridType = "large";
+ }
+ else if (lockupCount <= 16) {
+ searchResult.artworkGridType = "mixed";
+ }
+ else {
+ searchResult.artworkGridType = "small";
+ }
+ }
+ if (objectGraph.client.isVision) {
+ let badgeText;
+ let badgeArtwork = createArtworkForResource(objectGraph, "systemimage://appstore");
+ switch (searchResult.type) {
+ case "developer":
+ badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_DEVELOPER_TITLE_CASE");
+ badgeArtwork = createArtworkForResource(objectGraph, "systemimage://person.crop.square");
+ break;
+ case "category":
+ badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_CATEGORY_TITLE_CASE");
+ break;
+ case "collection":
+ badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_COLLECTION_TITLE_CASE");
+ break;
+ case "story":
+ badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_STORY_TITLE_CASE");
+ break;
+ default:
+ break;
+ }
+ searchResult.badgeText = badgeText;
+ searchResult.badgeArtwork = badgeArtwork;
+ }
+ }
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, resultData, searchResult.title, metricsOptions);
+ searchResult.clickAction = lockups.actionFromData(objectGraph, resultData, impressionOptions, null);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchResult, impressionOptions);
+ return searchResult;
+ });
+}
+function editorialSearchResultFromTodayCardData(objectGraph, cardData, todayParseContext, shouldResultBeCondensed) {
+ // Card configuration to use for building TodayCards that will be converted into editorial search results.
+ const cardConfig = defaultTodayCardConfiguration(objectGraph);
+ cardConfig.isSearchContext = true;
+ if (!objectGraph.client.isVision) {
+ cardConfig.prevailingCropCodes =
+ shouldResultBeCondensed && objectGraph.client.isPhone
+ ? { defaultCrop: "DMGE.AppST01" }
+ : { defaultCrop: "fo" };
+ }
+ // If we are on visionOS, drop any EIs that have a not-compatible app as the primary content.
+ if (objectGraph.client.isVision) {
+ const primaryContent = content.primaryContentForData(objectGraph, cardData);
+ if (isSome(primaryContent)) {
+ const runnableOnDevice = content.runnableOnDeviceWithData(objectGraph, primaryContent, objectGraph.client.deviceType, objectGraph.appleSilicon.isSupportEnabled);
+ if (!runnableOnDevice) {
+ return null;
+ }
+ }
+ }
+ const todayCard = todayCardFromData(objectGraph, cardData, cardConfig, todayParseContext);
+ if (!todayCard) {
+ return null;
+ }
+ const editorialSearchResult = new models.EditorialSearchResult(todayCard.title);
+ editorialSearchResult.type = "story";
+ editorialSearchResult.clickAction = todayCard.clickAction;
+ let collectionLockups = null;
+ if (todayCard.media) {
+ switch (todayCard.media.kind) {
+ case "brandedSingleApp":
+ const mediaSingleApp = todayCard.media;
+ editorialSearchResult.artwork = mediaSingleApp.artworks[0];
+ if (serverData.isNull(editorialSearchResult.artwork)) {
+ editorialSearchResult.iconArtwork = mediaSingleApp.icon;
+ }
+ const cardDisplayStyle = mediaAttributes.attributeAsString(cardData, "cardDisplayStyle");
+ switch (cardDisplayStyle) {
+ case TodayCardDisplayStyle.AppOfTheDay:
+ case TodayCardDisplayStyle.GameOfTheDay:
+ const relatedContentData = mediaRelationship.relationshipData(objectGraph, cardData, "card-contents");
+ if (relatedContentData) {
+ editorialSearchResult.title =
+ mediaAttributes.attributeAsString(relatedContentData, "name") ||
+ editorialSearchResult.title;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case "list":
+ const mediaList = todayCard.media;
+ collectionLockups = mediaList.lockups;
+ break;
+ case "river":
+ const mediaRiver = todayCard.media;
+ collectionLockups = mediaRiver.lockups;
+ break;
+ case "artwork":
+ const mediaArtwork = todayCard.media;
+ editorialSearchResult.artwork = mediaArtwork.artworks[0];
+ break;
+ case "grid":
+ const mediaGrid = todayCard.media;
+ collectionLockups = mediaGrid.lockups;
+ break;
+ case "multiApp":
+ const multiApp = todayCard.media;
+ collectionLockups = multiApp.lockups;
+ break;
+ case "video":
+ const mediaVideo = todayCard.media;
+ editorialSearchResult.artwork = mediaVideo.videos[0].preview;
+ editorialSearchResult.video = mediaVideo.videos[0];
+ if (todayCard.overlay instanceof models.TodayCardThreeLineOverlay) {
+ const overlay = todayCard.overlay;
+ editorialSearchResult.title = overlay.title;
+ editorialSearchResult.subtitle = overlay.description;
+ }
+ else {
+ editorialSearchResult.subtitle = mediaVideo.description;
+ }
+ break;
+ case "appEvent":
+ const media = todayCard.media;
+ editorialSearchResult.artwork = media.artworks[0];
+ editorialSearchResult.appEventFormattedDates = media.formattedDates;
+ editorialSearchResult.subtitle = todayCard.inlineDescription;
+ editorialSearchResult.tintColor = media.tintColor;
+ editorialSearchResult.type = "appEventStory";
+ if (serverData.isDefinedNonNull(todayCard.style)) {
+ switch (todayCard.style) {
+ case "light":
+ case "white":
+ editorialSearchResult.mediaOverlayStyle = "light";
+ break;
+ case "dark":
+ editorialSearchResult.mediaOverlayStyle = "dark";
+ break;
+ default:
+ errors.unreachable(todayCard.style);
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (todayCard.overlay) {
+ switch (todayCard.overlay.kind) {
+ case "lockup":
+ const cardOverlayLockup = todayCard.overlay;
+ if (!editorialSearchResult.artwork || objectGraph.client.isVision) {
+ collectionLockups = [cardOverlayLockup.lockup];
+ }
+ break;
+ case "lockupList":
+ const cardOverlayList = todayCard.overlay;
+ collectionLockups = cardOverlayList.lockups;
+ break;
+ case "paragraph":
+ const cardOverlayParagraph = todayCard.overlay;
+ editorialSearchResult.subtitle = cardOverlayParagraph.paragraph.text;
+ break;
+ default:
+ break;
+ }
+ }
+ if (serverData.isDefinedNonNull(collectionLockups)) {
+ editorialSearchResult.collectionAdamIds = [];
+ editorialSearchResult.collectionAppIcons = [];
+ for (const lockup of collectionLockups) {
+ editorialSearchResult.collectionAdamIds.push(lockup.adamId);
+ editorialSearchResult.collectionAppIcons.push(lockup.icon);
+ }
+ if (collectionLockups.length === 1) {
+ editorialSearchResult.lockup = collectionLockups[0];
+ }
+ }
+ const editorialClientParams = extractEditorialClientParams(objectGraph, cardData);
+ editorialSearchResult.editorialDisplayOptions = editorialDisplayOptionsFromClientParams(editorialClientParams);
+ /**
+ * Editorial tagline
+ */
+ const storyTagline = common.editorialSearchResultTagline(objectGraph, cardData);
+ if ((storyTagline === null || storyTagline === void 0 ? void 0 : storyTagline.length) > 0 && storyTagline !== editorialSearchResult.title) {
+ editorialSearchResult.tagline = storyTagline;
+ }
+ const heroMedia = todayCard.heroMedia;
+ if (serverData.isDefinedNonNullNonEmpty(heroMedia)) {
+ if (serverData.isDefinedNonNullNonEmpty(heroMedia.artworks[0])) {
+ editorialSearchResult.artwork = heroMedia.artworks[0];
+ editorialSearchResult.artwork.crop = "em";
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(heroMedia.videos[0])) {
+ editorialSearchResult.video = heroMedia.videos[0];
+ }
+ }
+ if (editorialSearchResult.video) {
+ editorialSearchResult.video.canPlayFullScreen = false;
+ editorialSearchResult.video.playbackControls = {};
+ }
+ if (!editorialSearchResult.collectionAdamIds &&
+ !editorialSearchResult.artwork &&
+ !editorialSearchResult.iconArtwork) {
+ return null;
+ }
+ return editorialSearchResult;
+}
+/// Gets the client identifier (or null) that should be used when building lockups for a given search entity.
+function clientIdentifierOverrideForSearchEntity(objectGraph, searchEntity) {
+ return searchEntity === "watch" ? client.watchIdentifier : null;
+}
+/**
+ * Indicates whether a search result meets basic sanity requirements to display app events. This does not include business rules for massaging a valid app event. For that, see `sanitizedAppEvent` function
+ * @param objectGraph The object graph
+ * @param searchResultData The data for the search result
+ * @returns a boolean result
+ */
+function searchResultIsEligibleToDisplayAppEvent(objectGraph, searchResultData) {
+ if (!appPromotionsCommon.appEventsAreEnabled(objectGraph) || serverData.isNull(searchResultData.meta)) {
+ return false;
+ }
+ // The first organic can't use CPP data from the ad if it has an app event that will be displayed.
+ // In this case, it should continue to use DPP assets
+ const appEventDataItems = serverData.asArrayOrEmpty(searchResultData.meta, "associations.app-events.data");
+ const hasInAppEvents = appEventDataItems.length > 0;
+ // App events can be displayed on most search result types, except for these ones
+ const typesIneligibleToDisplayAppEvent = [
+ "rooms",
+ "multirooms",
+ "developers",
+ "editorial-items",
+ "groupings",
+ "in-apps",
+ "app-bundles",
+ ];
+ const typeIsEligibleToDisplayAppEvent = !typesIneligibleToDisplayAppEvent.includes(searchResultData.type);
+ return typeIsEligibleToDisplayAppEvent && hasInAppEvents;
+}
+/**
+ * Sorts and filters app event for personalization, if applicable. May filter app events, and may select just the best one
+ * @param objectGraph The object graph
+ * @param resultData The data for the search result
+ * @param personalizationDataContainer Personalization criteria used for sorting and filtering app events
+ * @returns an object representing the personalized app events for a user, along with the personalization result
+ */
+function selectedAppEventDataItems(objectGraph, resultData, personalizationDataContainer) {
+ const alwaysShowAppEvent = serverData.asBooleanOrFalse(resultData.meta, "associations.app-events.attributes.forceAppEvent");
+ const appEventDataItems = serverData.asArrayOrEmpty(resultData.meta, "associations.app-events.data");
+ if (alwaysShowAppEvent) {
+ // In this scenario, there should always be only one event to choose from,
+ // and we don't want to do any personalization.
+ return { dataItems: [appEventDataItems[0]] };
+ }
+ const personalizedDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "search", appEventDataItems, false, personalizationDataContainer, false, undefined, resultData.id);
+ const personalizedDataItems = personalizedDataResult.personalizedData;
+ if (personalizedDataItems.length <= 0) {
+ return { dataItems: [] };
+ }
+ return { dataItems: personalizedDataItems, personalizationData: personalizedDataResult };
+}
+/**
+ * Sanitizes a raw app event metadata into an AppEvent model. If the event is not hydratable or has not started, this returns null.
+ * @param objectGraph The object graph
+ * @param appEventDataItem The app event that needs to be processed
+ * @param resultData The data for the search result
+ * @param baseMetricsOptions Metrics options for built lockups
+ * @param offerEnvironment Offer environment for the lockup. Typically comes from lockup options
+ * @param offerStyle Offer style for the lockup. Typically comes from lockup options
+ * @returns the processed app event with all business rules applied, or null if the app event was disqualified
+ */
+function sanitizedAppEvent(objectGraph, appEventDataItem, resultData, baseMetricsOptions, offerEnvironment, offerStyle) {
+ const metricsOptions = {
+ ...baseMetricsOptions,
+ targetType: "eventModule",
+ };
+ const appEventOrDate = appEvents.appEventOrPromotionStartDateFromData(objectGraph, appEventDataItem, resultData, false, true, offerEnvironment, offerStyle, false, metricsOptions, false, true, null, false, false);
+ // Ignore a future AppEvent promotionStartDate here.
+ if (serverData.isNull(appEventOrDate) || appEventOrDate instanceof Date) {
+ return null;
+ }
+ else {
+ return appEventOrDate;
+ }
+}
+/**
+ * Creates an app event search result from the data and associated lockup, if an app event exists in the metadata
+ * @param resultData The data blob
+ * @param lockup The associated mixed media lockup
+ * @param standardLockupOptions: The standard lockup options for search results
+ * @param personalizationDataContainer The data container to use for personalizing the selected app event
+ * @param baseMetricsOptions Base metrics options for this result
+ * @returns {AppEventSearchResult} The app event search result, or null
+ */
+function appEventSearchResultFromData(objectGraph, resultData, lockup, standardLockupOptions, personalizationDataContainer, baseMetricsOptions) {
+ return validation.context("appEventSearchResultFromData", () => {
+ const appEventEligibleToDisplay = searchResultIsEligibleToDisplayAppEvent(objectGraph, resultData);
+ if (!appEventEligibleToDisplay) {
+ return null;
+ }
+ const { dataItems, personalizationData } = selectedAppEventDataItems(objectGraph, resultData, personalizationDataContainer);
+ let appEvent;
+ let parentAppData;
+ for (const appEventDataItem of dataItems) {
+ const appEventOrNull = sanitizedAppEvent(objectGraph, appEventDataItem, resultData, baseMetricsOptions, standardLockupOptions.offerEnvironment, standardLockupOptions.offerStyle);
+ if (serverData.isDefinedNonNull(appEventOrNull)) {
+ appEvent = appEventOrNull;
+ parentAppData = resultData !== null && resultData !== void 0 ? resultData : mediaRelationship.relationshipData(objectGraph, appEventDataItem, "app");
+ break;
+ }
+ }
+ if (serverData.isNull(appEvent)) {
+ return null;
+ }
+ const alwaysShowAppEvent = serverData.asBooleanOrFalse(resultData.meta, "associations.app-events.attributes.forceAppEvent");
+ const searchResult = new models.AppEventSearchResult();
+ searchResult.lockup = lockup;
+ searchResult.appEvent = appEvent;
+ searchResult.alwaysShowAppEvent = alwaysShowAppEvent;
+ searchResult.clickAction = lockup.clickAction;
+ const recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(null, personalizationData === null || personalizationData === void 0 ? void 0 : personalizationData.processingType, null);
+ const impressionOptions = {
+ ...baseMetricsOptions,
+ id: appEvent.appEventId,
+ kind: "inAppEvent",
+ targetType: "eventModule",
+ title: appEvent.title,
+ softwareType: null,
+ recoMetricsData: recoMetricsData,
+ };
+ if (serverData.isDefinedNonNull(parentAppData)) {
+ impressionOptions.relatedSubjectIds = [parentAppData.id];
+ }
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchResult, impressionOptions);
+ return searchResult;
+ });
+}
+//# sourceMappingURL=search-results.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-shelves.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-shelves.js
new file mode 100644
index 0000000..03df66d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-shelves.js
@@ -0,0 +1,53 @@
+import * as models from "../../../api/models";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import { asInterface } from "../../../foundation/json-parsing/server-data";
+import { createMetricsOptionsForGenericSearchPageShelf } from "../../metrics/helpers/search/search-shelves";
+import { isNothing } from "@jet/environment";
+// The types of search pages shelves can render in
+export var SearchPageType;
+(function (SearchPageType) {
+ SearchPageType[SearchPageType["Landing"] = 0] = "Landing";
+ SearchPageType[SearchPageType["Results"] = 1] = "Results";
+ SearchPageType[SearchPageType["ChartsAndCategories"] = 2] = "ChartsAndCategories";
+ SearchPageType[SearchPageType["Focus"] = 3] = "Focus";
+})(SearchPageType || (SearchPageType = {}));
+/**
+ * Collections the shelf's attributes
+ * @param objectGraph The App Store Object Graph
+ * @param data The shelf data object
+ * @returns The attributes for the shelf
+ */
+export function shelfAttributesFromData(objectGraph, data, shelfKind = undefined, pageKind = models.SearchPageKind.Default) {
+ var _a, _b, _c;
+ const title = (_a = mediaAttributes.attributeAsString(data, "title")) !== null && _a !== void 0 ? _a : undefined;
+ let displayStyle = (_b = mediaAttributes.attributeAsInterface(data, "displayStyle")) !== null && _b !== void 0 ? _b : undefined;
+ /// If we are on the see-all page, we currently don't get back a displayStyle object, so we need to manually provide one
+ /// for the tile brick type as see-all is hard-coded for that density everywhere else (natively)
+ if (isNothing(displayStyle) && pageKind === models.SearchPageKind.CategoriesAndCharts) {
+ displayStyle = {
+ layoutDensity: models.GenericSearchPageShelfDisplayStyleDensity.Density1,
+ layout: undefined,
+ layoutSize: undefined,
+ };
+ }
+ const itemDisplayStyleAttributes = mediaAttributes.attributeAsDictionary(data, "itemDisplayStyle");
+ const itemDisplayStyle = asInterface(itemDisplayStyleAttributes);
+ const hasSeeAll = mediaAttributes.attributeAsBooleanOrFalse(data, "hasSeeAll");
+ const displayCount = (_c = mediaAttributes.attributeAsNumber(data, "displayCount")) !== null && _c !== void 0 ? _c : undefined;
+ const seeAllURL = hasSeeAll ? data.href : undefined;
+ return new models.SearchShelfAttributes(data.id, title, displayStyle, displayCount, hasSeeAll, seeAllURL, itemDisplayStyle, shelfKind);
+}
+/**
+ * Creates a base shelf context for the search shelf
+ * @param objectGraph The App Store Object Graph
+ * @param data The shelf data object
+ * @param shelfAttributes The shelf's attributes
+ * @param searchPageContext The context for the page containing the shelf
+ * @returns A standard shelf context for the shelf
+ */
+export function baseShelfContext(objectGraph, data, shelfAttributes, searchPageContext) {
+ return {
+ metricsOptions: createMetricsOptionsForGenericSearchPageShelf(objectGraph, data, shelfAttributes, searchPageContext),
+ };
+}
+//# sourceMappingURL=search-shelves.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/custom-creative.js b/node_modules/@jet-app/app-store/tmp/src/common/search/custom-creative.js
new file mode 100644
index 0000000..9b3aa26
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/custom-creative.js
@@ -0,0 +1,71 @@
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { isNothing, isSome } from "@jet/environment";
+import { asDictionary } from "@apple-media-services/media-api";
+import { artworkFromApiArtwork } from "../content/content";
+import { asString } from "../../foundation/json-parsing/server-data";
+import { Video } from "../../api/models";
+export function getSelectedCustomCreativeId(data) {
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ const customCreativeMeta = asDictionary(data, "meta.creativeAttributes");
+ const selectedCustomCreativeId = asString(customCreativeMeta, "creatives.0");
+ return selectedCustomCreativeId;
+ }
+ return null;
+}
+/**
+ * Create a custom creative artwork from the data.
+ * @param objectGraph Object graph
+ * @param data that we use to populate custom creative artwork
+ * @param customCreativeData where we store custom creative data.
+ * @param cropCode for the artwork
+ */
+export function customCreativeArtworkFromData(objectGraph, data, customCreativeData, cropCode) {
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ const selectedCustomCreativeId = getSelectedCustomCreativeId(data);
+ if (isNothing(customCreativeData) || isNothing(selectedCustomCreativeId)) {
+ return null;
+ }
+ const creativeAssetArtwork = artworkFromApiArtwork(objectGraph, serverData.asDictionary(customCreativeData, `${selectedCustomCreativeId}.adCreativeArtwork`), {
+ allowingTransparency: false,
+ useCase: 4 /* ArtworkUseCase.LockupScreenshots */,
+ });
+ if (isSome(cropCode) && isSome(creativeAssetArtwork)) {
+ creativeAssetArtwork.crop = cropCode;
+ }
+ return creativeAssetArtwork;
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Create a custom creative video from the data.
+ * @param objectGraph Object graph
+ * @param data that we use to populate custom creative video and preview.
+ * @param videoConfiguration for the custom creative video.
+ * @param customCreativeData where we store custom creative data.
+ * @param cropCode for the artwork
+ */
+export function customCreativeVideoFromData(objectGraph, data, customCreativeData, videoConfiguration, cropCode) {
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ const selectedCustomCreativeId = getSelectedCustomCreativeId(data);
+ if (isNothing(selectedCustomCreativeId)) {
+ return null;
+ }
+ const creativeVideoData = serverData.asDictionary(customCreativeData, `${selectedCustomCreativeId}.adCreativeVideo`);
+ const videoUrl = serverData.asString(creativeVideoData, "video");
+ const preview = artworkFromApiArtwork(objectGraph, serverData.asDictionary(creativeVideoData, "previewFrame"), {
+ allowingTransparency: false,
+ useCase: 4 /* ArtworkUseCase.LockupScreenshots */,
+ });
+ if (isNothing(videoUrl) || isNothing(preview)) {
+ return null;
+ }
+ if (isSome(cropCode)) {
+ preview.crop = cropCode;
+ }
+ return new Video(videoUrl, preview, videoConfiguration);
+ }
+ return null;
+}
+//# sourceMappingURL=custom-creative.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search-metrics.js b/node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search-metrics.js
new file mode 100644
index 0000000..83cdf90
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search-metrics.js
@@ -0,0 +1,132 @@
+/**
+ * Builds metrics entities for Guided Search
+ */
+import { ImpressionMetrics, } from "../../../api/models";
+import { isDefinedNonNullNonEmpty, isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { createMetricsClickData, createMetricsSearchData } from "../../metrics/builder";
+import { createBasicLocation, currentPosition } from "../../metrics/helpers/location";
+// region Action Metrics
+/**
+ * Add click + search metrics data to guided search token action
+ * @param objectGraph The dependency graph for the App Store.
+ * @param action Action to instrument, e.g. toggle or rewrite.
+ * @param targetToken The token display label, which may be toggleable.
+ * @param searchTerm The search term for which this token action was returned for.
+ * @param metricsOptions The metrics option use for adding instrumentation to token toggle.
+ */
+export function addEventsToGuidedSearchTokenAction(objectGraph, action, targetToken, searchTerm, metricsOptions) {
+ // Click Fields
+ const actionType = "guidedSearch";
+ const targetType = "guidedLabel";
+ const clickFields = {
+ actionType: actionType,
+ location: createBasicLocation(objectGraph, {
+ pageInformation: null,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: targetType,
+ }, targetToken),
+ searchTerm: searchTerm,
+ };
+ // Search Fields
+ const searchFields = {
+ targetId: targetToken,
+ };
+ // SSS: Clicks must be before Search
+ const clickData = createMetricsClickData(objectGraph, targetToken, targetType, clickFields, ["guidedSearch"]);
+ action.actionMetrics.addMetricsData(clickData);
+ const searchData = createMetricsSearchData(objectGraph, searchTerm, targetType, actionType, null, searchFields, [
+ "guidedSearch",
+ ]);
+ action.actionMetrics.addMetricsData(searchData);
+}
+export function addEventsToGuidedSearchTokenEntityChangeAction(objectGraph, action, searchTerm, targetEntity, metricsOptions) {
+ // Click Fields
+ const actionType = "hint";
+ const targetType = "hintsEntity";
+ const clickFields = {
+ actionType: actionType,
+ location: createBasicLocation(objectGraph, {
+ pageInformation: null,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: targetType,
+ }, targetEntity),
+ searchTerm: searchTerm,
+ };
+ // Search Fields
+ const searchFields = {
+ targetId: targetEntity,
+ };
+ // SSS: Clicks must be before Search
+ const clickData = createMetricsClickData(objectGraph, targetEntity, targetType, clickFields);
+ action.actionMetrics.addMetricsData(clickData);
+ const searchData = createMetricsSearchData(objectGraph, searchTerm, targetType, actionType, null, searchFields);
+ action.actionMetrics.addMetricsData(searchData);
+}
+// endregion
+// region Page Metrics
+/**
+ * Build a `MetricsFields` for guided search fields used for page and impression events.
+ * This is attached onto `MetricsPageInformation` for consumption during page and impression field generation.
+ */
+export function guidedSearchPageInformationFields(objectGraph, request, guidedSearchData) {
+ const fields = {};
+ // field for what tokens were selected on page.
+ if (isDefinedNonNullNonEmpty(request.guidedSearchTokens)) {
+ fields["searchSelectedGuidedFacets"] = request.guidedSearchTokens;
+ }
+ // field for what the server computed final query was
+ if (guidedSearchData && guidedSearchData.finalTerm) {
+ fields["searchGuidedFinalQuery"] = guidedSearchData.finalTerm;
+ }
+ if (isNullOrEmpty(fields)) {
+ return undefined;
+ }
+ return fields;
+}
+// endregion
+// region Impression Metrics
+export function addImpressionMetricsToGuidedSearchToken(objectGraph, token, type, metricsOptions) {
+ const tokenIndex = currentPosition(metricsOptions.locationTracker);
+ /**
+ * ID for parent is index. This should be explicit but is not in how we do metrics in JS today (sequential IDs are added later)
+ */
+ const impressionFields = {
+ impressionIndex: tokenIndex,
+ id: tokenIndex.toString(),
+ idType: "sequential",
+ name: token.displayName,
+ impressionType: type,
+ parentId: "search-revisions",
+ };
+ token.impressionMetrics = new ImpressionMetrics(impressionFields);
+}
+/**
+ * Add imaginary parent container for search results
+ * <rdar://problem/70515816> Guided Search: Tech Debt: Trim parent impression tech debt off SearchResults (JS Compatibility Breaking)
+ */
+export function addSearchResultParentImpressionMetrics(objectGraph, searchResultsPage, metricsOptions) {
+ const parentIndex = currentPosition(metricsOptions.locationTracker);
+ searchResultsPage.resultsParentImpressionMetrics = new ImpressionMetrics({
+ impressionIndex: parentIndex,
+ impressionType: "SearchResults",
+ idType: "relationship",
+ id: "search-results",
+ name: "Search Results",
+ });
+}
+/**
+ * Add an imaginary parent container for guided search tokens
+ * <rdar://problem/70515816> Guided Search: Tech Debt: Trim parent impression tech debt off SearchResults (JS Compatibility Breaking)
+ */
+export function addGuidedSearchParentImpressionMetrics(objectGraph, searchResultsPage, metricsOptions) {
+ const parentIndex = currentPosition(metricsOptions.locationTracker);
+ searchResultsPage.guidedSearchTokensParentImpressionMetrics = new ImpressionMetrics({
+ impressionIndex: parentIndex,
+ impressionType: "SearchRevisions",
+ idType: "relationship",
+ id: "search-revisions",
+ name: "Search Revisions",
+ });
+}
+// endregion
+//# sourceMappingURL=guided-search-metrics.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search.js b/node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search.js
new file mode 100644
index 0000000..3be4ae9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search.js
@@ -0,0 +1,105 @@
+/**
+ * Model Builder for Guided Search (STUB)
+ */
+import { GuidedSearchQuery, GuidedSearchToken, SearchAction, searchEntitySystemImage, } from "../../../api/models";
+import { GuidedSearchTokenToggleAction, SearchEntityChangeAction, } from "../../../api/models/search/guided-search-actions";
+import { isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { unreachable } from "../../../foundation/util/errors";
+import { addEventsToGuidedSearchTokenEntityChangeAction, addEventsToGuidedSearchTokenAction, addImpressionMetricsToGuidedSearchToken, } from "./guided-search-metrics";
+// region exports
+/**
+ * Create a Guided search token from facet data.
+ * @param objectGraph The App Store object graph.
+ * @param selectionBehavior The behavior for click action, e.g. query rewrite versus toggling tokens.
+ * @param requestDescriptor The request descriptor for search request that returned this data.
+ * @param facetData The facet data to build with.
+ * @param metricsOptions The metrics options.
+ */
+export function createGuidedSearchToken(objectGraph, selectionBehavior, requestDescriptor, facetData, metricsOptions) {
+ if (isNullOrEmpty(facetData)) {
+ return null;
+ }
+ // Create click and search metrics for token action.
+ const origin = "guidedToken";
+ const searchTerm = requestDescriptor.term;
+ const targetToken = facetData.displayLabel;
+ const clickAction = selectionBehavior === "rewrite"
+ ? new SearchAction(targetToken, facetData.finalTerm, null, origin)
+ : new GuidedSearchTokenToggleAction(targetToken, origin);
+ addEventsToGuidedSearchTokenAction(objectGraph, clickAction, targetToken, searchTerm, metricsOptions);
+ // Create the token with associated click action and metrics..
+ const token = new GuidedSearchToken(targetToken, facetData.isSelected, undefined, targetToken, clickAction);
+ addImpressionMetricsToGuidedSearchToken(objectGraph, token, "guidedLabel", metricsOptions);
+ return token;
+}
+/**
+ * Create the guided search queries from facet data.
+ * These are stored across a guided search session so client can send down precomputed combinations of search term and guided search facets as an optimization.
+ * @param requestDescriptor The request descriptor for search request that returned this data.
+ * @param facetData The data to generate `GuidedSearchQuery` for.
+ */
+export function createGuidedSearchQueries(objectGraph, requestDescriptor, facetData) {
+ var _a;
+ if (isNullOrEmpty(facetData)) {
+ return null;
+ }
+ // request parameters that returned this `facetData`
+ const requestTerm = requestDescriptor.term;
+ const requestFacets = (_a = requestDescriptor.guidedSearchTokens) !== null && _a !== void 0 ? _a : [];
+ const queries = [];
+ for (const data of facetData) {
+ /**
+ * For each facet data, project the facet selection if that facet was selected / deselected w.r.t. request's facets
+ */
+ const facetValue = data.displayLabel;
+ const lookaheadFacets = Array.from(requestFacets);
+ if (data.isSelected) {
+ const facetIndex = lookaheadFacets.indexOf(facetValue);
+ if (facetIndex !== -1) {
+ lookaheadFacets.splice(facetIndex, 1);
+ }
+ }
+ else {
+ lookaheadFacets.push(facetValue);
+ }
+ const query = new GuidedSearchQuery(requestTerm, lookaheadFacets, data.finalTerm);
+ queries.push(query);
+ }
+ return queries;
+}
+/**
+ * Create an action for **reversing** a selected entity hint. This is so user can reverse an selected entity filter for search.
+ * e.g. If user tapped "Search for Games in Apple Watch Apps", user can deselect the "Apple Watch Apps" entity filter from the Guided Search UI
+ */
+export function createGuidedSearchTokenClearingEntityFilter(objectGraph, requestDescriptor, metricsOptions) {
+ var _a;
+ const selectedEntityFilter = requestDescriptor.searchEntity;
+ if (!selectedEntityFilter) {
+ return null;
+ }
+ const deselectEntityAction = new SearchEntityChangeAction(null, "guidedToken");
+ addEventsToGuidedSearchTokenEntityChangeAction(objectGraph, deselectEntityAction, requestDescriptor.term, selectedEntityFilter, metricsOptions);
+ let displayText;
+ switch (selectedEntityFilter) {
+ case "arcade":
+ displayText = objectGraph.loc.string("GUIDED_SEARCH_TOKEN_ENTITY_ARCADE");
+ break;
+ case "developer":
+ displayText = objectGraph.loc.string("GUIDED_SEARCH_TOKEN_ENTITY_DEVELOPERS");
+ break;
+ case "story":
+ displayText = objectGraph.loc.string("GUIDED_SEARCH_TOKEN_ENTITY_STORIES");
+ break;
+ case "watch":
+ displayText = objectGraph.loc.string("GUIDED_SEARCH_TOKEN_ENTITY_APPLEWATCH");
+ break;
+ default:
+ unreachable(selectedEntityFilter);
+ break;
+ }
+ const token = new GuidedSearchToken(displayText, true, (_a = searchEntitySystemImage(selectedEntityFilter)) !== null && _a !== void 0 ? _a : "magnifyingglass", displayText, deselectEntityAction);
+ addImpressionMetricsToGuidedSearchToken(objectGraph, token, "hintsEntity", metricsOptions);
+ return token;
+}
+// endregion
+//# sourceMappingURL=guided-search.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-cohort.js b/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-cohort.js
new file mode 100644
index 0000000..6baf947
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-cohort.js
@@ -0,0 +1,84 @@
+/**
+ * Manages storing Cohort IDs for Search Landing Page (SLP)
+ *
+ * # What is Cohort ID?
+ * Cohort IDs (a.k.a. cluster IDs) are used to bucket users into different categories, e.g. a gamer.
+ * This ID can be used to specify a SLP that is suited to that cateogry of users, e.g. a page featuring more games.
+ *
+ * # SLP Endpoint Constraints
+ * Today, SLPs are not personalized, and rely heavily on CDN caching.
+ * We cannot fire a single request to SLP endpoint to have it adapt to a user's cohort based on cookies, etc.
+ *
+ * As a workaround we:
+ * 1. Store the cohort ID for user if we ever load a personalized endpoint, e.g. Today.
+ * 2. Send stored cohort ID as a query param on the SLP endpoint, if we have any.
+ */
+"use strict";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+// region exports
+/**
+ * Should be called whenever we recieve a MAPI response from a personalized endpoint.
+ * Persists the cohort id that may be present in given MAPI respose for specified user.
+ * @param dsid The user's dsid.
+ * @param metaDataProviding MAPI response that may contain cohort id for current user.
+ */
+export function storeCohortIdForUserFromResponse(objectGraph, dsid, metaDataProviding) {
+ if (serverData.isNullOrEmpty(dsid)) {
+ return;
+ }
+ const cohortId = cohortIdFromResponse(metaDataProviding);
+ if (serverData.isNullOrEmpty(cohortId)) {
+ return;
+ }
+ setCohortIdForDSID(objectGraph, dsid, cohortId);
+}
+/**
+ * Return the stored cohort id for given dsid.
+ * @param dsid The DSID of user to fetch cohort id for.
+ */
+export function cohortIdForUser(objectGraph, dsid) {
+ if (serverData.isNullOrEmpty(dsid)) {
+ return null;
+ }
+ return getCohortIdForDSID(objectGraph, dsid);
+}
+/**
+ * Deletes cohort id for given user. For testing.
+ */
+export function deleteCohortIdForUser(objectGraph, dsid) {
+ setCohortIdForDSID(objectGraph, dsid, undefined);
+}
+// endregion
+// region Internals
+/**
+ * Given a top-level MAPI response `metaDataProviding`, returns the cohort ID, if any.
+ * @param metaDataProviding A MAPI Response that may contain a `meta.metrics` blob with `clusterId`
+ */
+function cohortIdFromResponse(metaDataProviding) {
+ return serverData.asString(metaDataProviding, "meta.metrics.clusterId");
+}
+/**
+ * Converts a DSID into a dictionary key for storing cohort ID.
+ * @param dsid DSID to generate storage key for.
+ */
+function cohortIDStorageKeyForDSID(dsid) {
+ return dsid + "-cohort-id";
+}
+/**
+ * Set the stored cohort id for given user (by dsid).
+ * @param dsid DSID to associate cohortId with
+ * @param cohortId The cohort id for user.
+ */
+function setCohortIdForDSID(objectGraph, dsid, cohortId) {
+ const cohortForDSIDKey = cohortIDStorageKeyForDSID(dsid);
+ objectGraph.storage.storeString(cohortForDSIDKey, cohortId);
+}
+/**
+ * Gets the stored cohort id for given user by dsid.
+ * @param dsid DSID to get cohort id for.
+ */
+function getCohortIdForDSID(objectGraph, dsid) {
+ const cohortForDSIDKey = cohortIDStorageKeyForDSID(dsid);
+ return objectGraph.storage.retrieveString(cohortForDSIDKey);
+}
+//# sourceMappingURL=search-landing-cohort.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-shelf-controller.js
new file mode 100644
index 0000000..808384b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-shelf-controller.js
@@ -0,0 +1,836 @@
+import { isNothing, isSome, unwrapOptional } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as filtering from "../../filtering";
+import * as adLockups from "../../lockups/ad-lockups";
+import * as lockups from "../../lockups/lockups";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationships from "../../../foundation/media/relationships";
+import { searchLandingPageAdShelfIdentifier, searchLandingPagePositionInfo } from "../../ads/on-device-ad-stitch";
+import { createArtworkForResource } from "../../content/artwork/artwork";
+import * as content from "../../content/content";
+import * as metricsHelpersPage from "../../metrics/helpers/page";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { createChartsCategoryShelf } from "../content/search-categories";
+import { MediumAdLockupWithScreenshotsBackground, } from "../../../api/models";
+import * as adStitch from "../../ads/ad-stitcher";
+import * as adIncidents from "../../ads/ad-incident-recorder";
+import * as searchShelves from "../content/search-shelves";
+import { CollectionShelfDisplayStyle } from "../../editorial-pages/editorial-page-types";
+import { buildBrick } from "../../editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-brick-collection-shelf-builder";
+import { iconFromData } from "../../content/content";
+import * as searchHistoryShelf from "../shelves/search-history-shelf";
+import { adLogger } from "../search-ads";
+import { asString } from "../../../foundation/json-parsing/server-data";
+import { addImpressionsFieldsToAd } from "../../metrics/helpers/impressions";
+import { makeSearchResultsPageIntent } from "../../../api/intents/search-results-page-intent";
+import { getLocale } from "../../locale";
+import { actionFor } from "../../../foundation/runtime/action-provider";
+import * as impressionDemotion from "../../personalization/on-device-impression-demotion";
+import { applySearchAdMissedOpportunityToShelvesIfNeeded } from "../../ads/ad-common";
+// #region Shelf Creation
+export function firstShelfMarkerMatchingUseCase(dataContainer, searchLandingPageContext, onDevicePersonalizationUseCase) {
+ const shelfData = dataContainer.data;
+ if (serverData.isNullOrEmpty(shelfData)) {
+ return null;
+ }
+ for (const dataItem of shelfData) {
+ if (serverData.isNullOrEmpty(dataItem)) {
+ continue;
+ }
+ /// Skip shelf if not valid for context page type.
+ const shelfMetadata = serverData.asDictionary(dataItem, "meta");
+ const shelfPageType = pageTypeFromShelfMetaCategory(shelfMetadata === null || shelfMetadata === void 0 ? void 0 : shelfMetadata.category);
+ if (shelfPageType !== searchLandingPageContext.pageType) {
+ continue;
+ }
+ if (onDevicePersonalizationUseCase ===
+ mediaAttributes.attributeAsString(dataItem, "onDevicePersonalizationUseCase")) {
+ return dataItem;
+ }
+ }
+ return null;
+}
+/**
+ * Inserts the shelf made from the data container into the grouping parse context
+ * @param objectGraph The App Store dependency graph
+ * @param dataContainer The response data
+ * @param searchLandingPageContext The context for the search page, e.g. landing or focus
+ */
+export function insertShelvesIntoSearchPageContext(objectGraph, dataContainer, searchLandingPageContext) {
+ var _a;
+ const shelfData = dataContainer.data;
+ if (serverData.isNullOrEmpty(shelfData)) {
+ return;
+ }
+ // index to compare adPositionInfo
+ let builtShelves = 0;
+ const supportsFocus = objectGraph.bag.mediaAPISearchFocusEnabled && isSome(searchLandingPageContext.pageType);
+ for (const dataItem of shelfData) {
+ if (serverData.isNullOrEmpty(dataItem)) {
+ continue;
+ }
+ /// Skip shelf if not valid for context page type.
+ if (supportsFocus) {
+ const shelfMetadata = serverData.asDictionary(dataItem, "meta");
+ const shelfPageType = pageTypeFromShelfMetaCategory(shelfMetadata === null || shelfMetadata === void 0 ? void 0 : shelfMetadata.category);
+ if (isSome(shelfPageType) && shelfPageType !== searchLandingPageContext.pageType) {
+ continue;
+ }
+ }
+ const adMeta = dataContainer.meta || null;
+ const adUnitShelf = shelfFromAdStitcher(objectGraph, searchLandingPageContext, builtShelves, adMeta);
+ if (isSome(adUnitShelf)) {
+ searchLandingPageContext.shelves.push(adUnitShelf);
+ metricsHelpersLocation.nextPosition(searchLandingPageContext.metricsLocationTracker);
+ }
+ /// Get the necessary metadata for the shelf
+ const shelfKind = mediaAttributes.attributeAsString(dataItem, "contentKind");
+ const shelfAttributes = searchShelves.shelfAttributesFromData(objectGraph, dataItem, shelfKind);
+ const shelfContext = searchLandingPageShelfContext(objectGraph, dataItem, shelfAttributes, searchLandingPageContext, shelfKind);
+ /// Push the shelf content location so each shelf has the correct parent and starting index
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfContext.metricsOptions, (_a = shelfAttributes.title) !== null && _a !== void 0 ? _a : "");
+ /// Create the shelf
+ const shelf = createShelf(objectGraph, dataItem, searchLandingPageContext, shelfContext, shelfAttributes, shelfKind);
+ /// Pop the shelf location
+ metricsHelpersLocation.popLocation(searchLandingPageContext.metricsLocationTracker);
+ /// If the shelf is empty, skip it
+ if (serverData.isNullOrEmpty(shelf)) {
+ continue;
+ }
+ /// Add impressions for the shelf
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfContext.metricsOptions);
+ applySearchAdMissedOpportunityToShelvesIfNeeded(objectGraph, [shelf], "searchLanding", shelfContext.metricsOptions.id, searchLandingPageContext.metricsPageInformation);
+ /// Add the shelf to the page context for processing later
+ searchLandingPageContext.shelves.push(shelf);
+ builtShelves += 1;
+ /// Make sure each shelf is represented by its own impressions index position
+ metricsHelpersLocation.nextPosition(searchLandingPageContext.metricsLocationTracker);
+ }
+}
+function pageTypeFromShelfMetaCategory(category) {
+ switch (category) {
+ case "search-landing":
+ return searchShelves.SearchPageType.Landing;
+ case "search-focus":
+ return searchShelves.SearchPageType.Focus;
+ default:
+ return undefined;
+ }
+}
+/**
+ * Creates the appropriate shelf for the shelf's type
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfContext The context for the shelf
+ * @param shelfAttributes The attributes for the shelf
+ * @param shelfKind The content kind for the shelf
+ * @returns A shelf if supported, null otherwise
+ */
+function createShelf(objectGraph, data, pageContext, shelfContext, shelfAttributes, shelfKind) {
+ switch (shelfKind) {
+ case models.SearchLandingPageContentKind.Suggestion:
+ if (objectGraph.client.isVision || objectGraph.client.isWeb) {
+ return createSuggestedLinksShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ }
+ else if (pageContext.pageType === searchShelves.SearchPageType.Focus) {
+ return createSuggestedSearchesShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ }
+ else {
+ return createSuggestedLinkActionsShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ }
+ case models.SearchLandingPageContentKind.CategoriesAndCharts:
+ return createChartsCategoryShelf(objectGraph, data, false, shelfAttributes, pageContext, shelfContext);
+ case models.SearchLandingPageContentKind.Apps:
+ return createLockupsShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ case models.SearchLandingPageContentKind.EditorialCollection:
+ if (objectGraph.client.isVision || objectGraph.client.isWeb) {
+ return buildBrickShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ }
+ else {
+ return null;
+ }
+ default:
+ return createShelfFromMarker(objectGraph, data, pageContext, shelfContext, shelfAttributes);
+ }
+}
+/**
+ * Creates the appropriate shelf for the shelf's type
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfContext The context for the shelf
+ * @param shelfAttributes The attributes for the shelf
+ * @returns A shelf if supported, null otherwise
+ */
+function createShelfFromMarker(objectGraph, data, pageContext, shelfContext, shelfAttributes) {
+ if (data.type !== "search-recommendations-marker") {
+ return null;
+ }
+ switch (mediaAttributes.attributeAsString(data, "onDevicePersonalizationUseCase")) {
+ case "recentSearches":
+ return searchHistoryShelf.createShelfWithContext(objectGraph, pageContext, shelfAttributes);
+ default:
+ return null;
+ }
+}
+/**
+ * Creates the appropriate shelf for the shelf's type
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this shelf
+ * @param pageContext The context for the shelf's page
+ */
+function createMediumAdLockupWithScreenshotsBackgroundShelf(objectGraph, data, pageContext) {
+ var _a, _b, _c;
+ const shelf = new models.Shelf("mediumAdLockupWithScreenshotsBackground");
+ shelf.isHorizontal = false;
+ const offerEnvironment = "dark";
+ const offerStyle = "white";
+ const metricsOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, asString(data.attributes.name), {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ targetType: "card",
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ isAdvert: adLockups.isAdvert(objectGraph, data),
+ });
+ metricsOptions.kind = "adItem";
+ // Set up iAdInfo
+ metricsOptions.pageInformation.iAdInfo.apply(objectGraph, data);
+ const lockupOptions = {
+ offerEnvironment: offerEnvironment,
+ offerStyle: offerStyle,
+ metricsOptions: {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ isAdvert: adLockups.isAdvert(objectGraph, data),
+ disableFastImpressionsForAds: true,
+ },
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, "mediumAdLockupWithScreenshotsBackground"),
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "mediumAdLockupWithScreenshotsBackground"),
+ };
+ const videoConfiguration = {
+ canPlayFullScreen: false,
+ playbackControls: {},
+ };
+ let lockup = lockups.mixedMediaAdLockupFromData(objectGraph, data, lockupOptions, videoConfiguration, null, false);
+ const platformScreenshots = lockup.screenshots[0];
+ const templateString = adLockups.getTemplateTypeForMediumAdFromLockupWithScreenshots(platformScreenshots);
+ pageContext.metricsPageInformation.iAdInfo.setTemplateType(templateString);
+ const iconData = iconFromData(objectGraph, data, {
+ useCase: 0 /* ArtworkUseCase.Default */,
+ withJoeColorPlaceholder: true,
+ overrideTextColorKey: "textColor2",
+ });
+ // Update the lockup value after setting the template type so that the value gets added to the lockup.
+ lockup = lockups.mixedMediaAdLockupFromData(objectGraph, data, lockupOptions, videoConfiguration, null, false);
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_a = lockup.searchAdOpportunity) === null || _a === void 0 ? void 0 : _a.setTemplateType(templateString);
+ }
+ else {
+ (_b = lockup.searchAd) === null || _b === void 0 ? void 0 : _b.setTemplateType(templateString);
+ }
+ const backgroundColor = iconData.backgroundColor;
+ const secondaryTextColor = iconData.textColor;
+ const mediumAd = new MediumAdLockupWithScreenshotsBackground(lockup, [platformScreenshots], true, secondaryTextColor, backgroundColor, (_c = objectGraph.bag.todayAdMediumLockupScreenshotsRiverSpeed) !== null && _c !== void 0 ? _c : 8);
+ addImpressionsFieldsToAd(objectGraph, mediumAd, metricsOptions, metricsOptions.pageInformation.iAdInfo);
+ mediumAd.clickAction = lockups.actionFromData(objectGraph, data, metricsOptions, null);
+ shelf.items = [mediumAd];
+ return shelf;
+}
+/**
+ * Creates the appropriate shelf for the shelf's type
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this shelf
+ * @param pageContext The context for the shelf's page
+ */
+function createCondensedAdLockupWithIconBackgroundShelf(objectGraph, data, pageContext) {
+ var _a, _b, _c;
+ const shelf = new models.Shelf("condensedAdLockupWithIconBackground");
+ shelf.isHorizontal = false;
+ const offerEnvironment = "dark";
+ const offerStyle = "white";
+ const metricsOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, asString(data.attributes.name), {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ targetType: "card",
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ isAdvert: adLockups.isAdvert(objectGraph, data),
+ });
+ metricsOptions.kind = "adItem";
+ // Set up iAdInfo
+ metricsOptions.pageInformation.iAdInfo.apply(objectGraph, data);
+ const lockupOptions = {
+ offerEnvironment: offerEnvironment,
+ offerStyle: offerStyle,
+ metricsOptions: {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ isAdvert: adLockups.isAdvert(objectGraph, data),
+ disableFastImpressionsForAds: true,
+ },
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, "condensedAdLockupWithIconBackground"),
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "condensedAdLockupWithIconBackground"),
+ };
+ (_a = pageContext.metricsPageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.setTemplateType("APPLOCKUP");
+ const videoConfiguration = {
+ canPlayFullScreen: false,
+ playbackControls: {},
+ };
+ const lockup = lockups.mixedMediaAdLockupFromData(objectGraph, data, lockupOptions, videoConfiguration, null, false);
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_b = lockup.searchAdOpportunity) === null || _b === void 0 ? void 0 : _b.setTemplateType("APPLOCKUP");
+ }
+ else {
+ (_c = lockup.searchAd) === null || _c === void 0 ? void 0 : _c.setTemplateType("APPLOCKUP");
+ }
+ const condensedAd = new models.CondensedAdLockupWithIconBackground(lockup, lockup.icon);
+ addImpressionsFieldsToAd(objectGraph, condensedAd, metricsOptions, metricsOptions.pageInformation.iAdInfo);
+ shelf.items = [condensedAd];
+ return shelf;
+}
+// #endregion
+// #region Suggested Links/Actions Shelves
+/**
+ * Creates the suggested links shelf
+ * @param objectGraph The App Store dependency graph
+ * @param data The content items for the shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns A shelf for the suggested links, or null if the shelf would be empty
+ */
+function createSuggestedLinkActionsShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ var _a;
+ const linkData = mediaRelationships.relationshipCollection(data, "contents");
+ const items = [];
+ const shelf = new models.Shelf("action");
+ shelf.isHorizontal = false;
+ shelf.title = shelfAttributes.title;
+ shelf.presentationHints = { isWidthConstrained: true };
+ for (const [linkIndex, link] of linkData.entries()) {
+ const metricsBase = {
+ targetType: "link",
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ };
+ const searchAdAction = trendingSearchLinkActionFromData(objectGraph, link, pageContext, shelfAttributes);
+ if (serverData.isNull(searchAdAction) || serverData.isNull(searchAdAction.action)) {
+ continue;
+ }
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchAdAction.action, {
+ ...metricsBase,
+ kind: "link",
+ softwareType: null,
+ title: searchAdAction.action.title,
+ id: `${linkIndex}`,
+ idType: "sequential",
+ });
+ items.push(searchAdAction);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ }
+ if (serverData.isNullOrEmpty(items)) {
+ return null;
+ }
+ shelf.items = items;
+ if (serverData.isNumber((_a = shelfAttributes.displayStyle) === null || _a === void 0 ? void 0 : _a.layoutSize)) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: shelfAttributes.displayStyle.layoutSize,
+ };
+ }
+ else if (objectGraph.client.isPhone || objectGraph.client.isPad) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: items.length >= 6 ? 2 : 1,
+ };
+ }
+ return shelf;
+}
+/**
+ * Creates the suggested searches shelf for the focus page.
+ * @param objectGraph The App Store dependency graph
+ * @param data The content items for the shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns A shelf for the suggested links, or null if the shelf would be empty
+ */
+function createSuggestedSearchesShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ var _a, _b, _c;
+ const linkData = mediaRelationships.relationshipCollection(data, "contents");
+ if (isNothing(linkData)) {
+ return null;
+ }
+ const items = [];
+ const shelf = new models.Shelf("singleColumnList");
+ shelf.isHorizontal = false;
+ shelf.title = shelfAttributes.title;
+ shelf.presentationHints = { isWidthConstrained: true };
+ for (const [linkIndex, link] of linkData.entries()) {
+ const searchTerm = mediaAttributes.attributeAsString(link, "searchTerm");
+ if (isNothing(searchTerm) || searchTerm.length === 0) {
+ continue; // search term is required
+ }
+ const displayTerm = (_a = mediaAttributes.attributeAsString(link, "displayTerm")) !== null && _a !== void 0 ? _a : searchTerm;
+ const searchAction = createFocusPageSearchAction(objectGraph, displayTerm !== null && displayTerm !== void 0 ? displayTerm : "", searchTerm !== null && searchTerm !== void 0 ? searchTerm : "", undefined, pageContext.metricsLocationTracker, "suggested", undefined, /// MAINTAINER'S NOTE: In the future, we could use this to attribute the suggestion source to the search result fetch.
+ pageContext.metricsPageInformation, (_b = shelfAttributes.searchLandingItemDisplayStyle) !== null && _b !== void 0 ? _b : undefined);
+ if (isNothing(searchAction) || serverData.isNullOrEmpty(searchAction)) {
+ continue;
+ }
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchAction, {
+ targetType: "link",
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ kind: "link",
+ softwareType: null,
+ title: (_c = searchAction.title) !== null && _c !== void 0 ? _c : "",
+ id: `${linkIndex}`,
+ idType: "sequential",
+ });
+ items.push(searchAction);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ }
+ if (serverData.isNullOrEmpty(items)) {
+ return null;
+ }
+ shelf.items = items;
+ return shelf;
+}
+/**
+ * Creates the suggested links shelf
+ * @param objectGraph The App Store dependency graph
+ * @param data The content items for the shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns A shelf for the suggested links, or null if the shelf would be empty
+ */
+function createSuggestedLinksShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ var _a;
+ const linkData = mediaRelationships.relationshipCollection(data, "contents");
+ const links = [];
+ const shelf = new models.Shelf("searchLink");
+ shelf.isHorizontal = false;
+ shelf.title = shelfAttributes.title;
+ shelf.presentationHints = { isWidthConstrained: true };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfContext.metricsOptions);
+ for (const [linkIndex, link] of linkData.entries()) {
+ const metricsBase = {
+ targetType: "link",
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ };
+ const searchLink = trendingSearchLinkFromData(objectGraph, link, pageContext, shelfAttributes);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchLink, {
+ ...metricsBase,
+ kind: "link",
+ softwareType: null,
+ title: searchLink.clickAction.title,
+ id: `${linkIndex}`,
+ idType: "sequential",
+ });
+ if (serverData.isNullOrEmpty(searchLink)) {
+ continue;
+ }
+ links.push(searchLink);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ }
+ if (serverData.isNullOrEmpty(links)) {
+ return null;
+ }
+ shelf.items = links;
+ if (serverData.isNumber((_a = shelfAttributes.displayStyle) === null || _a === void 0 ? void 0 : _a.layoutSize)) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: shelfAttributes.displayStyle.layoutSize,
+ };
+ }
+ else if (objectGraph.client.isPhone) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: links.length >= 6 ? 2 : 1,
+ };
+ }
+ return shelf;
+}
+/**
+ * Creates a suggestion link action for the link data
+ * NOTE: This is legacy and the newer SLP protocol uses `trendingSearchLinkFromData`
+ * @param objectGraph The App Store dependency graph
+ * @param link The data for an individual suggestion link
+ * @param pageContext The page context for the search link
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns An action representing the suggestion link
+ */
+function trendingSearchLinkActionFromData(objectGraph, link, pageContext, shelfAttributes) {
+ var _a, _b;
+ /// We should always have search term, but not necessarily displayTerm.
+ const searchTerm = mediaAttributes.attributeAsString(link, "searchTerm");
+ if (isNothing(searchTerm) || searchTerm.length === 0) {
+ return null;
+ }
+ const displayTerm = (_a = mediaAttributes.attributeAsString(link, "displayTerm")) !== null && _a !== void 0 ? _a : searchTerm;
+ /// MAINTAINER'S NOTE: In the future, we could use this to attribute the suggestion source to the search result fetch.
+ const searchAction = new models.SearchAction(displayTerm, searchTerm, null, "suggested", undefined, undefined);
+ searchAction.artwork = createArtworkForSearchAction((_b = shelfAttributes.searchLandingItemDisplayStyle) !== null && _b !== void 0 ? _b : undefined, objectGraph);
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", pageContext.metricsLocationTracker);
+ return isSome(searchAction) ? new models.SearchAdAction(searchAction) : null;
+}
+/**
+ * Creates a search action
+ * @param objectGraph The App Store dependency graph
+ * @param searchTerm The term to search for
+ * @param entity The entity to scope the search to, e.g. apps, stories, arcade
+ * @param metricsLocationTracker The metrics location tracker
+ * @param origin The source the search was fired from
+ * @param isFocusPage Whether the origin is in context of the focus page
+ * @param displayStyle The style to display the action item
+ * @returns An action representing a search
+ */
+export function createFocusPageSearchAction(objectGraph, title, searchTerm, entity, metricsLocationTracker, origin, source, metricsPageInformation = undefined, displayStyle) {
+ if (serverData.isNullOrEmpty(searchTerm)) {
+ return null;
+ }
+ // For Search Focus Page, text uses primary color and icon uses secondary color (instead of tintColor).
+ const searchAction = new models.SearchAction(title, searchTerm, null, origin, entity !== null && entity !== void 0 ? entity : undefined, source, []);
+ searchAction.artwork = createArtworkForSearchAction(displayStyle, objectGraph);
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", metricsLocationTracker, metricsPageInformation);
+ return searchAction;
+}
+function createArtworkForSearchAction(displayStyle, objectGraph) {
+ var _a;
+ if ((displayStyle === null || displayStyle === void 0 ? void 0 : displayStyle.iconKind) === models.SearchLandingPageShelfItemIconKind.Symbol && ((_a = displayStyle === null || displayStyle === void 0 ? void 0 : displayStyle.iconKind) === null || _a === void 0 ? void 0 : _a.length)) {
+ return createArtworkForResource(objectGraph, `systemimage://${displayStyle.iconSymbol}`);
+ }
+ else if (objectGraph.client.isPhone || objectGraph.client.isVision) {
+ return createArtworkForResource(objectGraph, "systemimage://magnifyingglass");
+ }
+ return undefined;
+}
+/**
+ * Creates a suggestion link action for the link data
+ * @param objectGraph The App Store dependency graph
+ * @param link The data for an individual suggestion link
+ * @param pageContext The page context for the search link
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns An action representing the suggestion link
+ */
+function trendingSearchLinkFromData(objectGraph, link, pageContext, shelfAttributes) {
+ var _a, _b, _c;
+ const searchTerm = mediaAttributes.attributeAsString(link, "searchTerm");
+ if (isNothing(searchTerm) || searchTerm.length === 0) {
+ return null;
+ }
+ const displayTerm = (_a = mediaAttributes.attributeAsString(link, "displayTerm")) !== null && _a !== void 0 ? _a : searchTerm; /// we should always have search term, but not necessarily displayTerm
+ let searchAction;
+ if (objectGraph.client.isWeb) {
+ const intent = makeSearchResultsPageIntent({
+ ...getLocale(objectGraph),
+ origin: "suggested",
+ term: displayTerm,
+ platform: (_b = objectGraph.activeIntent) === null || _b === void 0 ? void 0 : _b.previewPlatform,
+ });
+ searchAction = unwrapOptional(actionFor(intent, objectGraph));
+ }
+ else {
+ searchAction = new models.SearchAction(displayTerm, displayTerm, null, "suggested");
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", pageContext.metricsLocationTracker);
+ }
+ const artwork = createArtworkForSearchAction((_c = shelfAttributes.searchLandingItemDisplayStyle) !== null && _c !== void 0 ? _c : undefined, objectGraph);
+ return new models.SearchLink(displayTerm, searchAction, artwork, null);
+}
+// #endregion
+// #region Discover Lockups Shelves
+/**
+ * Creates a shelf composed of lockups
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this lockups shelf
+ * @param pageContext The page context for the shelf
+ * @param shelfAttributes The attributes for the shelf
+ * @param shelfContext The context for the shelf
+ * @returns A lockups shelf if any lockups could be made
+ */
+function createLockupsShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ var _a, _b, _c, _d, _e;
+ const filterType = 80894 /* filtering.Filter.All */;
+ const items = [];
+ let hasAdLockup = false;
+ let shelfData = initialShelfContentsFromData(data);
+ // Set metadata
+ const shelf = new models.Shelf(shelfContext.shelfStyle);
+ shelf.isHorizontal = false;
+ shelf.title = shelfAttributes.title;
+ if (objectGraph.client.isVision) {
+ shelf.shouldFilterApps = !mediaAttributes.attributeAsBooleanOrFalse(data, "doNotFilter");
+ }
+ else {
+ shelf.shouldFilterApps = false;
+ }
+ shelf.filteringExcludedItems = shelfContext.filteringExcludedItems;
+ // Stitch First Position Ad (only if lockup array is nonempty)
+ if (serverData.isDefinedNonNullNonEmpty(shelfData)) {
+ const adLockup = lockupFromAdStitcher(objectGraph, pageContext, shelfContext);
+ if (adLockup && adLockup instanceof models.Lockup) {
+ hasAdLockup = true;
+ items.push(adLockup);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ shelfData = shelfData.filter((shelfItem) => shelfItem.id !== adLockup.adamId); // Filter dupe
+ }
+ }
+ // Determines whether all apps are displayed on SLP suggested apps shelf.
+ const hasDisplayCount = isSome(shelfAttributes.displayCount);
+ // If on device personalization is available, we need to personalize the data items. This reorders the shelf.
+ if (serverData.isDefinedNonNullNonEmpty(shelfData)) {
+ shelfData = impressionDemotion.personalizeDataItems(shelfData, (_a = pageContext.recoImpressionData) !== null && _a !== void 0 ? _a : {}, (_c = (_b = shelfContext.metricsOptions) === null || _b === void 0 ? void 0 : _b.recoMetricsData) !== null && _c !== void 0 ? _c : {});
+ }
+ // Build Lockups
+ for (const lockupData of shelfData) {
+ // If we encounter a type of app-events, this means they have been incorrectly programmed,
+ // and we should throw the shelf away.
+ if (lockupData.type === "app-events") {
+ return null;
+ }
+ if (serverData.isNull(lockupData.attributes)) {
+ continue;
+ }
+ // Filter out unwanted content
+ if (filtering.shouldFilter(objectGraph, lockupData, filterType)) {
+ continue;
+ }
+ const lockup = lockupFromData(objectGraph, lockupData, pageContext, shelfContext);
+ if (lockup) {
+ items.push(lockup);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ }
+ }
+ if (hasDisplayCount) {
+ // number of apps displayed on SLP suggested shelf
+ const displayCount = shelfAttributes.displayCount;
+ shelf.items = items.slice(0, displayCount);
+ }
+ else {
+ shelf.items = items;
+ }
+ if (hasDisplayCount) {
+ const seeAllShelf = new models.Shelf(shelfContext.shelfStyle);
+ if (hasAdLockup) {
+ // Ad is the first item in array so we are dropping it here.
+ seeAllShelf.items = items.splice(1, items.length - 1);
+ }
+ else {
+ seeAllShelf.items = items;
+ }
+ // Setup Page
+ const seeAllPage = new models.GenericPage([seeAllShelf]);
+ seeAllPage.title = shelf.title;
+ // Setup action
+ const seeAllAction = new models.FlowAction("page");
+ seeAllAction.pageUrl = shelfAttributes.seeAllLink;
+ seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ seeAllAction.pageData = seeAllPage;
+ // Connect action
+ shelf.seeAllAction = seeAllAction;
+ // Metrics
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, seeAllAction.pageUrl, {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ });
+ const seeAllPageInformation = metricsHelpersPage.pageInformationForRoom(objectGraph, data.id);
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, seeAllPage, seeAllPageInformation);
+ }
+ // Honor A/B treatment for using horizontal shelf for suggested apps, which may have an optional rowsPerColumn defined.
+ // NOTE: Client will choose an appropriate `rowsPerColumn` at display time, if not provided by server.
+ if (((_d = shelfAttributes.displayStyle) === null || _d === void 0 ? void 0 : _d.layout) === "horizontal" /* models.GenericSearchPageShelfDisplayStyleLayout.Horizontal */) {
+ shelf.isHorizontal = true;
+ shelf.rowsPerColumn = (_e = shelfAttributes.displayStyle) === null || _e === void 0 ? void 0 : _e.layoutSize;
+ }
+ return shelf;
+}
+/**
+ * Create a lockup for shelfContents to display within a grouping shelf
+ * @param objectGraph
+ * @param lockupData shelfContents to create lockup for.
+ * @param pageContext The page context for the lockup
+ * @param shelfContext The shelf context for the lockup
+ */
+function lockupFromData(objectGraph, lockupData, pageContext, shelfContext) {
+ if (serverData.isNullOrEmpty(lockupData)) {
+ return null;
+ }
+ if (shelfContext.shelfStyle !== "smallLockup") {
+ return null;
+ }
+ let offerStyle = null;
+ if (serverData.isDefinedNonNull(shelfContext.shelfBackground) &&
+ (shelfContext.shelfBackground.type === "color" || shelfContext.shelfBackground.type === "interactive")) {
+ offerStyle = "white";
+ }
+ // Create the lockup
+ const lockupOptions = {
+ metricsOptions: {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(lockupData),
+ isAdvert: adLockups.isAdvert(objectGraph, lockupData),
+ },
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, shelfContext.shelfStyle),
+ offerStyle: offerStyle,
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfContext.shelfStyle),
+ isContainedInPreorderExclusiveShelf: false,
+ shouldHideArcadeHeader: false,
+ };
+ const lockup = lockups.lockupFromData(objectGraph, lockupData, lockupOptions);
+ if (serverData.isNull(lockup) || !lockup.isValid()) {
+ return null;
+ }
+ return lockup;
+}
+/**
+ * Performs `lockupFromData`, but additional with Ad stitch related side-effects.
+ * @param objectGraph The AppStore dependency graph
+ * @param pageContext The page context for the ad lockup
+ * @param shelfContext The shelf context for the ad lockup
+ */
+function lockupFromAdStitcher(objectGraph, pageContext, shelfContext) {
+ const task = adStitch.consumeTask(pageContext.adStitcher, shelfContext.adPositionInfo);
+ if (serverData.isNull(task)) {
+ return null; // no task for position
+ }
+ // Try to create lockup
+ const lockupData = task.data;
+ try {
+ const lockup = lockupFromData(objectGraph, lockupData, pageContext, shelfContext);
+ if (serverData.isDefinedNonNull(lockup)) {
+ shelfContext.filteringExcludedItems = [lockupData.id];
+ }
+ else {
+ adIncidents.recordLockupFromDataFailed(objectGraph, pageContext.adIncidentRecorder, lockupData);
+ }
+ return lockup;
+ }
+ catch (error) {
+ adLogger(objectGraph, `Failed to create SLP ad lockup: ${error}`);
+ adIncidents.recordLockupFromDataFailed(objectGraph, pageContext.adIncidentRecorder, lockupData);
+ return null;
+ }
+}
+// #endregion
+// #region Brick Shelves
+function buildBrickShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ const items = [];
+ const shelf = new models.Shelf("brick");
+ shelf.isHorizontal = mediaAttributes.attributeAsString(data, "layoutDirection") === "Horizontal";
+ const shelfContents = mediaRelationships.relationshipCollection(data, "contents");
+ for (const itemData of shelfContents) {
+ const metricsOptions = {
+ ...shelfContext.metricsOptions,
+ targetType: "brickMedium",
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData),
+ };
+ // Using the location tracker from the context will cause positions to be unintentionally incremented
+ // by the lockup builder. Since we're only extracting icons we won't use the lockup metrics, so we can
+ // pass in a new, unused, location tracker instead.
+ const lockupMetricsOptions = {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ };
+ const brick = buildBrick(objectGraph, itemData, CollectionShelfDisplayStyle.BrickMedium, metricsOptions, lockupMetricsOptions);
+ brick.clickAction = createPrimaryActionForComponentFromData(objectGraph, itemData, shelfContext);
+ if (!brick.isValid()) {
+ continue;
+ }
+ items.push(brick);
+ metricsHelpersLocation.nextPosition(shelfContext.metricsOptions.locationTracker);
+ }
+ shelf.title = shelfAttributes.title;
+ shelf.items = items;
+ return shelf;
+}
+export function createPrimaryActionForComponentFromData(objectGraph, data, shelfContext) {
+ const clickOptions = createBrickClickOptionsFromData(objectGraph, data, shelfContext);
+ const primaryAction = lockups.actionFromData(objectGraph, data, clickOptions, null);
+ return primaryAction;
+}
+function createBrickClickOptionsFromData(objectGraph, data, shelfContext) {
+ const clickOptions = {
+ ...shelfContext.metricsOptions,
+ id: data.id,
+ targetType: "brickMedium",
+ };
+ return clickOptions;
+}
+// #endregion
+// #region Shelf Data Extraction
+/**
+ * Gets the initial raw shelf contents from the MAPI data object
+ * @param mediaApiData The raw MAPI data
+ * @returns A collection of data objects representing a shelf's contents
+ */
+function initialShelfContentsFromData(mediaApiData) {
+ const shelfContents = mediaRelationships.relationship(mediaApiData, "contents");
+ return shelfContents === null || shelfContents === void 0 ? void 0 : shelfContents.data;
+}
+// #endregion
+// #region Context Generators
+/**
+ * Performs either `createMediumAdLockupWithScreenshotsBackgroundShelf` or `createCondensedAdLockupWithIconBackgroundShelf
+ * depending on the format defined in adDisplayStyle, but with Ad stitch related side-effects.
+ * @param objectGraph The AppStore dependency graph
+ * @param pageContext The page context for the ad lockup
+ * @param builtShelves The index of the current shelf
+ * @param adMeta The SearchLandingPageAdMeta which includes adDisplayStyle
+ */
+function shelfFromAdStitcher(objectGraph, pageContext, builtShelves, adMeta) {
+ var _a;
+ const task = adStitch.consumeTask(pageContext.adStitcher, {
+ shelfIdentifier: searchLandingPageAdShelfIdentifier,
+ slot: builtShelves,
+ });
+ if (serverData.isNull(task)) {
+ return null; // no task for position
+ }
+ const adData = task.data;
+ try {
+ switch ((_a = adMeta === null || adMeta === void 0 ? void 0 : adMeta.adDisplayStyle) === null || _a === void 0 ? void 0 : _a.format) {
+ case "medium":
+ return createMediumAdLockupWithScreenshotsBackgroundShelf(objectGraph, adData, pageContext);
+ case "condensed":
+ return createCondensedAdLockupWithIconBackgroundShelf(objectGraph, adData, pageContext);
+ default:
+ adIncidents.recordLockupFromDataFailed(objectGraph, pageContext.adIncidentRecorder, adData);
+ return null;
+ }
+ }
+ catch (error) {
+ adLogger(objectGraph, `Failed to create SLP ad shelf: ${error}`);
+ adIncidents.recordLockupFromDataFailed(objectGraph, pageContext.adIncidentRecorder, adData);
+ return null;
+ }
+}
+/**
+ * Generates a shelf context for a search landing page shelf
+ * @param objectGraph The App Store Object Graph
+ * @param data The shelf's data object
+ * @param shelfAttributes The shelf's attribtues
+ * @param pageContext The context for the page containing the shelf
+ * @param shelfContentKind The type of content the shelf contains
+ * @returns A shelf context for a search landing page shelf
+ */
+function searchLandingPageShelfContext(objectGraph, data, shelfAttributes, pageContext, shelfContentKind = null) {
+ const baseShelfContext = searchShelves.baseShelfContext(objectGraph, data, shelfAttributes, pageContext);
+ switch (shelfContentKind) {
+ case models.SearchLandingPageContentKind.Apps:
+ return {
+ ...baseShelfContext,
+ shelfStyle: "smallLockup",
+ adPositionInfo: searchLandingPagePositionInfo,
+ };
+ default:
+ return baseShelfContext;
+ }
+}
+// #endregion
+//# sourceMappingURL=search-landing-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/category-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/category-metadata-ribbon-item.js
new file mode 100644
index 0000000..70c47c8
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/category-metadata-ribbon-item.js
@@ -0,0 +1,38 @@
+import { isSome } from "@jet/environment";
+import { MetadataRibbonItem } from "../../../api/models";
+import { isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { categoryArtworkData } from "../../categories";
+import * as content from "../../content/content";
+import { categoryFromData } from "../../lockups/lockups";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ const artworkData = categoryArtworkData(objectGraph, data, true);
+ const hasArtwork = isSome(artworkData);
+ const labelText = categoryFromData(objectGraph, data);
+ if (isNullOrEmpty(labelText)) {
+ return null;
+ }
+ if (labelText != null) {
+ if (dedupeSet.has(labelText)) {
+ return null;
+ }
+ else {
+ dedupeSet.add(labelText);
+ }
+ }
+ const viewType = hasArtwork ? "imageWithLabel" : "textLabel";
+ const categoryItem = new MetadataRibbonItem(viewType);
+ categoryItem.moduleType = "genreDisplayName";
+ categoryItem.labelText = labelText;
+ if (hasArtwork) {
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 20 /* content.ArtworkUseCase.CategoryIcon */,
+ });
+ artwork.crop = "sr";
+ categoryItem.artwork = artwork;
+ }
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "genreDisplayName", categoryItem.labelText, "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, categoryItem, impressionOptions);
+ return [categoryItem];
+}
+//# sourceMappingURL=category-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/chart-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/chart-metadata-ribbon-item.js
new file mode 100644
index 0000000..26f815c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/chart-metadata-ribbon-item.js
@@ -0,0 +1,75 @@
+import { isSome, isNothing } from "@jet/environment/types/optional";
+import { MetadataRibbonItem } from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { contentAttributeAsDictionary } from "../../content/attributes";
+import { badgeChartKeyForClientIdentifier } from "../../content/content";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ var _a, _b;
+ const chartData = chartFromData(objectGraph, data);
+ if (serverData.isNullOrEmpty(chartData)) {
+ return null;
+ }
+ const position = serverData.asNumber(chartData, "position");
+ /// Per product, if the app isn't charting in the top 50, we don't want to use the chart module
+ if (isNothing(position) || position > 50) {
+ return null;
+ }
+ const genreName = (_a = serverData.asString(chartData, "genreShortName")) !== null && _a !== void 0 ? _a : serverData.asString(chartData, "genreName");
+ if (genreName != null) {
+ if (dedupeSet.has(genreName)) {
+ return null;
+ }
+ else {
+ dedupeSet.add(genreName);
+ }
+ }
+ let chartItem;
+ if (objectGraph.bag.isLLMSearchTagsEnabled) {
+ chartItem = new MetadataRibbonItem("highlightedText");
+ }
+ else {
+ chartItem = new MetadataRibbonItem("borderedTextLabel");
+ }
+ chartItem.moduleType = "chartPositions";
+ // Only use an ad override locale if this is an ad.
+ const adsOverrideLanguage = isSome((_b = lockup.searchAdOpportunity) === null || _b === void 0 ? void 0 : _b.searchAd) || isSome(lockup.searchAd)
+ ? objectGraph.bag.adsOverrideLanguage
+ : null;
+ const useAdsLocale = isSome(adsOverrideLanguage);
+ const loc = useAdsLocale ? objectGraph.adsLoc : objectGraph.loc;
+ // MAINTAINER'S NOTE: This was previously guarded by the iOS only `search_tags` feature flag that has been enabled by default on iOS only.
+ if (objectGraph.client.isiOS) {
+ const chartPositionText = loc
+ .string("MetadataRibbon.ChartPosition")
+ .replace("@@chartPosition@@", objectGraph.loc.formattedCountForPreferredLocale(objectGraph, position, adsOverrideLanguage));
+ if (objectGraph.bag.isLLMSearchTagsEnabled) {
+ chartItem.highlightedText = chartPositionText;
+ chartItem.labelText = loc
+ .string("MetadataRibbon.ChartPositionAndCategory.Tags")
+ .replace("@@chartPosition@@", objectGraph.loc.formattedCountForPreferredLocale(objectGraph, position, adsOverrideLanguage))
+ .replace("@@category@@", genreName);
+ }
+ else {
+ chartItem.labelText = genreName;
+ chartItem.borderedText = chartPositionText;
+ }
+ }
+ chartItem.secondaryViewPlacement = "leading";
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "chartPosition", chartItem.labelText, "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, chartItem, impressionOptions);
+ return [chartItem];
+}
+export function chartFromData(objectGraph, data) {
+ const chartPositionsByStore = contentAttributeAsDictionary(objectGraph, data, "chartPositions");
+ if (serverData.isNullOrEmpty(chartPositionsByStore)) {
+ return null;
+ }
+ const storeChartKey = badgeChartKeyForClientIdentifier(objectGraph, objectGraph.host.clientIdentifier);
+ if (serverData.isNullOrEmpty(storeChartKey)) {
+ return null;
+ }
+ const chartData = serverData.asDictionary(chartPositionsByStore, storeChartKey);
+ return chartData;
+}
+//# sourceMappingURL=chart-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/developer-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/developer-metadata-ribbon-item.js
new file mode 100644
index 0000000..3a451a2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/developer-metadata-ribbon-item.js
@@ -0,0 +1,37 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../../api/models";
+import { attributeAsString } from "../../../foundation/media/attributes";
+import * as contentArtwork from "../../content/artwork/artwork";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ var _a;
+ let developerName = lockup.developerName;
+ if (isNothing(developerName)) {
+ developerName = (_a = attributeAsString(data, "artistName")) !== null && _a !== void 0 ? _a : attributeAsString(data, "developerName");
+ }
+ if (developerName != null) {
+ if (dedupeSet.has(developerName)) {
+ return null;
+ }
+ else {
+ dedupeSet.add(developerName);
+ }
+ }
+ if (isSome(developerName) && developerName.length > 0) {
+ const developerItem = new models.MetadataRibbonItem("imageWithLabel");
+ developerItem.moduleType = "developerInfo";
+ developerItem.labelText = developerName;
+ developerItem.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://person.crop.square");
+ const characterCountThreshold = 6;
+ developerItem.maxCharacterCount = 16;
+ developerItem.truncationLegibilityCharacterCountThreshold = Math.min(characterCountThreshold, developerName.length);
+ developerItem.allowsTruncation = developerName.length >= characterCountThreshold;
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "developerInfo", developerItem.labelText, "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, developerItem, impressionOptions);
+ return [developerItem];
+ }
+ else {
+ return null;
+ }
+}
+//# sourceMappingURL=developer-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/divider-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/divider-metadata-ribbon-item.js
new file mode 100644
index 0000000..91f5a5f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/divider-metadata-ribbon-item.js
@@ -0,0 +1,8 @@
+import { MetadataRibbonItem } from "../../../api/models";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ const dividerItem = new MetadataRibbonItem("divider");
+ dividerItem.moduleType = "divider";
+ dividerItem.labelText = "|";
+ return [dividerItem];
+}
+//# sourceMappingURL=divider-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/editors-choice-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/editors-choice-metadata-ribbon-item.js
new file mode 100644
index 0000000..764624e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/editors-choice-metadata-ribbon-item.js
@@ -0,0 +1,21 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ var _a;
+ if (lockup.isEditorsChoice) {
+ const editorsChoiceItem = new models.MetadataRibbonItem("editorsChoice");
+ editorsChoiceItem.moduleType = "editorialBadgeInfo";
+ // Only use an ad override locale if this is an ad.
+ editorsChoiceItem.useAdsLocale =
+ (isSome((_a = lockup.searchAdOpportunity) === null || _a === void 0 ? void 0 : _a.searchAd) || isSome(lockup.searchAd)) &&
+ isSome(objectGraph.bag.adsOverrideLanguage);
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "editorialBadgeInfo", "Editors Choice", "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, editorsChoiceItem, impressionOptions);
+ return [editorsChoiceItem];
+ }
+ else {
+ return null;
+ }
+}
+//# sourceMappingURL=editors-choice-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/game-controller-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/game-controller-metadata-ribbon-item.js
new file mode 100644
index 0000000..40d18c0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/game-controller-metadata-ribbon-item.js
@@ -0,0 +1,36 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as contentArtwork from "../../content/artwork/artwork";
+import * as contentAttributes from "../../content/attributes";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ var _a;
+ let isGameControllerSupported = false;
+ switch (contentAttributes.contentAttributeAsString(objectGraph, data, "remoteControllerRequirement")) {
+ case "CONTROLLER_REQUIRED":
+ case "CONTROLLER_OPTIONAL":
+ isGameControllerSupported = true;
+ break;
+ default:
+ break;
+ }
+ if (contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "supportsGameController")) {
+ isGameControllerSupported = true;
+ }
+ if (!isGameControllerSupported) {
+ return null;
+ }
+ const gameControllerItem = new models.MetadataRibbonItem("imageWithLabel");
+ gameControllerItem.moduleType = "supportsGameController";
+ // Only use an ad override locale if this is an ad.
+ const useAdsLocale = (isSome(lockup.searchAd) || isSome((_a = lockup.searchAdOpportunity) === null || _a === void 0 ? void 0 : _a.searchAd)) &&
+ isSome(objectGraph.bag.adsOverrideLanguage);
+ gameControllerItem.labelText = useAdsLocale
+ ? objectGraph.adsLoc.string("BADGE_MFI_SUPPORTED")
+ : objectGraph.loc.string("BADGE_MFI_SUPPORTED");
+ gameControllerItem.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://gamecontroller.fill");
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "supportsGameController", "Supports Game Controller", "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, gameControllerItem, impressionOptions);
+ return [gameControllerItem];
+}
+//# sourceMappingURL=game-controller-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon-item-factory.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon-item-factory.js
new file mode 100644
index 0000000..5c6f3a6
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon-item-factory.js
@@ -0,0 +1,24 @@
+import * as categoryMetadataRibbonItem from "./category-metadata-ribbon-item";
+import * as chartMetadataRibbonItem from "./chart-metadata-ribbon-item";
+import * as developerMetadataRibbonItem from "./developer-metadata-ribbon-item";
+import * as dividerMetadataRibbonItem from "./divider-metadata-ribbon-item";
+import * as editorsChoiceMetadataRibbonItem from "./editors-choice-metadata-ribbon-item";
+import * as gameControllerMetadataRibbonItem from "./game-controller-metadata-ribbon-item";
+import * as secondaryShortCategoriesMetadataRibbonItem from "./secondary-short-categories-metadata-ribbon-item";
+import * as shortCategoryMetadataRibbonItem from "./short-category-metadata-ribbon-item";
+import * as starRatingMetadataRibbonItem from "./star-rating-metadata-ribbon-item";
+import * as tagMetadataRibbonItem from "./tag-metadata-ribbon-item";
+export const standardList = {
+ // every key in Key must be present
+ chartPositions: chartMetadataRibbonItem.createMetadataRibbonItems,
+ genreDisplayName: categoryMetadataRibbonItem.createMetadataRibbonItems,
+ genreShortDisplayName: shortCategoryMetadataRibbonItem.createMetadataRibbonItems,
+ secondaryGenreShortDisplayNames: secondaryShortCategoriesMetadataRibbonItem.createMetadataRibbonItems,
+ developerInfo: developerMetadataRibbonItem.createMetadataRibbonItems,
+ editorialBadgeInfo: editorsChoiceMetadataRibbonItem.createMetadataRibbonItems,
+ userRating: starRatingMetadataRibbonItem.createMetadataRibbonItems,
+ supportsGameController: gameControllerMetadataRibbonItem.createMetadataRibbonItems,
+ tag: tagMetadataRibbonItem.createMetadataRibbonItems,
+ divider: dividerMetadataRibbonItem.createMetadataRibbonItems,
+};
+//# sourceMappingURL=metadata-ribbon-item-factory.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon.js
new file mode 100644
index 0000000..1b487cb
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon.js
@@ -0,0 +1,27 @@
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { standardList } from "./metadata-ribbon-item-factory";
+export function createMetadataRibbonItemsForLockup(objectGraph, data, lockup, itemTypes, options, metadataRibbonItemFactory = standardList) {
+ if (serverData.isNullOrEmpty(itemTypes)) {
+ return [];
+ }
+ const metadataRibbonItems = [];
+ const dedupeSet = new Set();
+ for (const itemSlot of itemTypes) {
+ if (serverData.isNullOrEmpty(itemSlot)) {
+ continue;
+ }
+ for (const itemType of itemSlot) {
+ const metadataRibbonFactory = metadataRibbonItemFactory[itemType];
+ if (serverData.isNull(metadataRibbonFactory)) {
+ continue;
+ }
+ const results = metadataRibbonFactory(objectGraph, data, lockup, dedupeSet, options.metricsOptions);
+ if (serverData.isDefinedNonNull(results)) {
+ metadataRibbonItems.push(...results);
+ break;
+ }
+ }
+ }
+ return metadataRibbonItems;
+}
+//# sourceMappingURL=metadata-ribbon.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/ranked-secondary-category-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/ranked-secondary-category-metadata-ribbon-item.js
new file mode 100644
index 0000000..217876d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/ranked-secondary-category-metadata-ribbon-item.js
@@ -0,0 +1,22 @@
+import { MetadataRibbonItem } from "../../../api/models";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import { isNothing, isSome } from "@jet/environment";
+/**
+ * Creates a metadata ribbon item for ranked secondary category metadata ribbon type.
+ * This particular function takes in the type string since we grab it from the data in the search-tags-ribbon.
+ */
+export function createMetadataRibbonItemsForRankedSecondaryCategory(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ if (isNothing(data) || data.length === 0 || dedupeSet.has(data)) {
+ return null;
+ }
+ const tagItem = new MetadataRibbonItem("textLabel");
+ tagItem.moduleType = "rankedSecondaryGenre";
+ if (isSome(data)) {
+ tagItem.labelText = data;
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "rankedSecondaryGenre", tagItem.labelText, "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, tagItem, impressionOptions);
+ }
+ dedupeSet.add(data);
+ return [tagItem];
+}
+//# sourceMappingURL=ranked-secondary-category-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/search-tags-ribbon.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/search-tags-ribbon.js
new file mode 100644
index 0000000..3901446
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/search-tags-ribbon.js
@@ -0,0 +1,72 @@
+import { isSome } from "@jet/environment";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { standardList } from "./metadata-ribbon-item-factory";
+import * as rankedSecondaryCategoryMetadataRibbonItem from "./ranked-secondary-category-metadata-ribbon-item";
+export function createSearchTagsRibbonItemsForLockup(objectGraph, data, lockup, itemSlots, options, metadataRibbonItemFactory = standardList) {
+ if (serverData.isNullOrEmpty(itemSlots)) {
+ return [];
+ }
+ const tagData = serverData.asArrayOrEmpty(data.meta, "associations.tags.data");
+ const metadataRibbonItems = [];
+ // We need to keep track of how many tags we have so we can assign the tag properly
+ let tagIndex = 0;
+ let rankedSecondaryGenreIndex = 0;
+ // We are going to pass in a set of strings for items we have already added to the ribbon so we never duplicate items
+ const dedupeSet = new Set();
+ for (const itemSlot of itemSlots) {
+ const itemTypes = Array.isArray(itemSlot) ? itemSlot : [itemSlot];
+ if (serverData.isNullOrEmpty(itemTypes)) {
+ continue;
+ }
+ for (const itemType of itemTypes) {
+ // If we find a tag, we pass in the tag data specifically
+ // If we find a rankedSecondaryGenre, we want to call the factory function specifically.
+ const isTag = itemType === "tag";
+ const isRankedSecondaryGenre = itemType === "rankedSecondaryGenre";
+ let results;
+ let metadataItemData = data;
+ let metadataItemString = "";
+ if (isRankedSecondaryGenre) {
+ const searchExperimentDataForLockup = serverData.asDictionary(data, "meta");
+ if (isSome(searchExperimentDataForLockup === null || searchExperimentDataForLockup === void 0 ? void 0 : searchExperimentDataForLockup.rankedSecondaryGenreShortDisplayNames)) {
+ metadataItemString =
+ searchExperimentDataForLockup === null || searchExperimentDataForLockup === void 0 ? void 0 : searchExperimentDataForLockup.rankedSecondaryGenreShortDisplayNames[rankedSecondaryGenreIndex];
+ }
+ if (isSome(metadataItemString)) {
+ results =
+ rankedSecondaryCategoryMetadataRibbonItem.createMetadataRibbonItemsForRankedSecondaryCategory(objectGraph, metadataItemString, lockup, dedupeSet, options.metricsOptions);
+ rankedSecondaryGenreIndex = rankedSecondaryGenreIndex + 1;
+ }
+ else {
+ results = [];
+ }
+ }
+ else {
+ const metadataRibbonFactory = metadataRibbonItemFactory[itemType];
+ if (serverData.isNull(metadataRibbonFactory)) {
+ continue;
+ }
+ if (isTag) {
+ metadataItemData = tagData[tagIndex];
+ }
+ else {
+ metadataItemData = data;
+ }
+ results = metadataRibbonFactory(objectGraph, metadataItemData, lockup, dedupeSet, options.metricsOptions);
+ tagIndex = isTag ? tagIndex + 1 : tagIndex;
+ }
+ if (serverData.isDefinedNonNull(results)) {
+ metadataRibbonItems.push(...results);
+ for (const result of results) {
+ if (isSome(result.impressionMetrics)) {
+ metricsHelpersLocation.nextPosition(options.metricsOptions.locationTracker);
+ }
+ }
+ break;
+ }
+ }
+ }
+ return metadataRibbonItems;
+}
+//# sourceMappingURL=search-tags-ribbon.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/secondary-short-categories-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/secondary-short-categories-metadata-ribbon-item.js
new file mode 100644
index 0000000..c5469c7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/secondary-short-categories-metadata-ribbon-item.js
@@ -0,0 +1,27 @@
+import { MetadataRibbonItem } from "../../../api/models";
+import { isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { attributeAsArrayOrEmpty } from "../../../foundation/media/attributes";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ const secondaryGenres = attributeAsArrayOrEmpty(data, "secondaryGenreShortDisplayNames");
+ if (isNullOrEmpty(secondaryGenres)) {
+ return null;
+ }
+ const secondaryCategoryItems = secondaryGenres.map((secondaryGenre) => {
+ const categoryItem = new MetadataRibbonItem("textLabel");
+ // Workaround for changing the moduleType to secondaryGenreShortDisplayNames from secondaryGenreShortDisplayName
+ // otherwise native doesnt layout the secondary genres correctly
+ // will be fixed with rdar://127458403 (Allow unknown metadataribbon items)
+ categoryItem.moduleType = "genreShortDisplayName";
+ categoryItem.labelText = secondaryGenre;
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "genreDisplayName", categoryItem.labelText, "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, categoryItem, impressionOptions);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ return categoryItem;
+ });
+ return secondaryCategoryItems.filter((category) => {
+ return category.labelText != null && !dedupeSet.has(category.labelText);
+ });
+}
+//# sourceMappingURL=secondary-short-categories-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/short-category-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/short-category-metadata-ribbon-item.js
new file mode 100644
index 0000000..16a148a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/short-category-metadata-ribbon-item.js
@@ -0,0 +1,36 @@
+import { isNothing, isSome } from "@jet/environment";
+import { MetadataRibbonItem } from "../../../api/models";
+import { attributeAsString } from "../../../foundation/media/attributes";
+import { categoryArtworkData } from "../../categories";
+import { artworkFromApiArtwork } from "../../content/content";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ const artworkData = categoryArtworkData(objectGraph, data, true);
+ const hasArtwork = isSome(artworkData);
+ const shortGenre = attributeAsString(data, "genreShortDisplayName");
+ if (shortGenre != null) {
+ if (dedupeSet.has(shortGenre)) {
+ return null;
+ }
+ else {
+ dedupeSet.add(shortGenre);
+ }
+ }
+ if (isNothing(shortGenre) || shortGenre.length === 0) {
+ return null;
+ }
+ const viewType = hasArtwork ? "imageWithLabel" : "textLabel";
+ const shortCategoryItem = new MetadataRibbonItem(viewType);
+ shortCategoryItem.moduleType = "genreShortDisplayName";
+ shortCategoryItem.labelText = shortGenre;
+ if (hasArtwork) {
+ shortCategoryItem.artwork = artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 20 /* ArtworkUseCase.CategoryIcon */,
+ cropCode: "sr",
+ });
+ }
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "genreDisplayName", shortCategoryItem.labelText, "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, shortCategoryItem, impressionOptions);
+ return [shortCategoryItem];
+}
+//# sourceMappingURL=short-category-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/star-rating-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/star-rating-metadata-ribbon-item.js
new file mode 100644
index 0000000..81587f8
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/star-rating-metadata-ribbon-item.js
@@ -0,0 +1,20 @@
+import { MetadataRibbonItem } from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { contentAttributeAsBooleanOrFalse } from "../../content/attributes";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ const isPreorder = contentAttributeAsBooleanOrFalse(objectGraph, data, "isPreorder");
+ if (serverData.isDefinedNonNull(lockup.ratingCount) && serverData.isDefinedNonNull(lockup.rating) && !isPreorder) {
+ const starRatingItem = new MetadataRibbonItem("starRating");
+ starRatingItem.moduleType = "userRating";
+ starRatingItem.starRating = lockup.rating;
+ starRatingItem.labelText = lockup.ratingCount;
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "userRating", "User Rating", "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, starRatingItem, impressionOptions);
+ return [starRatingItem];
+ }
+ else {
+ return null;
+ }
+}
+//# sourceMappingURL=star-rating-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/tag-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/tag-metadata-ribbon-item.js
new file mode 100644
index 0000000..37877c3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/tag-metadata-ribbon-item.js
@@ -0,0 +1,18 @@
+import { isNothing } from "@jet/environment";
+import { MetadataRibbonItem } from "../../../api/models";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ const tagData = data;
+ const tagItem = new MetadataRibbonItem("textLabel");
+ tagItem.moduleType = "tag";
+ tagItem.labelText = mediaAttributes.attributeAsString(tagData, "name");
+ if (isNothing(tagItem.labelText) || tagItem.labelText.length === 0 || dedupeSet.has(tagItem.labelText)) {
+ return null;
+ }
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, tagData.id, tagItem.labelText, "tag_id");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, tagItem, impressionOptions);
+ dedupeSet.add(tagItem.labelText);
+ return [tagItem];
+}
+//# sourceMappingURL=tag-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads-odml.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads-odml.js
new file mode 100644
index 0000000..aedcef1
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads-odml.js
@@ -0,0 +1,87 @@
+/**
+ * Implements the ODML Treatment (On-device machine learning) for Sponsored search.
+ */
+"use strict";
+import { adLogger } from "../../common/search/search-ads";
+import { isNull, isNullOrEmpty } from "../../foundation/json-parsing/server-data";
+import { shallowCopyOf } from "../../foundation/util/objects";
+import { decorateAdInstanceIdOnData } from "../ads/ad-common";
+import { productVariantDataForData, productVariantIDForVariantData } from "../product-page/product-page-variants";
+// region exports
+/**
+ * Merge the contents of the raw response with the data in `advertData` from `SearchAds.framework`
+ *
+ * @param rawAdverts The adverts returned in the network response.
+ * @param nativeAdvertData The advert data that was returned from native.
+ */
+export function applyNativeAdvertData(objectGraph, rawAdverts, nativeAdvertData) {
+ if (isNull(nativeAdvertData)) {
+ return rawAdverts; // even if `nativeAdvertData` may contain error, we need to perform apply to merge instance ids.
+ }
+ const updated = [];
+ const rawAdvertsMap = rawAdverts.reduce((acc, data) => ({ ...acc, [data.id]: data }), {});
+ for (const nativeAdData of nativeAdvertData.adverts) {
+ const rawAd = rawAdvertsMap[nativeAdData.adamId];
+ if (isNullOrEmpty(rawAd)) {
+ adLogger(objectGraph, `[${nativeAdData.adamId}] skipped - Data was not part of original response`);
+ continue;
+ }
+ if (isNullOrEmpty(rawAd.attributes)) {
+ adLogger(objectGraph, `[${rawAd.id}] skipped - Data is missing attributes`);
+ continue;
+ }
+ const newAd = createCopyWithNativeData(objectGraph, rawAd, nativeAdData);
+ updated.push(newAd);
+ }
+ if (!preprocessor.PRODUCTION_BUILD) {
+ const rawOrder = rawAdverts.map((ad) => ad.id).join(" ");
+ const updatedOrder = updated.map((ad) => ad.id).join(" ");
+ adLogger(objectGraph, `applyNativeAdvertData: [${rawOrder}] => [${updatedOrder}]`);
+ }
+ return updated;
+}
+/**
+ * Returns whether or not ODML treatment was successful
+ */
+export function wasODMLSuccessful(objectGraph, nativeAdvertData) {
+ return nativeAdvertData && nativeAdvertData.odmlSuccess;
+}
+// endregion
+// region internals
+/**
+ * Create a copy of `ad` with the `adData` replacing the "iad" attribute.
+ * @param ad The raw ad to copy
+ * @param adData The ad blob to overrwite wth.
+ */
+function createCopyWithNativeData(objectGraph, ad, nativeAdData) {
+ const copy = shallowCopyOf(ad);
+ const attributes = shallowCopyOf(ad.attributes);
+ attributes["iads"] = nativeAdData.adData;
+ copy.attributes = attributes;
+ overrideCustomProductPageIdIfRequired(objectGraph, copy, nativeAdData);
+ decorateAdInstanceIdOnData(objectGraph, copy, nativeAdData.instanceId);
+ return copy;
+}
+/**
+ * Modifies an `ad` by overriding the `ppid` value if native ODML has dictated a new selection.
+ * Note: This may intentionally replace the `ppid` value with `null` if CPP is disabled for Search Ads.
+ * If we pass native all `null` values for ODML processing (because the feature is disabled in the bag),
+ * we expect to receive `null` back and insert `null` into the `ad` here.
+ * @param ad The ad to modify.
+ * @param adData The ad blob to overrwite with.
+ */
+function overrideCustomProductPageIdIfRequired(objectGraph, ad, nativeAdData) {
+ var _a;
+ const productVariantData = productVariantDataForData(objectGraph, ad);
+ const productVariantId = productVariantIDForVariantData(productVariantData);
+ // If there is a "selected" cppId, and it's different to the `serverCppId`, native has made a new selection.
+ // Confirm we have a cppData of meta to modify
+ if (nativeAdData.selectedCppId === productVariantId || isNullOrEmpty((_a = ad === null || ad === void 0 ? void 0 : ad.meta) === null || _a === void 0 ? void 0 : _a.cppData)) {
+ return;
+ }
+ // Modify the meta data with the new selection.
+ const meta = shallowCopyOf(ad.meta);
+ meta.cppData["ppid"] = nativeAdData.selectedCppId;
+ ad.meta = meta;
+}
+//# sourceMappingURL=search-ads-odml.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js
new file mode 100644
index 0000000..e8377d4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js
@@ -0,0 +1,1047 @@
+//
+// search-ads.ts
+// AppStoreKit
+//
+// Created by Joel Parsons on 24/October/2019
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import { isSome } from "@jet/environment";
+import * as models from "../../api/models";
+import { ads } from "../../api/typings/constants";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import { shallowCopyOf } from "../../foundation/util/objects";
+import { isAdLocalizationValid } from "../ads/ad-common";
+import * as client from "../../foundation/wrappers/client";
+import * as contentAttributes from "../content/attributes";
+import * as content from "../content/content";
+import * as filtering from "../filtering";
+import * as lockups from "../lockups/lockups";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import { customCreativeArtworkFromData, customCreativeVideoFromData } from "./custom-creative";
+import { platformAttributeAsDictionary } from "../../foundation/media/platform-attributes";
+import { searchResultWillUseAppEventDisplay } from "./content/search-results";
+/**
+ * Data passed from native for requesting Sponsored Search
+ */
+export class SponsoredSearchRequestData {
+ constructor(data, appStoreClientRequestId) {
+ if (!data) {
+ return;
+ }
+ this.appStoreClientRequestId = appStoreClientRequestId;
+ this.iAdId = data["iAdId"];
+ this.sponsoredSearchRequestData = data["dataBlob"];
+ this.routingInfo = data["iAdRoutingInfo"];
+ this.canary = data["canary"];
+ }
+ validAdRequest() {
+ const hasRequestData = this.sponsoredSearchRequestData && this.sponsoredSearchRequestData.length > 0;
+ const hasRoutingInfo = this.routingInfo && this.routingInfo.length > 0;
+ return hasRequestData && hasRoutingInfo;
+ }
+}
+const searchVideoConfiguration = {
+ canPlayFullScreen: false,
+ playbackControls: {},
+};
+export function adsResultFromSearchResults(objectGraph, advertDatum, resultsDatum, requestMetadata, metricsOptions, installedStates, appStates, searchExperimentsData, personalizationDataContainer) {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
+ const advertsSearchResult = new models.AdvertsSearchResult();
+ const isNetworkConstrained = (_a = requestMetadata.requestDescriptor.isNetworkConstrained) !== null && _a !== void 0 ? _a : false;
+ const advertMetricsOptions = {
+ id: "ad_container",
+ kind: "iosSoftware",
+ softwareType: null,
+ targetType: null,
+ title: "ad_container",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ idType: "sequential",
+ };
+ /**
+ * # Why do we push ad_container?
+ * We push an identifier for element that doesn't exist (`ad_container`) to prevent impression indices of organic search results
+ * being affected by having multiple ads built for ad-rotation.
+ *
+ * It is expected that:
+ * - `ad_container` will not impresss, since it doesn't exist
+ * - `parentImpressionId` will not be set expected.
+ */
+ metricsHelpersLocation.pushContentLocation(objectGraph, advertMetricsOptions, "ad_container");
+ if (serverData.isNullOrEmpty(advertDatum)) {
+ return {
+ result: advertsSearchResult,
+ };
+ }
+ const firstSearchResult = resultsDatum[0];
+ let firstAdComputedStyle;
+ const adIdsString = advertDatum
+ .filter(serverData.isDefinedNonNull)
+ .map((ad) => `[${ad.id}]`)
+ .join(", ");
+ const adString = `Adverts received from ad server: ${adIdsString}`;
+ adLogger(objectGraph, adString);
+ let isFirstAd = true;
+ for (const ad of advertDatum) {
+ if (serverData.isNull(ad)) {
+ continue;
+ }
+ if (filtering.shouldFilter(objectGraph, ad)) {
+ adLogger(objectGraph, `[${ad.id}] filtered by shouldFilter() - app probably not supported on current os or device`);
+ continue;
+ }
+ const isDupe = adIsDupe(ad.id, firstSearchResult === null || firstSearchResult === void 0 ? void 0 : firstSearchResult.id, installedStates);
+ // Extract the iAd data dictionary based on the first organic search result.
+ const adDataType = iAdDataTypeForAdvert(firstSearchResult, isDupe);
+ ad.attributes["iad"] = iadAttributesForType(ad, adDataType);
+ if (serverData.isNullOrEmpty(ad.attributes["iad"])) {
+ adLogger(objectGraph, `[${ad.id}] filtered because no appropriate iAd dictionary was found. (Probably a server issue if hitting this)`);
+ continue;
+ }
+ const adLockupOptions = {
+ metricsOptions: {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "card",
+ isAdvert: true,
+ },
+ hideZeroRatings: true,
+ artworkUseCase: 8 /* content.ArtworkUseCase.SearchIcon */,
+ isNetworkConstrained: isNetworkConstrained,
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "mixedMediaLockup"),
+ };
+ const iAdData = contentAttributes.contentAttributeAsDictionary(objectGraph, ad, "iad");
+ const iAdAllowsMedia = serverData.asBooleanOrFalse(iAdData, "format.images");
+ let creativeHasArtworkToDisplay = false;
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ const customCreativeData = platformAttributeAsDictionary(ad, contentAttributes.bestAttributePlatformFromData(objectGraph, ad), "creativeAttributes");
+ const customCreativeArtwork = customCreativeArtworkFromData(objectGraph, ad, customCreativeData);
+ const customCreativeVideo = customCreativeVideoFromData(objectGraph, ad, customCreativeData, searchVideoConfiguration);
+ creativeHasArtworkToDisplay = isSome(customCreativeArtwork) || isSome(customCreativeVideo);
+ }
+ const noPreviousAdStyle = serverData.isNullOrEmpty(firstAdComputedStyle);
+ const temporaryAdLockup = lockups.mixedMediaAdLockupFromData(objectGraph, ad, adLockupOptions, searchVideoConfiguration, searchExperimentsData);
+ const platformLockupMedia = platformMediaForLockup(temporaryAdLockup);
+ // Adverts should only rotate between the same display style. The first advert dictates the display style
+ // for the rest of the adverts. Once we have an ad display style locked in we try to create the next ads in
+ // a style compatible with the first. If it can't satisfy any compatible styles, it gets thrown away.
+ const screenshotsDisplayStyle = creativeHasArtworkToDisplay
+ ? "four-screenshots"
+ : (_b = searchExperimentsData === null || searchExperimentsData === void 0 ? void 0 : searchExperimentsData.displayStyle) === null || _b === void 0 ? void 0 : _b.screenshots;
+ const computedAdDisplayStyle = resolvedAdDisplayStyleForMedia(objectGraph, platformLockupMedia, ad.id, adDataType, iAdAllowsMedia, firstAdComputedStyle, screenshotsDisplayStyle, firstSearchResult, installedStates, appStates, metricsOptions, personalizationDataContainer);
+ if (serverData.isNull(computedAdDisplayStyle)) {
+ adLogger(objectGraph, `[${ad.id}] will not be displayed because we could not create an ad style compatible with ${debugDescriptionForStyle(firstAdComputedStyle)}`);
+ continue;
+ }
+ // Check the localization is valid for the ad with the selected display style.
+ // We do this _before_ storing the style (if this is the first ad) to avoid setting
+ // a style based on an ad that can't be shown.
+ if (!isAdLocalizationValid(objectGraph, ad, null, computedAdDisplayStyle.style)) {
+ adLogger(objectGraph, `[${ad.id}] filtered because localization is not available`);
+ continue;
+ }
+ if (noPreviousAdStyle) {
+ // For the first advert we calculate a display style based on whether it is a dupe of the first organic result
+ // and what media is available for display for the app
+ adLogger(objectGraph, `[${ad.id}] first ad dictates ad display style of: ${debugDescriptionForStyle(computedAdDisplayStyle)}`);
+ firstAdComputedStyle = computedAdDisplayStyle;
+ }
+ else {
+ adLogger(objectGraph, `[${ad.id}] will be displayed because it is compatible with the display style of: ${debugDescriptionForStyle(computedAdDisplayStyle)}, which is the same height as display style: ${debugDescriptionForStyle(firstAdComputedStyle)}`);
+ }
+ metricsOptions.pageInformation.iAdInfo.apply(objectGraph, ad);
+ // Set the template type before creating the lockup to ensure the click event has the right data.
+ (_c = metricsOptions.pageInformation.iAdInfo) === null || _c === void 0 ? void 0 : _c.setTemplateType(computedAdDisplayStyle.style);
+ let adResultLockup = lockups.mixedMediaAdLockupFromData(objectGraph, ad, adLockupOptions, searchVideoConfiguration, searchExperimentsData);
+ adResultLockup = modifyLockupToMatchAdDisplayStyle(adResultLockup, computedAdDisplayStyle, isDupe, isFirstAd);
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_d = adResultLockup.searchAdOpportunity) === null || _d === void 0 ? void 0 : _d.setTemplateType(computedAdDisplayStyle.style);
+ }
+ else {
+ (_e = adResultLockup.searchAd) === null || _e === void 0 ? void 0 : _e.setTemplateType(computedAdDisplayStyle.style);
+ }
+ if (computedAdDisplayStyle.style === "TEXT" /* SearchAdDisplayStyle.TEXT */) {
+ const iAdTextKey = mediaAttributes.attributeAsString(ad, "iad.format.text");
+ if (iAdTextKey !== "none") {
+ let advertisingText;
+ if (iAdTextKey === "description") {
+ advertisingText = contentAttributes.contentAttributeAsString(objectGraph, ad, "description.standard");
+ }
+ else {
+ advertisingText = contentAttributes.contentAttributeAsString(objectGraph, ad, iAdTextKey);
+ }
+ const searchAd = (_f = adResultLockup.searchAd) !== null && _f !== void 0 ? _f : (_g = adResultLockup.searchAdOpportunity) === null || _g === void 0 ? void 0 : _g.searchAd;
+ if (serverData.isDefinedNonNull(searchAd) && serverData.isDefinedNonNull(advertisingText)) {
+ searchAd.advertisingText = advertisingText;
+ }
+ }
+ advertsSearchResult.displaysScreenshots = false;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(adResultLockup)) {
+ const duplicatePosition = findOrganicLockupPosition(resultsDatum, adResultLockup.adamId);
+ if (isSome(duplicatePosition) && !creativeHasArtworkToDisplay) {
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_h = adResultLockup.searchAdOpportunity) === null || _h === void 0 ? void 0 : _h.setDuplicatePosition(duplicatePosition);
+ }
+ else {
+ (_j = adResultLockup.searchAd) === null || _j === void 0 ? void 0 : _j.setDuplicatePosition(duplicatePosition);
+ }
+ }
+ advertsSearchResult.lockups.push(adResultLockup);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ isFirstAd = false;
+ }
+ }
+ // Always pop the `ad_container` location.
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ // There are situations where all ads are filtered out - check the results have at least one item prior to modifying metrics data.
+ if (serverData.isDefinedNonNullNonEmpty(advertsSearchResult.lockups)) {
+ // Once all the lockups are built re-set the page information to use the iAd Info of the first advert
+ // This (probably) happens to ensure that the page event accurately reflects what's being presented
+ // to the user at first view.
+ const firstAd = advertDatum[0];
+ metricsOptions.pageInformation.iAdInfo.apply(objectGraph, firstAd);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ }
+ if (firstAdComputedStyle) {
+ (_k = metricsOptions.pageInformation.iAdInfo) === null || _k === void 0 ? void 0 : _k.setTemplateType(firstAdComputedStyle.style);
+ }
+ else {
+ (_l = metricsOptions.pageInformation.iAdInfo) === null || _l === void 0 ? void 0 : _l.setTemplateType(null);
+ }
+ advertsSearchResult.condensedBehavior = "never";
+ return {
+ result: advertsSearchResult,
+ displayStyle: firstAdComputedStyle === null || firstAdComputedStyle === void 0 ? void 0 : firstAdComputedStyle.style,
+ };
+}
+/**
+ * Determines whether or not the ad is considered a dupe of the first organic result.
+ * @param adID The adaimID of the ad result
+ * @param firstResultID The adamID of the first organic result
+ * @param installedState A mapping of adamIDs to device app install state to determine if the ad/first result is on the user's device
+ * @returns whether the ad and result are dupes of the same app and that the first result will not condensed as a condensed first result
+ * will have no bearing on dupe status or display.
+ */
+function adIsDupe(adID, firstResultID, installedState) {
+ const isResultInstalled = installedState && installedState[firstResultID];
+ const isDupeResult = adID && firstResultID && adID === firstResultID;
+ return isDupeResult && !isResultInstalled;
+}
+// endregion
+// region Ad Display Styles
+/**
+ * Resolves a SearchAdDisplayStyle for the given ad media and the search results context (the first organic search result).
+ * If the ad is a dupe of the first organic search result, we attempt to create two lockups with "full creative" to ensure there is enough
+ * media to show both.
+ * @param media the media from which to calculate the style.
+ * @param adId the id of the ad, for logging purposes.
+ * @param adDataType the ad type, based on the first search result.
+ * @param firstAdComputedStyle a previously computed `SearchAdDisplayStyleContainer`, if any, to maintain compatibility.
+ * @param firstSearchResult The first organic search result which we informs how to handle DUP logic
+ * @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
+ * @param metricsOptions Metrics options for built models.
+ * @param personalizationDataContainer The data container to use for personalizing the data.
+ * @returns a `SearchAdDisplayStyleContainer` with a style compatible with the first search result, if one can be found.
+ */
+function resolvedAdDisplayStyleForMedia(objectGraph, media, adId, adDataType, iAdAllowsMedia, firstAdComputedStyle, screenshotsDisplayStyle, firstSearchResult, installStates, appStates, metricsOptions, personalizationDataContainer) {
+ const isFirstAd = serverData.isNullOrEmpty(firstAdComputedStyle);
+ const adDisplayStyle = getAdDisplayStyleForMedia(objectGraph, media, adId, iAdAllowsMedia, firstAdComputedStyle, screenshotsDisplayStyle);
+ if (serverData.isNull(adDisplayStyle)) {
+ return null;
+ }
+ const searchAdDisplayContainer = {
+ platform: media.mediaPlatformUsedForDisplayStyle,
+ style: adDisplayStyle,
+ };
+ adLogger(objectGraph, `[${adId}] tentatively resolved to: ${debugDescriptionForStyle(searchAdDisplayContainer)}`);
+ if (adDataType === "DUP" /* iAdDataType.DUPE_AD */) {
+ removeUsedMediaForAdDisplayStyle(adDisplayStyle, media);
+ const organicSearchResultDisplayStyle = getAdDisplayStyleForMedia(objectGraph, media, adId, iAdAllowsMedia, null, screenshotsDisplayStyle);
+ const organicHasFullCreative = isDisplayStyleFullCreativeForOrganic(objectGraph, organicSearchResultDisplayStyle, screenshotsDisplayStyle);
+ const organicWillUseAppEvent = searchResultWillUseAppEventDisplay(objectGraph, firstSearchResult, installStates, appStates, metricsOptions, personalizationDataContainer);
+ if ((organicHasFullCreative || organicWillUseAppEvent) && isFirstAd) {
+ adLogger(objectGraph, `[${adId}] Organic Dupe would be full creative as ${organicSearchResultDisplayStyle} so choosing tentative style for ad`);
+ return searchAdDisplayContainer;
+ }
+ else if (organicHasFullCreative &&
+ !isFirstAd &&
+ isProposedAdStyleCompatible(adDisplayStyle, firstAdComputedStyle)) {
+ adLogger(objectGraph, `[${adId}] Organic Dupe would be a full creative, but ad is not the first so returning compatible style with first ${adDisplayStyle}`);
+ return searchAdDisplayContainer;
+ }
+ else if (isProposedAdStyleCompatible("UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */, firstAdComputedStyle)) {
+ adLogger(objectGraph, `[${adId}] tentative style would not yield full creative for organic result so returning UNRESTRICTED_IMAGE_ONE_ASSET`);
+ return {
+ style: "UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */,
+ };
+ }
+ else if (isProposedAdStyleCompatible("UV1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_VIDEO_ONE_ASSET */, firstAdComputedStyle)) {
+ adLogger(objectGraph, `[${adId}] tentative style would not yield full creative for organic result so returning UNRESTRICTED_VIDEO_ONE_ASSET`);
+ return {
+ style: "UV1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_VIDEO_ONE_ASSET */,
+ };
+ }
+ else if (isProposedAdStyleCompatible("TEXT" /* SearchAdDisplayStyle.TEXT */, firstAdComputedStyle)) {
+ adLogger(objectGraph, `[${adId}] tentative style would not yield full creative for organic result so returning TEXT`);
+ return {
+ style: "TEXT" /* SearchAdDisplayStyle.TEXT */,
+ };
+ }
+ else {
+ adLogger(objectGraph, `[${adId}] tentative style would not yield full creative for organic result and first style is not compatible with TEXT so skipping ad`);
+ return null;
+ }
+ }
+ else if (serverData.isDefinedNonNull(firstAdComputedStyle) &&
+ firstAdComputedStyle.style === "TEXT" /* SearchAdDisplayStyle.TEXT */) {
+ adLogger(objectGraph, `[${adId}] tentative style would be filtered since the first ad has style: ${debugDescriptionForStyle(firstAdComputedStyle)}, so returning TEXT`);
+ return {
+ style: "TEXT" /* SearchAdDisplayStyle.TEXT */,
+ };
+ }
+ return searchAdDisplayContainer;
+}
+function allowsFourScreenshots(screenshotsDisplayStyle) {
+ if (!serverData.isDefinedNonNull(screenshotsDisplayStyle)) {
+ return false;
+ }
+ return screenshotsDisplayStyle === "four-screenshots";
+}
+/**
+ * Finds the position of a specific lockup within the organic search results
+ * @param searchResults List of search results
+ * @param adResult The adamId of the app to search for
+ * @returns The 0-indexed position in the list that contains the lockup. If the organic results doesn't contain the lockup, this instead returns null.
+ */
+function findOrganicLockupPosition(searchResults, adamId) {
+ const index = searchResults.findIndex((datum) => datum.id === adamId);
+ return index === -1 ? null : index;
+}
+/**
+ * Returns a suitable style for a given set of lockup media.
+ * If a firstComputedAdStyle is provided, we check to confirm the proposed style is compatible before selecting that new style.
+ * @param media a set of media from which to calculate a style.
+ * @param adId the id of the ad, for logging purposes.
+ * @param iAdAllowsMedia whether the iAd Data has enabled media for the given ad.
+ * @param firstComputedAdStyle whether the iAd Data has enabled media for the given ad.
+ * @returns the preferred display style from the provided set of media.
+ */
+function getAdDisplayStyleForMedia(objectGraph, media, adId, iAdAllowsMedia, firstComputedAdStyle, screenshotsDisplayStyle) {
+ if (!iAdAllowsMedia) {
+ adLogger(objectGraph, `[${adId}] is not allowed to display media because of iAd configuration.`);
+ return "TEXT" /* SearchAdDisplayStyle.TEXT */;
+ }
+ if (media.mediaPlatformUsedForDisplayStyle &&
+ firstComputedAdStyle &&
+ firstComputedAdStyle.mediaPlatform &&
+ !media.mediaPlatformUsedForDisplayStyle.isEqualTo(firstComputedAdStyle.mediaPlatform)) {
+ adLogger(objectGraph, `[${adId}] filtered because media is derived from: ${media.mediaPlatformUsedForDisplayStyle.mediaType}, but first ad media is derived from: ${firstComputedAdStyle.mediaPlatform.mediaType}`);
+ return null;
+ }
+ let displayStyle;
+ let firstVideoPreview = null;
+ if (serverData.isDefinedNonNullNonEmpty(media.videos)) {
+ const firstVideo = media.videos[0];
+ firstVideoPreview = firstVideo.preview;
+ }
+ if (isSome(media.alignedRegionArtwork) &&
+ isProposedAdStyleCompatible("UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */, firstComputedAdStyle)) {
+ displayStyle = "UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */;
+ }
+ else if (isSome(media.alignedRegionVideo) &&
+ isProposedAdStyleCompatible("UV1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_VIDEO_ONE_ASSET */, firstComputedAdStyle)) {
+ displayStyle = "UV1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_VIDEO_ONE_ASSET */;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(firstVideoPreview) &&
+ firstVideoPreview.isLandscape() &&
+ isProposedAdStyleCompatible("LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */, firstComputedAdStyle)) {
+ // If first trailer's preview is landscape the lockup will render as landscape video
+ displayStyle = "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(firstVideoPreview) &&
+ firstVideoPreview.isPortrait() &&
+ allowsFourScreenshots(screenshotsDisplayStyle) &&
+ isProposedAdStyleCompatible("PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */, firstComputedAdStyle)) {
+ // The assumption here is that all combinations of portrait video are compatible with each other.
+ // If this changes in future, this logic will need to be revisited.
+ // If first trailer's preview is portrait the lockup will render as portrait with portrait images filled in
+ if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) && media.portraitScreenshots.length >= 3) {
+ displayStyle = "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) &&
+ media.portraitScreenshots.length >= 2) {
+ displayStyle = "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots)) {
+ displayStyle = "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */;
+ }
+ else {
+ displayStyle = "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */;
+ }
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(firstVideoPreview) &&
+ firstVideoPreview.isPortrait() &&
+ isProposedAdStyleCompatible("PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */, firstComputedAdStyle)) {
+ // The assumption here is that all combinations of portrait video are compatible with each other.
+ // If this changes in future, this logic will need to be revisited.
+ // If first trailer's preview is portrait the lockup will render as portrait with portrait images filled in
+ if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) && media.portraitScreenshots.length >= 2) {
+ displayStyle = "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots)) {
+ displayStyle = "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */;
+ }
+ else {
+ displayStyle = "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */;
+ }
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(media.landscapeScreenshots) &&
+ isProposedAdStyleCompatible("LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */, firstComputedAdStyle)) {
+ displayStyle = "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) &&
+ allowsFourScreenshots(screenshotsDisplayStyle) &&
+ isProposedAdStyleCompatible("PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */, firstComputedAdStyle)) {
+ // The assumption here is that all combinations of portrait video are compatible with each other.
+ // If this changes in future, this logic will need to be revisited.
+ if (media.portraitScreenshots.length >= 4) {
+ displayStyle = "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */;
+ }
+ else if (media.portraitScreenshots.length >= 3) {
+ displayStyle = "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */;
+ }
+ else if (media.portraitScreenshots.length >= 2) {
+ displayStyle = "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */;
+ }
+ else {
+ displayStyle = "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */;
+ }
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) &&
+ isProposedAdStyleCompatible("PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */, firstComputedAdStyle)) {
+ // The assumption here is that all combinations of portrait video are compatible with each other.
+ // If this changes in future, this logic will need to be revisited.
+ if (media.portraitScreenshots.length >= 3) {
+ displayStyle = "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */;
+ }
+ else if (media.portraitScreenshots.length >= 2) {
+ displayStyle = "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */;
+ }
+ else {
+ displayStyle = "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */;
+ }
+ }
+ else if (isProposedAdStyleCompatible("TEXT" /* SearchAdDisplayStyle.TEXT */, firstComputedAdStyle)) {
+ displayStyle = "TEXT" /* SearchAdDisplayStyle.TEXT */;
+ }
+ else {
+ adLogger(objectGraph, `[${adId}] filtered because we could not create a compatible style for the first style of: ${debugDescriptionForStyle(firstComputedAdStyle)}`);
+ return null;
+ }
+ if (completePortraitMediaCount(objectGraph, screenshotsDisplayStyle) === 2) {
+ if (displayStyle === "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */) {
+ displayStyle = "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */;
+ }
+ else if (displayStyle === "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */) {
+ displayStyle = "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */;
+ }
+ }
+ return displayStyle;
+}
+/**
+ * Removes media used by the nominated displayStyle.
+ * Used in the case of a dupe where we need to confirm we have "full creative" for a subsequent presentation of a lockup.
+ * @param lockup the lockup to modify
+ * @param displayStyle the display style previously calculated as suitable for the lockup
+ */
+function removeUsedMediaForAdDisplayStyle(displayStyle, media) {
+ // In the case of any video assets being used, as per the implementation in `getAdDisplayStyleForMedia`,
+ // we always pick the first video, so whether it's portrait or landscape just remove the first.
+ switch (displayStyle) {
+ case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */:
+ // We don't want to splice here as we might need these for reuse on dupe ads
+ if (media.portraitScreenshots.length <= 5) {
+ media.portraitScreenshots.splice(0, 4);
+ }
+ break;
+ case "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */:
+ media.portraitScreenshots.splice(0, 3);
+ break;
+ case "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */:
+ media.portraitScreenshots.splice(0, 2);
+ break;
+ case "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */:
+ media.portraitScreenshots.splice(0, 1);
+ break;
+ case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */:
+ // We will keep the video splice as the video is always first, so it will never be reused between the ad and organic
+ media.videos.splice(0, 1);
+ // We don't want to splice here as we might need these for reuse on dupe ads
+ if (media.portraitScreenshots.length <= 4) {
+ media.portraitScreenshots.splice(0, 3);
+ }
+ break;
+ case "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */:
+ media.videos.splice(0, 1);
+ media.portraitScreenshots.splice(0, 2);
+ break;
+ case "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */:
+ media.videos.splice(0, 1);
+ media.portraitScreenshots.splice(0, 1);
+ break;
+ case "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */:
+ media.landscapeScreenshots.splice(0, 1);
+ break;
+ case "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */:
+ case "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */:
+ media.videos.splice(0, 1);
+ break;
+ default:
+ break;
+ }
+}
+/**
+ * A function to determine whether a calculated organic search display style is considered "full creative".
+ * @param displayStyle the display style calculated for an organic dupe result to validate.
+ * @returns whether the given display style is considered "full creative" for an organic result.
+ */
+function isDisplayStyleFullCreativeForOrganic(objectGraph, displayStyle, screenshotsDisplayStyle) {
+ switch (displayStyle) {
+ case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */:
+ case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */:
+ return true;
+ case "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */:
+ case "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */:
+ return completePortraitMediaCount(objectGraph, screenshotsDisplayStyle) === 3;
+ case "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */:
+ case "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */:
+ return true;
+ case "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */:
+ case "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */:
+ return completePortraitMediaCount(objectGraph) === 2;
+ default:
+ return false;
+ }
+}
+/**
+ * Indicates whether the proposed ad style is compatible with a previously computed ad style.
+ * If there is no previously computed style, we return true as it's assumed the new style is compatible.
+ * @param proposedAdStyle
+ * @param firstAdComputedStyle
+ * @returns a boolean indicating whether the styles are compatible.
+ */
+function isProposedAdStyleCompatible(proposedAdStyle, firstComputedAdStyle) {
+ if (serverData.isNull(firstComputedAdStyle)) {
+ return true;
+ }
+ let areStylesCompatible = true;
+ switch (proposedAdStyle) {
+ case "TEXT" /* SearchAdDisplayStyle.TEXT */:
+ areStylesCompatible = firstComputedAdStyle.style === "TEXT" /* SearchAdDisplayStyle.TEXT */;
+ break;
+ case "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */:
+ case "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */:
+ areStylesCompatible =
+ firstComputedAdStyle.style === "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */ ||
+ firstComputedAdStyle.style === "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */;
+ break;
+ case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */:
+ case "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */:
+ case "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */:
+ case "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */:
+ case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */:
+ case "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */:
+ case "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */:
+ case "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */:
+ areStylesCompatible =
+ firstComputedAdStyle.style === "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */ ||
+ firstComputedAdStyle.style === "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */ ||
+ firstComputedAdStyle.style === "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */ ||
+ firstComputedAdStyle.style === "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */ ||
+ firstComputedAdStyle.style === "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */ ||
+ firstComputedAdStyle.style === "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */ ||
+ firstComputedAdStyle.style === "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */ ||
+ firstComputedAdStyle.style === "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */;
+ break;
+ case "UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */:
+ areStylesCompatible = firstComputedAdStyle.style === "UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */;
+ break;
+ default:
+ areStylesCompatible = false;
+ }
+ return areStylesCompatible;
+}
+/**
+ * Extract the best media for the platform from the lockup.
+ * @param lockup a lockup created from which we should extract the media.
+ * @returns the best media for the platform.
+ */
+function platformMediaForLockup(lockup) {
+ let mediaPlatformUsedForDisplayStyle = null;
+ // This works on the assumption that when a lockup is created screenshots and trailers are sorted
+ // and the first in the array of each is what we attempt to display.
+ const firstTrailer = lockup.trailers[0];
+ let videos = null;
+ if (serverData.isDefinedNonNullNonEmpty(firstTrailer)) {
+ firstTrailer.videos.sort((a, b) => {
+ return artworkSortingFunction(a.preview, b.preview);
+ });
+ videos = firstTrailer.videos;
+ mediaPlatformUsedForDisplayStyle = firstTrailer.mediaPlatform;
+ }
+ // Grab the first array of platform screenshots, the one the search ad will display.
+ // Additional elements in this array might be for other platforms we wouldn't
+ // display (e.g. if current device is an iPhone then [1] might be iPad screenshots)
+ const platformScreenshots = lockup.screenshots[0];
+ // Split the screenshots into portrait and landscape arrays.
+ // Landscape is preferred, so we use those first before portrait images, but we also need to know
+ // of any portrait screenshots so we can fill in any space left after a portrait video, which is
+ // preferred over a landscape screenshot.
+ const portraitScreenshots = [];
+ const landscapeScreenshots = [];
+ if (serverData.isDefinedNonNullNonEmpty(platformScreenshots)) {
+ platformScreenshots.artwork.forEach((artwork) => {
+ if (artwork.isPortrait()) {
+ portraitScreenshots.push(artwork);
+ }
+ else {
+ landscapeScreenshots.push(artwork);
+ }
+ });
+ mediaPlatformUsedForDisplayStyle = platformScreenshots.mediaPlatform;
+ }
+ return {
+ portraitScreenshots: portraitScreenshots,
+ landscapeScreenshots: landscapeScreenshots,
+ alignedRegionArtwork: lockup.alignedRegionArtwork,
+ alignedRegionVideo: lockup.alignedRegionVideo,
+ videos: videos,
+ mediaPlatformUsedForDisplayStyle: mediaPlatformUsedForDisplayStyle,
+ };
+}
+// endregion
+function iadAttributesForType(data, type) {
+ let iAdDictionaryToUse = null;
+ const iAdOptions = mediaAttributes.attributeAsDictionary(data, "iads");
+ const iAdJSONStringToUse = serverData.asString(iAdOptions, type);
+ if (iAdJSONStringToUse && iAdJSONStringToUse.length) {
+ iAdDictionaryToUse = JSON.parse(iAdJSONStringToUse);
+ }
+ return iAdDictionaryToUse;
+}
+function iAdDataTypeForAdvert(firstSearchResult, isDupe) {
+ if (serverData.isNullOrEmpty(firstSearchResult)) {
+ return "NOORGANIC" /* iAdDataType.NO_ORGANIC_RESULTS */;
+ }
+ if (isDupe) {
+ return "DUP" /* iAdDataType.DUPE_AD */;
+ }
+ return "NORMAL" /* iAdDataType.NORMAL */;
+}
+/**
+ * Removes unused media from the provided ad lockup, matching the selected ad display style.
+ * @param ad the ad lockup from which media should be removed.
+ * @param searchAdDisplayStyle the selected `SearchAdDisplayStyle` to match.
+ * @param isDupe if the given ad is a dupe of the first organic search result.
+ * @param isFirstAd if the given ad is the first search ad.
+ * @returns
+ */
+function modifyLockupToMatchAdDisplayStyle(ad, searchAdDisplayStyle, isDupe, isFirstAd) {
+ var _a, _b;
+ // Derive whether the ad lockup has a CPP (custom product page/ppid) that's being used for it's assets.
+ const hasCPP = serverData.isDefinedNonNullNonEmpty((_b = (_a = ad.impressionMetrics) === null || _a === void 0 ? void 0 : _a.fields) === null || _b === void 0 ? void 0 : _b.pageCustomId);
+ // If this ad lockup is a duplicate of the first organic search result and is not the first received ad,
+ // the organic search result gets the "first choice" of media. This flag indicates whether we should keep
+ // the "first set" or "second set" of media for this ad lockup.
+ // This logic does *not* apply if the ad is using a CPP. This is because the CPP from an ad only gets applied
+ // to an organic dupe result where the matching ad is in the first position. If not, the ad and organic are
+ // using a different set of assets, so there's no need to prefer one with the "first" or "second" set of media.
+ const wantsSecondSetOfMedia = isDupe && !isFirstAd && !hasCPP;
+ if (serverData.isDefinedNonNullNonEmpty(ad.trailers)) {
+ const firstTrailers = ad.trailers.shift();
+ firstTrailers.videos.sort((a, b) => {
+ return artworkSortingFunction(a.preview, b.preview);
+ });
+ ad.trailers.unshift(firstTrailers);
+ }
+ // Split the screenshots into portrait and landscape arrays.
+ // When removing screenshots from the array below, we can't just assume the first ones match the orientation
+ // we're using. For example, we only use portrait images with a portait video and some landscape images could
+ // be mixed in.
+ let mediaPlatformUsedForDisplayStyle;
+ let portraitScreenshots = [];
+ let landscapeScreenshots = [];
+ if (serverData.isDefinedNonNullNonEmpty(ad.screenshots)) {
+ const firstScreenshots = ad.screenshots.shift();
+ firstScreenshots.artwork.forEach((artwork) => {
+ if (artwork.isPortrait()) {
+ portraitScreenshots.push(artwork);
+ }
+ else {
+ landscapeScreenshots.push(artwork);
+ }
+ });
+ mediaPlatformUsedForDisplayStyle = firstScreenshots.mediaPlatform;
+ }
+ switch (searchAdDisplayStyle.style) {
+ case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */:
+ // We've previously determined advert can support this type. Remove videos and landscape images so
+ // advert only displays portrait images.
+ // With 4 portrait assets, we need to make sure that the ads and organic can be full by borrowing some assets from the organic.
+ // However, per product, if the app has 5 or less portrait assets, then it will go to the text ad as today.
+ // So we need to check for 5 assets as the lower bound (exclusive) and 8 (exclusive) as the upper for
+ // the extra work to reuse assets.
+ ad.trailers = null;
+ landscapeScreenshots = null;
+ ad.screenshotsDisplayStyle = "four-screenshots";
+ if (wantsSecondSetOfMedia) {
+ if (portraitScreenshots.length > 5 && portraitScreenshots.length < 8) {
+ const usedAssets = portraitScreenshots.splice(0, 4);
+ const reuseCount = 4 - portraitScreenshots.length;
+ const reuseAssets = usedAssets.splice(usedAssets.length - reuseCount);
+ portraitScreenshots.unshift(...reuseAssets);
+ }
+ else {
+ portraitScreenshots.splice(0, 4);
+ }
+ }
+ else {
+ portraitScreenshots.splice(4);
+ }
+ break;
+ case "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */:
+ // We've previously determined advert can support this type. Remove videos and landscape images so
+ // advert only displays portrait images.
+ ad.trailers = null;
+ landscapeScreenshots = null;
+ if (wantsSecondSetOfMedia) {
+ portraitScreenshots.splice(0, 3);
+ }
+ else {
+ portraitScreenshots.splice(3);
+ }
+ break;
+ case "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */:
+ // We've previously determined advert can support this type. Remove videos and landscape images so
+ // advert only displays portrait images.
+ ad.trailers = null;
+ landscapeScreenshots = null;
+ if (wantsSecondSetOfMedia) {
+ portraitScreenshots.splice(0, 2);
+ }
+ else {
+ portraitScreenshots.splice(2);
+ }
+ break;
+ case "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */:
+ // We've previously determined advert can support this type. Remove videos and landscape images so
+ // advert only displays portrait images.
+ ad.trailers = null;
+ landscapeScreenshots = null;
+ if (wantsSecondSetOfMedia) {
+ portraitScreenshots.splice(0, 1);
+ }
+ else {
+ portraitScreenshots.splice(1);
+ }
+ break;
+ case "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */:
+ // We've previously determined advert can support this type. Remove videos and portrait images so
+ // advert only displays landscape images.
+ ad.trailers = null;
+ portraitScreenshots = null;
+ if (wantsSecondSetOfMedia) {
+ landscapeScreenshots.splice(0, 1);
+ }
+ else {
+ landscapeScreenshots.splice(1);
+ }
+ break;
+ case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */:
+ // We've previously determined advert can support this type. Remove landscape images so
+ // advert only displays portrait assets.
+ // With 4 portrait assets, we need to make sure that the ads and organic can be full by borrowing some assets from the organic.
+ // However, per product, if the app has 5 or less portrait assets, then it will go to the text ad as today.
+ // Since this style includes a portrait video, we only need to check for 4 assets (exclusive) as the lower bound and 7 (exclusive) as the upper for
+ // the extra work to reuse assets
+ landscapeScreenshots = null;
+ ad.screenshotsDisplayStyle = "four-screenshots";
+ if (wantsSecondSetOfMedia) {
+ ad.trailers[0].videos.splice(0, 1);
+ if (portraitScreenshots.length > 4 && portraitScreenshots.length < 7) {
+ const usedAssets = portraitScreenshots.splice(0, 3);
+ const reuseCount = 3 - portraitScreenshots.length;
+ const reuseAssets = usedAssets.splice(usedAssets.length - reuseCount);
+ portraitScreenshots.unshift(...reuseAssets);
+ }
+ else {
+ portraitScreenshots.splice(0, 3);
+ }
+ }
+ else {
+ ad.trailers[0].videos.splice(1);
+ portraitScreenshots.splice(3);
+ }
+ break;
+ case "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */:
+ // We've previously determined advert can support this type. Remove landscape images so
+ // advert only displays portrait assets.
+ landscapeScreenshots = null;
+ if (wantsSecondSetOfMedia) {
+ ad.trailers[0].videos.splice(0, 1);
+ portraitScreenshots.splice(0, 2);
+ }
+ else {
+ ad.trailers[0].videos.splice(1);
+ portraitScreenshots.splice(2);
+ }
+ break;
+ case "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */:
+ // We've previously determined advert can support this type. Remove landscape images so
+ // advert only displays portrait assets.
+ landscapeScreenshots = null;
+ if (wantsSecondSetOfMedia) {
+ ad.trailers[0].videos.splice(0, 1);
+ portraitScreenshots.splice(0, 1);
+ }
+ else {
+ ad.trailers[0].videos.splice(1);
+ portraitScreenshots.splice(1);
+ }
+ break;
+ case "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */:
+ case "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */:
+ // We've determined advert can support this type. Remove images so only the single video displays
+ if (wantsSecondSetOfMedia) {
+ ad.trailers[0].videos.splice(0, 1);
+ }
+ else {
+ ad.trailers[0].videos.splice(1);
+ }
+ landscapeScreenshots = null;
+ portraitScreenshots = null;
+ break;
+ case "TEXT" /* SearchAdDisplayStyle.TEXT */:
+ // All adverts can support text only, remove all screenshots and trailers so they display in this style
+ ad.trailers = null;
+ landscapeScreenshots = null;
+ portraitScreenshots = null;
+ break;
+ default:
+ break;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(ad.trailers)) {
+ const firstTrailers = ad.trailers.shift();
+ ad.trailers = [firstTrailers];
+ }
+ // Combine the remaining landscape and portrait screenshots, and set them back on the ad as the only values.
+ const allScreenshots = [].concat(...[landscapeScreenshots, portraitScreenshots].filter(serverData.isDefinedNonNull));
+ if (serverData.isDefinedNonNullNonEmpty(allScreenshots)) {
+ const screenshots = new models.Screenshots(allScreenshots, mediaPlatformUsedForDisplayStyle);
+ ad.screenshots = [screenshots];
+ }
+ else {
+ ad.screenshots = null;
+ }
+ return ad;
+}
+function completePortraitMediaCount(objectGraph, searchExperimentsData = null) {
+ if (objectGraph.client.isPhone) {
+ return allowsFourScreenshots(searchExperimentsData) ? 4 : 3;
+ }
+ else {
+ return 2;
+ }
+}
+function debugDescriptionForStyle(styleContainer) {
+ if (serverData.isNullOrEmpty(styleContainer)) {
+ return "";
+ }
+ let mediaTypeString = "";
+ if (styleContainer && styleContainer.mediaPlatform) {
+ mediaTypeString = ` derived from ${styleContainer.mediaPlatform.mediaType} media`;
+ }
+ return `${styleContainer.style}${mediaTypeString}`;
+}
+/**
+ * Stores a value representing the current setting of the native ad debug logging.
+ * This starts as `null`, and the first time we log we ask native to provide the current value.
+ * Requires a re-bootstrap to update, so we avoid sending a message back to native on every call
+ * if logging is disabled.
+ */
+let isNativeAdLoggingEnabled = null;
+export function adLogger(objectGraph, message) {
+ objectGraph.console.log(`[Ads] ${message}`);
+ if (objectGraph.client.buildType === "internal" &&
+ objectGraph.isAvailable(ads) &&
+ serverData.isDefinedNonNull(objectGraph.ads.debugLog)) {
+ // If we haven't asked native for whether the debug setting for ad logging is enabled, do it now.
+ if (serverData.isNull(isNativeAdLoggingEnabled) &&
+ serverData.isDefinedNonNull(objectGraph.ads.isNativeAdLoggingEnabled)) {
+ isNativeAdLoggingEnabled = objectGraph.ads.isNativeAdLoggingEnabled();
+ }
+ if (isNativeAdLoggingEnabled) {
+ objectGraph.ads.debugLog(message);
+ }
+ }
+}
+const artworkSortingFunction = (a, b) => {
+ const aLandscape = a.isLandscape();
+ const bLandscape = b.isLandscape();
+ if (aLandscape === bLandscape) {
+ return 0;
+ }
+ if (aLandscape) {
+ return -1;
+ }
+ return 1;
+};
+/**
+ * A wrapper around the ad search result to include the ad's displayStyle it will present with
+ */
+export class SearchAdsDisplayStyleResultContainer {
+}
+/**
+ * Removes any media used in a provided ad search result from the organic search result.
+ * @param objectGraph the Object Graph
+ * @param adResult the ad search result
+ * @param searchResult the organic search result
+ * @param searchExperimentsData the metadata for search result experiemnts
+ * @param searchAdDisplayStyle the display style for the ad that is matching with the organic
+ */
+export function dedupeAdMediaFromMatchingResult(objectGraph, adResult, searchResult, searchExperimentsData, searchAdDisplayStyle) {
+ var _a;
+ // Run de-duping on `AppSearchResult`s and `AppEventSearchResult`s.
+ // We specifically run this for `AppEventSearchResult`s because they can be presented in the search results
+ // as either a regular search result (ie. MixedMediaLockup) (if the app isn't installed), or as an IAE search
+ // result (if it is installed). This means we need to run de-duping in case it appears as a regular result,
+ // matching behaviour of the `AppSearchResult`.
+ if (!(searchResult instanceof models.AppSearchResult || searchResult instanceof models.AppEventSearchResult)) {
+ return;
+ }
+ const searchLockup = searchResult.lockup;
+ const adLockup = adResult.lockups[0];
+ if (adLockup.adamId !== searchLockup.adamId) {
+ return;
+ }
+ const usedTemplateUrls = new Set();
+ if (serverData.isDefinedNonNullNonEmpty(adLockup.screenshots)) {
+ for (const screenshot of adLockup.screenshots[0].artwork) {
+ usedTemplateUrls.add(screenshot.template);
+ }
+ }
+ if (serverData.isDefinedNonNullNonEmpty(adLockup.trailers)) {
+ for (const video of adLockup.trailers[0].videos) {
+ usedTemplateUrls.add(video.preview.template);
+ }
+ }
+ if (serverData.isDefinedNonNullNonEmpty(searchLockup.screenshots)) {
+ const filteredArtwork = searchLockup.screenshots[0].artwork.filter((artwork) => {
+ return !usedTemplateUrls.has(artwork.template);
+ });
+ searchLockup.screenshots[0] = new models.Screenshots(filteredArtwork, searchLockup.screenshots[0].mediaPlatform);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(searchLockup.trailers)) {
+ const filteredVideos = searchLockup.trailers[0].videos.filter((video) => {
+ return !usedTemplateUrls.has(video.preview.template);
+ });
+ searchLockup.trailers[0] = new models.Trailers(filteredVideos, searchLockup.trailers[0].mediaPlatform);
+ }
+ /// Exit early if we don't need to do anything special for the 4 screenshots case
+ if (((_a = searchExperimentsData === null || searchExperimentsData === void 0 ? void 0 : searchExperimentsData.displayStyle) === null || _a === void 0 ? void 0 : _a.screenshots) !== "four-screenshots") {
+ return;
+ }
+ // We need to possibly reuse some of the screenshots from the ad to make sure the organic has enough screenshots to show;
+ // This is only if we will show 4 portrait assets
+ const padSearchLockupScreenshotsToPreferredSize = (totalRequiredArtwork) => {
+ const currentScreenshots = searchLockup.screenshots[0].artwork;
+ if (currentScreenshots.length >= totalRequiredArtwork) {
+ return;
+ }
+ let screenshotsStillNeededCount = totalRequiredArtwork - currentScreenshots.length;
+ const adArtworksToReuse = adLockup.screenshots[0].artwork.slice().reverse();
+ for (const artwork of adArtworksToReuse) {
+ if (screenshotsStillNeededCount <= 0) {
+ return;
+ }
+ searchLockup.screenshots[0].artwork.unshift(artwork);
+ screenshotsStillNeededCount -= 1;
+ }
+ };
+ switch (searchAdDisplayStyle) {
+ case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */:
+ case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */:
+ padSearchLockupScreenshotsToPreferredSize(4);
+ break;
+ default:
+ break;
+ }
+}
+/**
+ * As part of the CPP implementation for Search Results ads, if the first ad result has a CPP applied, and there is a dupe scenario
+ * (ie. the first ad matches the first organic result) the CPP must also be applied to the first organic result.
+ * Here we update the CPP data on the organic result to match the first ad result, if applicable.
+ * @param objectGraph The object graph
+ * @param adData The raw ad data used to build the ad results.
+ * @param adResult The built ad result.
+ * @param searchResultData The data for the first organic search result.
+ * @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
+ * @param metricsOptions Metrics options for built models.
+ * @param personalizationDataContainer The data container to use for personalizing the data.
+ */
+export function updateDupeOrganicResultCPPData(objectGraph, adData, adResult, searchResultData, installStates, appStates, metricsOptions, personalizationDataContainer) {
+ var _a, _b;
+ const adLockup = adResult.lockups[0];
+ if (adLockup.adamId !== searchResultData.id) {
+ return;
+ }
+ // Get the data for the first ad lockup. This *should* just be the first, but
+ // it's possible the first data got dropped when building the ad lockups.
+ const dataForAdLockup = adData.find((data) => data.id === adLockup.adamId);
+ const shouldSkipUpdatingOrganicCPP = searchResultWillUseAppEventDisplay(objectGraph, searchResultData, installStates, appStates, metricsOptions, personalizationDataContainer);
+ if (shouldSkipUpdatingOrganicCPP) {
+ return;
+ }
+ // The first ad lockup matches the first organic result. Apply the Ad cppId to matching organic result.
+ updatePPIDInData((_b = (_a = dataForAdLockup === null || dataForAdLockup === void 0 ? void 0 : dataForAdLockup.meta) === null || _a === void 0 ? void 0 : _a.cppData) === null || _b === void 0 ? void 0 : _b["ppid"], searchResultData);
+}
+/**
+ * Update the ppid for the given data.
+ * If provided `ppid` is `null`, the field will be deleted.
+ * If `cppData` does not already exist on `meta`, it will be created.
+ * @param ppid The ppid to update in the data. Can be null, which will delete the field.
+ * @param data The data object to update.
+ */
+function updatePPIDInData(ppid, data) {
+ var _a;
+ let meta = shallowCopyOf(data.meta);
+ if (serverData.isNull(ppid)) {
+ (_a = meta === null || meta === void 0 ? void 0 : meta.cppData) === null || _a === void 0 ? true : delete _a["ppid"];
+ }
+ else {
+ if (serverData.isNull(meta)) {
+ meta = {};
+ }
+ if (serverData.isNull(meta.cppData)) {
+ meta.cppData = {};
+ }
+ meta.cppData["ppid"] = ppid;
+ }
+ data.meta = meta;
+}
+/**
+ * Whether or not platform supports adverts.
+ */
+export function platformSupportsAdverts(objectGraph) {
+ return ((clientIdentifierSupportsAdverts(objectGraph) && objectGraph.host.isiOS) ||
+ objectGraph.host.platform === "unknown");
+}
+function clientIdentifierSupportsAdverts(objectGraph) {
+ return (objectGraph.host.clientIdentifier === client.appStoreIdentifier ||
+ objectGraph.host.clientIdentifier === client.productPageExtensionIdentifier);
+}
+//# sourceMappingURL=search-ads.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-common.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-common.js
new file mode 100644
index 0000000..94e7438
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-common.js
@@ -0,0 +1,59 @@
+/**
+ * Common operations for builders in search tab.
+ */
+// region Search Term State
+/**
+ * Build the `SearchTermContext` for a given response triggered by a search for `term`, possibly originating from `originatingTerm`
+ * @param requestDescriptor The options that describe fetch request.
+ * @param searchResponse The sequential response that was returned.
+ */
+export function createTermContextForSpellcheckedSequentialResponse(objectGraph, requestDescriptor, searchResponse) {
+ var _a, _b, _c, _d, _e, _f, _g;
+ return {
+ term: requestDescriptor.term,
+ suggestedTerm: (_b = (_a = searchResponse.results) === null || _a === void 0 ? void 0 : _a.spellCheck) === null || _b === void 0 ? void 0 : _b.suggestedTerm,
+ correctedTerm: (_d = (_c = searchResponse.results) === null || _c === void 0 ? void 0 : _c.spellCheck) === null || _d === void 0 ? void 0 : _d.correctedTerm,
+ resultsTerm: (_g = (_f = (_e = searchResponse.results) === null || _e === void 0 ? void 0 : _e.spellCheck) === null || _f === void 0 ? void 0 : _f.correctedTerm) !== null && _g !== void 0 ? _g : requestDescriptor.term,
+ originatingTerm: requestDescriptor.originatingTerm,
+ };
+}
+/**
+ * Create a search term context for the segmented search results completed fetch
+ * @param objectGraph The app store object graph
+ * @param requestDescriptor The search request descriptor
+ * @param searchResponse The response for the segmented search results page
+ * @returns The search term context for the segmented search results fetch
+ */
+export function createTermContextForSpellcheckedGroupedResponse(objectGraph, requestDescriptor, searchResponse) {
+ var _a, _b, _c, _d, _e, _f, _g;
+ return {
+ term: requestDescriptor.term,
+ suggestedTerm: (_b = (_a = searchResponse.results) === null || _a === void 0 ? void 0 : _a.spellCheck) === null || _b === void 0 ? void 0 : _b.suggestedTerm,
+ correctedTerm: (_d = (_c = searchResponse.results) === null || _c === void 0 ? void 0 : _c.spellCheck) === null || _d === void 0 ? void 0 : _d.correctedTerm,
+ resultsTerm: (_g = (_f = (_e = searchResponse.results) === null || _e === void 0 ? void 0 : _e.spellCheck) === null || _f === void 0 ? void 0 : _f.correctedTerm) !== null && _g !== void 0 ? _g : requestDescriptor.term,
+ originatingTerm: requestDescriptor.originatingTerm,
+ };
+}
+/**
+ * Build the `SearchTermContext` purely from `requestDescriptor` for requests that don't have spellchecking.
+ * @param requestDescriptor The options that describe fetch request.
+ */
+export function createTermContextForNonspellcheckRequest(objectGraph, requestDescriptor) {
+ return {
+ term: requestDescriptor.term,
+ resultsTerm: requestDescriptor.term,
+ originatingTerm: requestDescriptor.originatingTerm,
+ };
+}
+// endregion
+// region Constants
+/**
+ * The field name desired within the `meta.metrics` in search response.
+ */
+export const searchMetricsDataSetID = "data.search.dataSetId";
+/**
+ * The actual field name within the `meta.metrics` in search response.
+ */
+export const legacySearchMetricsDataSetID = "dataSetId";
+// endregion
+//# sourceMappingURL=search-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-facets.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-facets.js
new file mode 100644
index 0000000..dd0ccf4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-facets.js
@@ -0,0 +1,146 @@
+/**
+ * Build methods for Search Facets.
+ */
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { categoryListFromApiResponse } from "../categories";
+import * as metricsClickHelpers from "../metrics/helpers/clicks";
+/**
+ * Create Search Facets. Theres platform specific variations here.
+ * @param requestFacets Facets in current request.
+ * @param categoryFacetsData Additional
+ */
+export function createSearchFacets(objectGraph, requestFacets, categoryFacetsData) {
+ const selectedFacets = requestFacets || {};
+ const facets = [];
+ // Device Type
+ if (objectGraph.client.deviceType !== "mac") {
+ facets.push(new models.SearchFacetSet("targetPlatform", [
+ new models.SearchFacetValue(objectGraph.loc.string("Search.Facets.iPadAndIPhone"), null, selectedFacets["targetPlatform"]),
+ new models.SearchFacetValue(objectGraph.loc.string("Search.Facets.iPhoneOnly"), "iphone", selectedFacets["targetPlatform"]),
+ ]));
+ }
+ // Price
+ facets.push(new models.SearchFacetSet("price", [
+ new models.SearchFacetValue(objectGraph.loc.string("SEARCH_FACET_ANY_PRICE", "Any"), null, selectedFacets["price"]),
+ new models.SearchFacetValue(objectGraph.loc.string("SEARCH_FACET_FREE", "Any"), "free", selectedFacets["price"]),
+ ]));
+ // Categories
+ const categoryList = categoryListFromApiResponse(objectGraph, categoryFacetsData, false);
+ if (categoryList) {
+ const serverCategories = categoryList.categories;
+ if (serverCategories.length) {
+ const genreFacetValues = serverCategories
+ .filter((category) => {
+ return serverData.isDefinedNonNull(category.genreId);
+ })
+ .map((category) => {
+ return new models.SearchFacetValue(category.name, category.genreId, selectedFacets["genre"]);
+ });
+ genreFacetValues.unshift(new models.SearchFacetValue(objectGraph.loc.string("SEARCH_FACET_ANY_CATEGORY", "Any"), null, selectedFacets["genre"]));
+ facets.push(new models.SearchFacetSet("genre", genreFacetValues));
+ }
+ }
+ const searchSortOptions = objectGraph.bag.searchSortOptions;
+ // Sorts
+ const sortFacetValues = [];
+ sortFacetValues.push(new models.SearchFacetValue(objectGraph.loc.string("SEARCH_FACET_RELEVANCE"), null, selectedFacets["sort"]));
+ for (const sortValue of searchSortOptions) {
+ sortFacetValues.push(new models.SearchFacetValue(objectGraph.loc.string("SEARCH_FACET_" + sortValue), sortValue, selectedFacets["sort"]));
+ }
+ if (sortFacetValues.length > 1) {
+ facets.push(new models.SearchFacetSet("sort", sortFacetValues));
+ }
+ const serverAgeBands = objectGraph.bag.ageBands;
+ const ageBandFacetValues = serverAgeBands.map((ageBand) => {
+ return new models.SearchFacetValue(serverData.asString(ageBand, "name"), serverData.asString(ageBand, "ageBandId"), selectedFacets["ages"]);
+ });
+ if (ageBandFacetValues.length > 0 && objectGraph.client.deviceType !== "mac") {
+ facets.push(new models.SearchFacetSet("ages", ageBandFacetValues));
+ }
+ return facets;
+}
+/**
+ * Create Search Facets. Theres platform specific variations here.
+ * @param categoryFacetsData Additional
+ */
+export function createSearchPageFacets(objectGraph, categoryFacetsData) {
+ let categoryFacet = null;
+ let sortsFacet = null;
+ let ageBandsFacet = null;
+ // Platform
+ const deviceTypeFacet = new models.PageFacetsFacet("targetPlatform", "targetPlatform", objectGraph.loc.string("SEARCH_FACET_TYPE_TITLE_DEVICE_TYPE"), "singleSelection", [
+ new models.PageFacetOption(objectGraph.loc.string("Search.Facets.iPadAndIPhone"), null),
+ new models.PageFacetOption(objectGraph.loc.string("Search.Facets.iPhoneOnly"), "iphone"),
+ ], null, null, pageFacetChangeAction(objectGraph, "targetPlatform"));
+ // Price
+ const priceFacet = new models.PageFacetsFacet("filter[price]", "filter[price]", objectGraph.loc.string("SEARCH_FACET_TYPE_TITLE_PRICE"), "singleSelection", [
+ new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_ANY_PRICE", "Any"), null),
+ new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_FREE", "Any"), "free"),
+ ], null, null, pageFacetChangeAction(objectGraph, "price"));
+ // Categories
+ const categoryList = categoryListFromApiResponse(objectGraph, categoryFacetsData, false);
+ if (categoryList) {
+ const serverCategories = categoryList.categories;
+ if (serverCategories.length) {
+ const categories = serverCategories.filter((category) => {
+ return serverData.isDefinedNonNull(category.genreId);
+ });
+ categoryFacet = new models.PageFacetsFacet("filter[genre]", "filter[genre]", objectGraph.loc.string("SEARCH_FACET_TYPE_TITLE_CATEGORY"), "singleSelection", [new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_ANY_CATEGORY", "Any"), null)], null, null, pageFacetChangeAction(objectGraph, "genre"));
+ for (const category of categories) {
+ categoryFacet.options.push(new models.PageFacetOption(category.name, category.genreId));
+ }
+ }
+ }
+ // Sorts
+ const searchSortOptions = objectGraph.bag.searchSortOptions;
+ sortsFacet = new models.PageFacetsFacet("sort", "sort", objectGraph.loc.string("SEARCH_FACET_TYPE_TITLE_SORT"), "singleSelection", [new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_RELEVANCE"), null)], null, null, pageFacetChangeAction(objectGraph, "sort"));
+ for (const sortValue of searchSortOptions) {
+ sortsFacet.options.push(new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_" + sortValue), sortValue));
+ }
+ // Age Bands
+ const serverAgeBands = objectGraph.bag.ageBands;
+ const ageBandFacetOptions = serverAgeBands.map((ageBand) => {
+ return new models.PageFacetOption(serverData.asString(ageBand, "name"), serverData.asString(ageBand, "ageBandId"));
+ });
+ if (ageBandFacetOptions.length > 0 && objectGraph.client.deviceType !== "mac") {
+ ageBandsFacet = new models.PageFacetsFacet("filter[ages]", "filter[ages]", objectGraph.loc.string("SEARCH_FACET_TYPE_TITLE_AGE_BAND"), "singleSelection", ageBandFacetOptions, null, null, pageFacetChangeAction(objectGraph, "ages"));
+ }
+ const pageFacets = new models.PageFacets([], false, null);
+ if (objectGraph.client.isMac) {
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([priceFacet]));
+ if (serverData.isDefinedNonNull(categoryFacet)) {
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([categoryFacet]));
+ }
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([sortsFacet]));
+ }
+ else {
+ const facets = [deviceTypeFacet, priceFacet];
+ if (serverData.isDefinedNonNull(categoryFacet)) {
+ facets.push(categoryFacet);
+ }
+ facets.push(sortsFacet);
+ if (serverData.isDefinedNonNull(ageBandsFacet)) {
+ facets.push(ageBandsFacet);
+ }
+ for (const facet of facets) {
+ facet.showsSelectedOptions = true;
+ }
+ pageFacets.facetGroups.push(new models.PageFacetsGroup(facets));
+ }
+ return pageFacets;
+}
+export function createDefaultSelectedFacetOptions(objectGraph) {
+ return {
+ "targetPlatform": [new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_IPAD_ONLY"), null)],
+ "filter[price]": [new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_ANY_PRICE", "Any"), null)],
+ "sort": [new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_RELEVANCE"), null)],
+ "filter[genre]": [new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_ANY_CATEGORY", "Any"), null)],
+ };
+}
+function pageFacetChangeAction(objectGraph, facetParameter) {
+ const action = new models.BlankAction();
+ metricsClickHelpers.addClickEventToPageFacetsChangeAction(objectGraph, action, facetParameter);
+ return action;
+}
+//# sourceMappingURL=search-facets.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-landing-page-utils.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-landing-page-utils.js
new file mode 100644
index 0000000..f26b78b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-landing-page-utils.js
@@ -0,0 +1,386 @@
+import * as validation from "@jet/environment/json/validation";
+import { FetchTimingMetricsBuilder } from "@jet/environment/metrics/fetch-timing-metrics-builder";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import { PageRefreshPolicy, SearchAction, SearchFocusPage, SearchLandingPage, Shelf } from "../../api/models";
+import * as mediaRequestUtils from "../../common/builders/url-mapping-utils";
+import * as appStoreExperiments from "../../foundation/experimentation/app-store-experiments";
+import { ExperimentAreaId } from "../../foundation/experimentation/experiment-area-id";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaDataFetching from "../../foundation/media/data-fetching";
+import * as mediaNetwork from "../../foundation/media/network";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import { Parameters, Path, Protocol } from "../../foundation/network/url-constants";
+import * as impressionDemotion from "../../common/personalization/on-device-impression-demotion";
+import { iadInfoFromOnDeviceAdResponse, isAdPlacementEnabled } from "../ads/ad-common";
+import * as adIncidents from "../ads/ad-incident-recorder";
+import { fetchAds as landingAdFetchFetchAds } from "../ads/on-device-ad-fetch";
+import * as landingAdStitch from "../ads/on-device-ad-stitch";
+import * as contentArtwork from "../content/artwork/artwork";
+import * as groupingCommon from "../grouping/grouping-common";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import * as metricsHelpersUtil from "../metrics/helpers/util";
+import * as onDevicePersonalization from "../personalization/on-device-personalization";
+import * as productPageVariants from "../product-page/product-page-variants";
+import { areAppTagsEnabled } from "../util/app-tags-util";
+import { isFeatureEnabledForCurrentUser } from "../util/lottery";
+import { SearchPageType } from "./content/search-shelves";
+import * as searchLandingCohort from "./landing/search-landing-cohort";
+import * as searchLandingShelfController from "./landing/search-landing-shelf-controller";
+/**
+ * Determines whether or not the user will use the legacy Search Landing Page protocol
+ * @param objectGraph The App Store Object Graph
+ * @returns Whether or not the user will use the legacy Search Landing Page protocol
+ */
+function shouldUseProtocolV1(objectGraph) {
+ if (objectGraph.client.isVision) {
+ return false; // visionOS always uses the modern V2 protocol.
+ }
+ if (objectGraph.client.isWeb) {
+ return false; // the "web" expects to use the V2 protocol as well
+ }
+ if (!objectGraph.bag.supportsSearchLandingPageV2) {
+ return true; // Use V1 protocol when V2 is unsupported
+ }
+ // Use V1 protocol based on the bags rollout rate for V2 protocol.
+ return !isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.searchLandingPageV2RolloutRate);
+}
+async function fetchSearchLandingPage(objectGraph, fetchAds) {
+ if (shouldUseProtocolV1(objectGraph)) {
+ return await fetchSearchLandingPageV1(objectGraph, fetchAds);
+ }
+ if (objectGraph.bag.mediaAPISearchFocusEnabled) {
+ return await fetchSearchLandingPageV2WithFocusPage(objectGraph, fetchAds);
+ }
+ return await fetchSearchLandingPageV2(objectGraph, fetchAds);
+}
+async function fetchSearchLandingPageV1(objectGraph, fetchAds) {
+ const searchLandingRequest = new mediaDataFetching.Request(objectGraph)
+ .forType("landing")
+ .includingAgeRestrictions()
+ .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph)); // for `extend=customArtwork`
+ searchLandingRequest.targetResourceType = "groupings";
+ const cohortIdOrNil = searchLandingCohort.cohortIdForUser(objectGraph, objectGraph.user.dsid);
+ if ((cohortIdOrNil === null || cohortIdOrNil === void 0 ? void 0 : cohortIdOrNil.length) > 0) {
+ searchLandingRequest.addingQuery("clusterId", cohortIdOrNil);
+ }
+ const fetchTimingMetricsBuilder = new FetchTimingMetricsBuilder();
+ const modifiedObjectGraph = objectGraph.addingFetchTimingMetricsBuilder(fetchTimingMetricsBuilder);
+ const fetchSearchLanding = mediaNetwork.fetchData(modifiedObjectGraph, searchLandingRequest);
+ return await Promise.all([fetchSearchLanding, fetchAds]).then(([responseData, adResponse]) => {
+ return fetchTimingMetricsBuilder.measureModelConstruction(() => {
+ return landingPageFromResponseV1(modifiedObjectGraph, responseData, adResponse);
+ });
+ });
+}
+/**
+ * Creates `SearchLandingPage` model from the V1 Search Landing Page protocol
+ * @param objectGraph The App Store Object Graph
+ * @param landingPageResponse The response from the fetch
+ * @param adResponse The response from the ad fetch
+ * @returns A `SearchLandingPage` model from the V1 Search Landing Page protocol
+ */
+function landingPageFromResponseV1(objectGraph, landingPageResponse, adResponse) {
+ const mediaApiGroupingDataArray = serverData.asArrayOrEmpty(landingPageResponse, "results.contents");
+ const mediaApiGroupingData = mediaApiGroupingDataArray[0];
+ if (serverData.isNullOrEmpty(mediaApiGroupingData)) {
+ return null;
+ }
+ if (!mediaRelationship.hasRelationship(mediaApiGroupingData, "tabs")) {
+ return null;
+ }
+ const groupingGenreAdamId = mediaAttributes.attributeAsString(mediaApiGroupingData, "id");
+ const pageInformation = metricsHelpersPage.metricsPageInformationFromMediaApiResponse(objectGraph, "Genre", mediaApiGroupingData.id, landingPageResponse);
+ const onDevicePersonalizationMetricsData = onDevicePersonalization.metricsData(objectGraph);
+ pageInformation.recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(pageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData);
+ pageInformation.iAdInfo = iadInfoFromOnDeviceAdResponse(objectGraph, "searchLanding", adResponse);
+ const adIncidentRecorder = adIncidents.newRecorder(objectGraph, pageInformation.iAdInfo);
+ adIncidents.recordAdResponseEventsIfNeeded(objectGraph, adIncidentRecorder, adResponse);
+ const groupingParseContext = {
+ shelves: [],
+ metricsPageInformation: pageInformation,
+ metricsLocationTracker: metricsHelpersLocation.newLocationTracker(),
+ pageGenreAdamId: groupingGenreAdamId,
+ pageGenreId: mediaAttributes.attributeAsNumber(mediaApiGroupingData, "genre"),
+ hasAuthenticatedUser: serverData.isDefinedNonNull(objectGraph.user.dsid),
+ isSearchLandingPage: true,
+ adStitcher: landingAdStitch.adStitcherForOnDeviceSLPAdvertData(objectGraph, adResponse),
+ adIncidentRecorder: adIncidentRecorder,
+ };
+ const flattenedGrouping = groupingCommon.flattenMediaApiGroupingData(objectGraph, mediaApiGroupingData);
+ groupingCommon.insertInitialShelvesIntoGroupingParseContext(objectGraph, flattenedGrouping, groupingParseContext);
+ const page = new SearchLandingPage(groupingParseContext.shelves);
+ // Page refresh
+ const refreshPolicy = new PageRefreshPolicy("timeSinceOnScreen", objectGraph.bag.searchLandingPageRefreshUpdateDelayInterval, objectGraph.bag.searchLandingPageOffscreenRefreshInterval, null);
+ page.pageRefreshPolicy = refreshPolicy;
+ // Ad Incidents
+ page.adIncidents = adIncidents.recordedIncidents(objectGraph, groupingParseContext.adIncidentRecorder);
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, groupingParseContext.metricsPageInformation);
+ return page;
+}
+function makeSearchLandingRequestV2(objectGraph, fetchAds) {
+ const searchLandingRequest = new mediaDataFetching.Request(objectGraph)
+ .forType("landing:new-protocol")
+ .includingAgeRestrictions()
+ .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph)) // for `extend=customArtwork`
+ .includingScopedRelationships("search-recommendations", ["contents"])
+ .addingQuery("name", "search-landing");
+ if (areAppTagsEnabled(objectGraph, "slp")) {
+ mediaRequestUtils.configureTagsForMediaRequest(searchLandingRequest);
+ }
+ if (objectGraph.client.isVision || objectGraph.client.isWeb) {
+ searchLandingRequest.includingScopedAttributes("editorial-items", ["editorialClientParams"]);
+ }
+ const cohortIdOrNil = searchLandingCohort.cohortIdForUser(objectGraph, objectGraph.user.dsid);
+ if ((cohortIdOrNil === null || cohortIdOrNil === void 0 ? void 0 : cohortIdOrNil.length) > 0) {
+ searchLandingRequest.addingQuery("clusterId", cohortIdOrNil);
+ }
+ if (objectGraph.client.isiOS) {
+ searchLandingRequest.addingQuery("meta", "adDisplayStyle");
+ }
+ return searchLandingRequest;
+}
+async function fetchSearchLandingPageV2(objectGraph, fetchAds) {
+ const searchLandingRequest = makeSearchLandingRequestV2(objectGraph, fetchAds);
+ const fetchTimingMetricsBuilder = new FetchTimingMetricsBuilder();
+ const modifiedObjectGraph = objectGraph.addingFetchTimingMetricsBuilder(fetchTimingMetricsBuilder);
+ const fetchSearchLanding = mediaNetwork.fetchData(modifiedObjectGraph, searchLandingRequest);
+ const amsEngagement = objectGraph.amsEngagement;
+ let amdPromise;
+ if (amsEngagement && objectGraph.bag.enableRecoOnDeviceReordering) {
+ const request = {
+ timeout: 500,
+ eventType: impressionDemotion.AMSEngagementAppStoreEventKey,
+ tab: "search",
+ };
+ amdPromise = amsEngagement.performRequest(request);
+ }
+ return await Promise.all([fetchSearchLanding, fetchAds, amdPromise]).then(([responseData, adResponse, amdResponse]) => {
+ return fetchTimingMetricsBuilder.measureModelConstruction(() => {
+ return landingPageFromResponseV2(modifiedObjectGraph, responseData, adResponse, amdResponse);
+ });
+ });
+}
+/**
+ * Creates `SearchLandingPage` model from the V2 Search Landing Page protocol
+ * @param objectGraph The App Store Object Graph
+ * @param landingPageResponse The response from the fetch
+ * @param adResponse The response from the ad fetch
+ * @returns A `SearchLandingPage` model from the V2 Search Landing Page protocol
+ */
+function landingPageFromResponseV2(objectGraph, landingPageResponse, adResponse, impressionData) {
+ if (serverData.isNullOrEmpty(landingPageResponse.data)) {
+ return null;
+ }
+ // Creates the page info
+ const pageInformation = metricsHelpersPage.metricsPageInformationFromMediaApiResponse(objectGraph, "SearchLanding", "SearchLanding", landingPageResponse);
+ // Decorate page info with personalization metrics
+ const onDevicePersonalizationMetricsData = onDevicePersonalization.metricsData(objectGraph);
+ pageInformation.recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(pageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData);
+ pageInformation.iAdInfo = iadInfoFromOnDeviceAdResponse(objectGraph, "searchLanding", adResponse);
+ const adIncidentRecorder = adIncidents.newRecorder(objectGraph, pageInformation.iAdInfo);
+ adIncidents.recordAdResponseEventsIfNeeded(objectGraph, adIncidentRecorder, adResponse);
+ // Creates Search Landing Page Context
+ const landingPageContext = {
+ shelves: [],
+ metricsLocationTracker: metricsHelpersLocation.newLocationTracker(),
+ metricsPageInformation: pageInformation,
+ adStitcher: landingAdStitch.adStitcherForOnDeviceSLPAdvertData(objectGraph, adResponse, landingPageResponse),
+ adIncidentRecorder: adIncidentRecorder,
+ pageType: SearchPageType.Landing,
+ recoImpressionData: impressionDemotion.impressionEventsFromData(objectGraph, impressionData),
+ };
+ // Create the shelves for the page
+ searchLandingShelfController.insertShelvesIntoSearchPageContext(objectGraph, landingPageResponse, landingPageContext);
+ // Add Unified Messaging placement to top of page for NLS BT.
+ const bubbleTipShelf = createNaturalLanguageSearchBubbleTipShelf(objectGraph);
+ if (bubbleTipShelf) {
+ landingPageContext.shelves.unshift(bubbleTipShelf);
+ }
+ const landingPage = new SearchLandingPage(landingPageContext.shelves);
+ // Page refresh
+ landingPage.pageRefreshPolicy = new PageRefreshPolicy("timeSinceOnScreen", objectGraph.bag.searchLandingPageRefreshUpdateDelayInterval, objectGraph.bag.searchLandingPageOffscreenRefreshInterval, null);
+ // Ad Incidents
+ landingPage.adIncidents = adIncidents.recordedIncidents(objectGraph, landingPageContext.adIncidentRecorder);
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, landingPage, landingPageContext.metricsPageInformation);
+ return landingPage;
+}
+/**
+ * Creates the NLS BT shelf for SLP if enabled.
+ * @param objectGraph The app store object graph.
+ * @returns The shelf for the NLS BT shown on SLP, or undefined if bag has feature disabled.
+ */
+export function createNaturalLanguageSearchBubbleTipShelf(objectGraph) {
+ var _a;
+ if (!objectGraph.bag.isNaturalLanguageSearchEnabled && !objectGraph.bag.isNaturalLanguageSearchResultsEnabled) {
+ return undefined; // feature not enabled in the bag
+ }
+ const context = {
+ signal: {
+ lastNLSQueryDate: objectGraph.storage.retrieveString("lastNLSQueryDate"),
+ treatmentId: (_a = appStoreExperiments.currentTreatmentIdForArea(objectGraph, ExperimentAreaId.SearchLandingPage)) !== null && _a !== void 0 ? _a : null,
+ },
+ };
+ const shelf = groupingCommon.shelfForUnifiedMessage(objectGraph, "searchFocusHeader", context, "pullOnly");
+ shelf.refreshUrl = `${Protocol.internal}:/${Path.searchLandingPage}/${Path.shelf}/?${Parameters.isSearchFocusHeaderShelf}=true`;
+ return shelf;
+}
+async function fetchSearchLandingPageV2WithFocusPage(objectGraph, fetchAds) {
+ const searchLandingRequest = makeSearchLandingRequestV2(objectGraph, fetchAds).enablingFeature("search-focus-suggestions");
+ const fetchTimingMetricsBuilder = new FetchTimingMetricsBuilder();
+ const modifiedObjectGraph = objectGraph.addingFetchTimingMetricsBuilder(fetchTimingMetricsBuilder);
+ const fetchSearchLanding = mediaNetwork.fetchData(modifiedObjectGraph, searchLandingRequest);
+ const amsEngagement = objectGraph.amsEngagement;
+ let amdPromise = null;
+ if (amsEngagement && objectGraph.bag.enableRecoOnDeviceReordering) {
+ const request = {
+ timeout: 500,
+ eventType: impressionDemotion.AMSEngagementAppStoreEventKey,
+ tab: "search",
+ };
+ amdPromise = amsEngagement.performRequest(request);
+ }
+ return await Promise.all([fetchSearchLanding, fetchAds, amdPromise]).then(async ([responseData, adResponse, amdResponse]) => {
+ return await fetchTimingMetricsBuilder.measureModelConstructionAsync(async () => await landingPageFromResponseV2WithFocusPage(modifiedObjectGraph, responseData, adResponse, amdResponse));
+ });
+}
+/**
+ * Creates `SearchLandingPage` model from the V2 Search Landing Page protocol, but with search-focus feature enabled.
+ * @param objectGraph The App Store Object Graph
+ * @param landingPageResponse The response from the landing fetch
+ * @param landingAdResponse The response from the landingAd fetch
+ * @returns A `SearchLandingPage` model created from server response.
+ */
+async function landingPageFromResponseV2WithFocusPage(objectGraph, landingPageResponse, landingAdResponse, impressionData) {
+ // MAINTAINER'S NOTE: V3 protocol does not change any existing SLP v2 fields and is purely additives for focus page support
+ const landingPage = landingPageFromResponseV2(objectGraph, landingPageResponse, landingAdResponse, impressionData);
+ // Create Search Focus Page
+ return await fetchFocusPageUsingLandingPageResponse(objectGraph, landingPageResponse).then((focusPage) => {
+ landingPage.searchFocusPage = focusPage;
+ return landingPage;
+ });
+}
+/**
+ * Creates `SearchFocusPage` model from the V3 Search Landing Page protocol, fetching search history if needed.
+ * @param objectGraph The App Store Object Graph
+ * @param focusPageResponse The response from the fetch
+ * @returns A `SearchFocusPage` model from the V3 Search Landing Page protocol
+ */
+async function fetchFocusPageUsingLandingPageResponse(objectGraph, landingPageResponse) {
+ var _a;
+ if (serverData.isNullOrEmpty(landingPageResponse.data)) {
+ return null;
+ }
+ // Creates the page info
+ const pageInformation = metricsHelpersPage.metricsPageInformationFromMediaApiResponse(objectGraph, "SearchFocus", "Focus", landingPageResponse, " ");
+ // Decorate page info with personalization metrics
+ const onDevicePersonalizationMetricsData = onDevicePersonalization.metricsData(objectGraph);
+ pageInformation.recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(pageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData);
+ // Creates Search Focus Page Context
+ const focusPageContext = {
+ shelves: [],
+ metricsLocationTracker: metricsHelpersLocation.newLocationTracker(),
+ metricsPageInformation: pageInformation,
+ pageType: SearchPageType.Focus,
+ };
+ const searchHistoryShelfMarker = searchLandingShelfController.firstShelfMarkerMatchingUseCase(landingPageResponse, focusPageContext, "recentSearches");
+ // Skip fetching search history if there isn't a marker for it in SLP response.
+ if (isNothing(searchHistoryShelfMarker)) {
+ return createFocusPageFromResponse(objectGraph, landingPageResponse, focusPageContext);
+ }
+ const searchHistoryDisplayCount = (_a = mediaAttributes.attributeAsNumber(searchHistoryShelfMarker, "displayCount")) !== null && _a !== void 0 ? _a : 0;
+ const fetchSearchHistory = objectGraph.onDeviceSearchHistoryManager.fetchRecentsWithLimit(searchHistoryDisplayCount);
+ return await fetchSearchHistory.then((searchHistory) => {
+ focusPageContext.searchHistory = searchHistory;
+ return createFocusPageFromResponse(objectGraph, landingPageResponse, focusPageContext);
+ });
+}
+function createFocusPageFromResponse(objectGraph, landingPageResponse, focusPageContext) {
+ // Create the shelves for the focus page using same logic as landing page, but
+ // includes search history in page context to support `search-recommendations-marker`.
+ searchLandingShelfController.insertShelvesIntoSearchPageContext(objectGraph, landingPageResponse, focusPageContext);
+ const focusPage = new SearchFocusPage(focusPageContext.shelves);
+ if (serverData.isNullOrEmpty(focusPage.shelves)) {
+ return null;
+ }
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, focusPage, focusPageContext.metricsPageInformation);
+ return focusPage;
+}
+async function fetchTrendingSearchesFallbackPage(objectGraph, fetchAds) {
+ const fetchRequest = {
+ url: objectGraph.bag.trendingSearchesURL,
+ };
+ const trendingSearchesPromise = objectGraph.network.fetch(fetchRequest).then((response) => {
+ if (!response.ok) {
+ throw Error(`Bad Status code ${response.status} for ${fetchRequest.url}`);
+ }
+ return JSON.parse(response.body);
+ });
+ return await Promise.all([trendingSearchesPromise, fetchAds]).then(([trendingSearchesData, adResponse]) => {
+ var _a;
+ const page = new SearchLandingPage(trendingSearchesShelvesForResponse(objectGraph, trendingSearchesData));
+ const pageInformation = metricsHelpersPage.fakeMetricsPageInformation(objectGraph, "SearchLanding", "trending", ""); // old trending endpoint doesn't have metrics meta
+ pageInformation.iAdInfo = iadInfoFromOnDeviceAdResponse(objectGraph, "searchLanding", adResponse);
+ (_a = pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.setMissedOpportunity(objectGraph, "SLPLOAD", "searchLanding"); // trending fallback never displays ad, so is always missed opportunity.
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation);
+ return page;
+ });
+}
+/**
+ * Creates a trending searches shelves from the given JSON response.
+ * @param objectGraph The App Store Object Graph.
+ * @param response The API response JSON data.
+ * @return {Shelf[]} Trending searches shelves created from response.
+ */
+function trendingSearchesShelvesForResponse(objectGraph, response) {
+ return validation.context("trendingSearchesShelfForResponse", () => {
+ const locationTracker = metricsHelpersLocation.newLocationTracker();
+ const searches = serverData.asArrayOrEmpty(response, "trendingSearches").map((rawSearch) => {
+ const term = serverData.asString(rawSearch, "label");
+ const searchAction = new SearchAction(term, term, serverData.asString(rawSearch, "url"), "trending");
+ if (objectGraph.client.isPhone) {
+ searchAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://magnifyingglass");
+ }
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", locationTracker);
+ metricsHelpersLocation.nextPosition(locationTracker);
+ return searchAction;
+ });
+ let maxNumberOfSearches = 0;
+ switch (objectGraph.client.deviceType) {
+ case "pad":
+ maxNumberOfSearches = 10;
+ break;
+ case "phone":
+ maxNumberOfSearches = 7;
+ break;
+ default:
+ break;
+ }
+ const shelf = new Shelf("action");
+ shelf.title = searches.length > 0 ? serverData.asString(response, "header.label") : null;
+ shelf.isHorizontal = false;
+ shelf.items = searches.slice(0, maxNumberOfSearches);
+ return [shelf];
+ });
+}
+export async function fetchPage(objectGraph) {
+ const fetchAds = isAdPlacementEnabled(objectGraph, "searchLanding")
+ ? landingAdFetchFetchAds(objectGraph, "searchLanding").catch(() => null)
+ : null;
+ return await fetchSearchLandingPage(objectGraph, fetchAds).catch(async (e) => {
+ // If the client has provided a `trendingSearchesURL`, we can fallback to that search
+ // mechanism if the search landing request fails. If `trendingSearchesURL` is not
+ // provided, we re-throw the original landing page error for the client to handle.
+ if (isSome(objectGraph.bag.trendingSearchesURL)) {
+ return await fetchTrendingSearchesFallbackPage(objectGraph, fetchAds);
+ }
+ else {
+ throw e;
+ }
+ });
+}
+//# sourceMappingURL=search-landing-page-utils.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-page-url.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-page-url.js
new file mode 100644
index 0000000..9462f94
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-page-url.js
@@ -0,0 +1,5 @@
+import { generateRoutes } from "../util/generate-routes";
+import { makeSearchResultsPageIntentFromURLParams } from "../../api/intents/search-results-page-intent";
+const { routes: searchResultsPageRoutes, makeCanonicalUrl: makeCanonicalSearchResultsPageUrl } = generateRoutes(makeSearchResultsPageIntentFromURLParams, "/{platform}/search", ["term"]);
+export { searchResultsPageRoutes, makeCanonicalSearchResultsPageUrl };
+//# sourceMappingURL=search-page-url.js.map \ No newline at end of file
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
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-learn-more-notice.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-learn-more-notice.js
new file mode 100644
index 0000000..8c2e741
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-learn-more-notice.js
@@ -0,0 +1,46 @@
+/**
+ * Build methods for Search Transparency.
+ * Search transparency provides additional disclosure message about search ranking required in certain storefronts.
+ */
+import { isNothing } from "@jet/environment";
+import { FlowAction, LinkableText, StyledText } from "../../api/models";
+import { addClickEventToAction } from "../metrics/helpers/clicks";
+import { makeRoutableArticlePageCanonicalUrl } from "../today/routable-article-page-url-utils";
+import { makeRoutableArticlePageIntent } from "../../api/intents/routable-article-page-intent";
+import { getPlatform } from "../preview-platform";
+import { getLocale } from "../locale";
+export function createSearchResultsLearnMoreNoticeLinkableText(objectGraph, metricsOptions) {
+ const editorialItemId = objectGraph.bag.searchResultsLearnMoreEditorialId;
+ if (isNothing(editorialItemId) || (editorialItemId === null || editorialItemId === void 0 ? void 0 : editorialItemId.length) === 0) {
+ return undefined;
+ }
+ const clickOptions = {
+ kind: "editorialItem",
+ softwareType: null,
+ title: objectGraph.loc.string("SEARCH_TRANSPARENCY_LINK"),
+ id: editorialItemId,
+ targetType: "link",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ };
+ const text = objectGraph.loc.string("SEARCH_TRANSPARENCY_TEXT");
+ const learnMoreAction = new FlowAction("article");
+ learnMoreAction.title = text;
+ learnMoreAction.pageUrl = `https://apps.apple.com/story/id${editorialItemId}`;
+ if (objectGraph.client.isWeb) {
+ const destination = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: editorialItemId,
+ });
+ const pageUrlString = makeRoutableArticlePageCanonicalUrl(objectGraph, destination);
+ learnMoreAction.pageUrl = pageUrlString;
+ learnMoreAction.destination = destination;
+ }
+ addClickEventToAction(objectGraph, learnMoreAction, clickOptions);
+ const linkSubstrings = {};
+ linkSubstrings[`${objectGraph.loc.string("SEARCH_TRANSPARENCY_LINK")}`] = learnMoreAction;
+ const styledText = new StyledText(text, "text/plain");
+ return new LinkableText(styledText, linkSubstrings);
+}
+//# sourceMappingURL=search-results-learn-more-notice.js.map \ No newline at end of file
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
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-spell-correction.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-spell-correction.js
new file mode 100644
index 0000000..60466dd
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-spell-correction.js
@@ -0,0 +1,98 @@
+/**
+ * Build methods for Search Spell Correction.
+ * Spell correction allows users to change an previous search term to higher or lower confidence searches.
+ */
+import * as models from "../../api/models";
+import { isDefinedNonNull } from "../../foundation/json-parsing/server-data";
+import { addEventsToSearchAction } from "../metrics/helpers/clicks";
+import { popLocation, pushBasicLocation } from "../metrics/helpers/location";
+/**
+ * Build a `SearchResultsMessage` for a search result with given `SearchTermContext`
+ * @param termContext Context for the state of search terms.
+ */
+export function spellCorrectionMessageFromTermContext(objectGraph, termContext, metricsOptions) {
+ const showUndoSpellCorrection = isDefinedNonNull(termContext.correctedTerm);
+ const showSuggestion = isDefinedNonNull(termContext.suggestedTerm);
+ pushBasicLocation(objectGraph, {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "link",
+ }, "spellCorrection");
+ if (showUndoSpellCorrection) {
+ return undoSpellCorrectionMessageFromTermContext(objectGraph, termContext, metricsOptions);
+ }
+ if (showSuggestion) {
+ return correctionSuggestionMessageFromTermContext(objectGraph, termContext, metricsOptions);
+ }
+ popLocation(metricsOptions.locationTracker);
+ return null;
+}
+/**
+ * Build a message that allows user to undo an auto-applied spell correction on user-initiated term.
+ * This is the "Showing Results for ABC. Search Instead For XYZ" variation for high-confidence misspellings
+ * @param termContext Context for the state of search terms.
+ * @param locationTracker Location tracker for page it is appearing in.
+ */
+function undoSpellCorrectionMessageFromTermContext(objectGraph, termContext, metricsOptions) {
+ // SearchAction for `term` again, disabling spell correction to `correctedTerm`.
+ const uncorrectedTerm = termContext.term;
+ const searchInsteadAction = searchActionForSpellCorrection(objectGraph, uncorrectedTerm, termContext.resultsTerm, "undoSpellCorrection");
+ addEventsToSearchAction(objectGraph, searchInsteadAction, "button", metricsOptions.locationTracker);
+ // "Showing Results for <i>correctedTerm</i>" with no links.
+ const correctedToTerm = `<i>${termContext.correctedTerm}</i>`;
+ const showingResultsForTemplate = objectGraph.loc.string("SEARCH_SHOWING_RESULTS_FOR_TEMPLATE");
+ const showingResultsForMessage = showingResultsForTemplate.replace("{searchTerm}", correctedToTerm);
+ const primaryText = new models.LinkableText(new models.StyledText(showingResultsForMessage, "text/x-apple-as3-nqml"), {});
+ // "Search Instead for term" with link.
+ const searchInsteadForTemplate = objectGraph.loc.string("SEARCH_SEARCH_INSTEAD_FOR_TEMPLATE");
+ const searchInsteadForMessage = searchInsteadForTemplate.replace("{searchTerm}", uncorrectedTerm);
+ const searchInsteadForLinks = {};
+ searchInsteadForLinks[`${searchInsteadForMessage}`] = searchInsteadAction;
+ const secondaryText = new models.LinkableText(new models.StyledText(searchInsteadForMessage), searchInsteadForLinks);
+ return new models.SearchResultsMessage(primaryText, secondaryText, searchInsteadAction);
+}
+/**
+ * Build a message that allows user to accept a suggestion to modify a user-initiated term.
+ * This is the "Did you mean ABC?" variation for low-confidence misspellings
+ * @param termContext Context for the state of search terms.
+ * @param locationTracker Location tracker for page it is appearing in.
+ */
+function correctionSuggestionMessageFromTermContext(objectGraph, termContext, metricsOptions) {
+ // Search action for `suggestedTerm`
+ const suggestedTerm = termContext.suggestedTerm;
+ const suggestedSearchAction = searchActionForSpellCorrection(objectGraph, suggestedTerm, termContext.resultsTerm, "applySpellCorrection");
+ addEventsToSearchAction(objectGraph, suggestedSearchAction, "button", metricsOptions.locationTracker);
+ // "Did you mean <i>suggestedTerm</i>?" where suggested term is linked.
+ const styledSuggestedTerm = `<i>${suggestedTerm}</i>`;
+ const didYouMeanTemplate = objectGraph.loc.string("SEARCH_DID_YOU_MEAN_TEMPLATE");
+ const didYouMeanMessage = didYouMeanTemplate.replace("{searchTerm}", styledSuggestedTerm);
+ // Link both the raw suggested term, and suggested term followed by ?
+ const didYouMeanLinks = {};
+ didYouMeanLinks[`${suggestedTerm}`] = suggestedSearchAction;
+ didYouMeanLinks[`${suggestedTerm}?`] = suggestedSearchAction;
+ const primaryText = new models.LinkableText(new models.StyledText(didYouMeanMessage, "text/x-apple-as3-nqml"), didYouMeanLinks);
+ return new models.SearchResultsMessage(primaryText, null, suggestedSearchAction);
+}
+// endregion
+// region Search Action Builders
+/**
+ * Build a SearchAction that is for:
+ * - Applying a suggested spell correction.
+ * - Undoing an automatically applied spell correction.
+ *
+ * Exported for testing
+ *
+ * @param suggestedOrUncorrectedTerm The suggestion or uncorrected term to search for.
+ * @param resultTerm The original result term.
+ * @param spellCorrectionActionType The type of spell correction search.
+ */
+export function searchActionForSpellCorrection(objectGraph, suggestedOrUncorrectedTerm, resultsTerm, spellCorrectionActionType) {
+ // SearchAction for `term` again, disabling spell correction to `correctedTerm`.
+ const suggestedSearchAction = new models.SearchAction(suggestedOrUncorrectedTerm, suggestedOrUncorrectedTerm, null, spellCorrectionActionType);
+ suggestedSearchAction.spellCheckEnabled = false; // Don't trigger corrections / suggestions again.
+ suggestedSearchAction.excludedTerms = [resultsTerm];
+ suggestedSearchAction.originatingTerm = resultsTerm;
+ return suggestedSearchAction;
+}
+// endregion
+//# sourceMappingURL=search-spell-correction.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-token.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-token.js
new file mode 100644
index 0000000..f49b680
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-token.js
@@ -0,0 +1,57 @@
+import { isNullOrEmpty } from "../../foundation/json-parsing/server-data";
+import { currentPosition } from "../metrics/helpers/location";
+/**
+ * Opaque token to use for paginating a list of search results.
+ * This is used for both standard and segmented search results.
+ */
+export class SearchToken {
+}
+/// The number of results to load per page.
+const suggestedMaxResutsPerPage = 30;
+/**
+ * Create a search token for loading more search results.
+ * @param results Remaining set of data that is yet to be paginated
+ * @param requestMetadata The nature of request that was fired.
+ * @param responseMetadata The meta blob returned as part of initial search. This is preserved, as subsequent pagination requests don't hit search endpoints.
+ * @param metricsOptions Metrics options to preserve during pagination
+ */
+export function createSearchToken(objectGraph, results, requestMetadata, responseMetadata, metricsOptions) {
+ if (isNullOrEmpty(results)) {
+ return null;
+ }
+ return {
+ results: results,
+ maxPerPage: suggestedMaxResutsPerPage,
+ requestMetadata: requestMetadata,
+ metricsOptions: metricsOptions,
+ responseMetadata: responseMetadata !== null && responseMetadata !== void 0 ? responseMetadata : {},
+ contentOffsetWithinResultsShelf: currentPosition(metricsOptions.locationTracker),
+ };
+}
+/**
+ * Returns the next set of items to load.
+ * @param searchToken Search token used for paginating.
+ */
+export function getNextItemsToFetch(objectGraph, searchToken) {
+ if (!searchToken || !searchToken.results) {
+ return [];
+ }
+ return searchToken.results.slice(0, searchToken.maxPerPage);
+}
+/**
+ * Advance the search token by the items that were loaded, consistent with `getNextItemsToFetch`
+ * @param searchToken Search token to create new token with.
+ */
+export function advanceSearchTokenResults(objectGraph, searchToken) {
+ let nextPageResults = [];
+ if (searchToken && searchToken.results) {
+ nextPageResults = searchToken.results.slice(searchToken.maxPerPage, searchToken.results.length);
+ }
+ if (isNullOrEmpty(nextPageResults)) {
+ return null;
+ }
+ const nextToken = { ...searchToken };
+ nextToken.results = nextPageResults;
+ return nextToken;
+}
+//# sourceMappingURL=search-token.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search.js
new file mode 100644
index 0000000..aec35e0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search.js
@@ -0,0 +1,1024 @@
+//
+// search.ts
+// AppStoreKit
+//
+// Created by Kevin MacWhinnie on 8/15/16.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import { isNothing, isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import { FetchTimingMetricsBuilder } from "@jet/environment/metrics/fetch-timing-metrics-builder";
+import { PageInvocationPoint } from "@jet/environment/types/metrics";
+import * as models from "../../api/models";
+import { SearchResultsLearnMoreNotice } from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import * as contentArtwork from "../content/artwork/artwork";
+import * as metricsBuilder from "../metrics/builder";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersMisc from "../metrics/helpers/misc";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import * as guidedSearch from "./guided-search/guided-search";
+import { addGuidedSearchParentImpressionMetrics, addSearchResultParentImpressionMetrics, } from "./guided-search/guided-search-metrics";
+import * as searchAdsODML from "./search-ads-odml";
+import * as searchCommon from "./search-common";
+import { createDefaultSelectedFacetOptions, createSearchFacets, createSearchPageFacets } from "./search-facets";
+import * as searchResultsFetching from "./search-results-fetching";
+import { createSearchResultsLearnMoreNoticeLinkableText } from "./search-results-learn-more-notice";
+import * as searchResultsPipeline from "./search-results-pipeline";
+import * as searchSpellCorrection from "./search-spell-correction";
+import * as searchToken from "./search-token";
+// MARK: - Search Hints
+/**
+ * Convert the response from the search hints endpoint into a model object.
+ * @param {String} prefixTerm The term the search hints are for (i.e. searchPrefix).
+ * @param {String} searchUrl The Url if we were to search for this term right away
+ * @param {*} response The API response.
+ * @return {SearchHintSet} The search hint set containing the search hint array
+ */
+export function searchHintsFromApiResponse(objectGraph, prefixTerm, hintsContainer) {
+ return validation.context("searchHintsFromApiResponse", () => {
+ var _a, _b, _c, _d;
+ const metricsOptions = {
+ targetType: "listItem",
+ pageInformation: metricsHelpersPage.pageInformationForSearchHintsPage(objectGraph, prefixTerm, hintsContainer.hintsRequestUrl, hintsContainer.dataSetId),
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ };
+ // Build user hint that matches what user typed. Appears in first position.
+ let userTypedHintAction = null;
+ if ((_a = hintsContainer.userTypedTerm) === null || _a === void 0 ? void 0 : _a.length) {
+ userTypedHintAction = new models.SearchAction(hintsContainer.userTypedTerm, hintsContainer.userTypedTerm, null, "userTypedHint");
+ userTypedHintAction.spellCheckEnabled = true;
+ userTypedHintAction.prefixTerm = prefixTerm;
+ metricsHelpersImpressions.addImpressionMetricsToHintsSearchAction(objectGraph, userTypedHintAction, metricsOptions);
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, userTypedHintAction, metricsOptions.targetType, metricsOptions.locationTracker, metricsOptions.pageInformation);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ }
+ // Build standard hints.
+ const searchHintActions = (_c = (_b = hintsContainer.rawHints) === null || _b === void 0 ? void 0 : _b.map((rawHint) => {
+ return searchHintAction(objectGraph, rawHint, prefixTerm, metricsOptions);
+ })) !== null && _c !== void 0 ? _c : [];
+ // Prepend user hint if any.
+ if (userTypedHintAction != null) {
+ searchHintActions.unshift(userTypedHintAction);
+ }
+ const hintSet = new models.SearchHintSet(searchHintActions, (_d = hintsContainer.ghostHintsTerm) !== null && _d !== void 0 ? _d : null);
+ /**
+ * Send `input` Search Events when search hints returns.
+ * For SSS, this is the granularity we agreed on, instead of sending it in native per keystroke.
+ * Unlike standard page metrics, we only:
+ * - Fire a 'input' search event
+ * - Setup pageFields for page fields generator.
+ */
+ const searchEvent = metricsBuilder.createMetricsSearchData(objectGraph, prefixTerm, "key", "input", hintsContainer.hintsRequestUrl, { ...metricsHelpersMisc.fieldsFromPageInformation(metricsOptions.pageInformation) });
+ hintSet.pageMetrics.pageFields = metricsHelpersMisc.fieldsFromPageInformation(metricsOptions.pageInformation);
+ hintSet.pageMetrics.addData(searchEvent, [PageInvocationPoint.pageEnter]);
+ hintSet.searchClearAction = createSearchCancelledOrClearedAction(objectGraph, "clear", metricsOptions.pageInformation, metricsOptions.locationTracker, prefixTerm);
+ hintSet.searchCancelAction = createSearchCancelledOrClearedAction(objectGraph, "cancel", metricsOptions.pageInformation, metricsOptions.locationTracker, prefixTerm);
+ return hintSet;
+ });
+}
+/**
+ * Creates a search hint action from the search hint data
+ * @param objectGraph The App Store object graph
+ * @param hintData The hint object data
+ * @param prefixTerm The hint prefix term
+ * @param metricsOptions The metrics options to use for click and impressions metrics for this hint
+ * @returns A search hint action for the hint data
+ */
+function searchHintAction(objectGraph, hintData, prefixTerm, metricsOptions) {
+ var _a, _b, _c, _d, _e;
+ const searchEntity = (_a = searchEntityFromHintData(hintData)) !== null && _a !== void 0 ? _a : undefined;
+ const searchAction = new models.SearchAction((_b = hintData.displayTerm) !== null && _b !== void 0 ? _b : "", (_c = hintData.searchTerm) !== null && _c !== void 0 ? _c : "", null, "hints", searchEntity, hintData.source);
+ searchAction.artwork = contentArtwork.createArtworkForSystemImage(objectGraph, (_d = models.searchEntitySystemImage(searchEntity)) !== null && _d !== void 0 ? _d : "magnifyingglass");
+ searchAction.spellCheckEnabled = true;
+ searchAction.prefixTerm = prefixTerm;
+ metricsHelpersImpressions.addImpressionMetricsToHintsSearchAction(objectGraph, searchAction, metricsOptions);
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, metricsOptions.targetType, metricsOptions.locationTracker, (_e = metricsOptions.pageInformation) !== null && _e !== void 0 ? _e : undefined);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ return searchAction;
+}
+/**
+ *
+ * @param objectGraph The App Store object graph
+ * @param entity The entity value string from the hint object
+ * @param context The context value string from the hint object
+ * @returns The correct entity for the hint based on legacy entity types
+ *
+ * Entities:
+ * "arcade" ==> "apps" with context "arcade"
+ "developer" ==> "developers"
+ "story" ==> "editorial-items"
+ "watch" ==> "apps" with context "watch"
+ */
+function searchEntityFromHintData(rawHint) {
+ const hintEntity = rawHint.entity;
+ switch (hintEntity) {
+ case "apps":
+ return rawHint.context;
+ case "developers":
+ return "developer";
+ case "editorial-items":
+ return "story";
+ default:
+ return null;
+ }
+}
+// MARK: - Trending Searches
+/**
+ * Convert the response from the trending searches endpoint into a model object.
+ * @param {*} response The API response.
+ * @return {TrendingSearch} A trending searches model object.
+ */
+export function trendingSearchesFromApiResponse(objectGraph, response) {
+ return validation.context("trendingSearchesFromApiResponse", () => {
+ const locationTracker = metricsHelpersLocation.newLocationTracker();
+ const searches = serverData.asArrayOrEmpty(response, "trendingSearches").map((rawSearch) => {
+ const term = serverData.asString(rawSearch, "label");
+ const searchAction = new models.SearchAction(term, term, serverData.asString(rawSearch, "url"), "trending");
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", locationTracker);
+ metricsHelpersLocation.nextPosition(locationTracker);
+ return searchAction;
+ });
+ const title = searches.length > 0 ? serverData.asString(response, "header.label") : null;
+ const trendingSearches = new models.TrendingSearches(title, searches);
+ switch (objectGraph.client.deviceType) {
+ case "pad":
+ trendingSearches.maxNumberOfSearches = 10;
+ break;
+ case "phone":
+ trendingSearches.maxNumberOfSearches = 7;
+ break;
+ default:
+ break;
+ }
+ return trendingSearches;
+ });
+}
+// MARK: - Search Results
+/**
+ * Gets the shelf ID for search results, on a specfic segment if applicable
+ * @param segmentTitle The title of the segment this shelf is on, if using segmented search
+ * @returns The id for the search results shelf
+ * @note This needs to include the segment title for segment search results since the contents on
+ * each segment would otherwise have the same shelf id and therefore would lead to the content never changing
+ */
+function getSearchResultsShelfId(segmentTitle) {
+ if (isSome(segmentTitle) && segmentTitle.length !== 0) {
+ return `SearchResults.${segmentTitle}.shelfId`;
+ }
+ else {
+ return "SearchResults.shelfId";
+ }
+}
+/**
+ * Gets metrics pageid to use for the search results page.
+ * @param segmentType The segment this page is displaying if any
+ * @returns The id for the search results page
+ */
+function getSearchResultsPageId(segmentType) {
+ switch (segmentType) {
+ case models.SegmentedSearchResultsPageSegmentType.iOS:
+ return "ios";
+ case models.SegmentedSearchResultsPageSegmentType.visionOS:
+ return "visionos";
+ default:
+ return "SearchTopResults";
+ }
+}
+/**
+ * Creates a new empty `SearchResults` object.
+ * @return {SearchResults} An empty search results model object.
+ */
+export function emptyResults(objectGraph, requestFacets) {
+ const searchResults = new models.SearchResults();
+ if (serverData.isDefinedNonNull(requestFacets)) {
+ searchResults.facets = createSearchFacets(objectGraph, requestFacets);
+ searchResults.pageFacets = createSearchPageFacets(objectGraph);
+ searchResults.selectedFacetOptions = createDefaultSelectedFacetOptions(objectGraph);
+ }
+ searchResults.results = [];
+ return searchResults;
+}
+/**
+ * Creates a new empty `SearchResultsPage` object.
+ * @return {SearchResults} An empty search results model object.
+ */
+export function emptyResultsPage(objectGraph, requestFacets) {
+ const searchResultsPage = new models.SearchResultsPage([]);
+ if (serverData.isDefinedNonNull(requestFacets)) {
+ searchResultsPage.facets = createSearchFacets(objectGraph, requestFacets);
+ searchResultsPage.pageFacets = createSearchPageFacets(objectGraph);
+ searchResultsPage.selectedFacetOptions = createDefaultSelectedFacetOptions(objectGraph);
+ }
+ return searchResultsPage;
+}
+/**
+ * A container like class to manage a search results page segment and surrounding context
+ */
+class SegmentedSearchResultsPageSegmentContext {
+}
+/**
+ * Builds `BaseSearchPage` from data.
+ */
+async function baseSearchPageFromResponse(objectGraph, combinedSearchData) {
+ return await validation.context("searchResultsFromResponse", async () => {
+ var _a;
+ const fetchTimingMetricsBuilder = (_a = objectGraph.fetchTimingMetricsBuilder) !== null && _a !== void 0 ? _a : new FetchTimingMetricsBuilder();
+ const page = await fetchTimingMetricsBuilder.measureModelConstructionAsync(async () => {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
+ const response = combinedSearchData.catalogResponse;
+ const requestMetadata = combinedSearchData.requestMetadata;
+ const searchRequestUrl = requestMetadata.searchRequestUrl;
+ const sponsoredSearchRequestData = combinedSearchData.sponsoredSearchRequestData;
+ const guidedSearchData = response.results.guidedSearch;
+ // Term Context
+ const termContext = searchCommon.createTermContextForSpellcheckedSequentialResponse(objectGraph, requestMetadata.requestDescriptor, response);
+ // Metrics
+ const wasOdmlSuccessful = searchAdsODML.wasODMLSuccessful(objectGraph, combinedSearchData.sponsoredSearchAdvertData);
+ const metricsOptions = {
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ pageInformation: metricsHelpersPage.pageInformationForSearchPage(objectGraph, requestMetadata.requestDescriptor, response, termContext, searchRequestUrl, getSearchResultsPageId(), sponsoredSearchRequestData, wasOdmlSuccessful, guidedSearchData),
+ createUniqueImpressionId: true,
+ };
+ // Root container
+ const isShelfBasedSearch = objectGraph.featureFlags.isEnabled("shelves_2_0_search") ||
+ objectGraph.client.isiOS ||
+ objectGraph.client.isTV ||
+ objectGraph.client.isVision ||
+ objectGraph.client.isWeb;
+ const baseSearchPage = isShelfBasedSearch
+ ? new models.SearchResultsPage()
+ : new models.SearchResults();
+ // Guided Search Experimentation (pinned vs mid-scroll)
+ const guidedSearchPosition = searchResultsPipeline.guidedSearchPositionFromSearchResponseMeta(response.meta, objectGraph);
+ if (isNothing(guidedSearchPosition)) {
+ /**
+ * Guided Search
+ * - note: This is important to occur before `createSearchResults` since its impression index should be lower than the container for search results.
+ */
+ addModelsForGuidedSearch(objectGraph, baseSearchPage, requestMetadata, guidedSearchData === null || guidedSearchData === void 0 ? void 0 : guidedSearchData.facets, metricsOptions);
+ }
+ const shelfMetricsOptions = {
+ id: "search-results",
+ kind: null,
+ softwareType: null,
+ targetType: "SearchResults",
+ title: "Search Results",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ idType: "relationship",
+ };
+ let resultsShelf;
+ if (isShelfBasedSearch) {
+ resultsShelf = new models.Shelf("searchResult");
+ resultsShelf.id = getSearchResultsShelfId();
+ resultsShelf.isHorizontal = false;
+ if (objectGraph.client.isWeb) {
+ resultsShelf.title = objectGraph.loc
+ .string("Search.ResultsTitle")
+ .replace("@@search_term@@", termContext.term);
+ }
+ // We need to create and apply impressions fields to the shelf before creating the search results,
+ // so that the shelf is sitting in the right location relative to its children from an impressions perspective.
+ metricsHelpersImpressions.addImpressionFields(objectGraph, resultsShelf, shelfMetricsOptions);
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, "Search Results");
+ }
+ let advertData = dataForAdvertsFromCombinedData(objectGraph, combinedSearchData);
+ const appliedPolicy = (_a = combinedSearchData.sponsoredSearchAdvertData) === null || _a === void 0 ? void 0 : _a.appliedPolicy;
+ if (isSome(appliedPolicy)) {
+ // If there was an applied policy, suppress the ad.
+ advertData = [];
+ if (appliedPolicy === "ageRestricted") {
+ (_b = metricsOptions.pageInformation) === null || _b === void 0 ? void 0 : _b.iAdInfo.setMissedOpportunity(objectGraph, "ODP_NOAD", "searchResults");
+ }
+ }
+ const unsafeSearch = (_f = (_e = (_d = (_c = response.meta) === null || _c === void 0 ? void 0 : _c.results) === null || _d === void 0 ? void 0 : _d.search) === null || _e === void 0 ? void 0 : _e.searchSafety) !== null && _f !== void 0 ? _f : false;
+ const missedOpportunityReason = ((_k = (_j = (_h = (_g = response.meta) === null || _g === void 0 ? void 0 : _g.results) === null || _h === void 0 ? void 0 : _h.search) === null || _j === void 0 ? void 0 : _j.reason) === null || _k === void 0 ? void 0 : _k.kind) === "no-results" ? "NLS_NORESULTS" : "NLS_NOAD";
+ if (unsafeSearch) {
+ // If the search was deemed unsafe, suppress Ad and record the missed opportunity.
+ advertData = [];
+ (_l = metricsOptions.pageInformation) === null || _l === void 0 ? void 0 : _l.iAdInfo.setMissedOpportunity(objectGraph, missedOpportunityReason, "searchResults");
+ }
+ /**
+ * Advert + Search Results
+ */
+ const builderResults = await searchResultsPipeline.createSearchResults(objectGraph, requestMetadata, response.meta, metricsOptions, dataForSearchResultsFromCombinedData(objectGraph, combinedSearchData), advertData, guidedSearchData === null || guidedSearchData === void 0 ? void 0 : guidedSearchData.facets, installedStatesForAdvertsData(combinedSearchData), appStatesForAdvertsData(combinedSearchData));
+ if (unsafeSearch && builderResults.builtSearchResults.length !== 0) {
+ const searchAdOpportunity = builderResults.builtSearchResults[0].lockup
+ .searchAdOpportunity;
+ searchAdOpportunity === null || searchAdOpportunity === void 0 ? void 0 : searchAdOpportunity.setMissedOpportunityReason(missedOpportunityReason);
+ searchAdOpportunity === null || searchAdOpportunity === void 0 ? void 0 : searchAdOpportunity.setTemplateType("APPLOCKUP");
+ }
+ // Add result shelves to page.
+ if (isShelfBasedSearch && resultsShelf) {
+ const searchResultsPage = baseSearchPage;
+ resultsShelf.items = builderResults.builtSearchResults;
+ searchResultsPage.resultsParentImpressionMetrics = resultsShelf.impressionMetrics;
+ searchResultsPage.shelves.push(resultsShelf);
+ // Display the context message for the results (or lack thereof).
+ const contextCard = createSearchResultsContextCard(response.results.queryContext, objectGraph);
+ const resultsReason = (_p = (_o = (_m = response.meta) === null || _m === void 0 ? void 0 : _m.results) === null || _o === void 0 ? void 0 : _o.search) === null || _p === void 0 ? void 0 : _p.reason;
+ if ((resultsReason === null || resultsReason === void 0 ? void 0 : resultsReason.kind) === "no-results") {
+ searchResultsPage.unavailableReason = {
+ title: objectGraph.loc.stringWithFallback("Search.Results.Empty.Title", "No results"),
+ message: resultsReason.text,
+ action: actionFromSearchResultsLinks(resultsReason.links),
+ contextCard: contextCard,
+ };
+ }
+ else if (contextCard) {
+ const paragraphShelf = new models.Shelf("searchResultsContextCard");
+ paragraphShelf.items = [contextCard];
+ const contextCardPosition = (_t = (_s = (_r = (_q = response.meta) === null || _q === void 0 ? void 0 : _q.displayStyle) === null || _r === void 0 ? void 0 : _r.queryContext) === null || _s === void 0 ? void 0 : _s.position) !== null && _t !== void 0 ? _t : 0;
+ if (contextCardPosition > 0) {
+ const postCardShelfContents = resultsShelf.items.splice(contextCardPosition);
+ const postCardResulsShelf = {
+ ...resultsShelf,
+ id: "searchResults2",
+ items: postCardShelfContents,
+ isValid: resultsShelf.isValid,
+ };
+ searchResultsPage.shelves.push(paragraphShelf);
+ searchResultsPage.shelves.push(postCardResulsShelf);
+ }
+ else {
+ searchResultsPage.shelves.unshift(paragraphShelf);
+ }
+ }
+ // Remove unsafe searches from recents if no results were provided.
+ if (unsafeSearch && builderResults.builtSearchResults.length === 0) {
+ (_v = (_u = objectGraph.onDeviceSearchHistoryManager).removeRecentSearchTerm) === null || _v === void 0 ? void 0 : _v.call(_u, termContext.term);
+ }
+ }
+ else {
+ const searchResults = baseSearchPage;
+ searchResults.results = builderResults.builtSearchResults;
+ addSearchResultParentImpressionMetrics(objectGraph, searchResults, metricsOptions);
+ }
+ /**
+ * Next Page
+ */
+ if (builderResults.deferredSearchResults.length > 0) {
+ baseSearchPage.nextPage = searchToken.createSearchToken(objectGraph, builderResults.deferredSearchResults, requestMetadata, response.meta, metricsOptions);
+ }
+ if (isShelfBasedSearch) {
+ metricsHelpersLocation.popLocation(shelfMetricsOptions.locationTracker);
+ }
+ /**
+ * Spell Correction Message
+ */
+ baseSearchPage.message = searchSpellCorrection.spellCorrectionMessageFromTermContext(objectGraph, termContext, metricsOptions);
+ /**
+ * Search Entity
+ */
+ const searchEntity = requestMetadata.requestDescriptor.searchEntity;
+ const searchEntitySpecified = !serverData.isNullOrEmpty(searchEntity);
+ if (!searchEntitySpecified) {
+ baseSearchPage.facets = createSearchFacets(objectGraph, requestMetadata.requestDescriptor.facets, combinedSearchData.categoriesFilterData);
+ baseSearchPage.pageFacets = createSearchPageFacets(objectGraph, combinedSearchData.categoriesFilterData);
+ baseSearchPage.selectedFacetOptions = serverData.isDefinedNonNullNonEmpty(combinedSearchData.requestMetadata.requestDescriptor.selectedFacetOptions)
+ ? combinedSearchData.requestMetadata.requestDescriptor.selectedFacetOptions
+ : createDefaultSelectedFacetOptions(objectGraph);
+ }
+ // Attach search term context
+ baseSearchPage.searchTermContext = termContext;
+ // Enable autoplay search results on all clients except tv.
+ baseSearchPage.isAutoPlayEnabled = objectGraph.client.deviceType !== "tv";
+ baseSearchPage.isCondensedSearchLockupsEnabled = objectGraph.client.isPhone;
+ // Search Transparency
+ baseSearchPage.transparencyLink = createSearchResultsLearnMoreNoticeLinkableText(objectGraph, metricsOptions);
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, baseSearchPage, metricsOptions.pageInformation);
+ baseSearchPage.searchClearAction = createSearchCancelledOrClearedAction(objectGraph, "clear", metricsOptions.pageInformation, metricsOptions.locationTracker, termContext.term);
+ baseSearchPage.searchCancelAction = createSearchCancelledOrClearedAction(objectGraph, "cancel", metricsOptions.pageInformation, metricsOptions.locationTracker, termContext.term);
+ return baseSearchPage;
+ });
+ return page;
+ });
+}
+// Creates context card model for search results from media API data.
+export function createSearchResultsContextCard(queryContext, objectGraph) {
+ return validation.context("createSearchResultsContextCard", () => {
+ var _a, _b;
+ if (isNothing(queryContext) || !["iOS", "macOS"].includes(objectGraph.host.platform)) {
+ return undefined; // no server data or platform unsupported
+ }
+ const linkActions = (_a = queryContext.links) === null || _a === void 0 ? void 0 : _a.map(actionFromSearchResultsLink);
+ const linkedSubstrings = linkActions === null || linkActions === void 0 ? void 0 : linkActions.reduce((map, linkAction) => {
+ var _a;
+ if ((_a = linkAction.title) === null || _a === void 0 ? void 0 : _a.length) {
+ if (objectGraph.host.isMac) {
+ linkAction.title += "\u00a0\u2197";
+ }
+ map[linkAction.title] = linkAction;
+ }
+ return map;
+ }, {});
+ let rawText = queryContext.text;
+ if ((linkActions === null || linkActions === void 0 ? void 0 : linkActions.length) === 1 && ((_b = linkActions[0].title) === null || _b === void 0 ? void 0 : _b.length)) {
+ // Add single link to end of text, separated by a space.
+ rawText += " " + linkActions[0].title;
+ }
+ else if (linkActions && linkActions.length > 1) {
+ // Add multiple lines below end of text, separated by a new line.
+ if (rawText.length > 0) {
+ rawText += "\n";
+ }
+ rawText += linkActions
+ .map((action) => action.title)
+ .filter((title) => title === null || title === void 0 ? void 0 : title.length)
+ .join("\n");
+ }
+ const styledText = new models.StyledText(rawText);
+ const linkableText = new models.LinkableText(styledText, linkedSubstrings);
+ const contextCard = new models.SearchResultsContextCard(linkableText);
+ return contextCard;
+ });
+}
+/// Creates and returns the first url action from the provided link data included in the search results response.
+function actionFromSearchResultsLinks(linksData) {
+ return validation.context("actionFromSearchResultsLinks", () => {
+ const bestLinkData = linksData === null || linksData === void 0 ? void 0 : linksData.find((linkData) => linkData.url.length > 0);
+ return bestLinkData ? actionFromSearchResultsLink(bestLinkData) : undefined;
+ });
+}
+/// Creates and returns an action from the provided link data included in the search results response.
+function actionFromSearchResultsLink(linkData) {
+ return validation.context("actionFromSearchResultsLink", () => {
+ var _a;
+ const linkAction = new models.ExternalUrlAction(linkData.url, false);
+ linkAction.title = (_a = linkData.label) === null || _a === void 0 ? void 0 : _a.replace(" ", "\u00a0"); // Use non-breaking spaces for link labels.
+ linkAction.artwork = new models.Artwork("systemimage://arrow.up.forward", 0, 0, []);
+ return linkAction;
+ });
+}
+function installedStatesForAdvertsData(combinedSearchData) {
+ var _a, _b;
+ return (_b = (_a = combinedSearchData.sponsoredSearchAdvertData) === null || _a === void 0 ? void 0 : _a.installedStates) !== null && _b !== void 0 ? _b : {};
+}
+function appStatesForAdvertsData(combinedSearchData) {
+ var _a, _b;
+ return (_b = (_a = combinedSearchData.sponsoredSearchAdvertData) === null || _a === void 0 ? void 0 : _a.appStates) !== null && _b !== void 0 ? _b : {};
+}
+export async function searchResultsFromResponse(objectGraph, combinedSearchData) {
+ return await baseSearchPageFromResponse(objectGraph, combinedSearchData);
+}
+export async function searchResultsPageFromResponse(objectGraph, combinedSearchData) {
+ return await baseSearchPageFromResponse(objectGraph, combinedSearchData);
+}
+/**
+ * Creates a new empty `SegmentedSearchResultsPage` object.
+ * @return An empty segmented search results model object.
+ */
+export function emptySegmentedResultsPage(objectGraph) {
+ return new models.SegmentedSearchResultsPage();
+}
+/**
+ * Creates the segmented search results page form the response
+ * @param objectGraph The app store object graph
+ * @param combinedSearchData The combined data for the segmented search results response
+ * @returns A promise of the segmented search results page
+ */
+export async function segmentedSearchResultsPageFromResponse(objectGraph, combinedSearchData) {
+ return await validation.context("segmentedSearchResultsPageFromResponse", async () => {
+ const response = combinedSearchData.catalogResponse;
+ const requestMetadata = combinedSearchData.requestMetadata;
+ if (isSome(response.results.search.groups)) {
+ const fetchPages = response.results.search.groups.map(async (group) => {
+ const segmentResponse = await baseSegmentFromGroupResponse(objectGraph, combinedSearchData, response, group, requestMetadata);
+ return segmentResponse;
+ });
+ return await Promise.all(fetchPages).then((pageContexts) => {
+ const page = new models.SegmentedSearchResultsPage();
+ if (serverData.isNullOrEmpty(pageContexts)) {
+ return page;
+ }
+ const segments = pageContexts.map((context) => {
+ return context.segment;
+ });
+ page.segments = segments;
+ const initialSegmentId = response.results.search.autoSelectedGroupId || segments[0].groupId;
+ page.selectedSegmentId = initialSegmentId;
+ addSegmentChangeActionsToPages(objectGraph, pageContexts, initialSegmentId);
+ return page;
+ });
+ }
+ else {
+ return new models.SegmentedSearchResultsPage();
+ }
+ });
+}
+/**
+ * Adds all segment change actions to each search result page segment
+ * @param objectGraph The App Store object graph
+ * @param contexts The segment contexts for the page
+ * @param initialSegmentId The initial segment Id.
+ */
+function addSegmentChangeActionsToPages(objectGraph, contexts, initialSegmentId) {
+ const firstNonEmptySegmentContext = contexts.find((context) => {
+ const hasNonEmptyPage = context.segment.page.shelves.some((shelf) => {
+ const isNotEmpty = shelf.items.length > 0;
+ return isNotEmpty;
+ });
+ return hasNonEmptyPage;
+ });
+ const firstNonEmptySegmentId = firstNonEmptySegmentContext === null || firstNonEmptySegmentContext === void 0 ? void 0 : firstNonEmptySegmentContext.segment.groupId;
+ const segments = contexts.map((context) => {
+ return context.segment;
+ });
+ const nativeSegmentGroupId = segmentGroupIdForPlatform(objectGraph, segments);
+ // Check if the native segment has content.
+ const nativeSegmentIsNonEmpty = segments.some((segment) => {
+ if (segment.groupId !== nativeSegmentGroupId) {
+ return false;
+ }
+ return segment.page.shelves.some((shelf) => {
+ return shelf.items.length > 0;
+ });
+ });
+ contexts.forEach((context) => {
+ context.segment.emptySegmentChangeAction = segmentChangeAction(objectGraph, SegmentChangeReason.EmptyResults, context.segment.page.id, context.metricsOptions, firstNonEmptySegmentId, context.segment.groupId);
+ // Only add the non-native segment change action if the native segment has content, and
+ // the initial segment wasn't the native segment.
+ if (nativeSegmentIsNonEmpty && initialSegmentId !== nativeSegmentGroupId) {
+ context.segment.nonNativeSegmentChangeAction = segmentChangeAction(objectGraph, SegmentChangeReason.NonNative, context.segment.page.id, context.metricsOptions, nativeSegmentGroupId, context.segment.groupId);
+ }
+ context.segment.segmentPickerSegmentChangeAction = segmentChangeAction(objectGraph, SegmentChangeReason.Picker, context.segment.page.id, context.metricsOptions, context.segment.groupId);
+ });
+}
+/**
+ *
+ * @param objectGraph The app store object graph
+ * @param combinedSearchData The combined search results which contains the response for segmented results
+ * @param groupResponse The response for the segmented results
+ * @param searchData The data for a single search results segment
+ * @param requestMetadata The metadata from the search request
+ * @returns A promise of a segmented search results page segment
+ */
+async function baseSegmentFromGroupResponse(objectGraph, combinedSearchData, groupResponse, searchData, requestMetadata) {
+ return await validation.context("baseSearchPageFromGroupResponse", async () => {
+ const searchRequestUrl = requestMetadata.searchRequestUrl;
+ const segmentType = segmentTypeFromGroupId(searchData.groupId);
+ // Term Context
+ const termContext = searchCommon.createTermContextForSpellcheckedGroupedResponse(objectGraph, requestMetadata.requestDescriptor, groupResponse);
+ // Metrics
+ const metricsOptions = {
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ pageInformation: metricsHelpersPage.pageInformationForSearchPage(objectGraph, requestMetadata.requestDescriptor, groupResponse, termContext, searchRequestUrl, getSearchResultsPageId(segmentType), null, false, null),
+ createUniqueImpressionId: true,
+ };
+ const shelfMetricsOptions = {
+ id: "search-results",
+ kind: null,
+ softwareType: null,
+ targetType: "SearchResults",
+ title: "Search Results",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ idType: "relationship",
+ };
+ // Root container
+ const searchResultsPage = new models.SearchResultsPage();
+ const searchSegment = new models.SegmentedSearchResultsPageSegment();
+ const resultsShelf = new models.Shelf("searchResult");
+ resultsShelf.isHorizontal = false;
+ // We need to create and apply impressions fields to the shelf before creating the search results,
+ // so that the shelf is sitting in the right location relative to its children from an impressions perspective.
+ metricsHelpersImpressions.addImpressionFields(objectGraph, resultsShelf, shelfMetricsOptions);
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, "Search Results");
+ const resultsData = mediaDataStructure.dataCollectionFromDataContainer(searchData);
+ const builderResults = await searchResultsPipeline.createSearchResults(objectGraph, requestMetadata, groupResponse.meta, metricsOptions, resultsData);
+ const learnMoreNoticeLinkableText = createSearchResultsLearnMoreNoticeLinkableText(objectGraph, metricsOptions);
+ const shelves = searchResultsShelvesFromBuilderResults(objectGraph, learnMoreNoticeLinkableText, builderResults, resultsShelf);
+ searchResultsPage.shelves.push(...shelves);
+ searchResultsPage.resultsParentImpressionMetrics = resultsShelf.impressionMetrics;
+ if (builderResults.deferredSearchResults.length > 0) {
+ searchResultsPage.nextPage = searchToken.createSearchToken(objectGraph, builderResults.deferredSearchResults, requestMetadata, groupResponse.meta, metricsOptions);
+ }
+ metricsHelpersLocation.popLocation(shelfMetricsOptions.locationTracker);
+ /**
+ * Spell Correction Message
+ */
+ searchResultsPage.message = searchSpellCorrection.spellCorrectionMessageFromTermContext(objectGraph, termContext, metricsOptions);
+ // Enable autoplay search results on all clients except tv.
+ searchResultsPage.isAutoPlayEnabled = objectGraph.client.deviceType !== "tv";
+ searchResultsPage.isCondensedSearchLockupsEnabled = objectGraph.client.isPhone;
+ // Search Transparency
+ searchResultsPage.transparencyLink = learnMoreNoticeLinkableText;
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, searchResultsPage, metricsOptions.pageInformation);
+ searchSegment.groupId = searchData.groupId;
+ resultsShelf.id = getSearchResultsShelfId(searchSegment.groupId);
+ searchSegment.page = searchResultsPage;
+ searchSegment.title = segmentTitleForSegmentType(objectGraph, false, segmentType);
+ return {
+ segment: searchSegment,
+ metricsOptions: metricsOptions,
+ };
+ });
+}
+/**
+ * Get an array of shelves from the search builder results.
+ * This is used to decide where to insert the search results learn more notice across
+ * initial and paginated page builds.
+ *
+ * This is currently only used on visionOS search paths, but has the appropriate checks to ensure
+ * it doesn't run on unsupported platforms, so could be used in all search code paths.
+ * @param objectGraph The Object Graph.
+ * @param learnMoreNoticeLinkableText The linkable text to display as part of the learn more notice.
+ * @param builderResults The results from `createSearchResults`.
+ * @param resultsShelf The initial shelf created to hold the results. This is passed in
+ * because in many cases a reference to this is required later when building out the page.
+ * @param startingContentOffsetWithinResults The starting content offset before the newly built
+ * content is added. For an initial page build, this will be zero, and for paginated pages this
+ * is stored on the token.
+ * @returns An array of shelves for a search results page, with the learn more notice inserted
+ * if required.
+ */
+function searchResultsShelvesFromBuilderResults(objectGraph, learnMoreNoticeLinkableText, builderResults, resultsShelf, startingContentOffsetWithinResults = 0) {
+ let learnMoreNoticeIndex = searchResultsLearnMoreNoticeIndex(objectGraph);
+ const builtResultsCount = builderResults.builtSearchResults.length;
+ const hasDeferredResults = builderResults.deferredSearchResults.length > 0;
+ // The total built results for the page so far. This is the initial page, plus any paginated content.
+ const totalBuiltResults = startingContentOffsetWithinResults + builtResultsCount;
+ // There are a number of conditions we don't want to insert the learn more notice:
+ // 1. No index at which to insert it - this generally means the platform doesn't support this method of insertion.
+ // 2. No linkable text object - the bag key wasn't present.
+ // 3. The starting offset is greater than or equal to the insertion index - we can assume we've already shown the
+ // notice in a previous page build.
+ // 4. We have deferred results and the total built results count is less than the threshold - we'll insert the
+ // notice in a subsequent fetch.
+ // 5. We have no built results and no deferred results, ie. the page will be empty.
+ // If we fail any of these conditions, we will just attach all the items into the single shelf and return that.
+ if (learnMoreNoticeIndex === undefined ||
+ learnMoreNoticeLinkableText === undefined ||
+ startingContentOffsetWithinResults >= learnMoreNoticeIndex ||
+ (hasDeferredResults && totalBuiltResults < learnMoreNoticeIndex) ||
+ (totalBuiltResults === 0 && !hasDeferredResults)) {
+ resultsShelf.items = builderResults.builtSearchResults;
+ return [resultsShelf];
+ }
+ // Adjust the index by the starting offset, to ensure we take any items built in the initial fetch into account.
+ learnMoreNoticeIndex = learnMoreNoticeIndex - startingContentOffsetWithinResults;
+ // We have satisfied the insertion conditions for the learn more notice - we need to split the shelf in two to incorporate
+ // the learn more notice as its own shelf in between. This is due to layout restrictions on visionOS where we cannot
+ // accommodate a full width item in a grid of two items per row.
+ resultsShelf.items = builderResults.builtSearchResults.slice(0, learnMoreNoticeIndex);
+ const learnMoreNoticeShelf = new models.Shelf("searchResultsLearnMoreNotice");
+ learnMoreNoticeShelf.isHorizontal = false;
+ const searchResult = new SearchResultsLearnMoreNotice(learnMoreNoticeLinkableText);
+ learnMoreNoticeShelf.items = [searchResult];
+ const shelves = [resultsShelf, learnMoreNoticeShelf];
+ const secondResultsShelfItems = builderResults.builtSearchResults.slice(learnMoreNoticeIndex);
+ if (secondResultsShelfItems.length > 0) {
+ const secondResultsShelf = new models.Shelf("searchResult");
+ // Give the second results shelf the same impression metrics. This will result in each shelf contributing to the same impression metrics,
+ // the only difference is we will see two "viewedInfo" entries for each distinct shelf within the top level impressions item.
+ secondResultsShelf.impressionMetrics = resultsShelf.impressionMetrics;
+ secondResultsShelf.isHorizontal = false;
+ secondResultsShelf.items = secondResultsShelfItems;
+ shelves.push(secondResultsShelf);
+ }
+ return shelves;
+}
+/**
+ * Get the index for where the Search Results Learn More Notice should be inserted.
+ * Returns undefined if either:
+ * - the editorial ID is not available in the bag, or
+ * - the platform does not support this style of presentation.
+ * @param objectGraph The Object Graph
+ */
+function searchResultsLearnMoreNoticeIndex(objectGraph) {
+ const editorialItemId = objectGraph.bag.searchResultsLearnMoreEditorialId;
+ if (isNothing(editorialItemId) || (editorialItemId === null || editorialItemId === void 0 ? void 0 : editorialItemId.length) === 0) {
+ return undefined;
+ }
+ if (objectGraph.client.isVision) {
+ return 6;
+ }
+ return undefined;
+}
+export async function paginatedSearchResultsWithToken(objectGraph, token) {
+ return await validation.context("paginatedSearchResultsWithToken", async () => {
+ const nextItemsToFetch = searchToken.getNextItemsToFetch(objectGraph, token);
+ const advancedToken = searchToken.advanceSearchTokenResults(objectGraph, token);
+ if (nextItemsToFetch.length === 0) {
+ return await Promise.resolve(emptyResults(objectGraph));
+ }
+ return await searchResultsFetching
+ .fetchSearchResultItems(objectGraph, nextItemsToFetch)
+ .then(async (dataContainer) => {
+ const resultsDatum = mediaDataStructure.dataCollectionFromDataContainer(dataContainer);
+ return await searchResultsPipeline
+ .createSearchResults(objectGraph, token.requestMetadata, token.responseMetadata, token.metricsOptions, resultsDatum)
+ .then((builderResults) => {
+ const searchResults = new models.SearchResults();
+ searchResults.results = builderResults.builtSearchResults;
+ searchResults.nextPage = advancedToken;
+ return searchResults;
+ });
+ });
+ });
+}
+export async function paginatedSearchResultsPageWithToken(objectGraph, token) {
+ return await validation.context("paginatedSearchResultsPageWithToken", async () => {
+ const nextItemsToFetch = searchToken.getNextItemsToFetch(objectGraph, token);
+ const advancedToken = searchToken.advanceSearchTokenResults(objectGraph, token);
+ if (nextItemsToFetch.length === 0) {
+ return await Promise.resolve(emptyResultsPage(objectGraph));
+ }
+ return await searchResultsFetching
+ .fetchSearchResultItems(objectGraph, nextItemsToFetch)
+ .then(async (dataContainer) => {
+ const resultsDatum = mediaDataStructure.dataCollectionFromDataContainer(dataContainer);
+ const shelfMetricsOptions = {
+ id: "search-results",
+ kind: null,
+ softwareType: null,
+ targetType: "SearchResults",
+ title: "Search Results",
+ pageInformation: token.metricsOptions.pageInformation,
+ locationTracker: token.metricsOptions.locationTracker,
+ idType: "relationship",
+ };
+ // Set up the "new" shelf
+ const resultsShelf = new models.Shelf("searchResult");
+ resultsShelf.id = getSearchResultsShelfId();
+ resultsShelf.isHorizontal = false;
+ // Shelf Impressions: Add impression fields.
+ // This shelf, and the associated metrics data isn't really used - it's basically just a vehicle for the new items to be
+ // merged into the old page/shelf. Even so, we create and apply impressions metrics correctly before setting the content
+ // location and current position so it looks correct.
+ metricsHelpersImpressions.addImpressionFields(objectGraph, resultsShelf, shelfMetricsOptions);
+ // Set the content location to the "search-results" shelf.
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, "Search Results");
+ // Update position to content offset within the same search shelf prior to building the new results.
+ metricsHelpersLocation.setCurrentPosition(shelfMetricsOptions.locationTracker, token.contentOffsetWithinResultsShelf);
+ return await searchResultsPipeline
+ .createSearchResults(objectGraph, token.requestMetadata, token.responseMetadata, token.metricsOptions, resultsDatum)
+ .then((builderResults) => {
+ const learnMoreNoticeLinkableText = createSearchResultsLearnMoreNoticeLinkableText(objectGraph, shelfMetricsOptions);
+ const shelves = searchResultsShelvesFromBuilderResults(objectGraph, learnMoreNoticeLinkableText, builderResults, resultsShelf, token.contentOffsetWithinResultsShelf);
+ const searchResultsPage = new models.SearchResultsPage(shelves);
+ // Ensure we increment the offset for any future paginated results.
+ if (serverData.isDefinedNonNull(advancedToken)) {
+ advancedToken.contentOffsetWithinResultsShelf = metricsHelpersLocation.currentPosition(shelfMetricsOptions.locationTracker);
+ searchResultsPage.nextPage = advancedToken;
+ }
+ searchResultsPage.isCondensedSearchLockupsEnabled = objectGraph.client.isPhone;
+ searchResultsPage.resultsParentImpressionMetrics = resultsShelf.impressionMetrics;
+ metricsHelpersLocation.popLocation(token.metricsOptions.locationTracker);
+ searchResultsPage.searchClearAction = createSearchCancelledOrClearedAction(objectGraph, "clear", token.metricsOptions.pageInformation, token.metricsOptions.locationTracker, token.requestMetadata.requestDescriptor.term);
+ searchResultsPage.searchCancelAction = createSearchCancelledOrClearedAction(objectGraph, "cancel", token.metricsOptions.pageInformation, token.metricsOptions.locationTracker, token.requestMetadata.requestDescriptor.term);
+ return searchResultsPage;
+ });
+ });
+ });
+}
+// region Extracting Data
+/**
+ * Returns the array of data objects to build search results with.
+ */
+function dataForSearchResultsFromCombinedData(objectGraph, combinedSearchData) {
+ return mediaDataStructure.dataCollectionFromDataContainer(combinedSearchData.catalogResponse.results.search);
+}
+/**
+ * Returns the array of data objects to build search adverts with.
+ */
+function dataForAdvertsFromCombinedData(objectGraph, combinedSearchData) {
+ const rawAdvertsData = mediaDataStructure.dataCollectionFromDataContainer(combinedSearchData.catalogResponse.results["ads-result"]);
+ return searchAdsODML.applyNativeAdvertData(objectGraph, rawAdvertsData, combinedSearchData.sponsoredSearchAdvertData);
+}
+// endregion
+// region Guided Search
+/**
+ * Add models for Guided Search into `SearchResults` model, specifically:
+ * - `GuidedSearchToken`s
+ * - `GuidedSearchQuery`s
+ * - Metrics Container for tokens.
+ */
+function addModelsForGuidedSearch(objectGraph, searchResultsPage, requestMetadata, facetData, metricsOptions) {
+ if (!objectGraph.host.isiOS) {
+ return;
+ }
+ const request = requestMetadata.requestDescriptor;
+ metricsHelpersLocation.pushBasicLocation(objectGraph, {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "SearchRevisions",
+ }, "");
+ const tokens = [];
+ // Tokens from facet data, if any
+ if (serverData.isDefinedNonNullNonEmpty(facetData)) {
+ for (const data of facetData) {
+ const token = guidedSearch.createGuidedSearchToken(objectGraph, "toggle", request, data, metricsOptions);
+ if (token) {
+ tokens.push(token);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ }
+ }
+ }
+ // Token from selected entity hint, iff there aren't tokens already
+ if (serverData.isNullOrEmpty(tokens) && requestMetadata.requestDescriptor.searchEntity) {
+ const entityClearingToken = guidedSearch.createGuidedSearchTokenClearingEntityFilter(objectGraph, requestMetadata.requestDescriptor, metricsOptions);
+ tokens.push(entityClearingToken);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ }
+ const queries = guidedSearch.createGuidedSearchQueries(objectGraph, requestMetadata.requestDescriptor, facetData);
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ if (serverData.isDefinedNonNullNonEmpty(tokens)) {
+ searchResultsPage.guidedSearchTokens = tokens;
+ searchResultsPage.guidedSearchQueries = queries;
+ addGuidedSearchParentImpressionMetrics(objectGraph, searchResultsPage, metricsOptions);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker); // increment **after** assigning guided token parent impression.
+ }
+}
+/// The contextual reason for the segment change
+var SegmentChangeReason;
+(function (SegmentChangeReason) {
+ SegmentChangeReason[SegmentChangeReason["EmptyResults"] = 0] = "EmptyResults";
+ SegmentChangeReason[SegmentChangeReason["Picker"] = 1] = "Picker";
+ SegmentChangeReason[SegmentChangeReason["NonNative"] = 2] = "NonNative";
+})(SegmentChangeReason || (SegmentChangeReason = {}));
+/**
+ * Creates a segment change action for moving from one segment to another for a specific reason
+ * @param objectGraph The App Store object graph
+ * @param reason The reason why the segment is changing
+ * @param pageId The search results page id
+ * @param metricsOptions The metrics options for the segment this action will attach to
+ * @param switchingToGroupId The groupId of the segment we are switching to
+ * @param switchingFromGroupId The groupId of the segment we are switching from (the current segment)
+ * @returns A segment change action for the specific context and locations
+ */
+function segmentChangeAction(objectGraph, reason, pageId, metricsOptions, switchingToGroupId, switchingFromGroupId) {
+ const includeAppsSuffix = reason !== SegmentChangeReason.Picker;
+ const switchingToSegmentTitle = segmentTitleForSegmentType(objectGraph, includeAppsSuffix, segmentTypeFromGroupId(switchingToGroupId));
+ let action;
+ switch (reason) {
+ case SegmentChangeReason.EmptyResults:
+ if (isNothing(switchingToSegmentTitle) || switchingFromGroupId === switchingToGroupId) {
+ return undefined;
+ }
+ metricsHelpersLocation.pushBasicLocation(objectGraph, {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "SearchResults",
+ }, "emptyResultsSegmentSwitch");
+ const emptyResultsTitle = objectGraph.loc
+ .string("SEARCH_RESULTS_SWITCH_TO_OTHER_RESULTS")
+ .replace("{platformApps}", switchingToSegmentTitle);
+ action = new models.SearchPageSegmentChangeAction(switchingToGroupId, switchingToSegmentTitle, new models.StyledText(emptyResultsTitle));
+ metricsHelpersClicks.addClickEventToSearchPageSegmentChangeAction(objectGraph, action, "button", metricsOptions.locationTracker);
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ break;
+ case SegmentChangeReason.Picker:
+ if (isNothing(switchingToSegmentTitle)) {
+ return undefined;
+ }
+ metricsHelpersLocation.pushBasicLocation(objectGraph, {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "SearchResults",
+ }, "searchResultsSegmentSwitch");
+ action = new models.SearchPageSegmentChangeAction(switchingToGroupId, switchingToSegmentTitle);
+ metricsHelpersClicks.addClickEventToSearchPageSegmentChangeAction(objectGraph, action, "button", metricsOptions.locationTracker);
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ break;
+ case SegmentChangeReason.NonNative:
+ const currentSegmentType = segmentTypeFromGroupId(switchingFromGroupId);
+ if (currentSegmentType === segmentTypeForPlatform(objectGraph)) {
+ return undefined;
+ }
+ const switchingFromSegmentTitle = segmentTitleForSegmentType(objectGraph, true, segmentTypeFromGroupId(switchingFromGroupId));
+ const nonNativeTitle = objectGraph.loc
+ .string("Search.Results.ShowingNonNativeResults")
+ .replace("@@current_platform_apps@@", switchingFromSegmentTitle)
+ .replace("@@native_platform_apps@@", switchingToSegmentTitle);
+ metricsHelpersLocation.pushBasicLocation(objectGraph, {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "SearchResults",
+ }, "nonNativeResultsSegmentSwitch");
+ action = new models.SearchPageSegmentChangeAction(switchingToGroupId, switchingToSegmentTitle, new models.StyledText(nonNativeTitle, "text/x-apple-as3-nqml"));
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ break;
+ default:
+ break;
+ }
+ const clickActionOptions = {
+ actionType: "navigate",
+ id: pageId,
+ targetType: "button",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, clickActionOptions);
+ return action;
+}
+/**
+ * Gets the segment type for the given group id
+ * @param groupId The group id of the segment
+ * @returns The segment type for the group id
+ */
+function segmentTypeFromGroupId(groupId) {
+ if (isSome(groupId)) {
+ switch (groupId) {
+ case "iOS":
+ case "ios":
+ return models.SegmentedSearchResultsPageSegmentType.iOS;
+ case "xrOS":
+ case "xros":
+ return models.SegmentedSearchResultsPageSegmentType.visionOS;
+ default:
+ return undefined;
+ }
+ }
+ return undefined;
+}
+/**
+ * Gets the segment type for the current platform
+ * @param objectGraph The App Store object graph
+ * @returns The segment type for the current platform
+ */
+function segmentTypeForPlatform(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "vision":
+ return models.SegmentedSearchResultsPageSegmentType.visionOS;
+ case "pad":
+ case "phone":
+ return models.SegmentedSearchResultsPageSegmentType.iOS;
+ default:
+ return undefined;
+ }
+}
+/**
+ * Gets the segment title for the given segment type
+ * @param objectGraph The App Store object graph
+ * @param includeAppsSuffix Whether to include the word "Apps" at the end of the title
+ * @param segmentType The segment type we want the title of
+ * @returns The segment title for the segment type
+ */
+function segmentTitleForSegmentType(objectGraph, includeAppsSuffix, segmentType) {
+ switch (segmentType) {
+ case models.SegmentedSearchResultsPageSegmentType.visionOS:
+ return includeAppsSuffix
+ ? objectGraph.loc.string("SEARCH_RESULTS_VISION_APPS_TITLE")
+ : objectGraph.loc.string("SEARCH_RESULTS_VISION_TITLE");
+ case models.SegmentedSearchResultsPageSegmentType.iOS:
+ return includeAppsSuffix
+ ? objectGraph.loc.string("SEARCH_RESULTS_IPHONE_IPAD_APPS_TITLE")
+ : objectGraph.loc.string("SEARCH_RESULTS_IPHONE_IPAD_TITLE");
+ default:
+ return undefined;
+ }
+}
+/**
+ * Gets the group id for the current platform
+ * @param objectGraph The App Store object graph
+ * @param segments The segments on the search results page
+ * @returns The group id for the current platform
+ */
+function segmentGroupIdForPlatform(objectGraph, segments) {
+ const nativeSegmentType = segmentTypeForPlatform(objectGraph);
+ const nativeSegment = segments.find((segment) => {
+ return segmentTypeFromGroupId(segment.groupId) === nativeSegmentType;
+ });
+ return nativeSegment === null || nativeSegment === void 0 ? void 0 : nativeSegment.groupId;
+}
+/**
+ *
+ * @param objectGraph The App Store Object Graph
+ * @param searchClearActionType The way the user cleared the search (x in search field or cancel button in toolbar)
+ * @param pageInformation The metrics page information
+ * @param locationTracker The metrics location tracker
+ * @param searchTerm The current search term
+ * @returns An action to trigger when the search is cancelled or cleared
+ */
+export function createSearchCancelledOrClearedAction(objectGraph, searchClearActionType, pageInformation, locationTracker, searchTerm) {
+ const action = new models.BlankAction();
+ let type;
+ let targetId;
+ switch (searchClearActionType) {
+ case "cancel":
+ type = "dismiss";
+ targetId = "cancel";
+ break;
+ case "clear":
+ type = "delete";
+ targetId = "clear";
+ break;
+ default:
+ break;
+ }
+ metricsHelpersClicks.addClickEventToSearchCancelOrDismissAction(objectGraph, action, {
+ targetType: "button",
+ id: targetId,
+ idType: undefined,
+ actionType: type,
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ }, "button", searchTerm);
+ return action;
+}
+//# sourceMappingURL=search.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/shelves/search-history-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/search/shelves/search-history-shelf.js
new file mode 100644
index 0000000..c24e8c3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/shelves/search-history-shelf.js
@@ -0,0 +1,148 @@
+import { isNothing } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as searchLandingShelfController from "../landing/search-landing-shelf-controller";
+/**
+ * The `SearchHistoryShelfToken` type is responsible for plumbing through all
+ * the data needed to render an incomplete shelf (a shelf that requires a
+ * lookup) on the focus page.
+ */
+export class SearchHistoryShelfToken {
+ constructor(title, maxItems, shelfDisplayStyle, itemDisplayStyle) {
+ this.title = title;
+ this.maxItems = maxItems;
+ this.shelfDisplayStyle = shelfDisplayStyle;
+ this.itemDisplayStyle = itemDisplayStyle;
+ }
+}
+function encodedShelfToken(token) {
+ return encodeURIComponent(JSON.stringify(token));
+}
+export function decodeShelfToken(json) {
+ if (isNothing(json)) {
+ return undefined;
+ }
+ return JSON.parse(decodeURIComponent(json));
+}
+export function createShelfWithContext(objectGraph, pageContext, shelfAttributes) {
+ const token = new SearchHistoryShelfToken(shelfAttributes.title, shelfAttributes.displayCount, shelfAttributes.displayStyle, shelfAttributes.searchLandingItemDisplayStyle);
+ return createShelfWithToken(objectGraph, token, pageContext.metricsPageInformation, pageContext.metricsLocationTracker, pageContext.searchHistory);
+}
+function createShelfItems(objectGraph, metricsPageInformation, metricsLocationTracker, itemDisplayStyle, searchHistory) {
+ if (serverData.isNullOrEmpty(searchHistory)) {
+ return [];
+ }
+ const items = [];
+ for (const [historyIndex, historyItem] of searchHistory.entries()) {
+ const item = createShelfItem(objectGraph, historyItem, historyIndex, metricsPageInformation, metricsLocationTracker, itemDisplayStyle);
+ if (serverData.isNullOrEmpty(item)) {
+ continue;
+ }
+ items.push(item);
+ metricsHelpersLocation.nextPosition(metricsLocationTracker);
+ }
+ return items;
+}
+function createShelfURL(token) {
+ return `${Protocol.internal}:/${Path.searchLandingPage}/${Path.shelf}/?${Parameters.isOnDeviceSearchHistoryShelf}=true&${Parameters.token}=${encodedShelfToken(token)}`;
+}
+export function createShelfWithToken(objectGraph, token, metricsPageInformation, metricsLocationTracker, searchHistory) {
+ // Create Items
+ const items = createShelfItems(objectGraph, metricsPageInformation, metricsLocationTracker, token.itemDisplayStyle, searchHistory);
+ // Clear History Action
+ const clearHistoryAction = new models.ClearSearchHistoryAction();
+ clearHistoryAction.title = objectGraph.loc.string("Action.ClearSearches");
+ metricsHelpersClicks.addClickEventToClearSearchHistoryAction(objectGraph, clearHistoryAction);
+ // Clear History Sheet Action
+ const clearHistorySheetAction = new models.SheetAction([clearHistoryAction]);
+ clearHistorySheetAction.title = objectGraph.loc.string("Sheet.ClearSearches.Title");
+ clearHistorySheetAction.message = objectGraph.loc.string("Sheet.ClearSearches.Message");
+ clearHistorySheetAction.destructiveActionIndex = 0;
+ clearHistorySheetAction.isCancelable = true;
+ // Clear History Compound Action
+ const clearHistoryShelfAction = new models.CompoundAction([clearHistorySheetAction]);
+ clearHistoryShelfAction.title = objectGraph.loc.string("Action.Clear");
+ // Shelf
+ const contentType = shelfContentTypeForDisplayStyle(token.shelfDisplayStyle);
+ const shelf = new models.Shelf(contentType);
+ shelf.id = "onDeviceSearchHistory";
+ shelf.presentationHints = { isWidthConstrained: true };
+ // Header
+ shelf.header = {
+ title: token.title,
+ accessoryAction: clearHistoryShelfAction,
+ };
+ // Grid
+ if (shelf.contentType === "scrollablePill") {
+ shelf.isHorizontal = true;
+ shelf.rowsPerColumn = token.shelfDisplayStyle.layoutSize;
+ }
+ shelf.contentsMetadata = {
+ type: "searchFocusTwoColumnList",
+ numberOfColumns: items.length > 1 ? token.shelfDisplayStyle.layoutSize : 1,
+ };
+ // Content
+ shelf.items = items;
+ shelf.isHidden = serverData.isNullOrEmpty(items);
+ shelf.refreshUrl = createShelfURL(token);
+ return shelf;
+}
+function shelfContentTypeForDisplayStyle(displayStyle) {
+ if (displayStyle.layout === "word_cloud" /* models.GenericSearchPageShelfDisplayStyleLayout.WordCloud */) {
+ return "scrollablePill";
+ }
+ if (displayStyle.layoutSize === 2) {
+ // MAINTAINER'S NOTE: Automatically renders as single column in AX text sizes.
+ return "twoColumnList";
+ }
+ return "singleColumnList";
+}
+function createItemTitle(objectGraph, searchTerm, searchEntity) {
+ if (isNothing(searchEntity)) {
+ return searchTerm;
+ }
+ let formatLocKey;
+ if (searchEntity === "developer") {
+ formatLocKey = "Search.ResultsTitle.InDevelopers";
+ }
+ else if (searchEntity === "story") {
+ formatLocKey = "Search.ResultsTitle.InStories";
+ }
+ else if (searchEntity === "watch") {
+ formatLocKey = "Search.ResultsTitle.InWatch";
+ }
+ else if (searchEntity === "arcade") {
+ formatLocKey = "Search.ResultsTitle.InArcade";
+ }
+ if (isNothing(formatLocKey)) {
+ return searchTerm;
+ }
+ return objectGraph.loc.string(formatLocKey).replace("@@search_term@@", searchTerm);
+}
+function createShelfItem(objectGraph, historyItem, itemIndex, metricsPageInformation, metricsLocationTracker, displayStyle) {
+ const searchTerm = historyItem.term;
+ const searchEntity = historyItem.entity;
+ const searchAction = searchLandingShelfController.createFocusPageSearchAction(objectGraph, createItemTitle(objectGraph, searchTerm, searchEntity), searchTerm, searchEntity, metricsLocationTracker, "recents", undefined, /// MAINTAINER'S NOTE: In the future, we could keep track of the original search source and attribute this recent search to that.
+ metricsPageInformation, displayStyle);
+ if (isNothing(searchAction)) {
+ return null;
+ }
+ searchAction.id = historyItem.id;
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchAction, {
+ targetType: "link",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ kind: "link",
+ softwareType: null,
+ title: historyItem.term,
+ hintsEntity: historyItem.entity,
+ id: `${itemIndex}`,
+ idType: "sequential",
+ });
+ return searchAction;
+}
+//# sourceMappingURL=search-history-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/sponsored-search-fetching.js b/node_modules/@jet-app/app-store/tmp/src/common/search/sponsored-search-fetching.js
new file mode 100644
index 0000000..c9ee59f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/sponsored-search-fetching.js
@@ -0,0 +1,122 @@
+/**
+ Data Fetching for Sponsored Search.
+ */
+import { ads } from "../../api/typings/constants";
+import { asString, isNullOrEmpty } from "../../foundation/json-parsing/server-data";
+import { attributeAsDictionary } from "../../foundation/media/attributes";
+import { dataCollectionFromDataContainer, } from "../../foundation/media/data-structure";
+import { allProductVariantIdsForData, productVariantDataForData, productVariantIDForVariantData, } from "../product-page/product-page-variants";
+import { adLogger } from "./search-ads";
+import * as content from "../content/content";
+// region exports
+/**
+ * Fetch the set of processed search ads with the raw adverts in the sequential response.
+ * @param sponsoredSearchRequestData The request data to fetch processed ads for.
+ * @param searchTerm The search term to fetch processed adverts for.
+ * @param fetchResponse The promise that will resolve to the response.
+ * @returns A promise that will provide `SponsoredSearchNativeAdvertData`. Note that even error conditions are represented in the result for instrumentation purposes.
+ */
+export async function fetchSponsoredSearchNativeAdvertData(objectGraph, sponsoredSearchRequestData, searchTerm, fetchResponse) {
+ var _a;
+ if (!sponsoredSearchRequestData.validAdRequest()) {
+ adLogger(objectGraph, `ODML fetch skipped - Malformed request`);
+ return {
+ adverts: [],
+ odmlSuccess: false,
+ };
+ }
+ /**
+ * This is a weird path where we:
+ * 1. Fetch response from search
+ * 2. Pass parts of the response back to native to be modified by SearchAds.
+ */
+ const response = await fetchResponse;
+ const adverts = sponsoredSearchAdvertsFromResponse(objectGraph, response);
+ const organics = sponsoredSearchOrganicsFromResponse(objectGraph, response, 1); // per POR, only 1 organic for now.
+ try {
+ if (!objectGraph.isAvailable(ads)) {
+ adLogger(objectGraph, `ODML fetch skipped - Unsupported client`);
+ return {
+ adverts: adverts,
+ odmlSuccess: false,
+ };
+ }
+ else {
+ const processedAdverts = await objectGraph.ads.processAdvertsForSponsoredSearch(adverts, organics, searchTerm, objectGraph.bag.sponsoredSearchODMLTimeout, objectGraph.client.isPhone || objectGraph.client.isPad);
+ if (!processedAdverts.odmlSuccess) {
+ adLogger(objectGraph, `ODML processing failed`);
+ }
+ else {
+ adLogger(objectGraph, `ODML processing completed`);
+ }
+ return {
+ adverts: (_a = processedAdverts.adverts) !== null && _a !== void 0 ? _a : adverts,
+ odmlSuccess: processedAdverts.odmlSuccess,
+ installedStates: processedAdverts.installedStates,
+ appliedPolicy: processedAdverts.appliedPolicy,
+ appStates: processedAdverts.appStates,
+ };
+ }
+ }
+ catch (e) {
+ adLogger(objectGraph, `ODML fetch failed - ${e}`);
+ return {
+ adverts: adverts,
+ odmlSuccess: false,
+ };
+ }
+}
+// endregion
+// region internals
+/**
+ * Build the search advert models from the response.
+ */
+function sponsoredSearchAdvertsFromResponse(objectGraph, response) {
+ const adverts = dataCollectionFromDataContainer(response.results["ads-result"]);
+ const bridgedAdverts = [];
+ for (const ad of adverts) {
+ const id = asString(ad, "id");
+ const adData = attributeAsDictionary(ad, "iads");
+ if (isNullOrEmpty(id) || isNullOrEmpty(adData)) {
+ continue;
+ }
+ let productVariantId = null;
+ let allProductVariantIds = null;
+ if (objectGraph.bag.enableCPPInSearchAds) {
+ const productVariantData = productVariantDataForData(objectGraph, ad);
+ productVariantId = productVariantIDForVariantData(productVariantData);
+ allProductVariantIds = allProductVariantIdsForData(objectGraph, ad);
+ }
+ bridgedAdverts.push({
+ instanceId: objectGraph.random.nextUUID(),
+ adamId: id,
+ assetInformation: {},
+ adData: adData,
+ cppIds: allProductVariantIds,
+ serverCppId: productVariantId,
+ selectedCppId: productVariantId,
+ appBinaryTraits: content.appBinaryTraitsFromData(objectGraph, ad),
+ });
+ }
+ return bridgedAdverts;
+}
+/**
+ * Build the search organic models from the response, up to `limit`.
+ */
+function sponsoredSearchOrganicsFromResponse(objectGraph, response, limit) {
+ const organics = dataCollectionFromDataContainer(response.results.search);
+ const bridgedOrganics = [];
+ for (const result of organics) {
+ const id = asString(result, "id");
+ if (isNullOrEmpty(id)) {
+ continue;
+ }
+ bridgedOrganics.push({
+ adamId: id,
+ assetInformation: {},
+ });
+ }
+ return bridgedOrganics;
+}
+// endregion
+//# sourceMappingURL=sponsored-search-fetching.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/web-search-action.js b/node_modules/@jet-app/app-store/tmp/src/common/search/web-search-action.js
new file mode 100644
index 0000000..28c0491
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/web-search-action.js
@@ -0,0 +1,23 @@
+import { FlowAction } from "../../api/models";
+import { makeSearchResultsPageIntent, } from "../../api/intents/search-results-page-intent";
+import { getLocale } from "../locale";
+import { makeCanonicalSearchResultsPageUrl } from "./search-page-url";
+/**
+ * Creates a `FlowAction` destined for the search results page
+ *
+ * This will be used by the "web" client to perform search from the search landing page,
+ * or to change the platform on search results page
+ */
+export function makeWebSearchAction(objectGraph, platform, term = "") {
+ const searchAction = new FlowAction("search");
+ const destination = makeSearchResultsPageIntent({
+ ...getLocale(objectGraph),
+ platform,
+ term,
+ origin: "externalUrl",
+ });
+ searchAction.destination = destination;
+ searchAction.pageUrl = makeCanonicalSearchResultsPageUrl(objectGraph, destination);
+ return searchAction;
+}
+//# sourceMappingURL=web-search-action.js.map \ No newline at end of file