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