fix(05-review): NEW-CR-01 pre-resolve SW registration to eliminate await before pushManager.subscribe
Pre-fetch ServiceWorkerRegistration into component state via useEffect in both PushPermissionPrompt and SettingsSheet. Gate the subscribe tap target as disabled until both vapidKey AND swRegistration are ready. The tap handler now has zero awaits between the user gesture and registration.pushManager.subscribe(), fully satisfying the iOS user-gesture requirement.
This commit is contained in:
@@ -54,6 +54,9 @@ export function SettingsSheet({ isOpen, onClose }: SettingsSheetProps) {
|
||||
// CR-04: pre-fetch the VAPID key into state so the toggle tap handler can call
|
||||
// subscribe(registration, vapidKey) without any network await before pushManager.subscribe().
|
||||
const [vapidKey, setVapidKey] = useState<string | null>(null)
|
||||
// NEW-CR-01: Pre-resolve the ServiceWorkerRegistration into state so the toggle
|
||||
// tap handler has ZERO awaits between the user gesture and pushManager.subscribe().
|
||||
const [swRegistration, setSwRegistration] = useState<ServiceWorkerRegistration | null>(null)
|
||||
const closeButtonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
// Compute initial toggle on/off state per UI-SPEC toggle initial state rule:
|
||||
@@ -87,6 +90,18 @@ export function SettingsSheet({ isOpen, onClose }: SettingsSheetProps) {
|
||||
})
|
||||
}, [isOpen, permission])
|
||||
|
||||
// NEW-CR-01: Pre-resolve the ServiceWorkerRegistration into state so the toggle
|
||||
// tap handler never needs to await navigator.serviceWorker.ready. Any await
|
||||
// between the tap gesture and pushManager.subscribe() breaks iOS.
|
||||
useEffect(() => {
|
||||
if (!isOpen) return
|
||||
if (permission === 'denied') return
|
||||
if (!navigator.serviceWorker) return
|
||||
void navigator.serviceWorker.ready.then((reg) => {
|
||||
setSwRegistration(reg)
|
||||
})
|
||||
}, [isOpen, permission])
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
const handleToggle = async () => {
|
||||
@@ -106,16 +121,15 @@ export function SettingsSheet({ isOpen, onClose }: SettingsSheetProps) {
|
||||
} else {
|
||||
// off → on, permission 'default': needs tap-gated subscribe with OS dialog
|
||||
// The toggle click IS the tap gesture — call subscribe() directly here.
|
||||
// CR-04: vapidKey was pre-fetched into state (useEffect above); pass it directly
|
||||
// so there is no await-fetch between the tap and pushManager.subscribe().
|
||||
if (!vapidKey) return // key not ready — this should be rare; button should show spinner
|
||||
// NEW-CR-01: Both vapidKey AND swRegistration are pre-resolved into state
|
||||
// (useEffects above). There is ZERO await between the tap and
|
||||
// registration.pushManager.subscribe() — iOS gesture gate satisfied.
|
||||
if (!vapidKey || !swRegistration) return // not ready — useEffects still resolving
|
||||
const resolvedVapidKey = vapidKey
|
||||
const resolvedRegistration = swRegistration
|
||||
setIsTogglingOn(true)
|
||||
try {
|
||||
const registration = await navigator.serviceWorker?.ready
|
||||
if (registration) {
|
||||
await subscribe(registration, resolvedVapidKey)
|
||||
}
|
||||
await subscribe(resolvedRegistration, resolvedVapidKey)
|
||||
} catch {
|
||||
// Permission denied by OS or error — permission state will update reactively
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user