Files
olcrtc/internal/auth/wbstream/api.go
zarazaex69 071106a674 refactor: migrate wbstream to engine/livekit + auth/wbstream
Split the WB Stream provider into two orthogonal pieces:
- internal/engine/livekit — generic LiveKit transport (URL+Token only,
  no service-specific assumptions). Registered as engine "livekit".
- internal/auth/wbstream — WB Stream API flow (guest register, join,
  token exchange). Implements auth.Provider and auth.RoomCreator,
  reports engine "livekit".

The carrier name "wbstream" now goes through registerEngineAuth, which
wires the auth provider to the engine it declares. CLI surface is
unchanged. session.Gen for wbstream calls the RoomCreator directly;
that path will become fully generic in a later step. jazz and telemost
remain on the legacy provider path for now.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 03:28:34 +03:00

192 lines
5.4 KiB
Go

// Package wbstream is the auth provider for the WB Stream service. It
// produces LiveKit credentials by registering a guest, optionally creating
// a room, joining it, and exchanging the guest access token for a room
// token.
package wbstream
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"github.com/openlibrecommunity/olcrtc/internal/protect"
)
const wsURL = "wss://wbstream01-el.wb.ru:7880"
var apiBase = "https://stream.wb.ru" //nolint:gochecknoglobals // overridable base URL for tests
var (
errGuestRegister = errors.New("guest register failed")
errCreateRoom = errors.New("create room failed")
errJoinRoom = errors.New("join room failed")
errGetToken = errors.New("get token failed")
)
type guestRegisterRequest struct {
DisplayName string `json:"displayName"`
Device device `json:"device"`
}
type device struct {
DeviceName string `json:"deviceName"`
DeviceType string `json:"deviceType"`
}
type guestRegisterResponse struct {
AccessToken string `json:"accessToken"`
}
type createRoomRequest struct {
RoomType string `json:"roomType"`
RoomPrivacy string `json:"roomPrivacy"`
}
type createRoomResponse struct {
RoomID string `json:"roomId"`
}
type tokenResponse struct {
RoomToken string `json:"roomToken"`
}
func registerGuest(ctx context.Context, displayName string) (string, error) {
u := apiBase + "/auth/api/v1/auth/user/guest-register"
reqBody := guestRegisterRequest{
DisplayName: displayName,
Device: device{
DeviceName: "Linux",
DeviceType: "PARTICIPANT_DEVICE_TYPE_WEB_DESKTOP",
},
}
body, err := json.Marshal(reqBody)
if err != nil {
return "", fmt.Errorf("marshal request body: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewBuffer(body))
if err != nil {
return "", fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Mozilla/5.0 (Linux x86_64)")
client := protect.NewHTTPClient()
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("do request: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("%w: %d %s", errGuestRegister, resp.StatusCode, b)
}
var res guestRegisterResponse
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return "", fmt.Errorf("decode response: %w", err)
}
return res.AccessToken, nil
}
func createRoom(ctx context.Context, accessToken string) (string, error) {
u := apiBase + "/api-room/api/v2/room"
reqBody := createRoomRequest{
RoomType: "ROOM_TYPE_ALL_ON_SCREEN",
RoomPrivacy: "ROOM_PRIVACY_FREE",
}
body, err := json.Marshal(reqBody)
if err != nil {
return "", fmt.Errorf("marshal request body: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewBuffer(body))
if err != nil {
return "", fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("User-Agent", "Mozilla/5.0 (Linux x86_64)")
client := protect.NewHTTPClient()
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("do request: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
b, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("%w: %d %s", errCreateRoom, resp.StatusCode, b)
}
var res createRoomResponse
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return "", fmt.Errorf("decode response: %w", err)
}
return res.RoomID, nil
}
func joinRoom(ctx context.Context, accessToken, roomID string) error {
u := fmt.Sprintf("%s/api-room/api/v1/room/%s/join", apiBase, roomID)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewReader([]byte("{}")))
if err != nil {
return fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("User-Agent", "Mozilla/5.0 (Linux x86_64)")
client := protect.NewHTTPClient()
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("do request: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(resp.Body)
return fmt.Errorf("%w: %d %s", errJoinRoom, resp.StatusCode, b)
}
return nil
}
func getToken(ctx context.Context, accessToken, roomID, displayName string) (string, error) {
u := fmt.Sprintf("%s/api-room-manager/api/v1/room/%s/token", apiBase, roomID)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return "", fmt.Errorf("create request: %w", err)
}
q := req.URL.Query()
q.Add("deviceType", "PARTICIPANT_DEVICE_TYPE_WEB_DESKTOP")
q.Add("displayName", displayName)
req.URL.RawQuery = q.Encode()
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("User-Agent", "Mozilla/5.0 (Linux x86_64)")
client := protect.NewHTTPClient()
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("do request: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("%w: %d %s", errGetToken, resp.StatusCode, b)
}
var res tokenResponse
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return "", fmt.Errorf("decode response: %w", err)
}
return res.RoomToken, nil
}