From f9828095e01aea0c2899792cf953adf86d52e48d Mon Sep 17 00:00:00 2001 From: RobPruzan Date: Fri, 27 Dec 2024 16:17:34 -0500 Subject: [PATCH 1/2] add monitor debugging scripts --- packages/scan/package.json | 1 + packages/scan/src/auto-monitor.ts | 36 +++++++++++++++++++ packages/scan/src/core/monitor/performance.ts | 2 ++ packages/scan/tsup.config.ts | 2 +- 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 packages/scan/src/auto-monitor.ts diff --git a/packages/scan/package.json b/packages/scan/package.json index 8cc4d24d..c9b5e2be 100644 --- a/packages/scan/package.json +++ b/packages/scan/package.json @@ -196,6 +196,7 @@ "build": "npm run build:css && NODE_ENV=production tsup", "postbuild": "pnpm copy-astro && node ../../scripts/version-warning.mjs", "build:copy": "NODE_ENV=production tsup && cat dist/auto.global.js | pbcopy", + "build:copy-monitor": "NODE_ENV=production tsup && cat dist/auto-monitor.global.js | pbcopy", "copy-astro": "cp -R src/core/monitor/params/astro dist/core/monitor/params", "dev:css": "npx tailwindcss -i ./src/core/web/assets/css/styles.tailwind.css -o ./src/core/web/assets/css/styles.css --watch", "dev:tsup": "NODE_ENV=development tsup --watch", diff --git a/packages/scan/src/auto-monitor.ts b/packages/scan/src/auto-monitor.ts new file mode 100644 index 00000000..f143caa6 --- /dev/null +++ b/packages/scan/src/auto-monitor.ts @@ -0,0 +1,36 @@ +import 'bippy'; // implicit init RDT hook +import { Store } from 'src'; +import { scanMonitoring } from 'src/core/monitor'; +import { initPerformanceMonitoring } from 'src/core/monitor/performance'; +import { Device } from 'src/core/monitor/types'; + +if (typeof window !== 'undefined') { + Store.monitor.value ??= { + pendingRequests: 0, + interactions: [], + session: new Promise((res) => + res({ + agent: 'mock', + branch: 'mock', + commit: 'mock', + cpu: -1, + device: Device.DESKTOP, + gpu: null, + id: 'mock', + mem: -1, + route: 'mock', + url: 'mock', + wifi: 'mock', + }), + ), + url: 'https://mock.com', + apiKey: '', + route: '', + commit: '', + branch: '', + }; + scanMonitoring({ + enabled: true, + }); + initPerformanceMonitoring(); +} diff --git a/packages/scan/src/core/monitor/performance.ts b/packages/scan/src/core/monitor/performance.ts index 5bec91b8..879e233d 100644 --- a/packages/scan/src/core/monitor/performance.ts +++ b/packages/scan/src/core/monitor/performance.ts @@ -293,6 +293,8 @@ const setupPerformanceListener = ( }; longestInteractionMap.set(interaction.id, interaction); + console.log('interaction', interaction); + onEntry(interaction); } }; diff --git a/packages/scan/tsup.config.ts b/packages/scan/tsup.config.ts index cdd93641..9cffa0df 100644 --- a/packages/scan/tsup.config.ts +++ b/packages/scan/tsup.config.ts @@ -85,7 +85,7 @@ void (async () => { export default defineConfig([ { - entry: ['./src/auto.ts', './src/install-hook.ts'], + entry: ['./src/auto.ts', './src/install-hook.ts', './src/auto-monitor.ts'], outDir: DIST_PATH, banner: { js: banner, From fd0b84ef0b2a108faabda50fd0d95ee8da21d538 Mon Sep 17 00:00:00 2001 From: RobPruzan Date: Tue, 21 Jan 2025 23:10:09 -0500 Subject: [PATCH 2/2] feat(scan): replay collection runtime collect detailed browser timing on interaction implement react scan rrweb replayer plugin refactor monitoring interaction and component collection --- packages/scan/package.json | 46 +- packages/scan/src/auto-monitor.ts | 11 +- packages/scan/src/auto.ts | 4 +- packages/scan/src/core/index.ts | 33 +- packages/scan/src/core/instrumentation.ts | 47 +- packages/scan/src/core/monitor/index.ts | 42 +- .../src/core/monitor/interaction-store.ts | 36 + .../src/core/monitor/monkey-patch-iife.js | 79 + packages/scan/src/core/monitor/network.ts | 304 ++-- .../src/core/monitor/performance-store.ts | 115 ++ .../src/core/monitor/performance-utils.ts | 103 ++ packages/scan/src/core/monitor/performance.ts | 798 +++++++++- .../src/core/monitor/session-replay/record.ts | 1046 +++++++++++++ .../core/monitor/session-replay/replay-v2.ts | 1329 +++++++++++++++++ packages/scan/src/core/monitor/types-v2.ts | 158 ++ packages/scan/src/core/monitor/types.ts | 112 +- packages/scan/src/core/web/overlay.ts | 31 +- .../scan/src/core/web/utils/outline-worker.ts | 2 +- packages/scan/src/core/web/utils/outline.ts | 12 +- packages/scan/src/index.ts | 2 + packages/scan/test-video.mp4 | Bin 0 -> 227081 bytes packages/scan/tsup.config.ts | 14 +- packages/website/app/api/waitlist/route.ts | 26 +- packages/website/app/globals.css | 108 ++ packages/website/app/layout.tsx | 32 +- packages/website/components/header.tsx | 147 +- pnpm-lock.yaml | 436 +++++- 27 files changed, 4686 insertions(+), 387 deletions(-) create mode 100644 packages/scan/src/core/monitor/interaction-store.ts create mode 100644 packages/scan/src/core/monitor/monkey-patch-iife.js create mode 100644 packages/scan/src/core/monitor/performance-store.ts create mode 100644 packages/scan/src/core/monitor/performance-utils.ts create mode 100644 packages/scan/src/core/monitor/session-replay/record.ts create mode 100644 packages/scan/src/core/monitor/session-replay/replay-v2.ts create mode 100644 packages/scan/src/core/monitor/types-v2.ts create mode 100644 packages/scan/test-video.mp4 diff --git a/packages/scan/package.json b/packages/scan/package.json index c9b5e2be..0c515331 100644 --- a/packages/scan/package.json +++ b/packages/scan/package.json @@ -1,8 +1,14 @@ { "name": "react-scan", - "version": "0.0.54", + "version": "0.0.1083", "description": "Scan your React app for renders", - "keywords": ["react", "react-scan", "react scan", "render", "performance"], + "keywords": [ + "react", + "react-scan", + "react scan", + "render", + "performance" + ], "homepage": "https://react-scan.million.dev", "bugs": { "url": "https://github.com/aidenybai/react-scan/issues" @@ -161,17 +167,27 @@ "types": "dist/index.d.ts", "typesVersions": { "*": { - "monitoring": ["./dist/core/monitor/index.d.ts"], - "monitoring/next": ["./dist/core/monitor/params/next.d.ts"], + "monitoring": [ + "./dist/core/monitor/index.d.ts" + ], + "monitoring/next": [ + "./dist/core/monitor/params/next.d.ts" + ], "monitoring/react-router-legacy": [ "./dist/core/monitor/params/react-router-v5.d.ts" ], "monitoring/react-router": [ "./dist/core/monitor/params/react-router-v6.d.ts" ], - "monitoring/remix": ["./dist/core/monitor/params/remix.d.ts"], - "monitoring/astro": ["./dist/core/monitor/params/astro/index.ts"], - "react-component-name/vite": ["./dist/react-component-name/vite.d.ts"], + "monitoring/remix": [ + "./dist/core/monitor/params/remix.d.ts" + ], + "monitoring/astro": [ + "./dist/core/monitor/params/astro/index.ts" + ], + "react-component-name/vite": [ + "./dist/react-component-name/vite.d.ts" + ], "react-component-name/webpack": [ "./dist/react-component-name/webpack.d.ts" ], @@ -187,11 +203,20 @@ "react-component-name/rollup": [ "./dist/react-component-name/rollup.d.ts" ], - "react-component-name/astro": ["./dist/react-component-name/astro.d.ts"] + "react-component-name/astro": [ + "./dist/react-component-name/astro.d.ts" + ] } }, "bin": "bin/cli.js", - "files": ["dist", "bin", "package.json", "README.md", "LICENSE", "auto.d.ts"], + "files": [ + "dist", + "bin", + "package.json", + "README.md", + "LICENSE", + "auto.d.ts" + ], "scripts": { "build": "npm run build:css && NODE_ENV=production tsup", "postbuild": "pnpm copy-astro && node ../../scripts/version-warning.mjs", @@ -217,6 +242,7 @@ "@clack/prompts": "^0.8.2", "@preact/signals": "^1.3.1", "@rollup/pluginutils": "^5.1.3", + "@rrweb/types": "2.0.0-alpha.18", "@types/node": "^20.17.9", "bippy": "^0.0.25", "esbuild": "^0.24.0", @@ -225,6 +251,8 @@ "mri": "^1.2.0", "playwright": "^1.49.0", "preact": "^10.25.1", + "rrweb": "2.0.0-alpha.4", + "rrweb-snapshot": "2.0.0-alpha.4", "tsx": "^4.0.0" }, "devDependencies": { diff --git a/packages/scan/src/auto-monitor.ts b/packages/scan/src/auto-monitor.ts index f143caa6..b563faa3 100644 --- a/packages/scan/src/auto-monitor.ts +++ b/packages/scan/src/auto-monitor.ts @@ -1,7 +1,7 @@ import 'bippy'; // implicit init RDT hook import { Store } from 'src'; import { scanMonitoring } from 'src/core/monitor'; -import { initPerformanceMonitoring } from 'src/core/monitor/performance'; +// import { initPerformanceMonitoring } from 'src/core/monitor/performance'; import { Device } from 'src/core/monitor/types'; if (typeof window !== 'undefined') { @@ -28,9 +28,10 @@ if (typeof window !== 'undefined') { route: '', commit: '', branch: '', + interactionListeningForRenders: null, }; - scanMonitoring({ - enabled: true, - }); - initPerformanceMonitoring(); + // scanMonitoring({ + // enabled: true, + // }); + // initPerformanceMonitoring(); } diff --git a/packages/scan/src/auto.ts b/packages/scan/src/auto.ts index 645a0bdc..90ac8a94 100644 --- a/packages/scan/src/auto.ts +++ b/packages/scan/src/auto.ts @@ -2,7 +2,9 @@ import 'bippy'; // implicit init RDT hook import { scan } from './index'; if (typeof window !== 'undefined') { - scan(); + scan({ + dangerouslyForceRunInProduction: true, + }); window.reactScan = scan; } diff --git a/packages/scan/src/core/index.ts b/packages/scan/src/core/index.ts index a7e73e7a..108c9a56 100644 --- a/packages/scan/src/core/index.ts +++ b/packages/scan/src/core/index.ts @@ -172,6 +172,9 @@ export type MonitoringOptions = Pick< interface Monitor { pendingRequests: number; interactions: Array; + interactionListeningForRenders: + | ((fiber: Fiber, renders: Array) => void) + | null; session: ReturnType; url: string | null; route: string | null; @@ -381,12 +384,14 @@ export const reportRender = (fiber: Fiber, renders: Array) => { // Get data from both current and alternate fibers const currentData = Store.reportData.get(reportFiber); - const alternateData = fiber.alternate ? Store.reportData.get(fiber.alternate) : null; + const alternateData = fiber.alternate + ? Store.reportData.get(fiber.alternate) + : null; // More efficient null checks and Math.max const existingCount = Math.max( (currentData && currentData.count) || 0, - (alternateData && alternateData.count) || 0 + (alternateData && alternateData.count) || 0, ); // Create single shared object for both fibers @@ -395,7 +400,7 @@ export const reportRender = (fiber: Fiber, renders: Array) => { time: selfTime || 0, renders, displayName, - type: getType(fiber.type) || null + type: getType(fiber.type) || null, }; // Store in both fibers @@ -461,7 +466,12 @@ const updateScheduledOutlines = (fiber: Fiber, renders: Array) => { for (let i = 0, len = renders.length; i < len; i++) { const render = renders[i]; const domFiber = getNearestHostFiber(fiber); - if (!domFiber || !domFiber.stateNode || !(domFiber.stateNode instanceof Element)) continue; + if ( + !domFiber || + !domFiber.stateNode || + !(domFiber.stateNode instanceof Element) + ) + continue; if (ReactScanInternals.scheduledOutlines.has(fiber)) { const existingOutline = ReactScanInternals.scheduledOutlines.get(fiber)!; @@ -512,6 +522,10 @@ export const getIsProduction = () => { return isProduction; }; +export const attachReplayCanvas = () => { + startFlushOutlineInterval(); +}; + export const start = () => { if (typeof window === 'undefined') return; @@ -540,6 +554,13 @@ export const start = () => { const instrumentation = createInstrumentation('devtools', { onActive() { + const rdtHook = getRDTHook(); + for (const renderer of rdtHook.renderers.values()) { + const buildType = detectReactBuildType(renderer); + if (buildType === 'production') { + isProduction = true; + } + } const existingRoot = document.querySelector('react-scan-root'); if (existingRoot) { return; @@ -556,7 +577,9 @@ export const start = () => { void audioContext.resume(); }; - window.addEventListener('pointerdown', createAudioContextOnInteraction, { once: true }); + window.addEventListener('pointerdown', createAudioContextOnInteraction, { + once: true, + }); const container = document.createElement('div'); container.id = 'react-scan-root'; diff --git a/packages/scan/src/core/instrumentation.ts b/packages/scan/src/core/instrumentation.ts index 0174e546..40d22b1a 100644 --- a/packages/scan/src/core/instrumentation.ts +++ b/packages/scan/src/core/instrumentation.ts @@ -23,15 +23,42 @@ let lastTime = performance.now(); let frameCount = 0; let initedFps = false; -const updateFPS = () => { +let fpsListeners: Array<(fps: number) => void> = []; + +export const listenToFps = (listener: (fps: number) => void) => { + // console.log('oushed', listener); + + fpsListeners.push(listener); + + return () => { + // console.log('unsub listener'); + + fpsListeners = fpsListeners.filter( + (currListener) => currListener !== listener, + ); + }; +}; + +const updateFPS = (onChange?: (fps: number) => void) => { frameCount++; const now = performance.now(); - if (now - lastTime >= 1000) { - fps = frameCount; + const timeSinceLastUpdate = now - lastTime; + + if (timeSinceLastUpdate >= 500) { + const calculatedFPS = Math.round((frameCount / timeSinceLastUpdate) * 1000); + + if (calculatedFPS !== fps) { + for (const listener of fpsListeners) { + listener(calculatedFPS); + } + } + + fps = calculatedFPS; frameCount = 0; lastTime = now; } - requestAnimationFrame(updateFPS); + + requestAnimationFrame(() => updateFPS(onChange)); }; export const getFPS = () => { @@ -361,30 +388,30 @@ export const createInstrumentation = ( const changes: Array = []; - const propsChanges = getChangedPropsDetailed(fiber).map(change => ({ + const propsChanges = getChangedPropsDetailed(fiber).map((change) => ({ type: 'props' as const, name: change.name, value: change.value, prevValue: change.prevValue, - unstable: false + unstable: false, })); - const stateChanges = getStateChanges(fiber).map(change => ({ + const stateChanges = getStateChanges(fiber).map((change) => ({ type: 'state' as const, name: change.name, value: change.value, prevValue: change.prevValue, count: change.count, - unstable: false + unstable: false, })); - const contextChanges = getContextChanges(fiber).map(change => ({ + const contextChanges = getContextChanges(fiber).map((change) => ({ type: 'context' as const, name: change.name, value: change.value, prevValue: change.prevValue, count: change.count, - unstable: false + unstable: false, })); changes.push(...propsChanges, ...stateChanges, ...contextChanges); diff --git a/packages/scan/src/core/monitor/index.ts b/packages/scan/src/core/monitor/index.ts index 1a42968d..faa4782e 100644 --- a/packages/scan/src/core/monitor/index.ts +++ b/packages/scan/src/core/monitor/index.ts @@ -10,10 +10,9 @@ import { } from '..'; import { createInstrumentation, type Render } from '../instrumentation'; import { updateFiberRenderData } from '../utils'; -import { initPerformanceMonitoring } from './performance'; import { getSession } from './utils'; import { flush } from './network'; -import { computeRoute } from './params/utils'; +import { scanWithRecord } from 'src/core/monitor/session-replay/record'; // max retries before the set of components do not get reported (avoid memory leaks of the set of fibers stored on the component aggregation) const MAX_RETRIES_BEFORE_COMPONENT_GC = 7; @@ -50,30 +49,23 @@ export const Monitoring = ({ }: MonitoringProps) => { if (!apiKey) throw new Error('Please provide a valid API key for React Scan monitoring'); - url ??= 'https://monitoring.react-scan.com/api/v1/ingest'; - + // url ??= "https://monitoring.react-scan.com/api/v1/ingest"; Store.monitor.value ??= { pendingRequests: 0, + url: 'http://localhost:4200/api/ingest', + apiKey, interactions: [], session: getSession({ commit, branch }).catch(() => null), - url, - apiKey, route, - commit, - branch, - }; + branch: 'main', + commit: '0x00000', - // When using Monitoring without framework, we need to compute the route from the path and params - if (!route && path && params) { - Store.monitor.value.route = computeRoute(path, params); - } else if (typeof window !== 'undefined') { - Store.monitor.value.route = - route ?? path ?? new URL(window.location.toString()).pathname; // this is inaccurate on vanilla react if the path is not provided but used for session route - } + interactionListeningForRenders: null, + }; useEffect(() => { + scanWithRecord(); scanMonitoring({ enabled: true }); - return initPerformanceMonitoring(); }, []); return null; @@ -101,6 +93,7 @@ export const startMonitoring = () => { flushInterval = setInterval(() => { try { + void flush(); } catch { /* */ @@ -126,6 +119,7 @@ export const startMonitoring = () => { if (isCompositeFiber(fiber)) { aggregateComponentRenderToInteraction(fiber, renders); } + publishToListeningInteraction(fiber, renders); ReactScanInternals.options.value.onRender?.(fiber, renders); }, onCommitFinish() { @@ -151,7 +145,7 @@ const aggregateComponentRenderToInteraction = ( const displayName = getDisplayName(fiber.type); if (!displayName) return; // TODO(nisarg): it may be useful to somehow report the first ancestor with a display name instead of completely ignoring - let component = lastInteraction.components.get(displayName); // TODO(nisarg): Same names are grouped together which is wrong. + let component = lastInteraction.components.get(displayName); // TODO(rob): we can be more precise with fiber types, but display name is fine for now if (!component) { component = { @@ -181,3 +175,15 @@ const aggregateComponentRenderToInteraction = ( component.selfTime += selfTime; } }; + +const publishToListeningInteraction = ( + fiber: Fiber, + renders: Array, +) => { + const monitor = Store.monitor.value; + if (!monitor || !monitor.interactionListeningForRenders) { + return; + } + + monitor.interactionListeningForRenders(fiber, renders); +}; diff --git a/packages/scan/src/core/monitor/interaction-store.ts b/packages/scan/src/core/monitor/interaction-store.ts new file mode 100644 index 00000000..06bd0a45 --- /dev/null +++ b/packages/scan/src/core/monitor/interaction-store.ts @@ -0,0 +1,36 @@ +import { CompletedInteraction } from 'src/core/monitor/performance'; +import { BoundedArray } from 'src/core/monitor/performance-utils'; + +type Subscriber = (data: T) => void; + +export class Store { + private subscribers: Set> = new Set(); + private currentValue: T; + + constructor(initialValue: T) { + this.currentValue = initialValue; + } + + subscribe(subscriber: Subscriber): () => void { + this.subscribers.add(subscriber); + + subscriber(this.currentValue); + + return () => { + this.subscribers.delete(subscriber); + }; + } + + setState(data: T) { + this.currentValue = data; + this.subscribers.forEach((subscriber) => subscriber(data)); + } + + getCurrentState(): T { + return this.currentValue; + } +} +export const MAX_INTERACTION_BATCH = 150 +export const interactionStore = new Store>( + new BoundedArray(MAX_INTERACTION_BATCH), +); diff --git a/packages/scan/src/core/monitor/monkey-patch-iife.js b/packages/scan/src/core/monitor/monkey-patch-iife.js new file mode 100644 index 00000000..04c8d533 --- /dev/null +++ b/packages/scan/src/core/monitor/monkey-patch-iife.js @@ -0,0 +1,79 @@ +(function () { + window.__layoutDebugLog = { + calls: [], + }; + + const LOG_EVERY_CALL = false; + + function recordCall(apiName) { + const now = performance.now(); + const stack = new Error().stack + .split('\n') + .slice(2) + .map((line) => line.trim()) + .join('\n'); + + const entry = { api: apiName, time: now, stack }; + window.__layoutDebugLog.calls.push(entry); + + if (LOG_EVERY_CALL) { + console.warn( + `[LayoutDebug] ${apiName} at ${now.toFixed(2)}ms\nStack:\n${stack}`, + ); + } + } + + function patchFunction(obj, funcName) { + const original = obj[funcName]; + if (typeof original !== 'function') return; + + Object.defineProperty(obj, funcName, { + value: function patchedFunction(...args) { + recordCall(`${funcName}()`); + return original.apply(this, args); + }, + writable: true, + configurable: true, + }); + } + + function patchGetter(obj, propName) { + const desc = Object.getOwnPropertyDescriptor(obj, propName); + if (!desc) return; + const originalGet = desc.get; + if (typeof originalGet !== 'function') return; + + const newDesc = { + ...desc, + get: function patchedGetter() { + recordCall(propName); + return originalGet.call(this); + }, + }; + Object.defineProperty(obj, propName, newDesc); + } + + patchFunction(Element.prototype, 'getBoundingClientRect'); + + patchGetter(Element.prototype, 'offsetWidth'); + patchGetter(Element.prototype, 'offsetHeight'); + patchGetter(Element.prototype, 'clientWidth'); + patchGetter(Element.prototype, 'clientHeight'); + patchGetter(Element.prototype, 'scrollWidth'); + patchGetter(Element.prototype, 'scrollHeight'); + + (function patchGetComputedStyle() { + const original = window.getComputedStyle; + if (typeof original === 'function') { + window.getComputedStyle = function (...args) { + recordCall('getComputedStyle()'); + return original.apply(this, args); + }; + } + })(); + + console.log( + '%c[LayoutDebug] Patched layout APIs. Interact and then inspect window.__layoutDebugLog.calls', + 'color: green;', + ); +})(); diff --git a/packages/scan/src/core/monitor/network.ts b/packages/scan/src/core/monitor/network.ts index 25e029fd..74162056 100644 --- a/packages/scan/src/core/monitor/network.ts +++ b/packages/scan/src/core/monitor/network.ts @@ -1,194 +1,183 @@ -import { Store } from '../..'; -import { GZIP_MIN_LEN, GZIP_MAX_LEN, MAX_PENDING_REQUESTS } from './constants'; -import { getSession } from './utils'; +import { Store } from "../.."; +import { GZIP_MIN_LEN, GZIP_MAX_LEN, MAX_PENDING_REQUESTS } from "./constants"; +import { getSession } from "./utils"; import type { Interaction, IngestRequest, InternalInteraction, Component, -} from './types'; + Session, +} from "./types"; +import { performanceEntryChannels } from "src/core/monitor/performance-store"; +import { + interactionStore, + MAX_INTERACTION_BATCH, +} from "src/core/monitor/interaction-store"; +import { BoundedArray } from "src/core/monitor/performance-utils"; +import { CompletedInteraction } from "./performance"; + +let afterFlushListeners: Array<() => void> = []; +export const addAfterFlushListener = ( + cb: () => void, + opts?: { once?: boolean } +) => { + afterFlushListeners.push(() => { + cb(); + if (opts?.once) { + afterFlushListeners = afterFlushListeners.filter( + (listener) => listener !== cb + ); + } + }); +}; + +export type InteractionWithArrayParents = { + detailedTiming: Omit< + CompletedInteraction["detailedTiming"], + "fiberRenders" + > & { + fiberRenders: { + [key: string]: { + renderCount: number; + parents: string[]; + selfTime: number; + }; + }; + }; + latency: number; + completedAt: number; + flushNeeded: boolean; +}; + +export const convertInteractionFiberRenderParents = ( + interaction: CompletedInteraction +): InteractionWithArrayParents => ({ + ...interaction, + detailedTiming: { + ...interaction.detailedTiming, + fiberRenders: Object.fromEntries( + Object.entries(interaction.detailedTiming.fiberRenders).map( + ([key, value]) => [ + key, + { + ...value, + parents: Array.from(value.parents), + }, + ] + ) + ), + }, +}); const INTERACTION_TIME_TILL_COMPLETED = 4000; -const truncate = (value: number, decimalPlaces = 4) => - Number(value.toFixed(decimalPlaces)); +// TODO: truncate floats for clickhouse +// const truncate = (value: number, decimalPlaces = 4) => +// Number(value.toFixed(decimalPlaces)); +let pendingInteractionUUIDS: Array = []; export const flush = async (): Promise => { const monitor = Store.monitor.value; if ( !monitor || - !navigator.onLine || + // // !navigator.onLine || !monitor.url || - !monitor.interactions.length + // // !monitor.interactions.length + !interactionStore.getCurrentState().length ) { return; } - const now = performance.now(); - // We might trigger flush before the interaction is completed, - // so we need to split them into pending and completed by an arbitrary time. - const pendingInteractions = new Array(); - const completedInteractions = new Array(); - - const interactions = monitor.interactions; - for (let i = 0; i < interactions.length; i++) { - const interaction = interactions[i]; - const timeSinceStart = now - interaction.performanceEntry.startTime; - // these interactions were retried enough and should be discarded to avoid mem leak - if (timeSinceStart > 30000) { - continue; - } else if (timeSinceStart <= INTERACTION_TIME_TILL_COMPLETED) { - pendingInteractions.push(interaction); - } else { - completedInteractions.push(interaction); - } - } - - // nothing to flush - if (!completedInteractions.length) return; - // idempotent const session = await getSession({ - commit: monitor.commit, - branch: monitor.branch, + commit: "mock", + branch: "mock", }).catch(() => null); - if (!session) return; - - const aggregatedComponents = new Array(); - const aggregatedInteractions = new Array(); - for (let i = 0; i < completedInteractions.length; i++) { - const interaction = completedInteractions[i]; - - // META INFORMATION IS FOR DEBUGGING THIS MUST BE REMOVED SOON - const { - duration, - entries, - id, - inputDelay, - latency, - presentationDelay, - processingDuration, - processingEnd, - processingStart, - referrer, - startTime, - timeOrigin, - timeSinceTabInactive, - timestamp, - type, - visibilityState, - } = interaction.performanceEntry; - aggregatedInteractions.push({ - id: i, - path: interaction.componentPath, - name: interaction.componentName, - time: truncate(duration), - timestamp, - type, - // fixme: we can aggregate around url|route|commit|branch better to compress payload - url: interaction.url, - route: interaction.route, - commit: interaction.commit, - branch: interaction.branch, - uniqueInteractionId: interaction.uniqueInteractionId, - meta: { - performanceEntry: { - id, - inputDelay: truncate(inputDelay), - latency: truncate(latency), - presentationDelay: truncate(presentationDelay), - processingDuration: truncate(processingDuration), - processingEnd, - processingStart, - referrer, - startTime, - timeOrigin, - timeSinceTabInactive, - visibilityState, - duration: truncate(duration), - entries: entries.map((entry) => { - const { - duration, - entryType, - interactionId, - name, - processingEnd, - processingStart, - startTime, - } = entry; - return { - duration: truncate(duration), - entryType, - interactionId, - name, - processingEnd, - processingStart, - startTime, - }; - }), - }, - }, - }); - - const components = Array.from(interaction.components.entries()); - for (let j = 0; j < components.length; j++) { - const [name, component] = components[j]; - aggregatedComponents.push({ - name, - instances: component.fibers.size, - interactionId: i, - renders: component.renders, - selfTime: - typeof component.selfTime === 'number' - ? truncate(component.selfTime) - : component.selfTime, - totalTime: - typeof component.totalTime === 'number' - ? truncate(component.totalTime) - : component.totalTime, - }); - } + if (!session) { + return; + } + + const completedInteractions = interactionStore + .getCurrentState() + .filter( + (interaction) => + !pendingInteractionUUIDS.includes( + interaction.detailedTiming.interactionUUID + ) && interaction.flushNeeded + ); + if (!completedInteractions.length) { + return; } - const payload: IngestRequest = { - interactions: aggregatedInteractions, - components: aggregatedComponents, - session: { - ...session, - url: window.location.toString(), - route: monitor.route, // this might be inaccurate but used to caculate which paths all the unique sessions are coming from without having to join on the interactions table (expensive) - }, + const payload: { + interactions: InteractionWithArrayParents[]; + session: Session; + } = { + interactions: completedInteractions.map( + convertInteractionFiberRenderParents + ), + session, }; monitor.pendingRequests++; - monitor.interactions = pendingInteractions; + + pendingInteractionUUIDS.push( + ...completedInteractions.map((interaction) => { + interaction.flushNeeded = false; + return interaction.detailedTiming.interactionUUID; + }) + ); try { transport(monitor.url, payload) .then(() => { + performanceEntryChannels.publish( + payload.interactions.map( + (interaction) => interaction.detailedTiming.interactionUUID + ), + "flushed-interactions" + ); monitor.pendingRequests--; - // there may still be renders associated with these interaction, so don't flush just yet + afterFlushListeners.forEach((cb) => { + cb(); + }); }) - .catch(async () => { + .catch(async (e) => { // we let the next interval handle retrying, instead of explicitly retrying - monitor.interactions = monitor.interactions.concat( - completedInteractions, + // monitor.interactions = monitor.interactions.concat( + // completedInteractions, + // ); + completedInteractions.forEach((interaction) => { + interaction.flushNeeded = true; + }); + interactionStore.setState( + BoundedArray.fromArray( + interactionStore.getCurrentState().concat(completedInteractions), + MAX_INTERACTION_BATCH + ) + ); + }) + .finally(() => { + pendingInteractionUUIDS = pendingInteractionUUIDS.filter( + (uuid) => + !completedInteractions.some( + (interaction) => + interaction.detailedTiming.interactionUUID === uuid + ) ); }); } catch { /* */ } - // Keep only recent interactions - monitor.interactions = pendingInteractions; }; -const CONTENT_TYPE = 'application/json'; -const supportsCompression = typeof CompressionStream === 'function'; +const CONTENT_TYPE = "application/json"; +const supportsCompression = typeof CompressionStream === "function"; export const compress = async (payload: string): Promise => { const stream = new Blob([payload], { type: CONTENT_TYPE }) .stream() - .pipeThrough(new CompressionStream('gzip')); + .pipeThrough(new CompressionStream("gzip")); return new Response(stream).arrayBuffer(); }; @@ -199,29 +188,29 @@ export const compress = async (payload: string): Promise => { */ export const transport = async ( url: string, - payload: IngestRequest, + payload: IngestRequest ): Promise<{ ok: boolean }> => { const fail = { ok: false }; const json = JSON.stringify(payload); // gzip may not be worth it for small payloads, // only use it if the payload is large enough - const shouldCompress = json.length > GZIP_MIN_LEN; + const shouldCompress = false; //TODO CHANGE THIS BACK ITS JUST TO MAKE DEBUGGING EASIER const body = shouldCompress && supportsCompression ? await compress(json) : json; if (!navigator.onLine) return fail; const headers: any = { - 'Content-Type': CONTENT_TYPE, - 'Content-Encoding': shouldCompress ? 'gzip' : undefined, - 'x-api-key': Store.monitor.value?.apiKey, + "Content-Type": CONTENT_TYPE, + "Content-Encoding": shouldCompress ? "gzip" : undefined, + "x-api-key": Store.monitor.value?.apiKey, }; - if (shouldCompress) url += '?z=1'; - const size = typeof body === 'string' ? body.length : body.byteLength; + if (shouldCompress) url += "?z=1"; + const size = typeof body === "string" ? body.length : body.byteLength; return fetch(url, { body, - method: 'POST', - referrerPolicy: 'origin', + method: "POST", + referrerPolicy: "origin", /** * Outgoing requests are usually cancelled when navigating to a different page, causing a "TypeError: Failed to * fetch" error and sending a "network_error" client-outcome - in Chrome, the request status shows "(cancelled)". @@ -243,8 +232,9 @@ export const transport = async ( keepalive: GZIP_MAX_LEN > size && MAX_PENDING_REQUESTS > (Store.monitor.value?.pendingRequests ?? 0), - priority: 'low', + priority: "low", // mode: 'no-cors', headers, + mode: "no-cors", // this fixes cors, but will need to actually fix correctly later }); }; diff --git a/packages/scan/src/core/monitor/performance-store.ts b/packages/scan/src/core/monitor/performance-store.ts new file mode 100644 index 00000000..727161fd --- /dev/null +++ b/packages/scan/src/core/monitor/performance-store.ts @@ -0,0 +1,115 @@ +import { BoundedArray } from 'src/core/monitor/performance-utils'; +import { + InternalInteraction, + PerformanceInteraction, +} from 'src/core/monitor/types'; + +// forgot why start time was here ngl +type Item = any; +type UnSubscribe = () => void; +type Callback = (item: Item) => void; +type Updater = (state: BoundedArray) => BoundedArray; +type ChanelName = string; + +type PerformanceEntryChannelsType = { + subscribe: (to: ChanelName, cb: Callback) => UnSubscribe; + publish: ( + item: Item, + to: ChanelName, + dropFirst: boolean, + createIfNoChannel: boolean, + ) => void; + channels: Record< + ChanelName, + { callbacks: BoundedArray; state: BoundedArray } + >; + getAvailableChannels: () => BoundedArray; + updateChannelState: ( + channel: ChanelName, + updater: Updater, + createIfNoChannel: boolean, + ) => void; +}; + +export const MAX_CHANNEL_SIZE = 50 +// a set of entities communicate to each other through channels +// the state in the channel is persisted until the receiving end consumes it +// multiple subscribes to the same channel will likely lead to unintended behavior if the subscribers are separate entities +class PerformanceEntryChannels implements PerformanceEntryChannelsType { + channels: PerformanceEntryChannelsType['channels'] = {}; + publish(item: Item, to: ChanelName, createIfNoChannel = true) { + const existingChannel = this.channels[to]; + if (!existingChannel) { + if (!createIfNoChannel) { + return; + } + this.channels[to] = { + callbacks: new BoundedArray(MAX_CHANNEL_SIZE), + state: new BoundedArray(MAX_CHANNEL_SIZE), + }; + this.channels[to].state.push(item); + return; + } + + existingChannel.state.push(item); + existingChannel.callbacks.forEach((cb) => cb(item)); + } + + getAvailableChannels() { + return BoundedArray.fromArray(Object.keys(this.channels), MAX_CHANNEL_SIZE); + } + subscribe(to: ChanelName, cb: Callback, dropFirst: boolean = false) { + const defer = () => { + if (!dropFirst) { + this.channels[to].state.forEach((item) => { + cb(item); + }); + } + return () => { + const filtered = this.channels[to].callbacks.filter( + (subscribed) => subscribed !== cb + ); + this.channels[to].callbacks = BoundedArray.fromArray(filtered, MAX_CHANNEL_SIZE); + }; + }; + const existing = this.channels[to]; + if (!existing) { + this.channels[to] = { + callbacks: new BoundedArray(MAX_CHANNEL_SIZE), + state: new BoundedArray(MAX_CHANNEL_SIZE), + }; + this.channels[to].callbacks.push(cb); + return defer(); + } + + existing.callbacks.push(cb); + return defer(); + } + updateChannelState( + channel: ChanelName, + updater: Updater, + createIfNoChannel = true, + ) { + const existingChannel = this.channels[channel]; + if (!existingChannel) { + if (!createIfNoChannel) { + return; + } + + const state = new BoundedArray(MAX_CHANNEL_SIZE) + const newChannel = { callbacks: new BoundedArray(MAX_CHANNEL_SIZE), state }; + + this.channels[channel] = newChannel; + newChannel.state = updater(state); + return; + } + + existingChannel.state = updater(existingChannel.state); + } + + getChannelState(channel: ChanelName) { + return this.channels[channel].state ?? new BoundedArray(MAX_CHANNEL_SIZE); + } +} + +export const performanceEntryChannels = new PerformanceEntryChannels(); diff --git a/packages/scan/src/core/monitor/performance-utils.ts b/packages/scan/src/core/monitor/performance-utils.ts new file mode 100644 index 00000000..37061502 --- /dev/null +++ b/packages/scan/src/core/monitor/performance-utils.ts @@ -0,0 +1,103 @@ +import { Fiber } from 'react-reconciler'; + +export const getChildrenFromFiberLL = (fiber: Fiber) => { + const children: Array = []; + + let curr: typeof fiber.child = fiber.child; + + while (curr) { + children.push(curr); + + curr = curr.sibling; + } + + return children; +}; + +type Node = Map< + Fiber, + { children: Array; parent: Fiber | null; isRoot: boolean } +>; + +export const createChildrenAdjacencyList = (root: Fiber) => { + const tree: Node = new Map([]); + + const queue: Array<[node: Fiber, parent: Fiber | null]> = []; + const visited = new Set(); + + queue.push([root, root.return]); + + while (queue.length) { + const [node, parent] = queue.pop()!; + const children = getChildrenFromFiberLL(node); + + tree.set(node, { + children: [], + parent, + isRoot: node === root, + }); + + for (const child of children) { + // this isn't needed since the fiber tree is a TREE, not a graph, but it makes me feel safer + if (visited.has(child)) { + continue; + } + visited.add(child); + tree.get(node)?.children.push(child); + queue.push([child, node]); + } + } + return tree; +}; + +const isProduction: boolean = process.env.NODE_ENV === 'production'; +const prefix: string = 'Invariant failed'; + +// FIX ME THIS IS PRODUCTION INVARIANT LOL +export function devInvariant( + condition: any, + message?: string | (() => string), +): asserts condition { + if (condition) { + return; + } + + if (isProduction) { + throw new Error(prefix); + } + + const provided: string | undefined = + typeof message === 'function' ? message() : message; + + const value: string = provided ? `${prefix}: ${provided}` : prefix; + throw new Error(value); +} + +// yes this is actually a production error, temporary since i test production builds +export const devError = (message: string | undefined) => { + if (isProduction) { + throw new Error(message); + } +}; + +export const iife = (fn: () => T): T => fn(); + +export class BoundedArray extends Array { + constructor(private capacity: number = 25) { + super(); + } + + push(...items: T[]): number { + const result = super.push(...items); + while (this.length > this.capacity) { + this.shift(); + } + return result; + } + + static fromArray(array: Array, capacity: number) { + const arr = new BoundedArray(capacity); + arr.push(...array); + return arr + } +} diff --git a/packages/scan/src/core/monitor/performance.ts b/packages/scan/src/core/monitor/performance.ts index 879e233d..26a10e0f 100644 --- a/packages/scan/src/core/monitor/performance.ts +++ b/packages/scan/src/core/monitor/performance.ts @@ -1,11 +1,27 @@ -import { getDisplayName } from 'bippy'; +import { getDisplayName, getTimings } from 'bippy'; import { type Fiber } from 'react-reconciler'; -import { Store } from '../..'; -import { getCompositeComponentFromElement } from '../web/inspect-element/utils'; +import { start, Store } from '../..'; +import { + getCompositeComponentFromElement, + getFiberFromElement, + getParentCompositeFiber, +} from '../web/inspect-element/utils'; import type { + InternalInteraction, PerformanceInteraction, PerformanceInteractionEntry, } from './types'; +import { + BoundedArray, + createChildrenAdjacencyList, + devError, + devInvariant, + iife, +} from 'src/core/monitor/performance-utils'; +import { + MAX_CHANNEL_SIZE, + performanceEntryChannels, +} from 'src/core/monitor/performance-store'; interface PathFilters { skipProviders: boolean; @@ -25,6 +41,7 @@ const DEFAULT_FILTERS: PathFilters = { skipBoundaries: true, }; + const FILTER_PATTERNS = { providers: [/Provider$/, /^Provider$/, /^Context$/], hocs: [/^with[A-Z]/, /^forward(?:Ref)?$/i, /^Forward(?:Ref)?\(/], @@ -93,23 +110,26 @@ export const getInteractionPath = ( fiber: Fiber | null, filters: PathFilters = DEFAULT_FILTERS, ): Array => { - if (!fiber) return []; - - const currentName = getDisplayName(fiber.type); - if (!currentName) return []; + if (!fiber) { + return []; + } const stack = new Array(); - while (fiber.return) { - const name = getCleanComponentName(fiber.type); + let currentFiber = fiber; + while (currentFiber.return) { + const name = getCleanComponentName(currentFiber.type); + if (name && !isMinified(name) && shouldIncludeInPath(name, filters)) { stack.push(name); } - fiber = fiber.return; + currentFiber = currentFiber.return; } + const fullPath = new Array(stack.length); for (let i = 0; i < stack.length; i++) { fullPath[i] = stack[stack.length - i - 1]; } + return fullPath; }; @@ -158,6 +178,20 @@ const getFirstNamedAncestorCompositeFiber = (element: Element) => { return parentCompositeFiber; }; +const getFirstNameFromAncestor = (fiber: Fiber) => { + let curr: Fiber | null = fiber; + + while (curr) { + const currName = getDisplayName(curr.type); + if (currName) { + return currName; + } + + curr = curr.return; + } + return null; +}; + let unsubscribeTrackVisibilityChange: (() => void) | undefined; // fixme: compress me if this stays here for bad interaction time checks let lastVisibilityHiddenAt: number | 'never-hidden' = 'never-hidden'; @@ -175,74 +209,143 @@ const trackVisibilityChange = () => { document.removeEventListener('visibilitychange', onVisibilityChange); }; }; +export type FiberRenders = Map< + Fiber, + { + renderCount: number; + // add self time/ total time later + } +>; + +type InteractionStartStage = { + kind: 'interaction-start'; + interactionType: 'pointer' | 'keyboard'; + rrwebId: number; + interactionUUID: string; + interactionStartDetail: number; + blockingTimeStart: number; + componentPath: Array; + componentName: string; + childrenTree: Record< + string, + { children: Array; firstNamedAncestor: string; isRoot: boolean } + >; + fiberRenders: Record< + string, + { + renderCount: number; + parents: Set; + selfTime: number; + } + >; + stopListeningForRenders: () => void; +}; -// todo: update monitoring api to expose filters for component names -export function initPerformanceMonitoring(options?: Partial) { - const filters = { ...DEFAULT_FILTERS, ...options }; - const monitor = Store.monitor.value; - if (!monitor) return; +type JSEndStage = Omit & { + kind: 'js-end-stage'; + jsEndDetail: number; +}; - document.addEventListener('mouseover', handleMouseover); - const disconnectPerformanceListener = setupPerformanceListener((entry) => { - const target = - entry.target ?? (entry.type === 'pointer' ? currentMouseOver : null); - if (!target) { - // most likely an invariant that we should log if its violated - return; - } - const parentCompositeFiber = getFirstNamedAncestorCompositeFiber(target); - if (!parentCompositeFiber) { - return; - } - const displayName = getDisplayName(parentCompositeFiber.type); - if (!displayName || isMinified(displayName)) { - // invariant, we know its named based on getFirstNamedAncestorCompositeFiber implementation - return; - } - const path = getInteractionPath(parentCompositeFiber, filters); - - monitor.interactions.push({ - componentName: displayName, - componentPath: path, - performanceEntry: entry, - components: new Map(), - url: window.location.toString(), - route: - Store.monitor.value?.route ?? new URL(window.location.href).pathname, - commit: Store.monitor.value?.commit ?? null, - branch: Store.monitor.value?.branch ?? null, - uniqueInteractionId: entry.id, - }); - }); +type RAFStage = Omit & { + kind: 'raf-stage'; + rafStart: number; +}; - return () => { - disconnectPerformanceListener(); - document.removeEventListener('mouseover', handleMouseover); - }; -} +export type TimeoutStage = Omit & { + kind: 'timeout-stage'; + commitEnd: number; + blockingTimeEnd: number; +}; + +export type CompletedInteraction = { + detailedTiming: TimeoutStage; + latency: number; + completedAt: number; + flushNeeded: boolean; +}; +type UnInitializedStage = { + kind: 'uninitialized-stage'; + interactionUUID: string; + interactionType: 'pointer' | 'keyboard'; +}; + +type CurrentInteraction = { + kind: 'pointer' | 'keyboard'; + interactionUUID: string; + pointerUpStart: number; + // needed for when inputs that can be clicked and trigger on change (like checkboxes) + clickChangeStart: number | null; + clickHandlerMicroTaskEnd: number | null; + rafStart: number | null; + commmitEnd: number | null; + timeorigin: number; + + // for now i don't trust performance now timing for UTC time... + blockingTimeStart: number; + blockingTimeEnd: number | null; + fiberRenders: Map< + string, + { + renderCount: number; + parents: Set; + selfTime: number; + } + >; + componentPath: Array; + componentName: string; + childrenTree: Record< + string, + { children: Array; firstNamedAncestor: string; isRoot: boolean } + >; +}; + +export let currentInteractions: Array = []; +export const fastHash = (str: string): string => { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; // Convert to 32-bit integer + } + return hash.toString(36); +}; const getInteractionType = ( eventName: string, ): 'pointer' | 'keyboard' | null => { - if (['pointerdown', 'pointerup', 'click'].includes(eventName)) { + // todo: track pointer down, but tends to not house expensive logic so not very high priority + if (['pointerup', 'click'].includes(eventName)) { return 'pointer'; } + if (eventName.includes('key')) { + // console.log('event', eventName); + } if (['keydown', 'keyup'].includes(eventName)) { return 'keyboard'; } return null; }; +export const getInteractionId = (interaction: any) => { + return `${interaction.performanceEntry.type}::${normalizePath(interaction.componentPath)}::${interaction.url}`; +}; +export function normalizePath(path: string[]): string { + const cleaned = path.filter(Boolean); + const deduped = cleaned.filter((name, i) => name !== cleaned[i - 1]); + + return deduped.join('.'); +} +let onEntryAnimationId: number | null = null; const setupPerformanceListener = ( onEntry: (interaction: PerformanceInteraction) => void, ) => { trackVisibilityChange(); - const longestInteractionMap = new Map(); + const interactionMap = new Map(); const interactionTargetMap = new Map(); const processInteractionEntry = (entry: PerformanceInteractionEntry) => { - if (!(entry.interactionId || entry.entryType === 'first-input')) return; + if (!entry.interactionId) return; if ( entry.interactionId && @@ -252,7 +355,7 @@ const setupPerformanceListener = ( interactionTargetMap.set(entry.interactionId, entry.target); } - const existingInteraction = longestInteractionMap.get(entry.interactionId); + const existingInteraction = interactionMap.get(entry.interactionId); if (existingInteraction) { if (entry.duration > existingInteraction.latency) { @@ -266,7 +369,9 @@ const setupPerformanceListener = ( } } else { const interactionType = getInteractionType(entry.name); - if (!interactionType) return; + if (!interactionType) { + return; + } const interaction: PerformanceInteraction = { id: entry.interactionId, @@ -275,6 +380,7 @@ const setupPerformanceListener = ( target: entry.target, type: interactionType, startTime: entry.startTime, + endTime: Date.now(), processingStart: entry.processingStart, processingEnd: entry.processingEnd, duration: entry.duration, @@ -291,11 +397,27 @@ const setupPerformanceListener = ( timeOrigin: performance.timeOrigin, referrer: document.referrer, }; - longestInteractionMap.set(interaction.id, interaction); - - console.log('interaction', interaction); - - onEntry(interaction); + // + interactionMap.set(interaction.id, interaction); + + + /** + * This seems odd, but it gives us determinism that we will receive an entry AFTER our detailed timing collection + * runs because browser semantics (raf(() => setTimeout) will always run before a doubleRaf) + * + * this also handles the case where multiple entries are dispatched for semantically the same interaction, + * they will get merged into a single interaction, where the largest latency is recorded, which is what + * we are interested in this application + */ + + if (!onEntryAnimationId) { + onEntryAnimationId = requestAnimationFrame(() => { + requestAnimationFrame(() => { + onEntry(interactionMap.get(interaction.id)!); + onEntryAnimationId = null; + }); + }); + } } }; @@ -323,3 +445,555 @@ const setupPerformanceListener = ( return () => po.disconnect(); }; + +export const setupPerformancePublisher = () => { + return setupPerformanceListener((entry) => { + + performanceEntryChannels.publish(entry, 'recording'); + }); +}; + +// we should actually only feed it the information it needs to complete so we can support safari +type Task = { + completeInteraction: (entry: PerformanceInteraction) => CompletedInteraction; + startDateTime: number; + endDateTime: number; + type: 'keyboard' | 'pointer'; +}; +export const MAX_INTERACTION_TASKS = 25; + +let tasks = new BoundedArray(MAX_INTERACTION_TASKS); + +const getAssociatedDetailedTimingInteraction = ( + entry: PerformanceInteraction, + activeTasks: Array, +) => { + let closestTask: Task | null = null; + for (const task of activeTasks) { + if (task.type !== entry.type) { + continue; + } + + if (closestTask === null) { + closestTask = task; + continue; + } + + const getAbsoluteDiff = (task: Task, entry: PerformanceInteraction) => + Math.abs(task.startDateTime) - (entry.startTime + entry.timeOrigin); + + if (getAbsoluteDiff(task, entry) < getAbsoluteDiff(closestTask, entry)) { + closestTask = task; + } + } + + return closestTask; +}; + +// this would be cool if it listened for merge, so it had to be after +export const listenForPerformanceEntryInteractions = ( + onComplete: (completedInteraction: CompletedInteraction) => void, +) => { + // we make the assumption that the detailed timing will be ready before the performance timing + const unsubscribe = performanceEntryChannels.subscribe( + 'recording', + (entry: PerformanceInteraction) => { + const associatedDetailedInteraction = + getAssociatedDetailedTimingInteraction(entry, tasks); + + // REMINDME: this likely means we clicked a non interactable thing but our handler still ran + // so we shouldn't treat this as an invariant, but instead use it to verify if we clicked + // something interactable + if (!associatedDetailedInteraction) { + console.log('dropped performance entry'); + + return; + } + // handles the case where we capture an event the browser doesn't + tasks = new BoundedArray(MAX_INTERACTION_TASKS); + performanceEntryChannels.updateChannelState( + 'recording', + (state: BoundedArray) => { + return BoundedArray.fromArray( + BoundedArray.fromArray( + // kill the previous detailed interactions not tracked by performance entry + state.filter( + (item) => + item.startTime + item.timeOrigin > + entry.startTime + entry.timeOrigin, + ), + MAX_CHANNEL_SIZE, + ), + MAX_CHANNEL_SIZE, + ); + }, + ); + const completedInteraction = + associatedDetailedInteraction.completeInteraction(entry); + onComplete(completedInteraction); + }, + ); + + return unsubscribe; +}; + + +type ShouldContinue = boolean; +const trackDetailedTiming = ({ + onMicroTask, + onRAF, + onTimeout, + abort, +}: { + onMicroTask: () => ShouldContinue; + onRAF: () => ShouldContinue; + onTimeout: () => void; + abort?: () => boolean; +}) => { + queueMicrotask(() => { + if (abort?.() === true) { + return; + } + if (!onMicroTask()) { + return; + } + requestAnimationFrame(() => { + if (abort?.() === true) { + return; + } + if (!onRAF()) { + return; + } + setTimeout(() => { + if (abort?.() === true) { + return; + } + onTimeout(); + }, 0); + }); + }); +}; + +const getTargetInteractionDetails = (target: Element) => { + const associatedFiber = getFiberFromElement(target); + if (!associatedFiber) { + return; + } + + // TODO: if element is minified, squash upwards till first non minified ancestor, and set name as ChildOf() + let componentName = associatedFiber + ? getDisplayName(associatedFiber?.type) + : 'N/A'; + + if (!componentName) { + componentName = getFirstNameFromAncestor(associatedFiber) ?? 'N/A'; + } + + if (!componentName) { + return; + } + + const componentPath = getInteractionPath(associatedFiber); + const childrenTree = collectFiberSubtree(associatedFiber); + + return { + componentPath, + childrenTree, + componentName, + }; +}; + +type LastInteractionRef = { + current: ( + | InteractionStartStage + | JSEndStage + | RAFStage + | TimeoutStage + | UnInitializedStage + ) & { stageStart: number }; +}; + +/** + * + * handles tracking event timings for arbitrarily overlapping handlers with cancel logic + */ +export const setupDetailedPointerTimingListener = ( + kind: 'pointer' | 'keyboard', + options: { + onStart?: (interactionUUID: string) => void; + onComplete?: ( + interactionUUID: string, + finalInteraction: { + detailedTiming: TimeoutStage; + latency: number; + completedAt: number; + flushNeeded: boolean; + }, + ) => void; + onError?: (interactionUUID: string) => void; + getNodeID: (node: Node) => number; + }, +) => { + let instrumentationIdInControl: string | null = null; + + const getEvent = ( + info: { phase: 'start' } | { phase: 'end'; target: Element }, + ) => { + switch (kind) { + case 'pointer': { + if (info.phase === 'start') { + return 'pointerup'; + } + if ( + info.target instanceof HTMLInputElement || + info.target instanceof HTMLSelectElement + ) { + return 'change'; + } + return 'click'; + } + case 'keyboard': { + if (info.phase === 'start') { + return 'keydown'; + } + + return 'change'; + } + } + }; + const [startEvent, endEvent] = + kind === 'pointer' ? ['pointerup', 'click'] : ['keydown', 'change']; + + console.log('[Performance] Setting up timing listener', { + kind, + startEvent, + endEvent, + instrumentationIdInControl, + }); + + + // this implementation does not allow for overlapping interactions + // getter/setter for debugging, doesn't do anything functional + const lastInteractionRef: LastInteractionRef = { + // @ts-expect-error + _current: { + kind: 'uninitialized-stage', + interactionUUID: crypto.randomUUID(), // the first interaction uses this + stageStart: Date.now(), + interactionType: kind, + }, + get current() { + // @ts-expect-error + return this._current; + }, + set current(value) { + // @ts-expect-error + this._current = value; + }, + }; + + const onInteractionStart = (e: { target: Element }) => { + + if (Date.now() - lastInteractionRef.current.stageStart > 2000) { + console.log('[Performance] Resetting stale state (>2s old)', { + staleDuration: Date.now() - lastInteractionRef.current.stageStart, + previousUUID: lastInteractionRef.current.interactionUUID, + instrumentationIdInControl, + fullState: lastInteractionRef.current, + }); + lastInteractionRef.current = { + kind: 'uninitialized-stage', + interactionUUID: crypto.randomUUID(), + stageStart: Date.now(), + interactionType: kind, + }; + } + + if (lastInteractionRef.current.kind !== 'uninitialized-stage') { + return; + } + + const pointerUpStart = performance.now(); + + options?.onStart?.(lastInteractionRef.current.interactionUUID); + const details = getTargetInteractionDetails(e.target as HTMLElement); + + if (!details) { + options?.onError?.(lastInteractionRef.current.interactionUUID); + return; + } + + const fiberRenders: InteractionStartStage['fiberRenders'] = {}; + const stopListeningForRenders = listenForRenders(fiberRenders); + lastInteractionRef.current = { + ...lastInteractionRef.current, + interactionType: kind, + blockingTimeStart: Date.now(), + childrenTree: details.childrenTree, + componentName: details.componentName, + componentPath: details.componentPath, + fiberRenders, + kind: 'interaction-start', + interactionStartDetail: pointerUpStart, + rrwebId: options.getNodeID(e.target), + stopListeningForRenders, + }; + + + const event = getEvent({ phase: 'end', target: e.target }); + document.addEventListener(event, onLastJS as any, { + once: true, + }); + + // this is an edge case where a click event is not fired after a pointerdown + // im not sure why this happens, but it seems to only happen on non intractable elements + // it causes the event handler to stay alive until a future interaction, which can break timing (looks super long) + // or invariants (the start metadata was removed, so now its an end metadata with no start) + requestAnimationFrame(() => { + document.removeEventListener(event as any, onLastJS as any); + }); + }; + + document.addEventListener( + getEvent({ phase: 'start' }), + onInteractionStart as any, + { + capture: true, + }, + ); + + const onLastJS = ( + e: { target: Element }, + instrumentationId: string, + abort: () => boolean, + ) => { + + if ( + lastInteractionRef.current.kind !== 'interaction-start' && + instrumentationId === instrumentationIdInControl + ) { + if (kind === 'pointer' && e.target instanceof HTMLSelectElement) { + lastInteractionRef.current = { + kind: 'uninitialized-stage', + interactionUUID: crypto.randomUUID(), + stageStart: Date.now(), + interactionType: kind, + }; + return; + } + + options?.onError?.(lastInteractionRef.current.interactionUUID); + lastInteractionRef.current = { + kind: 'uninitialized-stage', + interactionUUID: crypto.randomUUID(), + stageStart: Date.now(), + interactionType: kind, + }; + devError('pointer -> click'); + return; + } + + instrumentationIdInControl = instrumentationId; + + trackDetailedTiming({ + abort, + onMicroTask: () => { + + if (lastInteractionRef.current.kind === 'uninitialized-stage') { + return false; + } + + lastInteractionRef.current = { + ...lastInteractionRef.current, + kind: 'js-end-stage', + jsEndDetail: performance.now(), + }; + return true; + }, + onRAF: () => { + if ( + lastInteractionRef.current.kind !== 'js-end-stage' && + lastInteractionRef.current.kind !== 'raf-stage' + ) { + console.log('[Performance] Invalid state in RAF', { + currentStage: lastInteractionRef.current.kind, + expectedStage: 'js-end-stage', + interactionUUID: lastInteractionRef.current.interactionUUID, + instrumentationIdInControl, + fullState: lastInteractionRef.current, + }); + options?.onError?.(lastInteractionRef.current.interactionUUID); + devError('bad transition to raf'); + lastInteractionRef.current = { + kind: 'uninitialized-stage', + interactionUUID: crypto.randomUUID(), + stageStart: Date.now(), + interactionType: kind, + }; + return false; + } + + lastInteractionRef.current = { + ...lastInteractionRef.current, + kind: 'raf-stage', + rafStart: performance.now(), + }; + + return true; + }, + onTimeout: () => { + if (lastInteractionRef.current.kind !== 'raf-stage') { + console.log('[Performance] Invalid state in timeout', { + currentStage: lastInteractionRef.current.kind, + expectedStage: 'raf-stage', + interactionUUID: lastInteractionRef.current.interactionUUID, + instrumentationIdInControl, + fullState: lastInteractionRef.current, + }); + options?.onError?.(lastInteractionRef.current.interactionUUID); + lastInteractionRef.current = { + kind: 'uninitialized-stage', + interactionUUID: crypto.randomUUID(), + stageStart: Date.now(), + interactionType: kind, + }; + devError('raf->timeout'); + return; + } + const now = Date.now(); + const timeoutStage: TimeoutStage = Object.freeze({ + ...lastInteractionRef.current, + kind: 'timeout-stage', + blockingTimeEnd: now, + commitEnd: performance.now(), + }); + + lastInteractionRef.current = { + kind: 'uninitialized-stage', + interactionUUID: crypto.randomUUID(), + stageStart: now, + interactionType: kind, + }; + + + // this has to be the problem, i don't get how it's happening but timeOutStage is being mutated + tasks.push({ + completeInteraction: (entry) => { + console.log('[Performance] Completing interaction', { + interactionUUID: timeoutStage.interactionUUID, + latency: entry.latency, + completedAt: Date.now(), + instrumentationIdInControl, + fullState: timeoutStage, + }); + + const finalInteraction = { + detailedTiming: timeoutStage, + latency: entry.latency, + completedAt: Date.now(), + flushNeeded: true, + }; + options?.onComplete?.(timeoutStage.interactionUUID, finalInteraction); + + return finalInteraction; + }, + endDateTime: Date.now(), + startDateTime: timeoutStage.blockingTimeStart, + type: kind, + }); + }, + }); + }; + + // const onBase = (e: { target: Element }) => { + // const id = crypto.randomUUID(); + // onLastJS(e, id, () => id !== instrumentationIdInControl); + // }; + // i forgot why this was needed, i needed to remember and document it + // something about that event only sometimes firing, but it being later? I think? + const onKeyPress = (e: { target: Element }) => { + const id = crypto.randomUUID(); + onLastJS(e, id, () => id !== instrumentationIdInControl); + }; + + // document.addEventListener(getEvent({ phase: "start" }), onBase as any); + if (kind === 'keyboard') { + document.addEventListener('keypress', onKeyPress as any); + } + + return () => { + document.removeEventListener( + getEvent({ phase: 'start' }), + onInteractionStart as any, + { + capture: true, + }, + ); + // document.removeEventListener(getEvent({}), onLastJS as any); + document.removeEventListener('keypress', onKeyPress as any); + }; +}; + +const collectFiberSubtree = (fiber: Fiber) => { + const adjacencyList = createChildrenAdjacencyList(fiber).entries(); + const fiberToNames = Array.from(adjacencyList).map( + ([fiber, { children, parent, isRoot }]) => [ + getDisplayName(fiber.type) ?? 'N/A', + { + children: children.map((fiber) => getDisplayName(fiber.type) ?? 'N/A'), + firstNamedAncestor: parent + ? (getFirstNameFromAncestor(parent) ?? 'No Parent') + : 'No Parent', + isRoot, + }, + ], + ); + + return Object.fromEntries(fiberToNames); +}; + +const listenForRenders = ( + fiberRenders: InteractionStartStage['fiberRenders'], +) => { + const listener = (fiber: Fiber) => { + const displayName = getDisplayName(fiber.type); + if (!displayName) { + return; + } + const existing = fiberRenders[displayName]; + if (!existing) { + const parents = new Set(); + const parentCompositeName = getDisplayName( + getParentCompositeFiber(fiber), + ); + if (parentCompositeName) { + parents.add(parentCompositeName); + } + const { selfTime, totalTime } = getTimings(fiber); + fiberRenders[displayName] = { + renderCount: 1, + parents: parents, + selfTime, + }; + return; + } + const parentType = getParentCompositeFiber(fiber)?.[0]?.type; + if (parentType) { + const parentCompositeName = getDisplayName(parentType); + if (parentCompositeName) { + existing.parents.add(parentCompositeName); + } + } + const { selfTime } = getTimings(fiber); + existing.renderCount += 1; + existing.selfTime += selfTime; + }; + // todo: we need a general listener + Store.monitor.value!.interactionListeningForRenders = listener; + + return () => { + if (Store.monitor.value?.interactionListeningForRenders === listener) { + Store.monitor.value.interactionListeningForRenders = null; + } + }; +}; diff --git a/packages/scan/src/core/monitor/session-replay/record.ts b/packages/scan/src/core/monitor/session-replay/record.ts new file mode 100644 index 00000000..f14a2737 --- /dev/null +++ b/packages/scan/src/core/monitor/session-replay/record.ts @@ -0,0 +1,1046 @@ +import { eventWithTime } from "@rrweb/types"; +import { OutlineKey, ReactScanInternals } from "../.."; +import { Fiber } from "react-reconciler"; +import { createInstrumentation, listenToFps } from "../../instrumentation"; +import { getDisplayName, getNearestHostFiber, getTimings } from "bippy"; +import { onIdle } from "@web-utils/helpers"; +import { + CompletedInteraction, + listenForPerformanceEntryInteractions, + setupDetailedPointerTimingListener, + setupPerformancePublisher, + TimeoutStage, +} from "src/core/monitor/performance"; +import { + MAX_CHANNEL_SIZE, + performanceEntryChannels, +} from "src/core/monitor/performance-store"; +import { + BoundedArray, + devInvariant, + iife, +} from "src/core/monitor/performance-utils"; +import { PerformanceInteraction } from "src/core/monitor/types"; +import { + interactionStore, + MAX_INTERACTION_BATCH, +} from "src/core/monitor/interaction-store"; +import { + convertInteractionFiberRenderParents, + InteractionWithArrayParents, +} from "../network"; + +export let rrwebEvents: Array = []; +let timing: { startTime: number } = { + startTime: Date.now(), +}; + +const PLUGIN = 6; + +type BufferedScheduledOutline = { + renderCount: number; + selfTime: number; + fiber: Fiber; +}; + +const bufferedFiberRenderMap = new Map(); + +const addToBufferedFiberRenderMap = (fiber: Fiber) => { + const fiberId = getFiberId(fiber); + const existing = bufferedFiberRenderMap.get(fiberId); + const { selfTime } = getTimings(fiber); + + if (!existing) { + bufferedFiberRenderMap.set(fiberId, { + renderCount: 1, + selfTime: selfTime, + fiber, + }); + return; + } + + bufferedFiberRenderMap.set(fiberId, { + renderCount: existing.renderCount + 1, + selfTime: existing.selfTime + selfTime, + fiber, + }); +}; + +export const makeEvent = ( + payload: unknown, + plugin?: string +): eventWithTime => ({ + type: PLUGIN, + data: { + plugin: plugin ?? "react-scan-plugin", + payload, + }, + timestamp: Date.now(), +}); +type listenerHandler = () => void; +let recordingInstance: listenerHandler | undefined = undefined; +let record: (typeof import("rrweb"))["record"] | null = null; + +export const getEvents = () => rrwebEvents; + +const initRRWeb = async () => { + if (!record) { + const rrwebPkg = await import("rrweb"); + record = rrwebPkg.record; + } + return record; +}; + +export const startNewRecording = async () => { + console.log("new recording start"); + + lastRecordTime = Date.now(); + + const recordFn = await initRRWeb(); + + if (recordingInstance) { + recordingInstance(); + } + rrwebEvents.length = 0; + timing.startTime = Date.now(); + + recordingInstance = recordFn({ + emit: (event: eventWithTime) => { + rrwebEvents.push(event); + }, + }); +}; +type ReplayAggregatedRender = { + name: ComponentName; + frame: number | null; + computedKey: OutlineKey | null; + aggregatedCount: number; +}; + +type FiberId = number; + +type ComponentName = string; +export type ReactScanPayload = Array<{ + rrwebDomId: number; + renderCount: number; + fiberId: number; + componentName: string; + + selfTime: number; + + groupedAggregatedRender?: Map; + + /* Rects for interpolation */ + current?: DOMRect; + target?: DOMRect; + /* This value is computed before the full rendered text is shown, so its only considered an estimate */ + estimatedTextWidth?: number; // todo: estimated is stupid just make it the actual + + alpha?: number | null; + totalFrames?: number | null; +}>; + +let lastFiberId = 0; +const fiberIdMap = new Map(); +const getFiberId = (fiber: Fiber) => { + const existing = fiberIdMap.get(fiber); + + const inc = () => { + lastFiberId++; + fiberIdMap.set(fiber, lastFiberId); + + return lastFiberId; + }; + if (existing) { + return inc(); + } + + if (fiber.alternate) { + const existing = fiberIdMap.get(fiber.alternate); + if (existing) { + return inc(); + } + } + + lastFiberId++; + + fiberIdMap.set(fiber, lastFiberId); + + return lastFiberId; +}; + +const makeReactScanPayload = ( + buffered: Map +): ReactScanPayload => { + const payload: ReactScanPayload = []; + buffered.forEach(({ renderCount, fiber, selfTime }) => { + const stateNode = getNearestHostFiber(fiber)?.stateNode; + if (!stateNode) { + return; + } + + const rrwebId = record!.mirror.getId(stateNode); + if (rrwebId === -1) { + return; + } + + payload.push({ + renderCount, + rrwebDomId: rrwebId, + componentName: getDisplayName(fiber.type) ?? "N/A", + fiberId: getFiberId(fiber), + selfTime, + }); + }); + return payload; +}; + +let interval: ReturnType; +const startFlushRenderBufferInterval = () => { + clearInterval(interval); + interval = setInterval(() => { + if (bufferedFiberRenderMap.size === 0) { + return; + } + + const payload = makeReactScanPayload(bufferedFiberRenderMap); + + rrwebEvents.push(makeEvent(payload)); + bufferedFiberRenderMap.clear(); + }, 75); +}; + +const fpsDiffs: Array<[fps: number, changedAt: number]> = []; + +let lastInteractionTime: null | number = null; + +type ReactScanMetaPayload = + | { + kind: "interaction-start"; + interactionUUID: string; + dateNowTimestamp: number; + } + | { + kind: "interaction-end"; + interactionUUID: string; + dateNowTimestamp: number; + memoryUsage: number | null; + } + | { + kind: "fps-update"; + dateNowTimestamp: number; + fps: number; + }; + +export const makeMetaEvent = (payload: ReactScanMetaPayload) => + makeEvent(payload, "react-scan-meta"); + +export type SerializableFlushPayload = Omit & { + interactions: Array; +}; + +type FlushPayload = { + events: Array; + interactions: Array; + fpsDiffs: any; + startAt: number; + completedAt: number; +}; + +export const flushInvariant = (payload: SerializableFlushPayload) => { + // devInvariant( + // payload.events[0].type === 4, + // "first rrweb event must be meta event" + // ); + if (payload.events[0].type !== 4) { + console.warn("hm", payload); + } + // todo: reset state when invariant is violated + payload.interactions.forEach(() => { + const startEndEvents = payload.events.filter((event) => { + if (event.type !== 6) { + return; + } + if (event.data.plugin !== "react-scan-meta") { + return; + } + const payload = event.data.payload as ReactScanMetaPayload; + if (payload.kind === "fps-update") { + return; + } + return true; + }); + + const eventMapping = new Map<`${string}$${string}`, number>(); + + startEndEvents.forEach((value) => { + const data = (value.data as any).payload as ReactScanMetaPayload; + if (data.kind === "fps-update") { + return; + } + + let existingCount = eventMapping.get( + `${data.kind}$${data.interactionUUID}` + ); + if (existingCount) { + eventMapping.set( + `${data.kind}$${data.interactionUUID}`, + existingCount + 1 + ); + return; + } + + eventMapping.set(`${data.kind}$${data.interactionUUID}`, 1); + }); + + console.log("Event mapping data", eventMapping); + + eventMapping.forEach((value, key) => { + // devInvariant(value === 1); + const [kind, uuid] = key.split("$"); + + const associatedKind = + kind === "interaction-start" ? "interaction-end" : "interaction-start"; + + const associatedEvent = eventMapping.get(`${associatedKind}$${uuid}`); + }); + }); +}; + +/** + * we push a task to flush the recording + * + * that means we should use the store and channels + */ + +const recordingTasks: Array<{ + flush: () => void; + interactionUUIDs: Array; +}> = []; + +// this function handles flushing any tasks that are ready to be sent +const flushUploadedRecordingTasks = (tasks: typeof recordingTasks) => { + const completedInteractions = ( + performanceEntryChannels.getChannelState("flushed-interactions") as Array< + Array + > + ).flat(); + + tasks.forEach(({ interactionUUIDs, flush }) => { + if ( + interactionUUIDs.every((uuid) => completedInteractions.includes(uuid)) + ) { + // no longer needed, so we can clean them up from the channel state + performanceEntryChannels.updateChannelState( + "flushed-interactions", + (state: BoundedArray>) => { + return BoundedArray.fromArray( + state.map((inner) => + BoundedArray.fromArray( + inner.filter((uuid) => !interactionUUIDs.includes(uuid)), + MAX_CHANNEL_SIZE + ) + ), + MAX_CHANNEL_SIZE + ); + } + ); + + flush(); + } + }); +}; + +const setupFlushedInteractionsListener = () => { + return performanceEntryChannels.subscribe("flushed-interactions", () => { + flushUploadedRecordingTasks(recordingTasks); + }); +}; + +const getMemoryMB = () => { + if (!("memory" in performance)) { + return; + } + try { + // @ts-expect-error + if (performance?.memory?.usedJSHeapSize > 0) { + // @ts-expect-error + return Math.round(performance.memory.usedJSHeapSize / 1048576); + } + return null; + } catch { + return null; + } +}; + +export const scanWithRecord = () => { + const unSubPerformance = setupPerformancePublisher(); + const unSubFlushedInteractions = setupFlushedInteractionsListener(); + const onStart = (interactionUUID: string) => { + rrwebEvents.push( + makeMetaEvent({ + kind: "interaction-start", + interactionUUID, + dateNowTimestamp: Date.now(), // should use performance now but easier to get that wrong, so that's a future optimization + }) + ); + console.log("pushed rrweb start event", rrwebEvents); + countMeta(rrwebEvents); + }; + + const onComplete = async ( + interactionUUID: string, + finalInteraction: { + detailedTiming: TimeoutStage; + latency: number; + completedAt: number; + flushNeeded: boolean; + } + ) => { + lastInteractionTime = Date.now(); + const existingCompletedInteractions = + performanceEntryChannels.getChannelState( + "recording" + ) as Array; + + finalInteraction.detailedTiming.stopListeningForRenders(); + + rrwebEvents.push( + makeMetaEvent({ + kind: "interaction-end", + interactionUUID, + dateNowTimestamp: Date.now(), // should use performance now but easier to get that wrong, so that's a future optimization + memoryUsage: getMemoryMB() ?? null, + }) + ); + + console.log("pushed rrweb end event", rrwebEvents); + countMeta(rrwebEvents); + if (existingCompletedInteractions.length) { + // then performance entry and our detailed timing handlers are out of sync, we disregard that entry + // it may be possible the performance entry returned before detailed timing. If that's the case we should update + // assumptions and deal with mapping the entry back to the detailed timing here + performanceEntryChannels.updateChannelState( + "recording", + () => new BoundedArray(MAX_CHANNEL_SIZE) + ); + } + }; + const unSubDetailedPointerTiming = setupDetailedPointerTimingListener( + "pointer", + { + onStart, + onComplete, + getNodeID: (node) => record?.mirror.getId(node)!, + } + ); + const unSubDetailedKeyboardTiming = setupDetailedPointerTimingListener( + "keyboard", + { + onStart, + onComplete, + getNodeID: (node) => record?.mirror.getId(node)!, + } + ); + + const unSubFps = listenToFps((fps) => { + const event = makeMetaEvent({ + kind: "fps-update", + dateNowTimestamp: Date.now(), + fps, + }); + + rrwebEvents.push(event); + logFPSUpdates(rrwebEvents); + }); + const unSubInteractions = listenForPerformanceEntryInteractions( + (completedInteraction) => { + interactionStore.setState( + BoundedArray.fromArray( + interactionStore.getCurrentState().concat(completedInteraction), + MAX_INTERACTION_BATCH + ) + ); + countMeta(rrwebEvents); + } + ); + + startNewRecording(); + + startFlushRenderBufferInterval(); + + ReactScanInternals.instrumentation = createInstrumentation( + "react-scan-session-replay", + { + onRender: (fiber) => { + addToBufferedFiberRenderMap(fiber); + }, + isValidFiber: () => true, + + onCommitStart() { + ReactScanInternals.options.value.onCommitStart?.(); + }, + onCommitFinish() { + ReactScanInternals.options.value.onCommitFinish?.(); + }, + + onError: () => {}, + trackChanges: false, + forceAlwaysTrackRenders: true, + } + ); + + const stopInterval = setDebouncedInterval( + () => { + countMeta(rrwebEvents, "prequeue flush start"); + logFPSUpdates(rrwebEvents); + console.log( + "[Interval Callback]: before filters", + interactionStore.getCurrentState(), + rrwebEvents + ); + rrwebEvents = removeUnusableMetadata(rrwebEvents); + countMeta(rrwebEvents, "post filter pre quue flush start"); + interactionStore.setState( + BoundedArray.fromArray( + removeUnusableInteractions( + interactionStore.getCurrentState(), + rrwebEvents[0].timestamp + ), + MAX_INTERACTION_BATCH + ) + ); + console.log( + "[Interval Callback]: running", + rrwebEvents, + interactionStore.getCurrentState() + ); + + queueFlush(); + }, + () => { + const now = Date.now(); + rrwebEvents = removeOldUnmatchedInteractionsFromMetadata(rrwebEvents); + removeInteractionsBeforeRecordingStart(); + if (!rrwebEvents.length) { + console.log("[Interval Early Return]: no events"); + + // at minimum we want to wait for 3 seconds before and after the clip, so we should debounce for >6000ms + return 7000; + } /** + * NOTE: this may seem incorrect since the session replay is lastEventTimeStamp - firstEventTimestamp long, but + * we will fill the time difference with white space in puppeteer. It's only valid to add this white space if + * we are sure there is inactivity, and in this case we are since the difference between Date.now() and last event + * is the inactive time + */ + const recordStart = rrwebEvents[0].timestamp; + console.log("the first event is", rrwebEvents[0]); + + if (now - recordStart > 25_000) { + onIdle(() => { + startNewRecording(); + }); + + return false; + } + if (!lastInteractionTime) { + console.log("[Interval Early Return]: no interaction"); + return 7000; + } + + if (now - lastInteractionTime < 2000) { + console.log("[Interval Early Return]: interaction too close"); + return 5000; + } + // always flush if the recording is 25 seconds old + + // if we can flush at 15 seconds we wil, but if an interaction happens at the end + // of the recording we will keep pushing back the recording till it hits 25 seconds + if (now - recordStart > 15_000) { + console.log("time since record", now - recordStart); + + if (now - lastInteractionTime < 3000) { + console.log("[Interval Early Return]: push window back 3 seconds"); + return 3000; + } + + // ; + + return false; + } + + // it's possible an interaction is on going since the main thread could be unblocked before the frame is drawn + // if instead there are interaction details on a fake interaction, then the GC at the start of the interval will clean it up in due time + const ongoingInteraction = iife(() => { + let startCount = 0; + let endCount = 0; + for (const event of rrwebEvents) { + ifScanMeta(event, (payload) => { + switch (payload.kind) { + case "interaction-start": { + startCount++; + return; + } + case "interaction-end": { + endCount++; + return; + } + } + }); + } + return startCount !== endCount; + }); + + if (ongoingInteraction) { + console.log("[Interval Early Return]: ongoing interaction"); + return 4000; + } + + const timeSinceLastInteractionComplete = now - lastInteractionTime; + if (timeSinceLastInteractionComplete < 4000) { + console.log( + "[Interval Early Return]: capturing more of end window for clip" + ); + return timeSinceLastInteractionComplete; + } + + console.log("[Interval Early Return]: just flush then"); + return false; + } + ); + + return () => { + unSubPerformance(); + unSubFlushedInteractions(); + unSubDetailedPointerTiming(); + unSubFps(); + unSubInteractions(); + unSubFlushedInteractions(); + stopInterval(); + unSubDetailedKeyboardTiming(); + }; +}; + +const logFPSUpdates = (events: Array) => { + // console.group("fps-updates"); + // const fps = events.filter((event) => { + // if (event.type !== 6 || event.data.plugin !== "react-scan-meta") { + // return; + // } + // const payload = event.data.payload as ReactScanMetaPayload; + // if (payload.kind === "fps-update") { + // console.log(event); + // return true; + // } + // }); + // if (fps.length === 0) { + // console.log("no fps updates"); + // } + // console.groupEnd(); +}; + +// todo, slight race in timing when really close to start or end, so should give leniancy in the race +const logVideoAndInteractionMetadata = ( + interactions: Array, + events: Array +) => { + console.group("log-video-meta"); + + const start = events[0].timestamp; + console.log("video is:", events.at(-1)?.timestamp! - start, "ms long"); + console.log("inactivity time is:", Date.now() - events.at(-1)?.timestamp!); + console.log("the first event is", events[0]); + console.log( + "just incase, here is the delay", + events[0].delay, + events.at(-1)?.delay + ); + + interactions.forEach((interaction, index) => { + console.log( + "The", + index + 1, + "interaction occurred at", + interaction.completedAt - start, + "ms in the video" + ); + }); + + console.groupEnd(); +}; + +// turn this into a recursive timeout so we can run onIdle and then call 10 seconds later +let lastRecordTime: number = Date.now(); // this is a valid initializer since it represents the earliest time flushing was possible, and lets us calculate the amount of time has passed since it's been possible to flush +type DebounceTime = number; + +export const ifScanMeta = ( + event: eventWithTime, + then?: (payload: ReactScanMetaPayload) => T, + otherwise?: R +): T | R | undefined => { + if (event.type === 6 && event.data.plugin === "react-scan-meta") { + return then?.(event.data.payload as ReactScanMetaPayload); + } + + return otherwise; +}; +export const removeUnusableInteractions = ( + interactions: Array, + recordStart: number +) => { + return interactions.filter((interaction) => { + if (interaction.completedAt < recordStart) { + console.log( + "[Interaction Filter]: interaction happened before recording", + interaction, + recordStart + ); + + return false; + } + + if (interaction.completedAt - recordStart < 2000) { + console.log( + "[Interaction Filter]: interaction happened less than 2 seconds from start", + interaction, + recordStart + ); + return false; + } + if (Date.now() - interaction.completedAt < 2000) { + console.log( + "[Interaction Filter]: interaction happened less than 2 seconds from the current point in time", + interaction, + recordStart + ); + return false; + } + return true; + }); +}; + +const eventIsUnmatched = ( + event: eventWithTime, + events: Array +) => { + if (event.type !== 6 || event.data.plugin !== "react-scan-meta") { + return false; + } + + const payload = event.data.payload as ReactScanMetaPayload; + if (payload.kind === "fps-update") { + return false; + } + + const hasMatch = events.some((matchSearchEvent) => { + if ( + matchSearchEvent.type !== 6 || + matchSearchEvent.data.plugin !== "react-scan-meta" + ) { + return false; + } + const matchSearchPayload = matchSearchEvent.data + .payload as ReactScanMetaPayload; + + switch (payload.kind) { + case "interaction-start": { + if (matchSearchPayload.kind !== "interaction-end") { + return false; + } + + if (matchSearchPayload.interactionUUID !== payload.interactionUUID) { + return false; + } + return true; + } + // a valid example an interaction end might go unmatched is: + // very long task -> record force restart -> interaction end + // now there is an interaction end not matched to a start + case "interaction-end": { + if (matchSearchPayload.kind !== "interaction-start") { + return false; + } + + if (matchSearchPayload.interactionUUID !== payload.interactionUUID) { + return false; + } + return true; + } + } + }); + + return !hasMatch; +}; + +export const removeOldUnmatchedInteractionsFromMetadata = ( + events: Array +) => { + return events.filter((event) => { + // don't inline makes it easier to read + if ( + Date.now() - event.timestamp > 3000 && + eventIsUnmatched(event, events) + ) { + if (event.type !== 6) { + return true; + } + if (event.data.plugin !== "react-scan-meta") { + return true; + } + const payload = event.data.payload as ReactScanMetaPayload; + if (payload.kind === "fps-update") { + return true; + } + console.log( + `[Remove Old Unmatched] Removing ${payload.kind} for interaction ${payload.interactionUUID} - ` + + `${Date.now() - event.timestamp}ms old and unmatched` + ); + return false; + } + return true; + }); +}; + +export const removeInteractionsBeforeRecordingStart = () => { + const recordingStart = rrwebEvents.at(0)?.timestamp; + if (!recordingStart) { + return; + } + interactionStore.setState( + BoundedArray.fromArray( + interactionStore + .getCurrentState() + .filter((interaction) => interaction.completedAt >= recordingStart), + MAX_INTERACTION_BATCH + ) + ); + if (lastInteractionTime && lastInteractionTime < recordingStart) { + lastInteractionTime = null; + } +}; + +export const removeUnusableMetadata = (events: Array) => { + // me must make sure to unconditionally removed the associated end if the start is removed + const removedInteractions: Array = []; + + const recordStart = events.at(0)?.timestamp; + + if (recordStart === undefined) { + return []; + } + + events.forEach((event) => { + if (event.type !== 6) { + return; + } + + if (event.data.plugin !== "react-scan-meta") { + return; + } + const payload = event.data.payload as ReactScanMetaPayload; + if (payload.kind === "fps-update") { + return; + } + + if (payload.kind !== "interaction-start") { + return; + } + + if (Date.now() - payload.dateNowTimestamp < 2000) { + console.log( + `[Removal] Interaction ${payload.interactionUUID} removed - Too recent (${Date.now() - payload.dateNowTimestamp}ms from now)` + ); + // then its too close to the end of the interaction + removedInteractions.push(payload.interactionUUID); + return; + } + + if (payload.dateNowTimestamp - recordStart <= 2000) { + // then its too close to the start of the interaction + removedInteractions.push(payload.interactionUUID); + return; + } + }); + + const eventsFilteredForTimeInvariants = events.filter((event) => { + if (event.type !== 6) { + return true; + } + + if (event.data.plugin !== "react-scan-meta") { + return true; + } + + const payload = event.data.payload as ReactScanMetaPayload; + + if (payload.kind === "fps-update") { + return true; + } + if (removedInteractions.includes(payload.interactionUUID)) { + return false; + } + + return true; + }); + + countMeta(eventsFilteredForTimeInvariants, "first filter"); + const alwaysMatchingMetadata = eventsFilteredForTimeInvariants.filter( + (event) => { + const matches = !eventIsUnmatched(event, eventsFilteredForTimeInvariants); + if ( + !matches && + event.type === 6 && + event.data.plugin === "react-scan-meta" && + (event.data.payload as ReactScanMetaPayload).kind !== "fps-update" + ) { + } + return matches; + } + ); + countMeta(alwaysMatchingMetadata, "second filter"); + + return alwaysMatchingMetadata; +}; + +const countMeta = (events: Array, message?: string) => { + const count = { + start: 0, + end: 0, + raw: [] as any[], + }; + events.forEach((e) => { + ifScanMeta(e, (payload) => { + if (payload.kind === "interaction-start") { + count.start += 1; + count.raw.push(payload); + return; + } + if (payload.kind === "interaction-end") { + count.end += 1; + count.raw.push(payload); + return; + } + }); + }); +}; + +let currentTimeOut: ReturnType; +export const setDebouncedInterval = ( + callback: () => void, + shouldDebounce: () => DebounceTime | false, + baseTime = 4000 +) => { + const debounce = shouldDebounce(); + + if (debounce === false) { + callback(); + currentTimeOut = setTimeout(() => { + setDebouncedInterval(callback, shouldDebounce, baseTime); + }, baseTime); + return () => { + clearTimeout(currentTimeOut); + }; + } + currentTimeOut = setTimeout(() => { + setDebouncedInterval(callback, shouldDebounce, debounce); + }, debounce); + + return () => { + clearTimeout(currentTimeOut); + }; +}; + +export const queueFlush = async () => { + try { + let fpsUpdateCount = 0; + for (const event of rrwebEvents) { + if (event.type === 6 && event.data.plugin === "react-scan-meta") { + const payload = event.data.payload as ReactScanMetaPayload; + if (payload.kind === "fps-update") { + fpsUpdateCount++; + } + } + } + + countMeta(rrwebEvents, "queue flush start"); + const completedInteractions = interactionStore.getCurrentState(); + if (!completedInteractions.length) { + return; + } + + if (!rrwebEvents.length) { + return; + } + + const payload: SerializableFlushPayload = { + events: rrwebEvents, + interactions: completedInteractions.map( + convertInteractionFiberRenderParents + ), + fpsDiffs: [...fpsDiffs], + // should verify lastRecordTime is always correct + startAt: rrwebEvents[0].timestamp, + completedAt: Date.now(), + }; + logVideoAndInteractionMetadata(payload.interactions, payload.events); + + try { + flushInvariant(payload); + } catch (e) { + console.error(e); + } + const body = JSON.stringify(payload); + const completedUUIDS = payload.interactions.map( + (interaction) => interaction.detailedTiming.interactionUUID + ); + fpsDiffs.length = 0; + logFPSUpdates(rrwebEvents); + recordingTasks.push({ + flush: () => { + console.debug("[Recording] Sending payload to server"); + fetch("http://localhost:4200/api/replay", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body, + }).catch(() => null); + + interactionStore.setState( + BoundedArray.fromArray( + interactionStore + .getCurrentState() + .filter( + (interaction) => + !completedUUIDS.includes( + interaction.detailedTiming.interactionUUID + ) + ), + MAX_INTERACTION_BATCH + ) + ); + }, + interactionUUIDs: payload.interactions.map( + (interaction) => interaction.detailedTiming.interactionUUID + ), + }); + // we continue recording as there's benefit to continue recording for as long as possible + // but we no longer need to keep the interaction metadata related to pending flushed interactions + rrwebEvents = rrwebEvents.filter((event) => { + if (event.type !== 6 || event.data.plugin !== "react-scan-meta") { + return true; + } + const payload = event.data.payload as ReactScanMetaPayload; + if (payload.kind === "fps-update") { + return true; + } + + return !completedUUIDS.includes(payload.interactionUUID); + }); + + flushUploadedRecordingTasks(recordingTasks); + interactionStore.setState(new BoundedArray(MAX_INTERACTION_BATCH)); + } catch (e) { + console.error("[Recording] Error during flush:", e); + } +}; diff --git a/packages/scan/src/core/monitor/session-replay/replay-v2.ts b/packages/scan/src/core/monitor/session-replay/replay-v2.ts new file mode 100644 index 00000000..b8a2a00b --- /dev/null +++ b/packages/scan/src/core/monitor/session-replay/replay-v2.ts @@ -0,0 +1,1329 @@ +// todos + +import { + applyLabelTransform, + batchGetBoundingRects, + flushOutlines, + getBoundingRect, + getIsOffscreen, + getOverlapArea, + measureTextCached, + mergeOverlappingLabels, + Outline, + OutlineLabel, + pickColorClosestToStartStage, +} from '@web-utils/outline'; +import { OutlineKey } from 'src/core'; +import { ReactScanPayload } from 'src/core/monitor/session-replay/record'; + +// copy activate outline exactly + +// goal is to map the input datastructure what to schedueld outlines looks +// like exactly + +/** + * then we have a new scheduled outlines datastructure + * + * + * that datastructure will be plopped into the activateOutline function + * + * + * then we just reply fiber alternate stuff with the actual reference + * + * + * we collect the dom nodes for the scheduled outlines first + * + * + * then we simply just use exact naming and swap + * + * + * then we chop the properties we don't need + * + * + * then later we can optimize for the use case if some things aren't needed + */ + +type FiberId = number; + +type ComponentName = string; +export interface ReplayOutline { + timestamp: number; + domNodeId: number; + /** Aggregated render info */ // TODO: Flatten AggregatedRender into Outline to avoid re-creating objects + // this render is useless when in active outlines (confirm this rob) + aggregatedRender: ReplayAggregatedRender; // maybe we should set this to null when its useless + + /* Active Info- we re-use the Outline object to avoid over-allocing objects, which is why we have a singular aggregatedRender and collection of it (groupedAggregatedRender) */ + alpha: number | null; + totalFrames: number | null; + /* + - Invariant: This scales at a rate of O(unique components rendered at the same (x,y) coordinates) + - renders with the same x/y position but different fibers will be a different fiber -> aggregated render entry. + */ + groupedAggregatedRender: Map | null; + + /* Rects for interpolation */ + current: DOMRect | null; + target: DOMRect | null; + /* This value is computed before the full rendered text is shown, so its only considered an estimate */ + estimatedTextWidth: number | null; // todo: estimated is stupid just make it the actual +} +export interface ReplayAggregatedRender { + name: ComponentName; + frame: number | null; + // phase: Set<'mount' | 'update' | 'unmount'>; + time: number | null; // maybe... + aggregatedCount: number; + // forget: boolean; + // changes: AggregatedChange; + // unnecessary: boolean | null; + // didCommit: boolean; + // fps: number; + + computedKey: OutlineKey | null; + computedCurrent: DOMRect | null; // reference to dom rect to copy over to new outline made at new position +} + +const MAX_FRAME = 45; +type ScheduledReplayOutlines = Map; + +// const payloadToScheduledReplayOutlines = ( +// payload: ReactScanPayload, +// replayer: { +// getMirror: () => { getNode: (nodeId: number) => HTMLElement | null }; +// }, +// ): ScheduledReplayOutlines => { +// const scheduledReplayOutlines = new Map(); + +// for (const event of payload) { +// const domNode = replayer.getMirror().getNode(event.rrwebDomId); +// if (!domNode) { +// continue; +// } +// scheduledReplayOutlines.set(event.fiberId, { +// aggregatedRender: { +// aggregatedCount: event.renderCount, +// computedCurrent: null, +// computedKey: null, +// frame: 45, +// name: event.componentName, +// time: null, +// }, +// alpha: null, +// groupedAggregatedRender: null, +// target: null, +// current: null, +// totalFrames: null, +// estimatedTextWidth: null, +// domNode, +// }); +// } + +// return scheduledReplayOutlines; +// }; + +// const activateOutlines = async (scheduledOutlines: ScheduledReplayOutlines) => { +// }; + +/** + * + * now we can transform the payload into scheduled, and scheduled into active + * + * + * now we should make this all a plugin so we can actually access the replayer + * + * + * there will need to be an interval etc all globals tied to the plugin, not the global scope anymore + * + * + * so we init on the plugin, and replay like a boss + */ +import { EventType, eventWithTime } from '@rrweb/types'; +import { ReplayPlugin } from 'rrweb/typings/types'; +import { ReactScanInternals } from '../..'; +import { signal } from '@preact/signals'; +import { getLabelText } from 'src/core/utils'; +import { LRUMap } from '@web-utils/lru'; + +/** + * Deep clones an object, skipping non-cloneable values like functions, DOM nodes, etc. + * @param obj The object to clone + * @returns A deep clone of the object with non-cloneable values omitted + */ +export const safeDeepClone = (obj: T): T => { + if (obj === null || obj === undefined) { + return obj; + } + + // Handle primitive types + if (typeof obj !== 'object') { + return obj; + } + + // Handle arrays + if (Array.isArray(obj)) { + return obj.map((item) => safeDeepClone(item)) as unknown as T; + } + + // Handle dates + if (obj instanceof Date) { + return new Date(obj.getTime()) as unknown as T; + } + + // Create new object of same type + const clone = Object.create(Object.getPrototypeOf(obj)); + + // Clone each property + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + try { + const val = obj[key]; + // Skip if value is not cloneable (functions, DOM nodes etc) + if ( + val === null || + typeof val !== 'object' || + val instanceof Node || + typeof val === 'function' + ) { + clone[key] = val; + } else { + clone[key] = safeDeepClone(val); + } + } catch (e) { + // Skip any values that error during cloning + clone[key] = obj[key]; + } + } + } + + return clone; +}; + +export interface RRWebPlayer { + play: () => void; + pause: () => void; + getCurrentTime: () => number; + getMirror: () => { + getNode: (id: number) => HTMLElement | null; + }; + iframe: HTMLIFrameElement; + // addEventListener: (event: string, handler: Function) => void; + // removeEventListener: (event: string, handler: Function) => void; + // getReplayer: () => unknown; +} + +/** + * we should scope all data to this plugin, including the interval + * + * then we simply flush to the canvas we are going to make + */ + +export class ReactScanReplayPlugin implements ReplayPlugin { + private canvas: HTMLCanvasElement | null = null; + private ctx: CanvasRenderingContext2D | null = null; + private replayer: RRWebPlayer | null = null; + private activeOutlines = new Map(); + private scale: number = 1; + private translateX: number = 0; + private translateY: number = 0; + private resizeObserver: ResizeObserver | null = null; + private skipBefore: number | null = null; + + constructor(skipBefore?: number) { + console.log('da constructor'); + + if (skipBefore) { + this.skipBefore = skipBefore; + } + } + + // public async makeThumbnail( + // mode: + // | { kind: 'thumbnail'; payload: ReactScanPayload } + // | { kind: 'replay' } = { kind: 'replay' }, + // ) { + // this.mode = mode; + // try { + // console.log('calling', this.replayer, '<-'); + + // if (!this.canvas) { + // console.log('initing'); + + // this.initCanvas(this.replayer!.iframe); + // } + // console.log('okay...', this.mode.kind); + + // switch (this.mode.kind) { + // case 'replay': { + // console.log('well no...'); + + // return 'fnldaskjfl;kads'; + // } + // case 'thumbnail': { + // console.log('drawing payl'); + + // const scheduledOutlines = this.payloadToScheduledReplayableOutlines( + // this.mode.payload, + // -1 + // ); + // console.log('outlines', scheduledOutlines); + + // if (!scheduledOutlines) { + // // invariant + // console.log('big el'); + + // return; + // } + // console.log('aw man'); + + // await this.activateOutlines(scheduledOutlines); + // // .catch(() => { + // // console.log('big wooper'); + // // }); + // console.log('active', this, this.activeOutlines); + // drawThumbnailOutlines( + // this.ctx!, + // this.activeOutlines, + // this.translateX, + // this.translateY, + // ); + // return 'donso!'; + // } + // } + // } catch (e) { + // console.log('e', e); + // } + // } + + async handler( + event: eventWithTime, + isSync: boolean, + context: { replayer: any }, + ) { + if (!this.replayer) { + this.replayer = context.replayer; + } + + if ( + event.type === EventType.Plugin && + event.data.plugin === 'react-scan-plugin' + ) { + // console.log('running yipee'); + + const payload = event.data.payload as ReactScanPayload; + // const nodeId = data.payload.nodeId; + // const mirror = this.replayer!.getMirror(); + // const node = mirror.getNode(nodeId) as HTMLElement | null; + + // if (!node) { + // return; + // } + + if (!this.canvas) { + this.initCanvas(this.replayer!.iframe); + } + + // console.log( + // 'activated', + // safeDeepClone(Array.from(this.activeOutlines.values())), + // ); + + // flushOutlines(); + // this is really stupid but its fine + const scheduledOutlines = this.payloadToScheduledReplayableOutlines( + payload, + event.timestamp, + ); + + if (!scheduledOutlines) { + // invariant + // console.log('big el'); + + return; + } + await this.activateOutlines(scheduledOutlines); + if (!animationFrameId) { + // console.log('fading out'); + + animationFrameId = requestAnimationFrame(() => + fadeOutOutlineReplay( + this.ctx!, + this.activeOutlines, + this.translateX, + this.translateY, + this.skipBefore, + ), + ); + } + // switch (this.mode.kind) { + // case 'replay': { + + // return; + // } + // case 'thumbnail': { + // console.log('drawing payl'); + + // const scheduledOutlines = this.payloadToScheduledReplayableOutlines( + // this.mode.payload, + // ); + // console.log('outlines', scheduledOutlines); + + // if (!scheduledOutlines) { + // // invariant + // // console.log('big el'); + + // return; + // } + + // await this.activateOutlines(scheduledOutlines); + // console.log('active', this, this.activateOutlines); + + // drawThumbnailOutlines( + // this.ctx!, + // this.activeOutlines, + // this.translateX, + // this.translateY, + // ); + // } + // } + // if (this.mode === '') + + // here we recieve the replayer + + // ReactScanInternals.scheduledOutlines.push({ + // domNode: node, + // rect: node.getBoundingClientRect(), + // renders: data.payload.outline.renders, + // }); + + // flushOutlines(this.ctx!, previousOutlines); + } + } + + // this probably can be simplified? maybe? + private initCanvas(iframe: HTMLIFrameElement) { + this.canvas = document.createElement('canvas'); + const iframeRect = iframe.getBoundingClientRect(); + const dpi = window.devicePixelRatio || 1; + let wrapper = iframe.parentElement; + if (wrapper) { + wrapper.style.position = 'relative'; + } + + const updateCanvasSize = () => { + const newRect = iframe.getBoundingClientRect(); + const parentRect = wrapper?.getBoundingClientRect() || newRect; + + // Position relative to parent + this.canvas!.style.cssText = ` + position: absolute; + top: ${newRect.top - parentRect.top}px; + left: ${newRect.left - parentRect.left}px; + width: ${newRect.width}px; + height: ${newRect.height}px; + pointer-events: none; + z-index: 2147483647; + background: transparent; + opacity: 1 !important; + visibility: visible !important; + display: block !important; + `; + + // Update canvas dimensions + this.canvas!.width = newRect.width * dpi; + this.canvas!.height = newRect.height * dpi; + + if (this.ctx) { + this.ctx.scale(dpi, dpi); + } + + // Get both scale and translation from the transform matrix + const transform = iframe.style.transform; + const matrix = new DOMMatrix(transform); + this.scale = matrix.a; + this.translateX = matrix.e; + this.translateY = matrix.f; + }; + + // Initial setup + updateCanvasSize(); + iframe.parentElement?.appendChild(this.canvas); + this.ctx = this.canvas.getContext('2d', { + willReadFrequently: true, + }); + + if (this.ctx) { + this.ctx.imageSmoothingEnabled = false; // Disable antialiasing + } + + // Setup resize observer + this.resizeObserver = new ResizeObserver(() => { + updateCanvasSize(); + }); + this.resizeObserver.observe(iframe); + } + + // Add cleanup method + public destroy() { + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = null; + } + if (this.canvas) { + this.canvas.remove(); + this.canvas = null; + } + this.ctx = null; + } + + private payloadToScheduledReplayableOutlines( + payload: ReactScanPayload, + timestamp: number, + ) { + const scheduledReplayOutlines = new Map(); + if (!this.replayer) { + // invariant + // console.log('NOPEE'); + + return; + } + for (const event of payload) { + // const domNode = this.replayer.getMirror().getNode(event.rrwebDomId); + // if (!domNode) { + // continue; + // } + scheduledReplayOutlines.set(event.fiberId, { + aggregatedRender: { + aggregatedCount: event.renderCount, + computedCurrent: null, + computedKey: null, + frame: 0, + name: event.componentName, + time: null, + }, + alpha: null, + groupedAggregatedRender: null, + target: null, + current: null, + totalFrames: null, + estimatedTextWidth: null, + domNodeId: event.rrwebDomId, + timestamp, + }); + } + // console.log('scheduled', scheduledReplayOutlines, payload.length); + + return scheduledReplayOutlines; + } + private async activateOutlines(scheduledOutlines: ScheduledReplayOutlines) { + const domNodes: Array = []; + // const scheduledOutlines = ReactScanInternals.scheduledOutlines; + // const activeOutlines = ReactScanInternals.activeOutlines; + const activeFibers = new Map(); + + // fiber alternate merging and activeFiber tracking + // shouldn't need this anymore since alternate logic is handled + for (const activeOutline of this.activeOutlines.values()) { + if (!activeOutline.groupedAggregatedRender) { + continue; + } + for (const [ + fiber, + aggregatedRender, + ] of activeOutline.groupedAggregatedRender) { + // if (fiber.alternate && activeFibers.has(fiber.alternate)) { + // // if it already exists, copy it over + // const alternateAggregatedRender = activeFibers.get(fiber.alternate); + + // if (alternateAggregatedRender) { + // joinAggregations({ + // from: alternateAggregatedRender, + // to: aggregatedRender, + // }); + // } + // // fixme: this seems to leave a label/outline alive for an extra frame in some cases + // activeOutline.groupedAggregatedRender?.delete(fiber); + // activeFibers.delete(fiber.alternate); + // } + // match the current render to its fiber + activeFibers.set(fiber, aggregatedRender); + } + } + // handles the case where the fiber already is in a position group, and the data + // simply needs to be merged in the existing entry + for (const [fiberId, outline] of scheduledOutlines) { + const existingAggregatedRender = activeFibers.get(fiberId); + if (existingAggregatedRender) { + // joinAggregations({ + // to: existingAggregatedRender, + // from: outline.aggregatedRender, + // }); + // existing (10count) -> incoming (100count) -> should be (110count) + existingAggregatedRender.aggregatedCount += + outline.aggregatedRender.aggregatedCount; + existingAggregatedRender.frame = 0; + } + // else, the later logic will handle adding the entry + const domNode = this.replayer!.getMirror().getNode(outline.domNodeId); + if (!domNode) { + console.log('get fucked 2', outline.domNodeId); + + continue; + } + domNodes.push(domNode); + } + + const rects = await batchGetBoundingRects(domNodes); // todo + const totalFrames = 45; + const alpha = 0.8; + + /** + * - handles calculating + updating rects, adding new outlines to a groupedAggregatedRender, and moving fibers to new position groups if their rect moved + * + * - this logic makes sense together since we can only determine if an outline should be created OR moved after we calculate the latest + * rect for the scheduled outline since that allows us to compute the position key- first level of aggregation we do on aggregations + * (note: we aggregate on position because we will always merge outlines with the same rect. Within the position based aggregation we + * aggregate based on fiber because we want re-renders for a fibers outline to stay consistent between frames, and gives us tight + * control over animation restart/cancel + interpolation) + */ + for (const [fiberId, outline] of scheduledOutlines) { + // todo: put this behind config to use intersection observer or update speed + // outlineUpdateSpeed: throttled | synchronous // "using synchronous updates will result in smoother animations, but add more overhead to react-scan" + const domNode = this.replayer!.getMirror().getNode(outline.domNodeId); + if (!domNode) { + console.log('get fucked1 ', outline.domNodeId); + + continue; + } + const rect = rects.get(domNode); + // const rect = domNode.getBoundingClientRect(); + if (!rect) { + console.log('no rect lol'); + + // intersection observer could not get a rect, so we have nothing to paint/activate + continue; + } + + if (rect.top === rect.bottom || rect.left === rect.right) { + console.log('poopeoo'); + + continue; + } + + const prevAggregatedRender = activeFibers.get(fiberId); + + const isOffScreen = getIsOffscreen(rect); + if (isOffScreen) { + // console.log('off screen see yeah'); + + continue; + } + + const key = `${rect.x}-${rect.y}` as const; + let existingOutline = this.activeOutlines.get(key); + + if (!existingOutline) { + existingOutline = outline; // re-use the existing object to avoid GC time + + existingOutline.target = rect; + existingOutline.totalFrames = totalFrames; + + existingOutline.groupedAggregatedRender = new Map([ + [fiberId, outline.aggregatedRender], + ]); + existingOutline.aggregatedRender.aggregatedCount = + prevAggregatedRender?.aggregatedCount ?? 1; + + existingOutline.alpha = alpha; + + existingOutline.aggregatedRender.computedKey = key; + + // handles canceling the animation of the associated render that was painted at a different location + if (prevAggregatedRender?.computedKey) { + const groupOnKey = this.activeOutlines.get( + prevAggregatedRender.computedKey, + ); + groupOnKey?.groupedAggregatedRender?.forEach( + (value, prevStoredFiberId) => { + if (prevStoredFiberId === fiberId) { + value.frame = 45; // todo: make this max frame, not hardcoded + + // for interpolation reference equality + if (existingOutline) { + existingOutline.current = value.computedCurrent!; + } + } + }, + ); + } + // console.log('setting here tho'); + + this.activeOutlines.set(key, existingOutline); + } else if (!prevAggregatedRender) { + // console.log('setting', outline.aggregatedRender); + + existingOutline.alpha = outline.alpha; + existingOutline.groupedAggregatedRender?.set( + fiberId, + outline.aggregatedRender, + ); + } + // if there's an aggregation at the rect position AND a previously computed render + // the previous fiber joining logic handles merging render aggregations with updated data + + // FIXME(Alexis): `|| 0` just for tseslint to shutup + // existingOutline.alpha = Math.max( + // existingOutline.alpha || 0, + // outline.alpha || 0, + // ); + + existingOutline.totalFrames = Math.max( + existingOutline.totalFrames || 0, + outline.totalFrames || 0, + ); + } + } +} + +// let animationFrameId: ReturnType + +// export const flushOutlines = async ( +// ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, +// ) => { +// if ( +// !ReactScanInternals.scheduledOutlines.size && +// !ReactScanInternals.activeOutlines.size +// ) { +// return; +// } + +// const flattenedScheduledOutlines = Array.from( +// ReactScanInternals.scheduledOutlines.values(), +// ); + +// await activateOutlines(); + +// recalcOutlines(); + +// ReactScanInternals.scheduledOutlines = new Map(); + +// const { options } = ReactScanInternals; + +// options.value.onPaintStart?.(flattenedScheduledOutlines); + +// if (!animationFrameId) { +// animationFrameId = requestAnimationFrame(() => fadeOutOutline(ctx)); +// } +// }; + +let animationFrameId: number | null = null; + +const shouldSkipInterpolation = (rect: DOMRect) => { + // animations tend to transform out of screen/ to a very tiny size, those are noisy so we don't lerp them + if ( + rect.top >= window.innerHeight || // completely below viewport + rect.bottom <= 0 || // completely above viewport + rect.left >= window.innerWidth || // completely right of viewport + rect.right <= 0 // completely left of viewport + ) { + return true; + } + + return !ReactScanInternals.options.value.smoothlyAnimateOutlines; +}; + +const DEFAULT_THROTTLE_TIME = 32; // 2 frames + +const START_COLOR = { r: 115, g: 97, b: 230 }; +const END_COLOR = { r: 185, g: 49, b: 115 }; +const MONO_FONT = + 'Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace'; + +export const getOutlineKey = (rect: DOMRect): string => { + return `${rect.top}-${rect.left}-${rect.width}-${rect.height}`; +}; +const enum Reason { + Commit = 0b001, + Unstable = 0b010, + Unnecessary = 0b100, +} +export interface ReplayOutlineLabel { + alpha: number; + color: { r: number; g: number; b: number }; + // reasons: number; // based on Reason enum + labelText: string; + textWidth: number; + activeOutline: ReplayOutline; +} + +export const drawThumbnailOutlines = ( + ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, + activeOutlines: Map, + translateX: number = 0, + translateY: number = 0, +) => { + const dpi = window.devicePixelRatio || 1; + ctx.clearRect(0, 0, ctx.canvas.width / dpi, ctx.canvas.height / dpi); + ctx.save(); + + // Draw black rectangle over entire canvas + // const dpi = window.devicePixelRatio || 1; + const width = ctx.canvas.width / dpi; + const height = ctx.canvas.height / dpi; + + // console.log('Drawing black rectangle:', { + // x: 0, + // y: 0, + // width, + // height, + // }); + + // ctx.fillStyle = 'black'; + // ctx.fillRect(0, 0, width, height); + + // // Test if canvas context is working + // ctx.fillStyle = 'red'; + // ctx.fillRect(100, 100, 200, 200); + // console.log('Drew test rectangle'); + + // Track positions for label merging + const labelPositions: Array<{ + x: number; + y: number; + text: string; + alpha: number; + color: { r: number; g: number; b: number }; + }> = []; + console.log('about to draw demon mode', ctx); + + for (const [key, activeOutline] of activeOutlines) { + // console.log('active outline', activeOutline); + + // invariant: active outline has "active" info non nullable at this point of the program b/c they must be activated + const invariantActiveOutline = activeOutline as { + [K in keyof ReplayOutline]: NonNullable; + }; + + const color = START_COLOR; + const alpha = 0.8; + const fillAlpha = alpha * 0.1; + const target = invariantActiveOutline.target; + + // For screenshot, we always use target rect directly + invariantActiveOutline.current = target; + invariantActiveOutline.groupedAggregatedRender.forEach((v) => { + v.computedCurrent = target; + }); + + // Draw rectangle + const rect = invariantActiveOutline.current; + const rgb = `${color.r},${color.g},${color.b}`; + ctx.strokeStyle = `rgba(${rgb},${alpha})`; + ctx.lineWidth = 1; + ctx.fillStyle = `rgba(${rgb},${fillAlpha})`; + + ctx.beginPath(); + // console.log('drawing at', rect); + + ctx.rect(rect.x + translateX, rect.y + translateY, rect.width, rect.height); + ctx.stroke(); + ctx.fill(); + + // Get label text + const labelText = getReplayLabelText( + Array.from(invariantActiveOutline.groupedAggregatedRender.values()), + ); + + if (labelText) { + labelPositions.push({ + x: rect.x + translateX, + y: rect.y + translateY, + text: labelText, + alpha, + color, + }); + } + } + + // Draw labels + ctx.font = `11px ${MONO_FONT}`; + const textHeight = 11; + const padding = 4; + + // Sort labels by position for merging + labelPositions.sort((a, b) => { + if (Math.abs(a.y - b.y) < textHeight * 2) { + return a.x - b.x; + } + return a.y - b.y; + }); + + // Merge and draw labels + let currentGroup: typeof labelPositions = []; + let lastY = -Infinity; + + for (const label of labelPositions) { + if (Math.abs(label.y - lastY) > textHeight * 2) { + // Draw current group + if (currentGroup.length > 0) { + const mergedText = currentGroup.map((l) => l.text).join(', '); + const { x, y, alpha, color } = currentGroup[0]; + const textMetrics = ctx.measureText(mergedText); + + // Background + ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${alpha})`; + ctx.fillRect( + x, + y - textHeight - padding, + textMetrics.width + padding * 2, + textHeight + padding * 2, + ); + + // Text + ctx.fillStyle = `rgba(255,255,255,${alpha})`; + ctx.fillText(mergedText, x + padding, y - padding); + } + + // Start new group + currentGroup = [label]; + lastY = label.y; + } else { + currentGroup.push(label); + } + } + + // Draw final group + if (currentGroup.length > 0) { + const mergedText = currentGroup.map((l) => l.text).join(', '); + const { x, y, alpha, color } = currentGroup[0]; + const textMetrics = ctx.measureText(mergedText); + + ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${1})`; + ctx.fillRect( + x, + y - textHeight - padding, + textMetrics.width + padding * 2, + textHeight + padding * 2, + ); + + ctx.fillStyle = `rgba(255,255,255,${1})`; + ctx.fillText(mergedText, x + padding, y - padding); + } + + ctx.restore(); +}; + +export const fadeOutOutlineReplay = ( + ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, + activeOutlines: Map, + translateX: number = 0, + translateY: number = 0, + skipBefore: number | null, +) => { + const dpi = window.devicePixelRatio || 1; + ctx.clearRect(0, 0, ctx.canvas.width / dpi, ctx.canvas.height / dpi); + ctx.save(); + + // Track positions for label merging + const labelPositions: Array<{ + x: number; + y: number; + text: string; + alpha: number; + color: { r: number; g: number; b: number }; + }> = []; + + console.log('active', activeOutlines); + + for (const [key, activeOutline] of activeOutlines) { + // invariant: active outline has "active" info non nullable at this point of the program b/c they must be activated + const invariantActiveOutline = activeOutline as { + [K in keyof ReplayOutline]: NonNullable; + }; + let frame; + + for (const aggregatedRender of invariantActiveOutline.groupedAggregatedRender.values()) { + aggregatedRender.frame! += 1; + frame = frame + ? Math.max(aggregatedRender.frame!, frame) + : aggregatedRender.frame!; + } + + if (!frame) { + activeOutlines.delete(key); + continue; + } + + const t = 0; + const r = Math.round(START_COLOR.r + t * (END_COLOR.r - START_COLOR.r)); + const g = Math.round(START_COLOR.g + t * (END_COLOR.g - START_COLOR.g)); + const b = Math.round(START_COLOR.b + t * (END_COLOR.b - START_COLOR.b)); + const color = { r, g, b }; + + const alphaScalar = 0.8; + invariantActiveOutline.alpha = + alphaScalar * Math.max(0, 1 - frame / invariantActiveOutline.totalFrames); + + const alpha = invariantActiveOutline.alpha; + const fillAlpha = alpha * 0.1; + const target = invariantActiveOutline.target; + + const shouldSkip = shouldSkipInterpolation(target); + if (shouldSkip) { + invariantActiveOutline.current = target; + invariantActiveOutline.groupedAggregatedRender.forEach((v) => { + v.computedCurrent = target; + }); + } else { + if (!invariantActiveOutline.current) { + invariantActiveOutline.current = new DOMRect( + target.x, + target.y, + target.width, + target.height, + ); + } + + const INTERPOLATION_SPEED = 0.2; + const current = invariantActiveOutline.current; + + const lerp = (start: number, end: number) => { + return start + (end - start) * INTERPOLATION_SPEED; + }; + + const computedCurrent = new DOMRect( + lerp(current.x, target.x), + lerp(current.y, target.y), + lerp(current.width, target.width), + lerp(current.height, target.height), + ); + + invariantActiveOutline.current = computedCurrent; + + invariantActiveOutline.groupedAggregatedRender.forEach((v) => { + v.computedCurrent = computedCurrent; + }); + } + + // Draw rectangle + const rect = invariantActiveOutline.current; + const rgb = `${color.r},${color.g},${color.b}`; + ctx.strokeStyle = `rgba(${rgb},${alpha})`; + ctx.lineWidth = 1; + ctx.fillStyle = `rgba(${rgb},${fillAlpha})`; + + ctx.beginPath(); + ctx.rect(rect.x + translateX, rect.y + translateY, rect.width, rect.height); + ctx.stroke(); + ctx.fill(); + + // Get label text + const labelText = getReplayLabelText( + Array.from(invariantActiveOutline.groupedAggregatedRender.values()), + ); + + if (labelText) { + labelPositions.push({ + x: rect.x + translateX, + y: rect.y + translateY, + text: labelText, + alpha, + color, + }); + } + + const totalFrames = invariantActiveOutline.totalFrames; + for (const [ + fiber, + aggregatedRender, + ] of invariantActiveOutline.groupedAggregatedRender) { + if (aggregatedRender.frame! >= totalFrames) { + invariantActiveOutline.groupedAggregatedRender.delete(fiber); + } + } + + if (invariantActiveOutline.groupedAggregatedRender.size === 0) { + activeOutlines.delete(key); + } + } + + // Draw labels + ctx.font = `11px ${MONO_FONT}`; + const textHeight = 11; + const padding = 4; + + // Sort labels by position for merging + labelPositions.sort((a, b) => { + if (Math.abs(a.y - b.y) < textHeight * 2) { + return a.x - b.x; + } + return a.y - b.y; + }); + + let currentGroup: typeof labelPositions = []; + let lastY = -Infinity; + + for (const label of labelPositions) { + if (Math.abs(label.y - lastY) > textHeight * 2) { + // Draw current group + if (currentGroup.length > 0) { + const mergedText = currentGroup.map((l) => l.text).join(', '); + const { x, y, alpha, color } = currentGroup[0]; + const textMetrics = ctx.measureText(mergedText); + + // Background + ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${alpha})`; + ctx.fillRect( + x, + y - textHeight - padding, + textMetrics.width + padding * 2, + textHeight + padding * 2, + ); + + // Text + ctx.fillStyle = `rgba(255,255,255,${alpha})`; + ctx.fillText(mergedText, x + padding, y - padding); + } + + // Start new group + currentGroup = [label]; + lastY = label.y; + } else { + currentGroup.push(label); + } + } + + if (currentGroup.length > 0) { + const mergedText = currentGroup.map((l) => l.text).join(', '); + const { x, y, alpha, color } = currentGroup[0]; + const textMetrics = ctx.measureText(mergedText); + + ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${alpha})`; + ctx.fillRect( + x, + y - textHeight - padding, + textMetrics.width + padding * 2, + textHeight + padding * 2, + ); + + ctx.fillStyle = `rgba(255,255,255,${alpha})`; + ctx.fillText(mergedText, x + padding, y - padding); + } + + ctx.restore(); + + if (activeOutlines.size) { + animationFrameId = requestAnimationFrame(() => + fadeOutOutlineReplay( + ctx, + activeOutlines, + translateX, + translateY, + skipBefore, + ), + ); + } else { + animationFrameId = null; + } +}; +const textMeasurementCache = new LRUMap(100); + +export const measureTextCachedReplay = ( + text: string, + ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, +): TextMetrics => { + if (textMeasurementCache.has(text)) { + return textMeasurementCache.get(text)!; + } + ctx.font = `11px ${MONO_FONT}`; + const metrics = ctx.measureText(text); + textMeasurementCache.set(text, metrics); + return metrics; +}; + +export const getReplayLabelText = ( + groupedAggregatedRenders: Array, +) => { + let labelText = ''; + + const componentsByCount = new Map< + number, + Array<{ name: string; time: number }> + >(); + + for (const aggregatedRender of groupedAggregatedRenders) { + const { time, aggregatedCount, name } = aggregatedRender; + if (!componentsByCount.has(aggregatedCount)) { + componentsByCount.set(aggregatedCount, []); + } + componentsByCount.get(aggregatedCount)!.push({ name, time: time ?? 0 }); + } + + const sortedCounts = Array.from(componentsByCount.keys()).sort( + (a, b) => b - a, + ); + + const parts: Array = []; + let cumulativeTime = 0; + for (const count of sortedCounts) { + const componentGroup = componentsByCount.get(count)!; + const names = componentGroup + .slice(0, 4) + .map(({ name }) => name) + .join(', '); + let text = names; + + const totalTime = componentGroup.reduce((sum, { time }) => sum + time, 0); + + cumulativeTime += totalTime; + + if (componentGroup.length > 4) { + text += '…'; + } + + if (count > 1) { + text += ` ×${count}`; + } + + parts.push(text); + } + + labelText = parts.join(', '); + + if (!labelText.length) return null; + + if (labelText.length > 40) { + labelText = `${labelText.slice(0, 40)}…`; + } + + if (cumulativeTime >= 0.01) { + labelText += ` (${Number(cumulativeTime.toFixed(2))}ms)`; + } + + return labelText; +}; + +export interface MergedReplayOutlineLabel { + alpha: number; + color: { r: number; g: number; b: number }; + // reasons: number; + groupedAggregatedRender: Array; + rect: DOMRect; +} + +function toMergedReplayLabel( + label: ReplayOutlineLabel, + rectOverride?: DOMRect, +): MergedReplayOutlineLabel { + const baseRect = rectOverride ?? label.activeOutline.current!; + const rect = applyLabelTransform(baseRect, label.textWidth); + const groupedArray = Array.from( + label.activeOutline.groupedAggregatedRender!.values(), + ); + return { + alpha: label.alpha, + color: label.color, + groupedAggregatedRender: groupedArray, + rect, + }; +} +// todo: optimize me so this can run always +// note: this can be implemented in nlogn using https://en.wikipedia.org/wiki/Sweep_line_algorithm +export const replayMergeOverlappingLabels = ( + labels: Array, +): Array => { + if (labels.length > 1500) { + return labels.map((label) => toMergedReplayLabel(label)); + } + + const transformed = labels.map((label) => ({ + original: label, + rect: applyLabelTransform( + new DOMRect( + label.activeOutline.current!.x, // Don't scale here + label.activeOutline.current!.y, + label.activeOutline.current!.width, + label.activeOutline.current!.height, + ), + label.textWidth, + ), + })); + + // Sort by y position first, then x + transformed.sort((a, b) => { + if (Math.abs(a.rect.y - b.rect.y) < 20) { + // Group labels within 20px vertical distance + return a.rect.x - b.rect.x; + } + return a.rect.y - b.rect.y; + }); + + const mergedLabels: Array = []; + const mergedSet = new Set(); + + for (let i = 0; i < transformed.length; i++) { + if (mergedSet.has(i)) continue; + + let currentMerged = toMergedReplayLabel( + transformed[i].original, + transformed[i].rect, + ); + let currentRight = currentMerged.rect.x + currentMerged.rect.width; + let currentBottom = currentMerged.rect.y + currentMerged.rect.height; + + for (let j = i + 1; j < transformed.length; j++) { + if (mergedSet.has(j)) continue; + + const nextRect = transformed[j].rect; + + // Check if labels are close enough vertically and overlapping horizontally + if ( + Math.abs(nextRect.y - transformed[i].rect.y) < 20 && + nextRect.x <= currentRight + 10 + ) { + // Allow small gap between labels + const nextLabel = toMergedReplayLabel( + transformed[j].original, + nextRect, + ); + currentMerged = mergeTwoReplayLabels(currentMerged, nextLabel); + mergedSet.add(j); + + currentRight = Math.max( + currentRight, + currentMerged.rect.x + currentMerged.rect.width, + ); + currentBottom = Math.max( + currentBottom, + currentMerged.rect.y + currentMerged.rect.height, + ); + } + } + + mergedLabels.push(currentMerged); + } + + return mergedLabels; +}; + +function mergeTwoReplayLabels( + a: MergedReplayOutlineLabel, + b: MergedReplayOutlineLabel, +): MergedReplayOutlineLabel { + const mergedRect = getBoundingRect(a.rect, b.rect); + + const mergedGrouped = a.groupedAggregatedRender.concat( + b.groupedAggregatedRender, + ); + + // const mergedReasons = a.reasons | b.reasons; + + return { + alpha: Math.max(a.alpha, b.alpha), + + ...pickColorClosestToStartStage(a, b), // kinda wrong, should pick color in earliest stage + // reasons: mergedReasons, + groupedAggregatedRender: mergedGrouped, + rect: mergedRect, + }; +} diff --git a/packages/scan/src/core/monitor/types-v2.ts b/packages/scan/src/core/monitor/types-v2.ts new file mode 100644 index 00000000..868c63d5 --- /dev/null +++ b/packages/scan/src/core/monitor/types-v2.ts @@ -0,0 +1,158 @@ +import { type Fiber } from 'react-reconciler'; + +export enum Device { + DESKTOP = 0, + TABLET = 1, + MOBILE = 2, +} + +export interface Session { + id: string; + device: Device; + agent: string; + wifi: string; + cpu: number; + gpu: string | null; + mem: number; + url: string; + route: string | null; + commit: string | null; + branch: string | null; +} + +export interface Interaction { + id: string | number; // index of the interaction in the batch at ingest | server converts to a hashed string from route, type, name, path + path: Array; // the path of the interaction + name: string; // name of interaction + type: string; // type of interaction i.e pointer + time: number; // time of interaction in ms + timestamp: number; + url: string; + route: string | null; // the computed route that handles dynamic params + + // Regression tracking + commit: string | null; + branch: string | null; + + interactionStartAt: number; + interactionEndAt: number; + + componentRenderData: Record< + string, + { + renderCount: number; + parents: Array; + selfTime?: number | null; + // totalTime: number; + } + >; + + // clickhouse + ingest specific types + projectId?: string; + sessionId?: string; + uniqueInteractionId: string; + + meta?: { + performanceEntry: { + id: string; + inputDelay: number; + latency: number; + presentationDelay: number; + processingDuration: number; + processingEnd: number; + processingStart: number; + referrer: string; + startTime: number; + timeOrigin: number; + timeSinceTabInactive: number | 'never-hidden'; + visibilityState: DocumentVisibilityState; + duration: number; + entries: Array<{ + duration: number; + entryType: string; + interactionId: string; + name: string; + processingEnd: number; + processingStart: number; + startTime: number; + }>; + detailedTiming?: { + jsHandlersTime: number; + prePaintTime: number; + paintTime: number; + compositorTime: number; + }; + }; + }; +} + +export interface Component { + interactionId: string | number; // grouping components by interaction + name: string; + renders: number; // how many times it re-rendered / instances (normalized) + instances: number; // instances which will be used to get number of total renders by * by renders + totalTime?: number; + selfTime?: number; +} + +export interface IngestRequest { + interactions: Array; + components: Array; + session: Session; +} + +// used internally in runtime for interaction tracking. converted to Interaction when flushed +export interface InternalInteraction { + performanceEntry: PerformanceInteraction, + route:string, + url:string, + branch: string, + commit: string, +} +interface InternalComponentCollection { + uniqueInteractionId: string; + name: string; + renders: number; // re-renders associated with the set of components in this collection + totalTime?: number; + selfTime?: number; + fibers: Set; // no references will exist to this once array is cleared after flush, so we don't have to worry about memory leaks + retiresAllowed: number; // if our server is down and we can't collect fibers/ user has no network, it will memory leak. We need to only allow a set amount of retries before it gets gcd +} + +export interface PerformanceInteractionEntry extends PerformanceEntry { + interactionId: string; + target: Element; + name: string; + duration: number; + startTime: number; + processingStart: number; + processingEnd: number; + entryType: string; +} +export interface PerformanceInteraction { + id: string; + latency: number; + entries: Array; + target: Element | null; + type: 'pointer' | 'keyboard'; + startTime: number; + endTime: number; + processingStart: number; + processingEnd: number; + duration: number; + inputDelay: number; + processingDuration: number; + presentationDelay: number; + timestamp: number; + timeSinceTabInactive: number | 'never-hidden'; + visibilityState: DocumentVisibilityState; + timeOrigin: number; + referrer: string; + // Detailed timing breakdown + detailedTiming?: { + jsHandlersTime: number; // pointerup -> click + prePaintTime: number; // click -> RAF + paintTime: number; // RAF -> setTimeout + compositorTime: number; // remaining duration + }; +} diff --git a/packages/scan/src/core/monitor/types.ts b/packages/scan/src/core/monitor/types.ts index 95beb7e4..3b59179d 100644 --- a/packages/scan/src/core/monitor/types.ts +++ b/packages/scan/src/core/monitor/types.ts @@ -1,4 +1,5 @@ import { type Fiber } from 'react-reconciler'; +import { CompletedInteraction } from 'src/core/monitor/performance'; export enum Device { DESKTOP = 0, @@ -34,12 +35,56 @@ export interface Interaction { commit: string | null; branch: string | null; + interactionStartAt: number; + interactionEndAt: number; + + componentRenderData: Record< + string, + { + renderCount: number; + parents: Array; + selfTime?: number | null; + // totalTime: number; + } + >; + // clickhouse + ingest specific types projectId?: string; sessionId?: string; uniqueInteractionId: string; - meta?: unknown; + meta?: { + performanceEntry: { + id: string; + inputDelay: number; + latency: number; + presentationDelay: number; + processingDuration: number; + processingEnd: number; + processingStart: number; + referrer: string; + startTime: number; + timeOrigin: number; + timeSinceTabInactive: number | 'never-hidden'; + visibilityState: DocumentVisibilityState; + duration: number; + entries: Array<{ + duration: number; + entryType: string; + interactionId: string; + name: string; + processingEnd: number; + processingStart: number; + startTime: number; + }>; + detailedTiming?: { + jsHandlersTime: number; + prePaintTime: number; + paintTime: number; + compositorTime: number; + }; + }; + }; } export interface Component { @@ -51,14 +96,38 @@ export interface Component { selfTime?: number; } -export interface IngestRequest { - interactions: Array; - components: Array; +export type IngestRequest = ReplaceSetWithArray<{ + interactions: Array; session: Session; -} +}> +export type ReplaceSetWithArray = T extends Set + ? Array + : T extends Array + ? Array> + : T extends { [key: string]: any } + ? { [K in keyof T]: ReplaceSetWithArray } + : T extends object + ? { [K in keyof T]: ReplaceSetWithArray } + : T; // used internally in runtime for interaction tracking. converted to Interaction when flushed export interface InternalInteraction { + componentRenderData: Record< + string, + { + renderCount: number; + parents: Array; + selfTime?: number | null; + // totalTime: number; + } + >; + childrenTree: Record< + string, + { children: Array; firstNamedAncestor: string; isRoot: boolean } + > + + listeningForComponentRenders: boolean; + componentName: string; url: string; route: string | null; @@ -68,6 +137,29 @@ export interface InternalInteraction { componentPath: Array; performanceEntry: PerformanceInteraction; components: Map; + interactionUUID: string; + detailedTiming?: { + // // jsHandlersTime: number; + // clickHandlerEnd: number; + // pointerUpStart: number; + // commmitEnd: number; + // // prepaintLayerizeTimeStart: number; + // clickChangeStart: number; + // prePaintTime: number; + // paintTime: number; + // compositorTime: number; + + clickHandlerMicroTaskEnd: number; + clickChangeStart: number | null; + pointerUpStart: number; + // interactionId: lastInteractionId, + commmitEnd: number; + rafStart: number; + timeorigin: number; + }; + + // interactionStartAt: number + // interactionEndAt: number } interface InternalComponentCollection { uniqueInteractionId: string; @@ -93,9 +185,10 @@ export interface PerformanceInteraction { id: string; latency: number; entries: Array; - target: Element; + target: Element | null; type: 'pointer' | 'keyboard'; startTime: number; + endTime: number; processingStart: number; processingEnd: number; duration: number; @@ -107,4 +200,11 @@ export interface PerformanceInteraction { visibilityState: DocumentVisibilityState; timeOrigin: number; referrer: string; + // Detailed timing breakdown + detailedTiming?: { + jsHandlersTime: number; // pointerup -> click + prePaintTime: number; // click -> RAF + paintTime: number; // RAF -> setTimeout + compositorTime: number; // remaining duration + }; } diff --git a/packages/scan/src/core/web/overlay.ts b/packages/scan/src/core/web/overlay.ts index a1e87707..5c331d83 100644 --- a/packages/scan/src/core/web/overlay.ts +++ b/packages/scan/src/core/web/overlay.ts @@ -1,8 +1,20 @@ import { recalcOutlines } from '@web-utils/outline'; import { outlineWorker } from '@web-utils/outline-worker'; -export const initReactScanOverlay = () => { - const container = document.getElementById('react-scan-root'); +export const initReactScanOverlay = ( + root?: HTMLElement, + customStyle?: { + position?: string; + top?: string; + left?: string; + width?: string; + height?: string; + pointerEvents?: string; + zIndex?: string; + + }, +) => { + const container = root ?? document.getElementById('react-scan-root'); const shadow = container?.shadowRoot; if (!shadow) { @@ -12,13 +24,14 @@ export const initReactScanOverlay = () => { const overlayElement = document.createElement('canvas'); overlayElement.id = 'react-scan-overlay'; - overlayElement.style.position = 'fixed'; - overlayElement.style.top = '0'; - overlayElement.style.left = '0'; - overlayElement.style.width = '100vw'; - overlayElement.style.height = '100vh'; - overlayElement.style.pointerEvents = 'none'; - overlayElement.style.zIndex = '2147483646'; + overlayElement.style.position = customStyle?.position ?? 'fixed'; + overlayElement.style.top = customStyle?.top ?? '0'; + overlayElement.style.left = customStyle?.left ?? '0'; + overlayElement.style.width = customStyle?.width ?? '100vw'; + overlayElement.style.height = customStyle?.height ?? '100vh'; + overlayElement.style.pointerEvents = customStyle?.pointerEvents ?? 'none'; + overlayElement.style.zIndex = customStyle?.zIndex ?? '2147483646'; + overlayElement.setAttribute('aria-hidden', 'true'); shadow.appendChild(overlayElement); diff --git a/packages/scan/src/core/web/utils/outline-worker.ts b/packages/scan/src/core/web/utils/outline-worker.ts index eabf162b..bba60495 100644 --- a/packages/scan/src/core/web/utils/outline-worker.ts +++ b/packages/scan/src/core/web/utils/outline-worker.ts @@ -38,7 +38,7 @@ export type OutlineWorkerAction = }; }; -function setupOutlineWorker(): (action: OutlineWorkerAction) => Promise { +export function setupOutlineWorker(): (action: OutlineWorkerAction) => Promise { const MONO_FONT = 'Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace'; let ctx: OffscreenCanvasRenderingContext2D | undefined; diff --git a/packages/scan/src/core/web/utils/outline.ts b/packages/scan/src/core/web/utils/outline.ts index 11b4a9ea..488b1170 100644 --- a/packages/scan/src/core/web/utils/outline.ts +++ b/packages/scan/src/core/web/utils/outline.ts @@ -632,7 +632,7 @@ function mergeTwoLabels( }; } -function getBoundingRect(r1: DOMRect, r2: DOMRect): DOMRect { +export function getBoundingRect(r1: DOMRect, r2: DOMRect): DOMRect { const x1 = Math.min(r1.x, r2.x); const y1 = Math.min(r1.y, r2.y); const x2 = Math.max(r1.x + r1.width, r2.x + r2.width); @@ -640,9 +640,9 @@ function getBoundingRect(r1: DOMRect, r2: DOMRect): DOMRect { return new DOMRect(x1, y1, x2 - x1, y2 - y1); } -function pickColorClosestToStartStage( - a: MergedOutlineLabel, - b: MergedOutlineLabel, +export function pickColorClosestToStartStage( + a: {color: MergedOutlineLabel['color']}, + b: {color: MergedOutlineLabel['color']}, ) { // stupid hack to always take the gray value when the render is unnecessary (we know the gray value has equal rgb) if (a.color.r === a.color.g && a.color.g === a.color.b) { @@ -655,7 +655,7 @@ function pickColorClosestToStartStage( return { color: a.color.r <= b.color.r ? a.color : b.color }; } -function getOverlapArea(rect1: DOMRect, rect2: DOMRect): number { +export function getOverlapArea(rect1: DOMRect, rect2: DOMRect): number { if (rect1.right <= rect2.left || rect2.right <= rect1.left) { return 0; } @@ -672,7 +672,7 @@ function getOverlapArea(rect1: DOMRect, rect2: DOMRect): number { return xOverlap * yOverlap; } -function applyLabelTransform( +export function applyLabelTransform( rect: DOMRect, estimatedTextWidth: number, ): DOMRect { diff --git a/packages/scan/src/index.ts b/packages/scan/src/index.ts index 44916ab8..ad3f995e 100644 --- a/packages/scan/src/index.ts +++ b/packages/scan/src/index.ts @@ -1,3 +1,5 @@ import 'bippy'; // implicit init RDT hook export * from './core/index'; + +export { ReactScanReplayPlugin } from './core//monitor/session-replay/replay-v2'; diff --git a/packages/scan/test-video.mp4 b/packages/scan/test-video.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..d8591aa7bfad5c375ac081e074cfb3be9661dc6a GIT binary patch literal 227081 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 z ztRk@=0DwhAO9Qw5=c^^IcpFq;Au-5R_yhfJBlybGJ`5{Egrj_|3PhS zYXD$mKS0EegoLn&!)b%H7XVYR42}P)FOJe5z8d%|r%i%0cYzFmO_wFCx6=(k6jjIn z%wn`#&F_$p1#PZU-Tmz{wmrI!9L@p4vudDyq{q3+|3u);{HGaegD+~H|4?m zV3;NTYjidy5Q7QTyi%H8)YPRy5=zbHJj7BtV!WgA$y`hYq0rw`(t1a4eSPB9-QYFp zc?0A>NHDI^3P_wEAO&E3Ux0MnFde`&>1F;lv{ub4#ba7Xckhx02DJ){b}HQIyfc%D z&dLe7Gcj=2Sxbik!%3Q41h9>hgBNV1C}L%Pwzb#42NTB{$FyazC;uyCdB$R zOq6{hf{J1p>W!IlATpDja-ayhLfi>w4+AXGYZw_Sd#>GxYhBjXvwF-Yq9gga8Y0c0 zaeM$f^Z?)_R|M-vyZ~t40SK^b09#qeDOpHKM`$hChTjZ6PLgK|FfXzI%z#Zs0s^3hZE{oP0A9$GZTpPL!Ck~z zvhBC#2y_~wI9?hlpCprG{R+~X#Q$nuED;g_!l;4b1XcriBU=AGP9CLH%zz|N+Q8<6lbsZ9Y*FHOqt_GZS zT}@Wep56b1KP5})7haHvN?ff0@dBRV9_)7T!CTzr80dz16fUhpfM0$$H0D>gLw+ok zbS0y4RN*GbkrSpU*U(wgiVLZJ1rnPgf4==yKl@g)?3G8@!(>io`Z$d}KakW`lf~(@ zjY=2tl{$s+yK}72G4nCsrMuC26FTDA@#Tys|4kS zogY654tM#!u)O&Fd0}R7FuEfKe>q8aCDehN{$-~~fyLDdO}kjU@6QW734`!oaUOZ6 zk0xC`CI%nmfXRU&TA@dy6pg=%)~ggV9$rxTbLbr2>J!R6HomlU*5Bvd=n5(@IQaeG z@5xQ75}r2>cd}zFMx5w%tF%k@&!0KT)E#a_MDa(Ow=Gyd%5kkIU085Xc@|1tVK68? z758aVx2NGbc*(fj)q+>-$sY{`?@Q$N6<$f4C>?+8N%%32p1k^g-YFOig01D1I!FDw zr{U!yB*;+>TR!BUg|&IwMa0u<4tQ#qOGy}UTLptP4jZ?W`C?e8zMCxm6~eOJ`d|y6 zH?QqElFw5iO-1x|z-jlg%HPS;iS@ff&HMW|^{`UR2|DGQeT@0c+s~I(JNhiLf?NN~ zQdz(7Pa6wW>V7)qk3DT*`)<)byLd7tNLl*skJVvI9M68r5tky60&7zH%1&=4e44>@ z5H9G4NZLJ!3Tqf9iBhVUFKFCmo&Gj~y1%TZ_(MhKdb4wsRO@#CSuX9D z$a>#jq5V>=mx(QpOjGlw+ynw|dZ>6?(zV7Ca@bvAk;P9uK7TYT&|E$*iK~;r%{EMT zTYn2bSG1oi#ZuJsk zko>Ohk0$>9KvaBGVKX@CJ|gLUO@5qWrSNR?q=CT~|1`Oo0+zXl=4Xb-iou9Y)Ut?< zp?=c^I#7q

