HTTP 구독 링크
httpSubscriptionLink는 구독을 위해 서버 전송 이벤트(SSE)를 사용하는 종단 링크입니다.
SSE는 WebSocket 서버 설정보다 상대적으로 간단하므로 실시간 처리에 좋은 선택지입니다.
설정
클라이언트 환경이 EventSource를 지원하지 않는 경우 EventSource 폴리필이 필요합니다. React Native에 대한 구체적인 지침은 호환성 섹션을 참조하세요.
httpSubscriptionLink를 사용하려면 구독에 SSE를 사용한다는 것을 명시하기 위해 splitLink를 사용해야 합니다.
client/index.tstsimport {createTRPCClient ,httpBatchLink ,httpSubscriptionLink ,loggerLink ,splitLink ,} from '@trpc/client';import type {AppRouter } from './server';consttrpcClient =createTRPCClient <AppRouter >({/*** @see https://trpc.io/docs/v11/client/links*/links : [// adds pretty logs to your console in development and logs errors in productionloggerLink (),splitLink ({// uses the httpSubscriptionLink for subscriptionscondition : (op ) =>op .type === 'subscription',true :httpSubscriptionLink ({url : `/api/trpc`,}),false :httpBatchLink ({url : `/api/trpc`,}),}),],});
client/index.tstsimport {createTRPCClient ,httpBatchLink ,httpSubscriptionLink ,loggerLink ,splitLink ,} from '@trpc/client';import type {AppRouter } from './server';consttrpcClient =createTRPCClient <AppRouter >({/*** @see https://trpc.io/docs/v11/client/links*/links : [// adds pretty logs to your console in development and logs errors in productionloggerLink (),splitLink ({// uses the httpSubscriptionLink for subscriptionscondition : (op ) =>op .type === 'subscription',true :httpSubscriptionLink ({url : `/api/trpc`,}),false :httpBatchLink ({url : `/api/trpc`,}),}),],});
이 문서에서는 httpSubscriptionLink 사용에 대한 구체적인 세부 사항을 설명합니다. 구독의 일반적인 사용법에 대해서는 구독 가이드를 참조하세요.
헤더 및 인증 / 인증 처리
웹 앱
동일 도메인
웹 애플리케이션을 개발하는 경우, 클라이언트가 서버와 동일한 도메인에 있으면 쿠키가 요청의 일부로 전송됩니다.
크로스 도메인
클라이언트와 서버가 동일한 도메인에 있지 않은 경우, withCredentials: true(MDN에서 자세히 보기)를 사용할 수 있습니다.
예시:
tsx// [...]httpSubscriptionLink ({url : 'https://example.com/api/trpc',eventSourceOptions () {return {withCredentials : true, // <---};},});
tsx// [...]httpSubscriptionLink ({url : 'https://example.com/api/trpc',eventSourceOptions () {return {withCredentials : true, // <---};},});
폴리필을 통한 사용자 정의 헤더
비웹 환경에 권장
EventSource를 폴리필하고 eventSourceOptions 콜백을 사용하여 헤더를 설정할 수 있습니다.
활성 연결에서 설정 업데이트
httpSubscriptionLink는 EventSource를 통해 SSE를 활용하여 네트워크 장애나 잘못된 응답 코드와 같은 오류가 발생한 연결이 자동으로 재시도되도록 보장합니다. 그러나 EventSource는 설정을 업데이트하기 위해 eventSourceOptions() 또는 url() 옵션을 다시 실행할 수 없으며, 이는 마지막 연결 이후 인증이 만료된 시나리오에서 특히 중요합니다.
이러한 한계를 해결하기 위해 httpSubscriptionLink와 함께 retryLink를 사용할 수 있습니다. 이 접근 방식은 업데이트된 인증 세부 정보를 포함한 최신 설정으로 연결이 다시 설정되도록 보장합니다.
연결을 다시 시작하면 EventSource가 처음부터 다시 생성되므로 이전에 추적된 이벤트가 손실된다는 점에 유의하세요.
연결 매개변수
EventSource로 인증하려면 httpSubscriptionLink에서 connectionParams를 정의할 수 있습니다. 이는 URL의 일부로 전송되므로 다른 방법이 선호됩니다.
server/context.tstsimport type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';export constcreateContext = async (opts :CreateHTTPContextOptions ) => {consttoken =opts .info .connectionParams ?.token ;// [... authenticate]return {};};export typeContext =Awaited <ReturnType <typeofcreateContext >>;
server/context.tstsimport type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';export constcreateContext = async (opts :CreateHTTPContextOptions ) => {consttoken =opts .info .connectionParams ?.token ;// [... authenticate]return {};};export typeContext =Awaited <ReturnType <typeofcreateContext >>;
client/trpc.tstsimport {createTRPCClient ,httpBatchLink ,httpSubscriptionLink ,splitLink ,} from '@trpc/client';import type {AppRouter } from './server';// Initialize the tRPC clientconsttrpc =createTRPCClient <AppRouter >({links : [splitLink ({condition : (op ) =>op .type === 'subscription',true :httpSubscriptionLink ({url : 'http://localhost:3000',connectionParams : async () => {// Will be serialized as part of the URLreturn {token : 'supersecret',};},}),false :httpBatchLink ({url : 'http://localhost:3000',}),}),],});
client/trpc.tstsimport {createTRPCClient ,httpBatchLink ,httpSubscriptionLink ,splitLink ,} from '@trpc/client';import type {AppRouter } from './server';// Initialize the tRPC clientconsttrpc =createTRPCClient <AppRouter >({links : [splitLink ({condition : (op ) =>op .type === 'subscription',true :httpSubscriptionLink ({url : 'http://localhost:3000',connectionParams : async () => {// Will be serialized as part of the URLreturn {token : 'supersecret',};},}),false :httpBatchLink ({url : 'http://localhost:3000',}),}),],});
타임아웃 설정
httpSubscriptionLink는 reconnectAfterInactivityMs 옵션을 통해 비활성 상태에 대한 타임아웃을 설정할 수 있습니다. 지정된 타임아웃 기간 내에 메시지(핑 메시지를 포함하여)가 수신되지 않으면 연결이 "연결 중"으로 표시되고 자동으로 재연결을 시도합니다.
타임아웃 설정은 tRPC를 초기화할 때 서버 측에서 설정됩니다:
server/trpc.tstsimport {initTRPC } from '@trpc/server';export constt =initTRPC .create ({sse : {client : {reconnectAfterInactivityMs : 3_000,},},});
server/trpc.tstsimport {initTRPC } from '@trpc/server';export constt =initTRPC .create ({sse : {client : {reconnectAfterInactivityMs : 3_000,},},});
서버 핑 설정
서버는 연결을 유지하고 타임아웃으로 인한 연결 끊김을 방지하기 위해 주기적인 핑 메시지를 보내도록 설정할 수 있습니다. 이는 reconnectAfterInactivityMs 옵션과 함께 사용할 때 특히 유용합니다.
server/trpc.tstsimport {initTRPC } from '@trpc/server';export constt =initTRPC .create ({sse : {// Maximum duration of a single SSE connection in milliseconds// maxDurationMs: 60_000,ping : {// Enable periodic ping messages to keep connection aliveenabled : true,// Send ping message every 2sintervalMs : 2_000,},// client: {// reconnectAfterInactivityMs: 3_000// }},});
server/trpc.tstsimport {initTRPC } from '@trpc/server';export constt =initTRPC .create ({sse : {// Maximum duration of a single SSE connection in milliseconds// maxDurationMs: 60_000,ping : {// Enable periodic ping messages to keep connection aliveenabled : true,// Send ping message every 2sintervalMs : 2_000,},// client: {// reconnectAfterInactivityMs: 3_000// }},});
호환성 (React Native)
httpSubscriptionLink는 EventSource API, 스트림 API, AsyncIterator를 사용하며, 이러한 기능은 React Native에서 기본적으로 지원되지 않으므로 폴리필이 필요합니다.
EventSource를 폴리필할 때는 XMLHttpRequest API를 사용하는 폴리필보다 React Native가 노출하는 네트워킹 라이브러리를 활용하는 폴리필 사용을 권장합니다. XMLHttpRequest를 사용하여 EventSource를 폴리필하는 라이브러리는 앱이 백그라운드에 있다가 다시 실행된 후 재연결에 실패할 수 있습니다. rn-eventsource-reborn 패키지를 사용하는 것을 고려하세요.
Streams API는 web-streams-polyfill 패키지로 ponyfill할 수 있습니다.
AsyncIterator는 @azure/core-asynciterator-polyfill 패키지로 polyfill할 수 있습니다.
설치
필요한 polyfill을 설치합니다:
- npm
- yarn
- pnpm
- bun
- deno
npm install rn-eventsource-reborn web-streams-polyfill @azure/core-asynciterator-polyfill
yarn add rn-eventsource-reborn web-streams-polyfill @azure/core-asynciterator-polyfill
pnpm add rn-eventsource-reborn web-streams-polyfill @azure/core-asynciterator-polyfill
bun add rn-eventsource-reborn web-streams-polyfill @azure/core-asynciterator-polyfill
deno add npm:rn-eventsource-reborn npm:web-streams-polyfill npm:@azure/core-asynciterator-polyfill
링크가 사용되기 전에(예: TRPCReact.Provider를 추가하는 위치) polyfill을 프로젝트에 추가합니다:
utils/api.tsxtsimport '@azure/core-asynciterator-polyfill';import { RNEventSource } from 'rn-eventsource-reborn';import { ReadableStream, TransformStream } from 'web-streams-polyfill';globalThis.ReadableStream = globalThis.ReadableStream || ReadableStream;globalThis.TransformStream = globalThis.TransformStream || TransformStream;
utils/api.tsxtsimport '@azure/core-asynciterator-polyfill';import { RNEventSource } from 'rn-eventsource-reborn';import { ReadableStream, TransformStream } from 'web-streams-polyfill';globalThis.ReadableStream = globalThis.ReadableStream || ReadableStream;globalThis.TransformStream = globalThis.TransformStream || TransformStream;
ponyfill이 추가되면 setup 섹션에 설명된 대로 httpSubscriptionLink 설정을 계속 진행할 수 있습니다.
httpSubscriptionLink 옵션
tstypeHTTPSubscriptionLinkOptions <TRoot extendsAnyClientTypes ,TEventSource extendsEventSourceLike .AnyConstructor = typeofEventSource ,> = {/*** The URL to connect to (can be a function that returns a URL)*/url : string | (() => string |Promise <string>);/*** Connection params that are available in `createContext()`* Serialized as part of the URL under the `connectionParams` query parameter*/connectionParams ?:|Record <string, string>| null| (() =>|Record <string, string>| null|Promise <Record <string, string> | null>);/*** Data transformer* @see https://trpc.io/docs/v11/data-transformers*/transformer ?:DataTransformerOptions ;/*** EventSource ponyfill*/EventSource ?:TEventSource ;/*** EventSource options or a callback that returns them*/eventSourceOptions ?:|EventSourceLike .InitDictOf <TEventSource >| ((opts : {op :Operation ;}) =>|EventSourceLike .InitDictOf <TEventSource >|Promise <EventSourceLike .InitDictOf <TEventSource >>);};
tstypeHTTPSubscriptionLinkOptions <TRoot extendsAnyClientTypes ,TEventSource extendsEventSourceLike .AnyConstructor = typeofEventSource ,> = {/*** The URL to connect to (can be a function that returns a URL)*/url : string | (() => string |Promise <string>);/*** Connection params that are available in `createContext()`* Serialized as part of the URL under the `connectionParams` query parameter*/connectionParams ?:|Record <string, string>| null| (() =>|Record <string, string>| null|Promise <Record <string, string> | null>);/*** Data transformer* @see https://trpc.io/docs/v11/data-transformers*/transformer ?:DataTransformerOptions ;/*** EventSource ponyfill*/EventSource ?:TEventSource ;/*** EventSource options or a callback that returns them*/eventSourceOptions ?:|EventSourceLike .InitDictOf <TEventSource >| ((opts : {op :Operation ;}) =>|EventSourceLike .InitDictOf <TEventSource >|Promise <EventSourceLike .InitDictOf <TEventSource >>);};
서버의 SSE 옵션
tsexport interfaceSSEStreamProducerOptions <TValue = unknown> {ping ?: {/*** Enable ping comments sent from the server* @default false*/enabled : boolean;/*** Interval in milliseconds* @default 1000*/intervalMs ?: number;};/*** Maximum duration in milliseconds for the request before ending the stream* @default undefined*/maxDurationMs ?: number;/*** End the request immediately after data is sent* Only useful for serverless runtimes that do not support streaming responses* @default false*/emitAndEndImmediately ?: boolean;/*** Client-specific options - these will be sent to the client as part of the first message* @default {}*/client ?: {/*** Timeout and reconnect after inactivity in milliseconds* @default undefined*/reconnectAfterInactivityMs ?: number;};}
tsexport interfaceSSEStreamProducerOptions <TValue = unknown> {ping ?: {/*** Enable ping comments sent from the server* @default false*/enabled : boolean;/*** Interval in milliseconds* @default 1000*/intervalMs ?: number;};/*** Maximum duration in milliseconds for the request before ending the stream* @default undefined*/maxDurationMs ?: number;/*** End the request immediately after data is sent* Only useful for serverless runtimes that do not support streaming responses* @default false*/emitAndEndImmediately ?: boolean;/*** Client-specific options - these will be sent to the client as part of the first message* @default {}*/client ?: {/*** Timeout and reconnect after inactivity in milliseconds* @default undefined*/reconnectAfterInactivityMs ?: number;};}