クライアント接続の管理
バックエンドから、クライアントの接続・切断をリアルタイムで検知して対応します。
クライアント接続を使う理由
ユーザーがオンラインかどうかを把握することに依存する機能を作っているとします。たとえば、プレゼンスインジケーターを表示するチャットアプリ、アクティブな参加者を追跡する共同編集エディタ、接続中プレイヤーを表示するゲームロビーなどです。
接続追跡がない場合、ハートビートのポーリングや独自の WebSocket ロジックを実装する必要があります。Squid の client connection handler を使えば、単一の関数にデコレーターを付けるだけで、状態変化が起きた瞬間に反応できます。
// 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 を使用 |
仕組み
- クライアントが Squid に接続し、一意の
clientIdが割り当てられる - バックエンドの handler が state
CONNECTEDで呼び出される - クライアントが切断すると(例: ブラウザタブを閉じる)、handler が
DISCONNECTEDで呼び出される - 一定期間後、クライアントが再接続しなければ、Squid は
clientIdを削除し、handler をREMOVEDで呼び出す - 削除後にクライアントが再接続した場合、新しい
clientIdが付与される
クイックスタート
前提条件
squid init-backendで初期化された Squid backend project@squidcloud/backendパッケージがインストールされていること
ステップ 1: 接続 handler を作成する
サービスクラスに connection state handler を追加します。
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}`);
}
}
ステップ 2: バックエンドを起動またはデプロイする
ローカル開発では、Squid CLI を使ってバックエンドをローカル実行します。
squid start
クラウドにデプロイするには、deploying your backend を参照してください。
ステップ 3: クライアント側で接続状態を確認する
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);
});
コアコンセプト
接続状態
各クライアントは次の状態を遷移します。
| State | 意味 |
|---|---|
CONNECTED | クライアントがサーバーに接続した直後 |
DISCONNECTED | クライアントが切断したが、再接続に備えて Squid が clientId を保持している |
REMOVED | クライアントが長時間切断されていたため、Squid が clientId を破棄した。次回接続では新しい ID が付与される。 |
Client ID
すべてのクライアントは接続時に一意の clientId を受け取ります。この ID は次の特性を持ちます。
- フロントエンドでは
squid.connectionDetails().clientIdで利用可能 - バックエンドでは
this.context.clientIdで利用可能(connection handler に限らず任意の backend function で) - 短時間の切断を挟んでも安定(
REMOVEDされるまで同じ ID が保持される) - 認証プロバイダーの
userIdとは別物。必要に応じて相互にマッピングする必要があります。
@clientConnectionStateHandler デコレーター
デコレーターを付けた関数は 2 つの引数を受け取ります。
| Parameter | Type | 説明 |
|---|---|---|
clientId | ClientId (string) | 状態が変化したクライアントの ID |
connectionState | ClientConnectionState | 'CONNECTED'、'DISCONNECTED'、'REMOVED' のいずれか |
関数は void または Promise<void> を返せます。
フロントエンド接続 API
接続情報には squid.connectionDetails() でアクセスします。
| Property / Method | Return Type | 説明 |
|---|---|---|
.connected | boolean | クライアントが現在接続中かどうか |
.clientId | string | この接続に割り当てられた一意の client ID |
.observeConnected() | Observable<boolean> | 接続時に true、切断時に false を emit します |
Squid client は WebSocket 接続を遅延(lazy)で確立します。つまり、操作が必要になったとき(例: .snapshots() でクエリ購読する等)にだけサーバーへ接続します。.connected や .observeConnected() を呼ぶだけでは接続は開始されません。状態確認前に接続済みであることを保証したい場合は、まずデータの購読や、接続をトリガーする操作を実行してください。
エラーハンドリング
Handler のエラー
@clientConnectionStateHandler 関数がエラーを throw すると、サーバー側にログは残りますがクライアントの接続には影響しません。サイレントな失敗を避けるため、handler 内でエラーを処理してください。
@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 されていない、または未デプロイ | service/index.ts で service を export し、squid deploy を実行する |
clientId が想定外に変わる | 長時間切断の後に client が removed された | REMOVED でクリーンアップし、CONNECTED で再マッピングする |
observeConnected() が throw する | Client が passive mode になっている | Squid client が active(デフォルト) mode で初期化されていることを確認する |
ベストプラクティス
- 接続時に
clientIdをuserIdにマッピングする。clientIdは接続スコープ、userIdは認証スコープなので、クライアントが接続した時点でマッピングを挿入しておくと、接続からユーザーを引けます。 DISCONNECTEDだけでなくREMOVEDでクリーンアップする。DISCONNECTEDのクライアントは同じclientIdのまま再接続する可能性があります。リソース削除は state がREMOVEDのときだけにしてください。- handler の処理を高速に保つ。 接続状態 handler は全クライアントの接続/切断ごとに実行されます。高コストな処理は避けるか、非同期に連鎖させてください。
- ユーザー識別には authentication を使う。
clientIdだけではユーザー本人を特定できません。認証と組み合わせてプレゼンス機能を構築してください。
コード例
ユーザープレゼンスの追跡
この例では、presence collection で clientId を userId にマッピングすることで、どのユーザーがオンラインかを追跡します。
Frontend: 接続時にプレゼンスを登録
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: 切断/削除時にプレゼンスを更新
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: オンラインユーザーをクエリする
// 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 - collection のクエリと変更を行う