mirror of
https://github.com/jech/galene.git
synced 2024-11-23 00:55:58 +01:00
Improve typing of javascript code.
We now enable typing of sfu.js.
This commit is contained in:
parent
4e14c29fbb
commit
0aa77441bd
3 changed files with 266 additions and 118 deletions
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* toHex formats an array as a hexadecimal string.
|
* toHex formats an array as a hexadecimal string.
|
||||||
* @returns {string}
|
* @param {number[]|Uint8Array} array - the array to format
|
||||||
|
* @returns {string} - the hexadecimal representation of array
|
||||||
*/
|
*/
|
||||||
function toHex(array) {
|
function toHex(array) {
|
||||||
let a = new Uint8Array(array);
|
let a = new Uint8Array(array);
|
||||||
|
@ -37,43 +38,52 @@ function randomid() {
|
||||||
function ServerConnection() {
|
function ServerConnection() {
|
||||||
/**
|
/**
|
||||||
* The id of this connection.
|
* The id of this connection.
|
||||||
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
|
* @const
|
||||||
*/
|
*/
|
||||||
this.id = randomid();
|
this.id = randomid();
|
||||||
/**
|
/**
|
||||||
* The group that we have joined, or nil if we haven't joined yet.
|
* The group that we have joined, or nil if we haven't joined yet.
|
||||||
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.group = null;
|
this.group = null;
|
||||||
/**
|
/**
|
||||||
* The underlying websocket.
|
* The underlying websocket.
|
||||||
|
*
|
||||||
* @type {WebSocket}
|
* @type {WebSocket}
|
||||||
*/
|
*/
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
/**
|
/**
|
||||||
* The set of all up streams, indexed by their id.
|
* The set of all up streams, indexed by their id.
|
||||||
* @type {Object.<string,Stream>}
|
*
|
||||||
|
* @type {Object<string,Stream>}
|
||||||
*/
|
*/
|
||||||
this.up = {};
|
this.up = {};
|
||||||
/**
|
/**
|
||||||
* The set of all down streams, indexed by their id.
|
* The set of all down streams, indexed by their id.
|
||||||
* @type {Object.<string,Stream>}
|
*
|
||||||
|
* @type {Object<string,Stream>}
|
||||||
*/
|
*/
|
||||||
this.down = {};
|
this.down = {};
|
||||||
/**
|
/**
|
||||||
* The ICE configuration used by all associated streams.
|
* The ICE configuration used by all associated streams.
|
||||||
* @type {Array.<RTCIceServer>}
|
*
|
||||||
|
* @type {RTCIceServer[]}
|
||||||
*/
|
*/
|
||||||
this.iceServers = null;
|
this.iceServers = null;
|
||||||
/**
|
/**
|
||||||
* The permissions granted to this connection.
|
* The permissions granted to this connection.
|
||||||
* @type {Object.<string,boolean>}
|
*
|
||||||
|
* @type {Object<string,boolean>}
|
||||||
*/
|
*/
|
||||||
this.permissions = {};
|
this.permissions = {};
|
||||||
/**
|
/**
|
||||||
* clientdata is a convenient place to attach data to a ServerConnection.
|
* clientdata is a convenient place to attach data to a ServerConnection.
|
||||||
* It is not used by the library.
|
* It is not used by the library.
|
||||||
* @type{Object.<string,any>}
|
*
|
||||||
|
* @type{Object<any,any>}
|
||||||
*/
|
*/
|
||||||
this.userdata = {};
|
this.userdata = {};
|
||||||
|
|
||||||
|
@ -81,46 +91,54 @@ function ServerConnection() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* onconnected is called when the connection has been established
|
* onconnected is called when the connection has been established
|
||||||
* @type{function(): any}
|
*
|
||||||
|
* @type{(this: ServerConnection) => any}
|
||||||
*/
|
*/
|
||||||
this.onconnected = null;
|
this.onconnected = null;
|
||||||
/**
|
/**
|
||||||
* onclose is called when the connection is closed
|
* onclose is called when the connection is closed
|
||||||
* @type{function(number, string): any}
|
*
|
||||||
|
* @type{(this: ServerConnection, code: number, reason: string) => any}
|
||||||
*/
|
*/
|
||||||
this.onclose = null;
|
this.onclose = null;
|
||||||
/**
|
/**
|
||||||
* onuser is called whenever a user is added or removed from the group
|
* onuser is called whenever a user is added or removed from the group
|
||||||
* @type{function(string, string, string): any}
|
*
|
||||||
|
* @type{(this: ServerConnection, id: string, kind: string, username: string) => any}
|
||||||
*/
|
*/
|
||||||
this.onuser = null;
|
this.onuser = null;
|
||||||
/**
|
/**
|
||||||
* onpermissions is called whenever the current user's permissions change
|
* onpermissions is called whenever the current user's permissions change
|
||||||
* @type{function(Object.<string,boolean>): any}
|
*
|
||||||
|
* @type{(this: ServerConnection, permissions: Object<string,boolean>) => any}
|
||||||
*/
|
*/
|
||||||
this.onpermissions = null;
|
this.onpermissions = null;
|
||||||
/**
|
/**
|
||||||
* ondownstream is called whenever a new down stream is added. It
|
* ondownstream is called whenever a new down stream is added. It
|
||||||
* should set up the stream's callbacks; actually setting up the UI
|
* should set up the stream's callbacks; actually setting up the UI
|
||||||
* should be done in the stream's ondowntrack callback.
|
* should be done in the stream's ondowntrack callback.
|
||||||
* @type{function(Stream): any}
|
*
|
||||||
|
* @type{(this: ServerConnection, stream: Stream) => any}
|
||||||
*/
|
*/
|
||||||
this.ondownstream = null;
|
this.ondownstream = null;
|
||||||
/**
|
/**
|
||||||
* onchat is called whenever a new chat message is received.
|
* onchat is called whenever a new chat message is received.
|
||||||
* @type {function(string, string, string, string): any}
|
*
|
||||||
|
* @type {(this: ServerConnection, id: string, username: string, kind: string, message: string) => any}
|
||||||
*/
|
*/
|
||||||
this.onchat = null;
|
this.onchat = null;
|
||||||
/**
|
/**
|
||||||
* onclearchat is called whenever the server requests that the chat
|
* onclearchat is called whenever the server requests that the chat
|
||||||
* be cleared.
|
* be cleared.
|
||||||
* @type{function(): any}
|
*
|
||||||
|
* @type{(this: ServerConnection) => any}
|
||||||
*/
|
*/
|
||||||
this.onclearchat = null;
|
this.onclearchat = null;
|
||||||
/**
|
/**
|
||||||
* onusermessage is called when the server sends an error or warning
|
* onusermessage is called when the server sends an error or warning
|
||||||
* message that should be displayed to the user.
|
* message that should be displayed to the user.
|
||||||
* @type{function(string, string): any}
|
*
|
||||||
|
* @type{(this: ServerConnection, kind: string, message: string) => any}
|
||||||
*/
|
*/
|
||||||
this.onusermessage = null;
|
this.onusermessage = null;
|
||||||
}
|
}
|
||||||
|
@ -132,14 +150,14 @@ function ServerConnection() {
|
||||||
* @property {string} [id]
|
* @property {string} [id]
|
||||||
* @property {string} [username]
|
* @property {string} [username]
|
||||||
* @property {string} [password]
|
* @property {string} [password]
|
||||||
* @property {Object.<string,boolean>} [permissions]
|
* @property {Object<string,boolean>} [permissions]
|
||||||
* @property {string} [group]
|
* @property {string} [group]
|
||||||
* @property {string} [value]
|
* @property {string} [value]
|
||||||
* @property {RTCSessionDescriptionInit} [offer]
|
* @property {RTCSessionDescriptionInit} [offer]
|
||||||
* @property {RTCSessionDescriptionInit} [answer]
|
* @property {RTCSessionDescriptionInit} [answer]
|
||||||
* @property {RTCIceCandidate} [candidate]
|
* @property {RTCIceCandidate} [candidate]
|
||||||
* @property {Object.<string,string>} [labels]
|
* @property {Object<string,string>} [labels]
|
||||||
* @property {Object.<string,(boolean|number)>} [request]
|
* @property {Object<string,(boolean|number)>} [request]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -157,17 +175,18 @@ ServerConnection.prototype.close = function() {
|
||||||
*/
|
*/
|
||||||
ServerConnection.prototype.send = function(m) {
|
ServerConnection.prototype.send = function(m) {
|
||||||
if(!this.socket || this.socket.readyState !== this.socket.OPEN) {
|
if(!this.socket || this.socket.readyState !== this.socket.OPEN) {
|
||||||
// send on a closed connection doesn't throw
|
// send on a closed socket doesn't throw
|
||||||
throw(new Error('Connection is not open'));
|
throw(new Error('Connection is not open'));
|
||||||
}
|
}
|
||||||
return this.socket.send(JSON.stringify(m));
|
return this.socket.send(JSON.stringify(m));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** getIceServers fetches an ICE configuration from the server and
|
/**
|
||||||
|
* getIceServers fetches an ICE configuration from the server and
|
||||||
* populates the iceServers field of a ServerConnection. It is called
|
* populates the iceServers field of a ServerConnection. It is called
|
||||||
* lazily by connect.
|
* lazily by connect.
|
||||||
*
|
*
|
||||||
* @returns {Promise<Array.<Object>>}
|
* @returns {Promise<RTCIceServer[]>}
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
ServerConnection.prototype.getIceServers = async function() {
|
ServerConnection.prototype.getIceServers = async function() {
|
||||||
|
@ -294,8 +313,8 @@ ServerConnection.prototype.connect = async function(url) {
|
||||||
/**
|
/**
|
||||||
* login authenticates with the server.
|
* login authenticates with the server.
|
||||||
*
|
*
|
||||||
* @param {string} username
|
* @param {string} username - the username to login as.
|
||||||
* @param {string} password
|
* @param {string} password - the password.
|
||||||
*/
|
*/
|
||||||
ServerConnection.prototype.login = function(username, password) {
|
ServerConnection.prototype.login = function(username, password) {
|
||||||
this.send({
|
this.send({
|
||||||
|
@ -324,7 +343,7 @@ ServerConnection.prototype.join = function(group) {
|
||||||
* @param {string} what - One of '', 'audio', 'screenshare' or 'everything'.
|
* @param {string} what - One of '', 'audio', 'screenshare' or 'everything'.
|
||||||
*/
|
*/
|
||||||
ServerConnection.prototype.request = function(what) {
|
ServerConnection.prototype.request = function(what) {
|
||||||
/** @type {Object.<string,boolean>} */
|
/** @type {Object<string,boolean>} */
|
||||||
let request = {};
|
let request = {};
|
||||||
switch(what) {
|
switch(what) {
|
||||||
case '':
|
case '':
|
||||||
|
@ -422,7 +441,7 @@ ServerConnection.prototype.chat = function(username, kind, message) {
|
||||||
*
|
*
|
||||||
* @param {string} kind - One of "clearchat", "lock", "unlock", "record or
|
* @param {string} kind - One of "clearchat", "lock", "unlock", "record or
|
||||||
* "unrecord".
|
* "unrecord".
|
||||||
* @param {string} [message]
|
* @param {string} [message] - An optional user-readable message.
|
||||||
*/
|
*/
|
||||||
ServerConnection.prototype.groupAction = function(kind, message) {
|
ServerConnection.prototype.groupAction = function(kind, message) {
|
||||||
this.send({
|
this.send({
|
||||||
|
@ -436,8 +455,8 @@ ServerConnection.prototype.groupAction = function(kind, message) {
|
||||||
* userAction sends a request to act on a user.
|
* userAction sends a request to act on a user.
|
||||||
*
|
*
|
||||||
* @param {string} kind - One of "op", "unop", "kick", "present", "unpresent".
|
* @param {string} kind - One of "op", "unop", "kick", "present", "unpresent".
|
||||||
* @param {string} id
|
* @param {string} id - The id of the user to act upon.
|
||||||
* @param {string} [message]
|
* @param {string} [message] - An optional user-readable message.
|
||||||
*/
|
*/
|
||||||
ServerConnection.prototype.userAction = function(kind, id, message) {
|
ServerConnection.prototype.userAction = function(kind, id, message) {
|
||||||
this.send({
|
this.send({
|
||||||
|
@ -452,7 +471,7 @@ ServerConnection.prototype.userAction = function(kind, id, message) {
|
||||||
* Called when we receive an offer from the server. Don't call this.
|
* Called when we receive an offer from the server. Don't call this.
|
||||||
*
|
*
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* @param {Object.<string, string>} labels
|
* @param {Object<string, string>} labels
|
||||||
* @param {RTCSessionDescriptionInit} offer
|
* @param {RTCSessionDescriptionInit} offer
|
||||||
* @param {boolean} renegotiate
|
* @param {boolean} renegotiate
|
||||||
* @function
|
* @function
|
||||||
|
@ -656,13 +675,15 @@ function Stream(sc, id, pc) {
|
||||||
* The associated ServerConnection.
|
* The associated ServerConnection.
|
||||||
*
|
*
|
||||||
* @type {ServerConnection}
|
* @type {ServerConnection}
|
||||||
*/
|
* @const
|
||||||
|
*/
|
||||||
this.sc = sc;
|
this.sc = sc;
|
||||||
/**
|
/**
|
||||||
* The id of this stream.
|
* The id of this stream.
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
* @const
|
||||||
|
*/
|
||||||
this.id = id;
|
this.id = id;
|
||||||
/**
|
/**
|
||||||
* For up streams, one of "local" or "screenshare".
|
* For up streams, one of "local" or "screenshare".
|
||||||
|
@ -693,20 +714,20 @@ function Stream(sc, id, pc) {
|
||||||
/**
|
/**
|
||||||
* Track labels, indexed by track id.
|
* Track labels, indexed by track id.
|
||||||
*
|
*
|
||||||
* @type {Object.<string,string>}
|
* @type {Object<string,string>}
|
||||||
*/
|
*/
|
||||||
this.labels = {};
|
this.labels = {};
|
||||||
/**
|
/**
|
||||||
* Track labels, indexed by mid.
|
* Track labels, indexed by mid.
|
||||||
*
|
*
|
||||||
* @type {Object.<string,string>}
|
* @type {Object<string,string>}
|
||||||
*/
|
*/
|
||||||
this.labelsByMid = {};
|
this.labelsByMid = {};
|
||||||
/**
|
/**
|
||||||
* Buffered ICE candidates. This will be flushed by flushIceCandidates
|
* Buffered ICE candidates. This will be flushed by flushIceCandidates
|
||||||
* when the PC becomes stable.
|
* when the PC becomes stable.
|
||||||
*
|
*
|
||||||
* @type {Array.<RTCIceCandidate>}
|
* @type {RTCIceCandidate[]}
|
||||||
*/
|
*/
|
||||||
this.iceCandidates = [];
|
this.iceCandidates = [];
|
||||||
/**
|
/**
|
||||||
|
@ -721,7 +742,7 @@ function Stream(sc, id, pc) {
|
||||||
* a dictionary indexed by track id, with each value a disctionary of
|
* a dictionary indexed by track id, with each value a disctionary of
|
||||||
* statistics.
|
* statistics.
|
||||||
*
|
*
|
||||||
* @type {Object.<string,any>}
|
* @type {Object<string,any>}
|
||||||
*/
|
*/
|
||||||
this.stats = {};
|
this.stats = {};
|
||||||
/**
|
/**
|
||||||
|
@ -734,7 +755,7 @@ function Stream(sc, id, pc) {
|
||||||
/**
|
/**
|
||||||
* clientdata is a convenient place to attach data to a Stream.
|
* clientdata is a convenient place to attach data to a Stream.
|
||||||
* It is not used by the library.
|
* It is not used by the library.
|
||||||
* @type{Object.<string,any>}
|
* @type{Object<any,any>}
|
||||||
*/
|
*/
|
||||||
this.userdata = {};
|
this.userdata = {};
|
||||||
|
|
||||||
|
@ -743,21 +764,21 @@ function Stream(sc, id, pc) {
|
||||||
/**
|
/**
|
||||||
* onclose is called when the stream is closed.
|
* onclose is called when the stream is closed.
|
||||||
*
|
*
|
||||||
* @type{function(): any}
|
* @type{(this: Stream) => any}
|
||||||
*/
|
*/
|
||||||
this.onclose = null;
|
this.onclose = null;
|
||||||
/**
|
/**
|
||||||
* onerror is called whenever an error occurs. If the error is
|
* onerror is called whenever an error occurs. If the error is
|
||||||
* fatal, then onclose will be called afterwards.
|
* fatal, then onclose will be called afterwards.
|
||||||
*
|
*
|
||||||
* @type{function(any): any}
|
* @type{(this: Stream, error: any) => any}
|
||||||
*/
|
*/
|
||||||
this.onerror = null;
|
this.onerror = null;
|
||||||
/**
|
/**
|
||||||
* onnegotiationcompleted is called whenever negotiation or
|
* onnegotiationcompleted is called whenever negotiation or
|
||||||
* renegotiation has completed.
|
* renegotiation has completed.
|
||||||
*
|
*
|
||||||
* @type{function(): any}
|
* @type{(this: Stream) => any}
|
||||||
*/
|
*/
|
||||||
this.onnegotiationcompleted = null;
|
this.onnegotiationcompleted = null;
|
||||||
/**
|
/**
|
||||||
|
@ -765,32 +786,32 @@ function Stream(sc, id, pc) {
|
||||||
* If the stream parameter differs from its previous value, then it
|
* If the stream parameter differs from its previous value, then it
|
||||||
* indicates that the old stream has been discarded.
|
* indicates that the old stream has been discarded.
|
||||||
*
|
*
|
||||||
* @type{function(MediaStreamTrack, RTCRtpTransceiver, string, MediaStream): any}
|
* @type{(this: Stream, track: MediaStreamTrack, transceiver: RTCRtpTransceiver, label: string, stream: MediaStream) => any}
|
||||||
*/
|
*/
|
||||||
this.ondowntrack = null;
|
this.ondowntrack = null;
|
||||||
/**
|
/**
|
||||||
* onlabel is called whenever the server sets a new label for the stream.
|
* onlabel is called whenever the server sets a new label for the stream.
|
||||||
*
|
*
|
||||||
* @type{function(string): any}
|
* @type{(this: Stream, label: string) => any}
|
||||||
*/
|
*/
|
||||||
this.onlabel = null;
|
this.onlabel = null;
|
||||||
/**
|
/**
|
||||||
* onstatus is called whenever the status of the stream changes.
|
* onstatus is called whenever the status of the stream changes.
|
||||||
*
|
*
|
||||||
* @type{function(string): any}
|
* @type{(this: Stream, status: string) => any}
|
||||||
*/
|
*/
|
||||||
this.onstatus = null;
|
this.onstatus = null;
|
||||||
/**
|
/**
|
||||||
* onabort is called when the server requested that an up stream be
|
* onabort is called when the server requested that an up stream be
|
||||||
* closed. It is the resposibility of the client to close the stream.
|
* closed. It is the resposibility of the client to close the stream.
|
||||||
*
|
*
|
||||||
* @type{function(): any}
|
* @type{(this: Stream) => any}
|
||||||
*/
|
*/
|
||||||
this.onabort = null;
|
this.onabort = null;
|
||||||
/**
|
/**
|
||||||
* onstats is called when we have new statistics about the connection
|
* onstats is called when we have new statistics about the connection
|
||||||
*
|
*
|
||||||
* @type{function(Object.<string,any>): any}
|
* @type{(this: Stream, stats: Object<any,any>) => any}
|
||||||
*/
|
*/
|
||||||
this.onstats = null;
|
this.onstats = null;
|
||||||
}
|
}
|
||||||
|
@ -834,6 +855,7 @@ Stream.prototype.close = function(sendclose) {
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
Stream.prototype.flushIceCandidates = async function () {
|
Stream.prototype.flushIceCandidates = async function () {
|
||||||
|
/** @type {Promise<any>[]} */
|
||||||
let promises = [];
|
let promises = [];
|
||||||
this.iceCandidates.forEach(c => {
|
this.iceCandidates.forEach(c => {
|
||||||
promises.push(this.pc.addIceCandidate(c).catch(console.warn));
|
promises.push(this.pc.addIceCandidate(c).catch(console.warn));
|
||||||
|
@ -845,16 +867,16 @@ Stream.prototype.flushIceCandidates = async function () {
|
||||||
/**
|
/**
|
||||||
* negotiate negotiates or renegotiates an up stream. It is called
|
* negotiate negotiates or renegotiates an up stream. It is called
|
||||||
* automatically when required. If the client requires renegotiation, it
|
* automatically when required. If the client requires renegotiation, it
|
||||||
* is probably more effective to call restartIce on the underlying PC
|
* is probably better to call restartIce which will cause negotiate to be
|
||||||
* rather than invoking this function directly.
|
* called asynchronously.
|
||||||
*
|
*
|
||||||
* @function
|
* @function
|
||||||
* @param {boolean} [restartIce]
|
* @param {boolean} [restartIce] - Whether to restart ICE.
|
||||||
*/
|
*/
|
||||||
Stream.prototype.negotiate = async function (restartIce) {
|
Stream.prototype.negotiate = async function (restartIce) {
|
||||||
let c = this;
|
let c = this;
|
||||||
|
|
||||||
let options = null;
|
let options = {};
|
||||||
if(restartIce)
|
if(restartIce)
|
||||||
options = {iceRestart: true};
|
options = {iceRestart: true};
|
||||||
let offer = await c.pc.createOffer(options);
|
let offer = await c.pc.createOffer(options);
|
||||||
|
@ -892,8 +914,7 @@ Stream.prototype.negotiate = async function (restartIce) {
|
||||||
Stream.prototype.restartIce = function () {
|
Stream.prototype.restartIce = function () {
|
||||||
let c = this;
|
let c = this;
|
||||||
|
|
||||||
/** @ts-ignore */
|
if('restartIce' in c.pc) {
|
||||||
if(typeof c.pc.restartIce === 'function') {
|
|
||||||
try {
|
try {
|
||||||
/** @ts-ignore */
|
/** @ts-ignore */
|
||||||
c.pc.restartIce();
|
c.pc.restartIce();
|
||||||
|
@ -988,7 +1009,7 @@ Stream.prototype.updateStats = async function() {
|
||||||
* setStatsInterval sets the interval in milliseconds at which the onstats
|
* setStatsInterval sets the interval in milliseconds at which the onstats
|
||||||
* handler will be called. This is only useful for up streams.
|
* handler will be called. This is only useful for up streams.
|
||||||
*
|
*
|
||||||
* @param {number} ms
|
* @param {number} ms - The interval in milliseconds.
|
||||||
*/
|
*/
|
||||||
Stream.prototype.setStatsInterval = function(ms) {
|
Stream.prototype.setStatsInterval = function(ms) {
|
||||||
let c = this;
|
let c = this;
|
||||||
|
|
255
static/sfu.js
255
static/sfu.js
|
@ -103,9 +103,8 @@ function storeSettings(settings) {
|
||||||
*
|
*
|
||||||
* @returns {settings}
|
* @returns {settings}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getSettings() {
|
function getSettings() {
|
||||||
/** @type{settings} */
|
/** @type {settings} */
|
||||||
let settings;
|
let settings;
|
||||||
try {
|
try {
|
||||||
let json = window.sessionStorage.getItem('settings');
|
let json = window.sessionStorage.getItem('settings');
|
||||||
|
@ -127,22 +126,50 @@ function updateSettings(settings) {
|
||||||
storeSettings(s);
|
storeSettings(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
function getSelectElement(id) {
|
||||||
|
let elt = document.getElementById(id);
|
||||||
|
if(!elt || !(elt instanceof HTMLSelectElement))
|
||||||
|
throw new Error(`Couldn't find ${id}`);
|
||||||
|
return elt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
function getInputElement(id) {
|
||||||
|
let elt = document.getElementById(id);
|
||||||
|
if(!elt || !(elt instanceof HTMLInputElement))
|
||||||
|
throw new Error(`Couldn't find ${id}`);
|
||||||
|
return elt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
function getButtonElement(id) {
|
||||||
|
let elt = document.getElementById(id);
|
||||||
|
if(!elt || !(elt instanceof HTMLButtonElement))
|
||||||
|
throw new Error(`Couldn't find ${id}`);
|
||||||
|
return elt;
|
||||||
|
}
|
||||||
|
|
||||||
function reflectSettings() {
|
function reflectSettings() {
|
||||||
let settings = getSettings();
|
let settings = getSettings();
|
||||||
let store = false;
|
let store = false;
|
||||||
|
|
||||||
setLocalMute(settings.localMute);
|
setLocalMute(settings.localMute);
|
||||||
|
|
||||||
let videoselect =
|
let videoselect = getSelectElement('videoselect');
|
||||||
/** @type {HTMLSelectElement} */(document.getElementById('videoselect'));
|
|
||||||
if(!settings.video || !selectOptionAvailable(videoselect, settings.video)) {
|
if(!settings.video || !selectOptionAvailable(videoselect, settings.video)) {
|
||||||
settings.video = selectOptionDefault(videoselect);
|
settings.video = selectOptionDefault(videoselect);
|
||||||
store = true;
|
store = true;
|
||||||
}
|
}
|
||||||
videoselect.value = settings.video;
|
videoselect.value = settings.video;
|
||||||
|
|
||||||
let audioselect =
|
let audioselect = getSelectElement('audioselect');
|
||||||
/** @type {HTMLSelectElement} */(document.getElementById('audioselect'));
|
|
||||||
if(!settings.audio || !selectOptionAvailable(audioselect, settings.audio)) {
|
if(!settings.audio || !selectOptionAvailable(audioselect, settings.audio)) {
|
||||||
settings.audio = selectOptionDefault(audioselect);
|
settings.audio = selectOptionDefault(audioselect);
|
||||||
store = true;
|
store = true;
|
||||||
|
@ -150,24 +177,24 @@ function reflectSettings() {
|
||||||
audioselect.value = settings.audio;
|
audioselect.value = settings.audio;
|
||||||
|
|
||||||
if(settings.request)
|
if(settings.request)
|
||||||
document.getElementById('requestselect').value = settings.request;
|
getSelectElement('requestselect').value = settings.request;
|
||||||
else {
|
else {
|
||||||
settings.request = document.getElementById('requestselect').value;
|
settings.request = getSelectElement('requestselect').value;
|
||||||
store = true;
|
store = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(settings.send)
|
if(settings.send)
|
||||||
document.getElementById('sendselect').value = settings.send;
|
getSelectElement('sendselect').value = settings.send;
|
||||||
else {
|
else {
|
||||||
settings.send = document.getElementById('sendselect').value;
|
settings.send = getSelectElement('sendselect').value;
|
||||||
store = true;
|
store = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('activitybox').checked = settings.activityDetection;
|
getInputElement('activitybox').checked = settings.activityDetection;
|
||||||
|
|
||||||
document.getElementById('blackboardbox').checked = settings.blackboardMode;
|
getInputElement('blackboardbox').checked = settings.blackboardMode;
|
||||||
|
|
||||||
document.getElementById('studiobox').checked = settings.studioMode;
|
getInputElement('studiobox').checked = settings.studioMode;
|
||||||
|
|
||||||
if(store)
|
if(store)
|
||||||
storeSettings(settings);
|
storeSettings(settings);
|
||||||
|
@ -218,9 +245,9 @@ function setConnected(connected) {
|
||||||
} else {
|
} else {
|
||||||
resetUsers();
|
resetUsers();
|
||||||
let userpass = getUserPass();
|
let userpass = getUserPass();
|
||||||
document.getElementById('username').value =
|
getInputElement('username').value =
|
||||||
userpass ? userpass.username : '';
|
userpass ? userpass.username : '';
|
||||||
document.getElementById('password').value =
|
getInputElement('password').value =
|
||||||
userpass ? userpass.password : '';
|
userpass ? userpass.password : '';
|
||||||
statspan.textContent = 'Disconnected';
|
statspan.textContent = 'Disconnected';
|
||||||
statspan.classList.remove('connected');
|
statspan.classList.remove('connected');
|
||||||
|
@ -234,6 +261,7 @@ function setConnected(connected) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @this {ServerConnection} */
|
||||||
function gotConnected() {
|
function gotConnected() {
|
||||||
setConnected(true);
|
setConnected(true);
|
||||||
let up = getUserPass();
|
let up = getUserPass();
|
||||||
|
@ -243,6 +271,7 @@ function gotConnected() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @this {ServerConnection}
|
||||||
* @param {number} code
|
* @param {number} code
|
||||||
* @param {string} reason
|
* @param {string} reason
|
||||||
*/
|
*/
|
||||||
|
@ -255,6 +284,7 @@ function gotClose(code, reason) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @this {ServerConnection}
|
||||||
* @param {Stream} c
|
* @param {Stream} c
|
||||||
*/
|
*/
|
||||||
function gotDownStream(c) {
|
function gotDownStream(c) {
|
||||||
|
@ -291,12 +321,12 @@ setViewportHeight();
|
||||||
addEventListener('resize', setViewportHeight);
|
addEventListener('resize', setViewportHeight);
|
||||||
addEventListener('orientationchange', setViewportHeight);
|
addEventListener('orientationchange', setViewportHeight);
|
||||||
|
|
||||||
document.getElementById('presentbutton').onclick = function(e) {
|
getButtonElement('presentbutton').onclick = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
addLocalMedia();
|
addLocalMedia();
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('unpresentbutton').onclick = function(e) {
|
getButtonElement('unpresentbutton').onclick = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
delUpMediaKind('local');
|
delUpMediaKind('local');
|
||||||
resizePeers();
|
resizePeers();
|
||||||
|
@ -353,26 +383,34 @@ function setLocalMute(mute) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('videoselect').onchange = function(e) {
|
getSelectElement('videoselect').onchange = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if(!(this instanceof HTMLSelectElement))
|
||||||
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({video: this.value});
|
updateSettings({video: this.value});
|
||||||
changePresentation();
|
changePresentation();
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('audioselect').onchange = function(e) {
|
getSelectElement('audioselect').onchange = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if(!(this instanceof HTMLSelectElement))
|
||||||
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({audio: this.value});
|
updateSettings({audio: this.value});
|
||||||
changePresentation();
|
changePresentation();
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('blackboardbox').onchange = function(e) {
|
getInputElement('blackboardbox').onchange = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if(!(this instanceof HTMLInputElement))
|
||||||
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({blackboardMode: this.checked});
|
updateSettings({blackboardMode: this.checked});
|
||||||
changePresentation();
|
changePresentation();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('studiobox').onchange = function(e) {
|
getInputElement('studiobox').onchange = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if(!(this instanceof HTMLInputElement))
|
||||||
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({studioMode: this.checked});
|
updateSettings({studioMode: this.checked});
|
||||||
changePresentation();
|
changePresentation();
|
||||||
}
|
}
|
||||||
|
@ -413,7 +451,9 @@ function getMaxVideoThroughput() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('sendselect').onchange = async function(e) {
|
getSelectElement('sendselect').onchange = async function(e) {
|
||||||
|
if(!(this instanceof HTMLSelectElement))
|
||||||
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({send: this.value});
|
updateSettings({send: this.value});
|
||||||
let t = getMaxVideoThroughput();
|
let t = getMaxVideoThroughput();
|
||||||
for(let id in serverConnection.up) {
|
for(let id in serverConnection.up) {
|
||||||
|
@ -423,8 +463,10 @@ document.getElementById('sendselect').onchange = async function(e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('requestselect').onchange = function(e) {
|
getSelectElement('requestselect').onchange = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if(!(this instanceof HTMLSelectElement))
|
||||||
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({request: this.value});
|
updateSettings({request: this.value});
|
||||||
serverConnection.request(this.value);
|
serverConnection.request(this.value);
|
||||||
};
|
};
|
||||||
|
@ -433,7 +475,9 @@ const activityDetectionInterval = 200;
|
||||||
const activityDetectionPeriod = 700;
|
const activityDetectionPeriod = 700;
|
||||||
const activityDetectionThreshold = 0.2;
|
const activityDetectionThreshold = 0.2;
|
||||||
|
|
||||||
document.getElementById('activitybox').onchange = function(e) {
|
getInputElement('activitybox').onchange = function(e) {
|
||||||
|
if(!(this instanceof HTMLInputElement))
|
||||||
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({activityDetection: this.checked});
|
updateSettings({activityDetection: this.checked});
|
||||||
for(let id in serverConnection.down) {
|
for(let id in serverConnection.down) {
|
||||||
let c = serverConnection.down[id];
|
let c = serverConnection.down[id];
|
||||||
|
@ -446,6 +490,10 @@ document.getElementById('activitybox').onchange = function(e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this {Stream}
|
||||||
|
* @param {Object<string,any>} stats
|
||||||
|
*/
|
||||||
function gotUpStats(stats) {
|
function gotUpStats(stats) {
|
||||||
let c = this;
|
let c = this;
|
||||||
|
|
||||||
|
@ -477,8 +525,12 @@ function setActive(c, value) {
|
||||||
peer.classList.remove('peer-active');
|
peer.classList.remove('peer-active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this {Stream}
|
||||||
|
* @param {Object<string,any>} stats
|
||||||
|
*/
|
||||||
function gotDownStats(stats) {
|
function gotDownStats(stats) {
|
||||||
if(!document.getElementById('activitybox').checked)
|
if(!getInputElement('activitybox').checked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let c = this;
|
let c = this;
|
||||||
|
@ -514,7 +566,11 @@ function addSelectOption(select, label, value) {
|
||||||
if(!value)
|
if(!value)
|
||||||
value = label;
|
value = label;
|
||||||
for(let i = 0; i < select.children.length; i++) {
|
for(let i = 0; i < select.children.length; i++) {
|
||||||
let child = /** @type {HTMLOptionElement} */ (select.children[i]);
|
let child = select.children[i];
|
||||||
|
if(!(child instanceof HTMLOptionElement)) {
|
||||||
|
console.warn('Unexpected select child');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if(child.value === value) {
|
if(child.value === value) {
|
||||||
if(child.label !== label) {
|
if(child.label !== label) {
|
||||||
child.label = label;
|
child.label = label;
|
||||||
|
@ -536,10 +592,13 @@ function addSelectOption(select, label, value) {
|
||||||
function selectOptionAvailable(select, value) {
|
function selectOptionAvailable(select, value) {
|
||||||
let children = select.children;
|
let children = select.children;
|
||||||
for(let i = 0; i < children.length; i++) {
|
for(let i = 0; i < children.length; i++) {
|
||||||
let child = /** @type {HTMLOptionElement} */ (select.children[i]);
|
let child = select.children[i];
|
||||||
if(child.value === value) {
|
if(!(child instanceof HTMLOptionElement)) {
|
||||||
return true;
|
console.warn('Unexpected select child');
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
if(child.value === value)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -551,7 +610,11 @@ function selectOptionAvailable(select, value) {
|
||||||
function selectOptionDefault(select) {
|
function selectOptionDefault(select) {
|
||||||
/* First non-empty option. */
|
/* First non-empty option. */
|
||||||
for(let i = 0; i < select.children.length; i++) {
|
for(let i = 0; i < select.children.length; i++) {
|
||||||
let child = /** @type {HTMLOptionElement} */ (select.children[i]);
|
let child = select.children[i];
|
||||||
|
if(!(child instanceof HTMLOptionElement)) {
|
||||||
|
console.warn('Unexpected select child');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if(child.value)
|
if(child.value)
|
||||||
return child.value;
|
return child.value;
|
||||||
}
|
}
|
||||||
|
@ -587,13 +650,13 @@ async function setMediaChoices(done) {
|
||||||
if(d.kind === 'videoinput') {
|
if(d.kind === 'videoinput') {
|
||||||
if(!label)
|
if(!label)
|
||||||
label = `Camera ${cn}`;
|
label = `Camera ${cn}`;
|
||||||
addSelectOption(document.getElementById('videoselect'),
|
addSelectOption(getSelectElement('videoselect'),
|
||||||
label, d.deviceId);
|
label, d.deviceId);
|
||||||
cn++;
|
cn++;
|
||||||
} else if(d.kind === 'audioinput') {
|
} else if(d.kind === 'audioinput') {
|
||||||
if(!label)
|
if(!label)
|
||||||
label = `Microphone ${mn}`;
|
label = `Microphone ${mn}`;
|
||||||
addSelectOption(document.getElementById('audioselect'),
|
addSelectOption(getSelectElement('audioselect'),
|
||||||
label, d.deviceId);
|
label, d.deviceId);
|
||||||
mn++;
|
mn++;
|
||||||
}
|
}
|
||||||
|
@ -693,6 +756,7 @@ async function addLocalMedia(id) {
|
||||||
stopUpMedia(old);
|
stopUpMedia(old);
|
||||||
|
|
||||||
let constraints = {audio: audio, video: video};
|
let constraints = {audio: audio, video: video};
|
||||||
|
/** @type {MediaStream} */
|
||||||
let stream = null;
|
let stream = null;
|
||||||
try {
|
try {
|
||||||
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||||
|
@ -717,11 +781,11 @@ async function addLocalMedia(id) {
|
||||||
t.enabled = false;
|
t.enabled = false;
|
||||||
} else if(t.kind == 'video') {
|
} else if(t.kind == 'video') {
|
||||||
if(settings.blackboardMode) {
|
if(settings.blackboardMode) {
|
||||||
if('contentHint' in t)
|
/** @ts-ignore */
|
||||||
t.contentHint = 'detail';
|
t.contentHint = 'detail';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let sender = c.pc.addTrack(t, stream);
|
c.pc.addTrack(t, stream);
|
||||||
});
|
});
|
||||||
|
|
||||||
c.onstats = gotUpStats;
|
c.onstats = gotUpStats;
|
||||||
|
@ -730,12 +794,16 @@ async function addLocalMedia(id) {
|
||||||
setButtonsVisibility();
|
setButtonsVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addShareMedia(setup) {
|
async function addShareMedia() {
|
||||||
if(!getUserPass())
|
if(!getUserPass())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
/** @type {MediaStream} */
|
||||||
let stream = null;
|
let stream = null;
|
||||||
try {
|
try {
|
||||||
|
if(!('getDisplayMedia' in navigator.mediaDevices))
|
||||||
|
throw new Error('Your browser does not support screen sharing');
|
||||||
|
/** @ts-ignore */
|
||||||
stream = await navigator.mediaDevices.getDisplayMedia({video: true});
|
stream = await navigator.mediaDevices.getDisplayMedia({video: true});
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -747,7 +815,7 @@ async function addShareMedia(setup) {
|
||||||
c.kind = 'screenshare';
|
c.kind = 'screenshare';
|
||||||
c.stream = stream;
|
c.stream = stream;
|
||||||
stream.getTracks().forEach(t => {
|
stream.getTracks().forEach(t => {
|
||||||
let sender = c.pc.addTrack(t, stream);
|
c.pc.addTrack(t, stream);
|
||||||
t.onended = e => {
|
t.onended = e => {
|
||||||
delUpMedia(c);
|
delUpMedia(c);
|
||||||
};
|
};
|
||||||
|
@ -807,6 +875,9 @@ function delUpMediaKind(kind) {
|
||||||
hideVideo();
|
hideVideo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} kind
|
||||||
|
*/
|
||||||
function findUpMedia(kind) {
|
function findUpMedia(kind) {
|
||||||
for(let id in serverConnection.up) {
|
for(let id in serverConnection.up) {
|
||||||
if(serverConnection.up[id].kind === kind)
|
if(serverConnection.up[id].kind === kind)
|
||||||
|
@ -815,6 +886,9 @@ function findUpMedia(kind) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} mute
|
||||||
|
*/
|
||||||
function muteLocalTracks(mute) {
|
function muteLocalTracks(mute) {
|
||||||
if(!serverConnection)
|
if(!serverConnection)
|
||||||
return;
|
return;
|
||||||
|
@ -846,12 +920,14 @@ function setMedia(c, isUp) {
|
||||||
peersdiv.appendChild(div);
|
peersdiv.appendChild(div);
|
||||||
}
|
}
|
||||||
|
|
||||||
let media = document.getElementById('media-' + c.id);
|
let media = /** @type {HTMLVideoElement} */
|
||||||
|
(document.getElementById('media-' + c.id));
|
||||||
if(!media) {
|
if(!media) {
|
||||||
media = document.createElement('video');
|
media = document.createElement('video');
|
||||||
media.id = 'media-' + c.id;
|
media.id = 'media-' + c.id;
|
||||||
media.classList.add('media');
|
media.classList.add('media');
|
||||||
media.autoplay = true;
|
media.autoplay = true;
|
||||||
|
/** @ts-ignore */
|
||||||
media.playsinline = true;
|
media.playsinline = true;
|
||||||
media.controls = true;
|
media.controls = true;
|
||||||
if(isUp)
|
if(isUp)
|
||||||
|
@ -883,7 +959,9 @@ function delMedia(id) {
|
||||||
let peer = document.getElementById('peer-' + id);
|
let peer = document.getElementById('peer-' + id);
|
||||||
if(!peer)
|
if(!peer)
|
||||||
throw new Error('Removing unknown media');
|
throw new Error('Removing unknown media');
|
||||||
let media = document.getElementById('media-' + id);
|
|
||||||
|
let media = /** @type{HTMLVideoElement} */
|
||||||
|
(document.getElementById('media-' + id));
|
||||||
|
|
||||||
media.srcObject = null;
|
media.srcObject = null;
|
||||||
mediadiv.removeChild(peer);
|
mediadiv.removeChild(peer);
|
||||||
|
@ -941,14 +1019,19 @@ function resizePeers() {
|
||||||
if (!count)
|
if (!count)
|
||||||
// No video, nothing to resize.
|
// No video, nothing to resize.
|
||||||
return;
|
return;
|
||||||
let size = 100 / columns;
|
|
||||||
let container = document.getElementById("video-container")
|
let container = document.getElementById("video-container")
|
||||||
// Peers div has total padding of 30px, we remove 30 on offsetHeight
|
// Peers div has total padding of 30px, we remove 30 on offsetHeight
|
||||||
let max_video_height = Math.trunc((peers.offsetHeight - 30) / columns);
|
let max_video_height = Math.trunc((peers.offsetHeight - 30) / columns);
|
||||||
|
|
||||||
let media_list = document.getElementsByClassName("media");
|
let media_list = document.getElementsByClassName("media");
|
||||||
for(let i = 0; i < media_list.length; i++)
|
for(let i = 0; i < media_list.length; i++) {
|
||||||
media_list[i].style['max_height'] = max_video_height + "px";
|
let media = media_list[i];
|
||||||
|
if(!(media instanceof HTMLMediaElement)) {
|
||||||
|
console.warn('Unexpected media');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
media.style['max_height'] = max_video_height + "px";
|
||||||
|
}
|
||||||
|
|
||||||
if (count <= 2 && container.offsetHeight > container.offsetWidth) {
|
if (count <= 2 && container.offsetHeight > container.offsetWidth) {
|
||||||
peers.style['grid-template-columns'] = "repeat(1, 1fr)";
|
peers.style['grid-template-columns'] = "repeat(1, 1fr)";
|
||||||
|
@ -957,7 +1040,7 @@ function resizePeers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type{Object.<string,string>} */
|
/** @type{Object<string,string>} */
|
||||||
let users = {};
|
let users = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1051,7 +1134,7 @@ function clearUsername() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object.<string,boolean>} perms
|
* @param {Object<string,boolean>} perms
|
||||||
*/
|
*/
|
||||||
function gotPermissions(perms) {
|
function gotPermissions(perms) {
|
||||||
displayUsername();
|
displayUsername();
|
||||||
|
@ -1064,7 +1147,7 @@ const urlRegexp = /https?:\/\/[-a-zA-Z0-9@:%/._\\+~#=?]+[-a-zA-Z0-9@:%/_\\+~#=]/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} line
|
* @param {string} line
|
||||||
* @returns {Array.<Text|HTMLElement>}
|
* @returns {(Text|HTMLElement)[]}
|
||||||
*/
|
*/
|
||||||
function formatLine(line) {
|
function formatLine(line) {
|
||||||
let r = new RegExp(urlRegexp);
|
let r = new RegExp(urlRegexp);
|
||||||
|
@ -1088,7 +1171,7 @@ function formatLine(line) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array.<string>} lines
|
* @param {string[]} lines
|
||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
function formatLines(lines) {
|
function formatLines(lines) {
|
||||||
|
@ -1113,6 +1196,12 @@ function formatLines(lines) {
|
||||||
/** @type {lastMessage} */
|
/** @type {lastMessage} */
|
||||||
let lastMessage = {};
|
let lastMessage = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} peerId
|
||||||
|
* @param {string} nick
|
||||||
|
* @param {string} kind
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
function addToChatbox(peerId, nick, kind, message){
|
function addToChatbox(peerId, nick, kind, message){
|
||||||
let userpass = getUserPass();
|
let userpass = getUserPass();
|
||||||
let row = document.createElement('div');
|
let row = document.createElement('div');
|
||||||
|
@ -1174,7 +1263,7 @@ function clearChat() {
|
||||||
* part may be quoted and may include backslash escapes.
|
* part may be quoted and may include backslash escapes.
|
||||||
*
|
*
|
||||||
* @param {string} line
|
* @param {string} line
|
||||||
* @returns {Array.<string>}
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
function parseCommand(line) {
|
function parseCommand(line) {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
@ -1204,7 +1293,8 @@ function parseCommand(line) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInput() {
|
function handleInput() {
|
||||||
let input = document.getElementById('input');
|
let input = /** @type {HTMLTextAreaElement} */
|
||||||
|
(document.getElementById('input'));
|
||||||
let data = input.value;
|
let data = input.value;
|
||||||
input.value = '';
|
input.value = '';
|
||||||
|
|
||||||
|
@ -1356,39 +1446,66 @@ function chatResizer(e) {
|
||||||
|
|
||||||
document.getElementById('resizer').addEventListener('mousedown', chatResizer, false);
|
document.getElementById('resizer').addEventListener('mousedown', chatResizer, false);
|
||||||
|
|
||||||
|
/** @enum {string} */
|
||||||
|
const MessageLevel = {
|
||||||
|
info: 'info',
|
||||||
|
warning: 'warning',
|
||||||
|
error: 'error',
|
||||||
|
}
|
||||||
|
|
||||||
function displayError(message, level, position, gravity) {
|
/**
|
||||||
var background = "linear-gradient(to right, #e20a0a, #df2d2d)";
|
* @param {string} message
|
||||||
if (level === "info") {
|
* @param {MessageLevel} [level]
|
||||||
background = "linear-gradient(to right, #529518, #96c93d)";
|
*/
|
||||||
}
|
function displayError(message, level) {
|
||||||
if (level === "warning") {
|
if(!level)
|
||||||
background = "linear-gradient(to right, #edd800, #c9c200)";
|
level = MessageLevel.error;
|
||||||
|
|
||||||
|
var background = 'linear-gradient(to right, #e20a0a, #df2d2d)';
|
||||||
|
var position = 'center';
|
||||||
|
var gravity = 'top';
|
||||||
|
|
||||||
|
switch(level) {
|
||||||
|
case MessageLevel.info:
|
||||||
|
background = 'linear-gradient(to right, #529518, #96c93d)';
|
||||||
|
position = 'right';
|
||||||
|
gravity = 'bottom';
|
||||||
|
break;
|
||||||
|
case MessageLevel.warning:
|
||||||
|
background = "linear-gradient(to right, #edd800, #c9c200)";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @ts-ignore */
|
||||||
Toastify({
|
Toastify({
|
||||||
text: message,
|
text: message,
|
||||||
duration: 4000,
|
duration: 4000,
|
||||||
close: true,
|
close: true,
|
||||||
position: position ? position: 'center',
|
position: position,
|
||||||
gravity: gravity ? gravity : 'top',
|
gravity: gravity,
|
||||||
backgroundColor: background,
|
backgroundColor: background,
|
||||||
className: level,
|
className: level,
|
||||||
}).showToast();
|
}).showToast();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
function displayWarning(message) {
|
function displayWarning(message) {
|
||||||
let level = "warning";
|
return displayError(message, MessageLevel.warning);
|
||||||
return displayError(message, level);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
function displayMessage(message) {
|
function displayMessage(message) {
|
||||||
return displayError(message, "info", "right", "bottom");
|
return displayError(message, MessageLevel.info);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('userform').onsubmit = function(e) {
|
document.getElementById('userform').onsubmit = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let username = document.getElementById('username').value.trim();
|
let username = getInputElement('username').value.trim();
|
||||||
let password = document.getElementById('password').value;
|
let password = getInputElement('password').value;
|
||||||
storeUserPass(username, password);
|
storeUserPass(username, password);
|
||||||
serverConnect();
|
serverConnect();
|
||||||
};
|
};
|
||||||
|
@ -1438,6 +1555,8 @@ document.getElementById('clodeside').onclick = function(e) {
|
||||||
|
|
||||||
document.getElementById('collapse-video').onclick = function(e) {
|
document.getElementById('collapse-video').onclick = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if(!(this instanceof HTMLElement))
|
||||||
|
throw new Error('Unexpected type for this');
|
||||||
let width = window.innerWidth;
|
let width = window.innerWidth;
|
||||||
if (width <= 768) {
|
if (width <= 768) {
|
||||||
let user_box = document.getElementById('userDropdown');
|
let user_box = document.getElementById('userDropdown');
|
||||||
|
@ -1453,6 +1572,8 @@ document.getElementById('collapse-video').onclick = function(e) {
|
||||||
|
|
||||||
document.getElementById('switch-video').onclick = function(e) {
|
document.getElementById('switch-video').onclick = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if(!(this instanceof HTMLElement))
|
||||||
|
throw new Error('Unexpected type for this');
|
||||||
showVideo();
|
showVideo();
|
||||||
this.style.display = "";
|
this.style.display = "";
|
||||||
document.getElementById('collapse-video').style.display = "block";
|
document.getElementById('collapse-video').style.display = "block";
|
||||||
|
|
|
@ -4,10 +4,16 @@
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"strictBindCallApply": true
|
"strictFunctionTypes": true,
|
||||||
|
"strictBindCallApply": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noUnusedLocals": true
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"protocol.js"
|
"protocol.js",
|
||||||
|
"sfu.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue