Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,6 @@ module.exports = {
],
i18n: {
defaultLocale: "en",
locales: ["en", "fr", "pt-br", "zh-CN"],
locales: ["en", "es", "fr", "pt-br", "zh-CN"],
}
};
446 changes: 446 additions & 0 deletions i18n/es/code.json

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions i18n/es/docusaurus-plugin-content-blog/options.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"title": {
"message": "Blog",
"description": "El título para el blog usado en SEO"
},
"description": {
"message": "Blog",
"description": "La descripción para el blog usada en SEO"
},
"sidebar.title": {
"message": "Publicaciones recientes",
"description": "La etiqueta para la barra lateral izquierda"
}
}
38 changes: 38 additions & 0 deletions i18n/es/docusaurus-plugin-content-docs/current.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"version.label": {
"message": "4.x",
"description": "La etiqueta para la versión actual"
},
"sidebar.sidebar.category.Documentation": {
"message": "Documentación",
"description": "La etiqueta para la categoría Documentation en la barra lateral"
},
"sidebar.sidebar.category.Server": {
"message": "Servidor",
"description": "La etiqueta para la categoría Server en la barra lateral"
},
"sidebar.sidebar.category.Client": {
"message": "Cliente",
"description": "La etiqueta para la categoría Client en la barra lateral"
},
"sidebar.sidebar.category.Events": {
"message": "Eventos",
"description": "La etiqueta para la categoría Events en la barra lateral"
},
"sidebar.sidebar.category.Adapters": {
"message": "Adaptadores",
"description": "La etiqueta para la categoría Adapters en la barra lateral"
},
"sidebar.sidebar.category.Advanced": {
"message": "Avanzado",
"description": "La etiqueta para la categoría Advanced en la barra lateral"
},
"sidebar.sidebar.category.Migrations": {
"message": "Migraciones",
"description": "La etiqueta para la categoría Migrations en la barra lateral"
},
"sidebar.sidebar.category.Miscellaneous": {
"message": "Miscelánea",
"description": "La etiqueta para la categoría Miscellaneous en la barra lateral"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
title: Recuperación del estado de conexión
sidebar_position: 4
slug: /connection-state-recovery
---

La recuperación del estado de conexión es una característica que permite restaurar el estado de un cliente después de una desconexión temporal, incluyendo cualquier paquete perdido.

:::info

Esta característica fue añadida en la versión `4.6.0`, lanzada en febrero de 2023.

Las notas de lanzamiento se pueden encontrar [aquí](../../changelog/4.6.0.md).

:::

## Descargo de responsabilidad

Bajo condiciones reales, un cliente Socket.IO inevitablemente experimentará desconexiones temporales, independientemente de la calidad de la conexión.

Esta característica te ayudará a lidiar con tales desconexiones, pero por favor ten en cuenta que la recuperación **no siempre será exitosa**. Es por eso que todavía necesitarás manejar el caso donde los estados del cliente y el servidor deben sincronizarse.

## Uso

La recuperación del estado de conexión debe ser habilitada por el servidor:

```js
const io = new Server(httpServer, {
connectionStateRecovery: {
// la duración del respaldo de las sesiones y los paquetes
maxDisconnectionDuration: 2 * 60 * 1000,
// si se deben omitir los middlewares tras una recuperación exitosa
skipMiddlewares: true,
}
});
```

:::caution

La característica de recuperación del estado de conexión está diseñada para lidiar con desconexiones intermitentes, así que por favor usa un valor razonable para `maxDisconnectionDuration`.

:::

Ante una desconexión inesperada (es decir, no una desconexión manual con `socket.disconnect()`), el servidor almacenará el `id`, las salas y el atributo `data` del socket.

Luego, tras la reconexión, el servidor intentará restaurar el estado del cliente. El atributo `recovered` indica si esta recuperación fue exitosa:

*Servidor*

```js
io.on("connection", (socket) => {
if (socket.recovered) {
// la recuperación fue exitosa: socket.id, socket.rooms y socket.data fueron restaurados
} else {
// sesión nueva o no recuperable
}
});
```

*Cliente*

```js
socket.on("connect", () => {
if (socket.recovered) {
// cualquier evento perdido durante el período de desconexión se recibirá ahora
} else {
// sesión nueva o no recuperable
}
});
```

Puedes verificar que la recuperación está funcionando forzando el cierre del motor subyacente:

```js
import { io } from "socket.io-client";

const socket = io({
reconnectionDelay: 10000, // por defecto es 1000
reconnectionDelayMax: 10000 // por defecto es 5000
});

socket.on("connect", () => {
console.log("¿recuperado?", socket.recovered);

setTimeout(() => {
if (socket.io.engine) {
// cerrar la conexión de bajo nivel y activar una reconexión
socket.io.engine.close();
}
}, 10000);
});
```

:::tip

También puedes ejecutar este ejemplo directamente en tu navegador en:

- [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/socket.io/tree/main/examples/connection-state-recovery-example/esm?file=index.js)
- [StackBlitz](https://stackblitz.com/github/socketio/socket.io/tree/main/examples/connection-state-recovery-example/esm?file=index.js)

:::

## Compatibilidad con adaptadores existentes

| Adaptador | ¿Soporte? |
|------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------:|
| Adaptador integrado (en memoria) | SÍ :white_check_mark: |
| [Adaptador Redis](../05-Adapters/adapter-redis.md) | NO<sup>1</sup> |
| [Adaptador Redis Streams](../05-Adapters/adapter-redis-streams.md) | SÍ :white_check_mark: |
| [Adaptador MongoDB](../05-Adapters/adapter-mongo.md) | SÍ :white_check_mark: (desde la versión [`0.3.0`](https://github.com/socketio/socket.io-mongo-adapter/releases/tag/0.3.0)) |
| [Adaptador Postgres](../05-Adapters/adapter-postgres.md) | WIP |
| [Adaptador Cluster](../05-Adapters/adapter-cluster.md) | WIP |

[1] Persistir los paquetes no es compatible con el mecanismo Redis PUB/SUB.

## Cómo funciona internamente

- el servidor envía un ID de sesión [durante el handshake](../08-Miscellaneous/sio-protocol.md#connection-to-a-namespace-1) (que es diferente del atributo `id` existente, que es público y puede compartirse libremente)

Ejemplo:

```
40{"sid":"GNpWD7LbGCBNCr8GAAAB","pid":"YHcX2sdAF1z452-HAAAW"}

donde

4 => el tipo de mensaje Engine.IO
0 => el tipo CONNECT de Socket.IO
GN...AB => el id público de la sesión
YH...AW => el id privado de la sesión
```

- el servidor también incluye un offset en [cada paquete](../08-Miscellaneous/sio-protocol.md#sending-and-receiving-data-1) (añadido al final del array de datos, para compatibilidad hacia atrás)

Ejemplo:

```
42["foo","MzUPkW0"]

donde

4 => el tipo de mensaje Engine.IO
2 => el tipo EVENT de Socket.IO
foo => el nombre del evento (socket.emit("foo"))
MzUPkW0 => el offset
```

:::note

Para que la recuperación tenga éxito, el servidor debe enviar al menos un evento, para inicializar el offset en el lado del cliente.

:::

- tras una desconexión temporal, el servidor almacena el estado del cliente por un tiempo dado (implementado a nivel del adaptador)

- tras la reconexión, el cliente envía tanto el ID de sesión como el último offset que procesó, y el servidor intenta restaurar el estado

Ejemplo:

```
40{"pid":"YHcX2sdAF1z452-HAAAW","offset":"MzUPkW0"}

donde

4 => el tipo de mensaje Engine.IO
0 => el tipo CONNECT de Socket.IO
YH...AW => el id privado de la sesión
MzUPkW0 => el último offset procesado
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
title: Garantías de entrega
sidebar_position: 3
slug: /delivery-guarantees
toc_max_heading_level: 4
---

## Orden de los mensajes

Socket.IO garantiza el orden de los mensajes, sin importar qué transporte de bajo nivel se utilice (incluso durante una actualización de HTTP long-polling a WebSocket).

Esto se logra gracias a:

- las garantías proporcionadas por la conexión TCP subyacente
- el diseño cuidadoso del [mecanismo de actualización](how-it-works.md#mecanismo-de-actualización)

Ejemplo:

```js
socket.emit("event1");
socket.emit("event2");
socket.emit("event3");
```

En el ejemplo anterior, los eventos siempre serán recibidos en el mismo orden por el otro lado (siempre que realmente lleguen, ver [abajo](#llegada-de-mensajes)).

## Llegada de mensajes

### Como máximo una vez

Por defecto, Socket.IO proporciona una garantía de entrega de **como máximo una vez**:

- si la conexión se interrumpe mientras se envía un evento, entonces no hay garantía de que el otro lado lo haya recibido y no habrá reintento tras la reconexión
- un cliente desconectado [almacenará eventos en búfer hasta la reconexión](../03-Client/client-offline-behavior.md) (aunque el punto anterior sigue aplicando)
- no hay tal búfer en el servidor, lo que significa que cualquier evento que haya perdido un cliente desconectado no será transmitido a ese cliente tras la reconexión

:::info

Actualmente, garantías de entrega adicionales deben implementarse en tu aplicación.

:::

### Al menos una vez

#### Del cliente al servidor

Desde el lado del cliente, puedes lograr una garantía de **al menos una vez** con la opción [`retries`](../../client-options.md#retries):

```js
const socket = io({
retries: 3,
ackTimeout: 10000
});
```

El cliente intentará enviar el evento (hasta `retries + 1` veces), hasta que obtenga una confirmación del servidor.

:::caution

Incluso en ese caso, cualquier evento pendiente se perderá si el usuario actualiza su pestaña.

:::

#### Del servidor al cliente

Para eventos enviados por el servidor, garantías de entrega adicionales pueden implementarse:

- asignando un ID único a cada evento
- persistiendo los eventos en una base de datos
- almacenando el offset del último evento recibido en el lado del cliente, y enviándolo tras la reconexión

Ejemplo:

*Cliente*

```js
const socket = io({
auth: {
offset: undefined
}
});

socket.on("my-event", ({ id, data }) => {
// hacer algo con los datos, y luego actualizar el offset
socket.auth.offset = id;
});
```

*Servidor*

```js
io.on("connection", async (socket) => {
const offset = socket.handshake.auth.offset;
if (offset) {
// esto es una reconexión
for (const event of await fetchMissedEventsFromDatabase(offset)) {
socket.emit("my-event", event);
}
} else {
// esta es una primera conexión
}
});

setInterval(async () => {
const event = {
id: generateUniqueId(),
data: new Date().toISOString()
}

await persistEventToDatabase(event);
io.emit("my-event", event);
}, 1000);
```

Implementar los métodos faltantes (`fetchMissedEventsFromDatabase()`, `generateUniqueId()` y `persistEventToDatabase()`) es específico de la base de datos y se deja como ejercicio para el lector.

Referencias:

- [`socket.auth`](../../client-options.md#socket-options) (cliente)
- [`socket.handshake`](../../server-api.md#sockethandshake) (servidor)
Loading