Skip to content

Latest commit

 

History

History
818 lines (650 loc) · 29.4 KB

File metadata and controls

818 lines (650 loc) · 29.4 KB

Browser Extension Manager (BXM)

Identity

Browser Extension Manager (BXM) is a comprehensive framework for building modern browser extensions. It provides a template-based development system with built-in build tools, component architecture, theming support, and best practices for creating cross-browser extensions.

Quick Start

For Consuming Projects (Extensions Built Using BXM)

  1. Run npx bxm setup to initialize the project
  2. Run npm start to start development
  3. Load packaged/raw/ directory in browser as unpacked extension
  4. Edit files in src/ - changes auto-reload via WebSocket

For Framework Development (This Repository)

  1. Run npm start to watch and compile framework changes
  2. Test changes in a consuming project by linking: npm link (in BXM) then npm link browser-extension-manager (in consuming project)
  3. Changes in src/ compile to dist/ automatically

Architecture Overview

Component-Based System

Extensions are organized around components, each representing a distinct part:

Core Components:

  • background - Service worker (background script)
  • popup - Browser action popup
  • options - Options/settings page
  • sidepanel - Chrome side panel
  • content - Content scripts injected into web pages
  • pages - Custom extension pages (e.g., dashboard, welcome)
  • offscreen - Persistent offscreen document (WebSocket, long-running tasks)

Each component has three parts:

  1. View - HTML in src/views/[component]/index.html
  2. Styles - SCSS in src/assets/css/components/[component]/index.scss
  3. Script - JS in src/assets/js/components/[component]/index.js

Compilation output:

  • dist/views/[component]/index.html
  • dist/assets/css/components/[component].bundle.css
  • dist/assets/js/components/[component].bundle.js

Two-Tier Architecture

Framework Layer (src/ in this repository):

  • Core framework code and components
  • Default templates and configurations
  • Build system and tooling
  • Published to npm as browser-extension-manager

Project Layer (consuming extension projects):

  • User's extension-specific code
  • Overrides and customizations
  • Receives defaults from src/defaults/ via the defaults gulp task

Framework Structure (This Repository)

browser-extension-manager/
├── src/                                     # Framework source code
│   ├── assets/                              # Framework assets
│   │   ├── css/                             # SCSS framework
│   │   │   ├── browser-extension-manager.scss  # Main entry point
│   │   │   ├── core/                        # Core styles (utilities, animations, initialize)
│   │   │   ├── components/                  # Component-specific styles (popup, options, content, pages)
│   │   │   └── bundles/                     # Custom bundle SCSS files
│   │   ├── js/                              # JavaScript framework
│   │   │   └── main.js
│   │   ├── themes/                          # Theme system
│   │   │   ├── _template/                   # Template for creating new themes
│   │   │   ├── bootstrap/                   # Bootstrap theme (full Bootstrap 5 source)
│   │   │   └── classy/                      # Classy theme (Bootstrap + custom design system)
│   │   └── vendor/                          # Vendor assets
│   ├── defaults/                            # Default project structure (copied to consuming projects)
│   │   ├── src/
│   │   │   ├── assets/
│   │   │   │   ├── css/
│   │   │   │   │   ├── main.scss            # Global styles entry point
│   │   │   │   │   └── components/          # Component-specific overrides
│   │   │   │   ├── js/
│   │   │   │   │   └── components/          # Component JavaScript
│   │   │   │   ├── images/
│   │   │   │   └── vendor/
│   │   │   ├── views/                       # HTML views (templates)
│   │   │   ├── manifest.json                # Extension manifest (user-editable)
│   │   │   └── _locales/                    # Internationalization
│   │   ├── hooks/                           # Build hooks
│   │   ├── config/                          # Configuration files
│   │   └── CLAUDE.md                        # Project documentation template
│   ├── config/                              # Framework configuration
│   │   ├── manifest.json                    # Default manifest configuration
│   │   └── page-template.html               # HTML template for views
│   ├── gulp/                                # Build system
│   │   ├── main.js                          # Gulp entry point
│   │   ├── tasks/                           # Gulp tasks
│   │   │   ├── defaults.js                  # Copy default files to project
│   │   │   ├── distribute.js                # Distribute project files to dist/
│   │   │   ├── sass.js                      # Compile SCSS
│   │   │   ├── webpack.js                   # Bundle JavaScript
│   │   │   ├── html.js                      # Process HTML views
│   │   │   ├── icons.js                     # Generate icon sizes
│   │   │   ├── package.js                   # Package extension for distribution
│   │   │   ├── audit.js                     # Run audits
│   │   │   └── serve.js                     # Development server
│   │   └── plugins/                         # Custom plugins
│   │       └── webpack/
│   │           └── strip-dev-blocks.js      # Strip dev-only code
│   ├── lib/                                 # Utility libraries
│   │   ├── extension.js                     # Cross-browser extension API wrapper
│   │   ├── logger.js                        # Logging utility
│   │   └── logger-lite.js                   # Lightweight logger
│   ├── commands/                            # CLI commands
│   │   ├── setup.js                         # Setup project
│   │   ├── clean.js                         # Clean build artifacts
│   │   └── version.js                       # Version management
│   ├── build.js                             # Build manager class
│   ├── background.js                        # Background script manager
│   ├── popup.js                             # Popup manager
│   ├── options.js                           # Options manager
│   ├── sidepanel.js                         # Sidepanel manager
│   ├── page.js                              # Page manager
│   ├── content.js                           # Content script manager
│   └── cli.js                               # CLI entry point
├── dist/                                    # Compiled framework (published to npm)
├── bin/                                     # CLI binaries
│   └── browser-extension-manager
├── package.json
└── CLAUDE.md                                # This file

