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