1// Copyright 2015-present 650 Industries. All rights reserved. 2 3#import "EXKernelAppRegistry.h" 4#import "EXAbstractLoader.h" 5#import "EXEnvironment.h" 6#import "EXReactAppManager.h" 7#import "EXKernel.h" 8 9#import <React/RCTBridge.h> 10 11@interface EXKernelAppRegistry () 12 13@property (nonatomic, strong) NSMutableDictionary *appRegistry; 14@property (nonatomic, strong) EXKernelAppRecord *homeAppRecord; 15 16@end 17 18@implementation EXKernelAppRegistry 19 20- (instancetype)init 21{ 22 if (self = [super init]) { 23 _appRegistry = [[NSMutableDictionary alloc] init]; 24 } 25 return self; 26} 27 28- (NSString *)registerAppWithManifestUrl:(NSURL *)manifestUrl initialProps:(NSDictionary *)initialProps 29{ 30 NSAssert(manifestUrl, @"Cannot register an app with no manifest URL"); 31 // not enforcing uniqueness yet - we will do this once we download the manifest & have the experience scope key 32 EXKernelAppRecord *newRecord = [[EXKernelAppRecord alloc] initWithManifestUrl:manifestUrl initialProps:initialProps]; 33 NSString *recordId = [[NSUUID UUID] UUIDString]; 34 [_appRegistry setObject:newRecord forKey:recordId]; 35 36 if (_delegate) { 37 [_delegate appRegistry:self didRegisterAppRecord:newRecord]; 38 } 39 40 return recordId; 41} 42 43- (void)unregisterAppWithRecordId:(NSString *)recordId 44{ 45 EXKernelAppRecord *record = [_appRegistry objectForKey:recordId]; 46 if (record) { 47 if (_delegate) { 48 [_delegate appRegistry:self willUnregisterAppRecord:record]; 49 } 50 [record.appManager invalidate]; 51 [_appRegistry removeObjectForKey:recordId]; 52 } 53} 54 55- (void)unregisterAppWithRecord:(nullable EXKernelAppRecord *)appRecord 56{ 57 NSArray *recordIds = [_appRegistry allKeysForObject:appRecord]; 58 if (recordIds.count > 0) { 59 [self unregisterAppWithRecordId:recordIds[0]]; 60 } 61} 62 63- (void)registerHomeAppRecord:(EXKernelAppRecord *)homeRecord 64{ 65 _homeAppRecord = homeRecord; 66} 67 68- (void)unregisterHomeAppRecord 69{ 70 _homeAppRecord = nil; 71} 72 73- (EXKernelAppRecord *)homeAppRecord 74{ 75 return _homeAppRecord; 76} 77 78- (EXKernelAppRecord *)standaloneAppRecord 79{ 80 if ([EXEnvironment sharedEnvironment].isDetached) { 81 for (NSString *recordId in self.appEnumerator) { 82 EXKernelAppRecord *record = [self recordForId:recordId]; 83 if (record.appLoader.manifestUrl 84 && [record.appLoader.manifestUrl.absoluteString isEqualToString:[EXEnvironment sharedEnvironment].standaloneManifestUrl]) { 85 return record; 86 } 87 } 88 } 89 return nil; 90} 91 92- (EXKernelAppRecord *)recordForId:(NSString *)recordId 93{ 94 return [_appRegistry objectForKey:recordId]; 95} 96 97// when reloading, for a brief period of time there are two records with the same experience scopeKey in the registry 98- (EXKernelAppRecord * _Nullable)newestRecordWithScopeKey:(NSString *)scopeKey 99{ 100 EXKernelAppRecord *recordToReturn; 101 for (NSString *recordId in self.appEnumerator) { 102 EXKernelAppRecord *record = [self recordForId:recordId]; 103 if (record && record.scopeKey && [record.scopeKey isEqualToString:scopeKey]) { 104 if (recordToReturn && [recordToReturn.timeCreated compare:record.timeCreated] == NSOrderedDescending) { 105 continue; 106 } 107 recordToReturn = record; 108 } 109 } 110 return recordToReturn; 111} 112 113- (NSEnumerator<id> *)appEnumerator 114{ 115 // TODO: use mutexes to control access to _appRegistry rather than just copying it here 116 return [(NSDictionary *)[_appRegistry copy] keyEnumerator]; 117} 118 119- (NSString *)description 120{ 121 if (_appRegistry.count > 0) { 122 NSMutableString *results = [NSMutableString string]; 123 for (NSString *recordId in self.appEnumerator) { 124 EXKernelAppRecord *record = [self recordForId:recordId]; 125 [results appendString:[NSString stringWithFormat:@" %@: %@\n", recordId, record]]; 126 } 127 return [NSString stringWithFormat:@"EXKernelAppRegistry with apps: {\n%@}", results]; 128 } 129 return @"EXKernelAppRegistry (empty)"; 130} 131 132- (BOOL)isScopeKeyUnique:(NSString *)scopeKey 133{ 134 int count = 0; 135 for (NSString *recordId in self.appEnumerator) { 136 EXKernelAppRecord *appRecord = [self recordForId:recordId]; 137 if (appRecord.scopeKey && [appRecord.scopeKey isEqualToString:scopeKey]) { 138 count++; 139 if (count > 1) { 140 return NO; 141 } 142 } 143 } 144 return YES; 145} 146 147@end 148