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