@laplace.live/ws 6.3.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 simon3000
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,294 @@
1
+ # bilibili-live-ws [![npm](https://img.shields.io/npm/v/bilibili-live-ws.svg)](https://www.npmjs.com/package/bilibili-live-ws) ![Node CI](https://github.com/simon300000/bilibili-live-ws/workflows/Node%20CI/badge.svg)
2
+
3
+ Bilibili 直播 WebSocket/TCP API
4
+
5
+ LiveWS/KeepLiveWS 支持浏览器 *(实验性)*
6
+
7
+ 应该支持 bilibili直播开放平台 <https://open-live.bilibili.com>
8
+
9
+ 注:如果在浏览器环境遇到问题,可以尝试手动指定引入 `bilibili-live-ws/browser`
10
+
11
+ ## API
12
+
13
+ ```javascript
14
+ const { LiveWS, LiveTCP, KeepLiveWS, KeepLiveTCP } = require('bilibili-live-ws')
15
+ const live = new LiveWS(roomid)
16
+ // 或
17
+ const live = new LiveTCP(roomid)
18
+ ```
19
+
20
+ 举个栗子:
21
+
22
+ ```javascript
23
+ const live = new LiveWS(14275133)
24
+
25
+ live.on('open', () => console.log('Connection is established'))
26
+ // Connection is established
27
+ live.on('live', () => {
28
+ live.on('heartbeat', console.log)
29
+ // 74185
30
+ })
31
+ ```
32
+
33
+ 或者用TCP (新功能):
34
+
35
+ ```javascript
36
+ const live = new LiveTCP(26283)
37
+
38
+ live.on('open', () => console.log('Connection is established'))
39
+ // Connection is established
40
+ live.on('live', () => {
41
+ live.on('heartbeat', console.log)
42
+ // 13928
43
+ })
44
+ ```
45
+
46
+ > 晚上做梦梦到一个白胡子的老爷爷和我说TCP更节约内存哦!
47
+
48
+ ## Class: LiveWS / LiveTCP / KeepLiveWS / KeepLiveTCP
49
+
50
+ (Keep)LiveWS 和 (Keep)LiveTCP 的大部分API基本上一样, 区别在本文末尾有注明
51
+
52
+ ### new LiveWS(roomid [, { address, protover, key, authBody, uid, buvid }])
53
+
54
+ ### new LiveTCP(roomid [, { host, port, protover, key, authBody, uid, buvid }])
55
+
56
+ - `roomid` 房间号
57
+
58
+ 比如 https://live.bilibili.com/14327465 中的 `14327465`
59
+
60
+ - `address` 可选, WebSocket连接的地址
61
+
62
+ 默认 `'wss://broadcastlv.chat.bilibili.com/sub'`
63
+
64
+ - `host` 可选, TCP连接的地址
65
+
66
+ 默认 `'broadcastlv.chat.bilibili.com'`
67
+
68
+ - `port` 可选, TCP连接的端口
69
+
70
+ 默认 `2243`
71
+
72
+ - `protover` 可选 (1, 2, 3)
73
+
74
+ 默认 `2`
75
+
76
+ * 1 (见 [#17](https://github.com/simon300000/bilibili-live-ws/issues/17))
77
+ * 2 (zlib.inflate)
78
+ * 3 (brotliDecompress)
79
+
80
+ - `uid` 可选, WS握手的 uid [#397](https://github.com/simon300000/bilibili-live-ws/issues/397)
81
+
82
+ - `key` 可选, WS握手的 Token [#397](https://github.com/simon300000/bilibili-live-ws/issues/397)
83
+
84
+ - `buvid` 可选, WS握手的 Token [#397](https://github.com/simon300000/bilibili-live-ws/issues/397)
85
+
86
+ - `authBody` 可选, 可以和 <https://open-live.bilibili.com/document/> 配合使用, 会覆盖掉 `protover` `roomid` `key` `uid` `buvid`. 如果是 `object` 会按照握手包编码,如果是 `string`/`Buffer` 会直接发送
87
+
88
+ #### live.on('open')
89
+
90
+ WebSocket连接上了
91
+
92
+ #### live.on('live')
93
+
94
+ 成功登入房间
95
+
96
+ #### live.on('heartbeat', (online) => ...)
97
+
98
+ 收到服务器心跳包,会在30秒之后自动发送心跳包。
99
+
100
+ - `online` 当前人气值
101
+
102
+ #### live.on('msg', (data) => ...)
103
+
104
+ - `data` 收到信息/弹幕/广播等
105
+
106
+ data的例子: (我simon3000送了一个辣条)
107
+
108
+ ```javascript
109
+ {
110
+ cmd: 'SEND_GIFT',
111
+ data: {
112
+ giftName: '辣条',
113
+ num: 1,
114
+ uname: 'simon3000',
115
+ face: 'http://i1.hdslb.com/bfs/face/c26b9f670b10599ad105e2a7fea4b5f21c0f0bcf.jpg',
116
+ guard_level: 0,
117
+ rcost: 2318827,
118
+ uid: 3499295,
119
+ top_list: [],
120
+ timestamp: 1555690631,
121
+ giftId: 1,
122
+ giftType: 0,
123
+ action: '喂食',
124
+ super: 0,
125
+ super_gift_num: 0,
126
+ price: 100,
127
+ rnd: '1555690616',
128
+ newMedal: 0,
129
+ newTitle: 0,
130
+ medal: [],
131
+ title: '',
132
+ beatId: '0',
133
+ biz_source: 'live',
134
+ metadata: '',
135
+ remain: 6,
136
+ gold: 0,
137
+ silver: 0,
138
+ eventScore: 0,
139
+ eventNum: 0,
140
+ smalltv_msg: [],
141
+ specialGift: null,
142
+ notice_msg: [],
143
+ capsule: null,
144
+ addFollow: 0,
145
+ effect_block: 1,
146
+ coin_type: 'silver',
147
+ total_coin: 100,
148
+ effect: 0,
149
+ tag_image: '',
150
+ user_count: 0
151
+ }
152
+ }
153
+ ```
154
+
155
+ #### live.on(cmd, (data) => ...)
156
+
157
+ 用法如上,只不过只会收到特定cmd的Event。
158
+
159
+ 比如 `live.on('DANMU_MSG')` 只会收到弹幕Event,一个弹幕Event的Data例子如下: (我simon3000发了个`233`)
160
+
161
+ ```javascript
162
+ {
163
+ cmd: 'DANMU_MSG',
164
+ info: [
165
+ [0, 1, 25, 16777215, 1555692037, 1555690616, 0, 'c5c630b1', 0, 0, 0],
166
+ '233',
167
+ [3499295, 'simon3000', 0, 0, 0, 10000, 1, ''],
168
+ [5, '財布', '神楽めあOfficial', 12235923, 5805790, ''],
169
+ [11, 0, 6406234, '>50000'],
170
+ ['title-58-1', 'title-58-1'],
171
+ 0,
172
+ 0,
173
+ null,
174
+ { ts: 1555692037, ct: '2277D56A' }
175
+ ]
176
+ }
177
+ ```
178
+
179
+
180
+
181
+ #### live.on('close')
182
+
183
+ 连接关闭事件
184
+
185
+ #### live.on('error', (e) => ...)
186
+
187
+ 连接 error 事件,连接同时也会关闭
188
+
189
+ #### live.heartbeat()
190
+
191
+ 无视30秒时间,直接发送一个心跳包。
192
+
193
+ 如果连接正常,会收到来自服务器的心跳包,也就是 `live.on('heartbeat')`,可以用于更新人气值。
194
+
195
+ #### live.close()
196
+
197
+ 关闭WebSocket/TCP Socket连接。
198
+
199
+ #### live.getOnline()
200
+
201
+ 立即调用 `live.heartbeat()` 刷新人气数值,并且返回 Promise,resolve 人气刷新后数值
202
+
203
+ #### live.on('message')
204
+
205
+ WebSocket/TCP收到的Raw Buffer(不推荐直接使用)
206
+
207
+ #### live.send(buffer)
208
+
209
+ 使用 WebSocket 或者 TCP 发送信息
210
+
211
+ ### getConf(roomid)
212
+
213
+ 选一个cdn,Resolve host, address 和 key, 可以直接放进(Keep)LiveWS/TCP设置
214
+
215
+ ```typescript
216
+ const { getConf } = require('bilibili-live-ws')
217
+
218
+ getConf(roomid)
219
+ // Return
220
+ Promise<{
221
+ key: string;
222
+ host: string;
223
+ address: string;
224
+ }>
225
+ ```
226
+
227
+ ### getRoomid(short)
228
+
229
+ 通过短房间号获取长房间号
230
+
231
+ ```typescript
232
+ const { getRoomid } = require('bilibili-live-ws')
233
+
234
+ getRoomid(255)
235
+ // Return Promise<number>: 48743
236
+ ```
237
+
238
+ <hr>
239
+
240
+ ### Keep和无Keep的区别
241
+
242
+ KeepLiveWS 和 KeepLiveTCP 有断线重新连接的功能
243
+
244
+ #### new KeepLiveWS(roomid [, { address, protover, key }])
245
+
246
+ #### new KeepLiveTCP(roomid [, { host, port, protover, key }])
247
+
248
+ 所有上方的API都是一样的, 只不过会有以下微小的区别
249
+
250
+ * 收到error或者close事件的时候并不代表连接关闭, 必须要手动调用`live.close()`来关闭连接
251
+ * 内部的连接对象(`LiveWS`/`LiveTCP`)在`live.connection`中
252
+ * 内部的连接关闭了之后, 如果不是因为`live.close()`关闭, 会在100毫秒之后自动打开一个新的连接。
253
+ * 这个100毫秒可以通过改变`live.interval`来设置
254
+ * 内部的boolean, `live.closed`, 用于判断是否是因为`live.close()`关闭。
255
+
256
+ <hr>
257
+
258
+ ### LiveWS 和 LiveTCP 的区别
259
+
260
+ #### LiveWS.ws
261
+
262
+ LiveWS 内部 WebSocket 对象,需要时可以直接操作
263
+
264
+ Doc: <https://github.com/websockets/ws/blob/master/doc/ws.md#class-websocket>
265
+
266
+ #### LiveTCP.socket
267
+
268
+ LiveTCP 内部 TCP Socket 对象,需要时可以直接操作
269
+
270
+ Doc: <https://nodejs.org/api/net.html#net_class_net_socket>
271
+
272
+ #### LiveTCP.buffer
273
+
274
+ LiveTCP内部TCP流的Buffer缓冲区,有不完整的包
275
+
276
+ #### LiveTCP.splitBuffer()
277
+
278
+ 处理LiveTCP内部TCP流的Buffer缓冲区,把其中完整的包交给decoder处理
279
+
280
+ ### BenchMark 简单对比
281
+
282
+ 在连接了640个直播间后过了一分钟左右(@2.0.0)
283
+
284
+ | | LiveWS (wss) | LiveWS (ws) | LiveTCP |
285
+ | -------- | ------------ | ----------- | ------- |
286
+ | 内存占用 | 63 MB | 26 MB | 18 MB |
287
+
288
+ <hr>
289
+
290
+ 参考资料: <https://github.com/lovelyyoshino/Bilibili-Live-API/blob/master/API.WebSocket.md>
291
+
292
+ # 贡献
293
+
294
+ 欢迎Issue和Pull Request!
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@laplace.live/ws",
3
+ "version": "6.3.1",
4
+ "description": "Bilibili Live WebSocket/TCP API",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "browser": "./src/browser.ts",
8
+ "exports": {
9
+ ".": {
10
+ "browser": "./src/browser.ts",
11
+ "default": "./src/index.ts"
12
+ },
13
+ "./browser": "./src/browser.ts"
14
+ },
15
+ "scripts": {
16
+ "check": "tsc --noEmit",
17
+ "test": "bun test --timeout 5000"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/laplace-live/ws.git"
22
+ },
23
+ "keywords": [
24
+ "bilibili",
25
+ "api",
26
+ "websocket",
27
+ "live",
28
+ "ws",
29
+ "tcp"
30
+ ],
31
+ "author": "simon3000 <simon3000@163.com>",
32
+ "license": "MIT",
33
+ "bugs": {
34
+ "url": "https://github.com/laplace-live/ws/issues"
35
+ },
36
+ "homepage": "https://github.com/laplace-live/ws#readme",
37
+ "publishConfig": {
38
+ "access": "public",
39
+ "provenance": true
40
+ },
41
+ "dependencies": {
42
+ "isomorphic-ws": "^5.0.0",
43
+ "ws": "^8.19.0"
44
+ },
45
+ "devDependencies": {
46
+ "@biomejs/biome": "^2.4.6",
47
+ "@types/bun": "^1.3.10",
48
+ "@types/pako": "^2.0.4",
49
+ "@types/ws": "^8.18.1",
50
+ "typescript": "^5.9.3"
51
+ },
52
+ "peerDependencies": {
53
+ "buffer": "^6.0.3",
54
+ "events": "^3.3.0",
55
+ "pako": "^2.1.0"
56
+ }
57
+ }
package/src/browser.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { KeepLive } from './common'
2
+ import { inflates } from './inflate/browser'
3
+ import { LiveWSBase, type WSOptions } from './ws'
4
+
5
+ export type { LiveOptions } from './common'
6
+ export type { WSOptions } from './ws'
7
+
8
+ export { relayEvent } from './common'
9
+
10
+ export class LiveWS extends LiveWSBase {
11
+ constructor(roomid: number, opts?: WSOptions) {
12
+ super(inflates, roomid, opts)
13
+ }
14
+ }
15
+
16
+ export class KeepLiveWS extends KeepLive<typeof LiveWSBase> {
17
+ constructor(roomid: number, opts?: WSOptions) {
18
+ super(LiveWSBase, inflates, roomid, opts)
19
+ }
20
+ }
package/src/buffer.ts ADDED
@@ -0,0 +1,82 @@
1
+ export type { Buffer } from 'buffer'
2
+ export type Inflates = { inflateAsync: (b: Buffer) => Buffer | Promise<Buffer>, brotliDecompressAsync: (b: Buffer) => Buffer | Promise<Buffer>, Buffer: typeof Buffer }
3
+
4
+ // https://github.com/lovelyyoshino/Bilibili-Live-API/blob/master/API.WebSocket.md
5
+
6
+ const cutBuffer = (buffer: Buffer) => {
7
+ const bufferPacks: Buffer[] = []
8
+ let size: number
9
+ for (let i = 0; i < buffer.length; i += size) {
10
+ size = buffer.readInt32BE(i)
11
+ bufferPacks.push(buffer.slice(i, i + size))
12
+ }
13
+ return bufferPacks
14
+ }
15
+
16
+ export const makeDecoder = ({ inflateAsync, brotliDecompressAsync }: Inflates) => {
17
+ const decoder = async (buffer: Buffer) => {
18
+ const packs = await Promise.all(cutBuffer(buffer)
19
+ .map(async buf => {
20
+ const body = buf.slice(16)
21
+ const protocol = buf.readInt16BE(6)
22
+ const operation = buf.readInt32BE(8)
23
+
24
+ let type = 'unknow'
25
+ if (operation === 3) {
26
+ type = 'heartbeat'
27
+ } else if (operation === 5) {
28
+ type = 'message'
29
+ } else if (operation === 8) {
30
+ type = 'welcome'
31
+ }
32
+
33
+ let data: any
34
+ if (protocol === 0) {
35
+ data = JSON.parse(String(body))
36
+ }
37
+ if (protocol === 1 && body.length === 4) {
38
+ data = body.readUIntBE(0, 4)
39
+ }
40
+ if (protocol === 2) {
41
+ data = await decoder(await inflateAsync(body))
42
+ }
43
+ if (protocol === 3) {
44
+ data = await decoder(await brotliDecompressAsync(body))
45
+ }
46
+
47
+ return { buf, type, protocol, data }
48
+ }))
49
+
50
+ return packs.flatMap(pack => {
51
+ if (pack.protocol === 2 || pack.protocol === 3) {
52
+ return pack.data as typeof packs
53
+ }
54
+ return pack
55
+ })
56
+ }
57
+
58
+ return decoder
59
+ }
60
+
61
+ type EncodeType = 'heartbeat' | 'join'
62
+
63
+ export const encoder = (type: EncodeType, { Buffer }: Inflates, body: any = '') => {
64
+ const blank = Buffer.alloc(16)
65
+ if (typeof body !== 'string') {
66
+ body = JSON.stringify(body)
67
+ }
68
+ const head = Buffer.from(blank)
69
+ const buffer = Buffer.from(body)
70
+
71
+ head.writeInt32BE(buffer.length + head.length, 0)
72
+ head.writeInt16BE(16, 4)
73
+ head.writeInt16BE(1, 6)
74
+ if (type === 'heartbeat') {
75
+ head.writeInt32BE(2, 8)
76
+ }
77
+ if (type === 'join') {
78
+ head.writeInt32BE(7, 8)
79
+ }
80
+ head.writeInt32BE(1, 12)
81
+ return Buffer.concat([head, buffer])
82
+ }
package/src/common.ts ADDED
@@ -0,0 +1,216 @@
1
+ import { EventEmitter } from 'events'
2
+
3
+ import { encoder, type Inflates, makeDecoder } from './buffer'
4
+
5
+ export type LiveOptions = { protover?: 1 | 2 | 3; key?: string; authBody?: any; uid?: number; buvid?: string }
6
+
7
+ export const relayEvent = Symbol('relay')
8
+
9
+ class NiceEventEmitter extends EventEmitter {
10
+ emit(eventName: string | symbol, ...params: any[]) {
11
+ super.emit(eventName, ...params)
12
+ super.emit(relayEvent, eventName, ...params)
13
+ return true
14
+ }
15
+ }
16
+
17
+ export class Live extends NiceEventEmitter {
18
+ roomid: number
19
+ online: number
20
+ live: boolean
21
+ closed: boolean
22
+ timeout: ReturnType<typeof setTimeout>
23
+
24
+ inflates: Inflates
25
+
26
+ send: (data: Buffer) => void
27
+ close: () => void
28
+
29
+ constructor(
30
+ inflates: Inflates,
31
+ roomid: number,
32
+ {
33
+ send,
34
+ close,
35
+ protover = 3,
36
+ key,
37
+ authBody,
38
+ uid = 0,
39
+ buvid,
40
+ }: { send: (data: Buffer) => void; close: () => void } & LiveOptions
41
+ ) {
42
+ if (typeof roomid !== 'number' || Number.isNaN(roomid)) {
43
+ throw new Error(`roomid ${roomid} must be Number not NaN`)
44
+ }
45
+
46
+ super()
47
+ this.inflates = inflates
48
+ this.roomid = roomid
49
+ this.online = 0
50
+ this.live = false
51
+ this.closed = false
52
+ this.timeout = setTimeout(() => {}, 0)
53
+
54
+ this.send = send
55
+ this.close = () => {
56
+ this.closed = true
57
+ close()
58
+ }
59
+
60
+ this.on('message', async buffer => {
61
+ const packs = await makeDecoder(inflates)(buffer)
62
+ packs.forEach(({ type, data }) => {
63
+ if (type === 'welcome') {
64
+ this.live = true
65
+ this.emit('live')
66
+ this.send(encoder('heartbeat', inflates))
67
+ }
68
+ if (type === 'heartbeat') {
69
+ this.online = data
70
+ clearTimeout(this.timeout)
71
+ this.timeout = setTimeout(() => this.heartbeat(), 1000 * 30)
72
+ this.emit('heartbeat', this.online)
73
+ }
74
+ if (type === 'message') {
75
+ this.emit('msg', data)
76
+ const cmd = data.cmd || (data.msg && data.msg.cmd)
77
+ if (cmd) {
78
+ if (cmd.includes('DANMU_MSG')) {
79
+ this.emit('DANMU_MSG', data)
80
+ } else {
81
+ this.emit(cmd, data)
82
+ }
83
+ }
84
+ }
85
+ })
86
+ })
87
+
88
+ this.on('open', () => {
89
+ if (authBody) {
90
+ if (typeof authBody === 'object') {
91
+ authBody = encoder('join', inflates, authBody)
92
+ }
93
+ this.send(authBody)
94
+ } else {
95
+ const hi: {
96
+ uid: number
97
+ roomid: number
98
+ protover: number
99
+ platform: string
100
+ type: number
101
+ key?: string
102
+ buvid?: string
103
+ } = { uid: uid, roomid, protover, platform: 'web', type: 2 }
104
+ if (key) {
105
+ hi.key = key
106
+ }
107
+ if (buvid) {
108
+ hi.buvid = buvid
109
+ }
110
+ const buf = encoder('join', inflates, hi)
111
+ this.send(buf)
112
+ }
113
+ })
114
+
115
+ this.on('close', () => {
116
+ clearTimeout(this.timeout)
117
+ })
118
+
119
+ this.on('_error', error => {
120
+ this.close()
121
+ this.emit('error', error)
122
+ })
123
+ }
124
+
125
+ heartbeat() {
126
+ this.send(encoder('heartbeat', this.inflates))
127
+ }
128
+
129
+ getOnline() {
130
+ this.heartbeat()
131
+ return new Promise<number>(resolve => this.once('heartbeat', resolve))
132
+ }
133
+ }
134
+
135
+ export class KeepLive<Base extends typeof Live> extends EventEmitter {
136
+ params: ConstructorParameters<Base>
137
+ closed: boolean
138
+ interval: number
139
+ timeout: number
140
+ connection: InstanceType<Base>
141
+ Base: Base
142
+
143
+ constructor(Base: Base, ...params: ConstructorParameters<Base>) {
144
+ super()
145
+ this.params = params
146
+ this.closed = false
147
+ this.interval = 100
148
+ this.timeout = 45 * 1000
149
+ this.connection = new (Base as any)(...this.params)
150
+ this.Base = Base
151
+ this.connect(false)
152
+ }
153
+
154
+ connect(reconnect = true) {
155
+ if (reconnect) {
156
+ this.connection.close()
157
+ this.connection = new (this.Base as any)(...this.params)
158
+ }
159
+ const connection = this.connection
160
+
161
+ let timeout = setTimeout(() => {
162
+ connection.close()
163
+ connection.emit('timeout')
164
+ }, this.timeout)
165
+
166
+ connection.on(relayEvent, (eventName: string, ...params: any[]) => {
167
+ if (eventName !== 'error') {
168
+ this.emit(eventName, ...params)
169
+ }
170
+ })
171
+
172
+ connection.on('error', e => this.emit('e', e))
173
+ connection.on('close', () => {
174
+ if (!this.closed) {
175
+ setTimeout(() => this.connect(), this.interval)
176
+ }
177
+ })
178
+
179
+ connection.on('heartbeat', () => {
180
+ clearTimeout(timeout)
181
+ timeout = setTimeout(() => {
182
+ connection.close()
183
+ connection.emit('timeout')
184
+ }, this.timeout)
185
+ })
186
+
187
+ connection.on('close', () => {
188
+ clearTimeout(timeout)
189
+ })
190
+ }
191
+
192
+ get online() {
193
+ return this.connection.online
194
+ }
195
+
196
+ get roomid() {
197
+ return this.connection.roomid
198
+ }
199
+
200
+ close() {
201
+ this.closed = true
202
+ this.connection.close()
203
+ }
204
+
205
+ heartbeat() {
206
+ return this.connection.heartbeat()
207
+ }
208
+
209
+ getOnline() {
210
+ return this.connection.getOnline()
211
+ }
212
+
213
+ send(data: Buffer) {
214
+ return this.connection.send(data)
215
+ }
216
+ }