FCM iOS: Push notifications throw invalid argument - node.js

I am attempting to implement pushNotifications using Cloud Functions and FCM for iOS but I am consistently thrown this error:
2018-05-21T13:04:00.087Z I sendPushNotifications: Error sending
message: { Error: Request contains an invalid argument.
at FirebaseMessagingError.Error (native)
at FirebaseMessagingError.FirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:39:28)
at FirebaseMessagingError.PrefixedFirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:85:28)
at new FirebaseMessagingError (/user_code/node_modules/firebase-admin/lib/utils/error.js:241:16)
at Function.FirebaseMessagingError.fromServerError (/user_code/node_modules/firebase-admin/lib/utils/error.js:271:16)
at /user_code/node_modules/firebase-admin/lib/messaging/messaging-api-request.js:149:50
at process._tickDomainCallback (internal/process/next_tick.js:135:7) errorInfo: { code:
'messaging/invalid-argument',
message: 'Request contains an invalid argument.' }, codePrefix: 'messaging' }
My implementation in cloud functions are as follows:
exports.sendPushNotifications = functions.database.ref('/conversations/{userUid}/').onWrite((snap, context) => {
const userUid = context.params.userUid
console.log("Triggered user ", userUid)
return admin.database().ref('/fcmToken/' + userUid).once('value', snapshot => {
const values = snapshot.val()
const fcmToken = values.fcmToken
var message = {
"token": fcmToken,
"notification": {
"body": "New message"
},
"apns": {
"headers": {
"apns-priority": "5"
},
"payload": {
"aps": {
"alert": {
"body": "New message"
},
"badge": "1",
"sound": "default"
}
}
}
};
return admin.messaging().send(message)
.then((response) => {
return console.log('Successfully sent message:', response);
})
.catch((error) => {
return console.log('Error sending message:', error);
});
})
})
The frustrating thing is that when I remove the whole "apns" node the code actually works, ie I can receive the push notifications. I suppose this means my setup are all done properly. Once I included the "apns", it starts throwing the above error. I also reference these three posts, this, this and this, and made sure that I have carefully followed the code and instructions. For some reasons I cannot get it to work.
I also attempted to remove the "notification" node as the docs did mention that only use common keys when targetting all platforms. Since I am targetting only iOS for now, I suppose I should remove the "notification" key. But again it also throws the same error.

Ok, so it was a rookie mistake. It is correct that common keys should not be used if I am targetting iOS only. In addition to that, the badge should be an Int and not String.
This code worked:
var message = {
"token": fcmToken,
"apns": {
"headers": {
"apns-priority": "5"
},
"payload": {
"aps": {
"alert": {
"body": "New message"
},
"badge": 1,
"sound": "default"
}
}
}
}
Hope it helps anyone out there facing the same problem.

Just to add to this answer. If you are using both IOS and android while having custom sounds for both, the code below will work cross platform and avoid this issue.
const payload = {
token,
notification: {
title: `title text`,
body: `body text`,
},
android: {
// android
priority: "high", // legacy HTTP protocol (this can also be set to 10)
notification: {
channel_id: "call1",
priority: "high", // HTTP v1 protocol
notification_priority: "PRIORITY_MAX",
sound: "sound",
default_sound: true,
visibility: "PUBLIC",
},
},
apns: {
// apple
headers: { "apns-priority": "10" },
payload: {
aps: {
// If present with notification: {...}, this will create errors
// "alert": {
// "title": `title text`,
// "body": `body text`,
// },
badge: 1,
sound: {
critical: 1,
name: "sound.aiff",
volume: 1,
},
category: "call1",
},
},
},

Related

AJV validation doesn't return multiple errors when different values are missing in the fastify request body

I have a fastify server with a post endpoint. It accepts a JSON request body with a validation. Server code below:
const fastify = require("fastify");
const server = fastify({
ajv: {
customOptions: {
allErrors: true,
},
},
logger: true,
});
const schema = {
schema: {
body: {
type: "object",
properties: {
data: {
type: "array",
items: {
type: "object",
properties: {
foo: {
type: "string",
},
bar: {
type: "string",
},
},
required: ["foo", "bar"],
},
},
},
required: ["data"],
},
},
};
server.post("/", schema, function (request, reply) {
console.log({
request: {
body: JSON.stringify(request.body),
},
});
reply.send({ message: "hello" });
});
server.listen(3000, function (err, address) {
if (err) {
fastify.log.error(err);
process.exit(1);
}
console.log(`server listening on ${address}`);
});
Below is a valid request body.
{ "data":[{ "bar": "bar exists", "foo": "foo exists" }]}
When I try to access the same server with multiple values in input missing i.e.,
{ "data":[{ "bar": "bar exists, foo missing" }, {}] }
I am getting the below response.
{
"statusCode": 400,
"error": "Bad Request",
"message": "body.data[0] should have required property 'foo', body.data[1] should have required property 'foo', body.data[1] should have required property 'bar'"
}
I want to get each error separately, instead of getting a single large error message as this request can go very large. I have tried a bit of trial around the ajv options but couldn't find anything.
Any help is appreciated. Cheers :)
You need to have a custom parser after the error is caught.
In order to achieve this approach, there is a method called setErrorHandler.
According to Fastify documentation:
Set a function that will be called whenever an error happens.
This is a simple parser, but you may need to change it to your taste:
server.setErrorHandler(function (error, request, reply) {
if (error.validation) {
reply.status(400).send({
statusCode: error.statusCode,
error: 'Bad Request',
message: error.validation.map(err => `${err.instancePath} ${err.message}`)
});
}
})
// rest of code

