react-native-firebase icon indicating copy to clipboard operation
react-native-firebase copied to clipboard

[🐛] Other Platforms - [Web] [Auth] Methods are unsupported or not existing

Open younes0 opened this issue 1 year ago • 34 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues.

Please confirm you are aware of the 'Other' platform limitations.

  • [X] I confirm that issue is not relating to a known platform limitation.

Please confirm, this issue is NOT for Android or iOS?

  • [X] I confirm that this issue is not for Android and not for iOS.

Please describe your issue here.

When using auth module on web, usage of these methods Located in auth\lib\index.js throw exceptions :

NativeFirebaseError: [auth/unsupported] This operation is not supported in this environment for

this.native.configureAuthDomain();

NativeFirebaseError: [auth/unknown] "" is not a function for

this.native.addAuthStateListener();
this.native.addIdTokenListener();
this.native
      .signInWithCustomToken(customToken)
      .then(userCredential => this._setUserCredential(userCredential));

Firebase app, firestore & database are initiated without any issues.

Additional context and comments

No response

younes0 avatar Jul 19 '24 15:07 younes0

Another issue (would create later): the need of polyfill setimmediate otherwise error is thrown:

ReferenceError: setImmediate is not defined
    at Object.eventsNotifyReady (RNFBAppModule.js:209:7)
    at RNFBNativeEventEmitter.addListener (RNFBNativeEventEmitter.js:30:21)
    at subscribeToNativeModuleEvent (nativeModule.js:155:28)
    at initialiseNativeModule (nativeModule.js:133:7)
    at getNativeModule (nativeModule.js:207:10)
    at get native (FirebaseModule.js:56:41)
    at new FirebaseAuthModule (index.js:150:31)
    at firebaseModuleWithApp (namespace.js:171:57)
    at runEffect (BootingApp.tsx:52:31)

younes0 avatar Jul 19 '24 15:07 younes0

Another issue (would create later): the need of polyfill setimmediate otherwise error is thrown:

ReferenceError: setImmediate is not defined
    at Object.eventsNotifyReady (RNFBAppModule.js:209:7)
    at RNFBNativeEventEmitter.addListener (RNFBNativeEventEmitter.js:30:21)
    at subscribeToNativeModuleEvent (nativeModule.js:155:28)
    at initialiseNativeModule (nativeModule.js:133:7)
    at getNativeModule (nativeModule.js:207:10)
    at get native (FirebaseModule.js:56:41)
    at new FirebaseAuthModule (index.js:150:31)
    at firebaseModuleWithApp (namespace.js:171:57)
    at runEffect (BootingApp.tsx:52:31)

Hey @younes0 , did you find a solution for that?

shilo-klydo avatar Jul 29 '24 07:07 shilo-klydo

@shilo-klydo using the polyfill works fine. Which problem are you reffering to ?

younes0 avatar Jul 29 '24 08:07 younes0

@shilo-klydo using the polyfill works fine. Which problem are you reffering to ?

The setImmediate. sorry for the ignorance, what do you mean by polyfill?

shilo-klydo avatar Jul 29 '24 08:07 shilo-klydo

@shilo-klydo add this polyfill package https://github.com/yuzujs/setImmediate and import it at the top of your App.tsx

younes0 avatar Jul 29 '24 08:07 younes0

I am also having this problem (version: 20.3.0), happens when calling auth().onUserChanged inside of a useEffect() I followed the documentation and have similar code like here for initializing the firebase app.

Any solutions?

Also for setImmediate, same error happens to me but only when building with npx expo export --platform web However, this successfully solved it:

@shilo-klydo add this polyfill package https://github.com/yuzujs/setImmediate and import it at the top of your App.tsx

aleksaelezovic avatar Aug 01 '24 21:08 aleksaelezovic

I managed to make auth working by commenting these lines in @react-native-firebase/auth/lib/web/RNFBAuthModule.js:

// Returns a cached Firestore instance.
function getCachedAuthInstance(appName) {
  if (!instances[appName]) {
    // if (!isMemoryStorage()) {
    //   // Warn auth persistence is is disabled unless Async Storage implementation is provided.
    //   // eslint-disable-next-line no-console
    //   console.warn(
    //     ```
    // Firebase Auth persistence is disabled. To enable persistence, provide an Async Storage implementation.

    // For example, to use React Native Async Storage:

    //   import AsyncStorage from '@react-native-async-storage/async-storage';

    //   // Before initializing Firebase set the Async Storage implementation
    //   // that will be used to persist user sessions.
    //   firebase.setReactNativeAsyncStorage(AsyncStorage);

    //   // Then initialize Firebase as normal.
    //   await firebase.initializeApp({ ... });
    // ```,
    //   );
    // }
    instances[appName] = initializeAuth(getApp(appName), {
      // persistence: getReactNativePersistence(
      //   getReactNativeAsyncStorageInternal(),
      // ),
    });
  }
  return instances[appName];
}

