背景
事情是这样,公司的服务器登录使用的是webshell
用的xterm.js
框架在升级之前,在window
上下文是有一些全局变量,像ws
就是这个webshell
的WebSocket
对象。
之前基于这个ws
对象,写了一些发送指令给服务器的油猴脚本,但是后来这个页面升级成react
+ webpack
了,这样没有全局变量了。也就没法完成之前的工作了。
解法1 自行替换WebSocket
其实本质是要WebSocket
对象,而这个对象是由全局函数new WebSocket(url)
产生的。那么如果能对这个函数进行拦截,然后做相应的处理是不是就完事大吉。
var OldWs = window.WebSocket;
function NewWs(url, protocols) {
const wsInstance = OldWs(url, protocols);
// 实现原来功能之后,把全局变量ws赋值
window.ws = wsInstance;
return wsInstance;
}
// 原型链 和 静态属性 都copy过来
NewWs.prototype = OldWs.prototype;
Object.setPrototypeOf(NewWs, OldWs);
window.WebSocket = NewWs;
解法2 Proxy增强WebSocket
与解法1一样思路的另一种解法,直接使用js中Proxy
函数进行代理。
const OldWs = window.WebSocket;
// 创建一个 Proxy 构造函数来拦截 WebSocket 的实例化
const NewWs = new Proxy(OldWs, {
construct(target, args) {
const wsInstance = new target(...args);
// 将实例附加到全局变量 ws
window.ws = wsInstance;
return wsInstance;
}
});
// 替换原生的 WebSocket 构造函数
window.WebSocket = NewWs;
Proxy
显然更贴切一些,不需要处理原型链和属性,他的用法详细可参考MDN,const p = new Proxy(target, handler)
。
这里我们只需要捕捉构造函数,所以只需要在hanler中配置construct
,这个属性的定义可以参考。
解法2算是解法1的优雅版本。
解法3 解析react组件
前面两个方案都只适用于对已知函数WebSocket
的代理,但是实际我发现我还需要xterm
中的term
对象,来捕捉当前webshell
中选中的文字,因为默认的window.getSelection
无法获取shell中的选中文本,只能用term.getSelection
。
这就只能获取到这个term
对象了,他是一个xterm
中定义的Terminal
类型的对象,但是xterm
已经整个被WebPack
给打包到bundle.js
中了,很难对他进行注入了。
这时候,我使用React
的chrome组件发现当前页面结构非常简单,并且很容易就找到了term
所在的组件,并且上面所需要的WebSocket
实例也在这里面。
那接下来就是如何从一个生产环境的react页面中,解析其中的组件了,这个问题显然抛给GPT
在合适不过了,我询问GPT
如何在已有的react页面中获取指定名字的组件对象,答案就出来了,稍作加工如下。
//获取React根节点并遍历组件树
function getReactComponent(root, targetComponentName) {
const reactInstance = root._reactRootContainer._internalRoot.current;
function findcomponent(node) {
if (node.elementType &&
node.elementType.name === targetComponentName) {
return node;
}
const children = node.child;
while(children){
const result = findComponent(children);
if(result) return result;
children= children.sibling;
}
return null;
}
return findComponent(reactInstance);
}
var root = document.getElementById("root");
var term = getReactComponent(root, "w").memoizedProps.terminal
var ws = term.websocket;
原来react
应用渲染的<div id="root"></div>
根节点上会追加一个_reactRootContainer
的属性,通过这个属性就能把所有的react组件节点都遍历出来,估计react chrome插件就是这么干的。对于每个子节点的遍历,是通过sibling
函数,类似next
的作用;对于每个node的名字则是放到了elementType.name
中;每个node的属性则是放到了memoizedProps
注意不是props
,后者是我们开发的时候的叫法。
这里还有个问题就是,w
这个名字是打包的时候随机生成的一个简单hash值,他之后网站更新可能不叫w
了,所以最好改成一些特征校验,比如说要找一个组件,组件的memoizedProps.terminal != null
.
// 根据条件获取组件
function getReactComponent(root, checkFn) {
const reactInstance = root._reactRootContainer._internalRoot.current;
function findcomponent(node) {
if (checkFn(node)) {
return node;
}
const children = node.child;
while(children){
const result = findComponent(children);
if(result) return result;
children= children.sibling;
}
return null;
}
return findComponent(reactInstance);
}
var root = document.getElementById("root");
var term = getReactComponent(root, node=>node.memoizedProps && node.memoizedProps.terminal).memoizedProps.terminal
var ws = term.websocket;
启发
通过解法3,给我打开了新世界的大门,之前写的一些油猴脚本,要么就是一些老式网站的增强,要么就是一些全局层面的函数。对于react页面,因为觉得被bundle和compress了,觉得不太能解剖了。结果不经意间问了gpt,发现原来是有解法的。
由此,对于很多工作中的内部管理系统的页面,基本都是react
的,很多地方都可以进行增强了。后续有了新的感悟会更新到这里。