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

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu betiği yüklemek için bir betik yöneticisi eklentisi yüklemeniz gerekecektir.

(Zaten bir betik yöneticim var, hadi yükleyelim!)

Advertisement:

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAfbSURBVHhe7Z1bbBRVHIcXdlsKEmgtBYNENIEiAiaICCHeLyFKiCYaw4PRaowaXzRqjDwYnjRGIom++aJ9gRgNXmJiISiCIsYHJfGCxRcQUAMBSwsCLrT6+213yHH87+7Mzpw550zPl3zs7ux0d+Z8nJndsi0FjycPdMPNcKgqr8+BeUDaNy4bM3Bn/4T/hByAy6DLcPtPwPC+cX/zHXnx92c64L0Tuxdtwc3wAARycFyNXCtuIGdyYTz/yCl3w95JC5bcOHpTZCrcCl2LzO3ldnP7a3F79TJ/YOZeAvuX7RkaaeteJP3tDsuZsBy6QKOZGzgI8zeDeWjGxYet58tzy8WWcaWOztE76sOZwEO57ZGjzNyAT/lHrgIj7nRcbEXcpeVSa9x9CyLberiOE5cvINfySm4CIy7f9nyJuNc2ETeAg8dBtG0mL4F9MEpcHprvhL/wRi4CIy4H4KvW4XPdiDtudGnTBDPZlsjct22Qp55GMO5K+E3lFnA+MOKuwsVOOH1y+XRlWQrYEjlRXOJsYIQtwqdx9X14EZeNjEs6ef+D6ciJ4xInAyNsCRcvw1dhK5fh3Fs40TaFV9PEVORU4hLnAiMug26Az8IWLmNcnHt5VQdZR04tLnEqMOK24eI1+CQsclkKcTlIjQgi634LlWpc4kzg6sxl3CdgEa+YC+1nhxLFLbZ3FhbuOLinNG0GvzPUCEbW+RYq9bjEicCIy0PxevgYLFVmbbEl8Tl34rxFhZaLu25e0Nc/FCOyjsO1lrjE+sCIy0PxOsjD8mjclM+34ye0XWYwsra4xOrAiMv3PT3wORySS0kPyfUwFFlrXGJt4GrcG+B6zNoJaRySG5FxZO1xic0zeD7chLgdGt8C/Y+MImcSl1gZGLP3clxsQ9xLs4wboDlyZnGJdYERt6tl+FwfzrczTcQN0BQ507jEqsArdx+eOrl8euO5Yku37vNtFFKOnHlcYk3g1bsOdB2d3PnBqdZJt+GmNduVUmQjcYkVA/nGxncXt/99cheu3gKtO20kjGwsLjE+mIi7rr9z9uc/ds2x+nO8TUbme3hjcYnRwL29vQcOTZnx4tczr+aAWE8Tkd+CxuISk4En9fT0rH1z/Suvl4/+3l9dZj2VyJ/8PIjI/DGRRkT5BIK2uMRU4GvgPrhp6Iu+Z/rvu+7KA88/eHzk7JmDlXstZ3zbxNmIPBAxcj20xiUmAvPjNR/BWZVbYPjE8cLAlvc6f7prfjsi/1pdbDUpRNYel5gIvBpeiKty/tiRKYjcMQYiZxKXmAh8RfVSxMXIs17Y8EP1ZhQyi0tMBN5fvayJS5HLfxzae/ilp1ZUbzaCn+vNLC4xEfhjeHj0am1ciMy4+9asmH9+4FiUV8sjkJ/hziwuMRH4L3gP5KGqLjZHjhmXP+33KNxRuZUhJgKTb+GtkD+JXpcLkc+ctiZy+chvzcR9u3IrY0wFJt/BO2C0yKuusiJyJe79y+PG5Xe0jGAyMHEqsmtxienAxInILsYlNgQmVkd2NS6xJTCxMrLLcYlNgYlVkV2PS2wLTKyInIe4xMbAxGjkvMQltgYmRiLnKS6xOTDJNHLe4hLbA5NMIucxLnEhMNEaOa9xXYSf5ToOOch1LU2bMbhw+/7txfZO8X7K++a9s/uzUsc0/lOeuE5IrvcI9Ggk6i/jpFHW83EtJPJMTkkf1wBZRfZxDaI7so9rAXHOyXH0cS0i7Zns41pIWpF9XItJGtnHdYBmz8k+rkMw8kkohZTMbVxXvhcdl7mw8kvCI8IP4+8dveqxnYcgZ6Q0U+vJw7ruXxfsScgDsJm4gYwc9ReaeTImadxAH9lC0oob6CNbRLPn3Eb6c7IFpD1zw/qZbBDdcQN9ZANkFTfQR86QOOfcKOsxnrQ8rD8nZ0Ccmcv1+O1H6T7VOP9A4WeyRpqJS6T7VYmPbJhm4xJpHdUAH9kQSeISaT1VFR85Y5LGJdK6qmF85IxIIy6R1leV8JE1k1ZcIn2Nai18ZE2kGZdIX6daDx85ZdKOS6SvVW2Ej5wSOuIS6etVo+AjJ0RXXCI9hmpUfOQm0RmXSI+jGgcfOSa64xLpsVTj4iNHJIu4RHo81WbwkRuQVVwiPaZqs8T5CQqutxSOCa6Hw1AaiLBJ4xLpcVWTECfyIRjnw/hOwv+o8RSUBiBsGnGJ9NiqSYkTeQ3MLYzLX4Mk7XjYtOIS6fFV0+BxKD122LUwl5iKS6TnUE3KmJ/BJuMS6XlUkzDmz8Fx4vKF18MwbaTnUm2WMf8qOk5c/vjmTVAH0vOpNkPcuFw/V8SJq3sApOdUjYtN+2YE2wZAel7VOPi40LYBkJ5bNSo+LrRxAKTnV42CjwttHQBpG1Qb4eNCmwdA2g7Vevi40PYBkLZFtRY+LnRhAKTtUZXwcaErAyBtk2oYHxe6NADSdqmq+LjQtQGQtk01wMeFLg6AtH2qxMeFrg6AtI2qPi50eQCk7VTlNkvLw/q40MYBkLY1rj4utHUApO2No48LbR4AaZuj6uNC2wdA2u4o+rjQhQGQtr2RPi50ZQCk7a9nLuN2wzzGJdI+1DKXcclmKO1wWBcHQNoPydzGJYNQ2mlVVwdA2pewuY5LGgV2eQDyvG+RqXeIdn0A8rxvkan1IisPA1Br3wbgmIgbwIHg3/ahqrzOZXlA2rc50ONxmULhX4zPDW+4HAj6AAAAAElFTkSuQmCC
// @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);
})();