diff --git a/src/content/index.ts b/src/content/index.ts index 36da927..fe59bc7 100644 --- a/src/content/index.ts +++ b/src/content/index.ts @@ -23,6 +23,13 @@ let rrwebEvents: EventWithTime[] = []; // Буфер пользовательских событий let userEvents: UserEvent[] = []; +// Plan 04-01 P1 #14 — module-level previousUrl tracker. Initialized at module +// load (content-script-realm `window` is always present in production; the +// typeof guard keeps node-env unit tests importable). Mutated in +// `handleNavigation` so meta.previousUrl carries the prior URL instead of +// the stale `history.state?.url || 'unknown'` shape the original code used. +let previousUrl: string = (typeof window !== 'undefined') ? window.location.href : ''; + // Очистка старых событий function cleanupOldEvents() { const now = Date.now(); @@ -97,14 +104,23 @@ function setupInputLogging() { // Логирование навигации function setupNavigationLogging() { + // Plan 04-01 P1 #14 — read module-level `previousUrl` (initialized at module + // load) into `fromUrl`, then mutate before the addUserEvent emit so the + // next navigation captures THIS URL as its previousUrl. The original code + // read history.state?.url which is `null` in apps that don't populate it, + // leaking the literal string 'unknown' into every meta.previousUrl field + // of saved archives. const handleNavigation = () => { + const fromUrl = previousUrl; + const toUrl = window.location.href; + previousUrl = toUrl; addUserEvent({ timestamp: Date.now(), type: 'navigation', target: 'window', - value: window.location.href, - url: window.location.href, - meta: { previousUrl: history.state?.url || 'unknown' } + value: toUrl, + url: toUrl, + meta: { previousUrl: fromUrl }, }); }; @@ -171,7 +187,11 @@ function setupNetworkLogging() { addUserEvent({ timestamp: Date.now(), type: 'network_error', - target: args[0]?.toString() || 'unknown', + // Plan 04-01 P1 #11 — the original implicit-coercion call + // resolved to '[object Request]' when the page invoked + // fetch(new Request(url)). Type-narrowing on Request lets us + // pull the canonical .url instead. + target: (args[0] instanceof Request ? args[0].url : String(args[0])) || 'unknown', value: `HTTP ${response.status} ${response.statusText}`, url: window.location.href, meta: { @@ -187,7 +207,11 @@ function setupNetworkLogging() { addUserEvent({ timestamp: Date.now(), type: 'network_error', - target: args[0]?.toString() || 'unknown', + // Plan 04-01 P1 #11 — twin of the ok-branch narrow above. Both + // sites carry the same `instanceof Request` discipline so the + // failing URL surfaces verbatim in events.json regardless of + // whether the failure was an HTTP non-2xx OR a network throw. + target: (args[0] instanceof Request ? args[0].url : String(args[0])) || 'unknown', value: error.message, url: window.location.href, meta: { @@ -285,6 +309,13 @@ function getSelector(element: HTMLElement): string { function initRrweb() { record({ emit(event) { + // Plan 04-01 P1 #15 — normalize to Unix epoch for events.json + // downstream + cleanupOldEvents arithmetic correctness. rrweb's + // emit-side timestamps are page-load-relative small ints; without + // this normalization `(now - event.timestamp) < RRWEB_RETENTION_MS` + // at line ~33 becomes a category error (Date.now() - 42 is always + // >> 10 min). + event.timestamp = Date.now(); rrwebEvents.push(event); }, maskInputOptions: {