Skip to content

feat: detect URL changes #1192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

feat: detect URL changes #1192

wants to merge 5 commits into from

Conversation

bravecod
Copy link
Collaborator

@bravecod bravecod commented Jul 4, 2025

Summary

URL Tracking Feature Changes

Overview

This document outlines the comprehensive changes made to the URL tracking feature in the session-replay-browser package. The changes include converting dynamic imports to static imports, developing comprehensive test coverage, refactoring code for better maintainability, and adding detailed documentation.

Table of Contents


1. Dynamic Import to Static Import Conversion

Changes Made

File: packages/session-replay-browser/src/session-replay.ts

Before

const { URLTracker } = await import('./observers');

After

import { URLTracker } from './observers';

Implementation Details

  • Removed async keyword from initializeUrlTracking method
  • Eliminated await import('./observers') dynamic import
  • Updated method calls to remove void wrapper since method is no longer async
  • Fixed shutdown tests that were failing due to additional event listener removal

Benefits

  • Improved startup performance - No runtime module loading delay
  • Better bundle optimization - Static analysis enables better tree shaking
  • Simplified code flow - Synchronous initialization reduces complexity
  • Enhanced type safety - Static imports provide better TypeScript support

2. Comprehensive Test Development

New Test File Created

File: packages/session-replay-browser/test/url-tracker.test.ts

Test Coverage Categories

Constructor Tests

  • Default options initialization
  • Custom options with UGC rules and polling

Lifecycle Management

  • Start/stop tracking functionality
  • Prevention of double initialization
  • Graceful handling of missing global scope
  • Event listener cleanup verification

URL Change Detection

  • pushState navigation detection
  • replaceState navigation detection
  • popstate event handling (back/forward navigation)
  • Duplicate URL change prevention

UGC (User Generated Content) Filtering

  • Application of filtering rules when configured
  • Bypassing filtering when no rules exist
  • Integration with getPageUrl helper function

Polling Mechanism

  • Setup when enabled in configuration
  • Disabled state verification
  • Interval cleanup on stop
  • Callback execution during polling

Configuration Updates

  • Dynamic UGC filter rule updates
  • Verification of updated rules application

Edge Case Handling

  • Missing document title scenarios
  • Undefined global scope conditions
  • Missing location in global scope
  • Callback handling when tracker is stopped
  • Undefined document in global scope
  • Null original methods in history patches

Integration with Session Replay

File: packages/session-replay-browser/test/session-replay.test.ts

Added comprehensive integration tests:

  • URL tracking initialization during SessionReplay init
  • Configuration passing to URLTracker
  • Event listener teardown including URLTracker cleanup
  • Custom RRWeb event handling for URL changes
  • Various configuration scenarios

3. Code Refactoring

URLTracker Implementation Refactoring

File: packages/session-replay-browser/src/observers/url-tracker.ts

Key Improvements

Constants Extraction
const POLLING_INTERVAL_MS = 1000; // Eliminates magic numbers
Type Safety Enhancements
type GlobalScope = NonNullable<ReturnType<typeof getGlobalScope>>;
Method Decomposition

Before: Large monolithic methods
After: Focused, single-responsibility methods

Setup Method Breakdown
  • setupUrlTracking() → Main orchestration
  • storeOriginalHistoryMethods() → Method storage
  • patchHistoryMethods() → History API patching
  • setupEventListeners() → Event listener setup
  • setupPolling() → Polling configuration
Teardown Method Breakdown
  • teardownUrlTracking() → Main orchestration
  • restoreOriginalHistoryMethods() → Method restoration
  • removeEventListeners() → Event cleanup
  • clearPolling() → Polling cleanup
  • resetState() → State reset
Code Duplication Elimination

Generic History Method Patching:

private createHistoryMethodPatch<T extends typeof history.pushState | typeof history.replaceState>(
  originalMethod: T | null
) {
  const emitUrlChange = this.emitUrlChange;
  return function (this: History, ...args: Parameters<T>) {
    const result = originalMethod?.apply(this, args);
    emitUrlChange();
    return result;
  };
}
Helper Method Addition
private shouldApplyUgcFiltering(): boolean {
  return this.ugcFilterRules.length > 0;
}

Benefits of Refactoring

  • Improved maintainability - Smaller, focused methods
  • Enhanced readability - Clear separation of concerns
  • Better testability - Individual methods can be tested in isolation
  • Reduced duplication - Generic patching method eliminates code repetition
  • Type safety - Eliminated unsafe globalThis usage

Test Refactoring

File: packages/session-replay-browser/test/url-tracker.test.ts

Test Helper Functions Created

// Test data factories
const createMockUgcFilterRules = () => [/* ... */];
const createMockGlobalScope = (overrides = {}) => ({ /* ... */ });

// Test utility functions  
const setUrlAndTitle = (url, title) => { /* ... */ };
const startTrackingAndClearCallback = () => { /* ... */ };
const mockGlobalScopeReturn = (scope) => { /* ... */ };
const getPopstateListener = () => { /* ... */ };

Benefits of Test Refactoring

  • Reduced code duplication - Helper functions eliminate repetitive patterns
  • Improved readability - Tests focus on business logic rather than setup
  • Easier maintenance - Changes to test patterns only need updates in one place
  • Consistent patterns - Standardized approach across all tests

4. Documentation Enhancements

Comprehensive Code Documentation

File: packages/session-replay-browser/src/observers/url-tracker.ts

Documentation Added

Class-Level Documentation
/**
 * URLTracker monitors URL changes in single-page applications by:
 * 1. Patching browser history methods (pushState, replaceState)
 * 2. Listening for popstate events (back/forward navigation)  
 * 3. Optional polling as a fallback for edge cases
 * 
 * Features:
 * - Deduplication of identical URL changes
 * - UGC (User Generated Content) filtering for sensitive URLs
 * - Graceful handling of missing browser APIs
 * - Clean teardown with method restoration
 */
Method Documentation
  • JSDoc comments for all public and private methods
  • Parameter descriptions with types and purposes
  • Return value documentation where applicable
  • Implementation rationale explanations
Property Organization
  • Grouped related properties with explanatory comments
  • Explained purpose of each property group
  • Clarified state management logic
Inline Comments
  • Early return explanations
  • Sequence of operations in complex methods
  • Browser behavior explanations
  • Edge case handling rationale

Documentation Benefits

  • Faster developer onboarding - New team members understand code quickly
  • Easier maintenance - Implementation decisions are documented
  • Better debugging - Edge cases and error handling are explained
  • Reduced cognitive load - Developers don't need to reverse-engineer logic

5. Performance and Maintainability Benefits

Performance Improvements

Static Import Benefits

  • Faster startup time - No runtime module resolution
  • Better bundle optimization - Static analysis enables tree shaking
  • Reduced memory overhead - No dynamic import machinery

Refactoring Benefits

  • Smaller method complexity - Easier for JavaScript engines to optimize
  • Better type inference - TypeScript can optimize more effectively
  • Reduced object creation - Reusable generic method patterns

Maintainability Improvements

Code Structure

  • Single Responsibility Principle - Each method has one clear purpose
  • DRY Principle - Code duplication eliminated through generic methods
  • Clear Separation of Concerns - Setup, operation, and teardown are distinct

Testing

  • 100% Code Coverage - All branches, statements, functions, and lines covered
  • Comprehensive Edge Cases - Handles various browser environment scenarios
  • Maintainable Test Suite - Helper functions reduce duplication and improve clarity

Documentation

  • Self-Documenting Code - Clear method names and comprehensive comments
  • Implementation Rationale - Why decisions were made, not just what was done
  • API Documentation - Clear parameter and return value descriptions

6. Testing Summary

Coverage Metrics

Final Coverage Results:

  • Statements: 100% (98/98)
  • Branches: 100% (36/36)
  • Functions: 100% (20/20)
  • Lines: 100% (91/91)

Test Structure

