Return response if session tries to join room again.

This commit is contained in:
Joachim Bauch 2023-09-05 15:24:36 +02:00
parent 92690f4613
commit 35135433c2
No known key found for this signature in database
GPG key ID: 77C1D22D53E15F02
5 changed files with 171 additions and 6 deletions

View file

@ -24,6 +24,7 @@ package signaling
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"net/url" "net/url"
"sort" "sort"
"strings" "strings"
@ -220,9 +221,9 @@ func (r *ServerMessage) String() string {
} }
type Error struct { type Error struct {
Code string `json:"code"` Code string `json:"code"`
Message string `json:"message"` Message string `json:"message"`
Details interface{} `json:"details,omitempty"` Details json.RawMessage `json:"details,omitempty"`
} }
func NewError(code string, message string) *Error { func NewError(code string, message string) *Error {
@ -230,10 +231,19 @@ func NewError(code string, message string) *Error {
} }
func NewErrorDetail(code string, message string, details interface{}) *Error { func NewErrorDetail(code string, message string, details interface{}) *Error {
var rawDetails json.RawMessage
if details != nil {
var err error
if rawDetails, err = json.Marshal(details); err != nil {
log.Printf("Could not marshal details %+v for error %s with %s: %s", details, code, message, err)
return NewError("internal_error", "Could not marshal error details")
}
}
return &Error{ return &Error{
Code: code, Code: code,
Message: message, Message: message,
Details: details, Details: rawDetails,
} }
} }
@ -511,6 +521,10 @@ type RoomServerMessage struct {
Properties *json.RawMessage `json:"properties,omitempty"` Properties *json.RawMessage `json:"properties,omitempty"`
} }
type RoomErrorDetails struct {
Room *RoomServerMessage `json:"room"`
}
// Type "message" // Type "message"
const ( const (

View file

@ -417,6 +417,36 @@ func (s *ClientSession) SubscribeEvents() error {
return s.events.RegisterSessionListener(s.publicId, s.backend, s) return s.events.RegisterSessionListener(s.publicId, s.backend, s)
} }
func (s *ClientSession) UpdateRoomSessionId(roomSessionId string) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.roomSessionId == roomSessionId {
return nil
}
if err := s.hub.roomSessions.SetRoomSession(s, roomSessionId); err != nil {
return err
}
if roomSessionId != "" {
if room := s.GetRoom(); room != nil {
log.Printf("Session %s updated room session id to %s in room %s", s.PublicId(), roomSessionId, room.Id())
} else {
log.Printf("Session %s updated room session id to %s in unknown room", s.PublicId(), roomSessionId)
}
} else {
if room := s.GetRoom(); room != nil {
log.Printf("Session %s cleared room session id in room %s", s.PublicId(), room.Id())
} else {
log.Printf("Session %s cleared room session id in unknown room", s.PublicId())
}
}
s.roomSessionId = roomSessionId
return nil
}
func (s *ClientSession) SubscribeRoomEvents(roomid string, roomSessionId string) error { func (s *ClientSession) SubscribeRoomEvents(roomid string, roomSessionId string) error {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()

View file

@ -460,6 +460,26 @@ Message format (Server -> Client):
the current room or the properties of a room change. the current room or the properties of a room change.
Message format (Server -> Client if already joined before):
{
"id": "unique-request-id-from-request",
"type": "error",
"error": {
"code": "already_joined",
"message": "Human readable error message",
"details": {
"roomid": "the-room-id",
"properties": {
...additional room properties...
}
}
}
}
- Sent if a client tried to join a room it is already in.
### Backend validation ### Backend validation
Rooms are managed by the Nextcloud backend, so the signaling server has to Rooms are managed by the Nextcloud backend, so the signaling server has to

18
hub.go
View file

@ -1256,6 +1256,24 @@ func (h *Hub) processRoom(client *Client, message *ClientMessage) {
if session != nil { if session != nil {
if room := h.getRoomForBackend(roomId, session.Backend()); room != nil && room.HasSession(session) { if room := h.getRoomForBackend(roomId, session.Backend()); room != nil && room.HasSession(session) {
// Session already is in that room, no action needed. // Session already is in that room, no action needed.
roomSessionId := message.Room.SessionId
if roomSessionId == "" {
// TODO(jojo): Better make the session id required in the request.
log.Printf("User did not send a room session id, assuming session %s", session.PublicId())
roomSessionId = session.PublicId()
}
if err := session.UpdateRoomSessionId(roomSessionId); err != nil {
log.Printf("Error updating room session id for session %s: %s", session.PublicId(), err)
}
session.SendMessage(message.NewErrorServerMessage(
NewErrorDetail("already_joined", "Already joined this room.", &RoomErrorDetails{
Room: &RoomServerMessage{
RoomId: room.id,
Properties: room.properties,
},
}),
))
return return
} }
} }

View file

@ -22,6 +22,7 @@
package signaling package signaling
import ( import (
"bytes"
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/ed25519" "crypto/ed25519"
@ -58,6 +59,10 @@ const (
testTimeout = 10 * time.Second testTimeout = 10 * time.Second
) )
var (
testRoomProperties = []byte("{\"prop1\":\"value1\"}")
)
var ( var (
clusteredTests = []string{ clusteredTests = []string{
"local", "local",
@ -403,8 +408,9 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re
response := &BackendClientResponse{ response := &BackendClientResponse{
Type: "room", Type: "room",
Room: &BackendClientRoomResponse{ Room: &BackendClientRoomResponse{
Version: BackendVersion, Version: BackendVersion,
RoomId: request.Room.RoomId, RoomId: request.Room.RoomId,
Properties: (*json.RawMessage)(&testRoomProperties),
}, },
} }
switch request.Room.RoomId { switch request.Room.RoomId {
@ -2628,6 +2634,83 @@ func TestJoinRoom(t *testing.T) {
} }
} }
func TestJoinRoomTwice(t *testing.T) {
hub, _, _, server := CreateHubForTest(t)
client := NewTestClient(t, server, hub)
defer client.CloseWithBye()
if err := client.SendHello(testDefaultUserId); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
defer cancel()
hello, err := client.RunUntilHello(ctx)
if err != nil {
t.Fatal(err)
}
// Join room by id.
roomId := "test-room"
if room, err := client.JoinRoom(ctx, roomId); err != nil {
t.Fatal(err)
} else if room.Room.RoomId != roomId {
t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId)
} else if !bytes.Equal(testRoomProperties, *room.Room.Properties) {
t.Fatalf("Expected room properties %s, got %s", string(testRoomProperties), string(*room.Room.Properties))
}
// We will receive a "joined" event.
if err := client.RunUntilJoined(ctx, hello.Hello); err != nil {
t.Error(err)
}
msg := &ClientMessage{
Id: "ABCD",
Type: "room",
Room: &RoomClientMessage{
RoomId: roomId,
SessionId: roomId + "-" + client.publicId + "-2",
},
}
if err := client.WriteJSON(msg); err != nil {
t.Fatal(err)
}
message, err := client.RunUntilMessage(ctx)
if err != nil {
t.Fatal(err)
}
if err := checkUnexpectedClose(err); err != nil {
t.Fatal(err)
}
if msg.Id != message.Id {
t.Errorf("expected message id %s, got %s", msg.Id, message.Id)
} else if err := checkMessageType(message, "error"); err != nil {
t.Fatal(err)
} else if expected := "already_joined"; message.Error.Code != expected {
t.Errorf("expected error %s, got %s", expected, message.Error.Code)
} else if message.Error.Details == nil {
t.Fatal("expected error details")
}
var roomMsg RoomErrorDetails
if err := json.Unmarshal(message.Error.Details, &roomMsg); err != nil {
t.Fatal(err)
} else if roomMsg.Room == nil {
t.Fatalf("expected room details, got %+v", message)
}
if roomMsg.Room.RoomId != roomId {
t.Fatalf("Expected room %s, got %+v", roomId, roomMsg.Room)
} else if !bytes.Equal(testRoomProperties, *roomMsg.Room.Properties) {
t.Fatalf("Expected room properties %s, got %s", string(testRoomProperties), string(*roomMsg.Room.Properties))
}
}
func TestExpectAnonymousJoinRoom(t *testing.T) { func TestExpectAnonymousJoinRoom(t *testing.T) {
hub, _, _, server := CreateHubForTest(t) hub, _, _, server := CreateHubForTest(t)