Add pairchat example #2

Merged
decentral1se merged 3 commits from cblgh/iroh-go:examples-round-two into main 2026-06-23 20:36:16 +00:00
4 changed files with 184 additions and 2 deletions
+1
View File
@@ -1 +1,2 @@
iroh-go/target/
.*.sw[a-z]
+2 -2
View File
@@ -1,9 +1,9 @@
# connect
t connect
## Build a binary
```
go build --ldflags '-linkmode external -extldflags=-static -s -w' ./main.go
go build --ldflags '-linkmode external -extldflags=-static -s -w' ./connect.go
```
If you run into `glibc` incompatibility, you can compile with `zig`/`musl`.
+37
View File
@@ -0,0 +1,37 @@
# pairchat
This is a more complicated version of `examples/connect` which allows the listener to also
write back to the sender. The result is a basic pairwise (only 2 peers can connect at once)
chat program.
## Build a binary
```
go build --ldflags '-linkmode external -extldflags=-static -s -w' ./pairchat.go
```
If you run into `glibc` incompatibility, you can compile with `zig`/`musl`.
```
CC="zig cc -target x86_64-linux-musl -lunwind" \
CGO_ENABLED=1 \
CGO_LDFLAGS="-static" \
GOOS=linux GOARCH=amd64 \
go build -v -a -ldflags '-extldflags "-static"' ./pairchat.go
```
## Terminal 1 (listener)
```
./pairchat
```
## Terminal 2 (sender)
```
./pairchat -endpoint <endpoint-id>
```
You'll see a connection notice if the two endpoints manage to connect to each other. You can
type messages on both sides of the connection, and they will appear on the other end. So
Terminal 1 will see what Terminal 2 types, and Terminal 2 will see what Terminal 1 types.
+144
View File
@@ -0,0 +1,144 @@
package main
import (
"bufio"
"flag"
"fmt"
"strings"
"os"
"os/signal"
"syscall"
iroh "git.coopcloud.tech/decentral1se/iroh-go"
)
var (
alpn = []byte("iroh-go-pairchat/0")
frameSize uint32 = 256
endpointPeer *string
)
func check(err error) {
if err != nil {
irohErr := err.(*iroh.IrohError)
panic(irohErr.Message())
}
}
func awaitInterrupt() {
done := make(chan os.Signal, 1)
signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
<-done
os.Exit(0)
}
func parseFlags() {
endpointPeer = flag.String("endpoint", "", "endpoint id of peer")
flag.Parse()
}
func send(e *iroh.Endpoint, id string) {
remote, err := iroh.EndpointIdFromString(id)
check(err)
addr := iroh.NewEndpointAddr(remote, nil, nil)
conn, err := e.Connect(addr, alpn)
check(err)
stream, err := conn.OpenBi()
check(err)
fmt.Println("connected")
recv := stream.Recv()
done := make(chan struct{})
go handleInput(stream, done)
go handleReading(recv, done)
}
func listen(e *iroh.Endpoint) {
for {
incoming := *e.AcceptNext()
go handleIncoming(incoming)
}
}
func handleIncoming (incoming *iroh.Incoming) {
accepting, err := incoming.Accept()
check(err)
conn, err := accepting.Connect()
check(err)
stream, err := conn.AcceptBi()
check(err)
recv := stream.Recv()
done := make(chan struct{})
go handleInput(stream, done)
go handleReading(recv, done)
}
func handleInput (stream *iroh.BiStream, done chan struct{}) {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
if line := scanner.Text(); line != "" {
err := stream.Send().WriteAll([]byte(line))
check(err)
}
select {
// exit if channel done is closed
case <-done:
return
default:
// do nothing
}
}
if err := scanner.Err(); err != nil {
panic(err)
}
}
func handleReading(recv *iroh.RecvStream, done chan struct{}) {
for {
frame, err := recv.Read(frameSize)
// handle timeout
if err != nil {
irohErr := err.(*iroh.IrohError)
errMsg := irohErr.Message()
if strings.HasPrefix(errMsg, "ConnectionLost") {
fmt.Println("lost connection")
break
} else {
check(err)
}
}
fmt.Println(string(frame))
}
close(done)
}
func main() {
parseFlags()
preset := iroh.PresetN0()
opts := iroh.EndpointOptions{
Preset: &preset,
Alpns: &[][]byte{alpn},
}
endpoint, err := iroh.EndpointBind(opts)
check(err)
endpoint.Online()
if *endpointPeer == "" {
fmt.Println("this endpoint is online and listening! use this id to connect via a peer:")
fmt.Printf("pairchat -endpoint %s\n", endpoint.Id())
go func() { listen(endpoint) }()
} else {
go func() { send(endpoint, *endpointPeer) }()
}
awaitInterrupt()
}