17
votes

I have an ios chat application that uses openfire, what I need to do is send push notification when the message (1) can't be delivered for any reason, (2) app is in suspended state, i.e. can't generate a notification on its own.

I have read most of the related questions/suggestions on this on stackoverflow and elsewhere and I have concluded few solutions to my problem. I am not an ios developer nor did I know anything about openfire or xmpp before a couple of days, so I am afraid my understanding of things may not be complete and my solutions might be flawed.

Kindly confirm my understanding of it and suggest if I am missing something or if there is a better approach. Please also suggest about how complex it is going to be to implement a particular solution listed below.

Challenge here is to identify when the push is required and where the process be initiated, so

1) one way is to use the xep-0184 implementation of xmpp to check if the message is delivered. to do this we should have some delivered flag with message in ios database, which is updated when the delivered response is received form other end. So we need check for this flag after a little while and if the delivered status is false, initiate push process with the message. Looks to be a complicated solution (wait for response.. check flag with some time lag.. not very impressive)

2) A more straight forward approach is to do something in openfire, when openfire can't deliver a message it stores it in offline table, we can do some interception on that part and initiate the push process with the message. This looks to be the correct approach but I am really afraid of getting that much inside openfire and change something(It might be easy also, somebody who has worked a little with openfire can tell?)

3) This is my last resort, and this is not a solution.. but if I can't do it correctly within expected timeframe (which is a week from now) , We plan to send a push notification for all the messages. oppenfire will takecare of normal chat while a push will be sent from our server for each message but when the app is in foreground, we do something to handle the extra push messages that need not be shown, otherwise a push is received whenever there is a message. What do you guys think of this temporary way around (we will of course have to change this as soon as we can), is this doable (or I am missing something here as well).

P.S. Can anyone tell how Whatsapp and other popular apps handle this?

Many thanks for your help.

2
thanks for the xep-0184 protocol mention!VinceFR

2 Answers

6
votes

XMPP requires a persistent socket connection or a "persistent" BOSH connection during your whole XMPP session. I think your challenge is that iOS does not allow you to run your app and socket in background. Whenever your iOS app goes in background iOS kills your socket connection, and your Openfire server kills your XMPP session. This means the user goes offline. Which is also the reason why incoming messages for this user go to the offline storage.

Sorry for this response, but all 3 solutions you suggested are terrible hacks ;-). If you want to come up with a good solution you have to go very deep into XMPP and iOS. 1 week is a very short timeframe for this.

Can anyone tell how Whatsapp and other popular apps handle this?

They keep the XMPP session alive. This works only with highly modified XMPP servers, some "XMPP Client Proxy" in between which keeps your session running while your app is in background, or a combination or both.

2
votes

I have a solution for you.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.ary_UserStatus = [NSMutableArray array];
    NSMutableArray *ary_TempFromUserDefaults = [NSMutableArray array];
    ary_TempFromUserDefaults = [[NSUserDefaults standardUserDefaults] valueForKey:@"KejdoUserStatus"];

    if ([ary_TempFromUserDefaults count]>0)
    {
         self.ary_UserStatus = [[NSUserDefaults standardUserDefaults]    valueForKey:@"KejdoUserStatus"];
       }

      self.df_UserStatus = [[NSDateFormatter alloc] init];
      [self.df_UserStatus setDateFormat: @"hh:mm a MM/dd/yyyy"];
}

- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
{
    DDLogVerbose(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, [presence fromStr]);

    NSString *str_UserName = [[presence from] user];
    NSString *str_LastSeenDate = [self.df_UserStatus stringFromDate:[NSDate date]];
    NSMutableDictionary *mdic_UserPresence = [[NSMutableDictionary alloc] init];

    [mdic_UserPresence setValue:str_UserName forKey:@"Name"];
    [mdic_UserPresence setValue:str_LastSeenDate forKey:@"Date"];
    [mdic_UserPresence setValue:[presence type] forKey:@"Type"];

    if ([self.ary_UserStatus count]>0)
    {
        int index;
        BOOL IS_exist=FALSE;
        for (int i=0; i<[self.ary_UserStatus count]; i++)
        {
            NSString *str_UserFromArray = [[self.ary_UserStatus objectAtIndex:i] valueForKey:@"Name"];
            if ([str_UserName isEqualToString:str_UserFromArray])
            {
                IS_exist = TRUE;
                index = i;
                [[NSUserDefaults standardUserDefaults] setObject:str_UserName forKey:@"Status"];
            }
            else
            {
            }

        }
        if (IS_exist) {
            [self.ary_UserStatus replaceObjectAtIndex:index withObject:mdic_UserPresence];
        }
        else
        {
            [self.ary_UserStatus addObject:mdic_UserPresence];
        }
    }
    else
    {
        [self.ary_UserStatus addObject:mdic_UserPresence];
    }


    [[NSUserDefaults standardUserDefaults] setObject:self.ary_UserStatus forKey:@"KejdoUserStatus"];
    [[NSUserDefaults standardUserDefaults] synchronize];

    [[NSNotificationCenter defaultCenter] postNotificationName:@"UserStatusChangeNotification" object:self];

}

And wherever you are sending message to other user in chat. Do this

 if(appDelegate.ary_UserStatus.count>0)
    {
     for (int i=0; i<[appDelegate.ary_UserStatus count]; i++)
     {
      if ([jid.user isEqualToString:[NSString stringWithFormat:@"%@",[[appDelegate.ary_UserStatus objectAtIndex:i] valueForKey:@"Name"]]])
        {
         if ([[[appDelegate.ary_UserStatus objectAtIndex:i] valueForKey:@"Type"] isEqualToString:@"available"])
           {
                                // Do something like table reload.
                                break;
             }
               else
                    [self sendPushNotification];
           }
         }
       }
        else
            [self sendPushNotification];