1// Copyright 2015-present 650 Industries. All rights reserved.
2
3#import "EXAppLoader+Updates.h"
4#import "EXEnvironment.h"
5#import "EXKernel.h"
6#import "EXKernelAppRecord.h"
7#import "EXReactAppManager.h"
8#import "EXScopedModuleRegistry.h"
9#import "EXUpdates.h"
10#import "EXUpdatesManager.h"
11
12#import <React/RCTBridge.h>
13#import <React/RCTUtils.h>
14
15@interface EXUpdatesManager ()
16
17@property (nonatomic, strong) EXAppLoader *appLoader;
18
19@end
20
21@implementation EXUpdatesManager
22
23- (void)notifyApp:(EXKernelAppRecord *)appRecord
24ofDownloadWithManifest:(NSDictionary * _Nullable)manifest
25            isNew:(BOOL)isBundleNew
26            error:(NSError * _Nullable)error;
27{
28  NSDictionary *body;
29  if (error) {
30    body = @{
31             @"type": EXUpdatesErrorEventType,
32             @"message": error.localizedDescription
33             };
34  } else if (isBundleNew) {
35    if (!manifest) {
36      // prevent a crash, but this shouldn't ever happen
37      manifest = @{};
38    }
39    body = @{
40             @"type": EXUpdatesDownloadFinishedEventType,
41             @"manifest": manifest
42             };
43  } else {
44    body = @{
45             @"type": EXUpdatesNotAvailableEventType
46             };
47  }
48  RCTBridge *bridge = appRecord.appManager.reactBridge;
49  if (appRecord.status == kEXKernelAppRecordStatusRunning && [self _doesBridgeSupportUpdatesModule:bridge]) {
50    [bridge.scopedModules.updates sendEventWithBody:body];
51  }
52}
53
54# pragma mark - internal
55
56- (EXAppLoader *)_appLoaderWithScopedModule:(id)scopedModule
57{
58  NSString *experienceId = ((EXScopedBridgeModule *)scopedModule).experienceId;
59  EXKernelAppRecord *appRecord = [[EXKernel sharedInstance].appRegistry newestRecordWithExperienceId:experienceId];
60  return appRecord.appLoader;
61}
62
63# pragma mark - EXUpdatesScopedModuleDelegate
64
65- (void)updatesModuleDidSelectReload:(id)scopedModule
66{
67  NSString *experienceId = ((EXScopedBridgeModule *)scopedModule).experienceId;
68  [[EXKernel sharedInstance] reloadAppWithExperienceId:experienceId];
69}
70
71- (void)updatesModuleDidSelectReloadFromCache:(id)scopedModule
72{
73  NSString *experienceId = ((EXScopedBridgeModule *)scopedModule).experienceId;
74  [[EXKernel sharedInstance] reloadAppFromCacheWithExperienceId:experienceId];
75}
76
77- (void)updatesModule:(id)scopedModule
78didRequestManifestWithCacheBehavior:(EXCachedResourceBehavior)cacheBehavior
79              success:(void (^)(NSDictionary * _Nonnull))success
80              failure:(void (^)(NSError * _Nonnull))failure
81{
82  if ([EXEnvironment sharedEnvironment].isDetached && ![EXEnvironment sharedEnvironment].areRemoteUpdatesEnabled) {
83    failure(RCTErrorWithMessage(@"Remote updates are disabled in app.json"));
84    return;
85  }
86  EXAppLoader *appLoader = [self _appLoaderWithScopedModule:scopedModule];
87  [appLoader fetchManifestWithCacheBehavior:cacheBehavior success:success failure:failure];
88}
89
90- (void)updatesModule:(id)scopedModule
91didRequestBundleWithManifest:(NSDictionary *)manifest
92             progress:(void (^)(NSDictionary * _Nonnull))progressBlock
93              success:(void (^)(NSData * _Nonnull))success
94              failure:(void (^)(NSError * _Nonnull))failure
95{
96  if ([EXEnvironment sharedEnvironment].isDetached && ![EXEnvironment sharedEnvironment].areRemoteUpdatesEnabled) {
97    failure(RCTErrorWithMessage(@"Remote updates are disabled in app.json"));
98    return;
99  }
100  void (^progressDictBlock)(EXLoadingProgress * _Nonnull) = ^void(EXLoadingProgress * _Nonnull progress) {
101    progressBlock(@{
102                    @"status": progress.status,
103                    @"done": progress.done,
104                    @"total": progress.total
105                    });
106  };
107  EXAppLoader *appLoader = [self _appLoaderWithScopedModule:scopedModule];
108  [appLoader fetchJSBundleWithManifest:manifest
109                         cacheBehavior:EXCachedResourceWriteToCache
110                       timeoutInterval:kEXJSBundleTimeout
111                              progress:progressDictBlock
112                               success:success
113                                 error:failure];
114}
115
116- (BOOL)_doesBridgeSupportUpdatesModule:(RCTBridge *)bridge
117{
118  // sdk versions prior to 26 didn't include this module.
119  return ([bridge.scopedModules respondsToSelector:@selector(updates)]);
120}
121
122@end
123