Inspecting multiple WebSocket connections at the same time - javascript

I am using solutions provided in following topics to inspect WebSockets traffic (messages) on the web page, which I do not own (solely for learning purposes):
Inspecting WebSocket frames in an undetectable way
Listening to a WebSocket connection through prototypes
https://gist.github.com/maskit/2252422
Like this:
(function(){
var ws = window.WebSocket;
window.WebSocket = function (a, b, c) {
var that = c ? new ws(a, b, c) : b ? new ws(a, b) : new ws(a);
that.addEventListener('open', console.info.bind(console, 'socket open'));
that.addEventListener('close', console.info.bind(console, 'socket close'));
that.addEventListener('message', console.info.bind(console, 'socket msg'));
return that;
};
window.WebSocket.prototype=ws.prototype;
}());
The issue with the provided solutions is that they are listening on only 1 of 3 WebSocket connections ("wss://..."). I am able to see in the console the messages that I receive or send, but only for one connection.. Is there something I am missing? Is it possible that two other service are any different and prohibiting the use of prototype extension technique?
p.s. I will not provide an URL to the web resource that I am doing my tests on, in order to avoid possible bans or legal questions.

Okay, since it's been weeks and no answers, then I will post a solution which I ended up using.
I have built my own Chrome extension that listens to WebSocket connections and forwards all requests and responses to my own WebSocket server (which I happen to run in C#).
There are some limitations to this approach. You are not seeing the request header or who is sending the packets.. You are only able to see the payload and that is it. Also you are not able to modify the contents in any way or send your own requests (remember - you have no access to header metadata). Naturally, another limitation is that you have to be running Chrome (devtools APIs are used)..
Some instructions.
Here is how you attach debugger to listen to network packets:
chrome.debugger.attach({ tabId: tabId }, "1.2", function () {
chrome.debugger.sendCommand({ tabId: tabId }, "Network.enable");
chrome.debugger.onEvent.addListener(onTabDebuggerEvent);
});
Here is how you catch them:
function onTabDebuggerEvent(debuggeeId, message, params) {
var debugeeTabId = debuggeeId.tabId;
chrome.tabs.get(debugeeTabId, function (targetTab) {
var tabUrl = targetTab.url;
if (message == "Network.webSocketFrameSent") {
}
else if (message == "Network.webSocketFrameReceived") {
var payloadData = params.response.payloadData;
var request = {
source: tabUrl,
payload: params.response.payloadData
};
websocket.send(JSON.stringify(request));
}
});
}
Here is how you create a websocket client:
var websocket = new WebSocket("ws://127.0.0.1:13529");
setTimeout(() => {
if (websocket.readyState !== 1) {
console.log("Unable to connect to a WebsocketServer.");
websocket = null;
}
else {
console.log("WebsocketConnection started", websocket);
websocket.onclose = function (evt) {
console.log("WebSocket connection got closed!");
if (evt.code == 3001) {
console.log('ws closed');
} else {
console.log('ws connection error');
}
websocket = null;
};
websocket.onerror = function (evt) {
console.log('ws normal error: ' + evt.type);
websocket = null;
};
}
}, 3000);
Creating the server is outside the scope of this question. You can use one in Node.js, C# or Java, whatever is preferable for you..
This is certainly not the most convenient approach, but unlike java-script injection method - it works in all cases.
Edit: totally forgot to mention. There seems to be another way of solving this, BUT I have not dig into that topic therefore maybe this is false info in some way. It should be possible to catch packets on a network interface level, through packet sniffing utilities. Such as Wireshark or pcap. Maybe something I will investigate further in the future :)

Related

Websocket streams check connection

With the following code i open a stream connection to the Binance crypto exchange:
let adress = 'wss://stream.binance.com:9443/ws/btcusdt#kline_1h';
const ws = new WebSocket(adress);
If i make this call for different crypto currencys then i have later a few streams open, i want to know how can i check the current open streams, is there something like a function or parameter where i can see for which currencys i have a open stream running?
because i also have the problem that it looks like streams are stopping sometimes and i dont have a good solution for checking which streams have stop and how to receonnect them. My idea is now to first find a way how to check which streams are running and then maybe if one streams is stop i will just send the connection request again.
In javascript there is a websocket events thing, so all you need is to
ws.onclose = function(event) {
ws = new WebSocket(adress);
//just reopen it
};
Or, for more safety, you can
ws.onclose = function(event) {
if (event.wasClean) {
ws = new WebSocket(adress);
} else {
console.log('Connection error!');
//or whatever u want
}
};
Sorry for this stupid styling, I'm newbie there
If you have your ws variable, then checking whether the websocket is open and alive is done with
if(ws && ws.readyState === 1){
// is opened
}
For other states of the websocket, see the docs.
If you want to receive push messages from the server, you need to keep the ws connection open. If not, you can close the ws after a query and reopen it then for another query. You should wait for the closed state ws.readyState === 3 before reopening.
If you need to keep all ws connections open, then you need a list of ws Objects. You push new objects to the list:
let ws_list = [] // global list of ws objects
let create_connection = function(url){
try{
ws_list.push(new WebSocket(url));
} catch(err){
console.log(err, url);
}
}
let do_something = function(){
for(let ws of ws_list){
// do something with the ws object
}
}

