I've recently been reading up on the Gmail API, and i want to implement it onto my web application, howeverf I am struggling to get the source code to work on VueJS.
<!DOCTYPE html>
<html>
<head>
<title>Gmail API Quickstart</title>
<meta charset="utf-8" />
</head>
<body>
<p>Gmail API Quickstart</p>
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize_button" style="display: none;">Authorize</button>
<button id="signout_button" style="display: none;">Sign Out</button>
<pre id="content" style="white-space: pre-wrap;"></pre>
<script type="text/javascript">
// Client ID and API key from the Developer Console
var CLIENT_ID = '<YOUR_CLIENT_ID>';
var API_KEY = '<YOUR_API_KEY>';
// Array of API discovery doc URLs for APIs used by the quickstart
var DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest"];
// Authorization scopes required by the API; multiple scopes can be
// included, separated by spaces.
var SCOPES = 'https://www.googleapis.com/auth/gmail.readonly';
var authorizeButton = document.getElementById('authorize_button');
var signoutButton = document.getElementById('signout_button');
/**
* On load, called to load the auth2 library and API client library.
*/
function handleClientLoad() {
gapi.load('client:auth2', initClient);
}
/**
* Initializes the API client library and sets up sign-in state
* listeners.
*/
function initClient() {
gapi.client.init({
apiKey: API_KEY,
clientId: CLIENT_ID,
discoveryDocs: DISCOVERY_DOCS,
scope: SCOPES
}).then(function () {
// Listen for sign-in state changes.
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
// Handle the initial sign-in state.
updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
authorizeButton.onclick = handleAuthClick;
signoutButton.onclick = handleSignoutClick;
}, function(error) {
appendPre(JSON.stringify(error, null, 2));
});
}
/**
* Called when the signed in status changes, to update the UI
* appropriately. After a sign-in, the API is called.
*/
function updateSigninStatus(isSignedIn) {
if (isSignedIn) {
authorizeButton.style.display = 'none';
signoutButton.style.display = 'block';
listLabels();
} else {
authorizeButton.style.display = 'block';
signoutButton.style.display = 'none';
}
}
/**
* Sign in the user upon button click.
*/
function handleAuthClick(event) {
gapi.auth2.getAuthInstance().signIn();
}
/**
* Sign out the user upon button click.
*/
function handleSignoutClick(event) {
gapi.auth2.getAuthInstance().signOut();
}
/**
* Append a pre element to the body containing the given message
* as its text node. Used to display the results of the API call.
*
* #param {string} message Text to be placed in pre element.
*/
function appendPre(message) {
var pre = document.getElementById('content');
var textContent = document.createTextNode(message + '\n');
pre.appendChild(textContent);
}
/**
* Print all Labels in the authorized user's inbox. If no labels
* are found an appropriate message is printed.
*/
function listLabels() {
gapi.client.gmail.users.labels.list({
'userId': 'me'
}).then(function(response) {
var labels = response.result.labels;
appendPre('Labels:');
if (labels && labels.length > 0) {
for (i = 0; i < labels.length; i++) {
var label = labels[i];
appendPre(label.name)
}
} else {
appendPre('No Labels found.');
}
});
}
</script>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
</body>
</html>
I would like to convert this code into VueJS or atleast get it to work on my vue application. Would anyone be able to help me convert this?
There's nothing stopping you from setting the script tag programmatically in the mounted() hook, with document.createElement(), setAttribute(), document.appendChild() methods. Then wrap your gapi functions inside a try catch statement or use a promise, inspired by this tutorial (google maps): https://markus.oberlehner.net/blog/using-the-google-maps-api-with-vue/
Related
I have been reading different posts on Google and SO, but I just can't figure out why this does not work.
There are 2 HTML and 2 JS files involved in this case (explanation given below in words after the code chunks).
1) index.html
<div id="center">
<img id="logo" src="../img/logowshadow.png" alt="logo">
<p id="para">Get your google slides!</p>
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize-button" style="display: none;">Sign in</button>
<button id="signout-button" style="display: none;">Sign Out</button>
</div>
<script src="../js/homePage.js"></script>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
2) homePage.js
var apiKey = 'AIzaSyCSjg3rrx6Obl4ngZsDlFlV4degUJSMvbw';
var discoveryDocs = ["https://slides.googleapis.com/$discovery/rest?version=v1"];
var clientId = '408869653199-ruoft30vmoqrgpku3us3qd2leb3k6tp1.apps.googleusercontent.com';
var scopes = 'https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/drive';
var authorizeButton = document.getElementById('authorize-button');
var signoutButton = document.getElementById('signout-button');
var user;
var authResponse;
var oauthToken;
var pickerApiLoaded = false;
var chosenPresentation = null;
function handleClientLoad() {
// Load the API client and auth2 library
gapi.load('client:auth2', initClient);
//Load the Picker API
gapi.load('picker', onPickerApiLoad);
}
function initClient() {
gapi.auth2.init({
apiKey: apiKey,
discoveryDocs: discoveryDocs,
clientId: clientId,
scope: scopes
}).then(function () {
// Listen for sign-in state changes.
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
// Set the current Google User
gapi.auth2.getAuthInstance().currentUser.listen(updateUser);
// Handle the initial sign-in state.
updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
authorizeButton.onclick = handleAuthClick;
signoutButton.onclick = handleSignoutClick;
});
}
// Callback to make sure that the Picker API has loaded
function onPickerApiLoad() {
pickerApiLoaded = true;
createPicker();
}
// Store the current Google user
function updateUser(gUser) {
user = gUser;
updateToken();
}
// Store the access token
function updateToken() {
authResponse = user.getAuthResponse(true);
oauthToken = authResponse.access_token;
}
function updateSigninStatus(isSignedIn) {
if (isSignedIn) {
authorizeButton.style.display = 'none';
signoutButton.style.display = 'block';
createPicker();
} else {
authorizeButton.style.display = 'block';
signoutButton.style.display = 'none';
}
}
function handleAuthClick(event) {
gapi.auth2.getAuthInstance().signIn();
}
function handleSignoutClick(event) {
gapi.auth2.getAuthInstance().signOut();
}
// Create and render a Picker object for picking user slides
function createPicker() {
if (pickerApiLoaded && oauthToken) {
var picker = new google.picker.PickerBuilder().
addView(google.picker.ViewId.PRESENTATIONS).
setOAuthToken(oauthToken).
setDeveloperKey(apiKey).
setCallback(pickerCallback).
build();
picker.setVisible(true);
}
}
// 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].replace('edit', 'present');
var item_name = doc[google.picker.Document.NAME];
alert('You picked ' + item_name);
//export the chosen presentation for use in mobileControl.js
chosenPresentation = doc[google.picker.Document.ID];
alert("chosen: " + chosenPresentation);
exports.chosenPresentation = chosenPresentation;
window.location.replace(url);
}
}
3) mobile.html
<div id="instructions">
<p>Swipe <b>left</b> to go to the previous slide.</p>
<p>Swipe <b>right</b> to go to the next slide.</p>
</div>
<script src="../js/mobileControl.js"></script>
and 4) mobileControl.js
alert("Loaded the JavaScript!"); //Shows up
var m = require('./homePage.js');
alert("imported"); //Does not show up
alert(m.chosenPresentation); //Does not show up
To put in words what my code is trying to achieve: users will be able to sign in to their google accounts, and select a set of Presentation found on their Google drive. I want to pass the Presentation ID on from homePage.js to mobileControl.js, and I attempted to do so using Node's exports. I suspect that it is not working because both scripts are run 'at the same time' (index.html is meant to run on the computer, while mobile.html runs on a mobile device...concurrently). But I am not sure if I am right in identifying the cause, and if so, is there a way to export the variable from within the function after it has been defined? Perhaps I should detect it when the slide has been selected, and only load mobilePage.js after everything in homePage.js has ran?
My apologies for the long read, but my previous attempt at diluting the example obviously failed miserably so...
Update: user #vsenko is totally right in saying that I have mixed up client-side programming and server-side programming, so do read up more on this if you are facing the same issue as me
As far as I can see, you are trying to load .js files with NodeJS specific API (RequireJS API to precise) directly on the web page. This is not going to work because browsers do not implement it natively. To utilize this API you will have to use a preprocessor (Webpack, Browserify or something else).
Other apparent problem with your approach is that you assume that it is possible to transfer data between different devices using something that look like NodeJS modules. But it is not possible, you will have transfer data over the network between your devices directly or though your server.
I want to call back the thumbnail URL of selected images (multiple selected image) from Google Picker using Google Picker API (javascript). But the result only first selected image (1 image only). Anyone can help me to fix this problem?
Screenshot:
Below is my javascript API:
<!-- START PICKER -->
<button type="button" id="pick">Pick File</button>
<pre id="fileInfo"></pre>
<script>
(function() {
/**
* Initialise a Google Driver file picker
*/
var FilePicker = window.FilePicker = function(options) {
// Config
this.apiKey = options.apiKey;
this.clientId = options.clientId;
// Elements
this.buttonEl = options.buttonEl;
// Events
this.onSelect = options.onSelect;
this.buttonEl.addEventListener('click', this.open.bind(this));
// Disable the button until the API loads, as it won't work properly until then.
this.buttonEl.disabled = true;
// Load the drive API
gapi.client.setApiKey(this.apiKey);
gapi.client.load('drive', 'v2', this._driveApiLoaded.bind(this));
google.load('picker', '1', { callback: this._pickerApiLoaded.bind(this) });
}
FilePicker.prototype = {
/**
* Open the file picker.
*/
open: function() {
// Check if the user has already authenticated
var token = gapi.auth.getToken();
if (token) {
this._showPicker();
} else {
// The user has not yet authenticated with Google
// We need to do the authentication before displaying the Drive picker.
this._doAuth(false, function() { this._showPicker(); }.bind(this));
}
},
/**
* Show the file picker once authentication has been done.
* #private
*/
_showPicker: function() {
var accessToken = gapi.auth.getToken().access_token;
var view = new google.picker.DocsView();
view.setIncludeFolders(true);
this.picker = new google.picker.PickerBuilder()
.enableFeature(google.picker.Feature.NAV_HIDDEN)
.enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
.addView(google.picker.ViewId.DOCS_IMAGES)
.setAppId(this.clientId)
.setDeveloperKey(this.apiKey)
.setOAuthToken(accessToken)
.setCallback(this._pickerCallback.bind(this))
.build()
.setVisible(true);
},
/**
* Called when a file has been selected in the Google Drive file picker.
* #private
*/
_pickerCallback: function(data) {
if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
var file = data[google.picker.Response.DOCUMENTS][0],
id = file[google.picker.Document.ID],
request = gapi.client.drive.files.get({
fileId: id
});
request.execute(this._fileGetCallback.bind(this));
}
},
/**
* Called when file details have been retrieved from Google Drive.
* #private
*/
_fileGetCallback: function(file) {
if (this.onSelect) {
this.onSelect(file);
}
},
/**
* Called when the Google Drive file picker API has finished loading.
* #private
*/
_pickerApiLoaded: function() {
this.buttonEl.disabled = false;
},
/**
* Called when the Google Drive API has finished loading.
* #private
*/
_driveApiLoaded: function() {
this._doAuth(true);
},
/**
* Authenticate with Google Drive via the Google JavaScript API.
* #private
*/
_doAuth: function(immediate, callback) {
gapi.auth.authorize({
client_id: this.clientId,
scope: 'https://www.googleapis.com/auth/drive.readonly',
immediate: immediate
}, callback);
}
};
}());
</script>
<script>
function initPicker() {
var picker = new FilePicker({
apiKey: 'MY_API_KEY',
clientId: 'MY_CLIENT_ID-0bsroe3tqbfatoiie3h3qvaqtv4q0f5c.apps.googleusercontent.com',
buttonEl: document.getElementById('pick'),
onSelect: function(file) {
console.log(file);
document.getElementById('fileInfo').innerHTML = file.thumbnailLink;
}
});
}
</script>
<script src="https://www.google.com/jsapi?key=MY_API_KEY"></script>
<script src="https://apis.google.com/js/client.js?onload=initPicker"></script>
<!-- END PICKER -->
I see this line in your _pickerCallback method:
var file = data[google.picker.Response.DOCUMENTS][0]
Looks like copied from the Google example. Here, you always use only the first image of all selected ones.
Remove the [0] and it should work.
I hope I correctly understood your concern that you were able to select multiple images but returned result is only one. If it is, please try using Document.THUMBNAILS.
With this, an array of Thumbnails which describe the attributes of a photo or video will be in the Response.DOCUMENTS field in the callback data.
Important Note: Thumbnails will not be returned if the picked items belong to Google Drive.
Hope that helps!
Agreed with #crymis's answer. But he didn't provide the full solution.
Here is the code of pickerCallbak function:
_pickerCallback: function(data) {
if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
// get all selected files
var files = data[google.picker.Response.DOCUMENTS];
// loop over selected files
for (var i = 0; i < files.length; i++) {
// get file id, and request to get the file
var id = files[i][google.picker.Document.ID],
request = gapi.client.drive.files.get({
fileId: id
});
// execute request for file
request.execute(this._fileGetCallback.bind(this));
}
}
},
Note: to allow multiple selection in google drive picker dialog, you need to enable that feature while building picker with the following method,
.enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
(Kamarul Anuar, which you already did, so don't worry!)
I need to extract the first three upcoming events, in order of date, from Google Calendar to work with a new app I'm creating (a Pebble watchface).
I've used the JavaScript Quickstart at Google Developers to create an html page to extract the information I need. The code is below:-
<html>
<head>
<script type="text/javascript">
// Your Client ID can be retrieved from your project in the Google
// Developer Console, https://console.developers.google.com
var CLIENT_ID = 'CLIENT_ID_HERE';
// This quickstart only requires read-only scope, check
// https://developers.google.com/google-apps/calendar/auth if you want to
// request write scope.
var SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'];
/**
* Check if current user has authorized this application.
*/
function checkAuth() {
gapi.auth.authorize(
{
'client_id': CLIENT_ID,
'scope': SCOPES,
'immediate': true
}, handleAuthResult);
}
/**
* Handle response from authorization server.
*
* #param {Object} authResult Authorization result.
*/
function handleAuthResult(authResult) {
var authorizeDiv = document.getElementById('authorize-div');
if (authResult && !authResult.error) {
// Hide auth UI, then load Calendar client library.
authorizeDiv.style.display = 'none';
loadCalendarApi();
} else {
// Show auth UI, allowing the user to initiate authorization by
// clicking authorize button.
authorizeDiv.style.display = 'inline';
}
}
/**
* Initiate auth flow in response to user clicking authorize button.
*
* #param {Event} event Button click event.
*/
function handleAuthClick(event) {
gapi.auth.authorize(
{client_id: CLIENT_ID, scope: SCOPES, immediate: false},
handleAuthResult);
return false;
}
/**
* Load Google Calendar client library. List upcoming events
* once client library is loaded.
*/
function loadCalendarApi() {
gapi.client.load('calendar', 'v3', listCalendars);
}
/**
* List all calendars and call listUpcomingEvents on each
* calendarID.
*/
function listCalendars()
{
var request = gapi.client.calendar.calendarList.list();
request.execute(function(resp) {
var calendars = resp.items;
appendPre('Check Console');
for (i=0;i<calendars.length;i++)
{
listUpcomingEvents(calendars[i]['id']);
}
});
}
/**
* Print the summary and start datetime/date of the next ten events in
* the authorized user's calendar. If no events are found an
* appropriate message is printed.
*/
function listUpcomingEvents(calendarID)
{
var calendarId = calendarID;
var request_events = gapi.client.calendar.events.list({
'calendarId': calendarId,
'timeMin': (new Date()).toISOString(),
'singleEvents': true,
'showDeleted': false,
'maxResults': 3,
'orderBy': 'startTime'
});
request_events.execute(function(resp) {
var events = resp.items;
for (i = 0; i < events.length; i++)
{
var event = events[i];
var when = event.start.dateTime;
if (!when) {
when = event.start.date;
}
appendPre(event.summary + ' (' + when + ')');
}
})
}
/**
* Append a pre element to the body containing the given message
* as its text node.
*
* #param {string} message Text to be placed in pre element.
*/
function appendPre(message) {
var pre = document.getElementById('output');
var textContent = document.createTextNode(message + '\n');
pre.appendChild(textContent);
}
</script>
<script src="https://apis.google.com/js/client.js?onload=checkAuth">
</script>
</head>
<body>
<div id="authorize-div" style="display: none">
<span>Authorize access to calendar</span>
<!--Button for the user to click to initiate auth sequence -->
<button id="authorize-button" onclick="handleAuthClick(event)">
Authorize
</button>
</div>
<pre id="output"></pre>
</body>
</html>
It works to a certain extent; however, if you have access to more than one calendar there is a problem. Instead of returning only three events taken from any calendar depending on the date, it returns three from EACH calendar so if you have 5 calendars you get 15 results, all grouped by calendar and not sorted by date.
Therefore, I would be grateful if anyone could help with the following:-
How can I fix the code so that I only get the NEXT three upcoming events, no matter which calendar it came from?
Also, how can I get the output to display in the standard format so that each element is recognisable when I call it, such as "event":( "summary": "Party", "start": "date" : 2015-11-30) I'm not sure how it goes.
Thanks very much in advance.
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>
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
});
});
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.
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
});
}
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();
}
}
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>
Sign out
</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>
This worked for me:
https://stackoverflow.com/a/55314602/1034622
Include this script tag
<script src="https://apis.google.com/js/platform.js"></script>
Cheap Fix is:
To tell Eslint that this is a global variable by using
/* global gapi */
I am a newbie as far as web development is concerned and even more so with Google App Scripts and OAuth2.0. Having said that, I have researched enough and also tried several tricks, but still can't get past this issue.
I borrowed sample from here:
Google Developers - Client API Library
Then created an Apps Script project with an index.html file with code from that page. I also created a project on the developer console, created a client ID, API key and turned on the required API support. I also made the required changes to the sample to reflect the new client ID and API key.
The index.html page is served from HTML Service with SandBox Mode set to IFRAME. If I load the URL in a browser window (say using incognito mode) and click "Authorize" button, it opens the Google sign-in window. But after signing in, it opens two new tabs with messages
Please close this window
and the original browser window shows no change.
The JavaScript console shows error messages like these:
Unsafe JavaScript attempt to initiate navigation for frame with URL ''
from frame with URL
https://accounts.google.com/o/oauth2/postmessageRelay?parent=https%3A%2F%2F…6lxdpyio6iqy-script.googleusercontent.com#rpctoken=288384029&forcesecure=1.
The frame attempting navigation is sandboxed, and is therefore
disallowed from navigating its ancestors.
From the messages, it seems its an effect of using IFRAME and some sort of security feature is preventing the callback being delivered to the original window. If I reload the original window, things work OK. But that's not what I would ideally like.
How do I work around this issue? Its a very simple project and I can provide source code if that helps.
Thanks,
Pavan
Edit: Here is the sample code I'm trying. You would need to have your client ID and API Key and also set JS origins in the Google Console for things to work:
Code.gs
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('index').setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
index.html
<!--
Copyright (c) 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
To run this sample, replace YOUR API KEY with your application's API key.
It can be found at https://code.google.com/apis/console/?api=plus under API Access.
Activate the Google+ service at https://code.google.com/apis/console/ under Services
-->
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
</head>
<body>
<!--Add a button for the user to click to initiate auth sequence -->
<button id="authorize-button" style="visibility: hidden">Authorize</button>
<script type="text/javascript">
// Enter a client ID for a web application from the Google Developer Console.
// The provided clientId will only work if the sample is run directly from
// https://google-api-javascript-client.googlecode.com/hg/samples/authSample.html
// In your Developer Console project, add a JavaScript origin that corresponds to the domain
// where you will be running the script.
var clientId = 'YOUR_CLIENT_ID';
// Enter the API key from the Google Develoepr Console - to handle any unauthenticated
// requests in the code.
// The provided key works for this sample only when run from
// https://google-api-javascript-client.googlecode.com/hg/samples/authSample.html
// To use in your own application, replace this API key with your own.
var apiKey = 'YOUR API KEY';
// To enter one or more authentication scopes, refer to the documentation for the API.
var scopes = 'https://www.googleapis.com/auth/plus.me';
// Use a button to handle authentication the first time.
function handleClientLoad() {
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true, response_type: 'token'}, handleAuthResult);
}
function handleAuthResult(authResult) {
var authorizeButton = document.getElementById('authorize-button');
if (authResult && !authResult.error) {
authorizeButton.style.visibility = 'hidden';
makeApiCall();
} else {
authorizeButton.style.visibility = '';
authorizeButton.onclick = handleAuthClick;
}
}
function handleAuthClick(event) {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false, response_type: 'token'}, handleAuthResult);
return false;
}
// Load the API and make an API call. Display the results on the screen.
function makeApiCall() {
gapi.client.load('plus', 'v1', function() {
var request = gapi.client.plus.people.get({
'userId': 'me'
});
request.execute(function(resp) {
var heading = document.createElement('h4');
var image = document.createElement('img');
image.src = resp.image.url;
heading.appendChild(image);
heading.appendChild(document.createTextNode(resp.displayName));
heading.appendChild(document.createTextNode(resp.emails[0].value));
document.getElementById('content').appendChild(heading);
});
});
}
</script>
<script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
<div id="content"></div>
<p>Retrieves your profile name using the Google Plus API.</p>
</body>
</html>
found a solution... not nice but works oO:
the trick is to remove the oauth2relay iframes before the auth window is closed. after the window is closed you have to add the frames again and do a immediate request, if that works the user authorized the app.
be careful:
this script does not check if the user meanwhile is logged out or the token is expired, as long as the webapp window is open the same token is used.
Code.js:
function doGet(e) {
return HtmlService.createTemplateFromFile('Index').evaluate().setTitle(formSettings.title).setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function include(file) {
return HtmlService.createHtmlOutputFromFile(file).getContent();
}
function doPost(meta) {
if (!meta || !meta.auth) {
throw new Error('not authorized');
return;
}
var auth = JSON.parse(UrlFetchApp.fetch('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + meta.auth.access_token, { muteHttpExceptions: true }).getContentText());
if (auth.error || !auth.email) {
throw new Error('not authorized');
return;
}
if (typeof this[meta.method + '_'] == 'function') {
return this[meta.method + '_'](auth.email, meta.data);
}
throw new Error('unknown method');
}
function test_(email, data) {
return email;
}
Index.html:
<html>
<head>
<?!= include('JavaScript'); ?>
</head>
<body>
<div class="content-wrapper">
</div>
</body>
</html>
Javascript.html:
<script type='text/javascript' src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type='text/javascript' src="//apis.google.com/js/client.js?onload=apiLoaded" async></script>
<script type='text/javascript'>
var clientId = '*************-********************************.apps.googleusercontent.com';
var scopes = ['https://www.googleapis.com/auth/plus.me', 'https://www.googleapis.com/auth/userinfo.email'];
var loaded = false;
var auth = null;
function apiLoaded() {
loaded = true;
login();
}
window._open = window.open;
window._windows = [];
window.open = function(url) {
var w = window._open.apply(window,arguments);
window._windows.push(w);
return w;
}
function login(step) {
step || (step = 0);
if (!loaded) {
return;
}
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: (step <= 0 || step >= 2) }, function(authResult) {
if (authResult) {
if (authResult.error) {
if (authResult.error == 'immediate_failed' && authResult.error_subtype == 'access_denied' && step <= 0) {
var interval = setInterval(function() {
var $ifr = $('iframe');//[id^=oauth2relay]');
if (!window._windows.length) {
clearInterval(interval);
return;
}
if ($ifr.length) {
clearInterval(interval);
$ifr.detach();
var w = window._windows.pop();
if (w) {
var interval2 = setInterval(function() {
if (w.closed) {
clearInterval(interval2);
$('body').append($ifr);
login(2);
}
});
} else {
$('body').append($ifr);
}
}
},500);
login(1);
} else if (authResult.error == 'immediate_failed' && authResult.error_subtype == 'access_denied' && step >= 2) {
//user canceled auth
} else {
//error
}
} else {
auth = authResult;
doPost('test', { some: 'data' }, 'test');
}
} else {
//error
}
});
}
function test() {
console.log(arguments);
}
//with this method you can do a post request to webapp server
function doPost(method, data, callbackName) {
data || (data = {});
google.script.run.withSuccessHandler(onSuccess).withFailureHandler(onError).withUserObject({ callback: callbackName }).doPost({ method: method, data: data, auth: auth });
}
function onSuccess(data, meta) {
if (typeof window[meta.callback] == 'function') {
window[meta.callback](null, data);
}
}
function onError(err, meta) {
if (typeof window[meta.callback] == 'function') {
window[meta.callback](err);
}
}
</script>