Transmit
Transmitは、AdonisJS向けに作られたネイティブな意見のあるServer-Sent-Event(SSE)モジュールです。通知、ライブチャットメッセージ、またはその他のリアルタイムデータなど、クライアントへのリアルタイムの更新を送信するためのシンプルで効率的な方法です。
データの送信はサーバーからクライアントへのみ行われます。クライアントからサーバーへの通信にはフォームまたはフェッチリクエストを使用する必要があります。
インストール
次のコマンドを使用してパッケージをインストールおよび設定します:
node ace add @adonisjs/transmit
-
検出されたパッケージマネージャを使用して
@adonisjs/transmitパッケージをインストールします。 -
adonisrc.tsファイル内で@adonisjs/transmit/transmit_providerサービスプロバイダを登録します。 -
configディレクトリ内に新しいtransmit.tsファイルを作成します。
また、クライアント側でイベントを受信するためにTransmitクライアントパッケージもインストールする必要があります。
npm install @adonisjs/transmit-client
設定
Transmitパッケージの設定はconfig/transmit.tsファイルに保存されます。
参照:Config stub
import { defineConfig } from '@adonisjs/transmit'
export default defineConfig({
pingInterval: false,
transport: null,
})
-
pingInterval
-
クライアントにpingメッセージを送信するために使用される間隔です。値はミリ秒または文字列の
Duration形式(例:10s)で指定します。pingメッセージを無効にするにはfalseに設定します。 -
transport
-
Transmitは複数のサーバーやインスタンス間でイベントを同期できます。この機能を有効にするには、必要なトランスポートレイヤー(現在は
redisのみサポート)を参照するように設定します。同期を無効にするにはnullに設定します。import env from '#start/env'import { defineConfig } from '@adonisjs/transmit'import { redis } from '@adonisjs/transmit/transports'export default defineConfig({transport: {driver: redis({host: env.get('REDIS_HOST'),port: env.get('REDIS_PORT'),password: env.get('REDIS_PASSWORD'),keyPrefix: 'transmit',})}})redisトランスポートを使用する場合は、ioredisがインストールされていることを確認してください。
Register Routes
クライアントがサーバーに接続できるようにするためには、transmitルートを登録する必要があります。ルートは手動で登録されます。
import transmit from '@adonisjs/transmit/services/main'
transmit.registerRoutes()
各ルートを手動でコントローラーにバインドして手動で登録することもできます。
const EventStreamController = () => import('@adonisjs/transmit/controllers/event_stream_controller')
const SubscribeController = () => import('@adonisjs/transmit/controllers/subscribe_controller')
const UnsubscribeController = () => import('@adonisjs/transmit/controllers/unsubscribe_controller')
router.get('/__transmit/events', [EventStreamController])
router.post('/__transmit/subscribe', [SubscribeController])
router.post('/__transmit/unsubscribe', [UnsubscribeController])
ルート定義を変更して、たとえばRate Limiterや認証ミドルウェアを使用して一部のtransmitルートの乱用を防ぎたい場合は、ルート定義を変更するか、transmit.registerRoutesメソッドにコールバックを渡すことができます。
import transmit from '@adonisjs/transmit/services/main'
transmit.registerRoutes((route) => {
// クライアントを登録するために認証されていることを確認します
if (route.getPattern() === '__transmit/events') {
route.middleware(middleware.auth())
return
}
// 他のtransmitルートにスロットルミドルウェアを追加できます
route.use(throttle)
})
チャンネル
チャンネルはイベントをグループ化するために使用されます。たとえば、通知用のチャンネル、チャットメッセージ用の別のチャンネルなどがあります。クライアントがチャンネルに登録すると、チャンネルが動的に作成されます。
チャンネル名
チャンネル名はチャンネルを識別するために使用されます。大文字と小文字が区別され、文字列である必要があります。チャンネル名には/以外の特殊文字やスペースは使用できません。以下は有効なチャンネル名の例です:
import transmit from '@adonisjs/transmit/services/main'
transmit.broadcast('global', { message: 'Hello' })
transmit.broadcast('chats/1/messages', { message: 'Hello' })
transmit.broadcast('users/1', { message: 'Hello' })
チャンネル名はAdonisJSのルートと同じ構文を使用しますが、それらとは関係ありません。同じキーでHTTPルートとチャンネルを自由に定義できます。
チャンネルの認証
authorizeメソッドを使用して、チャンネルへの接続を承認または拒否できます。このメソッドはチャンネル名とHttpContextを受け取り、真偽値を返す必要があります。
import transmit from '@adonisjs/transmit/services/main'
import Chat from '#models/chat'
import type { HttpContext } from '@adonisjs/core/http'
transmit.authorize<{ id: string }>('users/:id', (ctx: HttpContext, { id }) => {
return ctx.auth.user?.id === +id
})
transmit.authorize<{ id: string }>('chats/:id/messages', async (ctx: HttpContext, { id }) => {
const chat = await Chat.findOrFail(+id)
return ctx.bouncer.allows('accessChat', chat)
})
イベントのブロードキャスト
broadcastメソッドを使用して、チャンネルにイベントをブロードキャストできます。このメソッドはチャンネル名と送信するデータを受け取ります。
import transmit from '@adonisjs/transmit/services/main'
transmit.broadcast('global', { message: 'Hello' })
また、broadcastExceptメソッドを使用して、特定のUIDを除くすべてのチャンネルにイベントをブロードキャストすることもできます。このメソッドはチャンネル名、送信するデータ、および無視するUIDを受け取ります。
transmit.broadcastExcept('global', { message: 'Hello' }, 'uid-of-sender')
複数のサーバーやインスタンス間での同期
デフォルトでは、イベントのブロードキャストはHTTPリクエストのコンテキスト内でのみ動作します。ただし、設定でtransportを登録することで、バックグラウンドからイベントをブロードキャストすることもできます。
トランスポートレイヤーは、複数のサーバーやインスタンス間でイベントを同期する責任を持ちます。これは、ブロードキャストされたイベント、サブスクリプション、およびアンサブスクリプションなどのイベントを、接続されているすべてのサーバーやインスタンスに対してMessage Busを使用してブロードキャストすることによって機能します。
クライアント接続に対して責任を持つサーバーまたはインスタンスは、イベントを受信し、クライアントにブロードキャストします。
Transmitクライアント
@adonisjs/transmit-clientパッケージを使用して、クライアント側でイベントを受信できます。このパッケージはTransmitクラスを提供します。クライアントはデフォルトでEventSource APIを使用してサーバーに接続します。
import { Transmit } from '@adonisjs/transmit-client'
export const transmit = new Transmit({
baseUrl: window.location.origin
})
Transmitクラスのインスタンスは1つだけ作成し、アプリケーション全体で再利用するべきです。
Transmitインスタンスの設定
Transmitクラスは、次のプロパティを持つオブジェクトを受け入れます:
-
baseUrl
-
サーバーのベースURLです。URLにはプロトコル(httpまたはhttps)とドメイン名を含める必要があります。
-
uidGenerator
-
クライアントのための一意の識別子を生成する関数です。関数は文字列を返す必要があります。デフォルトでは
crypto.randomUUIDです。 -
eventSourceFactory
-
新しい
EventSourceインスタンスを作成する関数です。デフォルトではWebAPIのEventSourceを使用します。EventSourceAPIをサポートしていないNode.js、React Native、または他の環境でクライアントを使用する場合は、カスタムの実装を提供する必要があります。 -
eventTargetFactory
-
新しい
EventTargetインスタンスを作成する関数です。デフォルトではWebAPIのEventTargetを使用します。EventTargetAPIをサポートしていないNode.js、React Native、または他の環境でクライアントを使用する場合は、カスタムの実装を提供する必要があります。EventTargetAPIを無効にするにはnullを返します。 -
httpClientFactory
-
新しい
HttpClientインスタンスを作成する関数です。主にテスト目的で使用されます。 -
beforeSubscribe
-
チャンネルに登録する前に呼び出される関数です。チャンネル名とサーバーに送信される
Requestオブジェクトが渡されます。この関数を使用してカスタムヘッダーを追加したり、リクエストオブジェクトを変更したりするために使用します。 -
beforeUnsubscribe
-
チャンネルから登録解除する前に呼び出される関数です。チャンネル名とサーバーに送信される
Requestオブジェクトが渡されます。この関数を使用してカスタムヘッダーを追加したり、リクエストオブジェクトを変更したりするために使用します。 -
maxReconnectAttempts
-
再接続試行の最大回数です。デフォルトは
5です。 -
onReconnectAttempt
-
各再接続試行の前に呼び出される関数で、これまでに行われた試行回数が渡されます。カスタムロジックを追加するためにこの関数を使用します。
-
onReconnectFailed
-
再接続試行が失敗したときに呼び出される関数です。カスタムロジックを追加するためにこの関数を使用します。
-
onSubscribeFailed
-
サブスクリプションが失敗したときに呼び出される関数です。
Responseオブジェクトが渡されます。カスタムロジックを追加するためにこの関数を使用します。 -
onSubscription
-
サブスクリプションが成功したときに呼び出される関数です。チャンネル名が渡されます。カスタムロジックを追加するためにこの関数を使用します。
-
onUnsubscription
-
アンサブスクリプションが成功したときに呼び出される関数です。チャンネル名が渡されます。カスタムロジックを追加するためにこの関数を使用します。
サブスクリプションの作成
subscriptionメソッドを使用して、チャンネルに対するサブスクリプションを作成できます。このメソッドはチャンネル名を受け取ります。
const subscription = transmit.subscription('chats/1/messages')
await subscription.create()
createメソッドはサブスクリプションをサーバーに登録します。awaitまたはvoidできるプロミスを返します。
createメソッドを呼び出さない場合、サブスクリプションはサーバーに登録されず、イベントを受信しません。
イベントのリスニング
onMessageメソッドを使用して、サブスクリプションでイベントをリスンできます。このメソッドはコールバック関数を受け取ります。異なるコールバックを追加するために、onMessageメソッドを複数回呼び出すことができます。
subscription.onMessage((data) => {
console.log(data)
})
また、onMessageOnceメソッドを使用して、コールバック関数を受け取ることで、チャンネルを一度だけリスンすることもできます。
subscription.onMessageOnce(() => {
console.log('一度だけ呼び出されます')
})
イベントのリスニングを停止する
onMessageおよびonMessageOnceメソッドは、特定のコールバックのリスニングを停止するために呼び出すことができる関数を返します。
const stopListening = subscription.onMessage((data) => {
console.log(data)
})
// リスニングを停止する
stopListening()
サブスクリプションの削除
deleteメソッドを使用して、サブスクリプションを削除できます。このメソッドはawaitまたはvoidできるプロミスを返します。このメソッドはサーバー上でサブスクリプションの登録を解除します。
await subscription.delete()
GZipの干渉を回避する
@adonisjs/transmitを使用するアプリケーションをデプロイする際には、GZip圧縮がSSE(Server-Sent Events)で使用されるtext/event-streamコンテンツタイプに干渉しないようにすることが重要です。text/event-streamに適用される圧縮は、接続の問題を引き起こし、頻繁な切断やSSEの失敗を引き起こす可能性があります。
もしデプロイメントがリバースプロキシ(TraefikやNginxなど)やその他のミドルウェアを使用している場合、text/event-streamコンテンツタイプに対して圧縮が無効になっていることを確認してください。
Traefikのための設定例
traefik.http.middlewares.gzip.compress=true
traefik.http.middlewares.gzip.compress.excludedcontenttypes=text/event-stream
traefik.http.routers.my-router.middlewares=gzip