[GSI_LOGGER]: The value of 'callback' is not a function. Configuration ignored
Asked Answered
M

8

13

I'm migrating from Google Sign-in platform to the newer Google Identity Services library.

App.svelte:

<svelte:head>
    <script src="https://accounts.google.com/gsi/client" async defer></script>
</svelte:head>

<div id="g_id_onload"
     data-client_id="x.apps.googleusercontent.com"
     data-callback="handleCredentialResponse">
</div>
<div class="g_id_signin"
     data-type="standard"
     data-size="large"
     data-theme="outline"
     data-text="sign_in_with"
     data-shape="rectangular"
     data-logo_alignment="left">
</div>
<script>
    function decodeJwtResponse(token) {
        let base64Url = token.split('.')[1]
        let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
        let jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        }).join(''));
        return JSON.parse(jsonPayload)
    }

    let responsePayload;
    function handleCredentialResponse(response) {
        // decodeJwtResponse() is a custom function defined by you
        // to decode the credential response.
        responsePayload = decodeJwtResponse(response.credential);

        console.log("ID: " + responsePayload.sub);
        console.log('Full Name: ' + responsePayload.name);
        console.log('Given Name: ' + responsePayload.given_name);
        console.log('Family Name: ' + responsePayload.family_name);
        console.log("Image URL: " + responsePayload.picture);
        console.log("Email: " + responsePayload.email);
    }
</script>

Reloading the page and I saw this error in the console:

[GSI_LOGGER]: The value of 'callback' is not a function. Configuration ignored.

What is the problem?

Mat answered 31/3, 2022 at 3:18 Comment(1)
Hi, I don't know about svelte, but can't you move the app-loading script tag (<script src="https://accounts.google.com/gsi/client" async defer></script>) out of the svelte tag? If I modify your code that way, I can run this successfully.Digitalize
W
14

After many hours searching, I found this answer: Call Svelte component's function from global scope

Simply, handleCredentialResponse needs to be global (window) scope.

JavaScript

window.handleCredentialResponse = (response) => {
  // decodeJwtResponse() is a custom function defined by you
  // to decode the credential response.
  responsePayload = decodeJwtResponse(response.credential);

  console.log("ID: " + responsePayload.sub);
  console.log('Full Name: ' + responsePayload.name);
  console.log('Given Name: ' + responsePayload.given_name);
  console.log('Family Name: ' + responsePayload.family_name);
  console.log("Image URL: " + responsePayload.picture);
  console.log("Email: " + responsePayload.email);
}

TypeScript

You need to modify Window interface so that TS won't raise error: How do you explicitly set a new property on `window` in TypeScript?

// global.d.ts

export {};

declare global {
  interface Window {
    handleCredentialResponse: (response: any) => void;
  }
}
Westsouthwest answered 28/4, 2022 at 20:40 Comment(1)
Amazing Answer...! This works well to get the user information from the new GSI implementation.Meninges
M
3

Looks like we have to render the Sign In With Google button by using Javascript.

<svelte:head>
    <script src="https://accounts.google.com/gsi/client" async defer on:load={googleLoaded}></script>
</svelte:head>

<script>
    import { onMount } from 'svelte';
    let googleReady = false;
    let mounted = false;

    onMount(() => {
        mounted = true;
        if (googleReady) {
            displaySignInButton()
        }
    });

    function googleLoaded() {
        googleReady = true;
        if (mounted) {
            displaySignInButton()
        }
    }

    function displaySignInButton() {
        google.accounts.id.initialize({
            client_id: "x.apps.googleusercontent.com",
            callback: handleCredentialResponse
        });
        google.accounts.id.renderButton(
            document.getElementById("buttonDiv"),
            { theme: "outline", size: "large" }  // customization attributes
        );
        google.accounts.id.prompt(); // also display the One Tap dialog
    }

Mat answered 31/3, 2022 at 9:44 Comment(2)
10 points for using on:load and preventing a race conditionHula
...although it doesn't seem to work on client side navigation. Might be better off adding the script tag via js in that scenarioHula
G
1

As already mentioned, handleCredentialResponse needs to be global.

Using window.handleCredentialResponse won't cut it in svelte.

Using globalThis.handleCredentialResponse however will work.

So instead of const handleCredentialResponse = async (response) => {}

DO THIS: globalThis.handleCredentialResponse = async (response) => {}

Gainor answered 19/5, 2022 at 7:44 Comment(0)
S
1

Like others mentioned, the issue here is that the callback function needs to be globally accessible. Hence why we need to attach it to window.

However, if you're using SvelteKit with SSR, the window object doesn't exist until the client side renders and you'll get an internal server error. The solution to this is to attach the function after the component has mounted. However again, I was still getting your error. It seems that the google script was running before my onMount statement.

So, I had to do the following, basically initializing the two buttons using Google's scripts after mount.

<script>
    import { createClient } from '@supabase/supabase-js';
    import { onMount } from 'svelte';

    onMount(() => {
        // Define the handleSignInWithGoogle function
        // @ts-ignore
        window.handleSignInWithGoogle = async function (response) {     
        };

        // Load the Google Sign-In library
        const script = document.createElement('script');
        script.src = 'https://accounts.google.com/gsi/client';
        script.async = true;
        script.onload = function () {
            // Initialize the Google Sign-In library
            // @ts-ignore
            window.google.accounts.id.initialize({
                client_id: 'blablabla.apps.googleusercontent.com',
                // @ts-ignore
                callback: window.handleSignInWithGoogle,
                context: 'signin',
                ux_mode: 'popup',
                nonce: '',
                auto_select: true,
                itp_support: true
            });
            // @ts-ignore
            window.google.accounts.id.renderButton(document.getElementById('g_id_onload'), {
                type: 'standard',
                shape: 'pill',
                theme: 'outline',
                text: 'signin_with',
                size: 'large',
                logo_alignment: 'left'
            });
        };
        document.body.appendChild(script);
    });
</script>

<div id="g_id_onload" />

Sherri answered 28/6, 2023 at 0:1 Comment(0)
C
0

I fixed it by: globalThis.handleToken

Caffeine answered 10/5, 2022 at 10:37 Comment(1)
Hi @Caffeine How would you write handleCredentialResponse or handleToken in typescript and NOT in java script? I am getting error in console '[GSI_LOGGER]: The value of 'callback' is not a function. Configuration ignored.'Kidding
S
0

For those who are looking for a short way if they are trying to implement login with google in angular.

I've used event broadcast and listen method to make it simpler.

from the google callback function

function handleCredentialResponse(response) {
      const lgwtggl = new CustomEvent("lgwtggl", {
        detail: response
      });
      document.dispatchEvent(lgwtggl);
    }

and from inside any angular component listen like below

// listen on event google login response
    document.addEventListener('lgwtggl', (e: any) => {
      if (e && e.detail && e.type === 'lgwtggl') {
    // call component function
        this.loginWithGoogle(e.detail);
      }
    });

and the controller function goes like below

public loginWithGoogle(payload: any) {
// your logic
    console.log(payload);
  }
Stasny answered 27/6, 2023 at 8:50 Comment(0)
L
0

Working version for Angular 15 either call it in ngOnInit or inside constructor. Here is the typescript version

 ngOnInit() {
       //For button login
       (globalThis as any).handleCredentialResponse = (response: any) => {
        // Handle the response here
        console.log('Received credential response:', response);
        //api call or jwt decode or any other logics will go here

      };
  }
Lorri answered 20/8, 2023 at 10:4 Comment(0)
T
0

I learned that this error appear whenever you put the js code on another file but when you move the code inside a script tag within the html then error will be fix. (base on my experience after 2 days of looking for solution lol)

Timisoara answered 17/4 at 15:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.