@vanzxy/baileys 1.6.2 → 1.6.4

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.

Potentially problematic release.


This version of @vanzxy/baileys might be problematic. Click here for more details.

Files changed (51) hide show
  1. package/NOTICE.md +50 -0
  2. package/lib/Utils/A2UI.js +217 -0
  3. package/lib/Utils/MessageBuilder.js +332 -46
  4. package/lib/Utils/MessageBuilder_d.ts +45 -0
  5. package/lib/Utils/PersistentStore.js +592 -0
  6. package/lib/Utils/PersistentStore_d.ts +60 -0
  7. package/lib/Utils/anti-delete.d.ts +68 -0
  8. package/lib/Utils/anti-delete.js +185 -0
  9. package/lib/Utils/auto-reply.d.ts +47 -0
  10. package/lib/Utils/auto-reply.js +155 -0
  11. package/lib/Utils/button-helper-utils.js +314 -0
  12. package/lib/Utils/button-sender.js +817 -0
  13. package/lib/Utils/chat-history-helpers.d.ts +21 -0
  14. package/lib/Utils/chat-history-helpers.js +71 -0
  15. package/lib/Utils/index.d.ts +11 -0
  16. package/lib/Utils/index.js +16 -0
  17. package/lib/Utils/media-messages.d.ts +18 -0
  18. package/lib/Utils/media-messages.js +71 -0
  19. package/lib/Utils/media-set.d.ts +13 -0
  20. package/lib/Utils/media-set.js +165 -0
  21. package/lib/Utils/message-kind.js +139 -0
  22. package/lib/Utils/message-search.d.ts +44 -0
  23. package/lib/Utils/message-search.js +174 -0
  24. package/lib/Utils/scheduling.d.ts +42 -0
  25. package/lib/Utils/scheduling.js +140 -0
  26. package/lib/Utils/status.d.ts +50 -0
  27. package/lib/Utils/status.js +108 -0
  28. package/lib/Utils/stickerpack.d.ts +51 -0
  29. package/lib/Utils/stickerpack.js +276 -0
  30. package/lib/Utils/templates.d.ts +76 -0
  31. package/lib/Utils/templates.js +151 -0
  32. package/lib/Utils/use-sqlite-auth-state.js +28 -1
  33. package/lib/Utils/vcard.d.ts +58 -0
  34. package/lib/Utils/vcard.js +94 -0
  35. package/lib/VoIP/audio-feeder.d.ts +15 -0
  36. package/lib/VoIP/audio-feeder.js +132 -0
  37. package/lib/VoIP/index.js +277 -0
  38. package/lib/VoIP/relay-transport.d.ts +43 -0
  39. package/lib/VoIP/relay-transport.js +559 -0
  40. package/lib/VoIP/signaling.js +624 -0
  41. package/lib/VoIP/types.d.ts +69 -0
  42. package/lib/VoIP/types.js +17 -0
  43. package/lib/VoIP/wasm-engine.d.ts +103 -0
  44. package/lib/VoIP/wasm-engine.js +1214 -0
  45. package/lib/VoIP/worker-bootstrap.js +1042 -0
  46. package/lib/WABinary/generic-utils.js +8 -0
  47. package/lib/assets/wasm/loader.js +5 -0
  48. package/lib/assets/wasm/whatsapp.wasm +0 -0
  49. package/lib/assets/wasm/worker-modules.js +273 -0
  50. package/lib/index.js +4 -0
  51. package/package.json +22 -1
