These days, we are increasingly using mobile and web apps in situations where there's little to no internet connectivity. We want to play videos during long road trips, navigate through areas with weak cell signals, and edit our cloud photos and documents on planes with no WiFi. That's why it's crucial to think about how your app will work offline as you're building your next big idea!
Firebase has long helped developers build offline-capable apps. We introduced offline persistence for the iOS and Android Realtime Database SDKs back in 2015. We took care of the hard work behind the scenes, and you only had to write a single line of initialization code.
Taking it a step further, the Cloud Firestore SDKs support offline persistence on the Web, in addition to Android and iOS, allowing you to build rich and offline-capable apps on all your favorite platforms.
With offline persistence, the Cloud Firestore SDKs manage all your data locally, and can execute advanced queries purely against the local cache. Even when your app is restarted, all local edits to documents remain buffered until they are successfully sent to the backend.
But until now, Cloud Firestore's offline support for Web had one big caveat: It only supported offline persistence for the first tab. If you've used it, you have probably come across this error:
'There is another tab open with offline persistence enabled. Only one such tab is allowed at a time. The other tab must be closed or persistence must be disabled.'
Fortunately, you never need to see that error again.
Today, we are introducing multi-tab offline persistence for the Cloud Firestore Web SDK. Your users can read and modify their local data even when they open your app in multiple tabs. All tabs access the same persisted data and synchronize local edits together, even when your network is not connected. This feature can even help reduce billing costs, because the SDK can share query results between tabs without reissuing the query to the backend!
The Firebase JS SDK 5.5.0 release includes experimental offline support for the Cloud Firestore Web SDK for Chrome, Safari and Firefox. You can turn on multi-tab synchronization as follows:
We have great news for web developers that use Firebase Cloud Messaging to send notifications to clients! The FCM v1 REST API has integrated fully with the Web Notifications API. This integration allows you to set icons, images, actions and more for your Web notifications from your server! Better yet, as the Web Notifications API continues to grow and change, these options will be immediately available to you. You won't have to wait for an update to FCM to support them!
Below is a sample payload you can send to your web clients on Push API supported browsers. This notification would be useful for a web app that supports image posting. It can encourage users to engage with the app.
{
"message": {
"webpush": {
"notification": {
"title": "Fish Photos ð",
"body":
"Thanks for signing up for Fish Photos! You now will receive fun daily photos of fish!",
"icon": "firebase-logo.png",
"image": "guppies.jpg",
"data": {
"notificationType": "fishPhoto",
"photoId": "123456"
},
"click_action": "https://example.com/fish_photos",
"actions": [
{
"title": "Like",
"action": "like",
"icon": "icons/heart.png"
},
{
"title": "Unsubscribe",
"action": "unsubscribe",
"icon": "icons/cross.png"
}
]
}
},
"token": "<APP_INSTANCE_REGISTRATION_TOKEN>"
}
}
Notice that you are able to set new parameters, such as actions, which gives the user different ways to interact with the notification. In the example below, users have the option to choose from actions to like the photo or to unsubscribe.
To handle action clicks in your app, you need to add an event listener in the default firebase-messaging-sw.js file (or your custom service worker). If an action button was clicked, event.action will contain the string that identifies the clicked action. Here's how to handle the "like" and "unsubscribe" events on the client:
// Retrieve an instance of Firebase Messaging so that it can handle background messages.
const messaging = firebase.messaging();
// Add an event listener to handle notification clicks
self.addEventListener('notificationclick', function(event) {
if (event.action === 'like') {
// Like button was clicked
const photoId = event.notification.data.photoId;
like(photoId);
}
else if (event.action === 'unsubscribe') {
// Unsubscribe button was clicked
const notificationType = event.notification.data.notificationType;
unsubscribe(notificationType);
}
event.notification.close();
});
The SDK will still handle regular notification clicks and redirect the user to your click_action link if provided. To see more on how to handle click actions on the client, check out the guide.
Since different browsers support different parameters in different platforms, it's important to check out the browser compatibility documentation to ensure your notifications work as intended. Want to learn more about what the Send API can do? Check out the FCM Send API documentation and the Web Notifications API documentation. If you're using the FCM Send API and you incorporate the Web Notifications API in a cool way, then let us know! Find Firebase on Twitter at @Firebase, and Facebook and Google+ by searching "Firebase".
Developer Advocate
If you've browsed the web at all, you've probably seen some sites that ask you
to prove you're a human by presenting a reCAPTCHA challenge. For
example, if you try to use the goo.gl URL shortener, it won't let you shorten a
link until you satisfy the reCAPTCHA, which looks like this:
Web site engineers do this to protect their site from spam and abuse from bots,
while allowing legitimate human use. Why is protection needed? Maybe you have
some backend code that's expensive in time and storage and you only want actual
users of your web to access it.
If you have a web site, you can also use reCAPTCHA to protect its services.
And, if you're building your site with Firebase Hosting, it's
pretty easy to get it integrated with the help of Cloud Functions for
Firebase to provide a secure, scalable backend to verify the completion of
the reCAPTCHA.
In this blog post, I'll walk you through a few steps that will get you to a very
basic integration that you can extend later for your own site. For this
walkthrough, I'm assuming you already have some experience with web development,
the Firebase console, and the
Firebase CLI.
1. Create a Firebase project in the console
Navigate to the Firebase console and create a new project. There's no need to
add billing to this project - you can experiment fully without providing a
credit card. Once you create the project, there's nothing else you need to do
in the console.
2. Set up a directory for your project code
Using the Firebase CLI, make sure you're logged in with the same Google account
that you used to create the project:
$ firebase login
Now, create a root project directory and initialize it:
$ mkdir my_project
$ cd my_project
$ firebase init
When running firebase init, be sure to select both hosting
and functions. When you're asked to choose a project, select the one
you just created earlier. Take the defaults for every other prompt. You'll end
up with a directory structure that contains a public folder for web
content, and a functions folder for your backend code.
For the Cloud Functions backend, we'll need a couple modules from npm to help
verify the reCAPTCHA. The reCAPTCHA API requires you to make an HTTP request
for verification from your backend, and you can do that with the request and
request-promise modules. Pull them into your project like this:
$ cd functions
$ npm install request request-promise
Your package.json file should now show those two new modules in
addition to firebase-functions and firebase-admin.
3. Test web deployment
Make sure you can deploy web content by running this deploy command:
$ firebase deploy --only hosting
When this finishes, you'll be given the public URL to your new web site, which
will look something like this:
where your-project is the unique id that was given to your project
at the time it was created in the console. If you paste the Hosting URL into
your browser, you should see a page that says "Firebase Hosting Setup Complete".
4. Get a reCAPTCHA API Key
reCAPTCHA requires a couple API keys for operation, one for the web client and
one for the server API. You can get those from the reCAPTCHA admin panel, so
navigate there. Create a new site and give it a name. Select "reCAPTCHA V2".
For domains, put the full hostname of your Firebase Hosting site name (e.g.
"your-project.firebaseapp.com").
After you register, you'll be given a Site key and a
Secret key. The Site key will be used in your frontend HTML,
and the Secret key will be used in your backend hosted by Cloud Functions.
5. Add a page with a reCAPTCHA
Now we'll add a new HTML page to display the reCAPTCHA. In the
public directory in your project, add a new HTML file called
recaptcha.html to display the reCAPTCHA. Simply copy and paste the
following content directly into that new file:
Notice in the body there is a div with the class "g-recaptcha". The
first thing you should do here is copy your reCAPTCHA site key into the
div's data-sitekey attribute value. This div will get automatically transformed
into a reCAPTCHA UI after the first script at the top is loaded. You can read
more about that here in the
docs.
You can see it right away if you firebase deploy again, then
navigate to /recaptcha.html under your Hosting URL. Don't bother dealing with
the reCAPTCHA yet, because we still need some backend code to complete the
verification!
The JavaScript code in this page defines two functions dataCallback
and dataExpiredCallback. These are referenced in the div, and
provide callbacks for the reCAPTCHA to tell you when the reCAPTCHA has been
satisfied, or if the user took too long to proceed.
The important thing to note in dataCallback is that it redirects
the browser to another URL in the site with the path
/checkRecaptcha, and pass it a parameter named
response. This response string is generated by reCAPTCHA and looks
like a random collection of characters.
The path /checkRecaptcha in your web site obviously doesn't exist
yet, so we need to create a Cloud Function to validate the response string it's
going to receive.
6. Create a Cloud Function to verify the reCAPTCHA response
In the functions directory in your project, edit the existing index.js file.
This has some sample code, but you can delete it. In its place, paste the
following JavaScript code:
The first thing you should do here is paste your reCAPTCHA
secret key from the registration site in place of "PASTE_YOUR_SECRET_CODE_HERE".
(Astute readers may note that the reCAPTCHA API endpoint host is
"recaptcha.google.com", while the docs say "www.google.com". This is OK! You
have to use recaptcha.google.com as shown in order to make the call on the Spark
plan, because that host has been whitelisted for outgoing traffic from Cloud
Functions.)
This code defines an HTTPS function that, when triggered, will make another
HTTPS request (using the request-promise module) to the reCAPTCHA API in order
to verify the
response that was received in the query string. Notice that there are three
cases with three different responses to the client. Either:
The reCAPTCHA verifies successfully (the user is human)
The reCAPTCHA fails (could be a robot)
The API call fails altogether
It's important to send a response to the client in all cases, otherwise
the function will time out with an error message in the Firebase console log.
To deploy this new function (and the web content at the same time) run the
following command:
$ firebase deploy
You'll notice in the output that the function is assigned its own URL, which
looks something like this:
This is clearly a different host than the one with your web content. However,
what we really want instead is for the function to be referenced through your
web host at a URL that looks like this:
What you've done here is add a new section for rewrites, and you can read more
about those in the docs. Specifically what this does is allow access to the URL
path /checkRecaptcha invoke the function called
checkRecaptcha that you pasted into your
functions/index.js file.
Remember that the JavaScript code in recaptcha.html redirects to
this path when the reCAPTCHA is satisfied by the user, so this effectively sends
to user to the function after they complete the reCAPTCHA.
Now do one final deploy to send everything to Firebase:
$ firebase deploy
8. Test the reCAPTCHA!
Navigate to /recaptcha.html under your hosting URL, then solve the
reCAPTCHA. It may ask you to identify some cars or roads in a set of pictures.
Once you've satisfied the reCAPTCHA with your humanity, the JavaScript in your
HTML should redirect you to your function, which verifies with the server that
you're indeed human, and you should see the message "You're good to go, human."
This example of how to use reCAPTCHA with Cloud Functions for Firebase is much
more simple than what you'd probably do in your own web site. You have several
options for how to send the reCAPTCHA response to your function, and you'd
obviously want to provide something more useful than a message to the user. But
this should get you started protecting your web content from abuse from bots.
Engineer, Firebase Hosting
Mature applications separate configuration from code. Doing so lets you easily switch between staging and production, deploy an open-source code sample, or spin up a new QA environment (see also "store config in the environment" from the 12 Factor App pattern).
Historically, this has been difficult for Firebase projects on the web, because
you needed to keep track of the configuration options for
firebase.initializeApp(). You might have had to write code that
looked like this:
// DON'T DO THIS ANYMORE!
switch (location.hostname) {
case 'myapp.com': firebase.initializeApp(prodConfig); break;
case 'myapp-staging.com': firebase.initializeApp(stagingConfig); break;
default: firebase.initializeApp(devConfig); break;
}
Not the best experience, plus it exposes your various staging environments in
public-facing code. Today we're excited to announce some new features of
Firebase Hosting and the Firebase CLI that makes configuring Firebase on your
Web app and working with multiple environments much simpler.
On-Demand Firebase SDK Auto-configuration
We've introduced some new reserved URLs to Firebase Hosting that will let you
load the Firebase SDK and automatically configure it for the current
environment without having to write any custom code. All you have to do
is include the right script tags:
<!doctype html>
<html>
<body>
...
<!-- Import and initialize the Firebase SDK -->
<script src="/__/firebase/3.7.4/firebase-app.js"></script>
<script src="/__/firebase/3.7.4/firebase-auth.js"></script>
<script src="/__/firebase/init.js"></script>
<script>
// The Firebase SDK is ready to rock!
firebase.auth().onAuthStateChange(function(user) { /* … */ });
</script>
</body>
</html>
This works on sites deployed to Firebase Hosting, and also
works locally when using firebase serve! The new URLs available
are:
/__/firebase/{VERSION}/firebase.js - the complete Firebase JS
SDK
/__/firebase/init.js - script that auto-initializes the SDK
when included. Must be loaded after any Firebase SDKs.
/__/firebase/init.json - JSON representation of the
configuration for firebase.initializeApp(). You can use this to
fetch config and initialize Firebase SDKs asynchronously.
When serving locally, init.js initializes the currently selected
project for your project directory (see "Using Project
Aliases" for more information). When deployed, it initializes for the
deployed project. In addition, when deployed, the scripts are served over HTTP/2
and you can enjoy the benefits of loading the SDK from the same origin without
having to manage your own dependencies.
Note: You will need at least version 3.6.0 of the
Firebase CLI to use these features, and may need to reauthenticate. If you see a
warning message when running firebase deploy, run firebase
login --reauth to enable init.js.
Integrating into Existing Toolchains
What if you're not on Firebase Hosting or already bundle or otherwise build your
JavaScript? You may want to have other ways to get the configuration for a given
project. We've added the new firebase setup:web command to version
3.6.0 of the Firebase CLI. This can serve as a handy reference for copy and
paste. The command prints out config information, but can also be integrated
with your existing Node.js build process by using the Firebase CLI as a module.
To do so, first install firebase-tools as a dependency:
$ npm install --save firebase-tools@^3.6
Next, require it and call the command. For example:
const fbcli = require('firebase-tools');
const fs = require('fs');
// by default, uses the current project and logged in user
fbcli.setup.web().then(config => {
fs.writeFileSync(
'build/initFirebase.js',
`firebase.initializeApp(${JSON.stringify(config)});`
);
});
// alternatively, you can pass project or token information
fbcli.setup.web({
project: 'my-custom-project',
token: process.env.FIREBASE_TOKEN
});
We think these updates will make it much simpler to build mature,
multi-environment web applications on Firebase, and we're excited to see the
creative ways you use them. Let us know what you think!