I'm trying to create a class with some fields on Google Apps Scripts. I can't even save the file. From what I understand of this SO answer, I'm using the correct syntax for class fields.
V8 runtime is enabled.
The error:
Syntax error: ParseError: Unexpected token = line: 5 file: Airtable_Class.gs
Line 5 is: foo = "bar";
Here's the whole code:
class FieldsTest{
foo = "bar";
}
This is a known issue. Add a star (★ on top left) to the issue, if you want this to be implemented.
https://issuetracker.google.com/issues/195752915
According to the tracker, it is supported, but it is blocked by the parser.
There's a way to simulate static fields in Apps Script. It involves using properties instead of a field. We can create a lazily initiated property that replaces itself with a field, using the following code:
class MyClass {
static get c() {
// Delete this property. We have to delete it first otherwise we cannot set it (due to it being a get-only property)
delete MyClass.c;
// Replace it with a static value.
return MyClass.c = new StaticObject();
}
}
This approach is better than using a static property, because it also works when instantiating static objects. To confirm this works, we can use the following:
SpreadsheetApp.getUi().alert(MyClass.c === MyClass.c)
This will only evaluate to true if the object was generated once and stored. If the field remains a property, it will return false, because the object is generated twice.
Although it seems that Google Apps Script doesn't support static class fields, it does support static methods/getters/setters, so you can do this:
class X {
// ⭐️ static getters & setters
static get a(){ return this._a || 0 } // this === X
static set a(value){ this._a = value } // this === X
}
then you can get/set X.a as usual:
X.a // get: 0.0
X.a = 3 // set: 3.0
X.a // get: 3.0
Related
I am hooking a certain function in Frida which uses the code:
this.carrier.getId()
However, at this point in time this.carrier has not been set yet, which causes the app to crash.
So I am thinking of manually setting this member in the current function in the class. So that carrier will exist by the time the code takes place.
The problem is that I encounter a problem by doing that.
So far this is what I got:
Java.perform(function () {
var SignUpActivity = Java.use('com.app.features.authentication.SignUpActivity');
SignUpActivity.validatePhoneNumber.implementation = function() {
var Carrier = Java.use("com.app.Carrier");
this.carrier = Carrier.$new();
console.log(this.carrier) // This prints "[object Object]"
console.log(this.carrier.setId) // This prints "undefined"
this.carrier.setId(123); // crashes
};
});
Code of carrier:
package com.app;
import android.os.Parcel;
import android.os.Parcelable;
public class Carrier implements Parcelable {
private int id;
private String name;
private String officeTerminalAddress;
public Carrier() {
}
protected Carrier(Parcel parcel) {
this.id = parcel.readInt();
this.name = parcel.readString();
this.officeTerminalAddress = parcel.readString();
}
public int getId() {
return this.id;
}
public void setId(int i) {
this.id = i;
}
}
Looks like the common problem in Frida that the way to access fields is different in Frida.
Frida uses JavaScript code so it can't handle non-JavaScript objects directly.
Therefore it wraps "native" objects (Android Java objects in this case) in JavaScript objects.
If you now call in Frida this.carrier you are getting the Frida JavaScript wrapper, not the Java Carrier instance you are aiming.
Of course the Frida JavaScript wrapper does not has the methods you try to call, therefore this.carrier.setId(123); will always fail.
Accessing a Java field with Frida
To access a field you always have to call .value on it to get the actual value:
So if you want this.carrier you have to use this.carrier.value.
Furthermore it is recommended to access a field by it's name with an additional underscore in front. Otherwise in obfuscated apps it may occur that there is a field and a method of the same name. In such a case Frida doesn't know if you want to access the field carrier or the method carrier.
Conclusion if you want to access a field of an Java class instance in an Android app using Frida the recommended way is
this._carrier.value
So for writing a field value you should call
this._carrier.value = ...
And the same way for reading.
Reference to Frida help pages
This is also described on the Frida pages, e.g. here:
Note we use this.m.value = 0 instead of this.m = 0 to set the field’s value. If there is also a method in this class called m, we need to use this._m.value = 0 to set the value of field m. In general, when looking at the properties of objects it will be necessary to use .value to access the values those fields refer to.
Complete simplified code
But in your case you can simplify everything by just using a local variable:
Java.perform(function () {
var SignUpActivity = Java.use('com.app.features.authentication.SignUpActivity');
SignUpActivity.validatePhoneNumber.implementation = function() {
const Carrier = Java.use("com.app.Carrier");
const c = Carrier.$new();
c.setId(123);
this._carrier.value = c;
};
});
In my code, I do the following (very simplified):
class AddOrSelectAddress {
static body; // <-- Error
static async open() {
await $.get(basePath + 'Manage/AddOrSelectAddress', null, result => {
this.body = document.createElement('div');
this.body.innerHTML = result;
});
// ...
}
static someOtherMethod() {
// do something with body
}
}
My code works fine in Chrome. Firefox, though, complaints an error in the second line of code:
SyntaxError: bad method definition
I'm relatively new to class-based JavaScript programming. What am I doing wrong here?
Static variables in JavaScript doesn't really help me, because it mainly uses old syntax.
Static class fields are a stage 3 proposal, meaning they're not yet an official part of the JavaScript language. (Stage 4 is the final stage.) You can read more about the proposal here and the proposal process here.
Currently, Chrome (as of version 72) is the only browser that supports static class fields.
To use this feature in other browsers you would need to use Babel with #babel/plugin-proposal-class-properties to transpile your code. If you're not already using Babel, however, this might be overkill.
Alternatively, you can assign a property to the class after initializing it. This isn't semantically identical, but works for your (and, indeed, most) use cases.
class AddOrSelectAddress {
// ...
}
AddOrSelectAddress.body = 'some initial value';
You can see this working in the below snippet.
class AddOrSelectAddress {
static changeBody(val) {
this.body = val;
}
static someMethod() {
console.log('in someMethod body is', this.body);
}
static someOtherMethod() {
console.log('in someOtherMethod body is', this.body);
}
}
AddOrSelectAddress.body = 'some initial value';
AddOrSelectAddress.someMethod();
AddOrSelectAddress.changeBody('some other value');
AddOrSelectAddress.someOtherMethod();
If you don't want to set an initial value for body then you could just omit the line (since accessing a nonexistent property of an object returns undefined), or you could explicitly set it to undefined.
Static methods are perfectly fine to use. However static properties are a recent addition that dont work in all browsers yet. It works in Chrome but like you said not in firefox. Please take a look at this article as it backs up my answer : https://javascript.info/static-properties-methods. To fix your issue you could declare the variable inside your static method.
I want to make a class of mine accessible in JavaScript via a C# WebView-Control.
Therefore I am using the WebView.AddWebAllowedObject method. However if I assign an attribute, it works fine, but if I assign the whole class to get all attributes in js, all of the attributes(and methods btw) are "undefined". I tried everything I found in the www. See the attached code:
//The class I want to make accessible
[AllowForWeb, ComVisible(true)]
[MarshalingBehavior(MarshalingType.Agile)]
public class DeviceInformation
{
public string IPAdress { get; private set; }
public DeviceInformation()
{
IPAdress = GetIPAdress();
}
public string GetDeviceUUID()
{
EasClientDeviceInformation deviceinfo = new EasClientDeviceInformation();
return deviceinfo.Id.ToString();
}
public string GetIPAdress()
{
List<string> ipAddresses = new List<string>();
var hostnames = NetworkInformation.GetHostNames();
foreach (var hn in hostnames)
{
if (hn?.IPInformation != null && (hn.IPInformation.NetworkAdapter.IanaInterfaceType == 71 ||
hn.IPInformation.NetworkAdapter.IanaInterfaceType == 6))
{
string ipadress = hn.DisplayName;
return ipadress;
}
}
return string.Empty;
}
}
Here the objects are initialized.
DeviceInformation devinf = new DeviceInformation();
private void View_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
if (args.Uri.Host == "")
{
//win_ipadress has an ipadress as value
view.AddWebAllowedObject("win_ipadress", devinf.IPAdress);
//deviceInformation is initialized as well but I have no access to its attributes
view.AddWebAllowedObject("deviceInformation", devinf);
}
}
That's the way i call it in js:
else if ($.os.ie) {
myIpAdr = window.win_ipadress;
//Throws an exception because GetIPAdress() is "undefined"
myIpAdr = window.deviceInformation.GetIPAdress();
}
I am using this in a Windows Universal App. The Javascript and in the WebView displayed HTML-Code is already in use for Android an iOS.
I believe you need to define the method name starting with a lower case character.
For example: change GetIPAddress to getIPAddress.
I tested it on my side and found if I use the upper case name 'GetIPAddress', it won't work. But if I use getIPAddress, it works.
And after I read kangax's explanation in this thread, I think it makes sense.
[Update]
Since it still doesn't work after you make the change on method name, I think the issue should be related to how you expose the windows runtime object. I guess you simply defined the DeviceInformation class and tried to use it in the same project.
First, we need to create a separate windows universal windows runtime component project.
The c# class DeviceInformation should be put into this project. Keep the same code.
Then, in your universal app project, add reference to the windows runtime component and keep rest code to consume the windows runtime object.
[Update 2]
Just noticed an interesting behavior in VS. No matter if the Method name we defined in C# is starting with uppercase or lowercase, the visual studio intellisense shows the lowercase, so the method name will be automatically converted when we try to use it in js.
I am developing a NodeJS module and its file size is increasing dramatically, however I have realized that I can divide my module into two separate modules. When this happens only a few functions in my second module need to use internal C++ classes of my first module. Is it possible to somehow only pass the prototype of the first module to the second one?
Example:
Module A:
Has a class called cModuleA:
class cModuleA {
//declarations
data* pointer;
}
Module B:
Has about 100 function but only one of them needs to manipulate data* pointers. It needs to return cModuleA object as well (therefore it needs a prototype of cModuleA or be aware of cModuleA implementation)
I have tried to export symbols from the first module (dllimport/dllexport in windows) but I was just wondering if there is any better option to inject dependencies at C++ level.
I found a solution to this problem and I am going to go over it in detail since probably nobody else has attempted to do such a crazy thing!
Assume you have two native node modules. Meaning that they live in separate executable files (.node). One of them is moduleA and the other one is moduleB:
ModuleA:
class cppClass
{
public:
cppClass();
~cppClass();
// C++ stuff here
}; // !class cppClass
class cppClassWrap
: public node::ObjectWrap
{
public:
// used for initializing this class for Node/v8
static void Initialize(v8::Handle<Object> target);
// internal C++ data accessor
cppClass* GetWrapped() const { return internal_; };
// internal C++ data accessor
void SetWrapped(cppClass* n) { internal_ = n; };
private:
cppClassWrap();
cppClassWrap(cppClass*);
~cppClassWrap() { if (internal_) delete internal_; };
// JS stuff here
static Persistent<Function> constructor;
// JS c'tor
static NAN_METHOD(New);
// internal C++ data
cppClass* internal_;
}; // !class cppClassWrap
//-------------------------------------------------
// JS c'tor implementation
NAN_METHOD(cppClassWrap::New)
{
NanScope();
cppClassWrap* obj;
if (args.Length() == 0)
{
obj = new cppClass();
}
// **** NOTICE THIS! ****
// This is a special case when in JS land we initialize our class like: new cppClassWrap(null)
// It constructs the object with a pointer, pointing to nothing!
else if (args[0]->IsNull())
{
obj = new cppClass(nullptr);
}
else
{
//copy constructor for the JS side
obj = new cppClassWrap(ObjectWrap::Unwrap<cppClassWrap>(args[0]->ToObject())->GetWrapped());
}
obj->Wrap(args.This());
NanReturnValue(args.This());
}
From this point on, all you need to do is to for example have Persistent handle in ModuleB to store a copy of the constructor of ModuleA's class c'tor in it. For example you can have a method called dependencies and call it in JS like:
var cppClassWrap = require("ModuleA.node").cppClassWrap;
var moduleB = require("ModuleB.node").dependencies({
"moduleA" : function() {return new cppClassWrap(null); }
});
And done! you have module injection at C++ level!
I'm using Google's Closure Compiler in advanced mode, and I'm having a strange issue. Here is the uncompiled code, with returned log statement from the compiled version running:
goog.provide('frame.store');
goog.require('frame.storeBack.LocalStore');
goog.require('frame.storeBack.Mem');
frame.store = (function() {
/** prioritised list of backends **/
var backends = [
frame.storeBack.LocalStore,
frame.storeBack.Mem
];
frame.log(backends);
// [function rc(){}, function tc(){this.q={}}]
frame.log(frame.storeBack.LocalStore === backends[0]);
// true
frame.log(frame.storeBack.LocalStore.isAvailable === backends[0].isAvailable);
// false
frame.log(frame.storeBack.LocalStore.isAvailable);
// function sc(){try{return"localStorage"in window&&window.localStorage!==k}catch(a){return l}}
frame.log(backends[0].isAvailable);
// undefined
for (var i=0, len=backends.length; i<len; i++)
if (backends[i].isAvailable())
return new backends[i]();
// Uncaught TypeError: Object function rc(){} has no method 'Ga'
throw('no suitable storage backend');
})();
For some reason the static method isAvailable is not present when LocalStore is accessed via the backends array, and is present when it's accessed via it's global namespace.
Can anyone see why?
EDIT: for reference, here is the method declaration:
frame.storeBack.LocalStore.isAvailable = function() {
try {
return 'localStorage' in window && window['localStorage'] !== null;
}catch (e) {
return false;
}
};
Turn on --debug true to check your output and what frame.storeBack.LocalStore.isAvailable is renamed to.
Dump a variables name map to check whether frame.storeBack.LocalStore.isAvailable has been flattened.
For example, the Closure Compiler may flatten frame.storeBack.LocalStore.isAvailable first to frame$storeBack$LocalStore$isAvailable, then rename the whole thing to the global function "a" or something. This is called flattening of namespaces. Check the debug output to see whether your function declaration has been renamed to:
$frame$storeBack$LocalStore$isAvailable$$ = function() {
In such case, calling frame.storeBack.LocalStore.isAvailable() directly will still call the flattened global version, no prob here! However, you can't expact that isAvailable() exists in frame.storeBack.LocalStore (another object) any more. In the compiled output, frame.storeBack.LocalStore.isAvailable and frame.storeBack.LocalStore are now separated. This is the behavior of the compiler's namespace flattening, if it happens.
You're asking for trouble putting properties into a constructor function itself -- the compiler does a lot of optimizations on classes that you may not expect.
Check the debug output and variable names map to confirm. You may have to remove the closure wrapper function in order to see the actual names in the map file.
Not sure what your back ends are exactly...
But shouldn't you instantiate them?
var backends = { localStore : new frame.storeBack.LocalStore(),
mem: new frame.storeBack.Mem() };