befly-shared 2.29.0 → 2.30.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/internal/reportUtil.js +6 -17
- package/package.json +1 -1
- package/utils/{onlineReport.js → dailyReport.js} +26 -26
- package/utils/errorReport.js +135 -112
- package/utils/infoReport.js +0 -31
package/internal/reportUtil.js
CHANGED
|
@@ -7,10 +7,7 @@ async function returnPayload(payload) {
|
|
|
7
7
|
export function assertReportRequest(options, operation) {
|
|
8
8
|
if (!options || (typeof options.request !== "function" && typeof options.apiPath !== "string")) {
|
|
9
9
|
throw new Error(`${operation}.options 必须提供 request 函数或 apiPath 字符串`, {
|
|
10
|
-
cause:
|
|
11
|
-
code: "validation",
|
|
12
|
-
subsystem: "shared",
|
|
13
|
-
operation: operation
|
|
10
|
+
cause: { code: "validation", subsystem: "shared", operation: operation }
|
|
14
11
|
});
|
|
15
12
|
}
|
|
16
13
|
}
|
|
@@ -34,9 +31,9 @@ export function copyReportData(target, source) {
|
|
|
34
31
|
return;
|
|
35
32
|
}
|
|
36
33
|
|
|
37
|
-
for (const key of Object.
|
|
38
|
-
if (
|
|
39
|
-
target[key] =
|
|
34
|
+
for (const [key, value] of Object.entries(source)) {
|
|
35
|
+
if (value !== undefined) {
|
|
36
|
+
target[key] = value;
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
39
|
}
|
|
@@ -68,16 +65,8 @@ export function getRouteReportData(route) {
|
|
|
68
65
|
|
|
69
66
|
export function getCurrentRouterData(router) {
|
|
70
67
|
const currentRoute = router?.currentRoute?.value || {};
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (typeof globalThis.window !== "undefined") {
|
|
75
|
-
fallbackPath = globalThis.window.location.hash || globalThis.window.location.pathname || "";
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (typeof globalThis.document !== "undefined") {
|
|
79
|
-
fallbackTitle = globalThis.document.title || "";
|
|
80
|
-
}
|
|
68
|
+
const fallbackPath = typeof globalThis.window !== "undefined" ? globalThis.window.location.hash || globalThis.window.location.pathname || "" : "";
|
|
69
|
+
const fallbackTitle = typeof globalThis.document !== "undefined" ? globalThis.document.title || "" : "";
|
|
81
70
|
|
|
82
71
|
return {
|
|
83
72
|
pagePath: String(currentRoute.fullPath || currentRoute.path || fallbackPath),
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { appendPageAndExtraData, createCommonReportData, getReportRequest } from "../internal/reportUtil.js";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
3
|
+
const DAILY_REPORT_PATH = "/core/tongJi/dailyReport";
|
|
4
|
+
const DAILY_REPORT_MIN_GAP = 15 * 1000;
|
|
5
|
+
const DAILY_REPORT_HEARTBEAT_INTERVAL = 10 * 60 * 1000;
|
|
6
|
+
const DAILY_REPORT_CLIENT_ID_KEY = "befly-daily-client-id";
|
|
7
7
|
|
|
8
|
-
function
|
|
8
|
+
function generateDailyClientId() {
|
|
9
9
|
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -48,8 +48,8 @@ function isDocumentActive() {
|
|
|
48
48
|
return true;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export function
|
|
52
|
-
const request = getReportRequest(options, "
|
|
51
|
+
export function createDailyReport(options) {
|
|
52
|
+
const request = getReportRequest(options, "createDailyReport");
|
|
53
53
|
|
|
54
54
|
let heartbeatStarted = false;
|
|
55
55
|
let lastReportTime = 0;
|
|
@@ -60,13 +60,13 @@ export function createOnlineReport(options) {
|
|
|
60
60
|
return String(options.getClientId() || "");
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const storedClientId = readStoredClientId(
|
|
63
|
+
const storedClientId = readStoredClientId(DAILY_REPORT_CLIENT_ID_KEY);
|
|
64
64
|
if (storedClientId) {
|
|
65
65
|
return storedClientId;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
const clientId =
|
|
69
|
-
writeStoredClientId(
|
|
68
|
+
const clientId = generateDailyClientId();
|
|
69
|
+
writeStoredClientId(DAILY_REPORT_CLIENT_ID_KEY, clientId);
|
|
70
70
|
return clientId;
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -88,29 +88,29 @@ export function createOnlineReport(options) {
|
|
|
88
88
|
clearHeartbeatTimer();
|
|
89
89
|
|
|
90
90
|
timeoutId = globalThis.setTimeout(() => {
|
|
91
|
-
void
|
|
91
|
+
void triggerDailyReport();
|
|
92
92
|
queueHeartbeat();
|
|
93
|
-
},
|
|
93
|
+
}, DAILY_REPORT_HEARTBEAT_INTERVAL);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
async function
|
|
97
|
-
|
|
98
|
-
return request(ONLINE_REPORT_PATH, buildReportData(options, getClientId(), extraData));
|
|
96
|
+
async function reportDaily(extraData = {}) {
|
|
97
|
+
return request(DAILY_REPORT_PATH, buildReportData(options, getClientId(), extraData));
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
async function
|
|
100
|
+
async function triggerDailyReport(force = false, extraData = {}) {
|
|
102
101
|
if (!isDocumentActive()) {
|
|
103
102
|
return false;
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
const now = Date.now();
|
|
107
106
|
|
|
108
|
-
if (!force && now - lastReportTime <
|
|
107
|
+
if (!force && now - lastReportTime < DAILY_REPORT_MIN_GAP) {
|
|
109
108
|
return false;
|
|
110
109
|
}
|
|
111
110
|
|
|
112
111
|
try {
|
|
113
|
-
await
|
|
112
|
+
await reportDaily(extraData);
|
|
113
|
+
lastReportTime = Date.now();
|
|
114
114
|
return true;
|
|
115
115
|
} catch {
|
|
116
116
|
return false;
|
|
@@ -126,30 +126,30 @@ export function createOnlineReport(options) {
|
|
|
126
126
|
|
|
127
127
|
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
128
128
|
window.addEventListener("focus", () => {
|
|
129
|
-
void
|
|
129
|
+
void triggerDailyReport();
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
134
134
|
document.addEventListener("visibilitychange", () => {
|
|
135
|
-
void
|
|
135
|
+
void triggerDailyReport();
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
void
|
|
139
|
+
void triggerDailyReport(true);
|
|
140
140
|
queueHeartbeat();
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
return {
|
|
144
|
-
|
|
144
|
+
reportDaily: reportDaily,
|
|
145
145
|
startHeartbeat: startHeartbeat
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
export function
|
|
150
|
-
const
|
|
149
|
+
export function setupDailyReport(options) {
|
|
150
|
+
const dailyReporter = createDailyReport(options);
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
dailyReporter.startHeartbeat();
|
|
153
153
|
|
|
154
|
-
return
|
|
154
|
+
return dailyReporter;
|
|
155
155
|
}
|
package/utils/errorReport.js
CHANGED
|
@@ -48,9 +48,9 @@ export function getErrorDetail(error) {
|
|
|
48
48
|
export function createErrorReport(options) {
|
|
49
49
|
const request = getReportRequest(options, "createErrorReport");
|
|
50
50
|
|
|
51
|
-
let reporting = false;
|
|
52
51
|
let lastErrorKey = "";
|
|
53
52
|
let lastErrorTime = 0;
|
|
53
|
+
const pendingKeys = new Set();
|
|
54
54
|
|
|
55
55
|
function shouldSkipError(errorType, message, detail) {
|
|
56
56
|
const now = Date.now();
|
|
@@ -75,11 +75,11 @@ export function createErrorReport(options) {
|
|
|
75
75
|
return false;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
if (
|
|
78
|
+
if (pendingKeys.has(safeErrorType)) {
|
|
79
79
|
return false;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
pendingKeys.add(safeErrorType);
|
|
83
83
|
|
|
84
84
|
try {
|
|
85
85
|
await request(ERROR_REPORT_PATH, buildReportData(options, safeErrorType, safeMessage, safeDetail, extraData));
|
|
@@ -87,7 +87,7 @@ export function createErrorReport(options) {
|
|
|
87
87
|
} catch {
|
|
88
88
|
return false;
|
|
89
89
|
} finally {
|
|
90
|
-
|
|
90
|
+
pendingKeys.delete(safeErrorType);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
};
|
|
@@ -115,55 +115,7 @@ export function setupErrorReport(options) {
|
|
|
115
115
|
|
|
116
116
|
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
117
117
|
if (options.reportWindow !== false) {
|
|
118
|
-
window.addEventListener(
|
|
119
|
-
"error",
|
|
120
|
-
(event) => {
|
|
121
|
-
const target = event.target;
|
|
122
|
-
|
|
123
|
-
if (target && target !== window) {
|
|
124
|
-
if (options.reportResource === false) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const resourceName = target.tagName ? `${String(target.tagName).toLowerCase()} 资源加载失败` : "资源加载失败";
|
|
129
|
-
const parts = [];
|
|
130
|
-
|
|
131
|
-
if (target?.tagName) {
|
|
132
|
-
parts.push(`tag: ${String(target.tagName).toLowerCase()}`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (target?.src) {
|
|
136
|
-
parts.push(`src: ${target.src}`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (target?.href) {
|
|
140
|
-
parts.push(`href: ${target.href}`);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
reportClientError("resource", resourceName, parts.join("\n"));
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const parts = [];
|
|
148
|
-
|
|
149
|
-
if (event.filename) {
|
|
150
|
-
parts.push(`file: ${event.filename}`);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (event.lineno || event.colno) {
|
|
154
|
-
parts.push(`line: ${event.lineno || 0}, col: ${event.colno || 0}`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const stack = getErrorDetail(event.error);
|
|
158
|
-
|
|
159
|
-
if (stack) {
|
|
160
|
-
parts.push(stack);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
reportClientError("window", getErrorMessage(event.message || event.error), parts.join("\n"));
|
|
164
|
-
},
|
|
165
|
-
true
|
|
166
|
-
);
|
|
118
|
+
window.addEventListener("error", (event) => createWindowErrorHandler(options, reportClientError, event), true);
|
|
167
119
|
}
|
|
168
120
|
|
|
169
121
|
if (options.reportPromise !== false) {
|
|
@@ -187,6 +139,60 @@ export function setupErrorReport(options) {
|
|
|
187
139
|
};
|
|
188
140
|
}
|
|
189
141
|
|
|
142
|
+
function createWindowErrorHandler(options, reportClientError, event) {
|
|
143
|
+
const target = event.target;
|
|
144
|
+
|
|
145
|
+
if (target && target !== window) {
|
|
146
|
+
if (options.reportResource === false) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
reportResourceError(reportClientError, target);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
reportWindowError(reportClientError, event);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function reportResourceError(reportClientError, target) {
|
|
158
|
+
const resourceName = target.tagName ? `${String(target.tagName).toLowerCase()} 资源加载失败` : "资源加载失败";
|
|
159
|
+
const parts = [];
|
|
160
|
+
|
|
161
|
+
if (target?.tagName) {
|
|
162
|
+
parts.push(`tag: ${String(target.tagName).toLowerCase()}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (target?.src) {
|
|
166
|
+
parts.push(`src: ${target.src}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (target?.href) {
|
|
170
|
+
parts.push(`href: ${target.href}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
reportClientError("resource", resourceName, parts.join("\n"));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function reportWindowError(reportClientError, event) {
|
|
177
|
+
const parts = [];
|
|
178
|
+
|
|
179
|
+
if (event.filename) {
|
|
180
|
+
parts.push(`file: ${event.filename}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (event.lineno || event.colno) {
|
|
184
|
+
parts.push(`line: ${event.lineno || 0}, col: ${event.colno || 0}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const stack = getErrorDetail(event.error);
|
|
188
|
+
|
|
189
|
+
if (stack) {
|
|
190
|
+
parts.push(stack);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
reportClientError("window", getErrorMessage(event.message || event.error), parts.join("\n"));
|
|
194
|
+
}
|
|
195
|
+
|
|
190
196
|
function setupFetchErrorReport(options, reportClientError) {
|
|
191
197
|
if (fetchWrapped) {
|
|
192
198
|
return;
|
|
@@ -198,7 +204,12 @@ function setupFetchErrorReport(options, reportClientError) {
|
|
|
198
204
|
|
|
199
205
|
const originalFetch = window.fetch.bind(window);
|
|
200
206
|
|
|
201
|
-
window.fetch =
|
|
207
|
+
window.fetch = createWrappedFetch(options, reportClientError, originalFetch);
|
|
208
|
+
fetchWrapped = true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function createWrappedFetch(options, reportClientError, originalFetch) {
|
|
212
|
+
return async (...args) => {
|
|
202
213
|
const input = args[0];
|
|
203
214
|
const url = typeof input === "string" ? input : input && typeof input.url === "string" ? input.url : "";
|
|
204
215
|
const apiPath = String(options.apiPath || "");
|
|
@@ -209,74 +220,86 @@ function setupFetchErrorReport(options, reportClientError) {
|
|
|
209
220
|
const response = await originalFetch(...args);
|
|
210
221
|
|
|
211
222
|
if (isApiRequest && !isErrorReportRequest) {
|
|
212
|
-
|
|
213
|
-
const parts = [];
|
|
214
|
-
|
|
215
|
-
if (url) {
|
|
216
|
-
parts.push(`url: ${url}`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (response.status) {
|
|
220
|
-
parts.push(`status: ${response.status}`);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
reportClientError("http", `请求失败:HTTP ${response.status}`, parts.join("\n"));
|
|
224
|
-
} else {
|
|
225
|
-
try {
|
|
226
|
-
const payload = await response.clone().json();
|
|
227
|
-
|
|
228
|
-
if (payload?.code !== 0) {
|
|
229
|
-
const parts = [];
|
|
230
|
-
|
|
231
|
-
if (url) {
|
|
232
|
-
parts.push(`url: ${url}`);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (response.status) {
|
|
236
|
-
parts.push(`status: ${response.status}`);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (payload?.code !== undefined) {
|
|
240
|
-
parts.push(`code: ${payload.code}`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (payload?.msg) {
|
|
244
|
-
parts.push(`msg: ${payload.msg}`);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (payload?.detail) {
|
|
248
|
-
parts.push(`detail: ${payload.detail}`);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
reportClientError("http", payload.msg || "请求失败", parts.join("\n"));
|
|
252
|
-
}
|
|
253
|
-
} catch {
|
|
254
|
-
// ignore
|
|
255
|
-
}
|
|
256
|
-
}
|
|
223
|
+
reportHttpResponseError(reportClientError, url, response);
|
|
257
224
|
}
|
|
258
225
|
|
|
259
226
|
return response;
|
|
260
227
|
} catch (error) {
|
|
261
228
|
if (isApiRequest && !isErrorReportRequest) {
|
|
262
|
-
|
|
229
|
+
reportHttpFetchError(reportClientError, url, error);
|
|
230
|
+
}
|
|
263
231
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
267
236
|
|
|
268
|
-
|
|
237
|
+
async function reportHttpResponseError(reportClientError, url, response) {
|
|
238
|
+
if (!response.ok) {
|
|
239
|
+
const parts = buildUrlParts(url);
|
|
269
240
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
241
|
+
if (response.status) {
|
|
242
|
+
parts.push(`status: ${response.status}`);
|
|
243
|
+
}
|
|
273
244
|
|
|
274
|
-
|
|
275
|
-
|
|
245
|
+
reportClientError("http", `请求失败:HTTP ${response.status}`, parts.join("\n"));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
276
248
|
|
|
277
|
-
|
|
249
|
+
try {
|
|
250
|
+
const payload = await response.clone().json();
|
|
251
|
+
|
|
252
|
+
if (payload?.code !== 0) {
|
|
253
|
+
reportClientError("http", payload.msg || "请求失败", buildHttpPayloadParts(url, response, payload).join("\n"));
|
|
278
254
|
}
|
|
279
|
-
}
|
|
255
|
+
} catch {
|
|
256
|
+
// ignore
|
|
257
|
+
}
|
|
258
|
+
}
|
|
280
259
|
|
|
281
|
-
|
|
260
|
+
function reportHttpFetchError(reportClientError, url, error) {
|
|
261
|
+
const parts = buildUrlParts(url);
|
|
262
|
+
const errorDetail = getErrorDetail(error);
|
|
263
|
+
|
|
264
|
+
if (errorDetail) {
|
|
265
|
+
parts.push(`detail: ${errorDetail}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
reportClientError("http", getErrorMessage(error), parts.join("\n"));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function buildUrlParts(url) {
|
|
272
|
+
const parts = [];
|
|
273
|
+
|
|
274
|
+
if (url) {
|
|
275
|
+
parts.push(`url: ${url}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return parts;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function buildHttpPayloadParts(url, response, payload) {
|
|
282
|
+
const parts = buildUrlParts(url);
|
|
283
|
+
|
|
284
|
+
if (response.status) {
|
|
285
|
+
parts.push(`status: ${response.status}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (payload?.code !== undefined) {
|
|
289
|
+
parts.push(`code: ${payload.code}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (payload?.msg) {
|
|
293
|
+
parts.push(`msg: ${payload.msg}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (payload?.detail) {
|
|
297
|
+
parts.push(`detail: ${payload.detail}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return parts;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function resetFetchWrapper() {
|
|
304
|
+
fetchWrapped = false;
|
|
282
305
|
}
|
package/utils/infoReport.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { appendPageAndExtraData, createCommonReportData, getReportRequest, getRouteReportData } from "../internal/reportUtil.js";
|
|
2
|
-
|
|
3
|
-
const INFO_REPORT_PATH = "/core/tongJi/infoReport";
|
|
4
|
-
|
|
5
|
-
function buildReportData(options, extraData) {
|
|
6
|
-
return appendPageAndExtraData(createCommonReportData(options), options, extraData);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function createInfoReport(options) {
|
|
10
|
-
const request = getReportRequest(options, "createInfoReport");
|
|
11
|
-
|
|
12
|
-
return {
|
|
13
|
-
reportInfo: function (extraData = {}) {
|
|
14
|
-
return request(INFO_REPORT_PATH, buildReportData(options, extraData));
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function setupInfoReport(options) {
|
|
20
|
-
const infoReporter = createInfoReport(options);
|
|
21
|
-
|
|
22
|
-
if (options.router && typeof options.router.afterEach === "function") {
|
|
23
|
-
options.router.afterEach((to) => {
|
|
24
|
-
void infoReporter.reportInfo(getRouteReportData(to)).catch(() => {
|
|
25
|
-
// ignore
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return infoReporter;
|
|
31
|
-
}
|