Send Push Notification from Cloud Function in iOS - javascript

I'm able to print and send test notification from firebase console with the token retrieved from firestore. But somehow I get Error Sending Notification: INTERNAL when I'm trying to send push notification via JavaScript
This is my JavaScript code
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp();
const messaging = admin.messaging();
exports.getUserData = functions.https.onCall(async (data, context) => {
const clientUid = data.clientUid;
try {
const snapshot = await admin.firestore().collection('users').doc(clientUid).get();
const userData = snapshot.data();
const token = userData.token;
const tokenString = JSON.stringify(token);
console.log(`Token String: ${tokenString}`);
console.log(`User data: ${JSON.stringify(userData)}`);
return userData;
} catch (error) {
console.error(`Error retrieving user data: ${error}`);
throw new functions.https.HttpsError('internal', 'Error retrieving user data.');
}
});
exports.sendNotification = functions.https.onCall(async (data, context) => {
const token = data.token;
const payload = {
notification: {
title: 'New Notification',
body: 'You have a new notification!'
}
};
try {
const response = await admin.messaging().sendToDevice(token, payload, {
contentAvailable: true,
priority: 'high'
});
console.log('Successfully sent message:', response);
return { message: 'Push notification sent successfully!' };
} catch (error) {
console.error('Error sending push notification:', error);
throw new functions.https.HttpsError('internal', 'Error sending push notification');
}
});
and the following is my code in SwiftUI
import SwiftUI
import JavaScriptCore
import Firebase
import StoreKit
import FirebaseFunctions
class JSNotification: ObservableObject {
#Published var token = ""
func sendNotificationFromSwift(clientUid: String) {
let context = JSContext()!
if let filePath = Bundle.main.path(forResource: "index", ofType: "js", inDirectory: "functions") {
do {
let content = try String(contentsOfFile: filePath)
context.evaluateScript(content)
let functions = Functions.functions()
let sendNotification = functions.httpsCallable("sendNotification")
Firestore.firestore().collection("users").document(clientUid).getDocument { snapshot, error in
if let error = error {
print(error.localizedDescription)
return
} else {
if let data = snapshot?.data() {
self.token = data["token"] as? String ?? ""
print(self.token)
sendNotification.call(["token": self.token]) { result, error in
if let error = error {
print("Error Sending Notification: \(error.localizedDescription)")
return
} else {
print("Push Notification Is Sent")
}
}
}
}
}
} catch {
print("Error Reading JS File.")
}
} else {
print("No Path Found.")
}
}
}
The sendNotificationFromSwift is called as follow
import Firebase
import SwiftUI
struct TestJSView: View {
#StateObject var jsNotification = JSNotification()
var body: some View {
Button {
jsNotification.sendNotificationFromSwift(clientUid: "A_Valid_Uid")
} label: {
Text("Test JS Notification")
}
}
}
The JavaScript function has been successfully deployed too.
Can anyone please let me know what went wrong? Any advice will be much appreciated.

Here's how I solved the issue.
I ran this command in my terminal:
firebase functions:log
that gives me the details of the error "INTERNAL" which in my case is "sendNotification: Error sending push notification: FirebaseMessagingError: An error occurred when trying to authenticate to the FCM servers. Make sure the credential used to authenticate this SDK has the proper permissions." generated from this function above
func sendNotificationFromSwift(clientUid: String) {
let context = JSContext()!
if let filePath = Bundle.main.path(forResource: "index", ofType: "js", inDirectory: "functions") {
do {
let content = try String(contentsOfFile: filePath)
context.evaluateScript(content)
let functions = Functions.functions()
let sendNotification = functions.httpsCallable("sendNotification")
Firestore.firestore().collection("users").document(clientUid).getDocument { snapshot, error in
if let error = error {
print(error.localizedDescription)
return
} else {
if let data = snapshot?.data() {
self.token = data["token"] as? String ?? ""
print(self.token)
sendNotification.call(["token": self.token]) { result, error in
if let error = error {
//Error generated from here
print("Error Sending Notification: \(error.localizedDescription)")
return
} else {
print("Push Notification Is Sent")
}
}
}
}
}
} catch {
print("Error Reading JS File.")
}
} else {
print("No Path Found.")
}
}
And the solution can be found here.

