2021-12-25 21:22:40 -03:00
|
|
|
package relayer
|
2021-01-13 23:46:06 -03:00
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
2022-07-11 16:21:47 -03:00
|
|
|
"fmt"
|
2021-01-13 23:46:06 -03:00
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
2022-01-02 09:00:14 -03:00
|
|
|
"github.com/fiatjaf/go-nostr"
|
2022-07-11 16:00:21 -03:00
|
|
|
"github.com/fiatjaf/go-nostr/nip11"
|
2021-01-13 23:46:06 -03:00
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// Time allowed to write a message to the peer.
|
|
|
|
writeWait = 10 * time.Second
|
|
|
|
|
|
|
|
// Time allowed to read the next pong message from the peer.
|
|
|
|
pongWait = 60 * time.Second
|
|
|
|
|
|
|
|
// Send pings to peer with this period. Must be less than pongWait.
|
|
|
|
pingPeriod = pongWait / 2
|
|
|
|
|
|
|
|
// Maximum message size allowed from peer.
|
|
|
|
maxMessageSize = 512000
|
|
|
|
)
|
|
|
|
|
|
|
|
var upgrader = websocket.Upgrader{
|
|
|
|
ReadBufferSize: 1024,
|
|
|
|
WriteBufferSize: 1024,
|
|
|
|
CheckOrigin: func(r *http.Request) bool { return true },
|
|
|
|
}
|
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
func handleWebsocket(relay Relay) func(http.ResponseWriter, *http.Request) {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
2022-07-24 16:55:59 -03:00
|
|
|
store := relay.Storage()
|
|
|
|
advancedQuerier, _ := store.(AdvancedQuerier)
|
|
|
|
advancedDeleter, _ := store.(AdvancedDeleter)
|
|
|
|
advancedSaver, _ := store.(AdvancedSaver)
|
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Warn().Err(err).Msg("failed to upgrade websocket")
|
|
|
|
return
|
|
|
|
}
|
2021-12-27 11:17:15 -03:00
|
|
|
ticker := time.NewTicker(pingPeriod)
|
2021-01-13 23:46:06 -03:00
|
|
|
|
2022-01-11 16:00:19 -03:00
|
|
|
ws := &WebSocket{conn: conn}
|
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
// reader
|
|
|
|
go func() {
|
|
|
|
defer func() {
|
2021-12-27 11:17:15 -03:00
|
|
|
ticker.Stop()
|
2021-12-25 21:22:40 -03:00
|
|
|
conn.Close()
|
|
|
|
}()
|
2021-01-13 23:46:06 -03:00
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
conn.SetReadLimit(maxMessageSize)
|
2021-01-13 23:46:06 -03:00
|
|
|
conn.SetReadDeadline(time.Now().Add(pongWait))
|
2021-12-25 21:22:40 -03:00
|
|
|
conn.SetPongHandler(func(string) error {
|
|
|
|
conn.SetReadDeadline(time.Now().Add(pongWait))
|
|
|
|
return nil
|
|
|
|
})
|
2021-01-13 23:46:06 -03:00
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
for {
|
|
|
|
typ, message, err := conn.ReadMessage()
|
|
|
|
if err != nil {
|
|
|
|
if websocket.IsUnexpectedCloseError(
|
|
|
|
err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
|
|
|
log.Warn().Err(err).Msg("unexpected close error")
|
2021-02-14 21:08:02 -03:00
|
|
|
}
|
2021-12-25 21:22:40 -03:00
|
|
|
break
|
2021-02-14 21:08:02 -03:00
|
|
|
}
|
2021-12-25 21:22:40 -03:00
|
|
|
|
|
|
|
if typ == websocket.PingMessage {
|
2022-01-11 16:00:19 -03:00
|
|
|
ws.WriteMessage(websocket.PongMessage, nil)
|
2021-12-25 21:22:40 -03:00
|
|
|
continue
|
2021-02-17 16:59:56 -03:00
|
|
|
}
|
2021-01-13 23:46:06 -03:00
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
go func(message []byte) {
|
2021-12-27 11:14:29 -03:00
|
|
|
var notice string
|
2021-12-25 21:22:40 -03:00
|
|
|
defer func() {
|
2021-12-27 11:14:29 -03:00
|
|
|
if notice != "" {
|
2022-01-11 16:00:19 -03:00
|
|
|
ws.WriteJSON([]interface{}{"NOTICE", notice})
|
2021-12-25 21:22:40 -03:00
|
|
|
}
|
|
|
|
}()
|
2021-02-14 21:08:02 -03:00
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
var request []json.RawMessage
|
2021-12-27 11:14:29 -03:00
|
|
|
if err := json.Unmarshal(message, &request); err != nil {
|
|
|
|
// stop silently
|
2021-12-25 21:22:40 -03:00
|
|
|
return
|
|
|
|
}
|
2021-12-27 11:14:29 -03:00
|
|
|
|
|
|
|
if len(request) < 2 {
|
|
|
|
notice = "request has less than 2 parameters"
|
2021-02-14 21:08:02 -03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
var typ string
|
|
|
|
json.Unmarshal(request[0], &typ)
|
|
|
|
|
|
|
|
switch typ {
|
|
|
|
case "EVENT":
|
|
|
|
// it's a new event
|
2022-01-02 09:00:14 -03:00
|
|
|
var evt nostr.Event
|
2021-12-27 11:14:29 -03:00
|
|
|
if err := json.Unmarshal(request[1], &evt); err != nil {
|
|
|
|
notice = "failed to decode event: " + err.Error()
|
2021-02-14 21:08:02 -03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
// check serialization
|
|
|
|
serialized := evt.Serialize()
|
2021-02-14 21:08:02 -03:00
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
// assign ID
|
|
|
|
hash := sha256.Sum256(serialized)
|
|
|
|
evt.ID = hex.EncodeToString(hash[:])
|
2021-02-14 21:08:02 -03:00
|
|
|
|
2022-07-24 16:55:59 -03:00
|
|
|
// block too many indexable tags
|
|
|
|
t := 0
|
|
|
|
for _, tag := range evt.Tags {
|
|
|
|
if len(tag[0]) == 1 {
|
|
|
|
t++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if t > 3 {
|
|
|
|
notice = "too many indexable tags"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
// check signature (requires the ID to be set)
|
|
|
|
if ok, err := evt.CheckSignature(); err != nil {
|
2021-12-27 11:14:29 -03:00
|
|
|
notice = "signature verification error"
|
2021-12-25 21:22:40 -03:00
|
|
|
return
|
|
|
|
} else if !ok {
|
2021-12-27 11:14:29 -03:00
|
|
|
notice = "signature invalid"
|
2021-12-25 21:22:40 -03:00
|
|
|
return
|
|
|
|
}
|
2021-01-13 23:46:06 -03:00
|
|
|
|
2022-07-11 16:21:47 -03:00
|
|
|
if evt.Kind == 5 {
|
|
|
|
// event deletion -- nip09
|
|
|
|
for _, tag := range evt.Tags {
|
|
|
|
if len(tag) >= 2 && tag[0] == "e" {
|
2022-07-24 16:55:59 -03:00
|
|
|
if advancedDeleter != nil {
|
|
|
|
advancedDeleter.BeforeDelete(tag[1], evt.PubKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := store.DeleteEvent(tag[1], evt.PubKey); err != nil {
|
2022-07-11 16:21:47 -03:00
|
|
|
notice = fmt.Sprintf("failed to delete: %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
2022-07-24 16:55:59 -03:00
|
|
|
|
|
|
|
if advancedDeleter != nil {
|
|
|
|
advancedDeleter.AfterDelete(tag[1], evt.PubKey)
|
|
|
|
}
|
2022-07-11 16:21:47 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-07-24 16:55:59 -03:00
|
|
|
if !relay.AcceptEvent(&evt) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-03 14:30:53 -03:00
|
|
|
if 20000 <= evt.Kind && evt.Kind < 30000 {
|
|
|
|
// do not store ephemeral events
|
|
|
|
} else {
|
|
|
|
if advancedSaver != nil {
|
|
|
|
advancedSaver.BeforeSave(&evt)
|
|
|
|
}
|
2022-07-24 16:55:59 -03:00
|
|
|
|
2022-11-03 14:30:53 -03:00
|
|
|
if err := store.SaveEvent(&evt); err != nil {
|
|
|
|
notice = err.Error()
|
|
|
|
return
|
|
|
|
}
|
2021-01-13 23:46:06 -03:00
|
|
|
|
2022-11-03 14:30:53 -03:00
|
|
|
if advancedSaver != nil {
|
|
|
|
advancedSaver.AfterSave(&evt)
|
|
|
|
}
|
2022-07-24 16:55:59 -03:00
|
|
|
}
|
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
notifyListeners(&evt)
|
2022-01-11 16:00:19 -03:00
|
|
|
break
|
2021-12-25 21:22:40 -03:00
|
|
|
case "REQ":
|
|
|
|
var id string
|
|
|
|
json.Unmarshal(request[1], &id)
|
|
|
|
if id == "" {
|
2021-12-27 11:14:29 -03:00
|
|
|
notice = "REQ has no <id>"
|
2021-12-25 21:22:40 -03:00
|
|
|
return
|
|
|
|
}
|
2021-01-13 23:46:06 -03:00
|
|
|
|
2022-02-13 08:37:38 -03:00
|
|
|
filters := make(nostr.Filters, len(request)-2)
|
2021-12-25 21:22:40 -03:00
|
|
|
for i, filterReq := range request[2:] {
|
2021-12-27 11:14:29 -03:00
|
|
|
if err := json.Unmarshal(
|
|
|
|
filterReq,
|
|
|
|
&filters[i],
|
|
|
|
); err != nil {
|
|
|
|
notice = "failed to decode filter"
|
2021-12-25 21:22:40 -03:00
|
|
|
return
|
|
|
|
}
|
2021-01-13 23:46:06 -03:00
|
|
|
|
2022-07-24 19:58:34 -03:00
|
|
|
filter := &filters[i]
|
|
|
|
|
2022-07-24 16:55:59 -03:00
|
|
|
if advancedQuerier != nil {
|
2022-07-24 19:58:34 -03:00
|
|
|
advancedQuerier.BeforeQuery(filter)
|
2022-07-24 16:55:59 -03:00
|
|
|
}
|
|
|
|
|
2022-07-24 19:58:34 -03:00
|
|
|
events, err := store.QueryEvents(filter)
|
2021-12-25 21:22:40 -03:00
|
|
|
if err == nil {
|
2022-07-24 19:58:34 -03:00
|
|
|
if advancedQuerier != nil {
|
|
|
|
advancedQuerier.AfterQuery(events, filter)
|
|
|
|
}
|
|
|
|
|
|
|
|
if filter.Limit > 0 && len(events) > filter.Limit {
|
|
|
|
events = events[0:filter.Limit]
|
|
|
|
}
|
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
for _, event := range events {
|
2022-01-11 16:00:19 -03:00
|
|
|
ws.WriteJSON([]interface{}{"EVENT", id, event})
|
2021-12-25 21:22:40 -03:00
|
|
|
}
|
2022-07-24 16:55:59 -03:00
|
|
|
|
2022-07-24 19:58:34 -03:00
|
|
|
ws.WriteJSON([]interface{}{"EOSE", id})
|
2022-07-24 16:55:59 -03:00
|
|
|
}
|
2021-12-25 21:22:40 -03:00
|
|
|
}
|
2021-01-13 23:46:06 -03:00
|
|
|
|
2022-01-11 16:00:19 -03:00
|
|
|
setListener(id, ws, filters)
|
|
|
|
break
|
2021-12-25 21:22:40 -03:00
|
|
|
case "CLOSE":
|
|
|
|
var id string
|
|
|
|
json.Unmarshal(request[0], &id)
|
|
|
|
if id == "" {
|
2021-12-27 11:14:29 -03:00
|
|
|
notice = "CLOSE has no <id>"
|
2021-12-25 21:22:40 -03:00
|
|
|
return
|
|
|
|
}
|
2021-01-13 23:46:06 -03:00
|
|
|
|
2022-01-11 16:00:19 -03:00
|
|
|
removeListener(ws, id)
|
|
|
|
break
|
2021-12-27 11:14:29 -03:00
|
|
|
default:
|
|
|
|
notice = "unknown message type " + typ
|
|
|
|
return
|
2021-12-25 21:22:40 -03:00
|
|
|
}
|
|
|
|
}(message)
|
|
|
|
}
|
|
|
|
}()
|
2021-01-13 23:46:06 -03:00
|
|
|
|
2021-12-25 21:22:40 -03:00
|
|
|
// writer
|
|
|
|
go func() {
|
|
|
|
defer func() {
|
|
|
|
ticker.Stop()
|
|
|
|
conn.Close()
|
|
|
|
}()
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
2022-01-11 16:00:19 -03:00
|
|
|
err := ws.WriteMessage(websocket.PingMessage, nil)
|
2021-12-25 21:22:40 -03:00
|
|
|
if err != nil {
|
|
|
|
log.Warn().Err(err).Msg("error writing ping, closing websocket")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2021-01-13 23:46:06 -03:00
|
|
|
}
|
|
|
|
}
|
2022-07-11 16:00:21 -03:00
|
|
|
|
|
|
|
func handleNIP11(relay Relay) func(http.ResponseWriter, *http.Request) {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
|
|
|
info := nip11.RelayInformationDocument{
|
|
|
|
Name: relay.Name(),
|
|
|
|
Description: "relay powered by the relayer framework",
|
|
|
|
PubKey: "~",
|
|
|
|
Contact: "~",
|
|
|
|
SupportedNIPs: []int{9, 15, 16},
|
|
|
|
Software: "https://github.com/fiatjaf/relayer",
|
|
|
|
Version: "~",
|
|
|
|
}
|
|
|
|
|
|
|
|
if ifmer, ok := relay.(Informationer); ok {
|
|
|
|
info = ifmer.GetNIP11InformationDocument()
|
|
|
|
}
|
|
|
|
|
|
|
|
json.NewEncoder(w).Encode(info)
|
|
|
|
}
|
|
|
|
}
|