Trying to use dev mode with GAS doPOST - google-apps-script

I'm trying to test my apps script doPost function, with postman. so far I have:
function doPost(e) {
var id = '1L_U7MhmV............................'
SpreadsheetApp.openById(id).getSheetByName('responses').appendRow([new Date()]);
var params = JSON.stringify(e.parameter);
return ContentService.createTextOutput(JSON.stringify(e.parameter));
}
I can get it working with the /exec string but when I try the /dev string I can't. I'm getting a 500 error. I'm logged into my account and have updated the version number under publish. How can I get this working?
EDIT:
Thank you. I'm trying to option 1. I created a function that logs the oath token:
function getOAuthToken1() {
Logger.log('Current project has ' + ScriptApp.getOAuthToken());
}
I ran it and got the token. Then inserted it into
https://script.google.com/macros/s/ACCESSTOKEN/dev
but posting to this produces:
Sorry, the file you have requested does not exist.
EDIT2:

You want to use doPost() and access to Web Apps with the endpoint of https://script.google.com/macros/s/###/dev.
If my understanding is correct, https://script.google.com/macros/s/ACCESSTOKEN/dev cannot be used as the endpoint. Please use the original endpoint of https://script.google.com/macros/s/###/dev.
As 2 examples for the simple test, when the curl command is used, the sample commands are as follows. You can select one of them and test at your terminal. Both commands use POST method.
curl -L -H "Authorization: Bearer ### access token ###" -d "key=value" "https://script.google.com/macros/s/#####/dev"
or
curl -L -d "key=value" "https://script.google.com/macros/s/#####/dev?access_token=### access token ###"
In order to access to the endpoint of https://script.google.com/macros/s/###/dev of the deployed Web Apps, it is required to use the access token.
Replace ### access token ### to the value retrieved by ScriptApp.getOAuthToken().
Replace https://script.google.com/macros/s/#####/dev to your endpoint retrieved by deploying Web Apps.
I used -d "key=value" for the post method. If you don't want to put the values, please replace to -d "".
Note:
If the error related to the scopes occurs when you test above command, please add the following comment. By this, the scope of https://www.googleapis.com/auth/drive is added.
// DriveApp.getFiles();
After added it, please run the function of getOAuthToken1() again. By this, the access token including the scope can be retrieved.

Related

Why is doGet() failing without posting logs?

I suppose my question is twofold: doGet() in the following context will just fail after 0.1~0.2 seconds without posting logs, so I have no idea how to troubleshoot it by myself. Additionally, if I'm having the script execute on my behalf, do I have to push a request with my authorization token to a more "pertinent" area than just the sheet name, such as within the iteration itself? Read further for more details:
I have a source spreadsheet where I am cross-referencing user inputted data to validate the information we have "on file". Most of our clients are over the age of 55, so I am trying to reduce end-user complexity by having the script run on my behalf whenever they need to use it (to bypass the Authorization screen, with the big scary "This application could be unsafe!" message). The way I've read to accomplish this seems to be with doGet(), so I set up a low-level HTTP Get request that just pushes a doGet() with my OAuth token, returning the sheet name. I also set up a masking function specifically to do this, and linked it to the button originally used for the iteration logic. The doGet() looks like this:
const doGet = e => {
Logger.log(`Recieved HTTP request.`);
const content = ContentService.createTextOutput(iterator(e));
Logger.log(content);
return content;
}
and the button that uses UrlFetchApp looks like:
const runMask = () => {
const active = SpreadsheetApp.getActiveSheet().getSheetName();
const v4 = 'https://script.google.com/macros/s/<scriptid>/dev' // ScriptApp.getService().getUrl() posts 404
UrlFetchApp.fetch(`${v4}?sheetName='${active}'`, {
headers: { Authorization: `Bearer ${ScriptApp.getOAuthToken()}` },
});
I have some logs set up within the real runMask() that proceed all the way to the end of the program, giving me real URLs and OAuth tokens, so I know it's making it through runMask() without an issue. However, the doGet() log doesn't post anything, even at the top of the function. I can see that it's executing the trigger in my execution log, but the log itself remains empty.
I've tried:
using ScriptApp.getService().getUrl() in place of v4: posts 404 in the log w/ truncated server response
replacing ${active} with the name of the sheet: same issue; logging ${active} also returns the correct name of the sheet.
Beyond this, I'm not even sure what to do. I have everything scoped correctly (auth/spreadsheets.currentonly, auth/script.external_request, and auth/userinfo.email), and I have no issues about operational security (as both the spreadsheet and script are written by me, the clients have no need to grant access to their entire drive). Before trying to implement doGet() and bypass the authorization screen, the iterator itself worked just fine. As such, I have chosen not to include it here, as it's hardly relevant (the function that executes the iteration function never makes it to that point).
I understand this has been quite the deluge of information; I'd be happy to provide more information or context as needed.
Getting ReferenceError: iterator is not defined (line 12, file "ag2")
With this:
const doGet = e => {
Logger.log(`Recieved HTTP request.`);
const content = ContentService.createTextOutput(iterator(e));
Logger.log(content);
return content;
}
Issued with url/exec?option=A
It runs with
const doGet = e => {
Logger.log(`Recieved HTTP request.`);
const content = ContentService.createTextOutput(JSON.stringify(e));
Logger.log(content);
return content;
}
and returns the appropriate stringified object
Only use the test URL (/dev) for testing the web app from a web browser.
Before doGet from a web browser using a versioned deployment (/exec) remember to publish a new version.
Assign a Google Cloud Project to your Google Apps Script project. For details see https://developers.google.com/apps-script/guides/cloud-platform-projects.
To make it easier to debug your avoid calling functions from a Google Apps Script method like createTextOutput, instead, assign the function result to a variable and use it as the method parameter, i.e. replace
const content = ContentService.createTextOutput(iterator(e));
by
const something = iterator(e);
const content = ContentService.createTextOutput(something);
For debugging purposes, create a function to call your doGet function, and check that it hasn't any problem to run, i.e.
function __test__doGet(){
const e = {
parameter: {}
}
doGet(e);
}
Related
Exception handling in google apps script web apps
Issue:
When I saw your question, I'm worried about I have everything scoped correctly (auth/spreadsheets.currentonly, auth/script.external_request, and auth/userinfo.email).
If you are using only the following scopes at oauthScopes of appsscript.json,
https://www.googleapis.com/auth/spreadsheets.currentonly
https://www.googleapis.com/auth/script.external_request
https://www.googleapis.com/auth/userinfo.email
Unfortunately, these scopes cannot be used for access to Web Apps. Although I'm not sure about the method for running your function of runMask, I thought that this might be the reason for your issue.
Solution:
If you want to access Web Apps of https://script.google.com/macros/s/<scriptid>/dev using the access token retrieved by ScriptApp.getOAuthToken(), please include the following scope.
https://www.googleapis.com/auth/drive.readonly
or
https://www.googleapis.com/auth/drive
After you include the above scope, please reauthorize the scopes, and test it again. When your function of iterator has already been declared and the script worked, by running runMask, you can see the log of Logger.log(Recieved HTTP request.) and Logger.log(content) at the log.
Reference:
Taking advantage of Web Apps with Google Apps Script

Access token is refreshed but not updated in my DB

I am using Box API 2.0, and I have a service running to sync the documents between Box and my application. Sometimes something happens like a network error or someone updating the application or restarting the server while the service is requesting a new access token. In this case my access token in my local DB is not the same as the issued one, and because of the error of the restart the new issued token is not saved in my local DB for future use.
How can I prevent something like this from happening? The refresh token is also renewed with each request, so it can't be used to issue a new access and refresh token.
You can use your old refresh token again, as long as you have not made calls with the new access token.
So lets say you have Access Token and Refresh token AT1/RT1. At some point you call the refresh URL to get a new set of tokens:
curl https://www.box.com/api/oauth2/token \ -d 'grant_type=refresh_token&refresh_token={RT1}&client_id={your_client_id}&client_secret={your_client_secret}' \ -X POST
And then your network goes dark, and you never get the response.
Box sent you back a new set of tokens AT2/RT2 like this:
{ "access_token": "AT2", "expires_in": 3696, "restricted_to": [], "token_type": "bearer", "refresh_token": "RT2" }
But you never got it. Boo :(... But never fear!
You can send the exact same request again, and Box's server will give you back EXACTLY the same response.
The same AT2/RT2
Then when you use AT2 (or RT2), RT1 becomes invalid.

Downloading file from Google Drive by using Google Picker

I'm trying to build a webpage on which user can select an image from Google Drive by using Google Picker and download selected files to my server by using PHP script.
I have managed to setup the Picker, and i get fileIDs but when i pass this IDs to my backend and try GET method i get an authentication error.
I have spent 2 days working on this and researching, but more I read google official documentation the more confused I am.
Can someone tell me, or link me the example how implement this? Is it possible to somehow pass the oAuthv2 token from the GooglePicker to my PHP backend and then use that token with the GET request?
Thank you very much in advance !
edit:
here is the error that im geting when i try GET https://www.googleapis.com/drive/v2/files/SOME_FILE_ID
{
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "dailyLimitExceededUnreg",
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.",
"extendedHelp": "https://code.google.com/apis/console"
}
],
"code": 403,
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup."
}
Before you call the GET, you must set an Authorisation-Header containing an up to date access token.
If you want to do this manually, the steps are:-
Request permission using your app-id/client-id and scopes. This will return to you an authorization code.
Use the authorization code to request a refresh token and an access token
Store the refresh token for future use
Set the access token in an http header Authorization (something like Authorization: Bearer ya29.AHES6ZR3HQa9trJM_IQcgNlM0SI4FvLQFiQfcAZCWLobfpjqtGlT6A)
Issue your GET
You can see the whole process in action by clicking around at https://developers.google.com/oauthplayground/
Alternatively, if your client app already has an access token, you could send that to your server alongside the file ID and your server can simply set that directly into the Authorization header.
There are PHP libraries you can use instead, eg. go to https://developers.google.com/drive/v2/reference/files/insert and scroll down to see the PHP samples. It's entirely your choice whether you build the URLs by hand or use the libraries. The big disadvantage to the libraries is that if something goes wrong, you really need to understand and trace the http anyway to see what's going on, so might as well get to learn and love them from day one.
The message "Daily Limit for Unauthenticated Use Exceeded" seems to confuse first timers (me included). It's the "Unauthenticated Use" which is the important part, meaning you haven't set the Authorization header. Google APIs have a daily quota for unauthorized use (stuff like URL shortener). In the case of Drive, that quota is ZERO, hence the error.
#pinoyyid said everything as it is, and inspired by him, here is the actual solution I came up with:
If you want to download a file, you need two variables - oAuthToken and fileId
oAuthToken you get from JS client side when user authenticates.
If you use the example from google docs (https://developers.google.com/picker/docs/), the function looks like this:
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
oauthToken = authResult.access_token;
oauthToken; // <-- THIS IS THE Bearer token
createPicker();
}
}
fileId you get from when user picks a file. Again, a modified example from google docs:
function pickerCallback(data) {
if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
alert('You picked fileId: ' + doc[google.picker.Document.ID]);
}
}
Probably you will pass these data as a form request, or through ajax. Simple cURL call from backend to download the file:
$oAuthToken = 'ya29.XXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXXXXX-XXXXXXX-XXXXXX-X-XXXXXXXXXXXX-XXXX';
$fileId = '0B4zzcXXXXXXXXXXXXXXXXXXXXXX';
$getUrl = 'https://www.googleapis.com/drive/v2/files/' . $fileId . '?alt=media';
$authHeader = 'Authorization: Bearer ' . $oAuthToken ;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $getUrl);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
$authHeader ,
]);
$data = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
file_put_contents("destination-file.jpg", $data);
Docs about file download: https://developers.google.com/drive/web/manage-downloads
You store the refresh token in the database. When an API call is checked whether the current token is still valid. If not, a new access-token is retrieved by using the refresh-token

