1// Copyright 2015-present 650 Industries. All rights reserved. 2 3#import "EXFileDownloader.h" 4#import "EXVersions.h" 5#import "EXKernelUtil.h" 6 7#import <React/RCTUtils.h> 8 9@import UIKit; 10 11#import <sys/utsname.h> 12 13NSString * const EXNetworkErrorDomain = @"EXNetwork"; 14NSTimeInterval const EXFileDownloaderDefaultTimeoutInterval = 60; 15 16@interface EXFileDownloader () <NSURLSessionDataDelegate> 17@end 18 19@implementation EXFileDownloader 20 21- (instancetype)init 22{ 23 if (self = [super init]) { 24 _timeoutInterval = EXFileDownloaderDefaultTimeoutInterval; 25 } 26 return self; 27} 28 29- (void)downloadFileFromURL:(NSURL *)url 30 successBlock:(EXFileDownloaderSuccessBlock)successBlock 31 errorBlock:(EXFileDownloaderErrorBlock)errorBlock 32{ 33 NSURLSessionConfiguration *configuration = _urlSessionConfiguration ?: [NSURLSessionConfiguration defaultSessionConfiguration]; 34 35 // also pass any custom cache policy onto this specific request 36 NSURLRequestCachePolicy cachePolicy = _urlSessionConfiguration ? _urlSessionConfiguration.requestCachePolicy : NSURLRequestUseProtocolCachePolicy; 37 38 NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; 39 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:cachePolicy timeoutInterval:_timeoutInterval]; 40 [self setHTTPHeaderFields:request]; 41 42 __weak typeof(self) weakSelf = self; 43 NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 44 if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) { 45 NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; 46 if (httpResponse.statusCode != 200) { 47 NSStringEncoding encoding = [weakSelf _encodingFromResponse:response]; 48 NSString *body = [[NSString alloc] initWithData:data encoding:encoding]; 49 error = [weakSelf _errorFromResponse:httpResponse body:body]; 50 } 51 } 52 53 if (error) { 54 errorBlock(error, response); 55 } else { 56 successBlock(data, response); 57 } 58 }]; 59 [task resume]; 60 [session finishTasksAndInvalidate]; 61} 62 63#pragma mark - Configuring the request 64 65- (void)setHTTPHeaderFields:(NSMutableURLRequest *)request 66{ 67 [request setValue:[self _userAgentString] forHTTPHeaderField:@"User-Agent"]; 68 69 NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; 70 [request setValue:version forHTTPHeaderField:@"Exponent-Version"]; 71 NSString *requestAbiVersion; 72 if (_abiVersion && _abiVersion.length) { 73 requestAbiVersion = _abiVersion; 74 } else { 75 NSArray *versionsAvailable = [EXVersions sharedInstance].versions[@"sdkVersions"]; 76 if (versionsAvailable) { 77 requestAbiVersion = [versionsAvailable componentsJoinedByString:@","]; 78 } else { 79 requestAbiVersion = [EXVersions sharedInstance].temporarySdkVersion; 80 } 81 } 82 NSString *releaseChannel; 83 if (_releaseChannel) { 84 releaseChannel = _releaseChannel; 85 } else { 86 releaseChannel = @"default"; 87 } 88 [request setValue:releaseChannel forHTTPHeaderField:@"Expo-Release-Channel"]; 89 [request setValue:@"true" forHTTPHeaderField:@"Expo-JSON-Error"]; 90 [request setValue:requestAbiVersion forHTTPHeaderField:@"Exponent-SDK-Version"]; 91 [request setValue:@"ios" forHTTPHeaderField:@"Exponent-Platform"]; 92 [request setValue:@"true" forHTTPHeaderField:@"Exponent-Accept-Signature"]; 93} 94 95- (NSString *)_userAgentString 96{ 97 struct utsname systemInfo; 98 uname(&systemInfo); 99 NSString *deviceModel = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; 100 return [NSString stringWithFormat:@"Exponent/%@ (%@; %@ %@; Scale/%.2f; %@)", 101 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"], 102 deviceModel, 103 [UIDevice currentDevice].systemName, 104 [UIDevice currentDevice].systemVersion, 105 [UIScreen mainScreen].scale, 106 [NSLocale autoupdatingCurrentLocale].localeIdentifier]; 107} 108 109#pragma mark - NSURLSessionTaskDelegate 110 111- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler 112{ 113 completionHandler(request); 114} 115 116#pragma mark - NSURLSessionDataDelegate 117 118- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler 119{ 120 completionHandler(proposedResponse); 121} 122 123#pragma mark - Parsing the response 124 125- (NSStringEncoding)_encodingFromResponse:(NSURLResponse *)response 126{ 127 if (response.textEncodingName) { 128 CFStringRef cfEncodingName = (__bridge CFStringRef)response.textEncodingName; 129 CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding(cfEncodingName); 130 if (cfEncoding != kCFStringEncodingInvalidId) { 131 return CFStringConvertEncodingToNSStringEncoding(cfEncoding); 132 } 133 } 134 // Default to UTF-8 135 return NSUTF8StringEncoding; 136} 137 138- (NSError *)_errorFromResponse:(NSHTTPURLResponse *)response body:(NSString *)body 139{ 140 NSDictionary *userInfo; 141 id errorInfo = RCTJSONParse(body, nil); 142 if ([errorInfo isKindOfClass:[NSDictionary class]]) { 143 userInfo = [self _formattedErrorInfo:(NSDictionary *)errorInfo]; 144 } else { 145 userInfo = @{ 146 NSLocalizedDescriptionKey: body, 147 }; 148 } 149 return [NSError errorWithDomain:EXNetworkErrorDomain code:response.statusCode userInfo:userInfo]; 150} 151 152- (NSDictionary *)_formattedErrorInfo:(NSDictionary *)errorInfo 153{ 154 NSString *message = errorInfo[@"message"] ?: errorInfo[@"message"] ?: @"There was a server error"; 155 NSString *errorCode = errorInfo[@"errorCode"] ?: @"UNEXPECTED_ERROR"; 156 NSString *errorMetadata = errorInfo[@"metadata"] ?: @{}; 157 NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:@{ NSLocalizedDescriptionKey: message, 158 @"errorCode": errorCode, 159 @"metadata": errorMetadata, 160 }]; 161 162 if ([errorInfo[@"errors"] isKindOfClass:[NSArray class]]) { 163 NSMutableArray *formattedErrorItems = [NSMutableArray array]; 164 for (NSDictionary *errorItem in errorInfo[@"errors"]) { 165 if ([errorItem isKindOfClass:[NSDictionary class]]) { 166 NSMutableDictionary *formattedErrorItem = [NSMutableDictionary dictionary]; 167 if (errorItem[@"description"]) { 168 formattedErrorItem[@"methodName"] = errorItem[@"description"]; 169 } 170 if (errorItem[@"filename"]) { 171 formattedErrorItem[@"file"] = errorItem[@"filename"]; 172 } 173 if (errorItem[@"lineNumber"]) { 174 formattedErrorItem[@"lineNumber"] = errorItem[@"lineNumber"]; 175 } 176 [formattedErrorItems addObject:formattedErrorItem]; 177 } 178 } 179 userInfo[@"stack"] = formattedErrorItems; 180 } 181 182 return userInfo; 183} 184 185@end 186