HTTPサブスクリプションリンク
このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →
httpSubscriptionLinkはServer-sent Events(SSE)をサブスクリプションに使用する終端リンクです。
SSEはリアルタイム通信に適した選択肢であり、WebSocketサーバーのセットアップよりも若干簡単です。
セットアップ
クライアント環境がEventSourceをサポートしていない場合、EventSourceポリフィルが必要です。React Native固有の手順については互換性セクションを参照してください。
httpSubscriptionLinkを使用するには、splitLinkを併用してSSEを明示的に指定する必要があります。
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の具体的な使用方法を説明しています。サブスクリプション全般の利用方法についてはサブスクリプションガイドを参照してください。
ヘッダーと認証/認可
Webアプリケーション
同一ドメイン
Webアプリケーションの場合、クライアントとサーバーが同じドメイン上にある限り、リクエストの一部としてクッキーが送信されます。
クロスドメイン
クライアントとサーバーが異なるドメインにある場合、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, // <---};},});
ポリフィルによるカスタムヘッダー
非Web環境向けに推奨
EventSourceをポリフィルし、eventSourceOptionsコールバックを使用してヘッダーを設定できます。
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',}),}),],});
アクティブ接続の設定更新
httpSubscriptionLinkはSSEをEventSource経由で活用し、ネットワーク障害や不正な応答コードなどのエラー発生時に接続を自動再試行します。ただしEventSourceは、eventSourceOptions()やurl()オプションを再実行して設定を更新する機能を提供せず、特に認証が期限切れになった場合に問題となります。
この制限に対処するには、httpSubscriptionLinkと併せてretryLinkを使用します。これにより、更新された認証情報を含む最新の設定で接続を再確立できます。
接続を再開するとEventSourceが最初から再作成されるため、以前に追跡されていたイベントはすべて失われることに注意してください。
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 }`,},};},}),],}),],});
接続パラメータ
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オプションによる非アクティブタイムアウトの設定をサポートしています。指定期間内に(pingメッセージを含む)メッセージが受信されない場合、接続は「接続中」とマークされ自動的に再接続を試みます。
タイムアウト設定はサーバーサイドで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,},},});
サーバーping設定
サーバーは定期的なpingメッセージを送信するように設定でき、接続を維持しタイムアウトによる切断を防ぎます。これは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、Streams API、およびAsyncIteratorを使用しますが、これらはReact Nativeでネイティブサポートされておらず、ポリフィルが必要です。
EventSourceをポリフィルする場合、XMLHttpRequest APIを使用するポリフィルよりも、React Nativeが公開するネットワークライブラリを活用するポリフィルの使用を推奨します。XMLHttpRequestを使用するEventSourceポリフィルライブラリは、アプリがバックグラウンドから復帰した後の再接続に失敗します。rn-eventsource-rebornパッケージの使用を検討してください。
Streams APIはweb-streams-polyfillパッケージでポリフィルできます。
AsyncIteratorは@azure/core-asynciterator-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を追加する場所で)プロジェクトにポリフィルを追加します:
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;
ポリフィルが追加されたら、セットアップセクションで説明されている通りに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;};}