Skip to content

Firebase backend plugin for Mavo. Store and sync app data in milliseconds. Store and serve files at Google scale. Authenticate users simply and securely. All the Google Firebase powers are at your fingertips.

License

Notifications You must be signed in to change notification settings

DmitrySharabin/mavo-firebase-firestore

Repository files navigation

Firebase backend plugin for Mavo

To use Firebase backend, follow the setup instructions below.

Table of Contents

Setup Firebase

Step 1: Sign into Firebase using your Google account

Step 2: Create a Firebase project

  1. In the Firebase console, click Add project, then enter a Project name.

  2. (Optional) If you are creating a new project, you can edit the Project ID.

    Firebase automatically assigns a unique ID to your Firebase project. To use a specific identifier, you must edit your project ID during this setup step. You cannot change your project ID later.

  3. Click Continue.

  4. (Optional) Set up Google Analytics for your project, then click Continue.

  5. Click Create project.

    Firebase automatically provisions resources for your Firebase project. When the process completes, you'll be taken to the overview page for your Firebase project in the Firebase console.

  6. Click Continue.

Step 3: Register your app with Firebase

  1. In the center of the Firebase console's project overview page, click the Web icon (</>) to launch the setup workflow.

If you've already added an app to your Firebase project, click Add app to display the platform options.

  1. Enter your app's nickname.

    This nickname is an internal, convenience identifier and is only visible to you in the Firebase console.

  2. (Optional) Set up Firebase Hosting for your web app.

  3. Click Register app.

  4. Save for later usage the two values: projectId and apiKey.

Firebase Config

  1. Click Continue to console.

Step 4: Activate Cloud Firestore

  1. In the Firebase console, open the Database section.

  2. Click Create database.

  3. Review the messaging about securing your data using security rules. Choose the mode you want to start in.

  4. Click Next.

  5. Select a location for your Cloud Firestore data.

Note: If you aren't able to select a location, then your project already has a default resource location. It was set either during project creation or when setting up another service that requires a location setting.

Warning: After you set your project's default resource location, you cannot change it.

  1. Click Done.

Step 5: Set up Cloud Firestore security rules

  1. In the Firebase console, open the Database section.

  2. Open the Rules tab.

  3. Write your rules in the online editor, then click Publish.

Step 6: Enable Google Sign-In in the Firebase console

  1. In the Firebase console, open the Authentication section.

  2. On the Sign-in method tab, in the Sign-in providers section, enable the Google sign-in method.

You must specify Project support email by selecting it from the list. You can also edit the provided Project public-facing name.

  1. Click Save.

Step 7: Whitelist your domain

To use Firebase Authentication in a web app, you must whitelist the domains that the Firebase Authentication servers can redirect to after signing in a user.

By default, localhost and your Firebase project's hosting domain are whitelisted. You must whitelist the full domain names of any other of your Mavo app's hosts. Note: whitelisting a domain allows for requests from any URL and port of that domain.

  1. In the Firebase console, open the Authentication section.

  2. On the Settings tab, in the Authorized domains section, click Add domain.

  3. Type in the name of your domain and click Add.

Step 8: Create a default Storage bucket

Cloud Storage for Firebase lets you upload and share user generated content, such as images and video, which allows you to build rich media content into your apps.

  1. From the navigation pane of the Firebase console, select Storage, then click Get started.

  2. Review the messaging about securing your Storage data using security rules. During development, consider setting up your rules for public access.

  3. Click Next.

  4. Select a location for your default Storage bucket.

Note: If you aren't able to select a location, then your project already has a default resource location. It was set either during project creation or when setting up another service that requires a location setting.

Warning: After you set your project's default resource location, you cannot change it.

  1. Click Done.

Step 9: Set up security rules for the default Storage bucket

  1. In the Firebase console, open the Storage section.

  2. Open the Rules tab.

  3. Write your rules in the online editor, then click Publish.

Setup Mavo application

Note: In all the following examples, instead of mv-storage, you can also use other backend types: mv-source, mv-init, and mv-uploads.

Set the following attributes on the Mavo root element (the one with the mv-app attribute):

