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