@sonata-sdk/voice 0.1.1 → 0.1.3
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/dist/connection.js +60 -3
- package/dist/gateway.d.ts +2 -0
- package/dist/gateway.js +54 -5
- package/package.json +1 -1
package/dist/connection.js
CHANGED
|
@@ -19,6 +19,7 @@ export class VoiceConnection extends EventEmitter {
|
|
|
19
19
|
#token = '';
|
|
20
20
|
#endpoint = '';
|
|
21
21
|
#destroyed = false;
|
|
22
|
+
#daveReady = false;
|
|
22
23
|
constructor(opts) {
|
|
23
24
|
super();
|
|
24
25
|
this.guildId = opts.guildId;
|
|
@@ -26,6 +27,7 @@ export class VoiceConnection extends EventEmitter {
|
|
|
26
27
|
this.channelId = opts.channelId;
|
|
27
28
|
this.encryption = opts.encryption ?? null;
|
|
28
29
|
this.#gateway = new VoiceGateway(opts.guildId, opts.userId);
|
|
30
|
+
this.#gateway.setDaveSession(opts.channelId);
|
|
29
31
|
this.#udp = new UdpSocket();
|
|
30
32
|
this.#setupListeners();
|
|
31
33
|
}
|
|
@@ -36,14 +38,15 @@ export class VoiceConnection extends EventEmitter {
|
|
|
36
38
|
}).catch((err) => this.emit('error', err));
|
|
37
39
|
});
|
|
38
40
|
this.#gateway.on('sessionDescription', (payload) => {
|
|
39
|
-
|
|
41
|
+
const secretKey = Buffer.from(payload.secret_key);
|
|
42
|
+
this.#udp.setSecretKey(secretKey);
|
|
40
43
|
this.udpInfo = {
|
|
41
44
|
ssrc: this.#gateway.ssrc,
|
|
42
45
|
ip: this.#gateway.ip,
|
|
43
46
|
port: this.#gateway.port,
|
|
44
|
-
secretKey
|
|
47
|
+
secretKey,
|
|
45
48
|
};
|
|
46
|
-
this.#encryption = new AudioEncryption(payload.mode,
|
|
49
|
+
this.#encryption = new AudioEncryption(payload.mode, secretKey, this.#gateway.ssrc);
|
|
47
50
|
this.state = { status: 'connected', reason: null, code: null };
|
|
48
51
|
this.emit('stateChange', { status: 'connecting' }, this.state);
|
|
49
52
|
this.emit('ready');
|
|
@@ -58,6 +61,60 @@ export class VoiceConnection extends EventEmitter {
|
|
|
58
61
|
this.ping = Date.now() - d.t;
|
|
59
62
|
});
|
|
60
63
|
this.#udp.on('error', (err) => this.emit('error', err));
|
|
64
|
+
// DAVE handshake handlers
|
|
65
|
+
this.#gateway.on('dave_external_sender', (data) => {
|
|
66
|
+
const session = this.#gateway.daveSession;
|
|
67
|
+
if (!session)
|
|
68
|
+
return this.emit('error', new Error('No DAVE session'));
|
|
69
|
+
try {
|
|
70
|
+
session.setExternalSender(data);
|
|
71
|
+
const keyPackage = session.getSerializedKeyPackage();
|
|
72
|
+
this.#gateway.sendDaveOp(26, keyPackage); // dave_mls_key_package
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
this.emit('error', new Error(`DAVE external sender: ${e.message}`));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
this.#gateway.on('dave_proposals', (d) => {
|
|
79
|
+
const session = this.#gateway.daveSession;
|
|
80
|
+
if (!session)
|
|
81
|
+
return this.emit('error', new Error('No DAVE session'));
|
|
82
|
+
try {
|
|
83
|
+
const { operationType, proposals } = d;
|
|
84
|
+
const result = session.processProposals(operationType, Buffer.from(proposals));
|
|
85
|
+
if (result.commit && result.welcome) {
|
|
86
|
+
this.#gateway.sendDaveOp(28, Buffer.concat([result.commit, result.welcome]));
|
|
87
|
+
}
|
|
88
|
+
else if (result.commit) {
|
|
89
|
+
this.#gateway.sendDaveOp(28, result.commit);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
this.emit('error', new Error(`DAVE proposals: ${e.message}`));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
this.#gateway.on('dave_commit', (data) => {
|
|
97
|
+
const session = this.#gateway.daveSession;
|
|
98
|
+
if (!session)
|
|
99
|
+
return this.emit('error', new Error('No DAVE session'));
|
|
100
|
+
try {
|
|
101
|
+
session.processCommit(data);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
this.emit('error', new Error(`DAVE commit: ${e.message}`));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
this.#gateway.on('dave_welcome', (data) => {
|
|
108
|
+
const session = this.#gateway.daveSession;
|
|
109
|
+
if (!session)
|
|
110
|
+
return this.emit('error', new Error('No DAVE session'));
|
|
111
|
+
try {
|
|
112
|
+
session.processWelcome(data);
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
this.emit('error', new Error(`DAVE welcome: ${e.message}`));
|
|
116
|
+
}
|
|
117
|
+
});
|
|
61
118
|
}
|
|
62
119
|
voiceStateUpdate(obj) {
|
|
63
120
|
this.#sessionId = obj.sessionId ?? obj.session_id ?? this.#sessionId;
|
package/dist/gateway.d.ts
CHANGED
|
@@ -26,11 +26,13 @@ export declare class VoiceGateway extends EventEmitter {
|
|
|
26
26
|
get secretKey(): Buffer<ArrayBufferLike> | null;
|
|
27
27
|
get daveProtocolVersion(): number;
|
|
28
28
|
constructor(guildId: string, userId: string);
|
|
29
|
+
setDaveSession(channelId: string): void;
|
|
29
30
|
voiceStateUpdate(sessionId: string): void;
|
|
30
31
|
voiceServerUpdate(token: string, endpoint: string, channelId?: string): void;
|
|
31
32
|
connect(): void;
|
|
32
33
|
sendBinary(data: Buffer): void;
|
|
33
34
|
sendSelectProtocol(address: string, port: number, mode: string): void;
|
|
35
|
+
sendDaveOp(op: number, data: Buffer): void;
|
|
34
36
|
setSpeaking(value: number, delay?: number): void;
|
|
35
37
|
close(): void;
|
|
36
38
|
}
|
package/dist/gateway.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import WebSocket from 'ws';
|
|
2
2
|
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { DAVESession, DAVE_PROTOCOL_VERSION, generateP256Keypair } from '@snazzah/davey';
|
|
3
4
|
const DISCORD_VOICE_VERSION = 8;
|
|
4
5
|
const DISCORD_VOICE_URL_TEMPLATE = 'wss://{endpoint}/?v={v}';
|
|
5
6
|
export class VoiceGateway extends EventEmitter {
|
|
@@ -19,6 +20,9 @@ export class VoiceGateway extends EventEmitter {
|
|
|
19
20
|
#secretKey = null;
|
|
20
21
|
#connected = false;
|
|
21
22
|
#daveProtocolVersion = 0;
|
|
23
|
+
#daveSession = null;
|
|
24
|
+
#daveKeypair = null;
|
|
25
|
+
#pendingBinary = [];
|
|
22
26
|
get connected() { return this.#connected; }
|
|
23
27
|
get ssrc() { return this.#ssrc; }
|
|
24
28
|
get ip() { return this.#ip; }
|
|
@@ -31,14 +35,27 @@ export class VoiceGateway extends EventEmitter {
|
|
|
31
35
|
this.#guildId = guildId;
|
|
32
36
|
this.#userId = userId;
|
|
33
37
|
}
|
|
38
|
+
setDaveSession(channelId) {
|
|
39
|
+
try {
|
|
40
|
+
this.#daveKeypair = generateP256Keypair();
|
|
41
|
+
this.#channelId = channelId;
|
|
42
|
+
this.#daveSession = new DAVESession(Number(DAVE_PROTOCOL_VERSION), this.#userId, channelId, this.#daveKeypair);
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
this.emit('error', new Error(`DAVE init failed: ${e.message}`));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
34
48
|
voiceStateUpdate(sessionId) {
|
|
35
49
|
this.#sessionId = sessionId;
|
|
36
50
|
}
|
|
37
51
|
voiceServerUpdate(token, endpoint, channelId) {
|
|
38
52
|
this.#token = token;
|
|
39
53
|
this.#endpoint = endpoint.replace(/^wss:\/\//, '').replace(/\/\?v=\d+$/, '');
|
|
40
|
-
if (channelId)
|
|
54
|
+
if (channelId) {
|
|
41
55
|
this.#channelId = channelId;
|
|
56
|
+
if (!this.#daveSession)
|
|
57
|
+
this.setDaveSession(channelId);
|
|
58
|
+
}
|
|
42
59
|
}
|
|
43
60
|
connect() {
|
|
44
61
|
const url = DISCORD_VOICE_URL_TEMPLATE
|
|
@@ -51,12 +68,15 @@ export class VoiceGateway extends EventEmitter {
|
|
|
51
68
|
this.#ws.on('error', (err) => this.emit('error', err));
|
|
52
69
|
}
|
|
53
70
|
#onOpen() {
|
|
54
|
-
|
|
71
|
+
const identify = {
|
|
55
72
|
server_id: this.#guildId,
|
|
56
73
|
user_id: this.#userId,
|
|
57
74
|
session_id: this.#sessionId,
|
|
58
75
|
token: this.#token,
|
|
59
|
-
|
|
76
|
+
max_dave_protocol_version: Number(DAVE_PROTOCOL_VERSION),
|
|
77
|
+
supported_dave_versions: [Number(DAVE_PROTOCOL_VERSION)],
|
|
78
|
+
};
|
|
79
|
+
this.#sendOp(0, identify);
|
|
60
80
|
}
|
|
61
81
|
#onMessage(data) {
|
|
62
82
|
try {
|
|
@@ -64,12 +84,22 @@ export class VoiceGateway extends EventEmitter {
|
|
|
64
84
|
this.#handleOp(json.op, json.d);
|
|
65
85
|
}
|
|
66
86
|
catch {
|
|
67
|
-
// binary op (DAVE)
|
|
87
|
+
// binary op (DAVE protocol)
|
|
68
88
|
if (data.length > 0) {
|
|
69
|
-
this
|
|
89
|
+
this.#handleDaveBinary(data);
|
|
70
90
|
}
|
|
71
91
|
}
|
|
72
92
|
}
|
|
93
|
+
#handleDaveBinary(data) {
|
|
94
|
+
if (!this.#daveSession) {
|
|
95
|
+
this.#pendingBinary.push(data);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Try to parse DAVE binary protocol messages
|
|
99
|
+
// First byte after the opcode determines the message type
|
|
100
|
+
// DAVE messages from Discord: external sender package, proposals, commit, welcome
|
|
101
|
+
this.emit('binary', data);
|
|
102
|
+
}
|
|
73
103
|
#handleOp(op, d) {
|
|
74
104
|
switch (op) {
|
|
75
105
|
case 2: { // Ready
|
|
@@ -106,6 +136,22 @@ export class VoiceGateway extends EventEmitter {
|
|
|
106
136
|
this.emit('resumed');
|
|
107
137
|
break;
|
|
108
138
|
}
|
|
139
|
+
case 25: { // DAVE MLS External Sender Package
|
|
140
|
+
this.emit('dave_external_sender', Buffer.from(d));
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case 27: { // DAVE MLS Proposals
|
|
144
|
+
this.emit('dave_proposals', d);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case 29: { // DAVE MLS Announce Commit Transition
|
|
148
|
+
this.emit('dave_commit', Buffer.from(d));
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 30: { // DAVE MLS Welcome
|
|
152
|
+
this.emit('dave_welcome', Buffer.from(d));
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
109
155
|
}
|
|
110
156
|
}
|
|
111
157
|
#onClose(code, reason) {
|
|
@@ -139,6 +185,9 @@ export class VoiceGateway extends EventEmitter {
|
|
|
139
185
|
data: { address, port, mode },
|
|
140
186
|
});
|
|
141
187
|
}
|
|
188
|
+
sendDaveOp(op, data) {
|
|
189
|
+
this.#sendOp(op, [...data]);
|
|
190
|
+
}
|
|
142
191
|
setSpeaking(value, delay = 0) {
|
|
143
192
|
this.#sendOp(5, { speaking: value, delay, ssrc: this.#ssrc });
|
|
144
193
|
}
|