Is it possible to use ES6 Proxies to track changes of location.pathname?
As far as I know, Proxies can be used for objects, and location.pathname is an object, right?
If the answer is yes, can someone help me understand how to achieve this?
The only working thing I managed to create is the following code:
const handler = {
get(target, key) {
console.log(`Reading value from ${key}`)
return target[key];
},
};
const p = new Proxy(location, handler);
console.log(p.pathname);
But the above code will only show once the current URL is in the browser's tab.
How can I make it work to keep tracking the changes?
window.location is a nonconfigurable getter/setter, which means that it can't be replaced:
console.log(Object.getOwnPropertyDescriptor(window, 'location'));
The same is true for the .pathname property of the object returned by the getter:
console.log(Object.getOwnPropertyDescriptor(window.location, 'pathname'));
And a location variable can't be declared on the top level:
const location = { newObject: 'foo' };
Which means that there's no way to intercept changes to it by replacing it with a Proxy - any references or assignments in the existing code to window.location.pathname will necessarily invoke the browser's built-in implementation.
Related
said simply:
I am trying to create a protective barrier around a JavaScript Object, using a Proxy to set 'traps' for any form of retrieval or manipulation. These traps will require the SessionKey parameter to be provided with any such Object manipulations or else default into clearing out -all- information and properties of the Object.
(This SessionKey is of course the one provided by the User Sign-in PHP which is sent back upon successful sign-in.)
If I have this right, and I'm not the most veteran coder here certainly, the Object will only call functions with the corresponding SessionKey parameter being provided - as this Object knows the SessionKey.
I cannot be 100% to thwart any or all such efforts to get into my code, but perhaps I can set up a 'trip' alarm that makes all such efforts unsuccessful. No SessionKey? NO DOM (.empty()), no WebServices/API. Nothing.
Thank you for your help, I appreciate it immensely.
What you are wanting can't really be done. You CAN create a proxy to trap retrieval/manipulation calls but you can't make the underlying object private in any way.
For example, if you have object
const privateObj = { name: 'John Smith', ssn: '123-45-6789' };
and proxy
const proxy = new Proxy(privateObj, {} /* handler for getter/setter traps */);
you can console log the proxy to get something like this:
[[Handler]]: Object
[[Target]]: Object
[[IsRevoked]]: false
and you can expand [[Target]] to see the properties of the underlying object. So if you want this property to be fully private you won't want to use proxies. Furthermore, if the user can inspect the proxy, they can easily get access to the underlying object and mutate it outside of the proxy traps.
If you want to be able to really protect that object, it would be better to protect it in a function closure and have getter/setter wrappers around that object.
You could try as a first step:
const protectObject = (SessionKey, ...objectProps) => {
const protectedObject = { // Do something here to create the object
...objectProps // and its properties you want to protect
};
return {
accessProp(_SessionKey, prop) {
if (_SessionKey !== SessionKey) throw Error("Your session key doesn't match");
return protectedObject[prop];
},
setProp(_SessionKey, prop, val) {
if (_SessionKey !== SessionKey) throw Error("Your session key doesn't match");
protectedObject[prop] = val;
}
};
};
So now you have a private object that is protected:
const privateObj = protectObject('12345', 'John Smith', '7/20/1992', '123-45-6789');
privateObj.accessProp('12345', 1); // '7/20/1992'
privateObj.accessProp('1234', 2); // Uncaught Error: Your session key doesn't match
privateObj.setProp('12345', 1, '7/21/1993');
privateObj.accessProp('12345', 1); // '7/21/1993'
I've peeked into many plugins' code (for educational purposes) and basically every one of them (which deals with prototypes), has bunch of functions like this:
myMarker.prototype.getPosition = function() {
return this.latlng;
};
//OR
myMarker.prototype.getObject = function() {
return this;
};
What's the reason behind this?
Why not just to use someObject.latlng instead of someObject.getPosition()?
One common reason for doing this is to avoid coupling the object's internal data storage to the API; in this example you could change the way the position is stored internally, and then add some processing to getPosition() to return a backwards compatible result.
For example, version 1.1 of this library might look like this, and calling code wouldn't need to be changed:
myMarker.prototype.getPosition = function() {
return this.latitude + this.longitude;
};
It is possible to accomplish this using computed properties with ES5 get and set, but only if the code doesn't need to run on Internet Explorer 8 and below.
When you say like this.
myMarker.prototype.getPosition = function() {
return this.latlng;
};
You are defining function getPosition which available to all instance to class myMarker.
So,all object of this class share this method without replication.
For someObject.latlng,there is nothing wrong.
But assume, this object is accessible to all which are in the current scope.So,it can be modified/accessible to anyone.
When you go through prototype you are trying to define some pattern,which gives restriction for access and modification of property
Jasmine's spyOn is good to change a method's behavior, but is there any way to change a value property (rather than a method) for an object? the code could be like below:
spyOn(myObj, 'valueA').andReturn(1);
expect(myObj.valueA).toBe(1);
In February 2017, they merged a PR adding this feature, they released in April 2017.
so to spy on getters/setters you use:
const spy = spyOnProperty(myObj, 'myGetterName', 'get');
where myObj is your instance, 'myGetterName' is the name of that one defined in your class as get myGetterName() {} and the third param is the type get or set.
You can use the same assertions that you already use with the spies created with spyOn.
So you can for example:
const spy = spyOnProperty(myObj, 'myGetterName', 'get'); // to stub and return nothing. Just spy and stub.
const spy = spyOnProperty(myObj, 'myGetterName', 'get').and.returnValue(1); // to stub and return 1 or any value as needed.
const spy = spyOnProperty(myObj, 'myGetterName', 'get').and.callThrough(); // Call the real thing.
Here's the line in the github source code where this method is available if you are interested.
https://github.com/jasmine/jasmine/blob/7f8f2b5e7a7af70d7f6b629331eb6fe0a7cb9279/src/core/requireInterface.js#L199
And the spyOnProperty method is here
Answering the original question, with jasmine 2.6.1, you would:
const spy = spyOnProperty(myObj, 'valueA', 'get').andReturn(1);
expect(myObj.valueA).toBe(1);
expect(spy).toHaveBeenCalled();
Any reason you cannot just change it on the object directly? It is not as if javascript enforces visibility of a property on an object.
The best way is to use spyOnProperty. It expects 3 parameters and you need to pass get or set as a third param.
Example
const div = fixture.debugElement.query(By.css('.ellipsis-overflow'));
// now mock properties
spyOnProperty(div.nativeElement, 'clientWidth', 'get').and.returnValue(1400);
spyOnProperty(div.nativeElement, 'scrollWidth', 'get').and.returnValue(2400);
Here I am setting the get of clientWidth of div.nativeElement object.
Jasmine doesn't have that functionality, but you might be able to hack something together using Object.defineProperty.
You could refactor your code to use a getter function, then spy on the getter.
spyOn(myObj, 'getValueA').andReturn(1);
expect(myObj.getValueA()).toBe(1);
The right way to do this is with the spy on property, it will allow you to simulate a property on an object with an specific value.
const spy = spyOnProperty(myObj, 'valueA').and.returnValue(1);
expect(myObj.valueA).toBe(1);
expect(spy).toHaveBeenCalled();
If you are using ES6 (Babel) or TypeScript you can stub out the property using get and set accessors
export class SomeClassStub {
getValueA = jasmine.createSpy('getValueA');
setValueA = jasmine.createSpy('setValueA');
get valueA() { return this.getValueA(); }
set valueA(value) { this.setValueA(value); }
}
Then in your test you can check that the property is set with:
stub.valueA = 'foo';
expect(stub.setValueA).toHaveBeenCalledWith('foo');
Suppose there is a method like this that needs testing
The src property of the tiny image needs checking
function reportABCEvent(cat, type, val) {
var i1 = new Image(1, 1);
var link = getABC('creosote');
link += "&category=" + String(cat);
link += "&event_type=" + String(type);
link += "&event_value=" + String(val);
i1.src = link;
}
The spyOn() below causes the "new Image" to be fed the fake code from the test
the spyOn code returns an object that only has a src property
As the variable "hook" is scoped to be visible in the fake code in the SpyOn and also later after the "reportABCEvent" is called
describe("Alphabetic.ads", function() {
it("ABC events create an image request", function() {
var hook={};
spyOn(window, 'Image').andCallFake( function(x,y) {
hook={ src: {} }
return hook;
}
);
reportABCEvent('testa', 'testb', 'testc');
expect(hook.src).
toEqual('[zubzub]&arg1=testa&arg2=testb&event_value=testc');
});
This is for jasmine 1.3 but might work on 2.0 if the "andCallFake" is altered to
the 2.0 name
I'm using a kendo grid and therefore can't change the implementation to a getter method but I want to test around this (mocking the grid) and not test the grid itself. I was using a spy object but this doesn't support property mocking so I do this:
this.$scope.ticketsGrid = {
showColumn: jasmine.createSpy('showColumn'),
hideColumn: jasmine.createSpy('hideColumn'),
select: jasmine.createSpy('select'),
dataItem: jasmine.createSpy('dataItem'),
_data: []
}
It's a bit long winded but it works a treat
You can also use
jasmin.creatSpyObj('ObjectName', [methodNames...], {prop1:propvalue, prop2:provalue2})
I'm a bit late to the party here i know but,
You could directly access the calls object, which can give you the variables for each call
expect(spy.calls.argsFor(0)[0].value).toBe(expectedValue)
You can not mock variable but you can create getter function for it and mock that method in your spec file.
I currently have a Web Application that runs off a global Javascript-based API, and it is initialized like this:
var Api = {
someVar: "test",
someFunction: function() {
return "foo";
}
}
This API is shared across many "Widgets" that live in the Web Application, and they should all run off this single Api instance so they can pass data to each other.
AJAX is currently used to load these Widgets, for example in widgets/mywidget.html, and it's placed in, say, <div id='widget_<random number>'>...</div>
Certain other parts of the code may choose to add more functionality to Api, and it's currently done like this:
Api.myExtension = {
myNewFunction: function() {
return "bar";
}
}
However, some issues arise from this kind of usage:
Problem One: What if one Widget (these may be provided by third-parties) decides to hide some code within, and does something similar to Api = {}, destroying the global Api var everything lives on, and breaking the whole Application? Is it possible to protect this Api variable from being overwritten from outside? Only "extending" is allowed (adding new things), but "removing/changing" is not allowed. i.e.:
Api.foo = { test: "bar" } // allowed
Api.someVar = "changing the existing someVar"; // not allowed
The following code is located "inside" Api, for example:
var Api = {
Debug: {
Messages = new Array,
Write: function() {
Api.Debug.Messages.push("test"); // allowed
}
}
}
Api.Debug.Messages.push("test 2"); // not allowed
Probable Solutions I've Thought Of:
Suppose we simply use frames to resolve this issue. The Apis provided are now separate from each other. However, there's additional overhead when loading Api again and again if I have many Widgets running, and they can no longer communicate with the "Host" of the widgets (the page where frames reside in), for example, I may want to tell the host to show a notification: Api.Notify.Show("Test"), but it cannot do so because this Api is completely independent from other instances, and it cannot communicate with the "Host"
Using something like a "getter" and "setter" function for the Api to be read and written. I'm unsure on how to implement this, so any help on directions on how to implement this is welcome!
A mixture of 1/2?
There's no good way to prevent having a "third party" widget overwrite the a global variable. Generally it is the responsibility of whoever is putting together the final application to ensure that whatever JavaScripts they are using aren't littering the global namespace and conflicting. The best thing you can do in that direction is give your "Api" a nice, unique name.
What I think can help you a lot is something like the "revealing pattern", which would be a way of doing the "getters and setters" you mentioned, plus more if you needed it.
A simple, useless example would be like the following:
var Api = (function () {
// private variable
var myArray = [];
return {
addItem: function (newItem) {
myArray.push(newItem);
},
printItems: function () {
console.log("lots if items");
}
};
})();
Api.addItem("Hello, world");
Api.extensionValue = 5;
I think you should make a clear delineation of what is shared, or "singleton" data, and keep those items private, as with myArray in my example.
Make it a constant:
const Api = "hi";
Api = 0;
alert(Api); //"hi"
Take a look at
Object.freeze
More info here
Here is a code example from Mozilla's page:
var obj = {
prop: function (){},
foo: "bar"
};
// New properties may be added, existing properties may be changed or removed
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;
var o = Object.freeze(obj);
assert(Object.isFrozen(obj) === true);
// Now any changes will fail
obj.foo = "quux"; // silently does nothing
obj.quaxxor = "the friendly duck"; // silently doesn't add the property
// ...and in strict mode such attempts will throw TypeErrors
function fail(){
"use strict";
obj.foo = "sparky"; // throws a TypeError
delete obj.quaxxor; // throws a TypeError
obj.sparky = "arf"; // throws a TypeError
}
fail();
// Attempted changes through Object.defineProperty will also throw
Object.defineProperty(obj, "ohai", { value: 17 }); // throws a TypeError
Object.defineProperty(obj, "foo", { value: "eit" }); // throws a TypeError
However browser support is still partial
EDIT: see Kernel James's answer, it's more relevant to your question (freeze will protect the object, but not protect reassigning it. however const will) same issue with limited browser support though.
The only way (at least that I can think of) to protect your global variable is to prevent the Widgets from having a direct access to it. This can be achieved by using frames functions, as you suggested. You should create an object that contains all the functions that the Widgets should be able to use, and pass such to each Widget. For example:
var Api = {
widgetApi = {
someFunction: function(){
// ...
}
},
addWidget:function(){
var temp = this.widgetApi.constructor();
for(var key in this.widgetApi)
temp[key] = clone(this.widgetApi[key]);
return temp;
}
// Include other variables that Widgets can't use
}
This way, the Widgets could execute functions and communicate with the host or global variable Api. To set variables, the Widget would be editing its private object, rather than the global one. For every frame (that represents a Widget), you must initialize or create a copy of the widgetApi object, and probably store it inside an array, in such a way that an instance of a Widget is stored in the main Api object.
For example, given <iframe id="widget"></iframe>
You would do the following:
var widget = document.getElementById("widget");
widget.contentWindow.Api = Api.addWidget();
widget.contentWindow.parent = null;
widget.contentWindow.top = null;
Additionally, in every frame you would need to set the parent and top variables to null so that the Widgets wouldn't be able to access the data of the main frame. I haven't tested this method in a while, so there might be ways to get around setting those variables to null.
What are the hidden or obscure features of MooTools that every MooTools developer should be aware of?
One feature per answer, please.
Class Mutators
MooTools has a wonderful feature that allows you to create your own Class mutators. Eg, to add a logger for particular class methods being referenced, you can do:
// define the mutator as 'Monitor', use as Mointor: ['methodname', 'method2'...]
Class.Mutators.Monitor = function(methods){
if (!this.prototype.initialize) this.implement('initialize', function(){});
return Array.from(methods).concat(this.prototype.Monitor || []);
};
Class.Mutators.initialize = function(initialize){
return function(){
Array.from(this.Monitor).each(function(name){
var original = this[name];
if (original) this[name] = function() {
console.log("[LOG] " + name, "[SCOPE]:", this, "[ARGS]", arguments);
original.apply(this, arguments);
}
}, this);
return initialize.apply(this, arguments);
};
};
and then in the Class:
var foo = new Class({
Monitor: 'bar',
initialize: function() {
this.bar("mootools");
},
bar: function(what) {
alert(what);
}
});
var f = new foo();
f.bar.call({hi:"there from a custom scope"}, "scope 2");
Try the jsfiddle: http://jsfiddle.net/BMsZ7/2/
This little gem has been instrumental to me catching nested bugfoot race condition issues inside a HUUUGE async webapp that would have been so difficult to trace otherwise.
Function.prototype.protect is maybe a lesser known nice one.
Is used to have protected methods in classes:
var Foo = new Class({
fooify: function(){
console.log('can\'t touch me');
}.protect(),
barify: function(){
this.fooify();
}
});
var foo = new Foo();
foo.fooify(); // throws error
foo.barify(); // logs "can't touch me"
Personally I don't use it very often, but it might be useful in some cases.
Function.prototype.overloadGetter and Function.prototype.overloadSetter
See this post: What does MooTools' Function.prototype.overloadSetter() do?
There are many features that one can use if you read the source code, although the official line is: if it's not in the documentation, it is not in the api and it's not supported so do not base your code around it as it may change
That being said, there are a few things that really can be quite useful. One of my favourites undocumented features is this:
Referenced Elements have a uid
Any element that has either being created or is passed on through a selector, gets assigned a property uid, which is incremental and unique. Since MooTools 1.4.2, this is only readable via Slick.uidOf(node) and not via the old element attr .uid. You can now use the new uniqueNumber property of any MooTools Element object.
How is that being used? For starters, Element Storage. It relies on the uid as the key in the Storage object inside a closure, which will have anything you have .store'd for that element.
element.store('foo', 'bar');
translates to:
Storage[Slick.uidOf(element)].foo = 'bar';
and
element.retrieve('foo'); // getter of the storage key
element.eliminate('foo'); // delete Storage[Slick.uidOf(element)].foo
Initializing storage for an element you have created externally, eg, via var foo = document.createElement('div') and not Element constructor
Slick.uidOf(foo); // makes it compatible with Storage
// same as:
document.id(foo);
Things that are stored by the framework into Storage also include all events callbacks, validators instances, Fx instances (tween, morph etc) and so forth.
What can you do knowing the UIDs of elements? Well, cloning an element does NOT get the element's storage or events. You can actually write a new Element.cloneWithStorage prototype that will also copy all of the stored values you may have, which is useful upto a point - instances that reference a particular element (such as, Fx.Tween) will continue referencing the old element, so it may have unexpected results. This can be useful in moving your own storage, though, all you need is a similar method that will record what you have stored and allow you to clone it.
Example Storage puncture of another Element's data:
var foo = new Element('div'),
uid = foo.uniqueNumber;
foo.store('foo', 'foo only');
var bar = new Element('div');
console.log(bar.retrieve('foo')); // null
bar.uniqueNumber = uid; // force overwrite of uid to the other el
console.log(bar.retrieve('foo')); // foo only - OH NOES
console.log(Object.keys(foo)); // ["uniqueNumber"] - oh dear. enumerable!
One of my favorite features that I learned later but wished I knew from the beginning - event pseudos, especially :once.
See http://mootools.net/docs/more/Class/Events.Pseudos#Pseudos:once
I'd recommend reading the excellent Up the Moo Herd series by Mark Obcena, author of Pro Javascript With MooTools :)