Skip to content

Add remote wiki data loading with local cache and BreezeWiki fallback#2

Open
Copilot wants to merge 6 commits intomainfrom
copilot/implement-remote-wiki-data-loading
Open

Add remote wiki data loading with local cache and BreezeWiki fallback#2
Copilot wants to merge 6 commits intomainfrom
copilot/implement-remote-wiki-data-loading

Conversation

Copy link

Copilot AI commented Mar 26, 2026

  • Explore codebase (popup.html, popup.js, content.js, manifest.json, project.pbxproj)
  • popup.html (both platforms): subtitle and mirror label/hint now clarify independent wikis are preferred, BreezeWiki is fallback
  • content.js (both platforms):
    • Reduce navigate ATTEMPT_THRESHOLD to flat 2 (was fromMirror ? 2 : 3)
    • Add consecutive-reload detection: two back-to-back reloads of the same URL suppress the redirect immediately (resets on any non-reload navigation)
    • Fix "Configure settings for Flean" link in suppression banner: cascade browser.action.openPopup()openOptionsPage()tabs.create(popup.html); always visible
    • Fix "Extension settings" link in overlay: same cascade; removed silent remove-if-unavailable guard
  • manifest.json (both platforms): "version" bumped to "2.1.3"
  • project.pbxproj (both platforms): MARKETING_VERSION = 2.1.3, CURRENT_PROJECT_VERSION = 2 (8 occurrences each)
  • Swift syntax check — all 15 .swift files pass with zero errors
  • Code review addressed (comment clarity, cancelTimeout initialization)
  • CodeQL security scan — 0 alerts
Original prompt

Overview

Implement Option C: Remote wiki data loading with local caching and fallback to heuristics.

Current State

  • Flean currently only supports Fandom → BreezeWiki redirects via simple heuristics
  • Attempted embedding full indie-wiki-buddy datapack caused performance issues (resource bloat)

Proposed Solution

Add a system to:

  1. Fetch indie-wiki-buddy's wiki data from GitHub CDN (sitesEN.json) on first run or weekly refresh
  2. Compress and cache locally using gzip + base64 (like indie-wiki-buddy does) to save storage
  3. Intelligent fallback to heuristic matching if network unavailable or data corruption
  4. Support wiki-to-wiki redirects beyond Fandom, with platform-specific search paths

Changes Required

1. New File: scripts/wiki-data-manager.js

  • fetchWikiData() - fetch from GitHub + compress
  • getWikiData() - retrieve from cache with fallback
  • findMatchingWiki(url) - lookup with both structured data and fallback heuristics
  • compressJSON(value) / decompressJSON(value) - gzip compression helpers

2. Update background.js

  • Initialize wiki data manager on extension load
  • Set up periodic refresh (7-day cycle)
  • Handle data corruption gracefully

3. Update content.js

  • Add new redirect logic that uses structured wiki data
  • Construct redirect URLs based on destination platform (MediaWiki, DokuWiki, etc.)
  • Preserve fallback for Fandom → BreezeWiki if data unavailable

4. Update manifest.json

  • Add webRequest permissions (if supported by Safari)
  • Add host permissions for GitHub CDN (raw.githubusercontent.com)

Benefits

  • ✅ Support 500+ wikis (not just Fandom)
  • ✅ Keep data size small (~50KB compressed, vs 2MB uncompressed)
  • ✅ Work offline with fallback
  • ✅ Auto-update weekly without user intervention
  • ✅ Graceful degradation if network fails

Testing Checklist

  • Data fetches and decompresses correctly
  • Falls back to heuristics if fetch fails
  • Cache persists across sessions
  • Weekly refresh works
  • Fandom redirects still work
  • Non-Fandom wiki redirects work (test with 1-2 wiki pairs)

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: Do you think it would be possible to port over to my extention functionality of @KevinPayravi/indie-wiki-buddy, in which it can also redirect to independent wikis, not just breezewiki mirrors, and the fact it also has the option to edit search engine results. What parts of that are possible in macOS/iOS ecosystem, are some parts impossible, would it be easier to port indiewikibuddy from scratch, or is my code a good base.

Assistant: I'll help you assess the possibility of porting indie-wiki-buddy functionality to your Flean extension. Let me first examine your repository and then look at indie-wiki-buddy to understand what we're working with.

Assistant: Now let me get more detailed information about the actual implementations:

Assistant: Based on my analysis of your Flean extension and the indie-wiki-buddy project, here's a detailed assessment:

Summary

Your code is a good base, but porting indie-wiki-buddy's full functionality to macOS/iOS has significant platform-specific limitations. Some features are possible, others would need major adaptation.


Feature Breakdown

Possible to Port (with your current code as a foundation):

  1. Wiki Redirects to Independent Mirrors - This is your core strength

    • Your current heuristic-based system works well
    • indie-wiki-buddy uses structured JSON data; you can adapt this into your settings system
    • Both use similar URL-rewriting logic
  2. Expanded Wiki Support Beyond BreezeWiki

    • Currently you only support Fandom → BreezeWiki
    • indie-wiki-buddy maps Fandom → independent wikis (Miraheze, Wiki.gg, self-hosted, etc.)
    • Your SettingsStore can be extended to load indie-wiki-buddy's JSON data format
  3. Ignore/Allow Lists

    • You already have this structure in place
    • Just needs UI expansion to match indie-wiki-buddy's granularity

⚠️ Severely Limited or Impossible:

  1. Search Engine Result Filtering - MAJOR LIMITATION

    • indie-wiki-buddy intercepts and modifies Google, Bing, DuckDuckGo, etc. search results
    • macOS Safari: Limited access to search results pages; can inject scripts but with constraints
    • iOS Safari: Severely restricted; content scripts can't reliably modify search results
    • Workaround: You could create a custom search proxy/redirect, but it's a completely different implementation
  2. Multiple Languages - Partial Support

    • indie-wiki-buddy supports 20+ languages with structured data files
    • iOS localizations work fine, but managing that many wiki mappings across languages would be complex
    • Your current approach stores settings in JSON, which can handle this
  3. Real-time Data Updates

    • indie-wiki-buddy updates its wiki mappings via GitHub
      ...

This pull request was created from Copilot chat.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…euristic fallback

Agent-Logs-Url: https://github.com/SillyLittleTech/Flean/sessions/e1d6c009-0550-4e30-8402-9f04d1e09994

Co-authored-by: kiyarose <75678535+kiyarose@users.noreply.github.com>
Copilot AI changed the title [WIP] Add remote wiki data loading with local caching and fallback Add remote wiki data loading with local cache and BreezeWiki fallback Mar 26, 2026
Copilot AI requested a review from kiyarose March 26, 2026 19:22
@kiyarose kiyarose marked this pull request as ready for review March 26, 2026 19:26
Copilot AI review requested due to automatic review settings March 26, 2026 19:26
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements on-demand loading of indie-wiki-buddy’s wiki mapping data, caching it locally (compressed) and using it to redirect to independent wikis when available, with fallback to existing BreezeWiki heuristics.

Changes:

  • Added a wiki data manager module to fetch, compress, cache, and look up structured wiki mappings.
  • Updated background scripts to warm the cache, refresh weekly via alarms, and expose a findWiki message API.
  • Updated content scripts to query the background for structured matches before applying heuristic BreezeWiki redirects; updated overlay copy accordingly.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
mos/Flean Extension/Resources/scripts/wiki-data-manager.js New module to fetch + cache compressed wiki mapping data and perform URL matching.
mos/Flean Extension/Resources/background.js Initializes/refreshes cached data and exposes lookup via runtime messaging.
mos/Flean Extension/Resources/content.js Uses structured lookup (with timeout) before BreezeWiki heuristic redirect; updates overlay text.
mos/Flean Extension/Resources/manifest.json Adds alarms permission and GitHub raw host permission for data fetches.
ios/extention/Resources/scripts/wiki-data-manager.js iOS copy of the wiki data manager module.
ios/extention/Resources/background.js iOS copy of background initialization/lookup logic.
ios/extention/Resources/content.js iOS copy of structured lookup + overlay updates.
ios/extention/Resources/manifest.json iOS copy of manifest permission updates.

