chore: auto-commit (1 archivos)
- mobile/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
// Package mobile exposes a flat, gomobile-friendly API over the unibus client
|
||||
// so an Android app can join rooms, publish, and receive messages with the same
|
||||
// end-to-end encryption as any native Go peer.
|
||||
//
|
||||
// gomobile only supports a limited set of types across the binding boundary
|
||||
// (string, []byte, int, bool, error, named structs, and interfaces). This layer
|
||||
// translates the richer client API into those primitives and delivers incoming
|
||||
// frames through a Java/Kotlin-implemented FrameListener callback. No protocol
|
||||
// or cryptography is reimplemented here: every call delegates to pkg/client,
|
||||
// which is the single source of truth shared with every other peer on the bus.
|
||||
package mobile
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/enmanuel/unibus/pkg/client"
|
||||
"github.com/enmanuel/unibus/pkg/frame"
|
||||
"github.com/enmanuel/unibus/pkg/room"
|
||||
)
|
||||
|
||||
// FrameListener receives decrypted messages for a subscribed room. The Android
|
||||
// side implements this interface. Its methods are invoked from a NATS delivery
|
||||
// goroutine, so implementations must hop back to the UI thread (for example via
|
||||
// a coroutine on the main dispatcher) before touching Android views.
|
||||
type FrameListener interface {
|
||||
OnFrame(roomID string, sender string, msgID string, text string)
|
||||
}
|
||||
|
||||
// Session is a connected unibus peer. Create it with NewSession and close it
|
||||
// with Close when the app stops.
|
||||
type Session struct {
|
||||
c *client.Client
|
||||
}
|
||||
|
||||
// GenerateIdentity creates (or loads) the long-term keypair stored at path.
|
||||
// Call it once on first launch. The resulting file holds the peer's private
|
||||
// Ed25519 and X25519 keys and must be kept private to the app sandbox.
|
||||
func GenerateIdentity(path string) error {
|
||||
_, err := client.LoadOrCreateIdentity(path)
|
||||
return err
|
||||
}
|
||||
|
||||
// NewSession loads the identity at idPath and connects to the bus. natsURL is
|
||||
// the data plane (for example nats://host:4250) and ctrlURL is the control
|
||||
// plane HTTP endpoint (for example http://host:8470).
|
||||
func NewSession(idPath, natsURL, ctrlURL string) (*Session, error) {
|
||||
id, err := client.LoadOrCreateIdentity(idPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := client.New(natsURL, ctrlURL, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Session{c: c}, nil
|
||||
}
|
||||
|
||||
// EndpointID returns this peer's stable endpoint identifier, derived from its
|
||||
// signing public key. It is the value that appears as the sender of frames.
|
||||
func (s *Session) EndpointID() string {
|
||||
return s.c.Endpoint().ID
|
||||
}
|
||||
|
||||
// CreateRoom opens a room on the given subject. mode is "matrix" for the
|
||||
// encrypted, persisted and signed policy, or "nats" for plain cleartext. It
|
||||
// returns the room id used by Join, Publish and Subscribe.
|
||||
func (s *Session) CreateRoom(subject, mode string) (string, error) {
|
||||
p := room.ModeNATS
|
||||
if mode == "matrix" {
|
||||
p = room.ModeMatrix
|
||||
}
|
||||
return s.c.CreateRoom(subject, p)
|
||||
}
|
||||
|
||||
// Join fetches the room key when the room is encrypted and prepares the session
|
||||
// to publish to and receive from the room.
|
||||
func (s *Session) Join(roomID string) error {
|
||||
return s.c.Join(roomID)
|
||||
}
|
||||
|
||||
// Publish sends a UTF-8 text message to the room.
|
||||
func (s *Session) Publish(roomID, text string) error {
|
||||
return s.c.Publish(roomID, []byte(text))
|
||||
}
|
||||
|
||||
// Subscribe streams decrypted messages of the room to the listener until the
|
||||
// session is closed.
|
||||
func (s *Session) Subscribe(roomID string, l FrameListener) error {
|
||||
_, err := s.c.Subscribe(roomID, func(f frame.Frame, plaintext []byte) {
|
||||
l.OnFrame(roomID, f.Sender, f.MsgID, string(plaintext))
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Request performs an RPC request/reply against subject and returns the reply
|
||||
// payload as text. timeoutMs bounds the wait in milliseconds.
|
||||
func (s *Session) Request(subject, text string, timeoutMs int) (string, error) {
|
||||
out, err := s.c.Request(subject, []byte(text), time.Duration(timeoutMs)*time.Millisecond)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
// Close disconnects the peer from the bus.
|
||||
func (s *Session) Close() error {
|
||||
return s.c.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user