vn-softphone versión inicial
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
datasources.development.json
|
|
@ -0,0 +1,66 @@
|
||||||
|
var database = require('./database.js');
|
||||||
|
let config = require('./config.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize auth
|
||||||
|
* @param {Object} request - Request object
|
||||||
|
* @param {Object} response - Response object
|
||||||
|
* @param {Object} next - Next object
|
||||||
|
*/
|
||||||
|
init: function(request, response, next) {
|
||||||
|
this.request = request;
|
||||||
|
this.response = response;
|
||||||
|
this.next = next;
|
||||||
|
|
||||||
|
this.validateToken();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate auth token
|
||||||
|
*/
|
||||||
|
validateToken: function() {
|
||||||
|
let query = 'SELECT userId, ttl, created FROM salix.AccessToken WHERE id = ?';
|
||||||
|
|
||||||
|
database.pool.query(query, [this.getToken()], (error, result) => {
|
||||||
|
let token = result[0];
|
||||||
|
|
||||||
|
if (error || result.length == 0)
|
||||||
|
return this.response.status(401).send({message: 'Invalid token'});
|
||||||
|
|
||||||
|
if (this.isTokenExpired(token.created, token.ttl))
|
||||||
|
return this.response.status(401).send({message: 'Token expired'});
|
||||||
|
|
||||||
|
this.request.user = {
|
||||||
|
id: token.userId,
|
||||||
|
token: this.getToken()
|
||||||
|
};
|
||||||
|
this.next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get request token
|
||||||
|
* @return {String} Token
|
||||||
|
*/
|
||||||
|
getToken: function() {
|
||||||
|
return this.request.headers.authorization || this.request.query.token;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the token has expired
|
||||||
|
* @param {String} created - Creation date
|
||||||
|
* @param {Integer} ttl - Ttl seconds
|
||||||
|
* @return {Boolean} True if the token has expired
|
||||||
|
*/
|
||||||
|
isTokenExpired: function(created, ttl) {
|
||||||
|
let date = new Date(created);
|
||||||
|
let currentDate = new Date();
|
||||||
|
|
||||||
|
date.setSeconds(date.getSeconds() + ttl);
|
||||||
|
|
||||||
|
if (currentDate > date)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
var path = require('path');
|
||||||
|
let configPath = path.join(__dirname, 'config/datasources.json');
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV != 'production')
|
||||||
|
configPath = path.join(__dirname, 'config/datasources.development.json');
|
||||||
|
|
||||||
|
module.exports = require(configPath);
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"port": 80,
|
||||||
|
"audioVolume": 0.5,
|
||||||
|
"microVolume": 0.5
|
||||||
|
},
|
||||||
|
"sip": {
|
||||||
|
"mediaConstraints": {
|
||||||
|
"audio": true,
|
||||||
|
"video": false
|
||||||
|
},
|
||||||
|
"session_timers": false
|
||||||
|
},
|
||||||
|
"mysql": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 3306,
|
||||||
|
"user": "",
|
||||||
|
"password": "",
|
||||||
|
"database": ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
var mysql = require('mysql');
|
||||||
|
let config = require('./config.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* Pool instance
|
||||||
|
*/
|
||||||
|
pool: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start database pool
|
||||||
|
*/
|
||||||
|
init: function() {
|
||||||
|
this.pool = mysql.createPool(config.mysql);
|
||||||
|
|
||||||
|
this.pool.getConnection(function(error, connection) {
|
||||||
|
if (error)
|
||||||
|
throw new Error('Can\'t connect to database: ' + error.code);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,18 @@
|
||||||
|
let express = require('express');
|
||||||
|
let app = express();
|
||||||
|
let config = require('./config.js'),
|
||||||
|
database = require('./database.js'),
|
||||||
|
auth = require('./auth.js');
|
||||||
|
|
||||||
|
app.engine('html', require('hogan-express'));
|
||||||
|
app.set('view engine', 'html');
|
||||||
|
app.use('/static', express.static('static'));
|
||||||
|
app.use(function(request, response, next) {
|
||||||
|
auth.init(request, response, next);
|
||||||
|
});
|
||||||
|
app.use('/', require('./router.js'));
|
||||||
|
|
||||||
|
app.listen(config.app.port, function() {
|
||||||
|
database.init();
|
||||||
|
console.log(`Softphone started at http://127.0.0.1:${config.app.port}`);
|
||||||
|
});
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "vn-softphone",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Softphone service",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "node index.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.verdnatura.es/?p=vn-softphone;a=summary"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"vn-softphone"
|
||||||
|
],
|
||||||
|
"author": "VerdNatura",
|
||||||
|
"license": "SEE LICENSE IN LICENSE.MD",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.16.2",
|
||||||
|
"hogan-express": "^0.5.2",
|
||||||
|
"mysql": "^2.15.0",
|
||||||
|
"path": "^0.12.7"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
let router = require('express')();
|
||||||
|
let path = require('path');
|
||||||
|
let view = require('./view.js');
|
||||||
|
|
||||||
|
router.get('/', function(request, response) {
|
||||||
|
view.render('home', {id: request.user.id}, response);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
|
@ -0,0 +1,206 @@
|
||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: #292929;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 65.5px 0 0 0;
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
min-height: 100%;
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
min-height: 100%;
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickbar {
|
||||||
|
background-color: #3C393B;
|
||||||
|
position: absolute;
|
||||||
|
padding: 15px;
|
||||||
|
width: 100%;
|
||||||
|
top: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickbar a {
|
||||||
|
text-decoration: none;
|
||||||
|
margin-left: 10px;
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickbar a:hover {
|
||||||
|
color: #ff6e40
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickbar .pull-right a i {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side {
|
||||||
|
background: #2C2C2C;
|
||||||
|
height: 100%;
|
||||||
|
width: 15%;
|
||||||
|
float: left
|
||||||
|
}
|
||||||
|
|
||||||
|
.side .user {
|
||||||
|
padding: 15px;
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.side .user .extension {
|
||||||
|
font-size: 12px
|
||||||
|
}
|
||||||
|
|
||||||
|
.side .user:after {
|
||||||
|
display: block;
|
||||||
|
content: ' ';
|
||||||
|
clear: both
|
||||||
|
}
|
||||||
|
|
||||||
|
.side .user .name {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.side .title {
|
||||||
|
background-color: #333333;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 15px 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #111111
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.side .navigation a {
|
||||||
|
background-color: #ff9e00;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: 0.2s all;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 24px;
|
||||||
|
display: block;
|
||||||
|
padding: 15px;
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.side .navigation a.active, .side .navigation a.active:hover {
|
||||||
|
background-color: #FFF;
|
||||||
|
color: #FFA410
|
||||||
|
}
|
||||||
|
|
||||||
|
.side .navigation a:hover {
|
||||||
|
background-color: #ffb030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side .navigation a i {
|
||||||
|
margin-right: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.side .audioControls {
|
||||||
|
position:absolute;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 15px;
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.chatbox .search {
|
||||||
|
margin-bottom: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbox .search .control {
|
||||||
|
-webkit-border-radius: 15px;
|
||||||
|
-moz-border-radius: 15px;
|
||||||
|
border-radius: 15px;
|
||||||
|
background: #232323;
|
||||||
|
padding: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbox .search input[type=text] {
|
||||||
|
background-color: transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #FFF;
|
||||||
|
border: 0px;
|
||||||
|
|
||||||
|
width:100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbox .callTimer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbox .callTimer span {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbox {
|
||||||
|
background-color: #292929;
|
||||||
|
height: 100%;
|
||||||
|
width: 85%;
|
||||||
|
float: left
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbox .navigation {
|
||||||
|
padding: 15px;
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
#notifications {
|
||||||
|
position: fixed;
|
||||||
|
overflow: hidden;
|
||||||
|
left: 50%;
|
||||||
|
max-height: 200px;
|
||||||
|
width: 500px;
|
||||||
|
margin-left: -250px;
|
||||||
|
top: 80px
|
||||||
|
}
|
||||||
|
|
||||||
|
#notifications .dialog {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
text-align:center;
|
||||||
|
-moz-border-radius:3px;
|
||||||
|
-webkit-border-radius:3px;
|
||||||
|
border-radius:3px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.dial {
|
||||||
|
width: 500px;
|
||||||
|
margin: 100px auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.dial .callStatus {
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: center;
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.dial .btn {
|
||||||
|
margin-bottom: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.dial .columns .column {
|
||||||
|
padding-right: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.dial .columns .column:last-child {
|
||||||
|
padding-right: 0
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
.columns:after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
clear: both
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns .column {
|
||||||
|
float: left
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns .column.x5 {
|
||||||
|
width: 5%
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns .column.x10 {
|
||||||
|
width: 10%
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns .column.x15 {
|
||||||
|
width: 15%
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns .column.x20 {
|
||||||
|
width: 20%
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns .column.x25 {
|
||||||
|
width: 25%
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns .column.x33 {
|
||||||
|
width: 33.33%
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns .column.x50 {
|
||||||
|
width: 50%
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns .column.x75 {
|
||||||
|
width: 75%
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns .column.x80 {
|
||||||
|
width: 80%
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns .column.x100 {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-left {
|
||||||
|
float: left
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-center {
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-right {
|
||||||
|
float: right
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
text-align: center;
|
||||||
|
display: block
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn, .btn:hover {
|
||||||
|
text-decoration: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn i {
|
||||||
|
font-size: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.small {
|
||||||
|
padding: 2px 10px;
|
||||||
|
font-size: 12px
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.small.icon {
|
||||||
|
padding: 1.75px 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.normal {
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-size: 14px
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.normal.icon {
|
||||||
|
padding: 4.75px 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.big {
|
||||||
|
padding: 8.5px 15px;
|
||||||
|
font-size: 24px
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.big.icon {
|
||||||
|
padding: 7.75px 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.white {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.white:hover {
|
||||||
|
border: 1px solid #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.green {
|
||||||
|
border: 1px solid rgba(110, 198, 47, 0.5);
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.green:hover {
|
||||||
|
border: 1px solid #6ec62f
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.red {
|
||||||
|
border: 1px solid rgba(198, 47, 47, 0.5);
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.red:hover {
|
||||||
|
border: 1px solid #c62f2f
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.yellow {
|
||||||
|
border: 1px solid rgba(255, 158, 0, 0.5);
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.yellow:hover {
|
||||||
|
border: 1px solid #ff9e00
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.disabled {
|
||||||
|
border: 1px solid rgba(31, 36, 40, 0.5);
|
||||||
|
color: #FFF
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M12 19c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM6 1c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12-8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-6 8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 649 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z"/>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 355 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 0h24v24H0zm0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M19 11h-1.7c0 .74-.16 1.43-.43 2.05l1.23 1.23c.56-.98.9-2.09.9-3.28zm-4.02.17c0-.06.02-.11.02-.17V5c0-1.66-1.34-3-3-3S9 3.34 9 5v.18l5.98 5.99zM4.27 3L3 4.27l6.01 6.01V11c0 1.66 1.33 3 2.99 3 .22 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c.91-.13 1.77-.45 2.54-.9L19.73 21 21 19.73 4.27 3z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 533 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 404 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M17 3h-2v7h2V3zm3 12.5c-1.25 0-2.45-.2-3.57-.57-.35-.11-.74-.03-1.02.24l-2.2 2.2c-2.83-1.44-5.15-3.75-6.59-6.59l2.2-2.21c.28-.26.36-.65.25-1C8.7 6.45 8.5 5.25 8.5 4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.5c0-.55-.45-1-1-1zM19 3v7h2V3h-2z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 437 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8 0-1.85.63-3.55 1.69-4.9L16.9 18.31C15.55 19.37 13.85 20 12 20zm6.31-3.1L7.1 5.69C8.45 4.63 10.15 4 12 4c4.42 0 8 3.58 8 8 0 1.85-.63 3.55-1.69 4.9z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 402 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M13 9h-2v2h2V9zm4 0h-2v2h2V9zm3 6.5c-1.25 0-2.45-.2-3.57-.57-.35-.11-.74-.03-1.02.24l-2.2 2.2c-2.83-1.44-5.15-3.75-6.59-6.58l2.2-2.21c.28-.27.36-.66.25-1.01C8.7 6.45 8.5 5.25 8.5 4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.5c0-.55-.45-1-1-1zM19 9v2h2V9h-2z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 453 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 265 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08c-.18-.17-.29-.42-.29-.7 0-.28.11-.53.29-.71C3.34 8.78 7.46 7 12 7s8.66 1.78 11.71 4.67c.18.18.29.43.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.11-.7-.28-.79-.74-1.69-1.36-2.67-1.85-.33-.16-.56-.5-.56-.9v-3.1C15.15 9.25 13.6 9 12 9z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 552 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M18 11l5-5-5-5v3h-4v4h4v3zm2 4.5c-1.25 0-2.45-.2-3.57-.57-.35-.11-.74-.03-1.02.24l-2.2 2.2c-2.83-1.44-5.15-3.75-6.59-6.59l2.2-2.21c.28-.26.36-.65.25-1C8.7 6.45 8.5 5.25 8.5 4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.5c0-.55-.45-1-1-1z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 432 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7 9v6h4l5 5V4l-5 5H7z"/>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 186 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 351 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"/>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 252 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 515 B |
|
@ -0,0 +1,76 @@
|
||||||
|
let audio = {
|
||||||
|
|
||||||
|
audioLoop: null,
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
if (ua.audioInstance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ua.audioInstance = document.createElement('audio');
|
||||||
|
},
|
||||||
|
|
||||||
|
addTrack: function(track) {
|
||||||
|
if (ua.audioInstance)
|
||||||
|
this.stop();
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
|
||||||
|
ua.audioInstance.src = this.getTrackPath(track);
|
||||||
|
},
|
||||||
|
|
||||||
|
addStream: function(stream) {
|
||||||
|
if (ua.audioInstance)
|
||||||
|
this.stop();
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
|
||||||
|
ua.audioInstance.srcObject = stream;
|
||||||
|
},
|
||||||
|
|
||||||
|
play: function(loop = false) {
|
||||||
|
if (!ua.audioInstance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ua.audioInstance.volume = Softphone.getVolume();
|
||||||
|
|
||||||
|
ua.audioInstance.play();
|
||||||
|
|
||||||
|
if (loop) {
|
||||||
|
this.audioLoop = setInterval(function() {
|
||||||
|
ua.audioInstance.play();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stop: function() {
|
||||||
|
if (!ua.audioInstance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.audioLoop)
|
||||||
|
clearInterval(this.audioLoop);
|
||||||
|
|
||||||
|
ua.audioInstance.pause();
|
||||||
|
ua.audioInstance = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
isPlaying: function() {
|
||||||
|
if (!ua.audioInstance.paused)
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
getTrackPath: function(track) {
|
||||||
|
return `/static/audio/${track}.wav`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make new call
|
||||||
|
*/
|
||||||
|
$('call').addEventListener('click', function(event) {
|
||||||
|
if (ua.sessionInstance && ua.sessionInstance.direction === 'incoming')
|
||||||
|
ua.sessionInstance.answer(options);
|
||||||
|
|
||||||
|
let number = $('callNumber').value;
|
||||||
|
$('callNumber').value = '';
|
||||||
|
|
||||||
|
if (number != '') {
|
||||||
|
Softphone.call(number);
|
||||||
|
} else {
|
||||||
|
notify('Introduce un destinatario para realizar la llamada');
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset call number field
|
||||||
|
*/
|
||||||
|
$('clear').addEventListener('click', function(event) {
|
||||||
|
$('callNumber').value = '';
|
||||||
|
|
||||||
|
audio.addTrack('reset');
|
||||||
|
audio.play();
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End a call
|
||||||
|
*/
|
||||||
|
$('callHangup').addEventListener('click', function(event) {
|
||||||
|
Softphone.hangup();
|
||||||
|
|
||||||
|
audio.addTrack('hangup');
|
||||||
|
audio.play();
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* *
|
||||||
|
* Insert key on call field
|
||||||
|
*/
|
||||||
|
$('zero').addEventListener('click', padKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert key on call field
|
||||||
|
*/
|
||||||
|
$('one').addEventListener('click', padKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert key on call field
|
||||||
|
*/
|
||||||
|
$('two').addEventListener('click', padKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert key on call field
|
||||||
|
*/
|
||||||
|
$('three').addEventListener('click', padKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert key on call field
|
||||||
|
*/
|
||||||
|
$('four').addEventListener('click', padKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert key on call field
|
||||||
|
*/
|
||||||
|
$('five').addEventListener('click', padKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert key on call field
|
||||||
|
*/
|
||||||
|
$('six').addEventListener('click', padKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert key on call field
|
||||||
|
*/
|
||||||
|
$('seven').addEventListener('click', padKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert key on call field
|
||||||
|
*/
|
||||||
|
$('eight').addEventListener('click', padKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert key on call field
|
||||||
|
*/
|
||||||
|
$('nine').addEventListener('click', padKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert key on call field
|
||||||
|
*/
|
||||||
|
$('asterisk').addEventListener('click', padKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert key on call field
|
||||||
|
*/
|
||||||
|
$('pad').addEventListener('click', padKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transfer current call
|
||||||
|
*/
|
||||||
|
$('callTransfer').addEventListener('click', function(event) {
|
||||||
|
let number = document.getElementById('callNumber').value;
|
||||||
|
|
||||||
|
if (number != '') {
|
||||||
|
Softphone.transfer(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hold current call
|
||||||
|
*/
|
||||||
|
$('callPause').addEventListener('click', function(event) {
|
||||||
|
if (Softphone.isOnHold()) {
|
||||||
|
Softphone.unhold();
|
||||||
|
} else {
|
||||||
|
Softphone.hold();
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not disturb
|
||||||
|
*/
|
||||||
|
$('doNotDisturb').addEventListener('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call settings
|
||||||
|
*/
|
||||||
|
$('callConfig').addEventListener('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change volume action
|
||||||
|
*/
|
||||||
|
$('volume').addEventListener('change', function(event) {
|
||||||
|
if (!ua.callInstance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ua.audioInstance.volume = this.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('mic').addEventListener('click', function(event) {
|
||||||
|
if(Softphone.isMuted()) {
|
||||||
|
Softphone.unmute();
|
||||||
|
} else {
|
||||||
|
Softphone.mute();
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pad keys
|
||||||
|
* @param {Object} event Event object
|
||||||
|
*/
|
||||||
|
function padKey(event) {
|
||||||
|
let key = event.target.innerHTML;
|
||||||
|
let keyName = event.target.id;
|
||||||
|
|
||||||
|
$('callNumber').value += key;
|
||||||
|
|
||||||
|
audio.addTrack(keyName);
|
||||||
|
audio.play();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent page reload without confirmation
|
||||||
|
*/
|
||||||
|
window.addEventListener("beforeunload", function (event) {
|
||||||
|
event.returnValue = "\o/";
|
||||||
|
});
|
|
@ -0,0 +1,111 @@
|
||||||
|
let Softphone = {
|
||||||
|
/**
|
||||||
|
* Make a new call
|
||||||
|
*/
|
||||||
|
call: function(number) {
|
||||||
|
if (ua.sessionInstance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ua.sessionInstance = ua.call(`sip:${number}@${uriHost}`, options);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hang up current call
|
||||||
|
*/
|
||||||
|
hangup: function() {
|
||||||
|
if (!ua.sessionInstance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ua.sessionInstance.terminate();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mute current call
|
||||||
|
*/
|
||||||
|
mute: function() {
|
||||||
|
if (!ua.sessionInstance)
|
||||||
|
return
|
||||||
|
|
||||||
|
ua.sessionInstance.mute();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unmute current call
|
||||||
|
*/
|
||||||
|
unmute: function() {
|
||||||
|
if (!ua.sessionInstance)
|
||||||
|
return
|
||||||
|
|
||||||
|
ua.sessionInstance.unmute();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get if current call is muted
|
||||||
|
*/
|
||||||
|
isMuted: function() {
|
||||||
|
if (!ua.sessionInstance)
|
||||||
|
return
|
||||||
|
|
||||||
|
return ua.sessionInstance.isMuted();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hold current call
|
||||||
|
*/
|
||||||
|
hold: function() {
|
||||||
|
if (!ua.sessionInstance)
|
||||||
|
return
|
||||||
|
|
||||||
|
ua.sessionInstance.hold();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unhold current call
|
||||||
|
*/
|
||||||
|
unhold: function() {
|
||||||
|
if (!ua.sessionInstance)
|
||||||
|
return
|
||||||
|
|
||||||
|
ua.sessionInstance.unhold();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get if current call is on hold
|
||||||
|
*/
|
||||||
|
isOnHold: function() {
|
||||||
|
if (!ua.sessionInstance)
|
||||||
|
return
|
||||||
|
|
||||||
|
return ua.sessionInstance.isOnHold().local;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transfer current call
|
||||||
|
*/
|
||||||
|
transfer: function(number) {
|
||||||
|
if (!ua.sessionInstance)
|
||||||
|
return
|
||||||
|
|
||||||
|
ua.sessionInstance.refer(`sip:${number}@${uriHost}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get volume
|
||||||
|
*/
|
||||||
|
getVolume: function() {
|
||||||
|
return options.volume;
|
||||||
|
},
|
||||||
|
|
||||||
|
isMicEnabled: function() {
|
||||||
|
if (options.microphone)
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
getStatus: function() {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
setStatus: function() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
const socketHost = 'ws://pbx.dyn.verdnatura.es:8088/asterisk/ws';
|
||||||
|
const uriHost = 'pbx.dyn.verdnatura.es';
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
mediaConstraints: {
|
||||||
|
audio: true,
|
||||||
|
video: false
|
||||||
|
},
|
||||||
|
session_timers: false,
|
||||||
|
volume: 0.5,
|
||||||
|
microphone: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let socket = new JsSIP.WebSocketInterface(socketHost);
|
||||||
|
let configuration = {
|
||||||
|
sockets : [ socket ],
|
||||||
|
uri : 'sip:8000@pbx.dyn.verdnatura.es',
|
||||||
|
password : '123456',
|
||||||
|
authorizationUser: '8000',
|
||||||
|
hackIpInContact: true,
|
||||||
|
rtcpMuxPolicy: 'negotiate',
|
||||||
|
hackWssInTransport: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let ua = new JsSIP.UA(configuration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show event message
|
||||||
|
* @param {Object} event
|
||||||
|
*/
|
||||||
|
function eventStatus(event) {
|
||||||
|
if (event.cause === JsSIP.C.causes.CONNECTION_ERROR)
|
||||||
|
return notify('Ha ocurrido un error de conexión');
|
||||||
|
|
||||||
|
if (event.cause === JsSIP.C.causes.INTERNAL_ERROR)
|
||||||
|
return notify('Ha ocurrido un error inesperado');
|
||||||
|
|
||||||
|
if (event.cause === JsSIP.C.causes.AUTHENTICATION_ERROR)
|
||||||
|
return notify('No se ha podido autenticar la sesión');
|
||||||
|
|
||||||
|
if (event.cause === JsSIP.C.causes.UNAVAILABLE)
|
||||||
|
return notify('El destinatario no está disponible');
|
||||||
|
|
||||||
|
if (event.cause === JsSIP.C.causes.BUSY)
|
||||||
|
return notify('El destinatario está ocupado');
|
||||||
|
|
||||||
|
if (event.cause === JsSIP.C.causes.REJECTED)
|
||||||
|
return notify('La conexión ha sido rechazada')
|
||||||
|
|
||||||
|
if (event.cause === JsSIP.C.causes.NOT_FOUND)
|
||||||
|
return notify('No se ha podido encontrar el destinatario');
|
||||||
|
|
||||||
|
if (event.cause === JsSIP.C.causes.REQUEST_TIMEOUT)
|
||||||
|
return notify('El destinatario no ha respondido la llamada');
|
||||||
|
|
||||||
|
if (event.cause === JsSIP.USER_DENIED_MEDIA_ACCESS)
|
||||||
|
return notify('Es necesario permitir el acceso al micrófono');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UA register
|
||||||
|
*/
|
||||||
|
ua.on('registered', function(event) {
|
||||||
|
$('callStatus').innerHTML = 'Conectado';
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client disconnection
|
||||||
|
*/
|
||||||
|
ua.on('disconnected', function(event) {
|
||||||
|
$('callStatus').innerHTML = 'Desconectado';
|
||||||
|
});
|
||||||
|
|
||||||
|
ua.on('registrationFailed', function(event) {
|
||||||
|
|
||||||
|
Softphone.notifyStatus(event);
|
||||||
|
|
||||||
|
$('callStatus').innerHTML = 'Desconectado';
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call session
|
||||||
|
*/
|
||||||
|
ua.on('newRTCSession', function(event) {
|
||||||
|
let session = event.session;
|
||||||
|
ua.sessionInstance = session;
|
||||||
|
|
||||||
|
console.log(session);
|
||||||
|
|
||||||
|
session.on('confirmed', function() {
|
||||||
|
console.log('confirmed');
|
||||||
|
})
|
||||||
|
|
||||||
|
session.on('reinvite', function() {
|
||||||
|
console.log('reinvite');
|
||||||
|
})
|
||||||
|
|
||||||
|
session.on('update', function() {
|
||||||
|
console.log('update');
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Call in progress
|
||||||
|
*/
|
||||||
|
session.on('progress', function(event) {
|
||||||
|
console.log('progress');
|
||||||
|
if (session.direction === 'incoming') {
|
||||||
|
audio.addTrack('incoming');
|
||||||
|
audio.play(true);
|
||||||
|
$('callStatus').innerHTML = 'Llamada entrante...';
|
||||||
|
} else {
|
||||||
|
audio.addTrack('outgoing');
|
||||||
|
audio.play(true);
|
||||||
|
$('callStatus').innerHTML = 'Llamando...';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call accepted
|
||||||
|
*/
|
||||||
|
session.on('accepted', function(event) {
|
||||||
|
console.log('accepted');
|
||||||
|
$('callStatus').innerHTML = 'Llamada conectada';
|
||||||
|
if (session.connection.getRemoteStreams().length > 0) {
|
||||||
|
let stream = session.connection.getRemoteStreams()[0];
|
||||||
|
|
||||||
|
audio.addStream(stream);
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
startTimer();
|
||||||
|
});
|
||||||
|
|
||||||
|
session.on('hold', function(event) {
|
||||||
|
$('callStatus').innerHTML = 'Llamada en espera';
|
||||||
|
});
|
||||||
|
|
||||||
|
session.on('unhold', function(event) {
|
||||||
|
$('callStatus').innerHTML = 'Llamada conectada';
|
||||||
|
});
|
||||||
|
|
||||||
|
session.on('refer', function() {
|
||||||
|
$('callStatus').innerHTML = 'Llamada transferida';
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call ended
|
||||||
|
*/
|
||||||
|
session.on('ended', function(event) {
|
||||||
|
audio.addTrack('hangup');
|
||||||
|
audio.play();
|
||||||
|
|
||||||
|
stopTimer();
|
||||||
|
|
||||||
|
$('callStatus').innerHTML = 'Conectado';
|
||||||
|
ua.sessionInstance = null;
|
||||||
|
eventStatus(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call failed
|
||||||
|
*/
|
||||||
|
session.on('failed', function(event) {
|
||||||
|
audio.addTrack('hangup');
|
||||||
|
audio.play();
|
||||||
|
|
||||||
|
$('callStatus').innerHTML = 'Conectado';
|
||||||
|
ua.sessionInstance = null;
|
||||||
|
eventStatus(event);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ua.start();
|
|
@ -0,0 +1,57 @@
|
||||||
|
let timer;
|
||||||
|
let timerSeconds = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object selector
|
||||||
|
*/
|
||||||
|
function $(object) {
|
||||||
|
return document.getElementById(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start call timer
|
||||||
|
*/
|
||||||
|
function startTimer() {
|
||||||
|
timer = setInterval(function() {
|
||||||
|
let date = new Date(null);
|
||||||
|
date.setSeconds(timerSeconds);
|
||||||
|
|
||||||
|
$('timer').innerHTML = date.toISOString().substr(11, 8);
|
||||||
|
timerSeconds++;
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop call timer
|
||||||
|
*/
|
||||||
|
function stopTimer() {
|
||||||
|
clearInterval(timer);
|
||||||
|
timer = null;
|
||||||
|
timerSeconds = 0;
|
||||||
|
$('timer').innerHTML = '00:00:00';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function notify(message) {
|
||||||
|
if (!$('notifications')) {
|
||||||
|
let container = document.createElement('div');
|
||||||
|
container.setAttribute('id', 'notifications');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dialog = document.createElement('div');
|
||||||
|
let text = document.createTextNode(message);
|
||||||
|
dialog.appendChild(text);
|
||||||
|
dialog.setAttribute('class', 'dialog');
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
dialog.remove();
|
||||||
|
}, 8000);
|
||||||
|
|
||||||
|
|
||||||
|
$('notifications').insertBefore(dialog, $('notifications').firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showStatus(status) {
|
||||||
|
$('callStatus').innerHTML = 'Desconectado';
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach model and render view
|
||||||
|
* @param {String} view View name
|
||||||
|
* @param {Object} response Http response
|
||||||
|
*/
|
||||||
|
render: function(view, args, response) {
|
||||||
|
let instance = require(`./views/${view}/index.js`);
|
||||||
|
|
||||||
|
instance.init(args, () => {
|
||||||
|
this.getPartials(instance, args, (instance) => {
|
||||||
|
response.render(`${view}/index`, instance);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach partial models
|
||||||
|
* @param {Object} instance Main instance
|
||||||
|
* @return {Object} Merged instance
|
||||||
|
*/
|
||||||
|
getPartials: function(instance, args, cb) {
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
for(let partial in instance.partials) {
|
||||||
|
let subInstance = require(`./views/${partial}/index.js`);
|
||||||
|
|
||||||
|
subInstance.init(args, () => {
|
||||||
|
Object.assign(instance, subInstance);
|
||||||
|
index++;
|
||||||
|
|
||||||
|
if (index == Object.keys(instance.partials).length)
|
||||||
|
cb(instance);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<title>Softphone</title>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/css/app.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/css/components.css"/>
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
<script type="text/javascript" src="/static/js/jssip.js?v=1"></script>
|
||||||
|
<script type="text/javascript" src="/static/js/ua.js?v=1"></script>
|
||||||
|
<script type="text/javascript" src="/static/js/audio.js?v=1"></script>
|
||||||
|
<script type="text/javascript" src="/static/js/softphone.js?v=1"></script>
|
||||||
|
<script type="text/javascript" src="/static/js/util.js?v=1"></script>
|
||||||
|
<script type="text/javascript" src="/static/js/event.js?v=1"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Quickbar block start -->
|
||||||
|
<div class="quickbar">
|
||||||
|
<img src="http://salix.verdnatura.es/static/34777fb85a1496d64469bd8092a53ef6.svg"/>
|
||||||
|
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="">
|
||||||
|
<i class="material-icons">settings</i>
|
||||||
|
</a>
|
||||||
|
<a href="">
|
||||||
|
<i class="material-icons">language</i>
|
||||||
|
</a>
|
||||||
|
<a href="">
|
||||||
|
<i class="material-icons">exit_to_app</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Quickbar block end -->
|
|
@ -0,0 +1,7 @@
|
||||||
|
module.exports = {
|
||||||
|
init: function(args, cb) {
|
||||||
|
this.title = 'Test';
|
||||||
|
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
{{> header}}
|
||||||
|
{{> side}}
|
||||||
|
|
||||||
|
<!-- Chatbox block start -->
|
||||||
|
<div class="chatbox">
|
||||||
|
<div class="dial">
|
||||||
|
<div class="search">
|
||||||
|
<div class="control">
|
||||||
|
<input type="text" placeholder="Introduce un número..." id="callNumber"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="callStatus" id="callStatus"></div>
|
||||||
|
<div class="callTimer">
|
||||||
|
<span id="timer">00:00:00</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Controls block start -->
|
||||||
|
<div class="controls">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column x33">
|
||||||
|
<a href="" class="btn big icon green" id="call" title="LLamar">
|
||||||
|
<i class="material-icons">call</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="column x33">
|
||||||
|
<a href="" class="btn big icon white" id="clear" title="Vaciar campo de llamada">
|
||||||
|
<i class="material-icons">clear_all</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="column x33">
|
||||||
|
<a href="" class="btn big icon red" id="callHangup" title="Colgar">
|
||||||
|
<i class="material-icons">call_end</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column x25">
|
||||||
|
<a href="" class="btn big white pad" id="one">1</a>
|
||||||
|
<a href="" class="btn big white pad" id="four">4</a>
|
||||||
|
<a href="" class="btn big white" id="seven">7</a>
|
||||||
|
<a href="" class="btn big white" id="asterisk">*</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column x25">
|
||||||
|
<a href="" class="btn big white pad" id="two">2</a>
|
||||||
|
<a href="" class="btn big white pad" id="five">5</a>
|
||||||
|
<a href="" class="btn big white pad" id="eight">8</a>
|
||||||
|
<a href="" class="btn big white pad" id="zero">0</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column x25">
|
||||||
|
<a href="" class="btn big white pad" id="three">3</a>
|
||||||
|
<a href="" class="btn big white pad" id="six">6</a>
|
||||||
|
<a href="" class="btn big white pad" id="nine">9</a>
|
||||||
|
<a href="" class="btn big white pad" id="pad">#</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column x25">
|
||||||
|
<a href="" class="btn big icon white" id="callTransfer" title="Transferir llamada">
|
||||||
|
<i class="material-icons">phone_forwarded</i>
|
||||||
|
</a>
|
||||||
|
<a href="" class="btn big icon white" id="callPause" title="Pausar llamada">
|
||||||
|
<i class="material-icons">phone_paused</i>
|
||||||
|
</a>
|
||||||
|
<a href="" class="btn big icon white" id="doNotDisturb" title="No molestar">
|
||||||
|
<i class="material-icons">phone_locked</i>
|
||||||
|
</a>
|
||||||
|
<a href="" class="btn big icon white" id="callConfig" title="Configuración">
|
||||||
|
<i class="material-icons">settings_phone</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="" class="btn normal yellow">
|
||||||
|
<i class="material-icons">add</i> Nuevo contacto</a>
|
||||||
|
<a href="" class="btn normal yellow">
|
||||||
|
<i class="material-icons">search</i>Buscar contacto</a>
|
||||||
|
<a href="" class="btn normal yellow">Ver historial</a>
|
||||||
|
</div>
|
||||||
|
<!-- Controls block end -->
|
||||||
|
</div>
|
||||||
|
<!-- Chatbox block end -->
|
|
@ -0,0 +1,11 @@
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
init: function(args, cb) {
|
||||||
|
cb();
|
||||||
|
},
|
||||||
|
|
||||||
|
partials: {
|
||||||
|
header: 'header',
|
||||||
|
side: 'side'
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
<!-- Side block start -->
|
||||||
|
<div class="side">
|
||||||
|
<!-- User block start -->
|
||||||
|
<div class="user">
|
||||||
|
<div class="pull-left">
|
||||||
|
<div class="name">{{name}}</div>
|
||||||
|
<spa class="extension">SIP {{extension}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="">
|
||||||
|
<i class="material-icons">menu</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- User block end -->
|
||||||
|
|
||||||
|
<!-- Navigation block start -->
|
||||||
|
<div class="navigation">
|
||||||
|
<a href="" class="active"><i class="material-icons">home</i></a>
|
||||||
|
<a href=""><i class="material-icons">contacts</i></a>
|
||||||
|
</div>
|
||||||
|
<!-- Navigation block end -->
|
||||||
|
|
||||||
|
<div class="title">
|
||||||
|
Historial de llamadas
|
||||||
|
</div>
|
||||||
|
<div class="callHistory">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="audioControls">
|
||||||
|
<div>
|
||||||
|
<i class="material-icons">volume_down</i>
|
||||||
|
<input type="range" id="volume" min="0" max="1" step="0.1" value="0.5"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="" id="mic"><i class="material-icons">mic</i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="contacts">
|
||||||
|
<i class="material-icons">load</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Side block end -->
|
|
@ -0,0 +1,28 @@
|
||||||
|
let path = require('path');
|
||||||
|
let rootPath = process.cwd();
|
||||||
|
let db = require(path.join(rootPath, '/database.js'));
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* Init model
|
||||||
|
*/
|
||||||
|
init: function(args, cb) {
|
||||||
|
this.getName(args.id, cb);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user data
|
||||||
|
*/
|
||||||
|
getName: function(id, cb) {
|
||||||
|
let sql = `SELECT name, extension FROM
|
||||||
|
account.user u JOIN pbx.sip s ON s.user_id = u.id WHERE id = ?`;
|
||||||
|
|
||||||
|
db.pool.query(sql, [id], (error, result) => {
|
||||||
|
if (error)
|
||||||
|
console.log(error);
|
||||||
|
|
||||||
|
Object.assign(this, result[0]);
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|