Unable to fetch members of a group (XMPP) - ios

When a specific room is created by me, I can fetch members in that group. But when the group is created by someone else, I can't fetch members in that group.
The didDiscoverRooms method is initially called and then within it the members are fetched like so...
func xmppMUC(_ sender: XMPPMUC, didDiscoverRooms rooms: [Any], forServiceNamed serviceName: String) {
print("XMPPRoom: didDiscoverRooms: \(rooms)")
if let elements = rooms as? [DDXMLElement] {
for element in elements {
print("Name: \(String(describing: element.attributeStringValue(forName: "name")))")
print("JID: \(String(describing: element.attributeStringValue(forName: "jid")))")
if let name = element.attributeStringValue(forName: "jid"), let roomJID = XMPPJID(string: name) {
let roomStorage = XMPPRoomMemoryStorage()
let room = XMPPRoom(roomStorage: roomStorage!, jid: roomJID, dispatchQueue: DispatchQueue.main)
room.addDelegate(self, delegateQueue: DispatchQueue.main)
room.activate(self.xmppStream)
room.fetchConfigurationForm()
room.fetchMembersList()
}
}
}
}
The room.fetchMembersList() in turn calls this delegate method..
func xmppRoom(_ sender: XMPPRoom, didFetchMembersList items: [Any]) {
print("XMPPRoom: \(sender.roomJID)")
print("XMPPRoom: didFetchMembersList: \(items)")
}
The first print statement gives the room name and the second print statement gives the room members. But this only works if the room is created by me. If the room is created by someone else then I can't get the members...

Fetching the member list (and the admin and owner list) usually requires owner permissions because it will leak the Jabber ID in anonymous rooms. Some modern servers (recent ejabberd versions for example) make an exception for non-anonymous and also allow regular participants to fetch that list.

Related

Why are previous views that have been popped from the stack still active

I have this truck driving app in swiftUI where I use fire base to log users in and out. The problem is that when I sign in with one user, and all it’s fire base functionalities are triggered, After I log out from the current user and into a new user, the functionality of the old user is still playing out. I think it might have something to do with the firebase functions being in an onAppear method. I am not sure though.
This is the firebase code. I dont think what Im querying has any relation to the solution so I wont explain it but if you think it does than please let me know.
.onAppear(perform: {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge]) { (_,_) in
}
myDrivers = []
getEmails()
db.collectionGroup("resources").whereField("control", isEqualTo: true).addSnapshotListener { (snapshot, err) in
if myDrivers.count == 0{}
else{
if err != nil{print("Error fetching motion status: \(err)")}
if ((snapshot?.documents.isEmpty) != nil){}
// Gets all the driverLocation documents
for doc in snapshot!.documents{
if myDrivers.contains(doc.reference.parent.parent!.documentID){
let isDriving = doc.get("isDriving") as! Bool
let isStopped = doc.get("isStopped") as! Bool
let notified = doc.get("notified") as! Bool
if (isDriving == false && isStopped == false) || notified{}
else{
// Gets the name of the drivers
doc.reference.parent.parent?.getDocument(completion: { (snapshot, err) in
if err != nil{print("Error parsing driver name: \(err)")}
let firstName = snapshot?.get("firstName") as! String
let lastName = snapshot?.get("lastName") as! String
self.notifiedDriver = "\(firstName) \(lastName)"
})
// Logic
if isDriving{
send(notiTitle: "MyFleet", notiBody: "\(notifiedDriver) is back on the road.")
showDrivingToast.toggle()
doc.reference.updateData(["notified" : true])
}else if isStopped{
send(notiTitle: "MyFleet", notiBody: "\(notifiedDriver) has stopped driving.")
showStoppedToast.toggle()
doc.reference.updateData(["notified" : true])
}
}
}
else{}
}
}
}
})
Listeners don't die when a view controller is left. They remain active.
It's important to manage them through handlers for specific listeners as a view closes or the user navigates away. Here's how to remove a specific listener
let listener = db.collection("cities").addSnapshotListener { querySnapshot, error in
// ...
}
and then later
// Stop listening to changes
listener.remove()
Or, if your user is logging out, you can use removeAllObservers (for the RealtimeDatabase) to remove them all at one time, noting that
removeAllObservers must be called again for each child reference where
a listener was established
For Firestore, store the listeners in a listener array class var and when you want to remove them all, just iterate over the array elements calling .remove() on each.
There's additional info in the Firebase Getting Started Guide Detach Listener

