[React] Passing Environment Variables to Service Workers
Yuta Kusuno
Posted on January 18, 2024
When working with Firebase Cloud Messaging in a React application, creating a service worker (the following firebase-messaging-sw.js
) in the public directory becomes essential. However, handling environment-specific configurations, such as API keys, poses a challenge due to the inability to access environment variables using process.env within the public directory. This blog post explores two approaches to dynamically pass environment variables to the service worker.
// public/firebase-messaging-sw.js
// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here. Other Firebase libraries are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/8.10.1/firebase-messaging.js');
// Initialize the Firebase app in the service worker by passing in your app's Firebase config object.
// https://firebase.google.com/docs/web/setup#config-object
firebase.initializeApp({
apiKey: 'api-key',
authDomain: 'project-id.firebaseapp.com',
databaseURL: 'https://project-id.firebaseio.com',
projectId: 'project-id',
storageBucket: 'project-id.appspot.com',
messagingSenderId: 'sender-id',
appId: 'app-id',
measurementId: 'G-measurement-id',
});
// Retrieve an instance of Firebase Messaging so that it can handle background messages.
const messaging = firebase.messaging();
Using a Separate Script to Build Environment Variables
Create a .env
file with your Firebase configurations.
REACT_APP_FIREBASE_API_KEY = 'api-key'
REACT_APP_FIREBASE_AUTH_DOMAIN = 'project-id.firebaseapp.com',
REACT_APP_FIREBASE_PROJECT_ID = 'project-id'
REACT_APP_FIREBASE_STORAGE_BUCKET = 'project-id.appspot.com'
REACT_APP_FIREBASE_MESSAGING_SENDER_ID = 'sender-id'
REACT_APP_FIREBASE_APP_ID = 'app-id'
REACT_APP_FIREBASE_MEASUREMENT_ID = 'G-measurement-id'
Create a script (swEnvBuild.js
) to build a JavaScript file (swEnv.js
) containing the environment variables.
// src/swEnvBuild.js
const fs = require("fs");
// This dotenv is the one used inside React.
// Load dotenv Intentionally because build process does not have access to .env file yet.
const dotenv = require("dotenv");
dotenv.config();
const {
REACT_APP_FIREBASE_API_KEY,
REACT_APP_FIREBASE_AUTH_DOMAIN,
REACT_APP_FIREBASE_PROJECT_ID,
REACT_APP_FIREBASE_STORAGE_BUCKET,
REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
REACT_APP_FIREBASE_APP_ID,
REACT_APP_FIREBASE_MEASUREMENT_ID,
} = process.env;
const content = `const swEnv = {
REACT_APP_FIREBASE_API_KEY: '${REACT_APP_FIREBASE_API_KEY}',
REACT_APP_FIREBASE_AUTH_DOMAIN: '${REACT_APP_FIREBASE_AUTH_DOMAIN}',
REACT_APP_FIREBASE_PROJECT_ID: '${REACT_APP_FIREBASE_PROJECT_ID}',
REACT_APP_FIREBASE_STORAGE_BUCKET: '${REACT_APP_FIREBASE_STORAGE_BUCKET}',
REACT_APP_FIREBASE_MESSAGING_SENDER_ID: '${REACT_APP_FIREBASE_MESSAGING_SENDER_ID}',
REACT_APP_FIREBASE_APP_ID: '${REACT_APP_FIREBASE_APP_ID}',
REACT_APP_FIREBASE_MEASUREMENT_ID: '${REACT_APP_FIREBASE_MEASUREMENT_ID}'
}`;
fs.writeFileSync("./public/swEnv.js", content);
Modify the npm start
command in package.json
to run the build script before starting the development server. Only the npm start
command is being modified for testing in the development environment. When deploying, I think it is necessary to modify the npm build
command.
"scripts": {
- "start": "react-scripts start",
+ "start": "node src/swEnvBuild.js && react-scripts start",
Now, running npm run start
will create public/swEnv.js
.
// public/swEnv.js
const swEnv = {
REACT_APP_FIREBASE_API_KEY: 'api-key',
REACT_APP_FIREBASE_AUTH_DOMAIN: 'project-id.firebaseapp.com',
REACT_APP_FIREBASE_PROJECT_ID: 'project-id',
REACT_APP_FIREBASE_STORAGE_BUCKET: 'project-id.appspot.com',
REACT_APP_FIREBASE_MESSAGING_SENDER_ID: 'sender-id',
REACT_APP_FIREBASE_APP_ID: 'app-id',
REACT_APP_FIREBASE_MEASUREMENT_ID: 'G-measurement-id'
}
Update public/firebase-messaging-sw.js
to import and use the dynamically generated environment variables.
// public/firebase-messaging-sw.js
importScripts("https://www.gstatic.com/firebasejs/8.3.1/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.3.1/firebase-messaging.js");
importScripts("swEnv.js"); // Added
firebase.initializeApp({
apiKey: swEnv.REACT_APP_FIREBASE_API_KEY, // Changed... repeat for other variables
authDomain: swEnv.REACT_APP_FIREBASE_AUTH_DOMAIN,
projectId: swEnv.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: swEnv.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: swEnv.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
appId: swEnv.REACT_APP_FIREBASE_APP_ID,
measurementId: swEnv.REACT_APP_FIREBASE_MEASUREMENT_ID,
});
const messaging = firebase.messaging();
Using Query Parameters
Call registerServiceWorker
in src/App.jsx
to dynamically construct the service worker script URL with query parameters.
export const registerServiceWorker = async () => {
let scriptURL = "firebase-messaging-sw.js";
scriptURL += `?apiKey=${process.env.REACT_APP_FIREBASE_API_KEY}`;
scriptURL += `&authDomain=${process.env.REACT_APP_FIREBASE_AUTH_DOMAIN}`;
scriptURL += `&projectId=${process.env.REACT_APP_FIREBASE_PROJECT_ID}`;
scriptURL += `&storageBucket=${process.env.REACT_APP_FIREBASE_STORAGE_BUCKET}`;
scriptURL += `&messagingSenderId=${process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID}`;
scriptURL += `&appId=${process.env.REACT_APP_FIREBASE_APP_ID}`;
scriptURL += `&measurementId=${process.env.REACT_APP_FIREBASE_MEASUREMENT_ID}`;
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register(scriptURL);
if (registration.installing) {
console.log("Service worker installing");
} else if (registration.waiting) {
console.log("Service worker installed");
} else if (registration.active) {
console.log("Service worker active");
}
} catch (error) {
console.error(`Registration failed with ${error}`);
}
}
};
Update public/firebase-messaging-sw.js
to extract query parameters and initialize Firebase.
// public/firebase-messaging-sw.js
importScripts("https://www.gstatic.com/firebasejs/8.3.1/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.3.1/firebase-messaging.js");
const params = new URL(location).searchParams;
const apiKey = params.get("apiKey");
const authDomain = params.get("authDomain");
const projectId = params.get("projectId");
const storageBucket = params.get("storageBucket");
const messagingSenderId = params.get("messagingSenderId");
const appId = params.get("appId");
const measurementId = params.get("measurementId");
firebase.initializeApp({
apiKey: apiKey,
authDomain: authDomain,
projectId: projectId,
storageBucket: storageBucket,
messagingSenderId: messagingSenderId,
appId: appId,
measurementId: measurementId,
});
const messaging = firebase.messaging();
Both approaches provide solutions for passing environment variables to service workers in a React application. Choose the approach that best fits your project requirements and development workflow. Additionally, alternative methods, such as using Webpack and Babel or workbox-webpack-plugin with InjectManifest are available. I will try these as well when I get a chance.
That is about it. Happy coding!
Posted on January 18, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.