Skip to main content

WebSockets

Friday Dev provides WebSocket connections for real-time updates.

Connection

Endpoint

ws://localhost:3000/ws
wss://your-domain.com/ws

Authentication

Include API key as query parameter:

ws://localhost:3000/ws?token=YOUR_API_KEY

Or send after connection:

{
"type": "auth",
"token": "YOUR_API_KEY"
}

Connection Example

JavaScript

const ws = new WebSocket('ws://localhost:3000/ws?token=YOUR_API_KEY');

ws.onopen = () => {
console.log('Connected to Friday Dev');

// Subscribe to task updates
ws.send(JSON.stringify({
type: 'subscribe',
channel: 'tasks'
}));
};

ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('Received:', message);
};

ws.onclose = () => {
console.log('Disconnected');
};

ws.onerror = (error) => {
console.error('WebSocket error:', error);
};

Python

import websockets
import asyncio
import json

async def connect():
uri = "ws://localhost:3000/ws?token=YOUR_API_KEY"

async with websockets.connect(uri) as ws:
# Subscribe to tasks
await ws.send(json.dumps({
"type": "subscribe",
"channel": "tasks"
}))

# Listen for messages
async for message in ws:
data = json.loads(message)
print(f"Received: {data}")

asyncio.run(connect())

Message Format

All messages use JSON:

{
"type": "event_type",
"channel": "channel_name",
"data": { ... },
"timestamp": "2024-01-15T16:00:00Z"
}

Channels

tasks

Task-related events:

// Subscribe
{ "type": "subscribe", "channel": "tasks" }

// Events received:
{
"type": "task.created",
"channel": "tasks",
"data": {
"id": 123,
"title": "New task",
"status": "backlog"
}
}

{
"type": "task.updated",
"channel": "tasks",
"data": {
"id": 123,
"changes": {
"status": "in_progress"
}
}
}

{
"type": "task.deleted",
"channel": "tasks",
"data": {
"id": 123
}
}

runs

Agent run events:

// Subscribe
{ "type": "subscribe", "channel": "runs" }

// Events received:
{
"type": "run.started",
"channel": "runs",
"data": {
"run_id": "run_xyz",
"task_id": 123,
"agent": "friday"
}
}

{
"type": "run.progress",
"channel": "runs",
"data": {
"run_id": "run_xyz",
"step": 3,
"total_steps": 5,
"message": "Writing code..."
}
}

{
"type": "run.completed",
"channel": "runs",
"data": {
"run_id": "run_xyz",
"status": "success",
"output": { ... }
}
}

{
"type": "run.failed",
"channel": "runs",
"data": {
"run_id": "run_xyz",
"error": "Rate limit exceeded"
}
}

logs

Real-time agent logs:

// Subscribe to specific run
{
"type": "subscribe",
"channel": "logs",
"run_id": "run_xyz"
}

// Events received:
{
"type": "log",
"channel": "logs",
"data": {
"run_id": "run_xyz",
"level": "info",
"message": "Reading task description...",
"timestamp": "2024-01-15T16:00:01Z"
}
}

projects

Project events:

// Subscribe
{ "type": "subscribe", "channel": "projects" }

// Events:
{
"type": "project.created",
"channel": "projects",
"data": { ... }
}

Subscribing

Subscribe to Channel

{
"type": "subscribe",
"channel": "tasks"
}

Subscribe with Filter

{
"type": "subscribe",
"channel": "tasks",
"filter": {
"project_id": "proj_abc123"
}
}

Subscribe to Specific Resource

{
"type": "subscribe",
"channel": "logs",
"run_id": "run_xyz"
}

Unsubscribe

{
"type": "unsubscribe",
"channel": "tasks"
}

Client Messages

Ping/Pong

Keep connection alive:

// Send
{ "type": "ping" }

// Receive
{ "type": "pong" }

Request Data

Request current state:

// Send
{
"type": "request",
"resource": "tasks",
"params": {
"status": "in_progress"
}
}

// Receive
{
"type": "response",
"resource": "tasks",
"data": [ ... ]
}

Error Handling

Error Messages

{
"type": "error",
"code": "UNAUTHORIZED",
"message": "Invalid or expired token"
}

Error Codes

CodeDescription
UNAUTHORIZEDAuth failed
INVALID_MESSAGEMalformed message
UNKNOWN_CHANNELChannel doesn't exist
SUBSCRIPTION_FAILEDCan't subscribe
RATE_LIMITEDToo many messages

Reconnection

Handle disconnections gracefully:

class FridayDevWebSocket {
constructor(url, token) {
this.url = `${url}?token=${token}`;
this.subscriptions = new Set();
this.connect();
}

connect() {
this.ws = new WebSocket(this.url);

this.ws.onopen = () => {
console.log('Connected');
// Resubscribe after reconnect
this.subscriptions.forEach(channel => {
this.subscribe(channel);
});
};

this.ws.onclose = () => {
console.log('Disconnected, reconnecting...');
setTimeout(() => this.connect(), 1000);
};

this.ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
this.handleMessage(msg);
};
}

subscribe(channel) {
this.subscriptions.add(channel);
this.ws.send(JSON.stringify({
type: 'subscribe',
channel
}));
}

handleMessage(msg) {
// Handle different message types
switch (msg.type) {
case 'task.created':
console.log('New task:', msg.data);
break;
case 'run.progress':
console.log('Progress:', msg.data);
break;
// ...
}
}
}

Rate Limits

  • Messages per second: 10
  • Subscriptions per connection: 50
  • Connections per client: 5

Best Practices

1. Handle All Events

ws.onmessage = (event) => {
const msg = JSON.parse(event.data);

switch (msg.type) {
case 'task.created':
case 'task.updated':
case 'task.deleted':
updateTaskList(msg.data);
break;
case 'run.progress':
updateProgress(msg.data);
break;
case 'error':
handleError(msg);
break;
default:
console.log('Unknown message type:', msg.type);
}
};

2. Implement Heartbeat

setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);

3. Clean Up

window.addEventListener('beforeunload', () => {
ws.close();
});

Next Steps