Home > Back-end >  How to iterate through all properties/functions of a global object(library) in v8?
How to iterate through all properties/functions of a global object(library) in v8?

Time:11-19

Google apps script provides a library feature, where if you include the project key, a library is added as global object. I'm looking to iterate all functions of a added library. This used to be possible in engine with a for...in loop. But I'm unable to iterate through any of the properties of to a library in engine.

The documentation says

In the V8 runtime, a project and its libraries are run in different execution contexts and hence have different globals and prototype chains.

Can anyone explain how this object is created or how to access all it's properties?

Project A:

function testLib(prop = 'main') {
  const isEnumerable = MyLibrary.propertyIsEnumerable(prop);
  const isOwnProperty = MyLibrary.hasOwnProperty(prop);
  console.info({ isEnumerable, isOwnProperty }); // { isEnumerable: false, isOwnProperty: true }
  console.info(prop in MyLibrary);//true
  for (const property in MyLibrary) {
    //loop doesn't start
    console.info(property);
  }
  console.info(Object.getOwnPropertyDescriptor(MyLibrary, prop)); //logs expected  data:
  /*{ value: [Function: main],
  writable: true,
  enumerable: false,
  configurable: true }*/
  console.log(Object.getOwnPropertyDescriptors(MyLibrary)); //actual: {} Expected: array of all functions including `main`
}

function testPropDescriptors() {
  const obj = { prop1: 1, b: 2 };
  console.log(Object.getOwnPropertyDescriptors(obj)); //logs expected data
  /*{prop1: { value: 1, writable: true, enumerable: true, configurable: true },
  b: { value: 2, writable: true, enumerable: true, configurable: true } }*/
}

MyLibrary(Project B):

function main(){}
function onEdit(){}

To reproduce,

CodePudding user response:

Issue and workaround:

I had been looking for the methods for directly retrieving the properties and functions in the library side from the client-side under enabling V8. But unfortunately, I cannot still find it. So in my case, I use 2 workarounds.

  1. Retrieve all scripts using Apps Script API and/or Drive API.

  2. Wrapping the properties and functions in an object.

By the above workarounds, the properties and functions on the library side can be retrieved from the client-side.

Workaround 1:

In this workaround, all scripts on the library side are retrieved using Apps Script API and Drive API.

Sample script 1:

In this sample, Apps Script API is used. So, when you use this script, please link Google Cloud Platform Project to Google Apps Script Project. Ref And, please enable Apps Script API at API console.

const projectIdOflibrary = "###"; // Please set the project ID of the library.

const url = `https://script.googleapis.com/v1/projects/${projectIdOflibrary}/content`;
const res = UrlFetchApp.fetch(url, {headers: {authorization: "Bearer "   ScriptApp.getOAuthToken()}});
const obj = JSON.parse(res.getContentText())
const functions = obj.files.reduce((ar, o) => {
  if (o.name != "appsscript") ar.push(o);
  return ar;
}, []);
console.log(functions)
// console.log(functions.map(({functionSet}) => functionSet.values)) // When you want to see the function names, you can use this line.
  • When this script is used for your library script, console.log(functions.flatMap(({functionSet}) => functionSet.values)) returns [ { name: 'main' }, { name: 'onEdit' } ].

  • In this case, even when the library is the container-bound script of Google Docs, this script can work.

Sample script 2:

In this sample, Drive API is used. So, when you use this script, please enable Drive API at Advanced Google services.

const projectIdOflibrary = "###"; // Please set the project ID of the library.

const url = `https://www.googleapis.com/drive/v3/files/${projectIdOflibrary}/export?mimeType=application/vnd.google-apps.script+json`;
const res = UrlFetchApp.fetch(url, {headers: {authorization: "Bearer "   ScriptApp.getOAuthToken()}});
const obj = JSON.parse(res.getContentText())
const functions = obj.files.reduce((ar, o) => {
  if (o.name != "appsscript") ar.push(o.source);
  return ar;
}, []);
console.log(functions)
  • When this script is used for your library script, console.log(functions) returns [ 'function main(){}\nfunction onEdit(){}\n' ].

  • In this case, the function names are not automatically parsed. But Google Apps Script Project is not required to be linked with Google Cloud Platform Project. But, when the library is the container-bound script of Google Docs, this script cannot be used. In this case, when the library is only the standalone type, this script can be used. Please be careful about this.

Workaround 2:

In this workaround, the properties and functions in the library side are wrapped with an object.

Sample script: Library side

var sample1 = {
  method1: function() {},
  method2: function() {}
};


var sample2 = class sample2 {
  constructor() {
    this.sample = "sample";
  }

  method1() {
    return this.sample;
  }
}

Sample script: Client side

function myFunction() {
  const res1 = MyLibrary.sample1;
  console.log(res1)

  const res2 = Object.getOwnPropertyNames(MyLibrary.sample2.prototype);
  console.log(res2)
}
  • In this case, console.log(res1) and console.log(res2) return { method1: [Function: method1], method2: [Function: method2] } and [ 'constructor', 'method1' ], respectively.

References:

CodePudding user response:

Google Apps Script is a custom embedding of V8, so it uses the V8 C API to create "magic" objects. The end result is similar to a Proxy: if you know a property's name, you can retrieve it; but there's no built-in way to enumerate available functions in a library. (I have no idea why it's designed that way.)

If you control the library in question, one possible workaround is to export the list of functions from there:

// MyLibrary:
Object.defineProperty(this, "global", {value: this});
function getExports() {
  let result = [];
  let descriptors = Object.getOwnPropertyDescriptors(global);
  for (let p in descriptors) {
    if (descriptors[p].enumerable) result.push(p);
  }
  return result;
}

// main project:
console.log(MyLibrary.getExports());

(If you don't control the library, @Tanaike's answer provides some suggestions.)

  • Related