change relaypool and subscription such that a Relay can have an independent existence.
This commit is contained in:
184
relay.go
Normal file
184
relay.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package nostr
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
s "github.com/SaveTheRbtz/generic-sync-map-go"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
PublishStatusSent Status = 0
|
||||
PublishStatusFailed Status = -1
|
||||
PublishStatusSucceeded Status = 1
|
||||
)
|
||||
|
||||
func (s Status) String() string {
|
||||
switch s {
|
||||
case PublishStatusSent:
|
||||
return "sent"
|
||||
case PublishStatusFailed:
|
||||
return "failed"
|
||||
case PublishStatusSucceeded:
|
||||
return "success"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
type Relay struct {
|
||||
URL string
|
||||
|
||||
Connection *Connection
|
||||
subscriptions s.MapOf[string, *Subscription]
|
||||
|
||||
Notices chan string
|
||||
}
|
||||
|
||||
func NewRelay(url string) *Relay {
|
||||
return &Relay{
|
||||
URL: NormalizeURL(url),
|
||||
subscriptions: s.MapOf[string, *Subscription]{},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Relay) Connect() error {
|
||||
if r.URL == "" {
|
||||
return fmt.Errorf("invalid relay URL '%s'", r.URL)
|
||||
}
|
||||
|
||||
socket, _, err := websocket.DefaultDialer.Dial(r.URL, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening websocket to '%s': %w", r.URL, err)
|
||||
}
|
||||
|
||||
conn := NewConnection(socket)
|
||||
|
||||
for {
|
||||
typ, message, err := conn.socket.ReadMessage()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read error: %w", err)
|
||||
}
|
||||
if typ == websocket.PingMessage {
|
||||
conn.WriteMessage(websocket.PongMessage, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
if typ != websocket.TextMessage || len(message) == 0 || message[0] != '[' {
|
||||
continue
|
||||
}
|
||||
|
||||
var jsonMessage []json.RawMessage
|
||||
err = json.Unmarshal(message, &jsonMessage)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(jsonMessage) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
var label string
|
||||
json.Unmarshal(jsonMessage[0], &label)
|
||||
|
||||
switch label {
|
||||
case "NOTICE":
|
||||
var content string
|
||||
json.Unmarshal(jsonMessage[1], &content)
|
||||
r.Notices <- content
|
||||
case "EVENT":
|
||||
if len(jsonMessage) < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
var channel string
|
||||
json.Unmarshal(jsonMessage[1], &channel)
|
||||
if subscription, ok := r.subscriptions.Load(channel); ok {
|
||||
var event Event
|
||||
json.Unmarshal(jsonMessage[2], &event)
|
||||
|
||||
// check signature of all received events, ignore invalid
|
||||
ok, err := event.CheckSignature()
|
||||
if !ok {
|
||||
errmsg := ""
|
||||
if err != nil {
|
||||
errmsg = err.Error()
|
||||
}
|
||||
log.Printf("bad signature: %s", errmsg)
|
||||
continue
|
||||
}
|
||||
|
||||
// check if the event matches the desired filter, ignore otherwise
|
||||
if !subscription.filters.Match(&event) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !subscription.stopped {
|
||||
subscription.Events <- event
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r Relay) Publish(event Event) chan Status {
|
||||
statusChan := make(chan Status)
|
||||
|
||||
go func() {
|
||||
err := r.Connection.WriteJSON([]interface{}{"EVENT", event})
|
||||
if err != nil {
|
||||
statusChan <- PublishStatusFailed
|
||||
close(statusChan)
|
||||
}
|
||||
statusChan <- PublishStatusSent
|
||||
|
||||
sub := r.Subscribe(Filters{Filter{IDs: []string{event.ID}}})
|
||||
for {
|
||||
select {
|
||||
case receivedEvent := <-sub.Events:
|
||||
if receivedEvent.ID == event.ID {
|
||||
statusChan <- PublishStatusSucceeded
|
||||
close(statusChan)
|
||||
break
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
close(statusChan)
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}()
|
||||
|
||||
return statusChan
|
||||
}
|
||||
|
||||
func (r *Relay) Subscribe(filters Filters) *Subscription {
|
||||
random := make([]byte, 7)
|
||||
rand.Read(random)
|
||||
id := hex.EncodeToString(random)
|
||||
return r.subscribe(id, filters)
|
||||
}
|
||||
|
||||
func (r *Relay) subscribe(id string, filters Filters) *Subscription {
|
||||
sub := Subscription{}
|
||||
sub.id = id
|
||||
|
||||
sub.Events = make(chan Event)
|
||||
r.subscriptions.Store(sub.id, &sub)
|
||||
|
||||
sub.Sub(filters)
|
||||
return &sub
|
||||
}
|
||||
|
||||
func (r *Relay) Close() error {
|
||||
return r.Connection.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user