Skip to content

Commit

Permalink
Experiments and POCs: adds file handling to the WebSocket client, cle…
Browse files Browse the repository at this point in the history
…ans WebSocket server code and adds list of the IPs on which the server is listerning
  • Loading branch information
robertrypula committed Feb 7, 2021
1 parent 36c385c commit bbdec36
Show file tree
Hide file tree
Showing 2 changed files with 305 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,228 @@
<head>
<meta charset="UTF-8" />
<title>WebSocket client</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<script>
let webSocket;

const parseHex = string => {
return string
.split(' ')
.filter(value => value !== '')
.map(value => parseInt(value, 16) % 256)
.filter(value => value || value === 0);
};

const getHex = rawBytes => rawBytes.map(rawByte => (rawByte < 16 ? '0' : '') + rawByte.toString(16)).join(' ');

const fileRead = file => {
return new Promise((resolve, reject) => {
const reader = new FileReader();

if (file) {
reader.onload = () => resolve(Array.from(new Uint8Array(reader.result)));
reader.readAsArrayBuffer(file);
} else {
reject('File was not selected');
}
});
};

const fileSave = (filename, bytes) => {
const blob = new Blob([new Uint8Array(bytes)]); // , { type: 'application/pdf' }
const link = document.createElement('a');

link.href = window.URL.createObjectURL(blob);
link.download = filename;
link.click();
};

const toKiB = rawBytesLength => `${(rawBytesLength / 1024).toFixed(2)} KiB`;

const showError = error => {
console.log(error);
document.getElementById('container-error').style.display = 'block';
document.getElementById('container-error').innerHTML = error instanceof Event ? 'Unknown error' : error;
connectionPendingDisable();
};

const connectionUp = () => {
document.getElementById('container-connect').style.display = 'none';
document.getElementById('container-send').style.display = 'block';
document.getElementById('container-error').innerHTML = '';
sendChange();
};

const connectionDown = () => {
document.getElementById('container-connect').style.display = 'block';
document.getElementById('container-send').style.display = 'none';
connectionPendingDisable();
};

const connectionPendingEnable = () => {
document.getElementById('connect').setAttribute('disabled', 'disabled');
document.getElementById('url').setAttribute('disabled', 'disabled');
};

const connectionPendingDisable = () => {
document.getElementById('connect').removeAttribute('disabled');
document.getElementById('url').removeAttribute('disabled');
};

const fileReadHandler = rawBytes => {
if (confirm(`Would you like to put ${rawBytes.length} bytes (${toKiB(rawBytes.length)}) into the textarea?`)) {
document.getElementById('send').value = getHex(rawBytes);
sendChange();
}
};

const send = () => {
if (!webSocket || webSocket.readyState !== WebSocket.OPEN || webSocket.bufferedAmount) {
return false;
}

const bytesRaw = document
.getElementById('send')
.value.split(' ')
.filter(value => value !== '')
.map(value => parseInt(value, 16) % 256)
.filter(value => value || value === 0);

const arrayBuffer = new Uint8Array(bytesRaw).buffer;
const arrayBuffer = new Uint8Array(parseHex(document.getElementById('send').value)).buffer;

webSocket.send(arrayBuffer);

return true;
};

const connect = url => {
document.getElementById('container-connect').style.display = 'none';
document.getElementById('container-send').style.display = 'block';
const sendChange = () => {
const rawBytes = parseHex(document.getElementById('send').value);

try {
webSocket = new WebSocket(url, ['audio-network-reborn']);
document.getElementById('send-button').innerHTML = `Send ${rawBytes.length} bytes (${toKiB(rawBytes.length)})`;
};

webSocket.binaryType = 'arraybuffer';
webSocket.addEventListener('open', () => console.log('webSocket open'));
webSocket.addEventListener('close', () => console.log('webSocket close'));
webSocket.addEventListener('error', event => console.log('webSocket error', event));
const receive = rawBytes => {
const receivedDataElement = document.createElement('div');
const receivedDataHexElement = document.createElement('div');
const receivedDataSaveElement = document.createElement('button');

receivedDataHexElement.innerHTML = getHex(rawBytes);
receivedDataSaveElement.innerHTML = `Save ${rawBytes.length} bytes to file (${toKiB(rawBytes.length)})`;
document.getElementById('container-received-data').appendChild(receivedDataElement);
receivedDataElement.appendChild(receivedDataHexElement);
receivedDataElement.appendChild(receivedDataSaveElement);

receivedDataSaveElement.addEventListener('click', event => {
const filename = new Date()
.toISOString()
.slice(0, 19)
.replace(/[:\-]/g, '')
.replace('T', '_');

webSocket.addEventListener('message', event => {
const rawBytes = [...new Uint8Array(event.data)];
const logElement = document.getElementById('log');
const logLine = rawBytes.map(rawByte => (rawByte < 16 ? '0' : '') + rawByte.toString(16)).join(' ');
fileSave(filename + '.bin', parseHex(event.target.parentNode.querySelector('div').innerHTML));
});
};

const connect = () => {
try {
connectionPendingEnable();
webSocket = new WebSocket(document.getElementById('url').value, ['audio-network-reborn']);

logElement.innerHTML = logElement.innerHTML + '\n' + logLine;
});
webSocket.binaryType = 'arraybuffer';
webSocket.addEventListener('open', () => connectionUp());
webSocket.addEventListener('close', () => connectionDown());
webSocket.addEventListener('error', event => showError(event));
webSocket.addEventListener('message', event => receive([...new Uint8Array(event.data)]));
} catch (error) {
console.log(error);
showError(error);
}
};
</script>

<style>
body,
html {
margin: 0;
padding: 0;
}

h1 {
display: block;
margin: 0;
padding: 0;
}

.container {
padding: 16px;
}

.row {
padding-bottom: 8px;
}

textarea {
box-sizing: border-box;
display: block;
width: 100%;
min-height: 300px;
padding: 8px;
}

textarea::placeholder {
color: #cecece;
}

#container-received-data > div {
margin-bottom: 16px;
border-radius: 8px;
padding: 16px;
background-color: lightgray;
}

#container-received-data > div > div {
margin-bottom: 8px;
font-family: monospace;
font-size: 12px;
line-height: 16px;
max-height: 100px;
overflow-y: auto;
}
</style>
</head>
<body>
<div id="container-connect">
<input type="text" id="url" value="ws://51.83.135.17:5612" />
<button onClick="connect(document.getElementById('url').value)">Connect</button>
<div class="container">
<h1>WebSocket - binary broadcast example</h1>
</div>

