summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile25
-rw-r--r--api/api.go4
-rw-r--r--api/user.go12
-rw-r--r--api/web_hub.go3
-rw-r--r--api/webrtc.go51
-rw-r--r--api/webrtc_test.go18
-rw-r--r--config/config.json10
-rw-r--r--einterfaces/webrtc.go23
-rw-r--r--i18n/en.json48
-rw-r--r--model/client.go11
-rw-r--r--model/config.go86
-rw-r--r--model/license.go9
-rw-r--r--model/utils.go24
-rw-r--r--model/websocket_message.go1
-rw-r--r--utils/config.go4
-rw-r--r--utils/license.go1
-rw-r--r--webapp/components/admin_console/admin_sidebar.jsx16
-rw-r--r--webapp/components/admin_console/webrtc_settings.jsx220
-rw-r--r--webapp/i18n/en.json24
-rw-r--r--webapp/routes/route_admin_console.jsx5
20 files changed, 592 insertions, 3 deletions
diff --git a/Makefile b/Makefile
index 16162d976..7138f8f36 100644
--- a/Makefile
+++ b/Makefile
@@ -96,6 +96,14 @@ start-docker:
sleep 10; \
fi
+ @if [ $(shell docker ps -a | grep -ci mattermost-webrtc) -eq 0 ]; then \
+ echo starting mattermost-webrtc; \
+ docker run --name mattermost-webrtc -p 7088:7088 -p 7089:7089 -p 8188:8188 -p 8189:8189 -d mattermost/webrtc:latest > /dev/null; \
+ elif [ $(shell docker ps | grep -ci mattermost-webrtc) -eq 0 ]; then \
+ echo restarting mattermost-webrtc; \
+ docker start mattermost-webrtc > /dev/null; \
+ fi
+
stop-docker:
@echo Stopping docker containers
@@ -114,6 +122,11 @@ stop-docker:
docker stop mattermost-openldap > /dev/null; \
fi
+ @if [ $(shell docker ps -a | grep -ci mattermost-webrtc) -eq 1 ]; then \
+ echo stopping mattermost-webrtc; \
+ docker stop mattermost-webrtc > /dev/null; \
+ fi
+
clean-docker:
@echo Removing docker containers
@@ -135,6 +148,12 @@ clean-docker:
docker rm -v mattermost-openldap > /dev/null; \
fi
+ @if [ $(shell docker ps -a | grep -ci mattermost-webrtc) -eq 1 ]; then \
+ echo removing mattermost-webrtc; \
+ docker stop mattermost-webrtc > /dev/null; \
+ docker rm -v mattermost-webrtc > /dev/null; \
+ fi
+
check-client-style:
@echo Checking client style
@@ -184,6 +203,7 @@ ifeq ($(BUILD_ENTERPRISE_READY),true)
$(GO) test $(GOFLAGS) -run=$(TESTS) -covermode=count -c ./enterprise/saml && ./saml.test -test.v -test.timeout=60s -test.coverprofile=csaml.out || exit 1
$(GO) test $(GOFLAGS) -run=$(TESTS) -covermode=count -c ./enterprise/cluster && ./cluster.test -test.v -test.timeout=60s -test.coverprofile=ccluster.out || exit 1
$(GO) test $(GOFLAGS) -run=$(TESTS) -covermode=count -c ./enterprise/account_migration && ./account_migration.test -test.v -test.timeout=60s -test.coverprofile=caccount_migration.out || exit 1
+ $(GO) test $(GOFLAGS) -run=$(TESTS) -covermode=count -c ./enterprise/webrtc && ./webrtc.test -test.v -test.timeout=60s -test.coverprofile=cwebrtc.out || exit 1
tail -n +2 cldap.out >> ecover.out
tail -n +2 ccompliance.out >> ecover.out
@@ -191,14 +211,15 @@ ifeq ($(BUILD_ENTERPRISE_READY),true)
tail -n +2 csaml.out >> ecover.out
tail -n +2 ccluster.out >> ecover.out
tail -n +2 caccount_migration.out >> ecover.out
- rm -f cldap.out ccompliance.out cemoji.out csaml.out ccluster.out caccount_migration.out
-
+ tail -n +2 cwebrtc.out >> ecover.out
+ rm -f cldap.out ccompliance.out cemoji.out csaml.out ccluster.out caccount_migration.out cwebrtc.out
rm -r ldap.test
rm -r compliance.test
rm -r emoji.test
rm -r saml.test
rm -r cluster.test
rm -r account_migration.test
+ rm -r webrtc.test
rm -f config/*.crt
rm -f config/*.key
endif
diff --git a/api/api.go b/api/api.go
index 5373565de..492c3b0a9 100644
--- a/api/api.go
+++ b/api/api.go
@@ -49,6 +49,8 @@ type Routes struct {
Emoji *mux.Router // 'api/v3/emoji'
+ Webrtc *mux.Router // 'api/v3/webrtc'
+
WebSocket *WebSocketRouter // websocket api
}
@@ -77,6 +79,7 @@ func InitApi() {
BaseRoutes.License = BaseRoutes.ApiRoot.PathPrefix("/license").Subrouter()
BaseRoutes.Public = BaseRoutes.ApiRoot.PathPrefix("/public").Subrouter()
BaseRoutes.Emoji = BaseRoutes.ApiRoot.PathPrefix("/emoji").Subrouter()
+ BaseRoutes.Webrtc = BaseRoutes.ApiRoot.PathPrefix("/webrtc").Subrouter()
BaseRoutes.WebSocket = NewWebSocketRouter()
@@ -95,6 +98,7 @@ func InitApi() {
InitLicense()
InitEmoji()
InitStatus()
+ InitWebrtc()
// 404 on any api route before web.go has a chance to serve it
Srv.Router.Handle("/api/{anything:.*}", http.HandlerFunc(Handle404))
diff --git a/api/user.go b/api/user.go
index 35cc3612e..a82fc5561 100644
--- a/api/user.go
+++ b/api/user.go
@@ -735,6 +735,10 @@ func RevokeSessionById(c *Context, sessionId string) {
c.Err = result.Err
}
}
+
+ if webrtcInterface := einterfaces.GetWebrtcInterface(); webrtcInterface != nil {
+ webrtcInterface.RevokeToken(session.Id)
+ }
}
}
@@ -757,6 +761,10 @@ func RevokeAllSession(c *Context, userId string) {
return
}
}
+
+ if webrtcInterface := einterfaces.GetWebrtcInterface(); webrtcInterface != nil {
+ webrtcInterface.RevokeToken(session.Id)
+ }
}
}
}
@@ -778,6 +786,10 @@ func RevokeAllSessionsNoContext(userId string) *model.AppError {
return result.Err
}
}
+
+ if webrtcInterface := einterfaces.GetWebrtcInterface(); webrtcInterface != nil {
+ webrtcInterface.RevokeToken(session.Id)
+ }
}
}
return nil
diff --git a/api/web_hub.go b/api/web_hub.go
index 452fa10f8..309d560a9 100644
--- a/api/web_hub.go
+++ b/api/web_hub.go
@@ -169,6 +169,9 @@ func shouldSendEvent(webCon *WebConn, msg *model.WebSocketEvent) bool {
} else if msg.Event == model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE {
// For now, ephemeral messages are sent directly to individual users
return false
+ } else if msg.Event == model.WEBSOCKET_EVENT_WEBRTC {
+ // No need to tell anyone that a webrtc event is going on
+ return false
}
// Only report events to users who are in the team for the event
diff --git a/api/webrtc.go b/api/webrtc.go
new file mode 100644
index 000000000..4664524f4
--- /dev/null
+++ b/api/webrtc.go
@@ -0,0 +1,51 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ l4g "github.com/alecthomas/log4go"
+ "github.com/mattermost/platform/einterfaces"
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+ "net/http"
+)
+
+func InitWebrtc() {
+ l4g.Debug(utils.T("api.webrtc.init.debug"))
+
+ BaseRoutes.Webrtc.Handle("/token", ApiUserRequired(webrtcToken)).Methods("POST")
+
+ BaseRoutes.WebSocket.Handle("webrtc", ApiWebSocketHandler(webrtcMessage))
+}
+
+func webrtcToken(c *Context, w http.ResponseWriter, r *http.Request) {
+ webrtcInterface := einterfaces.GetWebrtcInterface()
+
+ if webrtcInterface == nil {
+ c.Err = model.NewLocAppError("webrtcToken", "api.webrtc.not_available.app_error", nil, "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
+
+ if result, err := webrtcInterface.Token(c.Session.Id); err != nil {
+ c.Err = err
+ return
+ } else {
+ w.Write([]byte(model.MapToJson(result)))
+ }
+}
+
+func webrtcMessage(req *model.WebSocketRequest) (map[string]interface{}, *model.AppError) {
+ var ok bool
+ var toUserId string
+ if toUserId, ok = req.Data["to_user_id"].(string); !ok || len(toUserId) != 26 {
+ return nil, NewInvalidWebSocketParamError(req.Action, "to_user_id")
+ }
+
+ event := model.NewWebSocketEvent("", "", toUserId, model.WEBSOCKET_EVENT_WEBRTC)
+ event.Data = req.Data
+ go Publish(event)
+
+ return nil, nil
+}
diff --git a/api/webrtc_test.go b/api/webrtc_test.go
new file mode 100644
index 000000000..d6a690407
--- /dev/null
+++ b/api/webrtc_test.go
@@ -0,0 +1,18 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import "testing"
+
+func TestWebrtcToken(t *testing.T) {
+ th := Setup().InitBasic()
+
+ if _, err := th.BasicClient.GetWebrtcToken(); err != nil {
+ if err.Id != "api.webrtc.not_available.app_error" {
+ t.Fatal("Should have fail, webrtc not availble")
+ }
+ } else {
+ t.Fatal("Should have fail, webrtc not availble")
+ }
+}
diff --git a/config/config.json b/config/config.json
index 1629bada3..dde478089 100644
--- a/config/config.json
+++ b/config/config.json
@@ -220,5 +220,15 @@
"Enable": false,
"InterNodeListenAddress": ":8075",
"InterNodeUrls": []
+ },
+ "WebrtcSettings": {
+ "Enable": false,
+ "GatewayWebsocketUrl": "",
+ "GatewayAdminUrl": "",
+ "GatewayAdminSecret": "",
+ "StunURI": "",
+ "TurnURI": "",
+ "TurnUsername": "",
+ "TurnSharedKey": ""
}
} \ No newline at end of file
diff --git a/einterfaces/webrtc.go b/einterfaces/webrtc.go
new file mode 100644
index 000000000..97850643f
--- /dev/null
+++ b/einterfaces/webrtc.go
@@ -0,0 +1,23 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package einterfaces
+
+import (
+ "github.com/mattermost/platform/model"
+)
+
+type WebrtcInterface interface {
+ Token(sessionId string) (map[string]string, *model.AppError)
+ RevokeToken(sessionId string)
+}
+
+var theWebrtcInterface WebrtcInterface
+
+func RegisterWebrtcInterface(newInterface WebrtcInterface) {
+ theWebrtcInterface = newInterface
+}
+
+func GetWebrtcInterface() WebrtcInterface {
+ return theWebrtcInterface
+}
diff --git a/i18n/en.json b/i18n/en.json
index 1841ac918..15c2d773e 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -2392,6 +2392,14 @@
"translation": "Inappropriate permissions to regenerate outcoming webhook token"
},
{
+ "id": "api.webrtc.init.debug",
+ "translation": "Initializing WebRTC api routes"
+ },
+ {
+ "id": "api.webrtc.not_available.app_error",
+ "translation": "WebRTC is not available on this server."
+ },
+ {
"id": "api.websocket_handler.invalid_param.app_error",
"translation": "Invalid {{.Name}} parameter"
},
@@ -2664,6 +2672,18 @@
"translation": "Unable to update existing SAML user. Allowing login anyway. err=%v"
},
{
+ "id": "ent.webrtc.disabled.app_error",
+ "translation": "WebRTC is not enabled in this server."
+ },
+ {
+ "id": "ent.webrtc.license_disable.app_error",
+ "translation": "Your license does not support using Mattermost WebRTC"
+ },
+ {
+ "id": "ent.webrtc.register_token.app_error",
+ "translation": "We encountered an error trying to register the WebRTC Token"
+ },
+ {
"id": "error.generic.link_message",
"translation": "Back to Mattermost"
},
@@ -3200,6 +3220,34 @@
"translation": "Invalid maximum open connection for SQL settings. Must be a positive number."
},
{
+ "id": "model.config.is_valid.webrtc_gateway_admin_secret.app_error",
+ "translation": "WebRTC Gateway Admin Secret must be set."
+ },
+ {
+ "id": "model.config.is_valid.webrtc_gateway_admin_url.app_error",
+ "translation": "WebRTC Gateway Admin Url must be set a valid URL and start with http:// or https://."
+ },
+ {
+ "id": "model.config.is_valid.webrtc_gateway_ws_url.app_error",
+ "translation": "WebRTC Gateway Websocket Url must be a valid URL and start with ws:// or wss://."
+ },
+ {
+ "id": "model.config.is_valid.webrtc_stun_uri.app_error",
+ "translation": "WebRTC STUN URI must be a valid URI and start with stun:"
+ },
+ {
+ "id": "model.config.is_valid.webrtc_turn_shared_key.app_error",
+ "translation": "WebRTC TURN Shared Key cannot be empty if the TURN URI has been set."
+ },
+ {
+ "id": "model.config.is_valid.webrtc_turn_uri.app_error",
+ "translation": "WebRTC TURN URI must be a valid URI and start with turn:"
+ },
+ {
+ "id": "model.config.is_valid.webrtc_turn_username.app_error",
+ "translation": "WebRTC TURN Username cannot be empty if the TURN URI has been set."
+ },
+ {
"id": "model.emoji.create_at.app_error",
"translation": "Create at must be a valid time"
},
diff --git a/model/client.go b/model/client.go
index e54f61347..affdb54aa 100644
--- a/model/client.go
+++ b/model/client.go
@@ -1898,3 +1898,14 @@ func (c *Client) SamlCertificateStatus(filename string) (map[string]interface{},
return StringInterfaceFromJson(r.Body), nil
}
}
+
+// GetWebrtcToken if Successful returns a map with a valid token, stun server and turn server with credentials to use with
+// the Mattermost WebRTC service, otherwise returns an AppError. Must be authenticated user.
+func (c *Client) GetWebrtcToken() (map[string]string, *AppError) {
+ if r, err := c.DoApiPost("/webrtc/token", ""); err != nil {
+ return nil, err
+ } else {
+ defer closeBody(r)
+ return MapFromJson(r.Body), nil
+ }
+}
diff --git a/model/config.go b/model/config.go
index 3ad1c4c6c..13135f9ae 100644
--- a/model/config.go
+++ b/model/config.go
@@ -292,6 +292,17 @@ type NativeAppSettings struct {
IosAppDownloadLink *string
}
+type WebrtcSettings struct {
+ Enable *bool
+ GatewayWebsocketUrl *string
+ GatewayAdminUrl *string
+ GatewayAdminSecret *string
+ StunURI *string
+ TurnURI *string
+ TurnUsername *string
+ TurnSharedKey *string
+}
+
type Config struct {
ServiceSettings ServiceSettings
TeamSettings TeamSettings
@@ -312,6 +323,7 @@ type Config struct {
SamlSettings SamlSettings
NativeAppSettings NativeAppSettings
ClusterSettings ClusterSettings
+ WebrtcSettings WebrtcSettings
}
func (o *Config) ToJson() string {
@@ -881,6 +893,8 @@ func (o *Config) SetDefaults() {
o.NativeAppSettings.IosAppDownloadLink = new(string)
*o.NativeAppSettings.IosAppDownloadLink = "https://about.mattermost.com/mattermost-ios-app/"
}
+
+ o.defaultWebrtcSettings()
}
func (o *Config) IsValid() *AppError {
@@ -1083,6 +1097,10 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.sitename_length.app_error", map[string]interface{}{"MaxLength": SITENAME_MAX_LENGTH}, "")
}
+ if err := o.isValidWebrtcSettings(); err != nil {
+ return err
+ }
+
return nil
}
@@ -1121,3 +1139,71 @@ func (o *Config) Sanitize() {
o.SqlSettings.DataSourceReplicas[i] = FAKE_SETTING
}
}
+
+func (o *Config) defaultWebrtcSettings() {
+ if o.WebrtcSettings.Enable == nil {
+ o.WebrtcSettings.Enable = new(bool)
+ *o.WebrtcSettings.Enable = false
+ }
+
+ if o.WebrtcSettings.GatewayWebsocketUrl == nil {
+ o.WebrtcSettings.GatewayWebsocketUrl = new(string)
+ *o.WebrtcSettings.GatewayWebsocketUrl = ""
+ }
+
+ if o.WebrtcSettings.GatewayAdminUrl == nil {
+ o.WebrtcSettings.GatewayAdminUrl = new(string)
+ *o.WebrtcSettings.GatewayAdminUrl = ""
+ }
+
+ if o.WebrtcSettings.GatewayAdminSecret == nil {
+ o.WebrtcSettings.GatewayAdminSecret = new(string)
+ *o.WebrtcSettings.GatewayAdminSecret = ""
+ }
+
+ if o.WebrtcSettings.StunURI == nil {
+ o.WebrtcSettings.StunURI = new(string)
+ *o.WebrtcSettings.StunURI = ""
+ }
+
+ if o.WebrtcSettings.TurnURI == nil {
+ o.WebrtcSettings.TurnURI = new(string)
+ *o.WebrtcSettings.TurnURI = ""
+ }
+
+ if o.WebrtcSettings.TurnUsername == nil {
+ o.WebrtcSettings.TurnUsername = new(string)
+ *o.WebrtcSettings.TurnUsername = ""
+ }
+
+ if o.WebrtcSettings.TurnSharedKey == nil {
+ o.WebrtcSettings.TurnSharedKey = new(string)
+ *o.WebrtcSettings.TurnSharedKey = ""
+ }
+}
+
+func (o *Config) isValidWebrtcSettings() *AppError {
+ if *o.WebrtcSettings.Enable {
+ if len(*o.WebrtcSettings.GatewayWebsocketUrl) == 0 || !IsValidWebsocketUrl(*o.WebrtcSettings.GatewayWebsocketUrl) {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_ws_url.app_error", nil, "")
+ } else if len(*o.WebrtcSettings.GatewayAdminUrl) == 0 || !IsValidHttpUrl(*o.WebrtcSettings.GatewayAdminUrl) {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_admin_url.app_error", nil, "")
+ } else if len(*o.WebrtcSettings.GatewayAdminSecret) == 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_admin_secret.app_error", nil, "")
+ } else if len(*o.WebrtcSettings.StunURI) != 0 && !IsValidTurnOrStunServer(*o.WebrtcSettings.StunURI) {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_stun_uri.app_error", nil, "")
+ } else if len(*o.WebrtcSettings.TurnURI) != 0 {
+ if !IsValidTurnOrStunServer(*o.WebrtcSettings.TurnURI) {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_uri.app_error", nil, "")
+ }
+ if len(*o.WebrtcSettings.TurnUsername) == 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_username.app_error", nil, "")
+ } else if len(*o.WebrtcSettings.TurnSharedKey) == 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_shared_key.app_error", nil, "")
+ }
+
+ }
+ }
+
+ return nil
+}
diff --git a/model/license.go b/model/license.go
index 1fce1eeb1..465cca128 100644
--- a/model/license.go
+++ b/model/license.go
@@ -43,7 +43,9 @@ type Features struct {
MHPNS *bool `json:"mhpns"`
SAML *bool `json:"saml"`
PasswordRequirements *bool `json:"password_requirements"`
- FutureFeatures *bool `json:"future_features"`
+ // after we enabled more features for web rtc we'll need to control them with this
+ Webrtc *bool `json:"webrtc"`
+ FutureFeatures *bool `json:"future_features"`
}
func (f *Features) ToMap() map[string]interface{} {
@@ -122,6 +124,11 @@ func (f *Features) SetDefaults() {
f.PasswordRequirements = new(bool)
*f.PasswordRequirements = *f.FutureFeatures
}
+
+ if f.Webrtc == nil {
+ f.Webrtc = new(bool)
+ *f.Webrtc = *f.FutureFeatures
+ }
}
func (l *License) IsExpired() bool {
diff --git a/model/utils.go b/model/utils.go
index a4a4208c2..a9c441fee 100644
--- a/model/utils.go
+++ b/model/utils.go
@@ -413,6 +413,18 @@ func IsValidHttpsUrl(rawUrl string) bool {
return true
}
+func IsValidTurnOrStunServer(rawUri string) bool {
+ if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 {
+ return false
+ }
+
+ if _, err := url.ParseRequestURI(rawUri); err != nil {
+ return false
+ }
+
+ return true
+}
+
func IsSafeLink(link *string) bool {
if link != nil {
if IsValidHttpUrl(*link) {
@@ -426,3 +438,15 @@ func IsSafeLink(link *string) bool {
return true
}
+
+func IsValidWebsocketUrl(rawUrl string) bool {
+ if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 {
+ return false
+ }
+
+ if _, err := url.ParseRequestURI(rawUrl); err != nil {
+ return false
+ }
+
+ return true
+}
diff --git a/model/websocket_message.go b/model/websocket_message.go
index 18e070afd..4e1f1dee3 100644
--- a/model/websocket_message.go
+++ b/model/websocket_message.go
@@ -25,6 +25,7 @@ const (
WEBSOCKET_EVENT_EPHEMERAL_MESSAGE = "ephemeral_message"
WEBSOCKET_EVENT_STATUS_CHANGE = "status_change"
WEBSOCKET_EVENT_HELLO = "hello"
+ WEBSOCKET_EVENT_WEBRTC = "webrtc"
)
type WebSocketMessage interface {
diff --git a/utils/config.go b/utils/config.go
index 25dec7ace..2c4113009 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -337,6 +337,10 @@ func getClientConfig(c *model.Config) map[string]string {
props["PasswordRequireNumber"] = strconv.FormatBool(*c.PasswordSettings.Number)
props["PasswordRequireSymbol"] = strconv.FormatBool(*c.PasswordSettings.Symbol)
}
+
+ if *License.Features.Webrtc {
+ props["EnableWebrtc"] = strconv.FormatBool(*c.WebrtcSettings.Enable)
+ }
}
return props
diff --git a/utils/license.go b/utils/license.go
index 4d6387788..ae27dbc88 100644
--- a/utils/license.go
+++ b/utils/license.go
@@ -125,6 +125,7 @@ func getClientLicense(l *model.License) map[string]string {
props["Cluster"] = strconv.FormatBool(*l.Features.Cluster)
props["GoogleOAuth"] = strconv.FormatBool(*l.Features.GoogleOAuth)
props["Office365OAuth"] = strconv.FormatBool(*l.Features.Office365OAuth)
+ props["Webrtc"] = strconv.FormatBool(*l.Features.Webrtc)
props["Compliance"] = strconv.FormatBool(*l.Features.Compliance)
props["CustomBrand"] = strconv.FormatBool(*l.Features.CustomBrand)
props["MHPNS"] = strconv.FormatBool(*l.Features.MHPNS)
diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx
index 77d2fa2ae..1e74df05f 100644
--- a/webapp/components/admin_console/admin_sidebar.jsx
+++ b/webapp/components/admin_console/admin_sidebar.jsx
@@ -180,6 +180,7 @@ export default class AdminSidebar extends React.Component {
let samlSettings = null;
let clusterSettings = null;
let complianceSettings = null;
+ let webrtcSettings = null;
let license = null;
let audits = null;
@@ -256,6 +257,20 @@ export default class AdminSidebar extends React.Component {
);
}
+ if (global.window.mm_license.Webrtc === 'true') {
+ webrtcSettings = (
+ <AdminSidebarSection
+ name='webrtc'
+ title={
+ <FormattedMessage
+ id='admin.sidebar.webrtc'
+ defaultMessage='WebRTC'
+ />
+ }
+ />
+ );
+ }
+
oauthSettings = (
<AdminSidebarSection
name='oauth'
@@ -572,6 +587,7 @@ export default class AdminSidebar extends React.Component {
/>
}
/>
+ {webrtcSettings}
<AdminSidebarSection
name='external'
title={
diff --git a/webapp/components/admin_console/webrtc_settings.jsx b/webapp/components/admin_console/webrtc_settings.jsx
new file mode 100644
index 000000000..eac075bfa
--- /dev/null
+++ b/webapp/components/admin_console/webrtc_settings.jsx
@@ -0,0 +1,220 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import * as Utils from 'utils/utils.jsx';
+
+import AdminSettings from './admin_settings.jsx';
+import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+import SettingsGroup from './settings_group.jsx';
+import BooleanSetting from './boolean_setting.jsx';
+import TextSetting from './text_setting.jsx';
+
+export default class WebrtcSettings extends AdminSettings {
+ constructor(props) {
+ super(props);
+
+ this.getConfigFromState = this.getConfigFromState.bind(this);
+ this.renderSettings = this.renderSettings.bind(this);
+ }
+
+ getConfigFromState(config) {
+ config.WebrtcSettings.Enable = this.state.enableWebrtc;
+ config.WebrtcSettings.GatewayWebsocketUrl = this.state.gatewayWebsocketUrl;
+ config.WebrtcSettings.GatewayAdminUrl = this.state.gatewayAdminUrl;
+ config.WebrtcSettings.GatewayAdminSecret = this.state.gatewayAdminSecret;
+ config.WebrtcSettings.StunURI = this.state.stunURI;
+ config.WebrtcSettings.TurnURI = this.state.turnURI;
+ config.WebrtcSettings.TurnUsername = this.state.turnUsername;
+ config.WebrtcSettings.TurnSharedKey = this.state.turnSharedKey;
+
+ return config;
+ }
+
+ getStateFromConfig(config) {
+ const settings = config.WebrtcSettings;
+
+ return {
+ hasErrors: false,
+ enableWebrtc: settings.Enable,
+ gatewayWebsocketUrl: settings.GatewayWebsocketUrl,
+ gatewayAdminUrl: settings.GatewayAdminUrl,
+ gatewayAdminSecret: settings.GatewayAdminSecret,
+ stunURI: settings.StunURI,
+ turnURI: settings.TurnURI,
+ turnUsername: settings.TurnUsername,
+ turnSharedKey: settings.TurnSharedKey
+ };
+ }
+
+ renderTitle() {
+ return (
+ <h3>
+ <FormattedMessage
+ id='admin.integrations.webrtc'
+ defaultMessage='Mattermost WebRTC'
+ />
+ </h3>
+ );
+ }
+
+ renderSettings() {
+ return (
+ <SettingsGroup>
+ <BooleanSetting
+ id='enableWebrtc'
+ label={
+ <FormattedMessage
+ id='admin.webrtc.enableTitle'
+ defaultMessage='Enable Mattermost WebRTC: '
+ />
+ }
+ helpText={
+ <FormattedHTMLMessage
+ id='admin.webrtc.enableDescription'
+ defaultMessage='When true, Mattermost allows making <strong>one on one</strong> video calls.'
+ />
+ }
+ value={this.state.enableWebrtc}
+ onChange={this.handleChange}
+ />
+ <TextSetting
+ id='gatewayWebsocketUrl'
+ label={
+ <FormattedMessage
+ id='admin.webrtc.gatewayWebsocketUrlTitle'
+ defaultMessage='Gateway Websocket URL:'
+ />
+ }
+ placeholder={Utils.localizeMessage('admin.webrtc.gatewayWebsocketUrlExample', 'Ex "wss://webrtc.mattermost.com:8189"')}
+ helpText={
+ <FormattedMessage
+ id='admin.webrtc.gatewayWebsocketUrlDescription'
+ defaultMessage='Enter wss://<mattermost-webrtc-gateway-url>:<port>. Make sure you use WS or WSS in your URL depending on your server configuration.
+ This is the websocket used to signal and establish communication between the peers.'
+ />
+ }
+ value={this.state.gatewayWebsocketUrl}
+ onChange={this.handleChange}
+ disabled={!this.state.enableWebrtc}
+ />
+ <TextSetting
+ id='gatewayAdminUrl'
+ label={
+ <FormattedMessage
+ id='admin.webrtc.gatewayAdminUrlTitle'
+ defaultMessage='Gateway Admin URL:'
+ />
+ }
+ placeholder={Utils.localizeMessage('admin.webrtc.gatewayAdminUrlExample', 'Ex "https://webrtc.mattermost.com:7089/admin"')}
+ helpText={
+ <FormattedMessage
+ id='admin.webrtc.gatewayAdminUrlDescription'
+ defaultMessage='Enter https://<mattermost-webrtc-gateway-url>:<port>. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.
+ Mattermost WebRTC uses this URL to obtain valid tokens for each peer to establish the connection.'
+ />
+ }
+ value={this.state.gatewayAdminUrl}
+ onChange={this.handleChange}
+ disabled={!this.state.enableWebrtc}
+ />
+ <TextSetting
+ id='gatewayAdminSecret'
+ label={
+ <FormattedMessage
+ id='admin.webrtc.gatewayAdminSecretTitle'
+ defaultMessage='Gateway Admin Secret:'
+ />
+ }
+ placeholder={Utils.localizeMessage('admin.webrtc.gatewayAdminSecretExample', 'Ex "PVRzWNN1Tg6szn7IQWvhpAvLByScWxdy"')}
+ helpText={
+ <FormattedMessage
+ id='admin.webrtc.gatewayAdminSecretDescription'
+ defaultMessage='Enter your admin secret password to access the Gateway Admin URL.'
+ />
+ }
+ value={this.state.gatewayAdminSecret}
+ onChange={this.handleChange}
+ disabled={!this.state.enableWebrtc}
+ />
+ <TextSetting
+ id='stunURI'
+ label={
+ <FormattedMessage
+ id='admin.webrtc.stunUriTitle'
+ defaultMessage='STUN URI:'
+ />
+ }
+ placeholder={Utils.localizeMessage('admin.webrtc.stunUriExample', 'Ex "stun:webrtc.mattermost.com:5349"')}
+ helpText={
+ <FormattedMessage
+ id='admin.webrtc.stunUriDescription'
+ defaultMessage='Enter your STUN URI as stun:<your-stun-url>:<port>. STUN is a standardized network protocol to allow an end host to assist devices to access its public IP address if it is located behind a NAT.'
+ />
+ }
+ value={this.state.stunURI}
+ onChange={this.handleChange}
+ disabled={!this.state.enableWebrtc}
+ />
+ <TextSetting
+ id='turnURI'
+ label={
+ <FormattedMessage
+ id='admin.webrtc.turnUriTitle'
+ defaultMessage='TURN URI:'
+ />
+ }
+ placeholder={Utils.localizeMessage('admin.webrtc.turnUriExample', 'Ex "turn:webrtc.mattermost.com:5349"')}
+ helpText={
+ <FormattedHTMLMessage
+ id='admin.webrtc.turnUriDescription'
+ defaultMessage='Enter your TURN URI as turn:<your-turn-url>:<port>. TURN is a standardized network protocol to allow an end host to assist devices to establish a connection by using a relay public IP address if it is located behind a symmetric NAT.'
+ />
+ }
+ value={this.state.turnURI}
+ onChange={this.handleChange}
+ disabled={!this.state.enableWebrtc}
+ />
+ <TextSetting
+ id='turnUsername'
+ label={
+ <FormattedMessage
+ id='admin.webrtc.turnUsernameTitle'
+ defaultMessage='TURN Username:'
+ />
+ }
+ placeholder={Utils.localizeMessage('admin.webrtc.turnUsernameExample', 'Ex "myusername"')}
+ helpText={
+ <FormattedHTMLMessage
+ id='admin.webrtc.turnUsernameDescription'
+ defaultMessage='Enter your TURN Server Username.'
+ />
+ }
+ value={this.state.turnUsername}
+ onChange={this.handleChange}
+ disabled={!this.state.enableWebrtc || !this.state.turnURI}
+ />
+ <TextSetting
+ id='turnSharedKey'
+ label={
+ <FormattedMessage
+ id='admin.webrtc.turnSharedKeyTitle'
+ defaultMessage='TURN Shared Key:'
+ />
+ }
+ placeholder={Utils.localizeMessage('admin.webrtc.turnSharedKeyExample', 'Ex "bXdkOWQxc3d0Ynk3emY5ZmsxZ3NtazRjaWg="')}
+ helpText={
+ <FormattedHTMLMessage
+ id='admin.webrtc.turnSharedKeyDescription'
+ defaultMessage='Enter your TURN Server Shared Key. This is used to created dynamic passwords to establish the connection. Each password is valid for a short period of time.'
+ />
+ }
+ value={this.state.turnSharedKey}
+ onChange={this.handleChange}
+ disabled={!this.state.enableWebrtc || !this.state.turnURI}
+ />
+ </SettingsGroup>
+ );
+ }
+}
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 66e994893..4a6293b2d 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -392,6 +392,7 @@
"admin.image.thumbWidthTitle": "Attachment Thumbnail Width:",
"admin.integrations.custom": "Custom Integrations",
"admin.integrations.external": "External Services",
+ "admin.integrations.webrtc": "Mattermost WebRTC",
"admin.ldap.baseDesc": "The Base DN is the Distinguished Name of the location where Mattermost should start its search for users in the AD/LDAP tree.",
"admin.ldap.baseEx": "Ex \"ou=Unit Name,dc=corp,dc=example,dc=com\"",
"admin.ldap.baseTitle": "BaseDN:",
@@ -823,6 +824,29 @@
"admin.user_item.switchToEmail": "Switch to Email/Password",
"admin.user_item.sysAdmin": "System Admin",
"admin.user_item.teamAdmin": "Team Admin",
+ "admin.webrtc.enableDescription": "When true, Mattermost allows making <strong>one on one</strong> video calls.",
+ "admin.webrtc.enableTitle": "Enable Mattermost WebRTC: ",
+ "admin.webrtc.gatewayAdminSecretDescription": "Enter your admin secret password to access the Gateway Admin URL.",
+ "admin.webrtc.gatewayAdminSecretExample": "Ex \"PVRzWNN1Tg6szn7IQWvhpAvLByScWxdy\"",
+ "admin.webrtc.gatewayAdminSecretTitle": "Gateway Admin Secret:",
+ "admin.webrtc.gatewayAdminUrlDescription": "Enter https://<mattermost-webrtc-gateway-url>:<port>. Make sure you use HTTP or HTTPS in your URL depending on your server configuration. Mattermost WebRTC uses this URL to obtain valid tokens for each peer to establish the connection.",
+ "admin.webrtc.gatewayAdminUrlExample": "Ex \"https://webrtc.mattermost.com:7089/admin\"",
+ "admin.webrtc.gatewayAdminUrlTitle": "Gateway Admin URL:",
+ "admin.webrtc.gatewayWebsocketUrlDescription": "Enter wss://<mattermost-webrtc-gateway-url>:<port>. Make sure you use WS or WSS in your URL depending on your server configuration. This is the websocket used to signal and establish communication between the peers.",
+ "admin.webrtc.gatewayWebsocketUrlExample": "Ex \"wss://webrtc.mattermost.com:8189\"",
+ "admin.webrtc.gatewayWebsocketUrlTitle": "Gateway Websocket URL:",
+ "admin.webrtc.stunUriDescription": "Enter your STUN URI as stun:<your-stun-url>:<port>. STUN is a standardized network protocol to allow an end host to assist devices to access its public IP address if it is located behind a NAT.",
+ "admin.webrtc.stunUriExample": "Ex \"stun:webrtc.mattermost.com:5349\"",
+ "admin.webrtc.stunUriTitle": "STUN URI",
+ "admin.webrtc.turnSharedKeyDescription": "Enter your TURN Server Shared Key. This is used to created dynamic passwords to establish the connection. Each password is valid for a short period of time.",
+ "admin.webrtc.turnSharedKeyExample": "Ex \"bXdkOWQxc3d0Ynk3emY5ZmsxZ3NtazRjaWg=\"",
+ "admin.webrtc.turnSharedKeyTitle": "TURN Shared Key:",
+ "admin.webrtc.turnUriDescription": "Enter your TURN URI as turn:<your-turn-url>:<port>. TURN is a standardized network protocol to allow an end host to assist devices to establish a connection by using a relay public IP address if it is located behind a symmetric NAT.",
+ "admin.webrtc.turnUriExample": "Ex \"turn:webrtc.mattermost.com:5349\"",
+ "admin.webrtc.turnUriTitle": "TURN URI",
+ "admin.webrtc.turnUsernameDescription": "Enter your TURN Server Username.",
+ "admin.webrtc.turnUsernameExample": "Ex \"myusername\"",
+ "admin.webrtc.turnUsernameTitle": "TURN Username:",
"admin.webserverModeDisabled": "Disabled",
"admin.webserverModeDisabledDescription": "The Mattermost server will not serve static files.",
"admin.webserverModeGzip": "gzip",
diff --git a/webapp/routes/route_admin_console.jsx b/webapp/routes/route_admin_console.jsx
index f20c5c379..e3e22ed68 100644
--- a/webapp/routes/route_admin_console.jsx
+++ b/webapp/routes/route_admin_console.jsx
@@ -27,6 +27,7 @@ import EmailSettings from 'components/admin_console/email_settings.jsx';
import PushSettings from 'components/admin_console/push_settings.jsx';
import CustomIntegrationsSettings from 'components/admin_console/custom_integrations_settings.jsx';
import ExternalServiceSettings from 'components/admin_console/external_service_settings.jsx';
+import WebrtcSettings from 'components/admin_console/webrtc_settings.jsx';
import DatabaseSettings from 'components/admin_console/database_settings.jsx';
import StorageSettings from 'components/admin_console/storage_settings.jsx';
import ImageSettings from 'components/admin_console/image_settings.jsx';
@@ -147,6 +148,10 @@ export default (
path='external'
component={ExternalServiceSettings}
/>
+ <Route
+ path='webrtc'
+ component={WebrtcSettings}
+ />
</Route>
<Route path='files'>
<IndexRedirect to='storage'/>