I have a mobile site which lists jobs, the user applies and uploads their CV (resume) - I want them to be able to choose a file from their Google Drive.
I've created the Hello world example here - https://developers.google.com/picker/docs/ (code reproduced here for convenience)
Problem is that if not already logged into Drive, a popup to login is launched. This is bad enough on a desktop but really bad on a phone.
I have tried this solution, but get 'TypeError: gapi.auth is undefined'
I also tried launching the picker from an onclick event rather than the onload as described by the docs.
function launchDrive()
{
gapi.load('auth', {'callback': onAuthApiLoad});
gapi.load('picker', {'callback': onPickerApiLoad});
}
<input type='button' value='Launch Drive' onclick='launchDrive();'>
Sample Google code:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Google Picker Example</title>
<script type="text/javascript">
var developerKey = 'xxxxxxxYYYYYYYY-12345678';
var clientId = "1234567890-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com"
var scope = ['https://www.googleapis.com/auth/photos'];
var pickerApiLoaded = false;
var oauthToken;
function onApiLoad() {
gapi.load('auth', {'callback': onAuthApiLoad});
gapi.load('picker', {'callback': onPickerApiLoad});
}
function onAuthApiLoad() {
window.gapi.auth.authorize(
{
'client_id': clientId,
'scope': scope,
'immediate': false
},
handleAuthResult);
}
function onPickerApiLoad() {
pickerApiLoaded = true;
createPicker();
}
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
oauthToken = authResult.access_token;
createPicker();
}
}
// Create and render a Picker object for picking user Photos.
function createPicker() {
if (pickerApiLoaded && oauthToken) {
var picker = new google.picker.PickerBuilder().
addView(google.picker.ViewId.PHOTOS).
setOAuthToken(oauthToken).
setDeveloperKey(developerKey).
setCallback(pickerCallback).
build();
picker.setVisible(true);
}
}
// A simple callback implementation.
function pickerCallback(data) {
var url = 'nothing';
if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
url = doc[google.picker.Document.URL];
}
var message = 'You picked: ' + url;
document.getElementById('result').innerHTML = message;
}
</script>
</head>
<body>
<div id="result"></div>
<!-- The Google API Loader script. -->
<script type="text/javascript" src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>
13 May 2015 edit
Further to Jason's answer, here is what I also tried (called by a button oncllick):
function launchDrive()
{
//gapi.load('auth', {'callback': onAuthApiLoad});
gapi.auth.init(onAuthApiLoad);
gapi.load('picker', {'callback': onPickerApiLoad});
}
To solve your issue you need to understand how google performs oauth in your case:
- gapi performs init actions
- gapi opens a google auth page in new popup window and you perform login in it
- after login succeeded gapi gets notified and you receive your token
Why browser blocks the popup in 2nd step:
- original event is not present anymore in window (
window.event
is destroyed).
- User manually blocked the popup from current site
So if user didn't block a popup and you popup is still blocked, gapi actions look something like:
<input type="button" onclick="auth" value="click"/>
function auth() {
setTimeout(function() {
// by this time window.event is destroyed, that's why browser blocks the popup
window.open(document.URL, '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
}, 100)
}
So what you should do:
- Make sure that after button click you don't perform any asynchronous actions like XHRequests or other
- Make sure gapi is inited and ready by the time users click on the button, so by the time gapi needs to create a popup,
window.event
won't be null. So move all gapi init methods to DOMContentLoaded
.
- As another option you could use server-side oauth flow, which means instead of popup user will be redirected in current tab to gauth page.
You will want to call gapi.auth.init. See the docs here: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauthinit
Initializes the authorization feature. Call this when the client loads to prevent popup blockers from blocking the auth window on gapi.auth.authorize calls.
I have it working now.
In the example for the picker, https://developers.google.com/picker/docs/, it calls:
<script type="text/javascript" src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
In this example, https://developers.google.com/api-client-library/javascript/start/start-js, it calls:
<script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
Using client.js fixes the 'TypeError: gapi.auth is undefined' issue, and thus the login popup works.
Maybe api.js is an older version of the API?
Just Skip to the bottom
Here's the code that works for me currently. This is my first hour using this API though, so I really don't know what any of these functions do yet, nor do I know what the proper order and error handling is just yet, but at least this is functional now. Maybe it'll help someone else in the future.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Google Picker Example</title>
</head>
<body style="width: 70%; margin: 100px auto;">
<!-- Added a button to open picker -->
<button onclick="loadPicker();" >Open from GoogleDrive</button>
<div id="result"></div>
<!-- Moved to end of body tag instead of head -->
<script type="text/javascript">
// The Browser API key obtained from the Google API Console.
// Replace with your own Browser API key, or your own key.
var developerKey = '<IDK WHAT'S SUPPOSED TO GO HERE, BUT ITS OK>';
// The Client ID obtained from the Google API Console. Replace with your own Client ID.
var clientId = "<YOUR CLIENT ID GOES HERE>.apps.googleusercontent.com"
// Replace with your own project number from console.developers.google.com.
// See "Project number" under "IAM & Admin" > "Settings"
var appId = "<YOUR APP ID GOES HERE>";
// Scope to use to access user's Drive items.
var scope = ['https://www.googleapis.com/auth/drive'];
var pickerApiLoaded = false;
var oauthToken;
// Use the Google API Loader script to load the google.picker script.
function loadPicker() {
// This needs to be client:auth2 no client
gapi.load('client:auth2', {'callback': onAuthApiLoad});
gapi.load('picker', {'callback': onPickerApiLoad});
}
function onAuthApiLoad() {
// we need to init gapi.client with the clientId and scope first
gapi.client.init({
clientId: clientId,
scope: scope
});
// Now we can authorize? seems like the same thing here
window.gapi.auth.authorize(
{
'client_id': clientId,
'scope': scope,
'immediate': false
},
handleAuthResult);
}
function onPickerApiLoad() {
pickerApiLoaded = true;
createPicker();
}
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
oauthToken = authResult.access_token;
createPicker();
}
}
// Create and render a Picker object for searching images.
function createPicker() {
// Wow this is a mess
if (pickerApiLoaded && oauthToken) {
var view = new google.picker.View(google.picker.ViewId.DOCS);
view.setMimeTypes("image/png,image/jpeg,image/jpg");
var picker = new google.picker.PickerBuilder()
.enableFeature(google.picker.Feature.NAV_HIDDEN)
.enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
.setAppId(appId)
.setOAuthToken(oauthToken)
.addView(view)
.addView(new google.picker.DocsUploadView())
// Guess this is... optional?
//.setDeveloperKey(developerKey)
.setCallback(pickerCallback)
.build();
picker.setVisible(true);
}
}
// A simple callback implementation.
function pickerCallback(data) {
if (data.action == google.picker.Action.PICKED) {
var fileId = data.docs[0].id;
alert('Selected fileId: ' + fileId);
}
}
</script>
<!-- The Google API Loader script. Removed the autorun -->
<script type="text/javascript" src="https://apis.google.com/js/api.js"></script>
</body>
</html>
Edit: If you get a pop-up window that doesn't load, just close it and click the button again. That fixed another issue I just had.
Again, I don't know what I'm doing yet, so hopefully I can get a better understanding of this and clarify things later.
E2: Ah, there's more information about OAuth2 over on the Javascript GAPI documentation page which can be found here: https://developers.google.com/api-client-library/javascript/features/authentication
From another document, it appear that gapi.load('client', callback)
will load auth2 if not already loaded. Calling gapi.load('client:auth2', callback)
will just save 1 network request.
Note: when you authorize your application using Oauth 2.0, you do not also need to set the API key as in the first example. However, it is a good practice to do so, in case your code ever expands to handle unauthorized requests.
That explains why I could remove the API/developer key.
Edit 3: Ok the above code is technically wrong.
Warning: do not use this method alongside the recommended gapi.auth2.init and signIn flow. These are two distinct behaviors (Authorization for gapi.auth2.authorize vs Authentication for gapi.auth2.init/signIn) and will have unexpected issues if used within the same application.
autorize is for single use authentications (if you were logged into 2 google accounts for instance). While using gapi.init()
is meant to be for a more long term session (like for logging in and out of a website).
How this is working currently, I do not know.
Don't use the above code, just wanted to document the progress. Here's a better demo working with getAuthResponse()
<html>
<head></head>
<body>
<div style="padding: 50px">
<h2 style="color: #2196f3;">Status: <span id='status'></span></h2>
<button id="signin-button" onclick="handleSignInClick()">Sign In</button>
<button id="signout-button" onclick="handleSignOutClick()">Sign Out</button>
<button id="signout-button" onclick="openFile()">Open File</button>
</div>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script type="text/javascript">
var cid = '<CLIENTID_HERE>';
var scope = 'https://www.googleapis.com/auth/drive';
var authenticated = false;
var pickerLoaded = false;
var auth = null;
var user = null;
var response = null;
var token = null;
var stat = $('#status');
function openFile() {
gapi.load('client:auth2', initClient);
gapi.load('picker', onPickerLoad);
}
function initClient() {
stat.html("starting");
gapi.client.init({
clientId: cid,
scope: scope
}).then(
function () {
console.log("init");
// Check if we are logged in.
auth = gapi.auth2.getAuthInstance();
auth.isSignedIn.listen(onStatusChange);
authenticated = auth.isSignedIn.get();
stat.html(authenticated);
if (authenticated) {
stat.html("Logged In!");
user = auth.currentUser.get();
response = user.getAuthResponse(true);
token = response.access_token;
showPicker();
} else {
stat.html("Logged Out!");
}
}, function(){stat.html("error")});
}
function onStatusChange(isSignedIn) {
if (isSignedIn) {
stat.html("Logged In!");
authenticated = true;
user = auth.currentUser.get();
response = user.getAuthResponse(true);
token = response.access_token;
showPicker();
showPicker();
} else {
authenticated = false;
stat.html("Logged Out!");
}
}
function handleSignInClick(event) {
gapi.auth2.getAuthInstance().signIn();
}
function handleSignOutClick(event) {
gapi.auth2.getAuthInstance().signOut();
alert("signed out");
}
function onPickerLoad() {
pickerLoaded = true;
showPicker();
}
function showPicker() {
if (pickerLoaded && authenticated) {
var view = new google.picker.View(google.picker.ViewId.DOCS);
var picker = new google.picker.PickerBuilder();
picker.addView(view);
picker.enableFeature(google.picker.Feature.MULTISELECT_ENABLED);
picker.setOAuthToken(token);
picker.setAppId()
picker.setCallback(onDriveFileOpen);
picker = picker.build();
picker.setVisible(true);
}
}
function onDriveFileOpen(data) {
console.log(data);
if (data.action == google.picker.Action.PICKED) {
var fileId = data.docs[0].id;
console.log(fileId);
alert(data.docs[0].name);
}
}
</script>
<script async defer src="https://apis.google.com/js/api.js">
</script>
</body>
</html>