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,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
+ }
@@ -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 }