Attribute Value Example
mv-storage firebase://projectId
(see Step 3)
Note: firebase:// is mandatory.
mv-storage="firebase://mavo-demos".
mv-storage-key apiKey
(see Step 3)
mv-storage-key="AIzaSyDvZ3EBuhlvFm529vMPeU7dbqvdkjv9WQU"

Note: The Firebase backend lets you have multiple Mavo applications in one Firebase project if they have different names. That means you can use the projectId and apiKey values numerous times.

By default, the Firebase backend stores data in the mavo-apps collection in a document whose name matches the name of your Mavo app. You can change it by specifying the collection's name (and a path to it) and the corresponding document's name after the projectId. The projectId, the collection name, and the filename must be divided by a forward slash, like so: mv-storage="firebase://projectId/path/to/collection/filename". You can also leave the default filename and specify only the collection, like so: mv-storage="firebase://projectId/path/to/collection".

Note: You can specify values for either of these parameters (projectId, collection, and filename) via the mv-storage-project, mv-storage-collection, and mv-storage-filename storage attributes accordingly. See the example below.

Working with collections of documents

Starting from v0.4.0, the plugin allows you to work with a collection of documents in addition to working with a single document.

You can specify which documents you want to retrieve from the collection by adding the mv-source-query attribute to the root of your app. Its value is a query you'd like the plugin to perform while retrieving the collection documents.

For example, by adding mv-source-query='where("state", "==", "CA")' to the root of your app, you'll get all the documents whose state property equals CA. The plugin will return them as a list named $items, where each item has two properties. The id property is with the corresponding file name, and the data is with the actual file data. So if you want to show the names of those documents as an unordered list, you can do it like so:

<ul mv-list="$items">
  <li mv-list-item>
    <span property="id">Document name</span>
  </li>
</ul>

You can perform complex queries (with multiple criteria) by chaining multiple where() operators with a dot. For example, mv-source-query='where("state", "==", "CA").where("population", "<", 1000000)'.

In addition to == and <, you can use other comparison operators inside where(). You can find the complete list of supported query operators in the official docs.

Warning: The where() operator inside the mv-source-query value is the operator from Firebase. It's not the same thing as the Mavo where operator.

If you need to work with the whole collection of documents and there is no need to filter some of the documents out with the where() operator, you can use the mv-source-query attribute with no value.

You can be more explicit about your intention to get all collection documents (and get more readable code 😉) by specifying mv-source-query="all".

Note: mv-source-query, mv-source-query="", and mv-source-query="all" give the same result—you'll get all the documents from the specified collection.

Warning: If you provide both mv-source-query and the name of a document (in mv-source or via the mv-source-filename attribute), the plugin will ignore the latter.

Customization

By default, the Firebase backend doesn't enable authentication (auth) and storing an end-user's files (storage) and doesn't load the corresponding JavaScript files implementing these features. You can enable either of these features or both by adding the mv-storage-options attribute to the Mavo root. For example, mv-storage-options="auth" enables authentication, mv-storage-options="storage" lets end-users upload their files, and mv-storage-options="auth storage" enables both of these features (see example below).

Note: Keep in mind that by default, if the authentication feature is on, only signed-in users can edit and save the app's data, regardless of the security rules set for your app in the Firebase console. Add all-can-edit and/or all-can-write to the mv-storage-options attribute value to override the default behavior to enable the corresponding feature.

Need to get real-time updates? Add realtime to the mv-storage-options attribute value. If there is no mv-storage-options attribute, simply add mv-storage-options="realtime" to the root of your app.

The plugin supports offline data persistence. This feature caches a copy of the data that your app is using so your app can access the data when the device is offline. When the device comes back online, the plugin synchronizes any local changes made by your app to the backend. To enable this feature, add the offline-persistence option to mv-storage-options.

Note: Please keep in mind that if you enable both the real-time updates and the offline persistence features simultaneously (i.e., you specify mv-storage-options="realtime offline-persistence" in the root of your app), that might cause some issues with real-time updates if your app is opened in more than one tab. Changes made in the app in one tab won't be pushed to the app in the inactive tab.

The files in the storage bucket are presented in a hierarchical structure, just like the file system on your local hard disk. Every app has its folder (whose name matches the Mavo app's name) for all its files. You can specify the name of that folder (including the full path) via the mv-storage-bucketname attribute, like so: mv-storage-bucketname="folderName".

Authentication with Firebase using Google, Facebook, Twitter, or GitHub accounts

By integrating the corresponding services into your app, you can let your users authenticate with Firebase using their Google, Facebook, Twitter, or GitHub accounts. To do so, add the mv-storage-providers attribute to the root of your Mavo app. Its value is a space-separated set of names of services you want to enable. For example, mv-storage-providers="google twitter github".

Note: If you use mv-storage-providers, there is no need to add mv-storage-options="auth" to the root element.

Authenticate Using Facebook

  1. On the Facebook for Developers site, get the App ID and an App Secret for your app.
  2. Enable Facebook Login:
    1. In the Firebase console, open the Auth section.
    2. On the Sign in method tab, enable the Facebook sign-in method and specify the App ID and App Secret you got from Facebook.
    3. Make sure your Firebase OAuth redirect URI (e.g. mavo-demos.firebaseapp.com/__/auth/handler) is listed as one of your OAuth redirect URIs in your Facebook app's settings page on the Facebook for Developers site in the Product Settings > Facebook Login config.
  3. Click Save.

Authenticate Using Twitter

  1. In the Firebase console, open the Auth section.
  2. On the Sign in method tab, enable the Twitter provider.
  3. Add the API key and API secret from that provider's developer console to the provider configuration:
    1. Register your app as a developer application on Twitter and get your app's OAuth API key and API secret.
    2. Make sure your Firebase OAuth redirect URI (e.g. mavo-demos.firebaseapp.com/__/auth/handler) is set as your Authorization callback URL in your app's settings page on your Twitter app's config.
  4. Click Save.

Authenticate Using GitHub

  1. In the Firebase console, open the Auth section.
  2. On the Sign in method tab, enable the GitHub provider.
  3. Add the Client ID and Client Secret from that provider's developer console to the provider configuration:
    1. Register your app as a developer application on GitHub and get your app's OAuth 2.0 Client ID and Client Secret.
    2. Make sure your Firebase OAuth redirect URI (e.g. mavo-demos.firebaseapp.com/__/auth/handler) is set as your Authorization callback URL in your app's settings page on your GitHub app's config.
  4. Click Save.

Note: Authentication Using Google was described here.

Customizing Text & Localization

The plugin provides a set of phrases you can use, change, and localize. Here is the list of ids of these phrases and their default values:

Messages in the console

id Default Value
firebase-enable-auth You might need to enable authorization in your app. To do so, add mv-storage-options="auth" to the Mavo root. Note: Instead of mv-storage, you can also use other backend types: mv-source, mv-init, and mv-uploads.
firebase-enable-storage It seems your app does not support uploads. To enable uploads, add mv-storage-options="storage" to the Mavo root. Note: Instead of mv-storage, you can also use other backend types: mv-source, mv-init, and mv-uploads.
firebase-check-security-rules Please check the security rules for your app. They might be inappropriately set. For details, see https://plugins.mavo.io/plugin/firebase-firestore#security-rules-examples.
firebase-offline-persistence-unimplemented The current browser does not support all of the features required to enable offline persistence. This feature is supported only by Chrome, Safari, and Firefox web browsers.
firebase-offline-persistence-failed-precondition Multiple tabs open, offline persistence can only be enabled in one tab at a time.
firebase-query-syntax-error The query you provided is malformed. It must start with the “where()” operator. For example, “where("city", ==, "LA")”.
firebase-query-failed We couldn't retrieve documents from the “{collection}” collection by performing the following query: {query}. It might be your query is malformed or contains unsupported operators. For the list of all supported query operators, see https://firebase.google.com/docs/firestore/query-data/queries#query_operators.

Mavo toolbar buttons

id Default Value
firebase-auth-google Google
firebase-auth-facebook Facebook
firebase-auth-twitter Twitter
firebase-auth-github GitHub

Styling with CSS

You can style Mavo toolbar buttons separately via classes.

Button Class
Google mv-firebase-auth-google
Facebook mv-firebase-auth-facebook
Twitter mv-firebase-auth-twitter
GitHub mv-firebase-auth-github

Note: You may also find useful this selector .mv-bar.mv-ui button[class*="mv-firebase-auth"]::before to style the icon before the log-in buttons or change the icon itself.

Demo

<main
    mv-app="myAwesomeApp"
    mv-plugins="firebase-firestore"
    mv-storage="firebase://"
    mv-storage-key="AIzaSyDvZ3EBuhlvFm529vMPeU7dbqvdkjv9WQU"
    mv-storage-project="mavo-demos"
    mv-storage-collection="mavo-apps"
    mv-storage-filename="todo"
    mv-storage-options="auth">
    <header>
        <h1>My tasks</h1>
        <p>
            <strong>[count(done)]</strong> done out of
            <strong>[count(task)]</strong> total
        </p>
    </header>

    <ul>
        <li property="task" mv-multiple>
            <label>
                <input property="done" type="checkbox" />
                <span property="taskTitle">Do stuff</span>
            </label>
        </li>
        <button
            class="clear-completed mv-logged-in"
            mv-action="delete(task where done)">
            Clear Completed
        </button>
    </ul>
</main>
<style>
@font-face {
	font-family: "Marker Felt";
	src: local("Marker Felt"), url("https://cdn.jsdelivr.net/gh/DmitrySharabin/mavo-firebase-firestore/demos/todo/Marker-Felt.woff");
}

body {
	padding: 1em;
	background: url("https://cdn.jsdelivr.net/gh/DmitrySharabin/mavo-firebase-firestore/demos/todo/wood.png") #4e2e0e;
	text-align: center;
	font: 100% Helvetica Neue, sans-serif;
	color: white;
}
body * {
	margin: 0;
	padding: 0;
	font-family: inherit;
	font-size: inherit;
	line-height: inherit;
	color: inherit;
}
body a {
	text-decoration: none;
}

@-webkit-keyframes transform {
	from {
		transform: rotate(8deg);
	}
}

@keyframes transform {
	from {
		transform: rotate(8deg);
	}
}
main ul {
	position: relative;
	z-index: 1;
	display: block;
	max-width: 20em;
	padding: 2em;
	margin: 1em auto 3em;
	border-top-right-radius: 100% 2%;
	border-bottom-left-radius: 1% 100%;
	box-shadow: 2px 3px 20px black, 0 0 60px #8a4d0f inset;
	background: #fffef0;
	font-size: 120%;
	text-align: left;
}
main ul::before,
main ul::after {
	content: "";
	position: absolute;
	top: 0;
	right: 0;
	bottom: 0;
	left: 0;
	z-index: -1;
	background: inherit;
	box-shadow: inherit;
	transform: rotate(-3deg);
	-webkit-animation: transform 0.4s cubic-bezier(0.25, 0.1, 0.4, 1.5);
	animation: transform 0.4s cubic-bezier(0.25, 0.1, 0.4, 1.5);
}
main ul::after {
	transform: rotate(2deg);
}
main :checked + [property="taskTitle"] {
	font-style: italic;
	text-decoration: line-through;
	color: gray;
	mix-blend-mode: multiply;
}
main input::-webkit-input-placeholder {
	font-style: italic;
	color: #999;
	mix-blend-mode: multiply;
}
main input::-moz-placeholder {
	font-style: italic;
	color: #999;
	mix-blend-mode: multiply;
}
main button.mv-add-task {
	padding: 0.2em 0.5em;
	margin-top: 1em;
	border-radius: 0.3em;
	border: 1px solid rgba(0, 0, 0, 0.2);
	background: #85c20a
		linear-gradient(rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0));
	box-shadow: 0 1px rgba(255, 255, 255, 0.5) inset, 0 0.1em 0.2em -0.1em black;
	color: white;
	text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.5);
	font-weight: bold;
	cursor: pointer;
	font-size: 120%;
}
main button.mv-add:hover {
	background-color: orange;
}
main button.mv-add:active {
	box-shadow: 0 0.1em 0.3em rgba(0, 0, 0, 0.8) inset;
	background-image: none;
}
main > header {
	display: block;
	margin: 2em 0 3em;
	text-shadow: 0 -1px 3px black;
	opacity: 0.9;
	font-size: 120%;
}
main > header > h1 {
	font-weight: 100;
	font-size: 400%;
	font-family: inherit;
	opacity: 0.6;
}
main > header > p {
	margin-top: 0.5em;
}
main .auth-controls,
main .progress {
	mix-blend-mode: multiply;
}
main .mv-item-bar.mv-ui {
	position: static;
	pointer-events: auto;
	opacity: 1 !important;
	mix-blend-mode: multiply;
	border: 0;
}

li[property="task"] {
	display: flex;
	align-items: center;
	border-bottom: 1px dashed rgba(51, 85, 153, 0.5);
	list-style: none;
	color: #013;
	font-size: 1.5rem;
}
li[property="task"].mv-deleted {
	padding: 0.3em;
}
li[property="task"] label {
	flex: 1;
	padding: 0.2em;
	margin: 0.3em 0;
	font-family: Marker Felt, Helvetica Neue, sans-serif;
}

.clear-completed {
	background: none;
	border: none;
	color: black;
	float: right;
	margin-top: 1.5em;
	text-decoration: underline;
	opacity: 0.6;
	cursor: pointer;
}
.clear-completed:hover {
	opacity: 1;
}

[mv-app]:not([mv-mode="edit"]) .clear-completed {
	display: none;
}
</style>

Security rules examples

General rules

Cloud Firestore

  1. Allow read/write access on all documents to any authenticated user:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth.uid != null;
    }
  }
}
  1. Allow public read/write access on all documents:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}
  1. Allow public read access, but only authenticated users can write:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read: if true
      allow write: if request.auth.uid != null;
    }
  }
}

Storage bucket

  1. Only authenticated users can read or write to the bucket:
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}
  1. Anyone, even people not using the app, can read or write to the bucket:
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write;
    }
  }
}
  1. Anyone, even people not using the app, can read from the bucket, only authenticated users write to the bucket:
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read;
      allow write: if request.auth != null;
    }
  }
}

