@nemnesia/symbol-websocket 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/index.cjs +284 -0
- package/dist/index.d.cts +144 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +246 -0
- package/package.json +69 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 変更履歴
|
|
2
|
+
|
|
3
|
+
このプロジェクトにおけるすべての重要な変更は、このファイルに記録されます。
|
|
4
|
+
|
|
5
|
+
変更履歴のフォーマットは[変更履歴の管理](https://keepachangelog.com/en/1.0.0/)に基づいています。
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2025/12/29
|
|
8
|
+
|
|
9
|
+
### 追加
|
|
10
|
+
|
|
11
|
+
- Symbol ブロックチェーンのWebSocket接続をサポート。
|
|
12
|
+
- リアルタイムデータ取得機能(ブロック、トランザクション、アカウント情報など)。
|
|
13
|
+
- 柔軟なサブスクリプション管理機能。
|
|
14
|
+
- 自動再接続機能とサブスクリプション復元機能。
|
|
15
|
+
- エラーおよびクローズイベントのハンドリング機能。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ccHarvestasya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Symbol WebSocket
|
|
2
|
+
|
|
3
|
+
Symbol WebSocket は、Symbol ブロックチェーンのリアルタイムデータを監視するための TypeScript ライブラリです。このライブラリは、WebSocket を使用してブロックチェーンデータを効率的に取得し、サブスクリプションベースのイベントリスニングを提供します。
|
|
4
|
+
|
|
5
|
+
## 特徴
|
|
6
|
+
|
|
7
|
+
- **リアルタイムデータ取得**: ブロック、トランザクション、アカウント情報などをリアルタイムで取得可能。
|
|
8
|
+
- **柔軟なサブスクリプション管理**: 必要なチャネルに簡単にサブスクライブおよびアンサブスクライブ可能。
|
|
9
|
+
- **エラーおよびクローズイベントのハンドリング**: WebSocket のエラーや接続終了を簡単に処理可能。
|
|
10
|
+
- **自動再接続**: 接続が切断された場合、自動的に再接続し、サブスクリプションを復元。
|
|
11
|
+
|
|
12
|
+
## インストール
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @nemnesia/symbol-websocket
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 使い方
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { SymbolWebSocket } from '@nemnesia/symbol-websocket';
|
|
22
|
+
|
|
23
|
+
const ws = new SymbolWebSocket({
|
|
24
|
+
host: 'localhost',
|
|
25
|
+
ssl: true,
|
|
26
|
+
timeout: 5000,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// チャネルにサブスクライブ
|
|
30
|
+
ws.on('confirmedAdded', (message) => {
|
|
31
|
+
console.log('New confirmed transaction:', message);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// エラーイベントの登録
|
|
35
|
+
ws.onError((err) => {
|
|
36
|
+
console.error('WebSocket error:', err);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// クローズイベントの登録
|
|
40
|
+
ws.onClose((event) => {
|
|
41
|
+
console.log('WebSocket closed:', event);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 切断
|
|
45
|
+
ws.disconnect();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## API
|
|
49
|
+
|
|
50
|
+
#### コンストラクタ
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
new SymbolWebSocket(options: SymbolWebSocketOptions);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `options`: 接続設定。
|
|
57
|
+
- `host`: 接続先ホスト。
|
|
58
|
+
- `ssl`: SSL を使用するかどうか。
|
|
59
|
+
- `timeout`: 接続タイムアウト(ミリ秒)。指定時間内に接続が完了しない場合はエラーになります。
|
|
60
|
+
- `autoReconnect`: 自動再接続を有効にするか(デフォルト: `true`)。
|
|
61
|
+
- `maxReconnectAttempts`: 最大再接続試行回数(デフォルト: `Infinity`)。
|
|
62
|
+
- `reconnectInterval`: 再接続の間隔(ミリ秒、デフォルト: `3000`)。
|
|
63
|
+
|
|
64
|
+
#### メソッド
|
|
65
|
+
|
|
66
|
+
- `on(channel: SymbolChannel, callback: (message: WebSocket.MessageEvent) => void): void`
|
|
67
|
+
- 指定したチャネルにサブスクライブします。
|
|
68
|
+
- `on(channel: SymbolChannel, address: string, callback: (message: WebSocket.MessageEvent) => void): void`
|
|
69
|
+
- アドレスを指定してチャネルにサブスクライブします。
|
|
70
|
+
- `off(channel: SymbolChannel): void`
|
|
71
|
+
- 指定したチャネルのサブスクリプションを解除します。
|
|
72
|
+
- `off(channel: SymbolChannel, address: string): void`
|
|
73
|
+
- アドレスを指定してチャネルのサブスクリプションを解除します。
|
|
74
|
+
- `onConnect(callback: (uid: string) => void): void`
|
|
75
|
+
- WebSocket 接続完了時のコールバックを登録します。
|
|
76
|
+
- `onReconnect(callback: (attemptCount: number) => void): void`
|
|
77
|
+
- 再接続試行時のコールバックを登録します。
|
|
78
|
+
- `onError(callback: (err: WebSocket.ErrorEvent) => void): void`
|
|
79
|
+
- エラーイベントのコールバックを登録します。
|
|
80
|
+
- `onClose(callback: (event: WebSocket.CloseEvent) => void): void`
|
|
81
|
+
- クローズイベントのコールバックを登録します。
|
|
82
|
+
- `disconnect(): void`
|
|
83
|
+
- WebSocket 接続を切断します。
|
|
84
|
+
|
|
85
|
+
## 注意点
|
|
86
|
+
|
|
87
|
+
- 再接続は自動的に行われます(デフォルト有効)。
|
|
88
|
+
- 再接続時は既存のサブスクリプションが自動的に復元されます。
|
|
89
|
+
- `autoReconnect: false`を設定することで自動再接続を無効化できます。
|
|
90
|
+
|
|
91
|
+
## ライセンス
|
|
92
|
+
|
|
93
|
+
このプロジェクトは [MITライセンス](./LICENSE) のもとで公開されています。
|
|
94
|
+
|
|
95
|
+
## 貢献方法
|
|
96
|
+
|
|
97
|
+
バグ報告・機能要望・プルリクエストは [GitHubリポジトリ](https://github.com/nemnesia/symbol-tools/tree/main/packages/symbol-websocket) で受け付けています。お気軽にご参加ください。
|
|
98
|
+
|
|
99
|
+
## バグ報告・質問
|
|
100
|
+
|
|
101
|
+
問題や質問は [GitHub Issues](https://github.com/nemnesia/symbol-tools/issues) からご連絡ください。
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
SymbolWebSocket: () => SymbolWebSocket,
|
|
34
|
+
symbolChannelPaths: () => symbolChannelPaths
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/symbolChannelPaths.ts
|
|
39
|
+
var symbolChannelPaths = {
|
|
40
|
+
block: { subscribe: () => "block" },
|
|
41
|
+
finalizedBlock: { subscribe: () => "finalizedBlock" },
|
|
42
|
+
confirmedAdded: { subscribe: (address) => address ? `confirmedAdded/${address}` : "confirmedAdded" },
|
|
43
|
+
unconfirmedAdded: { subscribe: (address) => address ? `unconfirmedAdded/${address}` : "unconfirmedAdded" },
|
|
44
|
+
unconfirmedRemoved: {
|
|
45
|
+
subscribe: (address) => address ? `unconfirmedRemoved/${address}` : "unconfirmedRemoved"
|
|
46
|
+
},
|
|
47
|
+
partialAdded: { subscribe: (address) => address ? `partialAdded/${address}` : "partialAdded" },
|
|
48
|
+
partialRemoved: { subscribe: (address) => address ? `partialRemoved/${address}` : "partialRemoved" },
|
|
49
|
+
cosignature: { subscribe: (address) => address ? `cosignature/${address}` : "cosignature" },
|
|
50
|
+
status: { subscribe: (address) => address ? `status/${address}` : "status" }
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/SymbolWebSocket.ts
|
|
54
|
+
var import_isomorphic_ws = __toESM(require("isomorphic-ws"), 1);
|
|
55
|
+
var WS_OPEN = import_isomorphic_ws.default.OPEN ?? 1;
|
|
56
|
+
var WS_CONNECTING = import_isomorphic_ws.default.CONNECTING ?? 0;
|
|
57
|
+
var SymbolWebSocket = class {
|
|
58
|
+
_client;
|
|
59
|
+
_uid = null;
|
|
60
|
+
isFirstMessage = true;
|
|
61
|
+
eventCallbacks = {};
|
|
62
|
+
pendingSubscribes = [];
|
|
63
|
+
errorCallbacks = [];
|
|
64
|
+
onCloseCallback = () => {
|
|
65
|
+
};
|
|
66
|
+
connectCallbacks = [];
|
|
67
|
+
reconnectCallbacks = [];
|
|
68
|
+
// 再接続関連のプロパティ
|
|
69
|
+
options;
|
|
70
|
+
reconnectAttempts = 0;
|
|
71
|
+
reconnectTimer = null;
|
|
72
|
+
connectionTimeoutTimer = null;
|
|
73
|
+
isManualDisconnect = false;
|
|
74
|
+
activeSubscriptions = /* @__PURE__ */ new Set();
|
|
75
|
+
/**
|
|
76
|
+
* コンストラクタ
|
|
77
|
+
* @param options Symbolウェブソケットオプション
|
|
78
|
+
*/
|
|
79
|
+
constructor(options) {
|
|
80
|
+
this.options = {
|
|
81
|
+
autoReconnect: true,
|
|
82
|
+
maxReconnectAttempts: Infinity,
|
|
83
|
+
reconnectInterval: 3e3,
|
|
84
|
+
...options
|
|
85
|
+
};
|
|
86
|
+
this.createConnection();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* WebSocket接続を作成
|
|
90
|
+
*/
|
|
91
|
+
createConnection() {
|
|
92
|
+
const endPointHost = this.options.host;
|
|
93
|
+
const ssl = this.options.ssl ?? false;
|
|
94
|
+
const protocol = ssl ? "wss" : "ws";
|
|
95
|
+
const endPointPort = ssl ? "3001" : "3000";
|
|
96
|
+
this._client = new import_isomorphic_ws.default(`${protocol}://${endPointHost}:${endPointPort}/ws`);
|
|
97
|
+
if (this.options.timeout) {
|
|
98
|
+
this.connectionTimeoutTimer = setTimeout(() => {
|
|
99
|
+
if (this._client.readyState === WS_CONNECTING || !this._uid) {
|
|
100
|
+
const timeoutError = new Error(`WebSocket connection timeout after ${this.options.timeout}ms`);
|
|
101
|
+
const errorEvent = { error: timeoutError, message: timeoutError.message };
|
|
102
|
+
this.errorCallbacks.forEach((cb) => cb(errorEvent));
|
|
103
|
+
this._client.close();
|
|
104
|
+
}
|
|
105
|
+
}, this.options.timeout);
|
|
106
|
+
}
|
|
107
|
+
this._client.onclose = (event) => {
|
|
108
|
+
this.onCloseCallback(event);
|
|
109
|
+
if (!this.isManualDisconnect && this.options.autoReconnect) {
|
|
110
|
+
this.attemptReconnect();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
this._client.onerror = (err) => {
|
|
114
|
+
this.errorCallbacks.forEach((cb) => cb(err));
|
|
115
|
+
};
|
|
116
|
+
this._client.onmessage = (message) => {
|
|
117
|
+
try {
|
|
118
|
+
const data = JSON.parse(message.data.toString());
|
|
119
|
+
if (this.isFirstMessage) {
|
|
120
|
+
if (data.uid) {
|
|
121
|
+
this._uid = data.uid;
|
|
122
|
+
this.reconnectAttempts = 0;
|
|
123
|
+
if (this.connectionTimeoutTimer) {
|
|
124
|
+
clearTimeout(this.connectionTimeoutTimer);
|
|
125
|
+
this.connectionTimeoutTimer = null;
|
|
126
|
+
}
|
|
127
|
+
this.connectCallbacks.forEach((cb) => cb(this._uid));
|
|
128
|
+
this.activeSubscriptions.forEach((subscribePath) => {
|
|
129
|
+
this._client.send(JSON.stringify({ uid: this._uid, subscribe: subscribePath }));
|
|
130
|
+
});
|
|
131
|
+
this.pendingSubscribes.forEach(({ subscribePath }) => {
|
|
132
|
+
this._client.send(JSON.stringify({ uid: this._uid, subscribe: subscribePath }));
|
|
133
|
+
this.activeSubscriptions.add(subscribePath);
|
|
134
|
+
});
|
|
135
|
+
this.pendingSubscribes = [];
|
|
136
|
+
}
|
|
137
|
+
this.isFirstMessage = false;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const channel = data.topic;
|
|
141
|
+
if (channel && this.eventCallbacks[channel]) {
|
|
142
|
+
this.eventCallbacks[channel].forEach((cb) => cb(data));
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
if (this.errorCallbacks.length > 0) {
|
|
146
|
+
const errorEvent = { ...e instanceof Error ? e : { message: String(e) } };
|
|
147
|
+
this.errorCallbacks.forEach((cb) => cb(errorEvent));
|
|
148
|
+
} else {
|
|
149
|
+
throw e;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 再接続を試みる
|
|
156
|
+
*/
|
|
157
|
+
attemptReconnect() {
|
|
158
|
+
const maxAttempts = this.options.maxReconnectAttempts ?? Infinity;
|
|
159
|
+
if (this.reconnectAttempts >= maxAttempts) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.reconnectAttempts++;
|
|
163
|
+
this.reconnectCallbacks.forEach((cb) => cb(this.reconnectAttempts));
|
|
164
|
+
const interval = this.options.reconnectInterval ?? 3e3;
|
|
165
|
+
this.reconnectTimer = setTimeout(() => {
|
|
166
|
+
this.isFirstMessage = true;
|
|
167
|
+
this._uid = null;
|
|
168
|
+
this.createConnection();
|
|
169
|
+
}, interval);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* WebSocket接続完了イベント登録
|
|
173
|
+
* @param callback 接続時に呼ばれるコールバック
|
|
174
|
+
*/
|
|
175
|
+
onConnect(callback) {
|
|
176
|
+
this.connectCallbacks.push(callback);
|
|
177
|
+
if (this._uid) {
|
|
178
|
+
callback(this._uid);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* WebSocket再接続イベント登録
|
|
183
|
+
* @param callback 再接続試行時に呼ばれるコールバック
|
|
184
|
+
*/
|
|
185
|
+
onReconnect(callback) {
|
|
186
|
+
this.reconnectCallbacks.push(callback);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* UID
|
|
190
|
+
*/
|
|
191
|
+
get uid() {
|
|
192
|
+
return this._uid;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* クライアントインスタンスを取得
|
|
196
|
+
*/
|
|
197
|
+
get client() {
|
|
198
|
+
return this._client;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* 接続状態を取得
|
|
202
|
+
*/
|
|
203
|
+
get isConnected() {
|
|
204
|
+
return this._client.readyState === WS_OPEN;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* WebSocketエラーイベント登録
|
|
208
|
+
* @param callback エラー時に呼ばれるコールバック
|
|
209
|
+
*/
|
|
210
|
+
onError(callback) {
|
|
211
|
+
this.errorCallbacks.push(callback);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* WebSocketクローズイベント登録
|
|
215
|
+
* @param callback クローズ時に呼ばれるコールバック
|
|
216
|
+
*/
|
|
217
|
+
onClose(callback) {
|
|
218
|
+
this.onCloseCallback = callback;
|
|
219
|
+
}
|
|
220
|
+
on(channel, addressOrCallback, callback) {
|
|
221
|
+
const address = typeof addressOrCallback === "string" ? addressOrCallback : void 0;
|
|
222
|
+
const actualCallback = typeof addressOrCallback === "function" ? addressOrCallback : callback;
|
|
223
|
+
const channelPath = symbolChannelPaths[channel];
|
|
224
|
+
const subscribePath = typeof channelPath.subscribe === "function" ? channelPath.subscribe(address) : channelPath.subscribe;
|
|
225
|
+
if (!subscribePath) {
|
|
226
|
+
throw new Error(`Subscribe path could not be determined for channel: ${channel}`);
|
|
227
|
+
}
|
|
228
|
+
if (!this.eventCallbacks[subscribePath]) {
|
|
229
|
+
this.eventCallbacks[subscribePath] = [];
|
|
230
|
+
}
|
|
231
|
+
this.eventCallbacks[subscribePath].push(actualCallback);
|
|
232
|
+
if (!this._uid) {
|
|
233
|
+
this.pendingSubscribes.push({ subscribePath, callback: actualCallback });
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (this._client.readyState === WS_OPEN) {
|
|
237
|
+
this._client.send(JSON.stringify({ uid: this._uid, subscribe: subscribePath }));
|
|
238
|
+
this.activeSubscriptions.add(subscribePath);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
off(channel, address) {
|
|
242
|
+
const channelPath = symbolChannelPaths[channel];
|
|
243
|
+
const subscribePath = typeof channelPath.subscribe === "function" ? channelPath.subscribe(address) : channelPath.subscribe;
|
|
244
|
+
if (!subscribePath) {
|
|
245
|
+
throw new Error(`Subscribe path could not be determined for channel: ${channel}`);
|
|
246
|
+
}
|
|
247
|
+
delete this.eventCallbacks[subscribePath];
|
|
248
|
+
this.activeSubscriptions.delete(subscribePath);
|
|
249
|
+
if (this._uid && this._client.readyState === WS_OPEN) {
|
|
250
|
+
this._client.send(JSON.stringify({ uid: this._uid, unsubscribe: subscribePath }));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* WebSocket接続を切断
|
|
255
|
+
*/
|
|
256
|
+
disconnect() {
|
|
257
|
+
this.isManualDisconnect = true;
|
|
258
|
+
if (this.reconnectTimer) {
|
|
259
|
+
clearTimeout(this.reconnectTimer);
|
|
260
|
+
this.reconnectTimer = null;
|
|
261
|
+
}
|
|
262
|
+
if (this.connectionTimeoutTimer) {
|
|
263
|
+
clearTimeout(this.connectionTimeoutTimer);
|
|
264
|
+
this.connectionTimeoutTimer = null;
|
|
265
|
+
}
|
|
266
|
+
this.eventCallbacks = {};
|
|
267
|
+
this.pendingSubscribes = [];
|
|
268
|
+
this.errorCallbacks = [];
|
|
269
|
+
this.connectCallbacks = [];
|
|
270
|
+
this.reconnectCallbacks = [];
|
|
271
|
+
this.activeSubscriptions.clear();
|
|
272
|
+
if (this._client.readyState === WS_OPEN || this._client.readyState === WS_CONNECTING) {
|
|
273
|
+
this._client.close();
|
|
274
|
+
}
|
|
275
|
+
this._uid = null;
|
|
276
|
+
this.isFirstMessage = true;
|
|
277
|
+
this.reconnectAttempts = 0;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
281
|
+
0 && (module.exports = {
|
|
282
|
+
SymbolWebSocket,
|
|
283
|
+
symbolChannelPaths
|
|
284
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import WebSocket from 'isomorphic-ws';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Symbolチャネルタイプ
|
|
5
|
+
* - block: 生成されたブロックの通知
|
|
6
|
+
* - finalizedBlock: ファイナライズ通知
|
|
7
|
+
* - confirmedAdded: 承認トランザクション通知
|
|
8
|
+
* - unconfirmedAdded: 未承認トランザクション通知
|
|
9
|
+
* - unconfirmedRemoved: 未承認トランザクション削除通知
|
|
10
|
+
* - partialAdded: パーシャル追加通知
|
|
11
|
+
* - partialRemoved: パーシャル削除通知
|
|
12
|
+
* - cosignature: 連署要求通知
|
|
13
|
+
* - status: ステータス通知
|
|
14
|
+
*/
|
|
15
|
+
type SymbolChannel = 'block' | 'finalizedBlock' | 'confirmedAdded' | 'unconfirmedAdded' | 'unconfirmedRemoved' | 'partialAdded' | 'partialRemoved' | 'cosignature' | 'status';
|
|
16
|
+
/**
|
|
17
|
+
* Symbolチャネルパス定義
|
|
18
|
+
*/
|
|
19
|
+
declare const symbolChannelPaths: Record<SymbolChannel, {
|
|
20
|
+
subscribe: (address?: string) => string;
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Symbolウェブソケットモニターオプション
|
|
25
|
+
*/
|
|
26
|
+
interface SymbolWebSocketOptions {
|
|
27
|
+
host: string;
|
|
28
|
+
/**
|
|
29
|
+
* 接続タイムアウト(ミリ秒)
|
|
30
|
+
*/
|
|
31
|
+
timeout?: number;
|
|
32
|
+
ssl?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* 自動再接続を有効にする
|
|
35
|
+
* @default true
|
|
36
|
+
*/
|
|
37
|
+
autoReconnect?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* 最大再接続試行回数
|
|
40
|
+
* @default Infinity
|
|
41
|
+
*/
|
|
42
|
+
maxReconnectAttempts?: number;
|
|
43
|
+
/**
|
|
44
|
+
* 再接続間隔(ミリ秒)
|
|
45
|
+
* @default 3000
|
|
46
|
+
*/
|
|
47
|
+
reconnectInterval?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Symbolウェブソケットクラス
|
|
52
|
+
*/
|
|
53
|
+
declare class SymbolWebSocket {
|
|
54
|
+
private _client;
|
|
55
|
+
private _uid;
|
|
56
|
+
private isFirstMessage;
|
|
57
|
+
private eventCallbacks;
|
|
58
|
+
private pendingSubscribes;
|
|
59
|
+
private errorCallbacks;
|
|
60
|
+
private onCloseCallback;
|
|
61
|
+
private connectCallbacks;
|
|
62
|
+
private reconnectCallbacks;
|
|
63
|
+
private options;
|
|
64
|
+
private reconnectAttempts;
|
|
65
|
+
private reconnectTimer;
|
|
66
|
+
private connectionTimeoutTimer;
|
|
67
|
+
private isManualDisconnect;
|
|
68
|
+
private activeSubscriptions;
|
|
69
|
+
/**
|
|
70
|
+
* コンストラクタ
|
|
71
|
+
* @param options Symbolウェブソケットオプション
|
|
72
|
+
*/
|
|
73
|
+
constructor(options: SymbolWebSocketOptions);
|
|
74
|
+
/**
|
|
75
|
+
* WebSocket接続を作成
|
|
76
|
+
*/
|
|
77
|
+
private createConnection;
|
|
78
|
+
/**
|
|
79
|
+
* 再接続を試みる
|
|
80
|
+
*/
|
|
81
|
+
private attemptReconnect;
|
|
82
|
+
/**
|
|
83
|
+
* WebSocket接続完了イベント登録
|
|
84
|
+
* @param callback 接続時に呼ばれるコールバック
|
|
85
|
+
*/
|
|
86
|
+
onConnect(callback: (uid: string) => void): void;
|
|
87
|
+
/**
|
|
88
|
+
* WebSocket再接続イベント登録
|
|
89
|
+
* @param callback 再接続試行時に呼ばれるコールバック
|
|
90
|
+
*/
|
|
91
|
+
onReconnect(callback: (attemptCount: number) => void): void;
|
|
92
|
+
/**
|
|
93
|
+
* UID
|
|
94
|
+
*/
|
|
95
|
+
get uid(): string | null;
|
|
96
|
+
/**
|
|
97
|
+
* クライアントインスタンスを取得
|
|
98
|
+
*/
|
|
99
|
+
get client(): WebSocket;
|
|
100
|
+
/**
|
|
101
|
+
* 接続状態を取得
|
|
102
|
+
*/
|
|
103
|
+
get isConnected(): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* WebSocketエラーイベント登録
|
|
106
|
+
* @param callback エラー時に呼ばれるコールバック
|
|
107
|
+
*/
|
|
108
|
+
onError(callback: (err: WebSocket.ErrorEvent) => void): void;
|
|
109
|
+
/**
|
|
110
|
+
* WebSocketクローズイベント登録
|
|
111
|
+
* @param callback クローズ時に呼ばれるコールバック
|
|
112
|
+
*/
|
|
113
|
+
onClose(callback: (event: WebSocket.CloseEvent) => void): void;
|
|
114
|
+
/**
|
|
115
|
+
* チャネルサブスクメソッド
|
|
116
|
+
* @param channel チャネル名
|
|
117
|
+
* @param callback コールバック関数
|
|
118
|
+
*/
|
|
119
|
+
on(channel: SymbolChannel, callback: (message: WebSocket.MessageEvent) => void): void;
|
|
120
|
+
/**
|
|
121
|
+
* チャネルサブスクメソッド
|
|
122
|
+
* @param channel チャネル名
|
|
123
|
+
* @param address アドレス
|
|
124
|
+
* @param callback コールバック関数
|
|
125
|
+
*/
|
|
126
|
+
on(channel: SymbolChannel, address: string, callback: (message: WebSocket.MessageEvent) => void): void;
|
|
127
|
+
/**
|
|
128
|
+
* チャネルアンサブスクメソッド
|
|
129
|
+
* @param channel チャネル名
|
|
130
|
+
*/
|
|
131
|
+
off(channel: SymbolChannel): void;
|
|
132
|
+
/**
|
|
133
|
+
* チャネルアンサブスクメソッド
|
|
134
|
+
* @param channel チャネル名
|
|
135
|
+
* @param address アドレス
|
|
136
|
+
*/
|
|
137
|
+
off(channel: SymbolChannel, address: string): void;
|
|
138
|
+
/**
|
|
139
|
+
* WebSocket接続を切断
|
|
140
|
+
*/
|
|
141
|
+
disconnect(): void;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export { type SymbolChannel, SymbolWebSocket, type SymbolWebSocketOptions, symbolChannelPaths };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import WebSocket from 'isomorphic-ws';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Symbolチャネルタイプ
|
|
5
|
+
* - block: 生成されたブロックの通知
|
|
6
|
+
* - finalizedBlock: ファイナライズ通知
|
|
7
|
+
* - confirmedAdded: 承認トランザクション通知
|
|
8
|
+
* - unconfirmedAdded: 未承認トランザクション通知
|
|
9
|
+
* - unconfirmedRemoved: 未承認トランザクション削除通知
|
|
10
|
+
* - partialAdded: パーシャル追加通知
|
|
11
|
+
* - partialRemoved: パーシャル削除通知
|
|
12
|
+
* - cosignature: 連署要求通知
|
|
13
|
+
* - status: ステータス通知
|
|
14
|
+
*/
|
|
15
|
+
type SymbolChannel = 'block' | 'finalizedBlock' | 'confirmedAdded' | 'unconfirmedAdded' | 'unconfirmedRemoved' | 'partialAdded' | 'partialRemoved' | 'cosignature' | 'status';
|
|
16
|
+
/**
|
|
17
|
+
* Symbolチャネルパス定義
|
|
18
|
+
*/
|
|
19
|
+
declare const symbolChannelPaths: Record<SymbolChannel, {
|
|
20
|
+
subscribe: (address?: string) => string;
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Symbolウェブソケットモニターオプション
|
|
25
|
+
*/
|
|
26
|
+
interface SymbolWebSocketOptions {
|
|
27
|
+
host: string;
|
|
28
|
+
/**
|
|
29
|
+
* 接続タイムアウト(ミリ秒)
|
|
30
|
+
*/
|
|
31
|
+
timeout?: number;
|
|
32
|
+
ssl?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* 自動再接続を有効にする
|
|
35
|
+
* @default true
|
|
36
|
+
*/
|
|
37
|
+
autoReconnect?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* 最大再接続試行回数
|
|
40
|
+
* @default Infinity
|
|
41
|
+
*/
|
|
42
|
+
maxReconnectAttempts?: number;
|
|
43
|
+
/**
|
|
44
|
+
* 再接続間隔(ミリ秒)
|
|
45
|
+
* @default 3000
|
|
46
|
+
*/
|
|
47
|
+
reconnectInterval?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Symbolウェブソケットクラス
|
|
52
|
+
*/
|
|
53
|
+
declare class SymbolWebSocket {
|
|
54
|
+
private _client;
|
|
55
|
+
private _uid;
|
|
56
|
+
private isFirstMessage;
|
|
57
|
+
private eventCallbacks;
|
|
58
|
+
private pendingSubscribes;
|
|
59
|
+
private errorCallbacks;
|
|
60
|
+
private onCloseCallback;
|
|
61
|
+
private connectCallbacks;
|
|
62
|
+
private reconnectCallbacks;
|
|
63
|
+
private options;
|
|
64
|
+
private reconnectAttempts;
|
|
65
|
+
private reconnectTimer;
|
|
66
|
+
private connectionTimeoutTimer;
|
|
67
|
+
private isManualDisconnect;
|
|
68
|
+
private activeSubscriptions;
|
|
69
|
+
/**
|
|
70
|
+
* コンストラクタ
|
|
71
|
+
* @param options Symbolウェブソケットオプション
|
|
72
|
+
*/
|
|
73
|
+
constructor(options: SymbolWebSocketOptions);
|
|
74
|
+
/**
|
|
75
|
+
* WebSocket接続を作成
|
|
76
|
+
*/
|
|
77
|
+
private createConnection;
|
|
78
|
+
/**
|
|
79
|
+
* 再接続を試みる
|
|
80
|
+
*/
|
|
81
|
+
private attemptReconnect;
|
|
82
|
+
/**
|
|
83
|
+
* WebSocket接続完了イベント登録
|
|
84
|
+
* @param callback 接続時に呼ばれるコールバック
|
|
85
|
+
*/
|
|
86
|
+
onConnect(callback: (uid: string) => void): void;
|
|
87
|
+
/**
|
|
88
|
+
* WebSocket再接続イベント登録
|
|
89
|
+
* @param callback 再接続試行時に呼ばれるコールバック
|
|
90
|
+
*/
|
|
91
|
+
onReconnect(callback: (attemptCount: number) => void): void;
|
|
92
|
+
/**
|
|
93
|
+
* UID
|
|
94
|
+
*/
|
|
95
|
+
get uid(): string | null;
|
|
96
|
+
/**
|
|
97
|
+
* クライアントインスタンスを取得
|
|
98
|
+
*/
|
|
99
|
+
get client(): WebSocket;
|
|
100
|
+
/**
|
|
101
|
+
* 接続状態を取得
|
|
102
|
+
*/
|
|
103
|
+
get isConnected(): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* WebSocketエラーイベント登録
|
|
106
|
+
* @param callback エラー時に呼ばれるコールバック
|
|
107
|
+
*/
|
|
108
|
+
onError(callback: (err: WebSocket.ErrorEvent) => void): void;
|
|
109
|
+
/**
|
|
110
|
+
* WebSocketクローズイベント登録
|
|
111
|
+
* @param callback クローズ時に呼ばれるコールバック
|
|
112
|
+
*/
|
|
113
|
+
onClose(callback: (event: WebSocket.CloseEvent) => void): void;
|
|
114
|
+
/**
|
|
115
|
+
* チャネルサブスクメソッド
|
|
116
|
+
* @param channel チャネル名
|
|
117
|
+
* @param callback コールバック関数
|
|
118
|
+
*/
|
|
119
|
+
on(channel: SymbolChannel, callback: (message: WebSocket.MessageEvent) => void): void;
|
|
120
|
+
/**
|
|
121
|
+
* チャネルサブスクメソッド
|
|
122
|
+
* @param channel チャネル名
|
|
123
|
+
* @param address アドレス
|
|
124
|
+
* @param callback コールバック関数
|
|
125
|
+
*/
|
|
126
|
+
on(channel: SymbolChannel, address: string, callback: (message: WebSocket.MessageEvent) => void): void;
|
|
127
|
+
/**
|
|
128
|
+
* チャネルアンサブスクメソッド
|
|
129
|
+
* @param channel チャネル名
|
|
130
|
+
*/
|
|
131
|
+
off(channel: SymbolChannel): void;
|
|
132
|
+
/**
|
|
133
|
+
* チャネルアンサブスクメソッド
|
|
134
|
+
* @param channel チャネル名
|
|
135
|
+
* @param address アドレス
|
|
136
|
+
*/
|
|
137
|
+
off(channel: SymbolChannel, address: string): void;
|
|
138
|
+
/**
|
|
139
|
+
* WebSocket接続を切断
|
|
140
|
+
*/
|
|
141
|
+
disconnect(): void;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export { type SymbolChannel, SymbolWebSocket, type SymbolWebSocketOptions, symbolChannelPaths };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// src/symbolChannelPaths.ts
|
|
2
|
+
var symbolChannelPaths = {
|
|
3
|
+
block: { subscribe: () => "block" },
|
|
4
|
+
finalizedBlock: { subscribe: () => "finalizedBlock" },
|
|
5
|
+
confirmedAdded: { subscribe: (address) => address ? `confirmedAdded/${address}` : "confirmedAdded" },
|
|
6
|
+
unconfirmedAdded: { subscribe: (address) => address ? `unconfirmedAdded/${address}` : "unconfirmedAdded" },
|
|
7
|
+
unconfirmedRemoved: {
|
|
8
|
+
subscribe: (address) => address ? `unconfirmedRemoved/${address}` : "unconfirmedRemoved"
|
|
9
|
+
},
|
|
10
|
+
partialAdded: { subscribe: (address) => address ? `partialAdded/${address}` : "partialAdded" },
|
|
11
|
+
partialRemoved: { subscribe: (address) => address ? `partialRemoved/${address}` : "partialRemoved" },
|
|
12
|
+
cosignature: { subscribe: (address) => address ? `cosignature/${address}` : "cosignature" },
|
|
13
|
+
status: { subscribe: (address) => address ? `status/${address}` : "status" }
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/SymbolWebSocket.ts
|
|
17
|
+
import WebSocket from "isomorphic-ws";
|
|
18
|
+
var WS_OPEN = WebSocket.OPEN ?? 1;
|
|
19
|
+
var WS_CONNECTING = WebSocket.CONNECTING ?? 0;
|
|
20
|
+
var SymbolWebSocket = class {
|
|
21
|
+
_client;
|
|
22
|
+
_uid = null;
|
|
23
|
+
isFirstMessage = true;
|
|
24
|
+
eventCallbacks = {};
|
|
25
|
+
pendingSubscribes = [];
|
|
26
|
+
errorCallbacks = [];
|
|
27
|
+
onCloseCallback = () => {
|
|
28
|
+
};
|
|
29
|
+
connectCallbacks = [];
|
|
30
|
+
reconnectCallbacks = [];
|
|
31
|
+
// 再接続関連のプロパティ
|
|
32
|
+
options;
|
|
33
|
+
reconnectAttempts = 0;
|
|
34
|
+
reconnectTimer = null;
|
|
35
|
+
connectionTimeoutTimer = null;
|
|
36
|
+
isManualDisconnect = false;
|
|
37
|
+
activeSubscriptions = /* @__PURE__ */ new Set();
|
|
38
|
+
/**
|
|
39
|
+
* コンストラクタ
|
|
40
|
+
* @param options Symbolウェブソケットオプション
|
|
41
|
+
*/
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.options = {
|
|
44
|
+
autoReconnect: true,
|
|
45
|
+
maxReconnectAttempts: Infinity,
|
|
46
|
+
reconnectInterval: 3e3,
|
|
47
|
+
...options
|
|
48
|
+
};
|
|
49
|
+
this.createConnection();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* WebSocket接続を作成
|
|
53
|
+
*/
|
|
54
|
+
createConnection() {
|
|
55
|
+
const endPointHost = this.options.host;
|
|
56
|
+
const ssl = this.options.ssl ?? false;
|
|
57
|
+
const protocol = ssl ? "wss" : "ws";
|
|
58
|
+
const endPointPort = ssl ? "3001" : "3000";
|
|
59
|
+
this._client = new WebSocket(`${protocol}://${endPointHost}:${endPointPort}/ws`);
|
|
60
|
+
if (this.options.timeout) {
|
|
61
|
+
this.connectionTimeoutTimer = setTimeout(() => {
|
|
62
|
+
if (this._client.readyState === WS_CONNECTING || !this._uid) {
|
|
63
|
+
const timeoutError = new Error(`WebSocket connection timeout after ${this.options.timeout}ms`);
|
|
64
|
+
const errorEvent = { error: timeoutError, message: timeoutError.message };
|
|
65
|
+
this.errorCallbacks.forEach((cb) => cb(errorEvent));
|
|
66
|
+
this._client.close();
|
|
67
|
+
}
|
|
68
|
+
}, this.options.timeout);
|
|
69
|
+
}
|
|
70
|
+
this._client.onclose = (event) => {
|
|
71
|
+
this.onCloseCallback(event);
|
|
72
|
+
if (!this.isManualDisconnect && this.options.autoReconnect) {
|
|
73
|
+
this.attemptReconnect();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
this._client.onerror = (err) => {
|
|
77
|
+
this.errorCallbacks.forEach((cb) => cb(err));
|
|
78
|
+
};
|
|
79
|
+
this._client.onmessage = (message) => {
|
|
80
|
+
try {
|
|
81
|
+
const data = JSON.parse(message.data.toString());
|
|
82
|
+
if (this.isFirstMessage) {
|
|
83
|
+
if (data.uid) {
|
|
84
|
+
this._uid = data.uid;
|
|
85
|
+
this.reconnectAttempts = 0;
|
|
86
|
+
if (this.connectionTimeoutTimer) {
|
|
87
|
+
clearTimeout(this.connectionTimeoutTimer);
|
|
88
|
+
this.connectionTimeoutTimer = null;
|
|
89
|
+
}
|
|
90
|
+
this.connectCallbacks.forEach((cb) => cb(this._uid));
|
|
91
|
+
this.activeSubscriptions.forEach((subscribePath) => {
|
|
92
|
+
this._client.send(JSON.stringify({ uid: this._uid, subscribe: subscribePath }));
|
|
93
|
+
});
|
|
94
|
+
this.pendingSubscribes.forEach(({ subscribePath }) => {
|
|
95
|
+
this._client.send(JSON.stringify({ uid: this._uid, subscribe: subscribePath }));
|
|
96
|
+
this.activeSubscriptions.add(subscribePath);
|
|
97
|
+
});
|
|
98
|
+
this.pendingSubscribes = [];
|
|
99
|
+
}
|
|
100
|
+
this.isFirstMessage = false;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const channel = data.topic;
|
|
104
|
+
if (channel && this.eventCallbacks[channel]) {
|
|
105
|
+
this.eventCallbacks[channel].forEach((cb) => cb(data));
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
if (this.errorCallbacks.length > 0) {
|
|
109
|
+
const errorEvent = { ...e instanceof Error ? e : { message: String(e) } };
|
|
110
|
+
this.errorCallbacks.forEach((cb) => cb(errorEvent));
|
|
111
|
+
} else {
|
|
112
|
+
throw e;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 再接続を試みる
|
|
119
|
+
*/
|
|
120
|
+
attemptReconnect() {
|
|
121
|
+
const maxAttempts = this.options.maxReconnectAttempts ?? Infinity;
|
|
122
|
+
if (this.reconnectAttempts >= maxAttempts) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this.reconnectAttempts++;
|
|
126
|
+
this.reconnectCallbacks.forEach((cb) => cb(this.reconnectAttempts));
|
|
127
|
+
const interval = this.options.reconnectInterval ?? 3e3;
|
|
128
|
+
this.reconnectTimer = setTimeout(() => {
|
|
129
|
+
this.isFirstMessage = true;
|
|
130
|
+
this._uid = null;
|
|
131
|
+
this.createConnection();
|
|
132
|
+
}, interval);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* WebSocket接続完了イベント登録
|
|
136
|
+
* @param callback 接続時に呼ばれるコールバック
|
|
137
|
+
*/
|
|
138
|
+
onConnect(callback) {
|
|
139
|
+
this.connectCallbacks.push(callback);
|
|
140
|
+
if (this._uid) {
|
|
141
|
+
callback(this._uid);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* WebSocket再接続イベント登録
|
|
146
|
+
* @param callback 再接続試行時に呼ばれるコールバック
|
|
147
|
+
*/
|
|
148
|
+
onReconnect(callback) {
|
|
149
|
+
this.reconnectCallbacks.push(callback);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* UID
|
|
153
|
+
*/
|
|
154
|
+
get uid() {
|
|
155
|
+
return this._uid;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* クライアントインスタンスを取得
|
|
159
|
+
*/
|
|
160
|
+
get client() {
|
|
161
|
+
return this._client;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 接続状態を取得
|
|
165
|
+
*/
|
|
166
|
+
get isConnected() {
|
|
167
|
+
return this._client.readyState === WS_OPEN;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* WebSocketエラーイベント登録
|
|
171
|
+
* @param callback エラー時に呼ばれるコールバック
|
|
172
|
+
*/
|
|
173
|
+
onError(callback) {
|
|
174
|
+
this.errorCallbacks.push(callback);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* WebSocketクローズイベント登録
|
|
178
|
+
* @param callback クローズ時に呼ばれるコールバック
|
|
179
|
+
*/
|
|
180
|
+
onClose(callback) {
|
|
181
|
+
this.onCloseCallback = callback;
|
|
182
|
+
}
|
|
183
|
+
on(channel, addressOrCallback, callback) {
|
|
184
|
+
const address = typeof addressOrCallback === "string" ? addressOrCallback : void 0;
|
|
185
|
+
const actualCallback = typeof addressOrCallback === "function" ? addressOrCallback : callback;
|
|
186
|
+
const channelPath = symbolChannelPaths[channel];
|
|
187
|
+
const subscribePath = typeof channelPath.subscribe === "function" ? channelPath.subscribe(address) : channelPath.subscribe;
|
|
188
|
+
if (!subscribePath) {
|
|
189
|
+
throw new Error(`Subscribe path could not be determined for channel: ${channel}`);
|
|
190
|
+
}
|
|
191
|
+
if (!this.eventCallbacks[subscribePath]) {
|
|
192
|
+
this.eventCallbacks[subscribePath] = [];
|
|
193
|
+
}
|
|
194
|
+
this.eventCallbacks[subscribePath].push(actualCallback);
|
|
195
|
+
if (!this._uid) {
|
|
196
|
+
this.pendingSubscribes.push({ subscribePath, callback: actualCallback });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (this._client.readyState === WS_OPEN) {
|
|
200
|
+
this._client.send(JSON.stringify({ uid: this._uid, subscribe: subscribePath }));
|
|
201
|
+
this.activeSubscriptions.add(subscribePath);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
off(channel, address) {
|
|
205
|
+
const channelPath = symbolChannelPaths[channel];
|
|
206
|
+
const subscribePath = typeof channelPath.subscribe === "function" ? channelPath.subscribe(address) : channelPath.subscribe;
|
|
207
|
+
if (!subscribePath) {
|
|
208
|
+
throw new Error(`Subscribe path could not be determined for channel: ${channel}`);
|
|
209
|
+
}
|
|
210
|
+
delete this.eventCallbacks[subscribePath];
|
|
211
|
+
this.activeSubscriptions.delete(subscribePath);
|
|
212
|
+
if (this._uid && this._client.readyState === WS_OPEN) {
|
|
213
|
+
this._client.send(JSON.stringify({ uid: this._uid, unsubscribe: subscribePath }));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* WebSocket接続を切断
|
|
218
|
+
*/
|
|
219
|
+
disconnect() {
|
|
220
|
+
this.isManualDisconnect = true;
|
|
221
|
+
if (this.reconnectTimer) {
|
|
222
|
+
clearTimeout(this.reconnectTimer);
|
|
223
|
+
this.reconnectTimer = null;
|
|
224
|
+
}
|
|
225
|
+
if (this.connectionTimeoutTimer) {
|
|
226
|
+
clearTimeout(this.connectionTimeoutTimer);
|
|
227
|
+
this.connectionTimeoutTimer = null;
|
|
228
|
+
}
|
|
229
|
+
this.eventCallbacks = {};
|
|
230
|
+
this.pendingSubscribes = [];
|
|
231
|
+
this.errorCallbacks = [];
|
|
232
|
+
this.connectCallbacks = [];
|
|
233
|
+
this.reconnectCallbacks = [];
|
|
234
|
+
this.activeSubscriptions.clear();
|
|
235
|
+
if (this._client.readyState === WS_OPEN || this._client.readyState === WS_CONNECTING) {
|
|
236
|
+
this._client.close();
|
|
237
|
+
}
|
|
238
|
+
this._uid = null;
|
|
239
|
+
this.isFirstMessage = true;
|
|
240
|
+
this.reconnectAttempts = 0;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
export {
|
|
244
|
+
SymbolWebSocket,
|
|
245
|
+
symbolChannelPaths
|
|
246
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nemnesia/symbol-websocket",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Symbol WebSocket Client Library",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"nem",
|
|
7
|
+
"symbol",
|
|
8
|
+
"blockchain",
|
|
9
|
+
"websocket",
|
|
10
|
+
"nodejs"
|
|
11
|
+
],
|
|
12
|
+
"author": "ccHarvestasya",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/nemnesia/symbol-tools.git",
|
|
17
|
+
"directory": "packages/symbol-websocket"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/nemnesia/symbol-tools/issues"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/nemnesia/symbol-tools/tree/main/packages/symbol-websocket#readme",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20.0.0"
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/index.cjs",
|
|
28
|
+
"module": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"require": {
|
|
36
|
+
"types": "./dist/index.d.cts",
|
|
37
|
+
"default": "./dist/index.cjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist/**/*",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE",
|
|
44
|
+
"CHANGELOG.md"
|
|
45
|
+
],
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@stomp/stompjs": "^7.2.1",
|
|
48
|
+
"isomorphic-ws": "^5.0.0",
|
|
49
|
+
"ws": "^8.18.3"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"volta": {
|
|
55
|
+
"node": "20.19.6"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "pnpm run clean && tsup",
|
|
59
|
+
"clean": "rm -rf dist",
|
|
60
|
+
"lint": "eslint \"{src,test,e2e}/**/*.ts\"",
|
|
61
|
+
"format": "prettier --write \"{src,test,e2e}/**/*.ts\"",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"test:watch": "vitest",
|
|
64
|
+
"test:coverage": "vitest run --coverage",
|
|
65
|
+
"test:ui": "vitest --ui --coverage",
|
|
66
|
+
"publish:dryrun": "npm publish --dry-run",
|
|
67
|
+
"publish:release": "npm run build && npm publish"
|
|
68
|
+
}
|
|
69
|
+
}
|