improve server relay and client
This commit is contained in:
parent
9984583dd2
commit
fd0e2afddb
6 changed files with 461 additions and 109 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.env
|
||||||
|
contacts.json
|
||||||
|
identity.json
|
||||||
|
|
@ -7,30 +7,106 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gossip/pkg/protocol"
|
"gossip/pkg/protocol"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/textarea"
|
"github.com/charmbracelet/bubbles/textarea"
|
||||||
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
"github.com/charmbracelet/bubbles/viewport"
|
"github.com/charmbracelet/bubbles/viewport"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"golang.org/x/crypto/nacl/box"
|
"golang.org/x/crypto/nacl/box"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IdentityFile = "identity.json"
|
||||||
|
ContactsFile = "contacts.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AppMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ModeNormal AppMode = iota
|
||||||
|
ModeInsert
|
||||||
|
ModeConnect
|
||||||
|
ModeList
|
||||||
|
)
|
||||||
|
|
||||||
type KeyPair struct {
|
type KeyPair struct {
|
||||||
Public *[32]byte
|
Public *[32]byte
|
||||||
Private *[32]byte
|
Private *[32]byte
|
||||||
PubHex string
|
PubHex string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StoredIdentity struct {
|
||||||
|
PublicKey string `json:"public_key"`
|
||||||
|
PrivateKey string `json:"private_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadOrGenerateKeys() (KeyPair, error) {
|
||||||
|
fileBytes, err := os.ReadFile(IdentityFile)
|
||||||
|
if err == nil {
|
||||||
|
var stored StoredIdentity
|
||||||
|
if err := json.Unmarshal(fileBytes, &stored); err == nil {
|
||||||
|
pubBytes, _ := hex.DecodeString(stored.PublicKey)
|
||||||
|
privBytes, _ := hex.DecodeString(stored.PrivateKey)
|
||||||
|
var pubKey, privKey [32]byte
|
||||||
|
copy(pubKey[:], pubBytes)
|
||||||
|
copy(privKey[:], privBytes)
|
||||||
|
return KeyPair{Public: &pubKey, Private: &privKey, PubHex: stored.PublicKey}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub, priv, err := box.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return KeyPair{}, err
|
||||||
|
}
|
||||||
|
kp := KeyPair{Public: pub, Private: priv, PubHex: hex.EncodeToString(pub[:])}
|
||||||
|
saveData := StoredIdentity{PublicKey: kp.PubHex, PrivateKey: hex.EncodeToString(priv[:])}
|
||||||
|
bytes, _ := json.MarshalIndent(saveData, "", " ")
|
||||||
|
_ = os.WriteFile(IdentityFile, bytes, 0600)
|
||||||
|
return kp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContactBook map[string]string
|
||||||
|
|
||||||
|
func loadContacts() ContactBook {
|
||||||
|
cb := make(ContactBook)
|
||||||
|
data, err := os.ReadFile(ContactsFile)
|
||||||
|
if err == nil {
|
||||||
|
json.Unmarshal(data, &cb)
|
||||||
|
}
|
||||||
|
return cb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb ContactBook) save() {
|
||||||
|
data, _ := json.MarshalIndent(cb, "", " ")
|
||||||
|
os.WriteFile(ContactsFile, data, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
type model struct {
|
type model struct {
|
||||||
conn *websocket.Conn
|
conn *websocket.Conn
|
||||||
keys KeyPair
|
keys KeyPair
|
||||||
|
contacts ContactBook
|
||||||
|
|
||||||
|
mode AppMode
|
||||||
|
myAccountNum string
|
||||||
|
|
||||||
|
targetAcc string
|
||||||
|
targetName string
|
||||||
targetHex string
|
targetHex string
|
||||||
targetPub *[32]byte
|
targetPub *[32]byte
|
||||||
|
|
||||||
viewport viewport.Model
|
viewport viewport.Model
|
||||||
textarea textarea.Model
|
textarea textarea.Model
|
||||||
|
|
||||||
|
connectInput textinput.Model
|
||||||
|
|
||||||
|
contactList []string
|
||||||
|
listCursor int
|
||||||
|
|
||||||
messages []string
|
messages []string
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
@ -38,35 +114,42 @@ type model struct {
|
||||||
type wsMsg protocol.Message
|
type wsMsg protocol.Message
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
pub, priv, err := box.GenerateKey(rand.Reader)
|
keys, err := loadOrGenerateKeys()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
keys := KeyPair{Public: pub, Private: priv, PubHex: hex.EncodeToString(pub[:])}
|
contacts := loadContacts()
|
||||||
|
|
||||||
c, _, err := websocket.DefaultDialer.Dial("ws://localhost:8080/ws", nil)
|
c, _, err := websocket.DefaultDialer.Dial("ws://localhost:8080/ws", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not connect to server:", err)
|
log.Fatal("Could not connect:", err)
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
loginMsg := protocol.Message{Type: "login", Sender: keys.PubHex}
|
c.WriteJSON(protocol.Message{Type: protocol.TypeLogin, Sender: keys.PubHex})
|
||||||
c.WriteJSON(loginMsg)
|
|
||||||
|
|
||||||
ta := textarea.New()
|
ta := textarea.New()
|
||||||
ta.Placeholder = "Type a message (or /connect <PUBKEY>)..."
|
ta.Placeholder = "Type message..."
|
||||||
ta.Focus()
|
ta.SetHeight(3)
|
||||||
ta.SetHeight(2)
|
|
||||||
ta.ShowLineNumbers = false
|
ta.ShowLineNumbers = false
|
||||||
|
ta.Blur()
|
||||||
|
|
||||||
|
ti := textinput.New()
|
||||||
|
ti.Placeholder = "Enter Name or Account #"
|
||||||
|
ti.CharLimit = 20
|
||||||
|
ti.Width = 30
|
||||||
|
|
||||||
vp := viewport.New(80, 20)
|
vp := viewport.New(80, 20)
|
||||||
vp.SetContent(fmt.Sprintf("Your ID: %s\nTo start, type: /connect <FRIEND_ID>", keys.PubHex))
|
vp.SetContent("Welcome. Press 'i' to type, 'c' to connect, 'l' for contacts.")
|
||||||
|
|
||||||
m := model{
|
m := model{
|
||||||
conn: c,
|
conn: c,
|
||||||
keys: keys,
|
keys: keys,
|
||||||
|
contacts: contacts,
|
||||||
textarea: ta,
|
textarea: ta,
|
||||||
|
connectInput: ti,
|
||||||
viewport: vp,
|
viewport: vp,
|
||||||
|
mode: ModeNormal,
|
||||||
}
|
}
|
||||||
|
|
||||||
p := tea.NewProgram(m)
|
p := tea.NewProgram(m)
|
||||||
|
|
@ -88,96 +171,239 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd {
|
func (m model) Init() tea.Cmd { return textarea.Blink }
|
||||||
return textarea.Blink
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
var (
|
var cmd tea.Cmd
|
||||||
tiCmd tea.Cmd
|
var cmds []tea.Cmd
|
||||||
vpCmd tea.Cmd
|
|
||||||
)
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
switch msg.Type {
|
switch m.mode {
|
||||||
case tea.KeyCtrlC, tea.KeyEsc:
|
|
||||||
|
case ModeNormal:
|
||||||
|
switch msg.String() {
|
||||||
|
case "ctrl+c", "q":
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
case tea.KeyEnter:
|
case "i":
|
||||||
input := m.textarea.Value()
|
m.mode = ModeInsert
|
||||||
|
m.textarea.Focus()
|
||||||
|
m.textarea.CursorEnd()
|
||||||
|
return m, nil
|
||||||
|
case "c":
|
||||||
|
m.mode = ModeConnect
|
||||||
|
m.connectInput.Reset()
|
||||||
|
m.connectInput.Focus()
|
||||||
|
return m, nil
|
||||||
|
case "l":
|
||||||
|
m.mode = ModeList
|
||||||
|
m.contactList = []string{}
|
||||||
|
for k := range m.contacts {
|
||||||
|
m.contactList = append(m.contactList, k)
|
||||||
|
}
|
||||||
|
sort.Strings(m.contactList)
|
||||||
|
m.listCursor = 0
|
||||||
|
return m, nil
|
||||||
|
case "d":
|
||||||
|
m.targetPub = nil
|
||||||
|
m.targetAcc = ""
|
||||||
|
m.targetName = ""
|
||||||
|
m.messages = append(m.messages, "System: Disconnected.")
|
||||||
|
m.viewport.SetContent(strings.Join(m.messages, "\n"))
|
||||||
|
m.viewport.GotoBottom()
|
||||||
|
return m, nil
|
||||||
|
case "enter":
|
||||||
|
input := strings.TrimSpace(m.textarea.Value())
|
||||||
if input == "" {
|
if input == "" {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if dest, ok := strings.CutPrefix(input, "/connect "); ok {
|
|
||||||
m.targetHex = dest
|
|
||||||
|
|
||||||
decoded, _ := hex.DecodeString(dest)
|
|
||||||
var keyArr [32]byte
|
|
||||||
copy(keyArr[:], decoded)
|
|
||||||
m.targetPub = &keyArr
|
|
||||||
|
|
||||||
m.messages = append(m.messages, "System: Target set to "+dest[:8]+"...")
|
|
||||||
m.viewport.SetContent(strings.Join(m.messages, "\n"))
|
|
||||||
m.textarea.Reset()
|
|
||||||
m.viewport.GotoBottom()
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.targetPub == nil {
|
if m.targetPub == nil {
|
||||||
m.messages = append(m.messages, "System: No target set! Use /connect first.")
|
m.messages = append(m.messages, "System: ⛔ Not connected! Press 'c' to connect.")
|
||||||
} else {
|
} else {
|
||||||
var nonce [24]byte
|
var nonce [24]byte
|
||||||
rand.Read(nonce[:])
|
rand.Read(nonce[:])
|
||||||
encrypted := box.Seal(nonce[:], []byte(input), &nonce, m.targetPub, m.keys.Private)
|
encrypted := box.Seal(nonce[:], []byte(input), &nonce, m.targetPub, m.keys.Private)
|
||||||
b64Content := base64.StdEncoding.EncodeToString(encrypted)
|
|
||||||
|
|
||||||
outMsg := protocol.Message{
|
outMsg := protocol.Message{
|
||||||
Type: "msg",
|
Type: protocol.TypeMsg,
|
||||||
Sender: m.keys.PubHex,
|
Sender: m.keys.PubHex,
|
||||||
Target: m.targetHex,
|
Target: m.targetHex,
|
||||||
Content: b64Content,
|
Content: base64.StdEncoding.EncodeToString(encrypted),
|
||||||
}
|
}
|
||||||
|
|
||||||
m.conn.WriteJSON(outMsg)
|
m.conn.WriteJSON(outMsg)
|
||||||
|
|
||||||
m.messages = append(m.messages, "Me: "+input)
|
m.messages = append(m.messages, "Me: "+input)
|
||||||
}
|
|
||||||
|
|
||||||
m.textarea.Reset()
|
m.textarea.Reset()
|
||||||
|
}
|
||||||
m.viewport.SetContent(strings.Join(m.messages, "\n"))
|
m.viewport.SetContent(strings.Join(m.messages, "\n"))
|
||||||
m.viewport.GotoBottom()
|
m.viewport.GotoBottom()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ModeInsert:
|
||||||
|
if msg.String() == "esc" {
|
||||||
|
m.mode = ModeNormal
|
||||||
|
m.textarea.Blur()
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case ModeConnect:
|
||||||
|
if msg.String() == "esc" {
|
||||||
|
m.mode = ModeNormal
|
||||||
|
m.connectInput.Blur()
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
if msg.String() == "enter" {
|
||||||
|
target := m.connectInput.Value()
|
||||||
|
if num, ok := m.contacts[target]; ok {
|
||||||
|
m.targetName = target
|
||||||
|
target = num
|
||||||
|
} else {
|
||||||
|
m.targetName = target
|
||||||
|
}
|
||||||
|
req := protocol.Message{
|
||||||
|
Type: protocol.TypeLookup,
|
||||||
|
Sender: m.keys.PubHex,
|
||||||
|
Content: target,
|
||||||
|
}
|
||||||
|
m.conn.WriteJSON(req)
|
||||||
|
m.messages = append(m.messages, fmt.Sprintf("System: Connecting to %s...", m.targetName))
|
||||||
|
m.viewport.SetContent(strings.Join(m.messages, "\n"))
|
||||||
|
m.viewport.GotoBottom()
|
||||||
|
m.mode = ModeNormal
|
||||||
|
m.connectInput.Blur()
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case ModeList:
|
||||||
|
switch msg.String() {
|
||||||
|
case "esc", "q":
|
||||||
|
m.mode = ModeNormal
|
||||||
|
return m, nil
|
||||||
|
case "k", "up":
|
||||||
|
if m.listCursor > 0 {
|
||||||
|
m.listCursor--
|
||||||
|
}
|
||||||
|
case "j", "down":
|
||||||
|
if m.listCursor < len(m.contactList)-1 {
|
||||||
|
m.listCursor++
|
||||||
|
}
|
||||||
|
case "enter":
|
||||||
|
if len(m.contactList) > 0 {
|
||||||
|
selection := m.contactList[m.listCursor]
|
||||||
|
targetNum := m.contacts[selection]
|
||||||
|
m.targetName = selection
|
||||||
|
req := protocol.Message{
|
||||||
|
Type: protocol.TypeLookup,
|
||||||
|
Sender: m.keys.PubHex,
|
||||||
|
Content: targetNum,
|
||||||
|
}
|
||||||
|
m.conn.WriteJSON(req)
|
||||||
|
m.messages = append(m.messages, fmt.Sprintf("System: Connecting to %s...", m.targetName))
|
||||||
|
m.viewport.SetContent(strings.Join(m.messages, "\n"))
|
||||||
|
m.viewport.GotoBottom()
|
||||||
|
}
|
||||||
|
m.mode = ModeNormal
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case wsMsg:
|
case wsMsg:
|
||||||
|
switch msg.Type {
|
||||||
|
case protocol.TypeIdentity:
|
||||||
|
m.myAccountNum = msg.Content
|
||||||
|
m.messages = append(m.messages, fmt.Sprintf("System: 🟢 Online. Account #%s", m.myAccountNum))
|
||||||
|
case protocol.TypeLookupResponse:
|
||||||
|
if msg.Content == "" {
|
||||||
|
m.messages = append(m.messages, fmt.Sprintf("System: ❌ Account #%s not found.", msg.Target))
|
||||||
|
} else {
|
||||||
|
m.targetAcc = msg.Target
|
||||||
|
m.targetHex = msg.Content
|
||||||
|
decoded, _ := hex.DecodeString(m.targetHex)
|
||||||
|
var keyArr [32]byte
|
||||||
|
copy(keyArr[:], decoded)
|
||||||
|
m.targetPub = &keyArr
|
||||||
|
displayName := m.targetName
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = m.targetAcc
|
||||||
|
}
|
||||||
|
m.messages = append(m.messages, fmt.Sprintf("System: 🔒 Secure channel established with %s", displayName))
|
||||||
|
}
|
||||||
|
case protocol.TypeMsg:
|
||||||
encBytes, _ := base64.StdEncoding.DecodeString(msg.Content)
|
encBytes, _ := base64.StdEncoding.DecodeString(msg.Content)
|
||||||
|
|
||||||
senderBytes, _ := hex.DecodeString(msg.Sender)
|
senderBytes, _ := hex.DecodeString(msg.Sender)
|
||||||
var senderKey [32]byte
|
var senderKey [32]byte
|
||||||
copy(senderKey[:], senderBytes)
|
copy(senderKey[:], senderBytes)
|
||||||
|
|
||||||
var nonce [24]byte
|
var nonce [24]byte
|
||||||
copy(nonce[:], encBytes[:24])
|
copy(nonce[:], encBytes[:24])
|
||||||
decrypted, ok := box.Open(nil, encBytes[24:], &nonce, &senderKey, m.keys.Private)
|
decrypted, ok := box.Open(nil, encBytes[24:], &nonce, &senderKey, m.keys.Private)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
m.messages = append(m.messages, "System: Failed to decrypt message from "+msg.Sender[:8])
|
m.messages = append(m.messages, "System: ⚠️ Decryption failed!")
|
||||||
} else {
|
} else {
|
||||||
m.messages = append(m.messages, fmt.Sprintf("Friend (%s): %s", msg.Sender[:8], string(decrypted)))
|
m.messages = append(m.messages, fmt.Sprintf("%s: %s", m.targetName, string(decrypted)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m.viewport.SetContent(strings.Join(m.messages, "\n"))
|
m.viewport.SetContent(strings.Join(m.messages, "\n"))
|
||||||
m.viewport.GotoBottom()
|
m.viewport.GotoBottom()
|
||||||
}
|
}
|
||||||
|
|
||||||
m.textarea, tiCmd = m.textarea.Update(msg)
|
m.viewport, cmd = m.viewport.Update(msg)
|
||||||
m.viewport, vpCmd = m.viewport.Update(msg)
|
cmds = append(cmds, cmd)
|
||||||
return m, tea.Batch(tiCmd, vpCmd)
|
|
||||||
|
if m.mode == ModeInsert {
|
||||||
|
m.textarea, cmd = m.textarea.Update(msg)
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.mode == ModeConnect {
|
||||||
|
m.connectInput, cmd = m.connectInput.Update(msg)
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) View() string {
|
func (m model) View() string {
|
||||||
return fmt.Sprintf(
|
var status string
|
||||||
"%s\n\n%s",
|
var modeColor string
|
||||||
m.viewport.View(),
|
|
||||||
m.textarea.View(),
|
switch m.mode {
|
||||||
) + "\n\nPress Esc to quit."
|
case ModeNormal:
|
||||||
|
status = "-- NORMAL -- (i: insert, c: connect, l: list, d: disconnect)"
|
||||||
|
modeColor = "\033[1;34m" // Blue
|
||||||
|
case ModeInsert:
|
||||||
|
status = "-- INSERT -- (Esc: normal, Enter: newline)"
|
||||||
|
modeColor = "\033[1;32m" // Green
|
||||||
|
case ModeConnect:
|
||||||
|
status = "-- CONNECT -- (Enter: confirm, Esc: cancel)"
|
||||||
|
modeColor = "\033[1;33m" // Yellow
|
||||||
|
case ModeList:
|
||||||
|
status = "-- CONTACTS -- (j/k: move, Enter: select)"
|
||||||
|
modeColor = "\033[1;35m" // Purple
|
||||||
|
}
|
||||||
|
|
||||||
|
var mainView string
|
||||||
|
|
||||||
|
if m.mode == ModeConnect {
|
||||||
|
dialog := fmt.Sprintf(
|
||||||
|
"Connect to User:\n\n%s",
|
||||||
|
m.connectInput.View(),
|
||||||
|
)
|
||||||
|
mainView = fmt.Sprintf("\n\n %s\n\n", dialog)
|
||||||
|
} else if m.mode == ModeList {
|
||||||
|
var items []string
|
||||||
|
items = append(items, "Select a Contact:\n")
|
||||||
|
for i, name := range m.contactList {
|
||||||
|
cursor := " "
|
||||||
|
if i == m.listCursor {
|
||||||
|
cursor = ">"
|
||||||
|
}
|
||||||
|
items = append(items, fmt.Sprintf("%s %s (#%s)", cursor, name, m.contacts[name]))
|
||||||
|
}
|
||||||
|
if len(m.contactList) == 0 {
|
||||||
|
items = append(items, " (No contacts saved. Use /add in Insert mode or edit contacts.json)")
|
||||||
|
}
|
||||||
|
mainView = strings.Join(items, "\n")
|
||||||
|
} else {
|
||||||
|
mainView = fmt.Sprintf("%s\n\n%s", m.viewport.View(), m.textarea.View())
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s%s\033[0m\n\n%s", modeColor, status, mainView)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,62 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"gossip/pkg/protocol"
|
"gossip/pkg/protocol"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
_ "github.com/jackc/pgx/v5/stdlib"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
db *sql.DB
|
||||||
clients map[string]*websocket.Conn
|
clients map[string]*websocket.Conn
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
upgrader websocket.Upgrader
|
upgrader websocket.Upgrader
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if err := godotenv.Load(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbuser := os.Getenv("DBUSER")
|
||||||
|
dbname := os.Getenv("DBNAME")
|
||||||
|
dbport := os.Getenv("DBPORT")
|
||||||
|
dbhost := os.Getenv("DBHOST")
|
||||||
|
dbpass := os.Getenv("DBPASS")
|
||||||
|
|
||||||
|
connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=require", dbuser, dbpass, dbhost, dbport, dbname)
|
||||||
|
|
||||||
|
db, err := sql.Open("pgx", connStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to open DB:", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
if err := db.Ping(); err != nil {
|
||||||
|
log.Fatal("Failed to connect to DB:", err)
|
||||||
|
}
|
||||||
|
|
||||||
srv := &Server{
|
srv := &Server{
|
||||||
|
db: db,
|
||||||
clients: make(map[string]*websocket.Conn),
|
clients: make(map[string]*websocket.Conn),
|
||||||
upgrader: websocket.Upgrader{
|
upgrader: websocket.Upgrader{
|
||||||
ReadBufferSize: 1024,
|
|
||||||
WriteBufferSize: 1024,
|
|
||||||
CheckOrigin: func(r *http.Request) bool { return true },
|
CheckOrigin: func(r *http.Request) bool { return true },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
http.HandleFunc("/ws", srv.handleWS)
|
http.HandleFunc("/ws", srv.handleWS)
|
||||||
log.Println("Relay Server listening on :8080")
|
log.Println("Gossip Relay Server listening on :8080")
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,22 +82,47 @@ func (s *Server) handleWS(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case "login":
|
case protocol.TypeLogin:
|
||||||
s.mu.Lock()
|
|
||||||
s.clients[msg.Sender] = conn
|
|
||||||
s.mu.Unlock()
|
|
||||||
myPubKey = msg.Sender
|
myPubKey = msg.Sender
|
||||||
log.Printf("Client connected: %s...", myPubKey[:8])
|
|
||||||
case "msg":
|
s.mu.Lock()
|
||||||
|
s.clients[myPubKey] = conn
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
accNum, err := s.getOrCreateUser(myPubKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("DB Error:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := protocol.Message{
|
||||||
|
Type: protocol.TypeIdentity,
|
||||||
|
Target: myPubKey,
|
||||||
|
Content: strconv.Itoa(accNum),
|
||||||
|
}
|
||||||
|
conn.WriteJSON(resp)
|
||||||
|
log.Printf("User %d connected (%s...)", accNum, myPubKey[:8])
|
||||||
|
|
||||||
|
case protocol.TypeLookup:
|
||||||
|
targetAcc, _ := strconv.Atoi(msg.Content)
|
||||||
|
foundKey, ok := s.lookupKey(targetAcc)
|
||||||
|
|
||||||
|
resp := protocol.Message{
|
||||||
|
Type: protocol.TypeLookupResponse,
|
||||||
|
Target: msg.Content,
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
resp.Content = foundKey
|
||||||
|
}
|
||||||
|
conn.WriteJSON(resp)
|
||||||
|
|
||||||
|
case protocol.TypeMsg:
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
targetConn, ok := s.clients[msg.Target]
|
targetConn, ok := s.clients[msg.Target]
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
err = targetConn.WriteMessage(websocket.TextMessage, data)
|
targetConn.WriteMessage(websocket.TextMessage, data)
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to relay to %s", msg.Target[:8])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +131,24 @@ func (s *Server) handleWS(w http.ResponseWriter, r *http.Request) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
delete(s.clients, myPubKey)
|
delete(s.clients, myPubKey)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
log.Printf("Client disconnected: %s...", myPubKey[:8])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) getOrCreateUser(pubKey string) (int, error) {
|
||||||
|
var accNum int
|
||||||
|
err := s.db.QueryRow("SELECT account_number FROM users WHERE public_key=$1", pubKey).Scan(&accNum)
|
||||||
|
if err == nil {
|
||||||
|
return accNum, nil
|
||||||
|
}
|
||||||
|
err = s.db.QueryRow("INSERT INTO users (public_key) VALUES ($1) RETURNING account_number", pubKey).Scan(&accNum)
|
||||||
|
return accNum, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) lookupKey(accNum int) (string, bool) {
|
||||||
|
var pubKey string
|
||||||
|
err := s.db.QueryRow("SELECT public_key FROM users WHERE account_number=$1", accNum).Scan(&pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return pubKey, true
|
||||||
|
}
|
||||||
|
|
|
||||||
6
go.mod
6
go.mod
|
|
@ -6,6 +6,8 @@ require (
|
||||||
github.com/charmbracelet/bubbles v0.21.0
|
github.com/charmbracelet/bubbles v0.21.0
|
||||||
github.com/charmbracelet/bubbletea v1.3.10
|
github.com/charmbracelet/bubbletea v1.3.10
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
|
github.com/jackc/pgx/v5 v5.7.6
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
golang.org/x/crypto v0.45.0
|
golang.org/x/crypto v0.45.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -18,6 +20,9 @@ require (
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
|
|
@ -27,6 +32,7 @@ require (
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.31.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
26
go.sum
26
go.sum
|
|
@ -20,10 +20,23 @@ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0G
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
|
@ -38,18 +51,31 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||||
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,28 @@
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
|
// MessageType ensures we only use valid protocol commands
|
||||||
|
type MessageType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TypeLogin: Client -> Server (Handshake with Public Key)
|
||||||
|
TypeLogin MessageType = "login"
|
||||||
|
|
||||||
|
// TypeIdentity: Server -> Client (Here is your Account Number)
|
||||||
|
TypeIdentity MessageType = "identity"
|
||||||
|
|
||||||
|
// TypeLookup: Client -> Server (Who owns Account #10050?)
|
||||||
|
TypeLookup MessageType = "lookup"
|
||||||
|
|
||||||
|
// TypeLookupResponse: Server -> Client (Here is the Public Key for #10050)
|
||||||
|
TypeLookupResponse MessageType = "lookup_response"
|
||||||
|
|
||||||
|
// TypeMsg: Client -> Server -> Client (Encrypted Payload)
|
||||||
|
TypeMsg MessageType = "msg"
|
||||||
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Type string `json:"type"`
|
Type MessageType `json:"type"`
|
||||||
Sender string `json:"sender"`
|
Sender string `json:"sender"` // Hex Public Key
|
||||||
Target string `json:"target"`
|
Target string `json:"target"` // Hex Public Key (msg) or Account # (lookup)
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue