vail/static/scripts/repeaters.mjs

229 lines
5.9 KiB
JavaScript
Raw Normal View History

2021-04-27 18:37:25 -06:00
import {GetFortune} from "./fortunes.mjs"
const Millisecond = 1
const Second = 1000 * Millisecond
const Minute = 60 * Second
2022-05-15 21:12:36 -06:00
/**
* Compare two messages
*
* @param {Object} m1 First message
* @param {Object} m2 Second message
* @returns {Boolean} true if messages are equal
*/
function MessageEqual(m1, m2) {
if ((m1.Timestamp != m2.Timestamp) || (m1.Duration.length != m2.Duration.length)) {
return false
}
for (let i=0; i < m1.Duration.length; i++) {
if (m1.Duration[i] != m2.Duration[i]) {
return false
}
}
return true
}
2021-04-27 17:30:16 -06:00
export class Vail {
2021-04-28 14:28:59 -06:00
constructor(rx, name) {
2021-04-27 17:30:16 -06:00
this.rx = rx
2021-04-28 14:28:59 -06:00
this.name = name
2021-04-27 17:30:16 -06:00
this.lagDurations = []
this.sent = []
this.wantConnected = true
2022-06-06 10:55:11 -06:00
this.connected = false
2021-04-27 17:30:16 -06:00
this.wsUrl = new URL("chat", window.location)
this.wsUrl.protocol = this.wsUrl.protocol.replace("http", "ws")
this.wsUrl.pathname = this.wsUrl.pathname.replace("testing/", "") // Allow staging deploys
2021-04-27 17:30:16 -06:00
this.wsUrl.searchParams.set("repeater", name)
this.reopen()
}
reopen() {
if (!this.wantConnected) {
return
}
2022-06-06 10:55:11 -06:00
this.rx(0, 0, {connected: false})
console.info("Attempting to reconnect", this.wsUrl.href)
2021-04-27 17:30:16 -06:00
this.clockOffset = 0
2022-06-06 09:54:55 -06:00
this.socket = new WebSocket(this.wsUrl, ["json.vail.woozle.org"])
2021-04-27 17:30:16 -06:00
this.socket.addEventListener("message", e => this.wsMessage(e))
2022-06-06 10:55:11 -06:00
this.socket.addEventListener(
"open",
msg => {
this.connected = true
2022-06-06 16:52:22 -06:00
this.rx(0, 0, {connected: true, notice: "Repeater connected"})
2022-06-06 10:55:11 -06:00
}
)
2022-05-15 21:12:36 -06:00
this.socket.addEventListener(
2022-06-06 09:54:55 -06:00
"close",
msg => {
2022-06-06 16:52:22 -06:00
this.rx(0, 0, {connected: false, notice: `Repeater disconnected: ${msg.reason}`})
2022-06-06 09:54:55 -06:00
console.error("Repeater connection dropped:", msg.reason)
setTimeout(() => this.reopen(), 2*Second)
2022-05-15 21:12:36 -06:00
}
)
2021-04-27 17:30:16 -06:00
}
wsMessage(event) {
let now = Date.now()
let jmsg = event.data
let msg
try {
msg = JSON.parse(jmsg)
}
catch (err) {
2022-06-06 14:44:09 -06:00
console.error(err, jmsg)
2021-04-27 17:30:16 -06:00
return
}
2022-05-15 21:12:36 -06:00
let stats = {
averageLag: this.lagDurations.reduce((a,b) => (a+b), 0) / this.lagDurations.length,
clockOffset: this.clockOffset,
clients: msg.Clients,
2022-06-06 10:55:11 -06:00
connected: this.connected,
2022-05-15 21:12:36 -06:00
}
2022-05-15 21:12:36 -06:00
// XXX: Why is this happening?
if (msg.Timestamp == 0) {
2022-06-06 14:44:09 -06:00
console.debug("Got timestamp=0", msg)
2021-04-27 17:30:16 -06:00
return
}
2022-05-15 21:12:36 -06:00
let sent = this.sent.filter(m => !MessageEqual(msg, m))
2021-04-27 17:30:16 -06:00
if (sent.length < this.sent.length) {
// We're getting our own message back, which tells us our lag.
// We shouldn't emit a tone, though.
2022-05-15 21:12:36 -06:00
let totalDuration = msg.Duration.reduce((a, b) => a + b)
2021-04-27 17:30:16 -06:00
this.sent = sent
2022-05-15 21:12:36 -06:00
this.lagDurations.unshift(now - this.clockOffset - msg.Timestamp - totalDuration)
2021-04-27 17:30:16 -06:00
this.lagDurations.splice(20, 2)
2022-05-15 21:12:36 -06:00
this.rx(0, 0, stats)
2021-04-27 17:30:16 -06:00
return
}
2022-05-15 21:12:36 -06:00
// Packets with 0 length tell us what time the server thinks it is,
// and how many clients are connected
if (msg.Duration.length == 0) {
2022-06-06 14:44:09 -06:00
this.clockOffset = now - msg.Timestamp
this.rx(0, 0, stats)
2021-04-27 17:30:16 -06:00
return
}
// Adjust playback time to clock offset
2022-05-15 21:12:36 -06:00
let adjustedTxTime = msg.Timestamp + this.clockOffset
2021-04-27 17:30:16 -06:00
// Every second value is a silence duration
let tx = true
2022-05-15 21:12:36 -06:00
for (let duration of msg.Duration) {
2021-04-27 17:30:16 -06:00
duration = Number(duration)
if (tx && (duration > 0)) {
2022-05-15 21:12:36 -06:00
this.rx(adjustedTxTime, duration, stats)
2021-04-27 17:30:16 -06:00
}
adjustedTxTime = Number(adjustedTxTime) + duration
tx = !tx
}
}
/**
* Send a transmission
*
2022-05-15 21:12:36 -06:00
* @param {number} timestamp When to play this transmission
2021-04-27 17:30:16 -06:00
* @param {number} duration How long the transmission is
* @param {boolean} squelch True to mute this tone when we get it back from the repeater
*/
2022-05-15 21:12:36 -06:00
Transmit(timestamp, duration, squelch=true) {
let msg = {
Timestamp: timestamp,
Duration: [duration],
}
2021-04-27 17:30:16 -06:00
let jmsg = JSON.stringify(msg)
2022-05-15 21:12:36 -06:00
if (this.socket.readyState != 1) {
// If we aren't connected, complain.
console.error("Not connected, dropping", jmsg)
return
}
2021-04-27 17:30:16 -06:00
this.socket.send(jmsg)
if (squelch) {
2022-05-15 21:12:36 -06:00
this.sent.push(msg)
2021-04-27 17:30:16 -06:00
}
}
Close() {
this.wantConnected = false
2021-04-27 17:30:16 -06:00
this.socket.close()
}
}
export class Null {
2022-06-06 10:55:11 -06:00
constructor(rx, interval=3*Second) {
2021-04-28 14:28:59 -06:00
this.rx = rx
2022-06-06 16:52:22 -06:00
this.init()
2021-04-28 14:28:59 -06:00
}
2022-06-06 16:52:22 -06:00
notice(msg) {
this.rx(0, 0, {connected: false, notice: msg})
2021-04-28 14:28:59 -06:00
}
2022-06-06 16:52:22 -06:00
init() {
this.notice("Null repeater: nobody will hear you.")
2021-04-27 17:30:16 -06:00
}
2022-06-06 16:52:22 -06:00
Transmit(time, duration, squelch=true) {}
Close() {}
2021-04-28 14:28:59 -06:00
}
2022-06-06 10:55:11 -06:00
export class Echo extends Null {
2021-04-28 14:28:59 -06:00
constructor(rx, delay=0) {
2022-06-06 10:55:11 -06:00
super(rx)
2021-04-28 14:28:59 -06:00
this.delay = delay
}
2022-06-06 16:52:22 -06:00
init () {
this.notice("Echo repeater: you can only hear yourself.")
}
2021-04-28 14:28:59 -06:00
Transmit(time, duration, squelch=true) {
this.rx(time + this.delay, duration, {note: "local"})
2021-04-27 17:30:16 -06:00
}
}
2021-04-27 18:37:25 -06:00
2022-06-06 10:55:11 -06:00
export class Fortune extends Null {
2021-04-27 18:37:25 -06:00
/**
*
* @param rx Receive callback
2022-06-06 16:52:22 -06:00
* @param {Keyer} keyer Robokeyer
2021-04-27 18:37:25 -06:00
*/
constructor(rx, keyer) {
2022-06-06 16:52:22 -06:00
super(rx)
2021-04-27 18:37:25 -06:00
this.keyer = keyer
2022-06-06 16:52:22 -06:00
}
init() {
this.notice("Say something, and I will tell you your fortune.")
2021-04-27 18:37:25 -06:00
}
pulse() {
2022-06-06 16:52:22 -06:00
this.timeout = null
2022-06-06 10:55:11 -06:00
if (!this.keyer || this.keyer.Busy()) {
2021-04-27 18:37:25 -06:00
return
}
let fortune = GetFortune()
2022-06-06 10:55:11 -06:00
this.keyer.EnqueueAsciiString(`${fortune} \x04 `)
2021-04-27 18:37:25 -06:00
}
2022-06-06 16:52:22 -06:00
Transmit(time, duration, squelch=true) {
if (this.timeout) {
clearTimeout(this.timeout)
}
this.timeout = setTimeout(() => this.pulse(), 3 * Second)
}
2021-04-27 18:37:25 -06:00
Close() {
2021-04-28 14:28:59 -06:00
this.keyer.Flush()
2022-06-06 10:55:11 -06:00
super.Close()
2021-04-27 18:37:25 -06:00
}
}