From 9388a8e494daec3f36ccca50f58cd6d3f38667ef Mon Sep 17 00:00:00 2001 From: zarazaex69 Date: Mon, 11 May 2026 03:20:10 +0300 Subject: [PATCH] refactor: introduce engine and auth interfaces Lay the groundwork for splitting service-specific logic (WB / Jazz / Telemost API flows) from wire-level transport engines (LiveKit, Goolom, future Jitsi). An engine takes only URL+Token+Name+network knobs; an auth provider produces those credentials and reports which engine it feeds. RoomCreator is an optional capability for the gen mode. Existing carriers and providers are untouched. Co-Authored-By: Claude Opus 4.7 --- internal/auth/auth.go | 93 +++++++++++++++++++++++++++++++++++++ internal/engine/engine.go | 96 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 internal/auth/auth.go create mode 100644 internal/engine/engine.go diff --git a/internal/auth/auth.go b/internal/auth/auth.go new file mode 100644 index 0000000..1ac3e34 --- /dev/null +++ b/internal/auth/auth.go @@ -0,0 +1,93 @@ +// Package auth defines how room credentials are produced for an engine. +// +// An auth provider is responsible for any service-specific HTTP / login flow +// (WB Stream, SaluteJazz, Yandex Telemost, Jitsi, ...) and produces a +// Credentials value that an engine can use to connect. Some auth providers +// also support creating new rooms; that capability is optional and is +// expressed via the RoomCreator interface. +// +// The "none" auth provider passes a caller-supplied URL+Token through +// unchanged — this is the path that sing-box and other downstream consumers +// take when they want to use olcrtc as a generic LiveKit/Goolom/Jitsi +// transport without any service-specific behaviour baked in. +package auth + +import ( + "context" + "errors" +) + +var ( + // ErrAuthNotFound is returned when a requested auth provider is not registered. + ErrAuthNotFound = errors.New("auth provider not found") + // ErrRoomCreationUnsupported is returned when an auth provider cannot create rooms. + ErrRoomCreationUnsupported = errors.New("auth provider does not support room creation") + // ErrRoomIDRequired is returned when an auth flow needs an existing room ID and none was supplied. + ErrRoomIDRequired = errors.New("room ID required") +) + +// Credentials carry everything an engine needs to connect to an SFU. +// +// URL is the signaling endpoint (e.g. wss://livekit.example/). Token is the +// access token (LiveKit JWT, Goolom session credential, etc). Extra is for +// engine-specific bits that don't fit the common shape — engines should not +// rely on it being populated unless their paired auth provider documents it. +type Credentials struct { + URL string + Token string + Extra map[string]string +} + +// Config is the input to an auth provider. +type Config struct { + // RoomURL is the user-facing room link (e.g. https://telemost.yandex.ru/j/123). + // Optional for providers that can also create rooms on demand. + RoomURL string + // Name is the display name to register with. + Name string + // DNSServer / ProxyAddr / ProxyPort are network knobs for outbound HTTP. + DNSServer string + ProxyAddr string + ProxyPort int +} + +// Provider produces engine credentials. +type Provider interface { + // Engine reports which engine this auth provider feeds. This is what lets + // a carrier resolve (auth, engine) pairs consistently — e.g. auth=jazz + // always pairs with engine=livekit. + Engine() string + // Issue obtains credentials for the given room. + Issue(ctx context.Context, cfg Config) (Credentials, error) +} + +// RoomCreator is implemented by auth providers that can create new rooms +// against their backing service. Used by `olcrtc -mode gen`. +type RoomCreator interface { + CreateRoom(ctx context.Context, cfg Config) (roomID string, err error) +} + +var registry = make(map[string]Provider) //nolint:gochecknoglobals // package-level state intentional + +// Register adds an auth provider to the registry. +func Register(name string, p Provider) { + registry[name] = p +} + +// Get returns a registered auth provider by name. +func Get(name string) (Provider, error) { + p, ok := registry[name] + if !ok { + return nil, ErrAuthNotFound + } + return p, nil +} + +// Available returns the list of registered auth provider names. +func Available() []string { + names := make([]string, 0, len(registry)) + for name := range registry { + names = append(names, name) + } + return names +} diff --git a/internal/engine/engine.go b/internal/engine/engine.go new file mode 100644 index 0000000..00d357f --- /dev/null +++ b/internal/engine/engine.go @@ -0,0 +1,96 @@ +// Package engine defines the wire-level transport engine that connects to a +// remote SFU. An engine is independent of how the room credentials were +// obtained: it accepts a signaling URL and an access token, and exposes the +// byte/video primitives the rest of olcrtc consumes. +// +// Engines model the SFU protocol family (e.g. LiveKit, Goolom). Service- +// specific bits (e.g. WB / Jazz / Telemost API flows) live in the auth +// package, not here. +package engine + +import ( + "context" + "errors" + + "github.com/pion/webrtc/v4" +) + +var ( + // ErrEngineNotFound is returned when a requested engine is not registered. + ErrEngineNotFound = errors.New("engine not found") + // ErrByteStreamUnsupported is returned when an engine cannot expose a byte stream. + ErrByteStreamUnsupported = errors.New("engine does not support byte stream") + // ErrVideoTrackUnsupported is returned when an engine cannot exchange video tracks. + ErrVideoTrackUnsupported = errors.New("engine does not support video tracks") +) + +// Capabilities describes the transport primitives an engine can expose. +type Capabilities struct { + ByteStream bool + VideoTrack bool +} + +// Config is the runtime input to an engine factory. URL/Token are produced by +// an auth provider (or supplied directly by the caller for "none" auth). +type Config struct { + URL string + Token string + Name string + OnData func([]byte) + DNSServer string + ProxyAddr string + ProxyPort int +} + +// Session is the engine-level runtime handle. It is shaped to match what +// the upper transport layer expects: send/receive bytes, optional video +// tracks, and lifecycle callbacks. +// +//nolint:interfacebloat // mirrors the historical provider.Provider surface that the rest of olcrtc consumes +type Session interface { + Connect(ctx context.Context) error + Send(data []byte) error + Close() error + SetReconnectCallback(cb func(*webrtc.DataChannel)) + SetShouldReconnect(fn func() bool) + SetEndedCallback(cb func(string)) + WatchConnection(ctx context.Context) + CanSend() bool + GetSendQueue() chan []byte + GetBufferedAmount() uint64 + Capabilities() Capabilities +} + +// VideoTrackCapable is implemented by engines that can exchange video tracks. +type VideoTrackCapable interface { + AddVideoTrack(track webrtc.TrackLocal) error + SetVideoTrackHandler(cb func(*webrtc.TrackRemote, *webrtc.RTPReceiver)) +} + +// Factory creates a new engine session. +type Factory func(ctx context.Context, cfg Config) (Session, error) + +var registry = make(map[string]Factory) //nolint:gochecknoglobals // package-level state intentional + +// Register adds an engine factory to the registry. +func Register(name string, factory Factory) { + registry[name] = factory +} + +// New creates an engine session by name. +func New(ctx context.Context, name string, cfg Config) (Session, error) { + factory, ok := registry[name] + if !ok { + return nil, ErrEngineNotFound + } + return factory(ctx, cfg) +} + +// Available returns the list of registered engine names. +func Available() []string { + names := make([]string, 0, len(registry)) + for name := range registry { + names = append(names, name) + } + return names +}