FCM considered external network on Cloud Functions for Firebase - javascript

I am getting an error stating external network is not accessible, which makes sense as I am on the free tier of Firebase. But I thought Firebase services were included in the free tier, and as such, I should be able to use FCM.
Here is the code I am using for my index.js for the functions.
var functions = require('firebase-functions');
var admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
exports.buttonPress = functions.https.onRequest((req, res) => {
let testToken = "TOKEN";
let payload = {
data: {
type: req.body.type
}
};
admin.messaging().sendToDevice(testToken, payload)
.then(function (response) {
...
})
.catch(function (error) {
...
});
});

firebaser here
Billing account not configured. External network is not accessible and quotas are severily limited. Configure billing account to remove these restrictions.
This message now shows up for any Cloud Functions that are invoked from projects that are on the free tier. It doesn't mean that any calls have actively been blocked, just they calls to external services will be blocked for this project.
We're looking if we can get the message removed.

For Free tier account, Firebase has imposed a restriction on accessing external service that is not within google's network.
To get to the root cause of the problem just go to the Firebase console and check your functions's log. The log will show exactly what service or packages you installed is trying to make external HTTP request.

To sent FCM through cloud functions, you can use the code below.
Check log if you are getting right tokens.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendNotification = functions.firestore
.document('/users/{documentId}')
.onWrite((change, context) => {
console.log("DOCUMENT ID : " + context.params.documentId);
//Get all data
const payload = {
notification: {
title: 'Test title!',
body: `${userName} sent you a following request.`
// icon: follower.photoURL
}
};
admin.messaging().sendToDevice(followedFCMToken, payload)
.then(function (response) {
console.log("Push response : " + response);
return response
})
.catch(function (error) {
console.error("Error in sending push");
});
});

Related

Sending FCM messages to web apps through firebase cloud functions

Is it possible to send FCM notifications through Firebase Cloud Functions, when a Firestore data field changes, but for a website, not an app. There is lots of guidance out there for Android and iOS but nothing for simply web apps, outside of sending notifications from the Firebase Console).
I've been trying to find out how to trigger a notification from Cloud Functions but can't find anything useful.
As an example, my database has the following structure:
Collection: users
Documents: documents named using userID
Data Fields: Fields 1 through 5. Field 5 stores the FCM Token. Field 1 stores their status (online, offline, offline pending messages).
I would like to ensure that when Data Field 1 changes (to 'offline pending messages), that the relevant user gets notified (based on the Doc ID).
Edit: adding code below for reference
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/users/{doc}/{Hears}')
.onUpdate(async (change, context) => {
const db = admin.firestore();
db.collection('users').doc(context.params.userId) // get userId
.get()
.then(doc => {
//this is intended to get the FCM token stored in the user's document
const fcmToken = doc.data().usrTkn;
// Notification details
const payload = {
notification: {
title: 'You have a new message.',
body: 'Open your app'
}
};
})
//This should send a notification to the user's device when web app is not in focus.
//FCM is set up in service worker
const response = await admin.messaging().sendToDevice(fcmToken, payload);
console.log(response);
});
Sending messages to a web app is no different from sending it to a native mobile app, so the sending part of guidance you've found is equally applicable. The Firebase documentation even contains an example of sending notifications on a Realtime Database trigger, and doing the same for Firestore would not be much different.
If you're having a specific problem sending messages, I recommend showing what you tried, and what isn't working about it.
Update: your code doesn't work (no matter what sort of device you send the notification to), because you're not handling the asynchronous nature of get() in your code.
The simplest way to fix that is to use await there too, just like you do when calling sendToDevice. So:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/users/{doc}/{Hears}')
.onUpdate(async (change, context) => {
const db = admin.firestore();
const doc = await db.collection('users').doc(context.params.userId).get();
const fcmToken = doc.data().usrTkn;
const payload = {
notification: {
title: 'You have a new message.',
body: 'Open your app'
}
};
const response = await admin.messaging().sendToDevice(fcmToken, payload);
console.log(response);
})
I highly recommend spending some time on learning about asynchronous calls, closures, async/await, and how to debug something like this by adding logging.

Firebase Auth setCustomClaims() not working

I am facing a problem with setting custom claims for Firebase Authentication service's token. I am using Cloud function to set the custom claims for Hasura. The cloud function executes upon new user create event to set the custom claims. Here's my code running in cloud function
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.processSignup = functions.auth.user().onCreate(user => {
// create custom claims for hasura
const hasuraClaims = {
"x-hasura-default-role": "user",
"x-hasura-allowed-roles": ["user"],
"x-hasura-user-id": user.uid
}
// attach claims to user auth object
return admin.auth().setCustomUserClaims(user.uid, hasuraClaims)
.then(_ => {
functions.logger.info('SUCCESS: Custom claims attached');
})
.catch(err => {
console.log('ERROR: ', err);
})
})
In my frontend web page, I am running the following code to get the idToken
// subscribe to user state change
firebase.auth().onAuthStateChanged(async user => {
console.log('Firebase auth state changed');
if (user) {
// User is signed in.
window.User = user;
let idToken = await user.getIdTokenResult();
console.log('idToken: ', idToken);
}
})
I don't know what I'm doing wrong, but the token doesn't contain the custom claims that I've set in my Cloud function processSignup(). I know that the function executed without error because I can check my function logs and find the info entry SUCCESS: Custom claims attached.
Can anyone please help me solve this problem?
Updating claims does not trigger an onAuthStateChanged (the auth state of being logged in or not has not changed, but the users' claims have) and tokens are minted and then used for ~1h.
You are calling getIdTokenResult but not forcing a refresh, try:
let idToken = await user.getIdTokenResult(true);
which will force a new token to be fetched from the server and will (hopefully) include your custom claims.

sending push notification using firebase functions every time a new child is added in firebase realtime database is not working

I am trying to send a push notification every time a child is created with no success.
I am creating a child with 2 token names with a question mark between them and trying to send to those tokens the notification.
to get the tokens from the phones I am using
new FirebaseMessaging().getToken() .
here is the firebase functions code
`
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Cloud Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
exports.onNewMessage = functions.database.
ref('/messages/{pushId}')
.onCreate((snapShot,context)=>{
var str = snapShot.key();
var res = str.split("?");
// Notification details.
const payload = {
notification: {
title: 'title!',
body: `body!`,
click_action: 'FLUTTER_NOTIFICATION_CLICK'
}
};
// Send notifications to all tokens.
admin.messaging().sendToDevice(res[0], payload);
admin.messaging().sendToDevice(res[1], payload);
});` .
This may have many if-thens, but I will describe here the most common sources of errors
1) Did not grant permissions for notifications for iOS/Android platform. For Android it is fine, and relatively easy to receive notifications, but for iOS you need Developer account to do that (on December 2019 it was 99$ per year)
2) I would recommend using topic subscription instead of tokenization (i.e. .getToken()) as it removes burden of following every single sent message manually
For example:
final fbmsg = FirebaseMessaging();
fbmsg.requestNotificationPermissions();
fbmsg.configure(onMessage: (msg) {
print(msg);
return;
}, onLaunch: (msg) {
print(msg);
return;
}, onResume: (msg) {
print(msg);
return;
});
fbmsg.subscribeToTopic('chats');
You can configure onLaunch, onResume, and onMessage behaviors on your own demand
For (1) and (2), a great place to start is following documentation of firebase_messaging library
3) I am not sure about this, but I think a better way to use index.js file could be using the snapshot that you receive (or at least try console.log() of whatever you get to check validity). But if it works for you, just ignore this step :) Below I attach the code from my app with working notifications
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.myFunction = functions.firestore
.document('chats/{message}')
.onCreate((snapshot, context) => {
return admin.messaging().sendToTopic('chats', {
notification: {
title: snapshot.data().username,
body: snapshot.data().text,
clickAction: 'FLUTTER_NOTIFICATION_CLICK'
},
});
});
4) I had hard time with establishing this Firebase Functions feature, also check installation steps for them as well
5) Check how you are trying to send the notification, first try to simulate it from the console, make sure that receiving part works, and then try to create an automated one
Hope it helped!

Request from Firebase Hosting to Firebase Function blocked by CORS

I'm trying to send push notifications between two devices for a tiny prototype. Both of them are Vue.js apps with firebase SDK integrated, so to implement a push notification flow I deployed a firebase function, but when I call it from any of devices, a CORS error is received as a response.
Both devices (mobile and desktop) have the same client code and knows the token of each other (storage into a firebase real-time database).
The function only uses firebase messaging to send the push notification.
Firebase function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({ origin: true });
admin.initializeApp();
exports.notification = functions.https.onRequest((req, res) => {
return cors(req, res, () => {
if (req.method === "POST") {
return admin.messaging().send(notification)
.then(result => {
console.log(result);
res.status(200).send("ok")
})
.catch(err => res.status(500).send(err));
} else {
return res.status(400).send("Method not allowed");
}
});
});
Client code:
send(notification, token) {
return fetch("https://[zone]-[project].cloudfunctions.net/notifications", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token, notification })
});
}
And the error is:
Access to fetch at 'https://[zone]-[project].cloudfunctions.net/notifications' from origin 'https://[project].web.app' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
Could you use The functions.https.onCall trigger?
See https://firebase.google.com/docs/functions/callable .
The Cloud Functions for Firebase client SDKs let you call functions directly from a Firebase app. To call a function from your app in this way, write and deploy an HTTPS Callable function in Cloud Functions, and then add client logic to call the function from your app.
The URI that I used to invoke the function was wrong. It's solved. Sorry about that question.

gmail.users.watch fails to send test message to PubSub with a DwD service account

I'm trying to setup gmail.users.watch but am getting a 403 error:
Error sending test message to Cloud PubSub projects/project-id/topics/topic-id : User not authorized to perform this action.
Authentication is working using the GOOGLE_APPLICATION_CREDENTIALS approach and the downloaded credentials json file.
The following code works correctly which supports my hypothesis that the authentication is generally working:
const pubsub = PubSub();
const topic = pubsub.topic('topic-id');
const subscription = pubsub.subscription('subscription-id');
topic.exists()
.then(data => {
console.log(data);
return subscription.exists();
})
.then(data => {
console.log(data);
return subscription.pull()
})
.then(data => {
data[1].receivedMessages.forEach(d => console.log(d));
return topic.publish('Hello, world!');
})
.then(data => {
console.log(data)
})
.catch(err => console.log(err));
No errors from that code. However the following code throws the 403 error described above:
const authParams = {
subject: userId,
scopes: [
'https://mail.google.com/',
'https://www.googleapis.com/auth/pubsub'
]
};
gauth.getAuth(authParams)
.then(authClient => {
const params = {
auth: authClient,
userId: 'me',
resource: {
topicName: <topic-id>
}
};
return new Promise((resolve, reject) => {
gmail.users.watch(params, (err, response) => {
if (err) {
console.log(err);
reject(err);
return;
}
resolve(response);
});
});
})
.then(response => {
console.log(response);
});
gauth.getAuth is a simple wrapper around getApplicationDefaultGoogle Auth Library for Node.js.
The G Suite domain security Client Access is configured with the Client ID of the service account against the scopes needed:
https://www.googleapis.com/auth/pubsub, https://mail.google.com/
As the native cloud pub/sub stuff works I think the service account has all of the correct permissions configured on the console so I'm a bit at a loss as to why the gmail call is failing.
UPDATE:
Service Account has the following permissions on Google Cloud Console:
Project: (Does it need more than this for the gmail stuff?)
Service Account Actor
PubSub Admin
Topic:
Owner
Subscription:
Owner
When making the gmail call the service account is delegating to a 'subject'. I've added permissions for that subject userId to the Google Cloud Console:
Project:
Owner
Topic:
Owner
Subscription:
Owner
You need to add permissions so messages can be publish to your subscription.
Go to Google Cloud console, select the subscription and then Permissions.
In the Add members section, type allAuthenticatedUsers, select the Pub/Sub publisher role and click Add
Does it help?
Update (2017/05/09):
I'm editing my answer in order to be more accurate about what's needed.
According to the Gmail API documentation (see the Grant publish rights on your topic section):
the permission needs to be set at the topic level
the member is: serviceAccount:gmail-api-push#system.gserviceaccount.com
the role is: Pub/Sub Publisher

Categories

Resources