web-tracing-core 2.1.0
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 +112 -0
- package/__test__/recordscreen.spec.ts +50 -0
- package/__test__/utils/index.ts +99 -0
- package/__test__/utils/pollify.ts +14 -0
- package/__test__/utils.spec.ts +18 -0
- package/dist/LICENSE +21 -0
- package/dist/README.md +97 -0
- package/dist/index.cjs +15943 -0
- package/dist/index.d.ts +323 -0
- package/dist/index.iife.js +15946 -0
- package/dist/index.iife.min.js +28 -0
- package/dist/index.mjs +15913 -0
- package/dist/package.json +49 -0
- package/index.ts +75 -0
- package/package.json +49 -0
- 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 +164 -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 +49 -0
- package/src/utils/index.ts +551 -0
- package/src/utils/is.ts +78 -0
- package/src/utils/localStorage.ts +70 -0
- package/src/utils/session.ts +27 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import type { VoidFun } from '../types'
|
|
2
|
+
import { _global } from '../utils/global'
|
|
3
|
+
import {
|
|
4
|
+
on,
|
|
5
|
+
replaceAop,
|
|
6
|
+
throttle,
|
|
7
|
+
isValidKey,
|
|
8
|
+
getTimestamp,
|
|
9
|
+
off
|
|
10
|
+
} from '../utils'
|
|
11
|
+
import { EVENTTYPES } from '../common'
|
|
12
|
+
import { eventBus } from './eventBus'
|
|
13
|
+
|
|
14
|
+
// 存储原始方法,用于恢复
|
|
15
|
+
const originalMethods = {
|
|
16
|
+
consoleError: null as any,
|
|
17
|
+
xhrOpen: null as any,
|
|
18
|
+
xhrSend: null as any,
|
|
19
|
+
fetch: null as any,
|
|
20
|
+
historyPushState: null as any,
|
|
21
|
+
historyReplaceState: null as any
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 存储事件监听器引用,用于移除
|
|
25
|
+
const eventListeners = {
|
|
26
|
+
error: null as any,
|
|
27
|
+
unhandledrejection: null as any,
|
|
28
|
+
click: null as any,
|
|
29
|
+
load: null as any,
|
|
30
|
+
beforeunload: null as any,
|
|
31
|
+
hashchange: null as any,
|
|
32
|
+
popstate: null as any,
|
|
33
|
+
offline: null as any,
|
|
34
|
+
online: null as any
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 根据入参初始化 重写、监听
|
|
39
|
+
*
|
|
40
|
+
* 定义一个产出模块
|
|
41
|
+
* 其注册所有模块所需要的 - 监听,改写
|
|
42
|
+
* 当那些模块要用的时候,去产出模块那数据,这个模块的数据不应该有业务数据
|
|
43
|
+
* 只负责去采集数据,具体怎么拿是业务模块需要去分辨的,采集哪些数据是注册的时候决定的
|
|
44
|
+
* 只会管兼容性问题,不兼容的api就不做动作
|
|
45
|
+
* 这样就不需要所有的模块都要init了,之后需要init的模块,可以专门去做模块内的事
|
|
46
|
+
* 注意这里面耦合性不能太高,不要抽了还不如不抽,做到模块内改了代码不需要去另外个文件再改
|
|
47
|
+
*/
|
|
48
|
+
export function initReplace(): void {
|
|
49
|
+
for (const key in EVENTTYPES) {
|
|
50
|
+
if (isValidKey(key, EVENTTYPES)) {
|
|
51
|
+
replace(key)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function replace(type: EVENTTYPES): void {
|
|
57
|
+
if (!isValidKey(type, EVENTTYPES)) return
|
|
58
|
+
|
|
59
|
+
const value = EVENTTYPES[type]
|
|
60
|
+
// debug('replace-初始化挂载事件:', value)
|
|
61
|
+
switch (value) {
|
|
62
|
+
case EVENTTYPES.ERROR:
|
|
63
|
+
listenError(EVENTTYPES.ERROR)
|
|
64
|
+
break
|
|
65
|
+
case EVENTTYPES.UNHANDLEDREJECTION:
|
|
66
|
+
listenUnhandledrejection(EVENTTYPES.UNHANDLEDREJECTION)
|
|
67
|
+
break
|
|
68
|
+
case EVENTTYPES.CONSOLEERROR:
|
|
69
|
+
replaceConsoleError(EVENTTYPES.CONSOLEERROR)
|
|
70
|
+
break
|
|
71
|
+
case EVENTTYPES.CLICK:
|
|
72
|
+
listenClick(EVENTTYPES.CLICK)
|
|
73
|
+
break
|
|
74
|
+
case EVENTTYPES.LOAD:
|
|
75
|
+
listenLoad(EVENTTYPES.LOAD)
|
|
76
|
+
break
|
|
77
|
+
case EVENTTYPES.BEFOREUNLOAD:
|
|
78
|
+
listenBeforeunload(EVENTTYPES.BEFOREUNLOAD)
|
|
79
|
+
break
|
|
80
|
+
case EVENTTYPES.XHROPEN:
|
|
81
|
+
replaceXHROpen(EVENTTYPES.XHROPEN)
|
|
82
|
+
break
|
|
83
|
+
case EVENTTYPES.XHRSEND:
|
|
84
|
+
replaceXHRSend(EVENTTYPES.XHRSEND)
|
|
85
|
+
break
|
|
86
|
+
case EVENTTYPES.FETCH:
|
|
87
|
+
replaceFetch(EVENTTYPES.FETCH)
|
|
88
|
+
break
|
|
89
|
+
case EVENTTYPES.HASHCHANGE:
|
|
90
|
+
listenHashchange(EVENTTYPES.HASHCHANGE)
|
|
91
|
+
break
|
|
92
|
+
case EVENTTYPES.HISTORYPUSHSTATE:
|
|
93
|
+
replaceHistoryPushState(EVENTTYPES.HISTORYPUSHSTATE)
|
|
94
|
+
break
|
|
95
|
+
case EVENTTYPES.HISTORYREPLACESTATE:
|
|
96
|
+
replaceHistoryReplaceState(EVENTTYPES.HISTORYREPLACESTATE)
|
|
97
|
+
break
|
|
98
|
+
case EVENTTYPES.POPSTATE:
|
|
99
|
+
listenPopState(EVENTTYPES.POPSTATE)
|
|
100
|
+
break
|
|
101
|
+
case EVENTTYPES.OFFLINE:
|
|
102
|
+
listenOffline(EVENTTYPES.OFFLINE)
|
|
103
|
+
break
|
|
104
|
+
case EVENTTYPES.ONLINE:
|
|
105
|
+
listenOnline(EVENTTYPES.ONLINE)
|
|
106
|
+
break
|
|
107
|
+
|
|
108
|
+
default:
|
|
109
|
+
break
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 监听 - error
|
|
115
|
+
*/
|
|
116
|
+
function listenError(type: EVENTTYPES): void {
|
|
117
|
+
const errorHandler = function (e: ErrorEvent) {
|
|
118
|
+
eventBus.runEvent(type, e)
|
|
119
|
+
}
|
|
120
|
+
eventListeners.error = errorHandler
|
|
121
|
+
on(_global, 'error', errorHandler, true)
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 监听 - unhandledrejection(promise异常)
|
|
125
|
+
*/
|
|
126
|
+
function listenUnhandledrejection(type: EVENTTYPES): void {
|
|
127
|
+
const rejectionHandler = function (ev: PromiseRejectionEvent) {
|
|
128
|
+
eventBus.runEvent(type, ev)
|
|
129
|
+
}
|
|
130
|
+
eventListeners.unhandledrejection = rejectionHandler
|
|
131
|
+
on(_global, 'unhandledrejection', rejectionHandler)
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 重写 - console.error
|
|
135
|
+
*/
|
|
136
|
+
function replaceConsoleError(type: EVENTTYPES): void {
|
|
137
|
+
originalMethods.consoleError = console.error
|
|
138
|
+
replaceAop(console, 'error', (originalError: VoidFun) => {
|
|
139
|
+
return function (this: any, ...args: any[]): void {
|
|
140
|
+
if (
|
|
141
|
+
!(args[0] && args[0].slice && args[0].slice(0, 12) === '@web-tracing')
|
|
142
|
+
) {
|
|
143
|
+
eventBus.runEvent(type, args)
|
|
144
|
+
}
|
|
145
|
+
originalError.apply(this, args)
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 监听 - click
|
|
151
|
+
*/
|
|
152
|
+
function listenClick(type: EVENTTYPES): void {
|
|
153
|
+
if (!('document' in _global)) return
|
|
154
|
+
const clickThrottle = throttle(eventBus.runEvent, 100, true)
|
|
155
|
+
const clickHandler = function (this: any, e: MouseEvent) {
|
|
156
|
+
clickThrottle.call(eventBus, type, e)
|
|
157
|
+
}
|
|
158
|
+
eventListeners.click = clickHandler
|
|
159
|
+
on(_global.document, 'click', clickHandler, true)
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 监听 - load
|
|
163
|
+
*/
|
|
164
|
+
function listenLoad(type: EVENTTYPES): void {
|
|
165
|
+
const loadHandler = function (e: Event) {
|
|
166
|
+
eventBus.runEvent(type, e)
|
|
167
|
+
}
|
|
168
|
+
eventListeners.load = loadHandler
|
|
169
|
+
on(_global, 'load', loadHandler, true)
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 监听 - beforeunload
|
|
173
|
+
*/
|
|
174
|
+
function listenBeforeunload(type: EVENTTYPES): void {
|
|
175
|
+
const beforeunloadHandler = function (e: BeforeUnloadEvent) {
|
|
176
|
+
eventBus.runEvent(type, e)
|
|
177
|
+
}
|
|
178
|
+
eventListeners.beforeunload = beforeunloadHandler
|
|
179
|
+
on(_global, 'beforeunload', beforeunloadHandler, false)
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 重写 - XHR-open
|
|
183
|
+
*/
|
|
184
|
+
function replaceXHROpen(type: EVENTTYPES): void {
|
|
185
|
+
if (!('XMLHttpRequest' in _global)) return
|
|
186
|
+
originalMethods.xhrOpen = XMLHttpRequest.prototype.open
|
|
187
|
+
replaceAop(XMLHttpRequest.prototype, 'open', (originalOpen: VoidFun) => {
|
|
188
|
+
return function (this: any, ...args: any[]): void {
|
|
189
|
+
eventBus.runEvent(type, ...args)
|
|
190
|
+
originalOpen.apply(this, args)
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 重写 - XHR-send
|
|
196
|
+
*/
|
|
197
|
+
function replaceXHRSend(type: EVENTTYPES): void {
|
|
198
|
+
if (!('XMLHttpRequest' in _global)) return
|
|
199
|
+
originalMethods.xhrSend = XMLHttpRequest.prototype.send
|
|
200
|
+
replaceAop(XMLHttpRequest.prototype, 'send', (originalSend: VoidFun) => {
|
|
201
|
+
return function (this: any, ...args: any[]): void {
|
|
202
|
+
eventBus.runEvent(type, this, ...args)
|
|
203
|
+
originalSend.apply(this, args)
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 重写 - fetch
|
|
209
|
+
*/
|
|
210
|
+
function replaceFetch(type: EVENTTYPES): void {
|
|
211
|
+
if (!('fetch' in _global)) return
|
|
212
|
+
originalMethods.fetch = _global.fetch
|
|
213
|
+
replaceAop(_global, 'fetch', originalFetch => {
|
|
214
|
+
return function (this: any, ...args: any[]): void {
|
|
215
|
+
const fetchStart = getTimestamp()
|
|
216
|
+
const traceObj = {}
|
|
217
|
+
return originalFetch.apply(_global, args).then((res: any) => {
|
|
218
|
+
eventBus.runEvent(type, args[0], args[1], res, fetchStart, traceObj)
|
|
219
|
+
return res
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* 监听 - hashchange
|
|
226
|
+
*/
|
|
227
|
+
function listenHashchange(type: EVENTTYPES): void {
|
|
228
|
+
const hashchangeHandler = function (e: HashChangeEvent) {
|
|
229
|
+
eventBus.runEvent(type, e)
|
|
230
|
+
}
|
|
231
|
+
eventListeners.hashchange = hashchangeHandler
|
|
232
|
+
on(_global, 'hashchange', hashchangeHandler)
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 重写 - history-replaceState
|
|
236
|
+
*/
|
|
237
|
+
function replaceHistoryReplaceState(type: EVENTTYPES): void {
|
|
238
|
+
originalMethods.historyReplaceState = history.replaceState
|
|
239
|
+
replaceAop(history, 'replaceState', (originalReplaceState: VoidFun) => {
|
|
240
|
+
return function (this: any, ...args: any[]): void {
|
|
241
|
+
eventBus.runEvent(type, ...args)
|
|
242
|
+
originalReplaceState.apply(this, args)
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* 重写 - history-pushState
|
|
248
|
+
*/
|
|
249
|
+
function replaceHistoryPushState(type: EVENTTYPES): void {
|
|
250
|
+
originalMethods.historyPushState = history.pushState
|
|
251
|
+
replaceAop(history, 'pushState', (originalPushState: VoidFun) => {
|
|
252
|
+
return function (this: any, ...args: any[]): void {
|
|
253
|
+
eventBus.runEvent(type, ...args)
|
|
254
|
+
originalPushState.apply(this, args)
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* 监听 - popstate
|
|
260
|
+
*/
|
|
261
|
+
function listenPopState(type: EVENTTYPES): void {
|
|
262
|
+
const popstateHandler = function (e: PopStateEvent) {
|
|
263
|
+
eventBus.runEvent(type, e)
|
|
264
|
+
}
|
|
265
|
+
eventListeners.popstate = popstateHandler
|
|
266
|
+
on(_global, 'popstate', popstateHandler)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* 监听 - offline 网络是否关闭
|
|
271
|
+
*/
|
|
272
|
+
function listenOffline(type: EVENTTYPES): void {
|
|
273
|
+
const offlineHandler = function (e: Event) {
|
|
274
|
+
eventBus.runEvent(type, e)
|
|
275
|
+
}
|
|
276
|
+
eventListeners.offline = offlineHandler
|
|
277
|
+
on(_global, 'offline', offlineHandler)
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* 监听 - online 网络是否开启
|
|
281
|
+
*/
|
|
282
|
+
function listenOnline(type: EVENTTYPES): void {
|
|
283
|
+
const offlineHandler = function (e: Event) {
|
|
284
|
+
eventBus.runEvent(type, e)
|
|
285
|
+
}
|
|
286
|
+
eventListeners.offline = offlineHandler
|
|
287
|
+
on(_global, 'offline', offlineHandler)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* 销毁所有重写和监听
|
|
292
|
+
*/
|
|
293
|
+
export function destroyReplace(): void {
|
|
294
|
+
// 恢复 console.error
|
|
295
|
+
if (
|
|
296
|
+
originalMethods.consoleError &&
|
|
297
|
+
console.error !== originalMethods.consoleError
|
|
298
|
+
) {
|
|
299
|
+
console.error = originalMethods.consoleError
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 恢复 XMLHttpRequest
|
|
303
|
+
if (
|
|
304
|
+
originalMethods.xhrOpen &&
|
|
305
|
+
XMLHttpRequest.prototype.open !== originalMethods.xhrOpen
|
|
306
|
+
) {
|
|
307
|
+
XMLHttpRequest.prototype.open = originalMethods.xhrOpen
|
|
308
|
+
}
|
|
309
|
+
if (
|
|
310
|
+
originalMethods.xhrSend &&
|
|
311
|
+
XMLHttpRequest.prototype.send !== originalMethods.xhrSend
|
|
312
|
+
) {
|
|
313
|
+
XMLHttpRequest.prototype.send = originalMethods.xhrSend
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 恢复 fetch
|
|
317
|
+
if (originalMethods.fetch && _global.fetch !== originalMethods.fetch) {
|
|
318
|
+
_global.fetch = originalMethods.fetch
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// 恢复 history
|
|
322
|
+
if (
|
|
323
|
+
originalMethods.historyPushState &&
|
|
324
|
+
history.pushState !== originalMethods.historyPushState
|
|
325
|
+
) {
|
|
326
|
+
history.pushState = originalMethods.historyPushState
|
|
327
|
+
}
|
|
328
|
+
if (
|
|
329
|
+
originalMethods.historyReplaceState &&
|
|
330
|
+
history.replaceState !== originalMethods.historyReplaceState
|
|
331
|
+
) {
|
|
332
|
+
history.replaceState = originalMethods.historyReplaceState
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 移除全局事件监听器
|
|
336
|
+
if (eventListeners.error) {
|
|
337
|
+
off(_global, 'error', eventListeners.error, true)
|
|
338
|
+
}
|
|
339
|
+
if (eventListeners.unhandledrejection) {
|
|
340
|
+
off(_global, 'unhandledrejection', eventListeners.unhandledrejection)
|
|
341
|
+
}
|
|
342
|
+
if (eventListeners.click) {
|
|
343
|
+
off(_global.document, 'click', eventListeners.click, true)
|
|
344
|
+
}
|
|
345
|
+
if (eventListeners.load) {
|
|
346
|
+
off(_global, 'load', eventListeners.load, true)
|
|
347
|
+
}
|
|
348
|
+
if (eventListeners.beforeunload) {
|
|
349
|
+
off(_global, 'beforeunload', eventListeners.beforeunload, false)
|
|
350
|
+
}
|
|
351
|
+
if (eventListeners.hashchange) {
|
|
352
|
+
off(_global, 'hashchange', eventListeners.hashchange)
|
|
353
|
+
}
|
|
354
|
+
if (eventListeners.popstate) {
|
|
355
|
+
off(_global, 'popstate', eventListeners.popstate)
|
|
356
|
+
}
|
|
357
|
+
if (eventListeners.offline) {
|
|
358
|
+
off(_global, 'offline', eventListeners.offline)
|
|
359
|
+
}
|
|
360
|
+
if (eventListeners.online) {
|
|
361
|
+
off(_global, 'online', eventListeners.online)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 清空存储
|
|
365
|
+
Object.keys(originalMethods).forEach(key => {
|
|
366
|
+
;(originalMethods as any)[key] = null
|
|
367
|
+
})
|
|
368
|
+
Object.keys(eventListeners).forEach(key => {
|
|
369
|
+
;(eventListeners as any)[key] = null
|
|
370
|
+
})
|
|
371
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { _support, _global } from '../utils/global'
|
|
2
|
+
import { refreshSession } from '../utils/session'
|
|
3
|
+
import { LocalStorageUtil } from '../utils/localStorage'
|
|
4
|
+
import {
|
|
5
|
+
sendByBeacon,
|
|
6
|
+
sendByImage,
|
|
7
|
+
sendByXML,
|
|
8
|
+
nextTime,
|
|
9
|
+
map,
|
|
10
|
+
typeofAny,
|
|
11
|
+
randomBoolean,
|
|
12
|
+
getTimestamp,
|
|
13
|
+
isObjectOverSizeLimit
|
|
14
|
+
} from '../utils'
|
|
15
|
+
import { debug, logError } from '../utils/debug'
|
|
16
|
+
import { baseInfo } from './base'
|
|
17
|
+
import { options } from './options'
|
|
18
|
+
import { AnyObj } from '../types'
|
|
19
|
+
import { isFlase, isArray } from '../utils/is'
|
|
20
|
+
import { SDK_LOCAL_KEY } from '../common/config'
|
|
21
|
+
import { executeFunctions } from '../utils'
|
|
22
|
+
import { computed } from '../observer'
|
|
23
|
+
|
|
24
|
+
export class SendData {
|
|
25
|
+
private events: AnyObj[] = [] // 批次队列
|
|
26
|
+
private timeoutID: NodeJS.Timeout | undefined // 延迟发送ID
|
|
27
|
+
private isServerAvailable = true // 接口是否可用
|
|
28
|
+
private retryTimer: NodeJS.Timeout | undefined // 重试定时器
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 发送事件列表
|
|
32
|
+
*/
|
|
33
|
+
private send() {
|
|
34
|
+
if (!this.events.length) return
|
|
35
|
+
if (!this.isServerAvailable) return // 接口异常时暂停上报
|
|
36
|
+
|
|
37
|
+
// 选取首部的部分数据来发送,performance会一次性采集大量数据追加到events中
|
|
38
|
+
const sendEvents = this.events.slice(0, options.value.cacheMaxLength) // 需要发送的事件
|
|
39
|
+
this.events = this.events.slice(options.value.cacheMaxLength) // 剩下待发的事件
|
|
40
|
+
|
|
41
|
+
const time = getTimestamp()
|
|
42
|
+
const sendParams = computed(() => ({
|
|
43
|
+
baseInfo: {
|
|
44
|
+
...baseInfo.base?.value,
|
|
45
|
+
sendTime: time,
|
|
46
|
+
userUuid: options.value.userUuid
|
|
47
|
+
},
|
|
48
|
+
eventInfo: map(sendEvents, (e: any) => {
|
|
49
|
+
e.sendTime = time
|
|
50
|
+
return e
|
|
51
|
+
})
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
// 本地化拦截
|
|
55
|
+
if (options.value.localization) {
|
|
56
|
+
const success = LocalStorageUtil.setSendDataItem(
|
|
57
|
+
SDK_LOCAL_KEY,
|
|
58
|
+
sendParams.value
|
|
59
|
+
)
|
|
60
|
+
if (!success) options.value.localizationOverFlow(sendParams.value)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const afterSendParams = executeFunctions(
|
|
65
|
+
options.value.beforeSendData,
|
|
66
|
+
false,
|
|
67
|
+
sendParams.value
|
|
68
|
+
)
|
|
69
|
+
if (isFlase(afterSendParams)) return
|
|
70
|
+
if (!this.validateObject(afterSendParams, 'beforeSendData')) return
|
|
71
|
+
|
|
72
|
+
debug('send events', sendParams.value)
|
|
73
|
+
|
|
74
|
+
this.executeSend(options.value.dsn, afterSendParams).then((res: any) => {
|
|
75
|
+
if (res.success === false) {
|
|
76
|
+
this.handleServerError()
|
|
77
|
+
} else {
|
|
78
|
+
this.isServerAvailable = true
|
|
79
|
+
executeFunctions(options.value.afterSendData, true, {
|
|
80
|
+
...res,
|
|
81
|
+
params: afterSendParams
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// 如果一次性发生的事件超过了阈值(cacheMaxLength),那么这些经过裁剪的事件列表剩下的会直接发,并不会延迟等到下一个队列
|
|
87
|
+
if (this.events.length) {
|
|
88
|
+
nextTime(this.send.bind(this)) // 继续传输剩余内容,在下一个时间择机传输
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private handleServerError() {
|
|
93
|
+
this.isServerAvailable = false
|
|
94
|
+
// 定时重试,直到接口恢复
|
|
95
|
+
if (this.retryTimer) clearTimeout(this.retryTimer)
|
|
96
|
+
if (options.value.checkRecoverInterval === -1) return
|
|
97
|
+
const interval = (options.value.checkRecoverInterval ?? 1) * 60 * 1000
|
|
98
|
+
this.retryTimer = setTimeout(() => {
|
|
99
|
+
this.testServerAvailable()
|
|
100
|
+
}, interval)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private testServerAvailable() {
|
|
104
|
+
// 尝试发送一个空包检测接口是否恢复
|
|
105
|
+
sendByXML(options.value.dsn, { ping: 1 }, options.value.timeout)
|
|
106
|
+
.then(() => {
|
|
107
|
+
this.isServerAvailable = true
|
|
108
|
+
if (this.retryTimer) clearTimeout(this.retryTimer)
|
|
109
|
+
// 恢复上报
|
|
110
|
+
if (this.events.length) this.send()
|
|
111
|
+
})
|
|
112
|
+
.catch(() => {
|
|
113
|
+
this.handleServerError()
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 发送本地事件列表
|
|
119
|
+
* @param e 需要发送的事件信息
|
|
120
|
+
*/
|
|
121
|
+
public sendLocal(e: AnyObj) {
|
|
122
|
+
const afterSendParams = executeFunctions(
|
|
123
|
+
options.value.beforeSendData,
|
|
124
|
+
false,
|
|
125
|
+
e
|
|
126
|
+
)
|
|
127
|
+
if (isFlase(afterSendParams)) return
|
|
128
|
+
if (!this.validateObject(afterSendParams, 'beforeSendData')) return
|
|
129
|
+
|
|
130
|
+
debug('send events', afterSendParams)
|
|
131
|
+
|
|
132
|
+
this.executeSend(options.value.dsn, afterSendParams)
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 记录需要发送的埋点数据
|
|
136
|
+
* @param e 需要发送的事件信息
|
|
137
|
+
* @param flush 是否立即发送
|
|
138
|
+
*/
|
|
139
|
+
public emit(e: AnyObj, flush = false) {
|
|
140
|
+
if (!e) return
|
|
141
|
+
if (!_support.lineStatus.onLine) return
|
|
142
|
+
if (!flush && !randomBoolean(options.value.tracesSampleRate)) return
|
|
143
|
+
if (!isArray(e)) e = [e]
|
|
144
|
+
|
|
145
|
+
// 如果接口异常,且队列已达最大缓存数,则丢弃旧日志,保留最新日志
|
|
146
|
+
const maxQueueLength = options.value.maxQueueLength ?? 200
|
|
147
|
+
if (!this.isServerAvailable && this.events.length >= maxQueueLength) {
|
|
148
|
+
// 丢弃最旧的日志,保留最新日志
|
|
149
|
+
this.events = this.events
|
|
150
|
+
.slice(this.events.length - maxQueueLength + e.length)
|
|
151
|
+
.concat(e)
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const eventList = executeFunctions(
|
|
156
|
+
options.value.beforePushEventList,
|
|
157
|
+
false,
|
|
158
|
+
e
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if (isFlase(eventList)) return
|
|
162
|
+
if (!this.validateObject(eventList, 'beforePushEventList')) return
|
|
163
|
+
|
|
164
|
+
this.events = this.events.concat(eventList)
|
|
165
|
+
refreshSession()
|
|
166
|
+
// debug('receive event, waiting to send', e)
|
|
167
|
+
if (this.timeoutID) clearTimeout(this.timeoutID)
|
|
168
|
+
|
|
169
|
+
// 如果基础信息还未初始化完成,则等待完成后上报
|
|
170
|
+
if (!baseInfo._initSuccess) {
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 满足最大记录数,立即发送,否则定时发送
|
|
175
|
+
if (this.events.length >= options.value.cacheMaxLength || flush) {
|
|
176
|
+
this.send()
|
|
177
|
+
} else {
|
|
178
|
+
this.timeoutID = setTimeout(
|
|
179
|
+
this.send.bind(this),
|
|
180
|
+
options.value.cacheWatingTime
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 发送数据
|
|
186
|
+
* @param url 目标地址
|
|
187
|
+
* @param data 附带参数
|
|
188
|
+
*/
|
|
189
|
+
private executeSend(url: string, data: any) {
|
|
190
|
+
let sendType = 1
|
|
191
|
+
if (options.value.sendTypeByXmlBody) {
|
|
192
|
+
// 强制指定 xml body 形式
|
|
193
|
+
sendType = 3
|
|
194
|
+
} else if (_global.navigator) {
|
|
195
|
+
// sendBeacon 最大64kb
|
|
196
|
+
sendType = isObjectOverSizeLimit(data, 60) ? 3 : 1
|
|
197
|
+
} else {
|
|
198
|
+
// img 限制在 2kb
|
|
199
|
+
sendType = isObjectOverSizeLimit(data, 2) ? 3 : 2
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return new Promise(resolve => {
|
|
203
|
+
switch (sendType) {
|
|
204
|
+
case 1:
|
|
205
|
+
resolve({ sendType: 'sendBeacon', success: sendByBeacon(url, data) })
|
|
206
|
+
break
|
|
207
|
+
case 2:
|
|
208
|
+
sendByImage(url, data)
|
|
209
|
+
.then(() => {
|
|
210
|
+
resolve({ sendType: 'image', success: true })
|
|
211
|
+
})
|
|
212
|
+
.catch(() => {
|
|
213
|
+
resolve({ sendType: 'image', success: false })
|
|
214
|
+
})
|
|
215
|
+
break
|
|
216
|
+
case 3:
|
|
217
|
+
sendByXML(url, data, options.value.timeout)
|
|
218
|
+
.then(() => {
|
|
219
|
+
resolve({ sendType: 'xml', success: true })
|
|
220
|
+
})
|
|
221
|
+
.catch(() => {
|
|
222
|
+
resolve({ sendType: 'xml', success: false })
|
|
223
|
+
})
|
|
224
|
+
break
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* 验证选项的类型 - 只验证是否为 {} []
|
|
230
|
+
* 返回 false意思是取消放入队列 / 取消发送
|
|
231
|
+
*/
|
|
232
|
+
private validateObject(target: any, targetName: string): boolean | void {
|
|
233
|
+
if (target === false) return false
|
|
234
|
+
|
|
235
|
+
if (!target) {
|
|
236
|
+
logError(`NullError: ${targetName}期望返回 {} 或者 [] 类型,目前无返回值`)
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
if (['object', 'array'].includes(typeofAny(target))) return true
|
|
240
|
+
logError(
|
|
241
|
+
`TypeError: ${targetName}期望返回 {} 或者 [] 类型,目前是${typeofAny(
|
|
242
|
+
target
|
|
243
|
+
)}类型`
|
|
244
|
+
)
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 销毁定时器和队列
|
|
250
|
+
*/
|
|
251
|
+
public destroy() {
|
|
252
|
+
if (this.timeoutID) clearTimeout(this.timeoutID)
|
|
253
|
+
if (this.retryTimer) clearTimeout(this.retryTimer)
|
|
254
|
+
this.events = []
|
|
255
|
+
this.isServerAvailable = true
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export let sendData: SendData
|
|
260
|
+
|
|
261
|
+
export function initSendData() {
|
|
262
|
+
_support.sendData = new SendData()
|
|
263
|
+
sendData = _support.sendData
|
|
264
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Watcher } from './watcher'
|
|
2
|
+
import { ObserverValue, AnyFun } from './types'
|
|
3
|
+
import { OBSERVERSIGNBOARD } from './config'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 计算属性响应式
|
|
7
|
+
*/
|
|
8
|
+
export class Computed<T> {
|
|
9
|
+
target: ObserverValue<T>
|
|
10
|
+
constructor(target: ObserverValue<T>) {
|
|
11
|
+
this.target = target
|
|
12
|
+
}
|
|
13
|
+
defineReactive() {
|
|
14
|
+
const computedWatcher = new Watcher(this, { computed: true })
|
|
15
|
+
|
|
16
|
+
// const proxyCache = new WeakMap<ObserverValue<any>, any>()
|
|
17
|
+
const handlers: ProxyHandler<ObserverValue<any>> = {
|
|
18
|
+
get() {
|
|
19
|
+
if (computedWatcher.proxy.dirty) {
|
|
20
|
+
// 代表这个属性已经脏了,需要更新(重新运算)
|
|
21
|
+
// console.log('计算属性:取新值')
|
|
22
|
+
computedWatcher.depend() // 添加上下文与此属性绑定
|
|
23
|
+
return computedWatcher.get()
|
|
24
|
+
} else {
|
|
25
|
+
// 代表这个属性不需要重新运算
|
|
26
|
+
// console.log('计算属性:取旧值')
|
|
27
|
+
|
|
28
|
+
// 取旧值的时候也要添加上下文绑定
|
|
29
|
+
// 因为其他值在依赖这个计算属性的时候,有可能会依赖到旧的值
|
|
30
|
+
// 所以在依赖到旧值时也要添加上下文绑定,从而当这个计算属性被改变时也能通知到对方改变
|
|
31
|
+
// 一开始我就是没进行这一步,从而导致莫名bug
|
|
32
|
+
computedWatcher.depend()
|
|
33
|
+
return computedWatcher.proxy.value
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return new Proxy<ObserverValue<T>>(this.target, handlers)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const computedMap = new WeakMap<Computed<any>, AnyFun>()
|
|
42
|
+
|
|
43
|
+
export function computed<T>(fun: AnyFun) {
|
|
44
|
+
const target: any = { value: 0 }
|
|
45
|
+
target[OBSERVERSIGNBOARD] = true
|
|
46
|
+
|
|
47
|
+
const ob = new Computed<T>(target)
|
|
48
|
+
const proxy = ob.defineReactive()
|
|
49
|
+
|
|
50
|
+
computedMap.set(ob, fun)
|
|
51
|
+
return proxy
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const OBSERVERSIGNBOARD = '__webtracingobserver__'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Watcher } from './watcher'
|
|
2
|
+
|
|
3
|
+
export class Dep {
|
|
4
|
+
// set结构可以自动去重,因为不可避免有些依赖会被重复添加
|
|
5
|
+
// 例如有两个计算属性是依赖于dataA,第一遍计算出那两个计算属性时,dataA的dep是收集了他俩的watcher
|
|
6
|
+
// 但是当其中一个计算属性重新计算时(比如另外一个依赖项改动了会影响此计算属性重新计算),会再次调取dataA
|
|
7
|
+
// 的get拦截,也就是会再次触发 dep.addSub(),如果不加重复过滤这样的场景会一直递增下去,然后当dataA发生
|
|
8
|
+
// 更改时遍历其subs,届时有太多不需要遍历的watcher,很大概率卡死
|
|
9
|
+
subs = new Set<Watcher>()
|
|
10
|
+
static target: Watcher | undefined // 全局唯一收集容器
|
|
11
|
+
addSub() {
|
|
12
|
+
if (Dep.target) this.subs.add(Dep.target)
|
|
13
|
+
}
|
|
14
|
+
notify(...params: any[]) {
|
|
15
|
+
// 在某个属性发生变化时会执行其 dep.notify(),用来通知依赖这个属性的所有 watcher
|
|
16
|
+
this.subs.forEach(function (watcher: any) {
|
|
17
|
+
watcher.proxy.dirty = true // 标明数据脏了,当再次使用到这个值会重新计算
|
|
18
|
+
watcher.update(...params)
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
}
|