plugeen 0.0.6 → 0.0.8
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 +588 -587
- package/dist/index.d.cts +81 -110
- package/dist/index.d.ts +81 -110
- package/dist/index.js +587 -561
- package/dist/plugeen.global.js +2 -18
- package/package.json +17 -32
package/dist/index.cjs
CHANGED
|
@@ -1,655 +1,656 @@
|
|
|
1
|
-
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var src_exports = {};
|
|
22
|
-
__export(src_exports, {
|
|
23
|
-
createPlugeen: () => createPlugeen
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(src_exports);
|
|
26
|
-
|
|
27
|
-
// src/lib/helpers/brand.ts
|
|
28
|
-
var APP_NAME = "Plugeen";
|
|
29
|
-
var APP_PREFIX = APP_NAME.toLowerCase().slice(0, 2);
|
|
1
|
+
'use strict';
|
|
30
2
|
|
|
31
|
-
// src/
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
function ensureServerStore() {
|
|
36
|
-
if (!globalThis.Plugeen) {
|
|
37
|
-
globalThis.Plugeen = {};
|
|
3
|
+
// src/helpers/uuid.ts
|
|
4
|
+
function generateUUID() {
|
|
5
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
6
|
+
return crypto.randomUUID();
|
|
38
7
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
};
|
|
8
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
9
|
+
const bytes = new Uint8Array(16);
|
|
10
|
+
crypto.getRandomValues(bytes);
|
|
11
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
12
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
13
|
+
const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
14
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
15
|
+
}
|
|
16
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
17
|
+
const r = Math.floor(Math.random() * 16);
|
|
18
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
19
|
+
});
|
|
51
20
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (typeof raw === "string") return raw;
|
|
65
|
-
return JSON.parse(raw);
|
|
66
|
-
} catch {
|
|
67
|
-
return void 0;
|
|
21
|
+
|
|
22
|
+
// src/storage.ts
|
|
23
|
+
var IDENTITY_KEY = "plugeen_anon_id";
|
|
24
|
+
var SESSION_KEY = "plugeen_session_id";
|
|
25
|
+
var SESSION_TS_KEY = "plugeen_session_ts";
|
|
26
|
+
var SESSION_TTL = 18e5;
|
|
27
|
+
function getOrCreateIdentityId() {
|
|
28
|
+
try {
|
|
29
|
+
let id = localStorage.getItem(IDENTITY_KEY);
|
|
30
|
+
if (!id) {
|
|
31
|
+
id = `anon_${generateUUID()}`;
|
|
32
|
+
localStorage.setItem(IDENTITY_KEY, id);
|
|
68
33
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
function getStorage() {
|
|
76
|
-
if (typeof window === "undefined") return serverStorage();
|
|
77
|
-
return clientStorage();
|
|
34
|
+
return id;
|
|
35
|
+
} catch {
|
|
36
|
+
return `anon_${generateUUID()}`;
|
|
37
|
+
}
|
|
78
38
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (identity) return identity;
|
|
85
|
-
const newId = crypto.randomUUID();
|
|
86
|
-
storage.set("identity", newId);
|
|
87
|
-
return newId;
|
|
39
|
+
function setIdentityId(id) {
|
|
40
|
+
try {
|
|
41
|
+
localStorage.setItem(IDENTITY_KEY, id);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
88
44
|
}
|
|
89
|
-
|
|
90
|
-
// src/lib/helpers/session.ts
|
|
91
|
-
var SESSION_KEY = "pl_session";
|
|
92
45
|
function getOrCreateSessionId() {
|
|
93
46
|
try {
|
|
94
|
-
if (typeof window === "undefined") return "";
|
|
95
47
|
const existing = sessionStorage.getItem(SESSION_KEY);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
48
|
+
const ts = sessionStorage.getItem(SESSION_TS_KEY);
|
|
49
|
+
if (existing && ts && Date.now() - parseInt(ts, 10) < SESSION_TTL) {
|
|
50
|
+
sessionStorage.setItem(SESSION_TS_KEY, String(Date.now()));
|
|
51
|
+
return existing;
|
|
52
|
+
}
|
|
53
|
+
sessionStorage.removeItem(SESSION_KEY);
|
|
54
|
+
sessionStorage.removeItem(SESSION_TS_KEY);
|
|
55
|
+
const id = `sess_${generateUUID()}`;
|
|
100
56
|
sessionStorage.setItem(SESSION_KEY, id);
|
|
57
|
+
sessionStorage.setItem(SESSION_TS_KEY, String(Date.now()));
|
|
101
58
|
return id;
|
|
59
|
+
} catch {
|
|
60
|
+
return `sess_${generateUUID()}`;
|
|
102
61
|
}
|
|
103
62
|
}
|
|
104
63
|
|
|
105
|
-
// src/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (method === "POST" || method === "PUT") {
|
|
113
|
-
headers.set("Content-Type", "application/json");
|
|
64
|
+
// src/helpers/http-client.ts
|
|
65
|
+
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
66
|
+
var HttpClient = class {
|
|
67
|
+
constructor(apiUrl, apiKey, maxRetries = 3) {
|
|
68
|
+
this.apiUrl = apiUrl;
|
|
69
|
+
this.apiKey = apiKey;
|
|
70
|
+
this.maxRetries = maxRetries;
|
|
114
71
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
body: params.body ? JSON.stringify({ ...params.body, source: "api" }) : void 0,
|
|
121
|
-
headers: getHeaders(params.apiKey, params.method)
|
|
122
|
-
}).then((response) => response.json()).then((response) => response.data);
|
|
123
|
-
}
|
|
124
|
-
var createApi = (apiKey, options) => {
|
|
125
|
-
return {
|
|
126
|
-
post: (url, body) => fetcher({ url, method: "POST", body, options, apiKey }),
|
|
127
|
-
put: (url, body) => fetcher({ url, method: "PUT", body, options, apiKey }),
|
|
128
|
-
get: (url) => fetcher({ url, method: "GET", options, apiKey })
|
|
129
|
-
};
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
// src/lib/plugins/analytics/index.ts
|
|
133
|
-
function initAnalytics(api) {
|
|
134
|
-
const init = () => {
|
|
135
|
-
const changePage = () => {
|
|
136
|
-
api.post("/plugins/analytics", {
|
|
137
|
-
event: "page_view",
|
|
138
|
-
url: location.href,
|
|
139
|
-
title: document.title,
|
|
140
|
-
referrer: document.referrer ?? "",
|
|
141
|
-
screenWidth: window.screen.width,
|
|
142
|
-
screenHeight: window.screen.height,
|
|
143
|
-
sessionId: getOrCreateSessionId()
|
|
144
|
-
});
|
|
145
|
-
};
|
|
146
|
-
const pushState = history.pushState;
|
|
147
|
-
history.pushState = function(...args) {
|
|
148
|
-
pushState.apply(this, args);
|
|
149
|
-
changePage();
|
|
150
|
-
};
|
|
151
|
-
const replaceState = history.replaceState;
|
|
152
|
-
history.replaceState = function(...args) {
|
|
153
|
-
replaceState.apply(this, args);
|
|
154
|
-
changePage;
|
|
72
|
+
headers(method = "POST") {
|
|
73
|
+
const h = {
|
|
74
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
75
|
+
"x-identity-id": getOrCreateIdentityId(),
|
|
76
|
+
"x-session-id": getOrCreateSessionId()
|
|
155
77
|
};
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// src/lib/plugins/chat/index.tsx
|
|
163
|
-
var import_signals2 = require("@preact/signals");
|
|
164
|
-
var import_lucide_preact = require("lucide-preact");
|
|
165
|
-
|
|
166
|
-
// src/store/theme.ts
|
|
167
|
-
var import_signals = require("@preact/signals");
|
|
168
|
-
var theme = (0, import_signals.signal)({
|
|
169
|
-
accentColor: "",
|
|
170
|
-
foregroundColor: ""
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// src/components/button.tsx
|
|
174
|
-
var import_jsx_runtime = require("preact/jsx-runtime");
|
|
175
|
-
function Button({ children, disabled, fullWidth }) {
|
|
176
|
-
const deactivated = disabled;
|
|
177
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
178
|
-
"button",
|
|
179
|
-
{
|
|
180
|
-
type: "submit",
|
|
181
|
-
disabled: deactivated,
|
|
182
|
-
style: {
|
|
183
|
-
background: theme.value.accentColor,
|
|
184
|
-
color: theme.value.foregroundColor,
|
|
185
|
-
width: fullWidth ? "100%" : "max-content",
|
|
186
|
-
border: "none",
|
|
187
|
-
borderRadius: "8px",
|
|
188
|
-
padding: "8px 0",
|
|
189
|
-
fontWeight: "600",
|
|
190
|
-
fontSize: "13px",
|
|
191
|
-
opacity: deactivated ? 0.5 : 1,
|
|
192
|
-
cursor: deactivated ? "not allowed" : "pointer",
|
|
193
|
-
transition: "all 0.4s ease"
|
|
194
|
-
},
|
|
195
|
-
children
|
|
196
|
-
}
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// src/hooks/use-query.ts
|
|
201
|
-
var import_hooks = require("preact/hooks");
|
|
202
|
-
var cache = /* @__PURE__ */ new Map();
|
|
203
|
-
function serializeKey(key) {
|
|
204
|
-
return typeof key === "string" ? key : JSON.stringify(key);
|
|
205
|
-
}
|
|
206
|
-
function useQuery({
|
|
207
|
-
queryKey,
|
|
208
|
-
queryFn,
|
|
209
|
-
revalidateTime = 0
|
|
210
|
-
}) {
|
|
211
|
-
const key = serializeKey(queryKey);
|
|
212
|
-
const [state, setState] = (0, import_hooks.useState)({
|
|
213
|
-
data: void 0,
|
|
214
|
-
error: void 0,
|
|
215
|
-
isLoading: true,
|
|
216
|
-
isFetching: false
|
|
217
|
-
});
|
|
218
|
-
const mounted = (0, import_hooks.useRef)(true);
|
|
219
|
-
async function fetchData(initial = false) {
|
|
78
|
+
if (method !== "GET") h["Content-Type"] = "application/json";
|
|
79
|
+
return h;
|
|
80
|
+
}
|
|
81
|
+
async post(path, body, attempt = 0) {
|
|
82
|
+
const url = `${this.apiUrl}${path}`;
|
|
220
83
|
try {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const data = await queryFn();
|
|
228
|
-
cache.set(key, {
|
|
229
|
-
data,
|
|
230
|
-
updatedAt: Date.now()
|
|
231
|
-
});
|
|
232
|
-
if (!mounted.current) return;
|
|
233
|
-
setState({
|
|
234
|
-
data,
|
|
235
|
-
error: void 0,
|
|
236
|
-
isLoading: false,
|
|
237
|
-
isFetching: false
|
|
238
|
-
});
|
|
239
|
-
} catch (error) {
|
|
240
|
-
if (!mounted.current) return;
|
|
241
|
-
setState({
|
|
242
|
-
data: void 0,
|
|
243
|
-
error,
|
|
244
|
-
isLoading: false,
|
|
245
|
-
isFetching: false
|
|
84
|
+
const res = await fetch(url, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: this.headers("POST"),
|
|
87
|
+
body: JSON.stringify(body),
|
|
88
|
+
keepalive: true,
|
|
89
|
+
credentials: "omit"
|
|
246
90
|
});
|
|
91
|
+
if (res.status === 401) return null;
|
|
92
|
+
if ((res.status >= 500 || res.status === 429) && attempt < this.maxRetries) {
|
|
93
|
+
const jitter = 0.85 + Math.random() * 0.3;
|
|
94
|
+
await delay(500 * 2 ** attempt * jitter);
|
|
95
|
+
return this.post(path, body, attempt + 1);
|
|
96
|
+
}
|
|
97
|
+
if (res.status >= 200 && res.status < 300) {
|
|
98
|
+
try {
|
|
99
|
+
const json = await res.json();
|
|
100
|
+
return json?.data ?? null;
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
if (err instanceof TypeError && attempt < this.maxRetries) {
|
|
108
|
+
const jitter = 0.85 + Math.random() * 0.3;
|
|
109
|
+
await delay(500 * 2 ** attempt * jitter);
|
|
110
|
+
return this.post(path, body, attempt + 1);
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
247
113
|
}
|
|
248
114
|
}
|
|
249
|
-
(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
error: void 0,
|
|
257
|
-
isLoading: false,
|
|
258
|
-
isFetching: isStale
|
|
115
|
+
async get(path, attempt = 0) {
|
|
116
|
+
const url = `${this.apiUrl}${path}`;
|
|
117
|
+
try {
|
|
118
|
+
const res = await fetch(url, {
|
|
119
|
+
method: "GET",
|
|
120
|
+
headers: this.headers("GET"),
|
|
121
|
+
credentials: "omit"
|
|
259
122
|
});
|
|
260
|
-
if (
|
|
261
|
-
|
|
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);
|
|
262
128
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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;
|
|
276
145
|
}
|
|
277
|
-
return () => {
|
|
278
|
-
clearInterval(interval);
|
|
279
|
-
};
|
|
280
|
-
}, [revalidateTime]);
|
|
281
|
-
return {
|
|
282
|
-
...state,
|
|
283
|
-
refetch: () => fetchData(false)
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// src/lib/helpers/ui.ts
|
|
288
|
-
var import_preact = require("preact");
|
|
289
|
-
var noopUnmount = () => {
|
|
290
|
-
};
|
|
291
|
-
function getOrCreateRoot(target, id) {
|
|
292
|
-
const existing = document.getElementById(id);
|
|
293
|
-
if (existing instanceof HTMLDivElement) {
|
|
294
|
-
return existing;
|
|
295
146
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}) {
|
|
306
|
-
if (typeof document === "undefined") {
|
|
307
|
-
return { render: () => void 0, unmount: noopUnmount };
|
|
308
|
-
}
|
|
309
|
-
theme.value = {
|
|
310
|
-
accentColor: options.accentColor,
|
|
311
|
-
foregroundColor: options.foregroundColor
|
|
312
|
-
};
|
|
313
|
-
const target = document.body;
|
|
314
|
-
const mount = () => {
|
|
315
|
-
const container = getOrCreateRoot(document.body, id);
|
|
316
|
-
if (container.parentElement !== target) {
|
|
317
|
-
target.appendChild(container);
|
|
147
|
+
beacon(path, body) {
|
|
148
|
+
if (typeof navigator === "undefined" || !navigator.sendBeacon) return false;
|
|
149
|
+
try {
|
|
150
|
+
const blob = new Blob([JSON.stringify(body)], {
|
|
151
|
+
type: "application/json"
|
|
152
|
+
});
|
|
153
|
+
return navigator.sendBeacon(`${this.apiUrl}${path}`, blob);
|
|
154
|
+
} catch {
|
|
155
|
+
return false;
|
|
318
156
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
unmount: () => {
|
|
325
|
-
const el = document.getElementById(id);
|
|
326
|
-
if (el) {
|
|
327
|
-
(0, import_preact.render)(null, el);
|
|
328
|
-
el.remove();
|
|
329
|
-
}
|
|
157
|
+
}
|
|
158
|
+
send(path, body, useBeacon = false) {
|
|
159
|
+
if (useBeacon) {
|
|
160
|
+
const sent = this.beacon(path, body);
|
|
161
|
+
if (sent) return;
|
|
330
162
|
}
|
|
331
|
-
|
|
332
|
-
}
|
|
163
|
+
void this.post(path, body);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
333
166
|
|
|
334
|
-
// src/
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
function
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
});
|
|
353
|
-
const onSubmit = async () => {
|
|
354
|
-
submitting.value = true;
|
|
355
|
-
await api.post("/plugins/chats", {
|
|
356
|
-
text: text.value
|
|
357
|
-
});
|
|
358
|
-
await refetch();
|
|
359
|
-
text.value = "";
|
|
360
|
-
submitting.value = false;
|
|
361
|
-
};
|
|
362
|
-
if (!data && !error && isLoading) return null;
|
|
363
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
364
|
-
"div",
|
|
365
|
-
{
|
|
366
|
-
style: {
|
|
367
|
-
position: "fixed",
|
|
368
|
-
right: "24px",
|
|
369
|
-
bottom: "24px",
|
|
370
|
-
zIndex: "9999",
|
|
371
|
-
fontFamily: "system-ui, sans-serif"
|
|
372
|
-
},
|
|
373
|
-
children: [
|
|
374
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
375
|
-
"div",
|
|
376
|
-
{
|
|
377
|
-
style: {
|
|
378
|
-
width: "320px",
|
|
379
|
-
marginBottom: "12px",
|
|
380
|
-
borderRadius: "12px",
|
|
381
|
-
boxShadow: "0 8px 32px rgba(0,0,0,0.18)",
|
|
382
|
-
background: "#fff",
|
|
383
|
-
overflow: "hidden",
|
|
384
|
-
maxHeight: open.value ? "500px" : "0px",
|
|
385
|
-
opacity: open.value ? 1 : 0,
|
|
386
|
-
transition: "all 0.4s ease"
|
|
387
|
-
},
|
|
388
|
-
children: [
|
|
389
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
390
|
-
"div",
|
|
391
|
-
{
|
|
392
|
-
style: {
|
|
393
|
-
background: accentColor,
|
|
394
|
-
color: "#fff",
|
|
395
|
-
padding: "14px 16px",
|
|
396
|
-
fontWeight: "600",
|
|
397
|
-
fontSize: "14px"
|
|
398
|
-
},
|
|
399
|
-
children: "Chat with us"
|
|
400
|
-
}
|
|
401
|
-
),
|
|
402
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
403
|
-
"div",
|
|
404
|
-
{
|
|
405
|
-
style: {
|
|
406
|
-
display: "grid",
|
|
407
|
-
height: "200px",
|
|
408
|
-
overflowY: "auto",
|
|
409
|
-
padding: "16px",
|
|
410
|
-
gap: "4px"
|
|
411
|
-
},
|
|
412
|
-
children: data?.messages.map((m, index) => {
|
|
413
|
-
const isMine = identity === m.identity.id;
|
|
414
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
415
|
-
"div",
|
|
416
|
-
{
|
|
417
|
-
style: {
|
|
418
|
-
justifySelf: isMine ? "start" : "end",
|
|
419
|
-
width: "max-content"
|
|
420
|
-
},
|
|
421
|
-
children: [
|
|
422
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
423
|
-
"p",
|
|
424
|
-
{
|
|
425
|
-
style: {
|
|
426
|
-
padding: "2px 8px",
|
|
427
|
-
borderRadius: "8px",
|
|
428
|
-
width: "max-content",
|
|
429
|
-
background: isMine ? "silver" : accentColor,
|
|
430
|
-
color: isMine ? "black" : foregroundColor
|
|
431
|
-
},
|
|
432
|
-
children: m.text
|
|
433
|
-
}
|
|
434
|
-
),
|
|
435
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
436
|
-
"span",
|
|
437
|
-
{
|
|
438
|
-
style: {
|
|
439
|
-
fontSize: "12px",
|
|
440
|
-
color: "silver",
|
|
441
|
-
textAlign: isMine ? "left" : "right"
|
|
442
|
-
},
|
|
443
|
-
children: new Date(m.createdAt).toLocaleDateString()
|
|
444
|
-
}
|
|
445
|
-
)
|
|
446
|
-
]
|
|
447
|
-
},
|
|
448
|
-
m.text + index.toString()
|
|
449
|
-
);
|
|
450
|
-
})
|
|
451
|
-
}
|
|
452
|
-
),
|
|
453
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
454
|
-
"form",
|
|
455
|
-
{
|
|
456
|
-
onSubmit: (e) => {
|
|
457
|
-
e.preventDefault();
|
|
458
|
-
onSubmit();
|
|
459
|
-
},
|
|
460
|
-
style: { padding: "12px 16px" },
|
|
461
|
-
children: [
|
|
462
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
463
|
-
"textarea",
|
|
464
|
-
{
|
|
465
|
-
value: text.value,
|
|
466
|
-
onInput: (e) => {
|
|
467
|
-
text.value = e.target?.value;
|
|
468
|
-
},
|
|
469
|
-
name: "message",
|
|
470
|
-
placeholder: "Send us a message\u2026",
|
|
471
|
-
rows: 3,
|
|
472
|
-
style: {
|
|
473
|
-
width: "100%",
|
|
474
|
-
border: "1px solid #e5e7eb",
|
|
475
|
-
borderRadius: "8px",
|
|
476
|
-
padding: "8px",
|
|
477
|
-
fontSize: "13px",
|
|
478
|
-
resize: "none",
|
|
479
|
-
outline: "none",
|
|
480
|
-
boxSizing: "border-box"
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
),
|
|
484
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Button, { fullWidth: true, disabled: !text.value, children: submitting.value ? "Sending" : "Send" })
|
|
485
|
-
]
|
|
486
|
-
}
|
|
487
|
-
)
|
|
488
|
-
]
|
|
489
|
-
}
|
|
490
|
-
),
|
|
491
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
492
|
-
"button",
|
|
493
|
-
{
|
|
494
|
-
type: "button",
|
|
495
|
-
onClick: () => {
|
|
496
|
-
open.value = !open.value;
|
|
497
|
-
},
|
|
498
|
-
"aria-label": "Open chat",
|
|
499
|
-
style: {
|
|
500
|
-
transform: `rotate(${open.value ? 180 : 0}deg)`,
|
|
501
|
-
transition: "transform 0.4s ease",
|
|
502
|
-
width: "52px",
|
|
503
|
-
height: "52px",
|
|
504
|
-
borderRadius: "50%",
|
|
505
|
-
background: accentColor,
|
|
506
|
-
border: "none",
|
|
507
|
-
cursor: "pointer",
|
|
508
|
-
display: "flex",
|
|
509
|
-
alignItems: "center",
|
|
510
|
-
justifyContent: "center",
|
|
511
|
-
boxShadow: "0 4px 16px rgba(0,0,0,0.18)",
|
|
512
|
-
marginLeft: "auto"
|
|
513
|
-
},
|
|
514
|
-
children: open.value ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_preact.X, { color: foregroundColor }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_preact.MessageCircle, { color: foregroundColor })
|
|
515
|
-
}
|
|
516
|
-
)
|
|
517
|
-
]
|
|
518
|
-
}
|
|
167
|
+
// src/helpers/environment.ts
|
|
168
|
+
function isLocalhost() {
|
|
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
|
|
519
185
|
);
|
|
520
186
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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;
|
|
529
208
|
}
|
|
530
209
|
|
|
531
|
-
// src/
|
|
532
|
-
function
|
|
210
|
+
// src/modules/events.ts
|
|
211
|
+
function createEventsModule(http, options) {
|
|
533
212
|
return {
|
|
534
|
-
create: (
|
|
535
|
-
|
|
536
|
-
data
|
|
537
|
-
}
|
|
213
|
+
create: async (name, data = {}) => {
|
|
214
|
+
if (shouldSkip(options)) return null;
|
|
215
|
+
return http.post("/api/events", { name, data, source: "api" });
|
|
216
|
+
}
|
|
538
217
|
};
|
|
539
218
|
}
|
|
540
219
|
|
|
541
|
-
// src/
|
|
542
|
-
function
|
|
220
|
+
// src/modules/experiments.ts
|
|
221
|
+
function createExperimentsModule(http, options) {
|
|
543
222
|
return {
|
|
544
|
-
get: (
|
|
223
|
+
get: (id) => {
|
|
224
|
+
if (shouldSkip(options)) return Promise.resolve(null);
|
|
225
|
+
return http.get(
|
|
226
|
+
`/api/v1/experiments/${encodeURIComponent(id)}`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
545
229
|
};
|
|
546
230
|
}
|
|
547
231
|
|
|
548
|
-
// src/
|
|
549
|
-
function
|
|
232
|
+
// src/modules/feature-flags.ts
|
|
233
|
+
function createFeatureFlagsModule(http, options) {
|
|
550
234
|
return {
|
|
551
|
-
get:
|
|
552
|
-
return
|
|
553
|
-
|
|
235
|
+
get: (key) => {
|
|
236
|
+
if (shouldSkip(options)) return Promise.resolve(null);
|
|
237
|
+
return http.get(
|
|
238
|
+
`/api/v1/feature-flags/${encodeURIComponent(key)}`
|
|
554
239
|
);
|
|
555
240
|
}
|
|
556
241
|
};
|
|
557
242
|
}
|
|
558
243
|
|
|
559
|
-
// src/
|
|
560
|
-
function
|
|
561
|
-
const storage = getStorage();
|
|
562
|
-
async function set(distinctId, data) {
|
|
563
|
-
const res = await api.post("/identities", { ...data, id: distinctId });
|
|
564
|
-
storage.set("identity", distinctId);
|
|
565
|
-
return res;
|
|
566
|
-
}
|
|
244
|
+
// src/modules/identities.ts
|
|
245
|
+
function createIdentitiesModule(http, options) {
|
|
567
246
|
return {
|
|
568
|
-
|
|
569
|
-
|
|
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
|
+
}
|
|
570
256
|
};
|
|
571
257
|
}
|
|
572
258
|
|
|
573
|
-
// src/
|
|
574
|
-
function
|
|
259
|
+
// src/modules/logs.ts
|
|
260
|
+
function createLogsModule(http, options) {
|
|
575
261
|
return {
|
|
576
|
-
send: (
|
|
262
|
+
send: (payload) => {
|
|
263
|
+
if (shouldSkip(options)) return Promise.resolve(null);
|
|
264
|
+
return http.post("/api/v1/logs", payload);
|
|
265
|
+
}
|
|
577
266
|
};
|
|
578
267
|
}
|
|
579
268
|
|
|
580
|
-
// src/
|
|
581
|
-
function
|
|
269
|
+
// src/modules/surveys.ts
|
|
270
|
+
function createSurveysModule(http, options) {
|
|
582
271
|
return {
|
|
583
|
-
submit: (
|
|
272
|
+
submit: (payload) => {
|
|
273
|
+
if (shouldSkip(options)) return Promise.resolve(null);
|
|
274
|
+
return http.post("/api/v1/surveys", payload);
|
|
275
|
+
}
|
|
584
276
|
};
|
|
585
277
|
}
|
|
586
278
|
|
|
587
|
-
// src/
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
logs: initLogTracing(api),
|
|
603
|
-
surveys: initSurveys(api),
|
|
604
|
-
experiments: initExperiments(api)
|
|
279
|
+
// src/plugins/analytics.ts
|
|
280
|
+
function initAnalyticsPlugin(plugeen, http) {
|
|
281
|
+
if (typeof window === "undefined") return;
|
|
282
|
+
let pageStartTime = Date.now();
|
|
283
|
+
let maxScrollDepth = 0;
|
|
284
|
+
let interactionCount = 0;
|
|
285
|
+
let pageCount = 0;
|
|
286
|
+
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;
|
|
605
294
|
};
|
|
606
|
-
};
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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
|
+
const trackPageView = () => {
|
|
303
|
+
pageCount++;
|
|
304
|
+
const body = {
|
|
305
|
+
event: "page_view",
|
|
306
|
+
url: window.location.href,
|
|
307
|
+
title: document.title,
|
|
308
|
+
sessionId: getOrCreateSessionId(),
|
|
309
|
+
referrer: document.referrer || void 0,
|
|
310
|
+
screenWidth: window.innerWidth,
|
|
311
|
+
screenHeight: window.innerHeight
|
|
312
|
+
};
|
|
313
|
+
void http.post("/api/v1/analytics", body);
|
|
314
|
+
};
|
|
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
|
+
const trackPageExit = (useBeacon = false) => {
|
|
323
|
+
const data = buildPageExitData();
|
|
324
|
+
if (useBeacon) {
|
|
325
|
+
http.send(
|
|
326
|
+
"/api/events",
|
|
327
|
+
{ name: "page_exit", data, source: "api" },
|
|
328
|
+
true
|
|
329
|
+
);
|
|
330
|
+
} else {
|
|
331
|
+
void plugeen.events.create("page_exit", data);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
const onRouteChange = () => {
|
|
335
|
+
if (window.location.href === currentUrl) return;
|
|
336
|
+
trackPageExit();
|
|
337
|
+
maxScrollDepth = 0;
|
|
338
|
+
interactionCount = 0;
|
|
339
|
+
pageStartTime = Date.now();
|
|
340
|
+
currentUrl = window.location.href;
|
|
341
|
+
trackPageView();
|
|
342
|
+
};
|
|
343
|
+
const patchHistoryMethod = (method) => {
|
|
344
|
+
const original = history[method].bind(history);
|
|
345
|
+
history[method] = (...args) => {
|
|
346
|
+
original(...args);
|
|
347
|
+
onRouteChange();
|
|
348
|
+
};
|
|
349
|
+
};
|
|
350
|
+
patchHistoryMethod("pushState");
|
|
351
|
+
patchHistoryMethod("replaceState");
|
|
352
|
+
window.addEventListener("popstate", onRouteChange);
|
|
353
|
+
window.addEventListener("beforeunload", () => {
|
|
354
|
+
trackPageExit(true);
|
|
355
|
+
});
|
|
356
|
+
document.addEventListener("visibilitychange", () => {
|
|
357
|
+
if (document.visibilityState === "hidden") {
|
|
358
|
+
trackPageExit(true);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
trackPageView();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/plugins/errors.ts
|
|
365
|
+
var EXTENSION_PREFIXES = [
|
|
366
|
+
"chrome-extension://",
|
|
367
|
+
"moz-extension://",
|
|
368
|
+
"safari-extension://",
|
|
369
|
+
"edge-extension://"
|
|
370
|
+
];
|
|
371
|
+
function isExtensionSource(str) {
|
|
372
|
+
if (!str) return false;
|
|
373
|
+
const lower = str.toLowerCase();
|
|
374
|
+
return EXTENSION_PREFIXES.some((prefix) => lower.includes(prefix));
|
|
375
|
+
}
|
|
376
|
+
function initErrorsPlugin(plugeen) {
|
|
377
|
+
if (typeof window === "undefined") return;
|
|
378
|
+
window.addEventListener("error", (event) => {
|
|
379
|
+
if (isExtensionSource(event.filename) || isExtensionSource(event.error?.stack))
|
|
380
|
+
return;
|
|
381
|
+
if (event.error === null && event.message === "Script error.") return;
|
|
382
|
+
void plugeen.events.create("error", {
|
|
383
|
+
message: event.message || "Unknown Error",
|
|
384
|
+
filename: event.filename,
|
|
385
|
+
lineno: event.lineno,
|
|
386
|
+
colno: event.colno,
|
|
387
|
+
stack: event.error?.stack,
|
|
388
|
+
error_type: event.error?.name || "Error"
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
window.addEventListener(
|
|
392
|
+
"unhandledrejection",
|
|
393
|
+
(event) => {
|
|
394
|
+
const { reason } = event;
|
|
395
|
+
if (isExtensionSource(reason?.stack))
|
|
396
|
+
return;
|
|
397
|
+
let message = "Unknown Error";
|
|
398
|
+
let stack;
|
|
399
|
+
if (reason instanceof Error) {
|
|
400
|
+
message = reason.message;
|
|
401
|
+
stack = reason.stack;
|
|
402
|
+
} else if (typeof reason === "string") {
|
|
403
|
+
message = reason;
|
|
404
|
+
} else if (reason !== null && typeof reason === "object" && "message" in reason) {
|
|
405
|
+
message = String(reason.message);
|
|
406
|
+
}
|
|
407
|
+
void plugeen.events.create("error", {
|
|
408
|
+
message,
|
|
409
|
+
stack,
|
|
410
|
+
error_type: "UnhandledRejection"
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/plugins/web-vitals/helpers.ts
|
|
417
|
+
function getRating(value, thresholds) {
|
|
418
|
+
if (value > thresholds[1]) return "poor";
|
|
419
|
+
if (value > thresholds[0]) return "needs-improvement";
|
|
420
|
+
return "good";
|
|
421
|
+
}
|
|
422
|
+
function getActivationStart() {
|
|
423
|
+
const nav = performance.getEntriesByType(
|
|
424
|
+
"navigation"
|
|
425
|
+
)[0];
|
|
426
|
+
return nav?.activationStart ?? 0;
|
|
427
|
+
}
|
|
428
|
+
function getNavEntry() {
|
|
429
|
+
const entry = performance.getEntriesByType("navigation")[0];
|
|
430
|
+
if (entry && entry.responseStart > 0 && entry.responseStart < performance.now())
|
|
431
|
+
return entry;
|
|
432
|
+
}
|
|
433
|
+
function observe(type, cb, opts) {
|
|
434
|
+
try {
|
|
435
|
+
if (!PerformanceObserver.supportedEntryTypes.includes(type)) return;
|
|
436
|
+
const po = new PerformanceObserver((list) => {
|
|
437
|
+
Promise.resolve().then(() => cb(list.getEntries()));
|
|
438
|
+
});
|
|
439
|
+
po.observe({
|
|
440
|
+
type,
|
|
441
|
+
buffered: true,
|
|
442
|
+
...opts ?? {}
|
|
443
|
+
});
|
|
444
|
+
return po;
|
|
445
|
+
} catch {
|
|
446
|
+
return void 0;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function observeFCP(cb) {
|
|
450
|
+
const reported = /* @__PURE__ */ new Set();
|
|
451
|
+
observe("paint", (entries) => {
|
|
452
|
+
for (const entry of entries) {
|
|
453
|
+
if (entry.name === "first-contentful-paint" && !reported.has("FCP")) {
|
|
454
|
+
reported.add("FCP");
|
|
455
|
+
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
|
+
});
|
|
461
|
+
}
|
|
616
462
|
}
|
|
617
|
-
|
|
618
|
-
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
function observeLCP(cb) {
|
|
466
|
+
let reported = false;
|
|
467
|
+
const po = observe("largest-contentful-paint", (entries) => {
|
|
468
|
+
if (reported) return;
|
|
469
|
+
const last = entries[entries.length - 1];
|
|
470
|
+
if (last) {
|
|
471
|
+
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
|
+
});
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
if (!po) return;
|
|
480
|
+
const finalize = () => {
|
|
481
|
+
if (reported) return;
|
|
482
|
+
reported = true;
|
|
483
|
+
const records = po.takeRecords();
|
|
484
|
+
if (records.length > 0) {
|
|
485
|
+
const last = records[records.length - 1];
|
|
486
|
+
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
|
+
});
|
|
492
|
+
}
|
|
493
|
+
po.disconnect();
|
|
494
|
+
};
|
|
495
|
+
for (const evt of ["keydown", "click", "visibilitychange"]) {
|
|
496
|
+
addEventListener(evt, finalize, { capture: true, once: true });
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function observeCLS(cb) {
|
|
500
|
+
let sessionValue = 0;
|
|
501
|
+
let sessionEntries = [];
|
|
502
|
+
let clsValue = 0;
|
|
503
|
+
observe("layout-shift", (entries) => {
|
|
504
|
+
for (const raw of entries) {
|
|
505
|
+
const entry = raw;
|
|
506
|
+
if (entry.hadRecentInput) continue;
|
|
507
|
+
const last = sessionEntries[sessionEntries.length - 1];
|
|
508
|
+
const first = sessionEntries[0];
|
|
509
|
+
if (sessionEntries.length > 0 && last && first && entry.startTime - last.startTime < 1e3 && entry.startTime - first.startTime < 5e3) {
|
|
510
|
+
sessionValue += entry.value;
|
|
511
|
+
sessionEntries.push(entry);
|
|
512
|
+
} else {
|
|
513
|
+
sessionValue = entry.value;
|
|
514
|
+
sessionEntries = [entry];
|
|
515
|
+
}
|
|
516
|
+
if (sessionValue > clsValue) {
|
|
517
|
+
clsValue = sessionValue;
|
|
518
|
+
const rounded = Math.round(clsValue * 1e4) / 1e4;
|
|
519
|
+
cb({
|
|
520
|
+
name: "CLS",
|
|
521
|
+
value: rounded,
|
|
522
|
+
rating: getRating(clsValue, [0.1, 0.25])
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
function observeTTFB(cb) {
|
|
529
|
+
const nav = getNavEntry();
|
|
530
|
+
if (!nav) return;
|
|
531
|
+
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
|
+
});
|
|
537
|
+
}
|
|
538
|
+
function observeINP(cb) {
|
|
539
|
+
const interactions = /* @__PURE__ */ new Map();
|
|
540
|
+
let worstDuration = 0;
|
|
541
|
+
observe(
|
|
542
|
+
"event",
|
|
543
|
+
(entries) => {
|
|
544
|
+
for (const raw of entries) {
|
|
545
|
+
const entry = raw;
|
|
546
|
+
if (!entry.interactionId) continue;
|
|
547
|
+
const existing = interactions.get(entry.interactionId) ?? 0;
|
|
548
|
+
if (entry.duration > existing) {
|
|
549
|
+
interactions.set(entry.interactionId, entry.duration);
|
|
550
|
+
if (entry.duration > worstDuration) {
|
|
551
|
+
worstDuration = entry.duration;
|
|
552
|
+
cb({
|
|
553
|
+
name: "INP",
|
|
554
|
+
value: Math.round(entry.duration),
|
|
555
|
+
rating: getRating(entry.duration, [200, 500])
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
{ durationThreshold: 40 }
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
function observeFPS(cb) {
|
|
565
|
+
if (typeof requestAnimationFrame === "undefined") return;
|
|
566
|
+
let frames = 0;
|
|
567
|
+
const duration = 2e3;
|
|
568
|
+
const start = performance.now();
|
|
569
|
+
const tick = () => {
|
|
570
|
+
frames++;
|
|
571
|
+
if (performance.now() - start < duration) {
|
|
572
|
+
requestAnimationFrame(tick);
|
|
573
|
+
} else {
|
|
574
|
+
cb({ name: "FPS", value: Math.round(frames / duration * 1e3) });
|
|
619
575
|
}
|
|
620
|
-
|
|
576
|
+
};
|
|
577
|
+
if (document.readyState === "complete") {
|
|
578
|
+
requestAnimationFrame(tick);
|
|
579
|
+
} else {
|
|
580
|
+
window.addEventListener("load", () => requestAnimationFrame(tick), {
|
|
581
|
+
once: true
|
|
582
|
+
});
|
|
621
583
|
}
|
|
622
|
-
|
|
584
|
+
}
|
|
585
|
+
function initWebVitals(cb) {
|
|
586
|
+
if (typeof window === "undefined" || typeof PerformanceObserver === "undefined")
|
|
587
|
+
return;
|
|
588
|
+
observeFCP(cb);
|
|
589
|
+
observeLCP(cb);
|
|
590
|
+
observeCLS(cb);
|
|
591
|
+
observeTTFB(cb);
|
|
592
|
+
observeINP(cb);
|
|
593
|
+
observeFPS(cb);
|
|
623
594
|
}
|
|
624
595
|
|
|
625
|
-
// src/
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
|
|
596
|
+
// src/plugins/web-vitals/index.ts
|
|
597
|
+
function initWebVitalsPlugin(plugeen) {
|
|
598
|
+
initWebVitals((metric) => {
|
|
599
|
+
void plugeen.events.create("web_vital", {
|
|
600
|
+
name: metric.name,
|
|
601
|
+
value: metric.value,
|
|
602
|
+
...metric.rating !== void 0 ? { rating: metric.rating } : {}
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// src/plugins/index.ts
|
|
608
|
+
function initPlugins(plugeen, http, plugins) {
|
|
609
|
+
for (const plugin of plugins) {
|
|
610
|
+
if (plugin === "analytics") initAnalyticsPlugin(plugeen, http);
|
|
611
|
+
else if (plugin === "web-vitals") initWebVitalsPlugin(plugeen);
|
|
612
|
+
else if (plugin === "errors") initErrorsPlugin(plugeen);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// src/index.ts
|
|
634
617
|
function createPlugeen(apiKey, options) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
accentColor: options?.accentColor || defaultOptions.accentColor,
|
|
638
|
-
foregroundColor: options?.foregroundColor || defaultOptions.foregroundColor,
|
|
639
|
-
plugins: options?.plugins || []
|
|
640
|
-
};
|
|
641
|
-
if (!apiKey) {
|
|
642
|
-
console.warn("[Plugeen] Missing data-api-key attribute.");
|
|
618
|
+
if (!apiKey || typeof apiKey !== "string") {
|
|
619
|
+
throw new TypeError("[plugeen] apiKey is required");
|
|
643
620
|
}
|
|
644
|
-
if (!
|
|
645
|
-
|
|
621
|
+
if (!options?.apiUrl || typeof options.apiUrl !== "string") {
|
|
622
|
+
throw new TypeError("[plugeen] apiUrl is required");
|
|
646
623
|
}
|
|
647
|
-
|
|
648
|
-
|
|
624
|
+
const resolved = {
|
|
625
|
+
apiUrl: options.apiUrl,
|
|
626
|
+
debug: options.debug ?? false,
|
|
627
|
+
disabled: options.disabled ?? false,
|
|
628
|
+
samplingRate: options.samplingRate ?? 1,
|
|
629
|
+
plugins: options.plugins ?? [],
|
|
630
|
+
skipPatterns: options.skipPatterns ?? []
|
|
631
|
+
};
|
|
632
|
+
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
|
+
const plugeen = {
|
|
640
|
+
track: (event, properties) => {
|
|
641
|
+
void events.create(event, properties ?? {});
|
|
642
|
+
},
|
|
643
|
+
events,
|
|
644
|
+
identities,
|
|
645
|
+
featureFlags,
|
|
646
|
+
logs,
|
|
647
|
+
surveys,
|
|
648
|
+
experiments
|
|
649
|
+
};
|
|
650
|
+
if (resolved.plugins.length > 0) {
|
|
651
|
+
initPlugins(plugeen, http, resolved.plugins);
|
|
649
652
|
}
|
|
650
|
-
return
|
|
653
|
+
return plugeen;
|
|
651
654
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
createPlugeen
|
|
655
|
-
});
|
|
655
|
+
|
|
656
|
+
exports.createPlugeen = createPlugeen;
|