Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Adding extension for ProcessOne's push and fast-reconnect module.

  • Loading branch information...
commit b28b3b1ea2647f673c769b7f04ceebea9a02143e 1 parent 1320cc8
@robbiehanson authored
View
101 Extensions/ProcessOne/XMPPProcessOne.h
@@ -0,0 +1,101 @@
+#import <Foundation/Foundation.h>
+#import "XMPPSASLAuthentication.h"
+#import "XMPPStream.h"
+#import "XMPPModule.h"
+
+/**
+ * Process One has a proprietary module they sell for ejabberd that enables several
+ * features such as push notifications and fast reconnect.
+ *
+ * This file implements the client side functionality for XMPPFramework.
+**/
+@interface XMPPProcessOne : XMPPModule
+
+/**
+ * Once a connection is authenticated, the module automatically stores the session ID and related JID.
+ * The information is stored in the user defaults system, thus it is persisted across launches of the application.
+ *
+ * If the session information is available, and the server supports rebind, fast reconnect may be possible.
+**/
+@property (readonly) NSString *savedSessionID;
+@property (readonly) XMPPJID *savedSessionJID;
+
+/**
+ * Push Mode Configuration.
+ * Options are detailed in the documentation from ejabberd.
+ *
+ * An example of a pushConfiguration element you would set:
+ *
+ * <push xmlns='p1:push'>
+ * <keepalive max='30'/>
+ * <session duration='60'/>
+ * <body send='all' groupchat='true' from='jid'/>
+ * <status type='xa'>Text Message when in push mode</status>
+ * <offline>false</offline>
+ * <notification>
+ * <type>applepush</type>
+ * <id>DeviceToken</id>
+ * </notification>
+ * <appid>application1</appid>
+ * </push>
+ *
+ * To enable Apple Push on the ejabberd server, you must set the pushConfiguration element.
+ *
+ * You may set the pushConfiguration element at any time.
+ * If you set it after the xmpp stream has already authenticated, then the push settings will be sent right away.
+ * Otherwise, the push settings will be sent as soon as the stream is authenticated.
+ *
+ * After the pushConfiguration element has been set, you can change it at any time.
+ * If you do, it will send the updated configuration options to the server.
+ *
+ * To disable push, you can simply set the pushConfiguration to nil.
+**/
+@property (readwrite, strong) NSXMLElement *pushConfiguration;
+
+/**
+ * Standby Mode.
+ * The following methods allow you to switch on/off standby mode.
+ *
+ * Typical use case looks like this:
+ *
+ * - (void)applicationWillResignActive:(NSNotification *)notification
+ * {
+ * // Send standby element (via normal asynchronous mechanism)
+ * XMPPElementReceipt *receipt = [xmppProcessOne goOnStandby];
+ *
+ * // Wait until standby element gets sent (pumped through dispatch queues and into OS socket buffer)
+ * [receipt wait:-1.0];
+ * }
+ *
+ * - (void)applicationDidBecomeActive:(NSNotification *)notification
+ * {
+ * [xmppProcessOne goOffStandby];
+ * }
+**/
+- (XMPPElementReceipt *)goOnStandby;
+- (XMPPElementReceipt *)goOffStandby;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface XMPPRebindAuthentication : NSObject <XMPPSASLAuthentication>
+
+- (id)initWithStream:(XMPPStream *)stream sessionID:(NSString *)sessionID sessionJID:(XMPPJID *)sessionJID;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface XMPPStream (XMPPProcessOne)
+
+- (BOOL)supportsPush;
+- (BOOL)supportsRebind;
+
+- (NSString *)rebindSessionID;
+
+@end
View
359 Extensions/ProcessOne/XMPPProcessOne.m
@@ -0,0 +1,359 @@
+#import "XMPPProcessOne.h"
+#import "XMPP.h"
+#import "XMPPInternal.h"
+#import "XMPPLogging.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels: off, error, warn, info, verbose
+// Log flags: trace
+#if DEBUG
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE;
+#else
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
+#endif
+
+NSString *const XMPPProcessOneSessionID = @"XMPPProcessOneSessionID";
+NSString *const XMPPProcessOneSessionJID = @"XMPPProcessOneSessionJID";
+NSString *const XMPPProcessOneSessionDate = @"XMPPProcessOneSessionDate";
+
+@interface XMPPProcessOne ()
+{
+ NSXMLElement *pushConfiguration;
+ BOOL pushConfigurationSent;
+ BOOL pushConfigurationConfirmed;
+ NSString *pushIQID;
+}
+
+@property (readwrite) NSString *savedSessionID;
+@property (readwrite) XMPPJID *savedSessionJID;
+
+- (void)sendPushConfiguration;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPProcessOne
+
+- (id)initWithDispatchQueue:(dispatch_queue_t)queue
+{
+ if ((self = [super initWithDispatchQueue:NULL]))
+ {
+ pushConfiguration = nil;
+ pushConfigurationSent = YES;
+ pushConfigurationConfirmed = YES;
+ }
+ return self;
+}
+
+- (NSString *)savedSessionID
+{
+ return [[NSUserDefaults standardUserDefaults] stringForKey:XMPPProcessOneSessionID];
+}
+
+- (void)setSavedSessionID:(NSString *)savedSessionID
+{
+ if (savedSessionID)
+ [[NSUserDefaults standardUserDefaults] setObject:savedSessionID forKey:XMPPProcessOneSessionID];
+ else
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:XMPPProcessOneSessionID];
+}
+
+- (XMPPJID *)savedSessionJID
+{
+ NSString *sessionJidStr = [[NSUserDefaults standardUserDefaults] stringForKey:XMPPProcessOneSessionJID];
+
+ return [XMPPJID jidWithString:sessionJidStr];
+}
+
+- (void)setSavedSessionJID:(XMPPJID *)savedSessionJID
+{
+ NSString *sessionJidStr = [savedSessionJID full];
+
+ if (sessionJidStr)
+ [[NSUserDefaults standardUserDefaults] setObject:sessionJidStr forKey:XMPPProcessOneSessionJID];
+ else
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:XMPPProcessOneSessionJID];
+}
+
+- (NSXMLElement *)pushConfiguration
+{
+ if (dispatch_get_current_queue() == moduleQueue)
+ {
+ return pushConfiguration;
+ }
+ else
+ {
+ __block NSXMLElement *result = nil;
+
+ dispatch_sync(moduleQueue, ^{
+ result = [pushConfiguration copy];
+ });
+
+ return result;
+ }
+}
+
+- (void)setPushConfiguration:(NSXMLElement *)pushConfig
+{
+ NSXMLElement *newPushConfiguration = [pushConfig copy];
+
+ dispatch_block_t block = ^{
+
+ if (pushConfiguration == nil && newPushConfiguration == nil)
+ {
+ return;
+ }
+
+ pushConfiguration = newPushConfiguration;
+ pushConfigurationSent = NO;
+ pushConfigurationConfirmed = NO;
+
+ if ([xmppStream isAuthenticated])
+ {
+ [self sendPushConfiguration];
+ }
+ };
+
+ if (dispatch_get_current_queue() == moduleQueue)
+ block();
+ else
+ dispatch_async(moduleQueue, block);
+}
+
+- (void)sendPushConfiguration
+{
+ if (pushConfiguration)
+ {
+ pushIQID = [XMPPStream generateUUID];
+
+ XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:pushIQID child:pushConfiguration];
+
+ [xmppStream sendElement:iq];
+ pushConfigurationSent = YES;
+ }
+ else
+ {
+ // <iq type='set'>
+ // <disable xmlns='p1:push'>
+ // </iq>
+
+ NSXMLElement *disable = [NSXMLElement elementWithName:@"disable" xmlns:@"p1:push"];
+
+ XMPPIQ *iq = [XMPPIQ iqWithType:@"set" child:disable];
+
+ [xmppStream sendElement:iq];
+ pushConfigurationSent = YES;
+ }
+}
+
+- (XMPPElementReceipt *)goOnStandby
+{
+ // <standby>true</standby>
+
+ NSXMLElement *standby = [NSXMLElement elementWithName:@"standby" stringValue:@"true"];
+
+ XMPPElementReceipt *receipt = nil;
+ [xmppStream sendElement:standby andGetReceipt:&receipt];
+
+ return receipt;
+}
+
+- (XMPPElementReceipt *)goOffStandby
+{
+ // <standby>false</standby>
+
+ NSXMLElement *standby = [NSXMLElement elementWithName:@"standby" stringValue:@"false"];
+
+ XMPPElementReceipt *receipt = nil;
+ [xmppStream sendElement:standby andGetReceipt:&receipt];
+
+ return receipt;
+}
+
+- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
+{
+ // Successful authentication via non-rebind.
+ // Save session information.
+
+ self.savedSessionID = [xmppStream rebindSessionID];
+ self.savedSessionJID = [xmppStream myJID];
+
+ if (!pushConfigurationSent)
+ {
+ [self sendPushConfiguration];
+ }
+}
+
+- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
+{
+ if (!pushConfigurationConfirmed)
+ {
+ // The pushConfiguration was sent to the server, but we never received a confirmation.
+ // So either the pushConfiguration never made it to the server,
+ // or we got disconnected before we received the confirmation from the server.
+ //
+ // To be sure, we need to resent the pushConfiguration next time we authenticate.
+
+ pushConfigurationSent = NO;
+ }
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPRebindAuthentication
+{
+ #if __has_feature(objc_arc_weak)
+ __weak XMPPStream *xmppStream;
+ #else
+ __unsafe_unretained XMPPStream *xmppStream;
+ #endif
+
+ NSString *sessionID;
+ XMPPJID *sessionJID;
+}
+
++ (NSString *)mechanismName
+{
+ return nil;
+}
+
+- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password
+{
+ if ((self = [super init]))
+ {
+ xmppStream = stream;
+ }
+ return self;
+}
+
+- (id)initWithStream:(XMPPStream *)stream sessionID:(NSString *)aSessionID sessionJID:(XMPPJID *)aSessionJID
+{
+ if ((self = [super init]))
+ {
+ xmppStream = stream;
+ sessionID = aSessionID;
+ sessionJID = aSessionJID;
+ }
+ return self;
+}
+
+- (BOOL)start:(NSError **)errPtr
+{
+ if (!sessionID || !sessionJID)
+ {
+ NSString *errMsg = @"Missing sessionID and/or sessionJID.";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ NSError *err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info];
+
+ if (errPtr) *errPtr = err;
+ return NO;
+ }
+
+ // <rebind xmlns="p1:rebind">
+ // <jid>user@domain/resource</jid>
+ // <sid>123456789</sid>
+ // </rebind>
+
+ NSXMLElement *jid = [NSXMLElement elementWithName:@"jid" stringValue:[sessionJID full]];
+ NSXMLElement *sid = [NSXMLElement elementWithName:@"sid" stringValue:sessionID];
+
+ NSXMLElement *rebind = [NSXMLElement elementWithName:@"rebind" xmlns:@"p1:rebind"];
+ [rebind addChild:jid];
+ [rebind addChild:sid];
+
+ [xmppStream sendAuthElement:rebind];
+ return YES;
+}
+
+- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)response
+{
+ if ([[response name] isEqualToString:@"rebind"])
+ {
+ return XMPP_AUTH_SUCCESS;
+ }
+ else
+ {
+ return XMPP_AUTH_FAIL;
+ }
+}
+
+- (BOOL)shouldResendOpeningNegotiationAfterSuccessfulAuthentication
+{
+ return NO;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPStream (XMPPProcessOne)
+
+- (BOOL)supportsPush
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // The root element can be properly queried anytime after the
+ // stream:features are received, and TLS has been setup (if required)
+ if (state >= STATE_XMPP_POST_NEGOTIATION)
+ {
+ NSXMLElement *features = [rootElement elementForName:@"stream:features"];
+ NSXMLElement *push = [features elementForName:@"push" xmlns:@"p1:push"];
+
+ result = (push != nil);
+ }
+ }};
+
+ if (dispatch_get_current_queue() == xmppQueue)
+ block();
+ else
+ dispatch_sync(xmppQueue, block);
+
+ return result;
+}
+
+- (BOOL)supportsRebind
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // The root element can be properly queried anytime after the
+ // stream:features are received, and TLS has been setup (if required)
+ if (state >= STATE_XMPP_POST_NEGOTIATION)
+ {
+ NSXMLElement *features = [rootElement elementForName:@"stream:features"];
+ NSXMLElement *rebind = [features elementForName:@"rebind" xmlns:@"p1:rebind"];
+
+ result = (rebind != nil);
+ }
+ }};
+
+ if (dispatch_get_current_queue() == xmppQueue)
+ block();
+ else
+ dispatch_sync(xmppQueue, block);
+
+ return result;
+}
+
+- (NSString *)rebindSessionID
+{
+ return [[self rootElement] attributeStringValueForName:@"id"];
+}
+
+@end
Please sign in to comment.
Something went wrong with that request. Please try again.