@discordjs/voice 0.17.1-dev.1731672303-3669d5e11 → 0.18.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 CHANGED
@@ -51,9 +51,13 @@ try installing another.
51
51
 
52
52
  **Encryption Libraries (npm install):**
53
53
 
54
+ > [!NOTE]
55
+ > You only need to install one of these libraries if your system does not support `aes-256-gcm` (verify by running `require('node:crypto').getCiphers().includes('aes-256-gcm')`).
56
+
54
57
  - `sodium-native`: ^3.3.0
55
58
  - `sodium`: ^3.0.2
56
- - `tweetnacl`: ^1.0.3
59
+ - `@stablelib/xchacha20poly1305`: ^2.0.0
60
+ - `@noble/ciphers`: ^1.0.0
57
61
  - `libsodium-wrappers`: ^0.7.9
58
62
 
59
63
  **Opus Libraries (npm install):**
package/dist/index.js CHANGED
@@ -162,6 +162,7 @@ __name(deleteAudioPlayer, "deleteAudioPlayer");
162
162
 
163
163
  // src/networking/Networking.ts
164
164
  var import_node_buffer3 = require("buffer");
165
+ var import_node_crypto = __toESM(require("crypto"));
165
166
  var import_node_events3 = require("events");
166
167
  var import_v42 = require("discord-api-types/voice/v4");
167
168
 
@@ -169,66 +170,84 @@ var import_v42 = require("discord-api-types/voice/v4");
169
170
  var import_node_buffer = require("buffer");
170
171
  var libs = {
171
172
  "sodium-native": /* @__PURE__ */ __name((sodium) => ({
172
- open: /* @__PURE__ */ __name((buffer, nonce2, secretKey) => {
173
- if (buffer) {
174
- const output = import_node_buffer.Buffer.allocUnsafe(buffer.length - sodium.crypto_box_MACBYTES);
175
- if (sodium.crypto_secretbox_open_easy(output, buffer, nonce2, secretKey)) return output;
176
- }
177
- return null;
178
- }, "open"),
179
- close: /* @__PURE__ */ __name((opusPacket, nonce2, secretKey) => {
180
- const output = import_node_buffer.Buffer.allocUnsafe(opusPacket.length + sodium.crypto_box_MACBYTES);
181
- sodium.crypto_secretbox_easy(output, opusPacket, nonce2, secretKey);
182
- return output;
183
- }, "close"),
184
- random: /* @__PURE__ */ __name((num, buffer = import_node_buffer.Buffer.allocUnsafe(num)) => {
185
- sodium.randombytes_buf(buffer);
186
- return buffer;
187
- }, "random")
173
+ crypto_aead_xchacha20poly1305_ietf_decrypt: /* @__PURE__ */ __name((cipherText, additionalData, nonce2, key) => {
174
+ const message = import_node_buffer.Buffer.alloc(cipherText.length - sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES);
175
+ sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(message, null, cipherText, additionalData, nonce2, key);
176
+ return message;
177
+ }, "crypto_aead_xchacha20poly1305_ietf_decrypt"),
178
+ crypto_aead_xchacha20poly1305_ietf_encrypt: /* @__PURE__ */ __name((plaintext, additionalData, nonce2, key) => {
179
+ const cipherText = import_node_buffer.Buffer.alloc(plaintext.length + sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES);
180
+ sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(cipherText, plaintext, additionalData, null, nonce2, key);
181
+ return cipherText;
182
+ }, "crypto_aead_xchacha20poly1305_ietf_encrypt")
188
183
  }), "sodium-native"),
189
184
  sodium: /* @__PURE__ */ __name((sodium) => ({
190
- open: sodium.api.crypto_secretbox_open_easy,
191
- close: sodium.api.crypto_secretbox_easy,
192
- random: /* @__PURE__ */ __name((num, buffer = import_node_buffer.Buffer.allocUnsafe(num)) => {
193
- sodium.api.randombytes_buf(buffer);
194
- return buffer;
195
- }, "random")
185
+ crypto_aead_xchacha20poly1305_ietf_decrypt: /* @__PURE__ */ __name((cipherText, additionalData, nonce2, key) => {
186
+ return sodium.api.crypto_aead_xchacha20poly1305_ietf_decrypt(cipherText, additionalData, null, nonce2, key);
187
+ }, "crypto_aead_xchacha20poly1305_ietf_decrypt"),
188
+ crypto_aead_xchacha20poly1305_ietf_encrypt: /* @__PURE__ */ __name((plaintext, additionalData, nonce2, key) => {
189
+ return sodium.api.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce2, key);
190
+ }, "crypto_aead_xchacha20poly1305_ietf_encrypt")
196
191
  }), "sodium"),
