firebase functions save the file to firebase storage - node.js

I am trying to generate a bundle in firebase functions and save it to firebase storage
exports.xyzUpdated = functions.firestore.document("xyz/{xyzID}").onWrite(async (change, context) => {
const db = admin.firestore();
const bucket = admin.storage().bucket("gs://xxx.appspot.com");// or empty or "xxx.appspot.com"
const bundle = db.bundle(bundleName);
const file = bucket.file(`bundles/${bundleName}.xyz`);
const bundleBuffer = bundle.add(`queryName`, querySnapshot).build();
await file.setMetadata({
"Cache-Control": "public, max-age=86400, s-maxage=86400",
});
await file.save(bundleBuffer, {});
});
I see an error in the cloud console:
Error: No such object: xxx.appspot.com/bundles/bundleName.xyz
What's wrong?
Thanks!

You are trying to set metadata for a file before that is uploaded. Try changing the order of the statements as shown below:
await file.save(bundleBuffer, {});
await file.setMetadata({
"Cache-Control": "public, max-age=86400, s-maxage=86400",
});
Alternatively, you can also set metadata while saving the file itself withouy setMetadata like this:
await file.save(bundleBuffer, {
metadata: {
cacheControl: "public, max-age=172800",
}
});

Related

Saving a Base64-encoded PDF string to firebase storage using firebase-admin in a cloud function

I have the contents of a PDF file encoded in a base-64 string that I would like to save to Firebase Storage using the Firebase Admin SDK in a TypeScript cloud function. Here is what I am trying:
const base64Pdf = ...;
const file = admin.storage().bucket().file("invoices/642d5000-851f-449d-8c4a-ec49aafabf80.pdf");
const pdfBuffer = Buffer.from(base64Pdf, "base64");
try {
await file.setMetadata({
contentType: "application/pdf",
});
await file.save(pdfBuffer);
const signedUrls = await file.getSignedUrl({
action: "read",
expires: "12-31-2500",
});
...
} catch (e) {
functions.logger.error(`[checkDocuments] Error saving PDF: ${e}`);
}
But I keep getting an error saying that the file object does not exist. I know it does not exist, since I'm trying to create it:
Error saving PDF: Error: No such object:
myproject.appspot.com/invoices/642d5000-851f-449d-8c4a-ec49aafabf80.pdf
Note that I already double-checked that Firebase storage was enabled for my project, and I even tried to create an "invoices" folder already.
A file must exist before you can set its metadata. Try updating order of setMetadata() and save() as shown below:
// save file before setting metadata
await file.save(pdfBuffer);
await file.setMetadata({
contentType: "application/pdf",
});
const signedUrls = await file.getSignedUrl({
action: "read",
expires: "12-31-2500",
});
Alternatively, you can set metadata using save() method itself:
await file.save(pdfBuffer, {
metadata: {
contentType: "application/pdf"
},
});

Generate acces token when uploading file to firebase storage [duplicate]

This question already has answers here:
Get Download URL from file uploaded with Cloud Functions for Firebase
(25 answers)
Closed 1 year ago.
I am experimenting with Firebase cloud functions and PDFs. I would like to send a PDF via email and save the Access token in Firebase database. My cloudfunctions looks like current code:
exports.invoice = functions
.https
.onRequest( (req, res) => {
const myPdfFile = admin.storage().bucket().file('/test/Arbeitsvertrag-2.pdf');
const doc = new pdfkit({ margin: 50 });
const stream = doc.pipe(myPdfFile.createWriteStream());
doc.fontSize(25).text('Test 4 PDF!', 100, 100);
doc.end();
return res.send('Arbeitsvertrag-2.pdf');
});
Via this code a PDF is stored in firebase storage.
Only no access token is created. Is there any way to do this by default?
you can do this as well to get download url and include the url in the email.
If your file is already exists in firebase storage and you want to get the public url
const bucket = admin.storage().bucket();
const fileInStorage = bucket.file(uploadPath);
const [fileExists] = await fileInStorage.exists();
if (fileExists) {
const [metadata, response] = await fileInStorage.getMetadata();
return metadata.mediaLink;
}
If you want to upload the file and same time get download url
const bucket = admin.storage().bucket();
// create a temp file to upload to storage
const tempLocalFile = path.join(os.tmpdir(), fileName);
const wstream = fs.createWriteStream(tempLocalFile);
wstream.write(buffer);
wstream.end();
// upload file to storage and make it public + creating download token
const [file, meta] = await bucket.upload(tempLocalFile, {
destination: uploadPath,
resumable: false,
public: true,
metadata: {
contentType: 'image/png',
metadata: {
firebaseStorageDownloadTokens: uuidV4(),
},
},
});
//delete temp file
fs.unlinkSync(tempLocalFile);
return meta.mediaLink;

