220 lines
6.6 KiB
Go
220 lines
6.6 KiB
Go
package sdk
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"log"
|
||
"time"
|
||
)
|
||
|
||
const discoveryChannelName = "contact-discovery"
|
||
|
||
// Channel : ...
|
||
type Channel struct {
|
||
account *Account
|
||
name string
|
||
filterID string
|
||
ChannelSymKey string
|
||
ChannelPubKey string
|
||
TopicID string
|
||
visibility string
|
||
subscriptions []*Subscription
|
||
}
|
||
|
||
// Subscribe to the current channel by polling the network for new messages
|
||
// and executing provided handler
|
||
func (c *Channel) Subscribe(fn MsgHandler) (*Subscription, error) {
|
||
log.Println("Subscribed to channel '", c.name, "'")
|
||
subscription := &Subscription{}
|
||
go subscription.Subscribe(c, fn)
|
||
c.subscriptions = append(c.subscriptions, subscription)
|
||
|
||
return subscription, nil
|
||
}
|
||
|
||
// Close current channel and all its subscriptions
|
||
func (c *Channel) Close() {
|
||
for _, sub := range c.subscriptions {
|
||
c.removeSubscription(sub)
|
||
}
|
||
}
|
||
|
||
// NewContactKeyRequest first message that is sent to a future contact. At that
|
||
// point the only topic we know that the contact is filtering is the
|
||
// discovery-topic with his public key so that is what NewContactKey will
|
||
// be sent to.
|
||
// It contains the sym-key and topic that will be used for future communications
|
||
// as well as the actual message that we want to send.
|
||
// The sym-key and topic are generated randomly because we don’t want to have
|
||
// any correlation between a topic and its participants to avoid leaking
|
||
// metadata.
|
||
// When one of the contacts recovers his account, a NewContactKey message is
|
||
// sent as well to change the symmetric key and topic.
|
||
func (c *Channel) NewContactKeyRequest(username string) error {
|
||
format := `["%s",["%s","%s","%s","%s"]]]`
|
||
contactRequest := fmt.Sprintf(format, ContactRequestType, username, "", "", "")
|
||
|
||
format = `["%s",["%s","%s",%s]`
|
||
msg := fmt.Sprintf(format, NewContactKeyType, c.account.Address, c.TopicID, contactRequest)
|
||
|
||
return c.SendPostRawMsg(msg)
|
||
}
|
||
|
||
// ContactRequest wrapped in a NewContactKey message when initiating a contact request.
|
||
func (c *Channel) ContactRequest(username, image string) error {
|
||
format := `["%s",["%s","%s","%s","%s"]]]`
|
||
msg := fmt.Sprintf(format, ContactRequestType, username, image, c.account.Address, "")
|
||
|
||
return c.SendPostRawMsg(msg)
|
||
}
|
||
|
||
// ConfirmedContactRequest this is the message that will be sent when the
|
||
// contact accepts the contact request. It will be sent on the topic that
|
||
// was provided in the NewContactKey message and use the sym-key.
|
||
// Both users will therefore have the same filter.
|
||
func (c *Channel) ConfirmedContactRequest(ct *Contact) error {
|
||
format := `["%s",["%s","%s","%s","%s"]]`
|
||
msg := fmt.Sprintf(format, ConfirmedContactRequestType, c.account.Username, c.account.Image, c.account.Address, "")
|
||
|
||
return c.SendPostRawMsg(msg)
|
||
}
|
||
|
||
// Publish a message with the given body on the current channel
|
||
func (c *Channel) Publish(body string) error {
|
||
visibility := "~:public-group-user-message"
|
||
if c.visibility != "" {
|
||
visibility = c.visibility
|
||
}
|
||
|
||
return c.publish(body, visibility)
|
||
}
|
||
|
||
func (c *Channel) publish(body, visibility string) error {
|
||
now := time.Now().Unix() * 1000
|
||
format := `["%s",["%s","text/plain","%s",%d,%d]]`
|
||
|
||
msg := fmt.Sprintf(format, StandardMessageType, body, visibility, now*100, now)
|
||
println("[ SENDING ] : " + msg)
|
||
|
||
return c.SendPostRawMsg(msg)
|
||
}
|
||
|
||
// SeenRequest sent when a user sees a message (opens the chat and loads the
|
||
// message). Can acknowledge multiple messages at the same time
|
||
func (c *Channel) SeenRequest(ids []string) error {
|
||
format := `["%s",["%s","%s"]]`
|
||
body, err := json.Marshal(ids)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
msg := fmt.Sprintf(format, SeenType, body)
|
||
|
||
return c.SendPostRawMsg(msg)
|
||
}
|
||
|
||
// ContactUpdateRequest sent when the user changes his name or profile-image.
|
||
func (c *Channel) ContactUpdateRequest(username, image string) error {
|
||
format := `["%s",["%s","%s"]]`
|
||
msg := fmt.Sprintf(format, ContactUpdateType, username, image)
|
||
|
||
return c.SendPostRawMsg(msg)
|
||
}
|
||
|
||
// SendPostRawMsg sends a shh_post message with the given body.
|
||
func (c *Channel) SendPostRawMsg(body string) error {
|
||
msg := Message{
|
||
Signature: c.account.AddressKeyID,
|
||
SymKeyID: c.ChannelSymKey,
|
||
Payload: rawrChatMessage(body),
|
||
Topic: c.TopicID,
|
||
TTL: 10,
|
||
PowTarget: c.account.conn.minimumPoW,
|
||
PowTime: 1,
|
||
}
|
||
|
||
_, err := shhPostRequest(c.account.conn, &msg)
|
||
if err != nil {
|
||
log.Println(err.Error())
|
||
}
|
||
|
||
return err
|
||
}
|
||
|
||
// PNBroadcastAvailabilityRequest makes a request used by push notification
|
||
// servers to broadcast its availability, this request is exposing current
|
||
// push notification server Public Key.
|
||
func (c *Channel) PNBroadcastAvailabilityRequest() error {
|
||
format := `["%s",["%s"]]`
|
||
msg := fmt.Sprintf(format, PNBroadcastAvailabilityType, c.account.PubKey)
|
||
|
||
return c.SendPostRawMsg(msg)
|
||
}
|
||
|
||
// PNRegistrationRequest request sent by clients wanting to be registered on
|
||
// a specific push notification server.
|
||
// The client has to provide a channel(topic + symkey) so the future
|
||
// communications happen through this channel.
|
||
// Additionally a device token will identify the device on the push notification
|
||
// provider.
|
||
func (c *Channel) PNRegistrationRequest(symkey, topic, deviceToken string, slotAvailabilityRatio float32) error {
|
||
format := `["%s",["%s","%s","%s", %g]]`
|
||
msg := fmt.Sprintf(format, PNRegistrationType, symkey, topic, deviceToken, slotAvailabilityRatio)
|
||
|
||
return c.SendPostRawMsg(msg)
|
||
}
|
||
|
||
// PNRegistrationConfirmationRequest request sent by the push notification
|
||
// server to let a client know what's the pubkey associated with its registered
|
||
// token.
|
||
func (c *Channel) PNRegistrationConfirmationRequest(pubkey string) error {
|
||
format := `["%s",["%s"]]`
|
||
msg := fmt.Sprintf(format, PNRegistrationConfirmationType, pubkey)
|
||
|
||
return c.SendPostRawMsg(msg)
|
||
}
|
||
|
||
func (c *Channel) removeSubscription(sub *Subscription) {
|
||
var subs []*Subscription
|
||
for _, s := range c.subscriptions {
|
||
if s != sub {
|
||
subs = append(subs, s)
|
||
}
|
||
}
|
||
c.subscriptions = subs
|
||
}
|
||
|
||
// PubKey return the channel's associated publike key
|
||
func (c *Channel) PubKey() string {
|
||
return c.account.PubKey
|
||
}
|
||
|
||
func (c *Channel) pollMessages() (msg *Msg) {
|
||
res, err := shhGetFilterMessagesRequest(c.account.conn, c.filterID)
|
||
if err != nil {
|
||
log.Fatalf("Error when sending request to server: %s", err)
|
||
return
|
||
}
|
||
|
||
switch vv := res.(type) {
|
||
case []interface{}:
|
||
for _, u := range vv {
|
||
msg, err = messageFromEnvelope(u)
|
||
if err == nil && supportedMessage(msg.Type) {
|
||
msg.Channel = c
|
||
msg.ChannelName = c.name
|
||
return
|
||
} else if err != nil {
|
||
log.Println("[ ERROR ]", err.Error())
|
||
return
|
||
} else {
|
||
log.Println("[ ERROR ]", "Invalid message type", msg.Type)
|
||
}
|
||
}
|
||
return nil
|
||
default:
|
||
log.Println(res, "is of a type I don't know how to handle")
|
||
}
|
||
return
|
||
}
|