Commit 66dd7167 authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Implement private messages.

parent bd5cd7c1
...@@ -138,6 +138,7 @@ Typing a line starting with a slash `/` in the chat dialogue causes ...@@ -138,6 +138,7 @@ Typing a line starting with a slash `/` in the chat dialogue causes
a command to be sent to the server. The following commands are available a command to be sent to the server. The following commands are available
to all users: to all users:
- `/msg user text`: sends a private message;
- `/me text`: sends a chat message starting with the sender's username; - `/me text`: sends a chat message starting with the sender's username;
- `/leave`: equivalent to clicking the *Disconnect* button. - `/leave`: equivalent to clicking the *Disconnect* button.
- `/set var val`: sets the value of a configuration variable without any - `/set var val`: sets the value of a configuration variable without any
......
...@@ -141,6 +141,7 @@ type clientMessage struct { ...@@ -141,6 +141,7 @@ type clientMessage struct {
Type string `json:"type"` Type string `json:"type"`
Kind string `json:"kind,omitempty"` Kind string `json:"kind,omitempty"`
Id string `json:"id,omitempty"` Id string `json:"id,omitempty"`
Dest string `json:"dest,omitempty"`
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
Permissions group.ClientPermissions `json:"permissions,omitempty"` Permissions group.ClientPermissions `json:"permissions,omitempty"`
...@@ -158,14 +159,14 @@ func fromJSTime(tm uint64) time.Time { ...@@ -158,14 +159,14 @@ func fromJSTime(tm uint64) time.Time {
if tm == 0 { if tm == 0 {
return time.Time{} return time.Time{}
} }
return time.Unix(int64(tm)/1000, (int64(tm)%1000) * 1000000) return time.Unix(int64(tm)/1000, (int64(tm)%1000)*1000000)
} }
func toJSTime(tm time.Time) uint64 { func toJSTime(tm time.Time) uint64 {
if tm.Before(time.Unix(0, 0)) { if tm.Before(time.Unix(0, 0)) {
return 0 return 0
} }
return uint64((tm.Sub(time.Unix(0, 0)) + time.Millisecond / 2) / time.Millisecond) return uint64((tm.Sub(time.Unix(0, 0)) + time.Millisecond/2) / time.Millisecond)
} }
type closeMessage struct { type closeMessage struct {
...@@ -292,7 +293,7 @@ func addDownConn(c *webClient, id string, remote conn.Up) (*rtpDownConnection, e ...@@ -292,7 +293,7 @@ func addDownConn(c *webClient, id string, remote conn.Up) (*rtpDownConnection, e
return conn, err return conn, err
} }
func addDownConnHelper(c *webClient, conn *rtpDownConnection, remote conn.Up) (error) { func addDownConnHelper(c *webClient, conn *rtpDownConnection, remote conn.Up) error {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
...@@ -1048,23 +1049,38 @@ func handleClientMessage(c *webClient, m clientMessage) error { ...@@ -1048,23 +1049,38 @@ func handleClientMessage(c *webClient, m clientMessage) error {
} }
case "chat": case "chat":
tm := toJSTime(time.Now()) tm := toJSTime(time.Now())
c.group.AddToChatHistory( if m.Dest == "" {
m.Id, m.Username, tm, m.Kind, m.Value, c.group.AddToChatHistory(
) m.Id, m.Username, tm, m.Kind, m.Value,
)
}
mm := clientMessage{ mm := clientMessage{
Type: "chat", Type: "chat",
Id: m.Id, Id: m.Id,
Dest: m.Dest,
Username: m.Username, Username: m.Username,
Time: tm, Time: tm,
Kind: m.Kind, Kind: m.Kind,
Value: m.Value, Value: m.Value,
} }
clients := c.group.GetClients(nil) if m.Dest == "" {
for _, cc := range clients { clients := c.group.GetClients(nil)
cc, ok := cc.(*webClient) for _, cc := range clients {
if ok { ccc, ok := cc.(*webClient)
cc.write(mm) if ok {
ccc.write(mm)
}
}
} else {
cc := c.group.GetClient(m.Dest)
if cc == nil {
return c.error(group.UserError("user unknown"))
}
ccc, ok := cc.(*webClient)
if !ok {
return c.error(group.UserError("this user doesn't chat"))
} }
ccc.write(mm)
} }
case "groupaction": case "groupaction":
switch m.Kind { switch m.Kind {
......
...@@ -124,7 +124,7 @@ function ServerConnection() { ...@@ -124,7 +124,7 @@ function ServerConnection() {
/** /**
* onchat is called whenever a new chat message is received. * onchat is called whenever a new chat message is received.
* *
* @type {(this: ServerConnection, id: string, username: string, time: number, kind: string, message: string) => void} * @type {(this: ServerConnection, id: string, dest: string, username: string, time: number, kind: string, message: string) => void}
*/ */
this.onchat = null; this.onchat = null;
/** /**
...@@ -148,6 +148,7 @@ function ServerConnection() { ...@@ -148,6 +148,7 @@ function ServerConnection() {
* @property {string} type * @property {string} type
* @property {string} [kind] * @property {string} [kind]
* @property {string} [id] * @property {string} [id]
* @property {string} [dest]
* @property {string} [username] * @property {string} [username]
* @property {string} [password] * @property {string} [password]
* @property {Object<string,boolean>} [permissions] * @property {Object<string,boolean>} [permissions]
...@@ -285,7 +286,7 @@ ServerConnection.prototype.connect = async function(url) { ...@@ -285,7 +286,7 @@ ServerConnection.prototype.connect = async function(url) {
case 'chat': case 'chat':
if(sc.onchat) if(sc.onchat)
sc.onchat.call( sc.onchat.call(
sc, m.id, m.username, m.time, m.kind, m.value, sc, m.id, m.dest, m.username, m.time, m.kind, m.value,
); );
break; break;
case 'clearchat': case 'clearchat':
...@@ -428,10 +429,11 @@ ServerConnection.prototype.newUpStream = function(id) { ...@@ -428,10 +429,11 @@ ServerConnection.prototype.newUpStream = function(id) {
* @param {string} kind - The kind of message, either "" or "me". * @param {string} kind - The kind of message, either "" or "me".
* @param {string} message - The text of the message. * @param {string} message - The text of the message.
*/ */
ServerConnection.prototype.chat = function(username, kind, message) { ServerConnection.prototype.chat = function(username, kind, dest, message) {
this.send({ this.send({
type: 'chat', type: 'chat',
id: this.id, id: this.id,
dest: dest,
username: username, username: username,
kind: kind, kind: kind,
value: message, value: message,
......
...@@ -297,6 +297,15 @@ textarea.form-reply { ...@@ -297,6 +297,15 @@ textarea.form-reply {
background: #ececec; background: #ececec;
} }
.message-private {
background: white;
}
.message-private .message-header:after {
content: "(private)";
margin-left: 1em;
}
.message-system { .message-system {
font-size: 10px; font-size: 10px;
background: #ececec; background: #ececec;
......
...@@ -1227,6 +1227,7 @@ function formatTime(time) { ...@@ -1227,6 +1227,7 @@ function formatTime(time) {
* @typedef {Object} lastMessage * @typedef {Object} lastMessage
* @property {string} [nick] * @property {string} [nick]
* @property {string} [peerId] * @property {string} [peerId]
* @property {string} [dest]
*/ */
/** @type {lastMessage} */ /** @type {lastMessage} */
...@@ -1239,7 +1240,7 @@ let lastMessage = {}; ...@@ -1239,7 +1240,7 @@ let lastMessage = {};
* @param {string} kind * @param {string} kind
* @param {string} message * @param {string} message
*/ */
function addToChatbox(peerId, nick, time, kind, message){ function addToChatbox(peerId, dest, nick, time, kind, message) {
let userpass = getUserPass(); let userpass = getUserPass();
let row = document.createElement('div'); let row = document.createElement('div');
row.classList.add('message-row'); row.classList.add('message-row');
...@@ -1248,12 +1249,16 @@ function addToChatbox(peerId, nick, time, kind, message){ ...@@ -1248,12 +1249,16 @@ function addToChatbox(peerId, nick, time, kind, message){
row.appendChild(container); row.appendChild(container);
if(!peerId) if(!peerId)
container.classList.add('message-system'); container.classList.add('message-system');
else if(userpass.username === nick) { if(userpass.username === nick)
container.classList.add('message-sender'); container.classList.add('message-sender');
} if(dest)
container.classList.add('message-private');
if(kind !== 'me') { if(kind !== 'me') {
let p = formatLines(message.split('\n')); let p = formatLines(message.split('\n'));
if (lastMessage.nick !== nick || lastMessage.peerId !== peerId) { if(lastMessage.nick !== nick ||
lastMessage.peerId !== peerId ||
lastMessage.dest !== (dest || null)) {
let header = document.createElement('p'); let header = document.createElement('p');
let user = document.createElement('span'); let user = document.createElement('span');
user.textContent = nick; user.textContent = nick;
...@@ -1272,6 +1277,7 @@ function addToChatbox(peerId, nick, time, kind, message){ ...@@ -1272,6 +1277,7 @@ function addToChatbox(peerId, nick, time, kind, message){
container.appendChild(p); container.appendChild(p);
lastMessage.nick = nick; lastMessage.nick = nick;
lastMessage.peerId = peerId; lastMessage.peerId = peerId;
lastMessage.dest = (dest || null);
} else { } else {
let asterisk = document.createElement('span'); let asterisk = document.createElement('span');
asterisk.textContent = '*'; asterisk.textContent = '*';
...@@ -1288,8 +1294,7 @@ function addToChatbox(peerId, nick, time, kind, message){ ...@@ -1288,8 +1294,7 @@ function addToChatbox(peerId, nick, time, kind, message){
container.appendChild(user); container.appendChild(user);
container.appendChild(content); container.appendChild(content);
container.classList.add('message-me'); container.classList.add('message-me');
delete(lastMessage.nick); lastMessage = {};
delete(lastMessage.peerId);
} }
let box = document.getElementById('box'); let box = document.getElementById('box');
...@@ -1387,7 +1392,7 @@ function handleInput() { ...@@ -1387,7 +1392,7 @@ function handleInput() {
let s = ""; let s = "";
for(let key in settings) for(let key in settings)
s = s + `${key}: ${JSON.stringify(settings[key])}\n` s = s + `${key}: ${JSON.stringify(settings[key])}\n`
addToChatbox(null, null, Date.now(), null, s); addToChatbox(null, null, null, Date.now(), null, s);
return; return;
} }
let parsed = parseCommand(rest); let parsed = parseCommand(rest);
...@@ -1424,15 +1429,12 @@ function handleInput() { ...@@ -1424,15 +1429,12 @@ function handleInput() {
} }
serverConnection.groupAction(cmd.slice(1)); serverConnection.groupAction(cmd.slice(1));
return; return;
case '/msg':
case '/op': case '/op':
case '/unop': case '/unop':
case '/kick': case '/kick':
case '/present': case '/present':
case '/unpresent': { case '/unpresent': {
if(!serverConnection.permissions.op) {
displayError("You're not an operator");
return;
}
let parsed = parseCommand(rest); let parsed = parseCommand(rest);
let id; let id;
if(parsed[0] in users) { if(parsed[0] in users) {
...@@ -1449,7 +1451,18 @@ function handleInput() { ...@@ -1449,7 +1451,18 @@ function handleInput() {
displayError('Unknown user ' + parsed[0]); displayError('Unknown user ' + parsed[0]);
return; return;
} }
serverConnection.userAction(cmd.slice(1), id, parsed[1]); if(cmd === '/msg') {
let username = getUsername();
if(!username) {
displayError("Sorry, you're anonymous, you cannot chat");
return;
}
serverConnection.chat(username, '', id, parsed[1]);
addToChatbox(serverConnection.id,
id, username, Date.now(), '', parsed[1]);
} else {
serverConnection.userAction(cmd.slice(1), id, parsed[1]);
}
return; return;
} }
default: default:
...@@ -1474,7 +1487,7 @@ function handleInput() { ...@@ -1474,7 +1487,7 @@ function handleInput() {
} }
try { try {
serverConnection.chat(username, me ? 'me' : '', message); serverConnection.chat(username, me ? 'me' : '', '', message);
} catch(e) { } catch(e) {
console.error(e); console.error(e);
displayError(e); displayError(e);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment