6
1
mirror of https://git.mills.io/saltyim/saltyim.git synced 2024-06-16 11:58:24 +00:00
prologic-saltyim/client.go
mlctrez 05c44be6c0 refactor identity create/read for functional arguments to support PWA (#53)
This PR is for refactoring interaction with CreateIdentity(), GetIdentity(), and NewClient() to allow operations on the identity contents from within a PWA where a filesystem is not present.

It was suggested to use functional arguments so this pull request reflects this.

Co-authored-by: mlctrez <mlctrez@gmail.com>
Reviewed-on: https://git.mills.io/saltyim/saltyim/pulls/53
Co-authored-by: mlctrez <mlctrez@noreply@mills.io>
Co-committed-by: mlctrez <mlctrez@noreply@mills.io>
2022-03-24 20:21:29 +00:00

171 lines
4.4 KiB
Go

package saltyim
import (
"bytes"
"context"
"fmt"
"net/http"
"os"
"strings"
"time"
"git.mills.io/prologic/msgbus"
msgbus_client "git.mills.io/prologic/msgbus/client"
"github.com/keys-pub/keys"
log "github.com/sirupsen/logrus"
"go.mills.io/salty"
"go.mills.io/saltyim/internal/exec"
)
type configCache map[string]Config
// PackMessage formts an outoing message in the Message Format
// <timestamp>\t(<sender>) <message>
func PackMessage(me *Addr, msg string) []byte {
return []byte(fmt.Sprint(time.Now().UTC().Format(time.RFC3339), "\t", me.Formatted(), "\t", strings.TrimSpace(msg), "\n"))
}
// 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) error {
res, err := Request(http.MethodPost, endpoint, nil, 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 {
me *Addr
key *keys.EdX25519Key
cache configCache
}
// NewClient returns a new Salty IM client for sending and receiving
// encrypted messages to other Salty IM users as well as decrypting
// and displaying messages of the user's own inbox.
func NewClient(me *Addr, options ...IdentityOption) (*Client, error) {
ident, err := GetIdentity(options...)
if err != nil {
return nil, fmt.Errorf("error opening identity %s: %w", ident.Source(), err)
}
if me == nil || me.IsZero() {
me = ident.addr
}
if me == nil || me.IsZero() {
return nil, fmt.Errorf("unable to find your user addressn in %s", ident.Source())
}
if err := me.Refresh(); err != nil {
return nil, fmt.Errorf("error looking up user endpoint: %w", err)
}
log.Debugf("Using identity %s with public key %s", ident.Source(), ident.key)
log.Debugf("Salty Addr is %s", me)
log.Debugf("Endpoint is %s", me.Endpoint())
return &Client{
me: me,
key: ident.key,
cache: make(configCache),
}, nil
}
func (cli *Client) getConfig(user string) (Config, error) {
config, ok := cli.cache[user]
if ok {
return config, nil
}
config, err := Lookup(user)
if err != nil {
return Config{}, fmt.Errorf("error: failed to lookup user %s: %w", user, err)
}
cli.cache[user] = config
return config, nil
}
func (cli *Client) handleMessage(prehook, posthook string, msgs chan string) msgbus.HandlerFunc {
return func(msg *msgbus.Message) error {
if prehook != "" {
out, err := exec.RunCmd(exec.DefaultRunCmdTimeout, prehook, bytes.NewBuffer(msg.Payload))
if err != nil {
log.WithError(err).Debugf("error running pre-hook %s", prehook)
}
log.Debugf("pre-hook: %q", out)
}
data, _, err := salty.Decrypt(cli.key, msg.Payload)
if err != nil {
fmt.Fprintf(os.Stderr, "error decrypting message")
return err
}
msgs <- string(data)
if posthook != "" {
out, err := exec.RunCmd(exec.DefaultRunCmdTimeout, posthook, bytes.NewBuffer(data))
if err != nil {
log.WithError(err).Debugf("error running post-hook %s", posthook)
}
log.Debugf("post-hook: %q", out)
}
return nil
}
}
func (cli *Client) Me() *Addr { return cli.me }
func (cli *Client) Key() *keys.EdX25519PublicKey { return cli.key.PublicKey() }
// Read subscribers to this user's inbox for new messages
func (cli *Client) Read(ctx context.Context, prehook, posthook string) chan string {
uri, inbox := SplitInbox(cli.me.Endpoint().String())
bus := msgbus_client.NewClient(uri, nil)
msgs := make(chan string)
s := bus.Subscribe(inbox, cli.handleMessage(prehook, posthook, msgs))
s.Start()
log.Debugf("Connected to %s/%s", uri, inbox)
go func() {
<-ctx.Done()
s.Stop()
close(msgs)
}()
return msgs
}
// Send sends an encrypted message to the specified user
func (cli *Client) Send(user, msg string) error {
cfg, err := cli.getConfig(user)
if err != nil {
return fmt.Errorf("error looking up user %s: %w", user, err)
}
b, err := salty.Encrypt(cli.key, PackMessage(cli.me, msg), []string{cfg.Key})
if err != nil {
return fmt.Errorf("error encrypting message to %s: %w", user, err)
}
if err := Send(cfg.Endpoint, string(b)); err != nil {
return fmt.Errorf("error sending message to %s: %w", user, err)
}
return nil
}
// Register sends a registration requestn to a broker
func (cli *Client) Register() error {
return nil
}