Web App using Google Apps Script not working with distributed URL - google-apps-script

What the program does: I have written a simple Google Apps Script that retrieves unread emails from a label in the Gmail account currently logged in, and returns the number of those emails, and displays it on the website.
Problem: This program works when I created a web app from the computer I developed it on, and run it from the website. However, if I copy the URL and distribute it to another computer that is logged in as another Google account, it doesn't work.
Code:
/*Sample.gs*/
function doGet(e){
return HtmlService.createHtmlOutputFromFile('Run.html');
}
function GetUnreadCount(sourceLabel){
return GmailApp.getUserLabelByName(sourceLabel).getUnreadCount();
}
function Main()
{
/*For testing, return unread email count from "CustomerA" label of Gmail*/
return GetUnreadCount("CustomerA");
}
.
/*Run.html*/
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function CallGetUnreadCount() {
document.getElementById('Messages').innerHTML = 'Counting...';
google.script.run
.withSuccessHandler(onSuccess)
.withFailureHandler(onFailure)
.Main();
}
function onSuccess(UnreadCount)
{
document.getElementById('Messages').innerHTML = "There are " + UnreadCount + " unread emails.";
}
function onFailure(error)
{
document.getElementById('Messages').innerHTML = "Err: " + error.message;
}
</script>
</head>
<body>
<div>
<h1 id="Messages">Count # of unread emails</h1>
</div>
<div>
<button type="button" onclick='CallGetUnreadCount();' id="CountButton">Count Unread Emails</button>
</div>
</body>
</html>
This program works fine if I run it on the computer I wrote this script on. However, if I deploy it as a web app, and distribute the URL (ending with /exec), and run it on another computer that has logged in as a different Gmail account, it always returns 0 even when there are unread emails in CustomerA label.
What confuses me is, this same program works on another computer if I create a new project on that computer and copy-paste the above code into a .gs file and an html file, and deploy as a web app.
Why does the same program work if each computer deploys the web app on their own, but not work if I distribute the URL?

When Deploying as web app (Publish > Deploy as web app), there are a few options to configure:
Execute the app as: Can be either Me, or User accessing the web app. For your case, you must use the later to make it work as you expect.
Who has access to this app: Can be either Only myselft, Anyone within your domain or Anyone. You have to make sure it's either the 2nd or 3rd option, depending on your needs, so that the user can properly execute it.

Related

Deployed web app with anonymous access privileges asks for login or gives error

I set up a simple "Hello World" app that reads the url query, and set the deployment as follows:
While this is using my workdspace account, the code doesn't access any resources yet. But When i access the app on the URL in an incognito window I get a prompt to log into google. When I access it on a session with a different account (not from my work organization) logged in, I get this:
What could be wrong? The app's URL is: https://script.google.com/macros/s/AKfycby_FgMJNYgfoDW6eA1X7gWVDd1dFOR6dAQN6AV49QM0tcXseGtVlXNZC_Dc693dqBlK/exec?4567 (i tried adding the organization domain (/a/domain.com) but the only difference it did was that in incognito mode it added the domain suffix to the username field).
(Edit) code.gs content is:
function include(filename){
return HtmlService.createHtmlOutputFromFile(filename)
.getContent();
};
function doGet(e) {
asset_id = e.queryString;
webpagecontent = "start text " + asset_id + " end text.";
return HtmlService
.createTemplateFromFile('test.html')
.evaluate();
}
OP resolved the issue by creating a new script, put it in a public-accessible folder, and copied everything into it (including the deployment settings),then opened the link in incognito to test

GetURL returns wrong URL

I have a Google App Script program that has a number of HTML pages. To generate the URL's for links to individual pages, I use this function on the server code:
function getScriptUrl() {
var url = ScriptApp.getService().getUrl();
return url;
}
to return a URL that I could then embed like this, on the client side:
<?var myURL = getScriptUrl();?><a class="btn btn-success" href='<?=myURL?>?page=CreateNew'>My Button/a>
This always returned the URL of the app. So, if am testing in the Dev version, it returns the Dev URL, if I am in the Exe version, it returns the Exe URL. But, now, if I am in the Dev version, it returns the Exe URL. This was never an issue in the past but started today. Does anyone know why this is happening or a better way to generate the URL to create links between pages?
EDIT:
I have tried to republish the app (in Legacy Editor and the New Editor), log out of G Suite account, clear my cache but these attempts did not work to address the issue.
This should a Google part issue, I use the same logic to auto direct to prod and test page. It works fine in the passed few month. But it can't work from last week.
I too have this problem. Until recently I had this code to help me distinguish whether I was testing the development version or the deployed version. It worked for months.
const url = ScriptApp.getService().getUrl();
if (url.endsWith('dev')) {
// more code here
}
Now getUrl() will always return the exec url.
(I know this isn't an answer, but I am a first time poster and do not have enough points to "Add a comment" and I don't want to ask a duplicate question)

Embedding Google Apps Script in an iFrame Error

I'm trying to load a web app into an iframe in an external html file. I've built and tested the app and it works fine, stand alone. But when I load it into an iframe i get the following error..
"Exception: No HTML file named WebAppBoot was found. (line 2, file "Code")"
I modified the Code.gs to set the XFrameOptionsMode to allow all, like so...
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('WebAppBoot');
output.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
I still get the same error
I used this tutorial as the starting point for my web app.. Bootstrap Google Web Application Form Take a look at it to see the file structure in the google "Project", its exactly the same as my web app
Remove the ; of line 2, and output from line 3
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('WebAppBoot')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
then publish again your web app creating a new version (on the version dropdown, select New)
Every time that you made a change to your code that want to see on the /exec web app URL you have to publish again your web creating a new version.
try this:
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('WebAppBoot');
output.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

