4
votes

I need some users to be able to upload files to firebase storage, but they're non tech-savvy, so I will let them upload to drive first, and mirror drive from storage. The thing is, I can't figure out how without having to host a server; google apps scripts can't access firebase storage easily (it can access both firebase databases though) and I need a server to use the google drive API, which could be done using firebase cloud functions, but I wonder if there is an easier alternative.

1
You will have to create your own implementation for this feature is not mentioned in Drive API. What I can suggest is create your web client so that it accepts uploads/downloads from certain users. You can check the Get Started with Storage Security Rules about that.noogui

1 Answers

5
votes

An image file can be uploaded to firebase storage using Apps Script.

There are 4 critical things that need to be done:

  • Enable the "Google Cloud Storage JSON API"
  • Get the "bucket" name of your Firebase Storage
  • Add the "https://www.googleapis.com/auth/devstorage.read_write" scope to the appsscript.json manifest file, along with all other scopes already needed.
  • Enable "Write" access in your Firebase storage rules

You will need to get an OAuth token, but you don't need an OAuth library.

Enable the "Google Cloud Storage JSON API"

This needs to be done for the Google account that will be uploading the file. The solution described here is for uploading a file where the Apps Script project, and the Firebase Storage are owned by the same Google Account.

  • Go to your Google Cloud Platform - From the code editor choose, "Resources" and "Cloud Platform project" - Click something in the dialog box to go to your Cloud Platform. Find the "API's and Services" section. Click "Enable API's and Services" Search "JSON" Enable the "Google Cloud Storage JSON API" service.

Get the "bucket" name of your Firebase Storage

Go to your Firebase Storage settings. Look for "gs://your-bucket-name.appsspot.com" That is your bucket name. Don't include the "gs://" The bucket name needs to have the "appspot.com" part on the end.

Add the "https://www.googleapis.com/auth/devstorage.read_write" scope to the appsscript.json manifest file

From the script editor, choose "File" and "Project Properties" and click the "Scopes" tab. Copy out all the existing scopes, and paste them somewhere so that you can get them back.

From the script editor, choose "View" and "Show Manifest file." Click on the appsscript.json file to open it. Add all existing scopes, plus the "https://www.googleapis.com/auth/devstorage.read_write" scope to the manifest file.

Manifest file to look like this, except with your time zone and scopes.

{
  "timeZone": "America/New_York",
  "dependencies": {
  },
  "webapp": {
    "access": "ANYONE_ANONYMOUS",
    "executeAs": "USER_DEPLOYING"
  },
  "exceptionLogging": "STACKDRIVER",
  "oauthScopes": [
    "https://mail.google.com/",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/script.container.ui",
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/script.scriptapp",
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/devstorage.read_write"
  ]
}

Enable "Write" access in your Firebase storage rules

allow read, write: if request.auth != null;

Firebase Storage Rules

Get the OAuth token:

You can get the OAuth token with:

ScriptApp.getOAuthToken();

So, you don't need an OAuth library, you don't need any SDK, you don't need to do anything with client side code, you don't need special information from a firebase service account or the legacy Database Secret.

The Code:

This code uploads an image file from Google Drive to firebase Storage

Note! Any file over 5MB may need something a little different.