Adding a custom object to an array in Firestore

I am making an ordering app for customers to order their specific specs. When the user logs in they can go to a tab that contains a tableview with all their specs, once they click on a cell it will take them to a new view controller that will display more information on the spec. Once on this view controller they will have the ability to add x amount of pallets/rolls/etc of that item. I am able to add the spec to Firestore, but I cannot get it to an array in Firestore which I need. My goal is that on anther tab the user can view all the current specs they are trying to order until they hit submit. I am currently using the user.uid to get to that specific customers orders inside Firestore.
Code:
#IBAction func addPallet(_ sender: Any) {
// Get the current user
let user = Auth.auth().currentUser
if let user = user {
_ = user.uid
}
if spec != nil {
// Get the qty ordered for that spec
let totalQty: Int? = Int(palletsToAddTextField.text!)
let qty = spec!.palletCount * totalQty!
let specToAdd = Spec(specNumber: spec!.specNumber,
specDescription: spec!.specDescription,
palletCount: spec!.palletCount,
palletsOrdered: qty)
orderedArray.append(specToAdd)
let specAdded: [String: Any] = [
"SpecDesc": spec!.specDescription,
"SpecNum": spec!.specNumber,
"PalletCount": spec!.palletCount,
"PalletsOrder": qty
]
db.collection("orders").document(user?.uid ?? "error").setData(specAdded) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
}
code for spec:
struct Spec: Codable {
// Properties
var specNumber: String
var specDescription: String
var palletCount: Int
var palletsOrdered = 0
enum CodingKeys: String, CodingKey {
case specNumber
case specDescription
case palletCount
case palletsOrdered
}
}
I need something added like the below picture. The user will add x amount of pallets, then going to the next spec they want and add it to the array in Firestore as well.
Ok i guess i get what you want to do. Try:
db.collection("orders").document(userID).setData(["allMyData" : myArray])
"allMyData" will be the name of the field in which you want to save your array and myArray would be your array (specAdded). Thats what you are looking for?
If the document already exists you will want to use .updateData instead of .setData to keep all other fields that might already exist in that specific doc.
Kind regards

GetStream get activities is getting them all instead of specific ID

I'm working on a function that connects to GetStream in order to get the activities for a chosen feed group. The problem is that GetStream is returning all the activities inside the feed group as opposed to the ones for a specific slug. In MySQL terms it's ignoring the WHERE UUID = uuid :)
Here's the code I'm currently using which returns all activities, I've tried setting the elementFeed to nil before setting it but that doesn't work either.
func getStream(uuid: String, completion: #escaping([StreamActivity], Bool, APPError?) -> Void) {
let group = DispatchGroup()
group.enter()
var activities = [StreamActivity]()
elementFeed = nil
elementFeed = Client.shared.flatFeed(feedSlug: "element", userId: uuid)
elementFeed?.get(typeOf: StreamActivity.self, enrich: false, pagination: .limit(20), ranking: "", includeReactions: [], completion: { results in
if let foundActivities = results.value?.results {
activities.append(contentsOf: foundActivities)
group.leave()
} else {
completion([], false, .GetDiscussions)
group.leave()
}
})
group.notify(queue: .main) {
self.isUserFollowing(uuid: uuid) { (following) in
completion(activities, following, nil)
}
}
}
Here's the code used to create the activity:
func addActivity(type: ActivityType, body: String?, uuid: String, completion: #escaping(Bool, APPError?) -> Void) {
var feed: FeedId?
var originFeed: FeedId?
switch type {
case .comment:
feed = FeedId(feedSlug: "element", userId: uuid)
case .reply:
originFeed = FeedId(feedSlug: "comment", userId: uuid)
default:
break
}
let activity = StreamActivity(actor: StreamUser.current!, verb: type.rawValue, object: "element:\(uuid)", body: body!)
activity.feedIds = [feed!]
elementFeed = Client.shared.flatFeed(feedSlug: "element", userId: uuid)
elementFeed?.add(activity, completion: { (result) in
if result.error == nil {
completion(true, nil)
} else {
completion(false, .PostComment)
}
print("GETSTREAM: Activity added:\(result)")
})
}
Stream Feed API does not allow you to read activities outside a feed. Feeds are materialised so when you perform a call like this
Client.shared.flatFeed(feedSlug: "element", userId: uuid)
You are not performing a query (ie. SELECT * FROM activities WHERE slug = ? AND userId = ?) but you are reading the activities that were added (directly, via to targets or from followed feeds) to the element:$uuid feed.
If a feed contains wrong activities it is because something is going wrong with the logic used to add activities or to create follow relationships.
When you read a feed, Stream adds information about the origin of each activity; such information is stored in a field called "origin". If the origin field is missing / null it means the activity was added to the feed directly; if the activity was added via follow relationship, then the origin field will be set to the feed id from where the activity originated.
For example:
If element:x follows user:z; all activities user Z adds are propagated to element:x and will have origin = "user:z" on that feed.
You can use the origin field to get some more information about why the activity is in a feed; that should be very useful information to find out where the integration problem is.

