Skip to content

Commit

Permalink
Use magic url for sw sync
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesdaniels committed Sep 7, 2024
1 parent 6845143 commit 4be2a4b
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 42 deletions.
37 changes: 23 additions & 14 deletions nextjs-end/auth-service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,6 @@ self.addEventListener("activate", () => {
self.clients.claim();
});

// 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(() => {
onAuthStateChanged(auth, async (user) => {
const uid = user?.uid;
const clients = await self.clients.matchAll();
for (const client of clients) {
client.postMessage({ type: "onAuthStateChanged", uid });
}
});
});

async function getAuthIdToken() {
await auth.authStateReady();
if (!auth.currentUser) return;
Expand All @@ -43,12 +30,34 @@ async function getAuthIdToken() {
self.addEventListener("fetch", (event) => {
const { origin, pathname } = new URL(event.request.url);
if (origin !== self.location.origin) return;
// Use a magic url to ensure that auth state is in sync between
// the client and the sw, this helps with actions such as router.refresh();
if (pathname.startsWith('/__/auth/wait/')) {
const uid = pathname.split('/').at(-1);
event.respondWith(waitForMatchingUid(uid));
return;
}
if (pathname.startsWith('/_next/')) return;
// Don't add haeders to GET requests with an extension—this skips css, images, fonts, json, etc.
// Don't add headers to non-get requests or those with an extension—this
// helps with css, images, fonts, json, etc.
if (event.request.method === "GET" && !pathname.startsWith("/api/") && pathname.includes(".")) return;
event.respondWith(fetchWithFirebaseHeaders(event.request));
});

async function waitForMatchingUid(_uid) {
const uid = _uid === "undefined" ? undefined : _uid;
await auth.authStateReady();
await new Promise((resolve) => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user?.uid === uid) {
unsubscribe();
resolve();
}
});
});
return new Response(undefined, { status: 200, headers: { "cache-control": "no-store" } });
}

async function fetchWithFirebaseHeaders(request) {
const authIdToken = await getAuthIdToken();
if (authIdToken) {
Expand Down
41 changes: 13 additions & 28 deletions nextjs-end/src/components/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@ import {
signInWithGoogle,
signOut,
onAuthStateChanged,
onIdTokenChanged,
} from "@/src/lib/firebase/auth.js";
import { addFakeRestaurantsAndReviews } from "@/src/lib/firebase/firestore.js";
import { useRouter } from "next/navigation";
import { firebaseConfig } from "@/src/lib/firebase/config";
import { getIdToken } from "firebase/auth";

function useUserSession(initialUser) {
// The initialUser comes from the server via a server component
const [user, setUser] = useState(initialUser);
const [serviceWorker, setServiceWorker] = useState(undefined);
const router = useRouter();

// Register the service worker that sends auth state back to server
Expand All @@ -25,36 +22,24 @@ function useUserSession(initialUser) {
const serializedFirebaseConfig = encodeURIComponent(JSON.stringify(firebaseConfig));
const serviceWorkerUrl = `/auth-service-worker.js?firebaseConfig=${serializedFirebaseConfig}`

navigator.serviceWorker
.register(serviceWorkerUrl)
.then(async (registration) => {
setServiceWorker(registration.active);
console.log("scope is: ", registration.scope)
});
navigator
.serviceWorker
.register(serviceWorkerUrl, { scope: "/", updateViaCache: "none" })
.then((registration) => {
console.log("scope is: ", registration.scope);
registration.update();
});
}
}, []);

useEffect(() => {
return onAuthStateChanged((authUser) => {
setUser(authUser)
return onAuthStateChanged(async (authUser) => {
if (user?.uid === authUser?.uid) return;
await fetch(`/__/auth/wait/${authUser?.uid}`, { method: "HEAD" }).catch(() => undefined);
setUser(authUser);
router.refresh();
});
}, []);

useEffect(() => {
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]);
}, [user]);

return user;
}
Expand Down

0 comments on commit 4be2a4b

Please sign in to comment.