> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.rhombus.community/llms.txt
> Use this file to discover all available pages before exploring further.

# Ciclo de vida de la conexión WebSocket

> Administra el ciclo de vida de una conexión WebSocket de Rhombus: realiza handshakes STOMP, envía heartbeats, maneja reconexiones y cierra correctamente.

<Note>
  Esta página fue traducida automáticamente. Si encuentra errores o tiene sugerencias, [contáctenos](mailto:support@rhombus.com).
</Note>

Esta guía cubre el ciclo de vida completo de una conexión WebSocket de Rhombus: establecer la conexión, realizar el handshake STOMP, mantener la conexión con heartbeats y manejar las desconexiones adecuadamente.

## Descripción general del ciclo de vida

```text theme={null}
┌─────────────────┐
│  1. WebSocket    │
│     Handshake    │──── WSS + Auth Headers
└────────┬────────┘
         ▼
┌─────────────────┐
│  2. STOMP        │
│     CONNECT      │──── Version + Heartbeat negotiation
└────────┬────────┘
         ▼
┌─────────────────┐
│  3. SUBSCRIBE    │──── Topic subscription
└────────┬────────┘
         ▼
┌─────────────────┐
│  4. MESSAGE      │◄─── Real-time events
│     Loop         │───► Heartbeat ping/pong
└────────┬────────┘
         ▼
┌─────────────────┐
│  5. DISCONNECT   │──── Graceful shutdown
└─────────────────┘
```

## Paso 1: handshake de WebSocket

Inicia una conexión WebSocket segura al endpoint de Rhombus con headers de autenticación:

```python theme={null}
import websocket

ws = websocket.create_connection(
    "wss://ws.rhombussystems.com:8443/websocket?x-auth-scheme=api-token",
    header={"x-auth-apikey": API_TOKEN},
    timeout=10  # 10-second handshake timeout
)
```

Un handshake exitoso actualiza la conexión HTTP a WebSocket. Si la autenticación falla, el servidor devuelve un error HTTP (401/403) antes de que se complete la actualización.

## Paso 2: STOMP CONNECT

Después de que se establezca la conexión WebSocket, envía un frame STOMP `CONNECT` para inicializar el protocolo de mensajería:

### Frame CONNECT

```text theme={null}
CONNECT
accept-version:1.2
heart-beat:10000,10000

\x00
```

| Header           | Valor         | Descripción                                                           |
| ---------------- | ------------- | --------------------------------------------------------------------- |
| `accept-version` | `1.2`         | Versión del protocolo STOMP                                           |
| `heart-beat`     | `10000,10000` | Intervalo de envío/recepción de heartbeat del cliente en milisegundos |

### Respuesta esperada: frame CONNECTED

```text theme={null}
CONNECTED
version:1.2
heart-beat:10000,10000

\x00
```

El servidor devuelve la versión y los intervalos de heartbeat negociados.

### Construcción de frames STOMP

Los frames STOMP siguen un formato simple basado en texto:

```text theme={null}
COMMAND\n
header1:value1\n
header2:value2\n
\n
body (optional)
\x00
```

* Cada frame comienza con un nombre de comando
* Los headers son pares clave-valor separados por dos puntos, uno por línea
* Una línea vacía separa los headers del cuerpo
* El frame termina con un byte nulo (`\x00`)

<CodeGroup>
  ```python Python theme={null}
  def build_stomp_frame(command, headers=None, body=""):
      """Construct a STOMP protocol frame."""
      frame = command + "\n"
      if headers:
          for key, value in headers.items():
              frame += f"{key}:{value}\n"
      frame += "\n"
      frame += body
      frame += "\x00"
      return frame

  # Send CONNECT
  connect_frame = build_stomp_frame("CONNECT", {
      "accept-version": "1.2",
      "heart-beat": "10000,10000"
  })
  ws.send(connect_frame)

  # Read CONNECTED response
  response = ws.recv()
  ```

  ```javascript JavaScript theme={null}
  function buildStompFrame(command, headers = {}, body = "") {
    let frame = command + "\n";
    for (const [key, value] of Object.entries(headers)) {
      frame += `${key}:${value}\n`;
    }
    frame += "\n";
    frame += body;
    frame += "\x00";
    return frame;
  }

  // Send CONNECT
  ws.send(
    buildStompFrame("CONNECT", {
      "accept-version": "1.2",
      "heart-beat": "10000,10000",
    })
  );
  ```

  ```go Go theme={null}
  func buildStompFrame(command string, headers map[string]string, body string) string {
      var b strings.Builder
      b.WriteString(command + "\n")
      for k, v := range headers {
          b.WriteString(fmt.Sprintf("%s:%s\n", k, v))
      }
      b.WriteString("\n")
      b.WriteString(body)
      b.WriteByte(0x00)
      return b.String()
  }

  // Send CONNECT
  connectFrame := buildStompFrame("CONNECT", map[string]string{
      "accept-version": "1.2",
      "heart-beat":     "10000,10000",
  }, "")
  conn.WriteMessage(websocket.TextMessage, []byte(connectFrame))
  ```
</CodeGroup>

## Paso 3: suscribirse a un tópico

Después de establecer la conexión STOMP, suscríbete al tópico de cambios de la organización:

### Frame SUBSCRIBE

```text theme={null}
SUBSCRIBE
id:sub-0
destination:/topic/change/{orgUuid}

\x00
```

| Header        | Valor                     | Descripción                                          |
| ------------- | ------------------------- | ---------------------------------------------------- |
| `id`          | `sub-0`                   | Identificador de suscripción definido por el cliente |
| `destination` | `/topic/change/{orgUuid}` | El tópico al que suscribirse                         |

```python theme={null}
subscribe_frame = build_stomp_frame("SUBSCRIBE", {
    "id": "sub-0",
    "destination": f"/topic/change/{org_uuid}"
})
ws.send(subscribe_frame)
```

<Note>
  Reemplaza `{orgUuid}` con el UUID de tu organización. Recupéralo a través del endpoint REST `POST /api/org/getOrgV2`.
</Note>

## Paso 4: bucle de mensajes y heartbeats

Una vez suscrito, tu aplicación entra en un bucle de mensajes donde:

1. **Recibe frames MESSAGE** que contienen datos de eventos
2. **Envía heartbeats** para mantener viva la conexión
3. **Recibe heartbeats** del servidor

### Mecanismo de heartbeat

Los heartbeats son caracteres de salto de línea individuales (`\n`) enviados al intervalo negociado (10 segundos). Tanto el cliente como el servidor envían heartbeats. Si alguno de los lados deja de recibir heartbeats, la conexión se considera muerta.

```python theme={null}
import threading
import time

def send_heartbeats(ws, stop_event):
    """Send STOMP heartbeats every 10 seconds."""
    while not stop_event.is_set():
        try:
            ws.send("\n")
        except Exception:
            break
        stop_event.wait(10)

# Start heartbeat sender in background
stop_heartbeat = threading.Event()
heartbeat_thread = threading.Thread(
    target=send_heartbeats,
    args=(ws, stop_heartbeat),
    daemon=True
)
heartbeat_thread.start()
```

### Análisis de frames entrantes

```python theme={null}
def parse_stomp_frame(raw):
    """Parse a raw STOMP frame into command, headers, and body."""
    # Handle heartbeats (empty frames)
    raw = raw.lstrip("\n\r")
    if not raw or raw == "\x00":
        return None  # Heartbeat

    raw = raw.rstrip("\x00")
    parts = raw.split("\n\n", 1)

    lines = parts[0].split("\n")
    command = lines[0]

    headers = {}
    for line in lines[1:]:
        if ":" in line:
            key, value = line.split(":", 1)
            headers[key] = value

    body = parts[1] if len(parts) > 1 else ""
    return {"command": command, "headers": headers, "body": body}
```

### Bucle de mensajes

```python theme={null}
try:
    while True:
        raw = ws.recv()
        frame = parse_stomp_frame(raw)

        if frame is None:
            continue  # Heartbeat, skip

        if frame["command"] == "MESSAGE":
            payload = json.loads(frame["body"])
            handle_event(payload)

except KeyboardInterrupt:
    print("Shutting down...")
finally:
    stop_heartbeat.set()
    heartbeat_thread.join()
```

## Paso 5: desconexión adecuada

Antes de cerrar el WebSocket, envía un frame STOMP `DISCONNECT`:

```python theme={null}
# Send DISCONNECT frame
disconnect_frame = build_stomp_frame("DISCONNECT")
ws.send(disconnect_frame)

# Close WebSocket
ws.close()
```

Esto le indica al servidor que el cliente se está desconectando intencionalmente, permitiéndole liberar recursos de inmediato en lugar de esperar un timeout de heartbeat.

## Estrategia de reconexión

Las interrupciones de red son inevitables. Implementa una reconexión automática con backoff:

```python theme={null}
import time

def connect_with_retry(api_token, org_uuid, max_retries=10):
    """Connect to Rhombus WebSocket with exponential backoff."""
    for attempt in range(max_retries):
        try:
            ws = websocket.create_connection(
                "wss://ws.rhombussystems.com:8443/websocket"
                "?x-auth-scheme=api-token",
                header={"x-auth-apikey": api_token},
                timeout=10
            )
            stomp_connect(ws)
            stomp_subscribe(ws, org_uuid)
            print("Connected successfully")
            return ws
        except Exception as e:
            delay = min(5 * (2 ** attempt), 60)
            print(f"Connection failed: {e}. Retrying in {delay}s...")
            time.sleep(delay)

    raise ConnectionError("Max retries exceeded")
```

### Bucle completo de reconexión

```python theme={null}
while True:
    ws = connect_with_retry(API_TOKEN, ORG_UUID)

    try:
        run_message_loop(ws)
    except websocket.WebSocketConnectionClosedException:
        print("Connection lost. Reconnecting...")
    except KeyboardInterrupt:
        print("User interrupted. Exiting.")
        break
    finally:
        try:
            ws.close()
        except Exception:
            pass
```

## Tiempos de espera de la conexión

| Timeout         | Valor         | Descripción                                                                 |
| --------------- | ------------- | --------------------------------------------------------------------------- |
| Handshake       | 10 segundos   | Tiempo máximo para completar la actualización de WebSocket                  |
| Heartbeat       | 10 segundos   | Intervalo entre pings de keep-alive                                         |
| Conexión muerta | \~30 segundos | Tiempo aproximado antes de que un heartbeat faltante dispare la desconexión |

## Problemas comunes

| Síntoma                              | Causa probable                                       | Solución                                                                                |
| ------------------------------------ | ---------------------------------------------------- | --------------------------------------------------------------------------------------- |
| La conexión se cae cada \~30s        | No se están enviando heartbeats                      | Implementa el thread emisor de heartbeats                                               |
| El frame `CONNECTED` nunca se recibe | Frame STOMP `CONNECT` mal formado                    | Verifica que el formato del frame incluya el terminador nulo `\x00`                     |
| No se reciben mensajes               | Tópico incorrecto o falta el UUID de la organización | Verifica que el UUID de la organización coincida con la organización de tu token de API |
| Bucle de reconexión                  | Token expirado o revocado                            | Verifica la validez del token mediante la API REST                                      |
