Enlace HTTP para Suscripciones
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
httpSubscriptionLink es un enlace terminal que utiliza Eventos enviados por el servidor (SSE) para suscripciones.
SSE es una buena opción para aplicaciones en tiempo real ya que es más sencillo de configurar que un servidor WebSockets.
Configuración
Si el entorno de tu cliente no soporta EventSource, necesitarás un polyfill de EventSource. Para instrucciones específicas de React Native, consulta la sección de compatibilidad.
Para usar httpSubscriptionLink, debes emplear un splitLink para indicar explícitamente que queremos usar SSE para suscripciones.
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`,}),}),],});
Este documento explica los detalles específicos de usar httpSubscriptionLink. Para el uso general de suscripciones, consulta nuestra guía de suscripciones.
Cabeceras y autorización/autenticación
Aplicaciones web
Mismo dominio
En aplicaciones web, las cookies se envían automáticamente si tu cliente está en el mismo dominio que el servidor.
Dominios cruzados
Si el cliente y servidor están en dominios diferentes, puedes usar withCredentials: true (más información en MDN).
Ejemplo:
tsx// [...]httpSubscriptionLink ({url : 'https://example.com/api/trpc',eventSourceOptions () {return {withCredentials : true, // <---};},});
tsx// [...]httpSubscriptionLink ({url : 'https://example.com/api/trpc',eventSourceOptions () {return {withCredentials : true, // <---};},});
Cabeceras personalizadas mediante ponyfill
Recomendado para entornos no web
Puedes implementar un ponyfill de EventSource y usar el callback eventSourceOptions para establecer cabeceras.
tsximport {createTRPCClient ,httpBatchLink ,httpSubscriptionLink ,splitLink ,} from '@trpc/client';import {EventSourcePolyfill } from 'event-source-polyfill';import type {AppRouter } from './server';// Initialize the tRPC clientconsttrpc =createTRPCClient <AppRouter >({links : [splitLink ({condition : (op ) =>op .type === 'subscription',true :httpSubscriptionLink ({url : 'http://localhost:3000',// ponyfill EventSourceEventSource :EventSourcePolyfill ,// options to pass to the EventSourcePolyfill constructoreventSourceOptions : async ({op }) => {// you can use this to generate a signature for the operationconstsignature = awaitgetSignature (op );return {headers : {authorization : 'Bearer supersecret','x-signature':signature ,},};},}),false :httpBatchLink ({url : 'http://localhost:3000',}),}),],});
tsximport {createTRPCClient ,httpBatchLink ,httpSubscriptionLink ,splitLink ,} from '@trpc/client';import {EventSourcePolyfill } from 'event-source-polyfill';import type {AppRouter } from './server';// Initialize the tRPC clientconsttrpc =createTRPCClient <AppRouter >({links : [splitLink ({condition : (op ) =>op .type === 'subscription',true :httpSubscriptionLink ({url : 'http://localhost:3000',// ponyfill EventSourceEventSource :EventSourcePolyfill ,// options to pass to the EventSourcePolyfill constructoreventSourceOptions : async ({op }) => {// you can use this to generate a signature for the operationconstsignature = awaitgetSignature (op );return {headers : {authorization : 'Bearer supersecret','x-signature':signature ,},};},}),false :httpBatchLink ({url : 'http://localhost:3000',}),}),],});
Actualizar configuración en conexiones activas
httpSubscriptionLink utiliza SSE mediante EventSource, que reintenta automáticamente conexiones con errores como fallos de red o códigos de respuesta incorrectos. Sin embargo, EventSource no permite volver a ejecutar las opciones eventSourceOptions() o url() para actualizar la configuración, algo crucial cuando la autenticación ha expirado durante una conexión activa.
Para solucionar esto, puedes combinar un retryLink con httpSubscriptionLink. Este enfoque garantiza que la conexión se restablezca con la configuración más reciente, incluyendo credenciales actualizadas.
Ten en cuenta que reiniciar la conexión recreará EventSource desde cero, lo que significa que se perderán todos los eventos rastreados previamente.
tsximport {createTRPCClient ,httpBatchLink ,httpSubscriptionLink ,retryLink ,splitLink ,} from '@trpc/client';import {EventSourcePolyfill ,EventSourcePolyfillInit ,} from 'event-source-polyfill';import type {AppRouter } from './server';// Initialize the tRPC clientconsttrpc =createTRPCClient <AppRouter >({links : [splitLink ({condition : (op ) =>op .type === 'subscription',false :httpBatchLink ({url : 'http://localhost:3000',}),true : [retryLink ({retry : (opts ) => {opts .op .type ;// ^? will always be 'subscription' since we're in a splitLinkconstcode =opts .error .data ?.code ;if (!code ) {// This shouldn't happen as our httpSubscriptionLink will automatically retry within when there's a non-parsable responseconsole .error ('No error code found, retrying',opts );return true;}if (code === 'UNAUTHORIZED' ||code === 'FORBIDDEN') {console .log ('Retrying due to 401/403 error');return true;}return false;},}),httpSubscriptionLink ({url : async () => {// calculate the latest URL if needed...returngetAuthenticatedUri ();},// ponyfill EventSourceEventSource :EventSourcePolyfill ,eventSourceOptions : async () => {// ...or maybe renew an access tokenconsttoken = awaitauth .getOrRenewToken ();return {headers : {authorization : `Bearer ${token }`,},};},}),],}),],});
tsximport {createTRPCClient ,httpBatchLink ,httpSubscriptionLink ,retryLink ,splitLink ,} from '@trpc/client';import {EventSourcePolyfill ,EventSourcePolyfillInit ,} from 'event-source-polyfill';import type {AppRouter } from './server';// Initialize the tRPC clientconsttrpc =createTRPCClient <AppRouter >({links : [splitLink ({condition : (op ) =>op .type === 'subscription',false :httpBatchLink ({url : 'http://localhost:3000',}),true : [retryLink ({retry : (opts ) => {opts .op .type ;// ^? will always be 'subscription' since we're in a splitLinkconstcode =opts .error .data ?.code ;if (!code ) {// This shouldn't happen as our httpSubscriptionLink will automatically retry within when there's a non-parsable responseconsole .error ('No error code found, retrying',opts );return true;}if (code === 'UNAUTHORIZED' ||code === 'FORBIDDEN') {console .log ('Retrying due to 401/403 error');return true;}return false;},}),httpSubscriptionLink ({url : async () => {// calculate the latest URL if needed...returngetAuthenticatedUri ();},// ponyfill EventSourceEventSource :EventSourcePolyfill ,eventSourceOptions : async () => {// ...or maybe renew an access tokenconsttoken = awaitauth .getOrRenewToken ();return {headers : {authorization : `Bearer ${token }`,},};},}),],}),],});
Parámetros de conexión
Para autenticarte con EventSource, puedes definir connectionParams en httpSubscriptionLink. Estos parámetros se enviarán como parte de la URL, razón por la cual se prefieren otros métodos.
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',}),}),],});
Configuración de tiempo de espera
httpSubscriptionLink permite configurar un tiempo de espera por inactividad mediante reconnectAfterInactivityMs. Si no se reciben mensajes (incluyendo pings) durante este periodo, la conexión se marcará como "conectando" e intentará reconectarse automáticamente.
Esta configuración se establece en el servidor al inicializar 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,},},});
Configuración de pings del servidor
El servidor puede configurarse para enviar pings periódicos que mantengan viva la conexión y prevengan desconexiones por tiempo de espera. Esto es especialmente útil combinado con la opción 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// }},});
Compatibilidad (React Native)
El httpSubscriptionLink utiliza la API EventSource, la API Streams y AsyncIterators, las cuales no son compatibles de forma nativa con React Native y requerirán polyfills.
Para implementar un ponyfill de EventSource, recomendamos usar un polyfill que aproveche la biblioteca de red de React Native, en lugar de soluciones basadas en la API XMLHttpRequest. Las bibliotecas que polyfillan EventSource mediante XMLHttpRequest no logran reconectarse después de que la aplicación regresa de segundo plano. Considera usar el paquete rn-eventsource-reborn.
La API Streams puede implementarse como polyfill usando el paquete web-streams-polyfill.
Los AsyncIterators pueden polyfillarse usando el paquete @azure/core-asynciterator-polyfill.
Instalación
Instala los polyfills requeridos:
- 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
Agrega los polyfills a tu proyecto antes de usar el link (ej. donde agregas tu TRPCReact.Provider):
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;
Una vez agregados los polyfills, puedes continuar configurando el httpSubscriptionLink como se describe en la sección de configuración.
Opciones de 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 >>);};
Opciones de SSE en el servidor
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;};}