Copilot AI and others added 2 commits March 26, 2026 19:39
This commit fixes the style issues introduced in 813d03d according to the output
from PHP CS Fixer, StandardJS and swift-format.

Details: #2
@deepsource-io
Copy link

deepsource-io bot commented Mar 26, 2026

DeepSource Code Review

We reviewed changes in abb91d5...8b68900 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
PHP Mar 26, 2026 8:18p.m. Review ↗
Shell Mar 26, 2026 8:18p.m. Review ↗
JavaScript Mar 26, 2026 8:18p.m. Review ↗
Swift Mar 26, 2026 8:18p.m. Review ↗

kiyarose

This comment was marked as outdated.

@kiyarose kiyarose self-requested a review March 26, 2026 20:11
kiyarose

This comment was marked as resolved.

Copy link
Author

Copilot AI commented Mar 26, 2026

Address valid issues in this report, set up ignore rules (if possible), for those that do not affect this codebase.

DeepSource Code Review Report

...

Done in commit 572e434. Both reported categories are false positives for this codebase:

  • JS-0002 (no-console)console.* calls are intentional diagnostic logging in browser extension scripts; there is no other debug channel available in a Safari Web Extension. Suppressed via "no-console": "off" in the ESLint config.
  • JS-0125 (browser not defined)browser is the WebExtension runtime global injected by the host browser (Safari/Firefox/Chrome), not a project variable. Suppressed via "env": { "webextensions": true } which is ESLint's built-in environment for this exact case.

Three files were added to wire this up:

  • .eslintrc.json — legacy format that DeepSource's JS analyzer reads
  • eslint.config.js — ESLint v9+/v10 flat config for local npx eslint runs (exits 0 across all 8 extension JS files)
  • .deepsource.toml — configures the JS analyzer with environment = ["browser", "es2020"] and includes comments documenting why each rule is suppressed

This commit fixes the style issues introduced in 572e434 according to the output
from PHP CS Fixer, StandardJS and swift-format.

Details: #2
Copilot AI requested a review from kiyarose March 26, 2026 20:18
Copy link
Member

@kiyarose kiyarose left a comment

Choose a reason for hiding this comment

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

Details# DeepSource Code Review Report

Repository: SillyLittleTech/Flean
Branch: copilot/implement-remote-wiki-data-loading
PR: #2 · #2
Commit: abb91d5...8b68900
Run: https://app.deepsource.com/gh/SillyLittleTech/Flean/run/dcdad54b-1d88-4dc4-9eff-1d81968e59c0/


Summary

  • PHP: No issues detected- Shell: No issues detected- Swift: No issues detected- JavaScript: 107 issues

Code Review Findings

PHP

Status: Success
Findings: No new issues detected

Shell

Status: Success
Findings: No new issues detected

Swift

Status: Success
Findings: No new issues detected

JavaScript

