monkey-patching react's render method in devtools extension - javascript

I'm trying to create a monkey patch to React's render method from a devtool extension because I'm trying to recreate something similar to react_devtool's api for a feature in my extension.
I'm spoofing the inspected window's virtual DOM with these 2 lines of code
var reactRoot = document.querySelector("[data-reactroot]")
var dom = reactRoot[Object.getOwnPropertyNames(reactRoot)[0]]
I've also heard of accessing this in the same way by accessing the window's __REACT_DEVTOOLS_GLOBAL_HOOK__. I need to be able to access a new set of data from React's internalInstance anytime there is an update to the page (setState/rerender).
This is my attempt at monkey patching React's render method. This code is running from a separate file injected through my content-scripts.jswhich is able to access the dom of a React application in the inspected Window.
const reactInstances = window.__REACT_DEVTOOLS_GLOBAL_HOOK__._renderers
const instance = reactInstances[Object.keys(reactInstances)[0]]
console.log('current windows React INSTANCE: ', instance)
var reactRender = instance.Mount.render
console.log('reacts render method: ', reactRender)
reactRender = (function (original) {
return function (nextElement, container, callback) {
var result = original.apply(this, arguments)
console.log(original, result)
return result
}
})(reactRender)
Not sure if this is the correct way to monkey patch a method but I'm also wondering if this is the correct approach to what I'm trying to accomplish.

Related

Making a custom group of defined chaining methods in js

