google / google-api-javascript-client

Google APIs Client Library for browser JavaScript, aka gapi.
Apache License 2.0
3.24k stars 1.07k forks source link

Cannot read property 'style' of null #281

Open MaklaCof opened 7 years ago

MaklaCof commented 7 years ago

First, I already try to find solution on SO, but I think this is a bug.

I am using Angular2 application. (For backend I use ASP.NET core 1.1.) I also create SignIn button following official tutorial.

On successfully log in I re-route user to home page and then I get error. I get the same error if I logout from google account (in chrome).

The error is:

vendor.js:81139 Uncaught TypeError: Cannot read property 'style' of null at G_ (cb=gapi.loaded1:91) at H. (cb=gapi.loaded_1:94) at Function. (cb=gapi.loaded_0:155) at run (vendor.js:99289) at runIfPresent (vendor.js:99318) at onGlobalMessage (vendor.js:99358) at ZoneDelegate.invokeTask (vendor.js:81259) at Zone.runTask (vendor.js:81135) at ZoneTask.invoke (vendor.js:81329)

and line with error is: window.document.getElementById((c?"not_signed_in":"connected")+a.El).style.display="none"; Of course getElementById returns null, because I am no longer on Login page, and Angular remove html from DOM. Same happens if I log out from Google account. The user could be anywhere.

I read somewhere on SO, where tip for same error was

don't delete Google SignIn element, just set diplay to none

, but this is not possible here, since Angular2 remove element from DOM.

I think you should check first if element exists and then set style to whatever.

TMSCH commented 7 years ago

Hi @MaklaCof, Thanks for the detailed reporting. It's an error that you can safely ignore, however I understand it is not the ideal behavior. The widget is not meant to be removed from the page after a sign in or sign out, but many Single Page Apps will do that.

An alternative I would propose is to use this: https://developers.google.com/identity/sign-in/web/build-button#building_a_button_with_a_custom_graphic so you can control more precisely the lifecycle of the button. It will also help you automatically sign in the user if they were previously signed in:

auth2 = gapi.auth2.init({...}).then(auth => {
  if (auth.isSignedIn.get()) {
    // User already signed in, no need for the button, directly route to the "signed-in" state.
  }
});

Does that help?

MaklaCof commented 7 years ago

I will check it, but I can not ignore it. On successful login I do some work (some request are made to server, meni are reordered, some are hidden some are showed), but error in JavaScript stop executing this things.

MaklaCof commented 7 years ago

Unfortunately it is not working. It raise the same error. This is my updated code:

    ngAfterViewInit()
    {
        gapi.load("auth2", () =>
        {
            const auth = gapi.auth2.init({
                client_id: "**********"
            });

            if (auth.isSignedIn.get())
            {
                this.onGoogleLoginSuccess(auth.currentUser.get());
            }
            else
            {
                auth.attachClickHandler(this.el.nativeElement, {}, (user) =>
                {
                    this.onGoogleLoginSuccess(user);
                },
                    (error) =>
                    {
                        this.onGoogleLoginError(error);
                    });
            }

            /*gapi.signin2.render(
                this.googleLoginButtonId,
                {
                    onsuccess: (user) => { this.onGoogleLoginSuccess(user); },
                    onfailure: (error) => { this.onGoogleLoginError(error); },
                    scope: this.scopes.join(" ")
                });*/
        });
    }

    onGoogleLoginSuccess(user: gapi.auth2.GoogleUser)
    {
        this.isGoogleAccountVisible = true;
        const profile = user.getBasicProfile();
        this.googleAccount = profile.getEmail();
        const auth = user.getAuthResponse(true);
        this.authService.loginWithGoogle(new GoogleLoginModel(auth.access_token, profile.getName()))
            .subscribe((profile) => { this.getData(); });
    }

    onGoogleLoginError(error: string)
    {
        this.error = { code: eResponseErrorCodes.Error, message: error };
    }

    loginClick()
    {
        this.isGoogleAccountVisible = true;
        this.authService.login(new LoginUser(this.loginForm.value.email, this.loginForm.value.password))
            .subscribe((profile) =>
            {
                this.getData();
            },
            (err) =>
            {
                this.isGoogleAccountVisible = false;
                this.error = err;
            });
    }

    private async getData()
    {
        delete this.error;
        await this.usersService.readAndSave();
        this.isGoogleAccountVisible = false;
        delete this.googleAccount;
        this.router.navigate([""]);
    }