Create and upload pdf file to firebase storage using cloud functions? (can upload locally file, but not from cloud functions)

const { Storage } = require("#google-cloud/storage");
const storage = new Storage({
keyFilename: "./xxx-path-to-key(locally).json",
projectId: "my project ID",
});
exports.get_data = functions.https.onRequest(async (req, res) => {
const pdf = async () => {
const doc = new PDFDocument();
doc.text("Hello, World!");
doc.end();
return await getStream.buffer(doc);
};
const pdfBuffer = await pdf();
const pdfBase64string = pdfBuffer.toString("base64"); //getting string which I want to send to storage like pdf file
const bucketName = "gs://path-to-storage.com/"
let filename = "test_file.pdf" //some locally pdf file (but can't save locally while using cloud functions)
const uploadFile = async () => {
// Uploads a local file to the bucket
await storage.bucket(bucketName).upload(filename, { //this one fine works for locally uploading
metadata: {
cacheControl: "public, max-age=31536000",
contentType: "application/pdf",
},
});
console.log(`${filename} uploaded to ${bucketName}.`);
};
uploadFile();
})
I need to create PDF file using cloud functions and upload it to firebase storage.
I've create pdfBase64string - and just need to save this string-pdf to storage, but can't find information to do it.
I tried many different ways but got stuck, because Google answers come to the end.
Notice that you are using the Cloud Storage and not the Firebase Storage library. You can upload with:
// Create a root reference
var storageRef = firebase.storage().ref();
// Create a reference to 'mountains.pdf'
var ref = storageRef.child('mountains.pdf');
// Base64 formatted string
var message = '5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB';
ref.putString(message, 'base64').then(function(snapshot) {
console.log('Uploaded a base64 string!');
});
from the documentation
Now,
To be able to access Firebase Storage from Cloud Functions you need to add Firebase Editor role to the Cloud Function service account.
Alternatively you can use a Firebase Function to upload to Firebase Storage.
The Cloud Storage library is not the same as the Firebase Storage library.

Firebase Storage upload image file from Node.js

