1
votes

I'm trying to take the Google Apps Script Node.js tutorial and adapt it to be a generic Google Apps Script function caller. Here is what I came up with

var fs = require('fs');
var path=require('path');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');

var SCOPES = ['https://www.googleapis.com/auth/drive'];
var TOKEN_DIR = path.dirname(path.dirname(path.dirname(process.execPath))) + '/other/credentials/';
var TOKEN_PATH = TOKEN_DIR + 'script-nodejs-quickstart.json';
//I ADDED THIS FUNCTION WITH THE PARAMETERS 
exports.run=function (myfunction,myparam){
// Load client secrets from a local file.
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
  if (err) {
    console.log('Error loading client secret file: ' + err);
    return; 
  }
  // Authorize a client with the loaded credentials, then call the
  // Google Apps Script Execution API.
  console.log('one ran');
  //TRANSFER THE PARAMETERS TO authorize
  authorize(JSON.parse(content), callFunction,myfunction,myparam);
});
}

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 *
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, callback,myfunction,myparam) {
  var clientSecret = credentials.installed.client_secret;
  var clientId = credentials.installed.client_id;
  var redirectUrl = credentials.installed.redirect_uris[0];
  var auth = new googleAuth();
  var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, function(err, token) {
    if (err) {
      getNewToken(oauth2Client, callback);
    } else {
      oauth2Client.credentials = JSON.parse(token);
      console.log('now two');
      //TRANSFER THE PARAMETES TO callFunction
      callback(oauth2Client,myfunction,myparam);
    }
  });
}

/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 *
 * @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback to call with the authorized
 *     client.
 */
function getNewToken(oauth2Client, callback) {
  var authUrl = oauth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES
  });
  console.log('Authorize this app by visiting this url: \n', authUrl);
  var rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  rl.question('Enter the code from that page here: ', function(code) {
    rl.close();
    oauth2Client.getToken(code, function(err, token) {
      if (err) {
        console.log('Error while trying to retrieve access token', err);
        return;
      }
      oauth2Client.credentials = token;
      storeToken(token);
      callback(oauth2Client);
    });
  });
}

/**
 * Store token to disk be used in later program executions.
 *
 * @param {Object} token The token to store to disk.
 */
function storeToken(token) {
  try {
    fs.mkdirSync(TOKEN_DIR);
  } catch (err) {
    if (err.code != 'EEXIST') {
      throw err;
    }
  }
  fs.writeFile(TOKEN_PATH, JSON.stringify(token));
  console.log('Token stored to ' + TOKEN_PATH);
}

/**
 * Call an Apps Script function to list the folders in the user's root
 * Drive folder.
 *
 * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
 */
function callFunction(auth,myfunction,myparam) {
    console.log('now we are at the last');
  var scriptId = 'my apps script id';
  var script = google.script('v1');

  // Make the API request. The request object is included here as 'resource'.
  //PARAMETERS APPLIED 
  script.scripts.run({
    auth: auth,
    resource: {
      function: myfunction,
      parameters: myparam,
      devMode:true
    },
    scriptId: scriptId
  }, function(err, resp) {
  console.log(myfunction,myparam,auth);
    if (err) {
      // The API encountered a problem before the script started executing.
      console.log('The API returned an error: ' + err);
      return;
    }
    if (resp.error) {
      // The API executed, but the script returned an error.

      // Extract the first (and only) set of error details. The values of this
      // object are the script's 'errorMessage' and 'errorType', and an array
      // of stack trace elements.
      var error = resp.error.details[0];
      console.log('Script error message: ' + error.errorMessage);
      console.log('Script error stacktrace:');

      if (error.scriptStackTraceElements) {
        // There may not be a stacktrace if the script didn't start executing.
        for (var i = 0; i < error.scriptStackTraceElements.length; i++) {
          var trace = error.scriptStackTraceElements[i];
          console.log('\t%s: %s', trace.function, trace.lineNumber);
        }
      }
    } else {
      exports.result(resp.response.result);     
    }

  });
}
exports.result=function(result){
    return result;
}

Everything in CAPS is my commenting. My problem is that every time I test it out it returns:

The API returned an error: Error: ScriptError

Permissions for the api and the app script do line up. so it isnt a permissions error. It doesn't give me any other details. Does anyone know what is causing this error?

3
I have the same problem. It works as long as my server script does not contain a SpreadsheetApp method call, even in a function that isn't invoked by the API request. If it does I get this error. My client code contains both scopes - 'googleapis.com/auth/drive' and 'spreadsheets.google.com/feeds'. And I have generated a token using both of them. So it seems I have done everything I need to security-wise related to the spreadsheet API. - Dan Cancro
So is it a scopes error then? - Binvention
It isn't a scope error because my scopes line up. - Binvention
I really don't know. Probably not. I just can't think of anything else that would explain why my API works without the SpreadsheetApp call and doesn't work with it. I can run the script just fine from the script editor, so there aren't any script errors. It has to do with it being executed as an API. Maybe we have different problems. Searches for "ScriptError" don't turn up much. - Dan Cancro

3 Answers

1
votes

It is likely you have a scope problem. I found that the correct scope to use for spreadhseets is https://www.googleapis.com/auth/spreadsheets. This can be seen in the script editor under File -> Project properties -> Scopes.

0
votes

This is due to an earlier problem with how the Node.js library parsed responses, which was fixed in https://github.com/google/google-auth-library-nodejs/commit/323b794f3aa8bf7291041620155451c3f3b2b4d2. If you re-install google-auth-library then you should see the full error details.

0
votes

I have bumped into the same issue and solved by adding proper scopes to my script.

Make sure you have exact same list of scopes in your node.js script with the ones in apps script by looking at "Files > Project properties > Scopes".

This is not mentioned in Quick Start, but following document does describe it: https://developers.google.com/apps-script/guides/rest/api#general_procedure