fix(discord): use libopus structured decode errors

This commit is contained in:
Peter Steinberger
2026-06-01 23:43:08 +01:00
parent e8120a72e1
commit 2d17cb295d
6 changed files with 37 additions and 17 deletions

View File

@@ -10,7 +10,7 @@
"dependencies": {
"@discordjs/voice": "0.19.2",
"discord-api-types": "0.38.48",
"libopus-wasm": "0.1.0",
"libopus-wasm": "0.2.0",
"typebox": "1.1.39",
"undici": "8.3.0",
"ws": "8.21.0"
@@ -352,9 +352,9 @@
]
},
"node_modules/libopus-wasm": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/libopus-wasm/-/libopus-wasm-0.1.0.tgz",
"integrity": "sha512-/aurGcAVgy0GcBEUzFaX9pm9qv7zYcy8W5hBXFiK+cyqOXAX4lOS6rlFogkY9CcSIajhjnuXyixsbmziSHCDMQ==",
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/libopus-wasm/-/libopus-wasm-0.2.0.tgz",
"integrity": "sha512-x/2Gu1/C6L3IICY09zyfp984AWiOYjn53u4WfdY3yh+3KTzMN8Xkm77q3lenWMVIk5SnSzjGEkQT+VQMFHLBHQ==",
"license": "MIT",
"engines": {
"node": ">=20"

View File

@@ -10,7 +10,7 @@
"dependencies": {
"@discordjs/voice": "0.19.2",
"discord-api-types": "0.38.48",
"libopus-wasm": "0.1.0",
"libopus-wasm": "0.2.0",
"typebox": "1.1.39",
"undici": "8.3.0",
"ws": "8.21.0"

View File

@@ -1,4 +1,4 @@
import { OpusError } from "libopus-wasm";
import { OpusError, OpusErrorCode } from "libopus-wasm";
import { describe, expect, it, vi } from "vitest";
import {
analyzeVoiceReceiveError,
@@ -33,7 +33,11 @@ describe("voice receive recovery", () => {
});
it("treats corrupt Opus packets as non-recoverable decode noise", () => {
expect(analyzeVoiceReceiveError(new OpusError(-4, "not inspected", "decode"))).toEqual({
expect(
analyzeVoiceReceiveError(
new OpusError(OpusErrorCode.InvalidPacket, "not inspected", "decode"),
),
).toEqual({
message: "not inspected",
isAbortLike: false,
isDecodeCorruption: true,
@@ -42,6 +46,23 @@ describe("voice receive recovery", () => {
});
});
it("treats structurally equivalent Opus errors as decode corruption", () => {
const analysis = analyzeVoiceReceiveError({
name: "OpusError",
message: "libopus decode failed (-4): corrupted stream",
code: OpusErrorCode.InvalidPacket,
codeName: "InvalidPacket",
operation: "decode",
});
expect(analysis).toMatchObject({
isAbortLike: false,
isDecodeCorruption: true,
shouldAttemptPassthrough: false,
countsAsDecryptFailure: false,
});
});
it("does not classify corrupt Opus packet text without the Opus error contract", () => {
expect(
analyzeVoiceReceiveError(new Error("libopus decode failed (-4): corrupted stream")),

View File

@@ -1,4 +1,4 @@
import { OpusError } from "libopus-wasm";
import { OpusErrorCode, isOpusError } from "libopus-wasm";
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
const DECRYPT_FAILURE_WINDOW_MS = 30_000;
@@ -6,7 +6,6 @@ const DECRYPT_FAILURE_RECONNECT_THRESHOLD = 3;
const DECRYPT_FAILURE_MARKER = "DecryptionFailed(";
const DAVE_PASSTHROUGH_DISABLED_MARKER = "UnencryptedWhenPassthroughDisabled";
const WASM_MEMORY_ACCESS_MARKER = "memory access out of bounds";
const OPUS_INVALID_PACKET_CODE = -4;
export const DAVE_RECEIVE_PASSTHROUGH_INITIAL_EXPIRY_SECONDS = 30;
export const DAVE_RECEIVE_PASSTHROUGH_REARM_EXPIRY_SECONDS = 15;
@@ -85,8 +84,8 @@ function isAbortLikeReceiveError(err: unknown): boolean {
function isOpusDecodeInvalidPacketError(err: unknown): boolean {
return (
err instanceof OpusError &&
err.code === OPUS_INVALID_PACKET_CODE &&
isOpusError(err) &&
err.code === OpusErrorCode.InvalidPacket &&
(err.operation === "decode" || err.operation === "decodeFloat")
);
}

10
pnpm-lock.yaml generated
View File

@@ -678,8 +678,8 @@ importers:
specifier: 0.38.48
version: 0.38.48
libopus-wasm:
specifier: 0.1.0
version: 0.1.0
specifier: 0.2.0
version: 0.2.0
typebox:
specifier: 1.1.39
version: 1.1.39
@@ -5541,8 +5541,8 @@ packages:
resolution: {integrity: sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==}
engines: {node: '>=22.0.0'}
libopus-wasm@0.1.0:
resolution: {integrity: sha512-/aurGcAVgy0GcBEUzFaX9pm9qv7zYcy8W5hBXFiK+cyqOXAX4lOS6rlFogkY9CcSIajhjnuXyixsbmziSHCDMQ==}
libopus-wasm@0.2.0:
resolution: {integrity: sha512-x/2Gu1/C6L3IICY09zyfp984AWiOYjn53u4WfdY3yh+3KTzMN8Xkm77q3lenWMVIk5SnSzjGEkQT+VQMFHLBHQ==}
engines: {node: '>=20'}
libsignal@6.0.0:
@@ -11150,7 +11150,7 @@ snapshots:
kysely@0.29.2: {}
libopus-wasm@0.1.0: {}
libopus-wasm@0.2.0: {}
libsignal@6.0.0:
dependencies:

View File

@@ -17,7 +17,7 @@ minimumReleaseAgeExclude:
- "basic-ftp"
- "baileys@7.0.0-rc13"
- "hono"
- "libopus-wasm@0.1.0"
- "libopus-wasm@0.2.0"
- "libsignal@6.0.0"
- "openclaw"
- "protobufjs"