// 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 }