Status: Failure
Findings: 107 new issues

  1. Avoid using console in code that runs on the browser (JS-0002)
    File: ios/extention/Resources/background.js
    Line: 23

    try {
            await fetchWikiData()
            invalidateIndex()
            console.log('Flean: wiki data refreshed')
          } catch (e) {
            console.warn('Flean: scheduled wiki data refresh failed', e)
          }

    Category: Bug risk
    Severity: major

  2. Avoid using console in code that runs on the browser (JS-0002)
    File: ios/extention/Resources/background.js
    Line: 7

    async function initWikiData () {
      try {
        await getWikiData()
        console.log('Flean: wiki data ready')
      } catch (e) {
        console.warn('Flean: wiki data init failed, heuristics will be used', e)
      }

    Category: Bug risk
    Severity: major

  3. use Boolean(settingsLink) instead (JS-0066)
    File: ios/extention/Resources/content.js
    Line: 315

    const allowBtn = overlay.querySelector('#flean-allow-site')
        const settingsLink = overlay.querySelector('#flean-open-popup')
    
        console.debug('Flean: overlay buttons', { openBtn: !!openBtn, onceBtn: !!onceBtn, allowBtn: !!allowBtn, settingsLink: !!settingsLink })
    
        overlay.addEventListener('click', (ev) => {
          try { console.debug('Flean: overlay click', ev.target && (ev.target.id || ev.target.className || ev.target.tagName)) } catch (e) { /* ignore */ }

    Category: Anti-pattern
    Severity: minor

  4. Prefer using an optional chain expression instead, as it's more concise and easier to read (JS-W1044)
    File: ios/extention/Resources/content.js
    Line: 65

    let mirrors = DEFAULTS.mirrors.slice()
        try {
          const cache = JSON.parse(window.localStorage.getItem('__flean_cache') || 'null')
          if (cache && cache.ts && (Date.now() - cache.ts) < 30 * 1000) {
            allowedSites = cache.allowedSites || allowedSites
            selectedMirror = normalizeHost(cache.selectedMirror || selectedMirror)
            askOnVisit = !!cache.askOnVisit

    Category: Anti-pattern
    Severity: minor

  5. Unexpected string concatenation (JS-0246)
    File: ios/extention/Resources/content.js
    Line: 167

    const now = Date.now()
          const pageKey = url.href
          const attemptsKey = '__flean_attempts:' + pageKey
          const suppressKey = '__flean_suppressed:' + pageKey
          const reloadKey = '__flean_reloads:' + pageKey
    
          function readSessionArray (k) { try { return JSON.parse(window.sessionStorage.getItem(k) || '[]') } catch (e) { return [] } }

    Category: Anti-pattern
    Severity: minor

  6. use Boolean(cache.askOnVisit) instead (JS-0066)
    File: ios/extention/Resources/content.js
    Line: 68

    if (cache && cache.ts && (Date.now() - cache.ts) < 30 * 1000) {
            allowedSites = cache.allowedSites || allowedSites
            selectedMirror = normalizeHost(cache.selectedMirror || selectedMirror)
            askOnVisit = !!cache.askOnVisit
            mirrors = (cache.mirrors || mirrors).map(normalizeHost)
          }
        } catch (e) { /* ignore cache parse errors */ }

    Category: Anti-pattern
    Severity: minor

  7. use Boolean(openBtn) instead (JS-0066)
    File: ios/extention/Resources/content.js
    Line: 315

    const allowBtn = overlay.querySelector('#flean-allow-site')
        const settingsLink = overlay.querySelector('#flean-open-popup')
    
        console.debug('Flean: overlay buttons', { openBtn: !!openBtn, onceBtn: !!onceBtn, allowBtn: !!allowBtn, settingsLink: !!settingsLink })
    
        overlay.addEventListener('click', (ev) => {
          try { console.debug('Flean: overlay click', ev.target && (ev.target.id || ev.target.className || ev.target.tagName)) } catch (e) { /* ignore */ }

    Category: Anti-pattern
    Severity: minor

  8. use Boolean(s.askOnVisit) instead (JS-0066)
    File: ios/extention/Resources/content.js
    Line: 77

    (async () => {
          try {
            const s = await browser.storage.local.get({ allowedSites: [], selectedMirror: DEFAULTS.selectedMirror, askOnVisit: DEFAULTS.askOnVisit, mirrors: DEFAULTS.mirrors })
            const cache = { allowedSites: s.allowedSites || [], selectedMirror: normalizeHost(s.selectedMirror || DEFAULTS.selectedMirror), askOnVisit: !!s.askOnVisit, mirrors: (s.mirrors || DEFAULTS.mirrors).map(normalizeHost), ts: Date.now() }
            try { window.localStorage.setItem('__flean_cache', JSON.stringify(cache)) } catch (e) { /* ignore */ }
          } catch (e) { /* ignore background refresh errors */ }
        })()

    Category: Anti-pattern
    Severity: minor

  9. Prefer using an optional chain expression instead, as it's more concise and easier to read (JS-W1044)
    File: ios/extention/Resources/content.js
    Line: 151

    new Promise(resolve => { cancelTimeout = setTimeout(() => resolve(null), 500) })
          ])
          clearTimeout(cancelTimeout)
          if (structured && structured.destinationUrl) {
            finalDestUrl = structured.destinationUrl
            finalDestLabel = structured.wikiName || new URL(structured.destinationUrl).host
            isIndieWiki = true

    Category: Anti-pattern
    Severity: minor

  10. Prefer using an optional chain expression instead, as it's more concise and easier to read (JS-W1044)
    File: ios/extention/Resources/content.js
    Line: 83

    })()
    
        // Keep the localStorage cache in sync immediately when preferences change.
        if (browser && browser.storage && typeof browser.storage.onChanged === 'object') {
          try {
            browser.storage.onChanged.addListener((changes, areaName) => {
              if (areaName !== 'local') return

    Category: Anti-pattern
    Severity: minor

  11. use Boolean(allowBtn) instead (JS-0066)
    File: ios/extention/Resources/content.js
    Line: 315

    const allowBtn = overlay.querySelector('#flean-allow-site')
        const settingsLink = overlay.querySelector('#flean-open-popup')
    
        console.debug('Flean: overlay buttons', { openBtn: !!openBtn, onceBtn: !!onceBtn, allowBtn: !!allowBtn, settingsLink: !!settingsLink })
    
        overlay.addEventListener('click', (ev) => {
          try { console.debug('Flean: overlay click', ev.target && (ev.target.id || ev.target.className || ev.target.tagName)) } catch (e) { /* ignore */ }

    Category: Anti-pattern
    Severity: minor

  12. Function has a cyclomatic complexity of 57 with "critical" risk (JS-R1005)
    File: ios/extention/Resources/content.js
    Line: 3

    // Content script: intercept fandom/wikia wiki pages and redirect to a selected Breezewiki mirror.
    
    (async function () {
      try {
        const url = new URL(window.location.href)
        const host = url.host.toLowerCase()

    Category: Anti-pattern
    Severity: minor

  13. Unexpected string concatenation (JS-0246)
    File: ios/extention/Resources/content.js
    Line: 54

    // If it's a full URL, URL() will succeed and we can read host
            return new URL(str).host.toLowerCase()
          } catch (e) {
            try { return new URL('https://' + str).host.toLowerCase() } catch (e2) { return str.toLowerCase() }
          }
        }

    Category: Anti-pattern
    Severity: minor

  14. Variable name is too small (JS-C1002)
    File: ios/extention/Resources/content.js
    Line: 76

    // Background refresh of storage to keep the cache fresh (non-blocking)
        (async () => {
          try {
            const s = await browser.storage.local.get({ allowedSites: [], selectedMirror: DEFAULTS.selectedMirror, askOnVisit: DEFAULTS.askOnVisit, mirrors: DEFAULTS.mirrors })
            const cache = { allowedSites: s.allowedSites || [], selectedMirror: normalizeHost(s.selectedMirror || DEFAULTS.selectedMirror), askOnVisit: !!s.askOnVisit, mirrors: (s.mirrors || DEFAULTS.mirrors).map(normalizeHost), ts: Date.now() }
            try { window.localStorage.setItem('__flean_cache', JSON.stringify(cache)) } catch (e) { /* ignore */ }
          } catch (e) { /* ignore background refresh errors */ }

    Category: Anti-pattern
    Severity: minor

  15. use Boolean(changes.askOnVisit.newValue) instead (JS-0066)
    File: ios/extention/Resources/content.js
    Line: 92

    try { cache = JSON.parse(window.localStorage.getItem('__flean_cache') || 'null') || { ts: 0 } } catch (e) { cache = { ts: 0 } }
              if (changes.allowedSites) { cache.allowedSites = changes.allowedSites.newValue || []; updated = true }
              if (changes.selectedMirror) { cache.selectedMirror = normalizeHost(changes.selectedMirror.newValue || DEFAULTS.selectedMirror); updated = true }
              if (changes.askOnVisit) { cache.askOnVisit = !!changes.askOnVisit.newValue; updated = true }
              if (changes.mirrors) { cache.mirrors = (changes.mirrors.newValue || DEFAULTS.mirrors).map(normalizeHost); updated = true }
              if (updated) {
                cache.ts = Date.now()

    Category: Anti-pattern
    Severity: minor

  16. use Boolean(onceBtn) instead (JS-0066)
    File: ios/extention/Resources/content.js
    Line: 315

    const allowBtn = overlay.querySelector('#flean-allow-site')
        const settingsLink = overlay.querySelector('#flean-open-popup')
    
        console.debug('Flean: overlay buttons', { openBtn: !!openBtn, onceBtn: !!onceBtn, allowBtn: !!allowBtn, settingsLink: !!settingsLink })
    
        overlay.addEventListener('click', (ev) => {
          try { console.debug('Flean: overlay click', ev.target && (ev.target.id || ev.target.className || ev.target.tagName)) } catch (e) { /* ignore */ }

    Category: Anti-pattern
    Severity: minor

  17. Unexpected string concatenation (JS-0246)
    File: ios/extention/Resources/content.js
    Line: 166

    const now = Date.now()
          const pageKey = url.href
          const attemptsKey = '__flean_attempts:' + pageKey
          const suppressKey = '__flean_suppressed:' + pageKey
          const reloadKey = '__flean_reloads:' + pageKey

    Category: Anti-pattern
    Severity: minor

  18. Unexpected string concatenation (JS-0246)
    File: ios/extention/Resources/content.js
    Line: 202

    if (reloadCount >= ATTEMPT_THRESHOLD || recent.length >= ATTEMPT_THRESHOLD) {
            window.sessionStorage.setItem(suppressKey, String(now + SUPPRESS_COOLDOWN_MS))
            console.info('Flean: suppressing redirect for', pageKey, 'for', SUPPRESS_COOLDOWN_MS, 'ms (reloads=' + reloadCount + ', navigations=' + recent.length + ')')
            showSuppressionBanner()
            return
          }

    Category: Anti-pattern
    Severity: minor

  19. Unexpected string concatenation (JS-0246)
    File: ios/extention/Resources/content.js
    Line: 168

    const pageKey = url.href
          const attemptsKey = '__flean_attempts:' + pageKey
          const suppressKey = '__flean_suppressed:' + pageKey
          const reloadKey = '__flean_reloads:' + pageKey
    
          function readSessionArray (k) { try { return JSON.parse(window.sessionStorage.getItem(k) || '[]') } catch (e) { return [] } }
          function writeSessionArray (k, arr) { try { window.sessionStorage.setItem(k, JSON.stringify(arr)) } catch (e) { /* ignore */ } }

    Category: Anti-pattern
    Severity: minor

  20. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 360

    if (browser.action && typeof browser.action.openPopup === 'function') {
                await browser.action.openPopup(); return
              }
            } catch (e) { /* try next */ }
            try {
              if (browser.runtime && typeof browser.runtime.openOptionsPage === 'function') {
                browser.runtime.openOptionsPage(); return

    Category: Bug risk
    Severity: major

  21. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 89

    if (areaName !== 'local') return
              let updated = false
              let cache = null
              try { cache = JSON.parse(window.localStorage.getItem('__flean_cache') || 'null') || { ts: 0 } } catch (e) { cache = { ts: 0 } }
              if (changes.allowedSites) { cache.allowedSites = changes.allowedSites.newValue || []; updated = true }
              if (changes.selectedMirror) { cache.selectedMirror = normalizeHost(changes.selectedMirror.newValue || DEFAULTS.selectedMirror); updated = true }
              if (changes.askOnVisit) { cache.askOnVisit = !!changes.askOnVisit.newValue; updated = true }

    Category: Bug risk
    Severity: major

  22. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 129

    // Extract page title
        let page = path.replace(/^\/wiki\//i, '')
        try { page = decodeURIComponent(page) } catch (e) { /* ignore */ }
    
        // Compute mirror URL (fallback)
        const pageForMirror = page.replace(/\s+/g, '_').replace(/^\/+|\/+$/g, '')

    Category: Bug risk
    Severity: major

  23. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 365

    if (browser.runtime && typeof browser.runtime.openOptionsPage === 'function') {
                browser.runtime.openOptionsPage(); return
              }
            } catch (e) { /* try next */ }
            try { await browser.tabs.create({ url: browser.runtime.getURL('popup.html') }) } catch (e) { console.warn('Flean: could not open settings', e) }
          })
        }

    Category: Bug risk
    Severity: major

  24. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 170

    const suppressKey = '__flean_suppressed:' + pageKey
          const reloadKey = '__flean_reloads:' + pageKey
    
          function readSessionArray (k) { try { return JSON.parse(window.sessionStorage.getItem(k) || '[]') } catch (e) { return [] } }
          function writeSessionArray (k, arr) { try { window.sessionStorage.setItem(k, JSON.stringify(arr)) } catch (e) { /* ignore */ } }
    
          // Detect whether this page load is a browser reload (F5 / Cmd-R)

    Category: Bug risk
    Severity: major

  25. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 257

    if (browser.action && typeof browser.action.openPopup === 'function') {
                    await browser.action.openPopup(); return
                  }
                } catch (e) { /* try next */ }
                try {
                  if (browser.runtime && typeof browser.runtime.openOptionsPage === 'function') {
                    browser.runtime.openOptionsPage(); return

    Category: Bug risk
    Severity: major

  26. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 187

    try {
            reloadCount = isReload ? (parseInt(window.sessionStorage.getItem(reloadKey) || '0', 10) + 1) : 0
            window.sessionStorage.setItem(reloadKey, String(reloadCount))
          } catch (e) { /* ignore */ }
    
          const recent = readSessionArray(attemptsKey).filter(ts => (now - ts) <= ATTEMPT_WINDOW_MS)
          recent.push(now)

    Category: Bug risk
    Severity: major

  27. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 318

    console.debug('Flean: overlay buttons', { openBtn: !!openBtn, onceBtn: !!onceBtn, allowBtn: !!allowBtn, settingsLink: !!settingsLink })
    
        overlay.addEventListener('click', (ev) => {
          try { console.debug('Flean: overlay click', ev.target && (ev.target.id || ev.target.className || ev.target.tagName)) } catch (e) { /* ignore */ }
        }, { capture: true })
    
        if (openBtn) {

    Category: Bug risk
    Severity: major

  28. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 53

    try {
            // If it's a full URL, URL() will succeed and we can read host
            return new URL(str).host.toLowerCase()
          } catch (e) {
            try { return new URL('https://' + str).host.toLowerCase() } catch (e2) { return str.toLowerCase() }
          }
        }

    Category: Bug risk
    Severity: major

  29. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 156

    finalDestLabel = structured.wikiName || new URL(structured.destinationUrl).host
            isIndieWiki = true
          }
        } catch (e) { /* background unavailable – fall back to heuristic mirror */ }
    
        // Fast-path for askOnVisit=false using sessionStorage-only attempt tracking
        if (!askOnVisit) {

    Category: Bug risk
    Severity: major

  30. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 178

    try {
            const navEntry = performance.getEntriesByType('navigation')[0]
            isReload = navEntry ? navEntry.type === 'reload' : (performance.navigation && performance.navigation.type === 1)
          } catch (e) { /* ignore */ }
    
          // Track consecutive reloads separately; two in a row bypasses the redirect.
          // Intentionally resets to 0 on any non-reload navigation so only truly

    Category: Bug risk
    Severity: major

  31. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 111

    if (Date.now() <= until) isSessionAllowed = true
            else window.sessionStorage.removeItem('__flean_allow_until')
          }
        } catch (e) { /* ignore */ }
    
        if ((allowedSites || []).includes(host) || isSessionAllowed) {
          console.log('Flean: host is in ignore list or session-allowed, skipping redirection for', host)

    Category: Bug risk
    Severity: major

  32. Avoid using console in code that runs on the browser (JS-0002)
    File: ios/extention/Resources/content.js
    Line: 114

    } catch (e) { /* ignore */ }
    
        if ((allowedSites || []).includes(host) || isSessionAllowed) {
          console.log('Flean: host is in ignore list or session-allowed, skipping redirection for', host)
          return
        }

    Category: Bug risk
    Severity: major

  33. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 79

    const s = await browser.storage.local.get({ allowedSites: [], selectedMirror: DEFAULTS.selectedMirror, askOnVisit: DEFAULTS.askOnVisit, mirrors: DEFAULTS.mirrors })
            const cache = { allowedSites: s.allowedSites || [], selectedMirror: normalizeHost(s.selectedMirror || DEFAULTS.selectedMirror), askOnVisit: !!s.askOnVisit, mirrors: (s.mirrors || DEFAULTS.mirrors).map(normalizeHost), ts: Date.now() }
            try { window.localStorage.setItem('__flean_cache', JSON.stringify(cache)) } catch (e) { /* ignore */ }
          } catch (e) { /* ignore background refresh errors */ }
        })()
    
        // Keep the localStorage cache in sync immediately when preferences change.

    Category: Bug risk
    Severity: major

  34. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 171

    const reloadKey = '__flean_reloads:' + pageKey
    
          function readSessionArray (k) { try { return JSON.parse(window.sessionStorage.getItem(k) || '[]') } catch (e) { return [] } }
          function writeSessionArray (k, arr) { try { window.sessionStorage.setItem(k, JSON.stringify(arr)) } catch (e) { /* ignore */ } }
    
          // Detect whether this page load is a browser reload (F5 / Cmd-R)
          let isReload = false

    Category: Bug risk
    Severity: major

  35. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 71

    askOnVisit = !!cache.askOnVisit
            mirrors = (cache.mirrors || mirrors).map(normalizeHost)
          }
        } catch (e) { /* ignore cache parse errors */ }
    
        // Background refresh of storage to keep the cache fresh (non-blocking)
        (async () => {

    Category: Bug risk
    Severity: major

  36. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 99

    try { window.localStorage.setItem('__flean_cache', JSON.stringify(cache)) } catch (e) { /* ignore */ }
              }
            })
          } catch (e) { /* ignore if onChanged isn't available */ }
        }
    
        // Fast session-scoped allow (for immediate navigation within this tab)

    Category: Bug risk
    Severity: major

  37. Avoid using console in code that runs on the browser (JS-0002)
    File: ios/extention/Resources/content.js
    Line: 208

    }
    
          // Redirect quickly
          console.log('Flean: askOnVisit=false, redirecting', url.href, '->', finalDestUrl)
          try { window.location.replace(finalDestUrl) } catch (e) { console.warn('Flean: failed to redirect', e) }
          return
        }

    Category: Bug risk
    Severity: major

  38. 'e2' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 54

    // If it's a full URL, URL() will succeed and we can read host
            return new URL(str).host.toLowerCase()
          } catch (e) {
            try { return new URL('https://' + str).host.toLowerCase() } catch (e2) { return str.toLowerCase() }
          }
        }

    Category: Bug risk
    Severity: major

  39. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 78

    try {
            const s = await browser.storage.local.get({ allowedSites: [], selectedMirror: DEFAULTS.selectedMirror, askOnVisit: DEFAULTS.askOnVisit, mirrors: DEFAULTS.mirrors })
            const cache = { allowedSites: s.allowedSites || [], selectedMirror: normalizeHost(s.selectedMirror || DEFAULTS.selectedMirror), askOnVisit: !!s.askOnVisit, mirrors: (s.mirrors || DEFAULTS.mirrors).map(normalizeHost), ts: Date.now() }
            try { window.localStorage.setItem('__flean_cache', JSON.stringify(cache)) } catch (e) { /* ignore */ }
          } catch (e) { /* ignore background refresh errors */ }
        })()

    Category: Bug risk
    Severity: major

  40. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 96

    if (changes.mirrors) { cache.mirrors = (changes.mirrors.newValue || DEFAULTS.mirrors).map(normalizeHost); updated = true }
              if (updated) {
                cache.ts = Date.now()
                try { window.localStorage.setItem('__flean_cache', JSON.stringify(cache)) } catch (e) { /* ignore */ }
              }
            })
          } catch (e) { /* ignore if onChanged isn't available */ }

    Category: Bug risk
    Severity: major

  41. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 345

    try {
              const SESSION_ALLOW_MS = 5 * 1000 // 5 seconds
              const until = Date.now() + SESSION_ALLOW_MS
              try { window.sessionStorage.setItem('__flean_allow_until', String(until)) } catch (e) { /* ignore */ }
              console.info('Flean: session-allow for host', host, 'until', new Date(until).toISOString())
            } catch (err) { console.warn('Flean: failed to set session allow', err) }
            overlay.remove()

    Category: Bug risk
    Severity: major

  42. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/content.js
    Line: 262

    if (browser.runtime && typeof browser.runtime.openOptionsPage === 'function') {
                    browser.runtime.openOptionsPage(); return
                  }
                } catch (e) { /* try next */ }
                try { await browser.tabs.create({ url: browser.runtime.getURL('popup.html') }) } catch (e) { console.warn('Flean: could not open settings', e) }
              })
            }

    Category: Bug risk
    Severity: major

  43. Unexpected string concatenation (JS-0246)
    File: ios/extention/Resources/scripts/wiki-data-manager.js
    Line: 181

    destPath = '/doku.php?id=' + encodeURIComponent(article.replace(/ /g, '_').toLowerCase())
        } else {
          // Default: MediaWiki-style /wiki/ArticleName
          destPath = '/wiki/' + encodeURIComponent(article.replace(/ /g, '_'))
        }
    
        const destinationUrl = `https://${destBase}${destPath}${url.search}${url.hash}`

    Category: Anti-pattern
    Severity: minor

  44. Variable 'destPath' should be initialized on declaration (JS-0119)
    File: ios/extention/Resources/scripts/wiki-data-manager.js
    Line: 172

    .replace(/^https?:\/\//, '')
          .replace(/\/$/, '')
        const platform = (wiki.destination_platform || 'mediawiki').toLowerCase()
        let destPath
    
        if (wiki.destination_content_path) {
          destPath = wiki.destination_content_path

    Category: Anti-pattern
    Severity: minor

  45. Found async function without any await expressions (JS-0116)
    File: ios/extention/Resources/scripts/wiki-data-manager.js
    Line: 115-128

    let _wikiIndexPromise = null
    
    /** Ensure the in-memory lookup index is built. */
    async function ensureIndex () {
      if (_wikiIndex !== null) return _wikiIndex
      if (_wikiIndexPromise) return _wikiIndexPromise
      _wikiIndexPromise = getWikiData().then(data => {
        _wikiIndex = buildIndex(data)
        _wikiIndexPromise = null
        return _wikiIndex
      }).catch(() => {
        _wikiIndex = new Map()
        _wikiIndexPromise = null
        return _wikiIndex
      })
      return _wikiIndexPromise
    }
    
    /**
     * Invalidate the in-memory index so it is rebuilt on the next lookup.

    Category: Bug risk
    Severity: minor

  46. Unexpected string concatenation (JS-0246)
    File: ios/extention/Resources/scripts/wiki-data-manager.js
    Line: 178

    destPath = wiki.destination_content_path
            .replace('$1', encodeURIComponent(article.replace(/ /g, '_')))
        } else if (platform === 'dokuwiki') {
          destPath = '/doku.php?id=' + encodeURIComponent(article.replace(/ /g, '_').toLowerCase())
        } else {
          // Default: MediaWiki-style /wiki/ArticleName
          destPath = '/wiki/' + encodeURIComponent(article.replace(/ /g, '_'))

    Category: Anti-pattern
    Severity: minor

  47. 'e2' is defined but never used (JS-0128)
    File: ios/extention/Resources/scripts/wiki-data-manager.js
    Line: 54

    return JSON.parse(new TextDecoder().decode(decompressed))
      } catch (e) {
        // May be plain base64 JSON (written by the fallback path above)
        try { return JSON.parse(atob(value)) } catch (e2) { throw e }
      }
    }

    Category: Bug risk
    Severity: major

  48. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/scripts/wiki-data-manager.js
    Line: 187

    const destinationUrl = `https://${destBase}${destPath}${url.search}${url.hash}`
        const wikiName = wiki.destination || wiki.article || destBase
        return { destinationUrl, wikiName }
      } catch (e) {
        return null
      }
    }

    Category: Bug risk
    Severity: major

  49. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/scripts/wiki-data-manager.js
    Line: 165

    if (article.toLowerCase().startsWith(originPathPrefix.toLowerCase())) {
          article = article.slice(originPathPrefix.length)
        }
        try { article = decodeURIComponent(article) } catch (e) { /* keep encoded */ }
    
        // Build destination URL based on platform
        const destBase = wiki.destination_base_url

    Category: Bug risk
    Severity: major

  50. 'e' is defined but never used (JS-0128)
    File: ios/extention/Resources/scripts/wiki-data-manager.js
    Line: 30

    let binary = ''
        for (let i = 0; i < uint8.length; i++) binary += String.fromCharCode(uint8[i])
        return btoa(binary)
      } catch (e) {
        return btoa(json)
      }
    }

    Category: Bug risk
    Severity: major

  51. Avoid using console in code that runs on the browser (JS-0002)
    File: mos/Flean Extension/Resources/background.js
    Line: 23

    try {
            await fetchWikiData()
            invalidateIndex()
            console.log('Flean: wiki data refreshed')
          } catch (e) {
            console.warn('Flean: scheduled wiki data refresh failed', e)
          }

    Category: Bug risk
    Severity: major

  52. Avoid using console in code that runs on the browser (JS-0002)
    File: mos/Flean Extension/Resources/background.js
    Line: 7

    async function initWikiData () {
      try {
        await getWikiData()
        console.log('Flean: wiki data ready')
      } catch (e) {
        console.warn('Flean: wiki data init failed, heuristics will be used', e)
      }

    Category: Bug risk
    Severity: major

  53. use Boolean(changes.askOnVisit.newValue) instead (JS-0066)
    File: mos/Flean Extension/Resources/content.js
    Line: 92

    try { cache = JSON.parse(window.localStorage.getItem('__flean_cache') || 'null') || { ts: 0 } } catch (e) { cache = { ts: 0 } }
              if (changes.allowedSites) { cache.allowedSites = changes.allowedSites.newValue || []; updated = true }
              if (changes.selectedMirror) { cache.selectedMirror = normalizeHost(changes.selectedMirror.newValue || DEFAULTS.selectedMirror); updated = true }
              if (changes.askOnVisit) { cache.askOnVisit = !!changes.askOnVisit.newValue; updated = true }
              if (changes.mirrors) { cache.mirrors = (changes.mirrors.newValue || DEFAULTS.mirrors).map(normalizeHost); updated = true }
              if (updated) {
                cache.ts = Date.now()

    Category: Anti-pattern
    Severity: minor

  54. Prefer using an optional chain expression instead, as it's more concise and easier to read (JS-W1044)
    File: mos/Flean Extension/Resources/content.js
    Line: 65

    let mirrors = DEFAULTS.mirrors.slice()
        try {
          const cache = JSON.parse(window.localStorage.getItem('__flean_cache') || 'null')
          if (cache && cache.ts && (Date.now() - cache.ts) < 30 * 1000) {
            allowedSites = cache.allowedSites || allowedSites
            selectedMirror = normalizeHost(cache.selectedMirror || selectedMirror)
            askOnVisit = !!cache.askOnVisit

    Category: Anti-pattern
    Severity: minor

  55. Unexpected string concatenation (JS-0246)
    File: mos/Flean Extension/Resources/content.js
    Line: 202

    if (reloadCount >= ATTEMPT_THRESHOLD || recent.length >= ATTEMPT_THRESHOLD) {
            window.sessionStorage.setItem(suppressKey, String(now + SUPPRESS_COOLDOWN_MS))
            console.info('Flean: suppressing redirect for', pageKey, 'for', SUPPRESS_COOLDOWN_MS, 'ms (reloads=' + reloadCount + ', navigations=' + recent.length + ')')
            showSuppressionBanner()
            return
          }

    Category: Anti-pattern
    Severity: minor

  56. 'e' is already declared in the upper scope on line 251 column 48 (JS-0123)
    File: mos/Flean Extension/Resources/content.js
    Line: 263

    browser.runtime.openOptionsPage(); return
                  }
                } catch (e) { /* try next */ }
                try { await browser.tabs.create({ url: browser.runtime.getURL('popup.html') }) } catch (e) { console.warn('Flean: could not open settings', e) }
              })
            }
            closeBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); banner.remove() })

    Category: Bug risk
    Severity: minor

  57. Unexpected string concatenation (JS-0246)
    File: mos/Flean Extension/Resources/content.js
    Line: 167

    const now = Date.now()
          const pageKey = url.href
          const attemptsKey = '__flean_attempts:' + pageKey
          const suppressKey = '__flean_suppressed:' + pageKey
          const reloadKey = '__flean_reloads:' + pageKey
    
          function readSessionArray (k) { try { return JSON.parse(window.sessionStorage.getItem(k) || '[]') } catch (e) { return [] } }

    Category: Anti-pattern
    Severity: minor

  58. use Boolean(openBtn) instead (JS-0066)
    File: mos/Flean Extension/Resources/content.js
    Line: 315

    const allowBtn = overlay.querySelector('#flean-allow-site')
        const settingsLink = overlay.querySelector('#flean-open-popup')
    
        console.debug('Flean: overlay buttons', { openBtn: !!openBtn, onceBtn: !!onceBtn, allowBtn: !!allowBtn, settingsLink: !!settingsLink })
    
        overlay.addEventListener('click', (ev) => {
          try { console.debug('Flean: overlay click', ev.target && (ev.target.id || ev.target.className || ev.target.tagName)) } catch (e) { /* ignore */ }

    Category: Anti-pattern
    Severity: minor

  59. Variable name is too small (JS-C1002)
    File: mos/Flean Extension/Resources/content.js
    Line: 76

    // Background refresh of storage to keep the cache fresh (non-blocking)
        (async () => {
          try {
            const s = await browser.storage.local.get({ allowedSites: [], selectedMirror: DEFAULTS.selectedMirror, askOnVisit: DEFAULTS.askOnVisit, mirrors: DEFAULTS.mirrors })
            const cache = { allowedSites: s.allowedSites || [], selectedMirror: normalizeHost(s.selectedMirror || DEFAULTS.selectedMirror), askOnVisit: !!s.askOnVisit, mirrors: (s.mirrors || DEFAULTS.mirrors).map(normalizeHost), ts: Date.now() }
            try { window.localStorage.setItem('__flean_cache', JSON.stringify(cache)) } catch (e) { /* ignore */ }
          } catch (e) { /* ignore background refresh errors */ }

    Category: Anti-pattern
    Severity: minor

  60. 'e' is already declared in the upper scope on line 251 column 48 (JS-0123)
    File: mos/Flean Extension/Resources/content.js
    Line: 257

    if (browser.action && typeof browser.action.openPopup === 'function') {
                    await browser.action.openPopup(); return
                  }
                } catch (e) { /* try next */ }
                try {
                  if (browser.runtime && typeof browser.runtime.openOptionsPage === 'function') {
                    browser.runtime.openOptionsPage(); return

    Category: Bug risk
    Severity: minor

  61. use Boolean(onceBtn) instead (JS-0066)
    File: mos/Flean Extension/Resources/content.js
    Line: 315

    const allowBtn = overlay.querySelector('#flean-allow-site')
        const settingsLink = overlay.querySelector('#flean-open-popup')
    
        console.debug('Flean: overlay buttons', { openBtn: !!openBtn, onceBtn: !!onceBtn, allowBtn: !!allowBtn, settingsLink: !!settingsLink })
    
        overlay.addEventListener('click', (ev) => {
          try { console.debug('Flean: overlay click', ev.target && (ev.target.id || ev.target.className || ev.target.tagName)) } catch (e) { /* ignore */ }

    Category: Anti-pattern
    Severity: minor

  62. use Boolean(settingsLink) instead (JS-0066)
    File: mos/Flean Extension/Resources/content.js
    Line: 315

    const allowBtn = overlay.querySelector('#flean-allow-site')
        const settingsLink = overlay.querySelector('#flean-open-popup')
    
        console.debug('Flean: overlay buttons', { openBtn: !!openBtn, onceBtn: !!onceBtn, allowBtn: !!allowBtn, settingsLink: !!settingsLink })
    
        overlay.addEventListener('click', (ev) => {
          try { console.debug('Flean: overlay click', ev.target && (ev.target.id || ev.target.className || ev.target.tagName)) } catch (e) { /* ignore */ }

    Category: Anti-pattern
    Severity: minor

  63. Unexpected string concatenation (JS-0246)
    File: mos/Flean Extension/Resources/content.js
    Line: 54

    // If it's a full URL, URL() will succeed and we can read host
            return new URL(str).host.toLowerCase()
          } catch (e) {
            try { return new URL('https://' + str).host.toLowerCase() } catch (e2) { return str.toLowerCase() }
          }
        }

    Category: Anti-pattern
    Severity: minor

  64. Unexpected string concatenation (JS-0246)
    File: mos/Flean Extension/Resources/content.js
    Line: 168

    const pageKey = url.href
          const attemptsKey = '__flean_attempts:' + pageKey
          const suppressKey = '__flean_suppressed:' + pageKey
          const reloadKey = '__flean_reloads:' + pageKey
    
          function readSessionArray (k) { try { return JSON.parse(window.sessionStorage.getItem(k) || '[]') } catch (e) { return [] } }
          function writeSessionArray (k, arr) { try { window.sessionStorage.setItem(k, JSON.stringify(arr)) } catch (e) { /* ignore */ } }

    Category: Anti-pattern
    Severity: minor

  65. use Boolean(cache.askOnVisit) instead (JS-0066)
    File: mos/Flean Extension/Resources/content.js
    Line: 68

    if (cache && cache.ts && (Date.now() - cache.ts) < 30 * 1000) {
            allowedSites = cache.allowedSites || allowedSites
            selectedMirror = normalizeHost(cache.selectedMirror || selectedMirror)
            askOnVisit = !!cache.askOnVisit
            mirrors = (cache.mirrors || mirrors).map(normalizeHost)
          }
        } catch (e) { /* ignore cache parse errors */ }

    Category: Anti-pattern
    Severity: minor

  66. Prefer using an optional chain expression instead, as it's more concise and easier to read (JS-W1044)
    File: mos/Flean Extension/Resources/content.js
    Line: 83

    })()
    
        // Keep the localStorage cache in sync immediately when preferences change.
        if (browser && browser.storage && typeof browser.storage.onChanged === 'object') {
          try {
            browser.storage.onChanged.addListener((changes, areaName) => {
              if (areaName !== 'local') return

    Category: Anti-pattern
    Severity: minor

  67. 'e' is already declared in the upper scope on line 354 column 53 (JS-0123)
    File: mos/Flean Extension/Resources/content.js
    Line: 366

    browser.runtime.openOptionsPage(); return
              }
            } catch (e) { /* try next */ }
            try { await browser.tabs.create({ url: browser.runtime.getURL('popup.html') }) } catch (e) { console.warn('Flean: could not open settings', e) }
          })
        }
      } catch (err) {

    Category: Bug risk
    Severity: minor

  68. Function has a cyclomatic complexity of 57 with "critical" risk (JS-R1005)
    File: mos/Flean Extension/Resources/content.js
    Line: 3

    // Content script: intercept fandom/wikia wiki pages and redirect to a selected Breezewiki mirror.
    
    (async function () {
      try {
        const url = new URL(window.location.href)
        const host = url.host.toLowerCase()

    Category: Anti-pattern
    Severity: minor

  69. use Boolean(allowBtn) instead (JS-0066)
    File: mos/Flean Extension/Resources/content.js
    Line: 315

    const allowBtn = overlay.querySelector('#flean-allow-site')
        const settingsLink = overlay.querySelector('#flean-open-popup')
    
        console.debug('Flean: overlay buttons', { openBtn: !!openBtn, onceBtn: !!onceBtn, allowBtn: !!allowBtn, settingsLink: !!settingsLink })
    
        overlay.addEventListener('click', (ev) => {
          try { console.debug('Flean: overlay click', ev.target && (ev.target.id || ev.target.className || ev.target.tagName)) } catch (e) { /* ignore */ }

    Category: Anti-pattern
    Severity: minor

  70. 'e' is already declared in the upper scope on line 339 column 43 (JS-0123)
    File: mos/Flean Extension/Resources/content.js
    Line: 345

    try {
              const SESSION_ALLOW_MS = 5 * 1000 // 5 seconds
              const until = Date.now() + SESSION_ALLOW_MS
              try { window.sessionStorage.setItem('__flean_allow_until', String(until)) } catch (e) { /* ignore */ }
              console.info('Flean: session-allow for host', host, 'until', new Date(until).toISOString())
            } catch (err) { console.warn('Flean: failed to set session allow', err) }
            overlay.remove()

    Category: Bug risk
    Severity: minor

  71. use Boolean(s.askOnVisit) instead (JS-0066)
    File: mos/Flean Extension/Resources/content.js
    Line: 77

    (async () => {
          try {
            const s = await browser.storage.local.get({ allowedSites: [], selectedMirror: DEFAULTS.selectedMirror, askOnVisit: DEFAULTS.askOnVisit, mirrors: DEFAULTS.mirrors })
            const cache = { allowedSites: s.allowedSites || [], selectedMirror: normalizeHost(s.selectedMirror || DEFAULTS.selectedMirror), askOnVisit: !!s.askOnVisit, mirrors: (s.mirrors || DEFAULTS.mirrors).map(normalizeHost), ts: Date.now() }
            try { window.localStorage.setItem('__flean_cache', JSON.stringify(cache)) } catch (e) { /* ignore */ }
          } catch (e) { /* ignore background refresh errors */ }
        })()

    Category: Anti-pattern
    Severity: minor

  72. 'e' is already declared in the upper scope on line 354 column 53 (JS-0123)
    File: mos/Flean Extension/Resources/content.js
    Line: 365

    if (browser.runtime && typeof browser.runtime.openOptionsPage === 'function') {
                browser.runtime.openOptionsPage(); return
              }
            } catch (e) { /* try next */ }
            try { await browser.tabs.create({ url: browser.runtime.getURL('popup.html') }) } catch (e) { console.warn('Flean: could not open settings', e) }
          })
        }

    Category: Bug risk
    Severity: minor

  73. 'e' is already declared in the upper scope on line 354 column 53 (JS-0123)
    File: mos/Flean Extension/Resources/content.js
    Line: 360

    if (browser.action && typeof browser.action.openPopup === 'function') {
                await browser.action.openPopup(); return
              }
            } catch (e) { /* try next */ }
            try {
              if (browser.runtime && typeof browser.runtime.openOptionsPage === 'function') {
                browser.runtime.openOptionsPage(); return

    Category: Bug risk
    Severity: minor

  74. 'e' is already declared in the upper scope on line 251 column 48 (JS-0123)
    File: mos/Flean Extension/Resources/content.js
    Line: 262

    if (browser.runtime && typeof browser.runtime.openOptionsPage === 'function') {
                    browser.runtime.openOptionsPage(); return
                  }
                } catch (e) { /* try next */ }
                try { await browser.tabs.create({ url: browser.runtime.getURL('popup.html') }) } catch (e) { console.warn('Flean: could not open settings', e) }
              })
            }

    Category: Bug risk
    Severity: minor

  75. Unexpected string concatenation (JS-0246)
    File: mos/Flean Extension/Resources/content.js
    Line: 166

    const now = Date.now()
          const pageKey = url.href
          const attemptsKey = '__flean_attempts:' + pageKey
          const suppressKey = '__flean_suppressed:' + pageKey
          const reloadKey = '__flean_reloads:' + pageKey

    Category: Anti-pattern
    Severity: minor

  76. Prefer using an optional chain expression instead, as it's more concise and easier to read (JS-W1044)
    File: mos/Flean Extension/Resources/content.js
    Line: 151

    new Promise(resolve => { cancelTimeout = setTimeout(() => resolve(null), 500) })
          ])
          clearTimeout(cancelTimeout)
          if (structured && structured.destinationUrl) {
            finalDestUrl = structured.destinationUrl
            finalDestLabel = structured.wikiName || new URL(structured.destinationUrl).host
            isIndieWiki = true

    Category: Anti-pattern
    Severity: minor

  77. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 89

    if (areaName !== 'local') return
              let updated = false
              let cache = null
              try { cache = JSON.parse(window.localStorage.getItem('__flean_cache') || 'null') || { ts: 0 } } catch (e) { cache = { ts: 0 } }
              if (changes.allowedSites) { cache.allowedSites = changes.allowedSites.newValue || []; updated = true }
              if (changes.selectedMirror) { cache.selectedMirror = normalizeHost(changes.selectedMirror.newValue || DEFAULTS.selectedMirror); updated = true }
              if (changes.askOnVisit) { cache.askOnVisit = !!changes.askOnVisit.newValue; updated = true }

    Category: Bug risk
    Severity: major

  78. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 171

    const reloadKey = '__flean_reloads:' + pageKey
    
          function readSessionArray (k) { try { return JSON.parse(window.sessionStorage.getItem(k) || '[]') } catch (e) { return [] } }
          function writeSessionArray (k, arr) { try { window.sessionStorage.setItem(k, JSON.stringify(arr)) } catch (e) { /* ignore */ } }
    
          // Detect whether this page load is a browser reload (F5 / Cmd-R)
          let isReload = false

    Category: Bug risk
    Severity: major

  79. Avoid using console in code that runs on the browser (JS-0002)
    File: mos/Flean Extension/Resources/content.js
    Line: 208

    }
    
          // Redirect quickly
          console.log('Flean: askOnVisit=false, redirecting', url.href, '->', finalDestUrl)
          try { window.location.replace(finalDestUrl) } catch (e) { console.warn('Flean: failed to redirect', e) }
          return
        }

    Category: Bug risk
    Severity: major

  80. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 53

    try {
            // If it's a full URL, URL() will succeed and we can read host
            return new URL(str).host.toLowerCase()
          } catch (e) {
            try { return new URL('https://' + str).host.toLowerCase() } catch (e2) { return str.toLowerCase() }
          }
        }

    Category: Bug risk
    Severity: major

  81. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 318

    console.debug('Flean: overlay buttons', { openBtn: !!openBtn, onceBtn: !!onceBtn, allowBtn: !!allowBtn, settingsLink: !!settingsLink })
    
        overlay.addEventListener('click', (ev) => {
          try { console.debug('Flean: overlay click', ev.target && (ev.target.id || ev.target.className || ev.target.tagName)) } catch (e) { /* ignore */ }
        }, { capture: true })
    
        if (openBtn) {

    Category: Bug risk
    Severity: major

  82. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 178

    try {
            const navEntry = performance.getEntriesByType('navigation')[0]
            isReload = navEntry ? navEntry.type === 'reload' : (performance.navigation && performance.navigation.type === 1)
          } catch (e) { /* ignore */ }
    
          // Track consecutive reloads separately; two in a row bypasses the redirect.
          // Intentionally resets to 0 on any non-reload navigation so only truly

    Category: Bug risk
    Severity: major

  83. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 345

    try {
              const SESSION_ALLOW_MS = 5 * 1000 // 5 seconds
              const until = Date.now() + SESSION_ALLOW_MS
              try { window.sessionStorage.setItem('__flean_allow_until', String(until)) } catch (e) { /* ignore */ }
              console.info('Flean: session-allow for host', host, 'until', new Date(until).toISOString())
            } catch (err) { console.warn('Flean: failed to set session allow', err) }
            overlay.remove()

    Category: Bug risk
    Severity: major

  84. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 365

    if (browser.runtime && typeof browser.runtime.openOptionsPage === 'function') {
                browser.runtime.openOptionsPage(); return
              }
            } catch (e) { /* try next */ }
            try { await browser.tabs.create({ url: browser.runtime.getURL('popup.html') }) } catch (e) { console.warn('Flean: could not open settings', e) }
          })
        }

    Category: Bug risk
    Severity: major

  85. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 156

    finalDestLabel = structured.wikiName || new URL(structured.destinationUrl).host
            isIndieWiki = true
          }
        } catch (e) { /* background unavailable – fall back to heuristic mirror */ }
    
        // Fast-path for askOnVisit=false using sessionStorage-only attempt tracking
        if (!askOnVisit) {

    Category: Bug risk
    Severity: major

  86. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 71

    askOnVisit = !!cache.askOnVisit
            mirrors = (cache.mirrors || mirrors).map(normalizeHost)
          }
        } catch (e) { /* ignore cache parse errors */ }
    
        // Background refresh of storage to keep the cache fresh (non-blocking)
        (async () => {

    Category: Bug risk
    Severity: major

  87. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 78

    try {
            const s = await browser.storage.local.get({ allowedSites: [], selectedMirror: DEFAULTS.selectedMirror, askOnVisit: DEFAULTS.askOnVisit, mirrors: DEFAULTS.mirrors })
            const cache = { allowedSites: s.allowedSites || [], selectedMirror: normalizeHost(s.selectedMirror || DEFAULTS.selectedMirror), askOnVisit: !!s.askOnVisit, mirrors: (s.mirrors || DEFAULTS.mirrors).map(normalizeHost), ts: Date.now() }
            try { window.localStorage.setItem('__flean_cache', JSON.stringify(cache)) } catch (e) { /* ignore */ }
          } catch (e) { /* ignore background refresh errors */ }
        })()

    Category: Bug risk
    Severity: major

  88. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 96

    if (changes.mirrors) { cache.mirrors = (changes.mirrors.newValue || DEFAULTS.mirrors).map(normalizeHost); updated = true }
              if (updated) {
                cache.ts = Date.now()
                try { window.localStorage.setItem('__flean_cache', JSON.stringify(cache)) } catch (e) { /* ignore */ }
              }
            })
          } catch (e) { /* ignore if onChanged isn't available */ }

    Category: Bug risk
    Severity: major

  89. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 79

    const s = await browser.storage.local.get({ allowedSites: [], selectedMirror: DEFAULTS.selectedMirror, askOnVisit: DEFAULTS.askOnVisit, mirrors: DEFAULTS.mirrors })
            const cache = { allowedSites: s.allowedSites || [], selectedMirror: normalizeHost(s.selectedMirror || DEFAULTS.selectedMirror), askOnVisit: !!s.askOnVisit, mirrors: (s.mirrors || DEFAULTS.mirrors).map(normalizeHost), ts: Date.now() }
            try { window.localStorage.setItem('__flean_cache', JSON.stringify(cache)) } catch (e) { /* ignore */ }
          } catch (e) { /* ignore background refresh errors */ }
        })()
    
        // Keep the localStorage cache in sync immediately when preferences change.

    Category: Bug risk
    Severity: major

  90. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 262

    if (browser.runtime && typeof browser.runtime.openOptionsPage === 'function') {
                    browser.runtime.openOptionsPage(); return
                  }
                } catch (e) { /* try next */ }
                try { await browser.tabs.create({ url: browser.runtime.getURL('popup.html') }) } catch (e) { console.warn('Flean: could not open settings', e) }
              })
            }

    Category: Bug risk
    Severity: major

  91. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 170

    const suppressKey = '__flean_suppressed:' + pageKey
          const reloadKey = '__flean_reloads:' + pageKey
    
          function readSessionArray (k) { try { return JSON.parse(window.sessionStorage.getItem(k) || '[]') } catch (e) { return [] } }
          function writeSessionArray (k, arr) { try { window.sessionStorage.setItem(k, JSON.stringify(arr)) } catch (e) { /* ignore */ } }
    
          // Detect whether this page load is a browser reload (F5 / Cmd-R)

    Category: Bug risk
    Severity: major

  92. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 111

    if (Date.now() <= until) isSessionAllowed = true
            else window.sessionStorage.removeItem('__flean_allow_until')
          }
        } catch (e) { /* ignore */ }
    
        if ((allowedSites || []).includes(host) || isSessionAllowed) {
          console.log('Flean: host is in ignore list or session-allowed, skipping redirection for', host)

    Category: Bug risk
    Severity: major

  93. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 257

    if (browser.action && typeof browser.action.openPopup === 'function') {
                    await browser.action.openPopup(); return
                  }
                } catch (e) { /* try next */ }
                try {
                  if (browser.runtime && typeof browser.runtime.openOptionsPage === 'function') {
                    browser.runtime.openOptionsPage(); return

    Category: Bug risk
    Severity: major

  94. 'e2' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 54

    // If it's a full URL, URL() will succeed and we can read host
            return new URL(str).host.toLowerCase()
          } catch (e) {
            try { return new URL('https://' + str).host.toLowerCase() } catch (e2) { return str.toLowerCase() }
          }
        }

    Category: Bug risk
    Severity: major

  95. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 129

    // Extract page title
        let page = path.replace(/^\/wiki\//i, '')
        try { page = decodeURIComponent(page) } catch (e) { /* ignore */ }
    
        // Compute mirror URL (fallback)
        const pageForMirror = page.replace(/\s+/g, '_').replace(/^\/+|\/+$/g, '')

    Category: Bug risk
    Severity: major

  96. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 360

    if (browser.action && typeof browser.action.openPopup === 'function') {
                await browser.action.openPopup(); return
              }
            } catch (e) { /* try next */ }
            try {
              if (browser.runtime && typeof browser.runtime.openOptionsPage === 'function') {
                browser.runtime.openOptionsPage(); return

    Category: Bug risk
    Severity: major

  97. Avoid using console in code that runs on the browser (JS-0002)
    File: mos/Flean Extension/Resources/content.js
    Line: 114

    } catch (e) { /* ignore */ }
    
        if ((allowedSites || []).includes(host) || isSessionAllowed) {
          console.log('Flean: host is in ignore list or session-allowed, skipping redirection for', host)
          return
        }

    Category: Bug risk
    Severity: major

  98. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 187

    try {
            reloadCount = isReload ? (parseInt(window.sessionStorage.getItem(reloadKey) || '0', 10) + 1) : 0
            window.sessionStorage.setItem(reloadKey, String(reloadCount))
          } catch (e) { /* ignore */ }
    
          const recent = readSessionArray(attemptsKey).filter(ts => (now - ts) <= ATTEMPT_WINDOW_MS)
          recent.push(now)

    Category: Bug risk
    Severity: major

  99. 'e' is defined but never used (JS-0128)
    File: mos/Flean Extension/Resources/content.js
    Line: 99

    try { window.localStorage.setItem('__flean_cache', JSON.stringify(cache)) } catch (e) { /* ignore */ }
              }
            })
          } catch (e) { /* ignore if onChanged isn't available */ }
        }
    
        // Fast session-scoped allow (for immediate navigation within this tab)

    Category: Bug risk
    Severity: major

  100. Found async function without any await expressions (JS-0116)
    File: mos/Flean Extension/Resources/scripts/wiki-data-manager.js
    Line: 115-128

    let _wikiIndexPromise = null
    
    /** Ensure the in-memory lookup index is built. */
    async function ensureIndex () {
      if (_wikiIndex !== null) return _wikiIndex
      if (_wikiIndexPromise) return _wikiIndexPromise
      _wikiIndexPromise = getWikiData().then(data => {
        _wikiIndex = buildIndex(data)
        _wikiIndexPromise = null
        return _wikiIndex
      }).catch(() => {
        _wikiIndex = new Map()
        _wikiIndexPromise = null
        return _wikiIndex
      })
      return _wikiIndexPromise
    }
    
    /**
     * Invalidate the in-memory index so it is rebuilt on the next lookup.

    Category: Bug risk
    Severity: minor

...and 7 more occurrences. See full list on DeepSource.

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.

3 participants