0

I have a Web App I want to use as a library for another script I am writing and it will eventually be semi-public. I have made most of my relevant internal functions private the only way I know how - by appending an underscore to the function name.

However, this doesn't work for special reserved function names such as onOpen or onEdit. I know it's quite nitpicky of me, as anyone attempting to run these functions will just get errors anyway, but just curious if there is a way to specify these functions as private so they don't appear in the calling editor's auto-complete?

T Nguyen
  • 3,309
  • 2
  • 31
  • 48
  • `so they don't appear in the calling editor's auto-complete?` You mean `so they don't appear in the calling editor's function list ?` right? – TheMaster Sep 19 '22 at 08:54
  • Have you considered using classes for defining the private functions? Check this [answer](https://stackoverflow.com/a/68045160/14271633) – Emel Sep 19 '22 at 11:39

2 Answers2

2

If you are using the default runtime, V8, instead of using

function onEdit(){

}

use

const onEdit = () => {

};
  • Apply the same for any other simple trigger that you want to hide, such as onOpen, onSelectionChange, doGet and doPost.
  • Instead of const you might use let, but do not use var.
  • Instead of () => {} you might use function() {} or the name of a private function e.g. myFunction_ (note that the parenthesis aren't included).
tom
  • 21,844
  • 6
  • 43
  • 36
Rubén
  • 34,714
  • 9
  • 70
  • 166
1

@Rubén suggests to use const, which should work – the docs say only enumerable global properties are visible to library users.

However, for me functions declared using const (or let) currently still appear in the autocomplete list. This is probably a bug in the editor, because their value is undefined.

As a workaround until that bug is fixed, it is possible to define a non-enumerable property like this:

Object.defineProperty(globalThis, "onOpen", {
  enumerable: false,
  value: function() {
    ...
  },
});

For me this hides the function from the autocomplete list and prevents the app from calling the function. (Contrast with globalThis.onOpen = function() { ... };, which currently hides the function from the autocomplete list but allows the app to call the function.)



The question is about hiding functions defined in a library from apps that use the library. I originally misinterpreted the question and thought it was about hiding functions defined in a web app from being called from the client side using google.script.run. I am leaving the answer for the misinterpreted version of the question below in case it is useful to future readers.



How can I protect functions from being called from the client side?

Background: By default, functions declared in a web app can be called from the client side (web browser) using google.script.run.myFunction(). This feature is very powerful and convenient, but can have unexpected security implications.

Here are some ways to protect a function from being called from the client side:

A. Make the function private by appending "_" to its name (docs), e.g.

function myFunction_() {
  ...
}

B. Make the function a property of an object, e.g.

var obj = {
  myFunction: function() {
    ...
  }
};

(This function can be called on the server side using obj.myFunction() and cannot be called from the client side; ref.)

C. Write your code inside an immediately-invoked function expression:

(function() {
  function myFunction() {
    ...
  }

  globalThis.onOpen = function() {
    ...
  };
})();

(With this approach, special functions such as onOpen will need to be declared as shown, using the syntax globalThis.onOpen = ... rather than function onOpen() { ... }, in order expose them publicly while allowing them to call the private functions.)


Ways that don't work:

Declaring a web app function with const does not prevent it from being called from the client side, so this cannot be used to protect functions.

Declaring a web app function using globalThis.myFunction = () => { ... }; will prevent its name from being sent to the client (so running google.script.run.myFunction() in the web console will fail), however functions declared this way can still be called from the client side – it just requires a little more effort. So this is not a good security measure (see "security through obscurity"); also this behaviour is undocumented and may change in future versions of Google Apps Script.



How can I protect special functions such as onOpen from being called from the client side?

There are some special functions such as onOpen which must have that exact name. The approaches above (A, B, C) are not suitable for these special functions.

As far as I know there is no general-purpose way to define these special functions in a way that prevents them from being called from the client side.

However, there is a trick that can be used in some cases to detect whether the function was triggered normally or called from the client side. The idea is to look for properties on the event object that can't be created using google.script.run. Example:

function onOpen(e) {
  if (!e || !e.source || typeof e.source.getId !== "function") {
    throw new Error("Invalid argument");
  }
  ...
}

This code verifies that e.source is really a Document object by checking that it has a getId() function. Since the arguments passed to google.script.run are serialised as JSON (which doesn't support functions), it should be impossible for a call from the client side to spoof this function.

(A related idea is to check the prototype of e.source, however that doesn't work because Document isn't a real class – documents are represented as plain objects.)


An attempt that failed:

I tried to use new Error().stack to distinguish between legitimate calls and calls from the client side, but the value was identical in both cases (also when I used a custom getter to intercept the function lookup).

tom
  • 21,844
  • 6
  • 43
  • 36