web-tracing-core 2.1.1 → 2.1.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 (62) hide show
  1. package/__test__/css/performance.css +3 -0
  2. package/__test__/err-batch.spec.ts +47 -0
  3. package/__test__/err.spec.ts +82 -0
  4. package/__test__/event.spec.ts +62 -0
  5. package/__test__/html/performance.html +57 -0
  6. package/__test__/html/recordscreen.html +39 -0
  7. package/__test__/http.spec.ts +143 -0
  8. package/__test__/img/performance.png +0 -0
  9. package/__test__/js/performance.js +3 -0
  10. package/__test__/performance.spec.ts +115 -0
  11. package/__test__/recordscreen.spec.ts +50 -0
  12. package/__test__/utils/index.ts +132 -0
  13. package/__test__/utils/pollify.ts +14 -0
  14. package/__test__/utils.spec.ts +18 -0
  15. package/{index.cjs → dist/index.cjs} +1 -1
  16. package/{index.iife.js → dist/index.iife.js} +1 -1
  17. package/{index.iife.min.js → dist/index.iife.min.js} +1 -1
  18. package/{index.mjs → dist/index.mjs} +1 -1
  19. package/dist/package.json +49 -0
  20. package/index.ts +76 -0
  21. package/package.json +9 -9
  22. package/src/common/config.ts +13 -0
  23. package/src/common/constant.ts +57 -0
  24. package/src/common/index.ts +2 -0
  25. package/src/lib/base.ts +129 -0
  26. package/src/lib/err-batch.ts +134 -0
  27. package/src/lib/err.ts +323 -0
  28. package/src/lib/event-dwell.ts +63 -0
  29. package/src/lib/event.ts +252 -0
  30. package/src/lib/eventBus.ts +97 -0
  31. package/src/lib/exportMethods.ts +208 -0
  32. package/src/lib/http.ts +197 -0
  33. package/src/lib/intersectionObserver.ts +169 -0
  34. package/src/lib/line-status.ts +45 -0
  35. package/src/lib/options.ts +325 -0
  36. package/src/lib/performance.ts +302 -0
  37. package/src/lib/pv.ts +199 -0
  38. package/src/lib/recordscreen.ts +169 -0
  39. package/src/lib/replace.ts +371 -0
  40. package/src/lib/sendData.ts +264 -0
  41. package/src/observer/computed.ts +52 -0
  42. package/src/observer/config.ts +1 -0
  43. package/src/observer/dep.ts +21 -0
  44. package/src/observer/index.ts +91 -0
  45. package/src/observer/ref.ts +80 -0
  46. package/src/observer/types.ts +22 -0
  47. package/src/observer/watch.ts +19 -0
  48. package/src/observer/watcher.ts +88 -0
  49. package/src/types/index.ts +126 -0
  50. package/src/utils/debug.ts +17 -0
  51. package/src/utils/element.ts +47 -0
  52. package/src/utils/fingerprintjs.ts +2132 -0
  53. package/src/utils/getIps.ts +127 -0
  54. package/src/utils/global.ts +50 -0
  55. package/src/utils/index.ts +552 -0
  56. package/src/utils/is.ts +78 -0
  57. package/src/utils/localStorage.ts +70 -0
  58. package/src/utils/polyfill.ts +11 -0
  59. package/src/utils/session.ts +29 -0
  60. /package/{LICENSE → dist/LICENSE} +0 -0
  61. /package/{README.md → dist/README.md} +0 -0
  62. /package/{index.d.ts → dist/index.d.ts} +0 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 事件类型