The question is related to general js programming, but I'll use nightwatch.js as an example to elaborate my query.
NightWatch JS provides various chaining methods for its browser components, like: -
browser
.setValue('input[name='email']','example#mail.com')
.setValue('input[name='password']', '123456')
.click('#submitButton')
But if I'm writing method to select an option from dropdown, it requires multiple steps, and if there are multiple dropdowns in a form, it gets really confusing, like: -
browser
.click(`#country`)
.waitForElementVisible(`#india`)
.click(`#india`)
.click(`#state`)
.waitForElementVisible(`#delhi`)
.click(`#delhi`)
Is it possible to create a custom chaining method to group these already defined methods? For example something like:
/* custom method */
const dropdownSelector = (id, value) {
return this
.click(`${id}`).
.waitForElementVisible(`${value}`)
.click(`${value}`)
}
/* So it can be used as a chaining method */
browser
.dropdownSelector('country', 'india')
.dropdownSelector('state', 'delhi')
Or is there any other way I can solve my problem of increasing reusability and readability of my code?
I'm somewhat new to JS so couldn't tell you an ideal code solution, would have to admit I don't know what a proxy is in this context. But in the world of Nightwatch and test-automation i'd normally wrap multiple steps I plan on reusing into a page object. Create a new file in a pageObject folder and fill it with the method you want to reuse
So your test...
browser
.click(`#country`)
.waitForElementVisible(`#india`)
.click(`#india`)
.click(`#state`)
.waitForElementVisible(`#delhi`)
.click(`#delhi`)
becomes a page object method in another file called 'myObject' like...
selectLocation(browser, country, state, city) {
browser
.click(`#country`) <== assume this never changes?
.waitForElementVisible(country)
.click(country)
.click(state)
.waitForElementVisible(city)
.click(city);
}
and then each of your tests inherit the method and define those values themselves, however you chose to manage that...
const myObject = require ('<path to the new pageObject file>')
module.exports = {
'someTest': function (browser) {
const country = 'something'
const state = 'something'
const city = 'something'
myObject.selectLocation(browser);
You can also set your country / state / city as variables in a globals file and set them as same for everything but I don't know how granular you want to be.
Hope that made some sense :)
This is a great place to use a Proxy. Given some class:
function Apple ()
{
this.eat = function ()
{
console.log("I was eaten!");
return this;
}
this.nomnom = function ()
{
console.log("Nom nom!");
return this;
}
}
And a set of "extension methods":
const appleExtensions =
{
eatAndNomnom ()
{
this.eat().nomnom();
return this;
}
}
We can create function which returns a Proxy to select which properties are retrieved from the extension object and which are retrieved from the originating object:
function makeExtendedTarget(target, extensions)
{
return new Proxy(target,
{
get (obj, prop)
{
if (prop in extensions)
{
return extensions[prop];
}
return obj[prop];
}
});
}
And we can use it like so:
let apple = makeExtendedTarget(new Apple(), appleExtensions);
apple
.eatAndNomnom()
.eat();
// => "I was eaten!"
// "Nom nom!"
// "I was eaten!"
Of course, this requires you to call makeExtendedTarget whenever you want to create a new Apple. However, I would consider this a plus, as it makes it abundantly clear you are created an extended object, and to expect to be able to call methods not normally available on the class API.
Of course, whether or not you should be doing this is an entirely different discussion!

Rendering a graph with PhantomJS and taking a snapshot

I want to generate a chart to add to an email. I was thinking of using PhantomJS to do this. I am using version 2.0 of PhantomJS. I want to load d3.js (on cloudfare) and c3.js (which is in a local js file). I am missing something here. When I run the script with PhantomJS it returns d3 as undefined on the window object.
My code below does not work:
var page = require('webpage').create();
var fs = require('fs');
page.viewportSize = { width: 400, height : 400 };
page.content = '<html><body><canvas id="surface"></canvas> <button>Hello world</button> </body></html>';
page.includeJs("https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js");
var c3_js = "./c3.min.js";
page.includeJs(c3_js);
page.onLoadFinished = function(status) {
var c3 = page.evaluate(function() {
return window;
});
console.log(c3.d3);
page.render('test.png');
phantom.exit();
};
There are a lot of problems with your code:
page.includeJs(url, callback) is an asynchronous function, so you need to put the code that needs the JavaScript that it includes into the callback (which you don't have at all).
You need to use page.injectJs(filename) for local files (c3.js).
You cannot return window from the page context, because it is not a primitive object that can be serialized. From the docs:
Note: The arguments and the return value to the evaluate function must be a simple primitive object. The rule of thumb: if it can be serialized via JSON, then it is fine.
Closures, functions, DOM nodes, etc. will not work!
Either you do everything in the page context (inside of page.evaluate()) or you can return some representation of the stuff to the outside. If you decide to do it in the page context, then you should look into the various event listeners such as page.onConsoleMessage to see console messages from the page context and page.onCallback.

nodeJS prevent access to code for variable passed into a function

I'm creating a plugin system using the following:
function Plugin(thingy, code)
{
var GLOBAL = null;
var arguments = null;
var process = null;
var require = null;
eval(code);
};
plugins.push(new Plugin(thingy, code));
Please don't get too excited about the eval(), using ('vm') or a sandbox is not an option as this will be a long running object until the user unloads it. It will also be running in it's own nodeJS instance so they can't affect other users. I'd still have the same problem passing in this object reference to a sandbox system anyway.
What I am concerned about is someone seeing the code of the thingy object that has functions they need to use e.g shoot()
console.log(thingy.shoot.toString());
A way around this was the following:
function thingy()
{
// They can't see this
var _shoot = function(someone)
{
// Load weapon
// Aim
// Fire
};
// They can see this
this.shoot = function(someone)
{
_shoot(someone);
};
};
This way if they console.log(thingy.shoot.toString()) they'll only see _shoot(someone); and not the actual code that handles the shooting process.
Please could someone help me with the following:
Is there an easier way to limit access to a passed in variables code?
I'm setting GLOBAL, arguments, process and require to null; are there others I need to worry about?

Edit or Remove BreezeJS EntityManager Once Instance Reference Lost

I'm constructing a CRM application with a SPA structure, using BreezeJS and AngularJS, and I'm utilizing a dynamically-generated tabbed environment to display what I'll refer to here on out as modules. When a user clicks a side menu item, a new tab is created, and an HTML template (aka module) is loaded into the new content area. While some of the module types are to be opened only once at a time (a grid of Accounts), others, such as an Account Editor module, can be opened many times. That way, the user can modify many accounts at any given time throughout the day, but always be doing so from a single Accounts grid instance.
So far, I have a working system for the Account Editor module, in that I create a master Breeze EntityManager with appropriate configuration parameters (service name, save options, etc.) and then make a copy of it any time a new Account Editor is created, using masterManager.createEmptyCopy() (as per the code outlined at http://www.breezejs.com/documentation/multiple-managers#CreateMultipleManagers):
var serviceName = "/breeze/accounts";
var ds = new breeze.DataService({
serviceName: serviceName,
hasServerMetadata: true
});
var masterManager = new breeze.EntityManager({
dataService: ds,
saveOptions: new breeze.SaveOptions({ allowConcurrentSaves: false })
});
function createManagerCopy() {
// same configuration as master; no entities in cache.
var sandboxManager = masterManager.createEmptyCopy();
return sandboxManager;
}
Then, I call an EntityQuery, by passing in the copied EntityManager and entity Id (key), in order to get the appropriate Account and populate each open editor with apparently all the benefits of Breeze.
function queryAccountByKey(mgr, key) {
var keyPredicate = breeze.Predicate.create('id', 'eq', key);
var query = new breeze.EntityQuery('AccountsBase')
.expand('ContactsBase')
.where(keyPredicate);
var promise = mgr.executeQuery(query)
.catch(queryFailed);
return promise;
function queryFailed(error) {
console.log('Query Account by Id Failed', error);
return $q.reject(error); // so downstream promise users know it failed
}
}
There does not appear to be any conflict with the other open editors and their respective entity managers, so long I maintain the copied EntityManager in the module's Angular scope. Editing and saving this way is, well…a breeze! :P (sorry)
The problem I’m having is when I switch to another Angular route, such as a login screen, and then come back to the home screen. Due to the complexities of each tabbed module, any Account Editor module I had opened before the routing took place must be reloaded from stored settings (even if the layout is stored in the cache). However, then there are two Breeze entity managers handling that account. The effect is that saving changes for a single Account Editor now commits twice (or however many times you’ve gone away and back from the home layout).
What I’m trying to figure out is how to get access to a specific EntityManager instance, from the client, once I navigate back to the home layout and the original instance reference (in the scope, in my case) is lost. In other words, is there an EntityManager collection that I can query to reuse or delete an instance? If I can grab a manager instance by name or other Id, I can simply reassign it to my scope without removing and recreating it.
In the event that I do need to remove the EntityManager, I can’t find anything anywhere to describe implementing something like a destroy() method…just a clear() method, which only clears the entities and does not remove the EntityManager from the client. Of course, if I'm going about this all wrong, please advise as to a better approach. Hopefully I've explained things clearly enough that someone can lend a possible solution.
SOLUTION
So, thanks to PW Kad's answer, I was able to reuse the entity managers instead of deleting and re-creating them, by adding them to an empty object collection on the $rootScope when they're initialized (still using the createEmptyCopy() method outlined above). This allows for access throughout the Angular app, without polluting the global namespace. I had already implemented a unique ID to be associated with each tab - and thus the modules in the content areas - so I appended that ID to create a name, such as 'EM_' + instanceId, for storage in the $rootScope.entityManagers object. Later, I can retrieve the EntityManager instance in the $rootScope, using this ID, which is found in each Account Editor's Angular Controller.
Here's the new code:
// NEW: Add an 'entityManagers' object to the $rootScope of my main app module
angular.module('app', [])
.run(['$rootScope', function($rootScope) {
$rootScope.entityManagers = {};
$rootScope.entityManagers.count = 0;
}]);
// In the dataServices factory for the main app module
var serviceName = "/breeze/accounts";
var ds = new breeze.DataService({
serviceName: serviceName,
hasServerMetadata: true
});
var masterManager = new breeze.EntityManager({
dataService: ds,
saveOptions: new breeze.SaveOptions({ allowConcurrentSaves: false })
});
function createManager(instanceId) {
// make a copy of the above EntityManager (with no cached entities)
var sandboxManager = masterManager.createEmptyCopy();
// NEW: Save the EntityManager instance to the $rootScope
$rootScope.entityManagers['EM_' + instanceId] = sandboxManager;
$rootScope.entityManagers.count++;
return sandboxManager;
}
// In the event that you want to delete the EntityManager from the $rootScope
function deleteManager(instanceId) {
var manager = $rootScope.entityManagers['EM_' + instanceId];
manager.clear();
delete $rootScope.entityManagers['EM_' + instanceId];
$rootScope.entityManagers.count--;
}
// And lastly, inside any Angular controller
$scope.instanceId = '1234'; // Dynamically-assigned at runtime
$scope.init = function() {
var manager = $rootScope.entityManagers['EM_' + $scope.instanceId];
if (manager === undefined) {
$scope.entityManager =
entityManagerService.createManager($scope.instanceId);
} else {
$scope.entityManager = manager;
}
}
$scope.getEntityById = function(id){
//use $scope.entityManager here to query database via Breeze
}
While I would still like to know where BreezeJS keeps its EntityManager collection, I have a valid solution. I hope it helps someone!
It seems like a poor design choice to reinstantiate a new entity manager every time you change pages as in effect you are losing the caching mechanism and sharing the entities across pages but if you must do so you can always do so fairly trivially with something like -
var manager = {};
manager = manager ? manager.clear() : new breeze.EntityManager();
or
manager = manager ? (function () { delete manager; return new breeze.EntityManager(); })() : new breeze.EntityManager();
or many other ways really.
I would recommend not doing that though and just doing something like this -
var manager = {};
// Some route activation logic
if (!manager) {
manager = new breeze.EntityManager();
}
Edit
Well the short answer is I am not 100% sure how the breeze object in the global namespace is referencing the entity manager. I don't think there is a collection of entity managers that the breeze object keeps, but I may be wrong. I don't see why calling delete on the entity manager isn't working but this should do what you are trying to do -
Somewhere in either one of your closures or in the global namespace create an object called entityManagers. Example -
window.entityManagers = {};
window.entityManagers.count = 0;
Then tack the manager while it is created onto that namespace. There is probably a more dynamic way to do this just giving some pseudo-code -
window.entityManagers.createNewManager = function (name) {
window.entityManagers[name] = new breeze.EntityManager();
}
window.entityManagers.createNewManager('ManagerNumber1');
And then when you want to dispose of a specific instance just clear it then delete it. Get the instance you want either by a variable reference or if you can't do that for some crazy reason just grab it off the affected entity -
window.entityManagers.deleteManager = function (name) {
window.entityManagers[name].clear();
delete window.entityManagers[name];
}
window.entityManagers.deleteManager('ManagerNumber1');
For all intents and purposes, as long as there no other references to that instance of the manager in other modules / controllers / whatever than this should delete the entityManager from the world. I still don't fully understand the use case so take it with a grain of salt.

Ember JS how to set up Application

I am an Ember noob and am trying to get it to work; however I am confused about the App.initialize() method.
It throws errors (it can't find the object App) if I use the following code:
App = Ember.Application.extend()
App.initialize()
However if I use the following code; it says initialize is being called twice.
App = Ember.Application.create()
App.initialize()
What is the best way to do this?
The Application no longer provides the initialize method. Instead you should use Application#deferReadiness and Application#advanceReadiness combined.
Example extracted from Ember's source code:
App = Em.Application.create();
App.deferReadiness();
jQuery.getJSON("/auth-token", function(token) {
App.token = token;
App.advanceReadiness();
});
Additionally, check the sample in jsfiddle:
window.App = Em.Application.create();
App.deferReadiness();
window.setTimeout(function() {
Em.TEMPLATES["application"] = Em.Handlebars.compile('<h1>App</h1> this is a template');
App.advanceReadiness();
}, 1500);
First, You have to understand the difference between create() and extend(). Easy way to understand is extend() method just extends the class of Ember.Application but create() method creates the instance of Ember.Application(). While creating the instance it runs the constructor. There are 3 ways to create the Ember.App and run it.
1
var App= Ember.Application.extend()
App.initialize()
2.
var App = Ember.Application.create()
This initialises as soon as u create object.
3
var App= Ember.Application.extend()
App.create()
To understand Ember Objects more go through this link. Understanding Ember.Object
Just create your application and let Ember initialize it.
All you need to do is:
App = Ember.Application.create()
The App will not be initialized immediately. It waits, at least, for DOM readiness and for the rest of your classes to be defined (by waiting until control is returned to the browser from the currently executed JavaScript).
If you want to defer it for other reasons, do something like this:
App.deferReadiness();
$.getJSON("/boot", function() { App.advanceReadiness(); });
This will wait to boot the app until the /boot Ajax call returns.
Just have a look here how to do this stuff:
http://emberjs.com/documentation/#toc_creating-a-namespace
How to bootstrap:
window.App = Ember.Application.create();
Without ever using ember.js, I would suggest that create and initialize both do initialization, that's why you get the latter error telling you it's inited twice.
And your first version is trying to extend the Application object, that is you create new functionality.
Ember "create" method accepts either no arguments, or an object containing values to initialize the newly instantiated object with, so you might also go like this below:
var appConfig = {
Token: token;
};
App = Ember.Application.create(appConfig);

Categories

Resources