1// Copyright 2020-present 650 Industries. All rights reserved.
2
3#import "EXEmbeddedHomeLoader.h"
4#import "EXKernel.h"
5
6#import <React/RCTUtils.h>
7
8@import EXManifests;
9
10NS_ASSUME_NONNULL_BEGIN
11
12NSString * const EXEmbeddedHomeLoaderErrorDomain = @"embedded-home-loader";
13
14NSString *kEXHomeBundleResourceName = @"kernel.ios";
15NSString *kEXHomeManifestResourceName = @"kernel-manifest";
16
17typedef NS_ENUM(NSInteger, EXEmbeddedHomeLoaderErrorCode) {
18  EXEmbeddedHomeLoaderErrorCodeUnableToLoadEmbeddedManifest,
19  EXEmbeddedHomeLoaderErrorCodeUnableToLoadEmbeddedBundle,
20};
21
22@interface EXEmbeddedHomeLoader ()
23
24@property (nonatomic, strong, nullable) EXManifestsManifest *manifest;
25@property (nonatomic, strong, nullable) NSData *bundle;
26@property (nonatomic, assign) BOOL isUpToDate;
27
28@end
29
30@implementation EXEmbeddedHomeLoader
31
32- (nonnull instancetype)init {
33  return self = [super init];
34}
35
36@synthesize manifest = _manifest;
37@synthesize bundle = _bundle;
38@synthesize isUpToDate = _isUpToDate;
39
40#pragma mark - getters and lifecycle
41
42- (void)_reset
43{
44  _manifest = nil;
45  _bundle = nil;
46}
47
48- (EXAppLoaderStatus)status
49{
50  if (_bundle) {
51    return kEXAppLoaderStatusHasManifestAndBundle;
52  } else if (_manifest) {
53    return kEXAppLoaderStatusHasManifest;
54  }
55  return kEXAppLoaderStatusNew;
56}
57
58- (nullable EXManifestsManifest *)manifest
59{
60  return _manifest;
61}
62
63- (nullable NSData *)bundle
64{
65  return _bundle;
66}
67
68- (BOOL)supportsBundleReload
69{
70  return NO;
71}
72
73#pragma mark - public
74
75- (void)request
76{
77  [self _reset];
78  [self _beginRequest];
79}
80
81- (void)requestFromCache
82{
83  [self request];
84}
85
86#pragma mark - internal
87
88- (void)_beginRequest
89{
90  NSString *manifestPath = [[NSBundle mainBundle] pathForResource:kEXHomeManifestResourceName ofType:@"json"];
91  if (!manifestPath) {
92    NSError *error = [NSError errorWithDomain:EXEmbeddedHomeLoaderErrorDomain code:EXEmbeddedHomeLoaderErrorCodeUnableToLoadEmbeddedManifest userInfo:@{
93      NSLocalizedDescriptionKey: @"Could not load embedded manifest"
94    }];
95    [self.delegate appLoader:self didFailWithError:error];
96    return;
97  }
98
99  NSError *stringReadError;
100  NSString *manifestJson = [NSString stringWithContentsOfFile:manifestPath encoding:NSUTF8StringEncoding error:&stringReadError];
101  if (stringReadError) {
102    [self.delegate appLoader:self didFailWithError:stringReadError];
103    return;
104  }
105
106  id manifest = RCTJSONParse(manifestJson, nil);
107  if (![manifest isKindOfClass:[NSDictionary class]]) {
108    NSError *error = [NSError errorWithDomain:EXEmbeddedHomeLoaderErrorDomain code:EXEmbeddedHomeLoaderErrorCodeUnableToLoadEmbeddedManifest userInfo:@{
109      NSLocalizedDescriptionKey: @"Embedded manifest not valid JSON"
110    }];
111    [self.delegate appLoader:self didFailWithError:error];
112    return;
113  }
114
115  if (!([manifest[@"id"] isEqualToString:@"@exponent/home"])) {
116    DDLogError(@"Bundled kernel manifest was published with an id other than @exponent/home");
117  }
118
119  _manifest = [EXManifestsManifestFactory manifestForManifestJSON:manifest];
120  if (self.delegate) {
121    [self.delegate appLoader:self didLoadOptimisticManifest:_manifest];
122  }
123
124  _isUpToDate = true;
125
126  NSString *bundlePath = [[NSBundle mainBundle] pathForResource:kEXHomeBundleResourceName ofType:@"bundle"];
127  if (!bundlePath) {
128    NSError *error = [NSError errorWithDomain:EXEmbeddedHomeLoaderErrorDomain code:EXEmbeddedHomeLoaderErrorCodeUnableToLoadEmbeddedManifest userInfo:@{
129      NSLocalizedDescriptionKey: @"Could not load embedded bundle"
130    }];
131    [self.delegate appLoader:self didFailWithError:error];
132    return;
133  }
134
135  NSError *dataReadError;
136  NSData *bundleData = [NSData dataWithContentsOfFile:bundlePath options:0 error:&dataReadError];
137  if (dataReadError) {
138    [self.delegate appLoader:self didFailWithError:dataReadError];
139    return;
140  }
141
142  _bundle = bundleData;
143
144  if (self.delegate) {
145    [self.delegate appLoader:self didFinishLoadingManifest:_manifest bundle:_bundle];
146  }
147}
148
149@end
150
151NS_ASSUME_NONNULL_END
152