Hoppa till huvudinnehållet
Version: 11.x

WebSockets

Inofficiell Beta-översättning

Denna sida har översatts av PageTurner AI (beta). Inte officiellt godkänd av projektet. Hittade du ett fel? Rapportera problem →

Du kan använda WebSockets för all eller del av kommunikationen med din server. Se wsLink för hur du konfigurerar det på klientsidan.

tips

Det här dokumentet beskriver specifika detaljer för WebSockets-användning. För generell användning av prenumerationer, se vår prenumerationsguide.

Skapa en WebSocket-server

bash
yarn add ws
bash
yarn add ws
server/wsServer.ts
ts
import { applyWSSHandler } from '@trpc/server/adapters/ws';
import { WebSocketServer } from 'ws';
import { appRouter } from './routers/app';
import { createContext } from './trpc';
 
const wss = new WebSocketServer({
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 milliseconds
pingMs: 30000,
// connection is terminated if pong message is not received in this many milliseconds
pongWaitMs: 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.ts
ts
import { applyWSSHandler } from '@trpc/server/adapters/ws';
import { WebSocketServer } from 'ws';
import { appRouter } from './routers/app';
import { createContext } from './trpc';
 
const wss = new WebSocketServer({
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 milliseconds
pingMs: 30000,
// connection is terminated if pong message is not received in this many milliseconds
pongWaitMs: 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();
});

Konfigurera TRPCClient för WebSockets

tips

Du kan använda Länkar för att dirigera queries och/eller mutations via HTTP-transport och prenumerationer via WebSockets.

client.ts
tsx
import { createTRPCClient, createWSClient, wsLink } from '@trpc/client';
import type { AppRouter } from './server';
 
// create persistent WebSocket connection
const wsClient = createWSClient({
url: `ws://localhost:3001`,
});
 
// configure TRPCClient to use WebSockets transport
const client = createTRPCClient<AppRouter>({
links: [
wsLink({
client: wsClient,
}),
],
});
client.ts
tsx
import { createTRPCClient, createWSClient, wsLink } from '@trpc/client';
import type { AppRouter } from './server';
 
// create persistent WebSocket connection
const wsClient = createWSClient({
url: `ws://localhost:3001`,
});
 
// configure TRPCClient to use WebSockets transport
const client = createTRPCClient<AppRouter>({
links: [
wsLink({
client: wsClient,
}),
],
});

Autentisering / anslutningsparametrar

tips

Om du utvecklar en webbapplikation kan du ignorera detta avsnitt eftersom cookies skickas med i förfrågan.

För att autentisera med WebSockets kan du definiera connectionParams i createWSClient. Dessa skickas som första meddelande när klienten etablerar en WebSocket-anslutning.

server/context.ts
ts
import type { CreateWSSContextFnOptions } from '@trpc/server/adapters/ws';
 
export const createContext = async (opts: CreateWSSContextFnOptions) => {
const token = opts.info.connectionParams?.token;
const token: string | undefined
 
// [... authenticate]
 
return {};
};
 
export type Context = Awaited<ReturnType<typeof createContext>>;
server/context.ts
ts
import type { CreateWSSContextFnOptions } from '@trpc/server/adapters/ws';
 
export const createContext = async (opts: CreateWSSContextFnOptions) => {
const token = opts.info.connectionParams?.token;
const token: string | undefined
 
// [... authenticate]
 
return {};
};
 
export type Context = Awaited<ReturnType<typeof createContext>>;
client/trpc.ts
ts
import { createTRPCClient, createWSClient, wsLink } from '@trpc/client';
import type { AppRouter } from './server';
import superjson from 'superjson';
 
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.ts
ts
import { createTRPCClient, createWSClient, wsLink } from '@trpc/client';
import type { AppRouter } from './server';
import superjson from 'superjson';
 
const wsClient = createWSClient({
url: `ws://localhost:3000`,
 
connectionParams: async () => {
return {
token: 'supersecret',
};
},
});
export const trpc = createTRPCClient<AppRouter>({
links: [wsLink({ client: wsClient, transformer: superjson })],
});

Automatisk ID-spårning med tracked() (rekommenderas)

Om du yield:ar en händelse med vår tracked()-hjälpfunktion och inkluderar ett id kommer klienten automatiskt återansluta vid avbrott och skicka det senaste kända ID:t vid återanslutning som del av lastEventId-input.

Du kan skicka ett initialt lastEventId vid prenumerationsstart, vilket automatiskt uppdateras när webbläsaren tar emot data.

information

Om du hämtar data baserat på lastEventId och det är kritiskt att fånga alla händelser, överväg att använda ReadableStream eller liknande mönster som mellanlager. Detta görs i vårt fullständiga SSE-exempel för att förhindra att nysända händelser ignoreras medan den ursprungliga batchen baserad på lastEventId skapas.

ts
import EventEmitter, { on } from 'events';
import { initTRPC, tracked } from '@trpc/server';
import { z } from 'zod';
 
type Post = { id: string; title: string };
 
const t = initTRPC.create();
const publicProcedure = t.procedure;
const router = t.router;
 
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 received
lastEventId: 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 events
for await (const [data] of on(ee, 'add', {
// Passing the AbortSignal from the request automatically cancels the event emitter when the subscription is aborted
signal: opts.signal,
})) {
const post = data as Post;
// tracking the post id ensures the client can reconnect at any time and get the latest events since this id
yield tracked(post.id, post);
}
}),
});
ts
import EventEmitter, { on } from 'events';
import { initTRPC, tracked } from '@trpc/server';
import { z } from 'zod';
 
