UNPKG

@carpetai/rrweb-recorder

Version:

A React component for recording user sessions using rrweb. Meant to be used with CarpetAI's Analytics Agent.

627 lines (610 loc) 21.6 kB
'use strict'; var require$$0 = require('react'); var rrweb = require('rrweb'); var jsxRuntime = {exports: {}}; var reactJsxRuntime_production = {}; /** * @license React * react-jsx-runtime.production.js * * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var hasRequiredReactJsxRuntime_production; function requireReactJsxRuntime_production () { if (hasRequiredReactJsxRuntime_production) return reactJsxRuntime_production; hasRequiredReactJsxRuntime_production = 1; var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"); function jsxProd(type, config, maybeKey) { var key = null; void 0 !== maybeKey && (key = "" + maybeKey); void 0 !== config.key && (key = "" + config.key); if ("key" in config) { maybeKey = {}; for (var propName in config) "key" !== propName && (maybeKey[propName] = config[propName]); } else maybeKey = config; config = maybeKey.ref; return { $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, ref: void 0 !== config ? config : null, props: maybeKey }; } reactJsxRuntime_production.Fragment = REACT_FRAGMENT_TYPE; reactJsxRuntime_production.jsx = jsxProd; reactJsxRuntime_production.jsxs = jsxProd; return reactJsxRuntime_production; } var reactJsxRuntime_development = {}; /** * @license React * react-jsx-runtime.development.js * * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var hasRequiredReactJsxRuntime_development; function requireReactJsxRuntime_development () { if (hasRequiredReactJsxRuntime_development) return reactJsxRuntime_development; hasRequiredReactJsxRuntime_development = 1; "production" !== process.env.NODE_ENV && (function () { function getComponentNameFromType(type) { if (null == type) return null; if ("function" === typeof type) return type.$$typeof === REACT_CLIENT_REFERENCE ? null : type.displayName || type.name || null; if ("string" === typeof type) return type; switch (type) { case REACT_FRAGMENT_TYPE: return "Fragment"; case REACT_PROFILER_TYPE: return "Profiler"; case REACT_STRICT_MODE_TYPE: return "StrictMode"; case REACT_SUSPENSE_TYPE: return "Suspense"; case REACT_SUSPENSE_LIST_TYPE: return "SuspenseList"; case REACT_ACTIVITY_TYPE: return "Activity"; } if ("object" === typeof type) switch ( ("number" === typeof type.tag && console.error( "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue." ), type.$$typeof) ) { case REACT_PORTAL_TYPE: return "Portal"; case REACT_CONTEXT_TYPE: return (type.displayName || "Context") + ".Provider"; case REACT_CONSUMER_TYPE: return (type._context.displayName || "Context") + ".Consumer"; case REACT_FORWARD_REF_TYPE: var innerType = type.render; type = type.displayName; type || ((type = innerType.displayName || innerType.name || ""), (type = "" !== type ? "ForwardRef(" + type + ")" : "ForwardRef")); return type; case REACT_MEMO_TYPE: return ( (innerType = type.displayName || null), null !== innerType ? innerType : getComponentNameFromType(type.type) || "Memo" ); case REACT_LAZY_TYPE: innerType = type._payload; type = type._init; try { return getComponentNameFromType(type(innerType)); } catch (x) {} } return null; } function testStringCoercion(value) { return "" + value; } function checkKeyStringCoercion(value) { try { testStringCoercion(value); var JSCompiler_inline_result = !1; } catch (e) { JSCompiler_inline_result = !0; } if (JSCompiler_inline_result) { JSCompiler_inline_result = console; var JSCompiler_temp_const = JSCompiler_inline_result.error; var JSCompiler_inline_result$jscomp$0 = ("function" === typeof Symbol && Symbol.toStringTag && value[Symbol.toStringTag]) || value.constructor.name || "Object"; JSCompiler_temp_const.call( JSCompiler_inline_result, "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.", JSCompiler_inline_result$jscomp$0 ); return testStringCoercion(value); } } function getTaskName(type) { if (type === REACT_FRAGMENT_TYPE) return "<>"; if ( "object" === typeof type && null !== type && type.$$typeof === REACT_LAZY_TYPE ) return "<...>"; try { var name = getComponentNameFromType(type); return name ? "<" + name + ">" : "<...>"; } catch (x) { return "<...>"; } } function getOwner() { var dispatcher = ReactSharedInternals.A; return null === dispatcher ? null : dispatcher.getOwner(); } function UnknownOwner() { return Error("react-stack-top-frame"); } function hasValidKey(config) { if (hasOwnProperty.call(config, "key")) { var getter = Object.getOwnPropertyDescriptor(config, "key").get; if (getter && getter.isReactWarning) return !1; } return void 0 !== config.key; } function defineKeyPropWarningGetter(props, displayName) { function warnAboutAccessingKey() { specialPropKeyWarningShown || ((specialPropKeyWarningShown = !0), console.error( "%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)", displayName )); } warnAboutAccessingKey.isReactWarning = !0; Object.defineProperty(props, "key", { get: warnAboutAccessingKey, configurable: !0 }); } function elementRefGetterWithDeprecationWarning() { var componentName = getComponentNameFromType(this.type); didWarnAboutElementRef[componentName] || ((didWarnAboutElementRef[componentName] = !0), console.error( "Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release." )); componentName = this.props.ref; return void 0 !== componentName ? componentName : null; } function ReactElement( type, key, self, source, owner, props, debugStack, debugTask ) { self = props.ref; type = { $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, props: props, _owner: owner }; null !== (void 0 !== self ? self : null) ? Object.defineProperty(type, "ref", { enumerable: !1, get: elementRefGetterWithDeprecationWarning }) : Object.defineProperty(type, "ref", { enumerable: !1, value: null }); type._store = {}; Object.defineProperty(type._store, "validated", { configurable: !1, enumerable: !1, writable: !0, value: 0 }); Object.defineProperty(type, "_debugInfo", { configurable: !1, enumerable: !1, writable: !0, value: null }); Object.defineProperty(type, "_debugStack", { configurable: !1, enumerable: !1, writable: !0, value: debugStack }); Object.defineProperty(type, "_debugTask", { configurable: !1, enumerable: !1, writable: !0, value: debugTask }); Object.freeze && (Object.freeze(type.props), Object.freeze(type)); return type; } function jsxDEVImpl( type, config, maybeKey, isStaticChildren, source, self, debugStack, debugTask ) { var children = config.children; if (void 0 !== children) if (isStaticChildren) if (isArrayImpl(children)) { for ( isStaticChildren = 0; isStaticChildren < children.length; isStaticChildren++ ) validateChildKeys(children[isStaticChildren]); Object.freeze && Object.freeze(children); } else console.error( "React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead." ); else validateChildKeys(children); if (hasOwnProperty.call(config, "key")) { children = getComponentNameFromType(type); var keys = Object.keys(config).filter(function (k) { return "key" !== k; }); isStaticChildren = 0 < keys.length ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}"; didWarnAboutKeySpread[children + isStaticChildren] || ((keys = 0 < keys.length ? "{" + keys.join(": ..., ") + ": ...}" : "{}"), console.error( 'A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />', isStaticChildren, children, keys, children ), (didWarnAboutKeySpread[children + isStaticChildren] = !0)); } children = null; void 0 !== maybeKey && (checkKeyStringCoercion(maybeKey), (children = "" + maybeKey)); hasValidKey(config) && (checkKeyStringCoercion(config.key), (children = "" + config.key)); if ("key" in config) { maybeKey = {}; for (var propName in config) "key" !== propName && (maybeKey[propName] = config[propName]); } else maybeKey = config; children && defineKeyPropWarningGetter( maybeKey, "function" === typeof type ? type.displayName || type.name || "Unknown" : type ); return ReactElement( type, children, self, source, getOwner(), maybeKey, debugStack, debugTask ); } function validateChildKeys(node) { "object" === typeof node && null !== node && node.$$typeof === REACT_ELEMENT_TYPE && node._store && (node._store.validated = 1); } var React = require$$0, REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"); var REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_ACTIVITY_TYPE = Symbol.for("react.activity"), REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, hasOwnProperty = Object.prototype.hasOwnProperty, isArrayImpl = Array.isArray, createTask = console.createTask ? console.createTask : function () { return null; }; React = { "react-stack-bottom-frame": function (callStackForError) { return callStackForError(); } }; var specialPropKeyWarningShown; var didWarnAboutElementRef = {}; var unknownOwnerDebugStack = React["react-stack-bottom-frame"].bind( React, UnknownOwner )(); var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner)); var didWarnAboutKeySpread = {}; reactJsxRuntime_development.Fragment = REACT_FRAGMENT_TYPE; reactJsxRuntime_development.jsx = function (type, config, maybeKey, source, self) { var trackActualOwner = 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++; return jsxDEVImpl( type, config, maybeKey, !1, source, self, trackActualOwner ? Error("react-stack-top-frame") : unknownOwnerDebugStack, trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask ); }; reactJsxRuntime_development.jsxs = function (type, config, maybeKey, source, self) { var trackActualOwner = 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++; return jsxDEVImpl( type, config, maybeKey, !0, source, self, trackActualOwner ? Error("react-stack-top-frame") : unknownOwnerDebugStack, trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask ); }; })(); return reactJsxRuntime_development; } if (process.env.NODE_ENV === 'production') { jsxRuntime.exports = requireReactJsxRuntime_production(); } else { jsxRuntime.exports = requireReactJsxRuntime_development(); } var jsxRuntimeExports = jsxRuntime.exports; const defaultSaveSessionData = async (sessionData, apiKey, apiUrl) => { const url = apiUrl || '/api/session-replay'; try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify(sessionData) }); if (!response.ok) { throw new Error(`Failed to save session data: ${response.statusText}`); } } catch (error) { throw error; } }; const getOrCreateSessionId = (providedSessionId) => { if (typeof window === 'undefined') return ''; if (providedSessionId) { return providedSessionId; } let sessionId = sessionStorage.getItem('rrweb_session_id'); if (!sessionId) { sessionId = crypto.randomUUID(); sessionStorage.setItem('rrweb_session_id', sessionId); } return sessionId; }; const shouldExcludePath = (excludePaths) => { if (!excludePaths || typeof window === 'undefined') return false; const currentUrl = window.location.href; return excludePaths.some(path => currentUrl.startsWith(path)); }; const createMetaEvent = () => { if (typeof window === 'undefined') return null; return { type: 4, timestamp: Date.now(), data: { href: window.location.href, url: window.location.href, width: window.innerWidth, height: window.innerHeight, title: document.title, userAgent: navigator.userAgent, viewport: { width: window.innerWidth, height: window.innerHeight } } }; }; function useSessionRecorder({ autoStart = true, apiKey, apiUrl = "https://apt-agent-api-c5f32b392702.herokuapp.com/api/sessions", maxSessionDuration = 30 * 60 * 1000, saveInterval = 5000, excludePaths = ['http://localhost', 'https://localhost', 'http://127.0.0.1', 'https://127.0.0.1'], recordCanvas = false, recordCrossOriginIframes = false, sessionId: customSessionId, onSessionStart, onSessionStop, onError }) { const [isRecording, setIsRecording] = require$$0.useState(false); const sessionIdRef = require$$0.useRef(null); const eventsRef = require$$0.useRef([]); const recorderRef = require$$0.useRef(undefined); const saveIntervalRef = require$$0.useRef(null); const sessionStartTimeRef = require$$0.useRef(0); const lastSavedIndexRef = require$$0.useRef(0); const emit = (event) => { eventsRef.current.push(event); }; const saveEvents = async () => { const currentSessionId = sessionIdRef.current; if (eventsRef.current.length === 0 || !currentSessionId) return; try { const newEvents = eventsRef.current.slice(lastSavedIndexRef.current); if (newEvents.length === 0) return; const sessionData = { sessionId: currentSessionId, events: newEvents, timestamp: Date.now(), url: window.location.href }; if (apiKey) { await defaultSaveSessionData(sessionData, apiKey, apiUrl); } lastSavedIndexRef.current = eventsRef.current.length; } catch (error) { onError === null || onError === void 0 ? void 0 : onError(error); } }; const addMetaEvent = () => { if (!isRecording || typeof window === 'undefined') return; const metaEvent = createMetaEvent(); if (metaEvent) { eventsRef.current.push(metaEvent); } }; const startRecording = () => { if (isRecording || typeof window === 'undefined') return; const currentSessionId = customSessionId || getOrCreateSessionId(); sessionIdRef.current = currentSessionId; sessionStartTimeRef.current = Date.now(); lastSavedIndexRef.current = 0; eventsRef.current = []; try { recorderRef.current = rrweb.record({ emit, recordCanvas, recordCrossOriginIframes }); setIsRecording(true); onSessionStart === null || onSessionStart === void 0 ? void 0 : onSessionStart(currentSessionId); addMetaEvent(); saveIntervalRef.current = setInterval(saveEvents, saveInterval); setTimeout(() => { if (isRecording) { stopRecording(); } }, maxSessionDuration); } catch (error) { onError === null || onError === void 0 ? void 0 : onError(error); } }; const stopRecording = () => { if (!isRecording) return; if (recorderRef.current) { recorderRef.current(); recorderRef.current = undefined; } if (saveIntervalRef.current) { clearInterval(saveIntervalRef.current); saveIntervalRef.current = null; } setIsRecording(false); onSessionStop === null || onSessionStop === void 0 ? void 0 : onSessionStop(sessionIdRef.current); saveEvents(); }; require$$0.useEffect(() => { if (autoStart && !shouldExcludePath(excludePaths)) { const timer = setTimeout(() => { startRecording(); }, 1000); return () => clearTimeout(timer); } }, [autoStart, excludePaths]); require$$0.useEffect(() => { if (typeof window === 'undefined') return; const handlePopState = () => { if (isRecording) { setTimeout(() => { addMetaEvent(); }, 100); } }; const handleHashChange = () => { if (isRecording) { setTimeout(() => { addMetaEvent(); }, 100); } }; window.addEventListener('popstate', handlePopState); window.addEventListener('hashchange', handleHashChange); return () => { window.removeEventListener('popstate', handlePopState); window.removeEventListener('hashchange', handleHashChange); }; }, [isRecording]); require$$0.useEffect(() => { return () => { if (isRecording) { stopRecording(); } }; }, []); return { isRecording, sessionId: sessionIdRef.current, startRecording, stopRecording }; } function SessionRecorderInner(props) { useSessionRecorder(props); return null; } function SessionRecorder(props) { return (jsxRuntimeExports.jsx(SessionRecorderInner, { ...props })); } exports.SessionRecorder = SessionRecorder; exports.useSessionRecorder = useSessionRecorder; //# sourceMappingURL=index.js.map