Passing a javascript object to an apps script web app

I’m trying to pass javascript objects to my apps script webapp, but I can’t figure out how to pass objects that can’t be stringified (like a script lock or blob). For example:

Client-side gs:

function testEmail() {
  var attachment = DriveApp.getFileById("file id");
  var emailBody = 'email body content';

  //send email
  sendEmailFromClient('sendTo@example.com', "test", emailBody, attachment, 'replyTo@example.com');
}

function sendEmailFromClient(sendTo, title, body, attachments, replyTo) {
  var scriptLock = LockService.getScriptLock();
  if(!scriptLock.tryLock(8000)){
    return;
  }

  var formData = {
    'sendTo': sendTo,
    'title': title,
    'body' : body,
    'attachments': attachments,
    'replyTo' : replyTo,
    'scriptLock' : scriptLock
  };

  var options = {
    'method': 'post',
    'payload': formData
  };
      
  Logger.log(UrlFetchApp.fetch('web app url', options).getContentText());
}

Web App gs:

function doPost(event) {
  var parameters = event.parameter;
  try {
    var result = Library['sendEmail'].apply(this, [parameters.sendTo, parameters.title, parameters.body, parameters.attachments, parameters.replyTo]);
    parameters.scriptLock.releaseLock();
  } catch (error) {
    Library.troubleshooting(error);
    return ContentService.createTextOutput(error);
  }
  return ContentService.createTextOutput(result);
}

Library gs:

function sendEmail(sendTo, title, body, attachments, replyTo) {
  if (replyTo == undefined) {
    replyTo = gv_accountingEmail;
  }
  if (attachments == undefined) {
    attachments = [];
  }
  var success = false;
    try {
      GmailApp.sendEmail(
        sendTo.valid,
        title,
        "html content only",
        {
          attachments: attachments,
          replyTo: replyTo,
          name: gv_systemBrandName,
          htmlBody: body
        }
      );
      success = true;
    } catch (error) {
      success = error;
    } finally {
      return success;
    }
}

The client grabs the file from their drive, and then sends that info to the web app which then accesses the library to complete the action of sending the email. The web app has special privileges to the library that the client doesn’t have which is why I need to send this type of data to it.

If I run the above code from the client, the error that gets logged is “TypeError: parameters.scriptLock.releaseLock is not a function”

If I comment out // parameters.scriptLock.releaseLock();and run it again, I get “Exception: Invalid argument: attachments”

If I turn the attachment into a blob via attachment.getBlob(), then the email sends successfully, but there is nothing attached.

How do we pass these type of objects to web apps?

Thanks!

Answer

Try this:

  1. Encode the file first to base-64 web-safe
  2. Send it in your web app using post request
  3. Decode the base-64 web-safe to UTF-8 byte array
  4. Convert it to blob.

Example:

Client Side:

function myFunction(){
  var url = "webapp url";
  var file = DriveApp.getFileById("file id")
  var headers = {
    "Authorization": "Bearer " + ScriptApp.getOAuthToken()
  };
  var fileName = file.getName();
  var contentType = file.getBlob().getContentType();
  var formData = {
    fileName: fileName,
    contentType : contentType,
    file: Utilities.base64EncodeWebSafe(file.getBlob().getBytes())
  };

  var options = {
    'method' : 'post',
    'payload' : formData,
    'headers': headers,
  };

  UrlFetchApp.fetch(url, options);
}

WebApp:

function doPost(e) {
  var file = Utilities.newBlob(Utilities.base64DecodeWebSafe(e.parameter.file),e.parameter.contentType,e.parameter.fileName)
  Library['sendEmail'].apply(this, [file]);
  return ContentService.createTextOutput("test");
}

Library:

function sendEmail(attachments) {
  GmailApp.sendEmail(
    "recipient email address here",
    "Test",
    "html content only",
    {
      attachments: [attachments],
    }
  );
}

Output:

enter image description here

Reference: