trackersdk2 1.0.1 → 1.0.2

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.js CHANGED
@@ -5,584 +5,570 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var webVitals = require('web-vitals');
6
6
 
7
7
  class TrackerSDK {
8
- // #region 构造函数 & 初始化
9
- constructor() {
10
- this.autoTrackClickHandler = null;
11
- // 事件队列
12
- this.eventQueue = [];
13
- // 批量上报定时器
14
- this.batchTimer = null;
15
- // 页面卸载前是否已发送数据标识
16
- this.hasSentBeforeUnload = false;
17
- this.config = {
18
- appId: "",
19
- serverUrl: "",
20
- userId: "",
21
- debug: true,
22
- enableAutoClickTagNameList: [],
23
- autoTrack: false,
24
- batchSize: 10,
25
- batchInterval: 5000,
26
- };
8
+ // #region 构造函数 & 初始化
9
+ constructor() {
10
+ this.autoTrackClickHandler = null;
11
+ // 事件队列
12
+ this.eventQueue = [];
13
+ // 批量上报定时器
14
+ this.batchTimer = null;
15
+ // 页面卸载前是否已发送数据标识
16
+ this.hasSentBeforeUnload = false;
17
+ this.config = {
18
+ appId: "",
19
+ serverUrl: "",
20
+ userId: "",
21
+ debug: true,
22
+ enableAutoClickTagNameList: [],
23
+ autoTrack: false,
24
+ batchSize: 10,
25
+ batchInterval: 5000
26
+ };
27
+ }
28
+ init(config) {
29
+ if (!window || typeof window !== "object") {
30
+ console.warn("TrackerSDK 仅支持浏览器环境,当前环境已跳过初始化");
31
+ return;
27
32
  }
28
- init(config) {
29
- if (!window || typeof window !== "object") {
30
- console.warn("TrackerSDK 仅支持浏览器环境,当前环境已跳过初始化");
31
- return;
32
- }
33
- if (!config.serverUrl) {
34
- throw new Error("serverUrl is required");
35
- }
36
- if (!config.appId) {
37
- throw new Error("appId is required");
38
- }
39
- this.config = {
40
- ...this.config,
41
- ...config,
42
- };
43
- this.setupErrorTracking();
44
- this.setupPerformanceTracking();
45
- this.trackUV();
46
- // 监听页面卸载
47
- this.setupPageUnloadHandler();
48
- // 重试之前失败的批量上报
49
- this.retryFailedBatches();
33
+ if (!config.serverUrl) {
34
+ throw new Error("serverUrl is required");
50
35
  }
51
- // #endregion
52
- // #region 埋点api工具方法
53
- /**
54
- * @description 重试批量上报之前上报失败的数据
55
- * @returns Promise<void>
56
- */
57
- async retryFailedBatches() {
36
+ if (!config.appId) {
37
+ throw new Error("appId is required");
38
+ }
39
+ this.config = {
40
+ ...this.config,
41
+ ...config
42
+ };
43
+ this.setupErrorTracking();
44
+ this.setupPerformanceTracking();
45
+ this.trackUV();
46
+ // 监听页面卸载
47
+ this.setupPageUnloadHandler();
48
+ // 重试之前失败的批量上报
49
+ this.retryFailedBatches();
50
+ }
51
+ // #endregion
52
+ // #region 埋点api工具方法
53
+ /**
54
+ * @description 重试批量上报之前上报失败的数据
55
+ * @returns Promise<void>
56
+ */
57
+ async retryFailedBatches() {
58
+ try {
59
+ const key = "tracker_failed_batches";
60
+ const batchesStr = localStorage.getItem(key);
61
+ if (!batchesStr) return;
62
+ const batches = JSON.parse(batchesStr);
63
+ const remainingBatches = [];
64
+ for (const batch of batches) {
58
65
  try {
59
- const key = "tracker_failed_batches";
60
- const batchesStr = localStorage.getItem(key);
61
- if (!batchesStr)
62
- return;
63
- const batches = JSON.parse(batchesStr);
64
- const remainingBatches = [];
65
- for (const batch of batches) {
66
- try {
67
- await this.fallbackSendBatch(batch);
68
- }
69
- catch (err) {
70
- remainingBatches.push(batch);
71
- }
72
- }
73
- if (remainingBatches.length) {
74
- // 只保留3批
75
- const toSave = remainingBatches.splice(-3);
76
- localStorage.setItem(key, JSON.stringify(toSave));
77
- }
78
- else {
79
- localStorage.removeItem(key);
80
- }
81
- }
82
- catch (err) {
83
- console.warn("Failed to retry batches:", err);
66
+ await this.fallbackSendBatch(batch);
67
+ } catch (err) {
68
+ remainingBatches.push(batch);
84
69
  }
70
+ }
71
+ if (remainingBatches.length) {
72
+ // 只保留3批
73
+ const toSave = remainingBatches.splice(-3);
74
+ localStorage.setItem(key, JSON.stringify(toSave));
75
+ } else {
76
+ localStorage.removeItem(key);
77
+ }
78
+ } catch (err) {
79
+ console.warn("Failed to retry batches:", err);
85
80
  }
86
- /**
87
- * @description 监听页面卸载事件,确保数据上报
88
- * @returns void
89
- */
90
- setupPageUnloadHandler() {
91
- const flushAndSend = () => {
92
- if (this.hasSentBeforeUnload || this.eventQueue.length == 0)
93
- return;
94
- // 清除定时器,防止内存泄漏
95
- if (this.batchTimer) {
96
- clearTimeout(this.batchTimer);
97
- this.batchTimer = null;
98
- }
99
- // 标记为已上报
100
- this.hasSentBeforeUnload = true;
101
- const payload = { events: [...this.eventQueue] };
102
- // 清空队列
103
- this.eventQueue = [];
104
- // this.sendBatch(payload.events);
105
- if (navigator.sendBeacon) {
106
- const blob = new Blob([JSON.stringify(payload)], {
107
- type: "application/json",
108
- });
109
- if (!navigator.sendBeacon(this.config.serverUrl, blob)) {
110
- this.fallbackSendBatch(payload);
111
- }
112
- }
113
- else {
114
- this.fallbackSendBatch(payload);
115
- }
116
- };
117
- // 页面级别卸载事件
118
- // beforeunload safari不支持
119
- window.addEventListener("beforeunload", flushAndSend);
120
- // pagehide 兜底
121
- window.addEventListener("pagehide", flushAndSend);
122
- // 可选:页面最小化、切换页签时触发
123
- // window.addEventListener("visibilitychange", () => {
124
- // if (document.hidden) flushAndSend();
125
- // });
126
- }
127
- /**
128
- * @description 获取设备id
129
- */
130
- getDeviceId() {
131
- let id = localStorage.getItem("tracker_device_id");
132
- if (!id) {
133
- id =
134
- "device_" +
135
- Date.now() +
136
- "_" +
137
- Math.random().toString(32).substring(2, 10);
138
- localStorage.setItem("tracker_device_id", id);
81
+ }
82
+ /**
83
+ * @description 监听页面卸载事件,确保数据上报
84
+ * @returns void
85
+ */
86
+ setupPageUnloadHandler() {
87
+ const flushAndSend = () => {
88
+ if (this.hasSentBeforeUnload || this.eventQueue.length == 0) return;
89
+ // 清除定时器,防止内存泄漏
90
+ if (this.batchTimer) {
91
+ clearTimeout(this.batchTimer);
92
+ this.batchTimer = null;
93
+ }
94
+ // 标记为已上报
95
+ this.hasSentBeforeUnload = true;
96
+ const payload = {
97
+ events: [...this.eventQueue]
98
+ };
99
+ // 清空队列
100
+ this.eventQueue = [];
101
+ // this.sendBatch(payload.events);
102
+ if (navigator.sendBeacon) {
103
+ const blob = new Blob([JSON.stringify(payload)], {
104
+ type: "application/json"
105
+ });
106
+ if (!navigator.sendBeacon(this.config.serverUrl, blob)) {
107
+ this.fallbackSendBatch(payload);
139
108
  }
140
- return id;
109
+ } else {
110
+ this.fallbackSendBatch(payload);
111
+ }
112
+ };
113
+ // 页面级别卸载事件
114
+ // beforeunload safari不支持
115
+ window.addEventListener("beforeunload", flushAndSend);
116
+ // pagehide 兜底
117
+ window.addEventListener("pagehide", flushAndSend);
118
+ // 可选:页面最小化、切换页签时触发
119
+ // window.addEventListener("visibilitychange", () => {
120
+ // if (document.hidden) flushAndSend();
121
+ // });
122
+ }
123
+ /**
124
+ * @description 获取设备id
125
+ */
126
+ getDeviceId() {
127
+ let id = localStorage.getItem("tracker_device_id");
128
+ if (!id) {
129
+ id = "device_" + Date.now() + "_" + Math.random().toString(32).substring(2, 10);
130
+ localStorage.setItem("tracker_device_id", id);
141
131
  }
142
- /**
143
- * @description navigator.sendBeacon 和 fetch 双通道实现数据上报
144
- * @param data 上报的负载
145
- */
146
- send(data) {
147
- const payload = {
148
- ...data,
149
- userId: this.config.userId || this.getDeviceId(),
150
- timestamp: Date.now(),
151
- url: window.location.href,
152
- title: document.title,
153
- userAgent: navigator.userAgent,
154
- appId: this.config.appId,
155
- };
156
- // 错误事件立即上报
157
- if (data.eventType == "error")
158
- return this.sendImmediately(payload);
159
- this.enqueue(payload);
132
+ return id;
133
+ }
134
+ /**
135
+ * @description navigator.sendBeacon 和 fetch 双通道实现数据上报
136
+ * @param data 上报的负载
137
+ */
138
+ send(data) {
139
+ const payload = {
140
+ ...data,
141
+ userId: this.config.userId || this.getDeviceId(),
142
+ timestamp: Date.now(),
143
+ url: window.location.href,
144
+ title: document.title,
145
+ userAgent: navigator.userAgent,
146
+ appId: this.config.appId
147
+ };
148
+ // 错误事件立即上报
149
+ if (data.eventType == "error") return this.sendImmediately(payload);
150
+ this.enqueue(payload);
151
+ }
152
+ /**
153
+ * @description 入队列
154
+ * @param payload 上报的负载
155
+ */
156
+ enqueue(payload) {
157
+ this.eventQueue.push(payload);
158
+ // 达到数量阈值,立即上报
159
+ if (this.eventQueue.length >= this.config.batchSize) {
160
+ return this.flush();
160
161
  }
161
- /**
162
- * @description 入队列
163
- * @param payload 上报的负载
164
- */
165
- enqueue(payload) {
166
- this.eventQueue.push(payload);
167
- // 达到数量阈值,立即上报
168
- if (this.eventQueue.length >= this.config.batchSize) {
169
- return this.flush();
170
- }
171
- // 启动定时器
172
- if (!this.batchTimer) {
173
- this.batchTimer = setTimeout(() => {
174
- this.flush();
175
- }, this.config.batchInterval);
176
- }
162
+ // 启动定时器
163
+ if (!this.batchTimer) {
164
+ this.batchTimer = setTimeout(() => {
165
+ this.flush();
166
+ }, this.config.batchInterval);
177
167
  }
178
- /**
179
- * @description 刷新队列,批量上报
180
- * @return void
181
- */
182
- flush() {
183
- if (this.eventQueue.length === 0)
184
- return;
185
- const batchPayload = [...this.eventQueue];
186
- this.eventQueue = [];
187
- if (this.batchTimer) {
188
- clearTimeout(this.batchTimer);
189
- this.batchTimer = null;
190
- }
191
- this.sendBatch(batchPayload);
168
+ }
169
+ /**
170
+ * @description 刷新队列,批量上报
171
+ * @return void
172
+ */
173
+ flush() {
174
+ if (this.eventQueue.length === 0) return;
175
+ const batchPayload = [...this.eventQueue];
176
+ this.eventQueue = [];
177
+ if (this.batchTimer) {
178
+ clearTimeout(this.batchTimer);
179
+ this.batchTimer = null;
192
180
  }
193
- /**
194
- * @description navigator.sendBeacon单条错误上报失败的fetch回退方案
195
- * @param payload any
196
- * */
197
- async fallbackSend(payload) {
198
- try {
199
- await fetch(this.config.serverUrl, {
200
- method: "POST",
201
- headers: {
202
- "Content-Type": "application/json",
203
- },
204
- body: JSON.stringify(payload),
205
- keepalive: true,
206
- // credentials: "include",
207
- });
208
- }
209
- catch (error) {
210
- if (this.config.debug) {
211
- console.error("Single event fallback send failed:", error);
212
- }
213
- this.saveFailedBatch({ events: [payload] });
214
- }
181
+ this.sendBatch(batchPayload);
182
+ }
183
+ /**
184
+ * @description navigator.sendBeacon单条错误上报失败的fetch回退方案
185
+ * @param payload any
186
+ * */
187
+ async fallbackSend(payload) {
188
+ try {
189
+ await fetch(this.config.serverUrl, {
190
+ method: "POST",
191
+ headers: {
192
+ "Content-Type": "application/json"
193
+ },
194
+ body: JSON.stringify(payload),
195
+ keepalive: true
196
+ // credentials: "include",
197
+ });
198
+ } catch (error) {
199
+ if (this.config.debug) {
200
+ console.error("Single event fallback send failed:", error);
201
+ }
202
+ this.saveFailedBatch({
203
+ events: [payload]
204
+ });
215
205
  }
216
- /**
217
- * @description 错误事件立即上报
218
- * @param data 上报的负载
219
- * @returns void
220
- */
221
- sendImmediately(payload) {
222
- // if (navigator.sendBeacon) {
223
- // const blob = new Blob([JSON.stringify(payload)], {
224
- // type: "application/json",
225
- // });
226
- // if (!navigator.sendBeacon(this.config.serverUrl, blob)) {
227
- // this.fallbackSend(payload);
228
- // }
229
- // } else {
230
- // this.fallbackSend(payload);
231
- // }
232
- /* 改用 fetch api */
233
- this.fallbackSend(payload);
206
+ }
207
+ /**
208
+ * @description 错误事件立即上报
209
+ * @param data 上报的负载
210
+ * @returns void
211
+ */
212
+ sendImmediately(payload) {
213
+ // if (navigator.sendBeacon) {
214
+ // const blob = new Blob([JSON.stringify(payload)], {
215
+ // type: "application/json",
216
+ // });
217
+ // if (!navigator.sendBeacon(this.config.serverUrl, blob)) {
218
+ // this.fallbackSend(payload);
219
+ // }
220
+ // } else {
221
+ // this.fallbackSend(payload);
222
+ // }
223
+ /* 改用 fetch api */
224
+ this.fallbackSend(payload);
225
+ }
226
+ /**
227
+ * @description 批量上报
228
+ * @param events 上报的事件数组
229
+ * @returns Promise<void>
230
+ */
231
+ async sendBatch(events) {
232
+ const payload = {
233
+ events
234
+ };
235
+ // if (navigator.sendBeacon) {
236
+ // const blob = new Blob([JSON.stringify(payload)], {
237
+ // type: "application/json",
238
+ // });
239
+ // if (!navigator.sendBeacon(this.config.serverUrl, blob)) {
240
+ // await this.fallbackSendBatch(payload);
241
+ // }
242
+ // } else {
243
+ // await this.fallbackSendBatch(payload);
244
+ // }
245
+ /* 改用 fetch api */
246
+ await this.fallbackSendBatch(payload);
247
+ }
248
+ /**
249
+ * @description 批量上报的fetch回退方案
250
+ * @param payload 上报的负载
251
+ * @returns Promise<void>
252
+ */
253
+ async fallbackSendBatch(payload) {
254
+ try {
255
+ await fetch(this.config.serverUrl, {
256
+ method: "POST",
257
+ headers: {
258
+ "Content-Type": "application/json"
259
+ },
260
+ body: JSON.stringify(payload),
261
+ keepalive: true
262
+ // credentials: "include",
263
+ });
264
+ } catch (error) {
265
+ if (this.config.debug) {
266
+ console.error("[fallbackSendBatch] fecth批量上报失败:", error);
267
+ }
268
+ // 可选:存入 localStorage 用于下次重试
269
+ this.saveFailedBatch(payload);
234
270
  }
235
- /**
236
- * @description 批量上报
237
- * @param events 上报的事件数组
238
- * @returns Promise<void>
239
- */
240
- async sendBatch(events) {
241
- const payload = { events };
242
- // if (navigator.sendBeacon) {
243
- // const blob = new Blob([JSON.stringify(payload)], {
244
- // type: "application/json",
245
- // });
246
- // if (!navigator.sendBeacon(this.config.serverUrl, blob)) {
247
- // await this.fallbackSendBatch(payload);
248
- // }
249
- // } else {
250
- // await this.fallbackSendBatch(payload);
251
- // }
252
- /* 改用 fetch api */
253
- await this.fallbackSendBatch(payload);
271
+ }
272
+ saveFailedBatch(payload) {
273
+ try {
274
+ const key = "tracker_failed_batches";
275
+ const existing = localStorage.getItem(key);
276
+ const batches = existing ? JSON.parse(existing) : [];
277
+ batches.push(payload);
278
+ if (batches.length > 3) batches.shift(); // 限制最多3批
279
+ localStorage.setItem(key, JSON.stringify(batches));
280
+ } catch (e) {
281
+ // localStorage 可能满或禁用,静默忽略
254
282
  }
255
- /**
256
- * @description 批量上报的fetch回退方案
257
- * @param payload 上报的负载
258
- * @returns Promise<void>
259
- */
260
- async fallbackSendBatch(payload) {
261
- try {
262
- await fetch(this.config.serverUrl, {
263
- method: "POST",
264
- headers: {
265
- "Content-Type": "application/json",
266
- },
267
- body: JSON.stringify(payload),
268
- keepalive: true,
269
- // credentials: "include",
270
- });
271
- }
272
- catch (error) {
273
- if (this.config.debug) {
274
- console.error("[fallbackSendBatch] fecth批量上报失败:", error);
275
- }
276
- // 可选:存入 localStorage 用于下次重试
277
- this.saveFailedBatch(payload);
283
+ }
284
+ /**
285
+ * @description 自定义点击事件
286
+ */
287
+ track(event, properties = {}) {
288
+ this.send({
289
+ eventType: "custom",
290
+ event,
291
+ properties
292
+ });
293
+ }
294
+ /**
295
+ * @description 用于自动点击埋点
296
+ *
297
+ * @param element 点击的DOM元素
298
+ */
299
+ trackClick(element) {
300
+ const tagName = element.tagName.toLowerCase();
301
+ const id = element.id || "";
302
+ const classes = Array.from(element.classList).join(" ");
303
+ const text = ["button", "a"].includes(tagName) ? element.innerText?.trim().slice(0, 20) : "";
304
+ if (this.config.enableAutoClickTagNameList.includes(tagName)) {
305
+ this.send({
306
+ eventType: "click",
307
+ event: "auto_click",
308
+ properties: {
309
+ tagName,
310
+ id,
311
+ classes,
312
+ text
278
313
  }
314
+ });
315
+ } else {
316
+ if (this.config.debug) {
317
+ console.log("点击元素不触发自动埋点");
318
+ }
279
319
  }
280
- saveFailedBatch(payload) {
281
- try {
282
- const key = "tracker_failed_batches";
283
- const existing = localStorage.getItem(key);
284
- const batches = existing ? JSON.parse(existing) : [];
285
- batches.push(payload);
286
- if (batches.length > 3)
287
- batches.shift(); // 限制最多3批
288
- localStorage.setItem(key, JSON.stringify(batches));
289
- }
290
- catch (e) {
291
- // localStorage 可能满或禁用,静默忽略
320
+ }
321
+ // #endregion
322
+ // #region pv & uv
323
+ /**
324
+ * @description 自动pv
325
+ *
326
+ * @param path pv的页面路径
327
+ * @param properties 上报数据
328
+ */
329
+ trackPV(path, properties = {}) {
330
+ this.send({
331
+ eventType: "pv",
332
+ properties: {
333
+ path,
334
+ referrer: document.referrer,
335
+ ...properties
336
+ }
337
+ });
338
+ }
339
+ /**
340
+ * @description 统计uv(每天每个设备只上报一次)
341
+ *
342
+ */
343
+ trackUV() {
344
+ const deviceId = this.getDeviceId();
345
+ const now = new Date().getTime();
346
+ const beijingTime = new Date(now + 8 * 3600 * 1000);
347
+ const today = new Date(beijingTime).toISOString().split("T")[0];
348
+ const uvKey = `tracker_uv_${today}`;
349
+ if (!localStorage.getItem(uvKey)) {
350
+ this.send({
351
+ eventType: "uv",
352
+ properties: {
353
+ deviceId,
354
+ date: today
292
355
  }
356
+ });
357
+ localStorage.setItem(uvKey, "1");
293
358
  }
294
- /**
295
- * @description 自定义点击事件
296
- */
297
- track(event, properties = {}) {
298
- this.send({
299
- eventType: "custom",
300
- event,
301
- properties,
302
- });
303
- }
304
- /**
305
- * @description 用于自动点击埋点
306
- *
307
- * @param element 点击的DOM元素
308
- */
309
- trackClick(element) {
310
- var _a;
311
- const tagName = element.tagName.toLowerCase();
312
- const id = element.id || "";
313
- const classes = Array.from(element.classList).join(" ");
314
- const text = ["button", "a"].includes(tagName)
315
- ? (_a = element.innerText) === null || _a === void 0 ? void 0 : _a.trim().slice(0, 20)
316
- : "";
317
- if (this.config.enableAutoClickTagNameList.includes(tagName)) {
318
- this.send({
319
- eventType: "click",
320
- event: "auto_click",
321
- properties: {
322
- tagName,
323
- id,
324
- classes,
325
- text,
326
- },
327
- });
359
+ }
360
+ // #endregion
361
+ // #region 错误监控
362
+ /** @description 初始化错误事件监听
363
+ *
364
+ */
365
+ setupErrorTracking() {
366
+ // 全局 JS 错误
367
+ window.addEventListener("error", ({
368
+ message,
369
+ filename,
370
+ lineno,
371
+ colno,
372
+ error
373
+ }) => {
374
+ console.log("🚀 ~ '全局 JS 错误捕获了'");
375
+ this.send({
376
+ eventType: "error",
377
+ event: "js_error",
378
+ properties: {
379
+ message,
380
+ filename,
381
+ lineno,
382
+ colno,
383
+ stack: error?.stack || "no stack"
328
384
  }
329
- else {
330
- if (this.config.debug) {
331
- console.log("点击元素不触发自动埋点");
332
- }
385
+ });
386
+ });
387
+ // 资源加载错误
388
+ window.addEventListener("error", e => {
389
+ if (e.target?.localName) {
390
+ let url = e.target.src || e.target.href || "";
391
+ url = url.trim();
392
+ const tag = e.target;
393
+ const originalSrc = tag.getAttribute("src");
394
+ const originalHref = tag.getAttribute("href");
395
+ // 如果原始值是空字符串(空 src 会被解析成当前页)、null、undefined,则是伪错误
396
+ if (originalSrc != null && originalSrc.trim() === "" || originalHref != null && originalHref.trim() === "") {
397
+ return false; // 过滤掉
333
398
  }
334
- }
335
- // #endregion
336
- // #region pv & uv
337
- /**
338
- * @description 自动pv
339
- *
340
- * @param path pv的页面路径
341
- * @param properties 上报数据
342
- */
343
- trackPV(path, properties = {}) {
344
- this.send({
345
- eventType: "pv",
399
+ if (url == "") return false;
400
+ try {
401
+ // 验证是否是合法 URL
402
+ new URL(url);
403
+ console.log("🚀 ~ '资源加载错误捕获了'");
404
+ this.send({
405
+ eventType: "error",
406
+ event: "resource_load_error",
346
407
  properties: {
347
- path,
348
- referrer: document.referrer,
349
- ...properties,
350
- },
351
- });
352
- }
353
- /**
354
- * @description 统计uv(每天每个设备只上报一次)
355
- *
356
- */
357
- trackUV() {
358
- const deviceId = this.getDeviceId();
359
- const now = new Date().getTime();
360
- const beijingTime = new Date(now + 8 * 3600 * 1000);
361
- const today = new Date(beijingTime).toISOString().split("T")[0];
362
- const uvKey = `tracker_uv_${today}`;
363
- if (!localStorage.getItem(uvKey)) {
364
- this.send({
365
- eventType: "uv",
366
- properties: {
367
- deviceId,
368
- date: today,
369
- },
370
- });
371
- localStorage.setItem(uvKey, "1");
372
- }
373
- }
374
- // #endregion
375
- // #region 错误监控
376
- /** @description 初始化错误事件监听
377
- *
378
- */
379
- setupErrorTracking() {
380
- // 全局 JS 错误
381
- window.addEventListener("error", ({ message, filename, lineno, colno, error }) => {
382
- console.log("🚀 ~ '全局 JS 错误捕获了'");
383
- this.send({
384
- eventType: "error",
385
- event: "js_error",
386
- properties: {
387
- message,
388
- filename,
389
- lineno,
390
- colno,
391
- stack: (error === null || error === void 0 ? void 0 : error.stack) || "no stack",
392
- },
393
- });
394
- });
395
- // 资源加载错误
396
- window.addEventListener("error", (e) => {
397
- var _a;
398
- if ((_a = e.target) === null || _a === void 0 ? void 0 : _a.localName) {
399
- let url = e.target.src || e.target.href || "";
400
- url = url.trim();
401
- const tag = e.target;
402
- const originalSrc = tag.getAttribute("src");
403
- const originalHref = tag.getAttribute("href");
404
- // 如果原始值是空字符串(空 src 会被解析成当前页)、null、undefined,则是伪错误
405
- if ((originalSrc != null && originalSrc.trim() === "") ||
406
- (originalHref != null && originalHref.trim() === "")) {
407
- return false; // 过滤掉
408
- }
409
- if (url == "")
410
- return false;
411
- try {
412
- // 验证是否是合法 URL
413
- new URL(url);
414
- console.log("🚀 ~ '资源加载错误捕获了'");
415
- this.send({
416
- eventType: "error",
417
- event: "resource_load_error",
418
- properties: {
419
- resourceUrl: url,
420
- tageName: e.target.localName,
421
- },
422
- });
423
- }
424
- catch (err) {
425
- return false;
426
- }
408
+ resourceUrl: url,
409
+ tageName: e.target.localName
427
410
  }
428
- }, true);
429
- // Promise unhandled rejection
430
- window.addEventListener("unhandledrejection", (event) => {
431
- "Promise unhandled rejection捕获了";
432
- var _a;
433
- console.log("🚀 ~ 'Promise unhandled rejection捕获了'");
434
- this.send({
435
- eventType: "error",
436
- event: "promise_unhandled_rejection",
437
- properties: {
438
- reason: String(event.reason),
439
- stack: ((_a = event.reason) === null || _a === void 0 ? void 0 : _a.stack) || "no stack",
440
- },
441
- });
442
- // 阻止打印报错信息
443
- event.preventDefault();
444
- });
445
- }
446
- /**
447
- * @description vue3 全局错误处理器
448
- */
449
- setupVueErrorHandlder(app) {
450
- app.config.errorHandler = (err, instance, info) => {
451
- var _a;
452
- console.log("🚀 ~ vue3 全局错误处理器捕获了");
453
- // setTimeout(() => {
454
- // throw err;
455
- // }, 0);
456
- this.send({
457
- eventType: "error",
458
- event: "vue_component_error",
459
- properties: {
460
- message: err.message,
461
- stack: err.stack || "no stack",
462
- componentInfo: info,
463
- componentName: ((_a = instance === null || instance === void 0 ? void 0 : instance.$options) === null || _a === void 0 ? void 0 : _a.name) || "unknown",
464
- },
465
- });
466
- };
467
- }
468
- // #endregion
469
- // #region 性能监控
470
- setupPerformanceTracking() {
471
- // if (!("performance" in window)) return;
472
- // const perf: any = performance.getEntriesByType("navigation")[0];
473
- // if (!perf) return;
474
- // this.send({
475
- // eventType: "performance",
476
- // event: "page_performance",
477
- // properties: {
478
- // dns: perf.domainLookupEnd - perf.domainLookupStart,
479
- // tcpAndTls: perf.connectEnd - perf.connectStart,
480
- // domReady: perf.domContentLoadedEventEnd - perf.fetchStart,
481
- // load: perf.loadEventEnd - perf.fetchStart,
482
- // },
483
- // });
484
- this.observeFCP();
485
- this.observeLCP();
486
- this.observeCLS();
487
- this.observeTTFB();
488
- }
489
- /**
490
- * @description 监控FCP
491
- * @returns void
492
- */
493
- observeFCP() {
494
- webVitals.onFCP((metric) => {
495
- this.send({
496
- eventType: "performance",
497
- event: "first_contentful_paint",
498
- properties: metric,
499
- });
500
- });
501
- }
502
- /**
503
- * @description 监控LCP
504
- * @returns void
505
- */
506
- observeLCP() {
507
- webVitals.onLCP((metric) => {
508
- this.send({
509
- eventType: "performance",
510
- event: "largest_contentful_paint",
511
- properties: metric,
512
- });
513
- });
514
- }
515
- /**
516
- * @description 监控CLS
517
- * @returns void
518
- */
519
- observeCLS() {
520
- webVitals.onCLS((metric) => {
521
- this.send({
522
- eventType: "performance",
523
- event: "cumulative_layout_shift",
524
- properties: {
525
- ...metric,
526
- entries: undefined, // 去掉 entries,数据量可能很庞大
527
- },
528
- });
529
- });
530
- }
531
- /**
532
- * @description 监控TTFB
533
- * @returns void
534
- */
535
- observeTTFB() {
536
- webVitals.onTTFB((metric) => {
537
- this.send({
538
- eventType: "performance",
539
- event: "time_to_first_byte",
540
- properties: metric,
541
- });
542
- });
543
- }
544
- // #endregion
545
- // #region 全埋点
546
- /**
547
- * @description 全埋点(谨慎使用,存在性能问题)
548
- */
549
- enableAutoTracking() {
550
- // 防止重复绑定
551
- if (this.autoTrackClickHandler)
552
- return;
553
- this.autoTrackClickHandler = (e) => {
554
- if (e.target instanceof Element) {
555
- this.trackClick(e.target);
556
- }
557
- };
558
- document.documentElement.addEventListener("click", this.autoTrackClickHandler);
559
- }
560
- /**
561
- * @description: 设置可触发自动埋点的DOM元素标签名称白名单
562
- */
563
- setEnableAutoClickTagNameList(tagNameList) {
564
- if (this.config.autoTrack) {
565
- this.config.enableAutoClickTagNameList = tagNameList;
411
+ });
412
+ } catch (err) {
413
+ return false;
414
+ }
415
+ }
416
+ }, true);
417
+ // Promise unhandled rejection
418
+ window.addEventListener("unhandledrejection", event => {
419
+ "Promise unhandled rejection捕获了";
420
+
421
+ console.log("🚀 ~ 'Promise unhandled rejection捕获了'");
422
+ this.send({
423
+ eventType: "error",
424
+ event: "promise_unhandled_rejection",
425
+ properties: {
426
+ reason: String(event.reason),
427
+ stack: event.reason?.stack || "no stack"
428
+ }
429
+ });
430
+ // 阻止打印报错信息
431
+ event.preventDefault();
432
+ });
433
+ }
434
+ /**
435
+ * @description vue 全局错误处理器
436
+ */
437
+ setupVueErrorHandlder(app) {
438
+ app.config.errorHandler = (err, instance, info) => {
439
+ console.log("🚀 ~ vue 全局错误处理器捕获了");
440
+ // setTimeout(() => {
441
+ // throw err;
442
+ // }, 0);
443
+ this.send({
444
+ eventType: "error",
445
+ event: "vue_component_error",
446
+ properties: {
447
+ message: err.message,
448
+ stack: err.stack || "no stack",
449
+ componentInfo: info,
450
+ componentName: instance?.$options?.name || "unknown"
566
451
  }
452
+ });
453
+ };
454
+ }
455
+ // #endregion
456
+ // #region 性能监控
457
+ setupPerformanceTracking() {
458
+ // if (!("performance" in window)) return;
459
+ // const perf: any = performance.getEntriesByType("navigation")[0];
460
+ // if (!perf) return;
461
+ // this.send({
462
+ // eventType: "performance",
463
+ // event: "page_performance",
464
+ // properties: {
465
+ // dns: perf.domainLookupEnd - perf.domainLookupStart,
466
+ // tcpAndTls: perf.connectEnd - perf.connectStart,
467
+ // domReady: perf.domContentLoadedEventEnd - perf.fetchStart,
468
+ // load: perf.loadEventEnd - perf.fetchStart,
469
+ // },
470
+ // });
471
+ this.observeFCP();
472
+ this.observeLCP();
473
+ this.observeCLS();
474
+ this.observeTTFB();
475
+ }
476
+ /**
477
+ * @description 监控FCP
478
+ * @returns void
479
+ */
480
+ observeFCP() {
481
+ webVitals.onFCP(metric => {
482
+ this.send({
483
+ eventType: "performance",
484
+ event: "first_contentful_paint",
485
+ properties: metric
486
+ });
487
+ });
488
+ }
489
+ /**
490
+ * @description 监控LCP
491
+ * @returns void
492
+ */
493
+ observeLCP() {
494
+ webVitals.onLCP(metric => {
495
+ this.send({
496
+ eventType: "performance",
497
+ event: "largest_contentful_paint",
498
+ properties: metric
499
+ });
500
+ });
501
+ }
502
+ /**
503
+ * @description 监控CLS
504
+ * @returns void
505
+ */
506
+ observeCLS() {
507
+ webVitals.onCLS(metric => {
508
+ this.send({
509
+ eventType: "performance",
510
+ event: "cumulative_layout_shift",
511
+ properties: {
512
+ ...metric,
513
+ entries: undefined // 去掉 entries,数据量可能很庞大
514
+ }
515
+ });
516
+ });
517
+ }
518
+ /**
519
+ * @description 监控TTFB
520
+ * @returns void
521
+ */
522
+ observeTTFB() {
523
+ webVitals.onTTFB(metric => {
524
+ this.send({
525
+ eventType: "performance",
526
+ event: "time_to_first_byte",
527
+ properties: metric
528
+ });
529
+ });
530
+ }
531
+ // #endregion
532
+ // #region 全埋点
533
+ /**
534
+ * @description 全埋点(谨慎使用,存在性能问题)
535
+ */
536
+ enableAutoTracking() {
537
+ // 防止重复绑定
538
+ if (this.autoTrackClickHandler) return;
539
+ this.autoTrackClickHandler = e => {
540
+ if (e.target instanceof Element) {
541
+ this.trackClick(e.target);
542
+ }
543
+ };
544
+ document.documentElement.addEventListener("click", this.autoTrackClickHandler);
545
+ }
546
+ /**
547
+ * @description: 设置可触发自动埋点的DOM元素标签名称白名单
548
+ */
549
+ setEnableAutoClickTagNameList(tagNameList) {
550
+ if (this.config.autoTrack) {
551
+ this.config.enableAutoClickTagNameList = tagNameList;
567
552
  }
553
+ }
568
554
  }
569
555
  // 单例导出
570
556
  const tracker = new TrackerSDK();
571
557
 
572
558
  var index = {
573
- install(app, options) {
574
- tracker.init(options);
575
- if (app.provide) {
576
- app.provide("tracker", tracker);
577
- }
578
- // 自动化埋点,存在 performance
579
- if (options.autoTrack) {
580
- tracker.enableAutoTracking();
581
- }
582
- },
559
+ install(app, options) {
560
+ tracker.init(options);
561
+ if (app.provide) {
562
+ app.provide("tracker", tracker);
563
+ }
564
+ // 自动化埋点,存在 performance
565
+ if (options.autoTrack) {
566
+ tracker.enableAutoTracking();
567
+ }
568
+ }
583
569
  };
584
570
  function useTracker() {
585
- return tracker;
571
+ return tracker;
586
572
  }
587
573
 
588
574
  exports.default = index;