Compare commits
10 Commits
10318a3443
...
43de47addb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43de47addb | ||
|
|
e17995d427 | ||
|
|
d87066c9b9 | ||
|
|
ca3730e508 | ||
|
|
3ed3592e52 | ||
|
|
0cb0d1ccb0 | ||
|
|
f1fdb0788a | ||
|
|
241959d1e3 | ||
|
|
061cf7f68f | ||
|
|
de4eff64d1 |
@@ -56,6 +56,11 @@ func (b *BleveBackend) Init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BleveBackend) CountEvents(nostr.Filter) (uint32, error) {
|
func (b *BleveBackend) CountEvents(filter nostr.Filter) (uint32, error) {
|
||||||
|
if filter.String() == "{}" {
|
||||||
|
count, err := b.index.DocCount()
|
||||||
|
return uint32(count), err
|
||||||
|
}
|
||||||
|
|
||||||
return 0, errors.New("not supported")
|
return 0, errors.New("not supported")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/eventstore"
|
"fiatjaf.com/nostr/eventstore"
|
||||||
"fiatjaf.com/nostr/eventstore/internal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *BleveBackend) ReplaceEvent(evt nostr.Event) error {
|
func (b *BleveBackend) ReplaceEvent(evt nostr.Event) error {
|
||||||
@@ -19,7 +18,7 @@ func (b *BleveBackend) ReplaceEvent(evt nostr.Event) error {
|
|||||||
|
|
||||||
shouldStore := true
|
shouldStore := true
|
||||||
for previous := range b.QueryEvents(filter, 1) {
|
for previous := range b.QueryEvents(filter, 1) {
|
||||||
if internal.IsOlder(previous, evt) {
|
if nostr.IsOlder(previous, evt) {
|
||||||
if err := b.DeleteEvent(previous.ID); err != nil {
|
if err := b.DeleteEvent(previous.ID); err != nil {
|
||||||
return fmt.Errorf("failed to delete event for replacing: %w", err)
|
return fmt.Errorf("failed to delete event for replacing: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"iter"
|
"iter"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/eventstore/internal"
|
|
||||||
"go.etcd.io/bbolt"
|
"go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ func (b *BoltBackend) ReplaceEvent(evt nostr.Event) error {
|
|||||||
|
|
||||||
shouldStore := true
|
shouldStore := true
|
||||||
for previous := range results {
|
for previous := range results {
|
||||||
if internal.IsOlder(previous, evt) {
|
if nostr.IsOlder(previous, evt) {
|
||||||
if err := b.delete(txn, previous.ID); err != nil {
|
if err := b.delete(txn, previous.ID); err != nil {
|
||||||
return fmt.Errorf("failed to delete event %s for replacing: %w", previous.ID, err)
|
return fmt.Errorf("failed to delete event %s for replacing: %w", previous.ID, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsOlder(previous, next nostr.Event) bool {
|
|
||||||
return previous.CreatedAt < next.CreatedAt ||
|
|
||||||
(previous.CreatedAt == next.CreatedAt && bytes.Compare(previous.ID[:], next.ID[:]) == 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ChooseNarrowestTag(filter nostr.Filter) (key string, values []string, goodness int) {
|
func ChooseNarrowestTag(filter nostr.Filter) (key string, values []string, goodness int) {
|
||||||
var tagKey string
|
var tagKey string
|
||||||
var tagValues []string
|
var tagValues []string
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"iter"
|
"iter"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/eventstore/internal"
|
|
||||||
"github.com/PowerDNS/lmdb-go/lmdb"
|
"github.com/PowerDNS/lmdb-go/lmdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ func (b *LMDBBackend) ReplaceEvent(evt nostr.Event) error {
|
|||||||
|
|
||||||
shouldStore := true
|
shouldStore := true
|
||||||
for previous := range results {
|
for previous := range results {
|
||||||
if internal.IsOlder(previous, evt) {
|
if nostr.IsOlder(previous, evt) {
|
||||||
if err := b.delete(txn, previous.ID); err != nil {
|
if err := b.delete(txn, previous.ID); err != nil {
|
||||||
return fmt.Errorf("failed to delete event %s for replacing: %w", previous.ID, err)
|
return fmt.Errorf("failed to delete event %s for replacing: %w", previous.ID, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/eventstore/internal"
|
|
||||||
"github.com/PowerDNS/lmdb-go/lmdb"
|
"github.com/PowerDNS/lmdb-go/lmdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,7 +71,7 @@ func (il *IndexingLayer) ReplaceEvent(evt nostr.Event) error {
|
|||||||
var acquiredFreeRangeFromDelete *position
|
var acquiredFreeRangeFromDelete *position
|
||||||
shouldStore := true
|
shouldStore := true
|
||||||
for previous := range results {
|
for previous := range results {
|
||||||
if internal.IsOlder(previous, evt) {
|
if nostr.IsOlder(previous, evt) {
|
||||||
if pos, shouldPurge, err := il.delete(mmmtxn, iltxn, previous.ID); err != nil {
|
if pos, shouldPurge, err := il.delete(mmmtxn, iltxn, previous.ID); err != nil {
|
||||||
return fmt.Errorf("failed to delete event %s for replacing: %w", previous.ID, err)
|
return fmt.Errorf("failed to delete event %s for replacing: %w", previous.ID, err)
|
||||||
} else if shouldPurge {
|
} else if shouldPurge {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/eventstore"
|
"fiatjaf.com/nostr/eventstore"
|
||||||
"fiatjaf.com/nostr/eventstore/internal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ eventstore.Store = (*SliceStore)(nil)
|
var _ eventstore.Store = (*SliceStore)(nil)
|
||||||
@@ -134,7 +133,7 @@ func (b *SliceStore) ReplaceEvent(evt nostr.Event) error {
|
|||||||
|
|
||||||
shouldStore := true
|
shouldStore := true
|
||||||
for previous := range b.QueryEvents(filter, 1) {
|
for previous := range b.QueryEvents(filter, 1) {
|
||||||
if internal.IsOlder(previous, evt) {
|
if nostr.IsOlder(previous, evt) {
|
||||||
if err := b.delete(previous.ID); err != nil {
|
if err := b.delete(previous.ID); err != nil {
|
||||||
return fmt.Errorf("failed to delete event for replacing: %w", err)
|
return fmt.Errorf("failed to delete event for replacing: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,14 +155,14 @@ func (rl *Relay) StartExpirationManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
go rl.expirationManager.start(rl.ctx)
|
go rl.expirationManager.start(rl.ctx)
|
||||||
rl.Info.AddSupportedNIP(40)
|
rl.Info.AddSupportedNIP("40")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Relay) DisableExpirationManager() {
|
func (rl *Relay) DisableExpirationManager() {
|
||||||
rl.expirationManager.stop()
|
rl.expirationManager.stop()
|
||||||
rl.expirationManager = nil
|
rl.expirationManager = nil
|
||||||
|
|
||||||
idx := slices.Index(rl.Info.SupportedNIPs, 40)
|
idx := slices.Index(rl.Info.SupportedNIPs, "40")
|
||||||
if idx != -1 {
|
if idx != -1 {
|
||||||
rl.Info.SupportedNIPs[idx] = rl.Info.SupportedNIPs[len(rl.Info.SupportedNIPs)-1]
|
rl.Info.SupportedNIPs[idx] = rl.Info.SupportedNIPs[len(rl.Info.SupportedNIPs)-1]
|
||||||
rl.Info.SupportedNIPs = rl.Info.SupportedNIPs[0 : len(rl.Info.SupportedNIPs)-1]
|
rl.Info.SupportedNIPs = rl.Info.SupportedNIPs[0 : len(rl.Info.SupportedNIPs)-1]
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func New(rl *khatru.Relay, repositoryDir string) *GraspServer {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.Info.AddSupportedNIP(34)
|
rl.Info.AddSupportedNIP("34")
|
||||||
rl.Info.SupportedGrasps = append(rl.Info.SupportedGrasps, "GRASP-01")
|
rl.Info.SupportedGrasps = append(rl.Info.SupportedGrasps, "GRASP-01")
|
||||||
|
|
||||||
base := rl.Router()
|
base := rl.Router()
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
package khatru
|
package khatru
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func isOlder(previous, next *nostr.Event) bool {
|
|
||||||
return previous.CreatedAt < next.CreatedAt ||
|
|
||||||
(previous.CreatedAt == next.CreatedAt && bytes.Compare(previous.ID[:], next.ID[:]) == 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var privateMasks = func() []net.IPNet {
|
var privateMasks = func() []net.IPNet {
|
||||||
privateCIDRs := []string{
|
privateCIDRs := []string{
|
||||||
"127.0.0.0/8",
|
"127.0.0.0/8",
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ func (rl *Relay) HandleNIP11(w http.ResponseWriter, r *http.Request) {
|
|||||||
info := *rl.Info
|
info := *rl.Info
|
||||||
|
|
||||||
if nil != rl.DeleteEvent {
|
if nil != rl.DeleteEvent {
|
||||||
info.AddSupportedNIP(9)
|
info.AddSupportedNIP("9")
|
||||||
}
|
}
|
||||||
if nil != rl.Count {
|
if nil != rl.Count {
|
||||||
info.AddSupportedNIP(45)
|
info.AddSupportedNIP("45")
|
||||||
}
|
}
|
||||||
if rl.Negentropy {
|
if rl.Negentropy {
|
||||||
info.AddSupportedNIP(77)
|
info.AddSupportedNIP("77")
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve relative icon and banner URLs against base URL
|
// resolve relative icon and banner URLs against base URL
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func NewRelay() *Relay {
|
|||||||
Info: &nip11.RelayInformationDocument{
|
Info: &nip11.RelayInformationDocument{
|
||||||
Software: "https://pkg.go.dev/fiatjaf.com/nostr/khatru",
|
Software: "https://pkg.go.dev/fiatjaf.com/nostr/khatru",
|
||||||
Version: "n/a",
|
Version: "n/a",
|
||||||
SupportedNIPs: []any{1, 11, 42, 70, 86},
|
SupportedNIPs: []string{"1", "11", "42", "70", "86"},
|
||||||
},
|
},
|
||||||
|
|
||||||
upgrader: websocket.Upgrader{
|
upgrader: websocket.Upgrader{
|
||||||
|
|||||||
@@ -9,30 +9,30 @@ import (
|
|||||||
|
|
||||||
func TestAddSupportedNIP(t *testing.T) {
|
func TestAddSupportedNIP(t *testing.T) {
|
||||||
info := RelayInformationDocument{}
|
info := RelayInformationDocument{}
|
||||||
info.AddSupportedNIP(12)
|
info.AddSupportedNIP("12")
|
||||||
info.AddSupportedNIP(12)
|
info.AddSupportedNIP("12")
|
||||||
info.AddSupportedNIP(13)
|
info.AddSupportedNIP("13")
|
||||||
info.AddSupportedNIP(1)
|
info.AddSupportedNIP("1")
|
||||||
info.AddSupportedNIP(12)
|
info.AddSupportedNIP("12")
|
||||||
info.AddSupportedNIP(44)
|
info.AddSupportedNIP("44")
|
||||||
info.AddSupportedNIP(2)
|
info.AddSupportedNIP("2")
|
||||||
info.AddSupportedNIP(13)
|
info.AddSupportedNIP("13")
|
||||||
info.AddSupportedNIP(2)
|
info.AddSupportedNIP("2")
|
||||||
info.AddSupportedNIP(13)
|
info.AddSupportedNIP("13")
|
||||||
info.AddSupportedNIP(0)
|
info.AddSupportedNIP("0")
|
||||||
info.AddSupportedNIP(17)
|
info.AddSupportedNIP("17")
|
||||||
info.AddSupportedNIP(19)
|
info.AddSupportedNIP("19")
|
||||||
info.AddSupportedNIP(1)
|
info.AddSupportedNIP("1")
|
||||||
info.AddSupportedNIP(18)
|
info.AddSupportedNIP("18")
|
||||||
|
|
||||||
assert.Contains(t, info.SupportedNIPs, 0, 1, 2, 12, 13, 17, 18, 19, 44)
|
assert.Contains(t, info.SupportedNIPs, "0", "1", "2", "12", "13", "17", "18", "19", "44")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddSupportedNIPs(t *testing.T) {
|
func TestAddSupportedNIPs(t *testing.T) {
|
||||||
info := RelayInformationDocument{}
|
info := RelayInformationDocument{}
|
||||||
info.AddSupportedNIPs([]int{0, 1, 2, 12, 13, 17, 18, 19, 44})
|
info.AddSupportedNIPs([]int{"0", "1", "2", "12", "13", "17", "18", "19", "44"})
|
||||||
|
|
||||||
assert.Contains(t, info.SupportedNIPs, 0, 1, 2, 12, 13, 17, 18, 19, 44)
|
assert.Contains(t, info.SupportedNIPs, "0", "1", "2", "12", "13", "17", "18", "19", "44")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetch(t *testing.T) {
|
func TestFetch(t *testing.T) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type RelayInformationDocument struct {
|
|||||||
PubKey *nostr.PubKey `json:"pubkey,omitempty"`
|
PubKey *nostr.PubKey `json:"pubkey,omitempty"`
|
||||||
Self *nostr.PubKey `json:"self,omitempty"`
|
Self *nostr.PubKey `json:"self,omitempty"`
|
||||||
Contact string `json:"contact,omitempty"`
|
Contact string `json:"contact,omitempty"`
|
||||||
SupportedNIPs []any `json:"supported_nips,omitempty"`
|
SupportedNIPs []string `json:"supported_nips,omitempty"`
|
||||||
Software string `json:"software,omitempty"`
|
Software string `json:"software,omitempty"`
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
|
|
||||||
@@ -33,16 +33,16 @@ type RelayInformationDocument struct {
|
|||||||
SupportedGrasps []string `json:"supported_grasps,omitempty"`
|
SupportedGrasps []string `json:"supported_grasps,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (info *RelayInformationDocument) AddSupportedNIP(number int) {
|
func (info *RelayInformationDocument) AddSupportedNIP(nip string) {
|
||||||
idx := slices.IndexFunc(info.SupportedNIPs, func(n any) bool { return n == number })
|
idx := slices.IndexFunc(info.SupportedNIPs, func(n string) bool { return n == nip })
|
||||||
if idx != -1 {
|
if idx != -1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
info.SupportedNIPs = append(info.SupportedNIPs, number)
|
info.SupportedNIPs = append(info.SupportedNIPs, nip)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (info *RelayInformationDocument) AddSupportedNIPs(numbers []int) {
|
func (info *RelayInformationDocument) AddSupportedNIPs(numbers []string) {
|
||||||
for _, n := range numbers {
|
for _, n := range numbers {
|
||||||
info.AddSupportedNIP(n)
|
info.AddSupportedNIP(n)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ func Decode(bech32string string) (prefix string, value any, err error) {
|
|||||||
if len(data) != 32 {
|
if len(data) != 32 {
|
||||||
return prefix, nil, fmt.Errorf("note should be 32 bytes (%d)", len(data))
|
return prefix, nil, fmt.Errorf("note should be 32 bytes (%d)", len(data))
|
||||||
}
|
}
|
||||||
return prefix, nostr.ID(data[0:32]), nil
|
return prefix, nostr.EventPointer{
|
||||||
|
ID: nostr.ID(data[0:32]),
|
||||||
|
}, nil
|
||||||
case "npub":
|
case "npub":
|
||||||
if len(data) != 32 {
|
if len(data) != 32 {
|
||||||
return prefix, nil, fmt.Errorf("npub should be 32 bytes (%d)", len(data))
|
return prefix, nil, fmt.Errorf("npub should be 32 bytes (%d)", len(data))
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package nip46
|
package nip46
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/nip44"
|
"fiatjaf.com/nostr/nip44"
|
||||||
@@ -10,6 +12,9 @@ import (
|
|||||||
type Session struct {
|
type Session struct {
|
||||||
PublicKey nostr.PubKey
|
PublicKey nostr.PubKey
|
||||||
ConversationKey [32]byte
|
ConversationKey [32]byte
|
||||||
|
|
||||||
|
duplicatesBuf [6]string
|
||||||
|
serial int
|
||||||
}
|
}
|
||||||
|
|
||||||
type RelayReadWrite struct {
|
type RelayReadWrite struct {
|
||||||
@@ -17,7 +22,9 @@ type RelayReadWrite struct {
|
|||||||
Write bool `json:"write"`
|
Write bool `json:"write"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Session) ParseRequest(event nostr.Event) (Request, error) {
|
var AlreadyHandled = errors.New("already handled this request")
|
||||||
|
|
||||||
|
func (s *Session) ParseRequest(event nostr.Event) (Request, error) {
|
||||||
var req Request
|
var req Request
|
||||||
|
|
||||||
plain, err := nip44.Decrypt(event.Content, s.ConversationKey)
|
plain, err := nip44.Decrypt(event.Content, s.ConversationKey)
|
||||||
@@ -26,6 +33,15 @@ func (s Session) ParseRequest(event nostr.Event) (Request, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(plain), &req)
|
err = json.Unmarshal([]byte(plain), &req)
|
||||||
|
|
||||||
|
// discard duplicates
|
||||||
|
if slices.Contains(s.duplicatesBuf[:], req.ID) {
|
||||||
|
return req, AlreadyHandled
|
||||||
|
}
|
||||||
|
|
||||||
|
s.duplicatesBuf[s.serial%len(s.duplicatesBuf)] = req.ID
|
||||||
|
s.serial++
|
||||||
|
|
||||||
return req, err
|
return req, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type BunkerClient struct {
|
type BunkerClient struct {
|
||||||
|
Relays []string
|
||||||
|
|
||||||
serial atomic.Uint64
|
serial atomic.Uint64
|
||||||
clientSecretKey [32]byte
|
clientSecretKey [32]byte
|
||||||
pool *nostr.Pool
|
pool *nostr.Pool
|
||||||
target nostr.PubKey
|
target nostr.PubKey
|
||||||
relays []string
|
|
||||||
conversationKey [32]byte // nip44
|
conversationKey [32]byte // nip44
|
||||||
listeners *xsync.MapOf[string, chan Response]
|
listeners *xsync.MapOf[string, chan Response]
|
||||||
idPrefix string
|
idPrefix string
|
||||||
@@ -116,23 +117,27 @@ func NewBunker(
|
|||||||
|
|
||||||
clientPublicKey := nostr.GetPublicKey(clientSecretKey)
|
clientPublicKey := nostr.GetPublicKey(clientSecretKey)
|
||||||
conversationKey, _ := nip44.GenerateConversationKey(targetPublicKey, clientSecretKey)
|
conversationKey, _ := nip44.GenerateConversationKey(targetPublicKey, clientSecretKey)
|
||||||
|
now := nostr.Now()
|
||||||
|
|
||||||
bunker := &BunkerClient{
|
bunker := &BunkerClient{
|
||||||
pool: pool,
|
pool: pool,
|
||||||
clientSecretKey: clientSecretKey,
|
clientSecretKey: clientSecretKey,
|
||||||
target: targetPublicKey,
|
target: targetPublicKey,
|
||||||
relays: relays,
|
Relays: relays,
|
||||||
conversationKey: conversationKey,
|
conversationKey: conversationKey,
|
||||||
listeners: xsync.NewMapOf[string, chan Response](),
|
listeners: xsync.NewMapOf[string, chan Response](),
|
||||||
onAuth: onAuth,
|
onAuth: onAuth,
|
||||||
idPrefix: "nl-" + strconv.Itoa(rand.Intn(65536)),
|
idPrefix: "nl-" + strconv.Itoa(rand.Intn(65536)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancellableCtx, cancel := context.WithCancel(ctx)
|
||||||
|
_ = cancel
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
events := pool.SubscribeMany(ctx, relays, nostr.Filter{
|
events := pool.SubscribeMany(cancellableCtx, relays, nostr.Filter{
|
||||||
Tags: nostr.TagMap{"p": []string{clientPublicKey.Hex()}},
|
Tags: nostr.TagMap{"p": []string{clientPublicKey.Hex()}},
|
||||||
Kinds: []nostr.Kind{nostr.KindNostrConnect},
|
Kinds: []nostr.Kind{nostr.KindNostrConnect},
|
||||||
Since: nostr.Now(),
|
Since: now,
|
||||||
LimitZero: true,
|
LimitZero: true,
|
||||||
}, nostr.SubscriptionOptions{
|
}, nostr.SubscriptionOptions{
|
||||||
Label: "bunker46client",
|
Label: "bunker46client",
|
||||||
@@ -167,6 +172,14 @@ func NewBunker(
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// attempt switch_relays once every 10 times
|
||||||
|
if now%10 == 0 {
|
||||||
|
if newRelays, _ := bunker.SwitchRelays(ctx); newRelays != nil {
|
||||||
|
cancel()
|
||||||
|
bunker = NewBunker(ctx, clientSecretKey, targetPublicKey, newRelays, pool, func(string) {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return bunker
|
return bunker
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,6 +191,15 @@ func (bunker *BunkerClient) Ping(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bunker *BunkerClient) SwitchRelays(ctx context.Context) ([]string, error) {
|
||||||
|
var res []string
|
||||||
|
_, err := bunker.RPC(ctx, "switch_relays", res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (bunker *BunkerClient) GetPublicKey(ctx context.Context) (nostr.PubKey, error) {
|
func (bunker *BunkerClient) GetPublicKey(ctx context.Context) (nostr.PubKey, error) {
|
||||||
if bunker.getPublicKeyResponse != nostr.ZeroPK {
|
if bunker.getPublicKeyResponse != nostr.ZeroPK {
|
||||||
return bunker.getPublicKeyResponse, nil
|
return bunker.getPublicKeyResponse, nil
|
||||||
@@ -283,7 +305,7 @@ func (bunker *BunkerClient) RPC(ctx context.Context, method string, params []str
|
|||||||
relayConnectionWorked := make(chan struct{})
|
relayConnectionWorked := make(chan struct{})
|
||||||
bunkerConnectionWorked := make(chan struct{})
|
bunkerConnectionWorked := make(chan struct{})
|
||||||
|
|
||||||
for _, url := range bunker.relays {
|
for _, url := range bunker.Relays {
|
||||||
go func(url string) {
|
go func(url string) {
|
||||||
relay, err := bunker.pool.EnsureRelay(url)
|
relay, err := bunker.pool.EnsureRelay(url)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ var _ Signer = (*DynamicSigner)(nil)
|
|||||||
|
|
||||||
type DynamicSigner struct {
|
type DynamicSigner struct {
|
||||||
// { [handlePubkey]: {[clientKey]: Session} }
|
// { [handlePubkey]: {[clientKey]: Session} }
|
||||||
sessions map[nostr.PubKey]map[nostr.PubKey]Session
|
sessions map[nostr.PubKey]map[nostr.PubKey]*Session
|
||||||
|
|
||||||
// used for switch_relays call
|
// used for switch_relays call
|
||||||
DefaultRelays []string
|
DefaultRelays []string
|
||||||
@@ -47,7 +47,7 @@ type DynamicSigner struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *DynamicSigner) Init() {
|
func (p *DynamicSigner) Init() {
|
||||||
p.sessions = make(map[nostr.PubKey]map[nostr.PubKey]Session)
|
p.sessions = make(map[nostr.PubKey]map[nostr.PubKey]*Session)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DynamicSigner) HandleRequest(ctx context.Context, event nostr.Event) (
|
func (p *DynamicSigner) HandleRequest(ctx context.Context, event nostr.Event) (
|
||||||
@@ -85,14 +85,14 @@ func (p *DynamicSigner) HandleRequest(ctx context.Context, event nostr.Event) (
|
|||||||
|
|
||||||
handlerSessions, exists := p.sessions[handlerPubkey]
|
handlerSessions, exists := p.sessions[handlerPubkey]
|
||||||
if !exists {
|
if !exists {
|
||||||
handlerSessions = make(map[nostr.PubKey]Session)
|
handlerSessions = make(map[nostr.PubKey]*Session)
|
||||||
p.sessions[handlerPubkey] = handlerSessions
|
p.sessions[handlerPubkey] = handlerSessions
|
||||||
}
|
}
|
||||||
|
|
||||||
session, exists := handlerSessions[event.PubKey]
|
session, exists := handlerSessions[event.PubKey]
|
||||||
if !exists {
|
if !exists {
|
||||||
// create session if it doesn't exist
|
// create session if it doesn't exist
|
||||||
session = Session{}
|
session = &Session{}
|
||||||
|
|
||||||
session.ConversationKey, err = nip44.GenerateConversationKey(event.PubKey, handlerSecret)
|
session.ConversationKey, err = nip44.GenerateConversationKey(event.PubKey, handlerSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,14 +5,11 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
mrand "math/rand"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/nip44"
|
"fiatjaf.com/nostr/nip44"
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var NoConnectionReceived = errors.New("relay connections ended without a bunker connection established")
|
var NoConnectionReceived = errors.New("relay connections ended without a bunker connection established")
|
||||||
@@ -112,16 +109,17 @@ func NewBunkerFromNostrConnect(
|
|||||||
if req.Result != "" {
|
if req.Result != "" {
|
||||||
if req.Result == secret {
|
if req.Result == secret {
|
||||||
// secret validation passed - connection established
|
// secret validation passed - connection established
|
||||||
return &BunkerClient{
|
cancellableCtx, cancel := context.WithCancel(ctx)
|
||||||
pool: pool,
|
_ = cancel
|
||||||
clientSecretKey: clientSecretKey,
|
bunker := NewBunker(cancellableCtx, clientSecretKey, targetPublicKey, relayURLs, pool, func(string) {})
|
||||||
target: targetPublicKey,
|
|
||||||
relays: relayURLs,
|
// attempt switch_relays
|
||||||
conversationKey: conversationKey,
|
if newRelays, _ := bunker.SwitchRelays(ctx); newRelays != nil {
|
||||||
listeners: xsync.NewMapOf[string, chan Response](),
|
cancel()
|
||||||
onAuth: func(string) {},
|
bunker = NewBunker(ctx, clientSecretKey, targetPublicKey, newRelays, pool, func(string) {})
|
||||||
idPrefix: "nl-" + strconv.Itoa(mrand.Intn(65536)),
|
}
|
||||||
}, nil
|
|
||||||
|
return bunker, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package nip46
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
@@ -14,8 +16,8 @@ import (
|
|||||||
var _ Signer = (*StaticKeySigner)(nil)
|
var _ Signer = (*StaticKeySigner)(nil)
|
||||||
|
|
||||||
type StaticKeySigner struct {
|
type StaticKeySigner struct {
|
||||||
secretKey [32]byte
|
secretKey nostr.SecretKey
|
||||||
sessions map[nostr.PubKey]Session
|
sessions map[nostr.PubKey]*Session
|
||||||
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
|
||||||
@@ -28,11 +30,11 @@ type StaticKeySigner struct {
|
|||||||
func NewStaticKeySigner(secretKey [32]byte) StaticKeySigner {
|
func NewStaticKeySigner(secretKey [32]byte) StaticKeySigner {
|
||||||
return StaticKeySigner{
|
return StaticKeySigner{
|
||||||
secretKey: secretKey,
|
secretKey: secretKey,
|
||||||
sessions: make(map[nostr.PubKey]Session),
|
sessions: make(map[nostr.PubKey]*Session),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *StaticKeySigner) getOrCreateSession(clientPubkey nostr.PubKey) (Session, error) {
|
func (p *StaticKeySigner) getOrCreateSession(clientPubkey nostr.PubKey) (*Session, error) {
|
||||||
p.Lock()
|
p.Lock()
|
||||||
defer p.Unlock()
|
defer p.Unlock()
|
||||||
|
|
||||||
@@ -43,21 +45,57 @@ func (p *StaticKeySigner) getOrCreateSession(clientPubkey nostr.PubKey) (Session
|
|||||||
|
|
||||||
ck, err := nip44.GenerateConversationKey(clientPubkey, p.secretKey)
|
ck, err := nip44.GenerateConversationKey(clientPubkey, p.secretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Session{}, fmt.Errorf("failed to compute shared secret: %w", err)
|
return nil, fmt.Errorf("failed to compute shared secret: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pubkey := nostr.GetPublicKey(p.secretKey)
|
session = &Session{
|
||||||
session = Session{
|
PublicKey: p.secretKey.Public(),
|
||||||
PublicKey: pubkey,
|
|
||||||
ConversationKey: ck,
|
ConversationKey: ck,
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to pool
|
// add to pool
|
||||||
p.sessions[pubkey] = session
|
p.sessions[clientPubkey] = session
|
||||||
|
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleNostrConnectURI works like HandleRequest, but takes a nostrconnect:// URI as input, as scanned/pasted
|
||||||
|
// by the user, produced by the client.
|
||||||
|
func (p *StaticKeySigner) HandleNostrConnectURI(ctx context.Context, uri *url.URL) (
|
||||||
|
resp Response,
|
||||||
|
eventResponse nostr.Event,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
clientPublicKey, err := nostr.PubKeyFromHex(uri.Host)
|
||||||
|
if err != nil {
|
||||||
|
return resp, eventResponse, err
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := uri.Query().Get("secret")
|
||||||
|
|
||||||
|
// pretend they started with a request
|
||||||
|
conversationKey, err := nip44.GenerateConversationKey(clientPublicKey, p.secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return resp, eventResponse, err
|
||||||
|
}
|
||||||
|
reqj, _ := json.Marshal(Request{
|
||||||
|
ID: "nostrconnect-" + strconv.FormatInt(int64(nostr.Now()), 10),
|
||||||
|
Method: "imagined-nostrconnect",
|
||||||
|
Params: []string{clientPublicKey.Hex(), secret},
|
||||||
|
})
|
||||||
|
ciphertext, err := nip44.Encrypt(string(reqj), conversationKey)
|
||||||
|
if err != nil {
|
||||||
|
return resp, eventResponse, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, resp, eventResponse, err = p.HandleRequest(ctx, nostr.Event{
|
||||||
|
PubKey: clientPublicKey,
|
||||||
|
Kind: nostr.KindNostrConnect,
|
||||||
|
Content: ciphertext,
|
||||||
|
})
|
||||||
|
return resp, eventResponse, err
|
||||||
|
}
|
||||||
|
|
||||||
func (p *StaticKeySigner) HandleRequest(_ context.Context, event nostr.Event) (
|
func (p *StaticKeySigner) HandleRequest(_ context.Context, event nostr.Event) (
|
||||||
req Request,
|
req Request,
|
||||||
resp Response,
|
resp Response,
|
||||||
@@ -85,6 +123,14 @@ func (p *StaticKeySigner) HandleRequest(_ context.Context, event nostr.Event) (
|
|||||||
var resultErr error
|
var resultErr error
|
||||||
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
|
case "imagined-nostrconnect":
|
||||||
|
// this is a fake request we pretend has existed, but was actually just we reading the nostrconnect:// uri
|
||||||
|
if len(req.Params) < 2 || req.Params[1] == "" {
|
||||||
|
resultErr = fmt.Errorf("needs a second argument 'secret'")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
result = req.Params[1]
|
||||||
|
harmless = true
|
||||||
case "connect":
|
case "connect":
|
||||||
if len(req.Params) >= 2 {
|
if len(req.Params) >= 2 {
|
||||||
secret = req.Params[1]
|
secret = req.Params[1]
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package blossom
|
package blossom
|
||||||
|
|
||||||
import "mime"
|
import (
|
||||||
|
"mime"
|
||||||
|
)
|
||||||
|
|
||||||
func GetExtension(mimetype string) string {
|
func GetExtension(mimetype string) string {
|
||||||
if mimetype == "" {
|
if mimetype == "" {
|
||||||
@@ -24,6 +26,9 @@ func GetExtension(mimetype string) string {
|
|||||||
|
|
||||||
exts, _ := mime.ExtensionsByType(mimetype)
|
exts, _ := mime.ExtensionsByType(mimetype)
|
||||||
if len(exts) > 0 {
|
if len(exts) > 0 {
|
||||||
|
if exts[0] == ".moov" {
|
||||||
|
return ".mov"
|
||||||
|
}
|
||||||
return exts[0]
|
return exts[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
relay.go
8
relay.go
@@ -190,9 +190,11 @@ func (r *Relay) handleMessage(message string) {
|
|||||||
}
|
}
|
||||||
r.challenge = *env.Challenge
|
r.challenge = *env.Challenge
|
||||||
if r.authHandler != nil {
|
if r.authHandler != nil {
|
||||||
r.Auth(r.Context(), func(ctx context.Context, evt *Event) error {
|
go func() {
|
||||||
return r.authHandler(ctx, r, evt)
|
r.Auth(r.Context(), func(ctx context.Context, evt *Event) error {
|
||||||
})
|
return r.authHandler(ctx, r, evt)
|
||||||
|
})
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
case *EventEnvelope:
|
case *EventEnvelope:
|
||||||
// we already have the subscription from the pre-check above, so we can just reuse it
|
// we already have the subscription from the pre-check above, so we can just reuse it
|
||||||
|
|||||||
@@ -31,14 +31,15 @@ func (db *HintDB) Save(pubkey nostr.PubKey, relay string, key hints.HintKey, ts
|
|||||||
ts = now
|
ts = now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.Lock()
|
||||||
|
defer db.Unlock()
|
||||||
|
|
||||||
relayIndex := slices.Index(db.RelayBySerial, relay)
|
relayIndex := slices.Index(db.RelayBySerial, relay)
|
||||||
if relayIndex == -1 {
|
if relayIndex == -1 {
|
||||||
relayIndex = len(db.RelayBySerial)
|
relayIndex = len(db.RelayBySerial)
|
||||||
db.RelayBySerial = append(db.RelayBySerial, relay)
|
db.RelayBySerial = append(db.RelayBySerial, relay)
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Lock()
|
|
||||||
defer db.Unlock()
|
|
||||||
// fmt.Println(" ", relay, "index", relayIndex, "--", "adding", hints.HintKey(key).String(), ts)
|
// fmt.Println(" ", relay, "index", relayIndex, "--", "adding", hints.HintKey(key).String(), ts)
|
||||||
|
|
||||||
entries, _ := db.OrderedRelaysByPubKey[pubkey]
|
entries, _ := db.OrderedRelaysByPubKey[pubkey]
|
||||||
|
|||||||
10
sdk/list.go
10
sdk/list.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
@@ -23,7 +24,7 @@ type TagItemWithValue[V comparable] interface {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
genericListMutexes = [60]sync.Mutex{}
|
genericListMutexes = [60]sync.Mutex{}
|
||||||
valueWasJustCached = [60]bool{}
|
valueWasJustCached = [60]atomic.Bool{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func fetchGenericList[V comparable, I TagItemWithValue[V]](
|
func fetchGenericList[V comparable, I TagItemWithValue[V]](
|
||||||
@@ -42,10 +43,9 @@ func fetchGenericList[V comparable, I TagItemWithValue[V]](
|
|||||||
lockIdx := (nostr.Kind(n) + actualKind) % 60
|
lockIdx := (nostr.Kind(n) + actualKind) % 60
|
||||||
genericListMutexes[lockIdx].Lock()
|
genericListMutexes[lockIdx].Lock()
|
||||||
|
|
||||||
if valueWasJustCached[lockIdx] {
|
if valueWasJustCached[lockIdx].CompareAndSwap(true, false) {
|
||||||
// this ensures the cache has had time to commit the values
|
// this ensures the cache has had time to commit the values
|
||||||
// so we don't repeat a fetch immediately after the other
|
// so we don't repeat a fetch immediately after the other
|
||||||
valueWasJustCached[lockIdx] = false
|
|
||||||
time.Sleep(time.Millisecond * 10)
|
time.Sleep(time.Millisecond * 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ func fetchGenericList[V comparable, I TagItemWithValue[V]](
|
|||||||
|
|
||||||
// and finally save this to cache
|
// and finally save this to cache
|
||||||
cache.SetWithTTL(pubkey, v, time.Hour*6)
|
cache.SetWithTTL(pubkey, v, time.Hour*6)
|
||||||
valueWasJustCached[lockIdx] = true
|
valueWasJustCached[lockIdx].Store(true)
|
||||||
|
|
||||||
return v, true
|
return v, true
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ func fetchGenericList[V comparable, I TagItemWithValue[V]](
|
|||||||
|
|
||||||
// save cache even if we didn't get anything
|
// save cache even if we didn't get anything
|
||||||
cache.SetWithTTL(pubkey, v, time.Hour*6)
|
cache.SetWithTTL(pubkey, v, time.Hour*6)
|
||||||
valueWasJustCached[lockIdx] = true
|
valueWasJustCached[lockIdx].Store(true)
|
||||||
|
|
||||||
return v, false
|
return v, false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,18 +12,22 @@ type EventRef struct{ nostr.Pointer }
|
|||||||
func (e EventRef) Value() string { return e.Pointer.AsTagReference() }
|
func (e EventRef) Value() string { return e.Pointer.AsTagReference() }
|
||||||
|
|
||||||
func (sys *System) FetchBookmarkList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, EventRef] {
|
func (sys *System) FetchBookmarkList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, EventRef] {
|
||||||
if sys.BookmarkListCache == nil {
|
sys.bookmarkListCacheOnce.Do(func() {
|
||||||
sys.BookmarkListCache = cache_memory.New[GenericList[string, EventRef]](1000)
|
if sys.BookmarkListCache == nil {
|
||||||
}
|
sys.BookmarkListCache = cache_memory.New[GenericList[string, EventRef]](1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ml, _ := fetchGenericList(sys, ctx, pubkey, 10003, kind_10003, parseEventRef, sys.BookmarkListCache)
|
ml, _ := fetchGenericList(sys, ctx, pubkey, 10003, kind_10003, parseEventRef, sys.BookmarkListCache)
|
||||||
return ml
|
return ml
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *System) FetchPinList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, EventRef] {
|
func (sys *System) FetchPinList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, EventRef] {
|
||||||
if sys.PinListCache == nil {
|
sys.pinListCacheOnce.Do(func() {
|
||||||
sys.PinListCache = cache_memory.New[GenericList[string, EventRef]](1000)
|
if sys.PinListCache == nil {
|
||||||
}
|
sys.PinListCache = cache_memory.New[GenericList[string, EventRef]](1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ml, _ := fetchGenericList(sys, ctx, pubkey, 10001, kind_10001, parseEventRef, sys.PinListCache)
|
ml, _ := fetchGenericList(sys, ctx, pubkey, 10001, kind_10001, parseEventRef, sys.PinListCache)
|
||||||
return ml
|
return ml
|
||||||
|
|||||||
@@ -18,27 +18,33 @@ type ProfileRef struct {
|
|||||||
func (f ProfileRef) Value() nostr.PubKey { return f.Pubkey }
|
func (f ProfileRef) Value() nostr.PubKey { return f.Pubkey }
|
||||||
|
|
||||||
func (sys *System) FetchFollowList(ctx context.Context, pubkey nostr.PubKey) GenericList[nostr.PubKey, ProfileRef] {
|
func (sys *System) FetchFollowList(ctx context.Context, pubkey nostr.PubKey) GenericList[nostr.PubKey, ProfileRef] {
|
||||||
if sys.FollowListCache == nil {
|
sys.followListCacheOnce.Do(func() {
|
||||||
sys.FollowListCache = cache_memory.New[GenericList[nostr.PubKey, ProfileRef]](1000)
|
if sys.FollowListCache == nil {
|
||||||
}
|
sys.FollowListCache = cache_memory.New[GenericList[nostr.PubKey, ProfileRef]](1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
fl, _ := fetchGenericList(sys, ctx, pubkey, 3, kind_3, parseProfileRef, sys.FollowListCache)
|
fl, _ := fetchGenericList(sys, ctx, pubkey, 3, kind_3, parseProfileRef, sys.FollowListCache)
|
||||||
return fl
|
return fl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *System) FetchMuteList(ctx context.Context, pubkey nostr.PubKey) GenericList[nostr.PubKey, ProfileRef] {
|
func (sys *System) FetchMuteList(ctx context.Context, pubkey nostr.PubKey) GenericList[nostr.PubKey, ProfileRef] {
|
||||||
if sys.MuteListCache == nil {
|
sys.muteListCacheOnce.Do(func() {
|
||||||
sys.MuteListCache = cache_memory.New[GenericList[nostr.PubKey, ProfileRef]](1000)
|
if sys.MuteListCache == nil {
|
||||||
}
|
sys.MuteListCache = cache_memory.New[GenericList[nostr.PubKey, ProfileRef]](1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ml, _ := fetchGenericList(sys, ctx, pubkey, 10000, kind_10000, parseProfileRef, sys.MuteListCache)
|
ml, _ := fetchGenericList(sys, ctx, pubkey, 10000, kind_10000, parseProfileRef, sys.MuteListCache)
|
||||||
return ml
|
return ml
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *System) FetchFollowSets(ctx context.Context, pubkey nostr.PubKey) GenericSets[nostr.PubKey, ProfileRef] {
|
func (sys *System) FetchFollowSets(ctx context.Context, pubkey nostr.PubKey) GenericSets[nostr.PubKey, ProfileRef] {
|
||||||
if sys.FollowSetsCache == nil {
|
sys.followSetsCacheOnce.Do(func() {
|
||||||
sys.FollowSetsCache = cache_memory.New[GenericSets[nostr.PubKey, ProfileRef]](1000)
|
if sys.FollowSetsCache == nil {
|
||||||
}
|
sys.FollowSetsCache = cache_memory.New[GenericSets[nostr.PubKey, ProfileRef]](1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ml, _ := fetchGenericSets(sys, ctx, pubkey, 30000, kind_30000, parseProfileRef, sys.FollowSetsCache)
|
ml, _ := fetchGenericSets(sys, ctx, pubkey, 30000, kind_30000, parseProfileRef, sys.FollowSetsCache)
|
||||||
return ml
|
return ml
|
||||||
|
|||||||
@@ -25,27 +25,33 @@ func (sys *System) FetchRelayList(ctx context.Context, pubkey nostr.PubKey) Gene
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sys *System) FetchBlockedRelayList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, RelayURL] {
|
func (sys *System) FetchBlockedRelayList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, RelayURL] {
|
||||||
if sys.BlockedRelayListCache == nil {
|
sys.blockedRelayListCacheOnce.Do(func() {
|
||||||
sys.BlockedRelayListCache = cache_memory.New[GenericList[string, RelayURL]](1000)
|
if sys.BlockedRelayListCache == nil {
|
||||||
}
|
sys.BlockedRelayListCache = cache_memory.New[GenericList[string, RelayURL]](1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ml, _ := fetchGenericList(sys, ctx, pubkey, 10006, kind_10006, parseRelayURL, sys.BlockedRelayListCache)
|
ml, _ := fetchGenericList(sys, ctx, pubkey, 10006, kind_10006, parseRelayURL, sys.BlockedRelayListCache)
|
||||||
return ml
|
return ml
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *System) FetchSearchRelayList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, RelayURL] {
|
func (sys *System) FetchSearchRelayList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, RelayURL] {
|
||||||
if sys.SearchRelayListCache == nil {
|
sys.searchRelayListCacheOnce.Do(func() {
|
||||||
sys.SearchRelayListCache = cache_memory.New[GenericList[string, RelayURL]](1000)
|
if sys.SearchRelayListCache == nil {
|
||||||
}
|
sys.SearchRelayListCache = cache_memory.New[GenericList[string, RelayURL]](1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ml, _ := fetchGenericList(sys, ctx, pubkey, 10007, kind_10007, parseRelayURL, sys.SearchRelayListCache)
|
ml, _ := fetchGenericList(sys, ctx, pubkey, 10007, kind_10007, parseRelayURL, sys.SearchRelayListCache)
|
||||||
return ml
|
return ml
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *System) FetchRelaySets(ctx context.Context, pubkey nostr.PubKey) GenericSets[string, RelayURL] {
|
func (sys *System) FetchRelaySets(ctx context.Context, pubkey nostr.PubKey) GenericSets[string, RelayURL] {
|
||||||
if sys.RelaySetsCache == nil {
|
sys.relaySetsCacheOnce.Do(func() {
|
||||||
sys.RelaySetsCache = cache_memory.New[GenericSets[string, RelayURL]](1000)
|
if sys.RelaySetsCache == nil {
|
||||||
}
|
sys.RelaySetsCache = cache_memory.New[GenericSets[string, RelayURL]](1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ml, _ := fetchGenericSets(sys, ctx, pubkey, 30002, kind_30002, parseRelayURL, sys.RelaySetsCache)
|
ml, _ := fetchGenericSets(sys, ctx, pubkey, 30002, kind_30002, parseRelayURL, sys.RelaySetsCache)
|
||||||
return ml
|
return ml
|
||||||
|
|||||||
@@ -12,18 +12,22 @@ type Topic string
|
|||||||
func (r Topic) Value() string { return string(r) }
|
func (r Topic) Value() string { return string(r) }
|
||||||
|
|
||||||
func (sys *System) FetchTopicList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, Topic] {
|
func (sys *System) FetchTopicList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, Topic] {
|
||||||
if sys.TopicListCache == nil {
|
sys.topicListCacheOnce.Do(func() {
|
||||||
sys.TopicListCache = cache_memory.New[GenericList[string, Topic]](1000)
|
if sys.TopicListCache == nil {
|
||||||
}
|
sys.TopicListCache = cache_memory.New[GenericList[string, Topic]](1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ml, _ := fetchGenericList(sys, ctx, pubkey, 10015, kind_10015, parseTopicString, sys.TopicListCache)
|
ml, _ := fetchGenericList(sys, ctx, pubkey, 10015, kind_10015, parseTopicString, sys.TopicListCache)
|
||||||
return ml
|
return ml
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *System) FetchTopicSets(ctx context.Context, pubkey nostr.PubKey) GenericSets[string, Topic] {
|
func (sys *System) FetchTopicSets(ctx context.Context, pubkey nostr.PubKey) GenericSets[string, Topic] {
|
||||||
if sys.TopicSetsCache == nil {
|
sys.topicSetsCacheOnce.Do(func() {
|
||||||
sys.TopicSetsCache = cache_memory.New[GenericSets[string, Topic]](1000)
|
if sys.TopicSetsCache == nil {
|
||||||
}
|
sys.TopicSetsCache = cache_memory.New[GenericSets[string, Topic]](1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ml, _ := fetchGenericSets(sys, ctx, pubkey, 30015, kind_30015, parseTopicString, sys.TopicSetsCache)
|
ml, _ := fetchGenericSets(sys, ctx, pubkey, 30015, kind_30015, parseTopicString, sys.TopicSetsCache)
|
||||||
return ml
|
return ml
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package sdk
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
var outboxShortTermCache = [256]ostcEntry{}
|
var outboxShortTermCache = [256]atomic.Pointer[ostcEntry]{}
|
||||||
|
|
||||||
type ostcEntry struct {
|
type ostcEntry struct {
|
||||||
pubkey nostr.PubKey
|
pubkey nostr.PubKey
|
||||||
@@ -22,7 +23,9 @@ type ostcEntry struct {
|
|||||||
func (sys *System) FetchOutboxRelays(ctx context.Context, pubkey nostr.PubKey, n int) []string {
|
func (sys *System) FetchOutboxRelays(ctx context.Context, pubkey nostr.PubKey, n int) []string {
|
||||||
ostcIndex := pubkey[7]
|
ostcIndex := pubkey[7]
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if entry := outboxShortTermCache[ostcIndex]; entry.pubkey == pubkey && entry.when.Add(time.Minute*2).After(now) {
|
if entry := outboxShortTermCache[ostcIndex].Load(); entry != nil &&
|
||||||
|
entry.pubkey == pubkey &&
|
||||||
|
entry.when.Add(time.Minute*2).After(now) {
|
||||||
return entry.relays
|
return entry.relays
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +41,7 @@ func (sys *System) FetchOutboxRelays(ctx context.Context, pubkey nostr.PubKey, n
|
|||||||
// we will have a reference to a thing that the caller to this function may change at will)
|
// we will have a reference to a thing that the caller to this function may change at will)
|
||||||
relaysCopy := make([]string, len(relays))
|
relaysCopy := make([]string, len(relays))
|
||||||
copy(relaysCopy, relays)
|
copy(relaysCopy, relays)
|
||||||
outboxShortTermCache[ostcIndex] = ostcEntry{pubkey, relaysCopy, now}
|
outboxShortTermCache[ostcIndex].Store(&ostcEntry{pubkey, relaysCopy, now})
|
||||||
|
|
||||||
if len(relays) > n {
|
if len(relays) > n {
|
||||||
relays = relays[0:n]
|
relays = relays[0:n]
|
||||||
|
|||||||
@@ -31,10 +31,9 @@ func fetchGenericSets[V comparable, I TagItemWithValue[V]](
|
|||||||
lockIdx := (nostr.Kind(n) + actualKind) % 60
|
lockIdx := (nostr.Kind(n) + actualKind) % 60
|
||||||
genericListMutexes[lockIdx].Lock()
|
genericListMutexes[lockIdx].Lock()
|
||||||
|
|
||||||
if valueWasJustCached[lockIdx] {
|
if valueWasJustCached[lockIdx].CompareAndSwap(true, false) {
|
||||||
// this ensures the cache has had time to commit the values
|
// this ensures the cache has had time to commit the values
|
||||||
// so we don't repeat a fetch immediately after the other
|
// so we don't repeat a fetch immediately after the other
|
||||||
valueWasJustCached[lockIdx] = false
|
|
||||||
time.Sleep(time.Millisecond * 10)
|
time.Sleep(time.Millisecond * 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +73,7 @@ func fetchGenericSets[V comparable, I TagItemWithValue[V]](
|
|||||||
|
|
||||||
// and finally save this to cache
|
// and finally save this to cache
|
||||||
cache.SetWithTTL(pubkey, v, time.Hour*6)
|
cache.SetWithTTL(pubkey, v, time.Hour*6)
|
||||||
valueWasJustCached[lockIdx] = true
|
valueWasJustCached[lockIdx].Store(true)
|
||||||
|
|
||||||
return v, true
|
return v, true
|
||||||
}
|
}
|
||||||
@@ -93,7 +92,7 @@ func fetchGenericSets[V comparable, I TagItemWithValue[V]](
|
|||||||
|
|
||||||
// save cache even if we didn't get anything
|
// save cache even if we didn't get anything
|
||||||
cache.SetWithTTL(pubkey, v, time.Hour*6)
|
cache.SetWithTTL(pubkey, v, time.Hour*6)
|
||||||
valueWasJustCached[lockIdx] = true
|
valueWasJustCached[lockIdx].Store(true)
|
||||||
|
|
||||||
return v, false
|
return v, false
|
||||||
}
|
}
|
||||||
|
|||||||
121
sdk/system.go
121
sdk/system.go
@@ -1,7 +1,9 @@
|
|||||||
package sdk
|
package sdk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand/v2"
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/eventstore"
|
"fiatjaf.com/nostr/eventstore"
|
||||||
@@ -28,32 +30,47 @@ import (
|
|||||||
// default they're set to in-memory stores, but ideally persisteable
|
// default they're set to in-memory stores, but ideally persisteable
|
||||||
// implementations should be given (some alternatives are provided in subpackages).
|
// implementations should be given (some alternatives are provided in subpackages).
|
||||||
type System struct {
|
type System struct {
|
||||||
KVStore kvstore.KVStore
|
KVStore kvstore.KVStore
|
||||||
MetadataCache cache.Cache32[ProfileMetadata]
|
metadataCacheOnce sync.Once
|
||||||
RelayListCache cache.Cache32[GenericList[string, Relay]]
|
MetadataCache cache.Cache32[ProfileMetadata]
|
||||||
FollowListCache cache.Cache32[GenericList[nostr.PubKey, ProfileRef]]
|
relayListCacheOnce sync.Once
|
||||||
MuteListCache cache.Cache32[GenericList[nostr.PubKey, ProfileRef]]
|
RelayListCache cache.Cache32[GenericList[string, Relay]]
|
||||||
BookmarkListCache cache.Cache32[GenericList[string, EventRef]]
|
followListCacheOnce sync.Once
|
||||||
PinListCache cache.Cache32[GenericList[string, EventRef]]
|
FollowListCache cache.Cache32[GenericList[nostr.PubKey, ProfileRef]]
|
||||||
BlockedRelayListCache cache.Cache32[GenericList[string, RelayURL]]
|
muteListCacheOnce sync.Once
|
||||||
SearchRelayListCache cache.Cache32[GenericList[string, RelayURL]]
|
MuteListCache cache.Cache32[GenericList[nostr.PubKey, ProfileRef]]
|
||||||
TopicListCache cache.Cache32[GenericList[string, Topic]]
|
bookmarkListCacheOnce sync.Once
|
||||||
RelaySetsCache cache.Cache32[GenericSets[string, RelayURL]]
|
BookmarkListCache cache.Cache32[GenericList[string, EventRef]]
|
||||||
FollowSetsCache cache.Cache32[GenericSets[nostr.PubKey, ProfileRef]]
|
pinListCacheOnce sync.Once
|
||||||
TopicSetsCache cache.Cache32[GenericSets[string, Topic]]
|
PinListCache cache.Cache32[GenericList[string, EventRef]]
|
||||||
ZapProviderCache cache.Cache32[nostr.PubKey]
|
blockedRelayListCacheOnce sync.Once
|
||||||
MintKeysCache cache.Cache32[map[uint64]*btcec.PublicKey]
|
BlockedRelayListCache cache.Cache32[GenericList[string, RelayURL]]
|
||||||
NutZapInfoCache cache.Cache32[NutZapInfo]
|
searchRelayListCacheOnce sync.Once
|
||||||
Hints hints.HintsDB
|
SearchRelayListCache cache.Cache32[GenericList[string, RelayURL]]
|
||||||
Pool *nostr.Pool
|
topicListCacheOnce sync.Once
|
||||||
RelayListRelays *RelayStream
|
TopicListCache cache.Cache32[GenericList[string, Topic]]
|
||||||
FollowListRelays *RelayStream
|
relaySetsCacheOnce sync.Once
|
||||||
MetadataRelays *RelayStream
|
RelaySetsCache cache.Cache32[GenericSets[string, RelayURL]]
|
||||||
FallbackRelays *RelayStream
|
followSetsCacheOnce sync.Once
|
||||||
JustIDRelays *RelayStream
|
FollowSetsCache cache.Cache32[GenericSets[nostr.PubKey, ProfileRef]]
|
||||||
UserSearchRelays *RelayStream
|
topicSetsCacheOnce sync.Once
|
||||||
NoteSearchRelays *RelayStream
|
TopicSetsCache cache.Cache32[GenericSets[string, Topic]]
|
||||||
Store eventstore.Store
|
zapProviderCacheOnce sync.Once
|
||||||
|
ZapProviderCache cache.Cache32[nostr.PubKey]
|
||||||
|
mintKeysCacheOnce sync.Once
|
||||||
|
MintKeysCache cache.Cache32[map[uint64]*btcec.PublicKey]
|
||||||
|
nutZapInfoCacheOnce sync.Once
|
||||||
|
NutZapInfoCache cache.Cache32[NutZapInfo]
|
||||||
|
Hints hints.HintsDB
|
||||||
|
Pool *nostr.Pool
|
||||||
|
RelayListRelays *RelayStream
|
||||||
|
FollowListRelays *RelayStream
|
||||||
|
MetadataRelays *RelayStream
|
||||||
|
FallbackRelays *RelayStream
|
||||||
|
JustIDRelays *RelayStream
|
||||||
|
UserSearchRelays *RelayStream
|
||||||
|
NoteSearchRelays *RelayStream
|
||||||
|
Store eventstore.Store
|
||||||
|
|
||||||
Publisher wrappers.StorePublisher
|
Publisher wrappers.StorePublisher
|
||||||
|
|
||||||
@@ -69,18 +86,20 @@ type SystemModifier func(sys *System)
|
|||||||
// It's used to distribute requests across multiple relays.
|
// It's used to distribute requests across multiple relays.
|
||||||
type RelayStream struct {
|
type RelayStream struct {
|
||||||
URLs []string
|
URLs []string
|
||||||
serial int
|
serial atomic.Int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRelayStream creates a new RelayStream with the provided URLs.
|
// NewRelayStream creates a new RelayStream with the provided URLs.
|
||||||
func NewRelayStream(urls ...string) *RelayStream {
|
func NewRelayStream(urls ...string) *RelayStream {
|
||||||
return &RelayStream{URLs: urls, serial: rand.Int()}
|
rs := &RelayStream{URLs: urls}
|
||||||
|
rs.serial.Add(rand.Int31n(int32(len(urls))))
|
||||||
|
return rs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next returns the next URL in the rotation.
|
// Next returns the next URL in the rotation.
|
||||||
func (rs *RelayStream) Next() string {
|
func (rs *RelayStream) Next() string {
|
||||||
rs.serial++
|
v := rs.serial.Add(1)
|
||||||
return rs.URLs[rs.serial%len(rs.URLs)]
|
return rs.URLs[int(v)%len(rs.URLs)]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSystem creates a new System with default configuration,
|
// NewSystem creates a new System with default configuration,
|
||||||
@@ -129,21 +148,31 @@ func NewSystem() *System {
|
|||||||
PenaltyBox: true,
|
PenaltyBox: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
if sys.MetadataCache == nil {
|
sys.metadataCacheOnce.Do(func() {
|
||||||
sys.MetadataCache = cache_memory.New[ProfileMetadata](8000)
|
if sys.MetadataCache == nil {
|
||||||
}
|
sys.MetadataCache = cache_memory.New[ProfileMetadata](8000)
|
||||||
if sys.RelayListCache == nil {
|
}
|
||||||
sys.RelayListCache = cache_memory.New[GenericList[string, Relay]](8000)
|
})
|
||||||
}
|
sys.relayListCacheOnce.Do(func() {
|
||||||
if sys.ZapProviderCache == nil {
|
if sys.RelayListCache == nil {
|
||||||
sys.ZapProviderCache = cache_memory.New[nostr.PubKey](8000)
|
sys.RelayListCache = cache_memory.New[GenericList[string, Relay]](8000)
|
||||||
}
|
}
|
||||||
if sys.MintKeysCache == nil {
|
})
|
||||||
sys.MintKeysCache = cache_memory.New[map[uint64]*btcec.PublicKey](8000)
|
sys.zapProviderCacheOnce.Do(func() {
|
||||||
}
|
if sys.ZapProviderCache == nil {
|
||||||
if sys.NutZapInfoCache == nil {
|
sys.ZapProviderCache = cache_memory.New[nostr.PubKey](8000)
|
||||||
sys.NutZapInfoCache = cache_memory.New[NutZapInfo](8000)
|
}
|
||||||
}
|
})
|
||||||
|
sys.mintKeysCacheOnce.Do(func() {
|
||||||
|
if sys.MintKeysCache == nil {
|
||||||
|
sys.MintKeysCache = cache_memory.New[map[uint64]*btcec.PublicKey](8000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
sys.nutZapInfoCacheOnce.Do(func() {
|
||||||
|
if sys.NutZapInfoCache == nil {
|
||||||
|
sys.NutZapInfoCache = cache_memory.New[NutZapInfo](8000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if sys.Store == nil {
|
if sys.Store == nil {
|
||||||
sys.Store = &nullstore.NullStore{}
|
sys.Store = &nullstore.NullStore{}
|
||||||
|
|||||||
5
utils.go
5
utils.go
@@ -82,3 +82,8 @@ func AppendUnique[I comparable](arr []I, item ...I) []I {
|
|||||||
}
|
}
|
||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsOlder(previous, next Event) bool {
|
||||||
|
return previous.CreatedAt < next.CreatedAt ||
|
||||||
|
(previous.CreatedAt == next.CreatedAt && bytes.Compare(previous.ID[:], next.ID[:]) == 1)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user