diff --git a/README.md b/README.md index cda5e4296..d10d1193d 100644 --- a/README.md +++ b/README.md @@ -308,18 +308,11 @@ let clientOptions = ARTClientOptions() clientOptions.authCallback = { params, callback in getTokenRequestJSONFromYourServer(params) { json, error in //handle error - let tokenParams = ARTTokenParams(clientId:json["clientId"]) - - let tokenRequest = ARTTokenRequest(tokenParams:tokenParams, - keyName:json["keyName"], - nonce:json["nonce"], - mac:json["mac"]) - tokenRequest.clientId = json["clientId"] - tokenRequest.ttl = json["ttl"] - tokenRequest.capability = json["capability"] - tokenRequest.timestamp = NSDate(timeIntervalSince1970:(json["timestamp"] / 1000)) - - callback(tokenRequest, nil) + do { + callback(try ARTTokenRequest.fromJSON(json), nil) + } catch let error as NSError { + callback(nil, error) + } } } @@ -333,18 +326,8 @@ ARTClientOptions *clientOptions = [[ARTClientOptions alloc] init]; clientOptions.authCallback = ^(ARTTokenParams *params, void(^callback)(id, NSError*)) { [self getTokenRequestJSONFromYourServer:params completion:^(NSDictionary *json, NSError *error) { //handle error - ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:json[@"clientId"]]; - - ARTTokenRequest *tokenRequest = [[ARTTokenRequest alloc] initWithTokenParams:tokenParams - keyName:json[@"keyName"] - nonce:json[@"nonce"] - mac:json[@"mac"]]; - tokenRequest.clientId = json[@"clientId"]; - tokenRequest.ttl = [json[@"ttl"] doubleValue]; - tokenRequest.capability = json[@"capability"]; - tokenRequest.timestamp = [NSDate dateWithTimeIntervalSince1970:[json[@"timestamp"] doubleValue] / 1000]; - - callback(tokenRequest, nil); + ARTTokenRequest *tokenRequest = [ARTTokenRequest fromJSON:json error:&error]; + callback(tokenRequest, error); }]; }; diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index c175d671b..8d75191d8 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -148,8 +148,7 @@ - (NSDate *)decodeTime:(NSData *)data { if (resp && resp.count == 1) { NSNumber *num = resp[0]; if ([num isKindOfClass:[NSNumber class]]) { - long long msSince1970 = [num longLongValue]; - return [NSDate dateWithTimeIntervalSince1970:(msSince1970 / 1000.0)]; + return [NSDate dateWithTimeIntervalSince1970:([num doubleValue] / 1000.0)]; } } return nil; @@ -386,9 +385,9 @@ - (ARTTokenDetails *)tokenFromDictionary:(NSDictionary *)input error:(NSError * NSString *token = [input artString:@"token"]; NSNumber *expiresTimeInterval = [input objectForKey:@"expires"]; - NSDate *expires = expiresTimeInterval ? [NSDate dateWithTimeIntervalSince1970:expiresTimeInterval.longLongValue / 1000] : nil; + NSDate *expires = expiresTimeInterval ? [NSDate dateWithTimeIntervalSince1970:expiresTimeInterval.doubleValue / 1000] : nil; NSNumber *issuedInterval = [input objectForKey:@"issued"]; - NSDate *issued = issuedInterval ? [NSDate dateWithTimeIntervalSince1970:issuedInterval.longLongValue / 1000] : nil; + NSDate *issued = issuedInterval ? [NSDate dateWithTimeIntervalSince1970:issuedInterval.doubleValue / 1000] : nil; return [[ARTTokenDetails alloc] initWithToken:token expires:expires diff --git a/Source/ARTTokenDetails.h b/Source/ARTTokenDetails.h index a38457529..3cb22d790 100644 --- a/Source/ARTTokenDetails.h +++ b/Source/ARTTokenDetails.h @@ -46,6 +46,8 @@ ART_ASSUME_NONNULL_BEGIN - (instancetype)initWithToken:(NSString *)token; - (instancetype)initWithToken:(NSString *)token expires:(art_nullable NSDate *)expires issued:(art_nullable NSDate *)issued capability:(art_nullable NSString *)capability clientId:(art_nullable NSString *)clientId; ++ (ARTTokenDetails *__art_nullable)fromJSON:(id)json error:(NSError *__art_nullable *__art_nullable)error; + @end @interface ARTTokenDetails (ARTTokenDetailsCompatible) diff --git a/Source/ARTTokenDetails.m b/Source/ARTTokenDetails.m index 978f9631e..772e29953 100644 --- a/Source/ARTTokenDetails.m +++ b/Source/ARTTokenDetails.m @@ -43,6 +43,28 @@ - (id)copyWithZone:(NSZone *)zone { return tokenDetails; } ++ (ARTTokenDetails *__art_nullable)fromJSON:(id)json error:(NSError *__art_nullable *__art_nullable)error { + NSError *e; + NSDictionary *dict = [json toJSON:&e]; + if (e) { + if (error) { + *error = e; + } + return nil; + } + + NSNumber *expiresInterval = [dict objectForKey:@"expires"]; + NSDate *expires = expiresInterval ? [NSDate dateWithTimeIntervalSince1970:(expiresInterval.doubleValue) / 1000] : nil; + NSNumber *issuedInterval = [dict objectForKey:@"issued"]; + NSDate *issued = issuedInterval ? [NSDate dateWithTimeIntervalSince1970:(issuedInterval.doubleValue) / 1000] : nil; + + return [[ARTTokenDetails alloc] initWithToken:dict[@"token"] + expires:expires + issued:issued + capability:dict[@"capability"] + clientId:dict[@"clientId"]]; +} + @end @class ARTAuth; diff --git a/Source/ARTTokenRequest.h b/Source/ARTTokenRequest.h index 9b787659b..00f6d8abf 100644 --- a/Source/ARTTokenRequest.h +++ b/Source/ARTTokenRequest.h @@ -7,6 +7,7 @@ // #import +#import "ARTTypes.h" #import "ARTTokenParams.h" #import "ARTAuthOptions.h" @@ -56,6 +57,8 @@ ART_ASSUME_NONNULL_BEGIN - (instancetype)init UNAVAILABLE_ATTRIBUTE; - (instancetype)initWithTokenParams:(ARTTokenParams *)tokenParams keyName:(NSString *)keyName nonce:(NSString *)nonce mac:(NSString *)mac; ++ (ARTTokenRequest *__art_nullable)fromJSON:(id)json error:(NSError *__art_nullable *__art_nullable)error; + @end @interface ARTTokenRequest (ARTTokenDetailsCompatible) diff --git a/Source/ARTTokenRequest.m b/Source/ARTTokenRequest.m index 999170ce5..9f00ad354 100644 --- a/Source/ARTTokenRequest.m +++ b/Source/ARTTokenRequest.m @@ -34,6 +34,30 @@ - (NSString *)description { self.keyName, self.clientId, self.nonce, self.mac, self.ttl, self.capability, self.timestamp]; } ++ (ARTTokenRequest *__art_nullable)fromJSON:(id)json error:(NSError *__art_nullable *__art_nullable)error { + NSError *e; + NSDictionary *dict = [json toJSON:&e]; + if (e) { + if (error) { + *error = e; + } + return nil; + } + + ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:dict[@"clientId"]]; + + ARTTokenRequest *tokenRequest = [[ARTTokenRequest alloc] initWithTokenParams:tokenParams + keyName:dict[@"keyName"] + nonce:dict[@"nonce"] + mac:dict[@"mac"]]; + tokenRequest.clientId = dict[@"clientId"]; + tokenRequest.ttl = millisecondsToTimeInterval([dict[@"ttl"] doubleValue]); + tokenRequest.capability = dict[@"capability"]; + tokenRequest.timestamp = [NSDate dateWithTimeIntervalSince1970:[dict[@"timestamp"] doubleValue] / 1000]; + + return tokenRequest; +} + @end @implementation ARTTokenRequest (ARTTokenDetailsCompatible) diff --git a/Source/ARTTypes.h b/Source/ARTTypes.h index f491b240c..e416d4086 100644 --- a/Source/ARTTypes.h +++ b/Source/ARTTypes.h @@ -8,6 +8,7 @@ #import #import "CompatibilityMacros.h" +#import "ARTStatus.h" @class ARTStatus; @class ARTHttpResponse; @@ -113,4 +114,14 @@ NSString *generateNonce(); @end +@protocol ARTJsonCompatible +- (NSDictionary *__art_nullable)toJSON:(NSError *__art_nullable *__art_nullable)error; +@end + +@interface NSString (ARTJsonCompatible) +@end + +@interface NSDictionary (ARTJsonCompatible) +@end + ART_ASSUME_NONNULL_END diff --git a/Source/ARTTypes.m b/Source/ARTTypes.m index 3ada074d2..d2a955b9d 100644 --- a/Source/ARTTypes.m +++ b/Source/ARTTypes.m @@ -94,3 +94,37 @@ - (void)setRetryIn:(NSTimeInterval)retryIn { } @end + +@implementation NSString (ARTJsonCompatible) + +- (NSDictionary *)toJSON:(NSError *__art_nullable *__art_nullable)error { + NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding]; + NSError *jsonError; + 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; +} + +@end + +@implementation NSDictionary (ARTJsonCompatible) + +- (NSDictionary *)toJSON:(NSError *__art_nullable *__art_nullable)error { + if (error) { + *error = nil; + } + return self; +} + +@end diff --git a/Spec/Auth.swift b/Spec/Auth.swift index 53ee7d0e1..5393b73c9 100644 --- a/Spec/Auth.swift +++ b/Spec/Auth.swift @@ -2749,5 +2749,87 @@ class Auth : QuickSpec { } } } + + describe("TokenRequest") { + // TE6 + describe("fromJSON") { + let json = "{" + + " \"clientId\":\"myClientId\"," + + " \"mac\":\"4rr4J+JzjiCL1DoS8wq7k11Z4oTGCb1PoeN+yGjkaH4=\"," + + " \"capability\":\"{\\\"test\\\":[\\\"publish\\\"]}\"," + + " \"ttl\":42000," + + " \"timestamp\":1479087321934," + + " \"keyName\":\"xxxxxx.yyyyyy\"," + + " \"nonce\":\"7830658976108826\"" + + "}" + + func check(request: ARTTokenRequest) { + expect(request.clientId).to(equal("myClientId")) + expect(request.mac).to(equal("4rr4J+JzjiCL1DoS8wq7k11Z4oTGCb1PoeN+yGjkaH4=")) + expect(request.capability).to(equal("{\"test\":[\"publish\"]}")) + expect(request.ttl).to(equal(NSTimeInterval(42))) + expect(request.timestamp).to(equal(NSDate(timeIntervalSince1970: 1479087321.934))) + expect(request.keyName).to(equal("xxxxxx.yyyyyy")) + expect(request.nonce).to(equal("7830658976108826")) + } + + it("accepts a string, which should be interpreted as JSON") { + check(try! ARTTokenRequest.fromJSON(json)) + } + + it("accepts a NSDictionary") { + let data = json.dataUsingEncoding(NSUTF8StringEncoding)! + let dict = try! NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves) as! NSDictionary + check(try! ARTTokenRequest.fromJSON(dict)) + } + + it("rejects invalid JSON") { + expect{try ARTTokenRequest.fromJSON("not JSON")}.to(throwError()) + } + + it("rejects non-object JSON") { + expect{try ARTTokenRequest.fromJSON("[]")}.to(throwError()) + } + } + } + + describe("TokenDetails") { + // TD7 + describe("fromJSON") { + let json = "{" + + " \"token\": \"xxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\"," + + " \"issued\": 1479087321934," + + " \"expires\": 1479087363934," + + " \"capability\": \"{\\\"test\\\":[\\\"publish\\\"]}\"," + + " \"clientId\": \"myClientId\"" + + "}" + + func check(details: ARTTokenDetails) { + expect(details.token).to(equal("xxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy")) + expect(details.issued).to(equal(NSDate(timeIntervalSince1970: 1479087321.934))) + expect(details.expires).to(equal(NSDate(timeIntervalSince1970: 1479087363.934))) + expect(details.capability).to(equal("{\"test\":[\"publish\"]}")) + expect(details.clientId).to(equal("myClientId")) + } + + it("accepts a string, which should be interpreted as JSON") { + check(try! ARTTokenDetails.fromJSON(json)) + } + + it("accepts a NSDictionary") { + let data = json.dataUsingEncoding(NSUTF8StringEncoding)! + let dict = try! NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves) as! NSDictionary + check(try! ARTTokenDetails.fromJSON(dict)) + } + + it("rejects invalid JSON") { + expect{try ARTTokenDetails.fromJSON("not JSON")}.to(throwError()) + } + + it("rejects non-object JSON") { + expect{try ARTTokenDetails.fromJSON("[]")}.to(throwError()) + } + } + } } }