WindowTelemetry.js

import Overlay from "./Overlay";
import { escapeHTML } from "./utils";

/** The overlay builder for the telemetry window for Blue Marble.
 * @description This class handles the overlay UI for the telemetry window.
 * @class WindowTelemetry
 * @since 0.88.339
 * @see {@link Overlay} for examples
 */
export default class WindowTelemetry extends Overlay {

  /** Constructor for the telemetry window
   * @param {string} name - The name of the userscript
   * @param {string} version - The version of the userscript
   * @param {number} currentTelemetryVersion - The current "version" of the data collection agreement
   * @param {string} uuid - The UUID of the user
   * @since 0.88.339
   * @see {@link Overlay#constructor}
   */
  constructor(name, version, currentTelemetryVersion, uuid) {
    super(name, version); // Executes the code in the Overlay constructor
    this.window = null; // Contains the *window* DOM tree
    this.windowID = 'bm-window-telemetry'; // The ID attribute for this window
    this.windowParent = document.body; // The parent of the window DOM tree

    this.currentTelemetryVersion = currentTelemetryVersion; // The current telemetry "version". Increment this whenever the data collection agreement is changed!
    
    this.uuid = uuid; // The UUID of the user
  }

  /** Spawns a telemetry window.
   * If another telemetry window already exists, we DON'T spawn another!
   * Parent/child relationships in the DOM structure below are indicated by indentation.
   * @since 0.88.339
   */
  async buildWindow() {

    // If the telemetry window already exists, throw an error and return early
    if (document.querySelector(`#${this.windowID}`)) {
      this.handleDisplayError('Telemetry window already exists!');
      return;
    }

    const browser = await this.apiManager.getBrowserFromUA(navigator.userAgent);
    const os = this.apiManager.getOS(navigator.userAgent);

    this.window = this.addDiv({'id': this.windowID, 'class': 'bm-window', 'style': 'height: 80vh; z-index: 9998;'})
      .addDiv({'class': 'bm-window-content'})
        .addDiv({'class': 'bm-container bm-center-vertically'})
          .addHeader(1, {'textContent': `${this.name} Telemetry`}).buildElement()
        .buildElement()
        .addHr().buildElement()
        .addDiv({'class': 'bm-container bm-flex-center', 'style': 'gap: 1.5ch; flex-wrap: wrap;'})
          .addButton({'textContent': 'Enable Telemetry'}, (instance, button) => {
            button.onclick = () => {
              this.#setTelemetryValue(this.currentTelemetryVersion);
              const element = document.getElementById(this.windowID);
              element?.remove();
            };
          }).buildElement()
          .addButton({'textContent': 'Disable Telemetry'}, (instance, button) => {
            button.onclick = () => {
              this.#setTelemetryValue(0);
              const element = document.getElementById(this.windowID);
              element?.remove();
            };
          }).buildElement()
          .addButton({'textContent': 'More Information'}, (instance, button) => {
            button.onclick = () => {
              window.open('https://github.com/SwingTheVine/Wplace-TelemetryServer#telemetry-data', '_blank', 'noopener noreferrer');
            }
          }).buildElement()
        .buildElement()
        .addDiv({'class': 'bm-container bm-scrollable'})
          .addDiv({'class': 'bm-container'})
            .addHeader(2, {'textContent': 'Legal'}).buildElement()
            .addP({'textContent': `We collect anonymous telemetry data such as your browser, OS, and script version to make the experience better for everyone. The data is never shared personally. The data is never sold. You can turn this off by pressing the "Disable" button, but keeping it on helps us improve features and reliability faster. Thank you for supporting ${this.name}!`}).buildElement()
          .buildElement()
          .addHr().buildElement()
          .addDiv({'class': 'bm-container'})
            .addHeader(2, {'textContent': 'Non-Legal Summary'}).buildElement()
            .addP({'innerHTML': `You can disable telemetry by pressing the "Disable" button. If you would like to read more about what information we collect, press the "More Information" button.<br>This is the data <em>stored</em> on our servers:`}).buildElement()
            .addUl()
              .addLi({'innerHTML': `A unique identifier (UUIDv4) generated by Blue Marble. This enables our telemetry to function without tracking your actual user ID.<br>Your UUID is: <b>${escapeHTML(this.uuid)}</b>`}).buildElement()
              .addLi({'innerHTML': `The version of Blue Marble you are using.<br>Your version is: <b>${escapeHTML(this.version)}</b>`}).buildElement()
              .addLi({'innerHTML': `Your browser type, which is used to determine Blue Marble outages and browser popularity.<br>Your browser type is: <b>${escapeHTML(browser)}</b>`}).buildElement()
              .addLi({'innerHTML': `Your OS type, which is used to determine Blue Marble outages and OS popularity.<br>Your OS type is: <b>${escapeHTML(os)}</b>`}).buildElement()
              .addLi({'innerHTML': `The date and time that Blue Marble sent the telemetry information.`}).buildElement()
            .buildElement()
            .addP({'innerHTML': `All of the data mentioned above is <b>aggregated every hour</b>. This means every hour, anything that could even remotly be considered "personal data" is deleted from our server. Here, "aggregated" data means things like "42 people used Blue Marble on Google Chrome this hour", which can't be used to identify anyone in particular.`}).buildElement()
          .buildElement()
        .buildElement()
      .buildElement()
    .buildElement().buildOverlay(this.windowParent);
  }

  /** Enables or disables telemetry based on the value passed in.
   * A value of zero will always disable telemetry.
   * A numeric, non-zero value will enable telemetry until the telemetry agreement is changed.
   * @param {number} value - The value to set the telemetry to
   * @since 0.88.339
   */
  #setTelemetryValue(value) {
    const userSettings = JSON.parse(GM_getValue('bmUserSettings', '{}'));
    userSettings.telemetry = value;
    GM.setValue('bmUserSettings', JSON.stringify(userSettings));
  }
}