Skip to content
Back to skills

Agency Realtime

ASecurity

Handle real-time communication via WebSocket for live notifications, content collaboration, and approval updates. Use when working with WebSocket handlers, real-time notifications, or live collaboration features.

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 9, 2026
developmenttypescriptpythongofastapiapidatabasefrontendbackend

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 9, 2026

npx -y skills add gengirish/social-media-agency --skill agency-realtime --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Agency Realtime?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Agency Realtime
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/gengirish-agency-realtime/badge)](https://www.skillsdirectory.com/skills/gengirish-agency-realtime)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: agency-realtime
description: Handle real-time communication via WebSocket for live notifications, content collaboration, and approval updates. Use when working with WebSocket handlers, real-time notifications, or live collaboration features.
---

# Social Media Agency Real-Time Layer

## Architecture

```
Dashboard ←WebSocket→ FastAPI WS Handler ←→ Redis PubSub
                                          ←→ Database Events
```

## WebSocket (Notifications & Collaboration)

### Message Protocol

All WebSocket messages use JSON with a `type` field:

```python
# Server → Client (notifications)
{"type": "notification", "category": "approval", "content": "New content awaiting approval", "content_id": "uuid", "timestamp": "..."}
{"type": "notification", "category": "published", "content": "Post published to Instagram", "content_id": "uuid", "timestamp": "..."}
{"type": "notification", "category": "comment", "content": "Manager left feedback on your post", "content_id": "uuid", "timestamp": "..."}
{"type": "notification", "category": "analytics", "content": "Weekly analytics ready for Sunrise Coffee", "client_id": "uuid", "timestamp": "..."}

# Server → Client (collaboration)
{"type": "content_updated", "content_id": "uuid", "field": "body", "value": "...", "updated_by": "user_name"}
{"type": "user_presence", "content_id": "uuid", "users": ["Alice", "Bob"]}

# Client → Server
{"type": "subscribe", "channels": ["notifications", "content:uuid"]}
{"type": "unsubscribe", "channels": ["content:uuid"]}
{"type": "content_edit", "content_id": "uuid", "field": "body", "value": "..."}
{"type": "ping"}

# Server → Client
{"type": "pong"}
{"type": "error", "content": "Something went wrong."}
```

### WebSocket Handler

```python
# src/agency/websocket/notifications.py
from fastapi import WebSocket, WebSocketDisconnect
import json
import redis.asyncio as redis
from agency.config import get_settings

class ConnectionManager:
    def __init__(self):
        self.active_connections: dict[str, list[WebSocket]] = {}

    async def connect(self, user_id: str, websocket: WebSocket):
        await websocket.accept()
        if user_id not in self.active_connections:
            self.active_connections[user_id] = []
        self.active_connections[user_id].append(websocket)

    def disconnect(self, user_id: str, websocket: WebSocket):
        if user_id in self.active_connections:
            self.active_connections[user_id].remove(websocket)
            if not self.active_connections[user_id]:
                del self.active_connections[user_id]

    async def send_to_user(self, user_id: str, message: dict):
        if user_id in self.active_connections:
            for ws in self.active_connections[user_id]:
                await ws.send_json(message)

    async def broadcast_to_org(self, org_id: str, user_ids: list[str], message: dict):
        for uid in user_ids:
            await self.send_to_user(uid, message)

manager = ConnectionManager()

async def handle_notifications(websocket: WebSocket, user_id: str, org_id: str):
    await manager.connect(user_id, websocket)

    try:
        while True:
            data = await websocket.receive_text()
            msg = json.loads(data)

            if msg["type"] == "ping":
                await websocket.send_json({"type": "pong"})

            elif msg["type"] == "subscribe":
                pass  # Track channel subscriptions

    except WebSocketDisconnect:
        manager.disconnect(user_id, websocket)
```

### Registering WebSocket Route

```python
# In main.py or a dedicated websocket router
from fastapi import WebSocket, Depends
from agency.websocket.notifications import handle_notifications
from agency.dependencies import get_db

@app.websocket("/ws/notifications")
async def websocket_notifications(websocket: WebSocket):
    token = websocket.query_params.get("token")
    user = verify_ws_token(token)
    if not user:
        await websocket.close(code=4001)
        return
    await handle_notifications(websocket, user["sub"], user["org_id"])
```

### Emitting Notifications from Services

```python
# In any service that needs to notify users
from agency.websocket.notifications import manager

async def notify_approval_requested(org_id: str, reviewer_ids: list[str], content_id: str, content_title: str):
    for reviewer_id in reviewer_ids:
        await manager.send_to_user(reviewer_id, {
            "type": "notification",
            "category": "approval",
            "content": f"New content awaiting your approval: {content_title}",
            "content_id": content_id,
            "timestamp": datetime.utcnow().isoformat(),
        })

async def notify_content_published(org_id: str, creator_id: str, content_id: str, platform: str):
    await manager.send_to_user(creator_id, {
        "type": "notification",
        "category": "published",
        "content": f"Your post was published to {platform}",
        "content_id": content_id,
        "timestamp": datetime.utcnow().isoformat(),
    })
```

### Frontend WebSocket Client

```typescript
// lib/socket.ts
export class AgencySocket {
  private ws: WebSocket | null = null;
  private onMessage: (msg: any) => void;
  private reconnectAttempts = 0;
  private maxReconnects = 5;

  constructor(onMessage: (msg: any) => void) {
    this.onMessage = onMessage;
    this.connect();
  }

  private connect() {
    const token = localStorage.getItem("token");
    if (!token) return;

    const wsUrl = process.env.NEXT_PUBLIC_API_URL!.replace("http", "ws");
    this.ws = new WebSocket(`${wsUrl}/ws/notifications?token=${token}`);

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

    this.ws.onclose = () => {
      if (this.reconnectAttempts < this.maxReconnects) {
        this.reconnectAttempts++;
        setTimeout(() => this.connect(), 2000 * this.reconnectAttempts);
      }
    };

    this.ws.onopen = () => {
      this.reconnectAttempts = 0;
      this.send("subscribe", { channels: ["notifications"] });
    };
  }

  send(type: string, data: Record<string, any> = {}) {
    this.ws?.send(JSON.stringify({ type, ...data }));
  }

  close() {
    this.ws?.close();
  }
}
```

### Notification Store (Zustand)

```typescript
// stores/notification-store.ts
import { create } from "zustand";

interface Notification {
  id: string;
  category: string;
  content: string;
  contentId?: string;
  clientId?: string;
  timestamp: string;
  read: boolean;
}

interface NotificationStore {
  notifications: Notification[];
  unreadCount: number;
  addNotification: (notif: Notification) => void;
  markRead: (id: string) => void;
  markAllRead: () => void;
}

export const useNotificationStore = create<NotificationStore>((set) => ({
  notifications: [],
  unreadCount: 0,
  addNotification: (notif) =>
    set((state) => ({
      notifications: [notif, ...state.notifications].slice(0, 50),
      unreadCount: state.unreadCount + 1,
    })),
  markRead: (id) =>
    set((state) => ({
      notifications: state.notifications.map((n) =>
        n.id === id ? { ...n, read: true } : n
      ),
      unreadCount: Math.max(0, state.unreadCount - 1),
    })),
  markAllRead: () =>
    set((state) => ({
      notifications: state.notifications.map((n) => ({ ...n, read: true })),
      unreadCount: 0,
    })),
}));
```

## Redis PubSub for Cross-Process Notifications

When running multiple backend workers, use Redis PubSub to broadcast notifications.

```python
import redis.asyncio as redis
import json
from agency.config import get_settings

class RedisPubSub:
    def __init__(self):
        settings = get_settings()
        self.redis = redis.from_url(settings.redis_url)

    async def publish(self, channel: str, message: dict):
        await self.redis.publish(channel, json.dumps(message))

    async def subscribe(self, channel: str):
        pubsub = self.redis.pubsub()
        await pubsub.subscribe(channel)
        return pubsub
```

## Key Rules

1. **WebSocket for real-time notifications** — approval requests, publish confirmations, analytics alerts
2. **Always validate WebSocket token** before accepting connection
3. **Auto-reconnect on disconnect** — up to 5 attempts with exponential backoff
4. **Keep notification history capped** — max 50 in frontend store
5. **Use Redis PubSub** for multi-process notification delivery
6. **Send heartbeat pings** — detect stale connections
7. **Graceful degradation** — app works without WebSocket (polling fallback)
8. **Notification categories** — `approval`, `published`, `comment`, `analytics`, `system`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.