WebSockets
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Puedes usar WebSockets para toda o parte de la comunicación con tu servidor. Consulta wsLink para configurarlo en el cliente.
Este documento detalla aspectos específicos de WebSockets. Para uso general de suscripciones, visita nuestra guía de suscripciones.
Creación de un servidor WebSocket
bashyarn add ws
bashyarn add ws
server/wsServer.tstsimport {applyWSSHandler } from '@trpc/server/adapters/ws';import {WebSocketServer } from 'ws';import {appRouter } from './routers/app';import {createContext } from './trpc';constwss = newWebSocketServer ({port : 3001,});consthandler =applyWSSHandler ({wss ,router :appRouter ,createContext ,// Enable heartbeat messages to keep connection open (disabled by default)keepAlive : {enabled : true,// server ping message interval in millisecondspingMs : 30000,// connection is terminated if pong message is not received in this many millisecondspongWaitMs : 5000,},});wss .on ('connection', (ws ) => {console .log (`++ Connection (${wss .clients .size })`);ws .once ('close', () => {console .log (`-- Connection (${wss .clients .size })`);});});console .log ('WebSocket Server listening on ws://localhost:3001');process .on ('SIGTERM', () => {console .log ('SIGTERM');handler .broadcastReconnectNotification ();wss .close ();});
server/wsServer.tstsimport {applyWSSHandler } from '@trpc/server/adapters/ws';import {WebSocketServer } from 'ws';import {appRouter } from './routers/app';import {createContext } from './trpc';constwss = newWebSocketServer ({port : 3001,});consthandler =applyWSSHandler ({wss ,router :appRouter ,createContext ,// Enable heartbeat messages to keep connection open (disabled by default)keepAlive : {enabled : true,// server ping message interval in millisecondspingMs : 30000,// connection is terminated if pong message is not received in this many millisecondspongWaitMs : 5000,},});wss .on ('connection', (ws ) => {console .log (`++ Connection (${wss .clients .size })`);ws .once ('close', () => {console .log (`-- Connection (${wss .clients .size })`);});});console .log ('WebSocket Server listening on ws://localhost:3001');process .on ('SIGTERM', () => {console .log ('SIGTERM');handler .broadcastReconnectNotification ();wss .close ();});
Configurar TRPCClient para usar WebSockets
Puedes usar Links para enrutar queries y/o mutations a transporte HTTP, y suscripciones a través de WebSockets.
client.tstsximport {createTRPCClient ,createWSClient ,wsLink } from '@trpc/client';import type {AppRouter } from './server';// create persistent WebSocket connectionconstwsClient =createWSClient ({url : `ws://localhost:3001`,});// configure TRPCClient to use WebSockets transportconstclient =createTRPCClient <AppRouter >({links : [wsLink ({client :wsClient ,}),],});
client.tstsximport {createTRPCClient ,createWSClient ,wsLink } from '@trpc/client';import type {AppRouter } from './server';// create persistent WebSocket connectionconstwsClient =createWSClient ({url : `ws://localhost:3001`,});// configure TRPCClient to use WebSockets transportconstclient =createTRPCClient <AppRouter >({links : [wsLink ({client :wsClient ,}),],});
Autenticación / parámetros de conexión
En aplicaciones web puedes omitir esta sección, ya que las cookies se envían automáticamente con la solicitud.
Para autenticación con WebSockets, define connectionParams en createWSClient. Esto se enviará como primer mensaje cuando el cliente establezca la conexión WebSocket.
server/context.tstsimport type {CreateWSSContextFnOptions } from '@trpc/server/adapters/ws';export constcreateContext = async (opts :CreateWSSContextFnOptions ) => {consttoken =opts .info .connectionParams ?.token ;// [... authenticate]return {};};export typeContext =Awaited <ReturnType <typeofcreateContext >>;
server/context.tstsimport type {CreateWSSContextFnOptions } from '@trpc/server/adapters/ws';export constcreateContext = async (opts :CreateWSSContextFnOptions ) => {consttoken =opts .info .connectionParams ?.token ;// [... authenticate]return {};};export typeContext =Awaited <ReturnType <typeofcreateContext >>;
client/trpc.tstsimport {createTRPCClient ,createWSClient ,wsLink } from '@trpc/client';import type {AppRouter } from './server';importsuperjson from 'superjson';constwsClient =createWSClient ({url : `ws://localhost:3000`,connectionParams : async () => {return {token : 'supersecret',};},});export consttrpc =createTRPCClient <AppRouter >({links : [wsLink ({client :wsClient ,transformer :superjson })],});
client/trpc.tstsimport {createTRPCClient ,createWSClient ,wsLink } from '@trpc/client';import type {AppRouter } from './server';importsuperjson from 'superjson';constwsClient =createWSClient ({url : `ws://localhost:3000`,connectionParams : async () => {return {token : 'supersecret',};},});export consttrpc =createTRPCClient <AppRouter >({links : [wsLink ({client :wsClient ,transformer :superjson })],});
Seguimiento automático de ID con tracked() (recomendado)
Si usas yield con nuestro helper tracked() incluyendo un id, el cliente se reconectará automáticamente al desconectarse y enviará el último ID conocido como parte del input lastEventId.
Puedes enviar un lastEventId inicial al iniciar la suscripción, que se actualizará automáticamente cuando el navegador reciba datos.
Si obtienes datos basados en lastEventId y es crítico capturar todos los eventos, considera usar ReadableStream o patrones similares como intermediarios, como en nuestro ejemplo SSE full-stack, para evitar ignorar eventos nuevos mientras se procesa el lote original basado en lastEventId.
tsimportEventEmitter , {on } from 'events';import {initTRPC ,tracked } from '@trpc/server';import {z } from 'zod';typePost = {id : string;title : string };constt =initTRPC .create ();constpublicProcedure =t .procedure ;constrouter =t .router ;constee = newEventEmitter ();export constsubRouter =router ({onPostAdd :publicProcedure .input (z .object ({// lastEventId is the last event id that the client has received// On the first call, it will be whatever was passed in the initial setup// If the client reconnects, it will be the last event id that the client receivedlastEventId :z .string ().nullish (),}).optional (),).subscription (async function* (opts ) {if (opts .input ?.lastEventId ) {// [...] get the posts since the last event id and yield them}// listen for new eventsfor await (const [data ] ofon (ee , 'add', {// Passing the AbortSignal from the request automatically cancels the event emitter when the subscription is abortedsignal :opts .signal ,})) {constpost =data asPost ;// tracking the post id ensures the client can reconnect at any time and get the latest events since this idyieldtracked (post .id ,post );}}),});
tsimportEventEmitter , {on } from 'events';import {initTRPC ,tracked } from '@trpc/server';import {z } from 'zod';typePost = {id : string;title : string };constt =initTRPC .create ();constpublicProcedure =t .procedure ;constrouter =t .router ;constee = newEventEmitter ();export constsubRouter =router ({onPostAdd :publicProcedure .input (z .object ({// lastEventId is the last event id that the client has received// On the first call, it will be whatever was passed in the initial setup// If the client reconnects, it will be the last event id that the client receivedlastEventId :z .string ().nullish (),}).optional (),).subscription (async function* (opts ) {if (opts .input ?.lastEventId ) {// [...] get the posts since the last event id and yield them}// listen for new eventsfor await (const [data ] ofon (ee , 'add', {// Passing the AbortSignal from the request automatically cancels the event emitter when the subscription is abortedsignal :opts .signal ,})) {constpost =data asPost ;// tracking the post id ensures the client can reconnect at any time and get the latest events since this idyieldtracked (post .id ,post );}}),});
Especificación RPC para WebSockets
Puedes explorar más detalles en las definiciones de TypeScript:
query / mutation
Solicitud
tsinterfaceRequestMessage {id : number | string;jsonrpc ?: '2.0';method : 'query' | 'mutation';params : {path : string;input ?: unknown; // <-- pass input of procedure, serialized by transformer};}
tsinterfaceRequestMessage {id : number | string;jsonrpc ?: '2.0';method : 'query' | 'mutation';params : {path : string;input ?: unknown; // <-- pass input of procedure, serialized by transformer};}
Respuesta
... o un error a continuación.
tsinterfaceResponseMessage {id : number | string;jsonrpc ?: '2.0';result : {type : 'data'; // always 'data' for mutation / queriesdata :TOutput ; // output from procedure};}
tsinterfaceResponseMessage {id : number | string;jsonrpc ?: '2.0';result : {type : 'data'; // always 'data' for mutation / queriesdata :TOutput ; // output from procedure};}
subscription / subscription.stop
Iniciar una suscripción
tsinterfaceSubscriptionRequest {id : number | string;jsonrpc ?: '2.0';method : 'subscription';params : {path : string;input ?: unknown; // <-- pass input of procedure, serialized by transformer};}
tsinterfaceSubscriptionRequest {id : number | string;jsonrpc ?: '2.0';method : 'subscription';params : {path : string;input ?: unknown; // <-- pass input of procedure, serialized by transformer};}
Para cancelar una suscripción, llama subscription.stop
tsinterfaceSubscriptionStopRequest {id : number | string; // <-- id of your created subscriptionjsonrpc ?: '2.0';method : 'subscription.stop';}
tsinterfaceSubscriptionStopRequest {id : number | string; // <-- id of your created subscriptionjsonrpc ?: '2.0';method : 'subscription.stop';}
Formato de respuesta de suscripción
... o un error a continuación.
tsinterfaceSubscriptionResponse {id : number | string;jsonrpc ?: '2.0';result :| {type : 'data';data :TData ; // subscription emitted data}| {type : 'started'; // subscription started}| {type : 'stopped'; // subscription stopped};}
tsinterfaceSubscriptionResponse {id : number | string;jsonrpc ?: '2.0';result :| {type : 'data';data :TData ; // subscription emitted data}| {type : 'started'; // subscription started}| {type : 'stopped'; // subscription stopped};}
Parámetros de conexión
Si la conexión se inicializa con ?connectionParams=1, el primer mensaje deben ser los parámetros de conexión.
tsinterfaceConnectionParamsMessage {data :Record <string, string> | null;method : 'connectionParams';}
tsinterfaceConnectionParamsMessage {data :Record <string, string> | null;method : 'connectionParams';}
Errores
Consulta https://www.jsonrpc.org/specification#error_object o Formato de errores.
Notificaciones de servidor a cliente
{ id: null, type: 'reconnect' }
Indica a los clientes que se reconecten antes de apagar el servidor. Se invoca mediante wssHandler.broadcastReconnectNotification().