summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js370
1 files changed, 370 insertions, 0 deletions
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