From dbab6ab54c40a016f75e75a4143576c3fa41c3e0 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Tue, 1 Oct 2019 17:29:46 +0300 Subject: Drop AFNetworking and SSKeychain --- Juick/APIClient.m | 331 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 190 insertions(+), 141 deletions(-) (limited to 'Juick/APIClient.m') diff --git a/Juick/APIClient.m b/Juick/APIClient.m index 2712f1d..3332556 100644 --- a/Juick/APIClient.m +++ b/Juick/APIClient.m @@ -9,10 +9,12 @@ #import "Message.h" #import "Chat.h" -@interface APIClient() -@property(nonatomic, readwrite) AFHTTPSessionManager *manager; +@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 @@ -30,197 +32,244 @@ if (self = [super init]) { NSString *baseURLString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"base_url"]; NSLog(@"Initializing with %@ base URL", baseURLString); - _manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:baseURLString]]; - _manager.requestSerializer = [AFJSONRequestSerializer new]; - [_manager.requestSerializer setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; + 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"]]; - _urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; } return self; } --(AFHTTPSessionManager *) manager { - NSString *username = [SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.username"]; - if (username) { - [_manager.requestSerializer - setAuthorizationHeaderFieldWithUsername:username - password:[SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.password"]]; - } - return _manager; -} - -(void) pullNextFromPath:(NSString *)path params:(NSDictionary *) params callback:(void(^)(NSArray *, NSError *))callback { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self.backgroundQueue addOperationWithBlock:^{ - [self.manager GET:path parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - NSInteger statusCode = [((NSHTTPURLResponse *) task.response) statusCode]; - if (statusCode == 200) { - NSMutableArray *messages = [NSMutableArray new]; - [((NSArray *)responseObject) enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [messages addObject:[Message fromJSON:obj]]; - }]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(messages, nil); - }]; - } else { - callback(nil, [NSError errorWithDomain:@"JuickErrorDomain" code:statusCode userInfo:nil]); - } - - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"REST Error: %@", error); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - NSInteger statusCode = ((NSHTTPURLResponse *)task.response).statusCode; - callback(nil, [NSError errorWithDomain:@"JuickErrorDomain" code:statusCode userInfo:nil]); - }]; + [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.backgroundQueue addOperationWithBlock:^{ - [self.manager POST:@"post" parameters:nil constructingBodyWithBlock:^(id _Nonnull formData) { - [formData appendPartWithFormData:[text dataUsingEncoding:NSUTF8StringEncoding] name:@"body"]; - } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - NSLog(@"Success!"); - Message *result = [Message fromJSON:responseObject]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(result, nil); - }]; - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"Error: %@", [error localizedDescription]); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(nil, error); - }]; - }]; + [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.backgroundQueue addOperationWithBlock:^{ - [self.manager POST:@"comment" parameters:nil constructingBodyWithBlock:^(id _Nonnull formData) { - [formData appendPartWithFormData:[[mid stringValue] dataUsingEncoding:NSUTF8StringEncoding] name:@"mid"]; - [formData appendPartWithFormData:[[NSString stringWithFormat:@"%d", [rid intValue]] dataUsingEncoding:NSUTF8StringEncoding] name:@"rid"]; - [formData appendPartWithFormData:[text dataUsingEncoding:NSUTF8StringEncoding] name:@"body"]; - } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { + [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!"); - Message *reply = [Message fromJSON:responseObject]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(reply, nil); - }]; - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"Error: %@", [error localizedDescription]); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(nil, error); - }]; - }]; + reply = [Message fromJSON:response]; + } + callback(reply, err); }]; } + -(void) postPMToUser:(NSString *)uname text:(NSString *)text result:(void (^)(NSError *))callback { [self.backgroundQueue addOperationWithBlock:^{ - [self.manager POST:@"pm" parameters:nil constructingBodyWithBlock:^(id _Nonnull formData) { - [formData appendPartWithFormData:[text dataUsingEncoding:NSUTF8StringEncoding] name:@"body"]; - [formData appendPartWithFormData:[uname dataUsingEncoding:NSUTF8StringEncoding] name:@"uname"]; - } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(nil); - }]; - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"Error: %@", [error localizedDescription]); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(error); - }]; + [self post:@"pm" params:@{ + @"uname": uname, + @"body": text + } callback:^(NSDictionary *response, NSError *err) { + callback(err); }]; }]; } -(void) fetchChats:(void (^)(NSArray *, NSError *))callback { - [self.backgroundQueue addOperationWithBlock:^{ - [self.manager GET:@"groups_pms" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - NSMutableArray *groups = [NSMutableArray new]; - NSArray *pms = [(NSDictionary *)responseObject objectForKey:@"pms"]; - [pms enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [groups addObject:[Chat fromJSON:obj]]; - }]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(groups, nil); - }]; - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"Error: %@", [error localizedDescription]); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(nil, error); - }]; + [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}; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self.backgroundQueue addOperationWithBlock:^{ - [self.manager GET:@"pm" parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - NSMutableArray *messages = [NSMutableArray new]; - NSArray *messagesList = (NSArray *)responseObject; - [messagesList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [messages addObject:[Message fromJSON:obj]]; - }]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(messages, nil); - }]; - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"Error: %@", [error localizedDescription]); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(nil, error); - }]; + [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.manager GET:@"me" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - NSString *username = [SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.username"]; - NSString *password = [SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.password"]; - [self.manager.requestSerializer setAuthorizationHeaderFieldWithUsername:username password:password]; - callback([User fromJSON:responseObject], nil); - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - [self.manager.requestSerializer clearAuthorizationHeader]; - callback(nil, error); + [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.manager GET:@"users" parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - callback([User fromJSON:[(NSArray *)responseObject firstObject]]); - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - callback(nil); + [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 { - [self.manager PUT:@"/notifications" parameters:@[[registrationData toJSON]] success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - callback(YES); - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"fail %@", [error localizedDescription]); - callback(NO); - } ]; + 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 { + [[NSURLCredentialStorage sharedCredentialStorage] setCredential:credential forProtectionSpace:self.apiProtectionSpace]; +} + + -(BOOL) isAuthenticated { - NSString *password = [SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.password"]; + NSString *password = self.credential.password; return password != nil; } -- (void)fetchImageWithURL:(NSURL *)url callback:(void (^)(NSData *))callback { - NSURLSessionDataTask *dataTask = [self.urlSession dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(data); - }]; +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { + if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) { + completionHandler(NSURLSessionAuthChallengeUseCredential, self.credential); + } else { + 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:^{ - [dataTask resume]; + [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) { + NSError *jsonError; + NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + if (jsonError) { + callback(nil, jsonError); + } else { + callback(jsonData, nil); + } + }]; +} + +-(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); + } }]; } -- cgit v1.2.3