Related

Node.JS IPFS sending message TypeError: Cannot read properties of undefined (reading 'publish')

I'm trying to send a message through an IPFS network but I keep getting the error TypeError: Cannot read properties of undefined (reading 'publish').
The error occurs at line node.pubsub.publish(topic, msg, (err) => {}).
ipfs.js is the app's main engine and contains the methods needed to interact with the IPFS network.
The 'sendNewMsg' method is used to send messages to a topic where other peers can subscribe and read the messages.
index.js calls and executes the method.
Could you please help me spot and fix the problem?
Thanks in advance!
ipfs.js:
const IPFS = require('ipfs');
const BufferPackage = require('buffer');
const Buffer = BufferPackage.Buffer;
class IPFSEngine {
constructor(){
let node = IPFS.create({
EXPERIMENTAL: { pubsub: true },
repo: (() => `repo-${Math.random()}`)(),
config: {
Addresses: {
Swarm: [
'/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star'
]
}
}
});
this.node = node;
}
....
....
....
sendNewMsg(topic, newMsg) {
let {node} = this;
console.log('Message sent: ', newMsg)
const msg = Buffer.from(newMsg)
node.pubsub.publish(topic, msg, (err) => {
if (err) {
return console.error(`Failed to publish to ${topic}`, err)
}
// msg was broadcasted
console.log(`Published to ${topic}`)
})
}
}
// Export IPFSEngine
module.exports = {IPFSEngine};
index.js:
const {IPFSEngine} = require('./ipfs');
const ipfs = new IPFSEngine();
ipfs.sendNewMsg(`topic2`,`Messages 2 ..!`)
The error states that node.pubsub is undefined while you are trying to access the publish property on the object.
Quickly reading through an example from the IPFS documentation, it appears that IPFS.create is an asynchronous API, which result you are not awaiting. It could explain why you get an undefined pubsub property on your node.
Using an async function, you could write something like:
async function sendNewMsg(topic, newMsg) {
let { node } = this;
console.log('Message sent: ', newMsg);
const msg = Buffer.from(newMsg);
(await node).pubsub.publish(topic, msg, (err) => {
if (err) {
return console.error(`Failed to publish to ${topic}`, err);
}
// msg was broadcasted
console.log(`Published to ${topic}`);
});
}
Or without the async/await syntax:
function sendNewMsg2(topic, newMsg) {
let { node } = this;
console.log('Message sent: ', newMsg);
const msg = Buffer.from(newMsg);
node.then((readyNode) => {
readyNode.pubsub.publish(topic, msg, (err) => {
if (err) {
return console.error(`Failed to publish to ${topic}`, err);
}
// msg was broadcasted
console.log(`Published to ${topic}`);
});
})
}

nodejs TypeError: Cannot read property 'Message' of undefined

I have a lambda function in AWS. I use it to retrieve information from a REST API. When I test it runs returns a 200 status code, but an "ERROR TypeError: Cannot read property 'Message' of undefined
at smsResponder (/var/task/smsResponder.js:33:35)" also shows. I have googled, and tried to use .responseText.
My code is below. Should I be using return or something of the sort?
'use strict'
const AWS = require('aws-sdk')
AWS.config.update({ region: process.env.AWS_REGION || 'us-east-1' })
const { getStock } = require('./getStock')
const KEYWORD = 'stock'
const validateStock = function (elementValue){
let stockTest = AAPL
return stockTest.test(elementValue)
}
const sendSMS = async function (params) {
const pinpoint = new AWS.Pinpoint()
console.log('sendSMS called: ', params)
return new Promise((resolve, reject) => {
pinpoint.sendMessages(params, function(err, data) {
if(err) {
console.error(err)
reject(err)
} else {
console.log("Message sent. Data: ", data)
resolve(data)
}
})
})
}
const smsResponder = async (event) => {
const msg = JSON.parse(event.Sns.Message)
const msgWords = msg.messageBody.split(" ")
// Check the first word of the text message is the keyword
if (msgWords[0].toLowerCase() !== KEYWORD) return console.log('No keyword found - exiting')
// Validate stock name and get price
let message =''
const stockCode = msgWords[1]
if (validateStock(stockCode)) {
message = await getStock(stockCode)
} else {
message = 'Invalid stock symbol - text me in the format "stock stocksymbol".'
}
// Send the SMS response
const params = {
ApplicationId: process.env.ApplicationId,
MessageRequest: {
Addresses: {
[msg.originationNumber]: {
ChannelType: 'SMS'
}
},
MessageConfiguration: {
SMSMessage: {
Body: message,
MessageType: 'PROMOTIONAL',
OriginationNumber: msg.destinationNumber
}
}
}
}
return console.log(await sendSMS(params))
}
module.exports = { smsResponder }
The SNS-Event is differently structured, it should be event.Records[0].Sns.Message .
Here are the docs:
https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html

Always throws registration failed error while subscribing push notifications

I am working on solutions using which i can send desktop push notification to subscribed clients.
I have created basic solution in where whenever user click on button i ask user for whether they want to allow notifications for my app or not!
I am getting an error of "Registration failed - permission denied" whenever i click on button for first time.
So that i am not able to get required endpoints to save at backend
Here is my code
index.html
<html>
<head>
<title>PUSH NOT</title>
<script src="index.js"></script>
</head>
<body>
<button onclick="main()">Ask Permission</button>
</body>
</html>
index.js
const check = () => {
if (!("serviceWorker" in navigator)) {
throw new Error("No Service Worker support!");
} else {
console.log("service worker supported")
}
if (!("PushManager" in window)) {
throw new Error("No Push API Support!");
} else {
console.log("PushManager worker supported")
}
};
const registerServiceWorker = async () => {
const swRegistration = await navigator.serviceWorker.register("/service.js?"+Math.random());
return swRegistration;
};
const requestNotificationPermission = async () => {
const permission = await window.Notification.requestPermission();
// value of permission can be 'granted', 'default', 'denied'
// granted: user has accepted the request
// default: user has dismissed the notification permission popup by clicking on x
// denied: user has denied the request.
if (permission !== "granted") {
throw new Error("Permission not granted for Notification");
}
};
const main = async () => {
check();
const swRegistration = await registerServiceWorker();
const permission = await requestNotificationPermission();
};
// main(); we will not call main in the beginning.
service.js
// urlB64ToUint8Array is a magic function that will encode the base64 public key
// to Array buffer which is needed by the subscription option
const urlB64ToUint8Array = base64String => {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, "+")
.replace(/_/g, "/");
const rawData = atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
};
const saveSubscription = async subscription => {
console.log("Save Sub")
const SERVER_URL = "http://localhost:4000/save-subscription";
const response = await fetch(SERVER_URL, {
method: "post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(subscription)
});
return response.json();
};
self.addEventListener("activate", async () => {
try {
const applicationServerKey = urlB64ToUint8Array(
"BFPtpIVOcn2y25il322-bHQIqXXm-OACBtFLdo0EnzGfs-jIGXgAzjY6vNapPb4MM1Z1WuTBUo0wcIpQznLhVGM"
);
const options = { applicationServerKey, userVisibleOnly: true };
const subscription = await self.registration.pushManager.subscribe(options);
console.log(JSON.stringify(subscription))
const response = await saveSubscription(subscription);
} catch (err) {
console.log(err.code)
console.log(err.message)
console.log(err.name)
console.log('Error', err)
}
});
self.addEventListener("push", function(event) {
if (event.data) {
console.log("Push event!! ", event.data.text());
} else {
console.log("Push event but no data");
}
});
Also i have created a bit of backend as well
const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const webpush = require('web-push')
const app = express();
app.use(cors());
app.use(bodyParser.json());
const port = 4000;
app.get("/", (req, res) => res.send("Hello World!"));
const dummyDb = { subscription: null }; //dummy in memory store
const saveToDatabase = async subscription => {
// Since this is a demo app, I am going to save this in a dummy in memory store. Do not do this in your apps.
// Here you should be writing your db logic to save it.
dummyDb.subscription = subscription;
};
// The new /save-subscription endpoint
app.post("/save-subscription", async (req, res) => {
const subscription = req.body;
await saveToDatabase(subscription); //Method to save the subscription to Database
res.json({ message: "success" });
});
const vapidKeys = {
publicKey:
'BFPtpIVOcn2y25il322-bHQIqXXm-OACBtFLdo0EnzGfs-jIGXgAzjY6vNapPb4MM1Z1WuTBUo0wcIpQznLhVGM',
privateKey: 'mHSKS-uwqAiaiOgt4NMbzYUb7bseXydmKObi4v4bN6U',
}
webpush.setVapidDetails(
'mailto:janakprajapati90#email.com',
vapidKeys.publicKey,
vapidKeys.privateKey
)
const sendNotification = (subscription, dataToSend='') => {
webpush.sendNotification(subscription, dataToSend)
}
app.get('/send-notification', (req, res) => {
const subscription = {endpoint:"https://fcm.googleapis.com/fcm/send/dLjyDYvI8yo:APA91bErM4sn_wRIW6xCievhRZeJcIxTiH4r_oa58JG9PHUaHwX7hQlhMqp32xEKUrMFJpBTi14DeOlECrTsYduvHTTnb8lHVUv3DkS1FOT41hMK6zwMvlRvgWU_QDDS_GBYIMRbzjhg",expirationTime:null,keys:{"p256dh":"BE6kUQ4WTx6v8H-wtChgKAxh3hTiZhpfi4DqACBgNRoJHt44XymOWFkQTvRPnS_S9kmcOoDSgOVD4Wo8qDQzsS0",auth:"CfO4rOsisyA6axdxeFgI_g"}} //get subscription from your databse here.
const message = 'Hello World'
sendNotification(subscription, message)
res.json({ message: 'message sent' })
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
Please help me
Try the following code:
index.js
const check = () => {
if (!("serviceWorker" in navigator)) {
throw new Error("No Service Worker support!");
} else {
console.log("service worker supported")
}
if (!("PushManager" in window)) {
throw new Error("No Push API Support!");
} else {
console.log("PushManager worker supported")
}
};
const saveSubscription = async subscription => {
console.log("Save Sub")
const SERVER_URL = "http://localhost:4000/save-subscription";
const response = await fetch(SERVER_URL, {
method: "post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(subscription)
});
return response.json();
};
const urlB64ToUint8Array = base64String => {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, "+")
.replace(/_/g, "/");
const rawData = atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
};
const registerServiceWorker = async () => {
return navigator.serviceWorker.register("service.js?"+Math.random()).then((swRegistration) => {
console.log(swRegistration);
return swRegistration;
});
};
const requestNotificationPermission = async (swRegistration) => {
return window.Notification.requestPermission().then(() => {
const applicationServerKey = urlB64ToUint8Array(
"BFPtpIVOcn2y25il322-bHQIqXXm-OACBtFLdo0EnzGfs-jIGXgAzjY6vNapPb4MM1Z1WuTBUo0wcIpQznLhVGM"
);
const options = { applicationServerKey, userVisibleOnly: true };
return swRegistration.pushManager.subscribe(options).then((pushSubscription) => {
console.log(pushSubscription);
return pushSubscription;
});
});
};
const main = async () => {
check();
const swRegistration = await registerServiceWorker();
const subscription = await requestNotificationPermission(swRegistration);
// saveSubscription(subscription);
};
service.js
self.addEventListener("push", function(event) {
if (event.data) {
console.log("Push event!! ", event.data.text());
} else {
console.log("Push event but no data");
}
});
I can think of three reasons that the permission is denied
1) your site is not on https (including localhost that is not on https), the default behaviour from chrome as far as i know is to block notifications on http sites. If that's the case, click on the info icon near the url, then click on site settings, then change notifications to ask
2) if you are on Safari, then safari is using the deprecated interface of the Request permission, that is to say the value is not returned through the promise but through a callback so instead of
Notification.requestPermission().then(res => console.log(res))
it is
Notification.requestPermission(res => console.log(res))
3) Your browser settings are blocking the notifications request globally, to ensure that this is not your problem run the following code in the console (on a secured https site)
Notification.requestPermission().then(res => console.log(res))
if you receive the alert box then the problem is something else, if you don't then make sure that the browser is not blocking notifications requests

Push notification "Error Registration failed - permission denied" before prompt is clicked?

I'm trying to learn how to use Push Notifications but I am getting a
Registration failed - permission denied
Error even before I click allow or block the permission prompt.
The line that produces this error in my service worker file is
const subscription = await self.registration.pushManager.subscribe(options)
The strange thing is if I click allow notifications after the error and refresh the page it all works fine.
And when I check window.Notification.requestPermission it returns granted
Most of the code is from https://blog.atulr.com/web-notifications/
const urlB64ToUint8Array = base64String => {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
const rawData = atob(base64)
const outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i)
}
return outputArray
}
const saveSubscription = async subscription => {
const SERVER_URL = 'http://localhost:8081/save-subscription'
const response = await fetch(SERVER_URL, {
method: 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(subscription),
})
return response.json()
}
self.addEventListener('activate', async () => {
// This will be called only once when the service worker is installed for first time.
try {
const applicationServerKey = urlB64ToUint8Array(
'BO29MqroVuQiUch951aG0kASkvQy9S4OLshzA3xc12Ar3oAT7UMxjlsq3OMlQXQaSsZILLy7nWuvmbZNZ1YOrXo'
)
const options = { applicationServerKey, userVisibleOnly: true }
const subscription = await self.registration.pushManager.subscribe(options)
const response = await saveSubscription(subscription)
console.log(response)
} catch (err) {
console.log('Error', err.message)
}
})
self.addEventListener('push', function(event) {
if (event.data) {
console.log('Push event!! ', event.data.text())
showLocalNotification('Yolo', event.data.text(), self.registration)
} else {
console.log('Push event but no data')
}
})
const showLocalNotification = (title, body, swRegistration) => {
const options = {
body,
// here you can add more properties like icon, image, vibrate, etc.
}
swRegistration.showNotification(title, options)
}
You need to request notification permission before you register service worker or subscribe to the push service.
According to the code in https://blog.atulr.com/web-notifications/, change the order of below two statements in index.js from
const swRegistration = await registerServiceWorker();
const permission = await requestNotificationPermission();
to
const permission = await requestNotificationPermission();
const swRegistration = await registerServiceWorker();
This should fix the error message below.
Registration failed - permission denied

How to make a function that can be called in other functions in Cloud Functions?

I have the following case, when deleting any data, I need to delete the app badges (at the moment I delete them using silent push notication and reduce the app badges number with the cloud function) if the user who sent the request has deleted. But since the user who deleted could send several requests to different users in different places, so I decided that I need to create a function that will be called in firebase database trigger functions and also it will help not to duplicate the same code everywhere .
The function will be approximate such
function adminRemoveAppBadge(userID, dataID, categoryID) {
};
And for example, call it in this function
module.exports = functions.database.ref('/cards/{cardID}/interestedUsers/{interestedUserID}').onWrite(event => {
const currentData = event.data.current;
const prevData = event.data.previous;
const cardID = event.params.cardID;
const interestedUserID = event.params.interestedUserID;
if (currentData.val() && !prevData.val()) {
// value created
return console.log('cardInterestedUserHandler - created');
} else if (!currentData.val() && prevData.val()) {
// value removed
console.log('cardInterestedUserHandler - removed', currentData.val());
const cardRef = admin.database().ref("cards").child(cardID);
const cardRefPromise = cardRef.once("value", function(snap, error) {
if (error) {
return error;
};
if (snap.val()) {
const cardJSON = snap.val();
const cardOwnerID = cardJSON["ownerID"];
if (cardOwnerID) {
const cardOwnerAppBadgesRef = admin.database().ref("userAppBadges").child(cardOwnerID).child("appBadgeModels").orderByChild("dataID").equalTo(cardID);
const cardOwnerAppBadgesRefPromise = cardOwnerAppBadgesRef.once("value", function (cardOwnerAppBadgesRefSnap, error) {
if (error) {
return error;
};
if (cardOwnerAppBadgesRefSnap.val()) {
var deletingPromises = [];
cardOwnerAppBadgesRefSnap.forEach(function(cardOwnerAppBadgesRefSnapChild) {
const appBadgeModelJSON = cardOwnerAppBadgesRefSnapChild.val();
const appBadgeModelID = appBadgeModelJSON["id"];
const senderID = appBadgeModelJSON["senderID"];
if (appBadgeModelID && senderID) {
if (senderID == interestedUserID) {
const cardOwnerAppBadgeRef = admin.database().ref("userAppBadges").child(cardOwnerID).child("appBadgeModels").child(cardOwnerAppBadgeModelID);
const cardOwnerAppBadgeRefPromise = cardOwnerAppBadgeRef.remove();
deletingPromises.push(cardOwnerAppBadgeRefPromise);
// to call
adminRemoveAppBadge
};
} else {
console.log("cardOwnerAppBadgeModelID == null");
};
});
return Promise.all(deletingPromises);
};
});
return Promise.all([cardOwnerAppBadgesRefPromise]);
} else {
return console.log("owner id == null");
};
};
});
return Promise.all([cardRefPromise]);
} else {
return console.log('cardInterestedUserHandler - updated');
};
});
Also functions are in different files. How can I call it in other firebase cloud functions and how do I deploy this function?
Update I tried to do so one of the options as written here and here, but when I tried to do deploy I got an error Cannot find module 'AppBadges/adminRemoveAppBadge.js'.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
exports.adminRemoveAppBadge = function (userID, dataID, categoryID) {
console.log("adminRemoveAppBadge nil");
};
Requested this function so
var adminRemoveAppBadgeModule = require("AppBadges/adminRemoveAppBadge.js");
and call this functions so
adminRemoveAppBadgeModule.adminRemoveAppBadge(cardOwnerID, cardID, 0);
Google Functions are just JS - so normal routes to include code work.
I place my "library" functions in a folder /lib
So my functions folder looks like this:
/functions
/lib
BuildImage.js
SendEmail.js
index.js
package.json
...etc...
within my index.js I just include my code:
const SendMail = require('./lib/SendMail')
const sendMail = new SendMail({
database: db,
mailgun: mailgun
})
exports.sendContactUsMessage = functions.database.ref('/contact-messages/{itemId}').onWrite(sendMail.send(event))
EDIT Added /lib/SendMail.js code:
module.exports = class SendMail {
constructor(config) {
if (!config) {
throw new Error ('config is empty. Must pass database and mailgun settings')
}
if (!config.database) {
throw new Error('config.database is empty. Must pass config.database')
}
if (!config.mailgun) {
throw 'config.mailgun is empty. Must pass config.mailgun'
}
this.database = config.database
this.mailgun = config.mailgun
}
sendEmail (emailData) {
return new Promise((resolve, reject) => {
this.mailgun.messages().send(emailData, (error, body) => {
if (error) {
if (debug) {
console.log(error)
}
reject(error)
} else {
if (debug) {
console.log(body)
}
resolve(body)
}
})
})
}
...
}

Categories