拦截 WebSocket 消息

Intercept WebSocket messages

对于 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 如何实现呢?

简介

question/bounty/op 特意要求有信誉的消息来源。 我的建议是应该使用一个已知的经过验证的库,而不是推出自定义解决方案——它已经被社区使用、审核、分叉,并且通常被社区使用,并且托管在 github.[=18= 上]

第二种选择是自己动手(虽然不推荐),关于如何做到这一点有很多很好的答案,涉及 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 - function(data, url, wsObject):

Invoked just before calling the actual WebSocket's send() method.

This method must return data which can be modified as well.

wsHook.after - function(event, url, wsObject):

Invoked just after receiving the MessageEvent from the WebSocket server and before calling the WebSocket's onmessage Event Handler.

Websocket addEventListener

WebSocket对象支持.addEventListener().
请参阅:

在类似于您的解决方案中,window.XMLHttpRequest 被替换为提供 window.lastXhr 的包装版本,我们将 window.WebSockets 替换为提供 [=15= 的包装版本] 包含从此脚本之后创建的所有 websocket 收到的所有消息和时间戳。

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 可能已经存在。如果你需要保证所有的 websockets 都会被捕获,那么你需要尽快制作这个包装器。如果你不能,有一个不太好的技巧可以在它们发送消息后捕获已经存在的 websockets:

// 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);
}

它不太可信,因为如果他们不发送但接收,他们将不会被注意到。

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

yarn add socket.io

安装后,可以使用socket.io

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

you would need to make this wrapper as soon as possible

对于那个答案@brunoff 你是对的..你总是可以在服务器之前通过傀儡window逻辑使用你的功能,你可以劫持data 来自 MessageEvent 本身

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 示例

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

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

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

侦测传入消息修改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;

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