Compare commits
No commits in common. "master" and "design" have entirely different histories.
|
|
@ -1,49 +0,0 @@
|
||||||
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,8 +1,3 @@
|
||||||
body {
|
|
||||||
font-family: Roboto,sans-serif;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
@ -25,16 +20,6 @@ img {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.messaging_heading {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sidebarCollapse {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inbox_msg {
|
.inbox_msg {
|
||||||
border: 1px solid #c4c4c4;
|
border: 1px solid #c4c4c4;
|
||||||
clear: both;
|
clear: both;
|
||||||
|
|
@ -60,11 +45,10 @@ img {
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
#sidebar {
|
#sidebar {
|
||||||
border-left: 1px solid #c4c4c4;
|
border-left: 1px solid #c4c4c4;
|
||||||
float: left;
|
/*float: right;*/
|
||||||
|
/*position: relative;*/
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
/*width: 40%;*/
|
||||||
transform: none;
|
|
||||||
width: 40%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,22 +86,15 @@ img {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_bar .stylish-input-group {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search_bar input {
|
.search_bar input {
|
||||||
background: none;
|
background: none;
|
||||||
border-bottom: 1px solid #cdcdcd;
|
border-bottom: 1px solid #cdcdcd;
|
||||||
font-size: 1rem;
|
|
||||||
height: 25px;
|
|
||||||
padding: 2px 30px 4px 6px;
|
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
padding: 2px 0 4px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_bar .input-group-addon {
|
.search_bar .input-group-addon {
|
||||||
position: absolute;
|
margin: 0 0 0 -30px;
|
||||||
right: 28px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_bar .input-group-addon button {
|
.search_bar .input-group-addon button {
|
||||||
|
|
@ -126,17 +103,12 @@ img {
|
||||||
color: #707070;
|
color: #707070;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search_bar .input-group-addon button svg {
|
|
||||||
padding-top: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat_ib {
|
.chat_ib {
|
||||||
float: left;
|
float: left;
|
||||||
padding: 0 0 0 15px;
|
padding: 0 0 0 15px;
|
||||||
width: 75%;
|
width: 88%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat_ib h5 {
|
.chat_ib h5 {
|
||||||
|
|
@ -152,7 +124,7 @@ img {
|
||||||
|
|
||||||
.chat_ib p {
|
.chat_ib p {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #2b2b2b;
|
color: #989898;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,23 +145,22 @@ img {
|
||||||
}
|
}
|
||||||
|
|
||||||
.inbox_chat {
|
.inbox_chat {
|
||||||
height: 540px;
|
height: 550px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.active_chat {
|
.active_chat {
|
||||||
background: #dae3f2;
|
background: #ebebeb;
|
||||||
}
|
}
|
||||||
|
|
||||||
h5.incoming_msg_user {
|
.incoming_msg_img {
|
||||||
color: #464646;
|
display: inline-block;
|
||||||
font-size: 15px;
|
width: 6%;
|
||||||
margin: 0 0 8px 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.received_msg {
|
.received_msg {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding-left: 30px;
|
padding: 0 0 0 10px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
width: 92%;
|
width: 92%;
|
||||||
}
|
}
|
||||||
|
|
@ -199,23 +170,25 @@ h5.incoming_msg_user {
|
||||||
}
|
}
|
||||||
|
|
||||||
.received_withd_msg p {
|
.received_withd_msg p {
|
||||||
background: #dae3f2 none repeat scroll 0 0;
|
background: #ebebeb none repeat scroll 0 0;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
color: #2b2b2b;
|
color: #646464;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 10px 10px 10px 12px;
|
padding: 5px 10px 5px 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages {
|
.messages {
|
||||||
padding: 30px;
|
padding: 30px 15px 0 25px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.messages {
|
.messages {
|
||||||
width: auto;
|
/*float: left;*/
|
||||||
|
padding: 30px 15px 0 25px;
|
||||||
|
/*width: 60%;*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,10 +199,8 @@ h5.incoming_msg_user {
|
||||||
margin: 8px 0 0;
|
margin: 8px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Messages*/
|
|
||||||
.sent_msg {
|
.sent_msg {
|
||||||
float: right;
|
float: right;
|
||||||
margin-right: 20px;
|
|
||||||
width: 46%;
|
width: 46%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,13 +209,13 @@ h5.incoming_msg_user {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin: 0; color:#fff;
|
margin: 0; color:#fff;
|
||||||
padding: 10px 12px 10px 12px;
|
padding: 5px 10px 5px 12px;
|
||||||
/*width: 100%;*/
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.outgoing_msg {
|
.outgoing_msg {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 12px 0 12px;
|
margin: 26px 0 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input_msg_write input {
|
.input_msg_write input {
|
||||||
|
|
@ -253,33 +224,30 @@ h5.incoming_msg_user {
|
||||||
color: #4c4c4c;
|
color: #4c4c4c;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
padding-left: 30px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type_msg {
|
.type_msg {
|
||||||
border-top: 1px solid #c4c4c4;
|
border-top: 1px solid #c4c4c4;
|
||||||
float: left;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 60%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg_send_btn {
|
.msg_send_btn {
|
||||||
background: #05728f none repeat scroll 0 0;
|
background: #05728f none repeat scroll 0 0;
|
||||||
border: medium none;
|
border: medium none;
|
||||||
border-radius: 3px;
|
border-radius: 50%;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 35px;
|
font-size: 17px;
|
||||||
|
height: 33px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 7px;
|
top: 11px;
|
||||||
width: 52px;
|
width: 33px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg_send_btn svg {
|
.msg_send_btn svg {
|
||||||
fill: #ffffff;
|
fill: #ffffff;
|
||||||
padding-top: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg_history {
|
.msg_history {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>InstantChat</title>
|
<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">
|
<link rel="stylesheet" href="css/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -118,7 +121,7 @@
|
||||||
<div class="messages">
|
<div class="messages">
|
||||||
<div class="msg_history">
|
<div class="msg_history">
|
||||||
<div class="incoming_msg">
|
<div class="incoming_msg">
|
||||||
<h5 class="incoming_msg_user">Sunil Rajput</h5>
|
<div class="incoming_msg_img"> <img src="https://ptetutorials.com/images/user-profile.png" alt="sunil"> </div>
|
||||||
<div class="received_msg">
|
<div class="received_msg">
|
||||||
<div class="received_withd_msg">
|
<div class="received_withd_msg">
|
||||||
<p>Test which is a new approach to have all
|
<p>Test which is a new approach to have all
|
||||||
|
|
@ -134,7 +137,7 @@
|
||||||
<span class="time_date"> 11:01 AM | June 9</span> </div>
|
<span class="time_date"> 11:01 AM | June 9</span> </div>
|
||||||
</div>
|
</div>
|
||||||
<div class="incoming_msg">
|
<div class="incoming_msg">
|
||||||
<h5 class="incoming_msg_user">Sunil Rajput</h5>
|
<div class="incoming_msg_img"> <img src="https://ptetutorials.com/images/user-profile.png" alt="sunil"> </div>
|
||||||
<div class="received_msg">
|
<div class="received_msg">
|
||||||
<div class="received_withd_msg">
|
<div class="received_withd_msg">
|
||||||
<p>Test, which is a new approach to have</p>
|
<p>Test, which is a new approach to have</p>
|
||||||
|
|
@ -148,7 +151,7 @@
|
||||||
<span class="time_date"> 11:01 AM | Today</span> </div>
|
<span class="time_date"> 11:01 AM | Today</span> </div>
|
||||||
</div>
|
</div>
|
||||||
<div class="incoming_msg">
|
<div class="incoming_msg">
|
||||||
<h5 class="incoming_msg_user">Sunil Rajput</h5>
|
<div class="incoming_msg_img"> <img src="https://ptetutorials.com/images/user-profile.png" alt="sunil"> </div>
|
||||||
<div class="received_msg">
|
<div class="received_msg">
|
||||||
<div class="received_withd_msg">
|
<div class="received_withd_msg">
|
||||||
<p>We work directly with our designers and suppliers,
|
<p>We work directly with our designers and suppliers,
|
||||||
|
|
@ -163,8 +166,11 @@
|
||||||
<div class="input_msg_write">
|
<div class="input_msg_write">
|
||||||
<input type="text" class="write_msg" placeholder="Type a message" />
|
<input type="text" class="write_msg" placeholder="Type a message" />
|
||||||
<button class="msg_send_btn" type="button">
|
<button class="msg_send_btn" type="button">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="21" viewBox="0 0 25 21">
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
<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"/>
|
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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -175,10 +181,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="js/settings.js"></script>
|
|
||||||
<script src="js/events.js"></script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<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/client.js"></script>
|
<script src="js/client.js"></script>
|
||||||
<script src="js/ui.js"></script>
|
|
||||||
<script src="js/sidebar.js"></script>
|
<script src="js/sidebar.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -1,122 +1,47 @@
|
||||||
"use strict";
|
// Constants and variables
|
||||||
|
//const wsUri = "ws://localhost:32715";
|
||||||
|
const wsUri = "wss://chat.glitch-in.space:443/ws/";
|
||||||
|
let websocket;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
|
|
||||||
this.wsUri = wsUri;
|
// Initialization
|
||||||
this.chatID = chatID;
|
function init() {
|
||||||
this.nickname = nickname;
|
console.log("Init...");
|
||||||
|
openWebSocket();
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal WebSocket event handlers
|
|
||||||
_onSocketOpen(evt) {
|
|
||||||
console.log("Connected to " + this.wsUri);
|
|
||||||
this.sendInit(this.chatID, this.nickname);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onSocketClose(evt) {
|
|
||||||
console.log("Connection closed (code " + evt.code + ").");
|
|
||||||
this.dispatch("disconnected");
|
|
||||||
}
|
|
||||||
|
|
||||||
_onSocketError(evt) {
|
|
||||||
console.error("Connection error: ", evt);
|
|
||||||
this.dispatch("connectionError");
|
|
||||||
}
|
|
||||||
|
|
||||||
_onSocketMessage(evt) {
|
|
||||||
console.log("Received: " + evt.data);
|
|
||||||
this._parseMessage(evt.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebSocket event handlers
|
||||||
|
function onOpen(evt) {
|
||||||
|
console.log('Connected to ' + wsUri + '.');
|
||||||
|
|
||||||
|
let text = "Meow";
|
||||||
|
console.log('Sending "' + text + '".');
|
||||||
|
websocket.send(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClose(evt) {
|
||||||
|
console.log("Connection closed (code " + evt.code + "): ", evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMessage(evt) {
|
||||||
|
console.log('Received: "' + evt.data + '".');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError(evt) {
|
||||||
|
console.error('Connection error: ', evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Run script after page is loaded
|
||||||
|
$(function() {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
"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
|
|
@ -1,10 +0,0 @@
|
||||||
"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,10 +1,12 @@
|
||||||
// https://bootstrapious.com/p/bootstrap-sidebar
|
// https://bootstrapious.com/p/bootstrap-sidebar
|
||||||
document.addEventListener('DOMContentLoaded', function(){
|
$(document).ready(function () {
|
||||||
document.querySelector('#sidebarCollapse').addEventListener('click', function () {
|
|
||||||
document.querySelector('#sidebar').classList.remove('active');
|
$('#sidebarCollapse').on('click touch', function () {
|
||||||
|
$('#sidebar').removeClass('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector('#sidebarShow').addEventListener('click', function () {
|
$('#sidebarShow').on('click touch', function () {
|
||||||
document.querySelector('#sidebar').classList.add('active');
|
$('#sidebar').addClass('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
"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,89 +1,22 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Example code from:
|
||||||
|
# https://websockets.readthedocs.io/en/stable/intro.html
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
|
||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
|
|
||||||
def log_message_error(error, message_text):
|
async def hello(websocket, path):
|
||||||
print(f"[E] {error}")
|
name = await websocket.recv()
|
||||||
print(f" Message: {message_text}")
|
print(f"< {name}")
|
||||||
|
|
||||||
|
greeting = f"Hello {name}!"
|
||||||
|
|
||||||
async def parse_client_message(websocket, message_text):
|
await websocket.send(greeting)
|
||||||
"""Parse a message (JSON object) from a client."""
|
print(f"> {greeting}")
|
||||||
|
|
||||||
try:
|
start_server = websockets.serve(hello, '0.0.0.0', 32715)
|
||||||
# Parse JSON
|
|
||||||
message = json.loads(message_text)
|
|
||||||
|
|
||||||
# Handle message types
|
asyncio.get_event_loop().run_until_complete(start_server)
|
||||||
if message['action'] == 'init':
|
asyncio.get_event_loop().run_forever()
|
||||||
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