Browser Extension
Browser Extension
Section titled “Browser Extension”The BitMarks browser extension syncs your bookmarks across devices with end-to-end encryption.
Installation
Section titled “Installation”Chrome Web Store
Section titled “Chrome Web Store”The extension will be available on the Chrome Web Store.
Manual Installation (Development)
Section titled “Manual Installation (Development)”-
Clone the Repository
Section titled “Clone the Repository”Terminal window git clone https://github.com/bitmarks-sh/bitmarks.gitcd bitmarks -
Install Dependencies
Section titled “Install Dependencies”Terminal window bun install -
Build the Extension
Section titled “Build the Extension”Terminal window cd packages/webextensionbun run build -
Load in Chrome
Section titled “Load in Chrome”- Open
chrome://extensions - Enable “Developer mode”
- Click “Load unpacked”
- Select the
packages/webextension/distfolder
- Open
Features
Section titled “Features”Bookmark Sync
Section titled “Bookmark Sync”- Automatic sync: Changes sync in real-time across devices
- Offline support: Changes queue when offline, sync when online
- Conflict resolution: Last-Write-Wins with device ID tiebreaker
Multiple Data Sources
Section titled “Multiple Data Sources”| Source | Description |
|---|---|
| Bookmarks | Browser bookmarks from the bookmarks bar and folders |
| History | Browsing history (optional) |
| Tab Groups | Chrome tab groups (optional) |
| GitHub Stars | Your starred repositories (optional) |
View Modes
Section titled “View Modes”- Table View: Spreadsheet-like interface with sortable columns
- Cards View: Visual card grid layout
- Finder View: macOS Finder-style hierarchical browser
- Reader View: Distraction-free reading mode
Configuration
Section titled “Configuration”Extension Settings
Section titled “Extension Settings”Access settings via the extension popup or chrome://extensions:
interface ExtensionSettings { // Sync settings syncEnabled: boolean; autoSync: boolean; syncInterval: number; // milliseconds
// Data sources sources: { bookmarks: boolean; history: boolean; tabGroups: boolean; githubStars: boolean; };
// View settings defaultView: 'table' | 'cards' | 'finder' | 'reader'; theme: 'light' | 'dark' | 'system';
// GitHub integration githubToken?: string;}Default Settings
Section titled “Default Settings”const defaultSettings: ExtensionSettings = { syncEnabled: true, autoSync: true, syncInterval: 300000, // 5 minutes sources: { bookmarks: true, history: false, tabGroups: false, githubStars: false }, defaultView: 'table', theme: 'system'};Architecture
Section titled “Architecture”packages/webextension/├── src/│ ├── background.ts # Service worker│ ├── main.tsx # Popup entry│ ├── options.tsx # Options page│ ├── components/ # React components│ │ ├── views/ # View mode components│ │ └── ...│ ├── contexts/ # React contexts│ ├── hooks/ # Custom hooks│ ├── lib/ # Utilities│ │ ├── sync-manager.ts│ │ ├── storage.ts│ │ └── ...│ └── providers/ # Data providers│ ├── ChromeAPIProvider.ts│ ├── ChromeHistoryProvider.ts│ ├── ChromeTabGroupsProvider.ts│ └── GitHubStarsProvider.ts├── public/│ └── manifest.json└── package.jsonBackground Service Worker
Section titled “Background Service Worker”The background script handles:
- Bookmark change listeners
- Real-time WebSocket sync
- Periodic sync polling
- Message passing with popup
import { SyncManager } from './lib/sync-manager';
const syncManager = new SyncManager();
// Listen for bookmark changeschrome.bookmarks.onCreated.addListener(async (id, bookmark) => { await syncManager.pushChange('bookmark', id, 'create', bookmark);});
chrome.bookmarks.onChanged.addListener(async (id, changes) => { await syncManager.pushChange('bookmark', id, 'update', changes);});
chrome.bookmarks.onRemoved.addListener(async (id) => { await syncManager.pushChange('bookmark', id, 'delete');});Data Providers
Section titled “Data Providers”Each data source has a provider:
interface DataProvider<T> { name: string; fetch(): Promise<T[]>; subscribe?(callback: (changes: T[]) => void): () => void;}export class ChromeAPIProvider implements DataProvider<Bookmark> { name = 'bookmarks';
async fetch(): Promise<Bookmark[]> { const tree = await chrome.bookmarks.getTree(); return this.flattenTree(tree); }
subscribe(callback: (changes: Bookmark[]) => void) { const listener = () => this.fetch().then(callback);
chrome.bookmarks.onCreated.addListener(listener); chrome.bookmarks.onChanged.addListener(listener); chrome.bookmarks.onRemoved.addListener(listener);
return () => { chrome.bookmarks.onCreated.removeListener(listener); chrome.bookmarks.onChanged.removeListener(listener); chrome.bookmarks.onRemoved.removeListener(listener); }; }}Authentication
Section titled “Authentication”Login Flow
Section titled “Login Flow”- User clicks “Login” in extension popup
- Extension opens OAuth URL in new tab
- User authenticates with WorkOS
- Callback sets session cookie
- Extension detects cookie, stores session
- Popup shows authenticated state
// In popupconst handleLogin = async () => { const response = await fetch('https://app.bitmarks.sh/api/v1/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ returnTo: '/auth/callback' }) });
const { authorizationUrl } = await response.json();
// Open in new tab chrome.tabs.create({ url: authorizationUrl });};Session Management
Section titled “Session Management”export async function getSession() { const { session } = await chrome.storage.local.get('session'); return session;}
export async function setSession(session: Session) { await chrome.storage.local.set({ session });}
export async function clearSession() { await chrome.storage.local.remove('session');}Encryption Key Management
Section titled “Encryption Key Management”Key Setup (First Login)
Section titled “Key Setup (First Login)”async function setupEncryption(password: string) { // Get salt from server const response = await fetch('/api/v1/auth/session', { credentials: 'include' }); const { user } = await response.json();
// Derive key from password const key = await deriveKey(password, user.salt);
// Store encrypted with device key const deviceKey = await getOrCreateDeviceKey(); const encryptedKey = await encrypt(key, deviceKey);
await chrome.storage.local.set({ encryptedKey });}Key Retrieval
Section titled “Key Retrieval”async function getEncryptionKey(): Promise<Uint8Array | null> { const { encryptedKey } = await chrome.storage.local.get('encryptedKey'); if (!encryptedKey) return null;
const deviceKey = await getDeviceKey(); return decrypt(encryptedKey, deviceKey);}Development
Section titled “Development”Running Locally
Section titled “Running Locally”cd packages/webextensionbun run devThis starts Vite in watch mode. Load the dist folder in Chrome.
Building for Production
Section titled “Building for Production”bun run buildTesting
Section titled “Testing”bun run testTroubleshooting
Section titled “Troubleshooting”Extension Not Syncing
Section titled “Extension Not Syncing”- Check if logged in (popup shows user info)
- Verify sync is enabled in settings
- Check console for errors (
chrome://extensions→ “Errors”) - Try manual sync (click sync button in popup)
Login Issues
Section titled “Login Issues”- Clear extension data
- Try logging out and back in
- Check if cookies are blocked
Performance Issues
Section titled “Performance Issues”- Reduce sync frequency in settings
- Disable unused data sources
- Check for large bookmark collections (>10k)
Permissions
Section titled “Permissions”The extension requires these permissions:
{ "permissions": [ "bookmarks", // Access bookmarks "storage", // Store settings "identity", // OAuth flow "tabs", // Open auth tab "alarms" // Periodic sync ], "optional_permissions": [ "history", // History sync "tabGroups" // Tab group sync ], "host_permissions": [ "https://app.bitmarks.sh/*", "https://api.github.com/*" ]}