Getting a Parameters Error for OAuth2 - google-apps-script

I'm pretty new to this and am struggling at the moment to get an OAuth 2.0 token for use with Google Apps Script to write to a Fusion Table. I'm using the Google Developers Live code from Arun and I can't seem to get the access token. When I run the doGet function below, it gives me a "Type Error: cannot read property "parameters" from undefined".
function doGet(e) {
var HTMLToOutput;
if(e.parameters.code){//if we get "code" as a parameter in, then this is a callback. we can make this more explicit
getAndStoreAccessToken(e.parameters.code);
HTMLToOutput = '<html><h1>Finished with oAuth</h1>You can close this window.</html>';
}
return HtmlService.createHtmlOutput(HTMLToOutput);
}
function getAndStoreAccessToken(code){
var parameters = {
method : 'post',
payload : 'client_id='+CLIENT_ID+'&client_secret='+CLIENT_SECRET+'&grant_type=authorization_code&redirect_uri='+REDIRECT_URL+'&code=' + code
};
var response = UrlFetchApp.fetch(TOKEN_URL,parameters).getContentText();
var tokenResponse = JSON.parse(response);
// store the token for later retrieval
UserProperties.setProperty(tokenPropertyName, tokenResponse.access_token);
}
Any help would be greatly appreciated.

In Appsscript there are some triggers, these triggers execute a piece of code in response to certain action or parameters.
In this case you are using the trigger doGet (which is the name of your function). As you can see, that function receives the parameter "e". If you run that function directly in the environment, this parameter will be "undefined" as you are not passing anything to the function.
This trigger is executed when you access your code as a web application. To do this you have to click on the icon next to the "save" button (the one that looks like a cloud with an arrow) here you can find the information.
When you access your code through the url you obtained after deploying your app, the function receives the necessary parameter (inside "e") and then it should work.

Related

GAS - Authentication w/ UrlFetchApp - Form to Spreadsheet

