76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
function toArrayBuffer(src: Buffer | Uint8Array): ArrayBuffer {
|
|
const u8 = src instanceof Uint8Array ? src : new Uint8Array(src);
|
|
return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer;
|
|
}
|
|
|
|
export async function enableAudioE2EE(pc: RTCPeerConnection, keyBuffer: Buffer | Uint8Array) {
|
|
const raw = toArrayBuffer(keyBuffer);
|
|
if (raw.byteLength !== 32) throw new Error("E2EE key must be 32 bytes");
|
|
|
|
const key = await crypto.subtle.importKey(
|
|
"raw",
|
|
raw,
|
|
{ name: "AES-CTR" },
|
|
false,
|
|
["encrypt", "decrypt"]
|
|
);
|
|
|
|
const makeCounter = (ts: number, dir: number) => {
|
|
const counter = new Uint8Array(16);
|
|
const dv = new DataView(counter.buffer);
|
|
dv.setUint32(0, dir, false); // разделяем направление
|
|
dv.setBigUint64(8, BigInt(ts), false); // nonce из timestamp кадра
|
|
return counter;
|
|
};
|
|
|
|
const attachSender = (sender: RTCRtpSender) => {
|
|
// @ts-ignore Chromium/Electron API
|
|
if (!sender.createEncodedStreams) return;
|
|
// @ts-ignore
|
|
const { readable, writable } = sender.createEncodedStreams();
|
|
|
|
const enc = new TransformStream({
|
|
async transform(frame: any, controller) {
|
|
const counter = makeCounter(frame.timestamp ?? 0, 1);
|
|
const out = await crypto.subtle.encrypt(
|
|
{ name: "AES-CTR", counter, length: 64 },
|
|
key,
|
|
frame.data
|
|
);
|
|
frame.data = new Uint8Array(out); // тот же размер
|
|
controller.enqueue(frame);
|
|
}
|
|
});
|
|
|
|
readable.pipeThrough(enc).pipeTo(writable).catch(() => {});
|
|
};
|
|
|
|
const attachReceiver = (receiver: RTCRtpReceiver) => {
|
|
// @ts-ignore Chromium/Electron API
|
|
if (!receiver.createEncodedStreams) return;
|
|
// @ts-ignore
|
|
const { readable, writable } = receiver.createEncodedStreams();
|
|
|
|
const dec = new TransformStream({
|
|
async transform(frame: any, controller) {
|
|
const counter = makeCounter(frame.timestamp ?? 0, 1);
|
|
const out = await crypto.subtle.decrypt(
|
|
{ name: "AES-CTR", counter, length: 64 },
|
|
key,
|
|
frame.data
|
|
);
|
|
frame.data = new Uint8Array(out); // тот же размер
|
|
controller.enqueue(frame);
|
|
}
|
|
});
|
|
|
|
readable.pipeThrough(dec).pipeTo(writable).catch(() => {});
|
|
};
|
|
|
|
pc.getSenders().forEach((s) => s.track?.kind === "audio" && attachSender(s));
|
|
pc.getReceivers().forEach((r) => r.track?.kind === "audio" && attachReceiver(r));
|
|
|
|
pc.addEventListener("track", (e) => {
|
|
if (e.track.kind === "audio") attachReceiver(e.receiver);
|
|
});
|
|
} |