From ea91cbd8ca16ce6780de13fcb14fae98b79a1e24 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 25 May 2014 00:37:19 +0200 Subject: [PATCH 01/55] Defined and implemented contact request/response protocol and data structure. --- doc/CHANNELING-API.txt | 79 ++++++++++++++++++++-- src/app/spreed-webrtc-server/channeling.go | 30 ++++++-- src/app/spreed-webrtc-server/contact.go | 30 ++++++++ src/app/spreed-webrtc-server/hub.go | 79 ++++++++++++++++++++++ src/app/spreed-webrtc-server/server.go | 15 +++- 5 files changed, 221 insertions(+), 12 deletions(-) create mode 100644 src/app/spreed-webrtc-server/contact.go diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index b4624662..50a07ba6 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -303,11 +303,7 @@ Additional types for session listing and notifications Users (Request uses empty data) { - "Type": "Users", - "Users": { - "Type": "Users", - "Users": {} - } + "Type": "Users" } Users (Response with data) @@ -385,6 +381,37 @@ User authorization and session authentication the session (disconnect) and forget the token. +Information retrieval + + Sessions (Request uses Id, Token and Type) + + { + "Type": "Sessions", + "Sessions": { + "Id": "Client generated request ID", + "Token": "Request token", + "TokenType": "Token type" + } + } + + Valid known token types are: "contact", "token". + + Sesions (Response with Id, Token and Type from request and + populated Session list). + + { + "Type": "Sessions", + "Sessions": { + "Id": "ID as from request", + "Token": "Token as from request", + "TokenType": "Type as from request", + "Sessions": [ + "1", "2", "3", ... + ] + } + } + + Chat messages and status information The chat is used to transfer simple messages ore more complex structures @@ -457,6 +484,48 @@ Chat messages and status information message is shown or not. For file transfer information the message is always "File". + Chat with contact request/confirm information + + Request to create a contact token with Id. + + { + "Message": "Some message", + "Time": "2013-11-20T16:28:42+01:00", + "Status": { + "ContactRequest": { + "Id": "client-generated-id" + } + } + } + + Reply with success (Sever generates and inserts token). + + { + "Message": "Some response message", + "Time": "2013-11-20T16:28:59+01:00", + "Status": { + "ContactRequest": { + "Id": "request-id", + "Success": true, + "Token": "server-generated-token-on-success" + } + } + } + + Or reject (no reply is also possible). + + { + "Message": "Some response message", + "Time": "2013-11-20T16:28:59+01:00", + "Status": { + "ContactRequest": { + "Id": "request-id", + "Success": false + } + } + } + + Chat message deliver status extensions Send chat messages as normal, but add the "Mid" field which is a diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index c5e96f3a..b281a785 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -92,16 +92,34 @@ type DataChat struct { } type DataChatMessage struct { - Mid string Message string Time string - NoEcho bool `json:"NoEcho,omitempty"` - Status interface{} + NoEcho bool `json:",omitempty"` + Mid string `json:",omitempty"` + Status *DataChatStatus +} + +type DataChatStatus struct { + Typing string `json:",omitempty"` + State string `json:",omitempty"` + Mid string `json:",omitempty"` + SeenMids []string `json:",omitempty"` + FileInfo *DataFileInfo `json:",omitempty"` + ContactRequest *DataContactRequest `json:",omitempty"` } -type DataChatMessageStatus struct { - State string - Mid string +type DataFileInfo struct { + Id string `json:"id"` + Chunks uint64 `json:"chunks"` + Name string `json:"name"` + Size uint64 `json:"size"` + Type string `json:"type"` +} + +type DataContactRequest struct { + Id string + Success bool + Token string `json:",omitempty"` } type DataIncoming struct { diff --git a/src/app/spreed-webrtc-server/contact.go b/src/app/spreed-webrtc-server/contact.go new file mode 100644 index 00000000..f3ebaa09 --- /dev/null +++ b/src/app/spreed-webrtc-server/contact.go @@ -0,0 +1,30 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package main + +import () + +type Contact struct { + a string + b string + ok bool +} diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index 68ab5b26..f9848303 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -83,6 +83,7 @@ type Hub struct { realm string tokenName string useridRetriever func(*http.Request) (string, error) + contacts *securecookie.SecureCookie } func NewHub(version string, config *Config, sessionSecret, turnSecret, realm string) *Hub { @@ -106,6 +107,8 @@ func NewHub(version string, config *Config, sessionSecret, turnSecret, realm str h.buffers = NewBufferCache(1024, bytes.MinRead) h.buddyImages = NewImageCache() h.tokenName = fmt.Sprintf("token@%s", h.realm) + h.contacts = securecookie.New(h.sessionSecret, nil) + h.contacts.MaxAge(0) return h } @@ -423,3 +426,79 @@ func (h *Hub) sessiontokenHandler(st *SessionToken) (string, error) { return nonce, nil } + +func (h *Hub) contactrequestHandler(c *Connection, to string, cr *DataContactRequest) error { + + var err error + + if cr.Success { + // Client replied with success. + // Decode Token and make sure c.Session.Userid and the to Session.UserId are a match. + contact := &Contact{} + err = h.contacts.Decode("contactRequest", cr.Token, contact) + if err != nil { + return err + } + if contact.ok { + return errors.New("received success with ok state set") + } + bSessionData := c.Session.Data() + if bSessionData.Userid == "" { + return errors.New("no userid") + } + h.mutex.RLock() + aSession, ok := h.sessionTable[to] + h.mutex.RUnlock() + if !ok { + return errors.New("unknown to session for confirm") + } + aSessionData := aSession.Data() + if aSessionData.Userid == "" { + return errors.New("to has no userid for confirm") + } + if aSessionData.Userid != contact.a { + return errors.New("contact mismatch in a") + } + if bSessionData.Userid != contact.b { + return errors.New("contact mismatch in b") + } + contact.ok = true + cr.Token, err = h.contacts.Encode("contactConfirmed", contact) + } else { + if cr.Token != "" { + // Client replied with no success. + // Decode Token. + contact := &Contact{} + err = h.contacts.Decode("contactRequest", cr.Token, contact) + if err != nil { + return err + } + // Remove token. + cr.Token = "" + } else { + // New request. + // Create Token with flag and c.Session.Userid and the to Session.Userid. + aSessionData := c.Session.Data() + if aSessionData.Userid == "" { + return errors.New("no userid") + } + h.mutex.RLock() + bSession, ok := h.sessionTable[to] + h.mutex.RUnlock() + if !ok { + return errors.New("unknown to session") + } + bSessionData := bSession.Data() + if bSessionData.Userid == "" { + return errors.New("to has no userid") + } + // Create object. + contact := &Contact{aSessionData.Userid, bSessionData.Userid, false} + // Serialize. + cr.Token, err = h.contacts.Encode("contactRequest", contact) + } + } + + return err + +} diff --git a/src/app/spreed-webrtc-server/server.go b/src/app/spreed-webrtc-server/server.go index fb45e090..4cd77ab1 100644 --- a/src/app/spreed-webrtc-server/server.go +++ b/src/app/spreed-webrtc-server/server.go @@ -137,11 +137,18 @@ func (s *Server) OnText(c *Connection, b Buffer) { s.Broadcast(c, msg.Chat) } } else { + if msg.Chat.Chat.Status.ContactRequest != nil { + err = s.ContactRequest(c, msg.Chat.To, msg.Chat.Chat.Status.ContactRequest) + if err != nil { + log.Println("Ignoring invalid contact request.", err) + return + } + } atomic.AddUint64(&c.h.unicastChatMessages, 1) s.Unicast(c, msg.Chat.To, msg.Chat) if msg.Chat.Chat.Mid != "" { // Send out delivery confirmation status chat message. - s.Unicast(c, c.Id, &DataChat{To: msg.Chat.To, Type: "Chat", Chat: &DataChatMessage{Mid: msg.Chat.Chat.Mid, Status: &DataChatMessageStatus{State: "sent"}}}) + s.Unicast(c, c.Id, &DataChat{To: msg.Chat.To, Type: "Chat", Chat: &DataChatMessage{Mid: msg.Chat.Chat.Mid, Status: &DataChatStatus{State: "sent"}}}) } } case "Conference": @@ -194,6 +201,12 @@ func (s *Server) UpdateSession(c *Connection, su *SessionUpdate) uint64 { } +func (s *Server) ContactRequest(c *Connection, to string, cr *DataContactRequest) (err error) { + + return c.h.contactrequestHandler(c, to, cr) + +} + func (s *Server) Broadcast(c *Connection, m interface{}) { b := c.h.buffers.New() From ab3e5673b8baa46c5ae725e8e0a36678072effa8 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 25 May 2014 20:04:16 +0200 Subject: [PATCH 02/55] Implemented first simple Ui for contacts in chat. --- src/app/spreed-webrtc-server/channeling.go | 1 + src/app/spreed-webrtc-server/contact.go | 6 +-- src/app/spreed-webrtc-server/hub.go | 8 +-- src/app/spreed-webrtc-server/server.go | 2 + static/js/controllers/chatroomcontroller.js | 39 +++++++++++++- static/js/directives/buddylist.js | 9 ++++ static/js/directives/chat.js | 17 ++++++ static/js/directives/contactrequest.js | 57 +++++++++++++++++++++ static/js/directives/directives.js | 6 ++- static/js/services/buddylist.js | 9 ++++ static/partials/contactrequest.html | 10 ++++ 11 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 static/js/directives/contactrequest.js create mode 100644 static/partials/contactrequest.html diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index b281a785..3ad86555 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -119,6 +119,7 @@ type DataFileInfo struct { type DataContactRequest struct { Id string Success bool + Userid string `json:",omitempty"` Token string `json:",omitempty"` } diff --git a/src/app/spreed-webrtc-server/contact.go b/src/app/spreed-webrtc-server/contact.go index f3ebaa09..940015e4 100644 --- a/src/app/spreed-webrtc-server/contact.go +++ b/src/app/spreed-webrtc-server/contact.go @@ -24,7 +24,7 @@ package main import () type Contact struct { - a string - b string - ok bool + A string + B string + Ok bool } diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index f9848303..b86cab30 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -439,7 +439,7 @@ func (h *Hub) contactrequestHandler(c *Connection, to string, cr *DataContactReq if err != nil { return err } - if contact.ok { + if contact.Ok { return errors.New("received success with ok state set") } bSessionData := c.Session.Data() @@ -456,13 +456,13 @@ func (h *Hub) contactrequestHandler(c *Connection, to string, cr *DataContactReq if aSessionData.Userid == "" { return errors.New("to has no userid for confirm") } - if aSessionData.Userid != contact.a { + if aSessionData.Userid != contact.A { return errors.New("contact mismatch in a") } - if bSessionData.Userid != contact.b { + if bSessionData.Userid != contact.B { return errors.New("contact mismatch in b") } - contact.ok = true + contact.Ok = true cr.Token, err = h.contacts.Encode("contactConfirmed", contact) } else { if cr.Token != "" { diff --git a/src/app/spreed-webrtc-server/server.go b/src/app/spreed-webrtc-server/server.go index 4cd77ab1..742bdbd5 100644 --- a/src/app/spreed-webrtc-server/server.go +++ b/src/app/spreed-webrtc-server/server.go @@ -143,6 +143,8 @@ func (s *Server) OnText(c *Connection, b Buffer) { log.Println("Ignoring invalid contact request.", err) return } + msg.Chat.Chat.Status.ContactRequest.Userid = c.Session.Userid + log.Println("CCCCCCCC", msg.Chat.Chat.Status.ContactRequest) } atomic.AddUint64(&c.h.unicastChatMessages, 1) s.Unicast(c, msg.Chat.To, msg.Chat) diff --git a/static/js/controllers/chatroomcontroller.js b/static/js/controllers/chatroomcontroller.js index 74c66656..7b88dce7 100644 --- a/static/js/controllers/chatroomcontroller.js +++ b/static/js/controllers/chatroomcontroller.js @@ -18,7 +18,7 @@ * along with this program. If not, see . * */ -define(['underscore', 'moment', 'text!partials/fileinfo.html'], function(_, moment, templateFileInfo) { +define(['underscore', 'moment', 'text!partials/fileinfo.html', 'text!partials/contactrequest.html'], function(_, moment, templateFileInfo, templateContactRequest) { // ChatroomController return ["$scope", "$element", "$window", "safeMessage", "safeDisplayName", "$compile", "$filter", "translation", function($scope, $element, $window, safeMessage, safeDisplayName, $compile, $filter, translation) { @@ -45,6 +45,7 @@ define(['underscore', 'moment', 'text!partials/fileinfo.html'], function(_, mome var displayName = safeDisplayName; var buddyImageSrc = $filter("buddyImageSrc"); var fileInfo = $compile(templateFileInfo); + var contactRequest = $compile(templateContactRequest); var knowMessage = { r: {}, @@ -397,6 +398,7 @@ define(['underscore', 'moment', 'text!partials/fileinfo.html'], function(_, mome var fromself = from === userid; var noop = false; var element = null; + var subscope; var timestamp = data.Time; if (!timestamp) { @@ -441,7 +443,7 @@ define(['underscore', 'moment', 'text!partials/fileinfo.html'], function(_, mome // File offers. if (data.Status.FileInfo) { - var subscope = $scope.$new(); + subscope = $scope.$new(); subscope.info = data.Status.FileInfo; subscope.from = from; fileInfo(subscope, function(clonedElement, scope) { @@ -451,6 +453,39 @@ define(['underscore', 'moment', 'text!partials/fileinfo.html'], function(_, mome noop = true; } + // Contact request. + if (data.Status.ContactRequest) { + subscope = $scope.$new(); + subscope.request = data.Status.ContactRequest; + subscope.fromself = fromself; + contactRequest(subscope, function(clonedElement, scope) { + var text; + if (fromself) { + if (scope.request.Userid) { + if (scope.request.Success) { + text = translation._("You accepted the contact request."); + } else { + text = translation._("You rejected the contact request."); + } + } else { + text = translation._("You sent a contact request."); + } + } else { + if (scope.request.Success) { + text = translation._("Your contact request was accepted."); + } else{ + if (scope.request.Token) { + text = translation._("Incoming contact request."); + } else { + text = translation._("Your contact request was rejected."); + } + } + } + element = $scope.showmessage(from, timestamp, text, clonedElement); + }); + noop = true; + } + // Ignore unknown status messages. if (message === null && nodes === null) { noop = true; diff --git a/static/js/directives/buddylist.js b/static/js/directives/buddylist.js index 515b311d..6bdcfe95 100644 --- a/static/js/directives/buddylist.js +++ b/static/js/directives/buddylist.js @@ -46,6 +46,15 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { }; + $scope.doContact = function(id) { + + //console.log("doContact", id); + $scope.$emit("requestcontact", id, { + restore: true + }); + + }; + $scope.doAudioConference = function(id) { $scope.updateAutoAccept(id); diff --git a/static/js/directives/chat.js b/static/js/directives/chat.js index c40dc455..503132a7 100644 --- a/static/js/directives/chat.js +++ b/static/js/directives/chat.js @@ -138,6 +138,23 @@ define(['underscore', 'text!partials/chat.html', 'text!partials/chatroom.html'], }); + $scope.$parent.$on("requestcontact", function(event, id, options) { + + if (id !== group_chat_id) { + var subscope = $scope.showRoom(id, { + title: translation._("Chat with") + }, options); + $timeout(function() { + subscope.sendChat(id, "Contact request", { + ContactRequest: { + Id: randomGen.random({hex: true}) + } + }); + }, 0); + } + + }); + }]; var compile = function(tElement, tAttrs) { diff --git a/static/js/directives/contactrequest.js b/static/js/directives/contactrequest.js new file mode 100644 index 00000000..7fd97276 --- /dev/null +++ b/static/js/directives/contactrequest.js @@ -0,0 +1,57 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +define(['jquery', 'underscore'], function($, _) { + + return ["translation", function(translation) { + + var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { + + $scope.state = "request"; + $scope.doAccept = function() { + $scope.state = "accepted"; + $scope.doContact(true); + }; + + $scope.doReject = function() { + $scope.state = "rejected"; + $scope.doContact(false); + }; + + $scope.doContact = function(success) { + var request = $scope.request; + request.Success = !!success; + $scope.sendChat($scope.id, "Contact request answer", { + ContactRequest: request + }); + }; + + }]; + + return { + scope: true, + restrict: 'EAC', + controller: controller, + replace: false + } + + }]; + +}); diff --git a/static/js/directives/directives.js b/static/js/directives/directives.js index 510fd6e3..6067b952 100644 --- a/static/js/directives/directives.js +++ b/static/js/directives/directives.js @@ -35,7 +35,8 @@ define([ 'directives/screenshare', 'directives/roombar', 'directives/socialshare', - 'directives/page'], function(_, onEnter, onEscape, statusMessage, buddyList, buddyPicture, settings, chat, audioVideo, usability, audioLevel, fileInfo, screenshare, roomBar, socialShare, page) { + 'directives/page', + 'directives/contactrequest'], function(_, onEnter, onEscape, statusMessage, buddyList, settings, chat, audioVideo, usability, audioLevel, fileInfo, screenshare, roomBar, socialShare, page, contactRequest) { var directives = { onEnter: onEnter, @@ -52,7 +53,8 @@ define([ screenshare: screenshare, roomBar: roomBar, socialShare: socialShare, - page: page + page: page, + contactRequest: contactRequest }; var initialize = function(angModule) { diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 4b4b9f57..ab9cebad 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -144,6 +144,11 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! var buddyElement = $(event.currentTarget); buddyElement.scope().doDefault(); }, this)); + $element.on("click", ".fa-star-o", _.bind(function(event) { + event.stopPropagation(); + var buddyElement = $(event.currentTarget); + buddyElement.scope().doDefaultContact(); + }, this)); $element.attr("data-xthreshold", "10"); $element.on("swipeleft", ".buddy", _.bind(function(event) { event.preventDefault(); @@ -195,6 +200,10 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! } return scope.doCall(id); }; + scope.doDefaultContact = function() { + var id = scope.session.Id; + return scope.doContact(id); + }; scope.$on("$destroy", function() { scope.element = null; scope.killed = true; diff --git a/static/partials/contactrequest.html b/static/partials/contactrequest.html new file mode 100644 index 00000000..cf756d12 --- /dev/null +++ b/static/partials/contactrequest.html @@ -0,0 +1,10 @@ +
+
+
+
+ + +
+
+
+
\ No newline at end of file From ea43e57800932b39916e8e09b60279d46767cf95 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 25 May 2014 20:39:32 +0200 Subject: [PATCH 03/55] Fixed nil pointer. --- src/app/spreed-webrtc-server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/spreed-webrtc-server/server.go b/src/app/spreed-webrtc-server/server.go index 742bdbd5..359ca52a 100644 --- a/src/app/spreed-webrtc-server/server.go +++ b/src/app/spreed-webrtc-server/server.go @@ -137,7 +137,7 @@ func (s *Server) OnText(c *Connection, b Buffer) { s.Broadcast(c, msg.Chat) } } else { - if msg.Chat.Chat.Status.ContactRequest != nil { + if msg.Chat.Chat.Status != nil && msg.Chat.Chat.Status.ContactRequest != nil { err = s.ContactRequest(c, msg.Chat.To, msg.Chat.Chat.Status.ContactRequest) if err != nil { log.Println("Ignoring invalid contact request.", err) From 1227dbccef27b712a40ffd41fcb14774db906d4f Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 25 May 2014 21:22:22 +0200 Subject: [PATCH 04/55] Started end points for contact storage. --- static/js/directives/contactrequest.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/static/js/directives/contactrequest.js b/static/js/directives/contactrequest.js index 7fd97276..57ade37e 100644 --- a/static/js/directives/contactrequest.js +++ b/static/js/directives/contactrequest.js @@ -36,13 +36,23 @@ define(['jquery', 'underscore'], function($, _) { }; $scope.doContact = function(success) { - var request = $scope.request; - request.Success = !!success; + var r = $scope.request; + r.Success = !!success; $scope.sendChat($scope.id, "Contact request answer", { - ContactRequest: request + ContactRequest: r }); }; + $scope.addContact = function(request) { + console.log("AAAAAAAA", request); + }; + + // Add support for contacts on controller creation. + var request = $scope.request; + if (request.Success && request.Userid && request.Token) { + $scope.addContact(request); + } + }]; return { From 4c1f2e6fc8de06f060ea3e4e51fece5a189fb885 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Wed, 28 May 2014 14:29:22 +0200 Subject: [PATCH 05/55] Updated autocall feature to reflect API changes for contacts. --- doc/CHANNELING-API.txt | 16 +++++++++------- src/app/spreed-webrtc-server/channeling.go | 6 ++++++ static/js/directives/buddylist.js | 6 ++++-- static/js/services/buddylist.js | 2 +- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index 50a07ba6..1f23cef4 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -605,22 +605,24 @@ Chat messages and status information } - Request being called from audio mixer + Request an automatic callback, by sending a chat message with the AutoCall + document in Status. { "Type": "Chat", "Chat": { - "Message": null, + "Message": "Call me back", "Status": { - "type": "conference", - "id": "my-conference-room" + "AutoCall": { + "Type": "conference", + "Id": "my-conference-room" } } } - This can be sent to any participant that has a "Status" containing the "isMixer" - flag with a "true" value. The peer will then try to establish a peer connection - to the caller which it needs to pick up to join an audio conference. + For example this can be sent to sessions which have "autoCalls" set in session + status. The peer will then try to establish a peer connection to the caller which + the client eeds to pick up automatically. Data channel only messages diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index 3ad86555..c07c3753 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -106,6 +106,7 @@ type DataChatStatus struct { SeenMids []string `json:",omitempty"` FileInfo *DataFileInfo `json:",omitempty"` ContactRequest *DataContactRequest `json:",omitempty"` + AutoCall *DataAutoCall `json:",omitempty"` } type DataFileInfo struct { @@ -123,6 +124,11 @@ type DataContactRequest struct { Token string `json:",omitempty"` } +type DataAutoCall struct { + Id string + Type string +} + type DataIncoming struct { Type string Hello *DataHello diff --git a/static/js/directives/buddylist.js b/static/js/directives/buddylist.js index 6bdcfe95..191ab81a 100644 --- a/static/js/directives/buddylist.js +++ b/static/js/directives/buddylist.js @@ -59,8 +59,10 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { $scope.updateAutoAccept(id); mediaStream.api.sendChat(id, null, { - type: "conference", - id: mediaStream.connector.roomid + AutoCall: { + Type: "conference", + Id: mediaStream.connector.roomid + } }) }; diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index ab9cebad..1bcedd03 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -464,7 +464,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! } else { var scope = buddyData.get(id); var template = buddyActions; - if (scope.status.isMixer) { + if (scope.status.autoCalls && _.indexOf(scope.status.autoCalls, "conference") !== -1) { template = buddyActionsForAudioMixer; } //console.log("scope", scope, id); From 8b4156beba9e081c4e2066ccedc232128dcb4fad Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Wed, 28 May 2014 15:43:22 +0200 Subject: [PATCH 06/55] Do not allow to send contact requests to own userid sessions. --- src/app/spreed-webrtc-server/hub.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index b86cab30..15a1cb0f 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -492,6 +492,9 @@ func (h *Hub) contactrequestHandler(c *Connection, to string, cr *DataContactReq if bSessionData.Userid == "" { return errors.New("to has no userid") } + if bSessionData.Userid == aSessionData.Userid { + return errors.New("to userid cannot be the same as own userid") + } // Create object. contact := &Contact{aSessionData.Userid, bSessionData.Userid, false} // Serialize. From bd2bb7899462cff05f8e7f146392a037d0c35215 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Wed, 28 May 2014 16:30:24 +0200 Subject: [PATCH 07/55] Implemented support to request and accept contacts when in a call with that contact. --- static/js/controllers/chatroomcontroller.js | 10 +- static/js/directives/chat.js | 100 ++++++++++++-------- static/js/directives/contactrequest.js | 4 +- 3 files changed, 68 insertions(+), 46 deletions(-) diff --git a/static/js/controllers/chatroomcontroller.js b/static/js/controllers/chatroomcontroller.js index 7b88dce7..0b814c2e 100644 --- a/static/js/controllers/chatroomcontroller.js +++ b/static/js/controllers/chatroomcontroller.js @@ -286,17 +286,17 @@ define(['underscore', 'moment', 'text!partials/fileinfo.html', 'text!partials/co $scope.showmessage = function(from, timestamp, message, nodes) { - var userid = $scope.$parent.$parent.id; + var sessonid = $scope.$parent.$parent.id; // Prepare message to display. var s = []; if (message) { s.push(message); - $scope.$emit("incoming", message, from, userid); + $scope.$emit("incoming", message, from, sessonid); } var is_new_message = lastSender !== from; - var is_self = from === userid; + var is_self = from === sessonid; var extra_css = ""; var title = null; @@ -374,7 +374,7 @@ define(['underscore', 'moment', 'text!partials/fileinfo.html', 'text!partials/co $scope.$on("received", function(event, from, data) { - var userid = $scope.$parent.$parent.id; + var sessionid = $scope.$parent.$parent.id; var mid = data.Mid || null; switch (data.Type) { @@ -395,7 +395,7 @@ define(['underscore', 'moment', 'text!partials/fileinfo.html', 'text!partials/co // Definitions. var message = null; var nodes = null; - var fromself = from === userid; + var fromself = from === sessionid; var noop = false; var element = null; var subscope; diff --git a/static/js/directives/chat.js b/static/js/directives/chat.js index 503132a7..c58916c1 100644 --- a/static/js/directives/chat.js +++ b/static/js/directives/chat.js @@ -20,7 +20,7 @@ */ define(['underscore', 'text!partials/chat.html', 'text!partials/chatroom.html'], function(_, templateChat, templateChatroom) { - return ["$compile", "safeDisplayName", "mediaStream", "safeApply", "desktopNotify", "translation", "playSound", "fileUpload", "randomGen", "buddyData", "$timeout", function($compile, safeDisplayName, mediaStream, safeApply, desktopNotify, translation, playSound, fileUpload, randomGen, buddyData, $timeout) { + return ["$compile", "safeDisplayName", "mediaStream", "safeApply", "desktopNotify", "translation", "playSound", "fileUpload", "randomGen", "buddyData", "appData", "$timeout", function($compile, safeDisplayName, mediaStream, safeApply, desktopNotify, translation, playSound, fileUpload, randomGen, buddyData, appData, $timeout) { var displayName = safeDisplayName; var group_chat_id = ""; @@ -141,16 +141,26 @@ define(['underscore', 'text!partials/chat.html', 'text!partials/chatroom.html'], $scope.$parent.$on("requestcontact", function(event, id, options) { if (id !== group_chat_id) { + // Make sure the contact id is valid. + var ad = appData.get(); + if (!ad.userid) { + // Unable to add contacts as we have no own userid. + console.log("You need to log in to add contacts."); + return; + } + var bd = buddyData.get(id); + if (!bd || !bd.session || !bd.session.Userid || ad.userid === bd.session.Userid) { + console.log("This contact cannot be added."); + return; + } var subscope = $scope.showRoom(id, { title: translation._("Chat with") }, options); - $timeout(function() { - subscope.sendChat(id, "Contact request", { - ContactRequest: { - Id: randomGen.random({hex: true}) - } - }); - }, 0); + subscope.sendChatServer(id, "Contact request", { + ContactRequest: { + Id: randomGen.random({hex: true}) + } + }); } }); @@ -222,47 +232,59 @@ define(['underscore', 'text!partials/chat.html', 'text!partials/chatroom.html'], subscope.sendChat = function(to, message, status, mid, noloop) { //console.log("send chat", to, scope.peer); var peercall = mediaStream.webrtc.findTargetCall(to); - if (message && !mid) { - mid = randomGen.random({ - hex: true - }); - } if (peercall && peercall.peerconnection.datachannelReady) { subscope.p2p(true); // Send out stuff through data channel. - _.delay(function() { - mediaStream.api.apply("sendChat", { - send: function(type, data) { - // We also send to self, to display our own stuff. - if (!noloop) { - mediaStream.api.received({ - Type: data.Type, - Data: data, - From: mediaStream.api.id, - To: peercall.id - }); - } - return peercall.peerconnection.send(data); - } - })(to, message, status, mid); - }, 100); - + return subscope.sendChatPeer2Peer(peercall, to, message, status, mid, noloop); } else { subscope.p2p(false); - _.delay(function() { - mediaStream.api.send2("sendChat", function(type, data) { + return subscope.sendChatServer(to, message, status, mid, noloop); + } + return mid; + }; + subscope.sendChatPeer2Peer = function(peercall, to, message, status, mid, noloop) { + if (message && !mid) { + mid = randomGen.random({ + hex: true + }); + } + _.delay(function() { + mediaStream.api.apply("sendChat", { + send: function(type, data) { + // We also send to self, to display our own stuff. if (!noloop) { - //console.log("looped to self", type, data); mediaStream.api.received({ Type: data.Type, Data: data, From: mediaStream.api.id, - To: to + To: peercall.id }); } - })(to, message, status, mid); - }, 100); + return peercall.peerconnection.send(data); + } + })(to, message, status, mid); + }, 100); + return mid; + }; + subscope.sendChatServer = function(to, message, status, mid, noloop) { + if (message && !mid) { + mid = randomGen.random({ + hex: true + }); } + _.delay(function() { + mediaStream.api.send2("sendChat", function(type, data) { + if (!noloop) { + //console.log("looped to self", type, data); + mediaStream.api.received({ + Type: data.Type, + Data: data, + From: mediaStream.api.id, + To: to + }); + } + })(to, message, status, mid); + }, 100); return mid; }; subscope.p2p = function(state) { @@ -312,8 +334,8 @@ define(['underscore', 'text!partials/chat.html', 'text!partials/chatroom.html'], } safeApply(subscope); }); - subscope.$on("incoming", function(event, message, from, userid) { - if (from !== userid) { + subscope.$on("incoming", function(event, message, from, sessionid) { + if (from !== sessionid) { subscope.pending++; scope.$emit("chatincoming", subscope.id); } @@ -321,7 +343,7 @@ define(['underscore', 'text!partials/chat.html', 'text!partials/chatroom.html'], var room = event.targetScope.id; // Make sure we are not in group chat or the message is from ourselves // before we beep and shout. - if (!subscope.isgroupchat && from !== userid) { + if (!subscope.isgroupchat && from !== sessionid) { playSound.play("message1"); desktopNotify.notify(translation._("Message from ") + displayName(from), message); } diff --git a/static/js/directives/contactrequest.js b/static/js/directives/contactrequest.js index 57ade37e..89c3fd08 100644 --- a/static/js/directives/contactrequest.js +++ b/static/js/directives/contactrequest.js @@ -31,14 +31,14 @@ define(['jquery', 'underscore'], function($, _) { }; $scope.doReject = function() { - $scope.state = "rejected"; + $scope.state = "rejected"; $scope.doContact(false); }; $scope.doContact = function(success) { var r = $scope.request; r.Success = !!success; - $scope.sendChat($scope.id, "Contact request answer", { + $scope.sendChatServer($scope.id, "Contact request answer", { ContactRequest: r }); }; From 8792ebac2937b51cc473243838e9db1422c21e0e Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Fri, 30 May 2014 16:09:15 +0200 Subject: [PATCH 08/55] Implemented next bunch of contact services. --- src/styles/components/_buddylist.scss | 6 +++ static/js/directives/buddylist.js | 10 ++-- static/js/directives/contactrequest.js | 8 +-- static/js/services/buddydata.js | 9 +++- static/js/services/buddylist.js | 33 ++++++++++-- static/js/services/contactdata.js | 73 ++++++++++++++++++++++++++ static/js/services/contacts.js | 41 +++++++++++++++ static/js/services/services.js | 12 +++-- static/partials/buddy.html | 4 +- 9 files changed, 179 insertions(+), 17 deletions(-) create mode 100644 static/js/services/contactdata.js create mode 100644 static/js/services/contacts.js diff --git a/src/styles/components/_buddylist.scss b/src/styles/components/_buddylist.scss index d7f7d6fc..73a3352f 100644 --- a/src/styles/components/_buddylist.scss +++ b/src/styles/components/_buddylist.scss @@ -135,6 +135,12 @@ &.hovered .buddyactions { right: 0; } + & .fa.contact:before { + content: "\f006"; + } + &.contact .fa.contact:before { + content: "\f005"; + } .buddyPicture { background: $actioncolor1; border-radius: 2px; diff --git a/static/js/directives/buddylist.js b/static/js/directives/buddylist.js index 191ab81a..5fdb7e25 100644 --- a/static/js/directives/buddylist.js +++ b/static/js/directives/buddylist.js @@ -21,7 +21,7 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { // buddyList - return ["$compile", "buddyList", "mediaStream", function($compile, buddyList, mediaStream) { + return ["$compile", "buddyList", "mediaStream", "contacts", function($compile, buddyList, mediaStream, contacts) { //console.log("buddyList directive"); @@ -84,6 +84,7 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { var onJoined = _.bind(buddylist.onJoined, buddylist); var onLeft = _.bind(buddylist.onLeft, buddylist); var onStatus = _.bind(buddylist.onStatus, buddylist); + var onContactAdded = _.bind(buddylist.onContactAdded, buddylist); mediaStream.api.e.on("received.userleftorjoined", function(event, dataType, data) { if (dataType === "Left") { onLeft(data); @@ -108,11 +109,14 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { $scope.setRoomStatus(false); buddylist.onClosed(); }); - - // Request user list whenever the connection comes ready. + // Request user list whenever the connection comes ready. mediaStream.connector.ready(function() { mediaStream.api.requestUsers(); }); + // Contacts. + contacts.e.on("contactadded", function(event, data) { + onContactAdded(data); + }); }]; diff --git a/static/js/directives/contactrequest.js b/static/js/directives/contactrequest.js index 89c3fd08..3889d13b 100644 --- a/static/js/directives/contactrequest.js +++ b/static/js/directives/contactrequest.js @@ -20,7 +20,7 @@ */ define(['jquery', 'underscore'], function($, _) { - return ["translation", function(translation) { + return ["translation", "buddyData", "contacts", function(translation, buddyData, contacts) { var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { @@ -43,14 +43,14 @@ define(['jquery', 'underscore'], function($, _) { }); }; - $scope.addContact = function(request) { - console.log("AAAAAAAA", request); + $scope.addContact = function(request, status) { + contacts.add(request, status) }; // Add support for contacts on controller creation. var request = $scope.request; if (request.Success && request.Userid && request.Token) { - $scope.addContact(request); + $scope.addContact(request, buddyData.lookup($scope.id).status || null); } }]; diff --git a/static/js/services/buddydata.js b/static/js/services/buddydata.js index 16a2447e..fc0e110e 100644 --- a/static/js/services/buddydata.js +++ b/static/js/services/buddydata.js @@ -21,7 +21,7 @@ define(['underscore'], function(underscore) { // buddyData - return [function() { + return ["contactData", function(contactData) { var scopes = {}; var brain = {}; @@ -61,7 +61,7 @@ define(['underscore'], function(underscore) { } return 0; }, - get: function(id, createInParent, afterCreateCallback) { + get: function(id, createInParent, afterCreateCallback, userid) { if (scopes.hasOwnProperty(id)) { return scopes[id]; } else if (!createInParent && pushed.hasOwnProperty(id)) { @@ -71,6 +71,11 @@ define(['underscore'], function(underscore) { // If we have a parent we can create a new scope. var scope = scopes[id] = createInParent.$new(); scope.buddyIndex = ++count; + if (userid) { + scope.contact = contactData.get(userid); + } else { + scope.contact = null; + } scope.buddyIndexSortable = ("0000000" + scope.buddyIndex).slice(-7); if (pushed.hasOwnProperty(id)) { // Refresh pushed scope reference. diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 1bcedd03..5bbd4aab 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -102,6 +102,12 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; + BuddyTree.prototype.traverse = function(cb) { + + return this.tree.inOrderTraverse(cb); + + }; + BuddyTree.prototype.clear = function() { this.tree.clear(); @@ -144,7 +150,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! var buddyElement = $(event.currentTarget); buddyElement.scope().doDefault(); }, this)); - $element.on("click", ".fa-star-o", _.bind(function(event) { + $element.on("click", ".fa.contact", _.bind(function(event) { event.stopPropagation(); var buddyElement = $(event.currentTarget); buddyElement.scope().doDefaultContact(); @@ -348,7 +354,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! //console.log("onStatus", status); var id = data.Id; - var scope = buddyData.get(id, this.$scope, _.bind(this.onBuddyScope, this)); + var scope = buddyData.get(id, this.$scope, _.bind(this.onBuddyScope, this), data.Userid); // Update session. scope.session.Userid = data.Userid; scope.session.Rev = data.Rev; @@ -377,7 +383,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! //console.log("Joined", data); var id = data.Id; - var scope = buddyData.get(id, this.$scope, _.bind(this.onBuddyScope, this)); + var scope = buddyData.get(id, this.$scope, _.bind(this.onBuddyScope, this), data.Userid); // Create session. scope.session = { Id: data.Id, @@ -405,6 +411,27 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; + Buddylist.prototype.onContactAdded = function(contact) { + + console.log("onContactAdded", contact); + var userid = contact.Userid; + // TODO(longsleep): Traversing the whole tree is stupid. Implement key/value map for this. + this.tree.traverse(function(record) { + var scope = buddyData.lookup(record.id, true); + var sessionUserid = scope.session.Userid; + if (sessionUserid === userid) { + scope.contact = contact; + } + }); + + }; + + Buddylist.prototype.onContactRemoved = function(data) { + + console.log("onContactRemoved", data); + + }; + Buddylist.prototype.onLeft = function(data) { //console.log("Left", session); diff --git a/static/js/services/contactdata.js b/static/js/services/contactdata.js new file mode 100644 index 00000000..2becce40 --- /dev/null +++ b/static/js/services/contactdata.js @@ -0,0 +1,73 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +define(['underscore', 'jquery'], function(underscore, $) { + + // contactData + return [function() { + + var contacts = {}; + var users = {}; + var count = 0; + + var contactData = { + addByRequest: function(request, status) { + console.log("addByRequest", request, status); + var userid = request.Userid; + var token = request.Token; + var id; + if (users.hasOwnProperty(userid)) { + // Existing contact. Replace it. + id = users[userid]; + } else { + id = String(count++); + users[userid] = id; + } + var contact = contacts[id] = { + Id: "contact-"+id, + Userid: userid, + Token: token, + Status: $.extend({}, status) + } + // TODO(longsleep): Trigger this to somewhere. + return contact; + }, + get: function(userid) { + if (users.hasOwnProperty(userid)) { + var id = users[userid]; + return contacts[id]; + } + return null; + }, + getById: function(id) { + if (id.indexOf("contact-") === 0) { + id = id.substr(8) + } + if (contacts.hasOwnProperty(id)) { + return contacts[id]; + } + return null + } + }; + return contactData; + + }]; + +}); diff --git a/static/js/services/contacts.js b/static/js/services/contacts.js new file mode 100644 index 00000000..94714d72 --- /dev/null +++ b/static/js/services/contacts.js @@ -0,0 +1,41 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +define(['underscore', 'jquery'], function(underscore, $) { + + // contacts + return ["contactData", function(contactData) { + + var Contacts = function() { + this.e = $({}); + }; + + Contacts.prototype.add = function(request, status) { + + var contact = contactData.addByRequest(request, status); + this.e.triggerHandler("contactadded", contact); + + }; + + return new Contacts(); + + }]; + +}); diff --git a/static/js/services/services.js b/static/js/services/services.js index 2a9e0bc1..a15f3dba 100644 --- a/static/js/services/services.js +++ b/static/js/services/services.js @@ -43,7 +43,9 @@ define([ 'services/randomgen', 'services/fastscroll', 'services/videowaiter', - 'services/videolayout'], function(_, + 'services/videolayout', + 'services/contactdata', + 'services/contacts'], function(_, desktopNotify, playSound, safeApply, @@ -66,7 +68,9 @@ safeDisplayName, randomGen, fastScroll, videoWaiter, -videoLayout) { +videoLayout, +contactData, +contacts) { var services = { desktopNotify: desktopNotify, @@ -91,7 +95,9 @@ videoLayout) { randomGen: randomGen, fastScroll: fastScroll, videoWaiter: videoWaiter, - videoLayout: videoLayout + videoLayout: videoLayout, + contactData: contactData, + contacts: contacts }; var initialize = function(angModule) { diff --git a/static/partials/buddy.html b/static/partials/buddy.html index 0f94e185..26b59bdd 100644 --- a/static/partials/buddy.html +++ b/static/partials/buddy.html @@ -1,5 +1,5 @@ -
+
{{session.Id|displayName}}
-
{{session.Ua}}
+
{{session.Ua}}
From eae6b31dcd8b4bd6f9860f93fb0426e6ebf72792 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Fri, 30 May 2014 18:22:42 +0200 Subject: [PATCH 09/55] Cleaned up borked revision checks. --- static/js/directives/buddylist.js | 2 +- static/js/services/buddylist.js | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/static/js/directives/buddylist.js b/static/js/directives/buddylist.js index 5fdb7e25..e7540529 100644 --- a/static/js/directives/buddylist.js +++ b/static/js/directives/buddylist.js @@ -109,7 +109,7 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { $scope.setRoomStatus(false); buddylist.onClosed(); }); - // Request user list whenever the connection comes ready. + // Request user list whenever the connection comes ready. mediaStream.connector.ready(function() { mediaStream.api.requestUsers(); }); diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 5bbd4aab..d2d20718 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -352,16 +352,19 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! Buddylist.prototype.onStatus = function(data) { - //console.log("onStatus", status); + //console.log("onStatus", data); var id = data.Id; var scope = buddyData.get(id, this.$scope, _.bind(this.onBuddyScope, this), data.Userid); // Update session. - scope.session.Userid = data.Userid; - scope.session.Rev = data.Rev; + if (scope.session.Userid !== data.Userid) { + scope.session.Userid = data.Userid; + console.log("onStatus session is now userid", id, data.Userid); + } // Update status. - if (scope.status && scope.status.Rev >= data.Rev) { - console.warn("Received old status update in status", data.Rev, scope.status.Rev); - } else { + if (true) { + if (data.Rev) { + scope.session.Rev = data.Rev; + } scope.status = data.Status; this.updateBuddyPicture(scope.status); var displayName = scope.displayName; @@ -394,9 +397,10 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! // Add status. buddyCount++; if (data.Status) { - if (scope.status && scope.status.Rev >= data.Status.Rev) { - console.warn("Received old status update in join", data.Status.Rev, scope.status.Rev); - } else { + if (true) { + if (data.Rev) { + scope.session.Rev = data.Rev; + } scope.status = data.Status; scope.displayName = scope.status.displayName; this.updateBuddyPicture(scope.status); From 153ebc51d9ef4bc12b9bee75471ff637b5bb400e Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Mon, 2 Jun 2014 17:18:15 +0200 Subject: [PATCH 10/55] Implemented buddySession which aggregates sessions of the same userid. --- static/js/services/buddydata.js | 20 ++++- static/js/services/buddylist.js | 117 ++++++++++++++---------- static/js/services/buddysession.js | 140 +++++++++++++++++++++++++++++ static/js/services/services.js | 9 +- static/partials/buddy.html | 2 +- 5 files changed, 235 insertions(+), 53 deletions(-) create mode 100644 static/js/services/buddysession.js diff --git a/static/js/services/buddydata.js b/static/js/services/buddydata.js index fc0e110e..212b898d 100644 --- a/static/js/services/buddydata.js +++ b/static/js/services/buddydata.js @@ -67,9 +67,20 @@ define(['underscore'], function(underscore) { } else if (!createInParent && pushed.hasOwnProperty(id)) { return pushed[id].scope; } else { + var scope; + if (userid && scopes.hasOwnProperty(userid)) { + scope = scopes[userid]; + if (createInParent) { + scopes[id] = scope; + } + return scope; + } if (createInParent) { // If we have a parent we can create a new scope. - var scope = scopes[id] = createInParent.$new(); + scope = scopes[id] = createInParent.$new(); + if (userid) { + scopes[userid] = scope; + } scope.buddyIndex = ++count; if (userid) { scope.contact = contactData.get(userid); @@ -107,12 +118,17 @@ define(['underscore'], function(underscore) { var scope = scopes[id]; if (scope) { scope.$destroy(); - brain[id] = scope; + if (!hard) { + brain[id] = scope; + } delete scopes[id]; return scope; } else { return null; } + }, + set: function(id, scope) { + scopes[id] = scope; } }; return buddyData; diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index d2d20718..d4d9dd56 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -116,7 +116,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; // buddyList - return ["$window", "$compile", "playSound", "buddyData", "fastScroll", "mediaStream", function($window, $compile, playSound, buddyData, fastScroll, mediaStream) { + return ["$window", "$compile", "playSound", "buddyData", "buddySession", "fastScroll", "mediaStream", function($window, $compile, playSound, buddyData, buddySession, fastScroll, mediaStream) { var requestAnimationFrame = $window.requestAnimationFrame; @@ -196,7 +196,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; - Buddylist.prototype.onBuddyScope = function(scope) { + Buddylist.prototype.onBuddyScopeCreated = function(scope) { scope.element = null; scope.doDefault = function() { @@ -217,6 +217,28 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; + Buddylist.prototype.onBuddySessionUserid = function(scope, sourceSession) { + + var targetScope = buddyData.get(sourceSession.Userid); + if (targetScope === scope) { + // No action. + return; + } + // Merge sessions. + targetScope.session.merge(sourceSession); + // Cleanup old from tree and DOM. + var id = sourceSession.Id; + this.tree.remove(id); + if (scope.element) { + this.lefts[id] = scope.element; + scope.element = null; + } + scope.$destroy(); + buddyData.set(id, targetScope); + delete this.actionElements[id]; + + }; + Buddylist.prototype.soundLoop = function() { if (this.playSoundLeft) { @@ -354,22 +376,21 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! //console.log("onStatus", data); var id = data.Id; - var scope = buddyData.get(id, this.$scope, _.bind(this.onBuddyScope, this), data.Userid); + var scope = buddyData.get(id, this.$scope, _.bind(function(scope) { + this.onBuddyScopeCreated(scope); + scope.session = buddySession.create(data); + }, this), data.Userid); // Update session. - if (scope.session.Userid !== data.Userid) { - scope.session.Userid = data.Userid; - console.log("onStatus session is now userid", id, data.Userid); - } - // Update status. - if (true) { - if (data.Rev) { - scope.session.Rev = data.Rev; - } - scope.status = data.Status; - this.updateBuddyPicture(scope.status); + var sessionData = scope.session.update(id, data, _.bind(function(session) { + //console.log("Session is now authenticated", session); + this.onBuddySessionUserid(scope, session); + }, this)); + if (sessionData) { + // onStatus for main session. + scope.status = sessionData.Status; var displayName = scope.displayName; - if (scope.status.displayName) { - scope.displayName = scope.status.displayName; + if (sessionData.Status.displayName) { + scope.displayName = sessionData.Status.displayName; } else { scope.displayName = null; } @@ -377,8 +398,9 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! var before = this.tree.update(id, scope); this.queue.push(["status", id, before]); } - scope.$apply(); + this.updateBuddyPicture(sessionData.Status); } + scope.$apply(); }; @@ -386,31 +408,24 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! //console.log("Joined", data); var id = data.Id; - var scope = buddyData.get(id, this.$scope, _.bind(this.onBuddyScope, this), data.Userid); - // Create session. - scope.session = { - Id: data.Id, - Userid: data.Userid, - Ua: data.Ua, - Rev: 0 - }; - // Add status. + var scope = buddyData.get(id, this.$scope, _.bind(function(scope) { + this.onBuddyScopeCreated(scope); + scope.session = buddySession.create(data); + }, this), data.Userid); + // Update session. buddyCount++; - if (data.Status) { - if (true) { - if (data.Rev) { - scope.session.Rev = data.Rev; - } - scope.status = data.Status; - scope.displayName = scope.status.displayName; - this.updateBuddyPicture(scope.status); + var sessionData = scope.session.update(id, data); + if (sessionData && sessionData.Status) { + scope.status = sessionData.Status; + scope.displayName = sessionData.Status.displayName; + this.updateBuddyPicture(sessionData.Status); + if (!scope.element) { + var before = this.tree.add(id, scope); + this.queue.push(["joined", id, before]); + this.playSoundJoined = true; } - } - //console.log("Joined scope", scope, scope.element); - if (!scope.element) { - var before = this.tree.add(id, scope); - this.queue.push(["joined", id, before]); - this.playSoundJoined = true; + } else { + scope.$apply(); } }; @@ -438,9 +453,8 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! Buddylist.prototype.onLeft = function(data) { - //console.log("Left", session); + //console.log("Left", data); var id = data.Id; - this.tree.remove(id); var scope = buddyData.get(id); if (!scope) { //console.warn("Trying to remove buddy with no registered scope", session); @@ -449,12 +463,21 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! if (buddyCount > 0) { buddyCount--; } - if (scope.element) { - this.lefts[id] = scope.element; - this.playSoundLeft = true; + if (scope.session.remove(id)) { + // No session left. Cleanup. + this.tree.remove(id); + if (scope.element) { + this.lefts[id] = scope.element; + this.playSoundLeft = true; + } + buddyData.del(id); + if (scope.session.Userid) { + buddyData.del(scope.session.Userid); + } + delete this.actionElements[id]; + } else { + scope.$apply(); } - buddyData.del(id); - delete this.actionElements[id]; }; diff --git a/static/js/services/buddysession.js b/static/js/services/buddysession.js new file mode 100644 index 00000000..cd240f57 --- /dev/null +++ b/static/js/services/buddysession.js @@ -0,0 +1,140 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +define(["underscore"], function(_) { + + // buddySession + return [function() { + + var BuddySession = function(data) { + this.sessions = {}; + this.count = 0; + if (data.Id) { + this.add(data.Id, data); + this.use(data); + this.Userid = data.Userid || null; + } else { + this.set({}); + } + }; + + BuddySession.prototype.add = function(id, data) { + this.sessions[id] = data; + this.count++; + return data; + }; + + BuddySession.prototype.rm = function(id) { + delete this.sessions[id]; + this.count--; + }; + + BuddySession.prototype.get = function(id) { + if (!id) { + id = this.Id; + } + return this.sessions[id]; + }; + + BuddySession.prototype.use = function(data) { + this.Id = data.Id || null; + this.Ua = data.Ua || ""; + }; + + BuddySession.prototype.remove = function(id, onEmptyCallback) { + + this.rm(id); + if (id === this.Id) { + var sessions = this.sessions; + var sessionData; + for (var sd in sessions) { + if (sessions.hasOwnProperty(sd)) { + sessionData = sessions[sd]; + break; + } + } + if (sessionData) { + //console.log("remove session", sessionData); + this.use(sessionData); + } else { + console.log("Last session removed", sessions); + return true; + } + } + return false; + + }; + + BuddySession.prototype.update = function(id, data, onUseridCallback) { + + var sessionData = this.sessions[id]; + if (!sessionData) { + sessionData = this.add(id, data); + } + + if (data.Userid && !this.Userid) { + this.Userid = data.Userid; + console.log("Session now has a user id", this.Id, data.Userid); + if (onUseridCallback) { + onUseridCallback(this); + } + } + if (data.Rev) { + sessionData.Rev = data.Rev; + } + if (data.Status) { + sessionData.Status = data.Status; + } + + if (id === this.Id) { + return sessionData; + } else { + return null; + } + + }; + + BuddySession.prototype.merge = function(otherSession) { + if (!this.Userid) { + console.error("Refusing to merge into session as we have no userid", this, otherSession); + return; + } + if (otherSession.Userid !== this.Userid) { + console.error("Refusing to merge other session with different userid", otherSession, this); + return; + } + _.each(otherSession.sessions, _.bind(function(s, id) { + if (this.sessions.hasOwnProperty(id)) { + return; + } + this.add(id, s); + }, this)); + console.log("Merged sessions", this, otherSession); + }; + + return { + create: function(data) { + return new BuddySession(data); + } + }; + + }]; + +}); diff --git a/static/js/services/services.js b/static/js/services/services.js index a15f3dba..12f340b3 100644 --- a/static/js/services/services.js +++ b/static/js/services/services.js @@ -45,7 +45,8 @@ define([ 'services/videowaiter', 'services/videolayout', 'services/contactdata', - 'services/contacts'], function(_, + 'services/contacts', + 'services/buddysession'], function(_, desktopNotify, playSound, safeApply, @@ -70,7 +71,8 @@ fastScroll, videoWaiter, videoLayout, contactData, -contacts) { +contacts, +buddySession) { var services = { desktopNotify: desktopNotify, @@ -97,7 +99,8 @@ contacts) { videoWaiter: videoWaiter, videoLayout: videoLayout, contactData: contactData, - contacts: contacts + contacts: contacts, + buddySession: buddySession }; var initialize = function(angModule) { diff --git a/static/partials/buddy.html b/static/partials/buddy.html index 26b59bdd..9ba70952 100644 --- a/static/partials/buddy.html +++ b/static/partials/buddy.html @@ -1,5 +1,5 @@
{{session.Id|displayName}}
-
{{session.Ua}}
+
({{session.count}}) {{session.Ua}}
From 342a833eed8bae4d98bb7365496e77a0645a1652 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Tue, 3 Jun 2014 14:22:57 +0200 Subject: [PATCH 11/55] Make sure to set scope for userid if none is there yet. --- static/js/services/buddylist.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index d4d9dd56..1d4b7f75 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -219,7 +219,18 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! Buddylist.prototype.onBuddySessionUserid = function(scope, sourceSession) { - var targetScope = buddyData.get(sourceSession.Userid); + var userid = sourceSession.Userid; + /* + if (userid === scope.userid) { + // The source session has our own userid, ignore it. + + }*/ + var targetScope = buddyData.get(userid); + if (!targetScope) { + // No scope for this userid yet - set us. + buddyData.set(userid, scope); + return; + } if (targetScope === scope) { // No action. return; @@ -410,7 +421,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! var id = data.Id; var scope = buddyData.get(id, this.$scope, _.bind(function(scope) { this.onBuddyScopeCreated(scope); - scope.session = buddySession.create(data); + scope.session = buddySession.create(data); }, this), data.Userid); // Update session. buddyCount++; From f18dc2f8e84588fc65c0d91673c6e7c511303e35 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Tue, 3 Jun 2014 14:27:15 +0200 Subject: [PATCH 12/55] Only show user count when there is more than 1. --- static/partials/buddy.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/partials/buddy.html b/static/partials/buddy.html index 9ba70952..0fd87c54 100644 --- a/static/partials/buddy.html +++ b/static/partials/buddy.html @@ -1,5 +1,5 @@
{{session.Id|displayName}}
-
({{session.count}}) {{session.Ua}}
+
({{session.count}}) {{session.Ua}}
From 99ddea7fe4eedd1b63fb9938d95b490f2194c8e0 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Tue, 3 Jun 2014 16:42:31 +0200 Subject: [PATCH 13/55] Implemented display of aggregated sessions. --- src/styles/components/_buddylist.scss | 46 +++++++++++++++++++++++++-- static/partials/buddyactions.html | 13 ++++++-- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/styles/components/_buddylist.scss b/src/styles/components/_buddylist.scss index 73a3352f..94b9a04b 100644 --- a/src/styles/components/_buddylist.scss +++ b/src/styles/components/_buddylist.scss @@ -113,7 +113,7 @@ cursor: pointer; display: block; font-size: 13px; - height: 66px; + min-height: 66px; overflow: hidden; position: relative; text-align: left; @@ -164,6 +164,16 @@ top: 0; } } + .buddyPictureSmall { + margin:0px; + margin-left:-4px; + height:30px; + width:30px; + margin-right:4px; + .#{$fa-css-prefix} { + line-height: 30px; + } + } .buddy1 { color: $componentfg1; font-weight: bold; @@ -187,11 +197,19 @@ white-space: nowrap; display: none; } + .buddy3 { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: inline-block; + width:160px; + text-align:left; + } } .buddy .buddyactions { background: $buddylist-action-background; - bottom: 0px; + height: 66px; line-height: 66px; position: absolute; right: -125px; @@ -206,3 +224,27 @@ font-size: 2em; } } + +.buddy .buddysessions { + margin-top: 56px; + display:none; + ul { + padding-top: 10px; + margin-left: 20px; + padding-left: 0px; + border-left: 1px dotted $bordercolor; + } + ul li { + margin-bottom: 1px; + margin-top: 1px; + margin-left: -4px; + list-style-type: none; + } + .currentsession .buddy3 { + font-weight: bold; + } +} + +.buddy.hovered .buddysessions { + display: block; +} \ No newline at end of file diff --git a/static/partials/buddyactions.html b/static/partials/buddyactions.html index 6fdb45e8..78e31a09 100644 --- a/static/partials/buddyactions.html +++ b/static/partials/buddyactions.html @@ -1,4 +1,11 @@ -
- - + From f70e10e24257142205282e80bfcaaaa35c7b8307 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Thu, 5 Jun 2014 20:06:17 +0200 Subject: [PATCH 14/55] Implemented buddy list output display mapping and the methods to fill it. --- static/js/filters/buddyimagesrc.js | 14 ++--- static/js/filters/displayname.js | 4 +- static/js/services/buddylist.js | 92 ++++++++++++++++++++---------- static/js/services/buddysession.js | 3 +- static/partials/buddy.html | 6 +- 5 files changed, 77 insertions(+), 42 deletions(-) diff --git a/static/js/filters/buddyimagesrc.js b/static/js/filters/buddyimagesrc.js index cded8615..852a71c0 100644 --- a/static/js/filters/buddyimagesrc.js +++ b/static/js/filters/buddyimagesrc.js @@ -79,18 +79,18 @@ define(["underscore"], function(_) { var scope = buddyData.lookup(id); if (scope) { - var status = scope.status; - if (status) { - if (status.buddyPictureLocalUrl) { - return status.buddyPictureLocalUrl; - } else if (status.buddyPicture) { + var display = scope.display; + if (display) { + if (display.buddyPictureLocalUrl) { + return display.buddyPictureLocalUrl; + } else if (display.buddyPicture) { var url = urls[id]; if (url) { revokeURL(id, url); } // New data -> new url. - var blob = dataURLToBlob(status.buddyPicture); - url = status.buddyPictureLocalUrl = urls[id] = blobToObjectURL(blob); + var blob = dataURLToBlob(display.buddyPicture); + url = display.buddyPictureLocalUrl = urls[id] = blobToObjectURL(blob); return url; } } diff --git a/static/js/filters/displayname.js b/static/js/filters/displayname.js index ee104a19..f3695968 100644 --- a/static/js/filters/displayname.js +++ b/static/js/filters/displayname.js @@ -35,8 +35,8 @@ define([], function() { } var scope = buddyData.lookup(id); if (scope) { - if (scope.displayName) { - return scope.displayName; + if (scope.display.displayName) { + return scope.display.displayName; } return user_text + " " + scope.buddyIndex; } else { diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 1d4b7f75..05e999f6 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -31,7 +31,8 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! BuddyTree.prototype.create = function(id, scope) { - var sort = scope.displayName ? scope.displayName : "session " + scope.buddyIndexSortable + " " + id; + var display = scope.display || {}; + var sort = display.displayName ? display.displayName : "session " + scope.buddyIndexSortable + " " + id; var data = { id: id, sort: sort + "z" + id @@ -196,9 +197,12 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; - Buddylist.prototype.onBuddyScopeCreated = function(scope) { + Buddylist.prototype.onBuddyScopeCreated = function(scope, data) { scope.element = null; + scope.display = {}; + scope.session = buddySession.create(data); + scope.doDefault = function() { var id = scope.session.Id; if (scope.status.isMixer) { @@ -383,13 +387,58 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; + Buddylist.prototype.setDisplay = function(id, scope, data, queueName) { + + var status = data.Status; + var display = scope.display; + // Set display.name. + display.displayName = status.displayName; + // Set display.picture. + display.buddyPicture = status.buddyPicture; + this.updateBuddyPicture(display); + // Set display subline. + display.subLine = data.Ua; + // Add to render queue when no element exists. + if (!scope.element) { + var before = this.tree.add(id, scope); + this.queue.push([queueName, id, before]); + this.playSoundJoined = true; + } + + }; + + Buddylist.prototype.updateDisplay = function(id, scope, data, queueName) { + + var status = data.Status; + var display = scope.display; + // Update display name. + var displayName = display.displayName; + if (status.displayName) { + display.displayName = status.displayName; + } else { + display.displayName = null; + } + // Add to status queue if sorting has changed. + if (displayName !== status.displayName) { + var before = this.tree.update(id, scope); + this.queue.push([queueName, id, before]); + } + // Update display subline. + if (data.Ua) { + display.subLine = data.Ua; + } + // Update display picture. + display.buddyPicture = status.buddyPicture || null; + this.updateBuddyPicture(display); + + }; + Buddylist.prototype.onStatus = function(data) { //console.log("onStatus", data); var id = data.Id; var scope = buddyData.get(id, this.$scope, _.bind(function(scope) { - this.onBuddyScopeCreated(scope); - scope.session = buddySession.create(data); + this.onBuddyScopeCreated(scope, data); }, this), data.Userid); // Update session. var sessionData = scope.session.update(id, data, _.bind(function(session) { @@ -398,18 +447,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }, this)); if (sessionData) { // onStatus for main session. - scope.status = sessionData.Status; - var displayName = scope.displayName; - if (sessionData.Status.displayName) { - scope.displayName = sessionData.Status.displayName; - } else { - scope.displayName = null; - } - if (displayName !== scope.displayName) { - var before = this.tree.update(id, scope); - this.queue.push(["status", id, before]); - } - this.updateBuddyPicture(sessionData.Status); + this.updateDisplay(id, scope, sessionData, "status"); } scope.$apply(); @@ -420,21 +458,13 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! //console.log("Joined", data); var id = data.Id; var scope = buddyData.get(id, this.$scope, _.bind(function(scope) { - this.onBuddyScopeCreated(scope); - scope.session = buddySession.create(data); + this.onBuddyScopeCreated(scope, data); }, this), data.Userid); // Update session. buddyCount++; var sessionData = scope.session.update(id, data); if (sessionData && sessionData.Status) { - scope.status = sessionData.Status; - scope.displayName = sessionData.Status.displayName; - this.updateBuddyPicture(sessionData.Status); - if (!scope.element) { - var before = this.tree.add(id, scope); - this.queue.push(["joined", id, before]); - this.playSoundJoined = true; - } + this.setDisplay(id, scope, sessionData, "joined"); } else { scope.$apply(); } @@ -474,7 +504,8 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! if (buddyCount > 0) { buddyCount--; } - if (scope.session.remove(id)) { + var session = scope.session; + if (session.remove(id)) { // No session left. Cleanup. this.tree.remove(id); if (scope.element) { @@ -482,11 +513,14 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! this.playSoundLeft = true; } buddyData.del(id); - if (scope.session.Userid) { - buddyData.del(scope.session.Userid); + if (session.Userid) { + buddyData.del(session.Userid); } delete this.actionElements[id]; } else { + // Update display stuff if session left. + var sessionData = session.get(); + this.setDisplay(id, scope, sessionData, "status"); scope.$apply(); } diff --git a/static/js/services/buddysession.js b/static/js/services/buddysession.js index cd240f57..97a43d3d 100644 --- a/static/js/services/buddysession.js +++ b/static/js/services/buddysession.js @@ -97,8 +97,9 @@ define(["underscore"], function(_) { } } if (data.Rev) { - sessionData.Rev = data.Rev; + sessionData.Rev = data.Rev; } + if (data.Status) { sessionData.Status = data.Status; } diff --git a/static/partials/buddy.html b/static/partials/buddy.html index 0fd87c54..2002cb7a 100644 --- a/static/partials/buddy.html +++ b/static/partials/buddy.html @@ -1,5 +1,5 @@ -
-
+
+
{{session.Id|displayName}}
-
({{session.count}}) {{session.Ua}}
+
({{session.count}}) {{display.subLine}}
From 769598f1d48568524ec79c1a7537fb3bcfb4c45f Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Thu, 5 Jun 2014 20:39:25 +0200 Subject: [PATCH 15/55] Implemented proper leave of aggregated buddies. --- static/js/services/buddylist.js | 44 ++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 05e999f6..8fe5745b 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -471,26 +471,6 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; - Buddylist.prototype.onContactAdded = function(contact) { - - console.log("onContactAdded", contact); - var userid = contact.Userid; - // TODO(longsleep): Traversing the whole tree is stupid. Implement key/value map for this. - this.tree.traverse(function(record) { - var scope = buddyData.lookup(record.id, true); - var sessionUserid = scope.session.Userid; - if (sessionUserid === userid) { - scope.contact = contact; - } - }); - - }; - - Buddylist.prototype.onContactRemoved = function(data) { - - console.log("onContactRemoved", data); - - }; Buddylist.prototype.onLeft = function(data) { @@ -504,10 +484,12 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! if (buddyCount > 0) { buddyCount--; } + // Remove current id from tree. + this.tree.remove(id); + // Remove session. var session = scope.session; if (session.remove(id)) { // No session left. Cleanup. - this.tree.remove(id); if (scope.element) { this.lefts[id] = scope.element; this.playSoundLeft = true; @@ -520,7 +502,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! } else { // Update display stuff if session left. var sessionData = session.get(); - this.setDisplay(id, scope, sessionData, "status"); + this.updateDisplay(sessionData.Id, scope, sessionData, "status"); scope.$apply(); } @@ -538,6 +520,24 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; + Buddylist.prototype.onContactAdded = function(contact) { + + console.log("onContactAdded", contact); + var userid = contact.Userid; + + var scope = buddyData.get(userid); + if (scope) { + scope.contact = contact; + } + + }; + + Buddylist.prototype.onContactRemoved = function(data) { + + console.log("onContactRemoved", data); + + }; + Buddylist.prototype.hover = function(buddyElement, hover, id) { //console.log("hover handler", event, hover, id); From a9f7b54cb4649867638ee57eae1cc29fb8ea0bb5 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Thu, 5 Jun 2014 20:54:50 +0200 Subject: [PATCH 16/55] Added animation for session list. --- src/styles/components/_buddylist.scss | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/styles/components/_buddylist.scss b/src/styles/components/_buddylist.scss index 94b9a04b..4a11f974 100644 --- a/src/styles/components/_buddylist.scss +++ b/src/styles/components/_buddylist.scss @@ -227,7 +227,10 @@ .buddy .buddysessions { margin-top: 56px; - display:none; + max-height:0px; + transition-property: max-height; + transition-duration: 1s; + transition-delay: .1s; ul { padding-top: 10px; margin-left: 20px; @@ -246,5 +249,5 @@ } .buddy.hovered .buddysessions { - display: block; + max-height:999px; } \ No newline at end of file From d50fb327b3afdc82f5f33f95952f6318860b545c Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Fri, 6 Jun 2014 11:33:31 +0200 Subject: [PATCH 17/55] Implemented contact add and remove with aggregated buddies. --- static/js/directives/buddylist.js | 12 +++++- static/js/services/buddylist.js | 61 +++++++++++++++++++++++------- static/js/services/buddysession.js | 23 ++++++++--- static/js/services/contactdata.js | 5 ++- static/js/services/contacts.js | 11 ++++++ static/partials/buddy.html | 2 +- 6 files changed, 91 insertions(+), 23 deletions(-) diff --git a/static/js/directives/buddylist.js b/static/js/directives/buddylist.js index e7540529..b7b23542 100644 --- a/static/js/directives/buddylist.js +++ b/static/js/directives/buddylist.js @@ -46,7 +46,7 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { }; - $scope.doContact = function(id) { + $scope.doContactRequest = function(id) { //console.log("doContact", id); $scope.$emit("requestcontact", id, { @@ -55,6 +55,12 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { }; + $scope.doContactRemove = function(userid) { + + contacts.remove(userid); + + }; + $scope.doAudioConference = function(id) { $scope.updateAutoAccept(id); @@ -85,6 +91,7 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { var onLeft = _.bind(buddylist.onLeft, buddylist); var onStatus = _.bind(buddylist.onStatus, buddylist); var onContactAdded = _.bind(buddylist.onContactAdded, buddylist); + var onContactRemoved = _.bind(buddylist.onContactRemoved, buddylist); mediaStream.api.e.on("received.userleftorjoined", function(event, dataType, data) { if (dataType === "Left") { onLeft(data); @@ -117,6 +124,9 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { contacts.e.on("contactadded", function(event, data) { onContactAdded(data); }); + contacts.e.on("contactremoved", function(event, data) { + onContactRemoved(data); + }); }]; diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 8fe5745b..2d0cebdb 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -200,19 +200,25 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! Buddylist.prototype.onBuddyScopeCreated = function(scope, data) { scope.element = null; + scope.contact = null; scope.display = {}; scope.session = buddySession.create(data); scope.doDefault = function() { var id = scope.session.Id; - if (scope.status.isMixer) { - return scope.doAudioConference(id); - } + //if (scope.status.isMixer) { + // return scope.doAudioConference(id); + //} return scope.doCall(id); }; scope.doDefaultContact = function() { - var id = scope.session.Id; - return scope.doContact(id); + var contact = scope.contact; + if (contact) { + return scope.doContactRemove(contact.Userid); + } else { + var id = scope.session.Id; + return scope.doContactRequest(id); + } }; scope.$on("$destroy", function() { scope.element = null; @@ -472,7 +478,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; - Buddylist.prototype.onLeft = function(data) { + Buddylist.prototype.onLeft = function(data, force) { //console.log("Left", data); var id = data.Id; @@ -488,7 +494,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! this.tree.remove(id); // Remove session. var session = scope.session; - if (session.remove(id)) { + if ((session.remove(id) && scope.contact === null) || force) { // No session left. Cleanup. if (scope.element) { this.lefts[id] = scope.element; @@ -500,9 +506,12 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! } delete this.actionElements[id]; } else { - // Update display stuff if session left. + // Update display stuff if session left. This can + // return no session in case when we got this as contact. var sessionData = session.get(); - this.updateDisplay(sessionData.Id, scope, sessionData, "status"); + if (sessionData) { + this.updateDisplay(sessionData.Id, scope, sessionData, "status"); + } scope.$apply(); } @@ -528,13 +537,37 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! var scope = buddyData.get(userid); if (scope) { scope.contact = contact; + var sessionData = scope.session.get(); + if (sessionData) { + if (contact.Status === null && sessionData.Status) { + // Update contact status with session.Status + contact.Status = _.extend({}, sessionData.Status); + console.log("Injected status into contact", contact); + } + this.updateDisplay(sessionData.Id, scope, contact, "status"); + } + } else { + // TODO(longsleep): Implement rendering of contacts without scope. + console.log("No scope for contact", userid); } }; - Buddylist.prototype.onContactRemoved = function(data) { + Buddylist.prototype.onContactRemoved = function(contact) { - console.log("onContactRemoved", data); + console.log("onContactRemoved", contact); + var userid = contact.Userid; + + var scope = buddyData.get(userid); + if (scope) { + scope.contact = null; + // Remove with left when no session for this userid. + var sessionData = scope.session.get(); + if (!sessionData) { + // Force left. + this.onLeft({Id: userid}, true); + } + } }; @@ -563,9 +596,9 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! } else { var scope = buddyData.get(id); var template = buddyActions; - if (scope.status.autoCalls && _.indexOf(scope.status.autoCalls, "conference") !== -1) { - template = buddyActionsForAudioMixer; - } + //if (scope.status.autoCalls && _.indexOf(scope.status.autoCalls, "conference") !== -1) { + // template = buddyActionsForAudioMixer; + //} //console.log("scope", scope, id); template(scope, _.bind(function(clonedElement, $scope) { actionElements[id] = clonedElement; diff --git a/static/js/services/buddysession.js b/static/js/services/buddysession.js index 97a43d3d..4e085902 100644 --- a/static/js/services/buddysession.js +++ b/static/js/services/buddysession.js @@ -28,7 +28,6 @@ define(["underscore"], function(_) { this.count = 0; if (data.Id) { this.add(data.Id, data); - this.use(data); this.Userid = data.Userid || null; } else { this.set({}); @@ -37,6 +36,9 @@ define(["underscore"], function(_) { BuddySession.prototype.add = function(id, data) { this.sessions[id] = data; + if (this.count === 0) { + this.use(id, data); + } this.count++; return data; }; @@ -53,9 +55,13 @@ define(["underscore"], function(_) { return this.sessions[id]; }; - BuddySession.prototype.use = function(data) { - this.Id = data.Id || null; - this.Ua = data.Ua || ""; + BuddySession.prototype.use = function(id, data) { + if (id) { + this.Id = id; + } else { + this.Id = null; + } + console.log("Use session as default", id, data); }; BuddySession.prototype.remove = function(id, onEmptyCallback) { @@ -71,10 +77,15 @@ define(["underscore"], function(_) { } } if (sessionData) { - //console.log("remove session", sessionData); - this.use(sessionData); + this.use(sessionData.Id, sessionData); } else { console.log("Last session removed", sessions); + if (this.Userid) { + console.log("Using userid as session id"); + this.use(this.Userid); + } else { + this.use(null); + } return true; } } diff --git a/static/js/services/contactdata.js b/static/js/services/contactdata.js index 2becce40..0ab3f0dd 100644 --- a/static/js/services/contactdata.js +++ b/static/js/services/contactdata.js @@ -44,7 +44,7 @@ define(['underscore', 'jquery'], function(underscore, $) { Id: "contact-"+id, Userid: userid, Token: token, - Status: $.extend({}, status) + Status: null } // TODO(longsleep): Trigger this to somewhere. return contact; @@ -56,6 +56,9 @@ define(['underscore', 'jquery'], function(underscore, $) { } return null; }, + remove: function(userid) { + delete users[userid]; + }, getById: function(id) { if (id.indexOf("contact-") === 0) { id = id.substr(8) diff --git a/static/js/services/contacts.js b/static/js/services/contacts.js index 94714d72..cc89d90c 100644 --- a/static/js/services/contacts.js +++ b/static/js/services/contacts.js @@ -34,6 +34,17 @@ define(['underscore', 'jquery'], function(underscore, $) { }; + Contacts.prototype.remove = function(userid) { + + var contact = contactData.get(userid); + //console.log("contacts remove", userid, contact); + if (contact) { + contactData.remove(userid); + this.e.triggerHandler("contactremoved", contact); + } + + }; + return new Contacts(); }]; diff --git a/static/partials/buddy.html b/static/partials/buddy.html index 2002cb7a..d0075eea 100644 --- a/static/partials/buddy.html +++ b/static/partials/buddy.html @@ -1,5 +1,5 @@
{{session.Id|displayName}}
-
({{session.count}}) {{display.subLine}}
+
({{session.count}}) {{display.subLine}}
From 9928d714120ae7d48e0715ecaf16556349e12224 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Fri, 6 Jun 2014 16:25:28 +0200 Subject: [PATCH 18/55] Added correct support for room changes with contacts. --- static/js/services/buddydata.js | 4 +- static/js/services/buddylist.js | 88 ++++++++++++++++++++++++------ static/js/services/buddysession.js | 52 ++++++++++++++---- 3 files changed, 115 insertions(+), 29 deletions(-) diff --git a/static/js/services/buddydata.js b/static/js/services/buddydata.js index 212b898d..26f68052 100644 --- a/static/js/services/buddydata.js +++ b/static/js/services/buddydata.js @@ -63,6 +63,7 @@ define(['underscore'], function(underscore) { }, get: function(id, createInParent, afterCreateCallback, userid) { if (scopes.hasOwnProperty(id)) { + //console.log("found id scope", id); return scopes[id]; } else if (!createInParent && pushed.hasOwnProperty(id)) { return pushed[id].scope; @@ -73,9 +74,11 @@ define(['underscore'], function(underscore) { if (createInParent) { scopes[id] = scope; } + //console.log("found userid scope", userid); return scope; } if (createInParent) { + //console.log("creating scope", id, userid); // If we have a parent we can create a new scope. scope = scopes[id] = createInParent.$new(); if (userid) { @@ -117,7 +120,6 @@ define(['underscore'], function(underscore) { del: function(id, hard) { var scope = scopes[id]; if (scope) { - scope.$destroy(); if (!hard) { brain[id] = scope; } diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 2d0cebdb..252e54dd 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -109,6 +109,12 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; + BuddyTree.prototype.keys = function() { + + return _.keys(this.data); + + }; + BuddyTree.prototype.clear = function() { this.tree.clear(); @@ -202,7 +208,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! scope.element = null; scope.contact = null; scope.display = {}; - scope.session = buddySession.create(data); + var session = scope.session = buddySession.create(data); scope.doDefault = function() { var id = scope.session.Id; @@ -221,14 +227,19 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! } }; scope.$on("$destroy", function() { + //console.log("destroyed"); scope.element = null; scope.killed = true; }); + //console.log("on buddy scope", session.Userid, session); + }; Buddylist.prototype.onBuddySessionUserid = function(scope, sourceSession) { + //console.log("session with userid", sourceSession); + var userid = sourceSession.Userid; /* if (userid === scope.userid) { @@ -239,24 +250,29 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! if (!targetScope) { // No scope for this userid yet - set us. buddyData.set(userid, scope); + //console.log("set scope with userid", sourceSession); return; } - if (targetScope === scope) { + var session = targetScope.session; + if (sourceSession === session) { // No action. + //console.log("source session same as target"); return; } // Merge sessions. - targetScope.session.merge(sourceSession); + session.merge(sourceSession); // Cleanup old from tree and DOM. var id = sourceSession.Id; this.tree.remove(id); - if (scope.element) { - this.lefts[id] = scope.element; - scope.element = null; + if (targetScope !== scope) { + if (scope.element) { + this.lefts[id] = scope.element; + //console.log("destroying", id, scope.element); + scope.$destroy(); + } + buddyData.set(id, targetScope); + delete this.actionElements[id]; } - scope.$destroy(); - buddyData.set(id, targetScope); - delete this.actionElements[id]; }; @@ -456,10 +472,11 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! this.updateDisplay(id, scope, sessionData, "status"); } scope.$apply(); + return scope; }; - Buddylist.prototype.onJoined = function(data) { + Buddylist.prototype.onJoined = function(data, noApply) { //console.log("Joined", data); var id = data.Id; @@ -468,17 +485,21 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }, this), data.Userid); // Update session. buddyCount++; - var sessionData = scope.session.update(id, data); + var sessionData = scope.session.update(id, data, _.bind(function(session) { + //console.log("Session is now authenticated", session); + this.onBuddySessionUserid(scope, session); + }, this)); if (sessionData && sessionData.Status) { this.setDisplay(id, scope, sessionData, "joined"); - } else { + } else if (!noApply) { scope.$apply(); } + return scope; }; - Buddylist.prototype.onLeft = function(data, force) { + Buddylist.prototype.onLeft = function(data, force, noApply) { //console.log("Left", data); var id = data.Id; @@ -492,6 +513,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! } // Remove current id from tree. this.tree.remove(id); + buddyData.del(id); // Remove session. var session = scope.session; if ((session.remove(id) && scope.contact === null) || force) { @@ -500,32 +522,58 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! this.lefts[id] = scope.element; this.playSoundLeft = true; } - buddyData.del(id); if (session.Userid) { - buddyData.del(session.Userid); + buddyData.del(session.Userid, true); } delete this.actionElements[id]; + scope.$destroy(); } else { - // Update display stuff if session left. This can + // Update display stuff if a session is left. This can // return no session in case when we got this as contact. var sessionData = session.get(); if (sessionData) { this.updateDisplay(sessionData.Id, scope, sessionData, "status"); + } else if (scope.contact) { + // Use it with userid as id in tree. + this.tree.add(session.Userid, scope); + } + if (!noApply) { + scope.$apply(); } - scope.$apply(); } + return scope; }; Buddylist.prototype.onClosed = function() { - //console.log("Closed"); + console.log("Closed"); + + this.queue = []; + + var data = {}; + var sessions = buddySession.sessions(); + for (var id in sessions) { + if (sessions.hasOwnProperty(id)) { + console.log("close id", id); + data.Id = id; + this.onLeft(data, false, true); + } + } + + console.log("buddyCount after close", buddyCount, this.tree.keys()); + + /* this.$element.empty(); buddyCount = 0; buddyData.clear(); this.tree.clear(); this.actionElements = {}; this.queue = []; + */ + + + }; @@ -595,6 +643,10 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! buddy.addClass("hovered"); } else { var scope = buddyData.get(id); + if (!scope) { + console.warn("No scope for buddy", id); + return; + } var template = buddyActions; //if (scope.status.autoCalls && _.indexOf(scope.status.autoCalls, "conference") !== -1) { // template = buddyActionsForAudioMixer; diff --git a/static/js/services/buddysession.js b/static/js/services/buddysession.js index 4e085902..cf2a2013 100644 --- a/static/js/services/buddysession.js +++ b/static/js/services/buddysession.js @@ -23,12 +23,27 @@ define(["underscore"], function(_) { // buddySession return [function() { + var sessions = {}; + var serials = 0; + var BuddySession = function(data) { + this.serial = serials++; this.sessions = {}; this.count = 0; + //console.log("creating session", this.serial, this); + var id = data.Id; if (data.Id) { - this.add(data.Id, data); - this.Userid = data.Userid || null; + var userid = data.Userid || null; + if (id === userid) { + // Add as default with userid. + this.use(userid, data); + } else { + // Add as session. + var sessionData = this.add(id, data); + if (userid) { + this.auth(userid, sessionData); + } + } } else { this.set({}); } @@ -40,12 +55,14 @@ define(["underscore"], function(_) { this.use(id, data); } this.count++; + sessions[id] = this; return data; }; BuddySession.prototype.rm = function(id) { delete this.sessions[id]; this.count--; + delete sessions[id]; }; BuddySession.prototype.get = function(id) { @@ -61,7 +78,7 @@ define(["underscore"], function(_) { } else { this.Id = null; } - console.log("Use session as default", id, data); + console.log("Use session as default", id, data, this); }; BuddySession.prototype.remove = function(id, onEmptyCallback) { @@ -95,18 +112,16 @@ define(["underscore"], function(_) { BuddySession.prototype.update = function(id, data, onUseridCallback) { - var sessionData = this.sessions[id]; + var sessionData = this.get(id); if (!sessionData) { sessionData = this.add(id, data); } - if (data.Userid && !this.Userid) { - this.Userid = data.Userid; - console.log("Session now has a user id", this.Id, data.Userid); - if (onUseridCallback) { - onUseridCallback(this); - } + var userid = data.Userid; + if (userid) { + this.auth(userid, sessionData, onUseridCallback); } + if (data.Rev) { sessionData.Rev = data.Rev; } @@ -123,6 +138,20 @@ define(["underscore"], function(_) { }; + BuddySession.prototype.auth = function(userid, sessionData, onUseridCallback) { + + if (!this.Userid) { + this.Userid = userid; + console.log("Session now has a user id", this.Id, userid); + } + // Trigger callback if defined and not triggered before. + if (onUseridCallback && !sessionData.auth) { + onUseridCallback(this); + sessionData.auth = true; + } + + }; + BuddySession.prototype.merge = function(otherSession) { if (!this.Userid) { console.error("Refusing to merge into session as we have no userid", this, otherSession); @@ -144,6 +173,9 @@ define(["underscore"], function(_) { return { create: function(data) { return new BuddySession(data); + }, + sessions: function() { + return sessions; } }; From 20725908c9933dbf86ced10ac4d955987f7c8998 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Fri, 6 Jun 2014 16:32:54 +0200 Subject: [PATCH 19/55] Removed debug. --- static/js/services/buddylist.js | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 252e54dd..833e4562 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -547,34 +547,22 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! Buddylist.prototype.onClosed = function() { - console.log("Closed"); + //console.log("Closed"); + // Remove pending stuff from queue. this.queue = []; + // Trigger left events for all sessions. var data = {}; var sessions = buddySession.sessions(); for (var id in sessions) { if (sessions.hasOwnProperty(id)) { - console.log("close id", id); + //console.log("close id", id); data.Id = id; this.onLeft(data, false, true); } } - console.log("buddyCount after close", buddyCount, this.tree.keys()); - - /* - this.$element.empty(); - buddyCount = 0; - buddyData.clear(); - this.tree.clear(); - this.actionElements = {}; - this.queue = []; - */ - - - - }; Buddylist.prototype.onContactAdded = function(contact) { From 4e31529b3b23bd21bd79084223ec91c1c6088187 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Fri, 6 Jun 2014 19:10:28 +0200 Subject: [PATCH 20/55] Implemented local contact storage. --- src/styles/components/_buddylist.scss | 4 +- .../js/controllers/mediastreamcontroller.js | 1 + static/js/services/appdata.js | 11 +- static/js/services/buddylist.js | 25 +++- static/js/services/buddysession.js | 41 +++--- static/js/services/contactdata.js | 16 ++- static/js/services/contacts.js | 120 +++++++++++++++++- static/partials/buddy.html | 2 +- 8 files changed, 187 insertions(+), 33 deletions(-) diff --git a/src/styles/components/_buddylist.scss b/src/styles/components/_buddylist.scss index 4a11f974..1d000e03 100644 --- a/src/styles/components/_buddylist.scss +++ b/src/styles/components/_buddylist.scss @@ -126,10 +126,10 @@ } .buddy { - &.withSubline .buddy1 { + &.withSubline .buddy1, &.contact .buddy1 { top: 15px; } - &.withSubline .buddy2 { + &.withSubline .buddy2, &.contact .buddy2 { display: block; } &.hovered .buddyactions { diff --git a/static/js/controllers/mediastreamcontroller.js b/static/js/controllers/mediastreamcontroller.js index 17322dcb..d9a936a6 100644 --- a/static/js/controllers/mediastreamcontroller.js +++ b/static/js/controllers/mediastreamcontroller.js @@ -624,6 +624,7 @@ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function if (userid) { console.info("Session is now authenticated:", userid); } + appData.e.triggerHandler("authenticationChanged", [userid]); }); // Apply all layout stuff as classes to our element. diff --git a/static/js/services/appdata.js b/static/js/services/appdata.js index 2344904c..dea2595f 100644 --- a/static/js/services/appdata.js +++ b/static/js/services/appdata.js @@ -18,13 +18,17 @@ * along with this program. If not, see . * */ -define([], function() { +define(["jquery"], function($) { + + // appData.e events + // - authenticationChanged(userid) // appData return [function() { var data = { - data: null + data: null, + e: $({}) } var appData = { get: function() { @@ -33,7 +37,8 @@ define([], function() { set: function(d) { data.data = d; return d; - } + }, + e: data.e } return appData; diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 833e4562..5920e77d 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -67,6 +67,12 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; + BuddyTree.prototype.check = function(id) { + + return this.data.hasOwnProperty(id); + + }; + /** * Returns undefined when no change required. Position result otherwise. */ @@ -535,7 +541,10 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! this.updateDisplay(sessionData.Id, scope, sessionData, "status"); } else if (scope.contact) { // Use it with userid as id in tree. - this.tree.add(session.Userid, scope); + if (!this.tree.check(session.Userid)) { + this.tree.add(session.Userid, scope); + buddyCount++; + } } if (!noApply) { scope.$apply(); @@ -567,7 +576,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! Buddylist.prototype.onContactAdded = function(contact) { - console.log("onContactAdded", contact); + //console.log("onContactAdded", contact); var userid = contact.Userid; var scope = buddyData.get(userid); @@ -581,17 +590,23 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! console.log("Injected status into contact", contact); } this.updateDisplay(sessionData.Id, scope, contact, "status"); + scope.$apply(); } } else { - // TODO(longsleep): Implement rendering of contacts without scope. - console.log("No scope for contact", userid); + // Create new scope for contact. + scope = this.onJoined({ + Id: contact.Userid, + Userid: contact.Userid, + Status: contact.Status + }); + scope.contact = contact; } }; Buddylist.prototype.onContactRemoved = function(contact) { - console.log("onContactRemoved", contact); + //console.log("onContactRemoved", contact); var userid = contact.Userid; var scope = buddyData.get(userid); diff --git a/static/js/services/buddysession.js b/static/js/services/buddysession.js index cf2a2013..7d153c51 100644 --- a/static/js/services/buddysession.js +++ b/static/js/services/buddysession.js @@ -30,7 +30,7 @@ define(["underscore"], function(_) { this.serial = serials++; this.sessions = {}; this.count = 0; - //console.log("creating session", this.serial, this); + //console.log("creating session", this.serial, data.Id, data.Userid, this); var id = data.Id; if (data.Id) { var userid = data.Userid || null; @@ -78,7 +78,7 @@ define(["underscore"], function(_) { } else { this.Id = null; } - console.log("Use session as default", id, data, this); + //console.log("Use session as default", id, data, this); }; BuddySession.prototype.remove = function(id, onEmptyCallback) { @@ -96,9 +96,9 @@ define(["underscore"], function(_) { if (sessionData) { this.use(sessionData.Id, sessionData); } else { - console.log("Last session removed", sessions); + //console.log("Last session removed", sessions); if (this.Userid) { - console.log("Using userid as session id"); + //console.log("Using userid as session id"); this.use(this.Userid); } else { this.use(null); @@ -112,22 +112,29 @@ define(["underscore"], function(_) { BuddySession.prototype.update = function(id, data, onUseridCallback) { - var sessionData = this.get(id); - if (!sessionData) { - sessionData = this.add(id, data); - } - var userid = data.Userid; + //console.log("session update", id, userid, this, data); + + var sessionData + if (id === userid) { + // Fake updates from userid ids. + sessionData = data; + } else { + sessionData = this.get(id); + if (!sessionData) { + sessionData = this.add(id, data); + } + } if (userid) { this.auth(userid, sessionData, onUseridCallback); } - - if (data.Rev) { - sessionData.Rev = data.Rev; - } - - if (data.Status) { - sessionData.Status = data.Status; + if (data !== sessionData) { + if (data.Rev) { + sessionData.Rev = data.Rev; + } + if (data.Status) { + sessionData.Status = data.Status; + } } if (id === this.Id) { @@ -142,7 +149,7 @@ define(["underscore"], function(_) { if (!this.Userid) { this.Userid = userid; - console.log("Session now has a user id", this.Id, userid); + //console.log("Session now has a user id", this.Id, userid); } // Trigger callback if defined and not triggered before. if (onUseridCallback && !sessionData.auth) { diff --git a/static/js/services/contactdata.js b/static/js/services/contactdata.js index 0ab3f0dd..b06d2f0e 100644 --- a/static/js/services/contactdata.js +++ b/static/js/services/contactdata.js @@ -29,7 +29,7 @@ define(['underscore', 'jquery'], function(underscore, $) { var contactData = { addByRequest: function(request, status) { - console.log("addByRequest", request, status); + //console.log("addByRequest", request, status); var userid = request.Userid; var token = request.Token; var id; @@ -46,7 +46,19 @@ define(['underscore', 'jquery'], function(underscore, $) { Token: token, Status: null } - // TODO(longsleep): Trigger this to somewhere. + return contact; + }, + addByData: function(data) { + //console.log("addByData", data.Userid, data); + var userid = data.Userid; + if (users.hasOwnProperty(userid)) { + id = users[userid] + } else { + id = String(count++); + users[userid] = id; + } + var contact = contacts[id] = data; + contact.Id = id; return contact; }, get: function(userid) { diff --git a/static/js/services/contacts.js b/static/js/services/contacts.js index cc89d90c..5cd2d2e4 100644 --- a/static/js/services/contacts.js +++ b/static/js/services/contacts.js @@ -18,28 +18,142 @@ * along with this program. If not, see . * */ -define(['underscore', 'jquery'], function(underscore, $) { +define(['underscore', 'jquery', 'modernizr'], function(underscore, $, Modernizr) { + + var Database = function(name, version) { + this.ready = false; + this.db = null; + this.name = name; + this.e = $({}); + var request = indexedDB.open("mediastream", version); + var that = this; + request.onupgradeneeded = function(event) { + var db = event.target.result; + var transaction = event.target.transaction; + transaction.onerror = _.bind(that.onerror, that); + if (db.objectStoreNames.contains(name)) { + // TODO(longsleep): Database upgrade should keep the data and migrate it. + db.deleteObjectStore("contacts") + console.warn("Removed contacts database with old format.") + } + db.createObjectStore(name, { + // We use id field as our unique identifier. + keyPath: "id" + }); + console.log("Created contacts database.") + }; + request.onsuccess = _.bind(that.onsuccess, that); + }; + Database.prototype.onerror = function(event) { + console.log("IndexDB database error", event); + }; + Database.prototype.onsuccess = function(event) { + this.db = event.target.result; + this.ready = true; + console.log("Openend database for contacts", this.db); + this.e.triggerHandler("ready"); + }; + Database.prototype.put = function(data, successCallback, errorCallback) { + var transaction = this.db.transaction(this.name, "readwrite"); + var store = transaction.objectStore(this.name); + var request = store.put(data); + if (!errorCallback) { + errorCallback = _.bind(this.onerror, this); + } + transaction.onerror = request.onerror = errorCallback; + if (successCallback) { + request.onsuccess = successCallback; + } + return request; + }; + Database.prototype.delete = function(id, successCallback, errorCallback) { + var transaction = this.db.transaction(this.name, "readwrite"); + var store = transaction.objectStore(this.name); + var request = store.delete(id); + if (!errorCallback) { + errorCallback = _.bind(this.onerror, this); + } + transaction.onerror = request.onerror = errorCallback; + if (successCallback) { + request.onsuccess = successCallback; + } + return request; + }; + Database.prototype.all = function(iteratorCallback, errorCallback) { + var transaction = this.db.transaction(this.name); + var store = transaction.objectStore(this.name); + var keyRange = IDBKeyRange.lowerBound(0); + var cursorRequest = store.openCursor(keyRange); + cursorRequest.onsuccess = function(event) { + var result = event.target.result; + if (!!result === false) { + return; + } + //console.log("read data idb", result, event); + iteratorCallback(result.value); + result.continue(); + }; + if (!errorCallback) { + errorCallback = _.bind(this.onerror, this); + } + transaction.onerror = cursorRequest.onerror = errorCallback; + return cursorRequest; + }; + + var database; + if (Modernizr.indexeddb) { + database = new Database("contacts", 1); + } // contacts - return ["contactData", function(contactData) { + return ["appData", "contactData", function(appData, contactData) { var Contacts = function() { this.e = $({}); + this.userid = null; + appData.e.on("authenticationChanged", _.bind(function(event, userid) { + this.userid = userid; + if (database && userid) { + // TODO(longsleep): This needs to be delayed util self has ha userid. + if (database.ready) { + _.defer(_.bind(this.load, this)); + } else { + database.e.one("ready", _.bind(this.load, this)); + } + } + }, this)); + }; + + Contacts.prototype.load = function() { + console.log("Load contacts from storage", database); + database.all(_.bind(function(data) { + var contact = contactData.addByData(data.contact); + this.e.triggerHandler("contactadded", contact); + }, this)); }; Contacts.prototype.add = function(request, status) { var contact = contactData.addByRequest(request, status); this.e.triggerHandler("contactadded", contact); + if (database) { + database.put({ + id: contact.Userid, + contact: contact + }) + } }; Contacts.prototype.remove = function(userid) { var contact = contactData.get(userid); - //console.log("contacts remove", userid, contact); + console.log("contacts remove", userid, contact); if (contact) { contactData.remove(userid); + if (database) { + database.delete(userid); + } this.e.triggerHandler("contactremoved", contact); } diff --git a/static/partials/buddy.html b/static/partials/buddy.html index d0075eea..ceae38d2 100644 --- a/static/partials/buddy.html +++ b/static/partials/buddy.html @@ -1,5 +1,5 @@
-
{{session.Id|displayName}}
+
{{session.Id|displayName}} {{session.serial}}
({{session.count}}) {{display.subLine}}
From 06e54d894c02706a44f52bdc507bc897d5268853 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Fri, 6 Jun 2014 19:18:06 +0200 Subject: [PATCH 21/55] Validate javascript with es5 support set to true. --- .jshint | 3 ++- static/js/services/contacts.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.jshint b/.jshint index f7abd73d..73bb6d73 100644 --- a/.jshint +++ b/.jshint @@ -4,5 +4,6 @@ "curly": true, "forin": true, "trailing": true, - "asi": true + "asi": true, + "es5": true } diff --git a/static/js/services/contacts.js b/static/js/services/contacts.js index 5cd2d2e4..8e501d65 100644 --- a/static/js/services/contacts.js +++ b/static/js/services/contacts.js @@ -86,7 +86,7 @@ define(['underscore', 'jquery', 'modernizr'], function(underscore, $, Modernizr) var cursorRequest = store.openCursor(keyRange); cursorRequest.onsuccess = function(event) { var result = event.target.result; - if (!!result === false) { + if (!result) { return; } //console.log("read data idb", result, event); From e6714412deee5c539aba9e47b62346e7c956fdfc Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Fri, 6 Jun 2014 19:18:16 +0200 Subject: [PATCH 22/55] Removed debug. --- static/partials/buddy.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/partials/buddy.html b/static/partials/buddy.html index ceae38d2..d0075eea 100644 --- a/static/partials/buddy.html +++ b/static/partials/buddy.html @@ -1,5 +1,5 @@
-
{{session.Id|displayName}} {{session.serial}}
+
{{session.Id|displayName}}
({{session.count}}) {{display.subLine}}
From 89c75c0d6a877859e2e1f3cf280d717aaf053d1d Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Fri, 6 Jun 2014 22:57:02 +0200 Subject: [PATCH 23/55] Store contact buddy image as data url. --- .jshint | 3 +-- static/js/services/buddylist.js | 46 ++++++++++++++++++++++++++++++++- static/js/services/contacts.js | 5 +--- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/.jshint b/.jshint index 73bb6d73..f7abd73d 100644 --- a/.jshint +++ b/.jshint @@ -4,6 +4,5 @@ "curly": true, "forin": true, "trailing": true, - "asi": true, - "es5": true + "asi": true } diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 5920e77d..9253309d 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -415,6 +415,42 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; + Buddylist.prototype.dumpBuddyPictureToBlob = function(scope, data) { + + if (!data) { + data = this.dumpBuddyPictureToString(scope); + if (!data) { + return null; + } + } + // NOTE(longsleep): toBlob is not widely supported narf .. + // see: https://code.google.com/p/chromium/issues/detail?id=67587 + var parts = data.match(/data:([^;]*)(;base64)?,([0-9A-Za-z+/]+)/); + var binStr = atob(parts[3]); + var buf = new ArrayBuffer(binStr.length); + var view = new Uint8Array(buf); + for (var i = 0; i < view.length; i++) { + view[i] = binStr.charCodeAt(i); + } + return new Blob([view], {'type': parts[1]}); + + }; + + Buddylist.prototype.dumpBuddyPictureToString = function(scope) { + + var img = scope.element.find(".buddyPicture img").get(0); + if (img) { + var canvas = $window.document.createElement("canvas"); + canvas.width = img.width; + canvas.height = img.height; + var ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); + return canvas.toDataURL("image/jpeg"); + } + return null; + + }; + Buddylist.prototype.setDisplay = function(id, scope, data, queueName) { var status = data.Status; @@ -586,7 +622,15 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! if (sessionData) { if (contact.Status === null && sessionData.Status) { // Update contact status with session.Status - contact.Status = _.extend({}, sessionData.Status); + var status = contact.Status = _.extend({}, sessionData.Status); + if (status.buddyPicture) { + var img = this.dumpBuddyPictureToString(scope); + if (img) { + status.buddyPicture = img; + } else { + delete status.buddyPicture; + } + } console.log("Injected status into contact", contact); } this.updateDisplay(sessionData.Id, scope, contact, "status"); diff --git a/static/js/services/contacts.js b/static/js/services/contacts.js index 8e501d65..e2f6921b 100644 --- a/static/js/services/contacts.js +++ b/static/js/services/contacts.js @@ -128,12 +128,12 @@ define(['underscore', 'jquery', 'modernizr'], function(underscore, $, Modernizr) console.log("Load contacts from storage", database); database.all(_.bind(function(data) { var contact = contactData.addByData(data.contact); + // TODO(longsleep): Convert buddyImage string to Blob. this.e.triggerHandler("contactadded", contact); }, this)); }; Contacts.prototype.add = function(request, status) { - var contact = contactData.addByRequest(request, status); this.e.triggerHandler("contactadded", contact); if (database) { @@ -142,11 +142,9 @@ define(['underscore', 'jquery', 'modernizr'], function(underscore, $, Modernizr) contact: contact }) } - }; Contacts.prototype.remove = function(userid) { - var contact = contactData.get(userid); console.log("contacts remove", userid, contact); if (contact) { @@ -156,7 +154,6 @@ define(['underscore', 'jquery', 'modernizr'], function(underscore, $, Modernizr) } this.e.triggerHandler("contactremoved", contact); } - }; return new Contacts(); From a61632eda584b38478010da87698fe6e2c9a2417 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sat, 7 Jun 2014 14:02:57 +0200 Subject: [PATCH 24/55] Implemented central click handler for buddy list clicks. --- src/styles/components/_buddylist.scss | 3 + static/js/directives/buddylist.js | 6 +- static/js/services/buddylist.js | 84 +++++++++++++++++---------- static/partials/buddy.html | 2 +- static/partials/buddyactions.html | 4 +- 5 files changed, 60 insertions(+), 39 deletions(-) diff --git a/src/styles/components/_buddylist.scss b/src/styles/components/_buddylist.scss index 1d000e03..8eeaff88 100644 --- a/src/styles/components/_buddylist.scss +++ b/src/styles/components/_buddylist.scss @@ -223,6 +223,9 @@ .#{$fa-css-prefix} { font-size: 2em; } + i.fa { + pointer-events: none; + } } .buddy .buddysessions { diff --git a/static/js/directives/buddylist.js b/static/js/directives/buddylist.js index b7b23542..50426f1e 100644 --- a/static/js/directives/buddylist.js +++ b/static/js/directives/buddylist.js @@ -61,6 +61,7 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { }; + /* $scope.doAudioConference = function(id) { $scope.updateAutoAccept(id); @@ -71,7 +72,7 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { } }) - }; + };*/ $scope.setRoomStatus = function(status) { if (status !== $scope.enabled) { @@ -83,9 +84,6 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { } }; - //XXX(longsleep): Debug leftover ?? Remove this. - window.doAudioConference = $scope.doAudioConference; - var buddylist = $scope.buddylist = buddyList.buddylist($element, $scope, {}); var onJoined = _.bind(buddylist.onJoined, buddylist); var onLeft = _.bind(buddylist.onLeft, buddylist); diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 9253309d..692df29e 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -157,22 +157,24 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! $element.on("mouseenter mouseleave", ".buddy", _.bind(function(event) { // Hover handler for on Buddy actions. var buddyElement = $(event.currentTarget); - this.hover(buddyElement, event.type === "mouseenter" ? true : false, buddyElement.scope().session.Id); + this.hover(buddyElement, event.type === "mouseenter" ? true : false); }, this)); $element.on("click", ".buddy", _.bind(function(event) { + console.log("click event", event); var buddyElement = $(event.currentTarget); - buddyElement.scope().doDefault(); + //buddyElement.scope().doDefault(); + this.click(buddyElement, event.target); }, this)); - $element.on("click", ".fa.contact", _.bind(function(event) { + /*$element.on("click", ".fa.contact", _.bind(function(event) { event.stopPropagation(); var buddyElement = $(event.currentTarget); buddyElement.scope().doDefaultContact(); - }, this)); + }, this));*/ $element.attr("data-xthreshold", "10"); $element.on("swipeleft", ".buddy", _.bind(function(event) { event.preventDefault(); var buddyElement = $(event.currentTarget); - this.hover(buddyElement, !buddyElement.hasClass("hovered"), buddyElement.scope().session.Id); + this.hover(buddyElement, !buddyElement.hasClass("hovered")); }, this)); $window.setInterval(_.bind(this.soundLoop, this), 500); @@ -211,35 +213,17 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! Buddylist.prototype.onBuddyScopeCreated = function(scope, data) { + // Init scope with our stuff. scope.element = null; scope.contact = null; scope.display = {}; - var session = scope.session = buddySession.create(data); - - scope.doDefault = function() { - var id = scope.session.Id; - //if (scope.status.isMixer) { - // return scope.doAudioConference(id); - //} - return scope.doCall(id); - }; - scope.doDefaultContact = function() { - var contact = scope.contact; - if (contact) { - return scope.doContactRemove(contact.Userid); - } else { - var id = scope.session.Id; - return scope.doContactRequest(id); - } - }; + scope.session = buddySession.create(data); scope.$on("$destroy", function() { //console.log("destroyed"); scope.element = null; scope.killed = true; }); - //console.log("on buddy scope", session.Userid, session); - }; Buddylist.prototype.onBuddySessionUserid = function(scope, sourceSession) { @@ -666,9 +650,50 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; - Buddylist.prototype.hover = function(buddyElement, hover, id) { + Buddylist.prototype.click = function(buddyElement, target) { + + //console.log("click handler", buddyElement, target); + var action = $(target).data("action"); + if (!action) { + // Make call the default action. + action = "call"; + } + + var scope = buddyElement.scope(); + var session = scope.session; + var sessionData = session.get() + var contact = scope.contact; + var id; + if (!sessionData) { + // TODO(longsleep): Find session with help of contact. + console.log("No sessions for this buddy.", session, contact); + } else { + id = sessionData.Id; + } + //console.log("id", id); + switch (action) { + case "call": + scope.doCall(id); + break; + case "chat": + scope.doChat(id); + break; + case "contact": + if (contact) { + scope.doContactRemove(contact.Userid); + } else { + scope.doContactRequest(id); + } + break; + } + + }; + + Buddylist.prototype.hover = function(buddyElement, hover) { - //console.log("hover handler", event, hover, id); + //console.log("hover handler", buddyElement, hover); + var scope = buddyElement.scope(); + var id = scope.session.Id; var buddy = $(buddyElement); var actionElements = this.actionElements; var elem; @@ -689,11 +714,6 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! if (elem) { buddy.addClass("hovered"); } else { - var scope = buddyData.get(id); - if (!scope) { - console.warn("No scope for buddy", id); - return; - } var template = buddyActions; //if (scope.status.autoCalls && _.indexOf(scope.status.autoCalls, "conference") !== -1) { // template = buddyActionsForAudioMixer; diff --git a/static/partials/buddy.html b/static/partials/buddy.html index d0075eea..d566c41d 100644 --- a/static/partials/buddy.html +++ b/static/partials/buddy.html @@ -1,5 +1,5 @@
{{session.Id|displayName}}
-
({{session.count}}) {{display.subLine}}
+
({{session.count}}) {{display.subLine}}
diff --git a/static/partials/buddyactions.html b/static/partials/buddyactions.html index 78e31a09..df4aa7a0 100644 --- a/static/partials/buddyactions.html +++ b/static/partials/buddyactions.html @@ -1,7 +1,7 @@
- - + +
    From af8ecd7e360cb40c1e44c22e20e9e607e4d301f3 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Wed, 11 Jun 2014 15:21:06 +0200 Subject: [PATCH 25/55] Make sure to forget failed credentials. --- static/js/controllers/mediastreamcontroller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/js/controllers/mediastreamcontroller.js b/static/js/controllers/mediastreamcontroller.js index d9a936a6..113c5eec 100644 --- a/static/js/controllers/mediastreamcontroller.js +++ b/static/js/controllers/mediastreamcontroller.js @@ -402,6 +402,7 @@ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function delete data.nonce; }, function(data, status) { console.error("Failed to authorize session", status, data); + mediaStream.users.forget(); }); } } From 4aa4e3f9e84f835e73f388502cd9e7b6222acf75 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Wed, 11 Jun 2014 18:03:16 +0200 Subject: [PATCH 26/55] Implemented settings users controller as overrideable. --- static/js/controllers/controllers.js | 6 +- .../js/controllers/usersettingscontroller.js | 66 +++++++++++++++++++ static/js/directives/settings.js | 36 ---------- static/partials/settings.html | 48 +++++++------- 4 files changed, 94 insertions(+), 62 deletions(-) create mode 100644 static/js/controllers/usersettingscontroller.js diff --git a/static/js/controllers/controllers.js b/static/js/controllers/controllers.js index 6d3411d8..24b7c56b 100644 --- a/static/js/controllers/controllers.js +++ b/static/js/controllers/controllers.js @@ -24,13 +24,15 @@ define([ 'controllers/mediastreamcontroller', 'controllers/statusmessagecontroller', 'controllers/chatroomcontroller', - 'controllers/roomchangecontroller'], function(_, MediastreamController, StatusmessageController, ChatroomController, RoomchangeController) { + 'controllers/roomchangecontroller', + 'controllers/usersettingscontroller'], function(_, MediastreamController, StatusmessageController, ChatroomController, RoomchangeController, UsersettingsController) { var controllers = { MediastreamController: MediastreamController, StatusmessageController: StatusmessageController, ChatroomController: ChatroomController, - RoomchangeController: RoomchangeController + RoomchangeController: RoomchangeController, + UsersettingsController: UsersettingsController }; var initialize = function(angModule) { diff --git a/static/js/controllers/usersettingscontroller.js b/static/js/controllers/usersettingscontroller.js new file mode 100644 index 00000000..2baee76d --- /dev/null +++ b/static/js/controllers/usersettingscontroller.js @@ -0,0 +1,66 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +define([], function() { + + // UsersettingsController + return ["$scope", "$element", "mediaStream", function($scope, $element, mediaStream) { + + $scope.withUsersForget = true; + + this.registerUserid = function(btn) { + + var successHandler = function(data) { + console.info("Created new userid:", data.userid); + // If the server provided us a nonce, we can do everthing on our own. + mediaStream.users.store(data); + $scope.loadedUserlogin = true; + safeApply($scope); + // Directly authenticate ourselves with the provided nonce. + mediaStream.api.requestAuthentication(data.userid, data.nonce); + delete data.nonce; + }; + + console.log("No userid - creating one ..."); + mediaStream.users.register(btn.form, function(data) { + if (data.nonce) { + successHandler(data); + } else { + // No nonce received. So this means something we cannot do on our own. + // Make are GET request and retrieve nonce that way and let the + // browser/server do the rest. + mediaStream.users.authorize(data, successHandler, function(data, status) { + console.error("Failed to get nonce after create", status, data); + }); + } + }, function(data, status) { + console.error("Failed to create userid", status, data); + }); + + }; + + this.forgetUserid = function() { + mediaStream.users.forget(); + mediaStream.connector.forgetAndReconnect(); + }; + + }]; + +}); diff --git a/static/js/directives/settings.js b/static/js/directives/settings.js index 6716aa05..a05ec15a 100644 --- a/static/js/directives/settings.js +++ b/static/js/directives/settings.js @@ -74,42 +74,6 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t }); }; - $scope.registerUserid = function(btn) { - - var successHandler = function(data) { - console.info("Created new userid:", data.userid); - // If the server provided us a nonce, we can do everthing on our own. - mediaStream.users.store(data); - $scope.loadedUserlogin = true; - safeApply($scope); - // Directly authenticate ourselves with the provided nonce. - mediaStream.api.requestAuthentication(data.userid, data.nonce); - delete data.nonce; - }; - - console.log("No userid - creating one ..."); - mediaStream.users.register(btn.form, function(data) { - if (data.nonce) { - successHandler(data); - } else { - // No nonce received. So this means something we cannot do on our own. - // Make are GET request and retrieve nonce that way and let the - // browser/server do the rest. - mediaStream.users.authorize(data, successHandler, function(data, status) { - console.error("Failed to get nonce after create", status, data); - }); - } - }, function(data, status) { - console.error("Failed to create userid", status, data); - }); - - }; - - $scope.forgetUserid = function() { - mediaStream.users.forget(); - mediaStream.connector.forgetAndReconnect(); - }; - $scope.checkDefaultMediaSources = function() { if ($scope.master.settings.microphoneId && !$scope.mediaSources.hasAudioId($scope.master.settings.microphoneId)) { $scope.master.settings.microphoneId = null; diff --git a/static/partials/settings.html b/static/partials/settings.html index 8a53772d..abb955fa 100644 --- a/static/partials/settings.html +++ b/static/partials/settings.html @@ -19,35 +19,35 @@ {{_('Your picture and name are visible to others.')}}
-
- -
-
-
- +
+
+ +
+ +
+ + + +
+
+
{{userid}}
+ {{_('Authenticated by certificate. To log out you have to remove your certificate from the browser.')}} +
+ +
+
{{userid}}
+ - -
-
-
{{userid}}
- {{_('Authenticated by certificate. To log out you have to remove your certificate from the browser.')}}
- -
- -
{{userid}}
-
-
- {{_('Only register an ID if this is your private browser.')}} -

From 3cb72f2a864672ac4690d0c61fe878b19842f777 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Thu, 12 Jun 2014 10:24:38 +0200 Subject: [PATCH 27/55] Fixed double events. --- static/partials/settings.html | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/static/partials/settings.html b/static/partials/settings.html index abb955fa..1bb159f7 100644 --- a/static/partials/settings.html +++ b/static/partials/settings.html @@ -26,7 +26,7 @@
-
- +
+ + +
{{userid}}
-
From 70cdfce628940f8ad673b132e5f38898e0c67e03 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Thu, 12 Jun 2014 11:53:51 +0200 Subject: [PATCH 28/55] Implemented localStorage service. --- .../js/controllers/mediastreamcontroller.js | 3 +- static/js/directives/settings.js | 2 +- static/js/directives/usability.js | 2 +- static/js/services/localstorage.js | 73 +++++++++++++++++++ static/js/services/mediastream.js | 2 +- static/js/services/services.js | 9 ++- 6 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 static/js/services/localstorage.js diff --git a/static/js/controllers/mediastreamcontroller.js b/static/js/controllers/mediastreamcontroller.js index 113c5eec..6ffcba93 100644 --- a/static/js/controllers/mediastreamcontroller.js +++ b/static/js/controllers/mediastreamcontroller.js @@ -20,7 +20,7 @@ */ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function(_, BigScreen, moment, sjcl) { - return ["$scope", "$rootScope", "$element", "$window", "$timeout", "safeDisplayName", "safeApply", "mediaStream", "appData", "playSound", "desktopNotify", "alertify", "toastr", "translation", "fileDownload", function($scope, $rootScope, $element, $window, $timeout, safeDisplayName, safeApply, mediaStream, appData, playSound, desktopNotify, alertify, toastr, translation, fileDownload) { + return ["$scope", "$rootScope", "$element", "$window", "$timeout", "safeDisplayName", "safeApply", "mediaStream", "appData", "playSound", "desktopNotify", "alertify", "toastr", "translation", "fileDownload", "localStorage", function($scope, $rootScope, $element, $window, $timeout, safeDisplayName, safeApply, mediaStream, appData, playSound, desktopNotify, alertify, toastr, translation, fileDownload, localStorage) { /*console.log("route", $route, $routeParams, $location);*/ @@ -344,7 +344,6 @@ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function }); // Load stuff from localStorage. - // TODO(longsleep): Put localStorage into Angular service. var storedUser = localStorage.getItem("mediastream-user"); console.log("Found stored user data:", storedUser); if (storedUser) { diff --git a/static/js/directives/settings.js b/static/js/directives/settings.js index a05ec15a..7d36757e 100644 --- a/static/js/directives/settings.js +++ b/static/js/directives/settings.js @@ -22,7 +22,7 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t return ["$compile", "mediaStream", function($compile, mediaStream) { - var controller = ['$scope', 'desktopNotify', 'mediaSources', 'safeApply', 'availableLanguages', 'translation', function($scope, desktopNotify, mediaSources, safeApply, availableLanguages, translation) { + var controller = ['$scope', 'desktopNotify', 'mediaSources', 'safeApply', 'availableLanguages', 'translation', 'localStorage', function($scope, desktopNotify, mediaSources, safeApply, availableLanguages, translation, localStorage) { $scope.layout.settings = false; $scope.showAdvancedSettings = true; diff --git a/static/js/directives/usability.js b/static/js/directives/usability.js index a75aa58a..aace59a3 100644 --- a/static/js/directives/usability.js +++ b/static/js/directives/usability.js @@ -24,7 +24,7 @@ define(['jquery', 'underscore', 'text!partials/usability.html'], function($, _, return ["mediaStream", function(mediaStream) { - var controller = ['$scope', "mediaStream", "safeApply", "$timeout", function($scope, mediaStream, safeApply, $timeout) { + var controller = ['$scope', "mediaStream", "safeApply", "$timeout", "localStorage", function($scope, mediaStream, safeApply, $timeout, localStorage) { var pending = true; var complete = false; diff --git a/static/js/services/localstorage.js b/static/js/services/localstorage.js new file mode 100644 index 00000000..2d5d75c5 --- /dev/null +++ b/static/js/services/localstorage.js @@ -0,0 +1,73 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +define(["modernizr"], function(Modernizr) { + + // localStorage + return ["$window", function($window) { + + // PersistentStorage (c)2014 struktur AG. MIT license. + var PersistentStorage = function(prefix) { + this.prefix = prefix ? prefix : "ps"; + this.isPersistentStorage = true; + }; + PersistentStorage.prototype.setItem = function(key, data) { + var name = this.prefix+"_"+key; + $window.document.cookie = name + "=" + data + "; path=/"; + }; + PersistentStorage.prototype.getItem = function(key) { + var name = this.prefix+"_"+key+"="; + var ca = $window.document.cookie.split(';'); + for (var i=0; i Date: Thu, 12 Jun 2014 12:13:05 +0200 Subject: [PATCH 29/55] Fixed typo. --- static/js/services/localstorage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/services/localstorage.js b/static/js/services/localstorage.js index 2d5d75c5..dbf2332f 100644 --- a/static/js/services/localstorage.js +++ b/static/js/services/localstorage.js @@ -49,7 +49,7 @@ define(["modernizr"], function(Modernizr) { }; var storage; - if (Modernizr.localStorage) { + if (Modernizr.localstorage) { storage = $window.localStorage; } else { storage = new PersistentStorage(); From b85a8520f5fdba7b8f77d9bfdd5d649326a03739 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Thu, 12 Jun 2014 14:56:32 +0200 Subject: [PATCH 30/55] Added selfReceived event as example. --- static/js/controllers/mediastreamcontroller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/js/controllers/mediastreamcontroller.js b/static/js/controllers/mediastreamcontroller.js index 6ffcba93..e1c1f655 100644 --- a/static/js/controllers/mediastreamcontroller.js +++ b/static/js/controllers/mediastreamcontroller.js @@ -427,6 +427,8 @@ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function }, 0); } + appData.e.triggerHandler("selfReceived", data); + }); mediaStream.webrtc.e.on("peercall", function(event, peercall) { From 57d4f759983326940f16e1238880ebb1e63ab68c Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Thu, 12 Jun 2014 16:03:27 +0200 Subject: [PATCH 31/55] Added support for username in useridcombo. --- src/app/spreed-webrtc-server/users.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/spreed-webrtc-server/users.go b/src/app/spreed-webrtc-server/users.go index 4bc552dd..ed70687f 100644 --- a/src/app/spreed-webrtc-server/users.go +++ b/src/app/spreed-webrtc-server/users.go @@ -74,10 +74,11 @@ func (uh *UsersSharedsecretHandler) Get(request *http.Request) (userid string, e func (uh *UsersSharedsecretHandler) Validate(snr *SessionNonceRequest, request *http.Request) (string, error) { // Parse UseridCombo. - useridCombo := strings.SplitN(snr.UseridCombo, ":", 2) - if len(useridCombo) != 2 { + useridCombo := strings.SplitN(snr.UseridCombo, ":", 3) + if len(useridCombo) < 2 { return "", errors.New("invalid useridcombo") } + // TODO(longsleep): Add support for third field which provides the username. expirationString, userid := useridCombo[0], useridCombo[1] expiration, err := strconv.ParseInt(expirationString, 10, 64) From 9d5e8301e2d1c4b54bc89a4796894aa7659028d9 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Thu, 12 Jun 2014 16:04:16 +0200 Subject: [PATCH 32/55] Added docs about selfReceived event. --- static/js/services/appdata.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/js/services/appdata.js b/static/js/services/appdata.js index dea2595f..de688591 100644 --- a/static/js/services/appdata.js +++ b/static/js/services/appdata.js @@ -22,6 +22,7 @@ define(["jquery"], function($) { // appData.e events // - authenticationChanged(userid) + // - selfReceived(self) // appData return [function() { From 2da69c40871ada3cd695b8eed3a98e5477f7b99b Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Thu, 12 Jun 2014 19:55:42 +0200 Subject: [PATCH 33/55] Store contacts in per user database. --- static/js/directives/contactrequest.js | 7 +- static/js/services/contactdata.js | 15 +++ static/js/services/contacts.js | 138 ++++++++++++++++++------- 3 files changed, 120 insertions(+), 40 deletions(-) diff --git a/static/js/directives/contactrequest.js b/static/js/directives/contactrequest.js index 3889d13b..ef5358a7 100644 --- a/static/js/directives/contactrequest.js +++ b/static/js/directives/contactrequest.js @@ -50,7 +50,12 @@ define(['jquery', 'underscore'], function($, _) { // Add support for contacts on controller creation. var request = $scope.request; if (request.Success && request.Userid && request.Token) { - $scope.addContact(request, buddyData.lookup($scope.id).status || null); + var buddy = buddyData.lookup($scope.id); + var status = {}; + if (buddy) { + $.extend(status, buddy.status); + } + $scope.addContact(request, status); } }]; diff --git a/static/js/services/contactdata.js b/static/js/services/contactdata.js index b06d2f0e..20cf81e9 100644 --- a/static/js/services/contactdata.js +++ b/static/js/services/contactdata.js @@ -28,6 +28,16 @@ define(['underscore', 'jquery'], function(underscore, $) { var count = 0; var contactData = { + clear: function(cb) { + _.each(users, _.bind(function(idx, userid) { + var contact = contacts[idx]; + if (cb && contact) { + cb(contact); + } + this.remove(userid); + }, this)); + count = 0; + }, addByRequest: function(request, status) { //console.log("addByRequest", request, status); var userid = request.Userid; @@ -69,6 +79,10 @@ define(['underscore', 'jquery'], function(underscore, $) { return null; }, remove: function(userid) { + if (users.hasOwnProperty(userid)) { + var id = users[userid]; + delete contacts[id]; + } delete users[userid]; }, getById: function(id) { @@ -81,6 +95,7 @@ define(['underscore', 'jquery'], function(underscore, $) { return null } }; + return contactData; }]; diff --git a/static/js/services/contacts.js b/static/js/services/contacts.js index e2f6921b..efbcf2c0 100644 --- a/static/js/services/contacts.js +++ b/static/js/services/contacts.js @@ -18,44 +18,51 @@ * along with this program. If not, see . * */ -define(['underscore', 'jquery', 'modernizr'], function(underscore, $, Modernizr) { +define(['underscore', 'jquery', 'modernizr', 'sjcl'], function(underscore, $, Modernizr, sjcl) { - var Database = function(name, version) { + var Database = function(name) { + this.version = 3; this.ready = false; this.db = null; this.name = name; this.e = $({}); - var request = indexedDB.open("mediastream", version); + var request = indexedDB.open(this.name, this.version); var that = this; request.onupgradeneeded = function(event) { var db = event.target.result; var transaction = event.target.transaction; transaction.onerror = _.bind(that.onerror, that); - if (db.objectStoreNames.contains(name)) { - // TODO(longsleep): Database upgrade should keep the data and migrate it. - db.deleteObjectStore("contacts") - console.warn("Removed contacts database with old format.") - } - db.createObjectStore(name, { - // We use id field as our unique identifier. - keyPath: "id" - }); + that.init(db); console.log("Created contacts database.") }; request.onsuccess = _.bind(that.onsuccess, that); }; + Database.prototype.init = function(db) { + var createOrUpdateStore = function(name, obj) { + if (db.objectStoreNames.contains(name)) { + // TODO(longsleep): Migrate data. + db.deleteObjectStore(name); + } + db.createObjectStore(name, obj); + } + // Create our object stores. + createOrUpdateStore("contacts", { + // We use id field as our unique identifier. + keyPath: "id" + }); + }; Database.prototype.onerror = function(event) { console.log("IndexDB database error", event); }; Database.prototype.onsuccess = function(event) { this.db = event.target.result; this.ready = true; - console.log("Openend database for contacts", this.db); + console.log("Openend database", this.db); this.e.triggerHandler("ready"); }; - Database.prototype.put = function(data, successCallback, errorCallback) { - var transaction = this.db.transaction(this.name, "readwrite"); - var store = transaction.objectStore(this.name); + Database.prototype.put = function(store, data, successCallback, errorCallback) { + var transaction = this.db.transaction(store, "readwrite"); + var store = transaction.objectStore(store); var request = store.put(data); if (!errorCallback) { errorCallback = _.bind(this.onerror, this); @@ -66,9 +73,9 @@ define(['underscore', 'jquery', 'modernizr'], function(underscore, $, Modernizr) } return request; }; - Database.prototype.delete = function(id, successCallback, errorCallback) { - var transaction = this.db.transaction(this.name, "readwrite"); - var store = transaction.objectStore(this.name); + Database.prototype.delete = function(store, id, successCallback, errorCallback) { + var transaction = this.db.transaction(store, "readwrite"); + var store = transaction.objectStore(store); var request = store.delete(id); if (!errorCallback) { errorCallback = _.bind(this.onerror, this); @@ -79,9 +86,9 @@ define(['underscore', 'jquery', 'modernizr'], function(underscore, $, Modernizr) } return request; }; - Database.prototype.all = function(iteratorCallback, errorCallback) { - var transaction = this.db.transaction(this.name); - var store = transaction.objectStore(this.name); + Database.prototype.all = function(store, iteratorCallback, errorCallback) { + var transaction = this.db.transaction(store); + var store = transaction.objectStore(store); var keyRange = IDBKeyRange.lowerBound(0); var cursorRequest = store.openCursor(keyRange); cursorRequest.onsuccess = function(event) { @@ -99,45 +106,98 @@ define(['underscore', 'jquery', 'modernizr'], function(underscore, $, Modernizr) transaction.onerror = cursorRequest.onerror = errorCallback; return cursorRequest; }; - - var database; - if (Modernizr.indexeddb) { - database = new Database("contacts", 1); - } + Database.prototype.close = function() { + // TODO(longsleep): Database close. + this.e.off(); + if (this.db) { + this.db.close(); + this.db = null; + this.ready = false; + } + _.defer(_.bind(function() { + this.e.triggerHandler("closed"); + }, this)); + }; // contacts return ["appData", "contactData", function(appData, contactData) { var Contacts = function() { + this.e = $({}); this.userid = null; + this.database = null; + appData.e.on("authenticationChanged", _.bind(function(event, userid) { - this.userid = userid; + var database = this.open(userid); if (database && userid) { // TODO(longsleep): This needs to be delayed util self has ha userid. if (database.ready) { - _.defer(_.bind(this.load, this)); + _.defer(_.bind(function() { + if (this.database === database) { + this.load(); + } + }, this)); } else { - database.e.one("ready", _.bind(this.load, this)); + database.e.one("ready", _.bind(function() { + if (this.database === database) { + this.load(); + } + }, this)); } } }, this)); + + }; + + Contacts.prototype.open = function(userid) { + + if (this.database && (!userid || this.userid !== userid)) { + // Unload existing contacts. + this.unload(); + // Close existing database. + this.database.close(); + this.database = null; + } + if (userid) { + if (!Modernizr.indexeddb) { + return; + } + // Create HMAC database name for user. + var hmac = new sjcl.misc.hmac('mediastream'); + var id = "mediastream-"+sjcl.codec.base64.fromBits(hmac.encrypt(userid)); + console.log("Open of database:", id); + var database = this.database = new Database(id); + return database; + } else { + this.database = null; + return null; + } + }; Contacts.prototype.load = function() { - console.log("Load contacts from storage", database); - database.all(_.bind(function(data) { - var contact = contactData.addByData(data.contact); - // TODO(longsleep): Convert buddyImage string to Blob. - this.e.triggerHandler("contactadded", contact); + if (this.database) { + console.log("Load contacts from storage", this); + this.database.all("contacts", _.bind(function(data) { + var contact = contactData.addByData(data.contact); + // TODO(longsleep): Convert buddyImage string to Blob. + this.e.triggerHandler("contactadded", contact); + }, this)); + } + }; + + Contacts.prototype.unload = function() { + contactData.clear(_.bind(function(contact) { + this.e.triggerHandler("contactremoved", contact); }, this)); }; Contacts.prototype.add = function(request, status) { var contact = contactData.addByRequest(request, status); this.e.triggerHandler("contactadded", contact); - if (database) { - database.put({ + if (this.database) { + this.database.put("contacts", { id: contact.Userid, contact: contact }) @@ -149,8 +209,8 @@ define(['underscore', 'jquery', 'modernizr'], function(underscore, $, Modernizr) console.log("contacts remove", userid, contact); if (contact) { contactData.remove(userid); - if (database) { - database.delete(userid); + if (this.database) { + this.database.delete("contacts", userid); } this.e.triggerHandler("contactremoved", contact); } From 4a62319107b090ffcc0a054e3baf185ca187e0fa Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Thu, 12 Jun 2014 19:59:53 +0200 Subject: [PATCH 34/55] Fixed jshint errors. --- static/js/services/buddylist.js | 2 +- static/js/services/contacts.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 692df29e..71666833 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -409,7 +409,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! } // NOTE(longsleep): toBlob is not widely supported narf .. // see: https://code.google.com/p/chromium/issues/detail?id=67587 - var parts = data.match(/data:([^;]*)(;base64)?,([0-9A-Za-z+/]+)/); + var parts = data.match(/data:([^;]*)(;base64)?,([0-9A-Za-z+\/]+)/); var binStr = atob(parts[3]); var buf = new ArrayBuffer(binStr.length); var view = new Uint8Array(buf); diff --git a/static/js/services/contacts.js b/static/js/services/contacts.js index efbcf2c0..7c9f1faf 100644 --- a/static/js/services/contacts.js +++ b/static/js/services/contacts.js @@ -60,9 +60,9 @@ define(['underscore', 'jquery', 'modernizr', 'sjcl'], function(underscore, $, Mo console.log("Openend database", this.db); this.e.triggerHandler("ready"); }; - Database.prototype.put = function(store, data, successCallback, errorCallback) { - var transaction = this.db.transaction(store, "readwrite"); - var store = transaction.objectStore(store); + Database.prototype.put = function(storename, data, successCallback, errorCallback) { + var transaction = this.db.transaction(storename, "readwrite"); + var store = transaction.objectStore(storename); var request = store.put(data); if (!errorCallback) { errorCallback = _.bind(this.onerror, this); @@ -73,9 +73,9 @@ define(['underscore', 'jquery', 'modernizr', 'sjcl'], function(underscore, $, Mo } return request; }; - Database.prototype.delete = function(store, id, successCallback, errorCallback) { - var transaction = this.db.transaction(store, "readwrite"); - var store = transaction.objectStore(store); + Database.prototype.delete = function(storename, id, successCallback, errorCallback) { + var transaction = this.db.transaction(storename, "readwrite"); + var store = transaction.objectStore(storename); var request = store.delete(id); if (!errorCallback) { errorCallback = _.bind(this.onerror, this); @@ -86,9 +86,9 @@ define(['underscore', 'jquery', 'modernizr', 'sjcl'], function(underscore, $, Mo } return request; }; - Database.prototype.all = function(store, iteratorCallback, errorCallback) { - var transaction = this.db.transaction(store); - var store = transaction.objectStore(store); + Database.prototype.all = function(storename, iteratorCallback, errorCallback) { + var transaction = this.db.transaction(storename); + var store = transaction.objectStore(storename); var keyRange = IDBKeyRange.lowerBound(0); var cursorRequest = store.openCursor(keyRange); cursorRequest.onsuccess = function(event) { From 8260b4d350582b1e41b9bdcde340f07723f48ca3 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 15 Jun 2014 01:45:53 +0200 Subject: [PATCH 35/55] Added missing injection. --- static/js/controllers/usersettingscontroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/controllers/usersettingscontroller.js b/static/js/controllers/usersettingscontroller.js index 2baee76d..2bbc5c8d 100644 --- a/static/js/controllers/usersettingscontroller.js +++ b/static/js/controllers/usersettingscontroller.js @@ -21,7 +21,7 @@ define([], function() { // UsersettingsController - return ["$scope", "$element", "mediaStream", function($scope, $element, mediaStream) { + return ["$scope", "$element", "mediaStream", "safeApply", function($scope, $element, mediaStream, safeApply) { $scope.withUsersForget = true; From cc255be4c5886a83c91e9c5fffe5f324f35cabd7 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 15 Jun 2014 13:19:04 +0200 Subject: [PATCH 36/55] Added encryption to sessions and contact tokens to protect data. --- server.conf.in | 5 +++++ src/app/spreed-webrtc-server/hub.go | 30 ++++++++++++++++++---------- src/app/spreed-webrtc-server/main.go | 7 ++++++- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/server.conf.in b/server.conf.in index 9a9257d1..8b8c1d27 100644 --- a/server.conf.in +++ b/server.conf.in @@ -60,6 +60,11 @@ listen = 127.0.0.1:8080 ; Session secret to use for session id generator. 32 or 64 bytes of random data ; are recommented. sessionSecret = the-default-secret-do-not-keep-me +; Encryption secret protecting the data in generated server side tokens. Use +; 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256. When you change +; the encryption secret, stored authentications, sessions and contacts become +; invalid. +encryptionSecret = tne-default-encryption-block-key ; Full path to a text file containig client tokens which a user needs to enter ; when accessing the web client. Each line in this file represents a valid ; token. diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index 15a1cb0f..4972632f 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -23,6 +23,7 @@ package main import ( "bytes" + "crypto/aes" "crypto/hmac" "crypto/sha1" "crypto/sha256" @@ -72,6 +73,7 @@ type Hub struct { version string config *Config sessionSecret []byte + encryptionSecret []byte turnSecret []byte tickets *securecookie.SecureCookie count uint64 @@ -86,29 +88,35 @@ type Hub struct { contacts *securecookie.SecureCookie } -func NewHub(version string, config *Config, sessionSecret, turnSecret, realm string) *Hub { +func NewHub(version string, config *Config, sessionSecret, encryptionSecret, turnSecret, realm string) *Hub { h := &Hub{ - connectionTable: make(map[string]*Connection), - sessionTable: make(map[string]*Session), - roomTable: make(map[string]*RoomWorker), - version: version, - config: config, - sessionSecret: []byte(sessionSecret), - turnSecret: []byte(turnSecret), - realm: realm, + connectionTable: make(map[string]*Connection), + sessionTable: make(map[string]*Session), + roomTable: make(map[string]*RoomWorker), + version: version, + config: config, + sessionSecret: []byte(sessionSecret), + encryptionSecret: []byte(encryptionSecret), + turnSecret: []byte(turnSecret), + realm: realm, } if len(h.sessionSecret) < 32 { log.Printf("Weak sessionSecret (only %d bytes). It is recommended to use a key with 32 or 64 bytes.\n", len(h.sessionSecret)) } - h.tickets = securecookie.New(h.sessionSecret, nil) + h.tickets = securecookie.New(h.sessionSecret, h.encryptionSecret) + h.tickets.MaxAge(86400 * 30) // 30 days + h.tickets.HashFunc(sha256.New) + h.tickets.BlockFunc(aes.NewCipher) h.buffers = NewBufferCache(1024, bytes.MinRead) h.buddyImages = NewImageCache() h.tokenName = fmt.Sprintf("token@%s", h.realm) - h.contacts = securecookie.New(h.sessionSecret, nil) + h.contacts = securecookie.New(h.sessionSecret, h.encryptionSecret) h.contacts.MaxAge(0) + h.contacts.HashFunc(sha256.New) + h.contacts.BlockFunc(aes.NewCipher) return h } diff --git a/src/app/spreed-webrtc-server/main.go b/src/app/spreed-webrtc-server/main.go index 46d9eebd..3f1111b3 100644 --- a/src/app/spreed-webrtc-server/main.go +++ b/src/app/spreed-webrtc-server/main.go @@ -211,6 +211,11 @@ func runner(runtime phoenix.Runtime) error { return fmt.Errorf("No sessionSecret in config file.") } + encryptionSecret, err := runtime.GetString("app", "encryptionSecret") + if err != nil { + return fmt.Errorf("No encryptionSecret in config file.") + } + tokenFile, err := runtime.GetString("app", "tokenFile") if err == nil { if !httputils.HasFilePath(path.Clean(tokenFile)) { @@ -340,7 +345,7 @@ func runner(runtime phoenix.Runtime) error { computedRealm := fmt.Sprintf("%s.%s", serverRealm, serverToken) // Create our hub instance. - hub := NewHub(runtimeVersion, config, sessionSecret, turnSecret, computedRealm) + hub := NewHub(runtimeVersion, config, sessionSecret, encryptionSecret, turnSecret, computedRealm) // Set number of go routines if it is 1 if goruntime.GOMAXPROCS(0) == 1 { From 790b1c12b56124cdb1ad3b34b14060c269c7ebca Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 15 Jun 2014 14:08:53 +0200 Subject: [PATCH 37/55] Implemented secure userid. --- doc/CHANNELING-API.txt | 28 ++++++++++++---------- src/app/spreed-webrtc-server/channeling.go | 1 + src/app/spreed-webrtc-server/hub.go | 9 +++++++ src/app/spreed-webrtc-server/server.go | 1 + src/app/spreed-webrtc-server/session.go | 13 ---------- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index 1f23cef4..4dbd288e 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -87,6 +87,7 @@ Special purpose documents for channling "Id": "4", "Sid": "5157", "Userid": "", + "Suserid": "", "Token": "some-very-long-string", "Version": "server-version-number", "Turn": { @@ -108,20 +109,21 @@ Special purpose documents for channling Keys: - Type : Self (string) - Id : Public Session id for this connection (string). - Sid : Secure (non public) id for this session (string). - Userid : User id if this session belongs to an authenticated user. Else empty. - Token : Security token (string), to restablish connection with the same + Type : Self (string) + Id : Public Session id for this connection (string). + Sid : Secure (non public) id for this session (string). + Userid : User id if this session belongs to an authenticated user. Else empty. + Suserid : Secure (non public) user id if session has an user id. Else empty. + Token : Security token (string), to restablish connection with the same session. Pass the value as URL query parameter t, to the websocket URL. - Version: Server version number. Use this to detect server upgrades. - Turn : Mapping (interface{}) to contain TURN server details, like - urls, password and username. See - http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 - and TURN REST API section in - https://code.google.com/p/rfc5766-turn-server/wiki/turnserver - for details. - Stun : Array with STUN server URLs. + Version : Server version number. Use this to detect server upgrades. + Turn : Mapping (interface{}) to contain TURN server details, like + urls, password and username. See + http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 + and TURN REST API section in + https://code.google.com/p/rfc5766-turn-server/wiki/turnserver + for details. + Stun : Array with STUN server URLs. You can also send an empty Self document to the server to make the server transmit a fresh Self document (eg. to refresh when ttl was reached). Please diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index c07c3753..ef1209be 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -50,6 +50,7 @@ type DataSelf struct { Id string Sid string Userid string + Suserid string Token string Version string Turn *DataTurn diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index 4972632f..1be5d7cd 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -177,6 +177,15 @@ func (h *Hub) CreateTurnData(id string) *DataTurn { } +func (h *Hub) CreateSuserid(session *Session) (suserid string) { + if session.Userid != "" { + m := hmac.New(sha256.New, h.encryptionSecret) + m.Write([]byte(session.Userid)) + suserid = base64.StdEncoding.EncodeToString(m.Sum(nil)) + } + return suserid +} + func (h *Hub) CreateSession(request *http.Request, st *SessionToken) *Session { // NOTE(longsleep): Is it required to make this a secure cookie, diff --git a/src/app/spreed-webrtc-server/server.go b/src/app/spreed-webrtc-server/server.go index 359ca52a..4cca4e73 100644 --- a/src/app/spreed-webrtc-server/server.go +++ b/src/app/spreed-webrtc-server/server.go @@ -45,6 +45,7 @@ func (s *Server) OnRegister(c *Connection) { Id: c.Id, Sid: c.Session.Sid, Userid: c.Session.Userid, + Suserid: c.h.CreateSuserid(c.Session), Token: token, Version: c.h.version, Turn: c.h.CreateTurnData(c.Id), diff --git a/src/app/spreed-webrtc-server/session.go b/src/app/spreed-webrtc-server/session.go index 13dfe2a2..1ba35e9b 100644 --- a/src/app/spreed-webrtc-server/session.go +++ b/src/app/spreed-webrtc-server/session.go @@ -76,19 +76,6 @@ func (s *Session) Update(update *SessionUpdate) uint64 { } -func (s *Session) Apply(st *SessionToken) uint64 { - - s.mutex.Lock() - defer s.mutex.Unlock() - s.Id = st.Id - s.Sid = st.Sid - s.Userid = st.Userid - - s.UpdateRev++ - return s.UpdateRev - -} - func (s *Session) Authorize(realm string, st *SessionToken) (string, error) { s.mutex.Lock() From 67afd5389974002d65c4706bda5786f7d51ab2f3 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 15 Jun 2014 16:13:44 +0200 Subject: [PATCH 38/55] Store contacts encrypted. --- .../js/controllers/mediastreamcontroller.js | 10 ++- static/js/services/buddysession.js | 2 +- static/js/services/contacts.js | 67 +++++++++++++++---- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/static/js/controllers/mediastreamcontroller.js b/static/js/controllers/mediastreamcontroller.js index e1c1f655..99b8f80a 100644 --- a/static/js/controllers/mediastreamcontroller.js +++ b/static/js/controllers/mediastreamcontroller.js @@ -123,6 +123,7 @@ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function $scope.status = "initializing"; $scope.id = null; $scope.userid = null; + $scope.suserid = null; $scope.peer = null; $scope.dialing = null; $scope.conference = null; @@ -370,6 +371,7 @@ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function safeApply($scope, function(scope) { scope.id = scope.myid = data.Id; scope.userid = data.Userid; + scope.suserid = data.Suserid; scope.turn = data.Turn; scope.stun = data.Stun; scope.refreshWebrtcSettings(); @@ -547,7 +549,7 @@ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function if (opts.soft) { return; } - $scope.userid = null; + $scope.userid = $scope.suserid = null; break; case "error": if (reconnecting || connected) { @@ -623,10 +625,12 @@ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function }); $scope.$watch("userid", function(userid) { + var suserid; if (userid) { - console.info("Session is now authenticated:", userid); + suserid = $scope.suserid; + console.info("Session is now authenticated:", userid, suserid); } - appData.e.triggerHandler("authenticationChanged", [userid]); + appData.e.triggerHandler("authenticationChanged", [userid, suserid]); }); // Apply all layout stuff as classes to our element. diff --git a/static/js/services/buddysession.js b/static/js/services/buddysession.js index 7d153c51..d45fcd86 100644 --- a/static/js/services/buddysession.js +++ b/static/js/services/buddysession.js @@ -45,7 +45,7 @@ define(["underscore"], function(_) { } } } else { - this.set({}); + this.use(null, {}); } }; diff --git a/static/js/services/contacts.js b/static/js/services/contacts.js index 7c9f1faf..b9a49e6c 100644 --- a/static/js/services/contacts.js +++ b/static/js/services/contacts.js @@ -120,16 +120,17 @@ define(['underscore', 'jquery', 'modernizr', 'sjcl'], function(underscore, $, Mo }; // contacts - return ["appData", "contactData", function(appData, contactData) { + return ["appData", "contactData", "mediaStream", function(appData, contactData, mediaStream) { var Contacts = function() { this.e = $({}); this.userid = null; + this.key = null; this.database = null; - appData.e.on("authenticationChanged", _.bind(function(event, userid) { - var database = this.open(userid); + appData.e.on("authenticationChanged", _.bind(function(event, userid, suserid) { + var database = this.open(userid, suserid); if (database && userid) { // TODO(longsleep): This needs to be delayed util self has ha userid. if (database.ready) { @@ -150,7 +151,7 @@ define(['underscore', 'jquery', 'modernizr', 'sjcl'], function(underscore, $, Mo }; - Contacts.prototype.open = function(userid) { + Contacts.prototype.open = function(userid, suserid) { if (this.database && (!userid || this.userid !== userid)) { // Unload existing contacts. @@ -163,9 +164,10 @@ define(['underscore', 'jquery', 'modernizr', 'sjcl'], function(underscore, $, Mo if (!Modernizr.indexeddb) { return; } - // Create HMAC database name for user. - var hmac = new sjcl.misc.hmac('mediastream'); - var id = "mediastream-"+sjcl.codec.base64.fromBits(hmac.encrypt(userid)); + // Create secure key for hashing and encryption. + this.key = sjcl.codec.base64.fromBits(sjcl.hash.sha256.hash(suserid+mediaStream.config.Token)); + // Create database name for user which. + var id = "mediastream-" + this.id(userid); console.log("Open of database:", id); var database = this.database = new Database(id); return database; @@ -176,14 +178,53 @@ define(['underscore', 'jquery', 'modernizr', 'sjcl'], function(underscore, $, Mo }; + Contacts.prototype.id = function(userid) { + + var hmac = new sjcl.misc.hmac(this.key); + return sjcl.codec.base64.fromBits(hmac.encrypt(userid)); + + }; + + Contacts.prototype.encrypt = function(data) { + + return sjcl.encrypt(this.key, JSON.stringify(data)); + + }; + + Contacts.prototype.decrypt = function(data) { + + var result; + try { + var s = sjcl.decrypt(this.key, data); + result = JSON.parse(s); + } catch(err) { + console.error("Failed to decrypt contact data", err); + } + return result; + + }; + Contacts.prototype.load = function() { if (this.database) { console.log("Load contacts from storage", this); + var remove = []; this.database.all("contacts", _.bind(function(data) { - var contact = contactData.addByData(data.contact); - // TODO(longsleep): Convert buddyImage string to Blob. - this.e.triggerHandler("contactadded", contact); + var d = this.decrypt(data.contact); + if (d) { + var contact = contactData.addByData(d); + // TODO(longsleep): Convert buddyImage string to Blob. + this.e.triggerHandler("contactadded", d); + } else { + // Remove empty or invalid entries automatically. + remove.push(data.id); + } }, this)); + // Remove marked entries. + if (remove.length) { + _.each(remove, _.bind(function(id) { + this.database.delete("contacts", id); + }, this)); + } } }; @@ -198,8 +239,8 @@ define(['underscore', 'jquery', 'modernizr', 'sjcl'], function(underscore, $, Mo this.e.triggerHandler("contactadded", contact); if (this.database) { this.database.put("contacts", { - id: contact.Userid, - contact: contact + id: this.id(contact.Userid), + contact: this.encrypt(contact) }) } }; @@ -210,7 +251,7 @@ define(['underscore', 'jquery', 'modernizr', 'sjcl'], function(underscore, $, Mo if (contact) { contactData.remove(userid); if (this.database) { - this.database.delete("contacts", userid); + this.database.delete("contacts", this.id(userid)); } this.e.triggerHandler("contactremoved", contact); } From 293f938754c5eb68737aed0eedafb9153ebda8bc Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 15 Jun 2014 16:19:57 +0200 Subject: [PATCH 39/55] Cleanups. --- static/js/services/contacts.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/js/services/contacts.js b/static/js/services/contacts.js index b9a49e6c..4f900221 100644 --- a/static/js/services/contacts.js +++ b/static/js/services/contacts.js @@ -130,9 +130,10 @@ define(['underscore', 'jquery', 'modernizr', 'sjcl'], function(underscore, $, Mo this.database = null; appData.e.on("authenticationChanged", _.bind(function(event, userid, suserid) { + // TODO(longsleep): Avoid creating empty databases. Create db on store only. var database = this.open(userid, suserid); if (database && userid) { - // TODO(longsleep): This needs to be delayed util self has ha userid. + // Load when database is ready and userid is available. if (database.ready) { _.defer(_.bind(function() { if (this.database === database) { From 28ad495a63a2a57cbfa3d3c86b9ab23c0c7a520f Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 15 Jun 2014 18:15:16 +0200 Subject: [PATCH 40/55] Added service to build requestAnimationFrame calls. --- static/js/directives/audiolevel.js | 26 +++++++------- static/js/directives/audiovideo.js | 8 ++--- static/js/services/animationframe.js | 51 ++++++++++++++++++++++++++++ static/js/services/buddylist.js | 9 ++--- static/js/services/services.js | 9 +++-- 5 files changed, 76 insertions(+), 27 deletions(-) create mode 100644 static/js/services/animationframe.js diff --git a/static/js/directives/audiolevel.js b/static/js/directives/audiolevel.js index e8f91d02..74fd62d9 100644 --- a/static/js/directives/audiolevel.js +++ b/static/js/directives/audiolevel.js @@ -18,11 +18,10 @@ * along with this program. If not, see . * */ -define(['jquery', 'underscore', 'rAF'], function($, _) { +define(['jquery', 'underscore'], function($, _) { - return ["$window", "mediaStream", "safeApply", function($window, mediaStream, safeApply) { + return ["$window", "mediaStream", "safeApply", "animationFrame", function($window, mediaStream, safeApply, animationFrame) { - var requestAnimationFrame = $window.requestAnimationFrame; var webrtc = mediaStream.webrtc; // Consider anyting lower than this % as no audio. @@ -43,21 +42,22 @@ define(['jquery', 'underscore', 'rAF'], function($, _) { // Own audio level indicator. var element = $element[0]; + var width = 0; this.update = _.bind(function() { - if (this.active) { - requestAnimationFrame(this.update); - } - var width = 0; - if (webrtc.usermedia.audioLevel) { - width = Math.round(100 * webrtc.usermedia.audioLevel); - // Hide low volumes. - if (width < threshhold) { + if (this.active || width > 0) { + if (webrtc.usermedia.audioLevel) { + width = Math.round(100 * webrtc.usermedia.audioLevel); + // Hide low volumes. + if (width < threshhold) { + width = 0; + } + } else { width = 0; } + element.style.width = width + '%'; } - element.style.width = width + '%'; }, this); - this.update(); + animationFrame.register(this.update); // Talking state. this.audioActivityHistory = []; diff --git a/static/js/directives/audiovideo.js b/static/js/directives/audiovideo.js index 160b12c1..60243c1d 100644 --- a/static/js/directives/audiovideo.js +++ b/static/js/directives/audiovideo.js @@ -18,11 +18,10 @@ * along with this program. If not, see . * */ -define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/audiovideopeer.html', 'bigscreen', 'injectCSS', 'webrtc.adapter', 'rAF'], function($, _, template, templatePeer, BigScreen) { +define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/audiovideopeer.html', 'bigscreen', 'injectCSS', 'webrtc.adapter'], function($, _, template, templatePeer, BigScreen) { - return ["$window", "$compile", "$filter", "mediaStream", "safeApply", "desktopNotify", "buddyData", "videoWaiter", "videoLayout", function($window, $compile, $filter, mediaStream, safeApply, desktopNotify, buddyData, videoWaiter, videoLayout) { + return ["$window", "$compile", "$filter", "mediaStream", "safeApply", "desktopNotify", "buddyData", "videoWaiter", "videoLayout", "animationFrame", function($window, $compile, $filter, mediaStream, safeApply, desktopNotify, buddyData, videoWaiter, videoLayout, animationFrame) { - var requestAnimationFrame = $window.requestAnimationFrame; var peerTemplate = $compile(templatePeer); var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { @@ -316,9 +315,8 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ needsRedraw = false; redraw(); } - requestAnimationFrame(update); } - _.defer(update); + animationFrame.register(update); } diff --git a/static/js/services/animationframe.js b/static/js/services/animationframe.js new file mode 100644 index 00000000..dd6f43b4 --- /dev/null +++ b/static/js/services/animationframe.js @@ -0,0 +1,51 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +define(["underscore", "rAF"], function(_) { + + // animationFrame + return ["$window", function($window) { + + var requestAnimationFrame = $window.requestAnimationFrame; + var registry = []; + + var caller = function(f) { + f(); + }; + var worker = function() { + registry.forEach(caller) + requestAnimationFrame(worker); + }; + + // Public api. + var animationFrame = { + register: function(f) { + registry.push(f); + } + }; + + // Auto start worker. + _.defer(worker); + + return animationFrame; + + }]; + +}); diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 71666833..ffb81475 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -18,7 +18,7 @@ * along with this program. If not, see . * */ -define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text!partials/buddyactions.html', 'text!partials/buddyactionsforaudiomixer.html', 'rAF'], function(_, Modernizr, AvlTree, templateBuddy, templateBuddyActions, templateBuddyActionsForAudioMixer) { +define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text!partials/buddyactions.html', 'text!partials/buddyactionsforaudiomixer.html'], function(_, Modernizr, AvlTree, templateBuddy, templateBuddyActions, templateBuddyActionsForAudioMixer) { var BuddyTree = function() { @@ -129,9 +129,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; // buddyList - return ["$window", "$compile", "playSound", "buddyData", "buddySession", "fastScroll", "mediaStream", function($window, $compile, playSound, buddyData, buddySession, fastScroll, mediaStream) { - - var requestAnimationFrame = $window.requestAnimationFrame; + return ["$window", "$compile", "playSound", "buddyData", "buddySession", "fastScroll", "mediaStream", "animationFrame", function($window, $compile, playSound, buddyData, buddySession, fastScroll, mediaStream, animationFrame) { var buddyTemplate = $compile(templateBuddy); var buddyActions = $compile(templateBuddyActions); @@ -180,9 +178,8 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! $window.setInterval(_.bind(this.soundLoop, this), 500); var update = _.bind(function refreshBuddies() { this.refreshBuddies(); - requestAnimationFrame(update); }, this); - requestAnimationFrame(update); + animationFrame.register(update); }; diff --git a/static/js/services/services.js b/static/js/services/services.js index 4bc5852b..c95a5205 100644 --- a/static/js/services/services.js +++ b/static/js/services/services.js @@ -47,7 +47,8 @@ define([ 'services/contactdata', 'services/contacts', 'services/buddysession', - 'services/localstorage'], function(_, + 'services/localstorage', + 'services/animationframe'], function(_, desktopNotify, playSound, safeApply, @@ -74,7 +75,8 @@ videoLayout, contactData, contacts, buddySession, -localStorage) { +localStorage, +animationFrame) { var services = { desktopNotify: desktopNotify, @@ -103,7 +105,8 @@ localStorage) { contactData: contactData, contacts: contacts, buddySession: buddySession, - localStorage: localStorage + localStorage: localStorage, + animationFrame: animationFrame }; var initialize = function(angModule) { From f8866057550de23e84efeb160b4677c3bb736595 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 15 Jun 2014 18:51:13 +0200 Subject: [PATCH 41/55] Removed debug. --- src/app/spreed-webrtc-server/server.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/spreed-webrtc-server/server.go b/src/app/spreed-webrtc-server/server.go index 4cca4e73..f6da1486 100644 --- a/src/app/spreed-webrtc-server/server.go +++ b/src/app/spreed-webrtc-server/server.go @@ -145,7 +145,6 @@ func (s *Server) OnText(c *Connection, b Buffer) { return } msg.Chat.Chat.Status.ContactRequest.Userid = c.Session.Userid - log.Println("CCCCCCCC", msg.Chat.Chat.Status.ContactRequest) } atomic.AddUint64(&c.h.unicastChatMessages, 1) s.Unicast(c, msg.Chat.To, msg.Chat) From 19cc75c0fe43c8f1e46ff2fdf60ebe34545e7fcd Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 15 Jun 2014 22:44:21 +0200 Subject: [PATCH 42/55] Implemented server sessions lookup. --- doc/CHANNELING-API.txt | 26 ++++-- src/app/spreed-webrtc-server/channeling.go | 12 ++- src/app/spreed-webrtc-server/hub.go | 93 +++++++++++++++++++++- src/app/spreed-webrtc-server/server.go | 12 ++- src/app/spreed-webrtc-server/session.go | 34 ++++---- src/app/spreed-webrtc-server/user.go | 90 +++++++++++++++++++++ 6 files changed, 236 insertions(+), 31 deletions(-) create mode 100644 src/app/spreed-webrtc-server/user.go diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index 4dbd288e..6cde44fa 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -392,23 +392,35 @@ Information retrieval "Sessions": { "Id": "Client generated request ID", "Token": "Request token", - "TokenType": "Token type" + "Type": "Token type" } } - Valid known token types are: "contact", "token". + Valid known token types are: "contact". - Sesions (Response with Id, Token and Type from request and + Sessions (Response with Id, Token and Type from request and populated Session list). { "Type": "Sessions", "Sessions": { "Id": "ID as from request", - "Token": "Token as from request", - "TokenType": "Type as from request", - "Sessions": [ - "1", "2", "3", ... + "Token": "Token as in request", + "Type": "Type as in request", + "Users": [ + { + "Type": "Online", + "Id": "1", + "Ua": "Firefox 27", + "Status": {...} + }, + { + "Type": "Online", + "Id": "3", + "Userid": "u3", + "Ua": "Chrome 28", + "Status": {...} + }, ... ] } } diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index ef1209be..db3ed4ae 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -75,6 +75,11 @@ type DataSession struct { Status interface{} } +type DataUser struct { + Id string + Sessions int +} + type DataBye struct { Type string To string @@ -142,6 +147,7 @@ type DataIncoming struct { Conference *DataConference Alive *DataAlive Authentication *DataAuthentication + Sessions *DataSessions } type DataOutgoing struct { @@ -153,8 +159,10 @@ type DataOutgoing struct { type DataSessions struct { Type string Users []*DataSession - Index uint64 - Batch uint64 + Id string `json:",omitempty"` + Token string `json:",omitempty"` + Index uint64 `json:",omitempty"` + Batch uint64 `json:",omitempty"` } type DataConference struct { diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index 1be5d7cd..448cd1b6 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -57,11 +57,13 @@ type HubStat struct { Rooms int `json:"rooms"` Connections int `json:"connections"` Sessions int `json:"sessions"` + Users int `json:"users"` Count uint64 `json:"count"` BroadcastChatMessages uint64 `json:"broadcastchatmessages"` UnicastChatMessages uint64 `json:"unicastchatmessages"` IdsInRoom map[string][]string `json:"idsinroom,omitempty"` SessionsById map[string]*DataSession `json:"sessionsbyid,omitempty"` + UsersById map[string]*DataUser `json:"usersbyid,omitempty"` ConnectionsByIdx map[string]string `json:"connectionsbyidx,omitempty"` } @@ -70,6 +72,7 @@ type Hub struct { connectionTable map[string]*Connection sessionTable map[string]*Session roomTable map[string]*RoomWorker + userTable map[string]*User version string config *Config sessionSecret []byte @@ -94,6 +97,7 @@ func NewHub(version string, config *Config, sessionSecret, encryptionSecret, tur connectionTable: make(map[string]*Connection), sessionTable: make(map[string]*Session), roomTable: make(map[string]*RoomWorker), + userTable: make(map[string]*User), version: version, config: config, sessionSecret: []byte(sessionSecret), @@ -128,6 +132,7 @@ func (h *Hub) Stat(details bool) *HubStat { Rooms: len(h.roomTable), Connections: len(h.connectionTable), Sessions: len(h.sessionTable), + Users: len(h.userTable), Count: h.count, BroadcastChatMessages: atomic.LoadUint64(&h.broadcastChatMessages), UnicastChatMessages: atomic.LoadUint64(&h.unicastChatMessages), @@ -147,6 +152,11 @@ func (h *Hub) Stat(details bool) *HubStat { sessions[sessionid] = session.Data() } stat.SessionsById = sessions + users := make(map[string]*DataUser) + for userid, user := range h.userTable { + users[userid] = user.Data() + } + stat.UsersById = users connections := make(map[string]string) for id, connection := range h.connectionTable { connections[fmt.Sprintf("%d", connection.Idx)] = id @@ -203,8 +213,8 @@ func (h *Hub) CreateSession(request *http.Request, st *SessionToken) *Session { if st == nil { sid := NewRandomString(32) id, _ := h.tickets.Encode("id", sid) - session = NewSession(id, sid, userid) - log.Println("Created new session id", len(id), id, sid, userid) + session = NewSession(id, sid) + log.Println("Created new session id", len(id), id, sid) } else { if userid == "" { userid = st.Userid @@ -212,7 +222,11 @@ func (h *Hub) CreateSession(request *http.Request, st *SessionToken) *Session { if !usersEnabled { userid = "" } - session = NewSession(st.Id, st.Sid, userid) + session = NewSession(st.Id, st.Sid) + } + + if userid != "" { + h.authenticateHandler(session, st, userid) } return session @@ -359,6 +373,15 @@ func (h *Hub) unregisterHandler(c *Connection) { session := c.Session delete(h.connectionTable, c.Id) delete(h.sessionTable, c.Id) + if session != nil && session.Userid != "" { + user, ok := h.userTable[session.Userid] + if ok { + empty := user.RemoveSession(session) + if empty { + delete(h.userTable, session.Userid) + } + } + } h.mutex.Unlock() if session != nil { h.buddyImages.Delete(session.Id) @@ -397,6 +420,51 @@ func (h *Hub) aliveHandler(c *Connection, alive *DataAlive) { } +func (h *Hub) sessionsHandler(c *Connection, sessions *DataSessions) { + + reply := false + + switch sessions.Type { + case "contact": + contact := &Contact{} + err := h.contacts.Decode("contactConfirmed", sessions.Token, contact) + if err != nil { + log.Println("Failed to decode incoming contact token", err) + return + } + if contact.A != c.Session.Userid && contact.B != c.Session.Userid { + log.Println("Ignoring foreign contact token") + return + } + h.mutex.RLock() + defer h.mutex.RUnlock() + user, ok := h.userTable[c.Session.Userid] + if !ok { + return + } + sessions.Users = user.SessionsData() + reply = true + default: + log.Println("Unkown incoming sessions request type", sessions.Type) + } + + if reply { + + sessionsJson := h.buffers.New() + encoder := json.NewEncoder(sessionsJson) + err := encoder.Encode(&DataOutgoing{From: c.Id, Data: sessions}) + if err != nil { + log.Println("Sessions error while encoding JSON", err) + sessionsJson.Decref() + return + } + c.send(sessionsJson) + sessionsJson.Decref() + + } + +} + func (h *Hub) sessionupdateHandler(s *SessionUpdate) uint64 { //fmt.Println("Userupdate", u) @@ -444,6 +512,25 @@ func (h *Hub) sessiontokenHandler(st *SessionToken) (string, error) { } +func (h *Hub) authenticateHandler(session *Session, st *SessionToken, userid string) error { + + err := session.Authenticate(h.realm, st, userid) + if err == nil { + // Authentication success. + h.mutex.Lock() + user, ok := h.userTable[session.Userid] + if !ok { + user = NewUser(session.Userid) + h.userTable[session.Userid] = user + } + h.mutex.Unlock() + user.AddSession(session) + } + + return err + +} + func (h *Hub) contactrequestHandler(c *Connection, to string, cr *DataContactRequest) error { var err error diff --git a/src/app/spreed-webrtc-server/server.go b/src/app/spreed-webrtc-server/server.go index f6da1486..9c7668de 100644 --- a/src/app/spreed-webrtc-server/server.go +++ b/src/app/spreed-webrtc-server/server.go @@ -168,6 +168,8 @@ func (s *Server) OnText(c *Connection, b Buffer) { } case "Alive": s.Alive(c, msg.Alive) + case "Sessions": + s.Sessions(c, msg.Sessions) default: log.Println("OnText unhandled message type", msg.Type) } @@ -196,6 +198,12 @@ func (s *Server) Alive(c *Connection, alive *DataAlive) { } +func (s *Server) Sessions(c *Connection, sessions *DataSessions) { + + c.h.sessionsHandler(c, sessions) + +} + func (s *Server) UpdateSession(c *Connection, su *SessionUpdate) uint64 { su.Id = c.Id @@ -243,9 +251,9 @@ func (s *Server) Users(c *Connection) { func (s *Server) Authenticate(c *Connection, st *SessionToken) bool { - err := c.Session.Authenticate(c.h.realm, st) + err := c.h.authenticateHandler(c.Session, st, "") if err == nil { - log.Println("Authentication success", c.Id, c.Idx, st.Userid) + log.Println("Authentication success", c.Id, c.Idx, c.Session.Userid) return true } else { log.Println("Authentication failed", err, c.Id, c.Idx, st.Userid, st.Nonce) diff --git a/src/app/spreed-webrtc-server/session.go b/src/app/spreed-webrtc-server/session.go index 1ba35e9b..0d6f4fdf 100644 --- a/src/app/spreed-webrtc-server/session.go +++ b/src/app/spreed-webrtc-server/session.go @@ -42,12 +42,11 @@ type Session struct { mutex sync.RWMutex } -func NewSession(id, sid, userid string) *Session { +func NewSession(id, sid string) *Session { return &Session{ - Id: id, - Sid: sid, - Userid: userid, + Id: id, + Sid: sid, } } @@ -96,7 +95,7 @@ func (s *Session) Authorize(realm string, st *SessionToken) (string, error) { } -func (s *Session) Authenticate(realm string, st *SessionToken) error { +func (s *Session) Authenticate(realm string, st *SessionToken, userid string) error { s.mutex.Lock() defer s.mutex.Unlock() @@ -104,20 +103,21 @@ func (s *Session) Authenticate(realm string, st *SessionToken) error { if s.Userid != "" { return errors.New("session already authenticated") } - if s.Nonce == "" || s.Nonce != st.Nonce { - return errors.New("nonce validation failed") - } - var userid string - err := sessionNonces.Decode(fmt.Sprintf("%s@%s", s.Sid, realm), st.Nonce, &userid) - if err != nil { - return err - } - if st.Userid != userid { - return errors.New("user id mismatch") + if userid == "" { + if s.Nonce == "" || s.Nonce != st.Nonce { + return errors.New("nonce validation failed") + } + err := sessionNonces.Decode(fmt.Sprintf("%s@%s", s.Sid, realm), st.Nonce, &userid) + if err != nil { + return err + } + if st.Userid != userid { + return errors.New("user id mismatch") + } + s.Nonce = "" } - s.Nonce = "" - s.Userid = st.Userid + s.Userid = userid s.UpdateRev++ return nil diff --git a/src/app/spreed-webrtc-server/user.go b/src/app/spreed-webrtc-server/user.go new file mode 100644 index 00000000..fc041bc0 --- /dev/null +++ b/src/app/spreed-webrtc-server/user.go @@ -0,0 +1,90 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "fmt" + "sync" +) + +type User struct { + Id string + sessionTable map[string]*Session + mutex sync.RWMutex +} + +func NewUser(id string) *User { + + user := &User{ + Id: id, + sessionTable: make(map[string]*Session), + } + return user + +} + +// Return true if first session. +func (u *User) AddSession(s *Session) bool { + first := false + u.mutex.Lock() + u.sessionTable[s.Id] = s + if len(u.sessionTable) == 1 { + fmt.Println("First session registered for user", u.Id) + first = true + } + u.mutex.Unlock() + return first +} + +// Return true if no session left. +func (u *User) RemoveSession(s *Session) bool { + last := false + u.mutex.Lock() + delete(u.sessionTable, s.Id) + if len(u.sessionTable) == 0 { + fmt.Println("Last session unregistered for user", u.Id) + last = true + } + u.mutex.Unlock() + return last +} + +func (u *User) Data() *DataUser { + u.mutex.RLock() + defer u.mutex.RUnlock() + return &DataUser{ + Id: u.Id, + Sessions: len(u.sessionTable), + } +} + +func (u *User) SessionsData() []*DataSession { + + sessions := make([]*DataSession, 0, len(u.sessionTable)) + u.mutex.RLock() + defer u.mutex.RUnlock() + for _, session := range u.sessionTable { + sessions = append(sessions, session.Data()) + } + return sessions + +} From e5aeda31d9f5ab730f15c475fd7d7df1a1fa7270 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 15 Jun 2014 23:17:59 +0200 Subject: [PATCH 43/55] Fixed userid matching for sessions lookup. --- src/app/spreed-webrtc-server/hub.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index 448cd1b6..88c8737e 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -432,16 +432,25 @@ func (h *Hub) sessionsHandler(c *Connection, sessions *DataSessions) { log.Println("Failed to decode incoming contact token", err) return } - if contact.A != c.Session.Userid && contact.B != c.Session.Userid { - log.Println("Ignoring foreign contact token") + // Use the userid which is not ours from the contact data. + var userid string + if contact.A == c.Session.Userid { + userid = contact.B + } else if contact.B == c.Session.Userid { + userid = contact.A + } + if userid == "" { + log.Println("Ignoring foreign contact token", contact.A, contact.B) return } + // Find foreign user. h.mutex.RLock() defer h.mutex.RUnlock() - user, ok := h.userTable[c.Session.Userid] + user, ok := h.userTable[userid] if !ok { return } + // Add sessions for forein user. sessions.Users = user.SessionsData() reply = true default: @@ -449,7 +458,6 @@ func (h *Hub) sessionsHandler(c *Connection, sessions *DataSessions) { } if reply { - sessionsJson := h.buffers.New() encoder := json.NewEncoder(sessionsJson) err := encoder.Encode(&DataOutgoing{From: c.Id, Data: sessions}) @@ -460,7 +468,6 @@ func (h *Hub) sessionsHandler(c *Connection, sessions *DataSessions) { } c.send(sessionsJson) sessionsJson.Decref() - } } From e6f2182e49a9cb9f49faff5f7b4278b7c8e28e20 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Mon, 16 Jun 2014 00:04:59 +0200 Subject: [PATCH 44/55] Implemented client side sessions request. --- doc/CHANNELING-API.txt | 46 ++++++++++------------ src/app/spreed-webrtc-server/channeling.go | 25 ++++++------ src/app/spreed-webrtc-server/hub.go | 8 ++-- static/js/mediastream/api.js | 13 ++++++ static/js/services/buddylist.js | 4 ++ 5 files changed, 55 insertions(+), 41 deletions(-) diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index 6cde44fa..edadbd2c 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -389,11 +389,9 @@ Information retrieval { "Type": "Sessions", - "Sessions": { - "Id": "Client generated request ID", - "Token": "Request token", - "Type": "Token type" - } + "Id": "Client generated request ID", + "Token": "Request token", + "TokenType": "Token type" } Valid known token types are: "contact". @@ -403,26 +401,24 @@ Information retrieval { "Type": "Sessions", - "Sessions": { - "Id": "ID as from request", - "Token": "Token as in request", - "Type": "Type as in request", - "Users": [ - { - "Type": "Online", - "Id": "1", - "Ua": "Firefox 27", - "Status": {...} - }, - { - "Type": "Online", - "Id": "3", - "Userid": "u3", - "Ua": "Chrome 28", - "Status": {...} - }, ... - ] - } + "Id": "ID as from request", + "Token": "Token as in request", + "TokenType": "Type as in request", + "Users": [ + { + "Type": "Online", + "Id": "1", + "Ua": "Firefox 27", + "Status": {...} + }, + { + "Type": "Online", + "Id": "3", + "Userid": "u3", + "Ua": "Chrome 28", + "Status": {...} + }, ... + ] } diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index db3ed4ae..00a19aea 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -65,13 +65,13 @@ type DataTurn struct { } type DataSession struct { - Type string Id string - Userid string `json:"Userid,omitempty"` - Ua string `json:"Ua,omitempty"` - Token string `json:"Token,omitempty"` - Version string `json:"Version,omitempty"` - Rev uint64 `json:"Rev,omitempty"` + Type string `json:",omitempty"` + Userid string `json:",omitempty"` + Ua string `json:",omitempty"` + Token string `json:",omitempty"` + Version string `json:",omitempty"` + Rev uint64 `json:",omitempty"` Status interface{} } @@ -157,12 +157,13 @@ type DataOutgoing struct { } type DataSessions struct { - Type string - Users []*DataSession - Id string `json:",omitempty"` - Token string `json:",omitempty"` - Index uint64 `json:",omitempty"` - Batch uint64 `json:",omitempty"` + Type string + Users []*DataSession + Id string `json:",omitempty"` + Token string `json:",omitempty"` + TokenType string `json:",omitempty"` + Index uint64 `json:",omitempty"` + Batch uint64 `json:",omitempty"` } type DataConference struct { diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index 88c8737e..a16ba59f 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -424,12 +424,12 @@ func (h *Hub) sessionsHandler(c *Connection, sessions *DataSessions) { reply := false - switch sessions.Type { + switch sessions.TokenType { case "contact": contact := &Contact{} - err := h.contacts.Decode("contactConfirmed", sessions.Token, contact) + err := h.contacts.Decode("contactRequest", sessions.Token, contact) if err != nil { - log.Println("Failed to decode incoming contact token", err) + log.Println("Failed to decode incoming contact token", err, sessions.Token) return } // Use the userid which is not ours from the contact data. @@ -454,7 +454,7 @@ func (h *Hub) sessionsHandler(c *Connection, sessions *DataSessions) { sessions.Users = user.SessionsData() reply = true default: - log.Println("Unkown incoming sessions request type", sessions.Type) + log.Println("Unkown incoming sessions request type", sessions.TokenType) } if reply { diff --git a/static/js/mediastream/api.js b/static/js/mediastream/api.js index 7ed60a44..756ac4ac 100644 --- a/static/js/mediastream/api.js +++ b/static/js/mediastream/api.js @@ -321,6 +321,19 @@ define(['jquery', 'underscore'], function($, _) { return this.send("Alive", data); }; + Api.prototype.sendSessions = function(token, type) { + + var data = { + Type: "Sessions", + Id: "some-random-whatever", + Token: token, + TokenType: type + } + + return this.send("Sessions", data); + + }; + return Api; }); diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index ffb81475..6378b778 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -664,6 +664,10 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! if (!sessionData) { // TODO(longsleep): Find session with help of contact. console.log("No sessions for this buddy.", session, contact); + if (contact && contact.Token) { + mediaStream.api.sendSessions(contact.Token, "contact"); + } + return; } else { id = sessionData.Id; } From 3204c329bcb61429216926d463a301550c008247 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Mon, 16 Jun 2014 15:38:00 +0200 Subject: [PATCH 45/55] Implementd client side lookup support. --- doc/CHANNELING-API.txt | 31 ++++++++----- src/app/spreed-webrtc-server/channeling.go | 19 ++++---- src/app/spreed-webrtc-server/hub.go | 28 ++++++----- src/app/spreed-webrtc-server/server.go | 14 +++--- static/js/directives/audiolevel.js | 2 +- static/js/mediastream/api.js | 36 ++++++++++++--- static/js/services/buddylist.js | 54 +++++++++++++--------- 7 files changed, 115 insertions(+), 69 deletions(-) diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index edadbd2c..6716855e 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -52,11 +52,14 @@ Sending vs receiving document data encapsulation { "Type": "Whatever", - "Whatever": { /* your document */ } + "Whatever": { /* your document */ }, + "Iid": "request-identifier-unique-to-client" } So any document you sent, you have to specify a Type key pointing - to the key where the real document is to be found. + to the key where the real document is to be found. The Iid field is + optional and is returned back with the response wrapper document to + match requests with response data when supported by the type. Received documents are wrapped by a special Document which provides additional information. @@ -64,7 +67,8 @@ Sending vs receiving document data encapsulation { "From": "4", "To": "5", - "Data": {} + "Data": {}, + "Iid": "request-identifier-unique-to-client" } The Data key contains the real Document. @@ -76,6 +80,9 @@ Sending vs receiving document data encapsulation To : The Id, the server send this Document to. Should be the same as your current Self Id. Data : Contains the payload. + Iid : Optional request identifier to match this response to the calling + request. Only available when sent by the client and the requested + type implementation does support it. Special purpose documents for channling @@ -333,9 +340,7 @@ Additional types for session listing and notifications "Ua": "Chrome 28", "Status": {...} } - ], - "Index": 0, - "Batch": 0 + ] } Note: The Userid field is only present, if that session belongs to a known user. @@ -389,9 +394,10 @@ Information retrieval { "Type": "Sessions", - "Id": "Client generated request ID", - "Token": "Request token", - "TokenType": "Token type" + "Sessions": { + "Type": "Token type", + "Token": "Request token" + } } Valid known token types are: "contact". @@ -401,9 +407,10 @@ Information retrieval { "Type": "Sessions", - "Id": "ID as from request", - "Token": "Token as in request", - "TokenType": "Type as in request", + "Sessions": { + "Type": "Type as in request", + "Token": "Token as in request" + }, "Users": [ { "Type": "Online", diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index 00a19aea..608c8e8c 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -65,8 +65,8 @@ type DataTurn struct { } type DataSession struct { + Type string Id string - Type string `json:",omitempty"` Userid string `json:",omitempty"` Ua string `json:",omitempty"` Token string `json:",omitempty"` @@ -148,22 +148,25 @@ type DataIncoming struct { Alive *DataAlive Authentication *DataAuthentication Sessions *DataSessions + Iid string `json:",omitempty"` } type DataOutgoing struct { Data interface{} From string To string + Iid string `json:",omitempty"` } type DataSessions struct { - Type string - Users []*DataSession - Id string `json:",omitempty"` - Token string `json:",omitempty"` - TokenType string `json:",omitempty"` - Index uint64 `json:",omitempty"` - Batch uint64 `json:",omitempty"` + Type string + Sessions *DataSessionsRequest `json:",omitempty"` + Users []*DataSession +} + +type DataSessionsRequest struct { + Token string + Type string } type DataConference struct { diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index a16ba59f..d1002fb3 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -405,11 +405,11 @@ func (h *Hub) unicastHandler(m *MessageRequest) { } -func (h *Hub) aliveHandler(c *Connection, alive *DataAlive) { +func (h *Hub) aliveHandler(c *Connection, alive *DataAlive, iid string) { aliveJson := h.buffers.New() encoder := json.NewEncoder(aliveJson) - err := encoder.Encode(&DataOutgoing{From: c.Id, Data: alive}) + err := encoder.Encode(&DataOutgoing{From: c.Id, Data: alive, Iid: iid}) if err != nil { log.Println("Alive error while encoding JSON", err) aliveJson.Decref() @@ -420,16 +420,20 @@ func (h *Hub) aliveHandler(c *Connection, alive *DataAlive) { } -func (h *Hub) sessionsHandler(c *Connection, sessions *DataSessions) { +func (h *Hub) sessionsHandler(c *Connection, srq *DataSessionsRequest, iid string) { - reply := false + var users []*DataSession - switch sessions.TokenType { + switch srq.Type { case "contact": contact := &Contact{} - err := h.contacts.Decode("contactRequest", sessions.Token, contact) + err := h.contacts.Decode("contactConfirmed", srq.Token, contact) if err != nil { - log.Println("Failed to decode incoming contact token", err, sessions.Token) + log.Println("Failed to decode incoming contact token", err, srq.Token) + return + } + if !contact.Ok { + log.Println("Ignoring contact token without Ok", contact) return } // Use the userid which is not ours from the contact data. @@ -451,16 +455,16 @@ func (h *Hub) sessionsHandler(c *Connection, sessions *DataSessions) { return } // Add sessions for forein user. - sessions.Users = user.SessionsData() - reply = true + users = user.SessionsData() default: - log.Println("Unkown incoming sessions request type", sessions.TokenType) + log.Println("Unkown incoming sessions request type", srq.Type) } - if reply { + if users != nil { + sessions := &DataSessions{Type: "Sessions", Users: users, Sessions: srq} sessionsJson := h.buffers.New() encoder := json.NewEncoder(sessionsJson) - err := encoder.Encode(&DataOutgoing{From: c.Id, Data: sessions}) + err := encoder.Encode(&DataOutgoing{From: c.Id, Data: sessions, Iid: iid}) if err != nil { log.Println("Sessions error while encoding JSON", err) sessionsJson.Decref() diff --git a/src/app/spreed-webrtc-server/server.go b/src/app/spreed-webrtc-server/server.go index 9c7668de..392048a6 100644 --- a/src/app/spreed-webrtc-server/server.go +++ b/src/app/spreed-webrtc-server/server.go @@ -68,7 +68,7 @@ func (s *Server) OnUnregister(c *Connection) { func (s *Server) OnText(c *Connection, b Buffer) { - //log.Printf("OnText from %d: %s\n", c.id, b) + //log.Printf("OnText from %d: %s\n", c.Id, b) var msg DataIncoming err := json.Unmarshal(b.Bytes(), &msg) if err != nil { @@ -167,9 +167,9 @@ func (s *Server) OnText(c *Connection, b Buffer) { } } case "Alive": - s.Alive(c, msg.Alive) + s.Alive(c, msg.Alive, msg.Iid) case "Sessions": - s.Sessions(c, msg.Sessions) + s.Sessions(c, msg.Sessions.Sessions, msg.Iid) default: log.Println("OnText unhandled message type", msg.Type) } @@ -192,15 +192,15 @@ func (s *Server) Unicast(c *Connection, to string, m interface{}) { b.Decref() } -func (s *Server) Alive(c *Connection, alive *DataAlive) { +func (s *Server) Alive(c *Connection, alive *DataAlive, iid string) { - c.h.aliveHandler(c, alive) + c.h.aliveHandler(c, alive, iid) } -func (s *Server) Sessions(c *Connection, sessions *DataSessions) { +func (s *Server) Sessions(c *Connection, srq *DataSessionsRequest, iid string) { - c.h.sessionsHandler(c, sessions) + c.h.sessionsHandler(c, srq, iid) } diff --git a/static/js/directives/audiolevel.js b/static/js/directives/audiolevel.js index 74fd62d9..0ab7de42 100644 --- a/static/js/directives/audiolevel.js +++ b/static/js/directives/audiolevel.js @@ -44,7 +44,7 @@ define(['jquery', 'underscore'], function($, _) { var element = $element[0]; var width = 0; this.update = _.bind(function() { - if (this.active || width > 0) { + if (this.active || width > 0) { if (webrtc.usermedia.audioLevel) { width = Math.round(100 * webrtc.usermedia.audioLevel); // Hide low volumes. diff --git a/static/js/mediastream/api.js b/static/js/mediastream/api.js index 756ac4ac..c7f8b110 100644 --- a/static/js/mediastream/api.js +++ b/static/js/mediastream/api.js @@ -29,6 +29,7 @@ define(['jquery', 'underscore'], function($, _) { this.sid = null; this.session = {}; this.connector = connector; + this.iids= 0; this.e = $({}); @@ -74,7 +75,7 @@ define(['jquery', 'underscore'], function($, _) { Type: type }; payload[type] = data; - //console.log("<<<<<<<<<<<<", JSON.stringify(payload)); + //console.log("<<<<<<<<<<<<", JSON.stringify(payload, null, 2)); this.connector.send(payload, noqueue); }; @@ -91,6 +92,21 @@ define(['jquery', 'underscore'], function($, _) { return this.apply(name, obj); }; + Api.prototype.request = function(type, data, cb) { + + var payload = { + Type: type + } + payload[type] = data; + if (cb) { + var iid = ""+(this.iids++); + payload.Iid = iid; + this.e.one(iid+".request", cb); + } + this.connector.send(payload); + + } + // Helper hack function to send API requests to other destinations. // Simply provide an alternative send function on the obj Object. Api.prototype.apply = function(name, obj) { @@ -105,9 +121,16 @@ define(['jquery', 'underscore'], function($, _) { this.last_receive = now; this.last_receive_overdue = false; + var iid = d.Iid; var data = d.Data; var dataType = data.Type; + if (iid) { + // Shortcut for iid registered responses. + this.e.triggerHandler(iid+".request", [dataType, data]); + return; + } + switch (dataType) { case "Self": console.log("Self received", data); @@ -321,16 +344,17 @@ define(['jquery', 'underscore'], function($, _) { return this.send("Alive", data); }; - Api.prototype.sendSessions = function(token, type) { + Api.prototype.sendSessions = function(token, type, cb) { var data = { Type: "Sessions", - Id: "some-random-whatever", - Token: token, - TokenType: type + Sessions: { + Type: type, + Token: token + } } - return this.send("Sessions", data); + return this.request("Sessions", data, cb); }; diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 6378b778..93e997cc 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -129,7 +129,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; // buddyList - return ["$window", "$compile", "playSound", "buddyData", "buddySession", "fastScroll", "mediaStream", "animationFrame", function($window, $compile, playSound, buddyData, buddySession, fastScroll, mediaStream, animationFrame) { + return ["$window", "$compile", "playSound", "buddyData", "buddySession", "fastScroll", "mediaStream", "animationFrame", "$q", function($window, $compile, playSound, buddyData, buddySession, fastScroll, mediaStream, animationFrame, $q) { var buddyTemplate = $compile(templateBuddy); var buddyActions = $compile(templateBuddyActions); @@ -158,16 +158,9 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! this.hover(buddyElement, event.type === "mouseenter" ? true : false); }, this)); $element.on("click", ".buddy", _.bind(function(event) { - console.log("click event", event); var buddyElement = $(event.currentTarget); - //buddyElement.scope().doDefault(); this.click(buddyElement, event.target); }, this)); - /*$element.on("click", ".fa.contact", _.bind(function(event) { - event.stopPropagation(); - var buddyElement = $(event.currentTarget); - buddyElement.scope().doDefaultContact(); - }, this));*/ $element.attr("data-xthreshold", "10"); $element.on("swipeleft", ".buddy", _.bind(function(event) { event.preventDefault(); @@ -658,32 +651,47 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! var scope = buddyElement.scope(); var session = scope.session; - var sessionData = session.get() var contact = scope.contact; - var id; - if (!sessionData) { - // TODO(longsleep): Find session with help of contact. - console.log("No sessions for this buddy.", session, contact); - if (contact && contact.Token) { - mediaStream.api.sendSessions(contact.Token, "contact"); + + var promise = (function() { + var deferred = $q.defer(); + var sessionData = session.get() + if (!sessionData) { + // Find session with help of contact. + if (contact && contact.Token) { + mediaStream.api.sendSessions(contact.Token, "contact", function(event, type, data) { + //console.log("oooooooooooooooo", type, data); + if (data.Users && data.Users.length > 0) { + var s = data.Users[0]; + buddyData.set(s.Id, scope); + deferred.resolve(s.Id); + } + }); + } + } else { + deferred.resolve(sessionData.Id); } - return; - } else { - id = sessionData.Id; - } - //console.log("id", id); + return deferred.promise; + })(); + switch (action) { case "call": - scope.doCall(id); + promise.then(function(id) { + scope.doCall(id); + }); break; case "chat": - scope.doChat(id); + promise.then(function(id) { + scope.doChat(id); + }); break; case "contact": if (contact) { scope.doContactRemove(contact.Userid); } else { - scope.doContactRequest(id); + promise.then(function(id) { + scope.doContactRequest(id); + }); } break; } From 73ebac440c164a0e515b61d42a46806dbcf11268 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Mon, 16 Jun 2014 16:42:40 +0200 Subject: [PATCH 46/55] Implemented working client side sessions lookup. --- src/app/spreed-webrtc-server/contact.go | 5 +- src/app/spreed-webrtc-server/hub.go | 77 +++++++++++-------------- src/app/spreed-webrtc-server/server.go | 4 +- src/app/spreed-webrtc-server/session.go | 31 ++++++---- 4 files changed, 57 insertions(+), 60 deletions(-) diff --git a/src/app/spreed-webrtc-server/contact.go b/src/app/spreed-webrtc-server/contact.go index 940015e4..995f92d6 100644 --- a/src/app/spreed-webrtc-server/contact.go +++ b/src/app/spreed-webrtc-server/contact.go @@ -24,7 +24,6 @@ package main import () type Contact struct { - A string - B string - Ok bool + A string + B string } diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index d1002fb3..4d81a644 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -188,12 +188,13 @@ func (h *Hub) CreateTurnData(id string) *DataTurn { } func (h *Hub) CreateSuserid(session *Session) (suserid string) { - if session.Userid != "" { + userid := session.Userid() + if userid != "" { m := hmac.New(sha256.New, h.encryptionSecret) - m.Write([]byte(session.Userid)) + m.Write([]byte(userid)) suserid = base64.StdEncoding.EncodeToString(m.Sum(nil)) } - return suserid + return } func (h *Hub) CreateSession(request *http.Request, st *SessionToken) *Session { @@ -371,14 +372,15 @@ func (h *Hub) unregisterHandler(c *Connection) { return } session := c.Session + suserid := session.Userid() delete(h.connectionTable, c.Id) delete(h.sessionTable, c.Id) - if session != nil && session.Userid != "" { - user, ok := h.userTable[session.Userid] + if session != nil && suserid != "" { + user, ok := h.userTable[suserid] if ok { empty := user.RemoveSession(session) if empty { - delete(h.userTable, session.Userid) + delete(h.userTable, suserid) } } } @@ -427,20 +429,17 @@ func (h *Hub) sessionsHandler(c *Connection, srq *DataSessionsRequest, iid strin switch srq.Type { case "contact": contact := &Contact{} - err := h.contacts.Decode("contactConfirmed", srq.Token, contact) + err := h.contacts.Decode("contact", srq.Token, contact) if err != nil { log.Println("Failed to decode incoming contact token", err, srq.Token) return } - if !contact.Ok { - log.Println("Ignoring contact token without Ok", contact) - return - } // Use the userid which is not ours from the contact data. var userid string - if contact.A == c.Session.Userid { + suserid := c.Session.Userid() + if contact.A == suserid { userid = contact.B - } else if contact.B == c.Session.Userid { + } else if contact.B == suserid { userid = contact.A } if userid == "" { @@ -528,11 +527,12 @@ func (h *Hub) authenticateHandler(session *Session, st *SessionToken, userid str err := session.Authenticate(h.realm, st, userid) if err == nil { // Authentication success. + suserid := session.Userid() h.mutex.Lock() - user, ok := h.userTable[session.Userid] + user, ok := h.userTable[suserid] if !ok { - user = NewUser(session.Userid) - h.userTable[session.Userid] = user + user = NewUser(suserid) + h.userTable[suserid] = user } h.mutex.Unlock() user.AddSession(session) @@ -548,72 +548,61 @@ func (h *Hub) contactrequestHandler(c *Connection, to string, cr *DataContactReq if cr.Success { // Client replied with success. - // Decode Token and make sure c.Session.Userid and the to Session.UserId are a match. + // Decode Token and make sure c.Session.Userid and the to Session.Userid are a match. contact := &Contact{} - err = h.contacts.Decode("contactRequest", cr.Token, contact) + err = h.contacts.Decode("contact", cr.Token, contact) if err != nil { return err } - if contact.Ok { - return errors.New("received success with ok state set") - } - bSessionData := c.Session.Data() - if bSessionData.Userid == "" { + suserid := c.Session.Userid() + if suserid == "" { return errors.New("no userid") } h.mutex.RLock() - aSession, ok := h.sessionTable[to] + session, ok := h.sessionTable[to] h.mutex.RUnlock() if !ok { return errors.New("unknown to session for confirm") } - aSessionData := aSession.Data() - if aSessionData.Userid == "" { + userid := session.Userid() + if userid == "" { return errors.New("to has no userid for confirm") } - if aSessionData.Userid != contact.A { + if suserid != contact.A { return errors.New("contact mismatch in a") } - if bSessionData.Userid != contact.B { + if userid != contact.B { return errors.New("contact mismatch in b") } - contact.Ok = true - cr.Token, err = h.contacts.Encode("contactConfirmed", contact) } else { if cr.Token != "" { // Client replied with no success. - // Decode Token. - contact := &Contact{} - err = h.contacts.Decode("contactRequest", cr.Token, contact) - if err != nil { - return err - } // Remove token. cr.Token = "" } else { // New request. // Create Token with flag and c.Session.Userid and the to Session.Userid. - aSessionData := c.Session.Data() - if aSessionData.Userid == "" { + suserid := c.Session.Userid() + if suserid == "" { return errors.New("no userid") } h.mutex.RLock() - bSession, ok := h.sessionTable[to] + session, ok := h.sessionTable[to] h.mutex.RUnlock() if !ok { return errors.New("unknown to session") } - bSessionData := bSession.Data() - if bSessionData.Userid == "" { + userid := session.Userid() + if userid == "" { return errors.New("to has no userid") } - if bSessionData.Userid == aSessionData.Userid { + if userid == suserid { return errors.New("to userid cannot be the same as own userid") } // Create object. - contact := &Contact{aSessionData.Userid, bSessionData.Userid, false} + contact := &Contact{userid, suserid} // Serialize. - cr.Token, err = h.contacts.Encode("contactRequest", contact) + cr.Token, err = h.contacts.Encode("contact", contact) } } diff --git a/src/app/spreed-webrtc-server/server.go b/src/app/spreed-webrtc-server/server.go index 392048a6..cda9dd7e 100644 --- a/src/app/spreed-webrtc-server/server.go +++ b/src/app/spreed-webrtc-server/server.go @@ -44,7 +44,7 @@ func (s *Server) OnRegister(c *Connection) { Type: "Self", Id: c.Id, Sid: c.Session.Sid, - Userid: c.Session.Userid, + Userid: c.Session.Userid(), Suserid: c.h.CreateSuserid(c.Session), Token: token, Version: c.h.version, @@ -144,7 +144,7 @@ func (s *Server) OnText(c *Connection, b Buffer) { log.Println("Ignoring invalid contact request.", err) return } - msg.Chat.Chat.Status.ContactRequest.Userid = c.Session.Userid + msg.Chat.Chat.Status.ContactRequest.Userid = c.Session.Userid() } atomic.AddUint64(&c.h.unicastChatMessages, 1) s.Unicast(c, msg.Chat.To, msg.Chat) diff --git a/src/app/spreed-webrtc-server/session.go b/src/app/spreed-webrtc-server/session.go index 0d6f4fdf..66b2c1cc 100644 --- a/src/app/spreed-webrtc-server/session.go +++ b/src/app/spreed-webrtc-server/session.go @@ -33,13 +33,12 @@ var sessionNonces *securecookie.SecureCookie type Session struct { Id string Sid string - Userid string - Roomid string Ua string UpdateRev uint64 Status interface{} Nonce string mutex sync.RWMutex + userid string } func NewSession(id, sid string) *Session { @@ -60,8 +59,8 @@ func (s *Session) Update(update *SessionUpdate) uint64 { //fmt.Println("type update", key) switch key { - case "Roomid": - s.Roomid = update.Roomid + //case "Roomid": + // s.Roomid = update.Roomid case "Ua": s.Ua = update.Ua case "Status": @@ -83,7 +82,7 @@ func (s *Session) Authorize(realm string, st *SessionToken) (string, error) { if s.Id != st.Id || s.Sid != st.Sid { return "", errors.New("session id mismatch") } - if s.Userid != "" { + if s.userid != "" { return "", errors.New("session already authenticated") } @@ -100,7 +99,7 @@ func (s *Session) Authenticate(realm string, st *SessionToken, userid string) er s.mutex.Lock() defer s.mutex.Unlock() - if s.Userid != "" { + if s.userid != "" { return errors.New("session already authenticated") } if userid == "" { @@ -117,14 +116,18 @@ func (s *Session) Authenticate(realm string, st *SessionToken, userid string) er s.Nonce = "" } - s.Userid = userid + s.userid = userid s.UpdateRev++ return nil } func (s *Session) Token() *SessionToken { - return &SessionToken{Id: s.Id, Sid: s.Sid, Userid: s.Userid} + + s.mutex.RLock() + defer s.mutex.RUnlock() + + return &SessionToken{Id: s.Id, Sid: s.Sid, Userid: s.userid} } func (s *Session) Data() *DataSession { @@ -134,7 +137,7 @@ func (s *Session) Data() *DataSession { return &DataSession{ Id: s.Id, - Userid: s.Userid, + Userid: s.userid, Ua: s.Ua, Status: s.Status, Rev: s.UpdateRev, @@ -142,6 +145,12 @@ func (s *Session) Data() *DataSession { } +func (s *Session) Userid() string { + + return s.userid + +} + func (s *Session) DataSessionLeft(state string) *DataSession { s.mutex.RLock() @@ -163,7 +172,7 @@ func (s *Session) DataSessionJoined() *DataSession { return &DataSession{ Type: "Joined", Id: s.Id, - Userid: s.Userid, + Userid: s.userid, Ua: s.Ua, } @@ -177,7 +186,7 @@ func (s *Session) DataSessionStatus() *DataSession { return &DataSession{ Type: "Status", Id: s.Id, - Userid: s.Userid, + Userid: s.userid, Status: s.Status, Rev: s.UpdateRev, } From d9212307e969404c57c3512a89ca4b3261322072 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Mon, 16 Jun 2014 16:45:56 +0200 Subject: [PATCH 47/55] Fixed padding of typing hint. --- src/styles/components/_chat.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/components/_chat.scss b/src/styles/components/_chat.scss index 4da09c0b..a76ab562 100644 --- a/src/styles/components/_chat.scss +++ b/src/styles/components/_chat.scss @@ -439,7 +439,7 @@ } } .typinghint { - padding: 2px 6px 0 6px; + padding: 0 6px 0 6px; white-space: nowrap; overflow: hidden; font-size:.8em; From 10c6f254630791d27a00685bcc5abeff48d21fe1 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Mon, 16 Jun 2014 16:55:47 +0200 Subject: [PATCH 48/55] Simplified default room checks. --- src/app/spreed-webrtc-server/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/spreed-webrtc-server/server.go b/src/app/spreed-webrtc-server/server.go index cda9dd7e..5e3577d0 100644 --- a/src/app/spreed-webrtc-server/server.go +++ b/src/app/spreed-webrtc-server/server.go @@ -107,7 +107,7 @@ func (s *Server) OnText(c *Connection, b Buffer) { // TODO(longsleep): Validate Answer s.Unicast(c, msg.Answer.To, msg.Answer) case "Users": - if c.h.config.DefaultRoomEnabled || !c.h.isDefaultRoomid(c.Roomid) { + if c.Hello { s.Users(c) } case "Authentication": @@ -122,7 +122,7 @@ func (s *Server) OnText(c *Connection, b Buffer) { case "Status": //log.Println("Status", msg.Status) s.UpdateSession(c, &SessionUpdate{Types: []string{"Status"}, Status: msg.Status.Status}) - if c.h.config.DefaultRoomEnabled || !c.h.isDefaultRoomid(c.Roomid) { + if c.Hello { s.Broadcast(c, c.Session.DataSessionStatus()) } case "Chat": @@ -133,7 +133,7 @@ func (s *Server) OnText(c *Connection, b Buffer) { msg.Chat.Chat.Time = time.Now().Format(time.RFC3339) if msg.Chat.To == "" { // TODO(longsleep): Check if chat broadcast is allowed. - if c.h.config.DefaultRoomEnabled || !c.h.isDefaultRoomid(c.Roomid) { + if c.Hello { atomic.AddUint64(&c.h.broadcastChatMessages, 1) s.Broadcast(c, msg.Chat) } From fe67adf9658bc48038711481046a1932a342072a Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Mon, 16 Jun 2014 17:29:07 +0200 Subject: [PATCH 49/55] Added priority to sessions. --- doc/CHANNELING-API.txt | 12 ++++++++---- src/app/spreed-webrtc-server/channeling.go | 1 + src/app/spreed-webrtc-server/session.go | 13 +++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index 6716855e..65d28cc2 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -282,7 +282,8 @@ Additional types for session listing and notifications "Id": "7", "Userid": "u7", "Ua": "Chrome 28", - "Status": null + "Status": null, + "Prio": 100 } Note: The Userid field is only present if that session belongs to a known user. @@ -324,21 +325,24 @@ Additional types for session listing and notifications "Type": "Online", "Id": "1", "Ua": "Firefox 27", - "Status": {...} + "Status": {...}, + "Prio": 100 }, { "Type": "Online", "Id": "3", "Userid": "u3", "Ua": "Chrome 28", - "Status": {...} + "Status": {...}, + "Prio": 100 }, { "Type": "Online", "Id": "4", "Userid": "u4", "Ua": "Chrome 28", - "Status": {...} + "Status": {...}, + "Prio": 100 } ] } diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index 608c8e8c..d49e5300 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -72,6 +72,7 @@ type DataSession struct { Token string `json:",omitempty"` Version string `json:",omitempty"` Rev uint64 `json:",omitempty"` + Prio int `json:",omitempty"` Status interface{} } diff --git a/src/app/spreed-webrtc-server/session.go b/src/app/spreed-webrtc-server/session.go index 66b2c1cc..18b03c56 100644 --- a/src/app/spreed-webrtc-server/session.go +++ b/src/app/spreed-webrtc-server/session.go @@ -37,6 +37,7 @@ type Session struct { UpdateRev uint64 Status interface{} Nonce string + Prio int mutex sync.RWMutex userid string } @@ -44,8 +45,9 @@ type Session struct { func NewSession(id, sid string) *Session { return &Session{ - Id: id, - Sid: sid, + Id: id, + Sid: sid, + Prio: 100, } } @@ -59,12 +61,12 @@ func (s *Session) Update(update *SessionUpdate) uint64 { //fmt.Println("type update", key) switch key { - //case "Roomid": - // s.Roomid = update.Roomid case "Ua": s.Ua = update.Ua case "Status": s.Status = update.Status + case "Prio": + s.Prio = update.Prio } } @@ -174,6 +176,7 @@ func (s *Session) DataSessionJoined() *DataSession { Id: s.Id, Userid: s.userid, Ua: s.Ua, + Prio: s.Prio, } } @@ -189,6 +192,7 @@ func (s *Session) DataSessionStatus() *DataSession { Userid: s.userid, Status: s.Status, Rev: s.UpdateRev, + Prio: s.Prio, } } @@ -198,6 +202,7 @@ type SessionUpdate struct { Types []string Roomid string Ua string + Prio int Status interface{} } From 4780e79a5e00ad59f6e5c19280e39a2b8afeda8d Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Mon, 16 Jun 2014 20:27:39 +0200 Subject: [PATCH 50/55] Added Prio and stap to sessions. --- src/app/spreed-webrtc-server/channeling.go | 1 + src/app/spreed-webrtc-server/hub.go | 2 +- src/app/spreed-webrtc-server/session.go | 12 +++++++++--- src/app/spreed-webrtc-server/user.go | 22 ++++++++++++++++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index d49e5300..726d3005 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -74,6 +74,7 @@ type DataSession struct { Rev uint64 `json:",omitempty"` Prio int `json:",omitempty"` Status interface{} + stamp int64 } type DataUser struct { diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index 4d81a644..777eecbd 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -483,7 +483,6 @@ func (h *Hub) sessionupdateHandler(s *SessionUpdate) uint64 { h.mutex.RUnlock() var rev uint64 if ok { - rev = session.Update(s) if s.Status != nil { status, ok := s.Status.(map[string]interface{}) if ok && status["buddyPicture"] != nil { @@ -496,6 +495,7 @@ func (h *Hub) sessionupdateHandler(s *SessionUpdate) uint64 { } } } + rev = session.Update(s) } else { log.Printf("Update data for unknown user %s\n", s.Id) } diff --git a/src/app/spreed-webrtc-server/session.go b/src/app/spreed-webrtc-server/session.go index 18b03c56..a2ccb07b 100644 --- a/src/app/spreed-webrtc-server/session.go +++ b/src/app/spreed-webrtc-server/session.go @@ -26,6 +26,7 @@ import ( "fmt" "github.com/gorilla/securecookie" "sync" + "time" ) var sessionNonces *securecookie.SecureCookie @@ -40,14 +41,16 @@ type Session struct { Prio int mutex sync.RWMutex userid string + stamp int64 } func NewSession(id, sid string) *Session { return &Session{ - Id: id, - Sid: sid, - Prio: 100, + Id: id, + Sid: sid, + Prio: 100, + stamp: time.Now().Unix(), } } @@ -119,6 +122,7 @@ func (s *Session) Authenticate(realm string, st *SessionToken, userid string) er } s.userid = userid + s.stamp = time.Now().Unix() s.UpdateRev++ return nil @@ -143,6 +147,8 @@ func (s *Session) Data() *DataSession { Ua: s.Ua, Status: s.Status, Rev: s.UpdateRev, + Prio: s.Prio, + stamp: s.stamp, } } diff --git a/src/app/spreed-webrtc-server/user.go b/src/app/spreed-webrtc-server/user.go index fc041bc0..057e37a6 100644 --- a/src/app/spreed-webrtc-server/user.go +++ b/src/app/spreed-webrtc-server/user.go @@ -23,6 +23,7 @@ package main import ( "fmt" + "sort" "sync" ) @@ -85,6 +86,27 @@ func (u *User) SessionsData() []*DataSession { for _, session := range u.sessionTable { sessions = append(sessions, session.Data()) } + sort.Sort(ByPrioAndStamp(sessions)) return sessions } + +type ByPrioAndStamp []*DataSession + +func (a ByPrioAndStamp) Len() int { + return len(a) +} + +func (a ByPrioAndStamp) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +func (a ByPrioAndStamp) Less(i, j int) bool { + if a[i].Prio < a[j].Prio { + return true + } + if a[i].Prio == a[j].Prio { + return a[i].stamp < a[j].stamp + } + return false +} From 5add1c5a81ab04e7285d632a63a9af80e0b7d0f6 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Tue, 17 Jun 2014 15:12:31 +0200 Subject: [PATCH 51/55] Implemented user defined subline. --- .../js/controllers/mediastreamcontroller.js | 4 +- static/js/services/buddylist.js | 22 +++- static/partials/buddy.html | 4 +- static/partials/buddyactions.html | 10 +- static/partials/settings.html | 113 ++++++++++-------- 5 files changed, 93 insertions(+), 60 deletions(-) diff --git a/static/js/controllers/mediastreamcontroller.js b/static/js/controllers/mediastreamcontroller.js index 99b8f80a..ecb62a3e 100644 --- a/static/js/controllers/mediastreamcontroller.js +++ b/static/js/controllers/mediastreamcontroller.js @@ -139,6 +139,7 @@ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function $scope.master = { displayName: null, buddyPicture: null, + message: null, settings: { videoQuality: "high", stereo: true, @@ -175,7 +176,8 @@ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function // This is the user status. var status = { displayName: $scope.master.displayName || null, - buddyPicture: $scope.master.buddyPicture || null + buddyPicture: $scope.master.buddyPicture || null, + message: $scope.master.message || null } if (_.isEqual(status, cache.status)) { console.log("Status update skipped, as status has not changed.") diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 93e997cc..1475a0c9 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -435,7 +435,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! display.buddyPicture = status.buddyPicture; this.updateBuddyPicture(display); // Set display subline. - display.subLine = data.Ua; + this.updateSubline(display, status.message); // Add to render queue when no element exists. if (!scope.element) { var before = this.tree.add(id, scope); @@ -462,8 +462,8 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! this.queue.push([queueName, id, before]); } // Update display subline. - if (data.Ua) { - display.subLine = data.Ua; + if (status.message) { + this.updateSubline(display, status.message); } // Update display picture. display.buddyPicture = status.buddyPicture || null; @@ -471,6 +471,22 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! }; + Buddylist.prototype.updateSubline = function(display, s) { + + if (!s || s === "__none__") { + display.subline = ""; + return; + } + if (s.length > 20) { + display.sublineFull = s; + s = s.substr(0, 20) + "..."; + } else { + display.sublineFull = null; + } + display.subline = s; + + }; + Buddylist.prototype.onStatus = function(data) { //console.log("onStatus", data); diff --git a/static/partials/buddy.html b/static/partials/buddy.html index d566c41d..3f059dda 100644 --- a/static/partials/buddy.html +++ b/static/partials/buddy.html @@ -1,5 +1,5 @@ -
+
{{session.Id|displayName}}
-
({{session.count}}) {{display.subLine}}
+
({{session.count}}) {{display.subline}}
diff --git a/static/partials/buddyactions.html b/static/partials/buddyactions.html index df4aa7a0..c3e7d74b 100644 --- a/static/partials/buddyactions.html +++ b/static/partials/buddyactions.html @@ -1,11 +1,11 @@ diff --git a/static/partials/settings.html b/static/partials/settings.html index 1bb159f7..6bde5261 100644 --- a/static/partials/settings.html +++ b/static/partials/settings.html @@ -3,7 +3,7 @@
- {{_('Settings')}} + {{_('Profile')}}
@@ -16,69 +16,84 @@
- {{_('Your picture and name are visible to others.')}} +
+
+
+ +
+ +
+
+
+
+ {{_('Profile information is public.')}}
-
- -
-
-
- - - -
-
-
{{userid}}
- {{_('Authenticated by certificate. To log out you have to remove your certificate from the browser.')}} -
-
-
-
- -
-
-
-
- -
- +
+ {{_('Media')}} +
+
+ +
+ +
-
-
- -
- +
+ +
+ +
-
-
- -
-
- - - +
+ +
+
+ + + +
-
+ {{_('Settings')}}
From 91521f37ebe72bb54126b1098666242d3351cf65 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Tue, 17 Jun 2014 15:54:16 +0200 Subject: [PATCH 52/55] Properly implemented cleanup of message with contacts and session merge. --- static/js/services/buddylist.js | 35 +++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/static/js/services/buddylist.js b/static/js/services/buddylist.js index 1475a0c9..4ab1224a 100644 --- a/static/js/services/buddylist.js +++ b/static/js/services/buddylist.js @@ -252,7 +252,9 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! } buddyData.set(id, targetScope); delete this.actionElements[id]; + return targetScope; } + return; }; @@ -466,14 +468,16 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! this.updateSubline(display, status.message); } // Update display picture. - display.buddyPicture = status.buddyPicture || null; - this.updateBuddyPicture(display); + if (status.buddyPicture) { + display.buddyPicture = status.buddyPicture || null; + this.updateBuddyPicture(display); + } }; Buddylist.prototype.updateSubline = function(display, s) { - if (!s || s === "__none__") { + if (!s || s === "__e") { display.subline = ""; return; } @@ -497,7 +501,10 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! // Update session. var sessionData = scope.session.update(id, data, _.bind(function(session) { //console.log("Session is now authenticated", session); - this.onBuddySessionUserid(scope, session); + var newscope = this.onBuddySessionUserid(scope, session); + if (newscope) { + scope = newscope; + } }, this)); if (sessionData) { // onStatus for main session. @@ -519,7 +526,10 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! buddyCount++; var sessionData = scope.session.update(id, data, _.bind(function(session) { //console.log("Session is now authenticated", session); - this.onBuddySessionUserid(scope, session); + var newscope = this.onBuddySessionUserid(scope, session); + if (newscope) { + scope = newscope; + } }, this)); if (sessionData && sessionData.Status) { this.setDisplay(id, scope, sessionData, "joined"); @@ -566,11 +576,17 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! if (sessionData) { this.updateDisplay(sessionData.Id, scope, sessionData, "status"); } else if (scope.contact) { + var contact = scope.contact; // Use it with userid as id in tree. - if (!this.tree.check(session.Userid)) { - this.tree.add(session.Userid, scope); + if (!this.tree.check(contact.Userid)) { + this.tree.add(contact.Userid, scope); buddyCount++; } + if (contact.Status !== null && !contact.Status.message) { + // Remove status message. + contact.Status.message = "__e"; + } + this.updateDisplay(contact.Userid, scope, contact, "status"); } if (!noApply) { scope.$apply(); @@ -613,6 +629,9 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! if (contact.Status === null && sessionData.Status) { // Update contact status with session.Status var status = contact.Status = _.extend({}, sessionData.Status); + // Remove status message. + delete status.message; + // Convert buddy image. if (status.buddyPicture) { var img = this.dumpBuddyPictureToString(scope); if (img) { @@ -631,7 +650,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! scope = this.onJoined({ Id: contact.Userid, Userid: contact.Userid, - Status: contact.Status + Status: _.extend({}, contact.Status) }); scope.contact = contact; } From d8996870a298c09eb8752cd9d8773230272a6056 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Tue, 17 Jun 2014 22:51:04 +0200 Subject: [PATCH 53/55] Show buddy list and chat even when in no room. --- html/main.html | 4 ++-- src/styles/global/_base.scss | 1 + .../js/controllers/mediastreamcontroller.js | 19 +++++++++++++++++-- static/js/directives/buddylist.js | 10 ++-------- static/partials/buddylist.html | 2 +- static/partials/page/welcome.html | 2 +- 6 files changed, 24 insertions(+), 14 deletions(-) diff --git a/html/main.html b/html/main.html index 54dad972..0abebc27 100644 --- a/html/main.html +++ b/html/main.html @@ -15,7 +15,7 @@
- + @@ -35,7 +35,7 @@
-
+
diff --git a/src/styles/global/_base.scss b/src/styles/global/_base.scss index cbd1681c..63825c38 100644 --- a/src/styles/global/_base.scss +++ b/src/styles/global/_base.scss @@ -131,4 +131,5 @@ a { font-size: 1.1em; margin-top: 80px; text-shadow: 0 0 5px black; + max-width:500px; } diff --git a/static/js/controllers/mediastreamcontroller.js b/static/js/controllers/mediastreamcontroller.js index ecb62a3e..1cd9f07f 100644 --- a/static/js/controllers/mediastreamcontroller.js +++ b/static/js/controllers/mediastreamcontroller.js @@ -668,8 +668,23 @@ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function if (mediaStream.connector.connected) { $scope.setStatus("waiting"); } - $scope.layout.buddylist = true; - $scope.layout.buddylistAutoHide = false; + if ($scope.roomstatus) { + $scope.layout.buddylist = true; + $scope.layout.buddylistAutoHide = false; + } else { + $scope.layout.buddylist = false; + $scope.layout.buddylistAutoHide = true; + } + }); + + $scope.$watch("roomstatus", function(roomstatus) { + if (roomstatus && !$scope.peer) { + $scope.layout.buddylist = true; + $scope.layout.buddylistAutoHide = false; + } else if (!$scope.layout.buddylistAutoHide) { + $scope.layout.buddylist = false; + $scope.layout.buddylistAutoHide = true; + } }); mediaStream.webrtc.e.on("busy", function(event, from) { diff --git a/static/js/directives/buddylist.js b/static/js/directives/buddylist.js index 50426f1e..e3c71d5f 100644 --- a/static/js/directives/buddylist.js +++ b/static/js/directives/buddylist.js @@ -28,7 +28,7 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { $scope.layout.buddylist = false; - $scope.enabled = false; + $scope.layout.buddylistAutoHide = true; $scope.doCall = function(id) { @@ -75,13 +75,7 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) { };*/ $scope.setRoomStatus = function(status) { - if (status !== $scope.enabled) { - $scope.enabled = status; - $scope.$emit("roomStatus", status); - } - if (status && !$scope.layout.buddylistAutoHide) { - $scope.layout.buddylist = true - } + $scope.$emit("roomStatus", status); }; var buddylist = $scope.buddylist = buddyList.buddylist($element, $scope, {}); diff --git a/static/partials/buddylist.html b/static/partials/buddylist.html index 6f3b436c..ee1c088a 100644 --- a/static/partials/buddylist.html +++ b/static/partials/buddylist.html @@ -1,4 +1,4 @@ -
+
diff --git a/static/partials/page/welcome.html b/static/partials/page/welcome.html index b5325f63..cf1fd07d 100644 --- a/static/partials/page/welcome.html +++ b/static/partials/page/welcome.html @@ -1,4 +1,4 @@ -
+

{{_("Create your room")}}

{{_("This is your room link:")}}

From c31c9a24dacd73abc256df7dbffcbe2c656b42a3 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Tue, 17 Jun 2014 23:38:13 +0200 Subject: [PATCH 54/55] Make sure to show subline when buddy has a userid. --- static/partials/buddy.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/partials/buddy.html b/static/partials/buddy.html index 3f059dda..dffad982 100644 --- a/static/partials/buddy.html +++ b/static/partials/buddy.html @@ -1,4 +1,4 @@ -

+
{{session.Id|displayName}}
({{session.count}}) {{display.subline}}
From 4f475fe532993c74b96ebccc1fd613fc411307e7 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Wed, 18 Jun 2014 15:12:28 +0200 Subject: [PATCH 55/55] Fixed merge. --- static/js/directives/directives.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/directives/directives.js b/static/js/directives/directives.js index 6067b952..9b4f2ff4 100644 --- a/static/js/directives/directives.js +++ b/static/js/directives/directives.js @@ -36,7 +36,7 @@ define([ 'directives/roombar', 'directives/socialshare', 'directives/page', - 'directives/contactrequest'], function(_, onEnter, onEscape, statusMessage, buddyList, settings, chat, audioVideo, usability, audioLevel, fileInfo, screenshare, roomBar, socialShare, page, contactRequest) { + 'directives/contactrequest'], function(_, onEnter, onEscape, statusMessage, buddyList, buddyPicture, settings, chat, audioVideo, usability, audioLevel, fileInfo, screenshare, roomBar, socialShare, page, contactRequest) { var directives = { onEnter: onEnter,