function uploadToFirebaseStorage(po) {
try{
  var blob,bucketName,bytes,fileID,fileName,folderName,
    oA_Tkn,options,pathAndName,response,result,url;

  /* See
    https://cloud.google.com/storage/docs/uploading-objects?authuser=0
    for REST API information
  */

  /*
    Firebase uses the Google Cloud Storage API

  */

  bucketName = "your-bucket-name.appspot.com";
  folderName = "folder_name";
  fileName = "file_name";

  pathAndName = folderName + "/" + fileName;

  fileID = po.fileId;

  //curl "https://www.googleapis.com/upload/storage/v1/b/[BUCKET_NAME]/o?uploadType=media&name=[OBJECT_NAME]"
  url = 'https://www.googleapis.com/upload/storage/v1/b/' + bucketName + '/o?uploadType=media&name=' + pathAndName;

  blob = DriveApp.getFileById(fileID).getBlob();
  //Logger.log('blob.getContentType(): ' + blob.getContentType())

  bytes = blob.getBytes();
  //Logger.log('bytes: ' + bytes)

  oA_Tkn = ScriptApp.getOAuthToken();
  options = {
    method: "POST",//curl -X POST
    muteHttpExceptions: true,
    contentLength: bytes.length,
    contentType: blob.getContentType(),//curlv-H "Content-Type: [OBJECT_CONTENT_TYPE]"
    payload: bytes,
    headers: {//curl -H "Authorization: Bearer [OAUTH2_TOKEN]"
    Authorization: 'Bearer ' + oA_Tkn
    }
  }

  response = UrlFetchApp.fetch(url, options);

  result = JSON.parse(response.getContentText());
  Logger.log(JSON.stringify(result, null, 2));


  /*
    A successful return object looks like:

{
"kind": "storage#object",
"id": "bucket-name.appspot.com/online_store/file_name/abc123",
"selfLink": "https://www.googleapis.com/storage/v1/b/bucket-name.appspot.com/o/online_store%2FAAA_Test",
"name": "folder_Name/file_name",
"bucket": "bucket-name.appspot.com",
"generation": "abc123",
"metageneration": "1",
"contentType": "image/jpeg",
"timeCreated": "2018-10-24T00:47:33.435Z",
"updated": "2018-10-24T00:47:33.435Z",
"storageClass": "STANDARD",
"timeStorageClassUpdated": "2018-10-24T00:47:33.435Z",
"size": "950012",
"md5Hash": "abc123==",
"mediaLink": "https://www.googleapis.com/download/storage/v1/b/bucket-name.appspot.com/o/some_name%2FAAA_Test?generation=abc123&alt=media",
"crc32c": "kIY6Qg==",
"etag": "nwrfwfn="
}
*/
}catch(e) {
  Logger.log(e.message + "\n\n" + e.stack)

}
}

function testFB_Upload() {
  uploadToFirebaseStorage({fileId:"Put image file ID here"});
}

When the code is run for the first time, if the user has not enabled the API, there is a link provided in the extended error message in the response. So, you could modify the code to get the Cloud Console link from the error response. That link goes directly to the correct Cloud Console API, so the user doesn't need to know how to navigate their Cloud Console in order to find the correct API.

How to get the download url after the image has been uploaded

<head>
    <title>Your Site Name</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Firebase App is always required and must be first -->
    <script src="https://www.gstatic.com/firebasejs/5.5.7/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/5.5.7/firebase-auth.js"></script>
    <script src="https://www.gstatic.com/firebasejs/5.5.7/firebase-storage.js"></script>

  <script>
    // Initialize Firebase
    //Open the project - click Project Overview - Click the </> icon
    var config = {
      apiKey: "abc123",//Web API key in project settings
      authDomain: "your_name.firebaseapp.com",
      //databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
      projectId: "myID",//In Project Settings
      storageBucket: "myDomain.appspot.com"
    };
    firebase.initializeApp(config);
  </script>

</head>



window.srchForFile = function(po) {//client side code in a script tag
  //This function is called from a success handler AFTER the file has
  //originally been uploaded
try{
  /*
    po - parameters object - {fileID:'123ABC',folderName:'name_here'}
    po.fileID - the ID of the original file that was uploaded
    po.folderName - the name of the firebase folder to search
  */

  /*
    This code assumes that the firebase SDK has been loaded and that the
    firebase class is available
  */

  var fileID,fileName,imagesRef,spaceRef;

  fileID = po.fileId;
  fileName = "IMG_" + fileID;//The file name to search for which must
    //be exactly the same as the file just uploaded - make sure to use
    //a naming convention that is consistent

  if (!STORAGE_REF) {
    STORAGE_REF = firebase.storage().ref();//firebase SDK must be loaded
  }

  imagesRef = STORAGE_REF.child(po.folderName);
  //console.log('imagesRef: ' + imagesRef);

  spaceRef = imagesRef.child(fileName);// Points to the file name
  //console.log('spaceRef: ' + spaceRef);

  spaceRef.getDownloadURL().then(function(url) {
    //console.log('File available at: ' + url);

    if (!url) {
      url = false;
    }

    nextStepAfterFileSrch(url);//Now run another function
  }).catch(function(error) {//There was an error
     // Handle any errors here
     nextStepAfterFileSrch(false);
   }

  );

  //DO NOT HAVE ANY CODE HERE OR IT WILL RUN BEFORE THE ABOVE CODE IS
  //DONE
} catch(e) {
  showErrMsg(e.message);//client side error handling
}
}