1// Copyright 2015-present 650 Industries. All rights reserved. 2 3#import "EXKernelAppRegistry.h" 4#import "EXKernelAppLoader.h" 5#import "EXKernel.h" 6#import "EXFrame.h" 7#import "EXFrameReactAppManager.h" 8#import "EXKernelReactAppManager.h" 9 10#import <React/RCTBridge.h> 11 12@interface EXKernelAppRegistry () 13 14@property (nonatomic, strong) NSMutableDictionary *appRegistry; 15@property (nonatomic, assign) EXKernelReactAppManager *kernelAppManager; 16 17@end 18 19@implementation EXKernelAppRegistry 20 21- (instancetype)init 22{ 23 if (self = [super init]) { 24 _appRegistry = [[NSMutableDictionary alloc] init]; 25 } 26 return self; 27} 28 29- (NSString *)registerAppWithManifestUrl:(NSURL *)manifestUrl 30{ 31 NSAssert(manifestUrl, @"Cannot register an app with no manifest URL"); 32 // not enforcing uniqueness yet - we will do this once we download the manifest & have the experienceId 33 EXKernelAppRecord *newRecord = [EXKernelAppRecord recordWithManifestUrl:manifestUrl]; 34 NSString *recordId = [[NSUUID UUID] UUIDString]; 35 [_appRegistry setObject:newRecord forKey:recordId]; 36 return recordId; 37} 38 39- (void)unregisterAppWithRecordId:(NSString *)recordId 40{ 41 EXKernelAppRecord *record = [_appRegistry objectForKey:recordId]; 42 if (record) { 43 if (_delegate) { 44 [_delegate appRegistry:self willUnregisterAppRecord:record]; 45 } 46 [_appRegistry removeObjectForKey:recordId]; 47 } 48} 49 50- (void)_addAppManager:(EXFrameReactAppManager *)appManager toRecord:(EXKernelAppRecord *)appRecord 51{ 52 appRecord.appManager = appManager; 53 54 // TODO: this assumes we always load apps in the foreground (true at time of writing) 55 _lastKnownForegroundAppManager = appManager; 56 57 if (_delegate) { 58 [_delegate appRegistry:self didRegisterAppRecord:appRecord]; 59 } 60} 61 62- (void)addAppManager:(EXFrameReactAppManager *)appManager toRecordWithId:(NSString *)recordId 63{ 64 EXKernelAppRecord *appRecord = [self recordForId:recordId]; 65 [self _addAppManager:appManager toRecord:appRecord]; 66} 67 68- (void)addAppManager:(EXFrameReactAppManager *)appManager toRecordWithExperienceId:(NSString *)experienceId 69{ 70 EXKernelAppRecord *appRecord = [self newestRecordWithExperienceId:experienceId]; 71 [self _addAppManager:appManager toRecord:appRecord]; 72} 73 74- (void)unregisterRecordWithExperienceId:(NSString *)experienceId 75{ 76 for (NSString *recordId in self.appEnumerator) { 77 EXKernelAppRecord *record = [self recordForId:recordId]; 78 if (record && record.experienceId != nil && [record.experienceId isEqualToString:experienceId]) { 79 [self unregisterAppWithRecordId:recordId]; 80 break; 81 } 82 } 83} 84 85- (void)setExperienceFinishedLoading:(BOOL)experienceFinishedLoading onRecordWithId:(NSString *)recordId 86{ 87 EXKernelAppRecord *record = [self recordForId:recordId]; 88 if (record) { 89 record.experienceFinishedLoading = experienceFinishedLoading; 90 } 91} 92 93- (void)setExperienceFinishedLoading:(BOOL)experienceFinishedLoading onRecordWithExperienceId:(NSString *)experienceId 94{ 95 EXKernelAppRecord *record = [self newestRecordWithExperienceId:experienceId]; 96 if (record) { 97 record.experienceFinishedLoading = experienceFinishedLoading; 98 } 99} 100 101- (void)registerKernelAppManager:(EXKernelReactAppManager *)appManager 102{ 103 _kernelAppManager = appManager; 104} 105 106- (void)unregisterKernelAppManager 107{ 108 _kernelAppManager = nil; 109} 110 111- (EXKernelReactAppManager *)kernelAppManager 112{ 113 return _kernelAppManager; 114} 115 116- (EXKernelAppRecord *)recordForId:(NSString *)recordId 117{ 118 return [_appRegistry objectForKey:recordId]; 119} 120 121// when reloading, for a brief period of time there are two records with the same experienceId in the registry 122- (EXKernelAppRecord * _Nullable)newestRecordWithExperienceId:(NSString *)experienceId 123{ 124 EXKernelAppRecord *recordToReturn; 125 for (NSString *recordId in self.appEnumerator) { 126 EXKernelAppRecord *record = [self recordForId:recordId]; 127 if (record && record.experienceId && [record.experienceId isEqualToString:experienceId]) { 128 if (recordToReturn && [recordToReturn.timeCreated compare:record.timeCreated] == NSOrderedDescending) { 129 continue; 130 } 131 recordToReturn = record; 132 } 133 } 134 return recordToReturn; 135} 136 137- (NSEnumerator<id> *)appEnumerator 138{ 139 // TODO: use mutexes to control access to _appRegistry rather than just copying it here 140 return [(NSDictionary *)[_appRegistry copy] keyEnumerator]; 141} 142 143- (EXReactAppManager *)lastKnownForegroundAppManager 144{ 145 if (_lastKnownForegroundAppManager) { 146 return _lastKnownForegroundAppManager; 147 } 148 return _kernelAppManager; 149} 150 151- (NSString *)description 152{ 153 if (_appRegistry.count > 0) { 154 NSMutableString *results = [NSMutableString string]; 155 for (NSString *recordId in self.appEnumerator) { 156 EXKernelAppRecord *record = [self recordForId:recordId]; 157 [results appendString:[NSString stringWithFormat:@" %@: %@\n", recordId, record]]; 158 } 159 return [NSString stringWithFormat:@"EXKernelAppRegistry with apps: {\n%@}", results]; 160 } 161 return @"EXKernelAppRegistry (empty)"; 162} 163 164- (BOOL)isExperienceIdUnique:(NSString *)experienceId 165{ 166 int count = 0; 167 for (NSString *recordId in self.appEnumerator) { 168 EXKernelAppRecord *appRecord = [self recordForId:recordId]; 169 if (appRecord.experienceId && [appRecord.experienceId isEqualToString:experienceId]) { 170 count++; 171 if (count > 1) { 172 return NO; 173 } 174 } 175 } 176 return YES; 177} 178 179@end 180