mirror of
https://github.com/openlibrecommunity/olcrtc.git
synced 2026-06-03 02:49:47 +00:00
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>
192 lines
5.4 KiB
Go
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
|
|
}
|