427 lines
12 KiB
Objective-C
427 lines
12 KiB
Objective-C
#import "ARTTypes.h"
|
|
|
|
// MARK: Global helper functions
|
|
|
|
NSArray<NSString *> *decomposeKey(NSString *key) {
|
|
return [key componentsSeparatedByString:@":"];
|
|
}
|
|
|
|
NSString *encodeBase64(NSString *value) {
|
|
return [[value dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0];
|
|
}
|
|
|
|
NSString *decodeBase64(NSString *base64) {
|
|
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64 options:0];
|
|
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
}
|
|
|
|
uint64_t dateToMilliseconds(NSDate *date) {
|
|
return (uint64_t)(date.timeIntervalSince1970 * 1000);
|
|
}
|
|
|
|
uint64_t timeIntervalToMilliseconds(NSTimeInterval seconds) {
|
|
return (uint64_t)(seconds * 1000);
|
|
}
|
|
|
|
NSTimeInterval millisecondsToTimeInterval(uint64_t msecs) {
|
|
return ((NSTimeInterval)msecs) / 1000;
|
|
}
|
|
|
|
NSString *generateNonce() {
|
|
// Generate two random numbers up to 8 digits long and concatenate them to produce a 16 digit random number
|
|
NSUInteger r1 = arc4random_uniform(100000000);
|
|
NSUInteger r2 = arc4random_uniform(100000000);
|
|
return [NSString stringWithFormat:@"%08lu%08lu", (long)r1, (long)r2];
|
|
}
|
|
|
|
#pragma mark - ARTConnectionStateChange
|
|
|
|
@implementation ARTConnectionStateChange
|
|
|
|
- (instancetype)initWithCurrent:(ARTRealtimeConnectionState)current previous:(ARTRealtimeConnectionState)previous event:(ARTRealtimeConnectionEvent)event reason:(ARTErrorInfo *)reason {
|
|
return [self initWithCurrent:current previous:previous event:event reason:reason retryIn:(NSTimeInterval)0];
|
|
}
|
|
|
|
- (instancetype)initWithCurrent:(ARTRealtimeConnectionState)current previous:(ARTRealtimeConnectionState)previous event:(ARTRealtimeConnectionEvent)event reason:(ARTErrorInfo *)reason retryIn:(NSTimeInterval)retryIn {
|
|
self = [self init];
|
|
if (self) {
|
|
_current = current;
|
|
_previous = previous;
|
|
_event = event;
|
|
_reason = reason;
|
|
_retryIn = retryIn;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (NSString *)description {
|
|
return [NSString stringWithFormat:@"%@ - \n\t current: %@; \n\t previous: %@; \n\t reason: %@; \n\t retryIn: %f; \n", [super description], ARTRealtimeConnectionStateToStr(_current), ARTRealtimeConnectionStateToStr(_previous), _reason, _retryIn];
|
|
}
|
|
|
|
- (void)setRetryIn:(NSTimeInterval)retryIn {
|
|
_retryIn = retryIn;
|
|
}
|
|
|
|
@end
|
|
|
|
NSString *ARTRealtimeConnectionStateToStr(ARTRealtimeConnectionState state) {
|
|
switch(state) {
|
|
case ARTRealtimeInitialized:
|
|
return @"Initialized"; //0
|
|
case ARTRealtimeConnecting:
|
|
return @"Connecting"; //1
|
|
case ARTRealtimeConnected:
|
|
return @"Connected"; //2
|
|
case ARTRealtimeDisconnected:
|
|
return @"Disconnected"; //3
|
|
case ARTRealtimeSuspended:
|
|
return @"Suspended"; //4
|
|
case ARTRealtimeClosing:
|
|
return @"Closing"; //5
|
|
case ARTRealtimeClosed:
|
|
return @"Closed"; //6
|
|
case ARTRealtimeFailed:
|
|
return @"Failed"; //7
|
|
}
|
|
}
|
|
|
|
NSString *ARTRealtimeConnectionEventToStr(ARTRealtimeConnectionEvent event) {
|
|
switch(event) {
|
|
case ARTRealtimeConnectionEventInitialized:
|
|
return @"Initialized"; //0
|
|
case ARTRealtimeConnectionEventConnecting:
|
|
return @"Connecting"; //1
|
|
case ARTRealtimeConnectionEventConnected:
|
|
return @"Connected"; //2
|
|
case ARTRealtimeConnectionEventDisconnected:
|
|
return @"Disconnected"; //3
|
|
case ARTRealtimeConnectionEventSuspended:
|
|
return @"Suspended"; //4
|
|
case ARTRealtimeConnectionEventClosing:
|
|
return @"Closing"; //5
|
|
case ARTRealtimeConnectionEventClosed:
|
|
return @"Closed"; //6
|
|
case ARTRealtimeConnectionEventFailed:
|
|
return @"Failed"; //7
|
|
case ARTRealtimeConnectionEventUpdate:
|
|
return @"Update"; //8
|
|
}
|
|
}
|
|
|
|
#pragma mark - ARTChannelStateChange
|
|
|
|
@implementation ARTChannelStateChange
|
|
|
|
- (instancetype)initWithCurrent:(ARTRealtimeChannelState)current previous:(ARTRealtimeChannelState)previous event:(ARTChannelEvent)event reason:(ARTErrorInfo *)reason {
|
|
return [self initWithCurrent:current previous:previous event:event reason:reason resumed:NO];
|
|
}
|
|
|
|
- (instancetype)initWithCurrent:(ARTRealtimeChannelState)current previous:(ARTRealtimeChannelState)previous event:(ARTChannelEvent)event reason:(ARTErrorInfo *)reason resumed:(BOOL)resumed {
|
|
self = [self init];
|
|
if (self) {
|
|
_current = current;
|
|
_previous = previous;
|
|
_event = event;
|
|
_reason = reason;
|
|
_resumed = resumed;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (NSString *)description {
|
|
return [NSString stringWithFormat:@"%@ - \n\t current: %@; \n\t previous: %@; \n\t reason: %@; \n\t resumed: %d; \n", [super description], ARTRealtimeChannelStateToStr(_current), ARTRealtimeChannelStateToStr(_previous), _reason, _resumed];
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - ARTEventIdentification
|
|
|
|
@implementation NSString (ARTEventIdentification)
|
|
|
|
- (NSString *)identification {
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - ARTJsonCompatible
|
|
|
|
@implementation NSString (ARTJsonCompatible)
|
|
|
|
- (NSDictionary *)toJSON:(NSError *_Nullable *_Nullable)error {
|
|
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
|
|
NSError *jsonError = nil;
|
|
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
|
|
if (jsonError) {
|
|
if (error) {
|
|
*error = jsonError;
|
|
}
|
|
return nil;
|
|
}
|
|
if (![json isKindOfClass:[NSDictionary class]]) {
|
|
if (error) {
|
|
*error = [NSError errorWithDomain:ARTAblyErrorDomain code:0 userInfo:@{NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"expected JSON object, got %@", [json class]]}];
|
|
}
|
|
return nil;
|
|
}
|
|
return (NSDictionary *)json;
|
|
}
|
|
|
|
- (NSString *)toJSONString {
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation NSDictionary (ARTJsonCompatible)
|
|
|
|
- (NSDictionary *)toJSON:(NSError *_Nullable *_Nullable)error {
|
|
if (error) {
|
|
*error = nil;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (NSString *)toJSONString {
|
|
NSError *err = nil;
|
|
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self options:0 error:&err];
|
|
if (err) {
|
|
return nil;
|
|
}
|
|
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
|
return jsonString;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation NSURL (ARTLog)
|
|
|
|
- (NSString *)description {
|
|
return [NSString stringWithFormat:@"%@", self.absoluteString];
|
|
}
|
|
|
|
@end
|
|
|
|
NSString *ARTRealtimeChannelStateToStr(ARTRealtimeChannelState state) {
|
|
switch (state) {
|
|
case ARTRealtimeChannelInitialized:
|
|
return @"Initialized"; //0
|
|
case ARTRealtimeChannelAttaching:
|
|
return @"Attaching"; //1
|
|
case ARTRealtimeChannelAttached:
|
|
return @"Attached"; //2
|
|
case ARTRealtimeChannelDetaching:
|
|
return @"Detaching"; //3
|
|
case ARTRealtimeChannelDetached:
|
|
return @"Detached"; //4
|
|
case ARTRealtimeChannelSuspended:
|
|
return @"Suspended"; //5
|
|
case ARTRealtimeChannelFailed:
|
|
return @"Failed"; //6
|
|
}
|
|
}
|
|
|
|
NSString *ARTChannelEventToStr(ARTChannelEvent event) {
|
|
switch (event) {
|
|
case ARTChannelEventInitialized:
|
|
return @"Initialized"; //0
|
|
case ARTChannelEventAttaching:
|
|
return @"Attaching"; //1
|
|
case ARTChannelEventAttached:
|
|
return @"Attached"; //2
|
|
case ARTChannelEventDetaching:
|
|
return @"Detaching"; //3
|
|
case ARTChannelEventDetached:
|
|
return @"Detached"; //4
|
|
case ARTChannelEventSuspended:
|
|
return @"Suspended"; //5
|
|
case ARTChannelEventFailed:
|
|
return @"Failed"; //6
|
|
case ARTChannelEventUpdate:
|
|
return @"Update"; //7
|
|
}
|
|
}
|
|
|
|
@implementation NSDictionary (ARTURLQueryItemAdditions)
|
|
|
|
- (NSArray<NSURLQueryItem *> *)art_asURLQueryItems {
|
|
NSMutableArray<NSURLQueryItem *> *items = [NSMutableArray new];
|
|
for (id key in [self allKeys]) {
|
|
id value = [self valueForKey:key];
|
|
if ([key isKindOfClass:[NSString class]] && [value isKindOfClass:[NSString class]]) {
|
|
[items addObject:[NSURLQueryItem queryItemWithName:key value:value]];
|
|
}
|
|
}
|
|
return items;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation NSMutableArray (ARTQueueAdditions)
|
|
|
|
- (void)art_enqueue:(id)object {
|
|
[self addObject:object];
|
|
}
|
|
|
|
- (id)art_dequeue {
|
|
id item = [self firstObject];
|
|
if (item) {
|
|
[self removeObjectAtIndex:0];
|
|
}
|
|
return item;
|
|
}
|
|
|
|
- (id)art_peek {
|
|
return [self firstObject];
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - NSString (ARTUtilities)
|
|
|
|
@implementation NSString (ARTUtilities)
|
|
|
|
- (NSString *)art_shortString {
|
|
NSRange stringRange = {0, MIN([self length], 1000)}; //1KB
|
|
stringRange = [self rangeOfComposedCharacterSequencesForRange:stringRange];
|
|
return [self substringWithRange:stringRange];
|
|
}
|
|
|
|
- (NSString *)art_base64Encoded {
|
|
return encodeBase64(self);
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - NSDate (ARTUtilities)
|
|
|
|
@implementation NSDate (ARTUtilities)
|
|
|
|
+ (NSDate *)art_dateWithMillisecondsSince1970:(uint64_t)msecs {
|
|
return [NSDate dateWithTimeIntervalSince1970:millisecondsToTimeInterval(msecs)];
|
|
}
|
|
|
|
@end
|
|
|
|
@interface ARTCancellableFromCallback : NSObject<ARTCancellable>
|
|
+(instancetype)new NS_UNAVAILABLE;
|
|
-(instancetype)init NS_UNAVAILABLE;
|
|
-(instancetype)initWithCallback:(ARTResultCallback)callback;
|
|
@property(nonatomic, readonly) ARTResultCallback wrapper;
|
|
@end
|
|
|
|
@implementation ARTCancellableFromCallback {
|
|
id _lock;
|
|
|
|
// _callback will be nil once either cancel or invoke has been called on
|
|
// this instance.
|
|
volatile ARTResultCallback _callback;
|
|
}
|
|
|
|
@synthesize wrapper = _wrapper;
|
|
|
|
-(instancetype)initWithCallback:(const ARTResultCallback)callback {
|
|
if (!callback) {
|
|
[NSException raise:NSInternalInconsistencyException format:@"callback is nil."];
|
|
}
|
|
self = [super init];
|
|
if (!self) {
|
|
return nil;
|
|
}
|
|
|
|
_lock = [NSObject new];
|
|
_callback = callback;
|
|
|
|
__weak ARTCancellableFromCallback * _cancellable = self;
|
|
_wrapper = ^(const id result, NSError *const error) {
|
|
// _cancellable is a weak self so may, legitimately, be nil now.
|
|
ARTCancellableFromCallback *const cancellable = _cancellable;
|
|
[cancellable invokeWithResult:result error:error];
|
|
};
|
|
|
|
return self;
|
|
}
|
|
|
|
-(void)cancel {
|
|
@synchronized (_lock) {
|
|
_callback = nil;
|
|
}
|
|
}
|
|
|
|
-(void)invokeWithResult:(const id)result error:(NSError *const)error {
|
|
ARTResultCallback callback;
|
|
|
|
@synchronized (_lock) {
|
|
callback = _callback;
|
|
_callback = nil;
|
|
}
|
|
|
|
if (callback) {
|
|
callback(result, error);
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation NSObject (ARTArchive)
|
|
|
|
- (nullable NSData *)art_archive {
|
|
#if TARGET_OS_MACCATALYST // if (@available(iOS 13.0, macCatalyst 13.0, ... doesn't help
|
|
NSError *error;
|
|
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self requiringSecureCoding:false error:&error];
|
|
if (error) {
|
|
NSLog(@"%@ archive failed: %@", [self class], error);
|
|
}
|
|
return data;
|
|
#else
|
|
if (@available(macOS 10.13, iOS 11, tvOS 11, *)) {
|
|
NSError *error;
|
|
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self requiringSecureCoding:false error:&error];
|
|
if (error) {
|
|
NSLog(@"%@ archive failed: %@", [self class], error);
|
|
}
|
|
return data;
|
|
}
|
|
else {
|
|
return [NSKeyedArchiver archivedDataWithRootObject:self];
|
|
}
|
|
#endif
|
|
}
|
|
|
|
+ (nullable id)art_unarchiveFromData:(NSData *)data {
|
|
NSSet* allowedTypes = [NSSet setWithArray:@[ [NSArray class], [NSDictionary class], self]];
|
|
#if TARGET_OS_MACCATALYST
|
|
NSError *error;
|
|
id result = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowedTypes fromData:data error:&error];
|
|
if (error) {
|
|
NSLog(@"%@ unarchive failed: %@", self, error);
|
|
}
|
|
return result;
|
|
#else
|
|
if (@available(macOS 10.13, iOS 11, tvOS 11, *)) {
|
|
NSError *error;
|
|
id result = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowedTypes fromData:data error:&error];
|
|
if (error) {
|
|
NSLog(@"%@ unarchive failed: %@", self, error);
|
|
}
|
|
return result;
|
|
}
|
|
else {
|
|
return [NSKeyedUnarchiver unarchiveObjectWithData:data];
|
|
}
|
|
#endif
|
|
}
|
|
|
|
@end
|
|
|
|
NSObject<ARTCancellable> * artCancellableFromCallback(const ARTResultCallback callback, ARTResultCallback *const wrapper) {
|
|
if (!wrapper) {
|
|
[NSException raise:NSInternalInconsistencyException format:@"wrapper is nil."];
|
|
}
|
|
|
|
ARTCancellableFromCallback *const cancellable =
|
|
[[ARTCancellableFromCallback alloc] initWithCallback:callback];
|
|
*wrapper = cancellable.wrapper;
|
|
return cancellable;
|
|
}
|