拦截WebSocket消息

9
使用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变量将保存最后一个响应。

但是如何在WebSocket的情况下实现呢?


我可以通过控制“MessageEvent”类来监听WebSocket消息 >:D - The Bomb Squad
6个回答

25

你需要尽快制作这个包装器。

@brunoff,你是正确的,你可以在木偶窗口逻辑之前始终使用自己的函数,或者你可以直接劫持MessageEvent中的data

function listen(fn){
  fn = fn || console.log;

  let property = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  
  const data = property.get;

  // wrapper that replaces getter
  function lookAtMessage() {

    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))

您可以尝试将代码放入此页面上的控制台中运行,然后运行它们的WebSocket示例。


2
唯一有效的解决方案是... - Alex
@The Bomb Squad 恭喜你们!!!你们值得拥有这个荣誉! - Menelaos
1
优雅的方法 - brunoff
这是什么魔法,我不太理解为什么它能起作用,但这是唯一对我有效的解决方案,谢谢!快速问题 - 这个拦截器只拦截接收到的消息还是也拦截发送的消息在 WebSocket 上? - Jin
@Jin,它将拦截接收到的消息,然后在传递给 listen 的函数中,您可以使用 event=>event.socket.addEventListener('message',forIncomingMessages)。要获取从发送的消息中的 WebSocket 对象,请参见 brunoff's answer 中的第二个片段。 - The Bomb Squad

10
拦截消息,需要监听 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;

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


@brunoff 嗯,我尝试添加了一个部分,让你可以修改消息数据,在某些情况下这非常方便,可以模拟功能(特别是如果你无法控制页面功能,你可以拦截并注入自己的数据)。对于我的投票,抱歉。我刚刚给你的答案点了赞,确实很好。 - Ionică Bizău
要修改消息数据,您必须使arguments[0]成为一个完全不同的对象,因为arguments[0].data='someFakeText'无法实现。 - The Bomb Squad
@TheBombSquad 覆盖整个参数并不是一个好的建议,因为那是一个 MessageEvent 实例。保留引用,并仅覆盖 data 属性会更加顺畅。 - Ionică Bizău
@IonicăBizău 我忘记了你可以直接使用 Object.defineProperty 将数据键定义到实例中。但是,仅使用简单的 arguments[0].data="someOtherValue" 是无法覆盖它的。 - The Bomb Squad

2

简介

这个问题/赏金/操作特别要求使用可靠的来源。我的建议是使用一个已知的、经过验证的库,这个库已经被社区使用、审计、分叉,并且通常托管在Github上,而不是开发自定义解决方案。

enter image description here

第二个选项是自行编写(虽然不建议),有许多关于如何使用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):
在调用实际的 WebSocket 的 send() 方法之前调用。
这个方法必须返回可以修改的数据。
wsHook.after - function(event, url, wsObject):
在从 WebSocket 服务器接收 MessageEvent 之后,在调用 WebSocket 的 onmessage 事件处理程序之前调用。
WebSocket 对象支持 .addEventListener()。
请参见:Websocket Javascript 的多个处理程序

2
在与您类似的解决方案中,我们将 window.XMLHttpRequest 替换为一个包装版本,该版本提供了 window.lastXhr,现在我们将 window.WebSockets 替换为一个包装版本,该版本提供了 window.WebSocketMessages,其中包括此脚本创建后接收到的所有 WebSockets 的所有消息和时间戳。
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);
}

由于如果他们不发送而只接收,那么他们将不会被注意到,因此它并不是很可靠。


1

如果你遇到了以上解决方案无法正常工作的问题(就像我一样):

显然,WebSocket对象现在是JavaScript类而不是普通的原型,一些现代网站真的不喜欢你覆盖这个类并把它变回原型。

幸运的是,你只需创建一个扩展旧类的新类并将其覆盖即可!

window.WebSocketUnchanged = window.WebSocket;


class CustomWebSocket extends window.WebSocket {
    constructor(...args) {
        super(...args);

        this.onmessageListenerCallbackOriginal = function() {};

        super.onmessage = function(event) {
            //Runs as soon as a WebSocket message is received
            if (this.interceptReceivedMessage(event)) {
                this.onmessageListenerCallbackOriginal(event);
            }
        }


        Object.defineProperty(this, "onmessage", {
            set(func) {
                //Save this event handler in our own variable
                //but don't let it touch the original WebSocket
                //as we want to run our own code before running
                //the onmessage event handler we are given
                this.onmessageListenerCallbackOriginal = func;
            }
        });


    }

    onmessage(event) {
        console.log("onmessage was called but hasn't been overridden");
    }

    interceptReceivedMessage(event) {
        //Intercept the received message and do whatever you like with it
        console.log("Intercepted received message!");
        console.log(event);

        return true; //return true to allow the request to be received
    }

    send() {
        console.log(arguments);
        //Intercept the sent message and do whatever you like with it
        super.send(...arguments);
    }

    addEventListener(type, callback) {
        //Intercepts event handlers sent via addEventListener
        if (type == 'message') {
            super.addEventListener('message', (event) => {
                if (this.interceptReceivedMessage(event)) {
                    callback(event);
                }
            })
        } else {
            super.addEventListener(type, callback);
        }
    }


}

window.WebSocket = CustomWebSocket;



console.log("Finished adding interceptor to WebSocket");

希望这能帮到你!


1

如果你正在使用Node.js,那么可以使用socket.io

yarn add socket.io

安装完成后,您可以使用 socket.io中间件
io.use(async (socket, next) => {
  try {
    const user = await fetchUser(socket);
    socket.user = user;
  } catch (e) {
    next(new Error("unknown user"));
  }
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接