// // APIClient.m // Juick // // Created by Vitaly Takmazov on 22/08/16. // Copyright © 2016 com.juick. All rights reserved. // #import "APIClient.h" #import "Message.h" #import "Chat.h" @interface APIClient () @property(nonatomic, strong) NSOperationQueue *backgroundQueue; @property(nonatomic, strong) NSURL *baseURL; @property(nonatomic, strong) NSURLSession *urlSession; @property(nonatomic, strong) NSURLProtectionSpace *apiProtectionSpace; -(void) get:(NSString *)path params:(NSDictionary *)params callback:(void(^)(NSDictionary *, NSError *))callback; @end @implementation APIClient @synthesize credential = _credential; +(APIClient *) sharedClient { static APIClient *sharedAPIClient = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedAPIClient = [[self alloc] init]; }); return sharedAPIClient; } -(id)init { if (self = [super init]) { NSString *baseURLString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"base_url"]; NSLog(@"Initializing with %@ base URL", baseURLString); self.baseURL = [NSURL URLWithString:baseURLString]; self.apiProtectionSpace = [[NSURLProtectionSpace alloc] initWithHost:self.baseURL.host port:0 protocol:NSURLProtectionSpaceHTTPS realm:@"Juick" authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; self.backgroundQueue = [NSOperationQueue new]; self.urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:self.backgroundQueue]; self.dateFormatter = [[NSDateFormatter alloc] init]; self.dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss"; [self.dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]]; } return self; } -(void) pullNextFromPath:(NSString *)path params:(NSDictionary *) params callback:(void(^)(NSArray *, NSError *))callback { [self get:path params:params callback:^(NSDictionary *response, NSError *error) { NSMutableArray *messages = [NSMutableArray new]; if (!error) { [((NSArray *)response) enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [messages addObject:[Message fromJSON:obj]]; }]; } callback(messages, error); }]; } -(void) postMessage:(NSString *)text result:(void (^)(Message *, NSError *))callback { [self post:@"post" params:@{ @"body": text } callback:^(NSDictionary *response, NSError *err) { Message *result; if (!err) { result = [Message fromJSON:response[@"newMessage"]]; } callback(result, err); }]; } -(void) postReplyToThread:(NSNumber *)mid inReplyTo:(NSNumber *)rid text:(NSString *)text result:(void(^)(Message *, NSError *))callback { [self post:@"comment" params:@{ @"mid": mid, @"rid": [NSString stringWithFormat:@"%ld", [rid integerValue]], @"body": text } callback:^(NSDictionary *response, NSError *err) { Message *reply; if (!err) { NSLog(@"Success!"); reply = [Message fromJSON:response]; } callback(reply, err); }]; } -(void) postPMToUser:(NSString *)uname text:(NSString *)text result:(void (^)(NSError *))callback { [self.backgroundQueue addOperationWithBlock:^{ [self post:@"pm" params:@{ @"uname": uname, @"body": text } callback:^(NSDictionary *response, NSError *err) { callback(err); }]; }]; } -(void) fetchChats:(void (^)(NSArray *, NSError *))callback { [self get:@"groups_pms" params:nil callback:^(NSDictionary *response, NSError *err) { NSMutableArray *groups = [NSMutableArray new]; NSArray *pms = [response objectForKey:@"pms"]; [pms enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [groups addObject:[Chat fromJSON:obj]]; }]; callback(groups, err); }]; } -(void) fetchChatWithUser:(NSString *)uname callback:(void (^)(NSArray *, NSError *))callback { NSDictionary *params = @{@"uname": uname}; [self get:@"pm" params:params callback:^(NSDictionary *response, NSError *err) { NSMutableArray *messages = [NSMutableArray new]; if (!err) { NSArray *messagesList = (NSArray *)response; [messagesList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [messages addObject:[Message fromJSON:obj]]; }]; } callback(messages, err); }]; } -(void) authenticate:(void (^)(User *user, NSError *error))callback { [self get:@"me" params:nil callback:^(NSDictionary *response, NSError *error) { User *user; if (!error) { user = [User fromJSON:response]; } callback(user, error); }]; } -(void) getUserByName:(NSString *) name callback:(void(^)(User *))callback { NSDictionary *params = @{@"uname": name}; [self get:@"users" params:params callback:^(NSDictionary *response, NSError *err) { if (!err) { callback([User fromJSON:[(NSArray *)response firstObject]]); } else { callback(nil); } }]; } -(void) refreshDeviceRegistration:(DeviceRegistration *)registrationData callback:(void (^)(BOOL))callback { NSURL *notificationsUrl = [NSURL URLWithString:@"notifications" relativeToURL:self.baseURL]; NSError *error; NSDictionary *token = [registrationData toJSON]; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@[token] options:kNilOptions error:&error]; [self fetchDataWithURL:notificationsUrl data:jsonData boundary:nil callback:^(NSData *response, NSError *err) { callback(!err); }]; } - (NSURLCredential *)credential { NSDictionary *credentials = [[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:self.apiProtectionSpace]; return [credentials.objectEnumerator nextObject]; } - (void)setCredential:(NSURLCredential *)credential { if (credential) { [[NSURLCredentialStorage sharedCredentialStorage] removeCredential:_credential forProtectionSpace:self.apiProtectionSpace options:@{NSURLCredentialStorageRemoveSynchronizableCredentials:@YES}]; [[NSURLCredentialStorage sharedCredentialStorage] setCredential:credential forProtectionSpace:self.apiProtectionSpace]; } else { [[NSURLCredentialStorage sharedCredentialStorage] removeCredential:_credential forProtectionSpace:self.apiProtectionSpace]; } } -(BOOL) isAuthenticated { NSString *password = self.credential.password; return password != nil; } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) { if (self.isAuthenticated) { completionHandler(NSURLSessionAuthChallengeUseCredential, self.credential); return; } } completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } -(void) fetchDataWithURL:(NSURL *) url data:(NSData *)postData boundary:(NSString *)boundary callback:(void(^)(NSData *, NSError *))callback { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; if (boundary) { request.HTTPMethod = @"POST"; request.HTTPBody = postData; NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; [request setValue:contentType forHTTPHeaderField: @"Content-Type"]; } else if (postData) { request.HTTPMethod = @"PUT"; request.HTTPBody = postData; [request setValue:@"application/json" forHTTPHeaderField: @"Content-Type"]; } else { request.HTTPMethod = @"GET"; } NSURLSessionDataTask *task = [self.urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; if (!response || statusCode != 200) { NSError *err = [NSError errorWithDomain:@"JuickErrorDomain" code:statusCode userInfo:nil]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ callback(nil, err); }]; } else { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ callback(data, error); }]; } }]; [self.backgroundQueue addOperationWithBlock:^{ [task resume]; }]; } -(NSURL *) url:(NSURL *)url withParameters:(NSDictionary *)params { NSURLComponents *parametersUrl = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES]; NSMutableArray *items = [NSMutableArray array]; for (NSString * key in params) { NSString *value = [NSString stringWithFormat:@"%@", params[key]]; [items addObject:[NSURLQueryItem queryItemWithName:key value:value]]; } [parametersUrl setQueryItems:items]; return [parametersUrl URL]; } -(NSData *) multipartData:(NSDictionary *)params withBoundary:(NSString *)boundary { NSMutableData *body = [NSMutableData data]; for (NSString *key in params) { [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=%@\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:[[NSString stringWithFormat:@"%@\r\n", params[key]] dataUsingEncoding:NSUTF8StringEncoding]]; } [body appendData:[[NSString stringWithFormat:@"--%@--", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; return body; } -(void) get:(NSString *) path params:(NSDictionary *)params callback:(void(^)(NSDictionary *, NSError *))callback { NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL]; NSURL *requestUrl = params ? [self url:url withParameters:params] : url; [self fetchDataWithURL:requestUrl data:nil boundary:nil callback:^(NSData *data, NSError *err) { if (err) { callback(nil, err); } else { NSError *jsonError; NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; if (jsonError) { callback(nil, jsonError); } else { callback(jsonData, nil); } } }]; } -(void) post:(NSString *) path params:(NSDictionary *)params callback:(void(^)(NSDictionary *, NSError *))callback { NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL]; NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]]; [self fetchDataWithURL:url data:[self multipartData:params withBoundary:boundary] boundary:boundary callback:^(NSData *data, NSError *err) { if (!err) { NSError *jsonError; NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; if (jsonError) { callback(nil, jsonError); } else { callback(jsonData, nil); } } else { callback(nil, err); } }]; } -(void) fetchImageWithURL:(NSURL *) url callback:(void(^)(NSData *))callback { [self fetchDataWithURL:url data:nil boundary:nil callback:^(NSData *data, NSError *err) { if (err) { callback(nil); } else { callback(data); } }]; } +(NSString *) messagesUrl { return @"messages"; } +(NSString *) discussionsUrl { return @"messages/discussions"; } +(NSString *) threadUrl { return @"thread"; } +(NSString *) feedUrl { return @"home"; } @end