@antglobal/rlog-sdk 0.0.1755855517-dev.9 → 1.0.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 (127) hide show
  1. package/README.md +192 -432
  2. package/dist/esm/index.d.ts +14 -41
  3. package/dist/esm/index.d.ts.map +1 -1
  4. package/dist/esm/index.js +92 -76
  5. package/dist/esm/lib/api.d.ts +10 -0
  6. package/dist/esm/lib/api.d.ts.map +1 -1
  7. package/dist/esm/lib/api.js +58 -35
  8. package/dist/esm/lib/config.d.ts +13 -6
  9. package/dist/esm/lib/config.d.ts.map +1 -1
  10. package/dist/esm/lib/config.js +61 -33
  11. package/dist/esm/lib/constants.d.ts +89 -0
  12. package/dist/esm/lib/constants.d.ts.map +1 -0
  13. package/dist/esm/lib/constants.js +145 -0
  14. package/dist/esm/lib/drive/indexeddb-adapt.d.ts +12 -0
  15. package/dist/esm/lib/drive/indexeddb-adapt.d.ts.map +1 -1
  16. package/dist/esm/lib/drive/indexeddb-adapt.js +190 -20
  17. package/dist/esm/lib/drive/localstorage-adapt.d.ts +12 -0
  18. package/dist/esm/lib/drive/localstorage-adapt.d.ts.map +1 -1
  19. package/dist/esm/lib/drive/localstorage-adapt.js +179 -36
  20. package/dist/esm/lib/drive/memory-adapt.d.ts +10 -2
  21. package/dist/esm/lib/drive/memory-adapt.d.ts.map +1 -1
  22. package/dist/esm/lib/drive/memory-adapt.js +118 -26
  23. package/dist/esm/lib/drive/safe-storage.d.ts +24 -0
  24. package/dist/esm/lib/drive/safe-storage.d.ts.map +1 -0
  25. package/dist/esm/lib/drive/safe-storage.js +96 -0
  26. package/dist/esm/lib/drive/storage-interface.d.ts +20 -0
  27. package/dist/esm/lib/drive/storage-interface.d.ts.map +1 -1
  28. package/dist/esm/lib/error-trigger.d.ts +73 -0
  29. package/dist/esm/lib/error-trigger.d.ts.map +1 -0
  30. package/dist/esm/lib/error-trigger.js +162 -0
  31. package/dist/esm/lib/error.d.ts +9 -0
  32. package/dist/esm/lib/error.d.ts.map +1 -1
  33. package/dist/esm/lib/error.js +70 -118
  34. package/dist/esm/lib/init.d.ts +0 -4
  35. package/dist/esm/lib/init.d.ts.map +1 -1
  36. package/dist/esm/lib/init.js +148 -47
  37. package/dist/esm/lib/logger.d.ts +27 -0
  38. package/dist/esm/lib/logger.d.ts.map +1 -0
  39. package/dist/esm/lib/logger.js +79 -0
  40. package/dist/esm/lib/net.d.ts +11 -0
  41. package/dist/esm/lib/net.d.ts.map +1 -1
  42. package/dist/esm/lib/net.js +264 -119
  43. package/dist/esm/lib/request.d.ts.map +1 -1
  44. package/dist/esm/lib/request.js +2 -2
  45. package/dist/esm/lib/router-monitor.d.ts.map +1 -1
  46. package/dist/esm/lib/router-monitor.js +135 -49
  47. package/dist/esm/lib/rrweb.d.ts.map +1 -1
  48. package/dist/esm/lib/rrweb.js +31 -24
  49. package/dist/esm/lib/storage-manager.d.ts +12 -0
  50. package/dist/esm/lib/storage-manager.d.ts.map +1 -1
  51. package/dist/esm/lib/storage-manager.js +129 -46
  52. package/dist/esm/lib/upload-worker-manager.d.ts +47 -0
  53. package/dist/esm/lib/upload-worker-manager.d.ts.map +1 -0
  54. package/dist/esm/lib/upload-worker-manager.js +559 -0
  55. package/dist/esm/lib/upload-worker.d.ts +58 -0
  56. package/dist/esm/lib/upload-worker.d.ts.map +1 -0
  57. package/dist/esm/lib/upload-worker.js +28 -0
  58. package/dist/esm/lib/uploader.d.ts +43 -0
  59. package/dist/esm/lib/uploader.d.ts.map +1 -1
  60. package/dist/esm/lib/uploader.js +464 -102
  61. package/dist/esm/lib/utils.d.ts +75 -0
  62. package/dist/esm/lib/utils.d.ts.map +1 -1
  63. package/dist/esm/lib/utils.js +268 -5
  64. package/dist/lib/index.d.ts +14 -41
  65. package/dist/lib/index.d.ts.map +1 -1
  66. package/dist/lib/index.js +81 -63
  67. package/dist/lib/lib/api.d.ts +10 -0
  68. package/dist/lib/lib/api.d.ts.map +1 -1
  69. package/dist/lib/lib/api.js +59 -35
  70. package/dist/lib/lib/config.d.ts +13 -6
  71. package/dist/lib/lib/config.d.ts.map +1 -1
  72. package/dist/lib/lib/config.js +63 -33
  73. package/dist/lib/lib/constants.d.ts +89 -0
  74. package/dist/lib/lib/constants.d.ts.map +1 -0
  75. package/dist/lib/lib/constants.js +151 -0
  76. package/dist/lib/lib/drive/indexeddb-adapt.d.ts +12 -0
  77. package/dist/lib/lib/drive/indexeddb-adapt.d.ts.map +1 -1
  78. package/dist/lib/lib/drive/indexeddb-adapt.js +191 -19
  79. package/dist/lib/lib/drive/localstorage-adapt.d.ts +12 -0
  80. package/dist/lib/lib/drive/localstorage-adapt.d.ts.map +1 -1
  81. package/dist/lib/lib/drive/localstorage-adapt.js +179 -36
  82. package/dist/lib/lib/drive/memory-adapt.d.ts +10 -2
  83. package/dist/lib/lib/drive/memory-adapt.d.ts.map +1 -1
  84. package/dist/lib/lib/drive/memory-adapt.js +117 -26
  85. package/dist/lib/lib/drive/safe-storage.d.ts +24 -0
  86. package/dist/lib/lib/drive/safe-storage.d.ts.map +1 -0
  87. package/dist/lib/lib/drive/safe-storage.js +102 -0
  88. package/dist/lib/lib/drive/storage-interface.d.ts +20 -0
  89. package/dist/lib/lib/drive/storage-interface.d.ts.map +1 -1
  90. package/dist/lib/lib/error-trigger.d.ts +73 -0
  91. package/dist/lib/lib/error-trigger.d.ts.map +1 -0
  92. package/dist/lib/lib/error-trigger.js +167 -0
  93. package/dist/lib/lib/error.d.ts +9 -0
  94. package/dist/lib/lib/error.d.ts.map +1 -1
  95. package/dist/lib/lib/error.js +73 -118
  96. package/dist/lib/lib/init.d.ts +0 -4
  97. package/dist/lib/lib/init.d.ts.map +1 -1
  98. package/dist/lib/lib/init.js +144 -43
  99. package/dist/lib/lib/logger.d.ts +27 -0
  100. package/dist/lib/lib/logger.d.ts.map +1 -0
  101. package/dist/lib/lib/logger.js +84 -0
  102. package/dist/lib/lib/net.d.ts +11 -0
  103. package/dist/lib/lib/net.d.ts.map +1 -1
  104. package/dist/lib/lib/net.js +268 -120
  105. package/dist/lib/lib/request.d.ts.map +1 -1
  106. package/dist/lib/lib/request.js +3 -3
  107. package/dist/lib/lib/router-monitor.d.ts.map +1 -1
  108. package/dist/lib/lib/router-monitor.js +136 -49
  109. package/dist/lib/lib/rrweb.d.ts.map +1 -1
  110. package/dist/lib/lib/rrweb.js +31 -23
  111. package/dist/lib/lib/storage-manager.d.ts +12 -0
  112. package/dist/lib/lib/storage-manager.d.ts.map +1 -1
  113. package/dist/lib/lib/storage-manager.js +130 -46
  114. package/dist/lib/lib/upload-worker-manager.d.ts +47 -0
  115. package/dist/lib/lib/upload-worker-manager.d.ts.map +1 -0
  116. package/dist/lib/lib/upload-worker-manager.js +570 -0
  117. package/dist/lib/lib/upload-worker.d.ts +58 -0
  118. package/dist/lib/lib/upload-worker.d.ts.map +1 -0
  119. package/dist/lib/lib/upload-worker.js +33 -0
  120. package/dist/lib/lib/uploader.d.ts +43 -0
  121. package/dist/lib/lib/uploader.d.ts.map +1 -1
  122. package/dist/lib/lib/uploader.js +468 -101
  123. package/dist/lib/lib/utils.d.ts +75 -0
  124. package/dist/lib/lib/utils.d.ts.map +1 -1
  125. package/dist/lib/lib/utils.js +276 -6
  126. package/dist/rlog-sdk.min.js +1 -1
  127. package/package.json +3 -2
