【问题标题】:Intercept WebSocket messages拦截 WebSocket 消息
【发布时间】:2022-01-09 07:41:09
【问题描述】:

使用 ajax 请求可以使用以下代码完成:

let oldXHROpen = window.XMLHttpRequest.prototype.open;
window.lastXhr = '';
window.XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
  this.addEventListener('load', function() {
    window.lastXhr = this.responseText;
  });
  return oldXHROpen.apply(this, arguments);
};

lastXhr 变量将保存最后的响应。

但是如何在 websockets 上也实现这一点呢?

【问题讨论】:

  • 我可以通过控制MessageEvent类来监听websocket消息>:D

标签: javascript node.js websocket electron


【解决方案1】:

简介

问题/赏金/操作专门要求有信誉的来源。 我的建议不是推出自定义解决方案,而是应该使用一个已知的经过验证的库——它已经被社区使用、审核、分叉,并且通常被社区使用,并且托管在 github 上。

第二种选择是自己动手(虽然不推荐),关于如何使用addEventListener,有很多优秀的答案

wshook

Wshook 是一个库(托管在 github 上),可以轻松拦截和修改 WebSocket 请求和消息事件。它已被加星标和分叉多次。

免责声明:我与具体项目没有任何关系。强文本

例子:

wsHook.before = function(data, url, wsObject) {
    console.log("Sending message to " + url + " : " + data);
}

// Make sure your program calls `wsClient.onmessage` event handler somewhere.
wsHook.after = function(messageEvent, url, wsObject) {
    console.log("Received message from " + url + " : " + messageEvent.data);
    return messageEvent;
}

从文档中,您会发现:

wsHook.before - 函数(数据、url、wsObject)

之前调用过 调用实际 WebSocket 的 send() 方法。

此方法必须返回也可以修改的数据。

wsHook.after - 函数(事件、url、wsObject):

在之后调用 从 WebSocket 服务器和之前接收 MessageEvent 调用 WebSocket 的 onmessage 事件处理程序。

Websocket addEventListener

WebSocket 对象支持.addEventListener()
请看:Multiple Handlers for Websocket Javascript

