I am currently working on generating an image with node-canvas via Firebase Cloud Functions, then sending it as an email attachment with nodemailer.
The requirement of the image is that I need to input some string onto a template with a specific font.
So on Firebase Cloud Functions, here is what I did:
exports.test = functions.https.onRequest((request, response) => {
cors(request, response, () => {
let tempFilePath = path.join(os.tmpdir(), 'tnr.ttf');
functions.logger.log('tempFilePath is', tempFilePath);
bucket.file('tnr.ttf').download({destination: tempFilePath});
functions.logger.log('Font downloaded locally to', tempFilePath);
registerFont(path.resolve(tempFilePath), {family: 'tnr'});
functions.logger.log('Font registered successfully');
}
}
I have already verified repeatedly that the file 'tnr.ttf' exists on my project cloud and can successfully be loaded to that temp path.
At the registerFont function however, Firebase would always throw this error, no matter which font file I use:
Error: Could not parse font file
at registerFont (/workspace/node_modules/canvas/index.js:48:17)
Could there possibly be a problem with how Firebase handles this package or function?
You are trying to use the font before it has finished downloading. By the time you realize the problem, open up a terminal and confirm that it's actually present, the download has finished.
What you need to do is wait for either the Promise returned by download(...) to resolve or attach a callback to the download() method. Then after it's finished downloading, you can register the font.
exports.test = functions.https.onRequest((request, response) => {
cors(request, response, (corsErr) => {
if (corsErr) {
// this is rare, but still possible.
functions.logger.error('Request rejected by CORS: ', corsErr);
res.status(412).send('Rejected by CORS'); // 412 FAILED PRECONDITION
return;
}
let tempFilePath = path.join(os.tmpdir(), 'tnr.ttf');
functions.logger.log('tempFilePath is ', tempFilePath);
bucket.file('tnr.ttf').download(
{destination: tempFilePath},
(err) => {
if (err) {
functions.logger.error('Failed to download font to local system: ', err);
res.status(422).send('Failed to register font'); // 422 UNPROCESSABLE ENTITY
return;
}
functions.logger.log('Font downloaded locally to ', tempFilePath);
registerFont(path.resolve(tempFilePath), {family: 'tnr'});
functions.logger.log('Font registered successfully');
res.send('Registered font successfully');
}
);
}
}
Notes:
Don't forget to properly handle errors and terminate requests properly.
The same function invocation may be invoked again, consider setting a "I already downloaded that" flag somewhere. In a "worst-case scenario", you may end up with 100s of the same font file.
Make sure to execute the registerFont method where you will be using it. You can't have separate /registerFonts and /drawWithFont endpoints, as you aren't guaranteed to be talking to the same function instance you were a moment ago.
Related
I am trying to upload a base64 image to Firebase Storage using React Native Expo. The image I am uploading is taken through the takePictureAsync method in expo-camera library with the following CameraPictureOptions
quality:1
base64:true
exif:false
Next, I store this image in a state variable, named photo, and display it to the user in the app using the Image tag with the uri:
data:image/jpg;base64" + photo.base64
Now when uploading it I first tried the uploadString method as per the firebase storage documentation as below
uploadString(storageRef, photo.base64, 'base64').then((snapshot) => {
console.log('Uploaded a raw string!');
});
But it gave the error message
Unhandled promise rejection: FirebaseError: Firebase Storage: String does not match format 'base64': Invalid character found (storage/invalid-format)
I tried this with other parameters instead of base64 ("data_url", "base64url", and not putting anything in the parameter), but I got essentially the same error for "data_url" and "base64url", and when I don't put anything in the parameter the app crashes. After this I looked online to find a fix, and one issue that that some people brought up to explain this error message was that to decode and encode the base64 string firebase storage used the atob and btoa method respectively, but this did not work in javascript/was deprecated. To that end, the fix that was suggested was adding this to on top of App.js
import {decode, encode} from 'base-64';
if (!global.btoa) {
global.btoa = encode;
}
if (!global.atob)
{
global.atob = decode;
}
However, when I did this the app crashed after the button was pressed that triggered the uploadString method.
After this, I tried using the uploadBytes method as per the documentation. I first tried passing the uri string ("data:image/jpg;base64, base64 data) as input, as below. While this did make the image visible on my firebase storage, the image could not be opened. I think this was because this method expected a File or a Blob as input and the input I gave was a base64 string
uploadBytes(storageRef, uri).then((snapshot) => {
console.log('Uploaded a blob or file!');
});
Next, I looked online for ways to convert the base64 string to a Blob. The first approach I tried was calling the fetch api on uri and the blob function to get the blob and then put that in the uploadBytes function.
const response = await fetch(uri)
const blob = await response.blob()
uploadBytes(storageRef, blob).then((snapshot) => {
console.log('Uploaded a blob or file!');
});
However, I get the following error message, which I was unable to solve.
Error: ENOENT: no such file or directory, open pathToReactNativeApp/http:/LocalIPAddress:19000/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&hot=false&strict=false&minify=false'
Next, I tried the following to get the blob as suggested here, but this lead the app to crash.
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function () {
resolve(xhr.response);
};
xhr.onerror = function (e) {
console.log(e);
reject(new TypeError("Network request failed"));
};
// on complete
xhr.responseType = "blob";
xhr.open("GET", uri, true);
xhr.send(null);
});
Can someone please guide me on how I can upload this base64 image to firebase storage? Any help would be greatly appreciated!
ImagePicker.launchImageLibraryAsync from expo, will return uri, (no need to include base64 :true)
using that uri generate blob
const response = await fetch(result.uri);
const blob = await response.blob();
using this blob, upload to firebase storage using uploadBytes function
uploadBytes(ref, blob).....
i m using firebase 9 web moduler, will run in all platforms
uploading blob is far batter than using base64 and speedy aswell.
not only images, you can upload videos aswell with this,
I started to build an API using NextJs framework. I want it to be hosted on Firebase (Hosting and Functions). Everything is working as long as I send only GET requests. When I send a POST request I receive a "502 Bad Gateway" error.
It's very simple to reproduce. You just have to download and deploy the example provided by the team developing NextJs.
create a new project on Firebase console
install the "with Firebase hosting" example
change the project name in the .firebaserc (line 3) file
create a folder "api" under the folder "pages"
create a file "hello.js" under the folder "api" and add the following snippet
export default async (req, res) => {
const {
body,
method
} = req;
console.log("method :>> ", method);
console.log("body :>> ", body);
switch (method) {
case "POST":
res.status(200).end(`Method ${method} supported!`);
break;
default:
res.setHeader("Allow", ["POST"]);
res.status(405).end(`Method ${method} Not Allowed`);
}
};
deploy the app
send a GET request to "https://[project-name].web.app/api/hello" and see it works
send a POST request to "https://[project-name].web.app/api/hello" and see it does not work
Do you have a the same error as me?
I spent 2 days to read articles, watch videos and try different configurations. You can even update the firebaseFunctions to add a console.log and see the POST request is caught by the Firebase Cloud Function but the NextJs server does not pass it to our API like it does for a GET request. It's out of my skills range...
Below the output you should have. The POST request should be answered with 200 - Method POST is supported!.
This was a real pain to track down, but after poking around myself for a while, I found that the same issue crops up for PUT and PATCH requests. Which suggested that it had something to do with the body of the request. Annoyingly, after finding that out, I stumbled across the thread of Issue #7960, where they found the same problem.
Simply put, the body of the request processed once by https.onRequest() and then nextjsHandle() tries to parse it again. Because the body was handled already, the raw-body module (within nextjsHandle()) waits indefinitely for 'data' events that will never come.
Currently, there isn't a way to turn off the body parsing done by https.onRequest(), so it must be disabled on the next.js end. Unfortunately, there isn't a global off switch for body parsing that can be added in next.config.js and it must be done for each and every API route (the files in pages/api) (which may change if the proposed fix in PR #16169 is added).
To disable body parsing for a given route, you add the following to the route's file
export const config = {
api: {
// disables call to body parsing module
bodyParser: false,
}
};
However, as mentioned in Issue #7960 by #rscotten, you might also want to use next dev while developing your app, so you need to enable it while using next dev but disable it while deployed. This can be done using
export const config = {
api: {
// disables call to body parsing module while deployed
bodyParser: process.env.NODE_ENV !== 'production',
}
};
Applying these changes to hello.js gives:
export default async (req, res) => {
const {
body,
method
} = req;
console.log("method :>> ", method);
console.log("body :>> ", body);
switch (method) {
case "POST":
res.status(200).end(`Method ${method} supported!`);
break;
default:
res.setHeader("Allow", ["POST"]);
res.status(405).end(`Method ${method} Not Allowed`);
}
};
export const config = {
api: {
// disable nextjs's body parser while deployed
// (as body parsing is handled by `https.onRequest()`),
// but enable it for local development using `next dev`
bodyParser: process.env.NODE_ENV !== 'production',
}
};
Basically what I'm looking for is the ability to send emails with attachments (multiple attachments) to my email. My current approach is to get files and convert them to the dataUri then save email skeleton to firestore. Then I have a cloud function that fires onSave and sends the email to my email using Gmail transporter and after I delete the record from firestore so there is no persistent saving to a database. This approach works but its very limited because I can't send files which sum exceeding 1 MB and that's sucks.
Can anyone point me the best way to get similar behaviour but be able to send larger files? I would like to send up to 5 files with 3mb per file if its possible.
P.S.
I've enabled blaze plan
Im not sure whats wrong here.. Im unable to send an email.
Yeah, right I forgot about HTTPS functions. But I've one problem Im unable to send an email and Im not sure what's wrong.
exports.sendEmaill = functions.https.onRequest((req, res) => {
if (req.method !== 'POST') {
return res.status(403).send('Forbidden!');
}
console.log(req.body);
return cors(req, res, () => {
const mailOptions = parseBody(req.body);
console.log(mailOptions);
// return res.status(200).send(mailOptions)
return mailTransport.sendMail(mailOptions)
.then((info) => res.status(200).send(info))
.catch((error) => res.status(403).send(error));
});
});
With this I can fire the function
// return res.status(200).send(mailOptions)
But as soon as I want to use mailTransport I got
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I'm setting up this thread in order to clarify, whether firebase storage putString method does or does not work in React-native.
From what I've searched there is currently no way to upload File or Blob types to Firebase Storage using put method.
React Native does not support the File and Blob types, so Firebase Storage uploads will not work in this environment. File downloads do work however.
SOURCE: The Firebase Blog
Thus this call
firebase.storage().ref()
.child(userID)
.put(new File(['this is a small amount of data'], 'sample-text.txt', { type: "text/plain" }), { type: "text/plain" })
.then(p => {console.log(p)})
.catch(p => {console.log(p)})
does not work and ends up with response
code : "storage/unknown" message : "Firebase Storage: An unknown error
occurred, please check the error payload for server response." name :
"FirebaseError" serverResponse : "Multipart body does not contain 2 or
3 parts."
Nevertheless there is another option to upload data to Firebase Storage, using Firebase storage putString method. Which works with plain string. But even if I use this method to upload. I'm getting the same server reponse as before.
firebase.storage()
.ref()
.child(userID)
.putString('string')
.then(p => {console.log(p)})
.catch(p => {console.log(p)});
Bu from what I've learned from this answer. The putString way should work.
What am I doing wrong? The code works fine for me in React. Whenever I paste to React-native. It stops working.
I've just tried react-native-fetch-blob as Ven commented before, and I was able to make it work, try using this snippet from the index file in the example:
1) Before class declaration:
import RNFetchBlob from 'react-native-fetch-blob';
const Blob = RNFetchBlob.polyfill.Blob;
window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest;
window.Blob = Blob;
2) Inside the storing method:
let filePath = 'YOUR/FILE/PATH';
let fileName = 'file_name.jpg';
let rnfbURI = RNFetchBlob.wrap(filePath);
// create Blob from file path
Blob
.build(rnfbURI, { type : 'image/png;'})
.then((blob) => {
// upload image using Firebase SDK
firebase.storage()
.ref('images')
.child(fileName)
.put(blob, { contentType : 'image/jpg' })
.then((snapshot) => {
console.log('Uploaded', snapshot.totalBytes, 'bytes.');
console.log(snapshot.metadata);
var url = snapshot.metadata.downloadURLs[0];
console.log('File available at', url);
}).catch(function(error) {
console.error('Upload failed:', error);
});
I am sending a request from my Meteor server to download a file via an API. I then want to upload that file to S3. I keep getting the following "NoSuchKey: The specified key does not exist." I initially thought it was maybe a problem with my AcessKey/SecretKey form AWS but after googling this for a while the only examples I could find of other people getting this error is when trying to download a file from S3.
Setting up cfs:s3
var imageStore = new FS.Store.S3("images", {
accessKeyId: "MyAcessKeyId", //required if environment variables are not set
secretAccessKey: "MySecretAcessKey", //required if environment variables are not set
bucket: "BucketName", //required
});
Images = new FS.Collection("images", {
stores: [imageStore]
});
Start file transfer from API and upload to S3
client.get_result(id, Meteor.bindEnvironment(function(err, result){ //result is the download stream and id specifies which file to download.
if (err !== null){
return;
}
var file = new FS.File(result);
Images.insert(file, function (err, fileObj) {
if (err){
console.log(err);
}
});
}));
Note: I was getting the following error so I added Meteor.bindEnvironment.
"Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment."
Node.js example from API Documentation
client.get_result(id, function(err, result){
if (err != null) {
return;
}
file.writeFile(path.join('public', path.join('results', filename)), result, 'binary');
});
What ended up fixing the problem for me was moving part of the setup to the lib folder. Although I tried several different ways I was unable to get it to execute entirely on the server. It looks like the documentation was updated recently which states everything a bit more clearly. If you follow this setup it should eliminate the error. See the section titled Client, Server, and S3 credentials
https://github.com/CollectionFS/Meteor-CollectionFS/tree/master/packages/s3
Note: Make sure not to place you secret key is not in you lib folder as this is accessible from the client.