Web Bluetooth API Spoofer

Either fully removes navigator.bluetooth or replaces it with a fixed, non-revealing response set, to prevent fingerprinting/tracking via Bluetooth adapter presence and paired devices

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Advertisement:

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

Advertisement:

// ==UserScript==
// @name         Web Bluetooth API Spoofer
// @namespace    https://violentmonkey.github.io/
// @version      1.0
// @description  Either fully removes navigator.bluetooth or replaces it with a fixed, non-revealing response set, to prevent fingerprinting/tracking via Bluetooth adapter presence and paired devices
// @author       Streampunk
// @icon         data:image/png;base64,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
// @match        *://*/*
// @run-at       document-start
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  // ===================== CONFIG =====================
  // 'block' - completely removes navigator.bluetooth, so any code checking
  //           `if (navigator.bluetooth)` or calling its methods behaves
  //           exactly as if the API never existed (indistinguishable from
  //           Firefox/Safari, which don't implement Web Bluetooth at all).
  // 'fixed' - keeps navigator.bluetooth present (so feature-detection
  //           passes) but:
  //             - getAvailability() always resolves to a fixed value
  //             - getDevices() always resolves to an empty array
  //               (no previously-paired devices exposed)
  //             - requestDevice() always rejects with the same error a
  //               real browser throws when the user cancels the device
  //               picker, so no device-selection UI leaks any info and
  //               no site can silently detect connected devices.
  const MODE = 'block'; // <-- change to 'fixed' if you prefer a fake-but-present API
  // ====================================================

  if (typeof navigator.bluetooth === 'undefined') {
    return; // API not present in this browser at all, nothing to do
  }

  if (MODE === 'block') {
    try {
      Object.defineProperty(navigator, 'bluetooth', {
        configurable: true,
        get() { return undefined; },
      });
    } catch (e) {
      try {
        const proto = Object.getPrototypeOf(navigator);
        Object.defineProperty(proto, 'bluetooth', {
          configurable: true,
          get() { return undefined; },
        });
      } catch (e2) {
        console.warn('[block-bluetooth] failed to remove navigator.bluetooth', e2);
      }
    }
    return;
  }

  if (MODE === 'fixed') {
    // Fixed availability value. `false` is the safer/less revealing choice:
    // most desktop and laptop machines without a Bluetooth adapter (or with
    // it disabled) will report false, which is a very common, low-signal
    // state. Change to `true` if you want to look like a machine that has
    // Bluetooth hardware without exposing anything about it.
    const FAKE_AVAILABILITY = false;

    const listeners = {
      availabilitychanged: [],
    };

    function notFoundError() {
      // Mirrors the real DOMException a browser throws when the user
      // dismisses the device chooser without selecting anything, or when
      // no matching device is found.
      return new DOMException(
        'User cancelled the requestDevice() chooser.',
        'NotFoundError'
      );
    }

    const fakeBluetooth = {
      getAvailability() {
        return Promise.resolve(FAKE_AVAILABILITY);
      },

      getDevices() {
        // No previously-paired/granted devices exposed.
        return Promise.resolve([]);
      },

      requestDevice() {
        // Always behaves as if the user closed the picker without
        // choosing a device, so no real device (name, id, services) is
        // ever revealed. This does not require/trigger any native UI.
        return Promise.reject(notFoundError());
      },

      onavailabilitychanged: null,

      addEventListener(type, listener) {
        if (listeners[type] && typeof listener === 'function') {
          listeners[type].push(listener);
        }
      },
      removeEventListener(type, listener) {
        if (listeners[type]) {
          const idx = listeners[type].indexOf(listener);
          if (idx !== -1) listeners[type].splice(idx, 1);
        }
      },
      dispatchEvent(event) {
        // Never actually triggered since availability never changes, kept
        // only for interface completeness in case a site calls it directly.
        const type = event && event.type;
        if (listeners[type]) {
          listeners[type].forEach((fn) => {
            try { fn.call(fakeBluetooth, event); } catch (e) {}
          });
        }
        const handlerName = 'on' + type;
        if (typeof fakeBluetooth[handlerName] === 'function') {
          try { fakeBluetooth[handlerName].call(fakeBluetooth, event); } catch (e) {}
        }
        return true;
      },
    };

    try {
      Object.defineProperty(navigator, 'bluetooth', {
        configurable: true,
        get() { return fakeBluetooth; },
      });
    } catch (e) {
      try {
        const proto = Object.getPrototypeOf(navigator);
        Object.defineProperty(proto, 'bluetooth', {
          configurable: true,
          get() { return fakeBluetooth; },
        });
      } catch (e2) {
        console.warn('[spoof-bluetooth] failed to override navigator.bluetooth', e2);
      }
    }

    return;
  }

  console.warn('[bluetooth-privacy-script] unknown MODE value:', MODE);
})();