HTTP-prenumerationslänk
Denna sida har översatts av PageTurner AI (beta). Inte officiellt godkänd av projektet. Hittade du ett fel? Rapportera problem →
httpSubscriptionLink är en avslutande länk som använder Server-sent Events (SSE) för prenumerationer.
SSE är ett bra alternativ för realtidskommunikation eftersom det är lite enklare än att sätta upp en WebSockets-server.
Installation
Om din klientmiljö inte stöder EventSource behöver du en EventSource-polyfill. För React Native-specifika instruktioner, se kompatibilitetsavsnittet.
För att använda httpSubscriptionLink måste du använda en splitLink för att explicit ange att vi vill använda SSE för prenumerationer.
client/index.tstsimport type { TRPCLink } from '@trpc/client';import {httpBatchLink,httpSubscriptionLink,loggerLink,splitLink,} from '@trpc/client';const trpcClient = 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 type { TRPCLink } from '@trpc/client';import {httpBatchLink,httpSubscriptionLink,loggerLink,splitLink,} from '@trpc/client';const trpcClient = 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`,}),}),],});
Det här dokumentet beskriver de specifika detaljerna för httpSubscriptionLink. För generell användning av prenumerationer, se vår prenumerationsguide.
Headers samt autentisering/auktorisering
Webbapplikationer
Samma domän
I en webbapplikation skickas kakor automatiskt som del av förfrågorna om klienten finns på samma domän som servern.
Cross-domain
Om klienten och servern inte är på samma domän kan du använda withCredentials: true (läs mer på MDN).
Exempel:
tsx// [...]httpSubscriptionLink({url: 'https://example.com/api/trpc',eventSourceOptions() {return {withCredentials: true, // <---};},});
tsx// [...]httpSubscriptionLink({url: 'https://example.com/api/trpc',eventSourceOptions() {return {withCredentials: true, // <---};},});
Anpassade headers via ponyfill
Rekommenderas för icke-webbmiljöer
Du kan använda en ponyfill för EventSource och använda eventSourceOptions-callbacken för att lägga till headers.
tsximport {createTRPCClient,httpBatchLink,httpSubscriptionLink,splitLink,} from '@trpc/client';import { EventSourcePolyfill } from 'event-source-polyfill';import type { AppRouter } from '../server/index.js';// Initialize the tRPC clientconst trpc = 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 }) => {// ^ Includes the operation that's being executed// you can use this to generate a signature for the operationconst signature = await getSignature(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/index.js';// Initialize the tRPC clientconst trpc = 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 }) => {// ^ Includes the operation that's being executed// you can use this to generate a signature for the operationconst signature = await getSignature(op);return {headers: {authorization: 'Bearer supersecret','x-signature': signature,},};},}),false: httpBatchLink({url: 'http://localhost:3000',}),}),],});
Uppdatera konfiguration på aktiv anslutning
httpSubscriptionLink använder SSE via EventSource, vilket säkerställer att anslutningar som stöter på fel som nätverksproblem eller felaktiga statuskoder automatiskt försöker återansluta. Dock tillåter EventSource inte att eventSourceOptions() eller url() körs igen för att uppdatera konfigurationen, vilket är särskilt viktigt när autentisering har gått ut sedan senaste anslutningen.
För att lösa denna begränsning kan du använda en retryLink tillsammans med httpSubscriptionLink. Detta säkerställer att anslutningen återupprättas med senaste konfigurationen, inklusive uppdaterade autentiseringsuppgifter.
Observera att omstart av anslutningen kommer att återskapa EventSource från grunden, vilket innebär att tidigare spårade händelser går förlorade.
tsximport {createTRPCClient,httpBatchLink,httpSubscriptionLink,retryLink,splitLink,} from '@trpc/client';import {EventSourcePolyfill,EventSourcePolyfillInit,} from 'event-source-polyfill';import type { AppRouter } from '../server/index.js';// Initialize the tRPC clientconst trpc = 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 splitLinkconst code = 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...return getAuthenticatedUri();},// ponyfill EventSourceEventSource: EventSourcePolyfill,eventSourceOptions: async () => {// ...or maybe renew an access tokenconst token = await auth.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/index.js';// Initialize the tRPC clientconst trpc = 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 splitLinkconst code = 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...return getAuthenticatedUri();},// ponyfill EventSourceEventSource: EventSourcePolyfill,eventSourceOptions: async () => {// ...or maybe renew an access tokenconst token = await auth.getOrRenewToken();return {headers: {authorization: `Bearer ${token}`,},};},}),],}),],});
Anslutningsparametrar
För att autentisera med EventSource kan du definiera connectionParams i httpSubscriptionLink. Dessa skickas som del av URL:en, vilket är anledningen till att andra metoder föredras.
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/index.js';// Initialize the tRPC clientconst trpc = 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/index.js';// Initialize the tRPC clientconst trpc = 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',}),}),],});
Timeout-konfiguration
httpSubscriptionLink stöder konfiguration av timeout vid inaktivitet via alternativet reconnectAfterInactivityMs. Om inga meddelanden (inklusive ping-meddelanden) tas emot inom den angivna tidsperioden markeras anslutningen som "ansluter" och kommer automatiskt försöka återansluta.
Timeout-konfigurationen sätts på serversidan vid initiering av tRPC:
server/trpc.tstsimport { initTRPC } from '@trpc/server';export const t = initTRPC.create({sse: {client: {reconnectAfterInactivityMs: 3_000,},},});
server/trpc.tstsimport { initTRPC } from '@trpc/server';export const t = initTRPC.create({sse: {client: {reconnectAfterInactivityMs: 3_000,},},});
Server-ping-konfiguration
Servern kan konfigureras att skicka periodiska ping-meddelanden för att hålla anslutningen vid liv och undvika timeout-avbrott. Detta är särskilt användbart i kombination med reconnectAfterInactivityMs.
server/trpc.tstsimport { initTRPC } from '@trpc/server';export const t = initTRPC.create({sse: {// Maximum duration of a single SSE connection in milliseconds// maxDurationMs: 60_00,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 const t = initTRPC.create({sse: {// Maximum duration of a single SSE connection in milliseconds// maxDurationMs: 60_00,ping: {// Enable periodic ping messages to keep connection aliveenabled: true,// Send ping message every 2sintervalMs: 2_000,},// client: {// reconnectAfterInactivityMs: 3_000// }},});
Kompatibilitet (React Native)
httpSubscriptionLink använder EventSource-API:et, Streams API och AsyncIterators. Dessa stöds inte inbyggt i React Native och måste därför polyfillas.
För att polyfilla EventSource rekommenderar vi att använda en polyfill som utnyttjar React Native:s nätverksbibliotek istället för en polyfill som använder XMLHttpRequest-API:et. Bibliotek som polyfillar EventSource med XMLHttpRequest lyckas inte återansluta efter att appen har varit i bakgrunden. Överväg att använda paketet rn-eventsource-reborn.
Streams API kan polyfillas med hjälp av paketet web-streams-polyfill.
AsyncIterators kan polyfillas med hjälp av paketet @azure/core-asynciterator-polyfill.
Installation
Installera de nödvändiga polyfills:
- 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
Lägg till polyfills i ditt projekt innan länken används (t.ex. där du lägger till din 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;
När polyfills är tillagda kan du fortsätta att konfigurera httpSubscriptionLink enligt beskrivningen i avsnittet om installation.
Alternativ för httpSubscriptionLink
tstype HTTPSubscriptionLinkOptions<TRoot extends AnyClientTypes,TEventSource extends EventSourceLike.AnyConstructor = typeof EventSource,> = {/*** 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>>);};
tstype HTTPSubscriptionLinkOptions<TRoot extends AnyClientTypes,TEventSource extends EventSourceLike.AnyConstructor = typeof EventSource,> = {/*** 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-alternativ på servern
tsexport interface SSEStreamProducerOptions<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 interface SSEStreamProducerOptions<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;};}