mirror of
https://github.com/openlibrecommunity/olcrtc.git
synced 2026-05-30 08:59:43 +00:00
221 lines
4.3 KiB
Go
221 lines
4.3 KiB
Go
// Package mobile provides a gomobile-compatible API for olcRTC.
|
|
// Build with: gomobile bind -target=android ./mobile
|
|
package mobile
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/openlibrecommunity/olcrtc/internal/client"
|
|
"github.com/openlibrecommunity/olcrtc/internal/logger"
|
|
"github.com/openlibrecommunity/olcrtc/internal/protect"
|
|
|
|
_ "golang.org/x/mobile/bind" // ensure gomobile bind is available
|
|
)
|
|
|
|
// SocketProtector protects sockets from VPN routing on Android.
|
|
// Implement this interface in Kotlin/Java and pass to SetProtector.
|
|
type SocketProtector interface {
|
|
Protect(fd int) bool
|
|
}
|
|
|
|
// LogWriter receives log messages from olcRTC.
|
|
type LogWriter interface {
|
|
WriteLog(msg string)
|
|
}
|
|
|
|
var (
|
|
errAlreadyRunning = errors.New("olcRTC already running")
|
|
errRoomIDRequired = errors.New("roomID is required")
|
|
errKeyHexRequired = errors.New("keyHex is required")
|
|
errNotRunning = errors.New("olcRTC is not running")
|
|
errStoppedBeforeReady = errors.New("olcRTC stopped before becoming ready")
|
|
errStartTimedOut = errors.New("olcRTC start timed out")
|
|
)
|
|
|
|
//nolint:gochecknoglobals // Mobile bindings expose a singleton runtime controlled by the embedding app.
|
|
var (
|
|
mu sync.Mutex
|
|
cancel context.CancelFunc
|
|
done chan struct{}
|
|
ready chan struct{}
|
|
errRun error
|
|
)
|
|
|
|
// SetProtector sets the Android VPN socket protector.
|
|
// Must be called before Start.
|
|
func SetProtector(p SocketProtector) {
|
|
if p == nil {
|
|
protect.Protector = nil
|
|
return
|
|
}
|
|
protect.Protector = func(fd int) bool {
|
|
return p.Protect(fd)
|
|
}
|
|
}
|
|
|
|
// SetLogWriter sets a custom log writer for olcRTC output.
|
|
func SetLogWriter(w LogWriter) {
|
|
if w != nil {
|
|
log.SetOutput(&logBridge{w: w})
|
|
}
|
|
}
|
|
|
|
// SetDebug enables or disables verbose logging.
|
|
func SetDebug(enabled bool) {
|
|
logger.SetVerbose(enabled)
|
|
if enabled {
|
|
log.SetFlags(log.Ltime | log.Lshortfile)
|
|
return
|
|
}
|
|
|
|
log.SetFlags(log.Ltime)
|
|
}
|
|
|
|
// Start launches the olcRTC client in background.
|
|
// roomID: Telemost room ID (e.g. "xxx-xxx-xxx")
|
|
// keyHex: 64-char hex encryption key
|
|
// socksPort: local SOCKS5 proxy port (e.g. 10808)
|
|
// socksUser/socksPass: SOCKS5 credentials (empty = no auth).
|
|
func Start(roomID, keyHex string, socksPort int, socksUser, socksPass string) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
switch {
|
|
case cancel != nil:
|
|
return errAlreadyRunning
|
|
case roomID == "":
|
|
return errRoomIDRequired
|
|
case keyHex == "":
|
|
return errKeyHexRequired
|
|
}
|
|
|
|
roomURL := "https://telemost.yandex.ru/j/" + roomID
|
|
|
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
|
cancel = cancelFunc
|
|
done = make(chan struct{})
|
|
ready = make(chan struct{})
|
|
localReady := ready
|
|
errRun = nil
|
|
|
|
var readyOnce sync.Once
|
|
go func() {
|
|
defer cancelFunc()
|
|
|
|
err := client.RunWithReady(
|
|
ctx,
|
|
roomURL,
|
|
keyHex,
|
|
socksPort,
|
|
"",
|
|
socksUser,
|
|
socksPass,
|
|
func() {
|
|
readyOnce.Do(func() {
|
|
close(localReady)
|
|
})
|
|
},
|
|
)
|
|
|
|
mu.Lock()
|
|
cancel = nil
|
|
errRun = err
|
|
mu.Unlock()
|
|
close(done)
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// WaitReady blocks until the Telemost peers are connected and the local SOCKS5 listener is ready.
|
|
//
|
|
//nolint:cyclop // The control flow is intentionally linear so mobile callers can observe each startup state clearly.
|
|
func WaitReady(timeoutMillis int) error {
|
|
mu.Lock()
|
|
r := ready
|
|
d := done
|
|
runErr := errRun
|
|
running := cancel != nil
|
|
mu.Unlock()
|
|
|
|
if r == nil {
|
|
if runErr != nil {
|
|
return runErr
|
|
}
|
|
|
|
return errNotRunning
|
|
}
|
|
|
|
select {
|
|
case <-r:
|
|
return nil
|
|
default:
|
|
}
|
|
|
|
if !running {
|
|
if runErr != nil {
|
|
return runErr
|
|
}
|
|
|
|
return errStoppedBeforeReady
|
|
}
|
|
|
|
timer := time.NewTimer(time.Duration(timeoutMillis) * time.Millisecond)
|
|
defer timer.Stop()
|
|
|
|
select {
|
|
case <-r:
|
|
return nil
|
|
case <-d:
|
|
mu.Lock()
|
|
runErr = errRun
|
|
mu.Unlock()
|
|
if runErr != nil {
|
|
return runErr
|
|
}
|
|
|
|
return errStoppedBeforeReady
|
|
case <-timer.C:
|
|
return errStartTimedOut
|
|
}
|
|
}
|
|
|
|
// Stop gracefully stops the olcRTC client.
|
|
func Stop() {
|
|
mu.Lock()
|
|
cancelFunc := cancel
|
|
doneCh := done
|
|
mu.Unlock()
|
|
|
|
if cancelFunc == nil {
|
|
return
|
|
}
|
|
|
|
cancelFunc()
|
|
|
|
if doneCh != nil {
|
|
<-doneCh
|
|
}
|
|
}
|
|
|
|
// IsRunning returns true if the olcRTC client is active.
|
|
func IsRunning() bool {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
return cancel != nil
|
|
}
|
|
|
|
// logBridge adapts LogWriter to io.Writer for log package.
|
|
type logBridge struct {
|
|
w LogWriter
|
|
}
|
|
|
|
func (b *logBridge) Write(p []byte) (int, error) {
|
|
b.w.WriteLog(string(p))
|
|
return len(p), nil
|
|
}
|