Skip to content
quarry Artifact [1e48a1534e]
Login

Artifact [1e48a1534e]

Artifact 1e48a1534eca01026cade7460c8e3e786ccd12c44aa9e25a715f3836c0ab5876:


// Client-side exception capture. The fleet's Exceptions page is the one
// place a failure is meant to land, and until this existed it only ever
// heard about the half of the app that runs on the server: a broken
// handler on a phone was invisible unless the reader thought to say so.
//
// Deliberately small and defensive. It runs on every page, before any
// other script, and it must never itself become the thing that breaks a
// page: every path is wrapped, and a failure to report is dropped.
(function () {
  var ENDPOINT = "/api/client-error";
  // The server caps its ring anyway; this is about not making a loop on
  // the page into a flood on the wire. One report per signature per
  // page-load, and a hard ceiling however many distinct ones there are.
  var sent = Object.create(null);
  var budget = 8;

  function post(kind, message, source, line, col, stack) {
    try {
      if (budget <= 0) return;
      var sig = kind + "|" + message + "|" + source + "|" + line;
      if (sent[sig]) return;
      sent[sig] = true;
      budget--;
      var body = JSON.stringify({
        kind: kind,
        message: String(message || "").slice(0, 500),
        source: String(source || "").slice(0, 300),
        line: line || 0,
        col: col || 0,
        stack: String(stack || "").slice(0, 4000),
        url: location.pathname + location.search,
        ua: navigator.userAgent.slice(0, 200),
      });
      // sendBeacon survives the page going away, which is exactly when
      // a navigation-triggered error would otherwise be lost. fetch is
      // the fallback; both are best-effort and neither is awaited.
      if (navigator.sendBeacon) {
        navigator.sendBeacon(ENDPOINT, new Blob([body], { type: "application/json" }));
        return;
      }
      fetch(ENDPOINT, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: body,
        keepalive: true,
      }).catch(function () {});
    } catch (e) {
      /* reporting must never throw */
    }
  }

  window.addEventListener("error", function (ev) {
    // A failed <img>/<script>/<link> fires error on the ELEMENT, not the
    // window, and carries no message. Those are worth knowing about —
    // a stylesheet that 404s is a broken page — but they are a
    // different kind, not a thrown exception.
    if (ev && ev.target && ev.target !== window && ev.target.tagName) {
      var el = ev.target;
      post("resource", el.tagName.toLowerCase() + " failed to load",
        el.src || el.href || "", 0, 0, "");
      return;
    }
    post("error", (ev && ev.message) || "unknown error",
      ev && ev.filename, ev && ev.lineno, ev && ev.colno,
      ev && ev.error && ev.error.stack);
  }, true); // capture phase: resource errors do not bubble

  window.addEventListener("unhandledrejection", function (ev) {
    var r = ev && ev.reason;
    // A view transition that was skipped rejects its promise with this
    // message; Safari surfaces it as unhandled. Nothing failed for the
    // reader, so it is not an exception.
    var msg = String((r && (r.message || r)) || "");
    if (msg.indexOf("skipTransition") >= 0 || msg.indexOf("Skipping view transition") >= 0) { return; }
    post("unhandledrejection",
      (r && (r.message || r)) || "unhandled promise rejection",
      "", 0, 0, r && r.stack);
  });
})();

// Back-forward cache. Safari and Chrome restore a page from bfcache
// with the DOM it had when the reader left — a landing that said
// "waiting for checks" keeps saying it, and the streams that would have
// patched it are closed. A restored live page is a stale page, so it
// reloads; a fresh navigation (persisted false) is untouched.
(function () {
  try {
    window.addEventListener("pageshow", function (ev) {
      if (ev && ev.persisted && document.querySelector("[data-init]")) { location.reload(); }
    });
  } catch (e) {}
})();