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,197 @@
1
+ import {
2
+ on,
3
+ isValidKey,
4
+ getTimestamp,
5
+ parseGetParams,
6
+ getBaseUrl
7
+ } from '../utils'
8
+ import { handleSendError } from './err'
9
+ import { eventBus } from './eventBus'
10
+ import { EVENTTYPES, SENDID } from '../common'
11
+ import { options } from './options'
12
+ import { handleSendPerformance } from './performance'
13
+ // import { debug } from '../utils/debug'
14
+ import { isRegExp } from '../utils/is'
15
+
16
+ /**
17
+ * fetch请求拦截
18
+ */
19
+ function interceptFetch(): void {
20
+ eventBus.addEvent({
21
+ type: EVENTTYPES.FETCH,
22
+ callback: async (
23
+ reqUrl: string,
24
+ _options: Partial<Request> = {},
25
+ res: Response,
26
+ fetchStart: number,
27
+ traceObj: Partial<Request> = {}
28
+ ) => {
29
+ const { method = 'GET', body = {} } = _options
30
+ const { url, status, statusText } = res
31
+ const requestMethod = String(method).toLocaleLowerCase()
32
+
33
+ if (isIgnoreHttp(url)) return
34
+
35
+ if (status === 200 || status === 304) {
36
+ if (options.value.performance.server) {
37
+ handleSendPerformance({
38
+ eventId: SENDID.SERVER,
39
+ requestUrl: url,
40
+ triggerTime: fetchStart,
41
+ duration: getTimestamp() - fetchStart,
42
+ responseStatus: status,
43
+ requestMethod,
44
+ requestType: 'fetch',
45
+ ...traceObj,
46
+ params: method.toUpperCase() === 'POST' ? body : parseGetParams(url)
47
+ })
48
+ }
49
+ } else if (options.value.error.server) {
50
+ handleSendError({
51
+ eventId: SENDID.SERVER,
52
+ triggerTime: fetchStart,
53
+ duration: getTimestamp() - fetchStart,
54
+ errMessage: statusText,
55
+ requestUrl: getBaseUrl(url),
56
+ responseStatus: status,
57
+ requestMethod,
58
+ requestType: 'fetch',
59
+ ...traceObj,
60
+ params: method.toUpperCase() === 'POST' ? body : parseGetParams(url)
61
+ })
62
+ }
63
+ }
64
+ })
65
+ }
66
+
67
+ class RequestTemplate {
68
+ requestUrl = '' // 请求地址
69
+ requestMethod = '' // 请求类型 GET POST
70
+ requestParams = {} // get请求的参数
71
+ triggerTime = -1 // 请求发生时间
72
+ constructor(config = {}) {
73
+ Object.keys(config).forEach(key => {
74
+ if (isValidKey(key, config)) {
75
+ this[key] = config[key] || null
76
+ }
77
+ })
78
+ }
79
+ }
80
+
81
+ /**
82
+ * xhr 请求拦截
83
+ */
84
+ function interceptXHR(): void {
85
+ const _config = new RequestTemplate()
86
+
87
+ eventBus.addEvent({
88
+ type: EVENTTYPES.XHROPEN,
89
+ callback: (method, url) => {
90
+ _config.requestMethod = String(method).toLocaleLowerCase()
91
+ _config.requestUrl = url
92
+ _config.requestParams = parseGetParams(url)
93
+ }
94
+ })
95
+
96
+ eventBus.addEvent({
97
+ type: EVENTTYPES.XHRSEND,
98
+ callback: (that: XMLHttpRequest & any, body) => {
99
+ // readyState发生改变时触发,也就是请求状态改变时
100
+ // readyState 会依次变为 2,3,4 也就是会触发三次这里
101
+ on(that, EVENTTYPES.READYSTATECHANGE, function () {
102
+ const { readyState, status, responseURL, statusText } = that
103
+ const responseText =
104
+ that.responseType === '' || that.responseType === 'text'
105
+ ? that.responseText
106
+ : ''
107
+
108
+ if (readyState === 4) {
109
+ const requestUrl = responseURL || _config.requestUrl
110
+ if (isIgnoreHttp(requestUrl)) return
111
+
112
+ // 请求已完成,且响应已就绪
113
+ if (status === 200 || status === 304) {
114
+ if (options.value.performance.server) {
115
+ handleSendPerformance({
116
+ eventId: SENDID.SERVER,
117
+ requestUrl,
118
+ requestMethod: _config.requestMethod,
119
+ requestType: 'xhr',
120
+ responseStatus: status,
121
+ duration: getTimestamp() - _config.triggerTime,
122
+ params:
123
+ _config.requestMethod === 'post'
124
+ ? body
125
+ : _config.requestParams
126
+ })
127
+ }
128
+ } else if (options.value.error.server) {
129
+ handleSendError({
130
+ eventId: SENDID.SERVER,
131
+ errMessage: statusText || responseText,
132
+ requestUrl,
133
+ requestMethod: _config.requestMethod,
134
+ requestType: 'xhr',
135
+ responseStatus: status,
136
+ params:
137
+ _config.requestMethod === 'post' ? body : _config.requestParams
138
+ })
139
+ }
140
+ }
141
+ })
142
+
143
+ _config.triggerTime = getTimestamp()
144
+ }
145
+ })
146
+ }
147
+
148
+ /**
149
+ * 判断请求地址是否为需要拦截的
150
+ * @param url 请求地址
151
+ */
152
+ function isIgnoreHttp(url: string): boolean {
153
+ if (!options.value.ignoreRequest.length) return false
154
+ if (!url) return false
155
+
156
+ return options.value.ignoreRequest.some(item => {
157
+ if (isRegExp(item)) {
158
+ if ((item as RegExp).test(url)) {
159
+ // debug(`ignoreRequest拦截成功 - 截条件:${item} 拦截地址:${url}`)
160
+ return true
161
+ } else {
162
+ return false
163
+ }
164
+ } else {
165
+ if (url === item) {
166
+ // debug(`ignoreRequest拦截成功 - 截条件:${item} 拦截地址:${url}`)
167
+ return true
168
+ } else {
169
+ return false
170
+ }
171
+ }
172
+ })
173
+ }
174
+
175
+ /**
176
+ * 初始化http监控
177
+ */
178
+ function initHttp(): void {
179
+ if (!options.value.performance.server && !options.value.error.server) return
180
+
181
+ interceptXHR()
182
+ interceptFetch()
183
+ }
184
+
185
+ /**
186
+ * 卸载所有请求监听
187
+ */
188
+ export function destroyHttp() {
189
+ // 清除HTTP请求相关的事件类型
190
+ eventBus.removeEvents([
191
+ EVENTTYPES.FETCH,
192
+ EVENTTYPES.XHROPEN,
193
+ EVENTTYPES.XHRSEND
194
+ ])
195
+ }
196
+
197
+ export { initHttp }
@@ -0,0 +1,169 @@
1
+ import type { ElementOrList, TargetGather, AnyObj } from '../types'
2
+ import { unKnowToArray, getTimestamp, getLocationHref } from '../utils'
3
+ import { sendData } from './sendData'
4
+ import { _support } from '../utils/global'
5
+ import { SEDNEVENTTYPES } from '../common'
6
+
7
+ interface IoMap {
8
+ [key: number]: IntersectionObserver
9
+ }
10
+
11
+ interface TargetMap {
12
+ target: Element
13
+ threshold: number
14
+ observeTime: number // sdk开始监视的时间
15
+ showTime?: number // sdk检测到的开始时间
16
+ showEndTime?: number // sdk检测到的结束时间
17
+ params?: AnyObj
18
+ }
19
+
20
+ /**
21
+ * 元素曝光收集
22
+ * 收集参数:曝光开始时间、曝光结束时间、被曝光元素上附带的额外参数
23
+ * 收集契机:划出目标元素的收集范围
24
+ */
25
+ class Intersection {
26
+ private ioMap: IoMap = {}
27
+ private targetMap: TargetMap[] = []
28
+ private options = {
29
+ root: null,
30
+ rootMargin: '0px',
31
+ threshold: 0.5 // 阀值设为0.5,当只有比例达到一半时才触发回调函数
32
+ }
33
+ /**
34
+ * 针对 threshold 生成不同监听对象 (不允许同一个dom被两个监听对象监听)
35
+ * @param threshold 阈值
36
+ */
37
+ private initObserver(threshold: number) {
38
+ return new IntersectionObserver(
39
+ entries => {
40
+ entries.forEach(entry => {
41
+ if (entry.isIntersecting) {
42
+ const targetObj = this.targetMap.find(
43
+ mapTarget => mapTarget.target === entry.target
44
+ )
45
+ if (targetObj) {
46
+ targetObj.showTime = getTimestamp()
47
+ }
48
+ } else {
49
+ const targetObj = this.targetMap.find(
50
+ mapTarget => mapTarget.target === entry.target
51
+ )
52
+ if (targetObj) {
53
+ // 在进入页面时指定了没有在屏幕可视界面的dom,会立即触发这里
54
+ // 此时需要根据有无 showTime 区分是否为一个完整事件再去发送
55
+ if (!targetObj.showTime) return
56
+ targetObj.showEndTime = getTimestamp()
57
+ this.sendEvent(targetObj)
58
+ }
59
+ }
60
+ })
61
+ },
62
+ {
63
+ ...this.options,
64
+ threshold
65
+ }
66
+ )
67
+ }
68
+ /**
69
+ * 发送事件
70
+ */
71
+ private sendEvent(targetObj: TargetMap) {
72
+ // 只发送必要的数据,不包含 DOM 元素
73
+ sendData.emit({
74
+ eventType: SEDNEVENTTYPES.INTERSECTION,
75
+ triggerPageUrl: getLocationHref(),
76
+ threshold: targetObj.threshold,
77
+ observeTime: targetObj.observeTime,
78
+ showTime: targetObj.showTime,
79
+ showEndTime: targetObj.showEndTime,
80
+ params: targetObj.params
81
+ })
82
+ }
83
+ /**
84
+ * 开始观察目标元素
85
+ * 分为初始加载和过程中加载
86
+ * @param params 附带的额外参数
87
+ */
88
+ public observe(gather: TargetGather | TargetGather[]) {
89
+ const _gather = unKnowToArray(gather)
90
+ _gather.forEach(item => {
91
+ const _targetList = unKnowToArray(item.target)
92
+
93
+ if (!Object.prototype.hasOwnProperty.call(this.ioMap, item.threshold)) {
94
+ this.ioMap[item.threshold] = this.initObserver(item.threshold || 0.5)
95
+ }
96
+
97
+ _targetList.forEach(target => {
98
+ const index = this.targetMap.findIndex(
99
+ mapTarget => mapTarget.target === target
100
+ )
101
+ // 不允许重复观察
102
+ if (index === -1) {
103
+ this.ioMap[item.threshold].observe(target)
104
+
105
+ // 记录哪些元素被监听
106
+ this.targetMap.push({
107
+ target,
108
+ threshold: item.threshold,
109
+ observeTime: getTimestamp(), // 开始监听的时间
110
+ params: item.params
111
+ })
112
+ }
113
+ })
114
+ })
115
+ }
116
+ /**
117
+ * 对元素停止观察
118
+ */
119
+ public unobserve(target: ElementOrList) {
120
+ const _targetList = unKnowToArray(target)
121
+
122
+ _targetList.forEach(_target => {
123
+ // 第一步:找出此元素代表的 threshold 值
124
+ const targetIndex = this.targetMap.findIndex(
125
+ mapTarget => mapTarget.target === _target
126
+ )
127
+ if (targetIndex === -1) return // 不存在的元素则跳过
128
+
129
+ // 第二步:根据 threshold 值从 ioMap 获取到 io 实例
130
+ const io = this.ioMap[this.targetMap[targetIndex].threshold]
131
+ if (!io) return
132
+
133
+ this.targetMap.splice(targetIndex, 1)
134
+
135
+ // 第二步:io 实例执行 unobserve 方法
136
+ io.unobserve(_target)
137
+ })
138
+ }
139
+ /**
140
+ * 对所有元素停止观察
141
+ */
142
+ public disconnect() {
143
+ for (const key in this.ioMap) {
144
+ if (Object.prototype.hasOwnProperty.call(this.ioMap, key)) {
145
+ const io = this.ioMap[key]
146
+ io.disconnect()
147
+ }
148
+ }
149
+ this.targetMap = []
150
+ this.ioMap = {}
151
+ }
152
+ }
153
+
154
+ export let intersection: Intersection
155
+
156
+ /**
157
+ * 初始化曝光监听
158
+ */
159
+ export function initIntersection() {
160
+ _support.intersection = new Intersection()
161
+ intersection = _support.intersection
162
+ }
163
+
164
+ /**
165
+ * 卸载所有曝光监听
166
+ */
167
+ export function destroyIntersection() {
168
+ intersection?.disconnect()
169
+ }
@@ -0,0 +1,45 @@
1
+ import { _support } from '../utils/global'
2
+ import { EVENTTYPES } from '../common'
3
+ import { eventBus } from './eventBus'
4
+ import { debug } from '../utils/debug'
5
+
6
+ /**
7
+ * 监听网络状态
8
+ * 当处于断网状态下的所有埋点事件都无效(认为此时采集的数据大部分是无效的)
9
+ */
10
+ export class LineStatus {
11
+ onLine = true
12
+ constructor() {
13
+ this.init()
14
+ }
15
+ init() {
16
+ eventBus.addEvent({
17
+ type: EVENTTYPES.OFFLINE,
18
+ callback: e => {
19
+ if (e.type === 'offline') {
20
+ debug('网络断开')
21
+ this.onLine = false
22
+ }
23
+ }
24
+ })
25
+ eventBus.addEvent({
26
+ type: EVENTTYPES.ONLINE,
27
+ callback: e => {
28
+ if (e.type === 'online') {
29
+ debug('网络连接')
30
+ this.onLine = true
31
+ }
32
+ }
33
+ })
34
+ }
35
+ }
36
+
37
+ export let lineStatus: LineStatus
38
+
39
+ /**
40
+ * 初始化网络监听
41
+ */
42
+ export function initLineStatus() {
43
+ _support.lineStatus = new LineStatus()
44
+ lineStatus = _support.lineStatus
45
+ }