Open rlw87 opened 5 months ago
I used the Development Tool to "Update on reload" which fixed the refresh issue, but that isn't a setting I can have my users change.
Any luck with solving this?
Hey @ESRuth, would you able to resolve this issue? Thanks
I've just included the firebase config in the service worker itself, rather than having the app pass it the config when it's installed. None of the values should change so I don't see why it should be a problem.
I am experiencing problems with the service worker, which fails upon page reload, sometimes after a few hours, or when the page is opened in a new browser tab.
Having the same problem. Didn't clone the friendlyeats project, I copy pasted the auth code into my Next.js proiect and I have the same issue with the service worker. Still searching for a permanent solution.
Having the same issue here as well. Hardcoding the values stops it from crashing however, obviously this is not ideal.
I used Cache API, and it solved the issue of retaining the firebase config between runs.
import { initializeApp } from "firebase/app";
import { getAuth, getIdToken } from "firebase/auth";
import { getInstallations, getToken } from "firebase/installations";
// region variables
const CACHE_NAME = 'config-cache-v1';
/** @type {FirebaseOptions | undefined} */
let CONFIG;
// endregion
// region listeners
self.addEventListener('install', (event) => {
// extract firebase config from query string
const serializedFirebaseConfig = new URL(location).searchParams.get('firebaseConfig');
if (!serializedFirebaseConfig) {
throw new Error('Firebase Config object not found in service worker query string.');
}
self.skipWaiting();
event.waitUntil(saveConfig(serializedFirebaseConfig));
});
self.addEventListener("activate", (event) => {
event.waitUntil(clients.claim());
});
self.addEventListener("fetch", (event) => {
const { origin } = new URL(event.request.url);
if (origin !== self.location.origin) return;
event.respondWith(fetchWithFirebaseHeaders(event.request));
});
// endregion
// region functions
/**
* @return string
* */
function getConfigUrl() {
return `${self.location.origin}/firebase-config`;
}
/**
* @param {string} config
*
* return Promise<void>
* */
async function saveConfig(config) {
const cache = await caches.open(CACHE_NAME);
const response = new Response(config, {
headers: { 'Content-Type': 'application/json' }
});
await cache.put(getConfigUrl(), response);
}
/**
* @param {Request} request
*
* @return Response
* */
async function fetchWithFirebaseHeaders(request) {
const config = await getConfig();
if (!config) {
return await fetch(request);
}
const app = initializeApp(config);
const auth = getAuth(app);
const installations = getInstallations(app);
const headers = new Headers(request.headers);
const [authIdToken, installationToken] = await Promise.all([
getAuthIdToken(auth),
getToken(installations),
]);
headers.append("Firebase-Instance-ID-Token", installationToken);
if (authIdToken) headers.append("Authorization", `Bearer ${authIdToken}`);
const newRequest = new Request(request, { headers });
return await fetch(newRequest);
}
/**
* @param {Auth} auth
*
* @return Promise<string | undefined>
* */
async function getAuthIdToken(auth) {
await auth.authStateReady();
if (!auth.currentUser) return;
return await getIdToken(auth.currentUser);
}
/**
* @return FirebaseOptions | undefined
* */
async function getConfig() {
if (CONFIG) return CONFIG;
const cache = await caches.open(CACHE_NAME);
const configResponse = await cache.match(getConfigUrl());
if (!configResponse) {
return;
}
const config = await configResponse.json();
CONFIG = config;
return CONFIG;
}
// endregion
Hi @KirillSkomarovskiy - thanks for this. I'm still getting the error:
FirebaseError: Firebase: Need to provide options, when not being deployed to hosting via source. (app/no-options).
@rlw87 can you add a code snippet of your approach?
Thanks all!
Same problem from my side, just by coping codelab-friendlyeats-web and deploying it.
Same here, it is crazy how hard it is to find a working example.
Same problem here!
Same problem from my side, just by coping codelab-friendlyeats-web and deploying it.
Not inspiring much confidence in using firebase app hosting for nextjs...
import { initializeApp } from "firebase/app";
import { getAuth, getIdToken } from "firebase/auth";
import { getInstallations, getToken } from "firebase/installations";
// old code (dont include this
// // this is set during install
// let firebaseConfig;
// self.addEventListener('install', event => {
// // extract firebase config from query string
// const serializedFirebaseConfig = new URL(location).searchParams.get('firebaseConfig');
// if (!serializedFirebaseConfig) {
// throw new Error('Firebase Config object not found in service worker query string.');
// }
// firebaseConfig = JSON.parse(serializedFirebaseConfig);
// console.log("Service worker installed with Firebase config", firebaseConfig);
// });
// Default hardcoded Firebase configuration -- put your config here
let firebaseConfig = {
apiKey: "xxxxxxxxxxxxxxxxxx",
authDomain: "xxxxxxxxxxxxxxxxx",
projectId: "xxxxxxxxxxxxxxxxxxx",
storageBucket: "xxxxxxxxxxxxxxxxxxx",
messagingSenderId: "xxxxxxxxxxxxxxxxxxxxxxx",
appId: "xxxxxxxxxxxxxxxxxxxxxxx",
measurementId: "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
};
// Handle the 'install' event and extract the firebaseConfig from the query string if present
self.addEventListener('install', event => {
const serializedFirebaseConfig = new URL(location).searchParams.get('firebaseConfig');
if (serializedFirebaseConfig) {
try {
firebaseConfig = JSON.parse(serializedFirebaseConfig);
console.log("Service worker installed with Firebase config from query string", firebaseConfig);
} catch (error) {
console.error("Failed to parse Firebase config from query string", error);
}
} else {
console.log("Service worker installed with hardcoded Firebase config", firebaseConfig);
}
});
self.addEventListener("fetch", (event) => {
console.log("Fetching with Firebase config:", firebaseConfig);
const { origin } = new URL(event.request.url);
if (origin !== self.location.origin) return;
event.respondWith(fetchWithFirebaseHeaders(event.request));
});
async function fetchWithFirebaseHeaders(request) {
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const installations = getInstallations(app);
const headers = new Headers(request.headers);
const [authIdToken, installationToken] = await Promise.all([
getAuthIdToken(auth),
getToken(installations),
]);
headers.append("Firebase-Instance-ID-Token", installationToken);
if (authIdToken) headers.append("Authorization", `Bearer ${authIdToken}`);
const newRequest = new Request(request, { headers });
return await fetch(newRequest);
}
async function getAuthIdToken(auth) {
await auth.authStateReady();
if (!auth.currentUser) return;
return await getIdToken(auth.currentUser);
}
Then run
npx esbuild auth-service-worker.js --bundle --outfile=public/auth-service-worker.js
to compile the new service worker file and put it in public so that it works locally. For prod, you don't need to worry about this step, it will auto run this as part of the build process.
The problem is that the configuration is initialized during the install
event. When the service worker is stoped and then resumed, the install
event will not be triggered again, leaving firebaseConfig
undefined. You need to check in the fetch
event whether firebaseConfig
is set, if not, extract the config from the query string again:
self.addEventListener("fetch", (event) => {
if (!firebaseConfig) {
const serializedFirebaseConfig = new URL(location).searchParams.get(
"firebaseConfig"
);
firebaseConfig = JSON.parse(serializedFirebaseConfig);
}
// rest of code
});
But I think you could get rid of the install
event and just initialize it globally:
const serializedFirebaseConfig = new URL(location).searchParams.get(
"firebaseConfig"
);
if (!serializedFirebaseConfig) {
throw new Error(
"Firebase Config object not found in service worker query string."
);
}
const firebaseConfig = JSON.parse(serializedFirebaseConfig);
self.addEventListener("fetch", (event) => {
const { origin } = new URL(event.request.url);
if (origin !== self.location.origin) return;
event.respondWith(fetchWithFirebaseHeaders(event.request));
});
//...
Did this work for you @adrolc I am having the same issues right now as well.
The problem is that the configuration is initialized during the
install
event. When the service worker is stoped and then resumed, theinstall
event will not be triggered again, leavingfirebaseConfig
undefined. You need to check in thefetch
event whetherfirebaseConfig
is set, if not, extract the config from the query string again:self.addEventListener("fetch", (event) => { if (!firebaseConfig) { const serializedFirebaseConfig = new URL(location).searchParams.get( "firebaseConfig" ); firebaseConfig = JSON.parse(serializedFirebaseConfig); } // rest of code });
But I think you could get rid of the
install
event and just initialize it globally:const serializedFirebaseConfig = new URL(location).searchParams.get( "firebaseConfig" ); if (!serializedFirebaseConfig) { throw new Error( "Firebase Config object not found in service worker query string." ); } const firebaseConfig = JSON.parse(serializedFirebaseConfig); self.addEventListener("fetch", (event) => { const { origin } = new URL(event.request.url); if (origin !== self.location.origin) return; event.respondWith(fetchWithFirebaseHeaders(event.request)); }); //...
This worked for me 👍🏽
+1
Description If the auth service worker is restarted, it loses the firebaseConfig options that it was given at registration time and throws an exception.
Reproduction Steps
Expected The service worker starts up again and the website loads as normal
Actual You see the following error in the console and the page doesn't load
I'm new to working with service workers so I might be missing something, but it looks to me as though the service worker should be storing the firebaseConfig somewhere other than in memory, so it can be reloaded on restart of the service worker. It looks like due to the firebaseConfig variable only being populated on 'install' event, it will never get the required configuration again unless you either manually unregister it and let it re-install, or it is updated to a newer version.
https://github.com/firebase/friendlyeats-web/blob/41b0a4dbfca6c106d926fd5e65db53577f99ea75/nextjs-end/auth-service-worker.js#L6C5-L6C19