<div class="container" id="container-connect">
<input type="text" id="url" value="ws://sndu.pl:6175" />
<button id="connect" onClick="connect()">Connect</button>
</div>

<div class="container" id="container-error" style="display: none"></div>

<div class="container" id="container-send" style="display: none">
<div class="row">
<input
type="file"
id="file-attachment"
onchange="fileRead(this.files[0]).then(rawBytes => fileReadHandler(rawBytes)).catch(error => showError(error))"
/>
</div>
<div class="row">
<textarea
id="send"
oninput="sendChange()"
placeholder="Hex values like: 48 65 6c 6c 6f 20 77 6f 72 6c 64 21"
></textarea>
</div>
<div class="row">
<button id="send-button" onClick="send()">Send</button>
</div>
<div class="row">
<strong>NOTE:</strong> Server will fail when you will try to send more than 65535 bytes of data (>= 64 KiB)
</div>
</div>

<div id="container-send" style="display: none">
<textarea id="send">61 75 64 69 6f 20 6e 65 74 77 6f 72 6b 20 72 65 62 6f 72 6e</textarea>
<button onClick="send()">Send</button>
<div class="container" id="container-received-data"></div>

<pre id="log"></pre>
<div class="container">
Copyright (c) 2019-2021 Robert Rypuła -
<a href="https://github.com/robertrypula" target="_blank">https://github.com/robertrypula</a>
</div>
</body>
</html>
Loading

4 comments on commit bbdec36

@rocket-pig
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all so very awesome! I wish you hadn't moved away from the project and were still working on it. Do I understand correctly that it can establish a websocket connection between hosts on same network and then tx/rx over that channel too??

Thanks so much for sharing. 👍

@robertrypula
Copy link
Owner Author

@robertrypula robertrypula commented on bbdec36 Sep 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all so very awesome! I wish you hadn't moved away from the project and were still working on it. Do I understand correctly that it can establish a websocket connection between hosts on same network and then tx/rx over that channel too??

Thanks so much for sharing. 👍

Hello! I'm glad somebody found my project useful. There are some plans but now... frozen :D Here are some links where I was playing with this concept more:

https://stackoverflow.com/a/66187214/8264192

https://gist.github.com/robertrypula/f8da8f89819068a97bef4f27d04ad5b7
https://gist.github.com/robertrypula/b813ffe23a9489bae1b677f1608676c8

The plan is to implement "SendYou" website (SNDU). Currently it's just Proof Of Concept:
http://sndu.pl/

Maybe some day :D

@robertrypula
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand correctly that it can establish a websocket connection between hosts on same network and then tx/rx over that channel too??

A little bit more info regarding your question. Server works on my private VPS in OVH provider (regular Ubuntu installation with node). When you connect to server it basically tracks how many clients are active and whatever one client sends into the websocket it's forwarded to any other client connected to the server. Basically it works in broadcast mode. Since server is on VPS it's accessible from the entire world you don't have to be in the same network. The main goal of http://sndu.pl/ is to have ability to quickly share small files without having to create account or use email, WhatsApp, etc. Currently it's limited to 64 KiB but my plan was to implement packets handling to increase the file size.

@rocket-pig
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great - file sharing "party mode". You have a creative mind. Your code is clean and I've really appreciated what you've shared, thank you! :)

Please sign in to comment.