Compare commits
20 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
829a462ff7 | |
|
|
be389e93a4 | |
|
|
89c18b7c4c | |
|
|
ee80ee45d6 | |
|
|
20295558d1 | |
|
|
23339dedce | |
|
|
3c9492d4b6 | |
|
|
ceca822f97 | |
|
|
36ba716ad8 | |
|
|
e746a4e8ca | |
|
|
98c89f0702 | |
|
|
3993ae9c44 | |
|
|
fd6a40cb0d | |
|
|
733a537f58 | |
|
|
635b44fff3 | |
|
|
d5f1e76383 | |
|
|
48c467349d | |
|
|
0465e66ee9 | |
|
|
e99406ea63 | |
|
|
c3c41a346c |
|
|
@ -0,0 +1,49 @@
|
|||
InstantChat WebSocket Protocol
|
||||
==============================
|
||||
|
||||
Messages from client to server (commands)
|
||||
-----------------------------------------
|
||||
|
||||
JSON objects. Key `action` determines type of message.
|
||||
|
||||
### init
|
||||
Initialize the connection, set nickname and join/create a chat.
|
||||
|
||||
**Parameters:**
|
||||
* `action` = `"init"`
|
||||
* `chat_id`: either an existing ID of a chat instance or *[TODO: ?]* empty string to
|
||||
create a new chat
|
||||
* `nickname`: user's chosen nickname *[TODO: valid characters?]*
|
||||
|
||||
**Example:**
|
||||
|
||||
{
|
||||
"action": "init",
|
||||
"chat_id": "42",
|
||||
"nickname": "Alice"
|
||||
}
|
||||
|
||||
### send (?)
|
||||
Send a message...
|
||||
|
||||
*[TODO]*
|
||||
|
||||
|
||||
Messages from server to client (responses/events)
|
||||
-------------------------------------------------
|
||||
|
||||
JSON objects. Key `type` determines type of message.
|
||||
|
||||
### init
|
||||
Response to `init` command. Confirms initialization and chat join.
|
||||
|
||||
**Parameters:**
|
||||
* `type` = `"init"`
|
||||
* ***[TODO]***
|
||||
|
||||
**Example:**
|
||||
|
||||
{
|
||||
"type": "init",
|
||||
TODO ???
|
||||
}
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
body {
|
||||
font-family: Roboto,sans-serif;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
|
@ -20,6 +25,16 @@ img {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.messaging_heading {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#sidebarCollapse {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.inbox_msg {
|
||||
border: 1px solid #c4c4c4;
|
||||
clear: both;
|
||||
|
|
@ -45,10 +60,11 @@ img {
|
|||
@media (min-width: 768px) {
|
||||
#sidebar {
|
||||
border-left: 1px solid #c4c4c4;
|
||||
/*float: right;*/
|
||||
/*position: relative;*/
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
/*width: 40%;*/
|
||||
position: relative;
|
||||
transform: none;
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,15 +102,22 @@ img {
|
|||
width: 60%;
|
||||
}
|
||||
|
||||
.search_bar .stylish-input-group {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search_bar input {
|
||||
background: none;
|
||||
border-bottom: 1px solid #cdcdcd;
|
||||
font-size: 1rem;
|
||||
height: 25px;
|
||||
padding: 2px 30px 4px 6px;
|
||||
width: 80%;
|
||||
padding: 2px 0 4px 6px;
|
||||
}
|
||||
|
||||
.search_bar .input-group-addon {
|
||||
margin: 0 0 0 -30px;
|
||||
position: absolute;
|
||||
right: 28px;
|
||||
}
|
||||
|
||||
.search_bar .input-group-addon button {
|
||||
|
|
@ -103,12 +126,17 @@ img {
|
|||
color: #707070;
|
||||
font-size: 18px;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.search_bar .input-group-addon button svg {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.chat_ib {
|
||||
float: left;
|
||||
padding: 0 0 0 15px;
|
||||
width: 88%;
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.chat_ib h5 {
|
||||
|
|
@ -124,7 +152,7 @@ img {
|
|||
|
||||
.chat_ib p {
|
||||
font-size: 14px;
|
||||
color: #989898;
|
||||
color: #2b2b2b;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
|
|
@ -145,22 +173,23 @@ img {
|
|||
}
|
||||
|
||||
.inbox_chat {
|
||||
height: 550px;
|
||||
height: 540px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.active_chat {
|
||||
background: #ebebeb;
|
||||
background: #dae3f2;
|
||||
}
|
||||
|
||||
.incoming_msg_img {
|
||||
display: inline-block;
|
||||
width: 6%;
|
||||
h5.incoming_msg_user {
|
||||
color: #464646;
|
||||
font-size: 15px;
|
||||
margin: 0 0 8px 30px;
|
||||
}
|
||||
|
||||
.received_msg {
|
||||
display: inline-block;
|
||||
padding: 0 0 0 10px;
|
||||
padding-left: 30px;
|
||||
vertical-align: top;
|
||||
width: 92%;
|
||||
}
|
||||
|
|
@ -170,25 +199,23 @@ img {
|
|||
}
|
||||
|
||||
.received_withd_msg p {
|
||||
background: #ebebeb none repeat scroll 0 0;
|
||||
background: #dae3f2 none repeat scroll 0 0;
|
||||
border-radius: 3px;
|
||||
color: #646464;
|
||||
color: #2b2b2b;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
padding: 5px 10px 5px 12px;
|
||||
padding: 10px 10px 10px 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.messages {
|
||||
padding: 30px 15px 0 25px;
|
||||
padding: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.messages {
|
||||
/*float: left;*/
|
||||
padding: 30px 15px 0 25px;
|
||||
/*width: 60%;*/
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -199,8 +226,10 @@ img {
|
|||
margin: 8px 0 0;
|
||||
}
|
||||
|
||||
/*Messages*/
|
||||
.sent_msg {
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
width: 46%;
|
||||
}
|
||||
|
||||
|
|
@ -209,13 +238,13 @@ img {
|
|||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
margin: 0; color:#fff;
|
||||
padding: 5px 10px 5px 12px;
|
||||
width: 100%;
|
||||
padding: 10px 12px 10px 12px;
|
||||
/*width: 100%;*/
|
||||
}
|
||||
|
||||
.outgoing_msg {
|
||||
overflow: hidden;
|
||||
margin: 26px 0 26px;
|
||||
margin: 12px 0 12px;
|
||||
}
|
||||
|
||||
.input_msg_write input {
|
||||
|
|
@ -224,30 +253,33 @@ img {
|
|||
color: #4c4c4c;
|
||||
font-size: 15px;
|
||||
min-height: 48px;
|
||||
padding-left: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.type_msg {
|
||||
border-top: 1px solid #c4c4c4;
|
||||
float: left;
|
||||
position: relative;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.msg_send_btn {
|
||||
background: #05728f none repeat scroll 0 0;
|
||||
border: medium none;
|
||||
border-radius: 50%;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 17px;
|
||||
height: 33px;
|
||||
height: 35px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 11px;
|
||||
width: 33px;
|
||||
top: 7px;
|
||||
width: 52px;
|
||||
}
|
||||
|
||||
.msg_send_btn svg {
|
||||
fill: #ffffff;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.msg_history {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,6 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>InstantChat</title>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
|
||||
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
|
||||
crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -121,7 +118,7 @@
|
|||
<div class="messages">
|
||||
<div class="msg_history">
|
||||
<div class="incoming_msg">
|
||||
<div class="incoming_msg_img"> <img src="https://ptetutorials.com/images/user-profile.png" alt="sunil"> </div>
|
||||
<h5 class="incoming_msg_user">Sunil Rajput</h5>
|
||||
<div class="received_msg">
|
||||
<div class="received_withd_msg">
|
||||
<p>Test which is a new approach to have all
|
||||
|
|
@ -137,7 +134,7 @@
|
|||
<span class="time_date"> 11:01 AM | June 9</span> </div>
|
||||
</div>
|
||||
<div class="incoming_msg">
|
||||
<div class="incoming_msg_img"> <img src="https://ptetutorials.com/images/user-profile.png" alt="sunil"> </div>
|
||||
<h5 class="incoming_msg_user">Sunil Rajput</h5>
|
||||
<div class="received_msg">
|
||||
<div class="received_withd_msg">
|
||||
<p>Test, which is a new approach to have</p>
|
||||
|
|
@ -151,7 +148,7 @@
|
|||
<span class="time_date"> 11:01 AM | Today</span> </div>
|
||||
</div>
|
||||
<div class="incoming_msg">
|
||||
<div class="incoming_msg_img"> <img src="https://ptetutorials.com/images/user-profile.png" alt="sunil"> </div>
|
||||
<h5 class="incoming_msg_user">Sunil Rajput</h5>
|
||||
<div class="received_msg">
|
||||
<div class="received_withd_msg">
|
||||
<p>We work directly with our designers and suppliers,
|
||||
|
|
@ -166,11 +163,8 @@
|
|||
<div class="input_msg_write">
|
||||
<input type="text" class="write_msg" placeholder="Type a message" />
|
||||
<button class="msg_send_btn" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24">
|
||||
<path fill="none" d="M0 0h24v24H0V0z"/><path d="M4.01 6.03l7.51 3.22-7.52-1 .01-2.22m7.5 8.72L4 17.97v-2.22l7.51-1M2.01 3L2 10l15 2-15 2 .01 7L23 12 2.01 3z"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="21" viewBox="0 0 25 21">
|
||||
<path fill="#FFF" d="M24.0085292,2.21031867 L20.3673237,19.2383094 C20.099588,20.4698936 19.4034751,20.7376293 18.3860795,20.2021579 L12.870724,16.1325752 L10.1933669,18.702838 C9.92563124,18.9705737 9.65789553,19.2383094 9.06887699,19.2383094 L9.49725411,13.6158596 L19.724758,4.40575143 C20.1531351,3.97737431 19.6176637,3.76318575 19.0286452,4.13801573 L6.39151995,12.1165397 L0.929711603,10.4030312 C-0.248325492,10.0282012 -0.248325492,9.2249941 1.19744731,8.63597555 L22.4556622,0.496810165 C23.4730578,0.175527321 24.3298121,0.764545868 24.0085292,2.26386581 L24.0085292,2.21031867 Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -181,16 +175,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="js/jquery-3.3.1.js"></script>
|
||||
<!--Bootstrap Tooltip Library, not needed yet-->
|
||||
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>-->
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
|
||||
<script src="js/settings.js"></script>
|
||||
<script src="js/events.js"></script>
|
||||
<script src="js/client.js"></script>
|
||||
<script src="js/ui.js"></script>
|
||||
<script src="js/sidebar.js"></script>
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -1,47 +1,122 @@
|
|||
// Constants and variables
|
||||
//const wsUri = "ws://localhost:32715";
|
||||
const wsUri = "wss://chat.glitch-in.space:443/ws/";
|
||||
let websocket;
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Client class that implements the InstantChat protocol using WebSockets.
|
||||
*
|
||||
* Dispatches the following events which can be subscribed/unsubscribed using .on() and .off():
|
||||
* - initialized: Connection to server has been established and initialized, ready to send messages.
|
||||
* - disconnected: Connection has been closed.
|
||||
* - connectionError: Some connection error occurred.
|
||||
* - receivedMessage: Chat message has been received. Data: object {from: 'username', text: 'text'}
|
||||
*/
|
||||
class Client extends EventDispatcher {
|
||||
constructor(wsUri, chatID, nickname) {
|
||||
super();
|
||||
|
||||
// Initialization
|
||||
function init() {
|
||||
console.log("Init...");
|
||||
openWebSocket();
|
||||
this.wsUri = wsUri;
|
||||
this.chatID = chatID;
|
||||
this.nickname = nickname;
|
||||
|
||||
// Create WebSocket and set internal callbacks
|
||||
console.log("Initialize Client...")
|
||||
this.webSocket = new WebSocket(wsUri);
|
||||
this.webSocket.onopen = this._onSocketOpen.bind(this);
|
||||
this.webSocket.onclose = this._onSocketClose.bind(this);
|
||||
this.webSocket.onerror = this._onSocketError.bind(this);
|
||||
this.webSocket.onmessage = this._onSocketMessage.bind(this);
|
||||
}
|
||||
|
||||
// Open WebSocket
|
||||
function openWebSocket() {
|
||||
websocket = new WebSocket(wsUri);
|
||||
websocket.onopen = function(evt) { onOpen(evt) };
|
||||
websocket.onclose = function(evt) { onClose(evt) };
|
||||
websocket.onmessage = function(evt) { onMessage(evt) };
|
||||
websocket.onerror = function(evt) { onError(evt) };
|
||||
// Internal WebSocket event handlers
|
||||
_onSocketOpen(evt) {
|
||||
console.log("Connected to " + this.wsUri);
|
||||
this.sendInit(this.chatID, this.nickname);
|
||||
}
|
||||
|
||||
// WebSocket event handlers
|
||||
function onOpen(evt) {
|
||||
console.log('Connected to ' + wsUri + '.');
|
||||
|
||||
let text = "Meow";
|
||||
console.log('Sending "' + text + '".');
|
||||
websocket.send(text);
|
||||
_onSocketClose(evt) {
|
||||
console.log("Connection closed (code " + evt.code + ").");
|
||||
this.dispatch("disconnected");
|
||||
}
|
||||
|
||||
function onClose(evt) {
|
||||
console.log("Connection closed (code " + evt.code + "): ", evt);
|
||||
_onSocketError(evt) {
|
||||
console.error("Connection error: ", evt);
|
||||
this.dispatch("connectionError");
|
||||
}
|
||||
|
||||
function onMessage(evt) {
|
||||
console.log('Received: "' + evt.data + '".');
|
||||
_onSocketMessage(evt) {
|
||||
console.log("Received: " + evt.data);
|
||||
this._parseMessage(evt.data);
|
||||
}
|
||||
|
||||
function onError(evt) {
|
||||
console.error('Connection error: ', evt);
|
||||
/**
|
||||
* Sends an arbitrary command as JSON.
|
||||
*
|
||||
* commandObj: The command as an object ('action' specifies type of command).
|
||||
*/
|
||||
sendCommand(commandObj) {
|
||||
const commandJson = JSON.stringify(commandObj);
|
||||
console.log("Sending command: " + commandJson);
|
||||
this.webSocket.send(commandJson);
|
||||
}
|
||||
|
||||
|
||||
// Run script after page is loaded
|
||||
$(function() {
|
||||
init();
|
||||
/**
|
||||
* Sends the 'init' command which sets up the session. Also sets the chat ID and nickname.
|
||||
*
|
||||
* chatID: The ID of the chat instance.
|
||||
* nickname: The user's nickname.
|
||||
*/
|
||||
sendInit(chatID, nickname) {
|
||||
this.sendCommand({
|
||||
action: "init",
|
||||
chat_id: chatID,
|
||||
nickname: nickname,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the 'message' command which sends a chat message to the chat.
|
||||
*
|
||||
* msgText: The text of the chat message.
|
||||
*/
|
||||
sendChatMessage(msgText) {
|
||||
this.sendCommand({
|
||||
action: "message",
|
||||
text: msgText,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an incoming JSON message and dispatches specific events.
|
||||
*
|
||||
* msgText: The content of the message as a JSON string.
|
||||
*/
|
||||
_parseMessage(msgString) {
|
||||
try {
|
||||
const msg = JSON.parse(msgString);
|
||||
|
||||
switch (msg.type) {
|
||||
// Response to 'init' command (doesn't have much content, I guess)
|
||||
case "init":
|
||||
console.log("Got init response: ", msg);
|
||||
this.dispatch("initialized");
|
||||
break;
|
||||
|
||||
// Incoming chat message
|
||||
case "message":
|
||||
console.log("Received chat message from '" + msg.from + "', text '" + msg.text + "'");
|
||||
this.dispatch("receivedMessage", {
|
||||
from: msg.from,
|
||||
text: msg.text,
|
||||
});
|
||||
break;
|
||||
|
||||
// TODO Topic change, user join/leave, error, ...
|
||||
|
||||
default:
|
||||
console.error("Unknown message type '" + msg.type + "'");
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error("Error parsing message JSON: " + e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* Base class to implement an event system in any class. Events can be dispatched and
|
||||
* subscribed/unsubscribed. Events can have multiple registered callbacks.
|
||||
*/
|
||||
class EventDispatcher {
|
||||
constructor() {
|
||||
this._events = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a named event. Calls all callbacks that are subscribed to this event.
|
||||
*/
|
||||
dispatch(eventName, data = null) {
|
||||
if (this._events[eventName]) {
|
||||
this._events[eventName].forEach((callback) => {
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to an event.
|
||||
*/
|
||||
on(eventName, callback) {
|
||||
if (!this._events[eventName]) {
|
||||
this._events[eventName] = [];
|
||||
}
|
||||
this._events[eventName].push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from an event. Not sure how to unsubscribe anonymous functions though.
|
||||
* If callback is not found, nothing happens.
|
||||
*/
|
||||
off(eventName, callback) {
|
||||
if (this._events[eventName]) {
|
||||
this._events[eventName] = this._events[eventName].filter(item => item !== callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,10 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* Global settings object.
|
||||
*/
|
||||
const AppSettings = {
|
||||
// Alternative WebSocket URI for local testing:
|
||||
// serverWsUri: "ws://localhost:32715",
|
||||
serverWsUri: "wss://chat.glitch-in.space:443/ws/",
|
||||
};
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
// https://bootstrapious.com/p/bootstrap-sidebar
|
||||
$(document).ready(function () {
|
||||
|
||||
$('#sidebarCollapse').on('click touch', function () {
|
||||
$('#sidebar').removeClass('active');
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
document.querySelector('#sidebarCollapse').addEventListener('click', function () {
|
||||
document.querySelector('#sidebar').classList.remove('active');
|
||||
});
|
||||
|
||||
$('#sidebarShow').on('click touch', function () {
|
||||
$('#sidebar').addClass('active');
|
||||
document.querySelector('#sidebarShow').addEventListener('click', function () {
|
||||
document.querySelector('#sidebar').classList.add('active');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
"use strict";
|
||||
|
||||
// Global objects for debugging purposes
|
||||
let client;
|
||||
let ui;
|
||||
|
||||
/**
|
||||
* Class for handling the UI.
|
||||
*/
|
||||
class UI {
|
||||
constructor() {
|
||||
this.client = null;
|
||||
|
||||
// Initialize the UI
|
||||
this.initUI();
|
||||
|
||||
// TODO start client only after the user entered their nickname
|
||||
let chatID = "42";
|
||||
let nickname = "binaryDiv";
|
||||
this.initClient(chatID, nickname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the web UI.
|
||||
*/
|
||||
initUI() {
|
||||
// TODO stub
|
||||
}
|
||||
|
||||
/**
|
||||
* Create instance of Client and initialize connection.
|
||||
*
|
||||
* chatID: The ID of the chat instance.
|
||||
* nickname: The user's nickname.
|
||||
*/
|
||||
initClient(chatID, nickname) {
|
||||
const wsUri = AppSettings.serverWsUri;
|
||||
this.client = new Client(wsUri, chatID, nickname);
|
||||
|
||||
// Subscribe to Client events
|
||||
this.client.on("initialized", this._onClientInit.bind(this));
|
||||
this.client.on("disconnected", this._onClientDisconnect.bind(this));
|
||||
this.client.on("connectionError", this._onClientError.bind(this));
|
||||
this.client.on("receivedMessage", this._onClientReceivedMessage.bind(this));
|
||||
}
|
||||
|
||||
_onClientInit() {
|
||||
console.log("UI: Connection initialized!");
|
||||
|
||||
// Send a test message
|
||||
this.client.sendChatMessage("Meow meow! :3");
|
||||
}
|
||||
|
||||
_onClientDisconnect() {
|
||||
console.log("UI: Connection closed!");
|
||||
}
|
||||
|
||||
_onClientError() {
|
||||
console.log("UI: Connection error! :(");
|
||||
}
|
||||
|
||||
_onClientReceivedMessage(msg) {
|
||||
console.log("UI: Message from '" + msg.from + "', text: '" + msg.text + "'");
|
||||
}
|
||||
}
|
||||
|
||||
// Execute this on start (wrapped in an anonymous function)
|
||||
(function() {
|
||||
// TODO
|
||||
ui = new UI();
|
||||
client = ui.client;
|
||||
})();
|
||||
|
|
@ -1,22 +1,89 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Example code from:
|
||||
# https://websockets.readthedocs.io/en/stable/intro.html
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import websockets
|
||||
|
||||
|
||||
async def hello(websocket, path):
|
||||
name = await websocket.recv()
|
||||
print(f"< {name}")
|
||||
def log_message_error(error, message_text):
|
||||
print(f"[E] {error}")
|
||||
print(f" Message: {message_text}")
|
||||
|
||||
greeting = f"Hello {name}!"
|
||||
|
||||
await websocket.send(greeting)
|
||||
print(f"> {greeting}")
|
||||
async def parse_client_message(websocket, message_text):
|
||||
"""Parse a message (JSON object) from a client."""
|
||||
|
||||
start_server = websockets.serve(hello, '0.0.0.0', 32715)
|
||||
try:
|
||||
# Parse JSON
|
||||
message = json.loads(message_text)
|
||||
|
||||
# Handle message types
|
||||
if message['action'] == 'init':
|
||||
await handle_client_init(websocket, message)
|
||||
else:
|
||||
log_message_error(f"Unknown action '{message['action']}'", message_text)
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
log_message_error(f"JSON decode error: {e}", message_text)
|
||||
except KeyError as e:
|
||||
log_message_error(f"Missing key {e} in JSON message", message_text)
|
||||
|
||||
|
||||
async def handle_client_init(websocket, message):
|
||||
"""Handle client 'init' message."""
|
||||
|
||||
# TODO input check for nickname
|
||||
|
||||
print(f"< init: chat_id='{message['chat_id']}', nickname='{message['nickname']}'")
|
||||
await send_client_init_response(websocket)
|
||||
await send_client_previous_messages(websocket)
|
||||
|
||||
|
||||
async def send_client_init_response(websocket):
|
||||
"""Send an init response message to a newly connected client."""
|
||||
|
||||
response = json.dumps({
|
||||
"type": "init"
|
||||
})
|
||||
print(f"> {response}")
|
||||
await websocket.send(response)
|
||||
|
||||
|
||||
async def send_client_previous_messages(websocket):
|
||||
"""Sends previously written chat messages to a newly connected client."""
|
||||
|
||||
# For now: send test messages
|
||||
for i in range(1, 4):
|
||||
testmsg = json.dumps({
|
||||
"type": "message",
|
||||
"from": "Alice",
|
||||
"text": f"Message number {i}, hello!"
|
||||
})
|
||||
print(f"> {testmsg}")
|
||||
await websocket.send(testmsg)
|
||||
|
||||
|
||||
async def client_handler(websocket, path):
|
||||
"""Handle client connection."""
|
||||
|
||||
print(f"++ New client {websocket.remote_address}")
|
||||
try:
|
||||
# Read and parse messages until connection is closed
|
||||
async for message_text in websocket:
|
||||
await parse_client_message(websocket, message_text)
|
||||
except websockets.exceptions.ConnectionClosed:
|
||||
# Ignore ConnectionClosed exceptions because we handle this in finally
|
||||
pass
|
||||
finally:
|
||||
print(f"-- Client connection closed {websocket.remote_address}")
|
||||
|
||||
# Create WebSocket listener
|
||||
start_server = websockets.serve(client_handler, '0.0.0.0', 32715)
|
||||
|
||||
try:
|
||||
asyncio.get_event_loop().run_until_complete(start_server)
|
||||
asyncio.get_event_loop().run_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("[received SIGINT]")
|
||||
pass
|
||||
finally:
|
||||
print("Exiting...")
|
||||
|
|
|
|||
Loading…
Reference in New Issue