Examine raw HTTP request from UrlFetchApp.fetch

I am working on something to interact with Amazon's REST API, but I keep getting an error in my response that points to a mal-formed request. I don't see any errors in the code (the parameter that it says is missing is clearly there), so I want to see the raw request that is being sent.
I don't see any available method that will let me do this. Maybe a server that will just include my request as its response?
Create your own endpoint that will echo to the screen your request. For example, to echo a GET request, send it to a script like this (that's been Publish > Deploy as web app):
function doGet(e) {
var test = 'Echo at ' + new Date() + '\n' + e.queryString;
return ContentService.createTextOutput(test);
}
A little late to the game (10 years) but you can now do this:
const requestResult = UrlFetchApp.getRequest(url, options);
Run it in debug mode and break after this executes to examine requestResult.
See: https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#getrequesturl,-params for an official description and a full explanation of the properties for the options parameter.

Salesforce REST API and the DELETE method

I'm using Adobe AIR and integrating with the force.com platform via the REST API, and so far it's been relatively smooth sailing, but I'm coming unstuck on using the DELETE method.
The documentation is simple enough:
Deleting an Account Record
Use the DELETE method to delete a record.
In this example, an Account record is deleted.
Example usage for deleting fields in an Account object
curl https://instancename.salesforce.com/services/data/v20.0/sobjects/Account/001D000000INjVe
-H "Authorization: Bearer token" -H "X-PrettyPrint:1" -X DELETE
Example request body for deleting an Account record
none required
Example response body for deleting an Account record
none returned
My code is below, note that the second parameter of HTTPConnection.send() is the method to call.
var headers:Object = new Object();
headers["Authorization"] = "Bearer "+ConnectionAccessToken;
var url:String = ConnectionInstanceURL + "/services/data/v"+_apiVersionNumber+"/sobjects/"+type+"/"+id;
var response:RESTResponse = new RESTResponse(callback);
var httpCallback:IResponder = new mx.rpc.Responder(response.resultHandler,response.faultHandler);
HTTPConnection.send(headers,"DELETE",url,httpCallback);
Similar code works perfectly for other operations, and the weird thing is that this doesn't fail per se, rather it receives a success response, but gets the record in question back with all of it's fields. It would appear that I'm seeing the results of [select * from Object where Id = <id>, and just to clarify the record is not deleted. The object doesn't have any master detail relationships, so I'm not sure what else might be stopping this delete from happening — has anyone run into this before or have suggestions on how to resolve it?
With the setup I am using (where HTTPConnection is a custom class using HTTPService internally), the Adobe documentation states that the only HTTP methods available for me to use are just GET and POST:
"Optionally, you can pass parameters to the specified URL. When you do not go through the server-based proxy service, you can use only HTTP GET or POST methods. However, when you set the useProxy property to true and you use the server-based proxy service, you can also use the HTTP HEAD, OPTIONS, TRACE, and DELETE methods."
This is why the delete was failing as it must have been sending as a GET instead as per jkraybill's comment above. After experimenting I have found that POST can be used, with the actual method to call included as a parameter to the URL:
HTTPConnection.send(headers,"POST",url + "?_HttpMethod=DELETE",httpCallback);