keeson-web-report-sdk 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.
Files changed (2) hide show
  1. package/bundle.min.js +432 -1
  2. package/package.json +1 -1
package/bundle.min.js CHANGED
@@ -1 +1,432 @@
1
- function o(o){const t=[];return t.push(function(o){const t=history.pushState,e=history.replaceState;history.pushState=function(...e){const n=window.location.href,r=t.apply(this,e),s=window.location.href;return n!==s&&o({to:s,from:n}),r},history.replaceState=function(...t){const n=window.location.href,r=e.apply(this,t),s=window.location.href;return n!==s&&o({to:s,from:n}),r};const n=()=>{o({to:window.location.href,from:window.location.href})};return window.addEventListener("popstate",n),()=>{window.removeEventListener("popstate",n),history.pushState=t,history.replaceState=e}}(o)),()=>{t.forEach(o=>o())}}const t={appId:"",env:"production",appUrl:window.location.origin,reportError:!0,reportPromiseReject:!0,reportVueError:!0,vueInstance:null,reportPerformance:!0,reportLongTimeRequest:!0,longTimeRequestThreshold:2e3,reportErrorReqest:!0,reportUserNavigate:!0};class e{records=[];timer=null;options={};constructor(o){this.options={...t,...o}}init(){var t,e,n,r;if(this.options.reportError&&(t=({msg:o,url:t,line:e,col:n,error:r})=>{console.log({msg:o,url:t,line:e,col:n,error:r})},window.onerror=function(o,e,n,r,s){t&&t({msg:o,url:e,line:n,col:r,error:s})}),this.options.reportPromiseReject&&function(o){window.addEventListener("unhandledrejection",function(t){o&&o(t)})}(o=>{console.log("Unhandled Promise Rejection:",o,o.reason)}),this.options.reportVueError&&this.options.vueInstance&&function(o,t){o.config.errorHandler=function(o,e,n){console.error(`Error in ${n}:`,o),t&&t({err:o,vm:e,info:n})}}(this.options.vueInstance,({err:o,vm:t,info:e})=>{console.log("Vue error:",o,t,e)}),this.options.reportPerformance&&function(o){if(PerformanceNavigationTiming){const t=PerformanceNavigationTiming.loadEventEnd-PerformanceNavigationTiming.navigationStart;console.log("Page load time: "+t+"ms");const e=PerformanceNavigationTiming.getEntriesByType("largest-contentful-paint")[0];console.log("Largest Contentful Paint:",e.startTime);const n=PerformanceNavigationTiming.getEntriesByType("first-contentful-paint")[0];console.log("First Contentful Paint:",n.startTime);const r=PerformanceNavigationTiming.getEntriesByType("layout-shift")[0];console.log("Cumulative Layout Shift:",r.value);const s=PerformanceNavigationTiming.getEntriesByType("first-input-delay")[0];console.log("First Input Delay:",s.startTime),console.log("Total Blocking Time:",s.value);const i=PerformanceNavigationTiming.getEntriesByType("first-input")[0];console.log("First Input:",i.startTime);const a=PerformanceNavigationTiming.domainLookupEnd-PerformanceNavigationTiming.domainLookupStart;console.log("DNS查询时间:",a),o&&o({time:t,lcp:e,fcp:n,cls:r,fmp:s,FID:i,dnsTime:a})}}(({time:o,lcp:t,fcp:e,cls:n,fmp:r,FID:s,dnsTime:i})=>{console.log({time:o,lcp:t,fcp:e,cls:n,fmp:r,FID:s,dnsTime:i})}),this.options.reportLongTimeRequest&&(e=o=>{console.log(o)},n=o=>{console.log(o)},r=XMLHttpRequest.prototype.open,XMLHttpRequest.prototype.open=function(o,t,s,i,a){let c=Date.now();this.addEventListener("load",function(){const n=Date.now();e&&e({url:t,method:o,duration:n-c,status:this.status,statusText:this.statusText,response:this.responseText}),console.log("Response:",this.responseText)}),this.addEventListener("error",function(){console.log("Error:",this.statusText),n&&n({url:t,method:o,duration:duration,status:this.status,statusText:this.statusText,response:this.responseText})}),console.log("Request URL:",t),console.log("Request Method:",o),r.call(this,o,t,s,i,a)},function(o,t){var e=window.fetch;window.fetch=function(n,r){console.log("Fetch URL:",n),console.log("Fetch Options:",r),r&&r.headers&&(r.headers["X-Custom-Header"]="Value");let s=Date.now();return e(n,r).then(t=>{const e=Date.now();return o&&o({url:n,method:method,duration:e-s,status:t.status,statusText:t.statusText,response:t.responseText}),console.log("Response:",t),t}).catch(o=>{console.log("Error:",o),t&&t({url:n,method:method,duration:duration})})}}(o=>{console.log(o)},o=>{console.log(o)})),this.options.reportUserNavigate){const t=window.location.href;console.log("current href:",t),o(o=>{console.log("route change:",o)})}}addReportRecord(o){this.timer&&clearTimeout(this.timer),this.timer=setTimeout(()=>{try{this.sendReport(JSON.parse(JSON.stringify(this.records))),this.records=[]}catch(o){console.log(o)}},1e3),this.records.push(o)}sendReport(o){this.apiDo(this.records)}}let n;function r(o={}){n||(n=new e(o)),n.init()}export{r as init};
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.umd = {}));
5
+ })(this, (function (exports) { 'use strict';
6
+
7
+ // 错误上报
8
+
9
+ // 全局错误处理
10
+ function globalErrorHandler(callback) {
11
+ window.onerror = function (msg, url, line, col, error) {
12
+ callback && callback({msg, url, line, col, error});
13
+ };
14
+ }
15
+
16
+ function promiseErrorHandler(callback) {
17
+ window.addEventListener("unhandledrejection", function (event) {
18
+ // console.log("Unhandled Promise Rejection:", event.reason);
19
+ callback && callback(event);
20
+ });
21
+ }
22
+
23
+ /**
24
+ * @param {*} v vue2的话是Vue,vue3的话是app
25
+ */
26
+ function vueErrorHandler(v, callback) {
27
+ // vue2 vue3
28
+ v.config.errorHandler = function (err, vm, info) {
29
+ // handle error
30
+ // `info` 是 Vue 特定的错误信息,比如错误发生在哪一个组件上
31
+ console.error(`Error in ${info}:`, err);
32
+ callback && callback({err, vm, info});
33
+ };
34
+ }
35
+
36
+ // react
37
+ // export function reactErrorHandler() {
38
+ // }
39
+
40
+ // 性能上报
41
+ function reportPerformance(callback) {
42
+ if (PerformanceNavigationTiming) {
43
+ const time =
44
+ PerformanceNavigationTiming.loadEventEnd -
45
+ PerformanceNavigationTiming.navigationStart;
46
+ console.log("Page load time: " + time + "ms");
47
+
48
+ const lcp = PerformanceNavigationTiming.getEntriesByType(
49
+ "largest-contentful-paint"
50
+ )[0];
51
+ console.log("Largest Contentful Paint:", lcp.startTime);
52
+ const fcp = PerformanceNavigationTiming.getEntriesByType(
53
+ "first-contentful-paint"
54
+ )[0];
55
+ console.log("First Contentful Paint:", fcp.startTime);
56
+ const cls = PerformanceNavigationTiming.getEntriesByType("layout-shift")[0];
57
+ console.log("Cumulative Layout Shift:", cls.value);
58
+ const fmp =
59
+ PerformanceNavigationTiming.getEntriesByType("first-input-delay")[0];
60
+ console.log("First Input Delay:", fmp.startTime);
61
+ console.log("Total Blocking Time:", fmp.value);
62
+ const FID = PerformanceNavigationTiming.getEntriesByType("first-input")[0];
63
+ console.log("First Input:", FID.startTime);
64
+
65
+ // 计算DNS查询时间
66
+ const dnsTime =
67
+ PerformanceNavigationTiming.domainLookupEnd -
68
+ PerformanceNavigationTiming.domainLookupStart;
69
+ console.log("DNS查询时间:", dnsTime);
70
+
71
+
72
+ callback && callback({
73
+ time,
74
+ lcp,
75
+ fcp,
76
+ cls,
77
+ fmp,
78
+ FID,
79
+ dnsTime
80
+ });
81
+ }
82
+ }
83
+
84
+ function xhrReportHandle(onsuccess, onerror) {
85
+ var oldOpen = XMLHttpRequest.prototype.open;
86
+ XMLHttpRequest.prototype.open = function (method, url, async, user, pass) {
87
+ let startTime = Date.now();
88
+ this.addEventListener("load", function () {
89
+ const endTime = Date.now();
90
+ const duration = endTime - startTime;
91
+ onsuccess &&
92
+ onsuccess({
93
+ url: url,
94
+ method: method,
95
+ duration: duration,
96
+ status: this.status,
97
+ statusText: this.statusText,
98
+ response: this.responseText,
99
+ });
100
+ // 可以在这里处理响应
101
+ console.log("Response:", this.responseText);
102
+ });
103
+ this.addEventListener("error", function () {
104
+ console.log("Error:", this.statusText);
105
+ onerror &&
106
+ onerror({
107
+ url: url,
108
+ method: method,
109
+ duration: duration,
110
+ status: this.status,
111
+ statusText: this.statusText,
112
+ response: this.responseText,
113
+ });
114
+ });
115
+ // 修改或记录原始请求信息
116
+ console.log("Request URL:", url);
117
+ console.log("Request Method:", method);
118
+ // 继续原有的open调用
119
+ oldOpen.call(this, method, url, async, user, pass);
120
+ };
121
+ }
122
+
123
+ function fetchReportHandle(onsuccess, onerror) {
124
+ var realFetch = window.fetch;
125
+ window.fetch = function (url, options) {
126
+ console.log("Fetch URL:", url);
127
+ console.log("Fetch Options:", options);
128
+ // 可以在这里修改options,例如添加headers等
129
+ if (options && options.headers) {
130
+ options.headers["X-Custom-Header"] = "Value";
131
+ }
132
+ let startTime = Date.now();
133
+ // 继续原有的fetch调用
134
+ return realFetch(url, options)
135
+ .then((response) => {
136
+ const endTime = Date.now();
137
+ const duration = endTime - startTime;
138
+ onsuccess &&
139
+ onsuccess({
140
+ url: url,
141
+ method: method,
142
+ duration: duration,
143
+ status: response.status,
144
+ statusText: response.statusText,
145
+ response: response.responseText,
146
+ });
147
+ // 可以在这里处理响应
148
+ console.log("Response:", response);
149
+ return response;
150
+ })
151
+ .catch((error) => {
152
+ console.log("Error:", error);
153
+ onerror &&
154
+ onerror({
155
+ url: url,
156
+ method: method,
157
+ duration: duration,
158
+ // status: response.status,
159
+ // statusText: response.statusText,
160
+ // response: response.responseText,
161
+ });
162
+ });
163
+ };
164
+ }
165
+
166
+ // 用户行为上报
167
+
168
+ // 路由跳转监听
169
+
170
+ /**
171
+ * 监听地址栏路由变化,包括传统路由和虚拟路由(如Vue Router、React Router)
172
+ * 通过监听History API和popstate事件实现
173
+ * @param {Function} callback 路由变化时的回调函数,接收{ to, from }参数
174
+ * @returns {Function} 清理函数,用于移除监听
175
+ */
176
+ function routeChangeHandler(callback) {
177
+ // 保存原始的pushState和replaceState方法
178
+ const originalPushState = history.pushState;
179
+ const originalReplaceState = history.replaceState;
180
+
181
+ // 重写pushState方法
182
+ history.pushState = function (...args) {
183
+ const from = window.location.href;
184
+ const result = originalPushState.apply(this, args);
185
+ const to = window.location.href;
186
+ if (from !== to) {
187
+ callback({ to, from });
188
+ }
189
+ return result;
190
+ };
191
+
192
+ // 重写replaceState方法
193
+ history.replaceState = function (...args) {
194
+ const from = window.location.href;
195
+ const result = originalReplaceState.apply(this, args);
196
+ const to = window.location.href;
197
+ if (from !== to) {
198
+ callback({ to, from });
199
+ }
200
+ return result;
201
+ };
202
+
203
+ // 监听popstate事件(back/forward按钮)
204
+ const popstateHandler = () => {
205
+ // 对于popstate事件,我们无法直接获取from,所以使用当前URL作为from和to
206
+ // 在实际应用中,可以通过维护一个路由历史栈来获取更准确的from信息
207
+ callback({ to: window.location.href, from: window.location.href });
208
+ };
209
+ window.addEventListener("popstate", popstateHandler);
210
+
211
+ // 返回清理函数
212
+ return () => {
213
+ window.removeEventListener("popstate", popstateHandler);
214
+ // 恢复原始方法
215
+ history.pushState = originalPushState;
216
+ history.replaceState = originalReplaceState;
217
+ };
218
+ }
219
+
220
+ // /**
221
+ // * Vue Router路由监听
222
+ // * @param {Object} router Vue Router实例
223
+ // * @param {Function} callback 路由变化时的回调函数,接收{ to, from }参数
224
+ // * @returns {Function} 清理函数,用于移除监听
225
+ // */
226
+ // function vueRouterChangeHandler(router, callback) {
227
+ // if (!router) {
228
+ // console.warn('Vue Router实例未提供,无法监听路由变化');
229
+ // return () => {};
230
+ // }
231
+
232
+ // // 使用Vue Router的beforeEach钩子监听路由变化
233
+ // const removeRouterHook = router.beforeEach((to, from, next) => {
234
+ // callback({ to, from });
235
+ // next();
236
+ // });
237
+
238
+ // // 同时监听History API变化,确保覆盖所有路由变化场景
239
+ // const removeHistoryListener = routeChangeHandler(callback);
240
+
241
+ // // 返回组合的清理函数
242
+ // return () => {
243
+ // removeRouterHook();
244
+ // removeHistoryListener();
245
+ // };
246
+ // }
247
+
248
+ // /**
249
+ // * React Router路由监听(支持v4+)
250
+ // * @param {Object} history React Router的history对象
251
+ // * @param {Function} callback 路由变化时的回调函数,接收{ to, from }参数
252
+ // * @returns {Function} 清理函数,用于移除监听
253
+ // */
254
+ // function reactRouterChangeHandler(history, callback) {
255
+ // if (!history) {
256
+ // console.warn('React Router的history对象未提供,无法监听路由变化');
257
+ // return () => {};
258
+ // }
259
+
260
+ // let lastLocation = history.location;
261
+
262
+ // // 使用history.listen监听路由变化
263
+ // const unlisten = history.listen((location) => {
264
+ // callback({ to: location, from: lastLocation });
265
+ // lastLocation = location;
266
+ // });
267
+
268
+ // // 同时监听History API变化,确保覆盖所有路由变化场景
269
+ // const removeHistoryListener = routeChangeHandler(callback);
270
+
271
+ // // 返回组合的清理函数
272
+ // return () => {
273
+ // unlisten();
274
+ // removeHistoryListener();
275
+ // };
276
+ // }
277
+
278
+ /**
279
+ * 初始化路由监听
280
+ * @param {Object} options 配置选项
281
+ * @param {Object} [options.vueRouter] Vue Router实例
282
+ * @param {Object} [options.reactHistory] React Router的history对象
283
+ * @param {Function} callback 路由变化时的回调函数
284
+ * @returns {Function} 清理函数,用于移除所有监听
285
+ */
286
+ function initRouteListener(callback) {
287
+ const cleanups = [];
288
+
289
+ // // 如果提供了Vue Router实例,使用Vue Router的监听方式
290
+ // if (options.vueRouter) {
291
+ // cleanups.push(vueRouterChangeHandler(options.vueRouter, callback));
292
+ // }
293
+
294
+ // // 如果提供了React Router的history对象,使用React Router的监听方式
295
+ // else if (options.reactHistory) {
296
+ // cleanups.push(reactRouterChangeHandler(options.reactHistory, callback));
297
+ // }
298
+
299
+ // // 否则使用通用的路由监听方式
300
+ // else {
301
+ // cleanups.push(routeChangeHandler(callback));
302
+ // }
303
+ cleanups.push(routeChangeHandler(callback));
304
+ // 返回组合的清理函数
305
+ return () => {
306
+ cleanups.forEach((cleanup) => cleanup());
307
+ };
308
+ }
309
+
310
+ const defaultOptions = {
311
+ appId: "", // appId
312
+ // appName: '',
313
+ // appVersion: '',
314
+ env: "production",
315
+ appUrl: window.location.origin, // 站点地址, 对应应用配置网站站点地址
316
+
317
+ reportError: true,
318
+ reportPromiseReject: true,
319
+ reportVueError: true,
320
+ vueInstance: null,
321
+
322
+ reportPerformance: true, // 是否上报性能数据
323
+
324
+ reportLongTimeRequest: true, // 是否上报长请求时间接口
325
+ longTimeRequestThreshold: 2000, // 长请求时间阈值,单位毫秒
326
+ reportErrorReqest: true, // 是否上报报错请求
327
+
328
+ reportUserNavigate: true, // 是否上报用户导航行为
329
+ };
330
+
331
+ class Report {
332
+ records = [];
333
+ timer = null;
334
+ options = {};
335
+ constructor(options) {
336
+ this.options = { ...defaultOptions, ...options };
337
+ }
338
+ init() {
339
+ // listen and callback
340
+ if (this.options.reportError) {
341
+ globalErrorHandler(({ msg, url, line, col, error }) => {
342
+ console.log({ msg, url, line, col, error });
343
+ });
344
+ }
345
+ if (this.options.reportPromiseReject) {
346
+ promiseErrorHandler((event) => {
347
+ console.log("Unhandled Promise Rejection:", event, event.reason);
348
+ });
349
+ }
350
+ if (this.options.reportVueError && this.options.vueInstance) {
351
+ vueErrorHandler(
352
+ this.options.vueInstance,
353
+ ({ err, vm, info }) => {
354
+ console.log("Vue error:", err, vm, info);
355
+ }
356
+ );
357
+ }
358
+
359
+ if (this.options.reportPerformance) {
360
+ reportPerformance(
361
+ ({ time, lcp, fcp, cls, fmp, FID, dnsTime }) => {
362
+ console.log({
363
+ time,
364
+ lcp,
365
+ fcp,
366
+ cls,
367
+ fmp,
368
+ FID,
369
+ dnsTime,
370
+ });
371
+ }
372
+ );
373
+ }
374
+
375
+ if (this.options.reportLongTimeRequest) {
376
+ xhrReportHandle(
377
+ (duration) => {
378
+ console.log(duration);
379
+ },
380
+ (error) => {
381
+ console.log(error);
382
+ }
383
+ );
384
+ fetchReportHandle(
385
+ (duration) => {
386
+ console.log(duration);
387
+ },
388
+ (error) => {
389
+ console.log(error);
390
+ }
391
+ );
392
+ }
393
+
394
+ if (this.options.reportUserNavigate) {
395
+ const href = window.location.href;
396
+ // 上报当前路由
397
+ console.log("current href:", href);
398
+ initRouteListener((record) => {
399
+ // 上报路由变化
400
+ // this.addReportRecord(record);
401
+ console.log("route change:", record);
402
+ });
403
+ }
404
+ }
405
+ addReportRecord(record) {
406
+ this.timer && clearTimeout(this.timer);
407
+ this.timer = setTimeout(() => {
408
+ try {
409
+ this.sendReport(JSON.parse(JSON.stringify(this.records)));
410
+ this.records = [];
411
+ } catch (error) {
412
+ console.log(error);
413
+ }
414
+ }, 1000);
415
+ this.records.push(record);
416
+ }
417
+ sendReport(records) {
418
+ // if(this.records.length === 0) return;
419
+ // todo: 上报数据 api
420
+ this.apiDo(this.records);
421
+ }
422
+ }
423
+
424
+ let report;
425
+ function init(options = {}) {
426
+ if (!report) report = new Report(options);
427
+ report.init();
428
+ }
429
+
430
+ exports.init = init;
431
+
432
+ }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keeson-web-report-sdk",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "for web/h5",
5
5
  "main": "bundle.min.js",
6
6
  "type": "module",