// Copyright 2015-present 650 Industries. All rights reserved. #import "EXKernelAppRegistry.h" #import "EXAbstractLoader.h" #import "EXEnvironment.h" #import "EXReactAppManager.h" #import "EXKernel.h" #import @interface EXKernelAppRegistry () @property (nonatomic, strong) NSMutableDictionary *appRegistry; @property (nonatomic, strong) EXKernelAppRecord *homeAppRecord; @end @implementation EXKernelAppRegistry - (instancetype)init { if (self = [super init]) { _appRegistry = [[NSMutableDictionary alloc] init]; } return self; } - (NSString *)registerAppWithManifestUrl:(NSURL *)manifestUrl initialProps:(NSDictionary *)initialProps { NSAssert(manifestUrl, @"Cannot register an app with no manifest URL"); // not enforcing uniqueness yet - we will do this once we download the manifest & have the experience scope key EXKernelAppRecord *newRecord = [[EXKernelAppRecord alloc] initWithManifestUrl:manifestUrl initialProps:initialProps]; NSString *recordId = [[NSUUID UUID] UUIDString]; [_appRegistry setObject:newRecord forKey:recordId]; if (_delegate) { [_delegate appRegistry:self didRegisterAppRecord:newRecord]; } return recordId; } - (void)unregisterAppWithRecordId:(NSString *)recordId { EXKernelAppRecord *record = [_appRegistry objectForKey:recordId]; if (record) { if (_delegate) { [_delegate appRegistry:self willUnregisterAppRecord:record]; } [record.appManager invalidate]; [_appRegistry removeObjectForKey:recordId]; } } - (void)unregisterAppWithRecord:(nullable EXKernelAppRecord *)appRecord { NSArray *recordIds = [_appRegistry allKeysForObject:appRecord]; if (recordIds.count > 0) { [self unregisterAppWithRecordId:recordIds[0]]; } } - (void)registerHomeAppRecord:(EXKernelAppRecord *)homeRecord { _homeAppRecord = homeRecord; } - (void)unregisterHomeAppRecord { _homeAppRecord = nil; } - (EXKernelAppRecord *)homeAppRecord { return _homeAppRecord; } - (EXKernelAppRecord *)standaloneAppRecord { if ([EXEnvironment sharedEnvironment].isDetached) { for (NSString *recordId in self.appEnumerator) { EXKernelAppRecord *record = [self recordForId:recordId]; if (record.appLoader.manifestUrl && [record.appLoader.manifestUrl.absoluteString isEqualToString:[EXEnvironment sharedEnvironment].standaloneManifestUrl]) { return record; } } } return nil; } - (EXKernelAppRecord *)recordForId:(NSString *)recordId { return [_appRegistry objectForKey:recordId]; } // when reloading, for a brief period of time there are two records with the same experience scopeKey in the registry - (EXKernelAppRecord * _Nullable)newestRecordWithScopeKey:(NSString *)scopeKey { EXKernelAppRecord *recordToReturn; for (NSString *recordId in self.appEnumerator) { EXKernelAppRecord *record = [self recordForId:recordId]; if (record && record.scopeKey && [record.scopeKey isEqualToString:scopeKey]) { if (recordToReturn && [recordToReturn.timeCreated compare:record.timeCreated] == NSOrderedDescending) { continue; } recordToReturn = record; } } return recordToReturn; } - (NSEnumerator *)appEnumerator { // TODO: use mutexes to control access to _appRegistry rather than just copying it here return [(NSDictionary *)[_appRegistry copy] keyEnumerator]; } - (NSString *)description { if (_appRegistry.count > 0) { NSMutableString *results = [NSMutableString string]; for (NSString *recordId in self.appEnumerator) { EXKernelAppRecord *record = [self recordForId:recordId]; [results appendString:[NSString stringWithFormat:@" %@: %@\n", recordId, record]]; } return [NSString stringWithFormat:@"EXKernelAppRegistry with apps: {\n%@}", results]; } return @"EXKernelAppRegistry (empty)"; } - (BOOL)isScopeKeyUnique:(NSString *)scopeKey { int count = 0; for (NSString *recordId in self.appEnumerator) { EXKernelAppRecord *appRecord = [self recordForId:recordId]; if (appRecord.scopeKey && [appRecord.scopeKey isEqualToString:scopeKey]) { count++; if (count > 1) { return NO; } } } return YES; } @end