fix(05-review): CR-04 pre-fetch VAPID key into state; no await before pushManager.subscribe
This commit is contained in:
@@ -114,8 +114,14 @@ function persistNotificationsEnabled(value: boolean): void {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface UsePushSubscriptionReturn {
|
||||
/** Call this inside an onClick handler (no await before the call). */
|
||||
subscribe: (registration: ServiceWorkerRegistration) => Promise<void>
|
||||
/**
|
||||
* Call this inside an onClick handler (no await before the call).
|
||||
*
|
||||
* CR-04: `vapidKey` must be pre-fetched before the tap (e.g. via prefetchVapidKey()
|
||||
* in a useEffect) and passed in here. This eliminates the network round-trip inside
|
||||
* the tap handler, preserving the iOS user-gesture requirement for pushManager.subscribe().
|
||||
*/
|
||||
subscribe: (registration: ServiceWorkerRegistration, vapidKey: string) => Promise<void>
|
||||
unsubscribe: () => Promise<void>
|
||||
/** Current Notification.permission value ('default' | 'granted' | 'denied') */
|
||||
permission: NotificationPermission
|
||||
@@ -197,17 +203,19 @@ export function usePushSubscription(): UsePushSubscriptionReturn {
|
||||
* synchronously within a user gesture (tap) callback. This function starts
|
||||
* the subscription synchronously and awaits only the POST afterwards.
|
||||
*
|
||||
* The VAPID key should be pre-fetched and cached (e.g., in a useEffect before
|
||||
* the component renders) so this path avoids an extra round-trip.
|
||||
* CR-04: The VAPID key MUST be pre-fetched before the tap (e.g., via
|
||||
* prefetchVapidKey() in a useEffect) and passed in as `vapidKey`. Fetching
|
||||
* it here (inside the async function) would insert an await-fetch before
|
||||
* pushManager.subscribe() and break the iOS gesture gate on cache miss.
|
||||
*/
|
||||
const subscribe = async (registration: ServiceWorkerRegistration): Promise<void> => {
|
||||
// Fetch VAPID key (from cache if already prefetched)
|
||||
const vapidKey = await fetchVapidKey()
|
||||
|
||||
const subscribe = async (
|
||||
registration: ServiceWorkerRegistration,
|
||||
vapidKey: string,
|
||||
): Promise<void> => {
|
||||
// pushManager.subscribe — synchronous initiation; iOS gesture gate is here.
|
||||
// In practice the async resolution of PushSubscription is after the gesture
|
||||
// frame, which iOS allows as long as subscribe() is the first async operation
|
||||
// called from the tap handler.
|
||||
// No await before this call: vapidKey is already resolved by the caller.
|
||||
// iOS allows the async resolution of PushSubscription after the gesture frame,
|
||||
// as long as subscribe() is the first async operation in the tap handler.
|
||||
const sub = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(vapidKey),
|
||||
|
||||
Reference in New Issue
Block a user