I am testing the functionality of UrlFetchApp and passing data from a Form and its Spreadsheet. I know it's possible to do this another way, however I am testing the functionality of UrlFetchApp (first time using it) within google scripts themselves, and want to get it to work with this method.
Here's the scenario I got, add a bound script to a Form App as so:
function makeRequest()
{
var webAppUrl = "https://script.google.com/macros/s/WebAppID/exec";
var auth = ScriptApp.getOAuthToken();
var header = { 'Authorization': 'Bearer ' + auth };
var options = { 'method':'post', 'headers':header };
var resp = UrlFetchApp.fetch(webAppUrl, options);
Logger.log(resp);
}
Add a bound script to the attached spreadsheet:
function doPost()
{
var ss = SpreadsheetApp.openById('ssID');
var name = ss.getName();
return ContentService.createTextOutput(name);
}
And then publish this second script attached to the sheet as a web app with only myself to have access.
Currently the above code does not work. The following error appears on the Form side of the script:
Request failed for
https://script.google.com/macros/s/WebAppID/exec
returned code 401. Truncated server response:
Unauthorized Unauthorized Error 401
(use muteHttpExceptions option to examine full response) (line
12, file "Code")
Fails on the UrlFetchApp line of code.
However, if I remove the header option, then publish the web app for public use, this works just fine. Obviously this is not wanted.
What am I missing regarding authentication between scripts that I own?
Side Notes:
Anyone know why SpreadsheetApp.getActiveSheet() doesn't work when run in this fashion? That script is directly bound to a google sheet, so kind of odd.
Ok, found the answer to my own question. It was quite simple really. Needed to add the following scope to my project for accessing a spreadsheet:
https://www.googleapis.com/auth/drive
The easiest way I found to do this is to add a simple function like this and call it:
function authorizeDrive()
{
var forScope = DriveApp.getRootFolder();
}
Doesn't need to return or do anything, just call any method from the DriveApp. Once run, it'll then popup with a dialogue for authorization. Don't even need to call this every time you do your main method calls. Don't even need to leave it coded in the script either. I wonder if there is way to just simple add the scope you need to a project from a properties window (I didn't find any). Or perhaps a way to pass a parameter along with UrlFetchApp regarding what scope need authorized.
Buy anyhow this still wasn't too bad.
Regarding my side note, I still haven't found a reason as to why SpeadsheetApp.getActiveSheet() returns null or undefined. I have to open by ID or URL, which is a pain. Especially since this is a container bound script. Also noticed that Logger.log() doesn't actually add anything to the Logger when run in this manner. If anyone could still shed some light on either of these, that would be great.
You need to get the 'Spreadsheet' object first.
SpeadsheetApp.getActive().getActiveSheet()
However, if you are creating an add-on menu you can use 'SpreadsheetApp.getActiveSheet()'
function myFunction() {
var lastRow = SpreadsheetApp.getActiveSheet().getLastRow();
var range = SpreadsheetApp.getActiveSheet().getRange(lastRow, 1, 1, 26);
SpreadsheetApp.setActiveRange(range);
}
function onOpen(e) {
SpreadsheetApp.getUi().createAddonMenu()
.addItem('showLastRow', 'myFunction')
.addToUi();
}

Gmail API message.list from trigger

I am having a problem creating an google script that would run every night. The code runs fine when I run it from the script file and behaves as expected, however when performed by an installed trigger I get the following:
TypeError: Cannot read property "length" from undefined. (line 17, file "Main")
EDIT: To be clear I know that the particular query used has to return results as running the same script from the script editor works fine
Code:
function doGet(query) {
var sSheet = sheetSelect(), //calls spreadsheet selection function and assigns the spreadsheet to variable
queriedMessages, //object to store the queried messages list
pageToken, //string token value that will be pulled from the queredMessages
auth = 'me';
if (!query) query = 'in:all newer_than:1d -is:chats -in:trash';
do {
queriedMessages = Gmail.Users.Messages.list(auth, {'q':query, 'pageToken':pageToken}); //callls the Gmail API to query messages
dataOutput(sSheet, queriedMessages.messages, queriedMessages.messages.length); //calls function to output all data to spreadsheet from the current list
pageToken = queriedMessages.nextPageToken; //gets the next page token from the list
}
while (pageToken); //the loop is executed until there are no more next page tokens left
}
Any ideas why it behaves so differently? I have tried providing userId for a specific e-mail. Seems like this might be some kind of authentication issue but I cannot figure out how to fix it other than forgetting about Gmail API and go a roundabout way of using Gmail App as it seems to be an issue with Gmail API method messages.list()
Thank You for any help in advance!
I managed to fix the issue. The problem was me wanting to leave an option to pass on a query with the function call. The problem then is that the installed trigger actually passed on a variable to the query variable and a new one is then not set.
I think it's much simpler than that. If I list messages in the last day, I get:
Request:
GET https://www.googleapis.com/gmail/v1/users/me/messages?q=newer_than%3A1d
Response:
{
"messages": [
{
"id": "150612f9d7f83db9",
"threadId": "150611d4e92b7a5f"
}, ...
]
}
If I list messages in the last second, I get:
Request:
GET https://www.googleapis.com/gmail/v1/users/me/messages?q=newer_than%3A1s
Response:
{
"resultSizeEstimate": 0
}
In other words, queriedMessages.messages will be undefined if you get no messages with that particular query, and queriedMessages.messages.length will give rise to your error.

Call a function from external URL

I want to call a function I wrote within my Google Apps Script. When I execute a getJSON I suppose it'll automatically run my doGet(e).
My Javascript:
$.getJSON(https://script.google.com/macros/s/[ID]/exec, function(data){
//code here
});
Is there a possible way to call one of my custom functions for example
My Google Apps Script:
function getNumberOfFans(e){
//code here
}
Do I have to add some kind of extra function parameter to my URL?
In either a "stand alone" or bound Apps Script file add a doGet(e) function.
Publish the Apps Script file as a Web App.
Get the published URL of the Web App.
Add a search string parameter to the end of the URL.
You can add search string parameters to the URL of the published Wep App.
Here is an example:
https://script.google.com/macros/s/[ID]/exec?searchStringName=functionOne
The search string is at the end of the URL, after exec. You must add a question mark after exec and then name=value
url/exec?name=value
Where name and value will be replaced with your choices.
Put the event argument (denoted by the letter "e") into the doGet(e) function, not the function you want used.
function doGet(e) {
var passedString,whatToReturn;
passedString = e.parameter.searchStringName;
if (passedString === 'functionOne') {
whatToReturn = functionOne(); //Run function One
};
return ContentService.createTextOutput(whatToReturn);
};
function functionOne() {
var something;
//. . . . Code;
something = code here;
return something;
};
The above code is for a GET request. If you want to use a POST request, don't use a search string in the URL. For a POST request, you will send information in the payload. You'll still use e.parameter to access the data sent, but whatever is in e.parameter will be an object with key/value pairs. You'll need to know what the key (property) name is that was sent in the object.
For an explanation on URL Parameters, see this documentation:
URL Parameters

How to correctly construct state tokens for callback urls in Managed Libraries?

I'm having an issue with Google Apps Script state tokens when called from a managed library. This means a The state token is invalid or has expired. Please try again. error is always received is the state token is created from a sub function.
Here's some example code that would be in the library (you can add with project key MP9K5nBAvEJwbLYG58qx_coq9hSqx7jwh)
var SCRIPT_ID = "1eC5VsM2vkJXa9slM40MTKTlfARGAGyK1myMCU3AB_-Ox_jGxQaoPM8P2";
// get a callback url to render in popup
function getAuthURL() {
var authorizeURL = getCallbackURL('testCallback');
return authorizeURL;
}
// generate a user callback url
function getCallbackURL(callback) {
var state = ScriptApp.newStateToken().withTimeout(3600).withMethod(callback).createToken();
return 'https://script.google.com/macros/d/'+SCRIPT_ID+'/usercallback?state='+state;
}
// generate login popup
function showLogin(doctype){
doctype.getUi().showDialog(
HtmlService
.createTemplate("<div><p><a href='<?=getAuthURL()?>' id='start-auth'><?=getAuthURL()?></a></p>" +
"<p><a href='<?=getAuthURLStored()?>' id='start-auth'><?=getAuthURLStored()?></a></p></div>")
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.NATIVE)
);
}
// dummy callback function
function testCallback(e){
return HtmlService.createHtmlOutput('<b>Success. You can close this window. !</b>')
}
/*
Rather than using dynamic state url storing the callback url and getting from property
(you could set a script trigger to refresh this every 24 hours)
*/
function getAuthURLStored() {
var authorizeURL = getSetCallbackURL();
return authorizeURL;
}
function setCallbackURL(){
PropertiesService.getScriptProperties().setProperty('callbackURL', getCallbackURL('testCallback'))
}
function getSetCallbackURL(){
return PropertiesService.getScriptProperties().getProperty('callbackURL')
}
which could be called in a Google Document as (assuming managed library identifier is statetest.
function testFunction() {
statetest.showLogin(DocumentApp);
}
When testFunction is run the dialog in the Document presents two urls, the first with a dynamic state url is invalid the second with a stored state token works.
Is this a bug or expected behaviour?
What you are trying to do currently isn't supported. Specifically creating a state token in a library running in an outer script, but having the callback go straight to the library. As of today the callback must always be directed at the outer script, which can then delegate back to the library as needed. You can open a feature request on the issue tracker to support your use case and we'll consider it further.
An example to use a library to handle an authentication flow is to publish a web app from the library which the user is directed to to being the authentication process.
var SCRIPT_ID = "1eC5VsM2vkJXa9slM40MTKTlfARGAGyK1myMCU3AB_-Ox_jGxQaoPM8P2";
// get a callback url to render in popup
function getAuthURL() {
var authorizeURL = getCallbackURL('testCallback');
return authorizeURL;
}
// generate a user callback url
function getCallbackURL(callback) {
var state = ScriptApp.newStateToken().withTimeout(3600).withMethod(callback).createToken();
return 'https://script.google.com/macros/d/'+SCRIPT_ID+'/usercallback?state='+state;
}
// generate login
function doGet(e){
return HtmlService.createTemplate("<div><p><a href='<?=getAuthURL()?>' id='start-auth'><?=getAuthURL()?></a></p></div>")
.evaluate());
}
enter code here
// dummy callback function
function testCallback(e){
return HtmlService.createHtmlOutput('<b>Success. You can close this window. !</b>')
}

e.parameter undefined

Q: why is e.parameter.wfId undefined (in the log) after running the script below (as a web-app)
I call script with this URL
https://script.google.com/a/macros/gappspro.com/exec?service=my-webapp-key
without a parameter (&wfId=somecharacters)
function doGet(e) {
var app = UiApp.createApplication().setTitle('Workflow Builder');
var mainGrid = app.createGrid(2,1).setId('FILE_doGet_mainGrid');
app.add(mainGrid);
var wfId = '1234567890' // FILE.doGet.randomString();
mainGrid.setWidget(1,0, app.createTextBox().setValue(wfId).setId('wfId').setName('wfId'));
var handler = app.createServerHandler('func');
handler.addCallbackElement(mainGrid);
Logger.log(e.parameter.wfId);
return app;
}
function func(e) {
return x;
}
I am trying to implement the workflow script from chapter 8 of james ferreira’s book Enterprise Application Essentials and in the add Docs section i ran into the problem that e.parameter.wfId in line “var wfRowArray = FILE.ssOps.getWfRowFromSS(e.parameter.wfId), “ is undefined when running the script. (on page 134 in the book, not the PDF).
In the example above i brought the code back to the essence of what is causing the error,...for me.
e.parameter.wfId is only available in your func(e) function, not in the doGet. the variable e represents the elements of the callBackElement catch by the handler function.
If I have understood your question correctly, this is behaving as expected.
You say "h-ttps://script.google.com/a/macros/gappspro.com/exec?service=my-webapp-key without a parameter (&wfId=somecharacters)"
So, I believe you are not passing any URL parameters to the script URL and therefore you get them as undefined.
If you call your script with the URL parameters, say
h-ttps://script.google.com/a/macros/gappspro.com/exec?service=my-webapp&wfId=somecharacters
then you can expect to see e.parameter.wfld