├── JsSIP-3.0.21 ├── .eslintignore ├── .gitignore ├── banner.txt ├── test │ ├── test-properties.js │ ├── include │ │ ├── common.js │ │ └── testUA.js │ ├── test-normalizeTarget.js │ ├── test-UA-no-WebRTC.js │ ├── test-classes.js │ └── test-parser.js ├── AUTHORS.md ├── .travis.yml ├── lib │ ├── Timers.js │ ├── JsSIP.js │ ├── Exceptions.js │ ├── RTCSession │ │ ├── ReferNotifier.js │ │ ├── Info.js │ │ ├── ReferSubscriber.js │ │ └── DTMF.js │ ├── Socket.js │ ├── NameAddrHeader.js │ ├── WebSocketInterface.js │ ├── Dialog │ │ └── RequestSender.js │ ├── RequestSender.js │ ├── Constants.js │ ├── URI.js │ ├── sanityCheck.js │ ├── Message.js │ ├── DigestAuthentication.js │ ├── Config.js │ ├── Transport.js │ ├── Dialog.js │ ├── Parser.js │ ├── Registrator.js │ └── Utils.js ├── THANKS.md ├── bower.json ├── LICENSE ├── BUILDING.md ├── package.json ├── README.md ├── gulpfile.js └── .eslintrc.js ├── webrtc ├── requirement.txt ├── static │ ├── audio │ │ ├── dtmf.wav │ │ └── ringtone.mp3 │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── js │ │ └── main.js │ └── css │ │ └── phone.css ├── webrtc.py └── templates │ └── jssip.html ├── .gitignore ├── conf ├── rtp.conf ├── http.conf ├── extensions.conf └── pjsip.conf ├── LICENSE ├── boot.sh ├── asterisk-contrib-scipt ├── astgenkey └── ast_tls_cert └── Vagrantfile /JsSIP-3.0.21/.eslintignore: -------------------------------------------------------------------------------- 1 | lib/Grammar.js 2 | -------------------------------------------------------------------------------- /webrtc/requirement.txt: -------------------------------------------------------------------------------- 1 | Flask==0.12.2 2 | pyOpenSSL==17.3.0 -------------------------------------------------------------------------------- /JsSIP-3.0.21/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | .npmignore 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ubuntu-xenial-16.04-cloudimg-console.log 2 | .Vagrantfile.swp 3 | webrtc/.idea 4 | *.pyc 5 | .vagrant 6 | -------------------------------------------------------------------------------- /conf/rtp.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | rtpstart=10000 3 | rtpend=20000 4 | icesupport=true 5 | stunaddr=stun.l.google.com:19302 6 | -------------------------------------------------------------------------------- /webrtc/static/audio/dtmf.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paneru-rajan/asterisk-jssip/HEAD/webrtc/static/audio/dtmf.wav -------------------------------------------------------------------------------- /webrtc/static/audio/ringtone.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paneru-rajan/asterisk-jssip/HEAD/webrtc/static/audio/ringtone.mp3 -------------------------------------------------------------------------------- /webrtc/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paneru-rajan/asterisk-jssip/HEAD/webrtc/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /webrtc/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paneru-rajan/asterisk-jssip/HEAD/webrtc/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /webrtc/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paneru-rajan/asterisk-jssip/HEAD/webrtc/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /webrtc/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paneru-rajan/asterisk-jssip/HEAD/webrtc/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /webrtc/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paneru-rajan/asterisk-jssip/HEAD/webrtc/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /conf/http.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | enabled=yes 3 | bindaddr=0.0.0.0 4 | bindport=8088 5 | tlsenable=yes 6 | tlsbindaddr=0.0.0.0:8089 7 | tlscertfile=/etc/asterisk/keys/asterisk.pem 8 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/banner.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * <%= pkg.title %> v<%= pkg.version %> 3 | * <%= pkg.description %> 4 | * Copyright: 2012-<%= currentYear %> <%= pkg.author %> 5 | * Homepage: <%= pkg.homepage %> 6 | * License: <%= pkg.license %> 7 | */ 8 | 9 | -------------------------------------------------------------------------------- /conf/extensions.conf: -------------------------------------------------------------------------------- 1 | [helloworld] 2 | exten => _X.,1,NoOp(${EXTEN}) 3 | same => n,Playback(hello-world) 4 | same => n,Hangup() 5 | 6 | [helloworld2] 7 | exten => _X.,1,NoOp(${EXTEN}) 8 | same => n,Playback(hello-world) 9 | same => n,Dial(PJSIP/${EXTEN},20) 10 | same => n,Read(Digits,,) 11 | same => n,Playback(you-entered) 12 | same => n,SayNumber(${Digits}) 13 | 14 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/test/test-properties.js: -------------------------------------------------------------------------------- 1 | require('./include/common'); 2 | const JsSIP = require('../'); 3 | const pkg = require('../package.json'); 4 | 5 | 6 | module.exports = { 7 | 8 | 'name' : function(test) 9 | { 10 | test.equal(JsSIP.name, pkg.title); 11 | test.done(); 12 | }, 13 | 14 | 'version' : function(test) 15 | { 16 | test.equal(JsSIP.version, pkg.version); 17 | test.done(); 18 | } 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /webrtc/webrtc.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask import render_template 3 | import ssl 4 | 5 | app = Flask(__name__) 6 | 7 | @app.route('/') 8 | def index(): 9 | return render_template('jssip.html') 10 | 11 | 12 | if __name__ == '__main__': 13 | app.jinja_env.auto_reload = True 14 | app.config['TEMPLATES_AUTO_RELOAD'] = True 15 | app.run(host='0.0.0.0', port=5000, ssl_context='adhoc', threaded=True, debug=True) 16 | 17 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/AUTHORS.md: -------------------------------------------------------------------------------- 1 | # AUTHORS 2 | 3 | ### José Luis Millán 4 | 5 | * Main author. Core Designer and Developer. 6 | * (Github [@jmillan](https://github.com/jmillan)) 7 | 8 | ### Iñaki Baz Castillo 9 | 10 | * Core Designer and Developer. 11 | * (Github [@ibc](https://github.com/ibc)) 12 | 13 | ### Saúl Ibarra Corretgé 14 | 15 | * Core Designer. 16 | * (Github [@saghul](https://github.com/saghul)) 17 | 18 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/test/include/common.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0*/ 2 | 3 | // Show uncaught errors. 4 | process.on('uncaughtException', function(error) 5 | { 6 | console.error('uncaught exception:'); 7 | console.error(error.stack); 8 | process.exit(1); 9 | }); 10 | 11 | // Define global.WebSocket. 12 | global.WebSocket = function() 13 | { 14 | this.close = function() {}; 15 | }; 16 | 17 | // Define global.navigator for bowser module. 18 | global.navigator = { 19 | userAgent : '' 20 | }; 21 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/.travis.yml: -------------------------------------------------------------------------------- 1 | # Just apply Travis-CI for these branches 2 | branches: 3 | only: 4 | - master 5 | - experimental 6 | 7 | language: node_js 8 | 9 | node_js: 10 | - 6 11 | - 7 12 | - 8 13 | 14 | install: 15 | - npm install 16 | 17 | before_script: 18 | - npm install -g gulp-cli 19 | - gulp devel dist 20 | 21 | script: 22 | - gulp test 23 | 24 | notifications: 25 | email: 26 | recipients: 27 | - jmillan@aliax.net 28 | - ibc@aliax.net 29 | - saghul@gmail.com 30 | on_success: change 31 | on_failure: always 32 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/Timers.js: -------------------------------------------------------------------------------- 1 | const T1 = 500, T2 = 4000, T4 = 5000; 2 | 3 | module.exports = { 4 | T1, 5 | T2, 6 | T4, 7 | TIMER_B : 64 * T1, 8 | TIMER_D : 0 * T1, 9 | TIMER_F : 64 * T1, 10 | TIMER_H : 64 * T1, 11 | TIMER_I : 0 * T1, 12 | TIMER_J : 0 * T1, 13 | TIMER_K : 0 * T4, 14 | TIMER_L : 64 * T1, 15 | TIMER_M : 64 * T1, 16 | PROVISIONAL_RESPONSE_INTERVAL : 60000 // See RFC 3261 Section 13.3.1.1 17 | }; 18 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/THANKS.md: -------------------------------------------------------------------------------- 1 | THANKS 2 | ====== 3 | 4 | Here the list of contributors with code and patches to JsSIP project. Thanks a lot to all. 5 | 6 | * [vf1](https://github.com/vf1) 7 | * [Pedro Kiefer](https://github.com/pedrokiefer) 8 | * [Iwan Budi Kusnanto](https://github.com/iwanbk) 9 | * Davide Corda 10 | * [Anthony Minessale](https://github.com/FreeSWITCH) 11 | * [Gavin Llewellyn](https://github.com/gavllew) 12 | * [Julian Scheid](https://github.com/jscheid) 13 | * [James Mortensen](https://github.com/jamesmortensen) 14 | * [Steve Davies](https://github.com/davies147) 15 | 16 | 17 | JsSIP Debian and Ubuntu packaging 18 | 19 | * [Daniel Pocock](https://github.com/dpocock) 20 | 21 | 22 | JsSIP Bower packaging 23 | 24 | * [Linda Nichols](https://github.com/lynnaloo) 25 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jssip", 3 | "description": "the Javascript SIP library", 4 | "main": "dist/jssip.js", 5 | "homepage": "http://jssip.net", 6 | "authors": [ 7 | "José Luis Millán (https://github.com/jmillan)", 8 | "Iñaki Baz Castillo (https://github.com/ibc)", 9 | "Saúl Ibarra Corretgé (https://github.com/saghul)" 10 | ], 11 | "keywords": [ 12 | "sip", 13 | "websocket", 14 | "webrtc", 15 | "node", 16 | "browser", 17 | "library" 18 | ], 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/versatica/JsSIP.git" 23 | }, 24 | "ignore": [ 25 | "node_modules", 26 | "test", 27 | "lib", 28 | "gulpfile.js", 29 | "package.json", 30 | "banner.txt" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/JsSIP.js: -------------------------------------------------------------------------------- 1 | const adapter = require('webrtc-adapter'); 2 | const pkg = require('../package.json'); 3 | const C = require('./Constants'); 4 | const Exceptions = require('./Exceptions'); 5 | const Utils = require('./Utils'); 6 | const UA = require('./UA'); 7 | const URI = require('./URI'); 8 | const NameAddrHeader = require('./NameAddrHeader'); 9 | const Grammar = require('./Grammar'); 10 | const WebSocketInterface = require('./WebSocketInterface'); 11 | const debug = require('debug')('JsSIP'); 12 | 13 | debug('version %s', pkg.version); 14 | 15 | /** 16 | * Expose the JsSIP module. 17 | */ 18 | module.exports = { 19 | C, 20 | Exceptions, 21 | Utils, 22 | UA, 23 | URI, 24 | NameAddrHeader, 25 | WebSocketInterface, 26 | Grammar, 27 | // Expose the debug module. 28 | debug : require('debug'), 29 | // Expose the adapter module. 30 | adapter, 31 | get name() { return pkg.title; }, 32 | get version() { return pkg.version; } 33 | }; 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 YoungInnovations 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/Exceptions.js: -------------------------------------------------------------------------------- 1 | class ConfigurationError extends Error 2 | { 3 | constructor(parameter, value) 4 | { 5 | super(); 6 | 7 | this.code = 1; 8 | this.name = 'CONFIGURATION_ERROR'; 9 | this.parameter = parameter; 10 | this.value = value; 11 | this.message = (!this.value)? 12 | `Missing parameter: ${this.parameter}` : 13 | `Invalid value ${JSON.stringify(this.value)} for parameter "${this.parameter}"`; 14 | } 15 | } 16 | 17 | class InvalidStateError extends Error 18 | { 19 | constructor(status) 20 | { 21 | super(); 22 | 23 | this.code = 2; 24 | this.name = 'INVALID_STATE_ERROR'; 25 | this.status = status; 26 | this.message = `Invalid status: ${status}`; 27 | } 28 | } 29 | 30 | class NotSupportedError extends Error 31 | { 32 | constructor(message) 33 | { 34 | super(); 35 | 36 | this.code = 3; 37 | this.name = 'NOT_SUPPORTED_ERROR'; 38 | this.message = message; 39 | } 40 | } 41 | 42 | class NotReadyError extends Error 43 | { 44 | constructor(message) 45 | { 46 | super(); 47 | 48 | this.code = 4; 49 | this.name = 'NOT_READY_ERROR'; 50 | this.message = message; 51 | } 52 | } 53 | 54 | module.exports = { 55 | ConfigurationError, 56 | InvalidStateError, 57 | NotSupportedError, 58 | NotReadyError 59 | }; 60 | -------------------------------------------------------------------------------- /conf/pjsip.conf: -------------------------------------------------------------------------------- 1 | [transport-wss] 2 | type=transport 3 | protocol=wss 4 | bind=0.0.0.0 5 | 6 | [199] 7 | type=endpoint 8 | aors=199 9 | auth=199 10 | use_avpf=yes 11 | media_encryption=dtls 12 | dtls_ca_file=/etc/asterisk/keys/ca.crt 13 | dtls_cert_file=/etc/asterisk/keys/asterisk.pem 14 | dtls_verify=fingerprint 15 | dtls_setup=actpass 16 | ice_support=yes 17 | media_use_received_transport=yes 18 | rtcp_mux=yes 19 | context=helloworld2 20 | disallow=all 21 | allow=ulaw 22 | allow=opus 23 | 24 | [199] 25 | type=auth 26 | auth_type=userpass 27 | username=199 28 | password=199@pass1 29 | 30 | [199] 31 | type=aor 32 | max_contacts=1 33 | remove_existing=yes 34 | 35 | 36 | [transport-udp] 37 | type=transport 38 | protocol=udp 39 | bind=0.0.0.0 40 | 41 | [3001] 42 | type=endpoint 43 | context=helloworld2 44 | disallow=all 45 | allow=ulaw 46 | auth=3001 47 | aors=3001 48 | 49 | [3001] 50 | type=auth 51 | auth_type=userpass 52 | password=3001pass 53 | username=3001 54 | 55 | [3001] 56 | type=aor 57 | max_contacts=1 58 | remove_existing=yes 59 | 60 | 61 | [3002] 62 | type=endpoint 63 | context=helloworld2 64 | disallow=all 65 | allow=ulaw 66 | auth=3002 67 | aors=3002 68 | 69 | [3002] 70 | type=auth 71 | auth_type=userpass 72 | password=3002pass 73 | username=3002 74 | 75 | [3002] 76 | type=aor 77 | max_contacts=1 78 | remove_existing=yes 79 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/LICENSE: -------------------------------------------------------------------------------- 1 | Name: JsSIP 2 | Author: José Luis Millán 3 | Core Developer: Iñaki Baz Castillo 4 | Copyright (c) 2012-2015 José Luis Millán - Versatica 5 | 6 | 7 | License: The MIT License 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining 10 | a copy of this software and associated documentation files (the 11 | "Software"), to deal in the Software without restriction, including 12 | without limitation the rights to use, copy, modify, merge, publish, 13 | distribute, sublicense, and/or sell copies of the Software, and to 14 | permit persons to whom the Software is furnished to do so, subject to 15 | the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/BUILDING.md: -------------------------------------------------------------------------------- 1 | ## What you need to build JsSIP 2 | 3 | You just need to have [Node.js](http://nodejs.org/) and [Git](http://git-scm.com/). 4 | 5 | 6 | ### Node.js 7 | 8 | * [Install Node.js](https://nodejs.org/en/download/) 9 | 10 | ### Git 11 | 12 | * [Install Git](http://git-scm.com/book/en/Getting-Started-Installing-Git) 13 | 14 | 15 | ## How to build JsSIP 16 | 17 | Clone a copy of the main JsSIP git repository by running: 18 | 19 | ```bash 20 | $ git clone https://github.com/versatica/JsSIP.git JsSIP 21 | $ cd JsSIP 22 | ``` 23 | 24 | Install `gulp-cli` (>= 1.2.2) globally (which provides the `gulp` command): 25 | 26 | ```bash 27 | $ npm install -g gulp-cli 28 | ``` 29 | 30 | (you can also use the local `gulp` executable located in `node_modules/.bin/gulp`). 31 | 32 | Install the Node.js dependencies: 33 | 34 | ```bash 35 | $ npm install 36 | ``` 37 | 38 | Finally, run `gulp dist` (or just `gulp`) to get: 39 | 40 | * `dist/jssip.js`: uncompressed version of JsSIP. 41 | * `dist/jssip.min.js`: compressed version of JsSIP. 42 | 43 | ```bash 44 | $ gulp dist 45 | ``` 46 | 47 | 48 | ## Test units 49 | 50 | ```bash 51 | $ gulp test 52 | ``` 53 | 54 | 55 | ## Development 56 | 57 | ### Changes in JsSIP Grammar 58 | 59 | If you modify `lib/Grammar.pegjs` then you need to recompile it: 60 | 61 | ```bash 62 | $ gulp devel 63 | $ gulp dist 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/RTCSession/ReferNotifier.js: -------------------------------------------------------------------------------- 1 | const JsSIP_C = require('../Constants'); 2 | const debug = require('debug')('JsSIP:RTCSession:ReferNotifier'); 3 | 4 | const C = { 5 | event_type : 'refer', 6 | body_type : 'message/sipfrag;version=2.0', 7 | expires : 300 8 | }; 9 | 10 | module.exports = class ReferNotifier 11 | { 12 | constructor(session, id, expires) 13 | { 14 | this._session = session; 15 | this._id = id; 16 | this._expires = expires || C.expires; 17 | this._active = true; 18 | 19 | // The creation of a Notifier results in an immediate NOTIFY. 20 | this.notify(100); 21 | } 22 | 23 | notify(code, reason) 24 | { 25 | debug('notify()'); 26 | 27 | if (this._active === false) 28 | { 29 | return; 30 | } 31 | 32 | reason = reason || JsSIP_C.REASON_PHRASE[code] || ''; 33 | 34 | let state; 35 | 36 | if (code >= 200) 37 | { 38 | state = 'terminated;reason=noresource'; 39 | } 40 | else 41 | { 42 | state = `active;expires=${this._expires}`; 43 | } 44 | 45 | // Put this in a try/catch block. 46 | this._session.sendRequest(JsSIP_C.NOTIFY, { 47 | extraHeaders : [ 48 | `Event: ${C.event_type};id=${this._id}`, 49 | `Subscription-State: ${state}`, 50 | `Content-Type: ${C.body_type}` 51 | ], 52 | body : `SIP/2.0 ${code} ${reason}`, 53 | eventHandlers : { 54 | // If a negative response is received, subscription is canceled. 55 | onErrorResponse() { this._active = false; } 56 | } 57 | }); 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /boot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo apt-get update 3 | sudo apt-get upgrade -y 4 | sudo apt-get install xmlstarlet libpt-dev -y 5 | export PTLIB_CONFIG=/usr/share/ptlib/make/ptlib-config 6 | export DEBIAN_FRONTEND=noninteractive 7 | cd /usr/local 8 | sudo chmod -R 777 src 9 | cd src 10 | wget http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-13-current.tar.gz 11 | tar -xvzf asterisk-13-current.tar.gz 12 | cd asterisk-13.17.2/contrib/scripts 13 | sudo sed -i "s/libvpb-dev //g" install_prereq 14 | yes | sudo ./install_prereq install 15 | cd ../../ 16 | ./configure --with-pjproject-bundled 17 | make menuselect.makeopts 18 | menuselect/menuselect --enable codec_opus menuselect.makeopts 19 | make 20 | sudo make install 21 | sudo make samples 22 | sudo make config 23 | 24 | sudo cp -r /vagrant/conf/* /etc/asterisk/ 25 | sudo sh -c "echo 'noload => chan_sip.so' >> /etc/asterisk/modules.conf" 26 | cd contrib/scripts/ 27 | PASSPHRASE=NEWPASSPHRASE 28 | sudo cp /vagrant/asterisk-contrib-scipt/* ./ 29 | sudo sed -i "s/pass:yipl/pass:${PASSPHRASE}/g" astgenkey ast_tls_cert 30 | sudo ./ast_tls_cert -C pbx.example.com -O "My Super Company" -d /etc/asterisk/keys 31 | sudo apt-get install python-pip python-dev build-essential -y 32 | sudo -H pip install --upgrade pip 33 | sudo -H pip install virtualenvwrapper 34 | sed -i "1isource /usr/local/bin/virtualenvwrapper.sh" ~/.bashrc 35 | sed -i "1iexport PROJECT_HOME=\$HOME/Devel" ~/.bashrc 36 | sed -i "1iexport WORKON_HOME=\$HOME/.virtualenvs" ~/.bashrc 37 | source ~/.bashrc 38 | mkvirtualenv webrtc 39 | cd /vagrant/webrtc/ 40 | setvirtualenvproject 41 | pip install -r requirement.txt 42 | 43 | -------------------------------------------------------------------------------- /asterisk-contrib-scipt/astgenkey: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Usage: astgenkey [ -q ] [ -n ] [keyname] 4 | # 5 | DES3=-des3 6 | if [ "$1" = "-q" ]; then 7 | QUIET='y' 8 | if [ "$2" = "-n" ]; then 9 | DES3= 10 | KEY=$3 11 | else 12 | KEY=$2 13 | fi 14 | elif [ "$1" = "-n" ]; then 15 | DES3= 16 | if [ "$2" = "-q" ]; then 17 | QUIET='y' 18 | KEY=$3 19 | else 20 | KEY=$2 21 | fi 22 | else 23 | KEY=$1 24 | fi 25 | 26 | if [ "$QUIET" != 'y' ]; then 27 | echo "" 28 | echo "This script generates an RSA private and public key pair" 29 | echo "in PEM format for use by Asterisk. You will be asked to" 30 | echo "enter a passcode for your key multiple times. Please" 31 | echo "enter the same code each time. The resulting files will" 32 | echo "need to be moved to /var/lib/asterisk/keys if you want" 33 | echo "to use them, and any private keys (.key files) will" 34 | echo "need to be initialized at runtime either by running" 35 | echo "Asterisk with the '-i' option, or with the 'keys init'" 36 | echo "command once Asterisk is running." 37 | echo "" 38 | echo "Press ENTER to continue or ^C to cancel." 39 | read BLAH 40 | fi 41 | 42 | while [ "$KEY" = "" ]; do 43 | echo -n "Enter key name: " 44 | read KEY 45 | done 46 | 47 | rm -f ${KEY}.key ${KEY}.pub 48 | 49 | echo "Generating SSL key '$KEY': " 50 | openssl genrsa -passout pass:yipl -out ${KEY}.key ${DES3} 1024 51 | openssl rsa -passin pass:yipl -in ${KEY}.key -pubout -out ${KEY}.pub 52 | 53 | if [ -f "${KEY}.key" ] && [ -f "${KEY}.pub" ]; then 54 | if [ "$QUIET" != 'y' ]; then 55 | echo "Key creation successful." 56 | echo "Public key: ${KEY}.pub" 57 | echo "Private key: ${KEY}.key" 58 | fi 59 | else 60 | echo "Unknown error creating keys." 61 | fi 62 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jssip", 3 | "title": "JsSIP", 4 | "description": "the Javascript SIP library", 5 | "version": "3.0.21", 6 | "homepage": "http://jssip.net", 7 | "author": "José Luis Millán (https://github.com/jmillan)", 8 | "contributors": [ 9 | "Iñaki Baz Castillo (https://github.com/ibc)", 10 | "Saúl Ibarra Corretgé (https://github.com/saghul)" 11 | ], 12 | "main": "lib/JsSIP.js", 13 | "keywords": [ 14 | "sip", 15 | "websocket", 16 | "webrtc", 17 | "node", 18 | "browser", 19 | "library" 20 | ], 21 | "license": "MIT", 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/versatica/JsSIP.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/versatica/JsSIP/issues" 28 | }, 29 | "dependencies": { 30 | "debug": "^3.0.1", 31 | "sdp-transform": "^2.3.0", 32 | "webrtc-adapter": "^5.0.0" 33 | }, 34 | "browserify": { 35 | "transform": [ 36 | [ 37 | "babelify", 38 | { 39 | "presets": [ 40 | "es2015" 41 | ] 42 | } 43 | ] 44 | ] 45 | }, 46 | "devDependencies": { 47 | "babel-preset-es2015": "^6.24.1", 48 | "babelify": "7.3.0", 49 | "browserify": "^14.3.0", 50 | "gulp": "git+https://github.com/gulpjs/gulp.git#4.0", 51 | "gulp-eslint": "^4.0.0", 52 | "gulp-expect-file": "0.0.7", 53 | "gulp-header": "1.8.9", 54 | "gulp-nodeunit-runner": "^0.2.2", 55 | "gulp-plumber": "^1.1.0", 56 | "gulp-rename": "^1.2.2", 57 | "gulp-uglify": "^3.0.0", 58 | "gulp-util": "^3.0.8", 59 | "eslint": "^4.4.1", 60 | "pegjs": "0.7.0", 61 | "vinyl-buffer": "^1.0.0", 62 | "vinyl-source-stream": "^1.1.0" 63 | }, 64 | "scripts": { 65 | "test": "gulp test" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/test/include/testUA.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | UA_CONFIGURATION : { 3 | uri : 'sip:f%61keUA@jssip.net', 4 | password : '1234ññññ', 5 | 'sockets' : [ { 6 | 'via_transport' : 'WS', 7 | 'sip_uri' : 'sip:localhost:12345;transport=ws', 8 | 'url' : 'ws://localhost:12345' 9 | } ], 10 | display_name : 'Fake UA ð→€ł !!!', 11 | authorization_user : 'fakeUA', 12 | instance_id : 'uuid:8f1fa16a-1165-4a96-8341-785b1ef24f12', 13 | registrar_server : 'registrar.jssip.NET:6060;TRansport=TCP', 14 | register_expires : 600, 15 | register : false, 16 | connection_recovery_min_interval : 2, 17 | connection_recovery_max_interval : 30, 18 | use_preloaded_route : true, 19 | no_answer_timeout : 60000, 20 | session_timers : true 21 | }, 22 | 23 | UA_CONFIGURATION_AFTER_START : { 24 | uri : 'sip:fakeUA@jssip.net', 25 | password : '1234ññññ', 26 | display_name : 'Fake UA ð→€ł !!!', 27 | authorization_user : 'fakeUA', 28 | instance_id : '8f1fa16a-1165-4a96-8341-785b1ef24f12', // Without 'uuid:'. 29 | registrar_server : 'sip:registrar.jssip.net:6060;transport=tcp', 30 | register_expires : 600, 31 | register : false, 32 | use_preloaded_route : true, 33 | no_answer_timeout : 60000 * 1000, // Internally converted to miliseconds. 34 | session_timers : true 35 | }, 36 | 37 | UA_TRANSPORT_AFTER_START : { 38 | 'sockets' : [ { 39 | 'socket' : { 40 | 'via_transport' : 'WS', 41 | 'sip_uri' : 'sip:localhost:12345;transport=ws', 42 | 'url' : 'ws://localhost:12345' 43 | }, 44 | 'weight' : 0 45 | } ], 46 | 'recovery_options' : { 47 | 'min_interval' : 2, 48 | 'max_interval' : 30 49 | } 50 | } 51 | }; 52 | 53 | 54 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/Socket.js: -------------------------------------------------------------------------------- 1 | const Utils = require('./Utils'); 2 | const Grammar = require('./Grammar'); 3 | const debugerror = require('debug')('JsSIP:ERROR:Socket'); 4 | 5 | debugerror.log = console.warn.bind(console); 6 | 7 | /** 8 | * Interface documentation: http://jssip.net/documentation/$last_version/api/socket/ 9 | * 10 | * interface Socket { 11 | * attribute String via_transport 12 | * attribute String url 13 | * attribute String sip_uri 14 | * 15 | * method connect(); 16 | * method disconnect(); 17 | * method send(data); 18 | * 19 | * attribute EventHandler onconnect 20 | * attribute EventHandler ondisconnect 21 | * attribute EventHandler ondata 22 | * } 23 | * 24 | */ 25 | 26 | exports.isSocket = (socket) => 27 | { 28 | // Ignore if an array is given. 29 | if (Array.isArray(socket)) 30 | { 31 | return false; 32 | } 33 | 34 | if (typeof socket === 'undefined') 35 | { 36 | debugerror('undefined JsSIP.Socket instance'); 37 | 38 | return false; 39 | } 40 | 41 | // Check Properties. 42 | try 43 | { 44 | if (!Utils.isString(socket.url)) 45 | { 46 | debugerror('missing or invalid JsSIP.Socket url property'); 47 | throw new Error(); 48 | } 49 | 50 | if (!Utils.isString(socket.via_transport)) 51 | { 52 | debugerror('missing or invalid JsSIP.Socket via_transport property'); 53 | throw new Error(); 54 | } 55 | 56 | if (Grammar.parse(socket.sip_uri, 'SIP_URI') === -1) 57 | { 58 | debugerror('missing or invalid JsSIP.Socket sip_uri property'); 59 | throw new Error(); 60 | } 61 | } 62 | catch (e) 63 | { 64 | return false; 65 | } 66 | 67 | // Check Methods. 68 | try 69 | { 70 | [ 'connect', 'disconnect', 'send' ].forEach((method) => 71 | { 72 | if (!Utils.isFunction(socket[method])) 73 | { 74 | debugerror(`missing or invalid JsSIP.Socket method: ${method}`); 75 | throw new Error(); 76 | } 77 | }); 78 | } 79 | catch (e) 80 | { 81 | return false; 82 | } 83 | 84 | return true; 85 | }; 86 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/test/test-normalizeTarget.js: -------------------------------------------------------------------------------- 1 | require('./include/common'); 2 | const JsSIP = require('../'); 3 | 4 | 5 | module.exports = { 6 | 7 | 'valid targets' : function(test) 8 | { 9 | const domain = 'jssip.net'; 10 | 11 | function test_ok(given_data, expected) 12 | { 13 | const uri = JsSIP.Utils.normalizeTarget(given_data, domain); 14 | 15 | test.ok(uri instanceof(JsSIP.URI)); 16 | test.strictEqual(uri.toString(), expected); 17 | } 18 | 19 | test_ok('%61lice', 'sip:alice@jssip.net'); 20 | test_ok('ALICE', 'sip:ALICE@jssip.net'); 21 | test_ok('alice@DOMAIN.com', 'sip:alice@domain.com'); 22 | test_ok('iñaki', 'sip:i%C3%B1aki@jssip.net'); 23 | test_ok('€€€', 'sip:%E2%82%AC%E2%82%AC%E2%82%AC@jssip.net'); 24 | test_ok('iñaki@aliax.net', 'sip:i%C3%B1aki@aliax.net'); 25 | test_ok('SIP:iñaki@aliax.net:7070', 'sip:i%C3%B1aki@aliax.net:7070'); 26 | test_ok('SIPs:iñaki@aliax.net:7070', 'sip:i%C3%B1aki@aliax.net:7070'); 27 | test_ok('ibc@gmail.com@aliax.net', 'sip:ibc%40gmail.com@aliax.net'); 28 | test_ok('alice-1:passwd', 'sip:alice-1:passwd@jssip.net'); 29 | test_ok('SIP:alice-2:passwd', 'sip:alice-2:passwd@jssip.net'); 30 | test_ok('sips:alice-2:passwd', 'sip:alice-2:passwd@jssip.net'); 31 | test_ok('alice-3:passwd@domain.COM', 'sip:alice-3:passwd@domain.com'); 32 | test_ok('SIP:alice-4:passwd@domain.COM', 'sip:alice-4:passwd@domain.com'); 33 | test_ok('sip:+1234@aliax.net', 'sip:+1234@aliax.net'); 34 | test_ok('+999', 'sip:+999@jssip.net'); 35 | test_ok('*999', 'sip:*999@jssip.net'); 36 | test_ok('#999/?:1234', 'sip:%23999/?:1234@jssip.net'); 37 | test_ok('tel:+12345678', 'sip:+12345678@jssip.net'); 38 | test_ok('tel:(+34)-944-43-89', 'sip:+349444389@jssip.net'); 39 | test_ok('+123.456.78-9', 'sip:+123456789@jssip.net'); 40 | test_ok('+ALICE-123.456.78-9', 'sip:+ALICE-123.456.78-9@jssip.net'); 41 | 42 | test.done(); 43 | }, 44 | 45 | 'invalid targets' : function(test) 46 | { 47 | const domain = 'jssip.net'; 48 | 49 | function test_error(given_data) 50 | { 51 | test.deepEqual(JsSIP.Utils.normalizeTarget(given_data, domain), undefined); 52 | } 53 | 54 | test_error(null); 55 | test_error(undefined); 56 | test_error(NaN); 57 | test_error(false); 58 | test_error(true); 59 | test_error(''); 60 | test_error('ibc@iñaki.com'); 61 | test_error('ibc@aliax.net;;;;;'); 62 | 63 | test.deepEqual(JsSIP.Utils.normalizeTarget('alice'), undefined); 64 | 65 | test.done(); 66 | } 67 | 68 | }; 69 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/RTCSession/Info.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events').EventEmitter; 2 | const debugerror = require('debug')('JsSIP:ERROR:RTCSession:Info'); 3 | 4 | debugerror.log = console.warn.bind(console); 5 | const JsSIP_C = require('../Constants'); 6 | const Exceptions = require('../Exceptions'); 7 | const Utils = require('../Utils'); 8 | 9 | module.exports = class Info extends EventEmitter 10 | { 11 | constructor(session) 12 | { 13 | super(); 14 | 15 | this._session = session; 16 | this._direction = null; 17 | this._contentType = null; 18 | this._body = null; 19 | } 20 | 21 | get contentType() 22 | { 23 | return this._contentType; 24 | } 25 | 26 | get body() 27 | { 28 | return this._body; 29 | } 30 | 31 | send(contentType, body, options = {}) 32 | { 33 | this._direction = 'outgoing'; 34 | 35 | if (contentType === undefined) 36 | { 37 | throw new TypeError('Not enough arguments'); 38 | } 39 | 40 | // Check RTCSession Status. 41 | if (this._session.status !== this._session.C.STATUS_CONFIRMED && 42 | this._session.status !== this._session.C.STATUS_WAITING_FOR_ACK) 43 | { 44 | throw new Exceptions.InvalidStateError(this._session.status); 45 | } 46 | 47 | this._contentType = contentType; 48 | this._body = body; 49 | 50 | const extraHeaders = Utils.cloneArray(options.extraHeaders); 51 | 52 | extraHeaders.push(`Content-Type: ${contentType}`); 53 | 54 | this._session.newInfo({ 55 | originator : 'local', 56 | info : this, 57 | request : this.request 58 | }); 59 | 60 | this._session.sendRequest(this, JsSIP_C.INFO, { 61 | extraHeaders, 62 | eventHandlers : { 63 | onSuccessResponse : (response) => 64 | { 65 | this.emit('succeeded', { 66 | originator : 'remote', 67 | response 68 | }); 69 | }, 70 | onErrorResponse : (response) => 71 | { 72 | this.emit('failed', { 73 | originator : 'remote', 74 | response 75 | }); 76 | }, 77 | onTransportError : () => 78 | { 79 | this._session.onTransportError(); 80 | }, 81 | onRequestTimeout : () => 82 | { 83 | this._session.onRequestTimeout(); 84 | }, 85 | onDialogError : () => 86 | { 87 | this._session.onDialogError(); 88 | } 89 | }, 90 | body 91 | }); 92 | } 93 | 94 | init_incoming(request) 95 | { 96 | this._direction = 'incoming'; 97 | this.request = request; 98 | 99 | request.reply(200); 100 | 101 | this._contentType = request.getHeader('content-type'); 102 | this._body = request.body; 103 | 104 | this._session.newInfo({ 105 | originator : 'remote', 106 | info : this, 107 | request 108 | }); 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/NameAddrHeader.js: -------------------------------------------------------------------------------- 1 | const URI = require('./URI'); 2 | const Grammar = require('./Grammar'); 3 | 4 | module.exports = class NameAddrHeader 5 | { 6 | /** 7 | * Parse the given string and returns a NameAddrHeader instance or undefined if 8 | * it is an invalid NameAddrHeader. 9 | */ 10 | static parse(name_addr_header) 11 | { 12 | name_addr_header = Grammar.parse(name_addr_header, 'Name_Addr_Header'); 13 | 14 | if (name_addr_header !== -1) 15 | { 16 | return name_addr_header; 17 | } 18 | else 19 | { 20 | return undefined; 21 | } 22 | } 23 | 24 | constructor(uri, display_name, parameters) 25 | { 26 | // Checks. 27 | if (!uri || !(uri instanceof URI)) 28 | { 29 | throw new TypeError('missing or invalid "uri" parameter'); 30 | } 31 | 32 | // Initialize parameters. 33 | this._uri = uri; 34 | this._parameters = {}; 35 | this._display_name = display_name; 36 | 37 | for (const param in parameters) 38 | { 39 | if (Object.prototype.hasOwnProperty.call(parameters, param)) 40 | { 41 | this.setParam(param, parameters[param]); 42 | } 43 | } 44 | } 45 | 46 | get uri() 47 | { 48 | return this._uri; 49 | } 50 | 51 | get display_name() 52 | { 53 | return this._display_name; 54 | } 55 | 56 | set display_name(value) 57 | { 58 | this._display_name = (value === 0) ? '0' : value; 59 | } 60 | 61 | setParam(key, value) 62 | { 63 | if (key) 64 | { 65 | this._parameters[key.toLowerCase()] = (typeof value === 'undefined' || value === null) ? null : value.toString(); 66 | } 67 | } 68 | 69 | getParam(key) 70 | { 71 | if (key) 72 | { 73 | return this._parameters[key.toLowerCase()]; 74 | } 75 | } 76 | 77 | hasParam(key) 78 | { 79 | if (key) 80 | { 81 | return (this._parameters.hasOwnProperty(key.toLowerCase()) && true) || false; 82 | } 83 | } 84 | 85 | deleteParam(parameter) 86 | { 87 | parameter = parameter.toLowerCase(); 88 | if (this._parameters.hasOwnProperty(parameter)) 89 | { 90 | const value = this._parameters[parameter]; 91 | 92 | delete this._parameters[parameter]; 93 | 94 | return value; 95 | } 96 | } 97 | 98 | clearParams() 99 | { 100 | this._parameters = {}; 101 | } 102 | 103 | clone() 104 | { 105 | return new NameAddrHeader( 106 | this._uri.clone(), 107 | this._display_name, 108 | JSON.parse(JSON.stringify(this._parameters))); 109 | } 110 | 111 | toString() 112 | { 113 | let body = (this._display_name || this._display_name === 0) ? `"${this._display_name}" ` : ''; 114 | 115 | body += `<${this._uri.toString()}>`; 116 | 117 | for (const parameter in this._parameters) 118 | { 119 | if (Object.prototype.hasOwnProperty.call(this._parameters, parameter)) 120 | { 121 | body += `;${parameter}`; 122 | 123 | if (this._parameters[parameter] !== null) 124 | { 125 | body += `=${this._parameters[parameter]}`; 126 | } 127 | } 128 | } 129 | 130 | return body; 131 | } 132 | }; 133 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure("2") do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://vagrantcloud.com/search. 15 | config.vm.box = "ubuntu/xenial64" 16 | config.vm.network "private_network", ip: "192.168.33.10" 17 | config.vm.network "public_network", ip: "192.168.1.240", bridge: "wlp2s0" 18 | config.vm.provision :shell, path: "boot.sh", privileged: false 19 | 20 | # Disable automatic box update checking. If you disable this, then 21 | # boxes will only be checked for updates when the user runs 22 | # `vagrant box outdated`. This is not recommended. 23 | # config.vm.box_check_update = false 24 | 25 | # Create a forwarded port mapping which allows access to a specific port 26 | # within the machine from a port on the host machine. In the example below, 27 | # accessing "localhost:8080" will access port 80 on the guest machine. 28 | # NOTE: This will enable public access to the opened port 29 | # config.vm.network "forwarded_port", guest: 80, host: 8080 30 | 31 | # Create a forwarded port mapping which allows access to a specific port 32 | # within the machine from a port on the host machine and only allow access 33 | # via 127.0.0.1 to disable public access 34 | # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" 35 | 36 | # Create a private network, which allows host-only access to the machine 37 | # using a specific IP. 38 | # config.vm.network "private_network", ip: "192.168.33.10" 39 | 40 | # Create a public network, which generally matched to bridged network. 41 | # Bridged networks make the machine appear as another physical device on 42 | # your network. 43 | # config.vm.network "public_network" 44 | 45 | # Share an additional folder to the guest VM. The first argument is 46 | # the path on the host to the actual folder. The second argument is 47 | # the path on the guest to mount the folder. And the optional third 48 | # argument is a set of non-required options. 49 | # config.vm.synced_folder "../data", "/vagrant_data" 50 | 51 | # Provider-specific configuration so you can fine-tune various 52 | # backing providers for Vagrant. These expose provider-specific options. 53 | # Example for VirtualBox: 54 | # 55 | # config.vm.provider "virtualbox" do |vb| 56 | # # Display the VirtualBox GUI when booting the machine 57 | # vb.gui = true 58 | # 59 | # # Customize the amount of memory on the VM: 60 | # vb.memory = "1024" 61 | # end 62 | # 63 | # View the documentation for the provider you are using for more 64 | # information on available options. 65 | 66 | # Enable provisioning with a shell script. Additional provisioners such as 67 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the 68 | # documentation for more information about their specific syntax and use. 69 | # config.vm.provision "shell", inline: <<-SHELL 70 | # apt-get update 71 | # apt-get install -y apache2 72 | # SHELL 73 | end 74 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/test/test-UA-no-WebRTC.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0*/ 2 | 3 | require('./include/common'); 4 | const testUA = require('./include/testUA'); 5 | const JsSIP = require('../'); 6 | 7 | 8 | module.exports = { 9 | 10 | 'UA wrong configuration' : function(test) 11 | { 12 | test.throws( 13 | function() 14 | { 15 | JsSIP.UA({ 'lalala': 'lololo' }); 16 | }, 17 | JsSIP.Exceptions.ConfigurationError 18 | ); 19 | 20 | test.done(); 21 | }, 22 | 23 | 'UA no WS connection' : function(test) 24 | { 25 | const ua = new JsSIP.UA(testUA.UA_CONFIGURATION); 26 | 27 | console.log('LALALALALALA'); 28 | 29 | test.ok(ua instanceof(JsSIP.UA)); 30 | 31 | ua.start(); 32 | 33 | test.strictEqual(ua.contact.toString(), ``); 34 | test.strictEqual(ua.contact.toString({ outbound: false, anonymous: false, foo: true }), ``); 35 | test.strictEqual(ua.contact.toString({ outbound: true }), ``); 36 | test.strictEqual(ua.contact.toString({ anonymous: true }), ''); 37 | test.strictEqual(ua.contact.toString({ anonymous: true, outbound: true }), ''); 38 | 39 | for (const parameter in testUA.UA_CONFIGURATION_AFTER_START) 40 | { 41 | if (Object.prototype.hasOwnProperty.call( 42 | testUA.UA_CONFIGURATION_AFTER_START, parameter)) 43 | { 44 | switch (parameter) 45 | { 46 | case 'uri': 47 | case 'registrar_server': 48 | test.deepEqual(ua.configuration[parameter].toString(), testUA.UA_CONFIGURATION_AFTER_START[parameter], `testing parameter ${ parameter}`); 49 | break; 50 | case 'sockets': 51 | console.warn('IGNORE SOCKETS'); 52 | break; 53 | default: 54 | test.deepEqual(ua.configuration[parameter], testUA.UA_CONFIGURATION_AFTER_START[parameter], `testing parameter ${ parameter}`); 55 | } 56 | } 57 | } 58 | 59 | const transport = testUA.UA_TRANSPORT_AFTER_START; 60 | const sockets = transport.sockets; 61 | const socket = sockets[0].socket; 62 | 63 | test.deepEqual(sockets.length, ua.transport.sockets.length, 'testing transport sockets number'); 64 | test.deepEqual(sockets[0].weight, ua.transport.sockets[0].weight, 'testing sockets weight'); 65 | test.deepEqual(socket.via_transport, ua.transport.via_transport, 'testing transport via_transport'); 66 | test.deepEqual(socket.sip_uri, ua.transport.sip_uri, 'testing transport sip_uri'); 67 | test.deepEqual(socket.url, ua.transport.url, 'testing transport url'); 68 | 69 | test.deepEqual(transport.recovery_options, ua.transport.recovery_options, 'testing transport recovery_options'); 70 | 71 | ua.sendMessage('test', 'FAIL WITH CONNECTION_ERROR PLEASE', { 72 | eventHandlers : { 73 | failed : function(e) 74 | { 75 | test.strictEqual(e.cause, JsSIP.C.causes.CONNECTION_ERROR); 76 | } 77 | } 78 | }); 79 | 80 | test.throws( 81 | function() 82 | { 83 | ua.sendMessage('sip:ibc@iñaki.ðđß', 'FAIL WITH INVALID_TARGET PLEASE'); 84 | }, 85 | JsSIP.Exceptions.TypeError 86 | ); 87 | 88 | ua.stop(); 89 | test.done(); 90 | } 91 | 92 | }; 93 | -------------------------------------------------------------------------------- /webrtc/templates/jssip.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | WebPhone with JsSip And Asterisk 9 | 10 | 11 | 12 | 13 |
must set sip uri/password
14 |
15 | 23 | 31 | 32 | 33 | 53 | 54 | 55 |
56 |
57 | 58 |
59 |
60 | 61 |
62 |
63 |
64 | 65 | 66 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | [![Build Status](https://travis-ci.org/versatica/JsSIP.png?branch=new-design)](https://travis-ci.org/versatica/JsSIP) 4 | 5 | ## Overview 6 | 7 | * Runs in the browser and Node.js. 8 | * SIP over [WebSocket](http://jssip.net/documentation/misc/sip_websocket/) (use real SIP in your web apps) 9 | * Audio/video calls ([WebRTC](http://jssip.net/documentation/misc/webrtc)) and instant messaging 10 | * Lightweight! 11 | * Easy to use and powerful user API 12 | * Works with OverSIP, Kamailio, Asterisk. Mobicents and repro (reSIProcate) servers ([more info](http://jssip.net/documentation/misc/interoperability)) 13 | * Written by the authors of [RFC 7118 "The WebSocket Protocol as a Transport for SIP"](http://tools.ietf.org/html/rfc7118) and [OverSIP](http://oversip.net) 14 | 15 | 16 | ## NOTE 17 | 18 | Starting from 3.0.0, JsSIP no longer includes the [rtcninja](https://github.com/eface2face/rtcninja.js/) module. However, the [jssip-rtcninja](https://www.npmjs.com/package/jssip-rtcninja) package is based on the `2.0.x` branch, which does include `rtcninja`. 19 | 20 | 21 | ## Support 22 | 23 | * For questions or usage problems please use the **jssip** [public Google Group](https://groups.google.com/forum/#!forum/jssip). 24 | 25 | * For bug reports or feature requests open an [Github issue](https://github.com/versatica/JsSIP/issues). 26 | 27 | 28 | ## Getting Started 29 | 30 | The following simple JavaScript code creates a JsSIP User Agent instance and makes a SIP call: 31 | 32 | ```javascript 33 | // Create our JsSIP instance and run it: 34 | 35 | var socket = new JsSIP.WebSocketInterface('wss://sip.myhost.com'); 36 | var configuration = { 37 | sockets : [ socket ], 38 | uri : 'sip:alice@example.com', 39 | password : 'superpassword' 40 | }; 41 | 42 | var ua = new JsSIP.UA(configuration); 43 | 44 | ua.start(); 45 | 46 | // Register callbacks to desired call events 47 | var eventHandlers = { 48 | 'progress': function(e) { 49 | console.log('call is in progress'); 50 | }, 51 | 'failed': function(e) { 52 | console.log('call failed with cause: '+ e.data.cause); 53 | }, 54 | 'ended': function(e) { 55 | console.log('call ended with cause: '+ e.data.cause); 56 | }, 57 | 'confirmed': function(e) { 58 | console.log('call confirmed'); 59 | } 60 | }; 61 | 62 | var options = { 63 | 'eventHandlers' : eventHandlers, 64 | 'mediaConstraints' : { 'audio': true, 'video': true } 65 | }; 66 | 67 | var session = ua.call('sip:bob@example.com', options); 68 | ``` 69 | 70 | Want to see more? Check the full documentation at http://jssip.net/documentation/. 71 | 72 | 73 | ## Online Demo 74 | 75 | Check our **Tryit JsSIP** online demo: 76 | 77 | * [tryit.jssip.net](http://tryit.jssip.net) 78 | 79 | 80 | ## Website and Documentation 81 | 82 | * [jssip.net](http://jssip.net/) 83 | 84 | 85 | ## Download 86 | 87 | * As Node module: `$ npm install jssip` 88 | * As Bower module: `$ bower install jssip` 89 | * Manually: [jssip.net/download](http://jssip.net/download/) 90 | 91 | 92 | ## Authors 93 | 94 | #### José Luis Millán 95 | 96 | * Main author. Core Designer and Developer. 97 | * (Github [@jmillan](https://github.com/jmillan)) 98 | 99 | #### Iñaki Baz Castillo 100 | 101 | * Core Designer and Developer. 102 | * (Github [@ibc](https://github.com/ibc)) 103 | 104 | #### Saúl Ibarra Corretgé 105 | 106 | * Core Designer. 107 | * (Github [@saghul](https://github.com/saghul)) 108 | 109 | 110 | ## License 111 | 112 | JsSIP is released under the [MIT license](http://jssip.net/license). 113 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/RTCSession/ReferSubscriber.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events').EventEmitter; 2 | const JsSIP_C = require('../Constants'); 3 | const Grammar = require('../Grammar'); 4 | const Utils = require('../Utils'); 5 | const debug = require('debug')('JsSIP:RTCSession:ReferSubscriber'); 6 | 7 | module.exports = class ReferSubscriber extends EventEmitter 8 | { 9 | constructor(session) 10 | { 11 | super(); 12 | 13 | this._id = null; 14 | this._session = session; 15 | } 16 | 17 | get id() 18 | { 19 | return this._id; 20 | } 21 | 22 | sendRefer(target, options = {}) 23 | { 24 | debug('sendRefer()'); 25 | 26 | const extraHeaders = Utils.cloneArray(options.extraHeaders); 27 | const eventHandlers = options.eventHandlers || {}; 28 | 29 | // Set event handlers. 30 | for (const event in eventHandlers) 31 | { 32 | if (Object.prototype.hasOwnProperty.call(eventHandlers, event)) 33 | { 34 | this.on(event, eventHandlers[event]); 35 | } 36 | } 37 | 38 | // Replaces URI header field. 39 | let replaces = null; 40 | 41 | if (options.replaces) 42 | { 43 | replaces = options.replaces.request.call_id; 44 | replaces += `;to-tag=${options.replaces.to_tag}`; 45 | replaces += `;from-tag=${options.replaces.from_tag}`; 46 | 47 | replaces = encodeURIComponent(replaces); 48 | } 49 | 50 | // Refer-To header field. 51 | const referTo = `Refer-To: <${target}${replaces?`?Replaces=${replaces}`:''}>`; 52 | 53 | extraHeaders.push(referTo); 54 | 55 | const request = this._session.sendRequest(JsSIP_C.REFER, { 56 | extraHeaders, 57 | eventHandlers : { 58 | onSuccessResponse : (response) => 59 | { 60 | this._requestSucceeded(response); 61 | }, 62 | onErrorResponse : (response) => 63 | { 64 | this._requestFailed(response, JsSIP_C.causes.REJECTED); 65 | }, 66 | onTransportError : () => 67 | { 68 | this._requestFailed(null, JsSIP_C.causes.CONNECTION_ERROR); 69 | }, 70 | onRequestTimeout : () => 71 | { 72 | this._requestFailed(null, JsSIP_C.causes.REQUEST_TIMEOUT); 73 | }, 74 | onDialogError : () => 75 | { 76 | this._requestFailed(null, JsSIP_C.causes.DIALOG_ERROR); 77 | } 78 | } 79 | }); 80 | 81 | this._id = request.cseq; 82 | } 83 | 84 | receiveNotify(request) 85 | { 86 | debug('receiveNotify()'); 87 | 88 | if (!request.body) 89 | { 90 | return; 91 | } 92 | 93 | const status_line = Grammar.parse(request.body, 'Status_Line'); 94 | 95 | if (status_line === -1) 96 | { 97 | debug(`receiveNotify() | error parsing NOTIFY body: "${request.body}"`); 98 | 99 | return; 100 | } 101 | 102 | switch (true) 103 | { 104 | case /^100$/.test(status_line.status_code): 105 | this.emit('trying', { 106 | request, 107 | status_line 108 | }); 109 | break; 110 | 111 | case /^1[0-9]{2}$/.test(status_line.status_code): 112 | this.emit('progress', { 113 | request, 114 | status_line 115 | }); 116 | break; 117 | 118 | case /^2[0-9]{2}$/.test(status_line.status_code): 119 | this.emit('accepted', { 120 | request, 121 | status_line 122 | }); 123 | break; 124 | 125 | default: 126 | this.emit('failed', { 127 | request, 128 | status_line 129 | }); 130 | break; 131 | } 132 | } 133 | 134 | _requestSucceeded(response) 135 | { 136 | debug('REFER succeeded'); 137 | 138 | debug('emit "requestSucceeded"'); 139 | 140 | this.emit('requestSucceeded', { 141 | response 142 | }); 143 | } 144 | 145 | _requestFailed(response, cause) 146 | { 147 | debug('REFER failed'); 148 | 149 | debug('emit "requestFailed"'); 150 | 151 | this.emit('requestFailed', { 152 | response : response || null, 153 | cause 154 | }); 155 | } 156 | }; 157 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/WebSocketInterface.js: -------------------------------------------------------------------------------- 1 | const Grammar = require('./Grammar'); 2 | const debug = require('debug')('JsSIP:WebSocketInterface'); 3 | const debugerror = require('debug')('JsSIP:ERROR:WebSocketInterface'); 4 | 5 | debugerror.log = console.warn.bind(console); 6 | 7 | module.exports = class WebSocketInterface 8 | { 9 | constructor(url) 10 | { 11 | debug('new() [url:"%s"]', url); 12 | 13 | this._url = url; 14 | this._sip_uri = null; 15 | this._via_transport = null; 16 | this.ws = null; 17 | 18 | const parsed_url = Grammar.parse(url, 'absoluteURI'); 19 | 20 | if (parsed_url === -1) 21 | { 22 | debugerror(`invalid WebSocket URI: ${url}`); 23 | throw new TypeError(`Invalid argument: ${url}`); 24 | } 25 | else if (parsed_url.scheme !== 'wss' && parsed_url.scheme !== 'ws') 26 | { 27 | debugerror(`invalid WebSocket URI scheme: ${parsed_url.scheme}`); 28 | throw new TypeError(`Invalid argument: ${url}`); 29 | } 30 | else 31 | { 32 | this._sip_uri = `sip:${parsed_url.host}${parsed_url.port ? `:${parsed_url.port}` : ''};transport=ws`; 33 | this._via_transport = parsed_url.scheme; 34 | } 35 | } 36 | 37 | get via_transport() 38 | { 39 | return this._via_transport; 40 | } 41 | 42 | set via_transport(value) 43 | { 44 | this._via_transport = value.toUpperCase(); 45 | } 46 | 47 | get sip_uri() 48 | { 49 | return this._sip_uri; 50 | } 51 | 52 | get url() 53 | { 54 | return this._url; 55 | } 56 | 57 | connect() 58 | { 59 | debug('connect()'); 60 | 61 | if (this.isConnected()) 62 | { 63 | debug(`WebSocket ${this._url} is already connected`); 64 | 65 | return; 66 | } 67 | else if (this.isConnecting()) 68 | { 69 | debug(`WebSocket ${this._url} is connecting`); 70 | 71 | return; 72 | } 73 | 74 | if (this._ws) 75 | { 76 | this.disconnect(); 77 | } 78 | 79 | debug(`connecting to WebSocket ${this._url}`); 80 | 81 | try 82 | { 83 | this._ws = new WebSocket(this._url, 'sip'); 84 | 85 | this._ws.binaryType = 'arraybuffer'; 86 | 87 | this._ws.onopen = this._onOpen.bind(this); 88 | this._ws.onclose = this._onClose.bind(this); 89 | this._ws.onmessage = this._onMessage.bind(this); 90 | this._ws.onerror = this._onError.bind(this); 91 | } 92 | catch (e) 93 | { 94 | this._onError(e); 95 | } 96 | } 97 | 98 | disconnect() 99 | { 100 | debug('disconnect()'); 101 | 102 | if (this._ws) 103 | { 104 | // Unbind websocket event callbacks. 105 | this._ws.onopen = () => {}; 106 | this._ws.onclose = () => {}; 107 | this._ws.onmessage = () => {}; 108 | this._ws.onerror = () => {}; 109 | 110 | this._ws.close(); 111 | this._ws = null; 112 | } 113 | } 114 | 115 | send(message) 116 | { 117 | debug('send()'); 118 | 119 | if (this.isConnected()) 120 | { 121 | this._ws.send(message); 122 | 123 | return true; 124 | } 125 | else 126 | { 127 | debugerror('unable to send message, WebSocket is not open'); 128 | 129 | return false; 130 | } 131 | } 132 | 133 | isConnected() 134 | { 135 | return this._ws && this._ws.readyState === this._ws.OPEN; 136 | } 137 | 138 | isConnecting() 139 | { 140 | return this._ws && this._ws.readyState === this._ws.CONNECTING; 141 | } 142 | 143 | 144 | /** 145 | * WebSocket Event Handlers 146 | */ 147 | 148 | _onOpen() 149 | { 150 | debug(`WebSocket ${this._url} connected`); 151 | 152 | this.onconnect(); 153 | } 154 | 155 | _onClose({ wasClean, code, reason }) 156 | { 157 | debug(`WebSocket ${this._url} closed`); 158 | 159 | if (wasClean === false) 160 | { 161 | debug('WebSocket abrupt disconnection'); 162 | } 163 | 164 | const data = { 165 | socket : this, 166 | error : !wasClean, 167 | code, 168 | reason 169 | }; 170 | 171 | this.ondisconnect(data); 172 | } 173 | 174 | _onMessage({ data }) 175 | { 176 | debug('received WebSocket message'); 177 | 178 | this.ondata(data); 179 | } 180 | 181 | _onError(e) 182 | { 183 | debugerror(`WebSocket ${this._url} error: ${e}`); 184 | } 185 | }; 186 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/Dialog/RequestSender.js: -------------------------------------------------------------------------------- 1 | const JsSIP_C = require('../Constants'); 2 | const Transactions = require('../Transactions'); 3 | const RTCSession = require('../RTCSession'); 4 | const RequestSender = require('../RequestSender'); 5 | 6 | // Default event handlers. 7 | const EventHandlers = { 8 | onRequestTimeout : () => {}, 9 | onTransportError : () => {}, 10 | onSuccessResponse : () => {}, 11 | onErrorResponse : () => {}, 12 | onAuthenticated : () => {}, 13 | onDialogError : () => {} 14 | }; 15 | 16 | module.exports = class DialogRequestSender 17 | { 18 | constructor(dialog, request, eventHandlers) 19 | { 20 | this._dialog = dialog; 21 | this._ua = dialog._ua; 22 | this._request = request; 23 | this._eventHandlers = eventHandlers; 24 | 25 | // RFC3261 14.1 Modifying an Existing Session. UAC Behavior. 26 | this._reattempt = false; 27 | this._reattemptTimer = null; 28 | 29 | // Define the undefined handlers. 30 | for (const handler in EventHandlers) 31 | { 32 | if (Object.prototype.hasOwnProperty.call(EventHandlers, handler)) 33 | { 34 | if (!this._eventHandlers[handler]) 35 | { 36 | this._eventHandlers[handler] = EventHandlers[handler]; 37 | } 38 | } 39 | } 40 | } 41 | 42 | get request() 43 | { 44 | return this._request; 45 | } 46 | 47 | send() 48 | { 49 | const request_sender = new RequestSender(this._ua, this._request, { 50 | onRequestTimeout : () => 51 | { 52 | this._eventHandlers.onRequestTimeout(); 53 | }, 54 | onTransportError : () => 55 | { 56 | this._eventHandlers.onTransportError(); 57 | }, 58 | onAuthenticated : (request) => 59 | { 60 | this._eventHandlers.onAuthenticated(request); 61 | }, 62 | onReceiveResponse : (response) => 63 | { 64 | this._receiveResponse(response); 65 | } 66 | }); 67 | 68 | request_sender.send(); 69 | 70 | // RFC3261 14.2 Modifying an Existing Session -UAC BEHAVIOR-. 71 | if ((this._request.method === JsSIP_C.INVITE || 72 | (this._request.method === JsSIP_C.UPDATE && this._request.body)) && 73 | request_sender.clientTransaction.state !== Transactions.C.STATUS_TERMINATED) 74 | { 75 | this._dialog.uac_pending_reply = true; 76 | 77 | const stateChanged = () => 78 | { 79 | if (request_sender.clientTransaction.state === Transactions.C.STATUS_ACCEPTED || 80 | request_sender.clientTransaction.state === Transactions.C.STATUS_COMPLETED || 81 | request_sender.clientTransaction.state === Transactions.C.STATUS_TERMINATED) 82 | { 83 | request_sender.clientTransaction.removeListener('stateChanged', stateChanged); 84 | this._dialog.uac_pending_reply = false; 85 | } 86 | }; 87 | 88 | request_sender.clientTransaction.on('stateChanged', stateChanged); 89 | } 90 | } 91 | 92 | _receiveResponse(response) 93 | { 94 | // RFC3261 12.2.1.2 408 or 481 is received for a request within a dialog. 95 | if (response.status_code === 408 || response.status_code === 481) 96 | { 97 | this._eventHandlers.onDialogError(response); 98 | } 99 | else if (response.method === JsSIP_C.INVITE && response.status_code === 491) 100 | { 101 | if (this._reattempt) 102 | { 103 | if (response.status_code >= 200 && response.status_code < 300) 104 | { 105 | this._eventHandlers.onSuccessResponse(response); 106 | } 107 | else if (response.status_code >= 300) 108 | { 109 | this._eventHandlers.onErrorResponse(response); 110 | } 111 | } 112 | else 113 | { 114 | this._request.cseq.value = this._dialog.local_seqnum += 1; 115 | this._reattemptTimer = setTimeout(() => 116 | { 117 | // TODO: look at dialog state instead. 118 | if (this._dialog.owner.status !== RTCSession.C.STATUS_TERMINATED) 119 | { 120 | this._reattempt = true; 121 | this._request_sender.send(); 122 | } 123 | }, 1000); 124 | } 125 | } 126 | else if (response.status_code >= 200 && response.status_code < 300) 127 | { 128 | this._eventHandlers.onSuccessResponse(response); 129 | } 130 | else if (response.status_code >= 300) 131 | { 132 | this._eventHandlers.onErrorResponse(response); 133 | } 134 | } 135 | }; 136 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/gulpfile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 'use strict'; 3 | /* eslint-enable strict */ 4 | 5 | /** 6 | * Dependencies. 7 | */ 8 | const browserify = require('browserify'); 9 | const source = require('vinyl-source-stream'); 10 | const buffer = require('vinyl-buffer'); 11 | const gulp = require('gulp'); 12 | const gutil = require('gulp-util'); 13 | const uglify = require('gulp-uglify'); 14 | const rename = require('gulp-rename'); 15 | const header = require('gulp-header'); 16 | const expect = require('gulp-expect-file'); 17 | const nodeunit = require('gulp-nodeunit-runner'); 18 | const fs = require('fs'); 19 | const path = require('path'); 20 | const exec = require('child_process').exec; 21 | const eslint = require('gulp-eslint'); 22 | const plumber = require('gulp-plumber'); 23 | 24 | const PKG = require('./package.json'); 25 | 26 | // gulp-header. 27 | const BANNER = fs.readFileSync('banner.txt').toString(); 28 | const BANNER_OPTIONS = { 29 | pkg : PKG, 30 | currentYear : (new Date()).getFullYear() 31 | }; 32 | 33 | // gulp-expect-file options. 34 | const EXPECT_OPTIONS = { 35 | silent : true, 36 | errorOnFailure : true, 37 | checkRealFile : true 38 | }; 39 | 40 | function logError(error) 41 | { 42 | gutil.log(gutil.colors.red(String(error))); 43 | } 44 | 45 | gulp.task('lint', function() 46 | { 47 | const src = [ 'gulpfile.js', '.eslintrc.js', 'lib/**/*.js', 'test/**/*.js' ]; 48 | 49 | return gulp.src(src) 50 | .pipe(plumber()) 51 | .pipe(eslint()) 52 | .pipe(eslint.format()); 53 | }); 54 | 55 | 56 | gulp.task('browserify', function() 57 | { 58 | return browserify( 59 | { 60 | entries : PKG.main, 61 | extensions : [ '.js' ], 62 | // Required for sourcemaps (must be false otherwise). 63 | debug : false, 64 | // Required for watchify (not used here). 65 | cache : null, 66 | // Required for watchify (not used here). 67 | packageCache : null, 68 | // Required to be true only for watchify (not used here). 69 | fullPaths : false, 70 | standalone : PKG.title 71 | }) 72 | .bundle() 73 | .on('error', logError) 74 | .pipe(source(`${PKG.name}.js`)) 75 | .pipe(buffer()) 76 | .pipe(rename(`${PKG.name}.js`)) 77 | .pipe(header(BANNER, BANNER_OPTIONS)) 78 | .pipe(gulp.dest('dist/')); 79 | }); 80 | 81 | 82 | gulp.task('uglify', function() 83 | { 84 | const src = `dist/${ PKG.name }.js`; 85 | 86 | return gulp.src(src) 87 | .pipe(expect(EXPECT_OPTIONS, src)) 88 | .pipe(uglify()) 89 | .pipe(header(BANNER, BANNER_OPTIONS)) 90 | .pipe(rename(`${PKG.name }.min.js`)) 91 | .pipe(gulp.dest('dist/')); 92 | }); 93 | 94 | 95 | gulp.task('test', function() 96 | { 97 | // var src = 'test/*.js'; 98 | const src = [ 99 | 'test/test-classes.js', 100 | 'test/test-normalizeTarget.js', 101 | 'test/test-parser.js', 102 | 'test/test-properties.js' 103 | // 'test/test-UA-no-WebRTC.js' 104 | ]; 105 | 106 | return gulp.src(src) 107 | .pipe(expect(EXPECT_OPTIONS, src)) 108 | .pipe(nodeunit({ reporter: 'default' })); 109 | }); 110 | 111 | 112 | gulp.task('grammar', function(cb) 113 | { 114 | const local_pegjs = path.resolve('./node_modules/.bin/pegjs'); 115 | const Grammar_pegjs = path.resolve('lib/Grammar.pegjs'); 116 | const Grammar_js = path.resolve('lib/Grammar.js'); 117 | 118 | gutil.log('grammar: compiling Grammar.pegjs into Grammar.js...'); 119 | 120 | exec(`${local_pegjs } ${ Grammar_pegjs } ${ Grammar_js}`, 121 | function(error, stdout, stderr) 122 | { 123 | if (error) 124 | { 125 | cb(new Error(stderr)); 126 | } 127 | gutil.log(`grammar: ${ gutil.colors.yellow('done')}`); 128 | 129 | // Modify the generated Grammar.js file with custom changes. 130 | gutil.log('grammar: applying custom changes to Grammar.js...'); 131 | 132 | const grammar = fs.readFileSync('lib/Grammar.js').toString(); 133 | let modified_grammar = grammar.replace(/throw new this\.SyntaxError\(([\s\S]*?)\);([\s\S]*?)}([\s\S]*?)return result;/, 'new this.SyntaxError($1);\n return -1;$2}$3return data;'); 134 | 135 | modified_grammar = modified_grammar.replace(/\s+$/mg, ''); 136 | fs.writeFileSync('lib/Grammar.js', modified_grammar); 137 | gutil.log(`grammar: ${ gutil.colors.yellow('done')}`); 138 | cb(); 139 | } 140 | ); 141 | }); 142 | 143 | 144 | gulp.task('devel', gulp.series('grammar')); 145 | gulp.task('dist', gulp.series('lint', 'test', 'browserify', 'uglify')); 146 | gulp.task('default', gulp.series('dist')); 147 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | { 3 | env: 4 | { 5 | browser: true, 6 | es6: true, 7 | node: true 8 | }, 9 | plugins: 10 | [ 11 | ], 12 | extends: 13 | [ 14 | 'eslint:recommended' 15 | ], 16 | settings: {}, 17 | parserOptions: 18 | { 19 | ecmaVersion: 6, 20 | sourceType: 'script', 21 | ecmaFeatures: 22 | { 23 | impliedStrict: true 24 | } 25 | }, 26 | rules: 27 | { 28 | 'array-bracket-spacing': [ 2, 'always', 29 | { 30 | objectsInArrays: true, 31 | arraysInArrays: true 32 | }], 33 | 'arrow-parens': [ 2, 'always' ], 34 | 'arrow-spacing': 2, 35 | 'block-spacing': [ 2, 'always' ], 36 | 'brace-style': [ 2, 'allman', { allowSingleLine: true } ], 37 | 'camelcase': 0, 38 | 'comma-dangle': 2, 39 | 'comma-spacing': [ 2, { before: false, after: true } ], 40 | 'comma-style': 2, 41 | 'computed-property-spacing': 2, 42 | 'constructor-super': 2, 43 | 'func-call-spacing': 2, 44 | 'generator-star-spacing': 2, 45 | 'guard-for-in': 2, 46 | 'indent': [ 2, 2, { 'SwitchCase': 1 } ], 47 | 'key-spacing': [ 2, 48 | { 49 | singleLine: 50 | { 51 | beforeColon: false, 52 | afterColon: true 53 | }, 54 | multiLine: 55 | { 56 | beforeColon: true, 57 | afterColon: true, 58 | align: 'colon' 59 | } 60 | }], 61 | 'keyword-spacing': 2, 62 | 'linebreak-style': [ 2, 'unix' ], 63 | 'lines-around-comment': [ 2, 64 | { 65 | allowBlockStart: true, 66 | allowObjectStart: true, 67 | beforeBlockComment: true, 68 | beforeLineComment: false 69 | }], 70 | 'max-len': [ 2, 90, 71 | { 72 | tabWidth: 2, 73 | comments: 110, 74 | ignoreUrls: true, 75 | ignoreStrings: true, 76 | ignoreTemplateLiterals: true, 77 | ignoreRegExpLiterals: true 78 | }], 79 | 'newline-after-var': 2, 80 | 'newline-before-return': 2, 81 | 'newline-per-chained-call': 2, 82 | 'no-alert': 2, 83 | 'no-caller': 2, 84 | 'no-case-declarations': 2, 85 | 'no-catch-shadow': 2, 86 | 'no-class-assign': 2, 87 | 'no-confusing-arrow': 2, 88 | 'no-console': [ 2, { allow: [ 'warn' ] } ], 89 | 'no-const-assign': 2, 90 | 'no-constant-condition': [ 2 , { 'checkLoops': false } ], 91 | 'no-debugger': 2, 92 | 'no-dupe-args': 2, 93 | 'no-dupe-keys': 2, 94 | 'no-duplicate-case': 2, 95 | 'no-div-regex': 2, 96 | 'no-empty': [ 2, { allowEmptyCatch: true } ], 97 | 'no-empty-pattern': 2, 98 | 'no-else-return': 0, 99 | 'no-eval': 2, 100 | 'no-extend-native': 2, 101 | 'no-ex-assign': 2, 102 | 'no-extra-bind': 2, 103 | 'no-extra-boolean-cast': 2, 104 | 'no-extra-label': 2, 105 | 'no-extra-semi': 2, 106 | 'no-fallthrough': 2, 107 | 'no-func-assign': 2, 108 | 'no-global-assign': 2, 109 | 'no-implicit-coercion': 2, 110 | 'no-implicit-globals': 2, 111 | 'no-inner-declarations': 2, 112 | 'no-invalid-regexp': 2, 113 | 'no-invalid-this': 0, 114 | 'no-irregular-whitespace': 2, 115 | 'no-lonely-if': 2, 116 | 'no-mixed-operators': 2, 117 | 'no-mixed-spaces-and-tabs': 2, 118 | 'no-multi-spaces': 2, 119 | 'no-multi-str': 2, 120 | 'no-multiple-empty-lines': 2, 121 | 'no-native-reassign': 2, 122 | 'no-negated-in-lhs': 2, 123 | 'no-new': 2, 124 | 'no-new-func': 2, 125 | 'no-new-wrappers': 2, 126 | 'no-obj-calls': 2, 127 | 'no-proto': 2, 128 | 'no-prototype-builtins': 0, 129 | 'no-redeclare': 2, 130 | 'no-regex-spaces': 2, 131 | 'no-restricted-imports': 2, 132 | 'no-return-assign': 2, 133 | 'no-self-assign': 2, 134 | 'no-self-compare': 2, 135 | 'no-sequences': 2, 136 | 'no-shadow': 2, 137 | 'no-shadow-restricted-names': 2, 138 | 'no-spaced-func': 2, 139 | 'no-sparse-arrays': 2, 140 | 'no-this-before-super': 2, 141 | 'no-throw-literal': 2, 142 | 'no-undef': 2, 143 | 'no-unexpected-multiline': 2, 144 | 'no-unmodified-loop-condition': 2, 145 | 'no-unreachable': 2, 146 | 'no-unused-vars': [ 1, { vars: 'all', args: 'after-used' }], 147 | 'no-use-before-define': [ 2, { functions: false } ], 148 | 'no-useless-call': 2, 149 | 'no-useless-computed-key': 2, 150 | 'no-useless-concat': 2, 151 | 'no-useless-rename': 2, 152 | 'no-var': 2, 153 | 'no-whitespace-before-property': 2, 154 | 'object-curly-newline': 0, 155 | 'object-curly-spacing': [ 2, 'always' ], 156 | 'object-property-newline': [ 2, { allowMultiplePropertiesPerLine: true } ], 157 | 'prefer-const': 2, 158 | 'prefer-rest-params': 2, 159 | 'prefer-spread': 2, 160 | 'prefer-template': 2, 161 | 'quotes': [ 2, 'single', { avoidEscape: true } ], 162 | 'semi': [ 2, 'always' ], 163 | 'semi-spacing': 2, 164 | 'space-before-blocks': 2, 165 | 'space-before-function-paren': [ 2, 'never' ], 166 | 'space-in-parens': [ 2, 'never' ], 167 | 'spaced-comment': [ 2, 'always' ], 168 | 'strict': 2, 169 | 'valid-typeof': 2, 170 | 'yoda': 2 171 | } 172 | }; 173 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/RTCSession/DTMF.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events').EventEmitter; 2 | const JsSIP_C = require('../Constants'); 3 | const Exceptions = require('../Exceptions'); 4 | const Utils = require('../Utils'); 5 | const debug = require('debug')('JsSIP:RTCSession:DTMF'); 6 | const debugerror = require('debug')('JsSIP:ERROR:RTCSession:DTMF'); 7 | 8 | debugerror.log = console.warn.bind(console); 9 | 10 | const C = { 11 | MIN_DURATION : 70, 12 | MAX_DURATION : 6000, 13 | DEFAULT_DURATION : 100, 14 | MIN_INTER_TONE_GAP : 50, 15 | DEFAULT_INTER_TONE_GAP : 500 16 | }; 17 | 18 | module.exports = class DTMF extends EventEmitter 19 | { 20 | constructor(session) 21 | { 22 | super(); 23 | 24 | this._session = session; 25 | this._direction = null; 26 | this._tone = null; 27 | this._duration = null; 28 | this._request = null; 29 | } 30 | 31 | get tone() 32 | { 33 | return this._tone; 34 | } 35 | 36 | get duration() 37 | { 38 | return this._duration; 39 | } 40 | 41 | send(tone, options = {}) 42 | { 43 | if (tone === undefined) 44 | { 45 | throw new TypeError('Not enough arguments'); 46 | } 47 | 48 | this._direction = 'outgoing'; 49 | 50 | // Check RTCSession Status. 51 | if (this._session.status !== this._session.C.STATUS_CONFIRMED && 52 | this._session.status !== this._session.C.STATUS_WAITING_FOR_ACK) 53 | { 54 | throw new Exceptions.InvalidStateError(this._session.status); 55 | } 56 | 57 | const extraHeaders = Utils.cloneArray(options.extraHeaders); 58 | 59 | this.eventHandlers = options.eventHandlers || {}; 60 | 61 | // Check tone type. 62 | if (typeof tone === 'string') 63 | { 64 | tone = tone.toUpperCase(); 65 | } 66 | else if (typeof tone === 'number') 67 | { 68 | tone = tone.toString(); 69 | } 70 | else 71 | { 72 | throw new TypeError(`Invalid tone: ${tone}`); 73 | } 74 | 75 | // Check tone value. 76 | if (!tone.match(/^[0-9A-DR#*]$/)) 77 | { 78 | throw new TypeError(`Invalid tone: ${tone}`); 79 | } 80 | else 81 | { 82 | this._tone = tone; 83 | } 84 | 85 | // Duration is checked/corrected in RTCSession. 86 | this._duration = options.duration; 87 | 88 | extraHeaders.push('Content-Type: application/dtmf-relay'); 89 | 90 | let body = `Signal=${this._tone}\r\n`; 91 | 92 | body += `Duration=${this._duration}`; 93 | 94 | this._session.newDTMF({ 95 | originator : 'local', 96 | dtmf : this, 97 | request : this._request 98 | }); 99 | 100 | this._session.sendRequest(JsSIP_C.INFO, { 101 | extraHeaders, 102 | eventHandlers : { 103 | onSuccessResponse : (response) => 104 | { 105 | this.emit('succeeded', { 106 | originator : 'remote', 107 | response 108 | }); 109 | }, 110 | onErrorResponse : (response) => 111 | { 112 | if (this.eventHandlers.onFailed) 113 | { 114 | this.eventHandlers.onFailed(); 115 | } 116 | 117 | this.emit('failed', { 118 | originator : 'remote', 119 | response 120 | }); 121 | }, 122 | onRequestTimeout : () => 123 | { 124 | this._session.onRequestTimeout(); 125 | }, 126 | onTransportError : () => 127 | { 128 | this._session.onTransportError(); 129 | }, 130 | onDialogError : () => 131 | { 132 | this._session.onDialogError(); 133 | } 134 | }, 135 | body 136 | }); 137 | } 138 | 139 | init_incoming(request) 140 | { 141 | const reg_tone = /^(Signal\s*?=\s*?)([0-9A-D#*]{1})(\s)?.*/; 142 | const reg_duration = /^(Duration\s?=\s?)([0-9]{1,4})(\s)?.*/; 143 | 144 | this._direction = 'incoming'; 145 | this._request = request; 146 | 147 | request.reply(200); 148 | 149 | if (request.body) 150 | { 151 | const body = request.body.split('\n'); 152 | 153 | if (body.length >= 1) 154 | { 155 | if (reg_tone.test(body[0])) 156 | { 157 | this._tone = body[0].replace(reg_tone, '$2'); 158 | } 159 | } 160 | if (body.length >=2) 161 | { 162 | if (reg_duration.test(body[1])) 163 | { 164 | this._duration = parseInt(body[1].replace(reg_duration, '$2'), 10); 165 | } 166 | } 167 | } 168 | 169 | if (!this._duration) 170 | { 171 | this._duration = C.DEFAULT_DURATION; 172 | } 173 | 174 | if (!this._tone) 175 | { 176 | debug('invalid INFO DTMF received, discarded'); 177 | } 178 | else 179 | { 180 | this._session.newDTMF({ 181 | originator : 'remote', 182 | dtmf : this, 183 | request 184 | }); 185 | } 186 | } 187 | }; 188 | 189 | /** 190 | * Expose C object. 191 | */ 192 | module.exports.C = C; 193 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/RequestSender.js: -------------------------------------------------------------------------------- 1 | const JsSIP_C = require('./Constants'); 2 | const DigestAuthentication = require('./DigestAuthentication'); 3 | const Transactions = require('./Transactions'); 4 | const debug = require('debug')('JsSIP:RequestSender'); 5 | 6 | // Default event handlers. 7 | const EventHandlers = { 8 | onRequestTimeout : () => {}, 9 | onTransportError : () => {}, 10 | onReceiveResponse : () => {}, 11 | onAuthenticated : () => {} 12 | }; 13 | 14 | module.exports = class RequestSender 15 | { 16 | constructor(ua, request, eventHandlers) 17 | { 18 | this._ua = ua; 19 | this._eventHandlers = eventHandlers; 20 | this._method = request.method; 21 | this._request = request; 22 | this._auth = null; 23 | this._challenged = false; 24 | this._staled = false; 25 | 26 | // Define the undefined handlers. 27 | for (const handler in EventHandlers) 28 | { 29 | if (Object.prototype.hasOwnProperty.call(EventHandlers, handler)) 30 | { 31 | if (!this._eventHandlers[handler]) 32 | { 33 | this._eventHandlers[handler] = EventHandlers[handler]; 34 | } 35 | } 36 | } 37 | 38 | // If ua is in closing process or even closed just allow sending Bye and ACK. 39 | if (ua.status === ua.C.STATUS_USER_CLOSED && 40 | (this._method !== JsSIP_C.BYE || this._method !== JsSIP_C.ACK)) 41 | { 42 | this._eventHandlers.onTransportError(); 43 | } 44 | } 45 | 46 | /** 47 | * Create the client transaction and send the message. 48 | */ 49 | send() 50 | { 51 | const eventHandlers = { 52 | onRequestTimeout : () => { this._eventHandlers.onRequestTimeout(); }, 53 | onTransportError : () => { this._eventHandlers.onTransportError(); }, 54 | onReceiveResponse : (response) => { this._receiveResponse(response); } 55 | }; 56 | 57 | switch (this._method) 58 | { 59 | case 'INVITE': 60 | this.clientTransaction = new Transactions.InviteClientTransaction( 61 | this._ua, this._ua.transport, this._request, eventHandlers); 62 | break; 63 | case 'ACK': 64 | this.clientTransaction = new Transactions.AckClientTransaction( 65 | this._ua, this._ua.transport, this._request, eventHandlers); 66 | break; 67 | default: 68 | this.clientTransaction = new Transactions.NonInviteClientTransaction( 69 | this._ua, this._ua.transport, this._request, eventHandlers); 70 | } 71 | 72 | this.clientTransaction.send(); 73 | } 74 | 75 | /** 76 | * Called from client transaction when receiving a correct response to the request. 77 | * Authenticate request if needed or pass the response back to the applicant. 78 | */ 79 | _receiveResponse(response) 80 | { 81 | let challenge; 82 | let authorization_header_name; 83 | const status_code = response.status_code; 84 | 85 | /* 86 | * Authentication 87 | * Authenticate once. _challenged_ flag used to avoid infinite authentications. 88 | */ 89 | if ((status_code === 401 || status_code === 407) && 90 | (this._ua.configuration.password !== null || this._ua.configuration.ha1 !== null)) 91 | { 92 | 93 | // Get and parse the appropriate WWW-Authenticate or Proxy-Authenticate header. 94 | if (response.status_code === 401) 95 | { 96 | challenge = response.parseHeader('www-authenticate'); 97 | authorization_header_name = 'authorization'; 98 | } 99 | else 100 | { 101 | challenge = response.parseHeader('proxy-authenticate'); 102 | authorization_header_name = 'proxy-authorization'; 103 | } 104 | 105 | // Verify it seems a valid challenge. 106 | if (!challenge) 107 | { 108 | debug(`${response.status_code} with wrong or missing challenge, cannot authenticate`); 109 | this._eventHandlers.onReceiveResponse(response); 110 | 111 | return; 112 | } 113 | 114 | if (!this._challenged || (!this._staled && challenge.stale === true)) 115 | { 116 | if (!this._auth) 117 | { 118 | this._auth = new DigestAuthentication({ 119 | username : this._ua.configuration.authorization_user, 120 | password : this._ua.configuration.password, 121 | realm : this._ua.configuration.realm, 122 | ha1 : this._ua.configuration.ha1 123 | }); 124 | } 125 | 126 | // Verify that the challenge is really valid. 127 | if (!this._auth.authenticate(this._request, challenge)) 128 | { 129 | this._eventHandlers.onReceiveResponse(response); 130 | 131 | return; 132 | } 133 | this._challenged = true; 134 | 135 | // Update ha1 and realm in the UA. 136 | this._ua.set('realm', this._auth.get('realm')); 137 | this._ua.set('ha1', this._auth.get('ha1')); 138 | 139 | if (challenge.stale) 140 | { 141 | this._staled = true; 142 | } 143 | 144 | this._request = this._request.clone(); 145 | this._request.cseq += 1; 146 | this._request.setHeader('cseq', `${this._request.cseq} ${this._method}`); 147 | this._request.setHeader(authorization_header_name, this._auth.toString()); 148 | 149 | this._eventHandlers.onAuthenticated(this._request); 150 | this.send(); 151 | } 152 | else 153 | { 154 | this._eventHandlers.onReceiveResponse(response); 155 | } 156 | } 157 | else 158 | { 159 | this._eventHandlers.onReceiveResponse(response); 160 | } 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/Constants.js: -------------------------------------------------------------------------------- 1 | const pkg = require('../package.json'); 2 | 3 | module.exports = { 4 | USER_AGENT : `${pkg.title} ${pkg.version}`, 5 | 6 | // SIP scheme. 7 | SIP : 'sip', 8 | SIPS : 'sips', 9 | 10 | // End and Failure causes. 11 | causes : { 12 | // Generic error causes. 13 | CONNECTION_ERROR : 'Connection Error', 14 | REQUEST_TIMEOUT : 'Request Timeout', 15 | SIP_FAILURE_CODE : 'SIP Failure Code', 16 | INTERNAL_ERROR : 'Internal Error', 17 | 18 | // SIP error causes. 19 | BUSY : 'Busy', 20 | REJECTED : 'Rejected', 21 | REDIRECTED : 'Redirected', 22 | UNAVAILABLE : 'Unavailable', 23 | NOT_FOUND : 'Not Found', 24 | ADDRESS_INCOMPLETE : 'Address Incomplete', 25 | INCOMPATIBLE_SDP : 'Incompatible SDP', 26 | MISSING_SDP : 'Missing SDP', 27 | AUTHENTICATION_ERROR : 'Authentication Error', 28 | 29 | // Session error causes. 30 | BYE : 'Terminated', 31 | WEBRTC_ERROR : 'WebRTC Error', 32 | CANCELED : 'Canceled', 33 | NO_ANSWER : 'No Answer', 34 | EXPIRES : 'Expires', 35 | NO_ACK : 'No ACK', 36 | DIALOG_ERROR : 'Dialog Error', 37 | USER_DENIED_MEDIA_ACCESS : 'User Denied Media Access', 38 | BAD_MEDIA_DESCRIPTION : 'Bad Media Description', 39 | RTP_TIMEOUT : 'RTP Timeout' 40 | }, 41 | 42 | SIP_ERROR_CAUSES : { 43 | REDIRECTED : [ 300, 301, 302, 305, 380 ], 44 | BUSY : [ 486, 600 ], 45 | REJECTED : [ 403, 603 ], 46 | NOT_FOUND : [ 404, 604 ], 47 | UNAVAILABLE : [ 480, 410, 408, 430 ], 48 | ADDRESS_INCOMPLETE : [ 484, 424 ], 49 | INCOMPATIBLE_SDP : [ 488, 606 ], 50 | AUTHENTICATION_ERROR : [ 401, 407 ] 51 | }, 52 | 53 | // SIP Methods. 54 | ACK : 'ACK', 55 | BYE : 'BYE', 56 | CANCEL : 'CANCEL', 57 | INFO : 'INFO', 58 | INVITE : 'INVITE', 59 | MESSAGE : 'MESSAGE', 60 | NOTIFY : 'NOTIFY', 61 | OPTIONS : 'OPTIONS', 62 | REGISTER : 'REGISTER', 63 | REFER : 'REFER', 64 | UPDATE : 'UPDATE', 65 | SUBSCRIBE : 'SUBSCRIBE', 66 | 67 | /* SIP Response Reasons 68 | * DOC: http://www.iana.org/assignments/sip-parameters 69 | * Copied from https://github.com/versatica/OverSIP/blob/master/lib/oversip/sip/constants.rb#L7 70 | */ 71 | REASON_PHRASE : { 72 | 100 : 'Trying', 73 | 180 : 'Ringing', 74 | 181 : 'Call Is Being Forwarded', 75 | 182 : 'Queued', 76 | 183 : 'Session Progress', 77 | 199 : 'Early Dialog Terminated', // draft-ietf-sipcore-199 78 | 200 : 'OK', 79 | 202 : 'Accepted', // RFC 3265 80 | 204 : 'No Notification', // RFC 5839 81 | 300 : 'Multiple Choices', 82 | 301 : 'Moved Permanently', 83 | 302 : 'Moved Temporarily', 84 | 305 : 'Use Proxy', 85 | 380 : 'Alternative Service', 86 | 400 : 'Bad Request', 87 | 401 : 'Unauthorized', 88 | 402 : 'Payment Required', 89 | 403 : 'Forbidden', 90 | 404 : 'Not Found', 91 | 405 : 'Method Not Allowed', 92 | 406 : 'Not Acceptable', 93 | 407 : 'Proxy Authentication Required', 94 | 408 : 'Request Timeout', 95 | 410 : 'Gone', 96 | 412 : 'Conditional Request Failed', // RFC 3903 97 | 413 : 'Request Entity Too Large', 98 | 414 : 'Request-URI Too Long', 99 | 415 : 'Unsupported Media Type', 100 | 416 : 'Unsupported URI Scheme', 101 | 417 : 'Unknown Resource-Priority', // RFC 4412 102 | 420 : 'Bad Extension', 103 | 421 : 'Extension Required', 104 | 422 : 'Session Interval Too Small', // RFC 4028 105 | 423 : 'Interval Too Brief', 106 | 424 : 'Bad Location Information', // RFC 6442 107 | 428 : 'Use Identity Header', // RFC 4474 108 | 429 : 'Provide Referrer Identity', // RFC 3892 109 | 430 : 'Flow Failed', // RFC 5626 110 | 433 : 'Anonymity Disallowed', // RFC 5079 111 | 436 : 'Bad Identity-Info', // RFC 4474 112 | 437 : 'Unsupported Certificate', // RFC 4744 113 | 438 : 'Invalid Identity Header', // RFC 4744 114 | 439 : 'First Hop Lacks Outbound Support', // RFC 5626 115 | 440 : 'Max-Breadth Exceeded', // RFC 5393 116 | 469 : 'Bad Info Package', // draft-ietf-sipcore-info-events 117 | 470 : 'Consent Needed', // RFC 5360 118 | 478 : 'Unresolvable Destination', // Custom code copied from Kamailio. 119 | 480 : 'Temporarily Unavailable', 120 | 481 : 'Call/Transaction Does Not Exist', 121 | 482 : 'Loop Detected', 122 | 483 : 'Too Many Hops', 123 | 484 : 'Address Incomplete', 124 | 485 : 'Ambiguous', 125 | 486 : 'Busy Here', 126 | 487 : 'Request Terminated', 127 | 488 : 'Not Acceptable Here', 128 | 489 : 'Bad Event', // RFC 3265 129 | 491 : 'Request Pending', 130 | 493 : 'Undecipherable', 131 | 494 : 'Security Agreement Required', // RFC 3329 132 | 500 : 'JsSIP Internal Error', 133 | 501 : 'Not Implemented', 134 | 502 : 'Bad Gateway', 135 | 503 : 'Service Unavailable', 136 | 504 : 'Server Time-out', 137 | 505 : 'Version Not Supported', 138 | 513 : 'Message Too Large', 139 | 580 : 'Precondition Failure', // RFC 3312 140 | 600 : 'Busy Everywhere', 141 | 603 : 'Decline', 142 | 604 : 'Does Not Exist Anywhere', 143 | 606 : 'Not Acceptable' 144 | }, 145 | 146 | ALLOWED_METHODS : 'INVITE,ACK,CANCEL,BYE,UPDATE,MESSAGE,OPTIONS,REFER,INFO', 147 | ACCEPTED_BODY_TYPES : 'application/sdp, application/dtmf-relay', 148 | MAX_FORWARDS : 69, 149 | SESSION_EXPIRES : 90, 150 | MIN_SESSION_EXPIRES : 60 151 | }; 152 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/test/test-classes.js: -------------------------------------------------------------------------------- 1 | require('./include/common'); 2 | const JsSIP = require('../'); 3 | 4 | 5 | module.exports = { 6 | 7 | 'new URI' : function(test) 8 | { 9 | const uri = new JsSIP.URI(null, 'alice', 'jssip.net', 6060); 10 | 11 | test.strictEqual(uri.scheme, 'sip'); 12 | test.strictEqual(uri.user, 'alice'); 13 | test.strictEqual(uri.host, 'jssip.net'); 14 | test.strictEqual(uri.port, 6060); 15 | test.strictEqual(uri.toString(), 'sip:alice@jssip.net:6060'); 16 | test.strictEqual(uri.toAor(), 'sip:alice@jssip.net'); 17 | test.strictEqual(uri.toAor(false), 'sip:alice@jssip.net'); 18 | test.strictEqual(uri.toAor(true), 'sip:alice@jssip.net:6060'); 19 | 20 | uri.scheme = 'SIPS'; 21 | test.strictEqual(uri.scheme, 'sips'); 22 | test.strictEqual(uri.toAor(), 'sips:alice@jssip.net'); 23 | uri.scheme = 'sip'; 24 | 25 | uri.user = 'Iñaki ðđ'; 26 | test.strictEqual(uri.user, 'Iñaki ðđ'); 27 | test.strictEqual(uri.toString(), 'sip:I%C3%B1aki%20%C3%B0%C4%91@jssip.net:6060'); 28 | test.strictEqual(uri.toAor(), 'sip:I%C3%B1aki%20%C3%B0%C4%91@jssip.net'); 29 | 30 | uri.user = '%61lice'; 31 | test.strictEqual(uri.toAor(), 'sip:alice@jssip.net'); 32 | 33 | uri.user = null; 34 | test.strictEqual(uri.user, null); 35 | test.strictEqual(uri.toAor(), 'sip:jssip.net'); 36 | uri.user = 'alice'; 37 | 38 | test.throws( 39 | function() 40 | { 41 | uri.host = null; 42 | }, 43 | TypeError 44 | ); 45 | test.throws( 46 | function() 47 | { 48 | uri.host = { bar: 'foo' }; 49 | }, 50 | TypeError 51 | ); 52 | test.strictEqual(uri.host, 'jssip.net'); 53 | 54 | uri.host = 'VERSATICA.com'; 55 | test.strictEqual(uri.host, 'versatica.com'); 56 | uri.host = 'jssip.net'; 57 | 58 | uri.port = null; 59 | test.strictEqual(uri.port, null); 60 | 61 | uri.port = undefined; 62 | test.strictEqual(uri.port, null); 63 | 64 | uri.port = 'ABCD'; // Should become null. 65 | test.strictEqual(uri.toString(), 'sip:alice@jssip.net'); 66 | 67 | uri.port = '123ABCD'; // Should become 123. 68 | test.strictEqual(uri.toString(), 'sip:alice@jssip.net:123'); 69 | 70 | uri.port = 0; 71 | test.strictEqual(uri.port, 0); 72 | test.strictEqual(uri.toString(), 'sip:alice@jssip.net:0'); 73 | uri.port = null; 74 | 75 | test.strictEqual(uri.hasParam('foo'), false); 76 | 77 | uri.setParam('Foo', null); 78 | test.strictEqual(uri.hasParam('FOO'), true); 79 | 80 | uri.setParam('Baz', 123); 81 | test.strictEqual(uri.getParam('baz'), '123'); 82 | test.strictEqual(uri.toString(), 'sip:alice@jssip.net;foo;baz=123'); 83 | 84 | uri.setParam('zero', 0); 85 | test.strictEqual(uri.hasParam('ZERO'), true); 86 | test.strictEqual(uri.getParam('ZERO'), '0'); 87 | test.strictEqual(uri.toString(), 'sip:alice@jssip.net;foo;baz=123;zero=0'); 88 | test.strictEqual(uri.deleteParam('ZERO'), '0'); 89 | 90 | test.strictEqual(uri.deleteParam('baZ'), '123'); 91 | test.strictEqual(uri.deleteParam('NOO'), undefined); 92 | test.strictEqual(uri.toString(), 'sip:alice@jssip.net;foo'); 93 | 94 | uri.clearParams(); 95 | test.strictEqual(uri.toString(), 'sip:alice@jssip.net'); 96 | 97 | test.strictEqual(uri.hasHeader('foo'), false); 98 | 99 | uri.setHeader('Foo', 'LALALA'); 100 | test.strictEqual(uri.hasHeader('FOO'), true); 101 | test.deepEqual(uri.getHeader('FOO'), [ 'LALALA' ]); 102 | test.strictEqual(uri.toString(), 'sip:alice@jssip.net?Foo=LALALA'); 103 | 104 | uri.setHeader('bAz', [ 'ABC-1', 'ABC-2' ]); 105 | test.deepEqual(uri.getHeader('baz'), [ 'ABC-1', 'ABC-2' ]); 106 | test.strictEqual(uri.toString(), 'sip:alice@jssip.net?Foo=LALALA&Baz=ABC-1&Baz=ABC-2'); 107 | 108 | test.deepEqual(uri.deleteHeader('baZ'), [ 'ABC-1', 'ABC-2' ]); 109 | test.deepEqual(uri.deleteHeader('NOO'), undefined); 110 | 111 | uri.clearHeaders(); 112 | test.strictEqual(uri.toString(), 'sip:alice@jssip.net'); 113 | 114 | const uri2 = uri.clone(); 115 | 116 | test.strictEqual(uri2.toString(), uri.toString()); 117 | uri2.user = 'popo'; 118 | test.strictEqual(uri2.user, 'popo'); 119 | test.strictEqual(uri.user, 'alice'); 120 | 121 | test.done(); 122 | }, 123 | 124 | 'new NameAddr' : function(test) 125 | { 126 | const uri = new JsSIP.URI('sip', 'alice', 'jssip.net'); 127 | const name = new JsSIP.NameAddrHeader(uri, 'Alice æßð'); 128 | 129 | test.strictEqual(name.display_name, 'Alice æßð'); 130 | test.strictEqual(name.toString(), '"Alice æßð" '); 131 | 132 | name.display_name = null; 133 | test.strictEqual(name.toString(), ''); 134 | 135 | name.display_name = 0; 136 | test.strictEqual(name.toString(), '"0" '); 137 | 138 | name.display_name = ''; 139 | test.strictEqual(name.toString(), ''); 140 | 141 | name.setParam('Foo', null); 142 | test.strictEqual(name.hasParam('FOO'), true); 143 | 144 | name.setParam('Baz', 123); 145 | test.strictEqual(name.getParam('baz'), '123'); 146 | test.strictEqual(name.toString(), ';foo;baz=123'); 147 | 148 | test.strictEqual(name.deleteParam('bAz'), '123'); 149 | 150 | name.clearParams(); 151 | test.strictEqual(name.toString(), ''); 152 | 153 | const name2 = name.clone(); 154 | 155 | test.strictEqual(name2.toString(), name.toString()); 156 | name2.display_name = '@ł€'; 157 | test.strictEqual(name2.display_name, '@ł€'); 158 | test.strictEqual(name.user, undefined); 159 | 160 | test.done(); 161 | } 162 | 163 | }; 164 | -------------------------------------------------------------------------------- /webrtc/static/js/main.js: -------------------------------------------------------------------------------- 1 | var phone; 2 | var comfirmCall; 3 | var session; 4 | var socket = new JsSIP.WebSocketInterface('wss://' + asteriskIp + ':8089/ws'); 5 | var configuration = { 6 | sockets: [socket], 7 | 'uri': 'sip:' + asteriskUser + '@' + asteriskIp, 8 | 'password': asteriskUserPass, 9 | 'session_timers': false 10 | }; 11 | var remoteAudio = new window.Audio(); 12 | remoteAudio.autoplay = true; 13 | var callOptions = { 14 | mediaConstraints: {audio: true, video: false} 15 | }; 16 | if (configuration.uri && configuration.password) { 17 | JsSIP.debug.enable('JsSIP:*'); 18 | phone = new JsSIP.UA(configuration); 19 | phone.on('registrationFailed', function (ev) { 20 | console.log('Registering on SIP server failed with error: ' + ev.cause); 21 | configuration.uri = null; 22 | configuration.password = null; 23 | updateUI(); 24 | }); 25 | phone.on('newRTCSession', function (ev) { 26 | var newSession = ev.session; 27 | if (session) { 28 | session.terminate(); 29 | } 30 | session = newSession; 31 | var completeSession = function () { 32 | session = null; 33 | updateUI(); 34 | }; 35 | session.on('ended', completeSession); 36 | session.on('failed', completeSession); 37 | session.on('accepted', updateUI); 38 | session.on('peerconnection', addAudioStream); 39 | session.on('confirmed', updateUI); 40 | if (session._direction == 'incoming') { 41 | ringTone.play(); 42 | if (!document.hasFocus()) 43 | comfirmCall = setTimeout(function () { 44 | var receivePhone = confirm('Incoming Call\n Do you like to receive the Incoming Call?'); 45 | if (receivePhone) { 46 | session.answer(callOptions); 47 | } else { 48 | hangup(); 49 | } 50 | }, 3000); 51 | 52 | 53 | } 54 | updateUI(); 55 | }); 56 | phone.start(); 57 | } 58 | 59 | updateUI(); 60 | 61 | function addAudioStream() { 62 | session.connection.addEventListener('addstream', function (event) { 63 | ringTone.pause(); 64 | remoteAudio.src = window.URL.createObjectURL(event.stream); 65 | }); 66 | } 67 | 68 | $('#connectCall').click(function () { 69 | var dest = $('#toField').val(); 70 | phone.call(dest, callOptions); 71 | updateUI(); 72 | }); 73 | $('#answer').click(function () { 74 | session.answer(callOptions); 75 | if (comfirmCall) { 76 | clearTimeout(comfirmCall); 77 | comfirmCall = false; 78 | } 79 | }); 80 | var hangup = function () { 81 | session.terminate(); 82 | }; 83 | $('#hangUp').click(hangup); 84 | $('#reject').click(hangup); 85 | $('#mute').click(function () { 86 | if (session.isMuted().audio) { 87 | session.unmute({audio: true}); 88 | } else { 89 | session.mute({audio: true}); 90 | } 91 | updateUI(); 92 | }); 93 | $('#toField').keypress(function (e) { 94 | if (e.which === 13) {//enter 95 | $('#connectCall').click(); 96 | } 97 | }); 98 | var lock_dtmf_while_playing = false; //inorder to lock multiple input until playing ends 99 | dtmfTone.onended = function () { 100 | lock_dtmf_while_playing = false; 101 | }; 102 | $('#inCallButtons').on('click', '.dialpad-char', function (e) { 103 | if (!lock_dtmf_while_playing) { 104 | lock_dtmf_while_playing = true; 105 | dtmfTone.play(); 106 | var $target = $(e.target); 107 | var value = $target.data('value'); 108 | session.sendDTMF(value.toString()); 109 | } 110 | }); 111 | 112 | function updateUI() { 113 | if (configuration.uri && configuration.password) { 114 | $('#errorMessage').hide(); 115 | $('#wrapper').show(); 116 | if (session) { 117 | if (session.isInProgress()) { 118 | if (session._direction === 'incoming') { 119 | $('#incomingCallNumber').html(session.remote_identity.uri); 120 | $('#incomingCall').show(); 121 | $('#callControl').hide(); 122 | $('#incomingCall').show(); 123 | } else { 124 | $('#callInfoText').html('Ringing...'); 125 | $('#callInfoNumber').html(session.remote_identity.uri.user); 126 | $('#callStatus').show(); 127 | } 128 | 129 | } else if (session.isEstablished()) { 130 | $('#callStatus').show(); 131 | $('#incomingCall').hide(); 132 | $('#callInfoText').html('In Call'); 133 | $('#callInfoNumber').html(session.remote_identity.uri.user); 134 | $('#inCallButtons').show(); 135 | ringTone.pause(); 136 | } 137 | $('#callControl').hide(); 138 | } else { 139 | $('#incomingCall').hide(); 140 | $('#callControl').show(); 141 | $('#callStatus').hide(); 142 | $('#inCallButtons').hide(); 143 | ringTone.pause(); 144 | } 145 | if (session && session.isMuted().audio) { 146 | $('#muteIcon').addClass('fa-microphone-slash'); 147 | $('#muteIcon').removeClass('fa-microphone'); 148 | } else { 149 | $('#muteIcon').removeClass('fa-microphone-slash'); 150 | $('#muteIcon').addClass('fa-microphone'); 151 | } 152 | } else { 153 | $('#wrapper').hide(); 154 | $('#errorMessage').show(); 155 | } 156 | } 157 | 158 | window.onbeforeunload = function (event) { 159 | return false; 160 | }; -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/URI.js: -------------------------------------------------------------------------------- 1 | const JsSIP_C = require('./Constants'); 2 | const Utils = require('./Utils'); 3 | const Grammar = require('./Grammar'); 4 | 5 | /** 6 | * -param {String} [scheme] 7 | * -param {String} [user] 8 | * -param {String} host 9 | * -param {String} [port] 10 | * -param {Object} [parameters] 11 | * -param {Object} [headers] 12 | * 13 | */ 14 | module.exports = class URI 15 | { 16 | /** 17 | * Parse the given string and returns a JsSIP.URI instance or undefined if 18 | * it is an invalid URI. 19 | */ 20 | static parse(uri) 21 | { 22 | uri = Grammar.parse(uri, 'SIP_URI'); 23 | 24 | if (uri !== -1) 25 | { 26 | return uri; 27 | } 28 | else 29 | { 30 | return undefined; 31 | } 32 | } 33 | 34 | constructor(scheme, user, host, port, parameters = {}, headers = {}) 35 | { 36 | // Checks. 37 | if (!host) 38 | { 39 | throw new TypeError('missing or invalid "host" parameter'); 40 | } 41 | 42 | // Initialize parameters. 43 | this._parameters = {}; 44 | this._headers = {}; 45 | 46 | this._scheme = scheme || JsSIP_C.SIP; 47 | this._user = user; 48 | this._host = host; 49 | this._port = port; 50 | 51 | for (const param in parameters) 52 | { 53 | if (Object.prototype.hasOwnProperty.call(parameters, param)) 54 | { 55 | this.setParam(param, parameters[param]); 56 | } 57 | } 58 | 59 | for (const header in headers) 60 | { 61 | if (Object.prototype.hasOwnProperty.call(headers, header)) 62 | { 63 | this.setHeader(header, headers[header]); 64 | } 65 | } 66 | } 67 | 68 | get scheme() 69 | { 70 | return this._scheme; 71 | } 72 | 73 | set scheme(value) 74 | { 75 | this._scheme = value.toLowerCase(); 76 | } 77 | 78 | get user() 79 | { 80 | return this._user; 81 | } 82 | 83 | set user(value) 84 | { 85 | this._user = value; 86 | } 87 | 88 | get host() 89 | { 90 | return this._host; 91 | } 92 | 93 | set host(value) 94 | { 95 | this._host = value.toLowerCase(); 96 | } 97 | 98 | get port() 99 | { 100 | return this._port; 101 | } 102 | 103 | set port(value) 104 | { 105 | this._port = value === 0 ? value : (parseInt(value, 10) || null); 106 | } 107 | 108 | setParam(key, value) 109 | { 110 | if (key) 111 | { 112 | this._parameters[key.toLowerCase()] = (typeof value === 'undefined' || value === null) ? null : value.toString(); 113 | } 114 | } 115 | 116 | getParam(key) 117 | { 118 | if (key) 119 | { 120 | return this._parameters[key.toLowerCase()]; 121 | } 122 | } 123 | 124 | hasParam(key) 125 | { 126 | if (key) 127 | { 128 | return (this._parameters.hasOwnProperty(key.toLowerCase()) && true) || false; 129 | } 130 | } 131 | 132 | deleteParam(parameter) 133 | { 134 | parameter = parameter.toLowerCase(); 135 | if (this._parameters.hasOwnProperty(parameter)) 136 | { 137 | const value = this._parameters[parameter]; 138 | 139 | delete this._parameters[parameter]; 140 | 141 | return value; 142 | } 143 | } 144 | 145 | clearParams() 146 | { 147 | this._parameters = {}; 148 | } 149 | 150 | setHeader(name, value) 151 | { 152 | this._headers[Utils.headerize(name)] = (Array.isArray(value)) ? value : [ value ]; 153 | } 154 | 155 | getHeader(name) 156 | { 157 | if (name) 158 | { 159 | return this._headers[Utils.headerize(name)]; 160 | } 161 | } 162 | 163 | hasHeader(name) 164 | { 165 | if (name) 166 | { 167 | return (this._headers.hasOwnProperty(Utils.headerize(name)) && true) || false; 168 | } 169 | } 170 | 171 | deleteHeader(header) 172 | { 173 | header = Utils.headerize(header); 174 | if (this._headers.hasOwnProperty(header)) 175 | { 176 | const value = this._headers[header]; 177 | 178 | delete this._headers[header]; 179 | 180 | return value; 181 | } 182 | } 183 | 184 | clearHeaders() 185 | { 186 | this._headers = {}; 187 | } 188 | 189 | clone() 190 | { 191 | return new URI( 192 | this._scheme, 193 | this._user, 194 | this._host, 195 | this._port, 196 | JSON.parse(JSON.stringify(this._parameters)), 197 | JSON.parse(JSON.stringify(this._headers))); 198 | } 199 | 200 | toString() 201 | { 202 | const headers = []; 203 | 204 | let uri = `${this._scheme}:`; 205 | 206 | if (this._user) 207 | { 208 | uri += `${Utils.escapeUser(this._user)}@`; 209 | } 210 | uri += this._host; 211 | if (this._port || this._port === 0) 212 | { 213 | uri += `:${this._port}`; 214 | } 215 | 216 | for (const parameter in this._parameters) 217 | { 218 | if (Object.prototype.hasOwnProperty.call(this._parameters, parameter)) 219 | { 220 | uri += `;${parameter}`; 221 | 222 | if (this._parameters[parameter] !== null) 223 | { 224 | uri += `=${this._parameters[parameter]}`; 225 | } 226 | } 227 | } 228 | 229 | for (const header in this._headers) 230 | { 231 | if (Object.prototype.hasOwnProperty.call(this._headers, header)) 232 | { 233 | for (const item of this._headers[header]) 234 | { 235 | headers.push(`${header}=${item}`); 236 | } 237 | } 238 | } 239 | 240 | if (headers.length > 0) 241 | { 242 | uri += `?${headers.join('&')}`; 243 | } 244 | 245 | return uri; 246 | } 247 | 248 | toAor(show_port) 249 | { 250 | let aor = `${this._scheme}:`; 251 | 252 | if (this._user) 253 | { 254 | aor += `${Utils.escapeUser(this._user)}@`; 255 | } 256 | aor += this._host; 257 | if (show_port && (this._port || this._port === 0)) 258 | { 259 | aor += `:${this._port}`; 260 | } 261 | 262 | return aor; 263 | } 264 | }; 265 | -------------------------------------------------------------------------------- /webrtc/static/css/phone.css: -------------------------------------------------------------------------------- 1 | 2 | @charset "UTF-8"; 3 | 4 | /* CSS Document */ 5 | html, body { 6 | margin: 0; 7 | padding: 0; 8 | font-family: 'Open Sans', sans-serif; 9 | color: #494949; 10 | background: #f9f9f9; 11 | } 12 | 13 | #wrapper { 14 | width: 300px; 15 | margin: 0 auto; 16 | } 17 | 18 | .callInfo { 19 | width: 190px; 20 | height: 70px; 21 | float: left; 22 | } 23 | 24 | #connectedCall .callInfo { 25 | width: 240px; 26 | } 27 | 28 | #dialPad div { 29 | -moz-user-select: -moz-none; 30 | -khtml-user-select: none; 31 | -webkit-user-select: none; 32 | 33 | /* 34 | Introduced in IE 10. 35 | See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/ 36 | */ 37 | -ms-user-select: none; 38 | user-select: none; 39 | } 40 | 41 | .callInfo h3 { 42 | color: #389400; 43 | margin: 10px 0 10px 0; 44 | } 45 | 46 | .callInfo p { 47 | margin: 0px 25px 0px 0px; 48 | float: left; 49 | } 50 | 51 | #answer, #hangUp, #reject, #connectCall, #mute { 52 | color: #FFF; 53 | background-color: #389400; 54 | width: 50px; 55 | height: 50px; 56 | float: right; 57 | text-align: center; 58 | font-size: 30px; 59 | margin: 10px 0px 10px 0px; 60 | border-radius: 25px 25px 25px 25px; 61 | -moz-border-radius: 25px 25px 25px 25px; 62 | -webkit-border-radius: 25px 25px 25px 25px; 63 | cursor: pointer; 64 | cursor: hand; 65 | } 66 | 67 | #mute:active, #connectCall:active, #reject:active, #hangUp:active, #answer:active { 68 | background-color: #B6B6B6; 69 | -webkit-box-shadow: 0px 1px 7px 2px rgba(0, 0, 0, 0.45); 70 | -moz-box-shadow: 0px 1px 7px 2px rgba(0, 0, 0, 0.45); 71 | box-shadow: 0px 1px 7px 2px rgba(0, 0, 0, 0.45); 72 | border: none; 73 | } 74 | 75 | #hangUp, #reject { 76 | -ms-transform: rotate(135deg); 77 | /* IE 9 */ 78 | -webkit-transform: rotate(135deg); 79 | /* Chrome, Safari, Opera */ 80 | transform: rotate(135deg); 81 | } 82 | 83 | #hangUp { 84 | background-color: #A90002; 85 | } 86 | 87 | #reject { 88 | background-color: #FFF; 89 | color: #A90002; 90 | margin-right: 10px; 91 | } 92 | 93 | #connectCall, #mute { 94 | color: #FFF; 95 | background-color: #389400; 96 | width: 260px; 97 | height: 50px; 98 | font-size: 30px; 99 | text-align: center; 100 | margin: 20px 20px 0px 20px; 101 | border-radius: 5px 5px 5px 5px; 102 | -moz-border-radius: 5px 5px 5px 5px; 103 | -webkit-border-radius: 5px 5px 5px 5px; 104 | cursor: pointer; 105 | cursor: hand; 106 | } 107 | 108 | #mute { 109 | color: #545454; 110 | margin: 0 auto; 111 | width: 50px; 112 | height: 50px; 113 | margin: 15px 125px 10px 20px; 114 | border-radius: 25px 25px 25px 25px; 115 | -moz-border-radius: 25px 25px 25px 25px; 116 | -webkit-border-radius: 25px 25px 25px 25px; 117 | background-color: transparent; 118 | } 119 | 120 | .muteActive { 121 | color: #FFF; 122 | background-color: #389400; 123 | } 124 | 125 | #to { 126 | border-bottom: 2px solid #BBBBBB; 127 | } 128 | 129 | #toField { 130 | margin-top: 20px; 131 | padding-left: 10px; 132 | font-size: 1em; 133 | font-family: 'Open Sans', sans-serif; 134 | width: 300px; 135 | height: 40px; 136 | border-radius: 2px 2px 2px 2px; 137 | -moz-border-radius: 2px 2px 2px 2px; 138 | -webkit-border-radius: 2px 2px 2px 2px; 139 | border: 0px solid #B8B8B8; 140 | color: #494949; 141 | background: transparent; 142 | } 143 | 144 | #dialPad { 145 | width: 240px; 146 | height: 308px; 147 | margin: 0 auto; 148 | } 149 | 150 | #dialPad div { 151 | float: left; 152 | width: 50px; 153 | height: 50px; 154 | text-align: center; 155 | font-size: 26px; 156 | margin: 25px 15px 0px 15px; 157 | box-sizing: border-box; 158 | -moz-box-sizing: border-box; 159 | -webkit-box-sizing: border-box; 160 | border-radius: 25px 25px 25px 25px; 161 | -moz-border-radius: 25px 25px 25px 25px; 162 | -webkit-border-radius: 25px 25px 25px 25px; 163 | border: 1px solid #E8E8E8; 164 | padding-top: 5px; 165 | line-height: -moz-block-height; 166 | } 167 | 168 | #dialPad div:hover { 169 | background-color: #389400; 170 | color: #FFF; 171 | cursor: pointer; 172 | cursor: hand; 173 | } 174 | 175 | #dialPad div:active { 176 | background-color: #B2B2B2; 177 | -webkit-box-shadow: 0px 1px 7px 2px rgba(0, 0, 0, 0.45); 178 | -moz-box-shadow: 0px 1px 7px 2px rgba(0, 0, 0, 0.45); 179 | box-shadow: 0px 1px 7px 2px rgba(0, 0, 0, 0.45); 180 | border: none; 181 | } 182 | 183 | .fa { 184 | margin-top: 11px; 185 | } 186 | 187 | #answer .fa { 188 | -webkit-animation: callp 1s; 189 | animation: callp 1s; 190 | -webkit-animation-iteration-count: infinite; 191 | -webkit-animation-direction: alternate; 192 | -webkit-animation-play-state: running; 193 | animation-iteration-count: infinite; 194 | animation-direction: alternate; 195 | animation-play-state: running; 196 | } 197 | 198 | @-webkit-keyframes callp { 199 | from { 200 | color: #FFF; 201 | } 202 | to { 203 | color: #389400; 204 | } 205 | } 206 | 207 | @keyframes callp { 208 | from { 209 | color: #FFF; 210 | } 211 | to { 212 | color: #389400; 213 | } 214 | } 215 | 216 | #answer { 217 | -webkit-animation: call 1s; 218 | animation: call 1s; 219 | -webkit-animation-iteration-count: infinite; 220 | -webkit-animation-direction: alternate; 221 | -webkit-animation-play-state: running; 222 | animation-iteration-count: infinite; 223 | animation-direction: alternate; 224 | animation-play-state: running; 225 | } 226 | 227 | @-webkit-keyframes call { 228 | from { 229 | background: #389400; 230 | } 231 | to { 232 | background: #FFF; 233 | } 234 | } 235 | 236 | @keyframes call { 237 | from { 238 | background: #389400; 239 | } 240 | to { 241 | background: #FFF; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /asterisk-contrib-scipt/ast_tls_cert: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | DEFAULT_ORG="Asterisk" 3 | DEFAULT_CA_CN="Asterisk Private CA" 4 | DEFAULT_CLIENT_CN="asterisk" 5 | DEFAULT_SERVER_CN=`hostname -f` 6 | 7 | # arguments 8 | # $1 "ca" if we are to generate a CA cert 9 | # $2 alternate config file name (for ca) 10 | # $3 alternate common name 11 | # $4 alternate org name 12 | create_config () { 13 | if [ "$1" = "ca" ] 14 | then 15 | castring=" 16 | [ext] 17 | basicConstraints=CA:TRUE" 18 | fi 19 | 20 | cat > ${2:-"${CONFIG_FILE}"} << EOF 21 | [req] 22 | distinguished_name = req_distinguished_name 23 | prompt = no 24 | 25 | [req_distinguished_name] 26 | CN=${3:-"${COMMON_NAME}"} 27 | O=${4:-"${ORG_NAME}"} 28 | ${castring} 29 | EOF 30 | } 31 | 32 | create_ca () { 33 | echo "Creating CA key ${CAKEY}" 34 | openssl genrsa -passout pass:yipl -des3 -out ${CAKEY} 4096 > /dev/null 35 | if [ $? -ne 0 ]; 36 | then 37 | echo "Failed" 38 | exit 1 39 | fi 40 | echo "Creating CA certificate ${CACERT}" 41 | openssl req -passin pass:yipl -new -config ${CACFG} -x509 -days 365 -key ${CAKEY} -out ${CACERT} > /dev/null 42 | if [ $? -ne 0 ]; 43 | then 44 | echo "Failed" 45 | exit 1 46 | fi 47 | } 48 | 49 | create_cert () { 50 | local base=${OUTPUT_DIR}/${OUTPUT_BASE} 51 | echo "Creating certificate ${base}.key" 52 | openssl genrsa -passout pass:yipl -out ${base}.key 1024 > /dev/null 53 | if [ $? -ne 0 ]; 54 | then 55 | echo "Failed" 56 | exit 1 57 | fi 58 | echo "Creating signing request ${base}.csr" 59 | openssl req -passin pass:yipl -batch -new -config ${CONFIG_FILE} -key ${base}.key -out ${base}.csr > /dev/null 60 | if [ $? -ne 0 ]; 61 | then 62 | echo "Failed" 63 | exit 1 64 | fi 65 | echo "Creating certificate ${base}.crt" 66 | openssl x509 -req -days 365 -in ${base}.csr -CA ${CACERT} -CAkey ${CAKEY} -set_serial 01 -passin pass:yipl -out ${base}.crt > /dev/null 67 | if [ $? -ne 0 ]; 68 | then 69 | echo "Failed" 70 | exit 1 71 | fi 72 | echo "Combining key and crt into ${base}.pem" 73 | cat ${base}.key > ${base}.pem 74 | cat ${base}.crt >> ${base}.pem 75 | } 76 | 77 | usage () { 78 | cat << EOF 79 | This script is useful for quickly generating self-signed CA, server, and client 80 | certificates for use with Asterisk. It is still recommended to obtain 81 | certificates from a recognized Certificate Authority and to develop an 82 | understanding how SSL certificates work. Real security is hard work. 83 | 84 | OPTIONS: 85 | -h Show this message 86 | -m Type of cert "client" or "server". Defaults to server. 87 | -f Config filename (openssl config file format) 88 | -c CA cert filename (creates new CA cert/key as ca.crt/ca.key if not passed) 89 | -k CA key filename 90 | -C Common name (cert field) 91 | This should be the fully qualified domain name or IP address for 92 | the client or server. Make sure your certs have unique common 93 | names. 94 | -O Org name (cert field) 95 | An informational string (company name) 96 | -o Output filename base (defaults to asterisk) 97 | -d Output directory (defaults to the current directory) 98 | 99 | Example: 100 | 101 | To create a CA and a server (pbx.mycompany.com) cert with output in /tmp: 102 | ast_tls_cert -C pbx.mycompany.com -O "My Company" -d /tmp 103 | 104 | This will create a CA cert and key as well as asterisk.pem and the the two 105 | files that it is made from: asterisk.crt and asterisk.key. Copy asterisk.pem 106 | and ca.crt somewhere (like /etc/asterisk) and set tlscertfile=/etc/asterisk.pem 107 | and tlscafile=/etc/ca.crt. Since this is a self-signed key, many devices will 108 | require you to import the ca.crt file as a trusted cert. 109 | 110 | To create a client cert using the CA cert created by the example above: 111 | ast_tls_cert -m client -c /tmp/ca.crt -k /tmp/ca.key -C phone1.mycompany.com \\ 112 | -O "My Company" -d /tmp -o joe_user 113 | 114 | This will create client.crt/key/pem in /tmp. Use this if your device supports 115 | a client certificate. Make sure that you have the ca.crt file set up as 116 | a tlscafile in the necessary Asterisk configs. Make backups of all .key files 117 | in case you need them later. 118 | EOF 119 | } 120 | 121 | if ! type openssl >/dev/null 2>&1 122 | then 123 | echo "This script requires openssl to be in the path" 124 | exit 1 125 | fi 126 | 127 | OUTPUT_BASE=asterisk # Our default cert basename 128 | CERT_MODE=server 129 | ORG_NAME=${DEFAULT_ORG} 130 | 131 | while getopts "hf:c:k:o:d:m:C:O:" OPTION 132 | do 133 | case ${OPTION} in 134 | h) 135 | usage 136 | exit 1 137 | ;; 138 | f) 139 | CONFIG_FILE=${OPTARG} 140 | ;; 141 | c) 142 | CACERT=${OPTARG} 143 | ;; 144 | k) 145 | CAKEY=${OPTARG} 146 | ;; 147 | o) 148 | OUTPUT_BASE=${OPTARG} 149 | ;; 150 | d) 151 | OUTPUT_DIR=${OPTARG} 152 | ;; 153 | m) 154 | CERT_MODE=${OPTARG} 155 | ;; 156 | C) 157 | COMMON_NAME=${OPTARG} 158 | ;; 159 | O) 160 | ORG_NAME=${OPTARG} 161 | ;; 162 | ?) 163 | usage 164 | exit 165 | ;; 166 | esac 167 | done 168 | 169 | if [ -z "${OUTPUT_DIR}" ] 170 | then 171 | OUTPUT_DIR=. 172 | else 173 | mkdir -p "${OUTPUT_DIR}" 174 | fi 175 | 176 | umask 177 177 | 178 | case "${CERT_MODE}" in 179 | server) 180 | COMMON_NAME=${COMMON_NAME:-"${DEFAULT_SERVER_CN}"} 181 | ;; 182 | client) 183 | COMMON_NAME=${COMMON_NAME:-"${DEFAULT_CLIENT_CN}"} 184 | ;; 185 | *) 186 | echo 187 | echo "Unknown mode. Exiting." 188 | exit 1 189 | ;; 190 | esac 191 | 192 | if [ -z "${CONFIG_FILE}" ] 193 | then 194 | CONFIG_FILE="${OUTPUT_DIR}/tmp.cfg" 195 | echo 196 | echo "No config file specified, creating '${CONFIG_FILE}'" 197 | echo "You can use this config file to create additional certs without" 198 | echo "re-entering the information for the fields in the certificate" 199 | create_config 200 | fi 201 | 202 | if [ -z ${CACERT} ] 203 | then 204 | CAKEY=${OUTPUT_DIR}/ca.key 205 | CACERT=${OUTPUT_DIR}/ca.crt 206 | CACFG=${OUTPUT_DIR}/ca.cfg 207 | if [ ! -r "$CAKEY" ] && [ ! -r "$CACFG" ]; then 208 | create_config ca "${CACFG}" "${DEFAULT_CA_CN}" "${DEFAULT_CA_ORG}" 209 | fi 210 | if [ ! -r "$CACERT" ]; then 211 | create_ca 212 | fi 213 | else 214 | if [ -z ${CAKEY} ] 215 | then 216 | echo "-k must be specified if -c is" 217 | exit 1 218 | fi 219 | fi 220 | 221 | create_cert 222 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/sanityCheck.js: -------------------------------------------------------------------------------- 1 | const JsSIP_C = require('./Constants'); 2 | const SIPMessage = require('./SIPMessage'); 3 | const Utils = require('./Utils'); 4 | const debug = require('debug')('JsSIP:sanityCheck'); 5 | 6 | // Checks for requests and responses. 7 | const all = [ minimumHeaders ]; 8 | 9 | // Checks for requests. 10 | const requests = [ 11 | rfc3261_8_2_2_1, 12 | rfc3261_16_3_4, 13 | rfc3261_18_3_request, 14 | rfc3261_8_2_2_2 15 | ]; 16 | 17 | // Checks for responses. 18 | const responses = [ 19 | rfc3261_8_1_3_3, 20 | rfc3261_18_3_response 21 | ]; 22 | 23 | // local variables. 24 | let message; 25 | let ua; 26 | let transport; 27 | 28 | module.exports = (m, u, t) => 29 | { 30 | message = m; 31 | ua = u; 32 | transport = t; 33 | 34 | for (const check of all) 35 | { 36 | if (check() === false) 37 | { 38 | return false; 39 | } 40 | } 41 | 42 | if (message instanceof SIPMessage.IncomingRequest) 43 | { 44 | for (const check of requests) 45 | { 46 | if (check() === false) 47 | { 48 | return false; 49 | } 50 | } 51 | } 52 | 53 | else if (message instanceof SIPMessage.IncomingResponse) 54 | { 55 | for (const check of responses) 56 | { 57 | if (check() === false) 58 | { 59 | return false; 60 | } 61 | } 62 | } 63 | 64 | // Everything is OK. 65 | return true; 66 | }; 67 | 68 | 69 | /* 70 | * Sanity Check for incoming Messages 71 | * 72 | * Requests: 73 | * - _rfc3261_8_2_2_1_ Receive a Request with a non supported URI scheme 74 | * - _rfc3261_16_3_4_ Receive a Request already sent by us 75 | * Does not look at via sent-by but at jssip_id, which is inserted as 76 | * a prefix in all initial requests generated by the ua 77 | * - _rfc3261_18_3_request_ Body Content-Length 78 | * - _rfc3261_8_2_2_2_ Merged Requests 79 | * 80 | * Responses: 81 | * - _rfc3261_8_1_3_3_ Multiple Via headers 82 | * - _rfc3261_18_3_response_ Body Content-Length 83 | * 84 | * All: 85 | * - Minimum headers in a SIP message 86 | */ 87 | 88 | // Sanity Check functions for requests. 89 | function rfc3261_8_2_2_1() 90 | { 91 | if (message.s('to').uri.scheme !== 'sip') 92 | { 93 | reply(416); 94 | 95 | return false; 96 | } 97 | } 98 | 99 | function rfc3261_16_3_4() 100 | { 101 | if (!message.to_tag) 102 | { 103 | if (message.call_id.substr(0, 5) === ua.configuration.jssip_id) 104 | { 105 | reply(482); 106 | 107 | return false; 108 | } 109 | } 110 | } 111 | 112 | function rfc3261_18_3_request() 113 | { 114 | const len = Utils.str_utf8_length(message.body); 115 | const contentLength = message.getHeader('content-length'); 116 | 117 | if (len < contentLength) 118 | { 119 | reply(400); 120 | 121 | return false; 122 | } 123 | } 124 | 125 | function rfc3261_8_2_2_2() 126 | { 127 | const fromTag = message.from_tag; 128 | const call_id = message.call_id; 129 | const cseq = message.cseq; 130 | let tr; 131 | 132 | // Accept any in-dialog request. 133 | if (message.to_tag) 134 | { 135 | return; 136 | } 137 | 138 | // INVITE request. 139 | if (message.method === JsSIP_C.INVITE) 140 | { 141 | // If the branch matches the key of any IST then assume it is a retransmission 142 | // and ignore the INVITE. 143 | // TODO: we should reply the last response. 144 | if (ua._transactions.ist[message.via_branch]) 145 | { 146 | return false; 147 | } 148 | // Otherwise check whether it is a merged request. 149 | else 150 | { 151 | for (const transaction in ua._transactions.ist) 152 | { 153 | if (Object.prototype.hasOwnProperty.call(ua._transactions.ist, transaction)) 154 | { 155 | tr = ua._transactions.ist[transaction]; 156 | if (tr.request.from_tag === fromTag && 157 | tr.request.call_id === call_id && 158 | tr.request.cseq === cseq) 159 | { 160 | reply(482); 161 | 162 | return false; 163 | } 164 | } 165 | } 166 | } 167 | } 168 | 169 | // Non INVITE request. 170 | 171 | // If the branch matches the key of any NIST then assume it is a retransmission 172 | // and ignore the request. 173 | // TODO: we should reply the last response. 174 | else if (ua._transactions.nist[message.via_branch]) 175 | { 176 | return false; 177 | } 178 | 179 | // Otherwise check whether it is a merged request. 180 | else 181 | { 182 | for (const transaction in ua._transactions.nist) 183 | { 184 | if (Object.prototype.hasOwnProperty.call(ua._transactions.nist, transaction)) 185 | { 186 | tr = ua._transactions.nist[transaction]; 187 | if (tr.request.from_tag === fromTag && 188 | tr.request.call_id === call_id && 189 | tr.request.cseq === cseq) 190 | { 191 | reply(482); 192 | 193 | return false; 194 | } 195 | } 196 | } 197 | } 198 | } 199 | 200 | // Sanity Check functions for responses. 201 | function rfc3261_8_1_3_3() 202 | { 203 | if (message.getHeaders('via').length > 1) 204 | { 205 | debug('more than one Via header field present in the response, dropping the response'); 206 | 207 | return false; 208 | } 209 | } 210 | 211 | function rfc3261_18_3_response() 212 | { 213 | const len = Utils.str_utf8_length(message.body), contentLength = message.getHeader('content-length'); 214 | 215 | if (len < contentLength) 216 | { 217 | debug('message body length is lower than the value in Content-Length header field, dropping the response'); 218 | 219 | return false; 220 | } 221 | } 222 | 223 | // Sanity Check functions for requests and responses. 224 | function minimumHeaders() 225 | { 226 | const mandatoryHeaders = [ 'from', 'to', 'call_id', 'cseq', 'via' ]; 227 | 228 | for (const header of mandatoryHeaders) 229 | { 230 | if (!message.hasHeader(header)) 231 | { 232 | debug(`missing mandatory header field : ${header}, dropping the response`); 233 | 234 | return false; 235 | } 236 | } 237 | } 238 | 239 | // Reply. 240 | function reply(status_code) 241 | { 242 | const vias = message.getHeaders('via'); 243 | 244 | let to; 245 | let response = `SIP/2.0 ${status_code} ${JsSIP_C.REASON_PHRASE[status_code]}\r\n`; 246 | 247 | for (const via of vias) 248 | { 249 | response += `Via: ${via}\r\n`; 250 | } 251 | 252 | to = message.getHeader('To'); 253 | 254 | if (!message.to_tag) 255 | { 256 | to += `;tag=${Utils.newTag()}`; 257 | } 258 | 259 | response += `To: ${to}\r\n`; 260 | response += `From: ${message.getHeader('From')}\r\n`; 261 | response += `Call-ID: ${message.call_id}\r\n`; 262 | response += `CSeq: ${message.cseq} ${message.method}\r\n`; 263 | response += '\r\n'; 264 | 265 | transport.send(response); 266 | } 267 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/Message.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events').EventEmitter; 2 | const JsSIP_C = require('./Constants'); 3 | const SIPMessage = require('./SIPMessage'); 4 | const Utils = require('./Utils'); 5 | const RequestSender = require('./RequestSender'); 6 | const Exceptions = require('./Exceptions'); 7 | const debug = require('debug')('JsSIP:Message'); 8 | 9 | module.exports = class Message extends EventEmitter 10 | { 11 | constructor(ua) 12 | { 13 | super(); 14 | 15 | this._ua = ua; 16 | this._request = null; 17 | this._closed = false; 18 | 19 | this._direction = null; 20 | this._local_identity = null; 21 | this._remote_identity = null; 22 | 23 | // Whether an incoming message has been replied. 24 | this._is_replied = false; 25 | 26 | // Custom message empty object for high level use. 27 | this._data = {}; 28 | } 29 | 30 | get direction() 31 | { 32 | return this._direction; 33 | } 34 | 35 | get local_identity() 36 | { 37 | return this._local_identity; 38 | } 39 | 40 | get remote_identity() 41 | { 42 | return this._remote_identity; 43 | } 44 | 45 | send(target, body, options = {}) 46 | { 47 | const originalTarget = target; 48 | 49 | if (target === undefined || body === undefined) 50 | { 51 | throw new TypeError('Not enough arguments'); 52 | } 53 | 54 | // Check target validity. 55 | target = this._ua.normalizeTarget(target); 56 | if (!target) 57 | { 58 | throw new TypeError(`Invalid target: ${originalTarget}`); 59 | } 60 | 61 | // Get call options. 62 | const extraHeaders = Utils.cloneArray(options.extraHeaders); 63 | const eventHandlers = options.eventHandlers || {}; 64 | const contentType = options.contentType || 'text/plain'; 65 | 66 | // Set event handlers. 67 | for (const event in eventHandlers) 68 | { 69 | if (Object.prototype.hasOwnProperty.call(eventHandlers, event)) 70 | { 71 | this.on(event, eventHandlers[event]); 72 | } 73 | } 74 | 75 | extraHeaders.push(`Content-Type: ${contentType}`); 76 | 77 | this._request = new SIPMessage.OutgoingRequest( 78 | JsSIP_C.MESSAGE, target, this._ua, null, extraHeaders); 79 | 80 | if (body) 81 | { 82 | this._request.body = body; 83 | } 84 | 85 | const request_sender = new RequestSender(this._ua, this._request, { 86 | onRequestTimeout : () => 87 | { 88 | this._onRequestTimeout(); 89 | }, 90 | onTransportError : () => 91 | { 92 | this._onTransportError(); 93 | }, 94 | onReceiveResponse : (response) => 95 | { 96 | this._receiveResponse(response); 97 | } 98 | }); 99 | 100 | this._newMessage('local', this._request); 101 | 102 | request_sender.send(); 103 | } 104 | 105 | init_incoming(request) 106 | { 107 | this._request = request; 108 | 109 | this._newMessage('remote', request); 110 | 111 | // Reply with a 200 OK if the user didn't reply. 112 | if (!this._is_replied) 113 | { 114 | this._is_replied = true; 115 | request.reply(200); 116 | } 117 | 118 | this._close(); 119 | } 120 | 121 | /** 122 | * Accept the incoming Message 123 | * Only valid for incoming Messages 124 | */ 125 | accept(options = {}) 126 | { 127 | const extraHeaders = Utils.cloneArray(options.extraHeaders); 128 | const body = options.body; 129 | 130 | if (this._direction !== 'incoming') 131 | { 132 | throw new Exceptions.NotSupportedError('"accept" not supported for outgoing Message'); 133 | } 134 | 135 | if (this._is_replied) 136 | { 137 | throw new Error('incoming Message already replied'); 138 | } 139 | 140 | this._is_replied = true; 141 | this._request.reply(200, null, extraHeaders, body); 142 | } 143 | 144 | /** 145 | * Reject the incoming Message 146 | * Only valid for incoming Messages 147 | */ 148 | reject(options = {}) 149 | { 150 | const status_code = options.status_code || 480; 151 | const reason_phrase = options.reason_phrase; 152 | const extraHeaders = Utils.cloneArray(options.extraHeaders); 153 | const body = options.body; 154 | 155 | if (this._direction !== 'incoming') 156 | { 157 | throw new Exceptions.NotSupportedError('"reject" not supported for outgoing Message'); 158 | } 159 | 160 | if (this._is_replied) 161 | { 162 | throw new Error('incoming Message already replied'); 163 | } 164 | 165 | if (status_code < 300 || status_code >= 700) 166 | { 167 | throw new TypeError(`Invalid status_code: ${status_code}`); 168 | } 169 | 170 | this._is_replied = true; 171 | this._request.reply(status_code, reason_phrase, extraHeaders, body); 172 | } 173 | 174 | _receiveResponse(response) 175 | { 176 | if (this._closed) 177 | { 178 | return; 179 | } 180 | switch (true) 181 | { 182 | case /^1[0-9]{2}$/.test(response.status_code): 183 | // Ignore provisional responses. 184 | break; 185 | 186 | case /^2[0-9]{2}$/.test(response.status_code): 187 | this._succeeded('remote', response); 188 | break; 189 | 190 | default: 191 | { 192 | const cause = Utils.sipErrorCause(response.status_code); 193 | 194 | this._failed('remote', response, cause); 195 | break; 196 | } 197 | } 198 | } 199 | 200 | _onRequestTimeout() 201 | { 202 | if (this._closed) 203 | { 204 | return; 205 | } 206 | this._failed('system', null, JsSIP_C.causes.REQUEST_TIMEOUT); 207 | } 208 | 209 | _onTransportError() 210 | { 211 | if (this._closed) 212 | { 213 | return; 214 | } 215 | this._failed('system', null, JsSIP_C.causes.CONNECTION_ERROR); 216 | } 217 | 218 | _close() 219 | { 220 | this._closed = true; 221 | this._ua.destroyMessage(this); 222 | } 223 | 224 | /** 225 | * Internal Callbacks 226 | */ 227 | 228 | _newMessage(originator, request) 229 | { 230 | if (originator === 'remote') 231 | { 232 | this._direction = 'incoming'; 233 | this._local_identity = request.to; 234 | this._remote_identity = request.from; 235 | } 236 | else if (originator === 'local') 237 | { 238 | this._direction = 'outgoing'; 239 | this._local_identity = request.from; 240 | this._remote_identity = request.to; 241 | } 242 | 243 | this._ua.newMessage(this, { 244 | originator, 245 | message : this, 246 | request 247 | }); 248 | } 249 | 250 | _failed(originator, response, cause) 251 | { 252 | debug('MESSAGE failed'); 253 | 254 | this._close(); 255 | 256 | debug('emit "failed"'); 257 | 258 | this.emit('failed', { 259 | originator, 260 | response : response || null, 261 | cause 262 | }); 263 | } 264 | 265 | _succeeded(originator, response) 266 | { 267 | debug('MESSAGE succeeded'); 268 | 269 | this._close(); 270 | 271 | debug('emit "succeeded"'); 272 | 273 | this.emit('succeeded', { 274 | originator, 275 | response 276 | }); 277 | } 278 | }; 279 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/DigestAuthentication.js: -------------------------------------------------------------------------------- 1 | const Utils = require('./Utils'); 2 | const debug = require('debug')('JsSIP:DigestAuthentication'); 3 | const debugerror = require('debug')('JsSIP:ERROR:DigestAuthentication'); 4 | 5 | debugerror.log = console.warn.bind(console); 6 | 7 | module.exports = class DigestAuthentication 8 | { 9 | constructor(credentials) 10 | { 11 | this._credentials = credentials; 12 | this._cnonce = null; 13 | this._nc = 0; 14 | this._ncHex = '00000000'; 15 | this._algorithm = null; 16 | this._realm = null; 17 | this._nonce = null; 18 | this._opaque = null; 19 | this._stale = null; 20 | this._qop = null; 21 | this._method = null; 22 | this._uri = null; 23 | this._ha1 = null; 24 | this._response = null; 25 | } 26 | 27 | get(parameter) 28 | { 29 | switch (parameter) 30 | { 31 | case 'realm': 32 | return this._realm; 33 | 34 | case 'ha1': 35 | return this._ha1; 36 | 37 | default: 38 | debugerror('get() | cannot get "%s" parameter', parameter); 39 | 40 | return undefined; 41 | } 42 | } 43 | 44 | /** 45 | * Performs Digest authentication given a SIP request and the challenge 46 | * received in a response to that request. 47 | * Returns true if auth was successfully generated, false otherwise. 48 | */ 49 | authenticate({ method, ruri }, challenge) 50 | { 51 | this._algorithm = challenge.algorithm; 52 | this._realm = challenge.realm; 53 | this._nonce = challenge.nonce; 54 | this._opaque = challenge.opaque; 55 | this._stale = challenge.stale; 56 | 57 | if (this._algorithm) 58 | { 59 | if (this._algorithm !== 'MD5') 60 | { 61 | debugerror('authenticate() | challenge with Digest algorithm different than "MD5", authentication aborted'); 62 | 63 | return false; 64 | } 65 | } 66 | else 67 | { 68 | this._algorithm = 'MD5'; 69 | } 70 | 71 | if (!this._nonce) 72 | { 73 | debugerror('authenticate() | challenge without Digest nonce, authentication aborted'); 74 | 75 | return false; 76 | } 77 | 78 | if (!this._realm) 79 | { 80 | debugerror('authenticate() | challenge without Digest realm, authentication aborted'); 81 | 82 | return false; 83 | } 84 | 85 | // If no plain SIP password is provided. 86 | if (!this._credentials.password) 87 | { 88 | // If ha1 is not provided we cannot authenticate. 89 | if (!this._credentials.ha1) 90 | { 91 | debugerror('authenticate() | no plain SIP password nor ha1 provided, authentication aborted'); 92 | 93 | return false; 94 | } 95 | 96 | // If the realm does not match the stored realm we cannot authenticate. 97 | if (this._credentials.realm !== this._realm) 98 | { 99 | debugerror('authenticate() | no plain SIP password, and stored `realm` does not match the given `realm`, cannot authenticate [stored:"%s", given:"%s"]', this._credentials.realm, this._realm); 100 | 101 | return false; 102 | } 103 | } 104 | 105 | // 'qop' can contain a list of values (Array). Let's choose just one. 106 | if (challenge.qop) 107 | { 108 | if (challenge.qop.indexOf('auth') > -1) 109 | { 110 | this._qop = 'auth'; 111 | } 112 | else if (challenge.qop.indexOf('auth-int') > -1) 113 | { 114 | this._qop = 'auth-int'; 115 | } 116 | else 117 | { 118 | // Otherwise 'qop' is present but does not contain 'auth' or 'auth-int', so abort here. 119 | debugerror('authenticate() | challenge without Digest qop different than "auth" or "auth-int", authentication aborted'); 120 | 121 | return false; 122 | } 123 | } 124 | else 125 | { 126 | this._qop = null; 127 | } 128 | 129 | // Fill other attributes. 130 | 131 | this._method = method; 132 | this._uri = ruri; 133 | this._cnonce = Utils.createRandomToken(12); 134 | this._nc += 1; 135 | const hex = Number(this._nc).toString(16); 136 | 137 | this._ncHex = '00000000'.substr(0, 8-hex.length) + hex; 138 | 139 | // Nc-value = 8LHEX. Max value = 'FFFFFFFF'. 140 | if (this._nc === 4294967296) 141 | { 142 | this._nc = 1; 143 | this._ncHex = '00000001'; 144 | } 145 | 146 | // Calculate the Digest "response" value. 147 | 148 | // If we have plain SIP password then regenerate ha1. 149 | if (this._credentials.password) 150 | { 151 | // HA1 = MD5(A1) = MD5(username:realm:password). 152 | this._ha1 = Utils.calculateMD5(`${this._credentials.username}:${this._realm}:${this._credentials.password}`); 153 | } 154 | // Otherwise reuse the stored ha1. 155 | else 156 | { 157 | this._ha1 = this._credentials.ha1; 158 | } 159 | 160 | let ha2; 161 | 162 | if (this._qop === 'auth') 163 | { 164 | // HA2 = MD5(A2) = MD5(method:digestURI). 165 | ha2 = Utils.calculateMD5(`${this._method}:${this._uri}`); 166 | // Response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2). 167 | this._response = Utils.calculateMD5(`${this._ha1}:${this._nonce}:${this._ncHex}:${this._cnonce}:auth:${ha2}`); 168 | 169 | } 170 | else if (this._qop === 'auth-int') 171 | { 172 | // HA2 = MD5(A2) = MD5(method:digestURI:MD5(entityBody)). 173 | ha2 = Utils.calculateMD5(`${this._method}:${this._uri}:${Utils.calculateMD5(this.body ? this.body : '')}`); 174 | // Response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2). 175 | this._response = Utils.calculateMD5(`${this._ha1}:${this._nonce}:${this._ncHex}:${this._cnonce}:auth-int:${ha2}`); 176 | 177 | } 178 | else if (this._qop === null) 179 | { 180 | // HA2 = MD5(A2) = MD5(method:digestURI). 181 | ha2 = Utils.calculateMD5(`${this._method}:${this._uri}`); 182 | // Response = MD5(HA1:nonce:HA2). 183 | this._response = Utils.calculateMD5(`${this._ha1}:${this._nonce}:${ha2}`); 184 | } 185 | 186 | debug('authenticate() | response generated'); 187 | 188 | return true; 189 | } 190 | 191 | /** 192 | * Return the Proxy-Authorization or WWW-Authorization header value. 193 | */ 194 | toString() 195 | { 196 | const auth_params = []; 197 | 198 | if (!this._response) 199 | { 200 | throw new Error('response field does not exist, cannot generate Authorization header'); 201 | } 202 | 203 | auth_params.push(`algorithm=${this._algorithm}`); 204 | auth_params.push(`username="${this._credentials.username}"`); 205 | auth_params.push(`realm="${this._realm}"`); 206 | auth_params.push(`nonce="${this._nonce}"`); 207 | auth_params.push(`uri="${this._uri}"`); 208 | auth_params.push(`response="${this._response}"`); 209 | if (this._opaque) 210 | { 211 | auth_params.push(`opaque="${this._opaque}"`); 212 | } 213 | if (this._qop) 214 | { 215 | auth_params.push(`qop=${this._qop}`); 216 | auth_params.push(`cnonce="${this._cnonce}"`); 217 | auth_params.push(`nc=${this._ncHex}`); 218 | } 219 | 220 | return `Digest ${auth_params.join(', ')}`; 221 | } 222 | }; 223 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/Config.js: -------------------------------------------------------------------------------- 1 | const Utils = require('./Utils'); 2 | const JsSIP_C = require('./Constants'); 3 | const Grammar = require('./Grammar'); 4 | const URI = require('./URI'); 5 | const Socket = require('./Socket'); 6 | const Exceptions = require('./Exceptions'); 7 | 8 | // Default settings. 9 | exports.settings = { 10 | // SIP authentication. 11 | authorization_user : null, 12 | password : null, 13 | realm : null, 14 | ha1 : null, 15 | 16 | // SIP account. 17 | display_name : null, 18 | uri : null, 19 | contact_uri : null, 20 | 21 | // SIP instance id (GRUU). 22 | instance_id : null, 23 | 24 | // Preloaded SIP Route header field. 25 | use_preloaded_route : false, 26 | 27 | // Session parameters. 28 | session_timers : true, 29 | no_answer_timeout : 60, 30 | 31 | // Registration parameters. 32 | register : true, 33 | register_expires : 600, 34 | registrar_server : null, 35 | 36 | // Connection options. 37 | sockets : null, 38 | connection_recovery_max_interval : null, 39 | connection_recovery_min_interval : null, 40 | 41 | /* 42 | * Host address. 43 | * Value to be set in Via sent_by and host part of Contact FQDN. 44 | */ 45 | via_host : `${Utils.createRandomToken(12)}.invalid` 46 | }; 47 | 48 | // Configuration checks. 49 | const checks = { 50 | mandatory : { 51 | 52 | sockets(sockets) 53 | { 54 | /* Allow defining sockets parameter as: 55 | * Socket: socket 56 | * Array of Socket: [socket1, socket2] 57 | * Array of Objects: [{socket: socket1, weight:1}, {socket: Socket2, weight:0}] 58 | * Array of Objects and Socket: [{socket: socket1}, socket2] 59 | */ 60 | const _sockets = []; 61 | 62 | if (Socket.isSocket(sockets)) 63 | { 64 | _sockets.push({ socket: sockets }); 65 | } 66 | else if (Array.isArray(sockets) && sockets.length) 67 | { 68 | for (const socket of sockets) 69 | { 70 | if (Socket.isSocket(socket)) 71 | { 72 | _sockets.push({ socket: socket }); 73 | } 74 | } 75 | } 76 | else 77 | { 78 | return; 79 | } 80 | 81 | return _sockets; 82 | }, 83 | 84 | uri(uri) 85 | { 86 | if (!/^sip:/i.test(uri)) 87 | { 88 | uri = `${JsSIP_C.SIP}:${uri}`; 89 | } 90 | const parsed = URI.parse(uri); 91 | 92 | if (!parsed) 93 | { 94 | return; 95 | } 96 | else if (!parsed.user) 97 | { 98 | return; 99 | } 100 | else 101 | { 102 | return parsed; 103 | } 104 | } 105 | }, 106 | 107 | optional : { 108 | 109 | authorization_user(authorization_user) 110 | { 111 | if (Grammar.parse(`"${authorization_user}"`, 'quoted_string') === -1) 112 | { 113 | return; 114 | } 115 | else 116 | { 117 | return authorization_user; 118 | } 119 | }, 120 | 121 | connection_recovery_max_interval(connection_recovery_max_interval) 122 | { 123 | if (Utils.isDecimal(connection_recovery_max_interval)) 124 | { 125 | const value = Number(connection_recovery_max_interval); 126 | 127 | if (value > 0) 128 | { 129 | return value; 130 | } 131 | } 132 | }, 133 | 134 | connection_recovery_min_interval(connection_recovery_min_interval) 135 | { 136 | if (Utils.isDecimal(connection_recovery_min_interval)) 137 | { 138 | const value = Number(connection_recovery_min_interval); 139 | 140 | if (value > 0) 141 | { 142 | return value; 143 | } 144 | } 145 | }, 146 | 147 | contact_uri(contact_uri) 148 | { 149 | if (typeof contact_uri === 'string') 150 | { 151 | const uri = Grammar.parse(contact_uri, 'SIP_URI'); 152 | 153 | if (uri !== -1) 154 | { 155 | return uri; 156 | } 157 | } 158 | }, 159 | 160 | display_name(display_name) 161 | { 162 | if (Grammar.parse(`"${display_name}"`, 'display_name') === -1) 163 | { 164 | return; 165 | } 166 | else 167 | { 168 | return display_name; 169 | } 170 | }, 171 | 172 | instance_id(instance_id) 173 | { 174 | if ((/^uuid:/i.test(instance_id))) 175 | { 176 | instance_id = instance_id.substr(5); 177 | } 178 | 179 | if (Grammar.parse(instance_id, 'uuid') === -1) 180 | { 181 | return; 182 | } 183 | else 184 | { 185 | return instance_id; 186 | } 187 | }, 188 | 189 | no_answer_timeout(no_answer_timeout) 190 | { 191 | if (Utils.isDecimal(no_answer_timeout)) 192 | { 193 | const value = Number(no_answer_timeout); 194 | 195 | if (value > 0) 196 | { 197 | return value; 198 | } 199 | } 200 | }, 201 | 202 | session_timers(session_timers) 203 | { 204 | if (typeof session_timers === 'boolean') 205 | { 206 | return session_timers; 207 | } 208 | }, 209 | 210 | password(password) 211 | { 212 | return String(password); 213 | }, 214 | 215 | realm(realm) 216 | { 217 | return String(realm); 218 | }, 219 | 220 | ha1(ha1) 221 | { 222 | return String(ha1); 223 | }, 224 | 225 | register(register) 226 | { 227 | if (typeof register === 'boolean') 228 | { 229 | return register; 230 | } 231 | }, 232 | 233 | register_expires(register_expires) 234 | { 235 | if (Utils.isDecimal(register_expires)) 236 | { 237 | const value = Number(register_expires); 238 | 239 | if (value > 0) 240 | { 241 | return value; 242 | } 243 | } 244 | }, 245 | 246 | registrar_server(registrar_server) 247 | { 248 | if (!/^sip:/i.test(registrar_server)) 249 | { 250 | registrar_server = `${JsSIP_C.SIP}:${registrar_server}`; 251 | } 252 | 253 | const parsed = URI.parse(registrar_server); 254 | 255 | if (!parsed) 256 | { 257 | return; 258 | } 259 | else if (parsed.user) 260 | { 261 | return; 262 | } 263 | else 264 | { 265 | return parsed; 266 | } 267 | }, 268 | 269 | use_preloaded_route(use_preloaded_route) 270 | { 271 | if (typeof use_preloaded_route === 'boolean') 272 | { 273 | return use_preloaded_route; 274 | } 275 | } 276 | } 277 | }; 278 | 279 | exports.load = (dst, src) => 280 | { 281 | // Check Mandatory parameters. 282 | for (const parameter in checks.mandatory) 283 | { 284 | if (!src.hasOwnProperty(parameter)) 285 | { 286 | throw new Exceptions.ConfigurationError(parameter); 287 | } 288 | else 289 | { 290 | const value = src[parameter]; 291 | const checked_value = checks.mandatory[parameter](value); 292 | 293 | if (checked_value !== undefined) 294 | { 295 | dst[parameter] = checked_value; 296 | } 297 | else 298 | { 299 | throw new Exceptions.ConfigurationError(parameter, value); 300 | } 301 | } 302 | } 303 | 304 | // Check Optional parameters. 305 | for (const parameter in checks.optional) 306 | { 307 | if (src.hasOwnProperty(parameter)) 308 | { 309 | const value = src[parameter]; 310 | 311 | /* If the parameter value is null, empty string, undefined, empty array 312 | * or it's a number with NaN value, then apply its default value. 313 | */ 314 | if (Utils.isEmpty(value)) 315 | { 316 | continue; 317 | } 318 | 319 | const checked_value = checks.optional[parameter](value); 320 | 321 | if (checked_value !== undefined) 322 | { 323 | dst[parameter] = checked_value; 324 | } 325 | else 326 | { 327 | throw new Exceptions.ConfigurationError(parameter, value); 328 | } 329 | } 330 | } 331 | }; 332 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/Transport.js: -------------------------------------------------------------------------------- 1 | const Socket = require('./Socket'); 2 | const debug = require('debug')('JsSIP:Transport'); 3 | const debugerror = require('debug')('JsSIP:ERROR:Transport'); 4 | 5 | debugerror.log = console.warn.bind(console); 6 | 7 | /** 8 | * Constants 9 | */ 10 | const C = { 11 | // Transport status. 12 | STATUS_CONNECTED : 0, 13 | STATUS_CONNECTING : 1, 14 | STATUS_DISCONNECTED : 2, 15 | 16 | // Socket status. 17 | SOCKET_STATUS_READY : 0, 18 | SOCKET_STATUS_ERROR : 1, 19 | 20 | // Recovery options. 21 | recovery_options : { 22 | min_interval : 2, // minimum interval in seconds between recover attempts 23 | max_interval : 30 // maximum interval in seconds between recover attempts 24 | } 25 | }; 26 | 27 | /* 28 | * Manages one or multiple JsSIP.Socket instances. 29 | * Is reponsible for transport recovery logic among all socket instances. 30 | * 31 | * @socket JsSIP::Socket instance 32 | */ 33 | module.exports = class Transport 34 | { 35 | constructor(sockets, recovery_options = C.recovery_options) 36 | { 37 | debug('new()'); 38 | 39 | this.status = C.STATUS_DISCONNECTED; 40 | 41 | // Current socket. 42 | this.socket = null; 43 | 44 | // Socket collection. 45 | this.sockets = []; 46 | 47 | this.recovery_options = recovery_options; 48 | this.recover_attempts = 0; 49 | this.recovery_timer = null; 50 | 51 | this.close_requested = false; 52 | 53 | if (typeof sockets === 'undefined') 54 | { 55 | throw new TypeError('Invalid argument.' + 56 | ' undefined \'sockets\' argument'); 57 | } 58 | 59 | if (!(sockets instanceof Array)) 60 | { 61 | sockets = [ sockets ]; 62 | } 63 | 64 | sockets.forEach(function(socket) 65 | { 66 | if (!Socket.isSocket(socket.socket)) 67 | { 68 | throw new TypeError('Invalid argument.' + 69 | ' invalid \'JsSIP.Socket\' instance'); 70 | } 71 | 72 | if (socket.weight && !Number(socket.weight)) 73 | { 74 | throw new TypeError('Invalid argument.' + 75 | ' \'weight\' attribute is not a number'); 76 | } 77 | 78 | this.sockets.push({ 79 | socket : socket.socket, 80 | weight : socket.weight || 0, 81 | status : C.SOCKET_STATUS_READY 82 | }); 83 | }, this); 84 | 85 | // Get the socket with higher weight. 86 | this._getSocket(); 87 | } 88 | 89 | /** 90 | * Instance Methods 91 | */ 92 | 93 | get via_transport() 94 | { 95 | return this.socket.via_transport; 96 | } 97 | 98 | get url() 99 | { 100 | return this.socket.url; 101 | } 102 | 103 | get sip_uri() 104 | { 105 | return this.socket.sip_uri; 106 | } 107 | 108 | connect() 109 | { 110 | debug('connect()'); 111 | 112 | if (this.isConnected()) 113 | { 114 | debug('Transport is already connected'); 115 | 116 | return; 117 | } 118 | else if (this.isConnecting()) 119 | { 120 | debug('Transport is connecting'); 121 | 122 | return; 123 | } 124 | 125 | this.close_requested = false; 126 | this.status = C.STATUS_CONNECTING; 127 | this.onconnecting({ socket: this.socket, attempts: this.recover_attempts }); 128 | 129 | if (!this.close_requested) 130 | { 131 | // Bind socket event callbacks. 132 | this.socket.onconnect = this._onConnect.bind(this); 133 | this.socket.ondisconnect = this._onDisconnect.bind(this); 134 | this.socket.ondata = this._onData.bind(this); 135 | 136 | this.socket.connect(); 137 | } 138 | 139 | return; 140 | } 141 | 142 | disconnect() 143 | { 144 | debug('close()'); 145 | 146 | this.close_requested = true; 147 | this.recover_attempts = 0; 148 | this.status = C.STATUS_DISCONNECTED; 149 | 150 | // Clear recovery_timer. 151 | if (this.recovery_timer !== null) 152 | { 153 | clearTimeout(this.recovery_timer); 154 | this.recovery_timer = null; 155 | } 156 | 157 | // Unbind socket event callbacks. 158 | this.socket.onconnect = () => {}; 159 | this.socket.ondisconnect = () => {}; 160 | this.socket.ondata = () => {}; 161 | 162 | this.socket.disconnect(); 163 | this.ondisconnect(); 164 | } 165 | 166 | send(data) 167 | { 168 | debug('send()'); 169 | 170 | if (!this.isConnected()) 171 | { 172 | debugerror('unable to send message, transport is not connected'); 173 | 174 | return false; 175 | } 176 | 177 | const message = data.toString(); 178 | 179 | debug(`sending message:\n\n${message}\n`); 180 | 181 | return this.socket.send(message); 182 | } 183 | 184 | isConnected() 185 | { 186 | return this.status === C.STATUS_CONNECTED; 187 | } 188 | 189 | isConnecting() 190 | { 191 | return this.status === C.STATUS_CONNECTING; 192 | } 193 | 194 | /** 195 | * Private API. 196 | */ 197 | 198 | _reconnect() 199 | { 200 | this.recover_attempts+=1; 201 | 202 | let k = Math.floor((Math.random() * Math.pow(2, this.recover_attempts)) +1); 203 | 204 | if (k < this.recovery_options.min_interval) 205 | { 206 | k = this.recovery_options.min_interval; 207 | } 208 | 209 | else if (k > this.recovery_options.max_interval) 210 | { 211 | k = this.recovery_options.max_interval; 212 | } 213 | 214 | debug(`reconnection attempt: ${this.recover_attempts}. next connection attempt in ${k} seconds`); 215 | 216 | this.recovery_timer = setTimeout(() => 217 | { 218 | if (!this.close_requested && !(this.isConnected() || this.isConnecting())) 219 | { 220 | // Get the next available socket with higher weight. 221 | this._getSocket(); 222 | 223 | // Connect the socket. 224 | this.connect(); 225 | } 226 | }, k * 1000); 227 | } 228 | 229 | /** 230 | * get the next available socket with higher weight 231 | */ 232 | _getSocket() 233 | { 234 | 235 | let candidates = []; 236 | 237 | this.sockets.forEach((socket) => 238 | { 239 | if (socket.status === C.SOCKET_STATUS_ERROR) 240 | { 241 | return; // continue the array iteration 242 | } 243 | else if (candidates.length === 0) 244 | { 245 | candidates.push(socket); 246 | } 247 | else if (socket.weight > candidates[0].weight) 248 | { 249 | candidates = [ socket ]; 250 | } 251 | else if (socket.weight === candidates[0].weight) 252 | { 253 | candidates.push(socket); 254 | } 255 | }); 256 | 257 | if (candidates.length === 0) 258 | { 259 | // All sockets have failed. reset sockets status. 260 | this.sockets.forEach((socket) => 261 | { 262 | socket.status = C.SOCKET_STATUS_READY; 263 | }); 264 | 265 | // Get next available socket. 266 | this._getSocket(); 267 | 268 | return; 269 | } 270 | 271 | const idx = Math.floor((Math.random()* candidates.length)); 272 | 273 | this.socket = candidates[idx].socket; 274 | } 275 | 276 | /** 277 | * Socket Event Handlers 278 | */ 279 | 280 | _onConnect() 281 | { 282 | this.recover_attempts = 0; 283 | this.status = C.STATUS_CONNECTED; 284 | 285 | // Clear recovery_timer. 286 | if (this.recovery_timer !== null) 287 | { 288 | clearTimeout(this.recovery_timer); 289 | this.recovery_timer = null; 290 | } 291 | 292 | this.onconnect({ socket: this }); 293 | } 294 | 295 | _onDisconnect(error, code, reason) 296 | { 297 | this.status = C.STATUS_DISCONNECTED; 298 | this.ondisconnect({ 299 | socket : this.socket, 300 | error, 301 | code, 302 | reason 303 | }); 304 | 305 | if (this.close_requested) 306 | { 307 | return; 308 | } 309 | 310 | // Update socket status. 311 | else 312 | { 313 | this.sockets.forEach(function(socket) 314 | { 315 | if (this.socket === socket.socket) 316 | { 317 | socket.status = C.SOCKET_STATUS_ERROR; 318 | } 319 | }, this); 320 | } 321 | 322 | this._reconnect(error); 323 | } 324 | 325 | _onData(data) 326 | { 327 | // CRLF Keep Alive response from server. Ignore it. 328 | if (data === '\r\n') 329 | { 330 | debug('received message with CRLF Keep Alive response'); 331 | 332 | return; 333 | } 334 | 335 | // Binary message. 336 | else if (typeof data !== 'string') 337 | { 338 | try 339 | { 340 | data = String.fromCharCode.apply(null, new Uint8Array(data)); 341 | } 342 | catch (evt) 343 | { 344 | debug('received binary message failed to be converted into string,' + 345 | ' message discarded'); 346 | 347 | return; 348 | } 349 | 350 | debug(`received binary message:\n\n${data}\n`); 351 | } 352 | 353 | // Text message. 354 | else 355 | { 356 | debug(`received text message:\n\n${data}\n`); 357 | } 358 | 359 | this.ondata({ transport: this, message: data }); 360 | } 361 | }; 362 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/Dialog.js: -------------------------------------------------------------------------------- 1 | const SIPMessage = require('./SIPMessage'); 2 | const JsSIP_C = require('./Constants'); 3 | const Transactions = require('./Transactions'); 4 | const Dialog_RequestSender = require('./Dialog/RequestSender'); 5 | const Utils = require('./Utils'); 6 | const debug = require('debug')('JsSIP:Dialog'); 7 | 8 | const C = { 9 | // Dialog states. 10 | STATUS_EARLY : 1, 11 | STATUS_CONFIRMED : 2 12 | }; 13 | 14 | // RFC 3261 12.1. 15 | module.exports = class Dialog 16 | { 17 | // Expose C object. 18 | static get C() 19 | { 20 | return C; 21 | } 22 | 23 | constructor(owner, message, type, state = C.STATUS_CONFIRMED) 24 | { 25 | this._owner = owner; 26 | this._ua = owner._ua; 27 | 28 | this._uac_pending_reply = false; 29 | this._uas_pending_reply = false; 30 | 31 | if (!message.hasHeader('contact')) 32 | { 33 | return { 34 | error : 'unable to create a Dialog without Contact header field' 35 | }; 36 | } 37 | 38 | if (message instanceof SIPMessage.IncomingResponse) 39 | { 40 | state = (message.status_code < 200) ? C.STATUS_EARLY : C.STATUS_CONFIRMED; 41 | } 42 | 43 | const contact = message.parseHeader('contact'); 44 | 45 | // RFC 3261 12.1.1. 46 | if (type === 'UAS') 47 | { 48 | this._id = { 49 | call_id : message.call_id, 50 | local_tag : message.to_tag, 51 | remote_tag : message.from_tag, 52 | toString() 53 | { 54 | return this.call_id + this.local_tag + this.remote_tag; 55 | } 56 | }; 57 | this._state = state; 58 | this._remote_seqnum = message.cseq; 59 | this._local_uri = message.parseHeader('to').uri; 60 | this._remote_uri = message.parseHeader('from').uri; 61 | this._remote_target = contact.uri; 62 | this._route_set = message.getHeaders('record-route'); 63 | this._ack_seqnum = this._remote_seqnum; 64 | } 65 | // RFC 3261 12.1.2. 66 | else if (type === 'UAC') 67 | { 68 | this._id = { 69 | call_id : message.call_id, 70 | local_tag : message.from_tag, 71 | remote_tag : message.to_tag, 72 | toString() 73 | { 74 | return this.call_id + this.local_tag + this.remote_tag; 75 | } 76 | }; 77 | this._state = state; 78 | this._local_seqnum = message.cseq; 79 | this._local_uri = message.parseHeader('from').uri; 80 | this._remote_uri = message.parseHeader('to').uri; 81 | this._remote_target = contact.uri; 82 | this._route_set = message.getHeaders('record-route').reverse(); 83 | this._ack_seqnum = null; 84 | 85 | } 86 | 87 | this._ua.newDialog(this); 88 | debug(`new ${type} dialog created with status ${this._state === C.STATUS_EARLY ? 'EARLY': 'CONFIRMED'}`); 89 | } 90 | 91 | get id() 92 | { 93 | return this._id; 94 | } 95 | 96 | get local_seqnum() 97 | { 98 | return this._local_seqnum; 99 | } 100 | 101 | set local_seqnum(num) 102 | { 103 | this._local_seqnum = num; 104 | } 105 | 106 | get owner() 107 | { 108 | return this._owner; 109 | } 110 | 111 | get uac_pending_reply() 112 | { 113 | return this._uac_pending_reply; 114 | } 115 | 116 | set uac_pending_reply(pending) 117 | { 118 | this._uac_pending_reply = pending; 119 | } 120 | 121 | get uas_pending_reply() 122 | { 123 | return this._uas_pending_reply; 124 | } 125 | 126 | update(message, type) 127 | { 128 | this._state = C.STATUS_CONFIRMED; 129 | 130 | debug(`dialog ${this._id.toString()} changed to CONFIRMED state`); 131 | 132 | if (type === 'UAC') 133 | { 134 | // RFC 3261 13.2.2.4. 135 | this._route_set = message.getHeaders('record-route').reverse(); 136 | } 137 | } 138 | 139 | terminate() 140 | { 141 | debug(`dialog ${this._id.toString()} deleted`); 142 | this._ua.destroyDialog(this); 143 | } 144 | 145 | sendRequest(method, options = {}) 146 | { 147 | const extraHeaders = Utils.cloneArray(options.extraHeaders); 148 | const eventHandlers = options.eventHanlders || {}; 149 | const body = options.body || null; 150 | const request = this._createRequest(method, extraHeaders, body); 151 | 152 | // Increase the local CSeq on authentication. 153 | eventHandlers.onAuthenticated = () => 154 | { 155 | this._local_seqnum += 1; 156 | }; 157 | 158 | const request_sender = new Dialog_RequestSender(this, request, eventHandlers); 159 | 160 | request_sender.send(); 161 | 162 | // Return the instance of OutgoingRequest. 163 | return request; 164 | } 165 | 166 | receiveRequest(request) 167 | { 168 | // Check in-dialog request. 169 | if (!this._checkInDialogRequest(request)) 170 | { 171 | return; 172 | } 173 | 174 | // ACK received. Cleanup this._ack_seqnum. 175 | if (request.method === JsSIP_C.ACK && this._ack_seqnum !== null) 176 | { 177 | this._ack_seqnum = null; 178 | } 179 | // INVITE received. Set this._ack_seqnum. 180 | else if (request.method === JsSIP_C.INVITE) 181 | { 182 | this._ack_seqnum = request.cseq; 183 | } 184 | 185 | this._owner.receiveRequest(request); 186 | } 187 | 188 | // RFC 3261 12.2.1.1. 189 | _createRequest(method, extraHeaders, body) 190 | { 191 | extraHeaders = Utils.cloneArray(extraHeaders); 192 | 193 | if (!this._local_seqnum) { this._local_seqnum = Math.floor(Math.random() * 10000); } 194 | 195 | const cseq = (method === JsSIP_C.CANCEL || method === JsSIP_C.ACK) ? 196 | this._local_seqnum : 197 | this._local_seqnum += 1; 198 | 199 | const request = new SIPMessage.OutgoingRequest( 200 | method, 201 | this._remote_target, 202 | this._ua, { 203 | 'cseq' : cseq, 204 | 'call_id' : this._id.call_id, 205 | 'from_uri' : this._local_uri, 206 | 'from_tag' : this._id.local_tag, 207 | 'to_uri' : this._remote_uri, 208 | 'to_tag' : this._id.remote_tag, 209 | 'route_set' : this._route_set 210 | }, extraHeaders, body); 211 | 212 | return request; 213 | } 214 | 215 | // RFC 3261 12.2.2. 216 | _checkInDialogRequest(request) 217 | { 218 | 219 | if (!this._remote_seqnum) 220 | { 221 | this._remote_seqnum = request.cseq; 222 | } 223 | else if (request.cseq < this._remote_seqnum) 224 | { 225 | if (request.method === JsSIP_C.ACK) 226 | { 227 | // We are not expecting any ACK with lower seqnum than the current one. 228 | // Or this is not the ACK we are waiting for. 229 | if (this._ack_seqnum === null || request.cseq !== this._ack_seqnum) 230 | { 231 | return false; 232 | } 233 | } 234 | else 235 | { 236 | request.reply(500); 237 | 238 | return false; 239 | } 240 | } 241 | else if (request.cseq > this._remote_seqnum) 242 | { 243 | this._remote_seqnum = request.cseq; 244 | } 245 | 246 | // RFC3261 14.2 Modifying an Existing Session -UAS BEHAVIOR-. 247 | if (request.method === JsSIP_C.INVITE || 248 | (request.method === JsSIP_C.UPDATE && request.body)) 249 | { 250 | if (this._uac_pending_reply === true) 251 | { 252 | request.reply(491); 253 | } 254 | else if (this._uas_pending_reply === true) 255 | { 256 | const retryAfter = (Math.random() * 10 | 0) + 1; 257 | 258 | request.reply(500, null, [ `Retry-After:${retryAfter}` ]); 259 | 260 | return false; 261 | } 262 | else 263 | { 264 | this._uas_pending_reply = true; 265 | 266 | const stateChanged = () => 267 | { 268 | if (request.server_transaction.state === Transactions.C.STATUS_ACCEPTED || 269 | request.server_transaction.state === Transactions.C.STATUS_COMPLETED || 270 | request.server_transaction.state === Transactions.C.STATUS_TERMINATED) 271 | { 272 | 273 | request.server_transaction.removeListener('stateChanged', stateChanged); 274 | this._uas_pending_reply = false; 275 | } 276 | }; 277 | 278 | request.server_transaction.on('stateChanged', stateChanged); 279 | } 280 | 281 | // RFC3261 12.2.2 Replace the dialog`s remote target URI if the request is accepted. 282 | if (request.hasHeader('contact')) 283 | { 284 | request.server_transaction.on('stateChanged', () => 285 | { 286 | if (request.server_transaction.state === Transactions.C.STATUS_ACCEPTED) 287 | { 288 | this._remote_target = request.parseHeader('contact').uri; 289 | } 290 | }); 291 | } 292 | } 293 | else if (request.method === JsSIP_C.NOTIFY) 294 | { 295 | // RFC6665 3.2 Replace the dialog`s remote target URI if the request is accepted. 296 | if (request.hasHeader('contact')) 297 | { 298 | request.server_transaction.on('stateChanged', () => 299 | { 300 | if (request.server_transaction.state === Transactions.C.STATUS_COMPLETED) 301 | { 302 | this._remote_target = request.parseHeader('contact').uri; 303 | } 304 | }); 305 | } 306 | } 307 | 308 | return true; 309 | } 310 | }; 311 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/Parser.js: -------------------------------------------------------------------------------- 1 | const Grammar = require('./Grammar'); 2 | const SIPMessage = require('./SIPMessage'); 3 | const debugerror = require('debug')('JsSIP:ERROR:Parser'); 4 | 5 | debugerror.log = console.warn.bind(console); 6 | 7 | /** 8 | * Parse SIP Message 9 | */ 10 | exports.parseMessage = (data, ua) => 11 | { 12 | let message; 13 | let bodyStart; 14 | let headerEnd = data.indexOf('\r\n'); 15 | 16 | if (headerEnd === -1) 17 | { 18 | debugerror('parseMessage() | no CRLF found, not a SIP message'); 19 | 20 | return; 21 | } 22 | 23 | // Parse first line. Check if it is a Request or a Reply. 24 | const firstLine = data.substring(0, headerEnd); 25 | let parsed = Grammar.parse(firstLine, 'Request_Response'); 26 | 27 | if (parsed === -1) 28 | { 29 | debugerror(`parseMessage() | error parsing first line of SIP message: "${firstLine}"`); 30 | 31 | return; 32 | } 33 | else if (!parsed.status_code) 34 | { 35 | message = new SIPMessage.IncomingRequest(ua); 36 | message.method = parsed.method; 37 | message.ruri = parsed.uri; 38 | } 39 | else 40 | { 41 | message = new SIPMessage.IncomingResponse(); 42 | message.status_code = parsed.status_code; 43 | message.reason_phrase = parsed.reason_phrase; 44 | } 45 | 46 | message.data = data; 47 | let headerStart = headerEnd + 2; 48 | 49 | /* Loop over every line in data. Detect the end of each header and parse 50 | * it or simply add to the headers collection. 51 | */ 52 | while (true) 53 | { 54 | headerEnd = getHeader(data, headerStart); 55 | 56 | // The SIP message has normally finished. 57 | if (headerEnd === -2) 58 | { 59 | bodyStart = headerStart + 2; 60 | break; 61 | } 62 | // Data.indexOf returned -1 due to a malformed message. 63 | else if (headerEnd === -1) 64 | { 65 | debugerror('parseMessage() | malformed message'); 66 | 67 | return; 68 | } 69 | 70 | parsed = parseHeader(message, data, headerStart, headerEnd); 71 | 72 | if (parsed !== true) 73 | { 74 | debugerror('parseMessage() |', parsed.error); 75 | 76 | return; 77 | } 78 | 79 | headerStart = headerEnd + 2; 80 | } 81 | 82 | /* RFC3261 18.3. 83 | * If there are additional bytes in the transport packet 84 | * beyond the end of the body, they MUST be discarded. 85 | */ 86 | if (message.hasHeader('content-length')) 87 | { 88 | const contentLength = message.getHeader('content-length'); 89 | 90 | message.body = data.substr(bodyStart, contentLength); 91 | } 92 | else 93 | { 94 | message.body = data.substring(bodyStart); 95 | } 96 | 97 | return message; 98 | }; 99 | 100 | /** 101 | * Extract and parse every header of a SIP message. 102 | */ 103 | function getHeader(data, headerStart) 104 | { 105 | // 'start' position of the header. 106 | let start = headerStart; 107 | // 'end' position of the header. 108 | let end = 0; 109 | // 'partial end' position of the header. 110 | let partialEnd = 0; 111 | 112 | // End of message. 113 | if (data.substring(start, start + 2).match(/(^\r\n)/)) 114 | { 115 | return -2; 116 | } 117 | 118 | while (end === 0) 119 | { 120 | // Partial End of Header. 121 | partialEnd = data.indexOf('\r\n', start); 122 | 123 | // 'indexOf' returns -1 if the value to be found never occurs. 124 | if (partialEnd === -1) 125 | { 126 | return partialEnd; 127 | } 128 | 129 | if (!data.substring(partialEnd + 2, partialEnd + 4).match(/(^\r\n)/) && data.charAt(partialEnd + 2).match(/(^\s+)/)) 130 | { 131 | // Not the end of the message. Continue from the next position. 132 | start = partialEnd + 2; 133 | } 134 | else 135 | { 136 | end = partialEnd; 137 | } 138 | } 139 | 140 | return end; 141 | } 142 | 143 | function parseHeader(message, data, headerStart, headerEnd) 144 | { 145 | let parsed; 146 | const hcolonIndex = data.indexOf(':', headerStart); 147 | const headerName = data.substring(headerStart, hcolonIndex).trim(); 148 | const headerValue = data.substring(hcolonIndex + 1, headerEnd).trim(); 149 | 150 | // If header-field is well-known, parse it. 151 | switch (headerName.toLowerCase()) 152 | { 153 | case 'via': 154 | case 'v': 155 | message.addHeader('via', headerValue); 156 | if (message.getHeaders('via').length === 1) 157 | { 158 | parsed = message.parseHeader('Via'); 159 | if (parsed) 160 | { 161 | message.via = parsed; 162 | message.via_branch = parsed.branch; 163 | } 164 | } 165 | else 166 | { 167 | parsed = 0; 168 | } 169 | break; 170 | case 'from': 171 | case 'f': 172 | message.setHeader('from', headerValue); 173 | parsed = message.parseHeader('from'); 174 | if (parsed) 175 | { 176 | message.from = parsed; 177 | message.from_tag = parsed.getParam('tag'); 178 | } 179 | break; 180 | case 'to': 181 | case 't': 182 | message.setHeader('to', headerValue); 183 | parsed = message.parseHeader('to'); 184 | if (parsed) 185 | { 186 | message.to = parsed; 187 | message.to_tag = parsed.getParam('tag'); 188 | } 189 | break; 190 | case 'record-route': 191 | parsed = Grammar.parse(headerValue, 'Record_Route'); 192 | 193 | if (parsed === -1) 194 | { 195 | parsed = undefined; 196 | } 197 | else 198 | { 199 | for (const header of parsed) 200 | { 201 | message.addHeader('record-route', headerValue.substring(header.possition, header.offset)); 202 | message.headers['Record-Route'][message.getHeaders('record-route').length - 1].parsed = header.parsed; 203 | } 204 | } 205 | break; 206 | case 'call-id': 207 | case 'i': 208 | message.setHeader('call-id', headerValue); 209 | parsed = message.parseHeader('call-id'); 210 | if (parsed) 211 | { 212 | message.call_id = headerValue; 213 | } 214 | break; 215 | case 'contact': 216 | case 'm': 217 | parsed = Grammar.parse(headerValue, 'Contact'); 218 | 219 | if (parsed === -1) 220 | { 221 | parsed = undefined; 222 | } 223 | else 224 | { 225 | for (const header of parsed) 226 | { 227 | message.addHeader('contact', headerValue.substring(header.possition, header.offset)); 228 | message.headers.Contact[message.getHeaders('contact').length - 1].parsed = header.parsed; 229 | } 230 | } 231 | break; 232 | case 'content-length': 233 | case 'l': 234 | message.setHeader('content-length', headerValue); 235 | parsed = message.parseHeader('content-length'); 236 | break; 237 | case 'content-type': 238 | case 'c': 239 | message.setHeader('content-type', headerValue); 240 | parsed = message.parseHeader('content-type'); 241 | break; 242 | case 'cseq': 243 | message.setHeader('cseq', headerValue); 244 | parsed = message.parseHeader('cseq'); 245 | if (parsed) 246 | { 247 | message.cseq = parsed.value; 248 | } 249 | if (message instanceof SIPMessage.IncomingResponse) 250 | { 251 | message.method = parsed.method; 252 | } 253 | break; 254 | case 'max-forwards': 255 | message.setHeader('max-forwards', headerValue); 256 | parsed = message.parseHeader('max-forwards'); 257 | break; 258 | case 'www-authenticate': 259 | message.setHeader('www-authenticate', headerValue); 260 | parsed = message.parseHeader('www-authenticate'); 261 | break; 262 | case 'proxy-authenticate': 263 | message.setHeader('proxy-authenticate', headerValue); 264 | parsed = message.parseHeader('proxy-authenticate'); 265 | break; 266 | case 'session-expires': 267 | case 'x': 268 | message.setHeader('session-expires', headerValue); 269 | parsed = message.parseHeader('session-expires'); 270 | if (parsed) 271 | { 272 | message.session_expires = parsed.expires; 273 | message.session_expires_refresher = parsed.refresher; 274 | } 275 | break; 276 | case 'refer-to': 277 | case 'r': 278 | message.setHeader('refer-to', headerValue); 279 | parsed = message.parseHeader('refer-to'); 280 | if (parsed) 281 | { 282 | message.refer_to = parsed; 283 | } 284 | break; 285 | case 'replaces': 286 | message.setHeader('replaces', headerValue); 287 | parsed = message.parseHeader('replaces'); 288 | if (parsed) 289 | { 290 | message.replaces = parsed; 291 | } 292 | break; 293 | case 'event': 294 | case 'o': 295 | message.setHeader('event', headerValue); 296 | parsed = message.parseHeader('event'); 297 | if (parsed) 298 | { 299 | message.event = parsed; 300 | } 301 | break; 302 | default: 303 | // Do not parse this header. 304 | message.setHeader(headerName, headerValue); 305 | parsed = 0; 306 | } 307 | 308 | if (parsed === undefined) 309 | { 310 | return { 311 | error : `error parsing header "${headerName}"` 312 | }; 313 | } 314 | else 315 | { 316 | return true; 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/Registrator.js: -------------------------------------------------------------------------------- 1 | const Utils = require('./Utils'); 2 | const JsSIP_C = require('./Constants'); 3 | const SIPMessage = require('./SIPMessage'); 4 | const RequestSender = require('./RequestSender'); 5 | const debug = require('debug')('JsSIP:Registrator'); 6 | 7 | module.exports = class Registrator 8 | { 9 | constructor(ua, transport) 10 | { 11 | const reg_id=1; // Force reg_id to 1. 12 | 13 | this._ua = ua; 14 | this._transport = transport; 15 | 16 | this._registrar = ua.configuration.registrar_server; 17 | this._expires = ua.configuration.register_expires; 18 | 19 | // Call-ID and CSeq values RFC3261 10.2. 20 | this._call_id = Utils.createRandomToken(22); 21 | this._cseq = 0; 22 | 23 | this._to_uri = ua.configuration.uri; 24 | 25 | this._registrationTimer = null; 26 | 27 | // Ongoing Register request. 28 | this._registering = false; 29 | 30 | // Set status. 31 | this._registered = false; 32 | 33 | // Contact header. 34 | this._contact = this._ua.contact.toString(); 35 | 36 | // Sip.ice media feature tag (RFC 5768). 37 | this._contact += ';+sip.ice'; 38 | 39 | // Custom headers for REGISTER and un-REGISTER. 40 | this._extraHeaders = []; 41 | 42 | // Custom Contact header params for REGISTER and un-REGISTER. 43 | this._extraContactParams = ''; 44 | 45 | if (reg_id) 46 | { 47 | this._contact += `;reg-id=${reg_id}`; 48 | this._contact += `;+sip.instance=""`; 49 | } 50 | } 51 | 52 | setExtraHeaders(extraHeaders) 53 | { 54 | if (! Array.isArray(extraHeaders)) 55 | { 56 | extraHeaders = []; 57 | } 58 | 59 | this._extraHeaders = extraHeaders.slice(); 60 | } 61 | 62 | setExtraContactParams(extraContactParams) 63 | { 64 | if (! (extraContactParams instanceof Object)) 65 | { 66 | extraContactParams = {}; 67 | } 68 | 69 | // Reset it. 70 | this._extraContactParams = ''; 71 | 72 | for (const param_key in extraContactParams) 73 | { 74 | if (Object.prototype.hasOwnProperty.call(extraContactParams, param_key)) 75 | { 76 | const param_value = extraContactParams[param_key]; 77 | 78 | this._extraContactParams += (`;${param_key}`); 79 | if (param_value) 80 | { 81 | this._extraContactParams += (`=${param_value}`); 82 | } 83 | } 84 | } 85 | } 86 | 87 | register() 88 | { 89 | if (this._registering) 90 | { 91 | debug('Register request in progress...'); 92 | 93 | return; 94 | } 95 | 96 | const extraHeaders = this._extraHeaders.slice(); 97 | 98 | extraHeaders.push(`Contact: \ 99 | ${this._contact};expires=${this._expires}${this._extraContactParams}`); 100 | extraHeaders.push(`Expires: ${this._expires}`); 101 | 102 | const request = new SIPMessage.OutgoingRequest( 103 | JsSIP_C.REGISTER, this._registrar, this._ua, { 104 | 'to_uri' : this._to_uri, 105 | 'call_id' : this._call_id, 106 | 'cseq' : (this._cseq += 1) 107 | }, extraHeaders); 108 | 109 | const request_sender = new RequestSender(this._ua, request, { 110 | onRequestTimeout : () => 111 | { 112 | this._registrationFailure(null, JsSIP_C.causes.REQUEST_TIMEOUT); 113 | }, 114 | onTransportError : () => 115 | { 116 | this._registrationFailure(null, JsSIP_C.causes.CONNECTION_ERROR); 117 | }, 118 | // Increase the CSeq on authentication. 119 | onAuthenticated : () => 120 | { 121 | this._cseq += 1; 122 | }, 123 | onReceiveResponse : (response) => 124 | { 125 | let contact, expires, contacts = response.getHeaders('contact').length; 126 | 127 | // Discard responses to older REGISTER/un-REGISTER requests. 128 | if (response.cseq !== this._cseq) 129 | { 130 | return; 131 | } 132 | 133 | // Clear registration timer. 134 | if (this._registrationTimer !== null) 135 | { 136 | clearTimeout(this._registrationTimer); 137 | this._registrationTimer = null; 138 | } 139 | 140 | switch (true) 141 | { 142 | case /^1[0-9]{2}$/.test(response.status_code): 143 | // Ignore provisional responses. 144 | break; 145 | case /^2[0-9]{2}$/.test(response.status_code): 146 | this._registering = false; 147 | 148 | if (response.hasHeader('expires')) 149 | { 150 | expires = response.getHeader('expires'); 151 | } 152 | 153 | // Search the Contact pointing to us and update the expires value accordingly. 154 | if (!contacts) 155 | { 156 | debug('no Contact header in response to REGISTER, response ignored'); 157 | break; 158 | } 159 | 160 | while (contacts--) 161 | { 162 | contact = response.parseHeader('contact', contacts); 163 | if (contact.uri.user === this._ua.contact.uri.user) 164 | { 165 | expires = contact.getParam('expires'); 166 | break; 167 | } 168 | else 169 | { 170 | contact = null; 171 | } 172 | } 173 | 174 | if (!contact) 175 | { 176 | debug('no Contact header pointing to us, response ignored'); 177 | break; 178 | } 179 | 180 | if (!expires) 181 | { 182 | expires = this._expires; 183 | } 184 | 185 | // Re-Register or emit an event before the expiration interval has elapsed. 186 | // For that, decrease the expires value. ie: 3 seconds. 187 | this._registrationTimer = setTimeout(() => 188 | { 189 | this._registrationTimer = null; 190 | // If there are no listeners for registrationExpiring, renew registration. 191 | // If there are listeners, let the function listening do the register call. 192 | if (this._ua.listeners('registrationExpiring').length === 0) 193 | { 194 | this.register(); 195 | } 196 | else 197 | { 198 | this._ua.emit('registrationExpiring'); 199 | } 200 | }, (expires * 1000) - 5000); 201 | 202 | // Save gruu values. 203 | if (contact.hasParam('temp-gruu')) 204 | { 205 | this._ua.contact.temp_gruu = contact.getParam('temp-gruu').replace(/"/g, ''); 206 | } 207 | if (contact.hasParam('pub-gruu')) 208 | { 209 | this._ua.contact.pub_gruu = contact.getParam('pub-gruu').replace(/"/g, ''); 210 | } 211 | 212 | if (! this._registered) 213 | { 214 | this._registered = true; 215 | this._ua.registered({ 216 | response 217 | }); 218 | } 219 | break; 220 | // Interval too brief RFC3261 10.2.8. 221 | case /^423$/.test(response.status_code): 222 | if (response.hasHeader('min-expires')) 223 | { 224 | // Increase our registration interval to the suggested minimum. 225 | this._expires = response.getHeader('min-expires'); 226 | // Attempt the registration again immediately. 227 | this.register(); 228 | } 229 | else 230 | { // This response MUST contain a Min-Expires header field 231 | debug('423 response received for REGISTER without Min-Expires'); 232 | this._registrationFailure(response, JsSIP_C.causes.SIP_FAILURE_CODE); 233 | } 234 | break; 235 | default: 236 | { 237 | const cause = Utils.sipErrorCause(response.status_code); 238 | 239 | this._registrationFailure(response, cause); 240 | } 241 | } 242 | } 243 | }); 244 | 245 | this._registering = true; 246 | request_sender.send(); 247 | } 248 | 249 | unregister(options = {}) 250 | { 251 | if (!this._registered) 252 | { 253 | debug('already unregistered'); 254 | 255 | return; 256 | } 257 | 258 | this._registered = false; 259 | 260 | // Clear the registration timer. 261 | if (this._registrationTimer !== null) 262 | { 263 | clearTimeout(this._registrationTimer); 264 | this._registrationTimer = null; 265 | } 266 | 267 | const extraHeaders = this._extraHeaders.slice(); 268 | 269 | if (options.all) 270 | { 271 | extraHeaders.push(`Contact: *${this._extraContactParams}`); 272 | } 273 | else 274 | { 275 | extraHeaders.push(`Contact: ${this._contact};expires=0${this._extraContactParams}`); 276 | } 277 | 278 | extraHeaders.push('Expires: 0'); 279 | 280 | const request = new SIPMessage.OutgoingRequest( 281 | JsSIP_C.REGISTER, this._registrar, this._ua, { 282 | 'to_uri' : this._to_uri, 283 | 'call_id' : this._call_id, 284 | 'cseq' : (this._cseq += 1) 285 | }, extraHeaders); 286 | 287 | const request_sender = new RequestSender(this._ua, request, { 288 | onRequestTimeout : () => 289 | { 290 | this._unregistered(null, JsSIP_C.causes.REQUEST_TIMEOUT); 291 | }, 292 | onTransportError : () => 293 | { 294 | this._unregistered(null, JsSIP_C.causes.CONNECTION_ERROR); 295 | }, 296 | // Increase the CSeq on authentication. 297 | onAuthenticated : () => 298 | { 299 | this._cseq += 1; 300 | }, 301 | onReceiveResponse : (response) => 302 | { 303 | switch (true) 304 | { 305 | case /^1[0-9]{2}$/.test(response.status_code): 306 | // Ignore provisional responses. 307 | break; 308 | case /^2[0-9]{2}$/.test(response.status_code): 309 | this._unregistered(response); 310 | break; 311 | default: 312 | { 313 | const cause = Utils.sipErrorCause(response.status_code); 314 | 315 | this._unregistered(response, cause); 316 | } 317 | } 318 | } 319 | }); 320 | 321 | request_sender.send(); 322 | } 323 | 324 | close() 325 | { 326 | if (this._registered) 327 | { 328 | this.unregister(); 329 | } 330 | } 331 | 332 | 333 | onTransportClosed() 334 | { 335 | this._registering = false; 336 | if (this._registrationTimer !== null) 337 | { 338 | clearTimeout(this._registrationTimer); 339 | this._registrationTimer = null; 340 | } 341 | 342 | if (this._registered) 343 | { 344 | this._registered = false; 345 | this._ua.unregistered({}); 346 | } 347 | } 348 | 349 | _registrationFailure(response, cause) 350 | { 351 | this._registering = false; 352 | this._ua.registrationFailed({ 353 | response : response || null, 354 | cause 355 | }); 356 | 357 | if (this._registered) 358 | { 359 | this._registered = false; 360 | this._ua.unregistered({ 361 | response : response || null, 362 | cause 363 | }); 364 | } 365 | } 366 | 367 | _unregistered(response, cause) 368 | { 369 | this._registering = false; 370 | this._registered = false; 371 | this._ua.unregistered({ 372 | response : response || null, 373 | cause : cause || null 374 | }); 375 | } 376 | }; 377 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/test/test-parser.js: -------------------------------------------------------------------------------- 1 | require('./include/common'); 2 | const JsSIP = require('../'); 3 | 4 | 5 | module.exports = { 6 | 'parse URI' : function(test) 7 | { 8 | const data = 'SIP:%61liCE@versaTICA.Com:6060;TRansport=TCp;Foo=ABc;baz?X-Header-1=AaA1&X-Header-2=BbB&x-header-1=AAA2'; 9 | const uri = JsSIP.URI.parse(data); 10 | 11 | // Parsed data. 12 | test.ok(uri instanceof(JsSIP.URI)); 13 | test.strictEqual(uri.scheme, 'sip'); 14 | test.strictEqual(uri.user, 'aliCE'); 15 | test.strictEqual(uri.host, 'versatica.com'); 16 | test.strictEqual(uri.port, 6060); 17 | test.strictEqual(uri.hasParam('transport'), true); 18 | test.strictEqual(uri.hasParam('nooo'), false); 19 | test.strictEqual(uri.getParam('transport'), 'tcp'); 20 | test.strictEqual(uri.getParam('foo'), 'ABc'); 21 | test.strictEqual(uri.getParam('baz'), null); 22 | test.strictEqual(uri.getParam('nooo'), undefined); 23 | test.deepEqual(uri.getHeader('x-header-1'), [ 'AaA1', 'AAA2' ]); 24 | test.deepEqual(uri.getHeader('X-HEADER-2'), [ 'BbB' ]); 25 | test.strictEqual(uri.getHeader('nooo'), undefined); 26 | test.strictEqual(uri.toString(), 'sip:aliCE@versatica.com:6060;transport=tcp;foo=ABc;baz?X-Header-1=AaA1&X-Header-1=AAA2&X-Header-2=BbB'); 27 | test.strictEqual(uri.toAor(), 'sip:aliCE@versatica.com'); 28 | 29 | // Alter data. 30 | uri.user = 'Iñaki:PASSWD'; 31 | test.strictEqual(uri.user, 'Iñaki:PASSWD'); 32 | test.strictEqual(uri.deleteParam('foo'), 'ABc'); 33 | test.deepEqual(uri.deleteHeader('x-header-1'), [ 'AaA1', 'AAA2' ]); 34 | test.strictEqual(uri.toString(), 'sip:I%C3%B1aki:PASSWD@versatica.com:6060;transport=tcp;baz?X-Header-2=BbB'); 35 | test.strictEqual(uri.toAor(), 'sip:I%C3%B1aki:PASSWD@versatica.com'); 36 | uri.clearParams(); 37 | uri.clearHeaders(); 38 | uri.port = null; 39 | test.strictEqual(uri.toString(), 'sip:I%C3%B1aki:PASSWD@versatica.com'); 40 | test.strictEqual(uri.toAor(), 'sip:I%C3%B1aki:PASSWD@versatica.com'); 41 | 42 | test.done(); 43 | }, 44 | 45 | 'parse NameAddr' : function(test) 46 | { 47 | const data = '"Iñaki ðđøþ" ;QWE=QWE;ASd'; 48 | const name = JsSIP.NameAddrHeader.parse(data); 49 | 50 | // Parsed data. 51 | test.ok(name instanceof(JsSIP.NameAddrHeader)); 52 | test.strictEqual(name.display_name, 'Iñaki ðđøþ'); 53 | test.strictEqual(name.hasParam('qwe'), true); 54 | test.strictEqual(name.hasParam('asd'), true); 55 | test.strictEqual(name.hasParam('nooo'), false); 56 | test.strictEqual(name.getParam('qwe'), 'QWE'); 57 | test.strictEqual(name.getParam('asd'), null); 58 | 59 | const uri = name.uri; 60 | 61 | test.ok(uri instanceof(JsSIP.URI)); 62 | test.strictEqual(uri.scheme, 'sip'); 63 | test.strictEqual(uri.user, 'aliCE'); 64 | test.strictEqual(uri.host, 'versatica.com'); 65 | test.strictEqual(uri.port, 6060); 66 | test.strictEqual(uri.hasParam('transport'), true); 67 | test.strictEqual(uri.hasParam('nooo'), false); 68 | test.strictEqual(uri.getParam('transport'), 'tcp'); 69 | test.strictEqual(uri.getParam('foo'), 'ABc'); 70 | test.strictEqual(uri.getParam('baz'), null); 71 | test.strictEqual(uri.getParam('nooo'), undefined); 72 | test.deepEqual(uri.getHeader('x-header-1'), [ 'AaA1', 'AAA2' ]); 73 | test.deepEqual(uri.getHeader('X-HEADER-2'), [ 'BbB' ]); 74 | test.strictEqual(uri.getHeader('nooo'), undefined); 75 | 76 | // Alter data. 77 | name.display_name = 'Foo Bar'; 78 | test.strictEqual(name.display_name, 'Foo Bar'); 79 | name.display_name = null; 80 | test.strictEqual(name.display_name, null); 81 | test.strictEqual(name.toString(), ';qwe=QWE;asd'); 82 | uri.user = 'Iñaki:PASSWD'; 83 | test.strictEqual(uri.toAor(), 'sip:I%C3%B1aki:PASSWD@versatica.com'); 84 | 85 | test.done(); 86 | }, 87 | 88 | 'parse multiple Contact' : function(test) 89 | { 90 | const data = '"Iñaki @ł€" ;+sip.Instance="abCD", sip:bob@biloxi.COM;headerParam, '; 91 | const contacts = JsSIP.Grammar.parse(data, 'Contact'); 92 | 93 | test.ok(contacts instanceof(Array)); 94 | test.strictEqual(contacts.length, 3); 95 | const c1 = contacts[0].parsed; 96 | const c2 = contacts[1].parsed; 97 | const c3 = contacts[2].parsed; 98 | 99 | // Parsed data. 100 | test.ok(c1 instanceof(JsSIP.NameAddrHeader)); 101 | test.strictEqual(c1.display_name, 'Iñaki @ł€'); 102 | test.strictEqual(c1.hasParam('+sip.instance'), true); 103 | test.strictEqual(c1.hasParam('nooo'), false); 104 | test.strictEqual(c1.getParam('+SIP.instance'), '"abCD"'); 105 | test.strictEqual(c1.getParam('nooo'), undefined); 106 | test.ok(c1.uri instanceof(JsSIP.URI)); 107 | test.strictEqual(c1.uri.scheme, 'sip'); 108 | test.strictEqual(c1.uri.user, '+1234'); 109 | test.strictEqual(c1.uri.host, 'aliax.net'); 110 | test.strictEqual(c1.uri.port, undefined); 111 | test.strictEqual(c1.uri.getParam('transport'), 'ws'); 112 | test.strictEqual(c1.uri.getParam('foo'), undefined); 113 | test.strictEqual(c1.uri.getHeader('X-Header'), undefined); 114 | test.strictEqual(c1.toString(), '"Iñaki @ł€" ;+sip.instance="abCD"'); 115 | 116 | // Alter data. 117 | c1.display_name = '€€€'; 118 | test.strictEqual(c1.display_name, '€€€'); 119 | c1.uri.user = '+999'; 120 | test.strictEqual(c1.uri.user, '+999'); 121 | c1.setParam('+sip.instance', '"zxCV"'); 122 | test.strictEqual(c1.getParam('+SIP.instance'), '"zxCV"'); 123 | c1.setParam('New-Param', null); 124 | test.strictEqual(c1.hasParam('NEW-param'), true); 125 | c1.uri.setParam('New-Param', null); 126 | test.strictEqual(c1.toString(), '"€€€" ;+sip.instance="zxCV";new-param'); 127 | 128 | // Parsed data. 129 | test.ok(c2 instanceof(JsSIP.NameAddrHeader)); 130 | test.strictEqual(c2.display_name, undefined); 131 | test.strictEqual(c2.hasParam('HEADERPARAM'), true); 132 | test.ok(c2.uri instanceof(JsSIP.URI)); 133 | test.strictEqual(c2.uri.scheme, 'sip'); 134 | test.strictEqual(c2.uri.user, 'bob'); 135 | test.strictEqual(c2.uri.host, 'biloxi.com'); 136 | test.strictEqual(c2.uri.port, undefined); 137 | test.strictEqual(c2.uri.hasParam('headerParam'), false); 138 | test.strictEqual(c2.toString(), ';headerparam'); 139 | 140 | // Alter data. 141 | c2.display_name = '@ł€ĸłæß'; 142 | test.strictEqual(c2.toString(), '"@ł€ĸłæß" ;headerparam'); 143 | 144 | // Parsed data. 145 | test.ok(c3 instanceof(JsSIP.NameAddrHeader)); 146 | test.strictEqual(c3.display_name, undefined); 147 | test.ok(c3.uri instanceof(JsSIP.URI)); 148 | test.strictEqual(c3.uri.scheme, 'sip'); 149 | test.strictEqual(c3.uri.user, undefined); 150 | test.strictEqual(c3.uri.host, 'domain.com'); 151 | test.strictEqual(c3.uri.port, 5); 152 | test.strictEqual(c3.uri.hasParam('nooo'), false); 153 | test.strictEqual(c3.toString(), ''); 154 | 155 | // Alter data. 156 | c3.uri.setParam('newUriParam', 'zxCV'); 157 | c3.setParam('newHeaderParam', 'zxCV'); 158 | test.strictEqual(c3.toString(), ';newheaderparam=zxCV'); 159 | 160 | test.done(); 161 | }, 162 | 163 | 'parse Via' : function(test) 164 | { 165 | const data = 'SIP / 3.0 \r\n / UDP [1:ab::FF]:6060 ;\r\n BRanch=1234;Param1=Foo;paRAM2;param3=Bar'; 166 | const via = JsSIP.Grammar.parse(data, 'Via'); 167 | 168 | test.strictEqual(via.protocol, 'SIP'); 169 | test.strictEqual(via.transport, 'UDP'); 170 | test.strictEqual(via.host, '[1:ab::FF]'); 171 | test.strictEqual(via.host_type, 'IPv6'); 172 | test.strictEqual(via.port, 6060); 173 | test.strictEqual(via.branch, '1234'); 174 | test.deepEqual(via.params, { param1: 'Foo', param2: undefined, param3: 'Bar' }); 175 | 176 | test.done(); 177 | }, 178 | 179 | 'parse CSeq' : function(test) 180 | { 181 | const data = '123456 CHICKEN'; 182 | const cseq = JsSIP.Grammar.parse(data, 'CSeq'); 183 | 184 | test.strictEqual(cseq.value, 123456); 185 | test.strictEqual(cseq.method, 'CHICKEN'); 186 | 187 | test.done(); 188 | }, 189 | 190 | 'parse authentication challenge' : function(test) 191 | { 192 | const data = 'Digest realm = "[1:ABCD::abc]", nonce = "31d0a89ed7781ce6877de5cb032bf114", qop="AUTH,autH-INt", algorithm = md5 , stale = TRUE , opaque = "00000188"'; 193 | const auth = JsSIP.Grammar.parse(data, 'challenge'); 194 | 195 | test.strictEqual(auth.realm, '[1:ABCD::abc]'); 196 | test.strictEqual(auth.nonce, '31d0a89ed7781ce6877de5cb032bf114'); 197 | test.deepEqual(auth.qop, [ 'auth', 'auth-int' ]); 198 | test.strictEqual(auth.algorithm, 'MD5'); 199 | test.strictEqual(auth.stale, true); 200 | test.strictEqual(auth.opaque, '00000188'); 201 | 202 | test.done(); 203 | }, 204 | 205 | 'parse Event' : function(test) 206 | { 207 | const data = 'Presence;Param1=QWe;paraM2'; 208 | const event = JsSIP.Grammar.parse(data, 'Event'); 209 | 210 | test.strictEqual(event.event, 'presence'); 211 | test.deepEqual(event.params, { param1: 'QWe', param2: undefined }); 212 | 213 | test.done(); 214 | }, 215 | 216 | 'parse Session-Expires' : function(test) 217 | { 218 | let data, session_expires; 219 | 220 | data = '180;refresher=uac'; 221 | session_expires = JsSIP.Grammar.parse(data, 'Session_Expires'); 222 | 223 | test.strictEqual(session_expires.expires, 180); 224 | test.strictEqual(session_expires.refresher, 'uac'); 225 | 226 | data = '210 ; refresher = UAS ; foo = bar'; 227 | session_expires = JsSIP.Grammar.parse(data, 'Session_Expires'); 228 | 229 | test.strictEqual(session_expires.expires, 210); 230 | test.strictEqual(session_expires.refresher, 'uas'); 231 | 232 | test.done(); 233 | }, 234 | 235 | 'parse Reason' : function(test) 236 | { 237 | let data, reason; 238 | 239 | data = 'SIP ; cause = 488 ; text = "Wrong SDP"'; 240 | reason = JsSIP.Grammar.parse(data, 'Reason'); 241 | 242 | test.strictEqual(reason.protocol, 'sip'); 243 | test.strictEqual(reason.cause, 488); 244 | test.strictEqual(reason.text, 'Wrong SDP'); 245 | 246 | data = 'ISUP; cause=500 ; LALA = foo'; 247 | reason = JsSIP.Grammar.parse(data, 'Reason'); 248 | 249 | test.strictEqual(reason.protocol, 'isup'); 250 | test.strictEqual(reason.cause, 500); 251 | test.strictEqual(reason.text, undefined); 252 | test.strictEqual(reason.params.lala, 'foo'); 253 | 254 | test.done(); 255 | }, 256 | 257 | 'parse host' : function(test) 258 | { 259 | let data, parsed; 260 | 261 | data = 'versatica.com'; 262 | test.ok((parsed = JsSIP.Grammar.parse(data, 'host')) !== -1); 263 | test.strictEqual(parsed.host_type, 'domain'); 264 | 265 | data = 'myhost123'; 266 | test.ok((parsed = JsSIP.Grammar.parse(data, 'host')) !== -1); 267 | test.strictEqual(parsed.host_type, 'domain'); 268 | 269 | data = '1.2.3.4'; 270 | test.ok((parsed = JsSIP.Grammar.parse(data, 'host')) !== -1); 271 | test.strictEqual(parsed.host_type, 'IPv4'); 272 | 273 | data = '[1:0:fF::432]'; 274 | test.ok((parsed = JsSIP.Grammar.parse(data, 'host')) !== -1); 275 | test.strictEqual(parsed.host_type, 'IPv6'); 276 | 277 | data = '1.2.3.444'; 278 | test.ok(JsSIP.Grammar.parse(data, 'host') === -1); 279 | 280 | data = 'iñaki.com'; 281 | test.ok(JsSIP.Grammar.parse(data, 'host') === -1); 282 | 283 | data = '1.2.3.bar.qwe-asd.foo'; 284 | test.ok((parsed = JsSIP.Grammar.parse(data, 'host')) !== -1); 285 | test.strictEqual(parsed.host_type, 'domain'); 286 | 287 | data = '1.2.3.4.bar.qwe-asd.foo'; 288 | test.ok((parsed = JsSIP.Grammar.parse(data, 'host')) !== -1); 289 | test.strictEqual(parsed.host_type, 'domain'); 290 | 291 | test.done(); 292 | }, 293 | 294 | 'parse Refer-To' : function(test) 295 | { 296 | let data, parsed; 297 | 298 | data = 'sip:alice@versatica.com'; 299 | test.ok((parsed = JsSIP.Grammar.parse(data, 'Refer_To')) !== -1); 300 | test.strictEqual(parsed.uri.scheme, 'sip'); 301 | test.strictEqual(parsed.uri.user, 'alice'); 302 | test.strictEqual(parsed.uri.host, 'versatica.com'); 303 | 304 | data = ''; 305 | test.ok((parsed = JsSIP.Grammar.parse(data, 'Refer_To')) !== -1); 306 | test.strictEqual(parsed.uri.scheme, 'sip'); 307 | test.strictEqual(parsed.uri.user, 'bob'); 308 | test.strictEqual(parsed.uri.host, 'versatica.com'); 309 | test.ok(parsed.uri.hasHeader('Accept-Contact') === true); 310 | 311 | test.done(); 312 | }, 313 | 314 | 'parse Replaces' : function(test) 315 | { 316 | let parsed; 317 | 318 | const data = '5t2gpbrbi72v79p1i8mr;to-tag=03aq91cl9n;from-tag=kun98clbf7'; 319 | 320 | test.ok((parsed = JsSIP.Grammar.parse(data, 'Replaces')) !== -1); 321 | test.strictEqual(parsed.call_id, '5t2gpbrbi72v79p1i8mr'); 322 | test.strictEqual(parsed.to_tag, '03aq91cl9n'); 323 | test.strictEqual(parsed.from_tag, 'kun98clbf7'); 324 | 325 | test.done(); 326 | } 327 | }; 328 | -------------------------------------------------------------------------------- /JsSIP-3.0.21/lib/Utils.js: -------------------------------------------------------------------------------- 1 | const JsSIP_C = require('./Constants'); 2 | const URI = require('./URI'); 3 | const Grammar = require('./Grammar'); 4 | 5 | exports.str_utf8_length = (string) => unescape(encodeURIComponent(string)).length; 6 | 7 | // Used by 'hasMethods'. 8 | const isFunction = exports.isFunction = (fn) => 9 | { 10 | if (fn !== undefined) 11 | { 12 | return (Object.prototype.toString.call(fn) === '[object Function]')? true : false; 13 | } 14 | else 15 | { 16 | return false; 17 | } 18 | }; 19 | 20 | exports.isString = (str) => 21 | { 22 | if (str !== undefined) 23 | { 24 | return (Object.prototype.toString.call(str) === '[object String]')? true : false; 25 | } 26 | else 27 | { 28 | return false; 29 | } 30 | }; 31 | 32 | exports.isDecimal = (num) => !isNaN(num) && (parseFloat(num) === parseInt(num, 10)); 33 | 34 | exports.isEmpty = (value) => 35 | { 36 | return (value === null || 37 | value === '' || 38 | value === undefined || 39 | (Array.isArray(value) && value.length === 0) || 40 | (typeof(value) === 'number' && isNaN(value))); 41 | }; 42 | 43 | exports.hasMethods = function(obj, ...methodNames) 44 | { 45 | for (const methodName of methodNames) 46 | { 47 | if (isFunction(obj[methodName])) 48 | { 49 | return false; 50 | } 51 | } 52 | 53 | return true; 54 | }; 55 | 56 | // Used by 'newTag'. 57 | const createRandomToken = exports.createRandomToken = (size, base = 32) => 58 | { 59 | let i, r, token = ''; 60 | 61 | for (i=0; i < size; i++) 62 | { 63 | r = Math.random() * base|0; 64 | token += r.toString(base); 65 | } 66 | 67 | return token; 68 | }; 69 | 70 | exports.newTag = () => createRandomToken(10); 71 | 72 | // http://stackoverflow.com/users/109538/broofa. 73 | exports.newUUID = () => 74 | { 75 | const UUID = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => 76 | { 77 | const r = Math.random()*16|0, v = c === 'x' ? r : ((r&0x3)|0x8); 78 | 79 | 80 | return v.toString(16); 81 | }); 82 | 83 | return UUID; 84 | }; 85 | 86 | exports.hostType = (host) => 87 | { 88 | if (!host) 89 | { 90 | return; 91 | } 92 | else 93 | { 94 | host = Grammar.parse(host, 'host'); 95 | if (host !== -1) 96 | { 97 | return host.host_type; 98 | } 99 | } 100 | }; 101 | 102 | /** 103 | * Hex-escape a SIP URI user. 104 | * Don't hex-escape ':' (%3A), '+' (%2B), '?' (%3F"), '/' (%2F). 105 | * 106 | * Used by 'normalizeTarget'. 107 | */ 108 | const escapeUser = exports.escapeUser = (user) => 109 | encodeURIComponent(decodeURIComponent(user)) 110 | .replace(/%3A/ig, ':') 111 | .replace(/%2B/ig, '+') 112 | .replace(/%3F/ig, '?') 113 | .replace(/%2F/ig, '/'); 114 | 115 | /** 116 | * Normalize SIP URI. 117 | * NOTE: It does not allow a SIP URI without username. 118 | * Accepts 'sip', 'sips' and 'tel' URIs and convert them into 'sip'. 119 | * Detects the domain part (if given) and properly hex-escapes the user portion. 120 | * If the user portion has only 'tel' number symbols the user portion is clean of 'tel' visual separators. 121 | */ 122 | exports.normalizeTarget = (target, domain) => 123 | { 124 | // If no target is given then raise an error. 125 | if (!target) 126 | { 127 | return; 128 | // If a URI instance is given then return it. 129 | } 130 | else if (target instanceof URI) 131 | { 132 | return target; 133 | 134 | // If a string is given split it by '@': 135 | // - Last fragment is the desired domain. 136 | // - Otherwise append the given domain argument. 137 | } 138 | else if (typeof target === 'string') 139 | { 140 | const target_array = target.split('@'); 141 | let target_user; 142 | let target_domain; 143 | 144 | switch (target_array.length) 145 | { 146 | case 1: 147 | if (!domain) 148 | { 149 | return; 150 | } 151 | target_user = target; 152 | target_domain = domain; 153 | break; 154 | case 2: 155 | target_user = target_array[0]; 156 | target_domain = target_array[1]; 157 | break; 158 | default: 159 | target_user = target_array.slice(0, target_array.length-1).join('@'); 160 | target_domain = target_array[target_array.length-1]; 161 | } 162 | 163 | // Remove the URI scheme (if present). 164 | target_user = target_user.replace(/^(sips?|tel):/i, ''); 165 | 166 | // Remove 'tel' visual separators if the user portion just contains 'tel' number symbols. 167 | if (/^[-.()]*\+?[0-9\-.()]+$/.test(target_user)) 168 | { 169 | target_user = target_user.replace(/[-.()]/g, ''); 170 | } 171 | 172 | // Build the complete SIP URI. 173 | target = `${JsSIP_C.SIP}:${escapeUser(target_user)}@${target_domain}`; 174 | 175 | // Finally parse the resulting URI. 176 | let uri; 177 | 178 | if ((uri = URI.parse(target))) 179 | { 180 | return uri; 181 | } 182 | else 183 | { 184 | return; 185 | } 186 | } 187 | else 188 | { 189 | return; 190 | } 191 | }; 192 | 193 | exports.headerize = (string) => 194 | { 195 | const exceptions = { 196 | 'Call-Id' : 'Call-ID', 197 | 'Cseq' : 'CSeq', 198 | 'Www-Authenticate' : 'WWW-Authenticate' 199 | }; 200 | 201 | const name = string.toLowerCase() 202 | .replace(/_/g, '-') 203 | .split('-'); 204 | let hname = ''; 205 | const parts = name.length; 206 | let part; 207 | 208 | for (part = 0; part < parts; part++) 209 | { 210 | if (part !== 0) 211 | { 212 | hname +='-'; 213 | } 214 | hname += name[part].charAt(0).toUpperCase()+name[part].substring(1); 215 | } 216 | if (exceptions[hname]) 217 | { 218 | hname = exceptions[hname]; 219 | } 220 | 221 | return hname; 222 | }; 223 | 224 | exports.sipErrorCause = (status_code) => 225 | { 226 | for (const cause in JsSIP_C.SIP_ERROR_CAUSES) 227 | { 228 | if (JsSIP_C.SIP_ERROR_CAUSES[cause].indexOf(status_code) !== -1) 229 | { 230 | return JsSIP_C.causes[cause]; 231 | } 232 | } 233 | 234 | return JsSIP_C.causes.SIP_FAILURE_CODE; 235 | }; 236 | 237 | /** 238 | * Generate a random Test-Net IP (http://tools.ietf.org/html/rfc5735) 239 | */ 240 | exports.getRandomTestNetIP = () => 241 | { 242 | function getOctet(from, to) 243 | { 244 | return Math.floor((Math.random() * (to-from+1)) + from); 245 | } 246 | 247 | return `192.0.2.${getOctet(1, 254)}`; 248 | }; 249 | 250 | // MD5 (Message-Digest Algorithm) http://www.webtoolkit.info. 251 | exports.calculateMD5 = (string) => 252 | { 253 | function rotateLeft(lValue, iShiftBits) 254 | { 255 | return (lValue<>>(32-iShiftBits)); 256 | } 257 | 258 | function addUnsigned(lX, lY) 259 | { 260 | const lX8 = (lX & 0x80000000); 261 | const lY8 = (lY & 0x80000000); 262 | const lX4 = (lX & 0x40000000); 263 | const lY4 = (lY & 0x40000000); 264 | const lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF); 265 | 266 | if (lX4 & lY4) 267 | { 268 | return (lResult ^ 0x80000000 ^ lX8 ^ lY8); 269 | } 270 | if (lX4 | lY4) 271 | { 272 | if (lResult & 0x40000000) 273 | { 274 | return (lResult ^ 0xC0000000 ^ lX8 ^ lY8); 275 | } 276 | else 277 | { 278 | return (lResult ^ 0x40000000 ^ lX8 ^ lY8); 279 | } 280 | } 281 | else 282 | { 283 | return (lResult ^ lX8 ^ lY8); 284 | } 285 | } 286 | 287 | function doF(x, y, z) 288 | { 289 | return (x & y) | ((~x) & z); 290 | } 291 | 292 | function doG(x, y, z) 293 | { 294 | return (x & z) | (y & (~z)); 295 | } 296 | 297 | function doH(x, y, z) 298 | { 299 | return (x ^ y ^ z); 300 | } 301 | 302 | function doI(x, y, z) 303 | { 304 | return (y ^ (x | (~z))); 305 | } 306 | 307 | function doFF(a, b, c, d, x, s, ac) 308 | { 309 | a = addUnsigned(a, addUnsigned(addUnsigned(doF(b, c, d), x), ac)); 310 | 311 | return addUnsigned(rotateLeft(a, s), b); 312 | } 313 | 314 | function doGG(a, b, c, d, x, s, ac) 315 | { 316 | a = addUnsigned(a, addUnsigned(addUnsigned(doG(b, c, d), x), ac)); 317 | 318 | return addUnsigned(rotateLeft(a, s), b); 319 | } 320 | 321 | function doHH(a, b, c, d, x, s, ac) 322 | { 323 | a = addUnsigned(a, addUnsigned(addUnsigned(doH(b, c, d), x), ac)); 324 | 325 | return addUnsigned(rotateLeft(a, s), b); 326 | } 327 | 328 | function doII(a, b, c, d, x, s, ac) 329 | { 330 | a = addUnsigned(a, addUnsigned(addUnsigned(doI(b, c, d), x), ac)); 331 | 332 | return addUnsigned(rotateLeft(a, s), b); 333 | } 334 | 335 | function convertToWordArray(str) 336 | { 337 | let lWordCount; 338 | const lMessageLength = str.length; 339 | const lNumberOfWords_temp1=lMessageLength + 8; 340 | const lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64; 341 | const lNumberOfWords = (lNumberOfWords_temp2+1)*16; 342 | const lWordArray = new Array(lNumberOfWords-1); 343 | let lBytePosition = 0; 344 | let lByteCount = 0; 345 | 346 | while (lByteCount < lMessageLength) 347 | { 348 | lWordCount = (lByteCount-(lByteCount % 4))/4; 349 | lBytePosition = (lByteCount % 4)*8; 350 | lWordArray[lWordCount] = (lWordArray[lWordCount] | 351 | (str.charCodeAt(lByteCount)<>>29; 359 | 360 | return lWordArray; 361 | } 362 | 363 | function wordToHex(lValue) 364 | { 365 | let wordToHexValue='', wordToHexValue_temp='', lByte, lCount; 366 | 367 | for (lCount = 0; lCount<=3; lCount++) 368 | { 369 | lByte = (lValue>>>(lCount*8)) & 255; 370 | wordToHexValue_temp = `0${lByte.toString(16)}`; 371 | wordToHexValue = wordToHexValue + 372 | wordToHexValue_temp.substr(wordToHexValue_temp.length-2, 2); 373 | } 374 | 375 | return wordToHexValue; 376 | } 377 | 378 | function utf8Encode(str) 379 | { 380 | str = str.replace(/\r\n/g, '\n'); 381 | let utftext = ''; 382 | 383 | for (let n = 0; n < str.length; n++) 384 | { 385 | const c = str.charCodeAt(n); 386 | 387 | if (c < 128) 388 | { 389 | utftext += String.fromCharCode(c); 390 | } 391 | else if ((c > 127) && (c < 2048)) 392 | { 393 | utftext += String.fromCharCode((c >> 6) | 192); 394 | utftext += String.fromCharCode((c & 63) | 128); 395 | } 396 | else 397 | { 398 | utftext += String.fromCharCode((c >> 12) | 224); 399 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 400 | utftext += String.fromCharCode((c & 63) | 128); 401 | } 402 | } 403 | 404 | return utftext; 405 | } 406 | 407 | let x=[]; 408 | let k, AA, BB, CC, DD, a, b, c, d; 409 | const S11=7, S12=12, S13=17, S14=22; 410 | const S21=5, S22=9, S23=14, S24=20; 411 | const S31=4, S32=11, S33=16, S34=23; 412 | const S41=6, S42=10, S43=15, S44=21; 413 | 414 | string = utf8Encode(string); 415 | 416 | x = convertToWordArray(string); 417 | 418 | a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476; 419 | 420 | for (k=0; k 499 | { 500 | if (!stream) 501 | { 502 | return; 503 | } 504 | 505 | // Latest spec states that MediaStream has no stop() method and instead must 506 | // call stop() on every MediaStreamTrack. 507 | try 508 | { 509 | let tracks; 510 | 511 | if (stream.getTracks) 512 | { 513 | tracks = stream.getTracks(); 514 | for (const track of tracks) 515 | { 516 | track.stop(); 517 | } 518 | } 519 | else 520 | { 521 | tracks = stream.getAudioTracks(); 522 | for (const track of tracks) 523 | { 524 | track.stop(); 525 | } 526 | tracks = stream.getVideoTracks(); 527 | for (const track of tracks) 528 | { 529 | track.stop(); 530 | } 531 | } 532 | } 533 | catch (error) 534 | { 535 | // Deprecated by the spec, but still in use. 536 | // NOTE: In Temasys IE plugin stream.stop is a callable 'object'. 537 | if (typeof stream.stop === 'function' || typeof stream.stop === 'object') 538 | { 539 | stream.stop(); 540 | } 541 | } 542 | }; 543 | 544 | exports.cloneArray = (array) => 545 | { 546 | return (array && array.slice()) || []; 547 | }; 548 | --------------------------------------------------------------------------------