How to check if my contact list members are registered with my app just like whatsapp does?

I am creating a chat app, for which I need to check each of my contact list members if they are registered with my app and show the registered members list in my app.
Currently I am calling an API to check every number one by one. In case of 800 contacts it is getting called 800 times. I know this is not best way to do it. So, can some one please help me out and suggest me to do it in better way?
Below is my code:
func createContactNumbersArray() {
for i in 0...self.objects.count-1 {
let contact:CNContact! = self.objects[i]
if contact.phoneNumbers.count > 0 {
for j in 0...contact.phoneNumbers.count-1 {
print((contact.phoneNumbers[j].value).value(forKey: "digits")!)
let tappedContactMobileNumber = (contact.phoneNumbers[j].value).value(forKey: "digits")
let phoneNo = self.separateISDCodeMobileNo(mobileNo:tappedContactMobileNumber as! String)
contactNumbersArray.append(phoneNo.1)
}
}
}
callGetAppUserDetailService(mobileNumber: self.contactNumbersArray[currentIndex] as! String)
}
I am doing this whole process in the background and refreshing the member list on front in current scenario.
I want to make the whole process as fast as Whatsapp.
There is no way to do it without back-end modifications. So you need to work closely with your back-end engineer and build an API for this.
Here is an advise how you can build this API:
To have 2 APIs:
1) Upload the whole user address book to the back-end in a single request, something like:
let addressBook = NSMutableOrderedSet()
let contact1 = AddressBookContact()
contact1.name = "Jony Ive"
contact1.phone = "1-800-300-2000"
addressBook.add(contact1)
let contact2 = AddressBookContact()
contact2.name = "Steve Why"
contact2.phone = "412739123123"
addressBook.add(contact2)
let deviceUDID = nil
Request.uploadAddressBook(withUdid: deviceUDID, addressBook: addressBook, force: false, successBlock: { (updates) in
}) { (error) in
}
2) As a next step - retrieve a list of already registered users with phones from your address book, something like this:
let deviceUDID = nil
Request.registeredUsersFromAddressBook(withUdid: nil, successBlock: { (users) in
}) { (error) in
}
so now you can show it in UI
Found this example here https://developers.connectycube.com/ios/address-book
In fact,you can do little on client.
The normal way is: Server provide an interface that support check a phonenumber array, and return the phonenumbers has registered on server.
I think you need to save user's phone number to your database when user registers your app. Then you can find out contacts which have already used your chat app.

How to check if PFRelation in Parse contains exactly same array in Swift

