mirror of https://github.com/status-im/go-waku.git
init commit
This commit is contained in:
parent
7c13021a32
commit
ef1d05ad5c
|
@ -0,0 +1,2 @@
|
||||||
|
chat2
|
||||||
|
chat2-reliable
|
|
@ -0,0 +1,9 @@
|
||||||
|
.PHONY: all build run
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
go build -o build/chat2-reliable .
|
||||||
|
|
||||||
|
run:
|
||||||
|
./build/chat2
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Using the `chat2-reliable` application
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
The `chat2-reliable` application is a basic command-line chat app using the [Waku v2 suite of protocols](https://specs.vac.dev/specs/waku/v2/waku-v2). It connects to a [fleet of test nodes](fleets.status.im) to provide end-to-end p2p chat capabilities. The Waku team is currently using this application for internal testing. If you want try our protocols, or join the dogfooding fun, follow the instructions below.
|
||||||
|
|
||||||
|
## Preparation
|
||||||
|
```
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic application usage
|
||||||
|
|
||||||
|
To start the `chat2-reliable` application in its most basic form, run the following from the project directory
|
||||||
|
|
||||||
|
```
|
||||||
|
./build/chat2-reliable
|
||||||
|
```
|
||||||
|
|
||||||
|
You may need to set DNS server if behind a VPN,
|
||||||
|
|
||||||
|
```
|
||||||
|
./build/chat2-reliable --dns-discovery-name-server 8.8.8.8
|
||||||
|
```
|
||||||
|
|
||||||
|
## Specifying a static peer
|
||||||
|
|
||||||
|
In order to connect to a *specific* node as [`relay`](https://specs.vac.dev/specs/waku/v2/waku-relay) peer, define that node's `multiaddr` as a `staticnode` when starting the app:
|
||||||
|
|
||||||
|
```
|
||||||
|
./build/chat2-reliable -staticnode=/dns4/node-01.do-ams3.waku.test.statusim.net/tcp/30303/p2p/16Uiu2HAkykgaECHswi3YKJ5dMLbq2kPVCo89fcyTd38UcQD6ej5W
|
||||||
|
```
|
||||||
|
|
||||||
|
This will bypass the random peer selection process and connect to the specified node.
|
||||||
|
|
||||||
|
## In-chat options
|
||||||
|
|
||||||
|
| Command | Effect |
|
||||||
|
| --- | --- |
|
||||||
|
| `/help` | displays available in-chat commands |
|
||||||
|
| `/connect` | interactively connect to a new peer |
|
||||||
|
| `/nick` | change nickname for current chat session |
|
||||||
|
| `/peers` | Display the list of connected peers |
|
||||||
|
|
||||||
|
## `chat2-reliable` message protobuf format
|
||||||
|
|
||||||
|
Each `chat2-reliable` message is encoded as follows
|
||||||
|
|
||||||
|
```protobuf
|
||||||
|
message Chat2Message {
|
||||||
|
uint64 timestamp = 1;
|
||||||
|
string nick = 2;
|
||||||
|
bytes payload = 3;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
where `timestamp` is the Unix timestamp of the message, `nick` is the relevant `chat2-reliable` user's selected nickname and `payload` is the actual chat message being sent. The `payload` is the byte array representation of a UTF8 encoded string.
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
|
@ -0,0 +1,535 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chat2-reliable/pb"
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
"github.com/multiformats/go-multiaddr"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/dnsdisc"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/node"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/payload"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/protocol"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/protocol/filter"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_store"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/protocol/lightpush"
|
||||||
|
wpb "github.com/waku-org/go-waku/waku/v2/protocol/pb"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
|
||||||
|
wrln "github.com/waku-org/go-waku/waku/v2/protocol/rln"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/utils"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Chat represents a subscription to a single PubSub topic. Messages
|
||||||
|
// can be published to the topic with Chat.Publish, and received
|
||||||
|
// messages are pushed to the Messages channel.
|
||||||
|
type Chat struct {
|
||||||
|
ctx context.Context
|
||||||
|
wg sync.WaitGroup
|
||||||
|
node *node.WakuNode
|
||||||
|
ui UI
|
||||||
|
uiReady chan struct{}
|
||||||
|
inputChan chan string
|
||||||
|
options Options
|
||||||
|
|
||||||
|
C chan *protocol.Envelope
|
||||||
|
|
||||||
|
nick string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChat(ctx context.Context, node *node.WakuNode, connNotifier <-chan node.PeerConnection, options Options) *Chat {
|
||||||
|
chat := &Chat{
|
||||||
|
ctx: ctx,
|
||||||
|
node: node,
|
||||||
|
options: options,
|
||||||
|
nick: options.Nickname,
|
||||||
|
uiReady: make(chan struct{}, 1),
|
||||||
|
inputChan: make(chan string, 100),
|
||||||
|
}
|
||||||
|
|
||||||
|
chat.ui = NewUIModel(chat.uiReady, chat.inputChan)
|
||||||
|
|
||||||
|
topics := options.Relay.Topics.Value()
|
||||||
|
if len(topics) == 0 {
|
||||||
|
topics = append(topics, relay.DefaultWakuTopic)
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Filter.Enable {
|
||||||
|
cf := protocol.ContentFilter{
|
||||||
|
PubsubTopic: relay.DefaultWakuTopic,
|
||||||
|
ContentTopics: protocol.NewContentTopicSet(options.ContentTopic),
|
||||||
|
}
|
||||||
|
var filterOpt filter.FilterSubscribeOption
|
||||||
|
peerID, err := options.Filter.NodePeerID()
|
||||||
|
if err != nil {
|
||||||
|
filterOpt = filter.WithAutomaticPeerSelection()
|
||||||
|
} else {
|
||||||
|
filterOpt = filter.WithPeer(peerID)
|
||||||
|
chat.ui.InfoMessage(fmt.Sprintf("Subscribing to filter node %s", peerID))
|
||||||
|
}
|
||||||
|
theFilters, err := node.FilterLightnode().Subscribe(ctx, cf, filterOpt)
|
||||||
|
if err != nil {
|
||||||
|
chat.ui.ErrorMessage(err)
|
||||||
|
} else {
|
||||||
|
chat.C = theFilters[0].C //Picking first subscription since there is only 1 contentTopic specified.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
for _, topic := range topics {
|
||||||
|
sub, err := node.Relay().Subscribe(ctx, protocol.NewContentFilter(topic))
|
||||||
|
if err != nil {
|
||||||
|
chat.ui.ErrorMessage(err)
|
||||||
|
} else {
|
||||||
|
chat.C = make(chan *protocol.Envelope)
|
||||||
|
go func() {
|
||||||
|
for e := range sub[0].Ch {
|
||||||
|
chat.C <- e
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chat.wg.Add(7)
|
||||||
|
go chat.parseInput()
|
||||||
|
go chat.receiveMessages()
|
||||||
|
|
||||||
|
connectionWg := sync.WaitGroup{}
|
||||||
|
connectionWg.Add(2)
|
||||||
|
|
||||||
|
go chat.welcomeMessage()
|
||||||
|
|
||||||
|
go chat.connectionWatcher(&connectionWg, connNotifier)
|
||||||
|
go chat.staticNodes(&connectionWg)
|
||||||
|
go chat.discoverNodes(&connectionWg)
|
||||||
|
go chat.retrieveHistory(&connectionWg)
|
||||||
|
|
||||||
|
return chat
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chat) Stop() {
|
||||||
|
c.wg.Wait()
|
||||||
|
close(c.inputChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chat) connectionWatcher(connectionWg *sync.WaitGroup, connNotifier <-chan node.PeerConnection) {
|
||||||
|
defer c.wg.Done()
|
||||||
|
|
||||||
|
for conn := range connNotifier {
|
||||||
|
if conn.Connected {
|
||||||
|
c.ui.InfoMessage(fmt.Sprintf("Peer %s connected", conn.PeerID.String()))
|
||||||
|
} else {
|
||||||
|
c.ui.InfoMessage(fmt.Sprintf("Peer %s disconnected", conn.PeerID.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chat) receiveMessages() {
|
||||||
|
defer c.wg.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.ctx.Done():
|
||||||
|
return
|
||||||
|
case value := <-c.C:
|
||||||
|
|
||||||
|
msgContentTopic := value.Message().ContentTopic
|
||||||
|
if msgContentTopic != c.options.ContentTopic {
|
||||||
|
continue // Discard messages from other topics
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := decodeMessage(c.options.ContentTopic, value.Message())
|
||||||
|
if err == nil {
|
||||||
|
// send valid messages to the UI
|
||||||
|
c.ui.ChatMessage(int64(msg.Timestamp), msg.Nick, string(msg.Payload))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (c *Chat) parseInput() {
|
||||||
|
defer c.wg.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.ctx.Done():
|
||||||
|
return
|
||||||
|
case line := <-c.inputChan:
|
||||||
|
c.ui.SetSending(true)
|
||||||
|
go func() {
|
||||||
|
defer c.ui.SetSending(false)
|
||||||
|
|
||||||
|
// bail if requested
|
||||||
|
if line == "/exit" {
|
||||||
|
c.ui.Quit()
|
||||||
|
fmt.Println("Bye!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// add peer
|
||||||
|
if strings.HasPrefix(line, "/connect") {
|
||||||
|
peer := strings.TrimPrefix(line, "/connect ")
|
||||||
|
c.wg.Add(1)
|
||||||
|
go func(peer string) {
|
||||||
|
defer c.wg.Done()
|
||||||
|
|
||||||
|
ma, err := multiaddr.NewMultiaddr(peer)
|
||||||
|
if err != nil {
|
||||||
|
c.ui.ErrorMessage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerID, err := ma.ValueForProtocol(multiaddr.P_P2P)
|
||||||
|
if err != nil {
|
||||||
|
c.ui.ErrorMessage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ui.InfoMessage(fmt.Sprintf("Connecting to peer: %s", peerID))
|
||||||
|
ctx, cancel := context.WithTimeout(c.ctx, time.Duration(10)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err = c.node.DialPeerWithMultiAddress(ctx, ma)
|
||||||
|
if err != nil {
|
||||||
|
c.ui.ErrorMessage(err)
|
||||||
|
}
|
||||||
|
}(peer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// list peers
|
||||||
|
if line == "/peers" {
|
||||||
|
peers := c.node.Host().Network().Peers()
|
||||||
|
if len(peers) == 0 {
|
||||||
|
c.ui.InfoMessage("No peers available")
|
||||||
|
} else {
|
||||||
|
peerInfoMsg := "Peers: \n"
|
||||||
|
for _, p := range peers {
|
||||||
|
peerInfo := c.node.Host().Peerstore().PeerInfo(p)
|
||||||
|
peerProtocols, err := c.node.Host().Peerstore().GetProtocols(p)
|
||||||
|
if err != nil {
|
||||||
|
c.ui.ErrorMessage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peerInfoMsg += fmt.Sprintf("• %s:\n", p.String())
|
||||||
|
|
||||||
|
var strProtocols []string
|
||||||
|
for _, p := range peerProtocols {
|
||||||
|
strProtocols = append(strProtocols, string(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
peerInfoMsg += fmt.Sprintf(" Protocols: %s\n", strings.Join(strProtocols, ", "))
|
||||||
|
peerInfoMsg += " Addresses:\n"
|
||||||
|
for _, addr := range peerInfo.Addrs {
|
||||||
|
peerInfoMsg += fmt.Sprintf(" - %s/p2p/%s\n", addr.String(), p.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.ui.InfoMessage(peerInfoMsg)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// change nick
|
||||||
|
if strings.HasPrefix(line, "/nick") {
|
||||||
|
newNick := strings.TrimSpace(strings.TrimPrefix(line, "/nick "))
|
||||||
|
if newNick != "" {
|
||||||
|
c.nick = newNick
|
||||||
|
} else {
|
||||||
|
c.ui.ErrorMessage(errors.New("invalid nickname"))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if line == "/help" {
|
||||||
|
c.ui.InfoMessage(`Available commands:
|
||||||
|
/connect multiaddress - dials a node adding it to the list of connected peers
|
||||||
|
/peers - list of peers connected to this node
|
||||||
|
/nick newNick - change the user's nickname
|
||||||
|
/exit - closes the app`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SendMessage(line)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chat) SendMessage(line string) {
|
||||||
|
tCtx, cancel := context.WithTimeout(c.ctx, 3*time.Second)
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := c.publish(tCtx, line)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "validation failed" {
|
||||||
|
err = errors.New("message rate violation")
|
||||||
|
}
|
||||||
|
c.ui.ErrorMessage(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chat) publish(ctx context.Context, message string) error {
|
||||||
|
msg := &pb.Chat2Message{
|
||||||
|
Timestamp: uint64(c.node.Timesource().Now().Unix()),
|
||||||
|
Nick: c.nick,
|
||||||
|
Payload: []byte(message),
|
||||||
|
}
|
||||||
|
|
||||||
|
msgBytes, err := proto.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
version := uint32(0)
|
||||||
|
timestamp := utils.GetUnixEpochFrom(c.node.Timesource().Now())
|
||||||
|
keyInfo := &payload.KeyInfo{
|
||||||
|
Kind: payload.None,
|
||||||
|
}
|
||||||
|
|
||||||
|
p := new(payload.Payload)
|
||||||
|
p.Data = msgBytes
|
||||||
|
p.Key = keyInfo
|
||||||
|
|
||||||
|
payload, err := p.Encode(version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wakuMsg := &wpb.WakuMessage{
|
||||||
|
Payload: payload,
|
||||||
|
Version: proto.Uint32(version),
|
||||||
|
ContentTopic: options.ContentTopic,
|
||||||
|
Timestamp: timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.RLNRelay.Enable {
|
||||||
|
// for future version when we support more than one rln protected content topic,
|
||||||
|
// we should check the message content topic as well
|
||||||
|
err = c.node.RLNRelay().AppendRLNProof(wakuMsg, c.node.Timesource().Now())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rateLimitProof, err := wrln.BytesToRateLimitProof(wakuMsg.RateLimitProof)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ui.InfoMessage(fmt.Sprintf("RLN Epoch: %d", rateLimitProof.Epoch.Uint64()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.LightPush.Enable {
|
||||||
|
lightOpt := []lightpush.RequestOption{lightpush.WithDefaultPubsubTopic()}
|
||||||
|
var peerID peer.ID
|
||||||
|
peerID, err = options.LightPush.NodePeerID()
|
||||||
|
if err != nil {
|
||||||
|
lightOpt = append(lightOpt, lightpush.WithAutomaticPeerSelection())
|
||||||
|
} else {
|
||||||
|
lightOpt = append(lightOpt, lightpush.WithPeer(peerID))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.node.Lightpush().Publish(c.ctx, wakuMsg, lightOpt...)
|
||||||
|
} else {
|
||||||
|
_, err = c.node.Relay().Publish(ctx, wakuMsg, relay.WithDefaultPubsubTopic())
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeMessage(contentTopic string, wakumsg *wpb.WakuMessage) (*pb.Chat2Message, error) {
|
||||||
|
keyInfo := &payload.KeyInfo{
|
||||||
|
Kind: payload.None,
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := payload.DecodePayload(wakumsg, keyInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &pb.Chat2Message{}
|
||||||
|
if err := proto.Unmarshal(payload.Data, msg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chat) retrieveHistory(connectionWg *sync.WaitGroup) {
|
||||||
|
defer c.wg.Done()
|
||||||
|
|
||||||
|
connectionWg.Wait() // Wait until node connection operations are done
|
||||||
|
|
||||||
|
if !c.options.Store.Enable {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var storeOpt legacy_store.HistoryRequestOption
|
||||||
|
if c.options.Store.Node == nil {
|
||||||
|
c.ui.InfoMessage("No store node configured. Choosing one at random...")
|
||||||
|
storeOpt = legacy_store.WithAutomaticPeerSelection()
|
||||||
|
} else {
|
||||||
|
peerID, err := (*c.options.Store.Node).ValueForProtocol(multiaddr.P_P2P)
|
||||||
|
if err != nil {
|
||||||
|
c.ui.ErrorMessage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pID, err := peer.Decode(peerID)
|
||||||
|
if err != nil {
|
||||||
|
c.ui.ErrorMessage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
storeOpt = legacy_store.WithPeer(pID)
|
||||||
|
c.ui.InfoMessage(fmt.Sprintf("Querying historic messages from %s", peerID))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tCtx, cancel := context.WithTimeout(c.ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
q := legacy_store.Query{
|
||||||
|
ContentTopics: []string{options.ContentTopic},
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.node.LegacyStore().Query(tCtx, q,
|
||||||
|
legacy_store.WithAutomaticRequestID(),
|
||||||
|
storeOpt,
|
||||||
|
legacy_store.WithPaging(false, 100))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.ui.ErrorMessage(fmt.Errorf("could not query storenode: %w", err))
|
||||||
|
} else {
|
||||||
|
if len(response.Messages) == 0 {
|
||||||
|
c.ui.InfoMessage("0 historic messages available")
|
||||||
|
} else {
|
||||||
|
for _, msg := range response.Messages {
|
||||||
|
c.C <- protocol.NewEnvelope(msg, msg.GetTimestamp(), relay.DefaultWakuTopic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chat) staticNodes(connectionWg *sync.WaitGroup) {
|
||||||
|
defer c.wg.Done()
|
||||||
|
defer connectionWg.Done()
|
||||||
|
|
||||||
|
<-c.uiReady // wait until UI is ready
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
|
||||||
|
wg.Add(len(options.StaticNodes))
|
||||||
|
for _, n := range options.StaticNodes {
|
||||||
|
go func(addr multiaddr.Multiaddr) {
|
||||||
|
defer wg.Done()
|
||||||
|
ctx, cancel := context.WithTimeout(c.ctx, time.Duration(10)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c.ui.InfoMessage(fmt.Sprintf("Connecting to %s", addr.String()))
|
||||||
|
|
||||||
|
err := c.node.DialPeerWithMultiAddress(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
c.ui.ErrorMessage(err)
|
||||||
|
}
|
||||||
|
}(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chat) welcomeMessage() {
|
||||||
|
defer c.wg.Done()
|
||||||
|
|
||||||
|
<-c.uiReady // wait until UI is ready
|
||||||
|
|
||||||
|
c.ui.InfoMessage("Welcome, " + c.nick + "!")
|
||||||
|
c.ui.InfoMessage("type /help to see available commands \n")
|
||||||
|
|
||||||
|
addrMessage := "Listening on:\n"
|
||||||
|
for _, addr := range c.node.ListenAddresses() {
|
||||||
|
addrMessage += " -" + addr.String() + "\n"
|
||||||
|
}
|
||||||
|
c.ui.InfoMessage(addrMessage)
|
||||||
|
|
||||||
|
if !c.options.RLNRelay.Enable {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
credential, err := c.node.RLNRelay().IdentityCredential()
|
||||||
|
if err != nil {
|
||||||
|
c.ui.Quit()
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := c.node.RLNRelay().MembershipIndex()
|
||||||
|
|
||||||
|
idTrapdoor := credential.IDTrapdoor
|
||||||
|
idNullifier := credential.IDSecretHash
|
||||||
|
idSecretHash := credential.IDSecretHash
|
||||||
|
idCommitment := credential.IDCommitment
|
||||||
|
|
||||||
|
rlnMessage := "RLN config:\n"
|
||||||
|
rlnMessage += fmt.Sprintf("- Your membership index is: %d\n", idx)
|
||||||
|
rlnMessage += fmt.Sprintf("- Your rln identity trapdoor is: 0x%s\n", hex.EncodeToString(idTrapdoor[:]))
|
||||||
|
rlnMessage += fmt.Sprintf("- Your rln identity nullifier is: 0x%s\n", hex.EncodeToString(idNullifier[:]))
|
||||||
|
rlnMessage += fmt.Sprintf("- Your rln identity secret hash is: 0x%s\n", hex.EncodeToString(idSecretHash[:]))
|
||||||
|
rlnMessage += fmt.Sprintf("- Your rln identity commitment key is: 0x%s\n", hex.EncodeToString(idCommitment[:]))
|
||||||
|
|
||||||
|
c.ui.InfoMessage(rlnMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chat) discoverNodes(connectionWg *sync.WaitGroup) {
|
||||||
|
defer c.wg.Done()
|
||||||
|
defer connectionWg.Done()
|
||||||
|
|
||||||
|
<-c.uiReady // wait until UI is ready
|
||||||
|
|
||||||
|
var dnsDiscoveryUrl string
|
||||||
|
if options.Fleet != fleetNone {
|
||||||
|
if options.Fleet == fleetTest {
|
||||||
|
dnsDiscoveryUrl = "enrtree://AOGYWMBYOUIMOENHXCHILPKY3ZRFEULMFI4DOM442QSZ73TT2A7VI@test.waku.nodes.status.im"
|
||||||
|
} else {
|
||||||
|
// Connect to prod by default
|
||||||
|
dnsDiscoveryUrl = "enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.DNSDiscovery.Enable && options.DNSDiscovery.URL != "" {
|
||||||
|
dnsDiscoveryUrl = options.DNSDiscovery.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
if dnsDiscoveryUrl != "" {
|
||||||
|
c.ui.InfoMessage(fmt.Sprintf("attempting DNS discovery with %s", dnsDiscoveryUrl))
|
||||||
|
nodes, err := dnsdisc.RetrieveNodes(c.ctx, dnsDiscoveryUrl, dnsdisc.WithNameserver(options.DNSDiscovery.Nameserver))
|
||||||
|
if err != nil {
|
||||||
|
c.ui.ErrorMessage(errors.New(err.Error()))
|
||||||
|
} else {
|
||||||
|
var nodeList []peer.AddrInfo
|
||||||
|
for _, n := range nodes {
|
||||||
|
nodeList = append(nodeList, n.PeerInfo)
|
||||||
|
}
|
||||||
|
c.ui.InfoMessage(fmt.Sprintf("Discovered and connecting to %v ", nodeList))
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(len(nodeList))
|
||||||
|
for _, n := range nodeList {
|
||||||
|
go func(ctx context.Context, info peer.AddrInfo) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, time.Duration(10)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
err = c.node.DialPeerWithInfo(ctx, info)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
c.ui.ErrorMessage(fmt.Errorf("co!!uld not connect to %s: %w", info.ID.String(), err))
|
||||||
|
}
|
||||||
|
}(c.ctx, n)
|
||||||
|
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/libp2p/go-libp2p/core/protocol"
|
||||||
|
"github.com/multiformats/go-multiaddr"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/node"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/peerstore"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/protocol/filter"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_store"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/protocol/lightpush"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func execute(options Options) {
|
||||||
|
var err error
|
||||||
|
hostAddr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("0.0.0.0:%d", options.Port))
|
||||||
|
|
||||||
|
if options.NodeKey == nil {
|
||||||
|
options.NodeKey, err = crypto.GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Could not generate random key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connNotifier := make(chan node.PeerConnection)
|
||||||
|
|
||||||
|
opts := []node.WakuNodeOption{
|
||||||
|
node.WithPrivateKey(options.NodeKey),
|
||||||
|
node.WithNTP(),
|
||||||
|
node.WithHostAddress(hostAddr),
|
||||||
|
node.WithConnectionNotification(connNotifier),
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Relay.Enable {
|
||||||
|
opts = append(opts, node.WithWakuRelay())
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.RLNRelay.Enable {
|
||||||
|
spamHandler := func(message *pb.WakuMessage, topic string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.RLNRelay.Dynamic {
|
||||||
|
fmt.Println("Setting up dynamic rln...")
|
||||||
|
opts = append(opts, node.WithDynamicRLNRelay(
|
||||||
|
options.RLNRelay.CredentialsPath,
|
||||||
|
options.RLNRelay.CredentialsPassword,
|
||||||
|
"", // Will use default tree path
|
||||||
|
options.RLNRelay.MembershipContractAddress,
|
||||||
|
options.RLNRelay.MembershipIndex,
|
||||||
|
spamHandler,
|
||||||
|
options.RLNRelay.ETHClientAddress,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
opts = append(opts, node.WithStaticRLNRelay(
|
||||||
|
options.RLNRelay.MembershipIndex,
|
||||||
|
spamHandler))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Filter.Enable {
|
||||||
|
opts = append(opts, node.WithWakuFilterLightNode())
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
wakuNode, err := node.New(opts...)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = addPeer(wakuNode, options.Store.Node, options.Relay.Topics.Value(), legacy_store.StoreID_v20beta4)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = addPeer(wakuNode, options.LightPush.Node, options.Relay.Topics.Value(), lightpush.LightPushID_v20beta1)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = addPeer(wakuNode, options.Filter.Node, options.Relay.Topics.Value(), filter.FilterSubscribeID_v20beta1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wakuNode.Start(ctx); err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chat := NewChat(ctx, wakuNode, connNotifier, options)
|
||||||
|
p := tea.NewProgram(chat.ui)
|
||||||
|
if err := p.Start(); err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
wakuNode.Stop()
|
||||||
|
chat.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPeer(wakuNode *node.WakuNode, addr *multiaddr.Multiaddr, topics []string, protocols ...protocol.ID) error {
|
||||||
|
if addr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := wakuNode.AddPeer(*addr, peerstore.Static, topics, protocols...)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,230 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/waku-org/go-waku/waku/cliutils"
|
||||||
|
wcli "github.com/waku-org/go-waku/waku/cliutils"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/protocol"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FleetValue struct {
|
||||||
|
Value *Fleet
|
||||||
|
Default Fleet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *FleetValue) Set(value string) error {
|
||||||
|
if value == string(fleetProd) || value == string(fleetTest) || value == string(fleetNone) {
|
||||||
|
*v.Value = Fleet(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s is not a valid option. need %+v", value, []Fleet{fleetProd, fleetTest, fleetNone})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *FleetValue) String() string {
|
||||||
|
if v.Value == nil {
|
||||||
|
return string(v.Default)
|
||||||
|
}
|
||||||
|
return string(*v.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFlags() []cli.Flag {
|
||||||
|
// Defaults
|
||||||
|
options.Fleet = fleetProd
|
||||||
|
|
||||||
|
testCT, err := protocol.NewContentTopic("toy-chat", "3", "mingde", "proto")
|
||||||
|
if err != nil {
|
||||||
|
panic("invalid contentTopic")
|
||||||
|
}
|
||||||
|
testnetContentTopic := testCT.String()
|
||||||
|
|
||||||
|
return []cli.Flag{
|
||||||
|
&cli.GenericFlag{
|
||||||
|
Name: "nodekey",
|
||||||
|
Usage: "P2P node private key as hex. (default random)",
|
||||||
|
Value: &wcli.PrivateKeyValue{
|
||||||
|
Value: &options.NodeKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "listen-address",
|
||||||
|
Aliases: []string{"host", "address"},
|
||||||
|
Value: "0.0.0.0",
|
||||||
|
Usage: "Listening address",
|
||||||
|
Destination: &options.Address,
|
||||||
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "tcp-port",
|
||||||
|
Aliases: []string{"port", "p"},
|
||||||
|
Value: 0,
|
||||||
|
Usage: "Libp2p TCP listening port (0 for random)",
|
||||||
|
Destination: &options.Port,
|
||||||
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "udp-port",
|
||||||
|
Value: 60000,
|
||||||
|
Usage: "Listening UDP port for Node Discovery v5.",
|
||||||
|
Destination: &options.DiscV5.Port,
|
||||||
|
},
|
||||||
|
&cli.GenericFlag{
|
||||||
|
Name: "log-level",
|
||||||
|
Aliases: []string{"l"},
|
||||||
|
Value: &cliutils.ChoiceValue{
|
||||||
|
Choices: []string{"DEBUG", "INFO", "WARN", "ERROR", "DPANIC", "PANIC", "FATAL"},
|
||||||
|
Value: &options.LogLevel,
|
||||||
|
},
|
||||||
|
Usage: "Define the logging level,",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "content-topic",
|
||||||
|
Usage: "content topic to use for the chat",
|
||||||
|
Value: testnetContentTopic,
|
||||||
|
Destination: &options.ContentTopic,
|
||||||
|
},
|
||||||
|
&cli.GenericFlag{
|
||||||
|
Name: "fleet",
|
||||||
|
Usage: "Select the fleet to connect to",
|
||||||
|
Value: &FleetValue{
|
||||||
|
Default: fleetProd,
|
||||||
|
Value: &options.Fleet,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&cli.GenericFlag{
|
||||||
|
Name: "staticnode",
|
||||||
|
Usage: "Multiaddr of peer to directly connect with. Option may be repeated",
|
||||||
|
Value: &wcli.MultiaddrSlice{
|
||||||
|
Values: &options.StaticNodes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "nickname",
|
||||||
|
Usage: "nickname to use in chat.",
|
||||||
|
Destination: &options.Nickname,
|
||||||
|
Value: "Anonymous",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "relay",
|
||||||
|
Value: true,
|
||||||
|
Usage: "Enable relay protocol",
|
||||||
|
Destination: &options.Relay.Enable,
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "topic",
|
||||||
|
Usage: "Pubsub topics to subscribe to. Option can be repeated",
|
||||||
|
Destination: &options.Relay.Topics,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "store",
|
||||||
|
Usage: "Enable store protocol",
|
||||||
|
Value: true,
|
||||||
|
Destination: &options.Store.Enable,
|
||||||
|
},
|
||||||
|
&cli.GenericFlag{
|
||||||
|
Name: "storenode",
|
||||||
|
Usage: "Multiaddr of a peer that supports store protocol.",
|
||||||
|
Value: &wcli.MultiaddrValue{
|
||||||
|
Value: &options.Store.Node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "filter",
|
||||||
|
Usage: "Enable filter protocol",
|
||||||
|
Destination: &options.Filter.Enable,
|
||||||
|
},
|
||||||
|
&cli.GenericFlag{
|
||||||
|
Name: "filternode",
|
||||||
|
Usage: "Multiaddr of a peer that supports filter protocol.",
|
||||||
|
Value: &wcli.MultiaddrValue{
|
||||||
|
Value: &options.Filter.Node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "lightpush",
|
||||||
|
Usage: "Enable lightpush protocol",
|
||||||
|
Destination: &options.LightPush.Enable,
|
||||||
|
},
|
||||||
|
&cli.GenericFlag{
|
||||||
|
Name: "lightpushnode",
|
||||||
|
Usage: "Multiaddr of a peer that supports lightpush protocol.",
|
||||||
|
Value: &wcli.MultiaddrValue{
|
||||||
|
Value: &options.LightPush.Node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "discv5-discovery",
|
||||||
|
Usage: "Enable discovering nodes via Node Discovery v5",
|
||||||
|
Destination: &options.DiscV5.Enable,
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "discv5-bootstrap-node",
|
||||||
|
Usage: "Text-encoded ENR for bootstrap node. Used when connecting to the network. Option may be repeated",
|
||||||
|
Destination: &options.DiscV5.Nodes,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "discv5-enr-auto-update",
|
||||||
|
Usage: "Discovery can automatically update its ENR with the IP address as seen by other nodes it communicates with.",
|
||||||
|
Destination: &options.DiscV5.AutoUpdate,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "dns-discovery",
|
||||||
|
Usage: "Enable DNS discovery",
|
||||||
|
Destination: &options.DNSDiscovery.Enable,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "dns-discovery-url",
|
||||||
|
Usage: "URL for DNS node list in format 'enrtree://<key>@<fqdn>'",
|
||||||
|
Destination: &options.DNSDiscovery.URL,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "dns-discovery-name-server",
|
||||||
|
Aliases: []string{"dns-discovery-nameserver"},
|
||||||
|
Usage: "DNS nameserver IP to query (empty to use system's default)",
|
||||||
|
Destination: &options.DNSDiscovery.Nameserver,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "rln-relay",
|
||||||
|
Value: false,
|
||||||
|
Usage: "Enable spam protection through rln-relay",
|
||||||
|
Destination: &options.RLNRelay.Enable,
|
||||||
|
},
|
||||||
|
&cli.GenericFlag{
|
||||||
|
Name: "rln-relay-cred-index",
|
||||||
|
Usage: "the index of the onchain commitment to use",
|
||||||
|
Value: &wcli.OptionalUint{
|
||||||
|
Value: &options.RLNRelay.MembershipIndex,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "rln-relay-dynamic",
|
||||||
|
Usage: "Enable waku-rln-relay with on-chain dynamic group management",
|
||||||
|
Destination: &options.RLNRelay.Dynamic,
|
||||||
|
},
|
||||||
|
&cli.PathFlag{
|
||||||
|
Name: "rln-relay-cred-path",
|
||||||
|
Usage: "The path for persisting rln-relay credential",
|
||||||
|
Value: "",
|
||||||
|
Destination: &options.RLNRelay.CredentialsPath,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "rln-relay-cred-password",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Password for encrypting RLN credentials",
|
||||||
|
Destination: &options.RLNRelay.CredentialsPassword,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "rln-relay-eth-client-address",
|
||||||
|
Usage: "Ethereum testnet client address",
|
||||||
|
Value: "ws://localhost:8545",
|
||||||
|
Destination: &options.RLNRelay.ETHClientAddress,
|
||||||
|
},
|
||||||
|
&cli.GenericFlag{
|
||||||
|
Name: "rln-relay-eth-contract-address",
|
||||||
|
Usage: "Address of membership contract on an Ethereum testnet",
|
||||||
|
Value: &wcli.AddressValue{
|
||||||
|
Value: &options.RLNRelay.MembershipContractAddress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
module chat2-reliable
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.21.10
|
||||||
|
|
||||||
|
replace github.com/waku-org/go-waku => ../..
|
||||||
|
|
||||||
|
replace github.com/ethereum/go-ethereum v1.10.26 => github.com/status-im/go-ethereum v1.10.25-status.15
|
||||||
|
|
||||||
|
replace github.com/libp2p/go-libp2p-pubsub v0.11.0 => github.com/waku-org/go-libp2p-pubsub v0.0.0-20240703191659-2cbb09eac9b5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/charmbracelet/bubbles v0.13.0
|
||||||
|
github.com/charmbracelet/bubbletea v0.22.0
|
||||||
|
github.com/charmbracelet/lipgloss v0.5.0
|
||||||
|
github.com/ethereum/go-ethereum v1.10.26
|
||||||
|
github.com/ipfs/go-log/v2 v2.5.1
|
||||||
|
github.com/libp2p/go-libp2p v0.35.0
|
||||||
|
github.com/muesli/reflow v0.3.0
|
||||||
|
github.com/multiformats/go-multiaddr v0.12.4
|
||||||
|
github.com/urfave/cli/v2 v2.27.2
|
||||||
|
github.com/waku-org/go-waku v0.2.3-0.20221109195301-b2a5a68d28ba
|
||||||
|
golang.org/x/term v0.20.0
|
||||||
|
google.golang.org/protobuf v1.34.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||||
|
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
|
||||||
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
|
github.com/avast/retry-go/v4 v4.5.1 // indirect
|
||||||
|
github.com/beevik/ntp v0.3.0 // indirect
|
||||||
|
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/btcsuite/btcd v0.20.1-beta // indirect
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.2.1 // indirect
|
||||||
|
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/containerd/cgroups v1.1.0 // indirect
|
||||||
|
github.com/containerd/console v1.0.3 // indirect
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||||
|
github.com/cruxic/go-hmac-drbg v0.0.0-20170206035330-84c46983886d // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
|
||||||
|
github.com/deckarep/golang-set v1.8.0 // indirect
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
|
github.com/elastic/gosigar v0.14.2 // indirect
|
||||||
|
github.com/flynn/noise v1.1.0 // indirect
|
||||||
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.2.1 // indirect
|
||||||
|
github.com/go-stack/stack v1.8.1 // indirect
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
|
||||||
|
github.com/google/gopacket v1.1.19 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect
|
||||||
|
github.com/google/uuid v1.4.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.1 // indirect
|
||||||
|
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
|
github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect
|
||||||
|
github.com/huin/goupnp v1.3.0 // indirect
|
||||||
|
github.com/ipfs/go-cid v0.4.1 // indirect
|
||||||
|
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
|
||||||
|
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.8 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/koron/go-ssdp v0.0.4 // indirect
|
||||||
|
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
||||||
|
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
|
||||||
|
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
|
||||||
|
github.com/libp2p/go-libp2p-pubsub v0.11.0 // indirect
|
||||||
|
github.com/libp2p/go-msgio v0.3.0 // indirect
|
||||||
|
github.com/libp2p/go-nat v0.2.0 // indirect
|
||||||
|
github.com/libp2p/go-netroute v0.2.1 // indirect
|
||||||
|
github.com/libp2p/go-reuseport v0.4.0 // indirect
|
||||||
|
github.com/libp2p/go-yamux/v4 v4.0.1 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
|
github.com/miekg/dns v1.1.58 // indirect
|
||||||
|
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
|
||||||
|
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
|
||||||
|
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||||
|
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||||
|
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||||
|
github.com/muesli/cancelreader v0.2.1 // indirect
|
||||||
|
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 // indirect
|
||||||
|
github.com/multiformats/go-base32 v0.1.0 // indirect
|
||||||
|
github.com/multiformats/go-base36 v0.2.0 // indirect
|
||||||
|
github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
|
||||||
|
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
|
||||||
|
github.com/multiformats/go-multibase v0.2.0 // indirect
|
||||||
|
github.com/multiformats/go-multicodec v0.9.0 // indirect
|
||||||
|
github.com/multiformats/go-multihash v0.2.3 // indirect
|
||||||
|
github.com/multiformats/go-multistream v0.5.0 // indirect
|
||||||
|
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||||
|
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
|
||||||
|
github.com/opencontainers/runtime-spec v1.2.0 // indirect
|
||||||
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||||
|
github.com/pion/datachannel v1.5.6 // indirect
|
||||||
|
github.com/pion/dtls/v2 v2.2.11 // indirect
|
||||||
|
github.com/pion/ice/v2 v2.3.24 // indirect
|
||||||
|
github.com/pion/interceptor v0.1.29 // indirect
|
||||||
|
github.com/pion/logging v0.2.2 // indirect
|
||||||
|
github.com/pion/mdns v0.0.12 // indirect
|
||||||
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
|
github.com/pion/rtcp v1.2.14 // indirect
|
||||||
|
github.com/pion/rtp v1.8.6 // indirect
|
||||||
|
github.com/pion/sctp v1.8.16 // indirect
|
||||||
|
github.com/pion/sdp/v3 v3.0.9 // indirect
|
||||||
|
github.com/pion/srtp/v2 v2.0.18 // indirect
|
||||||
|
github.com/pion/stun v0.6.1 // indirect
|
||||||
|
github.com/pion/transport/v2 v2.2.5 // indirect
|
||||||
|
github.com/pion/turn/v2 v2.1.6 // indirect
|
||||||
|
github.com/pion/webrtc/v3 v3.2.40 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.44.0 // indirect
|
||||||
|
github.com/quic-go/webtransport-go v0.8.0 // indirect
|
||||||
|
github.com/raulk/go-watchdog v1.3.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/rjeczalik/notify v0.9.3 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
|
github.com/status-im/status-go/extkeys v1.1.2 // indirect
|
||||||
|
github.com/stretchr/testify v1.9.0 // indirect
|
||||||
|
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.5 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.2.2 // indirect
|
||||||
|
github.com/waku-org/go-discover v0.0.0-20240506173252-4912704efdc5 // indirect
|
||||||
|
github.com/waku-org/go-libp2p-rendezvous v0.0.0-20240110193335-a67d1cc760a0 // indirect
|
||||||
|
github.com/waku-org/go-zerokit-rln v0.1.14-0.20240102145250-fa738c0bdf59 // indirect
|
||||||
|
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b // indirect
|
||||||
|
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065 // indirect
|
||||||
|
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1 // indirect
|
||||||
|
github.com/wk8/go-ordered-map v1.0.0 // indirect
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
||||||
|
go.uber.org/dig v1.17.1 // indirect
|
||||||
|
go.uber.org/fx v1.21.1 // indirect
|
||||||
|
go.uber.org/mock v0.4.0 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||||
|
golang.org/x/mod v0.17.0 // indirect
|
||||||
|
golang.org/x/net v0.25.0 // indirect
|
||||||
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
golang.org/x/text v0.15.0 // indirect
|
||||||
|
golang.org/x/time v0.5.0 // indirect
|
||||||
|
golang.org/x/tools v0.21.0 // indirect
|
||||||
|
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
lukechampine.com/blake3 v1.2.1 // indirect
|
||||||
|
)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,34 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
logging "github.com/ipfs/go-log/v2"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var options Options
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := &cli.App{
|
||||||
|
Flags: getFlags(),
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
utils.InitLogger("console", "file:chat2.log", "chat2")
|
||||||
|
|
||||||
|
lvl, err := logging.LevelFromString(options.LogLevel)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logging.SetAllLoggers(lvl)
|
||||||
|
|
||||||
|
execute(options)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run(os.Args)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
"github.com/multiformats/go-multiaddr"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiscV5Options are settings to enable a modified version of Ethereum’s Node
|
||||||
|
// Discovery Protocol v5 as a means for ambient node discovery.
|
||||||
|
type DiscV5Options struct {
|
||||||
|
Enable bool
|
||||||
|
Nodes cli.StringSlice
|
||||||
|
Port int
|
||||||
|
AutoUpdate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// RelayOptions are settings to enable the relay protocol which is a pubsub
|
||||||
|
// approach to peer-to-peer messaging with a strong focus on privacy,
|
||||||
|
// censorship-resistance, security and scalability.
|
||||||
|
type RelayOptions struct {
|
||||||
|
Enable bool
|
||||||
|
Topics cli.StringSlice
|
||||||
|
}
|
||||||
|
|
||||||
|
type RLNRelayOptions struct {
|
||||||
|
Enable bool
|
||||||
|
CredentialsPath string
|
||||||
|
CredentialsPassword string
|
||||||
|
MembershipIndex *uint
|
||||||
|
Dynamic bool
|
||||||
|
ETHClientAddress string
|
||||||
|
MembershipContractAddress common.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodePeerID(node *multiaddr.Multiaddr) (peer.ID, error) {
|
||||||
|
if node == nil {
|
||||||
|
return peer.ID(""), errors.New("node is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
peerID, err := (*node).ValueForProtocol(multiaddr.P_P2P)
|
||||||
|
if err != nil {
|
||||||
|
return peer.ID(""), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer.Decode(peerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterOptions are settings used to enable filter protocol. This is a protocol
|
||||||
|
// that enables subscribing to messages that a peer receives. This is a more
|
||||||
|
// lightweight version of WakuRelay specifically designed for bandwidth
|
||||||
|
// restricted devices.
|
||||||
|
type FilterOptions struct {
|
||||||
|
Enable bool
|
||||||
|
Node *multiaddr.Multiaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FilterOptions) NodePeerID() (peer.ID, error) {
|
||||||
|
return nodePeerID(f.Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LightpushOptions are settings used to enable the lightpush protocol. This is
|
||||||
|
// a lightweight protocol used to avoid having to run the relay protocol which
|
||||||
|
// is more resource intensive. With this protocol a message is pushed to a peer
|
||||||
|
// that supports both the lightpush protocol and relay protocol. That peer will
|
||||||
|
// broadcast the message and return a confirmation that the message was
|
||||||
|
// broadcasted
|
||||||
|
type LightpushOptions struct {
|
||||||
|
Enable bool
|
||||||
|
Node *multiaddr.Multiaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f LightpushOptions) NodePeerID() (peer.ID, error) {
|
||||||
|
return nodePeerID(f.Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreOptions are settings used for enabling the store protocol, used to
|
||||||
|
// retrieve message history from other nodes
|
||||||
|
type StoreOptions struct {
|
||||||
|
Enable bool
|
||||||
|
Node *multiaddr.Multiaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f StoreOptions) NodePeerID() (peer.ID, error) {
|
||||||
|
return nodePeerID(f.Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSDiscoveryOptions are settings used for enabling DNS-based discovery
|
||||||
|
// protocol that stores merkle trees in DNS records which contain connection
|
||||||
|
// information for nodes. It's very useful for bootstrapping a p2p network.
|
||||||
|
type DNSDiscoveryOptions struct {
|
||||||
|
Enable bool
|
||||||
|
URL string
|
||||||
|
Nameserver string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fleet string
|
||||||
|
|
||||||
|
const fleetNone Fleet = "none"
|
||||||
|
const fleetProd Fleet = "prod"
|
||||||
|
const fleetTest Fleet = "test"
|
||||||
|
|
||||||
|
// Options contains all the available features and settings that can be
|
||||||
|
// configured via flags when executing chat2
|
||||||
|
type Options struct {
|
||||||
|
Port int
|
||||||
|
Fleet Fleet
|
||||||
|
Address string
|
||||||
|
NodeKey *ecdsa.PrivateKey
|
||||||
|
ContentTopic string
|
||||||
|
Nickname string
|
||||||
|
LogLevel string
|
||||||
|
StaticNodes []multiaddr.Multiaddr
|
||||||
|
|
||||||
|
Relay RelayOptions
|
||||||
|
Store StoreOptions
|
||||||
|
Filter FilterOptions
|
||||||
|
LightPush LightpushOptions
|
||||||
|
RLNRelay RLNRelayOptions
|
||||||
|
DiscV5 DiscV5Options
|
||||||
|
DNSDiscovery DNSDiscoveryOptions
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.26.0
|
||||||
|
// protoc v3.21.12
|
||||||
|
// source: chat2.proto
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Chat2Message struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Timestamp uint64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||||
|
Nick string `protobuf:"bytes,2,opt,name=nick,proto3" json:"nick,omitempty"`
|
||||||
|
Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Chat2Message) Reset() {
|
||||||
|
*x = Chat2Message{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_chat2_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Chat2Message) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Chat2Message) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Chat2Message) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_chat2_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Chat2Message.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Chat2Message) Descriptor() ([]byte, []int) {
|
||||||
|
return file_chat2_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Chat2Message) GetTimestamp() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Timestamp
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Chat2Message) GetNick() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Nick
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Chat2Message) GetPayload() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Payload
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_chat2_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_chat2_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x0b, 0x63, 0x68, 0x61, 0x74, 0x32, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x70,
|
||||||
|
0x62, 0x22, 0x5a, 0x0a, 0x0c, 0x43, 0x68, 0x61, 0x74, 0x32, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||||
|
0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01,
|
||||||
|
0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12,
|
||||||
|
0x12, 0x0a, 0x04, 0x6e, 0x69, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
|
||||||
|
0x69, 0x63, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03,
|
||||||
|
0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x62, 0x06, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_chat2_proto_rawDescOnce sync.Once
|
||||||
|
file_chat2_proto_rawDescData = file_chat2_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_chat2_proto_rawDescGZIP() []byte {
|
||||||
|
file_chat2_proto_rawDescOnce.Do(func() {
|
||||||
|
file_chat2_proto_rawDescData = protoimpl.X.CompressGZIP(file_chat2_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_chat2_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_chat2_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
|
var file_chat2_proto_goTypes = []interface{}{
|
||||||
|
(*Chat2Message)(nil), // 0: pb.Chat2Message
|
||||||
|
}
|
||||||
|
var file_chat2_proto_depIdxs = []int32{
|
||||||
|
0, // [0:0] is the sub-list for method output_type
|
||||||
|
0, // [0:0] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_chat2_proto_init() }
|
||||||
|
func file_chat2_proto_init() {
|
||||||
|
if File_chat2_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_chat2_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Chat2Message); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_chat2_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 1,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_chat2_proto_goTypes,
|
||||||
|
DependencyIndexes: file_chat2_proto_depIdxs,
|
||||||
|
MessageInfos: file_chat2_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_chat2_proto = out.File
|
||||||
|
file_chat2_proto_rawDesc = nil
|
||||||
|
file_chat2_proto_goTypes = nil
|
||||||
|
file_chat2_proto_depIdxs = nil
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package pb;
|
||||||
|
|
||||||
|
message Chat2Message {
|
||||||
|
uint64 timestamp = 1;
|
||||||
|
string nick = 2;
|
||||||
|
bytes payload = 3;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package pb
|
||||||
|
|
||||||
|
//go:generate protoc -I. --go_opt=paths=source_relative --go_opt=Mchat2.proto=./pb --go_out=. ./chat2.proto
|
|
@ -0,0 +1,15 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetTerminalDimensions() (int, int) {
|
||||||
|
physicalWidth, physicalHeight, err := term.GetSize(int(os.Stdout.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
panic("Could not determine terminal size")
|
||||||
|
}
|
||||||
|
return physicalWidth, physicalHeight
|
||||||
|
}
|
|
@ -0,0 +1,344 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/key"
|
||||||
|
"github.com/charmbracelet/bubbles/spinner"
|
||||||
|
"github.com/charmbracelet/bubbles/textarea"
|
||||||
|
"github.com/charmbracelet/bubbles/viewport"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/muesli/reflow/wordwrap"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const viewportMargin = 6
|
||||||
|
|
||||||
|
var (
|
||||||
|
appStyle = lipgloss.NewStyle().Padding(1, 2)
|
||||||
|
|
||||||
|
titleStyle = func() lipgloss.Style {
|
||||||
|
b := lipgloss.RoundedBorder()
|
||||||
|
b.Right = "├"
|
||||||
|
return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1)
|
||||||
|
}().Render
|
||||||
|
|
||||||
|
errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("9")).Render
|
||||||
|
infoStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("4")).Render
|
||||||
|
senderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("5")).Render
|
||||||
|
)
|
||||||
|
|
||||||
|
type errMsg error
|
||||||
|
|
||||||
|
type sending bool
|
||||||
|
|
||||||
|
type quit bool
|
||||||
|
|
||||||
|
type MessageType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChatMessageType MessageType = iota
|
||||||
|
InfoMessageType
|
||||||
|
ErrorMessageType
|
||||||
|
)
|
||||||
|
|
||||||
|
type message struct {
|
||||||
|
mType MessageType
|
||||||
|
err error
|
||||||
|
author string
|
||||||
|
clock time.Time
|
||||||
|
content string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UI struct {
|
||||||
|
ready bool
|
||||||
|
err error
|
||||||
|
|
||||||
|
quitChan chan struct{}
|
||||||
|
readyChan chan<- struct{}
|
||||||
|
inputChan chan<- string
|
||||||
|
|
||||||
|
messageChan chan message
|
||||||
|
messages []message
|
||||||
|
|
||||||
|
isSendingChan chan sending
|
||||||
|
isSending bool
|
||||||
|
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
|
||||||
|
viewport viewport.Model
|
||||||
|
textarea textarea.Model
|
||||||
|
|
||||||
|
spinner spinner.Model
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUIModel(readyChan chan<- struct{}, inputChan chan<- string) UI {
|
||||||
|
width, height := GetTerminalDimensions()
|
||||||
|
|
||||||
|
ta := textarea.New()
|
||||||
|
ta.Placeholder = "Send a message..."
|
||||||
|
ta.Focus()
|
||||||
|
|
||||||
|
ta.Prompt = "┃ "
|
||||||
|
ta.CharLimit = 2000
|
||||||
|
|
||||||
|
// Remove cursor line styling
|
||||||
|
ta.FocusedStyle.CursorLine = lipgloss.NewStyle()
|
||||||
|
ta.SetHeight(3)
|
||||||
|
ta.SetWidth(width)
|
||||||
|
ta.ShowLineNumbers = false
|
||||||
|
|
||||||
|
ta.KeyMap.InsertNewline.SetEnabled(false)
|
||||||
|
|
||||||
|
s := spinner.New()
|
||||||
|
s.Spinner = spinner.Jump
|
||||||
|
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
|
||||||
|
|
||||||
|
m := UI{
|
||||||
|
messageChan: make(chan message, 100),
|
||||||
|
isSendingChan: make(chan sending, 100),
|
||||||
|
quitChan: make(chan struct{}),
|
||||||
|
readyChan: readyChan,
|
||||||
|
inputChan: inputChan,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
textarea: ta,
|
||||||
|
spinner: s,
|
||||||
|
err: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UI) Init() tea.Cmd {
|
||||||
|
return tea.Batch(
|
||||||
|
recvQuitSignal(m.quitChan),
|
||||||
|
recvMessages(m.messageChan),
|
||||||
|
recvSendingState(m.isSendingChan),
|
||||||
|
textarea.Blink,
|
||||||
|
spinner.Tick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
var (
|
||||||
|
tiCmd tea.Cmd
|
||||||
|
vpCmd tea.Cmd
|
||||||
|
)
|
||||||
|
|
||||||
|
m.textarea, tiCmd = m.textarea.Update(msg)
|
||||||
|
m.viewport, vpCmd = m.viewport.Update(msg)
|
||||||
|
|
||||||
|
var cmdToReturn []tea.Cmd = []tea.Cmd{tiCmd, vpCmd}
|
||||||
|
|
||||||
|
headerHeight := lipgloss.Height(m.headerView())
|
||||||
|
|
||||||
|
printMessages := false
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
|
||||||
|
case tea.WindowSizeMsg:
|
||||||
|
m.width, m.height = msg.Width, msg.Height
|
||||||
|
|
||||||
|
if !m.ready {
|
||||||
|
// Since this program is using the full size of the viewport we
|
||||||
|
// need to wait until we've received the window dimensions before
|
||||||
|
// we can initialize the viewport. The initial dimensions come in
|
||||||
|
// quickly, though asynchronously, which is why we wait for them
|
||||||
|
// here.
|
||||||
|
m.viewport = viewport.New(msg.Width, msg.Height-headerHeight-viewportMargin)
|
||||||
|
m.viewport.SetContent("")
|
||||||
|
m.viewport.YPosition = headerHeight + 1
|
||||||
|
m.viewport.KeyMap = DefaultKeyMap()
|
||||||
|
m.ready = true
|
||||||
|
|
||||||
|
close(m.readyChan)
|
||||||
|
} else {
|
||||||
|
m.viewport.Width = msg.Width
|
||||||
|
m.viewport.Height = msg.Height - headerHeight - viewportMargin
|
||||||
|
}
|
||||||
|
|
||||||
|
printMessages = true
|
||||||
|
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.Type {
|
||||||
|
case tea.KeyCtrlC, tea.KeyEsc:
|
||||||
|
return m, tea.Quit
|
||||||
|
case tea.KeyEnter:
|
||||||
|
line := m.textarea.Value()
|
||||||
|
if len(line) != 0 {
|
||||||
|
m.inputChan <- line
|
||||||
|
m.textarea.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We handle errors just like any other message
|
||||||
|
case errMsg:
|
||||||
|
m.err = msg
|
||||||
|
return m, nil
|
||||||
|
|
||||||
|
case message:
|
||||||
|
m.messages = append(m.messages, msg)
|
||||||
|
printMessages = true
|
||||||
|
cmdToReturn = append(cmdToReturn, recvMessages(m.messageChan))
|
||||||
|
|
||||||
|
case quit:
|
||||||
|
fmt.Println("Bye!")
|
||||||
|
return m, tea.Quit
|
||||||
|
|
||||||
|
case sending:
|
||||||
|
m.isSending = bool(msg)
|
||||||
|
cmdToReturn = append(cmdToReturn, recvSendingState(m.isSendingChan))
|
||||||
|
|
||||||
|
case spinner.TickMsg:
|
||||||
|
var cmd tea.Cmd
|
||||||
|
m.spinner, cmd = m.spinner.Update(msg)
|
||||||
|
return m, cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
if printMessages {
|
||||||
|
var sb strings.Builder
|
||||||
|
for i, msg := range m.messages {
|
||||||
|
line := ""
|
||||||
|
|
||||||
|
switch msg.mType {
|
||||||
|
case ChatMessageType:
|
||||||
|
line += m.breaklineIfNeeded(i, ChatMessageType)
|
||||||
|
msgLine := "[" + msg.clock.Format("Jan 02 15:04") + " " + senderStyle(msg.author) + "] "
|
||||||
|
msgLine += msg.content
|
||||||
|
line += wordwrap.String(line+msgLine, m.width-10)
|
||||||
|
case ErrorMessageType:
|
||||||
|
line += m.breaklineIfNeeded(i, ErrorMessageType)
|
||||||
|
line += wordwrap.String(errorStyle("ERROR:")+" "+msg.err.Error(), m.width-10)
|
||||||
|
utils.Logger().Error(msg.content)
|
||||||
|
case InfoMessageType:
|
||||||
|
line += m.breaklineIfNeeded(i, InfoMessageType)
|
||||||
|
line += wordwrap.String(infoStyle("INFO:")+" "+msg.content, m.width-10)
|
||||||
|
utils.Logger().Info(msg.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(line + "\n")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
m.viewport.SetContent(sb.String())
|
||||||
|
m.viewport.GotoBottom()
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, tea.Batch(cmdToReturn...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UI) breaklineIfNeeded(i int, mt MessageType) string {
|
||||||
|
result := ""
|
||||||
|
if i > 0 {
|
||||||
|
if (mt == ChatMessageType && m.messages[i-1].mType != ChatMessageType) || (mt != ChatMessageType && m.messages[i-1].mType == ChatMessageType) {
|
||||||
|
result += "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UI) headerView() string {
|
||||||
|
title := titleStyle("Chat2 •")
|
||||||
|
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title)-4))
|
||||||
|
return lipgloss.JoinHorizontal(lipgloss.Center, title, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func max(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UI) View() string {
|
||||||
|
spinnerStr := ""
|
||||||
|
inputStr := ""
|
||||||
|
if m.isSending {
|
||||||
|
spinnerStr = m.spinner.View() + " Sending message..."
|
||||||
|
} else {
|
||||||
|
inputStr = m.textarea.View()
|
||||||
|
}
|
||||||
|
|
||||||
|
return appStyle.Render(fmt.Sprintf(
|
||||||
|
"%s\n%s\n%s%s\n",
|
||||||
|
m.headerView(),
|
||||||
|
m.viewport.View(),
|
||||||
|
inputStr,
|
||||||
|
spinnerStr,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func recvMessages(sub chan message) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
return <-sub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func recvSendingState(sub chan sending) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
return <-sub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func recvQuitSignal(q chan struct{}) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
<-q
|
||||||
|
return quit(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UI) Quit() {
|
||||||
|
m.quitChan <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UI) SetSending(isSending bool) {
|
||||||
|
m.isSendingChan <- sending(isSending)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UI) ErrorMessage(err error) {
|
||||||
|
m.messageChan <- message{mType: ErrorMessageType, err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UI) InfoMessage(text string) {
|
||||||
|
m.messageChan <- message{mType: InfoMessageType, content: text}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UI) ChatMessage(clock int64, author string, text string) {
|
||||||
|
m.messageChan <- message{mType: ChatMessageType, author: author, content: text, clock: time.Unix(clock, 0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultKeyMap returns a set of pager-like default keybindings.
|
||||||
|
func DefaultKeyMap() viewport.KeyMap {
|
||||||
|
return viewport.KeyMap{
|
||||||
|
PageDown: key.NewBinding(
|
||||||
|
key.WithKeys("pgdown"),
|
||||||
|
key.WithHelp("pgdn", "page down"),
|
||||||
|
),
|
||||||
|
PageUp: key.NewBinding(
|
||||||
|
key.WithKeys("pgup"),
|
||||||
|
key.WithHelp("pgup", "page up"),
|
||||||
|
),
|
||||||
|
HalfPageUp: key.NewBinding(
|
||||||
|
key.WithKeys("ctrl+u"),
|
||||||
|
key.WithHelp("ctrl+u", "½ page up"),
|
||||||
|
),
|
||||||
|
HalfPageDown: key.NewBinding(
|
||||||
|
key.WithKeys("ctrl+d"),
|
||||||
|
key.WithHelp("ctrl+d", "½ page down"),
|
||||||
|
),
|
||||||
|
Up: key.NewBinding(
|
||||||
|
key.WithKeys("up"),
|
||||||
|
key.WithHelp("↑", "up"),
|
||||||
|
),
|
||||||
|
Down: key.NewBinding(
|
||||||
|
key.WithKeys("down"),
|
||||||
|
key.WithHelp("↓", "down"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue