6
1
mirror of https://git.mills.io/saltyim/saltyim.git synced 2024-06-30 18:51:03 +00:00
prologic-saltyim/internal/pwa/components/saltychat.go
mlctrez fcc4f53f20 navigation drawer is now fixed for > 900px windows (#157)
Co-authored-by: mlctrez <mlctrez@gmail.com>
Reviewed-on: https://git.mills.io/saltyim/saltyim/pulls/157
Reviewed-by: James Mills <james@mills.io>
Co-authored-by: mlctrez <mlctrez@noreply@mills.io>
Co-committed-by: mlctrez <mlctrez@noreply@mills.io>
2022-04-07 03:43:09 +00:00

250 lines
6.4 KiB
Go

package components
import (
"time"
"github.com/maxence-charriere/go-app/v9/pkg/app"
"github.com/mlctrez/goapp-mdc/pkg/base"
"github.com/mlctrez/goapp-mdc/pkg/icon"
"github.com/mlctrez/goapp-mdc/pkg/textfield"
log "github.com/sirupsen/logrus"
"go.mills.io/saltyim"
"go.mills.io/saltyim/internal/pwa/storage"
"go.yarn.social/lextwt"
)
const (
descText = `salty.im is an open specification for a new Saltpack based e2e encrypted messaging protocol and platform for secure communications with a focus on privacy, security and being self-hosted.`
saltyChatRecvMessageAction = "saltychat.recv-message.action"
saltyChatSentMessageAction = "saltychat.sent-message.action"
)
var client *saltyim.Client
// SaltyChat ...
type SaltyChat struct {
app.Compo
base.JsUtil
isAppInstallable bool
dialog *ModalDialog
chatBox *ChatBox
friend string
chatInput *textfield.TextField
}
func (h *SaltyChat) init(ctx app.Context) {
ctx.Page().SetTitle("Salty Chat")
}
func (h *SaltyChat) load(ctx app.Context) {
ctx.Page().SetTitle("Salty Chat")
ctx.Page().SetDescription(descText)
}
func (h *SaltyChat) OnPreRender(ctx app.Context) {
h.init(ctx)
h.load(ctx)
}
func (h *SaltyChat) OnResize(ctx app.Context) {
h.ResizeContent()
}
func (h *SaltyChat) OnAppInstallChange(ctx app.Context) {
h.isAppInstallable = ctx.IsAppInstallable()
}
func (h *SaltyChat) OnNav(ctx app.Context) {
h.refreshMessages(ctx)
}
func (h *SaltyChat) refreshMessages(ctx app.Context) {
if ctx.Page().URL().Fragment == "" {
return
}
h.friend = ctx.Page().URL().Fragment
h.chatBox.User = h.friend
h.chatBox.UpdateMessages(ctx)
}
func (h *SaltyChat) OnMount(ctx app.Context) {
h.isAppInstallable = ctx.IsAppInstallable()
h.refreshMessages(ctx)
if app.IsClient {
h.connect(ctx)
} else {
log.Println("app not running as a client?")
}
ctx.Handle(saltyChatRecvMessageAction, h.incomingMessage)
ctx.Handle(saltyChatSentMessageAction, h.outgoingMessage)
}
func (h *SaltyChat) connect(ctx app.Context) {
log.Println("connect()")
if client != nil {
log.Println("already have a connected client")
return
}
log.Println("creating new client")
identity, err := GetIdentityFromState(ctx)
if err != nil {
h.dialog.ShowError("missing identity, please configure", err.Error())
return
}
state, err := GetStateFromState(ctx)
if err != nil {
log.Errorf("error loading state: %s", err)
state = saltyim.NewState()
}
clientIdentity := saltyim.WithClientIdentity(saltyim.WithIdentityBytes(identity.Contents()))
newClient, err := saltyim.NewClient(identity.Addr(), clientIdentity, saltyim.WithState(state))
if err != nil {
h.dialog.ShowError("error setting up client", err.Error())
return
}
newClient.SetSend(&saltyim.ProxySend{SendEndpoint: app.Getenv("SendEndpoint")})
newClient.SetLookup(&saltyim.ProxyLookup{LookupEndpoint: app.Getenv("LookupEndpoint")})
client = newClient
ctx.Async(func() {
stateCh := time.NewTicker(time.Second * 20)
inboxCh := client.Subscribe(ctx.Dispatcher().Context(), "", "", "")
outboxCh := client.OutboxClient(nil).Subscribe(ctx.Dispatcher().Context(), "", "", "")
for {
select {
case <-ctx.Done():
stateCh.Stop()
return
case msg := <-inboxCh:
// passing both the message and the text in case we need the message key at some point
ctx.NewActionWithValue(saltyChatRecvMessageAction, msg, app.T("text", msg.Text))
case msg := <-outboxCh:
// passing both the message and the text in case we need the message key at some point
ctx.NewActionWithValue(saltyChatSentMessageAction, msg, app.T("text", msg.Text))
case <-stateCh.C:
if err := SetStateToState(ctx, client.State()); err != nil {
log.WithError(err).Warn("error saving state")
}
}
}
})
}
func (h *SaltyChat) incomingMessage(ctx app.Context, action app.Action) {
messageText := action.Tags.Get("text")
s, err := lextwt.ParseSalty(messageText)
if err != nil {
h.dialog.ShowError("incoming message error", err.Error())
return
}
switch s := s.(type) {
case *lextwt.SaltyText:
user := s.User.String()
storage.ContactsLocalStorage(ctx).Add(user)
storage.ConversationsLocalStorage(ctx, user).Append(messageText)
// only update when incoming user's message is the active chat
if h.friend == user {
h.chatBox.UpdateMessages(ctx)
} else {
// TODO: Creates some initial content of the new chat
// to give the user a change to Accept/Reject the contact.
log.Printf("new incoming chat from %s", user)
}
}
}
func (h *SaltyChat) outgoingMessage(ctx app.Context, action app.Action) {
messageText := action.Tags.Get("text")
s, err := lextwt.ParseSalty(messageText)
if err != nil {
h.dialog.ShowError("outgoing message error", err.Error())
return
}
friend := ""
switch s := s.(type) {
case *lextwt.SaltyText:
friend = s.User.String()
storage.ContactsLocalStorage(ctx).Add(friend)
storage.ConversationsLocalStorage(ctx, friend).
Append(string(saltyim.PackMessageTime(client.Me(), s.LiteralText(), s.Timestamp)))
}
// only update when incoming user's message is the active chat
if h.friend == friend {
h.chatBox.UpdateMessages(ctx)
} else {
// TODO: Creates some initial content of the new chat
// to give the user a change to Accept/Reject the contact.
log.Printf("new incoming chat from %s", friend)
}
}
func (h *SaltyChat) Render() app.UI {
if h.chatBox == nil {
h.chatBox = &ChatBox{}
h.chatInput = &textfield.TextField{Id: "chat-input", Placeholder: "New Message"}
h.dialog = &ModalDialog{}
}
h.chatBox.User = h.friend
return PageBody(
app.Div().ID("wrapper").Body(
h.chatBox,
app.Form().OnSubmit(h.handleSendMessage).Body(
h.chatInput,
icon.MISend.Button().ID("chat-send"),
),
h.dialog,
),
)
}
func (h *SaltyChat) handleSendMessage(ctx app.Context, e app.Event) {
e.PreventDefault()
msg := h.chatInput.Value
h.chatInput.Value = ""
h.focusChatInput()
if msg == "" || h.friend == "" {
// nothing to send and no friend selected
log.Printf("no send since msg = %q or Friend = %q", msg, h.friend)
return
}
// determine current user to send message to and use client to send the message
if client != nil {
h.chatBox.User = h.friend
ctx.Async(func() {
if err := client.Send(h.friend, msg); err != nil {
h.dialog.ShowError("error sending message", err.Error())
}
})
}
}
func (h *SaltyChat) focusChatInput() {
chatInputValue := h.JsUtil.JsValueAtPath(h.chatInput.Id + "-input")
chatInputValue.Set("value", "")
chatInputValue.Call("focus")
}