@crashsense/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.d.mts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +139 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +126 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { CrashSenseCore, CrashSenseConfig, CrashReport, CrashSeverity, Breadcrumb } from '@crashsense/types';
|
|
3
|
+
export { CrashAnalysis, CrashEvent, CrashReport, CrashSenseConfig } from '@crashsense/types';
|
|
4
|
+
export { createCrashSense } from '@crashsense/core';
|
|
5
|
+
|
|
6
|
+
declare const CrashSenseContext: React.Context<CrashSenseCore | null>;
|
|
7
|
+
interface CrashSenseProviderProps {
|
|
8
|
+
config: CrashSenseConfig;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
fallback?: React.ReactNode;
|
|
11
|
+
onCrash?: (report: CrashReport) => void;
|
|
12
|
+
}
|
|
13
|
+
interface CrashSenseProviderState {
|
|
14
|
+
hasError: boolean;
|
|
15
|
+
}
|
|
16
|
+
declare class CrashSenseProvider extends React.Component<CrashSenseProviderProps, CrashSenseProviderState> {
|
|
17
|
+
private core;
|
|
18
|
+
constructor(props: CrashSenseProviderProps);
|
|
19
|
+
static getDerivedStateFromError(_error: Error): CrashSenseProviderState;
|
|
20
|
+
componentDidMount(): void;
|
|
21
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
|
|
22
|
+
componentWillUnmount(): void;
|
|
23
|
+
render(): React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare function useCrashSense(): {
|
|
27
|
+
captureException: (error: unknown, context?: Record<string, unknown>) => void;
|
|
28
|
+
captureMessage: (message: string, severity?: CrashSeverity) => void;
|
|
29
|
+
addBreadcrumb: (breadcrumb: Omit<Breadcrumb, 'timestamp'>) => void;
|
|
30
|
+
core: CrashSenseCore | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
declare function useRenderTracker(componentName: string, threshold?: number): void;
|
|
34
|
+
|
|
35
|
+
declare function createHydrationDetector(core: CrashSenseCore): {
|
|
36
|
+
install(): void;
|
|
37
|
+
uninstall(): void;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { CrashSenseContext, CrashSenseProvider, createHydrationDetector, useCrashSense, useRenderTracker };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { CrashSenseCore, CrashSenseConfig, CrashReport, CrashSeverity, Breadcrumb } from '@crashsense/types';
|
|
3
|
+
export { CrashAnalysis, CrashEvent, CrashReport, CrashSenseConfig } from '@crashsense/types';
|
|
4
|
+
export { createCrashSense } from '@crashsense/core';
|
|
5
|
+
|
|
6
|
+
declare const CrashSenseContext: React.Context<CrashSenseCore | null>;
|
|
7
|
+
interface CrashSenseProviderProps {
|
|
8
|
+
config: CrashSenseConfig;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
fallback?: React.ReactNode;
|
|
11
|
+
onCrash?: (report: CrashReport) => void;
|
|
12
|
+
}
|
|
13
|
+
interface CrashSenseProviderState {
|
|
14
|
+
hasError: boolean;
|
|
15
|
+
}
|
|
16
|
+
declare class CrashSenseProvider extends React.Component<CrashSenseProviderProps, CrashSenseProviderState> {
|
|
17
|
+
private core;
|
|
18
|
+
constructor(props: CrashSenseProviderProps);
|
|
19
|
+
static getDerivedStateFromError(_error: Error): CrashSenseProviderState;
|
|
20
|
+
componentDidMount(): void;
|
|
21
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
|
|
22
|
+
componentWillUnmount(): void;
|
|
23
|
+
render(): React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare function useCrashSense(): {
|
|
27
|
+
captureException: (error: unknown, context?: Record<string, unknown>) => void;
|
|
28
|
+
captureMessage: (message: string, severity?: CrashSeverity) => void;
|
|
29
|
+
addBreadcrumb: (breadcrumb: Omit<Breadcrumb, 'timestamp'>) => void;
|
|
30
|
+
core: CrashSenseCore | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
declare function useRenderTracker(componentName: string, threshold?: number): void;
|
|
34
|
+
|
|
35
|
+
declare function createHydrationDetector(core: CrashSenseCore): {
|
|
36
|
+
install(): void;
|
|
37
|
+
uninstall(): void;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { CrashSenseContext, CrashSenseProvider, createHydrationDetector, useCrashSense, useRenderTracker };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
var core = require('@crashsense/core');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
9
|
+
|
|
10
|
+
// src/provider.ts
|
|
11
|
+
var CrashSenseContext = React__default.default.createContext(null);
|
|
12
|
+
var CrashSenseProvider = class extends React__default.default.Component {
|
|
13
|
+
constructor(props) {
|
|
14
|
+
super(props);
|
|
15
|
+
this.core = null;
|
|
16
|
+
this.state = { hasError: false };
|
|
17
|
+
}
|
|
18
|
+
static getDerivedStateFromError(_error) {
|
|
19
|
+
return { hasError: true };
|
|
20
|
+
}
|
|
21
|
+
componentDidMount() {
|
|
22
|
+
const enrichedConfig = {
|
|
23
|
+
...this.props.config,
|
|
24
|
+
onCrash: (report) => {
|
|
25
|
+
this.props.onCrash?.(report);
|
|
26
|
+
this.props.config.onCrash?.(report);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
this.core = core.createCrashSense(enrichedConfig);
|
|
30
|
+
}
|
|
31
|
+
componentDidCatch(error, errorInfo) {
|
|
32
|
+
if (!this.core) return;
|
|
33
|
+
this.core.captureException(error, {
|
|
34
|
+
componentStack: errorInfo.componentStack ?? "",
|
|
35
|
+
framework: "react",
|
|
36
|
+
lifecycleStage: "rendering"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
componentWillUnmount() {
|
|
40
|
+
this.core?.destroy();
|
|
41
|
+
this.core = null;
|
|
42
|
+
}
|
|
43
|
+
render() {
|
|
44
|
+
if (this.state.hasError && this.props.fallback !== void 0) {
|
|
45
|
+
return React__default.default.createElement(
|
|
46
|
+
CrashSenseContext.Provider,
|
|
47
|
+
{ value: this.core },
|
|
48
|
+
this.props.fallback
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return React__default.default.createElement(
|
|
52
|
+
CrashSenseContext.Provider,
|
|
53
|
+
{ value: this.core },
|
|
54
|
+
this.props.children
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
function useCrashSense() {
|
|
59
|
+
const core = React.useContext(CrashSenseContext);
|
|
60
|
+
return {
|
|
61
|
+
captureException: (error, context) => {
|
|
62
|
+
core?.captureException(error, context);
|
|
63
|
+
},
|
|
64
|
+
captureMessage: (message, severity) => {
|
|
65
|
+
core?.captureMessage(message, severity);
|
|
66
|
+
},
|
|
67
|
+
addBreadcrumb: (breadcrumb) => {
|
|
68
|
+
core?.addBreadcrumb(breadcrumb);
|
|
69
|
+
},
|
|
70
|
+
core
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
var DEFAULT_THRESHOLD = 50;
|
|
74
|
+
var WINDOW_MS = 1e3;
|
|
75
|
+
function useRenderTracker(componentName, threshold = DEFAULT_THRESHOLD) {
|
|
76
|
+
const renderTimestamps = React.useRef([]);
|
|
77
|
+
const warned = React.useRef(false);
|
|
78
|
+
const { captureMessage } = useCrashSense();
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
renderTimestamps.current.push(now);
|
|
81
|
+
const cutoff = now - WINDOW_MS;
|
|
82
|
+
renderTimestamps.current = renderTimestamps.current.filter((t) => t > cutoff);
|
|
83
|
+
if (renderTimestamps.current.length > threshold && !warned.current) {
|
|
84
|
+
warned.current = true;
|
|
85
|
+
captureMessage(
|
|
86
|
+
`Potential infinite re-render detected in ${componentName}: ${renderTimestamps.current.length} renders in ${WINDOW_MS}ms`,
|
|
87
|
+
"warning"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
return () => {
|
|
92
|
+
renderTimestamps.current = [];
|
|
93
|
+
warned.current = false;
|
|
94
|
+
};
|
|
95
|
+
}, []);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/hydration-detector.ts
|
|
99
|
+
function createHydrationDetector(core) {
|
|
100
|
+
let originalConsoleError = null;
|
|
101
|
+
return {
|
|
102
|
+
install() {
|
|
103
|
+
if (typeof console === "undefined") return;
|
|
104
|
+
originalConsoleError = console.error;
|
|
105
|
+
console.error = (...args) => {
|
|
106
|
+
originalConsoleError.apply(console, args);
|
|
107
|
+
const message = args.map(String).join(" ");
|
|
108
|
+
if (/hydrat/i.test(message)) {
|
|
109
|
+
core.captureException(
|
|
110
|
+
new Error(`Hydration mismatch: ${message.slice(0, 200)}`),
|
|
111
|
+
{
|
|
112
|
+
source: "hydration_detector",
|
|
113
|
+
framework: "react",
|
|
114
|
+
lifecycleStage: "hydrating"
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
uninstall() {
|
|
121
|
+
if (originalConsoleError) {
|
|
122
|
+
console.error = originalConsoleError;
|
|
123
|
+
originalConsoleError = null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
Object.defineProperty(exports, "createCrashSense", {
|
|
130
|
+
enumerable: true,
|
|
131
|
+
get: function () { return core.createCrashSense; }
|
|
132
|
+
});
|
|
133
|
+
exports.CrashSenseContext = CrashSenseContext;
|
|
134
|
+
exports.CrashSenseProvider = CrashSenseProvider;
|
|
135
|
+
exports.createHydrationDetector = createHydrationDetector;
|
|
136
|
+
exports.useCrashSense = useCrashSense;
|
|
137
|
+
exports.useRenderTracker = useRenderTracker;
|
|
138
|
+
//# sourceMappingURL=index.js.map
|
|
139
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider.ts","../src/hooks.ts","../src/render-tracker.ts","../src/hydration-detector.ts"],"names":["React","createCrashSense","useContext","useRef","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import React from 'react';\nimport type { CrashSenseConfig, CrashSenseCore, CrashReport } from '@crashsense/types';\nimport { createCrashSense } from '@crashsense/core';\n\nexport const CrashSenseContext = React.createContext<CrashSenseCore | null>(null);\n\ninterface CrashSenseProviderProps {\n config: CrashSenseConfig;\n children: React.ReactNode;\n fallback?: React.ReactNode;\n onCrash?: (report: CrashReport) => void;\n}\n\ninterface CrashSenseProviderState {\n hasError: boolean;\n}\n\nexport class CrashSenseProvider extends React.Component<\n CrashSenseProviderProps,\n CrashSenseProviderState\n> {\n private core: CrashSenseCore | null = null;\n\n constructor(props: CrashSenseProviderProps) {\n super(props);\n this.state = { hasError: false };\n }\n\n static getDerivedStateFromError(_error: Error): CrashSenseProviderState {\n return { hasError: true };\n }\n\n componentDidMount(): void {\n const enrichedConfig: CrashSenseConfig = {\n ...this.props.config,\n onCrash: (report: CrashReport) => {\n this.props.onCrash?.(report);\n this.props.config.onCrash?.(report);\n },\n };\n\n this.core = createCrashSense(enrichedConfig);\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {\n if (!this.core) return;\n\n this.core.captureException(error, {\n componentStack: errorInfo.componentStack ?? '',\n framework: 'react',\n lifecycleStage: 'rendering',\n });\n }\n\n componentWillUnmount(): void {\n this.core?.destroy();\n this.core = null;\n }\n\n render(): React.ReactNode {\n if (this.state.hasError && this.props.fallback !== undefined) {\n return React.createElement(\n CrashSenseContext.Provider,\n { value: this.core },\n this.props.fallback,\n );\n }\n\n return React.createElement(\n CrashSenseContext.Provider,\n { value: this.core },\n this.props.children,\n );\n }\n}\n","import { useContext } from 'react';\nimport type { CrashSenseCore, Breadcrumb, CrashSeverity } from '@crashsense/types';\nimport { CrashSenseContext } from './provider';\n\nexport function useCrashSense(): {\n captureException: (error: unknown, context?: Record<string, unknown>) => void;\n captureMessage: (message: string, severity?: CrashSeverity) => void;\n addBreadcrumb: (breadcrumb: Omit<Breadcrumb, 'timestamp'>) => void;\n core: CrashSenseCore | null;\n} {\n const core = useContext(CrashSenseContext);\n\n return {\n captureException: (error: unknown, context?: Record<string, unknown>) => {\n core?.captureException(error, context);\n },\n captureMessage: (message: string, severity?: CrashSeverity) => {\n core?.captureMessage(message, severity);\n },\n addBreadcrumb: (breadcrumb: Omit<Breadcrumb, 'timestamp'>) => {\n core?.addBreadcrumb(breadcrumb);\n },\n core,\n };\n}\n","import { useRef, useEffect } from 'react';\nimport { useCrashSense } from './hooks';\n\nconst DEFAULT_THRESHOLD = 50;\nconst WINDOW_MS = 1000;\n\nexport function useRenderTracker(\n componentName: string,\n threshold: number = DEFAULT_THRESHOLD,\n): void {\n const renderTimestamps = useRef<number[]>([]);\n const warned = useRef(false);\n const { captureMessage } = useCrashSense();\n\n const now = Date.now();\n renderTimestamps.current.push(now);\n\n const cutoff = now - WINDOW_MS;\n renderTimestamps.current = renderTimestamps.current.filter((t) => t > cutoff);\n\n if (renderTimestamps.current.length > threshold && !warned.current) {\n warned.current = true;\n captureMessage(\n `Potential infinite re-render detected in ${componentName}: ${renderTimestamps.current.length} renders in ${WINDOW_MS}ms`,\n 'warning',\n );\n }\n\n useEffect(() => {\n return () => {\n renderTimestamps.current = [];\n warned.current = false;\n };\n }, []);\n}\n","import type { CrashSenseCore } from '@crashsense/types';\n\nexport function createHydrationDetector(core: CrashSenseCore) {\n let originalConsoleError: typeof console.error | null = null;\n\n return {\n install(): void {\n if (typeof console === 'undefined') return;\n\n originalConsoleError = console.error;\n\n console.error = (...args: unknown[]) => {\n originalConsoleError!.apply(console, args);\n\n const message = args.map(String).join(' ');\n if (/hydrat/i.test(message)) {\n core.captureException(\n new Error(`Hydration mismatch: ${message.slice(0, 200)}`),\n {\n source: 'hydration_detector',\n framework: 'react',\n lifecycleStage: 'hydrating',\n },\n );\n }\n };\n },\n\n uninstall(): void {\n if (originalConsoleError) {\n console.error = originalConsoleError;\n originalConsoleError = null;\n }\n },\n };\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React, { useContext, useRef, useEffect } from 'react';
|
|
2
|
+
import { createCrashSense } from '@crashsense/core';
|
|
3
|
+
export { createCrashSense } from '@crashsense/core';
|
|
4
|
+
|
|
5
|
+
// src/provider.ts
|
|
6
|
+
var CrashSenseContext = React.createContext(null);
|
|
7
|
+
var CrashSenseProvider = class extends React.Component {
|
|
8
|
+
constructor(props) {
|
|
9
|
+
super(props);
|
|
10
|
+
this.core = null;
|
|
11
|
+
this.state = { hasError: false };
|
|
12
|
+
}
|
|
13
|
+
static getDerivedStateFromError(_error) {
|
|
14
|
+
return { hasError: true };
|
|
15
|
+
}
|
|
16
|
+
componentDidMount() {
|
|
17
|
+
const enrichedConfig = {
|
|
18
|
+
...this.props.config,
|
|
19
|
+
onCrash: (report) => {
|
|
20
|
+
this.props.onCrash?.(report);
|
|
21
|
+
this.props.config.onCrash?.(report);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
this.core = createCrashSense(enrichedConfig);
|
|
25
|
+
}
|
|
26
|
+
componentDidCatch(error, errorInfo) {
|
|
27
|
+
if (!this.core) return;
|
|
28
|
+
this.core.captureException(error, {
|
|
29
|
+
componentStack: errorInfo.componentStack ?? "",
|
|
30
|
+
framework: "react",
|
|
31
|
+
lifecycleStage: "rendering"
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
componentWillUnmount() {
|
|
35
|
+
this.core?.destroy();
|
|
36
|
+
this.core = null;
|
|
37
|
+
}
|
|
38
|
+
render() {
|
|
39
|
+
if (this.state.hasError && this.props.fallback !== void 0) {
|
|
40
|
+
return React.createElement(
|
|
41
|
+
CrashSenseContext.Provider,
|
|
42
|
+
{ value: this.core },
|
|
43
|
+
this.props.fallback
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return React.createElement(
|
|
47
|
+
CrashSenseContext.Provider,
|
|
48
|
+
{ value: this.core },
|
|
49
|
+
this.props.children
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function useCrashSense() {
|
|
54
|
+
const core = useContext(CrashSenseContext);
|
|
55
|
+
return {
|
|
56
|
+
captureException: (error, context) => {
|
|
57
|
+
core?.captureException(error, context);
|
|
58
|
+
},
|
|
59
|
+
captureMessage: (message, severity) => {
|
|
60
|
+
core?.captureMessage(message, severity);
|
|
61
|
+
},
|
|
62
|
+
addBreadcrumb: (breadcrumb) => {
|
|
63
|
+
core?.addBreadcrumb(breadcrumb);
|
|
64
|
+
},
|
|
65
|
+
core
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
var DEFAULT_THRESHOLD = 50;
|
|
69
|
+
var WINDOW_MS = 1e3;
|
|
70
|
+
function useRenderTracker(componentName, threshold = DEFAULT_THRESHOLD) {
|
|
71
|
+
const renderTimestamps = useRef([]);
|
|
72
|
+
const warned = useRef(false);
|
|
73
|
+
const { captureMessage } = useCrashSense();
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
renderTimestamps.current.push(now);
|
|
76
|
+
const cutoff = now - WINDOW_MS;
|
|
77
|
+
renderTimestamps.current = renderTimestamps.current.filter((t) => t > cutoff);
|
|
78
|
+
if (renderTimestamps.current.length > threshold && !warned.current) {
|
|
79
|
+
warned.current = true;
|
|
80
|
+
captureMessage(
|
|
81
|
+
`Potential infinite re-render detected in ${componentName}: ${renderTimestamps.current.length} renders in ${WINDOW_MS}ms`,
|
|
82
|
+
"warning"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
return () => {
|
|
87
|
+
renderTimestamps.current = [];
|
|
88
|
+
warned.current = false;
|
|
89
|
+
};
|
|
90
|
+
}, []);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/hydration-detector.ts
|
|
94
|
+
function createHydrationDetector(core) {
|
|
95
|
+
let originalConsoleError = null;
|
|
96
|
+
return {
|
|
97
|
+
install() {
|
|
98
|
+
if (typeof console === "undefined") return;
|
|
99
|
+
originalConsoleError = console.error;
|
|
100
|
+
console.error = (...args) => {
|
|
101
|
+
originalConsoleError.apply(console, args);
|
|
102
|
+
const message = args.map(String).join(" ");
|
|
103
|
+
if (/hydrat/i.test(message)) {
|
|
104
|
+
core.captureException(
|
|
105
|
+
new Error(`Hydration mismatch: ${message.slice(0, 200)}`),
|
|
106
|
+
{
|
|
107
|
+
source: "hydration_detector",
|
|
108
|
+
framework: "react",
|
|
109
|
+
lifecycleStage: "hydrating"
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
uninstall() {
|
|
116
|
+
if (originalConsoleError) {
|
|
117
|
+
console.error = originalConsoleError;
|
|
118
|
+
originalConsoleError = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { CrashSenseContext, CrashSenseProvider, createHydrationDetector, useCrashSense, useRenderTracker };
|
|
125
|
+
//# sourceMappingURL=index.mjs.map
|
|
126
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider.ts","../src/hooks.ts","../src/render-tracker.ts","../src/hydration-detector.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import React from 'react';\nimport type { CrashSenseConfig, CrashSenseCore, CrashReport } from '@crashsense/types';\nimport { createCrashSense } from '@crashsense/core';\n\nexport const CrashSenseContext = React.createContext<CrashSenseCore | null>(null);\n\ninterface CrashSenseProviderProps {\n config: CrashSenseConfig;\n children: React.ReactNode;\n fallback?: React.ReactNode;\n onCrash?: (report: CrashReport) => void;\n}\n\ninterface CrashSenseProviderState {\n hasError: boolean;\n}\n\nexport class CrashSenseProvider extends React.Component<\n CrashSenseProviderProps,\n CrashSenseProviderState\n> {\n private core: CrashSenseCore | null = null;\n\n constructor(props: CrashSenseProviderProps) {\n super(props);\n this.state = { hasError: false };\n }\n\n static getDerivedStateFromError(_error: Error): CrashSenseProviderState {\n return { hasError: true };\n }\n\n componentDidMount(): void {\n const enrichedConfig: CrashSenseConfig = {\n ...this.props.config,\n onCrash: (report: CrashReport) => {\n this.props.onCrash?.(report);\n this.props.config.onCrash?.(report);\n },\n };\n\n this.core = createCrashSense(enrichedConfig);\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {\n if (!this.core) return;\n\n this.core.captureException(error, {\n componentStack: errorInfo.componentStack ?? '',\n framework: 'react',\n lifecycleStage: 'rendering',\n });\n }\n\n componentWillUnmount(): void {\n this.core?.destroy();\n this.core = null;\n }\n\n render(): React.ReactNode {\n if (this.state.hasError && this.props.fallback !== undefined) {\n return React.createElement(\n CrashSenseContext.Provider,\n { value: this.core },\n this.props.fallback,\n );\n }\n\n return React.createElement(\n CrashSenseContext.Provider,\n { value: this.core },\n this.props.children,\n );\n }\n}\n","import { useContext } from 'react';\nimport type { CrashSenseCore, Breadcrumb, CrashSeverity } from '@crashsense/types';\nimport { CrashSenseContext } from './provider';\n\nexport function useCrashSense(): {\n captureException: (error: unknown, context?: Record<string, unknown>) => void;\n captureMessage: (message: string, severity?: CrashSeverity) => void;\n addBreadcrumb: (breadcrumb: Omit<Breadcrumb, 'timestamp'>) => void;\n core: CrashSenseCore | null;\n} {\n const core = useContext(CrashSenseContext);\n\n return {\n captureException: (error: unknown, context?: Record<string, unknown>) => {\n core?.captureException(error, context);\n },\n captureMessage: (message: string, severity?: CrashSeverity) => {\n core?.captureMessage(message, severity);\n },\n addBreadcrumb: (breadcrumb: Omit<Breadcrumb, 'timestamp'>) => {\n core?.addBreadcrumb(breadcrumb);\n },\n core,\n };\n}\n","import { useRef, useEffect } from 'react';\nimport { useCrashSense } from './hooks';\n\nconst DEFAULT_THRESHOLD = 50;\nconst WINDOW_MS = 1000;\n\nexport function useRenderTracker(\n componentName: string,\n threshold: number = DEFAULT_THRESHOLD,\n): void {\n const renderTimestamps = useRef<number[]>([]);\n const warned = useRef(false);\n const { captureMessage } = useCrashSense();\n\n const now = Date.now();\n renderTimestamps.current.push(now);\n\n const cutoff = now - WINDOW_MS;\n renderTimestamps.current = renderTimestamps.current.filter((t) => t > cutoff);\n\n if (renderTimestamps.current.length > threshold && !warned.current) {\n warned.current = true;\n captureMessage(\n `Potential infinite re-render detected in ${componentName}: ${renderTimestamps.current.length} renders in ${WINDOW_MS}ms`,\n 'warning',\n );\n }\n\n useEffect(() => {\n return () => {\n renderTimestamps.current = [];\n warned.current = false;\n };\n }, []);\n}\n","import type { CrashSenseCore } from '@crashsense/types';\n\nexport function createHydrationDetector(core: CrashSenseCore) {\n let originalConsoleError: typeof console.error | null = null;\n\n return {\n install(): void {\n if (typeof console === 'undefined') return;\n\n originalConsoleError = console.error;\n\n console.error = (...args: unknown[]) => {\n originalConsoleError!.apply(console, args);\n\n const message = args.map(String).join(' ');\n if (/hydrat/i.test(message)) {\n core.captureException(\n new Error(`Hydration mismatch: ${message.slice(0, 200)}`),\n {\n source: 'hydration_detector',\n framework: 'react',\n lifecycleStage: 'hydrating',\n },\n );\n }\n };\n },\n\n uninstall(): void {\n if (originalConsoleError) {\n console.error = originalConsoleError;\n originalConsoleError = null;\n }\n },\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crashsense/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CrashSense React adapter — ErrorBoundary, hydration detection, re-render tracking",
|
|
5
|
+
"author": "hoainho <nhoxtvt@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/hoainho/crashsense.git",
|
|
10
|
+
"directory": "packages/react"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/hoainho/crashsense/tree/main/packages/react",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/hoainho/crashsense/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"crashsense",
|
|
18
|
+
"react",
|
|
19
|
+
"error-boundary",
|
|
20
|
+
"crash-detection",
|
|
21
|
+
"hydration",
|
|
22
|
+
"render-tracking"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"module": "./dist/index.mjs",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.mjs",
|
|
34
|
+
"require": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"dev": "tsup --watch",
|
|
43
|
+
"test": "vitest run"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@crashsense/types": "*",
|
|
47
|
+
"@crashsense/core": "*"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"react": ">=16.8.0",
|
|
51
|
+
"react-dom": ">=16.8.0"
|
|
52
|
+
},
|
|
53
|
+
"sideEffects": false
|
|
54
|
+
}
|