Of course, this is not a even a workaround since you lose auth persistence.

younes0 avatar Sep 02 '24 10:09 younes0

Hello 👋, to help manage issues we automatically close stale issues.

This issue has been automatically marked as stale because it has not had activity for quite some time.Has this issue been fixed, or does it still require attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

github-actions[bot] avatar Oct 01 '24 02:10 github-actions[bot]

not stale

younes0 avatar Oct 01 '24 02:10 younes0

I am also having the same issue when I try to run on web.

vipulbhj avatar Oct 14 '24 06:10 vipulbhj

@younes0 Are you using expo ? Is your app also using Native ? What does your setup look like ?

Does anyone know if calling await firebase.initializeApp({....}) only gets triggered for other platforms or not ? Do we need to manually do a platform check for calling this function ?

vipulbhj avatar Oct 14 '24 06:10 vipulbhj

Has anyone been able to use this library on web ? Can someone provide any examples ?

vipulbhj avatar Oct 14 '24 06:10 vipulbhj

I use it on web, but before the "other" platform support, via shims that alias the packages between react-native-firebase and firebase-js-sdk, similar to what was implemented here but an older style.

There is a demonstration of same here, you can see the shim definitions that do the aliasing for webpack:

https://github.com/invertase/react-native-firebase-authentication-example/blob/68bd37f8c219f892655b40c73e9818370f368b82/template/craco.config.js#L105-L116

On the web, you run initializeApp:

https://github.com/invertase/react-native-firebase-authentication-example/blob/main/template/src/shims/firebase-app-web.ts

On other platforms you do not, because it is done at a very very early stage of app initialization in native code

You can think of it this way: one of the primary reasons to run firebase natively (vs just using firebase-js-sdk) is for crashlytics to be usable, and catch all native crashes including those at startup before the javascript engine is running. Additionally to start logging performance timers and perhaps startup analytics. Those all happen natively, prior to javascript being available, so the app must already be initialized natively correct? It is, from the google services json/plist files whose information is built into the app at boot time and used by the native sdks

mikehardy avatar Oct 14 '24 11:10 mikehardy

I wonder the support for "Other Platforms" works. I tried this with Platform checks, but it looks ugly.

If someone can point to where I might be able to start looking into to fix this, I would even love to contribute to this. I was browsing package code today, but it being split into individual packages makes it slightly harder.

if someone has some context, I would love if I can be given context and help speed this up.

vipulbhj avatar Oct 14 '24 12:10 vipulbhj

I tried this with Platform checks, but it looks ugly.

I'm not aware of any other way to do it, given what I wrote above about the timing of initialization. Web must initialize delayed once you have javascript available, native must initialize very very early before javascript is available. Developers want features (boot time crash detection, performance etc), developer must accept realistic constraints 🤷

I haven't used the new "other" platform style myself yet and I'm not sure many have but the e2e test apps here may provide a reasonable baseline for understanding it - specifically the macos one as it is integrated using "other" if I understand correctly

mikehardy avatar Oct 14 '24 12:10 mikehardy

@Salakar If I remember correctly, it's you who implemented this. Do you have any insights on this.

Sorry for tagging you directly

vipulbhj avatar Oct 14 '24 14:10 vipulbhj

@vipulbhj

I've created unified interfaces for Firebase in web and React Native:

  • Using firebase-js for web and react-native-firebase for other platforms
  • Adapter files (auth.adapter.ts, database.adapter.ts etc.) provide consistent methods across platforms
  • FirebaseProviders with web-specific version for ReactFire integration
// auth.adapter.ts
// =====================================================
import { Platform } from "react-native";
import auth, { FirebaseAuthTypes } from "@react-native-firebase/auth";
import {
  Auth as WebAuth,
  UserCredential as WebUserCredential,
  getAuth as getWebAuth,
  signInWithCustomToken as webSignInWithCustomToken,
} from "@firebase/auth";

// types
// ---------------------------------------------------------------------------------
type AuthInstance = WebAuth | FirebaseAuthTypes.Module;

type UserCredential = WebUserCredential | FirebaseAuthTypes.UserCredential;

// methods
// ---------------------------------------------------------------------------------
export const getAuth = (): AuthInstance =>
  Platform.OS === "web" ? getWebAuth() : auth();

export const signInWithCustomToken = (token: string): Promise<UserCredential> =>
  Platform.OS === "web"
    ? webSignInWithCustomToken(getWebAuth(), token)
    : auth().signInWithCustomToken(token);

