186 lines
5.4 KiB
Plaintext
186 lines
5.4 KiB
Plaintext
String clean_chat_field(String raw, u32 max_length)
|
|
{
|
|
String value = trim(raw);
|
|
value = replace(value, "\r", " ");
|
|
value = replace(value, "\n", " ");
|
|
if(value.length() > max_length)
|
|
value.resize(max_length);
|
|
return(value);
|
|
}
|
|
|
|
DTree chat_event(String type, String body)
|
|
{
|
|
DTree event;
|
|
event["type"] = type;
|
|
event["body"] = body;
|
|
event["connection_id"] = ws_connection_id();
|
|
event["scope"] = first(context->params["DOCUMENT_URI"], ws_scope());
|
|
event["online"] = (f64)ws_connection_count();
|
|
event["at"] = gmdate("%H:%M:%S");
|
|
return(event);
|
|
}
|
|
|
|
RENDER()
|
|
{
|
|
String ws_url = context->params["DOCUMENT_URI"];
|
|
u64 online = ws_connection_count();
|
|
<>
|
|
<link rel="stylesheet" href='style.css?v=<?= time() ?>'></link>
|
|
<h1>
|
|
<a href="index.uce">UCE Test</a>:
|
|
WebSocket Chat
|
|
</h1>
|
|
|
|
<label>Chat Info</label>
|
|
<pre>Page scope: <?= ws_url ?>
|
|
Connected right now: <span id="online-count"><?= online ?></span>
|
|
Status: <span id="status">Connecting...</span></pre>
|
|
|
|
<form id="chat-form" action="?" method="post">
|
|
<div>
|
|
<label>Display name:</label>
|
|
<input id="chat-name" type="text" maxlength="32" value="guest-<?= draw_int(1000, 9999) ?>"/>
|
|
</div>
|
|
<div>
|
|
<label>Message:</label>
|
|
<textarea id="chat-message" maxlength="500" placeholder="Say something to everyone connected to this same .ws.uce page"></textarea>
|
|
</div>
|
|
<div>
|
|
<input id="send-button" type="submit" value="Send Message"/>
|
|
</div>
|
|
</form>
|
|
|
|
<label>Chat Log</label>
|
|
<pre id="chat-log"></pre>
|
|
|
|
<script>
|
|
const wsUrl = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}<?= ws_url ?>`;
|
|
const statusEl = document.getElementById('status');
|
|
const onlineEl = document.getElementById('online-count');
|
|
const logEl = document.getElementById('chat-log');
|
|
const formEl = document.getElementById('chat-form');
|
|
const nameEl = document.getElementById('chat-name');
|
|
const messageEl = document.getElementById('chat-message');
|
|
const sendButtonEl = document.getElementById('send-button');
|
|
const savedName = window.localStorage.getItem('uce-chat-name');
|
|
if (savedName) nameEl.value = savedName;
|
|
|
|
let ws;
|
|
let reconnectDelay = 1000;
|
|
|
|
function addLine(kind, title, body, meta = '') {
|
|
const prefix = kind === 'system' ? '[system]' : `[${title}]`;
|
|
const metaText = meta ? ` ${meta}` : '';
|
|
logEl.append(`${prefix} ${body}${metaText}\n`);
|
|
logEl.scrollTop = logEl.scrollHeight;
|
|
}
|
|
|
|
function setStatus(text, online) {
|
|
statusEl.textContent = text;
|
|
if (typeof online === 'number') onlineEl.textContent = String(online);
|
|
}
|
|
|
|
function sendPayload(payload) {
|
|
if (!ws || ws.readyState !== WebSocket.OPEN) return false;
|
|
payload.name = (nameEl.value || '').trim().slice(0, 32) || 'guest';
|
|
window.localStorage.setItem('uce-chat-name', payload.name);
|
|
ws.send(JSON.stringify(payload));
|
|
return true;
|
|
}
|
|
|
|
function connect() {
|
|
ws = new WebSocket(wsUrl);
|
|
setStatus(`Connecting to ${wsUrl}...`);
|
|
sendButtonEl.disabled = true;
|
|
|
|
ws.addEventListener('open', () => {
|
|
reconnectDelay = 1000;
|
|
sendButtonEl.disabled = false;
|
|
setStatus('Connected');
|
|
addLine('system', 'system', 'socket connected', new Date().toLocaleTimeString());
|
|
sendPayload({ type: 'join' });
|
|
});
|
|
|
|
ws.addEventListener('message', (event) => {
|
|
let payload;
|
|
try {
|
|
payload = JSON.parse(event.data);
|
|
} catch (error) {
|
|
addLine('system', 'decode error', String(error), new Date().toLocaleTimeString());
|
|
return;
|
|
}
|
|
if (typeof payload.online === 'number') onlineEl.textContent = String(payload.online);
|
|
if (payload.type === 'message') {
|
|
addLine('', payload.name || 'guest', payload.body || '', `${payload.at || ''} ${payload.connection_id || ''}`);
|
|
} else {
|
|
addLine('system', payload.type || 'system', payload.body || '', `${payload.at || ''} ${payload.connection_id || ''}`);
|
|
}
|
|
});
|
|
|
|
ws.addEventListener('close', () => {
|
|
sendButtonEl.disabled = true;
|
|
setStatus(`Disconnected, retrying in ${reconnectDelay / 1000}s`);
|
|
addLine('system', 'system', 'socket closed', new Date().toLocaleTimeString());
|
|
window.setTimeout(connect, reconnectDelay);
|
|
reconnectDelay = Math.min(reconnectDelay * 2, 10000);
|
|
});
|
|
|
|
ws.addEventListener('error', () => {
|
|
setStatus('WebSocket error');
|
|
});
|
|
}
|
|
|
|
formEl.addEventListener('submit', (event) => {
|
|
event.preventDefault();
|
|
const body = (messageEl.value || '').trim();
|
|
if (!body) return;
|
|
if (sendPayload({ type: 'message', body })) {
|
|
messageEl.value = '';
|
|
messageEl.focus();
|
|
}
|
|
});
|
|
|
|
connect();
|
|
</script>
|
|
</>
|
|
}
|
|
|
|
WS()
|
|
{
|
|
if(ws_is_binary())
|
|
{
|
|
ws_send_to(
|
|
ws_connection_id(),
|
|
json_encode(chat_event("notice", "Binary messages are not handled by this demo"))
|
|
);
|
|
return;
|
|
}
|
|
|
|
DTree payload = json_decode(ws_message());
|
|
String type = clean_chat_field(payload["type"].to_string(), 24);
|
|
String name = clean_chat_field(payload["name"].to_string(), 32);
|
|
String body = clean_chat_field(payload["body"].to_string(), 500);
|
|
|
|
if(name == "")
|
|
name = "guest";
|
|
|
|
if(type == "join")
|
|
{
|
|
ws_send(json_encode(chat_event("join", name + " joined the room")));
|
|
return;
|
|
}
|
|
|
|
if(type == "message" && body != "")
|
|
{
|
|
DTree event = chat_event("message", body);
|
|
event["name"] = name;
|
|
ws_send(json_encode(event));
|
|
return;
|
|
}
|
|
|
|
if(type != "")
|
|
{
|
|
ws_send_to(ws_connection_id(), json_encode(chat_event("notice", "Unknown message type: " + type)));
|
|
}
|
|
}
|