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