Overview
This MutationObserver Wrapper should be able to provide developers with any changes that have been made in the DOM for security reasons.
Problem
I need a simple way to create a wrapper for MutationObserver. I just want to be informed when a MutationObserver was created on the page. Then I want to make sure the code runs before all other codes so that I can put it in an extension.
Code
This is what I tried:
const dump = console.log.bind(console);
class Observer
{
static started = false
static jacked = {}
static start()
{
Observer.started = true;
Observer.hijack( MutationObserver, function constructor()
{
alert("CAREFUL!! - MutationObserver is running here!");
return (Observer.jacked.MutationObserver).constructor.apply(this, [...arguments]);
});
// if (!Observer.fixes[document.domain])
// {
// dump("not listining on: "+document.domain);
// return;
// };
//
// Observer.fixes[document.domain]()
}
static hijack(target, impose)
{
let waiting = setInterval(()=>
{
if ((typeof target.prototype) !== "object"){ return };
if ((typeof target.prototype.constructor) !== "function"){ return };
clearInterval(waiting);
let name = target.prototype.constructor.name;
dump("hijacking: "+name);
Observer.jacked[name] = target;
Object.defineProperty(target.prototype, "constructor", {writable:false, configurable:false, enumerable:false, value:impose});
},0);
}
let waiting = setInterval(() =>
{
let exists = ((typeof MutationObserver) !== "undefined");
if ( !exists )
{
console.log("observer waiting for `MutationObserver`");
return
};
if ( Observer.started )
{
clearInterval(waiting);
console.log("observer already started .. exiting");
return
};
clearInterval(waiting);
Observer.start();
}, 0);
Try something like this, however, be aware of what globalThis refers to in your extension:
const jacked = {};
jacked.MutationObserver = globalThis.MutationObserver;
delete globalThis.MutationObserver;
globalThis.MutationObserver = (class MutationObserver
{
constructor()
{
console.log("jacked");
}
observe()
{
console.log("busy");
}
});
after running the above, anywhere, like in a function, you can test if your wrapper-test works like this:
let watcher = (new MutationObserver(function(){}));
watcher.observe();
You can use jacked to resume normal operation inside your supplementary class constructor by returning a new jacked.MutationObserver.
Related
We developed a Vue custom directive that replaces DOM elements with a comment depending if the user has permission for specific information. This directive works fine when we use it in any DOM element, but when we apply it to another Vue component it doesn't work as expected because the mounted() function gets called, which we don't want
The problem really is that we don't want to render that component, specifically if it has an API call because this makes the whole system fail.
Here is the directive code:
Vue.directive("checkPermission", {
bind: (el, binded, vnode) => {
let permissions = self.$nuxt.$store.$auth.user.permissions;
if (
!self.$nuxt.$store.$auth.user.isAdmin &&
permissions &&
permissions.length > 0
) {
let hasPermission = false;
const { action, subject } = binded.value;
hasPermission = permissions.some(
permmission =>
permmission.action === action &&
(Array.isArray(subject) ? subject.includes(permmission.subject) : permmission.subject === subject)
);
if (!hasPermission) {
const comment = document.createComment(" ");
Object.defineProperty(comment, "setAttribute", {
value: () => undefined
});
vnode.text = " ";
vnode.elm = comment;
vnode.isComment = true;
vnode.context = undefined;
vnode.tag = undefined;
vnode.data.directives = undefined;
vnode.children = undefined
if (vnode.componentInstance) {
vnode.componentInstance.$el = comment;
}
if (el.parentNode) {
el.parentNode.replaceChild(comment, el);
}
}
}
},
I want this code to check if one of the keys in the "states" object updates from false to true, and if it does, then run the code inside of the if-statement. For some reason, even if I update the states variable manually (like I did here). The code never runs.
I'm using the "compareMe" variable to hold the last version of the "states" object by setting it equal to the "states" object at the end of the loop.
I'm sure this is not the best approach, any ideas would help a ton.
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms * 1000)
})
}
var states = { Dad: false, Mom: false, Brother: false, Sister: true }
var compareMe = {}
var loop = 0;
(async () => {
while(true) {
loop++
if (loop === 5) {
states.Dad = true
}
for (const key of Object.keys(states)) {
if(compareMe[key] === false && states[key] === true) {
console.log("Opening Door")
} else {
console.log('No change')
}
}
compareMe = states;
await sleep(5)
}
})();
What you are doing with compareMe = states is create a new reference on the same object, so whenever you mutate states, the mutation reflects on compareMe.
You should perform a clone instead:
compareMe = { ...states };
You can use proxy object to monitor your object for changes.
original answer with more details
var targetObj = {};
var targetProxy = new Proxy(targetObj, {
set: function (target, key, value) {
// function called
console.log(`${key} set to ${value}`);
target[key] = value;
return true;
}
});
targetProxy.newProp = "test"; // console: 'newProp set to test'
However it would be easier for you to just use a library to monitor and watch variables. There are many libraries and frameworks to do this.
Library: Obseravble-Slim
Framework: Angular
I am trying to build a plugin for Vue.
My plugin has a custom method caller customMethod for every component, I want it to run on after the page is mounted/created.
In a simple way, this is working as I want but I am having trouble accessing this inside customMethod.
It logs 'undefined' when I am trying to console.log(this).
so how can I access this inside my customMethod ?
var defaultParms = Object.freeze({
start : function(){},
leave : function(){},
});
const myPlugin = {
install(Vue, options = []) {
var ref = Vue.util;
var extend = ref.extend;
var assets = Object.create(null);
extend(assets, defaultParms);
Vue.options.customMethod = assets;
// set option merge strategy
var strats = Vue.config.optionMergeStrategies;
if (strats) {
strats.customMethod = (parent, child, vm)=>{
if (!child) return parent;
if (!parent) return child;
var ret = Object.create(null);
extend(ret, parent);
for (var key in child) {
ret[key] = child[key];
}
return ret
};
}
Vue.mixin({
customMethod:{
start: function(){
console.log('hi') // log 'hi'
console.log(this.$appName) // log 'undefined'
}
},
created: function () {
if(this.$options.customMethod){
this.$options.customMethod.start && this.$options.customMethod.start();
}
}
});
Vue.prototype.$appName = 'vikash';
}
}
Vue.use(myPlugin)
new Vue().$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
This is part of how the context this works in JS. You need to set it with bind, call or apply:
this.$options.customMethod.start.call(this)
I was wondering if anyone can help me understand how exactly to create different Custom event listeners.
I don't have a specific case of an event but I want to learn just in general how it is done, so I can apply it where it is needed.
What I was looking to do, just incase some folks might need to know, was:
var position = 0;
for(var i = 0; i < 10; i++)
{
position++;
if((position + 1) % 4 == 0)
{
// do some functions
}
}
var evt = document.createEvent("Event");
evt.initEvent("myEvent",true,true);
// custom param
evt.foo = "bar";
//register
document.addEventListener("myEvent",myEventHandler,false);
//invoke
document.dispatchEvent(evt);
Here is the way to do it more locally, pinpointing listeners and publishers:
http://www.kaizou.org/2010/03/generating-custom-javascript-events/
Implementing custom events is not hard. You can implement it in many ways. Lately I'm doing it like this:
/***************************************************************
*
* Observable
*
***************************************************************/
var Observable;
(Observable = function() {
}).prototype = {
listen: function(type, method, scope, context) {
var listeners, handlers;
if (!(listeners = this.listeners)) {
listeners = this.listeners = {};
}
if (!(handlers = listeners[type])){
handlers = listeners[type] = [];
}
scope = (scope ? scope : window);
handlers.push({
method: method,
scope: scope,
context: (context ? context : scope)
});
},
fireEvent: function(type, data, context) {
var listeners, handlers, i, n, handler, scope;
if (!(listeners = this.listeners)) {
return;
}
if (!(handlers = listeners[type])){
return;
}
for (i = 0, n = handlers.length; i < n; i++){
handler = handlers[i];
if (typeof(context)!=="undefined" && context !== handler.context) continue;
if (handler.method.call(
handler.scope, this, type, data
)===false) {
return false;
}
}
return true;
}
};
The Observable object can be reused and applied by whatever constructor needs it simply by mixng the prototype of Observable with the protoype of that constructor.
To start listening, you have to register yourself to the observable object, like so:
var obs = new Observable();
obs.listen("myEvent", function(observable, eventType, data){
//handle myEvent
});
Or if your listener is a method of an object, like so:
obs.listen("myEvent", listener.handler, listener);
Where listener is an instance of an object, which implements the method "handler".
The Observable object can now call its fireEvent method whenever something happens that it wants to communicate to its listeners:
this.fireEvent("myEvent", data);
Where data is some data that the listeners my find interesting. Whatever you put in there is up to you - you know best what your custom event is made up of.
The fireEvent method simply goes through all the listeners that were registered for "myEvent", and calls the registered function. If the function returns false, then that is taken to mean that the event is canceled, and the observable will not call the other listeners. As a result the entire fireEvent method will return fasle too so the observable knows that whatever action it was notifying its listeners of should now be rolled back.
Perhaps this solution doesn't suit everybody, but I;ve had much benefit from this relatively simple piece of code.
From here:
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
// create the event
const event = new Event('build');
// elem is any element
elem.dispatchEvent(event);
// later on.. binding to that event
// we'll bind to the document for the event delegation style.
document.addEventListener('build', function(e){
// e.target matches the elem from above
}, false);
Here is a really simple (TypeScript/Babelish) implementation:
const simpleEvent = <T extends Function>(context = null) => {
let cbs: T[] = [];
return {
addListener: (cb: T) => { cbs.push(cb); },
removeListener: (cb: T) => { let i = cbs.indexOf(cb); cbs.splice(i, Math.max(i, 0)); },
trigger: (<T> (((...args) => cbs.forEach(cb => cb.apply(context, args))) as any))
};
};
You use it like this:
let onMyEvent = simpleEvent();
let listener = (test) => { console.log("triggered", test); };
onMyEvent.addListener(listener);
onMyEvent.trigger("hello");
onMyEvent.removeListener(listener);
Or in classes like this
class Example {
public onMyEvent = simpleEvent(this);
}
If you want plain JavaScript you can transpile it using TypeScript playground.
I am trying to set a custom error handler for 3rd party plugins/modules in my core library, but somehow, myHandler does not alert the e.message.
Can somebody help me please? thank you
Function.prototype.setErrorHandler = function(f) {
if (!f) {
throw new Error('No function provided.');
}
var that = this;
var g = function() {
try {
var a = [];
for(var i=0; i<arguments.length; i++) {
a.push(arguments[i]);
}
that.apply(null,a);
}
catch(e) {
return f(e);
}
};
g.old = this;
return g;
};
function myHandler(e) {
alert(e.message)
};
// my Core library object
(function(){
if (typeof window.Core === 'undefined') {
var Core = window.Core = function() {
this.addPlugin = function(namespace, obj){
if (typeof this[namespace] === 'undefined') {
if (typeof obj === 'function') {
obj.setErrorHandler(myHandler);
} else if (!!obj && typeof obj === 'object') {
for (var o in obj) {
if (obj.hasOwnProperty(o) && typeof obj[o] === 'function') {
obj[o].setErrorHandler(myHandler);
}
}
}
this[namespace] = obj;
return true;
} else {
alert("The namespace '" + namespace + "' is already taken...");
//return false;
}
};
};
window.Core = new Core();
}
})();
// test plugin
(function(){
var myPlugin = {
init: function() {},
conf: function() {
return this.foo.x; // error here
}
};
Core.addPlugin("myPlugin", myPlugin);
})();
// test
Core.myPlugin.conf(); // supposed to alert(e.message) from myHandler()
setErrorHandler in the above code doesn't set an error handler on a Function, as such. JavaScript does not give you the ability to change the called code inside a Function object.
Instead it makes a wrapped version of the function it's called on, and returns it.
obj.setErrorHandler(myHandler);
Can't work as the returned wrapper function is thrown away, not assigned to anything.
You could say:
obj[o]= obj[o].setErrorHandler(myHandler);
though I'm a bit worried about the consequences of swapping out functions with different, wrapped versions. That won't necessarily work for all cases and could certainly confuse third-party code. At the least, you'd want to ensure you don't wrap functions twice, and also retain the call-time this value in the wrapper:
that.apply(this, a);
(Note: you don't need the manual conversion of arguments to an Array. It's valid to pass the arguments object directly to apply.)