mirror of
https://git.mills.io/saltyim/saltyim.git
synced 2024-06-16 11:58:24 +00:00
Refactor Endpoint for automatic endpoint discovery and less configuration (#46)
Co-authored-by: James Mills <prologic@shortcircuit.net.au> Reviewed-on: https://git.mills.io/prologic/saltyim/pulls/46
This commit is contained in:
parent
03a99bdccf
commit
7ffefb042e
68
client.go
68
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
|
||||
// <timestamp>\t(<sender>) <message>
|
||||
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
|
||||
}
|
||||
|
@ -15,31 +15,30 @@ import (
|
||||
var chatCmd = &cobra.Command{
|
||||
Use: "chat <user>",
|
||||
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)
|
||||
|
@ -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 <user> 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,
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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!")
|
||||
|
@ -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.
|
||||
|
@ -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/<sha256hex(user@domain.tld)>.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)
|
||||
|
@ -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,
|
||||
|
@ -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 ""
|
||||
}
|
||||
|
||||
|
2
go.mod
2
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
|
||||
|
7
go.sum
7
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=
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
cat >> msgs
|
||||
echo >> msgs
|
||||
|
17
identity.go
17
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)
|
||||
|
@ -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
|
||||
}
|
||||
}()
|
||||
|
@ -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"])).
|
||||
|
78
lookup.go
78
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
|
||||
// <timestamp\t(<user>) <message>\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
|
||||
|
Loading…
Reference in New Issue
Block a user