> For the complete documentation index, see [llms.txt](/llms.txt)

# GS2-Gateway

WebSocket 通知機能




GS2-Gateway はゲームクライアントとサーバーとの間で WebSocket による常時接続を維持し、サーバーから任意のタイミングで通知を送るための機能を提供します。

通常のゲームサーバーとの通信はクライアントからのリクエストに対してサーバーが応答する形式ですが、GS2-Gateway を使うことでサーバー起点の通知 (メッセージ着信・フレンド申請・ギルドからの招集など) をリアルタイムにクライアントへ届けることができます。

```mermaid
sequenceDiagram
  participant Client
  participant Gateway as GS2-Gateway
  participant Service as 各マイクロサービス

  Client->>Gateway: WebSocket 接続
  Client->>Gateway: SetUserId(認証)
  Service->>Gateway: SendNotification
  Gateway->>Client: 通知ペイロード配信
```

## 主な機能

### WebSocket 常時接続

GS2-Gateway は WebSocket プロトコルでクライアントからの接続を受け付け、ユーザーごとに接続情報を保持します。
他のマイクロサービスから「このユーザーに通知を送りたい」というリクエストが届くと、接続中のクライアントに対してペイロードを配信します。

主要な連携先は以下です。

- GS2-Inbox: 新規メッセージの着信通知
- GS2-Friend: フレンド申請・承認の通知
- GS2-Guild: ギルド参加申請、ギルド内の状況変化通知
- GS2-Matchmaking: マッチング完了通知
- GS2-Distributor: トランザクション自動実行のための通知
- GS2-JobQueue: 新規ジョブが積まれた際の通知

### 同時接続制御

ネームスペース内のユーザーは原則として同時に1セッションのみを保持できます。

`SetUserId` 実行時に `allowConcurrentAccess` を `false` にすると、すでに他のセッションが接続中の場合は、新しいセッション側で同時接続エラーとして検知できます。
`true` にすると、新しいセッションが接続された時点で古いセッションが切断されます。

この機能を利用することで、複数デバイスからの同時ログインを抑止できます。
GS2-Account のパスワード自動変更機能と組み合わせると、アカウント共有や引き継ぎ後の旧デバイスの締め出しをより強固に行うことができます。

### Firebase Cloud Messaging 連携 (モバイルプッシュ通知)

クライアントが起動していない (WebSocket が接続されていない) 状態でも通知を届けたい場合は、Firebase Cloud Messaging (FCM) との連携機能を利用できます。

GS2-Gateway は FCM HTTP v1 API で通知を送信します。
送信には GS2 がリージョンごとに保持している Google サービスアカウントを使用するため、FCM のサーバーキー (旧 `firebaseSecret`) を GS2 に登録する必要はありません。
旧来の FCM サーバーキーによる送信は Google 側で提供が終了しており、`firebaseSecret` は使用されなくなりました。

連携には以下の設定が必要です。

1. ネームスペースの `firebaseProjectId` に、通知の送信元となる Firebase プロジェクトのプロジェクトIDを設定します。
2. その Firebase (Google Cloud) プロジェクトで Firebase Cloud Messaging API を有効化します。
3. 同じプロジェクトの IAM で、利用しているリージョンに対応する GS2 のサービスアカウントに「Firebase Cloud Messaging API 管理者」(`roles/firebasemessaging.admin`) ロールを付与します。
4. 各ユーザーのデバイスから取得した FCM デバイストークンを `setFirebaseToken` で GS2-Gateway に登録します。

権限を付与するサービスアカウントは、利用しているリージョンごとに以下の通りです。

| リージョン | サービスアカウント |
| --- | --- |
| ap-northeast-1 | `api-access@gs2-ap-northeast-1-live.iam.gserviceaccount.com` |
| us-east-1 | `api-access@gs2-us-east-1-live.iam.gserviceaccount.com` |
| eu-west-1 | `api-access@gs2-eu-west-1-live.iam.gserviceaccount.com` |
| ap-southeast-1 | `api-access@gs2-ap-southeast-1-live.iam.gserviceaccount.com` |

これは GS2-Money2 のサブスクリプション検証のために Google Play Console で権限を付与するサービスアカウントと同じものです。

GS2-Gateway は、通知配信時に WebSocket セッションが存在しないユーザーに対しては、登録済みの FCM デバイストークンを使って FCM 経由でプッシュ通知を送ります。
プッシュ通知のタイトルには通知の `subject`、本文には `payload` が設定され、必要に応じて通知音を指定できます。
`issuer` `subject` `payload` はデータペイロードにも含まれるため、クライアント側で通知の発生元に応じた処理を行えます。
これによりアプリ未起動時でもユーザーに通知を届けることができます。

通知エントリの `enableTransferMobileNotification` を有効にすることで、各通知ごとにモバイルプッシュへの転送有無を制御できます。

デバイストークンが登録されていないユーザーや、`firebaseProjectId` が未設定のネームスペースでは、通知の送信結果は `offline` となります。
アプリのアンインストールなどにより FCM からデバイストークンが無効 (未登録) であると通知された場合は、保存されているデバイストークンを自動的に削除します。

#### 多言語のプッシュメッセージ

`setFirebaseToken` では、デバイストークンと合わせてプレイヤーの言語設定を `locale` として登録できます。
`locale` は省略可能で、`ja` や `en` のような任意の文字列を指定できます (固定の一覧はありません)。

通知の `payload` を次の形式の JSON にしておくと、登録された `locale` に応じてプッシュ通知の本文を出し分けられます。

```json
{
  "mobile": [
    {"locale": "ja", "message": "こんにちは"},
    {"locale": "en", "message": "Hello"},
    {"locale": "default", "message": "Hello"}
  ]
}
```

プッシュ通知の本文は以下の順序で選択されます。

1. `locale` がデバイストークンに登録された `locale` と一致するエントリ
2. 見つからない場合は `locale` が `default` のエントリ
3. それも見つからない場合は配列の先頭のエントリ

`payload` を JSON として解釈できない場合や、`mobile` が存在しない・配列ではない・空の配列である場合は、`payload` をそのままプッシュ通知の本文として使用します。
`message` が文字列ではないエントリは無視されます。

プッシュ通知のタイトルは、この場合も常に通知の `subject` です。
また、データペイロードには加工前の `payload` (および `issuer` `subject`) がそのまま含まれるため、通知からアプリを起動した際に元の JSON を解析して利用できます。

GS2-Gateway 経由で通知を送信するサービス (GS2-Chat の投稿通知、GS2-Friend のフレンドリクエスト通知、GS2-Matchmaking の成立通知 など) は、それぞれのネームスペースに `gatewayNamespaceId` / `enableTransferMobileNotification` / `sound` といった通知設定を持っています。
この設定には `mobileNotificationMessages` を指定できます。`mobileNotificationMessages` は `{ locale, title, message }` のエントリのリストで、`locale` は32文字以内、`title` は256文字以内 (省略可能)、`message` は1024文字以内、最大100件まで指定でき、`enableTransferMobileNotification` が有効な場合はマネジメントコンソールから編集できます。
通知がモバイルプッシュに転送される際は、上記と同じ規則 (デバイストークンに登録された `locale` → `default` → 配列の先頭) でエントリが選択され、`message` がプッシュ通知の本文、`title` がプッシュ通知のタイトルになります (`title` が空の場合は、通知の `subject`、たとえば `Gs2Chat:Post` が使用されます)。静的なテキストのみに対応しており、プレースホルダーは展開されません。

以下は GS2-Chat のネームスペースにおける通知設定の例です。

```json
{
  "gatewayNamespaceId": "grn:gs2:ap-northeast-1:YourOwnerId:gateway:default",
  "enableTransferMobileNotification": true,
  "mobileNotificationMessages": [
    {"locale": "ja", "title": "新着メッセージ", "message": "チャットに新しいメッセージが届きました"},
    {"locale": "en", "title": "New message", "message": "You have a new chat message"},
    {"locale": "default", "title": "New message", "message": "You have a new chat message"}
  ]
}
```

GS2-Gateway の `sendNotification` / `sendNotificationByOwnerId` / `batchSendNotification` (エントリごと) / `sendMobileNotificationByUserId` の各 API でも、同じ `mobileNotificationMessages` パラメータを省略可能な引数として、`payload` とは別に指定できます。

モバイルプッシュへの転送時の優先順位は以下の通りです。

1. `mobileNotificationMessages` (空でない場合)
2. `payload` の `mobile` 配列
3. 生の `payload`