Consuming Project Structure

When users create a project using BXM, they get this structure:

my-extension/
├── src/
│   ├── assets/
│   │   ├── css/
│   │   │   ├── main.scss                    # Global styles
│   │   │   └── components/                  # Component-specific styles
│   │   ├── js/
│   │   │   └── components/                  # Component JavaScript
│   │   ├── images/
│   │   │   └── icon.png                     # Source icon (1024x1024)
│   │   └── vendor/
│   ├── views/                               # HTML templates
│   ├── _locales/                            # i18n translations
│   └── manifest.json                        # Extension manifest
├── config/
│   └── config.json                          # BXM configuration
├── hooks/
│   ├── build:pre.js                         # Pre-build hook
│   └── build:post.js                        # Post-build hook
├── dist/                                    # Compiled files (gitignored)
├── packaged/                                # Packaged extension (gitignored)
│   ├── raw/                                 # Load this in browser
│   └── extension.zip                        # Production build
└── package.json

Development Instructions

Creating a New Component

When adding a new component type to the framework:

  1. Create framework component styles:

    src/assets/css/components/[component]/index.scss
    
  2. Create default template files:

    src/defaults/src/assets/css/components/[component]/index.scss
    src/defaults/src/assets/js/components/[component]/index.js
    src/defaults/src/views/[component]/index.html
    
  3. Create manager class (if needed):

    // src/[component].js
    const Manager = require('./build.js');
    
    class ComponentManager extends Manager {
      constructor(options) {
        super(options);
      }
    }
    
    module.exports = ComponentManager;
  4. Export in package.json:

    {
      "exports": {
        "./component": "./dist/component.js"
      }
    }

Modifying the Build System

Key gulp tasks:

HTML Templating

HTML views in src/views/ are processed through a two-step templating system using {{ }} brackets.

Available variables:

  • {{ brand.name }} - Brand name from config
  • {{ brand.url }} - Brand URL from config
  • {{ page.name }} - Component name (e.g., popup, pages/index)
  • {{ page.path }} - Full view path
  • {{ page.title }} - Page title (defaults to brand name)
  • {{ theme.appearance }} - Theme appearance (dark or light)
  • {{ cacheBust }} - Cache-busting timestamp

Example usage in views:

<a href="{{ brand.url }}/pricing">Upgrade to Premium</a>
<p>Welcome to {{ brand.name }}</p>

How it works:

  1. Your view file (src/views/[component]/index.html) is templated first
  2. The result is injected into page-template.html
  3. The outer template is processed with the same variables

Modifying Themes

Theme location: src/assets/themes/

Available themes:

  • bootstrap - Pure Bootstrap 5.3+
  • classy - Bootstrap + custom design system
  • _template - Template for new themes

Theme structure:

src/assets/themes/[theme-id]/
├── _config.scss      # Theme variables (with !default)
├── _theme.scss       # Theme entry point
├── scss/             # Theme-specific SCSS
└── js/               # Theme-specific JS

To create a new theme:

  1. Copy _template/ to new directory
  2. Customize _config.scss variables
  3. Add theme-specific styles in scss/
  4. Users activate via config/config.json

