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.
- package/__test__/css/performance.css +3 -0
- package/__test__/err-batch.spec.ts +47 -0
- package/__test__/err.spec.ts +82 -0
- package/__test__/event.spec.ts +62 -0
- package/__test__/html/performance.html +57 -0
- package/__test__/html/recordscreen.html +39 -0
- package/__test__/http.spec.ts +143 -0
- package/__test__/img/performance.png +0 -0
- package/__test__/js/performance.js +3 -0
- package/__test__/performance.spec.ts +115 -0
- package/__test__/recordscreen.spec.ts +50 -0
- package/__test__/utils/index.ts +132 -0
- package/__test__/utils/pollify.ts +14 -0
- package/__test__/utils.spec.ts +18 -0
- package/{index.cjs → dist/index.cjs} +1 -1
- package/{index.iife.js → dist/index.iife.js} +1 -1
- package/{index.iife.min.js → dist/index.iife.min.js} +1 -1
- package/{index.mjs → dist/index.mjs} +1 -1
- package/dist/package.json +49 -0
- package/index.ts +76 -0
- package/package.json +9 -9
- package/src/common/config.ts +13 -0
- package/src/common/constant.ts +57 -0
- package/src/common/index.ts +2 -0
- package/src/lib/base.ts +129 -0
- package/src/lib/err-batch.ts +134 -0
- package/src/lib/err.ts +323 -0
- package/src/lib/event-dwell.ts +63 -0
- package/src/lib/event.ts +252 -0
- package/src/lib/eventBus.ts +97 -0
- package/src/lib/exportMethods.ts +208 -0
- package/src/lib/http.ts +197 -0
- package/src/lib/intersectionObserver.ts +169 -0
- package/src/lib/line-status.ts +45 -0
- package/src/lib/options.ts +325 -0
- package/src/lib/performance.ts +302 -0
- package/src/lib/pv.ts +199 -0
- package/src/lib/recordscreen.ts +169 -0
- package/src/lib/replace.ts +371 -0
- package/src/lib/sendData.ts +264 -0
- package/src/observer/computed.ts +52 -0
- package/src/observer/config.ts +1 -0
- package/src/observer/dep.ts +21 -0
- package/src/observer/index.ts +91 -0
- package/src/observer/ref.ts +80 -0
- package/src/observer/types.ts +22 -0
- package/src/observer/watch.ts +19 -0
- package/src/observer/watcher.ts +88 -0
- package/src/types/index.ts +126 -0
- package/src/utils/debug.ts +17 -0
- package/src/utils/element.ts +47 -0
- package/src/utils/fingerprintjs.ts +2132 -0
- package/src/utils/getIps.ts +127 -0
- package/src/utils/global.ts +50 -0
- package/src/utils/index.ts +552 -0
- package/src/utils/is.ts +78 -0
- package/src/utils/localStorage.ts +70 -0
- package/src/utils/polyfill.ts +11 -0
- package/src/utils/session.ts +29 -0
- /package/{LICENSE → dist/LICENSE} +0 -0
- /package/{README.md → dist/README.md} +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
package/src/lib/http.ts
ADDED
|
@@ -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
|
+
}
|