Hi I am trying to create a chat feature between multiple users, but each chat must be only shared between the two members that are talking, I have done chats in the past that have been between an administrator and a user but never between just two users and I am unsrure how to do this. Here is what I have tried.
setup(){
const { ChatMess, error } = getChatMess(firebaseAuth.currentUser.uid);
const route = useRoute()
const { user } = getUser()
const message = ref('')
const Activate = ref(false)
const NewMessage = ref(true)
const NewMessage2 = ref(false)
const handleSubmit = async () => {
const NewChat = {
name: user.value.displayName,
message: message.value,
createdAt: timestamp(),
}
const NewMessages = {
NewMessage: NewMessage.value,
}
const NewMessages2 = {
NewMessage: NewMessage2.value,
}
message.value = ''
const res = await projectFirestore.collection('Subscribed').doc(firebaseAuth.currentUser.uid).collection('messages').doc(route.params.id).set({
name: user.value.displayName,
message: message.value,
createdAt: timestamp(),
}, { merge: true })
const messages = ref(null)
return { handleSubmit, message }
}
}
</script>
This above is the script that adds the values like name of who sent the message and a timestamp to order them and the message itself I was then going to add another one to the route.params.id doc which is just that users uid so that both users would have the fields in a document with the corresponding persons uid and I would be able to just filter it out using route.params.id.
I will show you how I am retrieving the messages collection and a screenshot of how the database looks so that this makes more sense.
import { projectFirestore } from "../Firebase/Config";
import { ref, watchEffect } from "vue"
const getChatMess = (id) => {
const ChatMess = ref(null)
const error = ref(null)
let collectionRef = projectFirestore.collection('Subscribed').doc(id).collection('messages').orderBy('createdAt')
const unsub = collectionRef.onSnapshot(snap => {
console.log(snap)
let results = []
snap.docs.forEach(doc => {
// must wait for the server to create the timestamp & send it back
// we don't want to edit data until it has done this
doc.data().createdAt && results.push({...doc.data(), id: doc.id})
});
// update values
ChatMess.value = results
error.value = null
console.log(ChatMess)
}, err => {
console.log(err.message)
ChatMess.value = null
error.value = 'could not fetch the data'
})
watchEffect((onInvalidate) => {
// unsub from prev collection when watcher is stopped (component unmounted)
onInvalidate(() => unsub());
});
return { ChatMess, error }
}
export default getChatMess
And here below is the firestore database screen shot so you can better understand how everything is laid out.
As you can see the messages collection has a doc with the uid which is the id of the user they are messaging.
The problem with the first piece of code I showed above is that doing it this way will overwrite all previous messages when a new message is sent, I have also tried just set({NewChat}), {merge:true }) but unfortunately that puts the field: name, message and createdAt within a map field which is a problem because there is no way to reach in and get the fields within this type of field you just have to get the whole lot which doesn't work or v-for which I need to use to display the message.
Some ideas I had wear put the documents within there own collection using the uid as the name as the collection path and then have a new document added for each message that was sent but I then wasn't able to pass the rout.params.id and I wasn't able to filter the messages so I just got the messages from that user since the id of that collection will be different each time.
Another idea I had which I think might be the right option is writing some specific rules for the messages collection in the firestore rules on firebase so that then the users could only read the documents if there was both there uid and the uid of the person they were conversing with. I would have this so that two new fields which get the route.params.id and the firebaseAuth.currentUser.uid are added to the document with the message, name and timstamp to filter this.
The problem is I honestly have no idea how to write rules which would accomplish this and although I am able top find a tone of tutorials regarding a chat between users I can't find any that have multiple users with multiple conversations that must only be shared between the two messagers.
Does anyone know how to do this through these two ways I think it could be done or have any other ideas of how I could accomplish this?
I was able to use my first idea and filter the messages by the id within each document and added to the collection within both users messages collection.
Here is the vue page revised where I display the chats javascript.
setup(){
const route = useRoute() // MAKE SURE THIS IS ALWAYS DECLARED BEFORE EVERYTHING SPENT AGES TO FIGURE THIS OUT!
const { ChatMess, error } = getChatMess(
firebaseAuth.currentUser.uid,
['id', '==', route.params.id]
);
const { user } = getUser()
const message = ref('')
const Activate = ref(false)
const NewMessage = ref(true)
const NewMessage2 = ref(false)
const handleSubmit = async () => {
const NewChat = {
name: user.value.displayName,
message: message.value,
createdAt: timestamp(),
id: route.params.id
}
const NewChat2 = {
name: user.value.displayName,
message: message.value,
createdAt: timestamp(),
id: firebaseAuth.currentUser.uid
}
message.value = ''
const res = await projectFirestore.collection('Subscribed').doc(firebaseAuth.currentUser.uid).collection('messages').add(NewChat)//originally route.params
const res3 = await projectFirestore.collection('Subscribed').doc(route.params.id).collection('messages').add(NewChat2)//originally auth
const res4 = await
}
const messages = ref(null)
return { handleSubmit, Activate, message, ChatMess, messages }
}
}
It is important to remember to have the route declared before ChatMess otherwise you will get the error cannot access route before initialisation.
Here below is the revised javascript page which gets the docs from the messages collection.
import { projectFirestore } from "../Firebase/Config";
import { ref, watchEffect } from "vue"
const getChatMess = (id, query) => {
const ChatMess = ref(null)
const error = ref(null)
let collectionRef = projectFirestore.collection('Subscribed').doc(id).collection('messages').orderBy('createdAt') // Mybe have a different composable if they want to see more click it? Have that one without limittolast
if (query) {
collectionRef = collectionRef.where(...query)
}
const unsub = collectionRef.onSnapshot(snap => {
console.log(snap)
let results = []
snap.docs.forEach(doc => {
// must wait for the server to create the timestamp & send it back
// we don't want to edit data until it has done this
doc.data().createdAt && results.push({...doc.data(), id: doc.id})
});
// update values
ChatMess.value = results
error.value = null
console.log(ChatMess)
}, err => {
console.log(err.message)
ChatMess.value = null
error.value = 'could not fetch the data'
})
watchEffect((onInvalidate) => {
// unsub from prev collection when watcher is stopped (component unmounted)
onInvalidate(() => unsub());
});
return { ChatMess, error }
}
export default getChatMess
I hope this helps anyone who is trying to do a private chat feature like I was, thanks.