punchblock 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +3 -1
- data/bin/punchblock-console +19 -1
- data/lib/punchblock.rb +11 -4
- data/lib/punchblock/command/reject.rb +6 -2
- data/lib/punchblock/component.rb +1 -0
- data/lib/punchblock/component/asterisk.rb +10 -0
- data/lib/punchblock/component/asterisk/agi.rb +11 -0
- data/lib/punchblock/component/asterisk/agi/command.rb +157 -0
- data/lib/punchblock/component/asterisk/ami.rb +12 -0
- data/lib/punchblock/component/asterisk/ami/action.rb +144 -0
- data/lib/punchblock/component/input.rb +2 -2
- data/lib/punchblock/connection.rb +1 -0
- data/lib/punchblock/connection/asterisk.rb +31 -0
- data/lib/punchblock/core_ext/celluloid.rb +11 -0
- data/lib/punchblock/event.rb +1 -1
- data/lib/punchblock/event/asterisk.rb +9 -0
- data/lib/punchblock/event/asterisk/ami.rb +11 -0
- data/lib/punchblock/event/asterisk/ami/event.rb +66 -0
- data/lib/punchblock/event/complete.rb +20 -0
- data/lib/punchblock/event/dtmf.rb +19 -0
- data/lib/punchblock/event/end.rb +23 -0
- data/lib/punchblock/event/offer.rb +23 -0
- data/lib/punchblock/header.rb +4 -44
- data/lib/punchblock/key_value_pair_node.rb +50 -0
- data/lib/punchblock/rayo_node.rb +1 -1
- data/lib/punchblock/ref.rb +6 -0
- data/lib/punchblock/translator.rb +7 -0
- data/lib/punchblock/translator/asterisk.rb +74 -0
- data/lib/punchblock/translator/asterisk/ami_action.rb +86 -0
- data/lib/punchblock/translator/asterisk/call.rb +25 -0
- data/lib/punchblock/translator/asterisk/component.rb +11 -0
- data/lib/punchblock/version.rb +1 -1
- data/punchblock.gemspec +3 -1
- data/spec/punchblock/command/accept_spec.rb +8 -0
- data/spec/punchblock/command/answer_spec.rb +8 -0
- data/spec/punchblock/command/dial_spec.rb +22 -2
- data/spec/punchblock/command/hangup_spec.rb +8 -0
- data/spec/punchblock/command/join_spec.rb +21 -0
- data/spec/punchblock/command/mute_spec.rb +8 -0
- data/spec/punchblock/command/redirect_spec.rb +21 -0
- data/spec/punchblock/command/reject_spec.rb +19 -8
- data/spec/punchblock/command/unjoin_spec.rb +17 -0
- data/spec/punchblock/command/unmute_spec.rb +8 -0
- data/spec/punchblock/component/asterisk/agi/command_spec.rb +102 -0
- data/spec/punchblock/component/asterisk/ami/action_spec.rb +118 -0
- data/spec/punchblock/component/input_spec.rb +40 -0
- data/spec/punchblock/component/output_spec.rb +28 -0
- data/spec/punchblock/component/record_spec.rb +27 -0
- data/spec/punchblock/connection/asterisk_spec.rb +69 -0
- data/spec/punchblock/event/asterisk/ami/event_spec.rb +60 -0
- data/spec/punchblock/event/complete_spec.rb +8 -0
- data/spec/punchblock/event/dtmf_spec.rb +8 -0
- data/spec/punchblock/event/end_spec.rb +8 -0
- data/spec/punchblock/event/offer_spec.rb +15 -2
- data/spec/punchblock/ref_spec.rb +6 -0
- data/spec/punchblock/translator/asterisk/ami_action_spec.rb +149 -0
- data/spec/punchblock/translator/asterisk/call_spec.rb +18 -0
- data/spec/punchblock/translator/asterisk/component_spec.rb +11 -0
- data/spec/punchblock/translator/asterisk_spec.rb +150 -0
- data/spec/spec_helper.rb +42 -0
- metadata +92 -42
- data/lib/punchblock/event/info.rb +0 -15
- data/spec/punchblock/event/info_spec.rb +0 -30
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
require 'punchblock/core_ext/celluloid'
|
3
|
+
require 'ruby_ami'
|
4
|
+
|
5
|
+
module Punchblock
|
6
|
+
module Translator
|
7
|
+
class Asterisk
|
8
|
+
include Celluloid
|
9
|
+
|
10
|
+
extend ActiveSupport::Autoload
|
11
|
+
|
12
|
+
autoload :AMIAction
|
13
|
+
autoload :Call
|
14
|
+
autoload :Component
|
15
|
+
|
16
|
+
attr_reader :ami_client, :connection
|
17
|
+
|
18
|
+
def initialize(ami_client, connection)
|
19
|
+
@ami_client, @connection = ami_client, connection
|
20
|
+
@calls, @components = {}, {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def register_call(call)
|
24
|
+
@calls[call.id] ||= call
|
25
|
+
end
|
26
|
+
|
27
|
+
def call_with_id(call_id)
|
28
|
+
@calls[call_id]
|
29
|
+
end
|
30
|
+
|
31
|
+
def register_component(component)
|
32
|
+
@components[component.id] ||= component
|
33
|
+
end
|
34
|
+
|
35
|
+
def component_with_id(component_id)
|
36
|
+
@components[component_id]
|
37
|
+
end
|
38
|
+
|
39
|
+
def handle_ami_event(event)
|
40
|
+
return unless event.is_a? RubyAMI::Event
|
41
|
+
connection.handle_event Event::Asterisk::AMI::Event.new(:name => event.name, :attributes => event.headers)
|
42
|
+
end
|
43
|
+
|
44
|
+
def execute_command(command, options = {})
|
45
|
+
command.request!
|
46
|
+
if command.call_id || options[:call_id]
|
47
|
+
command.call_id ||= options[:call_id]
|
48
|
+
if command.component_id || options[:component_id]
|
49
|
+
command.component_id ||= options[:component_id]
|
50
|
+
execute_component_command command
|
51
|
+
else
|
52
|
+
execute_call_command command
|
53
|
+
end
|
54
|
+
else
|
55
|
+
execute_global_command command
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def execute_call_command(command)
|
60
|
+
call_with_id(command.call_id).execute_command command
|
61
|
+
end
|
62
|
+
|
63
|
+
def execute_component_command(command)
|
64
|
+
call_with_id(command.call_id).execute_component_command command
|
65
|
+
end
|
66
|
+
|
67
|
+
def execute_global_command(command)
|
68
|
+
component = AMIAction.new command, ami_client
|
69
|
+
# register_component component
|
70
|
+
component.execute!
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Punchblock
|
2
|
+
module Translator
|
3
|
+
class Asterisk
|
4
|
+
class AMIAction < Component
|
5
|
+
attr_reader :action
|
6
|
+
|
7
|
+
def initialize(component_node, ami_client)
|
8
|
+
@component_node, @ami_client = component_node, ami_client
|
9
|
+
@action = create_action
|
10
|
+
@id = @action.action_id
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute
|
14
|
+
send_action
|
15
|
+
send_ref
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def create_action
|
21
|
+
headers = {}
|
22
|
+
@component_node.params_hash.each_pair do |key, value|
|
23
|
+
headers[key.to_s.capitalize] = value
|
24
|
+
end
|
25
|
+
RubyAMI::Action.new @component_node.name, headers do |response|
|
26
|
+
handle_response response
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def send_action
|
31
|
+
@ami_client.send_action @action
|
32
|
+
end
|
33
|
+
|
34
|
+
def send_ref
|
35
|
+
@component_node.response = Ref.new :id => @action.action_id
|
36
|
+
end
|
37
|
+
|
38
|
+
def handle_response(response)
|
39
|
+
case response
|
40
|
+
when RubyAMI::Error
|
41
|
+
send_event error_event(response)
|
42
|
+
when RubyAMI::Response
|
43
|
+
send_events
|
44
|
+
send_event complete_event(response)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def error_event(response)
|
49
|
+
Punchblock::Event::Complete.new.tap do |c|
|
50
|
+
c.reason = Punchblock::Event::Complete::Error.new :details => response.message
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def complete_event(response)
|
55
|
+
headers = response.headers
|
56
|
+
headers.merge! @extra_complete_attributes if @extra_complete_attributes
|
57
|
+
headers.delete 'ActionID'
|
58
|
+
Punchblock::Event::Complete.new.tap do |c|
|
59
|
+
c.reason = Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new :message => headers.delete('Message'), :attributes => headers
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def send_events
|
64
|
+
return unless @action.has_causal_events?
|
65
|
+
@action.events.each do |e|
|
66
|
+
if e.name.downcase == @action.causal_event_terminator_name
|
67
|
+
@extra_complete_attributes = e.headers
|
68
|
+
else
|
69
|
+
send_event pb_event_from_ami_event(e)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def pb_event_from_ami_event(ami_event)
|
75
|
+
headers = ami_event.headers
|
76
|
+
headers.delete 'ActionID'
|
77
|
+
Event::Asterisk::AMI::Event.new :name => ami_event.name, :attributes => headers
|
78
|
+
end
|
79
|
+
|
80
|
+
def send_event(event)
|
81
|
+
@component_node.add_event event
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Punchblock
|
2
|
+
module Translator
|
3
|
+
class Asterisk
|
4
|
+
class Call
|
5
|
+
include Celluloid
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@components = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register_component(component)
|
12
|
+
@components[component.id] ||= component
|
13
|
+
end
|
14
|
+
|
15
|
+
def component_with_id(component_id)
|
16
|
+
@components[component_id]
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute_component_command(command)
|
20
|
+
component_with_id(command.component_id).execute_command command
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/punchblock/version.rb
CHANGED
data/punchblock.gemspec
CHANGED
@@ -29,9 +29,11 @@ Gem::Specification.new do |s|
|
|
29
29
|
s.add_runtime_dependency %q<state_machine>, [">= 1.0.1"]
|
30
30
|
s.add_runtime_dependency %q<future-resource>, [">= 0.0.2"]
|
31
31
|
s.add_runtime_dependency %q<has-guarded-handlers>, [">= 0.1.0"]
|
32
|
+
s.add_runtime_dependency %q<celluloid>, [">= 0.5.0"]
|
33
|
+
s.add_runtime_dependency %q<ruby_ami>, [">= 0.1.2"]
|
32
34
|
|
33
35
|
s.add_development_dependency %q<bundler>, ["~> 1.0.0"]
|
34
|
-
s.add_development_dependency %q<rspec>, ["
|
36
|
+
s.add_development_dependency %q<rspec>, [">= 2.5.0"]
|
35
37
|
s.add_development_dependency %q<ci_reporter>, [">= 1.6.3"]
|
36
38
|
s.add_development_dependency %q<yard>, ["~> 0.6.0"]
|
37
39
|
s.add_development_dependency %q<rcov>, [">= 0"]
|
@@ -8,6 +8,14 @@ module Punchblock
|
|
8
8
|
end
|
9
9
|
|
10
10
|
it_should_behave_like 'command_headers'
|
11
|
+
|
12
|
+
describe "from a stanza" do
|
13
|
+
let(:stanza) { '<accept xmlns="urn:xmpp:rayo:1"/>' }
|
14
|
+
|
15
|
+
subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
|
16
|
+
|
17
|
+
it { should be_instance_of Accept }
|
18
|
+
end
|
11
19
|
end
|
12
20
|
end
|
13
21
|
end # Punchblock
|
@@ -8,6 +8,14 @@ module Punchblock
|
|
8
8
|
end
|
9
9
|
|
10
10
|
it_should_behave_like 'command_headers'
|
11
|
+
|
12
|
+
describe "from a stanza" do
|
13
|
+
let(:stanza) { '<answer xmlns="urn:xmpp:rayo:1"/>' }
|
14
|
+
|
15
|
+
subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
|
16
|
+
|
17
|
+
it { should be_instance_of Answer }
|
18
|
+
end
|
11
19
|
end
|
12
20
|
end
|
13
21
|
end # Punchblock
|
@@ -14,9 +14,9 @@ module Punchblock
|
|
14
14
|
RayoNode.class_from_registration(:dial, 'urn:xmpp:rayo:1').should == Dial
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
let(:join_params) { {:other_call_id => 'abc123'} }
|
17
|
+
let(:join_params) { {:other_call_id => 'abc123'} }
|
19
18
|
|
19
|
+
describe "when setting options in initializer" do
|
20
20
|
subject { Dial.new :to => 'tel:+14155551212', :from => 'tel:+13035551212', :headers => { :x_skill => 'agent', :x_customer_id => 8877 }, :join => join_params }
|
21
21
|
|
22
22
|
it_should_behave_like 'command_headers'
|
@@ -26,6 +26,26 @@ module Punchblock
|
|
26
26
|
its(:join) { should == Join.new(join_params) }
|
27
27
|
end
|
28
28
|
|
29
|
+
describe "from a stanza" do
|
30
|
+
let :stanza do
|
31
|
+
<<-MESSAGE
|
32
|
+
<dial to='tel:+14155551212' from='tel:+13035551212' xmlns='urn:xmpp:rayo:1'>
|
33
|
+
<join call-id="abc123" />
|
34
|
+
<header name="x-skill" value="agent" />
|
35
|
+
<header name="x-customer-id" value="8877" />
|
36
|
+
</dial>
|
37
|
+
MESSAGE
|
38
|
+
end
|
39
|
+
|
40
|
+
subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
|
41
|
+
|
42
|
+
it { should be_instance_of Dial }
|
43
|
+
|
44
|
+
its(:to) { should == 'tel:+14155551212' }
|
45
|
+
its(:from) { should == 'tel:+13035551212' }
|
46
|
+
its(:join) { should == Join.new(join_params) }
|
47
|
+
end
|
48
|
+
|
29
49
|
describe "#response=" do
|
30
50
|
before { subject.request! }
|
31
51
|
|
@@ -8,6 +8,14 @@ module Punchblock
|
|
8
8
|
end
|
9
9
|
|
10
10
|
it_should_behave_like 'command_headers'
|
11
|
+
|
12
|
+
describe "from a stanza" do
|
13
|
+
let(:stanza) { '<hangup xmlns="urn:xmpp:rayo:1"/>' }
|
14
|
+
|
15
|
+
subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
|
16
|
+
|
17
|
+
it { should be_instance_of Hangup }
|
18
|
+
end
|
11
19
|
end
|
12
20
|
end
|
13
21
|
end # Punchblock
|
@@ -16,6 +16,27 @@ module Punchblock
|
|
16
16
|
its(:direction) { should == :duplex }
|
17
17
|
its(:media) { should == :bridge }
|
18
18
|
end
|
19
|
+
|
20
|
+
describe "from a stanza" do
|
21
|
+
let :stanza do
|
22
|
+
<<-MESSAGE
|
23
|
+
<join xmlns="urn:xmpp:rayo:1"
|
24
|
+
call-id="abc123"
|
25
|
+
mixer-id="blah"
|
26
|
+
direction="duplex"
|
27
|
+
media="bridge" />
|
28
|
+
MESSAGE
|
29
|
+
end
|
30
|
+
|
31
|
+
subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
|
32
|
+
|
33
|
+
it { should be_instance_of Join }
|
34
|
+
|
35
|
+
its(:other_call_id) { should == 'abc123' }
|
36
|
+
its(:mixer_id) { should == 'blah' }
|
37
|
+
its(:direction) { should == :duplex }
|
38
|
+
its(:media) { should == :bridge }
|
39
|
+
end
|
19
40
|
end
|
20
41
|
end
|
21
42
|
end # Punchblock
|
@@ -6,6 +6,14 @@ module Punchblock
|
|
6
6
|
it 'registers itself' do
|
7
7
|
RayoNode.class_from_registration(:mute, 'urn:xmpp:rayo:1').should == Mute
|
8
8
|
end
|
9
|
+
|
10
|
+
describe "from a stanza" do
|
11
|
+
let(:stanza) { '<mute xmlns="urn:xmpp:rayo:1"/>' }
|
12
|
+
|
13
|
+
subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
|
14
|
+
|
15
|
+
it { should be_instance_of Mute }
|
16
|
+
end
|
9
17
|
end
|
10
18
|
end
|
11
19
|
end # Punchblock
|
@@ -14,6 +14,27 @@ module Punchblock
|
|
14
14
|
|
15
15
|
its(:to) { should == 'tel:+14045551234' }
|
16
16
|
end
|
17
|
+
|
18
|
+
describe "from a stanza" do
|
19
|
+
let :stanza do
|
20
|
+
<<-MESSAGE
|
21
|
+
<redirect xmlns='urn:xmpp:rayo:1'
|
22
|
+
to='tel:+14045551234'>
|
23
|
+
<!-- Signaling (e.g. SIP) Headers -->
|
24
|
+
<header name="x-skill" value="agent" />
|
25
|
+
<header name="x-customer-id" value="8877" />
|
26
|
+
</redirect>
|
27
|
+
MESSAGE
|
28
|
+
end
|
29
|
+
|
30
|
+
subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
|
31
|
+
|
32
|
+
it { should be_instance_of Redirect }
|
33
|
+
|
34
|
+
it_should_behave_like 'command_headers'
|
35
|
+
|
36
|
+
its(:to) { should == 'tel:+14045551234' }
|
37
|
+
end
|
17
38
|
end # Redirect
|
18
39
|
end # Command
|
19
40
|
end # Punchblock
|
@@ -15,19 +15,30 @@ module Punchblock
|
|
15
15
|
its(:reason) { should == :busy }
|
16
16
|
end
|
17
17
|
|
18
|
+
describe "from a stanza" do
|
19
|
+
let :stanza do
|
20
|
+
<<-MESSAGE
|
21
|
+
<reject xmlns='urn:xmpp:rayo:1'>
|
22
|
+
<busy />
|
23
|
+
<!-- Sample Headers (optional) -->
|
24
|
+
<header name="x-reason-internal" value="bad-skill" />
|
25
|
+
</reject>
|
26
|
+
MESSAGE
|
27
|
+
end
|
28
|
+
|
29
|
+
subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
|
30
|
+
|
31
|
+
it { should be_instance_of Reject }
|
32
|
+
|
33
|
+
its(:reason) { should == :busy }
|
34
|
+
its(:headers_hash) { should == { :x_reason_internal => 'bad-skill' } }
|
35
|
+
end
|
36
|
+
|
18
37
|
describe "with the reason" do
|
19
38
|
[:decline, :busy, :error].each do |reason|
|
20
39
|
describe reason do
|
21
40
|
subject { Reject.new :reason => reason }
|
22
41
|
|
23
|
-
let :expected_message do
|
24
|
-
<<-MESSAGE
|
25
|
-
<reject xmlns="urn:xmpp:rayo:1">
|
26
|
-
<#{reason}/>
|
27
|
-
</reject>
|
28
|
-
MESSAGE
|
29
|
-
end
|
30
|
-
|
31
42
|
its(:reason) { should == reason }
|
32
43
|
end
|
33
44
|
end
|
@@ -14,6 +14,23 @@ module Punchblock
|
|
14
14
|
its(:other_call_id) { should == 'abc123' }
|
15
15
|
its(:mixer_id) { should == 'blah' }
|
16
16
|
end
|
17
|
+
|
18
|
+
describe "from a stanza" do
|
19
|
+
let :stanza do
|
20
|
+
<<-MESSAGE
|
21
|
+
<unjoin xmlns="urn:xmpp:rayo:1"
|
22
|
+
call-id="abc123"
|
23
|
+
mixer-id="blah" />
|
24
|
+
MESSAGE
|
25
|
+
end
|
26
|
+
|
27
|
+
subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
|
28
|
+
|
29
|
+
it { should be_instance_of Unjoin }
|
30
|
+
|
31
|
+
its(:other_call_id) { should == 'abc123' }
|
32
|
+
its(:mixer_id) { should == 'blah' }
|
33
|
+
end
|
17
34
|
end
|
18
35
|
end
|
19
36
|
end # Punchblock
|