Skip to content

Instantly share code, notes, and snippets.

@Eridana
Last active October 10, 2018 12:36
Show Gist options
  • Save Eridana/cf3d3838fe8cd841bcbb to your computer and use it in GitHub Desktop.
Save Eridana/cf3d3838fe8cd841bcbb to your computer and use it in GitHub Desktop.
iOS XMPPFramework
//
// XmppManager.h
//
//
//
#import <Foundation/Foundation.h>
@protocol XmppChatDelegate <NSObject>
- (void)didReceiveMessage:(NSDictionary *)msgDict;
- (void)didFailToSendMessage;
@end
@interface XmppManager : NSObject
@property id<XmppChatDelegate> chatDelegate;
+ (XmppManager *)sharedInstance;
- (void)setupStream;
- (void)setupRoom;
- (void)sendMessage:(NSString *)messageStr;
- (NSArray *)requestOldMessagesForChat;
- (void)leaveChatRoom;
@end
//
// XmppManager.m
//
//
//
#import "XmppManager.h"
#import "XMPPFramework.h"
#import "XMPPRoomCoreDataStorage.h"
#import "XMPPRoom.h"
#import "AppDelegate.h"
#import "User.h"
#import "XMPPRoomMemoryStorage.h"
#import "XMPPMUC.h"
#import "XMPPReconnect.h"
#import "DDLog.h"
#import "DDTTYLogger.h"
#import "NSDate+XMPPDateTimeProfiles.h"
#import "XMPPMessageArchivingCoreDataStorage.h"
#import "XMPPMessageArchiving.h"
#import <JFMinimalNotification.h>
#define XMPP_HOSTNAME @"xmpp.aa.bb"
#define ROOM_JID @"[email protected]"
// Log levels: off, error, warn, info, verbose
#if DEBUG
static const int ddLogLevel = LOG_LEVEL_VERBOSE;
#else
static const int ddLogLevel = LOG_LEVEL_INFO;
#endif
@interface XmppManager() <XMPPStreamDelegate, XMPPRoomDelegate, XMPPReconnectDelegate, UIAlertViewDelegate>
{
BOOL roomJoined;
BOOL userAuthenticated;
int savedMessagesCount;
JFMinimalNotification *minimalNotification;
}
@property XMPPStream *xmppStream;
@property XMPPReconnect *xmppReconnect;
// saving messages
@property XMPPMessageArchivingCoreDataStorage *xmppMessageArchivingStorage;
@property XMPPMessageArchiving *xmppMessageArchivingModule;
// chat room
@property XMPPRoomMemoryStorage *xmppChatRoomStorage;
@property XMPPRoom *xmppChatRoom;
@property XMPPJID *chatRoomJid;
@property NSMutableArray *lastChatMessages;
// user data
@property XMPPJID *userJid;
@property User *currentUser;
@property NSString *jidUserName;
@end
@implementation XmppManager
+ (XmppManager *)sharedInstance
{
static XmppManager * _sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[XmppManager alloc] init];
[_sharedInstance setupStream];
});
return _sharedInstance;
}
- (void)setupStream
{
self.currentUser = [AppManagedContext getCurrentUser];
self.jidUserName = [self.currentUser.jid componentsSeparatedByString:@"@"][0];
self.userJid = [XMPPJID jidWithString:self.currentUser.jid];
self.lastChatMessages = [NSMutableArray new];
savedMessagesCount = 0;
// uncomment this to enable full logging
// [DDLog addLogger:[DDTTYLogger sharedInstance]];
self.xmppStream = [[XMPPStream alloc] init];
[self.xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
#if !TARGET_IPHONE_SIMULATOR
{
self.xmppStream.enableBackgroundingOnSocket = YES;
}
#endif
self.xmppStream.hostName = XMPP_HOSTNAME;
self.xmppStream.myJID = self.userJid;
self.xmppStream.hostPort = 5222;
self.xmppReconnect = [[XMPPReconnect alloc] init];
[self.xmppReconnect addDelegate:self delegateQueue:dispatch_get_main_queue()];
[self.xmppReconnect activate:self.xmppStream];
// uncomment this block to enable built-in message saving
/*
self.xmppMessageArchivingStorage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
self.xmppMessageArchivingModule = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:self.xmppMessageArchivingStorage];
[self.xmppMessageArchivingModule setClientSideMessageArchivingOnly:YES];
[self.xmppMessageArchivingModule activate:self.xmppStream];
[self.xmppMessageArchivingModule addDelegate:self delegateQueue:dispatch_get_main_queue()];
*/
[self connect];
}
- (void)connect
{
[self.xmppStream setMyJID:self.userJid];
NSError *error = nil;
if (![self.xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&error])
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error"
message:[NSString stringWithFormat:@"Cannot connect to server %@", [error localizedDescription]]
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alertView show];
}
}
- (void)setupRoom
{
if (roomJoined == YES) return;
roomJoined = YES;
self.chatRoomJid = [XMPPJID jidWithString:ROOM_JID];
self.xmppChatRoomStorage = [[XMPPRoomMemoryStorage alloc] init];
self.xmppChatRoom = [[XMPPRoom alloc]
initWithRoomStorage:self.xmppChatRoomStorage
jid:self.chatRoomJid
dispatchQueue:dispatch_get_main_queue()];
[self.xmppChatRoom activate:[self xmppStream]];
[self.xmppChatRoom addDelegate:self delegateQueue:dispatch_get_main_queue()];
// set room history messages count to 10
NSXMLElement *xmlHistoryCount = [[NSXMLElement alloc] initWithXMLString:@"<history maxstanzas='10'/>" error:nil];
[self.xmppChatRoom joinRoomUsingNickname:self.jidUserName history:xmlHistoryCount];
[self.xmppChatRoom fetchConfigurationForm];
}
- (void)leaveChatRoom
{
roomJoined = NO;
[self.xmppChatRoom leaveRoom];
[self.xmppChatRoom deactivate];
[self.xmppChatRoom removeDelegate:self];
}
- (NSArray *)requestOldMessagesForChat
{
return [self.lastChatMessages copy];
// uncomment this block to emable built-in message saving
/*
XMPPMessageArchivingCoreDataStorage *storage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
NSManagedObjectContext *moc = [storage mainThreadManagedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject"
inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entityDescription];
NSError *error;
NSArray *messages = [moc executeFetchRequest:request error:&error];
[self parseMessages:[messages mutableCopy]];
*/
}
/*
// parse saved messages
-(void)parseMessages:(NSMutableArray*)messages
{
for (XMPPMessageArchiving_Message_CoreDataObject *message in messages) {
NSError *error;
// here could be an exception sometimes
XMPPMessage *xmppMessage = [[XMPPMessage alloc] initWithXMLString:message.messageStr error:&error];
if (error) {
NSLog(@"error when parsing messages : %@", [error description]);
return;
}
if ([message.bareJidStr isEqualToString:[self.chatRoomJid full]]) {
[self parseMessage:xmppMessage];
}
}
}
*/
- (void)saveMessage:(NSMutableDictionary *)message
{
if ([self.lastChatMessages containsObject:message] == NO) {
[self.lastChatMessages addObject:message];
savedMessagesCount++;
savedMessagesCount = savedMessagesCount % 10;
}
}
- (void)sendMessage:(NSString *)messageStr
{
if([messageStr length] == 0 || [self.currentUser.username length] == 0) return;
// I have to send message with JSON body (my API required), but usually it's just text
/*
"chat": {
"username": "user123",
"type": "message",
"time": "2015-04-12T18:51:19+00:00",
"text": "Test"
}
*/
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setTimeZone:[NSTimeZone localTimeZone]];
NSLocale *enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
[formatter setLocale:enUSPOSIXLocale];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"];
NSDate *now = [NSDate date];
NSString *iso8601String = [formatter stringFromDate:now];
NSString *type = [self.currentUser.isChatModerator boolValue] == YES ? @"moderator_message" : @"message";
NSDictionary *chat = @{
@"username" : self.currentUser.username,
@"type" : type,
@"time" : iso8601String,
@"text" : messageStr
};
NSDictionary *json = @{@"chat" : chat};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:json options:NSJSONWritingPrettyPrinted error:&error];
NSString *jsonStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
XMPPMessage *message = [[XMPPMessage alloc] init];
[message addAttributeWithName:@"xml:lang" stringValue:@"en"];
NSXMLElement *nick = [NSXMLElement elementWithName:@"nick"];
[nick setXmlns:@"http://jabber.org/protocol/nick"];
[nick setStringValue:self.jidUserName];
NSXMLElement *body = [NSXMLElement elementWithName:@"body"];
[body setStringValue:jsonStr];
[message addChild:body];
[message addChild:nick];
[self.xmppChatRoom sendMessage:message];
}
- (NSMutableDictionary *)parseMessage:(XMPPMessage *)message
{
NSMutableDictionary *msgDict;
if ([message body].length > 0) {
// my API send a JSON with data. so I parse JSON here from message body
NSError *jsonError;
NSData *objectData = [[message body] dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:objectData
options:NSJSONReadingMutableContainers
error:&jsonError];
if (jsonError) {
return nil;
}
NSDictionary *innerDict = [jsonDict objectForKey:@"chat"];
if (innerDict) {
NSString *body = [innerDict objectForKey:@"text"];
BOOL myMessage = NO;
BOOL privateMessageForMe = NO;
NSString *from = [innerDict objectForKey:@"username"];
if ([from isEqualToString:self.currentUser.username]) {
from = self.currentUser.username;
myMessage = YES;
}
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *chatViewTemp = [defaults valueForKey:@"chatTemp"];
if([body hasPrefix:[NSString stringWithFormat:@"@%@",self.currentUser.username]] && [chatViewTemp isEqualToString:@"outchat"]) {
NSString *alertBody = [body substringFromIndex:[NSString stringWithFormat:@"@%@",self.currentUser.username].length];
NSString *messagePrivate = [NSString stringWithFormat:@"User %@ had sent you a message: %@", from, alertBody];
NSLog(@"messagePrivate %@", messagePrivate);
minimalNotification = [JFMinimalNotification notificationWithStyle:JFMinimalNotificationStyleInfo title:@"Сообщение" subTitle:messagePrivate dismissalDelay:2.0];
minimalNotification.presentFromTop = YES;
UIFont* titleFont = [UIFont fontWithName:@"HelveticaNeue" size:18];
[minimalNotification setTitleFont:titleFont];
UIFont* subTitleFont = [UIFont fontWithName:@"HelveticaNeue" size:16];
[minimalNotification setSubTitleFont:subTitleFont];
[App.window.rootViewController.view addSubview:minimalNotification];
[minimalNotification show];
}
NSString *type = [innerDict objectForKey:@"type"];
BOOL isAdmin = NO;
if ([type isEqualToString:@"moderator_message"]) {
isAdmin = YES;
}
NSString *timeStamp = [innerDict objectForKey:@"time"];
NSDate *date = [NSDate dateWithXmppDateTimeString:timeStamp];
if (!date) {
date = [NSDate date];
}
// if body contains @nick
if([body hasPrefix:[NSString stringWithFormat:@"@"]]) {
if ([body hasPrefix:[NSString stringWithFormat:@"@%@",self.currentUser.username]]) {
privateMessageForMe = YES;
NSLog(@"hi my private message from my friend");
msgDict = [[NSMutableDictionary alloc] init];
[msgDict setObject:body forKey:@"msg"];
[msgDict setObject:from forKey:@"sender"];
[msgDict setObject:[NSNumber numberWithBool:isAdmin] forKey:@"isAdmin"];
[msgDict setObject:date forKey:@"time"];
[msgDict setObject:[NSNumber numberWithBool:myMessage] forKey:@"isMine"];
[msgDict setObject:[NSNumber numberWithBool:privateMessageForMe] forKey:@"privateMessage"];
} else if ([from isEqualToString:self.currentUser.username]) {
privateMessageForMe = YES;
NSLog(@"hi private message from me!");
msgDict = [[NSMutableDictionary alloc] init];
[msgDict setObject:body forKey:@"msg"];
[msgDict setObject:from forKey:@"sender"];
[msgDict setObject:[NSNumber numberWithBool:isAdmin] forKey:@"isAdmin"];
[msgDict setObject:date forKey:@"time"];
[msgDict setObject:[NSNumber numberWithBool:myMessage] forKey:@"isMine"];
[msgDict setObject:[NSNumber numberWithBool:privateMessageForMe] forKey:@"privateMessage"];
} else {
msgDict = [[NSMutableDictionary alloc] init];
[msgDict setObject:@"Blocked" forKey:@"msg"];
[msgDict setObject:@"Blocked" forKey:@"sender"];
[msgDict setObject:[NSNumber numberWithBool:isAdmin] forKey:@"isAdmin"];
[msgDict setObject:date forKey:@"time"];
[msgDict setObject:[NSNumber numberWithBool:myMessage] forKey:@"isMine"];
[msgDict setObject:[NSNumber numberWithBool:privateMessageForMe] forKey:@"privateMessage"];
}
} else {
msgDict = [[NSMutableDictionary alloc] init];
[msgDict setObject:body forKey:@"msg"];
[msgDict setObject:from forKey:@"sender"];
[msgDict setObject:date forKey:@"time"];
}
}
return msgDict;
}
#pragma mark - Delegate Methods
#pragma mark - XMPPStreamDelegate
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
{
NSLog(@"xmppStream didReceivePresence");
}
- (void)xmppStreamDidConnect:(XMPPStream *)sender
{
NSLog(@"user connected");
if(sender == self.xmppStream) {
NSError *error = nil;
if (![[self xmppStream] authenticateWithPassword:self.currentUser.jid_password error:&error]) {
NSLog(@"Error authenticating: %@", error);
}
else {
if (self.xmppChatRoom && self.xmppChatRoom.isJoined == NO) {
// reconnect to existing room (if wi-fi or cell was turned off/on)
NSXMLElement *xmlHistoryCount = [[NSXMLElement alloc] initWithXMLString:@"<history maxstanzas='10'/>" error:nil];
[self.xmppChatRoom joinRoomUsingNickname:self.jidUserName history:xmlHistoryCount password:self.currentUser.jid_password];
}
}
}
}
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
NSLog(@"user authenticated");
XMPPPresence *presence = [XMPPPresence presence]; // type="available" is implicit
[self.xmppStream sendElement:presence];
userAuthenticated = YES;
[self setupRoom];
}
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
}
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message
{
NSLog(@"message did send from %@", sender.hostName);
}
- (void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error
{
NSLog(@"xmppStream didFailToSendMessage %@", [error description]);
if (self.chatDelegate && [self.chatDelegate respondsToSelector:@selector(didFailToSendMessage)]) {
[self.chatDelegate didFailToSendMessage];
}
}
#pragma mark - XMPPReconnectDelegate
- (void)xmppReconnect:(XMPPReconnect *)sender didDetectAccidentalDisconnect:(SCNetworkReachabilityFlags)connectionFlags
{
NSLog(@"didDetectAccidentalDisconnect:%u",connectionFlags);
[self leaveChatRoom];
}
- (BOOL)xmppReconnect:(XMPPReconnect *)sender shouldAttemptAutoReconnect:(SCNetworkReachabilityFlags)reachabilityFlags
{
NSLog(@"shouldAttemptAutoReconnect:%u",reachabilityFlags);
return YES;
}
#pragma mark - XMPPRoomDelegate
- (void)xmppRoom:(XMPPRoom *)sender occupantDidJoin:(XMPPJID *)occupantJID withPresence:(XMPPPresence *)presence
{
}
- (void)xmppRoom:(XMPPRoom *)sender occupantDidUpdate:(XMPPJID *)occupantJID withPresence:(XMPPPresence *)presence
{
}
- (void)xmppRoom:(XMPPRoom *)sender didReceiveMessage:(XMPPMessage *)message fromOccupant:(XMPPJID *)occupantJID
{
NSLog(@"xmppRoom didReceiveMessage %@",message);
if ([sender isEqual:self.xmppChatRoom]) {
NSMutableDictionary *msgDict = [self parseMessage:message];
[self saveMessage:msgDict];
if (self.chatDelegate && [self.chatDelegate respondsToSelector:@selector(didReceiveMessage:)]) {
[self.chatDelegate didReceiveMessage:msgDict];
}
}
}
- (void)xmppRoom:(XMPPRoom *)sender didFetchConfigurationForm:(NSXMLElement *)configForm
{
NSXMLElement *newConfig = [configForm copy];
NSLog(@"BEFORE Config for the room %@",newConfig);
NSArray *fields = [newConfig elementsForName:@"field"];
for (NSXMLElement *field in fields)
{
NSString *var = [field attributeStringValueForName:@"var"];
// Make Room Persistent
if ([var isEqualToString:@"muc#roomconfig_persistentroom"]) {
[field removeChildAtIndex:0];
[field addChild:[NSXMLElement elementWithName:@"value" stringValue:@"1"]];
}
}
[sender configureRoomUsingOptions:newConfig];
}
- (void)xmppRoomDidCreate:(XMPPRoom *)sender{
}
- (void)xmppRoomDidJoin:(XMPPRoom *)sender {
NSLog(@"xmppRoomDidJoin");
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment