how to upload firebase images to web using local emulator? - node.js

I'm trying to upload some images to a firebase storage bucket,
and trying to run the code locally in the firebase emulator.
With most examples I'll get an error like:
"Unhandled error FirebaseError: Firebase: Need to provide options, when not being deployed to hosting via source.
(app/no-options). at initializeApp
I tried this: https://firebase.google.com/docs/admin/setup
const defaultApp = initializeApp({ credential: applicationDefault() })
Which may or not be working.
I think this creates an authed app (maybe?) but if I later use
const storage = getStorage() I get the same auth error.
So next, I tried using the defaultApp for next steps, like:
const storage = defaultApp.storage();
that doesn't error, but it returns something different from getStorage, that I can't seem to use for other needed things like getting a ref
as per examples https://firebase.google.com/docs/storage/web/upload-files
So it's not clear how to upload files to firebase from the local simulator.
my full function looks like this:
import cuid from "cuid";
import {
getStorage,
ref,
uploadBytes,
uploadBytesResumable,
getDownloadURL
} from "firebase/storage";
import { admin, defaultApp } from '../utils/firebaseInit'
export function saveToBucket(url: string): string {
const normalStorage = getStorage() // Gets a FirebaseStorage instance for the given Firebase app. but NOT AUTHED
const defaultStorage = defaultApp.storage(); // Gets the default FirebaseStorage instance.
const storageRef = ref(normalStorage)
const bucketPath = `personas-out/img-${cuid()}.jpg`
const bucket = defaultStorage.bucket();
// const bucketRef = bucket.ref(); // Property 'ref' does not exist on type 'Bucket'.ts(2339)
const metadata = {
contentType: 'image/jpeg'
};
const imageRef = ref(storageRef, bucketPath);
const file = new File([url], bucketPath, {
type: "image/jpeg",
});
// will happen in the background?
uploadBytesResumable(imageRef, file, metadata)
.then((snapshot: any) => {
console.log('Uploaded', snapshot);
});
return bucketPath
}

You're not supposed to use the Firebase client SDK (firebase/storage) in nodejs code. The client SDKs are meant to work in a browser only. For nodejs code that runs on a backend (which is what you're doing when you deploy to Cloud Functions), you should use the Firebase Admin SDK. The API it exposes for Storage is essentially a thin wrapper around the Google Cloud Storage nodejs library.

Related

How to upload a base64 image URL to Firebase and retrieve its access token?

In a MERN + Firebase project, I have an image data string that I want to upload and then get the access token of that file.
The image data string is of the following form:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAAClCAYAAABSmmH3AAAAAXNSR0IArs4c6QAABFJJREFUeF7t1oENg0AQA8F8/6XSAx8pVWTloQIzZwvOvfd+PAQI/LXAMdS/vo9wBH4ChqoIBAIChho4kogEDFUHCAQEDDVwJBEJGKoOEAgIGGrgSCISMFQdIBAQMNTAkUQkYKg6QCAgYKiBI4lIwFB1gEBAwFADRxKRgKHqAIGAgKEGjiQiAUPVAQIBAUMNHElEAoaqAwQCAoYaOJKIBAxVBwgEBAw1cCQRCRiqDhAICBhq4EgiEjBUHSAQEDDUwJFEJGCoOkAgIGCogSOJSMBQdYBAQMBQA0cSkYCh6gCBgIChBo4kIgFD1QECAQFDDRxJRAKGqgMEAgKGGjiSiAQMVQcIBAQMNXAkEQkYqg4QCAgYauBIIhIwVB0gEBAw1MCRRCRgqDpAICBgqIEjiUjAUHWAQEDAUANHEpGAoeoAgYCAoQaOJCIBQ9UBAgEBQw0cSUQChqoDBAIChho4kogEDFUHCAQEDDVwJBEJGKoOEAgIGGrgSCISMFQdIBAQMNTAkUQkYKg6QCAgYKiBI4lIwFB1gEBAwFADRxKRgKHqAIGAgKEGjiQiAUPVAQIBAUMNHElEAoaqAwQCAoYaOJKIBAxVBwgEBAw1cCQRCRiqDhAICBhq4EgiEjBUHSAQEDDUwJFEJGCoOkAgIGCogSOJSMBQdYBAQMBQA0cSkYCh6gCBgIChBo4kIgFD1QECAQFDDRxJRAKGqgMEAgKGGjiSiAQMVQcIBAQMNXAkEQkYqg4QCAgYauBIIhIwVB0gEBAw1MCRRCRgqDpAICBgqIEjiUjAUHWAQEDAUANHEpGAoeoAgYCAoQaOJCIBQ9UBAgGB8zzPDeQUkcC0wHnf11CnK+DlCwJ+fQtXknFewBd1vgIACgK+qIUryTgvYKjzFQBQEDDUwpVknBcw1PkKACgIGGrhSjLOCxjqfAUAFAQMtXAlGecFDHW+AgAKAoZauJKM8wKGOl8BAAUBQy1cScZ5AUOdrwCAgoChFq4k47yAoc5XAEBBwFALV5JxXsBQ5ysAoCBgqIUryTgvYKjzFQBQEDDUwpVknBcw1PkKACgIGGrhSjLOCxjqfAUAFAQMtXAlGecFDHW+AgAKAoZauJKM8wKGOl8BAAUBQy1cScZ5AUOdrwCAgoChFq4k47yAoc5XAEBBwFALV5JxXsBQ5ysAoCBgqIUryTgvYKjzFQBQEDDUwpVknBcw1PkKACgIGGrhSjLOCxjqfAUAFAQMtXAlGecFDHW+AgAKAoZauJKM8wKGOl8BAAUBQy1cScZ5AUOdrwCAgoChFq4k47yAoc5XAEBBwFALV5JxXsBQ5ysAoCBgqIUryTgvYKjzFQBQEDDUwpVknBcw1PkKACgIGGrhSjLOCxjqfAUAFAQMtXAlGecFDHW+AgAKAoZauJKM8wKGOl8BAAUBQy1cScZ5AUOdrwCAgoChFq4k47yAoc5XAEBBwFALV5JxXsBQ5ysAoCDwBaT9ke70WG4vAAAAAElFTkSuQmCC
This is the reference to where the file should be uploaded:
const imageRef: StorageReference = ref(
storage,
`/issueImages/${firebaseImageId}`
);
So far, I have attempted to use the 'put' function with the imageRef, and when I try using uploadBytes() of firebase, I have to upload it as a Buffer, and even then I cannot seem to find the access token in the metadata.
To upload a data URL to Firebase, you would use storageRef.putString(url, 'DATA_URL') (legacy) or uploadString(storageRef, url, 'DATA_URL') (modern) depending on the SDK you are using.
When you upload a file to a Cloud Storage bucket, it will not be issued an access token until a client calls its version of getDownloadURL(). So to fix your issue, you would call getDownloadURL() immediately after upload.
If Node is running on a client's machine, you would use:
// legacy syntax
import * as firebase from "firebase";
// reference to file
const imageStorageRef = firebase.storage()
.ref(`/issueImages/${firebaseImageId}`);
// perform the upload
await imageStorageRef.putString(dataUrl, 'DATA_URL');
// get the download URL
const imageStorageDownloadURL = await imageStorageRef.getDownloadURL();
// modern syntax
import { getStorage, getDownloadURL, ref, uploadString } from "firebase/storage";
// reference to file
const imageStorageRef = ref(
getStorage(),
`/issueImages/${firebaseImageId}`
);
// perform the upload
await uploadString(imageStorageRef, dataUrl, 'DATA_URL');
// get the download URL
const imageStorageDownloadURL = await getDownloadURL(imageStorageRef);
If Node is running on a private server you control, you should opt to use the Firebase Admin SDK instead as it bypasses the rate limits and restrictions applied to the client SDKs.
As mentioned before, the download URLs aren't created automatically. Unfortunately for us, getDownloadURL is a feature of the client SDKs and the Admin SDK doesn't have it. So we can either let a client call getDownloadURL when it is needed or we can manually create the download URL if we want to insert it into a database.
Nico has an excellent write up on how Firebase Storage URLs work, where they collated information from the Firebase Extensions GitHub and this StackOverflow thread. In summary, to create (or recreate) a download URL once it has been uploaded, you can use the following function:
import { uuid } from "uuidv4";
// Original Credit: Nico (#nicomqh)
// https://www.sentinelstand.com/article/guide-to-firebase-storage-download-urls-tokens
// "file" is an instance of the File class from the Cloud Storage SDK
// executing this function more than once will revoke all previous tokens
function createDownloadURL(file) {
const downloadToken = uuid();
await file.setMetadata({
metadata: {
firebaseStorageDownloadTokens: downloadToken
}
});
return `https://firebasestorage.googleapis.com/v0/b/${file.bucket.name}/o/${encodeURIComponent(file.name)}?alt=media&token=${downloadToken}`;
}
The allows us to change the client-side code above into the following so it can run using the Admin SDK:
// assuming firebase-admin is initialized already
import { getStorage } from "firebase-admin/storage";
// reference to file
const imageStorageFile = getStorage()
.bucket()
.file(`/issueImages/${firebaseImageId}`);
// perform the upload
await imageStorageFile.save(dataUrl);
// get the download URL
const imageStorageDownloadURL = await createDownloadURL(imageStorageFile);
In all of the above examples, a download URL is retrieved and saved to the imageStorageDownloadURL variable. You should store this value as-is in your database. However, if you instead want to store only the access token and reassemble the URL on an as-needed basis, you can extract the token from its ?token= parameter using:
const downloadToken = new URL(imageStorageDownloadURL).searchParams.get('token');

