Feedforce Developer Blog

フィードフォース開発者ブログ

Pusher の Private channels と認証周りの処理を追いかけた

こんにちは、id:daido1976 です。もうすぐ 30 歳になります。

Pusher の Private channels と認証周りの処理が公式ドキュメントを読んだだけでは全然わからなかったので、ライブラリの実装を追いかけてみました。

結論

実装を追っていくと公式ドキュメントに載っている以下の図の通りになっていました。(当たり前)

f:id:daido1976:20200129205304p:plain
https://pusher.com/docs/channels/server_api/authenticating-users

コード例

以下のコード例は抜粋ですが、Rails サーバとの通信を仮定しているので、ライブラリは pusher-jspusher-http-ruby を利用する前提です。

クライアント(JavaScript)

const pusher = new Pusher("app_key", {
  auth: {
    headers: {
      // トークンベースで認証を行う場合、ここに何らかの認証情報を載せる
      // ...
    }
  }
});

// User ごとの Private チャンネルを作成し、Subscribe する場合
const channel = pusher.subscribe(`private-${userId}`);

// 以下イベントに bind する処理などが続く...

サーバ(Rails)

# Routing
Rails.application.routes.draw do
  # ...
  post '/pusher/auth', to: 'pusher_auth#create'
end
# Controller
class PusherAuthController < ApplicationController
  def create
    # クライアントから渡ってきた認証情報を使ってユーザを識別するコードは省略
    if current_user
      # Pusher のクライアント作成のコードは省略
      response = pusher_client.authenticate(params[:channel_name], params[:socket_id])
      render json: response
    else
      render json: {}, status: 403
    end
  end
end

ライブラリの実装を追いかけた

以下、ライブラリのコードを読む際に少しハマったので先にお伝えしておきます。

pusher-js は Web、 React Native など複数のランタイムのビルドを生成できるようになっています。(ビルド時に webpack によって依存関係の解決が切り替わるようになっている)1

プラットフォームに依存しない共通部分の実装は core/ 以下に、プラットフォームに依存する実装は runtimes/ 以下に置いてあるのでご留意ください。

それでは、公式ドキュメントの図に沿ってライブラリの実装を追っていきましょう。

1. WebSocket のコネクションを確立する

公式ドキュメントの図でいうと 1~3 まで。

コネクションの確立は Runtime.setup の中でやっています。(これは実装者が Pusher オブジェクトを new した時に呼ばれる)

具体的には上記の Runtime.setup の中で Pusher.ready が呼ばれ(実装はここ)、その中で Pusher#connect が呼ばれてコネクションが確立されます。

※ 内部の処理は深かったので省略しますが、最終的には公式ドキュメントの図の通り pusher:connection_established イベントが発火して connected な状態になります。(Web のための Runtime.setupこちら から始まりますので、興味のある方は追ってみてください)

2. App Server の /pusher/auth に POST リクエストして認証トークンを取得

公式ドキュメントの図でいうと 4~6 まで。

コネクションが確立されたら Pusher#subscribeここの条件分岐 に入り、*.Channel#authorize が呼ばれて App Server (コード例でいう Rails サーバ)の /pusher/auth2に POST リクエスト3して、App Server 側では ここ で認証トークン(authentication_string) を詰めて、 *.Channel#authorize の コールバック に返ってきます。

ちなみにチャンネル名を見て Private かどうかを調べてるのは ここ です。

※ Public channels の場合は ここ で認証をスキップしています。

3. 認証トークンをライブラリ内でセットして当該チャンネルを Subscribe するリクエストを Pusher に送る

公式ドキュメントの図でいうと 7。

認証トークンは ここ で勝手にセットされ、 pusher:subscribe のイベントが Pusher に送られます。(最終的には ここ で Pusher にデータが送られている)

参考


  1. https://github.com/pusher/pusher-js#core-vs-platform-specific-code

  2. authEndpoint は何も指定しなければ /pusher/auth になります。(ここでセットされてる

  3. App Server 側に POST /pusher/auth してるのは次のような流れ。Private チャンネルなので、#authorize したら PrivateChannel#authorize が呼ばれます。PusherAuthorizer#authorize の中で Runtime#getAuthorizers が呼ばれ、 この ajax 関数 が実行されます。