3

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);
  }
}
9
  • Welcome to stackoverflow. see how to write a good stackoverflow question in particular, show us your attempt at calling the apps script execution API and where it fails. Commented Apr 23, 2016 at 15:32
  • I have added in the request i am making the apps script but not sure how to make the connection with the Oauth2, i also get "Uncaught ReferenceError: gapi is not defined" i am guessing that is cause of me missing a step potentially with the connection between the Oauth2 and the Apps Script. I am a pretty big noob at these chrome apps. alot more steps than i am used to with apps script. Commented Apr 24, 2016 at 0:58
  • I have tried a few different things and it looks like my issue is with the .gapi and not able to load the client library api due to the security policy. Will have to try a work around. Commented Apr 24, 2016 at 11:24
  • i dont see where you are adding the access token. have you followed the examples for calling execution api? why not using the client libraries? developers.google.com/apps-script/guides/rest/… Commented Apr 24, 2016 at 14:25
  • 1
    try loading the library from a webpage and see what js file it finally downloads. you need to include that file locally in the extension or make the correct POST call like yours but with the access token. look at other google apis that make authebticated calls director with post/get and imitate the auth part (bearer etc) Commented Apr 25, 2016 at 1:25

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Hi Xan's, i have been trying to use the gapi-chrome-apps.js but you are totally correct about the additional modification required. It still requires several callback functions but makes more sense because it requires the scopes and client id Something i think has to be part of the request process. I am going to try several options with this over the weekend and hopefully make some ground. For some reason i thought because they are all google services they would work easily together. What a fool i was lol
GAPI was designed to dynamically load remote code. Apps were designed to severely limit external dependencies. You can see where this trainwreck is going.
Is there a way to just use the identity api do you think?. This works a treat for getting the oauth2 for the app its self but i have to idea at whats involved to use it for a function like passing the apps script oauth2. On the plus side of all this head banging is i am learning more :)
0

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);

7 Comments

Hi Eric, thanks for you answer, only problem i seem to have now is loading jQuery, "jQuery is not defined" from what i gather loading it via the manifest "content_scripts" is not allowed in packaged apps :(
@David Added solution using pure JS, I hope it helps solve your problem
Thanks for helping Eric, This definitely is shining a glimmer of hope. The only issue is CSP wont allow an external source. main.js:39 Refused to load the script 'https://apis.google.com/js/api.js' because it violates the following Content Security Policy directive: "default-src 'self' blob: filesystem: chrome-extension-resource:" I found on github a .js file for the gapi to save as a local copy but when referancing that i get this: gapiIsLoaded callback function must be defined prior to loading gapi-chrome-apps.js that comes from the gapi local file. trying to work my google magic atm.
I copied the api and loaded the api as a local script getScript("api.js?onload=handleClientLoad", onApiLoad); now i get Uncaught TypeError: Cannot read property 'request' of undefined from the var op = gapi.client.request({ line. its seems to be one brick wall after the next. i also tried putting the page in sandbox and that gave me this same error so at least its getting to this far in the code, just need it to execute the full client request.
Downvote: Chrome Apps are not allowed to load external scripts, so all of this is not useful.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.