mirror of
https://github.com/jech/galene.git
synced 2024-11-22 08:35:57 +01:00
Client-side support for protocol version 2.
This does not yet support the new 'need-username' error.
This commit is contained in:
parent
397892d906
commit
fae045fb61
3 changed files with 78 additions and 29 deletions
|
@ -70,6 +70,14 @@ message types:
|
||||||
- `dest`, the client-id of the destination client;
|
- `dest`, the client-id of the destination client;
|
||||||
- `privileged`, set by the server to indicate that the originating client
|
- `privileged`, set by the server to indicate that the originating client
|
||||||
had the `op` privilege at the time when it sent the message.
|
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
|
## Establishing and maintaining a connection
|
||||||
|
|
||||||
|
@ -81,15 +89,15 @@ start pipelining messages to the server.
|
||||||
```javascript
|
```javascript
|
||||||
{
|
{
|
||||||
type: 'handshake',
|
type: 'handshake',
|
||||||
version: ["1"],
|
version: ["2"],
|
||||||
id: id
|
id: id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The version field contains an array of supported protocol versions; the
|
The version field contains an array of supported protocol versions, in
|
||||||
client may announce multiple versions, but the server will always reply
|
decreasing preference order; the client may announce multiple versions,
|
||||||
with a singleton. If the field `id` is absent, then the peer doesn't
|
but the server will always reply with a singleton. If the field `id` is
|
||||||
originate streams.
|
absent, then the peer doesn't originate streams.
|
||||||
|
|
||||||
A peer may, at any time, send a `ping` message.
|
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,
|
username: username,
|
||||||
dest: dest-id,
|
dest: dest-id,
|
||||||
privileged: boolean,
|
privileged: boolean,
|
||||||
|
time: time,
|
||||||
noecho: false,
|
noecho: false,
|
||||||
value: message
|
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`
|
interpretation, the server only validates that the `source` and `username`
|
||||||
fields are authentic. The field `privileged` is set to true by the server
|
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
|
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
|
field `time` is the timestamp of the message, coded as a number in version
|
||||||
of its own message.
|
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
|
The `chathistory` message is similar to the `chat` message, but carries
|
||||||
a message taken from the chat history. Most clients should treat
|
a message taken from the chat history. Most clients should treat
|
||||||
|
|
|
@ -2520,7 +2520,7 @@ function gotFileTransferEvent(state, data) {
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* @param {string} dest
|
* @param {string} dest
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {number} time
|
* @param {Date} time
|
||||||
* @param {boolean} privileged
|
* @param {boolean} privileged
|
||||||
* @param {string} kind
|
* @param {string} kind
|
||||||
* @param {any} message
|
* @param {any} message
|
||||||
|
@ -2605,16 +2605,15 @@ function formatLines(lines) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} time
|
* @param {Date} time
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function formatTime(time) {
|
function formatTime(time) {
|
||||||
let delta = Date.now() - time;
|
let delta = Date.now() - time.getTime();
|
||||||
let date = new Date(time);
|
let m = time.getMinutes();
|
||||||
let m = date.getMinutes();
|
|
||||||
if(delta > -30000)
|
if(delta > -30000)
|
||||||
return date.getHours() + ':' + ((m < 10) ? '0' : '') + m;
|
return time.getHours() + ':' + ((m < 10) ? '0' : '') + m;
|
||||||
return date.toLocaleString();
|
return time.toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2622,7 +2621,7 @@ function formatTime(time) {
|
||||||
* @property {string} [nick]
|
* @property {string} [nick]
|
||||||
* @property {string} [peerId]
|
* @property {string} [peerId]
|
||||||
* @property {string} [dest]
|
* @property {string} [dest]
|
||||||
* @property {number} [time]
|
* @property {Date} [time]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @type {lastMessage} */
|
/** @type {lastMessage} */
|
||||||
|
@ -2632,7 +2631,7 @@ let lastMessage = {};
|
||||||
* @param {string} peerId
|
* @param {string} peerId
|
||||||
* @param {string} dest
|
* @param {string} dest
|
||||||
* @param {string} nick
|
* @param {string} nick
|
||||||
* @param {number} time
|
* @param {Date} time
|
||||||
* @param {boolean} privileged
|
* @param {boolean} privileged
|
||||||
* @param {boolean} history
|
* @param {boolean} history
|
||||||
* @param {string} kind
|
* @param {string} kind
|
||||||
|
@ -2662,7 +2661,7 @@ function addToChatbox(peerId, dest, nick, time, privileged, history, kind, messa
|
||||||
!time || !lastMessage.time) {
|
!time || !lastMessage.time) {
|
||||||
doHeader = true;
|
doHeader = true;
|
||||||
} else {
|
} else {
|
||||||
let delta = time - lastMessage.time;
|
let delta = time.getTime() - lastMessage.time.getTime();
|
||||||
doHeader = delta < 0 || delta > 60000;
|
doHeader = delta < 0 || delta > 60000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2725,7 +2724,7 @@ function addToChatbox(peerId, dest, nick, time, privileged, history, kind, messa
|
||||||
* @param {string} message
|
* @param {string} message
|
||||||
*/
|
*/
|
||||||
function localMessage(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() {
|
function clearChat() {
|
||||||
|
@ -2960,7 +2959,7 @@ commands.msg = {
|
||||||
throw new Error(`Unknown user ${p[0]}`);
|
throw new Error(`Unknown user ${p[0]}`);
|
||||||
serverConnection.chat('', id, p[1]);
|
serverConnection.chat('', id, p[1]);
|
||||||
addToChatbox(serverConnection.id, id, serverConnection.username,
|
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}
|
* @type {WebSocket}
|
||||||
*/
|
*/
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
|
/**
|
||||||
|
* The negotiated protocol version.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.version = null;
|
||||||
/**
|
/**
|
||||||
* The set of all up streams, indexed by their id.
|
* 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.
|
* 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;
|
this.onchat = null;
|
||||||
/**
|
/**
|
||||||
|
@ -199,7 +205,7 @@ function ServerConnection() {
|
||||||
* 'id' is non-null, 'privileged' indicates whether the message was
|
* 'id' is non-null, 'privileged' indicates whether the message was
|
||||||
* sent by an operator.
|
* 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;
|
this.onusermessage = null;
|
||||||
/**
|
/**
|
||||||
|
@ -225,6 +231,7 @@ function ServerConnection() {
|
||||||
* @property {string} type
|
* @property {string} type
|
||||||
* @property {Array<string>} [version]
|
* @property {Array<string>} [version]
|
||||||
* @property {string} [kind]
|
* @property {string} [kind]
|
||||||
|
* @property {string} [error]
|
||||||
* @property {string} [id]
|
* @property {string} [id]
|
||||||
* @property {string} [replace]
|
* @property {string} [replace]
|
||||||
* @property {string} [source]
|
* @property {string} [source]
|
||||||
|
@ -239,7 +246,7 @@ function ServerConnection() {
|
||||||
* @property {string} [group]
|
* @property {string} [group]
|
||||||
* @property {unknown} [value]
|
* @property {unknown} [value]
|
||||||
* @property {boolean} [noecho]
|
* @property {boolean} [noecho]
|
||||||
* @property {number} [time]
|
* @property {string|number} [time]
|
||||||
* @property {string} [sdp]
|
* @property {string} [sdp]
|
||||||
* @property {RTCIceCandidate} [candidate]
|
* @property {RTCIceCandidate} [candidate]
|
||||||
* @property {string} [label]
|
* @property {string} [label]
|
||||||
|
@ -291,7 +298,7 @@ ServerConnection.prototype.connect = async function(url) {
|
||||||
this.socket.onopen = function(e) {
|
this.socket.onopen = function(e) {
|
||||||
sc.send({
|
sc.send({
|
||||||
type: 'handshake',
|
type: 'handshake',
|
||||||
version: ["1"],
|
version: ["2", "1"],
|
||||||
id: sc.id,
|
id: sc.id,
|
||||||
});
|
});
|
||||||
if(sc.onconnected)
|
if(sc.onconnected)
|
||||||
|
@ -324,10 +331,23 @@ ServerConnection.prototype.connect = async function(url) {
|
||||||
this.socket.onmessage = function(e) {
|
this.socket.onmessage = function(e) {
|
||||||
let m = JSON.parse(e.data);
|
let m = JSON.parse(e.data);
|
||||||
switch(m.type) {
|
switch(m.type) {
|
||||||
case 'handshake':
|
case 'handshake': {
|
||||||
if(!m.version || !m.version.includes('1'))
|
/** @type {string} */
|
||||||
console.warn(`Unexpected protocol version ${m.version}.`);
|
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;
|
break;
|
||||||
|
}
|
||||||
case 'offer':
|
case 'offer':
|
||||||
sc.gotOffer(m.id, m.label, m.source, m.username,
|
sc.gotOffer(m.id, m.label, m.source, m.username,
|
||||||
m.sdp, m.replace);
|
m.sdp, m.replace);
|
||||||
|
@ -419,8 +439,8 @@ ServerConnection.prototype.connect = async function(url) {
|
||||||
case 'chathistory':
|
case 'chathistory':
|
||||||
if(sc.onchat)
|
if(sc.onchat)
|
||||||
sc.onchat.call(
|
sc.onchat.call(
|
||||||
sc, m.source, m.dest, m.username, m.time, m.privileged,
|
sc, m.source, m.dest, m.username, parseTime(m.time),
|
||||||
m.type === 'chathistory', m.kind, m.value,
|
m.privileged, m.type === 'chathistory', m.kind, m.value,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'usermessage':
|
case 'usermessage':
|
||||||
|
@ -428,7 +448,7 @@ ServerConnection.prototype.connect = async function(url) {
|
||||||
sc.fileTransfer(m.source, m.username, m.value);
|
sc.fileTransfer(m.source, m.username, m.value);
|
||||||
else if(sc.onusermessage)
|
else if(sc.onusermessage)
|
||||||
sc.onusermessage.call(
|
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,
|
m.privileged, m.kind, m.value,
|
||||||
);
|
);
|
||||||
break;
|
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
|
* join requests to join a group. The onjoined callback will be called
|
||||||
* when we've effectively joined.
|
* when we've effectively joined.
|
||||||
|
|
Loading…
Reference in a new issue