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,551 @@
|
|
|
1
|
+
import { AnyFun, AnyObj } from '../types'
|
|
2
|
+
import { logError } from './debug'
|
|
3
|
+
import { isRegExp, isArray, isFunction, isNumber } from './is'
|
|
4
|
+
import { isInit } from '../utils/global'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 添加事件监听器
|
|
8
|
+
* @param target 对象
|
|
9
|
+
* @param eventName 事件名称
|
|
10
|
+
* @param handler 回调函数
|
|
11
|
+
* @param opitons
|
|
12
|
+
*/
|
|
13
|
+
export function on(
|
|
14
|
+
target: Window | Document,
|
|
15
|
+
eventName: string,
|
|
16
|
+
handler: AnyFun,
|
|
17
|
+
opitons = false
|
|
18
|
+
): void {
|
|
19
|
+
target.addEventListener(eventName, handler, opitons)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 重写对象上面的某个属性
|
|
24
|
+
* @param source 需要被重写的对象
|
|
25
|
+
* @param name 需要被重写对象的key
|
|
26
|
+
* @param replacement 以原有的函数作为参数,执行并重写原有函数
|
|
27
|
+
* @param isForced 是否强制重写(可能原先没有该属性)
|
|
28
|
+
*/
|
|
29
|
+
export function replaceAop(
|
|
30
|
+
source: AnyObj,
|
|
31
|
+
name: string,
|
|
32
|
+
replacement: AnyFun,
|
|
33
|
+
isForced = false
|
|
34
|
+
): void {
|
|
35
|
+
if (source === undefined) return
|
|
36
|
+
if (name in source || isForced) {
|
|
37
|
+
const original = source[name]
|
|
38
|
+
const wrapped = replacement(original)
|
|
39
|
+
if (isFunction(wrapped)) {
|
|
40
|
+
source[name] = wrapped
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 格式化对象(针对数字类型属性)
|
|
47
|
+
* 小数位数保留最多两位、空值赋 undefined
|
|
48
|
+
* @param source 源对象
|
|
49
|
+
*/
|
|
50
|
+
export function normalizeObj(source: AnyObj) {
|
|
51
|
+
Object.keys(source).forEach(p => {
|
|
52
|
+
const v = source[p]
|
|
53
|
+
if (isNumber(v)) {
|
|
54
|
+
source[p] = v === 0 ? undefined : parseFloat(v.toFixed(2))
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
return source
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 获取当前页面的url
|
|
62
|
+
* @returns 当前页面的url
|
|
63
|
+
*/
|
|
64
|
+
export function getLocationHref(): string {
|
|
65
|
+
if (typeof document === 'undefined' || document.location == null) return ''
|
|
66
|
+
return document.location.href
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 获取当前的时间戳
|
|
71
|
+
* @returns 当前的时间戳
|
|
72
|
+
*/
|
|
73
|
+
export function getTimestamp(): number {
|
|
74
|
+
return Date.now()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 函数节流
|
|
79
|
+
* @param fn 需要节流的函数
|
|
80
|
+
* @param delay 节流的时间间隔
|
|
81
|
+
* @param runFirst 是否需要第一个函数立即执行 (每次)
|
|
82
|
+
* @returns 返回一个包含节流功能的函数
|
|
83
|
+
*/
|
|
84
|
+
export function throttle(func: AnyFun, wait: number, runFirst = false) {
|
|
85
|
+
let timer: NodeJS.Timeout | null = null
|
|
86
|
+
let lastArgs: any[]
|
|
87
|
+
|
|
88
|
+
return function (this: any, ...args: any[]) {
|
|
89
|
+
lastArgs = args
|
|
90
|
+
|
|
91
|
+
if (timer === null) {
|
|
92
|
+
if (runFirst) {
|
|
93
|
+
func.apply(this, lastArgs)
|
|
94
|
+
}
|
|
95
|
+
timer = setTimeout(() => {
|
|
96
|
+
timer = null
|
|
97
|
+
func.apply(this, lastArgs)
|
|
98
|
+
}, wait)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 函数防抖
|
|
105
|
+
* @param func 需要防抖的函数
|
|
106
|
+
* @param wait 防抖的时间间隔
|
|
107
|
+
* @param runFirst 是否需要第一个函数立即执行
|
|
108
|
+
* @returns 返回一个包含防抖功能的函数
|
|
109
|
+
*/
|
|
110
|
+
export function debounce(func: AnyFun, wait: number, runFirst = false) {
|
|
111
|
+
let timer: NodeJS.Timeout | null = null
|
|
112
|
+
|
|
113
|
+
return function (this: any, ...arg: any[]) {
|
|
114
|
+
if (runFirst) {
|
|
115
|
+
func.call(this, ...arg)
|
|
116
|
+
runFirst = false
|
|
117
|
+
}
|
|
118
|
+
if (timer) clearTimeout(timer)
|
|
119
|
+
timer = setTimeout(() => {
|
|
120
|
+
func.call(this, ...arg)
|
|
121
|
+
}, wait)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 将数组内对象以对象内的属性分类
|
|
127
|
+
* @param arr 数组源 - 格式为 [{}, {}...]
|
|
128
|
+
* @param pop 是否需要在遍历后清除源数组内的数据
|
|
129
|
+
* @param keys 需要匹配的属性名
|
|
130
|
+
*/
|
|
131
|
+
export function groupArray<T, K extends keyof T>(
|
|
132
|
+
arr: T[],
|
|
133
|
+
...keys: K[]
|
|
134
|
+
): T[][] {
|
|
135
|
+
const groups = new Map<string, T[]>()
|
|
136
|
+
for (const obj of arr) {
|
|
137
|
+
const key = keys
|
|
138
|
+
.filter(k => obj[k])
|
|
139
|
+
.map(k => obj[k])
|
|
140
|
+
.join(':')
|
|
141
|
+
if (!groups.has(key)) {
|
|
142
|
+
groups.set(key, [])
|
|
143
|
+
}
|
|
144
|
+
groups.get(key)!.push(obj)
|
|
145
|
+
}
|
|
146
|
+
return Array.from(groups.values())
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 深度合并对象
|
|
151
|
+
*/
|
|
152
|
+
export function deepAssign<T>(target: AnyObj, ...sources: AnyObj[]) {
|
|
153
|
+
sources.forEach(source => {
|
|
154
|
+
for (const key in source) {
|
|
155
|
+
if (source[key] !== null && isRegExp(source[key])) {
|
|
156
|
+
target[key] = source[key]
|
|
157
|
+
} else if (source[key] !== null && typeof source[key] === 'object') {
|
|
158
|
+
// 如果当前 key 对应的值是一个对象或数组,则进行递归
|
|
159
|
+
target[key] = deepAssign(
|
|
160
|
+
target[key] || (isArray(source[key]) ? [] : {}),
|
|
161
|
+
source[key]
|
|
162
|
+
)
|
|
163
|
+
} else {
|
|
164
|
+
// 如果当前 key 对应的值是基本类型数据,则直接赋值
|
|
165
|
+
target[key] = source[key]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
return target as T
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 验证调用sdk暴露的方法之前是否初始化
|
|
174
|
+
* @param methodsName 方法名
|
|
175
|
+
* @returns 是否通过验证
|
|
176
|
+
*/
|
|
177
|
+
export function validateMethods(methodsName: string): boolean {
|
|
178
|
+
if (!isInit()) {
|
|
179
|
+
logError(`${methodsName} 需要在SDK初始化之后使用`)
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
return true
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 判断入参类型
|
|
187
|
+
* @param target 任意入参
|
|
188
|
+
* @returns 类型
|
|
189
|
+
*/
|
|
190
|
+
export function typeofAny(target: any): string {
|
|
191
|
+
return Object.prototype.toString.call(target).slice(8, -1).toLowerCase()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 判断对象中是否包含该属性
|
|
196
|
+
* @param key 键
|
|
197
|
+
* @param object 对象
|
|
198
|
+
* @returns 是否包含
|
|
199
|
+
*/
|
|
200
|
+
export function isValidKey(
|
|
201
|
+
key: string | number | symbol,
|
|
202
|
+
object: object
|
|
203
|
+
): key is keyof typeof object {
|
|
204
|
+
return key in object
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 随机概率通过
|
|
209
|
+
* @param randow 设定比例,例如 0.7 代表 70%的概率通过
|
|
210
|
+
* @returns 是否通过
|
|
211
|
+
*/
|
|
212
|
+
export function randomBoolean(randow: number) {
|
|
213
|
+
return Math.random() <= randow
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 补全字符
|
|
218
|
+
* @param {*} num 初始值
|
|
219
|
+
* @param {*} len 需要补全的位数
|
|
220
|
+
* @param {*} placeholder 补全的值
|
|
221
|
+
* @returns 补全后的值
|
|
222
|
+
*/
|
|
223
|
+
export function pad(num: number, len: number, placeholder = '0') {
|
|
224
|
+
const str = String(num)
|
|
225
|
+
if (str.length < len) {
|
|
226
|
+
let result = str
|
|
227
|
+
for (let i = 0; i < len - str.length; i += 1) {
|
|
228
|
+
result = placeholder + result
|
|
229
|
+
}
|
|
230
|
+
return result
|
|
231
|
+
}
|
|
232
|
+
return str
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function getBaseUrl(url: string) {
|
|
236
|
+
return url.split('?')[0]
|
|
237
|
+
// // 正则表达式匹配不包含查询字符串的URL部分
|
|
238
|
+
// const regex = /^(?:https?:\/\/)?[^/?]+(?:\/[^?]*)?(?=\?.*)?/
|
|
239
|
+
// const match = url.match(regex)
|
|
240
|
+
|
|
241
|
+
// // 如果匹配成功,则返回匹配的结果,否则返回原URL
|
|
242
|
+
// return match ? match[0] : url
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 获取一个随机字符串
|
|
247
|
+
*/
|
|
248
|
+
export function uuid() {
|
|
249
|
+
const date = new Date()
|
|
250
|
+
|
|
251
|
+
// yyyy-MM-dd的16进制表示,7位数字
|
|
252
|
+
const hexDate = parseInt(
|
|
253
|
+
`${date.getFullYear()}${pad(date.getMonth() + 1, 2)}${pad(
|
|
254
|
+
date.getDate(),
|
|
255
|
+
2
|
|
256
|
+
)}`,
|
|
257
|
+
10
|
|
258
|
+
).toString(16)
|
|
259
|
+
|
|
260
|
+
// hh-mm-ss-ms的16进制表示,最大也是7位
|
|
261
|
+
const hexTime = parseInt(
|
|
262
|
+
`${pad(date.getHours(), 2)}${pad(date.getMinutes(), 2)}${pad(
|
|
263
|
+
date.getSeconds(),
|
|
264
|
+
2
|
|
265
|
+
)}${pad(date.getMilliseconds(), 3)}`,
|
|
266
|
+
10
|
|
267
|
+
).toString(16)
|
|
268
|
+
|
|
269
|
+
// 第8位数字表示后面的time字符串的长度
|
|
270
|
+
let guid = hexDate + hexTime.length + hexTime
|
|
271
|
+
|
|
272
|
+
// 补充随机数,补足32位的16进制数
|
|
273
|
+
while (guid.length < 32) {
|
|
274
|
+
guid += Math.floor(Math.random() * 16).toString(16)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 分为三段,前两段包含时间戳信息
|
|
278
|
+
return `${guid.slice(0, 8)}-${guid.slice(8, 16)}-${guid.slice(16)}`
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 获取cookie中目标name的值
|
|
283
|
+
* @param name cookie名
|
|
284
|
+
* @returns
|
|
285
|
+
*/
|
|
286
|
+
export function getCookieByName(name: string) {
|
|
287
|
+
const result = document.cookie.match(new RegExp(`${name}=([^;]+)(;|$)`))
|
|
288
|
+
return result ? result[1] : undefined
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 发送数据方式 - navigator.sendBeacon
|
|
293
|
+
*/
|
|
294
|
+
export function sendByBeacon(url: string, data: any) {
|
|
295
|
+
return navigator.sendBeacon(url, JSON.stringify(data))
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export const sendReaconImageList: any[] = []
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 发送数据方式 - image
|
|
302
|
+
*/
|
|
303
|
+
export function sendByImage(url: string, data: any): Promise<void> {
|
|
304
|
+
return new Promise(resolve => {
|
|
305
|
+
const beacon = new Image()
|
|
306
|
+
beacon.src = `${url}?v=${encodeURIComponent(JSON.stringify(data))}`
|
|
307
|
+
sendReaconImageList.push(beacon)
|
|
308
|
+
beacon.onload = () => {
|
|
309
|
+
// 发送成功
|
|
310
|
+
resolve()
|
|
311
|
+
}
|
|
312
|
+
beacon.onerror = function () {
|
|
313
|
+
// 发送失败
|
|
314
|
+
resolve()
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* 发送数据方式 - xml
|
|
321
|
+
*/
|
|
322
|
+
export function sendByXML(
|
|
323
|
+
url: string,
|
|
324
|
+
data: any,
|
|
325
|
+
timeout = 5000
|
|
326
|
+
): Promise<void> {
|
|
327
|
+
return new Promise((resolve, reject) => {
|
|
328
|
+
const xhr = new XMLHttpRequest()
|
|
329
|
+
xhr.open('post', url)
|
|
330
|
+
xhr.setRequestHeader('content-type', 'application/json')
|
|
331
|
+
xhr.timeout = timeout
|
|
332
|
+
xhr.send(JSON.stringify(data))
|
|
333
|
+
xhr.onreadystatechange = function () {
|
|
334
|
+
if (xhr.readyState === 4) {
|
|
335
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
336
|
+
resolve()
|
|
337
|
+
} else {
|
|
338
|
+
reject(new Error('@web-tracing XMLHttpRequest error: ' + xhr.status))
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
xhr.ontimeout = function () {
|
|
343
|
+
reject(new Error('@web-tracing XMLHttpRequest timeout'))
|
|
344
|
+
}
|
|
345
|
+
xhr.onerror = function () {
|
|
346
|
+
reject(new Error('@web-tracing XMLHttpRequest network error'))
|
|
347
|
+
}
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* 批量执行方法
|
|
353
|
+
* @param funList 方法数组
|
|
354
|
+
* @param through 是否将第一次参数贯穿全部方法
|
|
355
|
+
* @param args 额外参数
|
|
356
|
+
* @returns
|
|
357
|
+
*/
|
|
358
|
+
export function executeFunctions(
|
|
359
|
+
funList: AnyFun[],
|
|
360
|
+
through: boolean,
|
|
361
|
+
args: any
|
|
362
|
+
): any {
|
|
363
|
+
if (funList.length === 0) return args
|
|
364
|
+
|
|
365
|
+
let result: any = undefined
|
|
366
|
+
for (let i = 0; i < funList.length; i++) {
|
|
367
|
+
const func = funList[i]
|
|
368
|
+
if (i === 0 || through) {
|
|
369
|
+
result = func(args)
|
|
370
|
+
} else {
|
|
371
|
+
result = func(result)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return result
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* 将未知参数转换为数组格式
|
|
379
|
+
* @param target
|
|
380
|
+
*/
|
|
381
|
+
export function unKnowToArray<T>(target: T[] | T): T[] {
|
|
382
|
+
return (isArray(target) ? target : [target]) as T[]
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const arrayMap =
|
|
386
|
+
Array.prototype.map ||
|
|
387
|
+
function polyfillMap(this: any, fn) {
|
|
388
|
+
const result = []
|
|
389
|
+
for (let i = 0; i < this.length; i += 1) {
|
|
390
|
+
result.push(fn(this[i], i, this))
|
|
391
|
+
}
|
|
392
|
+
return result
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* map方法
|
|
397
|
+
* @param arr 源数组
|
|
398
|
+
* @param fn 条件函数
|
|
399
|
+
* @returns
|
|
400
|
+
*/
|
|
401
|
+
export function map(arr: any[], fn: AnyFun) {
|
|
402
|
+
return arrayMap.call(arr, fn)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const arrayFilter =
|
|
406
|
+
Array.prototype.filter ||
|
|
407
|
+
function filterPolyfill(this: any, fn: AnyFun) {
|
|
408
|
+
const result = []
|
|
409
|
+
for (let i = 0; i < this.length; i += 1) {
|
|
410
|
+
if (fn(this[i], i, this)) {
|
|
411
|
+
result.push(this[i])
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return result
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* filter方法
|
|
419
|
+
* @param arr 源数组
|
|
420
|
+
* @param fn 条件函数
|
|
421
|
+
*/
|
|
422
|
+
export function filter(arr: any[], fn: AnyFun) {
|
|
423
|
+
return arrayFilter.call(arr, fn)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const arrayFind =
|
|
427
|
+
Array.prototype.find ||
|
|
428
|
+
function findPolyfill(this: any, fn: AnyFun) {
|
|
429
|
+
for (let i = 0; i < this.length; i += 1) {
|
|
430
|
+
if (fn(this[i], i, this)) {
|
|
431
|
+
return this[i]
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return undefined
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* find方法
|
|
439
|
+
* @param arr 源数组
|
|
440
|
+
* @param fn 条件函数
|
|
441
|
+
*/
|
|
442
|
+
export function find(arr: any[], fn: AnyFun) {
|
|
443
|
+
return arrayFind.call(arr, fn)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* 去除头部或者尾部的空格
|
|
448
|
+
* @param str 需要去除的字符串
|
|
449
|
+
* @returns 去除后的字符串
|
|
450
|
+
*/
|
|
451
|
+
export function trim(str = '') {
|
|
452
|
+
return str.replace(/(^\s+)|(\s+$)/, '')
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* 可以理解为异步执行
|
|
457
|
+
* requestIdleCallback 是浏览器空闲时会自动执行内部函数
|
|
458
|
+
* requestAnimationFrame 是浏览器必须执行的
|
|
459
|
+
* 关于 requestIdleCallback 和 requestAnimationFrame 可以参考 https://www.cnblogs.com/cangqinglang/p/13877078.html
|
|
460
|
+
*/
|
|
461
|
+
export const nextTime =
|
|
462
|
+
window.requestIdleCallback ||
|
|
463
|
+
window.requestAnimationFrame ||
|
|
464
|
+
(callback => setTimeout(callback, 17))
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* 取消异步执行
|
|
468
|
+
*/
|
|
469
|
+
export const cancelNextTime =
|
|
470
|
+
window.cancelIdleCallback || window.cancelAnimationFrame || clearTimeout
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* 判断对象是否超过指定kb大小
|
|
474
|
+
* @param object 源对象
|
|
475
|
+
* @param limitInKB 最大kb
|
|
476
|
+
*/
|
|
477
|
+
export function isObjectOverSizeLimit(
|
|
478
|
+
object: object,
|
|
479
|
+
limitInKB: number
|
|
480
|
+
): boolean {
|
|
481
|
+
const serializedObject = JSON.stringify(object)
|
|
482
|
+
const sizeInBytes = new TextEncoder().encode(serializedObject).length
|
|
483
|
+
const sizeInKB = sizeInBytes / 1024
|
|
484
|
+
return sizeInKB > limitInKB
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* 获取url地址上的参数
|
|
489
|
+
* @param url 请求url
|
|
490
|
+
* @returns 参数对象
|
|
491
|
+
*/
|
|
492
|
+
export function parseGetParams(url: string): AnyObj<string> {
|
|
493
|
+
if (!url) return {}
|
|
494
|
+
const params: AnyObj<string> = {}
|
|
495
|
+
const query = url.split('?')[1]
|
|
496
|
+
|
|
497
|
+
if (query) {
|
|
498
|
+
const pairs = query.split('&')
|
|
499
|
+
for (const pair of pairs) {
|
|
500
|
+
const [key, value] = pair.split('=')
|
|
501
|
+
params[decodeURIComponent(key)] = decodeURIComponent(value)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return params
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* 深拷贝
|
|
509
|
+
* 兼容函数,对象,相互引用场景
|
|
510
|
+
* @param target 需要深拷贝的原对象
|
|
511
|
+
* @return 深拷贝后的对象
|
|
512
|
+
*/
|
|
513
|
+
export function deepCopy<T>(target: T, map = new Map()) {
|
|
514
|
+
if (target !== null && typeof target === 'object') {
|
|
515
|
+
let res = map.get(target)
|
|
516
|
+
if (res) return res
|
|
517
|
+
if (target instanceof Array) {
|
|
518
|
+
res = []
|
|
519
|
+
map.set(target, res)
|
|
520
|
+
target.forEach((item, index) => {
|
|
521
|
+
res[index] = deepCopy(item, map)
|
|
522
|
+
})
|
|
523
|
+
} else {
|
|
524
|
+
res = {}
|
|
525
|
+
map.set(target, res)
|
|
526
|
+
Object.keys(target).forEach(key => {
|
|
527
|
+
if (isValidKey(key, target)) {
|
|
528
|
+
res[key] = deepCopy(target[key], map)
|
|
529
|
+
}
|
|
530
|
+
})
|
|
531
|
+
}
|
|
532
|
+
return res
|
|
533
|
+
}
|
|
534
|
+
return target
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* 移除事件监听器
|
|
539
|
+
* @param target 对象
|
|
540
|
+
* @param eventName 事件名称
|
|
541
|
+
* @param handler 回调函数
|
|
542
|
+
* @param opitons
|
|
543
|
+
*/
|
|
544
|
+
export function off(
|
|
545
|
+
target: Window | Document,
|
|
546
|
+
eventName: string,
|
|
547
|
+
handler: AnyFun,
|
|
548
|
+
opitons = false
|
|
549
|
+
): void {
|
|
550
|
+
target.removeEventListener(eventName, handler, opitons)
|
|
551
|
+
}
|
package/src/utils/is.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
function isType(type: any) {
|
|
2
|
+
return function (value: any): boolean {
|
|
3
|
+
return Object.prototype.toString.call(value) === `[object ${type}]`
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const isRegExp = isType('RegExp')
|
|
8
|
+
export const isNumber = isType('Number')
|
|
9
|
+
export const isString = isType('String')
|
|
10
|
+
export const isBoolean = isType('Boolean')
|
|
11
|
+
export const isNull = isType('Null')
|
|
12
|
+
export const isUndefined = isType('Undefined')
|
|
13
|
+
export const isSymbol = isType('Symbol')
|
|
14
|
+
export const isFunction = isType('Function')
|
|
15
|
+
export const isObject = isType('Object')
|
|
16
|
+
export const isArray = isType('Array')
|
|
17
|
+
export const isProcess = isType('process')
|
|
18
|
+
export const isWindow = isType('Window')
|
|
19
|
+
export const isFlase = (val: any) => {
|
|
20
|
+
return isBoolean(val) && String(val) === 'false'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 检测变量类型
|
|
25
|
+
* @param type
|
|
26
|
+
*/
|
|
27
|
+
export const variableTypeDetection = {
|
|
28
|
+
isNumber: isType('Number'),
|
|
29
|
+
isString: isType('String'),
|
|
30
|
+
isBoolean: isType('Boolean'),
|
|
31
|
+
isNull: isType('Null'),
|
|
32
|
+
isUndefined: isType('Undefined'),
|
|
33
|
+
isSymbol: isType('Symbol'),
|
|
34
|
+
isFunction: isType('Function'),
|
|
35
|
+
isObject: isType('Object'),
|
|
36
|
+
isArray: isType('Array'),
|
|
37
|
+
isProcess: isType('process'),
|
|
38
|
+
isWindow: isType('Window')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 判断值是否为错误对象
|
|
43
|
+
*/
|
|
44
|
+
export function isError(error: Error): boolean {
|
|
45
|
+
switch (Object.prototype.toString.call(error)) {
|
|
46
|
+
case '[object Error]':
|
|
47
|
+
return true
|
|
48
|
+
case '[object Exception]':
|
|
49
|
+
return true
|
|
50
|
+
case '[object DOMException]':
|
|
51
|
+
return true
|
|
52
|
+
default:
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 判断值是否为空对象
|
|
59
|
+
*/
|
|
60
|
+
export function isEmptyObject(obj: object): boolean {
|
|
61
|
+
return isObject(obj) && Object.keys(obj).length === 0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 判断值是否为空 ['', undefined, null]
|
|
66
|
+
*/
|
|
67
|
+
export function isEmpty(wat: any): boolean {
|
|
68
|
+
return (
|
|
69
|
+
(isString(wat) && wat.trim() === '') || wat === undefined || wat === null
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 判断值与目标对象关系
|
|
75
|
+
*/
|
|
76
|
+
export function isExistProperty(obj: object, key: string): boolean {
|
|
77
|
+
return Object.prototype.hasOwnProperty.call(obj, key)
|
|
78
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { deepAssign } from '../utils'
|
|
2
|
+
import { SendData } from '../types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 操作 localstorage 的工具类
|
|
6
|
+
*/
|
|
7
|
+
export class LocalStorageUtil {
|
|
8
|
+
static maxSize = 5 * 1024 * 1000 // 5Mb
|
|
9
|
+
|
|
10
|
+
static getItem(key: string): any {
|
|
11
|
+
const value = localStorage.getItem(key)
|
|
12
|
+
if (value) {
|
|
13
|
+
return JSON.parse(value)
|
|
14
|
+
}
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static setItem(key: string, value: any): void {
|
|
19
|
+
localStorage.setItem(key, JSON.stringify(value))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static removeItem(key: string): void {
|
|
23
|
+
localStorage.removeItem(key)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static getSize(): number {
|
|
27
|
+
let size = 0
|
|
28
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
29
|
+
const key = localStorage.key(i)
|
|
30
|
+
if (key) {
|
|
31
|
+
const value = localStorage.getItem(key)
|
|
32
|
+
if (value) {
|
|
33
|
+
size += this.getBytes(value)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return size
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* sendData专属存储
|
|
42
|
+
* 特殊性:
|
|
43
|
+
* 1. 每次存储检查最大容量(5M),如超过则不再继续存并通知外部
|
|
44
|
+
* 2. 按照特定结构去拼接
|
|
45
|
+
*
|
|
46
|
+
* 注意:刷新页面测试会加入卸载事件,这在控制台是看不到的
|
|
47
|
+
*/
|
|
48
|
+
static setSendDataItem(key: string, value: SendData) {
|
|
49
|
+
if (this.getSize() >= this.maxSize) return false
|
|
50
|
+
|
|
51
|
+
const localItem = (this.getItem(key) || {
|
|
52
|
+
baseInfo: {},
|
|
53
|
+
eventInfo: []
|
|
54
|
+
}) as SendData
|
|
55
|
+
|
|
56
|
+
const newItem: SendData = {
|
|
57
|
+
baseInfo: deepAssign(localItem.baseInfo, value.baseInfo),
|
|
58
|
+
eventInfo: localItem.eventInfo.concat(value.eventInfo)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.setItem(key, newItem)
|
|
62
|
+
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private static getBytes(str: string): number {
|
|
67
|
+
const blob = new Blob([str])
|
|
68
|
+
return blob.size
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话控制,此会话只和具体的浏览器相关,与业务无关,和业务意义上的登录态没有任何关联,只是用于追踪同一个浏览器上访问页面的动作
|
|
3
|
+
*/
|
|
4
|
+
import { getCookieByName, uuid } from './index'
|
|
5
|
+
import { SURVIVIE_MILLI_SECONDS, SESSION_KEY } from '../common'
|
|
6
|
+
import { getTimestamp } from '../utils'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 刷新会话存续期
|
|
10
|
+
*/
|
|
11
|
+
function refreshSession() {
|
|
12
|
+
const id = getCookieByName(SESSION_KEY) || `s_${uuid()}`
|
|
13
|
+
const expires = new Date(getTimestamp() + SURVIVIE_MILLI_SECONDS)
|
|
14
|
+
document.cookie = `${SESSION_KEY}=${id};path=/;max-age=1800;expires=${expires.toUTCString()}`
|
|
15
|
+
return id
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 获取sessionid
|
|
20
|
+
*/
|
|
21
|
+
function getSessionId() {
|
|
22
|
+
return getCookieByName(SESSION_KEY) || refreshSession()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
refreshSession() // 初始化
|
|
26
|
+
|
|
27
|
+
export { getSessionId, refreshSession }
|