192 changed files with 38155 additions and 3574 deletions
@ -1,10 +1,27 @@
@@ -1,10 +1,27 @@
|
||||
{ |
||||
"smarttabs": true, |
||||
"onecase": true, |
||||
"asi": true, |
||||
"bitwise": false, |
||||
"browser": true, |
||||
"camelcase": false, // Disabled for now. |
||||
"curly": true, |
||||
"forin": true, |
||||
"trailing": true, |
||||
"asi": true, |
||||
"immed": true, |
||||
"latedef": true, |
||||
"maxlen": 1000, |
||||
"quotmark": false |
||||
"newcap": true, |
||||
"noarg": true, |
||||
"noempty": false, // Disabled for now. |
||||
"nonew": true, |
||||
"onecase": true, |
||||
"predef": [ |
||||
"define", |
||||
"require", |
||||
"console" |
||||
], |
||||
"quotmark": false, |
||||
"smarttabs": true, |
||||
"trailing": true, |
||||
"undef": true, |
||||
"unused": false, // Disabled for now |
||||
"globalstrict": true |
||||
} |
@ -0,0 +1,284 @@
@@ -0,0 +1,284 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"log" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
maxConferenceSize = 100 |
||||
) |
||||
|
||||
type ChannellingAPI interface { |
||||
OnConnect(Client, *Session) |
||||
OnIncoming(ResponseSender, *Session, *DataIncoming) |
||||
OnDisconnect(*Session) |
||||
} |
||||
|
||||
type channellingAPI struct { |
||||
*Config |
||||
RoomStatusManager |
||||
SessionEncoder |
||||
SessionManager |
||||
StatsCounter |
||||
ContactManager |
||||
TurnDataCreator |
||||
Unicaster |
||||
Broadcaster |
||||
buddyImages ImageCache |
||||
} |
||||
|
||||
func NewChannellingAPI(config *Config, roomStatus RoomStatusManager, sessionEncoder SessionEncoder, sessionManager SessionManager, statsCounter StatsCounter, contactManager ContactManager, turnDataCreator TurnDataCreator, unicaster Unicaster, broadcaster Broadcaster, buddyImages ImageCache) ChannellingAPI { |
||||
return &channellingAPI{ |
||||
config, |
||||
roomStatus, |
||||
sessionEncoder, |
||||
sessionManager, |
||||
statsCounter, |
||||
contactManager, |
||||
turnDataCreator, |
||||
unicaster, |
||||
broadcaster, |
||||
buddyImages, |
||||
} |
||||
} |
||||
|
||||
func (api *channellingAPI) OnConnect(client Client, session *Session) { |
||||
api.Unicaster.OnConnect(client, session) |
||||
api.SendSelf(client, session) |
||||
} |
||||
|
||||
func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *DataIncoming) { |
||||
switch msg.Type { |
||||
case "Self": |
||||
api.SendSelf(c, session) |
||||
case "Hello": |
||||
//log.Println("Hello", msg.Hello, c.Index())
|
||||
// TODO(longsleep): Filter room id and user agent.
|
||||
api.UpdateSession(session, &SessionUpdate{Types: []string{"Ua"}, Ua: msg.Hello.Ua}) |
||||
if session.Hello && session.Roomid != msg.Hello.Id { |
||||
api.LeaveRoom(session) |
||||
api.Broadcast(session, session.DataSessionLeft("soft")) |
||||
} |
||||
|
||||
// NOTE(lcooper): Iid filtered for compatibility's sake.
|
||||
// Evaluate sending unconditionally when supported by all clients.
|
||||
if room, err := api.JoinRoom(msg.Hello.Id, msg.Hello.Credentials, session, c); err == nil { |
||||
session.Hello = true |
||||
session.Roomid = msg.Hello.Id |
||||
if msg.Iid != "" { |
||||
c.Reply(msg.Iid, &DataWelcome{ |
||||
Type: "Welcome", |
||||
Room: room, |
||||
Users: api.RoomUsers(session), |
||||
}) |
||||
} |
||||
api.Broadcast(session, session.DataSessionJoined()) |
||||
} else { |
||||
session.Hello = false |
||||
if msg.Iid != "" { |
||||
c.Reply(msg.Iid, err) |
||||
} |
||||
} |
||||
case "Offer": |
||||
// TODO(longsleep): Validate offer
|
||||
api.Unicast(session, msg.Offer.To, msg.Offer) |
||||
case "Candidate": |
||||
// TODO(longsleep): Validate candidate
|
||||
api.Unicast(session, msg.Candidate.To, msg.Candidate) |
||||
case "Answer": |
||||
// TODO(longsleep): Validate Answer
|
||||
api.Unicast(session, msg.Answer.To, msg.Answer) |
||||
case "Users": |
||||
if session.Hello { |
||||
sessions := &DataSessions{Type: "Users", Users: api.RoomUsers(session)} |
||||
c.Reply(msg.Iid, sessions) |
||||
} |
||||
case "Authentication": |
||||
st := msg.Authentication.Authentication |
||||
if st == nil { |
||||
return |
||||
} |
||||
|
||||
if err := api.Authenticate(session, st, ""); err == nil { |
||||
log.Println("Authentication success", session.Userid) |
||||
api.SendSelf(c, session) |
||||
api.BroadcastSessionStatus(session) |
||||
} else { |
||||
log.Println("Authentication failed", err, st.Userid, st.Nonce) |
||||
} |
||||
case "Bye": |
||||
api.Unicast(session, msg.Bye.To, msg.Bye) |
||||
case "Status": |
||||
//log.Println("Status", msg.Status)
|
||||
api.UpdateSession(session, &SessionUpdate{Types: []string{"Status"}, Status: msg.Status.Status}) |
||||
api.BroadcastSessionStatus(session) |
||||
case "Chat": |
||||
// TODO(longsleep): Limit sent chat messages per incoming connection.
|
||||
if !msg.Chat.Chat.NoEcho { |
||||
api.Unicast(session, session.Id, msg.Chat) |
||||
} |
||||
msg.Chat.Chat.Time = time.Now().Format(time.RFC3339) |
||||
if msg.Chat.To == "" { |
||||
// TODO(longsleep): Check if chat broadcast is allowed.
|
||||
if session.Hello { |
||||
api.CountBroadcastChat() |
||||
api.Broadcast(session, msg.Chat) |
||||
} |
||||
} else { |
||||
if msg.Chat.Chat.Status != nil && msg.Chat.Chat.Status.ContactRequest != nil { |
||||
if err := api.contactrequestHandler(session, msg.Chat.To, msg.Chat.Chat.Status.ContactRequest); err != nil { |
||||
log.Println("Ignoring invalid contact request.", err) |
||||
return |
||||
} |
||||
msg.Chat.Chat.Status.ContactRequest.Userid = session.Userid() |
||||
} |
||||
if msg.Chat.Chat.Status == nil { |
||||
api.CountUnicastChat() |
||||
} |
||||
|
||||
api.Unicast(session, msg.Chat.To, msg.Chat) |
||||
if msg.Chat.Chat.Mid != "" { |
||||
// Send out delivery confirmation status chat message.
|
||||
api.Unicast(session, session.Id, &DataChat{To: msg.Chat.To, Type: "Chat", Chat: &DataChatMessage{Mid: msg.Chat.Chat.Mid, Status: &DataChatStatus{State: "sent"}}}) |
||||
} |
||||
} |
||||
case "Conference": |
||||
// Check conference maximum size.
|
||||
if len(msg.Conference.Conference) > maxConferenceSize { |
||||
log.Println("Refusing to create conference above limit.", len(msg.Conference.Conference)) |
||||
} else { |
||||
// Send conference update to anyone.
|
||||
for _, id := range msg.Conference.Conference { |
||||
if id != session.Id { |
||||
api.Unicast(session, id, msg.Conference) |
||||
} |
||||
} |
||||
} |
||||
case "Alive": |
||||
c.Reply(msg.Iid, msg.Alive) |
||||
case "Sessions": |
||||
var users []*DataSession |
||||
switch msg.Sessions.Sessions.Type { |
||||
case "contact": |
||||
if userID, err := api.getContactID(session, msg.Sessions.Sessions.Token); err == nil { |
||||
users = api.GetUserSessions(session, userID) |
||||
} else { |
||||
log.Printf(err.Error()) |
||||
} |
||||
case "session": |
||||
id, err := session.attestation.Decode(msg.Sessions.Sessions.Token) |
||||
if err != nil { |
||||
log.Printf("Failed to decode incoming attestation", err, msg.Sessions.Sessions.Token) |
||||
break |
||||
} |
||||
session, ok := api.GetSession(id) |
||||
if !ok { |
||||
log.Printf("Cannot retrieve session for id %s", id) |
||||
break |
||||
} |
||||
users = make([]*DataSession, 1, 1) |
||||
users[0] = session.Data() |
||||
default: |
||||
log.Printf("Unkown incoming sessions request type %s", msg.Sessions.Sessions.Type) |
||||
} |
||||
|
||||
// TODO(lcooper): We ought to reply with a *DataError here if failed.
|
||||
if users != nil { |
||||
c.Reply(msg.Iid, &DataSessions{Type: "Sessions", Users: users, Sessions: msg.Sessions.Sessions}) |
||||
} |
||||
case "Room": |
||||
if room, err := api.UpdateRoom(session, msg.Room); err == nil { |
||||
api.Broadcast(session, room) |
||||
c.Reply(msg.Iid, room) |
||||
} else { |
||||
c.Reply(msg.Iid, err) |
||||
} |
||||
default: |
||||
log.Println("OnText unhandled message type", msg.Type) |
||||
} |
||||
} |
||||
|
||||
func (api *channellingAPI) OnDisconnect(session *Session) { |
||||
dsl := session.DataSessionLeft("hard") |
||||
if session.Hello { |
||||
api.LeaveRoom(session) |
||||
api.Broadcast(session, dsl) |
||||
} |
||||
|
||||
session.RunForAllSubscribers(func(session *Session) { |
||||
log.Println("Notifying subscriber that we are gone", session.Id, session.Id) |
||||
api.Unicast(session, session.Id, dsl) |
||||
}) |
||||
|
||||
api.Unicaster.OnDisconnect(session) |
||||
|
||||
api.buddyImages.Delete(session.Id) |
||||
} |
||||
|
||||
func (api *channellingAPI) SendSelf(c Responder, session *Session) { |
||||
token, err := api.EncodeSessionToken(session) |
||||
if err != nil { |
||||
log.Println("Error in OnRegister", err) |
||||
return |
||||
} |
||||
|
||||
log.Println("Created new session token", len(token), token) |
||||
self := &DataSelf{ |
||||
Type: "Self", |
||||
Id: session.Id, |
||||
Sid: session.Sid, |
||||
Userid: session.Userid(), |
||||
Suserid: api.EncodeSessionUserID(session), |
||||
Token: token, |
||||
Version: api.Version, |
||||
Turn: api.CreateTurnData(session), |
||||
Stun: api.StunURIs, |
||||
} |
||||
c.Reply("", self) |
||||
} |
||||
|
||||
func (api *channellingAPI) UpdateSession(session *Session, s *SessionUpdate) uint64 { |
||||
if s.Status != nil { |
||||
status, ok := s.Status.(map[string]interface{}) |
||||
if ok && status["buddyPicture"] != nil { |
||||
pic := status["buddyPicture"].(string) |
||||
if strings.HasPrefix(pic, "data:") { |
||||
imageId := api.buddyImages.Update(session.Id, pic[5:]) |
||||
if imageId != "" { |
||||
status["buddyPicture"] = "img:" + imageId |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return session.Update(s) |
||||
} |
||||
|
||||
func (api *channellingAPI) BroadcastSessionStatus(session *Session) { |
||||
if session.Hello { |
||||
api.Broadcast(session, session.DataSessionStatus()) |
||||
} |
||||
} |
@ -0,0 +1,240 @@
@@ -0,0 +1,240 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"errors" |
||||
"testing" |
||||
) |
||||
|
||||
type fakeClient struct { |
||||
replies map[string]interface{} |
||||
} |
||||
|
||||
func (fake *fakeClient) Send(_ Buffer) { |
||||
} |
||||
|
||||
func (fake *fakeClient) Reply(iid string, msg interface{}) { |
||||
if fake.replies == nil { |
||||
fake.replies = make(map[string]interface{}) |
||||
} |
||||
|
||||
fake.replies[iid] = msg |
||||
} |
||||
|
||||
type fakeRoomManager struct { |
||||
joinedRoomID string |
||||
leftRoomID string |
||||
roomUsers []*DataSession |
||||
joinedID string |
||||
joinError error |
||||
leftID string |
||||
broadcasts []interface{} |
||||
updatedRoom *DataRoom |
||||
updateError error |
||||
} |
||||
|
||||
func (fake *fakeRoomManager) RoomUsers(session *Session) []*DataSession { |
||||
return fake.roomUsers |
||||
} |
||||
|
||||
func (fake *fakeRoomManager) JoinRoom(id string, _ *DataRoomCredentials, session *Session, _ Sender) (*DataRoom, error) { |
||||
fake.joinedID = id |
||||
return &DataRoom{Name: id}, fake.joinError |
||||
} |
||||
|
||||
func (fake *fakeRoomManager) LeaveRoom(session *Session) { |
||||
fake.leftID = session.Roomid |
||||
} |
||||
|
||||
func (fake *fakeRoomManager) Broadcast(_ *Session, msg interface{}) { |
||||
fake.broadcasts = append(fake.broadcasts, msg) |
||||
} |
||||
|
||||
func (fake *fakeRoomManager) UpdateRoom(_ *Session, _ *DataRoom) (*DataRoom, error) { |
||||
return fake.updatedRoom, fake.updateError |
||||
} |
||||
|
||||
func assertReply(t *testing.T, client *fakeClient, iid string) interface{} { |
||||
msg, ok := client.replies[iid] |
||||
if !ok { |
||||
t.Fatalf("No response received for Iid %v", iid) |
||||
} |
||||
return msg |
||||
} |
||||
|
||||
func assertErrorReply(t *testing.T, client *fakeClient, iid, code string) { |
||||
err, ok := assertReply(t, client, iid).(*DataError) |
||||
if !ok { |
||||
t.Fatalf("Expected response message to be an Error") |
||||
} |
||||
|
||||
if err.Type != "Error" { |
||||
t.Error("Message did not have the correct type") |
||||
} |
||||
|
||||
if err.Code != code { |
||||
t.Errorf("Expected error code to be %v, but was %v", code, err.Code) |
||||
} |
||||
} |
||||
|
||||
func NewTestChannellingAPI() (ChannellingAPI, *fakeClient, *Session, *fakeRoomManager) { |
||||
client, roomManager, session := &fakeClient{}, &fakeRoomManager{}, &Session{} |
||||
return NewChannellingAPI(nil, roomManager, nil, nil, nil, nil, nil, nil, roomManager, nil), client, session, roomManager |
||||
} |
||||
|
||||
func Test_ChannellingAPI_OnIncoming_HelloMessage_JoinsTheSelectedRoom(t *testing.T) { |
||||
roomID, ua := "foobar", "unit tests" |
||||
api, client, session, roomManager := NewTestChannellingAPI() |
||||
|
||||
api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomID, Ua: ua}}) |
||||
|
||||
if roomManager.joinedID != roomID { |
||||
t.Errorf("Expected to have joined room %v, but got %v", roomID, roomManager.joinedID) |
||||
} |
||||
|
||||
if broadcastCount := len(roomManager.broadcasts); broadcastCount != 1 { |
||||
t.Fatalf("Expected 1 broadcast, but got %d", broadcastCount) |
||||
} |
||||
|
||||
dataSession, ok := roomManager.broadcasts[0].(*DataSession) |
||||
if !ok { |
||||
t.Fatal("Expected a session data broadcast") |
||||
} |
||||
|
||||
if dataSession.Ua != ua { |
||||
t.Errorf("Expected to have broadcasted a user agent of %v, but was %v", ua, dataSession.Ua) |
||||
} |
||||
} |
||||
|
||||
func Test_ChannellingAPI_OnIncoming_HelloMessage_LeavesAnyPreviouslyJoinedRooms(t *testing.T) { |
||||
roomID := "foobar" |
||||
api, client, session, roomManager := NewTestChannellingAPI() |
||||
|
||||
api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomID}}) |
||||
api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: "baz"}}) |
||||
|
||||
if roomManager.leftID != roomID { |
||||
t.Errorf("Expected to have left room %v, but got %v", roomID, roomManager.leftID) |
||||
} |
||||
|
||||
if broadcastCount := len(roomManager.broadcasts); broadcastCount != 3 { |
||||
t.Fatalf("Expected 3 broadcasts, but got %d", broadcastCount) |
||||
} |
||||
|
||||
dataSession, ok := roomManager.broadcasts[1].(*DataSession) |
||||
if !ok { |
||||
t.Fatal("Expected a session data broadcast") |
||||
} |
||||
|
||||
if status := "soft"; dataSession.Status != status { |
||||
t.Errorf("Expected to have broadcast a leave status of of %v, but was %v", status, dataSession.Status) |
||||
} |
||||
} |
||||
|
||||
func Test_ChannellingAPI_OnIncoming_HelloMessage_DoesNotJoinIfNotPermitted(t *testing.T) { |
||||
api, client, session, roomManager := NewTestChannellingAPI() |
||||
roomManager.joinError = errors.New("Can't enter this room") |
||||
|
||||
api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{}}) |
||||
|
||||
if broadcastCount := len(roomManager.broadcasts); broadcastCount != 0 { |
||||
t.Fatalf("Expected no broadcasts, but got %d", broadcastCount) |
||||
} |
||||
} |
||||
|
||||
func Test_ChannellingAPI_OnIncoming_HelloMessageWithAnIid_RespondsWithAWelcome(t *testing.T) { |
||||
iid, roomID := "foo", "a-room" |
||||
api, client, session, roomManager := NewTestChannellingAPI() |
||||
roomManager.roomUsers = []*DataSession{&DataSession{}} |
||||
|
||||
api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Iid: iid, Hello: &DataHello{Id: roomID}}) |
||||
|
||||
msg, ok := client.replies[iid] |
||||
if !ok { |
||||
t.Fatalf("No response received for Iid %v", iid) |
||||
} |
||||
|
||||
welcome, ok := msg.(*DataWelcome) |
||||
if !ok { |
||||
t.Fatalf("Expected response message %#v to be a Welcome", msg) |
||||
} |
||||
|
||||
if welcome.Type != "Welcome" { |
||||
t.Error("Message did not have the correct type") |
||||
} |
||||
|
||||
if welcome.Room == nil || welcome.Room.Name != roomID { |
||||
t.Errorf("Expected room with name %v, but got %#v", roomID, welcome.Room) |
||||
} |
||||
|
||||
if len(welcome.Users) != len(roomManager.roomUsers) { |
||||
t.Errorf("Expected to get users %#v, but was %#v", roomManager.roomUsers, welcome.Users) |
||||
} |
||||
} |
||||
|
||||
func Test_ChannellingAPI_OnIncoming_HelloMessageWithAnIid_RespondsWithAnErrorIfTheRoomCannotBeJoined(t *testing.T) { |
||||
iid := "foo" |
||||
api, client, session, roomManager := NewTestChannellingAPI() |
||||
roomManager.joinError = NewDataError("bad_join", "") |
||||
|
||||
api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Iid: iid, Hello: &DataHello{}}) |
||||
|
||||
assertErrorReply(t, client, iid, "bad_join") |
||||
} |
||||
|
||||
func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAndBroadcastsTheUpdatedRoom(t *testing.T) { |
||||
iid, roomName := "123", "foo" |
||||
api, client, session, roomManager := NewTestChannellingAPI() |
||||
roomManager.updatedRoom = &DataRoom{Name: "FOO"} |
||||
|
||||
api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Iid: "0", Hello: &DataHello{Id: roomName}}) |
||||
api.OnIncoming(client, session, &DataIncoming{Type: "Room", Iid: iid, Room: &DataRoom{Name: roomName}}) |
||||
|
||||
room, ok := assertReply(t, client, iid).(*DataRoom) |
||||
if !ok { |
||||
t.Fatalf("Expected response message to be a Room") |
||||
} |
||||
|
||||
if room.Name != roomManager.updatedRoom.Name { |
||||
t.Errorf("Expected updated room with name %v, but got %#v", roomManager.updatedRoom, room) |
||||
} |
||||
|
||||
if broadcastCount := len(roomManager.broadcasts); broadcastCount != 2 { |
||||
t.Fatalf("Expected 1 broadcasts, but got %d", broadcastCount) |
||||
} |
||||
|
||||
if _, ok := roomManager.broadcasts[1].(*DataRoom); !ok { |
||||
t.Fatal("Expected a room data broadcast") |
||||
} |
||||
} |
||||
|
||||
func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAnErrorIfUpdatingTheRoomFails(t *testing.T) { |
||||
iid, roomName := "123", "foo" |
||||
api, client, session, roomManager := NewTestChannellingAPI() |
||||
roomManager.updateError = NewDataError("a_room_error", "") |
||||
|
||||
api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Iid: "0", Hello: &DataHello{Id: roomName}}) |
||||
api.OnIncoming(client, session, &DataIncoming{Type: "Room", Iid: iid, Room: &DataRoom{Name: roomName}}) |
||||
|
||||
assertErrorReply(t, client, iid, "a_room_error") |
||||
} |
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"log" |
||||
) |
||||
|
||||
type Sender interface { |
||||
Send(Buffer) |
||||
} |
||||
|
||||
type ResponseSender interface { |
||||
Sender |
||||
Responder |
||||
} |
||||
|
||||
type Responder interface { |
||||
Reply(iid string, m interface{}) |
||||
} |
||||
|
||||
type Client interface { |
||||
ResponseSender |
||||
Session() *Session |
||||
Index() uint64 |
||||
Close(bool) |
||||
} |
||||
|
||||
type client struct { |
||||
Codec |
||||
ChannellingAPI |
||||
Connection |
||||
session *Session |
||||
} |
||||
|
||||
func NewClient(codec Codec, api ChannellingAPI, session *Session) *client { |
||||
return &client{codec, api, nil, session} |
||||
} |
||||
|
||||
func (client *client) OnConnect(conn Connection) { |
||||
client.Connection = conn |
||||
client.ChannellingAPI.OnConnect(client, client.session) |
||||
} |
||||
|
||||
func (client *client) OnText(b Buffer) { |
||||
if incoming, err := client.DecodeIncoming(b); err == nil { |
||||
client.OnIncoming(client, client.session, incoming) |
||||
} else { |
||||
log.Println("OnText error while decoding JSON", err) |
||||
log.Printf("JSON:\n%s\n", b) |
||||
} |
||||
} |
||||
|
||||
func (client *client) OnDisconnect() { |
||||
client.ChannellingAPI.OnDisconnect(client.session) |
||||
} |
||||
|
||||
func (client *client) Reply(iid string, m interface{}) { |
||||
outgoing := &DataOutgoing{From: client.session.Id, Iid: iid, Data: m} |
||||
if b, err := client.EncodeOutgoing(outgoing); err == nil { |
||||
client.Send(b) |
||||
b.Decref() |
||||
} |
||||
} |
||||
|
||||
func (client *client) Session() *Session { |
||||
return client.session |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"testing" |
||||
) |
||||
|
||||
func assertDataError(t *testing.T, err error, code string) { |
||||
if err == nil { |
||||
t.Error("Expected an error, but none was returned") |
||||
return |
||||
} |
||||
|
||||
dataError, ok := err.(*DataError) |
||||
if !ok { |
||||
t.Errorf("Expected error %#v to be a *DataError", err) |
||||
return |
||||
} |
||||
|
||||
if code != dataError.Code { |
||||
t.Errorf("Expected error code to be %v, but was %v", code, dataError.Code) |
||||
} |
||||
} |
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"log" |
||||
) |
||||
|
||||
type IncomingDecoder interface { |
||||
DecodeIncoming(Buffer) (*DataIncoming, error) |
||||
} |
||||
|
||||
type OutgoingEncoder interface { |
||||
EncodeOutgoing(*DataOutgoing) (Buffer, error) |
||||
} |
||||
|
||||
type Codec interface { |
||||
NewBuffer() Buffer |
||||
IncomingDecoder |
||||
OutgoingEncoder |
||||
} |
||||
|
||||
type incomingCodec struct { |
||||
buffers BufferCache |
||||
} |
||||
|
||||
func NewCodec() Codec { |
||||
return &incomingCodec{NewBufferCache(1024, bytes.MinRead)} |
||||
} |
||||
|
||||
func (codec incomingCodec) NewBuffer() Buffer { |
||||
return codec.buffers.New() |
||||
} |
||||
|
||||
func (codec incomingCodec) DecodeIncoming(b Buffer) (*DataIncoming, error) { |
||||
incoming := &DataIncoming{} |
||||
return incoming, json.Unmarshal(b.Bytes(), incoming) |
||||
} |
||||
|
||||
func (codec incomingCodec) EncodeOutgoing(outgoing *DataOutgoing) (Buffer, error) { |
||||
b := codec.NewBuffer() |
||||
if err := json.NewEncoder(b).Encode(outgoing); err != nil { |
||||
log.Println("Error while encoding JSON", err) |
||||
b.Decref() |
||||
return nil, err |
||||
} |
||||
return b, nil |
||||
} |
@ -0,0 +1,201 @@
@@ -0,0 +1,201 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"log" |
||||
"sync" |
||||
) |
||||
|
||||
type RoomStatusManager interface { |
||||
RoomUsers(*Session) []*DataSession |
||||
JoinRoom(string, *DataRoomCredentials, *Session, Sender) (*DataRoom, error) |
||||
LeaveRoom(*Session) |
||||
UpdateRoom(*Session, *DataRoom) (*DataRoom, error) |
||||
} |
||||
|
||||
type Broadcaster interface { |
||||
Broadcast(*Session, interface{}) |
||||
} |
||||
|
||||
type RoomStats interface { |
||||
RoomInfo(includeSessions bool) (count int, sessionInfo map[string][]string) |
||||
} |
||||
|
||||
type RoomManager interface { |
||||
RoomStatusManager |
||||
Broadcaster |
||||
RoomStats |
||||
} |
||||
|
||||
type roomManager struct { |
||||
sync.RWMutex |
||||
*Config |
||||
OutgoingEncoder |
||||
roomTable map[string]RoomWorker |
||||
} |
||||
|
||||
func NewRoomManager(config *Config, encoder OutgoingEncoder) RoomManager { |
||||
return &roomManager{ |
||||
sync.RWMutex{}, |
||||
config, |
||||
encoder, |
||||
make(map[string]RoomWorker), |
||||
} |
||||
} |
||||
|
||||
func (rooms *roomManager) RoomUsers(session *Session) []*DataSession { |
||||
if room, ok := rooms.Get(session.Roomid); ok { |
||||
return room.GetUsers() |
||||
} |
||||
// TODO(lcooper): This should return an error.
|
||||
return []*DataSession{} |
||||
} |
||||
|
||||
func (rooms *roomManager) JoinRoom(id string, credentials *DataRoomCredentials, session *Session, sender Sender) (*DataRoom, error) { |
||||
if id == "" && !rooms.DefaultRoomEnabled { |
||||
return nil, NewDataError("default_room_disabled", "The default room is not enabled") |
||||
} |
||||
|
||||
roomWorker, err := rooms.GetOrCreate(id, credentials, session) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return roomWorker.Join(credentials, session, sender) |
||||
} |
||||
|
||||
func (rooms *roomManager) LeaveRoom(session *Session) { |
||||
if room, ok := rooms.Get(session.Roomid); ok { |
||||
room.Leave(session) |
||||
} |
||||
} |
||||
|
||||
func (rooms *roomManager) UpdateRoom(session *Session, room *DataRoom) (*DataRoom, error) { |
||||
if !session.Hello || session.Roomid != room.Name { |
||||
return nil, NewDataError("not_in_room", "Cannot update other rooms") |
||||
} |
||||
// XXX(lcooper): We'll process and send documents without this field
|
||||
// correctly, however clients cannot not handle it currently.
|
||||
room.Type = "Room" |
||||
if roomWorker, ok := rooms.Get(session.Roomid); ok { |
||||
return room, roomWorker.Update(room) |
||||
} |
||||
// TODO(lcooper): We should almost certainly return an error in this case.
|
||||
return room, nil |
||||
} |
||||
|
||||
func (rooms *roomManager) Broadcast(session *Session, m interface{}) { |
||||
outgoing := &DataOutgoing{ |
||||
From: session.Id, |
||||
A: session.Attestation(), |
||||
Data: m, |
||||
} |
||||
|
||||
message, err := rooms.EncodeOutgoing(outgoing) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
id := session.Roomid |
||||
if id != "" && id == rooms.globalRoomID { |
||||
rooms.RLock() |
||||
for _, room := range rooms.roomTable { |
||||
room.Broadcast(session, message) |
||||
} |
||||
rooms.RUnlock() |
||||
} else if room, ok := rooms.Get(id); ok { |
||||
room.Broadcast(session, message) |
||||
} else { |
||||
log.Printf("No room named %s found for broadcast message %#v", id, m) |
||||
} |
||||
message.Decref() |
||||
} |
||||
|
||||
func (rooms *roomManager) RoomInfo(includeSessions bool) (count int, sessionInfo map[string][]string) { |
||||
rooms.RLock() |
||||
defer rooms.RUnlock() |
||||
|
||||
count = len(rooms.roomTable) |
||||
if includeSessions { |
||||
sessionInfo := make(map[string][]string) |
||||
for roomid, room := range rooms.roomTable { |
||||
sessionInfo[roomid] = room.SessionIDs() |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (rooms *roomManager) Get(id string) (room RoomWorker, ok bool) { |
||||
rooms.RLock() |
||||
room, ok = rooms.roomTable[id] |
||||
rooms.RUnlock() |
||||
return |
||||
} |
||||
|
||||
func (rooms *roomManager) GetOrCreate(id string, credentials *DataRoomCredentials, session *Session) (RoomWorker, error) { |
||||
if room, ok := rooms.Get(id); ok { |
||||
return room, nil |
||||
} |
||||
|
||||
rooms.Lock() |
||||
// Need to re-check, another thread might have created the room
|
||||
// while we waited for the lock.
|
||||
if room, ok := rooms.roomTable[id]; ok { |
||||
rooms.Unlock() |
||||
return room, nil |
||||
} |
||||
|
||||
if rooms.UsersEnabled && rooms.AuthorizeRoomCreation && !session.Authenticated() { |
||||
rooms.Unlock() |
||||
return nil, NewDataError("room_join_requires_account", "Room creation requires a user account") |
||||
} |
||||
|
||||
room := NewRoomWorker(rooms, id, credentials) |
||||
rooms.roomTable[id] = room |
||||
rooms.Unlock() |
||||
go func() { |
||||
// Start room, this blocks until room expired.
|
||||
room.Start() |
||||
// Cleanup room when we are done.
|
||||
rooms.Lock() |
||||
defer rooms.Unlock() |
||||
delete(rooms.roomTable, id) |
||||
log.Printf("Cleaned up room '%s'\n", id) |
||||
}() |
||||
|
||||
return room, nil |
||||
} |
||||
|
||||
func (rooms *roomManager) GlobalUsers() []*roomUser { |
||||
if rooms.globalRoomID == "" { |
||||
return make([]*roomUser, 0) |
||||
} |
||||
rooms.RLock() |
||||
if room, ok := rooms.roomTable[rooms.globalRoomID]; ok { |
||||
rooms.RUnlock() |
||||
return room.Users() |
||||
} |
||||
|
||||
rooms.RUnlock() |
||||
return make([]*roomUser, 0) |
||||
} |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"testing" |
||||
) |
||||
|
||||
func NewTestRoomManager() (RoomManager, *Config) { |
||||
config := &Config{} |
||||
return NewRoomManager(config, nil), config |
||||
} |
||||
|
||||
func Test_RoomManager_JoinRoom_ReturnsAnErrorForUnauthenticatedSessionsWhenCreationRequiresAnAccount(t *testing.T) { |
||||
roomManager, config := NewTestRoomManager() |
||||
config.UsersEnabled = true |
||||
config.AuthorizeRoomCreation = true |
||||
|
||||
unauthenticatedSession := &Session{} |
||||
_, err := roomManager.JoinRoom("foo", nil, unauthenticatedSession, nil) |
||||
assertDataError(t, err, "room_join_requires_account") |
||||
|
||||
authenticatedSession := &Session{userid: "9870457"} |
||||
_, err = roomManager.JoinRoom("foo", nil, authenticatedSession, nil) |
||||
if err != nil { |
||||
t.Fatalf("Unexpected error %v joining room while authenticated", err) |
||||
} |
||||
|
||||
_, err = roomManager.JoinRoom("foo", nil, unauthenticatedSession, nil) |
||||
if err != nil { |
||||
t.Fatalf("Unexpected error %v joining room while unauthenticated", err) |
||||
} |
||||
} |
||||
|
||||
func Test_RoomManager_UpdateRoom_ReturnsAnErrorIfNoRoomHasBeenJoined(t *testing.T) { |
||||
roomManager, _ := NewTestRoomManager() |
||||
_, err := roomManager.UpdateRoom(&Session{}, nil) |
||||
|
||||
assertDataError(t, err, "not_in_room") |
||||
} |
||||
|
||||
func Test_RoomManager_UpdateRoom_ReturnsAnErrorIfUpdatingAnUnjoinedRoom(t *testing.T) { |
||||
roomManager, _ := NewTestRoomManager() |
||||
session := &Session{Hello: true, Roomid: "foo"} |
||||
_, err := roomManager.UpdateRoom(session, &DataRoom{Name: "bar"}) |
||||
assertDataError(t, err, "not_in_room") |
||||
} |
||||
|
||||
func Test_RoomManager_UpdateRoom_ReturnsACorrectlyTypedDocument(t *testing.T) { |
||||
roomManager, _ := NewTestRoomManager() |
||||
session := &Session{Hello: true, Roomid: "foo"} |
||||
room, err := roomManager.UpdateRoom(session, &DataRoom{Name: session.Roomid}) |
||||
if err != nil { |
||||
t.Fatalf("Unexpected error %v updating room", err) |
||||
} |
||||
|
||||
if room.Type != "Room" { |
||||
t.Errorf("Expected document type to be Room, but was %v", room.Type) |
||||
} |
||||
} |
@ -0,0 +1,124 @@
@@ -0,0 +1,124 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"testing" |
||||
) |
||||
|
||||
const ( |
||||
testRoomName string = "a-room-name" |
||||
) |
||||
|
||||
func NewTestRoomWorker() RoomWorker { |
||||
worker := NewRoomWorker(&roomManager{Config: &Config{}}, testRoomName, nil) |
||||
go worker.Start() |
||||
return worker |
||||
} |
||||
|
||||
func NewTestRoomWorkerWithPIN(t *testing.T) (RoomWorker, string) { |
||||
pin := "asdf" |
||||
worker := NewRoomWorker(&roomManager{Config: &Config{}}, testRoomName, &DataRoomCredentials{PIN: pin}) |
||||
go worker.Start() |
||||
return worker, pin |
||||
} |
||||
|
||||
func Test_RoomWorker_Join_SucceedsWhenNoCredentialsAreRequired(t *testing.T) { |
||||
worker := NewTestRoomWorker() |
||||
|
||||
_, err := worker.Join(nil, &Session{}, nil) |
||||
if err != nil { |
||||
t.Fatalf("Unexpected error %v", err) |
||||
} |
||||
|
||||
if userCount := len(worker.GetUsers()); userCount != 1 { |
||||
t.Errorf("Expected join to have been accepted but room contains %d users", userCount) |
||||
} |
||||
} |
||||
|
||||
func Test_RoomWorker_Join_FailsIfCredentialsAreGivenWhenUnneeded(t *testing.T) { |
||||
worker := NewTestRoomWorker() |
||||
|
||||
_, err := worker.Join(&DataRoomCredentials{}, &Session{}, nil) |
||||
|
||||
assertDataError(t, err, "authorization_not_required") |
||||
if userCount := len(worker.GetUsers()); userCount != 0 { |
||||
t.Errorf("Expected join to have been rejected but room contains %d users", userCount) |
||||
} |
||||
} |
||||
|
||||
func Test_RoomWorker_Join_FailsIfNoCredentialsAreGiven(t *testing.T) { |
||||
worker, _ := NewTestRoomWorkerWithPIN(t) |
||||
|
||||
_, err := worker.Join(nil, &Session{}, nil) |
||||
|
||||
assertDataError(t, err, "authorization_required") |
||||
if userCount := len(worker.GetUsers()); userCount != 0 { |
||||
t.Errorf("Expected join to have been rejected but room contains %d users", userCount) |
||||
} |
||||
} |
||||
|
||||
func Test_RoomWorker_Join_FailsIfIncorrectCredentialsAreGiven(t *testing.T) { |
||||
worker, _ := NewTestRoomWorkerWithPIN(t) |
||||
|
||||
_, err := worker.Join(&DataRoomCredentials{PIN: "adfs"}, &Session{}, nil) |
||||
|
||||
assertDataError(t, err, "invalid_credentials") |
||||
if userCount := len(worker.GetUsers()); userCount != 0 { |
||||
t.Errorf("Expected join to have been rejected but room contains %d users", userCount) |
||||
} |
||||
} |
||||
|
||||
func Test_RoomWorker_Join_SucceedsWhenTheCorrectPINIsGiven(t *testing.T) { |
||||
worker, pin := NewTestRoomWorkerWithPIN(t) |
||||
|
||||
if _, err := worker.Join(&DataRoomCredentials{PIN: pin}, &Session{}, nil); err != nil { |
||||
t.Fatalf("Unexpected error %v", err) |
||||
} |
||||
|
||||
if len(worker.GetUsers()) < 1 { |
||||
t.Error("Expected join to have been accepted but room contains no users") |
||||
} |
||||
} |
||||
|
||||
func Test_RoomWorker_Update_AllowsClearingCredentials(t *testing.T) { |
||||
worker, _ := NewTestRoomWorkerWithPIN(t) |
||||
|
||||
if err := worker.Update(&DataRoom{Credentials: &DataRoomCredentials{PIN: ""}}); err != nil { |
||||
t.Fatalf("Failed to update room: %v", err) |
||||
} |
||||
|
||||
_, err := worker.Join(&DataRoomCredentials{}, &Session{}, nil) |
||||
assertDataError(t, err, "authorization_not_required") |
||||
} |
||||
|
||||
func Test_RoomWorker_Update_RetainsCredentialsWhenOtherPropertiesAreUpdated(t *testing.T) { |
||||
worker, pin := NewTestRoomWorkerWithPIN(t) |
||||
|
||||
if err := worker.Update(&DataRoom{}); err != nil { |
||||
t.Fatalf("Failed to update room: %v", err) |
||||
} |
||||
|
||||
if _, err := worker.Join(&DataRoomCredentials{PIN: pin}, &Session{}, nil); err != nil { |
||||
t.Fatalf("Unexpected error joining room %v", err) |
||||
} |
||||
} |
@ -1,280 +0,0 @@
@@ -1,280 +0,0 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"log" |
||||
"sync/atomic" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
maxConferenceSize = 100 |
||||
) |
||||
|
||||
type Server struct { |
||||
} |
||||
|
||||
func (s *Server) OnRegister(c *Connection) { |
||||
//log.Println("OnRegister", c.id)
|
||||
if token, err := c.h.EncodeSessionToken(c.Session.Token()); err == nil { |
||||
log.Println("Created new session token", len(token), token) |
||||
// Send stuff back.
|
||||
s.Unicast(c, c.Id, &DataSelf{ |
||||
Type: "Self", |
||||
Id: c.Id, |
||||
Sid: c.Session.Sid, |
||||
Userid: c.Session.Userid(), |
||||
Suserid: c.h.CreateSuserid(c.Session), |
||||
Token: token, |
||||
Version: c.h.version, |
||||
Turn: c.h.CreateTurnData(c.Id), |
||||
Stun: c.h.config.StunURIs, |
||||
}) |
||||
} else { |
||||
log.Println("Error in OnRegister", c.Idx, err) |
||||
} |
||||
} |
||||
|
||||
func (s *Server) OnUnregister(c *Connection) { |
||||
//log.Println("OnUnregister", c.id)
|
||||
dsl := c.Session.DataSessionLeft("hard") |
||||
if c.Hello { |
||||
s.UpdateRoomConnection(c, &RoomConnectionUpdate{Id: c.Roomid}) |
||||
s.Broadcast(c, dsl) |
||||
} |
||||
c.Session.RunForAllSubscribers(func(session *Session) { |
||||
log.Println("Notifying subscriber that we are gone", c.Id, session.Id) |
||||
s.Unicast(c, session.Id, dsl) |
||||
}) |
||||
} |
||||
|
||||
func (s *Server) OnText(c *Connection, b Buffer) { |
||||
|
||||
//log.Printf("OnText from %d: %s\n", c.Id, b)
|
||||
var msg DataIncoming |
||||
err := json.Unmarshal(b.Bytes(), &msg) |
||||
if err != nil { |
||||
log.Println("OnText error while decoding JSON", err) |
||||
log.Printf("JSON:\n%s\n", b) |
||||
return |
||||
} |
||||
|
||||
switch msg.Type { |
||||
case "Self": |
||||
s.OnRegister(c) |
||||
case "Hello": |
||||
//log.Println("Hello", msg.Hello, c.Idx)
|
||||
// TODO(longsleep): Filter room id and user agent.
|
||||
s.UpdateSession(c, &SessionUpdate{Types: []string{"Roomid", "Ua"}, Roomid: msg.Hello.Id, Ua: msg.Hello.Ua}) |
||||
if c.Hello && c.Roomid != msg.Hello.Id { |
||||
// Room changed.
|
||||
s.UpdateRoomConnection(c, &RoomConnectionUpdate{Id: c.Roomid}) |
||||
s.Broadcast(c, c.Session.DataSessionLeft("soft")) |
||||
} |
||||
c.Roomid = msg.Hello.Id |
||||
if c.h.config.DefaultRoomEnabled || !c.h.isDefaultRoomid(c.Roomid) { |
||||
c.Hello = true |
||||
s.UpdateRoomConnection(c, &RoomConnectionUpdate{Id: c.Roomid, Status: true}) |
||||
s.Broadcast(c, c.Session.DataSessionJoined()) |
||||
} else { |
||||
c.Hello = false |
||||
} |
||||
case "Offer": |
||||
// TODO(longsleep): Validate offer
|
||||
s.Unicast(c, msg.Offer.To, msg.Offer) |
||||
case "Candidate": |
||||
// TODO(longsleep): Validate candidate
|
||||
s.Unicast(c, msg.Candidate.To, msg.Candidate) |
||||
case "Answer": |
||||
// TODO(longsleep): Validate Answer
|
||||
s.Unicast(c, msg.Answer.To, msg.Answer) |
||||
case "Users": |
||||
if c.Hello { |
||||
s.Users(c) |
||||
} |
||||
case "Authentication": |
||||
if msg.Authentication.Authentication != nil && s.Authenticate(c, msg.Authentication.Authentication) { |
||||
s.OnRegister(c) |
||||
if c.Hello { |
||||
s.Broadcast(c, c.Session.DataSessionStatus()) |
||||
} |
||||
} |
||||
case "Bye": |
||||
s.Unicast(c, msg.Bye.To, msg.Bye) |
||||
case "Status": |
||||
//log.Println("Status", msg.Status)
|
||||
s.UpdateSession(c, &SessionUpdate{Types: []string{"Status"}, Status: msg.Status.Status}) |
||||
if c.Hello { |
||||
s.Broadcast(c, c.Session.DataSessionStatus()) |
||||
} |
||||
case "Chat": |
||||
// TODO(longsleep): Limit sent chat messages per incoming connection.
|
||||
if !msg.Chat.Chat.NoEcho { |
||||
s.Unicast(c, c.Id, msg.Chat) |
||||
} |
||||
msg.Chat.Chat.Time = time.Now().Format(time.RFC3339) |
||||
if msg.Chat.To == "" { |
||||
// TODO(longsleep): Check if chat broadcast is allowed.
|
||||
if c.Hello { |
||||
atomic.AddUint64(&c.h.broadcastChatMessages, 1) |
||||
s.Broadcast(c, msg.Chat) |
||||
} |
||||
} else { |
||||
if msg.Chat.Chat.Status != nil && msg.Chat.Chat.Status.ContactRequest != nil { |
||||
err = s.ContactRequest(c, msg.Chat.To, msg.Chat.Chat.Status.ContactRequest) |
||||
if err != nil { |
||||
log.Println("Ignoring invalid contact request.", err) |
||||
return |
||||
} |
||||
msg.Chat.Chat.Status.ContactRequest.Userid = c.Session.Userid() |
||||
} |
||||
atomic.AddUint64(&c.h.unicastChatMessages, 1) |
||||
s.Unicast(c, msg.Chat.To, msg.Chat) |
||||
if msg.Chat.Chat.Mid != "" { |
||||
// Send out delivery confirmation status chat message.
|
||||
s.Unicast(c, c.Id, &DataChat{To: msg.Chat.To, Type: "Chat", Chat: &DataChatMessage{Mid: msg.Chat.Chat.Mid, Status: &DataChatStatus{State: "sent"}}}) |
||||
} |
||||
} |
||||
case "Conference": |
||||
// Check conference maximum size.
|
||||
if len(msg.Conference.Conference) > maxConferenceSize { |
||||
log.Println("Refusing to create conference above limit.", len(msg.Conference.Conference)) |
||||
} else { |
||||
// Send conference update to anyone.
|
||||
for _, id := range msg.Conference.Conference { |
||||
if id != c.Id { |
||||
//log.Println("participant", id)
|
||||
s.Unicast(c, id, msg.Conference) |
||||
} |
||||
} |
||||
} |
||||
case "Alive": |
||||
s.Alive(c, msg.Alive, msg.Iid) |
||||
case "Sessions": |
||||
s.Sessions(c, msg.Sessions.Sessions, msg.Iid) |
||||
default: |
||||
log.Println("OnText unhandled message type", msg.Type) |
||||
} |
||||
|
||||
} |
||||
|
||||
func (s *Server) Unicast(c *Connection, to string, m interface{}) { |
||||
|
||||
outgoing := &DataOutgoing{From: c.Id, To: to, Data: m} |
||||
if !c.isClosing && c.Id != to { |
||||
outgoing.A = c.Session.Attestation() |
||||
} |
||||
b := c.h.buffers.New() |
||||
encoder := json.NewEncoder(b) |
||||
err := encoder.Encode(outgoing) |
||||
if err != nil { |
||||
b.Decref() |
||||
log.Println("Unicast error while encoding JSON", err) |
||||
return |
||||
} |
||||
//log.Println("Unicast", b)
|
||||
|
||||
var msg = &MessageRequest{From: c.Id, To: to, Message: b} |
||||
c.h.unicastHandler(msg) |
||||
b.Decref() |
||||
} |
||||
|
||||
func (s *Server) Broadcast(c *Connection, m interface{}) { |
||||
|
||||
b := c.h.buffers.New() |
||||
encoder := json.NewEncoder(b) |
||||
err := encoder.Encode(&DataOutgoing{From: c.Id, Data: m, A: c.Session.Attestation()}) |
||||
if err != nil { |
||||
b.Decref() |
||||
log.Println("Broadcast error while encoding JSON", err) |
||||
return |
||||
} |
||||
|
||||
if c.h.isGlobalRoomid(c.Roomid) { |
||||
c.h.RunForAllRooms(func(room *RoomWorker) { |
||||
var msg = &MessageRequest{From: c.Id, Message: b, Id: room.Id} |
||||
room.broadcastHandler(msg) |
||||
}) |
||||
} else { |
||||
var msg = &MessageRequest{From: c.Id, Message: b, Id: c.Roomid} |
||||
room := c.h.GetRoom(c.Roomid) |
||||
room.broadcastHandler(msg) |
||||
} |
||||
b.Decref() |
||||
|
||||
} |
||||
|
||||
func (s *Server) Alive(c *Connection, alive *DataAlive, iid string) { |
||||
|
||||
c.h.aliveHandler(c, alive, iid) |
||||
|
||||
} |
||||
|
||||
func (s *Server) Sessions(c *Connection, srq *DataSessionsRequest, iid string) { |
||||
|
||||
c.h.sessionsHandler(c, srq, iid) |
||||
|
||||
} |
||||
|
||||
func (s *Server) UpdateSession(c *Connection, su *SessionUpdate) uint64 { |
||||
|
||||
su.Id = c.Id |
||||
return c.h.sessionupdateHandler(su) |
||||
|
||||
} |
||||
|
||||
func (s *Server) ContactRequest(c *Connection, to string, cr *DataContactRequest) (err error) { |
||||
|
||||
return c.h.contactrequestHandler(c, to, cr) |
||||
|
||||
} |
||||
|
||||
func (s *Server) Users(c *Connection) { |
||||
|
||||
room := c.h.GetRoom(c.Roomid) |
||||
room.usersHandler(c) |
||||
|
||||
} |
||||
|
||||
func (s *Server) Authenticate(c *Connection, st *SessionToken) bool { |
||||
|
||||
err := c.h.authenticateHandler(c.Session, st, "") |
||||
if err == nil { |
||||
log.Println("Authentication success", c.Id, c.Idx, c.Session.Userid) |
||||
return true |
||||
} else { |
||||
log.Println("Authentication failed", err, c.Id, c.Idx, st.Userid, st.Nonce) |
||||
return false |
||||
} |
||||
|
||||
} |
||||
|
||||
func (s *Server) UpdateRoomConnection(c *Connection, rcu *RoomConnectionUpdate) { |
||||
|
||||
rcu.Sessionid = c.Id |
||||
rcu.Connection = c |
||||
room := c.h.GetRoom(c.Roomid) |
||||
room.connectionHandler(rcu) |
||||
|
||||
} |
@ -0,0 +1,173 @@
@@ -0,0 +1,173 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"crypto/sha256" |
||||
"net/http" |
||||
"sync" |
||||
|
||||
"github.com/gorilla/securecookie" |
||||
) |
||||
|
||||
type UserStats interface { |
||||
UserInfo(bool) (int, map[string]*DataUser) |
||||
} |
||||
|
||||
type SessionManager interface { |
||||
UserStats |
||||
RetrieveUsersWith(func(*http.Request) (string, error)) |
||||
CreateSession(*http.Request) *Session |
||||
DestroySession(*Session) |
||||
Authenticate(*Session, *SessionToken, string) error |
||||
GetUserSessions(session *Session, id string) []*DataSession |
||||
} |
||||
|
||||
type sessionManager struct { |
||||
Tickets |
||||
sync.RWMutex |
||||
config *Config |
||||
userTable map[string]*User |
||||
fakesessionTable map[string]*Session |
||||
useridRetriever func(*http.Request) (string, error) |
||||
attestations *securecookie.SecureCookie |
||||
} |
||||
|
||||
func NewSessionManager(config *Config, tickets Tickets, sessionSecret []byte) SessionManager { |
||||
sessionManager := &sessionManager{ |
||||
tickets, |
||||
sync.RWMutex{}, |
||||
config, |
||||
make(map[string]*User), |
||||
make(map[string]*Session), |
||||
nil, |
||||
nil, |
||||
} |
||||
|
||||
sessionManager.attestations = securecookie.New(sessionSecret, nil) |
||||
sessionManager.attestations.MaxAge(300) // 5 minutes
|
||||
sessionManager.attestations.HashFunc(sha256.New) |
||||
|
||||
return sessionManager |
||||
} |
||||
|
||||
func (sessionManager *sessionManager) UserInfo(details bool) (userCount int, users map[string]*DataUser) { |
||||
sessionManager.RLock() |
||||
defer sessionManager.RUnlock() |
||||
|
||||
userCount = len(sessionManager.userTable) |
||||
if details { |
||||
users := make(map[string]*DataUser) |
||||
for userid, user := range sessionManager.userTable { |
||||
users[userid] = user.Data() |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (sessionManager *sessionManager) RetrieveUsersWith(retriever func(*http.Request) (string, error)) { |
||||
sessionManager.useridRetriever = retriever |
||||
} |
||||
|
||||
func (sessionManager *sessionManager) CreateSession(request *http.Request) *Session { |
||||
request.ParseForm() |
||||
token := request.FormValue("t") |
||||
st := sessionManager.DecodeSessionToken(token) |
||||
|
||||
var userid string |
||||
if sessionManager.config.UsersEnabled { |
||||
if sessionManager.useridRetriever != nil { |
||||
userid, _ = sessionManager.useridRetriever(request) |
||||
if userid == "" { |
||||
userid = st.Userid |
||||
} |
||||
} |
||||
} |
||||
|
||||
session := NewSession(sessionManager.attestations, st.Id, st.Sid) |
||||
|
||||
if userid != "" { |
||||
// XXX(lcooper): Should errors be handled here?
|
||||
sessionManager.Authenticate(session, st, userid) |
||||
} |
||||
|
||||
return session |
||||
} |
||||
|
||||
func (sessionManager *sessionManager) DestroySession(session *Session) { |
||||
session.Close() |
||||
|
||||
sessionManager.Lock() |
||||
if suserid := session.Userid(); suserid != "" { |
||||
user, ok := sessionManager.userTable[suserid] |
||||
if ok && user.RemoveSession(session) { |
||||
delete(sessionManager.userTable, suserid) |
||||
} |
||||
} |
||||
sessionManager.Unlock() |
||||
} |
||||
|
||||
func (sessionManager *sessionManager) Authenticate(session *Session, st *SessionToken, userid string) error { |
||||
if err := session.Authenticate(sessionManager.Realm(), st, userid); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Authentication success.
|
||||
suserid := session.Userid() |
||||
sessionManager.Lock() |
||||
user, ok := sessionManager.userTable[suserid] |
||||
if !ok { |
||||
user = NewUser(suserid) |
||||
sessionManager.userTable[suserid] = user |
||||
} |
||||
sessionManager.Unlock() |
||||
user.AddSession(session) |
||||
return nil |
||||
} |
||||
|
||||
func (sessionManager *sessionManager) GetUserSessions(session *Session, userid string) (users []*DataSession) { |
||||
var ( |
||||
user *User |
||||
ok bool |
||||
) |
||||
sessionManager.RLock() |
||||
user, ok = sessionManager.userTable[userid] |
||||
sessionManager.RUnlock() |
||||
if !ok { |
||||
// No user. Create fake session.
|
||||
sessionManager.Lock() |
||||
session, ok := sessionManager.fakesessionTable[userid] |
||||
if !ok { |
||||
st := sessionManager.FakeSessionToken(userid) |
||||
session = NewSession(sessionManager.attestations, st.Id, st.Sid) |
||||
session.SetUseridFake(st.Userid) |
||||
sessionManager.fakesessionTable[userid] = session |
||||
} |
||||
sessionManager.Unlock() |
||||
users = make([]*DataSession, 1, 1) |
||||
users[0] = session.Data() |
||||
} else { |
||||
// Add sessions for foreign user.
|
||||
users = user.SubscribeSessions(session) |
||||
} |
||||
return |
||||
} |
@ -0,0 +1,104 @@
@@ -0,0 +1,104 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"sync/atomic" |
||||
) |
||||
|
||||
type HubStat struct { |
||||
Rooms int `json:"rooms"` |
||||
Connections int `json:"connections"` |
||||
Sessions int `json:"sessions"` |
||||
Users int `json:"users"` |
||||
Count uint64 `json:"count"` |
||||
BroadcastChatMessages uint64 `json:"broadcastchatmessages"` |
||||
UnicastChatMessages uint64 `json:"unicastchatmessages"` |
||||
IdsInRoom map[string][]string `json:"idsinroom,omitempty"` |
||||
SessionsById map[string]*DataSession `json:"sessionsbyid,omitempty"` |
||||
UsersById map[string]*DataUser `json:"usersbyid,omitempty"` |
||||
ConnectionsByIdx map[string]string `json:"connectionsbyidx,omitempty"` |
||||
} |
||||
|
||||
type ConnectionCounter interface { |
||||
CountConnection() uint64 |
||||
} |
||||
|
||||
type StatsCounter interface { |
||||
CountBroadcastChat() |
||||
CountUnicastChat() |
||||
} |
||||
|
||||
type StatsGenerator interface { |
||||
Stat(details bool) *HubStat |
||||
} |
||||
|
||||
type StatsManager interface { |
||||
ConnectionCounter |
||||
StatsCounter |
||||
StatsGenerator |
||||
} |
||||
|
||||
type statsManager struct { |
||||
ClientStats |
||||
RoomStats |
||||
UserStats |
||||
connectionCount uint64 |
||||
broadcastChatMessages uint64 |
||||
unicastChatMessages uint64 |
||||
} |
||||
|
||||
func NewStatsManager(clientStats ClientStats, roomStats RoomStats, userStats UserStats) StatsManager { |
||||
return &statsManager{clientStats, roomStats, userStats, 0, 0, 0} |
||||
} |
||||
|
||||
func (stats *statsManager) CountConnection() uint64 { |
||||
return atomic.AddUint64(&stats.connectionCount, 1) |
||||
} |
||||
|
||||
func (stats *statsManager) CountBroadcastChat() { |
||||
atomic.AddUint64(&stats.broadcastChatMessages, 1) |
||||
} |
||||
|
||||
func (stats *statsManager) CountUnicastChat() { |
||||
atomic.AddUint64(&stats.unicastChatMessages, 1) |
||||
} |
||||
|
||||
func (stats *statsManager) Stat(details bool) *HubStat { |
||||
roomCount, roomSessionInfo := stats.RoomInfo(details) |
||||
clientCount, sessions, connections := stats.ClientInfo(details) |
||||
userCount, users := stats.UserInfo(details) |
||||
|
||||
return &HubStat{ |
||||
Rooms: roomCount, |
||||
Connections: clientCount, |
||||
Sessions: clientCount, |
||||
Users: userCount, |
||||
Count: atomic.LoadUint64(&stats.connectionCount), |
||||
BroadcastChatMessages: atomic.LoadUint64(&stats.broadcastChatMessages), |
||||
UnicastChatMessages: atomic.LoadUint64(&stats.unicastChatMessages), |
||||
IdsInRoom: roomSessionInfo, |
||||
SessionsById: sessions, |
||||
UsersById: users, |
||||
ConnectionsByIdx: connections, |
||||
} |
||||
} |
@ -0,0 +1,130 @@
@@ -0,0 +1,130 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"crypto/aes" |
||||
"crypto/hmac" |
||||
"crypto/sha256" |
||||
"encoding/base64" |
||||
"fmt" |
||||
"log" |
||||
|
||||
"github.com/gorilla/securecookie" |
||||
) |
||||
|
||||
type SessionValidator interface { |
||||
Realm() string |
||||
ValidateSession(string, string) bool |
||||
} |
||||
|
||||
type SessionEncoder interface { |
||||
EncodeSessionToken(*Session) (string, error) |
||||
EncodeSessionUserID(*Session) string |
||||
} |
||||
|
||||
type Tickets interface { |
||||
SessionValidator |
||||
SessionEncoder |
||||
DecodeSessionToken(token string) (st *SessionToken) |
||||
FakeSessionToken(userid string) *SessionToken |
||||
} |
||||
|
||||
type tickets struct { |
||||
*securecookie.SecureCookie |
||||
realm string |
||||
tokenName string |
||||
encryptionSecret []byte |
||||
} |
||||
|
||||
func NewTickets(sessionSecret, encryptionSecret []byte, realm string) Tickets { |
||||
tickets := &tickets{ |
||||
nil, |
||||
realm, |
||||
fmt.Sprintf("token@%s", realm), |
||||
encryptionSecret, |
||||
} |
||||
tickets.SecureCookie = securecookie.New(sessionSecret, encryptionSecret) |
||||
tickets.MaxAge(86400 * 30) // 30 days
|
||||
tickets.HashFunc(sha256.New) |
||||
tickets.BlockFunc(aes.NewCipher) |
||||
|
||||
return tickets |
||||
} |
||||
|
||||
func (tickets *tickets) Realm() string { |
||||
return tickets.realm |
||||
} |
||||
|
||||
func (tickets *tickets) DecodeSessionToken(token string) (st *SessionToken) { |
||||
var err error |
||||
if token != "" { |
||||
st = &SessionToken{} |
||||
err = tickets.Decode(tickets.tokenName, token, st) |
||||
if err != nil { |
||||
log.Println("Error while decoding session token", err) |
||||
} |
||||
} |
||||
|
||||
if st == nil || err != nil { |
||||
sid := NewRandomString(32) |
||||
id, _ := tickets.Encode("id", sid) |
||||
st = &SessionToken{Id: id, Sid: sid} |
||||
log.Println("Created new session id", id) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (tickets *tickets) FakeSessionToken(userid string) *SessionToken { |
||||
st := &SessionToken{} |
||||
st.Sid = fmt.Sprintf("fake-%s", NewRandomString(27)) |
||||
st.Id, _ = tickets.Encode("id", st.Sid) |
||||
st.Userid = userid |
||||
log.Println("Created new fake session id", st.Id) |
||||
return st |
||||
} |
||||
|
||||
func (tickets *tickets) ValidateSession(id, sid string) bool { |
||||
var decoded string |
||||
if err := tickets.Decode("id", id, &decoded); err != nil { |
||||
log.Println("Session validation error", err, id, sid) |
||||
return false |
||||
} |
||||
if decoded != sid { |
||||
log.Println("Session validation failed", id, sid) |
||||
return false |
||||
} |
||||
return true |
||||
} |
||||
|
||||
func (tickets *tickets) EncodeSessionToken(session *Session) (string, error) { |
||||
return tickets.Encode(tickets.tokenName, session.Token()) |
||||
} |
||||
|
||||
func (tickets *tickets) EncodeSessionUserID(session *Session) (suserid string) { |
||||
if userid := session.Userid(); userid != "" { |
||||
m := hmac.New(sha256.New, tickets.encryptionSecret) |
||||
m.Write([]byte(userid)) |
||||
suserid = base64.StdEncoding.EncodeToString(m.Sum(nil)) |
||||
} |
||||
return |
||||
} |
@ -1,91 +0,0 @@
@@ -1,91 +0,0 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
|
||||
$skin-name: "Dark Theme"; |
||||
$skin-version: "0.0.1"; |
||||
|
||||
$base-skin-color: #212121; |
||||
|
||||
$contrasted-dark-default: #262625; // compass color utility |
||||
$contrasted-light-default: #FCFCFC; // compass color utility |
||||
|
||||
// background |
||||
$main-background: url("../img/bg-tiles.jpg"); |
||||
$main-background-retina: url("../img/bg-tiles_x2.jpg"); |
||||
|
||||
// menu background color |
||||
$menu-background-color: $base-skin-color; |
||||
$menu-background-color-accent: lighten($menu-background-color, 5%); |
||||
|
||||
$componentfg1: contrast-color($menu-background-color); |
||||
$componentbg: $menu-background-color; |
||||
$buddylist-tab-background: $menu-background-color; |
||||
$chat-background: darken($menu-background-color, 5%); |
||||
$chat-msg-self-background: lighten($chat-background, 10%); |
||||
$chat-msg-remote-background: $chat-msg-self-background; |
||||
$fileinfo-background: darken($chat-msg-self-background, 10%); |
||||
$fileinfo-background-remote: darken($chat-msg-remote-background, 10%); |
||||
$buddylist-action-background: transparent; |
||||
$sidepanebg: $menu-background-color-accent; |
||||
$settings-background: $menu-background-color; |
||||
|
||||
$fileinfo-icon-background-color: $chat-msg-self-background; |
||||
$fileinfo-icon-background-color-remote: $chat-msg-remote-background; |
||||
|
||||
// font |
||||
$font-color: contrast-color($menu-background-color); |
||||
$font-color-accent: darken($font-color, 20%); |
||||
|
||||
$chat-ctrl: $font-color; |
||||
$bar-btn-action-color: $font-color; |
||||
$chat-meta: $font-color-accent; |
||||
$chat-timestamp: $font-color-accent; |
||||
$form-help-text: $font-color-accent; |
||||
$bar-logo-text-desc: $font-color-accent; |
||||
$buddylist-browser: $font-color-accent; |
||||
$buddylist-tab-color: $font-color-accent; |
||||
$bar-btn-action-hover: darken($font-color, 20%); |
||||
$bar-btn-action-border: darken($font-color, 50%); |
||||
|
||||
$bordercolor: lighten($menu-background-color, 10%); |
||||
$chat-msg-border: lighten($chat-background, 5%); |
||||
$fileinfo-border-remote: $chat-msg-remote-background; |
||||
$fileinfo-border: $chat-msg-self-background; |
||||
$overlaybar-btn: $font-color-accent; |
||||
$overlaybar-color: $font-color-accent; |
||||
|
||||
// bootstrap vars |
||||
$text-color: $font-color; |
||||
$legend-color: $font-color; |
||||
$legend-border-color: $bordercolor; |
||||
$hr-border: $bordercolor; |
||||
$list-group-bg: $menu-background-color-accent; |
||||
$list-group-border: $bordercolor; |
||||
$list-group-hover-bg: $menu-background-color; |
||||
$list-group-link-color: $font-color-accent; |
||||
$input-bg: lighten($menu-background-color, 20%); |
||||
$input-color: $font-color; |
||||
$input-border: $bar-btn-action-border; |
||||
$progress-bg: lighten($input-bg, 30%); |
||||
$modal-content-bg: $menu-background-color-accent; |
||||
$dialog-header-neutral: $menu-background-color-accent !default; |
||||
$close-color: white; |
||||
$modal-header-border-color: $bordercolor; |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
|
||||
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], |
||||
.ng-cloak, .x-ng-cloak, |
||||
.ng-hide { |
||||
display: none !important; |
||||
} |
||||
|
||||
ng\:form { |
||||
display: block; |
||||
} |
||||
|
||||
.ng-animate-block-transitions { |
||||
transition:0s all!important; |
||||
-webkit-transition:0s all!important; |
||||
} |
||||
|
||||
/* show the element during a show/hide animation when the |
||||
* animation is ongoing, but the .ng-hide class is active */ |
||||
.ng-hide-add-active, .ng-hide-remove { |
||||
display: block!important; |
||||
} |
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
/*! |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/[ng\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important}ng\:form{display:block}.ng-animate-block-transitions{transition:0s all !important;-webkit-transition:0s all !important}.ng-hide-add-active,.ng-hide-remove{display:block !important} |
File diff suppressed because one or more lines are too long
@ -1,90 +0,0 @@
@@ -1,90 +0,0 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
define([], function() { |
||||
|
||||
// RoomchangeController
|
||||
return ["$scope", "$element", "$window", "mediaStream", "$http", "$timeout", function($scope, $element, $window, mediaStream, $http, $timeout) { |
||||
|
||||
//console.log("Room change controller", $element, $scope.roomdata);
|
||||
|
||||
var url = mediaStream.url.api("rooms"); |
||||
|
||||
var ctrl = this; |
||||
ctrl.enabled = true; |
||||
|
||||
ctrl.getRoom = function(cb) { |
||||
$http({ |
||||
method: "POST", |
||||
url: url, |
||||
data: $.param({}), |
||||
headers: { |
||||
'Content-Type': 'application/x-www-form-urlencoded' |
||||
} |
||||
}). |
||||
success(function(data, status) { |
||||
cb(data); |
||||
}). |
||||
error(function() { |
||||
console.error("Failed to retrieve room link."); |
||||
cb({}); |
||||
}); |
||||
}; |
||||
|
||||
$scope.changeRoomToId = function(id) { |
||||
return mediaStream.changeRoom(id); |
||||
}; |
||||
|
||||
$scope.refreshRoom = function() { |
||||
if (ctrl.enabled) { |
||||
ctrl.getRoom(function(roomdata) { |
||||
console.info("Retrieved room data", roomdata); |
||||
$scope.roomdata = roomdata; |
||||
$element.find(".btn-roomcreate").get(0).focus(); |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
$scope.$on("$destroy", function() { |
||||
//console.log("Room change controller destroyed");
|
||||
ctrl.enabled = false; |
||||
}); |
||||
|
||||
$scope.roomdata = {}; |
||||
$scope.$watch("roomdata.name", function(n) { |
||||
//console.log("roomdata.name changed", n);
|
||||
if (!n) { |
||||
n = ""; |
||||
} |
||||
var u = encodeURIComponent(n); |
||||
$scope.roomdata.url = "/" + u; |
||||
$scope.roomdata.link = mediaStream.url.room(n); |
||||
}); |
||||
|
||||
var roomDataLinkInput = $element.find(".roomdata-link-input"); |
||||
if (roomDataLinkInput.length) { |
||||
$timeout(function() { |
||||
$scope.refreshRoom(); |
||||
}, 100); |
||||
} |
||||
|
||||
}]; |
||||
|
||||
}); |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
"use strict"; |
||||
define([], function() { |
||||
return [function() { |
||||
var link = function($scope, $element, attrs) { |
||||
var originalText = $element.text(); |
||||
var updateTitle = function(roomName) { |
||||
if (roomName) { |
||||
$element.text(roomName+ " - " + originalText); |
||||
} else { |
||||
$element.text(originalText); |
||||
} |
||||
}; |
||||
|
||||
$scope.$on("room.updated", function(ev, room) { |
||||
updateTitle(room.Name); |
||||
}); |
||||
|
||||
$scope.$on("room.left", function(ev) { |
||||
updateTitle(); |
||||
}); |
||||
}; |
||||
|
||||
return { |
||||
restrict: 'E', |
||||
replace: false, |
||||
link: link |
||||
}; |
||||
}]; |
||||
}); |
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2014 struktur AG |
||||
* |
||||
* This file is part of Spreed WebRTC. |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
"use strict"; |
||||
define([], function() { |
||||
|
||||
// welcome
|
||||
return ["rooms", "$timeout", "mediaStream", "translation", function(rooms, $timeout, mediaStream, translation) { |
||||
|
||||
function link($scope, $element) { |
||||
//console.log("xxx welcome", $scope.$id, $element);
|
||||
|
||||
var placeHolder = translation._("Room name"); |
||||
|
||||
$scope.randomRoom = rooms.randomRoom; |
||||
$scope.canCreateRooms = rooms.canCreateRooms; |
||||
$scope.joinRoomByName = function(name) { |
||||
if ($scope.welcome.$invalid) { |
||||
return; |
||||
} |
||||
if (!name) { |
||||
return; |
||||
} |
||||
rooms.joinByName(name); |
||||
}; |
||||
|
||||
var roomdata = rooms.getRandomRoom(); |
||||
var recreate = true; |
||||
if (roomdata) { |
||||
$scope.roomdata = {name: roomdata.name, placeholder: roomdata.name ? roomdata.name : placeHolder}; |
||||
recreate = false; |
||||
} else { |
||||
$scope.roomdata = {placeholder: placeHolder}; |
||||
} |
||||
|
||||
$scope.roomdataInput = { |
||||
name: "" |
||||
}; |
||||
|
||||
$scope.$watch("roomdata.name", function(name) { |
||||
$scope.roomdata.link = rooms.link({Name: name}); |
||||
}, true); |
||||
|
||||
$scope.$watch("roomdataInput.name", function(name) { |
||||
if (name === "") { |
||||
if (recreate) { |
||||
$scope.randomRoom(); |
||||
} else { |
||||
recreate = true; |
||||
} |
||||
} else { |
||||
$scope.roomdata.name = name; |
||||
} |
||||
}); |
||||
|
||||
$scope.$on("room.random", function(event, roomdata) { |
||||
$scope.roomdata = {name: roomdata.name, last: roomdata.name, placeholder: roomdata.name ? roomdata.name : placeHolder}; |
||||
$scope.roomdataInput.name = ""; |
||||
}); |
||||
|
||||
$timeout(function() { |
||||
$element.find(".roomdata-link-input:visible:enabled:first").focus(); |
||||
}); |
||||
|
||||
} |
||||
|
||||
return { |
||||
restrict: 'EA', |
||||
link: link |
||||
} |
||||
|
||||
}]; |
||||
|
||||
}); |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue