A unified browser extension that connects to the WhatPulse desktop client via WebSocket to enable website time insights.
The extension uses a local WebSocket connection to communicate with the WhatPulse client running on the same machine. It monitors active tabs and sends domain usage information when conditions are met.
├── background/ # Background service worker
│ └── background.js
├── popup/ # Extension popup UI
│ ├── popup.html
│ ├── popup.js
│ └── popup.css
├── options/ # Options page
│ ├── options.html
│ ├── options.js
│ └── options.css
├── icons/ # Extension icons (16, 32, 48, 128, 400px)
├── content.js # Content script for input tracking
├── manifest.json # Active manifest (copy of browser-specific manifest)
├── manifest-chrome.json # Chrome/Chromium manifest
├── manifest-firefox.json # Firefox manifest
├── manifest-safari.json # Safari manifest
├── switch-target.sh # Script to switch between browser targets
└── release.sh # Script to create a new release
- Chromium-based: Chrome, Edge, Brave, Opera, Vivaldi, Arc, Yandex, Samsung Internet
- Firefox: Firefox and Firefox-based browsers
- Safari: macOS Safari (requires native messaging)
- Local-only: Extensions only connect to
ws://127.0.0.1:<port>(localhost) - Domain-only tracking: Only eTLD+1 domains are sent, never full URLs
- No history storage: Extensions don't store any browsing data
- Explicit opt-in: Users must install the extension and approve pairing
- Private browsing: No data is sent for private/incognito windows
For full details, see the WhatPulse Privacy Policy.
- Switch to the Chrome manifest:
./switch-target.sh chrome
- Open
chrome://extensions/(oredge://extensions/for Edge) - Enable "Developer mode"
- Click "Load unpacked"
- Select this directory
- Click the extension icon - the extension will request pairing with WhatPulse
- Approve the pairing request in the WhatPulse client
- Switch to the Firefox manifest:
./switch-target.sh firefox
- Open
about:debugging#/runtime/this-firefox - Click "Load Temporary Add-on"
- Select the
manifest.jsonfile - Click the extension icon - the extension will request pairing with WhatPulse
- Approve the pairing request in the WhatPulse client
- Switch to the Safari manifest:
./switch-target.sh safari
- Safari extensions require additional setup through Xcode and the Safari Extension Builder
- Native messaging must be configured for Safari support
The extension uses a pairing-based authentication system:
- Extension connects to WebSocket and sends
hellomessage withoutauth_token - WhatPulse client emits
pairingRequestedsignal, showing UI notification to user - User approves pairing in WhatPulse client
- Server sends
pairing_approvedmessage containing theauth_token - Extension stores
auth_tokenin browser storage for future connections
- Extension connects and sends
hellomessage with storedauth_token - Server validates token and responds with
hello_ack - Normal message exchange begins
If the auth_token is invalid or expired, the server sends an error message with code AUTH_FAILED and closes the connection. The extension should clear its stored token and restart the pairing flow.
All messages are JSON objects with the following common fields:
| Field | Type | Description |
|---|---|---|
schema_version |
int | Protocol version (currently 1) |
type |
string | Message type |
ts |
int64 | Timestamp (UNIX epoch milliseconds) |
client_id |
string | Stable UUID per extension install |
Initial handshake message.
{
"schema_version": 1,
"type": "hello",
"ts": 1704067200000,
"client_id": "550e8400-e29b-41d4-a716-446655440000",
"auth_token": "abc123...",
"browser": {
"name": "chrome",
"version": "120.0.6099.109"
},
"capabilities": ["activeTab", "visibility", "url", "usageReport"],
"ext_version": "1.0.0"
}- Omit
auth_tokenfor initial pairing request capabilities: Features the extension supports (e.g.,activeTab,visibility,url,usageReport)browser.name: Detected browser name (e.g.,chrome,edge,brave,firefox,safari,opera,vivaldi,arc,yandex,samsung)
Successful authentication response.
{
"schema_version": 1,
"type": "hello_ack",
"ts": 1704067200000,
"server_time": 1704067200500,
"session_token": "session-xyz-789"
}Sent after user approves pairing request.
{
"schema_version": 1,
"type": "pairing_approved",
"ts": 1704067200000,
"auth_token": "new-auth-token-abc123"
}Extension should store this auth_token for future connections.
Tab focus state changed.
{
"schema_version": 1,
"type": "focus",
"ts": 1704067200000,
"client_id": "...",
"tab_id": "123",
"is_focused": true
}Tab visibility state changed.
{
"schema_version": 1,
"type": "visibility",
"ts": 1704067200000,
"client_id": "...",
"tab_id": "123",
"is_visible": true
}User navigated to a new domain.
{
"schema_version": 1,
"type": "active_domain",
"ts": 1704067200000,
"client_id": "...",
"domain": "github.com",
"window_id": "1",
"tab_id": "123"
}Periodic keepalive with current state. Currently not used - the extension uses usage_report instead.
{
"schema_version": 1,
"type": "heartbeat",
"ts": 1704067200000,
"client_id": "...",
"domain": "github.com",
"is_focused": true,
"is_visible": true,
"interval_ms": 1000
}Accumulated time and input stats per domain. Sent every 30 seconds.
{
"schema_version": 1,
"type": "usage_report",
"ts": 1704067200000,
"client_id": "...",
"period_start": 1704060000000,
"period_end": 1704067200000,
"report": [
{
"domain": "github.com",
"seconds": 25,
"keys": 150,
"clicks": 30,
"scrolls": 42,
"mouse_distance_in": 12.5
},
{
"domain": "youtube.com",
"seconds": 10,
"keys": 0,
"clicks": 5,
"scrolls": 15,
"mouse_distance_in": 3.2
}
]
}Validation limits: Max 35 seconds per domain, max 35 seconds total per report (matches ~30s reporting interval).
Domain metadata (favicon URLs) from extension.
{
"schema_version": 1,
"type": "metadata_update",
"ts": 1704067200000,
"client_id": "...",
"domain": "github.com",
"favicon_urls": [
"https://github.com/favicon.ico",
"https://github.githubassets.com/favicons/favicon.svg"
]
}Clean disconnect notification.
{
"schema_version": 1,
"type": "goodbye",
"ts": 1704067200000,
"client_id": "...",
"reason": "extension_disabled"
}Error response.
{
"schema_version": 1,
"type": "error",
"ts": 1704067200000,
"error_code": "AUTH_FAILED",
"reason": "Invalid authentication token"
}Error codes:
AUTH_FAILED: Invalid or expired auth tokenINVALID_MESSAGE: Malformed messageRATE_LIMITED: Too many messages
You can test the connection using the browser console:
// Pairing request (no auth token)
const ws = new WebSocket('ws://127.0.0.1:3488');
ws.onopen = () => {
ws.send(JSON.stringify({
schema_version: 1,
type: 'hello',
ts: Date.now(),
client_id: 'test-client-' + Math.random().toString(36).substr(2, 9),
browser: { name: 'chrome', version: '120.0' },
capabilities: ['activeTab', 'visibility'],
ext_version: '1.0.0'
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log('Received:', msg);
if (msg.type === 'pairing_approved') {
console.log('Save this auth_token:', msg.auth_token);
}
};// Authenticated connection (with auth token)
const ws = new WebSocket('ws://127.0.0.1:3488');
ws.onopen = () => {
ws.send(JSON.stringify({
schema_version: 1,
type: 'hello',
ts: Date.now(),
auth_token: 'YOUR_SAVED_TOKEN_HERE',
client_id: 'test-client-abc123',
browser: { name: 'chrome', version: '120.0' },
capabilities: ['activeTab', 'visibility'],
ext_version: '1.0.0'
}));
};
ws.onmessage = (event) => console.log('Received:', event.data);The extension stores configuration in browser local storage:
clientId: Stable UUID for this extension installauthToken: Authentication token (received from pairing)enabled: Feature toggle (default: true)metadataSentTimes: Cache of when favicons were last sent per domain
The WebSocket port is fixed at 3488 and must match the WhatPulse desktop client.
Use the release.sh script to create a new release:
./release.sh 1.0.0To repackage an existing version (overwrites the tag):
./release.sh 1.0.0 --forceThis script:
- Updates the version in all
manifest-*.jsonfiles - Commits the version change
- Creates a git tag (e.g.,
v1.0.0) - Pushes to main and the release branch
- Triggers the GitHub Actions build workflow
The GitHub Actions workflow then:
- Builds extensions for Chrome and Firefox
- Uploads to R2 storage
- Sends a Discord notification
Release artifacts are available at:
https://releases-dev.whatpulse.org/browser-extensions/v{version}-chrome-extension.ziphttps://releases-dev.whatpulse.org/browser-extensions/v{version}-firefox-extension.zip
For browser store releases, extensions should be packaged and signed through the respective browser stores.
