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