How to obtain initial OAuth2.0 code from browser? - html

I have made a javascript script in Google Apps Script, attached to a google sheet. I ultimately want to connect the Google Sheet to the Google Fit API and have my fitness data automatically inputted to the Google Sheet. At step 0, I made my Google Console project and OAuth2.0 client ID & secret. I am at step 1, where I need to obtain the authentication code from the initial 'GET' request.
My request is correct and I can send the request correctly using the callback notation; the code below is run in a callback in a timed for loop such that the html object (html_objet) remains active for a certain amount of time, waiting to get the code. I can see the code in the browser url when I have finished approving with the Google popup, but I do not know how to import this code value from the client browser(popup) into my javascript program. I open the client browser(popup) using :
var html_texte = '<html><head><script>'
+ 'const winRef = window.open("'+js2html_data+'");'
+ 'winRef ? google.script.host.close() : window.alert("Allow popup to redirect to url");'
+ 'window.onload=function(){document.getElementById("'+js2html_data+'").href ="'+js2html_data+'";};'
+ '</script></head><body>'
+ '</body></html>';
var html_objet = HtmlService.createHtmlOutput(html_texte).setWidth(90).setHeight(1);
html_objet.js2html_data = js2html_data;
SpreadsheetApp.getUi().showModalDialog(html_objet, "Opening ...");
This works. But, I can not do anything with the window once it is open (ie: reload the window, get the current url).
I tried modifying the html_texte variable to the text below, such that I can return the url of the authenticated page. It does not work, how can I update the js2html_data variable such that it shows the url of the final Google user authenticated page? Or, pass the url to another html variable (html2js_data) out to my javascript program?
var html_texte = '<html><head><script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>'
+ '<script>'
+ 'const winRef = window.open("'+js2html_data+'");'
+ 'winRef ? google.script.host.close() : window.alert("Allow popup to redirect to url");'
+ 'window.onload=function(){document.getElementById("'+js2html_data+'").href ="'+js2html_data+'";};'
// + '$(document).ready(function getUrl(){ document.getElementById("'+html2js_data+'").innerHTML=window.location.href; });'
+ '$(document).ready(function getUrl(){ document.getElementById("'+html2js_data+'").href=window.location.href; });'
//+ '$(document).ready(function getUrl(){ document.getElementById(<?="'+html2js_data+'"?>).href=window.location.href; });'
+ '</script></head><body>'
// + '<h3 id="html2js_data" onclick="getUrl()">OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO</h3>'
+ '<div id="html2js_data"></div>'
+ '</body></html>';
Any suggestions would be great...I tried a lot of things...

Related

Deployment ID in URL changes when app script runs causing the script to fail, how can I make the URL use the correct Deployment ID?

