@rk911/tracker-sdk 1.6.4

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/index.js ADDED
@@ -0,0 +1,403 @@
1
+ import { Tracker } from './core/tracker.js'
2
+ import {
3
+ createDadianProxy,
4
+ createImpressionDirective,
5
+ createPageStayMixin,
6
+ } from './core/compat.js'
7
+ import { WebAdapter } from './platforms/web.js'
8
+ import { WeappAdapter } from './platforms/weapp.js'
9
+ import { AppAdapter } from './platforms/app.js'
10
+ import { getProjectRules } from './customize/index.js'
11
+
12
+ // 插件注册表
13
+ import { PageViewPlugin } from './plugins/page-view.js'
14
+ import { ClickPlugin } from './plugins/click.js'
15
+ import { PageStayPlugin } from './plugins/page-stay.js'
16
+ import { ExposurePlugin } from './plugins/exposure.js'
17
+ import { JsErrorPlugin } from './plugins/js-error.js'
18
+ import { ApiWatchPlugin } from './plugins/api-watch.js'
19
+ import { AppLifecyclePlugin } from './plugins/app-lifecycle.js'
20
+ import { PerformancePlugin } from './plugins/performance.js'
21
+ import { ScrollDepthPlugin } from './plugins/scroll-depth.js'
22
+
23
+ const PLUGIN_MAP = {
24
+ 'page-view': PageViewPlugin,
25
+ click: ClickPlugin,
26
+ 'page-stay': PageStayPlugin,
27
+ exposure: ExposurePlugin,
28
+ 'js-error': JsErrorPlugin,
29
+ performance: PerformancePlugin,
30
+ 'scroll-depth': ScrollDepthPlugin,
31
+ 'api-watch': ApiWatchPlugin,
32
+ 'app-lifecycle': AppLifecyclePlugin,
33
+ }
34
+
35
+ // 单例
36
+ let trackerInstance = null
37
+
38
+ /**
39
+ * 自动检测当前运行平台
40
+ */
41
+ function detectPlatform() {
42
+ try {
43
+ if (
44
+ typeof wx !== 'undefined' &&
45
+ typeof wx.getSystemInfoSync === 'function' &&
46
+ typeof document === 'undefined'
47
+ ) {
48
+ return 'weapp'
49
+ }
50
+ } catch (e) {}
51
+
52
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
53
+ return 'web'
54
+ }
55
+
56
+ return 'web'
57
+ }
58
+
59
+ /**
60
+ * 创建平台适配器
61
+ */
62
+ function createAdapter(platform, config) {
63
+ switch (platform) {
64
+ case 'weapp':
65
+ return new WeappAdapter(config)
66
+ case 'app':
67
+ return new AppAdapter(config)
68
+ default:
69
+ return new WebAdapter(config)
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 创建 Tracker 实例
75
+ */
76
+ export function createTracker(config) {
77
+ if (trackerInstance) {
78
+ trackerInstance.destroy()
79
+ }
80
+
81
+ var platform = config.platform || detectPlatform()
82
+ config.platform = platform
83
+
84
+ // 自动加载项目定制规则(未显式传入时按 appId 匹配)
85
+ if (!config.projectRules && config.appId) {
86
+ config.projectRules = getProjectRules(config.appId)
87
+ }
88
+
89
+ var adapter = createAdapter(platform, config)
90
+ trackerInstance = new Tracker(config, adapter)
91
+
92
+ // 注册插件
93
+ var pluginNames = config.plugins || []
94
+ pluginNames.forEach(function (pluginName) {
95
+ var PluginClass = null
96
+
97
+ if (typeof pluginName === 'string') {
98
+ PluginClass = PLUGIN_MAP[pluginName]
99
+ } else if (typeof pluginName === 'function') {
100
+ PluginClass = pluginName
101
+ } else if (pluginName && typeof pluginName.install === 'function') {
102
+ // 直接传入插件实例
103
+ trackerInstance.use(pluginName)
104
+ return
105
+ }
106
+
107
+ if (PluginClass) {
108
+ trackerInstance.use(new PluginClass())
109
+ }
110
+ })
111
+
112
+ return trackerInstance
113
+ }
114
+
115
+ /**
116
+ * 获取当前 Tracker 单例
117
+ */
118
+ export function getTracker() {
119
+ return trackerInstance
120
+ }
121
+
122
+ /**
123
+ * 设备注册 — 无 deviceId 时调用后端接口获取,失败重试最多 3 次
124
+ */
125
+ function registerDeviceIfNeeded(tracker) {
126
+ var adapter = tracker.getAdapter()
127
+ var config = tracker.getConfig()
128
+ var existingId = adapter.getStorage('deviceId')
129
+ if (existingId) {
130
+ tracker.setDeviceId(existingId)
131
+ return
132
+ }
133
+
134
+ // [PROJECT-ADAPT] appId 默认值
135
+ // 店长端 appId=1, apph5 appId=12/13, 小程序 appId=18
136
+ // 各项目在初始化时传入自己的 appId,此处 ||1 为兜底
137
+ var deviceInfo = { appId: Number(config.appId) || 1, id: '' }
138
+ if (typeof adapter.getDetailedDeviceInfo === 'function') {
139
+ Object.assign(deviceInfo, adapter.getDetailedDeviceInfo())
140
+ }
141
+
142
+ // [PROJECT-ADAPT] 设备注册请求头
143
+ // 当前网关要求 appId/deviceId 作为自定义 header
144
+ var header = { 'content-type': 'application/json', appId: String(config.appId), deviceId: '' }
145
+ if (typeof config.getAuthHeaders === 'function') {
146
+ Object.assign(header, config.getAuthHeaders() || {})
147
+ }
148
+
149
+ var registerUrl = config.dadianUrl + (config.deviceRegister.url || '/api/point/device/add')
150
+ var maxRetries = 3
151
+
152
+ function attempt(retryCount) {
153
+ adapter
154
+ .request({ method: 'POST', url: registerUrl, data: deviceInfo, header: header })
155
+ .then(function (res) {
156
+ if (res && res.data && res.data.code === 0 && res.data.data) {
157
+ adapter.setStorage('deviceId', res.data.data)
158
+ tracker.setDeviceId(res.data.data)
159
+ if (config.debug) {
160
+ console.log('[TrackerSDK] device registered:', res.data.data)
161
+ }
162
+ } else if (retryCount < maxRetries) {
163
+ var delay = 1000 * Math.pow(2, retryCount)
164
+ setTimeout(function () { attempt(retryCount + 1) }, delay)
165
+ if (config.debug) {
166
+ console.warn('[TrackerSDK] device register bad response, retry ' + (retryCount + 1) + '/' + maxRetries + ' in ' + delay + 'ms')
167
+ }
168
+ }
169
+ })
170
+ .catch(function (err) {
171
+ if (retryCount < maxRetries) {
172
+ var delay = 1000 * Math.pow(2, retryCount)
173
+ setTimeout(function () { attempt(retryCount + 1) }, delay)
174
+ if (config.debug) {
175
+ console.warn('[TrackerSDK] device register failed, retry ' + (retryCount + 1) + '/' + maxRetries + ' in ' + delay + 'ms')
176
+ }
177
+ } else if (config.debug) {
178
+ console.warn('[TrackerSDK] device register failed after ' + maxRetries + ' retries:', err)
179
+ }
180
+ })
181
+ }
182
+
183
+ attempt(0)
184
+ }
185
+
186
+ /**
187
+ * 自动检测 userId:每次从 localStorage/cookie 实时读取
188
+ * 不依赖内存缓存,确保清除存储后不会残留旧用户、切换账号后能及时更新
189
+ */
190
+ function _ensureUserId(tracker) {
191
+ var adapter = tracker.getAdapter()
192
+ // 从源头实时读取(登录页写入了 uni.setStorageSync('userId', ...))
193
+ var uid = adapter.getStorage('userId')
194
+ // 降级:从 cookie 读(rkfeb-userId)
195
+ if (!uid && typeof document !== 'undefined') {
196
+ var match = document.cookie.match(/(?:^|;\s*)rkfeb-userId=([^;]+)/)
197
+ if (match) uid = decodeURIComponent(match[1])
198
+ }
199
+ var currentUid = tracker.getSession().getUserId()
200
+ if (uid && uid !== currentUid) {
201
+ tracker.setUser(uid)
202
+ } else if (!uid && currentUid) {
203
+ // 源头已清空(退出登录/清除缓存),同步清除内存中的旧值
204
+ tracker.setUser(null)
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Vue.use 插件入口
210
+ *
211
+ * 用法:
212
+ * import TrackerSDK from './tracker-sdk/index.js'
213
+ * Vue.use(TrackerSDK, {
214
+ * appId: '1',
215
+ * dadianUrl: 'https://state.renruikeji.cn',
216
+ * platform: 'web',
217
+ * tokenKey: 'token',
218
+ * plugins: ['page-view', 'click', 'page-stay', 'js-error', 'app-lifecycle', 'api-watch', 'scroll-depth'],
219
+ * legacy: true,
220
+ * getAuthHeaders: () => ({ Authorization: orgToken, token: orgToken }),
221
+ * })
222
+ */
223
+ function install(Vue, options) {
224
+ if (install._installed) return
225
+ install._installed = true
226
+
227
+ var tracker = createTracker(options || {})
228
+
229
+ // 0. 暴露 tracker 实例到 window(供验证脚本访问)
230
+ if (typeof window !== 'undefined') {
231
+ window.__tracker = tracker
232
+ }
233
+
234
+ // 0.1 设备注册(无 deviceId 时自动注册)
235
+ registerDeviceIfNeeded(tracker)
236
+
237
+ // 0.2 自动检测 userId(从 Cookie 或 storage 中读取已登录用户)
238
+ _ensureUserId(tracker)
239
+
240
+ // 0.3 注册 userId 刷新钩子:每次 flush 前从 storage 实时读取,
241
+ // 避免自动事件(page_view/click)在 userId 更新前用旧值发出
242
+ tracker.getQueue().setRefreshUserId(function () {
243
+ _ensureUserId(tracker)
244
+ })
245
+
246
+ // 1. 注册 $dadian(兼容现有所有业务调用)
247
+ var _rawDadian = createDadianProxy(tracker)
248
+ Vue.prototype.$dadian = function (frontUrl, postParam) {
249
+ _ensureUserId(tracker)
250
+ return _rawDadian(frontUrl, postParam)
251
+ }
252
+
253
+ // 2. 注册 $track(统一的上报方法,替代 $dadian)
254
+ // 行为与 $dadian 完全一致:第一个参数直接作为 frontUrl,不做任何路由拼接
255
+ Vue.prototype.$track = function (frontUrl, postParam) {
256
+ _ensureUserId(tracker)
257
+
258
+ // postParam 处理(与 $dadian 一致)
259
+ var postParamStr = ''
260
+ if (postParam && Object.keys(postParam).length > 0) {
261
+ var config = tracker.getConfig()
262
+ if (config.platform === 'weapp' && config.eventBus) {
263
+ var bus = config.eventBus
264
+ postParamStr = JSON.stringify(Object.assign({}, postParam, {
265
+ wxAppOnLaunch: bus.wxAppOnLaunch,
266
+ pageFrom: bus.pageFrom,
267
+ mallOrderExt: bus.mallOrderExt,
268
+ }))
269
+ } else {
270
+ postParamStr = JSON.stringify(postParam)
271
+ }
272
+ }
273
+
274
+ // 开关关(默认):旧接口也报一次(与 $dadian 一致)
275
+ var config = tracker.getConfig()
276
+ if (!config.useUnifiedReport) {
277
+ var header = { 'content-type': 'application/json' }
278
+ var hasLegacyAuth = false
279
+ if (typeof config.getAuthHeaders === 'function') {
280
+ var authHeaders = config.getAuthHeaders() || {}
281
+ if (authHeaders.Authorization || authHeaders.token || authHeaders['rsb-token']) {
282
+ hasLegacyAuth = true
283
+ Object.assign(header, authHeaders)
284
+ }
285
+ }
286
+ if (hasLegacyAuth) {
287
+ tracker.getQueue().pushLegacy(
288
+ config.dadianUrl + '/api/point/task/activeReport',
289
+ { frontUrl: frontUrl, postParam: postParamStr },
290
+ header
291
+ )
292
+ }
293
+ }
294
+
295
+ // SDK 事件入队(始终走)
296
+ tracker.track('custom', {
297
+ eventName: frontUrl || 'manual',
298
+ frontUrl: frontUrl,
299
+ postParam: postParamStr,
300
+ elementId: frontUrl,
301
+ })
302
+
303
+ // 项目定制:从 footer 导航类 frontUrl 中提取目标路由,
304
+ // 在 track() 之后更新 referrer 链(确保目标页事件能拿到正确来源页)
305
+ // 必须在 track 之后:否则当前事件的 referrer 会被提前污染
306
+ var rules = config.projectRules
307
+ if (rules && typeof rules.extractNavTarget === 'function') {
308
+ var navTarget = rules.extractNavTarget(null, frontUrl)
309
+ if (navTarget) {
310
+ tracker.updatePageRoute(navTarget)
311
+ }
312
+ }
313
+ }
314
+
315
+ // 3. 注册 $tracker 实例引用(含 resetSession 供登出时调用)
316
+ Vue.prototype.$tracker = tracker
317
+ Vue.prototype.$trackerResetSession = function () {
318
+ tracker.getSession().resetSession()
319
+ tracker.getQueue()._flushedEventIds.clear()
320
+ }
321
+
322
+ // 3.1 动态参数解析器快捷方式
323
+ Vue.prototype.$registerParam = function (name, fn) {
324
+ tracker.registerParam(name, fn)
325
+ }
326
+
327
+ // 4. 注册 v-impression 指令(兼容旧业务代码)
328
+ var adapter = tracker.getAdapter()
329
+ if (adapter.supportsIntersectionObserver()) {
330
+ Vue.directive('impression', createImpressionDirective(tracker))
331
+ }
332
+
333
+ // 5. 注册 v-track-exposure 指令(新的曝光指令,走自动采集链路)
334
+ var exposurePlugin = tracker.plugins.find(function (p) {
335
+ return p.name === 'exposure'
336
+ })
337
+ if (exposurePlugin && typeof exposurePlugin.createDirective === 'function') {
338
+ var trackExposureDirective = exposurePlugin.createDirective()
339
+ if (trackExposureDirective) {
340
+ Vue.directive('track-exposure', trackExposureDirective)
341
+ }
342
+ }
343
+
344
+ // 6. 注册页面停留 mixin
345
+ var hasPageStayPlugin = (options.plugins || []).some(function (p) {
346
+ return p === 'page-stay' || (p && p.name === 'page-stay')
347
+ })
348
+ if (hasPageStayPlugin) {
349
+ Vue.mixin(createPageStayMixin(tracker))
350
+ }
351
+
352
+ // 7. 注册 Vue 错误捕获(组件 render/lifecycle/watch 中的异常)
353
+ var jsErrorPlugin = tracker.plugins.find(function (p) {
354
+ return p.name === 'js-error'
355
+ })
356
+ if (jsErrorPlugin) {
357
+ var _prevErrorHandler = Vue.config.errorHandler
358
+ Vue.config.errorHandler = function (err, vm, info) {
359
+ jsErrorPlugin.handleVueError(err, vm, info)
360
+ if (_prevErrorHandler) _prevErrorHandler.call(this, err, vm, info)
361
+ }
362
+ }
363
+
364
+ // [PROJECT-ADAPT] 小程序生命周期注册
365
+ // H5 端通过 history API 监听路由变化,小程序端通过 onShow/onHide
366
+ if (tracker.getConfig().platform === 'weapp') {
367
+ var pageViewPlugin = tracker.plugins.find(function (p) {
368
+ return p.name === 'page-view'
369
+ })
370
+ var lifecyclePlugin = tracker.plugins.find(function (p) {
371
+ return p.name === 'app-lifecycle'
372
+ })
373
+
374
+ Vue.mixin({
375
+ onShow: function () {
376
+ if (pageViewPlugin) {
377
+ pageViewPlugin.trackPageShow()
378
+ }
379
+ if (lifecyclePlugin) {
380
+ lifecyclePlugin.trackAppShow()
381
+ }
382
+ },
383
+ onHide: function () {
384
+ if (lifecyclePlugin) {
385
+ lifecyclePlugin.trackAppHide()
386
+ }
387
+ },
388
+ })
389
+ }
390
+ }
391
+
392
+ // 默认导出 Vue 插件对象
393
+ export default {
394
+ install: install,
395
+ createTracker: createTracker,
396
+ getTracker: getTracker,
397
+ }
398
+
399
+ // 具名导出
400
+ export { Tracker } from './core/tracker.js'
401
+ export { WebAdapter } from './platforms/web.js'
402
+ export { WeappAdapter } from './platforms/weapp.js'
403
+ export { AppAdapter } from './platforms/app.js'
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@rk911/tracker-sdk",
3
+ "version": "1.6.4",
4
+ "description": "人康多端埋点 SDK — 自动化采集 + 手动埋点兼容",
5
+ "main": "index.js",
6
+ "module": "index.js",
7
+ "files": [
8
+ "index.js",
9
+ "core/",
10
+ "platforms/",
11
+ "plugins/",
12
+ "customize/"
13
+ ],
14
+ "keywords": ["tracker", "analytics", "uni-app", "埋点"],
15
+ "author": "rk-team",
16
+ "license": "MIT",
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "registry": "https://registry.npmjs.org/"
20
+ }
21
+ }
@@ -0,0 +1,36 @@
1
+ import { WebAdapter } from './web.js'
2
+
3
+ export class AppAdapter extends WebAdapter {
4
+ constructor(config) {
5
+ super(config)
6
+ }
7
+
8
+ getPlatformName() {
9
+ return 'app'
10
+ }
11
+
12
+ getToken() {
13
+ // APP WebView 内优先从 JSBridge 取 token,降级走 storage
14
+ try {
15
+ if (typeof window !== 'undefined' && window.NativeBridge && window.NativeBridge.getToken) {
16
+ const nativeToken = window.NativeBridge.getToken()
17
+ if (nativeToken) return nativeToken
18
+ }
19
+ } catch (e) {}
20
+ return this.getStorage(this.config.tokenKey || 'token')
21
+ }
22
+
23
+ getDeviceInfo() {
24
+ const baseInfo = super.getDeviceInfo()
25
+ // 尝试从 JSBridge 获取更多原生设备信息
26
+ try {
27
+ if (typeof window !== 'undefined' && window.NativeBridge && window.NativeBridge.getDeviceInfo) {
28
+ const nativeInfo = window.NativeBridge.getDeviceInfo()
29
+ if (nativeInfo) {
30
+ return { ...baseInfo, ...nativeInfo }
31
+ }
32
+ }
33
+ } catch (e) {}
34
+ return baseInfo
35
+ }
36
+ }
@@ -0,0 +1,218 @@
1
+ export class WeappAdapter {
2
+ constructor(config) {
3
+ this.config = config
4
+ this._sessionStore = new Map()
5
+ this._listeners = []
6
+ }
7
+
8
+ getPlatformName() {
9
+ return 'weapp'
10
+ }
11
+
12
+ // ---- 存储 ----
13
+
14
+ getStorage(key) {
15
+ try {
16
+ return uni.getStorageSync(key)
17
+ } catch (e) {
18
+ return null
19
+ }
20
+ }
21
+
22
+ setStorage(key, value) {
23
+ try {
24
+ uni.setStorageSync(key, value)
25
+ } catch (e) {}
26
+ }
27
+
28
+ removeStorage(key) {
29
+ try {
30
+ uni.removeStorageSync(key)
31
+ } catch (e) {}
32
+ }
33
+
34
+ // 小程序没有 sessionStorage,用内存 Map 模拟
35
+ getSessionStorage(key) {
36
+ return this._sessionStore.get(key) || null
37
+ }
38
+
39
+ setSessionStorage(key, value) {
40
+ this._sessionStore.set(key, value)
41
+ }
42
+
43
+ removeSessionStorage(key) {
44
+ this._sessionStore.delete(key)
45
+ }
46
+
47
+ // ---- 页面信息 ----
48
+
49
+ getCurrentPageRoute() {
50
+ try {
51
+ const pages = getCurrentPages()
52
+ if (pages.length > 0) {
53
+ return pages[pages.length - 1].route || ''
54
+ }
55
+ } catch (e) {}
56
+ return ''
57
+ }
58
+
59
+ getCurrentPagePath() {
60
+ return '/' + this.getCurrentPageRoute()
61
+ }
62
+
63
+ getToken() {
64
+ return this.getStorage(this.config.tokenKey || 'tokenV2')
65
+ }
66
+
67
+ // ---- 网络 ----
68
+
69
+ request(options) {
70
+ return new Promise((resolve, reject) => {
71
+ uni.request({
72
+ ...options,
73
+ success: (res) => resolve(res),
74
+ fail: (err) => reject(err),
75
+ })
76
+ })
77
+ }
78
+
79
+ sendBeacon(url, data) {
80
+ // 小程序没有 sendBeacon,降级为普通请求
81
+ try {
82
+ uni.request({
83
+ method: 'POST',
84
+ url: url,
85
+ data: data,
86
+ header: { 'content-type': 'application/json' },
87
+ })
88
+ } catch (e) {}
89
+ return true
90
+ }
91
+
92
+ // ---- 设备信息 ----
93
+
94
+ getDeviceInfo() {
95
+ try {
96
+ const info = uni.getSystemInfoSync()
97
+ return {
98
+ brand: info.brand || '',
99
+ model: info.model || '',
100
+ platform: info.platform || '',
101
+ system: info.system || '',
102
+ screenWidth: info.screenWidth || 0,
103
+ screenHeight: info.screenHeight || 0,
104
+ userAgent: '',
105
+ }
106
+ } catch (e) {
107
+ return {}
108
+ }
109
+ }
110
+
111
+ // ---- 生命周期事件(小程序通过 mixin/hook 在外部处理)----
112
+
113
+ onPageShow(callback) {
114
+ // 小程序的页面生命周期通过 Vue mixin 处理,这里仅作标记
115
+ this._onPageShowCallback = callback
116
+ }
117
+
118
+ onPageHide(callback) {
119
+ this._onPageHideCallback = callback
120
+ }
121
+
122
+ onPageClose(callback) {
123
+ this._onPageCloseCallback = callback
124
+ }
125
+
126
+ // ---- DOM 能力(小程序不支持)----
127
+
128
+ supportsDOM() {
129
+ return false
130
+ }
131
+
132
+ supportsIntersectionObserver() {
133
+ return false
134
+ }
135
+
136
+ addClickListener() {}
137
+ removeClickListener() {}
138
+
139
+ addErrorListener(handler) {
140
+ // 小程序全局错误监听
141
+ try {
142
+ if (typeof uni !== 'undefined' && uni.onError) {
143
+ uni.onError(handler)
144
+ this._listeners.push({ type: 'uniError', handler })
145
+ }
146
+ } catch (e) {}
147
+ }
148
+
149
+ addUnhandledRejectionListener(handler) {
150
+ try {
151
+ if (typeof uni !== 'undefined' && uni.onUnhandledRejection) {
152
+ uni.onUnhandledRejection(handler)
153
+ this._listeners.push({ type: 'uniRejection', handler })
154
+ }
155
+ } catch (e) {}
156
+ }
157
+
158
+ // ---- 网络状态 ----
159
+
160
+ getNetworkType() {
161
+ try {
162
+ var res = uni.getNetworkType && uni.getNetworkType({ success: function () {} })
163
+ // 同步方式获取不到,返回 null 由异步补充
164
+ } catch (e) {}
165
+ return this._networkType || null
166
+ }
167
+
168
+ initNetworkListener() {
169
+ var self = this
170
+ try {
171
+ uni.getNetworkType({
172
+ success: function (res) {
173
+ self._networkType = res.networkType || 'unknown'
174
+ },
175
+ })
176
+ uni.onNetworkStatusChange(function (res) {
177
+ self._networkType = res.networkType || 'unknown'
178
+ })
179
+ } catch (e) {}
180
+ }
181
+
182
+ // ---- 路由监听(小程序通过 mixin 处理)----
183
+
184
+ onRouteChange(callback) {
185
+ this._onRouteChangeCallback = callback
186
+ }
187
+
188
+ // ---- 工具 ----
189
+
190
+ generateId(prefix) {
191
+ return (
192
+ (prefix || '') +
193
+ '_' +
194
+ Date.now() +
195
+ '_' +
196
+ Math.random().toString(36).substr(2, 9)
197
+ )
198
+ }
199
+
200
+ getTimestamp() {
201
+ return Date.now()
202
+ }
203
+
204
+ getXPath() {
205
+ return ''
206
+ }
207
+
208
+ findTrackableElement(el) {
209
+ return el
210
+ }
211
+
212
+ // ---- 清理 ----
213
+
214
+ destroy() {
215
+ this._sessionStore.clear()
216
+ this._listeners = []
217
+ }
218
+ }