How to control the XMLHttpRequest object on an HTML5 Web Worker?

I have a page which will normally overrides window.XMLHttpRequest with a wrapper that does a few extra things like inserting in headers on certain requests.
I have some functionality in a 3rd party library that uses HTML5 Worker, and we are seeing that this request does not use the XMLHttpRequest wrapper object. So any request that this library makes is missing the required headers, and so the request will fail.
Is there a way to control the XMLHttpRequest that any Worker the current thread creates?
This 3rd party library code looks like this:
function createWorker(url) {
var worker = new Worker(url);
worker.onmessage = function (e) {
if (e.data.status) {
onprogress(e.data.status);
} else if (e.data.error) {
onerror(e.data.error);
} else {
exportUtils.saveFile(new Blob([e.data]), params.fileName);
onfinish();
}
};
worker.postMessage(params); // window.location.origin +
return worker;
}
The Javascript that is returned by the URL variable above contains code like this:
return new Promise(function(t, r) {
var n = new XMLHttpRequest
, a = "batch_" + o()
, u = e.dataUrl.split(e.serviceUrl)[1]
, c = [];
n.onload = function() {
for (var e = this.responseText, n = this.responseText.split("\r\n"), o = 0, a = n.length, i = a - 1; o < a && "{" !== n[o].slice(0, 1); )
o++;
for (; i > 0 && "}" !== n[i].slice(-1); )
i--;
n = n.slice(o, i + 1),
e = n.join("\r\n");
try {
var u = JSON.parse(e);
t(u)
} catch (t) {
r(s + e)
}
}
,
n.onerror = function() {
r(i)
}
,
n.onabort = function() {
r(i)
}
,
n.open("POST", e.serviceUrl + "$batch", !0),
n.setRequestHeader("Accept", "multipart/mixed"),
n.setRequestHeader("Content-Type", "multipart/mixed;boundary=" + a);
for (var p in e.headers)
"accept" != p.toLowerCase() && n.setRequestHeader(p, e.headers[p]);
c.push("--" + a),
c.push("Content-Type: application/http"),
c.push("Content-Transfer-Encoding: binary"),
c.push(""),
c.push("GET " + u + " HTTP/1.1");
for (var p in e.headers)
c.push(p + ":" + e.headers[p]);
c.push(""),
c.push(""),
c.push("--" + a + "--"),
c.push(""),
c = c.join("\r\n"),
n.send(c)
}
)
The answer is both a soft "no" and an eventual "yes".
When a piece of code runs in a different context (like a webworker or an iframe), you do not have direct control of its global object (1).
What's more, XMLHttpRequest isn't the only way to send out network requests - you have several other methods, chief among them the fetch api.
However, there's a relatively new kid in block called Service Workers which can help you quite a bit!
Service workers
Service workers (abbrev. SWs) are very much like the web workers you already know, but instead of only running in the current page, they continue to run in the background as long as your user stays in your domain. They are also global to your entire domain, so any request made from your site will be passed through them.
Their main purpose in life is reacting to network requests, usually used for caching purposes and offline content, serving push notifications, and several other niche uses.
Let's see a small example (note, run these from a local webserver):
// index.html
<script>
navigator.serviceWorker.register('sw.js')
.then(console.log.bind(console, 'SW registered!'))
.catch(console.error.bind(console, 'Oh nose!'));
setInterval(() => {
fetch('/hello/');
}, 5000);
</script>
// sw.js
console.log('Hello from a friendly service worker');
addEventListener('fetch', event => {
console.log('fetch!', event);
})
Here we're registering a service worker and then requesting a page every 5 seconds. In the service worker, we're simple logging each network event, which can be caught in the fetch event.
On first load, you should see the service worker being registered. SWs only begin intercepting requests from the first page after they were installed...so refresh the page to begin seeing the fetch events being logged. I advise you to play around with the event properties before reading on so things will be clearer.
Cool! We can see from poking around with the event in the console that event.request is the Request object our browser constructed. In an ideal world, we could access event.request.headers and add our own headers! Dreamy, isn't it!?
Unfortunately, request/response headers are guarded and immutable. Fortunately, we are a stubborn bunch and can simply re-construct the request:
// sw.js
console.log('Hello from a friendly service worker');
addEventListener('fetch', event => {
console.log('fetch!', event);
// extract our request
const { request } = event;
// clone the current headers
const newHeaders = new Headers();
for (const [key, val] of request.headers) {
newHeaders.append(key, val);
}
// ...and add one of our own
newHeaders.append('Say-What', 'You heard me!');
// clone the request, but override the headers with our own
const superDuperReq = new Request(request, {
headers: newHeaders
});
// now instead of the original request, our new request will take precedence!
return fetch(superDuperReq);
});
This is a few different concepts at play so it's okay if it takes more than once to get. Essentially though, we're creating a new request which will be sent in place of the original one, and setting a new header! Hurray!
The Bad
Now, to some of the downsides:
Since we're hijacking every single request, we can accidentally change requests we didn't mean to and potentially destroy the entire universe!
Upgrading SWs is a huge pain. SW lifecycle is complex, debugging it on your users is difficult. I've seen a good video on dealing with it, unfortunately can't find it right now, but mdn has a fairly good description
Debugging SWs is often a very annoying experience, especially when combined with their weird lifecycles
Because they are so powerful, SWs can only be served over https. You should already be using https anyway, but this is still a hindrance
This is a lot of things to do for a relatively small benefit, so maybe reconsider its necessity
(1) You can access the global object of an iframe in the same origin as you, but getting your code to run first to modify the global object is tricky indeed.

