84 changed files with 3260 additions and 1142 deletions
@ -1 +1,3 @@
@@ -1 +1,3 @@
|
||||
See debian/changelog or https://github.com/strukturag/spreed-webrtc for further information. |
||||
See CHANGELOG.md or https://github.com/strukturag/spreed-webrtc for further information. |
||||
|
||||
The changes are generated with git like `git log v0.24.12..HEAD --no-merges --format=" * %s"` and then manually formatted and added to CHANGELOG.md. |
||||
|
@ -1,11 +0,0 @@
@@ -1,11 +0,0 @@
|
||||
github.com/gorilla/context 215affda49addc4c8ef7e2534915df2c8c35c6cd |
||||
github.com/gorilla/mux ba336c9cfb43552c90de6cb2ceedd3271c747558 |
||||
github.com/gorilla/securecookie aeade84400a85c6875264ae51c7a56ecdcb61751 |
||||
github.com/gorilla/websocket 6eb6ad425a89d9da7a5549bc6da8f79ba5c17844 |
||||
github.com/longsleep/pkac 0.0.1 |
||||
github.com/satori/go.uuid afe1e2ddf0f05b7c29d388a3f8e76cb15c2231ca |
||||
github.com/strukturag/goacceptlanguageparser goacceptlanguageparser_v100 |
||||
github.com/strukturag/httputils httputils_v012 |
||||
github.com/strukturag/phoenix phoenix_v0133 |
||||
github.com/strukturag/sloth v0.9.2 |
||||
github.com/dlintw/goconf dcc070983490608a14480e3bf943bad464785df5 |
@ -1,2 +0,0 @@
@@ -1,2 +0,0 @@
|
||||
[DEFAULT] |
||||
debian-tag = v%(version)s |
|
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
type ChannellingAPI interface { |
||||
OnConnect(*Client, *Session) (interface{}, error) |
||||
OnDisconnect(*Client, *Session) |
||||
OnIncoming(Sender, *Session, *DataIncoming) (interface{}, error) |
||||
} |
||||
|
||||
type ChannellingAPIConsumer interface { |
||||
SetChannellingAPI(ChannellingAPI) |
||||
GetChannellingAPI() ChannellingAPI |
||||
} |
||||
|
||||
type channellingAPIConsumer struct { |
||||
ChannellingAPI ChannellingAPI |
||||
} |
||||
|
||||
func NewChannellingAPIConsumer() ChannellingAPIConsumer { |
||||
return &channellingAPIConsumer{} |
||||
} |
||||
|
||||
func (c *channellingAPIConsumer) SetChannellingAPI(api ChannellingAPI) { |
||||
c.ChannellingAPI = api |
||||
} |
||||
|
||||
func (c *channellingAPIConsumer) GetChannellingAPI() ChannellingAPI { |
||||
return c.ChannellingAPI |
||||
} |
@ -0,0 +1,196 @@
@@ -0,0 +1,196 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 api |
||||
|
||||
import ( |
||||
"log" |
||||
|
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
) |
||||
|
||||
const ( |
||||
maxConferenceSize = 100 |
||||
apiVersion = 1.4 // Keep this in sync with CHANNELING-API docs.Hand
|
||||
) |
||||
|
||||
type channellingAPI struct { |
||||
RoomStatusManager channelling.RoomStatusManager |
||||
SessionEncoder channelling.SessionEncoder |
||||
SessionManager channelling.SessionManager |
||||
StatsCounter channelling.StatsCounter |
||||
ContactManager channelling.ContactManager |
||||
TurnDataCreator channelling.TurnDataCreator |
||||
Unicaster channelling.Unicaster |
||||
BusManager channelling.BusManager |
||||
PipelineManager channelling.PipelineManager |
||||
config *channelling.Config |
||||
} |
||||
|
||||
// New creates and initializes a new ChannellingAPI using
|
||||
// various other services for initialization. It is intended to handle
|
||||
// incoming and outgoing channeling API events from clients.
|
||||
func New(config *channelling.Config, |
||||
roomStatus channelling.RoomStatusManager, |
||||
sessionEncoder channelling.SessionEncoder, |
||||
sessionManager channelling.SessionManager, |
||||
statsCounter channelling.StatsCounter, |
||||
contactManager channelling.ContactManager, |
||||
turnDataCreator channelling.TurnDataCreator, |
||||
unicaster channelling.Unicaster, |
||||
busManager channelling.BusManager, |
||||
pipelineManager channelling.PipelineManager) channelling.ChannellingAPI { |
||||
return &channellingAPI{ |
||||
roomStatus, |
||||
sessionEncoder, |
||||
sessionManager, |
||||
statsCounter, |
||||
contactManager, |
||||
turnDataCreator, |
||||
unicaster, |
||||
busManager, |
||||
pipelineManager, |
||||
config, |
||||
} |
||||
} |
||||
|
||||
func (api *channellingAPI) OnConnect(client *channelling.Client, session *channelling.Session) (interface{}, error) { |
||||
api.Unicaster.OnConnect(client, session) |
||||
self, err := api.HandleSelf(session) |
||||
if err == nil { |
||||
api.BusManager.Trigger(channelling.BusManagerConnect, session.Id, "", nil, nil) |
||||
} |
||||
return self, err |
||||
} |
||||
|
||||
func (api *channellingAPI) OnDisconnect(client *channelling.Client, session *channelling.Session) { |
||||
api.Unicaster.OnDisconnect(client, session) |
||||
api.BusManager.Trigger(channelling.BusManagerDisconnect, session.Id, "", nil, nil) |
||||
} |
||||
|
||||
func (api *channellingAPI) OnIncoming(sender channelling.Sender, session *channelling.Session, msg *channelling.DataIncoming) (interface{}, error) { |
||||
var pipeline *channelling.Pipeline |
||||
switch msg.Type { |
||||
case "Self": |
||||
return api.HandleSelf(session) |
||||
case "Hello": |
||||
if msg.Hello == nil { |
||||
return nil, channelling.NewDataError("bad_request", "message did not contain Hello") |
||||
} |
||||
|
||||
return api.HandleHello(session, msg.Hello, sender) |
||||
case "Offer": |
||||
if msg.Offer == nil || msg.Offer.Offer == nil { |
||||
log.Println("Received invalid offer message.", msg) |
||||
break |
||||
} |
||||
if _, ok := msg.Offer.Offer["_token"]; !ok { |
||||
pipeline = api.PipelineManager.GetPipeline(channelling.PipelineNamespaceCall, sender, session, msg.Offer.To) |
||||
// Trigger offer event when offer has no token, so this is
|
||||
// not triggered for peerxfer and peerscreenshare offers.
|
||||
api.BusManager.Trigger(channelling.BusManagerOffer, session.Id, msg.Offer.To, nil, pipeline) |
||||
} |
||||
|
||||
session.Unicast(msg.Offer.To, msg.Offer, pipeline) |
||||
case "Candidate": |
||||
if msg.Candidate == nil || msg.Candidate.Candidate == nil { |
||||
log.Println("Received invalid candidate message.", msg) |
||||
break |
||||
} |
||||
|
||||
pipeline = api.PipelineManager.GetPipeline(channelling.PipelineNamespaceCall, sender, session, msg.Candidate.To) |
||||
session.Unicast(msg.Candidate.To, msg.Candidate, pipeline) |
||||
case "Answer": |
||||
if msg.Answer == nil || msg.Answer.Answer == nil { |
||||
log.Println("Received invalid answer message.", msg) |
||||
break |
||||
} |
||||
if _, ok := msg.Answer.Answer["_token"]; !ok { |
||||
pipeline = api.PipelineManager.GetPipeline(channelling.PipelineNamespaceCall, sender, session, msg.Answer.To) |
||||
// Trigger answer event when answer has no token. so this is
|
||||
// not triggered for peerxfer and peerscreenshare answers.
|
||||
api.BusManager.Trigger(channelling.BusManagerAnswer, session.Id, msg.Answer.To, nil, pipeline) |
||||
} |
||||
|
||||
session.Unicast(msg.Answer.To, msg.Answer, pipeline) |
||||
case "Users": |
||||
return api.HandleUsers(session) |
||||
case "Authentication": |
||||
if msg.Authentication == nil || msg.Authentication.Authentication == nil { |
||||
return nil, channelling.NewDataError("bad_request", "message did not contain Authentication") |
||||
} |
||||
|
||||
return api.HandleAuthentication(session, msg.Authentication.Authentication) |
||||
case "Bye": |
||||
if msg.Bye == nil { |
||||
log.Println("Received invalid bye message.", msg) |
||||
break |
||||
} |
||||
pipeline = api.PipelineManager.GetPipeline(channelling.PipelineNamespaceCall, sender, session, msg.Bye.To) |
||||
api.BusManager.Trigger(channelling.BusManagerBye, session.Id, msg.Bye.To, nil, pipeline) |
||||
|
||||
session.Unicast(msg.Bye.To, msg.Bye, pipeline) |
||||
if pipeline != nil { |
||||
pipeline.Close() |
||||
} |
||||
case "Status": |
||||
if msg.Status == nil { |
||||
log.Println("Received invalid status message.", msg) |
||||
break |
||||
} |
||||
|
||||
//log.Println("Status", msg.Status)
|
||||
session.Update(&channelling.SessionUpdate{Types: []string{"Status"}, Status: msg.Status.Status}) |
||||
session.BroadcastStatus() |
||||
case "Chat": |
||||
if msg.Chat == nil || msg.Chat.Chat == nil { |
||||
log.Println("Received invalid chat message.", msg) |
||||
break |
||||
} |
||||
|
||||
api.HandleChat(session, msg.Chat) |
||||
case "Conference": |
||||
if msg.Conference == nil { |
||||
log.Println("Received invalid conference message.", msg) |
||||
break |
||||
} |
||||
|
||||
api.HandleConference(session, msg.Conference) |
||||
case "Alive": |
||||
return msg.Alive, nil |
||||
case "Sessions": |
||||
if msg.Sessions == nil || msg.Sessions.Sessions == nil { |
||||
return nil, channelling.NewDataError("bad_request", "message did not contain Sessions") |
||||
} |
||||
|
||||
return api.HandleSessions(session, msg.Sessions.Sessions) |
||||
case "Room": |
||||
if msg.Room == nil { |
||||
return nil, channelling.NewDataError("bad_request", "message did not contain Room") |
||||
} |
||||
|
||||
return api.HandleRoom(session, msg.Room) |
||||
default: |
||||
log.Println("OnText unhandled message type", msg.Type) |
||||
} |
||||
|
||||
return nil, nil |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 api |
||||
|
||||
import ( |
||||
"log" |
||||
|
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
) |
||||
|
||||
func (api *channellingAPI) HandleAuthentication(session *channelling.Session, st *channelling.SessionToken) (*channelling.DataSelf, error) { |
||||
if err := api.SessionManager.Authenticate(session, st, ""); err != nil { |
||||
log.Println("Authentication failed", err, st.Userid, st.Nonce) |
||||
return nil, err |
||||
} |
||||
|
||||
log.Println("Authentication success", session.Userid()) |
||||
self, err := api.HandleSelf(session) |
||||
if err == nil { |
||||
session.BroadcastStatus() |
||||
} |
||||
|
||||
return self, err |
||||
} |
@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 api |
||||
|
||||
import ( |
||||
"log" |
||||
"time" |
||||
|
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
) |
||||
|
||||
func (api *channellingAPI) HandleChat(session *channelling.Session, chat *channelling.DataChat) { |
||||
// TODO(longsleep): Limit sent chat messages per incoming connection.
|
||||
msg := chat.Chat |
||||
to := chat.To |
||||
|
||||
if !msg.NoEcho { |
||||
session.Unicast(session.Id, chat, nil) |
||||
} |
||||
msg.Time = time.Now().Format(time.RFC3339) |
||||
if to == "" { |
||||
// TODO(longsleep): Check if chat broadcast is allowed.
|
||||
if session.Hello { |
||||
api.StatsCounter.CountBroadcastChat() |
||||
session.Broadcast(chat) |
||||
} |
||||
} else { |
||||
if msg.Status != nil { |
||||
if msg.Status.ContactRequest != nil { |
||||
if !api.config.WithModule("contacts") { |
||||
return |
||||
} |
||||
if err := api.ContactManager.ContactrequestHandler(session, to, msg.Status.ContactRequest); err != nil { |
||||
log.Println("Ignoring invalid contact request.", err) |
||||
return |
||||
} |
||||
msg.Status.ContactRequest.Userid = session.Userid() |
||||
} |
||||
} else { |
||||
api.StatsCounter.CountUnicastChat() |
||||
} |
||||
|
||||
session.Unicast(to, chat, nil) |
||||
if msg.Mid != "" { |
||||
// Send out delivery confirmation status chat message.
|
||||
session.Unicast(session.Id, &channelling.DataChat{To: to, Type: "Chat", Chat: &channelling.DataChatMessage{Mid: msg.Mid, Status: &channelling.DataChatStatus{State: "sent"}}}, nil) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 api |
||||
|
||||
import ( |
||||
"log" |
||||
|
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
) |
||||
|
||||
func (api *channellingAPI) HandleConference(session *channelling.Session, conference *channelling.DataConference) { |
||||
// Check conference maximum size.
|
||||
if len(conference.Conference) > maxConferenceSize { |
||||
log.Println("Refusing to create conference above limit.", len(conference.Conference)) |
||||
return |
||||
} |
||||
|
||||
// Send conference update to anyone.
|
||||
for _, id := range conference.Conference { |
||||
if id != session.Id { |
||||
session.Unicast(id, conference, nil) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 api |
||||
|
||||
import ( |
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
) |
||||
|
||||
func (api *channellingAPI) HandleHello(session *channelling.Session, hello *channelling.DataHello, sender channelling.Sender) (*channelling.DataWelcome, error) { |
||||
// TODO(longsleep): Filter room id and user agent.
|
||||
session.Update(&channelling.SessionUpdate{Types: []string{"Ua"}, Ua: hello.Ua}) |
||||
|
||||
// Compatibily for old clients.
|
||||
roomName := hello.Name |
||||
if roomName == "" { |
||||
roomName = hello.Id |
||||
} |
||||
|
||||
room, err := session.JoinRoom(roomName, hello.Type, hello.Credentials, sender) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return &channelling.DataWelcome{ |
||||
Type: "Welcome", |
||||
Room: room, |
||||
Users: api.RoomStatusManager.RoomUsers(session), |
||||
}, nil |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 api |
||||
|
||||
import ( |
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
) |
||||
|
||||
func (api *channellingAPI) HandleRoom(session *channelling.Session, room *channelling.DataRoom) (*channelling.DataRoom, error) { |
||||
room, err := api.RoomStatusManager.UpdateRoom(session, room) |
||||
if err == nil { |
||||
session.Broadcast(room) |
||||
} |
||||
|
||||
return room, err |
||||
} |
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 api |
||||
|
||||
import ( |
||||
"log" |
||||
|
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
) |
||||
|
||||
func (api *channellingAPI) HandleSelf(session *channelling.Session) (*channelling.DataSelf, error) { |
||||
token, err := api.SessionEncoder.EncodeSessionToken(session) |
||||
if err != nil { |
||||
log.Println("Error in OnRegister", err) |
||||
return nil, err |
||||
} |
||||
|
||||
log.Println("Created new session token", len(token), token) |
||||
self := &channelling.DataSelf{ |
||||
Type: "Self", |
||||
Id: session.Id, |
||||
Sid: session.Sid, |
||||
Userid: session.Userid(), |
||||
Suserid: api.SessionEncoder.EncodeSessionUserID(session), |
||||
Token: token, |
||||
Version: api.config.Version, |
||||
ApiVersion: apiVersion, |
||||
Turn: api.TurnDataCreator.CreateTurnData(session), |
||||
Stun: api.config.StunURIs, |
||||
} |
||||
api.BusManager.Trigger(channelling.BusManagerSession, session.Id, session.Userid(), nil, nil) |
||||
|
||||
return self, nil |
||||
} |
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 api |
||||
|
||||
import ( |
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
) |
||||
|
||||
func (api *channellingAPI) HandleSessions(session *channelling.Session, sessions *channelling.DataSessionsRequest) (*channelling.DataSessions, error) { |
||||
switch sessions.Type { |
||||
case "contact": |
||||
if !api.config.WithModule("contacts") { |
||||
return nil, channelling.NewDataError("contacts_not_enabled", "incoming contacts session request with contacts disabled") |
||||
} |
||||
userID, err := api.ContactManager.GetContactID(session, sessions.Token) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &channelling.DataSessions{ |
||||
Type: "Sessions", |
||||
Users: api.SessionManager.GetUserSessions(session, userID), |
||||
Sessions: sessions, |
||||
}, nil |
||||
case "session": |
||||
id, err := session.DecodeAttestation(sessions.Token) |
||||
if err != nil { |
||||
return nil, channelling.NewDataError("bad_attestation", err.Error()) |
||||
} |
||||
session, ok := api.Unicaster.GetSession(id) |
||||
if !ok { |
||||
return nil, channelling.NewDataError("no_such_session", "cannot retrieve session") |
||||
} |
||||
return &channelling.DataSessions{ |
||||
Type: "Sessions", |
||||
Users: []*channelling.DataSession{session.Data()}, |
||||
Sessions: sessions, |
||||
}, nil |
||||
default: |
||||
return nil, channelling.NewDataError("bad_request", "unknown sessions request type") |
||||
} |
||||
} |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 api |
||||
|
||||
import ( |
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
) |
||||
|
||||
func (api *channellingAPI) HandleUsers(session *channelling.Session) (sessions *channelling.DataSessions, err error) { |
||||
if session.Hello { |
||||
sessions = &channelling.DataSessions{Type: "Users", Users: api.RoomStatusManager.RoomUsers(session)} |
||||
} else { |
||||
err = channelling.NewDataError("not_in_room", "Cannot list users without a current room") |
||||
} |
||||
|
||||
return |
||||
} |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
type SessionCreateRequest struct { |
||||
Id string |
||||
Session *DataSession |
||||
Room *DataRoom |
||||
SetAsDefault bool |
||||
} |
||||
|
||||
type DataSink struct { |
||||
SubjectOut string `json:subject_out"` |
||||
SubjectIn string `json:subject_in"` |
||||
} |
||||
|
||||
type DataSinkOutgoing struct { |
||||
Outgoing *DataOutgoing |
||||
ToUserid string |
||||
FromUserid string |
||||
Pipe string `json:",omitempty"` |
||||
} |
@ -0,0 +1,306 @@
@@ -0,0 +1,306 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"log" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/nats-io/nats" |
||||
|
||||
"github.com/strukturag/spreed-webrtc/go/natsconnection" |
||||
) |
||||
|
||||
const ( |
||||
BusManagerStartup = "startup" |
||||
BusManagerOffer = "offer" |
||||
BusManagerAnswer = "answer" |
||||
BusManagerBye = "bye" |
||||
BusManagerConnect = "connect" |
||||
BusManagerDisconnect = "disconnect" |
||||
BusManagerSession = "session" |
||||
) |
||||
|
||||
// A BusManager provides the API to interact with a bus.
|
||||
type BusManager interface { |
||||
ChannellingAPIConsumer |
||||
Start() |
||||
Publish(subject string, v interface{}) error |
||||
Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error |
||||
Trigger(name, from, payload string, data interface{}, pipeline *Pipeline) error |
||||
Subscribe(subject string, cb nats.Handler) (*nats.Subscription, error) |
||||
BindRecvChan(subject string, channel interface{}) (*nats.Subscription, error) |
||||
BindSendChan(subject string, channel interface{}) error |
||||
PrefixSubject(string) string |
||||
CreateSink(string) Sink |
||||
} |
||||
|
||||
// A BusTrigger is a container to serialize trigger events
|
||||
// for the bus backend.
|
||||
type BusTrigger struct { |
||||
Id string |
||||
Name string |
||||
From string |
||||
Payload string `json:",omitempty"` |
||||
Data interface{} `json:",omitempty"` |
||||
Pipeline string `json:",omitempty"` |
||||
} |
||||
|
||||
// BusSubjectTrigger returns the bus subject for trigger payloads.
|
||||
func BusSubjectTrigger(prefix, suffix string) string { |
||||
return fmt.Sprintf("%s.%s", prefix, suffix) |
||||
} |
||||
|
||||
// NewBusManager creates and initializes a new BusMager with the
|
||||
// provided flags for NATS support. It is intended to connect the
|
||||
// backend bus with a easy to use API to send and receive bus data.
|
||||
func NewBusManager(apiConsumer ChannellingAPIConsumer, id string, useNats bool, subjectPrefix string) BusManager { |
||||
var b BusManager |
||||
var err error |
||||
if useNats { |
||||
b, err = newNatsBus(apiConsumer, id, subjectPrefix) |
||||
if err == nil { |
||||
log.Println("NATS bus connected") |
||||
} else { |
||||
log.Println("Error connecting NATS bus", err) |
||||
b = &noopBus{apiConsumer, id} |
||||
} |
||||
} else { |
||||
b = &noopBus{apiConsumer, id} |
||||
} |
||||
|
||||
return b |
||||
} |
||||
|
||||
type noopBus struct { |
||||
ChannellingAPIConsumer |
||||
id string |
||||
} |
||||
|
||||
func (bus *noopBus) Start() { |
||||
// noop
|
||||
} |
||||
|
||||
func (bus *noopBus) Publish(subject string, v interface{}) error { |
||||
return nil |
||||
} |
||||
|
||||
func (bus *noopBus) Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error { |
||||
return nil |
||||
} |
||||
|
||||
func (bus *noopBus) Trigger(name, from, payload string, data interface{}, pipeline *Pipeline) error { |
||||
return nil |
||||
} |
||||
|
||||
func (bus *noopBus) PrefixSubject(subject string) string { |
||||
return subject |
||||
} |
||||
|
||||
func (bus *noopBus) BindRecvChan(subject string, channel interface{}) (*nats.Subscription, error) { |
||||
return nil, nil |
||||
} |
||||
|
||||
func (bus *noopBus) BindSendChan(subject string, channel interface{}) error { |
||||
return nil |
||||
} |
||||
|
||||
func (bus *noopBus) Subscribe(subject string, cb nats.Handler) (*nats.Subscription, error) { |
||||
return nil, nil |
||||
} |
||||
|
||||
func (bus *noopBus) CreateSink(id string) Sink { |
||||
return nil |
||||
} |
||||
|
||||
type natsBus struct { |
||||
ChannellingAPIConsumer |
||||
id string |
||||
prefix string |
||||
ec *natsconnection.EncodedConnection |
||||
triggerQueue chan *busQueueEntry |
||||
} |
||||
|
||||
func newNatsBus(apiConsumer ChannellingAPIConsumer, id, prefix string) (*natsBus, error) { |
||||
ec, err := natsconnection.EstablishJSONEncodedConnection(nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if prefix == "" { |
||||
prefix = "channelling.trigger" |
||||
} |
||||
// Create buffered channel for outbound NATS data.
|
||||
triggerQueue := make(chan *busQueueEntry, 50) |
||||
|
||||
return &natsBus{apiConsumer, id, prefix, ec, triggerQueue}, nil |
||||
} |
||||
|
||||
func (bus *natsBus) Start() { |
||||
// Start go routine to process outbount NATS publishing.
|
||||
go chPublish(bus.ec, bus.triggerQueue) |
||||
bus.Trigger(BusManagerStartup, bus.id, "", nil, nil) |
||||
} |
||||
|
||||
func (bus *natsBus) Publish(subject string, v interface{}) error { |
||||
return bus.ec.Publish(subject, v) |
||||
} |
||||
|
||||
func (bus *natsBus) Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error { |
||||
return bus.ec.Request(subject, v, vPtr, timeout) |
||||
} |
||||
|
||||
func (bus *natsBus) Trigger(name, from, payload string, data interface{}, pipeline *Pipeline) (err error) { |
||||
trigger := &BusTrigger{ |
||||
Id: bus.id, |
||||
Name: name, |
||||
From: from, |
||||
Payload: payload, |
||||
Data: data, |
||||
} |
||||
if pipeline != nil { |
||||
trigger.Pipeline = pipeline.GetID() |
||||
} |
||||
entry := &busQueueEntry{BusSubjectTrigger(bus.prefix, name), trigger} |
||||
select { |
||||
case bus.triggerQueue <- entry: |
||||
// sent ok
|
||||
default: |
||||
log.Println("Failed to queue NATS event - queue full?") |
||||
err = errors.New("NATS trigger queue full") |
||||
} |
||||
|
||||
return err |
||||
} |
||||
|
||||
func (bus *natsBus) PrefixSubject(sub string) string { |
||||
return fmt.Sprintf("%s.%s", bus.prefix, sub) |
||||
} |
||||
|
||||
func (bus *natsBus) Subscribe(subject string, cb nats.Handler) (*nats.Subscription, error) { |
||||
return bus.ec.Subscribe(subject, cb) |
||||
} |
||||
|
||||
func (bus *natsBus) BindRecvChan(subject string, channel interface{}) (*nats.Subscription, error) { |
||||
return bus.ec.BindRecvChan(subject, channel) |
||||
} |
||||
|
||||
func (bus *natsBus) BindSendChan(subject string, channel interface{}) error { |
||||
return bus.ec.BindSendChan(subject, channel) |
||||
} |
||||
|
||||
func (bus *natsBus) CreateSink(id string) (sink Sink) { |
||||
sink = newNatsSink(bus, id) |
||||
return |
||||
} |
||||
|
||||
type busQueueEntry struct { |
||||
subject string |
||||
data interface{} |
||||
} |
||||
|
||||
func chPublish(ec *natsconnection.EncodedConnection, channel chan (*busQueueEntry)) { |
||||
for { |
||||
entry := <-channel |
||||
err := ec.Publish(entry.subject, entry.data) |
||||
if err != nil { |
||||
log.Println("Failed to publish to NATS", entry.subject, err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
type natsSink struct { |
||||
sync.RWMutex |
||||
id string |
||||
bm BusManager |
||||
closed bool |
||||
SubjectOut string |
||||
SubjectIn string |
||||
sub *nats.Subscription |
||||
sendQueue chan *DataSinkOutgoing |
||||
} |
||||
|
||||
func newNatsSink(bm BusManager, id string) *natsSink { |
||||
sink := &natsSink{ |
||||
id: id, |
||||
bm: bm, |
||||
SubjectOut: bm.PrefixSubject(fmt.Sprintf("sink.%s.out", id)), |
||||
SubjectIn: bm.PrefixSubject(fmt.Sprintf("sink.%s.in", id)), |
||||
} |
||||
|
||||
sink.sendQueue = make(chan *DataSinkOutgoing, 100) |
||||
bm.BindSendChan(sink.SubjectOut, sink.sendQueue) |
||||
|
||||
return sink |
||||
} |
||||
|
||||
func (sink *natsSink) Write(outgoing *DataSinkOutgoing) (err error) { |
||||
if sink.Enabled() { |
||||
log.Println("Sending via NATS sink", sink.SubjectOut, outgoing) |
||||
sink.sendQueue <- outgoing |
||||
} |
||||
return err |
||||
} |
||||
|
||||
func (sink *natsSink) Enabled() bool { |
||||
sink.RLock() |
||||
defer sink.RUnlock() |
||||
return sink.closed == false |
||||
} |
||||
|
||||
func (sink *natsSink) Close() { |
||||
sink.Lock() |
||||
defer sink.Unlock() |
||||
if sink.sub != nil { |
||||
err := sink.sub.Unsubscribe() |
||||
if err != nil { |
||||
log.Println("Failed to unsubscribe NATS sink", err) |
||||
} else { |
||||
sink.sub = nil |
||||
} |
||||
} |
||||
sink.closed = true |
||||
} |
||||
|
||||
func (sink *natsSink) Export() *DataSink { |
||||
return &DataSink{ |
||||
SubjectOut: sink.SubjectOut, |
||||
SubjectIn: sink.SubjectIn, |
||||
} |
||||
} |
||||
|
||||
func (sink *natsSink) BindRecvChan(channel interface{}) (*nats.Subscription, error) { |
||||
sink.Lock() |
||||
defer sink.Unlock() |
||||
if sink.sub != nil { |
||||
sink.sub.Unsubscribe() |
||||
sink.sub = nil |
||||
} |
||||
sub, err := sink.bm.BindRecvChan(sink.SubjectIn, channel) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
sink.sub = sub |
||||
return sub, nil |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
type ClientStats interface { |
||||
ClientInfo(details bool) (int, map[string]*DataSession, map[string]string) |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
package channelling |
||||
|
||||
import ( |
||||
"net/http" |
||||
) |
||||
|
||||
type Config struct { |
||||
Title string // Title
|
||||
Ver string `json:"-"` // Version (not exported to Javascript)
|
||||
S string // Static URL prefix with version
|
||||
B string // Base URL
|
||||
Token string // Server token
|
||||
Renegotiation bool // Renegotiation flag
|
||||
StunURIs []string // STUN server URIs
|
||||
TurnURIs []string // TURN server URIs
|
||||
Tokens bool // True when we got a tokens file
|
||||
Version string // Server version number
|
||||
UsersEnabled bool // Flag if users are enabled
|
||||
UsersAllowRegistration bool // Flag if users can register
|
||||
UsersMode string // Users mode string
|
||||
DefaultRoomEnabled bool // Flag if default room ("") is enabled
|
||||
Plugin string // Plugin to load
|
||||
AuthorizeRoomCreation bool // Whether a user account is required to create rooms
|
||||
AuthorizeRoomJoin bool // Whether a user account is required to join rooms
|
||||
Modules []string // List of enabled modules
|
||||
ModulesTable map[string]bool `json:"-"` // Map of enabled modules
|
||||
GlobalRoomID string `json:"-"` // Id of the global room (not exported to Javascript)
|
||||
ContentSecurityPolicy string `json:"-"` // HTML content security policy
|
||||
ContentSecurityPolicyReportOnly string `json:"-"` // HTML content security policy in report only mode
|
||||
RoomTypeDefault string `json:"-"` // New rooms default to this type
|
||||
} |
||||
|
||||
func (config *Config) WithModule(m string) bool { |
||||
if val, ok := config.ModulesTable[m]; ok && val { |
||||
return true |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
func (config *Config) Get(request *http.Request) (int, interface{}, http.Header) { |
||||
return 200, config, http.Header{"Content-Type": {"application/json; charset=utf-8"}} |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
type ContactManager interface { |
||||
ContactrequestHandler(*Session, string, *DataContactRequest) error |
||||
GetContactID(*Session, string) (string, error) |
||||
} |
@ -0,0 +1,266 @@
@@ -0,0 +1,266 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"errors" |
||||
"log" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/strukturag/spreed-webrtc/go/buffercache" |
||||
) |
||||
|
||||
type PipelineFeedLine struct { |
||||
Seq int |
||||
Msg *DataOutgoing |
||||
} |
||||
|
||||
type Pipeline struct { |
||||
PipelineManager PipelineManager |
||||
mutex sync.RWMutex |
||||
namespace string |
||||
id string |
||||
from *Session |
||||
to *Session |
||||
expires *time.Time |
||||
data []*DataSinkOutgoing |
||||
sink Sink |
||||
recvQueue chan *DataIncoming |
||||
closed bool |
||||
} |
||||
|
||||
func NewPipeline(manager PipelineManager, |
||||
namespace string, |
||||
id string, |
||||
from *Session, |
||||
duration time.Duration) *Pipeline { |
||||
pipeline := &Pipeline{ |
||||
PipelineManager: manager, |
||||
namespace: namespace, |
||||
id: id, |
||||
from: from, |
||||
recvQueue: make(chan *DataIncoming, 100), |
||||
} |
||||
go pipeline.receive() |
||||
pipeline.Refresh(duration) |
||||
return pipeline |
||||
} |
||||
|
||||
func (pipeline *Pipeline) receive() { |
||||
// TODO(longsleep): Call to ToSession() should be avoided because it locks.
|
||||
api := pipeline.PipelineManager.GetChannellingAPI() |
||||
for data := range pipeline.recvQueue { |
||||
_, err := api.OnIncoming(nil, pipeline.ToSession(), data) |
||||
if err != nil { |
||||
// TODO(longsleep): Handle reply and error.
|
||||
log.Println("Pipeline receive incoming error", err) |
||||
} |
||||
} |
||||
log.Println("Pipeline receive done") |
||||
} |
||||
|
||||
func (pipeline *Pipeline) GetID() string { |
||||
return pipeline.id |
||||
} |
||||
|
||||
func (pipeline *Pipeline) Refresh(duration time.Duration) { |
||||
pipeline.mutex.Lock() |
||||
pipeline.refresh(duration) |
||||
pipeline.mutex.Unlock() |
||||
} |
||||
|
||||
func (pipeline *Pipeline) refresh(duration time.Duration) { |
||||
expiration := time.Now().Add(duration) |
||||
pipeline.expires = &expiration |
||||
} |
||||
|
||||
func (pipeline *Pipeline) Add(msg *DataSinkOutgoing) *Pipeline { |
||||
msg.Pipe = pipeline.id |
||||
pipeline.mutex.Lock() |
||||
pipeline.data = append(pipeline.data, msg) |
||||
pipeline.refresh(30 * time.Second) |
||||
pipeline.mutex.Unlock() |
||||
|
||||
return pipeline |
||||
} |
||||
|
||||
func (pipeline *Pipeline) Send(b buffercache.Buffer) { |
||||
// Noop.
|
||||
} |
||||
|
||||
func (pipeline *Pipeline) Index() uint64 { |
||||
return 0 |
||||
} |
||||
|
||||
func (pipeline *Pipeline) Close() { |
||||
pipeline.mutex.Lock() |
||||
if !pipeline.closed { |
||||
pipeline.expires = nil |
||||
if pipeline.sink != nil { |
||||
pipeline.sink = nil |
||||
} |
||||
close(pipeline.recvQueue) |
||||
pipeline.closed = true |
||||
log.Println("Closed pipeline") |
||||
} |
||||
pipeline.mutex.Unlock() |
||||
} |
||||
|
||||
func (pipeline *Pipeline) Expired() bool { |
||||
var expired bool |
||||
pipeline.mutex.RLock() |
||||
if pipeline.expires == nil { |
||||
expired = true |
||||
} else { |
||||
expired = pipeline.expires.Before(time.Now()) |
||||
} |
||||
pipeline.mutex.RUnlock() |
||||
|
||||
return expired |
||||
} |
||||
|
||||
func (pipeline *Pipeline) FromSession() *Session { |
||||
pipeline.mutex.RLock() |
||||
defer pipeline.mutex.RUnlock() |
||||
return pipeline.from |
||||
} |
||||
|
||||
func (pipeline *Pipeline) ToSession() *Session { |
||||
pipeline.mutex.RLock() |
||||
defer pipeline.mutex.RUnlock() |
||||
return pipeline.to |
||||
} |
||||
|
||||
func (pipeline *Pipeline) JSONFeed(since, limit int) ([]byte, error) { |
||||
pipeline.mutex.RLock() |
||||
var lineRaw []byte |
||||
var line *PipelineFeedLine |
||||
var buffer bytes.Buffer |
||||
var err error |
||||
data := pipeline.data[since:] |
||||
count := 0 |
||||
for seq, msg := range data { |
||||
line = &PipelineFeedLine{ |
||||
Seq: seq + since, |
||||
Msg: msg.Outgoing, |
||||
} |
||||
lineRaw, err = json.Marshal(line) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
buffer.Write(lineRaw) |
||||
buffer.WriteString("\n") |
||||
|
||||
count++ |
||||
if limit > 0 && count >= limit { |
||||
break |
||||
} |
||||
} |
||||
pipeline.mutex.RUnlock() |
||||
|
||||
return buffer.Bytes(), nil |
||||
} |
||||
|
||||
func (pipeline *Pipeline) FlushOutgoing(hub Hub, client *Client, to string, outgoing *DataOutgoing) bool { |
||||
//log.Println("Flush outgoing via pipeline", to, client == nil)
|
||||
if client == nil { |
||||
sinkOutgoing := &DataSinkOutgoing{ |
||||
Outgoing: outgoing, |
||||
} |
||||
|
||||
pipeline.mutex.Lock() |
||||
sink := pipeline.sink |
||||
toSession := pipeline.to |
||||
fromSession := pipeline.from |
||||
|
||||
for { |
||||
if sink != nil && sink.Enabled() { |
||||
// Sink it.
|
||||
pipeline.mutex.Unlock() |
||||
break |
||||
} |
||||
|
||||
sink, toSession = pipeline.PipelineManager.FindSinkAndSession(to) |
||||
if sink != nil { |
||||
pipeline.to = toSession |
||||
err := pipeline.attach(sink) |
||||
if err == nil { |
||||
pipeline.mutex.Unlock() |
||||
|
||||
// Create incoming receiver.
|
||||
sink.BindRecvChan(pipeline.recvQueue) |
||||
|
||||
// Sink it.
|
||||
break |
||||
} |
||||
} |
||||
|
||||
// Not pipelined, do nothing.
|
||||
pipeline.mutex.Unlock() |
||||
break |
||||
} |
||||
|
||||
if fromSession != nil { |
||||
sinkOutgoing.FromUserid = fromSession.Userid() |
||||
} |
||||
if toSession != nil { |
||||
sinkOutgoing.ToUserid = toSession.Userid() |
||||
} |
||||
pipeline.Add(sinkOutgoing) |
||||
|
||||
if sink != nil { |
||||
// Pipelined, sink data.
|
||||
sink.Write(sinkOutgoing) |
||||
return true |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
func (pipeline *Pipeline) Attach(sink Sink) error { |
||||
pipeline.mutex.Lock() |
||||
defer pipeline.mutex.Unlock() |
||||
|
||||
// Sink existing data first.
|
||||
log.Println("Attach sink to pipeline", pipeline.id) |
||||
err := pipeline.attach(sink) |
||||
if err == nil { |
||||
for _, msg := range pipeline.data { |
||||
log.Println("Flushing pipeline to sink after attach", len(pipeline.data)) |
||||
sink.Write(msg) |
||||
} |
||||
} |
||||
|
||||
return err |
||||
} |
||||
|
||||
func (pipeline *Pipeline) attach(sink Sink) error { |
||||
if pipeline.sink != nil { |
||||
return errors.New("pipeline already attached to sink") |
||||
} |
||||
pipeline.sink = sink |
||||
return nil |
||||
} |
@ -0,0 +1,244 @@
@@ -0,0 +1,244 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
PipelineNamespaceCall = "call" |
||||
) |
||||
|
||||
type PipelineManager interface { |
||||
BusManager |
||||
SessionStore |
||||
UserStore |
||||
SessionCreator |
||||
GetPipelineByID(id string) (pipeline *Pipeline, ok bool) |
||||
GetPipeline(namespace string, sender Sender, session *Session, to string) *Pipeline |
||||
FindSinkAndSession(to string) (Sink, *Session) |
||||
} |
||||
|
||||
type pipelineManager struct { |
||||
BusManager |
||||
SessionStore |
||||
UserStore |
||||
SessionCreator |
||||
mutex sync.RWMutex |
||||
pipelineTable map[string]*Pipeline |
||||
sessionTable map[string]*Session |
||||
sessionByBusIDTable map[string]*Session |
||||
sessionSinkTable map[string]Sink |
||||
duration time.Duration |
||||
defaultSinkID string |
||||
enabled bool |
||||
} |
||||
|
||||
func NewPipelineManager(busManager BusManager, sessionStore SessionStore, userStore UserStore, sessionCreator SessionCreator) PipelineManager { |
||||
plm := &pipelineManager{ |
||||
BusManager: busManager, |
||||
SessionStore: sessionStore, |
||||
UserStore: userStore, |
||||
SessionCreator: sessionCreator, |
||||
pipelineTable: make(map[string]*Pipeline), |
||||
sessionTable: make(map[string]*Session), |
||||
sessionByBusIDTable: make(map[string]*Session), |
||||
sessionSinkTable: make(map[string]Sink), |
||||
duration: 60 * time.Second, |
||||
} |
||||
|
||||
return plm |
||||
} |
||||
|
||||
func (plm *pipelineManager) Start() { |
||||
plm.enabled = true |
||||
|
||||
plm.start() |
||||
|
||||
plm.Subscribe("channelling.session.create", plm.sessionCreate) |
||||
plm.Subscribe("channelling.session.close", plm.sessionClose) |
||||
} |
||||
|
||||
func (plm *pipelineManager) cleanup() { |
||||
plm.mutex.Lock() |
||||
for id, pipeline := range plm.pipelineTable { |
||||
if pipeline.Expired() { |
||||
pipeline.Close() |
||||
delete(plm.pipelineTable, id) |
||||
} |
||||
} |
||||
plm.mutex.Unlock() |
||||
} |
||||
|
||||
func (plm *pipelineManager) start() { |
||||
c := time.Tick(30 * time.Second) |
||||
go func() { |
||||
for _ = range c { |
||||
plm.cleanup() |
||||
} |
||||
}() |
||||
} |
||||
|
||||
func (plm *pipelineManager) sessionCreate(subject, reply string, msg *SessionCreateRequest) { |
||||
log.Println("sessionCreate via NATS", subject, reply, msg) |
||||
|
||||
if msg.Session == nil || msg.Id == "" { |
||||
return |
||||
} |
||||
|
||||
var sink Sink |
||||
|
||||
plm.mutex.Lock() |
||||
session, ok := plm.sessionByBusIDTable[msg.Id] |
||||
if ok { |
||||
// Remove existing session with same ID.
|
||||
delete(plm.sessionTable, session.Id) |
||||
sink, _ = plm.sessionSinkTable[session.Id] |
||||
delete(plm.sessionSinkTable, session.Id) |
||||
session.Close() |
||||
} |
||||
session = plm.CreateSession(nil, "") |
||||
plm.sessionByBusIDTable[msg.Id] = session |
||||
plm.sessionTable[session.Id] = session |
||||
if sink == nil { |
||||
sink = plm.CreateSink(msg.Id) |
||||
log.Println("Created NATS sink", msg.Id) |
||||
} |
||||
if reply != "" { |
||||
// Always reply with our sink data
|
||||
plm.Publish(reply, sink.Export()) |
||||
} |
||||
plm.sessionSinkTable[session.Id] = sink |
||||
|
||||
if msg.SetAsDefault { |
||||
plm.defaultSinkID = session.Id |
||||
log.Println("Using NATS sink as default session", session.Id) |
||||
} |
||||
plm.mutex.Unlock() |
||||
|
||||
if msg.Session.Status != nil { |
||||
session.Status = msg.Session.Status |
||||
} |
||||
|
||||
if msg.Session.Userid != "" { |
||||
session.SetUseridFake(msg.Session.Userid) |
||||
} |
||||
|
||||
if msg.Room != nil { |
||||
room, err := session.JoinRoom(msg.Room.Name, msg.Room.Type, msg.Room.Credentials, nil) |
||||
log.Println("Joined NATS session to room", room, err) |
||||
} |
||||
|
||||
session.BroadcastStatus() |
||||
} |
||||
|
||||
func (plm *pipelineManager) sessionClose(subject, reply string, id string) { |
||||
log.Println("sessionClose via NATS", subject, reply, id) |
||||
|
||||
if id == "" { |
||||
return |
||||
} |
||||
|
||||
plm.mutex.Lock() |
||||
session, ok := plm.sessionByBusIDTable[id] |
||||
if ok { |
||||
delete(plm.sessionByBusIDTable, id) |
||||
delete(plm.sessionTable, session.Id) |
||||
if sink, ok := plm.sessionSinkTable[session.Id]; ok { |
||||
delete(plm.sessionSinkTable, session.Id) |
||||
sink.Close() |
||||
} |
||||
} |
||||
plm.mutex.Unlock() |
||||
|
||||
if ok { |
||||
session.Close() |
||||
} |
||||
} |
||||
|
||||
func (plm *pipelineManager) GetPipelineByID(id string) (*Pipeline, bool) { |
||||
plm.mutex.RLock() |
||||
pipeline, ok := plm.pipelineTable[id] |
||||
plm.mutex.RUnlock() |
||||
return pipeline, ok |
||||
} |
||||
|
||||
func (plm *pipelineManager) PipelineID(namespace string, sender Sender, session *Session, to string) string { |
||||
return fmt.Sprintf("%s.%s.%s", namespace, session.Id, to) |
||||
} |
||||
|
||||
func (plm *pipelineManager) GetPipeline(namespace string, sender Sender, session *Session, to string) *Pipeline { |
||||
if !plm.enabled { |
||||
return nil |
||||
} |
||||
|
||||
id := plm.PipelineID(namespace, sender, session, to) |
||||
|
||||
plm.mutex.Lock() |
||||
pipeline, ok := plm.pipelineTable[id] |
||||
if ok { |
||||
// Refresh. We do not care if the pipeline is expired.
|
||||
pipeline.Refresh(plm.duration) |
||||
plm.mutex.Unlock() |
||||
return pipeline |
||||
} |
||||
|
||||
log.Println("Creating pipeline", namespace, id) |
||||
pipeline = NewPipeline(plm, namespace, id, session, plm.duration) |
||||
plm.pipelineTable[id] = pipeline |
||||
plm.mutex.Unlock() |
||||
|
||||
return pipeline |
||||
} |
||||
|
||||
func (plm *pipelineManager) FindSinkAndSession(to string) (sink Sink, session *Session) { |
||||
plm.mutex.RLock() |
||||
|
||||
var found bool |
||||
if sink, found = plm.sessionSinkTable[to]; found { |
||||
session, _ = plm.sessionTable[to] |
||||
plm.mutex.RUnlock() |
||||
if sink.Enabled() { |
||||
log.Println("Pipeline sink found via manager", sink) |
||||
return sink, session |
||||
} |
||||
} else { |
||||
plm.mutex.RUnlock() |
||||
} |
||||
|
||||
if plm.defaultSinkID != "" && to != plm.defaultSinkID { |
||||
// Keep target to while returning a the default sink.
|
||||
log.Println("Find sink via default sink ID", plm.defaultSinkID) |
||||
sink, _ = plm.FindSinkAndSession(plm.defaultSinkID) |
||||
if sink != nil { |
||||
if session, found = plm.GetSession(to); found { |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
return nil, nil |
||||
} |
@ -0,0 +1,98 @@
@@ -0,0 +1,98 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 server |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"net/http" |
||||
"strconv" |
||||
|
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
|
||||
"github.com/gorilla/mux" |
||||
) |
||||
|
||||
type Pipelines struct { |
||||
channelling.PipelineManager |
||||
API channelling.ChannellingAPI |
||||
} |
||||
|
||||
func (pipelines *Pipelines) Get(request *http.Request) (int, interface{}, http.Header) { |
||||
vars := mux.Vars(request) |
||||
id, ok := vars["id"] |
||||
if !ok { |
||||
return http.StatusNotFound, "", nil |
||||
} |
||||
|
||||
pipeline, ok := pipelines.GetPipelineByID(id) |
||||
if !ok { |
||||
return http.StatusNotFound, "", nil |
||||
} |
||||
|
||||
since := 0 |
||||
limit := 0 |
||||
if sinceParam := request.Form.Get("since"); sinceParam != "" { |
||||
since, _ = strconv.Atoi(sinceParam) |
||||
} |
||||
if limitParam := request.Form.Get("limit"); limitParam != "" { |
||||
limit, _ = strconv.Atoi(limitParam) |
||||
} |
||||
|
||||
result, err := pipeline.JSONFeed(since, limit) |
||||
if err != nil { |
||||
return http.StatusInternalServerError, err.Error(), nil |
||||
} |
||||
|
||||
return http.StatusOK, result, nil |
||||
} |
||||
|
||||
func (pipelines *Pipelines) Post(request *http.Request) (int, interface{}, http.Header) { |
||||
vars := mux.Vars(request) |
||||
id, ok := vars["id"] |
||||
if !ok { |
||||
return http.StatusNotFound, "", nil |
||||
} |
||||
|
||||
pipeline, ok := pipelines.GetPipelineByID(id) |
||||
if !ok { |
||||
return http.StatusNotFound, "", nil |
||||
} |
||||
|
||||
var incoming channelling.DataIncoming |
||||
dec := json.NewDecoder(request.Body) |
||||
if err := dec.Decode(&incoming); err != nil { |
||||
return http.StatusBadRequest, err.Error(), nil |
||||
} |
||||
|
||||
result := &channelling.DataOutgoing{ |
||||
From: pipeline.FromSession().Id, |
||||
Iid: incoming.Iid, |
||||
} |
||||
reply, err := pipelines.API.OnIncoming(pipeline, pipeline.ToSession(), &incoming) |
||||
if err == nil { |
||||
result.Data = reply |
||||
} else { |
||||
result.Data = err |
||||
} |
||||
|
||||
return http.StatusOK, result, nil |
||||
} |
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
import ( |
||||
"time" |
||||
) |
||||
|
||||
type SessionAttestation struct { |
||||
refresh int64 |
||||
token string |
||||
s *Session |
||||
} |
||||
|
||||
func (sa *SessionAttestation) Update() (string, error) { |
||||
token, err := sa.Encode() |
||||
if err == nil { |
||||
sa.token = token |
||||
sa.refresh = time.Now().Unix() + 180 // expires after 3 minutes
|
||||
} |
||||
return token, err |
||||
} |
||||
|
||||
func (sa *SessionAttestation) Token() (token string) { |
||||
if sa.refresh < time.Now().Unix() { |
||||
token, _ = sa.Update() |
||||
} else { |
||||
token = sa.token |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (sa *SessionAttestation) Encode() (string, error) { |
||||
return sa.s.attestations.Encode("attestation", sa.s.Id) |
||||
} |
||||
|
||||
func (sa *SessionAttestation) Decode(token string) (string, error) { |
||||
var id string |
||||
err := sa.s.attestations.Decode("attestation", token, &id) |
||||
return id, err |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
type SessionCreator interface { |
||||
CreateSession(st *SessionToken, userid string) *Session |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
type SessionStore interface { |
||||
GetSession(id string) (session *Session, ok bool) |
||||
} |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
type SessionToken struct { |
||||
Id string // Public session id.
|
||||
Sid string // Secret session id.
|
||||
Userid string // Public user id.
|
||||
Nonce string `json:"Nonce,omitempty"` // User autentication nonce.
|
||||
} |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
type SessionUpdate struct { |
||||
Types []string |
||||
Ua string |
||||
Prio int |
||||
Status interface{} |
||||
} |
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
import ( |
||||
"github.com/nats-io/nats" |
||||
) |
||||
|
||||
// Sink connects a Pipeline with end points in both directions by
|
||||
// getting attached to a Pipeline.
|
||||
type Sink interface { |
||||
// Write sends outgoing data on the sink
|
||||
Write(*DataSinkOutgoing) error |
||||
Enabled() bool |
||||
Close() |
||||
Export() *DataSink |
||||
BindRecvChan(channel interface{}) (*nats.Subscription, error) |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
type TurnDataCreator interface { |
||||
CreateTurnData(*Session) *DataTurn |
||||
} |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
type Unicaster interface { |
||||
SessionStore |
||||
OnConnect(*Client, *Session) |
||||
OnDisconnect(*Client, *Session) |
||||
Unicast(to string, outgoing *DataOutgoing, pipeline *Pipeline) |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 channelling |
||||
|
||||
type UserStore interface { |
||||
GetUser(id string) (user *User, ok bool) |
||||
} |
@ -0,0 +1,140 @@
@@ -0,0 +1,140 @@
|
||||
package natsconnection |
||||
|
||||
import ( |
||||
"errors" |
||||
"log" |
||||
"time" |
||||
|
||||
"github.com/nats-io/nats" |
||||
) |
||||
|
||||
// DefaultNatsEstablishTimeout is the default timeout for
|
||||
// calls to EstablishNatsConnection.
|
||||
var DefaultEstablishTimeout = 60 * time.Second |
||||
|
||||
// DefaultNatsURL is the default NATS server URL used for
|
||||
// calls to NewConnection and EstablishConnection.
|
||||
var DefaultURL = nats.DefaultURL |
||||
|
||||
// Connection implements the wrapped nats.Conn.
|
||||
type Connection struct { |
||||
*nats.Conn |
||||
} |
||||
|
||||
// EncodedConnection implements the wrapped nats.EncodedConn.
|
||||
type EncodedConnection struct { |
||||
*nats.EncodedConn |
||||
} |
||||
|
||||
// NewConnection creates a connetion to the default NATS server
|
||||
// and tries to establish the connection. It returns the connection
|
||||
// and any connection error encountered.
|
||||
func NewConnection() (*Connection, error) { |
||||
opts := &nats.Options{ |
||||
Url: DefaultURL, |
||||
AllowReconnect: true, |
||||
MaxReconnect: -1, // Reconnect forever.
|
||||
ReconnectWait: nats.DefaultReconnectWait, |
||||
Timeout: nats.DefaultTimeout, |
||||
PingInterval: nats.DefaultPingInterval, |
||||
MaxPingsOut: nats.DefaultMaxPingOut, |
||||
SubChanLen: nats.DefaultMaxChanLen, |
||||
ClosedCB: func(conn *nats.Conn) { |
||||
log.Println("NATS connection closed") |
||||
}, |
||||
DisconnectedCB: func(conn *nats.Conn) { |
||||
log.Println("NATS disconnected") |
||||
}, |
||||
ReconnectedCB: func(conn *nats.Conn) { |
||||
log.Println("NATS reconnected") |
||||
}, |
||||
AsyncErrorCB: func(conn *nats.Conn, sub *nats.Subscription, err error) { |
||||
log.Println("NATS async error", sub, err) |
||||
}, |
||||
} |
||||
|
||||
nc, err := opts.Connect() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return &Connection{nc}, nil |
||||
} |
||||
|
||||
// NewJSONEncodedConnection creates a JSON-encoded connetion to the
|
||||
// default NATS server and tries to establish the connection. It
|
||||
// returns the JSON-encoded connection and any connection error
|
||||
// encountered.
|
||||
func NewJSONEncodedConnection() (*EncodedConnection, error) { |
||||
nc, err := NewConnection() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
ec, err := nats.NewEncodedConn(nc.Conn, nats.JSON_ENCODER) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &EncodedConnection{ec}, nil |
||||
} |
||||
|
||||
// EstablishConnection is a blocking way to create and establish
|
||||
// connection to the default NATS server. The function will only return
|
||||
// after a timeout has reached or a connection has been established. It
|
||||
// returns the connection and and any timeout error encountered.
|
||||
func EstablishConnection(timeout *time.Duration) (*Connection, error) { |
||||
if timeout == nil { |
||||
timeout = &DefaultEstablishTimeout |
||||
} |
||||
connch := make(chan *Connection, 1) |
||||
errch := make(chan error, 1) |
||||
go func() { |
||||
notify := true |
||||
for { |
||||
nc, err := NewConnection() |
||||
if err == nil { |
||||
connch <- nc |
||||
break |
||||
} |
||||
switch err { |
||||
case nats.ErrTimeout: |
||||
fallthrough |
||||
case nats.ErrNoServers: |
||||
if notify { |
||||
notify = false |
||||
log.Println("Waiting for NATS server to become available") |
||||
} |
||||
time.Sleep(1 * time.Second) |
||||
continue |
||||
default: |
||||
errch <- err |
||||
break |
||||
} |
||||
} |
||||
}() |
||||
|
||||
select { |
||||
case conn := <-connch: |
||||
return conn, nil |
||||
case err := <-errch: |
||||
return nil, err |
||||
case <-time.After(*timeout): |
||||
return nil, errors.New("NATS connection: timeout") |
||||
} |
||||
} |
||||
|
||||
// EstablishJSONEncodedConnection is a blocking way to create and establish
|
||||
// JSON-encoded connection to the default NATS server. The function will
|
||||
// only return after a timeout has reached or a connection has been
|
||||
// established. It returns the JSON-encoded connection and and any timeout
|
||||
// error encountered.
|
||||
func EstablishJSONEncodedConnection(timeout *time.Duration) (*EncodedConnection, error) { |
||||
nc, err := EstablishConnection(timeout) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
ec, err := nats.NewEncodedConn(nc.Conn, nats.JSON_ENCODER) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &EncodedConnection{ec}, nil |
||||
} |
@ -1,329 +0,0 @@
@@ -1,329 +0,0 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
maxConferenceSize = 100 |
||||
apiVersion = 1.4 // Keep this in sync with CHANNELING-API docs.Hand
|
||||
) |
||||
|
||||
type ChannellingAPI interface { |
||||
OnConnect(Client, *Session) (interface{}, error) |
||||
OnDisconnect(Client, *Session) |
||||
OnIncoming(Sender, *Session, *DataIncoming) (interface{}, error) |
||||
} |
||||
|
||||
type channellingAPI struct { |
||||
*Config |
||||
RoomStatusManager |
||||
SessionEncoder |
||||
SessionManager |
||||
StatsCounter |
||||
ContactManager |
||||
TurnDataCreator |
||||
Unicaster |
||||
} |
||||
|
||||
func NewChannellingAPI(config *Config, roomStatus RoomStatusManager, sessionEncoder SessionEncoder, sessionManager SessionManager, statsCounter StatsCounter, contactManager ContactManager, turnDataCreator TurnDataCreator, unicaster Unicaster) ChannellingAPI { |
||||
return &channellingAPI{ |
||||
config, |
||||
roomStatus, |
||||
sessionEncoder, |
||||
sessionManager, |
||||
statsCounter, |
||||
contactManager, |
||||
turnDataCreator, |
||||
unicaster, |
||||
} |
||||
} |
||||
|
||||
func (api *channellingAPI) OnConnect(client Client, session *Session) (interface{}, error) { |
||||
api.Unicaster.OnConnect(client, session) |
||||
return api.HandleSelf(session) |
||||
} |
||||
|
||||
func (api *channellingAPI) OnDisconnect(client Client, session *Session) { |
||||
api.Unicaster.OnDisconnect(client, session) |
||||
} |
||||
|
||||
func (api *channellingAPI) OnIncoming(sender Sender, session *Session, msg *DataIncoming) (interface{}, error) { |
||||
switch msg.Type { |
||||
case "Self": |
||||
return api.HandleSelf(session) |
||||
case "Hello": |
||||
if msg.Hello == nil { |
||||
return nil, NewDataError("bad_request", "message did not contain Hello") |
||||
} |
||||
|
||||
return api.HandleHello(session, msg.Hello, sender) |
||||
case "Offer": |
||||
if msg.Offer == nil { |
||||
log.Println("Received invalid offer message.", msg) |
||||
break |
||||
} |
||||
|
||||
// TODO(longsleep): Validate offer
|
||||
session.Unicast(msg.Offer.To, msg.Offer) |
||||
case "Candidate": |
||||
if msg.Candidate == nil { |
||||
log.Println("Received invalid candidate message.", msg) |
||||
break |
||||
} |
||||
|
||||
// TODO(longsleep): Validate candidate
|
||||
session.Unicast(msg.Candidate.To, msg.Candidate) |
||||
case "Answer": |
||||
if msg.Answer == nil { |
||||
log.Println("Received invalid answer message.", msg) |
||||
break |
||||
} |
||||
|
||||
// TODO(longsleep): Validate Answer
|
||||
session.Unicast(msg.Answer.To, msg.Answer) |
||||
case "Users": |
||||
return api.HandleUsers(session) |
||||
case "Authentication": |
||||
if msg.Authentication == nil || msg.Authentication.Authentication == nil { |
||||
return nil, NewDataError("bad_request", "message did not contain Authentication") |
||||
} |
||||
|
||||
return api.HandleAuthentication(session, msg.Authentication.Authentication) |
||||
case "Bye": |
||||
if msg.Bye == nil { |
||||
log.Println("Received invalid bye message.", msg) |
||||
break |
||||
} |
||||
|
||||
session.Unicast(msg.Bye.To, msg.Bye) |
||||
case "Status": |
||||
if msg.Status == nil { |
||||
log.Println("Received invalid status message.", msg) |
||||
break |
||||
} |
||||
|
||||
//log.Println("Status", msg.Status)
|
||||
session.Update(&SessionUpdate{Types: []string{"Status"}, Status: msg.Status.Status}) |
||||
session.BroadcastStatus() |
||||
case "Chat": |
||||
if msg.Chat == nil || msg.Chat.Chat == nil { |
||||
log.Println("Received invalid chat message.", msg) |
||||
break |
||||
} |
||||
|
||||
api.HandleChat(session, msg.Chat) |
||||
case "Conference": |
||||
if msg.Conference == nil { |
||||
log.Println("Received invalid conference message.", msg) |
||||
break |
||||
} |
||||
|
||||
api.HandleConference(session, msg.Conference) |
||||
case "Alive": |
||||
return msg.Alive, nil |
||||
case "Sessions": |
||||
if msg.Sessions == nil || msg.Sessions.Sessions == nil { |
||||
return nil, NewDataError("bad_request", "message did not contain Sessions") |
||||
} |
||||
|
||||
return api.HandleSessions(session, msg.Sessions.Sessions) |
||||
case "Room": |
||||
if msg.Room == nil { |
||||
return nil, NewDataError("bad_request", "message did not contain Room") |
||||
} |
||||
|
||||
return api.HandleRoom(session, msg.Room) |
||||
default: |
||||
log.Println("OnText unhandled message type", msg.Type) |
||||
} |
||||
|
||||
return nil, nil |
||||
} |
||||
|
||||
func (api *channellingAPI) HandleSelf(session *Session) (*DataSelf, error) { |
||||
token, err := api.EncodeSessionToken(session) |
||||
if err != nil { |
||||
log.Println("Error in OnRegister", err) |
||||
return nil, err |
||||
} |
||||
|
||||
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, |
||||
ApiVersion: apiVersion, |
||||
Turn: api.CreateTurnData(session), |
||||
Stun: api.StunURIs, |
||||
} |
||||
|
||||
return self, nil |
||||
} |
||||
|
||||
func (api *channellingAPI) HandleHello(session *Session, hello *DataHello, sender Sender) (*DataWelcome, error) { |
||||
// TODO(longsleep): Filter room id and user agent.
|
||||
session.Update(&SessionUpdate{Types: []string{"Ua"}, Ua: hello.Ua}) |
||||
|
||||
// Compatibily for old clients.
|
||||
roomName := hello.Name |
||||
if roomName == "" { |
||||
roomName = hello.Id |
||||
} |
||||
|
||||
room, err := session.JoinRoom(roomName, hello.Type, hello.Credentials, sender) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &DataWelcome{ |
||||
Type: "Welcome", |
||||
Room: room, |
||||
Users: api.RoomUsers(session), |
||||
}, nil |
||||
} |
||||
|
||||
func (api *channellingAPI) HandleUsers(session *Session) (sessions *DataSessions, err error) { |
||||
if session.Hello { |
||||
sessions = &DataSessions{Type: "Users", Users: api.RoomUsers(session)} |
||||
} else { |
||||
err = NewDataError("not_in_room", "Cannot list users without a current room") |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (api *channellingAPI) HandleAuthentication(session *Session, st *SessionToken) (*DataSelf, error) { |
||||
if err := api.Authenticate(session, st, ""); err != nil { |
||||
log.Println("Authentication failed", err, st.Userid, st.Nonce) |
||||
return nil, err |
||||
} |
||||
|
||||
log.Println("Authentication success", session.Userid()) |
||||
self, err := api.HandleSelf(session) |
||||
if err == nil { |
||||
session.BroadcastStatus() |
||||
} |
||||
|
||||
return self, err |
||||
} |
||||
|
||||
func (api *channellingAPI) HandleChat(session *Session, chat *DataChat) { |
||||
// TODO(longsleep): Limit sent chat messages per incoming connection.
|
||||
msg := chat.Chat |
||||
to := chat.To |
||||
|
||||
if !msg.NoEcho { |
||||
session.Unicast(session.Id, chat) |
||||
} |
||||
msg.Time = time.Now().Format(time.RFC3339) |
||||
if to == "" { |
||||
// TODO(longsleep): Check if chat broadcast is allowed.
|
||||
if session.Hello { |
||||
api.CountBroadcastChat() |
||||
session.Broadcast(chat) |
||||
} |
||||
} else { |
||||
if msg.Status != nil { |
||||
if msg.Status.ContactRequest != nil { |
||||
if !api.Config.WithModule("contacts") { |
||||
return |
||||
} |
||||
if err := api.contactrequestHandler(session, to, msg.Status.ContactRequest); err != nil { |
||||
log.Println("Ignoring invalid contact request.", err) |
||||
return |
||||
} |
||||
msg.Status.ContactRequest.Userid = session.Userid() |
||||
} |
||||
} else { |
||||
api.CountUnicastChat() |
||||
} |
||||
|
||||
session.Unicast(to, chat) |
||||
if msg.Mid != "" { |
||||
// Send out delivery confirmation status chat message.
|
||||
session.Unicast(session.Id, &DataChat{To: to, Type: "Chat", Chat: &DataChatMessage{Mid: msg.Mid, Status: &DataChatStatus{State: "sent"}}}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (api *channellingAPI) HandleConference(session *Session, conference *DataConference) { |
||||
// Check conference maximum size.
|
||||
if len(conference.Conference) > maxConferenceSize { |
||||
log.Println("Refusing to create conference above limit.", len(conference.Conference)) |
||||
return |
||||
} |
||||
|
||||
// Send conference update to anyone.
|
||||
for _, id := range conference.Conference { |
||||
if id != session.Id { |
||||
session.Unicast(id, conference) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (api *channellingAPI) HandleSessions(session *Session, sessions *DataSessionsRequest) (*DataSessions, error) { |
||||
switch sessions.Type { |
||||
case "contact": |
||||
if !api.Config.WithModule("contacts") { |
||||
return nil, NewDataError("contacts_not_enabled", "incoming contacts session request with contacts disabled") |
||||
} |
||||
userID, err := api.getContactID(session, sessions.Token) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &DataSessions{ |
||||
Type: "Sessions", |
||||
Users: api.GetUserSessions(session, userID), |
||||
Sessions: sessions, |
||||
}, nil |
||||
case "session": |
||||
id, err := session.attestation.Decode(sessions.Token) |
||||
if err != nil { |
||||
return nil, NewDataError("bad_attestation", err.Error()) |
||||
} |
||||
session, ok := api.GetSession(id) |
||||
if !ok { |
||||
return nil, NewDataError("no_such_session", "cannot retrieve session") |
||||
} |
||||
return &DataSessions{ |
||||
Type: "Sessions", |
||||
Users: []*DataSession{session.Data()}, |
||||
Sessions: sessions, |
||||
}, nil |
||||
default: |
||||
return nil, NewDataError("bad_request", "unknown sessions request type") |
||||
} |
||||
} |
||||
|
||||
func (api *channellingAPI) HandleRoom(session *Session, room *DataRoom) (*DataRoom, error) { |
||||
room, err := api.UpdateRoom(session, room) |
||||
if err == nil { |
||||
session.Broadcast(room) |
||||
} |
||||
return room, err |
||||
} |
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 ( |
||||
"net/http" |
||||
"strconv" |
||||
"time" |
||||
|
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
|
||||
"github.com/gorilla/mux" |
||||
) |
||||
|
||||
func makeImageHandler(buddyImages channelling.ImageCache, expires time.Duration) http.HandlerFunc { |
||||
return func(w http.ResponseWriter, r *http.Request) { |
||||
vars := mux.Vars(r) |
||||
image := buddyImages.Get(vars["imageid"]) |
||||
if image == nil { |
||||
http.Error(w, "Unknown image", http.StatusNotFound) |
||||
return |
||||
} |
||||
|
||||
w.Header().Set("Content-Type", image.MimeType()) |
||||
w.Header().Set("ETag", image.LastChangeID()) |
||||
age := time.Now().Sub(image.LastChange()) |
||||
if age >= time.Second { |
||||
w.Header().Set("Age", strconv.Itoa(int(age.Seconds()))) |
||||
} |
||||
if expires >= time.Second { |
||||
w.Header().Set("Expires", time.Now().Add(expires).Format(time.RFC1123)) |
||||
w.Header().Set("Cache-Control", "public, no-transform, max-age="+strconv.Itoa(int(expires.Seconds()))) |
||||
} |
||||
|
||||
http.ServeContent(w, r, "", image.LastChange(), image.Reader()) |
||||
} |
||||
} |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 ( |
||||
"net/http" |
||||
) |
||||
|
||||
func mainHandler(w http.ResponseWriter, r *http.Request) { |
||||
handleRoomView("", w, r) |
||||
} |
@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 ( |
||||
"net/http" |
||||
|
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
|
||||
"github.com/gorilla/mux" |
||||
) |
||||
|
||||
func roomHandler(w http.ResponseWriter, r *http.Request) { |
||||
vars := mux.Vars(r) |
||||
|
||||
handleRoomView(vars["room"], w, r) |
||||
} |
||||
|
||||
func handleRoomView(room string, w http.ResponseWriter, r *http.Request) { |
||||
var err error |
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=UTF-8") |
||||
w.Header().Set("Expires", "-1") |
||||
w.Header().Set("Cache-Control", "private, max-age=0") |
||||
|
||||
csp := false |
||||
|
||||
if config.ContentSecurityPolicy != "" { |
||||
w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy) |
||||
csp = true |
||||
} |
||||
if config.ContentSecurityPolicyReportOnly != "" { |
||||
w.Header().Set("Content-Security-Policy-Report-Only", config.ContentSecurityPolicyReportOnly) |
||||
csp = true |
||||
} |
||||
|
||||
scheme := "http" |
||||
|
||||
// Detect if the request was made with SSL.
|
||||
ssl := r.TLS != nil |
||||
proto, ok := r.Header["X-Forwarded-Proto"] |
||||
if ok { |
||||
ssl = proto[0] == "https" |
||||
scheme = "https" |
||||
} |
||||
|
||||
// Get languages from request.
|
||||
langs := getRequestLanguages(r, []string{}) |
||||
if len(langs) == 0 { |
||||
langs = append(langs, "en") |
||||
} |
||||
|
||||
// Prepare context to deliver to HTML..
|
||||
context := &channelling.Context{Cfg: config, App: "main", Host: r.Host, Scheme: scheme, Ssl: ssl, Csp: csp, Languages: langs, Room: room} |
||||
|
||||
// Get URL parameters.
|
||||
r.ParseForm() |
||||
|
||||
// Check if incoming request is a crawler which supports AJAX crawling.
|
||||
// See https://developers.google.com/webmasters/ajax-crawling/docs/getting-started for details.
|
||||
if _, ok := r.Form["_escaped_fragment_"]; ok { |
||||
// Render crawlerPage template..
|
||||
err = templates.ExecuteTemplate(w, "crawlerPage", &context) |
||||
} else { |
||||
// Render mainPage template.
|
||||
err = templates.ExecuteTemplate(w, "mainPage", &context) |
||||
} |
||||
|
||||
if err != nil { |
||||
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
} |
||||
} |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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 ( |
||||
"fmt" |
||||
"net/http" |
||||
"net/url" |
||||
|
||||
"github.com/strukturag/spreed-webrtc/go/channelling" |
||||
|
||||
"github.com/gorilla/mux" |
||||
) |
||||
|
||||
func sandboxHandler(w http.ResponseWriter, r *http.Request) { |
||||
vars := mux.Vars(r) |
||||
// NOTE(longsleep): origin_scheme is window.location.protocol (eg. https:, http:).
|
||||
originURL, err := url.Parse(fmt.Sprintf("%s//%s", vars["origin_scheme"], vars["origin_host"])) |
||||
if err != nil || originURL.Scheme == "" || originURL.Host == "" { |
||||
http.Error(w, "Invalid origin path", http.StatusBadRequest) |
||||
return |
||||
} |
||||
origin := fmt.Sprintf("%s://%s", originURL.Scheme, originURL.Host) |
||||
|
||||
handleSandboxView(vars["sandbox"], origin, w, r) |
||||
} |
||||
|
||||
func handleSandboxView(sandbox string, origin string, w http.ResponseWriter, r *http.Request) { |
||||
w.Header().Set("Content-Type", "text/html; charset=UTF-8") |
||||
w.Header().Set("Expires", "-1") |
||||
w.Header().Set("Cache-Control", "private, max-age=0") |
||||
|
||||
sandboxTemplateName := fmt.Sprintf("%s_sandbox.html", sandbox) |
||||
|
||||
// Prepare context to deliver to HTML..
|
||||
if t := templates.Lookup(sandboxTemplateName); t != nil { |
||||
|
||||
// CSP support for sandboxes.
|
||||
var csp string |
||||
switch sandbox { |
||||
case "odfcanvas": |
||||
csp = fmt.Sprintf("default-src 'none'; script-src %s; img-src data: blob:; style-src 'unsafe-inline'", origin) |
||||
case "pdfcanvas": |
||||
csp = fmt.Sprintf("default-src 'none'; script-src %s 'unsafe-eval'; img-src 'self' data: blob:; style-src 'unsafe-inline'", origin) |
||||
default: |
||||
csp = "default-src 'none'" |
||||
} |
||||
w.Header().Set("Content-Security-Policy", csp) |
||||
|
||||
// Prepare context to deliver to HTML..
|
||||
context := &channelling.Context{Cfg: config, Origin: origin, Csp: true} |
||||
err := t.Execute(w, &context) |
||||
if err != nil { |
||||
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
} |
||||
|
||||
} else { |
||||
http.Error(w, "404 Unknown Sandbox", http.StatusNotFound) |
||||
} |
||||
} |
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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" |
||||
"net/http" |
||||
"net/url" |
||||
"strings" |
||||
) |
||||
|
||||
func wellKnownHandler(w http.ResponseWriter, r *http.Request) { |
||||
// Detect if the request was made with SSL.
|
||||
ssl := r.TLS != nil |
||||
scheme := "http" |
||||
proto, ok := r.Header["X-Forwarded-Proto"] |
||||
if ok { |
||||
ssl = proto[0] == "https" |
||||
} |
||||
if ssl { |
||||
scheme = "https" |
||||
} |
||||
|
||||
// Construct our URL.
|
||||
url := url.URL{ |
||||
Scheme: scheme, |
||||
Host: r.Host, |
||||
Path: strings.TrimSuffix(config.B, "/"), |
||||
} |
||||
doc := &map[string]string{ |
||||
"spreed-webrtc_endpoint": url.String(), |
||||
} |
||||
data, err := json.MarshalIndent(doc, "", " ") |
||||
if err != nil { |
||||
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
} |
||||
|
||||
w.Header().Set("Content-Type", "application/json") |
||||
w.Write(data) |
||||
} |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/strukturag/goacceptlanguageparser" |
||||
) |
||||
|
||||
// Helper to retrieve languages from request.
|
||||
func getRequestLanguages(r *http.Request, supportedLanguages []string) []string { |
||||
acceptLanguageHeader, ok := r.Header["Accept-Language"] |
||||
var langs []string |
||||
if ok { |
||||
langs = goacceptlanguageparser.ParseAcceptLanguage(acceptLanguageHeader[0], supportedLanguages) |
||||
} |
||||
return langs |
||||
} |
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
/* |
||||
* Spreed WebRTC. |
||||
* Copyright (C) 2013-2015 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(["underscore"], function(_) { |
||||
function noopThen() { |
||||
// Automatic playback started.
|
||||
} |
||||
function noopCatch(error) { |
||||
// Automatic playback failed.
|
||||
} |
||||
|
||||
// playPromise
|
||||
return function() { |
||||
return function(elem, thenFunc, catchFunc) { |
||||
// Starting with Chome 50 play() returns a promise.
|
||||
// https://developers.google.com/web/updates/2016/03/play-returns-promise
|
||||
var playPromise = elem.play() |
||||
if (playPromise !== undefined) { |
||||
if (!thenFunc) { |
||||
thenFunc = noopThen; |
||||
} |
||||
if (!catchFunc) { |
||||
catchFunc = noopCatch; |
||||
} |
||||
playPromise.then(thenFunc).catch(catchFunc); |
||||
} else { |
||||
if (thenFunc) { |
||||
_.defer(thenFunc); |
||||
} |
||||
} |
||||
return playPromise; |
||||
} |
||||
}; |
||||
}); |
Loading…
Reference in new issue