1// Copyright 2015-present 650 Industries. All rights reserved.
2
3#import "EXApiUtil.h"
4#import "EXAppFetcher+Private.h"
5#import "EXAppLoader.h"
6#import "EXEnvironment.h"
7#import "EXErrorRecoveryManager.h"
8#import "EXJavaScriptResource.h"
9#import "EXKernel.h"
10#import "EXVersions.h"
11
12#import <React/RCTUtils.h>
13
14NS_ASSUME_NONNULL_BEGIN
15
16@implementation EXAppFetcher
17
18- (instancetype)initWithAppLoader:(EXAppLoader *)appLoader
19{
20  if (self = [super init]) {
21    _appLoader = appLoader;
22  }
23  return self;
24}
25
26- (void)start
27{
28  // overridden by subclasses
29  @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Should not call EXAppFetcher#start -- use a subclass instead" userInfo:nil];
30}
31
32- (void)fetchJSBundleWithManifest:(NSDictionary *)manifest
33                    cacheBehavior:(EXCachedResourceBehavior)cacheBehavior
34                  timeoutInterval:(NSTimeInterval)timeoutInterval
35                         progress:(void (^ _Nullable )(EXLoadingProgress *))progressBlock
36                          success:(void (^)(NSData *))successBlock
37                            error:(void (^)(NSError *))errorBlock
38{
39  EXJavaScriptResource *jsResource = [[EXJavaScriptResource alloc] initWithBundleName:[self.dataSource bundleResourceNameForAppFetcher:self withManifest:manifest]
40                                                                            remoteUrl:[EXApiUtil bundleUrlFromManifest:manifest]
41                                                                      devToolsEnabled:[[self class] areDevToolsEnabledWithManifest:manifest]];
42  jsResource.abiVersion = [[EXVersions sharedInstance] availableSdkVersionForManifest:manifest];
43  jsResource.requestTimeoutInterval = timeoutInterval;
44
45  EXCachedResourceBehavior behavior = cacheBehavior;
46  // if we've disabled updates, ignore all other settings and only use the cache
47  if ([EXEnvironment sharedEnvironment].isDetached && ![EXEnvironment sharedEnvironment].areRemoteUpdatesEnabled) {
48    behavior = EXCachedResourceOnlyCache;
49  }
50
51  if ([self.dataSource appFetcherShouldInvalidateBundleCache:self]) {
52    [jsResource removeCache];
53  }
54
55  [jsResource loadResourceWithBehavior:cacheBehavior progressBlock:progressBlock successBlock:successBlock errorBlock:errorBlock];
56}
57
58+ (NSString *)experienceIdWithManifest:(NSDictionary * _Nonnull)manifest
59{
60  id experienceIdJsonValue = manifest[@"id"];
61  if (experienceIdJsonValue) {
62    RCTAssert([experienceIdJsonValue isKindOfClass:[NSString class]], @"Manifest contains an id which is not a string: %@", experienceIdJsonValue);
63    return experienceIdJsonValue;
64  }
65  return nil;
66}
67
68+ (BOOL)areDevToolsEnabledWithManifest:(NSDictionary * _Nonnull)manifest
69{
70  NSDictionary *manifestDeveloperConfig = manifest[@"developer"];
71  BOOL isDeployedFromTool = (manifestDeveloperConfig && manifestDeveloperConfig[@"tool"] != nil);
72  return (isDeployedFromTool);
73}
74
75+ (EXCachedResourceBehavior)cacheBehaviorForJSWithManifest:(NSDictionary * _Nonnull)manifest
76{
77  if ([[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager experienceIdIsRecoveringFromError:[[self class] experienceIdWithManifest:manifest]]) {
78    // if this experience id encountered a loading error before, discard any cache we might have
79    return EXCachedResourceWriteToCache;
80  }
81  if ([[self class] areDevToolsEnabledWithManifest:manifest]) {
82    return EXCachedResourceNoCache;
83  }
84  return EXCachedResourceWriteToCache;
85}
86
87@end
88
89NS_ASSUME_NONNULL_END
90
91