prologic-saltyim/send.go

101 lines
3.0 KiB
Go

package saltyim
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"github.com/keys-pub/keys"
"github.com/timewasted/go-accept-headers"
"go.mills.io/salty"
)
var (
_ Sender = (*DirectSend)(nil)
_ Sender = (*ProxySend)(nil)
acceptEncodings = []string{"br", "gzip", ""}
)
// Sender defines an interface for sending messages to another Salty Address (user)
// and is primarily used by the PWA and possibly other clients to send outbound
// messages where it may not always be possible to send the message directly and
// instead proxy the message thorugh a broker. Note that even if proxying through
// a broker, the message is already encrypted at the point of proxying, so there
// needs not be any trust between the client and broker as the broker is treated
// as a "dumb" relay.
type Sender interface {
Send(key *keys.EdX25519Key, endpoint, msg string, cap Capabilities) error
}
// Send sends the encrypted message `msg` to the Endpoint `endpoint` using a
// `POST` request and returns nil on success or an error on failure.
func Send(endpoint, msg string, cap Capabilities) error {
headers := make(http.Header)
if cap.AcceptEncoding != "" {
ae, err := accept.Negotiate(cap.AcceptEncoding, acceptEncodings...)
if err != nil {
return fmt.Errorf("error publishing message to %s: %w", endpoint, err)
}
headers.Set("Content-Encoding", ae)
}
res, err := Request(http.MethodPost, endpoint, headers, bytes.NewBufferString(msg))
if err != nil {
return fmt.Errorf("error publishing message to %s: %w", endpoint, err)
}
defer res.Body.Close()
return nil
}
// DirectSend performs a direct send request
type DirectSend struct{}
// Send posts a message to an endpoint directly
func (s *DirectSend) Send(key *keys.EdX25519Key, endpoint, msg string, cap Capabilities) error {
return Send(endpoint, msg, cap)
}
// ProxySend proxies send requests through a Salty Broker's /api/v1/send endpoint
type ProxySend struct {
// SendEndpoint is the uri of the send endpoint of a broker
SendEndpoint string
}
// Send posts a message to an endpoint directly directly and if the request fails for
// whatever reason (usuaully due to Cross-Orogin-Resource-Sharing policies / CORS) it
// uses the Salty Broker the PWA was served from initially to perform the send by
// proxying the send request through the broker. Why? CORS sucks.
func (s *ProxySend) Send(key *keys.EdX25519Key, endpoint, msg string, cap Capabilities) error {
err := Send(endpoint, msg, cap)
if err == nil {
return nil
}
// Fallback to proxying the send request through the broker...
req := SendRequest{
Endpoint: endpoint,
Message: msg,
Capabilities: cap,
}
data, err := json.Marshal(req)
if err != nil {
return fmt.Errorf("error serializing send request: %w", err)
}
signed, err := salty.Sign(key, data)
if err != nil {
return fmt.Errorf("error signing send request: %w", err)
}
res, err := Request(http.MethodPost, s.SendEndpoint, nil, bytes.NewBuffer(signed))
if err != nil {
return err
}
defer res.Body.Close()
return nil
}