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

クライアント接続の管理

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

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

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

接続追跡がない場合、ハートビートのポーリングや独自の 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 を使用

仕組み

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

クイックスタート

前提条件

  • squid init-backend で初期化された Squid backend project
  • @squidcloud/backend パッケージがインストールされていること

ステップ 1: 接続 handler を作成する

サービスクラスに 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}`);
}
}

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

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

squid start

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

ステップ 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);
});

コアコンセプト

接続状態

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

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 つの引数を受け取ります。

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

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

フロントエンド接続 API

接続情報には squid.connectionDetails() でアクセスします。

Property / MethodReturn Type説明
.connectedbooleanクライアントが現在接続中かどうか
.clientIdstringこの接続に割り当てられた一意の client ID
.observeConnected()Observable<boolean>接続時に true、切断時に false を emit します
注記

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

エラーハンドリング

Handler のエラー

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

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 されていない、または未デプロイservice/index.ts で service を export し、squid deploy を実行する
clientId が想定外に変わる長時間切断の後に client が removed されたREMOVED でクリーンアップし、CONNECTED で再マッピングする
observeConnected() が throw するClient が passive mode になっているSquid client が active(デフォルト) mode で初期化されていることを確認する

ベストプラクティス

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

コード例

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

この例では、presence collection で 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 - collection のクエリと変更を行う