URL Tracker Tests (url-tracker.test.ts)

  • 24 comprehensive tests covering all functionality
  • Organized test suites by feature area
  • Edge case coverage for error conditions
  • Integration verification with helper utilities

Session Replay Integration Tests (session-replay.test.ts)

  • URL tracking initialization during SessionReplay setup
  • Configuration propagation testing
  • Event listener management verification
  • Custom RRWeb event handling validation

Test Quality Improvements

Before Optimization

  • 25 tests with some redundancy
  • 97.22% branch coverage (35/36 branches)
  • Overlapping test scenarios

After Optimization

  • 24 focused tests with eliminated redundancy
  • 100% branch coverage (36/36 branches)
  • Streamlined test scenarios without coverage loss

Edge Cases Covered

  1. Browser API Availability

    • Missing global scope
    • Undefined location object
    • Missing document object
  2. Method State Management

    • Null original history methods
    • Undefined callback scenarios
    • Double initialization prevention
  3. URL Change Scenarios

    • Duplicate URL prevention
    • Missing document titles
    • Various navigation methods
  4. Configuration Edge Cases

    • Empty UGC filter rules
    • Missing interaction config
    • Dynamic configuration updates

Summary

The URL tracking feature has undergone comprehensive improvements across multiple dimensions:

Code Quality

  • Converted from dynamic to static imports
  • Comprehensive refactoring with modern patterns
  • 100% test coverage with optimized test suite
  • Extensive documentation for maintainability

Performance

  • Faster initialization with static imports
  • Better bundle optimization opportunities
  • Reduced runtime complexity

Maintainability

  • Clear separation of concerns
  • Comprehensive documentation
  • Robust error handling
  • Extensive test coverage

Developer Experience

  • Well-documented API surface
  • Clear implementation patterns
  • Comprehensive test examples
  • Easy-to-understand code structure

These changes establish a solid foundation for the URL tracking feature that is performant, maintainable, and thoroughly tested.

Checklist

  • Does your PR title have the correct title format?
  • Does your PR have a breaking change?:

@bravecod bravecod marked this pull request as ready for review July 8, 2025 16:42
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: URL Tracker Initialization Overwrites Without Cleanup

The initializeUrlTracking method creates a new URLTracker instance without stopping the existing one. Repeated calls to init() or teardownEventListeners(false) result in multiple URLTracker instances running simultaneously, leading to duplicate URL change events, memory leaks, and potential incorrect behavior. The method should stop the existing URLTracker before creating a new instance.

packages/session-replay-browser/src/session-replay.ts#L112-L126

private initializeUrlTracking = () => {
if (!this.config) return;
// Initialize URLTracker with configuration
this.urlTracker = new URLTracker({
ugcFilterRules: this.config.interactionConfig?.ugcFilterRules || [],
enablePolling: this.config.enableUrlChangePolling || false,
});
// Start tracking and handle URL changes
this.urlTracker.start((event) => {
void this.addCustomRRWebEvent(CustomRRwebEvent.URL_CHANGE, event);
});
};

Fix in CursorFix in Web


Comment bugbot run to trigger another review on this PR
Was this report helpful? Give feedback by reacting with 👍 or 👎

Copy link

promptless bot commented Jul 8, 2025

📝 Documentation updates detected!

New suggestion: Document URL change tracking for Session Replay

Comment on lines 34 to +35
METADATA = 'metadata',
URL_CHANGE = 'url-change',
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reuse this metadata?

* Emit a URL change event if the URL has actually changed
* Uses arrow function to preserve 'this' context when called from patched methods
*/
private emitUrlChange = (): void => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we emit the screen size? (viewport)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Collaborator

@lewgordon-amplitude lewgordon-amplitude left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, especially since we chatted through most of this. I just would want to add make sure things are changeable in config before we ship it.

export type URLChangeCallback = (event: URLChangeEvent) => void;

// Constants
const POLLING_INTERVAL_MS = 1000; // Check for URL changes every second when polling is enabled
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a config instead?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants