I am trying to configure the index.js file in javascript for push notifications for firebase iOS. here is my code in question:
exports.newsFRA = functions.database
.ref('news/FRA/{uid}')
.onCreate((snapshot, context) => {
let newsItemFRA = snapshot.val()
sendNotificationFRA(newsItemFRA)
})
function sendNotificationFRA(newsItemFRA) {
let title = newsItemFRA.title
let message = newsItemFRA.message
let payload = {
notification: {
title: 'FRA News',
body: title,
sound: 'default'
}
}
console.log(payload)
let topic = 'FRA'
admin.messaging().sendToTopic(topic, payload)
}
exports.newsLHR = functions.database
.ref('news/LHR/{uid}')
.onCreate((snapshot, context) => {
let newsItemLHR = snapshot.val()
sendNotificationLHR(newsItemLHR)
})
function sendNotificationLHR(newsItemLHR) {
let title = newsItemLHR.title
let message = newsItemLHR.message
let payload = {
notification: {
title: 'LHR News',
body: title,
sound: 'default'
}
}
console.log(payload)
let topic = 'LHR'
admin.messaging().sendToTopic(topic, payload)
}
the 2 function are the same , what change is the topic and the matching name. I have 21 of the same.
I am sure there is a better way to do it by detecting in firebase the content of the authenticated current user 's child called "base": and using the value (FRA , CDG , ORD etc) to define the topic. so I dont have to repeat the same function for each available base.
Hope I am not too confusing and I greatly appreciate any help. thank you
You can use multiple wildcards and take the value of the parameter to determine what to do. For example, this is valid:
exports.news = functions.database
.ref('news/{x}/{uid}')
.onCreate((snapshot, context) => {
const x = context.params.x
let newsItem = snapshot.val()
sendNotification(x, newsItem)
})
Related
I have a Cloud Firestore DB with the following structure:
users
[uid]
name: "Test User"
posts
[id]
content: "Just some test post."
timestamp: (Dec. 22, 2017)
uid: [uid]
There is more data present in the actual DB, the above just illustrates the collection/document/field structure.
I have a view in my web app where I'm displaying posts and would like to display the name of the user who posted. I'm using the below query to fetch the posts:
let loadedPosts = {};
posts = db.collection('posts')
.orderBy('timestamp', 'desc')
.limit(3);
posts.get()
.then((docSnaps) => {
const postDocs = docSnaps.docs;
for (let i in postDocs) {
loadedPosts[postDocs[i].id] = postDocs[i].data();
}
});
// Render loadedPosts later
What I want to do is query the user object by the uid stored in the post's uid field, and add the user's name field into the corresponding loadedPosts object. If I was only loading one post at a time this would be no problem, just wait for the query to come back with an object and in the .then() function make another query to the user document, and so on.
However because I'm getting multiple post documents at once, I'm having a hard time figuring out how to map the correct user to the correct post after calling .get() on each post's user/[uid] document due to the asynchronous way they return.
Can anyone think of an elegant solution to this issue?
It seems fairly simple to me:
let loadedPosts = {};
posts = db.collection('posts')
.orderBy('timestamp', 'desc')
.limit(3);
posts.get()
.then((docSnaps) => {
docSnaps.forEach((doc) => {
loadedPosts[doc.id] = doc.data();
db.collection('users').child(doc.data().uid).get().then((userDoc) => {
loadedPosts[doc.id].userName = userDoc.data().name;
});
})
});
If you want to prevent loading a user multiple times, you can cache the user data client side. In that case I'd recommend factoring the user-loading code into a helper function. But it'll be a variation of the above.
I would do 1 user doc call and the needed posts call.
let users = {} ;
let loadedPosts = {};
db.collection('users').get().then((results) => {
results.forEach((doc) => {
users[doc.id] = doc.data();
});
posts = db.collection('posts').orderBy('timestamp', 'desc').limit(3);
posts.get().then((docSnaps) => {
docSnaps.forEach((doc) => {
loadedPosts[doc.id] = doc.data();
loadedPosts[doc.id].userName = users[doc.data().uid].name;
});
});
After trying multiple solution I get it done with RXJS combineLatest, take operator. Using map function we can combine result.
Might not be an optimum solution but here its solve your problem.
combineLatest(
this.firestore.collection('Collection1').snapshotChanges(),
this.firestore.collection('Collection2').snapshotChanges(),
//In collection 2 we have document with reference id of collection 1
)
.pipe(
take(1),
).subscribe(
([dataFromCollection1, dataFromCollection2]) => {
this.dataofCollection1 = dataFromCollection1.map((data) => {
return {
id: data.payload.doc.id,
...data.payload.doc.data() as {},
}
as IdataFromCollection1;
});
this.dataofCollection2 = dataFromCollection2.map((data2) => {
return {
id: data2.payload.doc.id,
...data2.payload.doc.data() as {},
}
as IdataFromCollection2;
});
console.log(this.dataofCollection2, 'all feeess');
const mergeDataFromCollection =
this.dataofCollection1.map(itm => ({
payment: [this.dataofCollection2.find((item) => (item.RefId === itm.id))],
...itm
}))
console.log(mergeDataFromCollection, 'all data');
},
my solution as below.
Concept: You know user id you want to get information, in your posts list, you can request user document and save it as promise in your post item. after promise resolve then you get user information.
Note: i do not test below code, but it is simplify version of my code.
let posts: Observable<{}[]>; // you can display in HTML directly with | async tag
this.posts = this.listenPosts()
.map( posts => {
posts.forEach( post => {
post.promise = this.getUserDoc( post.uid )
.then( (doc: DocumentSnapshot) => {
post.userName = doc.data().name;
});
}); // end forEach
return posts;
});
// normally, i keep in provider
listenPosts(): Observable<any> {
let fsPath = 'posts';
return this.afDb.collection( fsPath ).valueChanges();
}
// to get the document according the user uid
getUserDoc( uid: string ): Promise<any> {
let fsPath = 'users/' + uid;
return this.afDb.doc( fsPath ).ref.get();
}
Note: afDb: AngularFirestore it is initialize in constructor (by angularFire lib)
If you want to join observables instead of promises, use combineLatest. Here is an example joining a user document to a post document:
getPosts(): Observable<Post[]> {
let data: any;
return this.afs.collection<Post>('posts').valueChanges().pipe(
switchMap((r: any[]) => {
data = r;
const docs = r.map(
(d: any) => this.afs.doc<any>(`users/${d.user}`).valueChanges()
);
return combineLatest(docs).pipe(
map((arr: any) => arr.reduce((acc: any, cur: any) => [acc].concat(cur)))
);
}),
map((d: any) => {
let i = 0;
return d.map(
(doc: any) => {
const t = { ...data[i], user: doc };
++i;
return t;
}
);
})
);
}
This example joins each document in a collection, but you could simplify this if you wanted to just join one single document to another.
This assumes your post document has a user variable with the userId of the document.
J
I am writing an android application where I need to send a notification based on some condition.
For example, when notiType = home then send other message in notification. If notiType = inBetween then send another message
I have written the cloud function for this but getting an error while deploying.
Here is the cloud function :
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
/* Listens for new messages added to /messages/:pushId and sends a notification to users */
exports.pushNotification = functions.database.ref('/Notifications/{user_id}/{notification_id}').onWrite(event => {
console.log('Push notification event triggered');
/* Grab the current value of what was written to the Realtime Database
*/
const userId = event.params.user_id;
const notificationId = event.params.notification_id;
const deviceToken = admin.database().ref(`Notifications/${userId}/${notificationId}/deviceToken`).once('value');
const childName = admin.database().ref(`Notifications/${userId}/${notificationId}/childName`).once('value');
const notificationType = admin.database().ref(`Notifications/${userId}/${notificationId}/type`).once('value');
return Promise.all([deviceToken, childName, notificationType]).then(result => {
const token = result[0].val();
const name = result[1].val();
const type = result[2].val();
/* Create a notification and data payload. They contain the notification information, and message to be sent respectively */
const payload;
switch (type) {
case "home":
payload = {
notification: {
title: 'App Name',
body: `${name} is reached at home`,
sound: "default"
}
};
break;
case "between":
payload = {
notification: {
title: 'App Name',
body: `${name} stuck on the way for some reason`,
sound: "default"
}
};
break;
case "school":
payload = {
notification: {
title: 'App Name',
body: `${name} reached at school`,
sound: "default"
}
};
break;
};
return admin.messaging().sendToDevice(token, payload).then(response => {
return null;
});
});
});
Getting this error :
Please correct me where I am going wrong. Using Firebase -tools version 5.0.1
JavaScript is telling you this line is invalid:
const payload;
You can't declare a const variable without also giving it a value immediately. Since you are conditionally giving it a value later, perhaps you should use let payload; instead.
I want to send a notification to a specific device so I write this function and its work right but I got undefined in the username
Logs output:
Get this
after: { '-LhjfwZeu0Ryr6jYRq5r': { Price: '888', date: '2019-6-19', description: 'Ghh', id: 50, nameOfProblem: 'Vbh', providerName: 'Loy', providerService: 'Carpenter', statusInfo: 'Incomplete', time: '15:22', username:"devas" }}
And the username is undefined
Here is the function
exports.sendPushR = functions.database.ref('/request/{pid}/{uid}/orders')
.onWrite(async (snapshot, context) => {
const registrationTokens = "------";
const providerId = context.params.pid;
const userId = context.params.uid;
const event = context.params;
console.log("event", event);
console.log(`New Order from ${userId} to ${providerId}`);
const afterData = snapshot.after.val(); // data after the write
const username = snapshot.after.val().username;
console.log(afterData);
console.log(username);
const payload = {
notification: {
title: 'Message received',
body: `You received a new order from ${username} check it now! `,
sound: "default",
icon: "default",
}
};
try {
const response = await admin.messaging().sendToDevice(registrationTokens, payload);
console.log('Successfully sent message:', response);
}
catch (error) {
console.log('Error sending message:', error);
}
return null;
});
It looks like the code you wrote is meant to run when a new order is added to the database. But you've declared it to trigger like this:
exports.sendPushR = functions.database.ref('/request/{pid}/{uid}/orders')
.onWrite(async (snapshot, context) => {
This means that the code instead triggers whenever anything is written under the orders node for a user. To trigger only when an order is written under that orders node, define your trigger as:
exports.sendPushR = functions.database.ref('/request/{pid}/{uid}/orders/{orderid}')
.onWrite(async (snapshot, context) => {
The difference above is that the path now includes {orderid} meaning that it triggers one level lower in the tree, and your snapshot.after will no longer contain the -L level.
Since you actually only seem to care about when an order gets created, you can also only trigger on that (meaning your function won't get called when an order gets updated or deleted). That'd be something like this:
exports.sendPushR = functions.database.ref('/request/{pid}/{uid}/orders/{orderid}')
.onCreate(async (snapshot, context) => {
...
const afterData = snapshot.val();
const username = snapshot.val().username;
console.log(afterData);
console.log(username);
...
});
Here we again trigger on the lower-level in the JSON. But since we now trigger onCreate, we no longer have a before and after snapshot, and instead just do snapshot.val() to get the data that was just created.
Since the object you are retrieving has a generated member you could use a for-in loop to retrieve the value.
const object = snapshot.after.val()
for(const key in object) {
if (object.hasOwnProperty(key)) {
const element = object[key];
if(element.username) {
console.log(element.username);
break;
}
}
}
I am trying to map out my schema from Firebase Firestore. I used Flamelink as a headless CMS to make things easier, however I am facing a limitation.
The image storage location is separate from the data that i'm pulling in from my schema. To remedy this I created some nested Queries to find the correct path of the storage location and generate the URL for the image. I need that image URL to be placed back into the respected object somewhere.
Currently it's just adding it to the overall array not the objects already within it. The first console log is the file reference in the correct order, the second console log is the correct URL's I need into the original object. However, these seem to not be in the correct order. I am fairly new to react and ES6 so I could be over complicating things. Essentially I just want the output of the second Console.log into my ThumbnailURL position for each object in the respective place.
I have tried to just create a new Array of objects and map them in my front end, however that causes issues because they are not in the same object.
const storage = firebase.storage().ref('flamelink/media/sized/240/')
class ContentUpdates extends React.Component {
constructor(){
super();
this.ref = firebase.firestore().collection('fl_content').where('_fl_meta_.schema', '==', 'collateral');
this.unsubscribe = null;
this.state = {
collateral: [],
}
}
onCollectionUpdate = (querySnapshot) => {
const collateral = [];
querySnapshot.forEach((doc) => {
const { name, image, thumbnail, thumbnailURL, img} = doc.data();
collateral.push({
key: doc.id,
doc, // DocumentSnapshot
name,
image,
img,
thumbnail: image[0].id,
thumbnailURL: firebase.firestore().collection(`fl_files`).where(`id`, '==', `${image[0].id}`).get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
const { file } = doc.data();
console.log('this is in the correct order', `${file}`);
storage.child(`${file}`).getDownloadURL().then((url) => {
const fileurl = url
console.log('this is the value I need mapped to thumbnailURL', fileurl)
collateral.push({thumbnailURL: fileurl})
})
})
})
});
});
this.setState({
collateral
});
}
componentDidMount() {
this.unsubscribe = this.ref.onSnapshot(this.onCollectionUpdate);
}
I want each objected in collateral to go through the necessary queries to return the fileurl back to it's respected collateral object.
UPDATE: I cleaned up the function a bit. Issue now is that it's going through two loops and not returning the values but only a promise. Very close to solving it however. Will update soon.
onCollectionUpdate = (querySnapshot) => {
const collateral = [];
querySnapshot.forEach((doc) => {
const { name, image, thumbnail, thumbnailURL, img} = doc.data();
const thumbnailImg = this.getThumbnailUrl(image[0].id);
let obj = {
key: doc.id,
doc, // DocumentSnapshot
name,
image,
img,
thumbnail: image[0].id,
thumbnailURL: thumbnailImg
}
collateral.push(obj);
});
this.setState({
collateral
});
}
async getThumbnailUrl(imgRef) {
console.log('your in', imgRef)
let snapshot = await firebase.firestore().collection(`fl_files`).where(`id`, '==', imgRef).get();
console.log(snapshot)
let snapVal = snapshot.forEach( async(doc) => {
const { file } = doc.data();
console.log('this is in the correct order', `${file}`);
let downloadURL = await storage.child(`${file}`).getDownloadURL()
console.log('this is the value I need mapped to thumbnailURL', downloadURL)
return downloadURL;
})
return snapVal;
}
componentDidMount() {
this.unsubscribe = this.ref.onSnapshot(this.onCollectionUpdate);
}
There are a-lot of answers on Stack Overflow about how to get the uid once you do an onCreate() in cloud functions, but none to use the uid as a path. In my situation I want to monitor when a particular user gains a follower. I then what to alert that user that they have gained a follower.
Here is my code:
console.log('uid valll ' + exports.auth.uid)
exports.announceFollower = functions.database
.ref('users/{exports.auth.uid}/followers/{followerId}')
.onCreate(event => {
let follower = event.data.val()
sendNotification(follower)
})
function sendNotification(follower){
let uid = follower.val
let payload = {
notification: {
title: 'New Follower',
sound: 'default'
}
}
console.log(payload)
let topic = functions.database.ref('users/{exports.auth.uid}/followerTopicId')
console.log('topic val' + topic)
admin.messaging().sendToTopic(topic.val , payload)
}
where it says exports.auth.uid I am attempting to grab to uid of the currently logged in user.
I try that using this function :
exports.auth = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const tokenId = req.get('Authorization').split('Bearer ')[1];
return admin.auth().verifyIdToken(tokenId)
.then((decoded) => res.status(200).send(decoded))
.catch((err) => res.status(401).send(err));
});
});
Yet I get the message in Firebase cloud func console :
uid valll undefined
After reviewing the documentation here:
https://firebase.google.com/docs/functions/database-events
It still is unclear. What is the appropriate way to access the uid of a currently logged in user For path usage?