1// Copyright 2015-present 650 Industries. All rights reserved.
2
3#import "EXKernelAppRegistry.h"
4#import "EXKernelAppLoader.h"
5#import "EXReactAppManager.h"
6#import "EXKernel.h"
7#import "EXShellManager.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 experienceId
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)registerHomeAppRecord:(EXKernelAppRecord *)homeRecord
56{
57  _homeAppRecord = homeRecord;
58}
59
60- (void)unregisterHomeAppRecord
61{
62  _homeAppRecord = nil;
63}
64
65- (EXKernelAppRecord *)homeAppRecord
66{
67  return _homeAppRecord;
68}
69
70- (EXKernelAppRecord *)standaloneAppRecord
71{
72  if ([EXShellManager sharedInstance].isShell) {
73    for (NSString *recordId in self.appEnumerator) {
74      EXKernelAppRecord *record = [self recordForId:recordId];
75      if (record.appLoader.manifestUrl
76          && [record.appLoader.manifestUrl.absoluteString isEqualToString:[EXShellManager sharedInstance].shellManifestUrl]) {
77        return record;
78      }
79    }
80  }
81  return nil;
82}
83
84- (EXKernelAppRecord *)recordForId:(NSString *)recordId
85{
86  return [_appRegistry objectForKey:recordId];
87}
88
89// when reloading, for a brief period of time there are two records with the same experienceId in the registry
90- (EXKernelAppRecord * _Nullable)newestRecordWithExperienceId:(NSString *)experienceId
91{
92  EXKernelAppRecord *recordToReturn;
93  for (NSString *recordId in self.appEnumerator) {
94    EXKernelAppRecord *record = [self recordForId:recordId];
95    if (record && record.experienceId && [record.experienceId isEqualToString:experienceId]) {
96      if (recordToReturn && [recordToReturn.timeCreated compare:record.timeCreated] == NSOrderedDescending) {
97        continue;
98      }
99      recordToReturn = record;
100    }
101  }
102  return recordToReturn;
103}
104
105- (NSEnumerator<id> *)appEnumerator
106{
107  // TODO: use mutexes to control access to _appRegistry rather than just copying it here
108  return [(NSDictionary *)[_appRegistry copy] keyEnumerator];
109}
110
111- (NSString *)description
112{
113  if (_appRegistry.count > 0) {
114    NSMutableString *results = [NSMutableString string];
115    for (NSString *recordId in self.appEnumerator) {
116      EXKernelAppRecord *record = [self recordForId:recordId];
117      [results appendString:[NSString stringWithFormat:@"  %@: %@\n", recordId, record]];
118    }
119    return [NSString stringWithFormat:@"EXKernelAppRegistry with apps: {\n%@}", results];
120  }
121  return @"EXKernelAppRegistry (empty)";
122}
123
124- (BOOL)isExperienceIdUnique:(NSString *)experienceId
125{
126  int count = 0;
127  for (NSString *recordId in self.appEnumerator) {
128    EXKernelAppRecord *appRecord = [self recordForId:recordId];
129    if (appRecord.experienceId && [appRecord.experienceId isEqualToString:experienceId]) {
130      count++;
131      if (count > 1) {
132        return NO;
133      }
134    }
135  }
136  return YES;
137}
138
139@end
140