<commit_before>/* globals analytics, communication, makeUuid, Raven, catcher, auth, log */

"use strict";

this.senderror = (function() {
  let exports = {};

  let manifest = browser.runtime.getManifest();

  // Do not show an error more than every ERROR_TIME_LIMIT milliseconds:
  const ERROR_TIME_LIMIT = 3000;

  let messages = {
    REQUEST_ERROR: {
      title: browser.i18n.getMessage("requestErrorTitle"),
      info: browser.i18n.getMessage("requestErrorDetails")
    },
    CONNECTION_ERROR: {
      title: browser.i18n.getMessage("connectionErrorTitle"),
      info: browser.i18n.getMessage("connectionErrorDetails")
    },
    LOGIN_ERROR: {
      title: browser.i18n.getMessage("requestErrorTitle"),
      info: browser.i18n.getMessage("loginErrorDetails")
    },
    LOGIN_CONNECTION_ERROR: {
      title: browser.i18n.getMessage("connectionErrorTitle"),
      info: browser.i18n.getMessage("connectionErrorDetails")
    },
    UNSHOOTABLE_PAGE: {
      title: browser.i18n.getMessage("unshootablePageErrorTitle"),
      info: browser.i18n.getMessage("unshootablePageErrorDetails")
    },
    SHOT_PAGE: {
      title: browser.i18n.getMessage("selfScreenshotErrorTitle")
    },
    MY_SHOTS: {
      title: browser.i18n.getMessage("selfScreenshotErrorTitle")
    },
    EMPTY_SELECTION: {
      title: browser.i18n.getMessage("emptySelectionErrorTitle")
    },
    generic: {
      title: browser.i18n.getMessage("genericErrorTitle"),
      info: browser.i18n.getMessage("genericErrorDetails"),
      showMessage: true
    }
  };

  communication.register("reportError", (sender, error) => {
    catcher.unhandled(error);
  });

  let lastErrorTime;

  exports.showError = function(error) {
    if (lastErrorTime && (Date.now() - lastErrorTime) < ERROR_TIME_LIMIT) {
      return;
    }
    lastErrorTime = Date.now();
    let id = makeUuid();
    let popupMessage = error.popupMessage || "generic";
    if (!messages[popupMessage]) {
      popupMessage = "generic";
    }
    let title = messages[popupMessage].title;
    let message = messages[popupMessage].info || '';
    let showMessage = messages[popupMessage].showMessage;
    if (error.message && showMessage) {
      if (message) {
        message += "\n" + error.message;
      } else {
        message = error.message;
      }
    }
    browser.notifications.create(id, {
      type: "basic",
      // FIXME: need iconUrl for an image, see #2239
      title,
      message
    });
  };

  exports.reportError = function(e) {
    if (!analytics.getTelemetryPrefSync()) {
      log.error("Telemetry disabled. Not sending critical error:", e);
      return;
    }
    let dsn = auth.getSentryPublicDSN();
    if (!dsn) {
      log.warn("Screenshots error:", e);
      return;
    }
    if (!Raven.isSetup()) {
      Raven.config(dsn, {allowSecretKey: true}).install();
    }
    let exception = new Error(e.message);
    exception.stack = e.multilineStack || e.stack || undefined;
    let rest = {};
    for (let attr in e) {
      if (!["name", "message", "stack", "multilineStack", "popupMessage", "version", "sentryPublicDSN", "help"].includes(attr)) {
        rest[attr] = e[attr];
      }
    }
    rest.stack = e.multilineStack || e.stack;
    Raven.captureException(exception, {
      logger: 'addon',
      tags: {version: manifest.version, category: e.popupMessage},
      message: exception.message,
      extra: rest
    });
  };
<commit_msg>Fix #2975, normalize exception stack URLs to improve Sentry grouping<commit_after>/* globals analytics, communication, makeUuid, Raven, catcher, auth, log */

"use strict";

this.senderror = (function() {
  let exports = {};

  let manifest = browser.runtime.getManifest();

  // Do not show an error more than every ERROR_TIME_LIMIT milliseconds:
  const ERROR_TIME_LIMIT = 3000;

  let messages = {
    REQUEST_ERROR: {
      title: browser.i18n.getMessage("requestErrorTitle"),
      info: browser.i18n.getMessage("requestErrorDetails")
    },
    CONNECTION_ERROR: {
      title: browser.i18n.getMessage("connectionErrorTitle"),
      info: browser.i18n.getMessage("connectionErrorDetails")
    },
    LOGIN_ERROR: {
      title: browser.i18n.getMessage("requestErrorTitle"),
      info: browser.i18n.getMessage("loginErrorDetails")
    },
    LOGIN_CONNECTION_ERROR: {
      title: browser.i18n.getMessage("connectionErrorTitle"),
      info: browser.i18n.getMessage("connectionErrorDetails")
    },
    UNSHOOTABLE_PAGE: {
      title: browser.i18n.getMessage("unshootablePageErrorTitle"),
      info: browser.i18n.getMessage("unshootablePageErrorDetails")
    },
    SHOT_PAGE: {
      title: browser.i18n.getMessage("selfScreenshotErrorTitle")
    },
    MY_SHOTS: {
      title: browser.i18n.getMessage("selfScreenshotErrorTitle")
    },
    EMPTY_SELECTION: {
      title: browser.i18n.getMessage("emptySelectionErrorTitle")
    },
    generic: {
      title: browser.i18n.getMessage("genericErrorTitle"),
      info: browser.i18n.getMessage("genericErrorDetails"),
      showMessage: true
    }
  };

  communication.register("reportError", (sender, error) => {
    catcher.unhandled(error);
  });

  let lastErrorTime;

  exports.showError = function(error) {
    if (lastErrorTime && (Date.now() - lastErrorTime) < ERROR_TIME_LIMIT) {
      return;
    }
    lastErrorTime = Date.now();
    let id = makeUuid();
    let popupMessage = error.popupMessage || "generic";
    if (!messages[popupMessage]) {
      popupMessage = "generic";
    }
    let title = messages[popupMessage].title;
    let message = messages[popupMessage].info || '';
    let showMessage = messages[popupMessage].showMessage;
    if (error.message && showMessage) {
      if (message) {
        message += "\n" + error.message;
      } else {
        message = error.message;
      }
    }
    browser.notifications.create(id, {
      type: "basic",
      // FIXME: need iconUrl for an image, see #2239
      title,
      message
    });
  };

  exports.reportError = function(e) {
    if (!analytics.getTelemetryPrefSync()) {
      log.error("Telemetry disabled. Not sending critical error:", e);
      return;
    }
    let dsn = auth.getSentryPublicDSN();
    if (!dsn) {
      log.warn("Screenshots error:", e);
      return;
    }
    if (!Raven.isSetup()) {
      Raven.config(dsn, {allowSecretKey: true}).install();
    }
    let exception = new Error(e.message);
    exception.stack = e.multilineStack || e.stack || undefined;

    // To improve Sentry reporting & grouping, replace the
    // moz-extension://$uuid base URL with a generic resource:// URL.
    exception.stack = exception.stack.replace(
      /moz-extension:\/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g,
      "resource://screenshots-addon"
    );
    let rest = {};
    for (let attr in e) {
      if (!["name", "message", "stack", "multilineStack", "popupMessage", "version", "sentryPublicDSN", "help"].includes(attr)) {
        rest[attr] = e[attr];
      }
    }
    rest.stack = exception.stack;
    Raven.captureException(exception, {
      logger: 'addon',
      tags: {category: e.popupMessage},
      release: manifest.version,
      message: exception.message,
      extra: rest
    });
  };

{'fraction_non_alphanumeric': 0.1406575114440283, 'fraction_numerical': 0.013316687473990845, 'mean_word_length': 3.0711462450592886, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6803909', 'n_tokens_mistral': 2306, 'n_tokens_neox': 2248, 'n_words': 429}