Working with the Defaults System

Location: src/defaults/

How it works:

  1. Files in src/defaults/ are the starter template
  2. During build, they're copied to dist/defaults/
  3. When users run npx bxm setup, files copy from dist/defaults/ to their project
  4. File behavior controlled by FILE_MAP in gulp/tasks/defaults.js

File map rules:

const FILE_MAP = {
  'src/**/*': { overwrite: false },           // Never overwrite user code
  'hooks/**/*': { overwrite: false },         // Never overwrite hooks
  '.nvmrc': { template: { node: '22' } },    // Template with data
  // ...
};

Rule types:

  • overwrite: false - Never replace if exists
  • overwrite: true - Always update
  • skip: function - Dynamic skip logic
  • template: data - Run templating
  • name: function - Rename file

CSS Architecture

Main entry: src/assets/css/browser-extension-manager.scss

Core modules:

Component system: Each component can have framework defaults in src/assets/css/components/[name]/index.scss

Load path resolution:

  1. Framework CSS (node_modules/browser-extension-manager/dist/assets/css)
  2. Active theme (node_modules/browser-extension-manager/dist/assets/themes/[theme-id])
  3. Project dist (dist/assets/css)
  4. node_modules

This enables:

@use 'browser-extension-manager' as * with ($primary: #5B47FB);
@use 'theme' as *;  // Resolves to active theme
@use 'components/popup' as *;  // Import default component styles

JavaScript Architecture

Manager classes: src/background.js, src/popup.js, src/options.js, src/sidepanel.js, src/page.js, src/content.js

Extension API wrapper: src/lib/extension.js

A universal/agnostic API wrapper that enables cross-browser extension development. Write your extension once and it works on Chrome, Firefox, Edge, and other browsers.

How it works:

  • Detects and normalizes APIs from chrome.*, browser.*, and window.* namespaces
  • Automatically selects the correct API based on what's available in the current browser
  • Exports a singleton with unified access to all extension APIs

Supported APIs: action, alarms, bookmarks, browsingData, browserAction, certificateProvider, commands, contentSettings, contextMenus, cookies, debugger, declarativeContent, declarativeNetRequest, devtools, dns, documentScan, downloads, enterprise, events, extension, extensionTypes, fileBrowserHandler, fileSystemProvider, fontSettings, gcm, history, i18n, identity, idle, input, instanceID, management, notifications, offscreen, omnibox, pageAction, permissions, platformKeys, power, printerProvider, privacy, proxy, runtime, scripting, search, sessions, sidePanel, storage, tabGroups, tabs, topSites, tts, ttsEngine, userScripts, vpnProvider, wallpaper, webNavigation, webRequest, windows

Usage:

// Exposed via manager - no separate import needed
const Manager = new (require('browser-extension-manager/popup'));

Manager.initialize().then(() => {
  const { extension } = Manager;

  // Works on Chrome, Firefox, Edge, etc.
  extension.tabs.query({ active: true }, (tabs) => { ... });
  extension.storage.get('key', (result) => { ... });
  extension.runtime.sendMessage({ type: 'hello' });
});

Storage normalization: The wrapper automatically resolves storage to storage.sync if available, falling back to storage.local.

Auth Helpers: src/lib/auth-helpers.js

Provides cross-context auth synchronization and reusable auth UI event handlers.

Cross-Context Auth Architecture

Background.js is the source of truth for authentication. Browser extensions have multiple isolated JavaScript contexts (background, popup, options, pages, sidepanel) - each runs its own Firebase instance. BXM syncs them via messaging (no storage).

Sign-in Flow:

User clicks .auth-signin-btn
  → openAuthPage() opens https://{authDomain}/token?authSourceTabId=123
  → Website authenticates, redirects to /token?authToken=xxx
  → background.js tabs.onUpdated detects authDomain URL with authToken param
  → background.js signs in with signInWithCustomToken()
  → background.js broadcasts token to all open contexts via postMessage()
  → Closes the /token tab, reactivates original tab
  → Open contexts receive broadcast and sign in with the token

Context Load Flow:

Context loads (popup, page, options, sidepanel)
  → Web Manager initializes, waits for auth to settle via auth.listen({once: true})
  → Sends bxm:syncAuth message to background with local UID
  → Background compares UIDs:
    → Same UID (including both null) → already in sync, no action
    → Different UID → background fetches fresh custom token from server, sends to context
    → Background signed out, context signed in → tells context to sign out

Sign-out Flow:

User clicks .auth-signout-btn
  → Web Manager signs out that context's Firebase
  → setupSignOutListener() detects sign-out, sends bxm:signOut to background
  → background.js signs out its Firebase
  → background.js broadcasts bxm:signOut to all other contexts
  → All contexts sign out

Key Implementation Details:

  1. No storage: Auth state is NOT stored in chrome.storage. Firebase persists sessions in IndexedDB per context. Web Manager handles UI bindings.

  2. Firebase in service workers: Static ES6 imports are required. Dynamic import() fails with webpack chunking in service workers.

  3. Config path: authDomain is at config.firebase.app.config.authDomain (loaded via BXM_BUILD_JSON).

  4. Required permission: tabs permission needed for tabs.onUpdated listener.

Functions:

  • syncWithBackground(context) - Compares context's UID with background's UID on load, syncs if different
  • setupAuthBroadcastListener(context) - Listens for sign-in/sign-out broadcasts from background
  • setupSignOutListener(context) - Notifies background when context signs out
  • setupAuthEventListeners(context) - Sets up delegated click handlers for auth buttons
  • openAuthPage(context, options) - Opens auth page on the website with authSourceTabId for tab restoration

Auth Button CSS Classes:

Add these classes to your HTML elements to enable automatic auth handling:

Class Description Action
.auth-signin-btn Sign in button Opens /token page on website (authDomain)
.auth-signout-btn Sign out button Handled by Web Manager (triggers storage sync via auth listener)
.auth-account-btn Account button Opens /account page on website

Example usage:

<!-- Sign In Button (shown when logged out) -->
<button class="btn auth-signin-btn" data-wm-bind="@show !auth.user">
  Sign In
</button>

<!-- Account Dropdown (shown when logged in) -->
<div data-wm-bind="@show auth.user" hidden>
  <a class="auth-account-btn" href="#">Account</a>
  <button class="auth-signout-btn">Sign Out</button>
</div>

Reactive bindings:

  • data-wm-bind="@show auth.user" - Show when logged in
  • data-wm-bind="@show !auth.user" - Show when logged out
  • data-wm-bind="@text auth.user.displayName" - Display user's name
  • data-wm-bind="@text auth.user.email" - Display user's email
  • data-wm-bind="@attr src auth.user.photoURL" - Set avatar image src

Logger: src/lib/logger.js

Template replacement: Webpack plugin replaces markers during build:

  • %%% version %%% → package version
  • %%% brand.name %%% → brand name
  • %%% environment %%% → 'production' or 'development'
  • %%% liveReloadPort %%% → WebSocket port
  • %%% webManagerConfiguration %%% → JSON config

Build Hooks System

Hook locations:

  • hooks/build:pre.js - Before packaging
  • hooks/build:post.js - After packaging

Hook structure:

module.exports = async function (index) {
  // index contains build information
  console.log('Hook running...');
};

Implementation: src/gulp/tasks/package.js loads and executes hooks

CLI System

Entry point: bin/browser-extension-manager

CLI implementation: src/cli.js

Commands: src/commands/

Aliases in package.json:

{
  "bin": {
    "ext": "bin/browser-extension-manager",
    "xm": "bin/browser-extension-manager",
    "bxm": "bin/browser-extension-manager",
    "browser-extension-manager": "bin/browser-extension-manager"
  }
}

Best Practices for Framework Development

File Organization

  1. Keep framework code in src/ - Never edit dist/ directly
  2. Use modular design - Per Ian's standards, build modular code, not one giant file
  3. Maintain defaults/ - Keep template files up-to-date
  4. Document changes - Update CLAUDE.md

Coding Standards (Per Ian's Preferences)

Short-circuit pattern:

// Good
const $el = document.querySelector('...');
if (!$el) {
  return;
}
// Long code block

// Bad
if ($el) {
  // Long code block
}

Logical operators:

// Good
const a = b
  || c
  || d;

// Bad
const a = b ||
  c ||
  d;

DOM element naming:

const $submitBtn = document.querySelector('#submit');
const $emailInput = document.querySelector('#email');

File operations:

// Prefer fs-jetpack
const jetpack = require('fs-jetpack');
jetpack.write('file.txt', 'content');

Backwards Compatibility

Per Ian's instructions:

  • DO NOT make changes backwards compatible unless explicitly requested
  • Most changes are for unreleased code or local development
  • If we develop something and change it later, just change it - don't maintain old way
  • Only add backwards compatibility when specifically asked

Version Control

Commit:

  • src/ - All framework source code
  • package.json - Package configuration
  • Documentation (CLAUDE.md)

Ignore:

  • dist/ - Compiled framework (generated by prepare-package)
  • node_modules/

Testing Changes

Local Testing

  1. Make changes in src/
  2. Compile: npm start (watches and compiles to dist/)
  3. Link locally:
    npm link
  4. In consuming project:
    npm link browser-extension-manager
    npm start
  5. Test in browser: Load packaged/raw/ directory

Testing Build System

Test individual gulp tasks:

cd consuming-project/
npm run gulp -- [task-name]

Available tasks:

  • defaults - Test template copying
  • sass - Test SCSS compilation
  • webpack - Test JS bundling
  • html - Test HTML processing
  • package - Test extension packaging

Testing CLI

In this repository:

./bin/browser-extension-manager setup
./bin/browser-extension-manager clean
./bin/browser-extension-manager version

Or via npm:

npx bxm setup
npx bxm clean
npx bxm version

Publishing Updates

Preparation

  1. Update version in package.json
  2. Compile framework:
    npm run prepare
  3. Test in consuming project
  4. Update documentation if needed

Publishing to npm

npm publish

Published package includes:

  • dist/ - Compiled framework
  • bin/ - CLI binaries
  • package.json
  • README.md

Excluded via .npmignore:

  • src/ - Source code (users don't need this)
  • Development files

Common Development Tasks

Adding a Utility Class

Location: src/assets/css/core/_utilities.scss

// Add new utility
.shadow-lg {
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}

Adding a Theme Variable

Location: src/assets/themes/classy/_config.scss (or relevant theme)

$new-color: #FF6B6B !default;

// Make available to consumers
@forward '../bootstrap/scss/bootstrap.scss' with (
  $new-color: $new-color
);

Adding a Webpack Alias

Location: src/gulp/tasks/webpack.js

resolve: {
  alias: {
    '__new_alias__': path.resolve(paths.root, 'path/to/directory'),
  }
}

Adding a Template Replacement

Location: src/gulp/tasks/webpack.js

new ReplacePlugin({
  patterns: [
    {
      find: /%%% newVariable %%%/g,
      replacement: 'value'
    }
  ]
})

Modifying Default Manifest

Location: src/config/manifest.json

{
  // Add new default permission
  permissions: [
    'storage',
    'newPermission'
  ]
}

Compilation: src/gulp/tasks/package.js merges user manifest with defaults

Troubleshooting Framework Development

Changes not appearing in consuming project

  1. Rebuild framework: npm run prepare
  2. Reinstall in consuming project: npm install browser-extension-manager@latest
  3. Or use local link: npm link (in BXM) then npm link browser-extension-manager (in project)

Gulp task errors

  1. Check task file: src/gulp/tasks/
  2. Verify paths are correct
  3. Check for syntax errors
  4. Test task individually: npm run gulp -- task-name

SCSS compilation errors

  1. Check load paths in src/gulp/tasks/sass.js
  2. Verify theme structure
  3. Check for circular imports
  4. Test with minimal SCSS to isolate issue

Template replacement not working

  1. Check pattern in src/gulp/tasks/webpack.js
  2. Verify replacement value is correct
  3. Check if file is processed by webpack

Notable Dependencies

Web Manager

BXM integrates Web Manager for Firebase, analytics, and web services functionality. Each component manager initializes Web Manager automatically with configuration from config/config.json.

Web Manager API:

Usage in BXM:

const Manager = new (require('browser-extension-manager/popup'));

Manager.initialize().then(() => {
  const { webManager } = Manager;

  // Web Manager provides:
  // - Firebase (Firestore, Auth, Storage, etc.)
  // - Analytics
  // - User management
  // - Utilities
  const db = webManager.libraries.firebase.firestore();
});

Other Key Dependencies

  • Gulp - Build system and task automation
  • Webpack - JavaScript bundling with Babel transpilation
  • Sass - CSS preprocessing with advanced features
  • fs-jetpack - File operations (per Ian's preference)
  • wonderful-fetch - HTTP requests
  • wonderful-version - Version management
  • ws - WebSocket server for live reload

Resources

Key Files Reference

Build System:

Manager Classes:

Utilities:

Auth System (Cross-Context):

CSS Framework:

Templates:

CLI: