Saltar al contenido
Meeko
Escribe para buscar en las guías y en la referencia de la API. Ábrela desde cualquier página con / o Ctrl K; muévete con las flechas y entra con Intro.
Empezar

Contenidos

En esta página

socket

Registro generado de los canales de server/socket, con on y emit tipados en el navegador y broadcast tipado en el servidor.

utilidad Desde 1.0.0

socket es el registro que Meeko genera a partir de los canales de server/socket: server/socket/chat.ts es socket.chat. Los eventos y sus payloads tienen los tipos de los schemas clientToServer y serverToClient del canal.

Se autoimporta, con dos caras:

  • En el navegador, socket.chat es un cliente WebSocket del canal, con on(evento, listener), emit(evento, payload), state y close(). La conexión se abre la primera vez que se accede al canal en el navegador.
  • En el servidor (API, RPC, <script server>, middleware y handlers de Socket), desde 1.2, socket.chat.broadcast(sala, evento, payload) publica un evento a una sala, tipado con el contrato del canal.

Uso#

ts
// server/api/orders/[id]/ready.post.ts: avisa a la sala de la cocina
import { defineApiRoute } from "@meekojs/meeko/server";
import { orderParams, orderSchema } from "../../../../shared/schemas/order";

export default defineApiRoute({
  params: orderParams,
  response: orderSchema,
  auth: "required",
  handler: async ({ params }) => {
    const order = await markReady(params.id);
    await socket.operations.broadcast(order.kitchenId, "kitchenChanged", { orderId: order.id, status: "ready" });
    return order;
  },
});
ts
// app/plugins/kitchen.client.ts: escucha en el navegador
import { definePlugin } from "@meekojs/meeko/framework";

export default definePlugin(() => {
  socket.operations.on("kitchenChanged", (change) => console.info(`Pedido ${change.orderId}: ${change.status}`));
});

Tipo#

SocketClient

ts
interface SocketClient<C extends SocketContract> {
  readonly state: ReadonlySignal<SocketClientState>;
  emit<K extends keyof C["clientToServer"] & string>(
    event: K,
    data: SchemaInput<C["clientToServer"][K]>,
  ): Promise<void>;
  on<K extends keyof C["serverToClient"] & string>(
    event: K,
    listener: (data: SchemaOutput<C["serverToClient"][K]>) => void,
  ): () => void;
  close(): void;
}

SocketRegistry

ts
type SocketRegistry<C extends Record<string, SocketContract>> = {
	readonly [K in keyof C]: SocketClient<C[K]>
}

Parámetros#

Llamada Parámetros Descripción
socket.<canal>.on(evento, listener) un evento de serverToClient y (payload) => void En el navegador. El payload llega validado con su schema.
socket.<canal>.emit(evento, payload) un evento de clientToServer y su entrada En el navegador. El servidor vuelve a validarlo.
socket.<canal>.broadcast(roomId, evento, payload) la sala, un evento de serverToClient y su entrada En el servidor, desde 1.2.

Valor devuelto#

En el navegador, cada canal es un SocketClient:

Miembro Tipo Descripción
on(evento, listener) () => void Escucha un evento de serverToClient, con su payload validado. Devuelve la función que deja de escuchar.
emit(evento, payload) Promise<void> Envía un evento de clientToServer. Rechaza si el canal no está abierto: no hay cola ni reenvío.
state ReadonlySignal<"connecting" | "open" | "closed" | "unauthorized" | …> El estado de la conexión. "unauthorized" es terminal: el servidor rechazó la sesión.
close() void Cierra el canal.

En el servidor, cada canal tiene broadcast(roomId, evento, payload): Promise<void>.

Ejemplos#

Un indicador de conexión#

state es una señal, así que la plantilla la sigue:

meeko
<script lang="ts">
const state = typeof window === "undefined" ? undefined : socket.chat.state;
</script>

<template>
  <span class="dot" bind:data-state="state?.value ?? 'connecting'">{state?.value ?? "connecting"}</span>
</template>

Escuchar y dejar de escuchar#

socket es de alcance de aplicación: un listener registrado fuera de un componente se cancela con la función que devuelve on.

ts
const off = socket.chat.on("message", (message) => console.log(message.author, message.text));
// …
off();

En un componente, useSocket("chat", { roomId }) crea una conexión propia que se cierra al desmontarlo, y es la forma recomendada para las salas.

Errores comunes#

Un payload que no cumple el schema#

Incorrectots
await socket.chat.emit("message", { body: "Hola" });

meeko check da TS2353: Object literal may only specify known properties, and 'body' does not exist in type '{ text: string; }'.

Correctots
await socket.chat.emit("message", { text: "Hola" });

Un canal o un evento que no existen#

Incorrectots
socket.lobby.emit("message", { text: "x" });
socket.chat.on("typing", () => {});

meeko check da TS2339: Property 'lobby' does not exist on type 'SocketRegistry<…>'. y TS2345: Argument of type '"typing"' is not assignable to parameter of type '"message"'.

Correctots
socket.chat.on("message", (message) => console.log(message.text));

Notas#

  • SSR. Durante el SSR no se abre ninguna conexión. Para escuchar en un componente, await useSocket(…) divide el setup y el resto se ejecuta sólo en el navegador.
  • Entrega. A lo sumo una vez: emit rechaza si el canal está desconectado. El cliente se reconecta con retroceso, salvo tras un rechazo de sesión ("unauthorized").
  • Seguridad. El servidor valida Origin, sesión, sala, evento y payload; un mensaje entrante inválido cierra el canal. broadcast comprueba el evento y el payload con el contrato, a diferencia de publicar a mano en el SocketPubSub.
  • Varias instancias. broadcast publica en el broker del runtime (socket.pubsub): en memoria con una instancia; con varias, RedisSocketPubSub o los Durable Objects de Workers.
  • Errores. Un fallo del transporte o de un listener llega al observador de errores de la aplicación (mountClientApp({ onError }) o el provideErrorHandler activo); sin él, a la consola.

Relacionado#

¿Te sirvió esta página?

Añadir un comentario

Se envía al elegir «Sí» o «No». Solo se guardan la página, tu respuesta y el comentario: ni cookies ni datos tuyos.

0 de 2000 caracteres

Editar en GitHub: docs/reference/utils/client-socket.md