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.
Files changed (61) 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 +112 -0
  11. package/__test__/recordscreen.spec.ts +50 -0
  12. package/__test__/utils/index.ts +99 -0
  13. package/__test__/utils/pollify.ts +14 -0
  14. package/__test__/utils.spec.ts +18 -0
  15. package/dist/LICENSE +21 -0
  16. package/dist/README.md +97 -0
  17. package/dist/index.cjs +15943 -0
  18. package/dist/index.d.ts +323 -0
  19. package/dist/index.iife.js +15946 -0
  20. package/dist/index.iife.min.js +28 -0
  21. package/dist/index.mjs +15913 -0
  22. package/dist/package.json +49 -0
  23. package/index.ts +75 -0
  24. package/package.json +49 -0
  25. package/src/common/config.ts +13 -0
  26. package/src/common/constant.ts +57 -0
  27. package/src/common/index.ts +2 -0
  28. package/src/lib/base.ts +129 -0
  29. package/src/lib/err-batch.ts +134 -0
  30. package/src/lib/err.ts +323 -0
  31. package/src/lib/event-dwell.ts +63 -0
  32. package/src/lib/event.ts +252 -0
  33. package/src/lib/eventBus.ts +97 -0
  34. package/src/lib/exportMethods.ts +208 -0
  35. package/src/lib/http.ts +197 -0
  36. package/src/lib/intersectionObserver.ts +164 -0
  37. package/src/lib/line-status.ts +45 -0
  38. package/src/lib/options.ts +325 -0
  39. package/src/lib/performance.ts +302 -0
  40. package/src/lib/pv.ts +199 -0
  41. package/src/lib/recordscreen.ts +169 -0
  42. package/src/lib/replace.ts +371 -0
  43. package/src/lib/sendData.ts +264 -0
  44. package/src/observer/computed.ts +52 -0
  45. package/src/observer/config.ts +1 -0
  46. package/src/observer/dep.ts +21 -0
  47. package/src/observer/index.ts +91 -0
  48. package/src/observer/ref.ts +80 -0
  49. package/src/observer/types.ts +22 -0
  50. package/src/observer/watch.ts +19 -0
  51. package/src/observer/watcher.ts +88 -0
  52. package/src/types/index.ts +126 -0
  53. package/src/utils/debug.ts +17 -0
  54. package/src/utils/element.ts +47 -0
  55. package/src/utils/fingerprintjs.ts +2132 -0
  56. package/src/utils/getIps.ts +127 -0
  57. package/src/utils/global.ts +49 -0
  58. package/src/utils/index.ts +551 -0
  59. package/src/utils/is.ts +78 -0
  60. package/src/utils/localStorage.ts +70 -0
  61. 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
+ }