A personal, self-hosted browser extension to sync open tabs across devices (Chromium & Firefox).
- π Real-time sync of open tabs across all your devices
- π₯ Firebase Firestore backend (self-hosted)
- π« No authentication required - single tenant architecture
- π Dark mode UI with Tailwind CSS
- π¦ Cross-browser support (Chrome, Firefox, Edge)
- πΎ Dynamic configuration - provide your own Firebase config
- Frontend: React + TypeScript + Vite
- Build Tool: CRXJS (Vite plugin for extensions)
- Styling: Tailwind CSS
- Backend: Firebase Firestore (no Auth)
- Cross-Browser: webextension-polyfill
pnpm installPlace the following icon files in src/assets/:
icon-16.png(16x16)icon-32.png(32x32)icon-48.png(48x48)icon-128.png(128x128)
- Go to Firebase Console
- Create a new project (or use existing)
- Enable Firestore Database
- Go to Project Settings β General
- Scroll to "Your apps" and copy the
firebaseConfigobject - You'll paste this into the extension on first run
For Chrome/Edge (with packaging):
pnpm build:chromeThis creates a ZIP file in the build/ folder.
For Firefox (with packaging):
pnpm build:firefoxThis creates an XPI file in the build/ folder.
Development build (no packaging):
pnpm buildChrome/Edge (Development):
- Go to
chrome://extensions/ - Enable "Developer mode"
- Click "Load unpacked"
- Select the
distfolder
Chrome/Edge (Packaged CRX):
- Go to
chrome://extensions/ - Enable "Developer mode"
- Click "Pack extension"
- Select the
distfolder as the extension root - Chrome will generate a
.crxfile and a.pemkey - Drag the
.crxfile into the extensions page
Firefox (Development):
- Go to
about:debugging#/runtime/this-firefox - Click "Load Temporary Add-on"
- Select any file in the
distfolder
Firefox (Packaged XPI):
- The XPI file from
build/can be installed temporarily - For permanent installation, sign it at addons.mozilla.org
Step 1: Enter Firebase Configuration
- Click the extension icon
- You'll see a configuration form
- Paste your Firebase config JSON
- Click "Save Configuration"
Step 2: Select or Create Device
-
If you have existing devices:
- You'll see a list of all devices currently synced
- Click on any device to sync with it (merges your current tabs with that device)
- Or click "Create New Device" to add this as a new device
-
If this is your first device:
- Click "Create New Device"
- Enter a descriptive name (e.g., "Work Laptop", "Home Desktop")
- Click "Create Device"
- The extension automatically syncs your tabs every 2 seconds (debounced)
- Click the extension icon to see all synced devices
- Click on a device to expand and see its tabs
- Click any tab to open it
- Click "Open All" to open all tabs from another device
- Hover over a tab from another device to see the close button (Γ) - click to close that tab remotely!
TabSync implements a command pattern for remote operations:
- When you click the "Γ" button on a tab from Device A (while viewing from Device B), Device B writes a command to Firestore
- The command is stored in:
devices/DeviceA/commands/{commandId} - Device A's background script listens to its commands subcollection
- When Device A receives the command, it executes the action (closes the tab) and deletes the command document
If you need to change your Firebase config or switch devices:
- Open the extension popup
- Click "Reset Config"
- Confirm the reset
- You'll be taken back to the configuration screen
- Follow the first-run setup again
- Waits for Firebase config to be available in storage
- Watches for tab changes (create, update, remove, move)
- Debounces writes to Firestore (2 seconds)
- Writes to:
devices/{deviceId} - Listens to
devices/{deviceId}/commandsfor remote commands - Executes commands (close tab, open tab) and marks them as done
- ConfigForm: Shows on first run to collect Firebase config
- DeviceList: Shows all synced devices and their tabs
- Real-time listener to Firestore for device updates
devices/
{deviceId}/
deviceName: string
lastUpdated: timestamp
tabCount: number
tabs: [
{
id: number
url: string
title: string
favIconUrl: string
windowId: number
index: number
active: boolean
pinned: boolean
}
]
commands/ (subcollection)
{commandId}/
action: "closeTab" | "openTab"
tabId: number (for closeTab)
url: string (for openTab)
createdAt: timestamp
fromDevice: string
# Install dependencies
pnpm install
# Development mode (hot reload)
pnpm dev
# Build for production (Chrome/Edge)
pnpm build
# Build for production (Firefox)
set TARGET=firefox
pnpm build
# Build and package for Chrome
pnpm build:chrome
# Build and package for Firefox
pnpm build:firefoxSince this is self-hosted and single-tenant, you can use simple rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /devices/{deviceId} {
allow read, write: if true;
match /commands/{commandId} {
allow read, write: if true;
}
}
}
}Google blocks Tor exit node IPs from accessing firestore.googleapis.com directly.
To use TabSync in Tor Browser, deploy the included Cloudflare Worker as a proxy.
Prerequisites: Cloudflare account (free tier is fine) + wrangler CLI
pnpm install -g wrangler
wrangler login1. Create the worker
wrangler deploy scripts/cf-worker.js --name tabsync-proxy --compatibility-date 2024-01-012. Set your Firebase API key as a secret (never stored in code)
wrangler secret put FIREBASE_API_KEY
# paste your Firebase API key when prompted3. Configure TabSync
In the TabSync setup form, paste the worker URL into the "Proxy URL" field, e.g.:
https://tabsync-proxy.yourname.workers.dev
All Firestore requests route through your worker instead of hitting Google directly.
The worker injects the FIREBASE_API_KEY secret server-side, so your key is never sent over Tor.
MIT License - See LICENSE file for details
This is a personal project, but feel free to fork and customize for your own use!