extension/Shared (Extension)/Resources/ContentHandler.js

Summary

Maintainability
A
0 mins
Test Coverage
class RR0Data {
  /**
   * @type string
   */
  title

  /**
   * @type string
   */
  url
}

export class ContentHandler {
  /**
   * @readonly
   * @type {string}
   */
  #baseUrl = "https://rr0.org"

  /**
   * @readonly
   * @type {string}
   */
  #casesDirsUrl = "casesDirs.json"

  /**
   * @readonly
   * @type {string}
   */
  #peopleDirsUrl = "peopleDirs.json"

  /**
   * @type ContentHandler
   */
  static instance

  /**
   *
   * @param {URL} url
   * @param {string} suffix
   * @template T
   * @return {Promise<T[]>}
   */
  async fetchArray (url, suffix) {
    const casesJson = await this.fetchJson(url)
    return casesJson.map(dir => dir + suffix)
  }

  /**
   * @param {URL} url
   * @template T
   * @return {Promise<T>}
   */
  async fetchJson (url) {
    const casesResponse = await fetch(url, { headers: { "accept": "application/json" } })
    return await casesResponse.json()
  }

  /**
   * @param {string} caseUrl
   * @return Promise<RR0Data>
   */
  async fetchCase (caseUrl) {
    /** @type RR0Data */
    const rr0Case = await this.fetchJson(new URL(caseUrl, this.#baseUrl))
    const caseFile = "/case.json"
    rr0Case.url = new URL(caseUrl.replace(caseFile, "/index.html"), this.#baseUrl)
    if (!navigator.language.startsWith("fr") || !rr0Case.title) {
      rr0Case.type = "case"
      let titleFromUrl = caseUrl.substring(0, caseUrl.length - caseFile.length)
      titleFromUrl = titleFromUrl.substring(titleFromUrl.lastIndexOf("/") + 1)
      rr0Case.title = titleFromUrl.replaceAll(/([a-z0-9])([A-Z0-9])/g, "$1 $2").trim()
    }
    return rr0Case
  }

  /**
   * @param {string} peopleUrl
   * @return Promise<RR0Data>
   */
  async fetchPeople (peopleUrl) {
    /** @type RR0Data */
    let people
    try {
      /** @type RR0Data */
      people = await this.fetchJson(new URL(peopleUrl, this.#baseUrl))
    } catch (e) {
      people = {}
    }
    const peopleFile = "/people.json"
    if (!people.title) {
      people.type = "people"
      let titleFromUrl = peopleUrl.substring(0, peopleUrl.length - peopleFile.length)
      titleFromUrl = titleFromUrl.substring(titleFromUrl.lastIndexOf("/") + 1)
      people.title = titleFromUrl.replaceAll(/([a-z0-9])([A-Z0-9])/g, "$1 $2").trim()
    }
    people.url = new URL(peopleUrl.replace(peopleFile, "/index.html"), this.#baseUrl)
    return people
  }

  /**
   *
   * @type {RR0Data[]}
   */
  #rr0Data = []
  /**
   * @type {Map<RR0Data, number>}
   */
  #occurrences = new Map()

  /**
   * @return {Map<RR0Data, number>}
   */
  get occurences () {
    return this.#occurrences
  }

  async init () {
    // await this.readCasesData()
    // await this.readPeopleData()
    this.#rr0Data = await this.fetchJson(new URL("search/index.json", this.#baseUrl))
    console.debug(this.#rr0Data)
  }

  async readPeopleData () {
    const peopleFiles = /** @type {string[]} */ await this.fetchArray(new URL(this.#peopleDirsUrl, this.#baseUrl), "/people.json")
    for (const peopleHref of peopleFiles) {
      const people = await this.fetchPeople(peopleHref)
      this.#rr0Data.push(people)
    }
  }

  async readCasesData () {
    const casesFiles = /** @type {string[]} */ await this.fetchArray(new URL(this.#casesDirsUrl, this.#baseUrl), "/case.json")
    for (const casesHref of casesFiles) {
      const rr0Case = await this.fetchCase(casesHref)
      this.#rr0Data.push(rr0Case)
    }
  }

  get data () {
    return this.#rr0Data
  }

  /**
   * @return {Promise<ContentHandler>}
   */
  static async getInstance () {
    if (!ContentHandler.instance) {
      ContentHandler.instance = new ContentHandler()
      await ContentHandler.instance.init()
    }
    return ContentHandler.instance
  }

  #logDebug (...msg) {
    return console.debug(this.constructor.name, ...msg)
  }

  /**
   *
   * @param {number} tabId
   * @param {string} title
   * @return {Promise<number>} The count of the title.
   */
  async getTextCount (tabId, title) {
    // this.#logDebug("getTextCount", tabId, title)
    const getTextMsg = { type: "getText", title }
    console.debug("sends to tab", tabId, getTextMsg)
    //  const response = await browser.tabs.sendMessage(tabId, getTextMsg)
    return response.count
  }

  /**
   *
   * @param {number} tabId
   * @return {Map<RR0Data, number>}
   */
  async scanOccurrences (tabId) {
    this.#logDebug("scanOccurrences", tabId)
    let total = 0
    this.#occurrences.clear()
    for (const datum of this.#rr0Data.pages) {
      const title = datum.title
      const titleCount = await this.getTextCount(tabId, title)
      this.#occurrences.set(datum, titleCount)
      total += titleCount
    }
    browser.action.setBadgeText({ text: total ? String(total) : "" })
    return this.#occurrences
  }

  /**
   * @template T
   * @param {URL} url
   * @param {RequestInit} [init]
   * @return {Promise<T>}
   */
  async archiveFetch (url, init) {
    const archiveUrl = new URL("http://archive.org/wayback/available?url=" + url)
    const archiveInit = { headers: { "content-type": MimeType.json } }
    const archiveJson = await fetch(archiveUrl, archiveInit)
    const closest = archiveJson.archived_snapshots["closest"]
    if (!closest) {
      throw new Error("Link is not archived")
    }
    const archivedUrl = new URL(closest.url)
    const result = await fetch(archivedUrl, init)
    const timestamp = closest.timestamp
    const archiveDate = new Date(timestamp.substring(0, 4) + "-" + timestamp.substring(4, 6) + "-" + timestamp.substring(6, 8) + " " + timestamp.substring(8, 10) + ":" + timestamp.substring(10, 12) + ":" + timestamp.substring(12, 14))
    return result
  }
}