はじめに
今回はnpmモジュール @nemnesia/symbol-websocket の紹介です。
READMEには使い方(最新のAPI)を置いているので、この記事では「これを入れると何がラクになるのか」「どういう割り切りで作っているか」を中心に書きます。まずは雰囲気だけつかめればOK、くらいのテンションで。
npm: https://www.npmjs.com/package/@nemnesia/symbol-websocket
GitHub: https://github.com/nemnesia/symbol-tools/tree/main/packages/symbol-websocket
3行まとめ
- SymbolノードのWebSocketを、薄いAPIでサクッと購読できる
- 切断しても自動でつなぎ直して、購読もいい感じに戻る
- ノード探索やアプリ側の状態管理まで“全部やる”タイプではない(そこはアプリ側で)
インストール
npm i @nemnesia/symbol-websocket
pnpm add @nemnesia/symbol-websocket
yarn add @nemnesia/symbol-websocket
最小の使用例
import { SymbolWebSocket } from "@nemnesia/symbol-websocket";
const ws = new SymbolWebSocket({
host: "localhost",
ssl: true,
timeout: 5000,
});
ws.onConnect((uid) => {
console.log("connected uid:", uid);
});
ws.on("confirmedAdded", (message) => {
console.log("confirmedAdded:", message);
});
ws.onError((err) => {
console.error(
"type:",
err.type,
"severity:",
err.severity,
"message:",
err.message,
);
});
ws.onClose((event) => {
console.log("closed:", event.code, event.reason);
});
// 切断(close() は disconnect() のエイリアス)
// ws.disconnect();
※SymbolWebSocketはコンストラクタ生成時に接続を開始します。
これは何?(何が嬉しい?)
@nemnesia/symbol-websocketは、SymbolノードのWebSocket(/ws)に接続して、ブロックやトランザクション系のイベントをsubscribe/unsubscribeできるようにするTypeScriptライブラリです。
ログの出し方、リトライの作法、状態管理、UI反映…そういう“アプリ都合の部分”は触らずに、毎回書きがちな「接続→uid待ち→購読→再接続→購読の復元」あたりだけをまとめて肩代わりします。このへん、地味に手間なんですよね。
できること
- SymbolノードのWebSocketイベント購読(チャネル単位/アドレス単位)
- 購読の登録/解除(
on/off) - 自動再接続(デフォルト有効)+再接続後の購読復元
- エラーを分類して通知(タイプ/重大度/再接続中か…など)
やらないこと
- ノード探索(どのノードに繋ぐか決める)
- 複数ノード冗長化(それが欲しい場合は別パッケージの方が向きます)
- SDK的な高レベル抽象化(ブロック/Txの型変換、DB保存、UI連携など)
想定ユースケース
- UI/サーバ/バッチで「確定Txを監視して通知したい」
- まずはWebSocketだけ薄く扱いたい(ログや復旧はアプリ側で設計したい)
-
statusなどの失敗イベントを拾って、アプリ都合でリカバリしたい
動作環境
- 開発言語: TypeScript(型定義あり)
- 実行環境: Node.js>=20(Nodeで使う場合。ここは割と大事)
- モジュール形式: ESM(
import) - ブラウザ: Reactで検証済み(バンドラ環境で動きます。
isomorphic-wsはブラウザではネイティブWebSocketを使います)
README / APIへの導線
- README(最新情報): https://github.com/nemnesia/symbol-tools/tree/main/packages/symbol-websocket#readme
- 使い方・オプションはREADMEを正にして、この記事は「紹介+背景+方針」の補足に寄せます
なぜ作ったか(背景)
SymbolのWebSocket監視って、見た目はシンプルなんですが、実際にアプリへ組み込もうとすると地味にやることが増えます。作り始めは「すぐ終わるでしょ」と思いがちなんですけどね。
- 接続して最初に返ってくる
uidを待つ -
subscribeを投げる(アドレス付き/無しがある) - 切断時に再接続する(間隔、回数制限)
- 再接続時に「元の購読を戻す」
- JSONパース失敗やネットワーク系の扱いを揃える
この「毎回同じだけど、油断すると壊れやすい部分」だけを薄く切り出したのがこのパッケージです。
このライブラリがやろうとしていること
@nemnesia/symbol-websocketは、ここだけに集中しています。逆に言うと、ここ以外はやりません。
- ✅ WebSocket接続(
ws://{host}:3000/ws/wss://{host}:3001/ws) - ✅
subscribe/unsubscribeの送信と、コールバックの管理 - ✅ 自動再接続+購読復元
- ✅ エラーを「アプリが判断できる形」で通知
通知の出し方、DB保存、UI状態、ノード選定みたいな“アプリ固有の話”は利用側に任せます。
設計方針
1. APIは“素直”であること
import { SymbolWebSocket } from "@nemnesia/symbol-websocket";
const ws = new SymbolWebSocket({
host: "your-node.example.com",
ssl: true,
timeout: 5000,
});
- 余計な抽象化を足さず、WebSocketの購読をそのまま触れる
- “全部自動化”しすぎない(挙動を追いやすくしておく)
2. フレームワークを前提にしない
このモジュールを作るにあたって、個人的に一番気を付けたのが「Node.jsの組み込みモジュールに依存しない」ことです。そこに寄せてしまうとブラウザで使えなくなるので、React(ブラウザ)やReact Nativeも視野に入れて、依存関係はなるべく素直にしてあります。
- 特定フレームワークに寄せない
- Node.js向け(ESM)での使い方は明示しつつ、ブラウザ/React Native側でも詰まらない構成にする
3. 失敗を「静かに」しない
- エラーを握りつぶさない
- 利用側で分岐しやすい形(構造化エラー)で返す
チャネル一覧(SymbolChannel)
購読できるチャネルは以下です。アドレス指定できるものはws.on(channel, address, cb)が使えます。
blockfinalizedBlock-
confirmedAdded(アドレス指定可) -
unconfirmedAdded(アドレス指定可) -
unconfirmedRemoved(アドレス指定可) -
partialAdded(アドレス指定可) -
partialRemoved(アドレス指定可) -
cosignature(アドレス指定可) -
status(アドレス指定可)
もう少し実用寄りの例(アドレス指定+解除)
import { SymbolWebSocket } from "@nemnesia/symbol-websocket";
const address = "TB..."; // 監視したいアドレス
const ws = new SymbolWebSocket({
host: "001-sai-dual.symboltest.net",
ssl: true,
timeout: 5000,
autoReconnect: true,
});
ws.on("unconfirmedAdded", address, (msg) => {
console.log("unconfirmedAdded for address:", msg);
});
ws.on("status", address, (msg) => {
console.log("status for address:", msg);
});
// 監視を止める
// ws.off('unconfirmedAdded', address);
// ws.off('status', address);
// 完全に切断(コールバックもクリーンアップ)
// ws.disconnect();
エラー設計(構造化エラーが嬉しい)
onErrorで受け取れるエラーには、たとえば次のような情報が入ります。ログの出し分けや復旧方針を決めるときに、ここが効いてきます。
-
type:timeout/network/parse/connection/unknown -
severity:recoverable(再接続する) /fatal(再接続しない) -
reconnecting: 再接続中か -
reconnectAttempts: 何回目の再接続か -
host: 接続先
ws.onError((err) => {
if (err.severity === "fatal") {
// ここで「致命的なので止める」等、アプリ都合で判断しやすい
console.error("fatal error:", err.type, err.message);
return;
}
if (err.reconnecting) {
console.log("reconnecting...", err.reconnectAttempts);
}
});
注意点
- 自動再接続はデフォルト有効(
autoReconnect: falseで無効化) - 再接続後は、既存の購読が自動で復元されます
-
severity: 'fatal'相当になった場合は自動再接続を止めます(通知する/再起動する等はアプリ側で) -
off()はチャネル(とアドレス)単位で、登録済みコールバックをまとめて解除する挙動です
今後やりたいこと
- 通知JSONの型定義追加
- 利用者フィードバックを元にしたイベントの取り回し改善
- 周辺ツールとの組み合わせ例の拡充
※ 破壊的変更は慎重に行う予定ですが、バージョン0なので、やるときはやります。
おわりに
SymbolのWebSocket監視は「一見簡単そうだけど、運用まで考えるとやることが増える」領域なので、薄いレイヤとして@nemnesia/symbol-websocketを挟んでおくと実装がだいぶ落ち着きます。
Issue / PR は GitHub で受け付けています(気軽にどうぞ)。