- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// XMPPIQ+LastActivity.h
|
||||
// XEP-0012
|
||||
//
|
||||
// Created by Daniel Rodríguez Troitiño on 1/26/2013.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "XMPPIQ.h"
|
||||
|
||||
@class XMPPJID;
|
||||
|
||||
/**
|
||||
* Last Activity XEP-0012 namespace: "jabber:iq:last".
|
||||
*/
|
||||
extern NSString *const XMPPLastActivityNamespace;
|
||||
|
||||
/**
|
||||
* Helper methods to create last activity queries inside IQ stanzas or to
|
||||
* inspect result IQ answering last activity queries.
|
||||
*/
|
||||
@interface XMPPIQ (LastActivity)
|
||||
|
||||
/**
|
||||
* Returns an get XMPPIQ with a last activity query child element addressed to
|
||||
* the given JID.
|
||||
*/
|
||||
+ (XMPPIQ *)lastActivityQueryTo:(XMPPJID *)jid;
|
||||
|
||||
/**
|
||||
* Returns a result XMPPIQ answering the given XMPPIQ request with the given
|
||||
* number of seconds.
|
||||
*
|
||||
* The from and to attributes of the returned XMPPIQ will be switched from the
|
||||
* from and to attributes of the given request. The element ID will be the same
|
||||
* of the given request. No status message will be included.
|
||||
*/
|
||||
+ (XMPPIQ *)lastActivityResponseToIQ:(XMPPIQ *)request withSeconds:(NSUInteger)seconds;
|
||||
|
||||
/**
|
||||
* Returns a result XMPPIQ answering the given XMPPIQ request with the given
|
||||
* number of seconds and the given unavailable status.
|
||||
*
|
||||
* The from and to attributes of the returned XMPPIQ will be switched from the
|
||||
* from and to attributes of the given request. The element ID will be the same
|
||||
* of the given request.
|
||||
*/
|
||||
+ (XMPPIQ *)lastActivityResponseToIQ:(XMPPIQ *)request withSeconds:(NSUInteger)seconds status:(NSString *)status;
|
||||
|
||||
/**
|
||||
* Returns an error XMPPIQ answering the given XMPPIQ request.
|
||||
*
|
||||
* The from and to attributes of the returned XMPPIQ will be switched from the
|
||||
* from and to attributes of the given request. The element ID will be the same
|
||||
* of the given request.
|
||||
*
|
||||
* An error element with a forbidden element will be included as child element:
|
||||
*
|
||||
* <iq from='juliet@capulet.com'
|
||||
* id='last1'
|
||||
* to='romeo@montague.net/orchard'
|
||||
* type='error'>
|
||||
* <error type='auth'>
|
||||
* <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
|
||||
* </error>
|
||||
* </iq>
|
||||
*/
|
||||
+ (XMPPIQ *)lastActivityResponseForbiddenToIQ:(XMPPIQ *)request;
|
||||
|
||||
/**
|
||||
* Returns YES if the child element of this XMPPIQ is a query element from the
|
||||
* XMPPLastActivityNamespace. Otherwise returns NO.
|
||||
*/
|
||||
- (BOOL)isLastActivityQuery;
|
||||
|
||||
/**
|
||||
* Returns the parsed number from the seconds attribute of the query subelement.
|
||||
*
|
||||
* Returns NSNotFound in case this XMPPIQ has no query element, or that query
|
||||
* element does not have a seconds attribute. Returns 0 in case the seconds
|
||||
* attribute cannot be parsed as a integer.
|
||||
*/
|
||||
- (NSUInteger)lastActivitySeconds;
|
||||
|
||||
/**
|
||||
* Returns the contents of the query subelement.
|
||||
*
|
||||
* Returns nil in case this XMPPIQ has no query element, or the query element
|
||||
* does not have contents.
|
||||
*/
|
||||
- (NSString *)lastActivityUnavailableStatus;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// XMPPIQ+LastActivity.m
|
||||
// XEP-0012
|
||||
//
|
||||
// Created by Daniel Rodríguez Troitiño on 1/26/2013.
|
||||
//
|
||||
|
||||
#import "XMPPIQ+LastActivity.h"
|
||||
|
||||
#import "XMPP.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
NSString *const XMPPLastActivityNamespace = @"jabber:iq:last";
|
||||
|
||||
@implementation XMPPIQ (LastActivity)
|
||||
|
||||
+ (XMPPIQ *)lastActivityQueryTo:(XMPPJID *)jid
|
||||
{
|
||||
NSXMLElement *query = [[NSXMLElement alloc] initWithName:@"query" xmlns:XMPPLastActivityNamespace];
|
||||
return [[self alloc] initWithType:@"get" to:jid elementID:[XMPPStream generateUUID] child:query];
|
||||
}
|
||||
|
||||
+ (XMPPIQ *)lastActivityResponseToIQ:(XMPPIQ *)request withSeconds:(NSUInteger)seconds
|
||||
{
|
||||
return [self lastActivityResponseToIQ:request withSeconds:seconds status:nil];
|
||||
}
|
||||
|
||||
+ (XMPPIQ *)lastActivityResponseToIQ:(XMPPIQ *)request withSeconds:(NSUInteger)seconds status:(NSString *)status
|
||||
{
|
||||
NSXMLElement *query = [[NSXMLElement alloc] initWithName:@"query" xmlns:XMPPLastActivityNamespace];
|
||||
[query addAttributeWithName:@"seconds" stringValue:[NSString stringWithFormat:@"%lu", (unsigned long)seconds]];
|
||||
if (status && [status length] > 0)
|
||||
{
|
||||
[query setStringValue:status];
|
||||
}
|
||||
|
||||
return [[self alloc] initWithType:@"result" to:request.from elementID:request.elementID child:query];
|
||||
}
|
||||
|
||||
+ (XMPPIQ *)lastActivityResponseForbiddenToIQ:(XMPPIQ *)request
|
||||
{
|
||||
NSXMLElement *reason = [[NSXMLElement alloc] initWithName:@"forbidden" xmlns:@"urn:ietf:params:xml:ns:xmpp-stanzas"];
|
||||
NSXMLElement *error = [[NSXMLElement alloc] initWithName:@"error"];
|
||||
[error addAttributeWithName:@"type" stringValue:@"auth"];
|
||||
[error addChild:reason];
|
||||
|
||||
return [[self alloc] initWithType:@"error" to:request.from elementID:request.elementID child:error];
|
||||
}
|
||||
|
||||
- (BOOL)isLastActivityQuery
|
||||
{
|
||||
return nil != [self lastActivityQueryElement];
|
||||
}
|
||||
|
||||
- (NSUInteger)lastActivitySeconds
|
||||
{
|
||||
NSUInteger seconds = NSNotFound;
|
||||
NSXMLElement *query = [self lastActivityQueryElement];
|
||||
if (query)
|
||||
{
|
||||
NSXMLNode *attribute = [query attributeForName:@"seconds"];
|
||||
if (attribute)
|
||||
{
|
||||
// Some Servers Return -1 to indicate no activity, so we need to ignore these
|
||||
if([query attributeIntegerValueForName:@"seconds"] >= 0)
|
||||
{
|
||||
seconds = [query attributeUnsignedIntegerValueForName:@"seconds"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return seconds;
|
||||
}
|
||||
|
||||
- (NSString *)lastActivityUnavailableStatus
|
||||
{
|
||||
NSXMLElement *query = [self lastActivityQueryElement];
|
||||
if (query)
|
||||
{
|
||||
return [query stringValue];
|
||||
}
|
||||
else
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSXMLElement *)lastActivityQueryElement
|
||||
{
|
||||
return [self elementForName:@"query" xmlns:XMPPLastActivityNamespace];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// XMPPLastActivity.h
|
||||
// XEP-0012
|
||||
//
|
||||
// Created by Daniel Rodríguez Troitiño on 1/26/2013.
|
||||
//
|
||||
//
|
||||
|
||||
#import "XMPP.h"
|
||||
#import "XMPPIQ+LastActivity.h"
|
||||
|
||||
#define _XMPP_LAST_ACTIVITY_H
|
||||
|
||||
/**
|
||||
* Provides support to both sending last activity queries and answering those
|
||||
* last activity queries done by other entities, as documented in the XEP-0012.
|
||||
*
|
||||
* The automatic responses can be disabled setting respondsToQueries to NO.
|
||||
*
|
||||
* If no delegate of this class responds to
|
||||
* numberOfIdleTimeSecondsForXMPPLastActivity:currentIdleTimeSeconds: a default
|
||||
* value of 0 will be send to the other entities. You are encouraged to provide
|
||||
* at least one delegate with this method implemented.
|
||||
*/
|
||||
@interface XMPPLastActivity : XMPPModule
|
||||
|
||||
/**
|
||||
* Whether or not the module should respond to incoming last activity queries.
|
||||
*
|
||||
* If you create multiple instances of this module, only one instance should
|
||||
* respond to queries.
|
||||
*
|
||||
* It is recommended you set this (if needed) before you activate the module.
|
||||
*
|
||||
* The default value is YES.
|
||||
*/
|
||||
@property (atomic, assign) BOOL respondsToQueries;
|
||||
|
||||
/**
|
||||
* Send a last activity query to an specific JID.
|
||||
*
|
||||
* XEP-0012 specifies that last activity queries can be send to offline
|
||||
* entities (bare JID), to online resources (full JID) or servers (domain only
|
||||
* JID). The answers of each of those kind of JIDs are a little different and
|
||||
* has different meaning. It is responsability of the consumer to interpretate
|
||||
* those answers.
|
||||
*
|
||||
* The module will wait for an answer from the server or the entity for at most
|
||||
* 30 seconds before considering this request a timeout failure.
|
||||
*
|
||||
* The delegates of the module should implement
|
||||
* xmppLastActivity:didReceiveResponse: and
|
||||
* xmppLastActivity:didNotReceiveResponse:dueToTimeout: to be informed when the
|
||||
* result of the IQ arrives (or the timeout happens).
|
||||
*
|
||||
* The returning string is the element ID of the IQ that was sent including the
|
||||
* last activity query.
|
||||
*/
|
||||
- (NSString *)sendLastActivityQueryToJID:(XMPPJID *)jid;
|
||||
|
||||
/**
|
||||
* Send a last activity query to an specific JID with an specific timeout.
|
||||
*
|
||||
* XEP-0012 specifies that last activity queries can be send to offline
|
||||
* entities (bare JID), to online resources (full JID) or servers (domain only
|
||||
* JID). The answers of each of those kind of JIDs are a little different and
|
||||
* has different meaning. It is responsability of the consumer to interpretate
|
||||
* those answers.
|
||||
*
|
||||
* The module will wait for an answer from the server or the entity for at most
|
||||
* the number seconds given by tiemout before considering this request a timeout
|
||||
* failure.
|
||||
*
|
||||
* The delegates of the module should implement
|
||||
* xmppLastActivity:didReceiveResponse: and
|
||||
* xmppLastActivity:didNotReceiveResponse:dueToTimeout: to be informed when the
|
||||
* result of the IQ arrives (or the timeout happens).
|
||||
*
|
||||
* The returning string is the element ID of the IQ that was sent including the
|
||||
* last activity query.
|
||||
*/
|
||||
- (NSString *)sendLastActivityQueryToJID:(XMPPJID *)jid withTimeout:(NSTimeInterval)timeout;
|
||||
|
||||
@end
|
||||
|
||||
@protocol XMPPLastActivityDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* Callback when the XMPPLastActivity sender receives a result IQ or an error IQ
|
||||
* for a previous get IQ.
|
||||
*
|
||||
* The XMPPIQ response can be queried using the methods in the category
|
||||
* XMPPIQ (LastActivity) for the seconds and status value.
|
||||
*/
|
||||
- (void)xmppLastActivity:(XMPPLastActivity *)sender didReceiveResponse:(XMPPIQ *)response;
|
||||
|
||||
/**
|
||||
* Callback when the XMPPLastActivity sender does not receive neither a result
|
||||
* IQ nor a error IQ after timeout seconds have passed.
|
||||
*
|
||||
* The queryID is the element ID used to send the query, so the consumer can
|
||||
* compare it with the element ID returned by the sendLastActivityQueryToJID:
|
||||
* methods and act accordingly.
|
||||
*/
|
||||
- (void)xmppLastActivity:(XMPPLastActivity *)sender didNotReceiveResponse:(NSString *)queryID dueToTimeout:(NSTimeInterval)timeout;
|
||||
|
||||
/**
|
||||
* Callback to obtain the number of idle seconds that the XMPPLastActivity
|
||||
* sender should use as answer to a last activity query iq.
|
||||
*
|
||||
* Each delegate will be asked in turn (the order is not guaranteed) and each of
|
||||
* then should decide to return the given idleSeconds value, or a new value as
|
||||
* the number of idleSeconds. The first delegate will receive NSNotFound as the
|
||||
* value of idleSecond. If the last delegate returns NSNotFound as result, the
|
||||
* answer will be 0 seconds.
|
||||
*/
|
||||
- (NSUInteger)numberOfIdleTimeSecondsForXMPPLastActivity:(XMPPLastActivity *)sender queryIQ:(XMPPIQ *)iq currentIdleTimeSeconds:(NSUInteger)idleSeconds;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,222 @@
|
||||
//
|
||||
// XMPPLastActivity.m
|
||||
// XEP-0012
|
||||
//
|
||||
// Created by Daniel Rodríguez Troitiño on 1/26/2013.
|
||||
//
|
||||
//
|
||||
|
||||
#import "XMPPLastActivity.h"
|
||||
#import "XMPPIDTracker.h"
|
||||
#import "XMPPIQ+LastActivity.h"
|
||||
#import "XMPPFramework.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
static const NSTimeInterval XMPPLastActivityDefaultTimeout = 30.0;
|
||||
|
||||
|
||||
@interface XMPPLastActivity ()
|
||||
|
||||
@property (atomic, strong) XMPPIDTracker *queryTracker;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation XMPPLastActivity
|
||||
|
||||
@synthesize respondsToQueries = _respondsToQueries;
|
||||
@synthesize queryTracker = _queryTracker;
|
||||
|
||||
- (id)init
|
||||
{
|
||||
return [self initWithDispatchQueue:NULL];
|
||||
}
|
||||
|
||||
- (id)initWithDispatchQueue:(dispatch_queue_t)queue
|
||||
{
|
||||
if ((self = [super initWithDispatchQueue:queue]))
|
||||
{
|
||||
_respondsToQueries = YES;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)activate:(XMPPStream *)aXmppStream
|
||||
{
|
||||
if ([super activate:aXmppStream])
|
||||
{
|
||||
#ifdef _XMPP_CAPABILITIES_H
|
||||
[xmppStream autoAddDelegate:self delegateQueue:moduleQueue toModulesOfClass:[XMPPCapabilities class]];
|
||||
#endif
|
||||
|
||||
_queryTracker = [[XMPPIDTracker alloc] initWithDispatchQueue:moduleQueue];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)deactivate
|
||||
{
|
||||
#ifdef _XMPP_CAPABILITIES_H
|
||||
[xmppStream removeAutoDelegate:self delegateQueue:moduleQueue fromModulesOfClass:[XMPPCapabilities class]];
|
||||
#endif
|
||||
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
[_queryTracker removeAllIDs];
|
||||
_queryTracker = nil;
|
||||
}};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(moduleQueue, block);
|
||||
|
||||
[super deactivate];
|
||||
}
|
||||
|
||||
- (BOOL)respondsToQueries
|
||||
{
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
{
|
||||
return _respondsToQueries;
|
||||
}
|
||||
else
|
||||
{
|
||||
__block BOOL result;
|
||||
|
||||
dispatch_sync(moduleQueue, ^{
|
||||
result = _respondsToQueries;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setRespondsToQueries:(BOOL)respondsToQueries
|
||||
{
|
||||
dispatch_block_t block = ^{
|
||||
if (_respondsToQueries != respondsToQueries)
|
||||
{
|
||||
_respondsToQueries = respondsToQueries;
|
||||
|
||||
#ifdef _XMPP_CAPABILITIES_H
|
||||
@autoreleasepool {
|
||||
// Capabilities may have changed, need to notify others
|
||||
[xmppStream resendMyPresence];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(moduleQueue, block);
|
||||
}
|
||||
|
||||
- (NSString *)sendLastActivityQueryToJID:(XMPPJID *)jid
|
||||
{
|
||||
return [self sendLastActivityQueryToJID:jid withTimeout:XMPPLastActivityDefaultTimeout];
|
||||
}
|
||||
|
||||
- (NSString *)sendLastActivityQueryToJID:(XMPPJID *)jid withTimeout:(NSTimeInterval)timeout
|
||||
{
|
||||
XMPPIQ *query = [XMPPIQ lastActivityQueryTo:jid];
|
||||
NSString *queryID = query.elementID;
|
||||
|
||||
dispatch_async(moduleQueue, ^{
|
||||
__weak __typeof__(self) self_weak_ = self;
|
||||
[_queryTracker addID:queryID block:^(XMPPIQ *iq, id<XMPPTrackingInfo> info) {
|
||||
__strong __typeof__(self) self = self_weak_;
|
||||
if (iq)
|
||||
{
|
||||
[self delegateDidReceiveResponse:iq];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self delegateDidNotReceiveResponse:info.elementID dueToTimeout:info.timeout];
|
||||
}
|
||||
} timeout:timeout];
|
||||
|
||||
[xmppStream sendElement:query];
|
||||
});
|
||||
|
||||
return queryID;
|
||||
}
|
||||
|
||||
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
|
||||
{
|
||||
NSString *type = [iq type];
|
||||
|
||||
if (([type isEqualToString:@"result"] || [type isEqualToString:@"error"]))
|
||||
{
|
||||
return [_queryTracker invokeForID:iq.elementID withObject:iq];
|
||||
}
|
||||
else if (_respondsToQueries && [type isEqualToString:@"get"] && [iq isLastActivityQuery])
|
||||
{
|
||||
NSUInteger seconds = [self delegateNumberOfIdleTimeSecondsWithIQ:iq];
|
||||
XMPPIQ *response = [XMPPIQ lastActivityResponseToIQ:iq withSeconds:seconds];
|
||||
[sender sendElement:response];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
|
||||
{
|
||||
[_queryTracker removeAllIDs];
|
||||
}
|
||||
|
||||
#ifdef _XMPP_CAPABILITIES_H
|
||||
// If an XMPPCapabilities instance is used we want to advertise our support for last activity.
|
||||
- (void)xmppCapabilities:(XMPPCapabilities *)sender collectingMyCapabilities:(NSXMLElement *)query
|
||||
{
|
||||
if (_respondsToQueries)
|
||||
{
|
||||
NSXMLElement *feature = [NSXMLElement elementWithName:@"feature"];
|
||||
[feature addAttributeWithName:@"var" stringValue:XMPPLastActivityNamespace];
|
||||
|
||||
[query addChild:feature];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)delegateDidReceiveResponse:(XMPPIQ *)response
|
||||
{
|
||||
[multicastDelegate xmppLastActivity:self didReceiveResponse:response];
|
||||
}
|
||||
|
||||
- (void)delegateDidNotReceiveResponse:(NSString *)queryID dueToTimeout:(NSTimeInterval)timeout
|
||||
{
|
||||
[multicastDelegate xmppLastActivity:self didNotReceiveResponse:queryID dueToTimeout:timeout];
|
||||
}
|
||||
|
||||
- (NSUInteger)delegateNumberOfIdleTimeSecondsWithIQ:(XMPPIQ *)iq
|
||||
{
|
||||
__block NSUInteger idleSeconds = NSNotFound;
|
||||
|
||||
GCDMulticastDelegateEnumerator *delegateEnumerator = [multicastDelegate delegateEnumerator];
|
||||
|
||||
id delegate;
|
||||
dispatch_queue_t delegateQueue;
|
||||
SEL selector = @selector(numberOfIdleTimeSecondsForXMPPLastActivity:queryIQ:currentIdleTimeSeconds:);
|
||||
|
||||
while ([delegateEnumerator getNextDelegate:&delegate delegateQueue:&delegateQueue forSelector:selector])
|
||||
{
|
||||
dispatch_sync(delegateQueue, ^{ @autoreleasepool {
|
||||
idleSeconds = [delegate numberOfIdleTimeSecondsForXMPPLastActivity:self queryIQ:iq currentIdleTimeSeconds:idleSeconds];
|
||||
}});
|
||||
}
|
||||
|
||||
return idleSeconds == NSNotFound ? 0 : idleSeconds;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user