Compare commits

..

No commits in common. "master" and "design" have entirely different histories.

11 changed files with 10478 additions and 445 deletions

View File

@ -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 ???
}

View File

@ -1,8 +1,3 @@
body {
font-family: Roboto,sans-serif;
font-size: 1rem;
}
img {
max-width: 100%;
}
@ -25,16 +20,6 @@ img {
overflow: hidden;
}
@media (min-width: 768px) {
.messaging_heading {
display: none;
}
#sidebarCollapse {
display: none;
}
}
.inbox_msg {
border: 1px solid #c4c4c4;
clear: both;
@ -60,11 +45,10 @@ img {
@media (min-width: 768px) {
#sidebar {
border-left: 1px solid #c4c4c4;
float: left;
/*float: right;*/
/*position: relative;*/
overflow: hidden;
position: relative;
transform: none;
width: 40%;
/*width: 40%;*/
}
}
@ -102,22 +86,15 @@ 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 {
position: absolute;
right: 28px;
.search_bar .input-group-addon {
margin: 0 0 0 -30px;
}
.search_bar .input-group-addon button {
@ -126,17 +103,12 @@ 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: 75%;
width: 88%;
}
.chat_ib h5 {
@ -152,7 +124,7 @@ img {
.chat_ib p {
font-size: 14px;
color: #2b2b2b;
color: #989898;
margin: auto;
}
@ -173,23 +145,22 @@ img {
}
.inbox_chat {
height: 540px;
height: 550px;
overflow-y: scroll;
}
.active_chat {
background: #dae3f2;
background: #ebebeb;
}
h5.incoming_msg_user {
color: #464646;
font-size: 15px;
margin: 0 0 8px 30px;
.incoming_msg_img {
display: inline-block;
width: 6%;
}
.received_msg {
display: inline-block;
padding-left: 30px;
padding: 0 0 0 10px;
vertical-align: top;
width: 92%;
}
@ -199,23 +170,25 @@ h5.incoming_msg_user {
}
.received_withd_msg p {
background: #dae3f2 none repeat scroll 0 0;
background: #ebebeb none repeat scroll 0 0;
border-radius: 3px;
color: #2b2b2b;
color: #646464;
font-size: 14px;
margin: 0;
padding: 10px 10px 10px 12px;
padding: 5px 10px 5px 12px;
width: 100%;
}
.messages {
padding: 30px;
padding: 30px 15px 0 25px;
width: 100%;
}
@media (min-width: 768px) {
.messages {
width: auto;
/*float: left;*/
padding: 30px 15px 0 25px;
/*width: 60%;*/
}
}
@ -226,10 +199,8 @@ h5.incoming_msg_user {
margin: 8px 0 0;
}
/*Messages*/
.sent_msg {
float: right;
margin-right: 20px;
width: 46%;
}
@ -238,13 +209,13 @@ h5.incoming_msg_user {
border-radius: 3px;
font-size: 14px;
margin: 0; color:#fff;
padding: 10px 12px 10px 12px;
/*width: 100%;*/
padding: 5px 10px 5px 12px;
width: 100%;
}
.outgoing_msg {
overflow: hidden;
margin: 12px 0 12px;
margin: 26px 0 26px;
}
.input_msg_write input {
@ -253,33 +224,30 @@ h5.incoming_msg_user {
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: 3px;
border-radius: 50%;
color: #fff;
cursor: pointer;
height: 35px;
font-size: 17px;
height: 33px;
position: absolute;
right: 0;
top: 7px;
width: 52px;
top: 11px;
width: 33px;
}
.msg_send_btn svg {
fill: #ffffff;
padding-top: 4px;
}
.msg_history {

View File

@ -4,6 +4,9 @@
<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>
@ -118,7 +121,7 @@
<div class="messages">
<div class="msg_history">
<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_withd_msg">
<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>
</div>
<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_withd_msg">
<p>Test, which is a new approach to have</p>
@ -148,7 +151,7 @@
<span class="time_date"> 11:01 AM | Today</span> </div>
</div>
<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_withd_msg">
<p>We work directly with our designers and suppliers,
@ -163,8 +166,11 @@
<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="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 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>
</button>
</div>
@ -175,10 +181,16 @@
</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/ui.js"></script>
<script src="js/sidebar.js"></script>
</body>

View File

@ -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;
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);
}
// 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);
}
}
// Initialization
function init() {
console.log("Init...");
openWebSocket();
}
// 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();
});

View File

@ -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);
}
}
}

10364
public_html/js/jquery-3.3.1.js vendored Normal file

File diff suppressed because it is too large Load Diff

2
public_html/js/jquery-3.3.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -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/",
};

View File

@ -1,10 +1,12 @@
// https://bootstrapious.com/p/bootstrap-sidebar
document.addEventListener('DOMContentLoaded', function(){
document.querySelector('#sidebarCollapse').addEventListener('click', function () {
document.querySelector('#sidebar').classList.remove('active');
$(document).ready(function () {
$('#sidebarCollapse').on('click touch', function () {
$('#sidebar').removeClass('active');
});
document.querySelector('#sidebarShow').addEventListener('click', function () {
document.querySelector('#sidebar').classList.add('active');
$('#sidebarShow').on('click touch', function () {
$('#sidebar').addClass('active');
});
});

View File

@ -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;
})();

View File

@ -1,89 +1,22 @@
#!/usr/bin/env python
# Example code from:
# https://websockets.readthedocs.io/en/stable/intro.html
import asyncio
import json
import websockets
def log_message_error(error, message_text):
print(f"[E] {error}")
print(f" Message: {message_text}")
async def hello(websocket, path):
name = await websocket.recv()
print(f"< {name}")
greeting = f"Hello {name}!"
async def parse_client_message(websocket, message_text):
"""Parse a message (JSON object) from a client."""
await websocket.send(greeting)
print(f"> {greeting}")
try:
# Parse JSON
message = json.loads(message_text)
start_server = websockets.serve(hello, '0.0.0.0', 32715)
# 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...")
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()