122 lines
4.1 KiB
JavaScript
122 lines
4.1 KiB
JavaScript
(function(root, factory) {
|
|
if (typeof module === 'object' && module.exports) module.exports = factory;
|
|
else root.createMobileAppBadge = factory;
|
|
})(typeof self !== 'undefined' ? self : this, function createMobileAppBadge({
|
|
control,
|
|
status,
|
|
container,
|
|
navigator,
|
|
storage,
|
|
serviceWorker,
|
|
}) {
|
|
const ENABLED_KEY = 'stackchain.app-badge.enabled.v1';
|
|
let enabled = false;
|
|
const confirmedCounts = {updates:0, following:0};
|
|
let renderedCount = null;
|
|
|
|
|
|
function available() {
|
|
return typeof navigator?.setAppBadge === 'function'
|
|
&& typeof navigator?.clearAppBadge === 'function';
|
|
}
|
|
|
|
async function syncPreference() {
|
|
try {
|
|
const registration = await serviceWorker?.ready;
|
|
registration?.active?.postMessage({type:'stackchain-app-badge-preference', enabled});
|
|
return Boolean(registration?.active);
|
|
} catch (_error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function syncCount(channel, count) {
|
|
if (!enabled) return;
|
|
try {
|
|
const registration = await serviceWorker?.ready;
|
|
registration?.active?.postMessage({type:'stackchain-app-badge-count', channel, count});
|
|
} catch (_error) { /* A later refresh can retry. */ }
|
|
}
|
|
|
|
async function render() {
|
|
const confirmedCount = Math.min(9999, confirmedCounts.updates + confirmedCounts.following);
|
|
if (!enabled || !available() || renderedCount === confirmedCount) return true;
|
|
try {
|
|
if (confirmedCount > 0) await navigator.setAppBadge(confirmedCount);
|
|
else await navigator.clearAppBadge();
|
|
renderedCount = confirmedCount;
|
|
if (status) status.textContent = 'App icon badge is on and up to date.';
|
|
return true;
|
|
} catch (_error) {
|
|
if (status) status.textContent = 'Could not update the app icon badge. Stackchain still has the confirmed count.';
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function change() {
|
|
enabled = Boolean(control?.checked);
|
|
if (enabled) storage?.setItem(ENABLED_KEY, 'true');
|
|
else storage?.removeItem(ENABLED_KEY);
|
|
await syncPreference();
|
|
if (enabled) {
|
|
if (confirmedCounts.updates > 0) await syncCount('updates', confirmedCounts.updates);
|
|
if (confirmedCounts.following > 0) await syncCount('following', confirmedCounts.following);
|
|
}
|
|
if (!enabled && available()) {
|
|
confirmedCounts.updates = 0;
|
|
confirmedCounts.following = 0;
|
|
try {
|
|
await navigator.clearAppBadge();
|
|
renderedCount = null;
|
|
if (status) status.textContent = 'App icon badge is off.';
|
|
return true;
|
|
} catch (_error) {
|
|
if (status) status.textContent = 'App icon badge is off, but the browser could not clear the old count.';
|
|
return false;
|
|
}
|
|
}
|
|
return render();
|
|
}
|
|
|
|
function start() {
|
|
if (!available()) {
|
|
if (container) container.hidden = true;
|
|
if (control) control.disabled = true;
|
|
if (status) status.textContent = 'App icon badges are unavailable in this browser.';
|
|
return false;
|
|
}
|
|
enabled = storage?.getItem(ENABLED_KEY) === 'true';
|
|
if (enabled) void syncPreference();
|
|
if (control) {
|
|
control.checked = enabled;
|
|
control.addEventListener('change', change);
|
|
}
|
|
if (status) status.textContent = enabled ? 'App icon badge is on.' : 'App icon badge is off.';
|
|
return true;
|
|
}
|
|
|
|
function readiness() {
|
|
if (!available()) return {state:'unavailable', detail:'App icon badges are unavailable in this browser.'};
|
|
return enabled
|
|
? {state:'complete', detail:'The app icon shows review attention.'}
|
|
: {state:'incomplete', detail:'Show review attention without opening Stackchain.'};
|
|
}
|
|
|
|
async function enable() {
|
|
if (!available()) return false;
|
|
if (control) control.checked = true;
|
|
await change();
|
|
return true;
|
|
}
|
|
|
|
async function reconcile(channel, count, authoritative = false) {
|
|
if (!Object.hasOwn(confirmedCounts, channel)
|
|
|| authoritative !== true || !Number.isSafeInteger(count) || count < 0 || count > 9999) return false;
|
|
confirmedCounts[channel] = count;
|
|
await syncCount(channel, count);
|
|
return render();
|
|
}
|
|
|
|
return {start, change, reconcile, readiness, enable};
|
|
});
|