type Post = { id: string; title: string };
 
const t = initTRPC.create();
const publicProcedure = t.procedure;
const router = t.router;
 
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 received
lastEventId: 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 events
for await (const [data] of on(ee, 'add', {
// Passing the AbortSignal from the request automatically cancels the event emitter when the subscription is aborted
signal: opts.signal,
})) {
const post = data as Post;
// tracking the post id ensures the client can reconnect at any time and get the latest events since this id
yield tracked(post.id, post);
}
}),
});

WebSockets RPC-specifikation

Mer detaljer finns i TypeScript-definitionerna:

query / mutation

Förfrågan

ts
interface RequestMessage {
id: number | string;
jsonrpc?: '2.0';
method: 'query' | 'mutation';
params: {
path: string;
input?: unknown; // <-- pass input of procedure, serialized by transformer
};
}
ts
interface RequestMessage {
id: number | string;
jsonrpc?: '2.0';
method: 'query' | 'mutation';
params: {
path: string;
input?: unknown; // <-- pass input of procedure, serialized by transformer
};
}

Svar

... nedan, eller ett felmeddelande.

ts
interface ResponseMessage {
id: number | string;
jsonrpc?: '2.0';
result: {
type: 'data'; // always 'data' for mutation / queries
data: TOutput; // output from procedure
};
}
ts
interface ResponseMessage {
id: number | string;
jsonrpc?: '2.0';
result: {
type: 'data'; // always 'data' for mutation / queries
data: TOutput; // output from procedure
};
}

subscription / subscription.stop

Starta en prenumeration

ts
interface SubscriptionRequest {
id: number | string;
jsonrpc?: '2.0';
method: 'subscription';
params: {
path: string;
input?: unknown; // <-- pass input of procedure, serialized by transformer
};
}
ts
interface SubscriptionRequest {
id: number | string;
jsonrpc?: '2.0';
method: 'subscription';
params: {
path: string;
input?: unknown; // <-- pass input of procedure, serialized by transformer
};
}

Avbryt en prenumeration med subscription.stop

ts
interface SubscriptionStopRequest {
id: number | string; // <-- id of your created subscription
jsonrpc?: '2.0';
method: 'subscription.stop';
}
ts
interface SubscriptionStopRequest {
id: number | string; // <-- id of your created subscription
jsonrpc?: '2.0';
method: 'subscription.stop';
}

Prenumerationssvarsformat

... nedan, eller ett felmeddelande.

ts
interface SubscriptionResponse {
id: number | string;
jsonrpc?: '2.0';
result:
| {
type: 'data';
data: TData; // subscription emitted data
}
| {
type: 'started'; // subscription started
}
| {
type: 'stopped'; // subscription stopped
};
}
ts
interface SubscriptionResponse {
id: number | string;
jsonrpc?: '2.0';
result:
| {
type: 'data';
data: TData; // subscription emitted data
}
| {
type: 'started'; // subscription started
}
| {
type: 'stopped'; // subscription stopped
};
}

Anslutningsparametrar

Om anslutningen initieras med ?connectionParams=1 måste första meddelandet vara anslutningsparametrar.

ts
interface ConnectionParamsMessage {
data: Record<string, string> | null;
method: 'connectionParams';
}
ts
interface ConnectionParamsMessage {
data: Record<string, string> | null;
method: 'connectionParams';
}

Felmeddelanden

Se https://www.jsonrpc.org/specification#error_object eller Felformatering.

Notifieringar från server till klient

{ id: null, type: 'reconnect' }

Informerar klienter att återansluta innan servern stängs ned. Anropas via wssHandler.broadcastReconnectNotification().