C# WebSocket - No response from client during handshake

I'm attempting to write a C# WebSocket server for an application that interacts upon browser input.
This is the code:
class Program {
static void Main(string[] args) {
var listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 42001);
listener.Start();
using(var client = listener.AcceptTcpClient())
using(var stream = client.GetStream())
using(var reader = new StreamReader(stream))
using(var writer = new StreamWriter(stream)) {
while (!reader.EndOfStream) {
String line = reader.ReadLine();
if (new Regex("^GET").IsMatch(line)) {
line = reader.ReadLine();
if (new Regex("^Sec-WebSocket-Key: ").IsMatch(line)) {
String key = new Regex("(^Sec-WebSocket-Key\\: |\\r\\n)").Replace(line, "");
key = Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")));
writer.WriteLine("HTTP/1.1 101 Switching Protocols");
writer.WriteLine("Upgrade: websocket");
writer.WriteLine("Connection: Upgrade");
writer.WriteLine("Sec-WebSocket-Accept: " + key);
writer.WriteLine("Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits");
writer.WriteLine("WebSocket-Origin: http://127.0.0.1");
writer.WriteLine("WebSocket-Location: ws://localhost:42001/websocket");
writer.WriteLine("");
}
}
}
}
listener.Stop();
}
}
... and:
var ws = new WebSocket('ws://localhost:42001/websocket');
ws.onopen = function() {
console.log('connected');
};
ws.onmessage = function(e) {
console.log(e.data);
};
ws.onerror = function(e) {
console.log(e);
};
ws.onclose = function() {
console.log("closed");
};
On execution, the TPCListener successfully accepts the TCPClient and reads the incoming HTTP request. It parses the Key, generates the correct Accept token, but the JS - window native - WebSocket seems to have gone flat out bonkers: it does not answer no matter what it receives.
I would expect it throwing an error upon sending a HTTP/1.1 400 Bad Request, but nothing at all happens. It just goes mute.
Checking out Chrome Dev Tools' Networking tab, I do only see the websocket outgoing GET request, but no incoming packets - is that supposed to happen?
If I forcefully close the application, WebSocket throws this exception:
WebSocket connection to 'ws://localhost:42001/websocket' failed: Error during WebSocket handshake: net::ERR_CONNECTION_RESET.
What am I missing here? Thank you all in advance.
Also, I'm not using Net.WebSockets because it is available since .NET 4.5, and my application targets systems from Windows 7 to the current build of Windows 10.
well... how far does your C# code get? My first bet would be on buffering - you don't flush the writer or stream, so I would expect them to be holding onto data while stuck in the top of the while loop, but frankly it shouldn't be a while loop in the first place - you only get one handshake per socket, not many. You could try adding flushes after the blank line, and you should make sure the Socket itself has buffering disabled (NoDelay = true;) - but: fundamentally this isn't a good way to write a web-socket server. Apart from anything else, the data will cease to be text if the handshake succeeds, so having a TextReader is a very bad thing. Frankly, you should be dealing with raw Socket / byte[] data here, IMO (having implemented this very thing several times).

