Return XML in Firebase Cloud Function - javascript

I am trying to set up a cloud function that returns an xml. I am able to create and log the xml, but it crashes with the following error when I try to return it.
TypeError: Converting circular structure to JSON
at Object.stringify (native)
at stringify (/var/tmp/worker/node_modules/express/lib/response.js:1119:12)
at ServerResponse.json (/var/tmp/worker/node_modules/express/lib/response.js:260:14)
at ServerResponse.send (/var/tmp/worker/node_modules/express/lib/response.js:158:21)
at cors (/user_code/index.js:663:21)
at cors (/user_code/node_modules/cors/lib/index.js:188:7)
at /user_code/node_modules/cors/lib/index.js:224:17
at originCallback (/user_code/node_modules/cors/lib/index.js:214:15)
at /user_code/node_modules/cors/lib/index.js:219:13
at optionsCallback (/user_code/node_modules/cors/lib/index.js:199:9)
My Function
exports.sendXMLResponeSample = functions.https.onRequest((request, response) => {
cors(request, response, () => {
// import xmlbuilder
const builder = require('xmlbuilder');
// create my object to convert to xml
var myFeedObject = {
"somekey": "some value",
"age": 59,
"eye color": "brown"
}
// convert myFeedObject to xml
const feed = builder.create(myFeedObject, { encoding: 'utf-8' })
console.log("feed.end({ pretty: true }) = (below)");
console.log(feed.end({ pretty: true }));
// return xml
return response.send(200, feed) // <<< error occurs here
})
})
I believe the error suggests the the firebase cloud function is expecting I return a JSON object in the response rather than an xml object, but I am unsure how to tell it to expect an xml object in the response.
Does anyone understand how to return an xml object in a firebase cloud function?
EDIT: The object is converted to an xml object without any issue. The error occurs when the xml object is attempted to be returned.

You can use the .contentType(type: string) on the response object that the cloud function returns to the caller.
Like so:
res.status(200)
.contentType('text/xml; charset=utf8')
.send(xmlString);

You may install the object-to-xml library, and then set the response data type in the response header to text/XML, something like res.header('Content-type','text/xml').

This is what I'm doing.
const xmlString =
'<?xml version="1.0" encoding="UTF-8"?><Response><Message><Body>This is the
response</Body></Message></Response>';
res
.set("Content-Type", "text/xml; charset=utf8")
.status(200)
.send(xmlString);
Works for me. I'm sure there is a better way to convert your XML to a string.

Related

Can't use response.json() on received user object from MS Graph

I'm trying to get a user from the MS Graph API.
export const getUser = async (id) => {
//Gives me the token
const token = await getToken(["User.Read", "User.ReadWrite"])
//Appends to headers
const headers = getHeaders(token)
const options = {
method: "GET",
headers: headers
};
return fetch(`https://graph.microsoft.com/v1.0/users/${id}`, options)
}
getUser("MYID").then(response => response.json()).then(response => {
debugger
}).catch((error) => {
debugger
})
Normally I can use .json() to resolve promises from MS Graph, but this call fails with the following error:
Unexpected token < in JSON at position 0
If I remove .json() I can read the response, which gives me the following:
response: Response
body: ReadableStream
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 200
statusText: "OK"
type: "basic"
Given the fact that the body object should be of type ReadableStream, my intuition tells me that I in fact should be able to resolve the promise with .json(), i.e. response.body.json(). However, when I try to do so, I get the following error:
TypeError: response.body.json is not a function at http://localhost:3000/static/js/main.chunk.js:4679:40
I'm pretty unsure what's going on, and naturally I would love if you guys could give me some insights on what's going on. All my other graph "GET" calls encounter no problem when I want to resolve the readableStream.
I believe you are getting response body as html or xml which is why .json() which is used to parse the data to json is throwing exception Unexpected token < in JSON at position 0

How to pipe a HTTPS get response into an Object

