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