diff --git a/nextjs-end/auth-service-worker.js b/nextjs-end/auth-service-worker.js index e702f207..12db968c 100644 --- a/nextjs-end/auth-service-worker.js +++ b/nextjs-end/auth-service-worker.js @@ -1,5 +1,5 @@ import { initializeApp } from "firebase/app"; -import { getAuth, getIdToken, onIdTokenChanged } from "firebase/auth"; +import { getAuth, getIdToken, onAuthStateChanged } from "firebase/auth"; // extract firebase config from query string const serializedFirebaseConfig = new URLSearchParams(self.location.search).get('firebaseConfig'); @@ -21,36 +21,31 @@ self.addEventListener("activate", () => { self.clients.claim(); }); -// Allow the client to send a temporary override to the idToken, this allows for -// router.refresh() without worrying about the client and service worker having -// race conditions. -self.addEventListener("message", (event) => { - if (event.data && event.data.type === "FIREBASE_ID_TOKEN") { - getAuthIdToken = () => Promise.resolve(event.data.idToken); - } -}); - -// Once the idTokenChanged event fires, change back to the original method +// Notify clients of onAuthStateChanged events, so they can coordinate +// any actions which may normally be prone to race conditions, such as +// router.refresh(); auth.authStateReady().then(() => { - onIdTokenChanged(auth, () => { - getAuthIdToken = DEFAULT_GET_AUTH_ID_TOKEN; + onAuthStateChanged(auth, async (user) => { + const uid = user?.uid; + const clients = await self.clients.matchAll(); + for (const client of clients) { + client.postMessage({ type: "onAuthStateChanged", uid }); + } }); }); -const DEFAULT_GET_AUTH_ID_TOKEN = async () => { +async function getAuthIdToken() { await auth.authStateReady(); if (!auth.currentUser) return; return await getIdToken(auth.currentUser); }; -let getAuthIdToken = DEFAULT_GET_AUTH_ID_TOKEN; - self.addEventListener("fetch", (event) => { const { origin, pathname } = new URL(event.request.url); if (origin !== self.location.origin) return; if (pathname.startsWith('/_next/')) return; - // Ignore resources with an extension—this skips css, images, fonts, json, etc. - if (!pathname.startsWith("/api/") && pathname.includes(".")) return; + // Don't add haeders to GET requests with an extension—this skips css, images, fonts, json, etc. + if (event.request.method === "GET" && !pathname.startsWith("/api/") && pathname.includes(".")) return; event.respondWith(fetchWithFirebaseHeaders(event.request)); }); diff --git a/nextjs-end/src/components/Header.jsx b/nextjs-end/src/components/Header.jsx index 2c7896bf..14245ecb 100644 --- a/nextjs-end/src/components/Header.jsx +++ b/nextjs-end/src/components/Header.jsx @@ -27,7 +27,7 @@ function useUserSession(initialUser) { navigator.serviceWorker .register(serviceWorkerUrl) - .then((registration) => { + .then(async (registration) => { setServiceWorker(registration.active); console.log("scope is: ", registration.scope) }); @@ -41,16 +41,19 @@ function useUserSession(initialUser) { }, []); useEffect(() => { - // refresh when user changed to ease testing - return onIdTokenChanged(async (authUser) => { - if (user?.email === authUser?.email) return; - // Send the new ID_TOKEN to the worker so we don't have a race condition - if (serviceWorker) { - const idToken = authUser && await getIdToken(authUser); - serviceWorker.postMessage({ type: 'FIREBASE_ID_TOKEN', idToken }); - } - router.refresh(); - }) + if (serviceWorker) { + // listen to an onAuthStateChanged event from the service worker when + // refreshing the router, this is preferred over onAuthStateChanged as + // that can introduce race conditions as the client & service worker state + // can be out of sync + return navigator.serviceWorker.addEventListener("message", (event) => { + if (event.source !== serviceWorker) return; + if (event.data.type !== "onAuthStateChanged") return; + event.preventDefault(); + if (user?.uid === event.data?.uid) return; + router.refresh(); + }); + } }, [user, serviceWorker]); return user;