@laplace.live/ws 7.0.0 → 7.0.2
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/README.md +15 -82
- package/dist/browser.d.ts +37 -2
- package/dist/browser.js +439 -361
- package/dist/index.d.ts +128 -2
- package/dist/index.js +225 -124
- package/dist/ws-D63GKJdI.d.ts +250 -0
- package/package.json +1 -1
- package/dist/ws-DVInVzNN.d.ts +0 -67
package/dist/browser.js
CHANGED
|
@@ -1,293 +1,3 @@
|
|
|
1
|
-
// src/buffer.ts
|
|
2
|
-
var textEncoder = new TextEncoder();
|
|
3
|
-
var textDecoder = new TextDecoder();
|
|
4
|
-
function concatUint8Arrays(arrs) {
|
|
5
|
-
let totalLength = 0;
|
|
6
|
-
for (const arr of arrs) totalLength += arr.length;
|
|
7
|
-
const result = new Uint8Array(totalLength);
|
|
8
|
-
let offset = 0;
|
|
9
|
-
for (const arr of arrs) {
|
|
10
|
-
result.set(arr, offset);
|
|
11
|
-
offset += arr.length;
|
|
12
|
-
}
|
|
13
|
-
return result;
|
|
14
|
-
}
|
|
15
|
-
var cutBuffer = (buffer) => {
|
|
16
|
-
const bufferPacks = [];
|
|
17
|
-
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
18
|
-
let size;
|
|
19
|
-
for (let i = 0; i < buffer.length; i += size) {
|
|
20
|
-
size = view.getInt32(i);
|
|
21
|
-
bufferPacks.push(buffer.slice(i, i + size));
|
|
22
|
-
}
|
|
23
|
-
return bufferPacks;
|
|
24
|
-
};
|
|
25
|
-
var makeDecoder = ({ inflateAsync: inflateAsync2, brotliDecompressAsync: brotliDecompressAsync2 }) => {
|
|
26
|
-
const decoder = async (buffer) => {
|
|
27
|
-
const packs = await Promise.all(
|
|
28
|
-
cutBuffer(buffer).map(async (buf) => {
|
|
29
|
-
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
30
|
-
const body = buf.slice(16);
|
|
31
|
-
const protocol = view.getInt16(6);
|
|
32
|
-
const operation = view.getInt32(8);
|
|
33
|
-
let type = "unknow";
|
|
34
|
-
if (operation === 3) {
|
|
35
|
-
type = "heartbeat";
|
|
36
|
-
} else if (operation === 5) {
|
|
37
|
-
type = "message";
|
|
38
|
-
} else if (operation === 8) {
|
|
39
|
-
type = "welcome";
|
|
40
|
-
}
|
|
41
|
-
let data;
|
|
42
|
-
if (protocol === 0) {
|
|
43
|
-
data = JSON.parse(textDecoder.decode(body));
|
|
44
|
-
}
|
|
45
|
-
if (protocol === 1 && body.length === 4) {
|
|
46
|
-
const bodyView = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
47
|
-
data = bodyView.getUint32(0);
|
|
48
|
-
}
|
|
49
|
-
if (protocol === 2) {
|
|
50
|
-
data = await decoder(await inflateAsync2(body));
|
|
51
|
-
}
|
|
52
|
-
if (protocol === 3) {
|
|
53
|
-
data = await decoder(await brotliDecompressAsync2(body));
|
|
54
|
-
}
|
|
55
|
-
return { buf, type, protocol, data };
|
|
56
|
-
})
|
|
57
|
-
);
|
|
58
|
-
return packs.flatMap((pack) => {
|
|
59
|
-
if (pack.protocol === 2 || pack.protocol === 3) {
|
|
60
|
-
return pack.data;
|
|
61
|
-
}
|
|
62
|
-
return pack;
|
|
63
|
-
});
|
|
64
|
-
};
|
|
65
|
-
return decoder;
|
|
66
|
-
};
|
|
67
|
-
var encoder = (type, body = "") => {
|
|
68
|
-
const encoded = typeof body === "string" ? body : JSON.stringify(body);
|
|
69
|
-
const head = new Uint8Array(16);
|
|
70
|
-
const headView = new DataView(head.buffer, head.byteOffset, head.byteLength);
|
|
71
|
-
const buffer = textEncoder.encode(encoded);
|
|
72
|
-
headView.setInt32(0, buffer.length + head.length);
|
|
73
|
-
headView.setInt16(4, 16);
|
|
74
|
-
headView.setInt16(6, 1);
|
|
75
|
-
if (type === "heartbeat") {
|
|
76
|
-
headView.setInt32(8, 2);
|
|
77
|
-
}
|
|
78
|
-
if (type === "join") {
|
|
79
|
-
headView.setInt32(8, 7);
|
|
80
|
-
}
|
|
81
|
-
headView.setInt32(12, 1);
|
|
82
|
-
return concatUint8Arrays([head, buffer]);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// src/common.ts
|
|
86
|
-
var DataEvent = class extends Event {
|
|
87
|
-
data;
|
|
88
|
-
constructor(type, data) {
|
|
89
|
-
super(type);
|
|
90
|
-
this.data = data;
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
var EventEvent = class extends Event {
|
|
94
|
-
event;
|
|
95
|
-
constructor(event) {
|
|
96
|
-
super("event");
|
|
97
|
-
this.event = event;
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
var Live = class extends EventTarget {
|
|
101
|
-
roomid;
|
|
102
|
-
online;
|
|
103
|
-
live;
|
|
104
|
-
closed;
|
|
105
|
-
timeout;
|
|
106
|
-
send;
|
|
107
|
-
close;
|
|
108
|
-
constructor(inflates2, roomid, {
|
|
109
|
-
send,
|
|
110
|
-
close,
|
|
111
|
-
protover = 3,
|
|
112
|
-
key,
|
|
113
|
-
authBody,
|
|
114
|
-
uid = 0,
|
|
115
|
-
buvid
|
|
116
|
-
}) {
|
|
117
|
-
if (typeof roomid !== "number" || Number.isNaN(roomid)) {
|
|
118
|
-
throw new Error(`roomid ${roomid} must be Number not NaN`);
|
|
119
|
-
}
|
|
120
|
-
super();
|
|
121
|
-
this.roomid = roomid;
|
|
122
|
-
this.online = 0;
|
|
123
|
-
this.live = false;
|
|
124
|
-
this.closed = false;
|
|
125
|
-
this.timeout = setTimeout(() => {
|
|
126
|
-
}, 0);
|
|
127
|
-
this.send = send;
|
|
128
|
-
this.close = () => {
|
|
129
|
-
this.closed = true;
|
|
130
|
-
close();
|
|
131
|
-
};
|
|
132
|
-
const decode = makeDecoder(inflates2);
|
|
133
|
-
this.addEventListener("message", async (e) => {
|
|
134
|
-
const buffer = e.data;
|
|
135
|
-
const packs = await decode(buffer);
|
|
136
|
-
packs.forEach(({ type, data }) => {
|
|
137
|
-
if (type === "welcome") {
|
|
138
|
-
this.live = true;
|
|
139
|
-
this.dispatchEvent(new Event("live"));
|
|
140
|
-
this.send(encoder("heartbeat"));
|
|
141
|
-
}
|
|
142
|
-
if (type === "heartbeat") {
|
|
143
|
-
this.online = data;
|
|
144
|
-
clearTimeout(this.timeout);
|
|
145
|
-
this.timeout = setTimeout(() => this.heartbeat(), 1e3 * 30);
|
|
146
|
-
this.dispatchEvent(new DataEvent("heartbeat", this.online));
|
|
147
|
-
}
|
|
148
|
-
if (type === "message") {
|
|
149
|
-
this.dispatchEvent(new DataEvent("msg", data));
|
|
150
|
-
const cmd = data.cmd || data.msg?.cmd;
|
|
151
|
-
if (cmd) {
|
|
152
|
-
if (cmd.includes("DANMU_MSG")) {
|
|
153
|
-
this.dispatchEvent(new DataEvent("DANMU_MSG", data));
|
|
154
|
-
} else {
|
|
155
|
-
this.dispatchEvent(new DataEvent(cmd, data));
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
this.addEventListener("open", () => {
|
|
162
|
-
if (authBody) {
|
|
163
|
-
this.send(authBody instanceof Uint8Array ? authBody : encoder("join", authBody));
|
|
164
|
-
} else {
|
|
165
|
-
const hi = { uid, roomid, protover, platform: "web", type: 2 };
|
|
166
|
-
if (key) {
|
|
167
|
-
hi.key = key;
|
|
168
|
-
}
|
|
169
|
-
if (buvid) {
|
|
170
|
-
hi.buvid = buvid;
|
|
171
|
-
}
|
|
172
|
-
const buf = encoder("join", hi);
|
|
173
|
-
this.send(buf);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
this.addEventListener("close", () => {
|
|
177
|
-
clearTimeout(this.timeout);
|
|
178
|
-
});
|
|
179
|
-
this.addEventListener("_error", () => {
|
|
180
|
-
this.close();
|
|
181
|
-
this.dispatchEvent(new Event("error"));
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
dispatchEvent(event) {
|
|
185
|
-
const result = super.dispatchEvent(event);
|
|
186
|
-
super.dispatchEvent(new EventEvent(event));
|
|
187
|
-
return result;
|
|
188
|
-
}
|
|
189
|
-
heartbeat() {
|
|
190
|
-
this.send(encoder("heartbeat"));
|
|
191
|
-
}
|
|
192
|
-
getOnline() {
|
|
193
|
-
this.heartbeat();
|
|
194
|
-
return new Promise(
|
|
195
|
-
(resolve) => this.addEventListener("heartbeat", (e) => resolve(e.data), { once: true })
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
on(type, listener, options) {
|
|
199
|
-
this.addEventListener(type, listener, options);
|
|
200
|
-
}
|
|
201
|
-
off(type, listener, options) {
|
|
202
|
-
this.removeEventListener(type, listener, options);
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
var KeepLive = class extends EventTarget {
|
|
206
|
-
params;
|
|
207
|
-
closed;
|
|
208
|
-
interval;
|
|
209
|
-
timeout;
|
|
210
|
-
connection;
|
|
211
|
-
Base;
|
|
212
|
-
constructor(Base, ...params) {
|
|
213
|
-
super();
|
|
214
|
-
this.params = params;
|
|
215
|
-
this.closed = false;
|
|
216
|
-
this.interval = 100;
|
|
217
|
-
this.timeout = 45 * 1e3;
|
|
218
|
-
this.connection = new Base(...this.params);
|
|
219
|
-
this.Base = Base;
|
|
220
|
-
this.connect(false);
|
|
221
|
-
}
|
|
222
|
-
dispatchEvent(event) {
|
|
223
|
-
const result = super.dispatchEvent(event);
|
|
224
|
-
super.dispatchEvent(new EventEvent(event));
|
|
225
|
-
return result;
|
|
226
|
-
}
|
|
227
|
-
connect(reconnect = true) {
|
|
228
|
-
if (reconnect) {
|
|
229
|
-
this.connection.close();
|
|
230
|
-
this.connection = new this.Base(...this.params);
|
|
231
|
-
}
|
|
232
|
-
const connection = this.connection;
|
|
233
|
-
let timeout = setTimeout(() => {
|
|
234
|
-
connection.close();
|
|
235
|
-
connection.dispatchEvent(new Event("timeout"));
|
|
236
|
-
}, this.timeout);
|
|
237
|
-
connection.addEventListener("event", (e) => {
|
|
238
|
-
const evt = e.event;
|
|
239
|
-
if (evt.type !== "error") {
|
|
240
|
-
if (evt instanceof DataEvent) {
|
|
241
|
-
this.dispatchEvent(new DataEvent(evt.type, evt.data));
|
|
242
|
-
} else {
|
|
243
|
-
this.dispatchEvent(new Event(evt.type));
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
connection.addEventListener("error", () => this.dispatchEvent(new Event("e")));
|
|
248
|
-
connection.addEventListener("close", () => {
|
|
249
|
-
if (!this.closed) {
|
|
250
|
-
setTimeout(() => this.connect(), this.interval);
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
connection.addEventListener("heartbeat", () => {
|
|
254
|
-
clearTimeout(timeout);
|
|
255
|
-
timeout = setTimeout(() => {
|
|
256
|
-
connection.close();
|
|
257
|
-
connection.dispatchEvent(new Event("timeout"));
|
|
258
|
-
}, this.timeout);
|
|
259
|
-
});
|
|
260
|
-
connection.addEventListener("close", () => {
|
|
261
|
-
clearTimeout(timeout);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
get online() {
|
|
265
|
-
return this.connection.online;
|
|
266
|
-
}
|
|
267
|
-
get roomid() {
|
|
268
|
-
return this.connection.roomid;
|
|
269
|
-
}
|
|
270
|
-
close() {
|
|
271
|
-
this.closed = true;
|
|
272
|
-
this.connection.close();
|
|
273
|
-
}
|
|
274
|
-
heartbeat() {
|
|
275
|
-
return this.connection.heartbeat();
|
|
276
|
-
}
|
|
277
|
-
getOnline() {
|
|
278
|
-
return this.connection.getOnline();
|
|
279
|
-
}
|
|
280
|
-
send(data) {
|
|
281
|
-
return this.connection.send(data);
|
|
282
|
-
}
|
|
283
|
-
on(type, listener, options) {
|
|
284
|
-
this.addEventListener(type, listener, options);
|
|
285
|
-
}
|
|
286
|
-
off(type, listener, options) {
|
|
287
|
-
this.removeEventListener(type, listener, options);
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
|
|
291
1
|
// src/inflate/browser.ts
|
|
292
2
|
import { inflate } from "pako";
|
|
293
3
|
|
|
@@ -1993,92 +1703,461 @@ var makeBrotliDecode = () => {
|
|
|
1993
1703
|
flipBuffer(dictionaryData);
|
|
1994
1704
|
setData(asReadOnlyBuffer(dictionaryData), dictionarySizeBits);
|
|
1995
1705
|
}
|
|
1996
|
-
function min(a, b) {
|
|
1997
|
-
return a <= b ? a : b;
|
|
1706
|
+
function min(a, b) {
|
|
1707
|
+
return a <= b ? a : b;
|
|
1708
|
+
}
|
|
1709
|
+
function copyBytes(dst, target, src, start, end) {
|
|
1710
|
+
dst.set(src.slice(start, end), target);
|
|
1711
|
+
}
|
|
1712
|
+
function readInput(src, dst, offset, length) {
|
|
1713
|
+
if (src == null) return -1;
|
|
1714
|
+
let end = min(src.offset + length, src.data.length);
|
|
1715
|
+
let bytesRead = end - src.offset;
|
|
1716
|
+
dst.set(src.data.subarray(src.offset, end), offset);
|
|
1717
|
+
src.offset += bytesRead;
|
|
1718
|
+
return bytesRead;
|
|
1719
|
+
}
|
|
1720
|
+
function closeInput(src) {
|
|
1721
|
+
return 0;
|
|
1722
|
+
}
|
|
1723
|
+
function asReadOnlyBuffer(src) {
|
|
1724
|
+
return src;
|
|
1725
|
+
}
|
|
1726
|
+
function isReadOnly(src) {
|
|
1727
|
+
return 1;
|
|
1728
|
+
}
|
|
1729
|
+
function isDirect(src) {
|
|
1730
|
+
return 1;
|
|
1731
|
+
}
|
|
1732
|
+
function flipBuffer(buffer) {
|
|
1733
|
+
}
|
|
1734
|
+
function toUsAsciiBytes(src) {
|
|
1735
|
+
let n = src.length;
|
|
1736
|
+
let result = new Int8Array(n);
|
|
1737
|
+
for (let i = 0; i < n; ++i) {
|
|
1738
|
+
result[i] = src.charCodeAt(i);
|
|
1739
|
+
}
|
|
1740
|
+
return result;
|
|
1741
|
+
}
|
|
1742
|
+
function decode(bytes, options) {
|
|
1743
|
+
let s = new State();
|
|
1744
|
+
initState(s, new InputStream(bytes));
|
|
1745
|
+
if (options) {
|
|
1746
|
+
let customDictionary = (
|
|
1747
|
+
/** @type {?Int8Array} */
|
|
1748
|
+
options["customDictionary"]
|
|
1749
|
+
);
|
|
1750
|
+
if (customDictionary) attachDictionaryChunk(s, customDictionary);
|
|
1751
|
+
}
|
|
1752
|
+
let totalOutput = 0;
|
|
1753
|
+
let chunks = [];
|
|
1754
|
+
while (true) {
|
|
1755
|
+
let chunk = new Int8Array(16384);
|
|
1756
|
+
chunks.push(chunk);
|
|
1757
|
+
s.output = chunk;
|
|
1758
|
+
s.outputOffset = 0;
|
|
1759
|
+
s.outputLength = 16384;
|
|
1760
|
+
s.outputUsed = 0;
|
|
1761
|
+
decompress(s);
|
|
1762
|
+
totalOutput += s.outputUsed;
|
|
1763
|
+
if (s.outputUsed < 16384) break;
|
|
1764
|
+
}
|
|
1765
|
+
close(s);
|
|
1766
|
+
let result = new Int8Array(totalOutput);
|
|
1767
|
+
let offset = 0;
|
|
1768
|
+
for (let i = 0; i < chunks.length; ++i) {
|
|
1769
|
+
let chunk = chunks[i];
|
|
1770
|
+
let end = min(totalOutput, offset + 16384);
|
|
1771
|
+
let len = end - offset;
|
|
1772
|
+
if (len < 16384) {
|
|
1773
|
+
result.set(chunk.subarray(0, len), offset);
|
|
1774
|
+
} else {
|
|
1775
|
+
result.set(chunk, offset);
|
|
1776
|
+
}
|
|
1777
|
+
offset += len;
|
|
1778
|
+
}
|
|
1779
|
+
return result;
|
|
1780
|
+
}
|
|
1781
|
+
return decode;
|
|
1782
|
+
};
|
|
1783
|
+
var BrotliDecode = makeBrotliDecode();
|
|
1784
|
+
|
|
1785
|
+
// src/inflate/browser.ts
|
|
1786
|
+
var inflateAsync = (d) => inflate(d);
|
|
1787
|
+
var brotliDecompressAsync = (d) => Uint8Array.from(BrotliDecode(Int8Array.from(d)));
|
|
1788
|
+
var inflates = { inflateAsync, brotliDecompressAsync };
|
|
1789
|
+
|
|
1790
|
+
// src/events.ts
|
|
1791
|
+
var LaplaceRawEvent = class extends Event {
|
|
1792
|
+
data;
|
|
1793
|
+
constructor(type, data) {
|
|
1794
|
+
super(type);
|
|
1795
|
+
this.data = data;
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
|
|
1799
|
+
// src/keep-live.ts
|
|
1800
|
+
var KeepLive = class extends EventTarget {
|
|
1801
|
+
/** @internal Stored constructor arguments for reconnection. */
|
|
1802
|
+
params;
|
|
1803
|
+
/** `true` after {@link close} has been called; prevents further reconnects. */
|
|
1804
|
+
closed;
|
|
1805
|
+
/** Delay in milliseconds before attempting a reconnect. */
|
|
1806
|
+
interval;
|
|
1807
|
+
/** Maximum milliseconds to wait for a heartbeat before forcing a reconnect. */
|
|
1808
|
+
timeout;
|
|
1809
|
+
/** The current underlying connection instance. */
|
|
1810
|
+
connection;
|
|
1811
|
+
/** @internal The base class constructor used to create new connections. */
|
|
1812
|
+
Base;
|
|
1813
|
+
constructor(Base, ...params) {
|
|
1814
|
+
super();
|
|
1815
|
+
this.params = params;
|
|
1816
|
+
this.closed = false;
|
|
1817
|
+
this.interval = 100;
|
|
1818
|
+
this.timeout = 45 * 1e3;
|
|
1819
|
+
this.connection = new Base(...this.params);
|
|
1820
|
+
this.Base = Base;
|
|
1821
|
+
this.connect(false);
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Overridden to also dispatch a `LaplaceRawEvent<Event>` with type `"event"`
|
|
1825
|
+
* for every event, enabling catch-all listeners.
|
|
1826
|
+
*/
|
|
1827
|
+
dispatchEvent(event) {
|
|
1828
|
+
const result = super.dispatchEvent(event);
|
|
1829
|
+
super.dispatchEvent(new LaplaceRawEvent("event", event));
|
|
1830
|
+
return result;
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
* Wire up event forwarding and timeout handling on the current connection.
|
|
1834
|
+
* When `reconnect` is `true`, the previous connection is closed and a fresh
|
|
1835
|
+
* one is created from the stored constructor parameters.
|
|
1836
|
+
*
|
|
1837
|
+
* @param reconnect - Whether to tear down and recreate the connection first.
|
|
1838
|
+
*/
|
|
1839
|
+
connect(reconnect = true) {
|
|
1840
|
+
if (reconnect) {
|
|
1841
|
+
this.connection.close();
|
|
1842
|
+
this.connection = new this.Base(...this.params);
|
|
1843
|
+
}
|
|
1844
|
+
const connection = this.connection;
|
|
1845
|
+
let timeout = setTimeout(() => {
|
|
1846
|
+
connection.close();
|
|
1847
|
+
connection.dispatchEvent(new Event("timeout"));
|
|
1848
|
+
}, this.timeout);
|
|
1849
|
+
connection.addEventListener("event", (e) => {
|
|
1850
|
+
const evt = e.data;
|
|
1851
|
+
if (evt.type !== "error") {
|
|
1852
|
+
if (evt instanceof LaplaceRawEvent) {
|
|
1853
|
+
this.dispatchEvent(new LaplaceRawEvent(evt.type, evt.data));
|
|
1854
|
+
} else {
|
|
1855
|
+
this.dispatchEvent(new Event(evt.type));
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
});
|
|
1859
|
+
connection.addEventListener("error", () => this.dispatchEvent(new Event("e")));
|
|
1860
|
+
connection.addEventListener("close", () => {
|
|
1861
|
+
if (!this.closed) {
|
|
1862
|
+
setTimeout(() => this.connect(), this.interval);
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
connection.addEventListener("heartbeat", () => {
|
|
1866
|
+
clearTimeout(timeout);
|
|
1867
|
+
timeout = setTimeout(() => {
|
|
1868
|
+
connection.close();
|
|
1869
|
+
connection.dispatchEvent(new Event("timeout"));
|
|
1870
|
+
}, this.timeout);
|
|
1871
|
+
});
|
|
1872
|
+
connection.addEventListener("close", () => {
|
|
1873
|
+
clearTimeout(timeout);
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
/** Latest known online viewer count from the underlying connection. */
|
|
1877
|
+
get online() {
|
|
1878
|
+
return this.connection.online;
|
|
1879
|
+
}
|
|
1880
|
+
/** The live room ID. */
|
|
1881
|
+
get roomid() {
|
|
1882
|
+
return this.connection.roomid;
|
|
1883
|
+
}
|
|
1884
|
+
/** Permanently close the connection and stop reconnecting. */
|
|
1885
|
+
close() {
|
|
1886
|
+
this.closed = true;
|
|
1887
|
+
this.connection.close();
|
|
1888
|
+
}
|
|
1889
|
+
/** Send a heartbeat packet to the server. */
|
|
1890
|
+
heartbeat() {
|
|
1891
|
+
return this.connection.heartbeat();
|
|
1998
1892
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
1893
|
+
/**
|
|
1894
|
+
* Send a heartbeat and resolve with the online viewer count from the
|
|
1895
|
+
* next heartbeat response.
|
|
1896
|
+
* @returns A promise that resolves with the current online count.
|
|
1897
|
+
*/
|
|
1898
|
+
getOnline() {
|
|
1899
|
+
return this.connection.getOnline();
|
|
2001
1900
|
}
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
return bytesRead;
|
|
1901
|
+
/**
|
|
1902
|
+
* Send raw binary data through the underlying connection.
|
|
1903
|
+
* @param data - The binary packet to send.
|
|
1904
|
+
*/
|
|
1905
|
+
send(data) {
|
|
1906
|
+
return this.connection.send(data);
|
|
2009
1907
|
}
|
|
2010
|
-
|
|
2011
|
-
|
|
1908
|
+
/**
|
|
1909
|
+
* Subscribe to an event type with a typed {@link LaplaceRawEvent} listener.
|
|
1910
|
+
*
|
|
1911
|
+
* @typeParam T - Expected data type carried by the event.
|
|
1912
|
+
* @param type - Event name (e.g. `"heartbeat"`, `"msg"`, `"DANMU_MSG"`).
|
|
1913
|
+
* @param listener - Callback receiving a {@link LaplaceRawEvent LaplaceRawEvent\<T\>}.
|
|
1914
|
+
* @param options - Standard `addEventListener` options.
|
|
1915
|
+
*/
|
|
1916
|
+
on(type, listener, options) {
|
|
1917
|
+
this.addEventListener(type, listener, options);
|
|
2012
1918
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
1919
|
+
/**
|
|
1920
|
+
* Unsubscribe a previously registered listener.
|
|
1921
|
+
*
|
|
1922
|
+
* @typeParam T - Data type matching the original subscription.
|
|
1923
|
+
* @param type - Event name.
|
|
1924
|
+
* @param listener - The same function reference passed to {@link on}.
|
|
1925
|
+
* @param options - Standard `removeEventListener` options.
|
|
1926
|
+
*/
|
|
1927
|
+
off(type, listener, options) {
|
|
1928
|
+
this.removeEventListener(type, listener, options);
|
|
2015
1929
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
1930
|
+
};
|
|
1931
|
+
|
|
1932
|
+
// src/buffer.ts
|
|
1933
|
+
var textEncoder = new TextEncoder();
|
|
1934
|
+
var textDecoder = new TextDecoder();
|
|
1935
|
+
function concatUint8Arrays(arrs) {
|
|
1936
|
+
let totalLength = 0;
|
|
1937
|
+
for (const arr of arrs) totalLength += arr.length;
|
|
1938
|
+
const result = new Uint8Array(totalLength);
|
|
1939
|
+
let offset = 0;
|
|
1940
|
+
for (const arr of arrs) {
|
|
1941
|
+
result.set(arr, offset);
|
|
1942
|
+
offset += arr.length;
|
|
2018
1943
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
1944
|
+
return result;
|
|
1945
|
+
}
|
|
1946
|
+
var cutBuffer = (buffer) => {
|
|
1947
|
+
const bufferPacks = [];
|
|
1948
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
1949
|
+
let size;
|
|
1950
|
+
for (let i = 0; i < buffer.length; i += size) {
|
|
1951
|
+
size = view.getInt32(i);
|
|
1952
|
+
bufferPacks.push(buffer.slice(i, i + size));
|
|
2021
1953
|
}
|
|
2022
|
-
|
|
1954
|
+
return bufferPacks;
|
|
1955
|
+
};
|
|
1956
|
+
var makeDecoder = ({ inflateAsync: inflateAsync2, brotliDecompressAsync: brotliDecompressAsync2 }) => {
|
|
1957
|
+
const decoder = async (buffer) => {
|
|
1958
|
+
const packs = await Promise.all(
|
|
1959
|
+
cutBuffer(buffer).map(async (buf) => {
|
|
1960
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
1961
|
+
const body = buf.slice(16);
|
|
1962
|
+
const protocol = view.getInt16(6);
|
|
1963
|
+
const operation = view.getInt32(8);
|
|
1964
|
+
let type = "unknow";
|
|
1965
|
+
if (operation === 3) {
|
|
1966
|
+
type = "heartbeat";
|
|
1967
|
+
} else if (operation === 5) {
|
|
1968
|
+
type = "message";
|
|
1969
|
+
} else if (operation === 8) {
|
|
1970
|
+
type = "welcome";
|
|
1971
|
+
}
|
|
1972
|
+
let data;
|
|
1973
|
+
if (protocol === 0) {
|
|
1974
|
+
data = JSON.parse(textDecoder.decode(body));
|
|
1975
|
+
}
|
|
1976
|
+
if (protocol === 1 && body.length === 4) {
|
|
1977
|
+
const bodyView = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
1978
|
+
data = bodyView.getUint32(0);
|
|
1979
|
+
}
|
|
1980
|
+
if (protocol === 2) {
|
|
1981
|
+
data = await decoder(await inflateAsync2(body));
|
|
1982
|
+
}
|
|
1983
|
+
if (protocol === 3) {
|
|
1984
|
+
data = await decoder(await brotliDecompressAsync2(body));
|
|
1985
|
+
}
|
|
1986
|
+
return { buf, type, protocol, data };
|
|
1987
|
+
})
|
|
1988
|
+
);
|
|
1989
|
+
return packs.flatMap((pack) => {
|
|
1990
|
+
if (pack.protocol === 2 || pack.protocol === 3) {
|
|
1991
|
+
return pack.data;
|
|
1992
|
+
}
|
|
1993
|
+
return pack;
|
|
1994
|
+
});
|
|
1995
|
+
};
|
|
1996
|
+
return decoder;
|
|
1997
|
+
};
|
|
1998
|
+
var encoder = (type, body = "") => {
|
|
1999
|
+
const encoded = typeof body === "string" ? body : JSON.stringify(body);
|
|
2000
|
+
const head = new Uint8Array(16);
|
|
2001
|
+
const headView = new DataView(head.buffer, head.byteOffset, head.byteLength);
|
|
2002
|
+
const buffer = textEncoder.encode(encoded);
|
|
2003
|
+
headView.setInt32(0, buffer.length + head.length);
|
|
2004
|
+
headView.setInt16(4, 16);
|
|
2005
|
+
headView.setInt16(6, 1);
|
|
2006
|
+
if (type === "heartbeat") {
|
|
2007
|
+
headView.setInt32(8, 2);
|
|
2023
2008
|
}
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
let result = new Int8Array(n);
|
|
2027
|
-
for (let i = 0; i < n; ++i) {
|
|
2028
|
-
result[i] = src.charCodeAt(i);
|
|
2029
|
-
}
|
|
2030
|
-
return result;
|
|
2009
|
+
if (type === "join") {
|
|
2010
|
+
headView.setInt32(8, 7);
|
|
2031
2011
|
}
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2012
|
+
headView.setInt32(12, 1);
|
|
2013
|
+
return concatUint8Arrays([head, buffer]);
|
|
2014
|
+
};
|
|
2015
|
+
|
|
2016
|
+
// src/live.ts
|
|
2017
|
+
var Live = class extends EventTarget {
|
|
2018
|
+
/** The live room ID this instance is connected to. */
|
|
2019
|
+
roomid;
|
|
2020
|
+
/** Latest known online viewer count, updated on each heartbeat. */
|
|
2021
|
+
online;
|
|
2022
|
+
/** `true` after the server acknowledges the join (`welcome` packet). */
|
|
2023
|
+
live;
|
|
2024
|
+
/** `true` after {@link close} has been called. */
|
|
2025
|
+
closed;
|
|
2026
|
+
/** @internal Handle for the heartbeat interval timer. */
|
|
2027
|
+
timeout;
|
|
2028
|
+
/** Send raw binary data over the underlying transport. */
|
|
2029
|
+
send;
|
|
2030
|
+
/** Gracefully close the connection and set {@link closed} to `true`. */
|
|
2031
|
+
close;
|
|
2032
|
+
constructor(inflates2, roomid, {
|
|
2033
|
+
send,
|
|
2034
|
+
close,
|
|
2035
|
+
protover = 3,
|
|
2036
|
+
key,
|
|
2037
|
+
authBody,
|
|
2038
|
+
uid = 0,
|
|
2039
|
+
buvid
|
|
2040
|
+
}) {
|
|
2041
|
+
if (typeof roomid !== "number" || Number.isNaN(roomid)) {
|
|
2042
|
+
throw new Error(`roomid ${roomid} must be Number not NaN`);
|
|
2054
2043
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2044
|
+
super();
|
|
2045
|
+
this.roomid = roomid;
|
|
2046
|
+
this.online = 0;
|
|
2047
|
+
this.live = false;
|
|
2048
|
+
this.closed = false;
|
|
2049
|
+
this.timeout = setTimeout(() => {
|
|
2050
|
+
}, 0);
|
|
2051
|
+
this.send = send;
|
|
2052
|
+
this.close = () => {
|
|
2053
|
+
this.closed = true;
|
|
2054
|
+
close();
|
|
2055
|
+
};
|
|
2056
|
+
const decode = makeDecoder(inflates2);
|
|
2057
|
+
this.addEventListener("message", async (e) => {
|
|
2058
|
+
const buffer = e.data;
|
|
2059
|
+
const packs = await decode(buffer);
|
|
2060
|
+
packs.forEach(({ type, data }) => {
|
|
2061
|
+
if (type === "welcome") {
|
|
2062
|
+
this.live = true;
|
|
2063
|
+
this.dispatchEvent(new Event("live"));
|
|
2064
|
+
this.send(encoder("heartbeat"));
|
|
2065
|
+
}
|
|
2066
|
+
if (type === "heartbeat") {
|
|
2067
|
+
this.online = data;
|
|
2068
|
+
clearTimeout(this.timeout);
|
|
2069
|
+
this.timeout = setTimeout(() => this.heartbeat(), 1e3 * 30);
|
|
2070
|
+
this.dispatchEvent(new LaplaceRawEvent("heartbeat", this.online));
|
|
2071
|
+
}
|
|
2072
|
+
if (type === "message") {
|
|
2073
|
+
this.dispatchEvent(new LaplaceRawEvent("msg", data));
|
|
2074
|
+
const cmd = data.cmd || data.msg?.cmd;
|
|
2075
|
+
if (cmd) {
|
|
2076
|
+
if (cmd.includes("DANMU_MSG")) {
|
|
2077
|
+
this.dispatchEvent(new LaplaceRawEvent("DANMU_MSG", data));
|
|
2078
|
+
} else {
|
|
2079
|
+
this.dispatchEvent(new LaplaceRawEvent(cmd, data));
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
});
|
|
2084
|
+
});
|
|
2085
|
+
this.addEventListener("open", () => {
|
|
2086
|
+
if (authBody) {
|
|
2087
|
+
this.send(authBody instanceof Uint8Array ? authBody : encoder("join", authBody));
|
|
2064
2088
|
} else {
|
|
2065
|
-
|
|
2089
|
+
const hi = { uid, roomid, protover, platform: "web", type: 2 };
|
|
2090
|
+
if (key) {
|
|
2091
|
+
hi.key = key;
|
|
2092
|
+
}
|
|
2093
|
+
if (buvid) {
|
|
2094
|
+
hi.buvid = buvid;
|
|
2095
|
+
}
|
|
2096
|
+
const buf = encoder("join", hi);
|
|
2097
|
+
this.send(buf);
|
|
2066
2098
|
}
|
|
2067
|
-
|
|
2068
|
-
|
|
2099
|
+
});
|
|
2100
|
+
this.addEventListener("close", () => {
|
|
2101
|
+
clearTimeout(this.timeout);
|
|
2102
|
+
});
|
|
2103
|
+
this.addEventListener("_error", () => {
|
|
2104
|
+
this.close();
|
|
2105
|
+
this.dispatchEvent(new Event("error"));
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* Overridden to also dispatch a `LaplaceRawEvent<Event>` with type `"event"`
|
|
2110
|
+
* for every event, enabling catch-all listeners.
|
|
2111
|
+
*/
|
|
2112
|
+
dispatchEvent(event) {
|
|
2113
|
+
const result = super.dispatchEvent(event);
|
|
2114
|
+
super.dispatchEvent(new LaplaceRawEvent("event", event));
|
|
2069
2115
|
return result;
|
|
2070
2116
|
}
|
|
2071
|
-
|
|
2117
|
+
/** Send a heartbeat packet to the server. */
|
|
2118
|
+
heartbeat() {
|
|
2119
|
+
this.send(encoder("heartbeat"));
|
|
2120
|
+
}
|
|
2121
|
+
/**
|
|
2122
|
+
* Send a heartbeat and resolve with the online viewer count from the
|
|
2123
|
+
* next heartbeat response.
|
|
2124
|
+
* @returns A promise that resolves with the current online count.
|
|
2125
|
+
*/
|
|
2126
|
+
getOnline() {
|
|
2127
|
+
this.heartbeat();
|
|
2128
|
+
return new Promise(
|
|
2129
|
+
(resolve) => this.addEventListener("heartbeat", (e) => resolve(e.data), { once: true })
|
|
2130
|
+
);
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Subscribe to an event type with a typed {@link LaplaceRawEvent} listener.
|
|
2134
|
+
* Convenience wrapper around {@link EventTarget.addEventListener}.
|
|
2135
|
+
*
|
|
2136
|
+
* @typeParam T - Expected data type carried by the event.
|
|
2137
|
+
* @param type - Event name (e.g. `"heartbeat"`, `"msg"`, `"DANMU_MSG"`).
|
|
2138
|
+
* @param listener - Callback receiving a {@link LaplaceRawEvent LaplaceRawEvent\<T\>}.
|
|
2139
|
+
* @param options - Standard `addEventListener` options.
|
|
2140
|
+
*/
|
|
2141
|
+
on(type, listener, options) {
|
|
2142
|
+
this.addEventListener(type, listener, options);
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Unsubscribe a previously registered listener.
|
|
2146
|
+
* Convenience wrapper around {@link EventTarget.removeEventListener}.
|
|
2147
|
+
*
|
|
2148
|
+
* @typeParam T - Data type matching the original subscription.
|
|
2149
|
+
* @param type - Event name.
|
|
2150
|
+
* @param listener - The same function reference passed to {@link on}.
|
|
2151
|
+
* @param options - Standard `removeEventListener` options.
|
|
2152
|
+
*/
|
|
2153
|
+
off(type, listener, options) {
|
|
2154
|
+
this.removeEventListener(type, listener, options);
|
|
2155
|
+
}
|
|
2072
2156
|
};
|
|
2073
|
-
var BrotliDecode = makeBrotliDecode();
|
|
2074
|
-
|
|
2075
|
-
// src/inflate/browser.ts
|
|
2076
|
-
var inflateAsync = (d) => inflate(d);
|
|
2077
|
-
var brotliDecompressAsync = (d) => Uint8Array.from(BrotliDecode(Int8Array.from(d)));
|
|
2078
|
-
var inflates = { inflateAsync, brotliDecompressAsync };
|
|
2079
2157
|
|
|
2080
2158
|
// src/ws.ts
|
|
2081
2159
|
var LiveWSBase = class extends Live {
|
|
2160
|
+
/** The underlying native WebSocket instance. */
|
|
2082
2161
|
ws;
|
|
2083
2162
|
constructor(inflates2, roomid, { address = "wss://broadcastlv.chat.bilibili.com/sub", ...options } = {}) {
|
|
2084
2163
|
const ws = new WebSocket(address);
|
|
@@ -2093,7 +2172,7 @@ var LiveWSBase = class extends Live {
|
|
|
2093
2172
|
ws.addEventListener("open", (e) => this.dispatchEvent(new Event(e.type)));
|
|
2094
2173
|
ws.addEventListener(
|
|
2095
2174
|
"message",
|
|
2096
|
-
(e) => this.dispatchEvent(new
|
|
2175
|
+
(e) => this.dispatchEvent(new LaplaceRawEvent("message", new Uint8Array(e.data)))
|
|
2097
2176
|
);
|
|
2098
2177
|
ws.addEventListener("close", (e) => this.dispatchEvent(new Event(e.type)));
|
|
2099
2178
|
ws.addEventListener("error", () => this.dispatchEvent(new Event("_error")));
|
|
@@ -2113,8 +2192,7 @@ var KeepLiveWS = class extends KeepLive {
|
|
|
2113
2192
|
}
|
|
2114
2193
|
};
|
|
2115
2194
|
export {
|
|
2116
|
-
DataEvent,
|
|
2117
|
-
EventEvent,
|
|
2118
2195
|
KeepLiveWS,
|
|
2196
|
+
LaplaceRawEvent,
|
|
2119
2197
|
LiveWS
|
|
2120
2198
|
};
|