2022-03-21 22:27:35 +00:00
package components
import (
2022-04-07 03:43:09 +00:00
"time"
2022-03-24 22:48:51 +00:00
2022-03-21 22:27:35 +00:00
"github.com/maxence-charriere/go-app/v9/pkg/app"
2022-03-23 17:20:17 +00:00
"github.com/mlctrez/goapp-mdc/pkg/base"
2022-03-23 04:06:56 +00:00
"github.com/mlctrez/goapp-mdc/pkg/icon"
"github.com/mlctrez/goapp-mdc/pkg/textfield"
2022-04-07 03:43:09 +00:00
log "github.com/sirupsen/logrus"
2022-03-24 22:48:51 +00:00
"go.mills.io/saltyim"
2022-03-28 21:49:01 +00:00
"go.mills.io/saltyim/internal/pwa/storage"
"go.yarn.social/lextwt"
2022-03-21 22:27:35 +00:00
)
2022-03-22 04:04:47 +00:00
const (
2022-04-02 22:32:12 +00:00
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"
2022-03-22 04:04:47 +00:00
)
2022-03-28 21:49:01 +00:00
var client * saltyim . Client
2022-03-25 11:28:30 +00:00
// SaltyChat ...
type SaltyChat struct {
2022-03-21 22:27:35 +00:00
app . Compo
2022-03-23 17:20:17 +00:00
base . JsUtil
2022-03-23 04:06:56 +00:00
isAppInstallable bool
2022-03-24 22:48:51 +00:00
2022-04-07 03:43:09 +00:00
dialog * ModalDialog
chatBox * ChatBox
2022-03-25 04:20:16 +00:00
2022-03-30 00:35:43 +00:00
friend string
2022-03-24 22:48:51 +00:00
chatInput * textfield . TextField
2022-03-22 04:43:05 +00:00
}
2022-03-25 11:28:30 +00:00
func ( h * SaltyChat ) init ( ctx app . Context ) {
2022-03-25 06:49:42 +00:00
ctx . Page ( ) . SetTitle ( "Salty Chat" )
2022-03-22 04:43:05 +00:00
}
2022-03-25 11:28:30 +00:00
func ( h * SaltyChat ) load ( ctx app . Context ) {
2022-03-22 04:48:54 +00:00
ctx . Page ( ) . SetTitle ( "Salty Chat" )
2022-03-22 04:43:05 +00:00
ctx . Page ( ) . SetDescription ( descText )
}
2022-03-25 11:28:30 +00:00
func ( h * SaltyChat ) OnPreRender ( ctx app . Context ) {
2022-03-22 04:43:05 +00:00
h . init ( ctx )
h . load ( ctx )
}
2022-03-25 11:28:30 +00:00
func ( h * SaltyChat ) OnResize ( ctx app . Context ) {
2022-03-22 04:43:05 +00:00
h . ResizeContent ( )
2022-03-22 04:04:47 +00:00
}
2022-03-25 11:28:30 +00:00
func ( h * SaltyChat ) OnAppInstallChange ( ctx app . Context ) {
2022-03-22 04:04:47 +00:00
h . isAppInstallable = ctx . IsAppInstallable ( )
}
2022-03-28 21:49:01 +00:00
func ( h * SaltyChat ) OnNav ( ctx app . Context ) {
h . refreshMessages ( ctx )
}
func ( h * SaltyChat ) refreshMessages ( ctx app . Context ) {
if ctx . Page ( ) . URL ( ) . Fragment == "" {
return
}
2022-03-30 00:35:43 +00:00
h . friend = ctx . Page ( ) . URL ( ) . Fragment
h . chatBox . User = h . friend
2022-03-28 21:49:01 +00:00
h . chatBox . UpdateMessages ( ctx )
}
2022-03-25 11:28:30 +00:00
func ( h * SaltyChat ) OnMount ( ctx app . Context ) {
2022-03-28 21:49:01 +00:00
2022-03-22 04:04:47 +00:00
h . isAppInstallable = ctx . IsAppInstallable ( )
2022-03-28 21:49:01 +00:00
h . refreshMessages ( ctx )
2022-03-24 22:48:51 +00:00
if app . IsClient {
h . connect ( ctx )
2022-03-29 22:06:06 +00:00
} else {
log . Println ( "app not running as a client?" )
2022-03-24 22:48:51 +00:00
}
2022-04-02 22:32:12 +00:00
ctx . Handle ( saltyChatRecvMessageAction , h . incomingMessage )
ctx . Handle ( saltyChatSentMessageAction , h . outgoingMessage )
2022-03-24 22:48:51 +00:00
}
2022-03-25 11:28:30 +00:00
func ( h * SaltyChat ) connect ( ctx app . Context ) {
2022-03-29 22:06:06 +00:00
log . Println ( "connect()" )
2022-03-28 21:49:01 +00:00
if client != nil {
2022-03-29 22:06:06 +00:00
log . Println ( "already have a connected client" )
2022-03-24 22:48:51 +00:00
return
}
2022-03-29 22:06:06 +00:00
log . Println ( "creating new client" )
2022-03-24 22:48:51 +00:00
identity , err := GetIdentityFromState ( ctx )
if err != nil {
2022-04-04 16:04:38 +00:00
h . dialog . ShowError ( "missing identity, please configure" , err . Error ( ) )
2022-03-24 22:48:51 +00:00
return
}
2022-04-07 03:43:09 +00:00
state , err := GetStateFromState ( ctx )
if err != nil {
log . Errorf ( "error loading state: %s" , err )
state = saltyim . NewState ( )
}
clientIdentity := saltyim . WithClientIdentity ( saltyim . WithIdentityBytes ( identity . Contents ( ) ) )
2023-01-25 23:05:29 +00:00
newClient , err := saltyim . NewClient ( clientIdentity , saltyim . WithState ( state ) )
2022-03-24 22:48:51 +00:00
if err != nil {
2022-04-04 16:04:38 +00:00
h . dialog . ShowError ( "error setting up client" , err . Error ( ) )
2022-03-24 22:48:51 +00:00
return
}
2022-03-31 03:46:12 +00:00
newClient . SetSend ( & saltyim . ProxySend { SendEndpoint : app . Getenv ( "SendEndpoint" ) } )
2022-03-31 00:51:38 +00:00
newClient . SetLookup ( & saltyim . ProxyLookup { LookupEndpoint : app . Getenv ( "LookupEndpoint" ) } )
2022-03-24 22:48:51 +00:00
2022-03-28 21:49:01 +00:00
client = newClient
2022-03-24 22:48:51 +00:00
2022-03-25 12:47:06 +00:00
ctx . Async ( func ( ) {
2022-04-07 03:43:09 +00:00
stateCh := time . NewTicker ( time . Second * 20 )
inboxCh := client . Subscribe ( ctx . Dispatcher ( ) . Context ( ) , "" , "" , "" )
outboxCh := client . OutboxClient ( nil ) . Subscribe ( ctx . Dispatcher ( ) . Context ( ) , "" , "" , "" )
2022-04-02 22:32:12 +00:00
for {
select {
case <- ctx . Done ( ) :
2022-04-07 03:43:09 +00:00
stateCh . Stop ( )
2022-04-02 22:32:12 +00:00
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 ) )
2022-04-07 03:43:09 +00:00
case <- stateCh . C :
if err := SetStateToState ( ctx , client . State ( ) ) ; err != nil {
log . WithError ( err ) . Warn ( "error saving state" )
}
2022-04-02 22:32:12 +00:00
}
2022-03-24 22:48:51 +00:00
}
2022-03-25 12:47:06 +00:00
} )
2022-03-21 22:27:35 +00:00
}
2022-03-30 00:35:43 +00:00
func ( h * SaltyChat ) incomingMessage ( ctx app . Context , action app . Action ) {
messageText := action . Tags . Get ( "text" )
2022-04-07 03:43:09 +00:00
2022-03-30 00:35:43 +00:00
s , err := lextwt . ParseSalty ( messageText )
if err != nil {
2022-04-04 16:04:38 +00:00
h . dialog . ShowError ( "incoming message error" , err . Error ( ) )
2022-03-30 00:35:43 +00:00
return
2022-03-29 22:06:06 +00:00
}
2022-04-03 01:11:33 +00:00
2022-03-30 00:35:43 +00:00
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 )
}
2022-03-29 22:06:06 +00:00
}
2022-04-02 22:32:12 +00:00
}
func ( h * SaltyChat ) outgoingMessage ( ctx app . Context , action app . Action ) {
messageText := action . Tags . Get ( "text" )
s , err := lextwt . ParseSalty ( messageText )
if err != nil {
2022-04-04 16:04:38 +00:00
h . dialog . ShowError ( "outgoing message error" , err . Error ( ) )
2022-04-02 22:32:12 +00:00
return
}
2022-04-03 01:03:45 +00:00
friend := ""
2022-04-02 22:32:12 +00:00
switch s := s . ( type ) {
case * lextwt . SaltyText :
2022-04-03 01:03:45 +00:00
friend = s . User . String ( )
2022-04-02 22:32:12 +00:00
storage . ContactsLocalStorage ( ctx ) . Add ( friend )
storage . ConversationsLocalStorage ( ctx , friend ) .
2022-04-07 03:43:09 +00:00
Append ( string ( saltyim . PackMessageTime ( client . Me ( ) , s . LiteralText ( ) , s . Timestamp ) ) )
2022-04-03 01:03:45 +00:00
}
2022-03-30 00:35:43 +00:00
2022-04-03 01:03:45 +00:00
// 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 )
2022-04-02 22:32:12 +00:00
}
2022-03-29 22:06:06 +00:00
}
2022-03-25 11:28:30 +00:00
func ( h * SaltyChat ) Render ( ) app . UI {
2022-03-23 04:06:56 +00:00
2022-03-28 21:49:01 +00:00
if h . chatBox == nil {
h . chatBox = & ChatBox { }
2022-04-01 12:36:38 +00:00
h . chatInput = & textfield . TextField { Id : "chat-input" , Placeholder : "New Message" }
2022-03-28 21:49:01 +00:00
h . dialog = & ModalDialog { }
2022-03-23 04:06:56 +00:00
}
2022-03-30 00:35:43 +00:00
h . chatBox . User = h . friend
2022-03-28 21:49:01 +00:00
2022-04-07 03:43:09 +00:00
return PageBody (
app . Div ( ) . ID ( "wrapper" ) . Body (
h . chatBox ,
app . Form ( ) . OnSubmit ( h . handleSendMessage ) . Body (
h . chatInput ,
icon . MISend . Button ( ) . ID ( "chat-send" ) ,
2022-03-23 04:06:56 +00:00
) ,
2022-04-07 03:43:09 +00:00
h . dialog ,
2022-03-23 17:08:21 +00:00
) ,
)
2022-03-25 04:20:16 +00:00
}
2022-03-25 11:28:30 +00:00
func ( h * SaltyChat ) handleSendMessage ( ctx app . Context , e app . Event ) {
2022-03-25 04:20:16 +00:00
e . PreventDefault ( )
msg := h . chatInput . Value
h . chatInput . Value = ""
h . focusChatInput ( )
2022-03-28 21:49:01 +00:00
2022-03-30 00:35:43 +00:00
if msg == "" || h . friend == "" {
// nothing to send and no friend selected
log . Printf ( "no send since msg = %q or Friend = %q" , msg , h . friend )
2022-03-25 04:20:16 +00:00
return
}
// determine current user to send message to and use client to send the message
2022-03-28 21:49:01 +00:00
if client != nil {
2022-03-30 00:35:43 +00:00
h . chatBox . User = h . friend
2022-03-29 00:24:03 +00:00
ctx . Async ( func ( ) {
2022-04-07 03:43:09 +00:00
if err := client . Send ( h . friend , msg ) ; err != nil {
2022-04-04 16:04:38 +00:00
h . dialog . ShowError ( "error sending message" , err . Error ( ) )
2022-03-25 04:20:16 +00:00
}
2022-03-29 00:24:03 +00:00
} )
2022-03-25 04:20:16 +00:00
}
}
2022-03-25 11:28:30 +00:00
func ( h * SaltyChat ) focusChatInput ( ) {
2022-03-24 22:48:51 +00:00
chatInputValue := h . JsUtil . JsValueAtPath ( h . chatInput . Id + "-input" )
chatInputValue . Set ( "value" , "" )
chatInputValue . Call ( "focus" )
}