웹소켓
이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →
서버와의 통신 전부 또는 일부에 WebSockets를 사용할 수 있습니다. 클라이언트 설정 방법은 wsLink를 참조하세요.
이 문서는 WebSockets 사용에 관한 구체적인 세부 사항을 설명합니다. 구독의 일반적인 사용법은 구독 가이드를 참조하세요.
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 ();});
TRPCClient를 WebSockets로 설정하기
쿼리 및/또는 뮤테이션은 HTTP 전송으로, 구독은 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 ,}),],});
인증 / 연결 매개변수
웹 애플리케이션을 개발 중이라면, 쿠키가 요청의 일부로 전송되므로 이 섹션을 무시해도 됩니다.
WebSockets 인증을 위해 createWSClient에 connectionParams를 정의할 수 있습니다. 이는 클라이언트가 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 })],});
tracked()를 사용한 ID 자동 추적 (권장)
tracked() 헬퍼를 사용해 이벤트를 yield하고 id를 포함하면, 클라이언트는 연결이 끊겼을 때 자동으로 재연결하며 재연결 시 lastEventId 입력으로 마지막으로 확인된 ID를 전송합니다.
구독 초기화 시 초기 lastEventId를 전송할 수 있으며, 브라우저가 데이터를 수신하면 자동으로 업데이트됩니다.
lastEventId 기반 데이터 조회 시 모든 이벤트 수집이 중요한 경우, 전체 스택 SSE 예시에서처럼 ReadableStream 또는 유사한 패턴을 중간 계층으로 사용해 lastEventId 기반 원본 배치를 yield하는 동안 새로 발생한 이벤트가 무시되는 것을 방지할 수 있습니다.
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 );}}),});
WebSockets RPC 사양
자세한 내용은 TypeScript 정의를 확인하세요:
query / mutation
요청
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};}
응답
... 또는 아래 오류 발생
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
구독 시작
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};}
구독 취소를 위해 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';}
구독 응답 형태
... 또는 아래 오류 발생
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};}
연결 매개변수
연결이 ?connectionParams=1로 초기화된 경우, 첫 번째 메시지는 연결 매개변수여야 합니다.
tsinterfaceConnectionParamsMessage {data :Record <string, string> | null;method : 'connectionParams';}
tsinterfaceConnectionParamsMessage {data :Record <string, string> | null;method : 'connectionParams';}
오류
https://www.jsonrpc.org/specification#error_object 또는 오류 형식 참조.
서버→클라이언트 알림
{ id: null, type: 'reconnect' }
서버 종료 전 클라이언트에게 재연결을 지시합니다. wssHandler.broadcastReconnectNotification()로 호출됩니다.