I transformed my code so that instead of requiring an extra node_modules, I could just use some HTTPS GET requests, the problem is that when I try to pipe /releases/ which is basically a raw JSON file, my code requires it back and issues occur like SyntaxError: Unexpected end of JSON input, because for some reason, when I console.log() the so called JSON array, the end isn't completed with ] or }. So I try to pipe the response into an array, but now I get an error: dest.on isn't a function,
Code:
https.get({hostname: `api.github.com`, path: `/repos/${username}/${reponame}/releases`, headers: {'User-Agent': 'a user agent'}}, async (response) => {
var file = new Array()
response.pipe(file)
response.on('end', async function() { //issue occurs at response.pipe ???
var releases = JSON.parse(fs.readFileSync('./releases.json', 'utf8'))
console.log(releases)
The JSON file that I access from Github looks like: https://api.github.com/repos/davidmerfield/randomColor/releases (random repository)
But, my file (releases.json) looks like this
Edit: I did extensive testing. I used the same JSON file my pkg outputted, read it with fs and so on, and everything seems fine. So the issue is most likely with https / response
I found out how to pipe the HTTP request into an object, instead of piping it into a file. Thanks to this post. I did that and turned the string into a JSON array.
https.get({hostname: `api.github.com`, path: `/repos/${username}/${reponame}/releases`, headers: {'User-Agent': 'agent'}}, async response => {
var str = ''
response.on('data', (data) => {
str += data
})
response.on('end', async function() {
var releases = JSON.parse(str)
//and so on...
You can require JSON files. So, if you need this file, you can do something like:
const releases = require('./releases.json');
You do not need to read it with fs, unless you really want to.
TypeError: dest.on is not a function
This error will be thrown if you try to pipe to non-Writable Stream object. Check here
Which in this case Array is not a Writable Stream. You can create a writable stream using fs.createWriteStream() and pipe the response to it.
https.get(
{ hostname: `api.github.com`, path: `/repos/${username}/${reponame}/releases`, headers: { "User-Agent": "a user agent" } },
async response => {
const writableStreamFile = fs.createWriteStream("./releases.json");
response.pipe(writableStreamFile);
response.on("end", async function() {
var releases = JSON.parse(fs.readFileSync("./releases.json", "utf8"));
console.log(releases);
});
}
);

Node.Js Slack App Https request 'Uncaught Exception: Converting circular structure to JSON'

I am trying to create a slack app, using stdlib that can take a simple text argument and then use that information to make an https request to a url and get a json response back. However, when I try to make this request and output the string to slack as a text response, I get the following error:
Critical Error: TypeError: Converting circular structure to JSON
at JSON.stringify (<anonymous>)
at Domain.criticalExecution.run ([eval]:86:45)
at Domain.run (domain.js:242:14)
at callback ([eval]:66:23)
at func.index.apply.params.concat ([eval]:199:51)
at module.exports (/var/task/functions/commands/price.js:23:3)
at Domain.functionExecution.run ([eval]:197:22)
at Domain.run (domain.js:242:14)
at process.nextTick ([eval]:196:27)
at _combinedTickCallback (internal/process/next_tick.js:131:7)
Now, I can't see anywhere in the response json that makes it cyclical, since it is just a text response, I don't add to it, so it confuses me.
Below is the source code:
module.exports = (user, channel, text = '', command = {}, botToken = null, callback) => {
callback(null, {
response_type: 'in_channel',
text: getString()
});
};
function getString() {
return getNasaData('DEMO_KEY');
}
function getNasaData(key = 'DEMO_KEY', callback) {
return https.get({
host: 'api.nasa.gov',
path: '/planetary/apod?api_key=${key}'
}, (response) => {
let body = '';
response.on('data', (data) => {
body += data;
});
response.on('end', () => {
let parsed = JSON.parse(body);
callback({
copyright: parsed.copyright,
date: parsed.date
});
});
});
};
I've looked around for solutions and its unclear what would cause this exception other than a cyclical reference. Perhaps it is to do with the callbacks I'm using in the two methods?
Thank you for any help.

Digital Envelope Routines: Bad Decrypt

I am encrypting an object in the frontend and sending an HTTP POST request with the encrypted data. In the backend I am trying to decrypt this object but it fails.
The tests are passing but when integrated to the actual project this decrypt method fails with error:
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt at Error (native) at Decipher.final (crypto.js:158:26) at Object.exports.decrypt.error [as decrypt]
Here is the relevant code:
export const decrypt = text => {
if (!text)
throw Error('Decrypt: Text may not be blank');
const decipher = crypto.createDecipher(encryptAlgorithm,
encryptionKey)
let decrypted = decipher.update(text, textEncodingHex,
textEncodingUtf8)
decrypted += decipher.final(textEncodingUtf8)
return decrypted
}
And this is how I am using it
authSignInWeb(): any {
return async (request: any, reply: any) => {
try {
let decrytedRequestPayload = request.payload;
if (process.env.REACT_APP_ENCRYPT) {
decrytedRequestPayload = JSON.parse(cryptoHelper.decrypt(request.payload))
}
...
} catch (error) {
reply(error);
...
}
};
};
After going through the Documentation and other online resources I managed to solve this problem. What made this fail is that HapiJs takes the incoming payload, parses it, and pass it down to the authSignInWeb() as a Javascript object with the actual payload as the key in that object it makes on my behalf.
To solve this I had to, in the frontend, encrypt the data, manually create an object and assign the encrypted information. And then in the backend access the payload's key of the object.
In code:
The frontend is like so:
let encryptedData = {};
if (process.env.REACT_APP_ENCRYPT) {
encryptedData.data = Crypt.encrypt(JSON.stringify(requestBody))
}
and then in the backend (inside authSignInWeb()) do:
let userAuthData = request.payload;
if (process.env.REACT_APP_ENCRYPT) {
userAuthData = JSON.parse(cryptoHelper.decrypt(userAuthData.data))
}

Custom Boom error messages

On my Hapi.js server, I'd like to send a specific message if an account does not have permission rights to access an api endpoint. The Boom message I have right now looks like this:
return reply(Boom.unauthorized("unauthorized access to this API."));
This returns a message that looks like this:
{
"statusCode": 401,
"error": "Unauthorized",
"message": "unauthorized access to this API."
}
I would like to make it more customized to look like this:
{
"success": false,
"message": "unauthorized access to this API.",
"csrf-decorator": "",
"redirect": ""
}
Do we have access to customize the Boom error messages?
Thanks!
Boom comes with built in response error transformation. So in order to achieve my results I had reformat my error reply in the following way:
const error = Boom.forbidden("Sorry, you are restricted in accesssing this API. No soup for you!.");
error.output.statusCode = 403; // Assign a custom error code
error.output.payload["csrf-decorator"] = request.headers["csrf-decorator"];
error.reformat();
return reply(error);
According to the Hapijs documentation you can reformat error messages to customize them:
Error transformation
Errors can be customized by changing their output content. The boom error object includes the following properties:
isBoom - if true, indicates this is a Boom object instance.
message - the error message.
output - the formatted response. Can be directly manipulated after object construction to return a custom error response. Allowed root keys:
statusCode - the HTTP status code (typically 4xx or 5xx).
headers - an object containing any HTTP headers where each key is a header name and value is the header content.
payload - the formatted object used as the response payload (stringified). Can be directly manipulated but any changes will be lost if reformat() is called. Any content allowed and by default includes the following content:
statusCode - the HTTP status code, derived from
error.output.statusCode.
error - the HTTP status message (e.g. 'Bad Request', 'Internal Server
Error') derived from statusCode.
message - the error message derived from error.message.
inherited Error properties.
It also supports the following method:
reformat() - rebuilds error.output using the other object properties.
const Boom = require('boom');
const handler = function (request, h) {
const error = Boom.badRequest('Cannot feed after midnight');
error.output.statusCode = 499; // Assign a custom error code
error.reformat();
error.output.payload.custom = 'abc_123'; // Add custom key
throw error;
});
When a different error representation is desired, such as an HTML page or a different payload format, the onPreResponse extension point may be used to identify errors and replace them with a different response object.
const Hapi = require('hapi');
const Vision = require('vision');
const server = Hapi.server({ port: 80 });
server.register(Vision, (err) => {
server.views({
engines: {
html: require('handlebars')
}
});
});
const preResponse = function (request, h) {
const response = request.response;
if (!response.isBoom) {
return h.continue;
}
// Replace error with friendly HTML
const error = response;
const ctx = {
message: (error.output.statusCode === 404 ? 'page not found' : 'something went wrong')
};
return h.view('error', ctx).code(error.output.statusCode);
};
server.ext('onPreResponse', preResponse);

Categories

Resources