Flutter HTTP request with JSON data to a Firebase function - firebase

I am currently trying to set up our Firestore database for a Flutter mobile app to take data from a Firebase function and store it into the database. Currently, we are trying to pass an HTTP request to our URI and attach some JSON data to it.
My function is as follows:
exports.testFunction = functions.https.onRequest((request, response) => {
var data = {
name: request.body.name,
age: request.body.age
};
var setDoc = db.collection('users').add(data);
response.json({result: `User ${data.name} at age ${data.age} added.`});
return;
});
Currently, the data can be retrieved when using a web browser and requesting from https://us-central1-[project-name].cloudfunctions.net/testFunction?name=[string]&age=[int]. This also works within our Flutter app, using the entire URL as a single argument with no body. However, when trying a cURL request from the command line:
curl -H 'Content-Type: application/json' -d '{"name": "[string]", "age": [int]}' https://us-central1-[project-name].cloudfunctions.net/testFunction
Hypothetically, the data should be in request.body, but I am getting an error saying the request could not be handled. What exactly is going wrong here?
Later, we want to implement this request in a Dart/Flutter app by attaching the JSON object to the body of the http.post. Is there a missing link in between this process that I'm missing?

The error Request body is missing data means that the payload that's being sent in the request needs to be mapped inside data. It's mentioned here in the docs that the Request body should have a data field.
var data = {
'data':{
name: request.body.name,
age: request.body.age
}
}

Related

Creating new document with Firestore REST API and Local Emulator Suite, Returning Error 404: Problem with Path Parameter

I’m just getting acquainted with Firebase/Firestore as a beginner coder, and I'm attempting to create an integration test for a set of callable functions a friend had written for their project. I am writing a test to automate testing using the Firebase local emulator suite.
Right now, I'm attempting to write a POSt request using Axios that will create a document in a given collection in my local emulator suite, after having received an Id Token from generating an authorized user.
The project id is called okane-crud-dev. I’ve created a collection
called test.
I have created an authenticated user with a given email and password, and generated the unique Id Token from an initial post request:
interface createPostRequest {
url: string;
data: Object;
config: Object;
};
//create an instance of a user
const createUserInstance : createPostRequest = {
url: 'http://localhost:9099/identitytoolkit.googleapis.com/v1/accounts:signUp?key=hi',
data: {
'email': 'myemail#email.com',
'password': 'mypassword',
'returnSecureToken': true
},
config: {
'headers':
{'Content-Type': 'application/json'}
},
};
const createUserResponse = await axios.post(createUserInstance.url, createUserInstance.data, createUserInstance.config);
const userIdToken = createUserResponse.data.idToken;
const userLocalId = createUserResponse.data.localId;
Up to this point, I have had no issues.
As for the second POST request to create a document, this is my code. I used this post as a reference:
Creating new collection and document with Firestore REST API returning HTTP 400
const createDocumentInstance : createPostRequest = {
url: "https://firestore.googleapis.com/v1beta1/projects/'localhost:8080/okane-crud-dev'/databases/(default)/documents/test",
data: {
"fields": {
"localId": userLocalId,
'budget': '2000',
}
},
//directly pasted IdToken as using the variable resulted in problem with ' ' error
config: {
'headers':
{
'Content-Type': 'application/json',
'Authorization': `Bearer ${userIdToken}`,
}
}};
console.log(createDocumentInstance);
const createDocument = await axios.post(createDocumentInstance.url, createDocumentInstance.data, createDocumentInstance.config);
const docReference = createDocument.data;
console.log(docReference);
When I attempted to run this, the following error was returned:
Request failed with status code 404
at createError (../../node_modules/axios/lib/core/createError.js:16:15)
at settle (../../node_modules/axios/lib/core/settle.js:17:12)
at IncomingMessage.handleStreamEnd (../../node_modules/axios/lib/adapters/http.js:293:11)
I'm a beginner and am just starting to learn how to code, so bear with me if this is an easy answer as I'm still figuring out how to debug.
I know that a 404 error means an issue with locating the resource -> and after making some adjustments to the headers, I figured the issue must be in my URL. I’ve tried looking around for other posts that use local emulator suite and POST requests to figure out if there was something wrong with how I wrote the path.
"https://firestore.googleapis.com/v1beta1/projects/'localhost:8080/okane-crud-dev'/databases/(default)/documents/test"
I've been looking at the Firebase documentation closely for creating a document; https://firebase.google.com/docs/firestore/reference/rest/v1beta1/projects.databases.documents/createDocument#path-parameters
Borrowing from the other post, I’ve tried different variations of where to include the emulator suite port: localhost:8080 and the project id “okane-crud-dev”. But haven’t seemed to figure out. I made sure that the project id was connected to my local emulator suite. Does anyone have any suggestions?
If you're using the Firestore Emulator with the REST API, you should change the base URL https://firestore.googleapis.com/v1 to your localhost http://localhost:8080/v1 then proceed with the path of your Firestore database.
http://localhost:8080/v1/projects/okrane-crud-dev/databases/(default)/documents/test

