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