tw93/Pake · error · TypeError

Badge count must be a finite number.

Error message

Badge count must be a finite number.

What it means

This TypeError comes from Pake's injected polyfill for the Web Badging API (src-tauri/src/inject/event.js). Pake replaces navigator.setAppBadge with a bridge to its Rust commands (set_dock_badge / set_dock_badge_label), and normalizeBadgeCount validates the argument before invoking Rust: it must be typeof 'number' and Number.isFinite. Like the native browser API, the polyfill converts the throw into a rejected Promise, so you see it as an unhandled promise rejection (or a caught error) from navigator.setAppBadge(...).

Source

Thrown at src-tauri/src/inject/event.js:1323

// pages running inside the webview can drive the macOS dock badge (and
// taskbar badge on Linux/Windows). Installs synchronously instead of waiting
// for DOMContentLoaded so feature-detection on Notification/setAppBadge
// returns the polyfill before site scripts run.
(function () {
  const invoke = window.__TAURI__?.core?.invoke;
  if (!invoke) return;

  let permVal = "granted";
  let lastNotifTime = 0;
  let lastNotif = null;
  // Pages that drive the badge directly via setAppBadge own its lifecycle;
  // notifications-driven counts auto-clear on the next user interaction.
  let pageManagedBadge = false;
  let autoBadgeActive = false;

  const normalizeBadgeCount = (count) => {
    if (typeof count !== "number" || !Number.isFinite(count)) {
      throw new TypeError("Badge count must be a finite number.");
    }
    const normalized = Math.floor(count);
    return normalized > 0 ? Math.min(normalized, 99999) : null;
  };
  const setBadge = (count) => {
    pageManagedBadge = true;
    autoBadgeActive = false;
    return invoke("set_dock_badge", { count }).catch(() => {});
  };
  const clearBadge = () => invoke("clear_dock_badge").catch(() => {});
  const setLabel = (label) => {
    pageManagedBadge = true;
    autoBadgeActive = false;
    return invoke("set_dock_badge_label", { label }).catch(() => {});
  };
  const incrementAutoBadge = () => {
    if (pageManagedBadge) return Promise.resolve();
    autoBadgeActive = true;

View on GitHub (pinned to 88bdbbfd86)

Solutions

  1. Coerce the value to a finite number before calling: const n = Number(unread); if (Number.isFinite(n)) navigator.setAppBadge(Math.trunc(n));
  2. If the count is missing/invalid, call navigator.setAppBadge() with no argument (Pake shows a plain dot) or navigator.clearAppBadge()
  3. Attach a .catch(() => {}) to the setAppBadge call so a bad value degrades to a no-op instead of an unhandled rejection
  4. Fix the data source: parse server string counts once at the API boundary (Number(payload.unread_count)) instead of coercing at the badge call site
  5. Remember 0 clears the badge in this polyfill (normalizeBadgeCount maps <=0 to null), so clamp/guard 0 intentionally

Example fix

// before
navigator.setAppBadge(unreadCount); // unreadCount = "12" or NaN -> TypeError rejection

// after
const n = Number(unreadCount);
if (Number.isFinite(n) && n > 0) {
  navigator.setAppBadge(Math.trunc(n));
} else {
  navigator.clearAppBadge();
}
Defensive patterns

Strategy: validation

Validate before calling

const raw = getUnreadCount(); // string | number | null | undefined from your data source
const n = Number(raw);
if (raw !== undefined && Number.isFinite(n) && n > 0) {
  navigator.setAppBadge(Math.trunc(n)).catch(() => {});
} else if (raw === undefined) {
  navigator.setAppBadge().catch(() => {}); // dot only
} else {
  navigator.clearAppBadge().catch(() => {});
}

Type guard

// JavaScript/TypeScript
function isBadgeCount(value: unknown): value is number {
  return typeof value === "number" && Number.isFinite(value) && value > 0;
}

// Usage:
if (isBadgeCount(unreadCount)) navigator.setAppBadge(Math.trunc(unreadCount));
else navigator.clearAppBadge();

Try / catch

// The polyfill returns Promise.reject(TypeError); it does not throw synchronously.
// Prefer .catch on the promise; keep a sync try/catch only if the same code also
// targets engines where setAppBadge may throw before returning a promise.
try {
  navigator.setAppBadge(Number(count)).catch((err) => {
    console.warn('badge update failed', err); // TypeError: Badge count must be a finite number.
  });
} catch (err) {
  console.warn('badge API unavailable', err);
}

Prevention

When it happens

Trigger: Calling navigator.setAppBadge(x) inside a Pake-packaged app where x is a string ('5'), null, an object, a parsed-JSON value that was never coerced, NaN (e.g. parseInt returning NaN), Infinity (e.g. dividing by zero), or -Infinity. Only undefined is safe (no argument shows the '•' dot). The same throw path is hit for any value where typeof count !== 'number' || !Number.isFinite(count) at event.js:1322.

Common situations: Server APIs returning unread counts as strings ('"unread_count":"12"') and passing them straight to setAppBadge; computing a count from possibly-empty data (NaN via parseInt/Number on undefined); badge counts derived from division that can hit 0 denominators; migrations from dot-only usage navigator.setAppBadge() to passing counts; sites tested only in Chrome where the same call also rejects with TypeError, but the rejection was silently swallowed by a .catch added elsewhere.


AI-assisted analysis of tw93/Pake@88bdbbfd86 (2026-08-16). Data as JSON: /api/errors/63003deb8a91c813. Report an issue: GitHub.