├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── app.js ├── config └── default.json.example ├── data ├── rtp-transcoding.json └── srtp-transcoding.json ├── docs └── BUILD.md ├── lib ├── call-session.js ├── middleware.js ├── register.js ├── registrar.js ├── subscriber.js └── utils.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | 7 | config.json 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | .eslint* -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "attach", 10 | "name": "Attach to remote", 11 | "address": "webrtc01.drachtio.org", 12 | "port": 9229, 13 | "localRoot": "${workspaceFolder}", 14 | "remoteRoot": "/home/deploy/drachtio-rtpengine-webrtcproxy" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dave Horton 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # drachtio-rtpengine-webrtcproxy 2 | 3 | An open-source webrtc proxy server built using [drachtio](https://drachtio.org) and [rtpengine](https://github.com/sipwise/rtpengine) that allows webrtc clients to place or receive calls from their VoIP provider. The server can optionally be configured to handle authentication against SIP trunks requiring digest authentication (otherwise, digest challenges are passed back to the client). 4 | 5 | ## Installation 6 | 7 | As mentioned above, drachtio and rtpengine are pre-requisites and you will need each installed and reachable on hosted servers with public IP addresses. You can run both on the same server, or run them on different servers. 8 | 9 | For further details on building drachtio and rtpengine servers, please refer to [docs/BUILD.md](docs/BUILD.md) 10 | 11 | Once you have drachtio and rtpengine installed and running, you can install, configure and run the webrtc proxy app. 12 | 13 | ```bash 14 | $ git clone https://github.com/davehorton/drachtio-rtpengine-webrtcproxy.git 15 | $ cd drachtio-rtpengine-webrtcproxy 16 | $ npm install 17 | ``` 18 | 19 | Before starting the app you will need to copy `config/default.json.example` to `config/default.json` and modify it to your needs (details below). At point, simply start the app: 20 | 21 | ```bash 22 | $ npm start 23 | ``` 24 | ### Configuration 25 | The example configuration file (in config/default.json.example) looks like this: 26 | ``` 27 | { 28 | "drachtio": { 29 | "host": "127.0.0.1", 30 | "port": 9022, 31 | "secret": "cymru" 32 | }, 33 | "rtpengine": { 34 | "host": "127.0.0.1", 35 | "port": 22222, 36 | "local-port": 2223 37 | }, 38 | "credentials": [ 39 | { 40 | "trunk": "my.voipprovider.com", 41 | "auth": { 42 | "username": "", 43 | "password": "" 44 | } 45 | } 46 | ] 47 | } 48 | ``` 49 | The information is provided as follows: 50 | * `drachtio`: This is the location where the drachtio server is running and listening to connections from applications. In the example, the application would be running on the drachtio server itself, and thus the 'host' value is '127.0.0.1'. You could alternatively have the application running on a different server and connect to the drachtio server across the network, if you wish. 51 | * `rtpengine`: Similarly, this is the information needed for the application to connect to the rtpengine server, using the 'ng' protocol. 52 | * `credentials`: this is an optionaly array of SIP trunks that the webrtc proxy holds authorization credentials for, such that when an INVITE is being sent to that trunk and is subsequently challenged with a 401/407, the webrtc proxy will generate a new INVITE using the credentials provided. 53 | 54 | ## Basic operation 55 | 56 | The basic operation of the application is to enable *outbound* calls from webrtc clients to SIP endpoints or PSTN phone numbers. 57 | 58 | Once started the application listens on the configured interfaces for SIP INVITEs over secure web sockets (wss). The drachtio server should be configured to listen for wss traffic on a public IP address, and most commonly will be configured to listen on the default wss port 443 (although you can configure it to listen on an alternate port if desired). 59 | 60 | When an incoming SIP INVITE is received, the application will allocate endpoints on the rtpengine server to transcode the media stream from SRTP to RTP and will generate an INVITE offering RTP to the Request-URI of the SIP INVITE. An SRTP-to-RTP call will thus be established. 61 | 62 | If the host part of the Request-URI matches one of the configured trunks, the webrtc application will handle digest challenges from the far end. 63 | 64 | Calls can be therefore be made to the PSTN (i.e. regular phone numbers) using a VoIP carrier, or to any reachable SIP URI (e.g. an IP PBX). 65 | 66 | ## Advanced features 67 | 68 | The application also supports *inbound* calls to registered webrtc clients. These would be RTP to SRTP calls (i.e. the reverse of outbound calls) and require the use of a third-party registrar or VoIP carrier that you can register sip credentials with. 69 | 70 | To receive inbound calls, a webrtc client should send REGISTER requests. The application will forward the REGISTER requests on to the hosted service (specified in the Request-URI of the received REGISTER message) over udp. Any subsequent INVITEs received from the VoIP carrier for that registered user will be sent to the webrtc client over wss. 71 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const Srf = require('drachtio-srf') ; 3 | const srf = new Srf() ; 4 | const logger = require('pino')(); 5 | const Register = require('./lib/register'); 6 | const Registrar = require('./lib/registrar'); 7 | const Subscriber = require('./lib/subscriber'); 8 | const registrar = new Registrar(logger) ; 9 | const register = new Register(logger) ; 10 | const subscriber = new Subscriber(logger); 11 | const config = require('config') ; 12 | const { hostport, opts = {} } = config.get('rtpengine'); 13 | assert.ok(Array.isArray(hostport) && hostport.length, 'config: rtpengine.hostport must be array'); 14 | const { getRtpEngine, setRtpEngines } = require('@jambonz/rtpengine-utils')([], logger, opts); 15 | 16 | /** 17 | * Set the array of rtpengines, each entry a host:port that rtpengine is listening on for ng 18 | * NB: this could be called at any time with a new array of rtpengines, as they go down / come up 19 | */ 20 | setRtpEngines(hostport); 21 | 22 | srf.locals = { 23 | ...srf.locals, 24 | registrar, 25 | getRtpEngine 26 | }; 27 | 28 | srf.connect(config.get('drachtio')) 29 | .on('connect', (err, hostport) => { 30 | console.log(`connected to drachtio listening for SIP on hostport ${hostport}`) ; 31 | }) 32 | .on('error', (err) => { 33 | console.error(`Error connecting to drachtio server: ${err.message}`) ; 34 | }); 35 | 36 | const { 37 | initLocals, 38 | identifyCallDirection 39 | } = require('./lib/middleware')(srf, logger); 40 | const CallSession = require('./lib/call-session'); 41 | 42 | srf.use('invite', [initLocals, identifyCallDirection]); 43 | srf.invite((req, res) => { 44 | const session = new CallSession(req, res); 45 | session.connect(); 46 | }); 47 | 48 | register.start(srf, registrar); 49 | subscriber.start(srf, registrar); 50 | -------------------------------------------------------------------------------- /config/default.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "drachtio": { 3 | "host": "127.0.0.1", 4 | "port": 9022, 5 | "secret": "cymru" 6 | }, 7 | "rtpengine": { 8 | "hostport": ["127.0.0.1:22222"], 9 | "opts": { 10 | "protocol": "udp", 11 | "timeout": 2000 12 | } 13 | }, 14 | "credentials": [ 15 | { 16 | "trunk": "your.voipprovider.com", 17 | "auth": { 18 | "username": "", 19 | "password": "" 20 | } 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /data/rtp-transcoding.json: -------------------------------------------------------------------------------- 1 | { 2 | "transport protocol": "RTP/AVP", 3 | "DTLS": "off", 4 | "ICE": "remove", 5 | "rtcp-mux": [ 6 | "demux" 7 | ], 8 | "flags": [ 9 | "SDES-no" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /data/srtp-transcoding.json: -------------------------------------------------------------------------------- 1 | { 2 | "transport-protocol": "UDP/TLS/RTP/SAVPF", 3 | "ICE": "force", 4 | "rtcp-mux": [ 5 | "require" 6 | ], 7 | "flags": [ 8 | "SDES-no", 9 | "generate mid", 10 | "strip-extmap" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /docs/BUILD.md: -------------------------------------------------------------------------------- 1 | # Building drachtio and rtpengine 2 | 3 | The webrtc proxy app requires the use of both drachtio and rtpengine. They may be running on the same, or different servers. 4 | 5 | ## rtpengine 6 | 7 | Instructions for building rtpengine are covered [on its github page](https://github.com/sipwise/rtpengine). 8 | 9 | Additionally, an ansible role that automates the build process [can be found here](https://github.com/davehorton/ansible-role-rtpengine). 10 | 11 | Finally, [a docker image is available](https://hub.docker.com/r/davehorton/rtpengine/) (`docker pull davehorton/rtpengine:latest`), but this is recommended only for test purposes. 12 | 13 | ## drachtio 14 | 15 | Instructions for building drachtio are covered [on its github page](https://github.com/davehorton/drachtio-server/tree/develop). 16 | > Note: please build and use the 'develop' branch when building the drachtio-server from source. 17 | 18 | Additionally, an ansible role that automates the build process [can be found here](https://github.com/davehorton/ansible-role-drachtio). 19 | 20 | Finally, [a docker image is available](https://hub.docker.com/r/drachtio/drachtio-server/) (`docker pull drachtio/drachtio-server:latest`), but this is recommended only for test purposes. 21 | 22 | Additional detailed information about how to configure the drachtio server, as well as the APIs and frameworks for building apps can be found at [drachtio.org](https://drachtio.org). 23 | 24 | ### fail2ban 25 | 26 | Optionally, you may wish to configure fail2ban to block SIP spam traffic. An ansible role to install and configure fail2ban for drachtio [can be found here](https://github.com/davehorton/ansible-role-fail2ban-drachtio). 27 | 28 | ## A few notes on configuring the drachtio server 29 | You should configure the drachtio server to listen for both udp and wss traffic. In most cases, you will want to configure the server to listen on default ports (5060 for udp, 443 for wss), though you can certainly listen on non-standard ports if you like. 30 | 31 | You will need TLS certificates in order to run wss. We recommend [letsencrypt](https://letsencrypt.org/) as an easy and free way to generate them, but you may use any CA of your choosing. 32 | 33 | Below is shown an example drachtio configuration file (which would be found at `/etc/drachtio.conf.xml`) that illustrates how to configure your certificate information and SIP ports. 34 | > Note: the example below shows the configuration for a server that has a local (private) IP and a public IP that is assigned over the top by a hosted provider -- e.g. as with google or AWS clouds. If you instead have a public IP address that is explicitly bound to a local interface (e.g. as Digital Ocean does), then you would eliminate the "external-ip" property and simply put the public IP address in the sip uri. 35 | 36 | ```xml 37 | 38 | 39 | 40 | 10.132.0.28 41 | 42 | 43 | 44 | 45 | sip:10.132.0.28;transport=udp 46 | sips:10.132.0.28:443;transport=wss 47 | 48 | 49 | 50 | /etc/letsencrypt/live//privkey.pem 51 | /etc/letsencrypt/live//cert.pem 52 | /etc/letsencrypt/live//chain.pem 53 | 54 | 55 | 56 | 57 |
58 | sip-cli 59 | sipcli 60 | friendly-scanner 61 |
62 |
63 | sipvicious 64 |
65 |
66 | 67 | 4096 68 | 69 |
70 | 71 | true 72 | 73 | 74 | 75 | 76 | 77 | /var/log/drachtio/drachtio.log 78 | /var/log/drachtio/archive 79 | 5 80 | 10 81 | true 82 | 83 | 84 | 85 | 3 86 | 87 | 88 | info 89 | 90 | 91 |
92 | ``` -------------------------------------------------------------------------------- /lib/call-session.js: -------------------------------------------------------------------------------- 1 | const Emitter = require('events'); 2 | const { 3 | makeRtpEngineOpts, 4 | removeWebrtcAttributes, 5 | makeModifyDialogOpts 6 | } = require('./utils'); 7 | const { forwardInDialogRequests } = require('drachtio-fn-b2b-sugar'); 8 | const { parseUri, SipError } = require('drachtio-srf'); 9 | const config = require('config'); 10 | const debug = require('debug')('drachtio:rtpengine-webrtcproxy'); 11 | const calls = new Map(); 12 | 13 | const createHeaders = (registrar, callid) => { 14 | // check if we have a call-id / cseq that we used previously on a 407-challenged INVITE 15 | const obj = registrar.getNextCallIdAndCSeq(callid); 16 | if (obj) { 17 | registrar.removeTransaction(callid); 18 | return obj; 19 | } 20 | return { 'CSeq': '1 INVITE' }; 21 | }; 22 | 23 | const makeReplacesStr = (dlg) => { 24 | var s = ''; 25 | if (dlg.type === 'uas') { 26 | s = encodeURIComponent(`${dlg.sip.callId};to-tag=${dlg.sip.localTag};from-tag=${dlg.sip.remoteTag}`); 27 | } 28 | else { 29 | s = encodeURIComponent(`${dlg.sip.callId};to-tag=${dlg.sip.remoteTag};from-tag=${dlg.sip.localTag}`); 30 | } 31 | return s; 32 | }; 33 | 34 | class CallSession extends Emitter { 35 | constructor(req, res) { 36 | super(); 37 | this.req = req; 38 | this.res = res; 39 | this.srf = req.srf; 40 | this.logger = req.locals.logger; 41 | 42 | const { getRtpEngine, registrar } = req.srf.locals; 43 | this.getRtpEngine = getRtpEngine; 44 | this.registrar = registrar; 45 | } 46 | 47 | async connect() { 48 | const { protocol, source_address, uri } = this.req; 49 | this.logger.info( 50 | `received invite from ${protocol}/${source_address}:${uri} with request uri ${uri}`); 51 | const engine = this.getRtpEngine(); 52 | if (!engine) { 53 | this.logger.info('No available rtpengines, rejecting call!'); 54 | return this.res.send(480); 55 | } 56 | debug(`got engine: ${JSON.stringify(engine)}`); 57 | const { 58 | offer, 59 | answer, 60 | del, 61 | blockMedia, 62 | unblockMedia, 63 | blockDTMF, 64 | unblockDTMF, 65 | subscribeDTMF, 66 | unsubscribeDTMF 67 | } = engine; 68 | this.offer = offer; 69 | this.answer = answer; 70 | this.del = del; 71 | this.blockMedia = blockMedia; 72 | this.unblockMedia = unblockMedia; 73 | this.blockDTMF = blockDTMF; 74 | this.unblockDTMF = unblockDTMF; 75 | this.subscribeDTMF = subscribeDTMF; 76 | this.unsubscribeDTMF = unsubscribeDTMF; 77 | 78 | const { callDirection, remoteUri, callid } = this.req.locals; 79 | const parsedUri = parseUri(uri); 80 | const trunk = parsedUri.host; 81 | let inviteSent; 82 | 83 | //outbound is a call from webrtc (public) toward the SBC (private). 84 | const rtpDirection = 'outbound' === callDirection ? ['public', 'private'] : ['private', 'public']; 85 | this.rtpEngineOpts = makeRtpEngineOpts(this.req, ('outbound' === callDirection), ('inbound' === callDirection)); 86 | this.rtpEngineResource = { destroy: this.del.bind(null, this.rtpEngineOpts.common) }; 87 | const opts = { 88 | ...this.rtpEngineOpts.common, 89 | ...this.rtpEngineOpts.uac.mediaOpts, 90 | 'from-tag': this.rtpEngineOpts.uas.tag, 91 | 'direction': rtpDirection, 92 | sdp: this.req.body 93 | }; 94 | 95 | try { 96 | const response = await this.offer(opts); 97 | this.logger.debug({ opts, response }, 'response from rtpengine to offer'); 98 | if ('ok' !== response.result) { 99 | this.logger.error({}, `rtpengine offer failed with ${JSON.stringify(response)}`); 100 | throw new Error(`failed allocating endpoint for callID ${callid} from rtpengine: ${JSON.stringify(response)}`); 101 | } 102 | 103 | if ('outbound' === callDirection && response.sdp) { 104 | response.sdp = removeWebrtcAttributes(response.sdp); 105 | } 106 | 107 | const headers = createHeaders(this.registrar, callid); 108 | 109 | // check to see if we are sending to a trunk that we hold sip credentials for 110 | let t; 111 | if (trunk && config.has('credentials')) { 112 | t = config.get('credentials').find((c) => c.trunk === trunk); 113 | if (t) this.logger.info(`we will be handling auth challenges for this call to ${trunk}`); 114 | } 115 | 116 | const callOpts = { 117 | headers, 118 | ...(t && { auth: t.auth }), 119 | localSdpB: response.sdp, 120 | proxyRequestHeaders: [ 121 | 'from', 122 | 'to', 123 | 'proxy-authorization', 124 | 'authorization', 125 | 'supported', 126 | 'allow', 127 | 'content-type', 128 | 'user-agent', 129 | 'Diversion' 130 | ], 131 | proxyResponseHeaders: [ 132 | 'proxy-authenticate', 133 | 'www-authenticate', 134 | 'accept', 135 | 'allow', 136 | 'allow-events' 137 | ] 138 | }; 139 | 140 | this.logger.info({ callOpts }, 'sending INVITE to B'); 141 | const { uas, uac } = await this.srf.createB2BUA(this.req, this.res, remoteUri, { 142 | ...callOpts, 143 | localSdpA: async (sdp, res) => { 144 | this.rtpEngineOpts.uac.tag = res.getParsedHeader('To').params.tag; 145 | const opts = { 146 | ...this.rtpEngineOpts.common, 147 | ...this.rtpEngineOpts.uas.mediaOpts, 148 | 'from-tag': this.rtpEngineOpts.uas.tag, 149 | 'to-tag': this.rtpEngineOpts.uac.tag, 150 | sdp 151 | }; 152 | const response = await this.answer(opts); 153 | if ('ok' !== response.result) { 154 | this.logger.error(`rtpengine answer failed with ${JSON.stringify(response)}`); 155 | throw new Error('rtpengine failed: answer'); 156 | } 157 | return response.sdp; 158 | } 159 | }, { 160 | cbRequest: (err, req) => inviteSent = req 161 | }); 162 | 163 | // successfully connected 164 | this._setHandlers({ uas, uac }); 165 | } catch (err) { 166 | this.rtpEngineResource.destroy().catch((err) => this.logger.info({ err }, 'Error destroying rtpe after failure')); 167 | if (err instanceof SipError && [401, 407].includes(err.status)) { 168 | this.logger.info(`invite challenged with ${err.status}`); 169 | this.registrar.addTransaction({ 170 | aCallId: callid, 171 | bCallId: inviteSent.get('Call-Id'), 172 | bCseq: inviteSent.get('CSeq') 173 | }); 174 | } 175 | else if (487 === err.status) { 176 | this.logger.info('caller hung up'); 177 | } 178 | else { 179 | this.logger.error({ err }, `Error connecting call with callID ${callid}, ${err}`); 180 | this.res.send(503); 181 | } 182 | } 183 | } 184 | 185 | _dumpKeys() { 186 | 187 | for (var [key, value] of calls.entries()) { 188 | this.logger.info({ key, value }, '_dumpKeys'); 189 | } 190 | } 191 | 192 | _setHandlers({ uas, uac }) { 193 | this.emit('connected'); 194 | this.uas = uas; 195 | this.uac = uac; 196 | 197 | const key = makeReplacesStr(uas); 198 | const value = makeReplacesStr(uac); 199 | calls.set(key, value); 200 | this.logger.info(`after adding call there are now ${calls.size} calls in progress`); 201 | [uas, uac].forEach((dlg) => { 202 | dlg.on('destroy', async () => { 203 | const other = dlg.other; 204 | this.rtpEngineResource.destroy().catch((err) => { }); 205 | try { 206 | await other.destroy(); 207 | } catch (err) { } 208 | 209 | /* de-link the 2 Dialogs for GC */ 210 | dlg.removeAllListeners(); 211 | other.removeAllListeners(); 212 | dlg.other = null; 213 | other.other = null; 214 | 215 | calls.delete(key); 216 | this.logger.info(`call ended with normal termination, there are ${calls.size} active calls`); 217 | this.srf.endSession(this.req); 218 | }); 219 | }); 220 | 221 | uas.on('refer', this._handleRefer.bind(this, uas)); 222 | uac.on('refer', this._handleRefer.bind(this, uac)); 223 | 224 | uas.on('info', this._handleInfo.bind(this, uas)); 225 | uac.on('info', this._handleInfo.bind(this, uac)); 226 | 227 | uas.on('modify', this._handleReinvite.bind(this, uas)); 228 | uac.on('modify', this._handleReinvite.bind(this, uac)); 229 | 230 | // default forwarding of other request types 231 | forwardInDialogRequests(uas, ['notify', 'options', 'message']); 232 | } 233 | 234 | async _handleReinvite(dlg, req, res) { 235 | try { 236 | this.logger.info(`received reinvite on ${dlg.type} leg`); 237 | const fromTag = dlg.type === 'uas' ? this.rtpEngineOpts.uas.tag : this.rtpEngineOpts.uac.tag; 238 | const toTag = dlg.type === 'uas' ? this.rtpEngineOpts.uac.tag : this.rtpEngineOpts.uas.tag; 239 | const offerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uac.mediaOpts : this.rtpEngineOpts.uas.mediaOpts; 240 | const answerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uas.mediaOpts : this.rtpEngineOpts.uac.mediaOpts; 241 | let opts = { 242 | ...this.rtpEngineOpts.common, 243 | ...offerMedia, 244 | 'from-tag': fromTag, 245 | 'to-tag': toTag, 246 | sdp: req.body, 247 | }; 248 | 249 | let response = await this.offer(opts); 250 | if ('ok' !== response.result) { 251 | res.send(488); 252 | throw new Error(`_onReinvite: rtpengine failed: offer: ${JSON.stringify(response)}`); 253 | } 254 | this.logger.debug({ opts, response }, 'sent offer for reinvite to rtpengine'); 255 | if (JSON.stringify(offerMedia).includes('ICE\":\"remove')) { 256 | response.sdp = removeWebrtcAttributes(response.sdp); 257 | } 258 | 259 | let ackFunc; 260 | let optsSdp; 261 | if (!req.body) { 262 | const modifyOpts = makeModifyDialogOpts(req, true); 263 | this.logger.info({ modifyOpts }, 'calling dlg.modify with opts'); 264 | const { sdp, ack } = await dlg.other.modify(response.sdp, modifyOpts); 265 | this.logger.info({ sdp }, 'return from dlg.modify with sdp'); 266 | optsSdp = sdp; 267 | ackFunc = ack 268 | } 269 | else { 270 | const modifyOpts = makeModifyDialogOpts(req, false); 271 | optsSdp = await dlg.other.modify(response.sdp, modifyOpts); 272 | } 273 | 274 | opts = { 275 | ...this.rtpEngineOpts.common, 276 | ...answerMedia, 277 | 'from-tag': fromTag, 278 | 'to-tag': toTag, 279 | sdp: optsSdp 280 | }; 281 | response = await this.answer(opts); 282 | if ('ok' !== response.result) { 283 | res.send(488); 284 | throw new Error(`_onReinvite: rtpengine failed: ${JSON.stringify(response)}`); 285 | } 286 | 287 | if (JSON.stringify(answerMedia).includes('ICE\":\"remove')) { 288 | response.sdp = removeWebrtcAttributes(response.sdp); 289 | } 290 | res.send(200, { body: response.sdp }); 291 | 292 | if (ackFunc) { 293 | // set listener for ACK, so that we can use that SDP to create the ACK for the other leg. 294 | dlg.once('ack', this._handleAck.bind(this, dlg, ackFunc, optsSdp)); 295 | } 296 | } catch (err) { 297 | this.logger.error({ err }, 'Error handling reinvite'); 298 | } 299 | } 300 | 301 | async _handleInfo(dlg, req, res) { 302 | this.logger.info(`received info with content-type: ${req.get('Content-Type')}`); 303 | 304 | try { 305 | const immutableHdrs = ['via', 'from', 'to', 'call-id', 'cseq', 'max-forwards', 'content-length']; 306 | const headers = {}; 307 | Object.keys(req.headers).forEach((h) => { 308 | if (!immutableHdrs.includes(h)) headers[h] = req.headers[h]; 309 | }); 310 | const response = await dlg.other.request({ method: 'INFO', headers, body: req.body }); 311 | const responseHeaders = {}; 312 | if (response.has('Content-Type')) { 313 | Object.assign(responseHeaders, { 'Content-Type': response.get('Content-Type') }); 314 | } 315 | res.send(response.status, { headers: responseHeaders, body: response.body }); 316 | } catch (err) { 317 | this.logger.info({ err }, `Error handing INFO request on ${dlg.type} leg`); 318 | } 319 | } 320 | 321 | async _handleRefer(dlg, req, res) { 322 | this.logger.info('Received Refer on ', dlg.type); 323 | 324 | let referTo = req.get('Refer-To'); 325 | const arr = /(.*)Replaces=(.*)>/.exec(referTo); 326 | 327 | // for attended transfer: fixup the Replaces part of the Refer-To header 328 | if (arr) { 329 | const key = arr[2]; 330 | if (calls.has(key)) { 331 | referTo = `${arr[1]}Replaces=${calls.get(key)}>`; 332 | } 333 | else { 334 | this.logger.error( 335 | `attended transfer for callID ${req.get('Call-Id')} but we can't find ${key} in ${calls.size} entries`); 336 | } 337 | } 338 | 339 | try { 340 | const reqHeaders = req.get('Authorization') ? 341 | { 'Authorization': req.get('Authorization'), 'Refer-To': referTo, 'Referred-By': req.get('Referred-By') } : 342 | { 'Refer-To': referTo, 'Referred-By': req.get('Referred-By') }; 343 | const response = await dlg.other.request({ 344 | method: 'REFER', 345 | headers: reqHeaders 346 | }); 347 | if (response.status === 401) { 348 | const resHeaders = { 'headers': { 'www-authenticate': response.get('www-authenticate') } }; 349 | res.send(response.status, response.reason, resHeaders); 350 | } 351 | else { 352 | res.send(response.status); 353 | } 354 | } catch (err) { 355 | this.logger.error(err, `Error handling REFER for callID ${req.get('Call-Id')}`); 356 | } 357 | } 358 | 359 | /** 360 | * Handle ACK for late offer reInvite 361 | * @param {*} dlg dialog receiving the re-INVITE 362 | * @param {*} ack function to send the ACK w/sdp 363 | * @param {*} offerSdp sdp sent in the 200 OK 364 | * @param {*} req sip request 365 | 366 | */ 367 | async _handleAck(dlg, ack, offerSdp, req) { 368 | this.logger.info('Received ACK with late offer: ', offerSdp); 369 | 370 | try { 371 | let fromTag = dlg.other.sip.remoteTag; 372 | let toTag = dlg.other.sip.localTag; 373 | if (dlg.type === 'uac') { 374 | fromTag = dlg.sip.localTag; 375 | toTag = dlg.sip.remoteTag; 376 | } 377 | const offerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uas.mediaOpts : this.rtpEngineOpts.uac.mediaOpts; 378 | let answerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uac.mediaOpts : this.rtpEngineOpts.uas.mediaOpts; 379 | //if uas is webrtc facing, we need to keep that side as the active ssl role, so use passive in the ACK sdp 380 | if (dlg.type === 'uas' && JSON.stringify(answerMedia).includes('SAVPF')) { 381 | let mediaStringified = JSON.stringify(answerMedia); 382 | mediaStringified = mediaStringified.replace('SAVPF\"', 'SAVPF\",\"DTLS\":\"passive\"'); 383 | answerMedia = JSON.parse(mediaStringified); 384 | } 385 | 386 | const optsOffer = { 387 | ...this.rtpEngineOpts.common, 388 | ...offerMedia, 389 | 'from-tag': fromTag, 390 | 'to-tag': toTag, 391 | sdp: offerSdp 392 | }; 393 | //send an offer first so that rtpEngine knows that DTLS fingerprint needs to be in the answer sdp. 394 | const response = await this.offer(optsOffer); 395 | if ('ok' !== response.result) { 396 | throw new Error(`_handleAck: rtpengine failed: offer: ${JSON.stringify(response)}`); 397 | } 398 | 399 | const optsAnswer = { 400 | ...this.rtpEngineOpts.common, 401 | ...answerMedia, 402 | 'from-tag': fromTag, 403 | 'to-tag': toTag, 404 | sdp: req.body 405 | }; 406 | const ackResponse = await this.answer(optsAnswer); 407 | if ('ok' !== ackResponse.result) { 408 | throw new Error(`_handleAck ${req.get('Call-Id')}: rtpengine failed: answer: ${JSON.stringify(ackResponse)}`); 409 | } 410 | if (JSON.stringify(answerMedia).includes('ICE\":\"remove')) { 411 | ackResponse.sdp = removeWebrtcAttributes(ackResponse.sdp); 412 | } 413 | //send the ACK with sdp 414 | ack(ackResponse.sdp); 415 | 416 | } catch (err) { 417 | this.logger.error(err, `Error handling ACK with callId ${req.get('Call-Id')}`); 418 | } 419 | } 420 | } 421 | 422 | 423 | module.exports = CallSession; 424 | -------------------------------------------------------------------------------- /lib/middleware.js: -------------------------------------------------------------------------------- 1 | const {parseUri} = require('drachtio-srf'); 2 | 3 | module.exports = function(srf, logger) { 4 | 5 | const initLocals = (req, res, next) => { 6 | const callid = req.get('Call-Id'); 7 | const from = req.getParsedHeader('From'); 8 | req.locals = {logger: logger.child({callid}), from, callid}; 9 | next(); 10 | }; 11 | 12 | const identifyCallDirection = (req, res, next) => { 13 | const {registrar} = req.srf.locals; 14 | const {logger} = req.locals; 15 | const parsedUri = parseUri(req.uri); 16 | const user = parsedUri.user ; 17 | 18 | let remoteUri = req.uri ; 19 | let callDirection = 'outbound'; 20 | if (registrar.hasUser(user)) { 21 | const details = registrar.getUser(user) ; 22 | callDirection = 'inbound' ; 23 | remoteUri = details.uri ; 24 | logger.debug(`inbound call with details: ${JSON.stringify(details)}`) ; 25 | } 26 | else logger.debug(`outbound call to: ${remoteUri}`); 27 | 28 | req.locals = { 29 | ...req.locals, 30 | callDirection, 31 | remoteUri 32 | }; 33 | 34 | next(); 35 | }; 36 | 37 | return { 38 | initLocals, 39 | identifyCallDirection 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /lib/register.js: -------------------------------------------------------------------------------- 1 | const mw = require('drachtio-mw-registration-parser') ; 2 | const parseUri = require('drachtio-srf').parseUri ; 3 | const {stringifyContact, isValidRegister} = require('./utils') ; 4 | const debug = require('debug')('drachtio:rtpengine-webrtcproxy') ; 5 | 6 | class Register { 7 | 8 | constructor(logger) { 9 | this._logger = logger; 10 | } 11 | 12 | get logger() { 13 | return this._logger; 14 | } 15 | 16 | start(srf, registrar) { 17 | srf.register(mw, (req, res) => { 18 | const callid = req.get('Call-Id'); 19 | 20 | debug(`UAC registering: ${req.protocol}/${req.source_address}:${req.source_port} with uri ${req.uri}`) ; 21 | 22 | if (!isValidRegister(req)) { 23 | this.logger.info(`invalid register request: ${req.get('Call-Id')} ${req.get('CSeq')}`) ; 24 | return res.send(503); 25 | } 26 | const instanceId = req.registration.contact[0].params['+sip.instance'] ; 27 | const regId = req.registration.contact[0].params['reg-id'] ; 28 | const uri = parseUri(req.uri) ; 29 | const headers = {} ; 30 | 31 | // check if we have a call-id / cseq that we are using for this transaction 32 | const obj = registrar.getNextCallIdAndCSeq(callid) ; 33 | if (obj) { 34 | Object.assign(headers, obj) ; 35 | } 36 | else { 37 | Object.assign(headers, {'CSeq': '1 REGISTER'}) ; 38 | } 39 | 40 | ['from', 'to', 'authorization', 'supported', 'allow', 'user-agent'].forEach((hdr) => { 41 | if (req.has(hdr)) headers[hdr] = req.get(hdr) ; 42 | }) ; 43 | 44 | const uacContact = req.getParsedHeader('Contact') ; 45 | const from = req.getParsedHeader('From') ; 46 | const user = parseUri(from.uri).user ; 47 | 48 | // NB: drachtio server will replace 'localhost' appropriately 49 | headers.contact = `;expires=${req.registration.expires}` ; 50 | 51 | srf.request({ 52 | uri: req.uri, 53 | method: req.method, 54 | headers 55 | }, (err, request) => { 56 | if (err) { 57 | return this.logger.info(`Error forwarding register to ${uri.host}: ${err}`); 58 | } 59 | request.on('response', (response) => { 60 | const headers = {} ; 61 | ['www-authenticate'].forEach((hdr) => { 62 | if (response.has(hdr)) headers[hdr] = response.get(hdr); 63 | }); 64 | 65 | // construct a contact header 66 | let expires, contact ; 67 | if (response.has('Contact')) { 68 | contact = response.getParsedHeader('Contact') ; 69 | expires = parseInt(contact[0].params.expires) ; 70 | uacContact[0].params.expires = expires ; 71 | 72 | headers.contact = stringifyContact(uacContact) ; 73 | } 74 | 75 | res.send(response.status, response.reason, {headers}) ; 76 | if (200 === response.status) { 77 | const arr = /^(sip:.*);transport=(.*)$/.exec(req.registration.contact[0].uri); 78 | if (arr) { 79 | const via = req.getParsedHeader('Via') ; 80 | const transport = (via[0].protocol).toLowerCase() ; 81 | 82 | if ('register' === req.registration.type) { 83 | registrar.addUser(user, { 84 | expires: Date.now() + (expires * 1000), 85 | transport: transport, 86 | source_address: req.source_address, 87 | source_port: req.source_port, 88 | uri: arr[1], 89 | instanceId:instanceId, 90 | regId: regId, 91 | aor: req.registration.aor 92 | }); 93 | if (!registrar.hasTransaction(callid)) { 94 | registrar.addTransaction({ 95 | aCallId: callid, 96 | bCallId: response.get('Call-Id'), 97 | bCseq: response.get('CSeq') 98 | }) ; 99 | } 100 | } 101 | else { 102 | registrar.removeUser(user) ; 103 | registrar.removeTransaction(req.get('call-id')) ; 104 | } 105 | } 106 | } 107 | else if ([401, 407].includes(response.status)) { 108 | registrar.addTransaction({ 109 | aCallId: callid, 110 | bCallId: response.get('Call-Id'), 111 | bCseq: response.get('CSeq') 112 | }) ; 113 | } 114 | else { 115 | debug(`register failed with ${response.status}`) ; 116 | } 117 | }) ; 118 | }); 119 | }); 120 | } 121 | } 122 | 123 | module.exports = exports = Register ; 124 | -------------------------------------------------------------------------------- /lib/registrar.js: -------------------------------------------------------------------------------- 1 | class Registrar { 2 | 3 | constructor(logger) { 4 | this._logger = logger; 5 | this.users = new Map() ; 6 | this.transactions = new Map() ; 7 | } 8 | 9 | get logger() { 10 | return this._logger; 11 | } 12 | 13 | addUser(user, obj) { 14 | this.users.set(user, obj) ; 15 | } 16 | 17 | removeUser(user) { 18 | this.users.delete(user) ; 19 | } 20 | 21 | hasUser(user) { 22 | return this.users.has(user) ; 23 | } 24 | 25 | getUser(user) { 26 | return this.users.get(user) ; 27 | 28 | } 29 | 30 | addTransaction(obj) { 31 | this.transactions.set(obj.aCallId, obj) ; 32 | } 33 | 34 | getNextCallIdAndCSeq(callid) { 35 | const obj = this.transactions.get(callid) ; 36 | if (obj) { 37 | const arr = /^(\d+)\s+(.*)$/.exec(obj.bCseq) ; 38 | if (arr) { 39 | obj.bCseq = (++arr[1]) + ' ' + (arr[2]); 40 | return { 41 | 'Call-Id': obj.bCallId, 42 | 'CSeq': obj.bCseq 43 | }; 44 | } 45 | } 46 | } 47 | 48 | hasTransaction(callid) { 49 | return this.transactions.has(callid) ; 50 | } 51 | 52 | removeTransaction(callid) { 53 | this.transactions.delete(callid); 54 | } 55 | } 56 | 57 | module.exports = Registrar ; 58 | -------------------------------------------------------------------------------- /lib/subscriber.js: -------------------------------------------------------------------------------- 1 | const parseUri = require('drachtio-srf').parseUri ; 2 | const SipError = require('drachtio-srf').SipError; 3 | 4 | class Subscriber { 5 | 6 | constructor(logger) { 7 | this._logger = logger; 8 | } 9 | 10 | get logger() { 11 | return this._logger; 12 | } 13 | 14 | start(srf, registrar) { 15 | srf.subscribe((req, res) => { 16 | 17 | this.logger.info(`UAC subscribing: ${req.protocol}/${req.source_address}:${req.source_port}`) ; 18 | 19 | // only registered users are allowed to subscribe 20 | const from = req.getParsedHeader('from') ; 21 | const fromUser = parseUri(from.uri).user ; 22 | const callid = req.get('Call-Id'); 23 | 24 | if (!registrar.hasUser(fromUser)) { 25 | this.logger.fino(`invalid/unknown user ${fromUser} attempting to subscribe`) ; 26 | return res.send(503); 27 | } 28 | 29 | // check if we have a call-id / cseq that we used previously on a 401-challenged SUBSCRIBE 30 | const headers = {} ; 31 | const obj = registrar.getNextCallIdAndCSeq(callid) ; 32 | if (obj) { 33 | Object.assign(headers, obj) ; 34 | registrar.removeTransaction(callid) ; 35 | } 36 | else { 37 | Object.assign(headers, {'CSeq': '1 INVITE'}) ; 38 | } 39 | 40 | let subscribeSent ; 41 | 42 | return srf.createB2BUA(req, res, req.uri, { 43 | method: 'SUBSCRIBE', 44 | headers: headers, 45 | proxyRequestHeaders: ['event', 'expires', 'allow', 'authorization', 'accept'], 46 | proxyResponseHeaders: ['subscription-state', 'expires', 'allow-events', 'www-authenticate'] 47 | }, { 48 | cbRequest: (err, req) => subscribeSent = req 49 | }) 50 | .catch((err) => { 51 | if (err instanceof SipError && [401, 407].includes(err.status)) { 52 | if (subscribeSent) { 53 | registrar.addTransaction({ 54 | aCallId: callid, 55 | bCallId: subscribeSent.get('Call-Id'), 56 | bCseq: subscribeSent.get('CSeq') 57 | }) ; 58 | } 59 | } 60 | else { 61 | this.logger.error('Error establishing subscribe dialog: ', err.status || err) ; 62 | } 63 | }); 64 | }); 65 | } 66 | } 67 | 68 | module.exports = Subscriber ; 69 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const rtpCharacteristics = require('../data/rtp-transcoding'); 2 | const srtpCharacteristics = require('../data/srtp-transcoding'); 3 | 4 | function stringifyContact(h) { 5 | let s = `<${h[0].uri}>`; 6 | Object.keys(h[0].params).forEach((p) => s += `;${p}=${h[0].params[p]}`); 7 | return s; 8 | } 9 | 10 | function isValidRegister(req) { 11 | if (!req.has('Contact')) return false ; 12 | 13 | const contact = req.getParsedHeader('Contact') ; 14 | if (!contact || !contact.length) return false ; 15 | if (!req.registration) return false ; 16 | 17 | if (!req.registration.contact || req.registration.contact.length !== 1) return false ; 18 | 19 | return true ; 20 | } 21 | 22 | function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp) { 23 | const from = req.getParsedHeader('from'); 24 | const dstOpts = dstIsUsingSrtp ? srtpCharacteristics : rtpCharacteristics; 25 | const srctOpts = srcIsUsingSrtp ? srtpCharacteristics : rtpCharacteristics; 26 | const common = { 27 | 'call-id': req.get('Call-ID'), 28 | 'replace': ['origin', 'session-connection'] 29 | }; 30 | return { 31 | common, 32 | uas: { 33 | tag: from.params.tag, 34 | mediaOpts: srctOpts 35 | }, 36 | uac: { 37 | tag: null, 38 | mediaOpts: dstOpts 39 | } 40 | }; 41 | } 42 | 43 | /** 44 | * Function to remove all webrtc attributes from the sdp before sending a request or response 45 | * to the pure SIP and RTP side. 46 | * If a transfer is later done to a non-webrtc number, these attributes will not be relevant 47 | * and the removal of them causes Chrome to remove the receiving stream, resulting in one way audio. 48 | * @param sdp sdp to modify 49 | * @returns sdp without ssrc and msid attributes 50 | */ 51 | 52 | function removeWebrtcAttributes(sdp) { 53 | if (!sdp.includes('a=ssrc')) { 54 | return sdp; 55 | } 56 | let sdpArray = sdp.split(/\r\n/); 57 | sdpArray = sdpArray.filter((attribute) => !attribute.includes('a=ssrc') && !attribute.includes('a=msid')); 58 | return sdpArray.join('\r\n'); 59 | } 60 | 61 | /** 62 | * Function to generate the options to be passed to dialog.modify(). 63 | * @param req the request to use to modify the dialog 64 | * @param noAck boolean identifying if the ACK should be handled outside of the modify funciton 65 | * @returns modifyOpts 66 | */ 67 | function makeModifyDialogOpts(req, noAck) { 68 | let modifyOpts = { noAck }; 69 | 70 | //Retain the P-Asserted-Identity header that BW adds on the reInvite to perform a warm transfer. 71 | if (req.get('P-Asserted-Identity') && (req.get('Privacy') && (req.get('Privacy') == 'none'))) { 72 | modifyOpts = { 73 | headers: { 'P-Asserted-Identity': req.get('P-Asserted-Identity') }, 74 | noAck: noAck 75 | }; 76 | } 77 | return modifyOpts; 78 | } 79 | 80 | module.exports = { 81 | stringifyContact, 82 | isValidRegister, 83 | makeRtpEngineOpts, 84 | removeWebrtcAttributes, 85 | makeModifyDialogOpts 86 | }; 87 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drachtio-rtpengine-webrtcproxy", 3 | "version": "0.1.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@eslint/eslintrc": { 8 | "version": "1.4.1", 9 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", 10 | "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", 11 | "requires": { 12 | "ajv": "^6.12.4", 13 | "debug": "^4.3.2", 14 | "espree": "^9.4.0", 15 | "globals": "^13.19.0", 16 | "ignore": "^5.2.0", 17 | "import-fresh": "^3.2.1", 18 | "js-yaml": "^4.1.0", 19 | "minimatch": "^3.1.2", 20 | "strip-json-comments": "^3.1.1" 21 | } 22 | }, 23 | "@humanwhocodes/config-array": { 24 | "version": "0.11.8", 25 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", 26 | "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", 27 | "requires": { 28 | "@humanwhocodes/object-schema": "^1.2.1", 29 | "debug": "^4.1.1", 30 | "minimatch": "^3.0.5" 31 | } 32 | }, 33 | "@humanwhocodes/module-importer": { 34 | "version": "1.0.1", 35 | "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 36 | "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" 37 | }, 38 | "@humanwhocodes/object-schema": { 39 | "version": "1.2.1", 40 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", 41 | "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" 42 | }, 43 | "@jambonz/rtpengine-utils": { 44 | "version": "0.3.11", 45 | "resolved": "https://registry.npmjs.org/@jambonz/rtpengine-utils/-/rtpengine-utils-0.3.11.tgz", 46 | "integrity": "sha512-+WDZrAje2ww7zBY2+DYgPdiDg+Uh9qC4B/+EfQ/ghjseausaUWhUCDuJOdFQtHXA1qtEiMxTD1argeC5DtJBYQ==", 47 | "requires": { 48 | "debug": "^4.3.1", 49 | "rtpengine-client": "^0.3.9", 50 | "ws": "^8.5.0" 51 | }, 52 | "dependencies": { 53 | "rtpengine-client": { 54 | "version": "0.3.9", 55 | "resolved": "https://registry.npmjs.org/rtpengine-client/-/rtpengine-client-0.3.9.tgz", 56 | "integrity": "sha512-e22oEIHK2fazQdcQNSCNlM/87xi5BHCSiqcOKbxSsGL69KEA1nPdIjihnDpzxhZltYGUokqTpb5aTz/hZhlYjA==", 57 | "requires": { 58 | "bencode": "^2.0.3", 59 | "uuid": "^8.2.0", 60 | "ws": "^7.4.4" 61 | }, 62 | "dependencies": { 63 | "ws": { 64 | "version": "7.5.9", 65 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", 66 | "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" 67 | } 68 | } 69 | }, 70 | "uuid": { 71 | "version": "8.3.2", 72 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 73 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" 74 | } 75 | } 76 | }, 77 | "@nodelib/fs.scandir": { 78 | "version": "2.1.5", 79 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 80 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 81 | "requires": { 82 | "@nodelib/fs.stat": "2.0.5", 83 | "run-parallel": "^1.1.9" 84 | } 85 | }, 86 | "@nodelib/fs.stat": { 87 | "version": "2.0.5", 88 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 89 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" 90 | }, 91 | "@nodelib/fs.walk": { 92 | "version": "1.2.8", 93 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 94 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 95 | "requires": { 96 | "@nodelib/fs.scandir": "2.1.5", 97 | "fastq": "^1.6.0" 98 | } 99 | }, 100 | "acorn": { 101 | "version": "8.8.1", 102 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", 103 | "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" 104 | }, 105 | "acorn-jsx": { 106 | "version": "5.3.2", 107 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 108 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" 109 | }, 110 | "ajv": { 111 | "version": "6.12.6", 112 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 113 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 114 | "requires": { 115 | "fast-deep-equal": "^3.1.1", 116 | "fast-json-stable-stringify": "^2.0.0", 117 | "json-schema-traverse": "^0.4.1", 118 | "uri-js": "^4.2.2" 119 | } 120 | }, 121 | "ansi-regex": { 122 | "version": "5.0.1", 123 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 124 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 125 | }, 126 | "ansi-styles": { 127 | "version": "3.2.1", 128 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 129 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 130 | "requires": { 131 | "color-convert": "^1.9.0" 132 | } 133 | }, 134 | "any-base": { 135 | "version": "1.1.0", 136 | "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", 137 | "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" 138 | }, 139 | "argparse": { 140 | "version": "2.0.1", 141 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 142 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" 143 | }, 144 | "balanced-match": { 145 | "version": "1.0.2", 146 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 147 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 148 | }, 149 | "bencode": { 150 | "version": "2.0.3", 151 | "resolved": "https://registry.npmjs.org/bencode/-/bencode-2.0.3.tgz", 152 | "integrity": "sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w==" 153 | }, 154 | "brace-expansion": { 155 | "version": "1.1.11", 156 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 157 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 158 | "requires": { 159 | "balanced-match": "^1.0.0", 160 | "concat-map": "0.0.1" 161 | } 162 | }, 163 | "callsites": { 164 | "version": "3.1.0", 165 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 166 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" 167 | }, 168 | "chalk": { 169 | "version": "2.4.2", 170 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 171 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 172 | "requires": { 173 | "ansi-styles": "^3.2.1", 174 | "escape-string-regexp": "^1.0.5", 175 | "supports-color": "^5.3.0" 176 | } 177 | }, 178 | "color-convert": { 179 | "version": "1.9.3", 180 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 181 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 182 | "requires": { 183 | "color-name": "1.1.3" 184 | } 185 | }, 186 | "color-name": { 187 | "version": "1.1.3", 188 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 189 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 190 | }, 191 | "concat-map": { 192 | "version": "0.0.1", 193 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 194 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 195 | }, 196 | "config": { 197 | "version": "3.3.8", 198 | "resolved": "https://registry.npmjs.org/config/-/config-3.3.8.tgz", 199 | "integrity": "sha512-rFzF6VESOdp7wAXFlB9IOZI4ouL05g3A03v2eRcTHj2JBQaTNJ40zhAUl5wRbWHqLZ+uqp/7OE0BWWtAVgrong==", 200 | "requires": { 201 | "json5": "^2.2.1" 202 | } 203 | }, 204 | "core-util-is": { 205 | "version": "1.0.2", 206 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 207 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 208 | }, 209 | "cross-spawn": { 210 | "version": "7.0.3", 211 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 212 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 213 | "requires": { 214 | "path-key": "^3.1.0", 215 | "shebang-command": "^2.0.0", 216 | "which": "^2.0.1" 217 | } 218 | }, 219 | "debug": { 220 | "version": "4.3.4", 221 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 222 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 223 | "requires": { 224 | "ms": "2.1.2" 225 | } 226 | }, 227 | "deep-is": { 228 | "version": "0.1.4", 229 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 230 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" 231 | }, 232 | "delegates": { 233 | "version": "0.1.0", 234 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-0.1.0.tgz", 235 | "integrity": "sha512-tPYr58xmVlUWcL8zPk6ZAxP6XqiYx5IIn395dkeER12JmMy8P6ipGKnUvgD++g8+uCaALfs/CRERixvKBu1pow==" 236 | }, 237 | "doctrine": { 238 | "version": "3.0.0", 239 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 240 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 241 | "requires": { 242 | "esutils": "^2.0.2" 243 | } 244 | }, 245 | "drachtio-fn-b2b-sugar": { 246 | "version": "0.0.12", 247 | "resolved": "https://registry.npmjs.org/drachtio-fn-b2b-sugar/-/drachtio-fn-b2b-sugar-0.0.12.tgz", 248 | "integrity": "sha512-FKPAcEMJTYKDrd9DJUCc4VHnY/c65HOO9k8XqVNognF9T02hKEjGuBCM4Da9ipyfiHmVRuECwj0XNvZ361mkVQ==" 249 | }, 250 | "drachtio-mw-registration-parser": { 251 | "version": "0.0.2", 252 | "resolved": "https://registry.npmjs.org/drachtio-mw-registration-parser/-/drachtio-mw-registration-parser-0.0.2.tgz", 253 | "integrity": "sha1-Bmlv43Wvt/68AI8oaCJWukEHWAE=" 254 | }, 255 | "drachtio-srf": { 256 | "version": "4.5.21", 257 | "resolved": "https://registry.npmjs.org/drachtio-srf/-/drachtio-srf-4.5.21.tgz", 258 | "integrity": "sha512-c8F0opl3EpkeUNuorVRscg+n6yl535YAEK69BvTkzLvpSdSX98lUhjQCWVs7f6Lmm/WX1YgQR9/7+D7M60Cjtg==", 259 | "requires": { 260 | "debug": "^3.2.7", 261 | "delegates": "^0.1.0", 262 | "eslint": "^8.30.0", 263 | "eslint-plugin-promise": "^6.1.1", 264 | "node-noop": "^0.0.1", 265 | "only": "^0.0.2", 266 | "sdp-transform": "^2.14.1", 267 | "short-uuid": "^4.2.0", 268 | "sip-methods": "^0.3.0", 269 | "sip-status": "^0.1.0", 270 | "utils-merge": "^1.0.0", 271 | "uuid-random": "^1.3.2" 272 | }, 273 | "dependencies": { 274 | "debug": { 275 | "version": "3.2.7", 276 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 277 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 278 | "requires": { 279 | "ms": "^2.1.1" 280 | } 281 | } 282 | } 283 | }, 284 | "end-of-stream": { 285 | "version": "1.4.4", 286 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 287 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 288 | "requires": { 289 | "once": "^1.4.0" 290 | } 291 | }, 292 | "escape-string-regexp": { 293 | "version": "1.0.5", 294 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 295 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 296 | }, 297 | "eslint": { 298 | "version": "8.31.0", 299 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", 300 | "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", 301 | "requires": { 302 | "@eslint/eslintrc": "^1.4.1", 303 | "@humanwhocodes/config-array": "^0.11.8", 304 | "@humanwhocodes/module-importer": "^1.0.1", 305 | "@nodelib/fs.walk": "^1.2.8", 306 | "ajv": "^6.10.0", 307 | "chalk": "^4.0.0", 308 | "cross-spawn": "^7.0.2", 309 | "debug": "^4.3.2", 310 | "doctrine": "^3.0.0", 311 | "escape-string-regexp": "^4.0.0", 312 | "eslint-scope": "^7.1.1", 313 | "eslint-utils": "^3.0.0", 314 | "eslint-visitor-keys": "^3.3.0", 315 | "espree": "^9.4.0", 316 | "esquery": "^1.4.0", 317 | "esutils": "^2.0.2", 318 | "fast-deep-equal": "^3.1.3", 319 | "file-entry-cache": "^6.0.1", 320 | "find-up": "^5.0.0", 321 | "glob-parent": "^6.0.2", 322 | "globals": "^13.19.0", 323 | "grapheme-splitter": "^1.0.4", 324 | "ignore": "^5.2.0", 325 | "import-fresh": "^3.0.0", 326 | "imurmurhash": "^0.1.4", 327 | "is-glob": "^4.0.0", 328 | "is-path-inside": "^3.0.3", 329 | "js-sdsl": "^4.1.4", 330 | "js-yaml": "^4.1.0", 331 | "json-stable-stringify-without-jsonify": "^1.0.1", 332 | "levn": "^0.4.1", 333 | "lodash.merge": "^4.6.2", 334 | "minimatch": "^3.1.2", 335 | "natural-compare": "^1.4.0", 336 | "optionator": "^0.9.1", 337 | "regexpp": "^3.2.0", 338 | "strip-ansi": "^6.0.1", 339 | "strip-json-comments": "^3.1.0", 340 | "text-table": "^0.2.0" 341 | }, 342 | "dependencies": { 343 | "ansi-styles": { 344 | "version": "4.3.0", 345 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 346 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 347 | "requires": { 348 | "color-convert": "^2.0.1" 349 | } 350 | }, 351 | "chalk": { 352 | "version": "4.1.2", 353 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 354 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 355 | "requires": { 356 | "ansi-styles": "^4.1.0", 357 | "supports-color": "^7.1.0" 358 | } 359 | }, 360 | "color-convert": { 361 | "version": "2.0.1", 362 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 363 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 364 | "requires": { 365 | "color-name": "~1.1.4" 366 | } 367 | }, 368 | "color-name": { 369 | "version": "1.1.4", 370 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 371 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 372 | }, 373 | "escape-string-regexp": { 374 | "version": "4.0.0", 375 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 376 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" 377 | }, 378 | "has-flag": { 379 | "version": "4.0.0", 380 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 381 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 382 | }, 383 | "supports-color": { 384 | "version": "7.2.0", 385 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 386 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 387 | "requires": { 388 | "has-flag": "^4.0.0" 389 | } 390 | } 391 | } 392 | }, 393 | "eslint-plugin-promise": { 394 | "version": "6.1.1", 395 | "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", 396 | "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==" 397 | }, 398 | "eslint-scope": { 399 | "version": "7.1.1", 400 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", 401 | "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", 402 | "requires": { 403 | "esrecurse": "^4.3.0", 404 | "estraverse": "^5.2.0" 405 | } 406 | }, 407 | "eslint-utils": { 408 | "version": "3.0.0", 409 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", 410 | "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", 411 | "requires": { 412 | "eslint-visitor-keys": "^2.0.0" 413 | }, 414 | "dependencies": { 415 | "eslint-visitor-keys": { 416 | "version": "2.1.0", 417 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", 418 | "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" 419 | } 420 | } 421 | }, 422 | "eslint-visitor-keys": { 423 | "version": "3.3.0", 424 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", 425 | "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" 426 | }, 427 | "espree": { 428 | "version": "9.4.1", 429 | "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", 430 | "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", 431 | "requires": { 432 | "acorn": "^8.8.0", 433 | "acorn-jsx": "^5.3.2", 434 | "eslint-visitor-keys": "^3.3.0" 435 | } 436 | }, 437 | "esquery": { 438 | "version": "1.4.0", 439 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", 440 | "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", 441 | "requires": { 442 | "estraverse": "^5.1.0" 443 | } 444 | }, 445 | "esrecurse": { 446 | "version": "4.3.0", 447 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 448 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 449 | "requires": { 450 | "estraverse": "^5.2.0" 451 | } 452 | }, 453 | "estraverse": { 454 | "version": "5.3.0", 455 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 456 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" 457 | }, 458 | "esutils": { 459 | "version": "2.0.3", 460 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 461 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" 462 | }, 463 | "fast-deep-equal": { 464 | "version": "3.1.3", 465 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 466 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 467 | }, 468 | "fast-json-parse": { 469 | "version": "1.0.3", 470 | "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", 471 | "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==" 472 | }, 473 | "fast-json-stable-stringify": { 474 | "version": "2.1.0", 475 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 476 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 477 | }, 478 | "fast-levenshtein": { 479 | "version": "2.0.6", 480 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 481 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" 482 | }, 483 | "fast-safe-stringify": { 484 | "version": "1.2.3", 485 | "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-1.2.3.tgz", 486 | "integrity": "sha512-QJYT/i0QYoiZBQ71ivxdyTqkwKkQ0oxACXHYxH2zYHJEgzi2LsbjgvtzTbLi1SZcF190Db2YP7I7eTsU2egOlw==" 487 | }, 488 | "fastq": { 489 | "version": "1.14.0", 490 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", 491 | "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", 492 | "requires": { 493 | "reusify": "^1.0.4" 494 | } 495 | }, 496 | "file-entry-cache": { 497 | "version": "6.0.1", 498 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", 499 | "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", 500 | "requires": { 501 | "flat-cache": "^3.0.4" 502 | } 503 | }, 504 | "find-up": { 505 | "version": "5.0.0", 506 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 507 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 508 | "requires": { 509 | "locate-path": "^6.0.0", 510 | "path-exists": "^4.0.0" 511 | } 512 | }, 513 | "flat-cache": { 514 | "version": "3.0.4", 515 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", 516 | "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", 517 | "requires": { 518 | "flatted": "^3.1.0", 519 | "rimraf": "^3.0.2" 520 | } 521 | }, 522 | "flatstr": { 523 | "version": "1.0.12", 524 | "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", 525 | "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" 526 | }, 527 | "flatted": { 528 | "version": "3.2.7", 529 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", 530 | "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" 531 | }, 532 | "fs.realpath": { 533 | "version": "1.0.0", 534 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 535 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 536 | }, 537 | "glob": { 538 | "version": "7.2.3", 539 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 540 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 541 | "requires": { 542 | "fs.realpath": "^1.0.0", 543 | "inflight": "^1.0.4", 544 | "inherits": "2", 545 | "minimatch": "^3.1.1", 546 | "once": "^1.3.0", 547 | "path-is-absolute": "^1.0.0" 548 | } 549 | }, 550 | "glob-parent": { 551 | "version": "6.0.2", 552 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 553 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 554 | "requires": { 555 | "is-glob": "^4.0.3" 556 | } 557 | }, 558 | "globals": { 559 | "version": "13.19.0", 560 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", 561 | "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", 562 | "requires": { 563 | "type-fest": "^0.20.2" 564 | } 565 | }, 566 | "grapheme-splitter": { 567 | "version": "1.0.4", 568 | "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", 569 | "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" 570 | }, 571 | "has-flag": { 572 | "version": "3.0.0", 573 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 574 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 575 | }, 576 | "ignore": { 577 | "version": "5.2.4", 578 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", 579 | "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==" 580 | }, 581 | "import-fresh": { 582 | "version": "3.3.0", 583 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 584 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 585 | "requires": { 586 | "parent-module": "^1.0.0", 587 | "resolve-from": "^4.0.0" 588 | } 589 | }, 590 | "imurmurhash": { 591 | "version": "0.1.4", 592 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 593 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" 594 | }, 595 | "inflight": { 596 | "version": "1.0.6", 597 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 598 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 599 | "requires": { 600 | "once": "^1.3.0", 601 | "wrappy": "1" 602 | } 603 | }, 604 | "inherits": { 605 | "version": "2.0.4", 606 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 607 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 608 | }, 609 | "is-extglob": { 610 | "version": "2.1.1", 611 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 612 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" 613 | }, 614 | "is-glob": { 615 | "version": "4.0.3", 616 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 617 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 618 | "requires": { 619 | "is-extglob": "^2.1.1" 620 | } 621 | }, 622 | "is-path-inside": { 623 | "version": "3.0.3", 624 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", 625 | "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" 626 | }, 627 | "isarray": { 628 | "version": "1.0.0", 629 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 630 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 631 | }, 632 | "isexe": { 633 | "version": "2.0.0", 634 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 635 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" 636 | }, 637 | "js-sdsl": { 638 | "version": "4.2.0", 639 | "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", 640 | "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==" 641 | }, 642 | "js-yaml": { 643 | "version": "4.1.0", 644 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 645 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 646 | "requires": { 647 | "argparse": "^2.0.1" 648 | } 649 | }, 650 | "json-schema-traverse": { 651 | "version": "0.4.1", 652 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 653 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 654 | }, 655 | "json-stable-stringify-without-jsonify": { 656 | "version": "1.0.1", 657 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 658 | "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" 659 | }, 660 | "json5": { 661 | "version": "2.2.3", 662 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 663 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" 664 | }, 665 | "levn": { 666 | "version": "0.4.1", 667 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 668 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 669 | "requires": { 670 | "prelude-ls": "^1.2.1", 671 | "type-check": "~0.4.0" 672 | } 673 | }, 674 | "locate-path": { 675 | "version": "6.0.0", 676 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 677 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 678 | "requires": { 679 | "p-locate": "^5.0.0" 680 | } 681 | }, 682 | "lodash.merge": { 683 | "version": "4.6.2", 684 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 685 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" 686 | }, 687 | "minimatch": { 688 | "version": "3.1.2", 689 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 690 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 691 | "requires": { 692 | "brace-expansion": "^1.1.7" 693 | } 694 | }, 695 | "ms": { 696 | "version": "2.1.2", 697 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 698 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 699 | }, 700 | "natural-compare": { 701 | "version": "1.4.0", 702 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 703 | "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" 704 | }, 705 | "node-noop": { 706 | "version": "0.0.1", 707 | "resolved": "https://registry.npmjs.org/node-noop/-/node-noop-0.0.1.tgz", 708 | "integrity": "sha512-kAUvIRxZyDYFTLqGj+7zqXduG89vtqGmNMt9qDMvYH3H8uNTCOTz5ZN1q2Yg8++fWbzv+ERtYVqaOH42Ag5OpA==" 709 | }, 710 | "once": { 711 | "version": "1.4.0", 712 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 713 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 714 | "requires": { 715 | "wrappy": "1" 716 | } 717 | }, 718 | "only": { 719 | "version": "0.0.2", 720 | "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", 721 | "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==" 722 | }, 723 | "optionator": { 724 | "version": "0.9.1", 725 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", 726 | "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", 727 | "requires": { 728 | "deep-is": "^0.1.3", 729 | "fast-levenshtein": "^2.0.6", 730 | "levn": "^0.4.1", 731 | "prelude-ls": "^1.2.1", 732 | "type-check": "^0.4.0", 733 | "word-wrap": "^1.2.3" 734 | } 735 | }, 736 | "p-limit": { 737 | "version": "3.1.0", 738 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 739 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 740 | "requires": { 741 | "yocto-queue": "^0.1.0" 742 | } 743 | }, 744 | "p-locate": { 745 | "version": "5.0.0", 746 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 747 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 748 | "requires": { 749 | "p-limit": "^3.0.2" 750 | } 751 | }, 752 | "parent-module": { 753 | "version": "1.0.1", 754 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 755 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 756 | "requires": { 757 | "callsites": "^3.0.0" 758 | } 759 | }, 760 | "path-exists": { 761 | "version": "4.0.0", 762 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 763 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" 764 | }, 765 | "path-is-absolute": { 766 | "version": "1.0.1", 767 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 768 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" 769 | }, 770 | "path-key": { 771 | "version": "3.1.1", 772 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 773 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" 774 | }, 775 | "pino": { 776 | "version": "4.17.6", 777 | "resolved": "https://registry.npmjs.org/pino/-/pino-4.17.6.tgz", 778 | "integrity": "sha512-LFDwmhyWLBnmwO/2UFbWu1jEGVDzaPupaVdx0XcZ3tIAx1EDEBauzxXf2S0UcFK7oe+X9MApjH0hx9U1XMgfCA==", 779 | "requires": { 780 | "chalk": "^2.4.1", 781 | "fast-json-parse": "^1.0.3", 782 | "fast-safe-stringify": "^1.2.3", 783 | "flatstr": "^1.0.5", 784 | "pino-std-serializers": "^2.0.0", 785 | "pump": "^3.0.0", 786 | "quick-format-unescaped": "^1.1.2", 787 | "split2": "^2.2.0" 788 | } 789 | }, 790 | "pino-std-serializers": { 791 | "version": "2.5.0", 792 | "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.5.0.tgz", 793 | "integrity": "sha512-wXqbqSrIhE58TdrxxlfLwU9eDhrzppQDvGhBEr1gYbzzM4KKo3Y63gSjiDXRKLVS2UOXdPNR2v+KnQgNrs+xUg==" 794 | }, 795 | "prelude-ls": { 796 | "version": "1.2.1", 797 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 798 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" 799 | }, 800 | "process-nextick-args": { 801 | "version": "2.0.1", 802 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 803 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 804 | }, 805 | "pump": { 806 | "version": "3.0.0", 807 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 808 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 809 | "requires": { 810 | "end-of-stream": "^1.1.0", 811 | "once": "^1.3.1" 812 | } 813 | }, 814 | "punycode": { 815 | "version": "2.1.1", 816 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 817 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 818 | }, 819 | "queue-microtask": { 820 | "version": "1.2.3", 821 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 822 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" 823 | }, 824 | "quick-format-unescaped": { 825 | "version": "1.1.2", 826 | "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-1.1.2.tgz", 827 | "integrity": "sha1-DKWB3jF0vs7yWsPC6JVjQjgdtpg=", 828 | "requires": { 829 | "fast-safe-stringify": "^1.0.8" 830 | } 831 | }, 832 | "readable-stream": { 833 | "version": "2.3.7", 834 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 835 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 836 | "requires": { 837 | "core-util-is": "~1.0.0", 838 | "inherits": "~2.0.3", 839 | "isarray": "~1.0.0", 840 | "process-nextick-args": "~2.0.0", 841 | "safe-buffer": "~5.1.1", 842 | "string_decoder": "~1.1.1", 843 | "util-deprecate": "~1.0.1" 844 | } 845 | }, 846 | "regexpp": { 847 | "version": "3.2.0", 848 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", 849 | "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==" 850 | }, 851 | "resolve-from": { 852 | "version": "4.0.0", 853 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 854 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" 855 | }, 856 | "reusify": { 857 | "version": "1.0.4", 858 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 859 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" 860 | }, 861 | "rimraf": { 862 | "version": "3.0.2", 863 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 864 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 865 | "requires": { 866 | "glob": "^7.1.3" 867 | } 868 | }, 869 | "rtpengine-client": { 870 | "version": "0.2.1", 871 | "resolved": "https://registry.npmjs.org/rtpengine-client/-/rtpengine-client-0.2.1.tgz", 872 | "integrity": "sha512-1+Y/CBehtJolkqVlDK8U2dOfD7KOyN6RftRfg0nw1satq3/bNSPVwKDrv2XAfkKnfn2rS7IZ3q7hgKYu350wdw==", 873 | "requires": { 874 | "bencode": "^2.0.1", 875 | "uuid": "^8.2.0", 876 | "ws": "^7.4.4" 877 | }, 878 | "dependencies": { 879 | "uuid": { 880 | "version": "8.3.2", 881 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 882 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" 883 | }, 884 | "ws": { 885 | "version": "7.5.9", 886 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", 887 | "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" 888 | } 889 | } 890 | }, 891 | "run-parallel": { 892 | "version": "1.2.0", 893 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 894 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 895 | "requires": { 896 | "queue-microtask": "^1.2.2" 897 | } 898 | }, 899 | "safe-buffer": { 900 | "version": "5.1.2", 901 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 902 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 903 | }, 904 | "sdp-transform": { 905 | "version": "2.14.1", 906 | "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.1.tgz", 907 | "integrity": "sha512-RjZyX3nVwJyCuTo5tGPx+PZWkDMCg7oOLpSlhjDdZfwUoNqG1mM8nyj31IGHyaPWXhjbP7cdK3qZ2bmkJ1GzRw==" 908 | }, 909 | "shebang-command": { 910 | "version": "2.0.0", 911 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 912 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 913 | "requires": { 914 | "shebang-regex": "^3.0.0" 915 | } 916 | }, 917 | "shebang-regex": { 918 | "version": "3.0.0", 919 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 920 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" 921 | }, 922 | "short-uuid": { 923 | "version": "4.2.2", 924 | "resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz", 925 | "integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==", 926 | "requires": { 927 | "any-base": "^1.1.0", 928 | "uuid": "^8.3.2" 929 | }, 930 | "dependencies": { 931 | "uuid": { 932 | "version": "8.3.2", 933 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 934 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" 935 | } 936 | } 937 | }, 938 | "sip-methods": { 939 | "version": "0.3.0", 940 | "resolved": "https://registry.npmjs.org/sip-methods/-/sip-methods-0.3.0.tgz", 941 | "integrity": "sha512-jC7XdSJtscw/LgcuWbGwhSj0DeNPAh06rqPDf7BBicANJi/vG1ghpaPYE+BhW5DBvzYhmcjoA+BXhwChVpRCUA==" 942 | }, 943 | "sip-status": { 944 | "version": "0.1.0", 945 | "resolved": "https://registry.npmjs.org/sip-status/-/sip-status-0.1.0.tgz", 946 | "integrity": "sha512-2ZyuFMcqYYLsetLcPwUymg4mx5uciE5z5Mr8EJMvF+P0jIW1+plmgkMvZpdlj9uAQqzGqWIa/sFDGPJlkCmigQ==" 947 | }, 948 | "split2": { 949 | "version": "2.2.0", 950 | "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", 951 | "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", 952 | "requires": { 953 | "through2": "^2.0.2" 954 | } 955 | }, 956 | "string_decoder": { 957 | "version": "1.1.1", 958 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 959 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 960 | "requires": { 961 | "safe-buffer": "~5.1.0" 962 | } 963 | }, 964 | "strip-ansi": { 965 | "version": "6.0.1", 966 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 967 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 968 | "requires": { 969 | "ansi-regex": "^5.0.1" 970 | } 971 | }, 972 | "strip-json-comments": { 973 | "version": "3.1.1", 974 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 975 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" 976 | }, 977 | "supports-color": { 978 | "version": "5.5.0", 979 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 980 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 981 | "requires": { 982 | "has-flag": "^3.0.0" 983 | } 984 | }, 985 | "text-table": { 986 | "version": "0.2.0", 987 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 988 | "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" 989 | }, 990 | "through2": { 991 | "version": "2.0.5", 992 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", 993 | "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", 994 | "requires": { 995 | "readable-stream": "~2.3.6", 996 | "xtend": "~4.0.1" 997 | } 998 | }, 999 | "type-check": { 1000 | "version": "0.4.0", 1001 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 1002 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 1003 | "requires": { 1004 | "prelude-ls": "^1.2.1" 1005 | } 1006 | }, 1007 | "type-fest": { 1008 | "version": "0.20.2", 1009 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 1010 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" 1011 | }, 1012 | "uri-js": { 1013 | "version": "4.4.1", 1014 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1015 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1016 | "requires": { 1017 | "punycode": "^2.1.0" 1018 | } 1019 | }, 1020 | "util-deprecate": { 1021 | "version": "1.0.2", 1022 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1023 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1024 | }, 1025 | "utils-merge": { 1026 | "version": "1.0.1", 1027 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1028 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" 1029 | }, 1030 | "uuid": { 1031 | "version": "3.4.0", 1032 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 1033 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" 1034 | }, 1035 | "uuid-random": { 1036 | "version": "1.3.2", 1037 | "resolved": "https://registry.npmjs.org/uuid-random/-/uuid-random-1.3.2.tgz", 1038 | "integrity": "sha512-UOzej0Le/UgkbWEO8flm+0y+G+ljUon1QWTEZOq1rnMAsxo2+SckbiZdKzAHHlVh6gJqI1TjC/xwgR50MuCrBQ==" 1039 | }, 1040 | "which": { 1041 | "version": "2.0.2", 1042 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1043 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1044 | "requires": { 1045 | "isexe": "^2.0.0" 1046 | } 1047 | }, 1048 | "word-wrap": { 1049 | "version": "1.2.3", 1050 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 1051 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" 1052 | }, 1053 | "wrappy": { 1054 | "version": "1.0.2", 1055 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1056 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1057 | }, 1058 | "ws": { 1059 | "version": "8.11.0", 1060 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", 1061 | "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==" 1062 | }, 1063 | "xtend": { 1064 | "version": "4.0.2", 1065 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 1066 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" 1067 | }, 1068 | "yocto-queue": { 1069 | "version": "0.1.0", 1070 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 1071 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" 1072 | } 1073 | } 1074 | } 1075 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drachtio-rtpengine-webrtcproxy", 3 | "version": "0.1.1", 4 | "description": "Webrtc proxy server built using drachtio and rtpengine", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node app.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "engines": { 11 | "node": ">= 12.22.1" 12 | }, 13 | "keywords": [ 14 | "sip", 15 | "drachtio", 16 | "rtpengine", 17 | "webrtc", 18 | "webrtc proxy server" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/voxbone/drachtio-rtpengine-webrtcproxy.git" 23 | }, 24 | "author": "Dave Horton", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/voxbone/drachtio-rtpengine-webrtcproxy/issues" 28 | }, 29 | "homepage": "https://github.com/voxbone/drachtio-rtpengine-webrtcproxy#readme", 30 | "dependencies": { 31 | "@jambonz/rtpengine-utils": "^0.3.1", 32 | "config": "^3.3.7", 33 | "drachtio-fn-b2b-sugar": "0.0.12", 34 | "drachtio-mw-registration-parser": "0.0.2", 35 | "drachtio-srf": "^4.5.13", 36 | "pino": "^4.17.6", 37 | "rtpengine-client": "^0.2.0", 38 | "uuid": "^3.4.0" 39 | } 40 | } 41 | --------------------------------------------------------------------------------