@apex-inc/capacitor-plugin 0.1.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.
Files changed (83) hide show
  1. package/ApexCapacitorPlugin.podspec +17 -0
  2. package/LICENSE +17 -0
  3. package/README.md +136 -0
  4. package/android/build.gradle +68 -0
  5. package/android/src/main/AndroidManifest.xml +8 -0
  6. package/android/src/main/java/inc/apex/capacitor/ApexCapacitorPlugin.kt +325 -0
  7. package/android/src/main/java/inc/apex/capacitor/DeepLinkManager.kt +47 -0
  8. package/android/src/main/java/inc/apex/capacitor/InstallReferrerParser.kt +123 -0
  9. package/android/src/main/java/inc/apex/capacitor/OfflineQueue.kt +150 -0
  10. package/android/src/main/java/inc/apex/capacitor/SessionManager.kt +108 -0
  11. package/dist/batch-sender.d.ts +60 -0
  12. package/dist/batch-sender.d.ts.map +1 -0
  13. package/dist/batch-sender.js +115 -0
  14. package/dist/batch-sender.js.map +1 -0
  15. package/dist/definitions.d.ts +224 -0
  16. package/dist/definitions.d.ts.map +1 -0
  17. package/dist/definitions.js +14 -0
  18. package/dist/definitions.js.map +1 -0
  19. package/dist/esm/batch-sender.d.ts +60 -0
  20. package/dist/esm/batch-sender.d.ts.map +1 -0
  21. package/dist/esm/batch-sender.js +111 -0
  22. package/dist/esm/batch-sender.js.map +1 -0
  23. package/dist/esm/definitions.d.ts +224 -0
  24. package/dist/esm/definitions.d.ts.map +1 -0
  25. package/dist/esm/definitions.js +13 -0
  26. package/dist/esm/definitions.js.map +1 -0
  27. package/dist/esm/event-id.d.ts +17 -0
  28. package/dist/esm/event-id.d.ts.map +1 -0
  29. package/dist/esm/event-id.js +57 -0
  30. package/dist/esm/event-id.js.map +1 -0
  31. package/dist/esm/index.d.ts +29 -0
  32. package/dist/esm/index.d.ts.map +1 -0
  33. package/dist/esm/index.js +30 -0
  34. package/dist/esm/index.js.map +1 -0
  35. package/dist/esm/offline-queue.d.ts +111 -0
  36. package/dist/esm/offline-queue.d.ts.map +1 -0
  37. package/dist/esm/offline-queue.js +240 -0
  38. package/dist/esm/offline-queue.js.map +1 -0
  39. package/dist/esm/session-manager.d.ts +63 -0
  40. package/dist/esm/session-manager.d.ts.map +1 -0
  41. package/dist/esm/session-manager.js +100 -0
  42. package/dist/esm/session-manager.js.map +1 -0
  43. package/dist/esm/web.d.ts +65 -0
  44. package/dist/esm/web.d.ts.map +1 -0
  45. package/dist/esm/web.js +203 -0
  46. package/dist/esm/web.js.map +1 -0
  47. package/dist/event-id.d.ts +17 -0
  48. package/dist/event-id.d.ts.map +1 -0
  49. package/dist/event-id.js +61 -0
  50. package/dist/event-id.js.map +1 -0
  51. package/dist/index.d.ts +29 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +76 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/offline-queue.d.ts +111 -0
  56. package/dist/offline-queue.d.ts.map +1 -0
  57. package/dist/offline-queue.js +246 -0
  58. package/dist/offline-queue.js.map +1 -0
  59. package/dist/session-manager.d.ts +63 -0
  60. package/dist/session-manager.d.ts.map +1 -0
  61. package/dist/session-manager.js +104 -0
  62. package/dist/session-manager.js.map +1 -0
  63. package/dist/web.d.ts +65 -0
  64. package/dist/web.d.ts.map +1 -0
  65. package/dist/web.js +207 -0
  66. package/dist/web.js.map +1 -0
  67. package/ios/Package.swift +34 -0
  68. package/ios/Sources/ApexCapacitorPlugin/AdvertisingIdProvider.swift +66 -0
  69. package/ios/Sources/ApexCapacitorPlugin/AttManager.swift +82 -0
  70. package/ios/Sources/ApexCapacitorPlugin/DeepLinkManager.swift +64 -0
  71. package/ios/Sources/ApexCapacitorPlugin/DeviceInfo.swift +107 -0
  72. package/ios/Sources/ApexCapacitorPlugin/OfflineQueue.swift +191 -0
  73. package/ios/Sources/ApexCapacitorPlugin/SessionManager.swift +113 -0
  74. package/ios/Sources/ApexCapacitorPlugin/SkanManager.swift +95 -0
  75. package/ios/Sources/ApexCapacitorPluginBridge/ApexCapacitorPlugin.swift +269 -0
  76. package/ios/Tests/ApexCapacitorPluginTests/AdvertisingIdProviderTests.swift +74 -0
  77. package/ios/Tests/ApexCapacitorPluginTests/AttManagerTests.swift +82 -0
  78. package/ios/Tests/ApexCapacitorPluginTests/DeepLinkManagerTests.swift +69 -0
  79. package/ios/Tests/ApexCapacitorPluginTests/DeviceInfoTests.swift +52 -0
  80. package/ios/Tests/ApexCapacitorPluginTests/OfflineQueueTests.swift +134 -0
  81. package/ios/Tests/ApexCapacitorPluginTests/SessionManagerTests.swift +98 -0
  82. package/ios/Tests/ApexCapacitorPluginTests/SkanManagerTests.swift +91 -0
  83. package/package.json +82 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Offline event queue with durable storage and FIFO eviction.
