Reputation: 3713
I'm trying to reach files stored on my Firebase Storage to return a list of them to my APK. I made a cloud function, which is not working, even though when everything seems to be right settle.
index.js
const {Storage} = require('@google-cloud/storage');
const functions = require('firebase-functions');
const images = require('./images.js');
const storage = new Storage({
projectId: '<my_id>',
keyFilename: './key.json',
});
const bucket = storage.bucket('<my_bucket>')
exports.getImages = functions.https.onRequest((request, response) => {
images.getImages(bucket).then(urls => response.send(urls))
})
images.js
module.exports = {
getImages
}
const query = {
directory: '/images'
};
async function getImages(bucket) {
return bucket.getFiles(query).then(files => {
console.log(files)
getUrls(files).then(urls => console.log(urls))
.catch(() => console.error('URLs failure'));
}).catch(() => console.error('Files failure'));
}
function getUrls(files) {
const promises = []
files.forEach(file => promises.push(file.getDownloadURL()))
return Promise.all(promises);
}
My Key.json
is located inside my functions folder
Key.json
{
"type": "service_account",
"project_id": "…”,
"private_key_id": “…”,
"private_key": "-----BEGIN PRIVATE KEY——…”,
"client_email": “…”,
"client_id": “…”,
"auth_uri": “…”,
"token_uri": “…”,
"auth_provider_x509_cert_url": “…”,
"client_x509_cert_url": “…”
}
Storage Rules
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true
}
}
}
Executing getImages()
function output is:
console.log(files): [ [] ]
console.error('Files failure'): Files failure
What am I doing wrong here, please?
Upvotes: 2
Views: 1898
Reputation: 3713
First and main problem I had was connecting with my Google Cloud Service, this means:
Downloaded file and settle on my project as my new Key.json
Then comes the trickiest part. According to Google's documentation
You can use the bucket references returned by the Admin SDK in conjunction with the official Google Cloud Storage client libraries to upload, download, and modify content in the buckets associated with your Firebase projects. Note that you do not have to authenticate Google Cloud Storage libraries when using the Firebase Admin SDK. The bucket references returned by the Admin SDK are already authenticated with the credentials used to initialize your Firebase app.
This means, if you are running you service locally (npm run shell
), you need to instantiate your firebase-admin
.
var serviceAccount = require("./key.json");
var admin = require("firebase-admin");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://<my_project>.firebaseio.com"
});
and then calling
const bucket = admin.storage().bucket('<my_bucket>.appspot.com')
you will be ready to access your files. When accessing your google functions directly through deployment or from cloud console, then accessing your storage wouldn't require an admin
initialization or the key itself such as the first exposed index.js
on the question, turning the code only into something like
const {Storage} = require('@google-cloud/storage');
...
const storage = new Storage({
projectId: '<my_id>',
});
const bucket = storage.bucket('<my_bucket>')
...
Regarding what Mayeru
said:
you have specified the directory as "/images" but instead it should be "images/"
"images/"
could be used for querying prefix
parameters, not directory
. This example shows how it works, like its reference to google's documentation as well.
The following code, with some promise tweaks and parsing JSON data for Android, works like a charm. Locally and remotely.
index.js
var serviceAccount = require("./key.json");
const functions = require('firebase-functions');
const images = require('./images.js');
var admin = require("firebase-admin");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://<my_project>.firebaseio.com"
});
const bucket = admin.storage().bucket('<my_bucket>.appspot.com')
exports.getImages = functions.https.onRequest((request, response) => {
images.getImages(bucket)
.then(urls => response.status(200).send({ data: { urls } }))
.catch(err => console.error(err));
})
images.js
module.exports = {
getImages
}
const query = {
directory: 'images'
};
function getImages(bucket) {
return bucket.getFiles(query)
.then(response => getUrls(response))
.catch(err => console.error(err));
}
function getUrls(response) {
const promises = []
response.forEach( files => {
files.forEach (file => {
promises.push(getSignedUrl(file));
});
});
return Promise.all(promises).then(result => getParsedUrls(result));
}
function getSignedUrl(file) {
return file.getSignedUrl({
action: 'read',
expires: '09-01-2019'
})
}
function getParsedUrls(result) {
return JSON.stringify(result.map(mediaLink => createMedia(mediaLink)));
}
function createMedia(mediaLink) {
const reference = {};
reference.mediaLink = mediaLink[0];
return reference;
}
This will for sure list all the files under images
folder from your bucket and access their signed download urls to be rendered with libraries like Fresco
. Last, but not least, the download URL will be exposed, but to make a GET
request directly on it will require adjustment on your bucket permissions. Anyway, this "permissions thing" is another topic which you might find how to do it also here on stack overflow.
Upvotes: 3
Reputation: 1094
The logs show:
[[]] // this is the "files" you retrieved.
Files failure
As you can see, you are not retrieving any file, hence why it fails the call to get URLs from empty array.
you have specified the directory as "/images" but instead it should be "images/"
That should be able to retrieved the proper folder.
Also, the function "getDownloadURL()" don't appear as supported for "file" objects on nodejs lib documentation. You might be looking for getSignedUrl ?
Upvotes: 3