@just-every/code-bridge
v0.1.5
Published
Development-only bridge for capturing errors and logs in web, Node, and React Native
Maintainers
Readme
@just-every/code-bridge
Development-only bridge for capturing errors and logs from web, Node.js, and React Native applications and sending them to a centralized debugging server.
What’s included:
- Client SDK (
startBridge) for web/Node/RN (auto-ensures a host in dev) - Shared host daemon (
code-bridge-host) that fans out events from many bridge clients to many consumers - Workspace demo scripts for quick verification with Code
Features
- Zero production overhead: Completely no-op unless dev mode is detected
- Universal: Works in web browsers, Node.js, and React Native
- Auto-detection: Automatically detects platform and dev mode
- Global error capture: Hooks into unhandled errors and promise rejections
- Console patching: Intercepts console.log/info/warn/error/debug calls
- Breadcrumbs: Tracks history of events for better debugging context
- Throttling: Built-in throttling to prevent event spam
- Tree-shakeable: Won't bloat your production bundles
Installation
npm install @just-every/code-bridge
# or
yarn add @just-every/code-bridge
# or
pnpm add @just-every/code-bridgeQuick Start
Web Application
import { startBridge } from '@just-every/code-bridge';
// Initialize at app startup
const bridge = startBridge({
url: 'ws://localhost:9876',
secret: 'dev-secret',
projectId: 'my-web-app',
});
// The bridge will now capture all errors and console logs
// Clean up when needed (e.g., on unmount)
// bridge.disconnect();Node.js
import { startBridge } from '@just-every/code-bridge';
const bridge = startBridge({
url: 'ws://localhost:9876',
secret: 'dev-secret',
projectId: 'my-node-app',
});
// Your app code hereReact Native
import { useEffect } from 'react';
import { startBridge } from '@just-every/code-bridge';
function App() {
useEffect(() => {
const bridge = startBridge({
url: 'ws://localhost:9876',
secret: 'dev-secret',
projectId: 'my-rn-app',
});
return () => bridge.disconnect();
}, []);
// Rest of your app
}Configuration Options
interface BridgeOptions {
// WebSocket URL (default: ws://localhost:9876)
url?: string;
// Port number (used if url is not provided)
port?: number;
// Shared secret for authentication
secret?: string;
// Project identifier
projectId?: string;
// Maximum breadcrumbs to keep (default: 50)
maxBreadcrumbs?: number;
// Throttle delay in ms (default: 100)
throttleMs?: number;
// Force enable/disable (overrides auto-detection)
enabled?: boolean;
// Enable pageview tracking (default: false, dev-only)
enablePageview?: boolean;
// Enable SPA navigation tracking (History API + hashchange) (default: true, dev-only)
enableNavigation?: boolean;
// Enable network capture (fetch/XHR in web; http/https/fetch in Node) (default: true, dev-only)
enableNetwork?: boolean;
// Enable screenshot sending (default: false, dev-only)
enableScreenshot?: boolean;
// Optional provider used when sending screenshots
screenshotProvider?: () => Promise<{ mime: string; data: string }>;
// Enable two-way control channel (default: false, dev-only)
enableControl?: boolean;
}Auto-Enable Behavior
The bridge automatically enables itself in development when you provide a url or secret:
// In dev mode (NODE_ENV=development, Vite DEV, or React Native __DEV__)
// this auto-enables because url is provided:
startBridge({
url: 'ws://localhost:9876',
projectId: 'my-app',
});Gating Priority:
CODE_BRIDGE=1environment variable → force on (overrides everything)enabled: falsein options → force offenabled: truein options → force on- Dev mode detected + (
urlorsecretprovided) → auto-enable - Otherwise → disabled (production default, tree-shakeable)
Dev mode detection:
- Node.js:
process.env.NODE_ENV === 'development' - Vite:
import.meta.env.DEV - React Native:
__DEV__
This means you can safely call startBridge({ url, secret }) in your code and it will:
- Auto-enable in development when config is present
- Stay disabled in production builds (zero overhead)
- Be tree-shaken out by bundlers when disabled
Full-Fidelity Capture (dev-only)
Browser (console/errors/navigation/network/control + optional screenshots)
<script type="module">
import { startBridge } from '@just-every/code-bridge';
startBridge({
enabled: true,
enableNavigation: true,
enableNetwork: true,
enableControl: true,
enableScreenshot: true,
screenshotProvider: async () => ({ mime: 'image/png', data: 'iVBORw0KGgo=' }), // plug in html2canvas, etc.
});
</script>Node (console/errors/network/control)
const { startBridge } = require('@just-every/code-bridge');
const bridge = startBridge({ enabled: true, enableNetwork: true, enableControl: true });
await fetch('https://httpbin.org/status/404'); // emits network error event
// bridge.disconnect();Notes:
- Network capture flags 4xx/5xx as
error-level; other responses areinfo. - Navigation and network capture are enabled by default; set
enableNavigation: falseorenableNetwork: falseto opt out. - Screenshots are only sent when both the bridge advertises
screenshotand a consumer subscribes. - Control requests/responses are correlated by
id; consumer sendscontrol_request, bridge replies withcontrol_result.
Capabilities
The bridge client advertises its capabilities to the host upon connection via a hello frame:
interface HelloMessage {
type: 'hello';
capabilities: ('error' | 'console' | 'pageview' | 'screenshot' | 'control')[];
platform: 'web' | 'node' | 'react-native' | 'unknown';
projectId?: string;
route?: string; // Current route (web/RN)
url?: string; // Current URL (web/RN)
}Default capabilities (opt-out):
error- Captures uncaught errors and unhandled rejectionsconsole- Intercepts console.log/info/warn/error/debug callsnavigation- SPA navigation tracking (History API + hashchange); disable withenableNavigation: falsenetwork- fetch/XHR (web/worker) and http/https/fetch (Node) capture; disable withenableNetwork: false
Optional capabilities:
pageview- Tracks page/route changes (opt-in viaenablePageview: true)screenshot- Sends pre-encoded screenshots (opt-in viaenableScreenshot: true)control- Allows consumers (e.g., Code) to send control commands; opt-in viaenableControl: trueand handle them withonControl(...)
Subscriptions & Filtering
code-bridge-host keeps per-consumer subscriptions so each consumer only receives the events it wants.
- Bridges advertise capabilities in a
helloframe (capabilities: ['error', 'console', 'pageview', 'screenshot', 'control']). - Consumers can send a
subscribeframe withlevels: ['errors'|'warn'|'info'|'trace'], optionalcapabilities, and optionalllm_filter(off|minimal|aggressive). - No
subscribe→ default to errors only; capability-gated events (pageview, screenshot, control) require the consumer to request them and the bridge to have advertised them. - See
docs/subscriptions.mdfor the exact shapes and filtering logic; runnode demo/test-subscription-flow.js(with a host running) to observe routing.
30-second setup (npm user)
- In your app (dev build):
import { startBridge } from '@just-every/code-bridge'; const bridge = startBridge({ enableScreenshot: false, enableControl: false });- Defaults to errors-only; no screenshots/control unless enabled.
- In dev,
startBridgewill auto-ensure the host (using the local bin ornpx code-bridge-hostif none is running) and connect.
- Start Code in the same workspace (
codeorcode exec ...); it auto-connects as a consumer and shows developer messages from the bridge.
Pageview Tracking
Pageview tracking is opt-in and dev-only. Enable it to track route changes in your application:
const bridge = startBridge({
projectId: 'my-app',
enablePageview: true, // Opt-in to pageview tracking
});
// Manual pageview tracking
bridge.trackPageview({ route: '/dashboard', url: 'https://example.com/dashboard' });
// Or use auto-detected values (web only)
bridge.trackPageview({}); // Uses window.location.href and window.location.pathnameNote: Pageview events are only sent when enablePageview: true is set in options. Calling trackPageview() without enabling it will log a warning.
Screenshot Sending
Screenshot sending is opt-in and dev-only. Enable it to send pre-encoded screenshots from your application:
const bridge = startBridge({
projectId: 'my-app',
enableScreenshot: true, // Opt-in to screenshot sending
});
// Send a screenshot (you must provide pre-encoded image data)
// Example 1: Using base64-encoded data
const canvas = document.querySelector('canvas');
const dataUrl = canvas.toDataURL('image/png');
const base64Data = dataUrl.split(',')[1]; // Strip "data:image/png;base64," prefix
bridge.sendScreenshot({
mime: 'image/png',
data: base64Data,
url: window.location.href, // Optional
route: window.location.pathname, // Optional
});
// Example 2: Send a screenshot from an existing image
fetch('/screenshot.png')
.then(res => res.blob())
.then(blob => {
const reader = new FileReader();
reader.onload = () => {
const base64 = reader.result.split(',')[1];
bridge.sendScreenshot({
mime: 'image/png',
data: base64,
});
};
reader.readAsDataURL(blob);
});Important notes:
- Screenshot sending is dev-only and disabled by default
- The SDK does NOT automatically capture screenshots - you must provide pre-encoded image data
- Pass
mimeanddata(base64-encoded) tosendScreenshot() - Optional
urlandrouteparameters are auto-detected in web environments - This API is a transport layer only - it sends whatever image data you provide to the bridge host
Note: Screenshot events are only sent when enableScreenshot: true is set in options. Calling sendScreenshot() without enabling it will log a warning.
Event Schema
Events sent to the server follow this structure:
interface BridgeEvent {
type: 'error' | 'log' | 'console' | 'pageview' | 'screenshot';
level: 'log' | 'info' | 'warn' | 'error' | 'debug';
message: string;
stack?: string;
timestamp: number;
platform: 'web' | 'node' | 'react-native' | 'unknown';
projectId?: string;
breadcrumbs?: Array<{
timestamp: number;
level: string;
message: string;
}>;
url?: string; // For pageview and screenshot events
route?: string; // For pageview and screenshot events
mime?: string; // For screenshot events
data?: string; // For screenshot events (base64-encoded image)
}Development
# Install dependencies
npm install
# Build the library
npm run build
# Run Node.js demo (starts local server first: node demo/server.js)
npm test
# For web demo, open demo/web-demo.html in a browser after building
# Run end-to-end demo against a running `code` workspace
node demo/workspace-bridge-demo.js /path/to/workspaceSubscription filtering demo
With code-bridge-host running (so .code/code-bridge.json exists), run:
node demo/test-subscription-flow.jsThis spins up one bridge and three consumers to show level and capability filtering in action.
Dev demo with Code host
If you have code running in a workspace, it writes .code/code-bridge.json with the WebSocket port and secret. After building this package (npm run build), run:
node demo/workspace-bridge-demo.js /path/to/that/workspaceThe script reads the metadata, connects with startBridge, sends a few console logs, triggers an unhandled rejection, and throws an uncaught error. You should see these appear as developer messages in the active Code session.
To wire your app during development, read the same metadata and call startBridge({ url, secret, projectId }) early in your app startup. The bridge will auto-enable in dev mode when url or secret is provided.
Advanced override: Set CODE_BRIDGE=1 to force-enable the bridge even without dev mode detection (useful for testing production builds locally).
Screenshots (opt-in, dev-only)
- Enable in your app:
startBridge({ enableScreenshot: true, ... }) - Send a pre-encoded image:
conn.sendScreenshot({ mime: 'image/png', data: '<base64>' }) - Host forwards only if the bridge advertised
screenshotand the consumer subscribed toscreenshot; rate-limited per bridge.
Using with Code (consumer)
- Start the host in your workspace:
npx code-bridge-host /path/to/workspace(writes.code/code-bridge.json). - Start
code(TUI orcode exec ...) in the same workspace; it auto-detects the metadata, connects as a consumer, and posts a developer message with the host details. - Run any app/demo that calls
startBridge—in dev it will auto-ensure the host (spawns it if missing), then connect. Events stream into all active Code sessions on that workspace.
Host Server
Using code-bridge-host CLI
The package includes a code-bridge-host CLI that creates a singleton WebSocket server per workspace. This allows multiple bridge clients (apps using startBridge) and consumer clients (Code, other CLIs, MCP tools) to share the same stream.
Installation:
npm install -g @just-every/code-bridge
# or run directly with npx
npx code-bridge-host [workspace-path] [--port=9876]Usage:
# Start host in current directory
code-bridge-host
# Start host for specific workspace
code-bridge-host /path/to/workspace
# Use custom port preference
code-bridge-host --port=8080What it does:
- Acquires a lock at
.code/code-bridge.lock(single instance per workspace) - Finds an available port (starting from
--portor default 9876) - Generates a random shared secret
- Writes
.code/code-bridge.jsonwith connection details:{ "url": "ws://127.0.0.1:9876", "port": 9876, "secret": "abc123...", "workspacePath": "/path/to/workspace", "startedAt": "2024-01-01T12:00:00.000Z" } - Starts WebSocket server with role-based authentication
Connecting as a bridge client:
Bridge clients (apps using startBridge) read the metadata and connect automatically:
const fs = require('fs');
const { startBridge } = require('@just-every/code-bridge');
const meta = JSON.parse(fs.readFileSync('.code/code-bridge.json', 'utf8'));
startBridge({
url: meta.url,
secret: meta.secret,
projectId: 'my-app',
});Connecting as a consumer client:
Consumer clients (Code, CLIs, etc.) receive events from all bridge clients:
const fs = require('fs');
const WebSocket = require('ws');
const meta = JSON.parse(fs.readFileSync('.code/code-bridge.json', 'utf8'));
const ws = new WebSocket(meta.url);
ws.on('open', () => {
// Authenticate as consumer
ws.send(JSON.stringify({
type: 'auth',
secret: meta.secret,
role: 'consumer',
clientId: 'my-consumer',
}));
});
ws.on('message', (data) => {
const event = JSON.parse(data);
console.log('Event from bridge:', event);
});Protocol:
Authentication frame (first message):
{ "type": "auth", "secret": "<secret-from-metadata>", "role": "bridge" | "consumer", "clientId": "optional-identifier" }Hello frame (from bridge clients after auth):
{ "type": "hello", "capabilities": ["error", "console", "pageview"], "platform": "web", "projectId": "my-app", "route": "/dashboard", "url": "https://example.com/dashboard" }Bridge events (from bridge → consumers):
- Bridge clients send
BridgeEventobjects - Host broadcasts to all authenticated consumer clients
- Bridge clients send
Response (after auth):
{ "type": "auth_success", "role": "bridge" | "consumer", "clientId": "assigned-or-provided-id" }
Singleton behavior:
- Only one host can run per workspace (enforced by lock file)
- If host process dies, stale lock is detected and cleared
- Graceful shutdown removes lock and metadata on SIGINT/SIGTERM
Custom Server Setup
You can also implement your own WebSocket server. It should:
- Accept WebSocket connections
- Authenticate using the
X-Bridge-Secretheader (Node.js) or auth message (web/RN) - Receive and process JSON events matching the
BridgeEventschema
Example minimal server:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 9876 });
wss.on('connection', (ws, req) => {
const secret = req.headers['x-bridge-secret'];
ws.on('message', (data) => {
const event = JSON.parse(data);
console.log('Received event:', event);
});
});Tree-Shaking
The library is designed to be tree-shakeable. In production builds (when dev mode is not detected), the entire bridge becomes a no-op and can be removed by bundlers like Webpack, Rollup, or Vite.
Known Limitations (current dev build)
- Browser screenshots require you to supply
screenshotProvider; none is bundled. - Control channel routing depends on at least one control-capable bridge being connected; timeouts/targeting beyond basic broadcast are still being hardened.
demo/web-demo.htmlexercises console/error/pageview; trigger SPA navigation and network requests in your own app to observe the new navigation/network events.
License
MIT
