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/protocol/connections" "cwtch.im/cwtch/settings" "encoding/json" "errors" "git.openprivacy.ca/openprivacy/log" ) 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.QueueJoinServer} } 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... // queue join server gets triggered on load and on new servers so it's a nice // low-noise event to hook into... case event.QueueJoinServer: f.PublishServerUpdate(profile) } } } 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) error { serverListForOnion := f.GetServerInfoList(profile) serversListBytes, err := json.Marshal(serverListForOnion) profile.PublishEvent(event.NewEvent(UpdateServerInfo, map[event.Field]string{"ProfileOnion": profile.GetOnion(), ServerList: string(serversListBytes)})) return err } // 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() { server, err := f.GetServerInfo(profile, server) if err != nil { log.Errorf("profile server list is corrupted: %v", err) continue } servers = append(servers, server) } return servers } // DeleteServer purges a server and all related keys from a profile func (f *Functionality) DeleteServerInfo(profile peer.CwtchPeer, serverOnion string) error { // Servers are stores as special conversations ci, err := profile.FetchConversationInfo(serverOnion) if err != nil { return err } // Purge keys... // NOTE: This will leave some groups in the state of being unable to connect to a particular // server. profile.DeleteConversation(ci.ID) f.PublishServerUpdate(profile) return nil } // 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, error) { serverInfo, err := profile.FetchConversationInfo(serverOnion) if err != nil { return Server{}, errors.New("server not found") } 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}, nil }