invertase / react-native-firebase

🔥 A well-tested feature-rich modular Firebase implementation for React Native. Supports both iOS & Android platforms for all Firebase services.
https://rnfirebase.io
Other
11.71k stars 2.22k forks source link

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

Open younes0 opened 3 months ago

younes0 commented 3 months ago

Is there an existing issue for this?

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

Please confirm, this issue is NOT for Android or 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 commented 3 months ago

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)
shilo-klydo commented 3 months ago

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?

younes0 commented 3 months ago

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

shilo-klydo commented 3 months ago

@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?

younes0 commented 3 months ago

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

aleksaelezovic commented 3 months ago

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

younes0 commented 2 months ago

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.

github-actions[bot] commented 1 month ago

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.

younes0 commented 1 month ago

not stale

vipulbhj commented 4 weeks ago

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

vipulbhj commented 4 weeks ago

@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 commented 4 weeks ago

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

mikehardy commented 4 weeks ago

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

vipulbhj commented 4 weeks ago

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.

mikehardy commented 4 weeks ago

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

vipulbhj commented 4 weeks ago

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

Sorry for tagging you directly

younes0 commented 4 weeks ago

@vipulbhj

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

// 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.

vipulbhj commented 4 weeks ago

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

github-actions[bot] commented 1 day ago

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.

mikehardy commented 1 day ago

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