45 changed files with 2284 additions and 406 deletions
@ -0,0 +1,29 @@ |
|||||||
|
/* |
||||||
|
* Spreed WebRTC. |
||||||
|
* Copyright (C) 2013-2014 struktur AG |
||||||
|
* |
||||||
|
* This file is part of Spreed WebRTC. |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as published by |
||||||
|
* the Free Software Foundation, either version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
package main |
||||||
|
|
||||||
|
import () |
||||||
|
|
||||||
|
type Contact struct { |
||||||
|
A string |
||||||
|
B string |
||||||
|
} |
||||||
@ -0,0 +1,112 @@ |
|||||||
|
/* |
||||||
|
* Spreed WebRTC. |
||||||
|
* Copyright (C) 2013-2014 struktur AG |
||||||
|
* |
||||||
|
* This file is part of Spreed WebRTC. |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as published by |
||||||
|
* the Free Software Foundation, either version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"sort" |
||||||
|
"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()) |
||||||
|
} |
||||||
|
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 |
||||||
|
} |
||||||
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
* |
||||||
|
*/ |
||||||
|
define([], function() { |
||||||
|
|
||||||
|
// UsersettingsController
|
||||||
|
return ["$scope", "$element", "mediaStream", "safeApply", function($scope, $element, mediaStream, safeApply) { |
||||||
|
|
||||||
|
$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(); |
||||||
|
}; |
||||||
|
|
||||||
|
}]; |
||||||
|
|
||||||
|
}); |
||||||
@ -0,0 +1,72 @@ |
|||||||
|
/* |
||||||
|
* Spreed WebRTC. |
||||||
|
* Copyright (C) 2013-2014 struktur AG |
||||||
|
* |
||||||
|
* This file is part of Spreed WebRTC. |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as published by |
||||||
|
* the Free Software Foundation, either version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* |
||||||
|
*/ |
||||||
|
define(['jquery', 'underscore'], function($, _) { |
||||||
|
|
||||||
|
return ["translation", "buddyData", "contacts", function(translation, buddyData, contacts) { |
||||||
|
|
||||||
|
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 r = $scope.request; |
||||||
|
r.Success = !!success; |
||||||
|
$scope.sendChatServer($scope.id, "Contact request answer", { |
||||||
|
ContactRequest: r |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
$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) { |
||||||
|
var buddy = buddyData.lookup($scope.id); |
||||||
|
var status = {}; |
||||||
|
if (buddy) { |
||||||
|
$.extend(status, buddy.status); |
||||||
|
} |
||||||
|
$scope.addContact(request, status); |
||||||
|
} |
||||||
|
|
||||||
|
}]; |
||||||
|
|
||||||
|
return { |
||||||
|
scope: true, |
||||||
|
restrict: 'EAC', |
||||||
|
controller: controller, |
||||||
|
replace: false |
||||||
|
} |
||||||
|
|
||||||
|
}]; |
||||||
|
|
||||||
|
}); |
||||||
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
* |
||||||
|
*/ |
||||||
|
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; |
||||||
|
|
||||||
|
}]; |
||||||
|
|
||||||
|
}); |
||||||
@ -0,0 +1,191 @@ |
|||||||
|
/* |
||||||
|
* Spreed WebRTC. |
||||||
|
* Copyright (C) 2013-2014 struktur AG |
||||||
|
* |
||||||
|
* This file is part of Spreed WebRTC. |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as published by |
||||||
|
* the Free Software Foundation, either version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* |
||||||
|
*/ |
||||||
|
define(["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, data.Id, data.Userid, this);
|
||||||
|
var id = data.Id; |
||||||
|
if (data.Id) { |
||||||
|
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.use(null, {}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
BuddySession.prototype.add = function(id, data) { |
||||||
|
this.sessions[id] = data; |
||||||
|
if (this.count === 0) { |
||||||
|
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) { |
||||||
|
if (!id) { |
||||||
|
id = this.Id; |
||||||
|
} |
||||||
|
return this.sessions[id]; |
||||||
|
}; |
||||||
|
|
||||||
|
BuddySession.prototype.use = function(id, data) { |
||||||
|
if (id) { |
||||||
|
this.Id = id; |
||||||
|
} else { |
||||||
|
this.Id = null; |
||||||
|
} |
||||||
|
//console.log("Use session as default", id, data, this);
|
||||||
|
}; |
||||||
|
|
||||||
|
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) { |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
BuddySession.prototype.update = function(id, data, onUseridCallback) { |
||||||
|
|
||||||
|
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 !== sessionData) { |
||||||
|
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.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); |
||||||
|
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); |
||||||
|
}, |
||||||
|
sessions: function() { |
||||||
|
return sessions; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
}]; |
||||||
|
|
||||||
|
}); |
||||||
@ -0,0 +1,103 @@ |
|||||||
|
/* |
||||||
|
* Spreed WebRTC. |
||||||
|
* Copyright (C) 2013-2014 struktur AG |
||||||
|
* |
||||||
|
* This file is part of Spreed WebRTC. |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as published by |
||||||
|
* the Free Software Foundation, either version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* |
||||||
|
*/ |
||||||
|
define(['underscore', 'jquery'], function(underscore, $) { |
||||||
|
|
||||||
|
// contactData
|
||||||
|
return [function() { |
||||||
|
|
||||||
|
var contacts = {}; |
||||||
|
var users = {}; |
||||||
|
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; |
||||||
|
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: null |
||||||
|
} |
||||||
|
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) { |
||||||
|
if (users.hasOwnProperty(userid)) { |
||||||
|
var id = users[userid]; |
||||||
|
return contacts[id]; |
||||||
|
} |
||||||
|
return null; |
||||||
|
}, |
||||||
|
remove: function(userid) { |
||||||
|
if (users.hasOwnProperty(userid)) { |
||||||
|
var id = users[userid]; |
||||||
|
delete contacts[id]; |
||||||
|
} |
||||||
|
delete users[userid]; |
||||||
|
}, |
||||||
|
getById: function(id) { |
||||||
|
if (id.indexOf("contact-") === 0) { |
||||||
|
id = id.substr(8) |
||||||
|
} |
||||||
|
if (contacts.hasOwnProperty(id)) { |
||||||
|
return contacts[id]; |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return contactData; |
||||||
|
|
||||||
|
}]; |
||||||
|
|
||||||
|
}); |
||||||
@ -0,0 +1,265 @@ |
|||||||
|
/* |
||||||
|
* Spreed WebRTC. |
||||||
|
* Copyright (C) 2013-2014 struktur AG |
||||||
|
* |
||||||
|
* This file is part of Spreed WebRTC. |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as published by |
||||||
|
* the Free Software Foundation, either version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* |
||||||
|
*/ |
||||||
|
define(['underscore', 'jquery', 'modernizr', 'sjcl'], function(underscore, $, Modernizr, sjcl) { |
||||||
|
|
||||||
|
var Database = function(name) { |
||||||
|
this.version = 3; |
||||||
|
this.ready = false; |
||||||
|
this.db = null; |
||||||
|
this.name = name; |
||||||
|
this.e = $({}); |
||||||
|
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); |
||||||
|
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", this.db); |
||||||
|
this.e.triggerHandler("ready"); |
||||||
|
}; |
||||||
|
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); |
||||||
|
} |
||||||
|
transaction.onerror = request.onerror = errorCallback; |
||||||
|
if (successCallback) { |
||||||
|
request.onsuccess = successCallback; |
||||||
|
} |
||||||
|
return request; |
||||||
|
}; |
||||||
|
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); |
||||||
|
} |
||||||
|
transaction.onerror = request.onerror = errorCallback; |
||||||
|
if (successCallback) { |
||||||
|
request.onsuccess = successCallback; |
||||||
|
} |
||||||
|
return request; |
||||||
|
}; |
||||||
|
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) { |
||||||
|
var result = event.target.result; |
||||||
|
if (!result) { |
||||||
|
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; |
||||||
|
}; |
||||||
|
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", "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, suserid) { |
||||||
|
// TODO(longsleep): Avoid creating empty databases. Create db on store only.
|
||||||
|
var database = this.open(userid, suserid); |
||||||
|
if (database && userid) { |
||||||
|
// Load when database is ready and userid is available.
|
||||||
|
if (database.ready) { |
||||||
|
_.defer(_.bind(function() { |
||||||
|
if (this.database === database) { |
||||||
|
this.load(); |
||||||
|
} |
||||||
|
}, this)); |
||||||
|
} else { |
||||||
|
database.e.one("ready", _.bind(function() { |
||||||
|
if (this.database === database) { |
||||||
|
this.load(); |
||||||
|
} |
||||||
|
}, this)); |
||||||
|
} |
||||||
|
} |
||||||
|
}, this)); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
Contacts.prototype.open = function(userid, suserid) { |
||||||
|
|
||||||
|
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 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; |
||||||
|
} else { |
||||||
|
this.database = null; |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
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 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)); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
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 (this.database) { |
||||||
|
this.database.put("contacts", { |
||||||
|
id: this.id(contact.Userid), |
||||||
|
contact: this.encrypt(contact) |
||||||
|
}) |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Contacts.prototype.remove = function(userid) { |
||||||
|
var contact = contactData.get(userid); |
||||||
|
console.log("contacts remove", userid, contact); |
||||||
|
if (contact) { |
||||||
|
contactData.remove(userid); |
||||||
|
if (this.database) { |
||||||
|
this.database.delete("contacts", this.id(userid)); |
||||||
|
} |
||||||
|
this.e.triggerHandler("contactremoved", contact); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return new Contacts(); |
||||||
|
|
||||||
|
}]; |
||||||
|
|
||||||
|
}); |
||||||
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
* |
||||||
|
*/ |
||||||
|
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<ca.length; i++) { |
||||||
|
var c = ca[i].trim(); |
||||||
|
if (c.indexOf(name) === 0) { |
||||||
|
return c.substring(name.length, c.length); |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
}; |
||||||
|
PersistentStorage.prototype.removeItem = function(key) { |
||||||
|
var name = this.prefix+"_"+key; |
||||||
|
$window.document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/"; |
||||||
|
}; |
||||||
|
|
||||||
|
var storage; |
||||||
|
if (Modernizr.localstorage) { |
||||||
|
storage = $window.localStorage; |
||||||
|
} else { |
||||||
|
storage = new PersistentStorage(); |
||||||
|
} |
||||||
|
|
||||||
|
// public API.
|
||||||
|
return { |
||||||
|
setItem: function(key, data) { |
||||||
|
return storage.setItem(key, data); |
||||||
|
}, |
||||||
|
getItem: function(key) { |
||||||
|
return storage.getItem(key); |
||||||
|
}, |
||||||
|
removeItem: function(key) { |
||||||
|
return storage.removeItem(key); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
}]; |
||||||
|
|
||||||
|
}); |
||||||
@ -1,5 +1,5 @@ |
|||||||
<div class="buddy withSubline"> |
<div class="buddy" ng-class="{'contact': contact, 'withSubline': display.subline || session.Userid}"> |
||||||
<div class="buddyPicture"><i class="fa fa-user fa-3x"/><img ng-show="status.buddyPicture" alt ng-src="{{status.buddyPicture}}" width="46" height="46"/></div> |
<div class="buddyPicture"><i class="fa fa-user fa-3x"/><img ng-show="display.buddyPicture" alt ng-src="{{display.buddyPicture}}" width="46" height="46"/></div> |
||||||
<div class="buddy1">{{session.Id|displayName}}</div> |
<div class="buddy1">{{session.Id|displayName}}</div> |
||||||
<div class="buddy2"><i ng-show="session.Userid" class="fa fa-star-o"></i> {{session.Ua}}</div> |
<div class="buddy2"><span ng-show="session.Userid"><i class="fa contact" data-action="contact"></i><span ng-show="session.count"> ({{session.count}})</span></span> <span title="{{display.sublineFull}}">{{display.subline}}</span></div> |
||||||
</div> |
</div> |
||||||
|
|||||||
@ -1,4 +1,11 @@ |
|||||||
|
<div class="buddyhover"> |
||||||
<div class="buddyactions active"> |
<div class="buddyactions active"> |
||||||
<a class="btn btn-info" title="{{_('Start video call')}}"><i class="fa fa-eye"></i></a> |
<a class="btn btn-info" data-action="call" title="{{_('Start video call')}}"><i class="fa fa-eye"></i></a> |
||||||
<a class="btn btn-info" title="{{_('Start chat')}}" ng-click="doChat(session.Id); $event.stopPropagation()"><i class="fa fa-comments-o"></i></a> |
<a class="btn btn-info" data-action="chat" title="{{_('Start chat')}}"><i class="fa fa-comments-o"></i></a> |
||||||
|
</div> |
||||||
|
<div class="buddysessions" ng-if="session.count>1"> |
||||||
|
<ul> |
||||||
|
<li ng-repeat="(id, s) in session.sessions" ng-class="{currentsession: s.Id === session.Id}"><!--<div class="buddyPicture buddyPictureSmall"><i class="fa fa-user fa-2x"/><img ng-show="s.Status.buddyPicture" alt ng-src="{{s.Status.buddyPicture}}" width="30" height="30"/></div>--><span class="btn-group"><a class="btn btn-sm btn-default buddy3" ng-click="$event.stopPropagation()"><span>{{s.Status.displayName}}, {{s.Message}}</span></a><a class="btn btn-sm btn-default" title="{{_('Start video call')}}" ng-click="doCall(s.Id); $event.stopPropagation()"><i class="fa fa-eye"></i></a><a class="btn btn-sm btn-default" title="{{_('Start chat')}}" ng-click="doChat(s.Id); $event.stopPropagation()"><i class="fa fa-comments-o"></i></a></span></li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
</div> |
</div> |
||||||
|
|||||||
@ -0,0 +1,10 @@ |
|||||||
|
<div class="contact-request"> |
||||||
|
<div ng-if="!fromself && request.Userid && !request.Success && request.Token"> |
||||||
|
<div ng-switch="state"> |
||||||
|
<div ng-switch-when="request"> |
||||||
|
<button ng-click="doAccept()" class="btn btn-success btn-sm">Accept</button> |
||||||
|
<button ng-click="doReject()" class="btn btn-danger btn-sm">Reject</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
Loading…
Reference in new issue