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