I am making a chat Application in ios through parse server. I have made a MessageRoom collection which has many to many relationship with users through PFRelation. Now i am struck . Whenever a user starts a new conversation , I add new entry in MessageRoom collection and use its id in the messages of that group. But when i want to fetch a previous conversation , let say a conversation between 5 users , how will i query the messageRoom which has exactly the same 5 users (not more or less) in its relation ?
This is the code i am using to create or get Message Room . It is not working correctly. What it does is instead of making a new messageRoom first time and fetching the same for latter user , it makes a new messaga room every time.
class func createOrGetMessageRoom(users:[PFUser], description:String)->PFObject{
var returnMessageRoom:PFObject = PFObject(className: PF_MESSAGE_ROOM_CLASS_NAME);
let users = users.sort(increasingIDs)
let query:PFQuery = PFQuery(className: PF_MESSAGE_ROOM_CLASS_NAME)
query.whereKey(PF_MESSAGE_ROOM_USERS, containsAllObjectsInArray : users)
query.findObjectsInBackgroundWithBlock{(objects, error )->Void in
if error == nil {
if objects?.count == 0 {
let messageRoom = PFObject(className: PF_MESSAGE_ROOM_CLASS_NAME)
messageRoom[PF_MESSAGE_ROOM_DESCRIPTION] = description
messageRoom[PF_MESSAGE_ROOM_LAST_USER] = PFUser.currentUser()
messageRoom[PF_MESSAGE_ROOM_LAST_MESSAGE] = ""
messageRoom[PF_MESSAGE_ROOM_COUNTER] = 0
messageRoom[PF_MESSAGE_ROOM_UPDATE_TIME] = NSDate()
let messageUsers = messageRoom.relationForKey(PF_MESSAGE_ROOM_USERS)
for user in users {
messageUsers.addObject(user)
}
messageRoom.saveInBackgroundWithBlock{(success,error)->Void in
if error == nil {
returnMessageRoom = messageRoom
}
}
}else{
returnMessageRoom = objects![0]
}
}else{
print("Message.createMessage Erorr");
print(error)
}
}
return returnMessageRoom
}
class func increasingIDs(user1: PFUser, user2: PFUser) -> Bool {
return user1.objectId < user2.objectId
}
I have also checked this application . What it does is whenever it starts a new chat , it concatenates objectIds of users in ascending order and use it as a groupId which is used for future references and used in chat messages as a foreign key.
It'll work in private chat and in group chat , but what happens if a user has started a group chat , and wants to add new users to this chat ?? If we simple change the group id by concatenating this users id , the previous messages which have used the old group id will no longer appear in this message group.
Also tell me if this approach of making groupID through concatenation is better or many to many relationship is better?
One problem with your function createOrGetMessageRoom is that findObjectsInBackgroundWithBlock is asynchronous, and you're not taking that into account.
What this means is that the findObjectsInBackgroundWithBlock function gets a response a long time after createOrGetMessageRoom has returned.
So, the PFObject you create on the first line of your function is always returned - your function does not wait for findObjectsInBackgroundWithBlock to return a MessageRoom.
To fix this, make your code take a callback like this:
class func createOrGetMessageRoom(users:[PFUser], description:String, callback: (PFObject? -> Void)) {
let users = users.sort(increasingIDs)
let query:PFQuery = PFQuery(className: PF_MESSAGE_ROOM_CLASS_NAME)
query.whereKey(PF_MESSAGE_ROOM_USERS, containsAllObjectsInArray : users)
query.findObjectsInBackgroundWithBlock{(objects, error )->Void in
if error == nil {
if objects?.count == 0 {
let messageRoom = PFObject(className: PF_MESSAGE_ROOM_CLASS_NAME)
messageRoom[PF_MESSAGE_ROOM_DESCRIPTION] = description
messageRoom[PF_MESSAGE_ROOM_LAST_USER] = PFUser.currentUser()
messageRoom[PF_MESSAGE_ROOM_LAST_MESSAGE] = ""
messageRoom[PF_MESSAGE_ROOM_COUNTER] = 0
messageRoom[PF_MESSAGE_ROOM_UPDATE_TIME] = NSDate()
let messageUsers = messageRoom.relationForKey(PF_MESSAGE_ROOM_USERS)
for user in users {
messageUsers.addObject(user)
}
messageRoom.saveInBackgroundWithBlock{(success,error)->Void in
if error == nil {
callback(messageRoom)
}
callback(nil)
}
}else{
callback(objects![0])
}
}else{
print("Message.createMessage Erorr");
print(error)
callback(nil)
}
}
}
Usage:
YourClass.createOrGetMessageRoom([], description: "description") { messageRoom in
// Do something...
}
The db schema in my mind, you should have 3 collections, _User, MessageRoom, and Message.
MessageRoom: users, roomName and other infos.
Message: room(pointer of MessageRoom), msg(content), sender(pointer of _User)
below are pseudo code
In your app, query all current user involved messageRooms.
var query = new Parse.Query("MessageRoom")
query.equalTo("users", currentUser);
//other constraint, roomName, createdAt, limit ...
query.find(...)
Pick a messageRoom object, and then use it to getMessages.
var query2 = new Parse.Query("Message");
query2.eqaulTo("room", roomObj);
query2.include("sender");
query2.descending("createdAt");
query2.find(...)

Resources