package main import ( "bytes" "encoding/base64" "encoding/json" "errors" "fmt" "fyne.io/fyne/v2/data/binding" "io" "net/http" "strings" "sync" "time" ) const ( Register = "/api/register" SendMessage = "/api/sendMessage" PollMessages = "/api/pollMessages" GetUserKey = "/api/getUserKey" TryAuth = "/api/tryAuth" ) var storage struct { sync.RWMutex messages []Message data []string binding binding.ExternalStringList } var userKeys = make(map[string]string) type Request struct { User string Data string } type Response struct { Message string } type Message struct { User string Data string Payload string Signature string } func (msg *Message) toString() string { return fmt.Sprintf("%s: %s", msg.User, msg.Data) } func makeRequest(request Request, apiMethod string, user UserData) (*http.Response, error) { signedRequest, err := signRequest(request, user.ParsedUserKey) if err != nil { return nil, err } reader := bytes.NewReader([]byte(signedRequest)) return http.Post(user.ServerUrl+apiMethod, "application/json", reader) } func sendMessage(user UserData, message string) { // TODO(andrew): Добавить отображение ошибки в интерфейс _, _ = makeRequest(Request{ User: user.Username, Data: message, }, SendMessage, user) } func register(user UserData, serverKey string) (Response, error) { var resp Response httpResp, err := makeRequest(Request{ User: user.Username, Data: serverKey, }, Register, user) if err != nil { return resp, err } body, _ := io.ReadAll(httpResp.Body) _ = json.Unmarshal(body, &resp) if httpResp.StatusCode == http.StatusOK { return resp, nil } else { return resp, errors.New(resp.Message) } } func tryAuth(user UserData) (bool, error) { var resp Response httpResp, err := makeRequest(Request{ User: user.Username, Data: "auth", }, TryAuth, user) if err != nil { return false, err } body, _ := io.ReadAll(httpResp.Body) _ = json.Unmarshal(body, &resp) if httpResp.StatusCode == http.StatusOK { return true, nil } else { return false, errors.New(resp.Message) } } func getUserKey(user UserData, checkedUser string) (string, error) { var resp Response httpResp, err := makeRequest(Request{ User: user.Username, Data: checkedUser, }, GetUserKey, user) if err != nil { return "", err } body, _ := io.ReadAll(httpResp.Body) _ = json.Unmarshal(body, &resp) if httpResp.StatusCode == http.StatusOK { return resp.Message, nil } else { return "", errors.New(resp.Message) } } func pingServer(user UserData) error { _, err := http.Get(user.ServerUrl) return err } func parseMessage(signedMessage string) (Message, error) { var msg Message parts := strings.Split(signedMessage, ".") if len(parts) != 2 { return msg, errors.New("request doesn't contain exactly two parts") } payload := parts[0] signature := parts[1] messageBody, err := base64.StdEncoding.DecodeString(payload) if err != nil { return msg, err } err = json.Unmarshal(messageBody, &msg) if err != nil { return msg, errors.New("unable to parse message") } msg.Payload = payload msg.Signature = signature return msg, nil } // TODO(andrew): Заменить сбор новых сообщений через http запрос к серверу на // получения обновлений через сокет от сервера. func runClient(user UserData) { var lastPoll int64 = 0 for { httpResp, _ := makeRequest(Request{ User: user.Username, Data: fmt.Sprint(lastPoll), }, PollMessages, user) lastPoll = time.Now().UnixNano() if httpResp.StatusCode == http.StatusOK { body, _ := io.ReadAll(httpResp.Body) var resp Response _ = json.Unmarshal(body, &resp) var signedMessages []string _ = json.Unmarshal([]byte(resp.Message), &signedMessages) storage.Lock() for _, signedMessage := range signedMessages { msg, _ := parseMessage(signedMessage) userKey, ok := userKeys[msg.User] if !ok { key, err := getUserKey(user, msg.User) if err != nil { fmt.Printf("Error occurred when polling user (%s) key\n", msg.User) continue } userKey = key userKeys[msg.User] = key } passed, err := checkSignature(msg.Payload, msg.Signature, userKey) if !passed { fmt.Printf("Message didn't pass signature check. From %s: %s\n", msg.User, msg.Data) continue } if err != nil { fmt.Printf( "Error occurred when checking signature of message. From %s: %s\n", msg.User, msg.Data) } fmt.Printf("Polled new message from %s: %s\n", msg.User, msg.Data) _ = storage.binding.Append(msg.toString()) storage.messages = append(storage.messages, msg) } storage.Unlock() } else { fmt.Println(httpResp.StatusCode) ae, _ := io.ReadAll(httpResp.Body) fmt.Println(string(ae)) } time.Sleep(100 * time.Millisecond) } }