Google Apps Script Async function execution on Server side

I have a GMail add-on which uses CardService for UI. Some of the callback functions for the Card actions take over than 30 sec. Thus, I'm getting the following error.
Gmail could not perform this add-on action.
Is there, any way to run Google Apps Script functions on the Server side asynchronous way, so I can return to a user some notification and continue work behind the scenes.
I have tried using some libraries like this one but with no luck, I'm able to use syntactically the Promises but functionally it's still synchronous.
As of now, there is no asynchronous execution for the Gmail add-on. Even if there is something, there is no way to refresh the UI without user action.
But, there is a hack. What you can do is, if there is a long running process is there, just create a "openlink" action (set link), which should open a url (https://yourhtmlpageurl?redirect_uri=) with html response. This html can have jquery ajax call, which can wait for some time. Once you get the response in the html window, redirect the page to the redirect_uri that is passed by passing the response data. So, our add on will get a callback to the function with parameter as json object with all the query parameters to the redirect_uri. Once you get the expected response, cache the response by using CacheService. return some html success template, which should automatically close the window by itself.
For Creating openlink action:
For generating redirect script URI with state token:
function generateNewStateToken(callbackName, user_info) {
return ScriptApp.newStateToken()
.withMethod(callbackName)
.withArgument("user_info", JSON.stringify(user_info))
.withTimeout(3600)
.createToken();
}
function getRedirectURI() {
return "https://script.google.com/macros/d/" + ScriptApp.getScriptId() + "/usercallback";
}
var htmlUrl = <your_html_url> + "?redirect_uri="+ getRedirectURI() + "&state=" + generateNewStateToken("asyncCallback", {});
CardService.newOpenLink().setUrl(htmlUrl).setOpenAs(CardService.OpenAs.OVERLAY).setOnClose(CardService.OnClose.RELOAD_ADD_ON);
function asyncCallback(data) {
data.response; // response which you can pass from script
CacheService.getUserCache().put("long_running_process_resp", data.response);
data.user_info; // user_info passed during state token creation
// cache the data using cache service
return HtmlService.createHtmlOutputFromFile("success");
}
success.html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
</head>
<body>
<div class="sidebar">
<p>Long running Process completed.</p>
</div>
</body>
<script>
setTimeout(function() {
top.window.close();
}, 2000);
</script>
</html>
Once the success.html is closed by itself, there will be a refresh of the gmail add on. So, you can lookup for the long running response data from CacheService.
Let me know if you have any more questions on this process.
Sabbu's answer is pretty good. But, if you want to trigger the long running process without expecting the user to click on the openlink, you can render an image using:
CardService.newImage().setImageUrl('https://yourservice.com/pixel/action.png?name=value&_nocache='+new Date().getTime())
On the server side, you can map the path '/pixel/action.png' to the long running code which returns a tiny transparent pixel (1x1) on complete:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==

Google apps script won't update app

I am brand new to this and I know this is probably something simple but I just can't seem to get this working. I found this app script online that lets people upload files to my Google Drive but when I try to change anything in it and save it doesn't reflect in the app. I try to add CSS or update the text in Google Script editor then press Save then Run doGet function but nothing pops up to show me the page and the app doesn't change at all. It is deployed and authorized already.
server.gs:
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('form.html');
}
function uploadFiles(form) {
try {
var dropbox = "Student Files";
var folder, folders = DriveApp.getFoldersByName(dropbox);
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(dropbox);
}
var blob = form.myFile;
var file = folder.createFile(blob);
file.setDescription("Uploaded by " + form.myName);
return "File uploaded successfully " + file.getUrl();
} catch (error) {
return error.toString();
}
}
form.html:
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
<?!= include('style'); ?>
<form id="myForm">
<input type="text" name="myName" placeholder="Your name..">
<input type="file" name="myFile">
<input type="submit" value="Upload File"
onclick="this.value='Uploading..';
google.script.run.withSuccessHandler(fileUploaded)
.uploadFiles(this.parentNode);
return false;">
</form>
<div id="output"></div>
<script>
function fileUploaded(status) {
document.getElementById('myForm').style.display = 'none';
document.getElementById('output').innerHTML = status;
}
</script>
Thanks to #billy98111
Each time you deploy your web app it uses the same version. That's why it does not update.
Once you made changes, create new version then deploy it. Then when you deploy choose the the version you want to run
If you are using the URL with 'exec' on the end, that URL only serves the last version deployed, it won't show you the development version. Secondly, in order for any client side HTML, JavaScript, CSS to be displayed, you must refresh the browser tab. You don't need to refresh the browser tab to see results of any changes in .gs server side code. I have experienced situations where it seems like I needed to reload the browser, or reboot my computer to get something to work, but I can't verify that, and it could have just been a stupid mistake on my part. If it turns out to be none of those issues, let me know. I highly doubt it has anything to do with your code itself. If it's a shared file, and you are not the owner, there can be situations where the owner didn't authorize a new permission, so new code that needs a different permission that had not been authorized before won't run.
I believe you haven't published your app yet!
You need to go to Publish, Deploy as Web App.
There you'll have to generate a new version and them you can Deploy it, after that, you'll get a window showing the Deployed URL (the official URL) and a test URL (in order to test each save you make on your code):
After that if you need to publish new modifications officially, you have to go to File, manage Versions and create a new Version, after that you choose this new version on the Deploy menu again.