This file provides guidance to AI coding agents (Codex, etc.) when working with code in this repository.
BonusVarsler is a browser extension and userscript that displays notifications when users visit online stores that offer cashback bonus through various loyalty programs.
- Done: Trumf, re:member, DNB support
- Next: Add OBOS, SAS EuroBonus support
The project uses TypeScript with a modular architecture. Source code is in src/ and bundled to output files.
src/
├── main.ts # Shared initialization logic
├── config/
│ ├── constants.ts # Storage keys, timeouts, URLs
│ ├── services.ts # Service definitions and fallbacks
│ └── domain-aliases.ts # Domain → canonical host mappings
├── core/
│ ├── settings.ts # User settings management
│ ├── feed.ts # Feed fetching and caching
│ ├── merchant-matching.ts # Host matching logic
│ └── adblock-detection.ts # Adblock detection
├── platform/
│ ├── extension.ts # Browser extension entry point
│ └── userscript.ts # Userscript entry point
├── storage/
│ ├── types.ts # StorageAdapter interface
│ ├── extension-storage.ts # browser.storage.local adapter
│ └── gm-storage.ts # GM_getValue/setValue adapter
├── network/
│ ├── types.ts # FetchAdapter interface
│ ├── extension-fetch.ts # Extension fetch (via background)
│ └── gm-fetch.ts # GM_xmlhttpRequest adapter
├── i18n/
│ ├── types.ts # I18nAdapter interface
│ ├── extension-i18n.ts # Extension i18n (loads _locales)
│ └── static-i18n.ts # Static Norwegian strings (userscript)
└── ui/
├── views/
│ ├── notification.ts # Main notification UI
│ ├── service-selector.ts # First-run service picker
│ └── reminder.ts # Cashback portal reminder
├── components/
│ ├── shadow-host.ts # Shadow DOM container
│ ├── draggable.ts # Corner-snap drag behavior
│ └── icons.ts # Base64 icon data
└── styles/
├── index.ts # Style exports
├── notification.css # Main notification styles
└── service-selector.css
content.js- Extension content script (bundled fromsrc/platform/extension.ts)BonusVarsler.user.js- Userscript (bundled fromsrc/platform/userscript.ts)Trumf-Bonusvarsler-Lite.user.js- Legacy userscript copy
background.js- Extension service worker for feed fetching (handles CORS)options.html/js/css- Settings page (standalone, not bundled)manifest.json- Extension manifest (Manifest V3)_locales/*/messages.json- i18n translations (6 languages)data/services.json- Canonical service definitionsdata/sitelist.json- Merchant feed (generated by scraper)data/remember-domains.json- Manual domain mappings for re:member
build.js- Main build script (downloads feeds, checks CSP, bundles TS, creates packages)scripts/bundle.ts- Bun bundler for TypeScript → JSscripts/scrape-feeds.ts- Scrapes merchant data from all services
The codebase uses dependency injection for platform-specific APIs:
interface PlatformAdapters {
storage: StorageAdapter; // Persistent storage
sessionStorage: SessionStorageAdapter; // Session-only storage
fetcher: FetchAdapter; // Network requests
i18n: I18nAdapter; // Translations
}Each platform (extension/userscript) provides its own implementations in src/platform/*.ts.
- First-run service selector: New users pick which services to enable
- Adblock detection: URL fetch + DOM banner checks (skips on CSP-restricted sites)
- Settings pane: Theme, start minimized, default position, hidden sites
- Draggable: Drag notification to any corner, position saved per-site
- i18n: 6 languages (no, en, sv, da, fr, es)
- Per-site hiding: Permanently hide notifications for specific sites
- Minimized mode: Collapses to header with cashback badge
- Reminder notification: Shows on cashback portal pages
- Shadow DOM isolates styles from host page
- CSS custom properties (
--bg,--text,--accent, etc.) enable theming - Theme applied via class on shadow host:
tbvl-light,tbvl-dark,tbvl-system - Feed cached in browser storage for 48 hours
- CSP-restricted sites skip URL-based adblock checks
Defined in src/config/constants.ts:
BonusVarsler_FeedData_v1/_FeedTime_v1/_HostIndex_v1- Cached feedBonusVarsler_HiddenSites- Array of hidden hostnamesBonusVarsler_Theme- "light", "dark", or "system"BonusVarsler_StartMinimized- BooleanBonusVarsler_Position- Default positionBonusVarsler_SitePositions- Per-site position overridesBonusVarsler_Language- Language codeBonusVarsler_EnabledServices- Array of enabled service IDsBonusVarsler_SetupComplete- Boolean (first-run flow)BonusVarsler_SetupShowCount- Number (first-run flow)
scripts/scrape-feeds.ts requires these environment variables for Visma LogBuy scraping:
LOGBUY_USERNAMELOGBUY_PASSWORDLOGBUY_ACCESSKEY
Use values from an authorized LogBuy API/extension account. Do not commit them. In GitHub Actions, configure repository secrets with these exact names; .github/workflows/update-feed.yml validates them and passes them to the scraper step.
node build.jsThis will:
- Run
scripts/scrape-feeds.tsto fetch merchant data - Check CSP on merchant sites (cached for 24h)
- Update CSP_RESTRICTED_SITES in source files
- Bundle TypeScript via
scripts/bundle.ts - Create
dist/bonusvarsler-X.X.X.xpianddist/bonusvarsler-X.X.X-chrome.zip
The scraper caches results for 5 hours in .scraper-cache.json.
Delete the cache file to force a fresh scrape.
- Tracking-based (like Trumf, re:member): User clicks through to service site
- Code-based (like DNB): User gets a rebate code to enter at checkout
-
data/services.json- Add service definition:"serviceid": { "id": "serviceid", "name": "Service Name", "clickthroughUrl": "https://...", "reminderDomain": "service.no", "color": "#HEXCOLOR", "defaultEnabled": false, "type": "code" }
-
src/config/services.ts- Add toSERVICES_FALLBACKandSERVICE_ORDER -
options.js- Add toSERVICES_FALLBACK(~line 7) -
_locales/*/messages.json- Add service-specific i18n strings (all 6 locales) -
scripts/scrape-feeds.ts- Add scraping function for the new service
# Adjust hue value (0-200) to match service color
convert icon-64.png -modulate 100,100,HUE icon-64-serviceid.pngThen add base64 data to src/ui/components/icons.ts.
For code-based services (type: "code"):
- Button shows the rebate code instead of "Get X bonus"
- First click copies code, second click opens link
- Skip adblock detection (codes work regardless)
- Add service-specific checklist instructions
The userscript UI is in Norwegian. The extension supports multiple languages via _locales/. Use Norwegian for the primary language.