ゲーム内 (WebSocket) への配信には影響しません。WebSocket の通知には `payload` がそのまま含まれます。

## WebSocket API の設定

GS2 のクライアント SDK (Unity / Unreal Engine / Godot) には、GS2-Gateway の WebSocket 接続を自動的に確立・維持するユーティリティが組み込まれています。

ログイン時に `GatewaySetting` を指定することで、ログイン処理の流れに乗せて WebSocket 接続と `SetUserId` の呼び出しを自動的に行えます。

設定項目は以下の通りです。

- `gatewayNamespaceName`: 使用する GS2-Gateway のネームスペース名
- `allowConcurrentAccess`: 同時接続を許可するかどうか

## トランザクションアクション

GS2-Gateway ではトランザクションアクションを提供していません。

## マスターデータ管理

GS2-Gateway はマスターデータを持ちません。
ネームスペースの設定で Firebase 連携情報やログ設定を構成します。

## 実装例

### ログイン時に WebSocket 接続を確立

`GatewaySetting` を指定してログインすると、SDK が自動的に WebSocket セッションを確立し、`SetUserId` を呼び出してユーザーIDを紐付けます。



**Unity**
```csharp

    var gameSession = await gs2.LoginAsync(
        new Gs2AccountAuthenticator(
            accountSetting: new AccountSetting {
                accountNamespaceName = this.accountNamespaceName,
            },
            gatewaySetting: new GatewaySetting {
                gatewayNamespaceName = "namespace-0001",
                allowConcurrentAccess = false,
            }
        ),
        account.UserId,
        account.Password
    );
```
**Unreal Engine 5**
```cpp

    const auto Future = Profile->Login(
        MakeShareable<Gs2::UE5::Util::IAuthenticator>(
            new Gs2::UE5::Util::FGs2AccountAuthenticator(
                AccountNamespaceName,
                KeyId,
                "namespace-0001", // gatewayNamespaceName
                false             // allowConcurrentAccess
            )
        ),
        UserId,
        Password
    );

    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Result = Future->GetTask().Result();
```
**Godot**
```gdscript

var authenticator = Gs2AccountAuthenticator.new(
    "account-namespace-0001",
    "grn:gs2:{region}:{ownerId}:key:namespace-0001:key:key-0001",
    "gateway-namespace-0001",
    false
)
var game_session = Gs2GameSession.new(
    authenticator, connection, account.user_id, account.password
)
var async_result = await game_session.login()
if async_result.error != null:
    push_error(str(async_result.error))
    return

```


### 明示的にユーザーIDを設定

すでに接続している WebSocket セッションに対してユーザーIDを紐付けたい場合や、ログイン後に同時接続の許可状態を変更したい場合は、`SetUserId` を呼び出します。



**Unity**
```csharp

    var domain = await gs2.Gateway.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).WebSocketSession(
    ).SetUserIdAsync(
        allowConcurrentAccess: false
    );
```
**Unreal Engine 5**
```cpp

    const auto Future = Gs2->Gateway->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->WebSocketSession(
    )->SetUserId(
        false // allowConcurrentAccess
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```
**Godot**
```gdscript

var domain = ez.gateway.namespace_(
        "$hash"
    ).me(game_session).web_socket_session(
    )

var async_result = await domain.set_user_id(
    true, # allow_concurrent_access
    null # session_id
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

```


## より実践的な情報

### 同時接続切断のハンドリング

`allowConcurrentAccess: false` の設定で接続中に、別端末で同一ユーザーがログインを行うと、現在の WebSocket セッションが切断されます。
クライアントは切断イベントを検知して、再ログインを促す画面に遷移する、または「他の端末でログインされました」というメッセージを表示するといった対応を行う必要があります。

これにより、複数端末からの同時プレイを実質的に禁止する運用が可能になります。

### バージョン更新時の全プレイヤー切断

GS2-Version と組み合わせ、新バージョン公開時に全プレイヤーの WebSocket セッションを切断することで、再接続のタイミングで強制的にバージョンチェックを通過させる運用が可能です。

詳細は [GS2-Version]() の「バージョン更新の運用手順」を参照してください。

## 詳細なリファレンス

[GS2-Gateway API リファレンス](../../api_reference/gateway)



