2026-04-29 23:07:50 +08:00

111 lines
3.7 KiB
Go

// Package tencentrtc contains the gateway-owned Tencent RTC token boundary.
package tencentrtc
import (
"fmt"
"strconv"
"time"
"hyapp/pkg/roomid"
"hyapp/pkg/tencentsig"
)
const (
// RoomIDTypeString means clients must use TRTC strRoomId, not numeric roomId.
RoomIDTypeString = "string"
// AppSceneVoiceChatRoom is the first supported RTC scene for room audio.
AppSceneVoiceChatRoom = "voice_chat_room"
// RoleAudience is the safe default; publishing audio requires a later MicUp flow.
RoleAudience = "audience"
)
// TokenConfig describes the explicit RTC application used by gateway token signing.
type TokenConfig struct {
// Enabled must be true before gateway issues RTC UserSig credentials.
Enabled bool
// SDKAppID is the Tencent RTC application ID.
SDKAppID int64
// SecretKey signs UserSig and must stay server-side.
SecretKey string
// TTL controls the RTC UserSig validity window.
TTL time.Duration
// RoomIDType is fixed to "string" in v1 to avoid TRTC roomId/strRoomId drift.
RoomIDType string
// AppScene is fixed to voice_chat_room until product introduces more RTC scenes.
AppScene string
}
// TokenResult is returned by gateway-service after room-service presence is verified.
type TokenResult struct {
SDKAppID int64 `json:"sdk_app_id"`
UserID string `json:"user_id"`
RTCUserID string `json:"rtc_user_id"`
UserSig string `json:"user_sig"`
ExpireAtMS int64 `json:"expire_at_ms"`
RoomID string `json:"room_id"`
RTCRoomID string `json:"rtc_room_id"`
RTCRoomIDType string `json:"rtc_room_id_type"`
StrRoomID string `json:"str_room_id"`
AppScene string `json:"app_scene"`
Role string `json:"role"`
}
// FormatUserID converts the internal immutable user_id to Tencent RTC UserID.
func FormatUserID(userID int64) string {
// Decimal user_id matches Tencent IM identifier and keeps troubleshooting one-to-one.
return strconv.FormatInt(userID, 10)
}
// GenerateToken signs a Tencent RTC UserSig and attaches the explicit string room mapping.
func GenerateToken(cfg TokenConfig, userID int64, targetRoomID string, now time.Time) (TokenResult, error) {
if err := ValidateConfig(cfg); err != nil {
return TokenResult{}, err
}
if userID <= 0 {
// RTC UserID must always come from an authenticated internal user_id.
return TokenResult{}, fmt.Errorf("tencent rtc user_id is invalid")
}
if !roomid.ValidStringID(targetRoomID) {
// This guard protects package callers; gateway maps this case to INVALID_ARGUMENT first.
return TokenResult{}, fmt.Errorf("room_id is invalid for tencent rtc")
}
rtcUserID := FormatUserID(userID)
userSig, err := tencentsig.GenerateUserSig(tencentsig.Config{
SDKAppID: cfg.SDKAppID,
SecretKey: cfg.SecretKey,
TTL: cfg.TTL,
}, rtcUserID, now)
if err != nil {
return TokenResult{}, err
}
return TokenResult{
SDKAppID: userSig.SDKAppID,
UserID: rtcUserID,
RTCUserID: rtcUserID,
UserSig: userSig.UserSig,
ExpireAtMS: userSig.ExpireAtMS,
RoomID: targetRoomID,
RTCRoomID: targetRoomID,
RTCRoomIDType: RoomIDTypeString,
StrRoomID: targetRoomID,
AppScene: AppSceneVoiceChatRoom,
Role: RoleAudience,
}, nil
}
// ValidateConfig verifies gateway can safely sign RTC tokens before calling room-service.
func ValidateConfig(cfg TokenConfig) error {
if !cfg.Enabled || cfg.SDKAppID <= 0 || cfg.SecretKey == "" || cfg.TTL <= 0 {
// Gateway must fail closed when RTC credentials are incomplete or disabled.
return fmt.Errorf("tencent rtc token config is incomplete")
}
if cfg.RoomIDType != RoomIDTypeString || cfg.AppScene != AppSceneVoiceChatRoom {
// The first RTC version only supports TRTC strRoomId for voice chat rooms.
return fmt.Errorf("tencent rtc policy is unsupported")
}
return nil
}