xref: /expo/ios/Client/EXHomeAppManager.m (revision 8ef929c3)
1
2#import "EXBuildConstants.h"
3#import "EXHomeAppManager.h"
4#import "EXKernel.h"
5#import "EXAppFetcher.h"
6#import "EXAppLoader.h"
7#import "EXKernelLinkingManager.h"
8#import "EXHomeModule.h"
9#import "EXKernelUtil.h"
10#import "EXLog.h"
11#import "ExpoKit.h"
12#import "EXReactAppExceptionHandler.h"
13#import "EXReactAppManager+Private.h"
14#import "EXVersionManager.h"
15#import "EXVersions.h"
16
17#import <React/RCTUtils.h>
18#import <React/RCTBridge.h>
19
20#import <EXCore/EXModuleRegistryProvider.h>
21
22NSString * const kEXHomeLaunchUrlDefaultsKey = @"EXKernelLaunchUrlDefaultsKey";
23NSString *kEXHomeBundleResourceName = @"kernel.ios";
24NSString *kEXHomeManifestResourceName = @"kernel-manifest";
25
26@implementation EXHomeAppManager
27
28#pragma mark - interfacing with home JS
29
30- (void)addHistoryItemWithUrl:(NSURL *)manifestUrl manifest:(NSDictionary *)manifest
31{
32  if (!manifest || !manifestUrl || [manifest[@"id"] isEqualToString:@"@exponent/home"]) {
33    return;
34  }
35  NSDictionary *params = @{
36    @"manifestUrl": manifestUrl.absoluteString,
37    @"manifest": manifest,
38  };
39  [self _dispatchHomeJSEvent:@"addHistoryItem" body:params onSuccess:nil onFailure:nil];
40}
41
42- (void)getHistoryUrlForExperienceId:(NSString *)experienceId completion:(void (^)(NSString *))completion
43{
44  [self _dispatchHomeJSEvent:@"getHistoryUrlForExperienceId"
45                        body:@{ @"experienceId": experienceId }
46                   onSuccess:^(NSDictionary *result) {
47                     NSString *url = result[@"url"];
48                     completion(url);
49                   } onFailure:^(NSString *errorMessage) {
50                     completion(nil);
51                   }];
52}
53
54- (void)getIsValidHomeManifestToOpen:(NSDictionary *)manifest manifestUrl:(NSURL *) manifestUrl completion:(void (^)(BOOL))completion
55{
56  [self _dispatchHomeJSEvent:@"getIsValidHomeManifestToOpen"
57                        body:@{ @"manifest": manifest, @"manifestUrl": manifestUrl.absoluteString }
58                   onSuccess:^(NSDictionary *result) {
59                     BOOL isValid = [result[@"isValid"] boolValue];
60                     completion(isValid);
61                   } onFailure:^(NSString *errorMessage) {
62                     completion(NO);
63                   }];
64}
65
66- (void)showQRReader
67{
68  [self _dispatchHomeJSEvent:@"showQRReader" body:@{} onSuccess:nil onFailure:nil];
69}
70
71#pragma mark - EXReactAppManager
72
73- (NSArray *)extraModulesForBridge:(RCTBridge *)bridge
74{
75  NSMutableArray *modules = [NSMutableArray array];
76  self.exceptionHandler = [[EXReactAppExceptionHandler alloc] initWithAppRecord:self.appRecord];
77
78  NSMutableDictionary *params = [@{
79                                   @"bridge": bridge,
80                                   @"browserModuleClass": [EXHomeModule class],
81                                   @"constants": @{
82                                       @"installationId": [EXKernel deviceInstallUUID],
83                                       @"expoRuntimeVersion": [EXBuildConstants sharedInstance].expoRuntimeVersion,
84                                       @"linkingUri": @"exp://",
85                                       @"manifest": self.appRecord.appLoader.manifest,
86                                       @"appOwnership": @"expo",
87                                     },
88                                   @"exceptionsManagerDelegate": self.exceptionHandler,
89                                   @"supportedSdkVersions": [EXVersions sharedInstance].versions[@"sdkVersions"],
90                                   @"isDeveloper": @([EXBuildConstants sharedInstance].isDevKernel),
91                                   @"isStandardDevMenuAllowed": @(YES), // kernel enables traditional RN dev menu
92                                   @"manifest": self.appRecord.appLoader.manifest,
93                                   @"services": [EXKernel sharedInstance].serviceRegistry.allServices,
94                                   @"singletonModules": [EXModuleRegistryProvider singletonModules],
95                                   } mutableCopy];
96
97
98  NSURL *initialHomeUrl = [self _initialHomeUrl];
99  if (initialHomeUrl) {
100    params[@"initialUri"] = initialHomeUrl;
101  }
102
103  [modules addObjectsFromArray:[self.versionManager extraModulesWithParams:params]];
104
105  return modules;
106}
107
108- (void)computeVersionSymbolPrefix
109{
110  self.validatedVersion = nil;
111  self.versionSymbolPrefix = [[EXVersions sharedInstance] symbolPrefixForSdkVersion:self.validatedVersion isKernel:YES];
112}
113
114- (RCTLogFunction)logFunction
115{
116  return EXGetKernelRCTLogFunction();
117}
118
119- (RCTLogLevel)logLevel
120{
121  return RCTLogLevelWarning;
122}
123
124- (NSDictionary *)launchOptionsForBridge
125{
126  if (!self.hasBridgeEverLoaded) {
127    return [ExpoKit sharedInstance].launchOptions;
128  } else {
129    // don't want to re-consume launch options when the bridge reloads.
130    return nil;
131  }
132}
133
134- (NSString *)bundleResourceNameForAppFetcher:(__unused EXAppFetcher *)appFetcher withManifest:(nonnull __unused NSDictionary *)manifest
135{
136  return kEXHomeBundleResourceName;
137}
138
139- (BOOL)appFetcherShouldInvalidateBundleCache:(__unused EXAppFetcher *)appFetcher
140{
141  // if crashlytics shows that we're recovering from a native crash, invalidate any downloaded home cache.
142  BOOL shouldClearKernelCache = [[NSUserDefaults standardUserDefaults] boolForKey:kEXKernelClearJSCacheUserDefaultsKey];
143  if (shouldClearKernelCache) {
144    [[NSUserDefaults standardUserDefaults] removeObjectForKey:kEXKernelClearJSCacheUserDefaultsKey];
145    return YES;
146  }
147  return NO;
148}
149
150#pragma mark - util
151
152- (void)_dispatchHomeJSEvent:(NSString *)eventName body:(NSDictionary *)eventBody onSuccess:(void (^_Nullable)(NSDictionary * _Nullable))success onFailure:(void (^_Nullable)(NSString * _Nullable))failure
153{
154  EXHomeModule *homeModule = [[EXKernel sharedInstance] nativeModuleForAppManager:self named:@"ExponentKernel"];
155  if (homeModule) {
156    [homeModule dispatchJSEvent:eventName body:eventBody onSuccess:success onFailure:failure];
157  } else {
158    if (failure) {
159      failure(nil);
160    }
161  }
162}
163
164- (NSURL *)_initialHomeUrl
165{
166  // used by appetize - override the kernel initial url if there's something in NSUserDefaults
167  NSURL *initialHomeUrl;
168  NSString *kernelInitialUrlDefaultsValue = [[NSUserDefaults standardUserDefaults] stringForKey:kEXHomeLaunchUrlDefaultsKey];
169  if (kernelInitialUrlDefaultsValue) {
170    initialHomeUrl = [NSURL URLWithString:kernelInitialUrlDefaultsValue];
171    [[NSUserDefaults standardUserDefaults] removeObjectForKey:kEXHomeLaunchUrlDefaultsKey];
172    [[NSUserDefaults standardUserDefaults] synchronize];
173  } else {
174    initialHomeUrl = [EXKernelLinkingManager initialUrlFromLaunchOptions:[self launchOptionsForBridge]];
175  }
176  return initialHomeUrl;
177}
178
179+ (NSDictionary * _Nullable)bundledHomeManifest
180{
181  NSString *manifestJson = nil;
182  BOOL usesNSBundleManifest = NO;
183
184  // if developing, use development manifest from EXBuildConstants
185  if ([EXBuildConstants sharedInstance].isDevKernel) {
186    manifestJson = [EXBuildConstants sharedInstance].kernelManifestJsonString;
187  }
188
189  // otherwise use published manifest
190  if (!manifestJson) {
191    NSString *manifestPath = [[NSBundle mainBundle] pathForResource:kEXHomeManifestResourceName ofType:@"json"];
192    if (manifestPath) {
193      NSError *error;
194      usesNSBundleManifest = YES;
195      manifestJson = [NSString stringWithContentsOfFile:manifestPath encoding:NSUTF8StringEncoding error:&error];
196      if (error) {
197        manifestJson = nil;
198      }
199    }
200  }
201
202  if (manifestJson) {
203    id manifest = RCTJSONParse(manifestJson, nil);
204    if ([manifest isKindOfClass:[NSDictionary class]]) {
205      if (usesNSBundleManifest && ![manifest[@"id"] isEqualToString:@"@exponent/home"]) {
206        DDLogError(@"Bundled kernel manifest was published with an id other than @exponent/home");
207      }
208      return manifest;
209    }
210  }
211  return nil;
212}
213
214- (BOOL)requiresValidManifests
215{
216  // running home
217  return NO;
218}
219
220@end
221