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