メインコンテンツまでスキップ

クライアント接続の管理

バックエンドから、クライアントの接続・切断をリアルタイムに検知して応答します。

クライアント接続を使う理由

ユーザーがオンラインかどうかを知ることに依存する機能を作っているとします。たとえば、プレゼンス表示のあるチャットアプリ、アクティブ参加者を追跡する共同編集エディタ、接続中プレイヤーを表示するゲームロビーなどです。

接続追跡がない場合、ハートビートのポーリング(polling)や、独自の WebSocket ロジックを実装する必要があります。Squid の client connection handler を使えば、単一の関数にデコレータ(decorator)を付けるだけで、状態変化が起きた瞬間に反応できます。

// Backend: react to connection changes
@clientConnectionStateHandler()
async onConnectionChange(clientId: ClientId, state: ClientConnectionState): Promise<void> {
if (state === 'DISCONNECTED') {
await this.squid.collection('presence').doc(clientId).update({ status: 'offline' });
}
}

// Frontend: check connection status
const isConnected = squid.connectionDetails().connected;

ポーリング不要。カスタム WebSocket 管理も不要。必要なのはデコレータだけです。

概要

クライアント接続により、バックエンドはクライアントが Squid サーバーに接続した、切断した、または削除されたタイミングを検知できます。各クライアントには接続時に一意の clientId が割り当てられ、これを使ってプレゼンスの追跡、リソースのクリーンアップ、ワークフローのトリガーなどを行えます。

クライアント接続を使うべきとき

ユースケース推奨
ユーザーのオンライン/オフライン状態を追跡するクライアント接続
ユーザー離脱時にリソースをクリーンアップするクライアント接続
データベース変更に反応するTriggers を使用
スケジュール実行するSchedulers を使用

仕組み

  1. クライアントが Squid に接続し、一意の clientId が割り当てられる
  2. バックエンドの handler が state CONNECTED で呼び出される
  3. クライアントが切断すると(例: ブラウザタブを閉じる)、handler が DISCONNECTED で呼び出される
  4. 一定期間後、クライアントが再接続しない場合、Squid は clientId を削除し、handler を REMOVED で呼び出す
  5. 削除後にクライアントが再接続した場合、新しい clientId が発行される

クイックスタート

前提条件

  • squid init で初期化された Squid backend プロジェクト
  • @squidcloud/backend パッケージがNPMからインストールされていること

Step 1: 接続ハンドラーを作成する

サービスクラスに connection state handler を追加します。

Backend code
import { SquidService, clientConnectionStateHandler } from '@squidcloud/backend';
import { ClientConnectionState, ClientId } from '@squidcloud/client';

export class ExampleService extends SquidService {
@clientConnectionStateHandler()
async onConnectionChange(clientId: ClientId, state: ClientConnectionState): Promise<void> {
console.log(`Client ${clientId} is now ${state}`);
}
}

Step 2: バックエンドを起動またはデプロイする

ローカル開発では、Squid CLI を使ってバックエンドをローカルで実行します。

squid start

クラウドへデプロイする場合は、deploying your backend を参照してください。

Step 3: クライアントで接続状態を監視する

Client code
import { filter } from 'rxjs';

// Check if currently connected
const isConnected = squid.connectionDetails().connected;

// Get the current client's ID
const clientId = squid.connectionDetails().clientId;

// React to connection changes
squid
.connectionDetails()
.observeConnected()
.pipe(filter(Boolean))
.subscribe(() => {
console.log('Connected with client ID:', squid.connectionDetails().clientId);
});

コアコンセプト

接続状態(Connection states)

各クライアントは次の状態へ遷移します。

State意味
CONNECTEDクライアントがサーバーに接続した直後
DISCONNECTEDクライアントは切断したが、再接続に備えて Squid が clientId を保持している状態
REMOVEDクライアントが十分長く切断されていたため、Squid が clientId を破棄した状態。次回接続では新しい ID が割り当てられる。

Client ID(クライアント ID)

すべてのクライアントは接続時に一意の clientId を受け取ります。この ID は次の特性を持ちます。

  • フロントエンドでは squid.connectionDetails().clientId から参照可能
  • バックエンドでは this.context.clientId から参照可能(接続ハンドラーだけでなく任意のバックエンド関数内)
  • 短時間の切断に対しては安定(REMOVED になるまで同じ ID が保持される)
  • 認証プロバイダの userId とは別物。必要なら両者のマッピングが必要です。

@clientConnectionStateHandler デコレータ

デコレータが付いた関数は 2 つの引数を受け取ります。

パラメータ説明
clientIdClientId (string)状態が変化したクライアントの ID
connectionStateClientConnectionState'CONNECTED''DISCONNECTED''REMOVED' のいずれか

関数は void または Promise<void> を返せます。

フロントエンド接続 API

接続情報は squid.connectionDetails() から参照します。

プロパティ / メソッド戻り値の型説明
.connectedbooleanクライアントが現在接続中かどうか
.clientIdstringこの接続に割り当てられた一意のクライアント ID
.observeConnected()Observable<boolean>接続時に true、切断時に false を emit する
注記

Squid client は WebSocket 接続を遅延(lazy)で確立します。つまり、操作が必要になったときにのみサーバーへ接続します(例: .snapshots() でクエリを subscribe するなど)。.connected.observeConnected() を呼ぶだけでは接続は開始されません。状態確認の前に必ず接続されている必要がある場合は、まずデータを subscribe するか、接続をトリガーする操作を実行してください。

エラーハンドリング

ハンドラーのエラー

@clientConnectionStateHandler 関数がエラーを throw すると、サーバー側でログに記録されますが、クライアントの接続には影響しません。サイレントな失敗を避けるため、ハンドラー内でエラーを処理してください。

Backend code
@clientConnectionStateHandler()
async onConnectionChange(clientId: ClientId, state: ClientConnectionState): Promise<void> {
try {
await this.updatePresence(clientId, state);
} catch (error) {
console.error(`Failed to update presence for ${clientId}:`, error);
}
}

よくある問題

問題原因解決策
Handler が呼ばれないService が export されていない、または backend がデプロイされていないservice/index.ts で service を export し、squid deploy を実行する
clientId が予期せず変わる長時間の切断後に client が removed されたREMOVED 状態でクリーンアップし、CONNECTED で再マップする
observeConnected() が throw するclient が passive mode になっているSquid client が active(デフォルト)モードで初期化されていることを確認する

ベストプラクティス

  1. 接続時に clientIduserId にマップする。 clientId は接続スコープ、userId は認証スコープのため、クライアント接続時にマッピングを作成しておくと、接続からユーザーを引けます。
  2. DISCONNECTED だけでなく REMOVED でクリーンアップする。 DISCONNECTED のクライアントは同じ clientId で再接続する可能性があります。状態が REMOVED のときだけリソースを削除してください。
  3. ハンドラーのロジックは高速に保つ。 connection state handler は全クライアントの接続/切断ごとに実行されます。高コストな処理は避けるか、非同期で連鎖させてください。
  4. authentication を使ってユーザーを特定する。 clientId だけではユーザーが誰か分かりません。プレゼンス機能は認証と組み合わせて構築してください。

コード例

ユーザープレゼンスの追跡

この例では、presence コレクションで clientIduserId にマッピングすることで、どのユーザーがオンラインかを追跡します。

Frontend: 接続時にプレゼンスを登録

Client code
import { filter } from 'rxjs';

interface PresenceData {
userId: string;
status: 'online' | 'offline';
}

// When connected, insert a presence record linking clientId to userId
squid
.connectionDetails()
.observeConnected()
.pipe(filter(Boolean))
.subscribe(() => {
const clientId = squid.connectionDetails().clientId;
squid
.collection<PresenceData>('presence')
.doc(clientId)
.insert({ userId: currentUserId, status: 'online' });
});

Backend: 切断/削除時にプレゼンスを更新

Backend code
import { SquidService, clientConnectionStateHandler } from '@squidcloud/backend';
import { ClientConnectionState, ClientId } from '@squidcloud/client';

export class PresenceService extends SquidService {
@clientConnectionStateHandler()
async handlePresenceChange(clientId: ClientId, state: ClientConnectionState): Promise<void> {
const presenceRef = this.squid.collection('presence').doc(clientId);

if (state === 'DISCONNECTED') {
await presenceRef.update({ status: 'offline' });
} else if (state === 'REMOVED') {
await presenceRef.delete();
}
}
}

Frontend: オンラインユーザーをクエリする

Client code
// Get all online users
const onlineUsers = await squid
.collection<PresenceData>('presence')
.query()
.eq('status', 'online')
.dereference()
.snapshot();

console.log(
'Online users:',
onlineUsers.map((u) => u.userId)
);

クエリについての詳細は Queries を参照してください。

関連項目

  • Authentication - バックエンドでユーザーを特定する
  • Triggers - データベース変更に反応する
  • Database - コレクションのクエリと更新を行う