Please help
I receive images from the client and save it on my server in the file system and process this image, after which I need to upload it to firebase storage
I try upload image file to firebase storage from Node.js in my async function
const path = process.cwd() + '/my_image.jpg';
const file = readFileSync(path);
await firebase.storage().ref().child('my_image.jpg').put(file);
...
But i have error
The first argument must be of type string or an instance of Buffer. Received an instance of Uint8Array
Okey, i try binary format
const path = process.cwd() + '/my_image.jpg';
const file = readFileSync(path, { encoding: 'base64' });
await firebase.storage().ref().child('my_image.jpg').putString(file, 'base64');
...
And i get error
Firebase Storage: String does not match format 'base64': Invalid character found"
I've already tried a lot of things, but nothing works!
What am I doing wrong?
You can use this code right here
var admin = require("firebase-admin");
const uuid = require('uuid-v4');
// CHANGE: The path to your service account
var serviceAccount = require("path/to/serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
storageBucket: "<BUCKET_NAME>.appspot.com"
});
var bucket = admin.storage().bucket();
var filename = "path/to/image.png"
async function uploadFile() {
const metadata = {
metadata: {
// This line is very important. It's to create a download token.
firebaseStorageDownloadTokens: uuid()
},
contentType: 'image/png',
cacheControl: 'public, max-age=31536000',
};
// Uploads a local file to the bucket
await bucket.upload(filename, {
// Support for HTTP requests made with `Accept-Encoding: gzip`
gzip: true,
metadata: metadata,
});
console.log(`${filename} uploaded.`);
}
uploadFile().catch(console.error);
To successfully run this code, you will need to:
Add the Firebase Admin SDK to Your Server
Install uuid-v4
Replace "path/to/serviceAccountKey.json" with the path to your own service account. Here is a guide to get yours.
Replace <BUCKET_NAME> with the name of your default bucket. You can find this name in the Storage section of your Firebase Console. The bucket name must not contain gs:// or any other protocol prefixes. For example, if the bucket URL displayed in the Firebase Console is gs://bucket-name.appspot.com, pass the string bucket-name.appspot.com to the Admin SDK.
Replace "path/to/image.png" with the path to your own image.
If needed, adjust the contentType in the metadata accordingly.
Just to let you know, whenever you upload an image using Firebase Console, an access token will be automatically generated. However, if you upload an image using any Admin SDK or gsutil you will need to manually generate this access token yourself. That's why it is very important the uuid part
Firebase Support says that this is being fixed, but I think anyone having this problem should go this way instead of waiting for Firebase to fix this.
For Node js there is a library called '#google-cloud/storage
const {Storage} = require('#google-cloud/storage');
// Creates a client
const storage = new Storage();
const bucket = storage.bucket("my-bucket.appspot.com");
await bucket.upload(
'/my_image_path.jpg',
{
destination: 'my_uploaded_image.jpg',
metadata: {
cacheControl: "public, max-age=315360000",
contentType: "image/jpeg"
}
});
https://www.npmjs.com/package/#google-cloud/storage
You probably need to authenticate your nodejs client with service account key.
See this https://cloud.google.com/docs/authentication/getting-started
Maybe uploading a Uint8Array to Storage was not available a few months ago, but now you can do it. The only thing is you have to add the content type in a metadata object, this way:
const file = new Uint8Array(...)
const metadata = { contentType: 'image/jpeg; charset=utf-8' }
const storageRef = firebase.storage().ref().child('path/to/image.jpg')
await storageRef.put(file, metadata).then((snapshot) => {
console.log('Uploaded an array!', snapshot)
})
Maybe your node version does not support readFileSync function with { encoding: 'base64' } option.
Try original way to convert a buffer to a string:
const file = readFileSync(path).toString('base64');
// now file is a base64 string
await firebase.storage().ref().child('my_image.jpg').putString(file, 'base64');
Using Firebase-admin with service account make you able to upload file in Firebase
const admin = require('firebase-admin')
var serviceAccount = require("/pathTOServiceAccount.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
var bucket = admin.storage().bucket('your/firebaseStorage/folderPath')
bucket.upload('pathOfThe/FileIn/Backend', {
destination: 'path/in/firebase/storage',
metadata: {
cacheControl: "public, max-age=315360000",
contentType: "image/jpeg"
}
})

Uploading images to S3 with ContentType image/jpeg - nodejs and react?

Chrome implement recently a new feature called CORB and its in some very rare case block my website to display images that are hosted in AWS S3.
Investigating a bit the problem i discover that my images are currently sent with a header ContentType of application/x-www-form-urlencoded when it should be image/jpeg.
In the amazon console you can update the metadata content-type of your whole bucket but unfortunately it doesn't apply to the new image that will be uploaded on the future.
So i wonder if i am doing something wrong in my code? (i will show some extract below) or if there is something can make possible to create a JOB/CRON in amazone to recursivly update the metadata content-type of the uploaded image to ensure this issue with the CORB doesn't happen?
One of the solution would be to pass through an image optimizer handler such as https://imagekit.io/ to fix the problem, but in the case of the avatar images that are coming from other source than amazon (it can come from gravatar, or facebook, or google also) i can't use such feature (unless writing a regex script that would check if the specific image came from amazon)
i am using nodejs/express for the backend and react for the front. I don't upload the image on my server but directly from my front to amazon. To do so, i request a signedUrl from amazon and use it frontside to upload.
BACKEND:
const AWS = require("aws-sdk")
const router = express.Router()
// CONFIG AMAZONE
const s3 = new AWS.S3({
accessKeyId: keys.aws.clientID,
secretAccessKey: keys.aws.clientSecret,
signatureVersion: "v4",
region: "eu-west-3"
})
// #route GET api/profile/avatar/upload
// #desc Upload your profile avatar
// #access Private
router.get(
"/",
passport.authenticate("jwt", { session: false }),
(req, res) => {
const key = `avatars/${req.user.id}/${uuid()}-avatar-image.jpeg`
s3.getSignedUrl(
"putObject",
{
Bucket: "mybucket",
ContentType: "image/jpeg",
Key: key.toString()
},
(err, url) => res.send({ key, url })
)
}
)
FRONTEND:
here i am using redux and axios library. This last one have an issue of compatibility header with amazon so you need to disable the default header in order to upload corretly your images
// UPDATE PROFILE PICTURE
export const sendAvatar = file => async dispatch => {
dispatch(loading())
try {
const uploadConfig = await axios.get("/api/global/avatar")
delete axios.defaults.headers.common["Authorization"]
console.log(uploadConfig)
console.log(file)
file &&
(await axios.put(uploadConfig.data.url, file, {
headers: {
ContentType: file.type
}
}))
const token = localStorage.getItem("jwtToken")
axios.defaults.headers.common["Authorization"] = token
const res = await axios.post("/api/global/avatar", {
avatar: uploadConfig.data.key
})
dispatch({
type: USER_AVATAR_FETCH,
payload: res.data.avatar
})
} catch (e) {
dispatch({
type: GET_ERRORS,
payload: e.response.data
})
}
dispatch(clearLoading())
}

Resources