diff --git a/app/app.go b/app/app.go index 01afb99..23960f7 100644 --- a/app/app.go +++ b/app/app.go @@ -5,6 +5,7 @@ import ( "cwtch.im/cwtch/event" "cwtch.im/cwtch/extensions" "cwtch.im/cwtch/functionality/filesharing" + "cwtch.im/cwtch/functionality/servers" "cwtch.im/cwtch/model" "cwtch.im/cwtch/model/attr" "cwtch.im/cwtch/model/constants" @@ -362,6 +363,7 @@ func (app *application) registerHooks(profile peer.CwtchPeer) { profile.RegisterHook(extensions.ProfileValueExtension{}) profile.RegisterHook(new(filesharing.Functionality)) profile.RegisterHook(new(filesharing.ImagePreviewsFunctionality)) + profile.RegisterHook(new(servers.Functionality)) // Ensure that Profiles have the Most Up to Date Settings... profile.NotifySettingsUpdate(app.settings.ReadGlobalSettings()) } diff --git a/functionality/servers/servers_functionality.go b/functionality/servers/servers_functionality.go new file mode 100644 index 0000000..0e0ab55 --- /dev/null +++ b/functionality/servers/servers_functionality.go @@ -0,0 +1,126 @@ +package servers + + +import ( + "cwtch.im/cwtch/event" + "cwtch.im/cwtch/model" + "cwtch.im/cwtch/model/attr" + "cwtch.im/cwtch/model/constants" + "cwtch.im/cwtch/peer" + "cwtch.im/cwtch/settings" + "cwtch.im/cwtch/protocol/connections" + "encoding/json" +) + +const ( + // ServerList is a json encoded list of servers + ServerList = event.Field("ServerList") +) + +const ( + // UpdateServerInfo is an event containing a ProfileOnion and a ServerList + UpdateServerInfo = event.Type("UpdateServerInfo") +) + +// Functionality groups some common UI triggered functions for contacts... +type Functionality struct { +} + +func (f *Functionality) NotifySettingsUpdate(settings settings.GlobalSettings) { + +} + +func (f *Functionality) EventsToRegister() []event.Type { + return []event.Type{event.ProtocolEngineCreated, event.ManifestReceived, event.FileDownloaded} +} + +func (f *Functionality) ExperimentsToRegister() []string { + return []string{constants.GroupsExperiment} +} + +// OnEvent handles File Sharing Hooks like Manifest Received and FileDownloaded +func (f *Functionality) OnEvent(ev event.Event, profile peer.CwtchPeer) { + if profile.IsFeatureEnabled(constants.GroupsExperiment) { + switch ev.EventType { + // keep the UI in sync with the current backend server updates... + // TODO: do we need a secondary heartbeat for less common updates? + case event.Heartbeat: + f.PublishServerUpdate(profile) + break; + } + } +} + +func (f *Functionality) OnContactRequestValue(profile peer.CwtchPeer, conversation model.Conversation, eventID string, path attr.ScopedZonedPath) { + // nop +} + +func (f *Functionality) OnContactReceiveValue(profile peer.CwtchPeer, conversation model.Conversation, path attr.ScopedZonedPath, value string, exists bool) { + // nopt +} + +// FunctionalityGate returns filesharing functionality - gates now happen on function calls. +func FunctionalityGate() *Functionality { + return new(Functionality) +} + +// ServerKey packages up key information... +// TODO: Can this be merged with KeyBundle? +type ServerKey struct { + Type string `json:"type"` + Key string `json:"key"` +} + +// SyncStatus packages up server sync information... +type SyncStatus struct { + StartTime string `json:"startTime"` + LastMessageTime string `json:"lastMessageTime"` +} + + +// Server encapsulates the information needed to represent a server... +type Server struct { + Onion string `json:"onion"` + Identifier int `json:"identifier"` + Status string `json:"status"` + Description string `json:"description"` + Keys []ServerKey `json:"keys"` + SyncProgress SyncStatus `json:"syncProgress"` +} + +// PublishServerUpdate serializes the current list of group servers and publishes an event with this information +func (f *Functionality) PublishServerUpdate(profile peer.CwtchPeer) { + serverListForOnion := f.GetServerInfoList(profile) + serversListBytes, _ := json.Marshal(serverListForOnion) + profile.PublishEvent(event.NewEvent(UpdateServerInfo, map[event.Field]string{"ProfileOnion": profile.GetOnion(), ServerList: string(serversListBytes)})) +} + +// GetServerInfoList compiles all the information the UI might need regarding all servers.. +func (f *Functionality) GetServerInfoList(profile peer.CwtchPeer) []Server { + var servers []Server + for _, server := range profile.GetServers() { + servers = append(servers, f.GetServerInfo(profile, server)) + } + return servers +} + +// GetServerInfo compiles all the information the UI might need regarding a particular server including any verified +// cryptographic keys +func (f *Functionality) GetServerInfo(profile peer.CwtchPeer, serverOnion string) Server { + serverInfo, _ := profile.FetchConversationInfo(serverOnion) + keyTypes := []model.KeyType{model.KeyTypeServerOnion, model.KeyTypeTokenOnion, model.KeyTypePrivacyPass} + var serverKeys []ServerKey + + for _, keyType := range keyTypes { + if key, has := serverInfo.GetAttribute(attr.PublicScope, attr.ServerKeyZone, string(keyType)); has { + serverKeys = append(serverKeys, ServerKey{Type: string(keyType), Key: key}) + } + } + + description, _ := serverInfo.GetAttribute(attr.LocalScope, attr.ServerZone, constants.Description) + startTimeStr := serverInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.SyncPreLastMessageTime)).ToString()] + recentTimeStr := serverInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.SyncMostRecentMessageTime)).ToString()] + syncStatus := SyncStatus{startTimeStr, recentTimeStr} + + return Server{Onion: serverOnion, Identifier: serverInfo.ID, Status: connections.ConnectionStateName[profile.GetPeerState(serverInfo.Handle)], Keys: serverKeys, Description: description, SyncProgress: syncStatus} +} diff --git a/model/constants/attributes.go b/model/constants/attributes.go index ecdc03a..443f747 100644 --- a/model/constants/attributes.go +++ b/model/constants/attributes.go @@ -63,3 +63,6 @@ const ProfileStatus = "profile-status" const ProfileAttribute1 = "profile-attribute-1" const ProfileAttribute2 = "profile-attribute-2" const ProfileAttribute3 = "profile-attribute-3" + +// Description is used on server contacts, +const Description = "description" diff --git a/model/constants/experiments.go b/model/constants/experiments.go index a9fd358..62cd482 100644 --- a/model/constants/experiments.go +++ b/model/constants/experiments.go @@ -1,5 +1,7 @@ package constants +const GroupsExperiment = "tapir-groups-experiment" + // FileSharingExperiment Allows file sharing const FileSharingExperiment = "filesharing"