@jambonz/node-red-contrib-jambonz 2.4.11 → 2.4.13
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 +3 -2
- package/src/nodes/auth.html +1 -2
- package/src/nodes/conference.html +46 -23
- package/src/nodes/conference.js +7 -4
- package/src/nodes/create_call.html +75 -0
- package/src/nodes/create_call.js +14 -2
- package/src/nodes/get_call.html +57 -0
- package/src/nodes/get_call.js +45 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jambonz/node-red-contrib-jambonz",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.13",
|
|
4
4
|
"description": "Node-RED nodes for jambonz platform",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red"
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"create sms": "src/nodes/create_sms.js",
|
|
47
47
|
"lcc": "src/nodes/lcc.js",
|
|
48
48
|
"get_alerts": "src/nodes/get_alerts.js",
|
|
49
|
+
"get_call": "src/nodes/get_call.js",
|
|
49
50
|
"get_calls": "src/nodes/get_calls.js",
|
|
50
51
|
"get_recent_calls": "src/nodes/get_recent_calls.js"
|
|
51
52
|
}
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
"author": "Dave Horton",
|
|
54
55
|
"license": "MIT",
|
|
55
56
|
"dependencies": {
|
|
56
|
-
"aws-sdk": "^2.
|
|
57
|
+
"aws-sdk": "^2.1405.0",
|
|
57
58
|
"bent": "^7.3.12",
|
|
58
59
|
"body-parser": "^1.20.2",
|
|
59
60
|
"cookie-parser": "^1.4.6",
|
package/src/nodes/auth.html
CHANGED
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
$('#btn-test-credentials').on('click', testCredentials);
|
|
17
17
|
$('#node-config-input-url').typedInput({
|
|
18
18
|
types: ['str',
|
|
19
|
-
{value: 'https://api.jambonz.
|
|
20
|
-
{value: 'https://api.jambonz.xyz', label : 'jambonz.xyz', hasValue: false}],
|
|
19
|
+
{value: 'https://api.jambonz.cloud', label : 'jambonz.cloud', hasValue: false}],
|
|
21
20
|
typeField: $('#node-config-input-urlType')
|
|
22
21
|
});
|
|
23
22
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!-- Javascript -->
|
|
2
2
|
<script type="text/javascript">
|
|
3
|
-
|
|
3
|
+
var mustacheType = {
|
|
4
4
|
value: 'mustache',
|
|
5
5
|
label: 'mustache',
|
|
6
6
|
hasvalue: true,
|
|
@@ -16,13 +16,17 @@
|
|
|
16
16
|
conferenceType: {value: 'str'},
|
|
17
17
|
beep: {value: false},
|
|
18
18
|
endConferenceOnExit: {value: false},
|
|
19
|
-
startConferenceOnEnter: {value:
|
|
19
|
+
startConferenceOnEnter: {value: true},
|
|
20
20
|
maxParticipants: {},
|
|
21
21
|
maxParticipantsType: {value: 'num'},
|
|
22
22
|
enterHook: {},
|
|
23
23
|
enterHookType: {value: 'str'},
|
|
24
24
|
waitHook: {},
|
|
25
25
|
waitHookType: {value: 'str'},
|
|
26
|
+
actionHook: {},
|
|
27
|
+
actionHookType: {value: 'str'},
|
|
28
|
+
statusHook: {},
|
|
29
|
+
statusHookType: {value: 'str'},
|
|
26
30
|
joinMuted : {value: false}
|
|
27
31
|
},
|
|
28
32
|
inputs:1,
|
|
@@ -48,6 +52,14 @@
|
|
|
48
52
|
types: ['num', 'msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
49
53
|
typeField: $('#node-input-maxParticipantsType')
|
|
50
54
|
});
|
|
55
|
+
$('#node-input-actionHook').typedInput({
|
|
56
|
+
types: ['str', 'msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
57
|
+
typeField: $('#node-input-actionHookType')
|
|
58
|
+
});
|
|
59
|
+
$('#node-input-statusHook').typedInput({
|
|
60
|
+
types: ['str', 'msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
61
|
+
typeField: $('#node-input-statusHookType')
|
|
62
|
+
});
|
|
51
63
|
}
|
|
52
64
|
});
|
|
53
65
|
</script>
|
|
@@ -73,6 +85,16 @@
|
|
|
73
85
|
<input type="text" id="node-input-waitHook" placeholder="webhook url">
|
|
74
86
|
<input type="hidden" id="node-input-waitHookType">
|
|
75
87
|
</div>
|
|
88
|
+
<div class="form-row">
|
|
89
|
+
<label for="node-input-actionHook">Action hook</label>
|
|
90
|
+
<input type="text" id="node-input-actionHook" placeholder="webhook url">
|
|
91
|
+
<input type="hidden" id="node-input-actionHookType">
|
|
92
|
+
</div>
|
|
93
|
+
<div class="form-row">
|
|
94
|
+
<label for="node-input-statusHook">Status hook</label>
|
|
95
|
+
<input type="text" id="node-input-statusHook" placeholder="webhook url">
|
|
96
|
+
<input type="hidden" id="node-input-statusHookType">
|
|
97
|
+
</div>
|
|
76
98
|
<div class="form-row">
|
|
77
99
|
<label for="node-input-beep">Beep on entry</label>
|
|
78
100
|
<input type="checkbox" id="node-input-beep">
|
|
@@ -86,7 +108,7 @@
|
|
|
86
108
|
<input type="checkbox" id="node-input-endConferenceOnExit">
|
|
87
109
|
</div>
|
|
88
110
|
<div class="form-row">
|
|
89
|
-
<label for="node-input-joinMuted">Join
|
|
111
|
+
<label for="node-input-joinMuted">Join muted</label>
|
|
90
112
|
<input type="checkbox" id="node-input-joinMuted">
|
|
91
113
|
</div>
|
|
92
114
|
<div class="form-row">
|
|
@@ -94,30 +116,31 @@
|
|
|
94
116
|
<input type="text" id="node-input-maxParticipants">
|
|
95
117
|
<input type="hidden" id="node-input-maxParticipantsType">
|
|
96
118
|
</div>
|
|
97
|
-
|
|
119
|
+
</script>
|
|
98
120
|
|
|
99
121
|
<!-- Help Text -->
|
|
100
122
|
<script type="text/html" data-help-name="conference">
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<
|
|
112
|
-
<
|
|
113
|
-
<
|
|
114
|
-
</
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
<p>Places a caller in a conference.</p>
|
|
124
|
+
<h3>Properties</h3>
|
|
125
|
+
<p><code>Conference Name</code> - The name of the conference to join the caller to.</p>
|
|
126
|
+
<p><code>Enter hook</code> - A webhook to retrieve something to play or say to the caller just before they are put into a conference after waiting for it to start</p>
|
|
127
|
+
<p><code>Wait hook</code> - A webhook to retrieve commands to play or say while the caller is waiting for the conference to start</p>
|
|
128
|
+
<p><code>Action hook</code> - A webhook to call when the conference ends</p>
|
|
129
|
+
<p><code>Status hook</code> - A webhook to call with conference status events</p>
|
|
130
|
+
<p><code>Beep on entry</code> - If checked, play a beep tone to the conference when caller enters </p>
|
|
131
|
+
<p><code>Start on entry</code> - If checked, start the conference only when this caller enters</p>
|
|
132
|
+
<p><code>End on exit</code> - If checked, end the conference when this caller hangs up</p>
|
|
133
|
+
<p><code>Max participants</code> - Maximum number of participants that will be allowed in the conference</p>
|
|
134
|
+
<h3>Outputs</h3>
|
|
135
|
+
<dl class="message-properties">
|
|
136
|
+
<dt>jambonz<span class="property-type">object</span></dt>
|
|
137
|
+
<dd> <code>msg.jambonz</code> will contain any previous actions provided to the input with the new <code>conference</code> action appended </dd>
|
|
138
|
+
</dl>
|
|
139
|
+
<h3>Details</h3>
|
|
140
|
+
The conference verb places a call into a conference.
|
|
141
|
+
<h3>References</h3>
|
|
119
142
|
<ul>
|
|
120
|
-
|
|
143
|
+
<li><a href="https://www.jambonz.org/docs/webhooks/conference/">Jambonz conference reference</a></li>
|
|
121
144
|
</ul>
|
|
122
145
|
</script>
|
|
123
146
|
|
package/src/nodes/conference.js
CHANGED
|
@@ -6,17 +6,20 @@ module.exports = function(RED) {
|
|
|
6
6
|
RED.nodes.createNode(this, config);
|
|
7
7
|
var node = this;
|
|
8
8
|
node.on('input', function(msg) {
|
|
9
|
-
var
|
|
9
|
+
var statusHook = new_resolve(RED, config.statusHook, config.statusHookType, node, msg);
|
|
10
10
|
appendVerb(msg, {
|
|
11
11
|
verb: 'conference',
|
|
12
12
|
name: new_resolve(RED, config.conference, config.conferenceType, node, msg),
|
|
13
13
|
enterHook: new_resolve(RED, config.enterHook, config.enterHookType, node, msg),
|
|
14
14
|
waitHook: new_resolve(RED, config.waitHook, config.waitHookType, node, msg),
|
|
15
|
-
|
|
15
|
+
actionHook: new_resolve(RED, config.actionHook, config.actionHookType, node, msg),
|
|
16
|
+
statusHook,
|
|
17
|
+
...(statusHook && { statusEvents: ['start', 'end', 'join', 'leave'] }),
|
|
18
|
+
maxParticipants: new_resolve(RED, config.maxParticipants, config.maxParticipantsType, node, msg),
|
|
16
19
|
beep: config.beep,
|
|
17
20
|
startConferenceOnEnter: config.startConferenceOnEnter,
|
|
18
|
-
|
|
19
|
-
joinMuted
|
|
21
|
+
endConferenceOnExit: config.endConferenceOnExit,
|
|
22
|
+
joinMuted: config.joinMuted
|
|
20
23
|
});
|
|
21
24
|
node.send(msg);
|
|
22
25
|
});
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
application: {value: ''},
|
|
23
23
|
appName: {},
|
|
24
24
|
mode: {value: 'app'},
|
|
25
|
+
callername: {value: ''},
|
|
26
|
+
callernameType: {value: ''},
|
|
27
|
+
headers: {value: []},
|
|
25
28
|
call_hook_url : {},
|
|
26
29
|
call_hook_urlType : {value: 'str'},
|
|
27
30
|
call_hook_method : {value : 'GET'},
|
|
@@ -84,6 +87,10 @@
|
|
|
84
87
|
types: ['str','msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
85
88
|
typeField: $('#node-input-call_status_urlType')
|
|
86
89
|
});
|
|
90
|
+
$('#node-input-callername').typedInput({
|
|
91
|
+
types: ['str', 'msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
92
|
+
typeField: $('#node-input-callernameType')
|
|
93
|
+
});
|
|
87
94
|
|
|
88
95
|
var populateApplications = function() {
|
|
89
96
|
var serverId = $('#node-input-server option:selected').val();
|
|
@@ -127,6 +134,60 @@
|
|
|
127
134
|
|
|
128
135
|
}
|
|
129
136
|
});
|
|
137
|
+
$('#node-input-headers-container').css('min-height','120px').css('min-width','450px').editableList({
|
|
138
|
+
addItem: function(container, i, opt) {
|
|
139
|
+
var header = opt;
|
|
140
|
+
if (!header.hasOwnProperty('h')) {
|
|
141
|
+
header = {h: '', v: ''};
|
|
142
|
+
}
|
|
143
|
+
container.css({
|
|
144
|
+
overflow: 'hidden',
|
|
145
|
+
whiteSpace: 'nowrap'
|
|
146
|
+
});
|
|
147
|
+
let fragment = document.createDocumentFragment();
|
|
148
|
+
var row1 = $('<div/>',{style:"display:flex;"}).appendTo(fragment);
|
|
149
|
+
$('<input/>', {
|
|
150
|
+
class:"node-input-header-property-name",
|
|
151
|
+
type:"text",
|
|
152
|
+
placeholder: 'SIP Header'
|
|
153
|
+
})
|
|
154
|
+
.appendTo(row1);
|
|
155
|
+
$('<input/>', {
|
|
156
|
+
class:"node-input-value-property-name",
|
|
157
|
+
type:"text",
|
|
158
|
+
placeholder: 'value'
|
|
159
|
+
})
|
|
160
|
+
.appendTo(row1);
|
|
161
|
+
row1.find('.node-input-header-property-name').val(header.h);
|
|
162
|
+
row1.find('.node-input-value-property-name').val(header.v);
|
|
163
|
+
container[0].appendChild(fragment);
|
|
164
|
+
},
|
|
165
|
+
removable: true
|
|
166
|
+
});
|
|
167
|
+
if (!this.headers) {
|
|
168
|
+
var header = {
|
|
169
|
+
h: '',
|
|
170
|
+
v: '',
|
|
171
|
+
};
|
|
172
|
+
this.headers = [header];
|
|
173
|
+
}
|
|
174
|
+
for (var i=0; i < this.headers.length; i++) {
|
|
175
|
+
var header = this.headers[i];
|
|
176
|
+
$("#node-input-headers-container").editableList('addItem', header);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
oneditsave: function () {
|
|
180
|
+
var node = this;
|
|
181
|
+
var headers = [];
|
|
182
|
+
$("#node-input-headers-container").editableList('items').each(function(i) {
|
|
183
|
+
var header = $(this);
|
|
184
|
+
var h = header.find(".node-input-header-property-name").val();
|
|
185
|
+
var v = header.find(".node-input-value-property-name").val();
|
|
186
|
+
var obj = {};
|
|
187
|
+
obj[h] = v;
|
|
188
|
+
headers.push({h, v});
|
|
189
|
+
});
|
|
190
|
+
node.headers = headers;
|
|
130
191
|
}
|
|
131
192
|
});
|
|
132
193
|
</script>
|
|
@@ -146,6 +207,11 @@
|
|
|
146
207
|
<input type="text" id="node-input-from" placeholder="calling party number">
|
|
147
208
|
<input type="hidden" id="node-input-fromType">
|
|
148
209
|
</div>
|
|
210
|
+
<div class="form-row">
|
|
211
|
+
<label for="node-input-callername">From name</label>
|
|
212
|
+
<input type="text" id="node-input-callername" placeholder="calling party name">
|
|
213
|
+
<input type="hidden" id="node-input-callernameType">
|
|
214
|
+
</div>
|
|
149
215
|
<div class="form-row">
|
|
150
216
|
<label for="node-input-to">To</label>
|
|
151
217
|
<input type="text" id="node-input-to" placeholder="called party info">
|
|
@@ -179,6 +245,15 @@
|
|
|
179
245
|
</select>
|
|
180
246
|
</div>
|
|
181
247
|
</div>
|
|
248
|
+
<fieldset>
|
|
249
|
+
<legend>SIP Headers</legend>
|
|
250
|
+
<div class="form-row" style="margin-bottom:0;">
|
|
251
|
+
<label style="width:100%"><i class="fa fa-list"></i> <span>Add custom headers</span></label>
|
|
252
|
+
</div>
|
|
253
|
+
<div class="form-row node-input-headers-container-row">
|
|
254
|
+
<ol id="node-input-headers-container"></ol>
|
|
255
|
+
</div>
|
|
256
|
+
</fieldset>
|
|
182
257
|
<div id="url-options">
|
|
183
258
|
<fieldset>
|
|
184
259
|
<legend>Webhook options</legend>
|
package/src/nodes/create_call.js
CHANGED
|
@@ -19,8 +19,8 @@ module.exports = function(RED) {
|
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
var from = new_resolve(RED, config.from, config.fromType, node, msg)
|
|
23
|
-
var to =
|
|
22
|
+
var from = new_resolve(RED, config.from, config.fromType, node, msg);
|
|
23
|
+
var to = new_resolve(RED, config.to, config.toType, node, msg);
|
|
24
24
|
|
|
25
25
|
const opts = {
|
|
26
26
|
from,
|
|
@@ -29,6 +29,18 @@ module.exports = function(RED) {
|
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
if (config.headers) {
|
|
33
|
+
var headers = {};
|
|
34
|
+
config.headers.forEach(function(h) {
|
|
35
|
+
if (h.h.length && h.v.length) headers[h.h] = h.v;
|
|
36
|
+
});
|
|
37
|
+
Object.assign(opts, {headers});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (config.callername) {
|
|
41
|
+
opts.callerName = new_resolve(RED, config.callername, config.callernameType, node, msg);
|
|
42
|
+
}
|
|
43
|
+
|
|
32
44
|
switch (config.mode) {
|
|
33
45
|
case 'app':
|
|
34
46
|
opts.application_sid = config.application;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<!-- Javascript -->
|
|
2
|
+
<script type="text/javascript">
|
|
3
|
+
var mustacheType = {
|
|
4
|
+
value: 'mustache',
|
|
5
|
+
label: 'mustache',
|
|
6
|
+
hasvalue: true,
|
|
7
|
+
icon: 'resources/@jambonz/node-red-contrib-jambonz/icons/mustache.svg'
|
|
8
|
+
}
|
|
9
|
+
RED.nodes.registerType('get_call', {
|
|
10
|
+
category: 'jambonz',
|
|
11
|
+
color: '#aebfb9',
|
|
12
|
+
defaults: {
|
|
13
|
+
name: {value: ''},
|
|
14
|
+
server: {value: '', required: true, type: 'jambonz_auth'},
|
|
15
|
+
callSid: {value: '', validate: function(v) {
|
|
16
|
+
return v.length > 0;
|
|
17
|
+
}},
|
|
18
|
+
callSidType:{value: 'str'},
|
|
19
|
+
},
|
|
20
|
+
inputs: 1,
|
|
21
|
+
outputs: 1,
|
|
22
|
+
icon: "font-awesome/fa-cubes",
|
|
23
|
+
paletteLabel: "Get - Call",
|
|
24
|
+
label: function() { return this.name || 'Get Call';},
|
|
25
|
+
oneditprepare: () => {
|
|
26
|
+
$('#node-input-callSid').typedInput({
|
|
27
|
+
types: ['str','msg', 'flow', 'global', 'jsonata', 'env', mustacheType],
|
|
28
|
+
typeField: $('#node-input-callSidType')
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<!-- HTML -->
|
|
35
|
+
<script type="text/html" data-template-name="get_call">
|
|
36
|
+
<div class="form-row">
|
|
37
|
+
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
|
38
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
39
|
+
</div>
|
|
40
|
+
<div class="form-row">
|
|
41
|
+
<label for="node-input-server">Server</label>
|
|
42
|
+
<input type="text" id="node-input-server">
|
|
43
|
+
</div>
|
|
44
|
+
<div class="form-row">
|
|
45
|
+
<label for="node-input-callSid">CallSid</label>
|
|
46
|
+
<input type="text" id="node-input-callSid">
|
|
47
|
+
<input type="hidden" id="node-input-callSidType">
|
|
48
|
+
</div>
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<!-- Help Text -->
|
|
52
|
+
<script type="text/html" data-help-name="get_call">
|
|
53
|
+
<p>Get call</p>
|
|
54
|
+
|
|
55
|
+
<h3>Details</h3>
|
|
56
|
+
Retrieve info for a single call under an account.
|
|
57
|
+
</script>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const bent = require("bent");
|
|
2
|
+
var { new_resolve} = require("./libs");
|
|
3
|
+
|
|
4
|
+
module.exports = function (RED) {
|
|
5
|
+
function get_call(config) {
|
|
6
|
+
RED.nodes.createNode(this, config);
|
|
7
|
+
var node = this;
|
|
8
|
+
const server = RED.nodes.getNode(config.server);
|
|
9
|
+
const { accountSid, apiToken } = server.credentials;
|
|
10
|
+
node.on("input", async (msg, send, done) => {
|
|
11
|
+
const callSid = new_resolve(RED, config.callSid, config.callSidType, node, msg);
|
|
12
|
+
if (!callSid) {
|
|
13
|
+
if (done) done(new Error('CallSid empty'));
|
|
14
|
+
else node.error(new Error('CallSid empty'), msg);
|
|
15
|
+
return;
|
|
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
|
+
try {
|
|
26
|
+
const res = await req();
|
|
27
|
+
msg.payload = res;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (err.statusCode) {
|
|
30
|
+
node.error(`GetCall failed with ${err.statusCode}`);
|
|
31
|
+
msg.statusCode = err.statusCode;
|
|
32
|
+
} else {
|
|
33
|
+
node.error(`Error getting call info ${JSON.stringify(err)}`);
|
|
34
|
+
if (done) done(err);
|
|
35
|
+
else node.error(err, msg);
|
|
36
|
+
send(msg);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
send(msg);
|
|
41
|
+
if (done) done();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
RED.nodes.registerType("get_call", get_call);
|
|
45
|
+
};
|