10

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?

quanta
  • 3,960
  • 4
  • 40
  • 75
  • 1
    Hi, I don't know about svelte, but can't you move the app-loading script tag (``) out of the svelte tag? If I modify your code that way, I can run this successfully. – Iamblichus Mar 31 '22 at 08:34

7 Answers7

11

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;
  }
}
iamphduc
  • 163
  • 7
1

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
    }

quanta
  • 3,960
  • 4
  • 40
  • 75
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) => {}

0

I fixed it by: globalThis.handleToken

minor
  • 1
  • 1
    Hi @minor 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.' – Ash Oct 11 '22 at 10:02
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);
  }
The Mechanic
  • 2,301
  • 1
  • 26
  • 37
0

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

Chen W
  • 119
  • 2
  • 12
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

      };
  }
Rabby Hasan
  • 356
  • 4
  • 10