bootstrap backend nostr adapter
This commit is contained in:
parent
84d72140fb
commit
529c031c41
9 changed files with 294 additions and 138 deletions
|
@ -1,15 +1,18 @@
|
||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
. "forge.lightcrystal.systems/lightcrystal/underbbs/models"
|
. "forge.lightcrystal.systems/lightcrystal/underbbs/models"
|
||||||
nostr "github.com/nbd-wtf/go-nostr"
|
nostr "github.com/nbd-wtf/go-nostr"
|
||||||
"strings"
|
"strings"
|
||||||
"context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Adapter interface {
|
type Adapter interface {
|
||||||
Init(Settings, chan Message) error
|
Init(Settings, chan SocketData) error
|
||||||
Subscribe(string) error
|
Subscribe(string) []error
|
||||||
SendMessage(Message) error
|
SendMessage(Message) error
|
||||||
Follow(Author) error
|
Follow(Author) error
|
||||||
Unfollow(Author) error
|
Unfollow(Author) error
|
||||||
|
@ -18,13 +21,13 @@ type Adapter interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type NostrAdapter struct {
|
type NostrAdapter struct {
|
||||||
msgChan chan Message
|
msgChan chan SocketData
|
||||||
nickname string
|
nickname string
|
||||||
privkey string
|
privkey string
|
||||||
relays []*nostr.Relay
|
relays []*nostr.Relay
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *NostrAdapter) Init(settings Settings, msgChan chan Message) error {
|
func (self *NostrAdapter) Init(settings Settings, msgChan chan SocketData) error {
|
||||||
self.nickname = settings.Nickname
|
self.nickname = settings.Nickname
|
||||||
self.privkey = *settings.PrivKey
|
self.privkey = *settings.PrivKey
|
||||||
self.msgChan = msgChan
|
self.msgChan = msgChan
|
||||||
|
@ -34,7 +37,7 @@ func (self *NostrAdapter) Init(settings Settings, msgChan chan Message) error {
|
||||||
relays := strings.Split(*settings.Relays, ",")
|
relays := strings.Split(*settings.Relays, ",")
|
||||||
|
|
||||||
for _, r := range relays {
|
for _, r := range relays {
|
||||||
pr := nostr.RelayConnect(ctx, strings.Trim(r)
|
pr, _ := nostr.RelayConnect(ctx, strings.Trim(r, " "))
|
||||||
if pr == nil {
|
if pr == nil {
|
||||||
return errors.New("Relay connection could not be completed")
|
return errors.New("Relay connection could not be completed")
|
||||||
}
|
}
|
||||||
|
@ -43,7 +46,38 @@ func (self *NostrAdapter) Init(settings Settings, msgChan chan Message) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *NostrAdapter) Subscribe(filter string) error {
|
func (self *NostrAdapter) Subscribe(filter string) []error {
|
||||||
|
var filters nostr.Filters
|
||||||
|
err := json.Unmarshal([]byte(filter), &filters)
|
||||||
|
if err != nil {
|
||||||
|
return []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, 1)
|
||||||
|
|
||||||
|
for _, r := range self.relays {
|
||||||
|
|
||||||
|
sub, err := r.Subscribe(context.Background(), filters)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
} else {
|
||||||
|
go func() {
|
||||||
|
for ev := range sub.Events {
|
||||||
|
// try sequentially to encode into an underbbs object
|
||||||
|
// and send it to the appropriate channel
|
||||||
|
m, err := nostrEventToMsg(ev)
|
||||||
|
if err == nil {
|
||||||
|
self.msgChan <- m
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (self *NostrAdapter) SendMessage(msg Message) error {
|
func (self *NostrAdapter) SendMessage(msg Message) error {
|
||||||
|
@ -61,3 +95,24 @@ func (self *NostrAdapter) GetFollowers() error {
|
||||||
func (self *NostrAdapter) UpdateMetadata(data interface{}) error {
|
func (self *NostrAdapter) UpdateMetadata(data interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nostrEventToMsg(evt *nostr.Event) (Message, error) {
|
||||||
|
m := Message{
|
||||||
|
Protocol: "nostr",
|
||||||
|
}
|
||||||
|
if evt == nil {
|
||||||
|
return m, errors.New("no event")
|
||||||
|
}
|
||||||
|
switch evt.Kind {
|
||||||
|
case nostr.KindTextNote:
|
||||||
|
m.Uri = evt.ID
|
||||||
|
m.Author = Author{
|
||||||
|
Id: evt.PubKey,
|
||||||
|
}
|
||||||
|
m.Created = evt.CreatedAt.Time()
|
||||||
|
m.Content = evt.Content
|
||||||
|
return m, nil
|
||||||
|
default:
|
||||||
|
return m, errors.New(fmt.Sprintf("unsupported event kind: %d", evt.Kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
2
dist/index.html
vendored
2
dist/index.html
vendored
|
@ -22,7 +22,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<main>
|
<main>
|
||||||
<button id="connectbtn" onclick=">Connect</button>
|
<button id="connectbtn" onclick="connect()">Connect</button>
|
||||||
<div id="tabbar"></div>
|
<div id="tabbar"></div>
|
||||||
<div id="tabcontent"></div>
|
<div id="tabcontent"></div>
|
||||||
</main>
|
</main>
|
||||||
|
|
25
go.mod
25
go.mod
|
@ -1,3 +1,28 @@
|
||||||
module forge.lightcrystal.systems/lightcrystal/underbbs
|
module forge.lightcrystal.systems/lightcrystal/underbbs
|
||||||
|
|
||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/nbd-wtf/go-nostr v0.31.2
|
||||||
|
golang.org/x/time v0.5.0
|
||||||
|
hacklab.nilfm.cc/quartzgun v0.3.2
|
||||||
|
nhooyr.io/websocket v1.8.11
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||||
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
|
github.com/gobwas/ws v1.2.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
github.com/puzpuzpuz/xsync/v3 v3.0.2 // indirect
|
||||||
|
github.com/tidwall/gjson v1.14.4 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect
|
||||||
|
golang.org/x/sys v0.8.0 // indirect
|
||||||
|
)
|
||||||
|
|
43
go.sum
Normal file
43
go.sum
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM=
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||||
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
|
github.com/gobwas/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I=
|
||||||
|
github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/nbd-wtf/go-nostr v0.31.2 h1:PkHCAsSzG0Ce8tfF7LKyvZOjYtCdC+hPh5KfO/Rl1b4=
|
||||||
|
github.com/nbd-wtf/go-nostr v0.31.2/go.mod h1:vHKtHyLXDXzYBN0fi/9Y/Q5AD0p+hk8TQVKlldAi0gI=
|
||||||
|
github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew=
|
||||||
|
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
|
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||||
|
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
|
||||||
|
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||||
|
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||||
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
hacklab.nilfm.cc/quartzgun v0.3.2 h1:PmRFZ/IgsXVWyNn1iOsQ/ZeMnOQIQy0PzFakhXBdZoU=
|
||||||
|
hacklab.nilfm.cc/quartzgun v0.3.2/go.mod h1:P6qK4HB0CD/xfyRq8wdEGevAPFDDmv0KCaESSvv93LU=
|
||||||
|
nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0=
|
||||||
|
nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
|
@ -1,20 +1,17 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MessageFactory interface {
|
|
||||||
FromNostr(interface{}) Message
|
|
||||||
FromMasto(interface{}) Message
|
|
||||||
}
|
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
|
Uri string
|
||||||
Author Author
|
Author Author
|
||||||
Protocol string
|
Protocol string
|
||||||
Content string
|
Content string
|
||||||
Attachments []Attachment
|
Attachments []Attachment
|
||||||
ReplyTo Message
|
ReplyTo *Message
|
||||||
Replies []Message
|
Replies []Message
|
||||||
Mentions []Author
|
Mentions []Author
|
||||||
Created time.Time
|
Created time.Time
|
||||||
|
@ -26,6 +23,8 @@ type Author struct {
|
||||||
Name string
|
Name string
|
||||||
ProfileData interface{}
|
ProfileData interface{}
|
||||||
Messages []Message
|
Messages []Message
|
||||||
|
ProfileUri string
|
||||||
|
ProfilePic string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attachment struct {
|
type Attachment struct {
|
||||||
|
@ -33,3 +32,15 @@ type Attachment struct {
|
||||||
Data []uint8
|
Data []uint8
|
||||||
Desc string
|
Desc string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SocketData interface {
|
||||||
|
ToDatagram() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self Message) ToDatagram() []byte {
|
||||||
|
data, err := json.Marshal(self)
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
136
server/server.go
136
server/server.go
|
@ -1,35 +1,41 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"forge.lightcrystal.systems/lightcrystal/underbbs/models"
|
"context"
|
||||||
"hacklab.nilfm.cc/quartzgun/renderer"
|
|
||||||
"net/http"
|
|
||||||
"nhooyr.io/websocket"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"forge.lightcrystal.systems/lightcrystal/underbbs/adapter"
|
||||||
|
"forge.lightcrystal.systems/lightcrystal/underbbs/models"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
"hacklab.nilfm.cc/quartzgun/renderer"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"nhooyr.io/websocket"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type Subscriber struct {
|
type Subscriber struct {
|
||||||
msgs chan []byte
|
msgs chan []byte
|
||||||
closeSlow func()
|
closeSlow func()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type BBSServer struct {
|
type BBSServer struct {
|
||||||
subscribeMessageBuffer int
|
subscribeMessageBuffer int
|
||||||
publishLimiter *rate.Limiter
|
publishLimiter *rate.Limiter
|
||||||
logf func(f string, v ...interface{})
|
logf func(f string, v ...interface{})
|
||||||
serveMux http.ServeMux
|
serveMux http.ServeMux
|
||||||
subscribersLock sync.Mutex
|
subscribersLock sync.Mutex
|
||||||
subscribers map[*Subscriber][]models.Adapter
|
subscribers map[*Subscriber][]adapter.Adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *BBSServer {
|
func New() *BBSServer {
|
||||||
srvr := &BBSServer{
|
srvr := &BBSServer{
|
||||||
subscribeMessageBuffer: 16,
|
subscribeMessageBuffer: 16,
|
||||||
logf: log.Printf,
|
logf: log.Printf,
|
||||||
subscribers: make(map[*Subscriber][]model.Adapter),
|
subscribers: make(map[*Subscriber][]adapter.Adapter),
|
||||||
}
|
}
|
||||||
|
|
||||||
// frontend is here
|
// frontend is here
|
||||||
|
@ -42,14 +48,14 @@ func New() *BBSServer {
|
||||||
return srvr
|
return srvr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *GameTableServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (self *BBSServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
self.serveMux.ServeHTTP(w, r)
|
self.serveMux.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *GameTableServer) subscribeHandler(w http.ResponseWriter, r *http.Request) {
|
func (self *BBSServer) subscribeHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
|
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
|
||||||
Subprotocols: [],
|
Subprotocols: []string{},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
self.logf("%v", err)
|
self.logf("%v", err)
|
||||||
|
@ -63,31 +69,64 @@ func (self *GameTableServer) subscribeHandler(w http.ResponseWriter, r *http.Req
|
||||||
decoded, err := base64.StdEncoding.DecodeString(data)
|
decoded, err := base64.StdEncoding.DecodeString(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Close(3000, err.Error())
|
c.Close(3000, err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings := []models.Settings
|
settings := []models.Settings{}
|
||||||
// unmarshal the json into the settings array
|
// unmarshal the json into the settings array
|
||||||
err := json.Unmarshal([]byte(decoded), &settings)
|
err = json.Unmarshal([]byte(decoded), &settings)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
c.Close(3001, err.Erorr())
|
c.Close(3001, err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msgc := make(chan models.SocketData)
|
||||||
|
|
||||||
// attempt to initialize adapters
|
// attempt to initialize adapters
|
||||||
for i, s := range settings {
|
adapters := make([]adapter.Adapter, 0, 4)
|
||||||
switch(s.Protocol) {
|
for _, s := range settings {
|
||||||
|
var a adapter.Adapter
|
||||||
|
switch s.Protocol {
|
||||||
case "nostr":
|
case "nostr":
|
||||||
break;
|
a = &adapter.NostrAdapter{}
|
||||||
|
break
|
||||||
case "masto":
|
case "masto":
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
|
err := a.Init(s, msgc)
|
||||||
|
if err != nil {
|
||||||
|
c.Close(3002, err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
// keep reference to the adapters?
|
|
||||||
|
adapters = append(adapters, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep reference to the adapters so we can execute commands on them later
|
||||||
|
ctx := r.Context()
|
||||||
|
ctx = c.CloseRead(ctx)
|
||||||
|
|
||||||
|
s := &Subscriber{
|
||||||
|
msgs: make(chan []byte, self.subscribeMessageBuffer),
|
||||||
|
closeSlow: func() {
|
||||||
|
c.Close(websocket.StatusPolicyViolation, "connection too slow to keep up with messages")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addSubscriber(s, adapters)
|
||||||
|
|
||||||
|
defer self.deleteSubscriber(s)
|
||||||
|
|
||||||
defer c.Close(websocket.StatusInternalError, "")
|
defer c.Close(websocket.StatusInternalError, "")
|
||||||
|
|
||||||
err = self.subscribe(r, c)
|
// collect data from the channels injected into the adapters,
|
||||||
|
// and send it back to the client
|
||||||
|
// we block until we stop receiving data on all the input channels
|
||||||
|
|
||||||
|
listen([]chan models.SocketData{msgc}, s.msgs)
|
||||||
|
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -101,42 +140,23 @@ func (self *GameTableServer) subscribeHandler(w http.ResponseWriter, r *http.Req
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *GameTableServer) subscribe(r *http.Request, c *websocket.Conn) error {
|
func listen(channels []chan models.SocketData, out chan []byte) {
|
||||||
ctx := r.Context()
|
var wg sync.WaitGroup
|
||||||
ctx = c.CloseRead(ctx)
|
for _, ch := range channels {
|
||||||
|
wg.Add(1)
|
||||||
s := &Subscriber{
|
go func(ch <-chan models.SocketData) {
|
||||||
msgs: make(chan []byte, self.subscribeMessageBuffer),
|
defer wg.Done()
|
||||||
closeSlow: func() {
|
for data := range ch {
|
||||||
c.Close(websocket.StatusPolicyViolation, "connection too slow to keep up with messages")
|
out <- data.ToDatagram()
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
}(ch)
|
||||||
|
|
||||||
// validate the subprotocol
|
|
||||||
|
|
||||||
self.addSubscriber(s, tableKey)
|
|
||||||
|
|
||||||
defer self.deleteSubscriber(s)
|
|
||||||
select {
|
|
||||||
case s.msgs <- self.getCurrentState(tableKey):
|
|
||||||
default:
|
|
||||||
go s.closeSlow()
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case msg := <-s.msgs:
|
|
||||||
err := writeTimeout(ctx, time.Second*5, c, msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
|
close(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *GameTableServer) publishHandler(w http.ResponseWriter, r *http.Request) {
|
func (self *BBSServer) publishHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != "POST" {
|
if r.Method != "POST" {
|
||||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
|
@ -153,7 +173,7 @@ func (self *GameTableServer) publishHandler(w http.ResponseWriter, r *http.Reque
|
||||||
w.WriteHeader(http.StatusAccepted)
|
w.WriteHeader(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *GameTableServer) publish(msg []byte) {
|
func (self *BBSServer) publish(msg []byte) {
|
||||||
self.subscribersLock.Lock()
|
self.subscribersLock.Lock()
|
||||||
defer self.subscribersLock.Unlock()
|
defer self.subscribersLock.Unlock()
|
||||||
|
|
||||||
|
@ -163,19 +183,19 @@ func (self *GameTableServer) publish(msg []byte) {
|
||||||
|
|
||||||
// send any response from the adapter(s) back to the client
|
// send any response from the adapter(s) back to the client
|
||||||
|
|
||||||
for s, k := range self.subscribers {
|
/*for s, k := range self.subscribers {
|
||||||
// whatever logic to select which subscriber to send back to
|
// whatever logic to select which subscriber to send back to
|
||||||
}
|
}*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *GameTableServer) addSubscriber(s *Subscriber, k models.TableKey) {
|
func (self *BBSServer) addSubscriber(s *Subscriber, k []adapter.Adapter) {
|
||||||
self.subscribersLock.Lock()
|
self.subscribersLock.Lock()
|
||||||
self.subscribers[s] = k
|
self.subscribers[s] = k
|
||||||
self.subscribersLock.Unlock()
|
self.subscribersLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *GameTableServer) deleteSubscriber(s *Subscriber) {
|
func (self *BBSServer) deleteSubscriber(s *Subscriber) {
|
||||||
self.subscribersLock.Lock()
|
self.subscribersLock.Lock()
|
||||||
delete(self.subscribers, s)
|
delete(self.subscribers, s)
|
||||||
self.subscribersLock.Unlock()
|
self.subscribersLock.Unlock()
|
||||||
|
|
|
@ -212,9 +212,10 @@ function connect() {
|
||||||
}
|
}
|
||||||
subprotocol += "]";
|
subprotocol += "]";
|
||||||
subprotocol = btoa(subprotocol);
|
subprotocol = btoa(subprotocol);
|
||||||
// open the websocket connection with settings as subprotocol
|
|
||||||
|
|
||||||
_conn = new WebSocket("/subscribe", subprotocol);
|
// open the websocket connection with settings as subprotocol
|
||||||
|
const wsProto = location.protocol == "https:" ? "wss" : "ws";
|
||||||
|
_conn = new WebSocket(`${wsProto}://${location.host}/subscribe`, subprotocol);
|
||||||
_("websocket", _conn);
|
_("websocket", _conn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"forge.lightcrystal.systems/lightcrystal/underbbs/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -20,14 +21,14 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func run() error {
|
func run() error {
|
||||||
l, err := net.Listen("tcp", ":"+strconv.FormatInt(int64(cfg.Port), 10))
|
l, err := net.Listen("tcp", ":"+strconv.FormatInt(int64(9090), 10))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceHandler := // something
|
bbsServer := server.New()
|
||||||
s := &http.Server{
|
s := &http.Server{
|
||||||
Handler: serviceHandler,
|
Handler: bbsServer,
|
||||||
ReadTimeout: time.Second * 10,
|
ReadTimeout: time.Second * 10,
|
||||||
WriteTimeout: time.Second * 10,
|
WriteTimeout: time.Second * 10,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue