317 lines
12 KiB
Objective-C
317 lines
12 KiB
Objective-C
#import "ARTRestChannel+Private.h"
|
|
|
|
#import "ARTRest+Private.h"
|
|
#import "ARTRestPresence+Private.h"
|
|
#import "ARTChannel+Private.h"
|
|
#import "ARTChannelOptions.h"
|
|
#import "ARTMessage.h"
|
|
#import "ARTBaseMessage+Private.h"
|
|
#import "ARTPaginatedResult+Private.h"
|
|
#import "ARTDataQuery+Private.h"
|
|
#import "ARTJsonEncoder.h"
|
|
#import "ARTAuth+Private.h"
|
|
#import "ARTTokenDetails.h"
|
|
#import "ARTNSArray+ARTFunctional.h"
|
|
#import "ARTPushChannel+Private.h"
|
|
#import "ARTCrypto+Private.h"
|
|
#import "ARTClientOptions.h"
|
|
#import "ARTNSError+ARTUtils.h"
|
|
|
|
@implementation ARTRestChannel {
|
|
ARTQueuedDealloc *_dealloc;
|
|
}
|
|
|
|
- (instancetype)initWithInternal:(ARTRestChannelInternal *)internal queuedDealloc:(ARTQueuedDealloc *)dealloc {
|
|
self = [super init];
|
|
if (self) {
|
|
_internal = internal;
|
|
_dealloc = dealloc;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (ARTRestPresence*) presence {
|
|
return [[ARTRestPresence alloc] initWithInternal:_internal.presence queuedDealloc:_dealloc];
|
|
}
|
|
|
|
- (ARTPushChannel *)push {
|
|
return [[ARTPushChannel alloc] initWithInternal:_internal.push queuedDealloc:_dealloc];
|
|
}
|
|
|
|
- (NSString *)name {
|
|
return _internal.name;
|
|
}
|
|
|
|
- (BOOL)history:(nullable ARTDataQuery *)query callback:(ARTPaginatedMessagesCallback)callback error:(NSError *_Nullable *_Nullable)errorPtr {
|
|
return [_internal history:query callback:callback error:errorPtr];
|
|
}
|
|
|
|
- (void)publish:(nullable NSString *)name data:(nullable id)data {
|
|
[_internal publish:name data:data];
|
|
}
|
|
|
|
- (void)publish:(nullable NSString *)name data:(nullable id)data callback:(nullable ARTCallback)callback {
|
|
[_internal publish:name data:data callback:callback];
|
|
}
|
|
|
|
- (void)publish:(nullable NSString *)name data:(nullable id)data clientId:(NSString *)clientId {
|
|
[_internal publish:name data:data clientId:clientId];
|
|
}
|
|
|
|
- (void)publish:(nullable NSString *)name data:(nullable id)data clientId:(NSString *)clientId callback:(nullable ARTCallback)callback {
|
|
[_internal publish:name data:data clientId:clientId callback:callback];
|
|
}
|
|
|
|
- (void)publish:(nullable NSString *)name data:(nullable id)data extras:(nullable id<ARTJsonCompatible>)extras {
|
|
[_internal publish:name data:data extras:extras];
|
|
}
|
|
|
|
- (void)publish:(nullable NSString *)name data:(nullable id)data extras:(nullable id<ARTJsonCompatible>)extras callback:(nullable ARTCallback)callback {
|
|
[_internal publish:name data:data extras:extras callback:callback];
|
|
}
|
|
|
|
- (void)publish:(nullable NSString *)name data:(nullable id)data clientId:(NSString *)clientId extras:(nullable id<ARTJsonCompatible>)extras {
|
|
[_internal publish:name data:data clientId:clientId extras:extras];
|
|
}
|
|
|
|
- (void)publish:(nullable NSString *)name data:(nullable id)data clientId:(NSString *)clientId extras:(nullable id<ARTJsonCompatible>)extras callback:(nullable ARTCallback)callback {
|
|
[_internal publish:name data:data clientId:clientId extras:extras callback:callback];
|
|
}
|
|
|
|
- (void)publish:(NSArray<ARTMessage *> *)messages {
|
|
[_internal publish:messages];
|
|
}
|
|
|
|
- (void)publish:(NSArray<ARTMessage *> *)messages callback:(nullable ARTCallback)callback {
|
|
[_internal publish:messages callback:callback];
|
|
}
|
|
|
|
- (void)history:(ARTPaginatedMessagesCallback)callback {
|
|
[_internal history:callback];
|
|
}
|
|
|
|
- (ARTChannelOptions *)options {
|
|
return [_internal options];
|
|
}
|
|
|
|
- (void)setOptions:(ARTChannelOptions *_Nullable)options {
|
|
[_internal setOptions:options];
|
|
}
|
|
|
|
@end
|
|
|
|
static const NSUInteger kIdempotentLibraryGeneratedIdLength = 9; //bytes
|
|
|
|
@implementation ARTRestChannelInternal {
|
|
@private
|
|
dispatch_queue_t _userQueue;
|
|
ARTRestPresenceInternal *_presence;
|
|
ARTPushChannelInternal *_pushChannel;
|
|
@public
|
|
NSString *_basePath;
|
|
}
|
|
|
|
@dynamic options;
|
|
|
|
- (instancetype)initWithName:(NSString *)name withOptions:(ARTChannelOptions *)options andRest:(ARTRestInternal *)rest {
|
|
if (self = [super initWithName:name andOptions:options rest:rest]) {
|
|
_rest = rest;
|
|
_queue = rest.queue;
|
|
_userQueue = rest.userQueue;
|
|
_basePath = [NSString stringWithFormat:@"/channels/%@", [name stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]];
|
|
[self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p instantiating under '%@'", self, name];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (ARTLog *)getLogger {
|
|
return _rest.logger;
|
|
}
|
|
|
|
- (NSString *)getBasePath {
|
|
return _basePath;
|
|
}
|
|
|
|
- (ARTRestPresenceInternal *)presence {
|
|
if (!_presence) {
|
|
_presence = [[ARTRestPresenceInternal alloc] initWithChannel:self];
|
|
}
|
|
return _presence;
|
|
}
|
|
|
|
- (ARTPushChannelInternal *)push {
|
|
if (!_pushChannel) {
|
|
_pushChannel = [[ARTPushChannelInternal alloc] init:self.rest withChannel:self];
|
|
}
|
|
return _pushChannel;
|
|
}
|
|
|
|
- (void)history:(ARTPaginatedMessagesCallback)callback {
|
|
[self history:[[ARTDataQuery alloc] init] callback:callback error:nil];
|
|
}
|
|
|
|
- (BOOL)history:(ARTDataQuery *)query callback:(ARTPaginatedMessagesCallback)callback error:(NSError * __autoreleasing *)errorPtr {
|
|
if (callback) {
|
|
void (^userCallback)(__GENERIC(ARTPaginatedResult, ARTMessage *) *result, ARTErrorInfo *error) = callback;
|
|
callback = ^(__GENERIC(ARTPaginatedResult, ARTMessage *) *result, ARTErrorInfo *error) {
|
|
dispatch_async(self->_userQueue, ^{
|
|
userCallback(result, error);
|
|
});
|
|
};
|
|
}
|
|
|
|
__block BOOL ret;
|
|
dispatch_sync(_queue, ^{
|
|
if (query.limit > 1000) {
|
|
if (errorPtr) {
|
|
*errorPtr = [NSError errorWithDomain:ARTAblyErrorDomain
|
|
code:ARTDataQueryErrorLimit
|
|
userInfo:@{NSLocalizedDescriptionKey:@"Limit supports up to 1000 results only"}];
|
|
}
|
|
ret = NO;
|
|
return;
|
|
}
|
|
if ([query.start compare:query.end] == NSOrderedDescending) {
|
|
if (errorPtr) {
|
|
*errorPtr = [NSError errorWithDomain:ARTAblyErrorDomain
|
|
code:ARTDataQueryErrorTimestampRange
|
|
userInfo:@{NSLocalizedDescriptionKey:@"Start must be equal to or less than end"}];
|
|
}
|
|
ret = NO;
|
|
return;
|
|
}
|
|
|
|
NSURLComponents *componentsUrl = [NSURLComponents componentsWithString:[self->_basePath stringByAppendingPathComponent:@"messages"]];
|
|
NSError *error = nil;
|
|
componentsUrl.queryItems = [query asQueryItems:&error];
|
|
if (error) {
|
|
if (errorPtr) {
|
|
*errorPtr = error;
|
|
}
|
|
ret = NO;
|
|
return;
|
|
}
|
|
|
|
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:componentsUrl.URL];
|
|
|
|
ARTPaginatedResultResponseProcessor responseProcessor = ^NSArray<ARTMessage *> *(NSHTTPURLResponse *response, NSData *data, NSError **errorPtr) {
|
|
id<ARTEncoder> encoder = [self->_rest.encoders objectForKey:response.MIMEType];
|
|
return [[encoder decodeMessages:data error:errorPtr] artMap:^(ARTMessage *message) {
|
|
NSError *decodeError = nil;
|
|
message = [message decodeWithEncoder:self.dataEncoder error:&decodeError];
|
|
if (decodeError != nil) {
|
|
ARTErrorInfo *errorInfo = [ARTErrorInfo wrap:[ARTErrorInfo createWithCode:ARTErrorUnableToDecodeMessage message:decodeError.localizedFailureReason] prepend:@"Failed to decode data: "];
|
|
[self.logger error:@"RS:%p C:%p (%@) %@", self->_rest, self, self.name, errorInfo.message];
|
|
}
|
|
return message;
|
|
}];
|
|
};
|
|
|
|
[self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p C:%p (%@) stats request %@", self->_rest, self, self.name, request];
|
|
[ARTPaginatedResult executePaginated:self->_rest withRequest:request andResponseProcessor:responseProcessor callback:callback];
|
|
ret = YES;
|
|
});
|
|
return ret;
|
|
}
|
|
|
|
- (void)internalPostMessages:(id)data callback:(ARTCallback)callback {
|
|
if (callback) {
|
|
ARTCallback userCallback = callback;
|
|
callback = ^(ARTErrorInfo *__art_nullable error) {
|
|
dispatch_async(self->_userQueue, ^{
|
|
userCallback(error);
|
|
});
|
|
};
|
|
}
|
|
|
|
dispatch_async(_queue, ^{
|
|
NSData *encodedMessage = nil;
|
|
|
|
if ([data isKindOfClass:[ARTMessage class]]) {
|
|
ARTMessage *message = (ARTMessage *)data;
|
|
|
|
NSString *baseId = nil;
|
|
if (self.rest.options.idempotentRestPublishing && message.isIdEmpty) {
|
|
NSData *baseIdData = [ARTCrypto generateSecureRandomData:kIdempotentLibraryGeneratedIdLength];
|
|
baseId = [baseIdData base64EncodedStringWithOptions:0];
|
|
message.id = [NSString stringWithFormat:@"%@:0", baseId];
|
|
}
|
|
|
|
if (message.clientId && self.rest.auth.clientId_nosync && ![message.clientId isEqualToString:self.rest.auth.clientId_nosync]) {
|
|
callback([ARTErrorInfo createWithCode:ARTStateMismatchedClientId message:@"attempted to publish message with an invalid clientId"]);
|
|
return;
|
|
}
|
|
|
|
NSError *encodeError = nil;
|
|
encodedMessage = [self.rest.defaultEncoder encodeMessage:message error:&encodeError];
|
|
if (encodeError) {
|
|
callback([ARTErrorInfo createFromNSError:encodeError]);
|
|
return;
|
|
}
|
|
}
|
|
else if ([data isKindOfClass:[NSArray class]]) {
|
|
NSArray<ARTMessage *> *messages = (NSArray *)data;
|
|
|
|
NSString *baseId = nil;
|
|
if (self.rest.options.idempotentRestPublishing) {
|
|
BOOL messagesHaveEmptyId = [messages artFilter:^BOOL(ARTMessage *m) { return !m.isIdEmpty; }].count <= 0;
|
|
if (messagesHaveEmptyId) {
|
|
NSData *baseIdData = [ARTCrypto generateSecureRandomData:kIdempotentLibraryGeneratedIdLength];
|
|
baseId = [baseIdData base64EncodedStringWithOptions:0];
|
|
}
|
|
}
|
|
|
|
NSInteger serial = 0;
|
|
for (ARTMessage *message in messages) {
|
|
if (message.clientId && self.rest.auth.clientId_nosync && ![message.clientId isEqualToString:self.rest.auth.clientId_nosync]) {
|
|
callback([ARTErrorInfo createWithCode:ARTStateMismatchedClientId message:@"attempted to publish message with an invalid clientId"]);
|
|
return;
|
|
}
|
|
if (baseId) {
|
|
message.id = [NSString stringWithFormat:@"%@:%ld", baseId, (long)serial];
|
|
}
|
|
serial += 1;
|
|
}
|
|
|
|
NSError *encodeError = nil;
|
|
encodedMessage = [self.rest.defaultEncoder encodeMessages:data error:&encodeError];
|
|
if (encodeError) {
|
|
callback([ARTErrorInfo createFromNSError:encodeError]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:[self->_basePath stringByAppendingPathComponent:@"messages"]] resolvingAgainstBaseURL:YES];
|
|
NSMutableArray<NSURLQueryItem *> *queryItems = [NSMutableArray new];
|
|
|
|
if (queryItems.count > 0) {
|
|
components.queryItems = queryItems;
|
|
}
|
|
|
|
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]];
|
|
request.HTTPMethod = @"POST";
|
|
request.HTTPBody = encodedMessage;
|
|
|
|
if (self.rest.defaultEncoding) {
|
|
[request setValue:self.rest.defaultEncoding forHTTPHeaderField:@"Content-Type"];
|
|
}
|
|
|
|
[self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p C:%p (%@) post message %@", self->_rest, self, self.name, [[NSString alloc] initWithData:encodedMessage encoding:NSUTF8StringEncoding]];
|
|
|
|
[self->_rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
|
|
if (callback) {
|
|
ARTErrorInfo *errorInfo;
|
|
if (self->_rest.options.addRequestIds) {
|
|
errorInfo = error ? [ARTErrorInfo wrap:[ARTErrorInfo createFromNSError:error] prepend:[NSString stringWithFormat:@"Request '%@' failed with ", request.URL]] : nil;
|
|
} else {
|
|
errorInfo = error ? [ARTErrorInfo createFromNSError:error] : nil;
|
|
}
|
|
|
|
callback(errorInfo);
|
|
}
|
|
}];
|
|
});
|
|
}
|
|
|
|
@end
|