plugeen 0.0.8 → 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 +210 -321
- package/dist/index.d.cts +61 -62
- package/dist/index.d.ts +61 -62
- package/dist/index.js +210 -321
- package/dist/plugeen.global.js +1 -1
- package/package.json +16 -14
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,11 +37,12 @@ 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";
|
|
26
44
|
var SESSION_TTL = 18e5;
|
|
45
|
+
var memStore = {};
|
|
27
46
|
function getOrCreateIdentityId() {
|
|
28
47
|
try {
|
|
29
48
|
let id = localStorage.getItem(IDENTITY_KEY);
|
|
@@ -33,13 +52,17 @@ function getOrCreateIdentityId() {
|
|
|
33
52
|
}
|
|
34
53
|
return id;
|
|
35
54
|
} catch {
|
|
36
|
-
|
|
55
|
+
if (!memStore[IDENTITY_KEY]) {
|
|
56
|
+
memStore[IDENTITY_KEY] = `anon_${generateUUID()}`;
|
|
57
|
+
}
|
|
58
|
+
return memStore[IDENTITY_KEY];
|
|
37
59
|
}
|
|
38
60
|
}
|
|
39
61
|
function setIdentityId(id) {
|
|
40
62
|
try {
|
|
41
63
|
localStorage.setItem(IDENTITY_KEY, id);
|
|
42
64
|
} catch {
|
|
65
|
+
memStore[IDENTITY_KEY] = id;
|
|
43
66
|
}
|
|
44
67
|
}
|
|
45
68
|
function getOrCreateSessionId() {
|
|
@@ -57,19 +80,28 @@ function getOrCreateSessionId() {
|
|
|
57
80
|
sessionStorage.setItem(SESSION_TS_KEY, String(Date.now()));
|
|
58
81
|
return id;
|
|
59
82
|
} catch {
|
|
60
|
-
|
|
83
|
+
const existing = memStore[SESSION_KEY];
|
|
84
|
+
const ts = memStore[SESSION_TS_KEY];
|
|
85
|
+
if (existing && ts && Date.now() - parseInt(ts, 10) < SESSION_TTL) {
|
|
86
|
+
memStore[SESSION_TS_KEY] = String(Date.now());
|
|
87
|
+
return existing;
|
|
88
|
+
}
|
|
89
|
+
const id = `sess_${generateUUID()}`;
|
|
90
|
+
memStore[SESSION_KEY] = id;
|
|
91
|
+
memStore[SESSION_TS_KEY] = String(Date.now());
|
|
92
|
+
return id;
|
|
61
93
|
}
|
|
62
94
|
}
|
|
63
95
|
|
|
64
|
-
// src/
|
|
96
|
+
// src/http-client/index.ts
|
|
65
97
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
66
98
|
var HttpClient = class {
|
|
67
|
-
constructor(
|
|
68
|
-
this.
|
|
99
|
+
constructor(baseUrl, apiKey, maxRetries = 3) {
|
|
100
|
+
this.baseUrl = baseUrl;
|
|
69
101
|
this.apiKey = apiKey;
|
|
70
102
|
this.maxRetries = maxRetries;
|
|
71
103
|
}
|
|
72
|
-
headers(method
|
|
104
|
+
headers(method) {
|
|
73
105
|
const h = {
|
|
74
106
|
Authorization: `Bearer ${this.apiKey}`,
|
|
75
107
|
"x-identity-id": getOrCreateIdentityId(),
|
|
@@ -78,71 +110,58 @@ var HttpClient = class {
|
|
|
78
110
|
if (method !== "GET") h["Content-Type"] = "application/json";
|
|
79
111
|
return h;
|
|
80
112
|
}
|
|
81
|
-
async
|
|
82
|
-
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}`;
|
|
83
116
|
try {
|
|
84
117
|
const res = await fetch(url, {
|
|
85
|
-
method
|
|
86
|
-
headers: this.headers(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
credentials: "omit"
|
|
118
|
+
method,
|
|
119
|
+
headers: this.headers(method),
|
|
120
|
+
credentials: "omit",
|
|
121
|
+
body: body ? JSON.stringify(body) : void 0
|
|
90
122
|
});
|
|
91
|
-
if (res.status === 401) return null;
|
|
123
|
+
if (res.status === 401 || res.status === 404) return [null, res.status];
|
|
92
124
|
if ((res.status >= 500 || res.status === 429) && attempt < this.maxRetries) {
|
|
93
125
|
const jitter = 0.85 + Math.random() * 0.3;
|
|
94
126
|
await delay(500 * 2 ** attempt * jitter);
|
|
95
|
-
return this.
|
|
127
|
+
return this.request(path, {
|
|
128
|
+
body,
|
|
129
|
+
attempt: attempt + 1,
|
|
130
|
+
method
|
|
131
|
+
});
|
|
96
132
|
}
|
|
97
133
|
if (res.status >= 200 && res.status < 300) {
|
|
98
134
|
try {
|
|
99
135
|
const json = await res.json();
|
|
100
|
-
return json
|
|
136
|
+
return [json.data, null];
|
|
101
137
|
} catch {
|
|
102
|
-
return null;
|
|
138
|
+
return [null, "Failed to parse json"];
|
|
103
139
|
}
|
|
104
140
|
}
|
|
105
|
-
return null;
|
|
141
|
+
return [null, "Error"];
|
|
106
142
|
} catch (err) {
|
|
107
143
|
if (err instanceof TypeError && attempt < this.maxRetries) {
|
|
108
144
|
const jitter = 0.85 + Math.random() * 0.3;
|
|
109
145
|
await delay(500 * 2 ** attempt * jitter);
|
|
110
|
-
return this.
|
|
146
|
+
return this.get(path, {
|
|
147
|
+
attempt: attempt + 1
|
|
148
|
+
});
|
|
111
149
|
}
|
|
112
|
-
return null;
|
|
150
|
+
return [null, err];
|
|
113
151
|
}
|
|
114
152
|
}
|
|
115
|
-
async
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return this.get(path, attempt + 1);
|
|
128
|
-
}
|
|
129
|
-
if (res.status >= 200 && res.status < 300) {
|
|
130
|
-
try {
|
|
131
|
-
const json = await res.json();
|
|
132
|
-
return json?.data ?? null;
|
|
133
|
-
} catch {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return null;
|
|
138
|
-
} catch (err) {
|
|
139
|
-
if (err instanceof TypeError && attempt < this.maxRetries) {
|
|
140
|
-
const jitter = 0.85 + Math.random() * 0.3;
|
|
141
|
-
await delay(500 * 2 ** attempt * jitter);
|
|
142
|
-
return this.get(path, attempt + 1);
|
|
143
|
-
}
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
153
|
+
async post(path, body, options) {
|
|
154
|
+
return this.request(path, {
|
|
155
|
+
attempt: options?.attempt ?? 0,
|
|
156
|
+
body,
|
|
157
|
+
method: "POST"
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async get(path, options) {
|
|
161
|
+
return this.request(path, {
|
|
162
|
+
attempt: options?.attempt ?? 0,
|
|
163
|
+
method: "GET"
|
|
164
|
+
});
|
|
146
165
|
}
|
|
147
166
|
beacon(path, body) {
|
|
148
167
|
if (typeof navigator === "undefined" || !navigator.sendBeacon) return false;
|
|
@@ -150,158 +169,23 @@ var HttpClient = class {
|
|
|
150
169
|
const blob = new Blob([JSON.stringify(body)], {
|
|
151
170
|
type: "application/json"
|
|
152
171
|
});
|
|
153
|
-
return navigator.sendBeacon(`${this.
|
|
172
|
+
return navigator.sendBeacon(`${this.baseUrl}${path}`, blob);
|
|
154
173
|
} catch {
|
|
155
174
|
return false;
|
|
156
175
|
}
|
|
157
176
|
}
|
|
158
|
-
send(path, body, useBeacon = false) {
|
|
159
|
-
if (useBeacon) {
|
|
160
|
-
const sent = this.beacon(path, body);
|
|
161
|
-
if (sent) return;
|
|
162
|
-
}
|
|
163
|
-
void this.post(path, body);
|
|
164
|
-
}
|
|
165
177
|
};
|
|
166
178
|
|
|
167
|
-
// src/
|
|
168
|
-
function
|
|
169
|
-
if (typeof window === "undefined") return false;
|
|
170
|
-
const { hostname } = window.location;
|
|
171
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "0.0.0.0" || hostname.endsWith(".local");
|
|
172
|
-
}
|
|
173
|
-
function isOptedOut() {
|
|
174
|
-
try {
|
|
175
|
-
return localStorage.getItem("plugeen_opt_out") === "true";
|
|
176
|
-
} catch {
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
function detectBot() {
|
|
181
|
-
if (typeof navigator === "undefined") return false;
|
|
182
|
-
const ua = navigator.userAgent || "";
|
|
183
|
-
return Boolean(
|
|
184
|
-
navigator.webdriver || /HeadlessChrome/i.test(ua) || /PhantomJS/i.test(ua) || typeof window !== "undefined" && window._phantom
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// src/helpers/should-skip.ts
|
|
189
|
-
function matchesSkipPattern(patterns) {
|
|
190
|
-
if (patterns.length === 0 || typeof window === "undefined") return false;
|
|
191
|
-
const path = window.location.pathname;
|
|
192
|
-
for (const pattern of patterns) {
|
|
193
|
-
if (pattern === path) return true;
|
|
194
|
-
const star = pattern.indexOf("*");
|
|
195
|
-
if (star !== -1 && path.startsWith(pattern.slice(0, star))) return true;
|
|
196
|
-
}
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
function shouldSkip(options) {
|
|
200
|
-
if (options.disabled) return true;
|
|
201
|
-
if (isOptedOut()) return true;
|
|
202
|
-
if (detectBot()) return true;
|
|
203
|
-
if (!options.debug && isLocalhost()) return true;
|
|
204
|
-
if (matchesSkipPattern(options.skipPatterns)) return true;
|
|
205
|
-
if (options.samplingRate < 1 && Math.random() > options.samplingRate)
|
|
206
|
-
return true;
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// src/modules/events.ts
|
|
211
|
-
function createEventsModule(http, options) {
|
|
212
|
-
return {
|
|
213
|
-
create: async (name, data = {}) => {
|
|
214
|
-
if (shouldSkip(options)) return null;
|
|
215
|
-
return http.post("/api/events", { name, data, source: "api" });
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// src/modules/experiments.ts
|
|
221
|
-
function createExperimentsModule(http, options) {
|
|
222
|
-
return {
|
|
223
|
-
get: (id) => {
|
|
224
|
-
if (shouldSkip(options)) return Promise.resolve(null);
|
|
225
|
-
return http.get(
|
|
226
|
-
`/api/v1/experiments/${encodeURIComponent(id)}`
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// src/modules/feature-flags.ts
|
|
233
|
-
function createFeatureFlagsModule(http, options) {
|
|
234
|
-
return {
|
|
235
|
-
get: (key) => {
|
|
236
|
-
if (shouldSkip(options)) return Promise.resolve(null);
|
|
237
|
-
return http.get(
|
|
238
|
-
`/api/v1/feature-flags/${encodeURIComponent(key)}`
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// src/modules/identities.ts
|
|
245
|
-
function createIdentitiesModule(http, options) {
|
|
246
|
-
return {
|
|
247
|
-
set: async (distinctId, data = {}) => {
|
|
248
|
-
if (shouldSkip(options)) return null;
|
|
249
|
-
const result = await http.post("/api/identities", {
|
|
250
|
-
id: distinctId,
|
|
251
|
-
...data
|
|
252
|
-
});
|
|
253
|
-
setIdentityId(distinctId);
|
|
254
|
-
return result;
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// src/modules/logs.ts
|
|
260
|
-
function createLogsModule(http, options) {
|
|
261
|
-
return {
|
|
262
|
-
send: (payload) => {
|
|
263
|
-
if (shouldSkip(options)) return Promise.resolve(null);
|
|
264
|
-
return http.post("/api/v1/logs", payload);
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// src/modules/surveys.ts
|
|
270
|
-
function createSurveysModule(http, options) {
|
|
271
|
-
return {
|
|
272
|
-
submit: (payload) => {
|
|
273
|
-
if (shouldSkip(options)) return Promise.resolve(null);
|
|
274
|
-
return http.post("/api/v1/surveys", payload);
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// src/plugins/analytics.ts
|
|
280
|
-
function initAnalyticsPlugin(plugeen, http) {
|
|
179
|
+
// src/modules/client/analytics/index.ts
|
|
180
|
+
function initAnalyticsModule(http, logger) {
|
|
281
181
|
if (typeof window === "undefined") return;
|
|
182
|
+
logger.log("Analytics module started");
|
|
282
183
|
let pageStartTime = Date.now();
|
|
283
|
-
let maxScrollDepth = 0;
|
|
284
|
-
let interactionCount = 0;
|
|
285
184
|
let pageCount = 0;
|
|
286
185
|
let currentUrl = window.location.href;
|
|
287
|
-
const updateScrollDepth = () => {
|
|
288
|
-
const scrollY = window.scrollY;
|
|
289
|
-
const { scrollHeight, clientHeight } = document.documentElement;
|
|
290
|
-
const available = scrollHeight - clientHeight;
|
|
291
|
-
if (available <= 0) return;
|
|
292
|
-
const depth = Math.min(100, Math.round(scrollY / available * 100));
|
|
293
|
-
if (depth > maxScrollDepth) maxScrollDepth = depth;
|
|
294
|
-
};
|
|
295
|
-
window.addEventListener("scroll", updateScrollDepth, { passive: true });
|
|
296
|
-
const countInteraction = () => {
|
|
297
|
-
interactionCount++;
|
|
298
|
-
};
|
|
299
|
-
for (const evt of ["mousedown", "keydown", "touchstart"]) {
|
|
300
|
-
window.addEventListener(evt, countInteraction, { passive: true });
|
|
301
|
-
}
|
|
302
186
|
const trackPageView = () => {
|
|
303
187
|
pageCount++;
|
|
304
|
-
|
|
188
|
+
void http.post("/v1/analytics", {
|
|
305
189
|
event: "page_view",
|
|
306
190
|
url: window.location.href,
|
|
307
191
|
title: document.title,
|
|
@@ -309,33 +193,31 @@ function initAnalyticsPlugin(plugeen, http) {
|
|
|
309
193
|
referrer: document.referrer || void 0,
|
|
310
194
|
screenWidth: window.innerWidth,
|
|
311
195
|
screenHeight: window.innerHeight
|
|
312
|
-
};
|
|
313
|
-
void http.post("/api/v1/analytics", body);
|
|
196
|
+
});
|
|
314
197
|
};
|
|
315
|
-
const buildPageExitData = () => ({
|
|
316
|
-
url: currentUrl,
|
|
317
|
-
time_on_page: Math.round((Date.now() - pageStartTime) / 1e3),
|
|
318
|
-
scroll_depth: maxScrollDepth,
|
|
319
|
-
interaction_count: interactionCount,
|
|
320
|
-
page_count: pageCount
|
|
321
|
-
});
|
|
322
198
|
const trackPageExit = (useBeacon = false) => {
|
|
323
|
-
const data =
|
|
199
|
+
const data = {
|
|
200
|
+
event: "page_exit",
|
|
201
|
+
url: window.location.href,
|
|
202
|
+
title: document.title,
|
|
203
|
+
sessionId: getOrCreateSessionId(),
|
|
204
|
+
referrer: document.referrer || void 0,
|
|
205
|
+
screenWidth: window.innerWidth,
|
|
206
|
+
screenHeight: window.innerHeight,
|
|
207
|
+
metadata: {
|
|
208
|
+
timeOnPage: Math.round((Date.now() - pageStartTime) / 1e3),
|
|
209
|
+
pageCount
|
|
210
|
+
}
|
|
211
|
+
};
|
|
324
212
|
if (useBeacon) {
|
|
325
|
-
http.
|
|
326
|
-
"/api/events",
|
|
327
|
-
{ name: "page_exit", data, source: "api" },
|
|
328
|
-
true
|
|
329
|
-
);
|
|
213
|
+
http.beacon("/v1/analytics", data);
|
|
330
214
|
} else {
|
|
331
|
-
void
|
|
215
|
+
void http.post("/v1/analytics", data);
|
|
332
216
|
}
|
|
333
217
|
};
|
|
334
218
|
const onRouteChange = () => {
|
|
335
219
|
if (window.location.href === currentUrl) return;
|
|
336
220
|
trackPageExit();
|
|
337
|
-
maxScrollDepth = 0;
|
|
338
|
-
interactionCount = 0;
|
|
339
221
|
pageStartTime = Date.now();
|
|
340
222
|
currentUrl = window.location.href;
|
|
341
223
|
trackPageView();
|
|
@@ -350,18 +232,15 @@ function initAnalyticsPlugin(plugeen, http) {
|
|
|
350
232
|
patchHistoryMethod("pushState");
|
|
351
233
|
patchHistoryMethod("replaceState");
|
|
352
234
|
window.addEventListener("popstate", onRouteChange);
|
|
353
|
-
window.addEventListener("beforeunload", () =>
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
trackPageExit(true);
|
|
359
|
-
}
|
|
360
|
-
});
|
|
235
|
+
window.addEventListener("beforeunload", () => trackPageExit(true));
|
|
236
|
+
document.addEventListener(
|
|
237
|
+
"visibilitychange",
|
|
238
|
+
() => document.visibilityState === "hidden" && trackPageExit(true)
|
|
239
|
+
);
|
|
361
240
|
trackPageView();
|
|
362
241
|
}
|
|
363
242
|
|
|
364
|
-
// src/
|
|
243
|
+
// src/modules/client/error/helpers.ts
|
|
365
244
|
var EXTENSION_PREFIXES = [
|
|
366
245
|
"chrome-extension://",
|
|
367
246
|
"moz-extension://",
|
|
@@ -373,19 +252,24 @@ function isExtensionSource(str) {
|
|
|
373
252
|
const lower = str.toLowerCase();
|
|
374
253
|
return EXTENSION_PREFIXES.some((prefix) => lower.includes(prefix));
|
|
375
254
|
}
|
|
376
|
-
|
|
255
|
+
|
|
256
|
+
// src/modules/client/error/index.ts
|
|
257
|
+
function initErrorsModule(http, logger) {
|
|
377
258
|
if (typeof window === "undefined") return;
|
|
259
|
+
logger.log("Errors module started");
|
|
378
260
|
window.addEventListener("error", (event) => {
|
|
379
261
|
if (isExtensionSource(event.filename) || isExtensionSource(event.error?.stack))
|
|
380
262
|
return;
|
|
381
263
|
if (event.error === null && event.message === "Script error.") return;
|
|
382
|
-
void
|
|
264
|
+
void http.post("/v1/logs", {
|
|
383
265
|
message: event.message || "Unknown Error",
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
266
|
+
level: "error",
|
|
267
|
+
url: window.location.href,
|
|
268
|
+
source: "client",
|
|
269
|
+
metadata: {
|
|
270
|
+
stack: event.error?.stack,
|
|
271
|
+
type: event.error?.name
|
|
272
|
+
}
|
|
389
273
|
});
|
|
390
274
|
});
|
|
391
275
|
window.addEventListener(
|
|
@@ -404,21 +288,21 @@ function initErrorsPlugin(plugeen) {
|
|
|
404
288
|
} else if (reason !== null && typeof reason === "object" && "message" in reason) {
|
|
405
289
|
message = String(reason.message);
|
|
406
290
|
}
|
|
407
|
-
void
|
|
291
|
+
void http.post("/v1/logs", {
|
|
408
292
|
message,
|
|
409
|
-
|
|
410
|
-
|
|
293
|
+
url: window.location.href,
|
|
294
|
+
source: "client",
|
|
295
|
+
level: "error",
|
|
296
|
+
metadata: {
|
|
297
|
+
stack,
|
|
298
|
+
type: "UnhandledRejection"
|
|
299
|
+
}
|
|
411
300
|
});
|
|
412
301
|
}
|
|
413
302
|
);
|
|
414
303
|
}
|
|
415
304
|
|
|
416
|
-
// src/
|
|
417
|
-
function getRating(value, thresholds) {
|
|
418
|
-
if (value > thresholds[1]) return "poor";
|
|
419
|
-
if (value > thresholds[0]) return "needs-improvement";
|
|
420
|
-
return "good";
|
|
421
|
-
}
|
|
305
|
+
// src/modules/client/web-vitals/helpers.ts
|
|
422
306
|
function getActivationStart() {
|
|
423
307
|
const nav = performance.getEntriesByType(
|
|
424
308
|
"navigation"
|
|
@@ -450,14 +334,10 @@ function observeFCP(cb) {
|
|
|
450
334
|
const reported = /* @__PURE__ */ new Set();
|
|
451
335
|
observe("paint", (entries) => {
|
|
452
336
|
for (const entry of entries) {
|
|
453
|
-
if (entry.name === "first-contentful-paint" && !reported.has("
|
|
454
|
-
reported.add("
|
|
337
|
+
if (entry.name === "first-contentful-paint" && !reported.has("first-contentful-paint")) {
|
|
338
|
+
reported.add("first-contentful-paint");
|
|
455
339
|
const value = Math.max(entry.startTime - getActivationStart(), 0);
|
|
456
|
-
cb(
|
|
457
|
-
name: "FCP",
|
|
458
|
-
value: Math.round(value),
|
|
459
|
-
rating: getRating(value, [1800, 3e3])
|
|
460
|
-
});
|
|
340
|
+
cb("first-contentful-paint", Math.round(value));
|
|
461
341
|
}
|
|
462
342
|
}
|
|
463
343
|
});
|
|
@@ -469,11 +349,7 @@ function observeLCP(cb) {
|
|
|
469
349
|
const last = entries[entries.length - 1];
|
|
470
350
|
if (last) {
|
|
471
351
|
const value = Math.max(last.startTime - getActivationStart(), 0);
|
|
472
|
-
cb(
|
|
473
|
-
name: "LCP",
|
|
474
|
-
value: Math.round(value),
|
|
475
|
-
rating: getRating(value, [2500, 4e3])
|
|
476
|
-
});
|
|
352
|
+
cb("largest-contentful-paint", Math.round(value));
|
|
477
353
|
}
|
|
478
354
|
});
|
|
479
355
|
if (!po) return;
|
|
@@ -484,11 +360,7 @@ function observeLCP(cb) {
|
|
|
484
360
|
if (records.length > 0) {
|
|
485
361
|
const last = records[records.length - 1];
|
|
486
362
|
const value = Math.max(last.startTime - getActivationStart(), 0);
|
|
487
|
-
cb(
|
|
488
|
-
name: "LCP",
|
|
489
|
-
value: Math.round(value),
|
|
490
|
-
rating: getRating(value, [2500, 4e3])
|
|
491
|
-
});
|
|
363
|
+
cb("largest-contentful-paint", Math.round(value));
|
|
492
364
|
}
|
|
493
365
|
po.disconnect();
|
|
494
366
|
};
|
|
@@ -516,11 +388,7 @@ function observeCLS(cb) {
|
|
|
516
388
|
if (sessionValue > clsValue) {
|
|
517
389
|
clsValue = sessionValue;
|
|
518
390
|
const rounded = Math.round(clsValue * 1e4) / 1e4;
|
|
519
|
-
cb(
|
|
520
|
-
name: "CLS",
|
|
521
|
-
value: rounded,
|
|
522
|
-
rating: getRating(clsValue, [0.1, 0.25])
|
|
523
|
-
});
|
|
391
|
+
cb("cumulative-layout-shift", rounded);
|
|
524
392
|
}
|
|
525
393
|
}
|
|
526
394
|
});
|
|
@@ -529,11 +397,7 @@ function observeTTFB(cb) {
|
|
|
529
397
|
const nav = getNavEntry();
|
|
530
398
|
if (!nav) return;
|
|
531
399
|
const value = Math.max(nav.responseStart - getActivationStart(), 0);
|
|
532
|
-
cb(
|
|
533
|
-
name: "TTFB",
|
|
534
|
-
value: Math.round(value),
|
|
535
|
-
rating: getRating(value, [800, 1800])
|
|
536
|
-
});
|
|
400
|
+
cb("time-to-first-byte", Math.round(value));
|
|
537
401
|
}
|
|
538
402
|
function observeINP(cb) {
|
|
539
403
|
const interactions = /* @__PURE__ */ new Map();
|
|
@@ -549,11 +413,7 @@ function observeINP(cb) {
|
|
|
549
413
|
interactions.set(entry.interactionId, entry.duration);
|
|
550
414
|
if (entry.duration > worstDuration) {
|
|
551
415
|
worstDuration = entry.duration;
|
|
552
|
-
cb(
|
|
553
|
-
name: "INP",
|
|
554
|
-
value: Math.round(entry.duration),
|
|
555
|
-
rating: getRating(entry.duration, [200, 500])
|
|
556
|
-
});
|
|
416
|
+
cb("interaction-to-next-paint", Math.round(entry.duration));
|
|
557
417
|
}
|
|
558
418
|
}
|
|
559
419
|
}
|
|
@@ -571,7 +431,7 @@ function observeFPS(cb) {
|
|
|
571
431
|
if (performance.now() - start < duration) {
|
|
572
432
|
requestAnimationFrame(tick);
|
|
573
433
|
} else {
|
|
574
|
-
cb(
|
|
434
|
+
cb("frames-per-second", Math.round(frames / duration * 1e3));
|
|
575
435
|
}
|
|
576
436
|
};
|
|
577
437
|
if (document.readyState === "complete") {
|
|
@@ -582,74 +442,103 @@ function observeFPS(cb) {
|
|
|
582
442
|
});
|
|
583
443
|
}
|
|
584
444
|
}
|
|
585
|
-
function initWebVitals(
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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 }));
|
|
594
457
|
}
|
|
595
458
|
|
|
596
|
-
// src/
|
|
597
|
-
function
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
...metric.rating !== void 0 ? { rating: metric.rating } : {}
|
|
603
|
-
});
|
|
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()
|
|
604
465
|
});
|
|
605
466
|
}
|
|
606
467
|
|
|
607
|
-
// src/
|
|
608
|
-
function
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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)
|
|
510
|
+
};
|
|
614
511
|
}
|
|
615
512
|
|
|
616
513
|
// src/index.ts
|
|
617
514
|
function createPlugeen(apiKey, options) {
|
|
618
|
-
if (!apiKey || typeof apiKey !== "string") {
|
|
619
|
-
throw new TypeError("[plugeen] apiKey is required");
|
|
620
|
-
}
|
|
621
|
-
if (!options?.apiUrl || typeof options.apiUrl !== "string") {
|
|
622
|
-
throw new TypeError("[plugeen] apiUrl is required");
|
|
623
|
-
}
|
|
624
515
|
const resolved = {
|
|
625
|
-
apiUrl:
|
|
626
|
-
debug:
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
516
|
+
apiUrl: "https://dev.plugeen.app/api",
|
|
517
|
+
debug: false,
|
|
518
|
+
analytics: false,
|
|
519
|
+
webVitals: false,
|
|
520
|
+
errors: false,
|
|
521
|
+
...options
|
|
631
522
|
};
|
|
523
|
+
const logger = createLogger(resolved.debug);
|
|
524
|
+
if (!apiKey) {
|
|
525
|
+
throw new TypeError("[plugeen] data-api-key is required");
|
|
526
|
+
}
|
|
632
527
|
const http = new HttpClient(resolved.apiUrl, apiKey);
|
|
633
|
-
const events = createEventsModule(http, resolved);
|
|
634
|
-
const identities = createIdentitiesModule(http, resolved);
|
|
635
|
-
const featureFlags = createFeatureFlagsModule(http, resolved);
|
|
636
|
-
const logs = createLogsModule(http, resolved);
|
|
637
|
-
const surveys = createSurveysModule(http, resolved);
|
|
638
|
-
const experiments = createExperimentsModule(http, resolved);
|
|
639
528
|
const plugeen = {
|
|
640
|
-
track: (
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
identities,
|
|
645
|
-
featureFlags,
|
|
646
|
-
logs,
|
|
647
|
-
surveys,
|
|
648
|
-
experiments
|
|
529
|
+
track: initEventsModule(http, logger).track,
|
|
530
|
+
identify: initIdentitiesModule(http, logger).identify,
|
|
531
|
+
featureFlags: initFeatureFlagsModule(http, logger),
|
|
532
|
+
logs: initLogsModule(http, logger)
|
|
649
533
|
};
|
|
650
|
-
if (
|
|
651
|
-
|
|
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);
|
|
652
538
|
}
|
|
539
|
+
logger.log(
|
|
540
|
+
`Running on ${typeof window === "undefined" ? "server" : "client"} side`
|
|
541
|
+
);
|
|
653
542
|
return plugeen;
|
|
654
543
|
}
|
|
655
544
|
|