【讨论】:

    【解决方案2】:

    在与您的解决方案类似的解决方案中,window.XMLHttpRequest 被替换为提供 window.lastXhr 的包装版本,我们将 window.WebSockets 替换为提供来自所有 websocket 的所有消息和时间戳的 window.WebSocketMessages 的包装版本在此脚本之后创建。

    window.watchedWebSockets = [];
    window.WebSocketMessages = [];
    
    function WebSocketAttachWatcher(websocket) {
        websocket.addEventListener("message", (event)=>window.WebSocketMessages.push([event.data,Date.now()]));
        window.watchedWebSockets.push(websocket);
    }
    
    // here we replace WebSocket with a wrapped one, that attach listeners on 
    window.WebSocketUnchanged = window.WebSocket;
    window.WebSocket = function(...args) {
        const websocket = new window.WebSocketUnchanged(...args);
        WebSocketAttachWatcher(websocket);
        return websocket;
    }
    

    与您的 XMLRequest 案例不同,websocket 可能已经存在。如果您需要保证所有 websocket 都会被捕获,那么您需要尽快制作这个包装器。如果你不能,那么一旦它们发送消息,就有一个不太好的技巧来捕获已经存在的 websocket:

    // here we detect existing websockets on send event... not so trustable
    window.WebSocketSendUnchanged = window.WebSocketUnchanged.prototype.send;
    window.WebSocket.prototype.send = function(...args) {
        console.log("firstsend");
        if (!(this in window.watchedWebSockets))
            WebSocketAttachWatcher(this);
        this.send = window.WebSocketSendUnchanged; // avoid passing here again on next send
        window.WebSocketSendUnchanged.call(this, ...args);
    }
    

    这不是那么可信,因为如果他们不发送但接收,他们就会被忽视。

    【讨论】:

      【解决方案3】:

      如果你使用的是nodejs,那么你可以使用socket.io

      yarn add socket.io
      

      安装后可以使用socket.iomiddleware

      io.use(async (socket, next) => {
        try {
          const user = await fetchUser(socket);
          socket.user = user;
        } catch (e) {
          next(new Error("unknown user"));
        }
      });
      

      【讨论】:

        【解决方案4】:

        您需要尽快制作此包装器

        对于那个答案@brunoff,你是对的.. 你总是可以在服务器之前通过 puppet 窗口逻辑使用你的函数,或者你可以从MessageEvent 本身劫持data p>

        function listen(fn){
          fn=fn||console.log
          let property=Object.getOwnPropertyDescriptor
          (MessageEvent.prototype,"data")
          const data=property.get
          function lookAtMessage(){ //to replace get function
            let socket=this.currentTarget instanceof WebSocket
            if(!socket){return data.call(this)}
            let msg=data.call(this)
            Object.defineProperty(this,"data",{value:msg}) //anti-loop
            fn({data:msg,socket:this.currentTarget,event:this})
            return msg
          }
          property.get=lookAtMessage
          Object.defineProperty
          (MessageEvent.prototype,"data",property)
        }
        listen( ({data})=>console.log(data) )
        

        您可以尝试输入代码并在this page 的控制台中运行它,然后运行他们的 websocket 示例

        【讨论】:

        • 唯一有效的解决方案...
        • @炸弹小队恭喜!!!你活该!
        • :0 感谢@Menelaos
        • 优雅的方法
        【解决方案5】:

        要拦截消息,您必须监视 onmessage = fnaddEventListener("message", fn) 调用。

        为了能够修改onmessage,我们必须首先覆盖全局WebSocket。下面是拦截传入的消息,但您可以通过类似的方式窥探send 方法来拦截传出消息(客户端发送到服务器的消息)。

        我在使用 Firebase 的页面上对此进行了测试,它运行良好,但您必须在其他脚本之前对其进行初始化,以确保 websocket 库(可以是 socket.io、ws 等)正在使用覆盖的 @987654326 @构造函数。

        监视传入消息修改data

        最终您可以在调用真正的消息侦听器之前覆盖data - 如果您无法控制页面功能并希望在消息侦听器中注入您自己的数据,这将变得很方便。

        const OriginalWebsocket = window.WebSocket
        const ProxiedWebSocket = function() {
          console.log("Intercepting web socket creation")
        
          const ws = new OriginalWebsocket(...arguments)
        
          const originalAddEventListener = ws.addEventListener
          const proxiedAddEventListener = function() {
            if (arguments[0] === "message") {
              const cb = arguments[1]
              arguments[1] = function() {
                // Here you can get the actual data from the incoming messages
                // Here you can even change the data before calling the real message listener
                Object.defineProperty(e, "data", { value: 'your injected data' })
                console.log("intercepted", arguments[0].data)
                return cb.apply(this, arguments)
              }
            }
            return originalAddEventListener.apply(this, arguments)
          }
          ws.addEventListener = proxiedAddEventListener
        
          Object.defineProperty(ws, "onmessage", {
            set(func) {
              return proxiedAddEventListener.apply(this, [
                "message",
                func,
                false
              ]);
            }
          });
          return ws;
        };
        
        window.WebSocket = ProxiedWebSocket;
        

        如果不需要修改数据,可以按照第二部分的答案。

        在不修改数据的情况下监视传入消息

        如果您想只收听消息,而不覆盖数据,事情就更简单了:

        const OriginalWebsocket = window.WebSocket
        const ProxiedWebSocket = function() {
          const ws = new OriginalWebsocket(...arguments)
          ws.addEventListener("message", function (e) {
            // Only intercept
            console.log(e.data)
          })
          return ws;
        };
        window.WebSocket = ProxiedWebSocket;
        

        监视传出消息

        以非常相似的方式,您可以代理 send 方法,该方法用于向服务器发送数据。

        const OriginalWebsocket = window.WebSocket
        const ProxiedWebSocket = function() {
          const ws = new OriginalWebsocket(...arguments)
          const originalSend = ws.send
          const proxiedSend = function() {
            console.log("Intercepted outgoing ws message", arguments)
            // Eventually change the sent data
            // arguments[0] = ...
            // arguments[1] = ...
            return originalSend.apply(this, arguments)
          }
          ws.send = proxiedSend
          return ws;
        };
        window.WebSocket = ProxiedWebSocket;
        

        如果有任何不清楚的地方,请随时提出任何问题。

        【讨论】:

        • @brunoff 好吧,我尝试添加可以修改消息数据的部分,这对于在某些情况下模拟功能非常方便(特别是如果您无法控制页面功能,您可以拦截并注入您自己的数据)。很抱歉投反对票。我刚刚对您的回答表示赞同,这确实非常好。
        • modify the message data 你必须让arguments[0] 成为一个完全不同的对象,因为arguments[0].data='someFakeText' 不会这样做
        • @TheBombSquad 不建议覆盖整个参数,因为这是一个MessageEvent 实例。保留引用并仅覆盖 data 属性会更顺畅。
        • @IonicăBizău 我忘了你可以将数据键 Object.defineProperty 放入实例中。但是是的,一个简单的 arguments[0].data="someOtherValue" 单独不会覆盖它
        猜你喜欢
        • 1970-01-01
        • 2020-03-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-19
        • 2012-03-17
        • 2013-04-05
        • 1970-01-01
        相关资源
        最近更新 更多