datafast 1.0.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.
- package/README.md +140 -0
- package/dist/web/index.d.mts +432 -0
- package/dist/web/index.d.ts +432 -0
- package/dist/web/index.js +789 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/index.mjs +762 -0
- package/dist/web/index.mjs.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
// src/core/utils.ts
|
|
2
|
+
function generateVisitorId() {
|
|
3
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
4
|
+
const r = Math.random() * 16 | 0;
|
|
5
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
6
|
+
return v.toString(16);
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
function generateSessionId() {
|
|
10
|
+
return "sxxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
11
|
+
const r = Math.random() * 16 | 0;
|
|
12
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
13
|
+
return v.toString(16);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function isValidVisitorId(id) {
|
|
17
|
+
if (!id || typeof id !== "string" || id.length !== 36) return false;
|
|
18
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id);
|
|
19
|
+
}
|
|
20
|
+
function isValidSessionId(id) {
|
|
21
|
+
if (!id || typeof id !== "string" || id.length !== 37) return false;
|
|
22
|
+
return /^s[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id);
|
|
23
|
+
}
|
|
24
|
+
function buildScreenHref(platform, screenName) {
|
|
25
|
+
const normalizedName = screenName.replace(/^\/+/, "").trim();
|
|
26
|
+
return `datafast://${platform}/${normalizedName}`;
|
|
27
|
+
}
|
|
28
|
+
function buildUserAgent(sdkVersion, platform, osVersion, deviceModel) {
|
|
29
|
+
const os = platform === "ios" ? "iOS" : platform === "android" ? "Android" : "Web";
|
|
30
|
+
return `DataFast/${sdkVersion} (${os} ${osVersion}; ${deviceModel})`;
|
|
31
|
+
}
|
|
32
|
+
function isValidEventName(name) {
|
|
33
|
+
if (!name || typeof name !== "string") return false;
|
|
34
|
+
if (name.length === 0 || name.length > 64) return false;
|
|
35
|
+
return /^[a-z0-9_-]+$/.test(name.toLowerCase());
|
|
36
|
+
}
|
|
37
|
+
function sanitizeCustomProperties(properties) {
|
|
38
|
+
if (!properties || typeof properties !== "object" || Array.isArray(properties)) {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
const sanitized = {};
|
|
42
|
+
const entries = Object.entries(properties);
|
|
43
|
+
if (entries.length > 10) {
|
|
44
|
+
console.warn("[DataFast] Maximum 10 custom properties allowed");
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
for (const [key, value] of entries) {
|
|
48
|
+
if (key === "eventName") continue;
|
|
49
|
+
if (!key || typeof key !== "string" || key.length > 64) {
|
|
50
|
+
console.warn(`[DataFast] Invalid property name: ${key}`);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
if (!/^[a-z0-9_-]+$/i.test(key)) {
|
|
54
|
+
console.warn(`[DataFast] Property name must be alphanumeric with _ or -: ${key}`);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
let stringValue = value == null ? "" : String(value);
|
|
58
|
+
if (stringValue.length > 255) {
|
|
59
|
+
stringValue = stringValue.substring(0, 255);
|
|
60
|
+
}
|
|
61
|
+
stringValue = stringValue.replace(/[<>'"&]/g, "").replace(/javascript:/gi, "").replace(/on\w+=/gi, "").trim();
|
|
62
|
+
sanitized[key.toLowerCase()] = stringValue;
|
|
63
|
+
}
|
|
64
|
+
return sanitized;
|
|
65
|
+
}
|
|
66
|
+
function generateEventId() {
|
|
67
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
68
|
+
}
|
|
69
|
+
function isSessionExpired(sessionStartTime) {
|
|
70
|
+
const SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
71
|
+
return Date.now() - sessionStartTime > SESSION_TTL_MS;
|
|
72
|
+
}
|
|
73
|
+
function createLogger(debug) {
|
|
74
|
+
return {
|
|
75
|
+
log: (...args) => {
|
|
76
|
+
if (debug) console.log("[DataFast]", ...args);
|
|
77
|
+
},
|
|
78
|
+
warn: (...args) => {
|
|
79
|
+
if (debug) console.warn("[DataFast]", ...args);
|
|
80
|
+
},
|
|
81
|
+
error: (...args) => {
|
|
82
|
+
console.error("[DataFast]", ...args);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/core/queue.ts
|
|
88
|
+
var QUEUE_STORAGE_KEY = "datafast_event_queue";
|
|
89
|
+
var MAX_RETRIES = 3;
|
|
90
|
+
var EventQueue = class {
|
|
91
|
+
constructor(options) {
|
|
92
|
+
this.queue = [];
|
|
93
|
+
this.flushTimer = null;
|
|
94
|
+
this.isFlushing = false;
|
|
95
|
+
this.storage = options.storage;
|
|
96
|
+
this.network = options.network;
|
|
97
|
+
this.apiUrl = options.apiUrl;
|
|
98
|
+
this.userAgent = options.userAgent;
|
|
99
|
+
this.flushInterval = options.flushInterval ?? 3e4;
|
|
100
|
+
this.maxQueueSize = options.maxQueueSize ?? 20;
|
|
101
|
+
this.onFlushError = options.onFlushError;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Initialize the queue - load persisted events from storage
|
|
105
|
+
*/
|
|
106
|
+
async init() {
|
|
107
|
+
try {
|
|
108
|
+
const stored = await this.storage.getItem(QUEUE_STORAGE_KEY);
|
|
109
|
+
if (stored) {
|
|
110
|
+
const parsed = JSON.parse(stored);
|
|
111
|
+
if (Array.isArray(parsed)) {
|
|
112
|
+
this.queue = parsed;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
this.queue = [];
|
|
117
|
+
}
|
|
118
|
+
this.startFlushTimer();
|
|
119
|
+
if (this.network?.onConnectionChange) {
|
|
120
|
+
this.network.onConnectionChange((isConnected) => {
|
|
121
|
+
if (isConnected) {
|
|
122
|
+
this.flush();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Add an event to the queue
|
|
129
|
+
*/
|
|
130
|
+
async enqueue(payload) {
|
|
131
|
+
const event = {
|
|
132
|
+
id: generateEventId(),
|
|
133
|
+
payload,
|
|
134
|
+
timestamp: Date.now(),
|
|
135
|
+
retries: 0
|
|
136
|
+
};
|
|
137
|
+
this.queue.push(event);
|
|
138
|
+
await this.persistQueue();
|
|
139
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
140
|
+
this.flush();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Flush all queued events to the server
|
|
145
|
+
*/
|
|
146
|
+
async flush() {
|
|
147
|
+
if (this.isFlushing || this.queue.length === 0) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (this.network) {
|
|
151
|
+
const isConnected = await this.network.isConnected();
|
|
152
|
+
if (!isConnected) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
this.isFlushing = true;
|
|
157
|
+
const eventsToProcess = [...this.queue];
|
|
158
|
+
for (const event of eventsToProcess) {
|
|
159
|
+
try {
|
|
160
|
+
await this.sendEvent(event.payload);
|
|
161
|
+
this.queue = this.queue.filter((e) => e.id !== event.id);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
event.retries++;
|
|
164
|
+
if (event.retries >= MAX_RETRIES) {
|
|
165
|
+
this.queue = this.queue.filter((e) => e.id !== event.id);
|
|
166
|
+
if (this.onFlushError) {
|
|
167
|
+
this.onFlushError(error, event);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
await this.persistQueue();
|
|
173
|
+
this.isFlushing = false;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Send a single event to the API
|
|
177
|
+
*/
|
|
178
|
+
async sendEvent(payload) {
|
|
179
|
+
const response = await fetch(this.apiUrl, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: {
|
|
182
|
+
"Content-Type": "application/json",
|
|
183
|
+
"User-Agent": this.userAgent
|
|
184
|
+
},
|
|
185
|
+
body: JSON.stringify(payload)
|
|
186
|
+
});
|
|
187
|
+
if (!response.ok) {
|
|
188
|
+
throw new Error(`HTTP ${response.status}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Persist queue to storage
|
|
193
|
+
*/
|
|
194
|
+
async persistQueue() {
|
|
195
|
+
try {
|
|
196
|
+
await this.storage.setItem(QUEUE_STORAGE_KEY, JSON.stringify(this.queue));
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Start the periodic flush timer
|
|
202
|
+
*/
|
|
203
|
+
startFlushTimer() {
|
|
204
|
+
if (this.flushTimer) {
|
|
205
|
+
clearInterval(this.flushTimer);
|
|
206
|
+
}
|
|
207
|
+
this.flushTimer = setInterval(() => {
|
|
208
|
+
this.flush();
|
|
209
|
+
}, this.flushInterval);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Stop the flush timer and flush remaining events
|
|
213
|
+
*/
|
|
214
|
+
async shutdown() {
|
|
215
|
+
if (this.flushTimer) {
|
|
216
|
+
clearInterval(this.flushTimer);
|
|
217
|
+
this.flushTimer = null;
|
|
218
|
+
}
|
|
219
|
+
await this.flush();
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Get current queue size
|
|
223
|
+
*/
|
|
224
|
+
get size() {
|
|
225
|
+
return this.queue.length;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Clear all queued events
|
|
229
|
+
*/
|
|
230
|
+
async clear() {
|
|
231
|
+
this.queue = [];
|
|
232
|
+
await this.persistQueue();
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/core/client.ts
|
|
237
|
+
var SDK_VERSION = "1.0.0";
|
|
238
|
+
var DEFAULT_API_URL = "https://datafa.st/api/events";
|
|
239
|
+
var STORAGE_KEYS = {
|
|
240
|
+
VISITOR_ID: "datafast_visitor_id",
|
|
241
|
+
SESSION_ID: "datafast_session_id",
|
|
242
|
+
SESSION_START: "datafast_session_start",
|
|
243
|
+
IGNORE_TRACKING: "datafast_ignore"
|
|
244
|
+
};
|
|
245
|
+
var DataFastClient = class {
|
|
246
|
+
constructor() {
|
|
247
|
+
this.state = {
|
|
248
|
+
initialized: false,
|
|
249
|
+
visitorId: null,
|
|
250
|
+
sessionId: null,
|
|
251
|
+
sessionStartTime: null,
|
|
252
|
+
lastScreenName: null,
|
|
253
|
+
config: null,
|
|
254
|
+
deviceInfo: null
|
|
255
|
+
};
|
|
256
|
+
this.queue = null;
|
|
257
|
+
this.logger = createLogger(false);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Initialize the SDK with configuration
|
|
261
|
+
*/
|
|
262
|
+
async init(config) {
|
|
263
|
+
if (this.state.initialized) {
|
|
264
|
+
this.logger.warn("SDK already initialized");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (!config.appId) {
|
|
268
|
+
throw new Error("[DataFast] appId is required");
|
|
269
|
+
}
|
|
270
|
+
if (!config.domain) {
|
|
271
|
+
throw new Error("[DataFast] domain is required");
|
|
272
|
+
}
|
|
273
|
+
if (!config.storage) {
|
|
274
|
+
throw new Error("[DataFast] storage adapter is required");
|
|
275
|
+
}
|
|
276
|
+
if (!config.platform) {
|
|
277
|
+
throw new Error("[DataFast] platform is required");
|
|
278
|
+
}
|
|
279
|
+
this.state.config = config;
|
|
280
|
+
this.logger = createLogger(config.debug ?? false);
|
|
281
|
+
const ignoreTracking = await config.storage.getItem(STORAGE_KEYS.IGNORE_TRACKING);
|
|
282
|
+
if (ignoreTracking === "true") {
|
|
283
|
+
this.logger.log("Tracking disabled via user opt-out");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
await this.initVisitorId();
|
|
287
|
+
await this.initSessionId();
|
|
288
|
+
const apiUrl = config.apiUrl ?? DEFAULT_API_URL;
|
|
289
|
+
const userAgent = buildUserAgent(
|
|
290
|
+
SDK_VERSION,
|
|
291
|
+
config.platform,
|
|
292
|
+
config.appVersion ?? "1.0.0",
|
|
293
|
+
"Unknown"
|
|
294
|
+
);
|
|
295
|
+
this.queue = new EventQueue({
|
|
296
|
+
storage: config.storage,
|
|
297
|
+
network: config.network,
|
|
298
|
+
apiUrl,
|
|
299
|
+
userAgent,
|
|
300
|
+
flushInterval: config.flushInterval,
|
|
301
|
+
maxQueueSize: config.maxQueueSize,
|
|
302
|
+
onFlushError: (error, event) => {
|
|
303
|
+
this.logger.error("Failed to send event:", error, event.payload.type);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
await this.queue.init();
|
|
307
|
+
this.state.initialized = true;
|
|
308
|
+
this.logger.log("SDK initialized", { visitorId: this.state.visitorId, sessionId: this.state.sessionId });
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Track a screen view (equivalent to pageview)
|
|
312
|
+
*/
|
|
313
|
+
async trackScreen(screenName) {
|
|
314
|
+
if (!this.isReady()) return;
|
|
315
|
+
if (!screenName || typeof screenName !== "string") {
|
|
316
|
+
this.logger.warn("trackScreen requires a screen name");
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const config = this.state.config;
|
|
320
|
+
const referrer = this.state.lastScreenName ? buildScreenHref(config.platform, this.state.lastScreenName) : null;
|
|
321
|
+
const payload = this.buildBasePayload("pageview", screenName);
|
|
322
|
+
payload.referrer = referrer;
|
|
323
|
+
await this.queue.enqueue(payload);
|
|
324
|
+
this.state.lastScreenName = screenName;
|
|
325
|
+
this.logger.log("Screen tracked:", screenName);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Track a custom event
|
|
329
|
+
*/
|
|
330
|
+
async track(eventName, properties) {
|
|
331
|
+
if (!this.isReady()) return;
|
|
332
|
+
if (!isValidEventName(eventName)) {
|
|
333
|
+
this.logger.warn(
|
|
334
|
+
"Invalid event name. Use lowercase alphanumeric with _ or -, max 64 chars:",
|
|
335
|
+
eventName
|
|
336
|
+
);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const sanitized = sanitizeCustomProperties(properties ?? {});
|
|
340
|
+
if (sanitized === null) {
|
|
341
|
+
this.logger.warn("Custom properties validation failed");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const screenName = this.state.lastScreenName ?? "Unknown";
|
|
345
|
+
const payload = this.buildBasePayload("custom", screenName);
|
|
346
|
+
payload.extraData = {
|
|
347
|
+
eventName: eventName.toLowerCase(),
|
|
348
|
+
...sanitized
|
|
349
|
+
};
|
|
350
|
+
await this.queue.enqueue(payload);
|
|
351
|
+
this.logger.log("Event tracked:", eventName, sanitized);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Identify a user
|
|
355
|
+
*/
|
|
356
|
+
async identify(userId, properties) {
|
|
357
|
+
if (!this.isReady()) return;
|
|
358
|
+
if (!userId || typeof userId !== "string") {
|
|
359
|
+
this.logger.warn("identify requires a user_id");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const screenName = this.state.lastScreenName ?? "Unknown";
|
|
363
|
+
const payload = this.buildBasePayload("identify", screenName);
|
|
364
|
+
payload.extraData = {
|
|
365
|
+
user_id: userId,
|
|
366
|
+
name: properties?.name ?? "",
|
|
367
|
+
image: properties?.image ?? "",
|
|
368
|
+
...properties
|
|
369
|
+
};
|
|
370
|
+
await this.queue.enqueue(payload);
|
|
371
|
+
this.logger.log("User identified:", userId);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Track a payment event
|
|
375
|
+
*/
|
|
376
|
+
async trackPayment(data) {
|
|
377
|
+
if (!this.isReady()) return;
|
|
378
|
+
if (!data.email || typeof data.email !== "string") {
|
|
379
|
+
this.logger.warn("trackPayment requires an email");
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const screenName = this.state.lastScreenName ?? "Unknown";
|
|
383
|
+
const payload = this.buildBasePayload("payment", screenName);
|
|
384
|
+
payload.extraData = {
|
|
385
|
+
email: data.email,
|
|
386
|
+
...data.amount !== void 0 && { amount: data.amount },
|
|
387
|
+
...data.currency && { currency: data.currency }
|
|
388
|
+
};
|
|
389
|
+
await this.queue.enqueue(payload);
|
|
390
|
+
this.logger.log("Payment tracked:", data.email);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Track an external link click
|
|
394
|
+
*/
|
|
395
|
+
async trackExternalLink(url, text) {
|
|
396
|
+
if (!this.isReady()) return;
|
|
397
|
+
if (!url || typeof url !== "string") {
|
|
398
|
+
this.logger.warn("trackExternalLink requires a URL");
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const screenName = this.state.lastScreenName ?? "Unknown";
|
|
402
|
+
const payload = this.buildBasePayload("external_link", screenName);
|
|
403
|
+
payload.extraData = {
|
|
404
|
+
url,
|
|
405
|
+
...text && { text }
|
|
406
|
+
};
|
|
407
|
+
await this.queue.enqueue(payload);
|
|
408
|
+
this.logger.log("External link tracked:", url);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Flush all queued events immediately
|
|
412
|
+
*/
|
|
413
|
+
async flush() {
|
|
414
|
+
if (this.queue) {
|
|
415
|
+
await this.queue.flush();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Reset the session (start a new session)
|
|
420
|
+
*/
|
|
421
|
+
async resetSession() {
|
|
422
|
+
if (!this.state.config) return;
|
|
423
|
+
const newSessionId = generateSessionId();
|
|
424
|
+
const now = Date.now();
|
|
425
|
+
this.state.sessionId = newSessionId;
|
|
426
|
+
this.state.sessionStartTime = now;
|
|
427
|
+
this.state.lastScreenName = null;
|
|
428
|
+
await this.state.config.storage.setItem(STORAGE_KEYS.SESSION_ID, newSessionId);
|
|
429
|
+
await this.state.config.storage.setItem(STORAGE_KEYS.SESSION_START, now.toString());
|
|
430
|
+
this.logger.log("Session reset:", newSessionId);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Reset the visitor (new anonymous user)
|
|
434
|
+
*/
|
|
435
|
+
async reset() {
|
|
436
|
+
if (!this.state.config) return;
|
|
437
|
+
const newVisitorId = generateVisitorId();
|
|
438
|
+
const newSessionId = generateSessionId();
|
|
439
|
+
const now = Date.now();
|
|
440
|
+
this.state.visitorId = newVisitorId;
|
|
441
|
+
this.state.sessionId = newSessionId;
|
|
442
|
+
this.state.sessionStartTime = now;
|
|
443
|
+
this.state.lastScreenName = null;
|
|
444
|
+
await this.state.config.storage.setItem(STORAGE_KEYS.VISITOR_ID, newVisitorId);
|
|
445
|
+
await this.state.config.storage.setItem(STORAGE_KEYS.SESSION_ID, newSessionId);
|
|
446
|
+
await this.state.config.storage.setItem(STORAGE_KEYS.SESSION_START, now.toString());
|
|
447
|
+
if (this.queue) {
|
|
448
|
+
await this.queue.clear();
|
|
449
|
+
}
|
|
450
|
+
this.logger.log("Visitor reset:", newVisitorId);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Opt out of tracking
|
|
454
|
+
*/
|
|
455
|
+
async optOut() {
|
|
456
|
+
if (!this.state.config) return;
|
|
457
|
+
await this.state.config.storage.setItem(STORAGE_KEYS.IGNORE_TRACKING, "true");
|
|
458
|
+
this.state.initialized = false;
|
|
459
|
+
if (this.queue) {
|
|
460
|
+
await this.queue.clear();
|
|
461
|
+
await this.queue.shutdown();
|
|
462
|
+
}
|
|
463
|
+
this.logger.log("User opted out of tracking");
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Opt back into tracking
|
|
467
|
+
*/
|
|
468
|
+
async optIn() {
|
|
469
|
+
if (!this.state.config) return;
|
|
470
|
+
await this.state.config.storage.removeItem(STORAGE_KEYS.IGNORE_TRACKING);
|
|
471
|
+
this.logger.log("User opted back into tracking. Call init() to restart.");
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Set device info (call this when device info becomes available)
|
|
475
|
+
*/
|
|
476
|
+
setDeviceInfo(info) {
|
|
477
|
+
this.state.deviceInfo = {
|
|
478
|
+
platform: info.platform ?? this.state.config?.platform ?? "ios",
|
|
479
|
+
osVersion: info.osVersion ?? "0.0",
|
|
480
|
+
deviceModel: info.deviceModel ?? "Unknown",
|
|
481
|
+
appVersion: info.appVersion ?? this.state.config?.appVersion ?? "1.0.0",
|
|
482
|
+
screenWidth: info.screenWidth ?? 0,
|
|
483
|
+
screenHeight: info.screenHeight ?? 0,
|
|
484
|
+
viewport: info.viewport ?? { width: 0, height: 0 },
|
|
485
|
+
language: info.language ?? "en",
|
|
486
|
+
timezone: info.timezone ?? "UTC"
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Get current visitor ID
|
|
491
|
+
*/
|
|
492
|
+
getVisitorId() {
|
|
493
|
+
return this.state.visitorId;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Get current session ID
|
|
497
|
+
*/
|
|
498
|
+
getSessionId() {
|
|
499
|
+
return this.state.sessionId;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Check if SDK is initialized and ready
|
|
503
|
+
*/
|
|
504
|
+
isInitialized() {
|
|
505
|
+
return this.state.initialized;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Shutdown the SDK
|
|
509
|
+
*/
|
|
510
|
+
async shutdown() {
|
|
511
|
+
if (this.queue) {
|
|
512
|
+
await this.queue.shutdown();
|
|
513
|
+
}
|
|
514
|
+
this.state.initialized = false;
|
|
515
|
+
}
|
|
516
|
+
// Private methods
|
|
517
|
+
isReady() {
|
|
518
|
+
if (!this.state.initialized) {
|
|
519
|
+
this.logger.warn("SDK not initialized. Call init() first.");
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
async initVisitorId() {
|
|
525
|
+
const config = this.state.config;
|
|
526
|
+
let visitorId = await config.storage.getItem(STORAGE_KEYS.VISITOR_ID);
|
|
527
|
+
if (!visitorId || !isValidVisitorId(visitorId)) {
|
|
528
|
+
visitorId = generateVisitorId();
|
|
529
|
+
await config.storage.setItem(STORAGE_KEYS.VISITOR_ID, visitorId);
|
|
530
|
+
}
|
|
531
|
+
this.state.visitorId = visitorId;
|
|
532
|
+
}
|
|
533
|
+
async initSessionId() {
|
|
534
|
+
const config = this.state.config;
|
|
535
|
+
let sessionId = await config.storage.getItem(STORAGE_KEYS.SESSION_ID);
|
|
536
|
+
const sessionStartStr = await config.storage.getItem(STORAGE_KEYS.SESSION_START);
|
|
537
|
+
const sessionStart = sessionStartStr ? parseInt(sessionStartStr, 10) : 0;
|
|
538
|
+
if (!sessionId || !isValidSessionId(sessionId) || isSessionExpired(sessionStart)) {
|
|
539
|
+
sessionId = generateSessionId();
|
|
540
|
+
const now = Date.now();
|
|
541
|
+
await config.storage.setItem(STORAGE_KEYS.SESSION_ID, sessionId);
|
|
542
|
+
await config.storage.setItem(STORAGE_KEYS.SESSION_START, now.toString());
|
|
543
|
+
this.state.sessionStartTime = now;
|
|
544
|
+
} else {
|
|
545
|
+
this.state.sessionStartTime = sessionStart;
|
|
546
|
+
}
|
|
547
|
+
this.state.sessionId = sessionId;
|
|
548
|
+
}
|
|
549
|
+
buildBasePayload(type, screenName) {
|
|
550
|
+
const config = this.state.config;
|
|
551
|
+
const deviceInfo = this.state.deviceInfo;
|
|
552
|
+
return {
|
|
553
|
+
websiteId: config.appId,
|
|
554
|
+
domain: config.domain,
|
|
555
|
+
visitorId: this.state.visitorId,
|
|
556
|
+
sessionId: this.state.sessionId,
|
|
557
|
+
href: buildScreenHref(config.platform, screenName),
|
|
558
|
+
referrer: null,
|
|
559
|
+
type,
|
|
560
|
+
viewport: deviceInfo?.viewport ?? { width: 0, height: 0 },
|
|
561
|
+
language: deviceInfo?.language ?? "en",
|
|
562
|
+
timezone: deviceInfo?.timezone ?? "UTC",
|
|
563
|
+
screenWidth: deviceInfo?.screenWidth ?? 0,
|
|
564
|
+
screenHeight: deviceInfo?.screenHeight ?? 0
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
var instance = null;
|
|
569
|
+
function getDataFastClient() {
|
|
570
|
+
if (!instance) {
|
|
571
|
+
instance = new DataFastClient();
|
|
572
|
+
}
|
|
573
|
+
return instance;
|
|
574
|
+
}
|
|
575
|
+
function createDataFastClient() {
|
|
576
|
+
return new DataFastClient();
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/web/storage.ts
|
|
580
|
+
function createLocalStorageAdapter() {
|
|
581
|
+
return {
|
|
582
|
+
async getItem(key) {
|
|
583
|
+
try {
|
|
584
|
+
return localStorage.getItem(key);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
console.warn("[DataFast] localStorage.getItem failed:", error);
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
async setItem(key, value) {
|
|
591
|
+
try {
|
|
592
|
+
localStorage.setItem(key, value);
|
|
593
|
+
} catch (error) {
|
|
594
|
+
console.warn("[DataFast] localStorage.setItem failed:", error);
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
async removeItem(key) {
|
|
598
|
+
try {
|
|
599
|
+
localStorage.removeItem(key);
|
|
600
|
+
} catch (error) {
|
|
601
|
+
console.warn("[DataFast] localStorage.removeItem failed:", error);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function createMemoryStorageAdapter() {
|
|
607
|
+
const storage = /* @__PURE__ */ new Map();
|
|
608
|
+
return {
|
|
609
|
+
async getItem(key) {
|
|
610
|
+
return storage.get(key) ?? null;
|
|
611
|
+
},
|
|
612
|
+
async setItem(key, value) {
|
|
613
|
+
storage.set(key, value);
|
|
614
|
+
},
|
|
615
|
+
async removeItem(key) {
|
|
616
|
+
storage.delete(key);
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/web/network.ts
|
|
622
|
+
function createFetchNetworkAdapter() {
|
|
623
|
+
return {
|
|
624
|
+
async isConnected() {
|
|
625
|
+
return true;
|
|
626
|
+
},
|
|
627
|
+
onConnectionChange(callback) {
|
|
628
|
+
const handleOnline = () => callback(true);
|
|
629
|
+
const handleOffline = () => callback(false);
|
|
630
|
+
window.addEventListener("online", handleOnline);
|
|
631
|
+
window.addEventListener("offline", handleOffline);
|
|
632
|
+
return () => {
|
|
633
|
+
window.removeEventListener("online", handleOnline);
|
|
634
|
+
window.removeEventListener("offline", handleOffline);
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/web/device.ts
|
|
641
|
+
function getDeviceInfo() {
|
|
642
|
+
const getViewport = () => {
|
|
643
|
+
if (typeof window === "undefined") return { width: 0, height: 0 };
|
|
644
|
+
return {
|
|
645
|
+
width: window.innerWidth || 0,
|
|
646
|
+
height: window.innerHeight || 0
|
|
647
|
+
};
|
|
648
|
+
};
|
|
649
|
+
const getScreenSize = () => {
|
|
650
|
+
if (typeof window === "undefined" || !window.screen) return { width: 0, height: 0 };
|
|
651
|
+
return {
|
|
652
|
+
width: window.screen.width || 0,
|
|
653
|
+
height: window.screen.height || 0
|
|
654
|
+
};
|
|
655
|
+
};
|
|
656
|
+
const getLanguage = () => {
|
|
657
|
+
if (typeof navigator === "undefined") return "en";
|
|
658
|
+
return navigator.language || "en";
|
|
659
|
+
};
|
|
660
|
+
const getTimezone = () => {
|
|
661
|
+
try {
|
|
662
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
663
|
+
} catch {
|
|
664
|
+
return "UTC";
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
const getUserAgent = () => {
|
|
668
|
+
if (typeof navigator === "undefined") return "Unknown";
|
|
669
|
+
return navigator.userAgent || "Unknown";
|
|
670
|
+
};
|
|
671
|
+
const viewport = getViewport();
|
|
672
|
+
const screen = getScreenSize();
|
|
673
|
+
return {
|
|
674
|
+
platform: "web",
|
|
675
|
+
osVersion: getUserAgent(),
|
|
676
|
+
deviceModel: "Browser",
|
|
677
|
+
appVersion: "1.0.0",
|
|
678
|
+
screenWidth: screen.width,
|
|
679
|
+
screenHeight: screen.height,
|
|
680
|
+
viewport,
|
|
681
|
+
language: getLanguage(),
|
|
682
|
+
timezone: getTimezone()
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
function onViewportChange(callback) {
|
|
686
|
+
if (typeof window === "undefined") {
|
|
687
|
+
return () => {
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
const handleResize = () => {
|
|
691
|
+
callback({
|
|
692
|
+
width: window.innerWidth || 0,
|
|
693
|
+
height: window.innerHeight || 0
|
|
694
|
+
});
|
|
695
|
+
};
|
|
696
|
+
window.addEventListener("resize", handleResize);
|
|
697
|
+
return () => {
|
|
698
|
+
window.removeEventListener("resize", handleResize);
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/web/index.ts
|
|
703
|
+
async function initDataFast(config) {
|
|
704
|
+
const client = getDataFastClient();
|
|
705
|
+
let storage;
|
|
706
|
+
try {
|
|
707
|
+
localStorage.setItem("__datafast_test__", "test");
|
|
708
|
+
localStorage.removeItem("__datafast_test__");
|
|
709
|
+
storage = createLocalStorageAdapter();
|
|
710
|
+
} catch {
|
|
711
|
+
console.warn("[DataFast] localStorage not available, using in-memory storage");
|
|
712
|
+
storage = createMemoryStorageAdapter();
|
|
713
|
+
}
|
|
714
|
+
const network = createFetchNetworkAdapter();
|
|
715
|
+
const deviceInfo = getDeviceInfo();
|
|
716
|
+
const domain = config.domain ?? (typeof window !== "undefined" ? window.location.hostname : "unknown");
|
|
717
|
+
await client.init({
|
|
718
|
+
appId: config.websiteId,
|
|
719
|
+
domain,
|
|
720
|
+
storage,
|
|
721
|
+
network,
|
|
722
|
+
platform: "web",
|
|
723
|
+
apiUrl: config.apiUrl,
|
|
724
|
+
debug: config.debug,
|
|
725
|
+
flushInterval: config.flushInterval ?? 5e3,
|
|
726
|
+
maxQueueSize: config.maxQueueSize ?? 10
|
|
727
|
+
});
|
|
728
|
+
client.setDeviceInfo(deviceInfo);
|
|
729
|
+
onViewportChange((viewport) => {
|
|
730
|
+
client.setDeviceInfo({
|
|
731
|
+
...deviceInfo,
|
|
732
|
+
viewport
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
const webClient = client;
|
|
736
|
+
webClient.trackPageview = async (path) => {
|
|
737
|
+
const pagePath = path ?? (typeof window !== "undefined" ? window.location.pathname : "/");
|
|
738
|
+
await client.trackScreen(pagePath);
|
|
739
|
+
};
|
|
740
|
+
return webClient;
|
|
741
|
+
}
|
|
742
|
+
async function createDataFastWithAdapters(config) {
|
|
743
|
+
const client = createDataFastClient();
|
|
744
|
+
await client.init(config);
|
|
745
|
+
const deviceInfo = getDeviceInfo();
|
|
746
|
+
client.setDeviceInfo(deviceInfo);
|
|
747
|
+
return client;
|
|
748
|
+
}
|
|
749
|
+
async function createDataFastWeb(config) {
|
|
750
|
+
return initDataFast(config);
|
|
751
|
+
}
|
|
752
|
+
var dataFastWeb = {
|
|
753
|
+
init: initDataFast,
|
|
754
|
+
createWeb: createDataFastWeb,
|
|
755
|
+
getClient: getDataFastClient,
|
|
756
|
+
createClient: createDataFastClient
|
|
757
|
+
};
|
|
758
|
+
var web_default = dataFastWeb;
|
|
759
|
+
|
|
760
|
+
export { DataFastClient, EventQueue, buildScreenHref, buildUserAgent, createDataFastClient, createDataFastWeb, createDataFastWithAdapters, createFetchNetworkAdapter, createLocalStorageAdapter, createLogger, createMemoryStorageAdapter, web_default as default, generateEventId, generateSessionId, generateVisitorId, getDataFastClient, getDeviceInfo, initDataFast, isSessionExpired, isValidEventName, isValidSessionId, isValidVisitorId, onViewportChange, sanitizeCustomProperties };
|
|
761
|
+
//# sourceMappingURL=index.mjs.map
|
|
762
|
+
//# sourceMappingURL=index.mjs.map
|