Node.js FCM token is empty but it's not

async function sendNotif(title, body0, token) {
return await admin.messaging().send({
message: {
token:
"dHUKMkIxRbS3uIpdnA1Qef:APA91bHQd2XUpFyWzfdbKrpPV2T9b0uJx9TfKZcyF-O_oAbQ13yA5R-52t_RTb_QSPrMpxw1OV9z8sNFRth5wGuCAld_9VsKr4oRdSWsMzqhrbKcTLC2rAp5QLOUALqiTadyvyvcjTmb!",
notification: {
title: title,
body: body0,
},
data: {
hello: "world",
click_action: "FLUTTER_NOTIFICATION_CLICK",
},
// Set Android priority to "high"
android: {
priority: "high",
},
// Add APNS (Apple) config
apns: {
payload: {
aps: {
contentAvailable: true,
},
},
headers: {
"apns-push-type": "background",
"apns-priority": "5", // Must be `5` when `contentAvailable` is set to true.
"apns-topic": "io.flutter.plugins.firebase.messaging", // bundle identifier
},
},
},
});
}
I am wondering why I am getting this error
errorInfo: {
code: 'messaging/invalid-payload',
message: 'Exactly one of topic, token or condition is required'
},
I checked the token and it's a good one i add firebase-admin library and I initialized it but it still didn't work .
so any answers?

How to add values from coinmarketcap API to telegram message?

