2021-04-27 18:37:25 -06:00
|
|
|
import {GetFortune} from "./fortunes.mjs"
|
|
|
|
|
|
|
|
const Millisecond = 1
|
|
|
|
const Second = 1000 * Millisecond
|
|
|
|
const Minute = 60 * Second
|
|
|
|
|
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 = []
|
2022-04-26 12:49:35 -06:00
|
|
|
this.wantConnected = true
|
2021-04-27 17:30:16 -06:00
|
|
|
|
|
|
|
this.wsUrl = new URL("chat", window.location)
|
|
|
|
this.wsUrl.protocol = this.wsUrl.protocol.replace("http", "ws")
|
2022-03-31 12:40:45 -06:00
|
|
|
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() {
|
2022-04-26 12:49:35 -06:00
|
|
|
if (!this.wantConnected) {
|
|
|
|
return
|
|
|
|
}
|
2022-04-22 18:14:55 -06:00
|
|
|
console.info("Attempting to reconnect", this.wsUrl.href)
|
2021-04-27 17:30:16 -06:00
|
|
|
this.clockOffset = 0
|
|
|
|
this.socket = new WebSocket(this.wsUrl)
|
|
|
|
this.socket.addEventListener("message", e => this.wsMessage(e))
|
|
|
|
this.socket.addEventListener("close", () => this.reopen())
|
|
|
|
}
|
|
|
|
|
|
|
|
stats() {
|
|
|
|
return {
|
|
|
|
averageLag: this.lagDurations.reduce((a,b) => (a+b), 0) / this.lagDurations.length,
|
|
|
|
clockOffset: this.clockOffset,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wsMessage(event) {
|
|
|
|
let now = Date.now()
|
|
|
|
let jmsg = event.data
|
|
|
|
let msg
|
|
|
|
try {
|
|
|
|
msg = JSON.parse(jmsg)
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
console.error(err, jmsg)
|
|
|
|
return
|
|
|
|
}
|
2022-04-19 22:41:09 -06:00
|
|
|
|
2021-04-27 17:30:16 -06:00
|
|
|
let beginTxTime = msg[0]
|
|
|
|
let durations = msg.slice(1)
|
|
|
|
|
|
|
|
// Why is this happening?
|
|
|
|
if (beginTxTime == 0) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let sent = this.sent.filter(e => e != jmsg)
|
|
|
|
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.
|
|
|
|
let totalDuration = durations.reduce((a, b) => a + b)
|
|
|
|
this.sent = sent
|
|
|
|
this.lagDurations.unshift(now - this.clockOffset - beginTxTime - totalDuration)
|
|
|
|
this.lagDurations.splice(20, 2)
|
|
|
|
this.rx(0, 0, this.stats())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// The very first packet is the server telling us the current time
|
|
|
|
if (durations.length == 0) {
|
|
|
|
if (this.clockOffset == 0) {
|
|
|
|
this.clockOffset = now - beginTxTime
|
|
|
|
this.rx(0, 0, this.stats())
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adjust playback time to clock offset
|
|
|
|
let adjustedTxTime = beginTxTime + this.clockOffset
|
|
|
|
|
|
|
|
// Every second value is a silence duration
|
|
|
|
let tx = true
|
|
|
|
for (let duration of durations) {
|
|
|
|
duration = Number(duration)
|
|
|
|
if (tx && (duration > 0)) {
|
|
|
|
this.rx(adjustedTxTime, duration, this.stats())
|
|
|
|
}
|
|
|
|
adjustedTxTime = Number(adjustedTxTime) + duration
|
|
|
|
tx = !tx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Send a transmission
|
|
|
|
*
|
|
|
|
* @param {number} time When to play this transmission
|
|
|
|
* @param {number} duration How long the transmission is
|
|
|
|
* @param {boolean} squelch True to mute this tone when we get it back from the repeater
|
|
|
|
*/
|
|
|
|
Transmit(time, duration, squelch=true) {
|
|
|
|
let msg = [time - this.clockOffset, duration]
|
|
|
|
let jmsg = JSON.stringify(msg)
|
2022-04-22 18:14:55 -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) {
|
|
|
|
this.sent.push(jmsg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Close() {
|
2022-04-26 12:49:35 -06:00
|
|
|
this.wantConnected = false
|
2021-04-27 17:30:16 -06:00
|
|
|
this.socket.close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Null {
|
2021-04-28 14:28:59 -06:00
|
|
|
constructor(rx) {
|
|
|
|
this.rx = rx
|
2022-05-14 18:51:05 -06:00
|
|
|
this.interval = setInterval(() => this.pulse(), 3 * Second)
|
2021-04-28 14:28:59 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
pulse() {
|
|
|
|
this.rx(0, 0, {note: "local"})
|
|
|
|
}
|
|
|
|
|
|
|
|
Transmit(time, duration, squelch=true) {
|
2021-04-27 17:30:16 -06:00
|
|
|
}
|
|
|
|
|
2021-04-28 14:28:59 -06:00
|
|
|
Close() {
|
|
|
|
clearInterval(this.interval)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Echo {
|
|
|
|
constructor(rx, delay=0) {
|
|
|
|
this.rx = rx
|
|
|
|
this.delay = delay
|
|
|
|
this.Transmit(0, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
Transmit(time, duration, squelch=true) {
|
|
|
|
this.rx(time + this.delay, duration, {note: "local"})
|
2021-04-27 17:30:16 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
Close() {
|
|
|
|
}
|
|
|
|
}
|
2021-04-27 18:37:25 -06:00
|
|
|
|
|
|
|
export class Fortune {
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param rx Receive callback
|
|
|
|
* @param {Keyer} keyer Keyer object
|
|
|
|
*/
|
|
|
|
constructor(rx, keyer) {
|
|
|
|
this.rx = rx
|
|
|
|
this.keyer = keyer
|
|
|
|
|
|
|
|
this.interval = setInterval(() => this.pulse(), 1 * Minute)
|
|
|
|
this.pulse()
|
|
|
|
}
|
|
|
|
|
|
|
|
pulse() {
|
2021-04-28 14:28:59 -06:00
|
|
|
this.rx(0, 0, {note: "local"})
|
2021-04-27 18:37:25 -06:00
|
|
|
if (this.keyer.Busy()) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let fortune = GetFortune()
|
|
|
|
this.keyer.EnqueueAsciiString(`${fortune}\x04 `)
|
|
|
|
}
|
|
|
|
|
|
|
|
Transmit(time, duration, squelch=true) {
|
|
|
|
// Do nothing.
|
|
|
|
}
|
|
|
|
|
|
|
|
Close() {
|
2021-04-28 14:28:59 -06:00
|
|
|
this.keyer.Flush()
|
2021-04-27 18:37:25 -06:00
|
|
|
clearInterval(this.interval)
|
|
|
|
}
|
|
|
|
}
|