Recording addEventListener spy

This commit is contained in:
Mark Tolmacs 2025-05-21 20:05:26 +02:00
parent 712f267519
commit 624500b091
No known key found for this signature in database
2 changed files with 104 additions and 6 deletions

View File

@ -32,7 +32,7 @@ import {
isDevEnv, isDevEnv,
} from "@excalidraw/common"; } from "@excalidraw/common";
import polyfill from "@excalidraw/excalidraw/polyfill"; import polyfill from "@excalidraw/excalidraw/polyfill";
import { useCallback, useEffect, useRef, useState } from "react"; import React, { useCallback, useEffect, useRef, useState } from "react";
import { loadFromBlob } from "@excalidraw/excalidraw/data/blob"; import { loadFromBlob } from "@excalidraw/excalidraw/data/blob";
import { useCallbackRefState } from "@excalidraw/excalidraw/hooks/useCallbackRefState"; import { useCallbackRefState } from "@excalidraw/excalidraw/hooks/useCallbackRefState";
import { t } from "@excalidraw/excalidraw/i18n"; import { t } from "@excalidraw/excalidraw/i18n";
@ -1147,6 +1147,100 @@ const ExcalidrawWrapper = () => {
); );
}; };
const ExcalidrawRecorderWrapper = ({ children }: React.PropsWithChildren) => {
const listenerRef = useRef<
WeakMap<
EventListenerOrEventListenerObject,
{ type: string; listener: EventListener }
>
>(new WeakMap());
const dataRef = useRef<object[]>([]);
React.useEffect(() => {
dataRef.current.push({
time: new Date().getTime(),
type: "start",
localStorage: JSON.parse(JSON.stringify(window.localStorage)),
dimensions: {
outerWidth: window.outerWidth,
outerHeight: window.outerHeight,
},
chromeVersion: window.navigator.userAgent
.split(" ")
.find((v) => v.startsWith("Chrome/"))
?.substring(7),
});
Window.prototype._removeEventListener =
Window.prototype.removeEventListener;
window.removeEventListener = function <K extends keyof WindowEventMap>(
type: K,
listener: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions,
) {
const existing = listenerRef.current.get(listener);
if (existing) {
window._removeEventListener(type, existing.listener, options);
listenerRef.current.delete(listener);
} else {
window._removeEventListener(type, listener, options);
}
};
Window.prototype._addEventListener = Window.prototype.addEventListener;
window.addEventListener = function <K extends keyof WindowEventMap>(
type: K,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
) {
const existing = listenerRef.current.get(listener);
let wrappedListener: EventListener;
if (!existing || existing?.type !== type) {
wrappedListener = function (...args) {
dataRef.current.push({
time: new Date().getTime(),
type: "event",
name: type,
args,
});
if (listener instanceof Function) {
listener.apply(window, args);
} else {
listener.handleEvent.apply(window, args);
}
};
} else {
wrappedListener = existing.listener;
window._removeEventListener(type, wrappedListener);
}
listenerRef.current.set(listener, { type, listener: wrappedListener });
window._addEventListener(type, wrappedListener, options);
};
return () => {
Window.prototype.removeEventListener =
Window.prototype._removeEventListener;
Window.prototype.addEventListener = Window.prototype._addEventListener;
dataRef.current = [];
window.gc?.();
};
}, []);
React.useEffect(() => {
const handle = setInterval(() => {
console.log(dataRef.current);
}, 10000);
return () => clearInterval(handle);
}, []);
return <>{children}</>;
};
const ExcalidrawApp = () => { const ExcalidrawApp = () => {
const isCloudExportWindow = const isCloudExportWindow =
window.location.pathname === "/excalidraw-plus-export"; window.location.pathname === "/excalidraw-plus-export";
@ -1155,11 +1249,13 @@ const ExcalidrawApp = () => {
} }
return ( return (
<TopErrorBoundary> <ExcalidrawRecorderWrapper>
<Provider store={appJotaiStore}> <TopErrorBoundary>
<ExcalidrawWrapper /> <Provider store={appJotaiStore}>
</Provider> <ExcalidrawWrapper />
</TopErrorBoundary> </Provider>
</TopErrorBoundary>
</ExcalidrawRecorderWrapper>
); );
}; };

View File

@ -11425,6 +11425,8 @@ declare global {
history: History; history: History;
store: Store; store: Store;
}; };
_addEventListener: typeof window.addEventListener;
_removeEventListener: typeof window.removeEventListener;
} }
} }