Merge pull request #4 from status-im/feature/issue-3/new-protocol-support
[#ISSUE-3] New protocol messages support
This commit is contained in:
commit
86e90a7583
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"Exclude": [
|
||||
".*_mock.go",
|
||||
"geth/jail/doc.go",
|
||||
".*Errors unhandled.*fmt.Fprint.*gasv2.*"
|
||||
],
|
||||
"Skip": ["helpers", "static"],
|
||||
"Vendor": true,
|
||||
"Test": true,
|
||||
"Linters": {
|
||||
"gasv2": {
|
||||
"Command": "gas -fmt=csv",
|
||||
"Pattern": "^(?P<path>.*?\\.go),(?P<line>\\d+),(?P<message>[^,]+,[^,]+,[^,]+,\".*\")"
|
||||
}
|
||||
},
|
||||
"Enable": [
|
||||
"deadcode",
|
||||
"errcheck",
|
||||
"gasv2",
|
||||
"goconst",
|
||||
"gocyclo",
|
||||
"gofmt",
|
||||
"golint",
|
||||
"ineffassign",
|
||||
"interfacer",
|
||||
"megacheck",
|
||||
"misspell",
|
||||
"structcheck",
|
||||
"unconvert",
|
||||
"varcheck",
|
||||
"vet"
|
||||
],
|
||||
"Cyclo": 16,
|
||||
"Deadline": "200s"
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
notifications:
|
||||
email: false
|
||||
language: go
|
||||
go:
|
||||
- 1.9.x
|
||||
sudo: false
|
||||
dist: trusty
|
||||
install:
|
||||
- make lint-install
|
||||
jobs:
|
||||
include:
|
||||
- stage: Lint
|
||||
sudo: required
|
||||
script:
|
||||
- make lint
|
|
@ -0,0 +1,7 @@
|
|||
lint-install:
|
||||
go get -u github.com/alecthomas/gometalinter
|
||||
gometalinter --install
|
||||
|
||||
lint:
|
||||
@echo "lint"
|
||||
@gometalinter ./...
|
83
chan.go
83
chan.go
|
@ -1,34 +1,32 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Channel : ...
|
||||
type Channel struct {
|
||||
conn *Conn
|
||||
conn *SDK
|
||||
channelName string
|
||||
filterID string
|
||||
channelKey string
|
||||
topic string
|
||||
topicID string
|
||||
subscriptions []*Subscription
|
||||
}
|
||||
|
||||
// Publish : Publishes a message with the given body on the current channel
|
||||
func (c *Channel) Publish(body string) error {
|
||||
message := NewMsg(c.conn.userName, body, c.channelName)
|
||||
msg := NewMsg(c.conn.userName, body, c.channelName)
|
||||
cmd := fmt.Sprintf(standardMessageFormat,
|
||||
c.conn.address,
|
||||
c.channelKey,
|
||||
message.ToPayload(),
|
||||
c.topic,
|
||||
msg.ToPayload(),
|
||||
c.topicID,
|
||||
c.conn.minimumPoW,
|
||||
)
|
||||
|
||||
c.conn.rpc.Call(cmd)
|
||||
|
||||
return nil
|
||||
return c.conn.call(cmd, nil)
|
||||
}
|
||||
|
||||
// Subscribe : ...
|
||||
|
@ -48,6 +46,73 @@ func (c *Channel) Close() {
|
|||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
contactRequest := fmt.Sprintf(contactRequestMsg, username, "", "", "")
|
||||
msg := fmt.Sprintf(newContactKeyMsg, c.conn.address, c.topicID, contactRequest)
|
||||
|
||||
c.callStandardMsg(msg)
|
||||
}
|
||||
|
||||
// ContactRequest : Wrapped in a NewContactKey message when initiating a contact request.
|
||||
func (c *Channel) ContactRequest(username, image string) {
|
||||
msg := fmt.Sprintf(contactRequestMsg, username, image, c.conn.address, "")
|
||||
c.callStandardMsg(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(username, image string) {
|
||||
msg := fmt.Sprintf(confirmedContactRequestMsg, username, image, c.conn.address, "")
|
||||
c.callStandardMsg(msg)
|
||||
}
|
||||
|
||||
// ContactUpdateRequest : Sent when the user changes his name or profile-image.
|
||||
func (c *Channel) ContactUpdateRequest(username, image string) {
|
||||
msg := fmt.Sprintf(contactUpdateMsg, username, image)
|
||||
c.callStandardMsg(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 {
|
||||
body, err := json.Marshal(ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf(seenMsg, body)
|
||||
c.callStandardMsg(msg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Channel) callStandardMsg(body string) {
|
||||
msg := rawrChatMessage(body)
|
||||
|
||||
cmd := fmt.Sprintf(standardMessageFormat,
|
||||
c.conn.address,
|
||||
c.channelKey,
|
||||
msg,
|
||||
c.topicID,
|
||||
c.conn.minimumPoW)
|
||||
|
||||
// TODO (adriacidre) manage this error
|
||||
_ = c.conn.call(cmd, nil)
|
||||
}
|
||||
|
||||
func (c *Channel) removeSubscription(sub *Subscription) {
|
||||
var subs []*Subscription
|
||||
for _, s := range c.subscriptions {
|
||||
|
|
116
conn.go
116
conn.go
|
@ -1,116 +0,0 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/valyala/gorpc"
|
||||
)
|
||||
|
||||
// Conn : TODO ...
|
||||
type Conn struct {
|
||||
rpc *gorpc.Client
|
||||
address string
|
||||
userName string
|
||||
channels []*Channel
|
||||
minimumPoW string
|
||||
}
|
||||
|
||||
func NewConn(address string) *Conn {
|
||||
rpc := &gorpc.Client{
|
||||
Addr: address, // "rpc.server.addr:12345",
|
||||
}
|
||||
rpc.Start()
|
||||
|
||||
return &Conn{
|
||||
rpc: rpc,
|
||||
minimumPoW: "0.01",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Close() {
|
||||
for _, channel := range c.channels {
|
||||
channel.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Login logs in to the network with the given credentials
|
||||
func (c *Conn) Login(addr, pwd string) error {
|
||||
cmd := fmt.Sprintf(statusLoginFormat, addr, pwd)
|
||||
res, err := c.rpc.Call(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(adriacidre) unmarshall and treat the response
|
||||
println(res)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Signup creates a new account with the given credentials
|
||||
func (c *Conn) Signup(pwd string) error {
|
||||
cmd := fmt.Sprintf(statusSignupFormat, pwd)
|
||||
res, err := c.rpc.Call(cmd)
|
||||
println("------")
|
||||
println(res)
|
||||
println(err.Error())
|
||||
println("------")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(adriacidre) unmarshall and treat the response
|
||||
println(res)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignupOrLogin will attempt to login with given credentials, in first instance
|
||||
// or will sign up in case login does not work
|
||||
func (c *Conn) SignupOrLogin(user, password string) error {
|
||||
if err := c.Login(user, password); err != nil {
|
||||
c.Signup(password)
|
||||
return c.Login(user, password)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Join a specific channel by name
|
||||
func (c *Conn) Join(channelName string) (*Channel, error) {
|
||||
ch, err := c.joinPublicChannel(channelName)
|
||||
if err != nil {
|
||||
c.channels = append(c.channels, ch)
|
||||
}
|
||||
|
||||
return ch, err
|
||||
}
|
||||
|
||||
func (c *Conn) joinPublicChannel(channelName string) (*Channel, error) {
|
||||
cmd := fmt.Sprintf(generateSymKeyFromPasswordFormat, channelName)
|
||||
res, _ := c.rpc.Call(cmd)
|
||||
f := unmarshalJSON(res.(string))
|
||||
|
||||
key := f.(map[string]interface{})["result"].(string)
|
||||
id := int(f.(map[string]interface{})["id"].(float64))
|
||||
|
||||
src := []byte(channelName)
|
||||
p := "0x" + hex.EncodeToString(src)
|
||||
|
||||
cmd = fmt.Sprintf(web3ShaFormat, p, id)
|
||||
res, _ = c.rpc.Call(cmd)
|
||||
topic := res.(map[string]interface{})["result"].(string)
|
||||
topic = topic[0:10]
|
||||
|
||||
cmd = fmt.Sprintf(newMessageFilterFormat, topic, key)
|
||||
res, _ = c.rpc.Call(cmd)
|
||||
f3 := unmarshalJSON(res.(string))
|
||||
filterID := f3.(map[string]interface{})["result"].(string)
|
||||
|
||||
return &Channel{
|
||||
conn: c,
|
||||
channelName: channelName,
|
||||
filterID: filterID,
|
||||
topic: topic,
|
||||
channelKey: key,
|
||||
}, nil
|
||||
}
|
|
@ -6,9 +6,13 @@ var (
|
|||
getFilterMessagesFormat = `{"jsonrpc":"2.0","id":2968,"method":"shh_getFilterMessages","params":["%s"]}`
|
||||
standardMessageFormat = `{"jsonrpc":"2.0","id":633,"method":"shh_post","params":[{"sig":"%s","symKeyID":"%s","payload":"%s","topic":"%s","ttl":10,"powTarget":%g,"powTime":1}]}`
|
||||
web3ShaFormat = `{"jsonrpc":"2.0","method":"web3_sha3","params":["%s"],"id":%d}`
|
||||
statusLoginFormat = `{"jsonrpc":"2.0","method":"status_login","params":["%s","%s"]}`
|
||||
statusSignupFormat = `{"jsonrpc":"2.0","method":"status_signup","params":["%s","%s"]}`
|
||||
statusJoinPublicChannel = `{"jsonrpc":"2.0","method":"status_joinpublicchannel","params":["%s"]}`
|
||||
statusLoginFormat = `{"jsonrpc":"2.0","method":"status_login","params":[{"address":"%s","password":"%s"}]}`
|
||||
statusSignupFormat = `{"jsonrpc":"2.0","method":"status_signup","params":[{"password":"%s"}]}`
|
||||
|
||||
messagePayloadFormat = `["~#c4",["%s","text/plain","~:public-group-user-message",%d,%d]]`
|
||||
newContactKeyMsg = `["~#c1",["%s","%s",%s]`
|
||||
contactRequestMsg = `["~#c2",["%s","%s","%s","%s"]]]`
|
||||
confirmedContactRequestMsg = `["~#c3",["%s","%s","%s","%s"]]`
|
||||
messagePayloadMsg = `["~#c4",["%s","text/plain","~:public-group-user-message",%d,%d]]`
|
||||
seenMsg = `["~#c5",["%s","%s"]]`
|
||||
contactUpdateMsg = `["~#c6",["%s","%s"]]`
|
||||
)
|
||||
|
|
|
@ -8,18 +8,24 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
conn := sdk.NewConn("localhost:30303")
|
||||
if err := conn.Signup("111222333"); err != nil {
|
||||
panic("Couldn't create an account")
|
||||
client := sdk.New("localhost:30303")
|
||||
|
||||
addr, _, _, err := client.Signup("password")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ch, err := conn.Join("supu")
|
||||
if err := client.Login(addr, "password"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ch, err := client.JoinPublicChannel("supu")
|
||||
if err != nil {
|
||||
panic("Couldn't connect to status")
|
||||
}
|
||||
|
||||
for range time.Tick(10 * time.Second) {
|
||||
message := fmt.Sprintf("PING : %d", time.Now().Unix())
|
||||
ch.Publish(message)
|
||||
_ = ch.Publish(message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,24 +11,29 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
conn := sdk.NewConn()
|
||||
client := sdk.New("localhost:30303")
|
||||
|
||||
if err := conn.SignupOrLogin("supu", "password"); err != nil {
|
||||
addr, _, _, err := client.Signup("password")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := client.Login(addr, "password"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ch, err := conn.Join("supu")
|
||||
ch, err := client.JoinPublicChannel("supu")
|
||||
if err != nil {
|
||||
panic("Couldn't connect to status")
|
||||
}
|
||||
|
||||
ch.Subscribe(func(m *sdk.Msg) {
|
||||
_, _ = ch.Subscribe(func(m *sdk.Msg) {
|
||||
log.Println("Message from ", m.From, " with body: ", m.Text)
|
||||
|
||||
if strings.Contains(m.Text, "PING :") {
|
||||
time.Sleep(5 * time.Second)
|
||||
message := fmt.Sprintf("PONG : %d", time.Now().Unix())
|
||||
ch.Publish(message)
|
||||
_ = ch.Publish(message)
|
||||
}
|
||||
|
||||
})
|
||||
|
|
87
msg.go
87
msg.go
|
@ -1,36 +1,72 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
)
|
||||
|
||||
var (
|
||||
// NewContactKeyType message type for newContactKeyFormat
|
||||
NewContactKeyType = "~#c1"
|
||||
// ContactRequestType message type for contactRequestFormat
|
||||
ContactRequestType = "~#c2"
|
||||
// ConfirmedContactRequestType message type for confirmedContactRequestFormat
|
||||
ConfirmedContactRequestType = "~#c3"
|
||||
// StandardMessageType message type for StandardMessageFormat
|
||||
StandardMessageType = "~#c4"
|
||||
// SeenType message type for SeentType
|
||||
SeenType = "~#c5"
|
||||
// ContactUpdateType message type for contactUpdateMsg
|
||||
ContactUpdateType = "~#c6"
|
||||
)
|
||||
|
||||
// supportedMessage check if the message type is supported
|
||||
func supportedMessage(msgType string) bool {
|
||||
_, ok := map[string]bool{
|
||||
NewContactKeyType: true,
|
||||
ContactRequestType: true,
|
||||
ConfirmedContactRequestType: true,
|
||||
StandardMessageType: true,
|
||||
SeenType: true,
|
||||
ContactUpdateType: true,
|
||||
}[msgType]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// Msg is a structure used by Subscribers and Publish().
|
||||
type Msg struct {
|
||||
ID string `json:"id"`
|
||||
From string `json:"from"`
|
||||
Text string `json:"text"`
|
||||
ChannelName string `json:"channel"`
|
||||
Timestamp int64 `json:"ts"`
|
||||
Raw string `json:"-"`
|
||||
Type string `json:"-"`
|
||||
}
|
||||
|
||||
// NewMsg : Creates a new Msg with a generated UUID
|
||||
// NewMsg creates a new Msg with a generated UUID
|
||||
func NewMsg(from, text, channel string) *Msg {
|
||||
return &Msg{
|
||||
ID: newUUID(),
|
||||
From: from,
|
||||
Text: text,
|
||||
ChannelName: channel,
|
||||
Timestamp: time.Now().Unix() * 1000,
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
// ToPayload converts current struct to a valid payload
|
||||
// ID gets the message id
|
||||
func (m *Msg) ID() string {
|
||||
return fmt.Sprintf("%X", sha3.Sum256([]byte(m.Raw)))
|
||||
}
|
||||
|
||||
// ToPayload converts current struct to a valid payload
|
||||
func (m *Msg) ToPayload() string {
|
||||
message := fmt.Sprintf(messagePayloadFormat,
|
||||
message := fmt.Sprintf(messagePayloadMsg,
|
||||
m.Text,
|
||||
m.Timestamp*100,
|
||||
m.Timestamp)
|
||||
|
@ -38,26 +74,45 @@ func (m *Msg) ToPayload() string {
|
|||
return rawrChatMessage(message)
|
||||
}
|
||||
|
||||
// MessageFromPayload : TODO ...
|
||||
func rawrChatMessage(raw string) string {
|
||||
bytes := []byte(raw)
|
||||
|
||||
return fmt.Sprintf("0x%s", hex.EncodeToString(bytes))
|
||||
}
|
||||
|
||||
func unrawrChatMessage(message string) ([]byte, error) {
|
||||
return hex.DecodeString(message[2:])
|
||||
}
|
||||
|
||||
// MessageFromPayload creates a message from a payload
|
||||
func MessageFromPayload(payload string) (*Msg, error) {
|
||||
message, err := unrawrChatMessage(payload)
|
||||
var msg []interface{}
|
||||
|
||||
rawMsg, err := unrawrChatMessage(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var x []interface{}
|
||||
json.Unmarshal([]byte(message), &x)
|
||||
if len(x) < 1 {
|
||||
|
||||
if err = json.Unmarshal(rawMsg, &msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(msg) < 1 {
|
||||
return nil, errors.New("unknown message format")
|
||||
}
|
||||
|
||||
msgType := msg[0].(string)
|
||||
if !supportedMessage(msgType) {
|
||||
return nil, errors.New("unsupported message type")
|
||||
}
|
||||
if x[0].(string) != "~#c4" {
|
||||
return nil, errors.New("unsupported message type")
|
||||
}
|
||||
properties := x[1].([]interface{})
|
||||
|
||||
properties := msg[1].([]interface{})
|
||||
|
||||
return &Msg{
|
||||
Type: msgType,
|
||||
From: "TODO : someone",
|
||||
Text: properties[0].(string),
|
||||
Timestamp: int64(properties[3].(float64)),
|
||||
Raw: string(message),
|
||||
Raw: string(rawMsg),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package sdk
|
||||
|
||||
import "github.com/valyala/gorpc"
|
||||
|
||||
// RPCClient is a client to manage all rpc calls
|
||||
type RPCClient interface {
|
||||
Call(request interface{}) (response interface{}, err error)
|
||||
}
|
||||
|
||||
func newRPC(address string) RPCClient {
|
||||
rpc := &gorpc.Client{
|
||||
Addr: address, // "rpc.server.addr:12345",
|
||||
}
|
||||
rpc.Start()
|
||||
|
||||
return rpc
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// SDK is a set of tools to interact with status node
|
||||
type SDK struct {
|
||||
RPCClient RPCClient
|
||||
address string
|
||||
userName string
|
||||
channels []*Channel
|
||||
minimumPoW float64
|
||||
}
|
||||
|
||||
// New creates a default SDK object
|
||||
func New(address string) *SDK {
|
||||
return &SDK{
|
||||
RPCClient: newRPC(address),
|
||||
minimumPoW: 0.001,
|
||||
}
|
||||
}
|
||||
|
||||
// Close all channels you're subscribed to
|
||||
func (c *SDK) Close() {
|
||||
for _, channel := range c.channels {
|
||||
channel.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// LoginResponse : json response returned by status_login.
|
||||
type LoginResponse struct {
|
||||
Result struct {
|
||||
AddressKeyID string `json:"address_key_id"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// Login to status with the given credentials
|
||||
func (c *SDK) Login(addr, pwd string) error {
|
||||
var res LoginResponse
|
||||
cmd := fmt.Sprintf(statusLoginFormat, addr, pwd)
|
||||
if err := c.call(cmd, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
c.address = res.Result.AddressKeyID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignupResponse : json response returned by status_signup.
|
||||
type SignupResponse struct {
|
||||
Result struct {
|
||||
Address string `json:"address"`
|
||||
Pubkey string `json:"pubkey"`
|
||||
Mnemonic string `json:"mnemonic"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// Signup creates a new account with the given credentials
|
||||
func (c *SDK) Signup(pwd string) (addr string, pubkey string, mnemonic string, err error) {
|
||||
var res SignupResponse
|
||||
|
||||
cmd := fmt.Sprintf(statusSignupFormat, pwd)
|
||||
|
||||
if err = c.call(cmd, &res); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return res.Result.Address, res.Result.Pubkey, res.Result.Mnemonic, err
|
||||
}
|
||||
|
||||
// SignupAndLogin sign up and login on status network
|
||||
func (c *SDK) SignupAndLogin(password string) (addr string, pubkey string, mnemonic string, err error) {
|
||||
addr, pubkey, mnemonic, err = c.Signup(password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.Login(addr, password)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
// Join a specific channel by name
|
||||
func (c *SDK) Join(channelName string) (*Channel, error) {
|
||||
ch, err := c.joinPublicChannel(channelName)
|
||||
if err != nil {
|
||||
c.channels = append(c.channels, ch)
|
||||
}
|
||||
|
||||
return ch, err
|
||||
}
|
||||
*/
|
||||
|
||||
// GenerateSymKeyFromPasswordResponse GenerateSymKeyFromPassword json response
|
||||
type GenerateSymKeyFromPasswordResponse struct {
|
||||
Key string `json:"result"`
|
||||
ID int `json:"id"`
|
||||
}
|
||||
|
||||
// Web3ShaResponse Web3Sha json response
|
||||
type Web3ShaResponse struct {
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
// NewMessageFilterResponse NewMessageFilter json response
|
||||
type NewMessageFilterResponse struct {
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
// JoinPublicChannel joins a status public channel
|
||||
func (c *SDK) JoinPublicChannel(channelName string) (*Channel, error) {
|
||||
var symkeyResponse GenerateSymKeyFromPasswordResponse
|
||||
cmd := fmt.Sprintf(generateSymKeyFromPasswordFormat, channelName)
|
||||
if err := c.call(cmd, &symkeyResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := symkeyResponse.Key
|
||||
|
||||
p := "0x" + hex.EncodeToString([]byte(channelName))
|
||||
|
||||
var web3ShaResponse Web3ShaResponse
|
||||
cmd = fmt.Sprintf(web3ShaFormat, p, symkeyResponse.ID)
|
||||
if err := c.call(cmd, &web3ShaResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
topicID := web3ShaResponse.Result[0:10]
|
||||
|
||||
var newMessageFilterResponse NewMessageFilterResponse
|
||||
cmd = fmt.Sprintf(newMessageFilterFormat, topicID, key)
|
||||
if err := c.call(cmd, &newMessageFilterResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filterID := newMessageFilterResponse.Result
|
||||
|
||||
ch := &Channel{
|
||||
conn: c,
|
||||
channelName: channelName,
|
||||
filterID: filterID,
|
||||
topicID: topicID,
|
||||
channelKey: key,
|
||||
}
|
||||
c.channels = append(c.channels, ch)
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func (c *SDK) call(cmd string, res interface{}) error {
|
||||
log.Println("[ REQUST ] : " + cmd)
|
||||
body, err := c.RPCClient.Call(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println("[ RESPONSE ] : " + body.(string))
|
||||
|
||||
return json.Unmarshal([]byte(body.(string)), &res)
|
||||
}
|
|
@ -10,28 +10,34 @@ import (
|
|||
// asynchronous subscribers.
|
||||
type MsgHandler func(msg *Msg)
|
||||
|
||||
// Subscription is a polling helper for a specific channel
|
||||
type Subscription struct {
|
||||
unsubscribe chan bool
|
||||
channel *Channel
|
||||
}
|
||||
|
||||
// SubscriptionResponse json response for shh_getFilterMessages requests
|
||||
type SubscriptionResponse struct {
|
||||
Result interface{} `json:"result"`
|
||||
}
|
||||
|
||||
// Subscribe polls on specific channel topic and executes given function if
|
||||
// any message is received
|
||||
func (s *Subscription) Subscribe(channel *Channel, fn MsgHandler) {
|
||||
s.channel = channel
|
||||
s.unsubscribe = make(chan bool)
|
||||
for {
|
||||
select {
|
||||
case <-s.unsubscribe:
|
||||
return
|
||||
default:
|
||||
var res SubscriptionResponse
|
||||
cmd := fmt.Sprintf(getFilterMessagesFormat, channel.filterID)
|
||||
response, err := channel.conn.rpc.Call(cmd)
|
||||
if err != nil {
|
||||
if err := channel.conn.call(cmd, &res); err != nil {
|
||||
log.Fatalf("Error when sending request to server: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
f := unmarshalJSON(response.(string))
|
||||
v := f.(map[string]interface{})["result"]
|
||||
switch vv := v.(type) {
|
||||
switch vv := res.Result.(type) {
|
||||
case []interface{}:
|
||||
for _, u := range vv {
|
||||
payload := u.(map[string]interface{})["payload"]
|
||||
|
@ -39,17 +45,21 @@ func (s *Subscription) Subscribe(channel *Channel, fn MsgHandler) {
|
|||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
fn(message)
|
||||
if supportedMessage(message.Type) {
|
||||
fn(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
log.Println(v, "is of a type I don't know how to handle")
|
||||
log.Println(res.Result, "is of a type I don't know how to handle")
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// TODO(adriacidre) : move this period to configuration
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
}
|
||||
|
||||
// Unsubscribe stops polling on the current subscription channel
|
||||
func (s *Subscription) Unsubscribe() {
|
||||
s.unsubscribe <- true
|
||||
s.channel.removeSubscription(s)
|
||||
|
|
39
utils.go
39
utils.go
|
@ -1,39 +0,0 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func unmarshalJSON(j string) interface{} {
|
||||
var v interface{}
|
||||
json.Unmarshal([]byte(j), &v)
|
||||
return v
|
||||
}
|
||||
|
||||
// newUUID generates a random UUID according to RFC 4122
|
||||
func newUUID() string {
|
||||
uuid := make([]byte, 16)
|
||||
n, err := io.ReadFull(rand.Reader, uuid)
|
||||
if n != len(uuid) || err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// variant bits; see section 4.1.1
|
||||
uuid[8] = uuid[8]&^0xc0 | 0x80
|
||||
// version 4 (pseudo-random); see section 4.1.3
|
||||
uuid[6] = uuid[6]&^0xf0 | 0x40
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
|
||||
}
|
||||
|
||||
func rawrChatMessage(raw string) string {
|
||||
bytes := []byte(raw)
|
||||
|
||||
return fmt.Sprintf("0x%s", hex.EncodeToString(bytes))
|
||||
}
|
||||
|
||||
func unrawrChatMessage(message string) ([]byte, error) {
|
||||
return hex.DecodeString(message[2:])
|
||||
}
|
Loading…
Reference in New Issue