├── LICENSE ├── README.md ├── binding.gyp ├── lib ├── constants.js └── main.js ├── package.json └── src ├── SIPSTERAccount.cc ├── SIPSTERAccount.h ├── SIPSTERCall.cc ├── SIPSTERCall.h ├── SIPSTERMedia.cc ├── SIPSTERMedia.h ├── SIPSTERTransport.cc ├── SIPSTERTransport.h ├── binding.cc └── common.h /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Brian White. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Description 2 | =========== 3 | 4 | A [pjsip](http://www.pjsip.org) (or more accurately a [pjsua2](http://www.pjsip.org/docs/book-latest/html/index.html)) binding for node.js. 5 | 6 | Familiarity with pjsip/pjsua2 is a plus when using this binding. 7 | 8 | 9 | Requirements 10 | ============ 11 | 12 | * Non-Windows OS 13 | * [pjsip](http://www.pjsip.org/) -- v2.4.5 or newer 14 | * [node.js](http://nodejs.org/) -- v0.10 or newer 15 | 16 | 17 | Install 18 | ======= 19 | 20 | npm install sipster 21 | 22 | 23 | Examples 24 | ======== 25 | 26 | * UAS set up as a SIP trunk (no registration): 27 | 28 | ```javascript 29 | var sipster = require('sipster'); 30 | 31 | // initialize pjsip 32 | sipster.init(); 33 | 34 | // set up a transport to listen for incoming connections, defaults to UDP 35 | var transport = new sipster.Transport({ port: 5060 }); 36 | 37 | // set up a SIP account, we need at least one -- as required by pjsip. 38 | // this sets up an account for calls coming from 192.168.100.10 39 | var acct = new sipster.Account({ 40 | idUri: 'sip:192.168.100.10' 41 | }); 42 | 43 | // watch for incoming calls 44 | acct.on('call', function(info, call) { 45 | console.log('=== Incoming call from ' + info.remoteContact); 46 | 47 | // watch for call state changes 48 | call.on('state', function(state) { 49 | console.log('=== Call state is now: ' + state.toUpperCase()); 50 | }); 51 | 52 | // listen for DTMF digits 53 | call.on('dtmf', function(digit) { 54 | console.log('=== DTMF digit received: ' + digit); 55 | }); 56 | 57 | // audio stream(s) available 58 | call.on('media', function(medias) { 59 | // play looping .wav file to the first audio stream 60 | var player = sipster.createPlayer('sound.wav'); 61 | player.startTransmitTo(medias[0]); 62 | 63 | // record the audio of the other side, this will not include the audio from 64 | // the player above. 65 | var recorder = sipster.createRecorder('call.wav'); 66 | medias[0].startTransmitTo(recorder); 67 | // to include the player audio, you can mix the sources together simply 68 | // by transmitting to the same recorder: 69 | // player.startTransmitTo(recorder); 70 | }); 71 | 72 | // answer the call (with default 200 OK) 73 | call.answer(); 74 | }); 75 | 76 | // finalize the pjsip initialization phase ... 77 | sipster.start(); 78 | ``` 79 | 80 | 81 | API 82 | === 83 | 84 | Exported static methods 85 | ----------------------- 86 | 87 | * **init**([< _object_ >endpointCfg]) - _(void)_ - Starts the initializion of the pjsip library (`libInit()`). This is to be done only once. `endpointCfg` is an [EpConfig](http://www.pjsip.org/pjsip/docs/html/structpj_1_1EpConfig.htm)-like object for if you need to change any global options from the library defaults. 88 | 89 | * **start**() - _(void)_ - Finalizes the initialization of the pjsip library (`libStart()`). This is generally called once you've got everything configured and set up. 90 | 91 | * **hangupAllCalls**() - _(void)_ - Hangs up all existing calls. 92 | 93 | * **createRecorder**(< _string_ >filename[, < _string_ >format[, < _integer_ >maxSize]]) - _Media_ - Creates an audio recorder that writes to the given `filename`. `format` can be one of 'ulaw', 'alaw', or 'pcm' (default is 'ulaw'). `maxSize` is the maximum file size (default is no limit). 94 | 95 | * **createPlayer**(< _string_ >filename[, < _boolean_ >noLoop]) - _Media_ - Creates an audio player that reads from `filename`. Set `noLoop` to true to disable looping of the audio. When `noLoop` is true, an 'eof' event will be emitted on the Media object when it reaches the end of playback. 96 | 97 | * **createPlaylist**(< _array_ >filenames[, < _boolean_ >noLoop]) - _Media_ - Creates an audio player that sequentially reads from the list of `filenames`. Set `noLoop` to true to disable looping of the playlist. When `noLoop` is true, an 'eof' event will be emitted on the Media object when it reaches the end of the playlist. 98 | 99 | 100 | Exported properties 101 | ------------------- 102 | 103 | * **version** - _object_ - (Read-only) Contains information about the pjsip library version (`libVersion()`): 104 | * **major** - _integer_ - The major number. 105 | * **minor** - _integer_ - The minor number. 106 | * **rev** - _integer_ - The additional revision number. 107 | * **suffix** - _string_ - The version suffix (e.g. '-svn'). 108 | * **full** - _string_ - The concatenation of `major`, `minor`, `rev`, and `suffix` (e.g. '2.2.1-svn'). 109 | * **numeric** - _integer_ - The `major`, `minor`, and `rev` as a single integer in the form 0xMMIIRR00 where MM is `major`, II is `minor`, and RR is `rev`. 110 | 111 | * **config** - _object_ - (Read-only) Returns the **entire** current (EpConfig) config for pjsip. 112 | 113 | * **state** - _string_ - (Read-only) Returns the state of the library/endpoint (`libGetState()`). For example: 'created', 'init', 'starting', 'running', or 'closing'. 114 | 115 | * **mediaActivePorts** - _integer_ - (Read-only) Returns the total number of active Media ports. 116 | 117 | * **mediaMaxPorts** - _integer_ - (Read-only) Returns the maximum number of Media ports permitted. 118 | 119 | Additionally any needed pjsip library constants (may be needed when creating and passing in config objects) are exported as well. 120 | 121 | 122 | Exported types 123 | -------------- 124 | 125 | * **Transport** - Represents an underlying (network) interface that Calls and Accounts use. 126 | 127 | * **Account** - An entity used for identification purposes for incoming or outgoing requests. 128 | 129 | 130 | Transport methods 131 | ----------------- 132 | 133 | * **(constructor)**([< _object_ >transportConfig]) - Creates and returns a new, enabled Transport instance. `transportConfig` is a [TransportConfig](http://www.pjsip.org/pjsip/docs/html/structpj_1_1TransportConfig.htm)-like object for if you need to change any transport options from the library defaults. 134 | 135 | * **unref**() - _(void)_ - Detaches the Transport from the event loop. 136 | 137 | * **ref**() - _(void)_ - Attaches the Transport to the event loop (default upon instantiation). 138 | 139 | * **getInfo**() - _object_ - Returns information (`TransportInfo`) about the transport: 140 | * **type** - _string_ - Transport type name. 141 | * **info** - _string_ - Transport string info/description. 142 | * **flags** - _integer_ - Transport flags (e.g. PJSIP_TRANSPORT_RELIABLE, PJSIP_TRANSPORT_SECURE, PJSIP_TRANSPORT_DATAGRAM). 143 | * **localAddress** - _string_ - Local/bound address. 144 | * **localName** - _string_ - Published address. 145 | * **usageCount** - _integer_ - Current number of objects currently referencing this transport. 146 | 147 | * **disable**() - _(void)_ - Disables the transport. Disabling a transport does not necessarily close the socket, it will only discard incoming messages and prevent the transport from being used to send outgoing messages. 148 | 149 | * **enable**() - _(void)_ - Enables the transport. Transports are automatically enabled upon creation, so you don't need to call this method unless you explicitly disable the transport first. 150 | 151 | 152 | Transport properties 153 | -------------------- 154 | 155 | * **enabled** - _boolean_ - (Read-only) Indicates if the transport is currently enabled or not. 156 | 157 | 158 | Account methods 159 | --------------- 160 | 161 | * **(constructor)**(< _object_ >accountConfig) - Creates and returns a new Account instance. `accountConfig` is an [AccountConfig](http://www.pjsip.org/pjsip/docs/html/structpj_1_1AccountConfig.htm)-like object. 162 | 163 | * **unref**() - _(void)_ - Detaches the Account from the event loop. 164 | 165 | * **ref**() - _(void)_ - Attaches the Account to the event loop (default upon instantiation). 166 | 167 | * **modify**(< _object_ >accountConfig) - _(void)_ - Reconfigure the Account with the given `accountConfig`. 168 | 169 | * **getInfo**() - _object_ - Returns information (`AccountInfo`) about the account: 170 | * **uri** - _string_ - The account's URI. 171 | * **regIsConfigured** - _boolean_ - Flag to tell whether this account has registration setting (reg_uri is not empty). 172 | * **regIsActive** - _boolean_ - Flag to tell whether this account is currently registered (has active registration session). 173 | * **regExpiresSec** - _integer_ - An up to date expiration interval for account registration session. 174 | 175 | * **setRegistration**(< _boolean_ >renew) - _(void)_ - Update registration or perform unregistration. You only need to call this method if you want to manually update the registration or want to unregister from the server. If `renew` is false, this will begin the unregistration process. 176 | 177 | * **setTransport**(< _Transport_ >trans) - _(void)_ - Lock/bind the given transport to this account. Normally you shouldn't need to do this, as transports will be selected automatically by the library according to the destination. When an account is locked/bound to a specific transport, all outgoing requests from this account will use the specified transport (this includes SIP registration, dialog (call and event subscription), and out-of-dialog requests such as MESSAGE). 178 | 179 | * **makeCall**(< _string_ >destination) - _Call_ - Start a new SIP call to `destination`. 180 | 181 | 182 | Account properties 183 | ------------------ 184 | 185 | * **valid** - _boolean_ - (Read-only) Is the Account still valid? 186 | 187 | * **default** - _boolean_ - (Read/Write) Is this the default Account for when no other Account matches a request? 188 | 189 | 190 | Account events 191 | -------------- 192 | 193 | * **registering**() - The registration process has started. 194 | 195 | * **unregistering**() - The unregistration process has started. 196 | 197 | * **registered**() - The registration process has completed. 198 | 199 | * **unregistered**() - The unregistration process has completed. 200 | 201 | * **state**(< _boolean_ >active, < _integer_ >statusCode) - The account state has changed. `active` indicates if registration is active. `statusCode` refers to the relevant SIP status code. 202 | 203 | * **call**(< _object_ >info, < _Call_ >call) - An incoming call request. `info` contains: 204 | * **srcAddress** - _string_ - The ip (and port) of the request. 205 | * **localUri** - _string_ - Local SIP URI. 206 | * **localContact** - _string_ - Local Contact field. 207 | * **remoteUri** - _string_ - Remote SIP URI. 208 | * **remoteContact** - _string_ - Remote Contact field. 209 | * **callId** - _string_ - The Call-ID field. 210 | 211 | 212 | Call methods 213 | ------------ 214 | 215 | * **answer**([< _integer_ >statusCode[, < _string_ >reason]]) - _(void)_ - For incoming calls, this responds to the INVITE with an optional `statusCode` (defaults to 200) and optional `reason` phrase. 216 | 217 | * **hangup**([< _integer_ >statusCode[, < _string_ >reason]]) - _(void)_ - Hangs up the call with an optional `statusCode` (defaults to 603) and optional `reason` phrase. This function is different than answering the call with 3xx-6xx response (with answer()), in that this function will hangup the call regardless of the state and role of the call, while answer() only works with incoming calls on EARLY state. 218 | 219 | * **hold**() - _(void)_ - Puts the call on hold. 220 | 221 | * **reinvite**() - _(void)_ - Releases a hold. 222 | 223 | * **update**() - _(void)_ - Sends an UPDATE request. 224 | 225 | * **transfer**(< _string_ >destination) - _(void)_ - Transfers the call to `destination`. 226 | 227 | * **dtmf**(< _string_ >digits) - _(void)_ - Sends DTMF digits to the remote end using the RFC 2833 payload format. 228 | 229 | * **unref**() - _(void)_ - Detaches the Call from the event loop (default). 230 | 231 | * **ref**() - _(void)_ - Attaches the Call to the event loop. 232 | 233 | * **getStatsDump**([< _boolean_ >inclMediaStats[, < _string_ >indent]]) - _string_ - Returns formatted statistics about the call. If `inclMediaStats` is true, then statistics about the Call's media is included (default is true). `indent` is the string to use for indenting (default is two spaces). 234 | 235 | 236 | Call properties 237 | --------------- 238 | 239 | * **connDuration** - _double_ - (Read-only) Call connected duration in seconds (zero when call is not established). 240 | 241 | * **totalDuration** - _double_ - (Read-only) Total call duration in seconds, including set-up time. 242 | 243 | * **hasMedia** - _boolean_ - (Read-only) True if the Call has active media. 244 | 245 | * **isActive** - _boolean_ - (Read-only) True if the call has an active INVITE session and the INVITE session has not been disconnected. 246 | 247 | 248 | Call events 249 | ----------- 250 | 251 | * **state**(< _string_ >state) - The call state has changed. `state` is one of: 'calling', 'incoming', 'early', 'connecting', 'confirmed', or 'disconnected'. 252 | 253 | * **dtmf**(< _string_ >digit) - A DTMF digit has been received from the remote end. 254 | 255 | * **media**(< _array_ >medias) - The list of Medias associated with this call have changed and the current list is available in `medias`. 256 | 257 | 258 | Media methods 259 | ------------- 260 | 261 | * **startTransmitTo**(< _Media_ >sink) - _(void)_ - Starts transmitting to `sink`. 262 | 263 | * **stopTransmitTo**(< _Media_ >sink) - _(void)_ - Stops transmitting to `sink`. 264 | 265 | * **adjustTxLevel**(< _float_ >val) - _(void)_ - Adjust the signal level of the audio sent from this Media by making it louder or quieter: a `val` of 1.0 means no level adjustment and a `val` of 0 means to mute. 266 | 267 | * **adjustRxLevel**(< _float_ >val) - _(void)_ - Adjust the signal level of the audio sent to this Media by making it louder or quieter: a `val` of 1.0 means no level adjustment and a `val` of 0 means to mute. 268 | 269 | * **close**() - _(void)_ - Immediately closes the Media. This can be useful to do explicitly since v8's garbage collector is quite lazy. After calling this, using this particular Media instance (and its methods) is useless. 270 | 271 | 272 | Media properties 273 | ---------------- 274 | 275 | * **dir** - _string_ - Returns the direction of the media from our perspective. The value is one of: 'none', 'inbound', 'outbound', 'bidirectional', or 'unknown'. 276 | 277 | * **rtpAddr** - _string_ - Returns the remote address (and port) of where the RTP originates. 278 | 279 | * **rtcpAddr** - _string_ - Returns the remote address (and port) of where the RTCP originates. 280 | 281 | * **rxLevel** - _integer_ - Returns the last received signal level. 282 | 283 | * **txLevel** - _integer_ - Returns the last transmitted signal level. 284 | 285 | 286 | Media events 287 | ------------ 288 | 289 | * **eof**() - This is only applicable to player or playlist Media objects and indicates that the end of the file or end of playlist has been reached. 290 | 291 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'sipster', 5 | 'sources': [ 6 | 'src/binding.cc', 7 | 'src/SIPSTERAccount.cc', 8 | 'src/SIPSTERCall.cc', 9 | 'src/SIPSTERMedia.cc', 10 | 'src/SIPSTERTransport.cc', 11 | ], 12 | 'include_dirs': [ 13 | "src", 14 | "", 4 | "description": "A pjsip binding for node.js", 5 | "main": "./lib/main", 6 | "dependencies": { 7 | "nan": "^2.0.9" 8 | }, 9 | "os" : [ "!win32" ], 10 | "engines": { "node": ">=0.10.0" }, 11 | "keywords": [ "sip", "pjsip" ], 12 | "licenses": [ { "type": "MIT", "url": "http://github.com/mscdex/sipster/raw/master/LICENSE" } ], 13 | "repository": { "type": "git", "url": "http://github.com/mscdex/sipster.git" } 14 | } 15 | -------------------------------------------------------------------------------- /src/SIPSTERAccount.cc: -------------------------------------------------------------------------------- 1 | #include "SIPSTERAccount.h" 2 | #include "SIPSTERCall.h" 3 | #include "SIPSTERTransport.h" 4 | #include "common.h" 5 | 6 | Nan::Persistent SIPSTERAccount_constructor; 7 | 8 | regex_t fromuri_regex; 9 | regex_t touri_regex; 10 | 11 | SIPSTERAccount::SIPSTERAccount() { 12 | } 13 | SIPSTERAccount::~SIPSTERAccount() { 14 | if (emit) 15 | delete emit; 16 | } 17 | 18 | AccountConfig SIPSTERAccount::genConfig(Local acct_obj) { 19 | Nan::HandleScope scope; 20 | 21 | AccountConfig acct_cfg; 22 | 23 | Local val; 24 | 25 | JS2PJ_INT(acct_obj, priority, acct_cfg); 26 | JS2PJ_STR(acct_obj, idUri, acct_cfg); 27 | 28 | val = acct_obj->Get(Nan::New("regConfig").ToLocalChecked()); 29 | if (val->IsObject()) { 30 | AccountRegConfig regConfig; 31 | Local reg_obj = val->ToObject(); 32 | JS2PJ_STR(reg_obj, registrarUri, regConfig); 33 | JS2PJ_BOOL(reg_obj, registerOnAdd, regConfig); 34 | 35 | val = reg_obj->Get(Nan::New("headers").ToLocalChecked()); 36 | if (val->IsObject()) { 37 | const Local hdr_obj = val->ToObject(); 38 | const Local hdr_props = hdr_obj->GetPropertyNames(); 39 | const uint32_t hdr_length = hdr_props->Length(); 40 | if (hdr_length > 0) { 41 | vector sipheaders; 42 | for (uint32_t i = 0; i < hdr_length; ++i) { 43 | const Local key = hdr_props->Get(i); 44 | const Local value = hdr_obj->Get(key); 45 | SipHeader hdr; 46 | Nan::Utf8String name_str(key); 47 | Nan::Utf8String val_str(value); 48 | hdr.hName = string(*name_str); 49 | hdr.hValue = string(*val_str); 50 | sipheaders.push_back(hdr); 51 | } 52 | regConfig.headers = sipheaders; 53 | } 54 | } 55 | 56 | JS2PJ_UINT(reg_obj, timeoutSec, regConfig); 57 | JS2PJ_UINT(reg_obj, retryIntervalSec, regConfig); 58 | JS2PJ_UINT(reg_obj, firstRetryIntervalSec, regConfig); 59 | JS2PJ_UINT(reg_obj, delayBeforeRefreshSec, regConfig); 60 | JS2PJ_BOOL(reg_obj, dropCallsOnFail, regConfig); 61 | JS2PJ_UINT(reg_obj, unregWaitMsec, regConfig); 62 | JS2PJ_UINT(reg_obj, proxyUse, regConfig); 63 | 64 | acct_cfg.regConfig = regConfig; 65 | } 66 | val = acct_obj->Get(Nan::New("sipConfig").ToLocalChecked()); 67 | if (val->IsObject()) { 68 | AccountSipConfig sipConfig; 69 | Local sip_obj = val->ToObject(); 70 | 71 | val = sip_obj->Get(Nan::New("authCreds").ToLocalChecked()); 72 | if (val->IsArray()) { 73 | const Local arr_obj = Local::Cast(val); 74 | const uint32_t arr_length = arr_obj->Length(); 75 | if (arr_length > 0) { 76 | vector creds; 77 | for (uint32_t i = 0; i < arr_length; ++i) { 78 | const Local cred_value = arr_obj->Get(i); 79 | if (cred_value->IsObject()) { 80 | const Local auth_obj = cred_value->ToObject(); 81 | AuthCredInfo credinfo; 82 | Local scheme_val = auth_obj->Get(Nan::New("scheme").ToLocalChecked()); 83 | Local realm_val = auth_obj->Get(Nan::New("realm").ToLocalChecked()); 84 | Local username_val = auth_obj->Get(Nan::New("username").ToLocalChecked()); 85 | Local dataType_val = auth_obj->Get(Nan::New("dataType").ToLocalChecked()); 86 | Local data_val = auth_obj->Get(Nan::New("data").ToLocalChecked()); 87 | if (scheme_val->IsString() 88 | && realm_val->IsString() 89 | && username_val->IsString() 90 | && dataType_val->IsInt32() 91 | && data_val->IsString()) { 92 | Nan::Utf8String scheme_str(scheme_val); 93 | Nan::Utf8String realm_str(realm_val); 94 | Nan::Utf8String username_str(username_val); 95 | Nan::Utf8String data_str(data_val); 96 | credinfo.scheme = string(*scheme_str); 97 | credinfo.realm = string(*realm_str); 98 | credinfo.username = string(*username_str); 99 | credinfo.dataType = dataType_val->Int32Value(); 100 | credinfo.data = string(*data_str); 101 | creds.push_back(credinfo); 102 | } 103 | } 104 | } 105 | if (creds.size() > 0) 106 | sipConfig.authCreds = creds; 107 | } 108 | } 109 | 110 | val = sip_obj->Get(Nan::New("proxies").ToLocalChecked()); 111 | if (val->IsArray()) { 112 | const Local arr_obj = Local::Cast(val); 113 | const uint32_t arr_length = arr_obj->Length(); 114 | if (arr_length > 0) { 115 | vector proxies; 116 | for (uint32_t i = 0; i < arr_length; ++i) { 117 | const Local value = arr_obj->Get(i); 118 | Nan::Utf8String value_str(value); 119 | proxies.push_back(string(*value_str)); 120 | } 121 | sipConfig.proxies = proxies; 122 | } 123 | } 124 | 125 | JS2PJ_STR(sip_obj, contactForced, sipConfig); 126 | JS2PJ_STR(sip_obj, contactParams, sipConfig); 127 | JS2PJ_STR(sip_obj, contactUriParams, sipConfig); 128 | JS2PJ_BOOL(sip_obj, authInitialEmpty, sipConfig); 129 | JS2PJ_STR(sip_obj, authInitialAlgorithm, sipConfig); 130 | 131 | // deviates from the pjsip config structure to accept a Transport instance 132 | // instead of a transport id since that information is made available to 133 | // JS land 134 | val = sip_obj->Get(Nan::New("transport").ToLocalChecked()); 135 | if (Nan::New(SIPSTERTransport_constructor)->HasInstance(val)) { 136 | SIPSTERTransport* trans = 137 | Nan::ObjectWrap::Unwrap(Local::Cast(val)); 138 | sipConfig.transportId = trans->transId; 139 | } 140 | 141 | acct_cfg.sipConfig = sipConfig; 142 | } 143 | val = acct_obj->Get(Nan::New("callConfig").ToLocalChecked()); 144 | if (val->IsObject()) { 145 | AccountCallConfig callConfig; 146 | Local call_obj = val->ToObject(); 147 | JS2PJ_ENUM(call_obj, holdType, pjsua_call_hold_type, callConfig); 148 | JS2PJ_ENUM(call_obj, prackUse, pjsua_100rel_use, callConfig); 149 | JS2PJ_ENUM(call_obj, timerUse, pjsua_sip_timer_use, callConfig); 150 | JS2PJ_UINT(call_obj, timerMinSESec, callConfig); 151 | JS2PJ_UINT(call_obj, timerSessExpiresSec, callConfig); 152 | 153 | acct_cfg.callConfig = callConfig; 154 | } 155 | val = acct_obj->Get(Nan::New("presConfig").ToLocalChecked()); 156 | if (val->IsObject()) { 157 | AccountPresConfig presConfig; 158 | Local pres_obj = val->ToObject(); 159 | 160 | val = pres_obj->Get(Nan::New("headers").ToLocalChecked()); 161 | if (val->IsObject()) { 162 | const Local hdr_obj = val->ToObject(); 163 | const Local hdr_props = hdr_obj->GetPropertyNames(); 164 | const uint32_t hdr_length = hdr_props->Length(); 165 | if (hdr_length > 0) { 166 | vector sipheaders; 167 | for (uint32_t i = 0; i < hdr_length; ++i) { 168 | const Local key = hdr_props->Get(i); 169 | const Local value = hdr_obj->Get(key); 170 | SipHeader hdr; 171 | Nan::Utf8String name_str(key); 172 | Nan::Utf8String value_str(value); 173 | hdr.hName = string(*name_str); 174 | hdr.hValue = string(*value_str); 175 | sipheaders.push_back(hdr); 176 | } 177 | presConfig.headers = sipheaders; 178 | } 179 | } 180 | 181 | JS2PJ_BOOL(pres_obj, publishEnabled, presConfig); 182 | JS2PJ_BOOL(pres_obj, publishQueue, presConfig); 183 | JS2PJ_UINT(pres_obj, publishShutdownWaitMsec, presConfig); 184 | JS2PJ_STR(pres_obj, pidfTupleId, presConfig); 185 | 186 | acct_cfg.presConfig = presConfig; 187 | } 188 | val = acct_obj->Get(Nan::New("mwiConfig").ToLocalChecked()); 189 | if (val->IsObject()) { 190 | AccountMwiConfig mwiConfig; 191 | Local mwi_obj = val->ToObject(); 192 | JS2PJ_BOOL(mwi_obj, enabled, mwiConfig); 193 | JS2PJ_UINT(mwi_obj, expirationSec, mwiConfig); 194 | 195 | acct_cfg.mwiConfig = mwiConfig; 196 | } 197 | val = acct_obj->Get(Nan::New("natConfig").ToLocalChecked()); 198 | if (val->IsObject()) { 199 | AccountNatConfig natConfig; 200 | Local nat_obj = val->ToObject(); 201 | JS2PJ_ENUM(nat_obj, sipStunUse, pjsua_stun_use, natConfig); 202 | JS2PJ_ENUM(nat_obj, mediaStunUse, pjsua_stun_use, natConfig); 203 | JS2PJ_BOOL(nat_obj, iceEnabled, natConfig); 204 | JS2PJ_INT(nat_obj, iceMaxHostCands, natConfig); 205 | JS2PJ_BOOL(nat_obj, iceAggressiveNomination, natConfig); 206 | JS2PJ_UINT(nat_obj, iceNominatedCheckDelayMsec, natConfig); 207 | JS2PJ_INT(nat_obj, iceWaitNominationTimeoutMsec, natConfig); 208 | JS2PJ_BOOL(nat_obj, iceNoRtcp, natConfig); 209 | JS2PJ_BOOL(nat_obj, iceAlwaysUpdate, natConfig); 210 | JS2PJ_BOOL(nat_obj, turnEnabled, natConfig); 211 | JS2PJ_STR(nat_obj, turnServer, natConfig); 212 | JS2PJ_ENUM(nat_obj, turnConnType, pj_turn_tp_type, natConfig); 213 | JS2PJ_STR(nat_obj, turnUserName, natConfig); 214 | JS2PJ_INT(nat_obj, turnPasswordType, natConfig); 215 | JS2PJ_STR(nat_obj, turnPassword, natConfig); 216 | JS2PJ_INT(nat_obj, contactRewriteUse, natConfig); 217 | JS2PJ_INT(nat_obj, contactRewriteMethod, natConfig); 218 | JS2PJ_INT(nat_obj, viaRewriteUse, natConfig); 219 | JS2PJ_INT(nat_obj, sdpNatRewriteUse, natConfig); 220 | JS2PJ_INT(nat_obj, sipOutboundUse, natConfig); 221 | JS2PJ_STR(nat_obj, sipOutboundInstanceId, natConfig); 222 | JS2PJ_STR(nat_obj, sipOutboundRegId, natConfig); 223 | JS2PJ_UINT(nat_obj, udpKaIntervalSec, natConfig); 224 | JS2PJ_STR(nat_obj, udpKaData, natConfig); 225 | 226 | acct_cfg.natConfig = natConfig; 227 | } 228 | val = acct_obj->Get(Nan::New("mediaConfig").ToLocalChecked()); 229 | if (val->IsObject()) { 230 | AccountMediaConfig mediaConfig; 231 | Local media_obj = val->ToObject(); 232 | 233 | val = media_obj->Get(Nan::New("transportConfig").ToLocalChecked()); 234 | if (val->IsObject()) { 235 | TransportConfig transportConfig; 236 | Local obj = val->ToObject(); 237 | JS2PJ_UINT(obj, port, transportConfig); 238 | JS2PJ_UINT(obj, portRange, transportConfig); 239 | JS2PJ_STR(obj, publicAddress, transportConfig); 240 | JS2PJ_STR(obj, boundAddress, transportConfig); 241 | JS2PJ_ENUM(obj, qosType, pj_qos_type, transportConfig); 242 | 243 | val = obj->Get(Nan::New("qosParams").ToLocalChecked()); 244 | if (val->IsObject()) { 245 | pj_qos_params qos_params; 246 | Local qos_obj = val->ToObject(); 247 | Local flags_val = qos_obj->Get(Nan::New("flags").ToLocalChecked()); 248 | Local dscp_val = qos_obj->Get(Nan::New("dscp_val").ToLocalChecked()); 249 | Local so_prio_val = qos_obj->Get(Nan::New("so_prio").ToLocalChecked()); 250 | Local wmm_prio_val = qos_obj->Get(Nan::New("wmm_prio").ToLocalChecked()); 251 | if (flags_val->IsUint32()) { 252 | qos_params.flags = 253 | static_cast(flags_val->Uint32Value()); 254 | } 255 | if (dscp_val->IsUint32()) { 256 | qos_params.dscp_val = 257 | static_cast(dscp_val->Uint32Value()); 258 | } 259 | if (so_prio_val->IsUint32()) { 260 | qos_params.so_prio = 261 | static_cast(so_prio_val->Uint32Value()); 262 | } 263 | if (wmm_prio_val->IsUint32()) { 264 | qos_params.wmm_prio = 265 | static_cast(wmm_prio_val->Uint32Value()); 266 | } 267 | transportConfig.qosParams = qos_params; 268 | } 269 | 270 | val = obj->Get(Nan::New("tlsConfig").ToLocalChecked()); 271 | if (val->IsObject()) { 272 | TlsConfig tlsConfig; 273 | Local tls_obj = val->ToObject(); 274 | JS2PJ_STR(tls_obj, CaListFile, tlsConfig); 275 | JS2PJ_STR(tls_obj, certFile, tlsConfig); 276 | JS2PJ_STR(tls_obj, privKeyFile, tlsConfig); 277 | JS2PJ_STR(tls_obj, password, tlsConfig); 278 | JS2PJ_ENUM(tls_obj, method, pjsip_ssl_method, tlsConfig); 279 | 280 | val = tls_obj->Get(Nan::New("ciphers").ToLocalChecked()); 281 | if (val->IsArray()) { 282 | const Local arr_obj = Local::Cast(val); 283 | const uint32_t arr_length = arr_obj->Length(); 284 | if (arr_length > 0) { 285 | vector ciphers; 286 | for (uint32_t i = 0; i < arr_length; ++i) { 287 | const Local value = arr_obj->Get(i); 288 | ciphers.push_back(value->Int32Value()); 289 | } 290 | tlsConfig.ciphers = ciphers; 291 | } 292 | } 293 | 294 | JS2PJ_BOOL(tls_obj, verifyServer, tlsConfig); 295 | JS2PJ_BOOL(tls_obj, verifyClient, tlsConfig); 296 | JS2PJ_BOOL(tls_obj, requireClientCert, tlsConfig); 297 | JS2PJ_UINT(tls_obj, msecTimeout, tlsConfig); 298 | JS2PJ_ENUM(tls_obj, qosType, pj_qos_type, tlsConfig); 299 | 300 | val = tls_obj->Get(Nan::New("qosParams").ToLocalChecked()); 301 | if (val->IsObject()) { 302 | pj_qos_params qos_params; 303 | Local qos_obj = val->ToObject(); 304 | Local flags_val = qos_obj->Get(Nan::New("flags").ToLocalChecked()); 305 | Local dscp_val = qos_obj->Get(Nan::New("dscp_val").ToLocalChecked()); 306 | Local so_prio_val = qos_obj->Get(Nan::New("so_prio").ToLocalChecked()); 307 | Local wmm_prio_val = qos_obj->Get(Nan::New("wmm_prio").ToLocalChecked()); 308 | if (flags_val->IsUint32()) { 309 | qos_params.flags = 310 | static_cast(flags_val->Uint32Value()); 311 | } 312 | if (dscp_val->IsUint32()) { 313 | qos_params.dscp_val = 314 | static_cast(dscp_val->Uint32Value()); 315 | } 316 | if (so_prio_val->IsUint32()) { 317 | qos_params.so_prio = 318 | static_cast(so_prio_val->Uint32Value()); 319 | } 320 | if (wmm_prio_val->IsUint32()) { 321 | qos_params.wmm_prio = 322 | static_cast(wmm_prio_val->Uint32Value()); 323 | } 324 | tlsConfig.qosParams = qos_params; 325 | } 326 | 327 | JS2PJ_BOOL(tls_obj, qosIgnoreError, tlsConfig); 328 | 329 | transportConfig.tlsConfig = tlsConfig; 330 | } 331 | 332 | mediaConfig.transportConfig = transportConfig; 333 | } 334 | 335 | JS2PJ_BOOL(media_obj, lockCodecEnabled, mediaConfig); 336 | JS2PJ_BOOL(media_obj, streamKaEnabled, mediaConfig); 337 | JS2PJ_ENUM(media_obj, srtpUse, pjmedia_srtp_use, mediaConfig); 338 | JS2PJ_INT(media_obj, srtpSecureSignaling, mediaConfig); 339 | JS2PJ_ENUM(media_obj, ipv6Use, pjsua_ipv6_use, mediaConfig); 340 | 341 | acct_cfg.mediaConfig = mediaConfig; 342 | } 343 | val = acct_obj->Get(Nan::New("videoConfig").ToLocalChecked()); 344 | if (val->IsObject()) { 345 | AccountVideoConfig videoConfig; 346 | Local vid_obj = val->ToObject(); 347 | JS2PJ_BOOL(vid_obj, autoShowIncoming, videoConfig); 348 | JS2PJ_BOOL(vid_obj, autoTransmitOutgoing, videoConfig); 349 | JS2PJ_UINT(vid_obj, windowFlags, videoConfig); 350 | JS2PJ_INT(vid_obj, defaultCaptureDevice, videoConfig); 351 | JS2PJ_INT(vid_obj, defaultRenderDevice, videoConfig); 352 | JS2PJ_ENUM(vid_obj, 353 | rateControlMethod, 354 | pjmedia_vid_stream_rc_method, 355 | videoConfig); 356 | JS2PJ_UINT(vid_obj, rateControlBandwidth, videoConfig); 357 | 358 | acct_cfg.videoConfig = videoConfig; 359 | } 360 | 361 | return acct_cfg; 362 | } 363 | 364 | void SIPSTERAccount::onRegStarted(OnRegStartedParam &prm) { 365 | SETUP_EVENT(REGSTARTING); 366 | ev.acct = this; 367 | 368 | args->renew = prm.renew; 369 | 370 | ENQUEUE_EVENT(ev); 371 | } 372 | 373 | void SIPSTERAccount::onRegState(OnRegStateParam &prm) { 374 | AccountInfo ai = getInfo(); 375 | 376 | SETUP_EVENT(REGSTATE); 377 | ev.acct = this; 378 | 379 | args->active = ai.regIsActive; 380 | args->statusCode = prm.code; 381 | 382 | ENQUEUE_EVENT(ev); 383 | } 384 | 385 | void SIPSTERAccount::onIncomingCall(OnIncomingCallParam &iprm) { 386 | SIPSTERCall* call = new SIPSTERCall(*this, iprm.callId); 387 | CallInfo ci = call->getInfo(); 388 | 389 | SETUP_EVENT(INCALL); 390 | ev.call = call; 391 | ev.acct = this; 392 | 393 | args->localContact = ci.localContact; 394 | args->remoteContact = ci.remoteContact; 395 | args->callId = ci.callIdString; 396 | args->srcAddress = iprm.rdata.srcAddress; 397 | 398 | const char* msgcstr = NULL; 399 | int res; 400 | 401 | // pjsip replaces uri info if it exceeds 128 characters, so we have to 402 | // get the real uris from the original SIP message 403 | if (ci.remoteUri == "<-error: uri too long->") { 404 | msgcstr = iprm.rdata.wholeMsg.c_str(); 405 | regmatch_t match[2]; 406 | res = regexec(&fromuri_regex, msgcstr, 2, match, 0); 407 | if (res == 0) { 408 | args->remoteUri = string(msgcstr + match[1].rm_so, 409 | match[1].rm_eo - match[1].rm_so); 410 | } 411 | } else 412 | args->remoteUri = ci.remoteUri; 413 | 414 | if (ci.localUri == "<-error: uri too long->") { 415 | if (!msgcstr) 416 | msgcstr = iprm.rdata.wholeMsg.c_str(); 417 | regmatch_t match[2]; 418 | res = regexec(&touri_regex, msgcstr, 2, match, 0); 419 | if (res == 0) { 420 | args->localUri = string(msgcstr + match[1].rm_so, 421 | match[1].rm_eo - match[1].rm_so); 422 | } 423 | } else 424 | args->localUri = ci.localUri; 425 | 426 | ENQUEUE_EVENT(ev); 427 | } 428 | 429 | NAN_METHOD(SIPSTERAccount::New) { 430 | Nan::HandleScope scope; 431 | 432 | if (!info.IsConstructCall()) 433 | return Nan::ThrowError("Use `new` to create instances of this object."); 434 | 435 | SIPSTERAccount* acct = new SIPSTERAccount(); 436 | 437 | AccountConfig acct_cfg; 438 | string errstr; 439 | bool isDefault = false; 440 | if (info.Length() > 0 && info[0]->IsObject()) { 441 | acct_cfg = genConfig(info[0]->ToObject()); 442 | 443 | if (info.Length() > 1 && info[1]->IsBoolean()) 444 | isDefault = info[1]->BooleanValue(); 445 | } else if (info.Length() > 0 && info[0]->IsBoolean()) 446 | isDefault = info[0]->BooleanValue(); 447 | 448 | try { 449 | acct->create(acct_cfg, isDefault); 450 | } catch(Error& err) { 451 | delete acct; 452 | errstr = "Account.create() error: " + err.info(); 453 | return Nan::ThrowError(errstr.c_str()); 454 | } 455 | 456 | acct->Wrap(info.This()); 457 | acct->Ref(); 458 | 459 | acct->emit = new Nan::Callback( 460 | Local::Cast(acct->handle()->Get(Nan::New(emit_symbol))) 461 | ); 462 | 463 | info.GetReturnValue().Set(info.This()); 464 | } 465 | 466 | NAN_METHOD(SIPSTERAccount::Modify) { 467 | Nan::HandleScope scope; 468 | SIPSTERAccount* acct = Nan::ObjectWrap::Unwrap(info.This()); 469 | 470 | AccountConfig acct_cfg; 471 | if (info.Length() > 0 && info[0]->IsObject()) 472 | acct_cfg = genConfig(info[0]->ToObject()); 473 | else 474 | return Nan::ThrowTypeError("Missing renew argument"); 475 | 476 | try { 477 | acct->modify(acct_cfg); 478 | } catch(Error& err) { 479 | string errstr = "Account->modify() error: " + err.info(); 480 | return Nan::ThrowError(errstr.c_str()); 481 | } 482 | 483 | info.GetReturnValue().SetUndefined(); 484 | } 485 | 486 | NAN_GETTER(SIPSTERAccount::ValidGetter) { 487 | SIPSTERAccount* acct = Nan::ObjectWrap::Unwrap(info.This()); 488 | info.GetReturnValue().Set(Nan::New(acct->isValid())); 489 | } 490 | 491 | NAN_GETTER(SIPSTERAccount::DefaultGetter) { 492 | SIPSTERAccount* acct = Nan::ObjectWrap::Unwrap(info.This()); 493 | info.GetReturnValue().Set(Nan::New(acct->isDefault())); 494 | } 495 | 496 | NAN_SETTER(SIPSTERAccount::DefaultSetter) { 497 | Nan::HandleScope scope; 498 | SIPSTERAccount* acct = Nan::ObjectWrap::Unwrap(info.This()); 499 | 500 | if (value->BooleanValue()) { 501 | try { 502 | acct->setDefault(); 503 | } catch(Error& err) { 504 | string errstr = "Account->setDefault() error: " + err.info(); 505 | return Nan::ThrowError(errstr.c_str()); 506 | } 507 | } 508 | } 509 | 510 | NAN_METHOD(SIPSTERAccount::GetInfo) { 511 | Nan::HandleScope scope; 512 | SIPSTERAccount* acct = Nan::ObjectWrap::Unwrap(info.This()); 513 | 514 | AccountInfo ai; 515 | try { 516 | ai = acct->getInfo(); 517 | } catch(Error& err) { 518 | string errstr = "Account->getInfo() error: " + err.info(); 519 | return Nan::ThrowError(errstr.c_str()); 520 | } 521 | 522 | Local info_obj = Nan::New(); 523 | Nan::Set(info_obj, 524 | Nan::New("uri").ToLocalChecked(), 525 | Nan::New(ai.uri.c_str()).ToLocalChecked()); 526 | Nan::Set(info_obj, 527 | Nan::New("regIsConfigured").ToLocalChecked(), 528 | Nan::New(ai.regIsConfigured)); 529 | Nan::Set(info_obj, 530 | Nan::New("regIsActive").ToLocalChecked(), 531 | Nan::New(ai.regIsActive)); 532 | Nan::Set(info_obj, 533 | Nan::New("regExpiresSec").ToLocalChecked(), 534 | Nan::New(ai.regExpiresSec)); 535 | // TODO: onlineStatus*, regStatus*, regLastErr? 536 | 537 | info.GetReturnValue().Set(info_obj); 538 | } 539 | 540 | NAN_METHOD(SIPSTERAccount::SetRegistration) { 541 | Nan::HandleScope scope; 542 | SIPSTERAccount* acct = Nan::ObjectWrap::Unwrap(info.This()); 543 | 544 | bool renew; 545 | if (info.Length() > 0 && info[0]->IsBoolean()) 546 | renew = info[0]->BooleanValue(); 547 | else 548 | return Nan::ThrowTypeError("Missing renew argument"); 549 | 550 | try { 551 | acct->setRegistration(renew); 552 | } catch(Error& err) { 553 | string errstr = "Account->setRegistration() error: " + err.info(); 554 | return Nan::ThrowError(errstr.c_str()); 555 | } 556 | 557 | info.GetReturnValue().SetUndefined(); 558 | } 559 | 560 | NAN_METHOD(SIPSTERAccount::SetTransport) { 561 | Nan::HandleScope scope; 562 | SIPSTERAccount* acct = Nan::ObjectWrap::Unwrap(info.This()); 563 | 564 | TransportId tid; 565 | if (info.Length() > 0 566 | && Nan::New(SIPSTERTransport_constructor)->HasInstance(info[0])) { 567 | SIPSTERTransport* trans = 568 | Nan::ObjectWrap::Unwrap(Local::Cast(info[0])); 569 | tid = trans->transId; 570 | } else 571 | return Nan::ThrowTypeError("Missing Transport instance"); 572 | 573 | try { 574 | acct->setTransport(tid); 575 | } catch(Error& err) { 576 | string errstr = "Account->setTransport() error: " + err.info(); 577 | return Nan::ThrowError(errstr.c_str()); 578 | } 579 | 580 | info.GetReturnValue().SetUndefined(); 581 | } 582 | 583 | NAN_METHOD(SIPSTERAccount::MakeCall) { 584 | Nan::HandleScope scope; 585 | 586 | string dest; 587 | CallOpParam prm; 588 | if (info.Length() > 0 && info[0]->IsString()) { 589 | Nan::Utf8String dest_str(info[0]); 590 | dest = string(*dest_str); 591 | if (info.Length() > 1) { 592 | prm.statusCode = static_cast(info[1]->Int32Value()); 593 | if (info.Length() > 2 && info[2]->IsString()) { 594 | Nan::Utf8String reason_str(info[2]); 595 | prm.reason = string(*reason_str); 596 | } 597 | } 598 | } else 599 | return Nan::ThrowTypeError("Missing call destination"); 600 | 601 | Handle new_call_args[1] = { info.This() }; 602 | Local call_obj = 603 | Nan::New(SIPSTERCall_constructor)->GetFunction() 604 | ->NewInstance(1, new_call_args); 605 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(call_obj); 606 | 607 | try { 608 | call->makeCall(dest, prm); 609 | } catch(Error& err) { 610 | string errstr = "Call.makeCall() error: " + err.info(); 611 | return Nan::ThrowError(errstr.c_str()); 612 | } 613 | 614 | info.GetReturnValue().Set(call_obj); 615 | } 616 | 617 | NAN_METHOD(SIPSTERAccount::DoRef) { 618 | Nan::HandleScope scope; 619 | SIPSTERAccount* acct = Nan::ObjectWrap::Unwrap(info.This()); 620 | 621 | acct->Ref(); 622 | 623 | info.GetReturnValue().SetUndefined(); 624 | } 625 | 626 | NAN_METHOD(SIPSTERAccount::DoUnref) { 627 | Nan::HandleScope scope; 628 | SIPSTERAccount* acct = Nan::ObjectWrap::Unwrap(info.This()); 629 | 630 | acct->Unref(); 631 | 632 | info.GetReturnValue().SetUndefined(); 633 | } 634 | 635 | void SIPSTERAccount::Initialize(Handle target) { 636 | Nan::HandleScope scope; 637 | 638 | Local tpl = Nan::New(New); 639 | Local name = Nan::New("Account").ToLocalChecked(); 640 | 641 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 642 | tpl->SetClassName(name); 643 | 644 | Nan::SetPrototypeMethod(tpl, "modify", Modify); 645 | Nan::SetPrototypeMethod(tpl, "makeCall", MakeCall); 646 | Nan::SetPrototypeMethod(tpl, "getInfo", GetInfo); 647 | Nan::SetPrototypeMethod(tpl, "setRegistration", SetRegistration); 648 | Nan::SetPrototypeMethod(tpl, "setTransport", SetTransport); 649 | Nan::SetPrototypeMethod(tpl, "ref", DoRef); 650 | Nan::SetPrototypeMethod(tpl, "unref", DoUnref); 651 | 652 | Nan::SetAccessor(tpl->PrototypeTemplate(), 653 | Nan::New("valid").ToLocalChecked(), 654 | ValidGetter); 655 | Nan::SetAccessor(tpl->PrototypeTemplate(), 656 | Nan::New("default").ToLocalChecked(), 657 | DefaultGetter, 658 | DefaultSetter); 659 | 660 | Nan::Set(target, name, tpl->GetFunction()); 661 | 662 | SIPSTERAccount_constructor.Reset(tpl); 663 | 664 | int res; 665 | res = regcomp(&fromuri_regex, 666 | "^From:.*()", 667 | REG_ICASE|REG_EXTENDED|REG_NEWLINE); 668 | if (res != 0) { 669 | char errbuf[128]; 670 | errbuf[0] = '\0'; 671 | regerror(res, &fromuri_regex, errbuf, 128); 672 | fprintf(stderr, "Could not compile 'From:' URI regex: %s\n", errbuf); 673 | exit(1); 674 | } 675 | 676 | res = regcomp(&touri_regex, 677 | "^To:.*()", 678 | REG_ICASE|REG_EXTENDED|REG_NEWLINE); 679 | if (res != 0) { 680 | char errbuf[128]; 681 | errbuf[0] = '\0'; 682 | regerror(res, &touri_regex, errbuf, 128); 683 | fprintf(stderr, "Could not compile 'To:' URI regex: %s\n", errbuf); 684 | exit(1); 685 | } 686 | } 687 | -------------------------------------------------------------------------------- /src/SIPSTERAccount.h: -------------------------------------------------------------------------------- 1 | #ifndef SIPSTERACCOUNT_H_ 2 | #define SIPSTERACCOUNT_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | using namespace node; 13 | using namespace v8; 14 | using namespace pj; 15 | 16 | class SIPSTERAccount : public Account, public Nan::ObjectWrap { 17 | public: 18 | Nan::Callback* emit; 19 | 20 | SIPSTERAccount(); 21 | ~SIPSTERAccount(); 22 | 23 | static AccountConfig genConfig(Local acct_obj); 24 | virtual void onRegStarted(OnRegStartedParam &prm); 25 | virtual void onRegState(OnRegStateParam &prm); 26 | virtual void onIncomingCall(OnIncomingCallParam &iprm); 27 | static NAN_METHOD(New); 28 | static NAN_METHOD(Modify); 29 | static NAN_GETTER(ValidGetter); 30 | static NAN_GETTER(DefaultGetter); 31 | static NAN_SETTER(DefaultSetter); 32 | static NAN_METHOD(GetInfo); 33 | static NAN_METHOD(SetRegistration); 34 | static NAN_METHOD(SetTransport); 35 | static NAN_METHOD(MakeCall); 36 | static NAN_METHOD(DoRef); 37 | static NAN_METHOD(DoUnref); 38 | static void Initialize(Handle target); 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/SIPSTERCall.cc: -------------------------------------------------------------------------------- 1 | #include "SIPSTERCall.h" 2 | #include "SIPSTERAccount.h" 3 | #include "common.h" 4 | 5 | Nan::Persistent SIPSTERCall_constructor; 6 | 7 | SIPSTERCall::SIPSTERCall(Account &acc, int call_id) : Call(acc, call_id) { 8 | emit = NULL; 9 | uv_mutex_lock(&async_mutex); 10 | uv_ref(reinterpret_cast(&dumb)); 11 | uv_mutex_unlock(&async_mutex); 12 | } 13 | 14 | SIPSTERCall::~SIPSTERCall() { 15 | if (emit) 16 | delete emit; 17 | uv_mutex_lock(&async_mutex); 18 | uv_unref(reinterpret_cast(&dumb)); 19 | uv_mutex_unlock(&async_mutex); 20 | } 21 | 22 | void SIPSTERCall::onCallMediaState(OnCallMediaStateParam &prm) { 23 | SETUP_EVENT_NOARGS(CALLMEDIA); 24 | ev.call = this; 25 | 26 | ENQUEUE_EVENT(ev); 27 | } 28 | 29 | void SIPSTERCall::onCallState(OnCallStateParam &prm) { 30 | CallInfo ci = getInfo(); 31 | 32 | SETUP_EVENT(CALLSTATE); 33 | ev.call = this; 34 | 35 | args->_state = ci.state; 36 | 37 | ENQUEUE_EVENT(ev); 38 | } 39 | 40 | void SIPSTERCall::onDtmfDigit(OnDtmfDigitParam &prm) { 41 | CallInfo ci = getInfo(); 42 | 43 | SETUP_EVENT(CALLDTMF); 44 | ev.call = this; 45 | 46 | args->digit = prm.digit[0]; 47 | 48 | ENQUEUE_EVENT(ev); 49 | } 50 | 51 | NAN_METHOD(SIPSTERCall::New) { 52 | Nan::HandleScope scope; 53 | 54 | if (!info.IsConstructCall()) 55 | return Nan::ThrowError("Use `new` to create instances of this object."); 56 | 57 | SIPSTERCall* call = NULL; 58 | if (info.Length() > 0) { 59 | if (Nan::New(SIPSTERAccount_constructor)->HasInstance(info[0])) { 60 | Account* acct = 61 | Nan::ObjectWrap::Unwrap(Local::Cast(info[0])); 62 | call = new SIPSTERCall(*acct); 63 | } else { 64 | Local extCall = Local::Cast(info[0]); 65 | call = static_cast(extCall->Value()); 66 | } 67 | } else 68 | return Nan::ThrowError("Expected callId or Account argument"); 69 | 70 | call->Wrap(info.This()); 71 | 72 | call->emit = new Nan::Callback( 73 | Local::Cast(call->handle()->Get(Nan::New(emit_symbol))) 74 | ); 75 | 76 | info.GetReturnValue().Set(info.This()); 77 | } 78 | 79 | NAN_METHOD(SIPSTERCall::Answer) { 80 | Nan::HandleScope scope; 81 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 82 | 83 | CallOpParam prm; 84 | if (info.Length() > 0 && info[0]->IsUint32()) { 85 | prm.statusCode = static_cast(info[0]->Int32Value()); 86 | if (info.Length() > 1 && info[1]->IsString()) { 87 | Nan::Utf8String reason_str(info[1]); 88 | prm.reason = string(*reason_str); 89 | } 90 | } else 91 | prm.statusCode = PJSIP_SC_OK; 92 | 93 | try { 94 | call->answer(prm); 95 | } catch(Error& err) { 96 | string errstr = "Call.answer() error: " + err.info(); 97 | return Nan::ThrowError(errstr.c_str()); 98 | } 99 | 100 | info.GetReturnValue().SetUndefined(); 101 | } 102 | 103 | NAN_METHOD(SIPSTERCall::Hangup) { 104 | Nan::HandleScope scope; 105 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 106 | 107 | CallOpParam prm; 108 | if (info.Length() > 0 && info[0]->IsUint32()) { 109 | prm.statusCode = static_cast(info[0]->Int32Value()); 110 | if (info.Length() > 1 && info[1]->IsString()) { 111 | Nan::Utf8String reason_str(info[1]); 112 | prm.reason = string(*reason_str); 113 | } 114 | } 115 | 116 | try { 117 | call->hangup(prm); 118 | } catch(Error& err) { 119 | string errstr = "Call.hangup() error: " + err.info(); 120 | return Nan::ThrowError(errstr.c_str()); 121 | } 122 | 123 | info.GetReturnValue().SetUndefined(); 124 | } 125 | 126 | NAN_METHOD(SIPSTERCall::SetHold) { 127 | Nan::HandleScope scope; 128 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 129 | 130 | CallOpParam prm; 131 | if (info.Length() > 0 && info[0]->IsUint32()) { 132 | prm.statusCode = static_cast(info[0]->Int32Value()); 133 | if (info.Length() > 1 && info[1]->IsString()) { 134 | Nan::Utf8String reason_str(info[1]); 135 | prm.reason = string(*reason_str); 136 | } 137 | } 138 | 139 | try { 140 | call->setHold(prm); 141 | } catch(Error& err) { 142 | string errstr = "Call.setHold() error: " + err.info(); 143 | return Nan::ThrowError(errstr.c_str()); 144 | } 145 | 146 | info.GetReturnValue().SetUndefined(); 147 | } 148 | 149 | NAN_METHOD(SIPSTERCall::Reinvite) { 150 | Nan::HandleScope scope; 151 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 152 | 153 | CallOpParam prm; 154 | if (info.Length() > 0 && info[0]->IsUint32()) { 155 | prm.statusCode = static_cast(info[0]->Int32Value()); 156 | if (info.Length() > 1 && info[1]->IsString()) { 157 | Nan::Utf8String reason_str(info[1]); 158 | prm.reason = string(*reason_str); 159 | } 160 | } 161 | 162 | try { 163 | call->reinvite(prm); 164 | } catch(Error& err) { 165 | string errstr = "Call.reinvite() error: " + err.info(); 166 | return Nan::ThrowError(errstr.c_str()); 167 | } 168 | 169 | info.GetReturnValue().SetUndefined(); 170 | } 171 | 172 | NAN_METHOD(SIPSTERCall::Update) { 173 | Nan::HandleScope scope; 174 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 175 | 176 | CallOpParam prm; 177 | if (info.Length() > 0 && info[0]->IsUint32()) { 178 | prm.statusCode = static_cast(info[0]->Int32Value()); 179 | if (info.Length() > 1 && info[1]->IsString()) { 180 | Nan::Utf8String reason_str(info[1]); 181 | prm.reason = string(*reason_str); 182 | } 183 | } 184 | 185 | try { 186 | call->update(prm); 187 | } catch(Error& err) { 188 | string errstr = "Call.update() error: " + err.info(); 189 | return Nan::ThrowError(errstr.c_str()); 190 | } 191 | 192 | info.GetReturnValue().SetUndefined(); 193 | } 194 | 195 | NAN_METHOD(SIPSTERCall::DialDtmf) { 196 | Nan::HandleScope scope; 197 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 198 | 199 | if (info.Length() > 0 && info[0]->IsString()) { 200 | try { 201 | Nan::Utf8String dtmf_str(info[0]); 202 | call->dialDtmf(string(*dtmf_str)); 203 | } catch(Error& err) { 204 | string errstr = "Call.dialDtmf() error: " + err.info(); 205 | return Nan::ThrowError(errstr.c_str()); 206 | } 207 | } else 208 | return Nan::ThrowTypeError("Missing DTMF string"); 209 | 210 | info.GetReturnValue().SetUndefined(); 211 | } 212 | 213 | NAN_METHOD(SIPSTERCall::Transfer) { 214 | Nan::HandleScope scope; 215 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 216 | 217 | string dest; 218 | CallOpParam prm; 219 | if (info.Length() > 0 && info[0]->IsString()) { 220 | Nan::Utf8String dest_str(info[0]); 221 | dest = string(*dest_str); 222 | if (info.Length() > 1) { 223 | prm.statusCode = static_cast(info[1]->Int32Value()); 224 | if (info.Length() > 2 && info[2]->IsString()) { 225 | Nan::Utf8String reason_str(info[2]); 226 | prm.reason = string(*reason_str); 227 | } 228 | } 229 | } else 230 | return Nan::ThrowTypeError("Missing transfer destination"); 231 | 232 | try { 233 | call->xfer(dest, prm); 234 | } catch(Error& err) { 235 | string errstr = "Call.xfer() error: " + err.info(); 236 | return Nan::ThrowError(errstr.c_str()); 237 | } 238 | 239 | info.GetReturnValue().SetUndefined(); 240 | } 241 | 242 | NAN_METHOD(SIPSTERCall::DoRef) { 243 | Nan::HandleScope scope; 244 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 245 | 246 | call->Ref(); 247 | 248 | info.GetReturnValue().SetUndefined(); 249 | } 250 | 251 | NAN_METHOD(SIPSTERCall::DoUnref) { 252 | Nan::HandleScope scope; 253 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 254 | 255 | call->Unref(); 256 | 257 | info.GetReturnValue().SetUndefined(); 258 | } 259 | 260 | NAN_METHOD(SIPSTERCall::GetStats) { 261 | Nan::HandleScope scope; 262 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 263 | 264 | bool with_media = true; 265 | string indent = " "; 266 | if (info.Length() > 0 && info[0]->IsBoolean()) { 267 | with_media = info[0]->BooleanValue(); 268 | if (info.Length() > 1 && info[1]->IsString()) { 269 | Nan::Utf8String indent_str(info[1]); 270 | indent = string(*indent_str); 271 | } 272 | } 273 | 274 | string stats_info; 275 | try { 276 | stats_info = call->dump(with_media, indent); 277 | } catch(Error& err) { 278 | string errstr = "Call.dump() error: " + err.info(); 279 | return Nan::ThrowError(errstr.c_str()); 280 | } 281 | 282 | info.GetReturnValue().Set(Nan::New(stats_info.c_str()).ToLocalChecked()); 283 | } 284 | 285 | NAN_GETTER(SIPSTERCall::ConDurationGetter) { 286 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 287 | 288 | CallInfo ci; 289 | 290 | try { 291 | ci = call->getInfo(); 292 | } catch(Error& err) { 293 | string errstr = "Call.getInfo() error: " + err.info(); 294 | return Nan::ThrowError(errstr.c_str()); 295 | } 296 | 297 | double duration = ci.connectDuration.sec + (ci.connectDuration.msec / 1000); 298 | 299 | info.GetReturnValue().Set(Nan::New(duration)); 300 | } 301 | 302 | NAN_GETTER(SIPSTERCall::TotDurationGetter) { 303 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 304 | 305 | CallInfo ci; 306 | 307 | try { 308 | ci = call->getInfo(); 309 | } catch(Error& err) { 310 | string errstr = "Call.getInfo() error: " + err.info(); 311 | return Nan::ThrowError(errstr.c_str()); 312 | } 313 | 314 | double duration = ci.totalDuration.sec + (ci.totalDuration.msec / 1000); 315 | 316 | info.GetReturnValue().Set(Nan::New(duration)); 317 | } 318 | 319 | NAN_GETTER(SIPSTERCall::HasMediaGetter) { 320 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 321 | 322 | info.GetReturnValue().Set(Nan::New(call->hasMedia())); 323 | } 324 | 325 | NAN_GETTER(SIPSTERCall::IsActiveGetter) { 326 | SIPSTERCall* call = Nan::ObjectWrap::Unwrap(info.This()); 327 | 328 | info.GetReturnValue().Set(Nan::New(call->isActive())); 329 | } 330 | 331 | void SIPSTERCall::Initialize(Handle target) { 332 | Nan::HandleScope scope; 333 | 334 | Local tpl = Nan::New(New); 335 | Local name = Nan::New("Call").ToLocalChecked(); 336 | 337 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 338 | tpl->SetClassName(name); 339 | 340 | Nan::SetPrototypeMethod(tpl, "answer", Answer); 341 | Nan::SetPrototypeMethod(tpl, "hangup", Hangup); 342 | Nan::SetPrototypeMethod(tpl, "hold", SetHold); 343 | Nan::SetPrototypeMethod(tpl, "reinvite", Reinvite); 344 | Nan::SetPrototypeMethod(tpl, "update", Update); 345 | Nan::SetPrototypeMethod(tpl, "dtmf", DialDtmf); 346 | Nan::SetPrototypeMethod(tpl, "transfer", Transfer); 347 | Nan::SetPrototypeMethod(tpl, "ref", DoRef); 348 | Nan::SetPrototypeMethod(tpl, "unref", DoUnref); 349 | Nan::SetPrototypeMethod(tpl, "getStatsDump", GetStats); 350 | 351 | Nan::SetAccessor(tpl->PrototypeTemplate(), 352 | Nan::New("connDuration").ToLocalChecked(), 353 | ConDurationGetter); 354 | Nan::SetAccessor(tpl->PrototypeTemplate(), 355 | Nan::New("totalDuration").ToLocalChecked(), 356 | TotDurationGetter); 357 | Nan::SetAccessor(tpl->PrototypeTemplate(), 358 | Nan::New("hasMedia").ToLocalChecked(), 359 | HasMediaGetter); 360 | Nan::SetAccessor(tpl->PrototypeTemplate(), 361 | Nan::New("isActive").ToLocalChecked(), 362 | IsActiveGetter); 363 | 364 | Nan::Set(target, name, tpl->GetFunction()); 365 | 366 | SIPSTERCall_constructor.Reset(tpl); 367 | } 368 | -------------------------------------------------------------------------------- /src/SIPSTERCall.h: -------------------------------------------------------------------------------- 1 | #ifndef SIPSTERCALL_H_ 2 | #define SIPSTERCALL_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | using namespace node; 13 | using namespace v8; 14 | using namespace pj; 15 | 16 | class SIPSTERCall : public Call, public Nan::ObjectWrap { 17 | public: 18 | Nan::Callback* emit; 19 | 20 | SIPSTERCall(Account &acc, int call_id=PJSUA_INVALID_ID); 21 | ~SIPSTERCall(); 22 | 23 | void onCallMediaState(OnCallMediaStateParam &prm); 24 | virtual void onCallState(OnCallStateParam &prm); 25 | virtual void onDtmfDigit(OnDtmfDigitParam &prm); 26 | static NAN_METHOD(New); 27 | static NAN_METHOD(Answer); 28 | static NAN_METHOD(Hangup); 29 | static NAN_METHOD(SetHold); 30 | static NAN_METHOD(Reinvite); 31 | static NAN_METHOD(Update); 32 | static NAN_METHOD(DialDtmf); 33 | static NAN_METHOD(Transfer); 34 | static NAN_METHOD(DoRef); 35 | static NAN_METHOD(DoUnref); 36 | static NAN_METHOD(GetStats); 37 | static NAN_GETTER(ConDurationGetter); 38 | static NAN_GETTER(TotDurationGetter); 39 | static NAN_GETTER(HasMediaGetter); 40 | static NAN_GETTER(IsActiveGetter); 41 | static void Initialize(Handle target); 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/SIPSTERMedia.cc: -------------------------------------------------------------------------------- 1 | #include "SIPSTERMedia.h" 2 | #include "common.h" 3 | 4 | Nan::Persistent SIPSTERMedia_constructor; 5 | 6 | static Nan::Persistent media_dir_none_symbol; 7 | static Nan::Persistent media_dir_outbound_symbol; 8 | static Nan::Persistent media_dir_inbound_symbol; 9 | static Nan::Persistent media_dir_bidi_symbol; 10 | static Nan::Persistent media_dir_unknown_symbol; 11 | 12 | SIPSTERMedia::SIPSTERMedia() : emit(NULL), media(NULL), is_media_new(false) {} 13 | SIPSTERMedia::~SIPSTERMedia() { 14 | if (media && is_media_new) { 15 | delete media; 16 | } 17 | if (emit) { 18 | delete emit; 19 | } 20 | is_media_new = false; 21 | } 22 | 23 | NAN_METHOD(SIPSTERMedia::New) { 24 | Nan::HandleScope scope; 25 | 26 | if (!info.IsConstructCall()) 27 | return Nan::ThrowError("Use `new` to create instances of this object."); 28 | 29 | SIPSTERMedia* med = new SIPSTERMedia(); 30 | 31 | med->Wrap(info.This()); 32 | 33 | med->emit = new Nan::Callback( 34 | Local::Cast(med->handle()->Get(Nan::New(emit_symbol))) 35 | ); 36 | 37 | info.GetReturnValue().Set(info.This()); 38 | } 39 | 40 | NAN_METHOD(SIPSTERMedia::StartTransmit) { 41 | Nan::HandleScope scope; 42 | SIPSTERMedia* src = Nan::ObjectWrap::Unwrap(info.This()); 43 | 44 | if (info.Length() == 0 45 | || !Nan::New(SIPSTERMedia_constructor)->HasInstance(info[0])) 46 | return Nan::ThrowTypeError("Expected Media object"); 47 | 48 | SIPSTERMedia* dest = 49 | Nan::ObjectWrap::Unwrap(Local::Cast(info[0])); 50 | 51 | if (!src->media) 52 | return Nan::ThrowError("Invalid source"); 53 | else if (!dest->media) 54 | return Nan::ThrowError("Invalid destination"); 55 | 56 | try { 57 | src->media->startTransmit(*dest->media); 58 | } catch(Error& err) { 59 | string errstr = "AudioMedia.startTransmit() error: " + err.info(); 60 | return Nan::ThrowError(errstr.c_str()); 61 | } 62 | 63 | info.GetReturnValue().SetUndefined(); 64 | } 65 | 66 | NAN_METHOD(SIPSTERMedia::StopTransmit) { 67 | Nan::HandleScope scope; 68 | SIPSTERMedia* src = Nan::ObjectWrap::Unwrap(info.This()); 69 | 70 | if (info.Length() == 0 71 | || !Nan::New(SIPSTERMedia_constructor)->HasInstance(info[0])) 72 | return Nan::ThrowTypeError("Expected Media object"); 73 | 74 | SIPSTERMedia* dest = 75 | Nan::ObjectWrap::Unwrap(Local::Cast(info[0])); 76 | 77 | if (!src->media) 78 | return Nan::ThrowError("Invalid source"); 79 | else if (!dest->media) 80 | return Nan::ThrowError("Invalid destination"); 81 | 82 | try { 83 | src->media->stopTransmit(*dest->media); 84 | } catch(Error& err) { 85 | string errstr = "AudioMedia.stopTransmit() error: " + err.info(); 86 | return Nan::ThrowError(errstr.c_str()); 87 | } 88 | 89 | info.GetReturnValue().SetUndefined(); 90 | } 91 | 92 | NAN_METHOD(SIPSTERMedia::AdjustRxLevel) { 93 | Nan::HandleScope scope; 94 | SIPSTERMedia* med = Nan::ObjectWrap::Unwrap(info.This()); 95 | 96 | if (med->media) { 97 | if (info.Length() > 0 && info[0]->IsNumber()) { 98 | try { 99 | med->media->adjustRxLevel(static_cast(info[0]->NumberValue())); 100 | } catch(Error& err) { 101 | string errstr = "AudioMedia.adjustRxLevel() error: " + err.info(); 102 | return Nan::ThrowError(errstr.c_str()); 103 | } 104 | } else 105 | return Nan::ThrowTypeError("Missing signal level"); 106 | } 107 | 108 | info.GetReturnValue().SetUndefined(); 109 | } 110 | 111 | NAN_METHOD(SIPSTERMedia::AdjustTxLevel) { 112 | Nan::HandleScope scope; 113 | SIPSTERMedia* med = Nan::ObjectWrap::Unwrap(info.This()); 114 | 115 | if (med->media) { 116 | if (info.Length() > 0 && info[0]->IsNumber()) { 117 | try { 118 | med->media->adjustTxLevel(static_cast(info[0]->NumberValue())); 119 | } catch(Error& err) { 120 | string errstr = "AudioMedia.adjustTxLevel() error: " + err.info(); 121 | return Nan::ThrowError(errstr.c_str()); 122 | } 123 | } else 124 | return Nan::ThrowTypeError("Missing signal level"); 125 | } 126 | 127 | info.GetReturnValue().SetUndefined(); 128 | } 129 | 130 | NAN_METHOD(SIPSTERMedia::Close) { 131 | Nan::HandleScope scope; 132 | SIPSTERMedia* med = Nan::ObjectWrap::Unwrap(info.This()); 133 | 134 | if (med->media && med->is_media_new) { 135 | delete med->media; 136 | med->media = NULL; 137 | } 138 | 139 | info.GetReturnValue().SetUndefined(); 140 | } 141 | 142 | NAN_GETTER(SIPSTERMedia::RxLevelGetter) { 143 | SIPSTERMedia* med = Nan::ObjectWrap::Unwrap(info.This()); 144 | 145 | unsigned level = 0; 146 | 147 | if (med->media) { 148 | try { 149 | level = med->media->getRxLevel(); 150 | } catch(Error& err) { 151 | string errstr = "AudioMedia.getRxLevel() error: " + err.info(); 152 | return Nan::ThrowError(errstr.c_str()); 153 | } 154 | } 155 | 156 | info.GetReturnValue().Set(Nan::New(level)); 157 | } 158 | 159 | NAN_GETTER(SIPSTERMedia::TxLevelGetter) { 160 | SIPSTERMedia* med = Nan::ObjectWrap::Unwrap(info.This()); 161 | 162 | unsigned level = 0; 163 | 164 | if (med->media) { 165 | try { 166 | level = med->media->getTxLevel(); 167 | } catch(Error& err) { 168 | string errstr = "AudioMedia.getTxLevel() error: " + err.info(); 169 | return Nan::ThrowError(errstr.c_str()); 170 | } 171 | } 172 | 173 | info.GetReturnValue().Set(Nan::New(level)); 174 | } 175 | 176 | NAN_GETTER(SIPSTERMedia::DirGetter) { 177 | SIPSTERMedia* med = Nan::ObjectWrap::Unwrap(info.This()); 178 | Local str; 179 | switch (med->dir) { 180 | case PJMEDIA_DIR_NONE: 181 | str = Nan::New(media_dir_none_symbol); 182 | break; 183 | case PJMEDIA_DIR_ENCODING: 184 | str = Nan::New(media_dir_outbound_symbol); 185 | break; 186 | case PJMEDIA_DIR_DECODING: 187 | str = Nan::New(media_dir_inbound_symbol); 188 | break; 189 | case PJMEDIA_DIR_ENCODING_DECODING: 190 | str = Nan::New(media_dir_bidi_symbol); 191 | break; 192 | default: 193 | str = Nan::New(media_dir_unknown_symbol); 194 | } 195 | info.GetReturnValue().Set(str); 196 | } 197 | 198 | NAN_GETTER(SIPSTERMedia::SrcRTPGetter) { 199 | SIPSTERMedia* med = Nan::ObjectWrap::Unwrap(info.This()); 200 | 201 | info.GetReturnValue().Set(Nan::New(med->srcRTP.c_str()).ToLocalChecked()); 202 | } 203 | 204 | NAN_GETTER(SIPSTERMedia::SrcRTCPGetter) { 205 | SIPSTERMedia* med = Nan::ObjectWrap::Unwrap(info.This()); 206 | 207 | info.GetReturnValue().Set(Nan::New(med->srcRTCP.c_str()).ToLocalChecked()); 208 | } 209 | 210 | void SIPSTERMedia::Initialize(Handle target) { 211 | Nan::HandleScope scope; 212 | 213 | Local tpl = Nan::New(New); 214 | Local name = Nan::New("Media").ToLocalChecked(); 215 | 216 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 217 | tpl->SetClassName(name); 218 | 219 | media_dir_none_symbol.Reset(Nan::New("none").ToLocalChecked()); 220 | media_dir_outbound_symbol.Reset(Nan::New("outbound").ToLocalChecked()); 221 | media_dir_inbound_symbol.Reset(Nan::New("inbound").ToLocalChecked()); 222 | media_dir_bidi_symbol.Reset(Nan::New("bidirectional").ToLocalChecked()); 223 | media_dir_unknown_symbol.Reset(Nan::New("unknown").ToLocalChecked()); 224 | 225 | Nan::SetPrototypeMethod(tpl, "startTransmitTo", StartTransmit); 226 | Nan::SetPrototypeMethod(tpl, "stopTransmitTo", StopTransmit); 227 | Nan::SetPrototypeMethod(tpl, "adjustRxLevel", AdjustRxLevel); 228 | Nan::SetPrototypeMethod(tpl, "adjustTxLevel", AdjustTxLevel); 229 | Nan::SetPrototypeMethod(tpl, "close", Close); 230 | 231 | Nan::SetAccessor(tpl->PrototypeTemplate(), 232 | Nan::New("dir").ToLocalChecked(), 233 | DirGetter); 234 | Nan::SetAccessor(tpl->PrototypeTemplate(), 235 | Nan::New("rtpAddr").ToLocalChecked(), 236 | SrcRTPGetter); 237 | Nan::SetAccessor(tpl->PrototypeTemplate(), 238 | Nan::New("rtcpAddr").ToLocalChecked(), 239 | SrcRTCPGetter); 240 | Nan::SetAccessor(tpl->PrototypeTemplate(), 241 | Nan::New("rxLevel").ToLocalChecked(), 242 | RxLevelGetter); 243 | Nan::SetAccessor(tpl->PrototypeTemplate(), 244 | Nan::New("txLevel").ToLocalChecked(), 245 | TxLevelGetter); 246 | 247 | Nan::Set(target, name, tpl->GetFunction()); 248 | 249 | SIPSTERMedia_constructor.Reset(tpl); 250 | } 251 | -------------------------------------------------------------------------------- /src/SIPSTERMedia.h: -------------------------------------------------------------------------------- 1 | #ifndef SIPSTERMEDIA_H_ 2 | #define SIPSTERMEDIA_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | using namespace node; 13 | using namespace v8; 14 | using namespace pj; 15 | 16 | class SIPSTERMedia : public Nan::ObjectWrap { 17 | public: 18 | Nan::Callback* emit; 19 | AudioMedia* media; 20 | bool is_media_new; 21 | pjmedia_dir dir; 22 | string srcRTP; 23 | string srcRTCP; 24 | 25 | 26 | SIPSTERMedia(); 27 | ~SIPSTERMedia(); 28 | 29 | static NAN_METHOD(New); 30 | static NAN_METHOD(StartTransmit); 31 | static NAN_METHOD(StopTransmit); 32 | static NAN_METHOD(AdjustRxLevel); 33 | static NAN_METHOD(AdjustTxLevel); 34 | static NAN_METHOD(Close); 35 | static NAN_GETTER(RxLevelGetter); 36 | static NAN_GETTER(TxLevelGetter); 37 | static NAN_GETTER(DirGetter); 38 | static NAN_GETTER(SrcRTPGetter); 39 | static NAN_GETTER(SrcRTCPGetter); 40 | static void Initialize(Handle target); 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/SIPSTERTransport.cc: -------------------------------------------------------------------------------- 1 | #include "SIPSTERTransport.h" 2 | #include "common.h" 3 | 4 | Nan::Persistent SIPSTERTransport_constructor; 5 | 6 | SIPSTERTransport::SIPSTERTransport() : transId(-1), enabled(false) {} 7 | SIPSTERTransport::~SIPSTERTransport() { 8 | if (emit) 9 | delete emit; 10 | if (transId > -1) { 11 | try { 12 | ep->transportClose(transId); 13 | uv_mutex_lock(&async_mutex); 14 | uv_unref(reinterpret_cast(&dumb)); 15 | uv_mutex_unlock(&async_mutex); 16 | } catch(Error& err) { 17 | string errstr = "transportClose error: " + err.info(); 18 | Nan::ThrowError(errstr.c_str()); 19 | } 20 | } 21 | } 22 | 23 | NAN_METHOD(SIPSTERTransport::New) { 24 | Nan::HandleScope scope; 25 | 26 | if (!info.IsConstructCall()) 27 | return Nan::ThrowError("Use `new` to create instances of this object."); 28 | 29 | TransportConfig tp_cfg; 30 | string errstr; 31 | 32 | Local val; 33 | if (info.Length() > 0 && info[0]->IsObject()) { 34 | Local obj = info[0]->ToObject(); 35 | JS2PJ_UINT(obj, port, tp_cfg); 36 | JS2PJ_UINT(obj, portRange, tp_cfg); 37 | JS2PJ_STR(obj, publicAddress, tp_cfg); 38 | JS2PJ_STR(obj, boundAddress, tp_cfg); 39 | JS2PJ_ENUM(obj, qosType, pj_qos_type, tp_cfg); 40 | 41 | val = obj->Get(Nan::New("qosParams").ToLocalChecked()); 42 | if (val->IsObject()) { 43 | pj_qos_params qos_params; 44 | Local qos_obj = val->ToObject(); 45 | Local flags_val = qos_obj->Get(Nan::New("flags").ToLocalChecked()); 46 | Local dscp_val = qos_obj->Get(Nan::New("dscp_val").ToLocalChecked()); 47 | Local so_prio_val = qos_obj->Get(Nan::New("so_prio").ToLocalChecked()); 48 | Local wmm_prio_val = qos_obj->Get(Nan::New("wmm_prio").ToLocalChecked()); 49 | if (flags_val->IsUint32()) 50 | qos_params.flags = static_cast(flags_val->Uint32Value()); 51 | if (dscp_val->IsUint32()) { 52 | qos_params.dscp_val = 53 | static_cast(dscp_val->Uint32Value()); 54 | } 55 | if (so_prio_val->IsUint32()) { 56 | qos_params.so_prio = 57 | static_cast(so_prio_val->Uint32Value()); 58 | } 59 | if (wmm_prio_val->IsUint32()) { 60 | qos_params.wmm_prio = 61 | static_cast(wmm_prio_val->Uint32Value()); 62 | } 63 | tp_cfg.qosParams = qos_params; 64 | } 65 | 66 | val = obj->Get(Nan::New("tlsConfig").ToLocalChecked()); 67 | if (val->IsObject()) { 68 | Local tls_obj = val->ToObject(); 69 | JS2PJ_STR(tls_obj, CaListFile, tp_cfg.tlsConfig); 70 | JS2PJ_STR(tls_obj, certFile, tp_cfg.tlsConfig); 71 | JS2PJ_STR(tls_obj, privKeyFile, tp_cfg.tlsConfig); 72 | JS2PJ_STR(tls_obj, password, tp_cfg.tlsConfig); 73 | JS2PJ_ENUM(tls_obj, method, pjsip_ssl_method, tp_cfg.tlsConfig); 74 | 75 | val = tls_obj->Get(Nan::New("ciphers").ToLocalChecked()); 76 | if (val->IsArray()) { 77 | const Local arr_obj = Local::Cast(val); 78 | const uint32_t arr_length = arr_obj->Length(); 79 | if (arr_length > 0) { 80 | vector ciphers; 81 | for (uint32_t i = 0; i < arr_length; ++i) { 82 | const Local value = arr_obj->Get(i); 83 | ciphers.push_back(value->Int32Value()); 84 | } 85 | tp_cfg.tlsConfig.ciphers = ciphers; 86 | } 87 | } 88 | 89 | JS2PJ_BOOL(tls_obj, verifyServer, tp_cfg.tlsConfig); 90 | JS2PJ_BOOL(tls_obj, verifyClient, tp_cfg.tlsConfig); 91 | JS2PJ_BOOL(tls_obj, requireClientCert, tp_cfg.tlsConfig); 92 | JS2PJ_UINT(tls_obj, msecTimeout, tp_cfg.tlsConfig); 93 | JS2PJ_ENUM(tls_obj, qosType, pj_qos_type, tp_cfg.tlsConfig); 94 | 95 | val = tls_obj->Get(Nan::New("qosParams").ToLocalChecked()); 96 | if (val->IsObject()) { 97 | pj_qos_params qos_params; 98 | Local qos_obj = val->ToObject(); 99 | Local flags_val = qos_obj->Get(Nan::New("flags").ToLocalChecked()); 100 | Local dscp_val = qos_obj->Get(Nan::New("dscp_val").ToLocalChecked()); 101 | Local so_prio_val = qos_obj->Get(Nan::New("so_prio").ToLocalChecked()); 102 | Local wmm_prio_val = qos_obj->Get(Nan::New("wmm_prio").ToLocalChecked()); 103 | if (flags_val->IsUint32()) { 104 | qos_params.flags = 105 | static_cast(flags_val->Uint32Value()); 106 | } 107 | if (dscp_val->IsUint32()) { 108 | qos_params.dscp_val = 109 | static_cast(dscp_val->Uint32Value()); 110 | } 111 | if (so_prio_val->IsUint32()) { 112 | qos_params.so_prio = 113 | static_cast(so_prio_val->Uint32Value()); 114 | } 115 | if (wmm_prio_val->IsUint32()) { 116 | qos_params.wmm_prio = 117 | static_cast(wmm_prio_val->Uint32Value()); 118 | } 119 | tp_cfg.tlsConfig.qosParams = qos_params; 120 | } 121 | 122 | JS2PJ_BOOL(tls_obj, qosIgnoreError, tp_cfg.tlsConfig); 123 | } 124 | } 125 | 126 | val.Clear(); 127 | pjsip_transport_type_e transportType = PJSIP_TRANSPORT_UDP; 128 | if (info.Length() > 0 && info[0]->IsString()) 129 | val = info[0]; 130 | else if (info.Length() > 1 && info[1]->IsString()) 131 | val = info[1]; 132 | if (!val.IsEmpty()) { 133 | Nan::Utf8String type_str(val); 134 | const char* typecstr = *type_str; 135 | if (strcasecmp(typecstr, "udp") == 0) 136 | transportType = PJSIP_TRANSPORT_UDP; 137 | else if (strcasecmp(typecstr, "tcp") == 0) 138 | transportType = PJSIP_TRANSPORT_TCP; 139 | #if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6!=0 140 | else if (strcasecmp(typecstr, "udp6") == 0) 141 | transportType = PJSIP_TRANSPORT_UDP6; 142 | else if (strcasecmp(typecstr, "tcp6") == 0) 143 | transportType = PJSIP_TRANSPORT_TCP6; 144 | #endif 145 | #if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0 146 | else if (strcasecmp(typecstr, "tls") == 0) 147 | transportType = PJSIP_TRANSPORT_TLS; 148 | # if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6!=0 149 | else if (strcasecmp(typecstr, "tls6") == 0) 150 | transportType = PJSIP_TRANSPORT_TLS6; 151 | # endif 152 | #endif 153 | else 154 | return Nan::ThrowError("Unsupported transport type"); 155 | } 156 | 157 | TransportId tid; 158 | try { 159 | tid = ep->transportCreate(transportType, tp_cfg); 160 | } catch(Error& err) { 161 | errstr = "transportCreate error: " + err.info(); 162 | return Nan::ThrowError(errstr.c_str()); 163 | } 164 | 165 | uv_mutex_lock(&async_mutex); 166 | uv_ref(reinterpret_cast(&dumb)); 167 | uv_mutex_unlock(&async_mutex); 168 | 169 | SIPSTERTransport* trans = new SIPSTERTransport(); 170 | 171 | trans->Wrap(info.This()); 172 | trans->Ref(); 173 | 174 | trans->transId = tid; 175 | trans->enabled = true; 176 | trans->emit = new Nan::Callback( 177 | Local::Cast(trans->handle()->Get(Nan::New(emit_symbol))) 178 | ); 179 | 180 | info.GetReturnValue().Set(info.This()); 181 | } 182 | 183 | NAN_METHOD(SIPSTERTransport::GetInfo) { 184 | Nan::HandleScope scope; 185 | SIPSTERTransport* trans = 186 | Nan::ObjectWrap::Unwrap(info.This()); 187 | 188 | TransportInfo ti; 189 | 190 | try { 191 | ti = ep->transportGetInfo(trans->transId); 192 | } catch(Error& err) { 193 | string errstr = "transportGetInfo error: " + err.info(); 194 | return Nan::ThrowError(errstr.c_str()); 195 | } 196 | 197 | Local info_obj = Nan::New(); 198 | Nan::Set(info_obj, 199 | Nan::New("type").ToLocalChecked(), 200 | Nan::New(ti.typeName.c_str()).ToLocalChecked()); 201 | Nan::Set(info_obj, 202 | Nan::New("info").ToLocalChecked(), 203 | Nan::New(ti.info.c_str()).ToLocalChecked()); 204 | Nan::Set(info_obj, 205 | Nan::New("flags").ToLocalChecked(), 206 | Nan::New(ti.flags)); 207 | Nan::Set(info_obj, 208 | Nan::New("localAddress").ToLocalChecked(), 209 | Nan::New(ti.localAddress.c_str()).ToLocalChecked()); 210 | Nan::Set(info_obj, 211 | Nan::New("localName").ToLocalChecked(), 212 | Nan::New(ti.localName.c_str()).ToLocalChecked()); 213 | Nan::Set(info_obj, 214 | Nan::New("usageCount").ToLocalChecked(), 215 | Nan::New(ti.usageCount)); 216 | 217 | info.GetReturnValue().Set(info_obj); 218 | } 219 | 220 | NAN_METHOD(SIPSTERTransport::Enable) { 221 | Nan::HandleScope scope; 222 | SIPSTERTransport* trans = 223 | Nan::ObjectWrap::Unwrap(info.This()); 224 | 225 | if (!trans->enabled) { 226 | try { 227 | ep->transportSetEnable(trans->transId, true); 228 | } catch(Error& err) { 229 | string errstr = "transportSetEnable error: " + err.info(); 230 | return Nan::ThrowError(errstr.c_str()); 231 | } 232 | trans->enabled = true; 233 | } 234 | 235 | info.GetReturnValue().SetUndefined(); 236 | } 237 | 238 | NAN_METHOD(SIPSTERTransport::Disable) { 239 | Nan::HandleScope scope; 240 | SIPSTERTransport* trans = 241 | Nan::ObjectWrap::Unwrap(info.This()); 242 | 243 | if (trans->enabled) { 244 | try { 245 | ep->transportSetEnable(trans->transId, false); 246 | } catch(Error& err) { 247 | string errstr = "transportSetEnable error: " + err.info(); 248 | return Nan::ThrowError(errstr.c_str()); 249 | } 250 | trans->enabled = false; 251 | } 252 | 253 | info.GetReturnValue().SetUndefined(); 254 | } 255 | 256 | NAN_METHOD(SIPSTERTransport::DoRef) { 257 | Nan::HandleScope scope; 258 | SIPSTERTransport* trans = 259 | Nan::ObjectWrap::Unwrap(info.This()); 260 | 261 | trans->Ref(); 262 | 263 | info.GetReturnValue().SetUndefined(); 264 | } 265 | 266 | NAN_METHOD(SIPSTERTransport::DoUnref) { 267 | Nan::HandleScope scope; 268 | SIPSTERTransport* trans = 269 | Nan::ObjectWrap::Unwrap(info.This()); 270 | 271 | trans->Unref(); 272 | 273 | info.GetReturnValue().SetUndefined(); 274 | } 275 | 276 | NAN_GETTER(SIPSTERTransport::EnabledGetter) { 277 | SIPSTERTransport* trans = 278 | Nan::ObjectWrap::Unwrap(info.This()); 279 | 280 | info.GetReturnValue().Set(Nan::New(trans->enabled)); 281 | } 282 | 283 | void SIPSTERTransport::Initialize(Handle target) { 284 | Nan::HandleScope scope; 285 | 286 | Local tpl = Nan::New(New); 287 | Local name = Nan::New("Transport").ToLocalChecked(); 288 | 289 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 290 | tpl->SetClassName(name); 291 | 292 | Nan::SetPrototypeMethod(tpl, "getInfo", GetInfo); 293 | Nan::SetPrototypeMethod(tpl, "enable", Enable); 294 | Nan::SetPrototypeMethod(tpl, "disable", Disable); 295 | Nan::SetPrototypeMethod(tpl, "ref", DoRef); 296 | Nan::SetPrototypeMethod(tpl, "unref", DoUnref); 297 | 298 | Nan::SetAccessor(tpl->PrototypeTemplate(), 299 | Nan::New("enabled").ToLocalChecked(), 300 | EnabledGetter); 301 | 302 | Nan::Set(target, name, tpl->GetFunction()); 303 | 304 | SIPSTERTransport_constructor.Reset(tpl); 305 | } 306 | -------------------------------------------------------------------------------- /src/SIPSTERTransport.h: -------------------------------------------------------------------------------- 1 | #ifndef SIPSTERTRANSPORT_H_ 2 | #define SIPSTERTRANSPORT_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | using namespace node; 13 | using namespace v8; 14 | using namespace pj; 15 | 16 | class SIPSTERTransport : public Nan::ObjectWrap { 17 | public: 18 | Nan::Callback* emit; 19 | TransportId transId; 20 | bool enabled; 21 | 22 | SIPSTERTransport(); 23 | ~SIPSTERTransport(); 24 | 25 | static NAN_METHOD(New); 26 | static NAN_METHOD(GetInfo); 27 | static NAN_METHOD(Enable); 28 | static NAN_METHOD(Disable); 29 | static NAN_METHOD(DoRef); 30 | static NAN_METHOD(DoUnref); 31 | static NAN_GETTER(EnabledGetter); 32 | static void Initialize(Handle target); 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/binding.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "common.h" 9 | #include "SIPSTERTransport.h" 10 | 11 | #define X(kind, ctype, name, v8type, valconv) \ 12 | Nan::Persistent kind##_##name##_symbol; 13 | INCALL_FIELDS 14 | CALLDTMF_FIELDS 15 | REGSTATE_FIELDS 16 | REGSTARTING_FIELDS 17 | #undef X 18 | 19 | #define X(kind, literal) \ 20 | Nan::Persistent ev_##kind##_##literal##_symbol; 21 | EVENT_SYMBOLS 22 | #undef X 23 | 24 | using namespace std; 25 | using namespace node; 26 | using namespace v8; 27 | using namespace pj; 28 | 29 | class SIPSTERLogWriter; 30 | 31 | SIPSTERLogWriter* logger = NULL; 32 | struct CustomLogEntry { 33 | int level; 34 | string msg; 35 | double threadId; 36 | string threadName; 37 | }; 38 | list log_queue; 39 | uv_mutex_t log_mutex; 40 | uv_async_t logging; 41 | bool ep_init = false; 42 | bool ep_create = false; 43 | bool ep_start = false; 44 | EpConfig ep_cfg; 45 | 46 | list event_queue; 47 | uv_mutex_t event_mutex; 48 | uv_mutex_t async_mutex; 49 | uv_async_t dumb; 50 | Nan::Persistent emit_symbol; 51 | Endpoint* ep = new Endpoint; 52 | 53 | // DTMF event ================================================================== 54 | Nan::Persistent CALLDTMF_DTMF0_symbol; 55 | Nan::Persistent CALLDTMF_DTMF1_symbol; 56 | Nan::Persistent CALLDTMF_DTMF2_symbol; 57 | Nan::Persistent CALLDTMF_DTMF3_symbol; 58 | Nan::Persistent CALLDTMF_DTMF4_symbol; 59 | Nan::Persistent CALLDTMF_DTMF5_symbol; 60 | Nan::Persistent CALLDTMF_DTMF6_symbol; 61 | Nan::Persistent CALLDTMF_DTMF7_symbol; 62 | Nan::Persistent CALLDTMF_DTMF8_symbol; 63 | Nan::Persistent CALLDTMF_DTMF9_symbol; 64 | Nan::Persistent CALLDTMF_DTMFSTAR_symbol; 65 | Nan::Persistent CALLDTMF_DTMFPOUND_symbol; 66 | Nan::Persistent CALLDTMF_DTMFA_symbol; 67 | Nan::Persistent CALLDTMF_DTMFB_symbol; 68 | Nan::Persistent CALLDTMF_DTMFC_symbol; 69 | Nan::Persistent CALLDTMF_DTMFD_symbol; 70 | // ============================================================================= 71 | 72 | class SIPSTERPlayer : public AudioMediaPlayer { 73 | public: 74 | SIPSTERMedia* media; 75 | unsigned options; 76 | bool skip; 77 | 78 | SIPSTERPlayer() : skip(false) {} 79 | ~SIPSTERPlayer() {} 80 | 81 | virtual bool onEof() { 82 | if (skip) 83 | return false; 84 | SETUP_EVENT_NOARGS(PLAYEREOF); 85 | ev.media = media; 86 | 87 | ENQUEUE_EVENT(ev); 88 | if (options & PJMEDIA_FILE_NO_LOOP) { 89 | skip = true; 90 | return false; 91 | } 92 | return true; 93 | } 94 | }; 95 | 96 | class SIPSTERLogWriter : public LogWriter { 97 | public: 98 | Nan::Callback* func; 99 | 100 | SIPSTERLogWriter() {} 101 | ~SIPSTERLogWriter() { 102 | if (func) 103 | delete func; 104 | } 105 | 106 | virtual void write(const LogEntry& entry) { 107 | CustomLogEntry log; 108 | log.level = entry.level; 109 | log.msg = entry.msg; 110 | log.threadId = static_cast(entry.threadId); 111 | log.threadName = entry.threadName; 112 | 113 | uv_mutex_lock(&log_mutex); 114 | log_queue.push_back(log); 115 | uv_mutex_unlock(&log_mutex); 116 | uv_async_send(&logging); 117 | } 118 | }; 119 | 120 | // start event processing-related definitions ================================== 121 | # if NAUV_UVVERSION < 0x000b17 122 | void dumb_cb(uv_async_t* handle, int status) { 123 | assert(status == 0); 124 | # else 125 | void dumb_cb(uv_async_t* handle) { 126 | # endif 127 | Nan::HandleScope scope; 128 | while (true) { 129 | uv_mutex_lock(&event_mutex); 130 | if (event_queue.empty()) 131 | break; 132 | const SIPEventInfo ev = event_queue.front(); 133 | event_queue.pop_front(); 134 | uv_mutex_unlock(&event_mutex); 135 | 136 | switch (ev.type) { 137 | case EVENT_INCALL: { 138 | Local obj = Nan::New(); 139 | EV_ARGS_INCALL* args = reinterpret_cast(ev.args); 140 | #define X(kind, ctype, name, v8type, valconv) \ 141 | Nan::Set(obj, \ 142 | Nan::New(kind##_##name##_symbol), \ 143 | Nan::New(args->valconv).ToLocalChecked()); 144 | INCALL_FIELDS 145 | #undef X 146 | SIPSTERAccount* acct = ev.acct; 147 | SIPSTERCall* call = ev.call; 148 | Local new_call_args[1] = { Nan::New(call) }; 149 | Local call_obj; 150 | call_obj = Nan::New(SIPSTERCall_constructor) 151 | ->GetFunction() 152 | ->NewInstance(1, new_call_args); 153 | Local emit_argv[3] = { 154 | Nan::New(ev_INCALL_call_symbol), 155 | obj, 156 | call_obj 157 | }; 158 | call->emit->Call(acct->handle(), 3, emit_argv); 159 | delete args; 160 | } 161 | break; 162 | case EVENT_REGSTATE: { 163 | EV_ARGS_REGSTATE* args = reinterpret_cast(ev.args); 164 | SIPSTERAccount* acct = ev.acct; 165 | Local emit_argv[1] = { 166 | args->active 167 | ? Nan::New(ev_REGSTATE_registered_symbol) 168 | : Nan::New(ev_REGSTATE_unregistered_symbol) 169 | }; 170 | acct->emit->Call(acct->handle(), 1, emit_argv); 171 | Local emit_catchall_argv[N_REGSTATE_FIELDS + 1] = { 172 | Nan::New(ev_CALLSTATE_state_symbol), 173 | #define X(kind, ctype, name, v8type, valconv) \ 174 | Nan::New(args->valconv), 175 | REGSTATE_FIELDS 176 | #undef X 177 | }; 178 | acct->emit->Call(acct->handle(), 179 | N_REGSTATE_FIELDS + 1, 180 | emit_catchall_argv); 181 | delete args; 182 | } 183 | break; 184 | case EVENT_CALLSTATE: { 185 | EV_ARGS_CALLSTATE* args = reinterpret_cast(ev.args); 186 | Local ev_name; 187 | switch (args->_state) { 188 | case PJSIP_INV_STATE_CALLING: 189 | ev_name = Nan::New(ev_CALLSTATE_calling_symbol); 190 | break; 191 | case PJSIP_INV_STATE_INCOMING: 192 | ev_name = Nan::New(ev_CALLSTATE_incoming_symbol); 193 | break; 194 | case PJSIP_INV_STATE_EARLY: 195 | ev_name = Nan::New(ev_CALLSTATE_early_symbol); 196 | break; 197 | case PJSIP_INV_STATE_CONNECTING: 198 | ev_name = Nan::New(ev_CALLSTATE_connecting_symbol); 199 | break; 200 | case PJSIP_INV_STATE_CONFIRMED: 201 | ev_name = Nan::New(ev_CALLSTATE_confirmed_symbol); 202 | break; 203 | case PJSIP_INV_STATE_DISCONNECTED: 204 | ev_name = Nan::New(ev_CALLSTATE_disconnected_symbol); 205 | break; 206 | default: 207 | break; 208 | } 209 | if (!ev_name.IsEmpty()) { 210 | SIPSTERCall* call = ev.call; 211 | Local emit_argv[1] = { ev_name }; 212 | 213 | if (call->emit && !call->persistent().IsEmpty()) { 214 | call->emit->Call(call->handle(), 1, emit_argv); 215 | Local emit_catchall_argv[2] = { 216 | Nan::New(ev_CALLSTATE_state_symbol), 217 | ev_name 218 | }; 219 | call->emit->Call(call->handle(), 2, emit_catchall_argv); 220 | } 221 | } 222 | delete args; 223 | } 224 | break; 225 | case EVENT_CALLMEDIA: { 226 | SIPSTERCall* call = ev.call; 227 | 228 | Local medias = Nan::New(); 229 | CallInfo ci; 230 | try { 231 | ci = call->getInfo(); 232 | AudioMedia* media = NULL; 233 | // TODO: update srcRTP/srcRTCP on call CONFIRMED status? 234 | for (unsigned i = 0, m = 0; i < ci.media.size(); ++i) { 235 | if (ci.media[i].type == PJMEDIA_TYPE_AUDIO 236 | && (media = static_cast(call->getMedia(i)))) { 237 | Local med_obj; 238 | med_obj = Nan::New(SIPSTERMedia_constructor) 239 | ->GetFunction() 240 | ->NewInstance(0, NULL); 241 | SIPSTERMedia* med = 242 | Nan::ObjectWrap::Unwrap(med_obj); 243 | med->media = media; 244 | med->dir = ci.media[i].dir; 245 | /*if (ci.media[i].status == PJSUA_CALL_MEDIA_ACTIVE) { 246 | try { 247 | MediaTransportInfo mti = call->getMedTransportInfo(i); 248 | med->srcRTP = mti.srcRtpName; 249 | med->srcRTCP = mti.srcRtcpName; 250 | } catch (Error& err) {} 251 | }*/ 252 | Nan::Set(medias, m++, med_obj); 253 | } 254 | } 255 | if (medias->Length() > 0) { 256 | Local emit_argv[2] = { 257 | Nan::New(ev_CALLMEDIA_media_symbol), 258 | medias 259 | }; 260 | if (call->emit && !call->persistent().IsEmpty()) 261 | call->emit->Call(call->handle(), 2, emit_argv); 262 | } 263 | } catch(Error& err) {} 264 | } 265 | break; 266 | case EVENT_CALLDTMF: { 267 | EV_ARGS_CALLDTMF* args = reinterpret_cast(ev.args); 268 | SIPSTERCall* call = ev.call; 269 | Local dtmf_char; 270 | switch (args->digit) { 271 | case '0': 272 | dtmf_char = Nan::New(CALLDTMF_DTMF0_symbol); 273 | break; 274 | case '1': 275 | dtmf_char = Nan::New(CALLDTMF_DTMF1_symbol); 276 | break; 277 | case '2': 278 | dtmf_char = Nan::New(CALLDTMF_DTMF2_symbol); 279 | break; 280 | case '3': 281 | dtmf_char = Nan::New(CALLDTMF_DTMF3_symbol); 282 | break; 283 | case '4': 284 | dtmf_char = Nan::New(CALLDTMF_DTMF4_symbol); 285 | break; 286 | case '5': 287 | dtmf_char = Nan::New(CALLDTMF_DTMF5_symbol); 288 | break; 289 | case '6': 290 | dtmf_char = Nan::New(CALLDTMF_DTMF6_symbol); 291 | break; 292 | case '7': 293 | dtmf_char = Nan::New(CALLDTMF_DTMF7_symbol); 294 | break; 295 | case '8': 296 | dtmf_char = Nan::New(CALLDTMF_DTMF8_symbol); 297 | break; 298 | case '9': 299 | dtmf_char = Nan::New(CALLDTMF_DTMF9_symbol); 300 | break; 301 | case '*': 302 | dtmf_char = Nan::New(CALLDTMF_DTMFSTAR_symbol); 303 | break; 304 | case '#': 305 | dtmf_char = Nan::New(CALLDTMF_DTMFPOUND_symbol); 306 | break; 307 | case 'A': 308 | dtmf_char = Nan::New(CALLDTMF_DTMFA_symbol); 309 | break; 310 | case 'B': 311 | dtmf_char = Nan::New(CALLDTMF_DTMFB_symbol); 312 | break; 313 | case 'C': 314 | dtmf_char = Nan::New(CALLDTMF_DTMFC_symbol); 315 | break; 316 | case 'D': 317 | dtmf_char = Nan::New(CALLDTMF_DTMFD_symbol); 318 | break; 319 | default: 320 | const char digit[1] = { args->digit }; 321 | dtmf_char = Nan::New(digit, 1).ToLocalChecked(); 322 | } 323 | Local emit_argv[2] = { 324 | Nan::New(ev_CALLDTMF_dtmf_symbol), 325 | dtmf_char 326 | }; 327 | if (call->emit && !call->persistent().IsEmpty()) 328 | call->emit->Call(call->handle(), 2, emit_argv); 329 | delete args; 330 | } 331 | break; 332 | case EVENT_PLAYEREOF: { 333 | SIPSTERMedia* media = ev.media; 334 | Local emit_argv[1] = { 335 | Nan::New(ev_PLAYEREOF_eof_symbol) 336 | }; 337 | if (media->emit && !media->persistent().IsEmpty()) 338 | media->emit->Call(media->handle(), 1, emit_argv); 339 | } 340 | break; 341 | case EVENT_REGSTARTING: { 342 | EV_ARGS_REGSTARTING* args = 343 | reinterpret_cast(ev.args); 344 | SIPSTERAccount* acct = ev.acct; 345 | 346 | Local emit_argv[1] = { 347 | (args->renew 348 | ? Nan::New(ev_REGSTARTING_registering_symbol) 349 | : Nan::New(ev_REGSTARTING_unregistering_symbol)) 350 | }; 351 | acct->emit->Call(acct->handle(), 1, emit_argv); 352 | delete args; 353 | } 354 | break; 355 | } 356 | } 357 | uv_mutex_unlock(&event_mutex); 358 | } 359 | 360 | void logging_close_cb(uv_handle_t* handle) {} 361 | 362 | # if NAUV_UVVERSION < 0x000b17 363 | void logging_cb(uv_async_t* handle, int status) { 364 | assert(status == 0); 365 | # else 366 | void logging_cb(uv_async_t* handle) { 367 | # endif 368 | Nan::HandleScope scope; 369 | Local log_argv[4]; 370 | while (true) { 371 | uv_mutex_lock(&log_mutex); 372 | 373 | if (log_queue.empty()) { 374 | uv_mutex_unlock(&log_mutex); 375 | break; 376 | } 377 | 378 | const CustomLogEntry log = log_queue.front(); 379 | log_queue.pop_front(); 380 | uv_mutex_unlock(&log_mutex); 381 | 382 | log_argv[0] = Nan::New(log.level); 383 | log_argv[1] = Nan::New(log.msg).ToLocalChecked(); 384 | log_argv[2] = Nan::New(log.threadId); 385 | log_argv[3] = Nan::New(log.threadName).ToLocalChecked(); 386 | 387 | logger->func->Call(4, log_argv); 388 | } 389 | } 390 | // ============================================================================= 391 | 392 | // static methods ============================================================== 393 | static NAN_METHOD(CreateRecorder) { 394 | Nan::HandleScope scope; 395 | 396 | string dest; 397 | unsigned fmt = PJMEDIA_FILE_WRITE_ULAW; 398 | pj_ssize_t max_size = 0; 399 | if (info.Length() > 0 && info[0]->IsString()) { 400 | Nan::Utf8String dest_str(info[0]); 401 | dest = string(*dest_str); 402 | if (info.Length() > 1 && info[1]->IsString()) { 403 | Nan::Utf8String fmt_str(info[1]); 404 | const char* fmtcstr = *fmt_str; 405 | if (strcasecmp(fmtcstr, "pcm") == 0) 406 | fmt = PJMEDIA_FILE_WRITE_PCM; 407 | else if (strcasecmp(fmtcstr, "alaw") == 0) 408 | fmt = PJMEDIA_FILE_WRITE_ALAW; 409 | else if (strcasecmp(fmtcstr, "ulaw") != 0) 410 | return Nan::ThrowError("Invalid media format"); 411 | } 412 | if (info.Length() > 2 && info[2]->IsInt32()) { 413 | pj_ssize_t size = static_cast(info[2]->Int32Value()); 414 | if (size >= -1) 415 | max_size = size; 416 | } 417 | } else 418 | return Nan::ThrowTypeError("Missing destination filename"); 419 | 420 | AudioMediaRecorder* recorder = new AudioMediaRecorder(); 421 | try { 422 | recorder->createRecorder(dest, 0, max_size, fmt); 423 | } catch(Error& err) { 424 | delete recorder; 425 | string errstr = "recorder->createRecorder() error: " + err.info(); 426 | return Nan::ThrowError(errstr.c_str()); 427 | } 428 | 429 | Local med_obj; 430 | med_obj = Nan::New(SIPSTERMedia_constructor) 431 | ->GetFunction() 432 | ->NewInstance(0, NULL); 433 | SIPSTERMedia* med = Nan::ObjectWrap::Unwrap(med_obj); 434 | med->media = recorder; 435 | med->is_media_new = true; 436 | 437 | info.GetReturnValue().Set(med_obj); 438 | } 439 | 440 | static NAN_METHOD(CreatePlayer) { 441 | Nan::HandleScope scope; 442 | 443 | string src; 444 | unsigned opts = 0; 445 | if (info.Length() > 0 && info[0]->IsString()) { 446 | Nan::Utf8String src_str(info[0]); 447 | src = string(*src_str); 448 | if (info.Length() > 1 && info[1]->IsBoolean() && info[1]->BooleanValue()) 449 | opts = PJMEDIA_FILE_NO_LOOP; 450 | } else 451 | return Nan::ThrowTypeError("Missing source filename"); 452 | 453 | SIPSTERPlayer* player = new SIPSTERPlayer(); 454 | try { 455 | player->createPlayer(src, opts); 456 | } catch(Error& err) { 457 | delete player; 458 | string errstr = "player->createPlayer() error: " + err.info(); 459 | return Nan::ThrowError(errstr.c_str()); 460 | } 461 | 462 | Local med_obj; 463 | med_obj = Nan::New(SIPSTERMedia_constructor) 464 | ->GetFunction() 465 | ->NewInstance(0, NULL); 466 | SIPSTERMedia* med = Nan::ObjectWrap::Unwrap(med_obj); 467 | med->media = player; 468 | med->is_media_new = true; 469 | player->media = med; 470 | player->options = opts; 471 | 472 | info.GetReturnValue().Set(med_obj); 473 | } 474 | 475 | static NAN_METHOD(CreatePlaylist) { 476 | Nan::HandleScope scope; 477 | 478 | unsigned opts = 0; 479 | vector playlist; 480 | if (info.Length() > 0 && info[0]->IsArray()) { 481 | const Local arr_obj = Local::Cast(info[0]); 482 | const uint32_t arr_length = arr_obj->Length(); 483 | 484 | if (arr_length == 0) 485 | return Nan::ThrowError("Nothing to add to playlist"); 486 | 487 | playlist.reserve(arr_length); 488 | for (uint32_t i = 0; i < arr_length; ++i) { 489 | Nan::Utf8String filename_str(arr_obj->Get(i)); 490 | playlist.push_back(string(*filename_str)); 491 | } 492 | if (info.Length() > 1 && info[1]->IsBoolean() && info[1]->BooleanValue()) 493 | opts = PJMEDIA_FILE_NO_LOOP; 494 | } else 495 | return Nan::ThrowTypeError("Missing source filenames"); 496 | 497 | SIPSTERPlayer* player = new SIPSTERPlayer(); 498 | try { 499 | player->createPlaylist(playlist, "", opts); 500 | } catch(Error& err) { 501 | delete player; 502 | string errstr = "player->createPlayer() error: " + err.info(); 503 | return Nan::ThrowError(errstr.c_str()); 504 | } 505 | 506 | Local med_obj; 507 | med_obj = Nan::New(SIPSTERMedia_constructor) 508 | ->GetFunction() 509 | ->NewInstance(0, NULL); 510 | SIPSTERMedia* med = Nan::ObjectWrap::Unwrap(med_obj); 511 | med->media = player; 512 | med->is_media_new = true; 513 | player->media = med; 514 | player->options = opts; 515 | 516 | info.GetReturnValue().Set(med_obj); 517 | } 518 | 519 | static NAN_METHOD(EPVersion) { 520 | Nan::HandleScope scope; 521 | 522 | pj::Version v = ep->libVersion(); 523 | Local vinfo = Nan::New(); 524 | Nan::Set(vinfo, 525 | Nan::New("major").ToLocalChecked(), 526 | Nan::New(v.major)); 527 | Nan::Set(vinfo, 528 | Nan::New("minor").ToLocalChecked(), 529 | Nan::New(v.minor)); 530 | Nan::Set(vinfo, 531 | Nan::New("rev").ToLocalChecked(), 532 | Nan::New(v.rev)); 533 | Nan::Set(vinfo, 534 | Nan::New("suffix").ToLocalChecked(), 535 | Nan::New(v.suffix.c_str()).ToLocalChecked()); 536 | Nan::Set(vinfo, 537 | Nan::New("full").ToLocalChecked(), 538 | Nan::New(v.full.c_str()).ToLocalChecked()); 539 | Nan::Set(vinfo, 540 | Nan::New("numeric").ToLocalChecked(), 541 | Nan::New(v.numeric)); 542 | 543 | info.GetReturnValue().Set(vinfo); 544 | } 545 | 546 | static NAN_METHOD(EPInit) { 547 | Nan::HandleScope scope; 548 | string errstr; 549 | 550 | if (ep_init) 551 | return Nan::ThrowError("Already initialized"); 552 | 553 | if (!ep_create) { 554 | try { 555 | ep->libCreate(); 556 | ep_create = true; 557 | } catch(Error& err) { 558 | errstr = "libCreate error: " + err.info(); 559 | return Nan::ThrowError(errstr.c_str()); 560 | } 561 | } 562 | 563 | Local val; 564 | if (info.Length() > 0 && info[0]->IsObject()) { 565 | Local cfg_obj = info[0]->ToObject(); 566 | val = cfg_obj->Get(Nan::New("uaConfig").ToLocalChecked()); 567 | if (val->IsObject()) { 568 | UaConfig uaConfig; 569 | Local ua_obj = val->ToObject(); 570 | JS2PJ_UINT(ua_obj, maxCalls, uaConfig); 571 | JS2PJ_UINT(ua_obj, threadCnt, uaConfig); 572 | JS2PJ_BOOL(ua_obj, mainThreadOnly, uaConfig); 573 | 574 | val = ua_obj->Get(Nan::New("nameserver").ToLocalChecked()); 575 | if (val->IsArray()) { 576 | const Local arr_obj = Local::Cast(val); 577 | const uint32_t arr_length = arr_obj->Length(); 578 | if (arr_length > 0) { 579 | vector nameservers; 580 | for (uint32_t i = 0; i < arr_length; ++i) { 581 | const Local arr_val = arr_obj->Get(i); 582 | if (arr_val->IsString()) { 583 | Nan::Utf8String ns_str(arr_val); 584 | nameservers.push_back(string(*ns_str)); 585 | } 586 | } 587 | if (nameservers.size() > 0) 588 | uaConfig.nameserver = nameservers; 589 | } 590 | } 591 | 592 | JS2PJ_STR(ua_obj, userAgent, uaConfig); 593 | 594 | val = ua_obj->Get(Nan::New("stunServer").ToLocalChecked()); 595 | if (val->IsArray()) { 596 | const Local arr_obj = Local::Cast(val); 597 | const uint32_t arr_length = arr_obj->Length(); 598 | if (arr_length > 0) { 599 | vector stunServers; 600 | for (uint32_t i = 0; i < arr_length; ++i) { 601 | const Local arr_val = arr_obj->Get(i); 602 | if (arr_val->IsString()) { 603 | Nan::Utf8String stun_str(arr_val); 604 | stunServers.push_back(string(*stun_str)); 605 | } 606 | } 607 | if (stunServers.size() > 0) 608 | uaConfig.stunServer = stunServers; 609 | } 610 | } 611 | 612 | JS2PJ_BOOL(ua_obj, stunIgnoreFailure, uaConfig); 613 | JS2PJ_INT(ua_obj, natTypeInSdp, uaConfig); 614 | JS2PJ_BOOL(ua_obj, mwiUnsolicitedEnabled, uaConfig); 615 | 616 | ep_cfg.uaConfig = uaConfig; 617 | } 618 | 619 | val = cfg_obj->Get(Nan::New("logConfig").ToLocalChecked()); 620 | if (val->IsObject()) { 621 | LogConfig logConfig; 622 | Local log_obj = val->ToObject(); 623 | JS2PJ_UINT(log_obj, msgLogging, logConfig); 624 | JS2PJ_UINT(log_obj, level, logConfig); 625 | JS2PJ_UINT(log_obj, consoleLevel, logConfig); 626 | JS2PJ_UINT(log_obj, decor, logConfig); 627 | JS2PJ_STR(log_obj, filename, logConfig); 628 | JS2PJ_UINT(log_obj, fileFlags, logConfig); 629 | 630 | val = log_obj->Get(Nan::New("writer").ToLocalChecked()); 631 | if (val->IsFunction()) { 632 | if (logger) { 633 | delete logger; 634 | uv_close(reinterpret_cast(&logging), logging_close_cb); 635 | } 636 | logger = new SIPSTERLogWriter(); 637 | logger->func = new Nan::Callback(Local::Cast(val)); 638 | logConfig.writer = logger; 639 | uv_async_init(uv_default_loop(), 640 | &logging, 641 | static_cast(logging_cb)); 642 | } 643 | 644 | ep_cfg.logConfig = logConfig; 645 | } 646 | 647 | val = cfg_obj->Get(Nan::New("medConfig").ToLocalChecked()); 648 | if (val->IsObject()) { 649 | MediaConfig medConfig; 650 | Local med_obj = val->ToObject(); 651 | JS2PJ_UINT(med_obj, clockRate, medConfig); 652 | JS2PJ_UINT(med_obj, sndClockRate, medConfig); 653 | JS2PJ_UINT(med_obj, channelCount, medConfig); 654 | JS2PJ_UINT(med_obj, audioFramePtime, medConfig); 655 | JS2PJ_UINT(med_obj, maxMediaPorts, medConfig); 656 | JS2PJ_BOOL(med_obj, hasIoqueue, medConfig); 657 | JS2PJ_UINT(med_obj, threadCnt, medConfig); 658 | JS2PJ_UINT(med_obj, quality, medConfig); 659 | JS2PJ_UINT(med_obj, ptime, medConfig); 660 | JS2PJ_BOOL(med_obj, noVad, medConfig); 661 | JS2PJ_UINT(med_obj, ilbcMode, medConfig); 662 | JS2PJ_UINT(med_obj, txDropPct, medConfig); 663 | JS2PJ_UINT(med_obj, rxDropPct, medConfig); 664 | JS2PJ_UINT(med_obj, ecOptions, medConfig); 665 | JS2PJ_UINT(med_obj, ecTailLen, medConfig); 666 | JS2PJ_UINT(med_obj, sndRecLatency, medConfig); 667 | JS2PJ_UINT(med_obj, sndPlayLatency, medConfig); 668 | JS2PJ_INT(med_obj, jbInit, medConfig); 669 | JS2PJ_INT(med_obj, jbMinPre, medConfig); 670 | JS2PJ_INT(med_obj, jbMaxPre, medConfig); 671 | JS2PJ_INT(med_obj, jbMax, medConfig); 672 | JS2PJ_INT(med_obj, sndAutoCloseTime, medConfig); 673 | JS2PJ_BOOL(med_obj, vidPreviewEnableNative, medConfig); 674 | 675 | ep_cfg.medConfig = medConfig; 676 | } 677 | } 678 | 679 | try { 680 | ep->libInit(ep_cfg); 681 | ep_init = true; 682 | } catch(Error& err) { 683 | errstr = "libInit error: " + err.info(); 684 | return Nan::ThrowError(errstr.c_str()); 685 | } 686 | 687 | uv_async_init(uv_default_loop(), &dumb, static_cast(dumb_cb)); 688 | 689 | Endpoint::instance().audDevManager().setNullDev(); 690 | 691 | if ((info.Length() == 1 && info[0]->IsBoolean() && info[0]->BooleanValue()) 692 | || (info.Length() > 1 693 | && info[1]->IsBoolean() 694 | && info[1]->BooleanValue())) { 695 | if (ep_start) 696 | return Nan::ThrowError("Already started"); 697 | try { 698 | ep->libStart(); 699 | ep_start = true; 700 | } catch(Error& err) { 701 | string errstr = "libStart error: " + err.info(); 702 | return Nan::ThrowError(errstr.c_str()); 703 | } 704 | } 705 | info.GetReturnValue().SetUndefined(); 706 | } 707 | 708 | static NAN_METHOD(EPStart) { 709 | Nan::HandleScope scope; 710 | 711 | if (ep_start) 712 | return Nan::ThrowError("Already started"); 713 | else if (!ep_init) 714 | return Nan::ThrowError("Not initialized yet"); 715 | 716 | try { 717 | ep->libStart(); 718 | ep_start = true; 719 | } catch(Error& err) { 720 | string errstr = "libStart error: " + err.info(); 721 | return Nan::ThrowError(errstr.c_str()); 722 | } 723 | info.GetReturnValue().SetUndefined(); 724 | } 725 | 726 | static NAN_METHOD(EPGetConfig) { 727 | Nan::HandleScope scope; 728 | string errstr; 729 | 730 | JsonDocument wdoc; 731 | 732 | try { 733 | wdoc.writeObject(ep_cfg); 734 | } catch(Error& err) { 735 | errstr = "JsonDocument.writeObject(EpConfig) error: " + err.info(); 736 | return Nan::ThrowError(errstr.c_str()); 737 | } 738 | 739 | try { 740 | info.GetReturnValue().Set( 741 | Nan::New(wdoc.saveString().c_str()).ToLocalChecked() 742 | ); 743 | } catch(Error& err) { 744 | errstr = "JsonDocument.saveString error: " + err.info(); 745 | return Nan::ThrowError(errstr.c_str()); 746 | } 747 | } 748 | 749 | static NAN_METHOD(EPGetState) { 750 | Nan::HandleScope scope; 751 | 752 | try { 753 | pjsua_state st = ep->libGetState(); 754 | string state; 755 | switch (st) { 756 | case PJSUA_STATE_CREATED: 757 | state = "created"; 758 | break; 759 | case PJSUA_STATE_INIT: 760 | state = "init"; 761 | break; 762 | case PJSUA_STATE_STARTING: 763 | state = "starting"; 764 | break; 765 | case PJSUA_STATE_RUNNING: 766 | state = "running"; 767 | break; 768 | case PJSUA_STATE_CLOSING: 769 | state = "closing"; 770 | break; 771 | default: 772 | return info.GetReturnValue().SetNull(); 773 | } 774 | info.GetReturnValue().Set(Nan::New(state.c_str()).ToLocalChecked()); 775 | } catch(Error& err) { 776 | string errstr = "libGetState error: " + err.info(); 777 | return Nan::ThrowError(errstr.c_str()); 778 | } 779 | } 780 | 781 | static NAN_METHOD(EPHangupAllCalls) { 782 | Nan::HandleScope scope; 783 | 784 | try { 785 | ep->hangupAllCalls(); 786 | } catch(Error& err) { 787 | string errstr = "hangupAllCalls error: " + err.info(); 788 | return Nan::ThrowError(errstr.c_str()); 789 | } 790 | info.GetReturnValue().SetUndefined(); 791 | } 792 | 793 | static NAN_METHOD(EPMediaActivePorts) { 794 | Nan::HandleScope scope; 795 | 796 | info.GetReturnValue().Set(Nan::New(ep->mediaActivePorts())); 797 | } 798 | 799 | static NAN_METHOD(EPMediaMaxPorts) { 800 | Nan::HandleScope scope; 801 | 802 | info.GetReturnValue().Set(Nan::New(ep->mediaMaxPorts())); 803 | } 804 | 805 | extern "C" { 806 | void init(Handle target) { 807 | Nan::HandleScope scope; 808 | 809 | #define X(kind, literal) \ 810 | ev_##kind##_##literal##_symbol.Reset(Nan::New(#literal).ToLocalChecked()); 811 | EVENT_SYMBOLS 812 | #undef X 813 | #define X(kind, ctype, name, v8type, valconv) \ 814 | kind##_##name##_symbol.Reset(Nan::New(#name).ToLocalChecked()); 815 | INCALL_FIELDS 816 | CALLDTMF_FIELDS 817 | REGSTATE_FIELDS 818 | REGSTARTING_FIELDS 819 | #undef X 820 | 821 | CALLDTMF_DTMF0_symbol.Reset(Nan::New("0").ToLocalChecked()); 822 | CALLDTMF_DTMF1_symbol.Reset(Nan::New("1").ToLocalChecked()); 823 | CALLDTMF_DTMF2_symbol.Reset(Nan::New("2").ToLocalChecked()); 824 | CALLDTMF_DTMF3_symbol.Reset(Nan::New("3").ToLocalChecked()); 825 | CALLDTMF_DTMF4_symbol.Reset(Nan::New("4").ToLocalChecked()); 826 | CALLDTMF_DTMF5_symbol.Reset(Nan::New("5").ToLocalChecked()); 827 | CALLDTMF_DTMF6_symbol.Reset(Nan::New("6").ToLocalChecked()); 828 | CALLDTMF_DTMF7_symbol.Reset(Nan::New("7").ToLocalChecked()); 829 | CALLDTMF_DTMF8_symbol.Reset(Nan::New("8").ToLocalChecked()); 830 | CALLDTMF_DTMF9_symbol.Reset(Nan::New("9").ToLocalChecked()); 831 | CALLDTMF_DTMFSTAR_symbol.Reset(Nan::New("*").ToLocalChecked()); 832 | CALLDTMF_DTMFPOUND_symbol.Reset(Nan::New("#").ToLocalChecked()); 833 | CALLDTMF_DTMFA_symbol.Reset(Nan::New("A").ToLocalChecked()); 834 | CALLDTMF_DTMFB_symbol.Reset(Nan::New("B").ToLocalChecked()); 835 | CALLDTMF_DTMFC_symbol.Reset(Nan::New("C").ToLocalChecked()); 836 | CALLDTMF_DTMFD_symbol.Reset(Nan::New("D").ToLocalChecked()); 837 | 838 | emit_symbol.Reset(Nan::New("emit").ToLocalChecked()); 839 | 840 | uv_mutex_init(&event_mutex); 841 | uv_mutex_init(&log_mutex); 842 | uv_mutex_init(&async_mutex); 843 | 844 | SIPSTERAccount::Initialize(target); 845 | SIPSTERCall::Initialize(target); 846 | SIPSTERMedia::Initialize(target); 847 | SIPSTERTransport::Initialize(target); 848 | 849 | Nan::Set(target, 850 | Nan::New("version").ToLocalChecked(), 851 | Nan::New(EPVersion)->GetFunction()); 852 | Nan::Set(target, 853 | Nan::New("state").ToLocalChecked(), 854 | Nan::New(EPGetState)->GetFunction()); 855 | Nan::Set(target, 856 | Nan::New("config").ToLocalChecked(), 857 | Nan::New(EPGetConfig)->GetFunction()); 858 | Nan::Set(target, 859 | Nan::New("init").ToLocalChecked(), 860 | Nan::New(EPInit)->GetFunction()); 861 | Nan::Set(target, 862 | Nan::New("start").ToLocalChecked(), 863 | Nan::New(EPStart)->GetFunction()); 864 | Nan::Set(target, 865 | Nan::New("hangupAllCalls").ToLocalChecked(), 866 | Nan::New(EPHangupAllCalls)->GetFunction()); 867 | Nan::Set(target, 868 | Nan::New("mediaActivePorts").ToLocalChecked(), 869 | Nan::New(EPMediaActivePorts)->GetFunction()); 870 | Nan::Set(target, 871 | Nan::New("mediaMaxPorts").ToLocalChecked(), 872 | Nan::New(EPMediaMaxPorts)->GetFunction()); 873 | 874 | Nan::Set(target, 875 | Nan::New("createRecorder").ToLocalChecked(), 876 | Nan::New(CreateRecorder)->GetFunction()); 877 | Nan::Set(target, 878 | Nan::New("createPlayer").ToLocalChecked(), 879 | Nan::New(CreatePlayer)->GetFunction()); 880 | Nan::Set(target, 881 | Nan::New("createPlaylist").ToLocalChecked(), 882 | Nan::New(CreatePlaylist)->GetFunction()); 883 | } 884 | 885 | NODE_MODULE(sipster, init); 886 | } 887 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H_ 2 | #define COMMON_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "SIPSTERAccount.h" 12 | #include "SIPSTERCall.h" 13 | #include "SIPSTERMedia.h" 14 | 15 | using namespace std; 16 | using namespace node; 17 | using namespace v8; 18 | using namespace pj; 19 | 20 | #define SIP_DEBUG 0 21 | 22 | #define JS2PJ_INT(js, prop, pj) do { \ 23 | val = Nan::Get(js, Nan::New(#prop).ToLocalChecked()).ToLocalChecked(); \ 24 | if (val->IsInt32()) { \ 25 | pj.prop = val->Int32Value(); \ 26 | } \ 27 | } while(0) 28 | #define JS2PJ_UINT(js, prop, pj) do { \ 29 | val = Nan::Get(js, Nan::New(#prop).ToLocalChecked()).ToLocalChecked(); \ 30 | if (val->IsUint32()) { \ 31 | pj.prop = val->Uint32Value(); \ 32 | } \ 33 | } while(0) 34 | #define JS2PJ_ENUM(js, prop, type, pj) do { \ 35 | val = Nan::Get(js, Nan::New(#prop).ToLocalChecked()).ToLocalChecked(); \ 36 | if (val->IsInt32()) { \ 37 | pj.prop = static_cast(val->Int32Value()); \ 38 | } \ 39 | } while(0) 40 | #define JS2PJ_STR(js, prop, pj) do { \ 41 | val = Nan::Get(js, Nan::New(#prop).ToLocalChecked()).ToLocalChecked(); \ 42 | if (val->IsString()) { \ 43 | Nan::Utf8String v8str(val); \ 44 | pj.prop = string(*v8str); \ 45 | } \ 46 | } while(0) 47 | #define JS2PJ_BOOL(js, prop, pj) do { \ 48 | val = Nan::Get(js, Nan::New(#prop).ToLocalChecked()).ToLocalChecked(); \ 49 | if (val->IsBoolean()) { \ 50 | pj.prop = val->BooleanValue(); \ 51 | } \ 52 | } while(0) 53 | 54 | #define ENQUEUE_EVENT(ev) do { \ 55 | uv_mutex_lock(&event_mutex); \ 56 | event_queue.push_back(ev); \ 57 | uv_mutex_unlock(&event_mutex); \ 58 | uv_async_send(&dumb); \ 59 | } while(0) 60 | 61 | #define SETUP_EVENT(name) \ 62 | SIPEventInfo ev; \ 63 | EV_ARGS_##name* args = new EV_ARGS_##name; \ 64 | ev.type = EVENT_##name; \ 65 | ev.args = reinterpret_cast(args) 66 | 67 | #define SETUP_EVENT_NOARGS(name) \ 68 | SIPEventInfo ev; \ 69 | ev.type = EVENT_##name 70 | 71 | #define EVENT_TYPES \ 72 | X(INCALL) \ 73 | X(CALLSTATE) \ 74 | X(CALLDTMF) \ 75 | X(REGSTATE) \ 76 | X(CALLMEDIA) \ 77 | X(PLAYEREOF) \ 78 | X(REGSTARTING) 79 | 80 | #define EVENT_SYMBOLS \ 81 | X(INCALL, call) \ 82 | X(CALLSTATE, calling) \ 83 | X(CALLSTATE, incoming) \ 84 | X(CALLSTATE, early) \ 85 | X(CALLSTATE, connecting) \ 86 | X(CALLSTATE, confirmed) \ 87 | X(CALLSTATE, disconnected) \ 88 | X(CALLSTATE, state) \ 89 | X(CALLDTMF, dtmf) \ 90 | X(REGSTATE, registered) \ 91 | X(REGSTATE, unregistered) \ 92 | X(REGSTATE, state) \ 93 | X(CALLMEDIA, media) \ 94 | X(PLAYEREOF, eof) \ 95 | X(REGSTARTING, registering) \ 96 | X(REGSTARTING, unregistering) 97 | 98 | enum SIPEvent { 99 | #define X(kind) \ 100 | EVENT_##kind, 101 | EVENT_TYPES 102 | #undef X 103 | }; 104 | 105 | struct SIPEventInfo { 106 | SIPEvent type; 107 | SIPSTERCall* call; 108 | SIPSTERAccount* acct; 109 | SIPSTERMedia* media; 110 | void* args; 111 | }; 112 | 113 | // registration change event(s) ================================================ 114 | #define N_REGSTATE_FIELDS 2 115 | #define REGSTATE_FIELDS \ 116 | X(REGSTATE, bool, active, Boolean, active) \ 117 | X(REGSTATE, int, statusCode, Integer, statusCode) 118 | struct EV_ARGS_REGSTATE { 119 | #define X(kind, ctype, name, v8type, valconv) ctype name; 120 | REGSTATE_FIELDS 121 | #undef X 122 | }; 123 | // ============================================================================= 124 | 125 | // incoming call event ========================================================= 126 | #define N_INCALL_FIELDS 7 127 | #define INCALL_FIELDS \ 128 | X(INCALL, string, srcAddress, String, srcAddress.c_str()) \ 129 | X(INCALL, string, localUri, String, localUri.c_str()) \ 130 | X(INCALL, string, localContact, String, localContact.c_str()) \ 131 | X(INCALL, string, remoteUri, String, remoteUri.c_str()) \ 132 | X(INCALL, string, remoteContact, String, remoteContact.c_str()) \ 133 | X(INCALL, string, callId, String, callId.c_str()) 134 | struct EV_ARGS_INCALL { 135 | #define X(kind, ctype, name, v8type, valconv) ctype name; 136 | INCALL_FIELDS 137 | #undef X 138 | }; 139 | // ============================================================================= 140 | 141 | // call state change event(s) ================================================== 142 | #define N_CALLSTATE_FIELDS 1 143 | #define CALLSTATE_FIELDS \ 144 | X(CALLSTATE, pjsip_inv_state, _state, Integer, _state) 145 | struct EV_ARGS_CALLSTATE { 146 | #define X(kind, ctype, name, v8type, valconv) ctype name; 147 | CALLSTATE_FIELDS 148 | #undef X 149 | }; 150 | // ============================================================================= 151 | 152 | // Reg/Unreg starting event ==================================================== 153 | #define N_REGSTARTING_FIELDS 1 154 | #define REGSTARTING_FIELDS \ 155 | X(REGSTARTING, bool, renew, Boolean, renew) 156 | struct EV_ARGS_REGSTARTING { 157 | #define X(kind, ctype, name, v8type, valconv) ctype name; 158 | REGSTARTING_FIELDS 159 | #undef X 160 | }; 161 | // ============================================================================= 162 | 163 | #define N_CALLDTMF_FIELDS 1 164 | #define CALLDTMF_FIELDS \ 165 | X(CALLDTMF, char, digit, String, digit[0]) 166 | struct EV_ARGS_CALLDTMF { 167 | #define X(kind, ctype, name, v8type, valconv) ctype name; 168 | CALLDTMF_FIELDS 169 | #undef X 170 | }; 171 | 172 | #define X(kind, ctype, name, v8type, valconv) \ 173 | extern Nan::Persistent kind##_##name##_symbol; 174 | INCALL_FIELDS 175 | CALLDTMF_FIELDS 176 | REGSTATE_FIELDS 177 | REGSTARTING_FIELDS 178 | #undef X 179 | 180 | // start generic event-related definitions ===================================== 181 | #define X(kind, literal) \ 182 | extern Nan::Persistent ev_##kind##_##literal##_symbol; 183 | EVENT_SYMBOLS 184 | #undef X 185 | 186 | extern list event_queue; 187 | extern uv_mutex_t event_mutex; 188 | extern uv_mutex_t async_mutex; 189 | extern uv_async_t dumb; 190 | extern Nan::Persistent emit_symbol; 191 | // ============================================================================= 192 | 193 | extern Nan::Persistent SIPSTERMedia_constructor; 194 | extern Nan::Persistent SIPSTERAccount_constructor; 195 | extern Nan::Persistent SIPSTERCall_constructor; 196 | extern Nan::Persistent SIPSTERTransport_constructor; 197 | 198 | extern Endpoint* ep; 199 | 200 | #endif 201 | --------------------------------------------------------------------------------