Sebastian Schmidt
Software Engineer

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:

 const db = firebase.firestore();
 db.enablePersistence({experimentalTabSynchronization:true}).then(() => {
       console.log("Woohoo! Multi-Tab Persistence!");
     });

Try out our new offline persistence features and let us know what you think! Share your feedback in our Google Group or on our GitHub page.

Mertcan Mermerkaya
Software Engineer

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".

Doug Stevenson
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:

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/your-project/overview
Hosting URL: https://your-project.firebaseapp.com

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:

<html>
  <head>
    <title>Firebase + reCAPTCHA</title>
    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
    <script type="text/javascript">
    function dataCallback(response) {
        console.log("dataCallback", response)
        window.location.href = "/checkRecaptcha?response=" + encodeURIComponent(response)
    }
    function dataExpiredCallback() {
        console.log("dataExpiredCallback")
    }
    </script>
  </head>
  <body>
    <div class="g-recaptcha"
      data-sitekey="PASTE_YOUR_SITE_KEY_HERE"
      data-callback="dataCallback"
      data-expired-callback="dataExpiredCallback"/>
  </body>
</html>

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:

const functions = require('firebase-functions')
const rp = require('request-promise')

exports.checkRecaptcha = functions.https.onRequest((req, res) => {
    const response = req.query.response
    console.log("recaptcha response", response)
    rp({
        uri: 'https://recaptcha.google.com/recaptcha/api/siteverify',
        method: 'POST',
        formData: {
            secret: 'PASTE_YOUR_SECRET_CODE_HERE',
            response: response
        },
        json: true
    }).then(result => {
        console.log("recaptcha result", result)
        if (result.success) {
            res.send("You're good to go, human.")
        }
        else {
            res.send("Recaptcha verification failed. Are you a robot?")
        }
    }).catch(reason => {
        console.log("Recaptcha request failure", reason)
        res.send("Recaptcha request failed.")
    })
})

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:

  1. The reCAPTCHA verifies successfully (the user is human)
  2. The reCAPTCHA fails (could be a robot)
  3. 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:

https://us-central1-your-project.cloudfunctions.net/checkRecaptcha

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:

https://your-project.firebaseapp.com/checkRecaptcha

This makes the function look like it's part of your web site. With Firebase Hosting a Cloud Functions, this can be done!

7. Add rewrites to map a hosting URL to a Cloud Function

Edit the file firebase.json in the project root directory and paste the follow JSON configuration as its contents:

{
  "hosting": {
    "public": "public",
    "rewrites": [
      {
        "source": "/checkRecaptcha",
        "function": "checkRecaptcha"
      }
    ]
  }
}

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.

Michael Bleigh
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-{app,auth,database,messaging,storage}.js - modular SDK files (recommended)
  • /__/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!