package client import ( "context" "fmt" pb "github.com/sorti/openspeak/pkg/api/openspeak/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" ) // GRPCClient wraps gRPC service clients type GRPCClient struct { conn *grpc.ClientConn AuthClient pb.AuthServiceClient ChannelClient pb.ChannelServiceClient PresenceClient pb.PresenceServiceClient VoiceClient pb.VoiceServiceClient Token string } // NewGRPCClient creates a new gRPC client connection func NewGRPCClient(host string, port int) (*GRPCClient, error) { addr := fmt.Sprintf("%s:%d", host, port) conn, err := grpc.Dial( addr, grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { return nil, fmt.Errorf("failed to connect to gRPC server: %w", err) } return &GRPCClient{ conn: conn, AuthClient: pb.NewAuthServiceClient(conn), ChannelClient: pb.NewChannelServiceClient(conn), PresenceClient: pb.NewPresenceServiceClient(conn), VoiceClient: pb.NewVoiceServiceClient(conn), }, nil } // Close closes the gRPC connection func (c *GRPCClient) Close() error { if c.conn != nil { return c.conn.Close() } return nil } // ContextWithToken returns a context with the auth token in metadata func (c *GRPCClient) ContextWithToken(ctx context.Context) context.Context { if c.Token == "" { return ctx } return metadata.AppendToOutgoingContext(ctx, "authorization", c.Token) } // Login authenticates with the server using a token func (c *GRPCClient) Login(ctx context.Context, token string) (*pb.LoginResponse, error) { req := &pb.LoginRequest{Token: token} resp, err := c.AuthClient.Login(ctx, req) if err != nil { return nil, err } // Store token for future requests c.Token = token return resp, nil } // ValidateToken validates a token func (c *GRPCClient) ValidateToken(ctx context.Context, token string) (*pb.ValidateTokenResponse, error) { req := &pb.ValidateTokenRequest{Token: token} return c.AuthClient.ValidateToken(ctx, req) } // GetMyPermissions retrieves user permissions func (c *GRPCClient) GetMyPermissions(ctx context.Context) (*pb.GetMyPermissionsResponse, error) { ctx = c.ContextWithToken(ctx) req := &pb.GetMyPermissionsRequest{} return c.AuthClient.GetMyPermissions(ctx, req) } // CreateChannel creates a new channel func (c *GRPCClient) CreateChannel(ctx context.Context, name, description string, isPublic bool, maxUsers int32) (*pb.CreateChannelResponse, error) { ctx = c.ContextWithToken(ctx) req := &pb.CreateChannelRequest{ Name: name, Description: description, IsPublic: isPublic, MaxUsers: maxUsers, } return c.ChannelClient.CreateChannel(ctx, req) } // ListChannels lists all channels func (c *GRPCClient) ListChannels(ctx context.Context) (*pb.ListChannelsResponse, error) { ctx = c.ContextWithToken(ctx) req := &pb.ListChannelsRequest{} return c.ChannelClient.ListChannels(ctx, req) } // GetChannel retrieves a specific channel func (c *GRPCClient) GetChannel(ctx context.Context, channelID string) (*pb.Channel, error) { ctx = c.ContextWithToken(ctx) req := &pb.GetChannelRequest{ChannelId: channelID} return c.ChannelClient.GetChannel(ctx, req) } // JoinChannel joins a channel func (c *GRPCClient) JoinChannel(ctx context.Context, channelID string) (*pb.JoinChannelResponse, error) { ctx = c.ContextWithToken(ctx) req := &pb.JoinChannelRequest{ChannelId: channelID} return c.ChannelClient.JoinChannel(ctx, req) } // LeaveChannel leaves a channel func (c *GRPCClient) LeaveChannel(ctx context.Context, channelID string) (*pb.Status, error) { ctx = c.ContextWithToken(ctx) req := &pb.LeaveChannelRequest{ChannelId: channelID} return c.ChannelClient.LeaveChannel(ctx, req) } // ListMembers lists members of a channel func (c *GRPCClient) ListMembers(ctx context.Context, channelID string) (*pb.ListMembersResponse, error) { ctx = c.ContextWithToken(ctx) req := &pb.ListMembersRequest{ChannelId: channelID} return c.ChannelClient.ListMembers(ctx, req) } // GetMyPresence retrieves current user presence func (c *GRPCClient) GetMyPresence(ctx context.Context) (*pb.UserPresence, error) { ctx = c.ContextWithToken(ctx) req := &pb.GetPresenceRequest{} return c.PresenceClient.GetMyPresence(ctx, req) } // GetUserPresence retrieves another user's presence func (c *GRPCClient) GetUserPresence(ctx context.Context, userID string) (*pb.UserPresence, error) { ctx = c.ContextWithToken(ctx) req := &pb.GetPresenceRequest{UserId: userID} return c.PresenceClient.GetUserPresence(ctx, req) } // SetPresenceStatus updates presence status func (c *GRPCClient) SetPresenceStatus(ctx context.Context, status pb.PresenceStatus) (*pb.UserPresence, error) { ctx = c.ContextWithToken(ctx) req := &pb.SetPresenceStatusRequest{Status: status} return c.PresenceClient.SetPresenceStatus(ctx, req) } // SetMuteStatus updates mute status func (c *GRPCClient) SetMuteStatus(ctx context.Context, micMuted, speakerMuted bool) (*pb.UserPresence, error) { ctx = c.ContextWithToken(ctx) req := &pb.SetMuteStatusRequest{ MicrophoneMuted: micMuted, SpeakerMuted: speakerMuted, } return c.PresenceClient.SetMuteStatus(ctx, req) } // ReportActivity reports user activity func (c *GRPCClient) ReportActivity(ctx context.Context) (*pb.Status, error) { ctx = c.ContextWithToken(ctx) req := &pb.ReportActivityRequest{} return c.PresenceClient.ReportActivity(ctx, req) } // ListChannelMembers lists members with presence info func (c *GRPCClient) ListChannelMembers(ctx context.Context, channelID string) (*pb.ListChannelMembersResponse, error) { ctx = c.ContextWithToken(ctx) req := &pb.ListChannelMembersRequest{ChannelId: channelID} return c.PresenceClient.ListChannelMembers(ctx, req) }