Initial comit
This commit is contained in:
commit
9b46298eca
21
.github/ISSUE_TEMPLATE.md
vendored
Normal file
21
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Problem
|
||||
|
||||
An overview of the background required to understand the problem.
|
||||
A problem description.
|
||||
|
||||
# Implementation
|
||||
|
||||
Known steps towards feature implementation.
|
||||
What needs further specifying and investigating.
|
||||
|
||||
# Acceptance Criteria
|
||||
|
||||
Rules for the future PR to be accepted.
|
||||
|
||||
# Notes
|
||||
|
||||
Random notes to keep in mind while implementing it. Mostly about related issues and future plans and thoughts.
|
||||
|
||||
# Future Steps
|
||||
|
||||
Steps which should be taken after this issue has been resolved.
|
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
A short summary which serves as a squashed-commit message.
|
||||
|
||||
A description to understand introduced changes without reading the code.
|
||||
|
||||
Important changes:
|
||||
- [x] Something worth noting for reviewers.
|
||||
|
||||
Closes #
|
71
.gitignore
vendored
Normal file
71
.gitignore
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
#
|
||||
# If you find yourself ignoring temporary files generated by your text editor
|
||||
# or operating system, you probably want to add a global ignore instead:
|
||||
# git config --global core.excludesfile ~/.gitignore_global
|
||||
|
||||
/statusd-data
|
||||
/cmd/statusd/statusd-data
|
||||
/cmd/statusd/statusd
|
||||
/cmd/statusd-cli/statusd-cli
|
||||
|
||||
/wnode-status-data
|
||||
/cmd/wnode-status/wnode-status-data
|
||||
/cmd/wnode-status/wnode-status
|
||||
|
||||
/tmp
|
||||
*/**/*un~
|
||||
*/**/*.test
|
||||
*un~
|
||||
.DS_Store
|
||||
*/**/.DS_Store
|
||||
.ethtest
|
||||
*/**/*tx_database*
|
||||
*/**/*dapps*
|
||||
vendor/github.com/ethereum/go-ethereum/vendor
|
||||
node_modules/
|
||||
tags
|
||||
|
||||
#*
|
||||
.#*
|
||||
*#
|
||||
*~
|
||||
.project
|
||||
.settings
|
||||
|
||||
# used by the Makefile
|
||||
/build/_workspace/
|
||||
/build/bin/
|
||||
/vendor/github.com/karalabe/xgo
|
||||
|
||||
# travis
|
||||
profile.tmp
|
||||
profile.cov
|
||||
|
||||
# vagrant
|
||||
.vagrant
|
||||
|
||||
# tests
|
||||
.ethereumtest/
|
||||
/vendor/**/*_test.go
|
||||
*.test
|
||||
|
||||
#
|
||||
# golang
|
||||
coverage.out
|
||||
coverage-all.out
|
||||
coverage.html
|
||||
|
||||
# vim swap files
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-v][a-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
Session.vim
|
||||
.undodir/*
|
||||
/.idea/
|
||||
|
||||
# SDK examples
|
||||
/sdk/examples/ponger/data/
|
||||
/sdk/examples/pinger/data/
|
59
chan.go
Normal file
59
chan.go
Normal file
@ -0,0 +1,59 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Channel : ...
|
||||
type Channel struct {
|
||||
conn *Conn
|
||||
channelName string
|
||||
filterID string
|
||||
channelKey string
|
||||
topic 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)
|
||||
cmd := fmt.Sprintf(standardMessageFormat,
|
||||
c.conn.address,
|
||||
c.channelKey,
|
||||
message.ToPayload(),
|
||||
c.topic,
|
||||
c.conn.minimumPoW,
|
||||
)
|
||||
|
||||
c.conn.rpc.Call(cmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subscribe : ...
|
||||
func (c *Channel) Subscribe(fn MsgHandler) (*Subscription, error) {
|
||||
log.Println("Subscribed to channel '", c.channelName, "'")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Channel) removeSubscription(sub *Subscription) {
|
||||
var subs []*Subscription
|
||||
for _, s := range c.subscriptions {
|
||||
if s != sub {
|
||||
subs = append(subs, s)
|
||||
}
|
||||
}
|
||||
c.subscriptions = subs
|
||||
}
|
116
conn.go
Normal file
116
conn.go
Normal file
@ -0,0 +1,116 @@
|
||||
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
|
||||
}
|
14
dictionary.go
Normal file
14
dictionary.go
Normal file
@ -0,0 +1,14 @@
|
||||
package sdk
|
||||
|
||||
var (
|
||||
generateSymKeyFromPasswordFormat = `{"jsonrpc":"2.0","id":2950,"method":"shh_generateSymKeyFromPassword","params":["%s"]}`
|
||||
newMessageFilterFormat = `{"jsonrpc":"2.0","id":2,"method":"shh_newMessageFilter","params":[{"allowP2P":true,"topics":["%s"],"type":"sym","symKeyID":"%s"}]}`
|
||||
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"]}`
|
||||
|
||||
messagePayloadFormat = `["~#c4",["%s","text/plain","~:public-group-user-message",%d,%d]]`
|
||||
)
|
25
examples/pinger/main.go
Normal file
25
examples/pinger/main.go
Normal file
@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-go-sdk"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conn := sdk.NewConn("localhost:30303")
|
||||
if err := conn.Signup("111222333"); err != nil {
|
||||
panic("Couldn't create an account")
|
||||
}
|
||||
|
||||
ch, err := conn.Join("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)
|
||||
}
|
||||
}
|
37
examples/ponger/main.go
Normal file
37
examples/ponger/main.go
Normal file
@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-go-sdk"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conn := sdk.NewConn()
|
||||
|
||||
if err := conn.SignupOrLogin("supu", "password"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ch, err := conn.Join("supu")
|
||||
if err != nil {
|
||||
panic("Couldn't connect to status")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
runtime.Goexit()
|
||||
}
|
63
msg.go
Normal file
63
msg.go
Normal file
@ -0,0 +1,63 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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:"-"`
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// ToPayload converts current struct to a valid payload
|
||||
func (m *Msg) ToPayload() string {
|
||||
message := fmt.Sprintf(messagePayloadFormat,
|
||||
m.Text,
|
||||
m.Timestamp*100,
|
||||
m.Timestamp)
|
||||
|
||||
return rawrChatMessage(message)
|
||||
}
|
||||
|
||||
// MessageFromPayload : TODO ...
|
||||
func MessageFromPayload(payload string) (*Msg, error) {
|
||||
message, err := unrawrChatMessage(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var x []interface{}
|
||||
json.Unmarshal([]byte(message), &x)
|
||||
if len(x) < 1 {
|
||||
return nil, errors.New("unsupported message type")
|
||||
}
|
||||
if x[0].(string) != "~#c4" {
|
||||
return nil, errors.New("unsupported message type")
|
||||
}
|
||||
properties := x[1].([]interface{})
|
||||
|
||||
return &Msg{
|
||||
From: "TODO : someone",
|
||||
Text: properties[0].(string),
|
||||
Timestamp: int64(properties[3].(float64)),
|
||||
Raw: string(message),
|
||||
}, nil
|
||||
}
|
56
subscription.go
Normal file
56
subscription.go
Normal file
@ -0,0 +1,56 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MsgHandler is a callback function that processes messages delivered to
|
||||
// asynchronous subscribers.
|
||||
type MsgHandler func(msg *Msg)
|
||||
|
||||
type Subscription struct {
|
||||
unsubscribe chan bool
|
||||
channel *Channel
|
||||
}
|
||||
|
||||
func (s *Subscription) Subscribe(channel *Channel, fn MsgHandler) {
|
||||
s.channel = channel
|
||||
s.unsubscribe = make(chan bool)
|
||||
for {
|
||||
select {
|
||||
case <-s.unsubscribe:
|
||||
return
|
||||
default:
|
||||
cmd := fmt.Sprintf(getFilterMessagesFormat, channel.filterID)
|
||||
response, err := channel.conn.rpc.Call(cmd)
|
||||
if err != nil {
|
||||
log.Fatalf("Error when sending request to server: %s", err)
|
||||
}
|
||||
|
||||
f := unmarshalJSON(response.(string))
|
||||
v := f.(map[string]interface{})["result"]
|
||||
switch vv := v.(type) {
|
||||
case []interface{}:
|
||||
for _, u := range vv {
|
||||
payload := u.(map[string]interface{})["payload"]
|
||||
message, err := MessageFromPayload(payload.(string))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
fn(message)
|
||||
}
|
||||
}
|
||||
default:
|
||||
log.Println(v, "is of a type I don't know how to handle")
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Subscription) Unsubscribe() {
|
||||
s.unsubscribe <- true
|
||||
s.channel.removeSubscription(s)
|
||||
}
|
39
utils.go
Normal file
39
utils.go
Normal file
@ -0,0 +1,39 @@
|
||||
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…
x
Reference in New Issue
Block a user