猫でもわかるWebプログラミングと副業

本業エンジニアリングマネージャー。副業Webエンジニア。Web開発のヒントや、副業、日常生活のことを書きます。

yjs/y-websocket 共同編集エディタ用ライブラリ YJS の WebSocket 周りの実装

f:id:yoshiki_utakata:20220130202644j:plain

y-websocket の README の翻訳です。

github.com

y-websocket とは

Yjs 用の WebSocket プロバイダです。

Websocket は、昔からあるクライアント-サーバー間の通信方法です。ドキュメントの更新情報をサーバーからクライアントに配信します。

Yjs には、ドキュメントの更新をクライアント間で共有するためにさまざまな Provider がありますが、サーバーサイドでの認証や認可を利用場合は、WebSocketを選択するのが一番硬いです。WebSocket は、 cookie などのヘッダ情報が送れるため、サーバーで実装されている認証方法がそのまま使えます。

  • 同じドキュメントを、同じブラウザの異なるタブで開いても、ちゃんと動く「クロスタブ」に対応しています
  • 他の人のカーソルの移動を見ることができます

Quick Start

y-websocket をインストールします。

npm i y-websocket

WebSocket サーバーを立ち上げます

y-websocket のリポジトリには、基本的なサーバーの実装が付属しています。

ソースコードは: https://github.com/yjs/y-websocket/tree/master/bin

クライアントの実装例

WebSocket オブジェクトを利用してください。Polyfill (古いバージョンのブラウザでも動くようにするためのライブラリ)として、WSパッケージ も利用できます。

const wsProvider = new WebsocketProvider('ws://localhost:1234', 'my-roomname', doc, { WebSocketPolyfill: require('ws') })

API (Websocket Provider の API)

import 方法

import { WebsocketProvider } from 'y-websocket'

WebsocketProvider は以下のようにインスタンス化できます。

wsProvider = new WebsocketProvider(serverUrl: string, room: string, ydoc: Y.Doc [, wsOpts: WsOpts])

これにより、新しい WebSocket Provider インスタンスを作成します。サーバーに接続されている限り、変更はサーバーを介して他のクライアントと同期されます。weOps パラメータに値を渡すことで、各設定を変更できます。指定できる値と、デフォルトの値は以下の通りです。

wsOpts = {
  // WebSocket に接続する際に、自動的に接続せず、
  // 明示的に wsProvider.connect() を呼ぶようにしたい場合は false を指定してください
  connect: true,

  // この値を指定すると、serverUrl にクエリパラメータとして値が送信されます
  // 値は URL エンコードされて送られます
  // 例: params = { auth: "bearer" } と指定すると、"?auth=bearer" が URL に付きます
  params: {}, // Object<string,string>

  // Polyfill を使う場合に指定します
  // 例: nodejs では WebsocketPolyfill = require('ws') のように指定できます
  WebsocketPolyfill: Websocket,

  // 指定した Awareness インスタンスを利用します。
  // 詳細は https://github.com/yjs/y-protocols を読んでください
  awareness: new awarenessProtocol.Awareness(ydoc)
}

また、wsProvider (WebsocketProvider インスタンス)のプロパティやメソッドは以下の通りです。

wsProvider.wsconnected: boolean

  • サーバーに接続されていれば true を返します

`wsProvider.wsconnecting: boolean

  • サーバーに接続試行中の場合は true を返します

wsProvider.shouldConnect: boolean

  • false の場合は、クライアントは再接続しようとしません。

wsProvider.bcconnected: boolean

  • WebSocket のチャンネルを通して、他のブラウザと通信している場合は true を返します。

wsProvider.synced: boolean

  • サーバーに接続されていて、かつ、ドキュメントがサーバーと同期されている場合に true を返します。

wsProvider.disconnect()

  • サーバーから切断します。切断した後に再接続はしません。

wsProvider.connect()

  • WebSocket サーバーと接続しようとします。disconnect した後に再接続したい時や、wsOpts.connect = false を指定した場合に手動で接続する時にこのメソッドを呼んでください。

wsProvider.destroy()

  • WebsocketProvider インスタンスを破棄します。サーバーから切断した後に、全てのハンドラを削除します。

wsProvider.on('sync', function(isSynced: boolean))

  • サーバーから更新が送られてきたときに発火するイベントハンドラを追加します。

Websocket サーバー

y-websocket サーバーを立ち上げるには以下のコマンドを実行します。

HOST=localhost PORT=1234 npx y-websocket-server

y-websocket-server コマンドのシンボリックリンクが ./node_modukes/.bin に貼られるため、npx コマンドで実行できます。 PORT はデフォルトで 1234、 HOST はデフォルトで localhost です。

データを永続化したい場合

LevelDB を使ってドキュメント永続化したい場合

HOST=localhost PORT=1234 YPERSISTENCE=./dbDir node ./node_modules/y-websocket/bin/server.js

詳しくは、「LevelDB による永続化」のドキュメントを見てください: https://github.com/yjs/y-leveldb

Websocket サーバーの HTTP コールバック

ドキュメントが更新された際に HTTP サーバーに POST でコールバックを送ることができます、このコールバックにはリトライの仕組みがないので注意してください。

コールバックを利用する場合は以下の値を指定できます。

  • CALLBACK_URL : コールバックを送るサーバーのURL
  • CALLBACK_DEBOUNCE_WAIT : コールバックとコールバックの間の時間。一回コールバックを送った後、最低でもこの時間が経過しないと次のコールバックを送らない。ミリ秒で指定。デフォルトは2000ミリ秒。
  • CALLBACK_DEBOUNCE_MAXWAIT : コールバックを送るまでの最大待機時間。デフォルトでは10秒。
  • CALLBACK_TIMEOUT : HTTPリクエストのタイムアウト時間。デフォルトだと5秒。
  • CALLBACK_OBJECTS : データ取得するための Shared object の JSON(コールバックを送る際にリクエストボディにこれが付与されるという意味だと思う)'{"SHARED_OBJECT_NAME":"SHARED_OBJECT_TYPE}')

例:

CALLBACK_URL=http://localhost:3000/ CALLBACK_OBJECTS='{"prosemirror":"XmlFragment"}' npm start

この場合は、更新を受け取ってから2秒(デフォルトの DEBOUNCE_WAIT)後に、 localhost:3000 にコールバックを送ります。リクエストボディの "prosemirror" には "XmlFragment" という値を入れて送ります。