@tiktool/live 1.6.5 → 2.0.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/README.md +1 -1
- package/dist/index.d.mts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.js +195 -84
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +201 -83
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -146,7 +146,7 @@ All API requests require an API key. Get yours at [tik.tools](https://tik.tools)
|
|
|
146
146
|
|
|
147
147
|
| Tier | Rate Limit | WS Connections | Daily Requests | Price |
|
|
148
148
|
|------|-----------|---------------|---------------|-------|
|
|
149
|
-
| **Free** | 5/min |
|
|
149
|
+
| **Free** | 5/min | 1 | 2,500/day | Free |
|
|
150
150
|
| **Pro** | 120/min | 50 | 50,000/day | From $10/wk |
|
|
151
151
|
| **Ultra** | 300/min | 500 | 250,000/day | From $50/wk |
|
|
152
152
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
2
|
-
|
|
3
1
|
interface TikTokUser {
|
|
4
2
|
id: string;
|
|
5
3
|
nickname: string;
|
|
@@ -165,9 +163,20 @@ interface TikTokLiveOptions {
|
|
|
165
163
|
maxReconnectAttempts?: number;
|
|
166
164
|
heartbeatInterval?: number;
|
|
167
165
|
debug?: boolean;
|
|
166
|
+
/** Provide a custom WebSocket class (e.g. `ws` for Node < 22). Auto-resolved if omitted. */
|
|
167
|
+
webSocketImpl?: any;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
type Listener = (...args: any[]) => void;
|
|
171
|
+
declare class TypedEmitter {
|
|
172
|
+
private _listeners;
|
|
173
|
+
on(event: string, fn: Listener): this;
|
|
174
|
+
once(event: string, fn: Listener): this;
|
|
175
|
+
off(event: string, fn: Listener): this;
|
|
176
|
+
emit(event: string, ...args: any[]): boolean;
|
|
177
|
+
removeAllListeners(event?: string): this;
|
|
178
|
+
}
|
|
179
|
+
declare class TikTokLive extends TypedEmitter {
|
|
171
180
|
private ws;
|
|
172
181
|
private heartbeatTimer;
|
|
173
182
|
private reconnectAttempts;
|
|
@@ -183,6 +192,8 @@ declare class TikTokLive extends EventEmitter {
|
|
|
183
192
|
private readonly maxReconnectAttempts;
|
|
184
193
|
private readonly heartbeatInterval;
|
|
185
194
|
private readonly debug;
|
|
195
|
+
private readonly webSocketImpl?;
|
|
196
|
+
private WS;
|
|
186
197
|
constructor(options: TikTokLiveOptions);
|
|
187
198
|
connect(): Promise<void>;
|
|
188
199
|
disconnect(): void;
|
|
@@ -193,6 +204,7 @@ declare class TikTokLive extends EventEmitter {
|
|
|
193
204
|
once<K extends keyof TikTokLiveEvents>(event: K, listener: TikTokLiveEvents[K]): this;
|
|
194
205
|
off<K extends keyof TikTokLiveEvents>(event: K, listener: TikTokLiveEvents[K]): this;
|
|
195
206
|
emit<K extends keyof TikTokLiveEvents>(event: K, ...args: Parameters<TikTokLiveEvents[K]>): boolean;
|
|
207
|
+
private handleMessage;
|
|
196
208
|
private handleFrame;
|
|
197
209
|
private startHeartbeat;
|
|
198
210
|
private stopHeartbeat;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
2
|
-
|
|
3
1
|
interface TikTokUser {
|
|
4
2
|
id: string;
|
|
5
3
|
nickname: string;
|
|
@@ -165,9 +163,20 @@ interface TikTokLiveOptions {
|
|
|
165
163
|
maxReconnectAttempts?: number;
|
|
166
164
|
heartbeatInterval?: number;
|
|
167
165
|
debug?: boolean;
|
|
166
|
+
/** Provide a custom WebSocket class (e.g. `ws` for Node < 22). Auto-resolved if omitted. */
|
|
167
|
+
webSocketImpl?: any;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
type Listener = (...args: any[]) => void;
|
|
171
|
+
declare class TypedEmitter {
|
|
172
|
+
private _listeners;
|
|
173
|
+
on(event: string, fn: Listener): this;
|
|
174
|
+
once(event: string, fn: Listener): this;
|
|
175
|
+
off(event: string, fn: Listener): this;
|
|
176
|
+
emit(event: string, ...args: any[]): boolean;
|
|
177
|
+
removeAllListeners(event?: string): this;
|
|
178
|
+
}
|
|
179
|
+
declare class TikTokLive extends TypedEmitter {
|
|
171
180
|
private ws;
|
|
172
181
|
private heartbeatTimer;
|
|
173
182
|
private reconnectAttempts;
|
|
@@ -183,6 +192,8 @@ declare class TikTokLive extends EventEmitter {
|
|
|
183
192
|
private readonly maxReconnectAttempts;
|
|
184
193
|
private readonly heartbeatInterval;
|
|
185
194
|
private readonly debug;
|
|
195
|
+
private readonly webSocketImpl?;
|
|
196
|
+
private WS;
|
|
186
197
|
constructor(options: TikTokLiveOptions);
|
|
187
198
|
connect(): Promise<void>;
|
|
188
199
|
disconnect(): void;
|
|
@@ -193,6 +204,7 @@ declare class TikTokLive extends EventEmitter {
|
|
|
193
204
|
once<K extends keyof TikTokLiveEvents>(event: K, listener: TikTokLiveEvents[K]): this;
|
|
194
205
|
off<K extends keyof TikTokLiveEvents>(event: K, listener: TikTokLiveEvents[K]): this;
|
|
195
206
|
emit<K extends keyof TikTokLiveEvents>(event: K, ...args: Parameters<TikTokLiveEvents[K]>): boolean;
|
|
207
|
+
private handleMessage;
|
|
196
208
|
private handleFrame;
|
|
197
209
|
private startHeartbeat;
|
|
198
210
|
private stopHeartbeat;
|
package/dist/index.js
CHANGED
|
@@ -34,14 +34,28 @@ __export(index_exports, {
|
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
36
|
|
|
37
|
-
// src/client.ts
|
|
38
|
-
var import_events = require("events");
|
|
39
|
-
var http = __toESM(require("http"));
|
|
40
|
-
var https = __toESM(require("https"));
|
|
41
|
-
var zlib = __toESM(require("zlib"));
|
|
42
|
-
var import_ws = __toESM(require("ws"));
|
|
43
|
-
|
|
44
37
|
// src/proto.ts
|
|
38
|
+
var encoder = new TextEncoder();
|
|
39
|
+
var decoder = new TextDecoder();
|
|
40
|
+
function concatBytes(...arrays) {
|
|
41
|
+
let totalLength = 0;
|
|
42
|
+
for (const arr of arrays) totalLength += arr.length;
|
|
43
|
+
const result = new Uint8Array(totalLength);
|
|
44
|
+
let offset = 0;
|
|
45
|
+
for (const arr of arrays) {
|
|
46
|
+
result.set(arr, offset);
|
|
47
|
+
offset += arr.length;
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
function readInt32LE(buf, offset) {
|
|
52
|
+
return buf[offset] | buf[offset + 1] << 8 | buf[offset + 2] << 16 | buf[offset + 3] << 24;
|
|
53
|
+
}
|
|
54
|
+
function readBigInt64LE(buf, offset) {
|
|
55
|
+
const lo = BigInt(buf[offset] | buf[offset + 1] << 8 | buf[offset + 2] << 16 | buf[offset + 3] << 24 >>> 0);
|
|
56
|
+
const hi = BigInt(buf[offset + 4] | buf[offset + 5] << 8 | buf[offset + 6] << 16 | buf[offset + 7] << 24 >>> 0);
|
|
57
|
+
return hi << 32n | lo & 0xFFFFFFFFn;
|
|
58
|
+
}
|
|
45
59
|
function decodeVarint(buf, offset) {
|
|
46
60
|
let result = 0, shift = 0;
|
|
47
61
|
while (offset < buf.length) {
|
|
@@ -71,15 +85,15 @@ function encodeVarint(v) {
|
|
|
71
85
|
if (n > 0n) b |= 128;
|
|
72
86
|
bytes.push(b);
|
|
73
87
|
} while (n > 0n);
|
|
74
|
-
return
|
|
88
|
+
return new Uint8Array(bytes);
|
|
75
89
|
}
|
|
76
90
|
function encodeField(fn, wt, value) {
|
|
77
91
|
const tag = encodeVarint(fn << 3 | wt);
|
|
78
92
|
if (wt === 0) {
|
|
79
|
-
return
|
|
93
|
+
return concatBytes(tag, encodeVarint(typeof value === "number" ? BigInt(value) : value));
|
|
80
94
|
}
|
|
81
|
-
const data = typeof value === "string" ?
|
|
82
|
-
return
|
|
95
|
+
const data = typeof value === "string" ? encoder.encode(value) : value;
|
|
96
|
+
return concatBytes(tag, encodeVarint(data.length), data);
|
|
83
97
|
}
|
|
84
98
|
function decodeProto(buf) {
|
|
85
99
|
const fields = [];
|
|
@@ -100,10 +114,10 @@ function decodeProto(buf) {
|
|
|
100
114
|
offset += lenR.value;
|
|
101
115
|
fields.push({ fn, wt, value: data });
|
|
102
116
|
} else if (wt === 1) {
|
|
103
|
-
fields.push({ fn, wt, value:
|
|
117
|
+
fields.push({ fn, wt, value: readBigInt64LE(buf, offset) });
|
|
104
118
|
offset += 8;
|
|
105
119
|
} else if (wt === 5) {
|
|
106
|
-
fields.push({ fn, wt, value: BigInt(
|
|
120
|
+
fields.push({ fn, wt, value: BigInt(readInt32LE(buf, offset)) });
|
|
107
121
|
offset += 4;
|
|
108
122
|
} else {
|
|
109
123
|
break;
|
|
@@ -113,7 +127,7 @@ function decodeProto(buf) {
|
|
|
113
127
|
}
|
|
114
128
|
function getStr(fields, fn) {
|
|
115
129
|
const f = fields.find((x) => x.fn === fn && x.wt === 2);
|
|
116
|
-
return f ? f.value
|
|
130
|
+
return f ? decoder.decode(f.value) : "";
|
|
117
131
|
}
|
|
118
132
|
function getBytes(fields, fn) {
|
|
119
133
|
const f = fields.find((x) => x.fn === fn && x.wt === 2);
|
|
@@ -128,33 +142,33 @@ function getAllBytes(fields, fn) {
|
|
|
128
142
|
}
|
|
129
143
|
function buildHeartbeat(roomId) {
|
|
130
144
|
const payload = encodeField(1, 0, BigInt(roomId));
|
|
131
|
-
return
|
|
145
|
+
return concatBytes(
|
|
132
146
|
encodeField(6, 2, "pb"),
|
|
133
147
|
encodeField(7, 2, "hb"),
|
|
134
148
|
encodeField(8, 2, payload)
|
|
135
|
-
|
|
149
|
+
);
|
|
136
150
|
}
|
|
137
151
|
function buildImEnterRoom(roomId) {
|
|
138
|
-
const inner =
|
|
152
|
+
const inner = concatBytes(
|
|
139
153
|
encodeField(1, 0, BigInt(roomId)),
|
|
140
154
|
encodeField(4, 0, 12n),
|
|
141
155
|
encodeField(5, 2, "audience"),
|
|
142
156
|
encodeField(6, 2, ""),
|
|
143
157
|
encodeField(9, 2, ""),
|
|
144
158
|
encodeField(10, 2, "")
|
|
145
|
-
|
|
146
|
-
return
|
|
159
|
+
);
|
|
160
|
+
return concatBytes(
|
|
147
161
|
encodeField(6, 2, "pb"),
|
|
148
162
|
encodeField(7, 2, "im_enter_room"),
|
|
149
163
|
encodeField(8, 2, inner)
|
|
150
|
-
|
|
164
|
+
);
|
|
151
165
|
}
|
|
152
166
|
function buildAck(id) {
|
|
153
|
-
return
|
|
167
|
+
return concatBytes(
|
|
154
168
|
encodeField(2, 0, id),
|
|
155
169
|
encodeField(6, 2, "pb"),
|
|
156
170
|
encodeField(7, 2, "ack")
|
|
157
|
-
|
|
171
|
+
);
|
|
158
172
|
}
|
|
159
173
|
function parseUser(data) {
|
|
160
174
|
const f = decodeProto(data);
|
|
@@ -167,7 +181,7 @@ function parseUser(data) {
|
|
|
167
181
|
try {
|
|
168
182
|
const avatarFields = decodeProto(avatarBuf);
|
|
169
183
|
const urlBufs = getAllBytes(avatarFields, 1);
|
|
170
|
-
if (urlBufs.length > 0) profilePicture = urlBufs[0]
|
|
184
|
+
if (urlBufs.length > 0) profilePicture = decoder.decode(urlBufs[0]);
|
|
171
185
|
} catch {
|
|
172
186
|
}
|
|
173
187
|
}
|
|
@@ -484,27 +498,47 @@ function parseWebcastResponse(payload) {
|
|
|
484
498
|
// src/client.ts
|
|
485
499
|
var DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
486
500
|
var DEFAULT_SIGN_SERVER = "https://api.tik.tools";
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
501
|
+
var TypedEmitter = class {
|
|
502
|
+
_listeners = /* @__PURE__ */ new Map();
|
|
503
|
+
on(event, fn) {
|
|
504
|
+
const arr = this._listeners.get(event) || [];
|
|
505
|
+
arr.push(fn);
|
|
506
|
+
this._listeners.set(event, arr);
|
|
507
|
+
return this;
|
|
508
|
+
}
|
|
509
|
+
once(event, fn) {
|
|
510
|
+
const wrapper = (...args) => {
|
|
511
|
+
this.off(event, wrapper);
|
|
512
|
+
fn(...args);
|
|
513
|
+
};
|
|
514
|
+
return this.on(event, wrapper);
|
|
515
|
+
}
|
|
516
|
+
off(event, fn) {
|
|
517
|
+
const arr = this._listeners.get(event);
|
|
518
|
+
if (arr) {
|
|
519
|
+
this._listeners.set(event, arr.filter((l) => l !== fn));
|
|
520
|
+
}
|
|
521
|
+
return this;
|
|
522
|
+
}
|
|
523
|
+
emit(event, ...args) {
|
|
524
|
+
const arr = this._listeners.get(event);
|
|
525
|
+
if (!arr || arr.length === 0) return false;
|
|
526
|
+
for (const fn of [...arr]) fn(...args);
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
removeAllListeners(event) {
|
|
530
|
+
if (event) this._listeners.delete(event);
|
|
531
|
+
else this._listeners.clear();
|
|
532
|
+
return this;
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
function gunzipSync(data) {
|
|
536
|
+
try {
|
|
537
|
+
const zlib = require("zlib");
|
|
538
|
+
return new Uint8Array(zlib.gunzipSync(data));
|
|
539
|
+
} catch {
|
|
540
|
+
return data;
|
|
541
|
+
}
|
|
508
542
|
}
|
|
509
543
|
function getWsHost(clusterRegion) {
|
|
510
544
|
if (!clusterRegion) return "webcast-ws.tiktok.com";
|
|
@@ -513,7 +547,21 @@ function getWsHost(clusterRegion) {
|
|
|
513
547
|
if (r.startsWith("us") || r.includes("us")) return "webcast-ws.us.tiktok.com";
|
|
514
548
|
return "webcast-ws.tiktok.com";
|
|
515
549
|
}
|
|
516
|
-
|
|
550
|
+
async function resolveWebSocket(userImpl) {
|
|
551
|
+
if (userImpl) return userImpl;
|
|
552
|
+
if (typeof globalThis.WebSocket !== "undefined") {
|
|
553
|
+
return globalThis.WebSocket;
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
const ws = await import("ws");
|
|
557
|
+
return ws.default || ws;
|
|
558
|
+
} catch {
|
|
559
|
+
throw new Error(
|
|
560
|
+
'No WebSocket implementation found. Either use Node.js 22+ (native WebSocket), Cloudflare Workers, or install the "ws" package: npm i ws'
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
var TikTokLive = class extends TypedEmitter {
|
|
517
565
|
ws = null;
|
|
518
566
|
heartbeatTimer = null;
|
|
519
567
|
reconnectAttempts = 0;
|
|
@@ -521,7 +569,6 @@ var TikTokLive = class extends import_events.EventEmitter {
|
|
|
521
569
|
_connected = false;
|
|
522
570
|
_eventCount = 0;
|
|
523
571
|
_roomId = "";
|
|
524
|
-
// Cache host identities from battle events for enriching battleArmies
|
|
525
572
|
_battleHosts = /* @__PURE__ */ new Map();
|
|
526
573
|
uniqueId;
|
|
527
574
|
signServerUrl;
|
|
@@ -530,6 +577,8 @@ var TikTokLive = class extends import_events.EventEmitter {
|
|
|
530
577
|
maxReconnectAttempts;
|
|
531
578
|
heartbeatInterval;
|
|
532
579
|
debug;
|
|
580
|
+
webSocketImpl;
|
|
581
|
+
WS;
|
|
533
582
|
constructor(options) {
|
|
534
583
|
super();
|
|
535
584
|
this.uniqueId = options.uniqueId.replace(/^@/, "");
|
|
@@ -540,24 +589,40 @@ var TikTokLive = class extends import_events.EventEmitter {
|
|
|
540
589
|
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
|
|
541
590
|
this.heartbeatInterval = options.heartbeatInterval ?? 1e4;
|
|
542
591
|
this.debug = options.debug ?? false;
|
|
592
|
+
this.webSocketImpl = options.webSocketImpl;
|
|
543
593
|
}
|
|
544
594
|
async connect() {
|
|
545
595
|
this.intentionalClose = false;
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
596
|
+
if (!this.WS) {
|
|
597
|
+
this.WS = await resolveWebSocket(this.webSocketImpl);
|
|
598
|
+
}
|
|
599
|
+
const resp = await fetch(`https://www.tiktok.com/@${this.uniqueId}/live`, {
|
|
600
|
+
headers: {
|
|
601
|
+
"User-Agent": DEFAULT_UA,
|
|
602
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
603
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
604
|
+
},
|
|
605
|
+
redirect: "follow"
|
|
551
606
|
});
|
|
552
607
|
let ttwid = "";
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
608
|
+
const setCookies = resp.headers.get("set-cookie") || "";
|
|
609
|
+
for (const part of setCookies.split(",")) {
|
|
610
|
+
const trimmed = part.trim();
|
|
611
|
+
if (trimmed.startsWith("ttwid=")) {
|
|
612
|
+
ttwid = trimmed.split(";")[0].split("=").slice(1).join("=");
|
|
556
613
|
break;
|
|
557
614
|
}
|
|
558
615
|
}
|
|
616
|
+
if (!ttwid && typeof resp.headers.getSetCookie === "function") {
|
|
617
|
+
for (const sc of resp.headers.getSetCookie()) {
|
|
618
|
+
if (typeof sc === "string" && sc.startsWith("ttwid=")) {
|
|
619
|
+
ttwid = sc.split(";")[0].split("=").slice(1).join("=");
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
559
624
|
if (!ttwid) throw new Error("Failed to obtain session cookie");
|
|
560
|
-
const html = resp.
|
|
625
|
+
const html = await resp.text();
|
|
561
626
|
let roomId = "";
|
|
562
627
|
const sigiMatch = html.match(/id="SIGI_STATE"[^>]*>([^<]+)/);
|
|
563
628
|
if (sigiMatch) {
|
|
@@ -585,7 +650,7 @@ var TikTokLive = class extends import_events.EventEmitter {
|
|
|
585
650
|
browser_name: "Mozilla",
|
|
586
651
|
browser_version: DEFAULT_UA.split("Mozilla/")[1] || "5.0",
|
|
587
652
|
browser_online: "true",
|
|
588
|
-
tz_name:
|
|
653
|
+
tz_name: "Etc/UTC",
|
|
589
654
|
app_name: "tiktok_web",
|
|
590
655
|
sup_ws_ds_opt: "1",
|
|
591
656
|
update_version_code: "2.0.0",
|
|
@@ -626,18 +691,27 @@ var TikTokLive = class extends import_events.EventEmitter {
|
|
|
626
691
|
wsUrl = rawWsUrl.replace(/^https:\/\//, "wss://");
|
|
627
692
|
}
|
|
628
693
|
return new Promise((resolve, reject) => {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
694
|
+
const connUrl = wsUrl + (wsUrl.includes("?") ? "&" : "?") + `ttwid=${ttwid}`;
|
|
695
|
+
try {
|
|
696
|
+
this.ws = new this.WS(connUrl, {
|
|
697
|
+
headers: {
|
|
698
|
+
"User-Agent": DEFAULT_UA,
|
|
699
|
+
"Cookie": `ttwid=${ttwid}`,
|
|
700
|
+
"Origin": "https://www.tiktok.com"
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
} catch {
|
|
704
|
+
this.ws = new this.WS(connUrl);
|
|
705
|
+
}
|
|
706
|
+
const ws = this.ws;
|
|
707
|
+
let settled = false;
|
|
708
|
+
ws.onopen = () => {
|
|
637
709
|
this._connected = true;
|
|
638
710
|
this.reconnectAttempts = 0;
|
|
639
|
-
|
|
640
|
-
|
|
711
|
+
const hb = buildHeartbeat(roomId);
|
|
712
|
+
const enter = buildImEnterRoom(roomId);
|
|
713
|
+
ws.send(hb.buffer.byteLength === hb.length ? hb.buffer : hb.buffer.slice(hb.byteOffset, hb.byteOffset + hb.byteLength));
|
|
714
|
+
ws.send(enter.buffer.byteLength === enter.length ? enter.buffer : enter.buffer.slice(enter.byteOffset, enter.byteOffset + enter.byteLength));
|
|
641
715
|
this.startHeartbeat(roomId);
|
|
642
716
|
const roomInfo = {
|
|
643
717
|
roomId,
|
|
@@ -647,26 +721,41 @@ var TikTokLive = class extends import_events.EventEmitter {
|
|
|
647
721
|
};
|
|
648
722
|
this.emit("connected");
|
|
649
723
|
this.emit("roomInfo", roomInfo);
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
}
|
|
655
|
-
|
|
724
|
+
if (!settled) {
|
|
725
|
+
settled = true;
|
|
726
|
+
resolve();
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
ws.onmessage = (event) => {
|
|
730
|
+
const raw = event.data !== void 0 ? event.data : event;
|
|
731
|
+
this.handleMessage(raw);
|
|
732
|
+
};
|
|
733
|
+
ws.onclose = (event) => {
|
|
656
734
|
this._connected = false;
|
|
657
735
|
this.stopHeartbeat();
|
|
658
|
-
const
|
|
659
|
-
|
|
736
|
+
const code = event?.code ?? 1006;
|
|
737
|
+
const reason = event?.reason ?? "";
|
|
738
|
+
this.emit("disconnected", code, reason?.toString?.() || "");
|
|
660
739
|
if (!this.intentionalClose && this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
661
740
|
this.reconnectAttempts++;
|
|
662
741
|
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts - 1), 3e4);
|
|
663
742
|
setTimeout(() => this.connect().catch((e) => this.emit("error", e)), delay);
|
|
664
743
|
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
this.emit("error", err);
|
|
668
|
-
if (!
|
|
669
|
-
|
|
744
|
+
};
|
|
745
|
+
ws.onerror = (err) => {
|
|
746
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err?.message || err)));
|
|
747
|
+
if (!settled) {
|
|
748
|
+
settled = true;
|
|
749
|
+
reject(err);
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
setTimeout(() => {
|
|
753
|
+
if (!settled) {
|
|
754
|
+
settled = true;
|
|
755
|
+
ws.close();
|
|
756
|
+
reject(new Error("Connection timeout"));
|
|
757
|
+
}
|
|
758
|
+
}, 15e3);
|
|
670
759
|
});
|
|
671
760
|
}
|
|
672
761
|
disconnect() {
|
|
@@ -687,6 +776,7 @@ var TikTokLive = class extends import_events.EventEmitter {
|
|
|
687
776
|
get roomId() {
|
|
688
777
|
return this._roomId;
|
|
689
778
|
}
|
|
779
|
+
// Typed event emitter overrides
|
|
690
780
|
on(event, listener) {
|
|
691
781
|
return super.on(event, listener);
|
|
692
782
|
}
|
|
@@ -699,6 +789,25 @@ var TikTokLive = class extends import_events.EventEmitter {
|
|
|
699
789
|
emit(event, ...args) {
|
|
700
790
|
return super.emit(event, ...args);
|
|
701
791
|
}
|
|
792
|
+
// ── Message handling ──────────────────────────────────────────────
|
|
793
|
+
async handleMessage(raw) {
|
|
794
|
+
try {
|
|
795
|
+
let bytes;
|
|
796
|
+
if (raw instanceof ArrayBuffer) {
|
|
797
|
+
bytes = new Uint8Array(raw);
|
|
798
|
+
} else if (raw instanceof Uint8Array) {
|
|
799
|
+
bytes = raw;
|
|
800
|
+
} else if (typeof Blob !== "undefined" && raw instanceof Blob) {
|
|
801
|
+
bytes = new Uint8Array(await raw.arrayBuffer());
|
|
802
|
+
} else if (raw?.buffer instanceof ArrayBuffer) {
|
|
803
|
+
bytes = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
804
|
+
} else {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
this.handleFrame(bytes);
|
|
808
|
+
} catch {
|
|
809
|
+
}
|
|
810
|
+
}
|
|
702
811
|
handleFrame(buf) {
|
|
703
812
|
try {
|
|
704
813
|
const fields = decodeProto(buf);
|
|
@@ -706,14 +815,15 @@ var TikTokLive = class extends import_events.EventEmitter {
|
|
|
706
815
|
const id = idField ? idField.value : 0n;
|
|
707
816
|
const type = getStr(fields, 7);
|
|
708
817
|
const binary = getBytes(fields, 8);
|
|
709
|
-
if (id > 0n && this.ws
|
|
710
|
-
|
|
818
|
+
if (id > 0n && this.ws && this.ws.readyState === 1) {
|
|
819
|
+
const ack = buildAck(id);
|
|
820
|
+
this.ws.send(ack.buffer.byteLength === ack.length ? ack.buffer : ack.buffer.slice(ack.byteOffset, ack.byteOffset + ack.byteLength));
|
|
711
821
|
}
|
|
712
822
|
if (type === "msg" && binary && binary.length > 0) {
|
|
713
823
|
let inner = binary;
|
|
714
824
|
if (inner.length > 2 && inner[0] === 31 && inner[1] === 139) {
|
|
715
825
|
try {
|
|
716
|
-
inner =
|
|
826
|
+
inner = gunzipSync(inner);
|
|
717
827
|
} catch {
|
|
718
828
|
}
|
|
719
829
|
}
|
|
@@ -751,8 +861,9 @@ var TikTokLive = class extends import_events.EventEmitter {
|
|
|
751
861
|
startHeartbeat(roomId) {
|
|
752
862
|
this.stopHeartbeat();
|
|
753
863
|
this.heartbeatTimer = setInterval(() => {
|
|
754
|
-
if (this.ws
|
|
755
|
-
|
|
864
|
+
if (this.ws && this.ws.readyState === 1) {
|
|
865
|
+
const hb = buildHeartbeat(roomId);
|
|
866
|
+
this.ws.send(hb.buffer.byteLength === hb.length ? hb.buffer : hb.buffer.slice(hb.byteOffset, hb.byteOffset + hb.byteLength));
|
|
756
867
|
}
|
|
757
868
|
}, this.heartbeatInterval);
|
|
758
869
|
}
|