Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[🐛] Storage getDownloadURL() does not work with Firebase Emulator #6638

Closed
2 of 10 tasks
Nova41 opened this issue Oct 27, 2022 · 8 comments · Fixed by #6641
Closed
2 of 10 tasks

[🐛] Storage getDownloadURL() does not work with Firebase Emulator #6638

Nova41 opened this issue Oct 27, 2022 · 8 comments · Fixed by #6641
Labels
platform: ios plugin: storage Firebase Cloud Storage type: bug New bug report

Comments

@Nova41
Copy link

Nova41 commented Oct 27, 2022

Issue

The put and getDownloadURL() methods in the storage module works with Firebase but not with Firebase Emulator, specifically shown in the following snippet:

const storagePath = 'some/path';
const putResult = await storage().ref(storagePath).putFile(someFile);
console.log(putResult.ref);  // <-- undefined, but a ref to the file is expected.
console.log(storage().ref(storagePath));  // <-- this returns the correct ref, however
console.log(await storage().ref(storagePath).getDownloadURL());  // <-- [storage/unknown] An unknown error has occurred.

The client has given permission to read and write any files and was connected to the emulator before the function calls.

My speculation is that getDownloadURL() fetches a signed url, while the emulator explicitly does not support generating a signed url (however it does support generating an unsigned one, i.e. publicUrl, in the format of something like http://localhost:9091/foo.appspot.com/file.abc).

I am not sure about the ref being null. Had anybody encountered this issue before?

Thank you for taking the valuable time to read this. Please correct me if this is not an issue with react-native-firebase.


Project Files

Javascript

Click To Expand

package.json:

{
  "name": "app",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    "postinstall": "node ./bin/postInstall"
  },
  "dependencies": {
    "@react-native-firebase/app": "^16.1.0",
    "@react-native-firebase/auth": "^16.1.0",
    "@react-native-firebase/firestore": "^16.1.0",
    "@react-native-firebase/functions": "^16.1.0",
    "@react-native-firebase/storage": "^16.1.0",
    "@react-navigation/drawer": "^6.5.0",
    "@react-navigation/native": "^6.0.13",
    "@react-navigation/native-stack": "^6.9.1",
    "react": "18.1.0",
    "react-native": "0.70.3",
  },
  "devDependencies": {
    "@babel/core": "^7.12.9",
    "@babel/runtime": "^7.12.5",
    "@react-native-community/eslint-config": "^2.0.0",
    "@tsconfig/react-native": "^2.0.2",
    "@types/jest": "^26.0.23",
    "@types/react": "^18.0.21",
    "@types/react-native": "^0.70.4",
    "@types/react-test-renderer": "^18.0.0",
    "@typescript-eslint/eslint-plugin": "^5.37.0",
    "@typescript-eslint/parser": "^5.37.0",
    "babel-jest": "^26.6.3",
    "eslint": "^7.32.0",
    "eslint-plugin-reanimated": "^2.0.0",
    "jest": "^26.6.3",
    "metro-react-native-babel-preset": "0.72.3",
    "react-test-renderer": "18.1.0",
    "typescript": "^4.8.3"
  },
}

firebase.json for react-native-firebase v6:

# N/A

iOS

Click To Expand

ios/Podfile:

  • I'm not using Pods
  • I'm using Pods and my Podfile looks like:
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

$RNFirebaseAsStaticFramework = true

platform :ios, '12.4'
install! 'cocoapods', :deterministic_uuids => false

target 'App' do
  use_frameworks! :linkage => :static

  config = use_native_modules!

  # Flags change depending on the env values.
  flags = get_default_flags()

  use_react_native!(
    :path => config[:reactNativePath],
    # Hermes is now enabled by default. Disable by setting this flag to false.
    # Upcoming versions of React Native may rely on get_default_flags(), but
    # we make it explicit here to aid in the React Native upgrade process.
    :hermes_enabled => true,
    :fabric_enabled => flags[:fabric_enabled],
    # Enables Flipper.
    #
    # Note that if you have use_frameworks! enabled, Flipper will not work and
    # you should disable the next line.
    # :flipper_configuration => FlipperConfiguration.enabled,
    # An absolute path to your application root.
    :app_path => "#{Pod::Config.instance.installation_root}/.."
  )

  target 'KanbusTests' do
    inherit! :complete
    # Pods for testing
  end

  post_install do |installer|
    react_native_post_install(
      installer,
      # Set `mac_catalyst_enabled` to `true` in order to apply patches
      # necessary for Mac Catalyst builds
      :mac_catalyst_enabled => false
    )
    __apply_Xcode_12_5_M1_post_install_workaround(installer)
  end
end

AppDelegate.m:

#import "AppDelegate.h"

#import <Firebase.h>

#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import <React/RCTAppSetupUtils.h>

#if RCT_NEW_ARCH_ENABLED
#import <React/CoreModulesPlugins.h>
#import <React/RCTCxxBridgeDelegate.h>
#import <React/RCTFabricSurfaceHostingProxyRootView.h>
#import <React/RCTSurfacePresenter.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <ReactCommon/RCTTurboModuleManager.h>