3
+ */
4
+ export enum EVENTTYPES {
5
+ ERROR = 'error',
6
+ CONSOLEERROR = 'consoleError',
7
+ UNHANDLEDREJECTION = 'unhandledrejection',
8
+ CLICK = 'click',
9
+ LOAD = 'load',
10
+ BEFOREUNLOAD = 'beforeunload',
11
+ FETCH = 'fetch',
12
+ XHROPEN = 'xhr-open',
13
+ XHRSEND = 'xhr-send',
14
+ HASHCHANGE = 'hashchange',
15
+ HISTORYPUSHSTATE = 'history-pushState',
16
+ HISTORYREPLACESTATE = 'history-replaceState',
17
+ POPSTATE = 'popstate',
18
+ READYSTATECHANGE = 'readystatechange',
19
+ ONLINE = 'online',
20
+ OFFLINE = 'offline'
21
+ }
22
+
23
+ /**
24
+ * 触发的事件是什么类型 - eventType
25
+ */
26
+ export enum SEDNEVENTTYPES {
27
+ PV = 'pv', // 路由跳转
28
+ PVDURATION = 'pv-duration', // 页面停留事件
29
+ ERROR = 'error', // 错误
30
+ PERFORMANCE = 'performance', // 资源
31
+ CLICK = 'click', // 点击
32
+ DWELL = 'dwell', // 页面卸载
33
+ CUSTOM = 'custom', // 手动触发事件
34
+ INTERSECTION = 'intersection' // 曝光采集
35
+ }
36
+
37
+ /**
38
+ * 触发的事件id - eventID
39
+ */
40
+ export enum SENDID {
41
+ PAGE = 'page', // 页面
42
+ RESOURCE = 'resource', // 资源
43
+ SERVER = 'server', // 请求
44
+ CODE = 'code', // code
45
+ REJECT = 'reject', // reject
46
+ CONSOLEERROR = 'console.error' // console.error
47
+ }
48
+
49
+ /**
50
+ * 网页的几种加载方式
51
+ */
52
+ export const WEBPAGELOAD: Record<number, string> = {
53
+ 0: 'navigate', // 网页通过点击链接,地址栏输入,表单提交,脚本操作等方式加载
54
+ 1: 'reload', // 网页通过“重新加载”按钮或者location.reload()方法加载
55
+ 2: 'back_forward', // 网页通过“前进”或“后退”按钮加载
56
+ 255: 'reserved' // 任何其他来源的加载
57
+ }
@@ -0,0 +1,2 @@
1
+ export * from './config'
2
+ export * from './constant'
@@ -0,0 +1,129 @@
1
+ import { DEVICE_KEY, SDK_VERSION } from '../common'
2
+ import { _support, getGlobal, isTestEnv } from '../utils/global'
3
+ import { load } from '../utils/fingerprintjs'
4
+ import { getCookieByName, uuid } from '../utils'
5
+ import { getSessionId } from '../utils/session'
6
+ import { options } from './options'
7
+ import { getIPs } from '../utils/getIps'
8
+ import { AnyObj } from '../types'
9
+ import { computed } from '../observer'
10
+ import type { ObserverValue } from '../observer/types'
11
+ import { sendData } from './sendData'
12
+
13
+ interface Device {
14
+ clientHeight: number
15
+ clientWidth: number
16
+ colorDepth: number
17
+ pixelDepth: number
18
+ screenWidth: number
19
+ screenHeight: number
20
+ deviceId: string
21
+ vendor: string
22
+ platform: string
23
+ }
24
+ interface Base extends Device {
25
+ userUuid: string
26
+ sdkUserUuid: string
27
+ ext: AnyObj
28
+ appName: string
29
+ appCode: string
30
+ pageId: string
31
+ sessionId: string
32
+ sdkVersion: string
33
+ ip: string
34
+ }
35
+
36
+ export class BaseInfo {
37
+ public base: ObserverValue<Base> | undefined
38
+ public pageId: string
39
+ private sdkUserUuid = ''
40
+ private device: Device | undefined
41
+ // 基础信息是否初始化成功
42
+ public _initSuccess = false
43
+
44
+ constructor() {
45
+ this.pageId = uuid() // 当前应用ID,在整个页面生命周期内不变,单页应用路由变化也不会改变;加载SDK时创建且只创建一次
46
+
47
+ this.initSdkUserUuid()
48
+ .then(() => {
49
+ this.initDevice()
50
+ this.initBase()
51
+ })
52
+ .finally(() => {
53
+ this._initSuccess = true
54
+ sendData.emit([])
55
+ })
56
+ }
57
+ private initDevice() {
58
+ const { screen } = getGlobal()
59
+ const { clientWidth, clientHeight } = document.documentElement
60
+ const { width, height, colorDepth, pixelDepth } = screen
61
+ let deviceId = getCookieByName(DEVICE_KEY)
62
+ if (!deviceId) {
63
+ deviceId = `t_${uuid()}`
64
+ document.cookie = `${DEVICE_KEY}=${deviceId};path=/;`
65
+ }
66
+ this.device = {
67
+ clientHeight, // 网页可见区高度
68
+ clientWidth, // 网页可见区宽度
69
+ colorDepth, // 显示屏幕调色板的比特深度
70
+ pixelDepth, // 显示屏幕的颜色分辨率
71
+ deviceId, // id
72
+ screenWidth: width, // 显示屏幕的宽度
73
+ screenHeight: height, // 显示屏幕的高度
74
+ vendor: navigator.vendor, // 浏览器名称
75
+ platform: navigator.platform // 浏览器平台的环境,不是电脑系统的x64这样的(浏览器平台的环境可能是x32)
76
+ }
77
+ }
78
+ /**
79
+ * 初始化 base 数据
80
+ */
81
+ private initBase() {
82
+ // 与一般业务上理解的sessionId做区分,此session与业务无关,单纯就是浏览器端和后端直接的联系
83
+ const sessionId = getSessionId()
84
+ let ip = ''
85
+
86
+ this.base = computed<Base>(() => ({
87
+ ...this.device!,
88
+ userUuid: options.value.userUuid,
89
+ sdkUserUuid: this.sdkUserUuid,
90
+ ext: options.value.ext,
91
+ appName: options.value.appName,
92
+ appCode: options.value.appCode,
93
+ pageId: this.pageId,
94
+ sessionId,
95
+ sdkVersion: SDK_VERSION,
96
+ ip
97
+ }))
98
+
99
+ !isTestEnv &&
100
+ getIPs().then((res: any) => {
101
+ this.base!.value.ip = res[0]
102
+ ip = res[0]
103
+ })
104
+ }
105
+ /**
106
+ * 初始化sdk中给用户的唯一标识
107
+ */
108
+ private initSdkUserUuid() {
109
+ return isTestEnv
110
+ ? Promise.resolve().then(() => {
111
+ this.sdkUserUuid = 'unit-test-id'
112
+ options.value.sdkUserUuid = 'unit-test-id'
113
+ })
114
+ : load({})
115
+ .then((fp: any) => fp.get())
116
+ .then((result: any) => {
117
+ const visitorId = result.visitorId
118
+ this.sdkUserUuid = visitorId
119
+ options.value.sdkUserUuid = visitorId
120
+ })
121
+ }
122
+ }
123
+
124
+ export let baseInfo: BaseInfo
125
+
126
+ export function initBase() {
127
+ baseInfo = new BaseInfo()
128
+ _support.baseInfo = baseInfo
129
+ }
@@ -0,0 +1,134 @@
1
+ import { sendData } from './sendData'
2
+ import { AnyFun } from '../types'
3
+ import { debounce, throttle, groupArray } from '../utils'
4
+
5
+ const SETTIMEA = 2000
6
+ const SETTIMEB = 20000
7
+ const MAXLENGTHA = 5
8
+ const GROUPARRAYKEY = ['errMessage', 'eventId', 'requestUrl']
9
+
10
+ /**
11
+ * 判断是否为批量错误
12
+ * 判断流程:
13
+ * 1. 先把所有错误都放入 a栈
14
+ * 2. 每次发生错误后防抖 2s查 a栈是否有批量错误(批量错误: errMessage、errType相同且发生个数大于等于5)
15
+ * 1. 如果为批量错误则合并这些错误并加入[时间区间参数、发生个数参数]后放入 b栈
16
+ * 2. 不为批量错误则发送这些错误
17
+ * 3. 每次推入错误到b栈后延迟 20s查 b栈并发送这些错误
18
+ * 4. 在这个过程中,如果用户关闭了网页,会统一把 a栈、b栈内的数据发送
19
+ * 5. 在这个过程中,a栈每满50个错误也会强制触发a栈和b栈的错误处理(处理结果为直接发送批量错误)
20
+ */
21
+ class BatchError {
22
+ cacheErrorA: any[]
23
+ cacheErrorB: any[]
24
+ throttleProxyAddCacheErrorA: AnyFun
25
+ throttleProxyAddCacheErrorB: AnyFun
26
+ constructor() {
27
+ this.cacheErrorA = []
28
+ this.cacheErrorB = []
29
+ this.throttleProxyAddCacheErrorA = debounce(
30
+ this.proxyAddCacheErrorA,
31
+ SETTIMEA
32
+ )
33
+ this.throttleProxyAddCacheErrorB = throttle(
34
+ this.proxyAddCacheErrorB,
35
+ SETTIMEB
36
+ )
37
+ }
38
+ proxyAddCacheErrorA() {
39
+ let len = this.cacheErrorA.length
40
+ if (!len) return
41
+ const arr = groupArray(this.cacheErrorA, ...GROUPARRAYKEY)
42
+ const arrA = arr.filter(item => item.length < MAXLENGTHA)
43
+ const arrB = arr.filter(item => item.length >= MAXLENGTHA)
44
+
45
+ if (arrA.length) {
46
+ sendData.emit(arrA.flat(Infinity))
47
+ }
48
+ if (arrB.length) {
49
+ const arrBsum: any[] = []
50
+ arrB.forEach(item => {
51
+ const sumItem = item[0]
52
+ sumItem.batchError = true
53
+ sumItem.batchErrorLength = item.length
54
+ sumItem.batchErrorLastHappenTime = item[item.length - 1].triggerTime
55
+ arrBsum.push(sumItem)
56
+ })
57
+ this.cacheErrorB.push(...arrBsum)
58
+ this.throttleProxyAddCacheErrorB()
59
+ }
60
+
61
+ while (len--) {
62
+ this.cacheErrorA.shift()
63
+ }
64
+ }
65
+ proxyAddCacheErrorB() {
66
+ let len = this.cacheErrorB.length
67
+ if (!len) return
68
+ const arr = groupArray(this.cacheErrorB, ...GROUPARRAYKEY)
69
+
70
+ while (len--) {
71
+ this.cacheErrorB.shift()
72
+ }
73
+
74
+ // 将区间报错合并
75
+ const emitList: any[] = []
76
+ arr.forEach((itemList: any[]) => {
77
+ const sumItem = itemList[0]
78
+ if (itemList.length > 1) {
79
+ sumItem.batchErrorLength = itemList.reduce(
80
+ (p, item) => (p += item.batchErrorLength),
81
+ 0
82
+ )
83
+ sumItem.batchErrorLastHappenTime =
84
+ itemList[itemList.length - 1].triggerTime
85
+ }
86
+ emitList.push(sumItem)
87
+ })
88
+ sendData.emit(emitList)
89
+ }
90
+ /**
91
+ * 获取所有的错误
92
+ * 用户突然关闭页面时调用此方法集成错误
93
+ */
94
+ sendAllCacheError() {
95
+ const errInfoList = this.cacheErrorA.concat(this.cacheErrorB)
96
+ const arr = groupArray(errInfoList, ...GROUPARRAYKEY)
97
+ const arrA = arr.filter(item => item.length < MAXLENGTHA)
98
+ const arrB = arr.filter(item => item.length >= MAXLENGTHA)
99
+
100
+ if (arrA.length) {
101
+ sendData.emit(arrA.flat(Infinity), true)
102
+ }
103
+ if (arrB.length) {
104
+ const arrBsum: any[] = []
105
+ arrB.forEach(item => {
106
+ const sumItem = item[0]
107
+ sumItem.batchError = true
108
+ sumItem.batchErrorLength = item.length
109
+ sumItem.batchErrorLastHappenTime = item[item.length - 1].triggerTime
110
+ arrBsum.push(sumItem)
111
+ })
112
+ sendData.emit(arrBsum, true)
113
+ }
114
+ }
115
+ pushCacheErrorA(errorInfo: any) {
116
+ this.cacheErrorA.push(errorInfo)
117
+ this.throttleProxyAddCacheErrorA()
118
+
119
+ // 每 50 个触发一次强制发送事件
120
+ if (this.cacheErrorA.length >= 50) {
121
+ this.proxyAddCacheErrorA()
122
+ this.proxyAddCacheErrorB()
123
+ }
124
+ }
125
+ }
126
+
127
+ export let batchError: BatchError
128
+
129
+ /**
130
+ * 初始化错误缓存
131
+ */
132
+ export function initBatchError() {
133
+ batchError = new BatchError()
134
+ }
package/src/lib/err.ts ADDED
@@ -0,0 +1,323 @@
1
+ import { EVENTTYPES, SEDNEVENTTYPES, SENDID } from '../common'
2
+ import { map, filter, getLocationHref, getTimestamp } from '../utils'
3
+ import { _global } from '../utils/global'
4
+ import { sendData } from './sendData'
5
+ import { eventBus } from './eventBus'
6
+ import { isArray, isRegExp } from '../utils/is'
7
+ import { options } from './options'
8
+ import { zip, getEventList } from './recordscreen'
9
+ import { debug } from '../utils/debug'
10
+ import { initBatchError, batchError } from './err-batch'
11
+ import { RecordEventScope } from '../types'
12
+
13
+ /**
14
+ * 序列化console.error的参数
15
+ * 处理各种类型的参数,包括对象、函数、Error等
16
+ */
17
+ function serializeConsoleArgs(args: any[]): string {
18
+ const serializedArgs = args.map((arg: any) => {
19
+ if (arg === null) return 'null'
20
+ if (arg === undefined) return 'undefined'
21
+ if (typeof arg === 'string') return arg
22
+ if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg)
23
+ if (typeof arg === 'function')
24
+ return `[Function: ${arg.name || 'anonymous'}]`
25
+ if (arg instanceof Error) {
26
+ return `${arg.name}: ${arg.message}${arg.stack ? '\n' + arg.stack : ''}`
27
+ }
28
+ if (typeof arg === 'object') {
29
+ try {
30
+ // 尝试JSON序列化对象,保持缩进格式
31
+ return JSON.stringify(arg, null, 2)
32
+ } catch (error) {
33
+ // 如果JSON序列化失败(如循环引用),使用toString
34
+ return arg.toString()
35
+ }
36
+ }
37
+ return String(arg)
38
+ })
39
+ return serializedArgs.join(' | ')
40
+ }
41
+
42
+ interface ErrorStack {
43
+ errMessage: string
44
+ errStack: string
45
+ }
46
+
47
+ type InstabilityNature = {
48
+ lineNumber: string
49
+ fileName: string
50
+ columnNumber: string
51
+ }
52
+
53
+ /**
54
+ * 格式化错误对象信息
55
+ * @param err Error 错误对象
56
+ */
57
+ function parseStack(err: Error): ErrorStack {
58
+ const { stack = '', message = '' } = err
59
+ const result = { eventId: SENDID.CODE, errMessage: message, errStack: stack }
60
+
61
+ if (stack) {
62
+ const rChromeCallStack = /^\s*at\s*([^(]+)\s*\((.+?):(\d+):(\d+)\)$/
63
+ const rMozlliaCallStack = /^\s*([^@]*)@(.+?):(\d+):(\d+)$/
64
+ // chrome中包含了message信息,将其去除,并去除后面的换行符
65
+ const callStackStr = stack.replace(
66
+ new RegExp(`^[\\w\\s:]*${message}\n`),
67
+ ''
68
+ )
69
+ const callStackFrameList = map(
70
+ filter(callStackStr.split('\n'), (item: string) => item),
71
+ (str: string) => {
72
+ const chromeErrResult = str.match(rChromeCallStack)
73
+ if (chromeErrResult) {
74
+ return {
75
+ triggerPageUrl: chromeErrResult[2],
76
+ line: chromeErrResult[3], // 错误发生位置的行数
77
+ col: chromeErrResult[4] // 错误发生位置的列数
78
+ }
79
+ }
80
+
81
+ const mozlliaErrResult = str.match(rMozlliaCallStack)
82
+ if (mozlliaErrResult) {
83
+ return {
84
+ triggerPageUrl: mozlliaErrResult[2],
85
+ line: mozlliaErrResult[3],
86
+ col: mozlliaErrResult[4]
87
+ }
88
+ }
89
+ return {}
90
+ }
91
+ )
92
+ const item = callStackFrameList[0] || {}
93
+ return { ...result, ...item }
94
+ }
95
+ return result
96
+ }
97
+
98
+ /**
99
+ * 分析错误信息
100
+ * @param e 错误源信息
101
+ * @returns 相对标准格式的错误信息
102
+ */
103
+ function parseError(e: any) {
104
+ if (e instanceof Error) {
105
+ // fileName: 引发此错误的文件的路径 (此属性为非标准,所以下面得区分)
106
+ const { message, stack, lineNumber, fileName, columnNumber } = e as Error &
107
+ InstabilityNature
108
+ if (fileName) {
109
+ return {
110
+ errMessage: message,
111
+ errStack: stack,
112
+ eventId: SENDID.CODE,
113
+ line: lineNumber, // 不稳定属性 - 在某些浏览器可能是undefined,被废弃了
114
+ col: columnNumber, // 不稳定属性 - 非标准,有些浏览器可能不支持
115
+ triggerPageUrl: fileName // 不稳定属性 - 非标准,有些浏览器可能不支持
116
+ }
117
+ }
118
+ return parseStack(e)
119
+ }
120
+ if (e.message) return parseStack(e)
121
+
122
+ // reject 错误
123
+ if (typeof e === 'string') return { eventId: SENDID.REJECT, errMessage: e }
124
+
125
+ // console.error 暴露的错误
126
+ if (isArray(e)) {
127
+ return { eventId: SENDID.CONSOLEERROR, errMessage: serializeConsoleArgs(e) }
128
+ }
129
+
130
+ return {}
131
+ }
132
+
133
+ /**
134
+ * 判断是否为 promise-reject 错误类型
135
+ */
136
+ function isPromiseRejectedResult(
137
+ event: ErrorEvent | PromiseRejectedResult
138
+ ): event is PromiseRejectedResult {
139
+ return (event as PromiseRejectedResult).reason !== undefined
140
+ }
141
+
142
+ function parseErrorEvent(event: ErrorEvent | PromiseRejectedResult) {
143
+ // promise reject 错误
144
+ if (isPromiseRejectedResult(event)) {
145
+ return { eventId: SENDID.CODE, ...parseError(event.reason) }
146
+ }
147
+
148
+ // html元素上发生的异常错误
149
+ const { target } = event
150
+ if (target instanceof HTMLElement) {
151
+ // 为1代表节点是元素节点
152
+ if (target.nodeType === 1) {
153
+ const result = {
154
+ initiatorType: target.nodeName.toLowerCase(),
155
+ eventId: SENDID.RESOURCE,
156
+ requestUrl: ''
157
+ }
158
+ switch (target.nodeName.toLowerCase()) {
159
+ case 'link':
160
+ result.requestUrl = (target as HTMLLinkElement).href
161
+ break
162
+ default:
163
+ result.requestUrl =
164
+ (target as HTMLImageElement).currentSrc ||
165
+ (target as HTMLScriptElement).src
166
+ }
167
+ return result
168
+ }
169
+ }
170
+
171
+ // 代码异常
172
+ if (event.error) {
173
+ // chrome中的error对象没有fileName等属性,将event中的补充给error对象
174
+ const e = event.error
175
+ e.fileName = e.filename || event.filename
176
+ e.columnNumber = e.colno || event.colno
177
+ e.lineNumber = e.lineno || event.lineno
178
+ return { eventId: SENDID.CODE, ...parseError(e) }
179
+ }
180
+
181
+ // 兜底
182
+ // ie9版本,从全局的event对象中获取错误信息
183
+ return {
184
+ eventId: SENDID.CODE,
185
+ line: (_global as any).event.errorLine,
186
+ col: (_global as any).event.errorCharacter,
187
+ errMessage: (_global as any).event.errorMessage,
188
+ triggerPageUrl: (_global as any).event.errorUrl
189
+ }
190
+ }
191
+
192
+ /**
193
+ * 判断错误源信息是否为需要拦截的
194
+ * @param error 错误源信息
195
+ */
196
+ function isIgnoreErrors(error: any): boolean {
197
+ if (!options.value.ignoreErrors.length) return false
198
+ let errMessage = error.errMessage || error.message
199
+ if (!errMessage) return false
200
+ errMessage = String(errMessage)
201
+
202
+ return options.value.ignoreErrors.some(item => {
203
+ if (isRegExp(item)) {
204
+ if ((item as RegExp).test(errMessage)) {
205
+ debug(`ignoreErrors拦截成功 - 截条件:${item} 错误信息:${errMessage}`)
206
+ return true
207
+ } else {
208
+ return false
209
+ }
210
+ } else {
211
+ if (errMessage === item) {
212
+ debug(`ignoreErrors拦截成功 - 截条件:${item} 错误信息:${errMessage}`)
213
+ return true
214
+ } else {
215
+ return false
216
+ }
217
+ }
218
+ })
219
+ }
220
+
221
+ /**
222
+ * 获取错误录屏数据
223
+ */
224
+ function getRecordEvent(): RecordEventScope[] {
225
+ const _recordscreenList: RecordEventScope[] = JSON.parse(
226
+ JSON.stringify(getEventList())
227
+ )
228
+ return _recordscreenList
229
+ .slice(-2)
230
+ .map(item => item.eventList)
231
+ .flat()
232
+ }
233
+
234
+ /**
235
+ * 发送错误事件信息
236
+ * @param errorInfo 信息源
237
+ */
238
+ function emit(errorInfo: any, flush = false): void {
239
+ const info = {
240
+ ...errorInfo,
241
+ eventType: SEDNEVENTTYPES.ERROR,
242
+ recordscreen: options.value.recordScreen ? zip(getRecordEvent()) : null,
243
+ triggerPageUrl: getLocationHref(),
244
+ triggerTime: getTimestamp()
245
+ }
246
+
247
+ options.value.scopeError
248
+ ? batchError.pushCacheErrorA(info)
249
+ : sendData.emit(info, flush)
250
+ }
251
+
252
+ /**
253
+ * 初始化错误监听
254
+ */
255
+ function initError(): void {
256
+ if (!options.value.error.core) return
257
+
258
+ if (options.value.scopeError) {
259
+ initBatchError()
260
+ // 如果开启了检测批量错误 则要挂载卸载事件以防缓存池内的错误丢失
261
+ eventBus.addEvent({
262
+ type: EVENTTYPES.BEFOREUNLOAD,
263
+ callback: () => {
264
+ batchError.sendAllCacheError()
265
+ }
266
+ })
267
+ }
268
+
269
+ // 捕获阶段可以获取资源加载错误,script.onError link.onError img.onError,无法知道具体状态
270
+ eventBus.addEvent({
271
+ type: EVENTTYPES.ERROR,
272
+ callback: (e: ErrorEvent) => {
273
+ const errorInfo = parseErrorEvent(e)
274
+ if (isIgnoreErrors(errorInfo)) return
275
+ emit(errorInfo)
276
+ }
277
+ })
278
+
279
+ // promise调用链未捕获异常
280
+ // 只捕获未处理的 reject的错误,如果对reject进行了回调处理这边不进行捕获
281
+ eventBus.addEvent({
282
+ type: EVENTTYPES.UNHANDLEDREJECTION,
283
+ callback: (e: PromiseRejectedResult) => {
284
+ const errorInfo = parseErrorEvent(e)
285
+ if (isIgnoreErrors(errorInfo)) return
286
+ emit(errorInfo)
287
+ }
288
+ })
289
+
290
+ // 劫持console.error
291
+ eventBus.addEvent({
292
+ type: EVENTTYPES.CONSOLEERROR,
293
+ callback: e => {
294
+ const errorInfo = parseError(e)
295
+ if (isIgnoreErrors(errorInfo)) return
296
+ emit({ eventId: SENDID.CODE, ...errorInfo })
297
+ }
298
+ })
299
+ }
300
+
301
+ /**
302
+ * 主动触发错误上报
303
+ * @param eventId 事件ID
304
+ * @param message 错误信息
305
+ * @param options 自定义配置信息
306
+ */
307
+ function handleSendError(options = {}, flush = false): void {
308
+ emit(options, flush)
309
+ }
310
+
311
+ /**
312
+ * 卸载所有错误监听
313
+ */
314
+ export function destroyError() {
315
+ // 清除错误相关的事件类型
316
+ eventBus.removeEvents([
317
+ EVENTTYPES.ERROR,
318
+ EVENTTYPES.UNHANDLEDREJECTION,
319
+ EVENTTYPES.CONSOLEERROR
320
+ ])
321
+ }
322
+
323
+ export { initError, handleSendError, parseError }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * 这部分功能移植到 pv 中,并且默认开启
3
+ */
4
+ import { EVENTTYPES, SEDNEVENTTYPES, WEBPAGELOAD } from '../common'
5
+ import { uuid, isValidKey, getTimestamp, getLocationHref } from '../utils'
6
+ import { eventBus } from './eventBus'
7
+ import { sendData } from './sendData'
8
+ // import { options } from './options'
9
+
10
+ class DwellRequestTemplate {
11
+ eventId = '' // 事件ID
12
+ eventType = '' // 事件类型
13
+ triggerPageUrl = '' // 当前页面URL
14
+ referer = '' // 上级页面URL
15
+ entryTime = -1 // 加载完成时间
16
+ triggerTime = -1 // 卸载时间
17
+ millisecond = -1 // 页面停留时间
18
+ operateAction = '' // 页面加载来源
19
+ constructor(config = {}) {
20
+ Object.keys(config).forEach(key => {
21
+ if (isValidKey(key, config)) {
22
+ this[key] = config[key] || null
23
+ }
24
+ })
25
+ }
26
+ }
27
+
28
+ /**
29
+ * 加载 & 卸载事件
30
+ */
31
+ function dwellCollector() {
32
+ const _config = new DwellRequestTemplate({ eventType: SEDNEVENTTYPES.DWELL })
33
+
34
+ // 加载完成事件
35
+ eventBus.addEvent({
36
+ type: EVENTTYPES.LOAD,
37
+ callback: () => {
38
+ _config.entryTime = getTimestamp()
39
+ }
40
+ })
41
+
42
+ // 卸载事件
43
+ eventBus.addEvent({
44
+ type: EVENTTYPES.BEFOREUNLOAD,
45
+ callback: () => {
46
+ _config.eventId = uuid()
47
+ _config.triggerPageUrl = getLocationHref() // 当前页面 url
48
+ _config.referer = document.referrer // 上级页面 url(从哪个页面跳过来的就是上级页面)
49
+ _config.triggerTime = getTimestamp() // 卸载时间
50
+ _config.millisecond = getTimestamp() - _config.entryTime // 停留多久
51
+ const { type } = performance.navigation // 表示加载来源, type为 0,1,2,255
52
+ _config.operateAction = WEBPAGELOAD[type] || ''
53
+ sendData.emit(_config, true)
54
+ }
55
+ })
56
+ }
57
+
58
+ function initEventDwell() {
59
+ // options.value.event.unload && dwellCollector() // 放弃此方法
60
+ dwellCollector()
61
+ }
62
+
63
+ export { initEventDwell }