@zhongxiaobing/monitor-react 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/dist/index.mjs ADDED
@@ -0,0 +1,737 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/createReactMonitor.tsx
6
+ import { createContext, useContext } from "react";
7
+
8
+ // ../monitor-shared/src/utils/uuid.ts
9
+ function createEventId() {
10
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
11
+ return crypto.randomUUID();
12
+ }
13
+ return `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
14
+ }
15
+
16
+ // ../monitor-shared/src/utils/error.ts
17
+ function normalizeError(error) {
18
+ if (error instanceof Error) {
19
+ return {
20
+ name: error.name,
21
+ message: error.message,
22
+ stack: error.stack
23
+ };
24
+ }
25
+ if (typeof error === "string") {
26
+ return {
27
+ message: error
28
+ };
29
+ }
30
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
31
+ const maybeError = error;
32
+ return {
33
+ name: typeof maybeError.name === "string" ? maybeError.name : void 0,
34
+ message: maybeError.message,
35
+ stack: typeof maybeError.stack === "string" ? maybeError.stack : void 0
36
+ };
37
+ }
38
+ return {
39
+ message: "Unknown error"
40
+ };
41
+ }
42
+
43
+ // ../monitor-shared/src/utils/browser.ts
44
+ function getLocationInfo() {
45
+ return {
46
+ url: window.location.href,
47
+ pathname: window.location.pathname,
48
+ title: document.title,
49
+ userAgent: navigator.userAgent
50
+ };
51
+ }
52
+
53
+ // ../monitor-transport/src/queue.ts
54
+ var EventQueue = class {
55
+ constructor() {
56
+ __publicField(this, "events", []);
57
+ }
58
+ add(event) {
59
+ this.events.push(event);
60
+ }
61
+ drain() {
62
+ const current = [...this.events];
63
+ this.events = [];
64
+ return current;
65
+ }
66
+ size() {
67
+ return this.events.length;
68
+ }
69
+ };
70
+
71
+ // ../monitor-transport/src/sender.ts
72
+ async function sendEvents(dsn, events) {
73
+ if (!events.length) return;
74
+ if (navigator.sendBeacon) {
75
+ const blob = new Blob([JSON.stringify({ events })], {
76
+ type: "application/json"
77
+ });
78
+ navigator.sendBeacon(dsn, blob);
79
+ return;
80
+ }
81
+ await fetch(dsn, {
82
+ method: "POST",
83
+ headers: {
84
+ "Content-Type": "application/json"
85
+ },
86
+ body: JSON.stringify({ events }),
87
+ keepalive: true
88
+ });
89
+ }
90
+
91
+ // ../monitor-core/src/context.ts
92
+ function createBaseContext(options) {
93
+ const locationInfo = getLocationInfo();
94
+ return {
95
+ appId: options.appId,
96
+ appName: options.appName,
97
+ env: options.env,
98
+ release: options.release,
99
+ ...locationInfo
100
+ };
101
+ }
102
+
103
+ // ../monitor-core/src/plugin-system.ts
104
+ function setupPlugins(plugins, api, options) {
105
+ const disposers = [];
106
+ for (const plugin of plugins) {
107
+ const disposer = plugin.setup({ api, options });
108
+ if (typeof disposer === "function") {
109
+ disposers.push(disposer);
110
+ }
111
+ }
112
+ return () => {
113
+ for (const dispose of disposers) {
114
+ dispose();
115
+ }
116
+ };
117
+ }
118
+
119
+ // ../monitor-core/src/monitor.ts
120
+ var Monitor = class {
121
+ constructor(options) {
122
+ __publicField(this, "queue", new EventQueue());
123
+ __publicField(this, "options");
124
+ __publicField(this, "context");
125
+ __publicField(this, "disposePlugins", null);
126
+ var _a;
127
+ this.options = options;
128
+ this.context = createBaseContext(options);
129
+ this.disposePlugins = setupPlugins((_a = options.plugins) != null ? _a : [], this, options);
130
+ }
131
+ emit(event) {
132
+ const mergedEvent = {
133
+ ...this.context,
134
+ ...event,
135
+ appId: event.appId || this.context.appId,
136
+ appName: event.appName || this.context.appName,
137
+ env: event.env || this.context.env,
138
+ release: event.release || this.context.release,
139
+ url: event.url || this.context.url,
140
+ pathname: event.pathname || this.context.pathname,
141
+ title: event.title || this.context.title,
142
+ userAgent: event.userAgent || this.context.userAgent,
143
+ eventId: event.eventId || createEventId(),
144
+ timestamp: event.timestamp || Date.now()
145
+ };
146
+ const finalEvent = this.options.beforeSend ? this.options.beforeSend(mergedEvent) : mergedEvent;
147
+ if (!finalEvent) return;
148
+ this.queue.add(finalEvent);
149
+ void this.flush();
150
+ }
151
+ captureException(error, extra) {
152
+ const normalized = normalizeError(error);
153
+ const event = {
154
+ eventId: "",
155
+ eventType: "exception",
156
+ appId: "",
157
+ env: "",
158
+ url: "",
159
+ pathname: "",
160
+ title: "",
161
+ timestamp: 0,
162
+ userAgent: "",
163
+ extra,
164
+ error: {
165
+ name: normalized.name,
166
+ message: normalized.message,
167
+ stack: normalized.stack,
168
+ source: "react"
169
+ }
170
+ };
171
+ this.emit(event);
172
+ }
173
+ async flush() {
174
+ const events = this.queue.drain();
175
+ await sendEvents(this.options.dsn, events);
176
+ }
177
+ destroy() {
178
+ var _a;
179
+ (_a = this.disposePlugins) == null ? void 0 : _a.call(this);
180
+ this.disposePlugins = null;
181
+ }
182
+ };
183
+ function initMonitor(options) {
184
+ return new Monitor(options);
185
+ }
186
+
187
+ // ../monitor-plugin-blank-screen/src/dedupe.ts
188
+ function normalizeScore(score) {
189
+ return score.toFixed(1);
190
+ }
191
+ function createDedupeKey(input) {
192
+ return [input.pathname, input.trigger, normalizeScore(input.score)].join("|");
193
+ }
194
+ function createBlankScreenDedupe(windowMs) {
195
+ const cache = /* @__PURE__ */ new Map();
196
+ function shouldReport(input) {
197
+ const now = Date.now();
198
+ const key = createDedupeKey(input);
199
+ const lastReportedAt = cache.get(key);
200
+ if (!lastReportedAt || now - lastReportedAt > windowMs) {
201
+ cache.set(key, now);
202
+ return true;
203
+ }
204
+ return false;
205
+ }
206
+ function cleanup() {
207
+ const now = Date.now();
208
+ for (const [key, timestamp] of cache.entries()) {
209
+ if (now - timestamp > windowMs) {
210
+ cache.delete(key);
211
+ }
212
+ }
213
+ }
214
+ return {
215
+ shouldReport,
216
+ cleanup
217
+ };
218
+ }
219
+
220
+ // ../monitor-plugin-blank-screen/src/root.ts
221
+ function getRootElement(rootSelector) {
222
+ if (rootSelector) {
223
+ const customRoot = document.querySelector(rootSelector);
224
+ if (customRoot) {
225
+ return customRoot;
226
+ }
227
+ }
228
+ const appRoot = document.querySelector("#app");
229
+ if (appRoot) {
230
+ return appRoot;
231
+ }
232
+ const reactRoot = document.querySelector("#root");
233
+ if (reactRoot) {
234
+ return reactRoot;
235
+ }
236
+ return document.body;
237
+ }
238
+
239
+ // ../monitor-plugin-blank-screen/src/sampler.ts
240
+ var DEFAULT_SAMPLE_POINTS = [
241
+ [0.5, 0.5],
242
+ [0.2, 0.2],
243
+ [0.5, 0.2],
244
+ [0.8, 0.2],
245
+ [0.2, 0.5],
246
+ [0.8, 0.5],
247
+ [0.2, 0.8],
248
+ [0.5, 0.8],
249
+ [0.8, 0.8]
250
+ ];
251
+ function getSamplePoints(samplePoints) {
252
+ return (samplePoints == null ? void 0 : samplePoints.length) ? samplePoints : DEFAULT_SAMPLE_POINTS;
253
+ }
254
+ function sampleElements(points) {
255
+ const width = window.innerWidth;
256
+ const height = window.innerHeight;
257
+ return points.map(([xRatio, yRatio]) => {
258
+ const x = Math.floor(width * xRatio);
259
+ const y = Math.floor(height * yRatio);
260
+ const element = document.elementFromPoint(x, y);
261
+ return {
262
+ x,
263
+ y,
264
+ element
265
+ };
266
+ });
267
+ }
268
+
269
+ // ../monitor-plugin-blank-screen/src/score.ts
270
+ function matchesIgnoreSelectors(element, ignoreSelectors) {
271
+ return ignoreSelectors.some((selector) => {
272
+ try {
273
+ return element.matches(selector) || !!element.closest(selector);
274
+ } catch {
275
+ return false;
276
+ }
277
+ });
278
+ }
279
+ function isContainerElement(element, rootElement) {
280
+ const tagName = element.tagName.toLowerCase();
281
+ if (tagName === "html" || tagName === "body") {
282
+ return true;
283
+ }
284
+ if (element === rootElement) {
285
+ return true;
286
+ }
287
+ return false;
288
+ }
289
+ function getElementSummary(element) {
290
+ if (!element) return "null";
291
+ const tagName = element.tagName.toLowerCase();
292
+ const id = element.id ? `#${element.id}` : "";
293
+ const className = typeof element.className === "string" && element.className.trim() ? `.${element.className.trim().split(/\s+/).join(".")}` : "";
294
+ return `${tagName}${id}${className}`;
295
+ }
296
+ function calculateBlankScore(sampledElements, rootElement, ignoreSelectors) {
297
+ let blankCount = 0;
298
+ const domSummary = sampledElements.map(({ element }) => {
299
+ if (!element) {
300
+ blankCount += 1;
301
+ return "null";
302
+ }
303
+ if (matchesIgnoreSelectors(element, ignoreSelectors)) {
304
+ blankCount += 1;
305
+ return `${getElementSummary(element)}(ignored)`;
306
+ }
307
+ if (isContainerElement(element, rootElement)) {
308
+ blankCount += 1;
309
+ return `${getElementSummary(element)}(container)`;
310
+ }
311
+ return getElementSummary(element);
312
+ });
313
+ return {
314
+ score: sampledElements.length ? blankCount / sampledElements.length : 0,
315
+ domSummary
316
+ };
317
+ }
318
+
319
+ // ../monitor-plugin-blank-screen/src/detect.ts
320
+ function runBlankScreenCheck(options) {
321
+ const {
322
+ rootSelector,
323
+ scoreThreshold = 0.8,
324
+ samplePoints,
325
+ ignoreSelectors = []
326
+ } = options;
327
+ const rootElement = getRootElement(rootSelector);
328
+ const points = getSamplePoints(samplePoints);
329
+ const sampledElements = sampleElements(points);
330
+ const { score, domSummary } = calculateBlankScore(
331
+ sampledElements,
332
+ rootElement,
333
+ ignoreSelectors
334
+ );
335
+ if (score < scoreThreshold) {
336
+ return null;
337
+ }
338
+ return {
339
+ score,
340
+ rootSelector,
341
+ domSummary
342
+ };
343
+ }
344
+
345
+ // ../monitor-plugin-blank-screen/src/normalize.ts
346
+ function createBlankScreenEvent({
347
+ score,
348
+ rootSelector,
349
+ domSummary,
350
+ trigger
351
+ }) {
352
+ return {
353
+ eventId: "",
354
+ eventType: "blank_screen",
355
+ appId: "",
356
+ env: "",
357
+ url: "",
358
+ pathname: "",
359
+ title: "",
360
+ timestamp: 0,
361
+ userAgent: "",
362
+ blankScreen: {
363
+ score,
364
+ rootSelector,
365
+ domSummary,
366
+ readyState: document.readyState,
367
+ trigger
368
+ }
369
+ };
370
+ }
371
+
372
+ // ../monitor-plugin-blank-screen/src/route-change.ts
373
+ function registerRouteChangeListener(onRouteChange) {
374
+ const originalPushState = history.pushState;
375
+ const originalReplaceState = history.replaceState;
376
+ const handleRouteChange = () => {
377
+ onRouteChange();
378
+ };
379
+ history.pushState = function(...args) {
380
+ const result = originalPushState.apply(this, args);
381
+ handleRouteChange();
382
+ return result;
383
+ };
384
+ history.replaceState = function(...args) {
385
+ const result = originalReplaceState.apply(this, args);
386
+ handleRouteChange();
387
+ return result;
388
+ };
389
+ window.addEventListener("popstate", handleRouteChange);
390
+ window.addEventListener("hashchange", handleRouteChange);
391
+ return () => {
392
+ history.pushState = originalPushState;
393
+ history.replaceState = originalReplaceState;
394
+ window.removeEventListener("popstate", handleRouteChange);
395
+ window.removeEventListener("hashchange", handleRouteChange);
396
+ };
397
+ }
398
+
399
+ // ../monitor-plugin-blank-screen/src/index.ts
400
+ function blankScreenPlugin(options = {}) {
401
+ const {
402
+ delayMs = 3e3,
403
+ detectOnRouteChange = true,
404
+ routeChangeDelayMs = 2e3,
405
+ dedupeWindowMs = 1e4
406
+ } = options;
407
+ return {
408
+ name: "blank-screen-plugin",
409
+ setup({ api }) {
410
+ if (typeof window === "undefined" || typeof document === "undefined") {
411
+ return;
412
+ }
413
+ const dedupe = createBlankScreenDedupe(dedupeWindowMs);
414
+ const emitIfNeeded = (trigger) => {
415
+ dedupe.cleanup();
416
+ const result = runBlankScreenCheck(options);
417
+ if (!result) {
418
+ return;
419
+ }
420
+ const pathname = window.location.pathname;
421
+ const shouldReport = dedupe.shouldReport({
422
+ pathname,
423
+ trigger,
424
+ score: result.score
425
+ });
426
+ if (!shouldReport) {
427
+ return;
428
+ }
429
+ api.emit(
430
+ createBlankScreenEvent({
431
+ score: result.score,
432
+ rootSelector: result.rootSelector,
433
+ domSummary: result.domSummary,
434
+ trigger
435
+ })
436
+ );
437
+ };
438
+ const initialTimer = window.setTimeout(() => {
439
+ emitIfNeeded("initial");
440
+ }, delayMs);
441
+ let routeTimer = null;
442
+ const disposeRouteChange = detectOnRouteChange ? registerRouteChangeListener(() => {
443
+ if (routeTimer !== null) {
444
+ window.clearTimeout(routeTimer);
445
+ }
446
+ routeTimer = window.setTimeout(() => {
447
+ emitIfNeeded("route_change");
448
+ }, routeChangeDelayMs);
449
+ }) : () => {
450
+ };
451
+ return () => {
452
+ window.clearTimeout(initialTimer);
453
+ if (routeTimer !== null) {
454
+ window.clearTimeout(routeTimer);
455
+ }
456
+ disposeRouteChange();
457
+ };
458
+ }
459
+ };
460
+ }
461
+
462
+ // ../monitor-plugin-error/src/normalize.ts
463
+ function createExceptionEvent({
464
+ error,
465
+ source
466
+ }) {
467
+ const normalized = normalizeError(error);
468
+ return {
469
+ eventId: "",
470
+ eventType: "exception",
471
+ appId: "",
472
+ env: "",
473
+ url: "",
474
+ pathname: "",
475
+ title: "",
476
+ timestamp: 0,
477
+ userAgent: "",
478
+ error: {
479
+ name: normalized.name,
480
+ message: normalized.message,
481
+ stack: normalized.stack,
482
+ source
483
+ }
484
+ };
485
+ }
486
+
487
+ // ../monitor-plugin-error/src/unhandledrejection.ts
488
+ function registerUnhandledRejection(api) {
489
+ const handler = (event) => {
490
+ const exceptionEvent = createExceptionEvent({
491
+ error: event.reason,
492
+ source: "unhandledrejection"
493
+ });
494
+ api.emit(exceptionEvent);
495
+ };
496
+ window.addEventListener("unhandledrejection", handler);
497
+ return () => {
498
+ window.removeEventListener("unhandledrejection", handler);
499
+ };
500
+ }
501
+
502
+ // ../monitor-plugin-error/src/onerror.ts
503
+ function registerWindowOnError(api) {
504
+ const handler = (message, _source, _lineno, _colno, error) => {
505
+ const targetError = error != null ? error : message;
506
+ const event = createExceptionEvent({
507
+ error: targetError,
508
+ source: "window.onerror"
509
+ });
510
+ api.emit(event);
511
+ return false;
512
+ };
513
+ window.onerror = handler;
514
+ return () => {
515
+ if (window.onerror === handler) {
516
+ window.onerror = null;
517
+ }
518
+ };
519
+ }
520
+
521
+ // ../monitor-plugin-error/src/index.ts
522
+ function errorPlugin(options = {}) {
523
+ const {
524
+ captureOnError = true,
525
+ captureUnhandledRejection = true
526
+ } = options;
527
+ return {
528
+ name: "error-plugin",
529
+ setup({ api }) {
530
+ const disposers = [];
531
+ if (captureOnError) {
532
+ disposers.push(registerWindowOnError(api));
533
+ }
534
+ if (captureUnhandledRejection) {
535
+ disposers.push(registerUnhandledRejection(api));
536
+ }
537
+ return () => {
538
+ for (const dispose of disposers) {
539
+ dispose();
540
+ }
541
+ };
542
+ }
543
+ };
544
+ }
545
+
546
+ // ../monitor-plugin-network/src/normalize.ts
547
+ function createHttpErrorEvent({
548
+ url,
549
+ method,
550
+ status,
551
+ duration,
552
+ responseMessage
553
+ }) {
554
+ return {
555
+ eventId: "",
556
+ eventType: "http_error",
557
+ appId: "",
558
+ env: "",
559
+ url: "",
560
+ pathname: "",
561
+ title: "",
562
+ timestamp: 0,
563
+ userAgent: "",
564
+ request: {
565
+ url,
566
+ method,
567
+ status,
568
+ duration,
569
+ success: false,
570
+ source: "fetch"
571
+ },
572
+ response: {
573
+ message: responseMessage
574
+ }
575
+ };
576
+ }
577
+
578
+ // ../monitor-plugin-network/src/url.ts
579
+ function resolveUrlObject(url) {
580
+ return new URL(url, window.location.origin);
581
+ }
582
+ function isMonitorRequest(requestUrl, dsn) {
583
+ try {
584
+ const request = resolveUrlObject(requestUrl);
585
+ const target = resolveUrlObject(dsn);
586
+ return request.origin === target.origin && request.pathname === target.pathname;
587
+ } catch {
588
+ return requestUrl === dsn;
589
+ }
590
+ }
591
+
592
+ // ../monitor-plugin-network/src/patch-fetch.ts
593
+ function patchFetch(api, sdkOptions, options) {
594
+ if (typeof window === "undefined" || typeof window.fetch !== "function") {
595
+ return () => {
596
+ };
597
+ }
598
+ const originalFetch = window.fetch.bind(window);
599
+ const {
600
+ capture5xx = true,
601
+ captureNetworkError = true
602
+ } = options;
603
+ window.fetch = async (input, init) => {
604
+ const method = ((init == null ? void 0 : init.method) || "GET").toUpperCase();
605
+ const requestUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
606
+ if (isMonitorRequest(requestUrl, sdkOptions.dsn)) {
607
+ return originalFetch(input, init);
608
+ }
609
+ const startedAt = Date.now();
610
+ try {
611
+ const response = await originalFetch(input, init);
612
+ const duration = Date.now() - startedAt;
613
+ if (capture5xx && response.status >= 500) {
614
+ api.emit(
615
+ createHttpErrorEvent({
616
+ url: requestUrl,
617
+ method,
618
+ status: response.status,
619
+ duration,
620
+ responseMessage: response.statusText
621
+ })
622
+ );
623
+ }
624
+ return response;
625
+ } catch (error) {
626
+ const duration = Date.now() - startedAt;
627
+ if (captureNetworkError) {
628
+ api.emit(
629
+ createHttpErrorEvent({
630
+ url: requestUrl,
631
+ method,
632
+ duration,
633
+ responseMessage: error instanceof Error ? error.message : "Fetch request failed"
634
+ })
635
+ );
636
+ }
637
+ throw error;
638
+ }
639
+ };
640
+ return () => {
641
+ window.fetch = originalFetch;
642
+ };
643
+ }
644
+
645
+ // ../monitor-plugin-network/src/index.ts
646
+ function networkPlugin(options = {}) {
647
+ return {
648
+ name: "network-plugin",
649
+ setup({ api, options: sdkOptions }) {
650
+ const disposeFetch = patchFetch(api, sdkOptions, options);
651
+ return () => {
652
+ disposeFetch();
653
+ };
654
+ }
655
+ };
656
+ }
657
+
658
+ // src/MonitorErrorBoundary.tsx
659
+ import { Component } from "react";
660
+ var MonitorErrorBoundary = class extends Component {
661
+ constructor() {
662
+ super(...arguments);
663
+ __publicField(this, "state", {
664
+ hasError: false
665
+ });
666
+ }
667
+ static getDerivedStateFromError() {
668
+ return { hasError: true };
669
+ }
670
+ componentDidCatch(error, info) {
671
+ this.props.monitor.captureException(error, {
672
+ componentStack: info.componentStack
673
+ });
674
+ }
675
+ render() {
676
+ var _a;
677
+ if (this.state.hasError) {
678
+ return (_a = this.props.fallback) != null ? _a : null;
679
+ }
680
+ return this.props.children;
681
+ }
682
+ };
683
+
684
+ // src/createReactMonitor.tsx
685
+ import { jsx } from "react/jsx-runtime";
686
+ var MonitorContext = createContext(null);
687
+ function createReactMonitor(options) {
688
+ var _a;
689
+ const monitor = initMonitor({
690
+ ...options,
691
+ plugins: [
692
+ ...getDefaultPlugins(options),
693
+ ...(_a = options.plugins) != null ? _a : []
694
+ ]
695
+ });
696
+ function MonitorRoot({ children, fallback }) {
697
+ return /* @__PURE__ */ jsx(MonitorContext.Provider, { value: monitor, children: /* @__PURE__ */ jsx(MonitorErrorBoundary, { monitor, fallback: fallback != null ? fallback : options.fallback, children }) });
698
+ }
699
+ function withMonitor(Component2) {
700
+ return function WrappedComponent(props) {
701
+ return /* @__PURE__ */ jsx(MonitorRoot, { children: /* @__PURE__ */ jsx(Component2, { ...props }) });
702
+ };
703
+ }
704
+ return {
705
+ monitor,
706
+ MonitorRoot,
707
+ withMonitor
708
+ };
709
+ }
710
+ function useMonitor() {
711
+ const monitor = useContext(MonitorContext);
712
+ if (!monitor) {
713
+ throw new Error("useMonitor must be used within MonitorRoot");
714
+ }
715
+ return monitor;
716
+ }
717
+ function getDefaultPlugins(options) {
718
+ var _a;
719
+ const disabled = new Set((_a = options.disableDefaultPlugins) != null ? _a : []);
720
+ const plugins = [];
721
+ if (!disabled.has("error")) {
722
+ plugins.push(errorPlugin(options.error));
723
+ }
724
+ if (!disabled.has("network")) {
725
+ plugins.push(networkPlugin(options.network));
726
+ }
727
+ if (!disabled.has("blankScreen")) {
728
+ plugins.push(blankScreenPlugin(options.blankScreen));
729
+ }
730
+ return plugins;
731
+ }
732
+ export {
733
+ MonitorErrorBoundary,
734
+ createReactMonitor,
735
+ useMonitor
736
+ };
737
+ //# sourceMappingURL=index.mjs.map