How to build a Firebase Extension

Disclaimer: As of today, Firebase Extensions are an experimental feature in the platform and the way they work might change or even get removed from Firebase.

What are Firebase Extensions?

Firebase Extensions are the easiest way of pack and reuse Firebase Functions. That’s it. An extension is a packaged function that get installed in your Firebase project (one or multiple times) and that can be configured from the command line or the admin interface.

Firebase offers a set of extensions that have been growing on time, from sending emails, to process payments or translate text. Someone already created those functions for you and you just need to install and configure them in your projects.

You can request to become a publisher of extensions, this post is not about that, especially since it’s not clear to me how you apply to become a publisher. This post is about how to build one and test it.

Let’s build a Firebase Extension: Reverse geocoding

Let’s build a extension that given a document from your Firestore database with latitude and longitude, will reverse geocode those coordinates into a human readable address.

Firstly, mention that I wasn’t able to find much documentation about how to build them. So what I did is simple, use the power of Open Source and read the code:

You can find the codebase for this extension in this github repo.

First thing if you are going to work with extensions, tell firebase you want to play around with them, remember, is still a feature in preview mode:

$ firebase --open-sesame extdev

Let’s use the extension initializer that is, so far, hidden in the command line:

$ firebase ext:dev:init

After running that command and following some questions, you have an extension that is an example ready to modify:

Extension template created with firebase ext:dev:init

Your project structure will be:

  • Record your changes and different versions. Add release notes and those will appear in the Firebase Admin panel.
  • Documentation in markdown format that will be presented to the customer before installing the extension. The content of this file will be displayed by the command firebase ext:info. Useful if you plan to publish your extension.
  • This file provides your users an overview of how to use your extension after they’ve installed it. All content is optional, but this is the recommended format. Your users will see the contents of this file in the Firebase console after they install the extension.
  • extension.yaml: This is the most important file. It describes the resources that your extension is exposing, like function or multiple functions. Also, declares the most important part of an extension, the parameters that you can use to configure the extension and adapt it to your needs. Unfortunately, all links related to documentation in the template are broken, so learning more about this file, and the correct format was a reading exercise from the extensions already created.
  • functions: This directory will contain your Firebase functions.

Our extension.yaml

Our extension transform latitude and longitude into a human readable address using Google Maps SDK. If we want to make it reusable we need to make it flexible.

To achieve that we will do the following:

  • We will define a function that will be triggered every time an document is created or updated in an specific Firebase path. This path must be configurable so users of our extension can adapt it to their needs. We will define a variable, COLLECTION_PATH, to configure this.
  • We should read latitude and longitude from fields in that document. Those fields should be configurable too (for example, they could be plain fields like ‘latitude’ or could be part of a Map ‘geo.latitude’). We will define two variables, LATITUDE_FIELD_NAME and LONGITUDE_FIELD_NAME, to let our extension know where to get latitude and longitude values.
  • The result should be stored in an configurable field of the original document. We will define the variable OUTPUT_FIELD_NAME to define where to store the address information.
  • Finally we will be using the Google Maps SDK to perform the reverse geocoding operation. An extra configurable parameter will be used here to each developer using your extension will need to provide their own. For this purpose we will define the variable GOOGLE_MAPS_API_KEY.

Here is our extension.yaml file:

name: firestore-extension-reverse-geocoding  # Identifier for your extension
version: 0.0.1  # Follow semver versioning
specVersion: v1beta  # Version of the Firebase Extensions specification

# Friendly display name for your extension (~3-5 words)
displayName: Reverse Geocoding

# Brief description of the task your extension performs (~1 sentence)
description: >-
  Given a document with a latitude and longitude, add an attribute to the document with the address for that point.

license: Apache-2.0  #

# Public URL for the source code of your extension

# Specify whether a paid-tier billing plan is required to use your extension.
# As we will performing calls to an external service the paid-tier will be necessary for this extension.
billingRequired: true

