All files UrlObserver.ts

62.03% Statements 67/108
100% Branches 2/2
28.57% Functions 2/7
62.03% Lines 67/108

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 1091x 1x 1x 1x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x           5x 5x 5x 5x 5x 5x 5x 5x 5x 5x               5x 5x 5x 5x 5x 5x 5x 5x 5x                       5x 5x 5x 5x 5x 5x 5x 5x                           5x 5x 5x 5x 5x 5x 5x 5x           5x  
import { UiWindow } from "../types/wrapper"
 
/**
 * MutationObserver for observing URL changes.
 */
export class UrlObserver {
  private eventListeners: {
    [event: string]: ((...args: unknown[]) => void)[]
  } = {}
  private mutationObserver: MutationObserver | null
  private prevPathname: string
 
  private uiWindow: UiWindow
 
  /**
   * Constructor for UrlObserver.
   */
  constructor(uiWindow: UiWindow) {
    this.uiWindow = uiWindow
    this.prevPathname = this.uiWindow.getWindow().location.pathname
    this.mutationObserver = null
  }
 
  /**
   * Attach an event listener for the specified event.
   *
   * @param {string} event - The event name.
   * @param {Function} listener - The event listener function.
   * @returns {void}
   */
  on(event: string, listener: (...args: unknown[]) => void): void {
    if (!this.eventListeners[event]) {
      this.eventListeners[event] = []
    }
    this.eventListeners[event].push(listener)
  }
 
  /**
   * Emit an event to notify listeners.
   *
   * @private
   * @param {string} event - The event name.
   * @param {any[]} args - Arguments to pass to the listeners.
   * @returns {void}
   */
  private emit(event: string, ...args: unknown[]): void {
    const listeners = this.eventListeners[event]
    if (listeners) {
      for (const listener of listeners) {
        listener(...args)
      }
    }
  }
 
  /**
   * Handle a mutation event.
   *
   * @private
   * @param {MutationRecord[]} mutationsList - A list of MutationRecord objects.
   * @returns {void}
   */
  private handleMutation(mutationsList: MutationRecord[]): void {
    for (const mutation of mutationsList) {
      if (mutation.type !== "childList" && mutation.type !== "attributes")
        continue

      const currentPathname = this.uiWindow.getWindow().location.pathname
      if (currentPathname === this.prevPathname) continue
      this.prevPathname = currentPathname

      this.emit("url-mutation")
    }
  }
 
  /**
   * Start observing URL changes.
   *
   * @public
   * @returns {void}
   */
  public startObserving(): void {
    if (this.mutationObserver) return

    this.mutationObserver = new MutationObserver(
      (mutationsList: MutationRecord[]) => this.handleMutation(mutationsList)
    )

    this.mutationObserver.observe(this.uiWindow.getDocument().documentElement, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ["href"]
    })
  }
 
  /**
   * Stop observing URL changes.
   *
   * @public
   * @returns {void}
   */
  public stopObserving(): void {
    if (!this.mutationObserver) return

    this.mutationObserver.disconnect()
    this.mutationObserver = null
  }
}