#import <react/config/ReactNativeConfig.h>

static NSString *const kRNConcurrentRoot = @"concurrentRoot";

@interface AppDelegate () <RCTCxxBridgeDelegate, RCTTurboModuleManagerDelegate> {
  RCTTurboModuleManager *_turboModuleManager;
  RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
  std::shared_ptr<const facebook::react::ReactNativeConfig> _reactNativeConfig;
  facebook::react::ContextContainer::Shared _contextContainer;
}
@end
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  [FIRApp configure];
  RCTAppSetupPrepareApp(application);

  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];

#if RCT_NEW_ARCH_ENABLED
  _contextContainer = std::make_shared<facebook::react::ContextContainer const>();
  _reactNativeConfig = std::make_shared<facebook::react::EmptyReactNativeConfig const>();
  _contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
  _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer];
  bridge.surfacePresenter = _bridgeAdapter.surfacePresenter;
#endif

  NSDictionary *initProps = [self prepareInitialProps];
  UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"Kanbus", initProps);

  if (@available(iOS 13.0, *)) {
    rootView.backgroundColor = [UIColor systemBackgroundColor];
  } else {
    rootView.backgroundColor = [UIColor whiteColor];
  }

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  return YES;
}

/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
///
/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
/// @return: `true` if the `concurrentRoot` feture is enabled. Otherwise, it returns `false`.
- (BOOL)concurrentRootEnabled
{
  // Switch this bool to turn on and off the concurrent root
  return true;
}

- (NSDictionary *)prepareInitialProps
{
  NSMutableDictionary *initProps = [NSMutableDictionary new];

#ifdef RCT_NEW_ARCH_ENABLED
  initProps[kRNConcurrentRoot] = @([self concurrentRootEnabled]);
#endif

  return initProps;
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

#if RCT_NEW_ARCH_ENABLED

#pragma mark - RCTCxxBridgeDelegate

- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
{
  _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
                                                             delegate:self
                                                            jsInvoker:bridge.jsCallInvoker];
  return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager);
}

#pragma mark RCTTurboModuleManagerDelegate

- (Class)getModuleClassFromName:(const char *)name
{
  return RCTCoreModulesClassProvider(name);
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
                                                      jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
  return nullptr;
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
                                                     initParams:
                                                         (const facebook::react::ObjCTurboModule::InitParams &)params
{
  return nullptr;
}

- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
  return RCTAppSetupDefaultModuleFromClass(moduleClass);
}

#endif

@end


Android

