plugeen 0.0.9 → 0.0.10
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.cjs +146 -129
- package/dist/index.d.cts +88 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +146 -129
- package/dist/plugeen.global.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/helpers/logger.ts
|
|
4
|
+
function createLogger(debug) {
|
|
5
|
+
if (!debug) {
|
|
6
|
+
return { log: () => {
|
|
7
|
+
}, warn: () => {
|
|
8
|
+
}, error: () => {
|
|
9
|
+
} };
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
// biome-ignore lint/suspicious/noConsole: Required
|
|
13
|
+
log: (...args) => console.log("[plugeen]", ...args),
|
|
14
|
+
// biome-ignore lint/suspicious/noConsole: Required
|
|
15
|
+
warn: (...args) => console.warn("[plugeen]", ...args),
|
|
16
|
+
// biome-ignore lint/suspicious/noConsole: Required
|
|
17
|
+
error: (...args) => console.error("[plugeen]", ...args)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
3
21
|
// src/helpers/uuid.ts
|
|
4
22
|
function generateUUID() {
|
|
5
23
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
@@ -19,7 +37,7 @@ function generateUUID() {
|
|
|
19
37
|
});
|
|
20
38
|
}
|
|
21
39
|
|
|
22
|
-
// src/storage.ts
|
|
40
|
+
// src/storage/index.ts
|
|
23
41
|
var IDENTITY_KEY = "plugeen_anon_id";
|
|
24
42
|
var SESSION_KEY = "plugeen_session_id";
|
|
25
43
|
var SESSION_TS_KEY = "plugeen_session_ts";
|
|
@@ -75,7 +93,7 @@ function getOrCreateSessionId() {
|
|
|
75
93
|
}
|
|
76
94
|
}
|
|
77
95
|
|
|
78
|
-
// src/
|
|
96
|
+
// src/http-client/index.ts
|
|
79
97
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
80
98
|
var HttpClient = class {
|
|
81
99
|
constructor(baseUrl, apiKey, maxRetries = 3) {
|
|
@@ -92,8 +110,9 @@ var HttpClient = class {
|
|
|
92
110
|
if (method !== "GET") h["Content-Type"] = "application/json";
|
|
93
111
|
return h;
|
|
94
112
|
}
|
|
95
|
-
async request(path, { attempt = 0, method, body }) {
|
|
96
|
-
const
|
|
113
|
+
async request(path, { attempt = 0, method, body, searchParams }) {
|
|
114
|
+
const sufix = searchParams ? `?${new URLSearchParams(searchParams).toString()}` : "";
|
|
115
|
+
const url = `${this.baseUrl}${path}${sufix}`;
|
|
97
116
|
try {
|
|
98
117
|
const res = await fetch(url, {
|
|
99
118
|
method,
|
|
@@ -124,21 +143,23 @@ var HttpClient = class {
|
|
|
124
143
|
if (err instanceof TypeError && attempt < this.maxRetries) {
|
|
125
144
|
const jitter = 0.85 + Math.random() * 0.3;
|
|
126
145
|
await delay(500 * 2 ** attempt * jitter);
|
|
127
|
-
return this.get(path,
|
|
146
|
+
return this.get(path, {
|
|
147
|
+
attempt: attempt + 1
|
|
148
|
+
});
|
|
128
149
|
}
|
|
129
150
|
return [null, err];
|
|
130
151
|
}
|
|
131
152
|
}
|
|
132
|
-
async post(path, body,
|
|
153
|
+
async post(path, body, options) {
|
|
133
154
|
return this.request(path, {
|
|
134
|
-
attempt,
|
|
155
|
+
attempt: options?.attempt ?? 0,
|
|
135
156
|
body,
|
|
136
157
|
method: "POST"
|
|
137
158
|
});
|
|
138
159
|
}
|
|
139
|
-
async get(path,
|
|
160
|
+
async get(path, options) {
|
|
140
161
|
return this.request(path, {
|
|
141
|
-
attempt,
|
|
162
|
+
attempt: options?.attempt ?? 0,
|
|
142
163
|
method: "GET"
|
|
143
164
|
});
|
|
144
165
|
}
|
|
@@ -155,32 +176,10 @@ var HttpClient = class {
|
|
|
155
176
|
}
|
|
156
177
|
};
|
|
157
178
|
|
|
158
|
-
// src/
|
|
159
|
-
function
|
|
160
|
-
return {
|
|
161
|
-
create: async (name, data = {}) => {
|
|
162
|
-
return http.post("/events", { name, data, source: "api" });
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// src/plugins/identities/index.ts
|
|
168
|
-
function createIdentitiesModule(http) {
|
|
169
|
-
return {
|
|
170
|
-
create: async (distinctId, data = {}) => {
|
|
171
|
-
const result = await http.post("/identities", {
|
|
172
|
-
id: distinctId,
|
|
173
|
-
...data
|
|
174
|
-
});
|
|
175
|
-
setIdentityId(distinctId);
|
|
176
|
-
return result;
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// src/plugins/analytics/index.ts
|
|
182
|
-
function initAnalyticsPlugin(http) {
|
|
179
|
+
// src/modules/client/analytics/index.ts
|
|
180
|
+
function initAnalyticsModule(http, logger) {
|
|
183
181
|
if (typeof window === "undefined") return;
|
|
182
|
+
logger.log("Analytics module started");
|
|
184
183
|
let pageStartTime = Date.now();
|
|
185
184
|
let pageCount = 0;
|
|
186
185
|
let currentUrl = window.location.href;
|
|
@@ -234,15 +233,14 @@ function initAnalyticsPlugin(http) {
|
|
|
234
233
|
patchHistoryMethod("replaceState");
|
|
235
234
|
window.addEventListener("popstate", onRouteChange);
|
|
236
235
|
window.addEventListener("beforeunload", () => trackPageExit(true));
|
|
237
|
-
document.addEventListener(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
});
|
|
236
|
+
document.addEventListener(
|
|
237
|
+
"visibilitychange",
|
|
238
|
+
() => document.visibilityState === "hidden" && trackPageExit(true)
|
|
239
|
+
);
|
|
242
240
|
trackPageView();
|
|
243
241
|
}
|
|
244
242
|
|
|
245
|
-
// src/
|
|
243
|
+
// src/modules/client/error/helpers.ts
|
|
246
244
|
var EXTENSION_PREFIXES = [
|
|
247
245
|
"chrome-extension://",
|
|
248
246
|
"moz-extension://",
|
|
@@ -254,19 +252,24 @@ function isExtensionSource(str) {
|
|
|
254
252
|
const lower = str.toLowerCase();
|
|
255
253
|
return EXTENSION_PREFIXES.some((prefix) => lower.includes(prefix));
|
|
256
254
|
}
|
|
257
|
-
|
|
255
|
+
|
|
256
|
+
// src/modules/client/error/index.ts
|
|
257
|
+
function initErrorsModule(http, logger) {
|
|
258
258
|
if (typeof window === "undefined") return;
|
|
259
|
+
logger.log("Errors module started");
|
|
259
260
|
window.addEventListener("error", (event) => {
|
|
260
261
|
if (isExtensionSource(event.filename) || isExtensionSource(event.error?.stack))
|
|
261
262
|
return;
|
|
262
263
|
if (event.error === null && event.message === "Script error.") return;
|
|
263
264
|
void http.post("/v1/logs", {
|
|
264
265
|
message: event.message || "Unknown Error",
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
266
|
+
level: "error",
|
|
267
|
+
url: window.location.href,
|
|
268
|
+
source: "client",
|
|
269
|
+
metadata: {
|
|
270
|
+
stack: event.error?.stack,
|
|
271
|
+
type: event.error?.name
|
|
272
|
+
}
|
|
270
273
|
});
|
|
271
274
|
});
|
|
272
275
|
window.addEventListener(
|
|
@@ -287,19 +290,19 @@ function initErrorsPlugin(http) {
|
|
|
287
290
|
}
|
|
288
291
|
void http.post("/v1/logs", {
|
|
289
292
|
message,
|
|
290
|
-
|
|
291
|
-
|
|
293
|
+
url: window.location.href,
|
|
294
|
+
source: "client",
|
|
295
|
+
level: "error",
|
|
296
|
+
metadata: {
|
|
297
|
+
stack,
|
|
298
|
+
type: "UnhandledRejection"
|
|
299
|
+
}
|
|
292
300
|
});
|
|
293
301
|
}
|
|
294
302
|
);
|
|
295
303
|
}
|
|
296
304
|
|
|
297
|
-
// src/
|
|
298
|
-
function getRating(value, thresholds) {
|
|
299
|
-
if (value > thresholds[1]) return "poor";
|
|
300
|
-
if (value > thresholds[0]) return "needs-improvement";
|
|
301
|
-
return "good";
|
|
302
|
-
}
|
|
305
|
+
// src/modules/client/web-vitals/helpers.ts
|
|
303
306
|
function getActivationStart() {
|
|
304
307
|
const nav = performance.getEntriesByType(
|
|
305
308
|
"navigation"
|
|
@@ -331,14 +334,10 @@ function observeFCP(cb) {
|
|
|
331
334
|
const reported = /* @__PURE__ */ new Set();
|
|
332
335
|
observe("paint", (entries) => {
|
|
333
336
|
for (const entry of entries) {
|
|
334
|
-
if (entry.name === "first-contentful-paint" && !reported.has("
|
|
335
|
-
reported.add("
|
|
337
|
+
if (entry.name === "first-contentful-paint" && !reported.has("first-contentful-paint")) {
|
|
338
|
+
reported.add("first-contentful-paint");
|
|
336
339
|
const value = Math.max(entry.startTime - getActivationStart(), 0);
|
|
337
|
-
cb(
|
|
338
|
-
name: "FCP",
|
|
339
|
-
value: Math.round(value),
|
|
340
|
-
rating: getRating(value, [1800, 3e3])
|
|
341
|
-
});
|
|
340
|
+
cb("first-contentful-paint", Math.round(value));
|
|
342
341
|
}
|
|
343
342
|
}
|
|
344
343
|
});
|
|
@@ -350,11 +349,7 @@ function observeLCP(cb) {
|
|
|
350
349
|
const last = entries[entries.length - 1];
|
|
351
350
|
if (last) {
|
|
352
351
|
const value = Math.max(last.startTime - getActivationStart(), 0);
|
|
353
|
-
cb(
|
|
354
|
-
name: "LCP",
|
|
355
|
-
value: Math.round(value),
|
|
356
|
-
rating: getRating(value, [2500, 4e3])
|
|
357
|
-
});
|
|
352
|
+
cb("largest-contentful-paint", Math.round(value));
|
|
358
353
|
}
|
|
359
354
|
});
|
|
360
355
|
if (!po) return;
|
|
@@ -365,11 +360,7 @@ function observeLCP(cb) {
|
|
|
365
360
|
if (records.length > 0) {
|
|
366
361
|
const last = records[records.length - 1];
|
|
367
362
|
const value = Math.max(last.startTime - getActivationStart(), 0);
|
|
368
|
-
cb(
|
|
369
|
-
name: "LCP",
|
|
370
|
-
value: Math.round(value),
|
|
371
|
-
rating: getRating(value, [2500, 4e3])
|
|
372
|
-
});
|
|
363
|
+
cb("largest-contentful-paint", Math.round(value));
|
|
373
364
|
}
|
|
374
365
|
po.disconnect();
|
|
375
366
|
};
|
|
@@ -397,11 +388,7 @@ function observeCLS(cb) {
|
|
|
397
388
|
if (sessionValue > clsValue) {
|
|
398
389
|
clsValue = sessionValue;
|
|
399
390
|
const rounded = Math.round(clsValue * 1e4) / 1e4;
|
|
400
|
-
cb(
|
|
401
|
-
name: "CLS",
|
|
402
|
-
value: rounded,
|
|
403
|
-
rating: getRating(clsValue, [0.1, 0.25])
|
|
404
|
-
});
|
|
391
|
+
cb("cumulative-layout-shift", rounded);
|
|
405
392
|
}
|
|
406
393
|
}
|
|
407
394
|
});
|
|
@@ -410,11 +397,7 @@ function observeTTFB(cb) {
|
|
|
410
397
|
const nav = getNavEntry();
|
|
411
398
|
if (!nav) return;
|
|
412
399
|
const value = Math.max(nav.responseStart - getActivationStart(), 0);
|
|
413
|
-
cb(
|
|
414
|
-
name: "TTFB",
|
|
415
|
-
value: Math.round(value),
|
|
416
|
-
rating: getRating(value, [800, 1800])
|
|
417
|
-
});
|
|
400
|
+
cb("time-to-first-byte", Math.round(value));
|
|
418
401
|
}
|
|
419
402
|
function observeINP(cb) {
|
|
420
403
|
const interactions = /* @__PURE__ */ new Map();
|
|
@@ -430,11 +413,7 @@ function observeINP(cb) {
|
|
|
430
413
|
interactions.set(entry.interactionId, entry.duration);
|
|
431
414
|
if (entry.duration > worstDuration) {
|
|
432
415
|
worstDuration = entry.duration;
|
|
433
|
-
cb(
|
|
434
|
-
name: "INP",
|
|
435
|
-
value: Math.round(entry.duration),
|
|
436
|
-
rating: getRating(entry.duration, [200, 500])
|
|
437
|
-
});
|
|
416
|
+
cb("interaction-to-next-paint", Math.round(entry.duration));
|
|
438
417
|
}
|
|
439
418
|
}
|
|
440
419
|
}
|
|
@@ -452,7 +431,7 @@ function observeFPS(cb) {
|
|
|
452
431
|
if (performance.now() - start < duration) {
|
|
453
432
|
requestAnimationFrame(tick);
|
|
454
433
|
} else {
|
|
455
|
-
cb(
|
|
434
|
+
cb("frames-per-second", Math.round(frames / duration * 1e3));
|
|
456
435
|
}
|
|
457
436
|
};
|
|
458
437
|
if (document.readyState === "complete") {
|
|
@@ -463,65 +442,103 @@ function observeFPS(cb) {
|
|
|
463
442
|
});
|
|
464
443
|
}
|
|
465
444
|
}
|
|
466
|
-
function initWebVitals(
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
445
|
+
function initWebVitals() {
|
|
446
|
+
const isBrowser = typeof window !== "undefined";
|
|
447
|
+
const hasPerformanceObserver = typeof PerformanceObserver !== "undefined";
|
|
448
|
+
if (!isBrowser || !hasPerformanceObserver) return [];
|
|
449
|
+
const metrics = /* @__PURE__ */ new Map();
|
|
450
|
+
observeFCP((name, value) => metrics.set(name, value));
|
|
451
|
+
observeLCP((name, value) => metrics.set(name, value));
|
|
452
|
+
observeCLS((name, value) => metrics.set(name, value));
|
|
453
|
+
observeTTFB((name, value) => metrics.set(name, value));
|
|
454
|
+
observeINP((name, value) => metrics.set(name, value));
|
|
455
|
+
observeFPS((name, value) => metrics.set(name, value));
|
|
456
|
+
return Array.from(metrics).map(([name, value]) => ({ name, value }));
|
|
475
457
|
}
|
|
476
458
|
|
|
477
|
-
// src/
|
|
478
|
-
function
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
...metric.rating !== void 0 ? { rating: metric.rating } : {}
|
|
484
|
-
});
|
|
459
|
+
// src/modules/client/web-vitals/index.ts
|
|
460
|
+
function initWebVitalsModule(http, logger) {
|
|
461
|
+
logger.log("Web Vitals module started");
|
|
462
|
+
http.post("/v1/web-vitals", {
|
|
463
|
+
url: window.location.href,
|
|
464
|
+
metrics: initWebVitals()
|
|
485
465
|
});
|
|
486
466
|
}
|
|
487
467
|
|
|
488
|
-
// src/
|
|
489
|
-
function
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
468
|
+
// src/modules/identities/index.ts
|
|
469
|
+
function initIdentitiesModule(http, logger) {
|
|
470
|
+
logger.log("Identities module started");
|
|
471
|
+
return {
|
|
472
|
+
async identify(distinctId, data) {
|
|
473
|
+
const res = await http.post("/v1/identities", {
|
|
474
|
+
body: {
|
|
475
|
+
...data,
|
|
476
|
+
id: distinctId
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
setIdentityId(distinctId);
|
|
480
|
+
return res;
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/modules/server/events/index.ts
|
|
486
|
+
function initEventsModule(http, logger) {
|
|
487
|
+
logger.log("Events module started");
|
|
488
|
+
return {
|
|
489
|
+
track: async (name, data = {}) => {
|
|
490
|
+
return http.post("/v1/events", { name, data, source: "sdk" });
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/modules/server/feature-flags/index.ts
|
|
496
|
+
function initFeatureFlagsModule(http, logger) {
|
|
497
|
+
logger.log("Feature Flags module started");
|
|
498
|
+
return {
|
|
499
|
+
get: async (name) => {
|
|
500
|
+
return http.get(`/v1/feature-flags`, { searchParams: { key: name } });
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/modules/server/logs/index.ts
|
|
506
|
+
function initLogsModule(http, logger) {
|
|
507
|
+
logger.log("Logs module started");
|
|
508
|
+
return {
|
|
509
|
+
send: (payload) => http.post("/v1/logs", payload)
|
|
494
510
|
};
|
|
495
|
-
plugins.map((p) => initializers[p]);
|
|
496
511
|
}
|
|
497
512
|
|
|
498
513
|
// src/index.ts
|
|
499
|
-
var defaults = {
|
|
500
|
-
baseUrl: "https://dev.plugeen.app/api",
|
|
501
|
-
debug: false,
|
|
502
|
-
plugins: []
|
|
503
|
-
};
|
|
504
514
|
function createPlugeen(apiKey, options) {
|
|
505
515
|
const resolved = {
|
|
506
|
-
|
|
507
|
-
debug:
|
|
508
|
-
|
|
516
|
+
apiUrl: "https://dev.plugeen.app/api",
|
|
517
|
+
debug: false,
|
|
518
|
+
analytics: false,
|
|
519
|
+
webVitals: false,
|
|
520
|
+
errors: false,
|
|
521
|
+
...options
|
|
509
522
|
};
|
|
523
|
+
const logger = createLogger(resolved.debug);
|
|
510
524
|
if (!apiKey) {
|
|
511
|
-
throw new TypeError("[plugeen]
|
|
525
|
+
throw new TypeError("[plugeen] data-api-key is required");
|
|
512
526
|
}
|
|
513
|
-
const http = new HttpClient(resolved.
|
|
514
|
-
const events = createEventsModule(http);
|
|
515
|
-
const identities = createIdentitiesModule(http);
|
|
527
|
+
const http = new HttpClient(resolved.apiUrl, apiKey);
|
|
516
528
|
const plugeen = {
|
|
517
|
-
track: (
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
529
|
+
track: initEventsModule(http, logger).track,
|
|
530
|
+
identify: initIdentitiesModule(http, logger).identify,
|
|
531
|
+
featureFlags: initFeatureFlagsModule(http, logger),
|
|
532
|
+
logs: initLogsModule(http, logger)
|
|
521
533
|
};
|
|
522
|
-
if (
|
|
523
|
-
|
|
534
|
+
if (typeof window !== "undefined") {
|
|
535
|
+
if (resolved.analytics) initAnalyticsModule(http, logger);
|
|
536
|
+
if (resolved.webVitals) initWebVitalsModule(http, logger);
|
|
537
|
+
if (resolved.errors) initErrorsModule(http, logger);
|
|
524
538
|
}
|
|
539
|
+
logger.log(
|
|
540
|
+
`Running on ${typeof window === "undefined" ? "server" : "client"} side`
|
|
541
|
+
);
|
|
525
542
|
return plugeen;
|
|
526
543
|
}
|
|
527
544
|
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
declare const modules: readonly ["webVitals", "analytics", "errors"];
|
|
2
|
+
|
|
3
|
+
type IdentitySchema = {
|
|
4
|
+
id: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
email?: string;
|
|
7
|
+
metadata?: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
type EventsSchema = {
|
|
10
|
+
name: string;
|
|
11
|
+
source: "api" | "sdk";
|
|
12
|
+
data: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
type EventType = "page_view" | "page_exit";
|
|
15
|
+
type AnalyticsSchema = {
|
|
16
|
+
event: EventType;
|
|
17
|
+
url: string;
|
|
18
|
+
title: string;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
referrer?: string;
|
|
21
|
+
screenWidth: number;
|
|
22
|
+
screenHeight: number;
|
|
23
|
+
};
|
|
24
|
+
type LogLevel = "info" | "warn" | "error" | "debug";
|
|
25
|
+
type LogSource = "web" | "client";
|
|
26
|
+
type LogsSchema = {
|
|
27
|
+
level: LogLevel;
|
|
28
|
+
source: LogSource;
|
|
29
|
+
message: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
service?: string;
|
|
32
|
+
environment?: string;
|
|
33
|
+
http?: {
|
|
34
|
+
method: string;
|
|
35
|
+
status: number;
|
|
36
|
+
};
|
|
37
|
+
metadata?: Record<"stack" | "type", unknown>;
|
|
38
|
+
};
|
|
39
|
+
type FeatureFlagsSchema = {
|
|
40
|
+
key: string;
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
value?: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
};
|
|
45
|
+
declare const webVitalsMetrics: readonly ["cumulative-layout-shift", "interaction-to-next-paint", "first-contentful-paint", "largest-contentful-paint", "time-to-first-byte", "frames-per-second"];
|
|
46
|
+
type MetricName = (typeof webVitalsMetrics)[number];
|
|
47
|
+
type WebVitalsSchema = {
|
|
48
|
+
url: string;
|
|
49
|
+
metrics: {
|
|
50
|
+
name: MetricName;
|
|
51
|
+
value: number;
|
|
52
|
+
}[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type ClientModules = (typeof modules)[number];
|
|
56
|
+
type SdkOptions = {
|
|
57
|
+
apiUrl: string;
|
|
58
|
+
debug: boolean;
|
|
59
|
+
} & Record<ClientModules, boolean>;
|
|
60
|
+
type TrackFn = (event: string, data: Record<string, unknown>) => void;
|
|
61
|
+
type TupleResponse<T> = Promise<[T, null] | [null, unknown]>;
|
|
62
|
+
type EventsModule = {
|
|
63
|
+
track<T = unknown>(name: string, data?: Record<string, unknown>): TupleResponse<T>;
|
|
64
|
+
};
|
|
65
|
+
type IdentitiesModule = {
|
|
66
|
+
identify(distinctId: string, data?: Omit<IdentitySchema, "id">): TupleResponse<IdentitySchema>;
|
|
67
|
+
};
|
|
68
|
+
interface FeatureFlagsModule {
|
|
69
|
+
get(key: string): TupleResponse<FeatureFlagsSchema>;
|
|
70
|
+
}
|
|
71
|
+
interface LogsModule {
|
|
72
|
+
send(payload: LogsSchema): TupleResponse<FeatureFlagsSchema>;
|
|
73
|
+
}
|
|
74
|
+
interface Plugeen {
|
|
75
|
+
track: TrackFn;
|
|
76
|
+
identify: (distinctId: string, data: Omit<IdentitySchema, "id">) => TupleResponse<IdentitySchema>;
|
|
77
|
+
featureFlags: FeatureFlagsModule;
|
|
78
|
+
logs: LogsModule;
|
|
79
|
+
}
|
|
80
|
+
declare global {
|
|
81
|
+
interface Window {
|
|
82
|
+
plugeen?: Plugeen;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
declare function createPlugeen(apiKey: string, options: Partial<SdkOptions>): Plugeen;
|
|
87
|
+
|
|
88
|
+
export { type AnalyticsSchema, type EventsModule, type EventsSchema, type FeatureFlagsModule, type FeatureFlagsSchema, type IdentitiesModule, type IdentitySchema, type LogsModule, type LogsSchema, type MetricName, type Plugeen, type SdkOptions, type TrackFn, type TupleResponse, type WebVitalsSchema, createPlugeen, webVitalsMetrics };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
declare const modules: readonly ["webVitals", "analytics", "errors"];
|
|
2
|
+
|
|
3
|
+
type IdentitySchema = {
|
|
4
|
+
id: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
email?: string;
|
|
7
|
+
metadata?: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
type EventsSchema = {
|
|
10
|
+
name: string;
|
|
11
|
+
source: "api" | "sdk";
|
|
12
|
+
data: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
type EventType = "page_view" | "page_exit";
|
|
15
|
+
type AnalyticsSchema = {
|
|
16
|
+
event: EventType;
|
|
17
|
+
url: string;
|
|
18
|
+
title: string;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
referrer?: string;
|
|
21
|
+
screenWidth: number;
|
|
22
|
+
screenHeight: number;
|
|
23
|
+
};
|
|
24
|
+
type LogLevel = "info" | "warn" | "error" | "debug";
|
|
25
|
+
type LogSource = "web" | "client";
|
|
26
|
+
type LogsSchema = {
|
|
27
|
+
level: LogLevel;
|
|
28
|
+
source: LogSource;
|
|
29
|
+
message: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
service?: string;
|
|
32
|
+
environment?: string;
|
|
33
|
+
http?: {
|
|
34
|
+
method: string;
|
|
35
|
+
status: number;
|
|
36
|
+
};
|
|
37
|
+
metadata?: Record<"stack" | "type", unknown>;
|
|
38
|
+
};
|
|
39
|
+
type FeatureFlagsSchema = {
|
|
40
|
+
key: string;
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
value?: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
};
|
|
45
|
+
declare const webVitalsMetrics: readonly ["cumulative-layout-shift", "interaction-to-next-paint", "first-contentful-paint", "largest-contentful-paint", "time-to-first-byte", "frames-per-second"];
|
|
46
|
+
type MetricName = (typeof webVitalsMetrics)[number];
|
|
47
|
+
type WebVitalsSchema = {
|
|
48
|
+
url: string;
|
|
49
|
+
metrics: {
|
|
50
|
+
name: MetricName;
|
|
51
|
+
value: number;
|
|
52
|
+
}[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type ClientModules = (typeof modules)[number];
|
|
56
|
+
type SdkOptions = {
|
|
57
|
+
apiUrl: string;
|
|
58
|
+
debug: boolean;
|
|
59
|
+
} & Record<ClientModules, boolean>;
|
|
60
|
+
type TrackFn = (event: string, data: Record<string, unknown>) => void;
|
|
61
|
+
type TupleResponse<T> = Promise<[T, null] | [null, unknown]>;
|
|
62
|
+
type EventsModule = {
|
|
63
|
+
track<T = unknown>(name: string, data?: Record<string, unknown>): TupleResponse<T>;
|
|
64
|
+
};
|
|
65
|
+
type IdentitiesModule = {
|
|
66
|
+
identify(distinctId: string, data?: Omit<IdentitySchema, "id">): TupleResponse<IdentitySchema>;
|
|
67
|
+
};
|
|
68
|
+
interface FeatureFlagsModule {
|
|
69
|
+
get(key: string): TupleResponse<FeatureFlagsSchema>;
|
|
70
|
+
}
|
|
71
|
+
interface LogsModule {
|
|
72
|
+
send(payload: LogsSchema): TupleResponse<FeatureFlagsSchema>;
|
|
73
|
+
}
|
|
74
|
+
interface Plugeen {
|
|
75
|
+
track: TrackFn;
|
|
76
|
+
identify: (distinctId: string, data: Omit<IdentitySchema, "id">) => TupleResponse<IdentitySchema>;
|
|
77
|
+
featureFlags: FeatureFlagsModule;
|
|
78
|
+
logs: LogsModule;
|
|
79
|
+
}
|
|
80
|
+
declare global {
|
|
81
|
+
interface Window {
|
|
82
|
+
plugeen?: Plugeen;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
declare function createPlugeen(apiKey: string, options: Partial<SdkOptions>): Plugeen;
|
|
87
|
+
|
|
88
|
+
export { type AnalyticsSchema, type EventsModule, type EventsSchema, type FeatureFlagsModule, type FeatureFlagsSchema, type IdentitiesModule, type IdentitySchema, type LogsModule, type LogsSchema, type MetricName, type Plugeen, type SdkOptions, type TrackFn, type TupleResponse, type WebVitalsSchema, createPlugeen, webVitalsMetrics };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
// src/helpers/logger.ts
|
|
2
|
+
function createLogger(debug) {
|
|
3
|
+
if (!debug) {
|
|
4
|
+
return { log: () => {
|
|
5
|
+
}, warn: () => {
|
|
6
|
+
}, error: () => {
|
|
7
|
+
} };
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
// biome-ignore lint/suspicious/noConsole: Required
|
|
11
|
+
log: (...args) => console.log("[plugeen]", ...args),
|
|
12
|
+
// biome-ignore lint/suspicious/noConsole: Required
|
|
13
|
+
warn: (...args) => console.warn("[plugeen]", ...args),
|
|
14
|
+
// biome-ignore lint/suspicious/noConsole: Required
|
|
15
|
+
error: (...args) => console.error("[plugeen]", ...args)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
1
19
|
// src/helpers/uuid.ts
|
|
2
20
|
function generateUUID() {
|
|
3
21
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
@@ -17,7 +35,7 @@ function generateUUID() {
|
|
|
17
35
|
});
|
|
18
36
|
}
|
|
19
37
|
|
|
20
|
-
// src/storage.ts
|
|
38
|
+
// src/storage/index.ts
|
|
21
39
|
var IDENTITY_KEY = "plugeen_anon_id";
|
|
22
40
|
var SESSION_KEY = "plugeen_session_id";
|
|
23
41
|
var SESSION_TS_KEY = "plugeen_session_ts";
|
|
@@ -73,7 +91,7 @@ function getOrCreateSessionId() {
|
|
|
73
91
|
}
|
|
74
92
|
}
|
|
75
93
|
|
|
76
|
-
// src/
|
|
94
|
+
// src/http-client/index.ts
|
|
77
95
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
78
96
|
var HttpClient = class {
|
|
79
97
|
constructor(baseUrl, apiKey, maxRetries = 3) {
|
|
@@ -90,8 +108,9 @@ var HttpClient = class {
|
|
|
90
108
|
if (method !== "GET") h["Content-Type"] = "application/json";
|
|
91
109
|
return h;
|
|
92
110
|
}
|
|
93
|
-
async request(path, { attempt = 0, method, body }) {
|
|
94
|
-
const
|
|
111
|
+
async request(path, { attempt = 0, method, body, searchParams }) {
|
|
112
|
+
const sufix = searchParams ? `?${new URLSearchParams(searchParams).toString()}` : "";
|
|
113
|
+
const url = `${this.baseUrl}${path}${sufix}`;
|
|
95
114
|
try {
|
|
96
115
|
const res = await fetch(url, {
|
|
97
116
|
method,
|
|
@@ -122,21 +141,23 @@ var HttpClient = class {
|
|
|
122
141
|
if (err instanceof TypeError && attempt < this.maxRetries) {
|
|
123
142
|
const jitter = 0.85 + Math.random() * 0.3;
|
|
124
143
|
await delay(500 * 2 ** attempt * jitter);
|
|
125
|
-
return this.get(path,
|
|
144
|
+
return this.get(path, {
|
|
145
|
+
attempt: attempt + 1
|
|
146
|
+
});
|
|
126
147
|
}
|
|
127
148
|
return [null, err];
|
|
128
149
|
}
|
|
129
150
|
}
|
|
130
|
-
async post(path, body,
|
|
151
|
+
async post(path, body, options) {
|
|
131
152
|
return this.request(path, {
|
|
132
|
-
attempt,
|
|
153
|
+
attempt: options?.attempt ?? 0,
|
|
133
154
|
body,
|
|
134
155
|
method: "POST"
|
|
135
156
|
});
|
|
136
157
|
}
|
|
137
|
-
async get(path,
|
|
158
|
+
async get(path, options) {
|
|
138
159
|
return this.request(path, {
|
|
139
|
-
attempt,
|
|
160
|
+
attempt: options?.attempt ?? 0,
|
|
140
161
|
method: "GET"
|
|
141
162
|
});
|
|
142
163
|
}
|
|
@@ -153,32 +174,10 @@ var HttpClient = class {
|
|
|
153
174
|
}
|
|
154
175
|
};
|
|
155
176
|
|
|
156
|
-
// src/
|
|
157
|
-
function
|
|
158
|
-
return {
|
|
159
|
-
create: async (name, data = {}) => {
|
|
160
|
-
return http.post("/events", { name, data, source: "api" });
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// src/plugins/identities/index.ts
|
|
166
|
-
function createIdentitiesModule(http) {
|
|
167
|
-
return {
|
|
168
|
-
create: async (distinctId, data = {}) => {
|
|
169
|
-
const result = await http.post("/identities", {
|
|
170
|
-
id: distinctId,
|
|
171
|
-
...data
|
|
172
|
-
});
|
|
173
|
-
setIdentityId(distinctId);
|
|
174
|
-
return result;
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// src/plugins/analytics/index.ts
|
|
180
|
-
function initAnalyticsPlugin(http) {
|
|
177
|
+
// src/modules/client/analytics/index.ts
|
|
178
|
+
function initAnalyticsModule(http, logger) {
|
|
181
179
|
if (typeof window === "undefined") return;
|
|
180
|
+
logger.log("Analytics module started");
|
|
182
181
|
let pageStartTime = Date.now();
|
|
183
182
|
let pageCount = 0;
|
|
184
183
|
let currentUrl = window.location.href;
|
|
@@ -232,15 +231,14 @@ function initAnalyticsPlugin(http) {
|
|
|
232
231
|
patchHistoryMethod("replaceState");
|
|
233
232
|
window.addEventListener("popstate", onRouteChange);
|
|
234
233
|
window.addEventListener("beforeunload", () => trackPageExit(true));
|
|
235
|
-
document.addEventListener(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
});
|
|
234
|
+
document.addEventListener(
|
|
235
|
+
"visibilitychange",
|
|
236
|
+
() => document.visibilityState === "hidden" && trackPageExit(true)
|
|
237
|
+
);
|
|
240
238
|
trackPageView();
|
|
241
239
|
}
|
|
242
240
|
|
|
243
|
-
// src/
|
|
241
|
+
// src/modules/client/error/helpers.ts
|
|
244
242
|
var EXTENSION_PREFIXES = [
|
|
245
243
|
"chrome-extension://",
|
|
246
244
|
"moz-extension://",
|
|
@@ -252,19 +250,24 @@ function isExtensionSource(str) {
|
|
|
252
250
|
const lower = str.toLowerCase();
|
|
253
251
|
return EXTENSION_PREFIXES.some((prefix) => lower.includes(prefix));
|
|
254
252
|
}
|
|
255
|
-
|
|
253
|
+
|
|
254
|
+
// src/modules/client/error/index.ts
|
|
255
|
+
function initErrorsModule(http, logger) {
|
|
256
256
|
if (typeof window === "undefined") return;
|
|
257
|
+
logger.log("Errors module started");
|
|
257
258
|
window.addEventListener("error", (event) => {
|
|
258
259
|
if (isExtensionSource(event.filename) || isExtensionSource(event.error?.stack))
|
|
259
260
|
return;
|
|
260
261
|
if (event.error === null && event.message === "Script error.") return;
|
|
261
262
|
void http.post("/v1/logs", {
|
|
262
263
|
message: event.message || "Unknown Error",
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
264
|
+
level: "error",
|
|
265
|
+
url: window.location.href,
|
|
266
|
+
source: "client",
|
|
267
|
+
metadata: {
|
|
268
|
+
stack: event.error?.stack,
|
|
269
|
+
type: event.error?.name
|
|
270
|
+
}
|
|
268
271
|
});
|
|
269
272
|
});
|
|
270
273
|
window.addEventListener(
|
|
@@ -285,19 +288,19 @@ function initErrorsPlugin(http) {
|
|
|
285
288
|
}
|
|
286
289
|
void http.post("/v1/logs", {
|
|
287
290
|
message,
|
|
288
|
-
|
|
289
|
-
|
|
291
|
+
url: window.location.href,
|
|
292
|
+
source: "client",
|
|
293
|
+
level: "error",
|
|
294
|
+
metadata: {
|
|
295
|
+
stack,
|
|
296
|
+
type: "UnhandledRejection"
|
|
297
|
+
}
|
|
290
298
|
});
|
|
291
299
|
}
|
|
292
300
|
);
|
|
293
301
|
}
|
|
294
302
|
|
|
295
|
-
// src/
|
|
296
|
-
function getRating(value, thresholds) {
|
|
297
|
-
if (value > thresholds[1]) return "poor";
|
|
298
|
-
if (value > thresholds[0]) return "needs-improvement";
|
|
299
|
-
return "good";
|
|
300
|
-
}
|
|
303
|
+
// src/modules/client/web-vitals/helpers.ts
|
|
301
304
|
function getActivationStart() {
|
|
302
305
|
const nav = performance.getEntriesByType(
|
|
303
306
|
"navigation"
|
|
@@ -329,14 +332,10 @@ function observeFCP(cb) {
|
|
|
329
332
|
const reported = /* @__PURE__ */ new Set();
|
|
330
333
|
observe("paint", (entries) => {
|
|
331
334
|
for (const entry of entries) {
|
|
332
|
-
if (entry.name === "first-contentful-paint" && !reported.has("
|
|
333
|
-
reported.add("
|
|
335
|
+
if (entry.name === "first-contentful-paint" && !reported.has("first-contentful-paint")) {
|
|
336
|
+
reported.add("first-contentful-paint");
|
|
334
337
|
const value = Math.max(entry.startTime - getActivationStart(), 0);
|
|
335
|
-
cb(
|
|
336
|
-
name: "FCP",
|
|
337
|
-
value: Math.round(value),
|
|
338
|
-
rating: getRating(value, [1800, 3e3])
|
|
339
|
-
});
|
|
338
|
+
cb("first-contentful-paint", Math.round(value));
|
|
340
339
|
}
|
|
341
340
|
}
|
|
342
341
|
});
|
|
@@ -348,11 +347,7 @@ function observeLCP(cb) {
|
|
|
348
347
|
const last = entries[entries.length - 1];
|
|
349
348
|
if (last) {
|
|
350
349
|
const value = Math.max(last.startTime - getActivationStart(), 0);
|
|
351
|
-
cb(
|
|
352
|
-
name: "LCP",
|
|
353
|
-
value: Math.round(value),
|
|
354
|
-
rating: getRating(value, [2500, 4e3])
|
|
355
|
-
});
|
|
350
|
+
cb("largest-contentful-paint", Math.round(value));
|
|
356
351
|
}
|
|
357
352
|
});
|
|
358
353
|
if (!po) return;
|
|
@@ -363,11 +358,7 @@ function observeLCP(cb) {
|
|
|
363
358
|
if (records.length > 0) {
|
|
364
359
|
const last = records[records.length - 1];
|
|
365
360
|
const value = Math.max(last.startTime - getActivationStart(), 0);
|
|
366
|
-
cb(
|
|
367
|
-
name: "LCP",
|
|
368
|
-
value: Math.round(value),
|
|
369
|
-
rating: getRating(value, [2500, 4e3])
|
|
370
|
-
});
|
|
361
|
+
cb("largest-contentful-paint", Math.round(value));
|
|
371
362
|
}
|
|
372
363
|
po.disconnect();
|
|
373
364
|
};
|
|
@@ -395,11 +386,7 @@ function observeCLS(cb) {
|
|
|
395
386
|
if (sessionValue > clsValue) {
|
|
396
387
|
clsValue = sessionValue;
|
|
397
388
|
const rounded = Math.round(clsValue * 1e4) / 1e4;
|
|
398
|
-
cb(
|
|
399
|
-
name: "CLS",
|
|
400
|
-
value: rounded,
|
|
401
|
-
rating: getRating(clsValue, [0.1, 0.25])
|
|
402
|
-
});
|
|
389
|
+
cb("cumulative-layout-shift", rounded);
|
|
403
390
|
}
|
|
404
391
|
}
|
|
405
392
|
});
|
|
@@ -408,11 +395,7 @@ function observeTTFB(cb) {
|
|
|
408
395
|
const nav = getNavEntry();
|
|
409
396
|
if (!nav) return;
|
|
410
397
|
const value = Math.max(nav.responseStart - getActivationStart(), 0);
|
|
411
|
-
cb(
|
|
412
|
-
name: "TTFB",
|
|
413
|
-
value: Math.round(value),
|
|
414
|
-
rating: getRating(value, [800, 1800])
|
|
415
|
-
});
|
|
398
|
+
cb("time-to-first-byte", Math.round(value));
|
|
416
399
|
}
|
|
417
400
|
function observeINP(cb) {
|
|
418
401
|
const interactions = /* @__PURE__ */ new Map();
|
|
@@ -428,11 +411,7 @@ function observeINP(cb) {
|
|
|
428
411
|
interactions.set(entry.interactionId, entry.duration);
|
|
429
412
|
if (entry.duration > worstDuration) {
|
|
430
413
|
worstDuration = entry.duration;
|
|
431
|
-
cb(
|
|
432
|
-
name: "INP",
|
|
433
|
-
value: Math.round(entry.duration),
|
|
434
|
-
rating: getRating(entry.duration, [200, 500])
|
|
435
|
-
});
|
|
414
|
+
cb("interaction-to-next-paint", Math.round(entry.duration));
|
|
436
415
|
}
|
|
437
416
|
}
|
|
438
417
|
}
|
|
@@ -450,7 +429,7 @@ function observeFPS(cb) {
|
|
|
450
429
|
if (performance.now() - start < duration) {
|
|
451
430
|
requestAnimationFrame(tick);
|
|
452
431
|
} else {
|
|
453
|
-
cb(
|
|
432
|
+
cb("frames-per-second", Math.round(frames / duration * 1e3));
|
|
454
433
|
}
|
|
455
434
|
};
|
|
456
435
|
if (document.readyState === "complete") {
|
|
@@ -461,65 +440,103 @@ function observeFPS(cb) {
|
|
|
461
440
|
});
|
|
462
441
|
}
|
|
463
442
|
}
|
|
464
|
-
function initWebVitals(
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
443
|
+
function initWebVitals() {
|
|
444
|
+
const isBrowser = typeof window !== "undefined";
|
|
445
|
+
const hasPerformanceObserver = typeof PerformanceObserver !== "undefined";
|
|
446
|
+
if (!isBrowser || !hasPerformanceObserver) return [];
|
|
447
|
+
const metrics = /* @__PURE__ */ new Map();
|
|
448
|
+
observeFCP((name, value) => metrics.set(name, value));
|
|
449
|
+
observeLCP((name, value) => metrics.set(name, value));
|
|
450
|
+
observeCLS((name, value) => metrics.set(name, value));
|
|
451
|
+
observeTTFB((name, value) => metrics.set(name, value));
|
|
452
|
+
observeINP((name, value) => metrics.set(name, value));
|
|
453
|
+
observeFPS((name, value) => metrics.set(name, value));
|
|
454
|
+
return Array.from(metrics).map(([name, value]) => ({ name, value }));
|
|
473
455
|
}
|
|
474
456
|
|
|
475
|
-
// src/
|
|
476
|
-
function
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
...metric.rating !== void 0 ? { rating: metric.rating } : {}
|
|
482
|
-
});
|
|
457
|
+
// src/modules/client/web-vitals/index.ts
|
|
458
|
+
function initWebVitalsModule(http, logger) {
|
|
459
|
+
logger.log("Web Vitals module started");
|
|
460
|
+
http.post("/v1/web-vitals", {
|
|
461
|
+
url: window.location.href,
|
|
462
|
+
metrics: initWebVitals()
|
|
483
463
|
});
|
|
484
464
|
}
|
|
485
465
|
|
|
486
|
-
// src/
|
|
487
|
-
function
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
466
|
+
// src/modules/identities/index.ts
|
|
467
|
+
function initIdentitiesModule(http, logger) {
|
|
468
|
+
logger.log("Identities module started");
|
|
469
|
+
return {
|
|
470
|
+
async identify(distinctId, data) {
|
|
471
|
+
const res = await http.post("/v1/identities", {
|
|
472
|
+
body: {
|
|
473
|
+
...data,
|
|
474
|
+
id: distinctId
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
setIdentityId(distinctId);
|
|
478
|
+
return res;
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/modules/server/events/index.ts
|
|
484
|
+
function initEventsModule(http, logger) {
|
|
485
|
+
logger.log("Events module started");
|
|
486
|
+
return {
|
|
487
|
+
track: async (name, data = {}) => {
|
|
488
|
+
return http.post("/v1/events", { name, data, source: "sdk" });
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/modules/server/feature-flags/index.ts
|
|
494
|
+
function initFeatureFlagsModule(http, logger) {
|
|
495
|
+
logger.log("Feature Flags module started");
|
|
496
|
+
return {
|
|
497
|
+
get: async (name) => {
|
|
498
|
+
return http.get(`/v1/feature-flags`, { searchParams: { key: name } });
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/modules/server/logs/index.ts
|
|
504
|
+
function initLogsModule(http, logger) {
|
|
505
|
+
logger.log("Logs module started");
|
|
506
|
+
return {
|
|
507
|
+
send: (payload) => http.post("/v1/logs", payload)
|
|
492
508
|
};
|
|
493
|
-
plugins.map((p) => initializers[p]);
|
|
494
509
|
}
|
|
495
510
|
|
|
496
511
|
// src/index.ts
|
|
497
|
-
var defaults = {
|
|
498
|
-
baseUrl: "https://dev.plugeen.app/api",
|
|
499
|
-
debug: false,
|
|
500
|
-
plugins: []
|
|
501
|
-
};
|
|
502
512
|
function createPlugeen(apiKey, options) {
|
|
503
513
|
const resolved = {
|
|
504
|
-
|
|
505
|
-
debug:
|
|
506
|
-
|
|
514
|
+
apiUrl: "https://dev.plugeen.app/api",
|
|
515
|
+
debug: false,
|
|
516
|
+
analytics: false,
|
|
517
|
+
webVitals: false,
|
|
518
|
+
errors: false,
|
|
519
|
+
...options
|
|
507
520
|
};
|
|
521
|
+
const logger = createLogger(resolved.debug);
|
|
508
522
|
if (!apiKey) {
|
|
509
|
-
throw new TypeError("[plugeen]
|
|
523
|
+
throw new TypeError("[plugeen] data-api-key is required");
|
|
510
524
|
}
|
|
511
|
-
const http = new HttpClient(resolved.
|
|
512
|
-
const events = createEventsModule(http);
|
|
513
|
-
const identities = createIdentitiesModule(http);
|
|
525
|
+
const http = new HttpClient(resolved.apiUrl, apiKey);
|
|
514
526
|
const plugeen = {
|
|
515
|
-
track: (
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
527
|
+
track: initEventsModule(http, logger).track,
|
|
528
|
+
identify: initIdentitiesModule(http, logger).identify,
|
|
529
|
+
featureFlags: initFeatureFlagsModule(http, logger),
|
|
530
|
+
logs: initLogsModule(http, logger)
|
|
519
531
|
};
|
|
520
|
-
if (
|
|
521
|
-
|
|
532
|
+
if (typeof window !== "undefined") {
|
|
533
|
+
if (resolved.analytics) initAnalyticsModule(http, logger);
|
|
534
|
+
if (resolved.webVitals) initWebVitalsModule(http, logger);
|
|
535
|
+
if (resolved.errors) initErrorsModule(http, logger);
|
|
522
536
|
}
|
|
537
|
+
logger.log(
|
|
538
|
+
`Running on ${typeof window === "undefined" ? "server" : "client"} side`
|
|
539
|
+
);
|
|
523
540
|
return plugeen;
|
|
524
541
|
}
|
|
525
542
|
|
package/dist/plugeen.global.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/* plugeen v0.1.0 | https://plugeen.app */
|
|
2
|
-
"use strict";var plugeen=(()=>{function
|
|
2
|
+
"use strict";var plugeen=(()=>{function S(){if(typeof document>"u")return null;let t=document.currentScript;if(!t){let i=document.getElementsByTagName("script");for(let s of Array.from(i))if(s.src&&(s.src.includes("/plugeen.global")||s.src.includes("/plugeen."))){t=s;break}}if(!t)return null;let n={},e=t.getAttribute("data-api-key")??void 0,r=t.getAttribute("data-api-url");r&&(n.apiUrl=r),n.analytics=t.getAttribute("data-analytics")==="true",n.webVitals=t.getAttribute("data-web-vitals")==="true",n.errors=t.getAttribute("data-errors")==="true";let o=t.getAttribute("data-debug");return o!==null&&(n.debug=o==="true"||o===""),{apiKey:e,...n}}function h(t){return t?{log:(...n)=>console.log("[plugeen]",...n),warn:(...n)=>console.warn("[plugeen]",...n),error:(...n)=>console.error("[plugeen]",...n)}:{log:()=>{},warn:()=>{},error:()=>{}}}function f(){if(typeof crypto<"u"&&typeof crypto.randomUUID=="function")return crypto.randomUUID();if(typeof crypto<"u"&&typeof crypto.getRandomValues=="function"){let t=new Uint8Array(16);crypto.getRandomValues(t),t[6]=t[6]&15|64,t[8]=t[8]&63|128;let n=Array.from(t).map(e=>e.toString(16).padStart(2,"0")).join("");return`${n.slice(0,8)}-${n.slice(8,12)}-${n.slice(12,16)}-${n.slice(16,20)}-${n.slice(20)}`}return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let n=Math.floor(Math.random()*16);return(t==="x"?n:n&3|8).toString(16)})}var p="plugeen_anon_id",g="plugeen_session_id",d="plugeen_session_ts";var P=18e5,c={};function E(){try{let t=localStorage.getItem(p);return t||(t=`anon_${f()}`,localStorage.setItem(p,t)),t}catch{return c[p]||(c[p]=`anon_${f()}`),c[p]}}function I(t){try{localStorage.setItem(p,t)}catch{c[p]=t}}function m(){try{let t=sessionStorage.getItem(g),n=sessionStorage.getItem(d);if(t&&n&&Date.now()-parseInt(n,10)<P)return sessionStorage.setItem(d,String(Date.now())),t;sessionStorage.removeItem(g),sessionStorage.removeItem(d);let e=`sess_${f()}`;return sessionStorage.setItem(g,e),sessionStorage.setItem(d,String(Date.now())),e}catch{let t=c[g],n=c[d];if(t&&n&&Date.now()-parseInt(n,10)<P)return c[d]=String(Date.now()),t;let e=`sess_${f()}`;return c[g]=e,c[d]=String(Date.now()),e}}var k=t=>new Promise(n=>setTimeout(n,t)),w=class{constructor(n,e,r=3){this.baseUrl=n;this.apiKey=e;this.maxRetries=r}headers(n){let e={Authorization:`Bearer ${this.apiKey}`,"x-identity-id":E(),"x-session-id":m()};return n!=="GET"&&(e["Content-Type"]="application/json"),e}async request(n,{attempt:e=0,method:r,body:o,searchParams:i}){let s=i?`?${new URLSearchParams(i).toString()}`:"",u=`${this.baseUrl}${n}${s}`;try{let a=await fetch(u,{method:r,headers:this.headers(r),credentials:"omit",body:o?JSON.stringify(o):void 0});if(a.status===401||a.status===404)return[null,a.status];if((a.status>=500||a.status===429)&&e<this.maxRetries){let l=.85+Math.random()*.3;return await k(500*2**e*l),this.request(n,{body:o,attempt:e+1,method:r})}if(a.status>=200&&a.status<300)try{return[(await a.json()).data,null]}catch{return[null,"Failed to parse json"]}return[null,"Error"]}catch(a){if(a instanceof TypeError&&e<this.maxRetries){let l=.85+Math.random()*.3;return await k(500*2**e*l),this.get(n,{attempt:e+1})}return[null,a]}}async post(n,e,r){return this.request(n,{attempt:r?.attempt??0,body:e,method:"POST"})}async get(n,e){return this.request(n,{attempt:e?.attempt??0,method:"GET"})}beacon(n,e){if(typeof navigator>"u"||!navigator.sendBeacon)return!1;try{let r=new Blob([JSON.stringify(e)],{type:"application/json"});return navigator.sendBeacon(`${this.baseUrl}${n}`,r)}catch{return!1}}};function T(t,n){if(typeof window>"u")return;n.log("Analytics module started");let e=Date.now(),r=0,o=window.location.href,i=()=>{r++,t.post("/v1/analytics",{event:"page_view",url:window.location.href,title:document.title,sessionId:m(),referrer:document.referrer||void 0,screenWidth:window.innerWidth,screenHeight:window.innerHeight})},s=(l=!1)=>{let y={event:"page_exit",url:window.location.href,title:document.title,sessionId:m(),referrer:document.referrer||void 0,screenWidth:window.innerWidth,screenHeight:window.innerHeight,metadata:{timeOnPage:Math.round((Date.now()-e)/1e3),pageCount:r}};l?t.beacon("/v1/analytics",y):t.post("/v1/analytics",y)},u=()=>{window.location.href!==o&&(s(),e=Date.now(),o=window.location.href,i())},a=l=>{let y=history[l].bind(history);history[l]=(...F)=>{y(...F),u()}};a("pushState"),a("replaceState"),window.addEventListener("popstate",u),window.addEventListener("beforeunload",()=>s(!0)),document.addEventListener("visibilitychange",()=>document.visibilityState==="hidden"&&s(!0)),i()}var V=["chrome-extension://","moz-extension://","safari-extension://","edge-extension://"];function v(t){if(!t)return!1;let n=t.toLowerCase();return V.some(e=>n.includes(e))}function M(t,n){typeof window>"u"||(n.log("Errors module started"),window.addEventListener("error",e=>{v(e.filename)||v(e.error?.stack)||e.error===null&&e.message==="Script error."||t.post("/v1/logs",{message:e.message||"Unknown Error",level:"error",url:window.location.href,source:"client",metadata:{stack:e.error?.stack,type:e.error?.name}})}),window.addEventListener("unhandledrejection",e=>{let{reason:r}=e;if(v(r?.stack))return;let o="Unknown Error",i;r instanceof Error?(o=r.message,i=r.stack):typeof r=="string"?o=r:r!==null&&typeof r=="object"&&"message"in r&&(o=String(r.message)),t.post("/v1/logs",{message:o,url:window.location.href,source:"client",level:"error",metadata:{stack:i,type:"UnhandledRejection"}})}))}function x(){return performance.getEntriesByType("navigation")[0]?.activationStart??0}function H(){let t=performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStart<performance.now())return t}function b(t,n,e){try{if(!PerformanceObserver.supportedEntryTypes.includes(t))return;let r=new PerformanceObserver(o=>{Promise.resolve().then(()=>n(o.getEntries()))});return r.observe({type:t,buffered:!0,...e??{}}),r}catch{return}}function D(t){let n=new Set;b("paint",e=>{for(let r of e)if(r.name==="first-contentful-paint"&&!n.has("first-contentful-paint")){n.add("first-contentful-paint");let o=Math.max(r.startTime-x(),0);t("first-contentful-paint",Math.round(o))}})}function N(t){let n=!1,e=b("largest-contentful-paint",o=>{if(n)return;let i=o[o.length-1];if(i){let s=Math.max(i.startTime-x(),0);t("largest-contentful-paint",Math.round(s))}});if(!e)return;let r=()=>{if(n)return;n=!0;let o=e.takeRecords();if(o.length>0){let i=o[o.length-1],s=Math.max(i.startTime-x(),0);t("largest-contentful-paint",Math.round(s))}e.disconnect()};for(let o of["keydown","click","visibilitychange"])addEventListener(o,r,{capture:!0,once:!0})}function $(t){let n=0,e=[],r=0;b("layout-shift",o=>{for(let i of o){let s=i;if(s.hadRecentInput)continue;let u=e[e.length-1],a=e[0];if(e.length>0&&u&&a&&s.startTime-u.startTime<1e3&&s.startTime-a.startTime<5e3?(n+=s.value,e.push(s)):(n=s.value,e=[s]),n>r){r=n;let l=Math.round(r*1e4)/1e4;t("cumulative-layout-shift",l)}}})}function j(t){let n=H();if(!n)return;let e=Math.max(n.responseStart-x(),0);t("time-to-first-byte",Math.round(e))}function W(t){let n=new Map,e=0;b("event",r=>{for(let o of r){let i=o;if(!i.interactionId)continue;let s=n.get(i.interactionId)??0;i.duration>s&&(n.set(i.interactionId,i.duration),i.duration>e&&(e=i.duration,t("interaction-to-next-paint",Math.round(i.duration))))}},{durationThreshold:40})}function B(t){if(typeof requestAnimationFrame>"u")return;let n=0,e=2e3,r=performance.now(),o=()=>{n++,performance.now()-r<e?requestAnimationFrame(o):t("frames-per-second",Math.round(n/e*1e3))};document.readyState==="complete"?requestAnimationFrame(o):window.addEventListener("load",()=>requestAnimationFrame(o),{once:!0})}function L(){if(!(typeof window<"u")||!(typeof PerformanceObserver<"u"))return[];let e=new Map;return D((r,o)=>e.set(r,o)),N((r,o)=>e.set(r,o)),$((r,o)=>e.set(r,o)),j((r,o)=>e.set(r,o)),W((r,o)=>e.set(r,o)),B((r,o)=>e.set(r,o)),Array.from(e).map(([r,o])=>({name:r,value:o}))}function O(t,n){n.log("Web Vitals module started"),t.post("/v1/web-vitals",{url:window.location.href,metrics:L()})}function C(t,n){return n.log("Identities module started"),{async identify(e,r){let o=await t.post("/v1/identities",{body:{...r,id:e}});return I(e),o}}}function _(t,n){return n.log("Events module started"),{track:async(e,r={})=>t.post("/v1/events",{name:e,data:r,source:"sdk"})}}function R(t,n){return n.log("Feature Flags module started"),{get:async e=>t.get("/v1/feature-flags",{searchParams:{key:e}})}}function U(t,n){return n.log("Logs module started"),{send:e=>t.post("/v1/logs",e)}}function A(t,n){let e={apiUrl:"https://dev.plugeen.app/api",debug:!1,analytics:!1,webVitals:!1,errors:!1,...n},r=h(e.debug);if(!t)throw new TypeError("[plugeen] data-api-key is required");let o=new w(e.apiUrl,t),i={track:_(o,r).track,identify:C(o,r).identify,featureFlags:R(o,r),logs:U(o,r)};return typeof window<"u"&&(e.analytics&&T(o,r),e.webVitals&&O(o,r),e.errors&&M(o,r)),r.log(`Running on ${typeof window>"u"?"server":"client"} side`),i}function q(){let t=S(),n=h(!!t?.debug);if(t){if(!t.apiKey)return n.warn("Data-api-key is required.")}else return n.warn("Failed to start script");n.log("Started");let e=A(t.apiKey,t);return window.plugeen=e,e}window.plugeen||q();})();
|