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

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Advertisement:

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

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);
})();