mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-07-19 07:52:22 +02:00
basic event and relaypool.
This commit is contained in:
150
event/event.go
Normal file
150
event/event.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/fiatjaf/bip340"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KindSetMetadata uint8 = 0
|
||||||
|
KindTextNote uint8 = 1
|
||||||
|
KindRecommendServer uint8 = 2
|
||||||
|
KindContactList uint8 = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
ID string `json:"id"` // it's the hash of the serialized event
|
||||||
|
|
||||||
|
PubKey string `json:"pubkey"`
|
||||||
|
CreatedAt uint32 `json:"created_at"`
|
||||||
|
|
||||||
|
Kind uint8 `json:"kind"`
|
||||||
|
|
||||||
|
Tags Tags `json:"tags"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Sig string `json:"sig"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tags []Tag
|
||||||
|
|
||||||
|
func (t Tags) Scan(src interface{}) error {
|
||||||
|
var jtags []byte = make([]byte, 0)
|
||||||
|
|
||||||
|
switch v := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
jtags = v
|
||||||
|
case string:
|
||||||
|
jtags = []byte(v)
|
||||||
|
default:
|
||||||
|
return errors.New("couldn't scan tags, it's not a json string")
|
||||||
|
}
|
||||||
|
|
||||||
|
json.Unmarshal(jtags, t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tag []interface{}
|
||||||
|
|
||||||
|
// Serialize outputs a byte array that can be hashed/signed to identify/authenticate
|
||||||
|
func (evt *Event) Serialize() []byte {
|
||||||
|
// the serialization process is just putting everything into a JSON array
|
||||||
|
// so the order is kept
|
||||||
|
arr := make([]interface{}, 6)
|
||||||
|
|
||||||
|
// version: 0
|
||||||
|
arr[0] = 0
|
||||||
|
|
||||||
|
// pubkey
|
||||||
|
arr[1] = evt.PubKey
|
||||||
|
|
||||||
|
// created_at
|
||||||
|
arr[2] = int64(evt.CreatedAt)
|
||||||
|
|
||||||
|
// kind
|
||||||
|
arr[3] = int64(evt.Kind)
|
||||||
|
|
||||||
|
// tags
|
||||||
|
if evt.Tags != nil {
|
||||||
|
arr[4] = evt.Tags
|
||||||
|
} else {
|
||||||
|
arr[4] = make([]bool, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// content
|
||||||
|
arr[5] = evt.Content
|
||||||
|
|
||||||
|
serialized, _ := json.Marshal(arr)
|
||||||
|
return serialized
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckSignature checks if the signature is valid for the id
|
||||||
|
// (which is a hash of the serialized event content).
|
||||||
|
// returns an error if the signature itself is invalid.
|
||||||
|
func (evt Event) CheckSignature() (bool, error) {
|
||||||
|
// read and check pubkey
|
||||||
|
pubkeyb, err := hex.DecodeString(evt.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(pubkeyb) != 32 {
|
||||||
|
return false, fmt.Errorf("pubkey must be 32 bytes, not %d", len(pubkeyb))
|
||||||
|
}
|
||||||
|
|
||||||
|
// check tags
|
||||||
|
for _, tag := range evt.Tags {
|
||||||
|
for _, item := range tag {
|
||||||
|
switch item.(type) {
|
||||||
|
case string, int64, float64, int, bool:
|
||||||
|
// fine
|
||||||
|
default:
|
||||||
|
// not fine
|
||||||
|
return false, fmt.Errorf("tag contains an invalid value %v", item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := hex.DecodeString(evt.Sig)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("signature is invalid hex: %w", err)
|
||||||
|
}
|
||||||
|
if len(sig) != 64 {
|
||||||
|
return false, fmt.Errorf("signature must be 64 bytes, not %d", len(sig))
|
||||||
|
}
|
||||||
|
|
||||||
|
var p [32]byte
|
||||||
|
copy(p[:], pubkeyb)
|
||||||
|
|
||||||
|
var s [64]byte
|
||||||
|
copy(s[:], sig)
|
||||||
|
|
||||||
|
h := sha256.Sum256(evt.Serialize())
|
||||||
|
|
||||||
|
return bip340.Verify(p, h, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs an event with a given privateKey
|
||||||
|
func (evt Event) Sign(privateKey string) error {
|
||||||
|
h := sha256.Sum256(evt.Serialize())
|
||||||
|
s, _ := new(big.Int).SetString(privateKey, 16)
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
return errors.New("invalid private key " + privateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
aux := make([]byte, 32)
|
||||||
|
rand.Read(aux)
|
||||||
|
sig, err := bip340.Sign(s, h, aux)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.Sig = hex.EncodeToString(sig[:])
|
||||||
|
return nil
|
||||||
|
}
|
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module github.com/fiatjaf/go-nostr
|
||||||
|
|
||||||
|
go 1.15
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/fiatjaf/bip340 v1.0.0
|
||||||
|
github.com/gorilla/websocket v1.4.2
|
||||||
|
)
|
48
go.sum
Normal file
48
go.sum
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||||
|
github.com/btcsuite/btcd v0.0.0-20190109040709-5bda5314ca95 h1:bmv+LE3sbjb/M06u2DBi92imeKj7KnCUBOvyZYqI8d8=
|
||||||
|
github.com/btcsuite/btcd v0.0.0-20190109040709-5bda5314ca95/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0=
|
||||||
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||||
|
github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||||
|
github.com/btcsuite/btcutil v0.0.0-20190112041146-bf1e1be93589/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||||
|
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||||
|
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||||
|
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
|
||||||
|
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||||
|
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||||
|
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||||
|
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||||
|
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/fiatjaf/bip340 v1.0.0 h1:mpwbm+0KC9BXB/7/pnac4e4N1TiuppyEVXxtVAXj75k=
|
||||||
|
github.com/fiatjaf/bip340 v1.0.0/go.mod h1:MxAz+5FQUTW4OT2gnCBC6Our486wmqf72ykZIrh7+is=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||||
|
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||||
|
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
243
relaypool/relaypool.go
Normal file
243
relaypool/relaypool.go
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
package relaypool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/fiatjaf/go-nostr/event"
|
||||||
|
nostrutils "github.com/fiatjaf/go-nostr/utils"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RelayPool struct {
|
||||||
|
SecretKey *string
|
||||||
|
|
||||||
|
Relays map[string]Policy
|
||||||
|
websockets map[string]*websocket.Conn
|
||||||
|
|
||||||
|
Events chan *EventMessage
|
||||||
|
Notices chan *NoticeMessage
|
||||||
|
|
||||||
|
SubscribedKeys []string
|
||||||
|
SubscribedEvents []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Policy struct {
|
||||||
|
SimplePolicy
|
||||||
|
ReadSpecific map[string]SimplePolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
type SimplePolicy struct {
|
||||||
|
Read bool
|
||||||
|
Write bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventMessage struct {
|
||||||
|
Event event.Event
|
||||||
|
Context byte
|
||||||
|
Relay string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (em *EventMessage) UnmarshalJSON(b []byte) error {
|
||||||
|
var temp []json.RawMessage
|
||||||
|
if err := json.Unmarshal(b, temp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(temp) < 2 {
|
||||||
|
return errors.New("message is not an array of 2 or more")
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(temp[0], em.Event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var context string
|
||||||
|
if err := json.Unmarshal(temp[1], &context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
em.Context = context[0]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NoticeMessage struct {
|
||||||
|
Message string
|
||||||
|
Relay string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *NoticeMessage) UnmarshalJSON(b []byte) error {
|
||||||
|
var temp []json.RawMessage
|
||||||
|
if err := json.Unmarshal(b, temp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(temp) < 2 {
|
||||||
|
return errors.New("message is not an array of 2 or more")
|
||||||
|
}
|
||||||
|
var tag string
|
||||||
|
if err := json.Unmarshal(temp[0], &tag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if tag != "notice" {
|
||||||
|
return errors.New("tag is not 'notice'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(temp[1], &nm.Message); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new RelayPool with no relays in it
|
||||||
|
func New() *RelayPool {
|
||||||
|
return &RelayPool{
|
||||||
|
Relays: make(map[string]Policy),
|
||||||
|
websockets: make(map[string]*websocket.Conn),
|
||||||
|
|
||||||
|
Events: make(chan *EventMessage),
|
||||||
|
Notices: make(chan *NoticeMessage),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a new relay to the pool, if policy is nil, it will be a simple
|
||||||
|
// read+write policy.
|
||||||
|
func (r *RelayPool) Add(url string, policy *Policy) {
|
||||||
|
if policy == nil {
|
||||||
|
policy = &Policy{SimplePolicy: SimplePolicy{Read: true, Write: true}}
|
||||||
|
}
|
||||||
|
|
||||||
|
nm := nostrutils.NormalizeURL(url)
|
||||||
|
if nm == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Relays[nm] = *policy
|
||||||
|
conn, _, err := websocket.DefaultDialer.Dial(nostrutils.WebsocketURL(url), nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Remove(nm)
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
r.websockets[nm] = conn
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
for {
|
||||||
|
typ, message, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("read error: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if typ == websocket.PingMessage {
|
||||||
|
conn.WriteMessage(websocket.PongMessage, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if typ != websocket.TextMessage || len(message) == 0 || message[0] != '[' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var noticeMessage *NoticeMessage
|
||||||
|
var eventMessage *EventMessage
|
||||||
|
err = json.Unmarshal(message, eventMessage)
|
||||||
|
if err == nil {
|
||||||
|
eventMessage.Relay = nm
|
||||||
|
r.Events <- eventMessage
|
||||||
|
} else {
|
||||||
|
err = json.Unmarshal(message, noticeMessage)
|
||||||
|
if err == nil {
|
||||||
|
noticeMessage.Relay = nm
|
||||||
|
r.Notices <- noticeMessage
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes a relay from the pool.
|
||||||
|
func (r *RelayPool) Remove(url string) {
|
||||||
|
nm := nostrutils.NormalizeURL(url)
|
||||||
|
if conn, ok := r.websockets[nm]; ok {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
delete(r.Relays, nm)
|
||||||
|
delete(r.websockets, nm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RelayPool) SubKey(key string) {
|
||||||
|
for _, conn := range r.websockets {
|
||||||
|
conn.WriteMessage(websocket.TextMessage, []byte("sub-key:"+key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RelayPool) UnsubKey(key string) {
|
||||||
|
for _, conn := range r.websockets {
|
||||||
|
conn.WriteMessage(websocket.TextMessage, []byte("unsub-key:"+key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RelayPool) SubEvent(id string) {
|
||||||
|
for _, conn := range r.websockets {
|
||||||
|
conn.WriteMessage(websocket.TextMessage, []byte("sub-event:"+id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RelayPool) ReqFeed(opts map[string]interface{}) {
|
||||||
|
var sopts string
|
||||||
|
if opts == nil {
|
||||||
|
sopts = "{}"
|
||||||
|
} else {
|
||||||
|
jopts, _ := json.Marshal(opts)
|
||||||
|
sopts = string(jopts)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, conn := range r.websockets {
|
||||||
|
conn.WriteMessage(websocket.TextMessage, []byte("req-feed:"+sopts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RelayPool) ReqEvent(id string, opts map[string]interface{}) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
opts["id"] = id
|
||||||
|
|
||||||
|
jopts, _ := json.Marshal(opts)
|
||||||
|
sopts := string(jopts)
|
||||||
|
|
||||||
|
for _, conn := range r.websockets {
|
||||||
|
conn.WriteMessage(websocket.TextMessage, []byte("req-event:"+sopts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RelayPool) ReqKey(key string, opts map[string]interface{}) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
opts["key"] = key
|
||||||
|
|
||||||
|
jopts, _ := json.Marshal(opts)
|
||||||
|
sopts := string(jopts)
|
||||||
|
|
||||||
|
for _, conn := range r.websockets {
|
||||||
|
conn.WriteMessage(websocket.TextMessage, []byte("req-key:"+sopts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RelayPool) PublishEvent(evt *event.Event) error {
|
||||||
|
if r.SecretKey == nil && evt.Sig == "" {
|
||||||
|
return errors.New("PublishEvent needs either a signed event to publish or to have been configured with a .SecretKey.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if evt.Sig == "" {
|
||||||
|
evt.Sign(*r.SecretKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
jevt, _ := json.Marshal(evt)
|
||||||
|
|
||||||
|
for _, conn := range r.websockets {
|
||||||
|
conn.WriteMessage(websocket.TextMessage, jevt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
32
utils/utils.go
Normal file
32
utils/utils.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package nostrutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NormalizeURL(u string) string {
|
||||||
|
if !strings.HasPrefix(u, "http") && !strings.HasPrefix(u, "ws") {
|
||||||
|
u = "ws://" + u
|
||||||
|
}
|
||||||
|
p, err := url.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(p.RawPath, "/") {
|
||||||
|
p.RawPath = p.RawPath[0 : len(p.RawPath)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(p.RawPath, "/ws") {
|
||||||
|
p.RawPath = p.RawPath[0 : len(p.RawPath)-3]
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func WebsocketURL(u string) string {
|
||||||
|
p, _ := url.Parse(NormalizeURL(u))
|
||||||
|
p.RawPath += "/ws"
|
||||||
|
return p.String()
|
||||||
|
}
|
Reference in New Issue
Block a user