197
192
  "libsodium-wrappers": /* @__PURE__ */ __name((sodium) => ({
198
- open: sodium.crypto_secretbox_open_easy,
199
- close: sodium.crypto_secretbox_easy,
200
- random: sodium.randombytes_buf
193
+ crypto_aead_xchacha20poly1305_ietf_decrypt: /* @__PURE__ */ __name((cipherText, additionalData, nonce2, key) => {
194
+ return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, cipherText, additionalData, nonce2, key);
195
+ }, "crypto_aead_xchacha20poly1305_ietf_decrypt"),
196
+ crypto_aead_xchacha20poly1305_ietf_encrypt: /* @__PURE__ */ __name((plaintext, additionalData, nonce2, key) => {
197
+ return sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce2, key);
198
+ }, "crypto_aead_xchacha20poly1305_ietf_encrypt")
201
199
  }), "libsodium-wrappers"),
202
- tweetnacl: /* @__PURE__ */ __name((tweetnacl) => ({
203
- open: tweetnacl.secretbox.open,
204
- close: tweetnacl.secretbox,
205
- random: tweetnacl.randomBytes
206
- }), "tweetnacl")
200
+ "@stablelib/xchacha20poly1305": /* @__PURE__ */ __name((stablelib) => ({
201
+ crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, additionalData, nonce2, key) {
202
+ const crypto3 = new stablelib.XChaCha20Poly1305(key);
203
+ return crypto3.open(nonce2, plaintext, additionalData);
204
+ },
205
+ crypto_aead_xchacha20poly1305_ietf_encrypt(cipherText, additionalData, nonce2, key) {
206
+ const crypto3 = new stablelib.XChaCha20Poly1305(key);
207
+ return crypto3.seal(nonce2, cipherText, additionalData);
208
+ }
209
+ }), "@stablelib/xchacha20poly1305"),
210
+ "@noble/ciphers/chacha": /* @__PURE__ */ __name((noble) => ({
211
+ crypto_aead_xchacha20poly1305_ietf_decrypt(cipherText, additionalData, nonce2, key) {
212
+ const chacha = noble.xchacha20poly1305(key, nonce2, additionalData);
213
+ return chacha.decrypt(cipherText);
214
+ },
215
+ crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, nonce2, key) {
216
+ const chacha = noble.xchacha20poly1305(key, nonce2, additionalData);
217
+ return chacha.encrypt(plaintext);
218
+ }
219
+ }), "@noble/ciphers/chacha")
207
220
  };
208
221
  var fallbackError = /* @__PURE__ */ __name(() => {
209
222
  throw new Error(
210
223
  `Cannot play audio as no valid encryption package is installed.
211
- - Install sodium, libsodium-wrappers, or tweetnacl.
224
+ - Install one of:
225
+ - sodium
226
+ - libsodium-wrappers
227
+ - @stablelib/xchacha20poly1305
228
+ - @noble/ciphers.
212
229
  - Use the generateDependencyReport() function for more information.
213
230
  `
214
231
  );
215
232
  }, "fallbackError");
216
233
  var methods = {
217
- open: fallbackError,
218
- close: fallbackError,
219
- random: fallbackError
234
+ crypto_aead_xchacha20poly1305_ietf_encrypt: fallbackError,
235
+ crypto_aead_xchacha20poly1305_ietf_decrypt: fallbackError
220
236
  };
