Geolocation not working with HtmlService - google-apps-script

I created the following two files:
code.gs
function doGet() {
var html = HtmlService.createHtmlOutputFromFile('html.html');
return html;
}
html.html
<html>
<body>
<p id="messaging">Click the button to get your coordinates:</p>
<button onclick="getLocation()">Where am I</button>
<script>
var message=document.getElementById("messaging");
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
message.innerHTML="Geolocation is not supported.";
}
}
function showPosition(position) {
message.innerHTML="Latitude: " + position.coords.latitude + "<br>Longitude: " + position.coords.longitude;
}
</script>
</body>
</html>
When I call the published URL, I get the expected message and button. Click the button and I get my failure message "Geolocation is not supported". If I save html.html in a file and open it in a browser it works as expected.
Any ideas?

As of 2016, geolocation works with HtmlService in IFRAME mode. Test it here: GAS-geolocation
Tested with Chrome and Firefox (desktop), both ok. Doesn't work with Safari (desktop) (the share location confirmation box doesn't popup. I hate Safari!)
Funny thing is, it works with Safari on iOS 9 but not the latest Chrome on iOS 9. (same issue, no confirmation popup)

GeoLocation is not yet available in HtmlService

I believe, Caja is culprit here. Could you run your code on Caja playground to check if the behavior is same. If same, you may open an issue in Caja Issue Tracker
To know more what Caja does with HtmlService, you may refer to this page.
Update
Above answer is obsolete. Now one can access location using navigator.geolocation object available in browser.

I just tried your code, as I'd like to do something with Google Apps Script in Google Sites.
It appears that GeoLocation is still not supported in HTMLService at this time, but I found a possible workaround for my specific need (i.e. in conjunction with Google Sites) that may help someone else too :
What does work, is to create a custom "Google Sites Gadget"
using the example code from tutorials point
I end up with a 'skeleton' XML file for my gadget as such :
<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs title="GeoLocation"
>
</ModulePrefs>
<Content type="html"><![CDATA[
<form>
<input type="button" onclick="getLocation();"
value="Get Location"/>
</form>
<script>
function showLocation(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
alert("Latitude : " + latitude + " Longitude: " + longitude);
}
function errorHandler(err) {
if(err.code == 1) {
alert("Error: Access is denied!");
}else if( err.code == 2) {
alert("Error: Position is unavailable!");
}
}
function getLocation(){
if(navigator.geolocation){
// timeout at 60000 milliseconds (60 seconds)
var options = {timeout:60000};
navigator.geolocation.getCurrentPosition(showLocation,
errorHandler,
options);
}else{
alert("Sorry, browser does not support geolocation!");
}
}
</script>
]]></Content>
</Module>
Now instead of the alerts, I can pass the results to the Google Apps Scripts Web App as a URL parameter.
It's not ideal, but seems to work ok so far.

Related

Can a website detect extensions that are installed using Developed Mode? [duplicate]

