@axyle/expo-sdk 1.0.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/CONFIG.md +117 -0
- package/README.md +534 -0
- package/dist/client.d.ts +93 -0
- package/dist/client.js +416 -0
- package/dist/config.example.d.ts +8 -0
- package/dist/config.example.js +13 -0
- package/dist/configLoader.d.ts +10 -0
- package/dist/configLoader.js +22 -0
- package/dist/constants.d.ts +38 -0
- package/dist/constants.js +42 -0
- package/dist/context.d.ts +36 -0
- package/dist/context.js +216 -0
- package/dist/core.d.ts +9 -0
- package/dist/core.js +45 -0
- package/dist/hooks/useFeatureTracking.d.ts +34 -0
- package/dist/hooks/useFeatureTracking.js +60 -0
- package/dist/hooks/useOnboardingTracking.d.ts +85 -0
- package/dist/hooks/useOnboardingTracking.js +172 -0
- package/dist/hooks/useScreenTracking.d.ts +23 -0
- package/dist/hooks/useScreenTracking.js +52 -0
- package/dist/hooks/useScrollTracking.d.ts +39 -0
- package/dist/hooks/useScrollTracking.js +146 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.js +171 -0
- package/dist/integrations/expoRouter.d.ts +42 -0
- package/dist/integrations/expoRouter.js +66 -0
- package/dist/integrations/reactNavigation.d.ts +27 -0
- package/dist/integrations/reactNavigation.js +101 -0
- package/dist/queue.d.ts +52 -0
- package/dist/queue.js +143 -0
- package/dist/session.d.ts +44 -0
- package/dist/session.js +139 -0
- package/dist/sessionAnalytics.d.ts +43 -0
- package/dist/sessionAnalytics.js +74 -0
- package/dist/storage.d.ts +67 -0
- package/dist/storage.js +210 -0
- package/dist/transport.d.ts +41 -0
- package/dist/transport.js +158 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.js +5 -0
- package/dist/utils.d.ts +39 -0
- package/dist/utils.js +116 -0
- package/package.json +44 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Axyle client
|
|
3
|
+
* Public API surface for the SDK
|
|
4
|
+
*/
|
|
5
|
+
import { SessionAnalyticsStats } from "./sessionAnalytics";
|
|
6
|
+
import { AnalyticsEvent, AxyleInitConfig, EventProperties, UserTraits } from "./types";
|
|
7
|
+
export declare class AxyleClient {
|
|
8
|
+
private config;
|
|
9
|
+
private logger;
|
|
10
|
+
private sessionManager;
|
|
11
|
+
private sessionAnalytics;
|
|
12
|
+
private transport;
|
|
13
|
+
private queue;
|
|
14
|
+
private anonymousId;
|
|
15
|
+
private userId;
|
|
16
|
+
private _isInitialized;
|
|
17
|
+
private isOptedOut;
|
|
18
|
+
private appStateSubscription;
|
|
19
|
+
/**
|
|
20
|
+
* Check if SDK is initialized (public getter)
|
|
21
|
+
*/
|
|
22
|
+
get isInitialized(): boolean;
|
|
23
|
+
constructor();
|
|
24
|
+
/**
|
|
25
|
+
* Initialize the SDK
|
|
26
|
+
* Only accepts API key - all other settings use defaults
|
|
27
|
+
*/
|
|
28
|
+
init(config: AxyleInitConfig): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Track a custom event
|
|
31
|
+
*/
|
|
32
|
+
track(eventName: string, properties?: EventProperties): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Identify a user
|
|
35
|
+
*/
|
|
36
|
+
identify(userId: string, traits?: UserTraits): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Reset user data (logout)
|
|
39
|
+
*/
|
|
40
|
+
reset(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Opt out of tracking
|
|
43
|
+
*/
|
|
44
|
+
optOut(): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Opt back in to tracking
|
|
47
|
+
*/
|
|
48
|
+
optIn(): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Delete user data
|
|
51
|
+
*/
|
|
52
|
+
deleteUser(userId: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Get session analytics statistics
|
|
55
|
+
*/
|
|
56
|
+
getSessionStats(): SessionAnalyticsStats | null;
|
|
57
|
+
/**
|
|
58
|
+
* Get all events in current session
|
|
59
|
+
*/
|
|
60
|
+
getSessionEvents(): AnalyticsEvent[];
|
|
61
|
+
/**
|
|
62
|
+
* Get events by type in current session
|
|
63
|
+
*/
|
|
64
|
+
getEventsByType(eventName: string): AnalyticsEvent[];
|
|
65
|
+
/**
|
|
66
|
+
* Create an event object
|
|
67
|
+
*/
|
|
68
|
+
private createEvent;
|
|
69
|
+
/**
|
|
70
|
+
* Track an auto event
|
|
71
|
+
*/
|
|
72
|
+
private trackAutoEvent;
|
|
73
|
+
/**
|
|
74
|
+
* Handle session start
|
|
75
|
+
*/
|
|
76
|
+
private handleSessionStart;
|
|
77
|
+
/**
|
|
78
|
+
* Handle session end
|
|
79
|
+
*/
|
|
80
|
+
private handleSessionEnd;
|
|
81
|
+
/**
|
|
82
|
+
* Set up app state listener for background/foreground tracking
|
|
83
|
+
*/
|
|
84
|
+
private setupAppStateListener;
|
|
85
|
+
/**
|
|
86
|
+
* Manually flush all queued events to the server
|
|
87
|
+
*/
|
|
88
|
+
flush(): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Cleanup resources
|
|
91
|
+
*/
|
|
92
|
+
shutdown(): Promise<void>;
|
|
93
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Main Axyle client
|
|
4
|
+
* Public API surface for the SDK
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.AxyleClient = void 0;
|
|
8
|
+
const react_native_1 = require("react-native");
|
|
9
|
+
const constants_1 = require("./constants");
|
|
10
|
+
const context_1 = require("./context");
|
|
11
|
+
const queue_1 = require("./queue");
|
|
12
|
+
const session_1 = require("./session");
|
|
13
|
+
const sessionAnalytics_1 = require("./sessionAnalytics");
|
|
14
|
+
const storage_1 = require("./storage");
|
|
15
|
+
const transport_1 = require("./transport");
|
|
16
|
+
const utils_1 = require("./utils");
|
|
17
|
+
class AxyleClient {
|
|
18
|
+
/**
|
|
19
|
+
* Check if SDK is initialized (public getter)
|
|
20
|
+
*/
|
|
21
|
+
get isInitialized() {
|
|
22
|
+
return this._isInitialized;
|
|
23
|
+
}
|
|
24
|
+
constructor() {
|
|
25
|
+
this.sessionAnalytics = null;
|
|
26
|
+
this.transport = null;
|
|
27
|
+
this.queue = null;
|
|
28
|
+
this.anonymousId = "";
|
|
29
|
+
this.userId = null;
|
|
30
|
+
this._isInitialized = false;
|
|
31
|
+
this.isOptedOut = false;
|
|
32
|
+
this.appStateSubscription = null;
|
|
33
|
+
// Initialize with defaults, will be overridden in init()
|
|
34
|
+
this.config = {
|
|
35
|
+
apiKey: "",
|
|
36
|
+
environment: constants_1.DEFAULT_CONFIG.environment,
|
|
37
|
+
baseUrl: constants_1.DEFAULT_CONFIG.baseUrl,
|
|
38
|
+
debug: constants_1.DEFAULT_CONFIG.debug,
|
|
39
|
+
maxQueueSize: constants_1.DEFAULT_CONFIG.maxQueueSize,
|
|
40
|
+
flushInterval: constants_1.DEFAULT_CONFIG.flushInterval,
|
|
41
|
+
sessionTimeout: constants_1.DEFAULT_CONFIG.sessionTimeout,
|
|
42
|
+
userId: "",
|
|
43
|
+
};
|
|
44
|
+
this.logger = (0, utils_1.createLogger)(this.config.debug);
|
|
45
|
+
this.sessionManager = new session_1.SessionManager(this.config.sessionTimeout, this.logger, {
|
|
46
|
+
onSessionStart: (sessionId) => this.handleSessionStart(sessionId),
|
|
47
|
+
onSessionEnd: (sessionId, duration) => this.handleSessionEnd(sessionId, duration),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Initialize the SDK
|
|
52
|
+
* Only accepts API key - all other settings use defaults
|
|
53
|
+
*/
|
|
54
|
+
async init(config) {
|
|
55
|
+
try {
|
|
56
|
+
// Extract API key from string or object
|
|
57
|
+
const apiKey = typeof config === "string" ? config : config.apiKey;
|
|
58
|
+
if (!apiKey) {
|
|
59
|
+
this.logger.warn("API key is required for SDK initialization");
|
|
60
|
+
}
|
|
61
|
+
// Use defaults for all settings - only API key is configurable
|
|
62
|
+
this.config = {
|
|
63
|
+
apiKey: apiKey || "",
|
|
64
|
+
userId: "",
|
|
65
|
+
environment: constants_1.DEFAULT_CONFIG.environment,
|
|
66
|
+
debug: constants_1.DEFAULT_CONFIG.debug,
|
|
67
|
+
maxQueueSize: constants_1.DEFAULT_CONFIG.maxQueueSize,
|
|
68
|
+
flushInterval: constants_1.DEFAULT_CONFIG.flushInterval,
|
|
69
|
+
sessionTimeout: constants_1.DEFAULT_CONFIG.sessionTimeout,
|
|
70
|
+
baseUrl: constants_1.DEFAULT_CONFIG.baseUrl, // Always use production URL
|
|
71
|
+
};
|
|
72
|
+
// Update logger
|
|
73
|
+
this.logger = (0, utils_1.createLogger)(this.config.debug);
|
|
74
|
+
// Check opt-out status
|
|
75
|
+
this.isOptedOut = await storage_1.Storage.isOptedOut();
|
|
76
|
+
if (this.isOptedOut) {
|
|
77
|
+
this.logger.log("User has opted out, analytics disabled");
|
|
78
|
+
this._isInitialized = true; // Set initialized even if opted out
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Load anonymous ID
|
|
82
|
+
this.anonymousId = await storage_1.Storage.getAnonymousId();
|
|
83
|
+
// Load user ID from storage (not configurable at init)
|
|
84
|
+
this.userId = await storage_1.Storage.getUserId();
|
|
85
|
+
// Initialize session manager
|
|
86
|
+
this.sessionManager = new session_1.SessionManager(this.config.sessionTimeout, this.logger, {
|
|
87
|
+
onSessionStart: (sessionId) => this.handleSessionStart(sessionId),
|
|
88
|
+
onSessionEnd: (sessionId, duration) => this.handleSessionEnd(sessionId, duration),
|
|
89
|
+
});
|
|
90
|
+
await this.sessionManager.initialize();
|
|
91
|
+
// Initialize session analytics with current session start time
|
|
92
|
+
const sessionId = this.sessionManager.getSessionId();
|
|
93
|
+
if (sessionId) {
|
|
94
|
+
// Use current time as session start time
|
|
95
|
+
this.sessionAnalytics = new sessionAnalytics_1.SessionAnalytics(Date.now());
|
|
96
|
+
}
|
|
97
|
+
// Initialize transport and queue if API key is provided
|
|
98
|
+
if (this.config.apiKey) {
|
|
99
|
+
this.transport = new transport_1.Transport({
|
|
100
|
+
apiKey: this.config.apiKey,
|
|
101
|
+
baseUrl: this.config.baseUrl,
|
|
102
|
+
debug: this.config.debug,
|
|
103
|
+
}, this.logger);
|
|
104
|
+
this.queue = new queue_1.EventQueue({
|
|
105
|
+
maxQueueSize: this.config.maxQueueSize,
|
|
106
|
+
flushInterval: this.config.flushInterval,
|
|
107
|
+
}, this.transport, this.logger);
|
|
108
|
+
// Initialize queue (starts flush timer)
|
|
109
|
+
await this.queue.initialize();
|
|
110
|
+
this.logger.log("Event queue initialized");
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.logger.log("No API key provided, events will only be tracked locally");
|
|
114
|
+
}
|
|
115
|
+
// Set initialized BEFORE tracking auto events
|
|
116
|
+
this._isInitialized = true;
|
|
117
|
+
this.logger.log("Axyle initialized successfully");
|
|
118
|
+
// Track app opened (after setting initialized flag)
|
|
119
|
+
await this.trackAutoEvent(constants_1.AUTO_TRACKED_EVENTS.APP_OPENED);
|
|
120
|
+
// Set up app state listener
|
|
121
|
+
this.setupAppStateListener();
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
this.logger.error("Failed to initialize Axyle:", error);
|
|
125
|
+
this.logger.error("Error details:", error);
|
|
126
|
+
// Set initialized anyway to prevent infinite waiting
|
|
127
|
+
this._isInitialized = true;
|
|
128
|
+
// Fail silently - don't throw
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Track a custom event
|
|
133
|
+
*/
|
|
134
|
+
async track(eventName, properties = {}) {
|
|
135
|
+
try {
|
|
136
|
+
// Check if initialized and not opted out
|
|
137
|
+
if (!this._isInitialized) {
|
|
138
|
+
this.logger.warn("SDK not initialized, call init() first");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (this.isOptedOut) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Validate event name
|
|
145
|
+
if (!(0, utils_1.isValidEventName)(eventName)) {
|
|
146
|
+
this.logger.error("Invalid event name:", eventName);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Update session activity
|
|
150
|
+
await this.sessionManager.updateActivity();
|
|
151
|
+
// Create event
|
|
152
|
+
const event = await this.createEvent(eventName, properties);
|
|
153
|
+
// Validate event size
|
|
154
|
+
if ((0, utils_1.getObjectSize)(event) > constants_1.MAX_EVENT_SIZE) {
|
|
155
|
+
this.logger.error("Event size exceeds limit:", eventName);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Add to session analytics (local counting)
|
|
159
|
+
if (this.sessionAnalytics) {
|
|
160
|
+
this.sessionAnalytics.addEvent(event);
|
|
161
|
+
this.logger.log(`Event tracked locally: ${eventName}`);
|
|
162
|
+
}
|
|
163
|
+
// Queue event for sending to API (if queue is initialized)
|
|
164
|
+
if (this.queue) {
|
|
165
|
+
await this.queue.enqueue(event);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
this.logger.error("Failed to track event:", error);
|
|
170
|
+
// Fail silently
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Identify a user
|
|
175
|
+
*/
|
|
176
|
+
async identify(userId, traits = {}) {
|
|
177
|
+
try {
|
|
178
|
+
if (!this._isInitialized || this.isOptedOut) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Capture previous user ID before updating
|
|
182
|
+
const previousUserId = this.userId;
|
|
183
|
+
// Store new user ID
|
|
184
|
+
this.userId = userId;
|
|
185
|
+
await storage_1.Storage.setUserId(userId);
|
|
186
|
+
// Track identify event with traits
|
|
187
|
+
await this.track("User Identified", {
|
|
188
|
+
...traits,
|
|
189
|
+
previousUserId: previousUserId || this.anonymousId,
|
|
190
|
+
});
|
|
191
|
+
this.logger.log("User identified:", userId);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
this.logger.error("Failed to identify user:", error);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Reset user data (logout)
|
|
199
|
+
*/
|
|
200
|
+
async reset() {
|
|
201
|
+
try {
|
|
202
|
+
if (!this._isInitialized) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Clear user ID
|
|
206
|
+
this.userId = null;
|
|
207
|
+
await storage_1.Storage.clearUserId();
|
|
208
|
+
// Reset session
|
|
209
|
+
await this.sessionManager.reset();
|
|
210
|
+
this.logger.log("User data reset");
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
this.logger.error("Failed to reset:", error);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Opt out of tracking
|
|
218
|
+
*/
|
|
219
|
+
async optOut() {
|
|
220
|
+
try {
|
|
221
|
+
this.isOptedOut = true;
|
|
222
|
+
await storage_1.Storage.setOptOut(true);
|
|
223
|
+
// Clear session analytics
|
|
224
|
+
if (this.sessionAnalytics) {
|
|
225
|
+
this.sessionAnalytics.clear();
|
|
226
|
+
}
|
|
227
|
+
this.logger.log("User opted out");
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
this.logger.error("Failed to opt out:", error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Opt back in to tracking
|
|
235
|
+
*/
|
|
236
|
+
async optIn() {
|
|
237
|
+
try {
|
|
238
|
+
this.isOptedOut = false;
|
|
239
|
+
await storage_1.Storage.setOptOut(false);
|
|
240
|
+
// Reinitialize session if it was cleared during opt-out
|
|
241
|
+
if (!this.sessionManager || !this.sessionManager.getSessionId()) {
|
|
242
|
+
this.sessionManager = new session_1.SessionManager(this.config.sessionTimeout, this.logger, {
|
|
243
|
+
onSessionStart: (sessionId) => this.handleSessionStart(sessionId),
|
|
244
|
+
onSessionEnd: (sessionId, duration) => this.handleSessionEnd(sessionId, duration),
|
|
245
|
+
});
|
|
246
|
+
await this.sessionManager.initialize();
|
|
247
|
+
// Reinitialize session analytics
|
|
248
|
+
const sessionId = this.sessionManager.getSessionId();
|
|
249
|
+
if (sessionId) {
|
|
250
|
+
this.sessionAnalytics = new sessionAnalytics_1.SessionAnalytics(Date.now());
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
this.logger.log("User opted in");
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
this.logger.error("Failed to opt in:", error);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Delete user data
|
|
261
|
+
*/
|
|
262
|
+
async deleteUser(userId) {
|
|
263
|
+
try {
|
|
264
|
+
// Track deletion request
|
|
265
|
+
await this.track("User Deletion Requested", { userId });
|
|
266
|
+
// Clear session analytics
|
|
267
|
+
if (this.sessionAnalytics) {
|
|
268
|
+
this.sessionAnalytics.clear();
|
|
269
|
+
}
|
|
270
|
+
// Clear local data
|
|
271
|
+
await storage_1.Storage.clearAll();
|
|
272
|
+
this.logger.log("User deletion requested:", userId);
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
this.logger.error("Failed to delete user:", error);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Get session analytics statistics
|
|
280
|
+
*/
|
|
281
|
+
getSessionStats() {
|
|
282
|
+
if (!this.sessionAnalytics) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
return this.sessionAnalytics.getStats();
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Get all events in current session
|
|
289
|
+
*/
|
|
290
|
+
getSessionEvents() {
|
|
291
|
+
if (!this.sessionAnalytics) {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
return this.sessionAnalytics.getEvents();
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get events by type in current session
|
|
298
|
+
*/
|
|
299
|
+
getEventsByType(eventName) {
|
|
300
|
+
if (!this.sessionAnalytics) {
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
return this.sessionAnalytics.getEventsByType(eventName);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Create an event object
|
|
307
|
+
*/
|
|
308
|
+
async createEvent(name, properties) {
|
|
309
|
+
const context = await context_1.ContextCollector.getContext(this.config.environment);
|
|
310
|
+
// Get session ID, fallback to generating a temporary one if not available
|
|
311
|
+
let sessionId = this.sessionManager?.getSessionId();
|
|
312
|
+
if (!sessionId) {
|
|
313
|
+
sessionId = `temp_session_${Date.now()}`;
|
|
314
|
+
this.logger.warn("Session not initialized, using temporary session ID");
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
id: (0, utils_1.generateUUID)(),
|
|
318
|
+
name,
|
|
319
|
+
properties: (0, utils_1.sanitizeProperties)(properties),
|
|
320
|
+
timestamp: Date.now(),
|
|
321
|
+
userId: this.userId || this.anonymousId,
|
|
322
|
+
anonymousId: this.anonymousId,
|
|
323
|
+
sessionId,
|
|
324
|
+
context,
|
|
325
|
+
schemaVersion: constants_1.SCHEMA_VERSION,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Track an auto event
|
|
330
|
+
*/
|
|
331
|
+
async trackAutoEvent(eventName, properties = {}) {
|
|
332
|
+
await this.track(eventName, properties);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Handle session start
|
|
336
|
+
*/
|
|
337
|
+
handleSessionStart(sessionId) {
|
|
338
|
+
// Initialize or reset session analytics for new session
|
|
339
|
+
const sessionStartTime = Date.now();
|
|
340
|
+
if (this.sessionAnalytics) {
|
|
341
|
+
this.sessionAnalytics.resetSession(sessionStartTime);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
this.sessionAnalytics = new sessionAnalytics_1.SessionAnalytics(sessionStartTime);
|
|
345
|
+
}
|
|
346
|
+
this.trackAutoEvent(constants_1.AUTO_TRACKED_EVENTS.SESSION_STARTED, { sessionId });
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Handle session end
|
|
350
|
+
*/
|
|
351
|
+
handleSessionEnd(sessionId, duration) {
|
|
352
|
+
this.trackAutoEvent(constants_1.AUTO_TRACKED_EVENTS.SESSION_ENDED, {
|
|
353
|
+
sessionId,
|
|
354
|
+
duration,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Set up app state listener for background/foreground tracking
|
|
359
|
+
*/
|
|
360
|
+
setupAppStateListener() {
|
|
361
|
+
let previousState = react_native_1.AppState.currentState;
|
|
362
|
+
this.appStateSubscription = react_native_1.AppState.addEventListener("change", (nextState) => {
|
|
363
|
+
if (previousState === "active" &&
|
|
364
|
+
nextState.match(/inactive|background/)) {
|
|
365
|
+
// App went to background
|
|
366
|
+
this.trackAutoEvent(constants_1.AUTO_TRACKED_EVENTS.APP_BACKGROUNDED);
|
|
367
|
+
}
|
|
368
|
+
else if (previousState.match(/inactive|background/) &&
|
|
369
|
+
nextState === "active") {
|
|
370
|
+
// App came to foreground
|
|
371
|
+
this.trackAutoEvent(constants_1.AUTO_TRACKED_EVENTS.APP_FOREGROUNDED);
|
|
372
|
+
this.sessionManager.updateActivity(); // Update session
|
|
373
|
+
}
|
|
374
|
+
previousState = nextState;
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Manually flush all queued events to the server
|
|
379
|
+
*/
|
|
380
|
+
async flush() {
|
|
381
|
+
try {
|
|
382
|
+
if (!this._isInitialized) {
|
|
383
|
+
this.logger.warn("SDK not initialized, cannot flush");
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (this.queue) {
|
|
387
|
+
await this.queue.flush();
|
|
388
|
+
this.logger.log("Events flushed successfully");
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
this.logger.error("Failed to flush events:", error);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Cleanup resources
|
|
397
|
+
*/
|
|
398
|
+
async shutdown() {
|
|
399
|
+
try {
|
|
400
|
+
// Remove app state listener
|
|
401
|
+
if (this.appStateSubscription) {
|
|
402
|
+
this.appStateSubscription.remove();
|
|
403
|
+
}
|
|
404
|
+
// Shutdown queue (flushes remaining events)
|
|
405
|
+
if (this.queue) {
|
|
406
|
+
await this.queue.shutdown();
|
|
407
|
+
}
|
|
408
|
+
this._isInitialized = false;
|
|
409
|
+
this.logger.log("Axyle shut down");
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
this.logger.error("Error during shutdown:", error);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
exports.AxyleClient = AxyleClient;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Sample Configuration File (Deprecated)
|
|
4
|
+
*
|
|
5
|
+
* NOTE: This file is kept for reference but is no longer used.
|
|
6
|
+
* The SDK now only accepts an API key during initialization.
|
|
7
|
+
*
|
|
8
|
+
* Use Axyle.init('your-api-key') instead.
|
|
9
|
+
*/
|
|
10
|
+
// This file is deprecated - SDK now only accepts API key
|
|
11
|
+
// Example usage:
|
|
12
|
+
// import { Axyle } from '@axyle/expo-sdk';
|
|
13
|
+
// Axyle.init('your-api-key');
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Loader
|
|
3
|
+
*
|
|
4
|
+
* Handles API key validation for the SDK.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Validate API key format
|
|
8
|
+
* Adjust the regex pattern based on your platform's API key format
|
|
9
|
+
*/
|
|
10
|
+
export declare function isValidApiKeyFormat(apiKey: string): boolean;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration Loader
|
|
4
|
+
*
|
|
5
|
+
* Handles API key validation for the SDK.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.isValidApiKeyFormat = isValidApiKeyFormat;
|
|
9
|
+
/**
|
|
10
|
+
* Validate API key format
|
|
11
|
+
* Adjust the regex pattern based on your platform's API key format
|
|
12
|
+
*/
|
|
13
|
+
function isValidApiKeyFormat(apiKey) {
|
|
14
|
+
if (!apiKey || typeof apiKey !== "string") {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
// UUID format: 550e8400-e29b-41d4-a716-446655440000
|
|
18
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
19
|
+
// Alphanumeric with dashes/underscores: at least 32 characters
|
|
20
|
+
const alphanumericRegex = /^[a-zA-Z0-9_-]{32,}$/;
|
|
21
|
+
return uuidRegex.test(apiKey) || alphanumericRegex.test(apiKey);
|
|
22
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants used throughout the SDK
|
|
3
|
+
*/
|
|
4
|
+
export declare const STORAGE_PREFIX = "@axyle";
|
|
5
|
+
export declare const STORAGE_KEYS: {
|
|
6
|
+
EVENTS_QUEUE: string;
|
|
7
|
+
ANONYMOUS_ID: string;
|
|
8
|
+
USER_ID: string;
|
|
9
|
+
SESSION_DATA: string;
|
|
10
|
+
OPT_OUT: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const PRODUCTION_BASE_URL = "https://axyle-server-production.up.railway.app";
|
|
13
|
+
export declare const DEFAULT_CONFIG: {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
debug: boolean;
|
|
16
|
+
maxQueueSize: number;
|
|
17
|
+
flushInterval: number;
|
|
18
|
+
sessionTimeout: number;
|
|
19
|
+
environment: "prod";
|
|
20
|
+
};
|
|
21
|
+
export declare const SCHEMA_VERSION = "1.0.0";
|
|
22
|
+
export declare const MAX_EVENT_SIZE: number;
|
|
23
|
+
export declare const MAX_BATCH_SIZE: number;
|
|
24
|
+
export declare const MAX_EVENTS_PER_BATCH = 100;
|
|
25
|
+
export declare const RETRY_CONFIG: {
|
|
26
|
+
maxRetries: number;
|
|
27
|
+
initialDelay: number;
|
|
28
|
+
maxDelay: number;
|
|
29
|
+
backoffMultiplier: number;
|
|
30
|
+
};
|
|
31
|
+
export declare const AUTO_TRACKED_EVENTS: {
|
|
32
|
+
APP_OPENED: string;
|
|
33
|
+
APP_BACKGROUNDED: string;
|
|
34
|
+
APP_FOREGROUNDED: string;
|
|
35
|
+
SESSION_STARTED: string;
|
|
36
|
+
SESSION_ENDED: string;
|
|
37
|
+
SCREEN_VIEWED: string;
|
|
38
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Constants used throughout the SDK
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AUTO_TRACKED_EVENTS = exports.RETRY_CONFIG = exports.MAX_EVENTS_PER_BATCH = exports.MAX_BATCH_SIZE = exports.MAX_EVENT_SIZE = exports.SCHEMA_VERSION = exports.DEFAULT_CONFIG = exports.PRODUCTION_BASE_URL = exports.STORAGE_KEYS = exports.STORAGE_PREFIX = void 0;
|
|
7
|
+
exports.STORAGE_PREFIX = "@axyle";
|
|
8
|
+
exports.STORAGE_KEYS = {
|
|
9
|
+
EVENTS_QUEUE: `${exports.STORAGE_PREFIX}/events-queue`,
|
|
10
|
+
ANONYMOUS_ID: `${exports.STORAGE_PREFIX}/anonymous-id`,
|
|
11
|
+
USER_ID: `${exports.STORAGE_PREFIX}/user-id`,
|
|
12
|
+
SESSION_DATA: `${exports.STORAGE_PREFIX}/session-data`,
|
|
13
|
+
OPT_OUT: `${exports.STORAGE_PREFIX}/opt-out`,
|
|
14
|
+
};
|
|
15
|
+
// Production server URL - not configurable by users
|
|
16
|
+
exports.PRODUCTION_BASE_URL = "https://axyle-server-production.up.railway.app";
|
|
17
|
+
exports.DEFAULT_CONFIG = {
|
|
18
|
+
baseUrl: exports.PRODUCTION_BASE_URL,
|
|
19
|
+
debug: false,
|
|
20
|
+
maxQueueSize: 100,
|
|
21
|
+
flushInterval: 10000, // 10 seconds
|
|
22
|
+
sessionTimeout: 30 * 60 * 1000, // 30 minutes
|
|
23
|
+
environment: "prod",
|
|
24
|
+
};
|
|
25
|
+
exports.SCHEMA_VERSION = "1.0.0";
|
|
26
|
+
exports.MAX_EVENT_SIZE = 32 * 1024; // 32KB per event
|
|
27
|
+
exports.MAX_BATCH_SIZE = 500 * 1024; // 500KB per batch
|
|
28
|
+
exports.MAX_EVENTS_PER_BATCH = 100;
|
|
29
|
+
exports.RETRY_CONFIG = {
|
|
30
|
+
maxRetries: 3,
|
|
31
|
+
initialDelay: 1000, // 1 second
|
|
32
|
+
maxDelay: 32000, // 32 seconds
|
|
33
|
+
backoffMultiplier: 2,
|
|
34
|
+
};
|
|
35
|
+
exports.AUTO_TRACKED_EVENTS = {
|
|
36
|
+
APP_OPENED: "App Opened",
|
|
37
|
+
APP_BACKGROUNDED: "App Backgrounded",
|
|
38
|
+
APP_FOREGROUNDED: "App Foregrounded",
|
|
39
|
+
SESSION_STARTED: "Session Started",
|
|
40
|
+
SESSION_ENDED: "Session Ended",
|
|
41
|
+
SCREEN_VIEWED: "Screen Viewed",
|
|
42
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device and app context collection using Expo APIs
|
|
3
|
+
*/
|
|
4
|
+
import { EventContext } from "./types";
|
|
5
|
+
export declare class ContextCollector {
|
|
6
|
+
private static cachedContext;
|
|
7
|
+
/**
|
|
8
|
+
* Get device and app context
|
|
9
|
+
* Caches result for performance
|
|
10
|
+
*/
|
|
11
|
+
static getContext(environment: "dev" | "prod"): Promise<EventContext>;
|
|
12
|
+
/**
|
|
13
|
+
* Get device ID (may be null on some platforms)
|
|
14
|
+
*/
|
|
15
|
+
private static getDeviceId;
|
|
16
|
+
/**
|
|
17
|
+
* Determine device type
|
|
18
|
+
*/
|
|
19
|
+
private static getDeviceType;
|
|
20
|
+
/**
|
|
21
|
+
* Get device locale
|
|
22
|
+
*/
|
|
23
|
+
private static getLocale;
|
|
24
|
+
/**
|
|
25
|
+
* Get device timezone
|
|
26
|
+
*/
|
|
27
|
+
private static getTimezone;
|
|
28
|
+
/**
|
|
29
|
+
* Get minimal context as fallback
|
|
30
|
+
*/
|
|
31
|
+
private static getMinimalContext;
|
|
32
|
+
/**
|
|
33
|
+
* Clear cached context (useful for testing or when app info changes)
|
|
34
|
+
*/
|
|
35
|
+
static clearCache(): void;
|
|
36
|
+
}
|