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,325 @@
1
+ import type { InternalOptions, AnyFun, InitOptions, VoidFun } from '../types'
2
+ import { typeofAny, deepAssign } from '../utils'
3
+ import { isEmpty } from '../utils/is'
4
+ import { _support } from '../utils/global'
5
+ import { logError } from '../utils/debug'
6
+ import { ref } from '../observer'
7
+ import type { ObserverValue } from '../observer/types'
8
+
9
+ /**
10
+ * 管理全局的参数
11
+ */
12
+ export class Options implements InternalOptions {
13
+ dsn = '' // 上报地址
14
+ appName = '' // 应用名称
15
+ appCode = '' // 应用code
16
+ appVersion = '' // 应用版本
17
+ userUuid = '' // 用户id(外部填充进来的id)
18
+ sdkUserUuid = '' // 用户id(sdk内部生成的id)
19
+ debug = false // 是否开启调试模式(控制台会输出sdk动作)
20
+ pv = {
21
+ core: false // 页面跳转-是否自动发送页面跳转相关数据
22
+ }
23
+ performance = {
24
+ core: false, // 性能数据-是否采集静态资源、接口的相关数据
25
+ firstResource: false, // 性能数据-是否采集首次进入页面的数据(ps: tcp连接耗时,HTML加载完成时间,首次可交互时间)
26
+ server: false // 接口请求-是否采集接口请求(成功的才会采集)
27
+ }
28
+ error = {
29
+ core: false, // 是否采集异常数据(ps: 资源引入错误,promise错误,控制台输出错误)
30
+ server: false // 接口请求-是否采集报错接口数据
31
+ }
32
+ event = {
33
+ core: false // 页面点击-是否采集点击事件
34
+ }
35
+ recordScreen = true // 是否启动录屏
36
+ timeout = 5000 // 日志上报超时时间(毫秒)
37
+ maxQueueLength = 200 // 上报接口异常,日志队列最大缓存数
38
+ checkRecoverInterval = 1 // 多长时间检测一次上报接口是否恢复(分钟)
39
+
40
+ ext = {} // 自定义全局附加参数(放在baseInfo中)
41
+ tracesSampleRate = 1 // 抽样发送
42
+
43
+ cacheMaxLength = 5 // 上报数据最大缓存数
44
+ cacheWatingTime = 5000 // 上报数据最大等待时间
45
+ ignoreErrors = [] // 错误类型事件过滤
46
+ ignoreRequest: Array<string | RegExp> = [] // 请求类型事件过滤
47
+ scopeError = false // 当某个时间段报错时,会将此类错误转为特殊错误类型,会新增错误持续时间范围
48
+ localization = false // 是否本地化:sdk不再主动发送事件,事件都存储在本地,由用户手动调用方法发送
49
+ sendTypeByXmlBody = false // 是否强制指定发送形式为xml,body请求方式
50
+
51
+ // whiteScreen = false // 开启白屏检测
52
+
53
+ // 添加到行为列表前的 hook (在这里面可以给出错误类型,然后就能达到用户想拿到是何种事件类型的触发)
54
+ beforePushEventList: AnyFun[] = []
55
+
56
+ // 数据上报前的 hook
57
+ beforeSendData: AnyFun[] = []
58
+
59
+ // 数据上报后的 hook
60
+ afterSendData: VoidFun[] = []
61
+
62
+ // 本地化存储溢出后的回调
63
+ localizationOverFlow: VoidFun = () => {
64
+ // do something
65
+ }
66
+
67
+ constructor(initOptions: InitOptions) {
68
+ const _options = this.transitionOptions(initOptions)
69
+ _options.ignoreRequest.push(new RegExp(_options.dsn))
70
+ deepAssign<Options>(this, _options)
71
+ }
72
+
73
+ /**
74
+ * 对入参配置项进行转换
75
+ */
76
+ private transitionOptions(options: InitOptions): Options {
77
+ const _options = deepAssign<Options>({}, this, options)
78
+ const { beforePushEventList, beforeSendData, afterSendData } = options
79
+ const { pv, performance, error, event } = _options
80
+
81
+ if (typeof pv === 'boolean') {
82
+ _options.pv = {
83
+ core: pv
84
+ }
85
+ }
86
+ if (typeof performance === 'boolean') {
87
+ _options.performance = {
88
+ core: performance,
89
+ firstResource: performance,
90
+ server: performance
91
+ }
92
+ }
93
+ if (typeof error === 'boolean') {
94
+ _options.error = {
95
+ core: error,
96
+ server: error
97
+ }
98
+ }
99
+ if (typeof event === 'boolean') {
100
+ _options.event = {
101
+ core: event
102
+ }
103
+ }
104
+
105
+ if (beforePushEventList) {
106
+ _options.beforePushEventList = [beforePushEventList]
107
+ }
108
+ if (beforeSendData) {
109
+ _options.beforeSendData = [beforeSendData]
110
+ }
111
+ if (afterSendData) {
112
+ _options.afterSendData = [afterSendData]
113
+ }
114
+
115
+ if (options.timeout !== undefined) {
116
+ _options.timeout = options.timeout
117
+ }
118
+
119
+ if (options.maxQueueLength !== undefined) {
120
+ _options.maxQueueLength = options.maxQueueLength
121
+ }
122
+
123
+ if (options.checkRecoverInterval !== undefined) {
124
+ _options.checkRecoverInterval = options.checkRecoverInterval
125
+ }
126
+
127
+ return _options
128
+ }
129
+ }
130
+
131
+ function _validateInitOption(options: InitOptions) {
132
+ const {
133
+ dsn,
134
+ appName,
135
+ appCode,
136
+ appVersion,
137
+ userUuid,
138
+ debug,
139
+ recordScreen,
140
+ pv,
141
+ performance,
142
+ error,
143
+ event,
144
+ ext,
145
+ tracesSampleRate,
146
+ cacheMaxLength,
147
+ cacheWatingTime,
148
+ ignoreErrors,
149
+ ignoreRequest,
150
+ scopeError,
151
+ localization,
152
+ sendTypeByXmlBody,
153
+ // whiteScreen,
154
+ beforePushEventList,
155
+ beforeSendData,
156
+ timeout,
157
+ maxQueueLength,
158
+ checkRecoverInterval
159
+ } = options
160
+
161
+ const validateFunList = []
162
+
163
+ if (pv && typeof pv === 'object') {
164
+ validateFunList.push(validateOption(pv.core, 'pv.core', 'boolean'))
165
+ } else {
166
+ validateFunList.push(validateOption(pv, 'pv', 'boolean'))
167
+ }
168
+
169
+ if (performance && typeof performance === 'object') {
170
+ validateFunList.push(
171
+ validateOption(performance.core, 'performance.core', 'boolean'),
172
+ validateOption(
173
+ performance.firstResource,
174
+ 'performance.firstResource',
175
+ 'boolean'
176
+ ),
177
+ validateOption(performance.server, 'performance.server', 'boolean')
178
+ )
179
+ } else {
180
+ validateFunList.push(validateOption(performance, 'performance', 'boolean'))
181
+ }
182
+
183
+ if (error && typeof error === 'object') {
184
+ validateFunList.push(
185
+ validateOption(error.core, 'error.core', 'boolean'),
186
+ validateOption(error.server, 'error.server', 'boolean')
187
+ )
188
+ } else {
189
+ validateFunList.push(validateOption(error, 'error', 'boolean'))
190
+ }
191
+
192
+ if (event && typeof event === 'object') {
193
+ validateFunList.push(validateOption(event.core, 'event.core', 'boolean'))
194
+ } else {
195
+ validateFunList.push(validateOption(event, 'event', 'boolean'))
196
+ }
197
+
198
+ const validateList = [
199
+ validateOption(dsn, 'dsn', 'string'),
200
+ validateOption(appName, 'appName', 'string'),
201
+ validateOption(appCode, 'appCode', 'string'),
202
+ validateOption(appVersion, 'appVersion', 'string'),
203
+ validateOption(userUuid, 'userUuid', 'string'),
204
+ validateOption(debug, 'debug', 'boolean'),
205
+ validateOption(recordScreen, 'recordScreen', 'boolean'),
206
+
207
+ validateOption(ext, 'ext', 'object'),
208
+ validateOption(tracesSampleRate, 'tracesSampleRate', 'number'),
209
+
210
+ validateOption(cacheMaxLength, 'cacheMaxLength', 'number'),
211
+ validateOption(cacheWatingTime, 'cacheWatingTime', 'number'),
212
+
213
+ validateOption(ignoreErrors, 'ignoreErrors', 'array'),
214
+ validateOptionArray(ignoreErrors, 'ignoreErrors', ['string', 'regexp']),
215
+
216
+ validateOption(ignoreRequest, 'ignoreRequest', 'array'),
217
+ validateOptionArray(ignoreRequest, 'ignoreRequest', ['string', 'regexp']),
218
+
219
+ validateOption(scopeError, 'scopeError', 'boolean'),
220
+ validateOption(localization, 'localization', 'boolean'),
221
+ validateOption(sendTypeByXmlBody, 'sendTypeByXmlBody', 'boolean'),
222
+ // validateOption(whiteScreen, 'whiteScreen', 'boolean'),
223
+ validateOption(beforePushEventList, 'beforePushEventList', 'function'),
224
+ validateOption(beforeSendData, 'beforeSendData', 'function'),
225
+ validateOption(timeout, 'timeout', 'number'),
226
+ validateOption(maxQueueLength, 'maxQueueLength', 'number'),
227
+ validateOption(checkRecoverInterval, 'checkRecoverInterval', 'number')
228
+ ]
229
+
230
+ return validateList.every(res => !!res)
231
+ }
232
+
233
+ /**
234
+ * 验证必填项
235
+ * @param options 入参对象
236
+ */
237
+ function _validateMustFill(options: InitOptions) {
238
+ const validateList = [
239
+ validateOptionMustFill(options.appName, 'appName'),
240
+ validateOptionMustFill(options.dsn, 'dsn')
241
+ ]
242
+
243
+ return validateList.every(res => !!res)
244
+ }
245
+
246
+ /**
247
+ * 验证必填项
248
+ * @param target 属性值
249
+ * @param targetName 属性名
250
+ * @returns 是否通过验证
251
+ */
252
+ function validateOptionMustFill(target: any, targetName: string): boolean {
253
+ if (isEmpty(target)) {
254
+ logError(`【${targetName}】参数必填`)
255
+ return false
256
+ }
257
+ return true
258
+ }
259
+
260
+ /**
261
+ * 验证选项的类型是否符合要求
262
+ * @param target 源对象
263
+ * @param targetName 对象名
264
+ * @param expectType 期望类型
265
+ * @returns 是否通过验证
266
+ */
267
+ function validateOption(
268
+ target: any,
269
+ targetName: string,
270
+ expectType: string
271
+ ): boolean | void {
272
+ if (!target || typeofAny(target) === expectType) return true
273
+ logError(
274
+ `TypeError:【${targetName}】期望传入${expectType}类型,目前是${typeofAny(
275
+ target
276
+ )}类型`
277
+ )
278
+ return false
279
+ }
280
+
281
+ /**
282
+ * 验证选项的类型 - 针对数组内容类型的验证
283
+ * @param target 源对象
284
+ * @param targetName 对象名
285
+ * @param expectTypes 期望类型
286
+ * @returns 是否通过验证
287
+ */
288
+ function validateOptionArray(
289
+ target: any[] | undefined,
290
+ targetName: string,
291
+ expectTypes: string[]
292
+ ): boolean | void {
293
+ if (!target) return true
294
+ let pass = true
295
+
296
+ target.forEach(item => {
297
+ if (!expectTypes.includes(typeofAny(item))) {
298
+ logError(
299
+ `TypeError:【${targetName}】数组内的值期望传入${expectTypes.join(
300
+ '|'
301
+ )}类型,目前值${item}是${typeofAny(item)}类型`
302
+ )
303
+ pass = false
304
+ }
305
+ })
306
+
307
+ return pass
308
+ }
309
+
310
+ export let options: ObserverValue<InternalOptions>
311
+
312
+ /**
313
+ * 初始化参数
314
+ * @param initOptions 原始参数
315
+ * @returns 是否初始化成功
316
+ */
317
+ export function initOptions(initOptions: InitOptions): boolean {
318
+ // 必传校验 && 入参类型校验
319
+ if (!_validateMustFill(initOptions) || !_validateInitOption(initOptions))
320
+ return false
321
+
322
+ options = ref(new Options(initOptions))
323
+ _support.options = options
324
+ return true
325
+ }
@@ -0,0 +1,302 @@
1
+ import { sendData } from './sendData'
2
+ import { eventBus } from './eventBus'
3
+ import { EVENTTYPES, SEDNEVENTTYPES, SENDID } from '../common'
4
+ import { AnyObj } from '../types'
5
+ import {
6
+ on,
7
+ getLocationHref,
8
+ normalizeObj,
9
+ isValidKey,
10
+ sendReaconImageList,
11
+ getTimestamp
12
+ } from '../utils'
13
+ import { _global, _support } from '../utils/global'
14
+ import { options } from './options'
15
+
16
+ // 兼容判断
17
+ const supported = {
18
+ performance: !!_global.performance,
19
+ getEntriesByType: !!(
20
+ _global.performance && _global.performance.getEntriesByType
21
+ ),
22
+ PerformanceObserver: 'PerformanceObserver' in _global,
23
+ MutationObserver: 'MutationObserver' in _global,
24
+ PerformanceNavigationTiming: 'PerformanceNavigationTiming' in _global
25
+ }
26
+
27
+ // 资源属性
28
+ const performanceEntryAttrs = {
29
+ initiatorType: '',
30
+ transferSize: 0,
31
+ encodedBodySize: 0,
32
+ decodedBodySize: 0,
33
+ duration: 0,
34
+ redirectStart: 0,
35
+ redirectEnd: 0,
36
+ startTime: 0,
37
+ fetchStart: 0,
38
+ domainLookupStart: 0,
39
+ domainLookupEnd: 0,
40
+ connectStart: 0,
41
+ connectEnd: 0,
42
+ requestStart: 0,
43
+ responseStart: 0,
44
+ responseEnd: 0,
45
+ workerStart: 0
46
+ }
47
+
48
+ /**
49
+ * 发送页面追踪资源加载性能数据
50
+ * (支持getEntriesByType的情况下才追踪)
51
+ */
52
+ function traceResourcePerformance(performance: PerformanceObserverEntryList) {
53
+ // 排除xmlhttprequest类型,服务器有响应便会记录,包括404的请求,转由http-request模块负责记录请求数据,区分请求状态
54
+ // 同时也会排除一些其他类型,比如在引入一个script后会触发一次性能监控,它的类型是beacon,这一次的要排除
55
+ const observerTypeList = ['img', 'script', 'link', 'audio', 'video', 'css']
56
+
57
+ const entries = performance.getEntriesByType(
58
+ 'resource'
59
+ ) as PerformanceResourceTiming[]
60
+ const records: any[] = []
61
+
62
+ entries.forEach(entry => {
63
+ // initiatorType含义:通过某种方式请求的资源,例如script,link..
64
+ const { initiatorType = '' } = entry
65
+
66
+ // 只记录observerTypeList中列出的资源类型请求,不在列表中则跳过
67
+ if (observerTypeList.indexOf(initiatorType.toLowerCase()) < 0) return
68
+
69
+ // sdk内部 img 发送请求的错误不会记录
70
+ if (sendReaconImageList.length) {
71
+ const index = sendReaconImageList.findIndex(
72
+ item => item.src === entry.name
73
+ )
74
+
75
+ if (index !== -1) {
76
+ sendReaconImageList.splice(index, 1)
77
+ return
78
+ }
79
+ }
80
+
81
+ const value: AnyObj = {}
82
+ Object.keys(performanceEntryAttrs).forEach(attr => {
83
+ if (isValidKey(attr, entry)) {
84
+ value[attr] = entry[attr]
85
+ }
86
+ })
87
+
88
+ records.push(
89
+ normalizeObj({
90
+ ...value,
91
+ eventType: SEDNEVENTTYPES.PERFORMANCE,
92
+ eventId: SENDID.RESOURCE,
93
+ requestUrl: entry.name,
94
+ triggerTime: getTimestamp(),
95
+ triggerPageUrl: getLocationHref()
96
+ })
97
+ )
98
+ })
99
+
100
+ if (records.length) sendData.emit(records)
101
+ return records
102
+ }
103
+
104
+ let mutationObserver: MutationObserver | null = null
105
+ let performanceObserver: PerformanceObserver | null = null
106
+
107
+ /**
108
+ * 监听 - 异步插入的script、link、img, DOM更新操作记录
109
+ */
110
+ function observeSourceInsert() {
111
+ const tags = ['img', 'script', 'link']
112
+ // 检测异步插入的script、link、img,会有一些延迟,一些连接建立、包体大小的数据会丢失,精度下降
113
+ // MutationObserver DOM3 Events规范,是个异步监听,只有在全部DOM操作完成之后才会调用callback
114
+ mutationObserver = new MutationObserver(mutationsList => {
115
+ for (let i = 0; i < mutationsList.length; i += 1) {
116
+ const startTime = getTimestamp()
117
+ const { addedNodes = [] } = mutationsList[i]
118
+ addedNodes.forEach((node: Node & { src?: string; href?: string }) => {
119
+ const { nodeName } = node
120
+ if (tags.indexOf(nodeName.toLowerCase()) !== -1) {
121
+ on(node as Document, EVENTTYPES.LOAD, function () {
122
+ sendData.emit(
123
+ normalizeObj({
124
+ eventType: SEDNEVENTTYPES.PERFORMANCE,
125
+ eventId: SENDID.RESOURCE,
126
+ requestUrl: node.src || node.href,
127
+ duration: getTimestamp() - startTime,
128
+ triggerTime: getTimestamp(),
129
+ triggerPageUrl: getLocationHref()
130
+ })
131
+ )
132
+ })
133
+ on(node as Document, EVENTTYPES.ERROR, function () {
134
+ sendData.emit(
135
+ normalizeObj({
136
+ eventType: SEDNEVENTTYPES.PERFORMANCE,
137
+ eventId: SENDID.RESOURCE,
138
+ requestUrl: node.src || node.href,
139
+ responseStatus: 'error',
140
+ duration: getTimestamp() - startTime,
141
+ triggerTime: getTimestamp(),
142
+ triggerPageUrl: getLocationHref()
143
+ })
144
+ )
145
+ })
146
+ }
147
+ })
148
+ }
149
+ })
150
+ mutationObserver.observe(_global.document, {
151
+ subtree: true, // 目标以及目标的后代改变都会观察
152
+ childList: true // 表示观察目标子节点的变化,比如添加或者删除目标子节点,不包括修改子节点以及子节点后代的变化
153
+ // attributes: true, // 观察属性变动
154
+ // attributeFilter: ['src', 'href'] // 要观察的属性
155
+ })
156
+ // observer.disconnect()
157
+ }
158
+
159
+ /**
160
+ * 发送页面性能数据
161
+ */
162
+ function observeNavigationTiming() {
163
+ const times: AnyObj = {}
164
+ const { performance } = _global
165
+ let t: any = performance.timing
166
+
167
+ times.fmp = 0 // 首屏时间 (渲染节点增量最大的时间点)
168
+ if (supported.getEntriesByType) {
169
+ const paintEntries = performance.getEntriesByType('paint')
170
+ if (paintEntries.length) {
171
+ times.fmp = paintEntries[paintEntries.length - 1].startTime
172
+ }
173
+
174
+ // 优先使用 navigation v2 https://www.w3.org/TR/navigation-timing-2/
175
+ if (supported.PerformanceNavigationTiming) {
176
+ const nt2Timing = performance.getEntriesByType('navigation')[0]
177
+ if (nt2Timing) t = nt2Timing
178
+ }
179
+ }
180
+
181
+ // 从开始发起这个页面的访问开始算起,减去重定向跳转的时间,在performanceV2版本下才进行计算
182
+ // v1版本的fetchStart是时间戳而不是相对于访问起始点的相对时间
183
+ if (times.fmp && supported.PerformanceNavigationTiming) {
184
+ times.fmp -= t.fetchStart
185
+ }
186
+
187
+ // 白屏时间 (从请求开始到浏览器开始解析第一批HTML文档字节的时间差)
188
+ // times.fpt = t.responseEnd - t.fetchStart;
189
+
190
+ times.tti = t.domInteractive - t.fetchStart // 首次可交互时间
191
+
192
+ times.ready = t.domContentLoadedEventEnd - t.fetchStart // HTML加载完成时间
193
+
194
+ times.loadon = t.loadEventStart - t.fetchStart // 页面完全加载时间
195
+
196
+ times.firstbyte = t.responseStart - t.domainLookupStart // 首包时间
197
+
198
+ times.dns = t.domainLookupEnd - t.domainLookupStart // dns查询耗时
199
+
200
+ times.appcache = t.domainLookupStart - t.fetchStart // dns缓存时间
201
+
202
+ times.tcp = t.connectEnd - t.connectStart // tcp连接耗时
203
+
204
+ times.ttfb = t.responseStart - t.requestStart // 请求响应耗时
205
+
206
+ times.trans = t.responseEnd - t.responseStart // 内容传输耗时
207
+
208
+ times.dom = t.domInteractive - t.responseEnd // dom解析耗时
209
+
210
+ times.res = t.loadEventStart - t.domContentLoadedEventEnd // 同步资源加载耗时
211
+
212
+ times.ssllink = t.connectEnd - t.secureConnectionStart // SSL安全连接耗时
213
+
214
+ times.redirect = t.redirectEnd - t.redirectStart // 重定向时间
215
+
216
+ times.unloadTime = t.unloadEventEnd - t.unloadEventStart // 上一个页面的卸载耗时
217
+
218
+ const resultInfo = { ...times, triggerPageUrl: getLocationHref() }
219
+
220
+ _support.firstScreen = { ...resultInfo }
221
+
222
+ sendData.emit(
223
+ normalizeObj({
224
+ ...resultInfo,
225
+ eventType: SEDNEVENTTYPES.PERFORMANCE,
226
+ eventId: SENDID.PAGE
227
+ })
228
+ )
229
+ }
230
+
231
+ /**
232
+ * 页面资源加载性能数据
233
+ */
234
+ function observeResource() {
235
+ if (supported.performance && options.value.performance.firstResource) {
236
+ observeNavigationTiming()
237
+ }
238
+
239
+ if (supported.performance && options.value.performance.core) {
240
+ traceResourcePerformance(_global.performance)
241
+
242
+ if (supported.PerformanceObserver) {
243
+ // 监听异步资源加载性能数据 chrome≥52
244
+ performanceObserver = new PerformanceObserver(traceResourcePerformance)
245
+ performanceObserver.observe({ entryTypes: ['resource'] })
246
+ } else if (supported.MutationObserver) {
247
+ // 监听资源、DOM更新操作记录 chrome≥26 ie≥11
248
+ observeSourceInsert()
249
+ }
250
+ }
251
+ }
252
+
253
+ function initPerformance() {
254
+ if (
255
+ !options.value.performance.firstResource &&
256
+ !options.value.performance.core
257
+ )
258
+ return
259
+
260
+ // 初始化方法可能在onload事件之后才执行,此时不会触发load事件了 (例如delayInit)
261
+ // 检查document.readyState属性来判断onload事件是否会被触发
262
+ if (document.readyState === 'complete') {
263
+ observeResource()
264
+ } else {
265
+ eventBus.addEvent({
266
+ type: EVENTTYPES.LOAD,
267
+ callback: () => {
268
+ observeResource()
269
+ }
270
+ })
271
+ }
272
+ }
273
+
274
+ /**
275
+ * 主动触发性能事件上报
276
+ * @param options 自定义配置信息
277
+ */
278
+ function handleSendPerformance(options = {}, flush = false) {
279
+ const record = {
280
+ ...options,
281
+ triggerTime: getTimestamp(),
282
+ triggerPageUrl: getLocationHref(),
283
+ eventType: SEDNEVENTTYPES.PERFORMANCE
284
+ }
285
+ sendData.emit(normalizeObj(record), flush)
286
+ }
287
+
288
+ /**
289
+ * 销毁性能监听
290
+ */
291
+ export function destroyPerformance() {
292
+ if (mutationObserver) {
293
+ mutationObserver.disconnect()
294
+ mutationObserver = null
295
+ }
296
+ if (performanceObserver) {
297
+ performanceObserver.disconnect()
298
+ performanceObserver = null
299
+ }
300
+ }
301
+
302
+ export { initPerformance, handleSendPerformance }