diff --git a/client.go b/client.go index c77e5b4..cdac9ce 100644 --- a/client.go +++ b/client.go @@ -3,10 +3,8 @@ package saltyim import ( "bytes" "context" - "encoding/json" "fmt" "net/http" - "net/url" "os" "time" @@ -23,7 +21,7 @@ type configCache map[string]Config // PackMessage formts an outoing message in the Message Format // \t() -func PackMessage(me Addr, msg string) []byte { +func PackMessage(me *Addr, msg string) []byte { return []byte(fmt.Sprint(time.Now().UTC().Format(time.RFC3339), "\t", me.Formatted(), "\t", msg)) } @@ -41,40 +39,38 @@ func Send(endpoint, msg string) error { // 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 { - key *keys.EdX25519Key - - endpoint string - me Addr - + me *Addr + key *keys.EdX25519Key cache configCache } // NewClient reeturns 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, identity, endpoint string) (*Client, error) { +func NewClient(me *Addr, identity string) (*Client, error) { key, m, err := GetIdentity(identity) if err != nil { return nil, fmt.Errorf("error opening identity %s: %w", identity, err) } - if me.IsZero() { + if me == nil || me.IsZero() { me = m } - if me.IsZero() { + if me == nil || me.IsZero() { return nil, fmt.Errorf("unable to find your user addressn in %s", identity) } + 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", identity, key) log.Debugf("Salty Addr is %s", me) - log.Debugf("Endpoint is %s", endpoint) + log.Debugf("Endpoint is %s", me.Endpoint()) return &Client{ - key: key, - - endpoint: endpoint, - me: me, - + me: me, + key: key, cache: make(configCache), }, nil } @@ -125,16 +121,12 @@ func (cli *Client) handleMessage(prehook, posthook string, msgs chan string) msg } } -func (cli *Client) Me() Addr { return cli.me } -func (cli *Client) Endpoint() string { return cli.endpoint } +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, endpoint, prehook, posthook string) chan string { - if endpoint == "" { - endpoint = cli.endpoint - } - - uri, inbox := SplitInbox(endpoint) +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) @@ -173,31 +165,5 @@ func (cli *Client) Send(user, msg string) error { // Register sends a registration requestn to a broker func (cli *Client) Register() error { - req := RegisterRequest{ - Addr: cli.me, - Key: cli.key.ID().String(), - } - data, err := json.Marshal(req) - if err != nil { - return fmt.Errorf("error serializing register request: %w", err) - } - signed, err := salty.Sign(cli.key, data) - if err != nil { - return fmt.Errorf("error signing registration request: %w", err) - } - body := bytes.NewBuffer(signed) - - endpointURL, err := url.Parse(cli.endpoint) - if err != nil { - return fmt.Errorf("error parsing endpoint %s: %w", cli.endpoint, err) - } - endpointURL.Path = "/api/v1/register" - - res, err := Request(http.MethodPost, endpointURL.String(), nil, body) - if err != nil { - return fmt.Errorf("error registering to broker %s: %w", endpointURL, err) - } - defer res.Body.Close() - return nil } diff --git a/cmd/salty-chat/chat.go b/cmd/salty-chat/chat.go index 4ee0704..cd36d20 100644 --- a/cmd/salty-chat/chat.go +++ b/cmd/salty-chat/chat.go @@ -15,31 +15,30 @@ import ( var chatCmd = &cobra.Command{ Use: "chat ", Short: "Creates a chat with a specific user", - Long: `This command creates a chat with the specified user by subscribing -to your default inbox (normally $USER) and prompts for input and sends encrypted -messages to the user via their endpoint.`, + Long: `This command creates a chat with the specified user by discovering +and subscribing to your endpoint and prompts for input and sends encrypted +messages to the user via their discovered endpoint.`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { user := viper.GetString("user") - endpoint := viper.GetString("endpoint") identity := viper.GetString("identity") var profiles []profile viper.UnmarshalKey("profiles", &profiles) for _, p := range profiles { if user == p.User { - endpoint = p.Endpoint identity = p.Identity } } - var me saltyim.Addr + me := &saltyim.Addr{} if sp := strings.Split(user, "@"); len(sp) > 1 { me.User = sp[0] me.Domain = sp[1] } + // XXX: What if me.IsZero() - chat(me, identity, endpoint, args[0]) + chat(me, identity, args[0]) }, } @@ -47,8 +46,8 @@ func init() { rootCmd.AddCommand(chatCmd) } -func chat(me saltyim.Addr, identity, endpoint, user string) { - cli, err := saltyim.NewClient(me, identity, endpoint) +func chat(me *saltyim.Addr, identity, user string) { + cli, err := saltyim.NewClient(me, identity) if err != nil { fmt.Fprintf(os.Stderr, "error initializing client: %s\n", err) os.Exit(2) diff --git a/cmd/salty-chat/makeuser.go b/cmd/salty-chat/makeuser.go index 01189f6..6189903 100644 --- a/cmd/salty-chat/makeuser.go +++ b/cmd/salty-chat/makeuser.go @@ -5,6 +5,7 @@ import ( "net/url" "os" "path/filepath" + "strings" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -33,7 +34,7 @@ $ salty-chat lookup {{ .Addr }}` type setupCtx struct { Config saltyim.Config - Addr saltyim.Addr + Addr *saltyim.Addr } var makeuserCmd = &cobra.Command{ @@ -50,10 +51,16 @@ A valid top-level domain or sub-domain is required and the is in the form username@domain`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { - user := args[0] - endpoint := viper.GetString("endpoint") identity := viper.GetString("identity") - makeuser(identity, endpoint, user) + + me := &saltyim.Addr{} + if sp := strings.Split(args[0], "@"); len(sp) > 1 { + me.User = sp[0] + me.Domain = sp[1] + } + // XXX: What if me.IsZero() + + makeuser(me, identity) }, } @@ -61,19 +68,10 @@ func init() { rootCmd.AddCommand(makeuserCmd) } -func makeuser(identity, endpoint, user string) { - if endpoint == "" { - fmt.Fprintf(os.Stderr, "error no endpoint supplied\n") - os.Exit(2) - } - - endpointURL, err := url.Parse(endpoint) - if err != nil { - fmt.Fprintf(os.Stderr, "error parsing endpoint: %s\n", err) - os.Exit(2) - } - - endpointURL.Path = fmt.Sprintf("/%s", saltyim.MustGenerateULID()) +func makeuser(me *saltyim.Addr, identity string) { + // XXX: Is there a better way to do this? + endpoint, _ := url.Parse(fmt.Sprintf("https://%s", me.Domain)) + endpoint.Path = fmt.Sprintf("/%s", saltyim.MustGenerateULID()) dir := filepath.Dir(identity) if err := os.MkdirAll(dir, 0700); err != nil { @@ -81,20 +79,20 @@ func makeuser(identity, endpoint, user string) { os.Exit(2) } - if err := saltyim.CreateIdentity(identity, user); err != nil { - fmt.Fprintf(os.Stderr, "error creating identity %q for %s: %s\n", identity, user, err) + if err := saltyim.CreateIdentity(identity, me.String()); err != nil { + fmt.Fprintf(os.Stderr, "error creating identity %q for %s: %s\n", identity, me, err) os.Exit(2) } - key, me, err := saltyim.GetIdentity(identity) + key, _, err := saltyim.GetIdentity(identity) if err != nil { - fmt.Fprintf(os.Stderr, "error reading identity %s for %s: %s\n", identity, user, err) + fmt.Fprintf(os.Stderr, "error reading identity %s for %s: %s\n", identity, me, err) os.Exit(2) } ctx := setupCtx{ Config: saltyim.Config{ - Endpoint: endpointURL.String(), + Endpoint: endpoint.String(), Key: key.PublicKey().ID().String(), }, Addr: me, diff --git a/cmd/salty-chat/read.go b/cmd/salty-chat/read.go index f270d6a..16a3824 100644 --- a/cmd/salty-chat/read.go +++ b/cmd/salty-chat/read.go @@ -22,23 +22,22 @@ var readCmd = &cobra.Command{ not specified defaults to the local user ($USER)`, Run: func(cmd *cobra.Command, args []string) { user := viper.GetString("user") - endpoint := viper.GetString("endpoint") identity := viper.GetString("identity") var profiles []profile viper.UnmarshalKey("profiles", &profiles) for _, p := range profiles { if user == p.User { - endpoint = p.Endpoint identity = p.Identity } } - var me saltyim.Addr + me := &saltyim.Addr{} if sp := strings.Split(user, "@"); len(sp) > 1 { me.User = sp[0] me.Domain = sp[1] } + // XXX: What if me.IsZero() prehook, err := cmd.Flags().GetString("pre-hook") if err != nil { @@ -50,13 +49,12 @@ not specified defaults to the local user ($USER)`, log.Fatal("error getting --post-hook flag") } - read(me, identity, endpoint, prehook, posthook, args...) + read(me, identity, prehook, posthook, args...) }, } type profile struct { User string - Endpoint string Identity string } @@ -74,8 +72,8 @@ func init() { ) } -func read(me saltyim.Addr, identity, endpoint string, prehook, posthook string, args ...string) { - cli, err := saltyim.NewClient(me, identity, endpoint) +func read(me *saltyim.Addr, identity string, prehook, posthook string, args ...string) { + cli, err := saltyim.NewClient(me, identity) if err != nil { fmt.Fprintf(os.Stderr, "error initializing client: %s\n", err) os.Exit(2) @@ -92,7 +90,7 @@ func read(me saltyim.Addr, identity, endpoint string, prehook, posthook string, cancel() }() - for msg := range cli.Read(ctx, endpoint, prehook, posthook) { + for msg := range cli.Read(ctx, prehook, posthook) { fmt.Println(saltyim.FormatMessage(msg)) } } diff --git a/cmd/salty-chat/register.go b/cmd/salty-chat/register.go index b0bc466..a69bcbc 100644 --- a/cmd/salty-chat/register.go +++ b/cmd/salty-chat/register.go @@ -17,33 +17,31 @@ var registerCmd = &cobra.Command{ Short: "Registers a new account with a broker", Long: `This command registers a new account with a broker. -A request is sent to the broker to the registration endpoint with the contents -of the user's public key and salty address, signed with the user's private key. - -If the broker can verify the request was signed correctly by the user a new -account is created and a Well-Known COnfing and Inbox created.`, +TBD`, Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { + fmt.Fprintln(os.Stderr, "✋ This is being re-designed. Stay tuned! 🤗") + os.Exit(1) + user := viper.GetString("user") - endpoint := viper.GetString("endpoint") identity := viper.GetString("identity") var profiles []profile viper.UnmarshalKey("profiles", &profiles) for _, p := range profiles { if user == p.User { - endpoint = p.Endpoint identity = p.Identity } } - var me saltyim.Addr + me := &saltyim.Addr{} if sp := strings.Split(user, "@"); len(sp) > 1 { me.User = sp[0] me.Domain = sp[1] } + // XXX: What if me.IsZero() - register(me, identity, endpoint) + register(me, identity) }, } @@ -51,15 +49,15 @@ func init() { rootCmd.AddCommand(registerCmd) } -func register(me saltyim.Addr, identity, endpoint string) { - cli, err := saltyim.NewClient(me, identity, endpoint) +func register(me *saltyim.Addr, identity string) { + cli, err := saltyim.NewClient(me, identity) if err != nil { fmt.Fprintf(os.Stderr, "error initializing client: %s\n", err) os.Exit(2) } if err := cli.Register(); err != nil { - fmt.Fprintf(os.Stderr, "error registering to %s: %s\n", endpoint, err) + fmt.Fprintf(os.Stderr, "error registering: %s\n", err) os.Exit(2) } fmt.Println("Success!") diff --git a/cmd/salty-chat/root.go b/cmd/salty-chat/root.go index d7346e1..a4dce1a 100644 --- a/cmd/salty-chat/root.go +++ b/cmd/salty-chat/root.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/mitchellh/go-homedir" + sync "github.com/sasha-s/go-deadlock" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -36,6 +37,9 @@ See https://salty.im for more details.`, log.SetLevel(log.DebugLevel) } else { log.SetLevel(log.InfoLevel) + + // Disable deadlock detection in production mode + sync.Opts.Disable = true } }, } @@ -73,11 +77,6 @@ func init() { "Use the identity file at PATH", ) - rootCmd.PersistentFlags().StringP( - "endpoint", "e", saltyim.DefaultEndpoint(), - "URI to connect to saltyim endpoint", - ) - viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug")) viper.SetDefault("debug", false) @@ -87,14 +86,11 @@ func init() { viper.BindPFlag("identity", rootCmd.PersistentFlags().Lookup("identity")) viper.SetDefault("identity", saltyim.DefaultIdentity()) - viper.BindPFlag("endpoint", rootCmd.PersistentFlags().Lookup("endpoint")) - viper.SetDefault("endpoint", saltyim.DefaultEndpoint()) + viper.BindPFlag("pre-hook", rootCmd.PersistentFlags().Lookup("pre-hook")) + viper.SetDefault("pre-hook", "") viper.BindPFlag("pre-hook", rootCmd.PersistentFlags().Lookup("pre-hook")) - viper.SetDefault("endpoint", saltyim.DefaultEndpoint()) - - viper.BindPFlag("pre-hook", rootCmd.PersistentFlags().Lookup("pre-hook")) - viper.SetDefault("endpoint", saltyim.DefaultEndpoint()) + viper.SetDefault("post-hook", "") } // initConfig reads in config file and ENV variables if set. diff --git a/cmd/salty-chat/send.go b/cmd/salty-chat/send.go index 9b58b89..45915c5 100644 --- a/cmd/salty-chat/send.go +++ b/cmd/salty-chat/send.go @@ -22,7 +22,7 @@ var sendCmd = &cobra.Command{ Short: "Send a message to a user", Long: `This command attempts to lookup the user's Salty Config by using the Salty IM Discovery process by making a request to the user's Well-Known URI -After it will attempt to send the message via a post to the endpoint. +After it will attempt to send the message via a HTTP POST to their Endpoint. The User is expected to have a Configuration file located at a Well-Known URI of: /.well-known/salty/.json @@ -35,25 +35,24 @@ https://mills.io/.well-known/salty/prologic.json`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { user := viper.GetString("user") - endpoint := viper.GetString("endpoint") identity := viper.GetString("identity") var profiles []profile viper.UnmarshalKey("profiles", &profiles) for _, p := range profiles { if user == p.User { - endpoint = p.Endpoint identity = p.Identity } } - var me saltyim.Addr + me := &saltyim.Addr{} if sp := strings.Split(user, "@"); len(sp) > 1 { me.User = sp[0] me.Domain = sp[1] } + // XXX: What if me.IsZero() - send(me, identity, endpoint, args[0], args[1:]...) + send(me, identity, args[0], args[1:]...) }, } @@ -61,14 +60,14 @@ func init() { rootCmd.AddCommand(sendCmd) } -func send(me saltyim.Addr, identity, endpoint, user string, args ...string) { +func send(me *saltyim.Addr, identity, user string, args ...string) { user = strings.TrimSpace(user) if user == "" { fmt.Fprintf(os.Stderr, "error: no user supplied\n") os.Exit(2) } - cli, err := saltyim.NewClient(me, identity, endpoint) + cli, err := saltyim.NewClient(me, identity) if err != nil { fmt.Fprintf(os.Stderr, "error initializing client: %s\n", err) os.Exit(2) diff --git a/cmd/saltyd/main.go b/cmd/saltyd/main.go index 8c13687..8494dbd 100644 --- a/cmd/saltyd/main.go +++ b/cmd/saltyd/main.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" + sync "github.com/sasha-s/go-deadlock" log "github.com/sirupsen/logrus" flag "github.com/spf13/pflag" profiler "github.com/wblakecaldwell/profiler" @@ -88,6 +89,9 @@ func main() { log.SetLevel(log.DebugLevel) } else { log.SetLevel(log.InfoLevel) + + // Disable deadlock detection in production mode + sync.Opts.Disable = true } svr, err := internal.NewServer(bind, diff --git a/format.go b/format.go index 12e893d..b899fc6 100644 --- a/format.go +++ b/format.go @@ -8,6 +8,7 @@ import ( "github.com/logrusorgru/aurora" "github.com/posener/formatter" + log "github.com/sirupsen/logrus" "go.yarn.social/lextwt" ) @@ -36,7 +37,7 @@ func FormatMessage(msg string) string { st, ok := s.(*lextwt.SaltyText) if !ok { - fmt.Fprintf(os.Stderr, "unexpected error") + log.Errorf("unexpected error, expected type lextwt.SaltyText got #%v", st) return "" } diff --git a/go.mod b/go.mod index aa52c70..ed0a2ca 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.17 require ( github.com/mitchellh/go-homedir v1.1.0 github.com/oklog/ulid/v2 v2.0.2 + github.com/sasha-s/go-deadlock v0.3.1 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.4.0 github.com/spf13/viper v1.10.1 @@ -37,6 +38,7 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/pelletier/go-toml v1.9.4 // indirect + github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/plar/go-adaptive-radix-tree v1.0.4 // indirect github.com/prometheus/client_golang v1.12.1 // indirect diff --git a/go.sum b/go.sum index 1ee3c8d..9de920d 100644 --- a/go.sum +++ b/go.sum @@ -415,6 +415,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= +github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -479,6 +481,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= +github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= +github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -565,8 +569,6 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3 go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= -go.mills.io/salty v0.0.0-20220318125419-fb3d6fc9e870 h1:fH4ftkY8i0Y2ycstDXmVmqxKyY+l4Gx4OvgxBm/wk8Q= -go.mills.io/salty v0.0.0-20220318125419-fb3d6fc9e870/go.mod h1:bQ9yvK7wwThD4tzoioJq/YAuwYOB2XA9tAUHIYtjre8= go.mills.io/salty v0.0.0-20220322161301-ce2b9f6573fa h1:KBxzYJMWP7MXd72RgqsMCGOSEqV6aaDDSdSb8usJCzQ= go.mills.io/salty v0.0.0-20220322161301-ce2b9f6573fa/go.mod h1:bQ9yvK7wwThD4tzoioJq/YAuwYOB2XA9tAUHIYtjre8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -1043,7 +1045,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/hooks/log-posthook.sh b/hooks/log-posthook.sh index f40a94a..23c8250 100755 --- a/hooks/log-posthook.sh +++ b/hooks/log-posthook.sh @@ -1,4 +1,3 @@ #!/bin/sh cat >> msgs -echo >> msgs diff --git a/identity.go b/identity.go index 471d32e..c723d55 100644 --- a/identity.go +++ b/identity.go @@ -12,20 +12,21 @@ import ( "go.mills.io/salty" ) -func readUser(fd io.Reader) (Addr, error) { +func readUser(fd io.Reader) (*Addr, error) { scan := bufio.NewScanner(fd) - var a Addr + addr := &Addr{} + for scan.Scan() { if strings.HasPrefix(scan.Text(), "# user:") { user := strings.Split(strings.TrimSpace(strings.TrimPrefix(scan.Text(), "# user:")), "@") if len(user) != 2 { - return Addr{}, nil + return nil, nil } - a.User, a.Domain = user[0], user[1] + addr.User, addr.Domain = user[0], user[1] } } - return a, scan.Err() + return addr, scan.Err() } // DefaultIdentity returns a default identity file (if one exists) otherwise @@ -64,7 +65,7 @@ func CreateIdentity(fn, user string) error { } // GetIdentity ... -func GetIdentity(fn string) (*keys.EdX25519Key, Addr, error) { +func GetIdentity(fn string) (*keys.EdX25519Key, *Addr, error) { // Handle unix home with `~` if strings.HasPrefix(fn, "~/") { dirname, _ := os.UserHomeDir() @@ -73,13 +74,13 @@ func GetIdentity(fn string) (*keys.EdX25519Key, Addr, error) { id, err := os.Open(fn) if err != nil { - return nil, Addr{}, fmt.Errorf("error opening identity %q: %s", fn, err) + return nil, nil, fmt.Errorf("error opening identity %q: %s", fn, err) } defer id.Close() key, err := salty.ParseIdentity(id) if err != nil { - return nil, Addr{}, fmt.Errorf("error reading identity %q: %s", fn, err) + return nil, nil, fmt.Errorf("error reading identity %q: %s", fn, err) } id.Seek(0, 0) diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 76f01b6..8cda46e 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -5,13 +5,13 @@ import ( "context" "fmt" "strings" - "sync" "github.com/dim13/crc24" "github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2/encoding" "github.com/posener/formatter" "github.com/rivo/tview" + sync "github.com/sasha-s/go-deadlock" log "github.com/sirupsen/logrus" "go.yarn.social/lextwt" @@ -31,7 +31,6 @@ type ChatTUI struct { mu sync.RWMutex cli *saltyim.Client - me saltyim.Addr user string config saltyim.Config @@ -129,15 +128,13 @@ func (c *ChatTUI) SetScreen(inCh <-chan string, outCh chan<- string) { app := tview.NewApplication() - title := fmt.Sprintf( - "Chatting as %s via %s with %s via %s", - c.cli.Me(), c.cli.Endpoint(), c.user, c.config.Endpoint, - ) + chatTitle := fmt.Sprintf("Chatting to %s via %s", c.user, c.config.Endpoint) + inputTitle := fmt.Sprintf("Connected to %s as %s", c.cli.Me().Endpoint(), c.cli.Me()) // Generate UI components. c.mu.RLock() - chatBox := NewChatBox(c.palette, title) - inputField := NewChatInput(c.palette, c.newMessageHandler(outCh)) + chatBox := NewChatBox(c.palette, chatTitle) + inputField := NewChatInput(c.palette, inputTitle, c.newMessageHandler(outCh)) c.mu.RUnlock() // Layout the widgets in flex view. @@ -160,7 +157,7 @@ func (c *ChatTUI) RunChat(inCh chan<- string, outCh <-chan string) { // Receives incoming messages on a separate goroutine to be non-blocking. go func() { - for msg := range c.cli.Read(ctx, "", "", "") { + for msg := range c.cli.Read(ctx, "", "") { inCh <- msg } }() diff --git a/internal/tui/tview.go b/internal/tui/tview.go index bc55518..2c225c6 100644 --- a/internal/tui/tview.go +++ b/internal/tui/tview.go @@ -38,11 +38,13 @@ func NewChatBox(palette map[string]string, title string) *tview.TextView { // NewChatInput initializes and returns a 'chatInput' component // that handles user inputs and forwards chat messages. -func NewChatInput(palette map[string]string, handler InputHandler) *tview.InputField { +func NewChatInput(palette map[string]string, title string, handler InputHandler) *tview.InputField { nLine := regexp.MustCompile(`\\n\s?`) // Initialize 'inputField' for user inputs. chatInput := tview.NewInputField() + chatInput.SetTitle(title). + SetTitleColor(hexToTCell(palette["title"])) chatInput.SetFieldBackgroundColor(tcell.ColorBlack). SetFieldWidth(0). SetFieldTextColor(hexToTCell(palette["text"])). diff --git a/lookup.go b/lookup.go index 525d88d..b12e437 100644 --- a/lookup.go +++ b/lookup.go @@ -7,64 +7,109 @@ import ( "io/ioutil" "net" "net/http" + "net/url" "strings" + + sync "github.com/sasha-s/go-deadlock" ) // Addr represents a Salty IM User's Address type Addr struct { + mu sync.RWMutex + User string Domain string - DiscoveryDomain string + + endpoint *url.URL + discoveredDomain string } // IsZero returns true if the address is empty -func (a Addr) IsZero() bool { +func (a *Addr) IsZero() bool { + a.mu.RLock() + defer a.mu.RUnlock() + return a.User == "" && a.Domain == "" } -func (a Addr) String() string { +func (a *Addr) String() string { return fmt.Sprintf("%s@%s", a.User, a.Domain) } // Hash returns the Hex(SHA256Sum()) of the Address -func (a Addr) Hash() string { +func (a *Addr) Hash() string { return fmt.Sprintf("%x", sha256.Sum256([]byte(a.String()))) } // Formatted returns a formatted user used in the Salty Message Format // ) \n -func (a Addr) Formatted() string { +func (a *Addr) Formatted() string { return fmt.Sprintf("(%s)", a) } +// Endpoint returns the discovered Endpoint +func (a *Addr) Endpoint() *url.URL { + a.mu.RLock() + defer a.mu.RUnlock() + + return a.endpoint + +} + +// DiscoveredDomain returns the discovered domain (if any) of fallbacks to the Domain +func (a *Addr) DiscoveredDomain() string { + a.mu.RLock() + defer a.mu.RUnlock() + + if a.discoveredDomain != "" { + return a.discoveredDomain + } + return a.Domain +} + // URI returns the Well-Known URI for this Addr -func (a Addr) URI() string { - return fmt.Sprintf("https://%s/.well-known/salty/%s.json", a.DiscoveryDomain, a.User) +func (a *Addr) URI() string { + return fmt.Sprintf("https://%s/.well-known/salty/%s.json", a.DiscoveredDomain(), a.User) } // HashURI returns the Well-Known HashURI for this Addr -func (a Addr) HashURI() string { - return fmt.Sprintf("https://%s/.well-known/salty/%s.json", a.DiscoveryDomain, a.Hash()) +func (a *Addr) HashURI() string { + return fmt.Sprintf("https://%s/.well-known/salty/%s.json", a.DiscoveredDomain(), a.Hash()) } -func (a *Addr) RefreshDiscovery() { +func (a *Addr) Refresh() error { + a.mu.Lock() + defer a.mu.Unlock() + _, records, err := net.LookupSRV("salty", "tcp", a.Domain) - if err != nil || len(records) == 0 { - return + if err == nil && len(records) > 0 { + a.discoveredDomain = records[0].Target } - a.DiscoveryDomain = records[0].Target + + config, err := Lookup(a.String()) + if err != nil { + return fmt.Errorf("error looking up endpoint for %s: %w", a, err) + } + + u, err := url.Parse(config.Endpoint) + if err != nil { + return fmt.Errorf("error parsing endpoint %s: %w", config.Endpoint, err) + } + a.endpoint = u + + return nil } // ParseAddr parsers a Salty Address for a user into it's user and domain // parts and returns an Addr object with the User and Domain and a method // for returning the expected User's Well-Known URI -func ParseAddr(addr string) (Addr, error) { +func ParseAddr(addr string) (*Addr, error) { parts := strings.Split(addr, "@") if len(parts) != 2 { - return Addr{}, fmt.Errorf("expected nick@domain found %q", addr) + return nil, fmt.Errorf("expected nick@domain found %q", addr) } - return Addr{User: parts[0], Domain: parts[1], DiscoveryDomain: parts[1]}, nil + return &Addr{User: parts[0], Domain: parts[1]}, nil } // Lookup looks up a Salty Address for a User by parsing the user's domain and @@ -75,7 +120,6 @@ func Lookup(addr string) (Config, error) { if err != nil { return Config{}, err } - a.RefreshDiscovery() config, err := fetchConfig(a.HashURI()) if err != nil { // Fallback to plain user nick