@apex-inc/capacitor-plugin 2.4.1 → 2.5.0
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/README.md +41 -4
- package/android/src/main/java/inc/apex/capacitor/ApexCapacitorPlugin.kt +80 -5
- package/android/src/main/java/inc/apex/capacitor/ApexEvents.kt +11 -4
- package/android/src/main/java/inc/apex/capacitor/DeepLinkManager.kt +27 -0
- package/dist/deferred-link.d.ts +73 -0
- package/dist/deferred-link.d.ts.map +1 -0
- package/dist/deferred-link.js +71 -0
- package/dist/deferred-link.js.map +1 -0
- package/dist/definitions.d.ts +76 -4
- package/dist/definitions.d.ts.map +1 -1
- package/dist/esm/deferred-link.d.ts +73 -0
- package/dist/esm/deferred-link.d.ts.map +1 -0
- package/dist/esm/deferred-link.js +68 -0
- package/dist/esm/deferred-link.js.map +1 -0
- package/dist/esm/definitions.d.ts +76 -4
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/events.d.ts +8 -1
- package/dist/esm/events.d.ts.map +1 -1
- package/dist/esm/events.js +8 -1
- package/dist/esm/events.js.map +1 -1
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +157 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/referrer-link.d.ts +36 -0
- package/dist/esm/referrer-link.d.ts.map +1 -0
- package/dist/esm/referrer-link.js +103 -0
- package/dist/esm/referrer-link.js.map +1 -0
- package/dist/esm/skan-conversion-values.d.ts +175 -0
- package/dist/esm/skan-conversion-values.d.ts.map +1 -0
- package/dist/esm/skan-conversion-values.js +323 -0
- package/dist/esm/skan-conversion-values.js.map +1 -0
- package/dist/esm/web.d.ts +6 -1
- package/dist/esm/web.d.ts.map +1 -1
- package/dist/esm/web.js +26 -3
- package/dist/esm/web.js.map +1 -1
- package/dist/events.d.ts +8 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +8 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +166 -1
- package/dist/index.js.map +1 -1
- package/dist/referrer-link.d.ts +36 -0
- package/dist/referrer-link.d.ts.map +1 -0
- package/dist/referrer-link.js +107 -0
- package/dist/referrer-link.js.map +1 -0
- package/dist/skan-conversion-values.d.ts +175 -0
- package/dist/skan-conversion-values.d.ts.map +1 -0
- package/dist/skan-conversion-values.js +331 -0
- package/dist/skan-conversion-values.js.map +1 -0
- package/dist/web.d.ts +6 -1
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +26 -3
- package/dist/web.js.map +1 -1
- package/ios/Sources/ApexCapacitorPlugin/ApexEvents.swift +11 -4
- package/ios/Sources/ApexCapacitorPlugin/DeepLinkManager.swift +16 -2
- package/ios/Sources/ApexCapacitorPlugin/SkanManager.swift +18 -4
- package/ios/Sources/ApexCapacitorPluginBridge/ApexCapacitorPlugin.swift +22 -6
- package/ios/Tests/ApexCapacitorPluginTests/DeepLinkManagerTests.swift +26 -0
- package/ios/Tests/ApexCapacitorPluginTests/SkanManagerTests.swift +22 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -49,9 +49,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
49
49
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
50
50
|
};
|
|
51
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
-
exports.enableAutoScreenViews = exports.trackScreenView = exports.trackCheckoutCompleted = exports.trackCartSnapshot = exports.trackCartRemove = exports.trackCartAdd = exports.APEX_SPEC_VERSION = exports.ApexEvents = exports.isValidEventId = exports.generateEventId = exports.BatchSender = exports.SessionManager = exports.IndexedDBStorage = exports.InMemoryStorage = exports.OfflineQueue = exports.Apex = void 0;
|
|
52
|
+
exports.enableAutoScreenViews = exports.trackScreenView = exports.trackCheckoutCompleted = exports.trackCartSnapshot = exports.trackCartRemove = exports.trackCartAdd = exports.APEX_SPEC_VERSION = exports.ApexEvents = exports.isValidEventId = exports.generateEventId = exports.BatchSender = exports.SessionManager = exports.IndexedDBStorage = exports.InMemoryStorage = exports.OfflineQueue = exports.SKAN_SCHEMA_MAX_AGE_MS = exports.skanWindowAt = exports.pickConversionValue = exports.fetchSkanConversionSchema = exports.SkanAutoConversionValues = exports.parseReferrer = exports.deferredLinkFromReferrer = exports.fetchDeferredDeepLink = exports.Apex = void 0;
|
|
53
|
+
exports.__getSkanAutoConversionValues = __getSkanAutoConversionValues;
|
|
53
54
|
const core_1 = require("@capacitor/core");
|
|
54
55
|
const auto_screen_views_1 = require("./auto-screen-views");
|
|
56
|
+
const deferred_link_1 = require("./deferred-link");
|
|
57
|
+
const referrer_link_1 = require("./referrer-link");
|
|
58
|
+
const skan_conversion_values_1 = require("./skan-conversion-values");
|
|
55
59
|
/**
|
|
56
60
|
* Capacitor plugin proxy. In a native app, calls route through the bridge to
|
|
57
61
|
* the iOS (Swift) or Android (Kotlin) implementation. In a web context
|
|
@@ -75,10 +79,114 @@ const ApexBridge = (0, core_1.registerPlugin)("ApexCapacitorPlugin", {
|
|
|
75
79
|
// so the offline queue / batch sender paths are unchanged. Opt out
|
|
76
80
|
// with `initialize({ autoTrackScreenViews: false })`.
|
|
77
81
|
let disposeAutoScreenViews = null;
|
|
82
|
+
/**
|
|
83
|
+
* Init options retained at the proxy level so `getDeferredDeepLink` can
|
|
84
|
+
* address the API without a second round trip through the bridge. The
|
|
85
|
+
* native layers do not expose their config back to JS, and this is the
|
|
86
|
+
* only caller that needs it.
|
|
87
|
+
*/
|
|
88
|
+
let initOptions = null;
|
|
89
|
+
/**
|
|
90
|
+
* Marks the deferred link as spent so a relaunch doesn't route the user
|
|
91
|
+
* again. The server path is one-shot by itself, but Play keeps the
|
|
92
|
+
* install referrer forever — without this, every cold start of an
|
|
93
|
+
* Android app would re-navigate to the screen from the original ad tap.
|
|
94
|
+
*/
|
|
95
|
+
const CLAIMED_KEY = "apex_ddl_claimed";
|
|
96
|
+
function alreadyClaimed() {
|
|
97
|
+
try {
|
|
98
|
+
return globalThis.localStorage?.getItem(CLAIMED_KEY) === "1";
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Private mode / storage disabled. Falling through means at worst we
|
|
102
|
+
// resolve twice, which is better than never resolving at all.
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function markClaimed() {
|
|
107
|
+
try {
|
|
108
|
+
globalThis.localStorage?.setItem(CLAIMED_KEY, "1");
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
/* see alreadyClaimed */
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Resolve the link the user was headed for before they installed.
|
|
116
|
+
*
|
|
117
|
+
* Two mechanisms, tried in order of certainty:
|
|
118
|
+
*
|
|
119
|
+
* 1. **Play install referrer (Android, deterministic).** Google
|
|
120
|
+
* carries our routing payload across the install gap, so the app
|
|
121
|
+
* learns the exact link with no network round trip and no chance of
|
|
122
|
+
* matching the wrong person.
|
|
123
|
+
* 2. **Server fingerprint match (both platforms, probabilistic).** The
|
|
124
|
+
* fallback, and the only option on iOS, since the App Store
|
|
125
|
+
* forwards nothing.
|
|
126
|
+
*
|
|
127
|
+
* Lives at the proxy level rather than in each native bridge because
|
|
128
|
+
* step 2 is one HTTPS call with no platform APIs in it, and step 1 only
|
|
129
|
+
* needs the referrer string the bridge already returns.
|
|
130
|
+
*/
|
|
131
|
+
async function getDeferredDeepLink() {
|
|
132
|
+
const platform = core_1.Capacitor.getPlatform();
|
|
133
|
+
// Web has no install gap to bridge; see the web fallback for why
|
|
134
|
+
// claiming a link there would be actively wrong.
|
|
135
|
+
if (platform !== "ios" && platform !== "android")
|
|
136
|
+
return null;
|
|
137
|
+
if (alreadyClaimed())
|
|
138
|
+
return null;
|
|
139
|
+
if (platform === "android") {
|
|
140
|
+
try {
|
|
141
|
+
const { referrer, clickTimestamp } = await ApexBridge.getInstallReferrer();
|
|
142
|
+
const fromReferrer = (0, referrer_link_1.deferredLinkFromReferrer)(referrer, {
|
|
143
|
+
clickTimestampSeconds: clickTimestamp,
|
|
144
|
+
});
|
|
145
|
+
if (fromReferrer) {
|
|
146
|
+
markClaimed();
|
|
147
|
+
return fromReferrer;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// An older native build without the awaiting referrer method, or a
|
|
152
|
+
// device with no Play services. Fall through to the server match.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const workspaceKey = initOptions?.workspaceKey ?? initOptions?.projectKey ?? "";
|
|
156
|
+
if (!workspaceKey)
|
|
157
|
+
return null;
|
|
158
|
+
const resolved = await (0, deferred_link_1.fetchDeferredDeepLink)({
|
|
159
|
+
apiUrl: initOptions?.apiUrl ?? "https://app.apex.inc",
|
|
160
|
+
workspaceKey,
|
|
161
|
+
platform,
|
|
162
|
+
apiKey: initOptions?.apiKey,
|
|
163
|
+
});
|
|
164
|
+
if (resolved)
|
|
165
|
+
markClaimed();
|
|
166
|
+
return resolved;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Mobile Measurement P2 — automatic SKAN conversion values. Lives at the
|
|
170
|
+
* proxy level for the same reason `screen_view` auto-tracking does: the
|
|
171
|
+
* decision ("does this event mean something to Apple?") is one HTTPS
|
|
172
|
+
* fetch plus a table lookup in webview JS, and the send goes through the
|
|
173
|
+
* bridge's existing `updateConversionValue`. Null when the option is off
|
|
174
|
+
* or the platform is not iOS.
|
|
175
|
+
*/
|
|
176
|
+
let skanAuto = null;
|
|
177
|
+
function debugLog(options, message) {
|
|
178
|
+
if (options?.debug)
|
|
179
|
+
console.log(`[apex-capacitor] ${message}`);
|
|
180
|
+
}
|
|
181
|
+
/** Exposed for tests: the controller behind `autoConversionValues`. */
|
|
182
|
+
function __getSkanAutoConversionValues() {
|
|
183
|
+
return skanAuto;
|
|
184
|
+
}
|
|
78
185
|
exports.Apex = new Proxy(ApexBridge, {
|
|
79
186
|
get(target, prop, receiver) {
|
|
80
187
|
if (prop === "initialize") {
|
|
81
188
|
return async (options) => {
|
|
189
|
+
initOptions = options;
|
|
82
190
|
await target.initialize(options);
|
|
83
191
|
disposeAutoScreenViews?.();
|
|
84
192
|
disposeAutoScreenViews = null;
|
|
@@ -91,6 +199,47 @@ exports.Apex = new Proxy(ApexBridge, {
|
|
|
91
199
|
},
|
|
92
200
|
});
|
|
93
201
|
}
|
|
202
|
+
skanAuto = null;
|
|
203
|
+
const workspaceKey = options?.workspaceKey ?? options?.projectKey ?? "";
|
|
204
|
+
if (options?.autoConversionValues !== false &&
|
|
205
|
+
core_1.Capacitor.getPlatform() === "ios" &&
|
|
206
|
+
workspaceKey) {
|
|
207
|
+
skanAuto = new skan_conversion_values_1.SkanAutoConversionValues({
|
|
208
|
+
update: (value) => target.updateConversionValue(value),
|
|
209
|
+
log: (message) => debugLog(options, message),
|
|
210
|
+
});
|
|
211
|
+
await skanAuto.initialize({
|
|
212
|
+
apiUrl: options.apiUrl ?? "https://app.apex.inc",
|
|
213
|
+
workspaceKey,
|
|
214
|
+
mobileAppId: options.mobileAppId ?? null,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (prop === "getDeferredDeepLink") {
|
|
220
|
+
return getDeferredDeepLink;
|
|
221
|
+
}
|
|
222
|
+
if (prop === "track") {
|
|
223
|
+
return async (event) => {
|
|
224
|
+
await target.track(event);
|
|
225
|
+
if (skanAuto && event?.type) {
|
|
226
|
+
void skanAuto.onTrack(event.type).catch(() => undefined);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (prop === "updateConversionValue") {
|
|
231
|
+
return async (value) => {
|
|
232
|
+
skanAuto?.noteManualUpdate();
|
|
233
|
+
await target.updateConversionValue(value);
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (prop === "getConversionValueSchema") {
|
|
237
|
+
return async () => {
|
|
238
|
+
if (!skanAuto) {
|
|
239
|
+
return { schema: null, mobileAppId: null, etag: null, fetchedAt: 0 };
|
|
240
|
+
}
|
|
241
|
+
await skanAuto.whenReady();
|
|
242
|
+
return skanAuto.getSchema();
|
|
94
243
|
};
|
|
95
244
|
}
|
|
96
245
|
return Reflect.get(target, prop, receiver);
|
|
@@ -98,6 +247,22 @@ exports.Apex = new Proxy(ApexBridge, {
|
|
|
98
247
|
});
|
|
99
248
|
// Re-export types so consumers can import them cleanly.
|
|
100
249
|
__exportStar(require("./definitions"), exports);
|
|
250
|
+
// Deferred deep linking — the resolver is exported for advanced use
|
|
251
|
+
// (e.g. a custom bootstrap that already knows its platform).
|
|
252
|
+
var deferred_link_2 = require("./deferred-link");
|
|
253
|
+
Object.defineProperty(exports, "fetchDeferredDeepLink", { enumerable: true, get: function () { return deferred_link_2.fetchDeferredDeepLink; } });
|
|
254
|
+
var referrer_link_2 = require("./referrer-link");
|
|
255
|
+
Object.defineProperty(exports, "deferredLinkFromReferrer", { enumerable: true, get: function () { return referrer_link_2.deferredLinkFromReferrer; } });
|
|
256
|
+
Object.defineProperty(exports, "parseReferrer", { enumerable: true, get: function () { return referrer_link_2.parseReferrer; } });
|
|
257
|
+
// Mobile Measurement P2 — automatic SKAN conversion values. The pure
|
|
258
|
+
// pieces are exported for advanced use (a custom bootstrap that wants to
|
|
259
|
+
// read the table itself) and for tests.
|
|
260
|
+
var skan_conversion_values_2 = require("./skan-conversion-values");
|
|
261
|
+
Object.defineProperty(exports, "SkanAutoConversionValues", { enumerable: true, get: function () { return skan_conversion_values_2.SkanAutoConversionValues; } });
|
|
262
|
+
Object.defineProperty(exports, "fetchSkanConversionSchema", { enumerable: true, get: function () { return skan_conversion_values_2.fetchSkanConversionSchema; } });
|
|
263
|
+
Object.defineProperty(exports, "pickConversionValue", { enumerable: true, get: function () { return skan_conversion_values_2.pickConversionValue; } });
|
|
264
|
+
Object.defineProperty(exports, "skanWindowAt", { enumerable: true, get: function () { return skan_conversion_values_2.skanWindowAt; } });
|
|
265
|
+
Object.defineProperty(exports, "SKAN_SCHEMA_MAX_AGE_MS", { enumerable: true, get: function () { return skan_conversion_values_2.SKAN_SCHEMA_MAX_AGE_MS; } });
|
|
101
266
|
// Re-export core modules for advanced/test usage.
|
|
102
267
|
var offline_queue_1 = require("./offline-queue");
|
|
103
268
|
Object.defineProperty(exports, "OfflineQueue", { enumerable: true, get: function () { return offline_queue_1.OfflineQueue; } });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic deferred deep linking on Android, from the Play referrer.
|
|
3
|
+
*
|
|
4
|
+
* The server-side path (`./deferred-link.ts`) matches an install back to
|
|
5
|
+
* a tap using `hash(ip)#platform`, because that is all a browser tap and
|
|
6
|
+
* a fresh app install have in common. It works, and it is the only thing
|
|
7
|
+
* available on iOS, but it cannot tell two phones on the same office
|
|
8
|
+
* wifi apart — which is why it is one-shot and short-windowed.
|
|
9
|
+
*
|
|
10
|
+
* Android does not need to guess. Google Play carries one string across
|
|
11
|
+
* the install gap, the `referrer` param, and Apex packs the link's
|
|
12
|
+
* routing payload into it at redirect time (`links/play-referrer.ts` on
|
|
13
|
+
* the server). At first launch the Play Install Referrer API hands that
|
|
14
|
+
* exact string back, so the app learns precisely which link the user
|
|
15
|
+
* tapped: no IP, no fingerprint, no ambiguity, and no round trip.
|
|
16
|
+
*
|
|
17
|
+
* This module is the parsing half. It is pure so it can be tested
|
|
18
|
+
* without a device; the plugin proxy owns when to call it.
|
|
19
|
+
*/
|
|
20
|
+
import type { DeferredDeepLinkResult } from "./deferred-link";
|
|
21
|
+
/** Parse a `k=v&k=v` referrer string. Play hands it over URL-encoded. */
|
|
22
|
+
export declare function parseReferrer(raw: string): Record<string, string>;
|
|
23
|
+
export interface ReferrerLinkOptions {
|
|
24
|
+
/** Unix seconds, from the Play Install Referrer API. 0 when unknown. */
|
|
25
|
+
clickTimestampSeconds?: number;
|
|
26
|
+
/** Injectable for tests. */
|
|
27
|
+
now?: number;
|
|
28
|
+
maxAgeMs?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Turn a Play referrer into a deferred deep link, or null when it
|
|
32
|
+
* carries no Apex routing payload (an organic install, or a referrer
|
|
33
|
+
* written by another attribution provider).
|
|
34
|
+
*/
|
|
35
|
+
export declare function deferredLinkFromReferrer(raw: string | null | undefined, options?: ReferrerLinkOptions): DeferredDeepLinkResult | null;
|
|
36
|
+
//# sourceMappingURL=referrer-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"referrer-link.d.ts","sourceRoot":"","sources":["../src/referrer-link.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAuB9D,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUjE;AAED,MAAM,WAAW,mBAAmB;IAClC,wEAAwE;IACxE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,4BAA4B;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC9B,OAAO,GAAE,mBAAwB,GAChC,sBAAsB,GAAG,IAAI,CA4C/B"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic deferred deep linking on Android, from the Play referrer.
|
|
4
|
+
*
|
|
5
|
+
* The server-side path (`./deferred-link.ts`) matches an install back to
|
|
6
|
+
* a tap using `hash(ip)#platform`, because that is all a browser tap and
|
|
7
|
+
* a fresh app install have in common. It works, and it is the only thing
|
|
8
|
+
* available on iOS, but it cannot tell two phones on the same office
|
|
9
|
+
* wifi apart — which is why it is one-shot and short-windowed.
|
|
10
|
+
*
|
|
11
|
+
* Android does not need to guess. Google Play carries one string across
|
|
12
|
+
* the install gap, the `referrer` param, and Apex packs the link's
|
|
13
|
+
* routing payload into it at redirect time (`links/play-referrer.ts` on
|
|
14
|
+
* the server). At first launch the Play Install Referrer API hands that
|
|
15
|
+
* exact string back, so the app learns precisely which link the user
|
|
16
|
+
* tapped: no IP, no fingerprint, no ambiguity, and no round trip.
|
|
17
|
+
*
|
|
18
|
+
* This module is the parsing half. It is pure so it can be tested
|
|
19
|
+
* without a device; the plugin proxy owns when to call it.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.parseReferrer = parseReferrer;
|
|
23
|
+
exports.deferredLinkFromReferrer = deferredLinkFromReferrer;
|
|
24
|
+
/**
|
|
25
|
+
* How long a referrer stays worth honouring, matching the server's
|
|
26
|
+
* routing window. Play keeps the referrer forever, so without an age
|
|
27
|
+
* check an app reinstalled a year later would route the user to last
|
|
28
|
+
* year's promotion.
|
|
29
|
+
*/
|
|
30
|
+
const MAX_REFERRER_AGE_MS = 2 * 60 * 60 * 1000;
|
|
31
|
+
/** Set by `play-referrer.ts` on the server. Keep the two in sync. */
|
|
32
|
+
const DEEP_LINK_KEY = "apex_dl";
|
|
33
|
+
const LINK_REF_KEY = "apex_link";
|
|
34
|
+
const DESTINATION_KEY = "apex_dest";
|
|
35
|
+
function decode(value) {
|
|
36
|
+
try {
|
|
37
|
+
return decodeURIComponent(value.replace(/\+/g, " "));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Parse a `k=v&k=v` referrer string. Play hands it over URL-encoded. */
|
|
44
|
+
function parseReferrer(raw) {
|
|
45
|
+
const out = {};
|
|
46
|
+
for (const segment of raw.split("&")) {
|
|
47
|
+
const idx = segment.indexOf("=");
|
|
48
|
+
if (idx <= 0)
|
|
49
|
+
continue;
|
|
50
|
+
const key = decode(segment.slice(0, idx));
|
|
51
|
+
const value = decode(segment.slice(idx + 1));
|
|
52
|
+
if (key && value)
|
|
53
|
+
out[key] = value;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Turn a Play referrer into a deferred deep link, or null when it
|
|
59
|
+
* carries no Apex routing payload (an organic install, or a referrer
|
|
60
|
+
* written by another attribution provider).
|
|
61
|
+
*/
|
|
62
|
+
function deferredLinkFromReferrer(raw, options = {}) {
|
|
63
|
+
if (!raw)
|
|
64
|
+
return null;
|
|
65
|
+
const params = parseReferrer(raw);
|
|
66
|
+
const hint = params[DEEP_LINK_KEY] ?? null;
|
|
67
|
+
const destination = params[DESTINATION_KEY] ?? "";
|
|
68
|
+
const linkRef = params[LINK_REF_KEY];
|
|
69
|
+
// No routing payload means this referrer isn't ours to act on. UTMs
|
|
70
|
+
// alone don't qualify: they say which campaign paid for the install,
|
|
71
|
+
// not which screen the user was headed for, and routing on a campaign
|
|
72
|
+
// name would send people somewhere nobody asked for.
|
|
73
|
+
if (!hint && !destination)
|
|
74
|
+
return null;
|
|
75
|
+
const clickSeconds = options.clickTimestampSeconds ?? 0;
|
|
76
|
+
const clickedAtMs = clickSeconds > 0 ? clickSeconds * 1000 : 0;
|
|
77
|
+
if (clickedAtMs > 0) {
|
|
78
|
+
const age = (options.now ?? Date.now()) - clickedAtMs;
|
|
79
|
+
if (age > (options.maxAgeMs ?? MAX_REFERRER_AGE_MS))
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
// A namespaced partner link is stamped `handle/slug` — same shape the
|
|
83
|
+
// server's `linkRefValue` writes.
|
|
84
|
+
let slug;
|
|
85
|
+
let handle;
|
|
86
|
+
if (linkRef) {
|
|
87
|
+
const parts = linkRef.split("/").filter(Boolean);
|
|
88
|
+
if (parts.length === 2) {
|
|
89
|
+
handle = parts[0];
|
|
90
|
+
slug = parts[1];
|
|
91
|
+
}
|
|
92
|
+
else if (parts.length === 1) {
|
|
93
|
+
slug = parts[0];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
url: destination,
|
|
98
|
+
deepLinkHint: hint,
|
|
99
|
+
slug,
|
|
100
|
+
handle,
|
|
101
|
+
source: params.utm_source,
|
|
102
|
+
campaign: params.utm_campaign,
|
|
103
|
+
clickedAt: new Date(clickedAtMs > 0 ? clickedAtMs : Date.now()).toISOString(),
|
|
104
|
+
matchType: "deterministic",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=referrer-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"referrer-link.js","sourceRoot":"","sources":["../src/referrer-link.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic SKAdNetwork conversion values.
|
|
3
|
+
*
|
|
4
|
+
* The shop answers one question in Set up Apex ("which event means an
|
|
5
|
+
* install was worth it?") and Apex turns it into a table of what Apple's
|
|
6
|
+
* 0–63 numbers mean for the app. This module reads that table and, every
|
|
7
|
+
* time the app tracks an event that appears in it, tells Apple the new
|
|
8
|
+
* value — so the shop never writes `updateConversionValue` by hand.
|
|
9
|
+
*
|
|
10
|
+
* Behaviour, in order of importance:
|
|
11
|
+
*
|
|
12
|
+
* - **Manual wins.** The first time the app calls
|
|
13
|
+
* `Apex.updateConversionValue` itself, automatic updates stop for
|
|
14
|
+
* that install. A shop that hand-rolls its ladder keeps its ladder.
|
|
15
|
+
* - **Values only go up.** Apple keeps the last value it was given, and
|
|
16
|
+
* every network reads the ladder as "higher means more". Within a
|
|
17
|
+
* postback window the plugin never sends a lower value than it has
|
|
18
|
+
* already sent.
|
|
19
|
+
* - **Never in the way.** The schema is fetched once per day and cached;
|
|
20
|
+
* a slow or unreachable Apex means the app runs with yesterday's
|
|
21
|
+
* table (or none), never a delayed first screen.
|
|
22
|
+
*
|
|
23
|
+
* SKAN 4 windows, counted from first launch: window 1 covers days 0–2,
|
|
24
|
+
* window 2 days 3–7, window 3 days 8–35. After day 35 Apple is no longer
|
|
25
|
+
* listening and nothing is sent.
|
|
26
|
+
*
|
|
27
|
+
* Pure TypeScript on purpose: one HTTPS call and a little arithmetic, no
|
|
28
|
+
* platform APIs, so it runs identically under the native iOS bridge and
|
|
29
|
+
* in browser preview (where the update itself is a no-op).
|
|
30
|
+
*/
|
|
31
|
+
import type { SkanCoarseValue } from "./definitions";
|
|
32
|
+
export type SkanWindow = 1 | 2 | 3;
|
|
33
|
+
export interface SkanConversionValueEntry {
|
|
34
|
+
eventName: string;
|
|
35
|
+
fineValue: number;
|
|
36
|
+
coarseValue: SkanCoarseValue;
|
|
37
|
+
lockWindow?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface SkanConversionWindow {
|
|
40
|
+
window: SkanWindow;
|
|
41
|
+
values: SkanConversionValueEntry[];
|
|
42
|
+
}
|
|
43
|
+
export interface SkanConversionSchema {
|
|
44
|
+
windows: SkanConversionWindow[];
|
|
45
|
+
updatedAt: string;
|
|
46
|
+
}
|
|
47
|
+
/** What {@link fetchSkanConversionSchema} and the cache carry. */
|
|
48
|
+
export interface SkanSchemaSnapshot {
|
|
49
|
+
schema: SkanConversionSchema | null;
|
|
50
|
+
mobileAppId: string | null;
|
|
51
|
+
/** Weak ETag from the server, replayed as `If-None-Match`. */
|
|
52
|
+
etag: string | null;
|
|
53
|
+
/** Epoch ms when this snapshot was confirmed fresh. */
|
|
54
|
+
fetchedAt: number;
|
|
55
|
+
}
|
|
56
|
+
export interface FetchSkanSchemaOptions {
|
|
57
|
+
apiUrl: string;
|
|
58
|
+
workspaceKey: string;
|
|
59
|
+
mobileAppId?: string | null;
|
|
60
|
+
/** Previous ETag; a matching server tag returns `not_modified`. */
|
|
61
|
+
etag?: string | null;
|
|
62
|
+
/** Abort after this many ms. Defaults to 5000. */
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
fetch?: typeof fetch;
|
|
65
|
+
}
|
|
66
|
+
export type FetchSkanSchemaResult = {
|
|
67
|
+
status: "fresh";
|
|
68
|
+
snapshot: SkanSchemaSnapshot;
|
|
69
|
+
} | {
|
|
70
|
+
status: "not_modified";
|
|
71
|
+
} | {
|
|
72
|
+
status: "error";
|
|
73
|
+
};
|
|
74
|
+
export declare function fetchSkanConversionSchema(options: FetchSkanSchemaOptions, now?: () => number): Promise<FetchSkanSchemaResult>;
|
|
75
|
+
/**
|
|
76
|
+
* Keep only rungs the plugin can act on. The server validates on write,
|
|
77
|
+
* but a cached snapshot may outlive a schema change, so be strict here
|
|
78
|
+
* too: a bad rung is dropped, never sent to Apple.
|
|
79
|
+
*/
|
|
80
|
+
export declare function normalizeSchema(raw: SkanConversionSchema | null | undefined): SkanConversionSchema | null;
|
|
81
|
+
/**
|
|
82
|
+
* Which SKAN 4 postback window a moment falls in, counted from first
|
|
83
|
+
* launch. Null once Apple has stopped listening (after day 35).
|
|
84
|
+
*/
|
|
85
|
+
export declare function skanWindowAt(installedAt: number, now: number): SkanWindow | null;
|
|
86
|
+
/** What to tell Apple, when an event maps and is worth telling. */
|
|
87
|
+
export interface ConversionValueUpdate {
|
|
88
|
+
fineValue: number;
|
|
89
|
+
coarseValue: SkanCoarseValue;
|
|
90
|
+
lockWindow?: boolean;
|
|
91
|
+
window: SkanWindow;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Decide whether tracking `eventName` should change Apple's value.
|
|
95
|
+
* Returns null when the event has no meaning in the current window or
|
|
96
|
+
* when the mapped value would not move the ladder up.
|
|
97
|
+
*/
|
|
98
|
+
export declare function pickConversionValue(schema: SkanConversionSchema | null, eventName: string, ctx: {
|
|
99
|
+
installedAt: number;
|
|
100
|
+
now: number;
|
|
101
|
+
highestSent: number | null;
|
|
102
|
+
}): ConversionValueUpdate | null;
|
|
103
|
+
/** Minimal key/value store; `localStorage` fits, so does a Map in tests. */
|
|
104
|
+
export interface SkanValueStore {
|
|
105
|
+
getItem(key: string): string | null;
|
|
106
|
+
setItem(key: string, value: string): void;
|
|
107
|
+
}
|
|
108
|
+
export declare const SKAN_STORAGE_KEYS: {
|
|
109
|
+
readonly snapshot: "apex_skan_schema_v1";
|
|
110
|
+
readonly installedAt: "apex_skan_installed_at";
|
|
111
|
+
readonly manual: "apex_skan_manual";
|
|
112
|
+
readonly highest: "apex_skan_highest_v1";
|
|
113
|
+
};
|
|
114
|
+
/** How long a cached schema is trusted before revalidating. */
|
|
115
|
+
export declare const SKAN_SCHEMA_MAX_AGE_MS: number;
|
|
116
|
+
export interface SkanAutoConversionValuesOptions {
|
|
117
|
+
/** Sends the value to Apple (the bridge's `updateConversionValue`). */
|
|
118
|
+
update: (value: {
|
|
119
|
+
fineValue: number;
|
|
120
|
+
coarseValue?: SkanCoarseValue;
|
|
121
|
+
lockWindow?: boolean;
|
|
122
|
+
}) => Promise<void>;
|
|
123
|
+
store?: SkanValueStore | null;
|
|
124
|
+
fetch?: typeof fetch;
|
|
125
|
+
now?: () => number;
|
|
126
|
+
/** Debug sink; receives one line per decision when set. */
|
|
127
|
+
log?: (message: string) => void;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Owns the cached schema and the "what have I already told Apple"
|
|
131
|
+
* bookkeeping for one install. Created by the plugin proxy on
|
|
132
|
+
* `initialize({ autoConversionValues: true })`; not something an app
|
|
133
|
+
* constructs itself.
|
|
134
|
+
*/
|
|
135
|
+
export declare class SkanAutoConversionValues {
|
|
136
|
+
private readonly update;
|
|
137
|
+
private readonly store;
|
|
138
|
+
private readonly fetchImpl?;
|
|
139
|
+
private readonly now;
|
|
140
|
+
private readonly log?;
|
|
141
|
+
private snapshot;
|
|
142
|
+
private installedAt;
|
|
143
|
+
private manual;
|
|
144
|
+
private highest;
|
|
145
|
+
private ready;
|
|
146
|
+
private endpoint;
|
|
147
|
+
constructor(options: SkanAutoConversionValuesOptions);
|
|
148
|
+
/**
|
|
149
|
+
* Point at a workspace and refresh the schema if the cache is stale.
|
|
150
|
+
* Resolves as soon as the cache has been read; the network revalidate
|
|
151
|
+
* runs in the background and never blocks the caller.
|
|
152
|
+
*/
|
|
153
|
+
initialize(endpoint: {
|
|
154
|
+
apiUrl: string;
|
|
155
|
+
workspaceKey: string;
|
|
156
|
+
mobileAppId?: string | null;
|
|
157
|
+
}): Promise<void>;
|
|
158
|
+
/** Force a network revalidation (ETag-aware). */
|
|
159
|
+
revalidate(): Promise<void>;
|
|
160
|
+
/** Wait for any in-flight revalidation (tests and debug UIs). */
|
|
161
|
+
whenReady(): Promise<void>;
|
|
162
|
+
/** The schema the plugin is acting on right now. */
|
|
163
|
+
getSchema(): SkanSchemaSnapshot;
|
|
164
|
+
/** True once the app has called `updateConversionValue` itself. */
|
|
165
|
+
get manualMode(): boolean;
|
|
166
|
+
/** Called when the app updates Apple's value by hand. Auto steps aside. */
|
|
167
|
+
noteManualUpdate(): void;
|
|
168
|
+
/**
|
|
169
|
+
* Called after every `track`. Sends the mapped value when the event has
|
|
170
|
+
* one in the current window and it moves the ladder up.
|
|
171
|
+
*/
|
|
172
|
+
onTrack(eventName: string): Promise<ConversionValueUpdate | null>;
|
|
173
|
+
private readInstalledAt;
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=skan-conversion-values.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skan-conversion-values.d.ts","sourceRoot":"","sources":["../src/skan-conversion-values.ts"],"names":[],"mappings":"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"}
|