@@ -0,0 +1,94 @@
1
+ // Vanz@Add --- ported from Bail-master addons/vcard.ts (type-only
2
+ // annotations dropped; behavior unchanged).
3
+ export const escapeVCard = (s) => s.replace(/\\/g, '\\\\').replace(/;/g, '\\;').replace(/,/g, '\\,').replace(/\n/g, '\\n');
4
+ export const formatPhone = (p) => p.replace(/[^\d+]/g, '');
5
+ /** Build a VCARD 3.0 string from structured contact data. */
6
+ export const generateVCard = (c) => {
7
+ const lines = ['BEGIN:VCARD', 'VERSION:3.0', `FN:${escapeVCard(c.fullName)}`];
8
+ const parts = c.fullName.split(' ');
9
+ if (parts.length >= 2) {
10
+ const last = parts[parts.length - 1] || '';
11
+ const first = parts.slice(0, -1).join(' ');
12
+ lines.push(`N:${escapeVCard(last)};${escapeVCard(first)};;;`);
13
+ }
14
+ else {
15
+ lines.push(`N:${escapeVCard(c.fullName)};;;;`);
16
+ }
17
+ if (c.organization)
18
+ lines.push(`ORG:${escapeVCard(c.organization)}`);
19
+ if (c.title)
20
+ lines.push(`TITLE:${escapeVCard(c.title)}`);
21
+ for (const p of c.phones ?? []) {
22
+ const t = p.type || 'CELL';
23
+ const n = formatPhone(p.number);
24
+ lines.push(p.label ? `TEL;type=${t};type=VOICE;X-ABLabel=${escapeVCard(p.label)}:${n}` : `TEL;type=${t};type=VOICE:${n}`);
25
+ }
26
+ for (const e of c.emails ?? [])
27
+ lines.push(`EMAIL;type=${e.type || 'OTHER'}:${e.email}`);
28
+ for (const u of c.urls ?? [])
29
+ lines.push(`URL;type=${u.type || 'OTHER'}:${u.url}`);
30
+ for (const a of c.addresses ?? []) {
31
+ const t = a.type || 'OTHER';
32
+ const parts = ['', '', a.street || '', a.city || '', a.state || '', a.postalCode || '', a.country || ''].map((v) => escapeVCard(v));
33
+ lines.push(`ADR;type=${t}:${parts.join(';')}`);
34
+ }
35
+ if (c.birthday)
36
+ lines.push(`BDAY:${c.birthday}`);
37
+ if (c.note)
38
+ lines.push(`NOTE:${escapeVCard(c.note)}`);
39
+ lines.push('END:VCARD');
40
+ return lines.join('\r\n');
41
+ };
42
+ export const generateVCards = (contacts) => contacts.map(generateVCard).join('\r\n');
43
+ /** Parse a subset of VCARD fields back into structured data. */
44
+ export const parseVCard = (vcard) => {
45
+ const contact = {};
46
+ for (const line of vcard.split(/\r?\n/)) {
47
+ const [key, ...vp] = line.split(':');
48
+ if (!key)
49
+ continue;
50
+ const value = vp.join(':');
51
+ if (key.startsWith('FN'))
52
+ contact.fullName = value.replace(/\\([;,n\\])/g, '$1');
53
+ else if (key.startsWith('ORG'))
54
+ contact.organization = value.replace(/\\([;,n\\])/g, '$1');
55
+ else if (key.startsWith('TITLE'))
56
+ contact.title = value.replace(/\\([;,n\\])/g, '$1');
57
+ else if (key.startsWith('TEL')) {
58
+ contact.phones = contact.phones || [];
59
+ const tm = key.match(/type=(\w+)/i);
60
+ contact.phones.push({ number: value, type: tm?.[1]?.toUpperCase() || 'CELL' });
61
+ }
62
+ else if (key.startsWith('EMAIL')) {
63
+ contact.emails = contact.emails || [];
64
+ const tm = key.match(/type=(\w+)/i);
65
+ contact.emails.push({ email: value, type: tm?.[1]?.toUpperCase() || 'OTHER' });
66
+ }
67
+ else if (key.startsWith('BDAY'))
68
+ contact.birthday = value;
69
+ else if (key.startsWith('NOTE'))
70
+ contact.note = value.replace(/\\n/g, '\n');
71
+ }
72
+ return contact;
73
+ };
74
+ /** Ready-to-send `contacts` message content for a single contact. */
75
+ export const createContactCard = (contact) => ({
76
+ contacts: {
77
+ displayName: contact.displayName || contact.fullName,
78
+ contacts: [{ vcard: generateVCard(contact) }]
79
+ }
80
+ });
81
+ /** Ready-to-send `contacts` message content for multiple contacts. */
82
+ export const createContactCards = (contacts) => ({
83
+ contacts: {
84
+ displayName: contacts.length === 1 ? contacts[0]?.displayName || contacts[0]?.fullName || '' : `${contacts.length} Contacts`,
85
+ contacts: contacts.map((c) => ({ vcard: generateVCard(c) }))
86
+ }
87
+ });
88
+ export const quickContact = (name, phone, options) => ({
89
+ fullName: name,
90
+ phones: [{ number: phone, type: 'CELL' }],
91
+ organization: options?.organization,
92
+ emails: options?.email ? [{ email: options.email, type: 'WORK' }] : undefined
93
+ });
94
+ //# sourceMappingURL=vcard.js.map
@@ -0,0 +1,15 @@
1
+ export declare class AudioFeeder {
2
+ #private;
3
+ private readonly sampleRate;
4
+ private readonly channels;
5
+ private readonly framesPerChunk;
6
+ private readonly onChunk;
7
+ private readonly source;
8
+ droppedChunks: number;
9
+ underflowChunks: number;
10
+ bytesProduced: number;
11
+ chunksEmitted: number;
12
+ constructor(sampleRate: number, channels: number, framesPerChunk: number, onChunk: (chunk: Float32Array) => void, source?: string);
13
+ start: () => void;
14
+ stop: () => void;
15
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Audio feeder.
3
+ *
4
+ * Spawns ffmpeg to decode `source` into f32le PCM at the requested rate, then
5
+ * meters frames out at chunk-cadence to the WASM uplink.
6
+ *
7
+ * @author ShellTear
8
+ */
9
+ import { spawn } from "node:child_process";
10
+ const LOW_WATERMARK_CHUNKS = 16;
11
+ const MAX_QUEUED_CHUNKS = 1024;
12
+ const DEFAULT_WARMUP_MS = 500;
13
+ export class AudioFeeder {
14
+ sampleRate;
15
+ channels;
16
+ framesPerChunk;
17
+ onChunk;
18
+ source;
19
+ #proc = null;
20
+ #pending = Buffer.alloc(0);
21
+ #queue = [];
22
+ #emitTimer = null;
23
+ #nextEmitAtMs = 0;
24
+ #warmupUntilMs = 0;
25
+ droppedChunks = 0;
26
+ underflowChunks = 0;
27
+ bytesProduced = 0;
28
+ chunksEmitted = 0;
29
+ constructor(sampleRate, channels, framesPerChunk, onChunk, source = "silence") {
30
+ this.sampleRate = sampleRate;
31
+ this.channels = channels;
32
+ this.framesPerChunk = framesPerChunk;
33
+ this.onChunk = onChunk;
34
+ this.source = source;
35
+ }
36
+ start = () => {
37
+ if (this.#proc)
38
+ return;
39
+ const chunkSamples = this.framesPerChunk * this.channels;
40
+ const chunkBytes = chunkSamples * Float32Array.BYTES_PER_ELEMENT;
41
+ const chunkIntervalMs = (this.framesPerChunk / this.sampleRate) * 1000;
42
+ const inputArgs = this.#resolveInputArgs();
43
+ this.#proc = spawn("ffmpeg", [
44
+ "-hide_banner",
45
+ "-loglevel", "error",
46
+ "-thread_queue_size", "512",
47
+ ...inputArgs,
48
+ "-f", "f32le",
49
+ "-ac", String(this.channels),
50
+ "-ar", String(this.sampleRate),
51
+ "pipe:1",
52
+ ]);
53
+ this.#proc.stdout.on("data", (chunk) => {
54
+ this.#pending = Buffer.concat([this.#pending, chunk]);
55
+ while (this.#pending.length >= chunkBytes) {
56
+ if (this.#queue.length >= MAX_QUEUED_CHUNKS) {
57
+ this.#proc?.stdout.pause();
58
+ break;
59
+ }
60
+ const frame = this.#pending.subarray(0, chunkBytes);
61
+ this.#pending = this.#pending.subarray(chunkBytes);
62
+ const out = new Float32Array(chunkSamples);
63
+ out.set(new Float32Array(frame.buffer, frame.byteOffset, chunkSamples));
64
+ this.bytesProduced += chunkBytes;
65
+ this.#queue.push(out);
66
+ }
67
+ });
68
+ this.#proc.stderr.on("data", (chunk) => {
69
+ process.stderr.write(`[AudioFeeder] ${chunk.toString().trim()}\n`);
70
+ });
71
+ this.#proc.on("exit", (code) => {
72
+ if (code !== 0 && code !== null) {
73
+ process.stderr.write(`[AudioFeeder] ffmpeg exited with code=${code}\n`);
74
+ }
75
+ this.#proc = null;
76
+ });
77
+ this.#nextEmitAtMs = 0;
78
+ this.#warmupUntilMs = Date.now() + DEFAULT_WARMUP_MS;
79
+ this.#scheduleNext(chunkSamples, chunkIntervalMs);
80
+ };
81
+ stop = () => {
82
+ if (this.#emitTimer) {
83
+ clearTimeout(this.#emitTimer);
84
+ this.#emitTimer = null;
85
+ }
86
+ this.#proc?.kill("SIGTERM");
87
+ this.#proc = null;
88
+ this.#pending = Buffer.alloc(0);
89
+ this.#queue = [];
90
+ this.#warmupUntilMs = 0;
91
+ };
92
+ #resolveInputArgs = () => {
93
+ if (!this.source || this.source === "silence") {
94
+ return ["-f", "lavfi", "-i", `aevalsrc=0:d=3600:s=${this.sampleRate}`];
95
+ }
96
+ if (this.source.startsWith("lavfi:")) {
97
+ return ["-f", "lavfi", "-i", this.source.slice("lavfi:".length)];
98
+ }
99
+ return ["-i", this.source];
100
+ };
101
+ #scheduleNext = (chunkSamples, chunkIntervalMs) => {
102
+ if (!this.#proc)
103
+ return;
104
+ const now = Date.now();
105
+ if (this.#nextEmitAtMs === 0)
106
+ this.#nextEmitAtMs = now;
107
+ const delayMs = Math.max(0, this.#nextEmitAtMs - now);
108
+ this.#emitTimer = setTimeout(() => {
109
+ this.#emitTimer = null;
110
+ if (this.#queue.length < LOW_WATERMARK_CHUNKS && Date.now() < this.#warmupUntilMs) {
111
+ this.#nextEmitAtMs = Date.now() + 10;
112
+ this.#scheduleNext(chunkSamples, chunkIntervalMs);
113
+ return;
114
+ }
115
+ this.#flushOne(chunkSamples);
116
+ this.#nextEmitAtMs += chunkIntervalMs;
117
+ this.#scheduleNext(chunkSamples, chunkIntervalMs);
118
+ }, delayMs);
119
+ };
120
+ #flushOne = (chunkSamples) => {
121
+ let nextChunk = this.#queue.shift();
122
+ if (!nextChunk) {
123
+ nextChunk = new Float32Array(chunkSamples);
124
+ this.underflowChunks += 1;
125
+ }
126
+ this.chunksEmitted += 1;
127
+ this.onChunk(nextChunk);
128
+ if (this.#proc?.stdout.isPaused() && this.#queue.length <= MAX_QUEUED_CHUNKS / 4) {
129
+ this.#proc.stdout.resume();
130
+ }
131
+ };
132
+ }
@@ -0,0 +1,277 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { randomBytes, createHmac } from "node:crypto";
3
+
4
+ import { WasmEngine } from "./wasm-engine.js";
5
+ import { RelayRtcTransport } from "./relay-transport.js";
6
+ import { SignalingBridge } from "./signaling.js";
7
+ import { AudioFeeder } from "./audio-feeder.js";
8
+ import { CallState } from "./types.js";
9
+
10
+ export { CallState } from "./types.js";
11
+
12
+ const SHA256_LEN = 32;
13
+
14
+ const toBareJid = (jid) => {
15
+ if (!jid) return jid;
16
+ const at = jid.indexOf("@");
17
+ if (at < 0) return jid;
18
+ const user = jid.slice(0, at).split(":")[0];
19
+ return `${user}@${jid.slice(at + 1)}`;
20
+ };
21
+
22
+ const computeHkdf = (key, salt, info, length) => {
23
+ const effectiveSalt = salt && salt.length > 0 ? Buffer.from(salt) : Buffer.alloc(SHA256_LEN, 0);
24
+ const prk = createHmac("sha256", effectiveSalt).update(key).digest();
25
+ const blocks = Math.ceil(length / SHA256_LEN);
26
+ const okm = Buffer.alloc(blocks * SHA256_LEN);
27
+ let prev = Buffer.alloc(0);
28
+ for (let i = 1; i <= blocks; i += 1) {
29
+ prev = createHmac("sha256", prk)
30
+ .update(prev)
31
+ .update(info)
32
+ .update(Buffer.from([i]))
33
+ .digest();
34
+ prev.copy(okm, (i - 1) * SHA256_LEN);
35
+ }
36
+ return new Uint8Array(okm.buffer, okm.byteOffset, length);
37
+ };
38
+
39
+ const computeHmacSha256 = (data, key) => {
40
+ const result = createHmac("sha256", Buffer.from(key)).update(data).digest();
41
+ return new Uint8Array(result.buffer, result.byteOffset, result.byteLength);
42
+ };
43
+
44
+ const isCallReceiptNode = (node) => {
45
+ if (node?.tag !== "receipt") return false;
46
+ const child = Array.isArray(node.content) ? node.content[0] : null;
47
+ return !!(child?.attrs?.["call-id"] || child?.attrs?.call_id);
48
+ };
49
+
50
+ export class ActiveCall extends EventEmitter {
51
+ #state = CallState.Idle;
52
+ #endResolver;
53
+ #endPromise;
54
+ #endTimer = null;
55
+ #ended = false;
56
+ _audioSource = "silence";
57
+
58
+ constructor(callId, engine, durationMs) {
59
+ super();
60
+ this.callId = callId;
61
+ this.engine = engine;
62
+ this.#endPromise = new Promise((res) => { this.#endResolver = res; });
63
+ if (durationMs > 0) {
64
+ this.#endTimer = setTimeout(() => this.end(), durationMs);
65
+ }
66
+ }
67
+
68
+ get state() { return this.#state; }
69
+
70
+ end = () => {
71
+ if (this.#ended) return;
72
+ this.#ended = true;
73
+ if (this.#endTimer) { clearTimeout(this.#endTimer); this.#endTimer = null; }
74
+ try { this.engine.endCall(0, true); } catch {}
75
+ };
76
+
77
+ mute = (muted) => {
78
+ try { this.engine.setMute(muted); } catch {}
79
+ };
80
+
81
+ waitForEnd = () => this.#endPromise;
82
+
83
+ _updateState = (state) => {
84
+ this.#state = state;
85
+ if (state === CallState.PreacceptReceived) this.emit("ringing");
86
+ else if (state === CallState.Active) this.emit("connected");
87
+ else if (state === CallState.Idle || state === CallState.Ending) {
88
+ this._forceEnd("ended");
89
+ }
90
+ };
91
+
92
+ _emitAudio = (pcm) => { this.emit("audio", pcm); };
93
+
94
+ _forceEnd = (reason) => {
95
+ if (this.#ended) return;
96
+ this.#ended = true;
97
+ if (this.#endTimer) { clearTimeout(this.#endTimer); this.#endTimer = null; }
98
+ this.emit("ended", reason);
99
+ this.#endResolver(reason);
100
+ };
101
+ }
102
+
103
+ export class VoipClient {
104
+ #config;
105
+ #engine = null;
106
+ #relay = null;
107
+ #signaling = null;
108
+ #sock = null;
109
+ #activeCall = null;
110
+ #capturePtr = 0;
111
+ #captureChunkBytes = 0;
112
+ #captureSampleRate = 16000;
113
+ #captureChannels = 1;
114
+ #captureFramesPerChunk = 320;
115
+ #feeder = null;
116
+
117
+ constructor(config = {}) {
118
+ this.#config = config;
119
+ }
120
+
121
+ connectWithSocket = async (existingSock) => {
122
+ this.#sock = existingSock;
123
+ await this.#initVoipStack();
124
+ };
125
+
126
+ #initVoipStack = async () => {
127
+ this.#signaling = new SignalingBridge({ sock: this.#sock });
128
+ await this.#signaling.init();
129
+
130
+ this.#relay = new RelayRtcTransport({
131
+ onTransportMessage: (data, ip, port) => this.#engine?.handleOnTransportMessage(data, ip, port),
132
+ onIceRtt: (rttMs, ip, port) => this.#engine?.updateIceRtt(rttMs, ip, port),
133
+ });
134
+
135
+ this.#engine = new WasmEngine({
136
+ resourcesPath: this.#config.resourcesPath,
137
+ callbacks: {
138
+ onSignalingXmpp: (peerJid, callId, xmlPayload) =>
139
+ this.#signaling.sendSignaling(peerJid, callId, xmlPayload),
140
+ onCallEvent: (eventType, eventData) => this.#handleCallEvent(eventType, eventData),
141
+ sendDataToRelay: (data, ip, port) => this.#relay.send(data, ip, port),
142
+ onAudioCaptureInit: (config) => this.#handleAudioCaptureInit(config),
143
+ onAudioCaptureStart: () => this.#handleAudioCaptureStart(),
144
+ onAudioCaptureStop: () => this.#handleAudioCaptureStop(),
145
+ onAudioPlaybackData: (audioData) => this.#activeCall?._emitAudio(audioData),
146
+ cryptoHkdf: computeHkdf,
147
+ hmacSha256: computeHmacSha256,
148
+ },
149
+ });
150
+
151
+ await this.#engine.initialize();
152
+ this.#signaling.attachEngine(this.#engine);
153
+
154
+ const selfPnJid = this.#sock.authState.creds.me?.id;
155
+ const selfLidJid = this.#sock.authState.creds.me?.lid;
156
+ this.#engine.initVoipStack(selfPnJid, toBareJid(selfPnJid), selfLidJid);
157
+ await this.#engine.waitForVoipStackReady();
158
+ try { this.#engine.updateNetworkMedium(2, 0); } catch {}
159
+
160
+ this.#sock.ws.on("CB:call", (node) => {
161
+ this.#signaling.processIncomingCall(node, this.#engine, this.#activeCall?.callId ?? "");
162
+ });
163
+ this.#sock.ws.on("CB:receipt", (node) => {
164
+ if (!isCallReceiptNode(node)) return;
165
+ this.#signaling.processIncomingReceipt(node, this.#engine, this.#activeCall?.callId ?? "");
166
+ });
167
+ };
168
+
169
+ call = async (phoneNumber, opts = {}) => {
170
+ if (!this.#engine || !this.#signaling) throw new Error("Not connected. Call connectWithSocket() first.");
171
+ if (this.#activeCall) throw new Error("A call is already active.");
172
+
173
+ const targetNumber = phoneNumber.replace(/\D/g, "");
174
+ const targetPnJid = `${targetNumber}@s.whatsapp.net`;
175
+ const durationMs = opts.durationMs ?? 120_000;
176
+ const audioSource = opts.audioSource ?? "silence";
177
+
178
+ const peerLid = await this.#signaling.resolveLid(targetPnJid);
179
+ if (!peerLid) throw new Error(`Could not resolve LID for ${targetPnJid}`);
180
+
181
+ for (const jid of [targetPnJid, peerLid]) {
182
+ try { await this.#sock.presenceSubscribe(jid); } catch {}
183
+ }
184
+ await new Promise((r) => setTimeout(r, 750));
185
+
186
+ const peerDeviceJids = await this.#signaling.discoverPeerDevices(peerLid);
187
+ const deviceList = peerDeviceJids.length ? peerDeviceJids : [toBareJid(peerLid)];
188
+
189
+ await this.#signaling.ensureSessionsForPeers(deviceList);
190
+
191
+ await new Promise((r) => setTimeout(r, 500));
192
+ await this.#signaling.issueTcToken(peerLid);
193
+ const tcToken = await this.#signaling.ensureTcToken(peerLid, targetPnJid);
194
+
195
+ const callId = ("00" + randomBytes(16).toString("hex").slice(2)).toUpperCase();
196
+
197
+ const call = new ActiveCall(callId, this.#engine, durationMs);
198
+ call._audioSource = audioSource;
199
+ this.#activeCall = call;
200
+
201
+ this.#engine.startCall({
202
+ peerJid: peerLid,
203
+ peerPn: targetPnJid,
204
+ peerList: deviceList,
205
+ callId,
206
+ isVideo: false,
207
+ isLidCall: true,
208
+ isFromDialer: false,
209
+ extraData: tcToken,
210
+ });
211
+
212
+ return call;
213
+ };
214
+
215
+ disconnect = () => {
216
+ this.#activeCall?._forceEnd("disconnect");
217
+ this.#activeCall = null;
218
+ this.#relay?.closeAll();
219
+ this.#engine?.destroy();
220
+ this.#engine = null;
221
+ this.#relay = null;
222
+ this.#signaling = null;
223
+ this.#sock = null;
224
+ };
225
+
226
+ #handleCallEvent = (eventType, eventData) => {
227
+ if (eventType === 16 && eventData) {
228
+ try {
229
+ const parsed = JSON.parse(eventData);
230
+ const info = parsed.call_info ?? parsed.callInfo ?? {};
231
+ const callState = Number(info.call_state ?? info.callState ?? 0);
232
+ this.#activeCall?._updateState(callState);
233
+ } catch {}
234
+ } else if (eventType === 156 && eventData) {
235
+ try {
236
+ const update = JSON.parse(eventData);
237
+ this.#relay?.updateRelayList(update);
238
+ } catch {}
239
+ } else if (eventType === 2) {
240
+ this.#activeCall?._forceEnd("remote_end");
241
+ }
242
+ };
243
+
244
+ #handleAudioCaptureInit = (config) => {
245
+ if (!this.#engine) return;
246
+ this.#captureSampleRate = config.sampleRate || 16000;
247
+ this.#captureChannels = config.channels || 1;
248
+ this.#captureFramesPerChunk = config.framesPerChunk || 320;
249
+ const chunkSamples = this.#captureFramesPerChunk * this.#captureChannels;
250
+ this.#captureChunkBytes = chunkSamples * Float32Array.BYTES_PER_ELEMENT;
251
+ this.#capturePtr = this.#engine.malloc(this.#captureChunkBytes);
252
+ };
253
+
254
+ #handleAudioCaptureStart = () => {
255
+ if (!this.#engine || !this.#capturePtr) return;
256
+ const audioSource = this.#activeCall?._audioSource ?? "silence";
257
+ this.#feeder = new AudioFeeder(
258
+ this.#captureSampleRate,
259
+ this.#captureChannels,
260
+ this.#captureFramesPerChunk,
261
+ (chunk) => {
262
+ if (this.#engine && this.#capturePtr) this.#engine.sendAudioData(chunk, this.#capturePtr);
263
+ },
264
+ audioSource,
265
+ );
266
+ this.#feeder.start();
267
+ };
268
+
269
+ #handleAudioCaptureStop = () => {
270
+ this.#feeder?.stop();
271
+ this.#feeder = null;
272
+ if (this.#engine && this.#capturePtr) {
273
+ try { this.#engine.free(this.#capturePtr); } catch {}
274
+ this.#capturePtr = 0;
275
+ }
276
+ };
277
+ }
@@ -0,0 +1,43 @@
1
+ type RelayAddress = {
2
+ protocol: number;
3
+ ipv4?: string;
4
+ ipv6?: string;
5
+ port?: number;
6
+ port_v6?: number;
7
+ };
8
+ type RelayDescriptor = {
9
+ relay_id: number;
10
+ relay_name: string;
11
+ token_id: number;
12
+ auth_token_id?: number;
13
+ addresses: RelayAddress[];
14
+ };
15
+ export type RelayListUpdatePayload = {
16
+ relay_key: string;
17
+ relay_tokens: string[];
18
+ auth_tokens?: string[];
19
+ enable_edgeray_dtls_active_mode?: boolean;
20
+ relays: RelayDescriptor[];
21
+ };
22
+ export type RelayTransportStats = {
23
+ sentPackets: number;
24
+ receivedPackets: number;
25
+ sentBytes: number;
26
+ receivedBytes: number;
27
+ droppedPackets: number;
28
+ openConnections: number;
29
+ };
30
+ export type RelayTransportConfig = {
31
+ onTransportMessage: (data: Uint8Array, ip: string, port: number) => void;
32
+ onIceRtt?: (rttMs: number, ip: string, port: number) => void;
33
+ };
34
+ export declare class RelayRtcTransport {
35
+ #private;
36
+ private readonly config;
37
+ constructor(config: RelayTransportConfig);
38
+ updateRelayList: (update: RelayListUpdatePayload) => void;
39
+ send: (packet: Uint8Array | Buffer, ip: string, port: number) => number;
40
+ getStats: () => RelayTransportStats;
41
+ closeAll: () => Promise<void>;
42
+ }
43
+ export {};