Adds LocalSend-compatible text messaging alongside file transfer. Wire format (matches the LocalSend app): a message is a single "file" with fileType "text/plain" whose content rides in the prepare-upload `preview` field. The receiver returns an empty token set, so nothing is uploaded — the text is read straight from the preview. - client: SendMessage builds that single-file prepare-upload (no body upload). - server: detect a message (one text file with non-empty preview), surface it on a new Messages() channel instead of saving a file, and respond with an empty file set. Messages bypass the accept prompt (auto-received); the PIN gate still applies. - app: bridge the server's messages channel to the TUI as MessageMsg. - tui: a new Messages tab lists received messages (enter to read full, d to delete); press `m` on a device to compose and send one. Incoming messages show a footer notice. Tests: end-to-end send→receive (text intact, sender preserved, nothing written to disk) and unit coverage of the message-detection rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
84 lines
2.4 KiB
Go
84 lines
2.4 KiB
Go
// Package app bridges the Tea-agnostic domain layer (discovery, server, client)
|
|
// into Bubble Tea messages. Domain components emit on Go channels; a bridge
|
|
// goroutine forwards them via tea.Program.Send.
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
"omarchy-send/internal/discovery"
|
|
"omarchy-send/internal/server"
|
|
"omarchy-send/internal/transfer"
|
|
)
|
|
|
|
// PeerFoundMsg is delivered when a peer is discovered or its address changes.
|
|
type PeerFoundMsg struct{ Peer discovery.Peer }
|
|
|
|
// PeerLostMsg is delivered when a peer ages out (post-M1).
|
|
type PeerLostMsg struct{ Fingerprint string }
|
|
|
|
// IncomingMsg is delivered when a peer asks to send us files. The TUI shows an
|
|
// accept prompt and answers via the carried Reply channel.
|
|
type IncomingMsg struct{ Req server.AcceptRequest }
|
|
|
|
// TransferMsg reports progress/lifecycle of a transfer (either direction).
|
|
type TransferMsg struct{ Ev transfer.Event }
|
|
|
|
// MessageMsg is delivered when a peer sends us a plain-text message.
|
|
type MessageMsg struct{ Msg server.ReceivedMessage }
|
|
|
|
// BridgeServer forwards the server's accept requests, transfer events, and
|
|
// received messages to the Tea program until ctx is cancelled.
|
|
func BridgeServer(ctx context.Context, accepts <-chan server.AcceptRequest, transfers <-chan transfer.Event, messages <-chan server.ReceivedMessage, send func(tea.Msg)) {
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case req := <-accepts:
|
|
send(IncomingMsg{Req: req})
|
|
case ev := <-transfers:
|
|
send(TransferMsg{Ev: ev})
|
|
case m := <-messages:
|
|
send(MessageMsg{Msg: m})
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// BridgeTransfers forwards a transfer event channel (e.g. the sender's) to the
|
|
// Tea program until ctx is cancelled.
|
|
func BridgeTransfers(ctx context.Context, transfers <-chan transfer.Event, send func(tea.Msg)) {
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case ev := <-transfers:
|
|
send(TransferMsg{Ev: ev})
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// BridgeDiscovery forwards discovery events to the Tea program until ctx is
|
|
// cancelled. send is typically tea.Program.Send.
|
|
func BridgeDiscovery(ctx context.Context, events <-chan discovery.Event, send func(tea.Msg)) {
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case ev := <-events:
|
|
switch ev.Kind {
|
|
case discovery.PeerFound:
|
|
send(PeerFoundMsg{Peer: ev.Peer})
|
|
case discovery.PeerLost:
|
|
send(PeerLostMsg{Fingerprint: ev.Peer.Info.Fingerprint})
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
}
|