Creating a static site: large size file uploader by Firebase Hosting and Storage | Part.1

Posted: March 04, 2020


Background

I need a file uploader for some people. I never met them and they also don’t know me.

I just want to gather movie clips from them for my friends wedding party.

At first, I planned to use email with large file sender system. But it looked very slow and it looked not secure.

Then I thought it’s good to use AWS S3 to store it.

I’d like users to use social login for auth then they can upload files for security and audit reason.

I investigate some S3 API and Cognito but it’s not easy both of users and me!

So I choose Firebase Auth and Google login, I already know Firebase Hosting and Authentication.

It’s a piece of cake!


1. Create Firebase Project

Go to console: https://console.firebase.google.com/

And create a new project.

2. Set up Firebase Hosting

$ mkdir my-project
$ cd $_

$ npm install -g firebase-tools
$ firebase login
# => go to browser and login

$ firebase init

? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choi
ces.
 ◯ Database: Deploy Firebase Realtime Database Rules
 ◯ Firestore: Deploy rules and create indexes for Firestore
 ◯ Functions: Configure and deploy Cloud Functions
❯◉ Hosting: Configure and deploy Firebase Hosting sites
 ◯ Storage: Deploy Cloud Storage security rules
 ◯ Emulators: Set up local emulators for Firebase features

? Please select an option: (Use arrow keys)
❯ Use an existing project

? Select a default Firebase project for this directory: my-project-0000 (My Project 0000)
i  Using project my-project-0000 (My Project 0000)

? What do you want to use as your public directory? public
? Configure as a single-page app (rewrite all urls to /index.html)? No

✔  Firebase initialization complete!

i  hosting: Serving hosting files from: public
✔  hosting: Local server: http://localhost:5000

# deploy to Firebase Hosting
$ firebase deploy

=== Deploying to 'my-project-0000'...

i  deploying hosting
i  hosting[my-project-0000]: beginning deploy...
i  hosting[my-project-0000]: found 2 files in public
✔  hosting[my-project-0000]: file upload complete
i  hosting[my-project-0000]: finalizing version...
✔  hosting[my-project-0000]: version finalized
i  hosting[my-project-0000]: releasing new version...
✔  hosting[my-project-0000]: release complete

✔  Deploy complete!

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

Open Hosting URL in a browser,

Default homepage of firebase

See?

Now we can develop and deploy uploader site by firebase

3. Try Firebase Authentication

As a second step, activate Google sign-in in Firebase Authentication console.

Select Authentication , then click Sign-in method tab and choose Google and enable it.

Firebase Authentication setting

And add web app in Firebase next.

Go to Project settings and add app on the bottom.

After creation, you can see Firebase SDK snippet to use our JS code.

Firebase project setting

4. Prototyping #1 - Google login via Firebase Auth

It’s coding time!

Change contents in index.html below,

<!-- /* index.html */ -->

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Welcome to awesome site</title>

  <!-- update the version number as needed -->
  <script defer src="/__/firebase/7.9.3/firebase-app.js"></script>
  <!-- include only the Firebase features as you need -->
  <script defer src="/__/firebase/7.9.3/firebase-auth.js"></script>
  <script defer src="/__/firebase/7.9.3/firebase-storage.js"></script>

  <script src="https://www.gstatic.com/firebasejs/ui/4.4.0/firebase-ui-auth.js"></script>
  <!-- <script src="https://www.gstatic.com/firebasejs/ui/4.4.0/firebase-ui-auth__{lang}.js"></script> -->
  <link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.4.0/firebase-ui-auth.css" />

  <!-- initialize the SDK after all desired features are loaded -->
  <script src="https://www.gstatic.com/firebasejs/7.9.3/firebase.js"></script>
  <script defer src="/__/firebase/init.js"></script>
</head>

<body>

  <!-- #01 -->
  <section id="non-login" class="container"">
    <h2>Please login, then you can upload file. </h2>
    <br />
    <div id="loader">Firebase SDK Loading&hellip;</div>
    <div id="firebaseui-auth-container"></div>
  </section>

  <!-- #02 -->
  <section id="did-login" class="container" style="display: none;">
    <h2>You are logged in</h2>
  </section>

<script src="app.js"></script>

<style media="screen">
  body {
    background: #ECEFF1;
    min-height: 100vh;
    color: rgba(0,0,0,0.87);
    margin: 0;
    padding: 0;
  }

  .container {
    background: white; max-width: 380px; margin: 100px auto 16px; padding: 32px 24px; border-radius: 3px;
  }
  .container h2 {
    color: #333333;
    font-weight: bold;
    font-size: 16px;
    margin: 0 0 8px;
  }
</style>
</body>

</html>

And create app.js. Please use your own firebaseConfig data the previous step.

// app.js

