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", ""} ) 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 }