@codmir/sdk 0.1.1
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/README.md +206 -0
- package/dist/browser/index.cjs +409 -0
- package/dist/browser/index.d.cts +47 -0
- package/dist/browser/index.d.ts +47 -0
- package/dist/browser/index.js +186 -0
- package/dist/chunk-233XBWQD.js +43 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-T7OAAOG2.js +235 -0
- package/dist/chunk-X6Y5XEK5.js +255 -0
- package/dist/client.cjs +315 -0
- package/dist/client.d.cts +52 -0
- package/dist/client.d.ts +52 -0
- package/dist/client.js +10 -0
- package/dist/index-BlgYnCLd.d.cts +171 -0
- package/dist/index-BlgYnCLd.d.ts +171 -0
- package/dist/index.cjs +540 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +21 -0
- package/dist/nextjs/index.cjs +371 -0
- package/dist/nextjs/index.d.cts +77 -0
- package/dist/nextjs/index.d.ts +77 -0
- package/dist/nextjs/index.js +142 -0
- package/dist/overseer/index.cjs +250 -0
- package/dist/overseer/index.d.cts +1 -0
- package/dist/overseer/index.d.ts +1 -0
- package/dist/overseer/index.js +27 -0
- package/dist/react-native/index.cjs +356 -0
- package/dist/react-native/index.d.cts +95 -0
- package/dist/react-native/index.d.ts +95 -0
- package/dist/react-native/index.js +128 -0
- package/dist/replay/index.cjs +284 -0
- package/dist/replay/index.d.cts +206 -0
- package/dist/replay/index.d.ts +206 -0
- package/dist/replay/index.js +258 -0
- package/dist/types.cjs +69 -0
- package/dist/types.d.cts +281 -0
- package/dist/types.d.ts +281 -0
- package/dist/types.js +11 -0
- package/package.json +116 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { O as OverseerConfig } from '../index-BlgYnCLd.cjs';
|
|
2
|
+
export { B as Breadcrumb, S as SeverityLevel, U as UserContext, d as addBreadcrumb, c as captureException, a as captureMessage, e as close, f as flush, g as getClient, b as setTags, s as setUser } from '../index-BlgYnCLd.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @codmir/sdk/react-native - React Native Integration
|
|
6
|
+
*
|
|
7
|
+
* Error tracking and monitoring for React Native applications.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // App.tsx
|
|
12
|
+
* import * as Codmir from '@codmir/sdk/react-native';
|
|
13
|
+
*
|
|
14
|
+
* Codmir.init({
|
|
15
|
+
* dsn: 'https://your-project.codmir.com/api/overseer',
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* export default function App() {
|
|
19
|
+
* return (
|
|
20
|
+
* <Codmir.ErrorBoundary fallback={<ErrorScreen />}>
|
|
21
|
+
* <YourApp />
|
|
22
|
+
* </Codmir.ErrorBoundary>
|
|
23
|
+
* );
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
interface ReactNativeConfig extends OverseerConfig {
|
|
29
|
+
/** Automatically capture native crashes */
|
|
30
|
+
captureNativeCrashes?: boolean;
|
|
31
|
+
/** Automatically track screen views */
|
|
32
|
+
trackScreenViews?: boolean;
|
|
33
|
+
/** Automatically track app state changes */
|
|
34
|
+
trackAppState?: boolean;
|
|
35
|
+
/** Include device info in events */
|
|
36
|
+
includeDeviceInfo?: boolean;
|
|
37
|
+
}
|
|
38
|
+
interface DeviceInfo {
|
|
39
|
+
platform: "ios" | "android";
|
|
40
|
+
osVersion?: string;
|
|
41
|
+
deviceModel?: string;
|
|
42
|
+
appVersion?: string;
|
|
43
|
+
buildNumber?: string;
|
|
44
|
+
isEmulator?: boolean;
|
|
45
|
+
}
|
|
46
|
+
declare function init(config?: ReactNativeConfig): void;
|
|
47
|
+
/**
|
|
48
|
+
* Set device information (call this after getting device info from react-native-device-info)
|
|
49
|
+
*/
|
|
50
|
+
declare function setDeviceInfo(info: DeviceInfo): void;
|
|
51
|
+
/**
|
|
52
|
+
* Track screen view (call this from your navigation listener)
|
|
53
|
+
*/
|
|
54
|
+
declare function trackScreenView(screenName: string, params?: Record<string, unknown>): void;
|
|
55
|
+
/**
|
|
56
|
+
* Create a navigation listener for React Navigation
|
|
57
|
+
*/
|
|
58
|
+
declare function createNavigationListener(): (state: any) => void;
|
|
59
|
+
/**
|
|
60
|
+
* Track app state change (foreground/background)
|
|
61
|
+
*/
|
|
62
|
+
declare function trackAppStateChange(state: "active" | "background" | "inactive"): void;
|
|
63
|
+
/**
|
|
64
|
+
* Error Boundary props
|
|
65
|
+
*/
|
|
66
|
+
interface ErrorBoundaryProps {
|
|
67
|
+
children: React.ReactNode;
|
|
68
|
+
fallback: React.ReactNode | ((error: Error) => React.ReactNode);
|
|
69
|
+
onError?: (error: Error, componentStack: string) => void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Note: ErrorBoundary is a class component placeholder.
|
|
73
|
+
* In actual usage, import React and implement properly.
|
|
74
|
+
*/
|
|
75
|
+
declare function createErrorBoundary(React: any): {
|
|
76
|
+
new (props: ErrorBoundaryProps): {
|
|
77
|
+
[x: string]: any;
|
|
78
|
+
componentDidCatch(error: Error, errorInfo: {
|
|
79
|
+
componentStack: string;
|
|
80
|
+
}): void;
|
|
81
|
+
render(): any;
|
|
82
|
+
};
|
|
83
|
+
[x: string]: any;
|
|
84
|
+
getDerivedStateFromError(error: Error): {
|
|
85
|
+
hasError: boolean;
|
|
86
|
+
error: Error;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if Overseer is initialized
|
|
92
|
+
*/
|
|
93
|
+
declare function isOverseerInitialized(): boolean;
|
|
94
|
+
|
|
95
|
+
export { type DeviceInfo, type ErrorBoundaryProps, OverseerConfig, type ReactNativeConfig, createErrorBoundary, createNavigationListener, init, isOverseerInitialized, setDeviceInfo, trackAppStateChange, trackScreenView };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { O as OverseerConfig } from '../index-BlgYnCLd.js';
|
|
2
|
+
export { B as Breadcrumb, S as SeverityLevel, U as UserContext, d as addBreadcrumb, c as captureException, a as captureMessage, e as close, f as flush, g as getClient, b as setTags, s as setUser } from '../index-BlgYnCLd.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @codmir/sdk/react-native - React Native Integration
|
|
6
|
+
*
|
|
7
|
+
* Error tracking and monitoring for React Native applications.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // App.tsx
|
|
12
|
+
* import * as Codmir from '@codmir/sdk/react-native';
|
|
13
|
+
*
|
|
14
|
+
* Codmir.init({
|
|
15
|
+
* dsn: 'https://your-project.codmir.com/api/overseer',
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* export default function App() {
|
|
19
|
+
* return (
|
|
20
|
+
* <Codmir.ErrorBoundary fallback={<ErrorScreen />}>
|
|
21
|
+
* <YourApp />
|
|
22
|
+
* </Codmir.ErrorBoundary>
|
|
23
|
+
* );
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
interface ReactNativeConfig extends OverseerConfig {
|
|
29
|
+
/** Automatically capture native crashes */
|
|
30
|
+
captureNativeCrashes?: boolean;
|
|
31
|
+
/** Automatically track screen views */
|
|
32
|
+
trackScreenViews?: boolean;
|
|
33
|
+
/** Automatically track app state changes */
|
|
34
|
+
trackAppState?: boolean;
|
|
35
|
+
/** Include device info in events */
|
|
36
|
+
includeDeviceInfo?: boolean;
|
|
37
|
+
}
|
|
38
|
+
interface DeviceInfo {
|
|
39
|
+
platform: "ios" | "android";
|
|
40
|
+
osVersion?: string;
|
|
41
|
+
deviceModel?: string;
|
|
42
|
+
appVersion?: string;
|
|
43
|
+
buildNumber?: string;
|
|
44
|
+
isEmulator?: boolean;
|
|
45
|
+
}
|
|
46
|
+
declare function init(config?: ReactNativeConfig): void;
|
|
47
|
+
/**
|
|
48
|
+
* Set device information (call this after getting device info from react-native-device-info)
|
|
49
|
+
*/
|
|
50
|
+
declare function setDeviceInfo(info: DeviceInfo): void;
|
|
51
|
+
/**
|
|
52
|
+
* Track screen view (call this from your navigation listener)
|
|
53
|
+
*/
|
|
54
|
+
declare function trackScreenView(screenName: string, params?: Record<string, unknown>): void;
|
|
55
|
+
/**
|
|
56
|
+
* Create a navigation listener for React Navigation
|
|
57
|
+
*/
|
|
58
|
+
declare function createNavigationListener(): (state: any) => void;
|
|
59
|
+
/**
|
|
60
|
+
* Track app state change (foreground/background)
|
|
61
|
+
*/
|
|
62
|
+
declare function trackAppStateChange(state: "active" | "background" | "inactive"): void;
|
|
63
|
+
/**
|
|
64
|
+
* Error Boundary props
|
|
65
|
+
*/
|
|
66
|
+
interface ErrorBoundaryProps {
|
|
67
|
+
children: React.ReactNode;
|
|
68
|
+
fallback: React.ReactNode | ((error: Error) => React.ReactNode);
|
|
69
|
+
onError?: (error: Error, componentStack: string) => void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Note: ErrorBoundary is a class component placeholder.
|
|
73
|
+
* In actual usage, import React and implement properly.
|
|
74
|
+
*/
|
|
75
|
+
declare function createErrorBoundary(React: any): {
|
|
76
|
+
new (props: ErrorBoundaryProps): {
|
|
77
|
+
[x: string]: any;
|
|
78
|
+
componentDidCatch(error: Error, errorInfo: {
|
|
79
|
+
componentStack: string;
|
|
80
|
+
}): void;
|
|
81
|
+
render(): any;
|
|
82
|
+
};
|
|
83
|
+
[x: string]: any;
|
|
84
|
+
getDerivedStateFromError(error: Error): {
|
|
85
|
+
hasError: boolean;
|
|
86
|
+
error: Error;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if Overseer is initialized
|
|
92
|
+
*/
|
|
93
|
+
declare function isOverseerInitialized(): boolean;
|
|
94
|
+
|
|
95
|
+
export { type DeviceInfo, type ErrorBoundaryProps, OverseerConfig, type ReactNativeConfig, createErrorBoundary, createNavigationListener, init, isOverseerInitialized, setDeviceInfo, trackAppStateChange, trackScreenView };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addBreadcrumb,
|
|
3
|
+
captureException,
|
|
4
|
+
captureMessage,
|
|
5
|
+
close,
|
|
6
|
+
flush,
|
|
7
|
+
getClient,
|
|
8
|
+
init,
|
|
9
|
+
setTags,
|
|
10
|
+
setUser
|
|
11
|
+
} from "../chunk-T7OAAOG2.js";
|
|
12
|
+
import "../chunk-MLKGABMK.js";
|
|
13
|
+
|
|
14
|
+
// src/react-native/index.ts
|
|
15
|
+
var isInitialized = false;
|
|
16
|
+
var deviceInfo = null;
|
|
17
|
+
function init2(config = {}) {
|
|
18
|
+
if (isInitialized) return;
|
|
19
|
+
const {
|
|
20
|
+
captureNativeCrashes = true,
|
|
21
|
+
trackScreenViews = true,
|
|
22
|
+
trackAppState = true,
|
|
23
|
+
includeDeviceInfo = true,
|
|
24
|
+
...coreConfig
|
|
25
|
+
} = config;
|
|
26
|
+
init(coreConfig);
|
|
27
|
+
isInitialized = true;
|
|
28
|
+
const originalHandler = ErrorUtils.getGlobalHandler();
|
|
29
|
+
ErrorUtils.setGlobalHandler((error, isFatal) => {
|
|
30
|
+
captureException(error, {
|
|
31
|
+
handled: false,
|
|
32
|
+
mechanism: "global-error-handler",
|
|
33
|
+
fatal: isFatal,
|
|
34
|
+
device: deviceInfo
|
|
35
|
+
});
|
|
36
|
+
if (originalHandler) {
|
|
37
|
+
originalHandler(error, isFatal);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function setDeviceInfo(info) {
|
|
42
|
+
deviceInfo = info;
|
|
43
|
+
setTags({
|
|
44
|
+
"device.platform": info.platform,
|
|
45
|
+
"device.os_version": info.osVersion || "unknown",
|
|
46
|
+
"device.model": info.deviceModel || "unknown",
|
|
47
|
+
"app.version": info.appVersion || "unknown",
|
|
48
|
+
"app.build": info.buildNumber || "unknown"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function trackScreenView(screenName, params) {
|
|
52
|
+
addBreadcrumb({
|
|
53
|
+
category: "navigation",
|
|
54
|
+
message: `Screen: ${screenName}`,
|
|
55
|
+
data: params
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function createNavigationListener() {
|
|
59
|
+
return (state) => {
|
|
60
|
+
const currentRoute = getCurrentRouteName(state);
|
|
61
|
+
if (currentRoute) {
|
|
62
|
+
trackScreenView(currentRoute);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function getCurrentRouteName(state) {
|
|
67
|
+
if (!state) return null;
|
|
68
|
+
const route = state.routes[state.index];
|
|
69
|
+
if (route.state) {
|
|
70
|
+
return getCurrentRouteName(route.state);
|
|
71
|
+
}
|
|
72
|
+
return route.name;
|
|
73
|
+
}
|
|
74
|
+
function trackAppStateChange(state) {
|
|
75
|
+
addBreadcrumb({
|
|
76
|
+
category: "app.lifecycle",
|
|
77
|
+
message: `App state: ${state}`,
|
|
78
|
+
data: { state }
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function createErrorBoundary(React) {
|
|
82
|
+
return class ErrorBoundary extends React.Component {
|
|
83
|
+
constructor(props) {
|
|
84
|
+
super(props);
|
|
85
|
+
this.state = { hasError: false, error: null };
|
|
86
|
+
}
|
|
87
|
+
static getDerivedStateFromError(error) {
|
|
88
|
+
return { hasError: true, error };
|
|
89
|
+
}
|
|
90
|
+
componentDidCatch(error, errorInfo) {
|
|
91
|
+
captureException(error, {
|
|
92
|
+
mechanism: "react-error-boundary",
|
|
93
|
+
componentStack: errorInfo.componentStack
|
|
94
|
+
});
|
|
95
|
+
this.props.onError?.(error, errorInfo.componentStack);
|
|
96
|
+
}
|
|
97
|
+
render() {
|
|
98
|
+
if (this.state.hasError) {
|
|
99
|
+
const { fallback } = this.props;
|
|
100
|
+
if (typeof fallback === "function") {
|
|
101
|
+
return fallback(this.state.error);
|
|
102
|
+
}
|
|
103
|
+
return fallback;
|
|
104
|
+
}
|
|
105
|
+
return this.props.children;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function isOverseerInitialized() {
|
|
110
|
+
return isInitialized && getClient() !== null;
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
addBreadcrumb,
|
|
114
|
+
captureException,
|
|
115
|
+
captureMessage,
|
|
116
|
+
close,
|
|
117
|
+
createErrorBoundary,
|
|
118
|
+
createNavigationListener,
|
|
119
|
+
flush,
|
|
120
|
+
getClient,
|
|
121
|
+
init2 as init,
|
|
122
|
+
isOverseerInitialized,
|
|
123
|
+
setDeviceInfo,
|
|
124
|
+
setTags,
|
|
125
|
+
setUser,
|
|
126
|
+
trackAppStateChange,
|
|
127
|
+
trackScreenView
|
|
128
|
+
};
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/replay/index.ts
|
|
21
|
+
var replay_exports = {};
|
|
22
|
+
__export(replay_exports, {
|
|
23
|
+
ReplayPlayer: () => ReplayPlayer,
|
|
24
|
+
ReplayRecorder: () => ReplayRecorder,
|
|
25
|
+
createReplayPlayer: () => createReplayPlayer,
|
|
26
|
+
createReplayRecorder: () => createReplayRecorder
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(replay_exports);
|
|
29
|
+
var import_nanoid = require("nanoid");
|
|
30
|
+
var DEFAULT_PRIVACY = {
|
|
31
|
+
maskAllInputs: false,
|
|
32
|
+
maskInputTypes: ["password", "credit-card"],
|
|
33
|
+
maskSelectors: ["[data-private]", ".private"],
|
|
34
|
+
blockSelectors: ["[data-block-replay]"],
|
|
35
|
+
ignoreSelectors: []
|
|
36
|
+
};
|
|
37
|
+
var DEFAULT_CONFIG = {
|
|
38
|
+
sessionSampleRate: 0.1,
|
|
39
|
+
errorSampleRate: 1,
|
|
40
|
+
maxSessionDuration: 60 * 60 * 1e3,
|
|
41
|
+
segmentDuration: 5 * 60 * 1e3,
|
|
42
|
+
privacy: DEFAULT_PRIVACY
|
|
43
|
+
};
|
|
44
|
+
var ReplayRecorder = class {
|
|
45
|
+
config;
|
|
46
|
+
session = null;
|
|
47
|
+
currentSegment = null;
|
|
48
|
+
isRecording = false;
|
|
49
|
+
sessionStartTime = 0;
|
|
50
|
+
cleanupFns = [];
|
|
51
|
+
onSegmentReady;
|
|
52
|
+
onSessionEnd;
|
|
53
|
+
constructor(config = {}) {
|
|
54
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
55
|
+
}
|
|
56
|
+
start(options) {
|
|
57
|
+
if (this.isRecording) return this.session?.id || null;
|
|
58
|
+
if (typeof window === "undefined") return null;
|
|
59
|
+
if (Math.random() > this.config.sessionSampleRate) return null;
|
|
60
|
+
this.sessionStartTime = Date.now();
|
|
61
|
+
this.session = {
|
|
62
|
+
id: (0, import_nanoid.nanoid)(),
|
|
63
|
+
userId: options?.userId,
|
|
64
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
65
|
+
duration: 0,
|
|
66
|
+
url: window.location.href,
|
|
67
|
+
userAgent: navigator.userAgent,
|
|
68
|
+
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
69
|
+
events: [],
|
|
70
|
+
errorIds: [],
|
|
71
|
+
segments: [],
|
|
72
|
+
tags: options?.tags || {}
|
|
73
|
+
};
|
|
74
|
+
this.startNewSegment();
|
|
75
|
+
this.isRecording = true;
|
|
76
|
+
this.setupEventListeners();
|
|
77
|
+
this.captureSnapshot();
|
|
78
|
+
return this.session.id;
|
|
79
|
+
}
|
|
80
|
+
stop() {
|
|
81
|
+
if (!this.isRecording || !this.session) return null;
|
|
82
|
+
this.isRecording = false;
|
|
83
|
+
this.session.endedAt = /* @__PURE__ */ new Date();
|
|
84
|
+
this.session.duration = Date.now() - this.sessionStartTime;
|
|
85
|
+
if (this.currentSegment) {
|
|
86
|
+
this.currentSegment.endTime = this.session.duration;
|
|
87
|
+
this.session.segments.push(this.currentSegment);
|
|
88
|
+
this.onSegmentReady?.(this.currentSegment);
|
|
89
|
+
}
|
|
90
|
+
this.cleanupFns.forEach((fn) => fn());
|
|
91
|
+
this.cleanupFns = [];
|
|
92
|
+
let finalSession = this.session;
|
|
93
|
+
if (this.config.beforeSend) {
|
|
94
|
+
const processed = this.config.beforeSend(this.session);
|
|
95
|
+
if (!processed) {
|
|
96
|
+
this.session = null;
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
finalSession = processed;
|
|
100
|
+
}
|
|
101
|
+
this.onSessionEnd?.(finalSession);
|
|
102
|
+
const result = finalSession;
|
|
103
|
+
this.session = null;
|
|
104
|
+
this.currentSegment = null;
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
markError(errorId) {
|
|
108
|
+
this.session?.errorIds.push(errorId);
|
|
109
|
+
}
|
|
110
|
+
addCustomEvent(name, payload) {
|
|
111
|
+
this.addEvent({
|
|
112
|
+
type: "custom",
|
|
113
|
+
timestamp: this.getRelativeTime(),
|
|
114
|
+
data: { name, payload }
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
getSessionId() {
|
|
118
|
+
return this.session?.id || null;
|
|
119
|
+
}
|
|
120
|
+
isActive() {
|
|
121
|
+
return this.isRecording;
|
|
122
|
+
}
|
|
123
|
+
onSegment(handler) {
|
|
124
|
+
this.onSegmentReady = handler;
|
|
125
|
+
}
|
|
126
|
+
onSession(handler) {
|
|
127
|
+
this.onSessionEnd = handler;
|
|
128
|
+
}
|
|
129
|
+
getRelativeTime() {
|
|
130
|
+
return Date.now() - this.sessionStartTime;
|
|
131
|
+
}
|
|
132
|
+
addEvent(event) {
|
|
133
|
+
if (!this.isRecording || !this.currentSegment) return;
|
|
134
|
+
this.currentSegment.events.push(event);
|
|
135
|
+
if (event.timestamp - this.currentSegment.startTime >= this.config.segmentDuration) {
|
|
136
|
+
this.finalizeSegment();
|
|
137
|
+
this.startNewSegment();
|
|
138
|
+
}
|
|
139
|
+
if (event.timestamp >= this.config.maxSessionDuration) {
|
|
140
|
+
this.stop();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
startNewSegment() {
|
|
144
|
+
this.currentSegment = {
|
|
145
|
+
id: (0, import_nanoid.nanoid)(),
|
|
146
|
+
sessionId: this.session.id,
|
|
147
|
+
sequenceNumber: this.session.segments.length,
|
|
148
|
+
startTime: this.getRelativeTime(),
|
|
149
|
+
endTime: 0,
|
|
150
|
+
events: []
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
finalizeSegment() {
|
|
154
|
+
if (!this.currentSegment || !this.session) return;
|
|
155
|
+
this.currentSegment.endTime = this.getRelativeTime();
|
|
156
|
+
this.session.segments.push(this.currentSegment);
|
|
157
|
+
this.onSegmentReady?.(this.currentSegment);
|
|
158
|
+
}
|
|
159
|
+
captureSnapshot() {
|
|
160
|
+
if (typeof document === "undefined") return;
|
|
161
|
+
this.addEvent({
|
|
162
|
+
type: "dom_snapshot",
|
|
163
|
+
timestamp: this.getRelativeTime(),
|
|
164
|
+
data: {
|
|
165
|
+
html: document.documentElement.outerHTML,
|
|
166
|
+
baseUrl: document.baseURI,
|
|
167
|
+
doctype: document.doctype ? `<!DOCTYPE ${document.doctype.name}>` : void 0
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
setupEventListeners() {
|
|
172
|
+
let lastMouseMove = 0;
|
|
173
|
+
const onMouseMove = (e) => {
|
|
174
|
+
if (Date.now() - lastMouseMove < 50) return;
|
|
175
|
+
lastMouseMove = Date.now();
|
|
176
|
+
this.addEvent({
|
|
177
|
+
type: "mouse_move",
|
|
178
|
+
timestamp: this.getRelativeTime(),
|
|
179
|
+
data: { x: e.clientX, y: e.clientY }
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
window.addEventListener("mousemove", onMouseMove);
|
|
183
|
+
this.cleanupFns.push(() => window.removeEventListener("mousemove", onMouseMove));
|
|
184
|
+
const onClick = (e) => {
|
|
185
|
+
this.addEvent({
|
|
186
|
+
type: "mouse_click",
|
|
187
|
+
timestamp: this.getRelativeTime(),
|
|
188
|
+
data: { x: e.clientX, y: e.clientY, button: e.button }
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
window.addEventListener("click", onClick);
|
|
192
|
+
this.cleanupFns.push(() => window.removeEventListener("click", onClick));
|
|
193
|
+
let lastScroll = 0;
|
|
194
|
+
const onScroll = () => {
|
|
195
|
+
if (Date.now() - lastScroll < 100) return;
|
|
196
|
+
lastScroll = Date.now();
|
|
197
|
+
this.addEvent({
|
|
198
|
+
type: "scroll",
|
|
199
|
+
timestamp: this.getRelativeTime(),
|
|
200
|
+
data: { targetId: "window", x: window.scrollX, y: window.scrollY }
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
window.addEventListener("scroll", onScroll);
|
|
204
|
+
this.cleanupFns.push(() => window.removeEventListener("scroll", onScroll));
|
|
205
|
+
const onResize = () => {
|
|
206
|
+
this.addEvent({
|
|
207
|
+
type: "viewport",
|
|
208
|
+
timestamp: this.getRelativeTime(),
|
|
209
|
+
data: { width: window.innerWidth, height: window.innerHeight }
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
window.addEventListener("resize", onResize);
|
|
213
|
+
this.cleanupFns.push(() => window.removeEventListener("resize", onResize));
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
var ReplayPlayer = class {
|
|
217
|
+
config;
|
|
218
|
+
session = null;
|
|
219
|
+
state = "idle";
|
|
220
|
+
currentTime = 0;
|
|
221
|
+
animationFrameId = null;
|
|
222
|
+
constructor(config) {
|
|
223
|
+
this.config = {
|
|
224
|
+
speed: 1,
|
|
225
|
+
skipInactiveThreshold: 5e3,
|
|
226
|
+
showCursor: true,
|
|
227
|
+
showClickRipple: true,
|
|
228
|
+
highlightErrors: true,
|
|
229
|
+
...config
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
load(session) {
|
|
233
|
+
this.session = session;
|
|
234
|
+
this.currentTime = 0;
|
|
235
|
+
this.state = "idle";
|
|
236
|
+
}
|
|
237
|
+
play() {
|
|
238
|
+
if (!this.session) return;
|
|
239
|
+
this.state = "playing";
|
|
240
|
+
}
|
|
241
|
+
pause() {
|
|
242
|
+
this.state = "paused";
|
|
243
|
+
if (this.animationFrameId) {
|
|
244
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
stop() {
|
|
248
|
+
this.pause();
|
|
249
|
+
this.currentTime = 0;
|
|
250
|
+
this.state = "idle";
|
|
251
|
+
}
|
|
252
|
+
seek(time) {
|
|
253
|
+
if (!this.session) return;
|
|
254
|
+
this.currentTime = Math.max(0, Math.min(time, this.session.duration));
|
|
255
|
+
}
|
|
256
|
+
setSpeed(speed) {
|
|
257
|
+
this.config.speed = speed;
|
|
258
|
+
}
|
|
259
|
+
getState() {
|
|
260
|
+
return this.state;
|
|
261
|
+
}
|
|
262
|
+
getCurrentTime() {
|
|
263
|
+
return this.currentTime;
|
|
264
|
+
}
|
|
265
|
+
getDuration() {
|
|
266
|
+
return this.session?.duration || 0;
|
|
267
|
+
}
|
|
268
|
+
destroy() {
|
|
269
|
+
this.stop();
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
function createReplayRecorder(config) {
|
|
273
|
+
return new ReplayRecorder(config);
|
|
274
|
+
}
|
|
275
|
+
function createReplayPlayer(config) {
|
|
276
|
+
return new ReplayPlayer(config);
|
|
277
|
+
}
|
|
278
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
279
|
+
0 && (module.exports = {
|
|
280
|
+
ReplayPlayer,
|
|
281
|
+
ReplayRecorder,
|
|
282
|
+
createReplayPlayer,
|
|
283
|
+
createReplayRecorder
|
|
284
|
+
});
|