1// Copyright 2020-present 650 Industries. All rights reserved.
2
3#if __has_include(<EXFirebaseCore/EXFirebaseCore.h>)
4#import "EXScopedFirebaseCore.h"
5#import <EXFirebaseCore/EXFirebaseCore+FIROptions.h>
6
7@interface NSObject (Private)
8- (NSString*)_methodDescription;
9@end
10
11@implementation EXScopedFirebaseCore {
12  NSDictionary* _protectedAppNames;
13}
14
15- (instancetype)initWithExperienceId:(NSString *)experienceId andConstantsBinding:(EXConstantsBinding *)constantsBinding
16{
17  if (![@"expo" isEqualToString:constantsBinding.appOwnership]) {
18    return [super init];
19  }
20
21  // Setup the protected app names
22  NSMutableDictionary* protectedAppNames = [NSMutableDictionary dictionaryWithDictionary:@{
23    @"__FIRAPP_DEFAULT": @YES,
24    @"[DEFAULT]": @YES
25  }];
26  _protectedAppNames = protectedAppNames;
27
28  // Make sure the [DEFAULT] app is initialized
29  NSString *path = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"];
30  if (path && ![FIRApp defaultApp]) {
31    [FIRApp configure];
32  }
33  if ([FIRApp defaultApp]) [protectedAppNames setValue:@YES forKey:[FIRApp defaultApp].name];
34
35  // Determine project app name & options
36  NSString *encodedExperienceId = [self.class encodedResourceName:experienceId];
37  NSString* appName = [NSString stringWithFormat:@"__sandbox_%@", encodedExperienceId];
38  NSDictionary* googleServicesFile = [self.class googleServicesFileFromConstantsManifest:constantsBinding];
39  FIROptions* options = [self.class optionsWithGoogleServicesFile:googleServicesFile];
40
41  // Delete all previously created (project) apps, except for the currently
42  // loaded project and the "protected" ones
43  NSDictionary<NSString *,FIRApp *>* apps = [FIRApp allApps];
44  NSArray<NSString*>* names = [apps allKeys];
45  for (NSString* name in names) {
46    if (!protectedAppNames[name] && (!options || ![name isEqualToString:appName])) {
47      [[FIRApp appNamed:name] deleteApp:^(BOOL success) {
48        if (!success) {
49          UMLogWarn(@"Failed to delete Firebase app: %@", name);
50        }
51      }];
52    }
53  }
54
55  // Initialize the sandboxed firebase app
56  return [super initWithAppName:appName options:options];
57}
58
59# pragma mark - Overriden methods
60
61- (BOOL)isAppAccessible:(nonnull NSString *)name
62{
63  // Deny access to the protected default app on the Expo client
64  if (_protectedAppNames && _protectedAppNames[name]) {
65    return NO;
66  }
67  return [super isAppAccessible:name];
68}
69
70
71# pragma mark - Project methods
72
73+ (NSString *)encodedResourceName:(NSString *)name
74{
75  NSData *data = [name dataUsingEncoding:NSUTF8StringEncoding];
76  NSString *base64 = [data base64EncodedStringWithOptions:kNilOptions];
77  return [base64 stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"="]];
78}
79
80+ (nullable NSDictionary *)googleServicesFileFromConstantsManifest:(nullable id<UMConstantsInterface>)constants
81{
82  // load GoogleService-Info.plist from manifest
83  @try {
84    if (constants == nil) return nil;
85    NSDictionary* manifest = constants.constants[@"manifest"];
86    NSDictionary* ios = manifest ? manifest[@"ios"] : nil;
87    NSString* googleServicesFile = ios ? ios[@"googleServicesFile"] : nil;
88    if (!googleServicesFile) return nil;
89    NSData *data = [[NSData alloc] initWithBase64EncodedString:googleServicesFile options:0];
90    NSError* error;
91    NSDictionary* plist = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:nil error:&error];
92    if (error) UMLogWarn(@"Invalid googleServicesFile: %@", error);
93    return plist;
94  }
95  @catch(NSException* exception) {
96    UMLogWarn(@"Invalid googleServicesFile: %@", exception);
97    return nil;
98  }
99}
100
101+ (nullable FIROptions *)optionsWithGoogleServicesFile:(nullable NSDictionary *)plist
102{
103  if (!plist) return nil;
104
105  FIROptions *firOptions = [[FIROptions alloc] initWithGoogleAppID:plist[@"GOOGLE_APP_ID"] GCMSenderID:plist[@"GCM_SENDER_ID"]];
106
107  firOptions.APIKey = plist[@"API_KEY"];
108  firOptions.bundleID = plist[@"BUNDLE_ID"];
109  firOptions.clientID = plist[@"CLIENT_ID"];
110  firOptions.trackingID = plist[@"TRACKING_ID"];
111  firOptions.projectID = plist[@"PROJECT_ID"];
112  firOptions.androidClientID = plist[@"ANDROID_CLIENT_ID"];
113  firOptions.databaseURL = plist[@"DATABASE_URL"];
114  firOptions.storageBucket = plist[@"STORAGE_BUCKET"];
115
116  return firOptions;
117}
118
119@end
120
121#endif
122