// // API.m // Juick // // Created by Vitaly Takmazov on 22/08/16. // Copyright © 2016 com.juick. All rights reserved. // #import "Message.h" #import "Chat.h" #import "API.h" NSString * const JuickErrorDomain = @"JuickErrorDomain"; @interface API () @property(nonatomic, strong) NSURL *baseURL; @property(nonatomic, strong) NSURLSession *urlSession; -(void) get:(NSString *)path params:(NSDictionary *)params callback:(void(^)(NSDictionary *, NSError *))callback; -(NSString *) authorizationHeader; @end @implementation API -(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.urlSession = [NSURLSession sharedSession]; } return self; } - (NSString *) authorizationHeader { NSString *token = [[NSUserDefaults standardUserDefaults] stringForKey:@"token"]; if (token) { return [NSString stringWithFormat:@"Juick %@", token]; } return nil; } - (void)authenticateWithUser:(NSString *)username password:(NSString *)password callback:(void(^)(BOOL))completed { NSURL *url = [NSURL URLWithString:@"me"relativeToURL:self.baseURL]; NSString *basicAuthString = [NSString stringWithFormat:@"Basic %@", [[[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]]; [self fetchDataWithURL:url data:nil boundary:nil authorizationHeader:basicAuthString callback:^(NSData *response, NSError *err) { if (!err) { NSError *jsonError; NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:response options:0 error:&jsonError]; if (!jsonError) { User *me = [User fromJSON:jsonData]; [[NSUserDefaults standardUserDefaults] setValue:me.token forKeyPath:@"token"]; self.currentUser = me; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completed(YES); }]; } } completed(NO); }]; } - (void)signout { self.currentUser = nil; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"token"]; } -(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)me:(void (^)(User *, NSError *error))callback { [self get:@"me" params:nil callback:^(NSDictionary *response, NSError *err) { User *visitor; if (!err) { visitor = [User fromJSON:response]; self.currentUser = visitor; } callback(visitor, err); }]; } -(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 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 authorizationHeader:self.authorizationHeader callback:^(NSData *response, NSError *err) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ callback(!err); }]; }]; } -(void) fetchDataWithURL:(NSURL *) url data:(NSData *)postData boundary:(NSString *)boundary authorizationHeader:(NSString *)header callback:(void(^)(NSData *, NSError *))callback { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; if (header) { [request addValue:header forHTTPHeaderField:@"Authorization"]; } 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) { if (error) { callback(nil, error); } else { NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; if (!response || statusCode != 200) { NSURL *url = request.URL; NSError *err = [NSError errorWithDomain:JuickErrorDomain code:statusCode userInfo:@{@"url": url.absoluteString}]; callback(nil, err); } else { callback(data, error); } } }]; [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 authorizationHeader:self.authorizationHeader callback:^(NSData *data, NSError *err) { if (err) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ callback(nil, err); }]; } else { NSError *jsonError; NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; if (jsonError) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ callback(nil, jsonError); }]; } else { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 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 authorizationHeader:self.authorizationHeader callback:^(NSData *data, NSError *err) { if (!err) { NSError *jsonError; NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; if (jsonError) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ callback(nil, jsonError); }]; } else { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ callback(jsonData, nil); }]; } } else { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ callback(nil, err); }]; } }]; } -(void) getUserByUri:(NSString *)uri callback:(void (^)(User *))callback { [self post:@"u/" params:@{ @"uri": uri } callback:^(NSDictionary *response, NSError *err) { User *result; if (!err) { result = [User fromJSON:response]; } callback(result); }]; } -(void) fetchImageWithURL:(NSURL *) url callback:(void(^)(NSData *))callback { [self fetchDataWithURL:url data:nil boundary:nil authorizationHeader:self.authorizationHeader callback:^(NSData *data, NSError *err) { if (err) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ callback(nil); }]; } else { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ callback(data); }]; } }]; } +(NSString *) messagesUrl { return @"messages"; } +(NSString *) discussionsUrl { return @"messages/discussions"; } +(NSString *) threadUrl { return @"thread"; } +(NSString *) feedUrl { return @"home"; } +(NSString *) defaultAvatarUrl { return @"https://i.juick.com/av-96.png";; } @end