I am in the process of building a Chrome extension, and for the whole thing to work the way I would like it to, I need an external JavaScript script to be able to detect if a user has my extension installed.
For example: A user installs my plugin, then goes to a website with my script on it. The website detects that my extension is installed and updates the page accordingly.
Is this possible?
Chrome now has the ability to send messages from the website to the extension.
So in the extension background.js (content.js will not work) add something like:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (request) {
if (request.message) {
if (request.message == "version") {
sendResponse({version: 1.0});
}
}
}
return true;
});
This will then let you make a call from the website:
var hasExtension = false;
chrome.runtime.sendMessage(extensionId, { message: "version" },
function (reply) {
if (reply) {
if (reply.version) {
if (reply.version >= requiredVersion) {
hasExtension = true;
}
}
}
else {
hasExtension = false;
}
});
You can then check the hasExtension variable. The only drawback is the call is asynchronous, so you have to work around that somehow.
Edit:
As mentioned below, you'll need to add an entry to the manifest.json listing the domains that can message your addon. Eg:
"externally_connectable": {
"matches": ["*://localhost/*", "*://your.domain.com/*"]
},
2021 Update:
chrome.runtime.sendMessage will throw the following exception in console if the extension isn't installed or it's disabled.
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist
To fix this, add this validation inside the sendMessage callback
if (chrome.runtime.lastError) {
// handle error
}
I am sure there is a direct way (calling functions on your extension directly, or by using the JS classes for extensions), but an indirect method (until something better comes along):
Have your Chrome extension look for a specific DIV or other element on your page, with a very specific ID.
For example:
<div id="ExtensionCheck_JamesEggersAwesomeExtension"></div>
Do a getElementById and set the innerHTML to the version number of your extension or something. You can then read the contents of that client-side.
Again though, you should use a direct method if there is one available.
EDIT: Direct method found!!
Use the connection methods found here: https://developer.chrome.com/extensions/extension#global-events
Untested, but you should be able to do...
var myPort=chrome.extension.connect('yourextensionid_qwerqweroijwefoijwef', some_object_to_send_on_connect);
Another method is to expose a web-accessible resource, though this will allow any website to test if your extension is installed.
Suppose your extension's ID is aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, and you add a file (say, a transparent pixel image) as test.png in your extension's files.
Then, you expose this file to the web pages with web_accessible_resources manifest key:
"web_accessible_resources": [
"test.png"
],
In your web page, you can try to load this file by its full URL (in an <img> tag, via XHR, or in any other way):
chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/test.png
If the file loads, then the extension is installed. If there's an error while loading this file, then the extension is not installed.
// Code from https://groups.google.com/a/chromium.org/d/msg/chromium-extensions/8ArcsWMBaM4/2GKwVOZm1qMJ
function detectExtension(extensionId, callback) {
var img;
img = new Image();
img.src = "chrome-extension://" + extensionId + "/test.png";
img.onload = function() {
callback(true);
};
img.onerror = function() {
callback(false);
};
}
Of note: if there is an error while loading this file, said network stack error will appear in the console with no possibility to silence it. When Chromecast used this method, it caused quite a bit of controversy because of this; with the eventual very ugly solution of simply blacklisting very specific errors from Dev Tools altogether by the Chrome team.
Important note: this method will not work in Firefox WebExtensions. Web-accessible resources inherently expose the extension to fingerprinting, since the URL is predictable by knowing the ID. Firefox decided to close that hole by assigning an instance-specific random URL to web accessible resources:
The files will then be available using a URL like:
moz-extension://<random-UUID>/<path/to/resource>
This UUID is randomly generated for every browser instance and is not your extension's ID. This prevents websites from fingerprinting the extensions a user has installed.
However, while the extension can use runtime.getURL() to obtain this address, you can't hard-code it in your website.
I thought I would share my research on this.
I needed to be able to detect if a specific extension was installed for some file:/// links to work.
I came across this article here
This explained a method of getting the manifest.json of an extension.
I adjusted the code a bit and came up with:
function Ext_Detect_NotInstalled(ExtName, ExtID) {
console.log(ExtName + ' Not Installed');
if (divAnnounce.innerHTML != '')
divAnnounce.innerHTML = divAnnounce.innerHTML + "<BR>"
divAnnounce.innerHTML = divAnnounce.innerHTML + 'Page needs ' + ExtName + ' Extension -- to intall the LocalLinks extension click here';
}
function Ext_Detect_Installed(ExtName, ExtID) {
console.log(ExtName + ' Installed');
}
var Ext_Detect = function (ExtName, ExtID) {
var s = document.createElement('script');
s.onload = function () { Ext_Detect_Installed(ExtName, ExtID); };
s.onerror = function () { Ext_Detect_NotInstalled(ExtName, ExtID); };
s.src = 'chrome-extension://' + ExtID + '/manifest.json';
document.body.appendChild(s);
}
var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
if (is_chrome == true) {
window.onload = function () { Ext_Detect('LocalLinks', 'jllpkdkcdjndhggodimiphkghogcpida'); };
}
With this you should be able to use Ext_Detect(ExtensionName,ExtensionID) to detect the installation of any number of extensions.
Another possible solution if you own the website is to use inline installation.
if (chrome.app.isInstalled) {
// extension is installed.
}
I know this an old question but this way was introduced in Chrome 15 and so I thought Id list it for anyone only now looking for an answer.
Here is an other modern approach:
const checkExtension = (id, src, callback) => {
let e = new Image()
e.src = 'chrome-extension://'+ id +'/'+ src
e.onload = () => callback(1), e.onerror = () => callback(0)
}
// "src" must be included to "web_accessible_resources" in manifest.json
checkExtension('gighmmpiobklfepjocnamgkkbiglidom', 'icons/icon24.png', (ok) => {
console.log('AdBlock: %s', ok ? 'installed' : 'not installed')
})
checkExtension('bhlhnicpbhignbdhedgjhgdocnmhomnp', 'images/checkmark-icon.png', (ok) => {
console.log('ColorZilla: %s', ok ? 'installed' : 'not installed')
})
I used the cookie method:
In my manifest.js file I included a content script that only runs on my site:
"content_scripts": [
{
"matches": [
"*://*.mysite.co/*"
],
"js": ["js/mysite.js"],
"run_at": "document_idle"
}
],
in my js/mysite.js I have one line:
document.cookie = "extension_downloaded=True";
and in my index.html page I look for that cookie.
if (document.cookie.indexOf('extension_downloaded') != -1){
document.getElementById('install-btn').style.display = 'none';
}
You could have the extension set a cookie and have your websites JavaScript check if that cookie is present and update accordingly. This and probably most other methods mentioned here could of course be cirvumvented by the user, unless you try and have the extension create custom cookies depending on timestamps etc, and have your application analyze them server side to see if it really is a user with the extension or someone pretending to have it by modifying his cookies.
There's another method shown at this Google Groups post. In short, you could try detecting whether the extension icon loads successfully. This may be helpful if the extension you're checking for isn't your own.
Webpage interacts with extension through background script.
manifest.json:
"background": {
"scripts": ["background.js"],
"persistent": true
},
"externally_connectable": {
"matches": ["*://(domain.ext)/*"]
},
background.js:
chrome.runtime.onMessageExternal.addListener(function(msg, sender, sendResponse) {
if ((msg.action == "id") && (msg.value == id))
{
sendResponse({id : id});
}
});
page.html:
<script>
var id = "some_ext_id";
chrome.runtime.sendMessage(id, {action: "id", value : id}, function(response) {
if(response && (response.id == id)) //extension installed
{
console.log(response);
}
else //extension not installed
{
console.log("Please consider installig extension");
}
});
</script>
Your extension could interact with the website (e.g. changing variables) and your website could detect this.
But there should be a better way to do this. I wonder how Google is doing it on their extension gallery (already installed applications are marked).
Edit:
The gallery use the chrome.management.get function. Example:
chrome.management.get("mblbciejcodpealifnhfjbdlkedplodp", function(a){console.log(a);});
But you can only access the method from pages with the right permissions.
A lot of the answers here so far are Chrome only or incur an HTTP overhead penalty. The solution that we are using is a little different:
1. Add a new object to the manifest content_scripts list like so:
{
"matches": ["https://www.yoursite.com/*"],
"js": [
"install_notifier.js"
],
"run_at": "document_idle"
}
This will allow the code in install_notifier.js to run on that site (if you didn't already have permissions there).
2. Send a message to every site in the manifest key above.
Add something like this to install_notifier.js (note that this is using a closure to keep the variables from being global, but that's not strictly necessary):
// Dispatch a message to every URL that's in the manifest to say that the extension is
// installed. This allows webpages to take action based on the presence of the
// extension and its version. This is only allowed for a small whitelist of
// domains defined in the manifest.
(function () {
let currentVersion = chrome.runtime.getManifest().version;
window.postMessage({
sender: "my-extension",
message_name: "version",
message: currentVersion
}, "*");
})();
Your message could say anything, but it's useful to send the version so you know what you're dealing with. Then...
3. On your website, listen for that message.
Add this to your website somewhere:
window.addEventListener("message", function (event) {
if (event.source == window &&
event.data.sender &&
event.data.sender === "my-extension" &&
event.data.message_name &&
event.data.message_name === "version") {
console.log("Got the message");
}
});
This works in Firefox and Chrome, and doesn't incur HTTP overhead or manipulate the page.
You could also use a cross-browser method what I have used.
Uses the concept of adding a div.
in your content script (whenever the script loads, it should do this)
if ((window.location.href).includes('*myurl/urlregex*')) {
$('html').addClass('ifextension');
}
in your website you assert something like,
if (!($('html').hasClass('ifextension')){}
And throw appropriate message.
If you have control over the Chrome extension, you can try what I did:
// Inside Chrome extension
var div = document.createElement('div');
div.setAttribute('id', 'myapp-extension-installed-div');
document.getElementsByTagName('body')[0].appendChild(div);
And then:
// On web page that needs to detect extension
if ($('#myapp-extension-installed-div').length) {
}
It feels a little hacky, but I couldn't get the other methods to work, and I worry about Chrome changing its API here. It's doubtful this method will stop working any time soon.
If you're trying to detect any extension from any website,
This post helped: https://ide.hey.network/post/5c3b6c7aa7af38479accc0c7
Basically, the solution would be to simply try to get a specific file (manifest.json or an image) from the extension by specifying its path. Here's what I used. Definitely working:
const imgExists = function(_f, _cb) {
const __i = new Image();
__i.onload = function() {
if (typeof _cb === 'function') {
_cb(true);
}
}
__i.onerror = function() {
if (typeof _cb === 'function') {
_cb(false);
}
}
__i.src = _f;
__i = null;
});
try {
imgExists("chrome-extension://${CHROME_XT_ID}/xt_content/assets/logo.png", function(_test) {
console.log(_test ? 'chrome extension installed !' : 'chrome extension not installed..');
ifrm.xt_chrome = _test;
// use that information
});
} catch (e) {
console.log('ERROR', e)
}
Here is how you can detect a specific Extension installed and show a warning message.
First you need to open the manifest file of the extension by going to chrome-extension://extension_id_here_hkdppipefbchgpohn/manifest.json and look for any file name within "web_accessible_resources" section.
<div class="chromewarning" style="display:none">
<script type="text/javascript">
$.get("chrome-extension://extension_id_here_hkdppipefbchgpohn/filename_found_in_ web_accessible_resources.png").done(function () {
$(".chromewarning").show();
}).fail(function () {
// alert("failed.");
});
</script>
<p>We have detected a browser extension that conflicts with learning modules in this course.</p>
</div>
Chrome Extension Manifest v3:
const isFirefox = chrome.runtime.OnInstalledReason.CHROME_UPDATE != "chrome_update";
For FireFox, I believe chrome.runtime.OnInstalledReason.BROWSER_UPDATE will be "browser_update": https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/OnInstalledReason

Is there any way I can add or set the sandbox keywords in Google Web App?

I've added a button into my Google Web App that when clicked, dynamically creates a new file in my Drive and downloads it to my local machine. I'm looking for a way to add the key word 'allow-downloads-without-user-activation' to the iframe so that it will continue to work after drive-by downloads are deprecated.
Currently, the console is giving me a warning to add 'allow-downloads-without-user-activation' since it will be removed in M74. Background details: https://www.chromestatus.com/feature/5706745674465280
function doGet(e) {
return HtmlService.createTemplateFromFile('MainPage').evaluate();
}
The code above is how I currently display the web app, and I could not find any way to add sandbox keyword from there. Is there any way to add this keyword to the iframe in Google App Script?
Edit:
This is the code in my html file.
$('#downloadBtn').click( function() {
$('#downloadBtnLabel').html('Preparing file...');
$('#Spinner').css('display','block');
google.script.run.withSuccessHandler(downloadFile).getTempLink();
});
function downloadFile(createdSpreadsheetId) {
var dlink = document.createElement('a');
dlink.download = name;
dlink.href = 'https://docs.google.com/spreadsheets/d/' + createdSpreadsheetId + '/export?exportFormat=xlsx';
dlink.onclick = function(e) {
var that = this;
setTimeout(function() {
window.URL.revokeObjectURL(that.href);
}, 1500);
};
dlink.click();
dlink.remove();
$('#downloadBtnLabel').html('Download');
$('#Spinner').css('display','none');
}
Parts of the above code is from https://stackoverflow.com/a/35251739 (special thanks to the author), which is working perfectly, just with the warnings in console.

Download file from drive using appscript

I have a file in Google Drive with id=0B3fqdol6s0bWZHV3RWpoV1gyWkk
And I created appscript to download it over a link automatically. The code looks like this
function doGet(e) {
return HtmlService.createHtmlOutput(downloader());
}
function downloader() {
var out = "<body onload='dllink.click()'>";
out +="<a id='dllink' href='https://drive.google.com/uc?export=download&id=0B3fqdol6s0bWZHV3RWpoV1gyWkk'>wait will download automatically<a/>";
out +="</body>";
return out;
};
Then, I developed it as webapps so my friend can download it easily using link
https://script.google.com/macros/s/AKfycbzzToo8gwgdj30FBLrjA1izcfv4rddjW6VClaEGuNXAqZAkIH7S/exec
It's working perfect on PC browser, but sadly, It can't work on mobile phone browser which doesn't support handle onload event or javascript.
So, is there any solution to make an appscript download a file based on its id automatically?
Try it by using an iFrame:
function doGet(e) {
return HtmlService.createHtmlOutput(downloader());
}
function downloader() {
var out = "<iframe width=\"1\" height=\"1\" frameborder=\"0\" src=\"https://drive.google.com/uc?export=download&id=0B3fqdol6s0bWZHV3RWpoV1gyWkk\"></iframe>";
return out;
};
*Backslashes are for escaping

Open a "Help" page after Chrome extension is installed first time

I am new to Chrome extension. I have a question about how to make the extension to open a "Help" page automatically after installation. Currently, I am able to check whether the extension is running the first time or not by saving a value into localStorage. But this checking is only carried out when using click the icon on the tool bar. Just wondering if there is a way that likes FF extension which uses the javascript in to open a help page after the installation. Thanks.
Edit:
Thanks for the answer from davgothic. I have solved this problem.
I have another question about the popup. My extension checks the url of current tab,
if OK(url){
//open a tab and do something
}
else{
//display popup
}
Is it possible to show the popup in this way?
Check this updated and most reliable solution provided by Chrome: chrome.runtime Event
chrome.runtime.onInstalled.addListener(function (object) {
let externalUrl = "http://yoursite.com/";
let internalUrl = chrome.runtime.getURL("views/onboarding.html");
if (object.reason === chrome.runtime.OnInstalledReason.INSTALL) {
chrome.tabs.create({ url: externalUrl }, function (tab) {
console.log("New tab launched with http://yoursite.com/");
});
}
});
Add this to your background.js I mean the the page you defined on manifest like following,
....
"background": {
"scripts": ["background.js"],
"persistent": false
}
...
UPDATE: This method is no longer recommended. Please see Nuhil's more recent answer below.
I believe what you need to do is put something like this into a script in the <head> section of your extension's background page, e.g. background.html
function install_notice() {
if (localStorage.getItem('install_time'))
return;
var now = new Date().getTime();
localStorage.setItem('install_time', now);
chrome.tabs.create({url: "installed.html"});
}
install_notice();
As of now (Aug 2022) the right way to execute code on first install or update of an extension using Manifest V3 is by using the runtime.onInstalled event.
This event is documented here: https://developer.chrome.com/extensions/runtime#event-onInstalled
There is one example for this exact case in the docs now:
https://developer.chrome.com/docs/extensions/reference/tabs/#opening-an-extension-page-in-a-new-tab
Note: This example above is wrong as the callback function parameter is Object with the key reason and not reason directly.
And another example here (this one is correct but does not open a tab):
https://developer.chrome.com/docs/extensions/reference/runtime/#example-uninstall-url
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
// Code to be executed on first install
// eg. open a tab with a url
chrome.tabs.create({
url: "https://google.com"
});
} else if (details.reason === chrome.runtime.OnInstalledReason.UPDATE) {
// When extension is updated
} else if (details.reason === chrome.runtime.OnInstalledReason.CHROME_UPDATE) {
// When browser is updated
} else if (details.reason === chrome.runtime.OnInstalledReason.SHARED_MODULE_UPDATE) {
// When a shared module is updated
}
});
This code can be added to a background service worker: https://developer.chrome.com/docs/extensions/mv3/migrating_to_service_workers/
It would be better to place a "version" number so you can know when an extension is updated or installed.
It has been answered here:
Detect Chrome extension first run / update
All you need to do is adding the snippet below to your background.js file
chrome.runtime.onInstalled.addListener(function (object) {
chrome.tabs.create({url: `chrome-extension://${chrome.runtime.id}/options.html`}, function (tab) {
console.log("options page opened");
});
});

