WebSockets
Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →
Vous pouvez utiliser WebSockets pour tout ou partie de la communication avec votre serveur. Consultez wsLink pour la configuration côté client.
Ce document détaille l'utilisation spécifique des WebSockets. Pour une vue d'ensemble des abonnements, référez-vous à notre guide sur les abonnements.
Création d'un serveur WebSocket
bashyarn add ws
bashyarn add ws
server/wsServer.tstsimport { applyWSSHandler } from '@trpc/server/adapters/ws';import ws from 'ws';import { appRouter } from './routers/app';import { createContext } from './trpc';const wss = new ws.Server({port: 3001,});const handler = 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 ws from 'ws';import { appRouter } from './routers/app';import { createContext } from './trpc';const wss = new ws.Server({port: 3001,});const handler = 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();});
Configuration de TRPCClient pour utiliser WebSockets
Vous pouvez utiliser des Links pour router les queries/mutations via HTTP et les abonnements via WebSockets.
client.tstsximport { createTRPCClient, createWSClient, wsLink } from '@trpc/client';import type { AppRouter } from '../path/to/server/trpc';// create persistent WebSocket connectionconst wsClient = createWSClient({url: `ws://localhost:3001`,});// configure TRPCClient to use WebSockets transportconst client = createTRPCClient<AppRouter>({links: [wsLink({client: wsClient,}),],});
client.tstsximport { createTRPCClient, createWSClient, wsLink } from '@trpc/client';import type { AppRouter } from '../path/to/server/trpc';// create persistent WebSocket connectionconst wsClient = createWSClient({url: `ws://localhost:3001`,});// configure TRPCClient to use WebSockets transportconst client = createTRPCClient<AppRouter>({links: [wsLink({client: wsClient,}),],});
Authentification / paramètres de connexion
Dans une application web, vous pouvez ignorer cette section car les cookies sont envoyés avec la requête.
Pour vous authentifier via WebSockets, définissez des connectionParams dans createWSClient. Ils seront envoyés comme premier message lors de l'établissement de la connexion.
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/routers/_app';const wsClient = createWSClient({url: `ws://localhost:3000`,connectionParams: async () => {return {token: 'supersecret',};},});export const trpc = createTRPCClient<AppRouter>({links: [wsLink({ client: wsClient, transformer: superjson })],});
client/trpc.tstsimport { createTRPCClient, createWSClient, wsLink } from '@trpc/client';import type { AppRouter } from '~/server/routers/_app';const wsClient = createWSClient({url: `ws://localhost:3000`,connectionParams: async () => {return {token: 'supersecret',};},});export const trpc = createTRPCClient<AppRouter>({links: [wsLink({ client: wsClient, transformer: superjson })],});
Suivi automatique d'ID avec tracked() (recommandé)
Si vous utilisez yield avec notre helper tracked() en incluant un id, le client se reconnectera automatiquement après une déconnexion et renverra le dernier ID connu via lastEventId.
Vous pouvez envoyer un lastEventId initial lors de l'initialisation de l'abonnement, qui sera automatiquement mis à jour au fur et à mesure que le navigateur reçoit des données.
Si vous récupérez des données basées sur lastEventId et que la capture de tous les événements est critique, utilisez des ReadableStream ou un pattern similaire comme intermédiaire, comme dans notre exemple SSE full-stack, pour éviter l'ignorance d'événements émis pendant le traitement du batch initial basé sur lastEventId.
tsimport EventEmitter, { on } from 'events';import { tracked } from '@trpc/server';import { z } from 'zod';import { publicProcedure, router } from '../trpc';const ee = new EventEmitter();export const subRouter = 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] of on(ee, 'add', {// Passing the AbortSignal from the request automatically cancels the event emitter when the subscription is abortedsignal: opts.signal,})) {const post = data as Post;// tracking the post id ensures the client can reconnect at any time and get the latest events this idyield tracked(post.id, post);}}),});
tsimport EventEmitter, { on } from 'events';import { tracked } from '@trpc/server';import { z } from 'zod';import { publicProcedure, router } from '../trpc';const ee = new EventEmitter();export const subRouter = 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] of on(ee, 'add', {// Passing the AbortSignal from the request automatically cancels the event emitter when the subscription is abortedsignal: opts.signal,})) {const post = data as Post;// tracking the post id ensures the client can reconnect at any time and get the latest events this idyield tracked(post.id, post);}}),});
Spécification RPC pour WebSockets
Pour plus de détails, explorez les définitions TypeScript :
query / mutation
Requête
ts{id: number | string;jsonrpc?: '2.0'; // optionalmethod: 'query' | 'mutation';params: {path: string;input?: unknown; // <-- pass input of procedure, serialized by transformer};}
ts{id: number | string;jsonrpc?: '2.0'; // optionalmethod: 'query' | 'mutation';params: {path: string;input?: unknown; // <-- pass input of procedure, serialized by transformer};}
Réponse
... ci-dessous, ou une erreur.
ts{id: number | string;jsonrpc?: '2.0'; // only defined if included in requestresult: {type: 'data'; // always 'data' for mutation / queriesdata: TOutput; // output from procedure}}
ts{id: number | string;jsonrpc?: '2.0'; // only defined if included in requestresult: {type: 'data'; // always 'data' for mutation / queriesdata: TOutput; // output from procedure}}
subscription / subscription.stop
Démarrer un abonnement
ts{id: number | string;jsonrpc?: '2.0';method: 'subscription';params: {path: string;input?: unknown; // <-- pass input of procedure, serialized by transformer};}
ts{id: number | string;jsonrpc?: '2.0';method: 'subscription';params: {path: string;input?: unknown; // <-- pass input of procedure, serialized by transformer};}
Pour annuler un abonnement, appelez subscription.stop
ts{id: number | string; // <-- id of your created subscriptionjsonrpc?: '2.0';method: 'subscription.stop';}
ts{id: number | string; // <-- id of your created subscriptionjsonrpc?: '2.0';method: 'subscription.stop';}
Format de réponse d'abonnement
... ci-dessous, ou une erreur.
ts{id: number | string;jsonrpc?: '2.0';result: (| {type: 'data';data: TData; // subscription emitted data}| {type: 'started'; // subscription started}| {type: 'stopped'; // subscription stopped})}
ts{id: number | string;jsonrpc?: '2.0';result: (| {type: 'data';data: TData; // subscription emitted data}| {type: 'started'; // subscription started}| {type: 'stopped'; // subscription stopped})}
Paramètres de connexion
Si la connexion est initialisée avec ?connectionParams=1, le premier message doit contenir les paramètres de connexion.
ts{data: Record<string, string> | null;method: 'connectionParams';}
ts{data: Record<string, string> | null;method: 'connectionParams';}
Erreurs
Voir https://www.jsonrpc.org/specification#error_object ou Formatage des erreurs.
Notifications du serveur au client
{ id: null, type: 'reconnect' }
Demande aux clients de se reconnecter avant l'arrêt du serveur. Appelé via wssHandler.broadcastReconnectNotification().