plugeen 0.0.8 → 0.0.9
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 +106 -234
- package/dist/index.js +106 -234
- package/dist/plugeen.global.js +1 -1
- package/package.json +16 -14
- package/dist/index.d.cts +0 -89
- package/dist/index.d.ts +0 -89
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ var IDENTITY_KEY = "plugeen_anon_id";
|
|
|
24
24
|
var SESSION_KEY = "plugeen_session_id";
|
|
25
25
|
var SESSION_TS_KEY = "plugeen_session_ts";
|
|
26
26
|
var SESSION_TTL = 18e5;
|
|
27
|
+
var memStore = {};
|
|
27
28
|
function getOrCreateIdentityId() {
|
|
28
29
|
try {
|
|
29
30
|
let id = localStorage.getItem(IDENTITY_KEY);
|
|
@@ -33,13 +34,17 @@ function getOrCreateIdentityId() {
|
|
|
33
34
|
}
|
|
34
35
|
return id;
|
|
35
36
|
} catch {
|
|
36
|
-
|
|
37
|
+
if (!memStore[IDENTITY_KEY]) {
|
|
38
|
+
memStore[IDENTITY_KEY] = `anon_${generateUUID()}`;
|
|
39
|
+
}
|
|
40
|
+
return memStore[IDENTITY_KEY];
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
function setIdentityId(id) {
|
|
40
44
|
try {
|
|
41
45
|
localStorage.setItem(IDENTITY_KEY, id);
|
|
42
46
|
} catch {
|
|
47
|
+
memStore[IDENTITY_KEY] = id;
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
function getOrCreateSessionId() {
|
|
@@ -57,19 +62,28 @@ function getOrCreateSessionId() {
|
|
|
57
62
|
sessionStorage.setItem(SESSION_TS_KEY, String(Date.now()));
|
|
58
63
|
return id;
|
|
59
64
|
} catch {
|
|
60
|
-
|
|
65
|
+
const existing = memStore[SESSION_KEY];
|
|
66
|
+
const ts = memStore[SESSION_TS_KEY];
|
|
67
|
+
if (existing && ts && Date.now() - parseInt(ts, 10) < SESSION_TTL) {
|
|
68
|
+
memStore[SESSION_TS_KEY] = String(Date.now());
|
|
69
|
+
return existing;
|
|
70
|
+
}
|
|
71
|
+
const id = `sess_${generateUUID()}`;
|
|
72
|
+
memStore[SESSION_KEY] = id;
|
|
73
|
+
memStore[SESSION_TS_KEY] = String(Date.now());
|
|
74
|
+
return id;
|
|
61
75
|
}
|
|
62
76
|
}
|
|
63
77
|
|
|
64
|
-
// src/helpers/http-client.ts
|
|
78
|
+
// src/helpers/http-client/index.ts
|
|
65
79
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
66
80
|
var HttpClient = class {
|
|
67
|
-
constructor(
|
|
68
|
-
this.
|
|
81
|
+
constructor(baseUrl, apiKey, maxRetries = 3) {
|
|
82
|
+
this.baseUrl = baseUrl;
|
|
69
83
|
this.apiKey = apiKey;
|
|
70
84
|
this.maxRetries = maxRetries;
|
|
71
85
|
}
|
|
72
|
-
headers(method
|
|
86
|
+
headers(method) {
|
|
73
87
|
const h = {
|
|
74
88
|
Authorization: `Bearer ${this.apiKey}`,
|
|
75
89
|
"x-identity-id": getOrCreateIdentityId(),
|
|
@@ -78,71 +92,55 @@ var HttpClient = class {
|
|
|
78
92
|
if (method !== "GET") h["Content-Type"] = "application/json";
|
|
79
93
|
return h;
|
|
80
94
|
}
|
|
81
|
-
async
|
|
82
|
-
const url = `${this.
|
|
95
|
+
async request(path, { attempt = 0, method, body }) {
|
|
96
|
+
const url = `${this.baseUrl}${path}`;
|
|
83
97
|
try {
|
|
84
98
|
const res = await fetch(url, {
|
|
85
|
-
method
|
|
86
|
-
headers: this.headers(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
credentials: "omit"
|
|
99
|
+
method,
|
|
100
|
+
headers: this.headers(method),
|
|
101
|
+
credentials: "omit",
|
|
102
|
+
body: body ? JSON.stringify(body) : void 0
|
|
90
103
|
});
|
|
91
|
-
if (res.status === 401) return null;
|
|
104
|
+
if (res.status === 401 || res.status === 404) return [null, res.status];
|
|
92
105
|
if ((res.status >= 500 || res.status === 429) && attempt < this.maxRetries) {
|
|
93
106
|
const jitter = 0.85 + Math.random() * 0.3;
|
|
94
107
|
await delay(500 * 2 ** attempt * jitter);
|
|
95
|
-
return this.
|
|
108
|
+
return this.request(path, {
|
|
109
|
+
body,
|
|
110
|
+
attempt: attempt + 1,
|
|
111
|
+
method
|
|
112
|
+
});
|
|
96
113
|
}
|
|
97
114
|
if (res.status >= 200 && res.status < 300) {
|
|
98
115
|
try {
|
|
99
116
|
const json = await res.json();
|
|
100
|
-
return json
|
|
117
|
+
return [json.data, null];
|
|
101
118
|
} catch {
|
|
102
|
-
return null;
|
|
119
|
+
return [null, "Failed to parse json"];
|
|
103
120
|
}
|
|
104
121
|
}
|
|
105
|
-
return null;
|
|
122
|
+
return [null, "Error"];
|
|
106
123
|
} catch (err) {
|
|
107
124
|
if (err instanceof TypeError && attempt < this.maxRetries) {
|
|
108
125
|
const jitter = 0.85 + Math.random() * 0.3;
|
|
109
126
|
await delay(500 * 2 ** attempt * jitter);
|
|
110
|
-
return this.
|
|
127
|
+
return this.get(path, attempt + 1);
|
|
111
128
|
}
|
|
112
|
-
return null;
|
|
129
|
+
return [null, err];
|
|
113
130
|
}
|
|
114
131
|
}
|
|
132
|
+
async post(path, body, attempt = 0) {
|
|
133
|
+
return this.request(path, {
|
|
134
|
+
attempt,
|
|
135
|
+
body,
|
|
136
|
+
method: "POST"
|
|
137
|
+
});
|
|
138
|
+
}
|
|
115
139
|
async get(path, attempt = 0) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
headers: this.headers("GET"),
|
|
121
|
-
credentials: "omit"
|
|
122
|
-
});
|
|
123
|
-
if (res.status === 401 || res.status === 404) return null;
|
|
124
|
-
if ((res.status >= 500 || res.status === 429) && attempt < this.maxRetries) {
|
|
125
|
-
const jitter = 0.85 + Math.random() * 0.3;
|
|
126
|
-
await delay(500 * 2 ** attempt * jitter);
|
|
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
|
-
}
|
|
140
|
+
return this.request(path, {
|
|
141
|
+
attempt,
|
|
142
|
+
method: "GET"
|
|
143
|
+
});
|
|
146
144
|
}
|
|
147
145
|
beacon(path, body) {
|
|
148
146
|
if (typeof navigator === "undefined" || !navigator.sendBeacon) return false;
|
|
@@ -150,103 +148,27 @@ var HttpClient = class {
|
|
|
150
148
|
const blob = new Blob([JSON.stringify(body)], {
|
|
151
149
|
type: "application/json"
|
|
152
150
|
});
|
|
153
|
-
return navigator.sendBeacon(`${this.
|
|
151
|
+
return navigator.sendBeacon(`${this.baseUrl}${path}`, blob);
|
|
154
152
|
} catch {
|
|
155
153
|
return false;
|
|
156
154
|
}
|
|
157
155
|
}
|
|
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
156
|
};
|
|
166
157
|
|
|
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) {
|
|
158
|
+
// src/plugins/events/index.ts
|
|
159
|
+
function createEventsModule(http) {
|
|
212
160
|
return {
|
|
213
161
|
create: async (name, data = {}) => {
|
|
214
|
-
|
|
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
|
-
);
|
|
162
|
+
return http.post("/events", { name, data, source: "api" });
|
|
228
163
|
}
|
|
229
164
|
};
|
|
230
165
|
}
|
|
231
166
|
|
|
232
|
-
// src/
|
|
233
|
-
function
|
|
167
|
+
// src/plugins/identities/index.ts
|
|
168
|
+
function createIdentitiesModule(http) {
|
|
234
169
|
return {
|
|
235
|
-
|
|
236
|
-
|
|
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", {
|
|
170
|
+
create: async (distinctId, data = {}) => {
|
|
171
|
+
const result = await http.post("/identities", {
|
|
250
172
|
id: distinctId,
|
|
251
173
|
...data
|
|
252
174
|
});
|
|
@@ -256,52 +178,15 @@ function createIdentitiesModule(http, options) {
|
|
|
256
178
|
};
|
|
257
179
|
}
|
|
258
180
|
|
|
259
|
-
// src/
|
|
260
|
-
function
|
|
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) {
|
|
181
|
+
// src/plugins/analytics/index.ts
|
|
182
|
+
function initAnalyticsPlugin(http) {
|
|
281
183
|
if (typeof window === "undefined") return;
|
|
282
184
|
let pageStartTime = Date.now();
|
|
283
|
-
let maxScrollDepth = 0;
|
|
284
|
-
let interactionCount = 0;
|
|
285
185
|
let pageCount = 0;
|
|
286
186
|
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
187
|
const trackPageView = () => {
|
|
303
188
|
pageCount++;
|
|
304
|
-
|
|
189
|
+
void http.post("/v1/analytics", {
|
|
305
190
|
event: "page_view",
|
|
306
191
|
url: window.location.href,
|
|
307
192
|
title: document.title,
|
|
@@ -309,33 +194,31 @@ function initAnalyticsPlugin(plugeen, http) {
|
|
|
309
194
|
referrer: document.referrer || void 0,
|
|
310
195
|
screenWidth: window.innerWidth,
|
|
311
196
|
screenHeight: window.innerHeight
|
|
312
|
-
};
|
|
313
|
-
void http.post("/api/v1/analytics", body);
|
|
197
|
+
});
|
|
314
198
|
};
|
|
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
199
|
const trackPageExit = (useBeacon = false) => {
|
|
323
|
-
const data =
|
|
200
|
+
const data = {
|
|
201
|
+
event: "page_exit",
|
|
202
|
+
url: window.location.href,
|
|
203
|
+
title: document.title,
|
|
204
|
+
sessionId: getOrCreateSessionId(),
|
|
205
|
+
referrer: document.referrer || void 0,
|
|
206
|
+
screenWidth: window.innerWidth,
|
|
207
|
+
screenHeight: window.innerHeight,
|
|
208
|
+
metadata: {
|
|
209
|
+
timeOnPage: Math.round((Date.now() - pageStartTime) / 1e3),
|
|
210
|
+
pageCount
|
|
211
|
+
}
|
|
212
|
+
};
|
|
324
213
|
if (useBeacon) {
|
|
325
|
-
http.
|
|
326
|
-
"/api/events",
|
|
327
|
-
{ name: "page_exit", data, source: "api" },
|
|
328
|
-
true
|
|
329
|
-
);
|
|
214
|
+
http.beacon("/v1/analytics", data);
|
|
330
215
|
} else {
|
|
331
|
-
void
|
|
216
|
+
void http.post("/v1/analytics", data);
|
|
332
217
|
}
|
|
333
218
|
};
|
|
334
219
|
const onRouteChange = () => {
|
|
335
220
|
if (window.location.href === currentUrl) return;
|
|
336
221
|
trackPageExit();
|
|
337
|
-
maxScrollDepth = 0;
|
|
338
|
-
interactionCount = 0;
|
|
339
222
|
pageStartTime = Date.now();
|
|
340
223
|
currentUrl = window.location.href;
|
|
341
224
|
trackPageView();
|
|
@@ -350,9 +233,7 @@ function initAnalyticsPlugin(plugeen, http) {
|
|
|
350
233
|
patchHistoryMethod("pushState");
|
|
351
234
|
patchHistoryMethod("replaceState");
|
|
352
235
|
window.addEventListener("popstate", onRouteChange);
|
|
353
|
-
window.addEventListener("beforeunload", () =>
|
|
354
|
-
trackPageExit(true);
|
|
355
|
-
});
|
|
236
|
+
window.addEventListener("beforeunload", () => trackPageExit(true));
|
|
356
237
|
document.addEventListener("visibilitychange", () => {
|
|
357
238
|
if (document.visibilityState === "hidden") {
|
|
358
239
|
trackPageExit(true);
|
|
@@ -361,7 +242,7 @@ function initAnalyticsPlugin(plugeen, http) {
|
|
|
361
242
|
trackPageView();
|
|
362
243
|
}
|
|
363
244
|
|
|
364
|
-
// src/plugins/
|
|
245
|
+
// src/plugins/logs/index.ts
|
|
365
246
|
var EXTENSION_PREFIXES = [
|
|
366
247
|
"chrome-extension://",
|
|
367
248
|
"moz-extension://",
|
|
@@ -373,13 +254,13 @@ function isExtensionSource(str) {
|
|
|
373
254
|
const lower = str.toLowerCase();
|
|
374
255
|
return EXTENSION_PREFIXES.some((prefix) => lower.includes(prefix));
|
|
375
256
|
}
|
|
376
|
-
function initErrorsPlugin(
|
|
257
|
+
function initErrorsPlugin(http) {
|
|
377
258
|
if (typeof window === "undefined") return;
|
|
378
259
|
window.addEventListener("error", (event) => {
|
|
379
260
|
if (isExtensionSource(event.filename) || isExtensionSource(event.error?.stack))
|
|
380
261
|
return;
|
|
381
262
|
if (event.error === null && event.message === "Script error.") return;
|
|
382
|
-
void
|
|
263
|
+
void http.post("/v1/logs", {
|
|
383
264
|
message: event.message || "Unknown Error",
|
|
384
265
|
filename: event.filename,
|
|
385
266
|
lineno: event.lineno,
|
|
@@ -404,7 +285,7 @@ function initErrorsPlugin(plugeen) {
|
|
|
404
285
|
} else if (reason !== null && typeof reason === "object" && "message" in reason) {
|
|
405
286
|
message = String(reason.message);
|
|
406
287
|
}
|
|
407
|
-
void
|
|
288
|
+
void http.post("/v1/logs", {
|
|
408
289
|
message,
|
|
409
290
|
stack,
|
|
410
291
|
error_type: "UnhandledRejection"
|
|
@@ -594,9 +475,9 @@ function initWebVitals(cb) {
|
|
|
594
475
|
}
|
|
595
476
|
|
|
596
477
|
// src/plugins/web-vitals/index.ts
|
|
597
|
-
function initWebVitalsPlugin(
|
|
478
|
+
function initWebVitalsPlugin(http) {
|
|
598
479
|
initWebVitals((metric) => {
|
|
599
|
-
|
|
480
|
+
http.post("/v1/web-vitals", {
|
|
600
481
|
name: metric.name,
|
|
601
482
|
value: metric.value,
|
|
602
483
|
...metric.rating !== void 0 ? { rating: metric.rating } : {}
|
|
@@ -605,50 +486,41 @@ function initWebVitalsPlugin(plugeen) {
|
|
|
605
486
|
}
|
|
606
487
|
|
|
607
488
|
// src/plugins/index.ts
|
|
608
|
-
function initPlugins(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
489
|
+
function initPlugins(http, plugins) {
|
|
490
|
+
const initializers = {
|
|
491
|
+
analytics: initAnalyticsPlugin(http),
|
|
492
|
+
"web-vitals": initWebVitalsPlugin(http),
|
|
493
|
+
errors: initErrorsPlugin(http)
|
|
494
|
+
};
|
|
495
|
+
plugins.map((p) => initializers[p]);
|
|
614
496
|
}
|
|
615
497
|
|
|
616
498
|
// src/index.ts
|
|
499
|
+
var defaults = {
|
|
500
|
+
baseUrl: "https://dev.plugeen.app/api",
|
|
501
|
+
debug: false,
|
|
502
|
+
plugins: []
|
|
503
|
+
};
|
|
617
504
|
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
505
|
const resolved = {
|
|
625
|
-
|
|
626
|
-
debug: options.debug
|
|
627
|
-
|
|
628
|
-
samplingRate: options.samplingRate ?? 1,
|
|
629
|
-
plugins: options.plugins ?? [],
|
|
630
|
-
skipPatterns: options.skipPatterns ?? []
|
|
506
|
+
baseUrl: options.baseUrl || defaults.baseUrl,
|
|
507
|
+
debug: options.debug || defaults.debug,
|
|
508
|
+
plugins: options.plugins || defaults.plugins
|
|
631
509
|
};
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const
|
|
636
|
-
const
|
|
637
|
-
const
|
|
638
|
-
const experiments = createExperimentsModule(http, resolved);
|
|
510
|
+
if (!apiKey) {
|
|
511
|
+
throw new TypeError("[plugeen] apiKey is required");
|
|
512
|
+
}
|
|
513
|
+
const http = new HttpClient(resolved.baseUrl, apiKey);
|
|
514
|
+
const events = createEventsModule(http);
|
|
515
|
+
const identities = createIdentitiesModule(http);
|
|
639
516
|
const plugeen = {
|
|
640
517
|
track: (event, properties) => {
|
|
641
|
-
void events.create(event, properties
|
|
518
|
+
void events.create(event, properties);
|
|
642
519
|
},
|
|
643
|
-
|
|
644
|
-
identities,
|
|
645
|
-
featureFlags,
|
|
646
|
-
logs,
|
|
647
|
-
surveys,
|
|
648
|
-
experiments
|
|
520
|
+
identify: (userId, data) => identities.create(userId, data)
|
|
649
521
|
};
|
|
650
522
|
if (resolved.plugins.length > 0) {
|
|
651
|
-
initPlugins(
|
|
523
|
+
initPlugins(http, resolved.plugins);
|
|
652
524
|
}
|
|
653
525
|
return plugeen;
|
|
654
526
|
}
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ var IDENTITY_KEY = "plugeen_anon_id";
|
|
|
22
22
|
var SESSION_KEY = "plugeen_session_id";
|
|
23
23
|
var SESSION_TS_KEY = "plugeen_session_ts";
|
|
24
24
|
var SESSION_TTL = 18e5;
|
|
25
|
+
var memStore = {};
|
|
25
26
|
function getOrCreateIdentityId() {
|
|
26
27
|
try {
|
|
27
28
|
let id = localStorage.getItem(IDENTITY_KEY);
|
|
@@ -31,13 +32,17 @@ function getOrCreateIdentityId() {
|
|
|
31
32
|
}
|
|
32
33
|
return id;
|
|
33
34
|
} catch {
|
|
34
|
-
|
|
35
|
+
if (!memStore[IDENTITY_KEY]) {
|
|
36
|
+
memStore[IDENTITY_KEY] = `anon_${generateUUID()}`;
|
|
37
|
+
}
|
|
38
|
+
return memStore[IDENTITY_KEY];
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
41
|
function setIdentityId(id) {
|
|
38
42
|
try {
|
|
39
43
|
localStorage.setItem(IDENTITY_KEY, id);
|
|
40
44
|
} catch {
|
|
45
|
+
memStore[IDENTITY_KEY] = id;
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
48
|
function getOrCreateSessionId() {
|
|
@@ -55,19 +60,28 @@ function getOrCreateSessionId() {
|
|
|
55
60
|
sessionStorage.setItem(SESSION_TS_KEY, String(Date.now()));
|
|
56
61
|
return id;
|
|
57
62
|
} catch {
|
|
58
|
-
|
|
63
|
+
const existing = memStore[SESSION_KEY];
|
|
64
|
+
const ts = memStore[SESSION_TS_KEY];
|
|
65
|
+
if (existing && ts && Date.now() - parseInt(ts, 10) < SESSION_TTL) {
|
|
66
|
+
memStore[SESSION_TS_KEY] = String(Date.now());
|
|
67
|
+
return existing;
|
|
68
|
+
}
|
|
69
|
+
const id = `sess_${generateUUID()}`;
|
|
70
|
+
memStore[SESSION_KEY] = id;
|
|
71
|
+
memStore[SESSION_TS_KEY] = String(Date.now());
|
|
72
|
+
return id;
|
|
59
73
|
}
|
|
60
74
|
}
|
|
61
75
|
|
|
62
|
-
// src/helpers/http-client.ts
|
|
76
|
+
// src/helpers/http-client/index.ts
|
|
63
77
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
64
78
|
var HttpClient = class {
|
|
65
|
-
constructor(
|
|
66
|
-
this.
|
|
79
|
+
constructor(baseUrl, apiKey, maxRetries = 3) {
|
|
80
|
+
this.baseUrl = baseUrl;
|
|
67
81
|
this.apiKey = apiKey;
|
|
68
82
|
this.maxRetries = maxRetries;
|
|
69
83
|
}
|
|
70
|
-
headers(method
|
|
84
|
+
headers(method) {
|
|
71
85
|
const h = {
|
|
72
86
|
Authorization: `Bearer ${this.apiKey}`,
|
|
73
87
|
"x-identity-id": getOrCreateIdentityId(),
|
|
@@ -76,71 +90,55 @@ var HttpClient = class {
|
|
|
76
90
|
if (method !== "GET") h["Content-Type"] = "application/json";
|
|
77
91
|
return h;
|
|
78
92
|
}
|
|
79
|
-
async
|
|
80
|
-
const url = `${this.
|
|
93
|
+
async request(path, { attempt = 0, method, body }) {
|
|
94
|
+
const url = `${this.baseUrl}${path}`;
|
|
81
95
|
try {
|
|
82
96
|
const res = await fetch(url, {
|
|
83
|
-
method
|
|
84
|
-
headers: this.headers(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
credentials: "omit"
|
|
97
|
+
method,
|
|
98
|
+
headers: this.headers(method),
|
|
99
|
+
credentials: "omit",
|
|
100
|
+
body: body ? JSON.stringify(body) : void 0
|
|
88
101
|
});
|
|
89
|
-
if (res.status === 401) return null;
|
|
102
|
+
if (res.status === 401 || res.status === 404) return [null, res.status];
|
|
90
103
|
if ((res.status >= 500 || res.status === 429) && attempt < this.maxRetries) {
|
|
91
104
|
const jitter = 0.85 + Math.random() * 0.3;
|
|
92
105
|
await delay(500 * 2 ** attempt * jitter);
|
|
93
|
-
return this.
|
|
106
|
+
return this.request(path, {
|
|
107
|
+
body,
|
|
108
|
+
attempt: attempt + 1,
|
|
109
|
+
method
|
|
110
|
+
});
|
|
94
111
|
}
|
|
95
112
|
if (res.status >= 200 && res.status < 300) {
|
|
96
113
|
try {
|
|
97
114
|
const json = await res.json();
|
|
98
|
-
return json
|
|
115
|
+
return [json.data, null];
|
|
99
116
|
} catch {
|
|
100
|
-
return null;
|
|
117
|
+
return [null, "Failed to parse json"];
|
|
101
118
|
}
|
|
102
119
|
}
|
|
103
|
-
return null;
|
|
120
|
+
return [null, "Error"];
|
|
104
121
|
} catch (err) {
|
|
105
122
|
if (err instanceof TypeError && attempt < this.maxRetries) {
|
|
106
123
|
const jitter = 0.85 + Math.random() * 0.3;
|
|
107
124
|
await delay(500 * 2 ** attempt * jitter);
|
|
108
|
-
return this.
|
|
125
|
+
return this.get(path, attempt + 1);
|
|
109
126
|
}
|
|
110
|
-
return null;
|
|
127
|
+
return [null, err];
|
|
111
128
|
}
|
|
112
129
|
}
|
|
130
|
+
async post(path, body, attempt = 0) {
|
|
131
|
+
return this.request(path, {
|
|
132
|
+
attempt,
|
|
133
|
+
body,
|
|
134
|
+
method: "POST"
|
|
135
|
+
});
|
|
136
|
+
}
|
|
113
137
|
async get(path, attempt = 0) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
headers: this.headers("GET"),
|
|
119
|
-
credentials: "omit"
|
|
120
|
-
});
|
|
121
|
-
if (res.status === 401 || res.status === 404) return null;
|
|
122
|
-
if ((res.status >= 500 || res.status === 429) && attempt < this.maxRetries) {
|
|
123
|
-
const jitter = 0.85 + Math.random() * 0.3;
|
|
124
|
-
await delay(500 * 2 ** attempt * jitter);
|
|
125
|
-
return this.get(path, attempt + 1);
|
|
126
|
-
}
|
|
127
|
-
if (res.status >= 200 && res.status < 300) {
|
|
128
|
-
try {
|
|
129
|
-
const json = await res.json();
|
|
130
|
-
return json?.data ?? null;
|
|
131
|
-
} catch {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return null;
|
|
136
|
-
} catch (err) {
|
|
137
|
-
if (err instanceof TypeError && attempt < this.maxRetries) {
|
|
138
|
-
const jitter = 0.85 + Math.random() * 0.3;
|
|
139
|
-
await delay(500 * 2 ** attempt * jitter);
|
|
140
|
-
return this.get(path, attempt + 1);
|
|
141
|
-
}
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
138
|
+
return this.request(path, {
|
|
139
|
+
attempt,
|
|
140
|
+
method: "GET"
|
|
141
|
+
});
|
|
144
142
|
}
|
|
145
143
|
beacon(path, body) {
|
|
146
144
|
if (typeof navigator === "undefined" || !navigator.sendBeacon) return false;
|
|
@@ -148,103 +146,27 @@ var HttpClient = class {
|
|
|
148
146
|
const blob = new Blob([JSON.stringify(body)], {
|
|
149
147
|
type: "application/json"
|
|
150
148
|
});
|
|
151
|
-
return navigator.sendBeacon(`${this.
|
|
149
|
+
return navigator.sendBeacon(`${this.baseUrl}${path}`, blob);
|
|
152
150
|
} catch {
|
|
153
151
|
return false;
|
|
154
152
|
}
|
|
155
153
|
}
|
|
156
|
-
send(path, body, useBeacon = false) {
|
|
157
|
-
if (useBeacon) {
|
|
158
|
-
const sent = this.beacon(path, body);
|
|
159
|
-
if (sent) return;
|
|
160
|
-
}
|
|
161
|
-
void this.post(path, body);
|
|
162
|
-
}
|
|
163
154
|
};
|
|
164
155
|
|
|
165
|
-
// src/
|
|
166
|
-
function
|
|
167
|
-
if (typeof window === "undefined") return false;
|
|
168
|
-
const { hostname } = window.location;
|
|
169
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "0.0.0.0" || hostname.endsWith(".local");
|
|
170
|
-
}
|
|
171
|
-
function isOptedOut() {
|
|
172
|
-
try {
|
|
173
|
-
return localStorage.getItem("plugeen_opt_out") === "true";
|
|
174
|
-
} catch {
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function detectBot() {
|
|
179
|
-
if (typeof navigator === "undefined") return false;
|
|
180
|
-
const ua = navigator.userAgent || "";
|
|
181
|
-
return Boolean(
|
|
182
|
-
navigator.webdriver || /HeadlessChrome/i.test(ua) || /PhantomJS/i.test(ua) || typeof window !== "undefined" && window._phantom
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// src/helpers/should-skip.ts
|
|
187
|
-
function matchesSkipPattern(patterns) {
|
|
188
|
-
if (patterns.length === 0 || typeof window === "undefined") return false;
|
|
189
|
-
const path = window.location.pathname;
|
|
190
|
-
for (const pattern of patterns) {
|
|
191
|
-
if (pattern === path) return true;
|
|
192
|
-
const star = pattern.indexOf("*");
|
|
193
|
-
if (star !== -1 && path.startsWith(pattern.slice(0, star))) return true;
|
|
194
|
-
}
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
function shouldSkip(options) {
|
|
198
|
-
if (options.disabled) return true;
|
|
199
|
-
if (isOptedOut()) return true;
|
|
200
|
-
if (detectBot()) return true;
|
|
201
|
-
if (!options.debug && isLocalhost()) return true;
|
|
202
|
-
if (matchesSkipPattern(options.skipPatterns)) return true;
|
|
203
|
-
if (options.samplingRate < 1 && Math.random() > options.samplingRate)
|
|
204
|
-
return true;
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// src/modules/events.ts
|
|
209
|
-
function createEventsModule(http, options) {
|
|
156
|
+
// src/plugins/events/index.ts
|
|
157
|
+
function createEventsModule(http) {
|
|
210
158
|
return {
|
|
211
159
|
create: async (name, data = {}) => {
|
|
212
|
-
|
|
213
|
-
return http.post("/api/events", { name, data, source: "api" });
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// src/modules/experiments.ts
|
|
219
|
-
function createExperimentsModule(http, options) {
|
|
220
|
-
return {
|
|
221
|
-
get: (id) => {
|
|
222
|
-
if (shouldSkip(options)) return Promise.resolve(null);
|
|
223
|
-
return http.get(
|
|
224
|
-
`/api/v1/experiments/${encodeURIComponent(id)}`
|
|
225
|
-
);
|
|
160
|
+
return http.post("/events", { name, data, source: "api" });
|
|
226
161
|
}
|
|
227
162
|
};
|
|
228
163
|
}
|
|
229
164
|
|
|
230
|
-
// src/
|
|
231
|
-
function
|
|
165
|
+
// src/plugins/identities/index.ts
|
|
166
|
+
function createIdentitiesModule(http) {
|
|
232
167
|
return {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return http.get(
|
|
236
|
-
`/api/v1/feature-flags/${encodeURIComponent(key)}`
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// src/modules/identities.ts
|
|
243
|
-
function createIdentitiesModule(http, options) {
|
|
244
|
-
return {
|
|
245
|
-
set: async (distinctId, data = {}) => {
|
|
246
|
-
if (shouldSkip(options)) return null;
|
|
247
|
-
const result = await http.post("/api/identities", {
|
|
168
|
+
create: async (distinctId, data = {}) => {
|
|
169
|
+
const result = await http.post("/identities", {
|
|
248
170
|
id: distinctId,
|
|
249
171
|
...data
|
|
250
172
|
});
|
|
@@ -254,52 +176,15 @@ function createIdentitiesModule(http, options) {
|
|
|
254
176
|
};
|
|
255
177
|
}
|
|
256
178
|
|
|
257
|
-
// src/
|
|
258
|
-
function
|
|
259
|
-
return {
|
|
260
|
-
send: (payload) => {
|
|
261
|
-
if (shouldSkip(options)) return Promise.resolve(null);
|
|
262
|
-
return http.post("/api/v1/logs", payload);
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// src/modules/surveys.ts
|
|
268
|
-
function createSurveysModule(http, options) {
|
|
269
|
-
return {
|
|
270
|
-
submit: (payload) => {
|
|
271
|
-
if (shouldSkip(options)) return Promise.resolve(null);
|
|
272
|
-
return http.post("/api/v1/surveys", payload);
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// src/plugins/analytics.ts
|
|
278
|
-
function initAnalyticsPlugin(plugeen, http) {
|
|
179
|
+
// src/plugins/analytics/index.ts
|
|
180
|
+
function initAnalyticsPlugin(http) {
|
|
279
181
|
if (typeof window === "undefined") return;
|
|
280
182
|
let pageStartTime = Date.now();
|
|
281
|
-
let maxScrollDepth = 0;
|
|
282
|
-
let interactionCount = 0;
|
|
283
183
|
let pageCount = 0;
|
|
284
184
|
let currentUrl = window.location.href;
|
|
285
|
-
const updateScrollDepth = () => {
|
|
286
|
-
const scrollY = window.scrollY;
|
|
287
|
-
const { scrollHeight, clientHeight } = document.documentElement;
|
|
288
|
-
const available = scrollHeight - clientHeight;
|
|
289
|
-
if (available <= 0) return;
|
|
290
|
-
const depth = Math.min(100, Math.round(scrollY / available * 100));
|
|
291
|
-
if (depth > maxScrollDepth) maxScrollDepth = depth;
|
|
292
|
-
};
|
|
293
|
-
window.addEventListener("scroll", updateScrollDepth, { passive: true });
|
|
294
|
-
const countInteraction = () => {
|
|
295
|
-
interactionCount++;
|
|
296
|
-
};
|
|
297
|
-
for (const evt of ["mousedown", "keydown", "touchstart"]) {
|
|
298
|
-
window.addEventListener(evt, countInteraction, { passive: true });
|
|
299
|
-
}
|
|
300
185
|
const trackPageView = () => {
|
|
301
186
|
pageCount++;
|
|
302
|
-
|
|
187
|
+
void http.post("/v1/analytics", {
|
|
303
188
|
event: "page_view",
|
|
304
189
|
url: window.location.href,
|
|
305
190
|
title: document.title,
|
|
@@ -307,33 +192,31 @@ function initAnalyticsPlugin(plugeen, http) {
|
|
|
307
192
|
referrer: document.referrer || void 0,
|
|
308
193
|
screenWidth: window.innerWidth,
|
|
309
194
|
screenHeight: window.innerHeight
|
|
310
|
-
};
|
|
311
|
-
void http.post("/api/v1/analytics", body);
|
|
195
|
+
});
|
|
312
196
|
};
|
|
313
|
-
const buildPageExitData = () => ({
|
|
314
|
-
url: currentUrl,
|
|
315
|
-
time_on_page: Math.round((Date.now() - pageStartTime) / 1e3),
|
|
316
|
-
scroll_depth: maxScrollDepth,
|
|
317
|
-
interaction_count: interactionCount,
|
|
318
|
-
page_count: pageCount
|
|
319
|
-
});
|
|
320
197
|
const trackPageExit = (useBeacon = false) => {
|
|
321
|
-
const data =
|
|
198
|
+
const data = {
|
|
199
|
+
event: "page_exit",
|
|
200
|
+
url: window.location.href,
|
|
201
|
+
title: document.title,
|
|
202
|
+
sessionId: getOrCreateSessionId(),
|
|
203
|
+
referrer: document.referrer || void 0,
|
|
204
|
+
screenWidth: window.innerWidth,
|
|
205
|
+
screenHeight: window.innerHeight,
|
|
206
|
+
metadata: {
|
|
207
|
+
timeOnPage: Math.round((Date.now() - pageStartTime) / 1e3),
|
|
208
|
+
pageCount
|
|
209
|
+
}
|
|
210
|
+
};
|
|
322
211
|
if (useBeacon) {
|
|
323
|
-
http.
|
|
324
|
-
"/api/events",
|
|
325
|
-
{ name: "page_exit", data, source: "api" },
|
|
326
|
-
true
|
|
327
|
-
);
|
|
212
|
+
http.beacon("/v1/analytics", data);
|
|
328
213
|
} else {
|
|
329
|
-
void
|
|
214
|
+
void http.post("/v1/analytics", data);
|
|
330
215
|
}
|
|
331
216
|
};
|
|
332
217
|
const onRouteChange = () => {
|
|
333
218
|
if (window.location.href === currentUrl) return;
|
|
334
219
|
trackPageExit();
|
|
335
|
-
maxScrollDepth = 0;
|
|
336
|
-
interactionCount = 0;
|
|
337
220
|
pageStartTime = Date.now();
|
|
338
221
|
currentUrl = window.location.href;
|
|
339
222
|
trackPageView();
|
|
@@ -348,9 +231,7 @@ function initAnalyticsPlugin(plugeen, http) {
|
|
|
348
231
|
patchHistoryMethod("pushState");
|
|
349
232
|
patchHistoryMethod("replaceState");
|
|
350
233
|
window.addEventListener("popstate", onRouteChange);
|
|
351
|
-
window.addEventListener("beforeunload", () =>
|
|
352
|
-
trackPageExit(true);
|
|
353
|
-
});
|
|
234
|
+
window.addEventListener("beforeunload", () => trackPageExit(true));
|
|
354
235
|
document.addEventListener("visibilitychange", () => {
|
|
355
236
|
if (document.visibilityState === "hidden") {
|
|
356
237
|
trackPageExit(true);
|
|
@@ -359,7 +240,7 @@ function initAnalyticsPlugin(plugeen, http) {
|
|
|
359
240
|
trackPageView();
|
|
360
241
|
}
|
|
361
242
|
|
|
362
|
-
// src/plugins/
|
|
243
|
+
// src/plugins/logs/index.ts
|
|
363
244
|
var EXTENSION_PREFIXES = [
|
|
364
245
|
"chrome-extension://",
|
|
365
246
|
"moz-extension://",
|
|
@@ -371,13 +252,13 @@ function isExtensionSource(str) {
|
|
|
371
252
|
const lower = str.toLowerCase();
|
|
372
253
|
return EXTENSION_PREFIXES.some((prefix) => lower.includes(prefix));
|
|
373
254
|
}
|
|
374
|
-
function initErrorsPlugin(
|
|
255
|
+
function initErrorsPlugin(http) {
|
|
375
256
|
if (typeof window === "undefined") return;
|
|
376
257
|
window.addEventListener("error", (event) => {
|
|
377
258
|
if (isExtensionSource(event.filename) || isExtensionSource(event.error?.stack))
|
|
378
259
|
return;
|
|
379
260
|
if (event.error === null && event.message === "Script error.") return;
|
|
380
|
-
void
|
|
261
|
+
void http.post("/v1/logs", {
|
|
381
262
|
message: event.message || "Unknown Error",
|
|
382
263
|
filename: event.filename,
|
|
383
264
|
lineno: event.lineno,
|
|
@@ -402,7 +283,7 @@ function initErrorsPlugin(plugeen) {
|
|
|
402
283
|
} else if (reason !== null && typeof reason === "object" && "message" in reason) {
|
|
403
284
|
message = String(reason.message);
|
|
404
285
|
}
|
|
405
|
-
void
|
|
286
|
+
void http.post("/v1/logs", {
|
|
406
287
|
message,
|
|
407
288
|
stack,
|
|
408
289
|
error_type: "UnhandledRejection"
|
|
@@ -592,9 +473,9 @@ function initWebVitals(cb) {
|
|
|
592
473
|
}
|
|
593
474
|
|
|
594
475
|
// src/plugins/web-vitals/index.ts
|
|
595
|
-
function initWebVitalsPlugin(
|
|
476
|
+
function initWebVitalsPlugin(http) {
|
|
596
477
|
initWebVitals((metric) => {
|
|
597
|
-
|
|
478
|
+
http.post("/v1/web-vitals", {
|
|
598
479
|
name: metric.name,
|
|
599
480
|
value: metric.value,
|
|
600
481
|
...metric.rating !== void 0 ? { rating: metric.rating } : {}
|
|
@@ -603,50 +484,41 @@ function initWebVitalsPlugin(plugeen) {
|
|
|
603
484
|
}
|
|
604
485
|
|
|
605
486
|
// src/plugins/index.ts
|
|
606
|
-
function initPlugins(
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}
|
|
487
|
+
function initPlugins(http, plugins) {
|
|
488
|
+
const initializers = {
|
|
489
|
+
analytics: initAnalyticsPlugin(http),
|
|
490
|
+
"web-vitals": initWebVitalsPlugin(http),
|
|
491
|
+
errors: initErrorsPlugin(http)
|
|
492
|
+
};
|
|
493
|
+
plugins.map((p) => initializers[p]);
|
|
612
494
|
}
|
|
613
495
|
|
|
614
496
|
// src/index.ts
|
|
497
|
+
var defaults = {
|
|
498
|
+
baseUrl: "https://dev.plugeen.app/api",
|
|
499
|
+
debug: false,
|
|
500
|
+
plugins: []
|
|
501
|
+
};
|
|
615
502
|
function createPlugeen(apiKey, options) {
|
|
616
|
-
if (!apiKey || typeof apiKey !== "string") {
|
|
617
|
-
throw new TypeError("[plugeen] apiKey is required");
|
|
618
|
-
}
|
|
619
|
-
if (!options?.apiUrl || typeof options.apiUrl !== "string") {
|
|
620
|
-
throw new TypeError("[plugeen] apiUrl is required");
|
|
621
|
-
}
|
|
622
503
|
const resolved = {
|
|
623
|
-
|
|
624
|
-
debug: options.debug
|
|
625
|
-
|
|
626
|
-
samplingRate: options.samplingRate ?? 1,
|
|
627
|
-
plugins: options.plugins ?? [],
|
|
628
|
-
skipPatterns: options.skipPatterns ?? []
|
|
504
|
+
baseUrl: options.baseUrl || defaults.baseUrl,
|
|
505
|
+
debug: options.debug || defaults.debug,
|
|
506
|
+
plugins: options.plugins || defaults.plugins
|
|
629
507
|
};
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
const
|
|
635
|
-
const
|
|
636
|
-
const experiments = createExperimentsModule(http, resolved);
|
|
508
|
+
if (!apiKey) {
|
|
509
|
+
throw new TypeError("[plugeen] apiKey is required");
|
|
510
|
+
}
|
|
511
|
+
const http = new HttpClient(resolved.baseUrl, apiKey);
|
|
512
|
+
const events = createEventsModule(http);
|
|
513
|
+
const identities = createIdentitiesModule(http);
|
|
637
514
|
const plugeen = {
|
|
638
515
|
track: (event, properties) => {
|
|
639
|
-
void events.create(event, properties
|
|
516
|
+
void events.create(event, properties);
|
|
640
517
|
},
|
|
641
|
-
|
|
642
|
-
identities,
|
|
643
|
-
featureFlags,
|
|
644
|
-
logs,
|
|
645
|
-
surveys,
|
|
646
|
-
experiments
|
|
518
|
+
identify: (userId, data) => identities.create(userId, data)
|
|
647
519
|
};
|
|
648
520
|
if (resolved.plugins.length > 0) {
|
|
649
|
-
initPlugins(
|
|
521
|
+
initPlugins(http, resolved.plugins);
|
|
650
522
|
}
|
|
651
523
|
return plugeen;
|
|
652
524
|
}
|
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 _(){if(typeof window>"u")return!1;let{hostname:e}=window.location;return e==="localhost"||e==="127.0.0.1"||e==="[::1]"||e==="0.0.0.0"||e.endsWith(".local")}function U(){try{return localStorage.getItem("plugeen_opt_out")==="true"}catch{return!1}}function F(){if(typeof navigator>"u")return!1;let e=navigator.userAgent||"";return!!(navigator.webdriver||/HeadlessChrome/i.test(e)||/PhantomJS/i.test(e)||typeof window<"u"&&window._phantom)}function L(){if(typeof document>"u")return null;let e=document.currentScript,t="";if(!e){let l=document.getElementsByTagName("script");for(let p of Array.from(l))if(p.src&&(p.src.includes("/plugeen.global")||p.src.includes("/plugeen."))){e=p;break}}if(!e)return null;let n={},r=e.getAttribute("data-api-key")??e.getAttribute("data-client-id");r&&(t=r);let i=e.getAttribute("data-api-url");i&&(n.apiUrl=i);let o=e.getAttribute("data-plugins");o&&(n.plugins=o.split(",").map(l=>l.trim()).filter(Boolean));let s=e.getAttribute("data-debug");s!==null&&(n.debug=s==="true"||s==="");let c=e.getAttribute("data-disabled");c!==null&&(n.disabled=c==="true");let d=e.getAttribute("data-sampling-rate");return d!==null&&(n.samplingRate=Number(d)),{apiKey:t,...n}}function y(){if(typeof crypto<"u"&&typeof crypto.randomUUID=="function")return crypto.randomUUID();if(typeof crypto<"u"&&typeof crypto.getRandomValues=="function"){let e=new Uint8Array(16);crypto.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=Array.from(e).map(n=>n.toString(16).padStart(2,"0")).join("");return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`}return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.floor(Math.random()*16);return(e==="x"?t:t&3|8).toString(16)})}var I="plugeen_anon_id",k="plugeen_session_id",h="plugeen_session_ts";var z=18e5;function A(){try{let e=localStorage.getItem(I);return e||(e=`anon_${y()}`,localStorage.setItem(I,e)),e}catch{return`anon_${y()}`}}function H(e){try{localStorage.setItem(I,e)}catch{}}function w(){try{let e=sessionStorage.getItem(k),t=sessionStorage.getItem(h);if(e&&t&&Date.now()-parseInt(t,10)<z)return sessionStorage.setItem(h,String(Date.now())),e;sessionStorage.removeItem(k),sessionStorage.removeItem(h);let n=`sess_${y()}`;return sessionStorage.setItem(k,n),sessionStorage.setItem(h,String(Date.now())),n}catch{return`sess_${y()}`}}var x=e=>new Promise(t=>setTimeout(t,e)),S=class{constructor(t,n,r=3){this.apiUrl=t;this.apiKey=n;this.maxRetries=r}headers(t="POST"){let n={Authorization:`Bearer ${this.apiKey}`,"x-identity-id":A(),"x-session-id":w()};return t!=="GET"&&(n["Content-Type"]="application/json"),n}async post(t,n,r=0){let i=`${this.apiUrl}${t}`;try{let o=await fetch(i,{method:"POST",headers:this.headers("POST"),body:JSON.stringify(n),keepalive:!0,credentials:"omit"});if(o.status===401)return null;if((o.status>=500||o.status===429)&&r<this.maxRetries){let s=.85+Math.random()*.3;return await x(500*2**r*s),this.post(t,n,r+1)}if(o.status>=200&&o.status<300)try{return(await o.json())?.data??null}catch{return null}return null}catch(o){if(o instanceof TypeError&&r<this.maxRetries){let s=.85+Math.random()*.3;return await x(500*2**r*s),this.post(t,n,r+1)}return null}}async get(t,n=0){let r=`${this.apiUrl}${t}`;try{let i=await fetch(r,{method:"GET",headers:this.headers("GET"),credentials:"omit"});if(i.status===401||i.status===404)return null;if((i.status>=500||i.status===429)&&n<this.maxRetries){let o=.85+Math.random()*.3;return await x(500*2**n*o),this.get(t,n+1)}if(i.status>=200&&i.status<300)try{return(await i.json())?.data??null}catch{return null}return null}catch(i){if(i instanceof TypeError&&n<this.maxRetries){let o=.85+Math.random()*.3;return await x(500*2**n*o),this.get(t,n+1)}return null}}beacon(t,n){if(typeof navigator>"u"||!navigator.sendBeacon)return!1;try{let r=new Blob([JSON.stringify(n)],{type:"application/json"});return navigator.sendBeacon(`${this.apiUrl}${t}`,r)}catch{return!1}}send(t,n,r=!1){r&&this.beacon(t,n)||this.post(t,n)}};function X(e){if(e.length===0||typeof window>"u")return!1;let t=window.location.pathname;for(let n of e){if(n===t)return!0;let r=n.indexOf("*");if(r!==-1&&t.startsWith(n.slice(0,r)))return!0}return!1}function a(e){return!!(e.disabled||U()||F()||!e.debug&&_()||X(e.skipPatterns)||e.samplingRate<1&&Math.random()>e.samplingRate)}function N(e,t){return{create:async(n,r={})=>a(t)?null:e.post("/api/events",{name:n,data:r,source:"api"})}}function V(e,t){return{get:n=>a(t)?Promise.resolve(null):e.get(`/api/v1/experiments/${encodeURIComponent(n)}`)}}function j(e,t){return{get:n=>a(t)?Promise.resolve(null):e.get(`/api/v1/feature-flags/${encodeURIComponent(n)}`)}}function $(e,t){return{set:async(n,r={})=>{if(a(t))return null;let i=await e.post("/api/identities",{id:n,...r});return H(n),i}}}function D(e,t){return{send:n=>a(t)?Promise.resolve(null):e.post("/api/v1/logs",n)}}function B(e,t){return{submit:n=>a(t)?Promise.resolve(null):e.post("/api/v1/surveys",n)}}function K(e,t){if(typeof window>"u")return;let n=Date.now(),r=0,i=0,o=0,s=window.location.href,c=()=>{let u=window.scrollY,{scrollHeight:f,clientHeight:T}=document.documentElement,R=f-T;if(R<=0)return;let C=Math.min(100,Math.round(u/R*100));C>r&&(r=C)};window.addEventListener("scroll",c,{passive:!0});let d=()=>{i++};for(let u of["mousedown","keydown","touchstart"])window.addEventListener(u,d,{passive:!0});let l=()=>{o++;let u={event:"page_view",url:window.location.href,title:document.title,sessionId:w(),referrer:document.referrer||void 0,screenWidth:window.innerWidth,screenHeight:window.innerHeight};t.post("/api/v1/analytics",u)},p=()=>({url:s,time_on_page:Math.round((Date.now()-n)/1e3),scroll_depth:r,interaction_count:i,page_count:o}),g=(u=!1)=>{let f=p();u?t.send("/api/events",{name:"page_exit",data:f,source:"api"},!0):e.events.create("page_exit",f)},v=()=>{window.location.href!==s&&(g(),r=0,i=0,n=Date.now(),s=window.location.href,l())},M=u=>{let f=history[u].bind(history);history[u]=(...T)=>{f(...T),v()}};M("pushState"),M("replaceState"),window.addEventListener("popstate",v),window.addEventListener("beforeunload",()=>{g(!0)}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&g(!0)}),l()}var Q=["chrome-extension://","moz-extension://","safari-extension://","edge-extension://"];function O(e){if(!e)return!1;let t=e.toLowerCase();return Q.some(n=>t.includes(n))}function W(e){typeof window>"u"||(window.addEventListener("error",t=>{O(t.filename)||O(t.error?.stack)||t.error===null&&t.message==="Script error."||e.events.create("error",{message:t.message||"Unknown Error",filename:t.filename,lineno:t.lineno,colno:t.colno,stack:t.error?.stack,error_type:t.error?.name||"Error"})}),window.addEventListener("unhandledrejection",t=>{let{reason:n}=t;if(O(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)),e.events.create("error",{message:r,stack:i,error_type:"UnhandledRejection"})}))}function m(e,t){return e>t[1]?"poor":e>t[0]?"needs-improvement":"good"}function P(){return performance.getEntriesByType("navigation")[0]?.activationStart??0}function Z(){let e=performance.getEntriesByType("navigation")[0];if(e&&e.responseStart>0&&e.responseStart<performance.now())return e}function b(e,t,n){try{if(!PerformanceObserver.supportedEntryTypes.includes(e))return;let r=new PerformanceObserver(i=>{Promise.resolve().then(()=>t(i.getEntries()))});return r.observe({type:e,buffered:!0,...n??{}}),r}catch{return}}function ee(e){let t=new Set;b("paint",n=>{for(let r of n)if(r.name==="first-contentful-paint"&&!t.has("FCP")){t.add("FCP");let i=Math.max(r.startTime-P(),0);e({name:"FCP",value:Math.round(i),rating:m(i,[1800,3e3])})}})}function te(e){let t=!1,n=b("largest-contentful-paint",i=>{if(t)return;let o=i[i.length-1];if(o){let s=Math.max(o.startTime-P(),0);e({name:"LCP",value:Math.round(s),rating:m(s,[2500,4e3])})}});if(!n)return;let r=()=>{if(t)return;t=!0;let i=n.takeRecords();if(i.length>0){let o=i[i.length-1],s=Math.max(o.startTime-P(),0);e({name:"LCP",value:Math.round(s),rating:m(s,[2500,4e3])})}n.disconnect()};for(let i of["keydown","click","visibilitychange"])addEventListener(i,r,{capture:!0,once:!0})}function ne(e){let t=0,n=[],r=0;b("layout-shift",i=>{for(let o of i){let s=o;if(s.hadRecentInput)continue;let c=n[n.length-1],d=n[0];if(n.length>0&&c&&d&&s.startTime-c.startTime<1e3&&s.startTime-d.startTime<5e3?(t+=s.value,n.push(s)):(t=s.value,n=[s]),t>r){r=t;let l=Math.round(r*1e4)/1e4;e({name:"CLS",value:l,rating:m(r,[.1,.25])})}}})}function re(e){let t=Z();if(!t)return;let n=Math.max(t.responseStart-P(),0);e({name:"TTFB",value:Math.round(n),rating:m(n,[800,1800])})}function ie(e){let t=new Map,n=0;b("event",r=>{for(let i of r){let o=i;if(!o.interactionId)continue;let s=t.get(o.interactionId)??0;o.duration>s&&(t.set(o.interactionId,o.duration),o.duration>n&&(n=o.duration,e({name:"INP",value:Math.round(o.duration),rating:m(o.duration,[200,500])})))}},{durationThreshold:40})}function oe(e){if(typeof requestAnimationFrame>"u")return;let t=0,n=2e3,r=performance.now(),i=()=>{t++,performance.now()-r<n?requestAnimationFrame(i):e({name:"FPS",value:Math.round(t/n*1e3)})};document.readyState==="complete"?requestAnimationFrame(i):window.addEventListener("load",()=>requestAnimationFrame(i),{once:!0})}function q(e){typeof window>"u"||typeof PerformanceObserver>"u"||(ee(e),te(e),ne(e),re(e),ie(e),oe(e))}function Y(e){q(t=>{e.events.create("web_vital",{name:t.name,value:t.value,...t.rating!==void 0?{rating:t.rating}:{}})})}function J(e,t,n){for(let r of n)r==="analytics"?K(e,t):r==="web-vitals"?Y(e):r==="errors"&&W(e)}function G(e,t){if(!e||typeof e!="string")throw new TypeError("[plugeen] apiKey is required");if(!t?.apiUrl||typeof t.apiUrl!="string")throw new TypeError("[plugeen] apiUrl is required");let n={apiUrl:t.apiUrl,debug:t.debug??!1,disabled:t.disabled??!1,samplingRate:t.samplingRate??1,plugins:t.plugins??[],skipPatterns:t.skipPatterns??[]},r=new S(n.apiUrl,e),i=N(r,n),o=$(r,n),s=j(r,n),c=D(r,n),d=B(r,n),l=V(r,n),p={track:(g,v)=>{i.create(g,v??{})},events:i,identities:o,featureFlags:s,logs:c,surveys:d,experiments:l};return n.plugins.length>0&&J(p,r,n.plugins),p}var E=L();if(!E?.apiKey||!E?.apiUrl)console.warn("[plugeen] data-api-key and data-api-url attributes are required.");else{let e=G(E.apiKey,E);window.plugeen=e}})();
|
|
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}})();
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plugeen",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Lightweight plugin-based browser analytics SDK",
|
|
5
|
-
"type": "module",
|
|
3
|
+
"version": "0.0.9",
|
|
6
4
|
"main": "./dist/index.cjs",
|
|
7
5
|
"module": "./dist/index.js",
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"@biomejs/biome": "2.4.14",
|
|
8
|
+
"@types/node": "25.6.1",
|
|
9
|
+
"concurrently": "9.2.1",
|
|
10
|
+
"tsup": "^8.5.0",
|
|
11
|
+
"typescript": "^5.8.0"
|
|
12
|
+
},
|
|
12
13
|
"exports": {
|
|
13
14
|
".": {
|
|
14
15
|
"types": "./dist/index.d.ts",
|
|
@@ -16,18 +17,19 @@
|
|
|
16
17
|
"require": "./dist/index.cjs"
|
|
17
18
|
}
|
|
18
19
|
},
|
|
20
|
+
"browser": "./dist/index.js",
|
|
21
|
+
"description": "Lightweight plugin-based browser analytics SDK",
|
|
19
22
|
"files": [
|
|
20
23
|
"dist"
|
|
21
24
|
],
|
|
25
|
+
"jsdelivr": "./dist/plugeen.global.js",
|
|
22
26
|
"scripts": {
|
|
23
27
|
"build": "tsup",
|
|
24
|
-
"dev": "tsup --watch",
|
|
28
|
+
"dev": "concurrently 'tsup --watch' 'bun --watch ./src/demo/index.ts'",
|
|
25
29
|
"biome": "biome check",
|
|
26
30
|
"typecheck": "tsc --project tsconfig.json --noEmit"
|
|
27
31
|
},
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
}
|
|
32
|
+
"type": "module",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"unpkg": "./dist/plugeen.global.js"
|
|
35
|
+
}
|
package/dist/index.d.cts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
type PluginName = "analytics" | "web-vitals" | "errors";
|
|
2
|
-
interface SdkOptions {
|
|
3
|
-
apiUrl: string;
|
|
4
|
-
plugins?: PluginName[];
|
|
5
|
-
debug?: boolean;
|
|
6
|
-
disabled?: boolean;
|
|
7
|
-
samplingRate?: number;
|
|
8
|
-
skipPatterns?: string[];
|
|
9
|
-
}
|
|
10
|
-
type EventProperties = Record<string, string | number | boolean | null | undefined | Record<string, unknown>>;
|
|
11
|
-
interface Identity {
|
|
12
|
-
id: string;
|
|
13
|
-
name?: string;
|
|
14
|
-
email?: string;
|
|
15
|
-
metadata?: Record<string, unknown>;
|
|
16
|
-
}
|
|
17
|
-
interface LogPayload {
|
|
18
|
-
level: "debug" | "info" | "warn" | "error" | "fatal";
|
|
19
|
-
message: string;
|
|
20
|
-
traceId?: string;
|
|
21
|
-
spanId?: string;
|
|
22
|
-
service?: string;
|
|
23
|
-
environment?: string;
|
|
24
|
-
route?: string;
|
|
25
|
-
release?: string;
|
|
26
|
-
runtime?: string;
|
|
27
|
-
file?: string;
|
|
28
|
-
line?: number;
|
|
29
|
-
stack?: string;
|
|
30
|
-
}
|
|
31
|
-
interface SurveyPayload {
|
|
32
|
-
surveyId: string;
|
|
33
|
-
question: string;
|
|
34
|
-
response: string;
|
|
35
|
-
rating?: number;
|
|
36
|
-
sentiment?: "positive" | "neutral" | "negative";
|
|
37
|
-
path?: string;
|
|
38
|
-
}
|
|
39
|
-
interface FeatureFlag {
|
|
40
|
-
key: string;
|
|
41
|
-
enabled: boolean;
|
|
42
|
-
value?: string;
|
|
43
|
-
description?: string;
|
|
44
|
-
}
|
|
45
|
-
interface Experiment {
|
|
46
|
-
experimentKey: string;
|
|
47
|
-
variant: string;
|
|
48
|
-
userId?: string;
|
|
49
|
-
metric?: string;
|
|
50
|
-
converted?: boolean;
|
|
51
|
-
value?: number;
|
|
52
|
-
}
|
|
53
|
-
type TrackFn = (event: string, properties?: EventProperties) => void;
|
|
54
|
-
interface EventsModule {
|
|
55
|
-
create<T = unknown>(name: string, data?: EventProperties): Promise<T | null>;
|
|
56
|
-
}
|
|
57
|
-
interface IdentitiesModule {
|
|
58
|
-
set(distinctId: string, data?: Omit<Identity, "id">): Promise<Identity | null>;
|
|
59
|
-
}
|
|
60
|
-
interface FeatureFlagsModule {
|
|
61
|
-
get(key: string): Promise<FeatureFlag | null>;
|
|
62
|
-
}
|
|
63
|
-
interface LogsModule {
|
|
64
|
-
send<T = unknown>(payload: LogPayload): Promise<T | null>;
|
|
65
|
-
}
|
|
66
|
-
interface SurveysModule {
|
|
67
|
-
submit<T = unknown>(payload: SurveyPayload): Promise<T | null>;
|
|
68
|
-
}
|
|
69
|
-
interface ExperimentsModule {
|
|
70
|
-
get(id: string): Promise<Experiment | null>;
|
|
71
|
-
}
|
|
72
|
-
interface Plugeen {
|
|
73
|
-
track: TrackFn;
|
|
74
|
-
events: EventsModule;
|
|
75
|
-
identities: IdentitiesModule;
|
|
76
|
-
featureFlags: FeatureFlagsModule;
|
|
77
|
-
logs: LogsModule;
|
|
78
|
-
surveys: SurveysModule;
|
|
79
|
-
experiments: ExperimentsModule;
|
|
80
|
-
}
|
|
81
|
-
declare global {
|
|
82
|
-
interface Window {
|
|
83
|
-
plugeen?: Plugeen;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
declare function createPlugeen(apiKey: string, options: SdkOptions): Plugeen;
|
|
88
|
-
|
|
89
|
-
export { type EventProperties, type Experiment, type FeatureFlag, type Identity, type LogPayload, type Plugeen, type PluginName, type SdkOptions, type SurveyPayload, type TrackFn, createPlugeen };
|
package/dist/index.d.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
type PluginName = "analytics" | "web-vitals" | "errors";
|
|
2
|
-
interface SdkOptions {
|
|
3
|
-
apiUrl: string;
|
|
4
|
-
plugins?: PluginName[];
|
|
5
|
-
debug?: boolean;
|
|
6
|
-
disabled?: boolean;
|
|
7
|
-
samplingRate?: number;
|
|
8
|
-
skipPatterns?: string[];
|
|
9
|
-
}
|
|
10
|
-
type EventProperties = Record<string, string | number | boolean | null | undefined | Record<string, unknown>>;
|
|
11
|
-
interface Identity {
|
|
12
|
-
id: string;
|
|
13
|
-
name?: string;
|
|
14
|
-
email?: string;
|
|
15
|
-
metadata?: Record<string, unknown>;
|
|
16
|
-
}
|
|
17
|
-
interface LogPayload {
|
|
18
|
-
level: "debug" | "info" | "warn" | "error" | "fatal";
|
|
19
|
-
message: string;
|
|
20
|
-
traceId?: string;
|
|
21
|
-
spanId?: string;
|
|
22
|
-
service?: string;
|
|
23
|
-
environment?: string;
|
|
24
|
-
route?: string;
|
|
25
|
-
release?: string;
|
|
26
|
-
runtime?: string;
|
|
27
|
-
file?: string;
|
|
28
|
-
line?: number;
|
|
29
|
-
stack?: string;
|
|
30
|
-
}
|
|
31
|
-
interface SurveyPayload {
|
|
32
|
-
surveyId: string;
|
|
33
|
-
question: string;
|
|
34
|
-
response: string;
|
|
35
|
-
rating?: number;
|
|
36
|
-
sentiment?: "positive" | "neutral" | "negative";
|
|
37
|
-
path?: string;
|
|
38
|
-
}
|
|
39
|
-
interface FeatureFlag {
|
|
40
|
-
key: string;
|
|
41
|
-
enabled: boolean;
|
|
42
|
-
value?: string;
|
|
43
|
-
description?: string;
|
|
44
|
-
}
|
|
45
|
-
interface Experiment {
|
|
46
|
-
experimentKey: string;
|
|
47
|
-
variant: string;
|
|
48
|
-
userId?: string;
|
|
49
|
-
metric?: string;
|
|
50
|
-
converted?: boolean;
|
|
51
|
-
value?: number;
|
|
52
|
-
}
|
|
53
|
-
type TrackFn = (event: string, properties?: EventProperties) => void;
|
|
54
|
-
interface EventsModule {
|
|
55
|
-
create<T = unknown>(name: string, data?: EventProperties): Promise<T | null>;
|
|
56
|
-
}
|
|
57
|
-
interface IdentitiesModule {
|
|
58
|
-
set(distinctId: string, data?: Omit<Identity, "id">): Promise<Identity | null>;
|
|
59
|
-
}
|
|
60
|
-
interface FeatureFlagsModule {
|
|
61
|
-
get(key: string): Promise<FeatureFlag | null>;
|
|
62
|
-
}
|
|
63
|
-
interface LogsModule {
|
|
64
|
-
send<T = unknown>(payload: LogPayload): Promise<T | null>;
|
|
65
|
-
}
|
|
66
|
-
interface SurveysModule {
|
|
67
|
-
submit<T = unknown>(payload: SurveyPayload): Promise<T | null>;
|
|
68
|
-
}
|
|
69
|
-
interface ExperimentsModule {
|
|
70
|
-
get(id: string): Promise<Experiment | null>;
|
|
71
|
-
}
|
|
72
|
-
interface Plugeen {
|
|
73
|
-
track: TrackFn;
|
|
74
|
-
events: EventsModule;
|
|
75
|
-
identities: IdentitiesModule;
|
|
76
|
-
featureFlags: FeatureFlagsModule;
|
|
77
|
-
logs: LogsModule;
|
|
78
|
-
surveys: SurveysModule;
|
|
79
|
-
experiments: ExperimentsModule;
|
|
80
|
-
}
|
|
81
|
-
declare global {
|
|
82
|
-
interface Window {
|
|
83
|
-
plugeen?: Plugeen;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
declare function createPlugeen(apiKey: string, options: SdkOptions): Plugeen;
|
|
88
|
-
|
|
89
|
-
export { type EventProperties, type Experiment, type FeatureFlag, type Identity, type LogPayload, type Plugeen, type PluginName, type SdkOptions, type SurveyPayload, type TrackFn, createPlugeen };
|