LadybirdBrowser/ladybird

LibWeb: Implement OfflineAudioContext

Lubrsi opened this issue · 0 comments

Required by Cloudflare Turnstile's fingerprinting. It checks for the existence of OfflineAudioContext and, if that doesn't exist, webkitOfflineAudioContext. If neither exist, it returns from the AudioContext fingerprinting test but fails to progress the challenge runner to the next test, causing it to indefinitely lock-up. (Side note, this seems to be a bug in the challenge runner, as it progresses the runner for other things not existing such as fetch, which arrived to the web platform after OfflineAudioContext)

Reduced from Turnstile:

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="challenge-stage"></div>
<script>
    function sha256Hash(stringToHash) {
        const encodedString = new TextEncoder().encode(stringToHash);
        return crypto.subtle.digest("SHA-256", encodedString).then((value) => {
            const valueAsArray = Array.from(new Uint8Array(value));
            const arrayAsHex = valueAsArray.map(num => num.toString(16).padStart(2, '0'));
            return arrayAsHex.join("");
        });
    }

    const audioContext = new OfflineAudioContext(1, 5000, 44100);

    const oscillator = audioContext.createOscillator();
    oscillator.type = "triangle";
    oscillator.frequency.value = 9998.123456;

    const dynamicsCompressor = audioContext.createDynamicsCompressor();
    dynamicsCompressor.threshold.value = -52;
    dynamicsCompressor.knee.value = 40;
    dynamicsCompressor.ratio.value = 12;
    dynamicsCompressor.attack.value = 0.0001;
    dynamicsCompressor.release.value = 0.25;

    oscillator.connect(dynamicsCompressor);

    dynamicsCompressor.connect(audioContext.destination);

    oscillator.start();
    audioContext.oncomplete = function (ev) {
        const data = ev.renderedBuffer.getChannelData(0);
        if (data && data.length > 0) {
            const joinedValues = data.join("|");
            sha256Hash(joinedValues).then(fingerprint => {
                document.getElementById("challenge-stage").innerText = `AudioContext fingerprint: ${fingerprint}`
            });
        }
    };

    audioContext.startRendering();
</script>
</body>
</html>