Skip to content

Commit

Permalink
Implement API handler to fetch session config
Browse files Browse the repository at this point in the history
  • Loading branch information
streamer45 committed Jan 8, 2025
1 parent 290c5ce commit ed6f914
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 48 deletions.
76 changes: 76 additions & 0 deletions service/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import (
"log"
"net"
"net/http"
"net/url"
"sync"
"time"

"github.com/mattermost/rtcd/service/rtc"
"github.com/mattermost/rtcd/service/ws"
)

Expand All @@ -38,6 +40,52 @@ type Client struct {
wg sync.WaitGroup
}

type ClientConfig struct {
httpURL string
wsURL string

ClientID string
AuthKey string
URL string
ReconnectInterval time.Duration
}

func (c *ClientConfig) Parse() error {
if c.URL == "" {
return fmt.Errorf("invalid URL value: should not be empty")
}

u, err := url.Parse(c.URL)
if err != nil {
return fmt.Errorf("failed to parse url: %w", err)
}

if u.Host == "" {
return fmt.Errorf("invalid url host: should not be empty")
}

switch u.Scheme {
case "http":
c.httpURL = c.URL
u.Scheme = "ws"
u.Path = "/ws"
c.wsURL = u.String()
case "https":
c.httpURL = c.URL
u.Scheme = "wss"
u.Path = "/ws"
c.wsURL = u.String()
default:
return fmt.Errorf("invalid url scheme: %q is not valid", u.Scheme)
}

if c.ReconnectInterval <= 0 {
c.ReconnectInterval = defaultReconnectInterval
}

return nil
}

func NewClient(cfg ClientConfig, opts ...ClientOption) (*Client, error) {
var c Client

Expand Down Expand Up @@ -408,3 +456,31 @@ func (c *Client) GetSystemInfo() (SystemInfo, error) {

return info, nil
}

func (c *Client) GetSession(callID, sessionID string) (rtc.SessionConfig, int, error) {
reqURL := fmt.Sprintf("%s/calls/%s/sessions/%s", c.cfg.httpURL, callID, sessionID)

req, err := http.NewRequest("GET", reqURL, nil)
if err != nil {
return rtc.SessionConfig{}, 0, fmt.Errorf("failed to build request: %w", err)
}
req.SetBasicAuth(c.cfg.ClientID, c.cfg.AuthKey)

resp, err := c.httpClient.Do(req)
if err != nil {
return rtc.SessionConfig{}, 0, fmt.Errorf("http request failed: %w", err)
}
defer resp.Body.Close()

var cfg rtc.SessionConfig

if resp.StatusCode != http.StatusOK {
return rtc.SessionConfig{}, resp.StatusCode, fmt.Errorf("request failed with status %s", resp.Status)
}

if err := json.NewDecoder(resp.Body).Decode(&cfg); err != nil {
return rtc.SessionConfig{}, resp.StatusCode, fmt.Errorf("decoding http response failed: %w", err)
}

return cfg, resp.StatusCode, nil
}
100 changes: 100 additions & 0 deletions service/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"net"
"net/http"
"runtime"
"sync"
"sync/atomic"
Expand All @@ -16,6 +17,7 @@ import (

"github.com/mattermost/rtcd/service/auth"
"github.com/mattermost/rtcd/service/random"
"github.com/mattermost/rtcd/service/rtc"
"github.com/mattermost/rtcd/service/ws"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -780,3 +782,101 @@ func TestClientGetSystemInfo(t *testing.T) {
require.NotZero(t, info.CPULoad)
})
}

func TestClientGetSession(t *testing.T) {
th := SetupTestHelper(t, nil)
defer th.Teardown()

// register client
authKey := "Nl9OZthX5cMJz5a_HmU3kQJ4pHIIlohr"
err := th.srvc.auth.Register("clientA", authKey)
require.NoError(t, err)

t.Run("unauthorized", func(t *testing.T) {
c, err := NewClient(ClientConfig{
URL: th.apiURL,
})
require.NoError(t, err)
require.NotNil(t, c)
defer c.Close()

cfg, code, err := c.GetSession("callID", "sessionID")
require.EqualError(t, err, "request failed with status 401 Unauthorized")
require.Empty(t, cfg)
require.Equal(t, http.StatusUnauthorized, code)
})

t.Run("no call ongoing", func(t *testing.T) {
c, err := NewClient(ClientConfig{
URL: th.apiURL,
ClientID: "clientA",
AuthKey: authKey,
})
require.NoError(t, err)
require.NotNil(t, c)
defer c.Close()

cfg, code, err := c.GetSession("callID", "sessionID")
require.EqualError(t, err, "request failed with status 404 Not Found")
require.Empty(t, cfg)
require.Equal(t, http.StatusNotFound, code)
})

t.Run("no session found", func(t *testing.T) {
c, err := NewClient(ClientConfig{
URL: th.apiURL,
ClientID: "clientA",
AuthKey: authKey,
})
require.NoError(t, err)
require.NotNil(t, c)
defer c.Close()

cfg := rtc.SessionConfig{
GroupID: "clientA",
CallID: "callIDA",
UserID: "userID",
SessionID: "sessionID",
}
err = th.srvc.rtcServer.InitSession(cfg, nil)
require.NoError(t, err)
defer func() {
err := th.srvc.rtcServer.CloseSession("sessionID")
require.NoError(t, err)
}()

cfg, code, err := c.GetSession("callID", "sessionID")
require.EqualError(t, err, "request failed with status 404 Not Found")
require.Empty(t, cfg)
require.Equal(t, http.StatusNotFound, code)
})

t.Run("session found", func(t *testing.T) {
c, err := NewClient(ClientConfig{
URL: th.apiURL,
ClientID: "clientA",
AuthKey: authKey,
})
require.NoError(t, err)
require.NotNil(t, c)
defer c.Close()

cfg := rtc.SessionConfig{
GroupID: "clientA",
CallID: "callIDA",
UserID: "userID",
SessionID: "sessionID",
}
err = th.srvc.rtcServer.InitSession(cfg, nil)
require.NoError(t, err)
defer func() {
err := th.srvc.rtcServer.CloseSession("sessionID")
require.NoError(t, err)
}()

sessionCfg, code, err := c.GetSession("callIDA", "sessionID")
require.NoError(t, err)
require.Equal(t, cfg, sessionCfg)
require.Equal(t, http.StatusOK, code)
})
}
48 changes: 0 additions & 48 deletions service/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ package service