If I return to original problem:

The widget is not meant to be removed from the page after a sign in or sign out, but many Single Page Apps will do that.

I think you should really fix this. It is a little funny that Google (Angular2) is not working with himself (SignIn). It is not that hard to change this code: window.document.getElementById((c?"not_signed_in":"connected")+a.El).style.display="none"; to this:

var t = window.document.getElementById((c?"not_signed_in":"connected")+a.El);
if (t) t.style.display="none";
TMSCH commented 7 years ago

Hi @MaklaCof, Thanks for trying to implement what I recommended.

Are you saying this is still raising the same Uncaught TypeError: Cannot read property 'style' of null? I'm surprised as the method used now should not depend at all the same library gapi.signin2. Have you removed the HTML for the previous button? If there is still a button with the same class g-signin2 it will initialize as before.

I also see that we could avoid this error raised very easily, however, that does not fix the real issue here, being that the button/library state is not properly cleaned up by removing the button from the DOM. A more programmatic approach should be used within Angular framework instead of using the button widget.

Could you please make sure that gapi.signin2 is not initialized somehow in your code? On my end I will look at how properly fix the issue within the library itself. Thanks!

MaklaCof commented 7 years ago

Hi. Thank you. I tried again and it works. I must have forget something in previous try. Not sure what.

TMSCH commented 7 years ago

Glad it worked!

JordiOrriols commented 7 years ago

I'm getting the same error on ReactJs.

The first time the page is loaded everything is Ok. If I change from one page to another, the element is removed from the Dom, and when I go to the page with the button, gapi.signin2.render is called again.

But it seems that the events from the first call still alive, and that events show the error of "null". If I repeat the process, the events still there, but this time twice.

It feels that what we're missing is a function to clean all events when the DOM is removed.

TMSCH commented 7 years ago

Hi @JordiOrriols, as I mentioned earlier, gapi,signin2 is unfortunately not designed to be used in a single page application, hence the issue you observe. For now, you can build a custom button which will help you control the lifecycle more precisely: https://developers.google.com/identity/sign-in/web/build-button#building_a_button_with_a_custom_graphic.

We could potentially implement a dispose() method that would dispose properly of all the listeners, method that you would call when the button is removed from DOM. Would that help?

JordiOrriols commented 7 years ago

This would help for sure. It would be great to have this method on the future! Thank you!

tom10271 commented 7 years ago

If you search gapi Cannot read property 'style' of null in Google search, you will see how much pain we are suffering. Yes we can implement it ourself to fix the issue but it would be much better if Google can make it works with SPA

TMSCH commented 7 years ago

@tom10271 I apologize for the inconvenience. We are going to roll out a quick temporary fix while we work on a longer term solution for Single Page Application. I'll update this thread when it's deployed.

TMSCH commented 7 years ago

@tom10271 @JordiOrriols The error should not be raised anymore now. Please let me know if that works for you!

lapat commented 7 years ago

Any update on this? This bug make the google sign in button disappear completely.

TMSCH commented 7 years ago

@lapat what do you mean? In this bug's context, the framework used already remove the button from the DOM.

lapat commented 7 years ago

@TMSCH maybe I'm experiencing a different bug? Google sign in button will randomly not show up on my site with this error:

b=gapi.loaded2:20 Uncaught TypeError: Cannot read property 'style' of undefined    at QG (https://apis.google.com//scs/apps-static/_/js/k=oz.gapi.en.8ggGBlnRYmw.O/m=signin2/exm=auth2,client/rt=j/sv=1/d=1/ed=1/am=AQ/rs=AGLTcCORPt1ErXTGopWn2el2oYFxRcgjyw/cb=gapi.loaded2:20:434)    at H.S (https://apis.google.com/_/scs/apps-static/_/js/k=oz.gapi.en.8ggGBlnRYmw.O/m=signin2/exm=auth2,client/rt=j/sv=1/d=1/ed=1/am=AQ/rs=AGLTcCORPt1ErXTGopWn2el2oYFxRcgjyw/cb=gapi.loaded_2:22:72)    at tW (https://apis.google.com/_/scs/apps-static/_/js/k=oz.gapi.en.8ggGBlnRYmw.O/m=signin2/exm=auth2,client/rt=j/sv=1/d=1/ed=1/am=AQ/rs=AGLTcCORPt1ErXTGopWn2el2oYFxRcgjyw/cb=gapi.loaded_2:23:32)    at https://apis.google.com/js/platform.js?onload=onLoadCallback:50:615    at https://apis.google.com/js/platform.js?onload=onLoadCallback:20:348    at hb (https://apis.google.com/js/platform.js?onload=onLoadCallback:12:467)    at b (https://apis.google.com/js/platform.js?onload=onLoadCallback:20:331)    at G.(anonymous function).G.(anonymous function) (https://apis.google.com/js/platform.js?onload=onLoadCallback:20:421)    at c (https://apis.google.com/js/platform.js?onload=onLoadCallback:20:388)    at https://apis.google.com/js/platform.js?onload=onLoadCallback:20:352

TMSCH commented 7 years ago

Hmm this error should not happen anymore. Are you caching api.js or platform.js?

lapat commented 7 years ago

@TMSCH, should I be caching api.js or platform.js? If so, how do I that? Thanks man! This bug is killing me.

TMSCH commented 7 years ago

You should not, no. I am just surprised that you're getting this error. I tried removing the button from DOM and then signing out, which should update its state, and it doesn't raise the error.

Could you explain in a bit more details what you mean by "Google sign in button will randomly not show up on my site with this error:"? How does that happen? Could you provide a minimal snippet of code that reproduces the issue?

lapat commented 7 years ago

@TMSCH thanks for your help man!

The code is copy and pasted from here: Google sign in

I use it on my site: coinflash

It appears that the error appears sometimes when a user signs off my site and then tries to sign back in or a user is directed to a page in my site and the 'sign-off' method is called even though the user is not signed in. Although, even these steps will not reproduce the error consistently.

TMSCH commented 7 years ago

@lapat did this error happen recently? i.e. after I pushed the fix 10 days ago?

I am not able to reproduce it in your app.

lapat commented 7 years ago

@TMSCH it's very hard to reproduce. I saw it happened yesterday on my friends Chrome on a Mac. Maybe some browsers were caching the script? I just added a random string to the end of the URL now to force it to always get latest JS file from google server: <script src="https://apis.google.com/js/api:client.js?dev=" + Math.floor(Math.random() * 1000)>

TMSCH commented 7 years ago

You shouldn't have to add the random string, this endpoint doesn't set any cache headers.

If I don't have a way to reproduce it, I can't debug :(

lapat commented 7 years ago

@TMSCH okay let me try to reproduce and I'll hit you back up. Thanks for being responsive and cool!

TMSCH commented 7 years ago

You're welcome :)

lapat commented 7 years ago

Hi @TMSCH so this bug is still showing up for my users. I know you can't reproduce it but what would you recommend would be the way to fix it? I don't think building a custom google sign in button would do it because it uses the same JS files, right? Building a custom Google Sign-In button

Or maybe build my own HTML button that acts just like a Google sign in button?

MaklaCof commented 7 years ago

I did just that:

    <div *ngIf="!loginInProgress">
        <div *ngIf="googleAuth" class="button2" (click)="googleLoginHandler()">
        <span>
            <img src="https://google-developers.appspot.com/identity/sign-in/g-normal.png" />
        </span>
        Prijava z Google računom
        </div>
    </div>

and code:

    public googleAuth: gapi.auth2.GoogleAuth;

    ngAfterViewInit()
    {
        this.initGapi();
    }

    private initGapi()
    {
        if ((<any>window).gapi)   //just if(gapi) is not enough. I still get error: gapi is not defined. If this will not fix error, another options is to remove async deffer when loading platform.js library.
        {
            gapi.load("auth2", () =>
            {
                this.zone.run(() =>
                {
                    this.googleAuth = gapi.auth2.init({
                        client_id: OpPISTransferedData.googleClientId
                    });

                    if (this.googleAuth.isSignedIn.get())
                    {
                        this.onGoogleLoginSuccess(this.googleAuth.currentUser.get());
                    }
                    else
                    {
                    }
                });
            });
        }
        else
            window.setTimeout(() => { this.initGapi(); }, 100);
    }

    public googleLoginHandler()
    {
        this.googleAuth.signIn()
            .then(user => this.onGoogleLoginSuccess(user), error => this.onGoogleLoginError(error));
    }

Hope it helps someone. There is just one problem. I am using Chrome, and reopen old tabs (settings). When I turn of my computer, and turn it on again and start Chrome, all my tabs are reopen, but sign in does not work (until I refresh the page). IT writes "window was closed by user".

lapat commented 7 years ago

@MaklaCof this is great, too bad about the 'window was closed by user' thing. Did you have a disappearing button before you replaced your code with this?

MaklaCof commented 7 years ago

Yes I need to prepare scenario and report a bug, but didn't find the time to do it. I am not sure what you mean with disappearing button. I was having a problem with error in console, which is why I started the thread.

TMSCH commented 7 years ago

@MaklaCof thanks for providing a snippet to address the bug mentioned by @lapat. What do you mean by "IT write window was closed by user"? This is not an error coming from gapi.auth2 (popup_closed_by_user is).

@lapat, the option you propose (building your custom button) would work as it doesn't use gapi.signin2 which has the issue. Any chance you could provide a link to reproduce the issue?

deshazer72 commented 6 years ago

so this just happened to me and my button has dissappeared I am using this html <div align="center" class="g-signin2" data-onsuccess="onSignIn" data-theme="dark">

then I have a javascript function onSignIn this was working until today.

gusarov commented 4 years ago

Problem is still active, but it is not as easy as @MaklaCof suggests. In my case problem appears during navigation through tabs. And apparently element ID is not there after ngAfterViewInit indeed. So, I just added waiting for the button to appear, like this:

    var btn = document.getElementById(btnId);
    if (!btn) {
        console.log('sso', 'wait for button started');
        var waitForRes;
        var waitFor = new Promise((res, rej) => {
            waitForRes = res;
        });
        this.ngZone.runOutsideAngular(()=>{
            var timer = setInterval(() => {
                btn = document.getElementById(btnId);
                console.log('sso', 'wait for button...', btn);
                if (btn != null) {
                    clearInterval(timer);
                    this.ngZone.run(() => {
                        waitForRes();
                    });
                }
            }, 100);
        });
        await waitFor;
        console.log('sso', 'button is there!', btn);
    }

    console.log('sso', 'renderGoogle Loaded');
    gapi.signin2.render(btnId, { ...

So, there is nothing bad with gapi I believe... except that it was advised to use ngAfterViewInit for render call. But during SPA navigation with a router, switching components back and forth the target element ID that we supposed to path to Render call is actually not ready.

Collins1738 commented 3 years ago

Anyone have any idea how to implement something similar in React? There's a similar issue, and what I understand is that it is happening because React removes a certain element from DOM? but that element needs to stay in DOM? My problem is more concerned with the recaptcha in firebase Auth with Phone.

JohnnyFun commented 3 years ago

If it's any help, I see this error periodically. Here's an example stack trace:

TypeError: Cannot read property 'style' of null
    at Array.<anonymous> (https://www.gstatic.com/recaptcha/releases/lz3oilnU_FqeO0ydN3CXU9RT/recaptcha__en.js:225:269)
    at UK.<anonymous> (https://www.gstatic.com/recaptcha/releases/lz3oilnU_FqeO0ydN3CXU9RT/recaptcha__en.js:83:305)

Haven't been able to reproduce locally yet, but I'll update if I do.