ws-playground/client/index.html

361 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>WebSocket</title>
<link rel="stylesheet" href="css/milligram.min.css">
<style>
.page {margin: 0 auto; width: 800px}
.mark {color: mediumseagreen; padding: .5rem}
.mark a {color: mediumseagreen}
.alert-ok {background-color: mediumseagreen; color: #222; padding: .5rem;text-align: center;}
.alert-error {background-color: indianred; color: #EEE; padding: .5rem;text-align: center;}
.alert-warn {background-color: darkkhaki; color: #222; padding: .5rem;text-align: center;}
.card {
display: flex;
min-width: 276px;
width: 100%;
flex-direction: column;
}
.card-header {
font-weight: bold;
margin: 0;
padding: 1em;
}
.card-content {
flex: 1 1 auto;
padding: 1em;
}
.card-footer {
display: flex;
align-items: stretch;
}
.modal {
position: fixed;
background: rgba(0, 0, 0, .6);
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 1rem;
display: none;
align-items: center;
justify-content: center;
flex-direction: column;
}
.modal.active {
display: flex;
}
.modal-content {
display: flex;
width: 28rem;
max-width: 80vw;
height: 20rem;
background: white;
}
/* Theme */
button, input[type=submit] {background-color: steelblue; border: 0.1rem solid steelblue;}
input[type=text]:focus,select:focus {border: 1px solid steelblue}
table tbody tr:nth-child(odd) {
background-color: #EEE;
}
table tbody td:first-child {padding-left: .75rem}
.weak {color: #767676; font-size: .75em}
.uppercase {text-transform: uppercase;}
</style>
</head>
<body>
<div class="page">
<div id="wc">
<div :if="this.error" class="alert-error">{this.error}</div>
<!-- Connected-->
<div :if="this.connect.socket">
<div :if="this.connect.status">
<small class="mark">
<strong>CONNECTED:</strong> {this.connected} <strong>AS:</strong> {this.connect.as}
<span style="float:right;padding-right: 1rem">
<a @click="showClients">CLIENTS ({this.clients.length})</a>
| <a @click="disconnect">DISCONNECT</a>
</span>
</small>
<form action="">
<h3>Send Message</h3>
<label for="">Type</label>
<select @bind="message.type">
<option value="message">Message</option>
<option value="broadcast">Broadcast</option>
<option value="notification">Notification</option>
</select>
<div :if="this.message.type === 'message'">
<label for="">User</label>
<input type="text" @bind="message.to" value="{this.message.to}">
</div>
<div :if="this.message.type === 'notification'">
<label for="">User</label>
<input type="text" @bind="message.to" value="{this.message.to}">
<label for="">Title</label>
<input type="text" @bind="message.subject" value="{this.message.subject}">
</div>
<label for="">Message</label>
<input type="text" @bind="message.body">
<input type="submit" @click="send" value="Send">
</form>
</div>
<div :else class="alert-ok">
<strong>CONNECTING:</strong> {this.connected} <strong>AS:</strong> {this.connect.as}
</div>
</div>
<!-- No Connection -->
<div :else>
<div :if="this.connect.retry > 0 && this.connect.retry < 6" class="alert-warn">
<strong>RECONNECTING IN {this.connect.timeout / 1000 + 's'} ATTEMPT</strong> {this.connect.retry}
</div>
<div :else>
<p>
<h3>No Connection</h3>
<label for="">Type</label>
<select @bind="connect.scheme">
<option value="ws://">ws://</option>
<option value="wss://">wss://</option>
</select>
<label for="">URL</label>
<input type="text" @bind="connect.url" value="{this.connect.url}">
<label for="">Connect Alias</label>
<input type="text" @bind="connect.as" value="{this.connect.as}">
<input @click="connect" type="submit" value="Connect">
</p>
</div>
</div>
<!-- Messages -->
<div :if="this.messages.length">
<h3>Messages</h3>
<table>
<thead>
<tr>
<th>Type</th>
<th>From</th>
<th style="width: 50%">Message</th>
<th>Timestamp</th>
</tr>
</thead>
<tbody>
<tr :for="message in this.messages.reverse()" class="highlight: message.from === ">
<td class="weak uppercase">{message.type}</td>
<td class="weak">{message.from}</td>
<td>
<strong>{message.body}</strong>
</td>
<td class="weak">{message.date}</td>
</tr>
</tbody>
</table>
</div>
<div class="modal">
<div class="modal-content" style="background: #FFF">
<div class="card">
<div class="card-header">
Clients ({this.clients.length})
<span style="float: right; cursor: pointer;" @click="showClients">X</span>
</div>
<div class="card-content">
<table>
<tr :for="client in this.clients">
<th>{client.user}</th>
<th>{client.status}</th>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="module">
import Litedom from './js/litedom.es.js';
Litedom({
el: '#wc',
data: {
messages: [],
clients: [],
message: {
subject: 'Return to Yard Request',
body: '',
to: '0000000',
type: 'message',
data: {}
},
error: null,
connect: {
as: '1111111', // TODO: set or randomize
scheme: 'ws://',
url: 'localhost:3001',
socket: null,
status: null,
retry: 0,
timeout: 5000
},
connected: (state) => state.connect.scheme + state.connect.url,
},
connect(e) {
let scheme = this.data.connect.scheme;
let url = this.data.connect.url;
let socket = new WebSocket(scheme+url);
// HACK: cannot use litedom data with websockets
window.ws = socket;
// update binds
this.data.connect.socket = socket;
this.data.error = null;
if(socket) {
socket.onopen = (e) => {
console.log('CONNECTED');
this.data.connect.status = true;
// reset retrys
this.data.connect.retry = 0;
// register client after connection
socket.send(JSON.stringify({
user: this.data.connect.as,
type: 'register'
}));
}
socket.onmessage = (e) => {
console.log('MESSAGE:', e.data);
try {
let payload = JSON.parse(e.data);
switch(payload.type) {
case "broadcast":
case "message":
this.data.messages.push(payload);
break;
case "offline":
this.data.error = "Server Went Offline";
break;
case "clients":
this.data.clients = payload.data;
break;
default:
console.log("UNDEFINED", payload);
}
} catch(err) {
console.warn(err);
}
}
socket.onclose = (e) => {
this.data.error = `Connection Closed (${e.code} ${e.reason ? '- ' + e.reason : ''})`;
this.data.connect.socket = null;
ws = null;
// try to reconnect
this.reconnect();
}
socket.onerror = (e) => {
this.data.error = "Socket Error";
}
}
},
disconnect() {
this.data.connect.retry = false;
ws.close(1000, 'Going Bye-Bye!');
},
reconnect() {
// only reconnect if enabled
if(this.data.connect.retry === false) {
return;
}
this.data.connect.retry += 1;
if(this.data.connect.retry < 6) {
// wait before retrying
setTimeout(() => {
console.log("RECONNECTION:ATTEMPT", this.data.connect.retry);
this.connect();
}, this.data.connect.timeout);
} else {
if(this.data.connect.retry >= 5) {
console.log("RECONNECTION:EXHAUSTED");
this.data.error = "RECONNECTION ATTEMPTS EXHAUSTED";
}
}
},
send(e) {
if(ws.readyState === WebSocket.OPEN) {
switch(this.data.message.type) {
case "message":
case "broadcast":
let payload = {
"from": this.data.connect.as,
"to": this.data.message.to,
"type": this.data.message.type,
"body": this.data.message.body,
"data": this.data.message.data
};
return ws.send(JSON.stringify(payload));
case "notification":
return ws.send(JSON.stringify({
"to": this.data.message.to,
"from": this.data.connect.as,
"type": this.data.message.type,
"subject": this.data.message.subject,
"body": this.data.message.body,
"data": this.data.message.data || {}
}));
default:
console.log("UNDEFINED:TYPE", this.data.message);
}
} else {
this.data.connect.socket = null;
this.data.error = "Connection Lost";
}
},
showClients() {
var modal = document.querySelector('.modal');
modal.classList.toggle('active');
}
});
</script>
</body>
</html>