2022-11-06 21:15:42 -03:00
|
|
|
package nostr
|
|
|
|
|
|
|
|
import (
|
2022-12-17 19:39:10 +01:00
|
|
|
"context"
|
2022-11-06 21:15:42 -03:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2023-04-07 07:37:34 -03:00
|
|
|
"net"
|
2023-02-21 13:12:23 +01:00
|
|
|
"net/http"
|
2023-01-16 06:27:11 -05:00
|
|
|
"sync"
|
2022-11-06 21:15:42 -03:00
|
|
|
"time"
|
|
|
|
|
|
|
|
s "github.com/SaveTheRbtz/generic-sync-map-go"
|
2023-04-07 07:37:34 -03:00
|
|
|
"github.com/gobwas/ws"
|
|
|
|
"github.com/gobwas/ws/wsutil"
|
2022-11-06 21:15:42 -03:00
|
|
|
)
|
|
|
|
|
|
|
|
type Status int
|
|
|
|
|
|
|
|
const (
|
|
|
|
PublishStatusSent Status = 0
|
|
|
|
PublishStatusFailed Status = -1
|
|
|
|
PublishStatusSucceeded Status = 1
|
|
|
|
)
|
|
|
|
|
2023-03-17 16:43:48 -03:00
|
|
|
var subscriptionIdCounter = 0
|
|
|
|
|
2022-11-06 21:15:42 -03:00
|
|
|
func (s Status) String() string {
|
|
|
|
switch s {
|
|
|
|
case PublishStatusSent:
|
|
|
|
return "sent"
|
|
|
|
case PublishStatusFailed:
|
|
|
|
return "failed"
|
|
|
|
case PublishStatusSucceeded:
|
|
|
|
return "success"
|
|
|
|
}
|
|
|
|
|
|
|
|
return "unknown"
|
|
|
|
}
|
|
|
|
|
|
|
|
type Relay struct {
|
2023-02-21 13:12:23 +01:00
|
|
|
URL string
|
|
|
|
RequestHeader http.Header // e.g. for origin header
|
2022-11-06 21:15:42 -03:00
|
|
|
|
2023-04-07 07:37:34 -03:00
|
|
|
Connection net.Conn
|
2023-03-18 08:39:31 -03:00
|
|
|
subscriptions s.MapOf[string, *Subscription]
|
2022-11-06 21:15:42 -03:00
|
|
|
|
2023-03-16 14:15:16 -03:00
|
|
|
Challenges chan string // NIP-42 Challenges
|
|
|
|
Notices chan string
|
2023-03-18 08:39:31 -03:00
|
|
|
ConnectionError error
|
2023-03-16 14:15:16 -03:00
|
|
|
ConnectionContext context.Context // will be canceled when the connection closes
|
2022-11-15 07:53:50 -03:00
|
|
|
|
2023-03-16 14:27:33 -03:00
|
|
|
okCallbacks s.MapOf[string, func(bool, string)]
|
2023-03-31 22:09:01 -03:00
|
|
|
mutex sync.RWMutex
|
2023-03-14 17:07:22 -03:00
|
|
|
|
|
|
|
// custom things that aren't often used
|
|
|
|
//
|
|
|
|
AssumeValid bool // this will skip verifying signatures for events received from this relay
|
2022-11-06 21:15:42 -03:00
|
|
|
}
|
|
|
|
|
2023-01-16 06:27:11 -05:00
|
|
|
// RelayConnect returns a relay object connected to url.
|
|
|
|
// Once successfully connected, cancelling ctx has no effect.
|
|
|
|
// To close the connection, call r.Close().
|
2023-01-01 20:22:40 -03:00
|
|
|
func RelayConnect(ctx context.Context, url string) (*Relay, error) {
|
2022-11-15 07:53:50 -03:00
|
|
|
r := &Relay{URL: NormalizeURL(url)}
|
2023-01-01 20:22:40 -03:00
|
|
|
err := r.Connect(ctx)
|
2022-11-15 07:53:50 -03:00
|
|
|
return r, err
|
2022-11-06 21:15:42 -03:00
|
|
|
}
|
|
|
|
|
2022-11-26 09:25:31 -03:00
|
|
|
func (r *Relay) String() string {
|
|
|
|
return r.URL
|
|
|
|
}
|
|
|
|
|
2023-01-01 20:22:40 -03:00
|
|
|
// Connect tries to establish a websocket connection to r.URL.
|
2022-12-17 19:39:10 +01:00
|
|
|
// If the context expires before the connection is complete, an error is returned.
|
|
|
|
// Once successfully connected, context expiration has no effect: call r.Close
|
|
|
|
// to close the connection.
|
2023-01-01 20:22:40 -03:00
|
|
|
func (r *Relay) Connect(ctx context.Context) error {
|
2023-03-30 18:26:43 -03:00
|
|
|
connectionContext, cancel := context.WithCancel(ctx)
|
2023-03-18 08:39:31 -03:00
|
|
|
r.ConnectionContext = connectionContext
|
2023-03-16 14:15:16 -03:00
|
|
|
|
2022-11-06 21:15:42 -03:00
|
|
|
if r.URL == "" {
|
2023-03-16 14:15:16 -03:00
|
|
|
cancel()
|
2022-11-06 21:15:42 -03:00
|
|
|
return fmt.Errorf("invalid relay URL '%s'", r.URL)
|
|
|
|
}
|
|
|
|
|
2023-01-03 14:47:21 -03:00
|
|
|
if _, ok := ctx.Deadline(); !ok {
|
|
|
|
// if no timeout is set, force it to 7 seconds
|
|
|
|
var cancel context.CancelFunc
|
|
|
|
ctx, cancel = context.WithTimeout(ctx, 7*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
}
|
|
|
|
|
2023-04-07 07:37:34 -03:00
|
|
|
conn, _, _, err := ws.Dialer{
|
|
|
|
Header: ws.HandshakeHeaderHTTP(r.RequestHeader),
|
|
|
|
}.Dial(ctx, r.URL)
|
2022-11-06 21:15:42 -03:00
|
|
|
if err != nil {
|
2023-03-16 14:15:16 -03:00
|
|
|
cancel()
|
2022-11-06 21:15:42 -03:00
|
|
|
return fmt.Errorf("error opening websocket to '%s': %w", r.URL, err)
|
|
|
|
}
|
|
|
|
|
2023-01-16 06:27:11 -05:00
|
|
|
r.Challenges = make(chan string)
|
2022-11-15 07:53:50 -03:00
|
|
|
r.Notices = make(chan string)
|
|
|
|
|
2023-03-29 14:55:52 -03:00
|
|
|
// close these channels when the connection is dropped
|
|
|
|
go func() {
|
|
|
|
<-r.ConnectionContext.Done()
|
2023-03-31 22:09:01 -03:00
|
|
|
r.mutex.Lock()
|
2023-03-29 14:55:52 -03:00
|
|
|
close(r.Challenges)
|
|
|
|
close(r.Notices)
|
2023-03-31 22:09:01 -03:00
|
|
|
r.mutex.Unlock()
|
2023-03-29 14:55:52 -03:00
|
|
|
}()
|
|
|
|
|
2022-11-14 19:48:02 -03:00
|
|
|
r.Connection = conn
|
2022-11-06 21:15:42 -03:00
|
|
|
|
2023-03-25 14:59:35 -03:00
|
|
|
// ping every 29 seconds
|
|
|
|
go func() {
|
2023-03-29 14:55:52 -03:00
|
|
|
ticker := time.NewTicker(29 * time.Second)
|
|
|
|
defer ticker.Stop()
|
|
|
|
defer cancel()
|
2023-03-25 14:59:35 -03:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
2023-04-07 07:37:34 -03:00
|
|
|
err := wsutil.WriteClientMessage(conn, ws.OpPing, nil)
|
2023-03-25 14:59:35 -03:00
|
|
|
if err != nil {
|
2023-04-06 17:41:42 -03:00
|
|
|
log.Printf("error writing ping: %v", err)
|
2023-03-25 14:59:35 -03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// handling received messages
|
2022-11-14 19:48:02 -03:00
|
|
|
go func() {
|
2023-03-29 14:55:52 -03:00
|
|
|
defer cancel()
|
2023-04-07 07:37:34 -03:00
|
|
|
|
|
|
|
msgbuf := make([]wsutil.Message, 0, 1)
|
|
|
|
|
2022-11-14 19:48:02 -03:00
|
|
|
for {
|
2023-04-07 07:37:34 -03:00
|
|
|
message, err := wsutil.ReadServerMessage(conn, msgbuf)
|
2022-11-14 19:48:02 -03:00
|
|
|
if err != nil {
|
2023-03-18 08:39:31 -03:00
|
|
|
r.ConnectionError = err
|
2022-11-15 07:53:50 -03:00
|
|
|
break
|
2022-11-14 19:48:02 -03:00
|
|
|
}
|
2022-11-15 07:53:50 -03:00
|
|
|
|
2023-04-07 07:37:34 -03:00
|
|
|
if len(message) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if message[0].OpCode == ws.OpClose {
|
|
|
|
r.ConnectionError = fmt.Errorf("server closed connection")
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if message[0].OpCode == ws.OpPing {
|
|
|
|
wsutil.WriteClientMessage(conn, ws.OpPong, nil)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if message[0].OpCode != ws.OpText {
|
2022-11-14 19:48:02 -03:00
|
|
|
continue
|
|
|
|
}
|
2022-11-06 21:15:42 -03:00
|
|
|
|
2022-11-14 19:48:02 -03:00
|
|
|
var jsonMessage []json.RawMessage
|
2023-04-07 07:37:34 -03:00
|
|
|
err = json.Unmarshal(message[0].Payload, &jsonMessage)
|
|
|
|
|
|
|
|
msgbuf = message[:0]
|
|
|
|
|
2022-11-14 19:48:02 -03:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
2022-11-06 21:15:42 -03:00
|
|
|
|
2022-11-14 19:48:02 -03:00
|
|
|
if len(jsonMessage) < 2 {
|
2022-11-06 21:15:42 -03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-03-18 08:39:31 -03:00
|
|
|
var command string
|
|
|
|
json.Unmarshal(jsonMessage[0], &command)
|
2022-11-14 19:48:02 -03:00
|
|
|
|
2023-03-18 08:39:31 -03:00
|
|
|
switch command {
|
2022-11-14 19:48:02 -03:00
|
|
|
case "NOTICE":
|
|
|
|
var content string
|
|
|
|
json.Unmarshal(jsonMessage[1], &content)
|
2023-02-05 20:03:00 -03:00
|
|
|
go func() {
|
2023-03-31 22:09:01 -03:00
|
|
|
r.mutex.RLock()
|
2023-03-29 14:55:52 -03:00
|
|
|
if r.ConnectionContext.Err() == nil {
|
|
|
|
r.Notices <- content
|
|
|
|
}
|
2023-03-31 22:09:01 -03:00
|
|
|
r.mutex.RUnlock()
|
2023-02-05 20:03:00 -03:00
|
|
|
}()
|
2023-01-16 06:27:11 -05:00
|
|
|
case "AUTH":
|
|
|
|
var challenge string
|
|
|
|
json.Unmarshal(jsonMessage[1], &challenge)
|
|
|
|
go func() {
|
2023-03-31 22:09:01 -03:00
|
|
|
r.mutex.RLock()
|
2023-03-29 14:55:52 -03:00
|
|
|
if r.ConnectionContext.Err() == nil {
|
|
|
|
r.Challenges <- challenge
|
|
|
|
}
|
2023-03-31 22:09:01 -03:00
|
|
|
r.mutex.RUnlock()
|
2023-01-16 06:27:11 -05:00
|
|
|
}()
|
2022-11-14 19:48:02 -03:00
|
|
|
case "EVENT":
|
|
|
|
if len(jsonMessage) < 3 {
|
2022-11-06 21:15:42 -03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-03-18 08:39:31 -03:00
|
|
|
var subId string
|
|
|
|
json.Unmarshal(jsonMessage[1], &subId)
|
2023-03-18 15:09:58 -03:00
|
|
|
if subscription, ok := r.subscriptions.Load(subId); !ok {
|
|
|
|
log.Printf("no subscription with id '%s'\n", subId)
|
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
func() {
|
|
|
|
// decode event
|
|
|
|
var event Event
|
|
|
|
json.Unmarshal(jsonMessage[2], &event)
|
|
|
|
|
|
|
|
// check if the event matches the desired filter, ignore otherwise
|
|
|
|
if !subscription.Filters.Match(&event) {
|
2023-04-06 12:57:15 -03:00
|
|
|
log.Printf("filter does not match (%s): %v ~ %v\n", r.URL, subscription.Filters[0], event)
|
2023-03-18 15:09:58 -03:00
|
|
|
return
|
2022-11-14 19:48:02 -03:00
|
|
|
}
|
|
|
|
|
2023-01-15 07:19:00 -05:00
|
|
|
subscription.mutex.Lock()
|
|
|
|
defer subscription.mutex.Unlock()
|
2023-03-18 15:09:58 -03:00
|
|
|
if subscription.stopped {
|
2023-01-15 07:19:00 -05:00
|
|
|
return
|
|
|
|
}
|
2023-03-18 15:09:58 -03:00
|
|
|
|
|
|
|
// check signature, ignore invalid, except from trusted (AssumeValid) relays
|
|
|
|
if !r.AssumeValid {
|
|
|
|
if ok, err := event.CheckSignature(); !ok {
|
|
|
|
errmsg := ""
|
|
|
|
if err != nil {
|
|
|
|
errmsg = err.Error()
|
|
|
|
}
|
|
|
|
log.Printf("bad signature: %s\n", errmsg)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-26 09:04:27 -03:00
|
|
|
subscription.Events <- &event
|
2023-01-15 07:19:00 -05:00
|
|
|
}()
|
2022-11-14 19:48:02 -03:00
|
|
|
}
|
|
|
|
case "EOSE":
|
|
|
|
if len(jsonMessage) < 2 {
|
2022-11-06 21:15:42 -03:00
|
|
|
continue
|
|
|
|
}
|
2023-03-18 08:39:31 -03:00
|
|
|
var subId string
|
|
|
|
json.Unmarshal(jsonMessage[1], &subId)
|
|
|
|
if subscription, ok := r.subscriptions.Load(subId); ok {
|
2022-11-16 10:07:15 -03:00
|
|
|
subscription.emitEose.Do(func() {
|
|
|
|
subscription.EndOfStoredEvents <- struct{}{}
|
|
|
|
})
|
2022-11-06 21:15:42 -03:00
|
|
|
}
|
2022-11-14 19:48:02 -03:00
|
|
|
case "OK":
|
|
|
|
if len(jsonMessage) < 3 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var (
|
|
|
|
eventId string
|
|
|
|
ok bool
|
2023-03-16 14:27:33 -03:00
|
|
|
msg string
|
2022-11-14 19:48:02 -03:00
|
|
|
)
|
|
|
|
json.Unmarshal(jsonMessage[1], &eventId)
|
|
|
|
json.Unmarshal(jsonMessage[2], &ok)
|
|
|
|
|
2023-03-16 14:27:33 -03:00
|
|
|
if len(jsonMessage) > 3 {
|
|
|
|
json.Unmarshal(jsonMessage[3], &msg)
|
|
|
|
}
|
|
|
|
|
2023-01-01 20:22:40 -03:00
|
|
|
if okCallback, exist := r.okCallbacks.Load(eventId); exist {
|
2023-03-16 14:27:33 -03:00
|
|
|
okCallback(ok, msg)
|
2022-11-12 21:49:57 -03:00
|
|
|
}
|
|
|
|
}
|
2022-11-06 21:15:42 -03:00
|
|
|
}
|
2022-11-14 19:48:02 -03:00
|
|
|
}()
|
|
|
|
|
|
|
|
return nil
|
2022-11-06 21:15:42 -03:00
|
|
|
}
|
|
|
|
|
2023-01-16 06:27:11 -05:00
|
|
|
// Publish sends an "EVENT" command to the relay r as in NIP-01.
|
|
|
|
// Status can be: success, failed, or sent (no response from relay before ctx times out).
|
2023-03-16 14:27:33 -03:00
|
|
|
func (r *Relay) Publish(ctx context.Context, event Event) (Status, error) {
|
2023-02-05 13:44:26 +00:00
|
|
|
status := PublishStatusSent
|
2023-03-16 14:27:33 -03:00
|
|
|
var err error
|
2022-11-06 21:15:42 -03:00
|
|
|
|
2023-01-16 11:32:00 -05:00
|
|
|
// data races on status variable without this mutex
|
|
|
|
var mu sync.Mutex
|
|
|
|
|
2023-01-01 20:22:40 -03:00
|
|
|
if _, ok := ctx.Deadline(); !ok {
|
|
|
|
// if no timeout is set, force it to 3 seconds
|
|
|
|
var cancel context.CancelFunc
|
|
|
|
ctx, cancel = context.WithTimeout(ctx, 3*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
}
|
|
|
|
|
|
|
|
// make it cancellable so we can stop everything upon receiving an "OK"
|
|
|
|
var cancel context.CancelFunc
|
|
|
|
ctx, cancel = context.WithCancel(ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
// listen for an OK callback
|
2023-03-16 14:27:33 -03:00
|
|
|
okCallback := func(ok bool, msg string) {
|
2023-01-16 11:32:00 -05:00
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
2023-01-01 20:22:40 -03:00
|
|
|
if ok {
|
|
|
|
status = PublishStatusSucceeded
|
|
|
|
} else {
|
|
|
|
status = PublishStatusFailed
|
2023-03-16 14:27:33 -03:00
|
|
|
err = fmt.Errorf("msg: %s", msg)
|
2022-11-06 21:15:42 -03:00
|
|
|
}
|
2023-01-01 20:22:40 -03:00
|
|
|
cancel()
|
|
|
|
}
|
|
|
|
r.okCallbacks.Store(event.ID, okCallback)
|
|
|
|
defer r.okCallbacks.Delete(event.ID)
|
2022-11-06 21:15:42 -03:00
|
|
|
|
2023-01-01 20:22:40 -03:00
|
|
|
// publish event
|
2023-04-07 07:37:34 -03:00
|
|
|
j, _ := json.Marshal([]interface{}{"EVENT", event})
|
|
|
|
if err := wsutil.WriteClientText(r.Connection, j); err != nil {
|
2023-03-16 14:27:33 -03:00
|
|
|
return status, err
|
2023-01-01 20:22:40 -03:00
|
|
|
}
|
|
|
|
|
2023-04-06 16:21:25 -03:00
|
|
|
sub, err := r.Subscribe(ctx, Filters{Filter{IDs: []string{event.ID}}})
|
|
|
|
if err != nil {
|
|
|
|
return status, fmt.Errorf("failed to subscribe to just published event %s at %s: %w", event.ID, r.URL, err)
|
|
|
|
}
|
|
|
|
|
2023-01-01 20:22:40 -03:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case receivedEvent := <-sub.Events:
|
2023-03-16 15:53:24 -03:00
|
|
|
if receivedEvent == nil {
|
|
|
|
// channel is closed
|
|
|
|
return status, err
|
|
|
|
}
|
|
|
|
|
2023-01-01 20:22:40 -03:00
|
|
|
if receivedEvent.ID == event.ID {
|
|
|
|
// we got a success, so update our status and proceed to return
|
2023-01-16 11:32:00 -05:00
|
|
|
mu.Lock()
|
2023-01-01 20:22:40 -03:00
|
|
|
status = PublishStatusSucceeded
|
2023-02-05 16:00:48 +00:00
|
|
|
mu.Unlock()
|
2023-03-16 14:27:33 -03:00
|
|
|
return status, err
|
2022-11-06 21:15:42 -03:00
|
|
|
}
|
2023-01-01 20:22:40 -03:00
|
|
|
case <-ctx.Done():
|
|
|
|
// return status as it was
|
|
|
|
// will proceed to return status as it is
|
|
|
|
// e.g. if this happens because of the timeout then status will probably be "failed"
|
|
|
|
// but if it happens because okCallback was called then it might be "succeeded"
|
2023-01-16 11:32:00 -05:00
|
|
|
// do not return if okCallback is in process
|
2023-03-16 14:27:33 -03:00
|
|
|
return status, err
|
2023-03-17 16:21:04 -03:00
|
|
|
case <-r.ConnectionContext.Done():
|
|
|
|
// same as above, but when the relay loses connectivity entirely
|
|
|
|
return status, err
|
2022-11-06 21:15:42 -03:00
|
|
|
}
|
2023-01-01 20:22:40 -03:00
|
|
|
}
|
2022-11-06 21:15:42 -03:00
|
|
|
}
|
|
|
|
|
2023-01-16 06:27:11 -05:00
|
|
|
// Auth sends an "AUTH" command client -> relay as in NIP-42.
|
|
|
|
// Status can be: success, failed, or sent (no response from relay before ctx times out).
|
2023-03-16 14:27:33 -03:00
|
|
|
func (r *Relay) Auth(ctx context.Context, event Event) (Status, error) {
|
2023-01-16 06:27:11 -05:00
|
|
|
status := PublishStatusFailed
|
2023-03-16 14:27:33 -03:00
|
|
|
var err error
|
2023-01-16 06:27:11 -05:00
|
|
|
|
|
|
|
// data races on status variable without this mutex
|
|
|
|
var mu sync.Mutex
|
|
|
|
|
|
|
|
if _, ok := ctx.Deadline(); !ok {
|
|
|
|
// if no timeout is set, force it to 3 seconds
|
|
|
|
var cancel context.CancelFunc
|
|
|
|
ctx, cancel = context.WithTimeout(ctx, 3*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
}
|
|
|
|
|
|
|
|
// make it cancellable so we can stop everything upon receiving an "OK"
|
|
|
|
var cancel context.CancelFunc
|
|
|
|
ctx, cancel = context.WithCancel(ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
// listen for an OK callback
|
2023-03-16 14:27:33 -03:00
|
|
|
okCallback := func(ok bool, msg string) {
|
2023-01-16 06:27:11 -05:00
|
|
|
mu.Lock()
|
|
|
|
if ok {
|
|
|
|
status = PublishStatusSucceeded
|
|
|
|
} else {
|
|
|
|
status = PublishStatusFailed
|
2023-03-16 14:27:33 -03:00
|
|
|
err = fmt.Errorf("msg: %s", msg)
|
2023-01-16 06:27:11 -05:00
|
|
|
}
|
|
|
|
mu.Unlock()
|
|
|
|
cancel()
|
|
|
|
}
|
|
|
|
r.okCallbacks.Store(event.ID, okCallback)
|
|
|
|
defer r.okCallbacks.Delete(event.ID)
|
|
|
|
|
|
|
|
// send AUTH
|
2023-04-07 07:37:34 -03:00
|
|
|
j, _ := json.Marshal([]interface{}{"AUTH", event})
|
|
|
|
if err := wsutil.WriteClientText(r.Connection, j); err != nil {
|
2023-01-16 06:27:11 -05:00
|
|
|
// status will be "failed"
|
2023-03-16 14:27:33 -03:00
|
|
|
return status, err
|
2023-01-16 06:27:11 -05:00
|
|
|
}
|
2023-01-16 11:32:00 -05:00
|
|
|
// use mu.Lock() just in case the okCallback got called, extremely unlikely.
|
|
|
|
mu.Lock()
|
|
|
|
status = PublishStatusSent
|
|
|
|
mu.Unlock()
|
|
|
|
|
2023-01-16 06:27:11 -05:00
|
|
|
// the context either times out, and the status is "sent"
|
|
|
|
// or the okCallback is called and the status is set to "succeeded" or "failed"
|
|
|
|
// NIP-42 does not mandate an "OK" reply to an "AUTH" message
|
|
|
|
<-ctx.Done()
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
2023-03-16 14:27:33 -03:00
|
|
|
return status, err
|
2023-01-16 06:27:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Subscribe sends a "REQ" command to the relay r as in NIP-01.
|
|
|
|
// Events are returned through the channel sub.Events.
|
|
|
|
// The subscription is closed when context ctx is cancelled ("CLOSE" in NIP-01).
|
2023-04-06 16:21:25 -03:00
|
|
|
func (r *Relay) Subscribe(ctx context.Context, filters Filters) (*Subscription, error) {
|
2022-11-14 19:48:02 -03:00
|
|
|
if r.Connection == nil {
|
|
|
|
panic(fmt.Errorf("must call .Connect() first before calling .Subscribe()"))
|
|
|
|
}
|
|
|
|
|
2023-03-21 14:50:34 -03:00
|
|
|
sub := r.PrepareSubscription(ctx)
|
2022-11-19 14:00:29 -03:00
|
|
|
sub.Filters = filters
|
2023-01-01 20:22:40 -03:00
|
|
|
|
2023-04-06 16:21:25 -03:00
|
|
|
if err := sub.Fire(); err != nil {
|
|
|
|
return nil, fmt.Errorf("couldn't subscribe to %v at %s: %w", filters, r.URL, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return sub, nil
|
2022-11-19 14:00:29 -03:00
|
|
|
}
|
|
|
|
|
2023-04-06 16:21:25 -03:00
|
|
|
func (r *Relay) QuerySync(ctx context.Context, filter Filter) ([]*Event, error) {
|
|
|
|
sub, err := r.Subscribe(ctx, Filters{filter})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-01-01 20:22:40 -03:00
|
|
|
defer sub.Unsub()
|
|
|
|
|
|
|
|
if _, ok := ctx.Deadline(); !ok {
|
|
|
|
// if no timeout is set, force it to 3 seconds
|
|
|
|
var cancel context.CancelFunc
|
2023-03-18 08:39:31 -03:00
|
|
|
ctx, cancel = context.WithTimeout(ctx, 7*time.Second)
|
2023-01-01 20:22:40 -03:00
|
|
|
defer cancel()
|
|
|
|
}
|
|
|
|
|
2023-01-26 09:04:27 -03:00
|
|
|
var events []*Event
|
2022-11-26 19:32:03 -03:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case evt := <-sub.Events:
|
2023-03-16 15:53:24 -03:00
|
|
|
if evt == nil {
|
|
|
|
// channel is closed
|
2023-04-06 16:21:25 -03:00
|
|
|
return events, nil
|
2023-03-16 15:53:24 -03:00
|
|
|
}
|
2022-11-26 19:32:03 -03:00
|
|
|
events = append(events, evt)
|
|
|
|
case <-sub.EndOfStoredEvents:
|
2023-04-06 16:21:25 -03:00
|
|
|
return events, nil
|
2023-01-01 20:22:40 -03:00
|
|
|
case <-ctx.Done():
|
2023-04-06 16:21:25 -03:00
|
|
|
return events, nil
|
2022-11-26 19:32:03 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-21 14:50:34 -03:00
|
|
|
func (r *Relay) PrepareSubscription(ctx context.Context) *Subscription {
|
2023-03-18 15:09:49 -03:00
|
|
|
current := subscriptionIdCounter
|
2023-03-17 16:43:48 -03:00
|
|
|
subscriptionIdCounter++
|
2022-11-06 21:15:42 -03:00
|
|
|
|
2023-03-21 14:50:34 -03:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
|
2023-03-18 15:09:49 -03:00
|
|
|
return &Subscription{
|
2022-11-26 09:25:51 -03:00
|
|
|
Relay: r,
|
2023-03-21 14:50:34 -03:00
|
|
|
Context: ctx,
|
|
|
|
cancel: cancel,
|
2022-11-14 19:48:02 -03:00
|
|
|
conn: r.Connection,
|
2023-03-18 15:09:49 -03:00
|
|
|
counter: current,
|
2023-01-26 09:04:27 -03:00
|
|
|
Events: make(chan *Event),
|
2022-11-16 10:05:28 -03:00
|
|
|
EndOfStoredEvents: make(chan struct{}, 1),
|
2022-11-14 19:48:02 -03:00
|
|
|
}
|
2022-11-06 21:15:42 -03:00
|
|
|
}
|
|
|
|
|
2023-04-06 17:41:42 -03:00
|
|
|
func (r *Relay) Close(reason string) error {
|
2023-04-07 07:37:34 -03:00
|
|
|
return wsutil.WriteClientMessage(r.Connection, ws.OpClose, ws.CompiledCloseGoingAway)
|
2022-11-06 21:15:42 -03:00
|
|
|
}
|