>wAX)~WuInYwVkgp(nfQ*sg5E!y% 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 literal 0 HcmV?d00001 diff --git a/packages/scan/tsup.config.ts b/packages/scan/tsup.config.ts index 9cffa0df..8929318e 100644 --- a/packages/scan/tsup.config.ts +++ b/packages/scan/tsup.config.ts @@ -85,7 +85,12 @@ void (async () => { export default defineConfig([ { - entry: ['./src/auto.ts', './src/install-hook.ts', './src/auto-monitor.ts'], + entry: [ + './src/auto.ts', + './src/install-hook.ts', + './src/auto-monitor.ts', + './src/index.ts', + ], outDir: DIST_PATH, banner: { js: banner, @@ -94,11 +99,13 @@ export default defineConfig([ clean: false, sourcemap: false, format: ['iife'], - target: 'esnext', + target: 'es2017', platform: 'browser', treeshake: true, dts: true, - minify: process.env.NODE_ENV === 'production' ? 'terser' : false, + globalName: 'ReactScan', + // minify: process.env.NODE_ENV === 'production' ? 'terser' : false, + minify: false, env: { NODE_ENV: process.env.NODE_ENV ?? 'development', @@ -143,6 +150,7 @@ export default defineConfig([ treeshake: false, dts: true, watch: process.env.NODE_ENV === 'development', + noExternal: ['rrweb'], async onSuccess() { await Promise.all([ addDirectivesToChunkFiles(DIST_PATH), diff --git a/packages/website/app/api/waitlist/route.ts b/packages/website/app/api/waitlist/route.ts index 6895d86a..49e69e3f 100644 --- a/packages/website/app/api/waitlist/route.ts +++ b/packages/website/app/api/waitlist/route.ts @@ -1,5 +1,5 @@ -import { NextResponse } from 'next/server'; -import { z } from 'zod'; +import { NextResponse } from "next/server"; +import { z } from "zod"; const schema = z.object({ email: z.string().email(), @@ -22,36 +22,40 @@ export async function POST(request: Request) { const body = await request.json(); const { email, name } = schema.parse(body); const options = { - method: 'POST', + method: "POST", headers: { Authorization: `Bearer ${process.env.LOOPS_API_KEY}`, - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, body: JSON.stringify({ email, - firstName: name?.split(' ')[0], - lastName: name?.split(' ')[1], - source: 'monitoring waitlist', + firstName: name?.split(" ")[0], + lastName: name?.split(" ")[1], + source: "monitoring waitlist", }), }; const response = await fetch( - 'https://app.loops.so/api/v1/contacts/create', - options, + "https://app.loops.so/api/v1/contacts/create", + options ); const data = await response.json(); if (!data.success) { + console.log("error", data); + return NextResponse.json({ error: data.message }, { status: 500 }); } return NextResponse.json({ ok: true }); } catch (error) { + console.log("the error", error); + if (error instanceof z.ZodError) { return NextResponse.json({ error: error.message }, { status: 400 }); } return NextResponse.json( - { error: 'Failed to add to waitlist' }, - { status: 500 }, + { error: "Failed to add to waitlist" }, + { status: 500 } ); } } diff --git a/packages/website/app/globals.css b/packages/website/app/globals.css index b5c61c95..ce807a10 100644 --- a/packages/website/app/globals.css +++ b/packages/website/app/globals.css @@ -1,3 +1,111 @@ @tailwind base; @tailwind components; @tailwind utilities; + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes fadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +@keyframes modalIn { + 0% { + opacity: 0; + transform: scale(0.98) translateY(4px); + } + 100% { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +@keyframes modalOut { + 0% { + opacity: 1; + transform: scale(1); + } + 100% { + opacity: 0; + transform: scale(0.98); + } +} + +.animate-fade-in { + animation: fadeIn 0.15s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.animate-fade-out { + animation: fadeOut 0.15s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.animate-modal-in { + animation: modalIn 0.25s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.animate-modal-out { + animation: modalOut 0.2s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +/* Custom Video Player Styles */ +.custom-video-player { + --video-progress-color: #4B4DB3; +} + +.custom-video-player video::-webkit-media-controls-panel { + display: none !important; +} + +.custom-video-player video::-webkit-media-controls { + display: none !important; +} + +.custom-video-player .progress-bar { + @apply absolute bottom-0 left-0 right-0 h-1.5 bg-white/10 cursor-pointer transition-all duration-300; + transform-origin: bottom; + user-select: none; +} + +.custom-video-player:hover .progress-bar { + @apply h-2.5; +} + +.custom-video-player .progress-fill { + @apply h-full bg-[var(--video-progress-color)] origin-left transition-transform duration-100; + position: relative; +} + +.custom-video-player .progress-fill::after { + content: ''; + position: absolute; + right: -4px; + top: 50%; + transform: translateY(-50%); + width: 8px; + height: 8px; + border-radius: 50%; + background-color: var(--video-progress-color); + opacity: 0; + transition: opacity 0.2s; +} + +.custom-video-player:hover .progress-fill::after { + opacity: 1; +} + +.custom-video-player .progress-bar:active .progress-fill::after { + width: 12px; + height: 12px; + opacity: 1; +} diff --git a/packages/website/app/layout.tsx b/packages/website/app/layout.tsx index 85c53b3c..c1ff6917 100644 --- a/packages/website/app/layout.tsx +++ b/packages/website/app/layout.tsx @@ -1,25 +1,25 @@ -import './globals.css'; -import localFont from 'next/font/local'; -import Script from 'next/script'; -import { Analytics } from '@vercel/analytics/next'; -import { SpeedInsights } from '@vercel/speed-insights/next'; -import Header from '../components/header'; -import Footer from '../components/footer'; +import "./globals.css"; +import localFont from "next/font/local"; +import Script from "next/script"; +import { Analytics } from "@vercel/analytics/next"; +import { SpeedInsights } from "@vercel/speed-insights/next"; +import Header from "../components/header"; +import Footer from "../components/footer"; const geistSans = localFont({ - src: './fonts/GeistVF.woff', - variable: '--font-geist-sans', - weight: '100 900', + src: "./fonts/GeistVF.woff", + variable: "--font-geist-sans", + weight: "100 900", }); const geistMono = localFont({ - src: './fonts/GeistMonoVF.woff', - variable: '--font-geist-mono', - weight: '100 900', + src: "./fonts/GeistMonoVF.woff", + variable: "--font-geist-mono", + weight: "100 900", }); export const metadata = { - title: 'React Scan', - description: 'scan ur app', + title: "React Scan", + description: "scan ur app", }; export default function RootLayout({ @@ -77,7 +77,7 @@ export default function RootLayout({ -

+
{children}
diff --git a/packages/website/components/header.tsx b/packages/website/components/header.tsx index 94dc7459..457df300 100644 --- a/packages/website/components/header.tsx +++ b/packages/website/components/header.tsx @@ -1,10 +1,54 @@ -import React from 'react'; -import Link from 'next/link'; -import Image from 'next/image'; +"use client"; +import React, { useState, useEffect, useRef } from "react"; +import Link from "next/link"; +import Image from "next/image"; +import { usePathname } from "next/navigation"; export default function Header() { + const [isMenuOpen, setIsMenuOpen] = useState(false); + const pathname = usePathname(); + const menuRef = useRef(null); + const buttonRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + isMenuOpen && + menuRef.current && + buttonRef.current && + !menuRef.current.contains(event.target as Node) && + !buttonRef.current.contains(event.target as Node) + ) { + setIsMenuOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isMenuOpen]); + + const isActive = (path: string) => { + return pathname === path; + }; + + const linkClass = (path: string, isMobile = false) => { + const baseClass = isMobile + ? "block px-4 py-2 hover:bg-gray-100" + : "underline hover:text-black"; + const activeClass = isMobile + ? "bg-[#4B4DB3]/5 text-[#4B4DB3]" + : "text-[#4B4DB3]"; + const inactiveClass = isMobile + ? "text-neutral-600" + : "text-neutral-600"; + + return `${baseClass} ${isActive(path) ? activeClass : inactiveClass}`; + }; + return ( -

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