@clipto/reporter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +130 -0
- package/dist/index.cjs +557 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +188 -0
- package/dist/index.d.ts +188 -0
- package/dist/index.js +515 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
// src/id.ts
|
|
2
|
+
function generateEventId() {
|
|
3
|
+
return `evt_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/builtins.ts
|
|
7
|
+
var SDK_VERSION = "1.0.0";
|
|
8
|
+
var DB_NAME = "CliptoReportDB";
|
|
9
|
+
var STORE_NAME = "clipto-reporting";
|
|
10
|
+
var QUEUE_KEY = "pending";
|
|
11
|
+
var LS_KEY = "clipto_report_pending_queue";
|
|
12
|
+
function buildPayload(events) {
|
|
13
|
+
return {
|
|
14
|
+
// attempts 为 SDK 内部字段,不上报;name 输出为 eventName(对齐旧 SDK)
|
|
15
|
+
events: events.map(
|
|
16
|
+
({
|
|
17
|
+
id,
|
|
18
|
+
name,
|
|
19
|
+
timestamp,
|
|
20
|
+
sessionId,
|
|
21
|
+
userId,
|
|
22
|
+
deviceId,
|
|
23
|
+
properties,
|
|
24
|
+
category,
|
|
25
|
+
context
|
|
26
|
+
}) => ({
|
|
27
|
+
id,
|
|
28
|
+
eventName: name,
|
|
29
|
+
timestamp,
|
|
30
|
+
sessionId,
|
|
31
|
+
userId,
|
|
32
|
+
deviceId,
|
|
33
|
+
properties,
|
|
34
|
+
category,
|
|
35
|
+
context
|
|
36
|
+
})
|
|
37
|
+
),
|
|
38
|
+
metadata: {
|
|
39
|
+
sdkVersion: SDK_VERSION,
|
|
40
|
+
timestamp: Date.now(),
|
|
41
|
+
batchId: generateEventId()
|
|
42
|
+
// 与事件 id 同格式(对齐旧 SDK)
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function createBrowserTransport(endpoint, headers) {
|
|
47
|
+
const commonHeaders = {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
...headers ?? {}
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
async send(events) {
|
|
53
|
+
const response = await fetch(endpoint, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: commonHeaders,
|
|
56
|
+
body: JSON.stringify(buildPayload(events)),
|
|
57
|
+
keepalive: true
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new Error(`report send failed: HTTP ${response.status}`);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
sendSync(events) {
|
|
64
|
+
if (typeof navigator === "undefined" || typeof navigator.sendBeacon !== "function") {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const blob = new Blob([JSON.stringify(buildPayload(events))], {
|
|
69
|
+
type: "application/json"
|
|
70
|
+
});
|
|
71
|
+
return navigator.sendBeacon(endpoint, blob);
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function createNodeTransport(endpoint, headers) {
|
|
79
|
+
const { default: axios } = await import("axios");
|
|
80
|
+
return {
|
|
81
|
+
async send(events) {
|
|
82
|
+
await axios.post(endpoint, buildPayload(events), {
|
|
83
|
+
headers,
|
|
84
|
+
timeout: 1e4
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function createLocalStorageStorage() {
|
|
90
|
+
return {
|
|
91
|
+
save(events) {
|
|
92
|
+
try {
|
|
93
|
+
localStorage.setItem(LS_KEY, JSON.stringify(events));
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
load() {
|
|
98
|
+
try {
|
|
99
|
+
const text = localStorage.getItem(LS_KEY);
|
|
100
|
+
return text ? JSON.parse(text) : [];
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
clear() {
|
|
106
|
+
try {
|
|
107
|
+
localStorage.removeItem(LS_KEY);
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async function createBrowserStorage() {
|
|
114
|
+
try {
|
|
115
|
+
const { default: Dexie } = await import("dexie");
|
|
116
|
+
const db = new Dexie(DB_NAME);
|
|
117
|
+
db.version(1).stores({ queue: "key" });
|
|
118
|
+
await db.open();
|
|
119
|
+
const table = db.table(
|
|
120
|
+
"queue"
|
|
121
|
+
);
|
|
122
|
+
return {
|
|
123
|
+
async save(events) {
|
|
124
|
+
await table.put({ key: QUEUE_KEY, events });
|
|
125
|
+
},
|
|
126
|
+
async load() {
|
|
127
|
+
const row = await table.get(QUEUE_KEY);
|
|
128
|
+
return row ? row.events : [];
|
|
129
|
+
},
|
|
130
|
+
async clear() {
|
|
131
|
+
await table.delete(QUEUE_KEY);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
} catch {
|
|
135
|
+
return createLocalStorageStorage();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function createNodeStorage(persistDir) {
|
|
139
|
+
const { default: Conf } = await import(
|
|
140
|
+
/* webpackIgnore: true */
|
|
141
|
+
"conf"
|
|
142
|
+
);
|
|
143
|
+
const store = new Conf({
|
|
144
|
+
configName: STORE_NAME,
|
|
145
|
+
// 落盘为 persistDir/clipto-reporting.json
|
|
146
|
+
cwd: persistDir
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
save(events) {
|
|
150
|
+
store.set(QUEUE_KEY, events);
|
|
151
|
+
},
|
|
152
|
+
load() {
|
|
153
|
+
return store.get(QUEUE_KEY) ?? [];
|
|
154
|
+
},
|
|
155
|
+
clear() {
|
|
156
|
+
store.delete(QUEUE_KEY);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/reporter.ts
|
|
162
|
+
var DEFAULT_FLUSH_INTERVAL = 2e3;
|
|
163
|
+
var DEFAULT_MAX_BATCH_SIZE = 10;
|
|
164
|
+
var DEFAULT_MAX_BATCH_BYTES = 64 * 1024;
|
|
165
|
+
var DEFAULT_MAX_QUEUE_SIZE = 1e3;
|
|
166
|
+
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
167
|
+
var DEFAULT_BACKOFF_MS = 1e3;
|
|
168
|
+
var STORAGE_WRITE_THROTTLE_MS = 500;
|
|
169
|
+
var Reporter = class {
|
|
170
|
+
constructor(options) {
|
|
171
|
+
/** 会话 / 设备 id 兜底:enrich 未提供时按实例生成(对齐旧 SDK 格式) */
|
|
172
|
+
this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
173
|
+
this.deviceId = `device_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
174
|
+
/** 待发队列(内存中的事实源,持久化是它的影子) */
|
|
175
|
+
this.queue = [];
|
|
176
|
+
this.flushTimer = null;
|
|
177
|
+
this.retryTimer = null;
|
|
178
|
+
this.persistTimer = null;
|
|
179
|
+
this.persistDirty = false;
|
|
180
|
+
this.flushPromise = null;
|
|
181
|
+
this.disposed = false;
|
|
182
|
+
this.onExit = () => {
|
|
183
|
+
this.fire(this.dispose());
|
|
184
|
+
};
|
|
185
|
+
this.onPageHide = () => {
|
|
186
|
+
this.fire(this.dispose());
|
|
187
|
+
};
|
|
188
|
+
this.onVisibilityChange = () => {
|
|
189
|
+
if (document.visibilityState === "hidden") {
|
|
190
|
+
this.fire(this.flush());
|
|
191
|
+
} else if (this.opts.autoPageView) {
|
|
192
|
+
this.track("page_view");
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
if (!options.endpoint) {
|
|
196
|
+
throw new Error("Reporter: endpoint \u4E3A\u5FC5\u586B\u9879");
|
|
197
|
+
}
|
|
198
|
+
this.opts = options;
|
|
199
|
+
this.platform = options.platform ?? (typeof window !== "undefined" ? "browser" : "node");
|
|
200
|
+
this.flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL;
|
|
201
|
+
this.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
|
|
202
|
+
this.maxBatchBytes = options.maxBatchBytes ?? DEFAULT_MAX_BATCH_BYTES;
|
|
203
|
+
this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
204
|
+
this.overflowPolicy = options.overflowPolicy ?? "drop-oldest";
|
|
205
|
+
this.maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
206
|
+
this.backoffMs = options.backoffMs ?? DEFAULT_BACKOFF_MS;
|
|
207
|
+
this.flushOnExit = options.flushOnExit ?? true;
|
|
208
|
+
if (options.transport) {
|
|
209
|
+
this.transportPromise = Promise.resolve(options.transport);
|
|
210
|
+
} else if (this.platform === "browser") {
|
|
211
|
+
this.transportPromise = Promise.resolve(
|
|
212
|
+
createBrowserTransport(options.endpoint, options.headers)
|
|
213
|
+
);
|
|
214
|
+
} else {
|
|
215
|
+
this.transportPromise = createNodeTransport(
|
|
216
|
+
options.endpoint,
|
|
217
|
+
options.headers
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
if (options.storage) {
|
|
221
|
+
this.storagePromise = Promise.resolve(options.storage);
|
|
222
|
+
} else if (options.persist === false) {
|
|
223
|
+
this.storagePromise = Promise.resolve(void 0);
|
|
224
|
+
} else if (this.platform === "browser") {
|
|
225
|
+
this.storagePromise = createBrowserStorage();
|
|
226
|
+
} else if (options.persistDir) {
|
|
227
|
+
this.storagePromise = createNodeStorage(options.persistDir);
|
|
228
|
+
} else {
|
|
229
|
+
this.storagePromise = Promise.resolve(void 0);
|
|
230
|
+
}
|
|
231
|
+
this.fire(this.init());
|
|
232
|
+
}
|
|
233
|
+
track(name, properties, options) {
|
|
234
|
+
if (this.disposed) {
|
|
235
|
+
this.log("dispose \u540E\u5FFD\u7565\u65B0\u4E8B\u4EF6:", name);
|
|
236
|
+
return "";
|
|
237
|
+
}
|
|
238
|
+
const enriched = this.opts.enrich?.() ?? {};
|
|
239
|
+
const event = {
|
|
240
|
+
id: generateEventId(),
|
|
241
|
+
name,
|
|
242
|
+
properties: {
|
|
243
|
+
...this.opts.baseProperties ?? {},
|
|
244
|
+
...this.opts.context?.() ?? {},
|
|
245
|
+
...properties ?? {}
|
|
246
|
+
},
|
|
247
|
+
timestamp: options?.timestamp ?? Date.now(),
|
|
248
|
+
attempts: 0,
|
|
249
|
+
sessionId: enriched.sessionId ?? this.sessionId,
|
|
250
|
+
userId: enriched.userId ?? null,
|
|
251
|
+
deviceId: enriched.deviceId ?? this.deviceId,
|
|
252
|
+
category: options?.category ?? "custom",
|
|
253
|
+
context: enriched.context ?? {}
|
|
254
|
+
};
|
|
255
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
256
|
+
this.opts.onDropped?.([event], "invalid");
|
|
257
|
+
return "";
|
|
258
|
+
}
|
|
259
|
+
if (options?.immediate) {
|
|
260
|
+
this.fire(this.sendBatch([event]));
|
|
261
|
+
return event.id;
|
|
262
|
+
}
|
|
263
|
+
this.enqueue(event);
|
|
264
|
+
if (this.flushInterval <= 0) {
|
|
265
|
+
this.fire(this.flush());
|
|
266
|
+
} else {
|
|
267
|
+
this.scheduleFlush();
|
|
268
|
+
}
|
|
269
|
+
return event.id;
|
|
270
|
+
}
|
|
271
|
+
flush() {
|
|
272
|
+
if (!this.flushPromise) {
|
|
273
|
+
if (this.retryTimer) {
|
|
274
|
+
clearTimeout(this.retryTimer);
|
|
275
|
+
this.retryTimer = null;
|
|
276
|
+
}
|
|
277
|
+
this.flushPromise = this.drain().finally(() => {
|
|
278
|
+
this.flushPromise = null;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return this.flushPromise;
|
|
282
|
+
}
|
|
283
|
+
get size() {
|
|
284
|
+
return this.queue.length;
|
|
285
|
+
}
|
|
286
|
+
async dispose() {
|
|
287
|
+
if (this.disposed) return;
|
|
288
|
+
this.disposed = true;
|
|
289
|
+
if (this.flushTimer) {
|
|
290
|
+
clearTimeout(this.flushTimer);
|
|
291
|
+
this.flushTimer = null;
|
|
292
|
+
}
|
|
293
|
+
if (this.retryTimer) {
|
|
294
|
+
clearTimeout(this.retryTimer);
|
|
295
|
+
this.retryTimer = null;
|
|
296
|
+
}
|
|
297
|
+
if (this.persistTimer) {
|
|
298
|
+
clearTimeout(this.persistTimer);
|
|
299
|
+
this.persistTimer = null;
|
|
300
|
+
}
|
|
301
|
+
if (this.platform === "browser") {
|
|
302
|
+
window.removeEventListener("pagehide", this.onPageHide);
|
|
303
|
+
document.removeEventListener("visibilitychange", this.onVisibilityChange);
|
|
304
|
+
} else {
|
|
305
|
+
const exitProcess = this.getExitProcess();
|
|
306
|
+
exitProcess.off("beforeExit", this.onExit);
|
|
307
|
+
exitProcess.off("SIGINT", this.onExit);
|
|
308
|
+
exitProcess.off("SIGTERM", this.onExit);
|
|
309
|
+
}
|
|
310
|
+
const transport = await this.transportPromise;
|
|
311
|
+
if (this.queue.length === 0) {
|
|
312
|
+
if (this.persistDirty) {
|
|
313
|
+
await this.persistNow([]);
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (this.flushOnExit) {
|
|
318
|
+
const pending = this.queue.splice(0);
|
|
319
|
+
if (transport.sendSync?.(pending) ?? false) {
|
|
320
|
+
this.opts.onFlushed?.(pending);
|
|
321
|
+
await this.persistNow([]);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
await transport.send(pending);
|
|
326
|
+
this.opts.onFlushed?.(pending);
|
|
327
|
+
await this.persistNow([]);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
this.opts.onFailed?.(pending, error);
|
|
330
|
+
this.queue.push(...pending);
|
|
331
|
+
await this.persistNow();
|
|
332
|
+
}
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (this.persistDirty) {
|
|
336
|
+
await this.persistNow();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/** 异步初始化:创建内置持久化、恢复上次会话队列、挂载退出兜底 */
|
|
340
|
+
async init() {
|
|
341
|
+
this.storage = await this.storagePromise;
|
|
342
|
+
if (this.disposed) return;
|
|
343
|
+
if (this.storage) {
|
|
344
|
+
try {
|
|
345
|
+
const restored = await this.storage.load();
|
|
346
|
+
if (restored.length > 0) {
|
|
347
|
+
this.enqueueRestored(restored);
|
|
348
|
+
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
this.log("restore pending queue failed:", error);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (this.disposed) return;
|
|
354
|
+
if (this.platform === "browser") {
|
|
355
|
+
if (this.flushOnExit) {
|
|
356
|
+
window.addEventListener("pagehide", this.onPageHide);
|
|
357
|
+
}
|
|
358
|
+
if (this.flushOnExit || this.opts.autoPageView) {
|
|
359
|
+
document.addEventListener("visibilitychange", this.onVisibilityChange);
|
|
360
|
+
}
|
|
361
|
+
if (this.opts.autoPageView) {
|
|
362
|
+
this.track("page_view");
|
|
363
|
+
}
|
|
364
|
+
} else if (this.flushOnExit) {
|
|
365
|
+
const exitProcess = this.getExitProcess();
|
|
366
|
+
exitProcess.once("beforeExit", this.onExit);
|
|
367
|
+
exitProcess.once("SIGINT", this.onExit);
|
|
368
|
+
exitProcess.once("SIGTERM", this.onExit);
|
|
369
|
+
}
|
|
370
|
+
if (this.queue.length > 0) {
|
|
371
|
+
this.scheduleFlush();
|
|
372
|
+
this.schedulePersist();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/** 恢复队列入队:历史事件放队头保证先到先发,超限按溢出丢弃 */
|
|
376
|
+
enqueueRestored(events) {
|
|
377
|
+
let accepted = events;
|
|
378
|
+
if (accepted.length > this.maxQueueSize) {
|
|
379
|
+
this.opts.onDropped?.(accepted.slice(this.maxQueueSize), "overflow");
|
|
380
|
+
accepted = accepted.slice(0, this.maxQueueSize);
|
|
381
|
+
}
|
|
382
|
+
this.queue.unshift(...accepted);
|
|
383
|
+
}
|
|
384
|
+
enqueue(event) {
|
|
385
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
386
|
+
if (this.overflowPolicy === "drop-newest") {
|
|
387
|
+
this.opts.onDropped?.([event], "overflow");
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (this.overflowPolicy === "drop-oldest") {
|
|
391
|
+
this.opts.onDropped?.([this.queue.shift()], "overflow");
|
|
392
|
+
} else {
|
|
393
|
+
this.fire(this.flush());
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
this.queue.push(event);
|
|
397
|
+
this.schedulePersist();
|
|
398
|
+
}
|
|
399
|
+
/** 攒批定时:延迟上报的实现,窗口内事件合并为一批 */
|
|
400
|
+
scheduleFlush() {
|
|
401
|
+
if (this.flushTimer || this.disposed || this.flushInterval <= 0) return;
|
|
402
|
+
this.flushTimer = setTimeout(() => {
|
|
403
|
+
this.flushTimer = null;
|
|
404
|
+
if (this.queue.length > 0) {
|
|
405
|
+
this.fire(this.flush());
|
|
406
|
+
}
|
|
407
|
+
}, this.flushInterval);
|
|
408
|
+
}
|
|
409
|
+
/** 串行清空队列;失败批次重新入队后由 retryTimer 再次触发 drain */
|
|
410
|
+
async drain() {
|
|
411
|
+
while (!this.disposed && !this.retryTimer && this.queue.length > 0) {
|
|
412
|
+
await this.sendBatch(this.takeBatch());
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/** 取一批:受条数 / 字节数限制;首条必取,避免单事件超 maxBatchBytes 时队列卡死 */
|
|
416
|
+
takeBatch() {
|
|
417
|
+
const batch = [];
|
|
418
|
+
let bytes = 0;
|
|
419
|
+
while (this.queue.length > 0 && batch.length < this.maxBatchSize) {
|
|
420
|
+
const size = JSON.stringify(this.queue[0]).length;
|
|
421
|
+
if (batch.length > 0 && bytes + size > this.maxBatchBytes) break;
|
|
422
|
+
batch.push(this.queue.shift());
|
|
423
|
+
bytes += size;
|
|
424
|
+
}
|
|
425
|
+
return batch;
|
|
426
|
+
}
|
|
427
|
+
async sendBatch(batch) {
|
|
428
|
+
const transport = await this.transportPromise;
|
|
429
|
+
try {
|
|
430
|
+
await transport.send(batch);
|
|
431
|
+
this.opts.onFlushed?.(batch);
|
|
432
|
+
this.schedulePersist();
|
|
433
|
+
} catch (error) {
|
|
434
|
+
this.opts.onFailed?.(batch, error);
|
|
435
|
+
this.log("send batch failed:", error);
|
|
436
|
+
const updated = batch.map((event) => ({
|
|
437
|
+
...event,
|
|
438
|
+
attempts: event.attempts + 1
|
|
439
|
+
}));
|
|
440
|
+
const retryable = updated.filter(
|
|
441
|
+
(event) => event.attempts < this.maxAttempts
|
|
442
|
+
);
|
|
443
|
+
const dead = updated.filter(
|
|
444
|
+
(event) => event.attempts >= this.maxAttempts
|
|
445
|
+
);
|
|
446
|
+
if (dead.length > 0) {
|
|
447
|
+
this.opts.onDropped?.(dead, "max-attempts");
|
|
448
|
+
}
|
|
449
|
+
if (retryable.length > 0) {
|
|
450
|
+
this.queue.unshift(...retryable);
|
|
451
|
+
this.schedulePersist();
|
|
452
|
+
this.scheduleRetry(retryable);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/** 失败退避:按批次内最大 attempts 计 2^n 指数增长,避免失败批次频繁重试 */
|
|
457
|
+
scheduleRetry(retryable) {
|
|
458
|
+
if (this.retryTimer) return;
|
|
459
|
+
const maxAttemptsInBatch = Math.max(
|
|
460
|
+
...retryable.map((event) => event.attempts)
|
|
461
|
+
);
|
|
462
|
+
const delay = this.backoffMs * 2 ** (maxAttemptsInBatch - 1);
|
|
463
|
+
this.retryTimer = setTimeout(() => {
|
|
464
|
+
this.retryTimer = null;
|
|
465
|
+
this.fire(this.flush());
|
|
466
|
+
}, delay);
|
|
467
|
+
}
|
|
468
|
+
/** 持久化节流:窗口内多次变更只落盘一次,避免每次入队全量序列化 */
|
|
469
|
+
schedulePersist() {
|
|
470
|
+
if (!this.storage) return;
|
|
471
|
+
this.persistDirty = true;
|
|
472
|
+
if (this.persistTimer) return;
|
|
473
|
+
this.persistTimer = setTimeout(() => {
|
|
474
|
+
this.persistTimer = null;
|
|
475
|
+
if (!this.persistDirty) return;
|
|
476
|
+
this.persistDirty = false;
|
|
477
|
+
this.fire(this.persistNow());
|
|
478
|
+
}, STORAGE_WRITE_THROTTLE_MS);
|
|
479
|
+
}
|
|
480
|
+
async persistNow(events = this.queue) {
|
|
481
|
+
if (!this.storage) return;
|
|
482
|
+
try {
|
|
483
|
+
await this.storage.save(events);
|
|
484
|
+
} catch (error) {
|
|
485
|
+
this.log("persist queue failed:", error);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
log(...args) {
|
|
489
|
+
if (this.opts.debug) {
|
|
490
|
+
console.log("[reporter]", ...args);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Node 进程退出事件句柄:Electron 渲染进程 typings 里 process 只声明了 'loaded'
|
|
495
|
+
* 事件,与 @types/node 不一致,显式收窄到本 SDK 用到的信号,兼容两端编译器
|
|
496
|
+
*/
|
|
497
|
+
getExitProcess() {
|
|
498
|
+
return process;
|
|
499
|
+
}
|
|
500
|
+
/** 安全触发异步任务:吞掉 rejection,避免未处理异常 */
|
|
501
|
+
fire(promise) {
|
|
502
|
+
promise.catch((error) => {
|
|
503
|
+
this.log("async task failed:", error);
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
export {
|
|
508
|
+
Reporter,
|
|
509
|
+
createBrowserStorage,
|
|
510
|
+
createBrowserTransport,
|
|
511
|
+
createLocalStorageStorage,
|
|
512
|
+
createNodeStorage,
|
|
513
|
+
createNodeTransport
|
|
514
|
+
};
|
|
515
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/id.ts","../src/builtins.ts","../src/reporter.ts"],"sourcesContent":["/**\n * 事件 / 批次 id:对齐旧 SDK generateEventId 格式(evt_时间戳_随机串)\n */\nexport function generateEventId(): string {\n return `evt_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n}\n","// ============================================================\n// 内置 transport / storage —— 复用现有组件,不新造轮子:\n// 浏览器发送: 原生 fetch(keepalive)+ navigator.sendBeacon(退出兜底)\n// Node 发送: axios(项目现有依赖,动态加载,不进 renderer 包)\n// 浏览器持久化: Dexie(项目现有依赖,与 @renderer/common/idb 同款;\n// IndexedDB 不可用时降级 localStorage)\n// Node 持久化: conf(electron-store 的纯 Node 内核,Electron main 与\n// 纯 Node 服务端通吃,JSON 文件 + 内部原子写),整体覆盖保存\n// payload 沿用旧 SDK 的 { events, metadata } 信封,便于后端平滑过渡\n// ============================================================\nimport type { IReportEvent, IReportStorage, IReportTransport } from './index';\nimport { generateEventId } from './id';\n\nconst SDK_VERSION = '1.0.0'; // 对齐旧 SDK sdkVersion,便于后端平滑过渡\nconst DB_NAME = 'CliptoReportDB';\nconst STORE_NAME = 'clipto-reporting';\nconst QUEUE_KEY = 'pending';\nconst LS_KEY = 'clipto_report_pending_queue';\n\n/** 上报信封事件:顶层字段与旧 SDK TrackedEvent 完全对齐(name 序列化为 eventName) */\ninterface IEventPayloadEvent {\n id: string;\n eventName: string;\n timestamp: number;\n sessionId?: string;\n userId?: string | null;\n deviceId?: string;\n properties: Record<string, unknown>;\n category?: string;\n context?: Record<string, unknown>;\n}\n\n/** 上报信封:对齐旧 SDK { events, metadata } 结构 */\ninterface IEventPayload {\n events: IEventPayloadEvent[];\n metadata: {\n sdkVersion: string;\n timestamp: number;\n batchId: string;\n };\n}\n\nfunction buildPayload(events: IReportEvent[]): IEventPayload {\n return {\n // attempts 为 SDK 内部字段,不上报;name 输出为 eventName(对齐旧 SDK)\n events: events.map(\n ({\n id,\n name,\n timestamp,\n sessionId,\n userId,\n deviceId,\n properties,\n category,\n context,\n }) => ({\n id,\n eventName: name,\n timestamp,\n sessionId,\n userId,\n deviceId,\n properties,\n category,\n context,\n }),\n ),\n metadata: {\n sdkVersion: SDK_VERSION,\n timestamp: Date.now(),\n batchId: generateEventId(), // 与事件 id 同格式(对齐旧 SDK)\n },\n };\n}\n\n/** 浏览器发送:fetch(keepalive)走正常路径;sendBeacon 仅退出兜底(sendSync) */\nexport function createBrowserTransport(\n endpoint: string,\n headers?: Record<string, string>,\n): IReportTransport {\n const commonHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...(headers ?? {}),\n };\n return {\n async send(events) {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: commonHeaders,\n body: JSON.stringify(buildPayload(events)),\n keepalive: true,\n });\n if (!response.ok) {\n throw new Error(`report send failed: HTTP ${response.status}`);\n }\n },\n sendSync(events) {\n if (\n typeof navigator === 'undefined' ||\n typeof navigator.sendBeacon !== 'function'\n ) {\n return false;\n }\n try {\n // sendBeacon 不支持自定义请求头,与旧 SDK 行为一致\n const blob = new Blob([JSON.stringify(buildPayload(events))], {\n type: 'application/json',\n });\n return navigator.sendBeacon(endpoint, blob);\n } catch {\n return false;\n }\n },\n };\n}\n\n/** Node 发送:动态加载项目现有依赖 axios(仅在 Node 路径执行) */\nexport async function createNodeTransport(\n endpoint: string,\n headers?: Record<string, string>,\n): Promise<IReportTransport> {\n const { default: axios } = await import('axios');\n return {\n async send(events) {\n await axios.post(endpoint, buildPayload(events), {\n headers,\n timeout: 10000,\n });\n },\n };\n}\n\n/** 降级实现:localStorage(容量有限,仅兜底) */\nexport function createLocalStorageStorage(): IReportStorage {\n return {\n save(events) {\n try {\n localStorage.setItem(LS_KEY, JSON.stringify(events));\n } catch {\n // 配额满或不可用:静默降级为不持久化\n }\n },\n load() {\n try {\n const text = localStorage.getItem(LS_KEY);\n return text ? (JSON.parse(text) as IReportEvent[]) : [];\n } catch {\n return [];\n }\n },\n clear() {\n try {\n localStorage.removeItem(LS_KEY);\n } catch {\n // ignore\n }\n },\n };\n}\n\n/** 浏览器持久化:优先 Dexie(IndexedDB),不可用时降级 localStorage */\nexport async function createBrowserStorage(): Promise<IReportStorage> {\n try {\n // 动态加载项目现有依赖 Dexie:仅在浏览器路径执行,不进 main 进程\n const { default: Dexie } = await import('dexie');\n const db = new Dexie(DB_NAME);\n db.version(1).stores({ queue: 'key' });\n await db.open(); // 提前探活,失败走 localStorage 降级\n\n const table = db.table<{ key: string; events: IReportEvent[] }, string>(\n 'queue',\n );\n return {\n async save(events) {\n await table.put({ key: QUEUE_KEY, events });\n },\n async load() {\n const row = await table.get(QUEUE_KEY);\n return row ? row.events : [];\n },\n async clear() {\n await table.delete(QUEUE_KEY);\n },\n };\n } catch {\n // IndexedDB 不可用(隐私模式 / 极老浏览器):降级 localStorage\n return createLocalStorageStorage();\n }\n}\n\n/** Node 持久化:conf(纯 Node 可用,JSON 文件 + 内部原子写),整体覆盖保存 */\nexport async function createNodeStorage(\n persistDir: string,\n): Promise<IReportStorage> {\n // 动态加载:仅在 Node 路径执行,renderer 永不进入该分支。\n // webpackIgnore:让打包器跳过 conf(依赖 Node 内置模块,web 目标无法打包),\n // 运行时由 Node 直接解析;SDK 作为 npm 包发布时 conf 随依赖安装。\n // 注意:打包后的 main 进程若启用该分支,需将 conf 加入\n // release/app/package.json dependencies(同 better-sqlite3 等原生依赖模式)。\n const { default: Conf } = await import(/* webpackIgnore: true */ 'conf');\n const store = new Conf<Record<string, IReportEvent[]>>({\n configName: STORE_NAME, // 落盘为 persistDir/clipto-reporting.json\n cwd: persistDir,\n });\n return {\n save(events) {\n store.set(QUEUE_KEY, events);\n },\n load() {\n return store.get(QUEUE_KEY) ?? [];\n },\n clear() {\n store.delete(QUEUE_KEY);\n },\n };\n}\n","// ============================================================\n// Reporter 核心实现 —— 平台无关,差异通过 transport / storage 注入\n// 复用现有组件:setTimeout(攒批 / 退避定时);事件 id 对齐旧 SDK\n// generateEventId 格式(见 id.ts);\n// 入队、攒批、重试、有界队列即 SDK 本身,项目内 better-queue / p-queue\n// 面向任务队列语义,与攒批上报不匹配,故核心手写保持精简。\n// ============================================================\nimport type {\n IReportEvent,\n IReporter,\n IReporterOptions,\n IReportStorage,\n IReportTransport,\n} from './index';\nimport {\n createBrowserStorage,\n createBrowserTransport,\n createNodeStorage,\n createNodeTransport,\n} from './builtins';\nimport { generateEventId } from './id';\n\nconst DEFAULT_FLUSH_INTERVAL = 2000; // 攒批窗口(ms),即延迟上报间隔\nconst DEFAULT_MAX_BATCH_SIZE = 10; // 单批最大条数,达到即刷\nconst DEFAULT_MAX_BATCH_BYTES = 64 * 1024; // 单批最大字节数,达到即刷\nconst DEFAULT_MAX_QUEUE_SIZE = 1000; // 队列最大条数\nconst DEFAULT_MAX_ATTEMPTS = 3; // 单事件最大发送尝试次数,超限丢弃\nconst DEFAULT_BACKOFF_MS = 1000; // 重试退避基数,按 2^n 指数增长\nconst STORAGE_WRITE_THROTTLE_MS = 500; // 持久化写入节流窗口\n\n/** Node 进程退出信号:兼容 Electron 渲染进程与 @types/node 对 process 的 typings 差异 */\ntype ExitSignal = 'beforeExit' | 'SIGINT' | 'SIGTERM';\n\ntype ExitProcessHandle = {\n once(event: ExitSignal, listener: () => void): unknown;\n off(event: ExitSignal, listener: () => void): unknown;\n};\n\nexport class Reporter implements IReporter {\n private readonly opts: IReporterOptions;\n\n private readonly platform: 'browser' | 'node';\n\n private readonly flushInterval: number;\n\n private readonly maxBatchSize: number;\n\n private readonly maxBatchBytes: number;\n\n private readonly maxQueueSize: number;\n\n private readonly overflowPolicy: NonNullable<\n IReporterOptions['overflowPolicy']\n >;\n\n private readonly maxAttempts: number;\n\n private readonly backoffMs: number;\n\n private readonly flushOnExit: boolean;\n\n private readonly transportPromise: Promise<IReportTransport>;\n\n private readonly storagePromise: Promise<IReportStorage | undefined>;\n\n /** 会话 / 设备 id 兜底:enrich 未提供时按实例生成(对齐旧 SDK 格式) */\n private readonly sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n\n private readonly deviceId = `device_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n\n /** 待发队列(内存中的事实源,持久化是它的影子) */\n private readonly queue: IReportEvent[] = [];\n\n /** init 完成后指向内置 / 注入的持久化实现 */\n private storage: IReportStorage | undefined;\n\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n\n private persistTimer: ReturnType<typeof setTimeout> | null = null;\n\n private persistDirty = false;\n\n private flushPromise: Promise<void> | null = null;\n\n private disposed = false;\n\n private readonly onExit = (): void => {\n this.fire(this.dispose());\n };\n\n private readonly onPageHide = (): void => {\n this.fire(this.dispose());\n };\n\n private readonly onVisibilityChange = (): void => {\n if (document.visibilityState === 'hidden') {\n // 页面切后台:冲刷一次(非卸载,保持接收新事件)\n this.fire(this.flush());\n } else if (this.opts.autoPageView) {\n this.track('page_view');\n }\n };\n\n constructor(options: IReporterOptions) {\n if (!options.endpoint) {\n throw new Error('Reporter: endpoint 为必填项');\n }\n this.opts = options;\n this.platform =\n options.platform ?? (typeof window !== 'undefined' ? 'browser' : 'node');\n this.flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL;\n this.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;\n this.maxBatchBytes = options.maxBatchBytes ?? DEFAULT_MAX_BATCH_BYTES;\n this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;\n this.overflowPolicy = options.overflowPolicy ?? 'drop-oldest';\n this.maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n this.backoffMs = options.backoffMs ?? DEFAULT_BACKOFF_MS;\n this.flushOnExit = options.flushOnExit ?? true;\n\n // 注入优先;未注入按平台内置(复用现有组件,见 builtins.ts)\n if (options.transport) {\n this.transportPromise = Promise.resolve(options.transport);\n } else if (this.platform === 'browser') {\n this.transportPromise = Promise.resolve(\n createBrowserTransport(options.endpoint, options.headers),\n );\n } else {\n this.transportPromise = createNodeTransport(\n options.endpoint,\n options.headers,\n );\n }\n\n if (options.storage) {\n this.storagePromise = Promise.resolve(options.storage);\n } else if (options.persist === false) {\n this.storagePromise = Promise.resolve(undefined);\n } else if (this.platform === 'browser') {\n this.storagePromise = createBrowserStorage(); // Dexie → localStorage 降级\n } else if (options.persistDir) {\n this.storagePromise = createNodeStorage(options.persistDir);\n } else {\n this.storagePromise = Promise.resolve(undefined); // Node 未配置 persistDir:仅内存\n }\n\n this.fire(this.init());\n }\n\n track(\n name: string,\n properties?: Record<string, unknown>,\n options?: { immediate?: boolean; timestamp?: number; category?: string },\n ): string {\n if (this.disposed) {\n this.log('dispose 后忽略新事件:', name);\n return '';\n }\n\n // 顶层字段:enrich 实时补充,缺省由核心兜底(对齐旧 SDK TrackedEvent)\n const enriched = this.opts.enrich?.() ?? {};\n const event: IReportEvent = {\n id: generateEventId(),\n name,\n properties: {\n ...(this.opts.baseProperties ?? {}),\n ...(this.opts.context?.() ?? {}),\n ...(properties ?? {}),\n },\n timestamp: options?.timestamp ?? Date.now(),\n attempts: 0,\n sessionId: enriched.sessionId ?? this.sessionId,\n userId: enriched.userId ?? null,\n deviceId: enriched.deviceId ?? this.deviceId,\n category: options?.category ?? 'custom',\n context: enriched.context ?? {},\n };\n\n if (typeof name !== 'string' || name.length === 0) {\n this.opts.onDropped?.([event], 'invalid');\n return '';\n }\n\n if (options?.immediate) {\n // 立即发送:跳过攒批,失败仍走重试(重新入队)\n this.fire(this.sendBatch([event]));\n return event.id;\n }\n\n this.enqueue(event);\n if (this.flushInterval <= 0) {\n // 关闭攒批:事件即时发送\n this.fire(this.flush());\n } else {\n this.scheduleFlush();\n }\n return event.id;\n }\n\n flush(): Promise<void> {\n if (!this.flushPromise) {\n // 显式冲刷:跳过退避立即重试(退避只约束自动重试)\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n this.flushPromise = this.drain().finally(() => {\n this.flushPromise = null;\n });\n }\n return this.flushPromise;\n }\n\n get size(): number {\n return this.queue.length;\n }\n\n async dispose(): Promise<void> {\n if (this.disposed) return;\n this.disposed = true;\n\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n if (this.persistTimer) {\n clearTimeout(this.persistTimer);\n this.persistTimer = null;\n }\n\n if (this.platform === 'browser') {\n window.removeEventListener('pagehide', this.onPageHide);\n document.removeEventListener('visibilitychange', this.onVisibilityChange);\n } else {\n const exitProcess = this.getExitProcess();\n exitProcess.off('beforeExit', this.onExit);\n exitProcess.off('SIGINT', this.onExit);\n exitProcess.off('SIGTERM', this.onExit);\n }\n\n const transport = await this.transportPromise;\n\n if (this.queue.length === 0) {\n // 队列已空:若持久化有脏标记,写空队列收敛,避免恢复已发送事件造成重复上报\n if (this.persistDirty) {\n await this.persistNow([]);\n }\n return;\n }\n\n if (this.flushOnExit) {\n const pending = this.queue.splice(0);\n // 优先同步尽力发送(sendBeacon,页面卸载场景 fetch 可能来不及)\n if (transport.sendSync?.(pending) ?? false) {\n this.opts.onFlushed?.(pending);\n await this.persistNow([]);\n return;\n }\n try {\n await transport.send(pending);\n this.opts.onFlushed?.(pending);\n await this.persistNow([]);\n } catch (error) {\n this.opts.onFailed?.(pending, error);\n this.queue.push(...pending); // 放回队列持久化,下次会话恢复重发\n await this.persistNow();\n }\n return;\n }\n\n // 退出兜底关闭:剩余队列仅持久化,下次启动恢复\n if (this.persistDirty) {\n await this.persistNow();\n }\n }\n\n /** 异步初始化:创建内置持久化、恢复上次会话队列、挂载退出兜底 */\n private async init(): Promise<void> {\n this.storage = await this.storagePromise;\n if (this.disposed) return;\n\n if (this.storage) {\n try {\n const restored = await this.storage.load();\n if (restored.length > 0) {\n this.enqueueRestored(restored);\n }\n } catch (error) {\n this.log('restore pending queue failed:', error);\n }\n }\n if (this.disposed) return;\n\n if (this.platform === 'browser') {\n if (this.flushOnExit) {\n window.addEventListener('pagehide', this.onPageHide);\n }\n if (this.flushOnExit || this.opts.autoPageView) {\n document.addEventListener('visibilitychange', this.onVisibilityChange);\n }\n if (this.opts.autoPageView) {\n this.track('page_view');\n }\n } else if (this.flushOnExit) {\n const exitProcess = this.getExitProcess();\n exitProcess.once('beforeExit', this.onExit);\n exitProcess.once('SIGINT', this.onExit);\n exitProcess.once('SIGTERM', this.onExit);\n }\n\n if (this.queue.length > 0) {\n this.scheduleFlush();\n this.schedulePersist();\n }\n }\n\n /** 恢复队列入队:历史事件放队头保证先到先发,超限按溢出丢弃 */\n private enqueueRestored(events: IReportEvent[]): void {\n let accepted = events;\n if (accepted.length > this.maxQueueSize) {\n this.opts.onDropped?.(accepted.slice(this.maxQueueSize), 'overflow');\n accepted = accepted.slice(0, this.maxQueueSize);\n }\n this.queue.unshift(...accepted);\n }\n\n private enqueue(event: IReportEvent): void {\n if (this.queue.length >= this.maxQueueSize) {\n if (this.overflowPolicy === 'drop-newest') {\n this.opts.onDropped?.([event], 'overflow');\n return;\n }\n if (this.overflowPolicy === 'drop-oldest') {\n this.opts.onDropped?.([this.queue.shift()!], 'overflow');\n } else {\n // flush-now:队列满时先冲刷腾位,不丢事件\n this.fire(this.flush());\n }\n }\n this.queue.push(event);\n this.schedulePersist();\n }\n\n /** 攒批定时:延迟上报的实现,窗口内事件合并为一批 */\n private scheduleFlush(): void {\n if (this.flushTimer || this.disposed || this.flushInterval <= 0) return;\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n if (this.queue.length > 0) {\n this.fire(this.flush());\n }\n }, this.flushInterval);\n }\n\n /** 串行清空队列;失败批次重新入队后由 retryTimer 再次触发 drain */\n private async drain(): Promise<void> {\n while (!this.disposed && !this.retryTimer && this.queue.length > 0) {\n // eslint-disable-next-line no-await-in-loop\n await this.sendBatch(this.takeBatch());\n }\n }\n\n /** 取一批:受条数 / 字节数限制;首条必取,避免单事件超 maxBatchBytes 时队列卡死 */\n private takeBatch(): IReportEvent[] {\n const batch: IReportEvent[] = [];\n let bytes = 0;\n while (this.queue.length > 0 && batch.length < this.maxBatchSize) {\n const size = JSON.stringify(this.queue[0]).length;\n if (batch.length > 0 && bytes + size > this.maxBatchBytes) break;\n batch.push(this.queue.shift()!);\n bytes += size;\n }\n return batch;\n }\n\n private async sendBatch(batch: IReportEvent[]): Promise<void> {\n const transport = await this.transportPromise;\n try {\n await transport.send(batch);\n this.opts.onFlushed?.(batch);\n this.schedulePersist(); // 该批已移出队列,持久化剩余\n } catch (error) {\n this.opts.onFailed?.(batch, error);\n this.log('send batch failed:', error);\n\n const updated = batch.map((event) => ({\n ...event,\n attempts: event.attempts + 1,\n }));\n const retryable = updated.filter(\n (event) => event.attempts < this.maxAttempts,\n );\n const dead = updated.filter(\n (event) => event.attempts >= this.maxAttempts,\n );\n if (dead.length > 0) {\n this.opts.onDropped?.(dead, 'max-attempts');\n }\n if (retryable.length > 0) {\n this.queue.unshift(...retryable); // 保持原顺序放回队头\n this.schedulePersist();\n this.scheduleRetry(retryable);\n }\n }\n }\n\n /** 失败退避:按批次内最大 attempts 计 2^n 指数增长,避免失败批次频繁重试 */\n private scheduleRetry(retryable: IReportEvent[]): void {\n if (this.retryTimer) return;\n const maxAttemptsInBatch = Math.max(\n ...retryable.map((event) => event.attempts),\n );\n const delay = this.backoffMs * 2 ** (maxAttemptsInBatch - 1);\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n this.fire(this.flush());\n }, delay);\n }\n\n /** 持久化节流:窗口内多次变更只落盘一次,避免每次入队全量序列化 */\n private schedulePersist(): void {\n if (!this.storage) return;\n this.persistDirty = true;\n if (this.persistTimer) return;\n this.persistTimer = setTimeout(() => {\n this.persistTimer = null;\n if (!this.persistDirty) return;\n this.persistDirty = false;\n this.fire(this.persistNow());\n }, STORAGE_WRITE_THROTTLE_MS);\n }\n\n private async persistNow(events: IReportEvent[] = this.queue): Promise<void> {\n if (!this.storage) return;\n try {\n await this.storage.save(events);\n } catch (error) {\n this.log('persist queue failed:', error);\n }\n }\n\n private log(...args: unknown[]): void {\n if (this.opts.debug) {\n // eslint-disable-next-line no-console\n console.log('[reporter]', ...args);\n }\n }\n\n /**\n * Node 进程退出事件句柄:Electron 渲染进程 typings 里 process 只声明了 'loaded'\n * 事件,与 @types/node 不一致,显式收窄到本 SDK 用到的信号,兼容两端编译器\n */\n private getExitProcess(): ExitProcessHandle {\n return process as unknown as ExitProcessHandle;\n }\n\n /** 安全触发异步任务:吞掉 rejection,避免未处理异常 */\n private fire(promise: Promise<unknown>): void {\n promise.catch((error) => {\n this.log('async task failed:', error);\n });\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clipto/reporter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "通用数据上报 SDK:攒批 / 重试 / 有界队列 / 崩溃恢复,浏览器(sendBeacon+fetch+Dexie)与 Node(axios+conf)双端通用",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Clipto",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Clipto-AI/clipto-reporter-sdk.git"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"require": {
|
|
22
|
+
"types": "./dist/index.d.cts",
|
|
23
|
+
"default": "./dist/index.cjs"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"dev": "tsup --watch",
|
|
34
|
+
"prepublishOnly": "npm run build"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"axios": "^1.7.9",
|
|
38
|
+
"conf": "^10.2.0",
|
|
39
|
+
"dexie": "^4.0.10"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^26.3.0",
|
|
43
|
+
"tsup": "^8.5.0",
|
|
44
|
+
"typescript": "^5.9.0"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
49
|
+
"keywords": [
|
|
50
|
+
"reporting",
|
|
51
|
+
"analytics",
|
|
52
|
+
"telemetry",
|
|
53
|
+
"event-tracking",
|
|
54
|
+
"browser",
|
|
55
|
+
"node",
|
|
56
|
+
"typescript"
|
|
57
|
+
],
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
61
|
+
}
|