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 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/helpers/http-client/index.ts
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 url = `${this.baseUrl}${path}`;
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, attempt + 1);
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, attempt = 0) {
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, attempt = 0) {
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/plugins/events/index.ts
159
- function createEventsModule(http) {
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("visibilitychange", () => {
238
- if (document.visibilityState === "hidden") {
239
- trackPageExit(true);
240
- }
241
- });
236
+ document.addEventListener(
237
+ "visibilitychange",
238
+ () => document.visibilityState === "hidden" && trackPageExit(true)
239
+ );
242
240
  trackPageView();
243
241
  }
244
242
 
245
- // src/plugins/logs/index.ts
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
- function initErrorsPlugin(http) {
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
- filename: event.filename,
266
- lineno: event.lineno,
267
- colno: event.colno,
268
- stack: event.error?.stack,
269
- error_type: event.error?.name || "Error"
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
- stack,
291
- error_type: "UnhandledRejection"
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/plugins/web-vitals/helpers.ts
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("FCP")) {
335
- reported.add("FCP");
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({ name: "FPS", value: Math.round(frames / duration * 1e3) });
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(cb) {
467
- if (typeof window === "undefined" || typeof PerformanceObserver === "undefined")
468
- return;
469
- observeFCP(cb);
470
- observeLCP(cb);
471
- observeCLS(cb);
472
- observeTTFB(cb);
473
- observeINP(cb);
474
- observeFPS(cb);
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/plugins/web-vitals/index.ts
478
- function initWebVitalsPlugin(http) {
479
- initWebVitals((metric) => {
480
- http.post("/v1/web-vitals", {
481
- name: metric.name,
482
- value: metric.value,
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/plugins/index.ts
489
- function initPlugins(http, plugins) {
490
- const initializers = {
491
- analytics: initAnalyticsPlugin(http),
492
- "web-vitals": initWebVitalsPlugin(http),
493
- errors: initErrorsPlugin(http)
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
- baseUrl: options.baseUrl || defaults.baseUrl,
507
- debug: options.debug || defaults.debug,
508
- plugins: options.plugins || defaults.plugins
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] apiKey is required");
525
+ throw new TypeError("[plugeen] data-api-key is required");
512
526
  }
513
- const http = new HttpClient(resolved.baseUrl, apiKey);
514
- const events = createEventsModule(http);
515
- const identities = createIdentitiesModule(http);
527
+ const http = new HttpClient(resolved.apiUrl, apiKey);
516
528
  const plugeen = {
517
- track: (event, properties) => {
518
- void events.create(event, properties);
519
- },
520
- identify: (userId, data) => identities.create(userId, data)
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 (resolved.plugins.length > 0) {
523
- initPlugins(http, resolved.plugins);
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
 
@@ -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 };
@@ -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/helpers/http-client/index.ts
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 url = `${this.baseUrl}${path}`;
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, attempt + 1);
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, attempt = 0) {
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, attempt = 0) {
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/plugins/events/index.ts
157
- function createEventsModule(http) {
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("visibilitychange", () => {
236
- if (document.visibilityState === "hidden") {
237
- trackPageExit(true);
238
- }
239
- });
234
+ document.addEventListener(
235
+ "visibilitychange",
236
+ () => document.visibilityState === "hidden" && trackPageExit(true)
237
+ );
240
238
  trackPageView();
241
239
  }
242
240
 
243
- // src/plugins/logs/index.ts
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
- function initErrorsPlugin(http) {
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
- filename: event.filename,
264
- lineno: event.lineno,
265
- colno: event.colno,
266
- stack: event.error?.stack,
267
- error_type: event.error?.name || "Error"
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
- stack,
289
- error_type: "UnhandledRejection"
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/plugins/web-vitals/helpers.ts
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("FCP")) {
333
- reported.add("FCP");
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({ name: "FPS", value: Math.round(frames / duration * 1e3) });
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(cb) {
465
- if (typeof window === "undefined" || typeof PerformanceObserver === "undefined")
466
- return;
467
- observeFCP(cb);
468
- observeLCP(cb);
469
- observeCLS(cb);
470
- observeTTFB(cb);
471
- observeINP(cb);
472
- observeFPS(cb);
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/plugins/web-vitals/index.ts
476
- function initWebVitalsPlugin(http) {
477
- initWebVitals((metric) => {
478
- http.post("/v1/web-vitals", {
479
- name: metric.name,
480
- value: metric.value,
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/plugins/index.ts
487
- function initPlugins(http, plugins) {
488
- const initializers = {
489
- analytics: initAnalyticsPlugin(http),
490
- "web-vitals": initWebVitalsPlugin(http),
491
- errors: initErrorsPlugin(http)
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
- baseUrl: options.baseUrl || defaults.baseUrl,
505
- debug: options.debug || defaults.debug,
506
- plugins: options.plugins || defaults.plugins
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] apiKey is required");
523
+ throw new TypeError("[plugeen] data-api-key is required");
510
524
  }
511
- const http = new HttpClient(resolved.baseUrl, apiKey);
512
- const events = createEventsModule(http);
513
- const identities = createIdentitiesModule(http);
525
+ const http = new HttpClient(resolved.apiUrl, apiKey);
514
526
  const plugeen = {
515
- track: (event, properties) => {
516
- void events.create(event, properties);
517
- },
518
- identify: (userId, data) => identities.create(userId, data)
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 (resolved.plugins.length > 0) {
521
- initPlugins(http, resolved.plugins);
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
 
@@ -1,2 +1,2 @@
1
1
  /* plugeen v0.1.0 | https://plugeen.app */
2
- "use strict";var plugeen=(()=>{function P(){if(typeof document>"u")return null;let t=document.currentScript,e="";if(!t){let a=document.getElementsByTagName("script");for(let u of Array.from(a))if(u.src&&(u.src.includes("/plugeen.global")||u.src.includes("/plugeen."))){t=u;break}}if(!t)return null;let n={},r=t.getAttribute("data-api-key");r&&(e=r);let i=t.getAttribute("data-api-url");i&&(n.baseUrl=i);let s=t.getAttribute("data-plugins");s&&(n.plugins=s.split(",").map(a=>a.trim()).filter(Boolean));let o=t.getAttribute("data-debug");return o!==null&&(n.debug=o==="true"||o===""),{apiKey:e,...n}}function m(){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 e=Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join("");return`${e.slice(0,8)}-${e.slice(8,12)}-${e.slice(12,16)}-${e.slice(16,20)}-${e.slice(20)}`}return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=Math.floor(Math.random()*16);return(t==="x"?e:e&3|8).toString(16)})}var d="plugeen_anon_id",g="plugeen_session_id",c="plugeen_session_ts";var I=18e5,l={};function E(){try{let t=localStorage.getItem(d);return t||(t=`anon_${m()}`,localStorage.setItem(d,t)),t}catch{return l[d]||(l[d]=`anon_${m()}`),l[d]}}function T(t){try{localStorage.setItem(d,t)}catch{l[d]=t}}function y(){try{let t=sessionStorage.getItem(g),e=sessionStorage.getItem(c);if(t&&e&&Date.now()-parseInt(e,10)<I)return sessionStorage.setItem(c,String(Date.now())),t;sessionStorage.removeItem(g),sessionStorage.removeItem(c);let n=`sess_${m()}`;return sessionStorage.setItem(g,n),sessionStorage.setItem(c,String(Date.now())),n}catch{let t=l[g],e=l[c];if(t&&e&&Date.now()-parseInt(e,10)<I)return l[c]=String(Date.now()),t;let n=`sess_${m()}`;return l[g]=n,l[c]=String(Date.now()),n}}var k=t=>new Promise(e=>setTimeout(e,t)),h=class{constructor(e,n,r=3){this.baseUrl=e;this.apiKey=n;this.maxRetries=r}headers(e){let n={Authorization:`Bearer ${this.apiKey}`,"x-identity-id":E(),"x-session-id":y()};return e!=="GET"&&(n["Content-Type"]="application/json"),n}async request(e,{attempt:n=0,method:r,body:i}){let s=`${this.baseUrl}${e}`;try{let o=await fetch(s,{method:r,headers:this.headers(r),credentials:"omit",body:i?JSON.stringify(i):void 0});if(o.status===401||o.status===404)return[null,o.status];if((o.status>=500||o.status===429)&&n<this.maxRetries){let a=.85+Math.random()*.3;return await k(500*2**n*a),this.request(e,{body:i,attempt:n+1,method:r})}if(o.status>=200&&o.status<300)try{return[(await o.json()).data,null]}catch{return[null,"Failed to parse json"]}return[null,"Error"]}catch(o){if(o instanceof TypeError&&n<this.maxRetries){let a=.85+Math.random()*.3;return await k(500*2**n*a),this.get(e,n+1)}return[null,o]}}async post(e,n,r=0){return this.request(e,{attempt:r,body:n,method:"POST"})}async get(e,n=0){return this.request(e,{attempt:n,method:"GET"})}beacon(e,n){if(typeof navigator>"u"||!navigator.sendBeacon)return!1;try{let r=new Blob([JSON.stringify(n)],{type:"application/json"});return navigator.sendBeacon(`${this.baseUrl}${e}`,r)}catch{return!1}}};function C(t){return{create:async(e,n={})=>t.post("/events",{name:e,data:n,source:"api"})}}function O(t){return{create:async(e,n={})=>{let r=await t.post("/identities",{id:e,...n});return T(e),r}}}function _(t){if(typeof window>"u")return;let e=Date.now(),n=0,r=window.location.href,i=()=>{n++,t.post("/v1/analytics",{event:"page_view",url:window.location.href,title:document.title,sessionId:y(),referrer:document.referrer||void 0,screenWidth:window.innerWidth,screenHeight:window.innerHeight})},s=(u=!1)=>{let p={event:"page_exit",url:window.location.href,title:document.title,sessionId:y(),referrer:document.referrer||void 0,screenWidth:window.innerWidth,screenHeight:window.innerHeight,metadata:{timeOnPage:Math.round((Date.now()-e)/1e3),pageCount:n}};u?t.beacon("/v1/analytics",p):t.post("/v1/analytics",p)},o=()=>{window.location.href!==r&&(s(),e=Date.now(),r=window.location.href,i())},a=u=>{let p=history[u].bind(history);history[u]=(...F)=>{p(...F),o()}};a("pushState"),a("replaceState"),window.addEventListener("popstate",o),window.addEventListener("beforeunload",()=>s(!0)),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&s(!0)}),i()}var A=["chrome-extension://","moz-extension://","safari-extension://","edge-extension://"];function b(t){if(!t)return!1;let e=t.toLowerCase();return A.some(n=>e.includes(n))}function U(t){typeof window>"u"||(window.addEventListener("error",e=>{b(e.filename)||b(e.error?.stack)||e.error===null&&e.message==="Script error."||t.post("/v1/logs",{message:e.message||"Unknown Error",filename:e.filename,lineno:e.lineno,colno:e.colno,stack:e.error?.stack,error_type:e.error?.name||"Error"})}),window.addEventListener("unhandledrejection",e=>{let{reason:n}=e;if(b(n?.stack))return;let r="Unknown Error",i;n instanceof Error?(r=n.message,i=n.stack):typeof n=="string"?r=n:n!==null&&typeof n=="object"&&"message"in n&&(r=String(n.message)),t.post("/v1/logs",{message:r,stack:i,error_type:"UnhandledRejection"})}))}function f(t,e){return t>e[1]?"poor":t>e[0]?"needs-improvement":"good"}function v(){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 w(t,e,n){try{if(!PerformanceObserver.supportedEntryTypes.includes(t))return;let r=new PerformanceObserver(i=>{Promise.resolve().then(()=>e(i.getEntries()))});return r.observe({type:t,buffered:!0,...n??{}}),r}catch{return}}function D(t){let e=new Set;w("paint",n=>{for(let r of n)if(r.name==="first-contentful-paint"&&!e.has("FCP")){e.add("FCP");let i=Math.max(r.startTime-v(),0);t({name:"FCP",value:Math.round(i),rating:f(i,[1800,3e3])})}})}function V(t){let e=!1,n=w("largest-contentful-paint",i=>{if(e)return;let s=i[i.length-1];if(s){let o=Math.max(s.startTime-v(),0);t({name:"LCP",value:Math.round(o),rating:f(o,[2500,4e3])})}});if(!n)return;let r=()=>{if(e)return;e=!0;let i=n.takeRecords();if(i.length>0){let s=i[i.length-1],o=Math.max(s.startTime-v(),0);t({name:"LCP",value:Math.round(o),rating:f(o,[2500,4e3])})}n.disconnect()};for(let i of["keydown","click","visibilitychange"])addEventListener(i,r,{capture:!0,once:!0})}function j(t){let e=0,n=[],r=0;w("layout-shift",i=>{for(let s of i){let o=s;if(o.hadRecentInput)continue;let a=n[n.length-1],u=n[0];if(n.length>0&&a&&u&&o.startTime-a.startTime<1e3&&o.startTime-u.startTime<5e3?(e+=o.value,n.push(o)):(e=o.value,n=[o]),e>r){r=e;let p=Math.round(r*1e4)/1e4;t({name:"CLS",value:p,rating:f(r,[.1,.25])})}}})}function $(t){let e=H();if(!e)return;let n=Math.max(e.responseStart-v(),0);t({name:"TTFB",value:Math.round(n),rating:f(n,[800,1800])})}function B(t){let e=new Map,n=0;w("event",r=>{for(let i of r){let s=i;if(!s.interactionId)continue;let o=e.get(s.interactionId)??0;s.duration>o&&(e.set(s.interactionId,s.duration),s.duration>n&&(n=s.duration,t({name:"INP",value:Math.round(s.duration),rating:f(s.duration,[200,500])})))}},{durationThreshold:40})}function K(t){if(typeof requestAnimationFrame>"u")return;let e=0,n=2e3,r=performance.now(),i=()=>{e++,performance.now()-r<n?requestAnimationFrame(i):t({name:"FPS",value:Math.round(e/n*1e3)})};document.readyState==="complete"?requestAnimationFrame(i):window.addEventListener("load",()=>requestAnimationFrame(i),{once:!0})}function M(t){typeof window>"u"||typeof PerformanceObserver>"u"||(D(t),V(t),j(t),$(t),B(t),K(t))}function R(t){M(e=>{t.post("/v1/web-vitals",{name:e.name,value:e.value,...e.rating!==void 0?{rating:e.rating}:{}})})}function L(t,e){let n={analytics:_(t),"web-vitals":R(t),errors:U(t)};e.map(r=>n[r])}var S={baseUrl:"https://dev.plugeen.app/api",debug:!1,plugins:[]};function N(t,e){let n={baseUrl:e.baseUrl||S.baseUrl,debug:e.debug||S.debug,plugins:e.plugins||S.plugins};if(!t)throw new TypeError("[plugeen] apiKey is required");if(!n.baseUrl)throw new TypeError("[plugeen] baseUrl is required");let r=new h(n.baseUrl,t),i=C(r),s=O(r),o={track:(a,u)=>{i.create(a,u)},identify:(a,u)=>s.create(a,u)};return n.plugins.length>0&&L(r,n.plugins),o}var x=P();if(!x?.apiKey||!x?.baseUrl)console.warn("[plugeen] data-api-key and data-api-url attributes are required.");else{let t=N(x.apiKey,x);window.plugeen=t}})();
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();})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugeen",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.js",
6
6
  "devDependencies": {