Create web UI and socket code for the chat interface

This commit is contained in:
Saptak S 2020-03-08 14:51:43 +05:30
parent a217e54d28
commit 2c938fd777
No known key found for this signature in database
GPG Key ID: 2D9B32E54C68A3FB
8 changed files with 186 additions and 1 deletions

View File

@ -63,6 +63,9 @@ def main(cwd=None):
parser.add_argument(
"--website", action="store_true", dest="website", help="Publish website"
)
parser.add_argument(
"--chat", action="store_true", dest="chat", help="Start chat server"
)
# Tor connection-related args
parser.add_argument(
"--local-only",
@ -172,6 +175,7 @@ def main(cwd=None):
receive = bool(args.receive)
website = bool(args.website)
chat = bool(args.chat)
local_only = bool(args.local_only)
connect_timeout = int(args.connect_timeout)
config_filename = args.config
@ -190,6 +194,8 @@ def main(cwd=None):
mode = "receive"
elif website:
mode = "website"
elif chat:
mode = "chat"
else:
mode = "share"

View File

@ -215,6 +215,16 @@ class Common:
r = random.SystemRandom()
return "-".join(r.choice(wordlist) for _ in range(word_count))
def build_username(self, word_count=2):
"""
Returns a random string made of words from the wordlist, such as "deter-trig".
"""
with open(self.get_resource_path("wordlist.txt")) as f:
wordlist = f.read().split()
r = random.SystemRandom()
return "-".join(r.choice(wordlist) for _ in range(word_count))
@staticmethod
def random_string(num_bytes, output_len=None):
"""

View File

@ -49,6 +49,7 @@ class ModeSettings:
"share": {"autostop_sharing": True, "filenames": []},
"receive": {"data_dir": self.build_default_receive_data_dir()},
"website": {"disable_csp": False, "filenames": []},
"chat": {"room": "default"},
}
self._settings = {}

View File

@ -0,0 +1,75 @@
import os
import tempfile
import json
from datetime import datetime
from flask import Request, request, render_template, make_response, flash, redirect, session
from werkzeug.utils import secure_filename
from flask_socketio import emit, join_room, leave_room
from .. import strings
class ChatModeWeb:
"""
All of the web logic for chat mode
"""
def __init__(self, common, web):
self.common = common
self.common.log("ChatModeWeb", "__init__")
self.web = web
self.can_upload = True
self.uploads_in_progress = []
# This tracks the history id
self.cur_history_id = 0
self.define_routes()
def define_routes(self):
"""
The web app routes for chatting
"""
@self.web.app.route("/")
def index():
history_id = self.cur_history_id
self.cur_history_id += 1
self.web.add_request(
self.web.REQUEST_INDIVIDUAL_FILE_STARTED,
request.path,
{"id": history_id, "status_code": 200},
)
self.web.add_request(self.web.REQUEST_LOAD, request.path)
r = make_response(
render_template(
"chat.html", static_url_path=self.web.static_url_path
)
)
return self.web.add_security_headers(r)
@self.web.socketio.on("joined", namespace="/chat")
def joined(message):
"""Sent by clients when they enter a room.
A status message is broadcast to all people in the room."""
session["name"] = self.common.build_username()
session["room"] = self.web.settings.default_settings["chat"]["room"]
join_room(session.get("room"))
emit(
"status",
{"msg": session.get("name") + " has entered the room."},
room=session.get("room")
)
@self.web.socketio.on("text", namespace="/chat")
def text(message):
"""Sent by a client when the user entered a new message.
The message is sent to all people in the room."""
emit(
"message",
{"msg": session.get("name") + ": " + message["msg"]},
room=session.get("room")
)

View File

@ -20,12 +20,14 @@ from flask import (
__version__ as flask_version,
)
from flask_httpauth import HTTPBasicAuth
from flask_socketio import SocketIO
from .. import strings
from .share_mode import ShareModeWeb
from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeRequest
from .website_mode import WebsiteModeWeb
from .chat_mode import ChatModeWeb
# Stub out flask's show_server_banner function, to avoiding showing warnings that
# are not applicable to OnionShare
@ -134,12 +136,17 @@ class Web:
self.share_mode = None
self.receive_mode = None
self.website_mode = None
self.chat_mode = None
if self.mode == "share":
self.share_mode = ShareModeWeb(self.common, self)
elif self.mode == "receive":
self.receive_mode = ReceiveModeWeb(self.common, self)
elif self.mode == "website":
self.website_mode = WebsiteModeWeb(self.common, self)
elif self.mode == "chat":
self.socketio = SocketIO()
self.socketio.init_app(self.app)
self.chat_mode = ChatModeWeb(self.common, self)
def get_mode(self):
if self.mode == "share":
@ -148,6 +155,8 @@ class Web:
return self.receive_mode
elif self.mode == "website":
return self.website_mode
elif self.mode == "chat":
return self.chat_mode
else:
return None
@ -366,7 +375,10 @@ class Web:
host = "127.0.0.1"
self.running = True
self.app.run(host=host, port=port, threaded=True)
if self.mode == "chat":
self.socketio.run(self.app, host=host, port=port)
else:
self.app.run(host=host, port=port, threaded=True)
def stop(self, port):
"""

31
share/static/js/chat.js Normal file
View File

@ -0,0 +1,31 @@
$(function(){
var socket;
$(document).ready(function(){
socket = io.connect('http://' + document.domain + ':' + location.port + '/chat');
socket.on('connect', function() {
socket.emit('joined', {});
});
socket.on('status', function(data) {
console.log("received")
$('#chat').append('<p><small><i>' + data.msg + '</i></small></p>');
$('#chat').scrollTop($('#chat')[0].scrollHeight);
});
socket.on('message', function(data) {
$('#chat').append('<p>' + data.msg + '</p>');
$('#chat').scrollTop($('#chat')[0].scrollHeight);
});
$('#new-message').on('keypress', function(e) {
var code = e.keyCode || e.which;
if (code == 13) {
emitMessage(socket);
}
});
$('#send-button').on('click', emitMessage);
});
});
function emitMessage(socket) {
text = $('#new-message').val();
$('#new-message').val('');
socket.emit('text', {msg: text});
}

3
share/static/js/socket.io.min.js vendored Normal file

File diff suppressed because one or more lines are too long

47
share/templates/chat.html Normal file
View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<title>OnionShare</title>
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
<link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
</head>
<body>
<header class="clearfix">
<img class="logo" src="{{ static_url_path }}/img/logo.png" title="OnionShare">
<h1>OnionShare</h1>
</header>
<div>
<ul id="flashes" class="flashes">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<li class="{{ category }}">{{ message }}</li>
{% endfor %}
{% endif %}
{% endwith %}
</ul>
</div>
<div class="chat-container">
<div class="chat-users">
</div>
<div class="chat-wrapper">
<p class="chat-header">Chat Messages</p>
<div id="chat"></div>
<div class="chat-form">
<p><input type="text" id="new-message" name="new-message" placeholder="Type your message"/></p>
<p><button type="button" id="send-button" class="button">Send Message</button></p>
</div>
</div>
</div>
<script src="{{ static_url_path }}/js/jquery-3.4.0.min.js"></script>
<script src="{{ static_url_path }}/js/socket.io.min.js"></script>
<script async src="{{ static_url_path }}/js/chat.js"></script>
</body>
</html>