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
- Coerce the value to a finite number before calling: const n = Number(unread); if (Number.isFinite(n)) navigator.setAppBadge(Math.trunc(n));
- If the count is missing/invalid, call navigator.setAppBadge() with no argument (Pake shows a plain dot) or navigator.clearAppBadge()
- Attach a .catch(() => {}) to the setAppBadge call so a bad value degrades to a no-op instead of an unhandled rejection
- 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
- 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
- Normalize badge counts to numbers once at the API/data boundary, never coerce at the badge call site
- Validate with Number.isFinite (not !isNaN) so Infinity/objects/strings are also rejected
- Never pass values from JSON.parse directly to setAppBadge; JSON numbers are fine but string-encoded numbers are not
- Attach .catch() to every setAppBadge/clearAppBadge call so bad values degrade quietly
- Remember the polyfill's semantics: undefined shows a dot, 0 or negative clears, >99999 clamps to 99999
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.