Commit fae045fb authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Client-side support for protocol version 2.

This does not yet support the new 'need-username' error.
parent 397892d9
......@@ -70,6 +70,14 @@ message types:
- `dest`, the client-id of the destination client;
- `privileged`, set by the server to indicate that the originating client
had the `op` privilege at the time when it sent the message.
- `value`, the value of the message (which can be of any type).
There are two kinds of errors. Unsolicited errors are sent using messages
of type `usermessage` of kind `error` or `warning`. Errors sent in reply
to a message use the same type as the usual reply, but with a specific
kind (such as `fail`). In either case, the field `value` contains
a human-readable error message, while the field `error`, if present,
contains a stable, program-readable identifier for the error.
## Establishing and maintaining a connection
......@@ -81,15 +89,15 @@ start pipelining messages to the server.
```javascript
{
type: 'handshake',
version: ["1"],
version: ["2"],
id: id
}
```
The version field contains an array of supported protocol versions; the
client may announce multiple versions, but the server will always reply
with a singleton. If the field `id` is absent, then the peer doesn't
originate streams.
The version field contains an array of supported protocol versions, in
decreasing preference order; the client may announce multiple versions,
but the server will always reply with a singleton. If the field `id` is
absent, then the peer doesn't originate streams.
A peer may, at any time, send a `ping` message.
......@@ -302,6 +310,7 @@ A chat message may be sent using a `chat` message.
username: username,
dest: dest-id,
privileged: boolean,
time: time,
noecho: false,
value: message
}
......@@ -313,8 +322,10 @@ originated by the server. The message is forwarded by the server without
interpretation, the server only validates that the `source` and `username`
fields are authentic. The field `privileged` is set to true by the server
if the message was originated by a client with the `op` permission. The
field `noecho` is set by the client if it doesn't wish to receive a copy
of its own message.
field `time` is the timestamp of the message, coded as a number in version
1 of the protocol, and as a string in ISO 8601 format in later versions.
The field `noecho` is set by the client if it doesn't wish to receive
a copy of its own message.
The `chathistory` message is similar to the `chat` message, but carries
a message taken from the chat history. Most clients should treat
......
......@@ -2520,7 +2520,7 @@ function gotFileTransferEvent(state, data) {
* @param {string} id
* @param {string} dest
* @param {string} username
* @param {number} time
* @param {Date} time
* @param {boolean} privileged
* @param {string} kind
* @param {any} message
......@@ -2605,16 +2605,15 @@ function formatLines(lines) {
}
/**
* @param {number} time
* @param {Date} time
* @returns {string}
*/
function formatTime(time) {
let delta = Date.now() - time;
let date = new Date(time);
let m = date.getMinutes();
let delta = Date.now() - time.getTime();
let m = time.getMinutes();
if(delta > -30000)
return date.getHours() + ':' + ((m < 10) ? '0' : '') + m;
return date.toLocaleString();
return time.getHours() + ':' + ((m < 10) ? '0' : '') + m;
return time.toLocaleString();
}
/**
......@@ -2622,7 +2621,7 @@ function formatTime(time) {
* @property {string} [nick]
* @property {string} [peerId]
* @property {string} [dest]
* @property {number} [time]
* @property {Date} [time]
*/
/** @type {lastMessage} */
......@@ -2632,7 +2631,7 @@ let lastMessage = {};
* @param {string} peerId
* @param {string} dest
* @param {string} nick
* @param {number} time
* @param {Date} time
* @param {boolean} privileged
* @param {boolean} history
* @param {string} kind
......@@ -2662,7 +2661,7 @@ function addToChatbox(peerId, dest, nick, time, privileged, history, kind, messa
!time || !lastMessage.time) {
doHeader = true;
} else {
let delta = time - lastMessage.time;
let delta = time.getTime() - lastMessage.time.getTime();
doHeader = delta < 0 || delta > 60000;
}
......@@ -2725,7 +2724,7 @@ function addToChatbox(peerId, dest, nick, time, privileged, history, kind, messa
* @param {string} message
*/
function localMessage(message) {
return addToChatbox(null, null, null, Date.now(), false, false, '', message);
return addToChatbox(null, null, null, new Date(), false, false, '', message);
}
function clearChat() {
......@@ -2960,7 +2959,7 @@ commands.msg = {
throw new Error(`Unknown user ${p[0]}`);
serverConnection.chat('', id, p[1]);
addToChatbox(serverConnection.id, id, serverConnection.username,
Date.now(), false, false, '', p[1]);
new Date(), false, false, '', p[1]);
}
};
......
......@@ -106,6 +106,12 @@ function ServerConnection() {
* @type {WebSocket}
*/
this.socket = null;
/**
* The negotiated protocol version.
*
* @type {string}
*/
this.version = null;
/**
* The set of all up streams, indexed by their id.
*
......@@ -187,7 +193,7 @@ function ServerConnection() {
/**
* onchat is called whenever a new chat message is received.
*
* @type {(this: ServerConnection, id: string, dest: string, username: string, time: number, privileged: boolean, history: boolean, kind: string, message: unknown) => void}
* @type {(this: ServerConnection, id: string, dest: string, username: string, time: Date, privileged: boolean, history: boolean, kind: string, message: unknown) => void}
*/
this.onchat = null;
/**
......@@ -199,7 +205,7 @@ function ServerConnection() {
* 'id' is non-null, 'privileged' indicates whether the message was
* sent by an operator.
*
* @type {(this: ServerConnection, id: string, dest: string, username: string, time: number, privileged: boolean, kind: string, message: unknown) => void}
* @type {(this: ServerConnection, id: string, dest: string, username: string, time: Date, privileged: boolean, kind: string, message: unknown) => void}
*/
this.onusermessage = null;
/**
......@@ -225,6 +231,7 @@ function ServerConnection() {
* @property {string} type
* @property {Array<string>} [version]
* @property {string} [kind]
* @property {string} [error]
* @property {string} [id]
* @property {string} [replace]
* @property {string} [source]
......@@ -239,7 +246,7 @@ function ServerConnection() {
* @property {string} [group]
* @property {unknown} [value]
* @property {boolean} [noecho]
* @property {number} [time]
* @property {string|number} [time]
* @property {string} [sdp]
* @property {RTCIceCandidate} [candidate]
* @property {string} [label]
......@@ -291,7 +298,7 @@ ServerConnection.prototype.connect = async function(url) {
this.socket.onopen = function(e) {
sc.send({
type: 'handshake',
version: ["1"],
version: ["2", "1"],
id: sc.id,
});
if(sc.onconnected)
......@@ -324,10 +331,23 @@ ServerConnection.prototype.connect = async function(url) {
this.socket.onmessage = function(e) {
let m = JSON.parse(e.data);
switch(m.type) {
case 'handshake':
if(!m.version || !m.version.includes('1'))
console.warn(`Unexpected protocol version ${m.version}.`);
case 'handshake': {
/** @type {string} */
let v;
if(!m.version || !(m.version instanceof Array) ||
m.version.length < 1 || typeof(m.version[0]) !== 'string') {
v = null;
} else {
v = m.version[0];
}
if(v === "1" || v === "2") {
sc.version = v;
} else {
console.warn(`Unknown protocol version ${v || m.version}`);
sc.version = "1"
}
break;
}
case 'offer':
sc.gotOffer(m.id, m.label, m.source, m.username,
m.sdp, m.replace);
......@@ -419,8 +439,8 @@ ServerConnection.prototype.connect = async function(url) {
case 'chathistory':
if(sc.onchat)
sc.onchat.call(
sc, m.source, m.dest, m.username, m.time, m.privileged,
m.type === 'chathistory', m.kind, m.value,
sc, m.source, m.dest, m.username, parseTime(m.time),
m.privileged, m.type === 'chathistory', m.kind, m.value,
);
break;
case 'usermessage':
......@@ -428,7 +448,7 @@ ServerConnection.prototype.connect = async function(url) {
sc.fileTransfer(m.source, m.username, m.value);
else if(sc.onusermessage)
sc.onusermessage.call(
sc, m.source, m.dest, m.username, m.time,
sc, m.source, m.dest, m.username, parseTime(m.time),
m.privileged, m.kind, m.value,
);
break;
......@@ -448,6 +468,25 @@ ServerConnection.prototype.connect = async function(url) {
});
};
/**
* Protocol version 1 uses integers for dates, later versions use dates in
* ISO 8601 format. This function takes a date in either format and
* returns a Date object.
*
* @param {string|number} value
* @returns {Date}
*/
function parseTime(value) {
if(!value)
return null;
try {
return new Date(value);
} catch(e) {
console.warn(`Couldn't parse ${value}:`, e);
return null;
}
}
/**
* join requests to join a group. The onjoined callback will be called
* when we've effectively joined.
......
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