(() => {

// initialize app
function init() {
  _firebaseInit()
  _firebaseAuthInit()
}

// selector helper
function _(id) {
  return document.getElementById(id);
}

// initialize firebase
function _firebaseInit() {
  // Your web app's Firebase configuration
  var firebaseConfig = {
    apiKey: "<your own data here>",
    authDomain: "<your own data here>",
    databaseURL: "<your own data here>",
    projectId: "<your own data here>",
    storageBucket: "<your own data here>",
    messagingSenderId: "<your own data here>",
    appId: "<your own data here>"
  };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
}

// initialize firebase auth via firebase-ui
function _firebaseAuthInit() {
  var ui = new firebaseui.auth.AuthUI(firebase.auth());
  var uiConfig = {
    callbacks: {
      signInSuccessWithAuthResult: function(authResult, redirectUrl) {
        _('did-login').style.display = 'block';
        _('non-login').style.display = 'none';
        return false;
      },
      uiShown: function() {
        _('loader').style.display = 'none';
      }
    },
    signInFlow: 'popup',
    signInOptions: [
      firebase.auth.GoogleAuthProvider.PROVIDER_ID,
    ],
  };
  ui.start('#firebaseui-auth-container', uiConfig);
}

init()
})();

Then run and test locally,

$ firebase serve

i  hosting: Serving hosting files from: public
✔  hosting: Local server: http://localhost:5000

$ open http://localhost:5000

If code and setting are correct, you can see google login button and form like below image.

Google login button

Google sign in form

Logged in

5. Prototyping #2 - List data on Firebase Storage

Create a folder on Firebase Storage

Create bucket and add uploaded_items folder and go inside the folder.

Firebase storage: dir

Then upload some files, that names should be {YYYYMMDD}__{username}__{filename} . underscores are doubled _ _.

For example, I uploaded 20200305__evalphobia__awesome_photo.png .

Firebase storage: uploaded file

Then, go to Rules tab in Storage and setting the secure rules as below,

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /uploaded_items/{item} {
      allow list, create: if request.auth != null;
    }
    match /uploaded_items {
      allow list: if request.auth != null;
    }
  }
}

Firebase storage rule

Add codes for listing files

Add a table for listing file in the section after login.

<!-- /* index.html */ -->

  <!-- #02 -->
  <section id="did-login" class="container" style="display: none;">
    <div>
        <h3>Uploaded File List</h2>
        <table class="list-table">
          <thead>
            <tr>
              <th>Date</th>
              <th>User</th>
              <th>File</th>
            </th>
          </thead>
          <tbody id="uploaded-data-list"></tbody>
        </table>
        <ul  class="file-list">
        </ul>
      </div>
  </section>

...

<style>
  .list-table {
    border: #ECEFF1 solid 1px;
    border-collapse: collapse;
    border-spacing: 8px 2px;
  }
  .list-table tbody {
    border-top: #ECEFF1 solid 1px;
  }
  .list-table th, td {
    padding: 2px;
    border-left: #ECEFF1 solid 1px;
    border-right: #ECEFF1 solid 1px;
  }
</style>

And add listing logics,

// app.js

const storagePrefix = 'uploaded_items/'

function fetchFileListAndUpdateListView() {
  // fetch file list from Firebase Storage
  const listRef = firebase.storage().ref(storagePrefix);
  let results = [];
  listRef.listAll().then(res => {
    res.items.forEach(itemRef => {
      const path = itemRef.fullPath.replace(storagePrefix, '')
      results.push(path)
    });

    // update the table view
    updateListView(results)
  }).catch(err => {
    console.warn(`error on list: ${err}`)
  });
}

function updateListView(list) {
  // remove all list
  const el = _("uploaded-data-list")
  el.textContent = null;
  while (el.firstChild) el.removeChild(el.firstChild);

  // create new list
  let fragment = document.createDocumentFragment();
  list.forEach(item => {
    // naming convention must be `{date}__{username}__{filename}`
    // (e.g.) `20200305__evalphobia__my_awesome_movie-01.mp4`
    const parts = item.split('__')
    const date = parts[0]
    const user = parts[1]
    const path = parts[2]

    const td1 = document.createElement('td');
    td1.appendChild(document.createTextNode(date))
    const td2 = document.createElement('td');
    td2.appendChild(document.createTextNode(user))
    const td3 = document.createElement('td');
    td3.appendChild(document.createTextNode(path))
    const tr = document.createElement('tr');
    tr.appendChild(td1)
    tr.appendChild(td2)
    tr.appendChild(td3)

    fragment.appendChild(tr);
  })

  // add the list
  el.appendChild(fragment)
}

And use the new logic after login,

// app.js

function _firebaseAuthInit() {
  var ui = new firebaseui.auth.AuthUI(firebase.auth());
  var uiConfig = {
    callbacks: {
      signInSuccessWithAuthResult: function(authResult, redirectUrl) {
        _('did-login').style.display = 'block';
        _('non-login').style.display = 'none';
        fetchFileListAndUpdateListView()  // <-- insert here
        return false;
      },

Check this out!

Uploaded file list

Now we can see uploaded files.

We have one more last part, upload!


Next article is here - Creating a static site: large size file uploader by Firebase Hosting and Storage | Part.2