@apex-inc/capacitor-plugin 2.4.1 → 2.5.1

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.
Files changed (65) hide show
  1. package/README.md +66 -14
  2. package/android/src/main/java/inc/apex/capacitor/ApexCapacitorPlugin.kt +145 -15
  3. package/android/src/main/java/inc/apex/capacitor/ApexEvents.kt +11 -4
  4. package/android/src/main/java/inc/apex/capacitor/DeepLinkManager.kt +27 -0
  5. package/android/src/main/java/inc/apex/capacitor/TrackStamps.kt +26 -0
  6. package/dist/deferred-link.d.ts +73 -0
  7. package/dist/deferred-link.d.ts.map +1 -0
  8. package/dist/deferred-link.js +71 -0
  9. package/dist/deferred-link.js.map +1 -0
  10. package/dist/definitions.d.ts +94 -18
  11. package/dist/definitions.d.ts.map +1 -1
  12. package/dist/esm/deferred-link.d.ts +73 -0
  13. package/dist/esm/deferred-link.d.ts.map +1 -0
  14. package/dist/esm/deferred-link.js +68 -0
  15. package/dist/esm/deferred-link.js.map +1 -0
  16. package/dist/esm/definitions.d.ts +94 -18
  17. package/dist/esm/definitions.d.ts.map +1 -1
  18. package/dist/esm/events.d.ts +8 -1
  19. package/dist/esm/events.d.ts.map +1 -1
  20. package/dist/esm/events.js +8 -1
  21. package/dist/esm/events.js.map +1 -1
  22. package/dist/esm/index.d.ts +9 -0
  23. package/dist/esm/index.d.ts.map +1 -1
  24. package/dist/esm/index.js +157 -1
  25. package/dist/esm/index.js.map +1 -1
  26. package/dist/esm/referrer-link.d.ts +36 -0
  27. package/dist/esm/referrer-link.d.ts.map +1 -0
  28. package/dist/esm/referrer-link.js +103 -0
  29. package/dist/esm/referrer-link.js.map +1 -0
  30. package/dist/esm/skan-conversion-values.d.ts +175 -0
  31. package/dist/esm/skan-conversion-values.d.ts.map +1 -0
  32. package/dist/esm/skan-conversion-values.js +323 -0
  33. package/dist/esm/skan-conversion-values.js.map +1 -0
  34. package/dist/esm/web.d.ts +6 -1
  35. package/dist/esm/web.d.ts.map +1 -1
  36. package/dist/esm/web.js +27 -3
  37. package/dist/esm/web.js.map +1 -1
  38. package/dist/events.d.ts +8 -1
  39. package/dist/events.d.ts.map +1 -1
  40. package/dist/events.js +8 -1
  41. package/dist/events.js.map +1 -1
  42. package/dist/index.d.ts +9 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +166 -1
  45. package/dist/index.js.map +1 -1
  46. package/dist/referrer-link.d.ts +36 -0
  47. package/dist/referrer-link.d.ts.map +1 -0
  48. package/dist/referrer-link.js +107 -0
  49. package/dist/referrer-link.js.map +1 -0
  50. package/dist/skan-conversion-values.d.ts +175 -0
  51. package/dist/skan-conversion-values.d.ts.map +1 -0
  52. package/dist/skan-conversion-values.js +331 -0
  53. package/dist/skan-conversion-values.js.map +1 -0
  54. package/dist/web.d.ts +6 -1
  55. package/dist/web.d.ts.map +1 -1
  56. package/dist/web.js +27 -3
  57. package/dist/web.js.map +1 -1
  58. package/ios/Sources/ApexCapacitorPlugin/ApexEvents.swift +11 -4
  59. package/ios/Sources/ApexCapacitorPlugin/DeepLinkManager.swift +16 -2
  60. package/ios/Sources/ApexCapacitorPlugin/SkanManager.swift +18 -4
  61. package/ios/Sources/ApexCapacitorPluginBridge/ApexCapacitorPlugin.swift +25 -6
  62. package/ios/Tests/ApexCapacitorPluginTests/DeepLinkManagerTests.swift +26 -0
  63. package/ios/Tests/ApexCapacitorPluginTests/SkanManagerTests.swift +22 -1
  64. package/package.json +5 -2
  65. package/ios/Package.swift +0 -34
@@ -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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"}
@@ -0,0 +1,323 @@
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
+ const DEFAULT_TIMEOUT_MS = 5000;
32
+ export async function fetchSkanConversionSchema(options, now = Date.now) {
33
+ const { apiUrl, workspaceKey, mobileAppId } = options;
34
+ if (!workspaceKey)
35
+ return { status: "error" };
36
+ const doFetch = options.fetch ?? globalThis.fetch;
37
+ if (typeof doFetch !== "function")
38
+ return { status: "error" };
39
+ const controller = new AbortController();
40
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
41
+ try {
42
+ const url = new URL(`${apiUrl.replace(/\/+$/, "")}/api/mobile/skan/schema/resolve`);
43
+ url.searchParams.set("workspace", workspaceKey);
44
+ if (mobileAppId)
45
+ url.searchParams.set("mobileAppId", mobileAppId);
46
+ const headers = {
47
+ "x-apex-workspace": workspaceKey,
48
+ };
49
+ if (options.etag)
50
+ headers["If-None-Match"] = options.etag;
51
+ const response = await doFetch(url.toString(), {
52
+ method: "GET",
53
+ headers,
54
+ signal: controller.signal,
55
+ });
56
+ if (response.status === 304)
57
+ return { status: "not_modified" };
58
+ if (!response.ok)
59
+ return { status: "error" };
60
+ const payload = (await response.json());
61
+ return {
62
+ status: "fresh",
63
+ snapshot: {
64
+ schema: normalizeSchema(payload.data?.schema),
65
+ mobileAppId: payload.data?.mobileAppId ?? null,
66
+ etag: response.headers.get("etag"),
67
+ fetchedAt: now(),
68
+ },
69
+ };
70
+ }
71
+ catch {
72
+ return { status: "error" };
73
+ }
74
+ finally {
75
+ clearTimeout(timer);
76
+ }
77
+ }
78
+ /**
79
+ * Keep only rungs the plugin can act on. The server validates on write,
80
+ * but a cached snapshot may outlive a schema change, so be strict here
81
+ * too: a bad rung is dropped, never sent to Apple.
82
+ */
83
+ export function normalizeSchema(raw) {
84
+ if (!raw || !Array.isArray(raw.windows))
85
+ return null;
86
+ const windows = [];
87
+ for (const w of raw.windows) {
88
+ if (!w || (w.window !== 1 && w.window !== 2 && w.window !== 3))
89
+ continue;
90
+ if (!Array.isArray(w.values))
91
+ continue;
92
+ const values = w.values.filter((v) => !!v &&
93
+ typeof v.eventName === "string" &&
94
+ v.eventName.length > 0 &&
95
+ Number.isInteger(v.fineValue) &&
96
+ v.fineValue >= 0 &&
97
+ v.fineValue <= 63 &&
98
+ (v.coarseValue === "low" ||
99
+ v.coarseValue === "medium" ||
100
+ v.coarseValue === "high"));
101
+ windows.push({ window: w.window, values });
102
+ }
103
+ return {
104
+ windows,
105
+ updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : "",
106
+ };
107
+ }
108
+ // ─── Window arithmetic ────────────────────────────────────────────────────
109
+ const DAY_MS = 24 * 60 * 60 * 1000;
110
+ /**
111
+ * Which SKAN 4 postback window a moment falls in, counted from first
112
+ * launch. Null once Apple has stopped listening (after day 35).
113
+ */
114
+ export function skanWindowAt(installedAt, now) {
115
+ const elapsed = now - installedAt;
116
+ if (elapsed < 0)
117
+ return 1;
118
+ if (elapsed < 2 * DAY_MS)
119
+ return 1;
120
+ if (elapsed < 7 * DAY_MS)
121
+ return 2;
122
+ if (elapsed < 35 * DAY_MS)
123
+ return 3;
124
+ return null;
125
+ }
126
+ /**
127
+ * Decide whether tracking `eventName` should change Apple's value.
128
+ * Returns null when the event has no meaning in the current window or
129
+ * when the mapped value would not move the ladder up.
130
+ */
131
+ export function pickConversionValue(schema, eventName, ctx) {
132
+ if (!schema)
133
+ return null;
134
+ const window = skanWindowAt(ctx.installedAt, ctx.now);
135
+ if (!window)
136
+ return null;
137
+ const entry = schema.windows
138
+ .find((w) => w.window === window)
139
+ ?.values.find((v) => v.eventName === eventName);
140
+ if (!entry)
141
+ return null;
142
+ if (ctx.highestSent !== null && entry.fineValue <= ctx.highestSent) {
143
+ return null;
144
+ }
145
+ return {
146
+ fineValue: entry.fineValue,
147
+ coarseValue: entry.coarseValue,
148
+ ...(entry.lockWindow ? { lockWindow: true } : {}),
149
+ window,
150
+ };
151
+ }
152
+ export const SKAN_STORAGE_KEYS = {
153
+ snapshot: "apex_skan_schema_v1",
154
+ installedAt: "apex_skan_installed_at",
155
+ manual: "apex_skan_manual",
156
+ highest: "apex_skan_highest_v1",
157
+ };
158
+ /** How long a cached schema is trusted before revalidating. */
159
+ export const SKAN_SCHEMA_MAX_AGE_MS = DAY_MS;
160
+ /**
161
+ * Owns the cached schema and the "what have I already told Apple"
162
+ * bookkeeping for one install. Created by the plugin proxy on
163
+ * `initialize({ autoConversionValues: true })`; not something an app
164
+ * constructs itself.
165
+ */
166
+ export class SkanAutoConversionValues {
167
+ constructor(options) {
168
+ this.snapshot = null;
169
+ this.manual = false;
170
+ this.highest = {};
171
+ this.ready = Promise.resolve();
172
+ this.endpoint = null;
173
+ this.update = options.update;
174
+ this.store = options.store === undefined ? defaultStore() : options.store;
175
+ this.fetchImpl = options.fetch;
176
+ this.now = options.now ?? Date.now;
177
+ this.log = options.log;
178
+ this.installedAt = this.readInstalledAt();
179
+ this.manual = this.store?.getItem(SKAN_STORAGE_KEYS.manual) === "1";
180
+ this.highest = readJson(this.store, SKAN_STORAGE_KEYS.highest) ?? {};
181
+ this.snapshot = readJson(this.store, SKAN_STORAGE_KEYS.snapshot);
182
+ }
183
+ /**
184
+ * Point at a workspace and refresh the schema if the cache is stale.
185
+ * Resolves as soon as the cache has been read; the network revalidate
186
+ * runs in the background and never blocks the caller.
187
+ */
188
+ initialize(endpoint) {
189
+ this.endpoint = {
190
+ apiUrl: endpoint.apiUrl,
191
+ workspaceKey: endpoint.workspaceKey,
192
+ mobileAppId: endpoint.mobileAppId ?? null,
193
+ };
194
+ const stale = !this.snapshot ||
195
+ this.now() - this.snapshot.fetchedAt >= SKAN_SCHEMA_MAX_AGE_MS;
196
+ if (stale) {
197
+ this.ready = this.revalidate();
198
+ }
199
+ return Promise.resolve();
200
+ }
201
+ /** Force a network revalidation (ETag-aware). */
202
+ async revalidate() {
203
+ if (!this.endpoint)
204
+ return;
205
+ const result = await fetchSkanConversionSchema({
206
+ ...this.endpoint,
207
+ etag: this.snapshot?.etag ?? null,
208
+ fetch: this.fetchImpl,
209
+ }, this.now);
210
+ if (result.status === "fresh") {
211
+ this.snapshot = result.snapshot;
212
+ writeJson(this.store, SKAN_STORAGE_KEYS.snapshot, this.snapshot);
213
+ this.log?.(`skan schema refreshed (${countRungs(this.snapshot.schema)} rungs)`);
214
+ }
215
+ else if (result.status === "not_modified" && this.snapshot) {
216
+ this.snapshot = { ...this.snapshot, fetchedAt: this.now() };
217
+ writeJson(this.store, SKAN_STORAGE_KEYS.snapshot, this.snapshot);
218
+ this.log?.("skan schema unchanged");
219
+ }
220
+ else if (result.status === "error") {
221
+ this.log?.("skan schema fetch failed; keeping cached copy");
222
+ }
223
+ }
224
+ /** Wait for any in-flight revalidation (tests and debug UIs). */
225
+ whenReady() {
226
+ return this.ready;
227
+ }
228
+ /** The schema the plugin is acting on right now. */
229
+ getSchema() {
230
+ return (this.snapshot ?? {
231
+ schema: null,
232
+ mobileAppId: this.endpoint?.mobileAppId ?? null,
233
+ etag: null,
234
+ fetchedAt: 0,
235
+ });
236
+ }
237
+ /** True once the app has called `updateConversionValue` itself. */
238
+ get manualMode() {
239
+ return this.manual;
240
+ }
241
+ /** Called when the app updates Apple's value by hand. Auto steps aside. */
242
+ noteManualUpdate() {
243
+ if (this.manual)
244
+ return;
245
+ this.manual = true;
246
+ this.store?.setItem(SKAN_STORAGE_KEYS.manual, "1");
247
+ this.log?.("skan manual updateConversionValue seen; auto values off");
248
+ }
249
+ /**
250
+ * Called after every `track`. Sends the mapped value when the event has
251
+ * one in the current window and it moves the ladder up.
252
+ */
253
+ async onTrack(eventName) {
254
+ if (this.manual)
255
+ return null;
256
+ const now = this.now();
257
+ const window = skanWindowAt(this.installedAt, now);
258
+ if (!window)
259
+ return null;
260
+ const pick = pickConversionValue(this.snapshot?.schema ?? null, eventName, {
261
+ installedAt: this.installedAt,
262
+ now,
263
+ highestSent: this.highest[window] ?? null,
264
+ });
265
+ if (!pick)
266
+ return null;
267
+ try {
268
+ await this.update({
269
+ fineValue: pick.fineValue,
270
+ coarseValue: pick.coarseValue,
271
+ ...(pick.lockWindow ? { lockWindow: true } : {}),
272
+ });
273
+ }
274
+ catch (err) {
275
+ this.log?.(`skan updateConversionValue failed for ${eventName}: ${String(err)}`);
276
+ return null;
277
+ }
278
+ this.highest[window] = pick.fineValue;
279
+ writeJson(this.store, SKAN_STORAGE_KEYS.highest, this.highest);
280
+ this.log?.(`skan updatePostbackConversionValue(${pick.fineValue}, ${pick.coarseValue}) for ${eventName} in window ${window}`);
281
+ return pick;
282
+ }
283
+ readInstalledAt() {
284
+ const raw = this.store?.getItem(SKAN_STORAGE_KEYS.installedAt);
285
+ const parsed = raw ? Number(raw) : NaN;
286
+ if (Number.isFinite(parsed) && parsed > 0)
287
+ return parsed;
288
+ const now = this.now();
289
+ this.store?.setItem(SKAN_STORAGE_KEYS.installedAt, String(now));
290
+ return now;
291
+ }
292
+ }
293
+ // ─── Storage helpers ──────────────────────────────────────────────────────
294
+ function defaultStore() {
295
+ try {
296
+ const ls = globalThis.localStorage;
297
+ return ls ? { getItem: (k) => ls.getItem(k), setItem: (k, v) => ls.setItem(k, v) } : null;
298
+ }
299
+ catch {
300
+ return null;
301
+ }
302
+ }
303
+ function readJson(store, key) {
304
+ try {
305
+ const raw = store?.getItem(key);
306
+ return raw ? JSON.parse(raw) : null;
307
+ }
308
+ catch {
309
+ return null;
310
+ }
311
+ }
312
+ function writeJson(store, key, value) {
313
+ try {
314
+ store?.setItem(key, JSON.stringify(value));
315
+ }
316
+ catch {
317
+ // Private mode / quota. Worst case we refetch tomorrow's schema early.
318
+ }
319
+ }
320
+ function countRungs(schema) {
321
+ return schema?.windows.reduce((n, w) => n + w.values.length, 0) ?? 0;
322
+ }
323
+ //# sourceMappingURL=skan-conversion-values.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skan-conversion-values.js","sourceRoot":"","sources":["../../src/skan-conversion-values.ts"],"names":[],"mappings":"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"}
package/dist/esm/web.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * in separate files so unit tests can cover them without DOM mocking.
12
12
  */
13
13
  import { WebPlugin } from "@capacitor/core";
14
- import type { AdvertisingIdResult, ApexCapacitorPlugin, ApexEvent, AttStatus, DeviceInfo, FlushResult, IdentifyOptions, InitializeOptions, PushRegistrationResult, QueueStatus, SessionInfo, SkanCoarseValue, VariantAssignmentResult } from "./definitions";
14
+ import type { AdvertisingIdResult, ApexCapacitorPlugin, ApexEvent, AttStatus, DeferredDeepLinkResult, DeviceInfo, FlushResult, IdentifyOptions, InitializeOptions, PushRegistrationResult, QueueStatus, SessionInfo, SkanCoarseValue, SkanSchemaSnapshot, VariantAssignmentResult } from "./definitions";
15
15
  export declare class ApexCapacitorWeb extends WebPlugin implements ApexCapacitorPlugin {
16
16
  private queue?;
17
17
  private session?;
@@ -54,6 +54,7 @@ export declare class ApexCapacitorWeb extends WebPlugin implements ApexCapacitor
54
54
  getAdvertisingId(): Promise<AdvertisingIdResult>;
55
55
  getInstallReferrer(): Promise<{
56
56
  referrer: string | null;
57
+ clickTimestamp: number;
57
58
  }>;
58
59
  getVisitorId(): Promise<{
59
60
  visitorId: string;
@@ -80,10 +81,14 @@ export declare class ApexCapacitorWeb extends WebPlugin implements ApexCapacitor
80
81
  updateConversionValue(_options: {
81
82
  fineValue: number;
82
83
  coarseValue?: SkanCoarseValue;
84
+ lockWindow?: boolean;
83
85
  }): Promise<void>;
86
+ getConversionValueSchema(): Promise<SkanSchemaSnapshot>;
84
87
  getInitialDeepLink(): Promise<{
85
88
  url: string | null;
89
+ deepLinkHint: string | null;
86
90
  }>;
91
+ getDeferredDeepLink(): Promise<DeferredDeepLinkResult | null>;
87
92
  getDeviceInfo(): Promise<DeviceInfo>;
88
93
  startSession(): Promise<{
89
94
  sessionId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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"}
package/dist/esm/web.js CHANGED
@@ -212,7 +212,7 @@ export class ApexCapacitorWeb extends WebPlugin {
212
212
  return { id: null, fallback: null };
213
213
  }
214
214
  async getInstallReferrer() {
215
- return { referrer: null };
215
+ return { referrer: null, clickTimestamp: 0 };
216
216
  }
217
217
  async getVisitorId() {
218
218
  if (!this.visitorId) {
@@ -268,6 +268,7 @@ export class ApexCapacitorWeb extends WebPlugin {
268
268
  workspaceKey: this.workspaceKey,
269
269
  ...(options.userId ? { userId: options.userId } : {}),
270
270
  ...(options.traits ? { metadata: options.traits } : {}),
271
+ ...(options.userHash ? { userHash: options.userHash } : {}),
271
272
  timestamp: new Date().toISOString(),
272
273
  };
273
274
  const res = await fetch(url, {
@@ -283,11 +284,34 @@ export class ApexCapacitorWeb extends WebPlugin {
283
284
  async updateConversionValue(_options) {
284
285
  // No-op on web / non-iOS.
285
286
  }
287
+ async getConversionValueSchema() {
288
+ // The proxy in `index.ts` answers this from its cache on iOS. Reached
289
+ // directly only when the web fallback is used as a bare class, where
290
+ // Apple is not listening anyway.
291
+ return { schema: null, mobileAppId: null, etag: null, fetchedAt: 0 };
292
+ }
286
293
  async getInitialDeepLink() {
287
294
  if (typeof window !== "undefined" && window.location) {
288
- return { url: window.location.href };
295
+ const href = window.location.href;
296
+ let deepLinkHint = null;
297
+ try {
298
+ deepLinkHint = new URL(href).searchParams.get("apex_dl");
299
+ }
300
+ catch {
301
+ // Non-parseable location (rare, e.g. an about: page) — no hint.
302
+ }
303
+ return { url: href, deepLinkHint };
289
304
  }
290
- return { url: null };
305
+ return { url: null, deepLinkHint: null };
306
+ }
307
+ async getDeferredDeepLink() {
308
+ // Deferred deep linking exists to bridge a gap that only exists on
309
+ // native: browser tap → app store → fresh app with no shared state.
310
+ // In a browser there is no gap — the URL the user tapped is the URL
311
+ // the page is already on, and `getInitialDeepLink()` returns it.
312
+ // Claiming a deferred link here would also burn the one-shot match
313
+ // that the real app install needs.
314
+ return null;
291
315
  }
292
316
  async getDeviceInfo() {
293
317
  // Best-effort on web — mostly placeholders so callers get consistent shape.