(Didn't try)

Click To Expand

Have you converted to AndroidX?

  • my application is an AndroidX application?
  • I am using android/gradle.settings jetifier=true for Android compatibility?
  • I am using the NPM package jetifier for react-native compatibility?

android/build.gradle:

// N/A

android/app/build.gradle:

// N/A

android/settings.gradle:

// N/A

MainApplication.java:

// N/A

AndroidManifest.xml:

<!-- N/A -->


Environment

Click To Expand

react-native info output:

System:
    OS: macOS 12.6
    CPU: (10) arm64 Apple M1 Pro
    Memory: 924.13 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 16.17.1 - /opt/homebrew/opt/node@16/bin/node
    Yarn: 1.22.19 - /opt/homebrew/bin/yarn
    npm: 8.15.0 - /opt/homebrew/opt/node@16/bin/npm
    Watchman: 2022.10.03.00 - /opt/homebrew/bin/watchman
  Managers:
    CocoaPods: 1.11.3 - /opt/homebrew/bin/pod
  SDKs:
    iOS SDK:
      Platforms: DriverKit 21.4, iOS 15.5, macOS 12.3, tvOS 15.4, watchOS 8.5
    Android SDK: Not Found
  IDEs:
    Android Studio: Not Found
    Xcode: 13.4.1/13F100 - /usr/bin/xcodebuild
  Languages:
    Java: 17.0.2 - /usr/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 18.1.0 => 18.1.0 
    react-native: 0.70.3 => 0.70.3 
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found
  • Platform that you're experiencing the issue on:
    • iOS
    • Android
    • iOS but have not tested behavior on Android
    • Android but have not tested behavior on iOS
    • Both
  • react-native-firebase version you're using that has this issue: 16.1.0
  • Firebase module(s) you're using that has the issue: @react-native-firebase/storage
  • Are you using TypeScript? Y 4.8.3


@Nova41 Nova41 added help: needs-triage Issue needs additional investigation/triaging. type: bug New bug report labels Oct 27, 2022
@hirvesh
Copy link

hirvesh commented Oct 27, 2022

Can confirm; having the same error using the latest version of RNF but not when using the web SDK.

@mikehardy
Copy link
Collaborator

mikehardy commented Oct 27, 2022

Hey there - sorry for the hassle, I thought this was in the release notes but it is not. It was a known issue in the v16 release here but we needed to go ahead with release to get firestore counts support out

Anyway - this is an iOS specific upstream issue, it will be fixed when firebase-ios-sdk 10.1.0 is released, hopefully shortly

This is the issue tracking: firebase/firebase-ios-sdk#10374
This is the PR I made with the fix: firebase/firebase-ios-sdk#10370

It is already merged, just pending release

In the meantime, you may stay on react-native-firebase v15.7.0 or use Android

@mikehardy mikehardy added platform: ios Type: Firebase plugin: storage Firebase Cloud Storage and removed help: needs-triage Issue needs additional investigation/triaging. labels Oct 27, 2022
@mikehardy
Copy link
Collaborator

@Nova41
Copy link
Author

Nova41 commented Oct 27, 2022

Thank you so much for the timely response and clarification!

@Nova41 Nova41 closed this as completed Oct 27, 2022
@Nova41
Copy link
Author

Nova41 commented Oct 27, 2022

Sorry for reopening this. Downgrading to v15.7.0 solved the getDownloadURL() issue, but putResult.ref is still undefined. Is this expected behavior?

@Nova41 Nova41 reopened this Oct 27, 2022
@mikehardy
Copy link
Collaborator

Hmm - and it's fine to reopen, it is actually still an issue in the current release so being open for getDownloadURL alone is not a bad idea, while we wait for firebase-ios-sdk v10.1.0 to release

To your question on putResult.ref - my normal action is to go and check our e2e tests to see if they exercise it, and because the e2e coverage is very high in general I have the hope it does exercise it, and I can say "works for me" 😅

In this case it looks like an API usage thing - I don't see "ref" on the object returned from putFile:

https://rnfirebase.io/reference/storage/reference#putFile returns Task
https://rnfirebase.io/reference/storage/task -- no ref there?

So it seems expected

Task.snapshot is a TaskSnapshot and it has a ref?

https://rnfirebase.io/reference/storage/tasksnapshot

@Nova41
Copy link
Author

Nova41 commented Nov 1, 2022

Hmm - and it's fine to reopen, it is actually still an issue in the current release so being open for getDownloadURL alone is not a bad idea, while we wait for firebase-ios-sdk v10.1.0 to release

To your question on putResult.ref - my normal action is to go and check our e2e tests to see if they exercise it, and because the e2e coverage is very high in general I have the hope it does exercise it, and I can say "works for me" 😅

In this case it looks like an API usage thing - I don't see "ref" on the object returned from putFile:

https://rnfirebase.io/reference/storage/reference#putFile returns Task https://rnfirebase.io/reference/storage/task -- no ref there?

So it seems expected

Task.snapshot is a TaskSnapshot and it has a ref?

https://rnfirebase.io/reference/storage/tasksnapshot

Thanks for pointing that out, checking e2e tests is definitely a good way to self-diagnose!

I rechecked the definition - https://rnfirebase.io/reference/storage/reference#putFile does return Task, which is a promise that gets resolved to a TaskSnapshot (lib/index.d.ts#L848)

    then(
      onFulfilled?: ((a: TaskSnapshot) => any) | null,
      onRejected?: ((a: NativeFirebaseError) => any) | null,
    ): Promise<any>;

TaskSnapshot has a ref but it is always null when using emulators

@mikehardy
Copy link
Collaborator

🤔

I rechecked the definition - https://rnfirebase.io/reference/storage/reference#putFile does return Task, which is a promise that gets resolved to a TaskSnapshot (lib/index.d.ts#L848)

What happens if you treat it as a Task vs a TaskSnapshot, and instead chain Task.snapshot.ref ?

This may need to be a separate issue, and first step will be to determine if our types are correct, perhaps it should be return typed as Promise (?) with second job being to determine where the issue is, as it may be firebase-ios-sdk, firebase-tools, or react-native-firebase, there are a few issues that are in between like that and it is difficult to determine. Examining upstream APIs then comparing cloud vs emulator behavior takes some time...

Likely something in firebase-ios-sdk at the moment, but maybe not a bug really - they converted Storage to Swift so the typing is more strict now and is exposing things that are differences in previous behavior from APIs as well, which is cleaning out downstream expectation errors (in this module) at the same time that sometimes it's a Swift conversion issue.

Which is a bit vague, sorry, but if it were cleanly documented in a new issue that could help

jhengineerartist added a commit to jhengineerartist/art-website that referenced this issue Sep 2, 2023
firebase admin based backend.

I have an issue where the obtained signed URL links to
production google servers rather than the emulated storage,
and calling getDownloadURL to obtain the public url just
straight up crashes the emulator.

Some relevant links to investigate that particular issue.
firebase/firebase-admin-node#1352
firebase/firebase-tools#3396
invertase/react-native-firebase#6638
https://stackoverflow.com/questions/42956250/get-download-url
-from-file-uploaded-with-cloud-functions-for-firebase

This changelist will reproduce the issue if you try to change to
getDownloadURL or try to get an emulated storage url

THIS IS NOT A FUNCTIONAL CHANGELIST.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
platform: ios plugin: storage Firebase Cloud Storage type: bug New bug report
Projects
None yet
3 participants