I'm trying to finish telegram bot that will after several commands respond with message... Lost any hope of trying that i can solve this alone. Those commands with predefined message are done and working like a charm.. But now im stucked on the /price command which should show coin value in the message from coinmarket API respond..
I tried many variants but following results always called for API Call error: or message like [object Object]..
ALQO: $0.0443407142 | 9.73% 🙂
ETH: 0.000313592 | 10.14% 🙂
BTC: 0.0000107949 | 9.5% 🙂
Cap: $2,545,718
This text above is correct respond from bot... Unfortunately with free API from CMC i can do only price with USD so correct answer should be
Coinname: Price | Change%
Cap: Marketcap
My code of /price command
//This is /price command code
'use strict';
const Telegram = require('telegram-node-bot');
const rp = require('request-promise');
const requestOptions = {
method: 'GET',
uri: 'https://pro-
api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?
id=3501&convert=USD',
headers: {
'X-CMC_PRO_API_KEY': 'MYFREEAPIKEYFROMCMC'
},
json: true,
gzip: true
};
rp(requestOptions).then(response => {
console.log('API call response:', response['data'][3501]);
}).catch((err) => {
console.log('API call error:', err.message);
});
class PriceController extends Telegram.TelegramBaseController {
PriceHandler($) {
rp(requestOptions).then(response => {
console.log('API call response:', response['data'][3501]);
$.sendMessage('Cryptosoul: price', response['data']['USD']['price']
[3501]);
}).catch((err) => {
$.sendMessage('API call error:', err.message);
});
}
get routes() {
return {
'priceCommand': 'PriceHandler'
};
};
}
module.exports = PriceController;
Respond from API after node index.js (turning bot on, (message from visual studio terminal)
API call response: { id: 3501,
name: 'CryptoSoul',
symbol: 'SOUL',
slug: 'cryptosoul',
circulating_supply: 143362580.31,
total_supply: 499280500,
max_supply: null,
date_added: '2018-10-25T00:00:00.000Z',
num_market_pairs: 3,
tags: [],
platform:
{ id: 1027,
name: 'Ethereum',
symbol: 'ETH',
slug: 'ethereum',
token_address: '0xbb1f24c0c1554b9990222f036b0aad6ee4caec29' },
cmc_rank: 1194,
last_updated: '2019-04-01T23:03:07.000Z',
quote:
{ USD:
{ price: 0.000188038816143,
volume_24h: 11691.5261174775,
percent_change_1h: 0.29247,
percent_change_24h: 0.0222015,
percent_change_7d: 4.69888,
market_cap: 26957.72988069816,
last_updated: '2019-04-01T23:03:07.000Z' } } }
The messages that appears after /price command triggered
"API call error:"
"[object Object]"
"Error while running node index.js (bad code)"Chat with Bot
As I could see, you are incorrectly accessing the resulting json response object here:
$.sendMessage('Cryptosoul: price', response['data']['USD']['price']
[3501])
Just pretty printing that response object gives a correct way to access certain properties.
{
"status": {
"timestamp": "2019-04-02T08:38:09.230Z",
"error_code": 0,
"error_message": null,
"elapsed": 14,
"credit_count": 1
},
"data": {
"3501": {
"id": 3501,
"name": "CryptoSoul",
"symbol": "SOUL",
"slug": "cryptosoul",
"circulating_supply": 143362580.31,
"total_supply": 499280500,
"max_supply": null,
"date_added": "2018-10-25T00:00:00.000Z",
"num_market_pairs": 3,
"tags": [],
"platform": {
"id": 1027,
"name": "Ethereum",
"symbol": "ETH",
"slug": "ethereum",
"token_address": "0xbb1f24c0c1554b9990222f036b0aad6ee4caec29"
},
"cmc_rank": 1232,
"last_updated": "2019-04-02T08:37:08.000Z",
"quote": {
"USD": {
"price": 0.000201447607597,
"volume_24h": 12118.3983544441,
"percent_change_1h": 1.48854,
"percent_change_24h": 6.88076,
"percent_change_7d": 12.4484,
"market_cap": 28880.04882238228,
"last_updated": "2019-04-02T08:37:08.000Z"
}
}
}
}
}
So we could see that price field is located under USD object wich itself located under quote object, which is missing in your code.
Proper way to get it would be:
const price = response["data"][3501]["quote"]["USD"]["price"];
PriceHandler code:
PriceHandler($) {
rp(requestOptions)
.then((response) => {
const price = response["data"][3501]["quote"]["USD"]["price"];
$.sendMessage("Cryptosoul: price", price);
})
.catch((err) => {
console.error("API call error:", err.message);
});
}

How do I dynamically update the fields of a document?

I'm building a basic crud app for storing movie/tv titles. I am using version 2.4.4 of elasticsearch because my project manager chose this version. I am using the express framework along with the native elasticsearch nodejs driver.
Currently this is what my mappings look like for the "titles" index for a "title" type.
client.indices.create({
index: "titles",
body: {
"mappings": {
"title": {
"properties": {
"seriesName": { "type": "string", "index": "not_analyzed" },
"seriesEpisodeNumber": { "type": "long", "index": "not_analyzed" },
"seriesEpisodeTitle": { "type": "string", "index": "not_analyzed" },
"isDeleted": { "type": "boolean", "index": "not_analyzed" }
}
}
}
}
}).
I want to have an endpoint on my server that updates the document by its id. This is what I have so far for this endpoint " POST /api/titles/:titleID/updateTitleByID"
titlesController.updateTitleByID = function(req,res){
const titleID = req.params.titleID;
es.updateByQuery({
index: "titles",
type: "title",
body: {
query: {
bool: {
must: [{
term: {
_id: titleID
}},{
term: {
isDeleted: false
}}]
}
},
script: {
inline: "ctx._source.seriesName = seriesName;ctx._source.seriesEpisodeNumber = seriesEpisodeNumber; ctx._source.seriesEpisodeTitle = seriesEpisodeTitle;",
params: { ...req.body }
}
}}).then(results => {
logger.info(results);
res.status(200).json({
message: "OK"
});
}).catch(err => {
logger.error(err);
res.status(500).json({
error: "Internal server error"
});
});
};
What I want to achieve is to update the document with what ever I get from my req.body. For example if I get a seriesName I from the request body. That is only what I want to update and leave the rest untouched. How do I achieve this?
Edit: Here is the full stack trace
Error: [remote_transport_exception] [Agon][127.0.0.1:9300][indices:data/write/update[s]]
at respond (/home/natealcedo/Projects/digitalfolks.hooq/node_modules/elasticsearch/src/lib/transport.js:289:15)
at checkRespForFailure (/home/natealcedo/Projects/digitalfolks.hooq/node_modules/elasticsearch/src/lib/transport.js:248:7)
at HttpConnector.<anonymous> (/home/natealcedo/Projects/digitalfolks.hooq/node_modules/elasticsearch/src/lib/connectors/http.js:164:7)
at IncomingMessage.wrapper (/home/natealcedo/Projects/digitalfolks.hooq/node_modules/lodash/lodash.js:4968:19)
at emitNone (events.js:91:20)
at IncomingMessage.emit (events.js:186:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:74:11)
at process._tickDomainCallback (internal/process/next_tick.js:122:9)
You don't need to use the update-by-query endpoint if you want to update a single document, the Update API is more than sufficient since it supports partial updates, which is exactly what you're after. You need to use the update call instead:
titlesController.updateDocByID = function(req,res){
const titleID = req.params.titleID;
const partialFields = req.body;
// TODO do some validations on the received fields
es.update({
index: "titles",
type: "title",
id: titleID,
body: {
doc: partialFields
}}).then(results => {
logger.info(results);
res.status(200).json({
message: "OK"
});
}).catch(err => {
logger.error(err);
res.status(500).json({
error: "Internal server error"
});
});
};
If you need to only update the document if the isDeleted flag is false, then you can use the same API call but with a script instead, like this:
titlesController.updateDocByID = function(req,res){
const titleID = req.params.titleID;
const partialFields = req.body;
// TODO do some validations on the received fields
es.update({
index: "titles",
type: "title",
id: titleID,
body: {
script: "if (!ctx._source.isDeleted) { ctx._source.putAll(params.partialFields) }",
params: {partialFields: partialFields}
}}).then(results => {
logger.info(results);
res.status(200).json({
message: "OK"
});
}).catch(err => {
logger.error(err);
res.status(500).json({
error: "Internal server error"
});
});
};
If you use this second method, you'll need to enable dynamic scripting in your elasticsearch.yml configuration file.

Error when trying to send transactional email from template using sendGrid

I'm trying to send a transactional email via node using sendGrid. Below is an example of my code.
const subject = 'Email subject';
const templateId = 'templateId';
const sg = require('sendgrid')(secret);
const request = sg.emptyRequest({
method: 'POST',
path: '/v3/mail/send',
body: {
"personalizations": [
{
"bcc": userEmails,
"substitutions": {
"-userName-": userDetails.name,
"-productPrice-": productDetails.price,
"-productUrl-": productDetails.url,
"-productPercentageDrop-": productDetails.percentageDrop,
"-productName-": productDetails.name,
"-productOriginalPrice-": productDetails.origPrice,
"-productDroppedPrice-": productDetails.dropPrice,
"-productImageUrl-": productDetails.imageUrl
},
"subject": subject.substring(0, 75)
}
],
"from": {
"email": "myemail",
"name": "myname"
},
"content": [
{
"type": "text/html"
}
],
"template_id": templateId
}
});
sg.API(request, function (error, response) {
if (error) {
console.log('Error response received');
}
console.log(response.body.errors);
});
But every time I run the code I get the following error message.
400
message: 'Bad Request', field: null, help: null
Which isn't really that helpful when trying to find out why its erroring.
Body JSON being sent:
{
"host":"",
"method":"POST",
"path":"/v3/mail/send",
"headers":{
},
"body":{
"personalizations":[
{
"bcc":[
{
"email":"name1#hotmail.com",
"name":"name1"
},
{
"email":"name2#hotmail.com",
"name":"name2"
}
],
"substitutions":{
"-productPrice-":189.5,
"-productUrl-":"http://www.tesco.com/direct/humax-fvp-4000t500-m-smart-freeview-play-hd-digital-tv-recorder-with-wi-fi-500gb/483-1785.prd",
"-productName-":"Tesco direct: Humax FVP-4000T/500 (M) Smart Freeview Play HD Digital TV Recorder with Wi-Fi - 500GB"
},
"subject":"Product Tesco direct: Humax FVP-4000T/500 (M) Smart Freeview Play HD Digita"
}
],
"from":{
"email":"email#pricetracker.io",
"name":"Pricetracker"
},
"content":[
{
"type":"text/html"
}
],
"template_id":"XXXXXX"
},
"queryParams":{
},
"test":false,
"port":""
}
don't know if this could still be helpful for you, but I've had some similar issues when using substitutions with Sendgrid. I'm using the sendgrid-php library, but internally it sends the same format. What I found was that all substitution values should be converted to string. For instance your value for productPrice should be like this:
"-productPrice-": "189.5",
With quotes. In my case I had integers, and when I converted them to strings, all worked correctly.
If you are not passing string value to the template file then SendGrid will give you
{"errors":[{"message":"Bad Request","field":null,"help":null}]}
Convert your all non-string value to string before sending to the template file.
I got same issue, then I fix using this below syntax, change only you message format.
"value": "'.html_entity_decode((str_replace('"',"'",$message))).'"

Resources