Skip to content

Sillyfrogster/dave.ts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dave.ts

Discord DAVE support for Node.js and Bun voice libraries.

dave.ts handles DAVE messages and protects Opus audio. It also includes helpers for Voice Gateway payloads, UDP discovery, RTP packets, and transport encryption. Your voice library keeps control of sockets, timing, audio codecs, and SSRC mapping.

The library is experimental. It still needs live Discord testing and an independent security review.

Install

npm install dave.ts
pnpm add dave.ts
bun add dave.ts

Node.js 20.19 or newer, or Bun 1.3.14 or newer, is required. The package is ESM-only.

Create a DAVE connection

Create one DAVE connection for each Voice Gateway connection.

import { createDaveVoiceConnection } from "dave.ts";

const dave = await createDaveVoiceConnection({
  channelId,
  selfUserId,
  sendGatewayMessage(message) {
    voiceSocket.send(message);
  },
});

voiceSocket.binaryType = "arraybuffer";

const { dispatch, notices } = await dave.handleGatewayMessage(message);
handleVoiceDispatch(dispatch);
handleDaveNotices(notices);

Pass every Voice Gateway text or binary message to handleGatewayMessage(). The method sends required DAVE replies before it resolves.

lastSequence holds the latest Voice Gateway v8 sequence. Use it in Heartbeat and Resume payloads.

protocolVersion is undefined during negotiation. It becomes 0 for passthrough audio or 1 for protected audio after the transition completes.

Call await dave.close() when the Voice Gateway connection closes.

Protect audio

Call transformOpus() after Opus encoding and before RTP packet creation.

const result = dave.transformOpus({
  direction: "send",
  frame: encodedOpusPacket,
});

if (result.ok) {
  sendProtectedOpus(result.frame);
}

Run received audio through DAVE before Opus decoding.

const result = dave.transformOpus({
  direction: "receive",
  frame: protectedOpusPacket,
  senderId: userIdForSsrc(ssrc),
});

if (result.ok) {
  opusDecoder.decode(result.frame);
}

Failed media returns a typed drop result.

Encrypt RTP packets

Create one transport cryptor from the Session Description mode and secret key. Keep it for the life of that key.

import { createVoiceRtpCryptor } from "dave.ts";

const transport = createVoiceRtpCryptor({
  mode,
  secretKey,
});

const packet = transport.encryptRtpPacket({
  payload: protectedOpusPacket,
  sequence,
  ssrc,
  timestamp,
});

udpSocket.send(packet);

const received = transport.decryptRtpPacket(inboundPacket);
if (received.ok) {
  receiveProtectedOpus(received.payload, received.ssrc);
}

Both current Discord modes are supported.

  • aead_aes256_gcm_rtpsize
  • aead_xchacha20_poly1305_rtpsize

Call transport.close() when the key expires.

Use lower level RTP helpers

encodeOpusRtpPacket() creates a basic RTP packet with payload type 0x78. decodeRtpPacket() splits an RTP packet at the unencrypted rtpsize prefix. After decrypting the body yourself, use extractDecryptedRtpPayload() to remove RTP extension data and padding.

Build Voice Gateway payloads

import {
  DAVE_MAX_PROTOCOL_VERSION,
  encodeVoiceGatewayClientMessage,
} from "dave.ts";

voiceSocket.send(
  encodeVoiceGatewayClientMessage({
    kind: "identify",
    maxDaveProtocolVersion: DAVE_MAX_PROTOCOL_VERSION,
    selfUserId,
    serverId,
    sessionId,
    token,
  }),
);

voiceSocket.send(
  encodeVoiceGatewayClientMessage({
    kind: "heartbeat",
    nonce: Date.now(),
    sequenceAck: dave.lastSequence,
  }),
);

Identify, Select Protocol, Resume, Heartbeat, and Speaking are supported.

Discover the UDP address

import {
  decodeIpDiscoveryResponse,
  encodeIpDiscoveryRequest,
} from "dave.ts";

udpSocket.send(encodeIpDiscoveryRequest(ssrc));

const { address, port } = decodeIpDiscoveryResponse(responsePacket);

Use the lower level session

Use createDaveSession() if your library already handles Voice Gateway envelopes and ordered writes. Most integrations should use createDaveVoiceConnection().

Handle errors

Setup and lifecycle failures use DaveError with a stable code. Packet and payload validation failures throw RangeError. Expected media and transport decryption failures return typed drop results.

Develop

bun install
bun run check
bun run build

License

MIT

About

No description, website, or topics provided.

Resources

License

Security policy

Stars

5 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors