summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/metrics
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/metrics
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/metrics')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/builder.js257
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/event-linter.js563
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/buy.js331
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/clicks.js458
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/constants.js11
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/impressions.js419
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/legacy-metrics-identifier-fields-opt-out.js18
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/location.js188
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/media.js34
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/misc.js46
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/models.js671
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/page.js482
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-focus-impressions.js57
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-result-impressions.js56
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search/search-shelves.js98
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/util.js407
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js370
17 files changed, 4466 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/builder.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/builder.js
new file mode 100644
index 0000000..ffb205f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/builder.js
@@ -0,0 +1,257 @@
+import { isSome } from "@jet/environment";
+import { isNothing } from "@jet/environment/types/optional";
+import { AppStoreMetricsData, } from "../../api/models/metrics/metrics";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as client from "../../foundation/wrappers/client";
+import { EventLinter } from "./event-linter";
+import { optOutOfLegacyMetricsIdFieldsProvider } from "./helpers/legacy-metrics-identifier-fields-opt-out";
+export function createMetricsClickData(objectGraph, targetId, targetType, eventFields, additionalIncludingFields, isDefaultBrowser) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["eventType"] = "click";
+ fields["targetType"] = targetType;
+ fields["targetId"] = targetId;
+ const include = ["impressionsSnapshot", "pageFields"];
+ if (!preprocessor.GAMES_TARGET) {
+ include.push("contentRestrictionReasons");
+ }
+ if (additionalIncludingFields) {
+ include.push(...additionalIncludingFields);
+ }
+ addWebClientEventFields(objectGraph, fields);
+ addAltAb2DataToEventFields(objectGraph, fields);
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, include, [], topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields, null, isDefaultBrowser)));
+}
+export function createMetricsBackClickData(objectGraph, eventFields) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["actionType"] = "back";
+ const clickData = createMetricsClickData(objectGraph, "back", "button", fields);
+ return clickData;
+}
+export function createMetricsPageData(objectGraph, isReferralEligible, isCrossfireReferralCandidate, timingMetrics, eventFields, isDefaultBrowser) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["eventType"] = "page";
+ if (timingMetrics) {
+ fields["clientCorrelationKey"] = timingMetrics.clientCorrelationKey;
+ fields["requestStartTime"] = timingMetrics.requestStartTime;
+ fields["responseStartTime"] = timingMetrics.responseStartTime;
+ fields["responseEndTime"] = timingMetrics.responseEndTime;
+ }
+ const include = ["pageFields", "pageReferrer"];
+ if (!preprocessor.GAMES_TARGET) {
+ include.push("userContentRestriction");
+ }
+ if (isReferralEligible) {
+ include.push("crossfireReferral");
+ }
+ else if (isCrossfireReferralCandidate) {
+ include.push("crossfireReferralCandidate"); // Only possible when not crossfire eligible.
+ }
+ addAltAb2DataToEventFields(objectGraph, fields);
+ addWebClientEventFields(objectGraph, fields);
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, include, [], topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields, null, isDefaultBrowser)));
+}
+export function createMetricsSearchData(objectGraph, term, target, actionType, actionUrl, eventFields, additionalIncludingFields) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["term"] = term;
+ fields["targetType"] = target;
+ fields["actionType"] = actionType;
+ if (actionUrl) {
+ fields["actionUrl"] = actionUrl; // actionUrl is defined for `hints` but not for searches fired from elsewhere.
+ }
+ fields["eventType"] = "search";
+ const include = ["pageReferrer"];
+ if (additionalIncludingFields) {
+ include.push(...additionalIncludingFields);
+ }
+ addWebClientEventFields(objectGraph, fields);
+ addAltAb2DataToEventFields(objectGraph, fields);
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, include, [], topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields)));
+}
+export function createMetricsPageRenderFields(objectGraph, timingMetrics, eventFields) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["eventType"] = "pageRender";
+ if (timingMetrics) {
+ if (!fields["pageUrl"]) {
+ fields["pageUrl"] = timingMetrics.pageURL;
+ }
+ fields["clientCorrelationKey"] = timingMetrics.clientCorrelationKey;
+ fields["platformRequestStartTime"] = timingMetrics.requestStartTime;
+ fields["platformResponseStartTime"] = timingMetrics.responseStartTime;
+ fields["platformResponseEndTime"] = timingMetrics.responseEndTime;
+ fields["platformResponseWasCached"] = timingMetrics.responseWasCached;
+ fields["platformJsonParseStartTime"] = timingMetrics.parseStartTime;
+ fields["platformJsonParseEndTime"] = timingMetrics.parseEndTime;
+ }
+ addWebClientEventFields(objectGraph, fields);
+ addAltAb2DataToEventFields(objectGraph, fields);
+ return fields;
+}
+/**
+ * Create a metrics data for regular impressions.
+ * @param objectGraph The dependency graph for the App Store.
+ * @param eventFields Base fields to build off on.
+ * @param shouldIncludeAdFieldsForPad Whether or not metrics data should include iPad related fields for adverts.
+ * @param includeAdRotationFields Whether or not metrics data should include advert related fields.
+ * @param hasImpressionsAppendix Whether condensed format search results should track appendix data.
+ */
+export function createMetricsImpressionsData(objectGraph, eventFields, shouldIncludeAdFieldsForPad, includeAdRotationFields, hasImpressionsAppendix) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["eventType"] = "impressions";
+ fields["impressionQueue"] = "data-metrics";
+ fields["eventVersion"] = 4;
+ const include = ["impressions", "pageFields", "pageReferrer"];
+ if (!preprocessor.GAMES_TARGET) {
+ include.push("contentRestrictionReasons");
+ }
+ if (shouldIncludeAdFieldsForPad) {
+ include.push("advertDeviceWindow");
+ }
+ if (includeAdRotationFields) {
+ include.push("advertRotation");
+ }
+ if (hasImpressionsAppendix) {
+ include.push("impressionsAppendix");
+ }
+ addWebClientEventFields(objectGraph, fields);
+ addAltAb2DataToEventFields(objectGraph, fields);
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, include, ["eventVersion"], topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields)));
+}
+/**
+ * Create a metrics data for fast impressions, a special type for SearchAds on Search Page.
+ * Included fields assume fields are for search page w/ Ads.
+ * @param baseFields Base fields to build off on.
+ */
+export function createMetricsFastImpressionsData(objectGraph, baseFields, pageInformation) {
+ var _a, _b;
+ const shouldIncludeAdFieldsForPad = serverData.isDefinedNonNull(pageInformation.iAdInfo) &&
+ objectGraph.client.isPad &&
+ (isNothing(pageInformation.iAdInfo.missedOpportunityReason) ||
+ pageInformation.iAdInfo.missedOpportunityReason.length === 0);
+ const shouldIncludeAdRotationFields = (_b = (_a = pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.shouldIncludeAdRotationFields) !== null && _b !== void 0 ? _b : false;
+ const exclude = [];
+ const fields = createMetricsImpressionsData(objectGraph, baseFields, shouldIncludeAdFieldsForPad, shouldIncludeAdRotationFields, false).fields;
+ fields["impressionQueue"] = "data-metrics-impressions-low-latency";
+ if (pageInformation !== null && serverData.isDefinedNonNull(pageInformation.iAdInfo)) {
+ const eventVersion = pageInformation.iAdInfo.fastImpressionsEventVersion;
+ fields["eventVersion"] = eventVersion;
+ exclude.push("eventVersion");
+ // Make some manual adjustments to events for v5.
+ if (eventVersion === 5) {
+ // Indicates the viewable area where elements can be impressed excludes the tab bar
+ fields["viewableArea"] = "excludingTabBar";
+ // Remove the `iAdPlacementId` for v5. It's added for compatibility with v4 impressions.
+ delete fields["iAdPlacementId"];
+ }
+ }
+ const include = ["fastImpressions", "pageFields", "pageReferrer"];
+ if (shouldIncludeAdFieldsForPad) {
+ include.push("advertDeviceWindow");
+ }
+ if (shouldIncludeAdRotationFields) {
+ include.push("advertRotation");
+ }
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, include, exclude, topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields, pageInformation)));
+}
+export function createMetricsMediaData(objectGraph, eventFields) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["eventType"] = "media";
+ addWebClientEventFields(objectGraph, fields);
+ addAltAb2DataToEventFields(objectGraph, fields);
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, [], [], topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields)));
+}
+export function createMetricsMediaClickData(objectGraph, targetId, targetType, eventFields) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["eventType"] = "click";
+ fields["targetType"] = targetType;
+ fields["targetId"] = targetId;
+ const include = ["pageFields"];
+ addWebClientEventFields(objectGraph, fields);
+ addAltAb2DataToEventFields(objectGraph, fields);
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, include, [], topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields)));
+}
+function shouldFlushFromEventFields(objectGraph, eventFields, pageInformation = null, isDefaultBrowser) {
+ var _a, _b;
+ const eventType = eventFields["eventType"];
+ let shouldFlush = false;
+ if (!serverData.isDefinedNonNullNonEmpty(eventType)) {
+ return shouldFlush;
+ }
+ const isDefaultBrowserContext = isDefaultBrowser !== null && isDefaultBrowser !== void 0 ? isDefaultBrowser : false;
+ switch (eventType) {
+ case "click":
+ shouldFlush =
+ serverData.asBooleanOrFalse(eventFields, EventLinter.hasIAdData) ||
+ isDefaultBrowserContext;
+ break;
+ case "exit":
+ shouldFlush = true;
+ break;
+ case "impressions":
+ shouldFlush = serverData.asBooleanOrFalse(eventFields, EventLinter.hasIAdData);
+ // Not my best work here.
+ // For most ad placements on a page, it's fine to check the `hasIAdData` as an indicator for whether we should flush after a given event.
+ // The challenge for the product page placements is that they're fetched asynchronously, and we don't create an entirely new impressions
+ // event when that happens (we just update fields within the event via the `pageChange` mechanism). To ensure that fast impression events on
+ // the product page are actually fast, we have to force the events to flush if they meet the below conditions that effectively guarantee
+ // we will be impressing something on the low latency queue.
+ if (eventFields["impressionQueue"] === "data-metrics-impressions-low-latency" &&
+ (((_a = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.placementType) === "productPageYMAL" ||
+ ((_b = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.placementType) === "productPageYMALDuringDownload")) {
+ shouldFlush = true;
+ }
+ break;
+ case "page":
+ // rdar://98487650 (CL seed: Product Page observed lower % of events coming in <2 mins ( page change + page events ))
+ // In order to resolve issues with flushing timeliness in the PPE: rdar://93127678 ([Metrics] Dismissing store sheet does not fire clickstream events)
+ // we allow page events with ad data in them to cause a flush on product pages in the PPE.
+ // We also pre-emptively flush for page events where it's the default browser flow in order to "prewarm" the metrics pipeline, ensuring flushes can occur
+ // if and when the user taps "Open" on an installed app.
+ const isProductPageExtension = objectGraph.host.clientIdentifier === client.productPageExtensionIdentifier;
+ shouldFlush =
+ isProductPageExtension &&
+ (serverData.asBooleanOrFalse(eventFields, EventLinter.hasIAdData) ||
+ isDefaultBrowserContext);
+ break;
+ default:
+ break;
+ }
+ const isSubscribePageExtensionEnterExitEventsEnabled = objectGraph.host.isiOS;
+ if (objectGraph.host.clientIdentifier === client.subscribePageExtensionIdentifier &&
+ !isSubscribePageExtensionEnterExitEventsEnabled) {
+ // <rdar://problem/55865604> Metrics: missing metrics events for arcade upsell from springboard app open on unsubscribed user
+ // Until a native fix is in to address enter/exit events in SPE (subscribePageExtensionEnterExitEvents), we
+ // need to force events in the SubscribePageExtension to trigger a flush.
+ shouldFlush = true;
+ }
+ return shouldFlush;
+}
+function topicFromEventFields(objectGraph, eventFields) {
+ const topic = eventFields["topic"] || objectGraph.bag.metricsTopic;
+ return topic;
+}
+function addAltAb2DataToEventFields(objectGraph, eventFields) {
+ if (objectGraph.bag.isMetricsAb2DataFallbackEnabled && isSome(objectGraph.experimentCache)) {
+ eventFields["alt_ab2_data"] = JSON.stringify(objectGraph.experimentCache.createAb2Data());
+ }
+}
+/**
+ * Attach expected metrics fields specific to the "web" client, if necessary
+ */
+function addWebClientEventFields(objectGraph, eventFields) {
+ var _a;
+ if (!objectGraph.client.isWeb) {
+ return;
+ }
+ // The `platformContext` field is expected to reflect the "platform" that the user is browsing
+ // at the time of the event
+ eventFields["platformContext"] = (_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.previewPlatform;
+}
+//# sourceMappingURL=builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/event-linter.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/event-linter.js
new file mode 100644
index 0000000..ad9706c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/event-linter.js
@@ -0,0 +1,563 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import { LintedMetricsEvent } from "../../api/models/metrics/metrics";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { BuyParameters } from "../../foundation/metrics/buy-parameters";
+import { cookiesOf } from "../../foundation/metrics/cookies";
+import { MetricsIdentifierType } from "../../foundation/metrics/metrics-identifiers-cache";
+import { URL } from "../../foundation/network/urls";
+import { reduceSignificantDigits } from "../../foundation/util/math-util";
+import * as constants from "./helpers/constants";
+import { stripUniqueImpressionIdsIfNecessary } from "./helpers/impressions";
+import { iAdDismissAdActionMetricsParameterStringToken, iAdURLLineItemParameterStringToken, iAdURLParameterStringToken, } from "./helpers/models";
+import * as searchResultImpressions from "./helpers/search-result-impressions";
+import * as searchFocusImpressions from "./helpers/search-focus-impressions";
+import * as util from "./helpers/util";
+import { MetricsReferralContext } from "./metrics-referral-context";
+/**
+ * A type which applies App Store business rules to metrics fields
+ * and generates events which are ready for posting to figaro.
+ */
+export class EventLinter {
+ /**
+ * Create an event linter.
+ *
+ * @param options The options which specify various behaviors of the new linter.
+ * This object will be frozen.
+ */
+ constructor(options) {
+ this._options = Object.freeze(options);
+ }
+ // endsection
+ // section Public Properties
+ /**
+ * Topic to use if an event fields blob does not specify one.
+ */
+ get defaultTopic() {
+ return this._options.defaultTopic;
+ }
+ // endsection
+ // section Utilities
+ /**
+ * Reduce the accuracy of fields in a blob according to
+ * a given array of rules found in a metrics objectGraph.bag.
+ *
+ * @param eventFields The fields of an event to reduce the accuracy of.
+ * @param rules An array of rules from a metrics objectGraph.bag.
+ */
+ _reduceFieldAccuracy(eventFields, rules) {
+ for (const rule of rules) {
+ const fieldName = serverData.asString(rule, "fieldName");
+ if (serverData.isNull(fieldName)) {
+ continue;
+ }
+ const value = serverData.asNumber(eventFields, fieldName);
+ if (serverData.isNull(value)) {
+ continue;
+ }
+ let magnitude = serverData.asNumber(rule, "magnitude");
+ if (serverData.isNull(magnitude)) {
+ magnitude = 1024 * 1024;
+ }
+ let significantDigits = serverData.asNumber(rule, "significantDigits");
+ if (serverData.isNull(significantDigits)) {
+ significantDigits = 2;
+ }
+ if (magnitude <= 0.0 || significantDigits < 0.0) {
+ // This is the failure mode from MetricsKit.
+ eventFields[fieldName] = Number.NaN;
+ continue;
+ }
+ const scaledValue = value / magnitude;
+ eventFields[fieldName] = reduceSignificantDigits(scaledValue, significantDigits);
+ }
+ }
+ /**
+ * Returns a new URL by scrubbing any ad fields we've inserted into URLs to pass
+ * ad attribution information between pages.
+ * @param urlString The original URL to be scrubbed
+ * @returns A scrubbed URL.
+ */
+ _urlScrubbingAdParameters(urlString) {
+ const url = new URL(urlString);
+ url.removeParam(iAdURLParameterStringToken);
+ url.removeParam(iAdURLLineItemParameterStringToken);
+ url.removeParam(iAdDismissAdActionMetricsParameterStringToken);
+ return url.build();
+ }
+ /**
+ * Returns a new URL by scrubbing everything but the protocol, domain, and port (e.g. host).
+ * @param urlString The original URL to be scrubbed
+ * @returns A scrubbed URL.
+ */
+ _urlScrubbingExtRefUrl(urlString) {
+ const url = new URL(urlString);
+ url.username = "";
+ url.password = "";
+ url.pathname = undefined;
+ url.query = undefined;
+ url.hash = undefined;
+ return url.build();
+ }
+ // endsection
+ // section Fast Impressions Deresolution
+ _derezFastImpressions(eventFields) {
+ const impressionQueue = serverData.asString(eventFields, "impressionQueue");
+ const eventVersion = serverData.asNumber(eventFields, "eventVersion");
+ if (impressionQueue !== "data-metrics-impressions-low-latency") {
+ return; // Only scrub data going to the AP low latency queue.
+ }
+ // Scrub `viewedInfo` of a v4 event.
+ if (eventVersion === 4) {
+ const impressions = serverData.asArrayOrEmpty(eventFields, "impressions");
+ eventFields["impressions"] = impressions.map((impression) => {
+ if (isNothing(impression)) {
+ return impression;
+ }
+ const viewedInfo = serverData.asArrayOrEmpty(impression, "viewedInfo");
+ if (viewedInfo.length === 0) {
+ return impression; // V3. No modification.
+ }
+ /**
+ * <rdar://problem/64497066> Metrics: iAd: JS must clear impression start times, and derezz duration to 2 sig figs
+ * - start time to 0 (strange ask - we already send this as part of `impressionTimes`)
+ * - duration to 2 sig fig.
+ */
+ impression["viewedInfo"] = viewedInfo.map((interval) => {
+ if (isNothing(interval)) {
+ return interval;
+ }
+ const duration = serverData.asNumber(interval, "d");
+ interval["s"] = 0;
+ if (isSome(duration)) {
+ interval["d"] = reduceSignificantDigits(duration, 2);
+ }
+ return interval;
+ });
+ return impression;
+ });
+ }
+ // Scrub `viewedInfoDetailed` of a v5 event.
+ if (eventVersion === 5) {
+ const impressions = serverData.asArrayOrEmpty(eventFields, "impressions");
+ eventFields["impressions"] = impressions.map((impression) => {
+ if (isNothing(impression)) {
+ return impression;
+ }
+ // Impression items in a low latency impressions event shouldn't have a `cardType` field.
+ delete impression["cardType"];
+ if (serverData.isNullOrEmpty(serverData.asString(impression, "iAdMetadata")) ||
+ serverData.isNullOrEmpty(serverData.asString(impression, "iAdImpressionId"))) {
+ // If the iAdMetadata or iAdImpressionId for this impression on the fast queue is null,
+ // it's an organic result that has added instrumentation as it's in a defined ad slot.
+ // For these items, we have to scrub the adamId for privacy reasons.
+ delete impression["id"];
+ }
+ // Get the dictionary of values, formatted like so:
+ // {0: [{s: 11636658562756, d: 23281}], 50: [{s: 1636658559350, d: 378}]}
+ const viewedInfoDetailed = serverData.asDictionary(impression, "viewedInfoDetailed");
+ if (isNothing(viewedInfoDetailed) || serverData.isNullOrEmpty(viewedInfoDetailed)) {
+ return impression; // Nothing to modify.
+ }
+ /**
+ * rdar://89785026 (Chainlink: v5 Impressions ViewedInfoDetailed - Fixes for startTime and duration)
+ * - match scrubbing "s" value and de-rezing "d" value as per above v4 implementation.
+ */
+ Object.entries(viewedInfoDetailed).forEach(([key, value]) => {
+ // For each key/value pair, grab the array of traditional "viewedInfo".
+ const viewedInfo = serverData.asArrayOrEmpty(value);
+ // Iterate over the array of values, scrubbing the required children.
+ viewedInfoDetailed[key] = viewedInfo.map((interval) => {
+ if (isNothing(interval)) {
+ return interval;
+ }
+ const duration = serverData.asNumber(interval, "d");
+ interval["s"] = 0;
+ if (isSome(duration)) {
+ interval["d"] = reduceSignificantDigits(duration, 2);
+ }
+ return interval;
+ });
+ });
+ // Re-set the modified dictionary on the `impression` object.
+ impression["viewedInfoDetailed"] = viewedInfoDetailed;
+ return impression;
+ });
+ }
+ }
+ _stripContentRatingImpressionFields(eventFields) {
+ const impressions = serverData.asArrayOrEmpty(eventFields, "impressions");
+ eventFields["impressions"] = impressions.map((impression) => {
+ if (isSome(impression)) {
+ // "contentRating" and "bundleId" are used by native to
+ // determine the value of "contentRestrictionReasons" (whether a
+ // lockup's offer is disabled due to content restrictions).
+ // However we don't want these fields in the final impression.
+ delete impression["contentRating"];
+ delete impression["bundleId"];
+ }
+ return impression;
+ });
+ }
+ // endsection
+ // section Search Results Page Impressions
+ /**
+ * Decorate `impressions` field for click and impressions events on SRP.
+ */
+ _decorateSearchResultImpressions(eventFields) {
+ const pageType = serverData.asString(eventFields, "pageType");
+ const pageId = serverData.asString(eventFields, "pageId");
+ /**
+ * Only run on SRP.
+ */
+ const isSearchResultsPage = pageType === "Search" && pageId !== "hints";
+ if (isSearchResultsPage) {
+ searchResultImpressions.decorateImpressionParentId(eventFields);
+ }
+ }
+ // endsection
+ // section Search Focus Page Impressions
+ /**
+ * Decorate `impressions` field for click and impressions events on SFP.
+ */
+ _decorateSearchFocusImpressions(eventFields) {
+ const pageType = serverData.asString(eventFields, "pageType");
+ const pageId = serverData.asString(eventFields, "pageId");
+ /**
+ * Only run on SFP.
+ */
+ if (pageType === "SearchFocus" && pageId === "Focus") {
+ searchFocusImpressions.decorateImpressionParentId(eventFields);
+ }
+ }
+ // endsection
+ // section Rules
+ /**
+ * Apply the rules which are universal to all metrics events
+ * to a given metrics fields linter.
+ *
+ * @param eventFields The fields which will be used to construct a built event.
+ * @param topic The topic the built event will be submitted to.
+ */
+ _decorateAll(objectGraph, eventFields, topic) {
+ var _a, _b, _c;
+ const getBagValue = this._options.bagProvider;
+ // - Metrics base fields
+ const metricsBase = getBagValue("metricsBase", topic);
+ if (!serverData.isNull(metricsBase) && typeof metricsBase === "object") {
+ Object.assign(eventFields, metricsBase);
+ }
+ // - Universal basic fields
+ eventFields["clientBuildType"] = this._options.buildType;
+ eventFields["resourceRevNum"] = this._options.jsVersion;
+ eventFields["xpSendMethod"] = "jet-js";
+ this._options.buyDecorator.useApp(serverData.asString(eventFields, "app"));
+ // - Universal scrubbing
+ delete eventFields[constants.contextualAdamIdKey];
+ // - Cookie-based fields
+ const cookies = cookiesOf(serverData.asString(eventFields, "cookie"));
+ for (const cookie of cookies) {
+ if (cookie.key === "xp_ci") {
+ // Always update buy decorator.
+ this._options.buyDecorator.useClientId(cookie.value);
+ break;
+ }
+ }
+ delete eventFields["cookie"];
+ const clientIdFields = (_b = (_a = objectGraph.metricsIdentifiersCache) === null || _a === void 0 ? void 0 : _a.getMetricsFieldsForTypes([MetricsIdentifierType.client])) !== null && _b !== void 0 ? _b : {};
+ Object.assign(eventFields, clientIdFields);
+ delete eventFields["clientGeneratedId"];
+ // - page
+ const pageType = serverData.asString(eventFields, "pageType");
+ const pageId = serverData.asString(eventFields, "pageId");
+ if (!serverData.isNull(pageType) && !serverData.isNull(pageId)) {
+ const separator = serverData.asString(getBagValue("compoundSeparator", topic)) || "_";
+ eventFields["page"] = `${pageType}${separator}${pageId}`;
+ }
+ // - Field value resolution reduction
+ const rules = serverData.asArrayOrEmpty(getBagValue("deResFields", topic));
+ this._reduceFieldAccuracy(eventFields, rules);
+ // Scrub sensitive urls in event from data that should not leave the device.
+ // At times we insert ad metadata into URLs in order to pass ad attribution information between pages.
+ // Additionally, scrub extRefUrl of everything but the origin.
+ const urlFieldsToScrub = ["pageUrl", "actionUrl", "extRefUrl", "refUrl", "url", "parentPageUrl"];
+ for (const urlField of urlFieldsToScrub) {
+ const urlString = serverData.asString(eventFields, urlField);
+ if (isSome(urlString) && urlString.length > 0) {
+ eventFields[urlField] =
+ urlField === "extRefUrl"
+ ? this._urlScrubbingExtRefUrl(urlString)
+ : this._urlScrubbingAdParameters(urlString);
+ }
+ }
+ // Where an `overridePageContext` has been provided, use it as the `pageContext` value.
+ const overridePageContext = serverData.asString(eventFields, "overridePageContext");
+ if (isSome(overridePageContext)) {
+ delete eventFields["overridePageContext"];
+ eventFields["pageContext"] = overridePageContext;
+ }
+ if (objectGraph.bag.isMetricsUserIdFallbackEnabled) {
+ const existingUserId = serverData.asString(eventFields, "userId");
+ let metricsUserDSID = null;
+ if (isNothing(existingUserId) ||
+ existingUserId.length === 0 ||
+ existingUserId.length === EventLinter.clientGeneratedUserIdLength) {
+ metricsUserDSID = (_c = objectGraph.user.dsid) !== null && _c !== void 0 ? _c : null;
+ }
+ if (isSome(metricsUserDSID) && metricsUserDSID.length > 0) {
+ eventFields["dsId"] = metricsUserDSID;
+ }
+ }
+ }
+ _decorateClick(eventFields) {
+ util.adjustGhostHintFieldsForClick(eventFields);
+ // clicks have snapshot impressions field.
+ this._decorateSearchResultImpressions(eventFields);
+ this._decorateSearchFocusImpressions(eventFields);
+ MetricsReferralContext.shared.addReferralDataToEventIfNecessary(eventFields);
+ this._filterBuyParams(eventFields);
+ /// rdar://118948967 (Disable snapshot impressions on iOS 17)
+ const pageType = serverData.asString(eventFields, "pageType");
+ if (isNothing(pageType) || !pageType.toLowerCase().includes("search")) {
+ delete eventFields["impressions"];
+ }
+ stripUniqueImpressionIdsIfNecessary(eventFields);
+ }
+ /**
+ * Apply the rules specific to the `impression` event.
+ *
+ * @param objectGraph Current object graph
+ * @param eventFields The fields which will be used to construct a built event.
+ * @returns Whether or not the impressions event is valid and non-empty
+ */
+ _decorateImpressions(objectGraph, eventFields) {
+ if (serverData.isNullOrEmpty(eventFields["impressions"])) {
+ return false;
+ }
+ this._derezFastImpressions(eventFields);
+ this._decorateSearchResultImpressions(eventFields);
+ this._stripContentRatingImpressionFields(eventFields);
+ const refUrl = serverData.asString(eventFields, "refUrl");
+ if (isSome(refUrl) && refUrl.length > 0) {
+ eventFields["searchTerm"] = util.searchTermFromRefURL(refUrl);
+ delete eventFields["refUrl"];
+ }
+ if (objectGraph.client.isVision) {
+ const pageUrl = serverData.asString(eventFields, "pageUrl");
+ if (isSome(pageUrl) && isNothing(eventFields["searchTerm"])) {
+ const searchTerm = util.searchTermFromProductURL(pageUrl);
+ if (isSome(searchTerm)) {
+ eventFields["searchTerm"] = searchTerm;
+ }
+ }
+ }
+ stripUniqueImpressionIdsIfNecessary(eventFields);
+ // We need the impressionQueue for _derezFastImpressions, so we know which events to derez, but then we need to
+ // remove it from the event
+ // rdar://144333694 ([Ad Platforms][CrystalE][iOS]Seeing 'Impression Queue' as a top level
+ // field for impressions which was removed earlier)
+ delete eventFields["impressionQueue"];
+ return true;
+ }
+ /**
+ * Apply the rules specific to the `media` event.
+ *
+ * @param eventFields The fields which will be used to construct a built event.
+ */
+ _decorateMedia(eventFields) {
+ const position = serverData.asNumber(eventFields, "position");
+ if (!serverData.isNull(position)) {
+ eventFields["position"] = Math.round(position);
+ }
+ }
+ /**
+ * Apply the rules specific to the `buyparams` .
+ *
+ * @param eventFields The fields which will be used to construct a built event.
+ */
+ _filterBuyParams(eventFields) {
+ const buyParamsString = serverData.asString(eventFields, "actionDetails.buyParams");
+ if (isSome(buyParamsString) && buyParamsString.length > 0) {
+ const buyParams = new BuyParameters(buyParamsString);
+ const disallowedFields = ["ownerDsid"];
+ disallowedFields.forEach((key) => {
+ buyParams.set(key, null, null);
+ });
+ if (isSome(eventFields["actionDetails"])) {
+ eventFields["actionDetails"]["buyParams"] = buyParams.toString();
+ }
+ }
+ }
+ /**
+ * Apply the rules specific to the `page` event.
+ *
+ * @param objectGraph Current object graph
+ * @param eventFields The fields which will be used to construct a built event.
+ */
+ _decoratePage(objectGraph, eventFields) {
+ const page = serverData.asString(eventFields, "page");
+ if (!serverData.isNull(page)) {
+ eventFields["pageHistory"] = this._options.buyDecorator.getPageHistoryFor(page);
+ }
+ MetricsReferralContext.shared.setReferralDataForProductPageExtensionIfNecessary(eventFields);
+ MetricsReferralContext.shared.beginReferralContextForPageIfNecessary(eventFields);
+ MetricsReferralContext.shared.addReferralDataToEventIfNecessary(eventFields);
+ // Make sure to add the referral data before checking for valid refUrl
+ const refUrl = serverData.asString(eventFields, "refUrl");
+ if (!serverData.isNull(refUrl)) {
+ const refApp = util.extractSiriRefAppFromRefURL(refUrl);
+ const searchTerm = util.searchTermFromRefURL(refUrl);
+ if (refApp !== null && refApp.length > 0) {
+ eventFields["refApp"] = refApp;
+ }
+ if (searchTerm !== null && searchTerm.length > 0) {
+ eventFields["searchTerm"] = searchTerm;
+ }
+ }
+ if (objectGraph.client.isVision) {
+ const pageUrl = serverData.asString(eventFields, "pageUrl");
+ if (isSome(pageUrl) && isNothing(eventFields["searchTerm"])) {
+ const searchTerm = util.searchTermFromProductURL(pageUrl);
+ if (isSome(searchTerm)) {
+ eventFields["searchTerm"] = searchTerm;
+ }
+ }
+ }
+ }
+ /**
+ * Apply the rules specific to the `pageChange` event. The pageChange should have the same treatment
+ * as page event.
+ *
+ * @param eventFields The fields which will be used to construct a built event.
+ */
+ _decoratePageChange(objectGraph, eventFields) {
+ this._decoratePage(objectGraph, eventFields);
+ }
+ /**
+ * Apply the rules specific to the `search` event.
+ *
+ * @param objectGraph Current object graph
+ * @param eventFields The fields which will be used to construct a built event.
+ */
+ _decorateSearch(eventFields) {
+ eventFields["eventVersion"] = 3;
+ util.adjustGhostHintFieldsForSearch(eventFields);
+ }
+ /**
+ * Apply the rules specific to the `pageExit` event.
+ *
+ * @param eventFields The fields which will be used to construct a built event.
+ */
+ _decoratePageExit(eventFields) {
+ MetricsReferralContext.shared.endReferralContextIfNecessaryForPageEvent(eventFields);
+ }
+ // endsection
+ // section Filter
+ _filterExtraneous(eventFields) {
+ util.removeExtraGhostHintFields(eventFields);
+ MetricsReferralContext.shared.removeReferralContextInfoFromMetricsEvent(eventFields);
+ }
+ // endsection
+ // section Building Events
+ /**
+ * Create a metrics event by applying the business rules of App Store to a given fields blob.
+ *
+ * @param eventFields The fields to use to construct an event.
+ * @returns A built event ready for posting.
+ */
+ makeEvent(objectGraph, eventFields) {
+ var _a, _b;
+ if (preprocessor.GAMES_TARGET) {
+ // Until we get further with Privacy/Legal, the decision has been made to disable instrumentation
+ // for Game Overlay. the pre-consent fields provider is only active for Game Overlay. Other options
+ // for more fine-tuned control (_gameCenterPreConsent and _crossUsePreConsent) have been added to
+ // provide JS flexibility.
+ const isPreConsentFieldsProviderEnabled = eventFields["_isPreConsentFieldsProviderEnabled"];
+ const gameCenterPreConsent = eventFields["_gameCenterPreConsent"];
+ const crossUsePreConsent = eventFields["_crossUsePreConsent"];
+ if (isPreConsentFieldsProviderEnabled === true ||
+ gameCenterPreConsent === true ||
+ crossUsePreConsent === true) {
+ return new LintedMetricsEvent({});
+ }
+ delete eventFields["_isPreConsentFieldsProviderEnabled"];
+ delete eventFields["_gameCenterPreConsent"];
+ delete eventFields["_crossUsePreConsent"];
+ }
+ const eventType = serverData.asString(eventFields, "eventType");
+ if (this._options.isLoggingEnabled) {
+ objectGraph.console.log(`Building event for topic: ${eventType}`);
+ }
+ // rdar://135738684 (TLF: Off Store Lockups Removal)
+ // Currently all events from App Store Components are suppressed.
+ // Delete all fields to leave an empty event.
+ const app = eventFields["app"];
+ if (app === "com.apple.appstorecomponentsd") {
+ return new LintedMetricsEvent({});
+ }
+ const topic = serverData.asString(eventFields, "topic") || this._options.defaultTopic;
+ this._decorateAll(objectGraph, eventFields, topic);
+ // rdar://148554411 (Metrics: Disable PII collection from U13 accounts)
+ let isUnderThirteenAccount;
+ if (preprocessor.GAMES_TARGET && objectGraph.props.enabled("157263806-add-playerBridge-askGlobal")) {
+ isUnderThirteenAccount = (_b = (_a = objectGraph.player) === null || _a === void 0 ? void 0 : _a.isUnderThirteen) !== null && _b !== void 0 ? _b : false;
+ }
+ else {
+ isUnderThirteenAccount = objectGraph.user.isUnderThirteen;
+ }
+ if (isUnderThirteenAccount) {
+ delete eventFields["dsId"];
+ delete eventFields["userId"];
+ delete eventFields["canonicalAccountIdentifierOverride"];
+ }
+ const extRefUrl = eventFields["extRefUrl"];
+ if (extRefUrl && extRefUrl === "") {
+ delete eventFields["extRefUrl"];
+ }
+ switch (eventType) {
+ case "click":
+ this._decorateClick(eventFields);
+ break;
+ case "exit":
+ break;
+ case "impressions":
+ const isValidEvent = this._decorateImpressions(objectGraph, eventFields);
+ if (!isValidEvent) {
+ // We want to filter out empty impressions events,
+ // and passing an empty event will contractually drop it from the metrics recorder
+ return new LintedMetricsEvent({});
+ }
+ break;
+ case "media":
+ this._decorateMedia(eventFields);
+ break;
+ case "page":
+ this._decoratePage(objectGraph, eventFields);
+ break;
+ case "pageChange":
+ this._decoratePageChange(objectGraph, eventFields);
+ break;
+ case "pageExit":
+ this._decoratePageExit(eventFields);
+ break;
+ case "search":
+ this._decorateSearch(eventFields);
+ break;
+ default:
+ break;
+ }
+ this._filterExtraneous(eventFields);
+ if (objectGraph.bag.metricsIdMigrationEnabled) {
+ util.removeDSIDFields(eventFields);
+ }
+ return new LintedMetricsEvent(eventFields);
+ }
+}
+/**
+ * The length of a client generated user ID.
+ */
+EventLinter.clientGeneratedUserIdLength = 24;
+/**
+ * Key whose value indicates if event fields contain iAd data.
+ */
+EventLinter.hasIAdData = "hasiAdData";
+//# sourceMappingURL=event-linter.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/buy.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/buy.js
new file mode 100644
index 0000000..b39f9cf
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/buy.js
@@ -0,0 +1,331 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { BuyParameters } from "../../../foundation/metrics/buy-parameters";
+import { URL } from "../../../foundation/network/urls";
+import * as productVariants from "../../product-page/product-page-variants";
+import { MetricsReferralContext } from "../metrics-referral-context";
+import * as metricsPosting from "../posting";
+import * as misc from "./misc";
+import { IAdSearchInformation } from "./models";
+import * as metricsUtil from "./util";
+//* *************************
+//* Buy Metrics
+//* *************************
+/**
+ * Adds bag driven metrics fields.
+ * @param objectGraph
+ * @returns
+ */
+function addBagMetricsToBuyParams(objectGraph, baseBuyParams) {
+ const buyParams = new BuyParameters(baseBuyParams);
+ const metricsConfiguration = objectGraph.bag.metricsConfiguration;
+ const language = serverData.asString(metricsConfiguration, "metricsBase.language");
+ buyParams.set("languageId", language);
+ return buyParams.toString();
+}
+export function addPageMetricsToBuyParams(objectGraph, baseBuyParams, adamId, pageInformation, targetType, kind, metricsPlatformDisplayStyle, productVariantData, inAppEventId, excludeCrossfireAttribution, extRefApp2, extRefUrl2) {
+ const buyParams = new BuyParameters(baseBuyParams);
+ addPageMetricsToBuyParamsObject(objectGraph, buyParams, adamId, pageInformation, targetType, kind, metricsPlatformDisplayStyle, productVariantData, inAppEventId, excludeCrossfireAttribution, extRefApp2, extRefUrl2);
+ addExtraInfoToBuyParamsObject(objectGraph, buyParams, pageInformation);
+ return buyParams.toString();
+}
+export function addPageMetricsToBuyParamsObject(objectGraph, buyParams, adamId, pageInformation, targetType, kind, metricsPlatformDisplayStyle, productVariantData, inAppEventId, excludeCrossfireAttribution, extRefApp2, extRefUrl2) {
+ var _a, _b, _c;
+ const fields = misc.fieldsFromPageInformation(pageInformation);
+ if (targetType) {
+ buyParams.set("impressionType", targetType); // impressionType == targetType == locationType.
+ }
+ if (kind) {
+ buyParams.set("kind", kind);
+ }
+ const pageId = serverData.asString(serverData.asJSONData(fields), "pageId");
+ buyParams.set("pageId", pageId);
+ const pageType = serverData.asString(serverData.asJSONData(fields), "pageType");
+ buyParams.set("pageType", pageType);
+ // Ad container id
+ const iAdContainerId = serverData.asString(serverData.asJSONData(fields), "iAdContainerId");
+ if (isSome(iAdContainerId) && iAdContainerId.length > 0) {
+ buyParams.set(adBuyParamKeys.containerId, iAdContainerId, null);
+ }
+ // we add search terms to the page if the page is for the item we are buying, or if
+ // its for a streamlined contingent offer
+ const pageIds = (_a = pageId === null || pageId === void 0 ? void 0 : pageId.split("_")) !== null && _a !== void 0 ? _a : [];
+ const pageMatchesProduct = pageIds.includes(adamId);
+ const isProductPage = pageType === "Software";
+ const isStreamlinedContingentOffer = ((_b = buyParams.get("contingentItemId", null)) === null || _b === void 0 ? void 0 : _b.length) > 0;
+ if (!isProductPage || pageMatchesProduct || isStreamlinedContingentOffer) {
+ // If there is a native search term, it will overwrite this
+ // initial value below in `addNativeMetricsToBuyParams`.
+ let searchTerm = serverData.asString(serverData.asJSONData(pageInformation), "searchTermContext.resultsTerm");
+ // A search term may be attached to the product URL as a means of associating buys with searches.
+ if (serverData.isNullOrEmpty(searchTerm)) {
+ searchTerm = metricsUtil.searchTermFromProductURL(pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.pageUrl);
+ }
+ if (!serverData.isNull(searchTerm)) {
+ buyParams.set("searchTerm", searchTerm);
+ }
+ }
+ if (isProductPage && isSome(pageInformation) && isSome(pageInformation.pageUrl)) {
+ const pageUrl = new URL(pageInformation.pageUrl);
+ if (((_c = pageUrl.query) === null || _c === void 0 ? void 0 : _c["context"]) === "browserChoice") {
+ buyParams.set("prevPage", "BrowserChoice");
+ buyParams.set("browserChoiceScreenBuy", "1", null);
+ }
+ }
+ productVariants.addProductPageVariantMetricsToBuyParams(buyParams, adamId, pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.productVariantData, productVariantData !== null && productVariantData !== void 0 ? productVariantData : undefined);
+ if (!serverData.isNull(metricsPlatformDisplayStyle) && metricsPlatformDisplayStyle.length > 0) {
+ buyParams.set("platformDisplayStyle", metricsPlatformDisplayStyle);
+ }
+ // App Event ID
+ buyParams.set("inAppEventId", inAppEventId);
+ // Referrer
+ if (!excludeCrossfireAttribution) {
+ if (serverData.isDefinedNonNull(MetricsReferralContext.shared.activeReferralData)) {
+ buyParams.set("extRefApp2", MetricsReferralContext.shared.activeReferralData.extRefApp2, null);
+ buyParams.set("extRefUrl2", MetricsReferralContext.shared.activeReferralData.extRefUrl2, null);
+ if (isSome(MetricsReferralContext.shared.activeReferralData.kind)) {
+ const extRefAppKindName = MetricsReferralContext.shared.activeReferralData.kind.name;
+ if (extRefAppKindName === "clip" || extRefAppKindName === "appClip") {
+ buyParams.set("hostApp", "com.apple.AppStore.clipOverlay");
+ }
+ }
+ }
+ else {
+ buyParams.set("extRefApp2", extRefApp2, null);
+ buyParams.set("extRefUrl2", extRefUrl2, null);
+ }
+ }
+}
+function addExtraInfoToBuyParamsObject(objectGraph, buyParams, pageInformation) {
+ var _a, _b;
+ if (isNothing(pageInformation)) {
+ return;
+ }
+ const extraInfo = [];
+ const hasiAdData = isSome((_a = pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.clickFields["hasiAdData"]);
+ if (hasiAdData) {
+ const iAdExtraInfoKey = isIAdFromCurrentPage(objectGraph, pageInformation) ? "iAdSponsored" : "iAdOriginated";
+ extraInfo.push({ key: iAdExtraInfoKey, value: "true" });
+ }
+ if (serverData.isDefinedNonNullNonEmpty(extraInfo)) {
+ const extraInfoParamValue = extraInfo
+ .map((extraInfoValue) => `${extraInfoValue.key}=${extraInfoValue.value}`)
+ .join(";");
+ const encodedValue = (_b = objectGraph.cryptography) === null || _b === void 0 ? void 0 : _b.base64Encode(extraInfoParamValue);
+ if (isSome(encodedValue)) {
+ buyParams.set("extraInfo", encodedValue);
+ }
+ }
+}
+/**
+ * Determines if an iAd is from the current page by comparing the iAd's placement type with the page type.
+ *
+ * This function validates whether an advertisement is correctly placed on the appropriate page type.
+ * Different ad placement types are designed for specific page contexts, and this function ensures
+ * that the placement matches the current page context.
+ *
+ * @param objectGraph - The AppStore object graph providing access to app services and utilities
+ * @param pageInformation - Information about the current page, including its type and iAd information
+ * @returns True if the iAd's placement type matches the current page type, false otherwise
+ * or if pageInformation or iAdInfo is missing
+ */
+function isIAdFromCurrentPage(objectGraph, pageInformation) {
+ const iAdInfo = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo;
+ if (isNothing(pageInformation) || isNothing(iAdInfo)) {
+ return false;
+ }
+ const pageType = pageInformation.baseFields["pageType"];
+ const iAdPlacementType = IAdSearchInformation.placementTypeFromPlacementId(objectGraph, iAdInfo.placementId);
+ switch (iAdPlacementType) {
+ case "searchLanding":
+ return pageType === "SearchLanding";
+ case "today":
+ return pageType === "Today";
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ return pageType === "Software" || pageType === "SoftwareBundle";
+ default:
+ return pageType === "Search";
+ }
+}
+/**
+ * Returns a copy of the specified buy parameters enriched with native metrics fields.
+ *
+ * @param baseBuyParams The buy parameters of an app which have previously been
+ * decorated with page metrics. Passing buy parameters that were not decorated with page
+ * metrics can result in incorrect search terms being reported.
+ * @param adamId The identifier of the app whose buy parameters are being decorated.
+ * @param excludeCrossfireAttribution Specifies whether crossfire attribution should be
+ * excluded from the decorated buy params.
+ * @param nativeMetrics Metrics fields provided by native code.
+ * @param osMetrics Metrics fields describing the device's OS provided by native code.
+ * @returns A copy of `baseBuyParams` enriched with native metrics fields.
+ */
+export function addNativeValuesToBuyParams(objectGraph, baseBuyParams, adamId, excludeCrossfireAttribution, isAppInstalled, nativeMetrics, osMetrics) {
+ const baseBuyParamsObject = new BuyParameters(baseBuyParams);
+ addNativeValuesToBuyParamsObject(objectGraph, baseBuyParamsObject, adamId, excludeCrossfireAttribution, isAppInstalled, nativeMetrics, osMetrics);
+ return baseBuyParamsObject.toString();
+}
+/**
+ * Returns a copy of the specified buy parameters enriched with native metrics fields.
+ *
+ * @param baseBuyParams The buy parameters of an app which have previously been
+ * decorated with page metrics. Passing buy parameters that were not decorated with page
+ * metrics can result in incorrect search terms being reported.
+ * @param adamId The identifier of the app whose buy parameters are being decorated.
+ * @param excludeCrossfireAttribution Specifies whether crossfire attribution should be
+ * excluded from the decorated buy params.
+ * @param nativeMetrics Metrics fields provided by native code.
+ * @param osMetrics Metrics fields describing the device's OS provided by native code.
+ * @returns A copy of `baseBuyParams` enriched with native metrics fields.
+ */
+export function addNativeValuesToBuyParamsObject(objectGraph, buyParams, adamId, excludeCrossfireAttribution, isAppInstalled, nativeMetrics, osMetrics) {
+ var _a, _b;
+ const pageContext = serverData.asString(nativeMetrics, "pageContext");
+ buyParams.set("pageContext", pageContext);
+ const paymentTopic = objectGraph.props.enabled("paymentTopicFromBag")
+ ? objectGraph.bag.metricsPaymentTopic
+ : undefined;
+ buyParams.set("topic", paymentTopic !== null && paymentTopic !== void 0 ? paymentTopic : objectGraph.bag.metricsTopic);
+ metricsPosting.buyDecorator.useNativeValues(nativeMetrics);
+ const decoratorParams = metricsPosting.buyDecorator.params;
+ for (const key of Object.keys(decoratorParams)) {
+ if (key === "prevPage" && isSome(buyParams.get("prevPage"))) {
+ // We may have added a `prevPage` param earlier, specifically don't override this value if we have.
+ continue;
+ }
+ const value = serverData.asString(decoratorParams, key);
+ buyParams.set(key, value);
+ }
+ if (!serverData.isNull(osMetrics)) {
+ for (const key of Object.keys(osMetrics)) {
+ const value = serverData.asString(osMetrics, key);
+ buyParams.set(key, value);
+ buyParams.set(key, value, null);
+ }
+ }
+ if (!nativeMetrics) {
+ // ^^ Is this actually needed?
+ buyParams.set("searchTerm", null);
+ buyParams.set("platformDisplayStyle", null);
+ return;
+ }
+ const hostApp = serverData.asString(nativeMetrics, "hostApp");
+ if (isSome(hostApp) && hostApp.length > 0) {
+ buyParams.set("hostApp", hostApp);
+ }
+ const isContingentOffer = ((_a = buyParams.get("contingentItemId", null)) === null || _a === void 0 ? void 0 : _a.length) > 0;
+ const app = serverData.asString(nativeMetrics, "app");
+ if (isContingentOffer) {
+ // Contingent Offer Streamline buy app install flag
+ buyParams.set("app", objectGraph.host.clientIdentifier);
+ }
+ else if (isSome(app) && app.length > 0) {
+ buyParams.set("app", app);
+ }
+ if (!excludeCrossfireAttribution && !MetricsReferralContext.shared.shouldUseJSReferralData) {
+ const extRefUrl = serverData.asString(nativeMetrics, "extRefUrl2");
+ const extractedSiriRefApp = metricsUtil.extractSiriRefAppFromRefURL(extRefUrl);
+ if (extRefUrl && extractedSiriRefApp) {
+ nativeMetrics["refApp"] = extractedSiriRefApp;
+ }
+ // ^^ Is this actually needed?
+ const usageContext = serverData.asString(nativeMetrics, "usageContext");
+ if (isSome(usageContext)) {
+ switch (usageContext) {
+ case "overlay":
+ buyParams.set("hostApp", "com.apple.AppStore.overlay");
+ break;
+ case "overlayClip":
+ buyParams.set("hostApp", "com.apple.AppStore.clipOverlay");
+ break;
+ default:
+ break;
+ }
+ buyParams.set("extRefApp2", hostApp, null);
+ }
+ else {
+ const extRefApp2 = serverData.asString(nativeMetrics, "extRefApp2");
+ buyParams.set("extRefApp2", extRefApp2, null);
+ const extRefUrl2 = serverData.asString(nativeMetrics, "extRefUrl2");
+ buyParams.set("extRefUrl2", extRefUrl2, null);
+ const extRefAppType = serverData.asString(nativeMetrics, "extRefAppType");
+ if (extRefAppType === "clip") {
+ buyParams.set("hostApp", "com.apple.AppStore.clipOverlay");
+ }
+ }
+ }
+ // we add search terms to the page if the page is for the item we are buying
+ const pageId = buyParams.get("pageId");
+ const pageType = buyParams.get("pageType");
+ const pageIds = (_b = pageId === null || pageId === void 0 ? void 0 : pageId.split("_")) !== null && _b !== void 0 ? _b : [];
+ const pageMatchesProduct = pageIds.includes(adamId);
+ const isProductPage = pageType === "Software";
+ if (!isProductPage || pageMatchesProduct) {
+ const searchTerm = metricsUtil.searchTermFromRefURL(serverData.asString(nativeMetrics, "refUrl"));
+ if (serverData.isDefinedNonNull(searchTerm)) {
+ buyParams.set("searchTerm", searchTerm);
+ }
+ }
+ // Remove ownerDsid from buyParams
+ buyParams.set("ownerDsid", null, null);
+}
+/**
+ *
+ * @param adamId The identifier of the app whose buy parameters are being decorated.
+ * @param buyParams The buy parameters of an app from a Media API response.
+ * @param pageInformation The purchase configuration page metrics configuration
+ * from an offer action.
+ * @param excludeCrossfireAttribution Specifies whether crossfire attribution should be
+ * excluded from the decorated buy params.
+ * @param targetType The target type that buy occured on
+ * @param kind The Kind that buy occured on, if any.
+ * @param metricsPlatformDisplayStyle The platform display style from an offer action.
+ * @param nativeMetrics Metrics fields provided by native code.
+ * @param osMetrics Metrics fields describing the device's OS provided by native code.
+ * @returns A copy of `buyParams` decorated with all metrics.
+ */
+export function addMetricsToBuyParams(objectGraph, adamId, buyParams, pageInformation, excludeCrossfireAttribution, isAppInstalled, targetType, kind, metricsPlatformDisplayStyle, nativeMetrics, osMetrics, productVariantData, inAppEventId, extRefApp2, extRefUrl2) {
+ // Future: Build BuyParameters once, instead of creating it per modification.
+ const bagMetricsBuyParams = addBagMetricsToBuyParams(objectGraph, buyParams);
+ const pageMetricsBuyParams = addPageMetricsToBuyParams(objectGraph, bagMetricsBuyParams, adamId, pageInformation, targetType, kind, metricsPlatformDisplayStyle, productVariantData, inAppEventId, excludeCrossfireAttribution, extRefApp2, extRefUrl2);
+ const nativeMetricsBuyParams = addNativeValuesToBuyParams(objectGraph, pageMetricsBuyParams, adamId, excludeCrossfireAttribution, isAppInstalled, nativeMetrics, osMetrics);
+ return nativeMetricsBuyParams;
+}
+/**
+ * Keys used for ad-related buy params.
+ */
+export const adBuyParamKeys = {
+ containerId: "mtContainerId",
+ placementId: "mtIadPlacementId",
+ templateType: "mtIadTemplateType",
+};
+/**
+ * Copy ad-related buy params to the override buy params.
+ * Override buy params are used for updated and redownloads, and in those cases we lose iAd download attribution.
+ * If the original buy params had ad fields, we should copy them over here.
+ * @param originalBuyParamsString The original buy params attached to the offerAction.
+ * @param overrideBuyParamsString The override buy params from the purchase.
+ * @returns An updated buy params string with any ad-related fields copied over.
+ */
+export function copyAdBuyParamsToOverrideBuyParams(originalBuyParamsString, overrideBuyParamsString) {
+ const originalBuyParams = new BuyParameters(originalBuyParamsString);
+ const overrideBuyParams = new BuyParameters(overrideBuyParamsString);
+ const originalPlacementId = originalBuyParams.get(adBuyParamKeys.placementId, null);
+ if ((originalPlacementId === null || originalPlacementId === void 0 ? void 0 : originalPlacementId.length) > 0 &&
+ serverData.isNullOrEmpty(overrideBuyParams.get(adBuyParamKeys.placementId, null))) {
+ overrideBuyParams.set(adBuyParamKeys.placementId, originalPlacementId, null);
+ }
+ const originalContainerId = originalBuyParams.get(adBuyParamKeys.containerId, null);
+ if ((originalContainerId === null || originalContainerId === void 0 ? void 0 : originalContainerId.length) > 0 &&
+ serverData.isNullOrEmpty(overrideBuyParams.get(adBuyParamKeys.containerId, null))) {
+ overrideBuyParams.set(adBuyParamKeys.containerId, originalContainerId, null);
+ }
+ const originalTemplateType = originalBuyParams.get(adBuyParamKeys.templateType, null);
+ if ((originalTemplateType === null || originalTemplateType === void 0 ? void 0 : originalTemplateType.length) > 0 &&
+ serverData.isNullOrEmpty(overrideBuyParams.get(adBuyParamKeys.templateType, null))) {
+ overrideBuyParams.set(adBuyParamKeys.templateType, originalTemplateType, null);
+ }
+ return overrideBuyParams.toString();
+}
+//# sourceMappingURL=buy.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/clicks.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/clicks.js
new file mode 100644
index 0000000..cc7b6f2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/clicks.js
@@ -0,0 +1,458 @@
+import * as metricsBuilder from "../builder";
+import * as metricsConstants from "./constants";
+import * as metricsLocation from "./location";
+import * as metricsModels from "./models";
+import * as metricsUtil from "./util";
+import * as metricsMisc from "./misc";
+import * as validation from "@jet/environment/json/validation";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as dateUtil from "../../../foundation/util/date-util";
+//* *************************
+//* Click Metrics
+//* *************************
+export function clickOptionsForLockup(objectGraph, data, baseOptions, clickOptions) {
+ return validation.context("clickOptionsForLockup", () => {
+ const contextualAdamId = data.id.slice();
+ let id = data.id;
+ if (baseOptions.anonymizationOptions !== undefined &&
+ baseOptions.anonymizationOptions.anonymizationString.length > 0) {
+ id = baseOptions.anonymizationOptions.anonymizationString;
+ }
+ const metricsOptions = {
+ ...baseOptions,
+ ...clickOptions,
+ id: id,
+ contextualAdamId: contextualAdamId,
+ softwareType: metricsUtil.softwareTypeForData(objectGraph, data),
+ };
+ if (serverData.isNullOrEmpty(metricsOptions.targetType)) {
+ metricsOptions.targetType = objectGraph.client.isVision ? "lockupSmall" : "lockup";
+ }
+ // There's a delicate false split from `baseOptions` and click options interface. Some paths pre-populate an unexpected `kind`, but we don't want to change that suddenly.
+ // Derive `kind` from data if missing.
+ if (serverData.isNull(metricsOptions.kind)) {
+ metricsOptions.kind = metricsUtil.metricsKindFromData(objectGraph, data);
+ }
+ // Include offerType for pre-order impressions
+ const isPreorder = mediaAttributes.attributeAsBoolean(data, "isPreorder");
+ if (isPreorder) {
+ metricsOptions.offerType = "preorder";
+ }
+ return metricsOptions;
+ });
+}
+export function addBuyEventToOfferActionOnPage(objectGraph, action, options, isPreorder, isDefaultBrowser) {
+ var _a, _b, _c, _d, _e;
+ const pageInformation = options.pageInformation;
+ const buttonMetricsOptions = {
+ ...options,
+ targetType: "button",
+ };
+ const metricsLocations = metricsLocation.createContentLocation(objectGraph, buttonMetricsOptions, (_a = action.title) !== null && _a !== void 0 ? _a : "");
+ let targetId = (_c = (_b = options.anonymizationOptions) === null || _b === void 0 ? void 0 : _b.anonymizationString) !== null && _c !== void 0 ? _c : action.adamId;
+ if (isSome(options.targetId) && ((_d = options.targetId) === null || _d === void 0 ? void 0 : _d.length) > 0) {
+ targetId = options.targetId;
+ }
+ addBuyEventToOfferAction(objectGraph, action, targetId, isPreorder, pageInformation, metricsLocations, (_e = options.isAdvert) !== null && _e !== void 0 ? _e : false, options.recoMetricsData, isDefaultBrowser);
+}
+export function addBuyEventToOfferActionInheritingMetrics(objectGraph, newAction, originalAction, isPreorder) {
+ const originalPageInformation = originalAction.purchaseConfiguration.pageInformation;
+ // Find some event to inherit location event field from.
+ let metricsLocations;
+ if (isSome(originalAction.actionMetrics)) {
+ for (const event of originalAction.actionMetrics.data) {
+ metricsLocations = serverData.asArrayOrEmpty(serverData.asJSONValue(event.fields), "location");
+ if (metricsLocations) {
+ break;
+ }
+ }
+ }
+ // Fine for now - CMC doesn't use anonymization.
+ addBuyEventToOfferAction(objectGraph, newAction, newAction.adamId, isPreorder, originalPageInformation, metricsLocations, false);
+}
+function addBuyEventToOfferAction(objectGraph, action, targetId, isPreorder, pageInformation, metricsLocations, isAdvert, recoMetricsData, isDefaultBrowser) {
+ var _a, _b, _c, _d, _e;
+ const eventFields = {};
+ if (pageInformation) {
+ // We can't always check that `pageInformation` is an `instanceof MetricsPageInformation` here, because sometimes
+ // `pageInformation` is reconstituted from JSON which means it doesn't get the underlying type information.
+ // Instead, cast it and check for the existence of individual properties on the cast object.
+ const metricsPageInformation = pageInformation;
+ if (isAdvert &&
+ ((_c = (_b = (_a = metricsPageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.iAdAdamId) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) > 0 &&
+ isSome(metricsPageInformation.iAdInfo) &&
+ metricsPageInformation.iAdInfo.iAdAdamId === action.adamId) {
+ Object.assign(eventFields, metricsPageInformation.iAdInfo.clickFields);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(metricsPageInformation.searchTermContext)) {
+ eventFields["searchTerm"] = metricsPageInformation.searchTermContext.term;
+ }
+ }
+ // Add the reco metrics data if available
+ if (isSome(recoMetricsData)) {
+ Object.assign(eventFields, recoMetricsData);
+ }
+ eventFields["actionDetails"] = { buyParams: action.purchaseConfiguration.buyParams };
+ if (metricsLocations !== undefined) {
+ eventFields["location"] = metricsLocations;
+ }
+ // This hack where we add the adamId to the eventFields can be removed when `ActionDispatcher`
+ // is fully enabled and old ActionRunner code is removed. The custom data below replaces this.
+ eventFields[metricsConstants.contextualAdamIdKey] = action.adamId;
+ action.actionMetrics.custom[metricsConstants.contextualAdamIdKey] = action.adamId;
+ // Add Pre-order fields
+ if (isPreorder) {
+ eventFields["offerType"] = "preorder";
+ if (serverData.isDefinedNonNull(action.expectedReleaseDate)) {
+ eventFields["offerReleaseDate"] = dateUtil.millisecondsToUTCMidnightFromLocalDate(action.expectedReleaseDate);
+ }
+ }
+ const clickEvent = metricsBuilder.createMetricsClickData(objectGraph, targetId, "button", eventFields, undefined, isDefaultBrowser);
+ // This hack where we add the adamId to the eventFields can be removed when `ActionDispatcher`
+ // is fully enabled and old ActionRunner code is removed. The custom data below replaces this.
+ eventFields[metricsConstants.contextualAdamIdKey] = action.adamId; // needed for `appState`
+ action.actionMetrics.custom[metricsConstants.contextualAdamIdKey] = action.adamId;
+ clickEvent.includingFields.push("appState");
+ if (action.purchaseConfiguration.isArcadeApp) {
+ // Include button names in arcade buy action metrics
+ clickEvent.includingFields.push("buttonName");
+ }
+ const shouldIncludeAdRotationFields = (_e = (_d = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo) === null || _d === void 0 ? void 0 : _d.shouldIncludeAdRotationFields) !== null && _e !== void 0 ? _e : false;
+ if (isAdvert && shouldIncludeAdRotationFields) {
+ clickEvent.includingFields.push("advertRotation");
+ }
+ const shouldIncludeAdWindowFields = isAdvert && objectGraph.client.isPad;
+ if (shouldIncludeAdWindowFields) {
+ clickEvent.includingFields.push("advertDeviceWindow");
+ }
+ action.actionMetrics.addMetricsData(clickEvent);
+}
+export function addClickEventToArcadeBuyInitiateAction(objectGraph, action, options) {
+ var _a;
+ addClickEventToAction(objectGraph, action, {
+ ...options,
+ actionType: "buyInitiate",
+ subscriptionSKU: (_a = objectGraph.bag.arcadeProductId) !== null && _a !== void 0 ? _a : undefined,
+ actionContext: "Arcade",
+ targetType: "button",
+ });
+}
+export function addClickEventToAction(objectGraph, action, options, addIAdFields = false, targetType) {
+ var _a, _b, _c, _d, _e, _f, _g;
+ let actionType = options.actionType;
+ if (!actionType) {
+ actionType = "navigate";
+ }
+ const eventFields = {
+ actionType: actionType,
+ };
+ // Ad click events will be wrapped in a CompoundAction. We still want to handle attaching `actionUrl` properly
+ // below if a `FlowAction` or `ExternalUrlAction` are inside the `CompoundAction`, so we create an array of actions
+ // to check.
+ let actions;
+ if (action instanceof models.CompoundAction) {
+ actions = action.actions;
+ }
+ else {
+ actions = [action];
+ }
+ actions.forEach((subAction) => {
+ // Set the action URL if appropriate
+ if (subAction instanceof models.FlowAction) {
+ const flowAction = subAction;
+ eventFields["actionUrl"] = flowAction.pageUrl;
+ }
+ else if (subAction instanceof models.ExternalUrlAction) {
+ const flowAction = subAction;
+ eventFields["actionUrl"] = flowAction.url;
+ }
+ });
+ if (options.actionDetails) {
+ eventFields["actionDetails"] = options.actionDetails;
+ }
+ if (options.actionContext) {
+ eventFields["actionContext"] = options.actionContext;
+ }
+ // Add offer type for pre-orders
+ if (serverData.isDefinedNonNull(options.offerType)) {
+ eventFields["offerType"] = options.offerType;
+ }
+ // Add release date for pre-orders
+ if (serverData.isDefinedNonNull(options.offerReleaseDate)) {
+ eventFields["offerReleaseDate"] = dateUtil.millisecondsToUTCMidnightFromLocalDate(options.offerReleaseDate);
+ }
+ const title = (_c = (_b = (_a = options.anonymizationOptions) === null || _a === void 0 ? void 0 : _a.anonymizationString) !== null && _b !== void 0 ? _b : action === null || action === void 0 ? void 0 : action.title) !== null && _c !== void 0 ? _c : "";
+ eventFields["location"] = metricsLocation.createContentLocation(objectGraph, options, title);
+ // Search Term
+ if (options.pageInformation && options.pageInformation.searchTermContext) {
+ eventFields["searchTerm"] = options.pageInformation.searchTermContext.term;
+ }
+ if (serverData.isDefinedNonNull(options.softwareType)) {
+ eventFields["softwareType"] = options.softwareType;
+ }
+ // Advert Metrics
+ let additionalIncludingFields;
+ // Do we need both `options.isAdvert` and `addIAdFields`?
+ if ((options.isAdvert || options.isAdEligible) && addIAdFields && ((_d = options.pageInformation) === null || _d === void 0 ? void 0 : _d.iAdInfo)) {
+ Object.assign(eventFields, options.pageInformation.iAdInfo.clickFields);
+ if (objectGraph.client.isPad) {
+ additionalIncludingFields = ["advertDeviceWindow"];
+ }
+ }
+ if (options.mercuryMetricsData) {
+ Object.assign(eventFields, options.mercuryMetricsData);
+ }
+ if (isSome(options.subjectIds)) {
+ eventFields["subjectIds"] = options.subjectIds;
+ }
+ const event = metricsBuilder.createMetricsClickData(objectGraph, options.id, targetType !== null && targetType !== void 0 ? targetType : metricsUtil.targetTypeForMetricsOptions(objectGraph, options), eventFields, additionalIncludingFields);
+ // Include button names in arcade buy action metrics
+ const isArcadeBuyAction = options.actionContext === "Arcade" && (options.actionType === "buy" || options.actionType === "buyInitiate");
+ if (isArcadeBuyAction) {
+ event.includingFields.push("buttonName");
+ // This hack where we add the adamId to the eventFields can be removed when `ActionDispatcher`
+ // is fully enabled and old ActionRunner code is removed. The custom data below replaces this.
+ event.fields[metricsConstants.contextualAdamIdKey] = options.contextualAdamId;
+ if (isSome(action.adamId)) {
+ action.actionMetrics.custom[metricsConstants.contextualAdamIdKey] = action.adamId;
+ }
+ }
+ // Include ad rotation metrics and click events if enabled for placement.
+ const shouldIncludeAdRotationFields = (_g = (_f = (_e = options.pageInformation) === null || _e === void 0 ? void 0 : _e.iAdInfo) === null || _f === void 0 ? void 0 : _f.shouldIncludeAdRotationFields) !== null && _g !== void 0 ? _g : false;
+ if (options.isAdvert && shouldIncludeAdRotationFields) {
+ event.includingFields.push("advertRotation");
+ }
+ action.actionMetrics.addMetricsData(event);
+}
+/**
+ *
+ * @param objectGraph The App Store Object Graph
+ * @param action The search cancel or dismiss action
+ * @param options The metrics click options
+ * @param targetType The metrics click target type
+ * @param searchTerm The current search term
+ */
+export function addClickEventToSearchCancelOrDismissAction(objectGraph, action, options, targetType, searchTerm) {
+ const eventFields = {
+ searchTerm: searchTerm,
+ actionType: options.actionType,
+ };
+ if (options.actionDetails) {
+ eventFields["actionDetails"] = options.actionDetails;
+ }
+ if (options.actionContext) {
+ eventFields["actionContext"] = options.actionContext;
+ }
+ // Search Term
+ if (searchTerm) {
+ eventFields["searchTerm"] = searchTerm;
+ }
+ if (serverData.isDefinedNonNull(options.softwareType)) {
+ eventFields["softwareType"] = options.softwareType;
+ }
+ if (options.mercuryMetricsData) {
+ Object.assign(eventFields, options.mercuryMetricsData);
+ }
+ const event = metricsBuilder.createMetricsClickData(objectGraph, options.id, targetType !== null && targetType !== void 0 ? targetType : metricsUtil.targetTypeForMetricsOptions(objectGraph, options), eventFields);
+ action.actionMetrics.addMetricsData(event);
+}
+export function addClickEventsToAdLockup(objectGraph, lockup, clickOptions) {
+ var _a, _b, _c, _d, _e;
+ const searchAd = (_b = (_a = lockup.searchAdOpportunity) === null || _a === void 0 ? void 0 : _a.searchAd) !== null && _b !== void 0 ? _b : lockup.searchAd;
+ if (serverData.isNull(searchAd)) {
+ return;
+ }
+ // Theoretically, we would probably be fine to just overwrite the click event action metrics below in `addClickEventToAction`.
+ // But we'll keep the call to clear the metrics just to provide continuity with older code that ran this full path.
+ (_c = lockup.clickAction) === null || _c === void 0 ? void 0 : _c.actionMetrics.clearAll();
+ if (lockup.clickAction) {
+ addClickEventToAction(objectGraph, lockup.clickAction, clickOptions, true);
+ }
+ const pageInformation = clickOptions.pageInformation;
+ const eventFields = {
+ actionType: "ad_transparency",
+ };
+ if (pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo) {
+ Object.assign(eventFields, pageInformation.iAdInfo.clickFields);
+ }
+ const figaroEvent = metricsBuilder.createMetricsClickData(objectGraph, lockup.adamId, "button", eventFields);
+ const shouldIncludeAdRotationFields = (_e = (_d = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo) === null || _d === void 0 ? void 0 : _d.shouldIncludeAdRotationFields) !== null && _e !== void 0 ? _e : false;
+ if (shouldIncludeAdRotationFields) {
+ figaroEvent.includingFields.push("advertRotation");
+ }
+ searchAd.transparencyAction.actionMetrics.addMetricsData(figaroEvent);
+}
+export function addClickEventToSeeAllAction(objectGraph, action, url, options) {
+ const eventFields = {};
+ if (serverData.isDefinedNonNull(url)) {
+ eventFields["actionUrl"] = url;
+ }
+ if (!options.targetType) {
+ options.targetType = "button";
+ }
+ eventFields["location"] = metricsLocation.createBasicLocation(objectGraph, options, action.title);
+ eventFields["actionType"] = "navigate";
+ eventFields["target"] = "button_See All";
+ const event = metricsBuilder.createMetricsClickData(objectGraph, "See All", "button", eventFields);
+ action.actionMetrics.addMetricsData(event);
+}
+export function addClickEventToClearSearchHistoryAction(objectGraph, clearAction) {
+ // MAINTAINER'S NOTE:
+ // We intentionally use an unlocalized targetId, instead of localized `clearAction.title`,
+ // so metrics are consistent regardless of language.
+ const clickMetrics = metricsBuilder.createMetricsClickData(objectGraph, "Clear Searches", "button", {
+ actionType: "confirm",
+ });
+ clearAction.actionMetrics.addMetricsData(clickMetrics);
+}
+export function addClickEventToActivityFeedMetrics(objectGraph, actionMetrics, title, targetId, options) {
+ const eventFields = {
+ actionType: "navigate",
+ id: targetId,
+ idType: "static",
+ location: metricsLocation.createBasicLocation(objectGraph, options, title),
+ };
+ const clickEvent = metricsBuilder.createMetricsClickData(objectGraph, targetId, "link", eventFields);
+ actionMetrics.addMetricsData(clickEvent);
+}
+export function addClickEventToPageFacetsChangeAction(objectGraph, action, filterParameter) {
+ const eventFields = {};
+ eventFields["actionType"] = "filter";
+ const event = metricsBuilder.createMetricsClickData(objectGraph, `filter_${filterParameter}`, "button", eventFields);
+ event.includingFields.push("selectedPageFacets");
+ action.actionMetrics.addMetricsData(event);
+}
+//* *************************
+//* Search Metrics
+//* *************************
+/**
+ * Adds a given `SearchAction` with the metrics data based on the Search it will fire, i.e data for:
+ * - Click Event
+ * - Search Event
+ *
+ * @param action Action to add metrics to
+ * @param target Target of this action. This should correspond to the UI this action is attached to.
+ * @param locationTracker Location tracker.
+ */
+export function addEventsToSearchAction(objectGraph, action, target, locationTracker, pageInformation) {
+ var _a, _b, _c, _d;
+ const actionType = metricsActionTypeForSearchOrigin(action.origin);
+ if (isNothing(pageInformation)) {
+ pageInformation = new metricsModels.MetricsPageInformation({
+ page: "Search",
+ pageType: "Search",
+ pageId: "Search",
+ pageDetails: "Apps", // Legacy. Note sure why this is Apps.
+ });
+ }
+ const options = {
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ targetType: target,
+ };
+ // Click-Only Fields
+ const clickFields = {
+ ...metricsMisc.fieldsFromPageInformation(pageInformation),
+ actionType: actionType,
+ actionUrl: metricsUtil.emptyStringIfNullOrUndefined(action.url),
+ location: metricsLocation.createBasicLocation(objectGraph, options, action.term),
+ searchTerm: action.term,
+ };
+ // Search-Only Fields
+ const searchFields = {
+ targetId: action.term,
+ };
+ const searchActionDetails = {};
+ if ((_a = action.prefixTerm) === null || _a === void 0 ? void 0 : _a.length) {
+ // Keep `searchPrefix` in `actionDetails` for search events
+ searchActionDetails["searchPrefix"] = action.prefixTerm;
+ }
+ if ((_b = action.entity) === null || _b === void 0 ? void 0 : _b.length) {
+ searchActionDetails["hintsEntity"] = action.entity;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(searchActionDetails)) {
+ searchFields["actionDetails"] = searchActionDetails;
+ }
+ // Shared Fields
+ if ((_c = action.originatingTerm) === null || _c === void 0 ? void 0 : _c.length) {
+ clickFields["searchOriginatingTerm"] = action.originatingTerm;
+ searchFields["searchOriginatingTerm"] = action.originatingTerm;
+ }
+ // SSS: Clicks must be before Search
+ const clickData = metricsBuilder.createMetricsClickData(objectGraph, action.term, target, clickFields, [
+ "searchGhostHint",
+ ]);
+ action.actionMetrics.addMetricsData(clickData);
+ const searchData = metricsBuilder.createMetricsSearchData(objectGraph, action.term, target, actionType, (_d = action.url) !== null && _d !== void 0 ? _d : null, searchFields, ["searchGhostHint"]);
+ action.actionMetrics.addMetricsData(searchData);
+}
+/**
+ * Returns the mapped action type for given search origin.
+ * @param origin Origin to resolve action type for
+ */
+function metricsActionTypeForSearchOrigin(origin) {
+ // actionType based on search origin.
+ switch (origin) {
+ case "trending":
+ return "trending";
+ case "suggested":
+ return "suggested";
+ case "recents":
+ return "recentQuery";
+ case "hints":
+ return "hint";
+ case "undoSpellCorrection":
+ return "searchInsteadFor";
+ case "applySpellCorrection":
+ return "didYouMean";
+ case "userTypedHint":
+ return "userTypedHint";
+ // It is unexpected to see other search origins here. These are built in native:
+ default:
+ return "submit";
+ }
+}
+// region Segmented Search
+/**
+ * Add click metrics to segment change action
+ * @param action Action to add click event to.
+ * @param locationTracker Location tracker
+ */
+export function addEventsToSegmentChangeAction(objectGraph, action, targetId, locationTracker) {
+ // Click-Only Fields
+ const targetType = "link";
+ const clickFields = {
+ actionType: "navigate",
+ location: metricsLocation.createBasicLocation(objectGraph, {
+ pageInformation: null,
+ locationTracker: locationTracker,
+ targetType: targetType,
+ }, action.title),
+ };
+ const clickData = metricsBuilder.createMetricsClickData(objectGraph, targetId, targetType, clickFields);
+ action.actionMetrics.addMetricsData(clickData);
+}
+/**
+ * Add click metrics to segment change action
+ * @param action Action to add click event to.
+ * @param locationTracker Location tracker
+ */
+export function addClickEventToSearchPageSegmentChangeAction(objectGraph, action, targetId, locationTracker) {
+ // Click-Only Fields
+ const targetType = "SearchResults";
+ const clickFields = {
+ actionType: "navigate",
+ location: metricsLocation.createBasicLocation(objectGraph, {
+ pageInformation: null,
+ locationTracker: locationTracker,
+ targetType: targetType,
+ }, "searchPageSegmentChange"),
+ };
+ const clickData = metricsBuilder.createMetricsClickData(objectGraph, targetId, targetType, clickFields);
+ action.actionMetrics.addMetricsData(clickData);
+}
+// endregion
+//# sourceMappingURL=clicks.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/constants.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/constants.js
new file mode 100644
index 0000000..c23084b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/constants.js
@@ -0,0 +1,11 @@
+/**
+ * HACK: <rdar://problem/65687333> Tech Debt: Metrics: Adam ID attribution should be explicit
+ * This is an incremental step out of a hack that used `targetId` as `adamId` for decorating adamId-related fields for a given `MetricsData`.
+ * It should be leveraged for paths that use field providers that have an adam id dependency.
+ *
+ * As of 2022E we are using this key in the `custom` field of `actionMetrics`, which is a recommended method of sending down
+ * additional data to augment metrics events but shouldn't be included in the event itself. `custom` fields are automatically
+ * added to the `MetricsFieldsContext` by `ActionDispatcher`, allowing `MetricsFieldsProvider`s to access and use this value.
+ */
+export const contextualAdamIdKey = "jet_adamId";
+//# sourceMappingURL=constants.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/impressions.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/impressions.js
new file mode 100644
index 0000000..edc1d96
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/impressions.js
@@ -0,0 +1,419 @@
+import * as metricsLocation from "./location";
+import * as metricsUtil from "./util";
+import { isNothing, isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as productVariants from "../../product-page/product-page-variants";
+import * as lockups from "../../../common/lockups/lockups";
+//* *************************
+//* Impression Metrics
+//* *************************
+function generateImpressionFields(objectGraph, options) {
+ /// please note this is not the metrics event itself but the fields for the items that impress
+ /// there is a very strict spec for these items so please do not modify this unless you know
+ /// exactly what you are doing.
+ var _a, _b, _c, _d;
+ let id = options.id;
+ let title = options.title;
+ if (serverData.isDefinedNonNullNonEmpty(options.anonymizationOptions)) {
+ const anonymizationString = (_b = (_a = options.anonymizationOptions) === null || _a === void 0 ? void 0 : _a.anonymizationString) !== null && _b !== void 0 ? _b : "ANONYMOUS";
+ id = anonymizationString;
+ title = anonymizationString;
+ }
+ else if (id && options.createUniqueImpressionId && !objectGraph.client.isWatch) {
+ id = createUniqueImpressionId(objectGraph, id);
+ }
+ const impressionData = {
+ id: metricsUtil.emptyStringIfNullOrUndefined(id),
+ name: metricsUtil.emptyStringIfNullOrUndefined(title),
+ impressionType: metricsUtil.targetTypeForMetricsOptions(objectGraph, options),
+ };
+ const idType = metricsUtil.idTypeForMetricsOptions(options);
+ if (isSome(idType)) {
+ impressionData["idType"] = idType;
+ }
+ if (options && options.kind) {
+ impressionData["kind"] = options.kind;
+ }
+ if (options && options.softwareType) {
+ impressionData["softwareType"] = options.softwareType;
+ }
+ if (options && options.recoMetricsData) {
+ Object.assign(impressionData, options.recoMetricsData);
+ }
+ if (options && options.mercuryMetricsData) {
+ Object.assign(impressionData, options.mercuryMetricsData);
+ }
+ if (options && options.lockupDisplayStyle) {
+ impressionData["platformDisplayStyle"] = options.lockupDisplayStyle;
+ }
+ const shouldOmitImpressionIndex = (_c = options.shouldOmitImpressionIndex) !== null && _c !== void 0 ? _c : false;
+ if (options && options.locationTracker && !shouldOmitImpressionIndex) {
+ const currentPosition = metricsLocation.currentPosition(options.locationTracker);
+ impressionData["impressionIndex"] = currentPosition;
+ if (impressionData.id === "") {
+ impressionData.id = currentPosition.toString();
+ impressionData["idType"] = "sequential";
+ }
+ }
+ if (options && options.modelSource) {
+ impressionData["modelSource"] = options.modelSource;
+ }
+ // Add offerType if available
+ if (serverData.isDefinedNonNull(options.offerType)) {
+ impressionData["offerType"] = options.offerType;
+ }
+ // Arcade Upsell Tracking
+ if (options && serverData.isDefinedNonNull(options.displaysArcadeUpsell)) {
+ impressionData["displaysArcadeUpsell"] = options.displaysArcadeUpsell;
+ }
+ // Preorder Tracking
+ if (options && serverData.isDefinedNonNull(options.isPreorder)) {
+ impressionData["isPreorder"] = options.isPreorder;
+ }
+ // Add adamId if available
+ if (serverData.isDefinedNonNull(options.adamId) && serverData.isNullOrEmpty(options.anonymizationOptions)) {
+ impressionData["adamId"] = options.adamId;
+ }
+ // Badges
+ if (options && serverData.isDefinedNonNull(options.badges)) {
+ impressionData["badges"] = options.badges;
+ }
+ // In App Event ID
+ if (options && serverData.isDefinedNonNull(options.inAppEventId)) {
+ impressionData["inAppEventId"] = options.inAppEventId;
+ }
+ // Related subject IDs
+ if (options && serverData.isDefinedNonNull(options.relatedSubjectIds)) {
+ impressionData["relatedSubjectIds"] = options.relatedSubjectIds;
+ }
+ /// Hints entity
+ if ((_d = options === null || options === void 0 ? void 0 : options.hintsEntity) === null || _d === void 0 ? void 0 : _d.length) {
+ impressionData["hintsEntity"] = options.hintsEntity;
+ }
+ // autoAdvanceInterval for auto scrolling Views
+ if (options && serverData.isDefinedNonNull(options.autoAdvanceInterval)) {
+ impressionData["autoAdvanceInterval"] = options.autoAdvanceInterval;
+ }
+ // Add fcKind if available
+ if (serverData.isDefinedNonNull(options === null || options === void 0 ? void 0 : options.fcKind)) {
+ impressionData["fcKind"] = options.fcKind;
+ }
+ if (serverData.isDefinedNonNull(options === null || options === void 0 ? void 0 : options.canonicalId)) {
+ impressionData["canonicalId"] = options.canonicalId;
+ }
+ if (serverData.isDefinedNonNull(options === null || options === void 0 ? void 0 : options.displayStyle)) {
+ impressionData["displayStyle"] = options.displayStyle;
+ }
+ // Add product variant fields if available
+ if (serverData.isDefinedNonNull(options.productVariantData)) {
+ Object.assign(impressionData, productVariants.contentFieldsForProductVariantData(options.productVariantData));
+ }
+ if (isSome(options.contentRating)) {
+ impressionData["contentRating"] = options.contentRating;
+ }
+ if (isSome(options.bundleId)) {
+ impressionData["bundleId"] = options.bundleId;
+ }
+ if (impressionData.id === "") {
+ objectGraph.console.log(`impressionId missing. Tracking broken for ${impressionData.name} of ${impressionData.impressionType}`);
+ }
+ return impressionData;
+}
+export function addImpressionFields(objectGraph, impressionable, options) {
+ if (!impressionable) {
+ return;
+ }
+ impressionable.impressionMetrics = new models.ImpressionMetrics(generateImpressionFields(objectGraph, options));
+}
+export function addImpressionFieldsToTagRoomHeader(objectGraph, impressionable, options) {
+ if (!impressionable) {
+ return;
+ }
+ const impressionMetrics = new models.ImpressionMetrics(generateImpressionFields(objectGraph, options));
+ delete impressionMetrics.fields.impressionIndex;
+ impressionable.impressionMetrics = impressionMetrics;
+}
+/**
+ * Adds impression fields to the search metadata ribbon item.
+ *
+ * @param objectGraph - The object graph.
+ * @param metadataRibbonItem - The metadata ribbon item.
+ * @param options - The metrics impression options.
+ */
+export function addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, metadataRibbonItem, options) {
+ var _a;
+ if (!metadataRibbonItem) {
+ return;
+ }
+ addImpressionFields(objectGraph, metadataRibbonItem, options);
+ if ((options.isAdvert || options.isAdEligible) &&
+ ((_a = options.pageInformation) === null || _a === void 0 ? void 0 : _a.iAdInfo) &&
+ metadataRibbonItem.impressionMetrics) {
+ metadataRibbonItem.impressionMetrics = new models.FastImpressionMetrics(metadataRibbonItem.impressionMetrics, true);
+ }
+}
+/**
+ * Add impressions fields to a Today card.
+ * @param objectGraph The object graph.
+ * @param card The Today card to apply the impressions fields to.
+ * @param options A set of metrics options to gather data from.
+ * @param franchise The card franchise.
+ * @param cardType The card type.
+ * @param isOnboardingCard Whether the card is an onboarding card.
+ * @param coerceNullToEmptyStrings Whether `null` values for `franchise` and `cardType` should be coerced into empty strings. This
+ * was a behaviour that was always enabled by default, but as of the implementation of Chainlink ads, we don't necessarily want empty strings.
+ * Defaults to true to maintain legacy behaviour for other cards that don't want to opt out.
+ * @returns
+ */
+export function addImpressionsFieldsToTodayCard(objectGraph, card, options, franchise, cardType, isOnboardingCard, coerceNullToEmptyStrings = true) {
+ var _a, _b, _c, _d, _e, _f;
+ if (!card) {
+ return;
+ }
+ const impressionData = generateImpressionFields(objectGraph, options);
+ if (coerceNullToEmptyStrings) {
+ impressionData["franchise"] = metricsUtil.emptyStringIfNullOrUndefined(franchise);
+ impressionData["cardType"] = metricsUtil.emptyStringIfNullOrUndefined(cardType);
+ }
+ else {
+ if (franchise) {
+ impressionData["franchise"] = franchise;
+ }
+ if (cardType) {
+ impressionData["cardType"] = cardType;
+ }
+ }
+ if (isOnboardingCard) {
+ impressionData["isOnboardingCard"] = isOnboardingCard;
+ }
+ if (((_b = (_a = options === null || options === void 0 ? void 0 : options.optimizationEntityId) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0) {
+ impressionData["optimizationEntityId"] = options.optimizationEntityId;
+ }
+ if (((_d = (_c = options === null || options === void 0 ? void 0 : options.optimizationId) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0) {
+ impressionData["optimizationId"] = options.optimizationId;
+ }
+ if (isSome(options === null || options === void 0 ? void 0 : options.rowIndex)) {
+ impressionData["rowIndex"] = options.rowIndex;
+ }
+ card.impressionMetrics = new models.ImpressionMetrics(impressionData);
+ if ((options.isAdvert || options.isAdEligible) && ((_e = options.pageInformation) === null || _e === void 0 ? void 0 : _e.iAdInfo)) {
+ const sanitizedIAdData = metricsUtil.sanitizedMetricsDictionary(options.pageInformation.iAdInfo.fastImpressionsFieldsForCurrentItem(options.locationTracker, options.adSlotOverride));
+ Object.assign(card.impressionMetrics.fields, sanitizedIAdData);
+ card.impressionMetrics = new models.FastImpressionMetrics(card.impressionMetrics, true);
+ if (options.isAdvert) {
+ (_f = card.impressionMetrics) === null || _f === void 0 ? true : delete _f.fields["cardType"];
+ }
+ }
+}
+export function addImpressionsFieldsToAd(objectGraph, impressionable, options, iAdData) {
+ if (!impressionable || !iAdData) {
+ return;
+ }
+ addImpressionFields(objectGraph, impressionable, options);
+ const sanitizedIAdData = metricsUtil.sanitizedMetricsDictionary(iAdData.fastImpressionsFieldsForCurrentItem(options.locationTracker, options.adSlotOverride));
+ if (isSome(impressionable.impressionMetrics)) {
+ Object.assign(impressionable.impressionMetrics.fields, sanitizedIAdData);
+ const disableFastImpressions = serverData.asBooleanOrFalse(options.disableFastImpressionsForAds);
+ impressionable.impressionMetrics = new models.FastImpressionMetrics(impressionable.impressionMetrics, !disableFastImpressions);
+ }
+ /**
+ * This is an longstanding hack to prevent ad and organic impression showing same content (i.e. AdamId)
+ * to properly have nonclashing identifiers, and have separate impression objects.
+ * Even with impression item ids' hasing on `impressionIndex` - this is still needed in case the Nth rotated ad is the same as Nth organic.
+ * This is orthogonal to "ad_container" in location stack when building ads for ad-rotation, which is a separate metrics trick.
+ */
+ if (isSome(impressionable.impressionMetrics)) {
+ impressionable.impressionMetrics.fields["parentId"] = "ad_container";
+ }
+}
+export function addImpressionFieldsToInAppPurchaseLockup(objectGraph, lockup, options) {
+ if (!lockup) {
+ return;
+ }
+ // The code that calls this method currently does this indirectly,
+ // this is just to guard against that code changing in the future.
+ if (!lockup.impressionMetrics) {
+ addImpressionFields(objectGraph, lockup, options);
+ }
+ if (lockup.parent && lockup.parent.adamId && isSome(lockup.impressionMetrics)) {
+ lockup.impressionMetrics.fields["parentAdamId"] = metricsUtil.emptyStringIfNullOrUndefined(lockup.parent.adamId);
+ }
+}
+export function impressionOptionsForLockup(objectGraph, data, lockup, displayStyle, baseOptions, canDisplayArcadeOfferButton, attributePlatformOverride = undefined) {
+ var _a;
+ const options = impressionOptions(objectGraph, data, lockup.title, baseOptions);
+ options.lockupDisplayStyle = displayStyle;
+ options.contentRating = (_a = lockup.offerDisplayProperties) === null || _a === void 0 ? void 0 : _a.contentRating;
+ options.bundleId = lockup.bundleId;
+ // If no targetType is provided, set the correct value for the platform.
+ if (serverData.isNullOrEmpty(options.targetType)) {
+ options.targetType = objectGraph.client.isVision ? "lockupSmall" : "lockup";
+ }
+ if (canDisplayArcadeOfferButton && content.isArcadeSupported(objectGraph, data)) {
+ options.displaysArcadeUpsell = true;
+ }
+ // If it has a discounted offer then use the options already set
+ const parentID = baseOptions["id"];
+ if (serverData.isDefinedNonNullNonEmpty(lockups.discountedOfferFromData(data)) &&
+ serverData.isDefinedNonNull(parentID) &&
+ parentID.length > 0) {
+ options.id = parentID;
+ }
+ return options;
+}
+export function impressionOptions(objectGraph, data, title, baseOptions, attributePlatformOverride = undefined) {
+ return validation.context("impressionOptions", () => {
+ const kind = metricsUtil.metricsKindFromData(objectGraph, data);
+ const softwareType = metricsUtil.softwareTypeForData(objectGraph, data);
+ const metricsOptions = {
+ ...baseOptions,
+ kind: kind,
+ softwareType: softwareType,
+ title: title,
+ id: data.id,
+ };
+ // Include offerType for pre-order impressions
+ // NOTE: Even though metricsOptions.isPreorder may be true, we don't key off that here because
+ // offerType implies an offer exists, which is not always true (e.g. an Arcade Coming Soon breakout).
+ const containsPreorderOffer = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isPreorder", attributePlatformOverride);
+ if (containsPreorderOffer) {
+ metricsOptions.offerType = "preorder";
+ }
+ return metricsOptions;
+ });
+}
+/// Returns impression options for Arcade See All Games ribbon item.
+export function impressionOptionsForArcadeSeeAllGamesRibbonItem(baseOptions) {
+ return validation.context("impressionOptionsForArcadeSeeAllGamesRibbonItem", () => {
+ const metricsOptions = {
+ ...baseOptions,
+ id: "AllGames",
+ idType: "none",
+ kind: null,
+ softwareType: null,
+ title: "All Games",
+ };
+ return metricsOptions;
+ });
+}
+/// Returns impression options for tag ribbon item on product page.
+export function impressionOptionsForTagRibbonItem(objectGraph, data, title, baseOptions) {
+ return validation.context("impressionOptions", () => {
+ const kind = metricsUtil.metricsKindFromData(objectGraph, data);
+ const softwareType = metricsUtil.softwareTypeForData(objectGraph, data);
+ const metricsOptions = {
+ ...baseOptions,
+ kind: kind,
+ softwareType: softwareType,
+ title: title,
+ id: data.id,
+ idType: "its_id",
+ displayStyle: "textOnly",
+ };
+ return metricsOptions;
+ });
+}
+/// Returns impression options for tag ribbon item on product page.
+export function impressionOptionsForTagHeader(objectGraph, data, title, baseOptions) {
+ return validation.context("impressionOptions", () => {
+ const kind = metricsUtil.metricsKindFromData(objectGraph, data);
+ const softwareType = metricsUtil.softwareTypeForData(objectGraph, data);
+ const metricsOptions = {
+ ...baseOptions,
+ kind: kind,
+ softwareType: softwareType,
+ title: title,
+ id: data.id,
+ idType: "its_contentId",
+ targetType: "tagHeader",
+ };
+ return metricsOptions;
+ });
+}
+/// Returns impression options for Arcade Choose Your Favorites brick.
+export function impressionOptionsForArcadeChooseYourFavoritesBrick(baseOptions) {
+ return validation.context("impressionOptionsForArcadeChooseYourFavoritesBrick", () => {
+ const metricsOptions = {
+ ...baseOptions,
+ id: "",
+ kind: null,
+ softwareType: null,
+ title: "choose_your_games_brick",
+ };
+ return metricsOptions;
+ });
+}
+export function impressionOptionsForMetadataRibbonItem(baseOptions, id, name, idType) {
+ return validation.context("impressionOptionsForMetadataRibbonItem", () => {
+ const metricsOptions = {
+ ...baseOptions,
+ id: id,
+ kind: null,
+ softwareType: null,
+ title: name,
+ idType: idType,
+ targetType: "tag",
+ };
+ return metricsOptions;
+ });
+}
+// region Hints Impressions
+export function impressionOptionsForSearchHint(objectGraph, hintTerm, baseOptions, searchEntity, hintSource) {
+ return validation.context("impressionOptionsForSearchHint", () => {
+ const metricsOptions = {
+ ...baseOptions,
+ id: "",
+ kind: null,
+ softwareType: null,
+ title: hintTerm,
+ hintsEntity: searchEntity,
+ modelSource: hintSource,
+ };
+ return metricsOptions;
+ });
+}
+export function addImpressionMetricsToHintsSearchAction(objectGraph, searchAction, metricsOptions) {
+ const options = impressionOptionsForSearchHint(objectGraph, searchAction.term, metricsOptions, searchAction.entity, searchAction.source);
+ const impressionFields = generateImpressionFields(objectGraph, options);
+ searchAction.impressionMetrics = new models.ImpressionMetrics(impressionFields);
+}
+const uniqueIdDelimiter = "::";
+function createUniqueImpressionId(objectGraph, baseId) {
+ return `${baseId}${uniqueIdDelimiter}${objectGraph.random.nextUUID()}`;
+}
+/**
+ * Strips the unique impression ID from the event fields if necessary.
+ *
+ * @param eventFields - The event fields containing impressions.
+ */
+export function stripUniqueImpressionIdsIfNecessary(eventFields) {
+ const impressions = serverData.asArrayOrEmpty(eventFields, "impressions");
+ for (const impression of impressions) {
+ const impressionId = serverData.asString(impression, "id");
+ if (isNothing(impressionId)) {
+ continue;
+ }
+ impression["id"] = stripUniqueImpressionIdIfNecessary(impressionId);
+ }
+}
+/**
+ * Strips the unique impression ID from the given impression ID if necessary.
+ * If the impression ID contains a unique ID delimiter, it splits the string
+ * and returns the part before the delimiter. Otherwise, it returns the original
+ * impression ID.
+ *
+ * @param impressionId - The impression ID to process.
+ * @returns The processed impression ID.
+ */
+function stripUniqueImpressionIdIfNecessary(impressionId) {
+ if (impressionId.includes(uniqueIdDelimiter)) {
+ return impressionId.split(uniqueIdDelimiter)[0];
+ }
+ return impressionId;
+}
+// endregion
+//# sourceMappingURL=impressions.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/legacy-metrics-identifier-fields-opt-out.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/legacy-metrics-identifier-fields-opt-out.js
new file mode 100644
index 0000000..4232a33
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/legacy-metrics-identifier-fields-opt-out.js
@@ -0,0 +1,18 @@
+/**
+ * Hack for injecting clientId and metrics data if needed.
+ */
+import { AppStoreMetricsData } from "../../../api/models";
+/**
+ * Opt out of legacy metrics id fields provider, the `AMSMetricsIdentifierFieldsProvider` instead
+ * we'll rely solely on `MetricsIdFieldsProvider` added for Katana
+ * @param objectGraph - The object graph.
+ * @param metricsData - The metrics data.
+ * @returns The metrics data, with the `amsMetricsID` field excluded.
+ */
+export function optOutOfLegacyMetricsIdFieldsProvider(objectGraph, metricsData) {
+ var _a;
+ const excludingFields = (_a = metricsData.excludingFields) !== null && _a !== void 0 ? _a : [];
+ excludingFields.push("amsMetricsID");
+ return new AppStoreMetricsData(metricsData.fields, metricsData.includingFields, excludingFields, metricsData.topic, metricsData.shouldFlush);
+}
+//# sourceMappingURL=legacy-metrics-identifier-fields-opt-out.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/location.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/location.js
new file mode 100644
index 0000000..f7ffd4e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/location.js
@@ -0,0 +1,188 @@
+import * as validation from "@jet/environment/json/validation";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import { asJSONData } from "../../../foundation/json-parsing/server-data";
+import * as productVariant from "../../product-page/product-page-variants";
+import * as metricsUtil from "./util";
+class MetricsLocationStackItem {
+ constructor() {
+ this.position = 0;
+ }
+}
+export function newLocationTracker() {
+ return {
+ rootPosition: 0,
+ locationStack: [],
+ };
+}
+/**
+ * @param locationTracker The location tracker to copy
+ * @returns A copy of the location tracker
+ */
+export function createLocationTrackerCopy(locationTracker) {
+ const locationStackCopy = [];
+ for (const locationStackEntry of locationTracker.locationStack) {
+ locationStackCopy.push({
+ ...locationStackEntry,
+ });
+ }
+ return {
+ rootPosition: locationTracker.rootPosition,
+ locationStack: locationStackCopy,
+ };
+}
+export function createContentLocation(objectGraph, options, title) {
+ const locations = stackItemsToLocationStack(options.locationTracker);
+ const contentLocation = newContentLocation(objectGraph, options, title);
+ return [contentLocation, ...locations];
+}
+export function createBasicLocation(objectGraph, options, title) {
+ const locations = stackItemsToLocationStack(options.locationTracker);
+ const basicLocation = newBasicLocation(objectGraph, options, title);
+ return [basicLocation, ...locations];
+}
+export function pushContentLocation(objectGraph, options, title) {
+ const stackItem = new MetricsLocationStackItem();
+ stackItem.location = newContentLocation(objectGraph, options, title);
+ options.locationTracker.locationStack.unshift(stackItem);
+}
+export function pushBasicLocation(objectGraph, options, title) {
+ const stackItem = new MetricsLocationStackItem();
+ stackItem.location = newBasicLocation(objectGraph, options, title);
+ options.locationTracker.locationStack.unshift(stackItem);
+}
+export function popLocation(tracker) {
+ if (tracker.locationStack.length === 0) {
+ validation.unexpectedType("ignoredValue", "non-empty location stack", "empty location stack");
+ return;
+ }
+ tracker.locationStack.shift();
+}
+export function currentPosition(tracker) {
+ const stackItem = lastStackItemAdded(tracker);
+ if (stackItem) {
+ return stackItem.position;
+ }
+ else {
+ return tracker.rootPosition;
+ }
+}
+export function previousPosition(tracker) {
+ if (tracker.locationStack.length < 2) {
+ return null;
+ }
+ return tracker.locationStack[1].position;
+}
+export function currentLocation(tracker) {
+ const stackItem = lastStackItemAdded(tracker);
+ if (stackItem) {
+ return stackItem.location;
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Set current position of tracker. This is necessary when large today modules are broken apart into multipart shelves.
+ * We need to preserve the position of content within server-response, not our logical shelves.
+ * @param tracker Tracker update position of.
+ * @param position Position to set to.
+ */
+export function setCurrentPosition(tracker, position) {
+ const stackItem = lastStackItemAdded(tracker);
+ if (stackItem) {
+ stackItem.position = position;
+ }
+ else {
+ tracker.rootPosition = position;
+ }
+}
+export function nextPosition(tracker) {
+ const stackItem = lastStackItemAdded(tracker);
+ if (stackItem) {
+ stackItem.position++;
+ }
+ else {
+ tracker.rootPosition++;
+ }
+}
+function newContentLocation(objectGraph, options, title) {
+ var _a;
+ const base = newBasicLocation(objectGraph, options, title);
+ // Use the location tracker if there is no id override
+ if (!options.id && options.locationTracker) {
+ base.idType = "sequential";
+ base.id = currentPosition(options.locationTracker).toString();
+ }
+ else {
+ // If there is a id specified, use that
+ const idType = metricsUtil.idTypeForMetricsOptions(options);
+ if (isSome(idType)) {
+ base.idType = idType;
+ }
+ let id = options.id;
+ if ((_a = options.anonymizationOptions) === null || _a === void 0 ? void 0 : _a.anonymizationString) {
+ id = options.anonymizationOptions.anonymizationString;
+ }
+ base.id = isNothing(id) ? "" : id;
+ }
+ if (options.fcKind) {
+ base.fcKind = options.fcKind;
+ }
+ if (options.displayStyle) {
+ base.displayStyle = options.displayStyle;
+ }
+ if (options.inAppEventId) {
+ base.inAppEventId = options.inAppEventId;
+ }
+ if (options.relatedSubjectIds) {
+ base.relatedSubjectIds = options.relatedSubjectIds;
+ }
+ if (options.canonicalId) {
+ base.canonicalId = options.canonicalId;
+ }
+ if (options.optimizationEntityId) {
+ base.optimizationEntityId = options.optimizationEntityId;
+ }
+ if (options.optimizationId) {
+ base.optimizationId = options.optimizationId;
+ }
+ if (isSome(options.rowIndex)) {
+ base.rowIndex = options.rowIndex;
+ }
+ if (options.productVariantData) {
+ Object.assign(base, productVariant.contentFieldsForProductVariantData(options.productVariantData));
+ }
+ return base;
+}
+function newBasicLocation(objectGraph, options, title) {
+ var _a, _b;
+ let name = title;
+ if ((_a = options.anonymizationOptions) === null || _a === void 0 ? void 0 : _a.anonymizationString) {
+ name = options.anonymizationOptions.anonymizationString;
+ }
+ const location = {
+ locationPosition: currentPosition(options.locationTracker),
+ locationType: metricsUtil.targetTypeForMetricsOptions(objectGraph, options),
+ name: isNothing(name) ? "" : name,
+ };
+ if (isSome(options.badges)) {
+ location.badges = (_b = asJSONData(options.badges)) !== null && _b !== void 0 ? _b : undefined;
+ }
+ if (options.recoMetricsData) {
+ Object.assign(location, options.recoMetricsData);
+ }
+ return location;
+}
+function stackItemsToLocationStack(tracker) {
+ return tracker.locationStack.map((stackItem) => {
+ return stackItem.location;
+ });
+}
+function lastStackItemAdded(tracker) {
+ const length = tracker.locationStack.length;
+ if (length === 0) {
+ return null;
+ }
+ return tracker.locationStack[0];
+}
+//# sourceMappingURL=location.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/media.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/media.js
new file mode 100644
index 0000000..0b12cf9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/media.js
@@ -0,0 +1,34 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as objects from "../../../foundation/util/objects";
+import * as metricsBuilder from "../builder";
+import * as metricsLocation from "./location";
+import * as misc from "./misc";
+import * as metricsUtil from "./util";
+//* *************************
+//* Media Metrics
+//* *************************
+export function addMetricsEventsToVideo(objectGraph, video, options) {
+ if (isNothing(video)) {
+ return;
+ }
+ const mediaEventFields = misc.fieldsFromPageInformation(options.pageInformation);
+ if (mediaEventFields === null) {
+ return;
+ }
+ mediaEventFields["id"] = metricsUtil.emptyStringIfNullOrUndefined(options.id);
+ const idType = metricsUtil.idTypeForMetricsOptions(options);
+ if (isSome(idType)) {
+ mediaEventFields["idType"] = idType;
+ }
+ mediaEventFields["type"] = "video";
+ mediaEventFields["typeDetails"] = "iTunesStoreContent";
+ mediaEventFields["location"] = metricsLocation.createContentLocation(objectGraph, options, "");
+ if (options.actionDetails) {
+ mediaEventFields["actionDetails"] = options.actionDetails;
+ }
+ video.templateMediaEvent = metricsBuilder.createMetricsMediaData(objectGraph, mediaEventFields);
+ const clickEventFields = objects.shallowCopyOf(mediaEventFields);
+ clickEventFields["actionUrl"] = video.videoUrl;
+ video.templateClickEvent = metricsBuilder.createMetricsMediaClickData(objectGraph, null, "button", clickEventFields);
+}
+//# sourceMappingURL=media.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/misc.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/misc.js
new file mode 100644
index 0000000..ffb0b77
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/misc.js
@@ -0,0 +1,46 @@
+import { isDefinedNonNull } from "../../../foundation/json-parsing/server-data";
+import { pageFieldsForPageInfoProductVariantData } from "../../product-page/product-page-variants";
+import { EventLinter } from "../event-linter";
+import * as metricsUtil from "./util";
+// region Page Information
+/**
+ * Returns a set of pageFields from a given `pageInformation` - i.e. fields that are included on metrics data with `pageField` IncludingFields, and buyParams.
+ * It is not recommended to use this function for instrumentation that involves field providers, as these page fields will overwrite them.
+ * @param pageInformation Page information to create page fields for.
+ */
+export function fieldsFromPageInformation(pageInformation) {
+ var _a;
+ const fields = {};
+ if (!pageInformation) {
+ return fields;
+ }
+ Object.assign(fields, pageInformation.baseFields);
+ if (pageInformation.pageUrl) {
+ fields["pageUrl"] = pageInformation.pageUrl;
+ }
+ else if (pageInformation.timingMetrics && pageInformation.timingMetrics.pageURL) {
+ fields["pageUrl"] = pageInformation.timingMetrics.pageURL;
+ }
+ if (pageInformation.recoMetricsData) {
+ Object.assign(fields, pageInformation.recoMetricsData);
+ }
+ if (pageInformation.mercuryMetricsData) {
+ Object.assign(fields, pageInformation.mercuryMetricsData);
+ }
+ if (pageInformation.productVariantData) {
+ Object.assign(fields, pageFieldsForPageInfoProductVariantData(pageInformation.productVariantData));
+ }
+ if (pageInformation.iAdInfo && isDefinedNonNull(pageInformation.iAdInfo.pageFields[EventLinter.hasIAdData])) {
+ fields[EventLinter.hasIAdData] = pageInformation.iAdInfo.pageFields[EventLinter.hasIAdData];
+ }
+ const iAdId = (_a = pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.pageFields["iAdId"];
+ if (isDefinedNonNull(iAdId)) {
+ fields["iAdId"] = iAdId;
+ }
+ return metricsUtil.sanitizedMetricsDictionary(fields);
+}
+// endregion
+// region Network Performance
+// @see `JSNetworkPerformanceMetrics.metrics(fromResult:)` in native.
+// endregion
+//# sourceMappingURL=misc.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/models.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/models.js
new file mode 100644
index 0000000..3456e9e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/models.js
@@ -0,0 +1,671 @@
+import * as metricsLocation from "./location";
+import * as metricsUtil from "./util";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { attributeAsDictionary } from "../../../foundation/media/attributes";
+import { shallowCopyOf } from "../../../foundation/util/objects";
+import { productVariantDataForData, productVariantDataHasVariant } from "../../product-page/product-page-variants";
+import { EventLinter } from "../event-linter";
+import { eligibleSlotPositionsForAdPlacement } from "../../ads/ad-common";
+import { FlattenedTodayItemType } from "../../today/today-parse-util";
+import { getSelectedCustomCreativeId } from "../../search/custom-creative";
+export const iAdURLParameterStringToken = "X-AppStore-iAdClickToken";
+export const iAdURLLineItemParameterStringToken = "X-AppStore-iAdLineItem";
+export const iAdDismissAdActionMetricsParameterStringToken = "X-AppStore-iAdDismissAdActionMetrics";
+// User defined type guard for determining if an object conforms to ContentMetricsOptions interface.
+export function isContentMetricsOptions(object) {
+ return object && Object.prototype.hasOwnProperty.call(object, "id");
+}
+export class IAdSearchInformation {
+ /**
+ * Initialise a new `IAdSearchInformation`
+ * @param objectGraph The Object Graph.
+ * @param placementType The placement type for the ad this object is tracking.
+ * @param baseSlotInformation The initial list of slotInfos for this ad placement
+ * @param iAdId The unique id for the ad instance.
+ * @param appStoreClientRequestId The unique id for the client requesting the ad.
+ * @param wasOdmlSuccessful Whether native ODML processing was successful.
+ * @param positionInfo The position info data describing the requested position of this ad.
+ */
+ constructor(objectGraph, placementType, baseSlotInformation, iAdId, appStoreClientRequestId, wasOdmlSuccessful, positionInfo) {
+ this.placementType = placementType;
+ this.placementId = placementType === null ? null : this.placementIdFromType(placementType);
+ this.pageFields = {};
+ this.clickFields = {};
+ this.impressionsFields = {};
+ this.fastImpressionFields = {};
+ this.iAdClickEventFields = {};
+ this._iAdApplied = false;
+ this._iAdAdamId = undefined;
+ this.positionInfo = positionInfo;
+ this.slotInfo = baseSlotInformation;
+ this.setInitialAdData(objectGraph, iAdId, appStoreClientRequestId);
+ if (serverData.isDefinedNonNull(wasOdmlSuccessful)) {
+ this.pageFields["iAdOdmlSuccess"] = wasOdmlSuccessful;
+ }
+ this.fastImpressionFields["iAdEligible"] = true;
+ }
+ /**
+ * Construct an IAdSearchInformation from the given JSON representation.
+ * This is necessary over and above standard JSON parsing to preserve our ability to call functions on this object.
+ * @param objectGraph The Object Graph.
+ * @param json The JSON representation of the object.
+ * @returns A constructed IAdSearchInformation from the JSON.
+ */
+ static from(objectGraph, json) {
+ var _a, _b, _c, _d;
+ const iAdInfo = new IAdSearchInformation(objectGraph, serverData.asString(json.placementType), serverData.asArrayOrEmpty(json.slotInfo), (_a = serverData.asString(json.iAdId)) !== null && _a !== void 0 ? _a : undefined, (_b = serverData.asString(json.appStoreClientRequestId)) !== null && _b !== void 0 ? _b : undefined, (_c = serverData.asBoolean(json.wasOdmlSuccessful)) !== null && _c !== void 0 ? _c : undefined, serverData.asInterface(json.positionInfo));
+ iAdInfo._iAdApplied = serverData.asBooleanOrFalse(json._iAdApplied);
+ iAdInfo._iAdAdamId = (_d = serverData.asString(json._iAdAdamId)) !== null && _d !== void 0 ? _d : undefined;
+ Object.assign(iAdInfo.pageFields, json.pageFields);
+ Object.assign(iAdInfo.clickFields, json.clickFields);
+ Object.assign(iAdInfo.impressionsFields, json.impressionsFields);
+ Object.assign(iAdInfo.fastImpressionFields, json.fastImpressionFields);
+ Object.assign(iAdInfo.iAdClickEventFields, json.iAdClickEventFields);
+ iAdInfo.updateContainerId(serverData.asString(json.containerId));
+ return iAdInfo;
+ }
+ /**
+ * Create an array of `IAdSlotInformation` objects based on the current ad information available.
+ * This isn't ideal, but we need to understand the available ad slots so we can report
+ * on all slots, whether they have ads in them or not.
+ */
+ static createInitialSlotInfos(objectGraph, placementType, positionInfo, flattenedTodayFeed) {
+ var _a;
+ switch (placementType) {
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ const productPageContainerId = IAdSearchInformation.containerIdFromType(placementType);
+ const slotIndex = (_a = positionInfo === null || positionInfo === void 0 ? void 0 : positionInfo.slot) !== null && _a !== void 0 ? _a : 0;
+ const productPageSlot = {
+ slotId: `${productPageContainerId}_${slotIndex}`,
+ slotIndex: slotIndex,
+ hasAdData: false,
+ };
+ return [
+ {
+ containerId: productPageContainerId,
+ slots: [productPageSlot],
+ },
+ ];
+ break;
+ case "today":
+ const placementEligibleSlotPositions = eligibleSlotPositionsForAdPlacement(objectGraph, placementType);
+ const eligibleAdSlots = isNothing(placementEligibleSlotPositions)
+ ? []
+ : placementEligibleSlotPositions.map((slotPosition) => slotPosition.slot);
+ const containerSlotInfoMapping = {};
+ /// iAd provides slots that are not zero based, adjust the slot
+ const adjustedAdSlot = isSome(positionInfo) ? positionInfo.slot - 1 : null;
+ let adPositionEncountered = false;
+ eligibleAdSlots.forEach((eligibleIndex) => {
+ var _a;
+ // If we've reached the ad we need to subtract 1 from the index, to act as if the ad ad was
+ // actually part of the feed.
+ const augmentedFeedIndexIndex = adPositionEncountered ? eligibleIndex - 1 : eligibleIndex;
+ const todayItem = flattenedTodayFeed === null || flattenedTodayFeed === void 0 ? void 0 : flattenedTodayFeed.find((item) => item.containedAdSlots.includes(augmentedFeedIndexIndex));
+ const isAdSlotIndex = adjustedAdSlot === eligibleIndex;
+ const todayContainerId = iAdContainerIdForSlotInTodayItem(augmentedFeedIndexIndex, isAdSlotIndex, todayItem);
+ const containerSlotInformation = (_a = containerSlotInfoMapping[todayContainerId]) !== null && _a !== void 0 ? _a : {
+ containerId: todayContainerId,
+ slots: [],
+ };
+ containerSlotInfoMapping[todayContainerId] = containerSlotInformation;
+ const todaySlot = {
+ slotId: `${todayContainerId}_${eligibleIndex}`,
+ slotIndex: eligibleIndex,
+ hasAdData: false,
+ };
+ containerSlotInformation.slots.push(todaySlot);
+ adPositionEncountered = adPositionEncountered || isAdSlotIndex;
+ });
+ return Object.values(containerSlotInfoMapping);
+ default:
+ return null;
+ }
+ }
+ get iAdIsPresent() {
+ return this._iAdApplied;
+ }
+ get iAdAdamId() {
+ return this._iAdAdamId;
+ }
+ /**
+ * Update our information with a new ad response.
+ * This is primarily used for asynchronous ad requests, where we need to update the metrics info subsequent to the initial page load.
+ * @param objectGraph The Object Graph.
+ * @param adResponse An ad response to use to update some fields.
+ */
+ updateForAdResponse(objectGraph, adResponse) {
+ var _a;
+ if (serverData.isNull(adResponse)) {
+ return;
+ }
+ this.placementType = adResponse.placementType;
+ this.placementId = this.placementIdFromType(this.placementType);
+ this.positionInfo = (_a = adResponse.onDeviceAd) === null || _a === void 0 ? void 0 : _a.positionInfo;
+ this.setInitialAdData(objectGraph, adResponse.iAdId, adResponse.clientRequestId);
+ }
+ /**
+ * Set the initial ad data, if available.
+ * This function requires that an ad fetch has been made, and can be called from two paths:
+ * 1. Via the constructor, where the ad fetch is made at page load time, or
+ * 2. Via `updateForAdResponse`, where the ad fetch was made asynchronously after the page load.
+ * @param objectGraph The Object Graph.
+ * @param iAdId The unique id provided for the ad instance.
+ * @param appStoreClientRequestId The unique id representing the App Store ad request client.
+ */
+ setInitialAdData(objectGraph, iAdId, appStoreClientRequestId) {
+ if (isNothing(appStoreClientRequestId)) {
+ return;
+ }
+ // ToroID Suppression expects this to be replaced with -1 for figaro events
+ const iAdIdValue = isNothing(iAdId) ? "-1" : iAdId;
+ this.pageFields[EventLinter.hasIAdData] = true;
+ switch (this.placementType) {
+ case "today":
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ // Only set `hasIAdData` on impressions fields initially if it's a Chainlink placement.
+ // This covers the "ad eligible" cases where we still want to check ad metrics when ads are not present.
+ this.impressionsFields[EventLinter.hasIAdData] = true;
+ break;
+ default:
+ break;
+ }
+ this.pageFields["iAdAppStoreClientRequestId"] = appStoreClientRequestId;
+ switch (this.placementType) {
+ case "today":
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ this.clickFields["iAdAppStoreClientRequestId"] = appStoreClientRequestId;
+ this.impressionsFields["iAdAppStoreClientRequestId"] = appStoreClientRequestId;
+ break;
+ default:
+ break;
+ }
+ this.pageFields["iAdId"] = iAdIdValue;
+ this.impressionsFields["iAdId"] = iAdIdValue;
+ this.clickFields["iAdId"] = iAdIdValue;
+ this.updateContainerId(null);
+ // Update slot info with data
+ this.updateSlotInfo();
+ if (serverData.isDefinedNonNullNonEmpty(this.slotInfo)) {
+ this.pageFields["iAdSlotInfo"] = this.slotInfo;
+ this.clickFields["iAdSlotInfo"] = this.slotInfo;
+ this.impressionsFields["iAdSlotInfo"] = this.slotInfo;
+ }
+ if (this.placementId !== null && this.placementId.length > 0) {
+ this.pageFields["iAdPlacementId"] = this.placementId;
+ this.clickFields["iAdPlacementId"] = this.placementId;
+ // Attach the `iAdPlacementId` to top-level impressions for v4 impressions.
+ this.impressionsFields["iAdPlacementId"] = this.placementId;
+ // For Chainlink placements, the `iAdPlacementId` sits within the impressions array.
+ // We manually remove `iAdPlacementId` from the top-level fields for v5 impressions in `createMetricsFastImpressionsData`.
+ switch (this.placementType) {
+ case "today":
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ this.fastImpressionFields["iAdPlacementId"] = this.placementId;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ /**
+ * Update the containerId based on the current placementType.
+ */
+ updateContainerId(containerId) {
+ if (this.placementType === "today") {
+ this.containerId = containerId !== null && containerId !== void 0 ? containerId : null;
+ if (serverData.isDefinedNonNull(this.containerId)) {
+ this.clickFields["iAdContainerId"] = this.containerId;
+ this.fastImpressionFields["iAdContainerId"] = this.containerId;
+ }
+ }
+ else {
+ this.containerId =
+ this.placementType === null ? null : IAdSearchInformation.containerIdFromType(this.placementType);
+ if (serverData.isDefinedNonNull(this.containerId)) {
+ this.pageFields["iAdContainerId"] = this.containerId;
+ this.clickFields["iAdContainerId"] = this.containerId;
+ this.fastImpressionFields["iAdContainerId"] = this.containerId;
+ }
+ }
+ }
+ /**
+ * @param slotIndex The slot index to look for a container Id for
+ * @returns The container Id for the given slot index, based off the slot infos
+ */
+ containerIdForSlotIndex(slotIndex) {
+ if (isNothing(slotIndex) || isNothing(this.slotInfo)) {
+ return null;
+ }
+ for (const slotInfo of this.slotInfo) {
+ for (const slot of slotInfo.slots) {
+ if (slot.slotIndex === slotIndex) {
+ return slotInfo.containerId;
+ }
+ }
+ }
+ return this.containerId;
+ }
+ apply(objectGraph, adData) {
+ if (isNothing(adData) || serverData.isNullOrEmpty(adData)) {
+ return;
+ }
+ const iAdAdamId = adData.id;
+ const iAdConfigurationDictionary = attributeAsDictionary(adData, "iad");
+ this._iAdAdamId = iAdAdamId;
+ if (iAdConfigurationDictionary) {
+ this.impressionsFields[EventLinter.hasIAdData] = true;
+ this.clickFields[EventLinter.hasIAdData] = true;
+ const impressionId = metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["impressionId"]);
+ this.fastImpressionFields["iAdImpressionId"] = impressionId;
+ this.clickFields["iAdImpressionId"] = impressionId;
+ const metadata = metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["metadata"]);
+ this.clickFields["iAdMetadata"] = metadata;
+ this.fastImpressionFields["iAdMetadata"] = metadata;
+ // Ads boldly populate `adamId` on pages. This is correct.
+ this.pageFields["adamId"] = iAdAdamId;
+ this.pageFields["iAd"] = {
+ iAdFormat: metricsUtil.sanitizedMetricsDictionary(serverData.asInterface(serverData.asJSONValue(iAdConfigurationDictionary), "format")),
+ iAdAlgoId: metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["algoId"]),
+ iAdImpressionId: metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["impressionId"]),
+ iAdMetadata: metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["metadata"]),
+ };
+ const productVariantData = productVariantDataForData(objectGraph, adData);
+ this.updateIAdMetricsFieldsForProductVariantData(productVariantData, this.clickFields);
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ if (objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) {
+ const selectedCustomCreativeId = getSelectedCustomCreativeId(adData);
+ if (this.placementType === "today") {
+ this.updateIAdMetricsFieldsForAlignedRegion(selectedCustomCreativeId, this.fastImpressionFields);
+ }
+ else {
+ this.updateIAdMetricsFieldsForAlignedRegion(selectedCustomCreativeId, this.impressionsFields);
+ }
+ this.updateIAdMetricsFieldsForAlignedRegion(selectedCustomCreativeId, this.clickFields);
+ }
+ }
+ Object.assign(this.iAdClickEventFields, iAdConfigurationDictionary);
+ this._iAdApplied = true;
+ // Clear out any prior missed opportunity reason if we have an ad.
+ this.setMissedOpportunity(objectGraph, undefined, this.placementType);
+ }
+ // Update slot info after ad is applied.
+ this.updateSlotInfo();
+ if (serverData.isDefinedNonNullNonEmpty(this.slotInfo)) {
+ this.pageFields["iAdSlotInfo"] = this.slotInfo;
+ this.clickFields["iAdSlotInfo"] = this.slotInfo;
+ this.impressionsFields["iAdSlotInfo"] = this.slotInfo;
+ }
+ }
+ /**
+ * Apply the click fields that were attached to the page request we're following.
+ * This will generally be following on from click on a lockup with ad data attached, where we want to pull
+ * some of the click fields from ad to be applied to this new page data.
+ *
+ * The goal here is to attach enough metadata into the incoming page that we can attribute any offer action
+ * to the ad that was tapped on to reach this page. Whilst doing this we don't want to attach too much
+ * ad data to the page, as this can start to interfere with metrics for other ad placements. This results in
+ * us manually inserting and removing fields to achieve the right balance.
+ * @param iAdAdamId The adamId for the ad.
+ * @param fields The set of fields to apply to our clickFields.
+ */
+ applyClickFieldsFromPageRequest(iAdAdamId, fields) {
+ this._iAdApplied = true;
+ this._iAdAdamId = iAdAdamId;
+ Object.assign(this.clickFields, fields);
+ // We don't want to assign any of the fields to the page event, as this looks like an ad is being shown
+ // on the page we're navigating to.
+ Object.keys(this.pageFields).forEach((field) => delete this.pageFields[field]);
+ }
+ setSpecifiedAlignedRegionUsed(didUseSpecifiedCreative) {
+ this.fastImpressionFields["iAdIsSpecifiedCreativeUsed"] = didUseSpecifiedCreative;
+ this.clickFields["iAdIsSpecifiedCreativeUsed"] = didUseSpecifiedCreative;
+ }
+ /**
+ * Set the template type for the page.
+ */
+ setTemplateType(templateType) {
+ this.pageFields["iAdTemplateType"] = templateType;
+ this.impressionsFields["iAdTemplateType"] = templateType;
+ this.clickFields["iAdTemplateType"] = templateType;
+ }
+ setMissedOpportunity(objectGraph, reason, placementType) {
+ this.missedOpportunityReason = reason;
+ // Only set in page and impressions fields if reason is non-null.
+ if (serverData.isDefinedNonNull(reason)) {
+ this.clickFields["iAdMissedOpportunityReason"] = reason;
+ // Chainlink placements don't expect "iAdMissedOpportunityReason" at the top level of impressions or page events.
+ switch (this.placementType) {
+ case "today":
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ break;
+ default:
+ this.pageFields["iAdMissedOpportunityReason"] = reason;
+ this.impressionsFields["iAdMissedOpportunityReason"] = reason;
+ break;
+ }
+ }
+ else {
+ delete this.clickFields["iAdMissedOpportunityReason"];
+ // Generally, we just want to remove the missed opportunity reason if one isn't set.
+ // The 'productPageYMALDuringDownload' placement is a special case where it's placed on the page
+ // subsequent to a previous placement, and metrics is updated via the pageChange metrics.
+ // This means that we need to send `null` as the value for this placement, in order to clear
+ // any possible missed opportunity value from the placement it's replacing.
+ switch (placementType) {
+ case "productPageYMALDuringDownload":
+ this.pageFields["iAdMissedOpportunityReason"] = null;
+ this.impressionsFields["iAdMissedOpportunityReason"] = null;
+ break;
+ default:
+ delete this.pageFields["iAdMissedOpportunityReason"];
+ delete this.impressionsFields["iAdMissedOpportunityReason"];
+ break;
+ }
+ }
+ // Only set in page and impressions fields if reason is non-null.
+ if (serverData.isDefinedNonNull(reason)) {
+ this.pageFields["iAdMissedOpportunityReason"] = reason;
+ this.impressionsFields["iAdMissedOpportunityReason"] = reason;
+ }
+ else {
+ delete this.pageFields["iAdMissedOpportunityReason"];
+ delete this.impressionsFields["iAdMissedOpportunityReason"];
+ }
+ // Update slotInfo with missed opportunity reason.
+ this.updateSlotInfo();
+ if (serverData.isDefinedNonNullNonEmpty(this.slotInfo)) {
+ this.pageFields["iAdSlotInfo"] = this.slotInfo;
+ this.clickFields["iAdSlotInfo"] = this.slotInfo;
+ this.impressionsFields["iAdSlotInfo"] = this.slotInfo;
+ }
+ }
+ placementIdFromType(type) {
+ switch (type) {
+ case "searchLanding":
+ return "APPSTORE_SEARCH_LANDING_PAGE";
+ case "searchResults":
+ return "APPSTORE_SEARCH_RESULT_PAGE";
+ case "today":
+ return "APPSTORE_TODAY_TAB";
+ case "productPageYMAL":
+ return "APPSTORE_PRODUCT_PAGE";
+ case "productPageYMALDuringDownload":
+ return "APPSTORE_PRODUCT_PAGE_DOWNLOAD";
+ default:
+ throw new Error(`This method should never be called with value: ${type}`);
+ }
+ }
+ static placementTypeFromPlacementId(objectGraph, id) {
+ switch (id) {
+ case "APPSTORE_SEARCH_LANDING_PAGE":
+ return "searchLanding";
+ case "APPSTORE_SEARCH_RESULT_PAGE":
+ return "searchResults";
+ case "APPSTORE_TODAY_TAB":
+ return "today";
+ case "APPSTORE_PRODUCT_PAGE":
+ return "productPageYMAL";
+ case "APPSTORE_PRODUCT_PAGE_DOWNLOAD":
+ return "productPageYMALDuringDownload";
+ default:
+ objectGraph.console.log(`Failed to get placementType from placementId ${id}. Defaulting to searchResults`);
+ // For legacy reasons we fall back to `seachResults` when nothing is provided.
+ // Being the first ad slot, in the past this has been assumed as the "default".
+ return "searchResults";
+ }
+ }
+ /**
+ * Get a containerId value for a given placement type to attach to the ad's metrics.
+ * @param type The AdPlacementType for the current ad.
+ * @returns A string value for containerId, if the placement has one. Otherwise null.
+ */
+ static containerIdFromType(type) {
+ switch (type) {
+ case "productPageYMAL":
+ return "customers-also-bought-apps";
+ case "productPageYMALDuringDownload":
+ return "customers-also-bought-apps-download";
+ case "today":
+ return null; // Today page has variable containerIds that are updated programatically
+ default:
+ return null;
+ }
+ }
+ /**
+ * Return the fast impressions metrics fields for an item using the location tracker.
+ *
+ * `fastImpressionFields` are the items inside the impressions array of metrics events. Although some fields are stable,
+ * these are unique to a given position, so we must use that position information to generate the right fields.
+ *
+ * @param locationTracker A LocationTracker used to identify the relevant information to attach to the metrics fields.
+ * @param adSlotOverride The ad slot to use for the metrics fields. If not provided, the ad slot will be inferred from
+ * the location tracker. This is needed for today where the ad can span different shelves so the position is going to be 0 for multiple ads
+ * @returns Relevant metrics fields for the item in an impressions array.
+ */
+ fastImpressionsFieldsForCurrentItem(locationTracker, adSlotOverride) {
+ switch (this.placementType) {
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ case "today":
+ let position;
+ if (isSome(adSlotOverride)) {
+ position = adSlotOverride;
+ }
+ else {
+ const location = metricsLocation.currentLocation(locationTracker);
+ // If the current location is within a todayCard, we want to extract the location of the card, not the location
+ // of this lockup within the card.
+ if (location !== null && location.locationType === "todayCard") {
+ position = metricsLocation.previousPosition(locationTracker);
+ }
+ else {
+ position = metricsLocation.currentPosition(locationTracker);
+ }
+ }
+ const sharedFields = shallowCopyOf(this.fastImpressionFields);
+ sharedFields["iAdSlotId"] = `${this.containerIdForSlotIndex(position)}_${position}`;
+ if (position !== this.adjustedSlotIndex) {
+ // If the current position is not where the ad is, we only want a subset of the tracked fields.
+ const allowedFields = ["iAdEligible", "iAdPlacementId", "iAdContainerId", "iAdSlotId"];
+ Object.keys(sharedFields).forEach((key) => {
+ if (!allowedFields.includes(key)) {
+ delete sharedFields[key];
+ }
+ });
+ }
+ return sharedFields;
+ default:
+ return this.fastImpressionFields;
+ }
+ }
+ /**
+ * Get the adjusted slot index of the ad we're tracking.
+ * This is zero-based - Ad Platforms returns slot info to us one-based but we always adjust it before using it anywhere.
+ */
+ get adjustedSlotIndex() {
+ var _a;
+ const positionInfoSlotIndex = (_a = this.positionInfo) === null || _a === void 0 ? void 0 : _a.slot;
+ // The slot as provided by ad platforms is one-based - adjust it so we're working with zero-based numbers.
+ if (serverData.isDefinedNonNull(positionInfoSlotIndex)) {
+ return positionInfoSlotIndex - 1;
+ }
+ return null;
+ }
+ /**
+ * Update the existing slot information now that new data has been applied.
+ */
+ updateSlotInfo() {
+ if (isNothing(this.slotInfo)) {
+ return;
+ }
+ switch (this.placementType) {
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ for (const containerSlotInfo of this.slotInfo) {
+ for (const slot of containerSlotInfo.slots) {
+ slot.hasAdData = this.iAdIsPresent;
+ if (serverData.isDefinedNonNull(this.missedOpportunityReason)) {
+ slot.missedOpportunityReason = this.missedOpportunityReason;
+ }
+ }
+ }
+ break;
+ case "today":
+ const adjustedSlotIndex = this.adjustedSlotIndex;
+ for (const containerSlotInfo of this.slotInfo) {
+ for (const slot of containerSlotInfo.slots) {
+ // Check whether the current index matches our slot information.
+ // If so, an ad is intended to be placed here.
+ const isAdInCurrentIndex = adjustedSlotIndex === slot.slotIndex;
+ // In order to understand whether a given slot has ad data, we ensure that a usable ad is present,
+ // and the current slot is the one an ad is placed in.
+ const hasAdData = this.iAdIsPresent && isAdInCurrentIndex;
+ // By default, use the missed opportunity reason, if any, stored here.
+ let missedOpportunityReason = this.missedOpportunityReason;
+ // If:
+ // - the ad is not in the current index we're building slot info for, and
+ // - there is a valid slot index
+ // we override the reason to 'NOAD', as it means another eligible slot was filled.
+ if (!isAdInCurrentIndex && serverData.isDefinedNonNull(adjustedSlotIndex)) {
+ missedOpportunityReason = "NOAD";
+ }
+ slot.hasAdData = hasAdData;
+ if (serverData.isDefinedNonNull(missedOpportunityReason)) {
+ slot.missedOpportunityReason = missedOpportunityReason;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ /**
+ * Modifies the provided MetricsFields for the ProductVariantData specific to fields required for iAds.
+ * @param productVariantData The variant data of impressionable data to apply.
+ * @param metricsFields An existing set of MetricsFields to modify.
+ */
+ updateIAdMetricsFieldsForProductVariantData(productVariantData, metricsFields) {
+ if (isSome(productVariantData) && productVariantDataHasVariant(productVariantData, "customProductPage")) {
+ metricsFields["iAdPageCustomId"] = productVariantData.productPageId;
+ }
+ else {
+ // If a custom product page doesn't exist, we remove the fields.
+ delete metricsFields["iAdPageCustomId"];
+ }
+ }
+ /**
+ * Modifies the provided MetricsFields to include the custom id for iAds.
+ * @param selectedCustomCreativeId The id for the custom creative that we want to set as custom id.
+ * @param metricsFields An existing set of MetricsFields to modify.
+ */
+ updateIAdMetricsFieldsForAlignedRegion(selectedCustomCreativeId, metricsFields) {
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ if (isSome(selectedCustomCreativeId)) {
+ metricsFields["iAdCustomId"] = selectedCustomCreativeId;
+ }
+ else {
+ delete metricsFields["iAdCustomId"];
+ }
+ }
+ }
+ /**
+ * Return the event version for fast impressions based on the placement of the ad.
+ * Impressions v5 is being rolled out first for Chainlink. Once all impressions have adopted it, this can be removed.
+ */
+ get fastImpressionsEventVersion() {
+ switch (this.placementType) {
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ case "today":
+ return 5;
+ default:
+ return 4;
+ }
+ }
+ /**
+ * Return whether ad rotation fields should be included on metrics events, based on the current placement type.
+ */
+ get shouldIncludeAdRotationFields() {
+ switch (this.placementType) {
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ case "today":
+ return false;
+ case "searchLanding":
+ case "searchResults":
+ return true;
+ default:
+ return true;
+ }
+ }
+}
+/**
+ * Return the container id to use for a today feed item
+ * @param slotIndex The slot index that we found a TodayItem for
+ * @param isAdSlot Whether this slotIndex is the slot the ad is in
+ * @param todayItem The today item that this card is contained in, this could be a single item or a story group item
+ * @returns The configuration to use when parsing a today card
+ */
+export function iAdContainerIdForSlotInTodayItem(slotIndex, isAdSlot, todayItem) {
+ if (isNothing(todayItem)) {
+ return "0";
+ }
+ switch (todayItem.type) {
+ case FlattenedTodayItemType.EditorialItemGroup:
+ const storyGroupHasMultipleItems = todayItem.containedAdSlots.length > 1;
+ const slotIndexIsFirstOrLastInStoryGroup = !storyGroupHasMultipleItems ||
+ slotIndex === todayItem.containedAdSlots[0] ||
+ slotIndex === todayItem.containedAdSlots[todayItem.containedAdSlots.length - 1];
+ if (isAdSlot && slotIndexIsFirstOrLastInStoryGroup) {
+ // If the story group has fewer than 2 items, that means theres not valid slot **in** the actual story group
+ // to place the ad, so its going to be at the top level. Or if there are multiple items, but the slot is the
+ // first or last item there is no reason to pull the ad into the story group, it should be top level.
+ return "0";
+ }
+ else {
+ // Using the `data` from the todayItem here and not the todayCardData, because
+ // this will be the data for the containing story gorup
+ return "0";
+ }
+ default:
+ return "0";
+ }
+}
+/**
+ * Create the iAdInformation for a given page if necessary
+ * @param objectGraph The dependency graph for the app store
+ * @param pageId The id of the page this iAd info is being used for
+ * @param response The MAPI response for this page
+ * @param positionInfo The position information on where the ad will attempted to be inserted
+ * @param flattenedTodayFeed The flattened to today feed if this is for a today page
+ * @returns The iAdInformation to be used in a metrics page information object
+ */
+export function iAdInformationFromMediaApiResponse(objectGraph, pageId, response, positionInfo = null, flattenedTodayFeed = null) {
+ var _a;
+ /** <rdar://problem/33764430> Toro: iAd is missing AD_OPEN figaro event when tapping on ad and pressing OPEN through the product page */
+ const iAdClickInfoString = serverData.asString(response, iAdURLParameterStringToken);
+ if (isNothing(iAdClickInfoString)) {
+ return null;
+ }
+ const iAdClickInfo = JSON.parse(iAdClickInfoString);
+ // rdar://72607206 (Gibraltar: Tech Debt: Population of iAd fields in Product Page)
+ const placementType = IAdSearchInformation.placementTypeFromPlacementId(objectGraph, serverData.asString(iAdClickInfo, "iAdPlacementId"));
+ const iAdInfo = new IAdSearchInformation(objectGraph, placementType, IAdSearchInformation.createInitialSlotInfos(objectGraph, placementType, positionInfo, flattenedTodayFeed), (_a = serverData.asString(iAdClickInfo, "iAdId")) !== null && _a !== void 0 ? _a : undefined, undefined, undefined, positionInfo);
+ iAdInfo.applyClickFieldsFromPageRequest(pageId !== null && pageId !== void 0 ? pageId : undefined, iAdClickInfo);
+ return iAdInfo;
+}
+/** @public */
+export class MetricsPageInformation {
+ constructor(base = {}) {
+ this.baseFields = base;
+ }
+}
+//# sourceMappingURL=models.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/page.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/page.js
new file mode 100644
index 0000000..0c088b1
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/page.js
@@ -0,0 +1,482 @@
+import { PageInvocationPoint } from "@jet/environment/types/metrics";
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+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 { ResponseMetadata } from "../../../foundation/network/network";
+import { Parameters } from "../../../foundation/network/url-constants";
+import * as urls from "../../../foundation/network/urls";
+import * as dateUtil from "../../../foundation/util/date-util";
+import * as objects from "../../../foundation/util/objects";
+import * as content from "../../content/content";
+import { productVariantDataForData } from "../../product-page/product-page-variants";
+import * as guidedSearch from "../../search/guided-search/guided-search-metrics";
+import * as searchAds from "../../search/search-ads";
+import * as metricsBuilder from "../builder";
+import * as misc from "./misc";
+import * as metricsModels from "./models";
+import * as metricsUtil from "./util";
+import { searchMetricsDataSetID } from "../../search/search-common";
+//* *************************
+//* Page Metrics
+//* *************************
+export function metricsPageInformationFromMarketingItemMediaApiResponse(objectGraph, pageType, pageId, marketingItemData) {
+ var _a;
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, pageType, pageId, marketingItemData);
+ if (serverData.isDefinedNonNull(marketingItemData)) {
+ pageInformation.mercuryMetricsData =
+ (_a = metricsUtil.marketingItemTopLevelBaseFieldsFromData(objectGraph, marketingItemData)) !== null && _a !== void 0 ? _a : undefined;
+ }
+ return pageInformation;
+}
+export function metricsPageInformationFromMediaApiResponse(objectGraph, pageType, pageId, response, pageDetails, overrideIAdInfo) {
+ var _a, _b, _c;
+ const pageInformation = (_a = serverData.asInterface(response, ResponseMetadata.pageInformation, {})) !== null && _a !== void 0 ? _a : {};
+ const timingMetrics = serverData.asInterface(response, ResponseMetadata.timingValues);
+ const pageUrl = serverData.asString(response, ResponseMetadata.requestedUrl);
+ let productVariantData;
+ const responsePageBaseFields = pageInformation;
+ responsePageBaseFields.pageType = pageType;
+ responsePageBaseFields.pageId = pageId;
+ if (pageDetails) {
+ responsePageBaseFields["pageDetails"] = pageDetails;
+ }
+ if (pageType === "Software") {
+ const softwareData = mediaDataStructure.dataFromDataContainer(objectGraph, response);
+ if (isSome(softwareData)) {
+ const name = mediaAttributes.attributeAsString(softwareData, "name");
+ const artistName = mediaAttributes.attributeAsString(softwareData, "artistName");
+ responsePageBaseFields["pageDetails"] = `${artistName}_${name}`;
+ // To distinguish normal apps from Arcade apps
+ if (content.isArcadeSupported(objectGraph, softwareData)) {
+ responsePageBaseFields["softwareType"] = "Arcade";
+ }
+ productVariantData = productVariantDataForData(objectGraph, softwareData);
+ }
+ }
+ else if (pageType === "Genre") {
+ responsePageBaseFields["pageDetails"] = `${pageType}_${pageId}`;
+ }
+ else if (pageType === "Search") {
+ responsePageBaseFields["pageDetails"] = "Apps";
+ }
+ else if (pageType === "SearchLanding" && pageId === "SearchLanding") {
+ responsePageBaseFields["pageDetails"] = `${pageType}_${pageId}`;
+ }
+ const pageInfo = new metricsModels.MetricsPageInformation((_b = metricsUtil.sanitizedMetricsDictionary(responsePageBaseFields)) !== null && _b !== void 0 ? _b : {});
+ if (timingMetrics !== null) {
+ pageInfo.timingMetrics = timingMetrics;
+ if (isSome(pageUrl)) {
+ pageInfo.pageUrl = pageUrl;
+ }
+ }
+ // For product pages, every shelf is creating it's a new page information. There should be one top-level one, shared for page.
+ // Revisit in: rdar://77227964 (Metrics: ProductPage: Shelves should share page information instead of building it per-shelf.)
+ if (serverData.isDefinedNonNull(productVariantData)) {
+ pageInfo.productVariantData = productVariantData;
+ }
+ const iAdInfo = overrideIAdInfo !== null && overrideIAdInfo !== void 0 ? overrideIAdInfo : metricsModels.iAdInformationFromMediaApiResponse(objectGraph, pageId, response);
+ if (isSome(iAdInfo)) {
+ pageInfo.iAdInfo = iAdInfo;
+ }
+ pageInfo.recoMetricsData = (_c = mediaDataStructure.metricsFromMediaApiObject(response)) !== null && _c !== void 0 ? _c : undefined;
+ return pageInfo;
+}
+export function fakeMetricsPageInformation(objectGraph, pageType, pageId, pageDetails, iAdClickInfo) {
+ var _a;
+ const page = new metricsModels.MetricsPageInformation({
+ pageType: pageType,
+ pageId: pageId,
+ page: `${pageType}_${pageId}`,
+ pageDetails: pageDetails,
+ });
+ if (iAdClickInfo) {
+ // rdar://72607206 (Gibraltar: Tech Debt: Population of iAd fields in Product Page)
+ const placementType = metricsModels.IAdSearchInformation.placementTypeFromPlacementId(objectGraph, serverData.asString(iAdClickInfo, "iAdPlacementId"));
+ page.iAdInfo = new metricsModels.IAdSearchInformation(objectGraph, placementType, metricsModels.IAdSearchInformation.createInitialSlotInfos(objectGraph, placementType, null, null), (_a = serverData.asString(iAdClickInfo, "iAdId")) !== null && _a !== void 0 ? _a : undefined);
+ page.iAdInfo.applyClickFieldsFromPageRequest(pageId, iAdClickInfo);
+ }
+ return page;
+}
+export function addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation, fieldsModifier, isDefaultBrowser) {
+ if (serverData.isNull(pageInformation)) {
+ return;
+ }
+ page.pageMetrics.pageFields = misc.fieldsFromPageInformation(pageInformation);
+ page.pageMetrics.addManyInstructions(impressionsInstructionsFromPageData(objectGraph, pageInformation, fieldsModifier));
+ page.pageMetrics.addData(pageEventFromPageData(objectGraph, pageInformation, fieldsModifier, isDefaultBrowser), [
+ PageInvocationPoint.pageEnter,
+ ]);
+ page.pageMetrics.addData(pageExitEventFromPageData(objectGraph, pageInformation, fieldsModifier), [
+ PageInvocationPoint.pageExit,
+ ]);
+ page.pageMetrics.pageRenderFields = pageRenderFromPageData(objectGraph, pageInformation, fieldsModifier);
+ page.pageRenderMetrics = pageRenderFromPageData(objectGraph, pageInformation, fieldsModifier);
+ if (pageRequiresBackEvent(page)) {
+ page.pageMetrics.addData(backEventFromPageData(objectGraph, pageInformation, fieldsModifier), [
+ PageInvocationPoint.backButton,
+ ]);
+ }
+ const fetchTimingMetricsBuilder = objectGraph.fetchTimingMetricsBuilder;
+ if (isSome(fetchTimingMetricsBuilder)) {
+ fetchTimingMetricsBuilder.decorate(page);
+ }
+}
+/**
+ * @param page The page to check
+ * @returns Whether this page requires a back event to be sent when the user navigates back from it.
+ */
+function pageRequiresBackEvent(page) {
+ const isSearchResultsPage = page instanceof models.SearchResults;
+ const isContingentOfferDetailPage = page instanceof models.ContingentOfferDetailPage;
+ const isWinbackOfferDetailPage = page instanceof models.OfferItemDetailPage;
+ return !isSearchResultsPage && !isContingentOfferDetailPage && !isWinbackOfferDetailPage;
+}
+export function copyMetricsEventsIntoSidepackedPagewithInformation(objectGraph, page, pageInformation) {
+ if (serverData.isNull(pageInformation)) {
+ return;
+ }
+ page.pageMetrics.addData(backEventFromPageData(objectGraph, pageInformation, undefined), [
+ PageInvocationPoint.backButton,
+ ]);
+ if (serverData.isNull(page.pageMetrics.pageFields)) {
+ page.pageMetrics.pageFields = {};
+ }
+}
+/**
+ * Constructs metrics page information for a page navigated to from the action links on the product page.
+ * @param productId The adamId of the product for which these action links are displayed.
+ * @param pageType The metrics pageType for the page the links navigates to.
+ */
+export function pageInformationForActionLinkPage(objectGraph, productId, pageType) {
+ const base = {
+ pageId: productId || "",
+ pageType: pageType,
+ };
+ return new metricsModels.MetricsPageInformation(base);
+}
+/**
+ * Create metrics page information for rooms that may not have an its id.
+ * @param pageId The identifier to differentiate room
+ */
+export function pageInformationForRoom(objectGraph, pageId) {
+ const pageType = "Room";
+ const page = new metricsModels.MetricsPageInformation({
+ pageType: pageType,
+ pageId: pageId,
+ page: `${pageType}_${pageId}`,
+ });
+ return page;
+}
+/**
+ * Constructs empty page information for search hints 'page' for given prefix term and optional dataSetId.
+ */
+export function pageInformationForSearchHintsPage(objectGraph, prefixTerm, pageUrl, dataSetId) {
+ const base = {
+ pageId: "hints",
+ pageType: "Search",
+ };
+ if (dataSetId) {
+ base[searchMetricsDataSetID] = dataSetId;
+ }
+ const pageInformation = new metricsModels.MetricsPageInformation(base);
+ pageInformation.pageUrl = pageUrl;
+ return pageInformation;
+}
+export function pageInformationForIAPPage(objectGraph, parentId, iapId) {
+ const base = {
+ pageId: iapId || "",
+ pageType: "IAPInstallPage",
+ parentId: metricsUtil.emptyStringIfNullOrUndefined(parentId),
+ };
+ const pageInformation = new metricsModels.MetricsPageInformation(base);
+ return pageInformation;
+}
+/**
+ * Build the pageInformation to use for search pages.
+ */
+export function pageInformationForSearchPage(objectGraph, request, response, termContext, searchRequestUrl, pageId, sponsoredSearchRequestData, wasOdmlSuccessful, guidedSearchData) {
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "Search", pageId, response);
+ pageInformation.searchTermContext = termContext;
+ pageInformation.pageUrl = searchRequestUrl; // For Search, pageUrl is request url
+ if (guidedSearchData) {
+ pageInformation.guidedSearch = guidedSearch.guidedSearchPageInformationFields(objectGraph, request, guidedSearchData);
+ }
+ if (searchAds.platformSupportsAdverts(objectGraph) && sponsoredSearchRequestData != null) {
+ pageInformation.iAdInfo = new metricsModels.IAdSearchInformation(objectGraph, "searchResults", metricsModels.IAdSearchInformation.createInitialSlotInfos(objectGraph, "searchResults", null, null), sponsoredSearchRequestData.iAdId, sponsoredSearchRequestData.appStoreClientRequestId, wasOdmlSuccessful);
+ }
+ return pageInformation;
+}
+export function pageInformationForSegmentedSearchPage(objectGraph, response, termContext, searchRequestUrl, groupId) {
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "Search", groupId, response);
+ pageInformation.searchTermContext = termContext;
+ pageInformation.pageUrl = searchRequestUrl;
+ return pageInformation;
+}
+export function pageInformationForAppPromotionDetailPage(objectGraph, appPromotionType, appPromotionId, parentAppAdamId, referrerData, recoMetricsData) {
+ let pageId = "";
+ let pageType = "";
+ switch (appPromotionType) {
+ case models.AppPromotionType.AppEvent:
+ pageId = `${appPromotionId}_${parentAppAdamId}`;
+ pageType = "EventDetails";
+ break;
+ case models.AppPromotionType.ContingentOffer:
+ case models.AppPromotionType.OfferItem:
+ pageId = `${appPromotionId}`;
+ pageType = "OfferDetails";
+ break;
+ default:
+ break;
+ }
+ const base = {
+ pageId: pageId,
+ pageType: pageType,
+ };
+ if (serverData.isDefinedNonNull(referrerData)) {
+ base["refApp"] = referrerData["app"];
+ base["extRefUrl"] = referrerData["externalUrl"];
+ }
+ const pageInformation = new metricsModels.MetricsPageInformation(base);
+ pageInformation.recoMetricsData = recoMetricsData !== null && recoMetricsData !== void 0 ? recoMetricsData : undefined;
+ return pageInformation;
+}
+export function addPageEventsToInAppPurchasePage(objectGraph, page, pageInformation) {
+ addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation);
+}
+export function makePageReferralEligible(objectGraph, page) {
+ if (serverData.isNull(page) || serverData.isNull(page.pageMetrics)) {
+ return;
+ }
+ const pageInstructions = page.pageMetrics.instructions;
+ if (!serverData.isNull(pageInstructions)) {
+ for (const instruction of pageInstructions) {
+ // We shoudl only be requesting crossfire information for page events.
+ // if we dont, and add it to all of the instructions then we end up clearing out
+ // the referral information on the native side during any other event that happens on this page
+ // before a buy can happen, for instance an impressions event that occurs on page exit when
+ // displaying an upsell sheet.
+ if (instruction.data.fields["eventType"] !== "page") {
+ continue;
+ }
+ instruction.data.includingFields.push("crossfireReferral");
+ }
+ }
+ // Ensure that for referral eligible pages, the attribution is included in the purchase configuration
+ // <rdar://problem/45420867> Crossfire: Metrics: extRefApp2/extRefUrl2 buyparam are being persisted too strongly, being sent on buys of other app pages
+ let productLockup = null;
+ if (page instanceof models.ProductPage) {
+ productLockup = page;
+ }
+ else if (page instanceof models.ShelfBasedProductPage) {
+ productLockup = page.lockup;
+ }
+ if (productLockup) {
+ const eligibleActions = [];
+ if (productLockup.buttonAction instanceof models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction);
+ }
+ else if (productLockup.buttonAction instanceof models.OfferConfirmationAction &&
+ productLockup.buttonAction.buyAction instanceof models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction.buyAction);
+ }
+ else if (productLockup.buttonAction instanceof models.OfferAlertAction &&
+ productLockup.buttonAction.completionAction instanceof models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction.completionAction);
+ }
+ else if (productLockup.buttonAction instanceof models.OfferStateAction) {
+ if (productLockup.buttonAction.buyAction instanceof models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction.buyAction);
+ }
+ if (productLockup.buttonAction.defaultAction instanceof models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction.defaultAction);
+ }
+ if (productLockup.buttonAction.openAction instanceof models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction.openAction);
+ }
+ if (productLockup.buttonAction.subscribePageAction instanceof models.FlowAction &&
+ productLockup.buttonAction.subscribePageAction.page === "arcadeSubscribe" &&
+ isSome(productLockup.buttonAction.subscribePageAction.pageUrl) &&
+ productLockup.buttonAction.subscribePageAction.pageUrl.length > 0) {
+ const pageUrl = urls.URL.from(productLockup.buttonAction.subscribePageAction.pageUrl);
+ pageUrl.param(Parameters.includePostSubscribeAttribution, "true");
+ productLockup.buttonAction.subscribePageAction.pageUrl = pageUrl.build();
+ }
+ if (productLockup.buttonAction.subscribePageAction instanceof models.FlowAction &&
+ productLockup.buttonAction.subscribePageAction.pageData instanceof models.MarketingItemRequestInfo &&
+ productLockup.buttonAction.subscribePageAction.pageData.purchaseSuccessAction instanceof
+ models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction.subscribePageAction.pageData.purchaseSuccessAction);
+ }
+ }
+ for (const eligibleAction of eligibleActions) {
+ if (eligibleAction.purchaseConfiguration) {
+ eligibleAction.purchaseConfiguration.excludeAttribution = false;
+ }
+ }
+ }
+}
+/**
+ * Add an updated set of page metrics to the shelf that will be used to update the existing page metrics,
+ * as well as be sent as a `pageChange` event.
+ * @param objectGraph The object graph.
+ * @param shelf The shelf to attach the pageChange fields to
+ * @param pageInformation The modified page information for the containing page.
+ * @param fieldsModifier A field modifier, if required.
+ * @returns
+ */
+export function addPageChangeFieldsToShelfWithInformation(objectGraph, shelf, pageInformation, fieldsModifier) {
+ if (serverData.isNull(pageInformation)) {
+ return;
+ }
+ let updatedEvents = [];
+ // Page
+ const pageFields = misc.fieldsFromPageInformation(pageInformation);
+ const pageEvent = pageEventFromPageData(objectGraph, pageInformation, fieldsModifier);
+ updatedEvents.push(pageEvent);
+ // Impressions
+ const impressionsInstructions = impressionsInstructionsFromPageData(objectGraph, pageInformation, fieldsModifier);
+ const impressionsEvents = impressionsInstructions.map((instruction) => instruction.data);
+ updatedEvents = updatedEvents.concat(impressionsEvents);
+ filterPageMetricsFieldsForPageChange(pageFields);
+ updatedEvents.forEach((event) => filterPageMetricsFieldsForPageChange(event.fields));
+ shelf.pageChangeMetrics = {
+ pageFields: pageFields,
+ updatedEvents: updatedEvents,
+ };
+}
+/// An allow list for the fields that can change in a `pageChange` event.
+const allowedPageChangeKeys = new Set([
+ "iAdAppStoreClientRequestId",
+ "iAdId",
+ "iAdSlotInfo",
+ "iAdOdmlSuccess",
+ "iAdEligible",
+ "iAdContainerId",
+ "iAdImpressionId",
+ "iAdMetadata",
+ "adamId",
+ "iAd",
+ "iAdPlacementId",
+ "iAdMissedOpportunityReason",
+ "hasiAdData",
+ "iAdTemplateType",
+ // These are required to merge with existing events
+ "eventType",
+ "impressionQueue",
+]);
+/**
+ * Filter some fields for the allowed content for a pageChange event.
+ * @param metricsFields The MetricsFields to filter.
+ */
+function filterPageMetricsFieldsForPageChange(metricsFields) {
+ Object.keys(metricsFields)
+ .filter((key) => !allowedPageChangeKeys.has(key))
+ .forEach((key) => delete metricsFields[key]);
+}
+function pageEventFromPageData(objectGraph, pageInformation, fieldsModifier, isDefaultBrowser) {
+ var _a, _b, _c, _d, _e;
+ const base = baseFieldsForPageEventsFromPageInformation(pageInformation, fieldsModifier);
+ if (pageInformation.iAdInfo) {
+ // Always copy this, even if no ad is present.
+ Object.assign(base, pageInformation.iAdInfo.pageFields);
+ }
+ // Include pre-order release date for page events.
+ if (serverData.isDefinedNonNull(pageInformation.offerReleaseDate)) {
+ base["offerReleaseDate"] = dateUtil.millisecondsToUTCMidnightFromLocalDate(pageInformation.offerReleaseDate);
+ }
+ const searchTermContext = pageInformation.searchTermContext;
+ if (searchTermContext) {
+ base["searchTerm"] = searchTermContext.term;
+ if (searchTermContext.suggestedTerm) {
+ base["searchSuggestedTerm"] = searchTermContext.suggestedTerm;
+ }
+ if (searchTermContext.correctedTerm) {
+ base["searchCorrectedTerm"] = searchTermContext.correctedTerm;
+ }
+ if (searchTermContext.originatingTerm) {
+ base["searchOriginatingTerm"] = searchTermContext.originatingTerm;
+ }
+ }
+ if (pageInformation.guidedSearch) {
+ Object.assign(base, pageInformation.guidedSearch);
+ }
+ const pageEvent = metricsBuilder.createMetricsPageData(objectGraph, false, (_a = pageInformation.isCrossfireReferralCandidate) !== null && _a !== void 0 ? _a : false, pageInformation.timingMetrics, base, isDefaultBrowser);
+ const hasAdverts = (_c = (_b = pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.iAdIsPresent) !== null && _c !== void 0 ? _c : false;
+ const shouldIncludeAdRotationFields = (_e = (_d = pageInformation.iAdInfo) === null || _d === void 0 ? void 0 : _d.shouldIncludeAdRotationFields) !== null && _e !== void 0 ? _e : false;
+ if (hasAdverts && shouldIncludeAdRotationFields) {
+ pageEvent.includingFields.push("advertRotation");
+ }
+ return pageEvent;
+}
+/**
+ * Creates a pageExitEvent which has all of the same fields as a pageEvent but with an eventType of pageExit.
+ * Noted this event is an event that is primarily meant to be used so the JS can get a hook into when a page is
+ * left so we can clear out associated referralContexts
+ */
+function pageExitEventFromPageData(objectGraph, pageInformation, fieldsModifier) {
+ const pageExitEvent = pageEventFromPageData(objectGraph, pageInformation, fieldsModifier);
+ pageExitEvent.fields["eventType"] = "pageExit";
+ return pageExitEvent;
+}
+function backEventFromPageData(objectGraph, pageInformation, fieldsModifier) {
+ const base = baseFieldsForPageEventsFromPageInformation(pageInformation, fieldsModifier);
+ return metricsBuilder.createMetricsBackClickData(objectGraph, base);
+}
+function pageRenderFromPageData(objectGraph, pageInformation, fieldsModifier) {
+ const base = baseFieldsForPageEventsFromPageInformation(pageInformation, fieldsModifier);
+ if (pageInformation.searchTermContext) {
+ base["searchTerm"] = pageInformation.searchTermContext.term;
+ }
+ // Splice in page fields until all native clients have fix for <rdar://problem/68879825>
+ if (pageInformation.baseFields) {
+ Object.assign(base, pageInformation.baseFields);
+ }
+ return metricsBuilder.createMetricsPageRenderFields(objectGraph, pageInformation.timingMetrics, base);
+}
+function impressionsInstructionsFromPageData(objectGraph, pageInformation, fieldsModifier) {
+ var _a, _b, _c;
+ const base = baseFieldsForPageEventsFromPageInformation(pageInformation, fieldsModifier);
+ if (pageInformation.searchTermContext) {
+ base["searchTerm"] = pageInformation.searchTermContext.term;
+ }
+ const impressionBase = objects.shallowCopyOf(base);
+ if (pageInformation.iAdInfo) {
+ Object.assign(impressionBase, pageInformation.iAdInfo.impressionsFields);
+ }
+ if (pageInformation.guidedSearch) {
+ Object.assign(impressionBase, pageInformation.guidedSearch);
+ }
+ // Regular Impressions
+ const shouldIncludeAdMetrics = serverData.isDefinedNonNull(pageInformation.iAdInfo);
+ const iAdEligibleForWindowCollection = serverData.isNullOrEmpty((_a = pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.missedOpportunityReason) && objectGraph.client.isPad;
+ const shouldIncludeAdRotationFields = (_c = (_b = pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.shouldIncludeAdRotationFields) !== null && _c !== void 0 ? _c : false;
+ const impressionsData = metricsBuilder.createMetricsImpressionsData(objectGraph, impressionBase, shouldIncludeAdMetrics && iAdEligibleForWindowCollection, shouldIncludeAdRotationFields, true);
+ const instruction = {
+ data: impressionsData,
+ invocationPoints: [PageInvocationPoint.appExit, PageInvocationPoint.pageExit],
+ };
+ // Fast Impressions
+ const impressionsArray = [instruction];
+ if (shouldIncludeAdMetrics) {
+ const fastImpressions = metricsBuilder.createMetricsFastImpressionsData(objectGraph, impressionBase, pageInformation);
+ impressionsArray.push({
+ data: fastImpressions,
+ invocationPoints: [PageInvocationPoint.appExit, PageInvocationPoint.pageExit, PageInvocationPoint.timer],
+ });
+ }
+ return impressionsArray;
+}
+function baseFieldsForPageEventsFromPageInformation(pageInformation, fieldsModifier) {
+ const base = {};
+ // Add offer type for pre-orders (this should be added to everything, including impressions)
+ if (serverData.isDefinedNonNull(pageInformation.offerType)) {
+ base["offerType"] = pageInformation.offerType;
+ }
+ if (fieldsModifier !== undefined && base) {
+ fieldsModifier(base);
+ }
+ return base;
+}
+//# sourceMappingURL=page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-focus-impressions.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-focus-impressions.js
new file mode 100644
index 0000000..0bd030a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-focus-impressions.js
@@ -0,0 +1,57 @@
+import * as serverData from "../../../foundation/json-parsing/server-data";
+/**
+ * This is a workaround for Search Focus Page based on existing metrics tech debt in Search Results Page.
+ *
+ * # Context
+ * - In CrystalB, impressions for SFP now includes a recent searches shelf which needs to update dynamically as searches are performed.
+ *
+ * # What is the Workaround?
+ * The JetEngine Metrics API is designed to work s.t.:
+ * 1. JS populates `parentId`, which JetEngine Metric APIs uses **internally** to refer to between `ImpressionMetrics`.
+ * 2. Native generates `impressionParentId`, which is refers `impressionId` of parent assigned during serialization -
+ *
+ * Here, we instead:
+ * 1. JS populates `parentId` as normal
+ * 2. `event-linter` iterates through the impressions, finds the parent containers, and adds parent impression ids.
+ * 3. Native generates `impressionParentId` as normal where possible.
+ *
+ * # Why Workaround?
+ * 1. There are existing hacks for impression parents, e.g. fake `ad_container` and native child trackers w/ parent ID attribution.
+ * 2. AppStore is stuck between old ASK metrics and new JE metrics APIs. Our JS builders and existing native metrics are intertwined in a way that makes using JS defined `parentId` attribution nontrivial.
+ * 3. We need to dynamically update the recent searches shelf every time a search is performed.
+ *
+ * See `search-results-impressions` for more background.
+ */
+/**
+ * Update the `impression` field, attributing impressionParentIds per workaround above.
+ * @param eventFields Event fields to modify **in place**.
+ */
+export function decorateImpressionParentId(eventFields) {
+ var _a;
+ const impressions = serverData.asArrayOrEmpty(eventFields, "impressions");
+ // Find result parent id.
+ let recentsParentImpressionId;
+ for (const impression of impressions) {
+ const canonicalId = serverData.asString(impression, "canonicalId");
+ if (canonicalId === "R8804") {
+ recentsParentImpressionId = (_a = serverData.asString(impression, "impressionId")) !== null && _a !== void 0 ? _a : undefined; // *NOT* id.
+ break;
+ }
+ }
+ if (!recentsParentImpressionId) {
+ return;
+ }
+ // Update impressions for search results
+ eventFields["impressions"] = impressions.map((impression) => {
+ const canonicalId = serverData.asString(impression, "canonicalId");
+ const impressionType = serverData.asString(impression, "impressionType");
+ if (serverData.isNullOrEmpty(canonicalId) &&
+ impressionType === "link" &&
+ impression != null &&
+ serverData.asString(impression, "impressionParentId") == null) {
+ impression["impressionParentId"] = recentsParentImpressionId;
+ }
+ return impression;
+ });
+}
+//# sourceMappingURL=search-focus-impressions.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-result-impressions.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-result-impressions.js
new file mode 100644
index 0000000..6874724
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-result-impressions.js
@@ -0,0 +1,56 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+/**
+ * This is a workaround for existing metrics tech debt in Search Results Page.
+ *
+ * # Context
+ * - In AzulC, impressions for SRP now includes 'containers' for search results and guided search tokens.
+ * - The containers were added so tokens don't affect impression index, which search uses as a signal for ranking.
+ *
+ * # What is the Workaround?
+ * The JetEngine Metrics API is designed to work s.t.:
+ * 1. JS populates `parentId`, which JetEngine Metric APIs uses **internally** to refer to between `ImpressionMetrics`.
+ * 2. Native generates `impressionParentId`, which is refers `impressionId` of parent assigned during serialization -
+ *
+ * Here, we instead:
+ * 1. JS *doesn't* populate `parentId`
+ * 2. `event-linter` iterates through the impressions, finds the parent containers, and adds parent impression ids.
+ *
+ * # Why Workaround?
+ * 1. There are existing hacks for impression parents, e.g. fake `ad_container` and native child trackers w/ parent ID attribution.
+ * 2. AppStore is stuck between old ASK metrics and new JE metrics APIs. Our JS builders and existing native metrics are intertwined in a way that makes using JS defined `parentId` attribution nontrivial.
+ *
+ * Note that we **don't** use this for attributing parent id workaround for `search-revisions`, i.e. guided search tokens.
+ * This is because that codepath began from a clean slate, and uses JE metrics as-designed
+ */
+/**
+ * Update the `impression` field, attributing impressionParentIds per workaround above.
+ * @param eventFields Event fields to modify **in place**.
+ */
+export function decorateImpressionParentId(eventFields) {
+ const impressions = serverData.asArrayOrEmpty(eventFields, "impressions");
+ // Find result parent id.
+ let resultsParentImpressionId;
+ for (const impression of impressions) {
+ const impressionType = serverData.asString(impression, "impressionType");
+ if (isSome(impression) && impressionType === "SearchResults") {
+ resultsParentImpressionId = impression["impressionId"]; // *NOT* id.
+ break;
+ }
+ }
+ if (!resultsParentImpressionId) {
+ return;
+ }
+ // Update impressions for search results
+ eventFields["impressions"] = impressions.map((impression) => {
+ const impressionType = serverData.asString(impression, "impressionType");
+ const isCardResult = impressionType === "card";
+ const isEventModuleResult = impressionType === "eventModule";
+ const isSearchResult = isCardResult || isEventModuleResult; // search results are cards and app events.
+ if (isSome(impression) && isSearchResult) {
+ impression["impressionParentId"] = resultsParentImpressionId;
+ }
+ return impression;
+ });
+}
+//# sourceMappingURL=search-result-impressions.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search/search-shelves.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search/search-shelves.js
new file mode 100644
index 0000000..486fc9d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search/search-shelves.js
@@ -0,0 +1,98 @@
+import * as mediaDataStructure from "../../../../foundation/media/data-structure";
+import * as models from "../../../../api/models";
+import * as searchShelves from "../../../search/content/search-shelves";
+import { impressionOptions } from "../impressions";
+import { asString } from "../../../../foundation/json-parsing/server-data";
+import { relationship } from "../../../../foundation/media/relationships";
+import { isNothing } from "@jet/environment/types/optional";
+/**
+ * Generates the metrics impressions options 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 The metrics options for the shelf
+ */
+export function createMetricsOptionsForGenericSearchPageShelf(objectGraph, data, shelfAttributes, searchPageContext) {
+ var _a, _b, _c;
+ /// On shelves, the actual reco metrics are on the content and not the data blob itself
+ const recoMetricsDataContainer = relationship(data, "contents");
+ const recoMetricsData = recoMetricsDataContainer === null
+ ? undefined
+ : (_a = mediaDataStructure.metricsFromMediaApiObject(recoMetricsDataContainer)) !== null && _a !== void 0 ? _a : undefined;
+ let impressionsIdType = "its_contentId";
+ if (searchPageContext.pageType === searchShelves.SearchPageType.ChartsAndCategories) {
+ impressionsIdType = "static";
+ }
+ const shelfMetricsOptions = {
+ id: shelfAttributes.id,
+ kind: null,
+ softwareType: null,
+ targetType: "swoosh",
+ title: (_b = shelfAttributes.title) !== null && _b !== void 0 ? _b : "",
+ pageInformation: searchPageContext.metricsPageInformation,
+ locationTracker: searchPageContext.metricsLocationTracker,
+ idType: impressionsIdType,
+ fcKind: undefined,
+ canonicalId: (_c = asString(data.meta, "canonicalId")) !== null && _c !== void 0 ? _c : undefined,
+ recoMetricsData: recoMetricsData,
+ };
+ return shelfMetricsOptions;
+}
+/**
+ * Generates the impressions metrics options for the search chart or category
+ * @param objectGraph The App Store Object Graph
+ * @param model The chart or category model
+ * @param modelData The chart or category model data object
+ * @param searchShelfContext The context for the shelf containing the chart or category
+ * @returns The metrics options for the chart or category model
+ */
+export function createMetricsOptionsForChartOrCategory(objectGraph, model, modelData, searchShelfContext) {
+ var _a;
+ const chartModelMetricsOptions = {
+ ...searchShelfContext.metricsOptions,
+ ...impressionOptions(objectGraph, modelData, model.title, searchShelfContext.metricsOptions),
+ recoMetricsData: (_a = mediaDataStructure.metricsFromMediaApiObject(modelData)) !== null && _a !== void 0 ? _a : undefined,
+ targetType: metricsTargetTypeForChartOrCategory(model.density),
+ idType: "its_id",
+ };
+ return chartModelMetricsOptions;
+}
+/**
+ * Generates the click metrics options for the search chart or category
+ * @param objectGraph The App Store Object Graph
+ * @param model The chart or category model
+ * @param modelData The chart or category model data object
+ * @param searchShelfContext The context for the shelf containing the chart or category
+ * @returns The metrics options for the chart or category model
+ */
+export function createClickMetricsOptionsForChartOrCategory(objectGraph, modelDensity, modelData, searchShelfContext) {
+ var _a;
+ const chartModelMetricsOptions = {
+ pageInformation: searchShelfContext.metricsOptions.pageInformation,
+ locationTracker: searchShelfContext.metricsOptions.locationTracker,
+ recoMetricsData: (_a = mediaDataStructure.metricsFromMediaApiObject(modelData)) !== null && _a !== void 0 ? _a : undefined,
+ targetType: metricsTargetTypeForChartOrCategory(modelDensity),
+ id: modelData.id,
+ };
+ return chartModelMetricsOptions;
+}
+/**
+ * Gets the matching metrics target type for the chart or category model
+ * @param model The model we want the target type for
+ * @returns The target type for the model
+ */
+function metricsTargetTypeForChartOrCategory(modelDensity) {
+ if (isNothing(modelDensity)) {
+ return "tile";
+ }
+ switch (modelDensity) {
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density1:
+ return "tile";
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density2:
+ return "pill";
+ default:
+ return "tile";
+ }
+}
+//# sourceMappingURL=search-shelves.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/util.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/util.js
new file mode 100644
index 0000000..8be872f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/util.js
@@ -0,0 +1,407 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as urls from "../../../foundation/network/urls";
+import * as errors from "../../../foundation/util/errors";
+import * as objects from "../../../foundation/util/objects";
+import * as content from "../../content/content";
+import * as deviceFamily from "../../content/device-family";
+export function targetTypeForMetricsOptions(objectGraph, options) {
+ let type = options.targetType;
+ if (!type) {
+ type = objectGraph.client.isVision ? "lockupSmall" : "lockup";
+ }
+ return type;
+}
+export function idTypeForMetricsOptions(options) {
+ let type = options.idType;
+ if (type === "none") {
+ type = null;
+ }
+ else if (!type) {
+ type = "its_id";
+ }
+ return type;
+}
+export function softwareTypeForData(objectGraph, data) {
+ return content.isArcadeSupported(objectGraph, data) ? "Arcade" : null;
+}
+export function metricsKindFromData(objectGraph, data, attributePlatformOverride = undefined) {
+ const type = serverData.asString(data, "type");
+ const isMacType = deviceFamily.dataHasDeviceFamily(objectGraph, data, "mac", true);
+ const isOnlyMacType = deviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "mac", true);
+ const isIOSType = deviceFamily.dataHasAnyDeviceFamilies(objectGraph, data, ["iphone", "ipad", "ipod", "tvos", "watch"], true);
+ const isOnlyIOSType = deviceFamily.dataOnlyHasDeviceFamilies(objectGraph, data, ["iphone", "ipad", "ipod", "tvos", "watch"], true);
+ const isIOSDeviceType = objectGraph.client.isiOS || objectGraph.client.isTV || objectGraph.client.isWatch;
+ const isAppleSiliconDeviceType = objectGraph.client.isMac && objectGraph.appleSilicon.isSupportEnabled;
+ const isVisionDeviceType = objectGraph.client.isVision;
+ const isVisionType = deviceFamily.dataHasDeviceFamily(objectGraph, data, "realityDevice", true);
+ const isOnlyVisionType = deviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "realityDevice", true);
+ // If:
+ // - this is a mac only app, or
+ // - it has multiple types but we're currently on the Mac, or
+ // - it has multiple types and the plaform override is for Mac,
+ // use the 'macSoftware' or 'macSoftwareBundle' kinds
+ if (isOnlyMacType ||
+ (isMacType && objectGraph.client.isMac) ||
+ (isMacType && attributePlatformOverride === "osx")) {
+ switch (type) {
+ case "apps":
+ return "macSoftware";
+ case "app-bundles":
+ return "macSoftwareBundle";
+ default:
+ break;
+ }
+ }
+ // If:
+ // - this is a Vision only app, or
+ // - it has multiple types but we're currently on a Vision device, or
+ // - it has multiple types and the platform override is for Vision,
+ // use the 'visionSoftware' kind.
+ if (isOnlyVisionType ||
+ (isVisionType && objectGraph.client.isVision) ||
+ (isVisionType && attributePlatformOverride === "xros")) {
+ switch (type) {
+ case "apps":
+ return "visionSoftware";
+ case "app-bundles":
+ // To add if/when vision supports app bundles.
+ break;
+ default:
+ break;
+ }
+ }
+ // If this is an iOS only app or it has multiple types but we're currently:
+ // - on an iOS device, or
+ // - on an Apple Silicon Mac, or
+ // - on a Vision device, or
+ // - the platform override is for an iOS-like device
+ // use the 'iosSoftware' or 'mobileSoftwareBundle' kinds
+ if (isOnlyIOSType ||
+ (isIOSType && isIOSDeviceType) ||
+ (isIOSType && isAppleSiliconDeviceType) ||
+ (isIOSType && isVisionDeviceType) ||
+ (isIOSType && attributePlatformOverride === "ios") ||
+ (isIOSType && attributePlatformOverride === "watch") ||
+ (isIOSType && attributePlatformOverride === "appletvos")) {
+ switch (type) {
+ case "apps":
+ return "iosSoftware";
+ case "app-bundles":
+ return "mobileSoftwareBundle";
+ default:
+ break;
+ }
+ }
+ switch (type) {
+ case "in-apps":
+ return "softwareAddOn";
+ case "groupings":
+ return "grouping";
+ case "editorial-elements":
+ case "editorial-items":
+ return "editorialItem";
+ case "developers":
+ return "artist";
+ default:
+ return null;
+ }
+}
+export function emptyStringIfNullOrUndefined(object) {
+ if (object === null || object === undefined) {
+ return "";
+ }
+ return object;
+}
+export function extractSiriRefAppFromRefURL(urlString) {
+ if (!urlString) {
+ return null;
+ }
+ const refUrl = new urls.URL(urlString);
+ let extracteRefApp = null;
+ const query = refUrl.query;
+ if (isSome(query)) {
+ for (const key of Object.keys(query)) {
+ if (key === "referrer") {
+ if (query[key] === "siri") {
+ extracteRefApp = "com.apple.siri";
+ }
+ break;
+ }
+ }
+ }
+ return extracteRefApp;
+}
+export function sanitizedMetricsDictionary(dict) {
+ var _a;
+ if (isNothing(dict)) {
+ return {};
+ }
+ return (_a = serverData.asInterface(sanitizeJson(serverData.asJSONData(dict)))) !== null && _a !== void 0 ? _a : {};
+}
+function sanitizeJson(json) {
+ if (serverData.isNull(json)) {
+ return null;
+ }
+ else if (json instanceof Array) {
+ const arrayCopy = [];
+ for (const value of json) {
+ const sanitizedValue = sanitizeJson(value);
+ if (serverData.isDefinedNonNull(sanitizedValue)) {
+ arrayCopy.push(sanitizedValue);
+ }
+ }
+ return arrayCopy;
+ }
+ else if (json instanceof Object) {
+ const objectCopy = {};
+ Object.keys(json).forEach((key, index, array) => {
+ const value = json[key];
+ const sanitizedValue = sanitizeJson(value);
+ if (serverData.isDefinedNonNull(sanitizedValue)) {
+ objectCopy[key] = sanitizedValue;
+ }
+ });
+ return objectCopy;
+ }
+ return json;
+}
+export function searchTermFromRefURL(refUrlString) {
+ if (!refUrlString) {
+ return null;
+ }
+ const refUrl = new urls.URL(refUrlString);
+ const queryItems = refUrl.query;
+ const searchTerm = queryItems === null || queryItems === void 0 ? void 0 : queryItems["term"];
+ const path = refUrl.pathname;
+ if (serverData.isNull(searchTerm) || serverData.isNull(path)) {
+ return null;
+ }
+ if (!path.endsWith("/search")) {
+ return null;
+ }
+ // the url object has already urldecoded this query parameter
+ const plainTerm = searchTerm;
+ return plainTerm;
+}
+/**
+ * Get a search term from a product URL, if one has been added.
+ * @param productUrlString The URL of a product
+ * @returns A string, if a search term exists.
+ */
+export function searchTermFromProductURL(productUrlString) {
+ if (isNothing(productUrlString)) {
+ return null;
+ }
+ const productUrl = new urls.URL(productUrlString);
+ const queryItems = productUrl.query;
+ const searchTerm = queryItems === null || queryItems === void 0 ? void 0 : queryItems["searchTerm"];
+ const path = productUrl.pathname;
+ if (isNothing(searchTerm) || isNothing(path)) {
+ return null;
+ }
+ if (!path.includes("/app")) {
+ return null;
+ }
+ const plainTerm = searchTerm;
+ return plainTerm;
+}
+/**
+ * Convert a product's data `type` and top lockup icon into a `MetricsPlatformDisplayStyle` object.
+ * This is used to determine how an app icon is presented (i.e. as watch, as atv) for metrics.
+ * @param objectGraph Current object graph
+ * @param data Server data for the app
+ * @param artwork The product's top lockup icon.
+ * @param clientIdentifierOverride The preferred client identifier, if any.
+ * @returns a MetricsPlatformDisplayStyle object.
+ */
+// <rdar://problem/47715014> Metrics: Send editorial intent buy param to finance for watch apps
+export function metricsPlatformDisplayStyleFromData(objectGraph, data, artwork, clientIdentifierOverride) {
+ if (!data || !artwork) {
+ return "unknown";
+ }
+ if (data.type === "app-bundles") {
+ return "bundle";
+ }
+ const artworkStyle = artwork.style;
+ if (isNothing(artworkStyle)) {
+ return "unknown";
+ }
+ switch (artworkStyle) {
+ case "roundedRect":
+ case "roundedRectPrerendered": {
+ return "ios";
+ }
+ case "unadorned": {
+ return "mac";
+ }
+ case "tvRect": {
+ return "tv";
+ }
+ case "round":
+ case "roundPrerendered": {
+ const attributePlatform = content.iconAttributePlatform(objectGraph, data, clientIdentifierOverride !== null && clientIdentifierOverride !== void 0 ? clientIdentifierOverride : undefined);
+ if (attributePlatform === "xros") {
+ return "vision";
+ }
+ else {
+ return "watch";
+ }
+ }
+ case "pill": {
+ return "messages";
+ }
+ case "iap": {
+ return "iap";
+ }
+ default: {
+ errors.unreachable(artworkStyle);
+ return "unknown";
+ }
+ }
+}
+// region Search GhostHint
+/**
+ * Move ghostHint fields for click events
+ * @param eventFields Fields of event to modify in place.
+ */
+export function adjustGhostHintFieldsForClick(eventFields) {
+ /**
+ * Copy `searchGhostHintPrefix` to `searchPrefix` if no prefix is present.
+ * - JS-built search actions specify searchPrefix (matches prefixTerm of hint request).
+ * - Native-built search actions don't specify searchPrefix (dynamic).
+ */
+ const existingSearchPrefix = serverData.asString(eventFields, "searchPrefix");
+ const ghostHintPrefix = serverData.asString(eventFields, "searchGhostHintPrefix");
+ if (serverData.isNull(existingSearchPrefix) && isSome(ghostHintPrefix) && (ghostHintPrefix === null || ghostHintPrefix === void 0 ? void 0 : ghostHintPrefix.length) > 0) {
+ eventFields["searchPrefix"] = ghostHintPrefix;
+ }
+ /**
+ * Delete `searchGhostHintTerm` if phase is pending (i.e. only send if displayed or rejected) per POR.
+ */
+ const ghostHintTermPhase = serverData.asString(eventFields, "searchGhostHintTermPhase");
+ if (ghostHintTermPhase === "pending") {
+ delete eventFields["searchGhostHintTerm"];
+ }
+}
+/**
+ * Move ghostHint fields for seach events
+ * @param eventFields Fields of event to modify in place.
+ */
+export function adjustGhostHintFieldsForSearch(eventFields) {
+ var _a;
+ /**
+ * Copy `searchGhostHintPrefix` to `searchPrefix` if no prefix is present.
+ * - JS-built search actions specify actionDetails.searchPrefix (matches prefixTerm of hint request).
+ * - Native-built search actions don't specify actionDetails.searchPrefix (dynamic).
+ */
+ const actionDetails = (_a = eventFields["actionDetails"]) !== null && _a !== void 0 ? _a : {};
+ const existingSearchPrefix = actionDetails["searchPrefix"];
+ const ghostHintPrefix = serverData.asString(eventFields, "searchGhostHintPrefix");
+ if (serverData.isNull(existingSearchPrefix) && isSome(ghostHintPrefix) && (ghostHintPrefix === null || ghostHintPrefix === void 0 ? void 0 : ghostHintPrefix.length) > 0) {
+ actionDetails["searchPrefix"] = ghostHintPrefix;
+ eventFields["actionDetails"] = actionDetails;
+ }
+ /**
+ * Delete `searchGhostHintTerm` if phase is pending (i.e. only send if displayed or rejected) per POR.
+ */
+ const ghostHintTermPhase = serverData.asString(eventFields, "searchGhostHintTermPhase");
+ if (ghostHintTermPhase === "pending") {
+ delete eventFields["searchGhostHintTerm"];
+ }
+ /**
+ * Prune `searchGhostHintTerm` if `actionType` is `input`, i.e. is from hints page loading.
+ * This is a side-effect of when the event is fired, and otherwise doesn't belong there.
+ */
+ if (eventFields["actionType"] === "input") {
+ delete eventFields["searchGhostHintTerm"];
+ }
+}
+/**
+ * Clean up extraneous generated fields. These extra fields are speculative
+ * to allow some additional JS customization if needed for SSS.
+ * @param eventFields Event fields to modify in place.
+ */
+export function removeExtraGhostHintFields(eventFields) {
+ // Prune prefix annotation.
+ delete eventFields["searchGhostHintPrefix"];
+ // Prune phase annotation.
+ delete eventFields["searchGhostHintTermPhase"];
+ // Prune historical annotation.
+ delete eventFields["searchGhostHintTermLastDisplayed"];
+}
+// endregion
+// region Arcade Upsell Marketing Items
+/**
+ * Returns a dictionary of fields pulled out from the meta.metrics dictionary associated with a marketing item response.
+ * This data comes from Mercury, and we simply pull out relevant fields to be hoisted into the top-level base field on
+ * metrics events (page/impression/click).
+ * @param marketingItemData The marketing item response data.
+ */
+export function marketingItemTopLevelBaseFieldsFromData(objectGraph, marketingItemData) {
+ if (!serverData.isDefinedNonNull(marketingItemData)) {
+ return null;
+ }
+ const fieldsData = {};
+ const marketingDictionary = serverData.asDictionary(marketingItemData, "meta.metrics");
+ if (!serverData.isDefinedNonNullNonEmpty(marketingDictionary)) {
+ return null;
+ }
+ const channelPartner = serverData.asString(marketingDictionary, "channelPartner");
+ if (isSome(channelPartner) && (channelPartner === null || channelPartner === void 0 ? void 0 : channelPartner.length) > 0) {
+ fieldsData["channelPartner"] = channelPartner;
+ }
+ const eligibilityType = serverData.asString(marketingDictionary, "eligibilityType");
+ if (isSome(eligibilityType) && (eligibilityType === null || eligibilityType === void 0 ? void 0 : eligibilityType.length) > 0) {
+ fieldsData["eligibilityType"] = eligibilityType;
+ }
+ const upsellScenario = serverData.asString(marketingDictionary, "upsellScenario");
+ if (isSome(upsellScenario) && (upsellScenario === null || upsellScenario === void 0 ? void 0 : upsellScenario.length) > 0) {
+ fieldsData["upsellScenario"] = upsellScenario;
+ }
+ fieldsData["marketing"] = {
+ marketingItemId: marketingItemData.id,
+ };
+ return fieldsData;
+}
+// endregion
+// region On Device Personalization
+/**
+ * Merges the provided reco metrics data with the on device personalization metrics data.
+ * @param metricsData The input reco metrics data
+ * @param onDevicePersonalizationProcessingType The type of processing that occurred on the data
+ * @param onDevicePersonalizationMetricsData The metrics data provided by the on device personalization framework
+ * @returns Reco metrics data, or null
+ */
+export function combinedRecoMetricsDataFromMetricsData(metricsData, onDevicePersonalizationProcessingType, onDevicePersonalizationMetricsData) {
+ let combinedMetricsData = null;
+ if (serverData.isDefinedNonNull(metricsData)) {
+ combinedMetricsData = objects.shallowCopyOf(metricsData);
+ }
+ if (serverData.isDefinedNonNull(onDevicePersonalizationProcessingType)) {
+ if (serverData.isNull(combinedMetricsData)) {
+ combinedMetricsData = {};
+ }
+ combinedMetricsData["odpModuleUpdate"] = onDevicePersonalizationProcessingType.toString();
+ }
+ if (serverData.isDefinedNonNullNonEmpty(onDevicePersonalizationMetricsData)) {
+ if (serverData.isNull(combinedMetricsData)) {
+ combinedMetricsData = {};
+ }
+ combinedMetricsData["userSegment"] = onDevicePersonalizationMetricsData;
+ }
+ return combinedMetricsData;
+}
+// endregion
+// region Metrics ID
+/**
+ * Clean up dsid fields. We want this as a last line of defense for removing dsid.
+ * @param eventFields Event fields to modify in place.
+ */
+export function removeDSIDFields(eventFields) {
+ // Prune dsid
+ delete eventFields["dsid"];
+ delete eventFields["DSID"];
+}
+// endregion
+//# sourceMappingURL=util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js
new file mode 100644
index 0000000..2e0a81f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js
@@ -0,0 +1,370 @@
+import { isNothing } from "@jet/environment";
+import { asInterface, asJSONData, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, traverse, } from "../../foundation/json-parsing/server-data";
+/**
+ * This class is used as a singleton to track whether we should use the crossfire attribution from the JS purchase
+ * configuration, this is to remedy the following issue:
+ *
+ * rdar://96291594 ([Sydney][Regression][App Store][Clickstream][iTMS11] RefApp missing from page event - (iOS) Sydney20A312a)
+ *
+ * The flow of how this object is used is as follows:
+ *
+ * 1. Deeplink comes in and is routed to the product page controller, containing the crossfire referrer data
+ * 2. We build the product page model and pass the referrer data along
+ * 3. As we're building the page model for this product page, we check to see if we have valid referrer data. If we do
+ * we call into `MetricsReferralContext.beginReferralContextForProduct(appProductId)`, which will create store a unique
+ * referral context identifier for this product page. This identifier will be used to make sure we're only every dealing
+ * with the original product page.
+ * 4. When we create the pageEvent for this product page we modify all the event fields to include unique referral context id,
+ * using `MetricsReferralContext.addReferralContextToMetricsFieldsIfNecessary(metricsFields)`
+ * 5. The referrer data is then added to the purchase configuration for the product lockup's buy button, which comes back to us
+ * at time of purchase decoration
+ * 6. Later when the user taps ont the product buy button and we enter the purchase decoration flow, we check to see if we should
+ * skip adding the native referrer data to the purchase buy params, which would clear out the referrer data since the page change,
+ * event has cleared it out natively. We call `MetricsReferralContext.shouldUseJSReferralData` for this check.
+ * 7. If any page event comes in to the metrics event linter, we check to see if it has a referral context id that is not the
+ * current one, if it does we clear the current metrics referrer contents using `MetricsReferralContext.endReferralContextIfNecessaryForPageExitEvent(pageEvent)`
+ * 8. Additionally in the event linter we always call into `MetricsReferralContext.removeReferralContextInfoFromMetricsEvent(metricsFields)`
+ * which deletes the added referralContextId field.
+ */
+export class MetricsReferralContext {
+ static createSharedMetricsReferralContext(objectGraph) {
+ if (MetricsReferralContext.shared) {
+ return;
+ }
+ MetricsReferralContext.shared = new MetricsReferralContext(objectGraph);
+ }
+ /**
+ * Initializers
+ */
+ /**
+ * @param objectGraph The object graph to use to determine if the referral context is needed.
+ */
+ constructor(objectGraph) {
+ /**
+ * Properties
+ */
+ /**
+ * Identifier denoting that we should use the extRefUrl2 and extRefApp2 from the original purchase configuration
+ * found in the purchase token, rather than using the native metrics data which would clear out the ref data
+ * due to pageChange events.
+ */
+ this.currentReferral = null;
+ if (objectGraph.host.isiOS) {
+ this.isMetricsReferralContextRequired = true;
+ this.isEventDetailClickEventOverrideNecessary = !objectGraph.host.isOSAtLeast(16, 2, 0);
+ }
+ else if (objectGraph.host.isMac) {
+ this.isMetricsReferralContextRequired = objectGraph.host.isOSAtLeast(13, 0, 0);
+ this.isEventDetailClickEventOverrideNecessary = false;
+ }
+ else {
+ this.isMetricsReferralContextRequired = false;
+ this.isEventDetailClickEventOverrideNecessary = false;
+ }
+ }
+ /**
+ * Returns whether we should use the JS referral data or not.
+ */
+ get shouldUseJSReferralData() {
+ return this.isMetricsReferralContextRequired && isDefinedNonNull(this.currentReferral);
+ }
+ /**
+ * Returns the current referral data for the active context
+ */
+ get activeReferralData() {
+ if (!this.shouldUseJSReferralData) {
+ return null;
+ }
+ if (this.currentReferral === null || !this.currentReferral.isActive) {
+ return null;
+ }
+ return this.currentReferral.data;
+ }
+ /**
+ * Setting Referral Data
+ */
+ /**
+ * Called when we get a deep link into the product page and need to make sure we track
+ * the referral data for this page.
+ *
+ * @param productId The id of the product the referral context is for.
+ * @param referrerData The referral data for this product page.
+ */
+ setReferralDataForProduct(productId, referrerData) {
+ var _a, _b, _c;
+ if (!this.isMetricsReferralContextRequired || isNull(referrerData)) {
+ return;
+ }
+ const extRefApp2 = (_a = asString(referrerData, "app")) !== null && _a !== void 0 ? _a : null;
+ const extRefUrl2 = (_b = asString(referrerData, "externalUrl")) !== null && _b !== void 0 ? _b : null;
+ const kind = (_c = asInterface(referrerData, "kind")) !== null && _c !== void 0 ? _c : null;
+ this.currentReferral = {
+ id: `${productId}_${Date.now()}`,
+ data: {
+ extRefApp2,
+ extRefUrl2,
+ kind,
+ refUrl: null,
+ },
+ isActive: false,
+ productPageExtensionInfo: null,
+ };
+ }
+ /**
+ * Called when we are linting a page event, if this page event is for a product page extension, and
+ * we don't yet have an active crossfire referral context, we need to make sure we start one, since there
+ * should always be one in this case. The fact that there is not means that the pageChange event cleared it out
+ * natively.
+ *
+ * Additionally we're going to add the referral context id to the page event so we can track it later, and know
+ * when to end the referral context.
+ *
+ * @param pageEvent The page event that is currently being linted, so we can check to see if we're on a product page,
+ * in the product page extension.
+ */
+ setReferralDataForProductPageExtensionIfNecessary(pageEvent) {
+ var _a, _b;
+ if (!this.isMetricsReferralContextRequired) {
+ return;
+ }
+ const productId = asString(pageEvent, "pageId");
+ const refApp = asString(pageEvent, "refApp");
+ if (!MetricsReferralContextUtil.isProductPageExtension(pageEvent) ||
+ !MetricsReferralContextUtil.isValidPageEvent(pageEvent) ||
+ isNull(productId) ||
+ isNull(refApp)) {
+ return;
+ }
+ const extRefUrl = (_a = asString(pageEvent, "extRefUrl")) !== null && _a !== void 0 ? _a : null;
+ const refAppKindName = asString(pageEvent, "refAppType");
+ let refAppKindContext;
+ switch (refAppKindName) {
+ case "trampoline":
+ refAppKindContext = asJSONData(traverse(pageEvent, "trampolineContext"));
+ break;
+ case "widget":
+ refAppKindContext = asJSONData(traverse(pageEvent, "widgetContext"));
+ break;
+ default:
+ refAppKindContext = {};
+ }
+ const refUrl = (_b = asString(pageEvent, "refUrl")) !== null && _b !== void 0 ? _b : null;
+ this.currentReferral = {
+ id: `${productId}_${Date.now()}`,
+ data: {
+ extRefApp2: refApp,
+ extRefUrl2: extRefUrl,
+ refUrl: refUrl,
+ kind: {
+ name: refAppKindName,
+ context: refAppKindContext,
+ },
+ },
+ isActive: false,
+ productPageExtensionInfo: {
+ productId,
+ },
+ };
+ this.addReferralContextToMetricsFieldsIfNecessary(pageEvent);
+ }
+ /**
+ * Begin / End Metrics Referral Context
+ */
+ /**
+ * Called when we get a deep link into the product page and need to make sure we track
+ * whether the referral data should be used from the js configuration.
+ *
+ * @param pageEvent Some page event that may be associated with the current referral context.
+ */
+ beginReferralContextForPageIfNecessary(pageEvent) {
+ if (!this.isMetricsReferralContextRequired || !MetricsReferralContextUtil.isValidPageEvent(pageEvent)) {
+ return;
+ }
+ if (!MetricsReferralContextUtil.isReferralForEvent(this.currentReferral, pageEvent)) {
+ return;
+ }
+ if (this.currentReferral !== null) {
+ this.currentReferral.isActive = true;
+ }
+ }
+ /**
+ * Called when we get a pageExit event after the page event for the current deeplinked
+ * product page, if there is one. This will reset the flag to use the native metrics. This should
+ * always be the next pageExit event after the page enter event
+ */
+ endReferralContextIfNecessaryForPageEvent(pageExitEvent) {
+ if (!this.isMetricsReferralContextRequired || !MetricsReferralContextUtil.isValidPageEvent(pageExitEvent)) {
+ return;
+ }
+ if (!MetricsReferralContextUtil.isReferralForEvent(this.currentReferral, pageExitEvent)) {
+ return;
+ }
+ this.currentReferral = null;
+ }
+ /**
+ * Setting / Clearing Page Fields
+ */
+ /**
+ * Called when we're building the metrics events for a product page, this way we can tag the events with the current
+ * referral context id if there is one.
+ *
+ * @param pageMetricsFields The page event fields we can modify to track the current product page.
+ */
+ addReferralContextToMetricsFieldsIfNecessary(pageMetricsFields) {
+ var _a;
+ if (!this.isMetricsReferralContextRequired) {
+ return;
+ }
+ pageMetricsFields[MetricsReferralContext.referralContextEventField] = (_a = this.currentReferral) === null || _a === void 0 ? void 0 : _a.id;
+ }
+ /**
+ * Called when linting our metrics events so we can make sure to remove the referral context id, so its not sent to the server
+ *
+ * @param metricsEvent The metrics event we're currently linting.
+ */
+ removeReferralContextInfoFromMetricsEvent(metricsEvent) {
+ if (!this.isMetricsReferralContextRequired) {
+ return;
+ }
+ delete metricsEvent[MetricsReferralContext.referralContextEventField];
+ }
+ /**
+ * Event Attribution
+ */
+ /**
+ * If we have an active referral context, we need to make sure we add the referral data to the event.
+ *
+ * @param metricsEvent The metrics event we're currently linting.
+ */
+ addReferralDataToEventIfNecessary(metricsEvent) {
+ if (isNull(this.activeReferralData)) {
+ return;
+ }
+ if (!MetricsReferralContextUtil.shouldAddReferralDataToEvent(metricsEvent)) {
+ return;
+ }
+ if (MetricsReferralContextUtil.isEventDetailsClickEvent(metricsEvent) &&
+ !this.isEventDetailClickEventOverrideNecessary) {
+ return;
+ }
+ if (MetricsReferralContextUtil.isEventDetailsClickEvent(metricsEvent)) {
+ // Correct the `pageType` of this event for rdar://101302008 ([Sydney][App Store] [Clickstream][iTMS11] click event on EventDetails page from an app referral has incorrect pageType)
+ // Then continue on and apply referral data.
+ metricsEvent["pageType"] = "EventDetails";
+ }
+ metricsEvent["refApp"] = this.activeReferralData.extRefApp2;
+ metricsEvent["extRefUrl"] = this.activeReferralData.extRefUrl2;
+ if (isDefinedNonNullNonEmpty(this.activeReferralData.refUrl)) {
+ metricsEvent["refUrl"] = this.activeReferralData.refUrl;
+ }
+ if (this.activeReferralData !== null && this.activeReferralData.kind !== null) {
+ metricsEvent["refAppType"] = this.activeReferralData.kind.name;
+ switch (metricsEvent["refAppType"]) {
+ case "trampoline":
+ metricsEvent["trampolineContext"] = this.activeReferralData.kind.context;
+ break;
+ case "widget":
+ metricsEvent["widgetContext"] = this.activeReferralData.kind.context;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+/**
+ * They event field to use on a page event so we can determine later if this page event belongs to
+ * the same deeplinked product page.
+ */
+MetricsReferralContext.referralContextEventField = "referralContextId";
+// eslint-disable-next-line @typescript-eslint/no-extraneous-class
+class MetricsReferralContextUtil {
+ /**
+ * Check to see if the pageEvent is within the ProductPageExtension
+ *
+ * @param pageEvent The page event we're checking to see if its in an extension.
+ * @returns Whether "app" for this event is a valid type
+ */
+ static isProductPageExtension(pageEvent) {
+ const app = asString(pageEvent, "app");
+ return app === MetricsReferralContextUtil.productPageExtensionAppId;
+ }
+ /**
+ * Check to see if the current page event is for a product page.
+ *
+ * @param pageEvent The page event we're checking to see if its a product page.
+ * @returns Whether "pageType" for this event is a valid type
+ */
+ static isValidPageEvent(pageEvent) {
+ const pageType = asString(pageEvent, "pageType");
+ if (isNothing(pageType)) {
+ return false;
+ }
+ return MetricsReferralContextUtil.validPageEventTypes.has(pageType);
+ }
+ /**
+ * This method will check the `referralContextEventField` to see if it matches the current referral.
+ *
+ * @param referral The current metrics referral taken from the referral context
+ * @param event Some event to test whether there is an associated referral context. And if so
+ * if that referral context matches.
+ */
+ static isReferralForEvent(referral, event) {
+ var _a;
+ if (isNull(referral)) {
+ return false;
+ }
+ const referralContextId = event[MetricsReferralContext.referralContextEventField];
+ const productId = asString(event, "pageId");
+ if (isDefinedNonNull(referralContextId)) {
+ return referralContextId === referral.id;
+ }
+ else if (MetricsReferralContextUtil.isProductPageExtension(event) && isDefinedNonNull(productId)) {
+ // For product page extensions we do not get a chance to add the referralContextId to the
+ // pageExit event so we need to check the productId to see if it matches the current referral.
+ return productId === ((_a = referral === null || referral === void 0 ? void 0 : referral.productPageExtensionInfo) === null || _a === void 0 ? void 0 : _a.productId);
+ }
+ else {
+ return false;
+ }
+ }
+ static shouldAddReferralDataToEvent(event) {
+ // Generally, we don't want to force referral data onto click events, but this is not true for In-App Events (IAE):
+ // rdar://101399254 ([Sydney] [App Store][Clickstream][iTMS11] Missing refApp and extRefURL on IAE Click Open events through App/Web Referrals)
+ // This only applies prior to SydneyC, as this was fixed natively there.
+ if (event.eventType === "click") {
+ return this.isEventDetailsClickEvent(event);
+ }
+ return true;
+ }
+ /**
+ * Check whether the event is for a click on an In-App Events (IAE) page.
+ *
+ * @param event The event we're checking to see if it's on a an IAE page.
+ */
+ static isEventDetailsClickEvent(event) {
+ if (event.eventType !== "click") {
+ return false;
+ }
+ const location = event.location;
+ const currentLocation = location === null || location === void 0 ? void 0 : location[0];
+ return isDefinedNonNull(currentLocation) && currentLocation.locationType === "EventDetails";
+ }
+}
+/**
+ * The identifier for the product page extension in a metrics page event
+ */
+MetricsReferralContextUtil.productPageExtensionAppId = "com.apple.AppStore.ProductPageExtension";
+/**
+ * The identifier used for the pageType field of an app event page event.
+ */
+MetricsReferralContextUtil.eventDetailsPageType = "EventDetails";
+/**
+ * The set of valid page types for a product page, page event
+ */
+MetricsReferralContextUtil.validPageEventTypes = new Set([
+ "Software",
+ "SoftwareBundle",
+ MetricsReferralContextUtil.eventDetailsPageType,
+]);
+//# sourceMappingURL=metrics-referral-context.js.map \ No newline at end of file