@laplace.live/ws 6.3.3 → 6.3.5

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.
@@ -0,0 +1,53 @@
1
+ import { EventEmitter } from 'events';
2
+
3
+ type Inflates = {
4
+ inflateAsync: (b: Buffer) => Buffer | Promise<Buffer>;
5
+ brotliDecompressAsync: (b: Buffer) => Buffer | Promise<Buffer>;
6
+ Buffer: typeof Buffer;
7
+ };
8
+
9
+ type LiveOptions = {
10
+ protover?: 1 | 2 | 3;
11
+ key?: string;
12
+ authBody?: any;
13
+ uid?: number;
14
+ buvid?: string;
15
+ };
16
+ declare const relayEvent: unique symbol;
17
+ declare class NiceEventEmitter extends EventEmitter {
18
+ emit(eventName: string | symbol, ...params: any[]): boolean;
19
+ }
20
+ declare class Live extends NiceEventEmitter {
21
+ roomid: number;
22
+ online: number;
23
+ live: boolean;
24
+ closed: boolean;
25
+ timeout: ReturnType<typeof setTimeout>;
26
+ inflates: Inflates;
27
+ send: (data: Buffer) => void;
28
+ close: () => void;
29
+ constructor(inflates: Inflates, roomid: number, { send, close, protover, key, authBody, uid, buvid, }: {
30
+ send: (data: Buffer) => void;
31
+ close: () => void;
32
+ } & LiveOptions);
33
+ heartbeat(): void;
34
+ getOnline(): Promise<number>;
35
+ }
36
+ declare class KeepLive<Base extends typeof Live> extends EventEmitter {
37
+ params: ConstructorParameters<Base>;
38
+ closed: boolean;
39
+ interval: number;
40
+ timeout: number;
41
+ connection: InstanceType<Base>;
42
+ Base: Base;
43
+ constructor(Base: Base, ...params: ConstructorParameters<Base>);
44
+ connect(reconnect?: boolean): void;
45
+ get online(): number;
46
+ get roomid(): number;
47
+ close(): void;
48
+ heartbeat(): void;
49
+ getOnline(): Promise<number>;
50
+ send(data: Buffer): void;
51
+ }
52
+
53
+ export { type Inflates as I, KeepLive as K, Live as L, type LiveOptions as a, relayEvent as r };
@@ -0,0 +1,76 @@
1
+ import { L as Live, I as Inflates, a as LiveOptions, K as KeepLive } from './common-BGRg5uk7.js';
2
+ export { r as relayEvent } from './common-BGRg5uk7.js';
3
+ import { Socket } from 'node:net';
4
+ import { Agent } from 'node:http';
5
+ import { EventEmitter } from 'events';
6
+ import WS from 'ws';
7
+
8
+ type TCPOptions = LiveOptions & {
9
+ host?: string;
10
+ port?: number;
11
+ };
12
+ declare class LiveTCPBase extends Live {
13
+ socket: Socket;
14
+ buffer: Buffer;
15
+ i: number;
16
+ constructor(inflates: Inflates, roomid: number, { host, port, ...options }?: TCPOptions);
17
+ splitBuffer(): void;
18
+ }
19
+
20
+ type WSOptions = LiveOptions & {
21
+ address?: string;
22
+ agent?: Agent;
23
+ };
24
+ declare class WSWrapper extends EventEmitter {
25
+ ws: WS;
26
+ constructor(address: string, ...args: any[]);
27
+ get readyState(): 0 | 1 | 3 | 2;
28
+ send(data: Buffer): void;
29
+ close(code?: number, data?: string): void;
30
+ }
31
+ declare class LiveWSBase extends Live {
32
+ ws: WSWrapper;
33
+ constructor(inflates: Inflates, roomid: number, { address, agent, ...options }?: WSOptions);
34
+ }
35
+
36
+ type GET_DANMU_INFO = {
37
+ code: number;
38
+ message: string;
39
+ ttl: number;
40
+ data: {
41
+ business_id: number;
42
+ group: string;
43
+ host_list: {
44
+ host: string;
45
+ port: number;
46
+ wss_port: number;
47
+ ws_port: number;
48
+ }[];
49
+ max_delay: number;
50
+ refresh_rate: number;
51
+ refresh_row_factor: number;
52
+ token: string;
53
+ };
54
+ };
55
+ declare const getConf: (roomid: number) => Promise<{
56
+ key: string;
57
+ host: string;
58
+ address: string;
59
+ raw: GET_DANMU_INFO;
60
+ }>;
61
+ declare const getRoomid: (short: number) => Promise<any>;
62
+
63
+ declare class LiveWS extends LiveWSBase {
64
+ constructor(roomid: number, opts?: WSOptions);
65
+ }
66
+ declare class LiveTCP extends LiveTCPBase {
67
+ constructor(roomid: number, opts?: TCPOptions);
68
+ }
69
+ declare class KeepLiveWS extends KeepLive<typeof LiveWSBase> {
70
+ constructor(roomid: number, opts?: WSOptions);
71
+ }
72
+ declare class KeepLiveTCP extends KeepLive<typeof LiveTCPBase> {
73
+ constructor(roomid: number, opts?: TCPOptions);
74
+ }
75
+
76
+ export { KeepLiveTCP, KeepLiveWS, LiveOptions, LiveTCP, LiveWS, type TCPOptions, type WSOptions, getConf, getRoomid };
package/dist/index.js ADDED
@@ -0,0 +1,377 @@
1
+ // src/common.ts
2
+ import { EventEmitter } from "events";
3
+
4
+ // src/buffer.ts
5
+ var cutBuffer = (buffer) => {
6
+ const bufferPacks = [];
7
+ let size;
8
+ for (let i = 0; i < buffer.length; i += size) {
9
+ size = buffer.readInt32BE(i);
10
+ bufferPacks.push(buffer.slice(i, i + size));
11
+ }
12
+ return bufferPacks;
13
+ };
14
+ var makeDecoder = ({ inflateAsync: inflateAsync2, brotliDecompressAsync: brotliDecompressAsync2 }) => {
15
+ const decoder = async (buffer) => {
16
+ const packs = await Promise.all(cutBuffer(buffer).map(async (buf) => {
17
+ const body = buf.slice(16);
18
+ const protocol = buf.readInt16BE(6);
19
+ const operation = buf.readInt32BE(8);
20
+ let type = "unknow";
21
+ if (operation === 3) {
22
+ type = "heartbeat";
23
+ } else if (operation === 5) {
24
+ type = "message";
25
+ } else if (operation === 8) {
26
+ type = "welcome";
27
+ }
28
+ let data;
29
+ if (protocol === 0) {
30
+ data = JSON.parse(String(body));
31
+ }
32
+ if (protocol === 1 && body.length === 4) {
33
+ data = body.readUIntBE(0, 4);
34
+ }
35
+ if (protocol === 2) {
36
+ data = await decoder(await inflateAsync2(body));
37
+ }
38
+ if (protocol === 3) {
39
+ data = await decoder(await brotliDecompressAsync2(body));
40
+ }
41
+ return { buf, type, protocol, data };
42
+ }));
43
+ return packs.flatMap((pack) => {
44
+ if (pack.protocol === 2 || pack.protocol === 3) {
45
+ return pack.data;
46
+ }
47
+ return pack;
48
+ });
49
+ };
50
+ return decoder;
51
+ };
52
+ var encoder = (type, { Buffer: Buffer3 }, body = "") => {
53
+ const blank = Buffer3.alloc(16);
54
+ if (typeof body !== "string") {
55
+ body = JSON.stringify(body);
56
+ }
57
+ const head = Buffer3.from(blank);
58
+ const buffer = Buffer3.from(body);
59
+ head.writeInt32BE(buffer.length + head.length, 0);
60
+ head.writeInt16BE(16, 4);
61
+ head.writeInt16BE(1, 6);
62
+ if (type === "heartbeat") {
63
+ head.writeInt32BE(2, 8);
64
+ }
65
+ if (type === "join") {
66
+ head.writeInt32BE(7, 8);
67
+ }
68
+ head.writeInt32BE(1, 12);
69
+ return Buffer3.concat([head, buffer]);
70
+ };
71
+
72
+ // src/common.ts
73
+ var relayEvent = /* @__PURE__ */ Symbol("relay");
74
+ var NiceEventEmitter = class extends EventEmitter {
75
+ emit(eventName, ...params) {
76
+ super.emit(eventName, ...params);
77
+ super.emit(relayEvent, eventName, ...params);
78
+ return true;
79
+ }
80
+ };
81
+ var Live = class extends NiceEventEmitter {
82
+ roomid;
83
+ online;
84
+ live;
85
+ closed;
86
+ timeout;
87
+ inflates;
88
+ send;
89
+ close;
90
+ constructor(inflates2, roomid, {
91
+ send,
92
+ close,
93
+ protover = 3,
94
+ key,
95
+ authBody,
96
+ uid = 0,
97
+ buvid
98
+ }) {
99
+ if (typeof roomid !== "number" || Number.isNaN(roomid)) {
100
+ throw new Error(`roomid ${roomid} must be Number not NaN`);
101
+ }
102
+ super();
103
+ this.inflates = inflates2;
104
+ this.roomid = roomid;
105
+ this.online = 0;
106
+ this.live = false;
107
+ this.closed = false;
108
+ this.timeout = setTimeout(() => {
109
+ }, 0);
110
+ this.send = send;
111
+ this.close = () => {
112
+ this.closed = true;
113
+ close();
114
+ };
115
+ this.on("message", async (buffer) => {
116
+ const packs = await makeDecoder(inflates2)(buffer);
117
+ packs.forEach(({ type, data }) => {
118
+ if (type === "welcome") {
119
+ this.live = true;
120
+ this.emit("live");
121
+ this.send(encoder("heartbeat", inflates2));
122
+ }
123
+ if (type === "heartbeat") {
124
+ this.online = data;
125
+ clearTimeout(this.timeout);
126
+ this.timeout = setTimeout(() => this.heartbeat(), 1e3 * 30);
127
+ this.emit("heartbeat", this.online);
128
+ }
129
+ if (type === "message") {
130
+ this.emit("msg", data);
131
+ const cmd = data.cmd || data.msg && data.msg.cmd;
132
+ if (cmd) {
133
+ if (cmd.includes("DANMU_MSG")) {
134
+ this.emit("DANMU_MSG", data);
135
+ } else {
136
+ this.emit(cmd, data);
137
+ }
138
+ }
139
+ }
140
+ });
141
+ });
142
+ this.on("open", () => {
143
+ if (authBody) {
144
+ if (typeof authBody === "object") {
145
+ authBody = encoder("join", inflates2, authBody);
146
+ }
147
+ this.send(authBody);
148
+ } else {
149
+ const hi = { uid, roomid, protover, platform: "web", type: 2 };
150
+ if (key) {
151
+ hi.key = key;
152
+ }
153
+ if (buvid) {
154
+ hi.buvid = buvid;
155
+ }
156
+ const buf = encoder("join", inflates2, hi);
157
+ this.send(buf);
158
+ }
159
+ });
160
+ this.on("close", () => {
161
+ clearTimeout(this.timeout);
162
+ });
163
+ this.on("_error", (error) => {
164
+ this.close();
165
+ this.emit("error", error);
166
+ });
167
+ }
168
+ heartbeat() {
169
+ this.send(encoder("heartbeat", this.inflates));
170
+ }
171
+ getOnline() {
172
+ this.heartbeat();
173
+ return new Promise((resolve) => this.once("heartbeat", resolve));
174
+ }
175
+ };
176
+ var KeepLive = class extends EventEmitter {
177
+ params;
178
+ closed;
179
+ interval;
180
+ timeout;
181
+ connection;
182
+ Base;
183
+ constructor(Base, ...params) {
184
+ super();
185
+ this.params = params;
186
+ this.closed = false;
187
+ this.interval = 100;
188
+ this.timeout = 45 * 1e3;
189
+ this.connection = new Base(...this.params);
190
+ this.Base = Base;
191
+ this.connect(false);
192
+ }
193
+ connect(reconnect = true) {
194
+ if (reconnect) {
195
+ this.connection.close();
196
+ this.connection = new this.Base(...this.params);
197
+ }
198
+ const connection = this.connection;
199
+ let timeout = setTimeout(() => {
200
+ connection.close();
201
+ connection.emit("timeout");
202
+ }, this.timeout);
203
+ connection.on(relayEvent, (eventName, ...params) => {
204
+ if (eventName !== "error") {
205
+ this.emit(eventName, ...params);
206
+ }
207
+ });
208
+ connection.on("error", (e) => this.emit("e", e));
209
+ connection.on("close", () => {
210
+ if (!this.closed) {
211
+ setTimeout(() => this.connect(), this.interval);
212
+ }
213
+ });
214
+ connection.on("heartbeat", () => {
215
+ clearTimeout(timeout);
216
+ timeout = setTimeout(() => {
217
+ connection.close();
218
+ connection.emit("timeout");
219
+ }, this.timeout);
220
+ });
221
+ connection.on("close", () => {
222
+ clearTimeout(timeout);
223
+ });
224
+ }
225
+ get online() {
226
+ return this.connection.online;
227
+ }
228
+ get roomid() {
229
+ return this.connection.roomid;
230
+ }
231
+ close() {
232
+ this.closed = true;
233
+ this.connection.close();
234
+ }
235
+ heartbeat() {
236
+ return this.connection.heartbeat();
237
+ }
238
+ getOnline() {
239
+ return this.connection.getOnline();
240
+ }
241
+ send(data) {
242
+ return this.connection.send(data);
243
+ }
244
+ };
245
+
246
+ // src/inflate/node.ts
247
+ import { promisify } from "util";
248
+ import { brotliDecompress, inflate } from "zlib";
249
+ import { Buffer as Buffer2 } from "buffer";
250
+ var inflateAsync = promisify(inflate);
251
+ var brotliDecompressAsync = promisify(brotliDecompress);
252
+ var inflates = { inflateAsync, brotliDecompressAsync, Buffer: Buffer2 };
253
+
254
+ // src/tcp.ts
255
+ import net from "net";
256
+ var LiveTCPBase = class extends Live {
257
+ socket;
258
+ buffer;
259
+ i;
260
+ constructor(inflates2, roomid, { host = "broadcastlv.chat.bilibili.com", port = 2243, ...options } = {}) {
261
+ const socket = net.connect(port, host);
262
+ const send = (data) => {
263
+ socket.write(data);
264
+ };
265
+ const close = () => this.socket.end();
266
+ super(inflates2, roomid, { send, close, ...options });
267
+ this.i = 0;
268
+ this.buffer = Buffer.alloc(0);
269
+ socket.on("ready", () => this.emit("open"));
270
+ socket.on("close", () => this.emit("close"));
271
+ socket.on("error", (...params) => this.emit("_error", ...params));
272
+ socket.on("data", (buffer) => {
273
+ this.buffer = Buffer.concat([this.buffer, buffer]);
274
+ this.splitBuffer();
275
+ });
276
+ this.socket = socket;
277
+ }
278
+ splitBuffer() {
279
+ while (this.buffer.length >= 4 && this.buffer.readInt32BE(0) <= this.buffer.length) {
280
+ const size = this.buffer.readInt32BE(0);
281
+ const pack = this.buffer.slice(0, size);
282
+ this.buffer = this.buffer.slice(size);
283
+ this.i++;
284
+ if (this.i > 5) {
285
+ this.i = 0;
286
+ this.buffer = Buffer.from(this.buffer);
287
+ }
288
+ this.emit("message", pack);
289
+ }
290
+ }
291
+ };
292
+
293
+ // src/ws-node.ts
294
+ import { EventEmitter as EventEmitter2 } from "events";
295
+ import WS from "ws";
296
+ var WSWrapper = class extends EventEmitter2 {
297
+ ws;
298
+ constructor(address, ...args) {
299
+ super();
300
+ const ws = new WS(address, ...args);
301
+ this.ws = ws;
302
+ ws.onopen = () => this.emit("open");
303
+ ws.onmessage = ({ data }) => this.emit("message", data);
304
+ ws.onerror = () => this.emit("error");
305
+ ws.onclose = () => this.emit("close");
306
+ }
307
+ get readyState() {
308
+ return this.ws.readyState;
309
+ }
310
+ send(data) {
311
+ this.ws.send(data);
312
+ }
313
+ close(code, data) {
314
+ this.ws.close(code, data);
315
+ }
316
+ };
317
+ var LiveWSBase = class extends Live {
318
+ ws;
319
+ constructor(inflates2, roomid, { address = "wss://broadcastlv.chat.bilibili.com/sub", agent, ...options } = {}) {
320
+ const ws = new WSWrapper(address, { agent });
321
+ const send = (data) => {
322
+ if (ws.readyState === 1) {
323
+ ws.send(data);
324
+ }
325
+ };
326
+ const close = () => this.ws.close();
327
+ super(inflates2, roomid, { send, close, ...options });
328
+ ws.on("open", (...params) => this.emit("open", ...params));
329
+ ws.on("message", (data) => this.emit("message", data));
330
+ ws.on("close", (code, reason) => this.emit("close", code, reason));
331
+ ws.on("error", (error) => this.emit("_error", error));
332
+ this.ws = ws;
333
+ }
334
+ };
335
+
336
+ // src/extra.ts
337
+ var getConf = async (roomid) => {
338
+ const raw = await fetch(`https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=${roomid}`).then((w) => w.json());
339
+ const { data: { token: key, host_list: [{ host }] } } = raw;
340
+ const address = `wss://${host}/sub`;
341
+ return { key, host, address, raw };
342
+ };
343
+ var getRoomid = async (short) => {
344
+ const { data: { room_id } } = await fetch(`https://api.live.bilibili.com/room/v1/Room/mobileRoomInit?id=${short}`).then((w) => w.json());
345
+ return room_id;
346
+ };
347
+
348
+ // src/index.ts
349
+ var LiveWS = class extends LiveWSBase {
350
+ constructor(roomid, opts) {
351
+ super(inflates, roomid, opts);
352
+ }
353
+ };
354
+ var LiveTCP = class extends LiveTCPBase {
355
+ constructor(roomid, opts) {
356
+ super(inflates, roomid, opts);
357
+ }
358
+ };
359
+ var KeepLiveWS = class extends KeepLive {
360
+ constructor(roomid, opts) {
361
+ super(LiveWSBase, inflates, roomid, opts);
362
+ }
363
+ };
364
+ var KeepLiveTCP = class extends KeepLive {
365
+ constructor(roomid, opts) {
366
+ super(LiveTCPBase, inflates, roomid, opts);
367
+ }
368
+ };
369
+ export {
370
+ KeepLiveTCP,
371
+ KeepLiveWS,
372
+ LiveTCP,
373
+ LiveWS,
374
+ getConf,
375
+ getRoomid,
376
+ relayEvent
377
+ };
package/package.json CHANGED
@@ -1,22 +1,41 @@
1
1
  {
2
2
  "name": "@laplace.live/ws",
3
- "version": "6.3.3",
3
+ "version": "6.3.5",
4
4
  "description": "Bilibili Live WebSocket/TCP API",
5
5
  "type": "module",
6
- "main": "./src/index.ts",
7
- "browser": "./src/browser.ts",
6
+ "main": "./dist/index.js",
7
+ "browser": "./dist/browser.js",
8
+ "types": "./dist/index.d.ts",
8
9
  "exports": {
9
10
  ".": {
10
- "browser": "./src/browser.ts",
11
- "default": "./src/index.ts"
11
+ "browser": {
12
+ "types": "./dist/browser.d.ts",
13
+ "default": "./dist/browser.js"
14
+ },
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
12
17
  },
13
- "./server": "./src/index.ts",
14
- "./client": "./src/browser.ts",
15
- "./browser": "./src/browser.ts"
18
+ "./server": {
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "./client": {
23
+ "types": "./dist/browser.d.ts",
24
+ "default": "./dist/browser.js"
25
+ },
26
+ "./browser": {
27
+ "types": "./dist/browser.d.ts",
28
+ "default": "./dist/browser.js"
29
+ }
16
30
  },
31
+ "files": [
32
+ "dist"
33
+ ],
17
34
  "scripts": {
35
+ "build": "tsup",
18
36
  "check": "tsc --noEmit",
19
- "test": "bun test --timeout 5000"
37
+ "test": "bun test --timeout 5000",
38
+ "prepublishOnly": "bun run build"
20
39
  },
21
40
  "repository": {
22
41
  "type": "git",
@@ -41,6 +60,9 @@
41
60
  "provenance": true
42
61
  },
43
62
  "dependencies": {
63
+ "buffer": "^6.0.3",
64
+ "events": "^3.3.0",
65
+ "pako": "^2.1.0",
44
66
  "ws": "^8.19.0"
45
67
  },
46
68
  "devDependencies": {
@@ -48,11 +70,7 @@
48
70
  "@types/bun": "^1.3.10",
49
71
  "@types/pako": "^2.0.4",
50
72
  "@types/ws": "^8.18.1",
73
+ "tsup": "^8.5.1",
51
74
  "typescript": "^5.9.3"
52
- },
53
- "peerDependencies": {
54
- "buffer": "^6.0.3",
55
- "events": "^3.3.0",
56
- "pako": "^2.1.0"
57
75
  }
58
76
  }
package/src/browser.ts DELETED
@@ -1,20 +0,0 @@
1
- import { KeepLive } from './common.ts'
2
- import { inflates } from './inflate/browser.ts'
3
- import { LiveWSBase, type WSOptions } from './ws-client.ts'
4
-
5
- export type { LiveOptions } from './common.ts'
6
- export type { WSOptions } from './ws-client.ts'
7
-
8
- export { relayEvent } from './common.ts'
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 DELETED
@@ -1,82 +0,0 @@
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
- }