This commit is contained in:
giuseppenuc committed 2016-02-24 16:56:39 +01:00
1 parent d281d765ea
commit 5b074a5176
1261 files changed
+199158 -7303

No files matched your search

+94
View File
@@ -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
+97
View File
@@ -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
+119
View File
@@ -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
+222
View File
@@ -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