@shware/analytics 8.7.1 → 8.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/storage.cjs +2 -0
- package/dist/constants/storage.cjs.map +1 -1
- package/dist/constants/storage.d.cts +2 -0
- package/dist/constants/storage.d.cts.map +1 -1
- package/dist/constants/storage.d.mts +2 -0
- package/dist/constants/storage.d.mts.map +1 -1
- package/dist/constants/storage.mjs +2 -0
- package/dist/constants/storage.mjs.map +1 -1
- package/dist/native/setup.cjs +28 -1
- package/dist/native/setup.cjs.map +1 -1
- package/dist/native/setup.d.cts.map +1 -1
- package/dist/native/setup.d.mts.map +1 -1
- package/dist/native/setup.mjs +28 -1
- package/dist/native/setup.mjs.map +1 -1
- package/dist/next/index.cjs +1 -1
- package/dist/next/index.mjs +1 -1
- package/dist/react-router/index.cjs +1 -1
- package/dist/react-router/index.mjs +1 -1
- package/package.json +1 -1
|
@@ -5,6 +5,8 @@ const keys = {
|
|
|
5
5
|
visitor_id: "visitor_id",
|
|
6
6
|
first_open_time: "first_open_time",
|
|
7
7
|
first_visit_time: "first_visit_time",
|
|
8
|
+
/** Native: when a launch took the install referrer's utm as its own (see native `getTags`). */
|
|
9
|
+
install_referrer_claim_time: "install_referrer_claim_time",
|
|
8
10
|
session: "session"
|
|
9
11
|
};
|
|
10
12
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.cjs","names":[],"sources":["../../src/constants/storage.ts"],"sourcesContent":["export const keys = {\n device_id: 'device_id',\n visitor_id: 'visitor_id',\n first_open_time: 'first_open_time',\n first_visit_time: 'first_visit_time',\n session: 'session',\n} as const;\n"],"mappings":";;AAAA,MAAa,OAAO;CAClB,WAAW;CACX,YAAY;CACZ,iBAAiB;CACjB,kBAAkB;
|
|
1
|
+
{"version":3,"file":"storage.cjs","names":[],"sources":["../../src/constants/storage.ts"],"sourcesContent":["export const keys = {\n device_id: 'device_id',\n visitor_id: 'visitor_id',\n first_open_time: 'first_open_time',\n first_visit_time: 'first_visit_time',\n /** Native: when a launch took the install referrer's utm as its own (see native `getTags`). */\n install_referrer_claim_time: 'install_referrer_claim_time',\n session: 'session',\n} as const;\n"],"mappings":";;AAAA,MAAa,OAAO;CAClB,WAAW;CACX,YAAY;CACZ,iBAAiB;CACjB,kBAAkB;;CAElB,6BAA6B;CAC7B,SAAS;AACX"}
|
|
@@ -4,6 +4,8 @@ declare const keys: {
|
|
|
4
4
|
readonly visitor_id: 'visitor_id';
|
|
5
5
|
readonly first_open_time: 'first_open_time';
|
|
6
6
|
readonly first_visit_time: 'first_visit_time';
|
|
7
|
+
/** Native: when a launch took the install referrer's utm as its own (see native `getTags`). */
|
|
8
|
+
readonly install_referrer_claim_time: 'install_referrer_claim_time';
|
|
7
9
|
readonly session: 'session';
|
|
8
10
|
};
|
|
9
11
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.cts","names":[],"sources":["../../src/constants/storage.ts"],"mappings":";cAAa;WACX;WACA;WACA;WACA;WACA"}
|
|
1
|
+
{"version":3,"file":"storage.d.cts","names":[],"sources":["../../src/constants/storage.ts"],"mappings":";cAAa;WACX;WACA;WACA;WACA;;WAEA;WACA"}
|
|
@@ -4,6 +4,8 @@ declare const keys: {
|
|
|
4
4
|
readonly visitor_id: 'visitor_id';
|
|
5
5
|
readonly first_open_time: 'first_open_time';
|
|
6
6
|
readonly first_visit_time: 'first_visit_time';
|
|
7
|
+
/** Native: when a launch took the install referrer's utm as its own (see native `getTags`). */
|
|
8
|
+
readonly install_referrer_claim_time: 'install_referrer_claim_time';
|
|
7
9
|
readonly session: 'session';
|
|
8
10
|
};
|
|
9
11
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.mts","names":[],"sources":["../../src/constants/storage.ts"],"mappings":";cAAa;WACX;WACA;WACA;WACA;WACA"}
|
|
1
|
+
{"version":3,"file":"storage.d.mts","names":[],"sources":["../../src/constants/storage.ts"],"mappings":";cAAa;WACX;WACA;WACA;WACA;;WAEA;WACA"}
|
|
@@ -4,6 +4,8 @@ const keys = {
|
|
|
4
4
|
visitor_id: "visitor_id",
|
|
5
5
|
first_open_time: "first_open_time",
|
|
6
6
|
first_visit_time: "first_visit_time",
|
|
7
|
+
/** Native: when a launch took the install referrer's utm as its own (see native `getTags`). */
|
|
8
|
+
install_referrer_claim_time: "install_referrer_claim_time",
|
|
7
9
|
session: "session"
|
|
8
10
|
};
|
|
9
11
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.mjs","names":[],"sources":["../../src/constants/storage.ts"],"sourcesContent":["export const keys = {\n device_id: 'device_id',\n visitor_id: 'visitor_id',\n first_open_time: 'first_open_time',\n first_visit_time: 'first_visit_time',\n session: 'session',\n} as const;\n"],"mappings":";AAAA,MAAa,OAAO;CAClB,WAAW;CACX,YAAY;CACZ,iBAAiB;CACjB,kBAAkB;
|
|
1
|
+
{"version":3,"file":"storage.mjs","names":[],"sources":["../../src/constants/storage.ts"],"sourcesContent":["export const keys = {\n device_id: 'device_id',\n visitor_id: 'visitor_id',\n first_open_time: 'first_open_time',\n first_visit_time: 'first_visit_time',\n /** Native: when a launch took the install referrer's utm as its own (see native `getTags`). */\n install_referrer_claim_time: 'install_referrer_claim_time',\n session: 'session',\n} as const;\n"],"mappings":";AAAA,MAAa,OAAO;CAClB,WAAW;CACX,YAAY;CACZ,iBAAiB;CACjB,kBAAkB;;CAElB,6BAA6B;CAC7B,SAAS;AACX"}
|
package/dist/native/setup.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_setup_index = require("../setup/index.cjs");
|
|
3
|
+
const require_constants_storage = require("../constants/storage.cjs");
|
|
3
4
|
let react_native = require("react-native");
|
|
4
5
|
let expo_application = require("expo-application");
|
|
5
6
|
let expo_device = require("expo-device");
|
|
@@ -72,12 +73,38 @@ function getDeviceType() {
|
|
|
72
73
|
default: return;
|
|
73
74
|
}
|
|
74
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* The install referrer's utm belongs to one launch: the first one that has the referrer in hand
|
|
78
|
+
* — normally the install launch, since `first_open` is the first event the app tracks and
|
|
79
|
+
* tracking it builds the first tags. That launch claims the referrer by writing a marker of its
|
|
80
|
+
* own, and holds the claim for the rest of the process, so every event of the launch and the
|
|
81
|
+
* visitor created from it carry the utm; every later launch finds the marker and sends the raw
|
|
82
|
+
* `install_referrer` alone. Cleared storage and a reinstall lose the marker and claim again,
|
|
83
|
+
* exactly as they make `first_open` fire again.
|
|
84
|
+
*
|
|
85
|
+
* Claimed only once a referrer has actually been resolved. The Install Referrer API needs the
|
|
86
|
+
* Play Store service, which is at its least available right after an install; a launch that
|
|
87
|
+
* could not reach it must not spend the claim on nothing, because the referrer stays readable
|
|
88
|
+
* for 90 days and a later launch can still take it — late attribution, rather than none. iOS
|
|
89
|
+
* has no referrer and so never claims.
|
|
90
|
+
*
|
|
91
|
+
* A marker of its own rather than `first_open_time`, so that nothing here depends on when the
|
|
92
|
+
* hook writes that one relative to the first `getTags`. The storage read waits for that call:
|
|
93
|
+
* `setupAnalytics` and this module's evaluation can run where no storage exists.
|
|
94
|
+
*/
|
|
95
|
+
let installLaunch;
|
|
96
|
+
function claimInstallReferrer() {
|
|
97
|
+
if (require_setup_index.config.storage.getItem(require_constants_storage.keys.install_referrer_claim_time)) return false;
|
|
98
|
+
require_setup_index.config.storage.setItem(require_constants_storage.keys.install_referrer_claim_time, (/* @__PURE__ */ new Date()).toISOString());
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
75
101
|
async function getTags() {
|
|
76
102
|
const screen = react_native.Dimensions.get("screen");
|
|
77
103
|
const screen_width = Math.floor(screen.width);
|
|
78
104
|
const screen_height = Math.floor(screen.height);
|
|
79
105
|
const install_referrer = await getInstallReferrer();
|
|
80
|
-
|
|
106
|
+
if (install_referrer && installLaunch === void 0) installLaunch = claimInstallReferrer();
|
|
107
|
+
const params = new react_native_url_polyfill.URLSearchParams(installLaunch ? install_referrer : void 0);
|
|
81
108
|
const tags = {
|
|
82
109
|
os: `${expo_device.osName} ${expo_device.osVersion}`,
|
|
83
110
|
os_name: expo_device.osName ?? void 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.cjs","names":["Platform","getInstallReferrerAsync","getIosIdForVendorAsync","getAndroidId","randomUUID","deviceType","DeviceType","Dimensions","URLSearchParams","osName","osVersion","modelName","manufacturer","modelId","PixelRatio","
|
|
1
|
+
{"version":3,"file":"setup.cjs","names":["Platform","getInstallReferrerAsync","getIosIdForVendorAsync","getAndroidId","randomUUID","deviceType","DeviceType","config","keys","Dimensions","URLSearchParams","osName","osVersion","modelName","manufacturer","modelId","PixelRatio","getLocales","getCalendars","getAdvertisingId"],"sources":["../../src/native/setup.ts"],"sourcesContent":["import 'expo-sqlite/localStorage/install';\nimport { getAndroidId, getInstallReferrerAsync, getIosIdForVendorAsync } from 'expo-application';\nimport { randomUUID } from 'expo-crypto';\nimport {\n DeviceType,\n deviceType,\n manufacturer,\n modelId,\n modelName,\n osName,\n osVersion,\n} from 'expo-device';\nimport { getCalendars, getLocales } from 'expo-localization';\nimport { getAdvertisingId } from 'expo-tracking-transparency';\nimport { Dimensions, PixelRatio, Platform } from 'react-native';\nimport { URLSearchParams } from 'react-native-url-polyfill';\nimport { keys } from '../constants/storage';\nimport { type Storage, cache, config } from '../setup/index';\nimport type { TrackTags } from '../track/types';\n\nconst map = new Map<string, string>();\n\nexport const storage: Storage = {\n getItem: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n console.error('localStorage is not available');\n return map.get(key) ?? null;\n }\n },\n setItem: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n console.error('localStorage is not available');\n map.set(key, value);\n }\n },\n};\n\n/**\n * `getTags` runs once per event now, and both of these reach for a native module. Neither answer\n * can change while the app is running, so each is resolved once and the promise is reused. A\n * failed lookup is not cached, so the next event tries again.\n */\nlet deviceIdPromise: Promise<string> | undefined;\nlet installReferrerPromise: Promise<string | undefined> | undefined;\n\nexport function getDeviceId(): Promise<string> {\n deviceIdPromise ??= resolveDeviceId().catch((error: unknown) => {\n deviceIdPromise = undefined;\n throw error;\n });\n return deviceIdPromise;\n}\n\nfunction getInstallReferrer(): Promise<string | undefined> {\n if (Platform.OS !== 'android') return Promise.resolve(undefined);\n installReferrerPromise ??= getInstallReferrerAsync().catch((error: unknown) => {\n installReferrerPromise = undefined;\n throw error;\n });\n return installReferrerPromise;\n}\n\nasync function resolveDeviceId(): Promise<string> {\n let deviceId: string | null = null;\n if (Platform.OS === 'ios') {\n deviceId = await getIosIdForVendorAsync();\n } else if (Platform.OS === 'android') {\n deviceId = getAndroidId();\n }\n if (!deviceId) {\n deviceId = localStorage.getItem('device_id');\n if (!deviceId) {\n deviceId = randomUUID();\n localStorage.setItem('device_id', deviceId);\n }\n }\n return deviceId;\n}\n\nexport function getDeviceType(): string | undefined {\n switch (deviceType) {\n case DeviceType.PHONE:\n return 'mobile';\n case DeviceType.TABLET:\n return 'tablet';\n case DeviceType.DESKTOP:\n return 'desktop';\n case DeviceType.TV:\n return 'smarttv';\n default:\n return undefined;\n }\n}\n\n/**\n * The install referrer's utm belongs to one launch: the first one that has the referrer in hand\n * — normally the install launch, since `first_open` is the first event the app tracks and\n * tracking it builds the first tags. That launch claims the referrer by writing a marker of its\n * own, and holds the claim for the rest of the process, so every event of the launch and the\n * visitor created from it carry the utm; every later launch finds the marker and sends the raw\n * `install_referrer` alone. Cleared storage and a reinstall lose the marker and claim again,\n * exactly as they make `first_open` fire again.\n *\n * Claimed only once a referrer has actually been resolved. The Install Referrer API needs the\n * Play Store service, which is at its least available right after an install; a launch that\n * could not reach it must not spend the claim on nothing, because the referrer stays readable\n * for 90 days and a later launch can still take it — late attribution, rather than none. iOS\n * has no referrer and so never claims.\n *\n * A marker of its own rather than `first_open_time`, so that nothing here depends on when the\n * hook writes that one relative to the first `getTags`. The storage read waits for that call:\n * `setupAnalytics` and this module's evaluation can run where no storage exists.\n */\nlet installLaunch: boolean | undefined;\n\nfunction claimInstallReferrer(): boolean {\n if (config.storage.getItem(keys.install_referrer_claim_time)) return false;\n config.storage.setItem(keys.install_referrer_claim_time, new Date().toISOString());\n return true;\n}\n\nexport async function getTags(): Promise<TrackTags> {\n const screen = Dimensions.get('screen');\n const screen_width = Math.floor(screen.width);\n const screen_height = Math.floor(screen.height);\n\n // The install referrer never changes, and it used to be spread into `utm_*` on every launch —\n // so every session of an Android install reported the install campaign as its own acquisition\n // for as long as the app stayed installed, and a report reading a session's tags could not tell\n // the install from the thousandth open. The campaign is the install's touch, not every\n // session's.\n const install_referrer = await getInstallReferrer();\n if (install_referrer && installLaunch === undefined) installLaunch = claimInstallReferrer();\n const params = new URLSearchParams(installLaunch ? install_referrer : undefined);\n\n const tags: TrackTags = {\n os: `${osName} ${osVersion}`,\n os_name: osName ?? undefined,\n os_version: osVersion ?? undefined,\n device: modelName ?? undefined,\n device_id: await getDeviceId(),\n device_type: getDeviceType(),\n device_vendor: manufacturer ?? undefined,\n device_model_id: modelId ?? undefined,\n device_pixel_ratio: PixelRatio.get(),\n screen_width,\n screen_height,\n screen_resolution: `${screen_width}x${screen_height}`,\n release: config.release,\n language: getLocales().at(0)?.languageTag ?? 'en',\n time_zone: getCalendars().at(0)?.timeZone ?? 'UTC',\n // ads\n advertising_id: getAdvertisingId() ?? undefined,\n install_referrer,\n // utm params: from the install referrer, on the launch that claimed it\n utm_source: params.get('utm_source') ?? undefined,\n utm_medium: params.get('utm_medium') ?? undefined,\n utm_campaign: params.get('utm_campaign') ?? undefined,\n utm_term: params.get('utm_term') ?? undefined,\n utm_content: params.get('utm_content') ?? undefined,\n utm_id: params.get('utm_id') ?? undefined,\n utm_source_platform: params.get('utm_source_platform') ?? undefined,\n utm_creative_format: params.get('utm_creative_format') ?? undefined,\n utm_marketing_tactic: params.get('utm_marketing_tactic') ?? undefined,\n };\n\n cache.tags = tags;\n return tags;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.cts","names":[],"sources":["../../src/native/setup.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"setup.d.cts","names":[],"sources":["../../src/native/setup.ts"],"mappings":";;;;cAsBa,SAAS;iBA2BN,eAAe;iBAkCf;iBA0CM,WAAW,QAAQ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.mts","names":[],"sources":["../../src/native/setup.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"setup.d.mts","names":[],"sources":["../../src/native/setup.ts"],"mappings":";;;;cAsBa,SAAS;iBA2BN,eAAe;iBAkCf;iBA0CM,WAAW,QAAQ"}
|
package/dist/native/setup.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cache, config } from "../setup/index.mjs";
|
|
2
|
+
import { keys } from "../constants/storage.mjs";
|
|
2
3
|
import { Dimensions, PixelRatio, Platform } from "react-native";
|
|
3
4
|
import { getAndroidId, getInstallReferrerAsync, getIosIdForVendorAsync } from "expo-application";
|
|
4
5
|
import { DeviceType, deviceType, manufacturer, modelId, modelName, osName, osVersion } from "expo-device";
|
|
@@ -71,12 +72,38 @@ function getDeviceType() {
|
|
|
71
72
|
default: return;
|
|
72
73
|
}
|
|
73
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* The install referrer's utm belongs to one launch: the first one that has the referrer in hand
|
|
77
|
+
* — normally the install launch, since `first_open` is the first event the app tracks and
|
|
78
|
+
* tracking it builds the first tags. That launch claims the referrer by writing a marker of its
|
|
79
|
+
* own, and holds the claim for the rest of the process, so every event of the launch and the
|
|
80
|
+
* visitor created from it carry the utm; every later launch finds the marker and sends the raw
|
|
81
|
+
* `install_referrer` alone. Cleared storage and a reinstall lose the marker and claim again,
|
|
82
|
+
* exactly as they make `first_open` fire again.
|
|
83
|
+
*
|
|
84
|
+
* Claimed only once a referrer has actually been resolved. The Install Referrer API needs the
|
|
85
|
+
* Play Store service, which is at its least available right after an install; a launch that
|
|
86
|
+
* could not reach it must not spend the claim on nothing, because the referrer stays readable
|
|
87
|
+
* for 90 days and a later launch can still take it — late attribution, rather than none. iOS
|
|
88
|
+
* has no referrer and so never claims.
|
|
89
|
+
*
|
|
90
|
+
* A marker of its own rather than `first_open_time`, so that nothing here depends on when the
|
|
91
|
+
* hook writes that one relative to the first `getTags`. The storage read waits for that call:
|
|
92
|
+
* `setupAnalytics` and this module's evaluation can run where no storage exists.
|
|
93
|
+
*/
|
|
94
|
+
let installLaunch;
|
|
95
|
+
function claimInstallReferrer() {
|
|
96
|
+
if (config.storage.getItem(keys.install_referrer_claim_time)) return false;
|
|
97
|
+
config.storage.setItem(keys.install_referrer_claim_time, (/* @__PURE__ */ new Date()).toISOString());
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
74
100
|
async function getTags() {
|
|
75
101
|
const screen = Dimensions.get("screen");
|
|
76
102
|
const screen_width = Math.floor(screen.width);
|
|
77
103
|
const screen_height = Math.floor(screen.height);
|
|
78
104
|
const install_referrer = await getInstallReferrer();
|
|
79
|
-
|
|
105
|
+
if (install_referrer && installLaunch === void 0) installLaunch = claimInstallReferrer();
|
|
106
|
+
const params = new URLSearchParams(installLaunch ? install_referrer : void 0);
|
|
80
107
|
const tags = {
|
|
81
108
|
os: `${osName} ${osVersion}`,
|
|
82
109
|
os_name: osName ?? void 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.mjs","names":[],"sources":["../../src/native/setup.ts"],"sourcesContent":["import 'expo-sqlite/localStorage/install';\nimport { getAndroidId, getInstallReferrerAsync, getIosIdForVendorAsync } from 'expo-application';\nimport { randomUUID } from 'expo-crypto';\nimport {\n DeviceType,\n deviceType,\n manufacturer,\n modelId,\n modelName,\n osName,\n osVersion,\n} from 'expo-device';\nimport { getCalendars, getLocales } from 'expo-localization';\nimport { getAdvertisingId } from 'expo-tracking-transparency';\nimport { Dimensions, PixelRatio, Platform } from 'react-native';\nimport { URLSearchParams } from 'react-native-url-polyfill';\nimport { type Storage, cache, config } from '../setup/index';\nimport type { TrackTags } from '../track/types';\n\nconst map = new Map<string, string>();\n\nexport const storage: Storage = {\n getItem: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n console.error('localStorage is not available');\n return map.get(key) ?? null;\n }\n },\n setItem: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n console.error('localStorage is not available');\n map.set(key, value);\n }\n },\n};\n\n/**\n * `getTags` runs once per event now, and both of these reach for a native module. Neither answer\n * can change while the app is running, so each is resolved once and the promise is reused. A\n * failed lookup is not cached, so the next event tries again.\n */\nlet deviceIdPromise: Promise<string> | undefined;\nlet installReferrerPromise: Promise<string | undefined> | undefined;\n\nexport function getDeviceId(): Promise<string> {\n deviceIdPromise ??= resolveDeviceId().catch((error: unknown) => {\n deviceIdPromise = undefined;\n throw error;\n });\n return deviceIdPromise;\n}\n\nfunction getInstallReferrer(): Promise<string | undefined> {\n if (Platform.OS !== 'android') return Promise.resolve(undefined);\n installReferrerPromise ??= getInstallReferrerAsync().catch((error: unknown) => {\n installReferrerPromise = undefined;\n throw error;\n });\n return installReferrerPromise;\n}\n\nasync function resolveDeviceId(): Promise<string> {\n let deviceId: string | null = null;\n if (Platform.OS === 'ios') {\n deviceId = await getIosIdForVendorAsync();\n } else if (Platform.OS === 'android') {\n deviceId = getAndroidId();\n }\n if (!deviceId) {\n deviceId = localStorage.getItem('device_id');\n if (!deviceId) {\n deviceId = randomUUID();\n localStorage.setItem('device_id', deviceId);\n }\n }\n return deviceId;\n}\n\nexport function getDeviceType(): string | undefined {\n switch (deviceType) {\n case DeviceType.PHONE:\n return 'mobile';\n case DeviceType.TABLET:\n return 'tablet';\n case DeviceType.DESKTOP:\n return 'desktop';\n case DeviceType.TV:\n return 'smarttv';\n default:\n return undefined;\n }\n}\n\nexport async function getTags(): Promise<TrackTags> {\n const screen = Dimensions.get('screen');\n const screen_width = Math.floor(screen.width);\n const screen_height = Math.floor(screen.height);\n\n const install_referrer = await getInstallReferrer();\n const params = new URLSearchParams(install_referrer);\n\n const tags: TrackTags = {\n os: `${osName} ${osVersion}`,\n os_name: osName ?? undefined,\n os_version: osVersion ?? undefined,\n device: modelName ?? undefined,\n device_id: await getDeviceId(),\n device_type: getDeviceType(),\n device_vendor: manufacturer ?? undefined,\n device_model_id: modelId ?? undefined,\n device_pixel_ratio: PixelRatio.get(),\n screen_width,\n screen_height,\n screen_resolution: `${screen_width}x${screen_height}`,\n release: config.release,\n language: getLocales().at(0)?.languageTag ?? 'en',\n time_zone: getCalendars().at(0)?.timeZone ?? 'UTC',\n // ads\n advertising_id: getAdvertisingId() ?? undefined,\n install_referrer,\n // utm params\n utm_source: params.get('utm_source') ?? undefined,\n utm_medium: params.get('utm_medium') ?? undefined,\n utm_campaign: params.get('utm_campaign') ?? undefined,\n utm_term: params.get('utm_term') ?? undefined,\n utm_content: params.get('utm_content') ?? undefined,\n utm_id: params.get('utm_id') ?? undefined,\n utm_source_platform: params.get('utm_source_platform') ?? undefined,\n utm_creative_format: params.get('utm_creative_format') ?? undefined,\n utm_marketing_tactic: params.get('utm_marketing_tactic') ?? undefined,\n };\n\n cache.tags = tags;\n return tags;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"setup.mjs","names":[],"sources":["../../src/native/setup.ts"],"sourcesContent":["import 'expo-sqlite/localStorage/install';\nimport { getAndroidId, getInstallReferrerAsync, getIosIdForVendorAsync } from 'expo-application';\nimport { randomUUID } from 'expo-crypto';\nimport {\n DeviceType,\n deviceType,\n manufacturer,\n modelId,\n modelName,\n osName,\n osVersion,\n} from 'expo-device';\nimport { getCalendars, getLocales } from 'expo-localization';\nimport { getAdvertisingId } from 'expo-tracking-transparency';\nimport { Dimensions, PixelRatio, Platform } from 'react-native';\nimport { URLSearchParams } from 'react-native-url-polyfill';\nimport { keys } from '../constants/storage';\nimport { type Storage, cache, config } from '../setup/index';\nimport type { TrackTags } from '../track/types';\n\nconst map = new Map<string, string>();\n\nexport const storage: Storage = {\n getItem: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n console.error('localStorage is not available');\n return map.get(key) ?? null;\n }\n },\n setItem: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n console.error('localStorage is not available');\n map.set(key, value);\n }\n },\n};\n\n/**\n * `getTags` runs once per event now, and both of these reach for a native module. Neither answer\n * can change while the app is running, so each is resolved once and the promise is reused. A\n * failed lookup is not cached, so the next event tries again.\n */\nlet deviceIdPromise: Promise<string> | undefined;\nlet installReferrerPromise: Promise<string | undefined> | undefined;\n\nexport function getDeviceId(): Promise<string> {\n deviceIdPromise ??= resolveDeviceId().catch((error: unknown) => {\n deviceIdPromise = undefined;\n throw error;\n });\n return deviceIdPromise;\n}\n\nfunction getInstallReferrer(): Promise<string | undefined> {\n if (Platform.OS !== 'android') return Promise.resolve(undefined);\n installReferrerPromise ??= getInstallReferrerAsync().catch((error: unknown) => {\n installReferrerPromise = undefined;\n throw error;\n });\n return installReferrerPromise;\n}\n\nasync function resolveDeviceId(): Promise<string> {\n let deviceId: string | null = null;\n if (Platform.OS === 'ios') {\n deviceId = await getIosIdForVendorAsync();\n } else if (Platform.OS === 'android') {\n deviceId = getAndroidId();\n }\n if (!deviceId) {\n deviceId = localStorage.getItem('device_id');\n if (!deviceId) {\n deviceId = randomUUID();\n localStorage.setItem('device_id', deviceId);\n }\n }\n return deviceId;\n}\n\nexport function getDeviceType(): string | undefined {\n switch (deviceType) {\n case DeviceType.PHONE:\n return 'mobile';\n case DeviceType.TABLET:\n return 'tablet';\n case DeviceType.DESKTOP:\n return 'desktop';\n case DeviceType.TV:\n return 'smarttv';\n default:\n return undefined;\n }\n}\n\n/**\n * The install referrer's utm belongs to one launch: the first one that has the referrer in hand\n * — normally the install launch, since `first_open` is the first event the app tracks and\n * tracking it builds the first tags. That launch claims the referrer by writing a marker of its\n * own, and holds the claim for the rest of the process, so every event of the launch and the\n * visitor created from it carry the utm; every later launch finds the marker and sends the raw\n * `install_referrer` alone. Cleared storage and a reinstall lose the marker and claim again,\n * exactly as they make `first_open` fire again.\n *\n * Claimed only once a referrer has actually been resolved. The Install Referrer API needs the\n * Play Store service, which is at its least available right after an install; a launch that\n * could not reach it must not spend the claim on nothing, because the referrer stays readable\n * for 90 days and a later launch can still take it — late attribution, rather than none. iOS\n * has no referrer and so never claims.\n *\n * A marker of its own rather than `first_open_time`, so that nothing here depends on when the\n * hook writes that one relative to the first `getTags`. The storage read waits for that call:\n * `setupAnalytics` and this module's evaluation can run where no storage exists.\n */\nlet installLaunch: boolean | undefined;\n\nfunction claimInstallReferrer(): boolean {\n if (config.storage.getItem(keys.install_referrer_claim_time)) return false;\n config.storage.setItem(keys.install_referrer_claim_time, new Date().toISOString());\n return true;\n}\n\nexport async function getTags(): Promise<TrackTags> {\n const screen = Dimensions.get('screen');\n const screen_width = Math.floor(screen.width);\n const screen_height = Math.floor(screen.height);\n\n // The install referrer never changes, and it used to be spread into `utm_*` on every launch —\n // so every session of an Android install reported the install campaign as its own acquisition\n // for as long as the app stayed installed, and a report reading a session's tags could not tell\n // the install from the thousandth open. The campaign is the install's touch, not every\n // session's.\n const install_referrer = await getInstallReferrer();\n if (install_referrer && installLaunch === undefined) installLaunch = claimInstallReferrer();\n const params = new URLSearchParams(installLaunch ? install_referrer : undefined);\n\n const tags: TrackTags = {\n os: `${osName} ${osVersion}`,\n os_name: osName ?? undefined,\n os_version: osVersion ?? undefined,\n device: modelName ?? undefined,\n device_id: await getDeviceId(),\n device_type: getDeviceType(),\n device_vendor: manufacturer ?? undefined,\n device_model_id: modelId ?? undefined,\n device_pixel_ratio: PixelRatio.get(),\n screen_width,\n screen_height,\n screen_resolution: `${screen_width}x${screen_height}`,\n release: config.release,\n language: getLocales().at(0)?.languageTag ?? 'en',\n time_zone: getCalendars().at(0)?.timeZone ?? 'UTC',\n // ads\n advertising_id: getAdvertisingId() ?? undefined,\n install_referrer,\n // utm params: from the install referrer, on the launch that claimed it\n utm_source: params.get('utm_source') ?? undefined,\n utm_medium: params.get('utm_medium') ?? undefined,\n utm_campaign: params.get('utm_campaign') ?? undefined,\n utm_term: params.get('utm_term') ?? undefined,\n utm_content: params.get('utm_content') ?? undefined,\n utm_id: params.get('utm_id') ?? undefined,\n utm_source_platform: params.get('utm_source_platform') ?? undefined,\n utm_creative_format: params.get('utm_creative_format') ?? undefined,\n utm_marketing_tactic: params.get('utm_marketing_tactic') ?? undefined,\n };\n\n cache.tags = tags;\n return tags;\n}\n"],"mappings":"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"}
|
package/dist/next/index.cjs
CHANGED
|
@@ -6,11 +6,11 @@ const require_hooks_use_outbound_click_analytics = require("../hooks/use-outboun
|
|
|
6
6
|
const require_hooks_use_uet_id_sync = require("../hooks/use-uet-id-sync.cjs");
|
|
7
7
|
const require_hooks_use_web_analytics = require("../hooks/use-web-analytics.cjs");
|
|
8
8
|
let react = require("react");
|
|
9
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
10
|
let next_navigation_js = require("next/navigation.js");
|
|
10
11
|
let next_script_js = require("next/script.js");
|
|
11
12
|
next_script_js = require_runtime.__toESM(next_script_js, 1);
|
|
12
13
|
let next_web_vitals_js = require("next/web-vitals.js");
|
|
13
|
-
let react_jsx_runtime = require("react/jsx-runtime");
|
|
14
14
|
//#region src/next/index.tsx
|
|
15
15
|
/**
|
|
16
16
|
* Its own component under a Suspense boundary: `useSearchParams` in a statically rendered route
|
package/dist/next/index.mjs
CHANGED
|
@@ -4,10 +4,10 @@ import { useOutboundClickAnalytics } from "../hooks/use-outbound-click-analytics
|
|
|
4
4
|
import { useUETIdSync } from "../hooks/use-uet-id-sync.mjs";
|
|
5
5
|
import { useWebAnalytics } from "../hooks/use-web-analytics.mjs";
|
|
6
6
|
import { Suspense } from "react";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
8
|
import { usePathname, useSearchParams } from "next/navigation.js";
|
|
8
9
|
import Script from "next/script.js";
|
|
9
10
|
import { useReportWebVitals } from "next/web-vitals.js";
|
|
10
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
11
|
//#region src/next/index.tsx
|
|
12
12
|
/**
|
|
13
13
|
* Its own component under a Suspense boundary: `useSearchParams` in a statically rendered route
|
|
@@ -4,8 +4,8 @@ const require_hooks_use_outbound_click_analytics = require("../hooks/use-outboun
|
|
|
4
4
|
const require_hooks_use_report_web_vitals = require("../hooks/use-report-web-vitals.cjs");
|
|
5
5
|
const require_hooks_use_uet_id_sync = require("../hooks/use-uet-id-sync.cjs");
|
|
6
6
|
const require_hooks_use_web_analytics = require("../hooks/use-web-analytics.cjs");
|
|
7
|
-
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
7
|
let react_router = require("react-router");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
9
|
//#region src/react-router/index.tsx
|
|
10
10
|
function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, redditPixelId, linkedInPartnerId, uetTagId, uetCustomerId, hotjarId, facebookAppId, reportWebVitals = true }) {
|
|
11
11
|
const { pathname, search } = (0, react_router.useLocation)();
|
|
@@ -3,8 +3,8 @@ import { useOutboundClickAnalytics } from "../hooks/use-outbound-click-analytics
|
|
|
3
3
|
import { useReportWebVitals } from "../hooks/use-report-web-vitals.mjs";
|
|
4
4
|
import { useUETIdSync } from "../hooks/use-uet-id-sync.mjs";
|
|
5
5
|
import { useWebAnalytics } from "../hooks/use-web-analytics.mjs";
|
|
6
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
6
|
import { useLocation } from "react-router";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/react-router/index.tsx
|
|
9
9
|
function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, redditPixelId, linkedInPartnerId, uetTagId, uetCustomerId, hotjarId, facebookAppId, reportWebVitals = true }) {
|
|
10
10
|
const { pathname, search } = useLocation();
|