import (
"fmt"
"net/url"
"time"

"github.com/mattermost/rtcd/service/auth"

Expand Down Expand Up @@ -105,49 +103,3 @@ func (c StoreConfig) IsValid() error {
}
return nil
}

type ClientConfig struct {
httpURL string
wsURL string

ClientID string
AuthKey string
URL string
ReconnectInterval time.Duration
}

func (c *ClientConfig) Parse() error {
if c.URL == "" {
return fmt.Errorf("invalid URL value: should not be empty")
}

u, err := url.Parse(c.URL)
if err != nil {
return fmt.Errorf("failed to parse url: %w", err)
}

if u.Host == "" {
return fmt.Errorf("invalid url host: should not be empty")
}

switch u.Scheme {
case "http":
c.httpURL = c.URL
u.Scheme = "ws"
u.Path = "/ws"
c.wsURL = u.String()
case "https":
c.httpURL = c.URL
u.Scheme = "wss"
u.Path = "/ws"
c.wsURL = u.String()
default:
return fmt.Errorf("invalid url scheme: %q is not valid", u.Scheme)
}

if c.ReconnectInterval <= 0 {
c.ReconnectInterval = defaultReconnectInterval
}

return nil
}
19 changes: 19 additions & 0 deletions service/rtc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,3 +413,22 @@ func (s *Server) handleDCMessage(data []byte, us *session, dataCh *webrtc.DataCh

return nil
}

func (s *Server) GetSessionConfig(groupID, callID, sessionID string) (SessionConfig, error) {
group := s.getGroup(groupID)
if group == nil {
return SessionConfig{}, fmt.Errorf("group not found")
}

call := group.getCall(callID)
if call == nil {
return SessionConfig{}, fmt.Errorf("call not found")
}

session := call.getSession(sessionID)
if session == nil {
return SessionConfig{}, fmt.Errorf("session not found")
}

return session.cfg, nil
}
1 change: 1 addition & 0 deletions service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func New(cfg Config) (*Service, error) {
s.apiServer.RegisterHandleFunc("/login", s.loginClient)
s.apiServer.RegisterHandleFunc("/register", s.registerClient)
s.apiServer.RegisterHandleFunc("/unregister", s.unregisterClient)
s.apiServer.RegisterHandleFunc("/calls/{callID}/sessions/{sessionID}", s.handleGetSession)
s.apiServer.RegisterHandler("/ws", s.wsServer)

if runtime.GOOS != "darwin" {
Expand Down
52 changes: 52 additions & 0 deletions service/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) 2022-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

package service

import (
"encoding/json"
"fmt"
"net/http"

"github.com/mattermost/mattermost/server/public/shared/mlog"
)

func (s *Service) handleGetSession(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet {
http.NotFound(w, req)
return
}

clientID, code, err := s.authHandler(w, req)
if err != nil {
s.log.Error("failed to authenticate", mlog.Err(err), mlog.Int("code", code))
}

if clientID == "" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}

callID := req.PathValue("callID")
if callID == "" {
http.Error(w, "callID is required", http.StatusBadRequest)
return
}

sessionID := req.PathValue("sessionID")
if sessionID == "" {
http.Error(w, "sessionID is required", http.StatusBadRequest)
return
}

cfg, err := s.rtcServer.GetSessionConfig(clientID, callID, sessionID)
if err != nil {
http.Error(w, fmt.Sprintf("failed to get session config: %s", err.Error()), http.StatusNotFound)
return
}

w.Header().Add("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(cfg); err != nil {
s.log.Error("failed to encode data", mlog.Err(err))
}
}

0 comments on commit ed6f914

Please sign in to comment.