User Script Debugging in Google Chrome

What's the best way to debug custom User Scripts (aka Greasemonkey) in Chrome? Is there a way to enable User Script tracking in the Developer Tools?
What kind of debugging do you want? Like Alex said, user scripts will be listed in the same context as debugging the page itself. If you go to the "scripts" tab in the developer tools, you'll see a bar with a dropdown which will allow you to select the appropriate javascript file you wish to debug. Such scripts should have urls that look like chrome-extension://<hash>/<script file>.js. Those scripts will also log to the console of the page they're embedded on.
Additionally, if you want to log in the same place for all pages, you can try building your script as a full chrome extension, using the user script as a content script. Then you can send a message from your content script to your background page and log there. For example, if this were your content script:
function log(text) {
chrome.extension.sendRequest({'action' : 'log', 'text' : text}, function() {});
};
log("Content script loaded: " + window.location.href);
And this were your background page:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script>
function onRequest(request, sender, callback) {
if (request.action && request.action == 'log') {
console.log(request.text);
}
};
chrome.extension.onRequest.addListener(onRequest);
</script>
</body>
</html>
You would see each load of the content script in the background page's log.
i user the following function in my scripts for cross-browser GM Api compatibility:
function testGM() {
var isGM = typeof GM_getValue != 'undefined' && typeof GM_getValue('a', 'b') != 'undefined';
if(typeof(unsafeWindow) == 'undefined') { unsafeWindow = window; }
if(!isGM) { log = function(msg) { try { unsafeWindow.console.log(msg); } catch(e) {} }; } else { log = GM_log; }
if(window.opera) log = opera.postError;
setValue = isGM ? GM_setValue : function (name, value) { return localStorage.setItem(name, value) };
getValue = isGM ? GM_getValue : function(name, def){ var s = localStorage.getItem(name); return s == null ? def : s };
}
testGM();
it's not mine. it's courtesy sizzemctwizzle # userscripts.org
i only use log, getValue & setValue as of now, hence only these tree in that function.
You can also check out his guide.
Or you can checkout GIJoe's cross-browser GM Api too.
You could use a smaller script to actually inject your custom debug script into the page. At that point you will have the same access inside of developer tools as if it was actually included on the page.