From bce557cc2dc767628bed6aac87301a1be7c5431b Mon Sep 17 00:00:00 2001 From: rxliuli Date: Tue, 4 Nov 2025 05:03:50 +0800 Subject: init commit --- .../src/common/metrics/metrics-referral-context.js | 370 +++++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js (limited to 'node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js') 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 -- cgit v1.2.3