2021-03-04 23:57:48 +00:00
package utils
import (
"cwtch.im/cwtch/app"
"cwtch.im/cwtch/app/plugins"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/protocol/connections"
"encoding/json"
"git.openprivacy.ca/flutter/libcwtch-go/constants"
2021-03-19 19:39:20 +00:00
"git.openprivacy.ca/flutter/libcwtch-go/features/groups"
2021-03-12 12:24:37 +00:00
"git.openprivacy.ca/openprivacy/log"
2021-04-13 00:04:21 +00:00
"strconv"
2021-03-04 23:57:48 +00:00
)
import "cwtch.im/cwtch/event"
type EventProfileEnvelope struct {
2021-03-12 12:24:37 +00:00
Event event . Event
Profile string
2021-03-04 23:57:48 +00:00
}
type EventHandler struct {
2021-03-05 00:14:58 +00:00
app app . Application
appBusQueue event . Queue
2021-03-04 23:57:48 +00:00
profileEvents chan EventProfileEnvelope
}
2021-06-15 17:17:05 +00:00
func NewEventHandler ( ) * EventHandler {
2021-06-22 22:34:46 +00:00
eh := & EventHandler { app : nil , appBusQueue : event . NewQueue ( ) , profileEvents : make ( chan EventProfileEnvelope ) }
2021-06-15 17:17:05 +00:00
return eh
}
2021-06-16 21:10:54 +00:00
// PublishAppEvent is a way for libCwtch-go to publish an event for consumption by a UI before a Cwtch app has been initialized
2021-06-15 17:17:05 +00:00
// Main use: to signal an error before a cwtch app could be created
func ( eh * EventHandler ) PublishAppEvent ( event event . Event ) {
eh . appBusQueue . Publish ( event )
}
func ( eh * EventHandler ) HandleApp ( application app . Application ) {
eh . app = application
application . GetPrimaryBus ( ) . Subscribe ( event . NewPeer , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( event . PeerError , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( event . PeerDeleted , eh . appBusQueue )
2021-06-16 21:10:54 +00:00
application . GetPrimaryBus ( ) . Subscribe ( event . Shutdown , eh . appBusQueue )
2021-06-15 17:17:05 +00:00
application . GetPrimaryBus ( ) . Subscribe ( event . AppError , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( event . ACNStatus , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( event . ReloadDone , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( event . ACNVersion , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( UpdateGlobalSettings , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( CwtchStarted , eh . appBusQueue )
2021-03-04 23:57:48 +00:00
}
func ( eh * EventHandler ) GetNextEvent ( ) string {
appChan := eh . appBusQueue . OutChan ( )
select {
case e := <- appChan :
return eh . handleAppBusEvent ( & e )
case ev := <- eh . profileEvents :
return eh . handleProfileEvent ( & ev )
}
}
// handleAppBusEvent enriches AppBus events so they are usable with out further data fetches
func ( eh * EventHandler ) handleAppBusEvent ( e * event . Event ) string {
2021-03-19 21:47:48 +00:00
log . Debugf ( "New AppBus Event to Handle: %v" , e )
2021-06-22 00:47:43 +00:00
if eh . app != nil {
switch e . EventType {
case event . ACNStatus :
if e . Data [ event . Progress ] == "100" {
for onion := range eh . app . ListPeers ( ) {
// launch a listen thread (internally this does a check that the protocol engine is not listening)
// and as such is safe to call.
eh . app . GetPeer ( onion ) . Listen ( )
}
2021-04-13 22:26:23 +00:00
}
2021-06-22 00:47:43 +00:00
case event . NewPeer :
onion := e . Data [ event . Identity ]
profile := eh . app . GetPeer ( e . Data [ event . Identity ] )
log . Debug ( "New Peer Event: %v" , e )
2021-06-22 22:46:28 +00:00
if e . Data [ "Reload" ] != event . True {
eh . startHandlingPeer ( onion )
}
2021-06-22 00:47:43 +00:00
2021-06-24 17:35:19 +00:00
tag , isTagged := profile . GetAttribute ( app . AttributeTag )
if isTagged {
e . Data [ app . AttributeTag ] = tag
} else {
// Assume encrypted for non-tagged profiles - this isn't always true, but all post-beta profiles
// are tagged on creation.
e . Data [ app . AttributeTag ] = constants . ProfileTypeV1Password
}
2021-06-22 00:47:43 +00:00
if e . Data [ event . Created ] == event . True {
name , _ := profile . GetAttribute ( attr . GetLocalScope ( constants . Name ) )
profile . SetAttribute ( attr . GetPublicScope ( constants . Name ) , name )
profile . SetAttribute ( attr . GetPublicScope ( constants . Picture ) , ImageToString ( NewImage ( RandomProfileImage ( onion ) , TypeImageDistro ) ) )
2021-04-06 21:56:01 +00:00
}
2021-06-22 00:47:43 +00:00
if e . Data [ event . Status ] != event . StorageRunning || e . Data [ event . Created ] == event . True {
profile . SetAttribute ( attr . GetLocalScope ( constants . PeerOnline ) , event . False )
eh . app . AddPeerPlugin ( onion , plugins . CONNECTIONRETRY )
eh . app . AddPeerPlugin ( onion , plugins . NETWORKCHECK )
// If the user has chosen to block unknown profiles
// then explicitly configure the protocol engine to do so..
if ReadGlobalSettings ( ) . BlockUnknownConnections {
profile . BlockUnknownConnections ( )
} else {
// For completeness
profile . AllowUnknownConnections ( )
}
2021-04-06 21:56:01 +00:00
2021-06-22 00:47:43 +00:00
// Start up the Profile
profile . Listen ( )
profile . StartPeersConnections ( )
if _ , err := groups . ExperimentGate ( ReadGlobalSettings ( ) . Experiments ) ; err == nil {
profile . StartServerConnections ( )
}
2021-03-19 19:39:20 +00:00
}
2021-03-04 23:57:48 +00:00
2021-06-22 00:47:43 +00:00
nick , exists := profile . GetAttribute ( attr . GetPublicScope ( constants . Name ) )
if ! exists {
nick = onion
}
2021-03-12 12:24:37 +00:00
2021-06-22 00:47:43 +00:00
picVal , ok := profile . GetAttribute ( attr . GetPublicScope ( constants . Picture ) )
if ! ok {
picVal = ImageToString ( NewImage ( RandomProfileImage ( onion ) , TypeImageDistro ) )
}
pic , err := StringToImage ( picVal )
if err != nil {
pic = NewImage ( RandomProfileImage ( onion ) , TypeImageDistro )
}
picPath := GetPicturePath ( pic )
//tag, _ := profile.GetAttribute(app.AttributeTag)
online , _ := profile . GetAttribute ( attr . GetLocalScope ( constants . PeerOnline ) )
e . Data [ constants . Name ] = nick
e . Data [ constants . Picture ] = picPath
e . Data [ "Online" ] = online
var contacts [ ] Contact
var servers [ ] groups . Server
for _ , contact := range profile . GetContacts ( ) {
// Only compile the server info if we have enabled the experiment...
// Note that this means that this info can become stale if when first loaded the experiment
// has been disabled and then is later re-enabled. As such we need to ensure that this list is
// re-fetched when the group experiment is enabled via a dedicated ListServerInfo event...
if profile . GetContact ( contact ) . IsServer ( ) {
groupHandler , err := groups . ExperimentGate ( ReadGlobalSettings ( ) . Experiments )
if err == nil {
servers = append ( servers , groupHandler . GetServerInfo ( contact , profile ) )
}
continue
}
2021-04-15 22:17:50 +00:00
2021-06-22 00:47:43 +00:00
contactInfo := profile . GetContact ( contact )
ph := NewPeerHelper ( profile )
name := ph . GetNick ( contact )
cpicPath := ph . GetProfilePic ( contact )
saveHistory , set := contactInfo . GetAttribute ( event . SaveHistoryKey )
if ! set {
saveHistory = event . DeleteHistoryDefault
2021-04-15 22:17:50 +00:00
}
2021-06-22 00:47:43 +00:00
contacts = append ( contacts , Contact {
Name : name ,
Onion : contactInfo . Onion ,
Status : contactInfo . State ,
Picture : cpicPath ,
Authorization : string ( contactInfo . Authorization ) ,
SaveHistory : saveHistory ,
Messages : contactInfo . Timeline . Len ( ) ,
Unread : 0 ,
LastMessage : strconv . Itoa ( getLastMessageTime ( & contactInfo . Timeline ) ) ,
IsGroup : false ,
} )
2021-03-15 22:24:49 +00:00
}
2021-06-22 00:47:43 +00:00
// We compile and send the groups regardless of the experiment flag, and hide them in the UI
for _ , groupId := range profile . GetGroups ( ) {
group := profile . GetGroup ( groupId )
2021-04-20 22:23:20 +00:00
2021-06-22 00:47:43 +00:00
// Check that the group is cryptographically valid
if ! group . CheckGroup ( ) {
continue
}
2021-05-18 19:55:25 +00:00
2021-06-22 00:47:43 +00:00
ph := NewPeerHelper ( profile )
cpicPath := ph . GetProfilePic ( groupId )
2021-05-18 19:55:25 +00:00
2021-06-22 00:47:43 +00:00
authorization := model . AuthUnknown
if group . Accepted {
authorization = model . AuthApproved
}
2021-04-20 22:23:20 +00:00
2021-06-22 00:47:43 +00:00
contacts = append ( contacts , Contact {
Name : ph . GetNick ( groupId ) ,
Onion : group . GroupID ,
Status : group . State ,
Picture : cpicPath ,
Authorization : string ( authorization ) ,
SaveHistory : event . SaveHistoryConfirmed ,
Messages : group . Timeline . Len ( ) ,
Unread : 0 ,
LastMessage : strconv . Itoa ( getLastMessageTime ( & group . Timeline ) ) ,
IsGroup : true ,
GroupServer : group . GroupServer ,
} )
2021-04-20 22:23:20 +00:00
}
2021-06-22 00:47:43 +00:00
bytes , _ := json . Marshal ( contacts )
e . Data [ "ContactsJson" ] = string ( bytes )
2021-04-15 22:17:50 +00:00
2021-06-22 00:47:43 +00:00
// Marshal the server list into the new peer event...
serversListBytes , _ := json . Marshal ( servers )
e . Data [ groups . ServerList ] = string ( serversListBytes )
2021-04-15 22:17:50 +00:00
2021-06-24 17:35:19 +00:00
log . Debugf ( "contactsJson %v" , e . Data [ "ContactsJson" ] )
2021-06-22 00:47:43 +00:00
}
2021-03-04 23:57:48 +00:00
}
json , _ := json . Marshal ( e )
return string ( json )
}
2021-03-12 12:24:37 +00:00
// handleProfileEvent enriches Profile events so they are usable with out further data fetches
2021-03-04 23:57:48 +00:00
func ( eh * EventHandler ) handleProfileEvent ( ev * EventProfileEnvelope ) string {
2021-06-22 00:47:43 +00:00
if eh . app == nil {
log . Errorf ( "eh.app == nil in handleProfileEvent... this shouldnt happen?" )
} else {
peer := eh . app . GetPeer ( ev . Profile )
ph := NewPeerHelper ( peer )
log . Debugf ( "New Profile Event to Handle: %v" , ev )
switch ev . Event . EventType {
/ *
TODO : still handle this somewhere - network info from plugin Network check
case event . NetworkStatus :
online , _ := peer . GetAttribute ( attr . GetLocalScope ( constants . PeerOnline ) )
if e . Data [ event . Status ] == plugins . NetworkCheckSuccess && online == event . False {
peer . SetAttribute ( attr . GetLocalScope ( constants . PeerOnline ) , event . True )
uiManager . UpdateNetworkStatus ( true )
// TODO we may have to reinitialize the peer
} else if e . Data [ event . Status ] == plugins . NetworkCheckError && online == event . True {
peer . SetAttribute ( attr . GetLocalScope ( constants . PeerOnline ) , event . False )
uiManager . UpdateNetworkStatus ( false )
} * /
case event . NewMessageFromPeer : //event.TimestampReceived, event.RemotePeer, event.Data
// only needs contact nickname and picture, for displaying on popup notifications
ev . Event . Data [ "Nick" ] = ph . GetNick ( ev . Event . Data [ "RemotePeer" ] )
ev . Event . Data [ "Picture" ] = ph . GetProfilePic ( ev . Event . Data [ "RemotePeer" ] )
case event . NewMessageFromGroup :
// only needs contact nickname and picture, for displaying on popup notifications
ev . Event . Data [ "Nick" ] = ph . GetNick ( ev . Event . Data [ event . GroupID ] )
ev . Event . Data [ "Picture" ] = ph . GetProfilePic ( ev . Event . Data [ event . GroupID ] )
case event . PeerAcknowledgement :
// No enrichement required
case event . PeerCreated :
handle := ev . Event . Data [ event . RemotePeer ]
err := EnrichNewPeer ( handle , ph , ev )
if err != nil {
return ""
}
case event . GroupCreated :
// This event should only happen after we have validated the invite, as such the error
// condition *should* never happen.
2021-03-04 23:57:48 +00:00
2021-06-22 00:47:43 +00:00
groupPic := ph . GetProfilePic ( ev . Event . Data [ event . GroupID ] )
2021-05-18 20:42:38 +00:00
ev . Event . Data [ "PicturePath" ] = groupPic
2021-06-22 00:47:43 +00:00
ev . Event . Data [ "GroupName" ] = ph . GetNick ( ev . Event . Data [ event . GroupID ] )
case event . NewGroup :
// This event should only happen after we have validated the invite, as such the error
// condition *should* never happen.
serializedInvite := ev . Event . Data [ event . GroupInvite ]
if invite , err := model . ValidateInvite ( serializedInvite ) ; err == nil {
groupPic := ph . GetProfilePic ( invite . GroupID )
ev . Event . Data [ "PicturePath" ] = groupPic
} else {
log . Errorf ( "received a new group event which contained an invalid invite %v. this should never happen and likely means there is a bug in cwtch. Please file a ticket @ https://git.openprivcy.ca/cwtch.im/cwtch" , err )
return ""
}
case event . PeerStateChange :
cxnState := connections . ConnectionStateToType ( ) [ ev . Event . Data [ event . ConnectionState ] ]
contact := peer . GetContact ( ev . Event . Data [ event . RemotePeer ] )
2021-03-04 23:57:48 +00:00
2021-06-22 00:47:43 +00:00
if cxnState == connections . AUTHENTICATED && contact == nil {
peer . AddContact ( ev . Event . Data [ event . RemotePeer ] , ev . Event . Data [ event . RemotePeer ] , model . AuthUnknown )
return ""
}
2021-03-05 00:18:15 +00:00
2021-06-22 00:47:43 +00:00
if contact != nil {
// No enrichment needed
//uiManager.UpdateContactStatus(contact.Onion, int(cxnState), false)
if cxnState == connections . AUTHENTICATED {
// if known and authed, get vars
peer . SendGetValToPeer ( ev . Event . Data [ event . RemotePeer ] , attr . PublicScope , constants . Name )
peer . SendGetValToPeer ( ev . Event . Data [ event . RemotePeer ] , attr . PublicScope , constants . Picture )
}
2021-03-04 23:57:48 +00:00
}
2021-06-22 00:47:43 +00:00
case event . NewRetValMessageFromPeer :
// auto handled event means the setting is already done, we're just deciding if we need to tell the UI
onion := ev . Event . Data [ event . RemotePeer ]
scope := ev . Event . Data [ event . Scope ]
path := ev . Event . Data [ event . Path ]
//val := ev.Event.Data[event.Data]
exists , _ := strconv . ParseBool ( ev . Event . Data [ event . Exists ] )
if exists && scope == attr . PublicScope {
if _ , exists := peer . GetContactAttribute ( onion , attr . GetLocalScope ( path ) ) ; exists {
// we have a locally set ovverride, don't pass this remote set public scope update to UI
return ""
}
2021-03-04 23:57:48 +00:00
}
2021-06-16 21:10:54 +00:00
}
2021-03-04 23:57:48 +00:00
}
2021-03-16 22:16:04 +00:00
json , _ := json . Marshal ( unwrap ( ev ) )
2021-03-04 23:57:48 +00:00
return string ( json )
}
2021-03-16 22:16:04 +00:00
func unwrap ( original * EventProfileEnvelope ) * event . Event {
2021-03-16 22:05:44 +00:00
unwrapped := & original . Event
unwrapped . Data [ "ProfileOnion" ] = original . Profile
return unwrapped
}
2021-03-04 23:57:48 +00:00
func ( eh * EventHandler ) startHandlingPeer ( onion string ) {
eventBus := eh . app . GetEventBus ( onion )
q := event . NewQueue ( )
eventBus . Subscribe ( event . NewMessageFromPeer , q )
eventBus . Subscribe ( event . PeerAcknowledgement , q )
2021-06-15 00:23:47 +00:00
eventBus . Subscribe ( event . DeleteContact , q )
eventBus . Subscribe ( event . AppError , q )
2021-05-03 18:37:34 +00:00
eventBus . Subscribe ( event . IndexedAcknowledgement , q )
2021-05-26 22:21:01 +00:00
eventBus . Subscribe ( event . IndexedFailure , q )
2021-03-04 23:57:48 +00:00
eventBus . Subscribe ( event . NewMessageFromGroup , q )
2021-05-28 09:12:19 +00:00
eventBus . Subscribe ( event . GroupCreated , q )
2021-05-05 20:05:02 +00:00
eventBus . Subscribe ( event . NewGroup , q )
2021-04-28 22:13:43 +00:00
eventBus . Subscribe ( event . AcceptGroupInvite , q )
2021-05-28 09:12:19 +00:00
eventBus . Subscribe ( event . SetGroupAttribute , q )
eventBus . Subscribe ( event . DeleteGroup , q )
2021-03-04 23:57:48 +00:00
eventBus . Subscribe ( event . SendMessageToGroupError , q )
eventBus . Subscribe ( event . SendMessageToPeerError , q )
eventBus . Subscribe ( event . ServerStateChange , q )
eventBus . Subscribe ( event . PeerStateChange , q )
eventBus . Subscribe ( event . PeerCreated , q )
eventBus . Subscribe ( event . NetworkStatus , q )
eventBus . Subscribe ( event . ChangePasswordSuccess , q )
eventBus . Subscribe ( event . ChangePasswordError , q )
eventBus . Subscribe ( event . NewRetValMessageFromPeer , q )
2021-04-10 03:04:39 +00:00
eventBus . Subscribe ( event . SetAttribute , q )
2021-03-04 23:57:48 +00:00
go eh . forwardProfileMessages ( onion , q )
}
func ( eh * EventHandler ) forwardProfileMessages ( onion string , q event . Queue ) {
2021-06-22 22:46:28 +00:00
log . Infof ( "Launching Forwarding Goroutine for %v" , onion )
2021-03-16 20:16:45 +00:00
// TODO: graceful shutdown, via an injected event of special QUIT type exiting loop/go routine
2021-03-05 00:14:58 +00:00
for {
2021-03-04 23:57:48 +00:00
e := q . Next ( )
2021-03-24 23:24:42 +00:00
ev := EventProfileEnvelope { Event : e , Profile : onion }
2021-03-04 23:57:48 +00:00
eh . profileEvents <- ev
2021-06-22 22:34:46 +00:00
if ev . Event . EventType == event . Shutdown {
return
}
2021-03-04 23:57:48 +00:00
}
}
2021-03-24 23:24:42 +00:00
func ( eh * EventHandler ) Push ( newEvent event . Event ) {
eh . appBusQueue . Publish ( newEvent )
}