feat: recover blocked notification setup (Closes #1132)
This commit is contained in:
1 parent
b3832d107f
commit
1248647aa4
16 files changed
+239
-42
No files matched your search
@@ -7990,9 +7990,15 @@
|
||||
enableOffline:() => setOfflineWorkEnabled(true),
|
||||
storageProtectionReadiness:() => deviceStorage.persistenceReadiness(),
|
||||
protectStorage:() => deviceStorage.requestPersistence(),
|
||||
notificationReadiness:() => pushController?.notificationReadiness()
|
||||
|| {state:'unavailable', detail:'Update notifications are unavailable.'},
|
||||
enablePush:async () => {
|
||||
const controller = await pushControllerReady;
|
||||
if (!controller) return;
|
||||
if (controller.notificationReadiness().state === 'blocked') {
|
||||
await controller.recoverPermission('updates');
|
||||
return;
|
||||
}
|
||||
qs('#push-updates').checked = true;
|
||||
await controller.change();
|
||||
},
|
||||
@@ -8003,6 +8009,10 @@
|
||||
if (!controller) return;
|
||||
qs('#push-deadline-hour').value = qs('#device-setup-deadline-hour').value;
|
||||
qs('#push-deadline-days').value = qs('#device-setup-deadline-days').value;
|
||||
if (controller.deadlineReadiness().state === 'blocked') {
|
||||
await controller.recoverPermission('deadline');
|
||||
return;
|
||||
}
|
||||
await controller.enableDeadline();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
const promptDismissKey = 'stackchain.device-setup-prompt-dismissed-until';
|
||||
const promptDismissMs = 7 * 24 * 60 * 60 * 1000;
|
||||
const steps = [
|
||||
['install', options.installButton, options.installStatus, options.install],
|
||||
['offline', options.offlineButton, options.offlineStatus, options.enableOffline],
|
||||
['protection', options.protectionButton, options.protectionStatus, options.protectStorage],
|
||||
['push', options.pushButton, options.pushStatus, options.enablePush],
|
||||
['deadline', options.deadlineButton, options.deadlineStatus, options.enableDeadline],
|
||||
['install', options.installButton, options.installStatus, options.install, 'Install'],
|
||||
['offline', options.offlineButton, options.offlineStatus, options.enableOffline, 'Enable'],
|
||||
['protection', options.protectionButton, options.protectionStatus, options.protectStorage, 'Protect'],
|
||||
['push', options.pushButton, options.pushStatus, options.enablePush, 'Enable'],
|
||||
['deadline', options.deadlineButton, options.deadlineStatus, options.enableDeadline, 'Enable'],
|
||||
];
|
||||
let trigger = options.launcher;
|
||||
let ownsDetour = false;
|
||||
@@ -35,9 +35,10 @@
|
||||
const readiness = await options.getReadiness();
|
||||
let available = 0;
|
||||
let complete = 0;
|
||||
steps.forEach(([name, button, status]) => {
|
||||
steps.forEach(([name, button, status, _action, defaultActionLabel]) => {
|
||||
const step = readiness[name];
|
||||
status.textContent = step.detail;
|
||||
button.textContent = step.actionLabel || defaultActionLabel;
|
||||
button.hidden = step.state === 'complete' || step.state === 'unavailable';
|
||||
button.disabled = step.state === 'pending';
|
||||
if (step.state !== 'unavailable') available += 1;
|
||||
@@ -130,11 +131,7 @@ function mountMobileDeviceSetup(options) {
|
||||
? {state:'complete', detail:qs('#offline-work-status').textContent || 'Offline work is saved.'}
|
||||
: {state:'incomplete', detail:'Private My Work and Today data are not saved offline.'},
|
||||
protection:options.storageProtectionReadiness(),
|
||||
push:qs('#push-updates').disabled
|
||||
? {state:'unavailable', detail:qs('#push-update-status').textContent || 'Update notifications are unavailable.'}
|
||||
: qs('#push-updates').checked
|
||||
? {state:'complete', detail:'New update notifications are enabled.'}
|
||||
: {state:'incomplete', detail:qs('#push-update-status').textContent || 'New update notifications are off.'},
|
||||
push:options.notificationReadiness(),
|
||||
deadline:options.deadlineReadiness(),
|
||||
}),
|
||||
install:() => options.installApp.install(),
|
||||
|
||||
@@ -7,6 +7,34 @@
|
||||
notification, serviceWorker, fetchJson,
|
||||
}) {
|
||||
let configuration = null;
|
||||
let pendingIntent = null;
|
||||
let recoveryPromise = null;
|
||||
|
||||
function blockedReadiness() {
|
||||
return {
|
||||
state:'blocked',
|
||||
detail:'Notifications are blocked. Allow them in browser settings, then check again.',
|
||||
actionLabel:'Check again',
|
||||
};
|
||||
}
|
||||
|
||||
function pendingRecoveryReadiness() {
|
||||
return notification.permission === 'granted'
|
||||
? {state:'blocked', detail:'Notification permission changed. Check again to finish setup.', actionLabel:'Check again'}
|
||||
: blockedReadiness();
|
||||
}
|
||||
|
||||
function notificationReadiness() {
|
||||
if (!configuration?.available || control?.disabled) {
|
||||
return {state:'unavailable', detail:status?.textContent || 'Update notifications are unavailable.'};
|
||||
}
|
||||
if (configuration.subscribed) {
|
||||
return {state:'complete', detail:'New update notifications are enabled.'};
|
||||
}
|
||||
if (pendingIntent === 'updates') return pendingRecoveryReadiness();
|
||||
if (notification.permission === 'denied') return blockedReadiness();
|
||||
return {state:'incomplete', detail:status?.textContent || 'New update notifications are off.'};
|
||||
}
|
||||
|
||||
function renderDeliveryHealth() {
|
||||
const health = Object.values(configuration?.delivery_health || {});
|
||||
@@ -77,6 +105,7 @@
|
||||
if (!configuration?.available || deadlineControl?.disabled) {
|
||||
return {state:'unavailable', detail:deadlineStatus?.textContent || 'Deadline reminders are unavailable.'};
|
||||
}
|
||||
if (pendingIntent === 'deadline') return pendingRecoveryReadiness();
|
||||
return configuration.deadline_enabled
|
||||
? {state:'complete', detail:enabledDeadlineText(configuration.reminder_hour, configuration.reminder_days)}
|
||||
: {state:'incomplete', detail:deadlineStatus?.textContent || 'Choose when to receive deadline reminders.'};
|
||||
@@ -98,15 +127,20 @@
|
||||
control.checked = false;
|
||||
if (testControl) testControl.hidden = true;
|
||||
if (deadlineControl) deadlineControl.checked = false;
|
||||
configuration.subscribed = false;
|
||||
configuration.deadline_enabled = false;
|
||||
pendingIntent = null;
|
||||
status.textContent = 'New update notifications are off for this device.';
|
||||
if (deadlineStatus) deadlineStatus.textContent = 'Deadline reminders are off for this device.';
|
||||
}
|
||||
|
||||
async function ensureSubscription() {
|
||||
const permission = await notification.requestPermission();
|
||||
const permission = notification.permission === 'granted'
|
||||
? 'granted'
|
||||
: await notification.requestPermission();
|
||||
if (permission !== 'granted') {
|
||||
control.checked = false;
|
||||
status.textContent = 'Notifications are blocked. Allow them in your browser settings to enable updates.';
|
||||
status.textContent = blockedReadiness().detail;
|
||||
return null;
|
||||
}
|
||||
const registration = await serviceWorker.ready;
|
||||
@@ -130,7 +164,10 @@
|
||||
}
|
||||
|
||||
async function enable() {
|
||||
await ensureSubscription();
|
||||
pendingIntent = 'updates';
|
||||
const subscription = await ensureSubscription();
|
||||
if (subscription) pendingIntent = null;
|
||||
return subscription;
|
||||
}
|
||||
|
||||
async function change() {
|
||||
@@ -153,6 +190,7 @@
|
||||
try {
|
||||
const registration = await serviceWorker.ready;
|
||||
let subscription = await registration.pushManager.getSubscription();
|
||||
if (deadlineControl.checked) pendingIntent = 'deadline';
|
||||
if (deadlineControl.checked && !subscription) subscription = await ensureSubscription();
|
||||
if (deadlineControl.checked && !subscription) {
|
||||
deadlineControl.checked = false;
|
||||
@@ -171,6 +209,7 @@
|
||||
configuration.reminder_hour = reminderHour;
|
||||
configuration.reminder_days = reminderDays;
|
||||
configuration.timezone = timezone;
|
||||
pendingIntent = null;
|
||||
deadlineStatus.textContent = deadlineControl.checked
|
||||
? enabledDeadlineText(reminderHour, reminderDays)
|
||||
: 'Deadline reminders are off for this device.';
|
||||
@@ -191,6 +230,24 @@
|
||||
return changeDeadline();
|
||||
}
|
||||
|
||||
async function recoverPermission(intent = null) {
|
||||
if (!pendingIntent && (intent === 'updates' || intent === 'deadline')) pendingIntent = intent;
|
||||
if (!pendingIntent || notification.permission !== 'granted') return false;
|
||||
if (recoveryPromise) return recoveryPromise;
|
||||
recoveryPromise = (async () => {
|
||||
if (pendingIntent === 'deadline') {
|
||||
deadlineControl.checked = true;
|
||||
return changeDeadline();
|
||||
}
|
||||
return Boolean(await enable());
|
||||
})();
|
||||
try {
|
||||
return await recoveryPromise;
|
||||
} finally {
|
||||
recoveryPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
if (!control || !notification || !serviceWorker) return;
|
||||
control.addEventListener('change', change);
|
||||
@@ -215,5 +272,5 @@
|
||||
renderDeadlineSnooze();
|
||||
}
|
||||
|
||||
return {init, change, changeDeadline, enableDeadline, deadlineReadiness};
|
||||
return {init, change, changeDeadline, enableDeadline, deadlineReadiness, notificationReadiness, recoverPermission};
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
const BASE = new URL('./', self.location.href).pathname;
|
||||
importScripts(BASE + 'static/private-data-registry.js');
|
||||
importScripts(BASE + 'static/background-issue-sync.js');
|
||||
const CACHE = 'stackchain-dashboard-shell-v122';
|
||||
const CACHE = 'stackchain-dashboard-shell-v123';
|
||||
const OFFLINE_LEASE_URL = new URL(BASE + '__offline-session-lease', self.location.origin).href;
|
||||
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
|
||||
const NAVIGATION_TIMEOUT_MS = self.__STACKCHAIN_NAVIGATION_TIMEOUT_MS || 4000;
|
||||
|
||||
Reference in new issue
Block a user