Setting a timer for a long period of time, i.e. multiple minutes

前端 未结 14 1197
面向向阳花
面向向阳花 2020-12-02 12:23

I want to use firebase auth with react native for Login and Signup but I got a yellow error:

Setting a timer for a long p

相关标签:
14条回答
  • 2020-12-02 12:37

    I got the same issue and I think it's problem of firebase web SDK so I decided to drop firebase web SDK because it runs in js thread, not react-native thread.

    And I found react-native-firebase . It's better than firebase web SDK with higher performance and this issue went away

    0 讨论(0)
  • 2020-12-02 12:41

    For firebase/firestore users: Rather than trying to mask / hide the warning, I simply reverted to the REST API. Works like a charm :)

    • I'm using the firebase node package for auth
    • I'm using regular HTTP calls for firestore (which also gets rid of the otherwise necessary atob and bto hacks)

    Snippet below just uses an HTTP client (https://github.com/hardcodet/httpclient-js), you can use whatever works for you (e.g. fetch or axios).

    // Firebase App (the core Firebase SDK) is always required and
    // must be listed before other Firebase SDKs
    import * as firebase from "firebase/app";
    import "firebase/auth";
    import {LoginStatus} from "./LoginStatus";
    import {auth, User} from "firebase";
    import {ApiResponse, DelegateBearerAuthClient, HttpClient} from "@hardcodet/httpclient";
    import {Env} from "../../Env";
    
    
    
    export class FirebaseClient {
    
        /**
         * Creates a simple API client to use with Firestore.
         * We don't want to use the JS package's implementation since it has issues with
         * long-running timers - see https://github.com/firebase/firebase-js-sdk/issues/97
         * @param idToken The user's ID token as retrieved through firebase auth
         */
        private static getFirestoreClient(idToken: string) {
            const projectId = Env.firebaseProjectId;
            const baseUri = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/`
    
            const authClient = new DelegateBearerAuthClient(async () => idToken);
            return new HttpClient(baseUri, {authClient});
        }
    
    
        /**
         * Use firebase auth for login etc. because lazy.
         */
        public static async userLogin(email: string, password: string): Promise<User> {
            try {
                const credentials: firebase.auth.UserCredential = await auth().signInWithEmailAndPassword(email, password);
                return credentials.user;
            } catch (error) {
                console.error(error);
                return undefined;
            }
        }
    
    
        private static resolveStatus(errorCode: string): { user: User, status: LoginStatus } {
            switch (errorCode) {
                case "auth/invalid-email":
                    return {user: undefined, status: LoginStatus.InvalidEmailAddress};
                case "auth/user-not-found":
                    return {user: undefined, status: LoginStatus.UnknownUserId};
                case "auth/wrong-password":
                    return {user: undefined, status: LoginStatus.WrongPassword};
                case "auth/email-already-in-use":
                    return {user: undefined, status: LoginStatus.EmailAddressAlreadyInUse};
                case "auth/weak-password":
                    return {user: undefined, status: LoginStatus.WeakPassword};
                case "auth/user-disabled":
                    return {user: undefined, status: LoginStatus.UserDisabled};
                case "auth/expired-action-code":
                    return {user: undefined, status: LoginStatus.InvalidActionCode};
                default:
                    return {user: undefined, status: LoginStatus.Undefined};
            }
        }
    
    
        /**
         * Resolve the user's keys from the backend.
         */
        public static async getSomeUserData(firebaseUserId: string, idToken: string): Promise<...> {
    
            const client: HttpClient = FirebaseClient.getFirestoreClient(idToken);
    
            // userData here is the name of my collection in firestore. i'm using the user ID as the document ID
            var result = await client.getAs<any>(`userData/${firebaseUserId}?key=${Env.firebaseApiKey}`);
            if (result.success) {
                const json = result.value;
                const foo = json.fields.foo.stringValue;
                const bar = json.fields.bar.stringValue;
                return ...
            } else {
                if (result.notFound) {
                    // that document with that key doesn't exist
                    console.warn("no document with key " + firebaseUserId);
                    return undefined;
                }
                throw result.createError();
            }
        }
    
    
        public static async writeSomeData(idToken: string, firebaseUserId: string, foo: string, bar: string): Promise<...> {
    
            const data = {
                "fields": {
                    "foo": {
                        "stringValue": foo
                    },
                    "bar": {
                        "stringValue": bar
                    }
                }
            };
    
            // again, just do an HTTP post, use the firebase user ID as the document key
            const client: HttpClient = FirebaseClient.getFirestoreClient(idToken);
            const result: ApiResponse = await client.post(`userData?documentId=${firebaseUserId}&key=${Env.firebaseApiKey}`, data);
    
            if (result.success) {
                return ...
            } else {
                throw result.createError();
            }
        }
    
    
        /**
         * Gets the currently logged in user, if any.
         */
        public static getCurrentUser(): User {
            return auth().currentUser;
        }
    
    
    
        public static async sendPasswordReset(email: string): Promise<LoginStatus> {
    
            try {
                await auth().sendPasswordResetEmail(email);
                return LoginStatus.Success;
            } catch (error) {
                return FirebaseClient.resolveStatus(error.code).status;
            }
        }
    
    
        /**
         * Clears the current user from the session.
         */
        public static async signOut() {
            await auth().signOut();
        }
    }
    

    Note the idToken parameter - this is simply the ID token you get off the firebase User class:

            const user: User = await FirebaseClient.userLogin("me@domain.com", "mypassword");
    
            const idToken= await user.getIdToken(false);
            const data = await FirebaseClient.getSomeUserData(user.uid, idToken);
    
    0 讨论(0)
  • 2020-12-02 12:43

    Work around issue with yellow warning 'Setting a timer' .

    copy & import following file (as fast as you can ;-))

    import {Platform, InteractionManager} from 'react-native';
    
    const _setTimeout = global.setTimeout;
    const _clearTimeout = global.clearTimeout;
    const MAX_TIMER_DURATION_MS = 60 * 1000;
    if (Platform.OS === 'android') {
        // Work around issue `Setting a timer for long time`
        // see: https://github.com/firebase/firebase-js-sdk/issues/97
        const timerFix = {};
        const runTask = (id, fn, ttl, args) => {
            const waitingTime = ttl - Date.now();
            if (waitingTime <= 1) {
                InteractionManager.runAfterInteractions(() => {
                    if (!timerFix[id]) {
                        return;
                    }
                    delete timerFix[id];
                    fn(...args);
                });
                return;
            }
    
            const afterTime = Math.min(waitingTime, MAX_TIMER_DURATION_MS);
            timerFix[id] = _setTimeout(() => runTask(id, fn, ttl, args), afterTime);
        };
    
        global.setTimeout = (fn, time, ...args) => {
            if (MAX_TIMER_DURATION_MS < time) {
                const ttl = Date.now() + time;
                const id = '_lt_' + Object.keys(timerFix).length;
                runTask(id, fn, ttl, args);
                return id;
            }
            return _setTimeout(fn, time, ...args);
        };
    
        global.clearTimeout = id => {
            if (typeof id === 'string' && id.startsWith('_lt_')) {
                _clearTimeout(timerFix[id]);
                delete timerFix[id];
                return;
            }
            _clearTimeout(id);
        };
    }
    
    0 讨论(0)
  • 2020-12-02 12:43

    In login(), your setTimeout() call is missing an interval value. As a general, browsers now do not fire timeouts/intervals if the window/tab is in the background, or at least they are not expected to be timely. This is to prevent abusive script behaviour, and to reduce power consumption by scripts that may be polling.

    Your code should work in principle, if the user switches away from the window whilst the timer is running, it will complete when they return. This is probably what you want from a UX point of view, because the users sees the transition, rather than it happening in the background when they are not looking. It helps them maintain mental context.

    The yellow box is because you are setting an excessively long timer according to the message (nearly two minutes) and that is unlikely to be contextually what you want. The JS environment is warning you that what you are doing it not likely what you intend. You can mute the warning if it is what you want.

    0 讨论(0)
  • 2020-12-02 12:45

    This fixes the yellow box and the console log. It even fixes it for Expo.

    Simply place the following script at the beginning of your codebase.

    import { YellowBox } from 'react-native';
    import _ from 'lodash';
    
    YellowBox.ignoreWarnings(['Setting a timer']);
    const _console = _.clone(console);
    console.warn = message => {
      if (message.indexOf('Setting a timer') <= -1) {
        _console.warn(message);
      }
    };
    
    0 讨论(0)
  • 2020-12-02 12:52
    import { YellowBox } from 'react-native';
    
    construct() {
        YellowBox.ignoreWarnings(['Setting a timer']);
    }
    

    This ignores the warning for me. You should add this to the constructor of every page that shows the warning.

    Ignoring it is not the best approach, but if you're using Firebase Realtime Database. They are looking into solving this issue with their library, even though the issue is 2 years old.

    0 讨论(0)
提交回复
热议问题