MessageChannel port.postMessage's data is null when calling postMessage with a transferable object?

I'm learning about MessageChannel and transferable objects.
I've got an iframe which is cross-domain from my page. The documentation surrounding MessageChannel indicates that it fully supports cross-domain communications.
I've got this code inside of my cross-domain page inside of an iframe:
var messageChannel = new MessageChannel();
// Transfer port2 to the background page to establish communications.
window.parent.postMessage('connect', 'chrome-extension://jbnkffmindojffecdhbbmekbmkkfpmjd', [messageChannel.port2]);
messageChannel.port1.start();
// Give time for background to setup its port. Not great practice, but OK for example.
setTimeout(function(){
// Create a 32MB "file" and fill it.
var uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (var i = 0; i < uInt8Array.length; ++i) {
uInt8Array[i] = i;
}
messageChannel.port1.onmessage = function(message){
console.log('iframe message:', message);
};
messageChannel.port1.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
if (uInt8Array.buffer.byteLength)
throw "Failed to transfer buffer";
}, 1000);
and in my background page I have:
window.onmessage = function(messageEvent) {
// Make sure the origin is correct for security
if (messageEvent.origin === 'https://www.youtube.com') {
if (messageEvent.ports.length > 0 && messageEvent.data === 'connect') {
var port = messageEvent.ports[0];
port.onmessage = function (message) {
console.log("background message:", message);
};
}
}
};
When I attempt to postMessage the uInt8Array buffer -- I receive no data on the other side:
but if I try and send something simple, say:
messageChannel.port1.postMessage('hello');
then I see:
When using transferable objects -- is the data represented somewhere else? I seem to be able to transfer the port just fine, but I'm struggling to transfer the array of data. BUT, since my exception isn't being thrown -- it looks like it IS transferred... but where did it go??
I've reduced your code sample and discovered that the ArrayBuffer is always lost when it is passed through a MessagePort of a MessageChannel.
Reported as issue 334408: "ArrayBuffer is lost in MessageChannel during postMessage (receiver's event.data == null)"
https://code.google.com/p/chromium/issues/detail?id=334408

How do I catch a WebSocket connection interruption?

In Firefox (at least), if you hit ESC, then it will close all open WebSockets connections.
I need to capture that disconnection and try to re-connect once it's available again.
Here's an example of the code I've tried to implement, but nothing I can figure out will catch the error and allow me to handle it gracefully.
Have a look at the code: http://jsfiddle.net/w5aAK/
var url = "ws://echo.websocket.org";
try {
socket = window['MozWebSocket'] ? new MozWebSocket(url) : new WebSocket(url);
socket.onopen = function(){
console.log('Socket is now open.');
};
socket.onerror = function (error) {
console.error('There was an un-identified Web Socket error');
};
socket.onmessage = function (message) {
console.info("Message: %o", message.data);
};
} catch (e) {
console.error('Sorry, the web socket at "%s" is un-available', url);
}
setTimeout(function(){
socket.send("Hello World");
}, 1000);
Turn on your console and watch the output.
Am I doing something wrong here, or is it just not possible because the connection is running outside of the scope of the JS script?
Any input would be helpful.
Thanks!
You can attach a handler to the socket.onclose event. It will be called when you hit ESC and the connection is interrupted.
See: http://jsfiddle.net/w5aAK/1/
One problem that you can't get around at the moment is the interrupted error being output to the console. There's no way of capturing that at the moment I'm afraid.
You can't catch it and it's not your fault. It's FireFox bug. Vote for it here:
https://bugzilla.mozilla.org/show_bug.cgi?id=712329
I personally tried all kind of solutions:
event handlers onunload onbeforeunload onclose try..catch some js error handling 3rd party services etc.
You can log to console your socket, it's closed before unload, but FF thinks different.. :(
Solution (not answer directly to the answer, but it works):
It's a bug, so you can't catch, but this info is not Solution. After all kind of crazy workarounds and tries to catch that bug, i finally found this working. If you use socket.io to work with WebScokets it can work with different transport technologies. xhr-polling works with Firefox.
if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) { //test for Firefox/x.x or Firefox x.x (ignoring remaining digits);
socket = io.connect('//' + node_server + '/', {
transports: ['polling']
});
} else {
socket = io.connect('//' + node_server + '/');
}
What helped me - might help you too:
Web Socket support in Node.js/Socket.io for older browser
Define transport types on the client side
socket.io doens't work with transports: [ 'xhr-polling' ]

Categories