// database.adapter.ts
// =====================================================
import { Platform } from "react-native";
import {
  DataSnapshot as WebDataSnapshot,
  Database as WebDatabase,
  DatabaseReference as WebDatabaseReference,
  getDatabase as webGetDatabase,
  onValue as webOnValue,
  ref as webRef,
  set as webSet,
} from "@firebase/database";
import {
  FirebaseDatabaseTypes as NativeTypes,
  getDatabase as nativeGetDatabase,
  onValue as nativeOnValue,
  ref as nativeRef,
  set as nativeSet,
} from "@react-native-firebase/database";

// types
// ---------------------------------------------------------------------------------
export type DatabaseType = WebDatabase | NativeTypes.Module;

export type DatabaseReferenceType =
  | WebDatabaseReference
  | NativeTypes.Reference;

export type DataSnapshotType = WebDataSnapshot | NativeTypes.DataSnapshot;

export type UnsubscribeType = () => void;

// methods
// ---------------------------------------------------------------------------------
export const getDatabase = (): DatabaseType =>
  Platform.OS === "web" ? webGetDatabase() : nativeGetDatabase();

export const ref = (db: DatabaseType, path: string): DatabaseReferenceType =>
  Platform.OS === "web"
    ? webRef(db as WebDatabase, path)
    : nativeRef(db as NativeTypes.Module, path);

export const onValue = (
  reference: DatabaseReferenceType,
  callback: (snapshot: DataSnapshotType) => void,
): UnsubscribeType =>
  Platform.OS === "web"
    ? webOnValue(
        reference as WebDatabaseReference,
        callback as (snapshot: WebDataSnapshot) => void,
      )
    : nativeOnValue(
        reference as NativeTypes.Reference,
        callback as (snapshot: NativeTypes.DataSnapshot) => void,
      );

export const set = (
  reference: DatabaseReferenceType,
  value: any,
): Promise<void> =>
  Platform.OS === "web"
    ? webSet(reference as WebDatabaseReference, value)
    : nativeSet(reference as NativeTypes.Reference, value);


// FirebaseProviders.tsx
// =====================================================
import { PropsWithChildren } from "react";

const FirebaseProviders = ({ children }: PropsWithChildren) => children;

export default FirebaseProviders;

// FirebaseProviders.web.tsx
// =====================================================
import { PropsWithChildren } from "react";
import { getApp, getApps, initializeApp } from "@firebase/app";
import { initializeAuth } from "@firebase/auth";
import { FirebaseAppProvider, AuthProvider } from "reactfire";

import { getMmkvPersistence } from "@/modules/firebase/utils/auth-mmkv-persistence";

const firebaseConfig = {
  apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY,
  appId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID,
  authDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.EXPO_PUBLIC_FIREBASE_DATABASE_URL,
  messagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID,
};

export const firebaseApp = getApps().length
  ? getApp()
  : initializeApp(firebaseConfig);

export const fireBaseAuth = initializeAuth(firebaseApp, {
  persistence: getMmkvPersistence(), // required: persist auth token in mmkv
});

const FirebaseProviders = ({ children }: PropsWithChildren) => (
  <FirebaseAppProvider firebaseConfig={firebaseConfig}>
    <AuthProvider sdk={fireBaseAuth}>{children}</AuthProvider>
  </FirebaseAppProvider>
);

export default FirebaseProviders;

Code shared under MIT license.

younes0 avatar Oct 14 '24 14:10 younes0

Thank you so much @younes0, I did something very similar too.

vipulbhj avatar Oct 14 '24 14:10 vipulbhj

Hello 👋, to help manage issues we automatically close stale issues.

This issue has been automatically marked as stale because it has not had activity for quite some time.Has this issue been fixed, or does it still require attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

github-actions[bot] avatar Nov 11 '24 14:11 github-actions[bot]

The shim / adapter style does work, I use it myself and clearly there is success here from you two, however, the APIs here using "other" platform support should work as well or at least be documented as not working / PRs happily accepted... going to tag this to keep open as auth is one of the most important features for react-native-firebase so this won't just go away as long as other / web support is a goal

mikehardy avatar Nov 11 '24 15:11 mikehardy

Expo 52 with Android, iOS, and web. Android and iOS work fine, web does not. I've followed the docs to ensure I only initializeApp when the platform is web. I see these errors on auth().onAuthStateChanged(...)

Image

Image

init func:

export const initializeFirebaseApp = async () => {
    if (Platform.OS === 'web') {
        console.log('Init firebase app');
        firebase.setReactNativeAsyncStorage(AsyncStorage);
        await firebase.initializeApp(getFirebaseConfig());
    }
};

Guess I will have to go with explicitly splitting RNFB and JSFB usages as described above. Too bad because the docs say this fallback is supposed to happen automatically within RNFB.

rdfurman avatar Feb 02 '25 15:02 rdfurman

Have you tried the v9 APIs? That is, the modern ones we should all be using now?

getAuth(getApp()) and onAuthStateChanged(getAuth(getApp(), etc) (or using local vars to hold app and auth, however you want to do it)

https://firebase.google.com/docs/reference/js/auth.md#onauthstatechanged_b0d07ab

mikehardy avatar Feb 03 '25 00:02 mikehardy

I'm not using Auth so I don't have all the context here. But I ran into a similar error when trying to use Analytics for the "other" platform:

TypeError: "" is not a function
    at AnalyticsApi._getCid (entry.bundle?platform=web&dev=true&hot=false&transform.asyncRoutes=true&transform.routerRoot=src%2Fapp:71390:24)
    at async AnalyticsApi._sendEvents (entry.bundle?platform=web&dev=true&hot=false&transform.asyncRoutes=true&transform.routerRoot=src%2Fapp:71412:32)
    at async AnalyticsApi._processQueue (entry.bundle?platform=web&dev=true&hot=false&transform.asyncRoutes=true&transform.routerRoot=src%2Fapp:71377:7)

The error is because the Analytics/Auth code is using three backticks for a multi-line comment see here and here. Changing those to single backticks will at least resolve the error.

The underlying persistence problem seems to be that setReactNativeAsyncStorage is missing from packages/app/lib/modular/index.d.ts. Or maybe it's not supposed to be there, I'm not positive. But anyway I managed to get Analytics (with persistent client IDs) working by using module.setReactNativeAsyncStorage, which seems awkward but it works:

import module from '@react-native-firebase/app';
import AsyncStorage from '@react-native-async-storage/async-storage';
...
initializeApp(firebaseConfig);
module.setReactNativeAsyncStorage(AsyncStorage);

ristewar avatar Feb 03 '25 01:02 ristewar

Have you tried the v9 APIs? That is, the modern ones we should all be using now?

getAuth(getApp()) and onAuthStateChanged(getAuth(getApp(), etc) (or using local vars to hold app and auth, however you want to do it)

https://firebase.google.com/docs/reference/js/auth.md#onauthstatechanged_b0d07ab

Same deal unfortunately:

Image

imports for reference:

import {
    FirebaseAuthTypes,
    getAuth,
    onAuthStateChanged,
    signOut,
} from '@react-native-firebase/auth';

I'm not sure how you'd get the getAuth(app?: FirebaseApp) overload working considering it needs JS FirebaseApp. Import web JS Firebase, init and store, then reference it if platform is web I guess? But at that point you've already started down shim / adapter style.

Speaking of, I was able to get it working by using a similar approach to the one @younes0 was kind enough to share above.

rdfurman avatar Feb 03 '25 04:02 rdfurman

Clearly a ways to go before the "works on web seamlessly" use case is supported well! @ristewar and @rdfurman thanks for the extra details, lots of specific items for improvement there

mikehardy avatar Feb 03 '25 12:02 mikehardy

I use it on web, but before the "other" platform support, via shims that alias the packages between react-native-firebase and firebase-js-sdk, similar to what was implemented here but an older style.

There is a demonstration of same here, you can see the shim definitions that do the aliasing for webpack:

https://github.com/invertase/react-native-firebase-authentication-example/blob/68bd37f8c219f892655b40c73e9818370f368b82/template/craco.config.js#L105-L116

On the web, you run initializeApp:

https://github.com/invertase/react-native-firebase-authentication-example/blob/main/template/src/shims/firebase-app-web.ts

On other platforms you do not, because it is done at a very very early stage of app initialization in native code

You can think of it this way: one of the primary reasons to run firebase natively (vs just using firebase-js-sdk) is for crashlytics to be usable, and catch all native crashes including those at startup before the javascript engine is running. Additionally to start logging performance timers and perhaps startup analytics. Those all happen natively, prior to javascript being available, so the app must already be initialized natively correct? It is, from the google services json/plist files whose information is built into the app at boot time and used by the native sdks

Is there a modular API version of the shim example?

jsg2021 avatar May 03 '25 23:05 jsg2021

Any news about this ? Seem's to be a real issue. According to the documentation getAuth() should be working on web. I've try many workaround, without success.

enzo-billis avatar May 09 '25 09:05 enzo-billis

I open sourced a project that features web/native authentification, database & firestore implementation: https://github.com/younes0/firebase-rn-chat

younes0 avatar May 09 '25 10:05 younes0

I open sourced a project that features web/native authentification, database & firestore implementation: https://github.com/younes0/firebase-rn-chat

Thanks ! But it sounds like an heavy workaround

enzo-billis avatar May 09 '25 10:05 enzo-billis

Webpack seems to be no longer supported in the latest version of the Expo SDK. Any updates? Any way to configure shims on babel/metro?

julian-hecker avatar May 21 '25 00:05 julian-hecker