drachtio-srf 5.0.26 → 5.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/srf.js +82 -11
- package/package.json +1 -1
- package/test/b2b.js +55 -1
- package/test/scenarios/uac-3pcc-nosdp.xml +78 -0
- package/test/scripts/b2b.js +53 -0
- package/test/scripts/crash-signatures.js +15 -0
- package/test/sipp.js +23 -6
package/lib/srf.js
CHANGED
|
@@ -587,6 +587,35 @@ class Srf extends Emitter {
|
|
|
587
587
|
resolve(dialog);
|
|
588
588
|
});
|
|
589
589
|
},
|
|
590
|
+
// teardown for the case where the caller (e.g. createB2BUA)
|
|
591
|
+
// needs to abandon this answered-but-not-yet-acked B leg:
|
|
592
|
+
// ACK the 200 OK (echoing the remote answer back as our
|
|
593
|
+
// offer to complete the transaction) then BYE the dialog.
|
|
594
|
+
// Without this the un-acked 200 OK is retransmitted by the
|
|
595
|
+
// far end and the B-leg dialog leaks.
|
|
596
|
+
destroy: async() => {
|
|
597
|
+
try {
|
|
598
|
+
// NOTE: if the caller reaches destroy() after the ack()
|
|
599
|
+
// closure above already sent an ACK (e.g. ackFunction
|
|
600
|
+
// rejected on the no-Contact path *after* ack'ing), this
|
|
601
|
+
// sends a second ACK. Duplicate ACKs are tolerated by
|
|
602
|
+
// the far end, so this is harmless. The common failure
|
|
603
|
+
// path (localSdpB/localSdpA throwing before ackFunction
|
|
604
|
+
// runs) reaches here having sent no ACK at all.
|
|
605
|
+
ack({body: res.body});
|
|
606
|
+
if (!res.has('Contact')) {
|
|
607
|
+
// cannot form in-dialog BYE without a Contact; the
|
|
608
|
+
// ACK above is the best we can do to release it.
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const dialog = new Dialog(this, 'uac', {req, res, auth: opts.auth});
|
|
612
|
+
dialog.local.sdp = res.body;
|
|
613
|
+
this.addDialog(dialog);
|
|
614
|
+
await dialog.destroy().catch(() => {});
|
|
615
|
+
} catch(err) {
|
|
616
|
+
debug(`createUAC: error tearing down 3pcc B leg: ${err}`);
|
|
617
|
+
}
|
|
618
|
+
},
|
|
590
619
|
res
|
|
591
620
|
});
|
|
592
621
|
}
|
|
@@ -1118,14 +1147,28 @@ class Srf extends Emitter {
|
|
|
1118
1147
|
if (is3pcc) {
|
|
1119
1148
|
debug('createB2BUA: successfully created UAS..but this is 3pcc, so a bit more work to do');
|
|
1120
1149
|
uas.once('ack', async(ackRequest) => {
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1150
|
+
// this handler runs after __x has already returned, so any throw
|
|
1151
|
+
// here escapes the createB2BUA promise chain and would become an
|
|
1152
|
+
// unhandled rejection. catch it, tear both legs down, and route
|
|
1153
|
+
// the error back to the caller's callback (which rejects the
|
|
1154
|
+
// returned promise).
|
|
1155
|
+
try {
|
|
1156
|
+
debug(`createB2BUA: got ACK from UAS, pass on sdp: ${ackRequest.body}`);
|
|
1157
|
+
const sdp = await (typeof opts.localSdpB === 'function' ?
|
|
1158
|
+
opts.localSdpB(ackRequest.body) : Promise.resolve(ackRequest.body));
|
|
1159
|
+
uac = await ackFunction(sdp);
|
|
1160
|
+
uac.other = uas;
|
|
1161
|
+
uas.other = uac;
|
|
1162
|
+
debug('createB2BUA: successfully created bot dialogs in 3pcc!');
|
|
1163
|
+
return callback(null, {uac, uas}); // successfully connected! resolve promise with both dialogs
|
|
1164
|
+
} catch(err) {
|
|
1165
|
+
debug({err}, 'createB2BUA: failed finalizing 3pcc dialogs on ACK');
|
|
1166
|
+
if (uac && typeof uac.destroy === 'function') {
|
|
1167
|
+
Promise.resolve(uac.destroy()).catch(() => {});
|
|
1168
|
+
}
|
|
1169
|
+
uas.destroy().catch(() => {});
|
|
1170
|
+
return callback(err);
|
|
1171
|
+
}
|
|
1129
1172
|
});
|
|
1130
1173
|
return;
|
|
1131
1174
|
}
|
|
@@ -1140,21 +1183,49 @@ class Srf extends Emitter {
|
|
|
1140
1183
|
return callback(null, {uac, uas}); // successfully connected! resolve promise with both dialogs
|
|
1141
1184
|
} catch(err) {
|
|
1142
1185
|
debug({err}, 'createB2BUA: failed creating UAS..done!');
|
|
1143
|
-
|
|
1186
|
+
// failed A leg after success on B: tear down B.
|
|
1187
|
+
// in normal mode `uac` is a Dialog; in 3pcc/no-offer mode it is the
|
|
1188
|
+
// {sdp, ack, res, destroy} object from createUAC (the real dialog is
|
|
1189
|
+
// not created until the A-leg ACK). both expose destroy(), which for
|
|
1190
|
+
// the 3pcc object ACKs then BYEs the answered B leg so it is released
|
|
1191
|
+
// rather than leaked. the typeof guard is retained for safety in case
|
|
1192
|
+
// uac is ever some other shape.
|
|
1193
|
+
if (uac && typeof uac.destroy === 'function') {
|
|
1194
|
+
Promise.resolve(uac.destroy()).catch(() => {});
|
|
1195
|
+
}
|
|
1144
1196
|
return callback(err);
|
|
1145
1197
|
}
|
|
1146
1198
|
};
|
|
1147
1199
|
|
|
1200
|
+
// wrap the callback so __x can invoke it exactly once; a late async throw
|
|
1201
|
+
// routed through .catch() must not double-invoke it.
|
|
1202
|
+
const once = (cb) => {
|
|
1203
|
+
let called = false;
|
|
1204
|
+
return (...args) => {
|
|
1205
|
+
if (called) return;
|
|
1206
|
+
called = true;
|
|
1207
|
+
return cb(...args);
|
|
1208
|
+
};
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1148
1211
|
if (callback) {
|
|
1149
|
-
|
|
1212
|
+
const cb = once(callback);
|
|
1213
|
+
// __x is async; if it throws after returning (e.g. a cleanup failure or
|
|
1214
|
+
// a throw in the detached 3pcc ack handler) surface it through the
|
|
1215
|
+
// callback instead of leaking a process-level unhandledRejection.
|
|
1216
|
+
__x(cb).catch((err) => cb(err));
|
|
1150
1217
|
return this;
|
|
1151
1218
|
}
|
|
1152
1219
|
|
|
1153
1220
|
return new Promise((resolve, reject) => {
|
|
1154
|
-
|
|
1221
|
+
const settle = once((err, dialogs) => {
|
|
1155
1222
|
if (err) return reject(err);
|
|
1156
1223
|
resolve(dialogs);
|
|
1157
1224
|
});
|
|
1225
|
+
// __x is async; if it throws after the callback path (e.g. a cleanup
|
|
1226
|
+
// failure), surface it as a promise rejection to the caller's
|
|
1227
|
+
// try/catch instead of leaking a process-level unhandledRejection.
|
|
1228
|
+
__x(settle).catch(settle);
|
|
1158
1229
|
});
|
|
1159
1230
|
}
|
|
1160
1231
|
|
package/package.json
CHANGED
package/test/b2b.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const test = require('tape');
|
|
2
|
-
const { output, sippUac } = require('./sipp')('test_testbed');
|
|
2
|
+
const { output, sippUac, sippUas } = require('./sipp')('test_testbed');
|
|
3
|
+
const { matchesUacDestroyCrash } = require('./scripts/crash-signatures');
|
|
3
4
|
const B2b = require('./scripts/b2b');
|
|
4
5
|
const debug = require('debug')('drachtio:test');
|
|
5
6
|
|
|
@@ -390,6 +391,59 @@ test('B2B', (t) => {
|
|
|
390
391
|
});
|
|
391
392
|
})
|
|
392
393
|
|
|
394
|
+
// 3PCC no-offer INVITE where UAS leg fails after B leg succeeds.
|
|
395
|
+
// Regression test for `uac.destroy is not a function` crash: the
|
|
396
|
+
// cleanup path must reject cleanly instead of throwing a TypeError
|
|
397
|
+
// that escapes as an unhandledRejection and crashes the worker.
|
|
398
|
+
//
|
|
399
|
+
// It ALSO pins the B-leg teardown: the sipp-uas scenario (uas.xml)
|
|
400
|
+
// sends 200 OK then requires ACK + BYE before it completes with exit 0.
|
|
401
|
+
// If createB2BUA failed to tear down the answered B leg (the residual
|
|
402
|
+
// leak), sipp-uas would never see the ACK/BYE and time out -> the
|
|
403
|
+
// sippUasB probe below rejects and this test fails.
|
|
404
|
+
.then(() => {
|
|
405
|
+
debug('starting sipp');
|
|
406
|
+
let sawCrashSignature = false;
|
|
407
|
+
// the bug surfaces as a process-level unhandledRejection (the promise
|
|
408
|
+
// wrapper around __x does not catch async throws); a correct fix
|
|
409
|
+
// surfaces it, if at all, as an app-side error event.
|
|
410
|
+
const onError = (err) => { if (matchesUacDestroyCrash(err)) sawCrashSignature = true; };
|
|
411
|
+
const onRejection = (reason) => { if (matchesUacDestroyCrash(reason)) sawCrashSignature = true; };
|
|
412
|
+
b2b.on('error', onError);
|
|
413
|
+
process.on('unhandledRejection', onRejection);
|
|
414
|
+
b2b.expect3pccUasFailure('sip:sipp-uas-b-teardown');
|
|
415
|
+
// start the B-leg UAS probe: it answers 200 OK and REQUIRES the
|
|
416
|
+
// subsequent ACK + BYE (from createB2BUA's 3pcc teardown) to succeed.
|
|
417
|
+
const sippUasB = sippUas('uas.xml', 'sipp-uas-b-teardown');
|
|
418
|
+
// give the UAS container a moment to start listening before the A-leg
|
|
419
|
+
// INVITE triggers the B-leg INVITE towards it.
|
|
420
|
+
return new Promise((resolve) => setTimeout(resolve, 1500))
|
|
421
|
+
.then(() => sippUac('uac-3pcc-nosdp.xml'))
|
|
422
|
+
.catch(() => {}) // A-leg gets a 480; its own exit code is not the assertion
|
|
423
|
+
.then(() => new Promise((resolve) => setTimeout(resolve, 250))) // let any late rejection surface
|
|
424
|
+
.then(() => {
|
|
425
|
+
b2b.removeListener('error', onError);
|
|
426
|
+
process.removeListener('unhandledRejection', onRejection);
|
|
427
|
+
if (sawCrashSignature) {
|
|
428
|
+
throw new Error('createB2BUA leaked `uac.destroy is not a function` on 3pcc UAS failure');
|
|
429
|
+
}
|
|
430
|
+
// the B-leg UAS must have received ACK + BYE (proving teardown).
|
|
431
|
+
return sippUasB;
|
|
432
|
+
});
|
|
433
|
+
})
|
|
434
|
+
.then(() => {
|
|
435
|
+
return t.pass('b2b 3pcc UAS failure does not crash and tears down the B leg');
|
|
436
|
+
})
|
|
437
|
+
.then(() => {
|
|
438
|
+
b2b.disconnect();
|
|
439
|
+
return new Promise((resolve, reject) => {
|
|
440
|
+
setTimeout(() => {
|
|
441
|
+
b2b = new B2b();
|
|
442
|
+
resolve();
|
|
443
|
+
}, 100);
|
|
444
|
+
});
|
|
445
|
+
})
|
|
446
|
+
|
|
393
447
|
// very fast reinvite from B, before ACK from A
|
|
394
448
|
.then(() => {
|
|
395
449
|
debug('starting sipp');
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
|
2
|
+
<!DOCTYPE scenario SYSTEM "sipp.dtd">
|
|
3
|
+
|
|
4
|
+
<!-- -->
|
|
5
|
+
<!-- 3PCC / no-offer UAC scenario. -->
|
|
6
|
+
<!-- -->
|
|
7
|
+
<!-- Sends an INVITE with NO SDP body (Content-Length: 0). This forces -->
|
|
8
|
+
<!-- drachtio-srf createB2BUA down the 3PCC (no-offer) path, where the -->
|
|
9
|
+
<!-- B-leg UAC dialog is not finalized until the A-leg ACK carries the -->
|
|
10
|
+
<!-- answer SDP. -->
|
|
11
|
+
<!-- -->
|
|
12
|
+
<!-- Used to reproduce the `uac.destroy is not a function` crash: when -->
|
|
13
|
+
<!-- the UAS leg fails AFTER the B leg has produced its {ack, res} -->
|
|
14
|
+
<!-- object, the createB2BUA cleanup path calls uac.destroy() on an -->
|
|
15
|
+
<!-- object that has no destroy() method. -->
|
|
16
|
+
<!-- -->
|
|
17
|
+
|
|
18
|
+
<scenario name="3PCC no-offer UAC">
|
|
19
|
+
<send retrans="500">
|
|
20
|
+
<![CDATA[
|
|
21
|
+
|
|
22
|
+
INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
|
|
23
|
+
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
|
24
|
+
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
|
25
|
+
To: [service] <sip:[service]@[remote_ip]:[remote_port]>
|
|
26
|
+
Call-ID: [call_id]
|
|
27
|
+
CSeq: 1 INVITE
|
|
28
|
+
Contact: sip:sipp@[local_ip]:[local_port]
|
|
29
|
+
Max-Forwards: 70
|
|
30
|
+
Subject: uac-3pcc-nosdp
|
|
31
|
+
Content-Length: 0
|
|
32
|
+
|
|
33
|
+
]]>
|
|
34
|
+
</send>
|
|
35
|
+
|
|
36
|
+
<recv response="100" optional="true">
|
|
37
|
+
</recv>
|
|
38
|
+
|
|
39
|
+
<recv response="180" optional="true">
|
|
40
|
+
</recv>
|
|
41
|
+
|
|
42
|
+
<recv response="183" optional="true">
|
|
43
|
+
</recv>
|
|
44
|
+
|
|
45
|
+
<!-- Because the UAS leg fails, the B2BUA app rejects and sends a 480 -->
|
|
46
|
+
<!-- failure final response to the A leg. The important assertion -->
|
|
47
|
+
<!-- (worker did not crash on uac.destroy) is made on the drachtio-srf -->
|
|
48
|
+
<!-- application side; here we simply complete the transaction. -->
|
|
49
|
+
<recv response="480" rtd="true">
|
|
50
|
+
</recv>
|
|
51
|
+
|
|
52
|
+
<!-- ACK the non-2xx final response to complete the INVITE -->
|
|
53
|
+
<!-- transaction (RFC 3261 - ACK for a failure response uses the same -->
|
|
54
|
+
<!-- branch/CSeq as the INVITE). -->
|
|
55
|
+
<send>
|
|
56
|
+
<![CDATA[
|
|
57
|
+
|
|
58
|
+
ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0
|
|
59
|
+
[last_Via]
|
|
60
|
+
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
|
61
|
+
To: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
|
|
62
|
+
Call-ID: [call_id]
|
|
63
|
+
CSeq: 1 ACK
|
|
64
|
+
Contact: sip:sipp@[local_ip]:[local_port]
|
|
65
|
+
Max-Forwards: 70
|
|
66
|
+
Subject: uac-3pcc-nosdp
|
|
67
|
+
Content-Length: 0
|
|
68
|
+
|
|
69
|
+
]]>
|
|
70
|
+
</send>
|
|
71
|
+
|
|
72
|
+
<!-- definition of the response time repartition table (unit is ms) -->
|
|
73
|
+
<ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
|
|
74
|
+
|
|
75
|
+
<!-- definition of the call length repartition table (unit is ms) -->
|
|
76
|
+
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
|
|
77
|
+
|
|
78
|
+
</scenario>
|
package/test/scripts/b2b.js
CHANGED
|
@@ -2,6 +2,7 @@ const Emitter = require('events');
|
|
|
2
2
|
const Srf = require('../..');
|
|
3
3
|
const config = require('config');
|
|
4
4
|
const debug = require('debug')('drachtio:test');
|
|
5
|
+
const { matchesUacDestroyCrash } = require('./crash-signatures');
|
|
5
6
|
|
|
6
7
|
class App extends Emitter {
|
|
7
8
|
constructor() {
|
|
@@ -106,6 +107,58 @@ class App extends Emitter {
|
|
|
106
107
|
});
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
// Shared invite -> createB2BUA scaffold used by the expect* helpers.
|
|
111
|
+
// onConnected(dialogs, req, res) runs on success; onFailure(err, req, res)
|
|
112
|
+
// runs on rejection. Removes the duplicated invite/createB2BUA/then/catch
|
|
113
|
+
// boilerplate from each caller.
|
|
114
|
+
_inviteB2BUA(uri, opts, {onConnected, onFailure} = {}) {
|
|
115
|
+
this.srf.invite((req, res) => {
|
|
116
|
+
this.srf.createB2BUA(req, res, uri, opts)
|
|
117
|
+
.then((dialogs) => {
|
|
118
|
+
if (onConnected) return onConnected(dialogs, req, res);
|
|
119
|
+
this.emit('connected', dialogs);
|
|
120
|
+
})
|
|
121
|
+
.catch((err) => {
|
|
122
|
+
if (onFailure) return onFailure(err, req, res);
|
|
123
|
+
throw err;
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Reproduces the `uac.destroy is not a function` crash.
|
|
129
|
+
//
|
|
130
|
+
// The A leg INVITE arrives with no SDP, so createB2BUA takes the 3PCC
|
|
131
|
+
// (no-offer) path. The B leg UAC succeeds and returns a {ack, res}
|
|
132
|
+
// object (NOT a finalized dialog). We then force the UAS leg to fail
|
|
133
|
+
// by supplying a localSdpA function that throws. This drives execution
|
|
134
|
+
// into the createB2BUA catch block while `uac` is still the {ack, res}
|
|
135
|
+
// object, exercising the guarded uac.destroy() cleanup.
|
|
136
|
+
//
|
|
137
|
+
// Before the fix this threw a synchronous TypeError inside the catch,
|
|
138
|
+
// which surfaced as an unhandledRejection and crashed the worker.
|
|
139
|
+
// After the fix the promise rejects cleanly and this onFailure runs.
|
|
140
|
+
expect3pccUasFailure(uri) {
|
|
141
|
+
this._inviteB2BUA(uri, {
|
|
142
|
+
localSdpA: () => {
|
|
143
|
+
throw new Error('simulated SDP/route processing failure on UAS leg');
|
|
144
|
+
}
|
|
145
|
+
}, {
|
|
146
|
+
onConnected: () => {
|
|
147
|
+
this.emit('error', new Error('unexpected dialog success - expected UAS leg failure'));
|
|
148
|
+
},
|
|
149
|
+
onFailure: (err, req, res) => {
|
|
150
|
+
debug(`expect3pccUasFailure: got expected error ${err}`);
|
|
151
|
+
if (matchesUacDestroyCrash(err)) {
|
|
152
|
+
// the original bug leaked back to the app as a rejection
|
|
153
|
+
this.emit('error', err);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.emit('failed', err);
|
|
157
|
+
if (!res.finalResponseSent) res.send(480);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
109
162
|
immediateReinviteFromB(uri) {
|
|
110
163
|
this.srf.invite((req, res) => {
|
|
111
164
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Single source of truth for the crash signature the 3pcc regression test
|
|
2
|
+
// watches for. Referenced by both the app-side helper (scripts/b2b.js) and
|
|
3
|
+
// the test assertion (b2b.js) so the two cannot drift out of sync.
|
|
4
|
+
//
|
|
5
|
+
// NOTE: this matches on the V8 TypeError wording. The underlying defect is
|
|
6
|
+
// "uac was the {ack,res} 3pcc object, not a Dialog", so if a future engine
|
|
7
|
+
// rewords the message this regex must be updated here (one place).
|
|
8
|
+
const UAC_DESTROY_CRASH = /uac\.destroy is not a function/;
|
|
9
|
+
|
|
10
|
+
const matchesUacDestroyCrash = (reason) => {
|
|
11
|
+
const message = typeof reason === 'string' ? reason : reason && reason.message;
|
|
12
|
+
return UAC_DESTROY_CRASH.test(message || '');
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
module.exports = { UAC_DESTROY_CRASH, matchesUacDestroyCrash };
|
package/test/sipp.js
CHANGED
|
@@ -25,7 +25,6 @@ obj.output = () => {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
obj.sippUac = (file) => {
|
|
28
|
-
const cmd = 'docker';
|
|
29
28
|
const args = [
|
|
30
29
|
'run', '--rm', '--net', `${network}`,
|
|
31
30
|
'-v', `${__dirname}/scenarios:/tmp/scenarios`,
|
|
@@ -38,9 +37,29 @@ obj.sippUac = (file) => {
|
|
|
38
37
|
];
|
|
39
38
|
|
|
40
39
|
clearOutput();
|
|
40
|
+
return runSipp(args);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Run a sipp UAS as a named, DNS-resolvable container on the test network so
|
|
44
|
+
// the SUT can route a B-leg INVITE to `sip:<containerName>`. Resolves when the
|
|
45
|
+
// scenario completes (exit 0), e.g. uas.xml requires INVITE -> 200 -> ACK -> BYE,
|
|
46
|
+
// so a resolved promise proves the B leg was answered AND torn down.
|
|
47
|
+
obj.sippUas = (file, containerName) => {
|
|
48
|
+
const args = [
|
|
49
|
+
'run', '--rm', '--net', `${network}`, '--name', `${containerName}`,
|
|
50
|
+
'-v', `${__dirname}/scenarios:/tmp/scenarios`,
|
|
51
|
+
'drachtio/sipp', 'sipp', '-sf', `/tmp/scenarios/${file}`,
|
|
52
|
+
'-m', '1',
|
|
53
|
+
'-nostdin'
|
|
54
|
+
// no target host: UAS listens for incoming calls
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
return runSipp(args);
|
|
58
|
+
};
|
|
41
59
|
|
|
60
|
+
function runSipp(args) {
|
|
42
61
|
return new Promise((resolve, reject) => {
|
|
43
|
-
const child_process = spawn(
|
|
62
|
+
const child_process = spawn('docker', args, {stdio: ['inherit', 'pipe', 'pipe']});
|
|
44
63
|
|
|
45
64
|
child_process.on('exit', (code, signal) => {
|
|
46
65
|
if (code === 0) {
|
|
@@ -54,12 +73,10 @@ obj.sippUac = (file) => {
|
|
|
54
73
|
});
|
|
55
74
|
|
|
56
75
|
child_process.stdout.on('data', (data) => {
|
|
57
|
-
//debug(`stdout: ${data}`);
|
|
58
76
|
addOutput(data.toString());
|
|
59
77
|
});
|
|
60
|
-
child_process.
|
|
61
|
-
//debug(`stdout: ${data}`);
|
|
78
|
+
child_process.stderr.on('data', (data) => {
|
|
62
79
|
addOutput(data.toString());
|
|
63
80
|
});
|
|
64
81
|
});
|
|
65
|
-
}
|
|
82
|
+
}
|