implemented event and req
This commit is contained in:
@@ -16,7 +16,6 @@ import (
|
||||
"go-simpler.org/env"
|
||||
lol "lol.mleku.dev"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/pkg/version"
|
||||
)
|
||||
|
||||
@@ -28,7 +27,8 @@ type C struct {
|
||||
DataDir string `env:"ORLY_DATA_DIR" usage:"storage location for the event store" default:"~/.local/share/ORLY"`
|
||||
Listen string `env:"ORLY_LISTEN" default:"0.0.0.0" usage:"network listen address"`
|
||||
Port int `env:"ORLY_PORT" default:"3334" usage:"port to listen on"`
|
||||
LogLevel string `env:"ORLY_LOG_LEVEL" default:"info" usage:"debug level: fatal error warn info debug trace"`
|
||||
LogLevel string `env:"ORLY_LOG_LEVEL" default:"info" usage:"relay log level: fatal error warn info debug trace"`
|
||||
DBLogLevel string `env:"ORLY_DB_LOG_LEVEL" default:"info" usage:"database log level: fatal error warn info debug trace"`
|
||||
Pprof string `env:"ORLY_PPROF" usage:"enable pprof in modes: cpu,memory,allocation"`
|
||||
IPWhitelist []string `env:"ORLY_IP_WHITELIST" usage:"comma-separated list of IP addresses to allow access from, matches on prefixes to allow private subnets, eg 10.0.0 = 10.0.0.0/8"`
|
||||
}
|
||||
@@ -71,7 +71,6 @@ func New() (cfg *C, err error) {
|
||||
os.Exit(0)
|
||||
}
|
||||
lol.SetLogLevel(cfg.LogLevel)
|
||||
log.I.S(cfg.IPWhitelist)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
1
app/handle-auth.go
Normal file
1
app/handle-auth.go
Normal file
@@ -0,0 +1 @@
|
||||
package app
|
||||
1
app/handle-close.go
Normal file
1
app/handle-close.go
Normal file
@@ -0,0 +1 @@
|
||||
package app
|
||||
66
app/handle-event.go
Normal file
66
app/handle-event.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"encoders.orly/envelopes/eventenvelope"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
utils "utils.orly"
|
||||
)
|
||||
|
||||
func (l *Listener) HandleEvent(c context.Context, msg []byte) (
|
||||
err error,
|
||||
) {
|
||||
// decode the envelope
|
||||
env := eventenvelope.NewSubmission()
|
||||
if msg, err = env.Unmarshal(msg); chk.E(err) {
|
||||
return
|
||||
}
|
||||
if len(msg) > 0 {
|
||||
log.I.F("extra '%s'", msg)
|
||||
}
|
||||
// check the event ID is correct
|
||||
calculatedId := env.E.GetIDBytes()
|
||||
if !utils.FastEqual(calculatedId, env.E.ID) {
|
||||
if err = Ok.Invalid(
|
||||
l, env, "event id is computed incorrectly, "+
|
||||
"event has ID %0x, but when computed it is %0x",
|
||||
env.E.ID, calculatedId,
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
// verify the signature
|
||||
var ok bool
|
||||
if ok, err = env.Verify(); chk.T(err) {
|
||||
if err = Ok.Error(
|
||||
l, env, fmt.Sprintf(
|
||||
"failed to verify signature: %s",
|
||||
err.Error(),
|
||||
),
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
} else if !ok {
|
||||
if err = Ok.Invalid(
|
||||
l, env,
|
||||
"signature is invalid",
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
// store the event
|
||||
if _, _, err = l.SaveEvent(c, env.E, false, nil); chk.E(err) {
|
||||
return
|
||||
}
|
||||
// Send a success response after storing
|
||||
if err = Ok.Ok(l, env, ""); chk.E(err) {
|
||||
return
|
||||
}
|
||||
log.D.F("saved event %0x", env.E.ID)
|
||||
return
|
||||
}
|
||||
@@ -7,12 +7,14 @@ import (
|
||||
"encoders.orly/envelopes/authenvelope"
|
||||
"encoders.orly/envelopes/closeenvelope"
|
||||
"encoders.orly/envelopes/eventenvelope"
|
||||
"encoders.orly/envelopes/noticeenvelope"
|
||||
"encoders.orly/envelopes/reqenvelope"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/errorf"
|
||||
"lol.mleku.dev/log"
|
||||
)
|
||||
|
||||
func (s *Server) HandleMessage(msg []byte, remote string) {
|
||||
func (l *Listener) HandleMessage(msg []byte, remote string) {
|
||||
log.D.C(
|
||||
func() string {
|
||||
return fmt.Sprintf(
|
||||
@@ -20,36 +22,36 @@ func (s *Server) HandleMessage(msg []byte, remote string) {
|
||||
)
|
||||
},
|
||||
)
|
||||
var notice []byte
|
||||
var err error
|
||||
var t string
|
||||
var rem []byte
|
||||
if t, rem, err = envelopes.Identify(msg); chk.E(err) {
|
||||
notice = []byte(err.Error())
|
||||
if t, rem, err = envelopes.Identify(msg); !chk.E(err) {
|
||||
switch t {
|
||||
case eventenvelope.L:
|
||||
log.D.F("eventenvelope: %s", rem)
|
||||
err = l.HandleEvent(l.ctx, rem)
|
||||
case reqenvelope.L:
|
||||
log.D.F("reqenvelope: %s", rem)
|
||||
err = l.HandleReq(l.ctx, rem)
|
||||
case closeenvelope.L:
|
||||
log.D.F("closeenvelope: %s", rem)
|
||||
case authenvelope.L:
|
||||
log.D.F("authenvelope: %s", rem)
|
||||
default:
|
||||
err = errorf.E("unknown envelope type %s\n%s", t, rem)
|
||||
}
|
||||
}
|
||||
switch t {
|
||||
case eventenvelope.L:
|
||||
log.D.F("eventenvelope: %s", rem)
|
||||
case reqenvelope.L:
|
||||
log.D.F("reqenvelope: %s", rem)
|
||||
case closeenvelope.L:
|
||||
log.D.F("closeenvelope: %s", rem)
|
||||
case authenvelope.L:
|
||||
log.D.F("authenvelope: %s", rem)
|
||||
default:
|
||||
notice = []byte(fmt.Sprintf("unknown envelope type %s\n%s", t, rem))
|
||||
}
|
||||
if len(notice) > 0 {
|
||||
if err != nil {
|
||||
log.D.C(
|
||||
func() string {
|
||||
return fmt.Sprintf(
|
||||
"notice->%s %s", remote, notice,
|
||||
"notice->%s %s", remote, err,
|
||||
)
|
||||
},
|
||||
)
|
||||
// if err = noticeenvelope.NewFrom(notice).Write(a.Listener); chk.E(err) {
|
||||
// return
|
||||
// }
|
||||
if err = noticeenvelope.NewFrom(err.Error()).Write(l); chk.E(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
120
app/handle-req.go
Normal file
120
app/handle-req.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"encoders.orly/envelopes/closedenvelope"
|
||||
"encoders.orly/envelopes/eoseenvelope"
|
||||
"encoders.orly/envelopes/eventenvelope"
|
||||
"encoders.orly/envelopes/reqenvelope"
|
||||
"encoders.orly/event"
|
||||
"encoders.orly/filter"
|
||||
"encoders.orly/tag"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"utils.orly/normalize"
|
||||
"utils.orly/pointers"
|
||||
)
|
||||
|
||||
func (l *Listener) HandleReq(c context.Context, msg []byte) (
|
||||
err error,
|
||||
) {
|
||||
var rem []byte
|
||||
env := reqenvelope.New()
|
||||
if rem, err = env.Unmarshal(msg); chk.E(err) {
|
||||
return normalize.Error.Errorf(err.Error())
|
||||
}
|
||||
if len(rem) > 0 {
|
||||
log.I.F("extra '%s'", rem)
|
||||
}
|
||||
var events event.S
|
||||
for _, f := range *env.Filters {
|
||||
if pointers.Present(f.Limit) {
|
||||
if *f.Limit == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if events, err = l.QueryEvents(c, f); chk.E(err) {
|
||||
if errors.Is(err, badger.ErrDBClosed) {
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
// write out the events to the socket
|
||||
seen := make(map[string]struct{})
|
||||
for _, ev := range events {
|
||||
// track the IDs we've sent
|
||||
seen[string(ev.ID)] = struct{}{}
|
||||
var res *eventenvelope.Result
|
||||
if res, err = eventenvelope.NewResultWith(
|
||||
env.Subscription, ev,
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
if err = res.Write(l); chk.E(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// write the EOSE to signal to the client that all events found have been
|
||||
// sent.
|
||||
if err = eoseenvelope.NewFrom(env.Subscription).
|
||||
Write(l); chk.E(err) {
|
||||
return
|
||||
}
|
||||
// if the query was for just Ids, we know there can't be any more results,
|
||||
// so cancel the subscription.
|
||||
cancel := true
|
||||
var subbedFilters filter.S
|
||||
for _, f := range *env.Filters {
|
||||
if f.Ids.Len() < 1 {
|
||||
cancel = false
|
||||
subbedFilters = append(subbedFilters, f)
|
||||
} else {
|
||||
// remove the IDs that we already sent
|
||||
var notFounds [][]byte
|
||||
for _, ev := range events {
|
||||
if _, ok := seen[string(ev.ID)]; ok {
|
||||
continue
|
||||
}
|
||||
notFounds = append(notFounds, ev.ID)
|
||||
}
|
||||
// if all were found, don't add to subbedFilters
|
||||
if len(notFounds) == 0 {
|
||||
continue
|
||||
}
|
||||
// rewrite the filter Ids to remove the ones we already sent
|
||||
f.Ids = tag.NewFromBytesSlice(notFounds...)
|
||||
// add the filter to the list of filters we're subscribing to
|
||||
subbedFilters = append(subbedFilters, f)
|
||||
}
|
||||
// also, if we received the limit number of events, subscription ded
|
||||
if pointers.Present(f.Limit) {
|
||||
if len(events) < int(*f.Limit) {
|
||||
cancel = false
|
||||
}
|
||||
}
|
||||
}
|
||||
receiver := make(event.C, 32)
|
||||
// if the subscription should be cancelled, do so
|
||||
if !cancel {
|
||||
l.publishers.Receive(
|
||||
&W{
|
||||
Conn: l.conn,
|
||||
remote: l.remote,
|
||||
Id: string(env.Subscription),
|
||||
Receiver: receiver,
|
||||
Filters: env.Filters,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
if err = closedenvelope.NewFrom(
|
||||
env.Subscription, nil,
|
||||
).Write(l); chk.E(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/coder/websocket"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"protocol.orly/publish"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,7 +29,7 @@ const (
|
||||
|
||||
func (s *Server) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
remote := GetRemoteFromReq(r)
|
||||
log.D.F("handling websocket connection from %s", remote)
|
||||
log.T.F("handling websocket connection from %s", remote)
|
||||
if len(s.Config.IPWhitelist) > 0 {
|
||||
for _, ip := range s.Config.IPWhitelist {
|
||||
log.T.F("checking IP whitelist: %s", ip)
|
||||
@@ -52,6 +53,13 @@ whitelist:
|
||||
return
|
||||
}
|
||||
defer conn.CloseNow()
|
||||
listener := &Listener{
|
||||
ctx: s.Ctx,
|
||||
Server: s,
|
||||
conn: conn,
|
||||
remote: remote,
|
||||
}
|
||||
listener.publishers = publish.New(NewPublisher())
|
||||
go s.Pinger(s.Ctx, conn, time.NewTicker(time.Second*10), cancel)
|
||||
for {
|
||||
select {
|
||||
@@ -85,7 +93,7 @@ whitelist:
|
||||
}
|
||||
continue
|
||||
}
|
||||
go s.HandleMessage(msg, remote)
|
||||
go listener.HandleMessage(msg, remote)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"lol.mleku.dev/chk"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
conn *websocket.Conn
|
||||
*Server
|
||||
conn *websocket.Conn
|
||||
ctx context.Context
|
||||
remote string
|
||||
}
|
||||
|
||||
func (l *Listener) Write(p []byte) (n int, err error) {
|
||||
if err = l.conn.Write(l.ctx, websocket.MessageText, p); chk.E(err) {
|
||||
return
|
||||
}
|
||||
n = len(p)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
database "database.orly"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/app/config"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, cfg *config.C) (quit chan struct{}) {
|
||||
func Run(
|
||||
ctx context.Context, cfg *config.C, db *database.D,
|
||||
) (quit chan struct{}) {
|
||||
// shutdown handler
|
||||
go func() {
|
||||
select {
|
||||
@@ -23,6 +26,7 @@ func Run(ctx context.Context, cfg *config.C) (quit chan struct{}) {
|
||||
l := &Server{
|
||||
Ctx: ctx,
|
||||
Config: cfg,
|
||||
D: db,
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", cfg.Listen, cfg.Port)
|
||||
log.I.F("starting listener on http://%s", addr)
|
||||
|
||||
118
app/ok.go
Normal file
118
app/ok.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoders.orly/envelopes/eventenvelope"
|
||||
"encoders.orly/envelopes/okenvelope"
|
||||
"encoders.orly/reason"
|
||||
)
|
||||
|
||||
// OK represents a function that processes events or operations, using provided
|
||||
// parameters to generate formatted messages and return errors if any issues
|
||||
// occur during processing.
|
||||
type OK func(
|
||||
l *Listener, env *eventenvelope.Submission, format string, params ...any,
|
||||
) (err error)
|
||||
|
||||
// OKs provides a collection of handler functions for managing different types
|
||||
// of operational outcomes, each corresponding to specific error or status
|
||||
// conditions such as authentication requirements, rate limiting, and invalid
|
||||
// inputs.
|
||||
type OKs struct {
|
||||
Ok OK
|
||||
AuthRequired OK
|
||||
PoW OK
|
||||
Duplicate OK
|
||||
Blocked OK
|
||||
RateLimited OK
|
||||
Invalid OK
|
||||
Error OK
|
||||
Unsupported OK
|
||||
Restricted OK
|
||||
}
|
||||
|
||||
// Ok provides a collection of handler functions for managing different types of
|
||||
// operational outcomes, each corresponding to specific error or status
|
||||
// conditions such as authentication requirements, rate limiting, and invalid
|
||||
// inputs.
|
||||
var Ok = OKs{
|
||||
Ok: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
env.Id(), true, nil,
|
||||
).Write(l)
|
||||
},
|
||||
AuthRequired: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
env.Id(), false, reason.AuthRequired.F(format, params...),
|
||||
).Write(l)
|
||||
},
|
||||
PoW: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
env.Id(), false, reason.PoW.F(format, params...),
|
||||
).Write(l)
|
||||
},
|
||||
Duplicate: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
env.Id(), false, reason.Duplicate.F(format, params...),
|
||||
).Write(l)
|
||||
},
|
||||
Blocked: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
env.Id(), false, reason.Blocked.F(format, params...),
|
||||
).Write(l)
|
||||
},
|
||||
RateLimited: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
env.Id(), false, reason.RateLimited.F(format, params...),
|
||||
).Write(l)
|
||||
},
|
||||
Invalid: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
env.Id(), false, reason.Invalid.F(format, params...),
|
||||
).Write(l)
|
||||
},
|
||||
Error: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
env.Id(), false, reason.Error.F(format, params...),
|
||||
).Write(l)
|
||||
},
|
||||
Unsupported: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
env.Id(), false, reason.Unsupported.F(format, params...),
|
||||
).Write(l)
|
||||
},
|
||||
Restricted: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
env.Id(), false, reason.Restricted.F(format, params...),
|
||||
).Write(l)
|
||||
},
|
||||
}
|
||||
222
app/publisher.go
Normal file
222
app/publisher.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"encoders.orly/envelopes/eventenvelope"
|
||||
"encoders.orly/event"
|
||||
"encoders.orly/filter"
|
||||
"github.com/coder/websocket"
|
||||
"interfaces.orly/publisher"
|
||||
"interfaces.orly/typer"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
)
|
||||
|
||||
const Type = "socketapi"
|
||||
|
||||
type Subscription struct {
|
||||
remote string
|
||||
*filter.S
|
||||
}
|
||||
|
||||
// Map is a map of filters associated with a collection of ws.Listener
|
||||
// connections.
|
||||
type Map map[*websocket.Conn]map[string]Subscription
|
||||
|
||||
type W struct {
|
||||
*websocket.Conn
|
||||
|
||||
remote string
|
||||
|
||||
// If Cancel is true, this is a close command.
|
||||
Cancel bool
|
||||
|
||||
// Id is the subscription Id. If Cancel is true, cancel the named
|
||||
// subscription, otherwise, cancel the publisher for the socket.
|
||||
Id string
|
||||
|
||||
// The Receiver holds the event channel for receiving notifications or data
|
||||
// relevant to this WebSocket connection.
|
||||
Receiver event.C
|
||||
|
||||
// Filters holds a collection of filters used to match or process events
|
||||
// associated with this WebSocket connection. It is used to determine which
|
||||
// notifications or data should be received by the subscriber.
|
||||
Filters *filter.S
|
||||
}
|
||||
|
||||
func (w *W) Type() (typeName string) { return Type }
|
||||
|
||||
// P is a structure that manages subscriptions and associated filters for
|
||||
// websocket listeners. It uses a mutex to synchronize access to a map storing
|
||||
// subscriber connections and their filter configurations.
|
||||
type P struct {
|
||||
c context.Context
|
||||
// Mx is the mutex for the Map.
|
||||
Mx sync.Mutex
|
||||
// Map is the map of subscribers and subscriptions from the websocket api.
|
||||
Map
|
||||
}
|
||||
|
||||
var _ publisher.I = &P{}
|
||||
|
||||
func NewPublisher() (publisher *P) {
|
||||
return &P{
|
||||
Map: make(Map),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P) Type() (typeName string) { return Type }
|
||||
|
||||
// Receive handles incoming messages to manage websocket listener subscriptions
|
||||
// and associated filters.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - msg (publisher.Message): The incoming message to process; expected to be of
|
||||
// type *W to trigger subscription management actions.
|
||||
//
|
||||
// # Expected behaviour
|
||||
//
|
||||
// - Checks if the message is of type *W.
|
||||
//
|
||||
// - If Cancel is true, removes a subscriber by ID or the entire listener.
|
||||
//
|
||||
// - Otherwise, adds the subscription to the map under a mutex lock.
|
||||
//
|
||||
// - Logs actions related to subscription creation or removal.
|
||||
func (p *P) Receive(msg typer.T) {
|
||||
if m, ok := msg.(*W); ok {
|
||||
if m.Cancel {
|
||||
if m.Id == "" {
|
||||
p.removeSubscriber(m.Conn)
|
||||
log.T.F("removed listener %s", m.remote)
|
||||
} else {
|
||||
p.removeSubscriberId(m.Conn, m.Id)
|
||||
log.T.C(
|
||||
func() string {
|
||||
return fmt.Sprintf(
|
||||
"removed subscription %s for %s", m.Id,
|
||||
m.remote,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
p.Mx.Lock()
|
||||
defer p.Mx.Unlock()
|
||||
if subs, ok := p.Map[m.Conn]; !ok {
|
||||
subs = make(map[string]Subscription)
|
||||
subs[m.Id] = Subscription{S: m.Filters, remote: m.remote}
|
||||
p.Map[m.Conn] = subs
|
||||
log.T.C(
|
||||
func() string {
|
||||
return fmt.Sprintf(
|
||||
"created new subscription for %s, %s",
|
||||
m.remote,
|
||||
m.Filters.Marshal(nil),
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
subs[m.Id] = Subscription{S: m.Filters, remote: m.remote}
|
||||
log.T.C(
|
||||
func() string {
|
||||
return fmt.Sprintf(
|
||||
"added subscription %s for %s", m.Id,
|
||||
m.remote,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deliver processes and distributes an event to all matching subscribers based on their filter configurations.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ev (*event.E): The event to be delivered to subscribed clients.
|
||||
//
|
||||
// # Expected behaviour
|
||||
//
|
||||
// Delivers the event to all subscribers whose filters match the event. It
|
||||
// applies authentication checks if required by the server and skips delivery
|
||||
// for unauthenticated users when events are privileged.
|
||||
func (p *P) Deliver(ev *event.E) {
|
||||
var err error
|
||||
p.Mx.Lock()
|
||||
defer p.Mx.Unlock()
|
||||
log.T.C(
|
||||
func() string {
|
||||
return fmt.Sprintf(
|
||||
"delivering event %0x to websocket subscribers %d", ev.ID,
|
||||
len(p.Map),
|
||||
)
|
||||
},
|
||||
)
|
||||
for w, subs := range p.Map {
|
||||
log.T.C(
|
||||
func() string {
|
||||
return fmt.Sprintf(
|
||||
"%v %s", subs,
|
||||
)
|
||||
},
|
||||
)
|
||||
for id, subscriber := range subs {
|
||||
if !subscriber.Match(ev) {
|
||||
continue
|
||||
}
|
||||
// if p.Server.AuthRequired() {
|
||||
// if !auth.CheckPrivilege(w.AuthedPubkey(), ev) {
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
var res *eventenvelope.Result
|
||||
if res, err = eventenvelope.NewResultWith(id, ev); chk.E(err) {
|
||||
continue
|
||||
}
|
||||
if err = w.Write(
|
||||
p.c, websocket.MessageText, res.Marshal(nil),
|
||||
); chk.E(err) {
|
||||
continue
|
||||
}
|
||||
log.T.C(
|
||||
func() string {
|
||||
return fmt.Sprintf(
|
||||
"dispatched event %0x to subscription %s, %s",
|
||||
ev.ID, id, subscriber.remote,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeSubscriberId removes a specific subscription from a subscriber
|
||||
// websocket.
|
||||
func (p *P) removeSubscriberId(ws *websocket.Conn, id string) {
|
||||
p.Mx.Lock()
|
||||
var subs map[string]Subscription
|
||||
var ok bool
|
||||
if subs, ok = p.Map[ws]; ok {
|
||||
delete(p.Map[ws], id)
|
||||
_ = subs
|
||||
if len(subs) == 0 {
|
||||
delete(p.Map, ws)
|
||||
}
|
||||
}
|
||||
p.Mx.Unlock()
|
||||
}
|
||||
|
||||
// removeSubscriber removes a websocket from the P collection.
|
||||
func (p *P) removeSubscriber(ws *websocket.Conn) {
|
||||
p.Mx.Lock()
|
||||
clear(p.Map[ws])
|
||||
delete(p.Map, ws)
|
||||
p.Mx.Unlock()
|
||||
}
|
||||
@@ -4,14 +4,19 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"database.orly"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/app/config"
|
||||
"protocol.orly/publish"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
mux *http.ServeMux
|
||||
Config *config.C
|
||||
Ctx context.Context
|
||||
remote string
|
||||
*database.D
|
||||
publishers *publish.S
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user