I'm using a script to create an annual leave request workflow that begins with a google form, collects responses in a sheet, then emails a manager for approval, then emails the requester with either an Approved/Denied confirmation email. The script runs and sends the email to the manager, but once either the approve or deny button is pressed, the URL generated uses a deployment ID that does not match the version of either the current /exec
or the /dev version.
Here is the code snippet for the section that should run when the buttons are used:
function SheetHandler(sheet) {
var _sheet = sheet;
var _data = getRowsData(_sheet);
var _markPending = function(d) {
d.state = PENDING_STATE;
d.identifier = Utils.generateUUID();
manager_email = d[SETTINGS.MANAGERS_EMAIL_COLUMN_NAME].match(EMAIL_REGEX);
var scriptUri = ScriptApp.getService().getUrl();
// hack some values on to the data just for email templates.
d.approval_url = scriptUri + "?i=" + d.identifier + '&state=' + APPROVED_STATE;
d.deny_url = scriptUri + "?i=" + d.identifier + '&state=' + DENIED_STATE;
d.manager_email = manager_email
message = Utils.processTemplate(SETTINGS.PENDING_MANAGER_EMAIL, d);
subject = Utils.processTemplate(SETTINGS.PENDING_MANAGER_EMAIL_SUBJECT, d);
MailApp.sendEmail(manager_email,subject,"",{ htmlBody: message });
setRowData(_sheet, d);
}
Is there another way to get the correct deployment url for this?
I've tested it by copying the Head and Version deployment ID into the URL generated by the approve/deny buttons in the emails, if I copy the correct ID from the Deploy menu into the URL in place of the incorrect one then the remaining code executes as expected.
The link looks like this:
https://script.google.com/a/macros//s//exec?i=6b843ea2-7b2b-454a-ff2e-89bc9a54d48e&state=APPROVED

Error "302 Moved Temporarily" in "Test as add-on" mode (Telegram)

The spreadsheet contains project 1, deployed as a webapp with permissions: Execute as: Me, Who has access: Anyone.
Webapp
function doPost(e) {
myLog('Received from Addon: ' + JSON.stringify(e));
// console.log('parameters from caller ' + JSON.stringify(e));
return ContentService.createTextOutput(JSON.stringify(e));
}
A webhook aTelegram-bot and this webapp is set.
I am using this spreadsheet for testing (as add-on) of another project 2.
Add-on
function sendPost() {
var sheetURL = SpreadsheetApp.getActiveSpreadsheet().getUrl();
// var webAppUrl = "https://script.google.com/macros/s/#####/exec"; // 7: Part_1 - WebApp: My
var webAppUrl = "https://script.google.com/macros/s/###/exec"; // 7: Part_1 - WebApp: Tester
// var auth = ScriptApp.getOAuthToken();
// var header = { 'Authorization': 'Bearer ' + auth };
var payload = { scriptName: 'updateData', sheetURL: 'sheetURL' };
var options = {
method: 'post',
// headers: header,
muteHttpExceptions: true,
payload: payload
};
var resp = UrlFetchApp.fetch(webAppUrl, options);
var respCode = resp.getResponseCode();
console.log('resp: ' + respCode);
myLog(respCode);
var respTxt = resp.getContentText();
myLog('Response from webApp: ' + respTxt);
console.log('resp: ' + respTxt);
}
Here is a short video of the process (EN-subtitles).
I run sendPost() and everything works fine. Project 2 sends data to the webapp, which returns it. Since this is a Container-bound script and not a standalone one, I cannot watch the logs in the GCC logger. Therefore, I look at them in the custom logger and the entries are added normally.
Also https://api.telegram.org/bot{API_token}/getWebhookInfo shows that there are no errors:
{"ok":true,"result": {"url":"https://script.google.com/macros/s/###/exec", "has_custom_certificate":false, "pending_update_count":0, "max_connections":40,"ip_address":"142.250.***.***"}}
Now I am sending a message from the chat with the bot. The doPost(e) function in the webapp accepts it and writes it to the spreadsheet.
However, everything is not limited to one message. Requests from the bot come and come, and the logger creates more and more new rows in the spreadsheet. This happens until I redeploy the webapp with the doPost () function commented out. I tried to figure out if this is a limited loop or not. My patience was only enough for 20 such iterations, because as a result, the messages start repeating at intervals of about 1 minute. Then I have to reinstall the webhook.
In any case, it interferes with testing the addon.
GetWebhookInfo is now showing that there is a "Wrong response from the webhook: 302 Moved Temporarily" error:
{"ok":true,"result": {"url":"https://script.google.com/macros/s/###/exec", "has_custom_certificate":false, "pending_update_count":1, "last_error_date":1635501472, "last_error_message":"Wrong response from the webhook: 302 Moved Temporarily", "max_connections":40,"ip_address":"142.250.1***.***"}}
Googling revealed several possible reasons. From url to the script has changed to MITM in your network.
I do not really believe in MITM and I suppose that this is due to the fact that the spreadsheet is open in testing mode as add-on and the URL of the webapp has changed in this mode. If so, then I'm not sure if this is the correct behavior of the testing system. In theory, such a situation should have been provided for and the webap url should remain unchanged. But maybe I'm wrong and the reason is different, so
QUESTION:
Has anyone come across such a situation and will suggest a workaround on how to test a script as an addon in such conditions?
http-status-code-302 refers to redirection. If ContentService is used, Google temporarily redirects the resource to a another domain to serve the content. This redirection is not performed when using HtmlService. So, if the issue is related to redirection, use HtmlService instead.

Google app script runs in debug mode, not in run mode

Hello Google Script experts, I'm new to google script and trying something related to HTTP POST from google docs.
I've a google app script that sends a post request (API) on opening a google doc.
function onOpen() {
var ui = DocumentApp.getUi(); // Same variations.
var actDoc = DocumentApp.getActiveDocument();
var docID = actDoc.getId();
var repeater = 0;
var data = {
'bstask': text,
'docid': docID
};
console.log(data);
var options = {
'method' : 'post',
'contentType': 'application/json',
// Convert the JavaScript object to a JSON string.
'payload' : JSON.stringify(data)
};
ui.alert("Sending request with the payload" + data.bstask + " and " + data.docid);
var response = UrlFetchApp.fetch('https://test.com/path', options);
ui.alert("Response is: " + response.getResponseCode());
if (response.getResponseCode() == 200) {
Logger.log(response.getContentText() + response.getAllHeaders());
ui.alert('Yey!! Document refreshed.');
}
else{
ui.alert('Opps!! Document refresh failed.'+ response.getContent());
}
}
This script is used to provide an option to the user to update the document when it is opened or refreshed. This script runs fine and invoking the API when I test it in debug mode. But, when I want this script to be executed on opening the document, it is not invoking the API and the rest of the UI prompts are working fine. Am I missing something here or something wrong with the script? I really appreciate any help!!
Simple Triggers – Restrictions
Because simple triggers fire automatically, without asking the user for authorization, they are subject to several restrictions:
They cannot access services that require authorization. For example, a simple trigger cannot send an email because the Gmail service requires authorization, but a simple trigger can translate a phrase with the Language service, which is anonymous.
In other words, the UrlFetchApp.fetch() call will not execute when opening the document because it requires authorization. You should be able to see this failure in the execution logs as well.

How to have One URL which sends to one-of-three Google Forms URLs?

I have a need to get equal populations in each of three surveys. The three surveys are identical except for one change - it contains different pictures.
I would like to distribute a single URL to my survey respondents.
I would like to count the number of previous responses I have, and add one.
I would like to redirect the session to one of three (Google Forms) URLs based upon the calculation
(Responses.Count + 1) MOD 3.
I think I need a Google Apps script to do this?
Here is some pseudocode:
var form0 = FormApp.openByUrl(
'htttps://docs.google.com/forms/d/e/2342f23f1mg/viewform'
);
var form1 = FormApp.openByUrl(
'htttps://docs.google.com/forms/d/e/23422333g/viewform'
);
var form2 = FormApp.openByUrl(
'htttps://docs.google.com/forms/d/e/2342wfeijqeovig/viewform'
);
var form0Responses = form0.getResponses();
var form1Responses = form1.getResponses();
var form2Responses = form2.getResponses();
var whichURL = (
form0Responses.length +
form1Responses.length +
form2Responses.length + 1
) % 3; // modulo three
// var goToForm = switch ( whichURL ) blah blah;
// redirect to goToForm;
// How do I redirect now?
Thanks!
Maybe there's a simpler solution possible but I don't think I know of it :)
The common link that you give out could be a link to a "proxy" page that doesn't contain anything but just redirects users to the correct page. Or it could be a link to the actual page with a necessary form embedded. Let's look at the options.
0) Publish your code as web app
In either case you'll need to have your code published as a web app. In GAS it's way simpler than it sounds, you'll find all the info here: https://developers.google.com/apps-script/guides/web
Make sure you set that Anyone, even anonymous can access the app and it always runs as you (if you choose User accessing the app, they'll have to go through authentication process which is not what you need here).
1) Redirect via GAS web app.
Your code in this case would look like so:
// I changed your function a bit so that it could be easier to work with
// in case the number of your forms changes later on
function getForm() {
var forms = [
{
// I'm using openById instead of openByUrl 'cause I've run into issues with
// the latter
form: FormApp.openById('1')
},
{
form: FormApp.openById('2')
},
{
form: FormApp.openById('3')
}
];
var whichURL = 0;
for (var i in forms) {
forms[i].responses = forms[i].form.getResponses().length;
whichURL += forms[i].responses;
}
whichURL++;
// we're returning the actual URL to which we should redirect the visitors
return forms[whichURL % forms.length].form.getPublishedUrl();
}
// doGet is Google's reserved name for functions that
// take care of http get requests to your web app
function doGet() {
// we're creating an html template from which getForm function is called
// as the template is evaluated, the returned result
// of the function is inserted into it
// window.open function is a client-side function
// that will open the URL passed to it as attribute
return HtmlService.createTemplate('<script>window.open("<?= getForm() ?>", "_top");</script>').evaluate();
}
So, after you've published your app, you'll get the link opening which the doGet function will run — and you're going to be redirected to your form.
The thing here is that the URL that you're getting this way is not rather beautiful, sth like https://script.google.com/macros/s/1234567890abcdefghijklmnopqrstuvwxyz/exec and it will also show a message at the top of the page "The app wasn't developed by Google" during those 1-2 seconds before redirect happens.
2) Embed your form into another webpage
The idea here is different: instead of giving your users a "proxy" link, you'll provide them with a page that'll ask a Google script for a correct form link and will display that form in the page in an iframe.
So, there are a couple of steps:
2.1) Change your doGet function (getForm will stay the same):
function doGet() {
return ContentService.createTextOutput(getForm());
}
In this case doGet will not return an html to render by browser but just a link to your form.
Sidenote: after changing the code you'll need to publish a new version of your code for the changes to take effect.
2.2) Create a Google site at sites.google.com
2.3) Insert an "Embed" block into your page, with the following code:
<script>
function reqListener(response) {
// change height and width as needed
document.body.insertAdjacentHTML('beforeend', '<iframe src="' + response.target.response + '" width="400" height="400"></iframe>');
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "INSERT-YOUR-SCRIPT-URL");
oReq.send();
</script>
What it does: javascript code sends an http request to your script, gets the form URL and passes it on into the callback function, reqListener which in turn inserts it into document body within an iframe element.
The good thing is that your URL which will be much more user-friendly (you could use this approach on your own site, too).
As a result, you'll have sth like this:

Post # characters in URL being encoded by HtmlService

I am having a problem building links to my Zendesk instance.
In my script, I pull some ticket information from Zendesk and display it in a web app for some important people. I the table of tickets, I want to include a link to the ticket in Zendesk. In the code, I set the value of the cell at such.
var cell = "<td>" + ticket.subject + "</td>";
However, in the resulting webapp the link shows up as follows:
https://mydomain.zendesk.com/agent#%2ftickets%2f1507
This link doesn't work. I have checked the string before sending it to the HtmlService, and I don't see anything in the Caja documentation.
Thanks
This code causes the same problem:
function doGet() {
var html = '<html><body><table><tr><th>A Header</th></tr><tr><td>aoeuaoue</td></tr></table>';
html += '</body></html>';
Logger.log(html);
return HtmlService.createHtmlOutput(html);
}