Firestore Recursive Delete via Firebase Admin SDK Local Node Server

I'm building a Firebase Admin SDK local nodejs server to which I'm trying to add recursive delete functionality. The examples I've been able to find, such as:
https://firebase.google.com/docs/firestore/solutions/delete-collections
use firebase-functions and appear to require an auth token, which all seems applicable when using the Firebase SDK rather than the Firebase Admin SDK.
Here is my code, which I know isn't the way it can be done, but it hopefully conveys what I am attempting to do. Any leads would be greatly appreciated.
const firebaseTools = require('firebase-tools');
async function recursiveDocumentDelete(fsDb, docRefPath) {
const collRefPath = extractCollRefPath(docRefPath);
const docId = extractDocId(docRefPath);
try {
const docToBeDeleted = await fsDb.collection(collRefPath).doc(docId).get();
if (docToBeDeleted.exists) {
recursiveDeleteFn = firebaseTools.functions().httpsCallable('recursiveDelete');
const recursiveDeleteResponse = await recursiveDeleteFn({ path: docRefPath })
console.log(`Document with ID ${docId} in the ${collRefPath} collection/subcollection, and all of its decendants, were successfully deleted at ${recursiveDeleteResponse.writeTime.toDate()}!`);
return JSON.stringify(recursiveDeleteResponse);
} else {
console.log(`Document with ID ${docId} in the ${collRefPath} collection/subcollection was not found`);
}
} catch(err) {
console.log(err);
}
}

Google Storage is not a constructor error

I am building an App and my objective is every time someone upload an image to firebase storage, the cloud function resize that image.
...
import * as Storage from '#google-cloud/storage'
const gcs = new Storage()
...
exports.resizeImage = functions.storage.object().onFinalize( async object => {
const bucket = gcs.bucket(object.bucket);
const filePath = object.name;
const fileName = filePath.split('/').pop();
const bucketDir = dirname(filePath);
....
And when I tried to deploy this funtion I get this error:
Error: Error occurred while parsing your function triggers.
TypeError: Storage is not a constructor
I tried with "new Storage()" or just "Storage" and nothing works.
I'm newbie around here so if there's anything I forgot for you to debug this just let me know.
Thanks!
google-cloud/storage: 2.0.0
Node js: v8.11.4
The API documentation for Cloud Storage suggests that you should use require to load the module:
const Storage = require('#google-cloud/storage');
This applied to versions of Cloud Storage prior to version 2.x.
In 2.x, there was a breaking API change. You need to do this now:
const { Storage } = require('#google-cloud/storage');
If you would like TypeScript bindings, consider using Cloud Storage via the Firebase Admin SDK. The Admin SDK simply wraps the Cloud Storage module, and also exports type bindings to go along with it. It's easy to use:
import * as admin from 'firebase-admin'
admin.initializeApp()
admin.storage().bucket(...)
admin.storage() gives you a reference to the Storage object that you're trying to work with.
On node 14, using commonJS with babel (although I don't think Babel was interfering here), this is how I eventually got it working on an older project bumping GCS from 1.x to 5.x:
const Storage = require('#google-cloud/storage');
const gcs = new Storage({project_id});
didn't see this captured anywhere on the web
Just for knowledge purpose if you guys are using the ES module approach and node 12, the below snippet would work. I couldn't make none of other syntax working~
import storagePackage from '#google-cloud/storage';
const { Storage } = storagePackage;
const storage = new Storage();
If you are using electron and still don't work above solutions,try this.
const {Storage} = window.require('#google-cloud/storage');
const storage = new Storage({ keyFilename: "./uploader-credentials.json" });

How do I access Firebase Storage inside of Functions on Node.js server?

I am running a Firebase Functions instance like so:
import * as functions from 'firebase-functions'
import * as express from 'express'
import * as admin from 'firebase-admin'
import { MyApi } from './server'
admin.initializeApp(functions.config().firebase)
const firebaseDb: admin.database.Database = admin.database()
const app: express.Application = MyApi.bootstrap(firebaseDb).app
export const myApp = functions.https.onRequest(app)
Everything works fine with my Realtime Database, but I am having trouble integrating Storage properly. According to the docs, I need to set it up like this:
var config = {
apiKey: '<your-api-key>',
authDomain: '<your-auth-domain>',
databaseURL: '<your-database-url>',
storageBucket: '<your-storage-bucket>'
};
firebase.initializeApp(config);
// Get a reference to the storage service, which is used to create references in your storage bucket
var storage = firebase.storage();
var storageRef = storage.ref();
However this doesn't work for me since I'm using the Admin SDK. I'm not importing the firebase library at all. I tried accessing Storage like this:
const fbStorage = admin.storage()
This feels wrong. The interface for the Admin storage method is completely different from the Firebase client SDK's:
node_modules/firebase-admin/lib/index.d.ts
declare namespace admin.storage {
interface Storage {
app: admin.app.App;
bucket(name?: string): Bucket;
}
}
node_modules/firebase/index.d.ts
declare namespace firebase.storage {
interface Storage {
app: firebase.app.App;
maxOperationRetryTime: number;
maxUploadRetryTime: number;
ref(path?: string): firebase.storage.Reference;
refFromURL(url: string): firebase.storage.Reference;
setMaxOperationRetryTime(time: number): any;
setMaxUploadRetryTime(time: number): any;
}
Significantly, the admin Storage interface is missing the ref() and ref().put() methods, so none of the documentation dealing with uploading files applies. I can access my files through admin.storage().bucket().file('path/to/file.jpg'), but that seems rather roundabout and I'm not sure I'm supposed to be doing that.
As a workaround, I tried to initialize a non-admin Firebase app (firebase.initializeApp(config)) on top of admin.initializeApp(). But when I try to launch Functions that gives the fatal error database: Firebase: Firebase service named 'database' already registered (app/duplicate-service).
Right now I made a separate app, and am trying to delegate Storage functionality to that secondary app. Is there a better way?
Thanks.
UDPATE (ANSWER):
Thanks to Renaud's advice, I learned that my initial attempt was actually the correct one. Turns out you ARE supposed to access your Storage instance through admin.storage(). The interface definitions do differ, because the client-side SDK and admin (server-side) SDK are catering to different needs. If you want to include the type definitions, you need to import them from '#google-cloud/storage'.
Below is a sample of how to use the Bucket API based on the official docs:
import { Bucket } from '#google-cloud/storage'
const filename = 'path/to/myfile.mp3'
const bucket: Bucket = admin.storage().bucket()
bucket
.upload(filename, {
destination: 'audio/music/myfile.mp3',
})
.then(() => {
console.log(`${filename} uploaded to ${bucket.name}.`)
})
.catch(err => {
console.error('ERROR:', err)
})
(I hope I understood correctly your question) Here is an example on how to access the Cloud Storage from a Cloud Function in order to stream a file:
const config = {
projectId: '.....',
keyFilename: './......json'
};
const storage = require('#google-cloud/storage')(config);
const yourStorageBucket = 'xyz.appspot.com';
const bucket = storage.bucket(yourStorageBucket);
const file = bucket.file(filename);
const stream = file.createWriteStream({resumable: false});
....
You can have a look for more details at:
https://cloud.google.com/nodejs/getting-started/using-cloud-storage#uploading_to_cloud_storage
and
https://firebase.google.com/docs/storage/extend-with-functions

firebase nodejs server and gcloud storage--get metadata

for the correct security of my app i have been obliged to set up a small nodejs server for firebase, but the problems are not over.... the firebase server sdk not support the storage apis. i read that for this is neccessary a gcloud storage apis because firebase use the same service.
in the server side is important to get a files custom metadata because i must read and update they. i not find the apposite functions to get a file metadata. in the client sdk is simple
// Create a reference to the file whose metadata we want to retrieve
var forestRef = storageRef.child('images/forest.jpg');
// Get metadata properties
forestRef.getMetadata().then(function(metadata) {
// Metadata now contains the metadata for 'images/forest.jpg'
}).catch(function(error) {
// Uh-oh, an error occurred!
});
and in gcloud storage what function can i use?? thanks
You'll want to use the getMetadata() method in gcloud:
var gcloud = require('gcloud');
// Initialize GCS
var gcs = gcloud.storage({
projectId: 'my-project',
keyFilename: '/path/to/keyfile.json'
});
// Reference an existing bucket
var bucket = gcs.bucket('foo.appspot.com');
// Reference to a file
var file = bucket.file('path/to/my/file');
// Get the file metadata
file.getMetadata(function(err, metadata, apiResponse) {
if (err) {
console.log(err);
} else {
console.log(metadata);
}
});

Resources