mirror of
https://git.mills.io/saltyim/saltyim.git
synced 2024-06-16 03:48:24 +00:00
Add support for sender interfaces and the pwa to fallback to sending via a broker as fallback (#101)
Co-authored-by: James Mills <prologic@shortcircuit.net.au> Reviewed-on: https://git.mills.io/saltyim/saltyim/pulls/101 Reviewed-by: xuu <xuu@noreply@mills.io>
This commit is contained in:
parent
88d46eaf13
commit
95be492208
41
client.go
41
client.go
@ -6,7 +6,6 @@ import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
@ -18,7 +17,6 @@ import (
|
||||
"github.com/avast/retry-go"
|
||||
"github.com/keys-pub/keys"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/timewasted/go-accept-headers"
|
||||
|
||||
"go.mills.io/salty"
|
||||
"go.mills.io/saltyim/internal/exec"
|
||||
@ -70,27 +68,6 @@ func PackMessage(me *Addr, msg string) []byte {
|
||||
)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Client is a Salty IM client that handles talking to a Salty IM Broker
|
||||
// and Sedngina and Receiving messages to/from Salty IM Users.
|
||||
type Client struct {
|
||||
@ -100,6 +77,7 @@ type Client struct {
|
||||
cache addrCache
|
||||
|
||||
lookup Lookuper
|
||||
send Sender
|
||||
}
|
||||
|
||||
// NewClient reeturns a new Salty IM client for sending and receiving
|
||||
@ -127,11 +105,14 @@ func NewClient(me *Addr, options ...IdentityOption) (*Client, error) {
|
||||
log.Debugf("Endpoint is %s", me.Endpoint())
|
||||
|
||||
return &Client{
|
||||
me: me,
|
||||
id: id,
|
||||
key: id.key,
|
||||
cache: make(addrCache),
|
||||
me: me,
|
||||
id: id,
|
||||
key: id.key,
|
||||
|
||||
cache: make(addrCache),
|
||||
|
||||
lookup: &DirectLookup{},
|
||||
send: &DirectSend{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -261,6 +242,12 @@ func (cli *Client) SetLookup(lookup Lookuper) {
|
||||
cli.lookup = lookup
|
||||
}
|
||||
|
||||
// SetSend sets the internal send interface to use (Sender) for sending
|
||||
// messages to endpoints. By default the DirectSend implementation is used.
|
||||
func (cli *Client) SetSend(send Sender) {
|
||||
cli.send = send
|
||||
}
|
||||
|
||||
// Drain drains this user's inbox by simulteneiously reading until empty anda
|
||||
// subscribing to the inbox for new messages.
|
||||
func (cli *Client) Drain(ctx context.Context, extraenvs, prehook, posthook string) chan Message {
|
||||
|
@ -32,8 +32,10 @@ func (a *API) initRoutes() {
|
||||
router := a.router.Group("/api/v1")
|
||||
|
||||
router.GET("/ping", a.PingEndpoint())
|
||||
router.POST("/register", a.RegisterEndpoint())
|
||||
router.GET("/lookup/:addr", a.LookupEndpoint())
|
||||
|
||||
router.POST("/send", a.SendEndpoint())
|
||||
router.POST("/register", a.RegisterEndpoint())
|
||||
}
|
||||
|
||||
// PingEndpoint ...
|
||||
@ -44,6 +46,18 @@ func (a *API) PingEndpoint() httprouter.Handle {
|
||||
}
|
||||
}
|
||||
|
||||
// LookupEndpoint ...
|
||||
func (a *API) LookupEndpoint() httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
addr, err := saltyim.LookupAddr(p.ByName("addr"))
|
||||
if err != nil {
|
||||
http.Error(w, "Lookup Error", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
a.r.JSON(w, http.StatusOK, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterEndpoint ...
|
||||
func (a *API) RegisterEndpoint() httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
@ -68,14 +82,26 @@ func (a *API) RegisterEndpoint() httprouter.Handle {
|
||||
}
|
||||
}
|
||||
|
||||
// LookupEndpoint ...
|
||||
func (a *API) LookupEndpoint() httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
addr, err := saltyim.LookupAddr(p.ByName("addr"))
|
||||
// SendEndpoint ...
|
||||
func (a *API) SendEndpoint() httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
req, signer, err := saltyim.NewSendRequest(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Lookup Error", http.StatusBadRequest)
|
||||
log.WithError(err).Error("error parsing send request")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
a.r.JSON(w, http.StatusOK, addr)
|
||||
if signer != r.Header.Get("Signature") {
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Queue up an internal retry and return immediately on failure?
|
||||
if err := saltyim.Send(req.Endpoint, req.Message, req.Capabilities); err != nil {
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, "Message Accepted", http.StatusAccepted)
|
||||
}
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ func (h *SaltyChat) connect(ctx app.Context) {
|
||||
h.dialog.ShowDialog("error setting up client", err.Error())
|
||||
return
|
||||
}
|
||||
newClient.SetSend(&saltyim.ProxySend{SendEndpoint: app.Getenv("SendEndpoint")})
|
||||
newClient.SetLookup(&saltyim.ProxyLookup{LookupEndpoint: app.Getenv("LookupEndpoint")})
|
||||
|
||||
client = newClient
|
||||
|
@ -295,6 +295,7 @@ func (s *Server) initRoutes() {
|
||||
|
||||
Env: map[string]string{
|
||||
"LookupEndpoint": strings.TrimSuffix(s.config.BaseURL, "/") + "/api/v1/lookup/",
|
||||
"SendEndpoint": strings.TrimSuffix(s.config.BaseURL, "/") + "/api/v1/send/",
|
||||
},
|
||||
|
||||
Icon: app.Icon{
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6756046e89856a68e31c8dde6a2c1229674064e47dbf815fd87465a193e45e2c
|
||||
size 28023655
|
||||
oid sha256:c25a7a58e7780b3b84afc242a8fd8e1cb70f25aa84238cd8478afb645853c28c
|
||||
size 28036206
|
||||
|
87
send.go
Normal file
87
send.go
Normal file
@ -0,0 +1,87 @@
|
||||
package saltyim
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/timewasted/go-accept-headers"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Sender = (*DirectSend)(nil)
|
||||
_ Sender = (*ProxySend)(nil)
|
||||
)
|
||||
|
||||
type Sender interface {
|
||||
Send(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 {
|
||||
req := SendRequest{
|
||||
Endpoint: endpoint,
|
||||
Message: msg,
|
||||
Capabilities: cap,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error serialing send request: %w", err)
|
||||
}
|
||||
|
||||
res, err := Request(http.MethodPost, endpoint, nil, bytes.NewBuffer(data))
|
||||
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(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(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...
|
||||
|
||||
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.MethodGet, s.SendEndpoint+endpoint, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
24
types.go
24
types.go
@ -30,3 +30,27 @@ func NewRegisterRequest(r io.Reader) (req RegisterRequest, signer string, err er
|
||||
err = json.Unmarshal(out, &req)
|
||||
return
|
||||
}
|
||||
|
||||
// SendRequest is the request used by clients to send messages via a broker
|
||||
type SendRequest struct {
|
||||
Endpoint string
|
||||
Message string
|
||||
Capabilities Capabilities
|
||||
}
|
||||
|
||||
// NewSendRequest reads the signed request body from a client, verifies its signature
|
||||
// and returns the resulting `SendRequest` and key used to sign the request on success
|
||||
// otherwise an empty object and en error on failure.
|
||||
func NewSendRequest(r io.Reader) (req SendRequest, signer string, err error) {
|
||||
body, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out, key, err := salty.Verify(body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signer = key.ID().String()
|
||||
err = json.Unmarshal(out, &req)
|
||||
return
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user