@@ -0,0 +1,559 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw new Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator.return && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw new Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, catch: function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
3
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
4
+ function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
5
+ /**
6
+ * Upload Worker 管理器
7
+ *
8
+ * 职责:
9
+ * - Worker 生命周期管理(创建、销毁、降级)
10
+ * - 将 Worker 脚本以 inline Blob 方式加载
11
+ * - 封装 postMessage/onmessage 通信
12
+ * - 处理 Worker 回传的消息,调用对应的主线程逻辑
13
+ * - 提供与现有 uploader.ts 相同的对外 API
14
+ */
15
+
16
+ import { getUploadWorkerCode } from "./upload-worker";
17
+ import { storage } from "./storage-manager";
18
+ import { getDeviceId, getStorageKey } from "./utils";
19
+ import { getConfig } from "./config";
20
+ import { buildQueryParams } from "./api";
21
+ import { getCustomHeaders } from "./net";
22
+ import { record } from 'rrweb';
23
+ import logger from "./logger";
24
+ import { STORAGE_KEY_CONSUME_ONLY, STORAGE_VALUE_CONSUME_ONLY, EVENT_UPLOAD_FAILURE, PULL_BATCH_SIZE, WORKER_READY_TIMEOUT } from "./constants";
25
+
26
+ // Worker 实例
27
+ var worker = null;
28
+
29
+ // Worker 是否就绪
30
+ var workerReady = false;
31
+
32
+ // 是否正在使用 Worker 模式
33
+ var isWorkerMode = false;
34
+
35
+ // 当前配置缓存
36
+ var currentAppId = '';
37
+
38
+ // 错误模式状态
39
+ var errorModeUploading = false;
40
+ var errorModeWindowStart = 0;
41
+
42
+ // 上传锁(Worker 模式下用于防止并发数据读取)
43
+ var isFetchingBatch = false;
44
+
45
+ // batchId 计数器
46
+ var batchIdCounter = 0;
47
+
48
+ // Worker 就绪超时检测
49
+ var workerReadyTimeout = null;
50
+
51
+ // Worker 降级回调(当 Worker 模式失败时调用)
52
+ var onWorkerFallback = null;
53
+
54
+ // 由 init.ts 注入的取消回调,解除循环依赖
55
+ var onCancelCallback = null;
56
+
57
+ /**
58
+ * 设置 SDK 取消回调(由 init.ts 在启动上传循环前注入)
59
+ */
60
+ export function setWorkerCancelCallback(callback) {
61
+ onCancelCallback = callback;
62
+ }
63
+
64
+ /**
65
+ * 生成唯一的 batchId
66
+ */
67
+ function generateBatchId() {
68
+ return "batch_".concat(Date.now(), "_").concat(++batchIdCounter);
69
+ }
70
+
71
+ /**
72
+ * 创建内联 Worker
73
+ * 使用 Blob URL 方式,避免额外的文件加载问题
74
+ */
75
+ function createInlineWorker() {
76
+ try {
77
+ // 检查 Worker 是否可用
78
+ if (typeof Worker === 'undefined') {
79
+ return null;
80
+ }
81
+ var workerCode = getUploadWorkerCode();
82
+ var blob = new Blob([workerCode], {
83
+ type: 'application/javascript'
84
+ });
85
+ var url = URL.createObjectURL(blob);
86
+ var w = new Worker(url);
87
+ // 创建后立即释放 Blob URL,Worker 已经加载了代码
88
+ URL.revokeObjectURL(url);
89
+ return w;
90
+ } catch (e) {
91
+ logger.warn('Failed to create inline Worker, will fallback to main thread:', e);
92
+ return null;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * 处理 Worker 发来的消息
98
+ */
99
+ function handleWorkerMessage(e) {
100
+ var msg = e.data;
101
+ if (!msg || !msg.type) {
102
+ logger.warn('Received invalid Worker message:', msg);
103
+ return;
104
+ }
105
+ switch (msg.type) {
106
+ case 'READY':
107
+ workerReady = true;
108
+ // 清除超时检测
109
+ if (workerReadyTimeout) {
110
+ clearTimeout(workerReadyTimeout);
111
+ workerReadyTimeout = null;
112
+ }
113
+ // Worker is ready
114
+ break;
115
+ case 'REQUEST_BATCH':
116
+ handleRequestBatch();
117
+ break;
118
+ case 'UPLOAD_SUCCESS':
119
+ handleUploadSuccess(msg.batchId);
120
+ break;
121
+ case 'UPLOAD_FAILURE':
122
+ handleUploadFailure(msg.batchId, msg.error, msg.failureCount);
123
+ break;
124
+ case 'MAX_RETRY_REACHED':
125
+ handleMaxRetryReached(msg.batchId, msg.error);
126
+ break;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * 处理 Worker 请求数据批次
132
+ * Worker 内部定时器触发,主线程从 storage 读取数据后发给 Worker
133
+ */
134
+ function handleRequestBatch() {
135
+ return _handleRequestBatch.apply(this, arguments);
136
+ } // 待处理的批次元数据
137
+ function _handleRequestBatch() {
138
+ _handleRequestBatch = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
139
+ var currentConfig, isErrorMode, events, windowStart, config, consumeOnly, queryParams, customHeaders, batchId;
140
+ return _regeneratorRuntime().wrap(function _callee$(_context) {
141
+ while (1) switch (_context.prev = _context.next) {
142
+ case 0:
143
+ if (!isFetchingBatch) {
144
+ _context.next = 2;
145
+ break;
146
+ }
147
+ return _context.abrupt("return");
148
+ case 2:
149
+ // 检查采集总开关
150
+ currentConfig = getConfig();
151
+ if (currentConfig.enable) {
152
+ _context.next = 5;
153
+ break;
154
+ }
155
+ return _context.abrupt("return");
156
+ case 5:
157
+ isErrorMode = currentConfig.captureMode === 'error'; // 错误模式下,如果未处于 uploading 状态,跳过
158
+ if (!(isErrorMode && !errorModeUploading)) {
159
+ _context.next = 8;
160
+ break;
161
+ }
162
+ return _context.abrupt("return");
163
+ case 8:
164
+ _context.prev = 8;
165
+ isFetchingBatch = true;
166
+ windowStart = 0;
167
+ if (!isErrorMode) {
168
+ _context.next = 19;
169
+ break;
170
+ }
171
+ // 错误模式:按时间范围提取事件
172
+ windowStart = errorModeWindowStart > 0 ? errorModeWindowStart : Date.now();
173
+ _context.next = 15;
174
+ return storage.pullByTimeRange(getStorageKey(), windowStart, Date.now());
175
+ case 15:
176
+ events = _context.sent;
177
+ errorModeWindowStart = 0;
178
+ _context.next = 22;
179
+ break;
180
+ case 19:
181
+ _context.next = 21;
182
+ return storage.pull(getStorageKey(), PULL_BATCH_SIZE);
183
+ case 21:
184
+ events = _context.sent;
185
+ case 22:
186
+ if (!(!events || events.length === 0)) {
187
+ _context.next = 25;
188
+ break;
189
+ }
190
+ isFetchingBatch = false;
191
+ return _context.abrupt("return");
192
+ case 25:
193
+ // 检查是否只消费不上报
194
+ config = getConfig();
195
+ consumeOnly = sessionStorage.getItem(STORAGE_KEY_CONSUME_ONLY) === STORAGE_VALUE_CONSUME_ONLY || config.consumeOnly;
196
+ if (!consumeOnly) {
197
+ _context.next = 30;
198
+ break;
199
+ }
200
+ isFetchingBatch = false;
201
+ return _context.abrupt("return");
202
+ case 30:
203
+ // 构建查询参数
204
+ queryParams = buildQueryParams({
205
+ appId: currentAppId,
206
+ deviceId: getDeviceId(),
207
+ data: events
208
+ }); // 获取自定义请求头
209
+ customHeaders = getCustomHeaders(); // 生成 batchId 并记录元数据(用于成功/失败回调)
210
+ batchId = generateBatchId(); // 将批次元数据存储起来,供回调使用
211
+ pendingBatches.set(batchId, {
212
+ events: events,
213
+ isErrorMode: isErrorMode,
214
+ windowStart: windowStart
215
+ });
216
+
217
+ // 发送数据给 Worker 执行上传
218
+ sendToWorker({
219
+ type: 'UPLOAD_BATCH',
220
+ batchId: batchId,
221
+ events: events,
222
+ queryParams: queryParams,
223
+ customHeaders: customHeaders
224
+ });
225
+ isFetchingBatch = false;
226
+ _context.next = 42;
227
+ break;
228
+ case 38:
229
+ _context.prev = 38;
230
+ _context.t0 = _context["catch"](8);
231
+ isFetchingBatch = false;
232
+ logger.error('Error fetching batch for Worker:', _context.t0);
233
+ case 42:
234
+ case "end":
235
+ return _context.stop();
236
+ }
237
+ }, _callee, null, [[8, 38]]);
238
+ }));
239
+ return _handleRequestBatch.apply(this, arguments);
240
+ }
241
+ var pendingBatches = new Map();
242
+
243
+ /**
244
+ * 处理上传成功
245
+ */
246
+ function handleUploadSuccess(_x) {
247
+ return _handleUploadSuccess.apply(this, arguments);
248
+ }
249
+ /**
250
+ * 处理上传失败(未达到最大重试次数)
251
+ */
252
+ function _handleUploadSuccess() {
253
+ _handleUploadSuccess = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(batchId) {
254
+ var batch, lastEvent;
255
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
256
+ while (1) switch (_context2.prev = _context2.next) {
257
+ case 0:
258
+ batch = pendingBatches.get(batchId);
259
+ if (batch) {
260
+ _context2.next = 3;
261
+ break;
262
+ }
263
+ return _context2.abrupt("return");
264
+ case 3:
265
+ pendingBatches.delete(batchId);
266
+ _context2.prev = 4;
267
+ if (!(batch.isErrorMode && batch.windowStart > 0 && batch.events.length > 0)) {
268
+ _context2.next = 10;
269
+ break;
270
+ }
271
+ _context2.next = 8;
272
+ return storage.removeByTimeRange(getStorageKey(), batch.windowStart);
273
+ case 8:
274
+ // 更新窗口起点为最后一个事件的时间戳
275
+ lastEvent = batch.events[batch.events.length - 1];
276
+ if (lastEvent && lastEvent.timestamp) {
277
+ errorModeWindowStart = lastEvent.timestamp;
278
+ }
279
+ case 10:
280
+ _context2.next = 15;
281
+ break;
282
+ case 12:
283
+ _context2.prev = 12;
284
+ _context2.t0 = _context2["catch"](4);
285
+ logger.warn('Error in handleUploadSuccess:', _context2.t0);
286
+ case 15:
287
+ case "end":
288
+ return _context2.stop();
289
+ }
290
+ }, _callee2, null, [[4, 12]]);
291
+ }));
292
+ return _handleUploadSuccess.apply(this, arguments);
293
+ }
294
+ function handleUploadFailure(_x2, _x3, _x4) {
295
+ return _handleUploadFailure.apply(this, arguments);
296
+ }
297
+ /**
298
+ * 处理达到最大重试次数
299
+ */
300
+ function _handleUploadFailure() {
301
+ _handleUploadFailure = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(batchId, error, failureCount) {
302
+ var batch;
303
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
304
+ while (1) switch (_context3.prev = _context3.next) {
305
+ case 0:
306
+ batch = pendingBatches.get(batchId);
307
+ if (batch) {
308
+ _context3.next = 3;
309
+ break;
310
+ }
311
+ return _context3.abrupt("return");
312
+ case 3:
313
+ pendingBatches.delete(batchId);
314
+ logger.warn("Upload failed ".concat(failureCount, " time(s), will retry in next cycle:"), error);
315
+ _context3.prev = 5;
316
+ _context3.next = 8;
317
+ return storage.unshiftBatch(getStorageKey(), batch.events);
318
+ case 8:
319
+ _context3.next = 13;
320
+ break;
321
+ case 10:
322
+ _context3.prev = 10;
323
+ _context3.t0 = _context3["catch"](5);
324
+ logger.warn('Failed to push events back to storage:', _context3.t0);
325
+ case 13:
326
+ case "end":
327
+ return _context3.stop();
328
+ }
329
+ }, _callee3, null, [[5, 10]]);
330
+ }));
331
+ return _handleUploadFailure.apply(this, arguments);
332
+ }
333
+ function handleMaxRetryReached(_x5, _x6) {
334
+ return _handleMaxRetryReached.apply(this, arguments);
335
+ }
336
+ /**
337
+ * 向 Worker 发送消息
338
+ */
339
+ function _handleMaxRetryReached() {
340
+ _handleMaxRetryReached = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(batchId, error) {
341
+ var batch, config, maxRetryCount, customEvent;
342
+ return _regeneratorRuntime().wrap(function _callee4$(_context4) {
343
+ while (1) switch (_context4.prev = _context4.next) {
344
+ case 0:
345
+ batch = pendingBatches.get(batchId);
346
+ if (batch) {
347
+ _context4.next = 3;
348
+ break;
349
+ }
350
+ return _context4.abrupt("return");
351
+ case 3:
352
+ pendingBatches.delete(batchId);
353
+ config = getConfig();
354
+ maxRetryCount = config.maxRetryCount || 3;
355
+ logger.warn("Upload failed ".concat(maxRetryCount, " times consecutively, stopping upload"));
356
+
357
+ // 将事件放回存储
358
+ _context4.next = 9;
359
+ return storage.unshiftBatch(getStorageKey(), batch.events);
360
+ case 9:
361
+ // 创建并派发自定义事件
362
+ customEvent = new CustomEvent(EVENT_UPLOAD_FAILURE, {
363
+ detail: {
364
+ error: error,
365
+ retryCount: maxRetryCount,
366
+ appId: currentAppId,
367
+ deviceId: getDeviceId()
368
+ }
369
+ });
370
+ if (typeof window !== 'undefined') {
371
+ window.dispatchEvent(customEvent);
372
+ }
373
+
374
+ // 使用 addCustomEvent 记录上传失败事件
375
+ try {
376
+ record.addCustomEvent(EVENT_UPLOAD_FAILURE, {
377
+ error: error,
378
+ retryCount: maxRetryCount,
379
+ appId: currentAppId,
380
+ deviceId: getDeviceId(),
381
+ timestamp: Date.now()
382
+ });
383
+ } catch (e) {
384
+ logger.warn('Failed to record upload failure event via addCustomEvent:', e);
385
+ }
386
+
387
+ // 停止 Worker
388
+ destroyWorker();
389
+
390
+ // 停止 rrweb 录制(通过回调避免循环依赖)
391
+ if (onCancelCallback) {
392
+ setTimeout(function () {
393
+ try {
394
+ var _onCancelCallback;
395
+ (_onCancelCallback = onCancelCallback) === null || _onCancelCallback === void 0 || _onCancelCallback();
396
+ } catch (error) {
397
+ logger.error('Error in cancel callback:', error);
398
+ }
399
+ }, 0);
400
+ }
401
+ logger.error('Upload and recording stopped due to repeated failures');
402
+ case 15:
403
+ case "end":
404
+ return _context4.stop();
405
+ }
406
+ }, _callee4);
407
+ }));
408
+ return _handleMaxRetryReached.apply(this, arguments);
409
+ }
410
+ function sendToWorker(command) {
411
+ if (worker && workerReady) {
412
+ try {
413
+ worker.postMessage(command);
414
+ } catch (error) {
415
+ logger.warn('Failed to send message to Worker:', error);
416
+ }
417
+ } else {
418
+ logger.warn('Cannot send to Worker - worker:', !!worker, 'workerReady:', workerReady, 'command:', command.type);
419
+ }
420
+ }
421
+
422
+ /**
423
+ * 处理 Worker 错误
424
+ */
425
+ function handleWorkerError(e) {
426
+ logger.error('Upload Worker error:', e.message);
427
+ // Worker 出错时销毁,降级到主线程
428
+ destroyWorker(true);
429
+ }
430
+
431
+ // ==================== 对外 API ====================
432
+
433
+ /**
434
+ * 设置 Worker 降级回调
435
+ * @param callback 降级回调函数
436
+ */
437
+ export function setWorkerFallbackCallback(callback) {
438
+ onWorkerFallback = callback;
439
+ }
440
+
441
+ /**
442
+ * 尝试初始化 Worker 模式
443
+ * @returns 是否成功启用 Worker 模式
444
+ */
445
+ export function initWorkerUpload(serv, appId) {
446
+ currentAppId = appId;
447
+
448
+ // 尝试创建 Worker
449
+ var w = createInlineWorker();
450
+ if (!w) {
451
+ isWorkerMode = false;
452
+ return false;
453
+ }
454
+ worker = w;
455
+ worker.onmessage = handleWorkerMessage;
456
+ worker.onerror = handleWorkerError;
457
+
458
+ // 发送启动指令
459
+ var config = getConfig();
460
+ var startConfig = {
461
+ serv: serv,
462
+ appId: appId,
463
+ storageKey: getStorageKey(),
464
+ uploadInterval: config.uploadInterval,
465
+ maxRetryCount: config.maxRetryCount || 3
466
+ };
467
+
468
+ // 注意:这里先将 isWorkerMode 设为 true
469
+ // 但 workerReady 仍为 false,需要等待 Worker 发送 READY 消息
470
+ // sendToWorker 会检查 workerReady 状态
471
+ worker.postMessage({
472
+ type: 'START',
473
+ config: startConfig
474
+ });
475
+ isWorkerMode = true;
476
+
477
+ // 设置超时检测:如果指定时间内没有收到 READY 消息,降级到主线程模式
478
+ workerReadyTimeout = setTimeout(function () {
479
+ if (!workerReady) {
480
+ logger.warn("Worker failed to send READY within ".concat(WORKER_READY_TIMEOUT, "ms, falling back to main thread"));
481
+ // 销毁并触发降级回调
482
+ destroyWorker(true);
483
+ }
484
+ }, WORKER_READY_TIMEOUT);
485
+ return true;
486
+ }
487
+
488
+ /**
489
+ * 销毁 Worker
490
+ * @param triggerFallback 是否触发降级回调
491
+ */
492
+ export function destroyWorker() {
493
+ var triggerFallback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
494
+ // 清理超时检测
495
+ if (workerReadyTimeout) {
496
+ clearTimeout(workerReadyTimeout);
497
+ workerReadyTimeout = null;
498
+ }
499
+ if (worker) {
500
+ try {
501
+ worker.postMessage({
502
+ type: 'STOP'
503
+ });
504
+ worker.terminate();
505
+ } catch (e) {
506
+ // 忽略终止错误
507
+ }
508
+ worker = null;
509
+ }
510
+ workerReady = false;
511
+ isWorkerMode = false;
512
+ isFetchingBatch = false;
513
+ errorModeUploading = false;
514
+ errorModeWindowStart = 0;
515
+ batchIdCounter = 0;
516
+ pendingBatches.clear();
517
+
518
+ // 触发降级回调(仅在需要时)
519
+ if (triggerFallback && onWorkerFallback) {
520
+ onWorkerFallback();
521
+ }
522
+ }
523
+
524
+ /**
525
+ * 是否正在使用 Worker 模式
526
+ */
527
+ export function isUsingWorkerMode() {
528
+ return isWorkerMode && worker !== null;
529
+ }
530
+
531
+ /**
532
+ * 设置错误模式上传状态(Worker 模式)
533
+ */
534
+ export function setWorkerErrorModeUploading(uploading) {
535
+ errorModeUploading = uploading;
536
+
537
+ // 同步通知 Worker
538
+ sendToWorker({
539
+ type: 'SET_ERROR_MODE_UPLOADING',
540
+ uploading: uploading
541
+ });
542
+ }
543
+
544
+ /**
545
+ * 设置错误模式上传时间窗口起点(Worker 模式)
546
+ */
547
+ export function setWorkerErrorModeWindowStart(startTime) {
548
+ errorModeWindowStart = startTime;
549
+ }
550
+
551
+ /**
552
+ * 更新 Worker 配置
553
+ */
554
+ export function updateWorkerConfig(config) {
555
+ sendToWorker({
556
+ type: 'UPDATE_CONFIG',
557
+ config: config
558
+ });
559
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Upload Worker 脚本
3
+ *
4
+ * 在 Web Worker 中执行上传任务,将 JSON 序列化 + FormData 构建 + XHR 上传
5
+ * 从主线程移到 Worker 线程,避免阻塞 UI。
6
+ *
7
+ * 通信协议:
8
+ * 主线程 → Worker: WorkerCommand
9
+ * Worker → 主线程: WorkerMessage
10
+ */
11
+ export interface WorkerConfig {
12
+ serv: string;
13
+ appId: string;
14
+ storageKey: string;
15
+ uploadInterval: number;
16
+ maxRetryCount: number;
17
+ }
18
+ export type WorkerCommand = {
19
+ type: 'START';
20
+ config: WorkerConfig;
21
+ } | {
22
+ type: 'STOP';
23
+ } | {
24
+ type: 'UPLOAD_BATCH';
25
+ batchId: string;
26
+ events: any[];
27
+ queryParams: Record<string, string>;
28
+ customHeaders: Record<string, string>;
29
+ } | {
30
+ type: 'UPDATE_CONFIG';
31
+ config: Partial<WorkerConfig>;
32
+ } | {
33
+ type: 'SET_ERROR_MODE_UPLOADING';
34
+ uploading: boolean;
35
+ };
36
+ export type WorkerMessage = {
37
+ type: 'READY';
38
+ } | {
39
+ type: 'REQUEST_BATCH';
40
+ } | {
41
+ type: 'UPLOAD_SUCCESS';
42
+ batchId: string;
43
+ } | {
44
+ type: 'UPLOAD_FAILURE';
45
+ batchId: string;
46
+ error: string;
47
+ failureCount: number;
48
+ } | {
49
+ type: 'MAX_RETRY_REACHED';
50
+ batchId: string;
51
+ error: string;
52
+ };
53
+ /**
54
+ * 返回 Worker 脚本的源代码字符串
55
+ * 用于通过 Blob URL 内联创建 Worker
56
+ */
57
+ export declare function getUploadWorkerCode(): string;
58
+ //# sourceMappingURL=upload-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-worker.d.ts","sourceRoot":"","sources":["../../../src/lib/upload-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAGD,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,GAAG,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACpI;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,0BAA0B,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC;AAG7D,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,GACzB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAChF;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAIlE;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAmN5C"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Upload Worker 脚本
3
+ *
4
+ * 在 Web Worker 中执行上传任务,将 JSON 序列化 + FormData 构建 + XHR 上传
5
+ * 从主线程移到 Worker 线程,避免阻塞 UI。
6
+ *
7
+ * 通信协议:
8
+ * 主线程 → Worker: WorkerCommand
9
+ * Worker → 主线程: WorkerMessage
10
+ */
11
+
12
+ import { REQUEST_TIMEOUT } from "./constants";
13
+
14
+ // ==================== 类型定义 ====================
15
+
16
+ // 主线程 → Worker
17
+
18
+ // Worker → 主线程
19
+
20
+ // ==================== Worker 代码(字符串形式) ====================
21
+
22
+ /**
23
+ * 返回 Worker 脚本的源代码字符串
24
+ * 用于通过 Blob URL 内联创建 Worker
25
+ */
26
+ export function getUploadWorkerCode() {
27
+ return "\n'use strict';\n\n// ===== Worker \u5185\u90E8\u72B6\u6001 =====\nvar config = null;\nvar pollTimer = null;\nvar isRunning = false;\nvar failureCountMap = {};\n\n// ===== UUID \u751F\u6210 =====\nfunction generateUUID() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = (Math.random() * 16) | 0;\n var v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n// ===== \u6784\u5EFA\u4E0A\u4F20 URL =====\nfunction buildUploadUrl(serv, queryParams) {\n var params = [];\n for (var key in queryParams) {\n if (queryParams.hasOwnProperty(key) && queryParams[key] != null && queryParams[key] !== '') {\n params.push(encodeURIComponent(key) + '=' + encodeURIComponent(queryParams[key]));\n }\n }\n var queryString = params.join('&');\n return serv + (queryString ? '?' + queryString : '');\n}\n\n// ===== \u6267\u884C\u4E0A\u4F20 =====\nfunction performUpload(msg) {\n var batchId = msg.batchId;\n var events = msg.events;\n var queryParams = msg.queryParams;\n var customHeaders = msg.customHeaders;\n\n try {\n // JSON \u5E8F\u5217\u5316\u5728 Worker \u4E2D\u6267\u884C\uFF0C\u4E0D\u963B\u585E\u4E3B\u7EBF\u7A0B\n var jsonStr = JSON.stringify(events);\n var blob = new Blob([jsonStr], { type: 'application/json' });\n var formData = new FormData();\n formData.append('file', blob, generateUUID() + '.json');\n\n var url = buildUploadUrl(config ? config.serv : '', queryParams);\n\n var xhr = new XMLHttpRequest();\n xhr.open('POST', url, true);\n xhr.timeout = ".concat(REQUEST_TIMEOUT, ";\n\n // \u8BBE\u7F6E\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n if (customHeaders) {\n for (var key in customHeaders) {\n if (customHeaders.hasOwnProperty(key)) {\n xhr.setRequestHeader(key, customHeaders[key]);\n }\n }\n }\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n if (xhr.status >= 200 && xhr.status < 300) {\n // \u68C0\u67E5\u4E1A\u52A1\u5C42 success \u5B57\u6BB5\n var uploadSuccess = true;\n var errorMessage = '';\n try {\n var responseData = JSON.parse(xhr.responseText);\n if (responseData && typeof responseData === 'object' && responseData.success === false) {\n uploadSuccess = false;\n errorMessage = responseData.errorMessage || 'Upload failed with success=false';\n }\n } catch(e) {\n // \u65E0\u6CD5\u89E3\u6790 JSON\uFF0C\u89C6\u4E3A\u6210\u529F\n }\n\n if (uploadSuccess) {\n // \u4E0A\u4F20\u6210\u529F\uFF0C\u91CD\u7F6E\u5931\u8D25\u8BA1\u6570\n var batchKey = config ? (config.appId + '_' + config.storageKey) : batchId;\n delete failureCountMap[batchKey];\n self.postMessage({ type: 'UPLOAD_SUCCESS', batchId: batchId });\n } else {\n handleFailure(batchId, errorMessage);\n }\n } else {\n handleFailure(batchId, 'HTTP ' + xhr.status + ': ' + xhr.statusText);\n }\n }\n };\n\n xhr.onerror = function() {\n handleFailure(batchId, 'Network error');\n };\n\n xhr.ontimeout = function() {\n handleFailure(batchId, 'Request timeout');\n };\n\n xhr.send(formData);\n } catch (e) {\n handleFailure(batchId, 'Upload exception: ' + (e.message || String(e)));\n }\n}\n\n// ===== \u5904\u7406\u4E0A\u4F20\u5931\u8D25 =====\nfunction handleFailure(batchId, errorMessage) {\n var batchKey = config ? (config.appId + '_' + config.storageKey) : batchId;\n var currentCount = failureCountMap[batchKey] || 0;\n var newCount = currentCount + 1;\n failureCountMap[batchKey] = newCount;\n\n var maxRetry = (config && config.maxRetryCount) ? config.maxRetryCount : 3;\n\n if (newCount >= maxRetry) {\n // \u8FBE\u5230\u6700\u5927\u91CD\u8BD5\u6B21\u6570\n delete failureCountMap[batchKey];\n self.postMessage({\n type: 'MAX_RETRY_REACHED',\n batchId: batchId,\n error: errorMessage\n });\n } else {\n self.postMessage({\n type: 'UPLOAD_FAILURE',\n batchId: batchId,\n error: errorMessage,\n failureCount: newCount\n });\n }\n}\n\n// ===== \u8F6E\u8BE2\u8C03\u5EA6 =====\nfunction startPolling() {\n if (pollTimer) {\n clearInterval(pollTimer);\n }\n\n var interval = (config && config.uploadInterval) ? config.uploadInterval : 2000;\n\n pollTimer = setInterval(function() {\n if (!isRunning) return;\n\n // \u5411\u4E3B\u7EBF\u7A0B\u8BF7\u6C42\u6570\u636E\u6279\u6B21\n // \u4E3B\u7EBF\u7A0B\u4F1A\u6839\u636E\u5F53\u524D\u6A21\u5F0F\uFF08full/error\uFF09\u548C\u72B6\u6001\u51B3\u5B9A\u662F\u5426\u63D0\u4F9B\u6570\u636E\n self.postMessage({\n type: 'REQUEST_BATCH'\n });\n }, interval);\n}\n\nfunction stopPolling() {\n if (pollTimer) {\n clearInterval(pollTimer);\n pollTimer = null;\n }\n}\n\n// ===== \u6E05\u7406 =====\nfunction cleanup() {\n isRunning = false;\n stopPolling();\n failureCountMap = {};\n config = null;\n}\n\n// ===== \u6D88\u606F\u5904\u7406 =====\nself.onmessage = function(e) {\n var msg = e.data;\n if (!msg || !msg.type) return;\n\n switch (msg.type) {\n case 'START':\n config = msg.config;\n isRunning = true;\n failureCountMap = {};\n startPolling();\n break;\n\n case 'STOP':\n cleanup();\n break;\n\n case 'UPLOAD_BATCH':\n if (isRunning) {\n performUpload(msg);\n }\n break;\n\n case 'UPDATE_CONFIG':\n if (msg.config) {\n for (var key in msg.config) {\n if (msg.config.hasOwnProperty(key) && config) {\n config[key] = msg.config[key];\n }\n }\n // \u5982\u679C uploadInterval \u53D8\u4E86\uFF0C\u91CD\u542F\u8F6E\u8BE2\n if (msg.config.uploadInterval && isRunning) {\n startPolling();\n }\n }\n break;\n\n case 'SET_ERROR_MODE_UPLOADING':\n // \u72B6\u6001\u7531\u4E3B\u7EBF\u7A0B\u7BA1\u7406\uFF0CWorker \u4EC5\u63A5\u6536\u6307\u4EE4\u4FDD\u6301\u534F\u8BAE\u517C\u5BB9\n break;\n }\n};\n\n// Worker \u5C31\u7EEA\u901A\u77E5\nself.postMessage({ type: 'READY' });\n");
28
+ }