15d8188f5SBen Roth// Copyright 2015-present 650 Industries. All rights reserved. 25d8188f5SBen Roth 39a7f140aSEric Samelson#import "EXEnvironment.h" 4ec45106eSQuinlan Jung#import "EXHomeModule.h" 55d8188f5SBen Roth#import "EXFileDownloader.h" 68a20a963SEric Samelson#import "EXSession.h" 75d8188f5SBen Roth#import "EXVersions.h" 85d8188f5SBen Roth#import "EXKernelUtil.h" 9*72d142a5SStanisław Chmiela#import "EXClientReleaseType.h" 105d8188f5SBen Roth 115d8188f5SBen Roth#import <React/RCTUtils.h> 125d8188f5SBen Roth 135d8188f5SBen Roth@import UIKit; 145d8188f5SBen Roth 155d8188f5SBen Roth#import <sys/utsname.h> 165d8188f5SBen Roth 175d8188f5SBen RothNSString * const EXNetworkErrorDomain = @"EXNetwork"; 185d8188f5SBen RothNSTimeInterval const EXFileDownloaderDefaultTimeoutInterval = 60; 195d8188f5SBen Roth 205d8188f5SBen Roth@interface EXFileDownloader () <NSURLSessionDataDelegate> 215d8188f5SBen Roth@end 225d8188f5SBen Roth 235d8188f5SBen Roth@implementation EXFileDownloader 245d8188f5SBen Roth 255d8188f5SBen Roth- (instancetype)init 265d8188f5SBen Roth{ 275d8188f5SBen Roth if (self = [super init]) { 285d8188f5SBen Roth _timeoutInterval = EXFileDownloaderDefaultTimeoutInterval; 295d8188f5SBen Roth } 305d8188f5SBen Roth return self; 315d8188f5SBen Roth} 325d8188f5SBen Roth 335d8188f5SBen Roth- (void)downloadFileFromURL:(NSURL *)url 345d8188f5SBen Roth successBlock:(EXFileDownloaderSuccessBlock)successBlock 355d8188f5SBen Roth errorBlock:(EXFileDownloaderErrorBlock)errorBlock 365d8188f5SBen Roth{ 375d8188f5SBen Roth NSURLSessionConfiguration *configuration = _urlSessionConfiguration ?: [NSURLSessionConfiguration defaultSessionConfiguration]; 385d8188f5SBen Roth 395d8188f5SBen Roth // also pass any custom cache policy onto this specific request 405d8188f5SBen Roth NSURLRequestCachePolicy cachePolicy = _urlSessionConfiguration ? _urlSessionConfiguration.requestCachePolicy : NSURLRequestUseProtocolCachePolicy; 415d8188f5SBen Roth 425d8188f5SBen Roth NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; 435d8188f5SBen Roth NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:cachePolicy timeoutInterval:_timeoutInterval]; 445d8188f5SBen Roth [self setHTTPHeaderFields:request]; 455d8188f5SBen Roth 465d8188f5SBen Roth __weak typeof(self) weakSelf = self; 475d8188f5SBen Roth NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 485d8188f5SBen Roth if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) { 495d8188f5SBen Roth NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; 505d8188f5SBen Roth if (httpResponse.statusCode != 200) { 515d8188f5SBen Roth NSStringEncoding encoding = [weakSelf _encodingFromResponse:response]; 525d8188f5SBen Roth NSString *body = [[NSString alloc] initWithData:data encoding:encoding]; 535d8188f5SBen Roth error = [weakSelf _errorFromResponse:httpResponse body:body]; 545d8188f5SBen Roth } 555d8188f5SBen Roth } 565d8188f5SBen Roth 575d8188f5SBen Roth if (error) { 585d8188f5SBen Roth errorBlock(error, response); 595d8188f5SBen Roth } else { 605d8188f5SBen Roth successBlock(data, response); 615d8188f5SBen Roth } 625d8188f5SBen Roth }]; 635d8188f5SBen Roth [task resume]; 645d8188f5SBen Roth [session finishTasksAndInvalidate]; 655d8188f5SBen Roth} 665d8188f5SBen Roth 675d8188f5SBen Roth#pragma mark - Configuring the request 685d8188f5SBen Roth 695d8188f5SBen Roth- (void)setHTTPHeaderFields:(NSMutableURLRequest *)request 705d8188f5SBen Roth{ 715d8188f5SBen Roth [request setValue:[self _userAgentString] forHTTPHeaderField:@"User-Agent"]; 725d8188f5SBen Roth 735d8188f5SBen Roth NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; 745d8188f5SBen Roth [request setValue:version forHTTPHeaderField:@"Exponent-Version"]; 755d8188f5SBen Roth NSString *requestAbiVersion; 766b1d8f53SEric Samelson if (_abiVersion && _abiVersion.length) { 775d8188f5SBen Roth requestAbiVersion = _abiVersion; 785d8188f5SBen Roth } else { 795d8188f5SBen Roth NSArray *versionsAvailable = [EXVersions sharedInstance].versions[@"sdkVersions"]; 805d8188f5SBen Roth if (versionsAvailable) { 815d8188f5SBen Roth requestAbiVersion = [versionsAvailable componentsJoinedByString:@","]; 825d8188f5SBen Roth } else { 835d8188f5SBen Roth requestAbiVersion = [EXVersions sharedInstance].temporarySdkVersion; 845d8188f5SBen Roth } 855d8188f5SBen Roth } 865d8188f5SBen Roth NSString *releaseChannel; 875d8188f5SBen Roth if (_releaseChannel) { 885d8188f5SBen Roth releaseChannel = _releaseChannel; 895d8188f5SBen Roth } else { 905d8188f5SBen Roth releaseChannel = @"default"; 915d8188f5SBen Roth } 929a7f140aSEric Samelson NSString *clientEnvironment; 939a7f140aSEric Samelson if ([EXEnvironment sharedEnvironment].isDetached) { 949a7f140aSEric Samelson clientEnvironment = @"STANDALONE"; 959a7f140aSEric Samelson } else { 969a7f140aSEric Samelson clientEnvironment = @"EXPO_DEVICE"; 979a7f140aSEric Samelson#if TARGET_IPHONE_SIMULATOR 989a7f140aSEric Samelson clientEnvironment = @"EXPO_SIMULATOR"; 999a7f140aSEric Samelson#endif 1009a7f140aSEric Samelson } 101ec45106eSQuinlan Jung 1025d8188f5SBen Roth [request setValue:releaseChannel forHTTPHeaderField:@"Expo-Release-Channel"]; 1035d8188f5SBen Roth [request setValue:@"true" forHTTPHeaderField:@"Expo-JSON-Error"]; 1045d8188f5SBen Roth [request setValue:requestAbiVersion forHTTPHeaderField:@"Exponent-SDK-Version"]; 1055d8188f5SBen Roth [request setValue:@"ios" forHTTPHeaderField:@"Exponent-Platform"]; 1065d8188f5SBen Roth [request setValue:@"true" forHTTPHeaderField:@"Exponent-Accept-Signature"]; 1079b65365fSquinlanj [request setValue:@"application/expo+json,application/json" forHTTPHeaderField:@"Accept"]; 1089a7f140aSEric Samelson [request setValue:@"1" forHTTPHeaderField:@"Expo-Api-Version"]; 1099a7f140aSEric Samelson [request setValue:clientEnvironment forHTTPHeaderField:@"Expo-Client-Environment"]; 110*72d142a5SStanisław Chmiela [request setValue:[EXClientReleaseType clientReleaseType] forHTTPHeaderField:@"Expo-Client-Release-Type"]; 1119a7f140aSEric Samelson 1128a20a963SEric Samelson NSString *sessionSecret = [[EXSession sharedInstance] sessionSecret]; 1139a7f140aSEric Samelson if (sessionSecret) { 1149a7f140aSEric Samelson [request setValue:sessionSecret forHTTPHeaderField:@"Expo-Session"]; 1159a7f140aSEric Samelson } 1165d8188f5SBen Roth} 1175d8188f5SBen Roth 1185d8188f5SBen Roth- (NSString *)_userAgentString 1195d8188f5SBen Roth{ 1205d8188f5SBen Roth struct utsname systemInfo; 1215d8188f5SBen Roth uname(&systemInfo); 1225d8188f5SBen Roth NSString *deviceModel = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; 1235d8188f5SBen Roth return [NSString stringWithFormat:@"Exponent/%@ (%@; %@ %@; Scale/%.2f; %@)", 1245d8188f5SBen Roth [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"], 1255d8188f5SBen Roth deviceModel, 1265d8188f5SBen Roth [UIDevice currentDevice].systemName, 1275d8188f5SBen Roth [UIDevice currentDevice].systemVersion, 1285d8188f5SBen Roth [UIScreen mainScreen].scale, 1295d8188f5SBen Roth [NSLocale autoupdatingCurrentLocale].localeIdentifier]; 1305d8188f5SBen Roth} 1315d8188f5SBen Roth 1325d8188f5SBen Roth#pragma mark - NSURLSessionTaskDelegate 1335d8188f5SBen Roth 1345d8188f5SBen Roth- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler 1355d8188f5SBen Roth{ 1365d8188f5SBen Roth completionHandler(request); 1375d8188f5SBen Roth} 1385d8188f5SBen Roth 1395d8188f5SBen Roth#pragma mark - NSURLSessionDataDelegate 1405d8188f5SBen Roth 1415d8188f5SBen Roth- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler 1425d8188f5SBen Roth{ 1435d8188f5SBen Roth completionHandler(proposedResponse); 1445d8188f5SBen Roth} 1455d8188f5SBen Roth 1465d8188f5SBen Roth#pragma mark - Parsing the response 1475d8188f5SBen Roth 1485d8188f5SBen Roth- (NSStringEncoding)_encodingFromResponse:(NSURLResponse *)response 1495d8188f5SBen Roth{ 1505d8188f5SBen Roth if (response.textEncodingName) { 1515d8188f5SBen Roth CFStringRef cfEncodingName = (__bridge CFStringRef)response.textEncodingName; 1525d8188f5SBen Roth CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding(cfEncodingName); 1535d8188f5SBen Roth if (cfEncoding != kCFStringEncodingInvalidId) { 1545d8188f5SBen Roth return CFStringConvertEncodingToNSStringEncoding(cfEncoding); 1555d8188f5SBen Roth } 1565d8188f5SBen Roth } 1575d8188f5SBen Roth // Default to UTF-8 1585d8188f5SBen Roth return NSUTF8StringEncoding; 1595d8188f5SBen Roth} 1605d8188f5SBen Roth 1615d8188f5SBen Roth- (NSError *)_errorFromResponse:(NSHTTPURLResponse *)response body:(NSString *)body 1625d8188f5SBen Roth{ 1635d8188f5SBen Roth NSDictionary *userInfo; 1645d8188f5SBen Roth id errorInfo = RCTJSONParse(body, nil); 1655d8188f5SBen Roth if ([errorInfo isKindOfClass:[NSDictionary class]]) { 1665d8188f5SBen Roth userInfo = [self _formattedErrorInfo:(NSDictionary *)errorInfo]; 1675d8188f5SBen Roth } else { 1685d8188f5SBen Roth userInfo = @{ 1695d8188f5SBen Roth NSLocalizedDescriptionKey: body, 1705d8188f5SBen Roth }; 1715d8188f5SBen Roth } 1725d8188f5SBen Roth return [NSError errorWithDomain:EXNetworkErrorDomain code:response.statusCode userInfo:userInfo]; 1735d8188f5SBen Roth} 1745d8188f5SBen Roth 1755d8188f5SBen Roth- (NSDictionary *)_formattedErrorInfo:(NSDictionary *)errorInfo 1765d8188f5SBen Roth{ 1775d8188f5SBen Roth NSString *message = errorInfo[@"message"] ?: errorInfo[@"message"] ?: @"There was a server error"; 1785d8188f5SBen Roth NSString *errorCode = errorInfo[@"errorCode"] ?: @"UNEXPECTED_ERROR"; 1795d8188f5SBen Roth NSString *errorMetadata = errorInfo[@"metadata"] ?: @{}; 1805d8188f5SBen Roth NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:@{ NSLocalizedDescriptionKey: message, 1815d8188f5SBen Roth @"errorCode": errorCode, 1825d8188f5SBen Roth @"metadata": errorMetadata, 1835d8188f5SBen Roth }]; 1845d8188f5SBen Roth 1855d8188f5SBen Roth if ([errorInfo[@"errors"] isKindOfClass:[NSArray class]]) { 1865d8188f5SBen Roth NSMutableArray *formattedErrorItems = [NSMutableArray array]; 1875d8188f5SBen Roth for (NSDictionary *errorItem in errorInfo[@"errors"]) { 1885d8188f5SBen Roth if ([errorItem isKindOfClass:[NSDictionary class]]) { 1895d8188f5SBen Roth NSMutableDictionary *formattedErrorItem = [NSMutableDictionary dictionary]; 1905d8188f5SBen Roth if (errorItem[@"description"]) { 1915d8188f5SBen Roth formattedErrorItem[@"methodName"] = errorItem[@"description"]; 1925d8188f5SBen Roth } 1935d8188f5SBen Roth if (errorItem[@"filename"]) { 1945d8188f5SBen Roth formattedErrorItem[@"file"] = errorItem[@"filename"]; 1955d8188f5SBen Roth } 1965d8188f5SBen Roth if (errorItem[@"lineNumber"]) { 1975d8188f5SBen Roth formattedErrorItem[@"lineNumber"] = errorItem[@"lineNumber"]; 1985d8188f5SBen Roth } 1995d8188f5SBen Roth [formattedErrorItems addObject:formattedErrorItem]; 2005d8188f5SBen Roth } 2015d8188f5SBen Roth } 2025d8188f5SBen Roth userInfo[@"stack"] = formattedErrorItems; 2035d8188f5SBen Roth } 2045d8188f5SBen Roth 2055d8188f5SBen Roth return userInfo; 2065d8188f5SBen Roth} 2075d8188f5SBen Roth 2085d8188f5SBen Roth@end 209