50

I'm trying to implement Google Sign In and retrieve the profile information of the user. The error is: Uncaught ReferenceError: gapi is not defined. Why is that?

<!doctype html>
<html>
<head>      
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://apis.google.com/js/platform.js" async defer></script>
    <script type="text/javascript">
    $(function(){
        gapi.auth2.init({
            client_id: 'filler_text_for_client_id.apps.googleusercontent.com'
        });
    });
</head>
<body>
</body>
</html>

7 Answers 7

57

It happens because you have async and defer attributes on your script tag. gapi would be loaded after your script tag with gapi.auth2.init...

To wait for gapi before executing this code you can use onload query param in script tag, like following:

<script src="https://apis.google.com/js/platform.js?onload=onLoadCallback" async defer></script>
<script>
window.onLoadCallback = function(){
  gapi.auth2.init({
      client_id: 'filler_text_for_client_id.apps.googleusercontent.com'
    });
}
</script>

Or for case when you need it in many places, you can use promises to better structure it:

// promise that would be resolved when gapi would be loaded
var gapiPromise = (function(){
  var deferred = $.Deferred();
  window.onLoadCallback = function(){
    deferred.resolve(gapi);
  };
  return deferred.promise()
}());

var authInited = gapiPromise.then(function(){
  gapi.auth2.init({
      client_id: 'filler_text_for_client_id.apps.googleusercontent.com'
    });
})


$('#btn').click(function(){
  gapiPromise.then(function(){
    // will be executed after gapi is loaded
  });

  authInited.then(function(){
    // will be executed after gapi is loaded, and gapi.auth2.init was called
  });
});
Sign up to request clarification or add additional context in comments.

8 Comments

where should I add the onSignOut function then? It also requires gapi, but I only call it when the user clicks on the button
you can add it to onLoadCallback too, e.g place $(function(){ .... }) there..
That was the first thing I tried. Sorry I didn't mention it before. Trying to call signOut results in a "Uncaught ReferenceError: signOut is not defined"
OH wait, nevermind. you're talking about the document.ready function lol. wait a second, let me try it.
added example how to structure this with promises
|
19

I think you'll find with the above example that this won't work either, as gapi.auth2 will not yet be defined (I know this because I made the same mistake myself, today) You first need to call gapi.load('auth2', callback) and pass THAT a callback which then calls gapi.auth2.init. Here is an example of my _onGoogleLoad function, which is the callback for loading the first platform.js script.

var _auth2

var _onGoogleLoad = function () {
  gapi.load('auth2', function () {
    _auth2 = gapi.auth2.init({
      client_id: 'OUR_REAL_ID_GOES_HERE',
      scope: 'email',
      fetch_basic_profile: false
    })
    _enableGoogleButton()
  })
}

After that, you can use the _auth2 variable to actually sign users in.

3 Comments

OR you include a <div class="g-signin2" data-onsuccess="onSignIn"></div> (etc..) button as described in the docs. When present, platform.js will automatically download auth2. This seems to trigger the exact same content being downloaded. The documentation is not very good in this regard, unfortunately. Also, the <script> in index.html can be https://apis.google.com/js/api.js instead of platform.js in your case, which contains a little less code.
@phil294 is there a way to suppress that behaviour ?
@beppe9000 I dont know, sorry
18

The problem is not only in gapi. To call init method - auth2 object must be initialized. There is a promise once a google auth object is in fully initialized GoogleAuth.then(onInit, onFailure)

gapi.load('auth2', initSigninV2);

function initSigninV2() {
    gapi.auth2.init({
        client_id: 'CLIENT_ID.apps.googleusercontent.com'
    }).then(function (authInstance) {
        // now auth2 is fully initialized
    });
}

3 Comments

All i needed was this
This should be the accepted answer! If you're using Vue.js and you're using a separate component for logging out, this works!
You saved my day! This is the right answer. Thanks. Long Live!
6

While these answers helped me, I believe there is better answer in official docs.

See Integrating Google Sign-In using listeners

var auth2; // The Sign-In object.
var googleUser; // The current user.

/**
 * Calls startAuth after Sign in V2 finishes setting up.
 */
var appStart = function() {
  gapi.load('auth2', initSigninV2);
};

/**
 * Initializes Signin v2 and sets up listeners.
 */
