punchblock 0.5.1 → 0.6.0

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.
Files changed (64) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/LICENSE.txt +3 -1
  3. data/bin/punchblock-console +19 -1
  4. data/lib/punchblock.rb +11 -4
  5. data/lib/punchblock/command/reject.rb +6 -2
  6. data/lib/punchblock/component.rb +1 -0
  7. data/lib/punchblock/component/asterisk.rb +10 -0
  8. data/lib/punchblock/component/asterisk/agi.rb +11 -0
  9. data/lib/punchblock/component/asterisk/agi/command.rb +157 -0
  10. data/lib/punchblock/component/asterisk/ami.rb +12 -0
  11. data/lib/punchblock/component/asterisk/ami/action.rb +144 -0
  12. data/lib/punchblock/component/input.rb +2 -2
  13. data/lib/punchblock/connection.rb +1 -0
  14. data/lib/punchblock/connection/asterisk.rb +31 -0
  15. data/lib/punchblock/core_ext/celluloid.rb +11 -0
  16. data/lib/punchblock/event.rb +1 -1
  17. data/lib/punchblock/event/asterisk.rb +9 -0
  18. data/lib/punchblock/event/asterisk/ami.rb +11 -0
  19. data/lib/punchblock/event/asterisk/ami/event.rb +66 -0
  20. data/lib/punchblock/event/complete.rb +20 -0
  21. data/lib/punchblock/event/dtmf.rb +19 -0
  22. data/lib/punchblock/event/end.rb +23 -0
  23. data/lib/punchblock/event/offer.rb +23 -0
  24. data/lib/punchblock/header.rb +4 -44
  25. data/lib/punchblock/key_value_pair_node.rb +50 -0
  26. data/lib/punchblock/rayo_node.rb +1 -1
  27. data/lib/punchblock/ref.rb +6 -0
  28. data/lib/punchblock/translator.rb +7 -0
  29. data/lib/punchblock/translator/asterisk.rb +74 -0
  30. data/lib/punchblock/translator/asterisk/ami_action.rb +86 -0
  31. data/lib/punchblock/translator/asterisk/call.rb +25 -0
  32. data/lib/punchblock/translator/asterisk/component.rb +11 -0
  33. data/lib/punchblock/version.rb +1 -1
  34. data/punchblock.gemspec +3 -1
  35. data/spec/punchblock/command/accept_spec.rb +8 -0
  36. data/spec/punchblock/command/answer_spec.rb +8 -0
  37. data/spec/punchblock/command/dial_spec.rb +22 -2
  38. data/spec/punchblock/command/hangup_spec.rb +8 -0
  39. data/spec/punchblock/command/join_spec.rb +21 -0
  40. data/spec/punchblock/command/mute_spec.rb +8 -0
  41. data/spec/punchblock/command/redirect_spec.rb +21 -0
  42. data/spec/punchblock/command/reject_spec.rb +19 -8
  43. data/spec/punchblock/command/unjoin_spec.rb +17 -0
  44. data/spec/punchblock/command/unmute_spec.rb +8 -0
  45. data/spec/punchblock/component/asterisk/agi/command_spec.rb +102 -0
  46. data/spec/punchblock/component/asterisk/ami/action_spec.rb +118 -0
  47. data/spec/punchblock/component/input_spec.rb +40 -0
  48. data/spec/punchblock/component/output_spec.rb +28 -0
  49. data/spec/punchblock/component/record_spec.rb +27 -0
  50. data/spec/punchblock/connection/asterisk_spec.rb +69 -0
  51. data/spec/punchblock/event/asterisk/ami/event_spec.rb +60 -0
  52. data/spec/punchblock/event/complete_spec.rb +8 -0
  53. data/spec/punchblock/event/dtmf_spec.rb +8 -0
  54. data/spec/punchblock/event/end_spec.rb +8 -0
  55. data/spec/punchblock/event/offer_spec.rb +15 -2
  56. data/spec/punchblock/ref_spec.rb +6 -0
  57. data/spec/punchblock/translator/asterisk/ami_action_spec.rb +149 -0
  58. data/spec/punchblock/translator/asterisk/call_spec.rb +18 -0
  59. data/spec/punchblock/translator/asterisk/component_spec.rb +11 -0
  60. data/spec/punchblock/translator/asterisk_spec.rb +150 -0
  61. data/spec/spec_helper.rb +42 -0
  62. metadata +92 -42
  63. data/lib/punchblock/event/info.rb +0 -15
  64. 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
@@ -0,0 +1,11 @@
1
+ module Punchblock
2
+ module Translator
3
+ class Asterisk
4
+ class Component
5
+ include Celluloid
6
+
7
+ attr_reader :id
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Punchblock
2
- VERSION = "0.5.1"
2
+ VERSION = "0.6.0"
3
3
  end
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>, ["~> 2.3.0"]
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
- describe "when setting options in initializer" do
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