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…