# In a `roles` field, list any IAM access roles required for your extension to operate.
  - role: datastore.user
    reason: Allows the extension to write to your Firestore Database instance.

# In the `resources` field, list each of your extension's functions, including the trigger for each function.
  - name: fsreversegeocoding
    type: firebaseextensions.v1beta.function
      Listens for new documents on the path specified via configuration, will look for latitude and longitude
      parameters and resolve them to a concrete address.
      location: ${LOCATION}
      runtime: nodejs12
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/${COLLECTION_PATH}/{messageId}

# In the `params` field, set up your extension's user-configured parameters.
  - param: LOCATION
    label: Cloud Functions location
    description: >-
      Where do you want to deploy the functions created for this extension?
      You usually want a location close to your database. For help selecting a
      location, refer to the [location selection
    type: select
      - label: Iowa (us-central1)
        value: us-central1
      - label: South Carolina (us-east1)
        value: us-east1
      - label: Northern Virginia (us-east4)
        value: us-east4
      - label: Belgium (europe-west1)
        value: europe-west1
      - label: London (europe-west2)
        value: europe-west2
      - label: Frankfurt (europe-west3)
        value: europe-west3
      - label: Hong Kong (asia-east2)
        value: asia-east2
      - label: Tokyo (asia-northeast1)
        value: asia-northeast1
    default: us-central1
    required: true
    immutable: true

    label: Collection path
    description: >
      What is the path to the collection that contains the latitude and longitued that you want to analyze?
    example: items
    validationRegex: "^[^/]+(/[^/]+/[^/]+)*$"
    validationErrorMessage: Must be a valid Cloud Firestore Collection
    default: items
    required: true

    label: Latitude field name
    description: >
      What is the name of the field that contains latitude value. Accepts dot notation.
    example: lat
    default: lat
    required: true

    label: Longitude field name
    description: >
      What is the name of the field that contains the longitude value. Accepts dot notation.
    example: lon
    default: lon
    required: true
    label: Output field
    description: >
      What is the name of the field where you want to store your reverse geocoding components.
    example: reverse_geo
    default: reverse_geo
    required: true

    label: Google Maps Key
    description: >
      Google Maps Api Key used to request the reverse geocoding.
    example: XXX
    default: null
    required: true

Fetching our configuration

Once we install the extension we will need to configure and give value to those parameters. How do we fetch that data? Easy, through environment variables: process.env.

You can request this values at will, we will store them in a simple object in a file called config.js:

"use strict";

module.exports.default = {
  location: process.env.LOCATION,
  latitudeFieldName: process.env.LATITUDE_FIELD_NAME,
  longitudeFieldName: process.env.LONGITUDE_FIELD_NAME,
  outputFieldName: process.env.OUTPUT_FIELD_NAME,
  apiKey: process.env.GOOGLE_MAPS_API_KEY,

Building our main function

In our extension.yaml file we defined which resources our extension will be providing, and we name them. In our case we named a function called fsreversegeocoding, so that will be the function exported in our index.js file.

As well, for those familiar with Firestore, there are specific handlers for dealing with changes in documents, so our function will look like:

module.exports.fsreversegeocoding =
    functions.handler.firestore.document.onWrite(async (change) => {
        // do your stuff here

We will use node-geocoder for calling the Google Maps API and Firebase SDK to store the results.

Take a look to the final result here.

Installing your extension

Let’s try our extension in one of our projects. We will do that from command line, from the directory containing the extension. Remember we are not distributing this extension, so if you want to install it download the source code and:

  • Remember to enable the firebase beta features if you didn’t do it before:
$ firebase --open-sesame extdev

Install the extension and provide the values for the configurable variables:

$ firebase ext:install . --project <your_project_name>

When you execute the install command you will get prompt for all the configurable attributes that you defined in the extension.

And from here, you’ll be able to interact and configure your extension from the Firebase Console.

Replacing irccloud with open web technologies

I’ve been a very happy irccloud user for the last 3 years, I think they offer a great service, but my passion for open web technologies and self-hosting solutions made this service the first candidate to be part of my revolution.

For me, the most important use cases for irccloud are to be always online, keep the logs of all channels I’m in and being notified when someone ping me or an interesting topic arises.

The search for self-hosting starts

With those as primary needs, I started searching for different open source projects but quickly I realised that hosting weechat is the best option for me:

  • It’s open source and well maintained.
  • Super extensible with several scripting languages.
  • Really well documented.
  • Has several opens source clients.

From those clients, I’m specially interested in the web ones, don’t need another app in my phone.

Glowing Bear is for me the best of the web clients:

  • Again well maintained and supported by the community.
  • Works well in desktop and mobile.
  • On the way to be a progressive web app, with manifest and serviceworker support!

What’s missing?

The only part missing in my setup is the ability to this combination of weechat + glowing-bear to use web push to perform notifications. Right now it uses normal desktop notifications to notify about private messages, but that’s not a push from the server running weechat. When we are not running glowing-bear we won’t know. And that part is important!

So what do we need to build? Spoiler alert, I already did, as a proof of concept, so I end up building the following:

  • A weechat plugin that uses web push when you receive a private message to send the notification to your browser.
  • Modify Glowing Bear to use web push and display notifications. Also reopen the client when user clicks on the notifcation.

A webpush Weechat plugin

Based on notifo plugin by Alex Poirot, modified the original python script to use webpush libraries and variables coming from the client.

You can find the weechat script in this github repo.

When you load the script via:
/python load

You will be ask to provide a set of variables to make the script work. Some of those variables you will find them in the Glowing Bear client (scroll down for more details), other variables as the VAPID information needed for performing push can be generated by your own or via a curl request doing:
$bash> curl

While setting the variables needed you will see something like:

/set plugins.var.python.webpush.endpoint ""

Variables settings for webpush plugin

Modifications to the Glowing Bear client

You can find all the modifications done to the client in this github fork (in the branch push_notifications).

Just two modifications. Once you accept to receive notifications from the glowing bear client, you will have in the settings screen the extra variables needed to configure the weechat plugin variables, see the image below:

push settings

The other modification happens directly in the file serviceworker.js where I only had to add the logic to process push notifications, open a new tab is there is no tab already open.


So I recorded a little video showing how you can receive the notifications even if your tab is closed, hope you enjoy (better to make the video bigger to check the text :O)


Playing around with The Physical Web

It’s been a while since I heard the first time from The Physical Web Project, but was not until last Over the Air where I had the chance with @wilsonpage and others to try out my self a beacon, and be surrounded by multiple of this tiny URL emitters.

The concept is quite simple, but at the same time adds so many new paths, questions and possibilities that after some time, that is after restarting my work at Mozilla decided to give it a spin.

My first approach was pretty simple, I spend a lot of time in front of a browser. Firefox for Android and Firefox OS are my choices, when I’m in a mobile device. They take care of my privacy, synchronize tabs with my desktop, also sync passwords, bookmarks, history and are damn fast. So, with all the love that I have for the browser, what could be a better place than the browser to interact with the Physical Web?

I started coding on my spare time, first with the android version, since it’s been a while that I don’t contribute to that project and wanted to refresh my skills.

And just a couple of weeks ago I finished my hack, at the same time that Google Ubiquity conference was happening. I didn’t have the opportunity to attend but while watching it offline came to the Physical Web introduction video that you can watch below:

Scott Jenson talk was stunning and pretty inspirational. So many possibilities! But the thing that make me open my eyes the most was the announcement of Opera and Chrome considering to add the concept of the Physical Web to their browsers! \o/

Seems I was not that crazy! So after another couple of nights finally I managed to polish the hack. Integration of the Physical Web into Firefox for Android, still a lot to do, but it does work beautifully.

No better place to find physical urls than your browser ?

After talking with some people from the Firefox for Android team they commented that will be amazing to have it as an add-on, for that we still need to wait until Gecko catch up with the implementation of the W3C Web Bluethooth api, but that will be really soon!