@inslytic/sdk-browser 0.1.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/dist/index.d.mts +38 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +314 -0
- package/dist/index.mjs +282 -0
- package/package.json +52 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type InslyticConfig = {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
endpoint?: string;
|
|
4
|
+
debug?: boolean;
|
|
5
|
+
flushInterval?: number;
|
|
6
|
+
maxBatchSize?: number;
|
|
7
|
+
};
|
|
8
|
+
type TrackOptions = {
|
|
9
|
+
properties?: Record<string, string>;
|
|
10
|
+
};
|
|
11
|
+
type QueuedEvent = {
|
|
12
|
+
eventName: string;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
anonymousId: string;
|
|
15
|
+
sessionId: string;
|
|
16
|
+
userId: string | null;
|
|
17
|
+
consentGiven: boolean;
|
|
18
|
+
properties: Record<string, string>;
|
|
19
|
+
pageUrl: string;
|
|
20
|
+
pageTitle: string;
|
|
21
|
+
referrer: string;
|
|
22
|
+
utmSource: string | null;
|
|
23
|
+
utmMedium: string | null;
|
|
24
|
+
utmCampaign: string | null;
|
|
25
|
+
deviceType: "desktop" | "mobile" | "tablet";
|
|
26
|
+
browser: string;
|
|
27
|
+
os: string;
|
|
28
|
+
};
|
|
29
|
+
type IdentifyTraits = Record<string, string>;
|
|
30
|
+
|
|
31
|
+
declare function init(cfg: InslyticConfig): void;
|
|
32
|
+
declare function track(eventName: string, options?: TrackOptions): void;
|
|
33
|
+
declare function identify(userId: string, _traits?: IdentifyTraits): void;
|
|
34
|
+
declare function page(properties?: Record<string, string>): void;
|
|
35
|
+
declare function setConsent(granted: boolean): void;
|
|
36
|
+
declare function reset(): void;
|
|
37
|
+
|
|
38
|
+
export { type IdentifyTraits, type InslyticConfig, type QueuedEvent, type TrackOptions, identify, init, page, reset, setConsent, track };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type InslyticConfig = {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
endpoint?: string;
|
|
4
|
+
debug?: boolean;
|
|
5
|
+
flushInterval?: number;
|
|
6
|
+
maxBatchSize?: number;
|
|
7
|
+
};
|
|
8
|
+
type TrackOptions = {
|
|
9
|
+
properties?: Record<string, string>;
|
|
10
|
+
};
|
|
11
|
+
type QueuedEvent = {
|
|
12
|
+
eventName: string;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
anonymousId: string;
|
|
15
|
+
sessionId: string;
|
|
16
|
+
userId: string | null;
|
|
17
|
+
consentGiven: boolean;
|
|
18
|
+
properties: Record<string, string>;
|
|
19
|
+
pageUrl: string;
|
|
20
|
+
pageTitle: string;
|
|
21
|
+
referrer: string;
|
|
22
|
+
utmSource: string | null;
|
|
23
|
+
utmMedium: string | null;
|
|
24
|
+
utmCampaign: string | null;
|
|
25
|
+
deviceType: "desktop" | "mobile" | "tablet";
|
|
26
|
+
browser: string;
|
|
27
|
+
os: string;
|
|
28
|
+
};
|
|
29
|
+
type IdentifyTraits = Record<string, string>;
|
|
30
|
+
|
|
31
|
+
declare function init(cfg: InslyticConfig): void;
|
|
32
|
+
declare function track(eventName: string, options?: TrackOptions): void;
|
|
33
|
+
declare function identify(userId: string, _traits?: IdentifyTraits): void;
|
|
34
|
+
declare function page(properties?: Record<string, string>): void;
|
|
35
|
+
declare function setConsent(granted: boolean): void;
|
|
36
|
+
declare function reset(): void;
|
|
37
|
+
|
|
38
|
+
export { type IdentifyTraits, type InslyticConfig, type QueuedEvent, type TrackOptions, identify, init, page, reset, setConsent, track };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
identify: () => identify,
|
|
24
|
+
init: () => init,
|
|
25
|
+
page: () => page,
|
|
26
|
+
reset: () => reset,
|
|
27
|
+
setConsent: () => setConsent,
|
|
28
|
+
track: () => track
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/context.ts
|
|
33
|
+
function getPageContext() {
|
|
34
|
+
const url = typeof location !== "undefined" ? location.href : "";
|
|
35
|
+
const title = typeof document !== "undefined" ? document.title : "";
|
|
36
|
+
const referrer = typeof document !== "undefined" ? document.referrer : "";
|
|
37
|
+
let utmSource = null;
|
|
38
|
+
let utmMedium = null;
|
|
39
|
+
let utmCampaign = null;
|
|
40
|
+
try {
|
|
41
|
+
const params = new URLSearchParams(location.search);
|
|
42
|
+
utmSource = params.get("utm_source");
|
|
43
|
+
utmMedium = params.get("utm_medium");
|
|
44
|
+
utmCampaign = params.get("utm_campaign");
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
return { pageUrl: url, pageTitle: title, referrer, utmSource, utmMedium, utmCampaign };
|
|
48
|
+
}
|
|
49
|
+
function getDeviceContext() {
|
|
50
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
51
|
+
return {
|
|
52
|
+
deviceType: parseDeviceType(ua),
|
|
53
|
+
browser: parseBrowser(ua),
|
|
54
|
+
os: parseOS(ua)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function parseDeviceType(ua) {
|
|
58
|
+
if (/tablet|ipad/i.test(ua)) return "tablet";
|
|
59
|
+
if (/mobile|iphone|android(?!.*tablet)/i.test(ua)) return "mobile";
|
|
60
|
+
return "desktop";
|
|
61
|
+
}
|
|
62
|
+
function parseBrowser(ua) {
|
|
63
|
+
if (/edg\//i.test(ua)) return "Edge";
|
|
64
|
+
if (/opr\//i.test(ua) || /opera/i.test(ua)) return "Opera";
|
|
65
|
+
if (/firefox\//i.test(ua)) return "Firefox";
|
|
66
|
+
if (/chrome\//i.test(ua) && !/edg\//i.test(ua)) return "Chrome";
|
|
67
|
+
if (/safari\//i.test(ua) && !/chrome\//i.test(ua)) return "Safari";
|
|
68
|
+
return "Other";
|
|
69
|
+
}
|
|
70
|
+
function parseOS(ua) {
|
|
71
|
+
if (/iphone|ipad|ipod/i.test(ua)) return "iOS";
|
|
72
|
+
if (/android/i.test(ua)) return "Android";
|
|
73
|
+
if (/windows/i.test(ua)) return "Windows";
|
|
74
|
+
if (/macintosh|mac os/i.test(ua)) return "macOS";
|
|
75
|
+
if (/linux/i.test(ua)) return "Linux";
|
|
76
|
+
return "Other";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/identity.ts
|
|
80
|
+
var ANON_ID_KEY = "inslytic_anon_id";
|
|
81
|
+
var SESSION_ID_KEY = "inslytic_session_id";
|
|
82
|
+
var SESSION_LAST_ACTIVE_KEY = "inslytic_session_ts";
|
|
83
|
+
var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
84
|
+
function generateId() {
|
|
85
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
86
|
+
return crypto.randomUUID();
|
|
87
|
+
}
|
|
88
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
89
|
+
const r = Math.random() * 16 | 0;
|
|
90
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
91
|
+
return v.toString(16);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function getOrCreateAnonymousId() {
|
|
95
|
+
try {
|
|
96
|
+
const existing = localStorage.getItem(ANON_ID_KEY);
|
|
97
|
+
if (existing) return existing;
|
|
98
|
+
const id = generateId();
|
|
99
|
+
localStorage.setItem(ANON_ID_KEY, id);
|
|
100
|
+
return id;
|
|
101
|
+
} catch {
|
|
102
|
+
return generateId();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function getOrCreateSessionId() {
|
|
106
|
+
try {
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
const lastActive = sessionStorage.getItem(SESSION_LAST_ACTIVE_KEY);
|
|
109
|
+
const existingId = sessionStorage.getItem(SESSION_ID_KEY);
|
|
110
|
+
if (existingId && lastActive) {
|
|
111
|
+
const elapsed = now - parseInt(lastActive, 10);
|
|
112
|
+
if (elapsed < SESSION_TIMEOUT_MS) {
|
|
113
|
+
sessionStorage.setItem(SESSION_LAST_ACTIVE_KEY, String(now));
|
|
114
|
+
return existingId;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const id = generateId();
|
|
118
|
+
sessionStorage.setItem(SESSION_ID_KEY, id);
|
|
119
|
+
sessionStorage.setItem(SESSION_LAST_ACTIVE_KEY, String(now));
|
|
120
|
+
return id;
|
|
121
|
+
} catch {
|
|
122
|
+
return generateId();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function resetAnonymousId() {
|
|
126
|
+
try {
|
|
127
|
+
localStorage.removeItem(ANON_ID_KEY);
|
|
128
|
+
sessionStorage.removeItem(SESSION_ID_KEY);
|
|
129
|
+
sessionStorage.removeItem(SESSION_LAST_ACTIVE_KEY);
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/queue.ts
|
|
135
|
+
var DEFAULT_FLUSH_INTERVAL = 5e3;
|
|
136
|
+
var DEFAULT_MAX_BATCH_SIZE = 20;
|
|
137
|
+
var EventQueue = class {
|
|
138
|
+
queue = [];
|
|
139
|
+
timer = null;
|
|
140
|
+
config;
|
|
141
|
+
constructor(config) {
|
|
142
|
+
this.config = {
|
|
143
|
+
flushInterval: config.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
|
|
144
|
+
maxBatchSize: config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE,
|
|
145
|
+
debug: config.debug ?? false,
|
|
146
|
+
endpoint: config.endpoint,
|
|
147
|
+
apiKey: config.apiKey
|
|
148
|
+
};
|
|
149
|
+
this.startTimer();
|
|
150
|
+
this.setupUnloadHandler();
|
|
151
|
+
}
|
|
152
|
+
enqueue(event) {
|
|
153
|
+
this.queue.push(event);
|
|
154
|
+
if (this.queue.length >= this.config.maxBatchSize) {
|
|
155
|
+
this.flush();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
flush() {
|
|
159
|
+
if (this.queue.length === 0) return;
|
|
160
|
+
const batch = this.queue.splice(0, this.config.maxBatchSize);
|
|
161
|
+
this.send(batch);
|
|
162
|
+
}
|
|
163
|
+
destroy() {
|
|
164
|
+
if (this.timer) {
|
|
165
|
+
clearInterval(this.timer);
|
|
166
|
+
this.timer = null;
|
|
167
|
+
}
|
|
168
|
+
this.flush();
|
|
169
|
+
}
|
|
170
|
+
get pending() {
|
|
171
|
+
return this.queue.length;
|
|
172
|
+
}
|
|
173
|
+
startTimer() {
|
|
174
|
+
this.timer = setInterval(() => this.flush(), this.config.flushInterval);
|
|
175
|
+
}
|
|
176
|
+
setupUnloadHandler() {
|
|
177
|
+
if (typeof document === "undefined") return;
|
|
178
|
+
const handleUnload = () => {
|
|
179
|
+
if (this.queue.length === 0) return;
|
|
180
|
+
const batch = this.queue.splice(0);
|
|
181
|
+
const url = `${this.config.endpoint}/v1/ingest`;
|
|
182
|
+
const body = JSON.stringify({ events: batch });
|
|
183
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
184
|
+
const blob = new Blob([body], { type: "application/json" });
|
|
185
|
+
const sent = navigator.sendBeacon(url, blob);
|
|
186
|
+
if (sent) return;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
fetch(url, {
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers: {
|
|
192
|
+
"Content-Type": "application/json",
|
|
193
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
194
|
+
},
|
|
195
|
+
body,
|
|
196
|
+
keepalive: true
|
|
197
|
+
});
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
document.addEventListener("visibilitychange", () => {
|
|
202
|
+
if (document.visibilityState === "hidden") handleUnload();
|
|
203
|
+
});
|
|
204
|
+
addEventListener("beforeunload", handleUnload);
|
|
205
|
+
}
|
|
206
|
+
send(batch) {
|
|
207
|
+
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
|
208
|
+
this.queue.unshift(...batch);
|
|
209
|
+
this.log("Offline, re-queued", batch.length, "events");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const url = `${this.config.endpoint}/v1/ingest`;
|
|
213
|
+
this.log("Flushing", batch.length, "events");
|
|
214
|
+
fetch(url, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers: {
|
|
217
|
+
"Content-Type": "application/json",
|
|
218
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
219
|
+
},
|
|
220
|
+
body: JSON.stringify({ events: batch })
|
|
221
|
+
}).catch(() => {
|
|
222
|
+
this.queue.unshift(...batch);
|
|
223
|
+
this.log("Send failed, re-queued", batch.length, "events");
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
log(...args) {
|
|
227
|
+
if (this.config.debug) {
|
|
228
|
+
console.log("[inslytic]", ...args);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// src/client.ts
|
|
234
|
+
var DEFAULT_ENDPOINT = "https://api.inslytic.com";
|
|
235
|
+
var queue = null;
|
|
236
|
+
var currentUserId = null;
|
|
237
|
+
var consentGranted = false;
|
|
238
|
+
var initialized = false;
|
|
239
|
+
var _config = null;
|
|
240
|
+
function init(cfg) {
|
|
241
|
+
if (initialized) return;
|
|
242
|
+
_config = { ...cfg };
|
|
243
|
+
consentGranted = false;
|
|
244
|
+
currentUserId = null;
|
|
245
|
+
queue = new EventQueue({
|
|
246
|
+
endpoint: cfg.endpoint ?? DEFAULT_ENDPOINT,
|
|
247
|
+
apiKey: cfg.apiKey,
|
|
248
|
+
flushInterval: cfg.flushInterval,
|
|
249
|
+
maxBatchSize: cfg.maxBatchSize,
|
|
250
|
+
debug: cfg.debug
|
|
251
|
+
});
|
|
252
|
+
initialized = true;
|
|
253
|
+
}
|
|
254
|
+
function track(eventName, options) {
|
|
255
|
+
if (!initialized || !queue) return;
|
|
256
|
+
const event = buildEvent(eventName, options?.properties ?? {});
|
|
257
|
+
queue.enqueue(event);
|
|
258
|
+
}
|
|
259
|
+
function identify(userId, _traits) {
|
|
260
|
+
if (!initialized || !queue) return;
|
|
261
|
+
currentUserId = userId;
|
|
262
|
+
const event = buildEvent("$identify", {});
|
|
263
|
+
queue.enqueue(event);
|
|
264
|
+
}
|
|
265
|
+
function page(properties) {
|
|
266
|
+
if (!initialized || !queue) return;
|
|
267
|
+
const event = buildEvent("$pageview", properties ?? {});
|
|
268
|
+
queue.enqueue(event);
|
|
269
|
+
}
|
|
270
|
+
function setConsent(granted) {
|
|
271
|
+
consentGranted = granted;
|
|
272
|
+
}
|
|
273
|
+
function reset() {
|
|
274
|
+
currentUserId = null;
|
|
275
|
+
consentGranted = false;
|
|
276
|
+
resetAnonymousId();
|
|
277
|
+
if (queue) {
|
|
278
|
+
queue.destroy();
|
|
279
|
+
queue = null;
|
|
280
|
+
}
|
|
281
|
+
initialized = false;
|
|
282
|
+
_config = null;
|
|
283
|
+
}
|
|
284
|
+
function buildEvent(eventName, properties) {
|
|
285
|
+
const page2 = getPageContext();
|
|
286
|
+
const device = getDeviceContext();
|
|
287
|
+
return {
|
|
288
|
+
eventName,
|
|
289
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
290
|
+
anonymousId: getOrCreateAnonymousId(),
|
|
291
|
+
sessionId: getOrCreateSessionId(),
|
|
292
|
+
userId: currentUserId,
|
|
293
|
+
consentGiven: consentGranted,
|
|
294
|
+
properties,
|
|
295
|
+
pageUrl: page2.pageUrl,
|
|
296
|
+
pageTitle: page2.pageTitle,
|
|
297
|
+
referrer: page2.referrer,
|
|
298
|
+
utmSource: page2.utmSource,
|
|
299
|
+
utmMedium: page2.utmMedium,
|
|
300
|
+
utmCampaign: page2.utmCampaign,
|
|
301
|
+
deviceType: device.deviceType,
|
|
302
|
+
browser: device.browser,
|
|
303
|
+
os: device.os
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
307
|
+
0 && (module.exports = {
|
|
308
|
+
identify,
|
|
309
|
+
init,
|
|
310
|
+
page,
|
|
311
|
+
reset,
|
|
312
|
+
setConsent,
|
|
313
|
+
track
|
|
314
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
// src/context.ts
|
|
2
|
+
function getPageContext() {
|
|
3
|
+
const url = typeof location !== "undefined" ? location.href : "";
|
|
4
|
+
const title = typeof document !== "undefined" ? document.title : "";
|
|
5
|
+
const referrer = typeof document !== "undefined" ? document.referrer : "";
|
|
6
|
+
let utmSource = null;
|
|
7
|
+
let utmMedium = null;
|
|
8
|
+
let utmCampaign = null;
|
|
9
|
+
try {
|
|
10
|
+
const params = new URLSearchParams(location.search);
|
|
11
|
+
utmSource = params.get("utm_source");
|
|
12
|
+
utmMedium = params.get("utm_medium");
|
|
13
|
+
utmCampaign = params.get("utm_campaign");
|
|
14
|
+
} catch {
|
|
15
|
+
}
|
|
16
|
+
return { pageUrl: url, pageTitle: title, referrer, utmSource, utmMedium, utmCampaign };
|
|
17
|
+
}
|
|
18
|
+
function getDeviceContext() {
|
|
19
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
20
|
+
return {
|
|
21
|
+
deviceType: parseDeviceType(ua),
|
|
22
|
+
browser: parseBrowser(ua),
|
|
23
|
+
os: parseOS(ua)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function parseDeviceType(ua) {
|
|
27
|
+
if (/tablet|ipad/i.test(ua)) return "tablet";
|
|
28
|
+
if (/mobile|iphone|android(?!.*tablet)/i.test(ua)) return "mobile";
|
|
29
|
+
return "desktop";
|
|
30
|
+
}
|
|
31
|
+
function parseBrowser(ua) {
|
|
32
|
+
if (/edg\//i.test(ua)) return "Edge";
|
|
33
|
+
if (/opr\//i.test(ua) || /opera/i.test(ua)) return "Opera";
|
|
34
|
+
if (/firefox\//i.test(ua)) return "Firefox";
|
|
35
|
+
if (/chrome\//i.test(ua) && !/edg\//i.test(ua)) return "Chrome";
|
|
36
|
+
if (/safari\//i.test(ua) && !/chrome\//i.test(ua)) return "Safari";
|
|
37
|
+
return "Other";
|
|
38
|
+
}
|
|
39
|
+
function parseOS(ua) {
|
|
40
|
+
if (/iphone|ipad|ipod/i.test(ua)) return "iOS";
|
|
41
|
+
if (/android/i.test(ua)) return "Android";
|
|
42
|
+
if (/windows/i.test(ua)) return "Windows";
|
|
43
|
+
if (/macintosh|mac os/i.test(ua)) return "macOS";
|
|
44
|
+
if (/linux/i.test(ua)) return "Linux";
|
|
45
|
+
return "Other";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/identity.ts
|
|
49
|
+
var ANON_ID_KEY = "inslytic_anon_id";
|
|
50
|
+
var SESSION_ID_KEY = "inslytic_session_id";
|
|
51
|
+
var SESSION_LAST_ACTIVE_KEY = "inslytic_session_ts";
|
|
52
|
+
var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
53
|
+
function generateId() {
|
|
54
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
55
|
+
return crypto.randomUUID();
|
|
56
|
+
}
|
|
57
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
58
|
+
const r = Math.random() * 16 | 0;
|
|
59
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
60
|
+
return v.toString(16);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function getOrCreateAnonymousId() {
|
|
64
|
+
try {
|
|
65
|
+
const existing = localStorage.getItem(ANON_ID_KEY);
|
|
66
|
+
if (existing) return existing;
|
|
67
|
+
const id = generateId();
|
|
68
|
+
localStorage.setItem(ANON_ID_KEY, id);
|
|
69
|
+
return id;
|
|
70
|
+
} catch {
|
|
71
|
+
return generateId();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function getOrCreateSessionId() {
|
|
75
|
+
try {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const lastActive = sessionStorage.getItem(SESSION_LAST_ACTIVE_KEY);
|
|
78
|
+
const existingId = sessionStorage.getItem(SESSION_ID_KEY);
|
|
79
|
+
if (existingId && lastActive) {
|
|
80
|
+
const elapsed = now - parseInt(lastActive, 10);
|
|
81
|
+
if (elapsed < SESSION_TIMEOUT_MS) {
|
|
82
|
+
sessionStorage.setItem(SESSION_LAST_ACTIVE_KEY, String(now));
|
|
83
|
+
return existingId;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const id = generateId();
|
|
87
|
+
sessionStorage.setItem(SESSION_ID_KEY, id);
|
|
88
|
+
sessionStorage.setItem(SESSION_LAST_ACTIVE_KEY, String(now));
|
|
89
|
+
return id;
|
|
90
|
+
} catch {
|
|
91
|
+
return generateId();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function resetAnonymousId() {
|
|
95
|
+
try {
|
|
96
|
+
localStorage.removeItem(ANON_ID_KEY);
|
|
97
|
+
sessionStorage.removeItem(SESSION_ID_KEY);
|
|
98
|
+
sessionStorage.removeItem(SESSION_LAST_ACTIVE_KEY);
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/queue.ts
|
|
104
|
+
var DEFAULT_FLUSH_INTERVAL = 5e3;
|
|
105
|
+
var DEFAULT_MAX_BATCH_SIZE = 20;
|
|
106
|
+
var EventQueue = class {
|
|
107
|
+
queue = [];
|
|
108
|
+
timer = null;
|
|
109
|
+
config;
|
|
110
|
+
constructor(config) {
|
|
111
|
+
this.config = {
|
|
112
|
+
flushInterval: config.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
|
|
113
|
+
maxBatchSize: config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE,
|
|
114
|
+
debug: config.debug ?? false,
|
|
115
|
+
endpoint: config.endpoint,
|
|
116
|
+
apiKey: config.apiKey
|
|
117
|
+
};
|
|
118
|
+
this.startTimer();
|
|
119
|
+
this.setupUnloadHandler();
|
|
120
|
+
}
|
|
121
|
+
enqueue(event) {
|
|
122
|
+
this.queue.push(event);
|
|
123
|
+
if (this.queue.length >= this.config.maxBatchSize) {
|
|
124
|
+
this.flush();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
flush() {
|
|
128
|
+
if (this.queue.length === 0) return;
|
|
129
|
+
const batch = this.queue.splice(0, this.config.maxBatchSize);
|
|
130
|
+
this.send(batch);
|
|
131
|
+
}
|
|
132
|
+
destroy() {
|
|
133
|
+
if (this.timer) {
|
|
134
|
+
clearInterval(this.timer);
|
|
135
|
+
this.timer = null;
|
|
136
|
+
}
|
|
137
|
+
this.flush();
|
|
138
|
+
}
|
|
139
|
+
get pending() {
|
|
140
|
+
return this.queue.length;
|
|
141
|
+
}
|
|
142
|
+
startTimer() {
|
|
143
|
+
this.timer = setInterval(() => this.flush(), this.config.flushInterval);
|
|
144
|
+
}
|
|
145
|
+
setupUnloadHandler() {
|
|
146
|
+
if (typeof document === "undefined") return;
|
|
147
|
+
const handleUnload = () => {
|
|
148
|
+
if (this.queue.length === 0) return;
|
|
149
|
+
const batch = this.queue.splice(0);
|
|
150
|
+
const url = `${this.config.endpoint}/v1/ingest`;
|
|
151
|
+
const body = JSON.stringify({ events: batch });
|
|
152
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
153
|
+
const blob = new Blob([body], { type: "application/json" });
|
|
154
|
+
const sent = navigator.sendBeacon(url, blob);
|
|
155
|
+
if (sent) return;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
fetch(url, {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers: {
|
|
161
|
+
"Content-Type": "application/json",
|
|
162
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
163
|
+
},
|
|
164
|
+
body,
|
|
165
|
+
keepalive: true
|
|
166
|
+
});
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
document.addEventListener("visibilitychange", () => {
|
|
171
|
+
if (document.visibilityState === "hidden") handleUnload();
|
|
172
|
+
});
|
|
173
|
+
addEventListener("beforeunload", handleUnload);
|
|
174
|
+
}
|
|
175
|
+
send(batch) {
|
|
176
|
+
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
|
177
|
+
this.queue.unshift(...batch);
|
|
178
|
+
this.log("Offline, re-queued", batch.length, "events");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const url = `${this.config.endpoint}/v1/ingest`;
|
|
182
|
+
this.log("Flushing", batch.length, "events");
|
|
183
|
+
fetch(url, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers: {
|
|
186
|
+
"Content-Type": "application/json",
|
|
187
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
188
|
+
},
|
|
189
|
+
body: JSON.stringify({ events: batch })
|
|
190
|
+
}).catch(() => {
|
|
191
|
+
this.queue.unshift(...batch);
|
|
192
|
+
this.log("Send failed, re-queued", batch.length, "events");
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
log(...args) {
|
|
196
|
+
if (this.config.debug) {
|
|
197
|
+
console.log("[inslytic]", ...args);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// src/client.ts
|
|
203
|
+
var DEFAULT_ENDPOINT = "https://api.inslytic.com";
|
|
204
|
+
var queue = null;
|
|
205
|
+
var currentUserId = null;
|
|
206
|
+
var consentGranted = false;
|
|
207
|
+
var initialized = false;
|
|
208
|
+
var _config = null;
|
|
209
|
+
function init(cfg) {
|
|
210
|
+
if (initialized) return;
|
|
211
|
+
_config = { ...cfg };
|
|
212
|
+
consentGranted = false;
|
|
213
|
+
currentUserId = null;
|
|
214
|
+
queue = new EventQueue({
|
|
215
|
+
endpoint: cfg.endpoint ?? DEFAULT_ENDPOINT,
|
|
216
|
+
apiKey: cfg.apiKey,
|
|
217
|
+
flushInterval: cfg.flushInterval,
|
|
218
|
+
maxBatchSize: cfg.maxBatchSize,
|
|
219
|
+
debug: cfg.debug
|
|
220
|
+
});
|
|
221
|
+
initialized = true;
|
|
222
|
+
}
|
|
223
|
+
function track(eventName, options) {
|
|
224
|
+
if (!initialized || !queue) return;
|
|
225
|
+
const event = buildEvent(eventName, options?.properties ?? {});
|
|
226
|
+
queue.enqueue(event);
|
|
227
|
+
}
|
|
228
|
+
function identify(userId, _traits) {
|
|
229
|
+
if (!initialized || !queue) return;
|
|
230
|
+
currentUserId = userId;
|
|
231
|
+
const event = buildEvent("$identify", {});
|
|
232
|
+
queue.enqueue(event);
|
|
233
|
+
}
|
|
234
|
+
function page(properties) {
|
|
235
|
+
if (!initialized || !queue) return;
|
|
236
|
+
const event = buildEvent("$pageview", properties ?? {});
|
|
237
|
+
queue.enqueue(event);
|
|
238
|
+
}
|
|
239
|
+
function setConsent(granted) {
|
|
240
|
+
consentGranted = granted;
|
|
241
|
+
}
|
|
242
|
+
function reset() {
|
|
243
|
+
currentUserId = null;
|
|
244
|
+
consentGranted = false;
|
|
245
|
+
resetAnonymousId();
|
|
246
|
+
if (queue) {
|
|
247
|
+
queue.destroy();
|
|
248
|
+
queue = null;
|
|
249
|
+
}
|
|
250
|
+
initialized = false;
|
|
251
|
+
_config = null;
|
|
252
|
+
}
|
|
253
|
+
function buildEvent(eventName, properties) {
|
|
254
|
+
const page2 = getPageContext();
|
|
255
|
+
const device = getDeviceContext();
|
|
256
|
+
return {
|
|
257
|
+
eventName,
|
|
258
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
259
|
+
anonymousId: getOrCreateAnonymousId(),
|
|
260
|
+
sessionId: getOrCreateSessionId(),
|
|
261
|
+
userId: currentUserId,
|
|
262
|
+
consentGiven: consentGranted,
|
|
263
|
+
properties,
|
|
264
|
+
pageUrl: page2.pageUrl,
|
|
265
|
+
pageTitle: page2.pageTitle,
|
|
266
|
+
referrer: page2.referrer,
|
|
267
|
+
utmSource: page2.utmSource,
|
|
268
|
+
utmMedium: page2.utmMedium,
|
|
269
|
+
utmCampaign: page2.utmCampaign,
|
|
270
|
+
deviceType: device.deviceType,
|
|
271
|
+
browser: device.browser,
|
|
272
|
+
os: device.os
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
export {
|
|
276
|
+
identify,
|
|
277
|
+
init,
|
|
278
|
+
page,
|
|
279
|
+
reset,
|
|
280
|
+
setConsent,
|
|
281
|
+
track
|
|
282
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@inslytic/sdk-browser",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Browser tracking SDK for Inslytic — AI-powered product analytics",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/inslytic/inslytic",
|
|
9
|
+
"directory": "packages/sdk-browser"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"analytics",
|
|
13
|
+
"tracking",
|
|
14
|
+
"product-analytics",
|
|
15
|
+
"browser",
|
|
16
|
+
"inslytic"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"module": "./dist/index.mjs",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": {
|
|
25
|
+
"types": "./dist/index.d.mts",
|
|
26
|
+
"default": "./dist/index.mjs"
|
|
27
|
+
},
|
|
28
|
+
"require": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"tsup": "^8.0.0",
|
|
42
|
+
"typescript": "^5.7.0",
|
|
43
|
+
"vitest": "^3.0.0"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
47
|
+
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"clean": "rm -rf dist .turbo node_modules"
|
|
51
|
+
}
|
|
52
|
+
}
|