I am using http://mailin.io/doc to parse e-mails to my (future) Meteor application. Therefore I created a webhook using Iron-Router that should process the message and the attachments.
Using the following code, a { } is written in the console.
Router.route('/receive/', {where: 'server'})
.post(function(req, res) {
console.log(this.request.body);
});
I found some people reporting similar issues but the provided solutions don't work for me.
https://github.com/iron-meteor/iron-router/issues/909
https://github.com/iron-meteor/iron-router/issues/1003
Any ideas?
In the links the correct workaround was described, seems as is I had another error as well. The following code did the trick for me:
if (Meteor.isServer) {
var Busboy = Meteor.npmRequire("busboy"),
fs = Npm.require("fs"),
os = Npm.require("os"),
path = Npm.require("path");
Router.onBeforeAction(function (req, res, next) {
var filenames = []; // Store filenames and then pass them to request.
_.extend(req, {postData: {}});
if (req.method === "POST") {
var busboy = new Busboy({ headers: req.headers });
busboy.on("file", function (fieldname, file, filename, encoding, mimetype) {
var saveTo = path.join(os.tmpDir(), filename);
file.pipe(fs.createWriteStream(saveTo));
filenames.push(saveTo);
});
busboy.on("field", function(fieldname, value) {
req.postData[fieldname] = value;
});
busboy.on("finish", function () {
// Pass filenames to request
req.filenames = filenames;
next();
});
// Pass request to busboy
req.pipe(busboy);
} else {
this.next();
}
});
}
and the required route looks like this:
Router.route('/receive/', {where: 'server'})
.post(function() {
// Use this.request.postData to access the message content
postData = this.request.postData;
});
Related
I have an existing async function:
async doJSONGetRequest(getUrl, accessToken) {
return new Promise(function(resolve, reject) {
const reqHeaders = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
};
console.info('url = ' + getUrl);
request.get({
url: getUrl,
headers: reqHeaders,
}, function(err, response) {
if (err) return reject(err);
try {
// console.debug(`response = ${response.body}`);
const parsed = JSON.parse(response.body);
return resolve(parsed);
} catch (err) {
return reject(err);
}
});
});
}
}
I'm trying to test it with Jasmine(v4).
Of course, I don't want this thing to actually make an HTTP request, so I tried rigging up a spy on the 'request' package's 'get' function in the 'beforeAll' section:
describe('RAPIDAPIService', function() {
beforeAll(async function() {
spyOn(request, 'get')
.and
.callFake(async (parameters) => {
if (parameters.url === 'http://localhost/api/getSomething') {
const rsp = {};
rsp.body = 'good stuff';
return rsp;
} else if (parameters.url === 'http://localhost/api/whoops') {
return new Error('401 not found');
} else {
return null;
}
});
});
it('doJSONGetRequest should run successfully', async () => {
expect(api.doJSONGetRequest).toBeDefined();
const res = await api.doJSONGetRequest('http://localhost/api/getSomething', '12345678');
expect(data).toEqual('good stuff');
});
it('doJSONGetRequest should resolve errors properly', async () => {
expect(api.doJSONGetRequest).toBeDefined();
const res = await api.doJSONGetRequest('http://localhost/api/whoops', '12345678');
const expectedError = new Error('401 not found');
expect(res).toEqual(expectedError);
});
Console log statements seem to indicate that I'm actually getting past / returning something from my "await" calls in the "it" tests. But the spies are actually working / detecting that the url's have been called.
(Note that I'm not including here other tests in the same file that do not make asynchronous calls and ARE working... just so you know that there's no problem accessing the actual "api" library and its functions.)
These two tests keep failing with "Error: Timeout - Async function did not complete within 5000ms". And like I said, it seems like they're not returning back to the tests from their calls to the doJSONGetRequest function.
Any thoughts?
Thanks!
I am thinking the issue is the mocking. request.get seems to take two parameters and I am thinking you need to call the 2nd parameter (callback function) once you are done so the resolve can be called.
Try this:
spyOn(request, 'get')
.and
// add callbackFunction as 2nd argument
.callFake((parameters, callbackFunction) => {
if (parameters.url === 'http://localhost/api/getSomething') {
const rsp = {};
rsp.body = 'good stuff';
callbackFunction(null, rsp);
} else if (parameters.url === 'http://localhost/api/whoops') {
callbackFunction({ error: '401 not found' }, {});
} else {
callbackFunction(null, null);
}
});
I've been having trouble viewing the image files I've uploaded to firebase and just noticed the issue is with the file type in firebase.
Two files in my firebase storage console. One uploaded from my IOS simulator (octet-stream) and the other uploaded directly into the console from the browser which uploads properly and is viewable.
Here are my select and upload functions:
_selectPhoto = async () => {
const status = await getPermission(Permissions.CAMERA_ROLL);
if (status) {
let imageName = "pic"
const result = await ImagePicker.launchImageLibraryAsync(options);
if (!result.cancelled) {
Animated.timing(this.animatedWidth, {
toValue: 600,
duration: 15000
}).start()
this.uploadImage(result.uri, imageName)
.then(() => {
this.props.navigation.navigate('Profile')
})
.catch((error) => {
Alert.alert('Must Sign In');
this.props.navigation.navigate('Login')
console.log(error);
})
}
}
};
uploadImage = async (uri, imageName) => {
const user = firebase.auth().currentUser;
const response = await fetch(uri);
const blob = await response.blob();
let storageRef = firebase.storage().ref().child(''images/'+user.displayName+'/'+imageName+'.jpg'');
const snapshot = await storageRef.put(blob);
blob.close();
snapshot.ref.getDownloadURL().then(function(downloadURL) {
console.log("File available at", downloadURL);
user.updateProfile({
photoURL: downloadURL.toString(),
}).then(function() {
console.log('saved photo')
}).catch(function(error) {
console.log('failed photo')
});
});
}
When I get the link in my console, it also has the media&token:
... .appspot.com/o/profile-pic.jpg?alt=media&token=56eb9c36-b5cd-4dbb-bec1-3ea5c3a74bdd
If I CMD+Click in VS Code I receive an error:
{
error: {
code: 400,
message: "Invalid HTTP method/URL pair."
}
}
So naturally, when I put that link in the browser it downloads a file with that name but says:
The file “pic.jpg” could not be opened.
It may be damaged or use a
file format that Preview doesn’t recognize.
Maybe it could be something with mediaTypes, but I'm not exactly sure how to use it.
mediaTypes : String -- Choose what type of media to pick. Usage:
ImagePicker.MediaTypeOptions., where is one of: Images,
Videos, All.
Thanks!
I've been fighting with this same issue for the past few days. I was finally able get images to upload and render as expected by following the Firebase Upload example in the Expo repo. I don't fully understand why it works, but it seems like Firebase doesn't like the blob that's generated by
const blob = await response.blob();
Try replacing the above with:
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'));
};
xhr.responseType = 'blob';
xhr.open('GET', uri, true);
xhr.send(null);
});
I'm trying to use set up a Firebase Cloud Function to access IBM Watson Text-to-Speech. The problem is writing the returned audiofile to my Firestore database.
This test to return the list of voices worked, logging the response to the Functions log:
exports.test = functions.firestore.document('IBM_Watson_Token/Test_Value').onUpdate((change, context) => {
var textToSpeech = new TextToSpeechV1({
username: 'groucho',
password: 'swordfish'
});
return textToSpeech.listVoices(null, function(error, voices) {
if (error) {
console.log(error);
} else {
console.log(JSON.stringify(voices, null, 2));
}
});
});
Here is the documentation example Node code for returning an audiofile and writing it to the server:
var TextToSpeechV1 = require('watson-developer-cloud/text-to-speech/v1');
var fs = require('fs');
var textToSpeech = new TextToSpeechV1({
username: '{username}',
password: '{password}'
});
var synthesizeParams = {
text: 'Hello world',
accept: 'audio/wav',
voice: 'en-US_AllisonVoice'
};
// Pipe the synthesized text to a file.
textToSpeech.synthesize(synthesizeParams).on('error', function(error) {
console.log(error);
}).pipe(fs.createWriteStream('hello_world.wav'));
Firebase doesn't allow writing files to the server using fs, you have to write to a Firestore database. I changed the last line of the example code to write to Firestore, using a promise:
exports.test = functions.firestore.document('IBM_Watson_Token/Test_Value').onUpdate((change, context) => {
var textToSpeech = new TextToSpeechV1({
username: 'groucho',
password: 'swordfish'
});
var synthesizeParams = {
text: 'Hello world',
accept: 'audio/wav',
voice: 'en-US_AllisonVoice'
};
return textToSpeech.synthesize(synthesizeParams).on('error', function(error) {
console.log(error);
}).then(function (audiofile) {
admin.firestore().collection('IBM_Watson_Token').doc('hello_world').set({
'audiofile': audiofile
})
})
.catch(function (error) {
console.log(error);
});
});
The error message was
TypeError: textToSpeech.synthesize(...).on(...).then is not a function
How do I save the audiofile that comes back from Watson to Firestore?
That would be because the synthesize method is not returning a promise. You will need to use a callback construct that looks like
textToSpeech.synthesize(params, function (err, body, response) {
if (err) {
...
} else {
// body is the audio
...
}
});
Is there a way to use the Fetch API to upload a binary file (for instance to S3 using a signed URL) ?
That would be a simple PUT for some 'application/octet-stream'.
The XHR library is working, but I believe Fetch is better, especially in a React-Native environment.
Does React-Native Fetch support Blob nowadays?
Ideally I would like to do something like this, but Blob is undefined:
fetch('https://s3.amazonaws.com/signedUrl/', {
method: 'PUT',
headers: {
'Content-Type': 'application/octet-stream',
},
body: Blob(filePath)
})
This works on Android/iOS and simulators, if you have the file system path to your binary, such as an image using the built-in XMLHttpRequest to send requests:
const xhr = new XMLHttpRequest();
xhr.open('post', serviceUrl);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.response);
} else {
reject(xhr.response);
}
}
};
xhr.setRequestHeader('Content-Type', 'image/jpeg');
xhr.send({ uri: 'pathToFile', type: 'image/jpeg', name: 'file' });
A sample pathToFile on macOS is file:///Users/username/Library/Developer/CoreSimulator/Devices/061D4A47-6363-4996-A157-03E6AD2DD9E4/data/Containers/Data/Application/3900F3EF-3259-43CF-9400-87B638EF9A1B/Library/Caches/Camera/F86E7345-080A-4D51-A80E-0CAD3370A353.jpg
I would recommend to use: https://github.com/github/fetch as polyfill for Fetch since is not widely supported.
Fetch documentation sample:
var input = document.querySelector('input[type="file"]')
var data = new FormData()
data.append('file', input.files[0])
data.append('user', 'hubot')
fetch('/avatars', {
method: 'POST',
body: data
})
I am using it as follows:
var input = document.querySelector('input[type="file"]')
function upload(e) {
const file = input.files[0];
const reader = new FileReader();
reader.onload = (e) => {
fetch('/avatars', {
method: 'POST',
body: e.currentTarget.result
})
};
reader.readAsArrayBuffer(file);
}
input.addEventListener('change', upload, false)
I am trying to learn Meteor, starting by writing a simple application where the server calls an HTTP API based on user input, processes the information, then returns it to the client to display it.
I am not having much success. I can't seem to return the result from server to client:
if (Meteor.isServer) {
Meteor.methods({
checkTransit: function(method, url, options) {
this.unblock();
return Meteor.http.call(method, url, function(error, result) {
if (error) {
console.log('SERVER ERRR');
console.log(error);
} else {
console.log('SERVER RESULT');
console.log(result);
}
});
}
})
}
if (Meteor.isClient) {
Template.body.events({
"submit .new-task": function(event) {
// Prevent default browser form submit
event.preventDefault();
var text = event.target.text.value;
var method = 'GET';
var url = 'http://sometransitapi.com';
var options = {
headers: {
'accept': 'application/XML',
'content-type': 'application/XML'
}
}
Meteor.call('checkTransit', method, url, options, function (error, result) {
if (error) {
console.log('CLIENT ERRR');
console.log(error);
} else {
console.log('CLIENT RESULT');
var parser;
parser = new DOMParser();
var xmlDoc
xmlDoc = parser.parseFromString(result, "text/xml");
console.log(xmlDoc);
}
})
}
})
}
I can see the results being returned to the result variable at isServer, but I can not pass the result to the xmlDoc variable in isClient. What am I doing wrong? Is this the correct way to structure things for what I want to do in Meteor?
Meteor.http.call is being called asynchronously in your server code (you are passing a callback). The functions in your Meteor.methods object expect to return a value, so you should be calling Meteor.http.call synchronously. Changing your server code to below should do the trick.
if (Meteor.isServer) {
Meteor.methods({
checkTransit: function(method, url, options) {
this.unblock();
// This will throw an exception and return it as the error object
// in your Meteor.call if an error occurs, otherwise it will
// return an empty error object and the result object will be
// the return value from Meteor.http.call
return Meteor.http.call(method, url);
}
})
}
If you want to keep the logic on the server side then #Curtis' answer should help you.
Looking at what you have, I don't see much of a point of getting the server to do the work. The Http api is available everywhere in Meteor. You are just fetching data, which can be solely done on the front end. This would also technically make it faster. Taking your code and moving it around a bit you get the following.
if (Meteor.isClient) {
Template.body.events({
"submit .new-task": function(event) {
// Prevent default browser form submit
event.preventDefault();
var text = event.target.text.value;
var method = 'GET';
var url = 'http://sometransitapi.com';
var options = {
headers: {
'accept': 'application/XML',
'content-type': 'application/XML'
}
}
Http.call(method, url, options, function (error, result) {
if (error) {
console.log('CLIENT ERRR');
console.log(error);
} else {
console.log('CLIENT RESULT');
var parser;
parser = new DOMParser();
var xmlDoc
xmlDoc = parser.parseFromString(result, "text/xml");
console.log(xmlDoc);
}
});
}
});
}