var initSigninV2 = function() {
  auth2 = gapi.auth2.init({
      client_id: 'CLIENT_ID.apps.googleusercontent.com',
      scope: 'profile'
  });

  // Listen for sign-in state changes.
  auth2.isSignedIn.listen(signinChanged);

  // Listen for changes to current user.
  auth2.currentUser.listen(userChanged);

  // Sign in the user if they are currently signed in.
  if (auth2.isSignedIn.get() == true) {
    auth2.signIn();
  }

  // Start with the current live values.
  refreshValues();
};

/**
 * Listener method for sign-out live value.
 *
 * @param {boolean} val the updated signed out state.
 */
var signinChanged = function (val) {
  console.log('Signin state changed to ', val);
  document.getElementById('signed-in-cell').innerText = val;
};

/**
 * Listener method for when the user changes.
 *
 * @param {GoogleUser} user the updated user.
 */
var userChanged = function (user) {
  console.log('User now: ', user);
  googleUser = user;
  updateGoogleUser();
  document.getElementById('curr-user-cell').innerText =
    JSON.stringify(user, undefined, 2);
};

/**
 * Updates the properties in the Google User table using the current user.
 */
var updateGoogleUser = function () {
  if (googleUser) {
    document.getElementById('user-id').innerText = googleUser.getId();
    document.getElementById('user-scopes').innerText =
      googleUser.getGrantedScopes();
    document.getElementById('auth-response').innerText =
      JSON.stringify(googleUser.getAuthResponse(), undefined, 2);
  } else {
    document.getElementById('user-id').innerText = '--';
    document.getElementById('user-scopes').innerText = '--';
    document.getElementById('auth-response').innerText = '--';
  }
};

/**
 * Retrieves the current user and signed in states from the GoogleAuth
 * object.
 */
var refreshValues = function() {
  if (auth2){
    console.log('Refreshing values...');

    googleUser = auth2.currentUser.get();

    document.getElementById('curr-user-cell').innerText =
      JSON.stringify(googleUser, undefined, 2);
    document.getElementById('signed-in-cell').innerText =
      auth2.isSignedIn.get();

    updateGoogleUser();
  }
}

Comments

5

For a Vue.JS app

  • Add an onload hook to the script tag for platform.js
  • Set up a function that dispatches an event
  • Catch that event in the mounted hook of your component.

Here's a fairly complete example you can just paste into a new vue-cli project.

Don't forget to supply your own client ID!

public/index.html

    <script type="text/javascript">
      function triggerGoogleLoaded() {
        window.dispatchEvent(new Event("google-loaded"));
      }
    </script>
    <script
      src="https://apis.google.com/js/platform.js?onload=triggerGoogleLoaded"
      async
      defer
    ></script>
    <meta
      name="google-signin-client_id"
      content="xxxxxxxxxxxx.apps.googleusercontent.com"
    />

App.vue

<template>
  <div id="app">
    <div id="nav">
      <div id="google-signin-btn"></div>
      <a href="#" class="sign-out" @click="signOut" v-if="profile">Sign out</a>
    </div>
    <div v-if="profile" class="">
      <h2>Signed In User Profile</h2>
      <pre>{{ profile }}</pre>
    </div>
    <div v-if="!profile">
      <h2>Signed out.</h2>
    </div>
    <router-view />
  </div>
</template>

<script>
export default {
  components: {},
  data() {
    return {
      profile: false
    };
  },
  methods: {
    onSignIn(user) {
      const profile = user.getBasicProfile();
      this.profile = profile;
    },
    signOut() {
      var auth2 = gapi.auth2.getAuthInstance();
      auth2.signOut().then(() => {
        location.reload(true);
      });
    },
    renderGoogleLoginButton() {
      gapi.signin2.render("google-signin-btn", {
        onsuccess: this.onSignIn
      });
    }
  },
  mounted() {
    window.addEventListener("google-loaded", this.renderGoogleLoginButton);
  }
};
</script>


Comments

1

This worked for me: https://stackoverflow.com/a/55314602/1034622

Include this script tag

<script src="https://apis.google.com/js/platform.js"></script>

1 Comment

This has the minified version: <script src='https://apis.google.com/js/api.js'></script>
-4

Cheap Fix is: To tell Eslint that this is a global variable by using /* global gapi */

1 Comment

This question doesn't have anything to do with eslint, and the error isn't coming from eslint; it's a JavaScript runtime error.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.