3
+ *
4
+ * Every event tracked through the plugin gets enqueued here first. A batch
5
+ * sender drains the queue in the background (`batch-sender.ts`). When the
6
+ * user is offline (flights, subways, dead zones), events accumulate safely
7
+ * up to `maxSize`; the oldest events are dropped rather than crashing the
8
+ * app or silently failing.
9
+ *
10
+ * Pluggable `Storage` interface supports three implementations:
11
+ * - `InMemoryStorage` — used in tests and as the default for SSR/Node
12
+ * - `IndexedDBStorage` — used in browser + Capacitor WebView contexts
13
+ * - `NativeBridgeStorage` — used when Capacitor native plugin is active
14
+ * (persists through the native OfflineQueue on iOS Core Data / Android Room)
15
+ *
16
+ * This keeps the queue logic pure (no DOM/Native API dependencies in the
17
+ * algorithmic core), which makes testing trivial.
18
+ */
19
+ import type { ApexEvent } from "./definitions";
20
+ /** A single queued event ready for delivery to the server. */
21
+ export interface QueuedEvent {
22
+ event: ApexEvent;
23
+ /** Attempt count so the batch sender can apply exponential backoff. */
24
+ attempts: number;
25
+ /** ISO timestamp when enqueued. Used for queue-age metrics + eviction. */
26
+ enqueuedAt: string;
27
+ }
28
+ /**
29
+ * Storage backend for the offline queue. Implementations persist events
30
+ * across app restarts and may be synchronous (in-memory) or asynchronous
31
+ * (IndexedDB, native bridge).
32
+ */
33
+ export interface OfflineQueueStorage {
34
+ /** Read all queued events in insertion order. */
35
+ readAll(): Promise<QueuedEvent[]>;
36
+ /** Append one event to the queue. */
37
+ append(event: QueuedEvent): Promise<void>;
38
+ /** Replace the entire queue contents (used for flush + eviction). */
39
+ replaceAll(events: QueuedEvent[]): Promise<void>;
40
+ /** Remove all events matching the given event IDs. */
41
+ removeByIds(eventIds: string[]): Promise<void>;
42
+ /** Number of events currently persisted. */
43
+ size(): Promise<number>;
44
+ /** Best-effort clear — used on test mode toggle + uninstall cleanup. */
45
+ clear(): Promise<void>;
46
+ }
47
+ /** Options for {@link OfflineQueue}. */
48
+ export interface OfflineQueueOptions {
49
+ storage: OfflineQueueStorage;
50
+ /** Maximum events to hold before FIFO eviction. Default 1000. */
51
+ maxSize?: number;
52
+ /** Called when an event is evicted due to queue overflow. */
53
+ onEvicted?: (event: QueuedEvent) => void;
54
+ }
55
+ export declare class OfflineQueue {
56
+ private readonly storage;
57
+ private readonly maxSize;
58
+ private readonly onEvicted?;
59
+ constructor(options: OfflineQueueOptions);
60
+ /**
61
+ * Enqueue an event. If the queue is at `maxSize`, the oldest event is
62
+ * evicted first (FIFO) so the new event can be recorded.
63
+ */
64
+ enqueue(event: ApexEvent): Promise<void>;
65
+ /** Enqueue many events in a single storage write. */
66
+ enqueueMany(events: ApexEvent[]): Promise<void>;
67
+ /** Read the next batch of events without removing them from the queue. */
68
+ peek(batchSize: number): Promise<QueuedEvent[]>;
69
+ /** Mark events as successfully delivered and remove them from the queue. */
70
+ markSent(eventIds: string[]): Promise<void>;
71
+ /** Increment attempt count for events that failed to send (for backoff). */
72
+ markFailed(eventIds: string[]): Promise<void>;
73
+ /** Current queue size. */
74
+ size(): Promise<number>;
75
+ /** ISO timestamp of the oldest queued event, or null if queue is empty. */
76
+ oldestEventAt(): Promise<string | null>;
77
+ /** Empty the queue. Typically called when toggling test mode. */
78
+ clear(): Promise<void>;
79
+ }
80
+ /**
81
+ * In-memory storage. Used in tests and as a fallback where neither IndexedDB
82
+ * nor the native bridge is available (e.g. SSR contexts).
83
+ */
84
+ export declare class InMemoryStorage implements OfflineQueueStorage {
85
+ private events;
86
+ readAll(): Promise<QueuedEvent[]>;
87
+ append(event: QueuedEvent): Promise<void>;
88
+ replaceAll(events: QueuedEvent[]): Promise<void>;
89
+ removeByIds(eventIds: string[]): Promise<void>;
90
+ size(): Promise<number>;
91
+ clear(): Promise<void>;
92
+ }
93
+ /**
94
+ * IndexedDB-backed storage for browser + Capacitor WebView contexts.
95
+ * Persists events across app restarts using a single object store keyed by
96
+ * event ID.
97
+ */
98
+ export declare class IndexedDBStorage implements OfflineQueueStorage {
99
+ private readonly dbName;
100
+ private readonly storeName;
101
+ private dbPromise?;
102
+ constructor(dbName?: string, storeName?: string);
103
+ private openDb;
104
+ readAll(): Promise<QueuedEvent[]>;
105
+ append(event: QueuedEvent): Promise<void>;
106
+ replaceAll(events: QueuedEvent[]): Promise<void>;
107
+ removeByIds(eventIds: string[]): Promise<void>;
108
+ size(): Promise<number>;
109
+ clear(): Promise<void>;
110
+ }
111
+ //# sourceMappingURL=offline-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-queue.d.ts","sourceRoot":"","sources":["../src/offline-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ /**
3
+ * Offline event queue with durable storage and FIFO eviction.
4
+ *
5
+ * Every event tracked through the plugin gets enqueued here first. A batch
6
+ * sender drains the queue in the background (`batch-sender.ts`). When the
7
+ * user is offline (flights, subways, dead zones), events accumulate safely
8
+ * up to `maxSize`; the oldest events are dropped rather than crashing the
9
+ * app or silently failing.
10
+ *
11
+ * Pluggable `Storage` interface supports three implementations:
12
+ * - `InMemoryStorage` — used in tests and as the default for SSR/Node
13
+ * - `IndexedDBStorage` — used in browser + Capacitor WebView contexts
14
+ * - `NativeBridgeStorage` — used when Capacitor native plugin is active
15
+ * (persists through the native OfflineQueue on iOS Core Data / Android Room)
16
+ *
17
+ * This keeps the queue logic pure (no DOM/Native API dependencies in the
18
+ * algorithmic core), which makes testing trivial.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.IndexedDBStorage = exports.InMemoryStorage = exports.OfflineQueue = void 0;
22
+ class OfflineQueue {
23
+ constructor(options) {
24
+ this.storage = options.storage;
25
+ this.maxSize = options.maxSize ?? 1000;
26
+ this.onEvicted = options.onEvicted;
27
+ if (this.maxSize < 1) {
28
+ throw new Error(`OfflineQueue maxSize must be >= 1, got ${this.maxSize}`);
29
+ }
30
+ }
31
+ /**
32
+ * Enqueue an event. If the queue is at `maxSize`, the oldest event is
33
+ * evicted first (FIFO) so the new event can be recorded.
34
+ */
35
+ async enqueue(event) {
36
+ if (!event.id) {
37
+ throw new Error("OfflineQueue.enqueue: event.id is required (set via event-id.generateEventId)");
38
+ }
39
+ const existing = await this.storage.readAll();
40
+ const queued = {
41
+ event,
42
+ attempts: 0,
43
+ enqueuedAt: new Date().toISOString(),
44
+ };
45
+ const next = [...existing, queued];
46
+ if (next.length > this.maxSize) {
47
+ const overflow = next.splice(0, next.length - this.maxSize);
48
+ if (this.onEvicted) {
49
+ for (const ev of overflow) {
50
+ this.onEvicted(ev);
51
+ }
52
+ }
53
+ }
54
+ await this.storage.replaceAll(next);
55
+ }
56
+ /** Enqueue many events in a single storage write. */
57
+ async enqueueMany(events) {
58
+ if (events.length === 0)
59
+ return;
60
+ const existing = await this.storage.readAll();
61
+ const now = new Date().toISOString();
62
+ const queued = events.map((event) => {
63
+ if (!event.id) {
64
+ throw new Error("OfflineQueue.enqueueMany: every event must have an id");
65
+ }
66
+ return { event, attempts: 0, enqueuedAt: now };
67
+ });
68
+ const next = [...existing, ...queued];
69
+ if (next.length > this.maxSize) {
70
+ const overflow = next.splice(0, next.length - this.maxSize);
71
+ if (this.onEvicted) {
72
+ for (const ev of overflow) {
73
+ this.onEvicted(ev);
74
+ }
75
+ }
76
+ }
77
+ await this.storage.replaceAll(next);
78
+ }
79
+ /** Read the next batch of events without removing them from the queue. */
80
+ async peek(batchSize) {
81
+ if (batchSize < 1)
82
+ return [];
83
+ const all = await this.storage.readAll();
84
+ return all.slice(0, batchSize);
85
+ }
86
+ /** Mark events as successfully delivered and remove them from the queue. */
87
+ async markSent(eventIds) {
88
+ if (eventIds.length === 0)
89
+ return;
90
+ await this.storage.removeByIds(eventIds);
91
+ }
92
+ /** Increment attempt count for events that failed to send (for backoff). */
93
+ async markFailed(eventIds) {
94
+ if (eventIds.length === 0)
95
+ return;
96
+ const idSet = new Set(eventIds);
97
+ const all = await this.storage.readAll();
98
+ const next = all.map((q) => q.event.id && idSet.has(q.event.id) ? { ...q, attempts: q.attempts + 1 } : q);
99
+ await this.storage.replaceAll(next);
100
+ }
101
+ /** Current queue size. */
102
+ async size() {
103
+ return this.storage.size();
104
+ }
105
+ /** ISO timestamp of the oldest queued event, or null if queue is empty. */
106
+ async oldestEventAt() {
107
+ const all = await this.storage.readAll();
108
+ return all.length > 0 ? all[0].enqueuedAt : null;
109
+ }
110
+ /** Empty the queue. Typically called when toggling test mode. */
111
+ async clear() {
112
+ await this.storage.clear();
113
+ }
114
+ }
115
+ exports.OfflineQueue = OfflineQueue;
116
+ // ── Storage implementations ────────────────────────────────────────────────
117
+ /**
118
+ * In-memory storage. Used in tests and as a fallback where neither IndexedDB
119
+ * nor the native bridge is available (e.g. SSR contexts).
120
+ */
121
+ class InMemoryStorage {
122
+ constructor() {
123
+ this.events = [];
124
+ }
125
+ async readAll() {
126
+ return [...this.events];
127
+ }
128
+ async append(event) {
129
+ this.events.push(event);
130
+ }
131
+ async replaceAll(events) {
132
+ this.events = [...events];
133
+ }
134
+ async removeByIds(eventIds) {
135
+ const idSet = new Set(eventIds);
136
+ this.events = this.events.filter((q) => !q.event.id || !idSet.has(q.event.id));
137
+ }
138
+ async size() {
139
+ return this.events.length;
140
+ }
141
+ async clear() {
142
+ this.events = [];
143
+ }
144
+ }
145
+ exports.InMemoryStorage = InMemoryStorage;
146
+ /**
147
+ * IndexedDB-backed storage for browser + Capacitor WebView contexts.
148
+ * Persists events across app restarts using a single object store keyed by
149
+ * event ID.
150
+ */
151
+ class IndexedDBStorage {
152
+ constructor(dbName = "apex-capacitor-queue", storeName = "events") {
153
+ this.dbName = dbName;
154
+ this.storeName = storeName;
155
+ }
156
+ async openDb() {
157
+ if (this.dbPromise)
158
+ return this.dbPromise;
159
+ const db = await new Promise((resolve, reject) => {
160
+ const idb = globalThis.indexedDB;
161
+ if (!idb) {
162
+ reject(new Error("IndexedDB not available in this environment"));
163
+ return;
164
+ }
165
+ const req = idb.open(this.dbName, 1);
166
+ req.onupgradeneeded = () => {
167
+ const database = req.result;
168
+ if (!database.objectStoreNames.contains(this.storeName)) {
169
+ database.createObjectStore(this.storeName, { keyPath: "key", autoIncrement: true });
170
+ }
171
+ };
172
+ req.onsuccess = () => resolve(req.result);
173
+ req.onerror = () => reject(req.error);
174
+ });
175
+ this.dbPromise = Promise.resolve(db);
176
+ return db;
177
+ }
178
+ async readAll() {
179
+ const db = await this.openDb();
180
+ return new Promise((resolve, reject) => {
181
+ const tx = db.transaction(this.storeName, "readonly");
182
+ const store = tx.objectStore(this.storeName);
183
+ const req = store.getAll();
184
+ req.onsuccess = () => {
185
+ const rows = req.result ?? [];
186
+ // Strip internal autoIncrement key for caller.
187
+ resolve(rows
188
+ .sort((a, b) => (a.key ?? 0) - (b.key ?? 0))
189
+ .map(({ key: _k, ...rest }) => rest));
190
+ };
191
+ req.onerror = () => reject(req.error);
192
+ });
193
+ }
194
+ async append(event) {
195
+ const db = await this.openDb();
196
+ await new Promise((resolve, reject) => {
197
+ const tx = db.transaction(this.storeName, "readwrite");
198
+ const store = tx.objectStore(this.storeName);
199
+ store.add(event);
200
+ tx.oncomplete = () => resolve();
201
+ tx.onerror = () => reject(tx.error);
202
+ });
203
+ }
204
+ async replaceAll(events) {
205
+ const db = await this.openDb();
206
+ await new Promise((resolve, reject) => {
207
+ const tx = db.transaction(this.storeName, "readwrite");
208
+ const store = tx.objectStore(this.storeName);
209
+ store.clear();
210
+ for (const ev of events) {
211
+ store.add(ev);
212
+ }
213
+ tx.oncomplete = () => resolve();
214
+ tx.onerror = () => reject(tx.error);
215
+ });
216
+ }
217
+ async removeByIds(eventIds) {
218
+ if (eventIds.length === 0)
219
+ return;
220
+ const idSet = new Set(eventIds);
221
+ const all = await this.readAll();
222
+ const kept = all.filter((q) => !q.event.id || !idSet.has(q.event.id));
223
+ await this.replaceAll(kept);
224
+ }
225
+ async size() {
226
+ const db = await this.openDb();
227
+ return new Promise((resolve, reject) => {
228
+ const tx = db.transaction(this.storeName, "readonly");
229
+ const store = tx.objectStore(this.storeName);
230
+ const req = store.count();
231
+ req.onsuccess = () => resolve(req.result ?? 0);
232
+ req.onerror = () => reject(req.error);
233
+ });
234
+ }
235
+ async clear() {
236
+ const db = await this.openDb();
237
+ await new Promise((resolve, reject) => {
238
+ const tx = db.transaction(this.storeName, "readwrite");
239
+ tx.objectStore(this.storeName).clear();
240
+ tx.oncomplete = () => resolve();
241
+ tx.onerror = () => reject(tx.error);
242
+ });
243
+ }
244
+ }
245
+ exports.IndexedDBStorage = IndexedDBStorage;
246
+ //# sourceMappingURL=offline-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-queue.js","sourceRoot":"","sources":["../src/offline-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Mobile session manager.
3
+ *
4
+ * Defines the boundaries of a user's interaction with the app. A session
5
+ * starts on the first event after inactivity and ends when either
6
+ * (a) no events arrive for `timeoutMinutes` (default 30), or
7
+ * (b) the app is force-backgrounded and the OS reclaims memory.
8
+ *
9
+ * Emits `session_start` and `session_end` events that plug directly into the
10
+ * server's MSESSION# rollup (Phase 1c). The timeout is configurable and the
11
+ * clock is injectable for deterministic testing.
12
+ */
13
+ export interface SessionSnapshot {
14
+ sessionId: string;
15
+ startedAt: string;
16
+ lastActivityAt: string;
17
+ eventCount: number;
18
+ endedAt?: string;
19
+ durationSeconds?: number;
20
+ }
21
+ export interface SessionManagerOptions {
22
+ /** Inactivity window after which a session ends. Default 30 minutes. */
23
+ timeoutMinutes?: number;
24
+ /** Called when a new session starts (emits `session_start` server-side). */
25
+ onSessionStart?: (session: SessionSnapshot) => void;
26
+ /** Called when a session ends (emits `session_end` server-side). */
27
+ onSessionEnd?: (session: SessionSnapshot) => void;
28
+ /** Injectable clock for deterministic testing. Defaults to `Date.now()`. */
29
+ now?: () => number;
30
+ /** Injectable `setTimeout` / `clearTimeout` for deterministic testing. */
31
+ scheduler?: {
32
+ setTimeout: (callback: () => void, ms: number) => number;
33
+ clearTimeout: (handle: number) => void;
34
+ };
35
+ }
36
+ export declare class SessionManager {
37
+ private readonly timeoutMs;
38
+ private readonly onSessionStart?;
39
+ private readonly onSessionEnd?;
40
+ private readonly now;
41
+ private readonly setTimeoutFn;
42
+ private readonly clearTimeoutFn;
43
+ private current;
44
+ private timeoutHandle;
45
+ constructor(options?: SessionManagerOptions);
46
+ /**
47
+ * Records user activity. Starts a new session if none is active, otherwise
48
+ * resets the inactivity timeout. Returns the current session snapshot.
49
+ */
50
+ recordActivity(): SessionSnapshot;
51
+ /**
52
+ * Explicitly ends the current session immediately. Fires `onSessionEnd`.
53
+ * No-op if no session is active.
54
+ */
55
+ endSession(): void;
56
+ /** Returns the current session snapshot, or null. */
57
+ getCurrent(): SessionSnapshot | null;
58
+ /** Force-starts a new session, ending any current one. */
59
+ forceStart(): SessionSnapshot;
60
+ private scheduleTimeout;
61
+ private clearTimeout;
62
+ }
63
+ //# sourceMappingURL=session-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,oEAAoE;IACpE,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAClD,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,0EAA0E;IAC1E,SAAS,CAAC,EAAE;QACV,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;QACzD,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;KACxC,CAAC;CACH;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAqC;IACrE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAqC;IACnE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyC;IACtE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2B;IAC1D,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,aAAa,CAAuB;gBAEhC,OAAO,GAAE,qBAA0B;IAgB/C;;;OAGG;IACH,cAAc,IAAI,eAAe;IAwBjC;;;OAGG;IACH,UAAU,IAAI,IAAI;IAkBlB,qDAAqD;IACrD,UAAU,IAAI,eAAe,GAAG,IAAI;IAIpC,0DAA0D;IAC1D,UAAU,IAAI,eAAe;IAK7B,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,YAAY;CAMrB"}
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ /**
3
+ * Mobile session manager.
4
+ *
5
+ * Defines the boundaries of a user's interaction with the app. A session
6
+ * starts on the first event after inactivity and ends when either
7
+ * (a) no events arrive for `timeoutMinutes` (default 30), or
8
+ * (b) the app is force-backgrounded and the OS reclaims memory.
9
+ *
10
+ * Emits `session_start` and `session_end` events that plug directly into the
11
+ * server's MSESSION# rollup (Phase 1c). The timeout is configurable and the
12
+ * clock is injectable for deterministic testing.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SessionManager = void 0;
16
+ const event_id_1 = require("./event-id");
17
+ class SessionManager {
18
+ constructor(options = {}) {
19
+ this.current = null;
20
+ this.timeoutHandle = null;
21
+ this.timeoutMs = (options.timeoutMinutes ?? 30) * 60 * 1000;
22
+ this.onSessionStart = options.onSessionStart;
23
+ this.onSessionEnd = options.onSessionEnd;
24
+ this.now = options.now ?? (() => Date.now());
25
+ const defaultScheduler = {
26
+ setTimeout: (cb, ms) => setTimeout(cb, ms),
27
+ clearTimeout: (handle) => clearTimeout(handle),
28
+ };
29
+ const scheduler = options.scheduler ?? defaultScheduler;
30
+ this.setTimeoutFn = scheduler.setTimeout;
31
+ this.clearTimeoutFn = scheduler.clearTimeout;
32
+ }
33
+ /**
34
+ * Records user activity. Starts a new session if none is active, otherwise
35
+ * resets the inactivity timeout. Returns the current session snapshot.
36
+ */
37
+ recordActivity() {
38
+ const nowMs = this.now();
39
+ const isoNow = new Date(nowMs).toISOString();
40
+ if (!this.current) {
41
+ this.current = {
42
+ sessionId: (0, event_id_1.generateEventId)(),
43
+ startedAt: isoNow,
44
+ lastActivityAt: isoNow,
45
+ eventCount: 1,
46
+ };
47
+ if (this.onSessionStart)
48
+ this.onSessionStart({ ...this.current });
49
+ }
50
+ else {
51
+ this.current = {
52
+ ...this.current,
53
+ lastActivityAt: isoNow,
54
+ eventCount: this.current.eventCount + 1,
55
+ };
56
+ }
57
+ this.scheduleTimeout();
58
+ return { ...this.current };
59
+ }
60
+ /**
61
+ * Explicitly ends the current session immediately. Fires `onSessionEnd`.
62
+ * No-op if no session is active.
63
+ */
64
+ endSession() {
65
+ if (!this.current)
66
+ return;
67
+ this.clearTimeout();
68
+ const nowMs = this.now();
69
+ const startedMs = Date.parse(this.current.startedAt);
70
+ const durationSeconds = Math.max(0, Math.floor((nowMs - startedMs) / 1000));
71
+ const ended = {
72
+ ...this.current,
73
+ endedAt: new Date(nowMs).toISOString(),
74
+ durationSeconds,
75
+ };
76
+ if (this.onSessionEnd)
77
+ this.onSessionEnd(ended);
78
+ this.current = null;
79
+ }
80
+ /** Returns the current session snapshot, or null. */
81
+ getCurrent() {
82
+ return this.current ? { ...this.current } : null;
83
+ }
84
+ /** Force-starts a new session, ending any current one. */
85
+ forceStart() {
86
+ if (this.current)
87
+ this.endSession();
88
+ return this.recordActivity();
89
+ }
90
+ scheduleTimeout() {
91
+ this.clearTimeout();
92
+ this.timeoutHandle = this.setTimeoutFn(() => {
93
+ this.endSession();
94
+ }, this.timeoutMs);
95
+ }
96
+ clearTimeout() {
97
+ if (this.timeoutHandle !== null) {
98
+ this.clearTimeoutFn(this.timeoutHandle);
99
+ this.timeoutHandle = null;
100
+ }
101
+ }
102
+ }
103
+ exports.SessionManager = SessionManager;
104
+ //# sourceMappingURL=session-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"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"}
package/dist/web.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Web fallback implementation of `ApexCapacitorPlugin`.
3
+ *
4
+ * Runs when the app is in a browser or Capacitor PWA context where no native
5
+ * iOS/Android bridge is present. Provides best-effort behavior for every
6
+ * method — identifiers return null, ATT is a no-op (returns `authorized`),
7
+ * SKAN is a no-op, deep links come from `window.location`, and events are
8
+ * batched + sent over fetch via the normal offline queue.
9
+ *
10
+ * This module deliberately keeps the heavy lifting (queue, session, batch)
11
+ * in separate files so unit tests can cover them without DOM mocking.
12
+ */
13
+ import { WebPlugin } from "@capacitor/core";
14
+ import type { AdvertisingIdResult, ApexCapacitorPlugin, ApexEvent, AttStatus, DeviceInfo, FlushResult, InitializeOptions, QueueStatus, SessionInfo, SkanCoarseValue } from "./definitions";
15
+ export declare class ApexCapacitorWeb extends WebPlugin implements ApexCapacitorPlugin {
16
+ private queue?;
17
+ private session?;
18
+ private sender?;
19
+ private visitorId?;
20
+ private testMode;
21
+ private debug;
22
+ private initialized;
23
+ initialize(options: InitializeOptions): Promise<void>;
24
+ requestTrackingAuthorization(): Promise<{
25
+ status: AttStatus;
26
+ }>;
27
+ getTrackingStatus(): Promise<{
28
+ status: AttStatus;
29
+ }>;
30
+ getAdvertisingId(): Promise<AdvertisingIdResult>;
31
+ getInstallReferrer(): Promise<{
32
+ referrer: string | null;
33
+ }>;
34
+ getVisitorId(): Promise<{
35
+ visitorId: string;
36
+ }>;
37
+ setVisitorId(options: {
38
+ visitorId: string;
39
+ }): Promise<void>;
40
+ updateConversionValue(_options: {
41
+ fineValue: number;
42
+ coarseValue?: SkanCoarseValue;
43
+ }): Promise<void>;
44
+ getInitialDeepLink(): Promise<{
45
+ url: string | null;
46
+ }>;
47
+ getDeviceInfo(): Promise<DeviceInfo>;
48
+ startSession(): Promise<{
49
+ sessionId: string;
50
+ }>;
51
+ endSession(): Promise<void>;
52
+ getCurrentSession(): Promise<SessionInfo>;
53
+ track(event: ApexEvent): Promise<void>;
54
+ getQueueSize(): Promise<QueueStatus>;
55
+ flushQueue(): Promise<FlushResult>;
56
+ setTestMode(options: {
57
+ enabled: boolean;
58
+ }): Promise<void>;
59
+ removeAllListeners(): Promise<void>;
60
+ private emitSession;
61
+ private loadOrCreateVisitorId;
62
+ private saveVisitorId;
63
+ private ensureInitialized;
64
+ }
65
+ //# sourceMappingURL=web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACnB,SAAS,EACT,SAAS,EACT,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,WAAW,EACX,eAAe,EAChB,MAAM,eAAe,CAAC;AAQvB,qBAAa,gBAAiB,SAAQ,SAAU,YAAW,mBAAmB;IAC5E,OAAO,CAAC,KAAK,CAAC,CAAe;IAC7B,OAAO,CAAC,OAAO,CAAC,CAAiB;IACjC,OAAO,CAAC,MAAM,CAAC,CAAc;IAC7B,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,WAAW,CAAS;IAEtB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiCrD,4BAA4B,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,SAAS,CAAA;KAAE,CAAC;IAI9D,iBAAiB,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,SAAS,CAAA;KAAE,CAAC;IAInD,gBAAgB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAIhD,kBAAkB,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAI1D,YAAY,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAO9C,YAAY,CAAC,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3D,qBAAqB,CAAC,QAAQ,EAAE;QACpC,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,eAAe,CAAC;KAC/B,GAAG,OAAO,CAAC,IAAI,CAAC;IAIX,kBAAkB,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAOrD,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAiBpC,YAAY,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAM9C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,iBAAiB,IAAI,OAAO,CAAC,WAAW,CAAC;IAQzC,KAAK,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BtC,YAAY,IAAI,OAAO,CAAC,WAAW,CAAC;IASpC,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC;IAMlC,WAAW,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzC,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,iBAAiB;CAK1B"}