221
- void (async () => {
237
+ var secretboxLoadPromise = new Promise(async (resolve2) => {
222
238
  for (const libName of Object.keys(libs)) {
223
239
  try {
224
- const lib = require(libName);
225
- if (libName === "libsodium-wrappers" && lib.ready) await lib.ready;
240
+ const lib = await import(libName);
241
+ if (libName === "libsodium-wrappers" && lib.ready) {
242
+ await lib.ready;
243
+ }
226
244
  Object.assign(methods, libs[libName](lib));
227
245
  break;
228
246
  } catch {
229
247
  }
230
248
  }
231
- })();
249
+ resolve2();
250
+ });
232
251
 
233
252
  // src/util/util.ts
234
253
  var noop = /* @__PURE__ */ __name(() => {
@@ -503,7 +522,10 @@ var VoiceWebSocket = class extends import_node_events2.EventEmitter {
503
522
  var CHANNELS = 2;
504
523
  var TIMESTAMP_INC = 48e3 / 100 * CHANNELS;
505
524
  var MAX_NONCE_SIZE = 2 ** 32 - 1;
506
- var SUPPORTED_ENCRYPTION_MODES = ["xsalsa20_poly1305_lite", "xsalsa20_poly1305_suffix", "xsalsa20_poly1305"];
525
+ var SUPPORTED_ENCRYPTION_MODES = ["aead_xchacha20_poly1305_rtpsize"];
526
+ if (import_node_crypto.default.getCiphers().includes("aes-256-gcm")) {
527
+ SUPPORTED_ENCRYPTION_MODES.unshift("aead_aes256_gcm_rtpsize");
528
+ }
507
529
  var nonce = import_node_buffer3.Buffer.alloc(24);
508
530
  function stringifyState(state) {
509
531
  return JSON.stringify({
@@ -736,7 +758,7 @@ to ${stringifyState(newState)}`);
736
758
  sequence: randomNBit(16),
737
759
  timestamp: randomNBit(32),
738
760
  nonce: 0,
739
- nonceBuffer: import_node_buffer3.Buffer.alloc(24),
761
+ nonceBuffer: encryptionMode === "aead_aes256_gcm_rtpsize" ? import_node_buffer3.Buffer.alloc(12) : import_node_buffer3.Buffer.alloc(24),
740
762
  speaking: false,
741
763
  packetsPlayed: 0
742
764
  }
@@ -840,15 +862,15 @@ to ${stringifyState(newState)}`);
840
862
  * @param connectionData - The current connection data of the instance
841
863
  */
842
864
  createAudioPacket(opusPacket, connectionData) {
843
- const packetBuffer = import_node_buffer3.Buffer.alloc(12);
844
- packetBuffer[0] = 128;
845
- packetBuffer[1] = 120;
865
+ const rtpHeader = import_node_buffer3.Buffer.alloc(12);
866
+ rtpHeader[0] = 128;
867
+ rtpHeader[1] = 120;
846
868
  const { sequence, timestamp, ssrc } = connectionData;
847
- packetBuffer.writeUIntBE(sequence, 2, 2);
848
- packetBuffer.writeUIntBE(timestamp, 4, 4);
849
- packetBuffer.writeUIntBE(ssrc, 8, 4);
850
- packetBuffer.copy(nonce, 0, 0, 12);
851
- return import_node_buffer3.Buffer.concat([packetBuffer, ...this.encryptOpusPacket(opusPacket, connectionData)]);
869
+ rtpHeader.writeUIntBE(sequence, 2, 2);
870
+ rtpHeader.writeUIntBE(timestamp, 4, 4);
871
+ rtpHeader.writeUIntBE(ssrc, 8, 4);
872
+ rtpHeader.copy(nonce, 0, 0, 12);
873
+ return import_node_buffer3.Buffer.concat([rtpHeader, ...this.encryptOpusPacket(opusPacket, connectionData, rtpHeader)]);
852
874
  }
853
875
  /**
854
876
  * Encrypts an Opus packet using the format agreed upon by the instance and Discord.
@@ -856,26 +878,39 @@ to ${stringifyState(newState)}`);
856
878
  * @param opusPacket - The Opus packet to encrypt
857
879
  * @param connectionData - The current connection data of the instance
858
880
  */
859
- encryptOpusPacket(opusPacket, connectionData) {
881
+ encryptOpusPacket(opusPacket, connectionData, additionalData) {
860
882
  const { secretKey, encryptionMode } = connectionData;
861
- if (encryptionMode === "xsalsa20_poly1305_lite") {
862
- connectionData.nonce++;
863
- if (connectionData.nonce > MAX_NONCE_SIZE) connectionData.nonce = 0;
864
- connectionData.nonceBuffer.writeUInt32BE(connectionData.nonce, 0);
865
- return [
866
- methods.close(opusPacket, connectionData.nonceBuffer, secretKey),
867
- connectionData.nonceBuffer.slice(0, 4)
868
- ];
869
- } else if (encryptionMode === "xsalsa20_poly1305_suffix") {
870
- const random = methods.random(24, connectionData.nonceBuffer);
871
- return [methods.close(opusPacket, random, secretKey), random];
872
- }
873
- return [methods.close(opusPacket, nonce, secretKey)];
883
+ connectionData.nonce++;
884
+ if (connectionData.nonce > MAX_NONCE_SIZE) connectionData.nonce = 0;
885
+ connectionData.nonceBuffer.writeUInt32BE(connectionData.nonce, 0);
886
+ const noncePadding = connectionData.nonceBuffer.subarray(0, 4);
887
+ let encrypted;
888
+ switch (encryptionMode) {
889
+ case "aead_aes256_gcm_rtpsize": {
890
+ const cipher = import_node_crypto.default.createCipheriv("aes-256-gcm", secretKey, connectionData.nonceBuffer);
891
+ cipher.setAAD(additionalData);
892
+ encrypted = import_node_buffer3.Buffer.concat([cipher.update(opusPacket), cipher.final(), cipher.getAuthTag()]);
893
+ return [encrypted, noncePadding];
894
+ }
895
+ case "aead_xchacha20_poly1305_rtpsize": {
896
+ encrypted = methods.crypto_aead_xchacha20poly1305_ietf_encrypt(
897
+ opusPacket,
898
+ additionalData,
899
+ connectionData.nonceBuffer,
900
+ secretKey
901
+ );
902
+ return [encrypted, noncePadding];
903
+ }
904
+ default: {
905
+ throw new RangeError(`Unsupported encryption method: ${encryptionMode}`);
906
+ }
907
+ }
874
908
  }
875
909
  };
876
910
 
877
911
  // src/receive/VoiceReceiver.ts
878
912
  var import_node_buffer5 = require("buffer");
913
+ var import_node_crypto2 = __toESM(require("crypto"));
879
914
  var import_v43 = require("discord-api-types/voice/v4");
880
915
 
881
916
  // src/receive/AudioReceiveStream.ts
@@ -1466,6 +1501,9 @@ var SpeakingMap = class _SpeakingMap extends import_node_events6.EventEmitter {
1466
1501
  };
1467
1502
 
1468
1503
  // src/receive/VoiceReceiver.ts
1504
+ var HEADER_EXTENSION_BYTE = import_node_buffer5.Buffer.from([190, 222]);
1505
+ var UNPADDED_NONCE_LENGTH = 4;
1506
+ var AUTH_TAG_LENGTH = 16;
1469
1507
  var VoiceReceiver = class {
1470
1508
  static {
1471
1509
  __name(this, "VoiceReceiver");
@@ -1521,19 +1559,37 @@ var VoiceReceiver = class {
1521
1559
  }
1522
1560
  }
1523
1561
  decrypt(buffer, mode, nonce2, secretKey) {
1524
- let end;
1525
- if (mode === "xsalsa20_poly1305_lite") {
1526
- buffer.copy(nonce2, 0, buffer.length - 4);
1527
- end = buffer.length - 4;
1528
- } else if (mode === "xsalsa20_poly1305_suffix") {
1529
- buffer.copy(nonce2, 0, buffer.length - 24);
1530
- end = buffer.length - 24;
1531
- } else {
1532
- buffer.copy(nonce2, 0, 0, 12);
1562
+ buffer.copy(nonce2, 0, buffer.length - UNPADDED_NONCE_LENGTH);
1563
+ let headerSize = 12;
1564
+ const first = buffer.readUint8();
1565
+ if (first >> 4 & 1) headerSize += 4;
1566
+ const header = buffer.subarray(0, headerSize);
1567
+ const encrypted = buffer.subarray(headerSize, buffer.length - AUTH_TAG_LENGTH - UNPADDED_NONCE_LENGTH);
1568
+ const authTag = buffer.subarray(
1569
+ buffer.length - AUTH_TAG_LENGTH - UNPADDED_NONCE_LENGTH,
1570
+ buffer.length - UNPADDED_NONCE_LENGTH
1571
+ );
1572
+ switch (mode) {
1573
+ case "aead_aes256_gcm_rtpsize": {
1574
+ const decipheriv = import_node_crypto2.default.createDecipheriv("aes-256-gcm", secretKey, nonce2);
1575
+ decipheriv.setAAD(header);
1576
+ decipheriv.setAuthTag(authTag);
1577
+ return import_node_buffer5.Buffer.concat([decipheriv.update(encrypted), decipheriv.final()]);
1578
+ }
1579
+ case "aead_xchacha20_poly1305_rtpsize": {
1580
+ return import_node_buffer5.Buffer.from(
1581
+ methods.crypto_aead_xchacha20poly1305_ietf_decrypt(
1582
+ import_node_buffer5.Buffer.concat([encrypted, authTag]),
1583
+ header,
1584
+ nonce2,
1585
+ secretKey
1586
+ )
1587
+ );
1588
+ }
1589
+ default: {
1590
+ throw new RangeError(`Unsupported decryption method: ${mode}`);
1591
+ }
1533
1592
  }
1534
- const decrypted = methods.open(buffer.slice(12, end), nonce2, secretKey);
1535
- if (!decrypted) return;
1536
- return import_node_buffer5.Buffer.from(decrypted);
1537
1593
  }
1538
1594
  /**
1539
1595
  * Parses an audio packet, decrypting it to yield an Opus packet.
@@ -1547,9 +1603,9 @@ var VoiceReceiver = class {
1547
1603
  parsePacket(buffer, mode, nonce2, secretKey) {
1548
1604
  let packet = this.decrypt(buffer, mode, nonce2, secretKey);
1549
1605
  if (!packet) return;
1550
- if (packet[0] === 190 && packet[1] === 222) {
1551
- const headerExtensionLength = packet.readUInt16BE(2);
1552
- packet = packet.subarray(4 + 4 * headerExtensionLength);
1606
+ if (buffer.subarray(12, 14).compare(HEADER_EXTENSION_BYTE) === 0) {
1607
+ const headerExtensionLength = buffer.subarray(14).readUInt16BE();
1608
+ packet = packet.subarray(4 * headerExtensionLength);
1553
1609
  }
1554
1610
  return packet;
1555
1611
  }
@@ -2432,7 +2488,7 @@ __name(findPackageJSON, "findPackageJSON");
2432
2488
  function version(name) {
2433
2489
  try {
2434
2490
  if (name === "@discordjs/voice") {
2435
- return "0.17.1-dev.1731672303-3669d5e11";
2491
+ return "0.18.0";
2436
2492
  }
2437
2493
  const pkg = findPackageJSON((0, import_node_path.dirname)(require.resolve(name)), name, 3);
2438
2494
  return pkg?.version ?? "not found";
@@ -2456,7 +2512,8 @@ function generateDependencyReport() {
2456
2512
  addVersion("sodium-native");
2457
2513
  addVersion("sodium");
2458
2514
  addVersion("libsodium-wrappers");
2459
- addVersion("tweetnacl");
2515
+ addVersion("@stablelib/xchacha20poly1305");
2516
+ addVersion("@noble/ciphers");
2460
2517
  report.push("");
2461
2518
  report.push("FFmpeg");
2462
2519
  try {
@@ -2575,7 +2632,7 @@ async function demuxProbe(stream, probeSize = 1024, validator = validateDiscordO
2575
2632
  __name(demuxProbe, "demuxProbe");
2576
2633
 
2577
2634
  // src/index.ts
2578
- var version2 = "0.17.1-dev.1731672303-3669d5e11";
2635
+ var version2 = "0.18.0";
2579
2636
  // Annotate the CommonJS export names for ESM import in node:
2580
2637
  0 && (module.exports = {
2581
2638
  AudioPlayer,