Add DM support to test script; fix DM routing and JSON encoding
ipc: CmdSendMessage now routes to the named recipient only (m.SendTo) when the "to" field is set, rather than broadcasting to all peers. Group messages continue to use Broadcast. This is the correct privacy behaviour for DMs. test-network.sh: - Resolve each peer's hex id from get_state before joining the network, using a retry loop with timeout to handle slow daemon startup. - Added a DM section: alice→bob, bob→alice, charlie→alice, alice→charlie. DMs use the "to" + "room":"dm:<peer_id>" convention. - Fixed jq -cn (compact) instead of jq -n (pretty) so the IPC newline- delimited protocol receives a single JSON line per command. - pretty() now renders DMs as 📨 DM <from→to> rather than 💬 #room. - Persistence check now breaks down totals by group vs DM message count. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -160,7 +160,12 @@ func handleClient(conn net.Conn, m *mesh.Mesh, doJoin func(string), doLeave func
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
m.Broadcast(payload)
|
||||
if cmd.To != nil {
|
||||
// DM — send only to the named recipient.
|
||||
m.SendTo(*cmd.To, payload)
|
||||
} else {
|
||||
m.Broadcast(payload)
|
||||
}
|
||||
m.SaveMessage(msg)
|
||||
m.Emit(proto.IpcMessage{Type: proto.EvtMessageReceived, Message: msg})
|
||||
|
||||
|
||||
115
test-network.sh
115
test-network.sh
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# test-network.sh — spin up anchor + 3 peers, connect them, exchange messages.
|
||||
# test-network.sh — spin up anchor + 3 peers, connect them, exchange messages + DMs.
|
||||
# Usage: ./test-network.sh
|
||||
# Requires: go, nc, jq
|
||||
# Requires: go, nc, jq, sqlite3
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -32,7 +32,8 @@ cleanup() {
|
||||
kill "$pid" 2>/dev/null || true
|
||||
done
|
||||
wait 2>/dev/null || true
|
||||
rm -rf "$DATA_ROOT"
|
||||
echo -e "${DIM}data left at: ${DATA_ROOT}${RESET}"
|
||||
echo -e "${DIM} inspect: sqlite3 ${DATA_ROOT}/alice/messages.db${RESET}"
|
||||
echo -e "${DIM}done.${RESET}"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
@@ -75,11 +76,18 @@ pretty() {
|
||||
echo -e "${color}${BOLD}[${label}]${RESET} ${RED}← peer_disconnected${RESET} ${DIM}${pid}${RESET}"
|
||||
;;
|
||||
message_received)
|
||||
local from body room
|
||||
local from body room to_field
|
||||
from=$(echo "$line" | jq -r '.message.from[:8] // "?"' 2>/dev/null)
|
||||
body=$(echo "$line" | jq -r '.message.body // ""' 2>/dev/null)
|
||||
room=$(echo "$line" | jq -r '.message.room // ""' 2>/dev/null)
|
||||
echo -e "${color}${BOLD}[${label}]${RESET} ${BOLD}💬 #${room}${RESET} ${DIM}<${from}…>${RESET} ${body}"
|
||||
to_field=$(echo "$line" | jq -r '.message.to // ""' 2>/dev/null)
|
||||
if [ -n "$to_field" ]; then
|
||||
local to_short
|
||||
to_short=$(echo "$to_field" | cut -c1-8)
|
||||
echo -e "${color}${BOLD}[${label}]${RESET} ${BLUE}📨 DM${RESET} ${DIM}<${from}…→${to_short}…>${RESET} ${body}"
|
||||
else
|
||||
echo -e "${color}${BOLD}[${label}]${RESET} ${BOLD}💬 #${room}${RESET} ${DIM}<${from}…>${RESET} ${body}"
|
||||
fi
|
||||
;;
|
||||
error)
|
||||
local msg
|
||||
@@ -113,6 +121,27 @@ ipc() {
|
||||
echo "$json" | nc -q 0 127.0.0.1 "$port" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# Query get_state and return a single field via jq from the state_snapshot line.
|
||||
# Retries for up to 3 seconds in case the daemon is still initialising.
|
||||
# Usage: peer_field <ipc_port> <jq_expr>
|
||||
peer_field() {
|
||||
local port="$1"
|
||||
local expr="$2"
|
||||
local result=""
|
||||
local n=0
|
||||
while [ -z "$result" ] || [ "$result" = "null" ]; do
|
||||
result=$(echo '{"type":"get_state"}' \
|
||||
| timeout 2 nc 127.0.0.1 "$port" 2>/dev/null \
|
||||
| grep '"type":"state_snapshot"' \
|
||||
| head -1 \
|
||||
| jq -r "$expr" 2>/dev/null || true)
|
||||
n=$(( n + 1 ))
|
||||
[ "$n" -ge 10 ] && break
|
||||
[ -z "$result" ] || [ "$result" = "null" ] && sleep 0.3
|
||||
done
|
||||
echo "$result"
|
||||
}
|
||||
|
||||
# Wait until a TCP port accepts connections (up to N seconds).
|
||||
wait_port() {
|
||||
local port="$1"
|
||||
@@ -193,8 +222,18 @@ subscribe "$BOB_COLOR" "bob " "$BOB_IPC"
|
||||
subscribe "$CHARLIE_COLOR" "charlie" "$CHARLIE_IPC"
|
||||
sleep 0.3 # let state_snapshot lines arrive
|
||||
|
||||
# ── join network ──────────────────────────────────────────────────────────────
|
||||
# ── resolve peer IDs ──────────────────────────────────────────────────────────
|
||||
# Each daemon knows its own id from the state_snapshot.
|
||||
ALICE_ID=$(peer_field "$ALICE_IPC" '.local_peer.id')
|
||||
BOB_ID=$(peer_field "$BOB_IPC" '.local_peer.id')
|
||||
CHARLIE_ID=$(peer_field "$CHARLIE_IPC" '.local_peer.id')
|
||||
|
||||
log "$ANCHOR_COLOR" "ids" "alice = ${ALICE_ID:0:16}…"
|
||||
log "$ANCHOR_COLOR" "ids" "bob = ${BOB_ID:0:16}…"
|
||||
log "$ANCHOR_COLOR" "ids" "charlie = ${CHARLIE_ID:0:16}…"
|
||||
echo ""
|
||||
|
||||
# ── join network ──────────────────────────────────────────────────────────────
|
||||
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
||||
echo -e "joining all peers to network: ${BOLD}${NETWORK_NAME}${RESET}"
|
||||
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
||||
@@ -209,32 +248,63 @@ ipc "$CHARLIE_IPC" "$JOIN"
|
||||
echo -e "${DIM}waiting for ICE / DataChannel setup (up to 10s)…${RESET}"
|
||||
sleep 6
|
||||
|
||||
# ── chat ──────────────────────────────────────────────────────────────────────
|
||||
# ── group chat ────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
||||
echo -e "sending messages"
|
||||
echo -e "group chat (#general)"
|
||||
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
||||
|
||||
ipc "$ALICE_IPC" '{"type":"send_message","room":"general","body":"hey everyone, alice here"}'
|
||||
ipc "$ALICE_IPC" '{"type":"send_message","room":"general","body":"hey everyone, alice here"}'
|
||||
sleep 0.4
|
||||
ipc "$BOB_IPC" '{"type":"send_message","room":"general","body":"hi alice! bob checking in"}'
|
||||
ipc "$BOB_IPC" '{"type":"send_message","room":"general","body":"hi alice! bob checking in"}'
|
||||
sleep 0.4
|
||||
ipc "$CHARLIE_IPC" '{"type":"send_message","room":"general","body":"charlie online. the mesh works!"}'
|
||||
sleep 0.4
|
||||
ipc "$ALICE_IPC" '{"type":"send_message","room":"general","body":"nice — three-way chat over WebRTC 🎉"}'
|
||||
ipc "$ALICE_IPC" '{"type":"send_message","room":"general","body":"nice — three-way chat over WebRTC 🎉"}'
|
||||
sleep 0.4
|
||||
ipc "$BOB_IPC" '{"type":"send_message","room":"general","body":"no central server, fully peer-to-peer"}'
|
||||
ipc "$BOB_IPC" '{"type":"send_message","room":"general","body":"no central server, fully peer-to-peer"}'
|
||||
sleep 0.4
|
||||
|
||||
# ── state snapshot ────────────────────────────────────────────────────────────
|
||||
# ── direct messages ───────────────────────────────────────────────────────────
|
||||
# DMs set the "to" field to the recipient's full hex peer ID.
|
||||
# The room field is used as "dm:<recipient_id>" by convention so both sides
|
||||
# can query their DM history for the same conversation.
|
||||
echo ""
|
||||
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
||||
echo -e "requesting state snapshots"
|
||||
echo -e "direct messages"
|
||||
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
||||
ipc "$ALICE_IPC" '{"type":"get_state"}'
|
||||
ipc "$BOB_IPC" '{"type":"get_state"}'
|
||||
ipc "$CHARLIE_IPC" '{"type":"get_state"}'
|
||||
sleep 0.5
|
||||
|
||||
# Alice → Bob (privately)
|
||||
ipc "$ALICE_IPC" "$(jq -cn \
|
||||
--arg to "$BOB_ID" \
|
||||
--arg room "dm:$BOB_ID" \
|
||||
--arg body "hey bob, this is just between us" \
|
||||
'{"type":"send_message","room":$room,"body":$body,"to":$to}')"
|
||||
sleep 0.4
|
||||
|
||||
# Bob replies to Alice
|
||||
ipc "$BOB_IPC" "$(jq -cn \
|
||||
--arg to "$ALICE_ID" \
|
||||
--arg room "dm:$ALICE_ID" \
|
||||
--arg body "got it alice, loud and clear 🤫" \
|
||||
'{"type":"send_message","room":$room,"body":$body,"to":$to}')"
|
||||
sleep 0.4
|
||||
|
||||
# Charlie → Alice (privately)
|
||||
ipc "$CHARLIE_IPC" "$(jq -cn \
|
||||
--arg to "$ALICE_ID" \
|
||||
--arg room "dm:$ALICE_ID" \
|
||||
--arg body "alice, can you hear me? charlie dm test" \
|
||||
'{"type":"send_message","room":$room,"body":$body,"to":$to}')"
|
||||
sleep 0.4
|
||||
|
||||
# Alice replies to Charlie
|
||||
ipc "$ALICE_IPC" "$(jq -cn \
|
||||
--arg to "$CHARLIE_ID" \
|
||||
--arg room "dm:$CHARLIE_ID" \
|
||||
--arg body "yes charlie, DMs working perfectly!" \
|
||||
'{"type":"send_message","room":$room,"body":$body,"to":$to}')"
|
||||
sleep 0.4
|
||||
|
||||
# ── leave ─────────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
@@ -244,6 +314,7 @@ echo -e "${DIM}─────────────────────
|
||||
ipc "$CHARLIE_IPC" '{"type":"leave_network"}'
|
||||
sleep 1
|
||||
|
||||
# ── persistence check ─────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
||||
echo -e "verifying persistence (SQLite)"
|
||||
@@ -251,9 +322,11 @@ echo -e "${DIM}─────────────────────
|
||||
for peer in alice bob charlie; do
|
||||
db="$DATA_ROOT/$peer/messages.db"
|
||||
if [ -f "$db" ]; then
|
||||
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM messages;" 2>/dev/null || echo "?")
|
||||
peers=$(sqlite3 "$db" "SELECT COUNT(*) FROM peers;" 2>/dev/null || echo "?")
|
||||
echo -e " ${BOLD}${peer}${RESET}: ${count} messages, ${peers} known peers in messages.db"
|
||||
total=$(sqlite3 "$db" "SELECT COUNT(*) FROM messages;" 2>/dev/null || echo "?")
|
||||
group=$(sqlite3 "$db" "SELECT COUNT(*) FROM messages WHERE room='general';" 2>/dev/null || echo "?")
|
||||
dms=$(sqlite3 "$db" "SELECT COUNT(*) FROM messages WHERE room LIKE 'dm:%';" 2>/dev/null || echo "?")
|
||||
peers_count=$(sqlite3 "$db" "SELECT COUNT(*) FROM peers;" 2>/dev/null || echo "?")
|
||||
echo -e " ${BOLD}${peer}${RESET}: ${total} total (${group} group, ${dms} DM), ${peers_count} known peers"
|
||||
else
|
||||
echo -e " ${RED}${peer}: no messages.db found${RESET}"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user