{"record":{"id":"63003deb8a91c813","repo":"tw93/Pake","slug":"badge-count-must-be-a-finite-number","errorCode":null,"errorMessage":"Badge count must be a finite number.","messagePattern":"Badge count must be a finite number\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src-tauri/src/inject/event.js","lineNumber":1323,"sourceCode":"// pages running inside the webview can drive the macOS dock badge (and\n// taskbar badge on Linux/Windows). Installs synchronously instead of waiting\n// for DOMContentLoaded so feature-detection on Notification/setAppBadge\n// returns the polyfill before site scripts run.\n(function () {\n  const invoke = window.__TAURI__?.core?.invoke;\n  if (!invoke) return;\n\n  let permVal = \"granted\";\n  let lastNotifTime = 0;\n  let lastNotif = null;\n  // Pages that drive the badge directly via setAppBadge own its lifecycle;\n  // notifications-driven counts auto-clear on the next user interaction.\n  let pageManagedBadge = false;\n  let autoBadgeActive = false;\n\n  const normalizeBadgeCount = (count) => {\n    if (typeof count !== \"number\" || !Number.isFinite(count)) {\n      throw new TypeError(\"Badge count must be a finite number.\");\n    }\n    const normalized = Math.floor(count);\n    return normalized > 0 ? Math.min(normalized, 99999) : null;\n  };\n  const setBadge = (count) => {\n    pageManagedBadge = true;\n    autoBadgeActive = false;\n    return invoke(\"set_dock_badge\", { count }).catch(() => {});\n  };\n  const clearBadge = () => invoke(\"clear_dock_badge\").catch(() => {});\n  const setLabel = (label) => {\n    pageManagedBadge = true;\n    autoBadgeActive = false;\n    return invoke(\"set_dock_badge_label\", { label }).catch(() => {});\n  };\n  const incrementAutoBadge = () => {\n    if (pageManagedBadge) return Promise.resolve();\n    autoBadgeActive = true;","sourceCodeStart":1305,"sourceCodeEnd":1341,"githubUrl":"https://github.com/tw93/Pake/blob/88bdbbfd8615f89c25c9f2be14dd5dc3d2c56428/src-tauri/src/inject/event.js#L1305-L1341","documentation":"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(...).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nnavigator.setAppBadge(unreadCount); // unreadCount = \"12\" or NaN -> TypeError rejection\n\n// after\nconst n = Number(unreadCount);\nif (Number.isFinite(n) && n > 0) {\n  navigator.setAppBadge(Math.trunc(n));\n} else {\n  navigator.clearAppBadge();\n}","handlingStrategy":"validation","validationCode":"const raw = getUnreadCount(); // string | number | null | undefined from your data source\nconst n = Number(raw);\nif (raw !== undefined && Number.isFinite(n) && n > 0) {\n  navigator.setAppBadge(Math.trunc(n)).catch(() => {});\n} else if (raw === undefined) {\n  navigator.setAppBadge().catch(() => {}); // dot only\n} else {\n  navigator.clearAppBadge().catch(() => {});\n}","typeGuard":"// JavaScript/TypeScript\nfunction isBadgeCount(value: unknown): value is number {\n  return typeof value === \"number\" && Number.isFinite(value) && value > 0;\n}\n\n// Usage:\nif (isBadgeCount(unreadCount)) navigator.setAppBadge(Math.trunc(unreadCount));\nelse navigator.clearAppBadge();","tryCatchPattern":"// The polyfill returns Promise.reject(TypeError); it does not throw synchronously.\n// Prefer .catch on the promise; keep a sync try/catch only if the same code also\n// targets engines where setAppBadge may throw before returning a promise.\ntry {\n  navigator.setAppBadge(Number(count)).catch((err) => {\n    console.warn('badge update failed', err); // TypeError: Badge count must be a finite number.\n  });\n} catch (err) {\n  console.warn('badge API unavailable', err);\n}","preventionTips":["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"],"tags":["badging-api","typeerror","input-validation","promise-rejection","pake","webview"],"backgroundTag":null,"analyzedSha":"88bdbbfd8615f89c25c9f2be14dd5dc3d2c56428","analyzedAt":"2026-08-16T06:45:45.980Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}