I have a library with HTML-form like this:
code.gs:
function openDialog() {
SpreadsheetApp.getUi().showModalDialog(HtmlService.createHtmlOutputFromFile("h"), "Test" );
}
function hello() {
console.log('booo');
}
h.html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<button id="b">Click me</button>
<script>
var b = document.getElementById('b');
b.onclick = function() {
google.script.run
.withSuccessHandler(function(str){window.alert("executed");})
// .withFailureHandler(function(error){window.alert("failed");})
.hello();
}
</script>
</body>
</html>
I shared this script for view and deployd it as a library. Next I created a bound script in Google Sheet with this code:
function onOpen() {
SpreadsheetApp.getUi().createMenu('test').addItem('run', 'myFunction').addToUi();
}
var hello = function() {};
function myFunction() {
TT.openDialog();
}
I've added the library with identifier: TT.
Next I refreshed my Google Sheet file with bound code to see the menu "test", ran test > run. The HTML-window appeared. When I clicked the button, nothing happened. When I opened console, I saw the error:
This error does not appear if I do not use library.
I have experienced the same situation with you. In my case, the reason of the issue was due to the authorization at the library side.
When the authorization process for using the scopes in the library is NOT done at the library side, I confirmed that the error of Uncaught occurred.
When the authorization process for using the scopes in the library is done at the library side, I confirmed that the error of Uncaught didn't occur.
Namely, in my environment, I confirmed that when the library is used for your situation, it was required to authorize the scopes for both the client side and the library side.
So, as a workaround, I used the following flow.
Workaround:
Create a Google Apps Script library.
Please copy and paste your script of code.gs and h.html to the standalone script or the container-bound script.
Deploy the Google Apps Script as the library.
In your script, for example, please directly run hello() at the library side, and authorize the scopes.
Install the library to the client side and load the library from the client side.
Please run myFunction() at the client side.
By this flow, when you run run at the custom menu and click the button, the dialog of executed is opened.
Note:
In this case, when I wanted to make users use the client script, it was required to authorize the scopes for both the client side and the library side. I thought that this may be a little inconvenient.
So, how about reporting this for the Google issue tracker? Ref Unfortunately, I couldn't find the issue tracker with the same situation.
Added:
As the method for authorizing the scopes at the library side from the client side, I would like to propose to use Web Apps. I thought that when the Web Apps is used, the authorization of the library side can be done at the client side. By this, I thought that the inconvenience may be resolved a little.
Please do the following flow.
1. Library side.
Please copy and paste the following scripts.
Google Apps Script: code.gs
function openDialog() {
SpreadsheetApp.getUi().showModalDialog(HtmlService.createHtmlOutputFromFile("h"), "Test" );
}
function hello() {
console.log('booo');
}
function doGet() {
return HtmlService.createHtmlOutput("ok");
}
HTML: h.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<button id="b">Click me</button>
<script>
var b = document.getElementById('b');
b.onclick = function() {
google.script.run
.withSuccessHandler(function(str){window.alert("executed");})
// .withFailureHandler(function(error){window.alert("failed");})
.hello();
}
</script>
</body>
</html>
2. Deploy Web Apps at library side.
Please deploy Web Apps at the library side. About the method for this, you can see the official document. Ref The detail setting is as follows.
Execute as: User accessing the web app
Who has access: Anyone with Google account
3. Deploy as library.
Please deploy as the library. Ref
4. Client side.
Please install the library to the client side. And, please copy and paste the following scripts. In this case, please replace https://script.google.com/macros/s/###/exec with your Web Apps URL.
function onOpen() {
SpreadsheetApp.getUi().createMenu('test').addItem('auth', 'auth').addItem('run', 'myFunction').addToUi();
}
var hello = function() {};
function myFunction() {
TT.openDialog();
}
function auth() {
const html = HtmlService.createHtmlOutput(`<input type="button" value="Authorize" onclick="window.open('https://script.google.com/macros/s/###/exec', '_blank');google.script.host.close()">`);
SpreadsheetApp.getUi().showDialog(html);
}
5. Testing.
At first, please run auth at the custom menu. By this, you can authorize the scopes of both the client side and the library side. When the new tab is not opened when auth is run, please run auth() at the script editor again.
As the next step, please run run. By this, your dialog is opened. And, when both authorizations (client and library side) with auth has already been finished, when you click the button, the dialog of executed is opened.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
Related
I have a web app that accepts JS plugins. That is, JavaScript code that someone else wrote that the user wants to load into my app.
Currently I am using eval() to evaluate their JS code into the runtime, but I know thats not secure. Is there a better method than eval() for doing this?
To be clear, the user is pointing me to a dry text file (a URL), and that JS in the file needs to come to life somehow.
There's only two ways I know of dynamically importing a JS script:
Use AJAX, get the JS code, then run eval() on it.
Dynamically add a <script> tag to the DOM
The purpose of the question is to figure out if one is more secure than the other or if there is a better way than the above 2 options.
Is one of these two more secure than the other?
No, they're equally bad (good) from a security perspective.
They differ in details that would lead to different approaches in making them more secure, but ultimately both do run code written by an untrusted third party in your environment, with all its privileges. It's basically a persisted XSS issue.
Is there a better way than the above 2 options?
Many. It depends mostly on what those plugins should do in your application, who writes them and who installs (enables) them. Neither you as the application provider nor the user wants arbitrary code run havoc on the user's data. If the plugins need to access the data, you need administrative measures to ensure that only trusted code will run, like plugin code audits. At least you will need to inform your users that they must trust the plugin authors before enabling the plugin, which puts the burden on them. Also you should ensure to have usable logs in case something went wrong.
If you really want to run arbitrary, untrusted code without giving it access to user data, you will want to consider sandboxing. There are various approaches that essentially do the execution in a virtual machine that the code cannot break out from.
For Chrome Extensions I would specifically use sandboxingEval which allows the loading of sandboxed files that can be accessed within an iframe that the extension hosts. The only message passing would be through normal iframe messaging.
For example, declare within the manifest.json which page to sandbox:
{
...
"sandbox": {
"pages": [
"plugin.html"
]
"content_security_policy":
"sandbox allow-scripts; script-src 'self' https://plugin.com/"
],
...
}
Make sure the external domain is white-listed so it could be embedded. In the CSP policy, allow-scripts is there if embedding <scripts> is needed.
Now within the sandboxed page plugin.html, do anything with the external script. In this case, the external plugin is downloaded, and passing messages back to the extension process through messaging.
<!doctype html>
<html>
<head>
<script src="https://plugin.com/mohamedmansour/plugin.js"></script>
</head>
<body>
<script>
// Whatever my plugin contract is, lets send something back to our extension
// that the plugin initialized.
Plugin.do.something.here(() => {
window.postMessage({
name: 'CustomInitEvent',
data: 'initializing'
}, *);
});
// Listen from your extension plugin.html page some events.
window.addEventListener('message', (event) => {
var command = event.data.command;
switch(command) {
case 'CustomCommandA':
event.source.postMessage({
command: 'CustomCommandHello',
data: 'pong command a'
}, event.origin);
break;
}
});
</script>
</body>
</html>
Now within the popup or anywhere, just embed the plugin.html. In this case, popup.html looks like this
<html>
<head>
<script src="plugin-manager.js"></script>
</head>
<body>
<iframe id="theFrame" src="plugin.html"></iframe>
</body>
</html>
Then your plugin-manager.js is responsible of controlling plugin.
const iframe = document.getElementById('theFrame');
window.addEventListener('message', function(event) {
switch(event.name) {
case 'CustomInitEvent':
console.log('Plugin Initialized');
break;
case 'CustomCommandHello':
console.log('Hey!');
break;
}
});
iframe.contentWindow.postMessage({
command: 'CustomCommandA'
});
iframe.contentWindow.postMessage({
command: 'CustomCommandB'
});
Something along those lines. If dynamic plugins is what is needed, just add query parameters to the iframe. Within plugin.html, just dynamically add the script element, and just call it this way:
<iframe id="theFrame" src="plugin.html?id=121212"></iframe>
I am sending a post request to a Google App Script webapp from a webpage under different domain.
The doPost method at the GAS side handles the request, and returns a string.
I can see the request reaching the server, but no response is received at the client side.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
function myFunc() {
$.post("https://googlewebappURL", postdata).done(function(responsedata) {
alert("Data Loaded: " + responsedata);
});
};
</script>
The alert call does not happen.
The post does reach the web app, my logs confirm that.
Am I doing something wrong here?
I think that your javascript works fine. For your situation, I think of the following confirmation points.
Confirmation points :
It seems that postdata is not defined.
When the script was modified, whether the script version was updated and deployed Web Apps as the new version. How to deploy Web Apps is as follows.
On the Script Editor
Publish
Deploy as Web App
Create new Project version
At Execute the app as, select "your account"
At Who has access to the app, select "Anyone, even anonymous"
Click "Deploy"
Copy "Current web app URL"
Click "OK"
The detail of deploying Web Apps is here.
Even if you confirm above points, if the problem is not solved, can you try a simple sample as follows? If the modified your script didn't work, you can also use a curl sample. When the sample works, you retrieve value.
Sample :
Google Apps Script : code.gs
function doPost(e) {
return ContentService.createTextOutput(e.parameter.key);
}
Modified your script :
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
$(function() {
var postdata = {key: "value"}; // Added
$.post("https://googlewebappURL", postdata).done(function(responsedata) {
alert("Data Loaded: " + responsedata);
});
});
</script>
curl sample :
curl -L -d "key=value" "https://googlewebappURL"
If above samples didn't work, can I ask you about your doPost()? If I misunderstand your question, I'm sorry.
I am trying to simply get the Google sign-in button working on my website, but I am stuck at the following step:
Getting profile information
As you can see, Google has taken an extremely straightforward task and made it impossible with contextless code snippets, and I am absolutely stuck. Right now, all I have done is put the following in the head of my HTML file:
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="(I PUT MY CLIENT ID HERE ALREADY).apps.googleusercontent.com">
All I want to do is sign the user in WITHOUT requesting email address. My problem is the following:
WHERE do I put the gapi.load('auth2', function()... code snippet? At the top level of my webpage's JavaScript file? In the body's onload function? In the onSignIn function? NONE of the above are working. I get an exception, or it says "gapi is not defined" or it says "auth2 is not defined" etc... And once that code snippet is in place, where do I put the if (auth2.isSignedIn.get()) code snippet? You'd think the geniuses at google would think to include this incredibly simple, vital information in their tutorial. Incredibly frustrating.
You can pass the url a callback function to be run when the javascript gets loaded.
<script src="https://apis.google.com/js/platform.js?onload=googleLoaded" async defer></script>
So in your code you can do
function googleLoaded(){
// The script is ready to use.
// gapi.load('auth2', function()...
}
found here https://developers.google.com/identity/sign-in/web/build-button
I'm new at nodejs and I want to write to a serial port using node but now I want it to be triggered by a button. And the data is coming from a textbox.
My node script is doing fine when I run it in the console/terminal. But can't do it with a button in a webpage.
Here's my nodejs embedded in html
<!DOCTYPE html>
<html>
<head>
<title>Node x HTML</title>
</head>
<body>
<script>
function write_serial()
{
var serialport = require("serialport");
var SerialPort = serialport.SerialPort;
var sp = new SerialPort("/dev/ttyACM0", {
baudrate: 9600,
parser: serialport.parsers.readline("\n")
});
var text = document.getElementById("textbox1").value;
sp.on('open', function()
{
sp.on('data', function (data)
{
sp.write(name);
});
});
}
</script>
<Input Type = "text" id = "textbox1" >
<BR>
<br><button onclick="write_serial();" href="javascript:;">Go!</button>
</body>
</html>
Here's the error I got when I open the console of the page (F12)
ReferenceError: require is not defined
Thanks in advance for your help. :)
Node.js is a hosting environment, that can execute JS and provides Node.js specific API. Browser, is a different hosting environment, with different API's. You can't use Node's specific API in a browser, and JS that uses Node API will result in an error in a browser.
For example, your script is using global require function, which is not available in a browser API's. And that's why:
ReferenceError: require is not defined
Conversely, your script can't be executed on Node as well, since it uses browser API:
document.getElementById("textbox1")
You've mixed API's from different environments in one script.
However, if your JS script doesn't use Node or browser specific API, it can be executed in both Node and a browser without an error. But it's not the case with your script.
The solution to your problem can be to split your script into two separate scripts, run one in a browser, and the other in Node.js, and to exchange data between them using XMLHttpRequest.
NodeJS is a non-browser JavaScript environment. You can't use most NodeJS features in a browser environment, because they aren't designed for it.
Instead, you'd have a local NodeJS process providing a web endpoint (e.g., a web server; perhaps using Express, but you don't have to) and run that NodeJS process so it's listening for web requests. Then you'd have a button on your web page that makes an ajax call to the NodeJS server, which performs the work.
Naturally, this would only allow you to perform the work on the machine where the server process is running.
Maybe import the serialport module from https://wzrd.in/standalone/serialport#latest
In other hand, try to seperate the logic from the view, i don't know if your app will grow or if it's just a POC, but use a messageBroker or sockets to bind your actions'view with the engine ?
Hope it helps
Some company is providing me with web-based API for using their services. I have no problem calling this API functions from within web brower:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script type="text/javascript"
src="http://someaddress.com?&key=somekey"></script>
<script type="text/javascript">
var object = new SomeObject();
object.SomeFunction();
</script>
</head>
I am interested in return value of SomeFunction().
How would I get this return value from windows application?
You can use a tool like Firebug. That will let you watch the actual HTTP requests, and step through the JavaScript. In combination, that will let you see where the return value comes from.
Once you understand this, you can replicate the requests (and possibly parts of the JavaScript logic) in your Windows app using an appropriate HTTP client library.
Note that this may be a violation of the TOS.
You can embed web browser control in WinForm and return value from SomeFunction
into some DOM element, then you can access that element from WinForm.WebBrowser1.
There are also javascript emulators available, I think you can even use JSCRIPT dll from c#
but that may not work if SomeFunction() depends on DOM.