-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
injected.old.js
284 lines (232 loc) · 9.2 KB
/
injected.old.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
console.log(`injected.js has been initiated`);
const pp = stuff => JSON.stringify(stuff, null, 2);
// messaging function (to communicate with content script which will send it to frontend)
const sendToContentScript = (messageHead, messageBody) => {
try {
window.postMessage({deValtioMessage: [messageHead, messageBody]});
return true;
} catch (err) {
console.dir(err);
return false;
}
};
// disable locking down object properties for fiberNode (and any other) objects
Object.preventExtensions = () => true;
// DECLARATIONS GO HERE
// func to get component (i.e. constructor) name
// this returns one or two letter names in prod mode but
// this can be adapted to still get proper component names if the site uses source maps
// and we add source map parsing
const getFiberNodeName = (node) => {
// root node
if (node.tag === 3) return 'fiberRoot';
// functional or class component
if (node.tag === 0 || node.tag === 1) return node.type.name;
// host component (renders to browser DOM)
if (node.tag === 5) {
return node.stateNode.className ? `${node.type}.${node.stateNode.className}` : node.type;
}
// everything else
if (typeof node.type === 'string') return node.type;
if (typeof node.type === 'function') return node.type.name;
if (typeof node.type === 'symbol') return node.type.toString();
};
// DeValtioNode constructor
function DeValtioNode(fiberNode, parentNode = null) {
this.tag = fiberNode.tag;
this.deValtioID = fiberNode.deValtioID;
this.index = fiberNode.index;
this.componentName = getFiberNodeName(fiberNode, parentNode);
this.hasProps = fiberNode.memoizedProps ? true : false;
this.hasState = fiberNode.memoizedState ? true : false;
};
// declare fiberRoot object
let fiberRoot;
// declare deValtioNodes array here so we can access it from browser console
const deValtioNodes = [];
const origProxy = Proxy;
const objectHandler = {
get: (target, prop, receiver) => {
console.log(`target: ${target}, prop: ${prop}, receiver: ${receiver}`);
Reflect.get(...arguments);
}
};
const proxyHandler = {
construct: (proxyObj, targetObj, handler) => {
let caller;
try {
throw new Error();
} catch(err) {
// console.log(`err.stack is ${pp(err.stack)}`);
caller = err.stack.split("at ")[2].split(" (")[0];
}
// console.log(`Proxy caller is: ${caller} and params are ${pp(args)}`)
// console.dir(args)
// console.dir(Reflect.ownKeys(args[1]));
// console.dir(args[1]['f'])
// console.dir(args[1].constructor)
// console.log(Reflect.ownKeys(args[-1]));
// args.forEach((arg) => {
// console.log(`arg is ${arg}`);
// arg = new Proxy(arg, objectHandler);
// });
// // return new origProxy(target, args, receiver);
return Reflect.construct(proxyObj, targetObj, handler);
}
};
Proxy = new origProxy(origProxy, proxyHandler);
// Proxy = (target, args, receiver) => {
// console.log(`target: ${target}, args: ${args}, receiver: ${receiver}`);
// Proxy = () => {
// console.log(`params are: ${arguments}`);
// // return new origProxy(arguments);
// };
// Proxy.prototype = origProxy.prototype;
// generateDeValtioID
const generateDeValtioID = (fiberNode, prevNode = null) => {
try {
let width = fiberNode.index;
// check if fiberNode already has deValtioID
fiberNode.deValtioID ? currentID = fiberNode.deValtioID : currentID = null;
// func to check if currentID and generatedID mismatch. If they do, notify via console log
const checkMismatch = function(newID) {
if (currentID && currentID !== newID) {
//throw new Error('Generated ID mismatch. Existing ID is ${currentID} and generated ID is ${newID');
console.log('Generated ID mismatch. Existing ID is ${currentID} and generated ID is ${newID');
}
}
// detect fiberRoot (tag is 3 and return property is null)
if (fiberNode.return === null && fiberNode.tag === 3 && fiberNode.index === 0) {
fiberNode.deValtioID = '0,0';
checkMismatch(fiberNode.deValtioID);
return fiberNode.deValtioID;
}
// throw exception if return property has not been set and node isn't fiberRoot
if (fiberNode.return === null) {
throw new Error('node is not fiberRoot but return property is null.');
}
// get current depth by getting the depth of return and incrementing by one
// let depth = Number(fiberNode.return.deValtioID.split(':').pop().split(',')[0]) + 1;
// width is just the index property of the fiberNode
// let width = fiberNode.index;
// detect if current node is child of sibling
if (fiberNode.return.deValtioID && fiberNode.return.index > 0) {
const returnName = fiberNode.return.deValtioID;
fiberNode.deValtioID = `${returnName}:1,${width}`;
checkMismatch(fiberNode.deValtioID);
return fiberNode.deValtioID;
}
// get depth by parsing name of return, adding one to the last depth, and appending the width
let returnName = fiberNode.return.deValtioID;
if (prevNode && fiberNode.return !== prevNode) returnName = prevNode.deValtioID;
const splitReturnName = returnName.split(':');
const returnNodeDepth = Number(splitReturnName.pop().split(',')[0]);
const newName = splitReturnName.join(':') ?
splitReturnName.join(':') + ':' + (returnNodeDepth + 1) + ',' + width :
(returnNodeDepth + 1) + ',' + width;
fiberNode.deValtioID = newName;
checkMismatch(fiberNode.deValtioID);
return fiberNode.deValtioID;
} catch (err) {
console.dir(fiberNode);
throw err;
}
}
const hijackFiberNodePrototype = () => {
// check if we have a fiberRoot
if (!fiberRoot) return false;
// get FiberNode prototype object
fiberNodePrototype = Object.getPrototypeOf(fiberRoot);
newProperties = {
_return: {
value: null,
writable: true,
enumerable: false,
configurable: true
},
return: {
get: function() {
return this._return;
},
set: function(fiber) {
// check if return is set to null or anything else that's not a fiberNode
if (!(fiber instanceof fiberNodePrototype.constructor)) {
this._return = fiber;
return this;
}
console.log(`return value being set on fiberNode`);
if (this.deValtioID) {
console.log(`this fiberNode already exists and has the name: ${this.deValtioID}`);
console.log(`the return is being set to: ${getFiberNodeName(fiber)}`);
console.dir(fiber);
}
this._return = fiber;
if (fiber.deValtioID) {
console.log(`return node has deValtioID (${fiber.deValtioID}) so pushing this to deValtioNodes`)
generateDeValtioID(this, fiber);
deValtioNodes.push(new DeValtioNode(this));
}
return this;
}
}
};
return Object.defineProperties(fiberNodePrototype, newProperties);
};
document.onreadystatechange = () => {
if (document.readyState === 'complete') {
const getFiberRoot = () => {
const reactRoots = [];
document.querySelectorAll('*').forEach((node) => {
if (node._reactRootContainer) reactRoots.push(node);
});
if (reactRoots[0]) {
console.log(`React Root Found`);
fiberRoot = reactRoots[0]._reactRootContainer._internalRoot.current;
// repeating message to test comms with front end
setInterval(() => window.postMessage({message: 'This is a React App'}), 2000)
} return fiberRoot;
};
//tree parsing part.
// climb initial Tree, add valtioID to fiberNode properties
// we should only need to do this once per page load and, after that,
// if we hijack the fiberNode constructor we can have the React Reconciler
// generate DeValtioNodes on the fly.
// valtioID format:
// 0,0:
const climbFiber = (fiberNode, callback, prevNode = null) => {
callback(fiberNode, prevNode);
// climb child
try {
if (fiberNode.child) climbFiber(fiberNode.child, callback, fiberNode);
} catch (err) {
console.log(`Recursive call to child node failed. Node before failed call is:`);
console.dir(fiberNode);
throw err;
};
// climb sibling
try {
if (fiberNode.sibling) climbFiber(fiberNode.sibling, callback, fiberNode);
} catch (err) {
console.log(`Recursive call to sibling node failed. Node before failed call is:`);
console.dir(fiberNode);
throw err;
}
}
setTimeout(() => {
fiberRoot = getFiberRoot();
if (fiberRoot) {
climbFiber(fiberRoot, (node, prevNode) => {
prevNode ? generateDeValtioID(node, prevNode) : generateDeValtioID(node);
deValtioNodes.push(new DeValtioNode(node));
});
sendToContentScript('deValtioTree', deValtioNodes);
console.dir(deValtioNodes);
console.log(`${deValtioNodes.length} fiberNodes found.`)
console.log(`Number of nodes with props: ${deValtioNodes.filter(node => node.hasProps).length}`);
console.log(`Number of nodes with state: ${deValtioNodes.filter(node => node.hasState).length}`);
// console.log(`Hijacking fiberNode prototype return property...`);
// hijackFiberNodePrototype();
}
}, 1000);
}
};