@dcloudio/uni-stat 3.0.0-5000720260410001 → 3.0.0-5010520260709001

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.
@@ -23,6 +23,409 @@ const APP_PVER_TIME = 300; // 应用在后台结束访问时间 单位s
23
23
  const OPERATING_TIME = 10; // 数据上报时间 单位s
24
24
  const DIFF_TIME = 60 * 1000 * 60 * 24;
25
25
 
26
+ /**
27
+ * 事件类型与会话创建类型常量。
28
+ *
29
+ * 与私有版 / 文档 `uni统计上报参数.md` 的兼容关系:
30
+ * - 上行参数文档明确:`lt` 仅取 `1 / 3 / 11 / 21 / 31 / 41`,**没有** `lt=0`。
31
+ * - 历史架构文档(03-公有版架构设计.md §3.2)曾设计 `lt=0` 作为"客户端 session 边界"事件,
32
+ * 但与服务端入库口径不一致(会话日志 = lt=1),已**整体移除**:
33
+ * 新会话直接发一条 lt=1,会话字段(`sid / cst / fvts / lvts / tvc`)随 lt=1 上行。
34
+ * - 因此 `LT` 不再包含 `Session`;删除 lt=0 不影响老接收端。
35
+ */
36
+ /**
37
+ * Log Type(事件类型)。统一在此声明,禁止其他模块裸写字符串。
38
+ *
39
+ * 注:`lt=41`(uni-app x 原生崩溃日志)暂未在公有版实现,详见 `docs/暂未实现字段说明.md`。
40
+ */
41
+ const LT = {
42
+ Launch: '1',
43
+ Hide: '3',
44
+ Page: '11',
45
+ Event: '21',
46
+ Error: '31',
47
+ Push: '101',
48
+ };
49
+
50
+ /******************************************************************************
51
+ Copyright (c) Microsoft Corporation.
52
+
53
+ Permission to use, copy, modify, and/or distribute this software for any
54
+ purpose with or without fee is hereby granted.
55
+
56
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
57
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
58
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
59
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
60
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
61
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
62
+ PERFORMANCE OF THIS SOFTWARE.
63
+ ***************************************************************************** */
64
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
65
+
66
+
67
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
68
+ var e = new Error(message);
69
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
70
+ };
71
+
72
+ /**
73
+ * 安全工具集:序列化、try 包裹、指数退避重试。
74
+ *
75
+ * 修复缺陷:
76
+ * - #1 `_retry` 未初始化导致重试链路 NaN(公有版直接以参数显式传 `times`)。
77
+ * - #7 取值反向(私有版 `if (data.length > MAX_LENGTH)` 误判)。
78
+ * - #8 循环引用导致 `JSON.stringify` 抛错(用 WeakSet replacer 兜底)。
79
+ */
80
+ const DEFAULT_MAX_LENGTH = 4096;
81
+ const TRUNCATED_SUFFIX = '…[truncated]';
82
+ /**
83
+ * 序列化任意值为字符串:支持循环引用与最大长度截断。
84
+ *
85
+ * @param value 待序列化的值。`undefined` 返回 '';string 直接返回(仍参与截断)。
86
+ * @param max 字符串最大长度,默认 4096;超长会截断并附 `…[truncated]`。
87
+ */
88
+ function safeStringify(value, max = DEFAULT_MAX_LENGTH) {
89
+ var _a;
90
+ if (value === undefined)
91
+ return '';
92
+ let raw;
93
+ if (typeof value === 'string') {
94
+ raw = value;
95
+ }
96
+ else {
97
+ const seen = new WeakSet();
98
+ try {
99
+ raw =
100
+ (_a = JSON.stringify(value, (_key, val) => {
101
+ if (typeof val === 'object' && val !== null) {
102
+ if (seen.has(val))
103
+ return '[Circular]';
104
+ seen.add(val);
105
+ }
106
+ if (typeof val === 'bigint')
107
+ return val.toString();
108
+ if (typeof val === 'function')
109
+ return `[Function ${val.name || 'anonymous'}]`;
110
+ return val;
111
+ })) !== null && _a !== void 0 ? _a : '';
112
+ }
113
+ catch (e) {
114
+ raw = `[Unserializable: ${e.message}]`;
115
+ }
116
+ }
117
+ if (raw.length > max) {
118
+ return (raw.slice(0, Math.max(0, max - TRUNCATED_SUFFIX.length)) +
119
+ TRUNCATED_SUFFIX);
120
+ }
121
+ return raw;
122
+ }
123
+
124
+ /**
125
+ * 解析 uni-app 运行时根对象 `uni`。
126
+ *
127
+ * - H5 / App:常见为 `globalThis.uni`。
128
+ * - 微信小程序等:多为 Vite/rollup 向**当前模块**注入的标识符 `uni`,
129
+ * **未必**同步挂到 `globalThis`;仅读 `globalThis.uni` 会导致
130
+ * `bindLifecycle` / `uni.request` / storage 等全部静默失败。
131
+ * - 支付宝等旧版小程序:**无 `globalThis` 标识符**,须用 `getGlobalObject()` 兜底。
132
+ * - H5 发行摇树:`pages.json.js` 会先把 `window.uni = {}` 占位;若仍按「有 object 即用」
133
+ * 会误把空桩当真 uni。须用 `isUsableUniRuntime` 过滤后再择源。
134
+ *
135
+ * 第二路依赖宿主构建对 `uni` 的注入(与业务页面同一套解析规则),
136
+ * 类型兜底见 `packages/uni-stat/src/uni-global.d.ts`。
137
+ */
138
+ /**
139
+ * 判断候选 `uni` 是否具备统计 SDK 可用的最小 API 集合(排除 H5 摇树空桩 `{}`)。
140
+ *
141
+ * 任一核心 API 存在即视为可用;与具体平台无关,微信/QQ/抖音/支付宝/百度等
142
+ * 完整 runtime 均满足,仅「占位空对象」会被过滤。
143
+ */
144
+ /**
145
+ * H5 兜底:在 `globalThis` / `self` 不可用时尝试读取 `window`。
146
+ *
147
+ * 通过 `Function` 间接访问,避免 ESLint `no-restricted-globals` 对 `window` 标识符的限制;
148
+ * 小程序等环境执行失败时返回 `undefined`。
149
+ */
150
+ function getWindowObject() {
151
+ try {
152
+ const w = Function('return typeof window !== "undefined" ? window : undefined')();
153
+ return w != null ? w : undefined;
154
+ }
155
+ catch (_a) {
156
+ return undefined;
157
+ }
158
+ }
159
+ /**
160
+ * 安全获取全局对象。
161
+ *
162
+ * 支付宝 / 部分旧版小程序运行时未提供 `globalThis`,直接写 `globalThis` 会
163
+ * `ReferenceError: globalThis is not defined`,导致 install 阶段整包崩溃。
164
+ */
165
+ function getGlobalObject() {
166
+ if (typeof globalThis !== 'undefined' && globalThis != null) {
167
+ return globalThis;
168
+ }
169
+ if (typeof global !== 'undefined' && global != null) {
170
+ return global;
171
+ }
172
+ if (typeof self !== 'undefined' && self != null) {
173
+ return self;
174
+ }
175
+ const win = getWindowObject();
176
+ if (win)
177
+ return win;
178
+ return {};
179
+ }
180
+
181
+ /**
182
+ * 公有版统一日志出口。
183
+ *
184
+ * 修复的私有版缺陷:
185
+ * - #19 `!!process.env.UNI_STAT_DEBUG` 在构建时若被替换为字符串 `"false"` 仍是 truthy。
186
+ * 公有版严格使用 `=== 'true'` 判定,并允许在运行时通过 `setDebug()` 临时打开
187
+ * (供调试 / 灰度小流量验证)。
188
+ *
189
+ * 行为约定:
190
+ * - `debug` 受调试开关控制;其他 level 始终输出到对应的 `console.*`。
191
+ * - **Android / iOS 真机**:`TAG` 与正文拼成单条字符串,避免桥接丢弃第二参起。
192
+ * - **其它平台**:`console.*(TAG, ...args)`,对象保持原生传递。
193
+ *
194
+ * 兼容性:
195
+ * - 历史版本插件 define 误把 `process.env.UNI_STAT_DEBUG` 替换成布尔字面量
196
+ * (未 `JSON.stringify`),导致 dist 运行时该值为 `true`/`false` 而非 `'true'`/`'false'`。
197
+ * `isDebug()` 同时接受字符串 `'true'` 与布尔字面量 `true`,避免历史构建产物完全失效。
198
+ */
199
+ const TAG = '[uni统计 2.0]';
200
+ let runtimeDebug;
201
+ /**
202
+ * 是否屏蔽 info / warn / error。
203
+ * `undefined`:自动——`NODE_ENV === 'test'` 时默认屏蔽,避免 Jest 用例预期失败路径刷屏。
204
+ */
205
+ let muteNonDebug;
206
+ /**
207
+ * 是否将日志合并为单行(Android / iOS 真机侧)。
208
+ */
209
+ function preferSingleLineConsole() {
210
+ return isAndroidOrIosRuntime();
211
+ }
212
+ /**
213
+ * 是否为 App 或小程序运行在 **Android / iOS** 上(仅此类环境对对象参数做字符串化)。
214
+ */
215
+ function isAndroidOrIosRuntime() {
216
+ var _a, _b, _c, _d, _e, _f, _g, _h;
217
+ const raw = (_a = process.env.UNI_PLATFORM) !== null && _a !== void 0 ? _a : '';
218
+ const g = getGlobalObject();
219
+ if (raw === 'app' || raw === 'app-plus' || raw === 'app-harmony') {
220
+ const n = (_d = (_c = (_b = g.plus) === null || _b === void 0 ? void 0 : _b.os) === null || _c === void 0 ? void 0 : _c.name) === null || _d === void 0 ? void 0 : _d.toLowerCase();
221
+ if (!n)
222
+ return false;
223
+ if (n.includes('android'))
224
+ return true;
225
+ if (n === 'ios' || n.includes('iphone'))
226
+ return true;
227
+ return false;
228
+ }
229
+ if (raw.startsWith('mp-')) {
230
+ try {
231
+ const p = (_h = (_g = (_f = (_e = g.uni) === null || _e === void 0 ? void 0 : _e.getSystemInfoSync) === null || _f === void 0 ? void 0 : _f.call(_e)) === null || _g === void 0 ? void 0 : _g.platform) === null || _h === void 0 ? void 0 : _h.toLowerCase();
232
+ return p === 'android' || p === 'ios';
233
+ }
234
+ catch (_j) {
235
+ return false;
236
+ }
237
+ }
238
+ return false;
239
+ }
240
+ /**
241
+ * 在 Android/iOS 上将「对象类」参数转为可打印字符串;其余类型原样返回。
242
+ */
243
+ function stringifyObjectArgForNative(value) {
244
+ if (value === null || value === undefined)
245
+ return value;
246
+ if (typeof value !== 'object')
247
+ return value;
248
+ if (value instanceof Error)
249
+ return `${value.name}: ${value.message}`;
250
+ return safeStringify(value);
251
+ }
252
+ /**
253
+ * 将单段日志参数格式化为可拼进一行文本的片段(Android/iOS 单参输出用)。
254
+ */
255
+ function formatLogArgForNativeConsole(value) {
256
+ if (value === null)
257
+ return 'null';
258
+ if (value === undefined)
259
+ return 'undefined';
260
+ if (typeof value === 'string')
261
+ return value;
262
+ if (typeof value === 'number' || typeof value === 'boolean') {
263
+ return String(value);
264
+ }
265
+ if (typeof value === 'bigint')
266
+ return String(value);
267
+ if (typeof value === 'symbol') {
268
+ try {
269
+ return value.toString();
270
+ }
271
+ catch (_a) {
272
+ return '?';
273
+ }
274
+ }
275
+ if (typeof value === 'function') {
276
+ const fn = value;
277
+ return `[Function ${fn.name || 'anonymous'}]`;
278
+ }
279
+ if (typeof value === 'object') {
280
+ if (value instanceof Error)
281
+ return `${value.name}: ${value.message}`;
282
+ return safeStringify(value);
283
+ }
284
+ return String(value);
285
+ }
286
+ /**
287
+ * 当前是否应屏蔽 info / warn / error(debug 仍由 `isDebug()` 单独控制)。
288
+ */
289
+ function isNonDebugMuted() {
290
+ if (muteNonDebug !== undefined)
291
+ return muteNonDebug;
292
+ return process.env.NODE_ENV === 'test';
293
+ }
294
+ /**
295
+ * 测试 / CI 下临时恢复 warn 等输出(如断言 install 告警文案)。
296
+ *
297
+ * @param value `true` 屏蔽;`false` 允许;`undefined` 恢复为按 NODE_ENV 自动判定。
298
+ */
299
+ function setMuteNonDebug(value) {
300
+ muteNonDebug = value;
301
+ }
302
+ /**
303
+ * 输出到 console:Android/iOS 真机整行单参;其余平台 `TAG` + 多参。
304
+ */
305
+ function emitConsole(method, args) {
306
+ if (method !== 'log' && isNonDebugMuted())
307
+ return;
308
+ const fn = console[method];
309
+ if (!preferSingleLineConsole()) {
310
+ fn.call(console, TAG, ...args);
311
+ return;
312
+ }
313
+ const mapped = isAndroidOrIosRuntime()
314
+ ? args.map(stringifyObjectArgForNative)
315
+ : args;
316
+ if (mapped.length === 0) {
317
+ fn.call(console, TAG);
318
+ return;
319
+ }
320
+ const body = mapped.map(formatLogArgForNativeConsole).join(' ');
321
+ fn.call(console, `${TAG} ${body}`);
322
+ }
323
+ /**
324
+ * 当前是否启用 debug 输出。优先级:
325
+ * 1. `setDebug(value)` 显式设置过 → 直接返回。
326
+ * 2. `process.env.UNI_STAT_DEBUG === 'true'` 或被构建期替换为布尔字面量 `true`
327
+ * (历史插件兼容路径)。
328
+ */
329
+ function isDebug() {
330
+ if (runtimeDebug !== undefined)
331
+ return runtimeDebug;
332
+ const v = process.env.UNI_STAT_DEBUG;
333
+ return v === 'true' || v === true;
334
+ }
335
+ /**
336
+ * 运行时切换 debug 开关;传 `undefined` 恢复为「按 process.env 判断」。
337
+ */
338
+ function setDebug(value) {
339
+ runtimeDebug = value;
340
+ }
341
+ const logger = {
342
+ debug(...args) {
343
+ if (!isDebug())
344
+ return;
345
+ // eslint-disable-next-line no-console
346
+ emitConsole('log', args);
347
+ },
348
+ info(...args) {
349
+ // eslint-disable-next-line no-console
350
+ emitConsole('info', args);
351
+ },
352
+ warn(...args) {
353
+ // eslint-disable-next-line no-console
354
+ emitConsole('warn', args);
355
+ },
356
+ error(...args) {
357
+ // eslint-disable-next-line no-console
358
+ emitConsole('error', args);
359
+ },
360
+ setDebug,
361
+ isDebug,
362
+ setMuteNonDebug,
363
+ };
364
+
365
+ /**
366
+ * 公有版调试日志:面向业务方的"采集 / 上报"过程日志封装。
367
+ *
368
+ * 与 `logger.debug` 的差异:
369
+ * - `logger.debug` 是底层 console.log + 闸门;调用点散落,文案随意。
370
+ * - 本模块提供**统一文案 / 统一格式**的高层包装,覆盖:
371
+ * 1. 采集动作:每个 lt 都有中文动作名 +「采集 → 数据」标记。
372
+ * 2. 上报生命周期:开始 / 成功 / 失败 / 冷启续传。
373
+ * 3. 启动摘要:通道版本、上报间隔、ak 是否就位等。
374
+ * - 所有 helper 都内嵌 `logger.isDebug()` 判断;非 debug 模式下零开销,
375
+ * 调用方无需再写 `if (logger.isDebug()) ...`。
376
+ *
377
+ * 文案风格参考私有版 `utils/pageInfo.js#log`:直接面向业务调试,**中文**为主,
378
+ * 关键字段(lt / 通道 / 用时 / 错误原因)一目了然。
379
+ *
380
+ * 注意:不在此处吞错;任意 console.log 异常仍会冒泡。运行时调用方需要 `tryRun` 兜底
381
+ * 时自行处理(一般 console.log 不会抛错,故未做包装)。
382
+ */
383
+ /**
384
+ * `lt` → 用户友好的中文动作名映射。
385
+ *
386
+ * 与私有版 `pageInfo.js#log` 的 msg_type 对齐。
387
+ * 注:`lt=0` 已废弃(详见 `domain/eventTypes.ts` 头注释),新会话信息直接随 lt=1 上行。
388
+ *
389
+ * 未知 lt 走默认 "未知事件 (lt=X)",便于排查异常上行。
390
+ */
391
+ function getActionLabel(lt) {
392
+ switch (lt) {
393
+ case LT.Launch:
394
+ return '应用启动';
395
+ case LT.Hide:
396
+ return '应用进入后台';
397
+ case LT.Page:
398
+ return '页面切换';
399
+ case LT.Event:
400
+ return '事件触发';
401
+ case LT.Error:
402
+ return '应用错误';
403
+ case LT.Push:
404
+ return 'PUSH 设备标识';
405
+ default:
406
+ return `未知事件 (lt=${String(lt !== null && lt !== void 0 ? lt : '?')})`;
407
+ }
408
+ }
409
+ /**
410
+ * 单次事件采集日志。
411
+ *
412
+ * 文案示意:
413
+ * ```text
414
+ * [uni统计 2.0] === 统计数据采集:应用启动 (lt=1) ===
415
+ * [uni统计 2.0] {lt: '1', t: 1714123456, ut: 'h5', ...}
416
+ * [uni统计 2.0] === 采集结束 ===
417
+ * ```
418
+ */
419
+ function logCollect(data) {
420
+ if (!logger.isDebug())
421
+ return;
422
+ const lt = data.lt;
423
+ const label = getActionLabel(lt);
424
+ logger.debug(`=== 统计数据采集:${label} (lt=${String(lt !== null && lt !== void 0 ? lt : '?')}) ===`);
425
+ logger.debug(data);
426
+ logger.debug('=== 采集结束 ===');
427
+ }
428
+
26
429
  const appid = process.env.UNI_APP_ID; // 做应用隔离
27
430
  const dbSet = (name, value) => {
28
431
  let data = uni.getStorageSync('$$STAT__DBDATA:'+appid) || {};
@@ -69,7 +472,9 @@ let statConfig = {
69
472
  appid: process.env.UNI_APP_ID,
70
473
  };
71
474
  let titleJsons = {};
72
- let debug = !!process.env.UNI_STAT_DEBUG || false;
475
+ let debug =
476
+ process.env.UNI_STAT_DEBUG === 'true' ||
477
+ process.env.UNI_STAT_DEBUG === true;
73
478
  // #ifdef VUE3
74
479
  titleJsons = process.env.UNI_STAT_TITLE_JSON;
75
480
  // #endif
@@ -544,52 +949,18 @@ const get_space = (config) => {
544
949
  const is_debug = debug;
545
950
 
546
951
  /**
547
- * 日志输出
548
- * @param {*} data
952
+ * 日志输出(采集 / 上报过程日志,文案与公有版 `debugLog` 对齐)。
953
+ * @param {*} data 采集事件或上报 payload
954
+ * @param {*} type 为真时表示上报阶段
549
955
  */
550
956
  const log = (data, type) => {
551
- let msg_type = '';
552
- switch (data.lt) {
553
- case '1':
554
- msg_type = '应用启动';
555
- break
556
- case '3':
557
- msg_type = '应用进入后台';
558
- break
559
-
560
- case '11':
561
- msg_type = '页面切换';
562
- break
563
- case '21':
564
- msg_type = '事件触发';
565
- break
566
- case '31':
567
- msg_type = '应用错误';
568
- break
569
- case '101':
570
- msg_type = 'PUSH';
571
- break
572
- }
573
-
574
- // #ifdef APP
575
- // 在 app 中,日志转为 字符串
576
- if (typeof data === 'object') {
577
- data = JSON.stringify(data);
578
- }
579
- // #endif
580
-
957
+ if (!logger.isDebug()) return
581
958
  if (type) {
582
- console.log(`=== 统计队列数据上报 ===`);
583
- console.log(data);
584
- console.log(`=== 上报结束 ===`);
959
+ logger.debug('=== 准备上报 ===');
960
+ logger.debug(data);
585
961
  return
586
962
  }
587
-
588
- if (msg_type) {
589
- console.log(`=== 统计数据采集:${msg_type} ===`);
590
- console.log(data);
591
- console.log(`=== 采集结束 ===`);
592
- }
963
+ logCollect(data);
593
964
  };
594
965
 
595
966
  /**
@@ -1643,7 +2014,7 @@ function main() {
1643
2014
  if (is_debug) {
1644
2015
  {
1645
2016
  // #ifndef APP-NVUE
1646
- console.log('=== uni统计开启,version:2.0 ===');
2017
+ logger.debug('=== uni统计 2.0(私有版)已启用 ===');
1647
2018
  // #endif
1648
2019
  }
1649
2020
  load_stat();