DocumentApp causes App Script executed from Javascript client to 401 - javascript

Currently attempting to execute a GAS from a Javascript client. The GAS script is as follows:
function findAndReplace() {
var doc = DocumentApp.openById('ID_OMITTED');
}
This executes fine by pressing Play in the GAS editor, however when I try to execute from my Javascript client, I click authorise.
Then I receive a 401.
Error calling API: {
"error": {
"code": 401,
"message": "ScriptError",
"status": "UNAUTHENTICATED",
"details": [{
"#type": "type.googleapis.com/google.apps.script.v1.ExecutionError",
"errorMessage": "Authorization is required to perform that action.",
"errorType": "ScriptError"
}]
}
}
And now when I press Play in the GAS editor, I get a Action not allowed error. I can then create a new script by copying it, press Play and it works fine. Executing the from the Javascript causes the whole the 401 and then the whole cycle repeats.
However if I execute a simple:
function test() {
return 'hello';
}
It works, and returns
Folders under your root folder:
h (0)
e (1)
l (2)
l (3)
o (4)
(The output looks like this because the program expects an array, as you can see below).
This is the Javascript client, basically copy and paste of the Javascript quickstart.
var CLIENT_ID = 'CLIENT_ID_OMITTED';
var SCOPES = ['https://www.googleapis.com/auth/drive'];
function checkAuth() {
gapi.auth.authorize(
{
'client_id': CLIENT_ID,
'scope': SCOPES.join(' '),
'immediate': true
}, handleAuthResult
);
}
function handleAuthResult(authResult) {
var authorizeDiv = document.getElementById('authorize-div');
if (authResult && !authResult.error) {
authorizeDiv.style.display = 'none';
callScriptFunction();
} else {
authorizeDiv.style.display = 'inline';
}
}
function handleAuthClick(event) {
gapi.auth.authorize(
{client_id: CLIENT_ID, scope: SCOPES, immediate: false},
handleAuthResult
);
return false;
}
function callScriptFunction() {
var scriptId = "SCRIPT_ID_OMITTED";
var request = {
'function': 'findAndReplace'
};
var op = gapi.client.request({
'root': 'https://script.googleapis.com',
'path': 'v1/scripts/' + scriptId + ':run',
'method': 'POST',
'body': request
});
op.execute(function(resp) {
if (resp.error && resp.error.status) {
appendPre('Error calling API:');
appendPre(JSON.stringify(resp, null, 2));
} else if (resp.error) {
var error = resp.error.details[0];
appendPre('Script error message: ' + error.errorMessage);
if (error.scriptStackTraceElements) {
appendPre('Script error stacktrace:');
for (var i = 0; i < error.scriptStackTraceElements.length; i++) {
var trace = error.scriptStackTraceElements[i];
appendPre('\t' + trace.function + ':' + trace.lineNumber);
}
}
} else {
var folderSet = resp.response.result;
if (Object.keys(folderSet).length === 0) {
appendPre('No folders returned!');
} else {
appendPre('Folders under your root folder:');
Object.keys(folderSet).forEach(function(id){
appendPre('\t' + folderSet[id] + ' (' + id + ')');
});
}
}
});
}
function appendPre(message) {
var pre = document.getElementById('output');
var textContent = document.createTextNode(message + '\n');
pre.appendChild(textContent);
}
I made sure to create the permissions for DocumentApp, by copying it. Which when the copy is ran it gives me this prompt:
Which I obviously accept.
I create a new project on the script each time, enabling the Scripts API
and then creating an OAuth2 client,
copying the client id into the script. I also Deploy as API Executable on the script, and take the script id.
What am I missing? Why is the code producing a 401? And why is the GAS script then mysteriously returning Action now allowed in the editor only to work once its copied?
Thanks in advance.

After trying to do this for a couple nights I solved it straight after posted to SO... typical!
This post solved it
Basically, go to File > Project Properties and click the Scopes tab. From here you can see all the scopes required by your project.
This then must be added to the scopes array in your client-side javascript.
So my scopes now looks like this:
var SCOPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/documents'];
Why the GAS script then decide to return Action not allowed I'll never know, it really threw me off the trail.
Hopefully this helps some body else.

Related

Ouath2 in Google Apps Script

I've followed the guide on https://github.com/gsuitedevs/apps-script-oauth2 for setting up the Oauth2 token for a google services very closely but i'm still not able to get the access token to work.
The error I'm receiving is Access not granted or expired. (line 454, file "Service", project "OAuth2")
Note* My project has already been whitelisted for the GMB API library and I have enabled it in the API console console. I retrieved a ClientID and ClientSecret from my project aswell.
//Oauth 2 Flow Start
function getGmbService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth2.createService('gmb')
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId('...')
.setClientSecret('...')
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
.setCache(CacheService.getUserCache())
// Set the scopes to request (space-separated for Google services).
.setScope('https://www.googleapis.com/auth/business.manage')
// Below are Google-specific OAuth2 parameters.
// Sets the login hint, which will prevent the account chooser screen
// from being shown to users logged in with multiple accounts.
.setParam('login_hint', Session.getActiveUser().getEmail())
// Requests offline access.
.setParam('access_type', 'offline')
// Forces the approval prompt every time. This is useful for testing,
// but not desirable in a production application.
.setParam('approval_prompt', 'force');
}
function showSidebar() {
var gmbService = getGmbService();
if (!gmbService.hasAccess()) {
var authorizationUrl = gmbService.getAuthorizationUrl();
var template = HtmlService.createTemplate(
'Authorize. ' +
'Reopen the sidebar when the authorization is complete.');
template.authorizationUrl = authorizationUrl;
var page = template.evaluate();
DocumentApp.getUi().showSidebar(page);
} else {
Logger.log("No Access")
}
}
function authCallback(request) {
var gmbService = getGmbService();
var isAuthorized = gmbService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
//Oauth2 Flow Finish
function testRequest() {
var gmbService = getGmbService();
var payload = {
"pageSize": 5
}
var options = {
"headers": {
"Authorization": 'Bearer ' + gmbService.getAccessToken()
},
"method": 'GET',
"payload": payload,
"muteHttpExceptions": true
};
var response = UrlFetchApp.fetch("https://mybusiness.googleapis.com/v4/accounts",options)
Logger.log(response);
}

Telegram API returning HTML instead of JSON

I'm writing a telegram bot to report fail2ban bans. It's very simple and dirty, written hastily, but it can be used to report any message to a single telegram user:
var TelegramBot = require('node-telegram-bot-api');
var fs = require('fs');
var store = {
get: function (key) {
return fs.readFileSync(__dirname + '/' + key, { encoding: 'utf-8' });
},
set: function (key, value) {
fs.writeFileSync(__dirname + '/' + key, value, { encoding: 'utf-8' });
}
};
var token = store.get('token');
var args = process.argv.slice(2);
if (args.length == 0) {
console.error('No mode specified');
process.exit(0);
}
TelegramBot.prototype.unregisterText = function (regexp) {
for (var i = 0; i < bot.textRegexpCallbacks.length; ++i) {
if (bot.textRegexpCallbacks[i].regexp.toString() == regexp) {
bot.textRegexpCallbacks.splice(i, 1);
return;
}
}
};
fs.appendFileSync(__dirname + '/logs',
'[' + (new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')) + '] '
+ args.join(' ') + '\n',
{ encoding: 'utf-8' });
switch (args[0]) {
case 'setup':
var bot = new TelegramBot(token, { polling: true });
var step = 'none';
bot.onText(/\/setup/, function (msg, match) {
var fromId = msg.from.id;
step = 'setup-started';
bot.sendMessage(fromId, 'Starting setup. Please enter the verification key.');
bot.onText(/(.+)/, function (msg, match) {
if (step == 'setup-started') {
var key = match[1];
var verification = store.get('key');
if (key == verification) {
store.set('owner', msg.from.id);
step = 'verified';
bot.sendMessage(msg.from.id, 'Correct. Setup complete.');
} else {
step = 'none';
bot.unregisterText(/(.+)/);
bot.sendMessage(msg.from.id, 'Wrong. Setup aborted.');
}
}
});
});
break;
case 'report':
var bot = new TelegramBot(token, { polling: false });
var owner = store.get('owner');
var subject = args[1];
if (subject == 'message') {
var message = args.slice(2).join(' ');
bot.sendMessage(owner, message);
} else if (subject == 'file') {
var content = fs.readFileSync(args[2], { encoding: 'utf-8' });
bot.sendMessage(owner, content);
}
break;
default:
console.error('Unrecognized mode', args[0]);
break;
}
On my developer machine it works fine. I invoke:
node bot.js report message whatever message i want
And I correctly received "whatever message i want" on telegram. However, once I gitted it on my digitalocean vps, it no longer worked. It turns out the problem is with the telegram library:
Unhandled rejection Error: Error parsing Telegram response: <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Bots: An introduction for developers</title>
...
Which apparently returns an html page instead of json... I also tried to contact the same endpoint (api.telegram.org/bothash/sendMessage) with curl on my vps and it returned json (with an error message because i didnt send any parameters, but still json).
I cannot fathom why this happens. Any help?
It seems like either you don't have a file with token on your VPN or the token is incorrect.
You can check it by yourself:
When you make a request to api.telegram.org/{token}/sendMessage, and {token} is incorrect, it redirects you to this page, which responds with HTML you've mentioned in your question.
So you have to debug a behavior of your store.get and store.get functions along with files and tokens to make sure you are using a correct one.
Also, I'd recommend to run bot.getMe() before using any other Telegram API methods to ensure you specified a correct bot token.

Chrome App using Apps Script API

Ran into another road block today, on my path of writing a desktop chrome app for logging users project time.
What i am trying to do (and failing) is use the Apps Script API to access a google sheet that retains the information (project numbers) that i want to populate a drop down in my Chrome App UI.
Update:
I have reworded this as to get to the point and be a little clear on what my issue is.
What i cant seem to achieve is calling the Apps script function from my chrome app. I have read this Execution API but still cant seem to make the coloration. for some reasons i keep getting the "Uncaught ReferenceError: gapi is not defined" in my console.
What i have managed to do is have both the Apps Script and the Chrome App under the same project name in the Developers Console. not sure if its needed but thought it might help with only 1 Oauth2 request.
Is there something my thick head is missing?
Any help or ideas would be much appreciated.
This is my manifest.json
{
"manifest_version": 2,
"name": "TimeSheet",
"description": "Small and easy desktop app for entering time spent on project files",
"version": "0.1.0",
"icons": {
"128": "icon_128.png"
},
"app": {
"background": {
"scripts": ["background.js"]
}
},
"permissions": [
"identity",
"app.window.alwaysOnTop"
],
"oauth2": {
"client_id": "clientid.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/spreadsheets"
]
},
"key": "very long string"
}
This is the bit of Oauth2 code running in my main.js
//This code confirms Oauth2 for access to google drive and related files
window.onload = function(){
document.querySelector("#Oauth2").addEventListener("click", function(){
chrome.identity.getAuthToken({"interactive": true}, function(token){
console.log(token);
});
});
};
// ID of the script to call. Acquire this from the Apps Script editor,
// under Publish > Deploy as API executable.
var scriptId = "blah";
// Create execution request.
var request = {
'function': 'getProjectNumbers',
};
// Make the request.
var op = gapi.client.request({
'root': 'https://script.googleapis.com',
'path': 'v1/scripts/' + scriptId + ':run',
'method': 'POST',
'body': request
});
// Log the results of the request.
op.execute(function(resp) {
if (resp.error && resp.error.status) {
// The API encountered a problem before the script started executing.
console.log('Error calling API: ' + JSON.stringify(resp, null, 2));
} else if (resp.error) {
// The API executed, but the script returned an error.
var error = resp.error.details[0];
console.log('Script error! Message: ' + error.errorMessage);
if (error.scriptStackTraceElements) {
// There may not be a stacktrace if the script didn't start executing.
console.log('Script error stacktrace:');
for (var i = 0; i < error.scriptStackTraceElements.length; i++) {
var trace = error.scriptStackTraceElements[i];
console.log('\t' + trace.function + ':' + trace.lineNumber);
}
}
} else {
// Here, the function returns an array of strings.
var projectNumbers = resp.response.result;
console.log('Project numbers in spreadsheet:');
projectNumbers.forEach(function(name){
console.log(name);
});
}
});
And this is the apps script code:
var projectDatabaseKey = 'blah'; //Project Database Sheet spreadsheet key
var pprojectDatabaseSheet = 'Project Database'; //Project Database Sheet spreadsheet sheet
//Function to revieve data of project numbers for drop down list
function getProjectNumbers() {
return SpreadsheetApp
.openById(projectDatabaseKey).getSheetByName(projectDatabaseSheet)
.getRange("A2:A" + (SpreadsheetApp.openById(projectDatabaseKey).getSheetByName(projectDatabaseSheet).getLastRow()))
.getValues();
}
I am just really unsure how to use the Oauth2 token and how to apply it to the apps script.
UPDATE
Ok i have tried to call an apps script in a different manor, What i am trying today is using the gapi-chrome-apps.js library to do the oauth2 work.
Now my problem is i get this error, that could be a range of things i am guessing:
POST https://www.googleapis.com/v1/scripts/blahblah:run 404 ()
gapi.client.request # VM80 gapi-chrome-apps.js:105
getSheetsList # gapiCallback.js:17
(anonymous function) # gapiCallback.js:49
callbackWrapper # VM80 gapi-chrome-apps.js:68
target.(anonymous function) # extensions::SafeBuiltins:19
safeCallbackApply # extensions::sendRequest:21
handleResponse # extensions::sendRequest:72
And this Error, that comes from the gapi-chrome-apps.js script:
Uncaught SyntaxError: Unexpected token N in JSON at position 0
Really not sure what is causing this, here is my updated code:
//get listof sheets in spreadsheet
function getSheetsList(){
var scriptId = "blahblah";
// Initialize parameters for function call.
var sheetId = "blahblah";
// Create execution request.
var requests = {
'function': 'getSheetNames',
'parameters': [sheetId],
'devMode': true // Optional.
};
// Make the request.
gapi.client.request({
'root': 'https://script.googleapis.com',
'path': 'v1/scripts/' + scriptId + ':run',
'method': 'POST',
'body': requests,
'callback': printSheetsList
});
}
// Log the results of the request.
function printSheetsList(resp) {
if (resp.error && resp.error.status) {
// The API encountered a problem before the script started executing.
console.log('Error calling API: ' + JSON.stringify(resp, null, 2));
} else if (resp.error) {
// The API executed, but the script returned an error.
var error = resp.error.details[0];
console.log('Script error! Message: ' + error.errorMessage);
} else {
// Here, the function returns an array of strings.
var sheetNames = resp.response.result;
console.log('Sheet names in spreadsheet:');
sheetNames.forEach(function(name){
console.log(name);
});
}
}
//Prompts the user for authorization and then proceeds to
function authorize(params, callback) {
gapi.auth.authorize(params, function(accessToken) {
if (!accessToken) {
console.log("Error getting authorization");
} else {
callback();
}
});
}
function gapiIsLoaded() {
var params = { 'immediate': true };
if (!(chrome && chrome.app && chrome.app.runtime)) {
params.scope = "https://www.googleapis.com/auth/drive";
params.client_id = "blahblah";
gapi.auth.init(authorize.bind(null, params, getSheetsList));
} else {
authorize(params, getSheetsList);
}
}
Using traditional GAPI will not work, since it dynamically loads more external scripts and Apps are not allowed to do that.
One possible solution is to run GAPI code in a sandboxed page, which can overcome the remote code restriction. This, however, is cumbersome as you'll need to pass data back and forth using postMessage.
Another way is to try and use Google-provided library gapi-chrome-apps.js, that works in Chrome apps (and uses chrome.identity to manage OAuth) - but please note this comment:
This library is likely not suitable for use without additional modifications.
According to your post, you are simply not defining gapi. You can load it like this
jQuery.getScript( "https://apis.google.com/js/api.js", onApiLoad );
where onApiLoad is the function you would like to call when the gapi is loaded.
For your code, I would wrap the following code in a function like this:
function onApiLoad() {
// Make the request.
var op = gapi.client.request({
'root': 'https://script.googleapis.com',
'path': 'v1/scripts/' + scriptId + ':run',
'method': 'POST',
'body': request
});
// Log the results of the request.
op.execute(function(resp) {
if (resp.error && resp.error.status) {
// The API encountered a problem before the script started executing.
console.log('Error calling API: ' + JSON.stringify(resp, null, 2));
} else if (resp.error) {
// The API executed, but the script returned an error.
var error = resp.error.details[0];
console.log('Script error! Message: ' + error.errorMessage);
if (error.scriptStackTraceElements) {
// There may not be a stacktrace if the script didn't start executing.
console.log('Script error stacktrace:');
for (var i = 0; i < error.scriptStackTraceElements.length; i++) {
var trace = error.scriptStackTraceElements[i];
console.log('\t' + trace.function + ':' + trace.lineNumber);
}
}
} else {
// Here, the function returns an array of strings.
var projectNumbers = resp.response.result;
console.log('Project numbers in spreadsheet:');
projectNumbers.forEach(function(name){
console.log(name);
});
}
});
}
Edit: no jQuery, only pure JS
Thanks to the second answer in this post, the below code is a pure JS implementation of $.getScript(). It includes a callback, so the code snippet below should work, assuming you wrap your code in a function as described above.
function getScript(source, callback) {
var script = document.createElement('script');
var prior = document.getElementsByTagName('script')[0];
script.async = 1;
prior.parentNode.insertBefore(script, prior);
script.onload = script.onreadystatechange = function( _, isAbort ) {
if(isAbort || !script.readyState || /loaded|complete/.test(script.readyState) ) {
script.onload = script.onreadystatechange = null;
script = undefined;
if(!isAbort) { if(callback) callback(); }
}
};
script.src = source;
}
getScript("https://apis.google.com/js/api.js", onApiLoad);

ADAL JS not attaching user token while invoking WebApi

I am using ADAL JS for authenticating the users against Azure AD. And as I am new to ADAL JS, I started reading with following articles, which I find very informative:
Introducing ADAL JS v1
ADAL JavaScript and AngularJS – Deep Dive
After reading the articles, I had the impression that ADAL JS intercepts the service calls and if the service url is registered as one of the endpoint in AuthenticationContext configuration, it attaches the JWT token as Authentication Bearer information.
However, I found the same is not happening in my case. And after some digging, it seemed to me that it is only possible, if adal-angular counter part is also used, which I am not using currently, simply because my web application is not based on Angular.
Please let me know if my understanding is correct or not. If I need to add the bearer information explicitly, the same can be done, but I am more concerned whether I am missing some out-of-the-box facility or not.
Additional Details: My present configuration looks like following:
private endpoints: any = {
"https://myhost/api": "here_goes_client_id"
}
...
private config: any;
private authContext: any = undefined;
....
this.config = {
tenant: "my_tenant.onmicrosoft.com",
clientId: "client_id_of_app_in_tenant_ad",
postLogoutRedirectUri: window.location.origin,
cacheLocation: "sessionStorage",
endpoints: this.endpoints
};
this.authContext = new (window["AuthenticationContext"])(this.config);
Also on server-side (WebApi), Authentication configuration (Startup.Auth) is as follows:
public void ConfigureOAuth(IAppBuilder app, HttpConfiguration httpConfig)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = "my_tenant.onmicrosoft.com",
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = "client_id_of_app_in_tenant_ad"
}
});
}
However, the Authorization is always null in request.Headers.
UPDATE: It seems that the same applies for auto-renewal of tokens as well; when used in conjunction with adal-angular, the renewal of token works seamlessly by calling AuthenticationContext.acquireToken(resource, callback) under the hood. Please correct me if I am wrong.
After reading the articles, I had the impression that ADAL JS intercepts the service calls and if the service url is registered as one of the endpoint in AuthenticationContext configuration, it attaches the JWT token as Authentication Bearer information.
This will work only if your application is angular based. As you mentioned, the logic for this lives in adal-angular.
If, however, you want to stick to pure JS, you will not get the automatic "get-access-token-and-attach-it-to-header" support. You can use acquireToken(resource, callback api to get a token for the endpoint. But you will have to do some work in the controller that is sending the request to the api.
This might give you some idea: https://github.com/Azure-Samples/active-directory-javascript-singlepageapp-dotnet-webapi/blob/master/TodoSPA/App/Scripts/Ctrls/todoListCtrl.js. This sample does not uses angular.
ADAL.JS is incompatible with v2.0 implicit flow. I could not get it working since I set my project up recently and don't think projects are backwards compatible.
This was very confusing and took me a long time to figure out that I was mixing up the versions, and can't use ADAL.JS with v2.0. Once I removed it, things went much smoother, just did a couple of XHR requests and a popup window, no magic actually required!
Here is code for v2:
function testNoADAL() {
var clientId = "..guid..";
var redirectUrl = "..your one.."
var authServer = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?";
var responseType = "token";
var stateParam = Math.random() * new Date().getTime();
var authUrl = authServer +
"response_type=" + encodeURI(responseType) +
"&client_id=" + encodeURI(clientId) +
"&scope=" + encodeURI("https://outlook.office.com/Mail.ReadWrite") +
"&redirect_uri=" + encodeURI(redirectUrl) +
"&state=" + stateParam;
var popupWindow = window.open(authUrl, "Login", 'width=' + 300 + ', height=' + 600 + ', top=' + 10 + ', left=' + 10 + ',location=no,toolbar=yes');
if (popupWindow.focus) {
popupWindow.focus();
}
}
Note: redirectUrl will appear in popup window, needs to have code in it to pass location hash, such as this:
<script>window.opener.processMicrosoftAuthResultUrl(location.hash);window.close();</script>
function processMicrosoftAuthResultUrl(hash) {
if (hash.indexOf("#") == 0) {
hash = hash.substr(1);
}
var obj = getUrlParameters(hash);
if (obj.error) {
if (obj.error == "invalid_resource") {
errorDialog("Your Office 365 needs to be configured to enable access to Outlook Mail.");
} else {
errorDialog("ADAL: " + obj.error_description);
}
} else {
if (obj.access_token) {
console.log("ADAL got access token!");
var token = obj.access_token;
var url = "https://outlook.office.com/api/v2.0/me/MailFolders/Inbox/messages";
$.ajax({
type: "GET",
url: url,
headers: {
'Authorization': 'Bearer ' + token,
},
}).done(function (data) {
console.log("got data!", data);
var message = "Your latest email is: " + data.value[0].Subject + " from " + data.value[0].From.EmailAddress.Name+ " on " + df_FmtDateTime(new Date(data.value[0].ReceivedDateTime));
alertDialog(message);
}).fail(function () {
console.error('Error getting todo list data')
});
}
}
}
function getUrlParameters(url) {
// get querystring and turn it into an object
if (!url) return {};
if (url.indexOf("?") > -1) {
url = url.split("?")[1];
}
if (url.indexOf("#") > -1) {
url = url.split("#")[0];
}
if (!url) return {};
url = url.split('&')
var b = {};
for (var i = 0; i < url.length; ++i) {
var p = url[i].split('=', 2);
if (p.length == 1) {
b[p[0]] = "";
} else {
b[decodeURIComponent(p[0])] = decodeURIComponent(p[1].replace(/\+/g, " "));
}
}
return b;
}

Server Side Logging Of Client Side Javascript Crashes

I have a large complex web app with thousands of lines of Javascript. There is a small set of intermittent Javascript bugs that are report by users.
I think these are epiphenomena of race conditions - something has not initialised correctly and the Javascript crashes causing 'down stream' js not to run.
Is there anyway to get Javascript execution crashes to log back server side?
All the js logging libraries like Blackbird and Log4JavaScript are client-side only.
I have written a remote error logging function using window.onerror as suggested by #pimvdb
Err = {};
Err.Remoterr = {};
Err.Remoterr.onerror = function (msg, errorfileurl, lineno) {
var jsonstring, response, pageurl, cookies;
// Get some user input
response = prompt("There has been an error. " +
"It has been logged and will be investigated.",
"Put in comments (and e-mail or phone number for" +
" response.)");
// get some context of where and how the error occured
// to make debugging easier
pageurl = window.location.href;
cookies = document.cookie;
// Make the json message we are going to post
// Could use JSON.stringify() here if you are sure that
// JSON will have run when the error occurs
// http://www.JSON.org/js.html
jsonstring = "{\"set\": {\"jserr\": " +
"{\"msg\": \"" + msg + "\", " +
"\"errorfileurl\": \"" + errorfileurl + "\", " +
"\"pageurl\": \"" + pageurl + "\", " +
"\"cookies\": \"" + cookies + "\", " +
"\"lineno\": \"" + lineno + "\", " +
"\"response\": \"" + response + "\"}}}";
// Use the jquery cross-browser post
// http://api.jquery.com/jQuery.post/
// this assumes that no errors happen before jquery has initialised
$.post("?jserr", jsonstring, null, "json");
// I don't want the page to 'pretend' to work
// so I am going to return 'false' here
// Returning 'true' will clear the error in the browser
return false;
};
window.onerror = Err.Remoterr.onerror;
I deploy this between the head and body tags of the webpage.
You will want to change the JSON and the URL that you post it to depending on how you are going to log the data server side.
Take a look at https://log4sure.com (disclosure: I created it) - but it is really useful, check it out and decide for yourself. It allows you to log errors/event and also lets you create your custom log table. It also allows you to monitor your logs real-time. And the best part, its free.
You can also use bower to install it, use bower install log4sure
The set up code is really easy too:
// setup
var _logServer;
(function() {
var ls = document.createElement('script');
ls.type = 'text/javascript';
ls.async = true;
ls.src = 'https://log4sure.com/ScriptsExt/log4sure.min.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ls, s);
ls.onload = function() {
// use your token here.
_logServer = new LogServer("use-your-token-here");
};
})();
// example for logging text
_logServer.logText("your log message goes here.")
//example for logging error
divide = function(numerator, divisor) {
try {
if (parseFloat(value) && parseFloat(divisor)) {
throw new TypeError("Invalid input", "myfile.js", 12, {
value: value,
divisor: divisor
});
} else {
if (divisor == 0) {
throw new RangeError("Divide by 0", "myfile.js", 15, {
value: value,
divisor: divisor
});
}
}
} catch (e) {
_logServer.logError(e.name, e.message, e.stack);
}
}
// another use of logError in window.onerror
// must be careful with window.onerror as you might be overwriting some one else's window.onerror functionality
// also someone else can overwrite window.onerror.
window.onerror = function(msg, url, line, column, err) {
// may want to check if url belongs to your javascript file
var data = {
url: url,
line: line,
column: column,
}
_logServer.logError(err.name, err.message, err.stack, data);
};
// example for custom logs
var foo = "some variable value";
var bar = "another variable value";
var flag = "false";
var temp = "yet another variable value";
_logServer.log(foo, bar, flag, temp);

Categories

Resources