@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/core/compat.js +344 -0
- package/core/config.js +132 -0
- package/core/queue.js +461 -0
- package/core/session.js +128 -0
- package/core/tracker.js +287 -0
- package/customize/index.js +92 -0
- package/customize/modules/common.js +43 -0
- package/customize/modules/store-manager.js +56 -0
- package/index.js +403 -0
- package/package.json +21 -0
- package/platforms/app.js +36 -0
- package/platforms/weapp.js +218 -0
- package/platforms/web.js +483 -0
- package/plugins/api-watch.js +240 -0
- package/plugins/app-lifecycle.js +90 -0
- package/plugins/click.js +557 -0
- package/plugins/exposure.js +376 -0
- package/plugins/js-error.js +232 -0
- package/plugins/page-stay.js +44 -0
- package/plugins/page-view.js +172 -0
- package/plugins/performance.js +157 -0
- package/plugins/scroll-depth.js +256 -0
package/core/compat.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 兼容层 — 确保现有业务代码的 this.$dadian() 和 v-impression 零改动可用
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
function isObject(value) {
|
|
6
|
+
return Object.prototype.toString.call(value) === '[object Object]'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 创建 $dadian 代理函数
|
|
11
|
+
* 行为与 new-platform-store-manager/common/public.js 的 activeReport 一致:
|
|
12
|
+
* 直接将 address 作为 frontUrl,不做路由拼接
|
|
13
|
+
*/
|
|
14
|
+
export function createDadianProxy(tracker) {
|
|
15
|
+
const config = tracker.getConfig()
|
|
16
|
+
const dadianUrl = config.dadianUrl
|
|
17
|
+
|
|
18
|
+
return function activeReport(frontUrl, postParam) {
|
|
19
|
+
// [PROJECT-ADAPT] 认证头检查
|
|
20
|
+
var header = { 'content-type': 'application/json' }
|
|
21
|
+
if (typeof config.getAuthHeaders === 'function') {
|
|
22
|
+
var authHeaders = config.getAuthHeaders() || {}
|
|
23
|
+
if (!authHeaders.Authorization && !authHeaders.token) {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
Object.assign(header, authHeaders)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// postParam 处理(小程序合并 eventBus 字段)
|
|
30
|
+
var postParamStr = null
|
|
31
|
+
if (postParam) {
|
|
32
|
+
if (config.platform === 'weapp' && config.eventBus) {
|
|
33
|
+
var eventBus = config.eventBus
|
|
34
|
+
postParamStr = JSON.stringify(
|
|
35
|
+
isObject(postParam)
|
|
36
|
+
? {
|
|
37
|
+
...postParam,
|
|
38
|
+
wxAppOnLaunch: eventBus.wxAppOnLaunch,
|
|
39
|
+
pageFrom: eventBus.pageFrom,
|
|
40
|
+
mallOrderExt: eventBus.mallOrderExt,
|
|
41
|
+
}
|
|
42
|
+
: postParam
|
|
43
|
+
)
|
|
44
|
+
} else {
|
|
45
|
+
postParamStr = JSON.stringify(postParam)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 开关关(默认):旧接口直接 POST + SDK 入队,报 2 次
|
|
50
|
+
// 开关开:只走 SDK 队列,不调旧接口
|
|
51
|
+
if (!config.useUnifiedReport) {
|
|
52
|
+
var url = dadianUrl + '/api/point/task/activeReport'
|
|
53
|
+
tracker.getQueue().pushLegacy(url, { frontUrl: frontUrl, postParam: postParamStr }, header)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// SDK 事件入队(不论开关,始终入队;字段映射始终一套规则)
|
|
57
|
+
tracker.track('custom', {
|
|
58
|
+
eventName: frontUrl || 'manual',
|
|
59
|
+
frontUrl: frontUrl,
|
|
60
|
+
postParam: postParamStr,
|
|
61
|
+
elementId: frontUrl,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
if (config.debug) {
|
|
65
|
+
console.log('[TrackerSDK] dadian:', frontUrl, postParam)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 创建 v-impression 指令
|
|
72
|
+
* 逻辑与 apph5/directives/impression.js 完全一致
|
|
73
|
+
*/
|
|
74
|
+
export function createImpressionDirective(tracker) {
|
|
75
|
+
var observers = new WeakMap()
|
|
76
|
+
var reportedElements = new WeakSet()
|
|
77
|
+
|
|
78
|
+
function handleIntersection(entries, observer) {
|
|
79
|
+
entries.forEach(function (entry) {
|
|
80
|
+
if (entry.isIntersecting && entry.intersectionRatio >= 1.0) {
|
|
81
|
+
var el = entry.target
|
|
82
|
+
if (!reportedElements.has(el)) {
|
|
83
|
+
var impressionData = el._impressionData || {}
|
|
84
|
+
var handler = impressionData.handler
|
|
85
|
+
var data = impressionData.data
|
|
86
|
+
if (handler && typeof handler === 'function') {
|
|
87
|
+
handler(data)
|
|
88
|
+
reportedElements.add(el)
|
|
89
|
+
observer.unobserve(el)
|
|
90
|
+
} else {
|
|
91
|
+
reportedElements.add(el)
|
|
92
|
+
observer.unobserve(el)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
bind: function (el, binding) {
|
|
101
|
+
if (
|
|
102
|
+
!binding.value ||
|
|
103
|
+
typeof binding.value.handler !== 'function' ||
|
|
104
|
+
typeof binding.value.data === 'undefined'
|
|
105
|
+
) {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
el._impressionData = binding.value
|
|
110
|
+
|
|
111
|
+
var options = {
|
|
112
|
+
root: null,
|
|
113
|
+
rootMargin: '0px',
|
|
114
|
+
threshold: 1.0,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
var observer = new IntersectionObserver(handleIntersection, options)
|
|
118
|
+
observer.observe(el)
|
|
119
|
+
observers.set(el, observer)
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
update: function (el, binding) {
|
|
123
|
+
if (
|
|
124
|
+
binding.value &&
|
|
125
|
+
binding.oldValue &&
|
|
126
|
+
JSON.stringify(binding.value.data) !==
|
|
127
|
+
JSON.stringify(binding.oldValue.data)
|
|
128
|
+
) {
|
|
129
|
+
el._impressionData = binding.value
|
|
130
|
+
} else if (binding.value) {
|
|
131
|
+
el._impressionData = binding.value
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
unbind: function (el) {
|
|
136
|
+
var observer = observers.get(el)
|
|
137
|
+
if (observer) {
|
|
138
|
+
observer.unobserve(el)
|
|
139
|
+
observers.delete(el)
|
|
140
|
+
}
|
|
141
|
+
reportedElements.delete(el)
|
|
142
|
+
delete el._impressionData
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 创建页面停留时长 mixin
|
|
149
|
+
* 简化版 PageStayTracker,保留核心计时和上报逻辑
|
|
150
|
+
*/
|
|
151
|
+
export function createPageStayMixin(tracker) {
|
|
152
|
+
var _lastMixinRoute = null
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
data: function () {
|
|
156
|
+
return {
|
|
157
|
+
_pageStayStartTime: 0,
|
|
158
|
+
_pageStayRoute: '',
|
|
159
|
+
_pageStayTitle: '',
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
onLoad: function () {
|
|
164
|
+
var route = ''
|
|
165
|
+
try {
|
|
166
|
+
// 优先用组件自身的页面路由(不受全局导航栈时序影响)
|
|
167
|
+
if (this.$mp && this.$mp.page && this.$mp.page.route) {
|
|
168
|
+
route = this.$mp.page.route
|
|
169
|
+
} else if (this.$page && this.$page.route) {
|
|
170
|
+
route = this.$page.route
|
|
171
|
+
} else {
|
|
172
|
+
// 降级:取全局栈顶(仅首次加载可靠,导航中可能已被更新)
|
|
173
|
+
var pages = getCurrentPages()
|
|
174
|
+
if (pages.length > 0) {
|
|
175
|
+
route = pages[pages.length - 1].route || ''
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch (e) {}
|
|
179
|
+
if (!route) return
|
|
180
|
+
this._pageStayRoute = route.charAt(0) === '/' ? route : '/' + route
|
|
181
|
+
this._pageStayTitle = ''
|
|
182
|
+
try {
|
|
183
|
+
if (typeof document !== 'undefined' && document.title) {
|
|
184
|
+
this._pageStayTitle = document.title
|
|
185
|
+
}
|
|
186
|
+
} catch (e) {}
|
|
187
|
+
this._pageStayStartTime = Date.now()
|
|
188
|
+
this._pageShowReported = false
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
mounted: function () {
|
|
192
|
+
if (typeof window === 'undefined') return
|
|
193
|
+
var self = this
|
|
194
|
+
this._pageshowHandler = function (e) {
|
|
195
|
+
if (e.persisted) {
|
|
196
|
+
self._pageStayStartTime = Date.now()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
window.addEventListener('pageshow', this._pageshowHandler)
|
|
200
|
+
|
|
201
|
+
// H5 下 app 前后台切换(visibilitychange)不总触发页面 onShow/onHide,
|
|
202
|
+
// 切回前台后停留起点未重置 → 下次 onHide 因 start=0 早退 → 停留漏测。
|
|
203
|
+
// 用 visibilitychange 兜底(app-lifecycle 已证明它可靠触发):
|
|
204
|
+
// 仅栈顶页面响应,与 onShow/onHide 幂等共存(重复触发因 start=0 早退,不会重报)。
|
|
205
|
+
if (typeof document !== 'undefined') {
|
|
206
|
+
this._pageVisHandler = function () {
|
|
207
|
+
if (!self._isTopStayPage()) return
|
|
208
|
+
if (document.visibilityState === 'visible') {
|
|
209
|
+
self._pageStayStartTime = Date.now()
|
|
210
|
+
} else if (document.visibilityState === 'hidden') {
|
|
211
|
+
self._pageStayReport()
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
document.addEventListener('visibilitychange', this._pageVisHandler)
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
onShow: function () {
|
|
219
|
+
if (!this._pageStayRoute) return
|
|
220
|
+
this._pageStayStartTime = Date.now()
|
|
221
|
+
if (!this._pageShowReported) {
|
|
222
|
+
this._pageShowReported = true
|
|
223
|
+
this._pageShowReport('onShow')
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
onHide: function () {
|
|
228
|
+
if (!this._pageStayRoute) return
|
|
229
|
+
_lastMixinRoute = this._pageStayRoute
|
|
230
|
+
this._pageStayReport()
|
|
231
|
+
this._pageShowReported = false
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
onUnload: function () {
|
|
235
|
+
if (!this._pageStayRoute) return
|
|
236
|
+
_lastMixinRoute = this._pageStayRoute
|
|
237
|
+
this._pageStayReport()
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
beforeDestroy: function () {
|
|
241
|
+
if (this._pageshowHandler && typeof window !== 'undefined') {
|
|
242
|
+
window.removeEventListener('pageshow', this._pageshowHandler)
|
|
243
|
+
this._pageshowHandler = null
|
|
244
|
+
}
|
|
245
|
+
if (this._pageVisHandler && typeof document !== 'undefined') {
|
|
246
|
+
document.removeEventListener('visibilitychange', this._pageVisHandler)
|
|
247
|
+
this._pageVisHandler = null
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
methods: {
|
|
252
|
+
// 当前组件是否为导航栈顶(活跃)页面。
|
|
253
|
+
// visibilitychange 是全局事件,栈中多个页面实例都会收到,
|
|
254
|
+
// 仅栈顶页响应,避免后台页面误报停留。
|
|
255
|
+
_isTopStayPage: function () {
|
|
256
|
+
try {
|
|
257
|
+
var pages = getCurrentPages()
|
|
258
|
+
if (!pages || !pages.length) return false
|
|
259
|
+
var top = pages[pages.length - 1]
|
|
260
|
+
var route = top && top.route
|
|
261
|
+
? (top.route.charAt(0) === '/' ? top.route : '/' + top.route)
|
|
262
|
+
: ''
|
|
263
|
+
return route === this._pageStayRoute
|
|
264
|
+
} catch (e) {
|
|
265
|
+
return false
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
_pageShowReport: function (trigger) {
|
|
270
|
+
var config = tracker.getConfig()
|
|
271
|
+
var showFrontUrl = config.pageShowFrontUrl || '/storeManager/page_show'
|
|
272
|
+
var showPostParam = JSON.stringify({
|
|
273
|
+
pagePath: this._pageStayRoute,
|
|
274
|
+
timestamp: Date.now(),
|
|
275
|
+
trigger: trigger,
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
if (!config.useUnifiedReport) {
|
|
279
|
+
var header = { 'content-type': 'application/json' }
|
|
280
|
+
if (typeof config.getAuthHeaders === 'function') {
|
|
281
|
+
Object.assign(header, config.getAuthHeaders() || {})
|
|
282
|
+
}
|
|
283
|
+
tracker.getQueue().pushLegacy(
|
|
284
|
+
config.dadianUrl + '/api/point/task/activeReport',
|
|
285
|
+
{ frontUrl: showFrontUrl, postParam: showPostParam },
|
|
286
|
+
header
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
// 旧模式:page_show 通过 SDK 队列上报(与 page_view 并存)
|
|
290
|
+
tracker.track('page_show', {
|
|
291
|
+
pageRoute: this._pageStayRoute,
|
|
292
|
+
referrer: _lastMixinRoute || tracker.getReferrer() || null,
|
|
293
|
+
frontUrl: showFrontUrl,
|
|
294
|
+
postParam: showPostParam,
|
|
295
|
+
elementId: showFrontUrl,
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
// 统一模式:不再上报 page_show,由 PageViewPlugin 的 page_view 统一负责
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
_pageStayReport: function () {
|
|
302
|
+
if (!this._pageStayStartTime) return
|
|
303
|
+
var duration = Date.now() - this._pageStayStartTime
|
|
304
|
+
this._pageStayStartTime = 0
|
|
305
|
+
|
|
306
|
+
var config = tracker.getConfig()
|
|
307
|
+
// 最小停留阈值:iOS 前后台抖动会产生大量 1~2s 的碎停留,默认 2000ms 过滤噪音
|
|
308
|
+
var minDuration = (config.pageStay || {}).minStayDuration || 2000
|
|
309
|
+
if (duration < minDuration) return
|
|
310
|
+
|
|
311
|
+
var maxDuration = (config.pageStay || {}).maxStayDuration || 1800000
|
|
312
|
+
if (duration > maxDuration) duration = maxDuration
|
|
313
|
+
|
|
314
|
+
var stayFrontUrl = config.pageStayFrontUrl || '/storeManager/page_stay'
|
|
315
|
+
var stayPostParam = JSON.stringify({
|
|
316
|
+
pagePath: this._pageStayRoute,
|
|
317
|
+
duration: duration,
|
|
318
|
+
timestamp: Date.now(),
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
if (!config.useUnifiedReport) {
|
|
322
|
+
var header = { 'content-type': 'application/json' }
|
|
323
|
+
if (typeof config.getAuthHeaders === 'function') {
|
|
324
|
+
Object.assign(header, config.getAuthHeaders() || {})
|
|
325
|
+
}
|
|
326
|
+
tracker.getQueue().pushLegacy(
|
|
327
|
+
config.dadianUrl + '/api/point/task/activeReport',
|
|
328
|
+
{ frontUrl: stayFrontUrl, postParam: stayPostParam },
|
|
329
|
+
header
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
tracker.track('page_stay', {
|
|
334
|
+
pageRoute: this._pageStayRoute,
|
|
335
|
+
pageTitle: this._pageStayTitle || null,
|
|
336
|
+
duration: duration,
|
|
337
|
+
frontUrl: stayFrontUrl,
|
|
338
|
+
postParam: stayPostParam,
|
|
339
|
+
elementId: stayFrontUrl,
|
|
340
|
+
})
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
}
|
|
344
|
+
}
|
package/core/config.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// [PROJECT-ADAPT] 默认配置
|
|
2
|
+
// 各项目通过 Vue.use(TrackerSDK, { ... }) 覆盖以下默认值
|
|
3
|
+
// 标记有 ★ 的字段是各项目最可能需要覆盖的
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
appId: '', // ★ 店长端=1, apph5=12/13, 小程序=18(每个项目固定,不会变)
|
|
6
|
+
dadianUrl: '', // ★ 各环境域名不同
|
|
7
|
+
platform: '', // ★ 运行时类型: 'web' | 'weapp' | 'app'(SDK 内部使用,决定插件行为)
|
|
8
|
+
pflag: '', // ★ 业务平台标识: 对应 PlatformList 的值(上报用,区分白标)
|
|
9
|
+
tokenKey: 'token', // ★ 小程序用 'tokenV2'
|
|
10
|
+
|
|
11
|
+
plugins: [],
|
|
12
|
+
|
|
13
|
+
queue: {
|
|
14
|
+
maxBatchSize: 10, // 达到此条数立即上报(页面维度批量)
|
|
15
|
+
flushInterval: 60000, // 同页面停留超过此时长(ms)也触发上报
|
|
16
|
+
clickFlushDelay: 3000, // 点击后延迟上报时间(ms),等待后续接口请求一起批量发出
|
|
17
|
+
maxQueueSize: 100,
|
|
18
|
+
offlineCacheKey: '_tracker_offline_cache',
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
session: {
|
|
22
|
+
timeout: 30 * 60 * 1000,
|
|
23
|
+
storageKey: '_tracker_session',
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
pageStay: {
|
|
27
|
+
heartbeatInterval: 5000,
|
|
28
|
+
maxStayDuration: 1800000,
|
|
29
|
+
minStayDuration: 2000,
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// 曝光插件配置
|
|
33
|
+
exposure: {
|
|
34
|
+
validExposureTime: 1000, // 有效曝光最小停留时长 ms
|
|
35
|
+
exitRatio: 0.7, // 低于此比例判定为离开视口
|
|
36
|
+
threshold: 0.5, // 普通模式可见比例阈值
|
|
37
|
+
useDuration: false, // 是否启用停留时长验证
|
|
38
|
+
observeDynamicElements: true, // 是否监控动态 DOM 元素
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// 滚动深度插件配置
|
|
42
|
+
scrollDepth: {
|
|
43
|
+
throttle: 1000, // 节流间隔 ms
|
|
44
|
+
milestone: false, // 是否只在 25/50/75/100% 里程碑时上报
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// 采样率 0-1,1 表示全量采集,0.5 表示 50% 的会话被采集
|
|
48
|
+
// 采用会话级别采样,同一个 session 内所有事件要么全采要么全不采
|
|
49
|
+
sampling: 1,
|
|
50
|
+
|
|
51
|
+
batchReportUrl: '/api/point/task/trackBatch',
|
|
52
|
+
|
|
53
|
+
// ★ 无 token 上报开关
|
|
54
|
+
// false(默认):flush 时必须有 token 才上报,无 token 事件留在队列等待
|
|
55
|
+
// true:无 token 也可上报(适用于不需要登录的场景)
|
|
56
|
+
allowAnonymousTrack: false,
|
|
57
|
+
|
|
58
|
+
// ★ 统一上报开关
|
|
59
|
+
// false(默认):$dadian/$track 旧接口直接 POST + SDK 入队,报 2 次(过渡期双写)
|
|
60
|
+
// true:$dadian/$track 只走 SDK 队列 → trackBatch,不再调旧接口(迁移完成后开启)
|
|
61
|
+
useUnifiedReport: true,
|
|
62
|
+
|
|
63
|
+
// ★ page_stay / page_show 事件在旧接口中的 frontUrl(各项目已注册的值)
|
|
64
|
+
// 店长端: '/storeManager/page_stay', '/storeManager/page_show'
|
|
65
|
+
// appH5: 根据实际注册值填写
|
|
66
|
+
pageStayFrontUrl: '/storeManager/page_stay',
|
|
67
|
+
pageShowFrontUrl: '/storeManager/page_show',
|
|
68
|
+
|
|
69
|
+
// ★ [PROJECT-ADAPT] 认证头回调
|
|
70
|
+
// 店长端: () => ({ Authorization: orgToken, token: orgToken, 'rsb-token': token })
|
|
71
|
+
// 小程序: () => ({ token: tokenV2 })
|
|
72
|
+
// apph5: () => ({ 'rsb-token': token })
|
|
73
|
+
getAuthHeaders: null,
|
|
74
|
+
|
|
75
|
+
// 设备注册配置
|
|
76
|
+
deviceRegister: {
|
|
77
|
+
url: '/api/point/device/add',
|
|
78
|
+
appVersion: '1.0',
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
eventBus: null,
|
|
82
|
+
|
|
83
|
+
debug: false,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function deepMerge(target, source) {
|
|
87
|
+
const result = { ...target }
|
|
88
|
+
for (const key of Object.keys(source)) {
|
|
89
|
+
if (source[key] === undefined) continue
|
|
90
|
+
if (
|
|
91
|
+
source[key] &&
|
|
92
|
+
typeof source[key] === 'object' &&
|
|
93
|
+
!Array.isArray(source[key]) &&
|
|
94
|
+
target[key] &&
|
|
95
|
+
typeof target[key] === 'object' &&
|
|
96
|
+
!Array.isArray(target[key])
|
|
97
|
+
) {
|
|
98
|
+
result[key] = deepMerge(target[key], source[key])
|
|
99
|
+
} else {
|
|
100
|
+
result[key] = source[key]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return result
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getDefaultConfig() {
|
|
107
|
+
return JSON.parse(JSON.stringify(DEFAULT_CONFIG))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function mergeConfig(userConfig) {
|
|
111
|
+
const config = deepMerge(DEFAULT_CONFIG, userConfig)
|
|
112
|
+
if (userConfig.eventBus) {
|
|
113
|
+
config.eventBus = userConfig.eventBus
|
|
114
|
+
}
|
|
115
|
+
if (typeof userConfig.getAuthHeaders === 'function') {
|
|
116
|
+
config.getAuthHeaders = userConfig.getAuthHeaders
|
|
117
|
+
}
|
|
118
|
+
if (userConfig.projectRules) {
|
|
119
|
+
config.projectRules = userConfig.projectRules
|
|
120
|
+
}
|
|
121
|
+
validateConfig(config)
|
|
122
|
+
return config
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function validateConfig(config) {
|
|
126
|
+
if (!config.appId) {
|
|
127
|
+
throw new Error('[TrackerSDK] appId is required')
|
|
128
|
+
}
|
|
129
|
+
if (!config.dadianUrl) {
|
|
130
|
+
throw new Error('[TrackerSDK] dadianUrl is required')
|
|
131
|
+
}
|
|
132
|
+
}
|