"Handler crashed with error runtime error: invalid memory address or nil pointer dereference", but POSTMAN is ok! Why this happens?

I work with vue and go for frontend and backend respectively. I send post request to my server and get 403 error code message(notAllowed). But in postman I get the objects and is fine.
Vue and Vuex
My axios post request:
const response = await this.$axios.post(`http://localhost:8000/v1/org/${params.organization}/kkms/${params.kkm}/closeShift`,{
headers : {
'token' : this.state.token.value
}});
I know I should also use other properties like 'Content-Type' and etc in headers, but know it works well with only "token" property in the other requests. I want to know whether problem in backend or frontend?
It seems you have a mistake in the axios request.
You are receiving a 403, that means you are not authorized (or sometimes something else, check the comments in the question and down here ).
As can be found in axios docs, the post request looks like this:
axios.post(url[, data[, config]]).
It accepts the config (so the headers) as THIRD parameter, while you are setting it as second parameter. Add an empty FormData object as second param, and just shift your config to the third param.
const fakeData = new FormData();
const response = await this.$axios.post(`http://localhost:8000/v1/org/${params.organization}/kkms/${params.kkm}/closeShift`,
fakeData,
{
headers : {
'token' : this.state.token.value
}
});

http post request to api nativescript vue

Hello I need help with http post request to my server and get response with authentication.
Look on the screens on 1 I use insomnia REST API application. Using this app I got success response with premium days and id.
In second image I got response just from my nativescript vue.js app where I got false response.
There is something wrong with my code. please tell me what.
You are sending a JSON object in your request body from {N} app, on the other hand you are using FormData with your REST client for testing.
You must either change your API to support JSON data on request body which is generally the standard way. In case if you can't do that, then you must use the nativescript-background-http plugin to send FormData. It will be something like,
var params = [
{ name: "username", value: "test" },
{ name: "password", value: "test123" },
{ name: "uuid", value: "xxxx" }
];
var task = session.multipartUpload(params, request);

Unit test Actions on Google Dialogflow locally

I'm trying to unit test a DialogflowApp locally by using the firebase shell environment. (in a cli do firebase experimental:functions:shell and then call my methods)
I have followed this guide by google https://firebase.google.com/docs/functions/local-emulator but they don't use the DialogflowApp where the invoked function tries to bind a request object containing intents and parameters like this ->
exports.myFunction = functions.https.onRequest((request, response) => {
const app = new App({ request, response });
function myMethod(app) {
let myArgument = app.getArgument(MY_ARGUMENT);
app.tell('Here we are responding');
}
let actionMap = new Map();
actionMap.set(MYMETHOD_ACTION, myMethod);
app.handleRequest(actionMap);
});
Regardless of what request object I send in the CLI, like this myFunction(require("../test/testdata.json")), the request body object is empty, like this body: {} which means I can't do app.handleRequest() or app.getArgument(). The error message I get is
RESPONSE RECEIVED FROM FUNCTION: 400, Action Error: no matching intent
handler for: null
I thought that if I populated testdata.json with the json request data shown in Actions on Google -> console.actions.google.com -> Simulator it would be valid data but no.
My question is, how can i mock my request data so that I can start unit testing my fullfillment methods locally?
EDIT 1:
firebase > myMethod.post("/").form(require("../test/testdata.json"))
Sent request to function.
firebase > info: User function triggered, starting execution
info: Function crashed
info: TypeError: Cannot destructure property `parameters` of 'undefined' or 'null'.
if we look in dialogflow_app.js we can see this code for fetching an argument value
getArgument (argName) {
debug('getArgument: argName=%s', argName);
if (!argName) {
error('Invalid argument name');
return null;
}
const { parameters } = this.body_.result;
if (parameters && parameters[argName]) {
return parameters[argName];
}
return this.getArgumentCommon(argName);
}
this.body_ is always just empty {}, regardless of how and what I send into the method when running locally.
EDIT 3
firebase > myMethod({method: "post",json: true, body: require("../test/testdata.json")})
Sent request to function.
firebase > info: User function triggered, starting execution
info: Function crashed
info: TypeError: Cannot destructure property parameters of 'undefined' or 'null'.
Invoking a Firebase HTTPS function using the shell requires a different form. It takes the parameters that the request module does, so in order to emulate a webhook, it will be something like this:
myfunction({
method: 'POST',
json: true,
body: require("../test/testdata.json")
});
These three parameters are important:
You need to specify that this is a POST operation
You need to indicate that the body will be JSON. This will send the correct header and won't try to send the body as x-www-form-urlencoded
You need to include the body. As an object is ok because you've set the json parameter to true.

How can I use notification actions with Firebase Messaging Web SDK

How do I use notification actions with the Firebase Messaging SDK on the web?
There are a few common pitfalls people hit when attempting this.
Firebase Notifications - There is a feature of the Firebase Messaging SD
K's none as "Firebase Notifications". When you send a push message to a Firebase Instance-ID (IID) token, you can use a "notification" key which the SDK's will look for and if found, construct a notification for you. The benefit of this is that you have to write no code to show a notification. The downside is that it can be restrictive if you want to do anything complex or perform work on the device once the notification is received. So to use actions, you MUST NOT USE THIS. Instead call the FCM API with the IID token and a "data" payload.
Data Payload - The data payload has a restriction where it can only be key value pairs, where the value must be a string, i.e. no arrays. What this means is that you can't just send an array of actions and construct a notification with that. The way around this is to create a JSON string, send that to the FCM API and then parse and use the JSON on the device.
Time for an example.
Calling the FCM API
The format of your payload should be something like this:
{
"data": {
"some-data": "Im a string",
"some-other-data": "Im also a string",
"json-data": "{\"actions\": [{\"action\":\"yes\", \"title\":\"Yes\"},{\"action\":\"no\",\"title\":\"No\"}]}"
},
"to": "YOUR-IID-TOKEN"
}
You can send this with curl like so:
curl -X POST -H "Authorization: key=YOUR-SERVER-KEY" -H "Content-Type: application/json" -d '{
"data": {
"some-data": "Im a string",
"some-other-data": "Im also a string",
"json-data": "{\"actions\": [{\"action\":\"yes\", \"title\":\"Yes\"},{\"action\":\"no\",\"title\":\"No\"}]}"
},
"to": "YOUR-IID-TOKEN"
}' "https://fcm.googleapis.com/fcm/send"
With that you'll be able to get the data in the onBackgroundMessage callback in your service worker.
Receiving the Payload on the Device
In a service worker we could have the following code:
messaging.setBackgroundMessageHandler(function(payload) {
console.log('Message received: ', payload);
});
Which would print out the following in the console:
Notice the JSON data is still just a string, not an object.
Next up we can parse the JSON data and check its the right format to use as our notification actions.
We can change our code to the following:
messaging.setBackgroundMessageHandler(function(payload) {
console.log('Message received: ', payload);
const parsedJSON = JSON.parse(payload.data['json-data']);
console.log('Actions:', parsedJSON);
});
This will give the following log:
With this, we can finally create our notification with the following code:
messaging.setBackgroundMessageHandler(function(payload) {
console.log('Message received: ', payload);
const parsedJSON = JSON.parse(payload.data['json-data']);
console.log('Actions:', parsedJSON);
// Customize notification here
const notificationTitle = 'Actions Title';
const notificationOptions = {
body: 'Actions body.',
actions: parsedJSON.actions,
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
Now you should have a notification with actions:
Testing
As Meggin as pointed out in the comments, it's not obvious how to test it, so a few guiding principles.
The biggest pain point is that if your web server sets a cache header for you service worker file, it won't update between refreshes, one way to fix this it to open your service worker file in a new tab and refresh that page until your service worker is up to date (This is viewing the actual source code of your service worker). Then when you refresh your web page your service worker will be the latest one and you can tell it's updated by the number next to the service worker incrementing.
Alternatively, just unregister the service worker the service worker and refresh the page - this should give you the latest service worker.
To test your notification, you'll need to click a tab that is for a different web page before sending a push message.
The reason for this is that if the user is currently on one of your pages, the push message is sent to the pages onMessage() callback instead of the onBackgroundMessage() callback.
Following Matt's advice, I was able to get a proper notification with content from my firebase function passed into my service worker (including actions), but I had to pass all of my data through the one json object, otherwise it wouldn't work for me.
Here's what my firebase functions code looks like:
function sendPayload(tokenArray) {
const payload = {
"data": {
"jsondata": "{\"body\":\"Meggin needs help\", \"title\":\"Can you help her make the code work?\",\"actions\": [{\"action\":\"yes\", \"title\":\"Yes\"},{\"action\":\"no\",\"title\":\"No\"}]}"
}
};
admin.messaging().sendToDevice(tokenArray, payload)
.then(function(response) {
// See the MessagingDevicesResponse reference documentation for
// the contents of response.
console.log("Successfully sent message:", response);
})
.catch(function(error) {
console.log("Error sending message:", error);
});
}
And here's what my code looks like in my service worker:
messaging.setBackgroundMessageHandler(function(payload) {
console.log('Payload received: ', payload);
const parsedJSON = JSON.parse(payload.data.jsondata);
console.log("What does actions look like? " + parsedJSON.actions);
console.log("What does title look like? " + parsedJSON.title);
const notificationTitle = parsedJSON.title;
const parsedBody = parsedJSON.body;
const parsedActions = parsedJSON.actions;
// Customize notification here
const notificationOptions = {
body: parsedBody,
actions: parsedActions,
};
return self.registration.showNotification(notificationTitle, notificationOptions);
});
It's worth noting that one major hurdle that helped me get passed this is understanding how to test push notifications and service workers!
You actually can't see my notification unless the browser is closed, so obviously, you can't watch the console.
But then once you've pushed the notification, you go into the console, and change the file at the top of console to be the service worker file specifically.
And then you can see the console logs!
I realize this might seem obvious to many people, but it wasn't to me, and it's crucial to understanding how to parse the payload and get it to do what you want!

Resources