App-specific rules

Suppose your Mavo app has the name myAwesomeApp. The Firebase backend stores its data in files and folders matching the app's name by default. Keep in mind that the app data is stored in the file inside the mavo-apps collection.

Default collection “mavo-apps”

The corresponding security rules could look like this:

Cloud Firestore

  1. Allow read/write access on your app's data to any authenticated user:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /mavo-apps/myAwesomeApp {
      allow read, write: if request.auth.uid != null;
    }
  }
}
  1. Allow public read/write access on your app's data:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /mavo-apps/myAwesomeApp {
      allow read, write: if true;
    }
  }
}
  1. Allow public read access, but only authenticated users can write:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /mavo-apps/myAwesomeApp {
      allow read: if true
      allow write: if request.auth.uid != null;
    }
  }
}

Storage bucket

  1. Only authenticated users can read or write to the bucket:
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /myAwesomeApp {
        match /{allPaths=**} {
            allow read, write: if request.auth != null;
        }
    }
  }
}
  1. Anyone, even people not using the app, can read or write to the bucket:
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /myAwesomeApp {
        match /{allPaths=**} {
            allow read, write;
        }
    }
  }
}
  1. Anyone, even people not using the app, can read from the bucket, only authenticated users write to the bucket:
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /myAwesomeApp {
        match /{allPaths=**} {
            allow read;
            allow write: if request.auth != null;
        }
    }
  }
}

Warning: These rules are applied only to the myAwesomeApp app and its data. If you use them as is, data and files of other apps that use the same Firebase project become inaccessible. So what to do? Save some patience and wait a bit for the security rules generator app. 😉

About

Firebase backend plugin for Mavo. Store and sync app data in milliseconds. Store and serve files at Google scale. Authenticate users simply and securely. All the Google Firebase powers are at your fingertips.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published