@jambonz/node-red-contrib-jambonz 2.4.18 → 2.4.20
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/package.json +2 -2
- package/src/nodes/_template.js +1 -1
- package/src/nodes/config.html +534 -414
- package/src/nodes/config.js +24 -0
- package/src/nodes/create_call.html +21 -1
- package/src/nodes/create_call.js +14 -16
- package/src/nodes/create_sms.js +10 -16
- package/src/nodes/dial.html +16 -0
- package/src/nodes/dial.js +8 -0
- package/src/nodes/get_alerts.js +24 -13
- package/src/nodes/get_call.js +19 -13
- package/src/nodes/get_calls.js +19 -13
- package/src/nodes/get_recent_calls.js +20 -9
- package/src/nodes/lcc.html +51 -0
- package/src/nodes/lcc.js +17 -5
- package/src/nodes/libs.js +54 -16
- package/src/nodes/listen.html +1 -0
- package/src/nodes/sip-decline.html +15 -1
- package/src/nodes/sip-decline.js +7 -2
- package/src/utils/http-helpers.js +15 -9
package/src/nodes/config.js
CHANGED
|
@@ -62,6 +62,30 @@ module.exports = function(RED) {
|
|
|
62
62
|
config.record_siprecServerURL != '' ? obj.record.siprecServerURL = new_resolve(RED, config.record_siprecServerURL, config.record_siprecServerURLType, node, msg) : null
|
|
63
63
|
config.record_recordingID != '' ? obj.record.recordingID = new_resolve(RED, config.record_recordingID, config.record_recordingIDType, node, msg) : null
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
if (config.listenRequest) {
|
|
67
|
+
obj.listen = {}
|
|
68
|
+
const authUser = new_resolve(RED, config.listenAuthUser, config.listenAuthUserType, node, msg);
|
|
69
|
+
const authPass = new_resolve(RED, config.listenAuthPass, config.listenAuthPassType, node, msg);
|
|
70
|
+
if (authUser && authPass) {
|
|
71
|
+
obj.listen.wsAuth = {
|
|
72
|
+
username: authUser,
|
|
73
|
+
password: authPass
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
obj.listen.enable = config.listenEnabled;
|
|
77
|
+
obj.listen.sampleRate = +config.listenSampleRate;
|
|
78
|
+
obj.listen.mixType = config.listenMixType;
|
|
79
|
+
config.url != '' ? obj.listen.url = new_resolve(RED, config.listenUrl, config.listenUrlType, node, msg) : null;
|
|
80
|
+
config.listenMetadata != '' ? obj.listen.metadata = new_resolve(RED, config.listenMetadata, config.listenMetadataType, node, msg) : null;
|
|
81
|
+
if (!Object.keys(obj.listen.metadata).length) {
|
|
82
|
+
delete obj.listen.metadata;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (config.sipRequest) {
|
|
87
|
+
config.sipRequestWithinDialogHook != '' ? obj.sipRequestWithinDialogHook = new_resolve(RED, config.sipRequestWithinDialogHook, config.sipRequestWithinDialogHookType, node, msg) : null;
|
|
88
|
+
}
|
|
65
89
|
appendVerb(msg, obj);
|
|
66
90
|
node.send(msg);
|
|
67
91
|
});
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
fromType: {value: ''},
|
|
18
18
|
to: {value: '', required: true},
|
|
19
19
|
toType: {value: ''},
|
|
20
|
+
trunk: {value: ''},
|
|
21
|
+
trunkType: {value: 'str'},
|
|
20
22
|
dest: {value: 'phone', required: true},
|
|
21
23
|
timeout: {validate: RED.validators.regex(/^\d*$/) },
|
|
22
24
|
tag: {required: true},
|
|
@@ -66,9 +68,18 @@
|
|
|
66
68
|
var destElem = $('#node-input-dest');
|
|
67
69
|
var serverElem = $('#node-input-server');
|
|
68
70
|
var applicationElem = $('#node-input-application');
|
|
71
|
+
var trunkDiv = $('#trunk');
|
|
69
72
|
prepareTtsControls(node);
|
|
70
73
|
prepareSttControls(node);
|
|
71
74
|
|
|
75
|
+
destElem.change(onDestChanged);
|
|
76
|
+
|
|
77
|
+
var onDestChanged = function () {
|
|
78
|
+
var selectedDest = destElem.find(':selected').val();
|
|
79
|
+
if ('phone' === selectedDest) trunkDiv.show();
|
|
80
|
+
else trunkDiv.hide();
|
|
81
|
+
}
|
|
82
|
+
|
|
72
83
|
$('#node-input-from').typedInput({
|
|
73
84
|
default: $('#node-input-fromType').val(),
|
|
74
85
|
types: ['str','msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
@@ -98,7 +109,11 @@
|
|
|
98
109
|
types: ['json', 'msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
99
110
|
typeField: $('#node-input-tagType')
|
|
100
111
|
});
|
|
101
|
-
|
|
112
|
+
$('#node-input-trunk').typedInput({
|
|
113
|
+
default: $('#node-input-trunkType').val(),
|
|
114
|
+
types: ['num', 'msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
115
|
+
typeField: $('#node-input-trunkType')
|
|
116
|
+
});
|
|
102
117
|
var populateApplications = function() {
|
|
103
118
|
var serverId = $('#node-input-server option:selected').val();
|
|
104
119
|
$.ajax({
|
|
@@ -224,6 +239,11 @@
|
|
|
224
239
|
<input type="text" id="node-input-to" placeholder="called party info">
|
|
225
240
|
<input type="hidden" id="node-input-toType">
|
|
226
241
|
</div>
|
|
242
|
+
<div class="form-row" id ="trunk">
|
|
243
|
+
<label for="node-input-trunk">Trunk</label>
|
|
244
|
+
<input type="text" id="node-input-trunk" placeholder="Specify the name of the trunk used for this outbound call">
|
|
245
|
+
<input type="hidden" id="node-input-trunkType">
|
|
246
|
+
</div>
|
|
227
247
|
<div class="form-row">
|
|
228
248
|
<label for="node-input-dest">Call type</label>
|
|
229
249
|
<select id="node-input-dest">
|
package/src/nodes/create_call.js
CHANGED
|
@@ -71,6 +71,10 @@ module.exports = function(RED) {
|
|
|
71
71
|
|
|
72
72
|
switch (config.dest) {
|
|
73
73
|
case 'phone':
|
|
74
|
+
if (config.trunk) {
|
|
75
|
+
var trunk = new_resolve(RED, config.trunk, config.trunkType, node, msg);
|
|
76
|
+
opts.to.trunk = trunk;
|
|
77
|
+
}
|
|
74
78
|
opts.to.number = to;
|
|
75
79
|
break;
|
|
76
80
|
case 'user':
|
|
@@ -89,26 +93,20 @@ module.exports = function(RED) {
|
|
|
89
93
|
return;
|
|
90
94
|
}
|
|
91
95
|
try {
|
|
92
|
-
node
|
|
93
|
-
const res = await doCreateCall(url, accountSid, apiToken, opts);
|
|
96
|
+
const response = await doCreateCall(node, url, accountSid, apiToken, opts);
|
|
94
97
|
msg.statusCode = 201;
|
|
95
|
-
msg.callSid =
|
|
96
|
-
msg.callId =
|
|
98
|
+
msg.callSid = response.sid;
|
|
99
|
+
msg.callId = response.callId;
|
|
97
100
|
} catch (err) {
|
|
98
101
|
if (err.statusCode) {
|
|
99
|
-
node.
|
|
100
|
-
try {
|
|
101
|
-
const responseBody = await err.json();
|
|
102
|
-
node.error(`create-call failed with ${err.statusCode}. Response ${JSON.stringify(responseBody)}`);
|
|
103
|
-
} catch (e) {
|
|
104
|
-
node.error(`create-call failed with ${err.statusCode}`);
|
|
105
|
-
}
|
|
102
|
+
node.error(`create-call failed with ${err.statusCode}`);
|
|
106
103
|
msg.statusCode = err.statusCode;
|
|
107
|
-
|
|
108
|
-
else {
|
|
109
|
-
|
|
110
|
-
if (done) done(
|
|
111
|
-
else node.error(
|
|
104
|
+
msg.errorMessage = err.statusText;
|
|
105
|
+
} else {
|
|
106
|
+
const errorMessage = `Error sending create call ${err.message}`;
|
|
107
|
+
if (done) done(errorMessage);
|
|
108
|
+
else node.error(errorMessage, msg);
|
|
109
|
+
msg.errorMessage = errorMessage;
|
|
112
110
|
send(msg);
|
|
113
111
|
return;
|
|
114
112
|
}
|
package/src/nodes/create_sms.js
CHANGED
|
@@ -31,26 +31,20 @@ function create_sms(config) {
|
|
|
31
31
|
provider
|
|
32
32
|
};
|
|
33
33
|
try {
|
|
34
|
-
node
|
|
35
|
-
const res = await doCreateMessage(url, accountSid, apiToken, opts);
|
|
34
|
+
const response = await doCreateMessage(node, url, accountSid, apiToken, opts);
|
|
36
35
|
msg.statusCode = 201;
|
|
37
|
-
msg.messageSid =
|
|
38
|
-
msg.providerResponse =
|
|
36
|
+
msg.messageSid = response.sid;
|
|
37
|
+
msg.providerResponse = response.providerResponse;
|
|
39
38
|
} catch (err) {
|
|
40
39
|
if (err.statusCode) {
|
|
41
|
-
node.
|
|
42
|
-
try {
|
|
43
|
-
const responseBody = await err.json();
|
|
44
|
-
node.error(`create_sms failed with ${err.statusCode}. Response ${JSON.stringify(responseBody)}`);
|
|
45
|
-
} catch (e) {
|
|
46
|
-
node.error(`create_sms failed with ${err.statusCode}`);
|
|
47
|
-
}
|
|
40
|
+
node.error(`create_sms failed with ${err.statusCode}`);
|
|
48
41
|
msg.statusCode = err.statusCode;
|
|
49
|
-
|
|
50
|
-
else {
|
|
51
|
-
|
|
52
|
-
if (done) done(
|
|
53
|
-
else node.error(
|
|
42
|
+
msg.errorMessage = err.statusText;
|
|
43
|
+
} else {
|
|
44
|
+
const errorMessage = `Error sending create message ${err.message}`;
|
|
45
|
+
if (done) done(errorMessage);
|
|
46
|
+
else node.error(errorMessage, msg);
|
|
47
|
+
msg.errorMessage = errorMessage;
|
|
54
48
|
send(msg);
|
|
55
49
|
return;
|
|
56
50
|
}
|
package/src/nodes/dial.html
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
actionhook: {value: ''},
|
|
17
17
|
actionhookType: {value: 'str'},
|
|
18
18
|
answeronbridge: {value: false},
|
|
19
|
+
anchormedia: {value: false},
|
|
19
20
|
callerid: {value: ''},
|
|
20
21
|
calleridType: {value: ''},
|
|
21
22
|
callername: {value: ''},
|
|
@@ -30,6 +31,8 @@
|
|
|
30
31
|
referhookType: {value: 'str'},
|
|
31
32
|
dtmfhook: {value: ''},
|
|
32
33
|
dtmfhookType: {value: 'str'},
|
|
34
|
+
onholdhook: {value: ''},
|
|
35
|
+
onholdhookType: {value: 'str'},
|
|
33
36
|
timelimit: {validate:RED.validators.regex(/^\d*$/) },
|
|
34
37
|
timeout: {validate:RED.validators.regex(/^\d*$/) },
|
|
35
38
|
listenurl: {value: ''},
|
|
@@ -106,6 +109,10 @@
|
|
|
106
109
|
types: ['str', 'msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
107
110
|
typeField: $('#node-input-dtmfhookType')
|
|
108
111
|
});
|
|
112
|
+
$('#node-input-onholdhook').typedInput({
|
|
113
|
+
types: ['str', 'msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
114
|
+
typeField: $('#node-input-onholdhookType')
|
|
115
|
+
});
|
|
109
116
|
$('#node-input-transcribeurl').typedInput({
|
|
110
117
|
types: ['str', 'msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
111
118
|
typeField: $('#node-input-transcribeurlType')
|
|
@@ -408,6 +415,10 @@
|
|
|
408
415
|
<label for="node-input-answeronbridge">Answer on bridge</label>
|
|
409
416
|
<input type="checkbox" id="node-input-answeronbridge">
|
|
410
417
|
</div>
|
|
418
|
+
<div class="form-row">
|
|
419
|
+
<label for="node-input-anchormedia">Anchor media</label>
|
|
420
|
+
<input type="checkbox" id="node-input-anchormedia">
|
|
421
|
+
</div>
|
|
411
422
|
<div class="form-row">
|
|
412
423
|
<label for="node-input-callerid">Caller ID</label>
|
|
413
424
|
<input type="text" id="node-input-callerid" placeholder="caller id to place on outbound call">
|
|
@@ -443,6 +454,11 @@
|
|
|
443
454
|
<input type="text" id="node-input-dtmfhook" placeholder="webhook to call when dtmf is captured">
|
|
444
455
|
<input type="hidden" id="node-input-dtmfhookType">
|
|
445
456
|
</div>
|
|
457
|
+
<div class="form-row">
|
|
458
|
+
<label for="node-input-onholdhook">Onhold hook</label>
|
|
459
|
+
<input type="text" id="node-input-onholdhook" placeholder="webhook to call when call is on hold">
|
|
460
|
+
<input type="hidden" id="node-input-onholdhookType">
|
|
461
|
+
</div>
|
|
446
462
|
<div class="form-row">
|
|
447
463
|
<label for="node-input-timelimit">Time limit</label>
|
|
448
464
|
<input type="text" id="node-input-timelimit" placeholder="max duration of call in secs">
|
package/src/nodes/dial.js
CHANGED
|
@@ -54,6 +54,14 @@ module.exports = function(RED) {
|
|
|
54
54
|
dtmfHook: new_resolve(RED, config.dtmfhook, config.dtmfhookType, node, msg),
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
if (config.hasOwnProperty('anchormedia')) {
|
|
58
|
+
data.anchorMedia = config.anchormedia;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (config.onholdhook) {
|
|
62
|
+
data.onHoldHook = new_resolve(RED, config.onholdhook, config.onholdhookType, node, msg);
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
// headers
|
|
58
66
|
var headers = {};
|
|
59
67
|
config.headers.forEach(function(h) {
|
package/src/nodes/get_alerts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const {fetch} = require('undici');
|
|
2
2
|
var {new_resolve} = require('./libs');
|
|
3
3
|
|
|
4
4
|
module.exports = function(RED) {
|
|
@@ -14,25 +14,36 @@ module.exports = function(RED) {
|
|
|
14
14
|
days: new_resolve(RED, config.days, config.daysType, node, msg),
|
|
15
15
|
}
|
|
16
16
|
Object.keys(data).forEach((k) => data[k] == null || data[k] == '' && delete data[k]);
|
|
17
|
-
const params = new URLSearchParams(data).toString()
|
|
18
|
-
const req = bent(`${server.url}/v1/Accounts/${accountSid}/Alerts?${params}`, 'GET', 'json', {
|
|
19
|
-
'Authorization': `Bearer ${apiToken}`
|
|
20
|
-
});
|
|
17
|
+
const params = new URLSearchParams(data).toString()
|
|
21
18
|
try {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
const response = await fetch(`${server.url}/v1/Accounts/${accountSid}/Alerts?${params}`, {
|
|
20
|
+
method: 'GET',
|
|
21
|
+
headers: {
|
|
22
|
+
'Authorization': `Bearer ${apiToken}`
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
const error = new Error('Bad response');
|
|
27
|
+
error.statusCode = response.status;
|
|
28
|
+
error.statusText = response.statusText;
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
const res = await response.json();
|
|
32
|
+
msg.payload = res.data;
|
|
33
|
+
msg.total = res.total;
|
|
34
|
+
msg.page_size = res.page_size;
|
|
35
|
+
msg.page = res.page;
|
|
27
36
|
} catch (err) {
|
|
28
37
|
if (err.statusCode) {
|
|
29
38
|
node.error(`GetAlerts failed with ${err.statusCode}`);
|
|
30
39
|
msg.statusCode = err.statusCode;
|
|
40
|
+
msg.errorMessage = err.statusText;
|
|
31
41
|
}
|
|
32
42
|
else {
|
|
33
|
-
|
|
34
|
-
if (done) done(
|
|
35
|
-
else node.error(
|
|
43
|
+
const errorMessage = `Error getting alerts ${err.message}`;
|
|
44
|
+
if (done) done(errorMessage);
|
|
45
|
+
else node.error(errorMessage, msg);
|
|
46
|
+
msg.errorMessage = errorMessage;
|
|
36
47
|
send(msg);
|
|
37
48
|
return;
|
|
38
49
|
}
|
package/src/nodes/get_call.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const {fetch} = require('undici');
|
|
2
2
|
var { new_resolve} = require("./libs");
|
|
3
3
|
|
|
4
4
|
module.exports = function (RED) {
|
|
@@ -14,25 +14,31 @@ module.exports = function (RED) {
|
|
|
14
14
|
else node.error(new Error('CallSid empty'), msg);
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
-
const req = bent(
|
|
18
|
-
`${server.url}/v1/Accounts/${accountSid}/Calls/${callSid}`,
|
|
19
|
-
'GET',
|
|
20
|
-
'json',
|
|
21
|
-
{
|
|
22
|
-
Authorization: `Bearer ${apiToken}`,
|
|
23
|
-
}
|
|
24
|
-
);
|
|
25
17
|
try {
|
|
26
|
-
const
|
|
18
|
+
const response = await fetch(`${server.url}/v1/Accounts/${accountSid}/Calls/${callSid}`, {
|
|
19
|
+
method: 'GET',
|
|
20
|
+
headers: {
|
|
21
|
+
'Authorization': `Bearer ${apiToken}`
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
const error = new Error('Bad response');
|
|
26
|
+
error.statusCode = response.status;
|
|
27
|
+
error.statusText = response.statusText;
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
const res = await response.json();
|
|
27
31
|
msg.payload = res;
|
|
28
32
|
} catch (err) {
|
|
29
33
|
if (err.statusCode) {
|
|
30
34
|
node.error(`GetCall failed with ${err.statusCode}`);
|
|
31
35
|
msg.statusCode = err.statusCode;
|
|
36
|
+
msg.errorMessage = err.statusText;
|
|
32
37
|
} else {
|
|
33
|
-
|
|
34
|
-
if (done) done(
|
|
35
|
-
else node.error(
|
|
38
|
+
const errorMessage = `Error getting call info ${err.message}`;
|
|
39
|
+
if (done) done(errorMessage);
|
|
40
|
+
else node.error(errorMessage, msg);
|
|
41
|
+
msg.errorMessage = errorMessage;
|
|
36
42
|
send(msg);
|
|
37
43
|
return;
|
|
38
44
|
}
|
package/src/nodes/get_calls.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const {fetch} = require('undici');
|
|
2
2
|
var { new_resolve} = require("./libs");
|
|
3
3
|
|
|
4
4
|
module.exports = function (RED) {
|
|
@@ -18,25 +18,31 @@ module.exports = function (RED) {
|
|
|
18
18
|
(k) => data[k] == null || (data[k] == '' && delete data[k])
|
|
19
19
|
);
|
|
20
20
|
const params = new URLSearchParams(data).toString();
|
|
21
|
-
const req = bent(
|
|
22
|
-
`${server.url}/v1/Accounts/${accountSid}/Calls${ params ? '?' + params : ''}`,
|
|
23
|
-
'GET',
|
|
24
|
-
'json',
|
|
25
|
-
{
|
|
26
|
-
Authorization: `Bearer ${apiToken}`,
|
|
27
|
-
}
|
|
28
|
-
);
|
|
29
21
|
try {
|
|
30
|
-
const
|
|
22
|
+
const response = await fetch(`${server.url}/v1/Accounts/${accountSid}/Calls${ params ? '?' + params : ''}`, {
|
|
23
|
+
method: 'GET',
|
|
24
|
+
headers: {
|
|
25
|
+
'Authorization': `Bearer ${apiToken}`
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
const error = new Error('Bad response');
|
|
30
|
+
error.statusCode = response.status;
|
|
31
|
+
error.statusText = response.statusText;
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
const res = await response.json();
|
|
31
35
|
msg.payload = res;
|
|
32
36
|
} catch (err) {
|
|
33
37
|
if (err.statusCode) {
|
|
34
38
|
node.error(`GetCalls failed with ${err.statusCode}`);
|
|
35
39
|
msg.statusCode = err.statusCode;
|
|
40
|
+
msg.errorMessage = err.statusText;
|
|
36
41
|
} else {
|
|
37
|
-
|
|
38
|
-
if (done) done(
|
|
39
|
-
else node.error(
|
|
42
|
+
const errorMessage = `Error getting calls ${err.message}`;
|
|
43
|
+
if (done) done(errorMessage);
|
|
44
|
+
else node.error(errorMessage, msg);
|
|
45
|
+
msg.errorMessage = errorMessage;
|
|
40
46
|
send(msg);
|
|
41
47
|
return;
|
|
42
48
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const {fetch} = require('undici');
|
|
2
2
|
var {new_resolve} = require('./libs');
|
|
3
3
|
|
|
4
4
|
module.exports = function(RED) {
|
|
@@ -16,12 +16,21 @@ module.exports = function(RED) {
|
|
|
16
16
|
days: new_resolve(RED, config.days, config.daysType, node, msg),
|
|
17
17
|
}
|
|
18
18
|
Object.keys(data).forEach((k) => data[k] == null || data[k] == '' && delete data[k]);
|
|
19
|
-
const params = new URLSearchParams(data).toString();
|
|
20
|
-
const req = bent(`${server.url}/v1/Accounts/${accountSid}/RecentCalls?${params}`, 'GET', 'json', {
|
|
21
|
-
'Authorization': `Bearer ${apiToken}`
|
|
22
|
-
});
|
|
19
|
+
const params = new URLSearchParams(data).toString();
|
|
23
20
|
try {
|
|
24
|
-
const
|
|
21
|
+
const response = await fetch(`${server.url}/v1/Accounts/${accountSid}/RecentCalls?${params}`, {
|
|
22
|
+
method: 'GET',
|
|
23
|
+
headers: {
|
|
24
|
+
'Authorization': `Bearer ${apiToken}`
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
const error = new Error('Bad response');
|
|
29
|
+
error.statusCode = response.status;
|
|
30
|
+
error.statusText = response.statusText;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
const res = await response.json();
|
|
25
34
|
msg.payload = res.data;
|
|
26
35
|
msg.total = res.total;
|
|
27
36
|
msg.page_size = res.page_size;
|
|
@@ -30,11 +39,13 @@ module.exports = function(RED) {
|
|
|
30
39
|
if (err.statusCode) {
|
|
31
40
|
node.error(`GetRecentCalls failed with ${err.statusCode}`);
|
|
32
41
|
msg.statusCode = err.statusCode;
|
|
42
|
+
msg.errorMessage = err.statusText;
|
|
33
43
|
}
|
|
34
44
|
else {
|
|
35
|
-
|
|
36
|
-
if (done) done(
|
|
37
|
-
else node.error(
|
|
45
|
+
const errorMessage = `Error getting recent calls ${err.message}`;
|
|
46
|
+
if (done) done(errorMessage);
|
|
47
|
+
else node.error(errorMessage, msg);
|
|
48
|
+
msg.errorMessage = errorMessage;
|
|
38
49
|
send(msg);
|
|
39
50
|
return;
|
|
40
51
|
}
|
package/src/nodes/lcc.html
CHANGED
|
@@ -55,6 +55,15 @@
|
|
|
55
55
|
siprecHeaders: {value: []},
|
|
56
56
|
recordingID: {value: ''},
|
|
57
57
|
recordingIDType: {value: 'str'},
|
|
58
|
+
dtmfDigit: {value: '', validate: function(v) {
|
|
59
|
+
const action = $('#node-input-action').val();
|
|
60
|
+
return action !== 'send_dtmf' || v.length > 0;
|
|
61
|
+
}},
|
|
62
|
+
dtmfDigitType: {value: 'str'},
|
|
63
|
+
dtmfDuration: {value: ''},
|
|
64
|
+
dtmfDurationType: {value: 'str'},
|
|
65
|
+
tag: {value: ''},
|
|
66
|
+
tagType: {value: 'str'},
|
|
58
67
|
},
|
|
59
68
|
inputs:1,
|
|
60
69
|
outputs:1,
|
|
@@ -74,6 +83,8 @@
|
|
|
74
83
|
var waitHookDiv = $('#wait-options');
|
|
75
84
|
var recordDiv = $('#record-options');
|
|
76
85
|
var sipRequestDiv = $('#sip-request-options');
|
|
86
|
+
var dtmfDiv = $('#dtmf-options');
|
|
87
|
+
var tagDiv = $('#tag-options');
|
|
77
88
|
|
|
78
89
|
$('#node-input-callSid').typedInput({
|
|
79
90
|
default: $('#node-input-callSidType').val(),
|
|
@@ -120,6 +131,21 @@
|
|
|
120
131
|
types: ['str','msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
121
132
|
typeField: $('#node-input-recordingIDType')
|
|
122
133
|
});
|
|
134
|
+
$('#node-input-dtmfDigit').typedInput({
|
|
135
|
+
default: $('#node-input-dtmfDigitType').val(),
|
|
136
|
+
types: ['str','msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
137
|
+
typeField: $('#node-input-dtmfDigitType')
|
|
138
|
+
});
|
|
139
|
+
$('#node-input-dtmfDuration').typedInput({
|
|
140
|
+
default: $('#node-input-dtmfDurationType').val(),
|
|
141
|
+
types: ['str','msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
142
|
+
typeField: $('#node-input-dtmfDurationType')
|
|
143
|
+
});
|
|
144
|
+
$('#node-input-tag').typedInput({
|
|
145
|
+
default: $('#node-input-tagType').val(),
|
|
146
|
+
types: ['json', 'msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
147
|
+
typeField: $('#node-input-tagType')
|
|
148
|
+
});
|
|
123
149
|
|
|
124
150
|
var onActionChanged = function () {
|
|
125
151
|
var selectedAction = actionElem.find(':selected').val();
|
|
@@ -133,6 +159,10 @@
|
|
|
133
159
|
else recordDiv.hide();
|
|
134
160
|
if ('sip_request' === selectedAction) sipRequestDiv.show();
|
|
135
161
|
else sipRequestDiv.hide();
|
|
162
|
+
if ('send_dtmf' === selectedAction) dtmfDiv.show();
|
|
163
|
+
else dtmfDiv.hide();
|
|
164
|
+
if ('tag' === selectedAction) tagDiv.show();
|
|
165
|
+
else tagDiv.hide();
|
|
136
166
|
}
|
|
137
167
|
|
|
138
168
|
var onVendorChanged = function() {
|
|
@@ -299,6 +329,8 @@
|
|
|
299
329
|
<option value="stop_call_recording">stop call recording</option>
|
|
300
330
|
<option value="pause_call_recording">pause call recording</option>
|
|
301
331
|
<option value="resume_call_recording">resume call recording</option>
|
|
332
|
+
<option value="send_dtmf">send RFC 2833 DTMF</option>
|
|
333
|
+
<option value="tag">tag call</option>
|
|
302
334
|
</select>
|
|
303
335
|
</div>
|
|
304
336
|
<div id="say-options">
|
|
@@ -394,6 +426,25 @@
|
|
|
394
426
|
<input type="hidden" id="node-input-sipRequestHeadersType">
|
|
395
427
|
</div>
|
|
396
428
|
</div>
|
|
429
|
+
<div id="dtmf-options">
|
|
430
|
+
<div class="form-row">
|
|
431
|
+
<label for="node-input-dtmfDigit">DTMF digit</label>
|
|
432
|
+
<input type="text" id="node-input-dtmfDigit">
|
|
433
|
+
<input type="hidden" id="node-input-dtmfDigitType">
|
|
434
|
+
</div>
|
|
435
|
+
<div class="form-row">
|
|
436
|
+
<label for="node-input-dtmfDuration">DTMF duration</label>
|
|
437
|
+
<input type="text" id="node-input-dtmfDuration" placeholder="250">
|
|
438
|
+
<input type="hidden" id="node-input-dtmfDurationType">
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
<div id="tag-options">
|
|
442
|
+
<div class="form-row">
|
|
443
|
+
<label for="node-input-tag">Data</label>
|
|
444
|
+
<input type="text" id="node-input-tag" placeholder="data object">
|
|
445
|
+
<input type="hidden" id="node-input-tagType">
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
397
448
|
</script>
|
|
398
449
|
|
|
399
450
|
<!-- Help Text -->
|
package/src/nodes/lcc.js
CHANGED
|
@@ -113,6 +113,15 @@ function lcc(config) {
|
|
|
113
113
|
case 'resume_call_recording':
|
|
114
114
|
opts.record = { action: 'resumeCallRecording' };
|
|
115
115
|
break;
|
|
116
|
+
case 'send_dtmf':
|
|
117
|
+
opts.dtmf = {
|
|
118
|
+
digit: new_resolve(RED, config.dtmfDigit, config.dtmfDigitType, node, msg),
|
|
119
|
+
duration: new_resolve(RED, config.dtmfDuration, config.dtmfDurationType, node, msg) || '250'
|
|
120
|
+
};
|
|
121
|
+
break;
|
|
122
|
+
case 'tag':
|
|
123
|
+
opts.tag = new_resolve(RED, config.tag, config.tagType, node, msg);
|
|
124
|
+
break;
|
|
116
125
|
default:
|
|
117
126
|
node.log(`invalid action: ${config.action}`);
|
|
118
127
|
send(msg);
|
|
@@ -124,11 +133,14 @@ function lcc(config) {
|
|
|
124
133
|
msg.payload = await doLCC(node, url, accountSid, apiToken, callSid, opts);
|
|
125
134
|
msg.statusCode = config.action === 'sip_request' ? 200 : 202;
|
|
126
135
|
} catch (err) {
|
|
127
|
-
if (err.statusCode)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
if (err.statusCode) {
|
|
137
|
+
msg.statusCode = err.statusCode;
|
|
138
|
+
msg.errorMessage = err.statusText;
|
|
139
|
+
} else {
|
|
140
|
+
const errorMessage = `Error sending LCC ${err.message}`;
|
|
141
|
+
if (done) done(errorMessage);
|
|
142
|
+
else node.error(errorMessage, msg);
|
|
143
|
+
msg.errorMessage = errorMessage;
|
|
132
144
|
send(msg);
|
|
133
145
|
return;
|
|
134
146
|
}
|
package/src/nodes/libs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const {fetch} = require('undici');
|
|
2
2
|
var mustache = require('mustache');
|
|
3
3
|
mustache.escape = function(text) {return text;};
|
|
4
4
|
|
|
@@ -110,27 +110,65 @@ exports.appendVerb = (msg, obj) => {
|
|
|
110
110
|
return mustache.render(newString, data);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
exports.doLCC = async (node, baseUrl, accountSid, apiToken, callSid, opts) => {
|
|
114
|
+
const url = `${baseUrl}/v1/Accounts/${accountSid}/Calls/${callSid}`;
|
|
115
|
+
node.log(`invoking LCC with payload ${JSON.stringify(opts)} at ${url}`);
|
|
116
|
+
const response = await fetch(url, {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
headers: {
|
|
119
|
+
'Content-Type': 'application/json',
|
|
120
|
+
'Authorization': `Bearer ${apiToken}`
|
|
121
|
+
},
|
|
122
|
+
body: JSON.stringify(opts)
|
|
117
123
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
const error = new Error('Bad response');
|
|
126
|
+
error.statusCode = response.status;
|
|
127
|
+
error.statusText = response.statusText;
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
const contentType = response.headers.get('content-type');
|
|
131
|
+
if (contentType && contentType.indexOf('application/json') !== -1) {
|
|
132
|
+
return response.json();
|
|
133
|
+
}
|
|
134
|
+
return response.text();
|
|
121
135
|
}
|
|
122
136
|
|
|
123
|
-
exports.doCreateCall = (baseUrl, accountSid, apiToken, opts) => {
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
exports.doCreateCall = async (node, baseUrl, accountSid, apiToken, opts) => {
|
|
138
|
+
node.log(`invoking create call with payload ${JSON.stringify(opts)}`);
|
|
139
|
+
const response = await fetch(`${baseUrl}/v1/Accounts/${accountSid}/Calls`, {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: {
|
|
142
|
+
'Content-Type': 'application/json',
|
|
143
|
+
'Authorization': `Bearer ${apiToken}`
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify(opts)
|
|
126
146
|
});
|
|
127
|
-
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
const error = new Error('Bad response');
|
|
149
|
+
error.statusCode = response.status;
|
|
150
|
+
error.statusText = response.statusText;
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
return response.json();
|
|
128
154
|
}
|
|
129
155
|
|
|
130
|
-
exports.doCreateMessage = (baseUrl, accountSid, apiToken, opts) => {
|
|
131
|
-
|
|
132
|
-
|
|
156
|
+
exports.doCreateMessage = async (node, baseUrl, accountSid, apiToken, opts) => {
|
|
157
|
+
node.log(`invoking create message with payload ${JSON.stringify(opts)}`);
|
|
158
|
+
const response = await fetch(`${baseUrl}/v1/Accounts/${accountSid}/Messages`, {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
headers: {
|
|
161
|
+
'Content-Type': 'application/json',
|
|
162
|
+
'Authorization': `Bearer ${apiToken}`
|
|
163
|
+
},
|
|
164
|
+
body: JSON.stringify(opts)
|
|
133
165
|
});
|
|
134
|
-
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
const error = new Error('Bad response');
|
|
168
|
+
error.statusCode = response.status;
|
|
169
|
+
error.statusText = response.statusText;
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
return response.json();
|
|
135
173
|
}
|
|
136
174
|
|