Merge pull request 'Add Contact Flow' (#18) from peersettings into trunk
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #18
This commit is contained in:
commit
3ea5121998
|
@ -0,0 +1,41 @@
|
||||||
|
package contact
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/cwtch/model"
|
||||||
|
"cwtch.im/cwtch/peer"
|
||||||
|
"git.openprivacy.ca/flutter/libcwtch-go/features"
|
||||||
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Functionality groups some common UI triggered functions for contacts...
|
||||||
|
type Functionality struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
const addContactPrefix = "addcontact"
|
||||||
|
|
||||||
|
const sendMessagePrefix = "sendmessage"
|
||||||
|
|
||||||
|
// FunctionalityGate returns contact.Functionality always
|
||||||
|
func FunctionalityGate(experimentMap map[string]bool) (*Functionality, error) {
|
||||||
|
return new(Functionality), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMessage handles sending messages to contacts
|
||||||
|
func (pf *Functionality) SendMessage(peer peer.SendMessages, handle string, message string) features.Response {
|
||||||
|
eventID := peer.SendMessageToPeer(handle, message)
|
||||||
|
return features.ConstructResponse(sendMessagePrefix, eventID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleImportString handles contact import strings
|
||||||
|
func (pf *Functionality) HandleImportString(peer peer.ModifyContactsAndPeers, importString string) features.Response {
|
||||||
|
if tor.IsValidHostname(importString) {
|
||||||
|
if peer.GetContact(importString) == nil {
|
||||||
|
peer.AddContact(importString, importString, model.AuthApproved)
|
||||||
|
// Implicit Peer Attempt
|
||||||
|
peer.PeerWithOnion(importString)
|
||||||
|
return features.ConstructResponse(addContactPrefix, "success")
|
||||||
|
}
|
||||||
|
return features.ConstructResponse(addContactPrefix, "contact_already_exists")
|
||||||
|
}
|
||||||
|
return features.ConstructResponse(addContactPrefix, "invalid_import_string")
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
package contact
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/cwtch/model"
|
||||||
|
"git.openprivacy.ca/flutter/libcwtch-go/features"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ValidHostname = "openpravyvc6spbd4flzn4g2iqu4sxzsizbtb5aqec25t76dnoo5w7yd"
|
||||||
|
|
||||||
|
type MockPeer struct {
|
||||||
|
hasContact bool
|
||||||
|
addContact bool
|
||||||
|
peerRequest bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MockPeer) GetContacts() []string {
|
||||||
|
panic("should never be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MockPeer) GetContact(s string) *model.PublicProfile {
|
||||||
|
if m.hasContact {
|
||||||
|
return &(model.GenerateNewProfile("").PublicProfile)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MockPeer) GetContactAttribute(s string, s2 string) (string, bool) {
|
||||||
|
panic("should never be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockPeer) AddContact(nick, onion string, authorization model.Authorization) {
|
||||||
|
m.addContact = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MockPeer) SetContactAuthorization(s string, authorization model.Authorization) error {
|
||||||
|
panic("should never be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MockPeer) SetContactAttribute(s string, s2 string, s3 string) {
|
||||||
|
panic("should never be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MockPeer) DeleteContact(s string) {
|
||||||
|
panic("should never be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockPeer) PeerWithOnion(s string) {
|
||||||
|
m.peerRequest = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MockPeer) JoinServer(s string) error {
|
||||||
|
panic("should never be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContactFunctionality_InValidHostname(t *testing.T) {
|
||||||
|
cf, _ := FunctionalityGate(map[string]bool{})
|
||||||
|
|
||||||
|
peer := &MockPeer{
|
||||||
|
hasContact: false,
|
||||||
|
addContact: false,
|
||||||
|
peerRequest: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
response := cf.HandleImportString(peer, "")
|
||||||
|
|
||||||
|
if peer.addContact || peer.peerRequest {
|
||||||
|
t.Fatalf("HandleImportString for a malformed import string should have no resulted in addContact or a peerRequest: %v", peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Error() != features.ConstructResponse(addContactPrefix, "invalid_import_string").Error() {
|
||||||
|
t.Fatalf("Response to a successful import is malformed: %v", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContactFunctionality_ValidHostnameExistingContact(t *testing.T) {
|
||||||
|
cf, _ := FunctionalityGate(map[string]bool{})
|
||||||
|
|
||||||
|
peer := &MockPeer{
|
||||||
|
hasContact: true,
|
||||||
|
addContact: false,
|
||||||
|
peerRequest: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
response := cf.HandleImportString(peer, ValidHostname)
|
||||||
|
|
||||||
|
if peer.addContact || peer.peerRequest {
|
||||||
|
t.Fatalf("HandleImportString for a valid string should not call addContact or a peerRequest when the contact already exists: %v", peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Error() != features.ConstructResponse(addContactPrefix, "contact_already_exists").Error() {
|
||||||
|
t.Fatalf("Response to a successful import is malformed: %v", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContactFunctionality_ValidHostnameUnknownContact(t *testing.T) {
|
||||||
|
cf, _ := FunctionalityGate(map[string]bool{})
|
||||||
|
|
||||||
|
peer := &MockPeer{
|
||||||
|
hasContact: false,
|
||||||
|
addContact: false,
|
||||||
|
peerRequest: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
response := cf.HandleImportString(peer, ValidHostname)
|
||||||
|
|
||||||
|
if peer.addContact && peer.peerRequest {
|
||||||
|
if response.Error() != features.ConstructResponse(addContactPrefix, "success").Error() {
|
||||||
|
t.Fatalf("Response to a successful import is malformed: %v", response)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("HandleImportString for a valid import string should have resulted in addContact or a peerRequest: %v", peer)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package features
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// Response is a wrapper to better semantically convey the response type...
|
||||||
|
type Response error
|
||||||
|
|
||||||
|
const errorSeparator = "."
|
||||||
|
|
||||||
|
// ConstructResponse is a helper function for creating Response structures.
|
||||||
|
func ConstructResponse(prefix string, error string) Response {
|
||||||
|
return errors.New(prefix + errorSeparator + error)
|
||||||
|
}
|
4
go.mod
4
go.mod
|
@ -3,7 +3,7 @@ module git.openprivacy.ca/flutter/libcwtch-go
|
||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cwtch.im/cwtch v0.6.0
|
cwtch.im/cwtch v0.6.3
|
||||||
git.openprivacy.ca/openprivacy/connectivity v1.3.3
|
git.openprivacy.ca/openprivacy/connectivity v1.3.3
|
||||||
git.openprivacy.ca/openprivacy/log v1.0.2
|
git.openprivacy.ca/openprivacy/log v1.0.2
|
||||||
)
|
)
|
6
go.sum
6
go.sum
|
@ -2,6 +2,12 @@ cwtch.im/cwtch v0.5.1 h1:84foD/HBebPbA4gUEwp+feakeHkD3Di53Q3FnSbqDMM=
|
||||||
cwtch.im/cwtch v0.5.1/go.mod h1:snHZIZwRQPAZG2LRZsN5SpAIbeR597VJoDS+KHm7q9w=
|
cwtch.im/cwtch v0.5.1/go.mod h1:snHZIZwRQPAZG2LRZsN5SpAIbeR597VJoDS+KHm7q9w=
|
||||||
cwtch.im/cwtch v0.6.0 h1:LaIRs8dDtnSr/MVFX3giTxnYwSyAIu0w55eZWWO7VZI=
|
cwtch.im/cwtch v0.6.0 h1:LaIRs8dDtnSr/MVFX3giTxnYwSyAIu0w55eZWWO7VZI=
|
||||||
cwtch.im/cwtch v0.6.0/go.mod h1:snHZIZwRQPAZG2LRZsN5SpAIbeR597VJoDS+KHm7q9w=
|
cwtch.im/cwtch v0.6.0/go.mod h1:snHZIZwRQPAZG2LRZsN5SpAIbeR597VJoDS+KHm7q9w=
|
||||||
|
cwtch.im/cwtch v0.6.1 h1:NqfLPS7k3rRi5A0qf/tHoq42BEc/K89LHgZqsBs7Luk=
|
||||||
|
cwtch.im/cwtch v0.6.1/go.mod h1:snHZIZwRQPAZG2LRZsN5SpAIbeR597VJoDS+KHm7q9w=
|
||||||
|
cwtch.im/cwtch v0.6.2 h1:UqwVnxNXvhhG7yGpcY9aXyq0dy31XzjV708BWCHHIms=
|
||||||
|
cwtch.im/cwtch v0.6.2/go.mod h1:snHZIZwRQPAZG2LRZsN5SpAIbeR597VJoDS+KHm7q9w=
|
||||||
|
cwtch.im/cwtch v0.6.3 h1:AifcbxK60UTeOiOt0ur8PLQeDCuljQLhLqrAOO/8guA=
|
||||||
|
cwtch.im/cwtch v0.6.3/go.mod h1:snHZIZwRQPAZG2LRZsN5SpAIbeR597VJoDS+KHm7q9w=
|
||||||
cwtch.im/tapir v0.2.1 h1:t1YJB9q5sV1A9xwiiwL6WVfw3dwQWLoecunuzT1PQtw=
|
cwtch.im/tapir v0.2.1 h1:t1YJB9q5sV1A9xwiiwL6WVfw3dwQWLoecunuzT1PQtw=
|
||||||
cwtch.im/tapir v0.2.1/go.mod h1:xzzZ28adyUXNkYL1YodcHsAiTt3IJ8Loc29YVn9mIEQ=
|
cwtch.im/tapir v0.2.1/go.mod h1:xzzZ28adyUXNkYL1YodcHsAiTt3IJ8Loc29YVn9mIEQ=
|
||||||
git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c=
|
git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c=
|
||||||
|
|
13
lib.go
13
lib.go
|
@ -10,6 +10,7 @@ import (
|
||||||
"cwtch.im/cwtch/model"
|
"cwtch.im/cwtch/model"
|
||||||
"cwtch.im/cwtch/model/attr"
|
"cwtch.im/cwtch/model/attr"
|
||||||
"cwtch.im/cwtch/peer"
|
"cwtch.im/cwtch/peer"
|
||||||
|
contact "git.openprivacy.ca/flutter/libcwtch-go/features/contacts"
|
||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -73,6 +74,7 @@ func StartCwtch(appDir string, torPath string) {
|
||||||
newApp.GetPrimaryBus().Subscribe(event.ACNStatus, acnQueue)
|
newApp.GetPrimaryBus().Subscribe(event.ACNStatus, acnQueue)
|
||||||
newApp.GetPrimaryBus().Subscribe(utils.UpdateGlobalSettings, acnQueue)
|
newApp.GetPrimaryBus().Subscribe(utils.UpdateGlobalSettings, acnQueue)
|
||||||
newApp.GetPrimaryBus().Subscribe(utils.SetLoggingLevel, acnQueue)
|
newApp.GetPrimaryBus().Subscribe(utils.SetLoggingLevel, acnQueue)
|
||||||
|
newApp.GetPrimaryBus().Subscribe(event.AppError, acnQueue)
|
||||||
|
|
||||||
eventHandler = utils.NewEventHandler(newApp)
|
eventHandler = utils.NewEventHandler(newApp)
|
||||||
|
|
||||||
|
@ -168,6 +170,11 @@ func c_SendProfileEvent(onion_ptr *C.char, onion_len C.int, json_ptr *C.char, js
|
||||||
SendProfileEvent(onion, eventJson)
|
SendProfileEvent(onion, eventJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
AddContact = event.Type("AddContact")
|
||||||
|
ImportString = event.Field("ImportString")
|
||||||
|
)
|
||||||
|
|
||||||
// SendProfileEvent is a generic method for Rebroadcasting Profile Events from a UI
|
// SendProfileEvent is a generic method for Rebroadcasting Profile Events from a UI
|
||||||
func SendProfileEvent(onion string, eventJson string) {
|
func SendProfileEvent(onion string, eventJson string) {
|
||||||
// Convert the Event Json back to a typed Event Struct, this will make the
|
// Convert the Event Json back to a typed Event Struct, this will make the
|
||||||
|
@ -185,6 +192,12 @@ func SendProfileEvent(onion string, eventJson string) {
|
||||||
// We need to update the local cache
|
// We need to update the local cache
|
||||||
// Ideally I think this would be pushed back into Cwtch
|
// Ideally I think this would be pushed back into Cwtch
|
||||||
switch new_event.EventType {
|
switch new_event.EventType {
|
||||||
|
case AddContact:
|
||||||
|
// Peer Functionality is Always Enabled, so we forgo the existence check...
|
||||||
|
// TODO: Combine with GroupFunctionality to make a meta-handleimportstring that can do both!
|
||||||
|
pf, _ := contact.FunctionalityGate(utils.ReadGlobalSettings().Experiments)
|
||||||
|
err := pf.HandleImportString(peer, new_event.Data[ImportString])
|
||||||
|
eventHandler.Push(event.NewEvent(event.AppError, map[event.Field]string{event.Data: err.Error()}))
|
||||||
case event.SetAttribute:
|
case event.SetAttribute:
|
||||||
peer.SetAttribute(new_event.Data[event.Key], new_event.Data[event.Data])
|
peer.SetAttribute(new_event.Data[event.Key], new_event.Data[event.Data])
|
||||||
case event.SetPeerAttribute:
|
case event.SetPeerAttribute:
|
||||||
|
|
|
@ -290,7 +290,11 @@ func (eh *EventHandler) forwardProfileMessages(onion string, q event.Queue) {
|
||||||
// TODO: graceful shutdown, via an injected event of special QUIT type exiting loop/go routine
|
// TODO: graceful shutdown, via an injected event of special QUIT type exiting loop/go routine
|
||||||
for {
|
for {
|
||||||
e := q.Next()
|
e := q.Next()
|
||||||
ev := EventProfileEnvelope{Event: *e, Profile: onion}
|
ev := EventProfileEnvelope{Event: e, Profile: onion}
|
||||||
eh.profileEvents <- ev
|
eh.profileEvents <- ev
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (eh *EventHandler) Push(newEvent event.Event) {
|
||||||
|
eh.appBusQueue.Publish(newEvent)
|
||||||
|
}
|
||||||
|
|
|
@ -245,6 +245,7 @@ func NewManager(profile string, gcd *GrandCentralDispatcher) Manager {
|
||||||
// uiManager.AddContact(onion)
|
// uiManager.AddContact(onion)
|
||||||
// (handle string, displayName string, image string, badge int, status int, authorization string, loading bool, lastMsgTime int)
|
// (handle string, displayName string, image string, badge int, status int, authorization string, loading bool, lastMsgTime int)
|
||||||
func EnrichNewPeer(handle string, ph *PeerHelper, ev *EventProfileEnvelope) error {
|
func EnrichNewPeer(handle string, ph *PeerHelper, ev *EventProfileEnvelope) error {
|
||||||
|
log.Infof("Enriching New Peer %v", handle)
|
||||||
if ph.IsGroup(handle) {
|
if ph.IsGroup(handle) {
|
||||||
group := ph.peer.GetGroup(handle)
|
group := ph.peer.GetGroup(handle)
|
||||||
if group != nil {
|
if group != nil {
|
||||||
|
@ -266,11 +267,19 @@ func EnrichNewPeer(handle string, ph *PeerHelper, ev *EventProfileEnvelope) erro
|
||||||
ev.Event.Data["picture"] = ph.GetProfilePic(handle)
|
ev.Event.Data["picture"] = ph.GetProfilePic(handle)
|
||||||
|
|
||||||
ev.Event.Data["nick"] = ph.GetNick(handle)
|
ev.Event.Data["nick"] = ph.GetNick(handle)
|
||||||
ev.Event.Data["status"] = strconv.Itoa(int(connections.ConnectionStateToType()[contact.State]))
|
|
||||||
|
|
||||||
|
// TODO Replace this if with a better flow that separates New Contacts and Peering Updates
|
||||||
|
if contact.State == "" {
|
||||||
|
// Will be disconnected to start
|
||||||
|
ev.Event.Data["status"] = connections.ConnectionStateName[connections.DISCONNECTED]
|
||||||
|
} else {
|
||||||
|
ev.Event.Data["status"] = contact.State
|
||||||
|
}
|
||||||
ev.Event.Data["authorization"] = string(contact.Authorization)
|
ev.Event.Data["authorization"] = string(contact.Authorization)
|
||||||
ev.Event.Data["loading"] = "false"
|
ev.Event.Data["loading"] = "false"
|
||||||
ev.Event.Data["lastMsgTime"] = strconv.Itoa(getLastMessageTime(&contact.Timeline))
|
ev.Event.Data["lastMsgTime"] = strconv.Itoa(getLastMessageTime(&contact.Timeline))
|
||||||
|
} else {
|
||||||
|
log.Errorf("Failed to find contact: %v", handle)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// could be a server?
|
// could be a server?
|
||||||
|
|
Loading…
Reference in New Issue