mirror of https://github.com/nealey/vail.git
Compare commits
3 Commits
15e43c28df
...
b910676539
Author | SHA1 | Date |
---|---|---|
|
b910676539 | |
|
314994adcd | |
|
b45876bcf0 |
|
@ -20,7 +20,7 @@ func TestBook(t *testing.T) {
|
|||
c1.Expect(1)
|
||||
|
||||
// Send to an empty channel
|
||||
m := Message{0, 0, []uint8{22, 33}}
|
||||
m := Message{0, 0, []uint16{22, 33}}
|
||||
b.Send("merf", m)
|
||||
b.loop()
|
||||
if c1.Len() > 0 {
|
||||
|
|
105
cmd/vail/main.go
105
cmd/vail/main.go
|
@ -1,17 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
"nhooyr.io/websocket"
|
||||
)
|
||||
|
||||
var book Book
|
||||
|
||||
const JsonProtocol = "json.vail.woozle.org"
|
||||
const BinaryProtocol = "binary.vail.woozle.org"
|
||||
|
||||
// Clock defines an interface for getting the current time.
|
||||
//
|
||||
// We use this in testing to provide a fixed value for the current time, so we
|
||||
|
@ -20,7 +24,7 @@ type Clock interface {
|
|||
Now() time.Time
|
||||
}
|
||||
|
||||
// WallClock provides the actual time
|
||||
// WallClock is a Clock which provides the actual time
|
||||
type WallClock struct{}
|
||||
|
||||
func (WallClock) Now() time.Time {
|
||||
|
@ -30,32 +34,87 @@ func (WallClock) Now() time.Time {
|
|||
// VailWebSocketConnection reads and writes Message structs
|
||||
type VailWebSocketConnection struct {
|
||||
*websocket.Conn
|
||||
usingJSON bool
|
||||
}
|
||||
|
||||
func (c *VailWebSocketConnection) Receive() (Message, error) {
|
||||
var m Message
|
||||
err := websocket.JSON.Receive(c.Conn, &m)
|
||||
messageType, buf, err := c.Read(context.Background())
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
|
||||
if messageType == websocket.MessageText {
|
||||
err = json.Unmarshal(buf, &m)
|
||||
} else {
|
||||
err = m.UnmarshalBinary(buf)
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
func (c *VailWebSocketConnection) Send(m Message) error {
|
||||
return websocket.JSON.Send(c.Conn, m)
|
||||
var err error
|
||||
var buf []byte
|
||||
var messageType websocket.MessageType
|
||||
|
||||
log.Println("Send", m)
|
||||
if c.usingJSON {
|
||||
messageType = websocket.MessageText
|
||||
buf, err = json.Marshal(m)
|
||||
} else {
|
||||
messageType = websocket.MessageBinary
|
||||
buf, err = m.MarshalBinary()
|
||||
}
|
||||
log.Println(buf, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
repeaterName string
|
||||
log.Println("Sending")
|
||||
return c.Write(context.Background(), messageType, buf)
|
||||
}
|
||||
|
||||
func (c Client) Handle(ws *websocket.Conn) {
|
||||
sock := &VailWebSocketConnection{ws}
|
||||
nowMilli := time.Now().UnixMilli()
|
||||
ws.MaxPayloadBytes = 50
|
||||
book.Join(c.repeaterName, sock)
|
||||
defer book.Part(c.repeaterName, sock)
|
||||
func ChatHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Set up websocket
|
||||
ws, err := websocket.Accept(
|
||||
w, r,
|
||||
&websocket.AcceptOptions{
|
||||
Subprotocols: []string{JsonProtocol, BinaryProtocol},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
defer ws.Close(websocket.StatusInternalError, "Internal error")
|
||||
|
||||
// Create our Vail websocket connection for books to send to
|
||||
sock := VailWebSocketConnection{
|
||||
Conn: ws,
|
||||
}
|
||||
|
||||
// websockets apparently sends a subprotocol string, so we can ignore Accept headers!
|
||||
switch ws.Subprotocol() {
|
||||
case JsonProtocol:
|
||||
sock.usingJSON = true
|
||||
case BinaryProtocol:
|
||||
sock.usingJSON = false
|
||||
default:
|
||||
ws.Close(websocket.StatusPolicyViolation, "client must speak a vail protocol")
|
||||
return
|
||||
}
|
||||
|
||||
// Join the repeater
|
||||
repeaterName := r.FormValue("repeater")
|
||||
book.Join(repeaterName, &sock)
|
||||
defer book.Part(repeaterName, &sock)
|
||||
|
||||
for {
|
||||
// Read a packet
|
||||
m, err := sock.Receive()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
ws.Close(websocket.StatusInvalidFramePayloadData, err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
|
@ -65,30 +124,20 @@ func (c Client) Handle(ws *websocket.Conn) {
|
|||
}
|
||||
|
||||
// If it's wildly out of time, reject it
|
||||
timeDelta := (nowMilli - m.Timestamp)
|
||||
timeDelta := (time.Now().UnixMilli() - m.Timestamp)
|
||||
if timeDelta < 0 {
|
||||
timeDelta = -timeDelta
|
||||
}
|
||||
if timeDelta > 9999 {
|
||||
fmt.Fprintln(ws, "Bad timestamp")
|
||||
ws.Close()
|
||||
return
|
||||
log.Println(err)
|
||||
ws.Close(websocket.StatusInvalidFramePayloadData, "Your clock is off by too much")
|
||||
break
|
||||
}
|
||||
|
||||
book.Send(c.repeaterName, m)
|
||||
book.Send(repeaterName, m)
|
||||
}
|
||||
}
|
||||
|
||||
func ChatHandler(w http.ResponseWriter, r *http.Request) {
|
||||
c := Client{
|
||||
repeaterName: r.FormValue("repeater"),
|
||||
}
|
||||
|
||||
// This API is confusing as hell.
|
||||
// I suspect there's a better way to do this.
|
||||
websocket.Handler(c.Handle).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func main() {
|
||||
book = NewBook()
|
||||
http.Handle("/chat", http.HandlerFunc(ChatHandler))
|
||||
|
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -34,13 +33,13 @@ type Message struct {
|
|||
// Message timing in ms.
|
||||
// Timings alternate between tone and silence.
|
||||
// For example, `A` could be sent as [80, 80, 240]
|
||||
Duration []uint8
|
||||
Duration []uint16
|
||||
}
|
||||
|
||||
func NewMessage(ts time.Time, durations ...time.Duration) Message {
|
||||
msg := Message{
|
||||
Timestamp: ts.UnixNano() / time.Millisecond.Nanoseconds(),
|
||||
Duration: make([]uint8, len(durations)),
|
||||
Duration: make([]uint16, len(durations)),
|
||||
}
|
||||
for i, dns := range durations {
|
||||
ms := dns.Milliseconds()
|
||||
|
@ -49,7 +48,7 @@ func NewMessage(ts time.Time, durations ...time.Duration) Message {
|
|||
} else if ms < 0 {
|
||||
ms = 0
|
||||
}
|
||||
msg.Duration[i] = uint8(ms)
|
||||
msg.Duration[i] = uint16(ms)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
@ -69,7 +68,7 @@ func (m Message) MarshalBinary() ([]byte, error) {
|
|||
return w.Bytes(), nil
|
||||
}
|
||||
|
||||
// Unmarshaling presumes something else is keeping track of lengths
|
||||
// UnmarshalBinary unpacks a binary buffer into a Message.
|
||||
func (m *Message) UnmarshalBinary(data []byte) error {
|
||||
r := bytes.NewReader(data)
|
||||
if err := binary.Read(r, binary.BigEndian, &m.Timestamp); err != nil {
|
||||
|
@ -78,32 +77,14 @@ func (m *Message) UnmarshalBinary(data []byte) error {
|
|||
if err := binary.Read(r, binary.BigEndian, &m.Clients); err != nil {
|
||||
return err
|
||||
}
|
||||
dlen := r.Len()
|
||||
m.Duration = make([]uint8, dlen)
|
||||
dlen := r.Len() / 2
|
||||
m.Duration = make([]uint16, dlen)
|
||||
if err := binary.Read(r, binary.BigEndian, &m.Duration); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Message) MarshalJSON() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprint(buf, "{")
|
||||
fmt.Fprintf(buf, "\"Timestamp\":%d,", m.Timestamp)
|
||||
fmt.Fprintf(buf, "\"Clients\":%d,", m.Clients)
|
||||
fmt.Fprint(buf, "\"Duration\":[")
|
||||
for i := 0; i < len(m.Duration); i++ {
|
||||
fmt.Fprint(buf, m.Duration[i])
|
||||
if i <= len(m.Duration)-1 {
|
||||
fmt.Fprint(buf, ",")
|
||||
}
|
||||
}
|
||||
fmt.Fprint(buf)
|
||||
fmt.Fprint(buf, "]")
|
||||
fmt.Fprint(buf, "}")
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (m Message) Equal(m2 Message) bool {
|
||||
if m.Timestamp != m2.Timestamp {
|
||||
return false
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
)
|
||||
|
||||
func TestMessageStruct(t *testing.T) {
|
||||
m := Message{0x1122334455, 0, []uint8{0xaa, 0xbb, 0xcc}}
|
||||
m2 := Message{12, 0, []uint8{1}}
|
||||
m := Message{0x1122334455, 0, []uint16{0xaa, 0xbb, 0xcc}}
|
||||
m2 := Message{12, 0, []uint16{1}}
|
||||
|
||||
if !m.Equal(m) {
|
||||
t.Error("Equal messages did not compare equal")
|
||||
|
@ -16,7 +16,7 @@ func TestMessageStruct(t *testing.T) {
|
|||
if m.Equal(m2) {
|
||||
t.Error("Unequal messages compared equal")
|
||||
}
|
||||
if m.Equal(Message{m.Timestamp, 0, []uint8{1, 2, 3}}) {
|
||||
if m.Equal(Message{m.Timestamp, 0, []uint16{1, 2, 3}}) {
|
||||
t.Error("Messages with different payloads compared equal")
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ func TestMessageStruct(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !bytes.Equal(bm, []byte("\x00\x00\x00\x11\x22\x33\x44\x55\x00\x00\xaa\xbb\xcc")) {
|
||||
if !bytes.Equal(bm, []byte("\x00\x00\x00\x11\x22\x33\x44\x55\x00\x00\x00\xaa\x00\xbb\x00\xcc")) {
|
||||
t.Error("Encoded wrong:", bm)
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ func (tc *TestingClient) Len() int {
|
|||
return len(tc.buf)
|
||||
}
|
||||
|
||||
func (tc *TestingClient) Expect(clients uint16, payload ...uint8) {
|
||||
func (tc *TestingClient) Expect(clients uint16, payload ...uint16) {
|
||||
m := Message{0, clients, payload}
|
||||
tc.expected = append(tc.expected, m)
|
||||
if len(tc.buf) != len(tc.expected) {
|
||||
|
|
5
go.mod
5
go.mod
|
@ -2,4 +2,7 @@ module github.com/nealey/vail
|
|||
|
||||
go 1.12
|
||||
|
||||
require golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5
|
||||
require (
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5
|
||||
nhooyr.io/websocket v1.8.7 // indirect
|
||||
)
|
||||
|
|
39
go.sum
39
go.sum
|
@ -1,6 +1,45 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<!-- This appears as a little light that turns on when someone's sending -->
|
||||
<span class="tag" class="recv-lamp">
|
||||
<span class="tag recv-lamp">
|
||||
<output class="has-text-info" id="note"></output>
|
||||
<i class="mdi mdi-volume-off" id="muted"></i>
|
||||
</span>
|
||||
|
|
|
@ -137,6 +137,10 @@ class Sample {
|
|||
* A (mostly) virtual class defining a buzzer.
|
||||
*/
|
||||
class Buzzer {
|
||||
constructor() {
|
||||
this.connected = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal an error
|
||||
*/
|
||||
|
@ -175,6 +179,15 @@ class Buzzer {
|
|||
this.Buzz(tx, when)
|
||||
this.Silence(tx, when + duration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the "connectedness" indicator.
|
||||
*
|
||||
* @param {boolean} connected True if connected
|
||||
*/
|
||||
SetConnected(connected) {
|
||||
this.connected = connected
|
||||
}
|
||||
}
|
||||
|
||||
class AudioBuzzer extends Buzzer {
|
||||
|
@ -297,6 +310,17 @@ class LampBuzzer extends Buzzer {
|
|||
ms,
|
||||
)
|
||||
}
|
||||
|
||||
SetConnected(connected) {
|
||||
console.log(connected)
|
||||
for (let e of this.elements) {
|
||||
if (connected) {
|
||||
e.classList.add("connected")
|
||||
} else {
|
||||
e.classList.remove("connected")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MIDIBuzzer extends Buzzer {
|
||||
|
@ -415,7 +439,7 @@ class Collection {
|
|||
*
|
||||
* @param tx True if transmitting
|
||||
*/
|
||||
Silence(tx=False) {
|
||||
Silence(tx=false) {
|
||||
for (let b of this.collection) {
|
||||
b.Silence(tx)
|
||||
}
|
||||
|
@ -433,6 +457,19 @@ class Collection {
|
|||
b.BuzzDuration(tx, when, duration)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the "connected" status display.
|
||||
*
|
||||
* For example, turn the receive light to black if the repeater is not connected.
|
||||
*
|
||||
* @param {boolean} connected True if we are "connected"
|
||||
*/
|
||||
SetConnected(connected) {
|
||||
for (let b of this.collection) {
|
||||
b.SetConnected(connected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {AudioReady, Collection}
|
||||
|
|
|
@ -30,6 +30,7 @@ export class Vail {
|
|||
this.lagDurations = []
|
||||
this.sent = []
|
||||
this.wantConnected = true
|
||||
this.connected = false
|
||||
|
||||
this.wsUrl = new URL("chat", window.location)
|
||||
this.wsUrl.protocol = this.wsUrl.protocol.replace("http", "ws")
|
||||
|
@ -43,15 +44,23 @@ export class Vail {
|
|||
if (!this.wantConnected) {
|
||||
return
|
||||
}
|
||||
this.rx(0, 0, {connected: false})
|
||||
console.info("Attempting to reconnect", this.wsUrl.href)
|
||||
this.clockOffset = 0
|
||||
this.socket = new WebSocket(this.wsUrl)
|
||||
this.socket = new WebSocket(this.wsUrl, ["json.vail.woozle.org"])
|
||||
this.socket.addEventListener("message", e => this.wsMessage(e))
|
||||
this.socket.addEventListener(
|
||||
"open",
|
||||
msg => {
|
||||
this.connected = true
|
||||
this.rx(0, 0, {connected: true})
|
||||
}
|
||||
)
|
||||
this.socket.addEventListener(
|
||||
"close",
|
||||
() => {
|
||||
console.info("Repeater connection dropped.")
|
||||
setTimeout(() => this.reopen(), 5*Second)
|
||||
msg => {
|
||||
console.error("Repeater connection dropped:", msg.reason)
|
||||
setTimeout(() => this.reopen(), 2*Second)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -71,6 +80,12 @@ export class Vail {
|
|||
averageLag: this.lagDurations.reduce((a,b) => (a+b), 0) / this.lagDurations.length,
|
||||
clockOffset: this.clockOffset,
|
||||
clients: msg.Clients,
|
||||
connected: this.connected,
|
||||
}
|
||||
console.log(msg)
|
||||
if (typeof(msg) == "string") {
|
||||
console.error(msg)
|
||||
return
|
||||
}
|
||||
|
||||
// XXX: Why is this happening?
|
||||
|
@ -147,13 +162,14 @@ export class Vail {
|
|||
}
|
||||
|
||||
export class Null {
|
||||
constructor(rx) {
|
||||
constructor(rx, interval=3*Second) {
|
||||
this.rx = rx
|
||||
this.interval = setInterval(() => this.pulse(), 3 * Second)
|
||||
this.interval = setInterval(() => this.pulse(), interval)
|
||||
this.pulse()
|
||||
}
|
||||
|
||||
pulse() {
|
||||
this.rx(0, 0, {note: "local"})
|
||||
this.rx(0, 0, {note: "local", connected: false})
|
||||
}
|
||||
|
||||
Transmit(time, duration, squelch=true) {
|
||||
|
@ -164,38 +180,32 @@ export class Null {
|
|||
}
|
||||
}
|
||||
|
||||
export class Echo {
|
||||
export class Echo extends Null {
|
||||
constructor(rx, delay=0) {
|
||||
this.rx = rx
|
||||
super(rx)
|
||||
this.delay = delay
|
||||
this.Transmit(0, 0)
|
||||
}
|
||||
|
||||
Transmit(time, duration, squelch=true) {
|
||||
this.rx(time + this.delay, duration, {note: "local"})
|
||||
}
|
||||
|
||||
Close() {
|
||||
}
|
||||
}
|
||||
|
||||
export class Fortune {
|
||||
export class Fortune extends Null {
|
||||
/**
|
||||
*
|
||||
* @param rx Receive callback
|
||||
* @param {Keyer} keyer Keyer object
|
||||
*/
|
||||
constructor(rx, keyer) {
|
||||
this.rx = rx
|
||||
super(rx, 1*Minute)
|
||||
this.keyer = keyer
|
||||
|
||||
this.interval = setInterval(() => this.pulse(), 1 * Minute)
|
||||
this.pulse()
|
||||
}
|
||||
|
||||
pulse() {
|
||||
this.rx(0, 0, {note: "local"})
|
||||
if (this.keyer.Busy()) {
|
||||
super.pulse()
|
||||
if (!this.keyer || this.keyer.Busy()) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -203,12 +213,8 @@ export class Fortune {
|
|||
this.keyer.EnqueueAsciiString(`${fortune} \x04 `)
|
||||
}
|
||||
|
||||
Transmit(time, duration, squelch=true) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
Close() {
|
||||
this.keyer.Flush()
|
||||
clearInterval(this.interval)
|
||||
super.Close()
|
||||
}
|
||||
}
|
|
@ -16,7 +16,15 @@
|
|||
-webkit-user-select: none; /* 2022-04-26 Safari still needs this */
|
||||
}
|
||||
|
||||
.recv-lamp.rx {
|
||||
.tag.recv-lamp {
|
||||
background-color: #444;
|
||||
color: white;
|
||||
}
|
||||
.tag.recv-lamp.connected {
|
||||
background-color: #fec;
|
||||
}
|
||||
.tag.recv-lamp.rx,
|
||||
.tag.recv-lamp.connected.rx {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
|
|
|
@ -375,6 +375,9 @@ class VailClient {
|
|||
let longestRxDuration = this.rxDurations.reduce((a,b) => Math.max(a,b))
|
||||
let suggestedDelay = ((averageLag + longestRxDuration) * 1.2).toFixed(0)
|
||||
|
||||
if (stats.connected !== undefined) {
|
||||
this.outputs.SetConnected(stats.connected)
|
||||
}
|
||||
this.updateReading("#note", stats.note || "☁")
|
||||
this.updateReading("#lag-value", averageLag)
|
||||
this.updateReading("#longest-rx-value", longestRxDuration)
|
||||
|
|
Loading…
Reference in New Issue