punchblock 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +31 -0
- data/Rakefile +23 -0
- data/assets/ozone/ask-1.0.xsd +56 -0
- data/assets/ozone/conference-1.0.xsd +17 -0
- data/assets/ozone/ozone-1.0.xsd +127 -0
- data/assets/ozone/say-1.0.xsd +24 -0
- data/assets/ozone/transfer-1.0.xsd +32 -0
- data/bin/punchblock-console +125 -0
- data/lib/punchblock/command/accept.rb +30 -0
- data/lib/punchblock/command/answer.rb +30 -0
- data/lib/punchblock/command/dial.rb +88 -0
- data/lib/punchblock/command/hangup.rb +25 -0
- data/lib/punchblock/command/join.rb +81 -0
- data/lib/punchblock/command/mute.rb +7 -0
- data/lib/punchblock/command/redirect.rb +49 -0
- data/lib/punchblock/command/reject.rb +61 -0
- data/lib/punchblock/command/unjoin.rb +50 -0
- data/lib/punchblock/command/unmute.rb +7 -0
- data/lib/punchblock/command.rb +16 -0
- data/lib/punchblock/command_node.rb +46 -0
- data/lib/punchblock/component/input.rb +320 -0
- data/lib/punchblock/component/output.rb +449 -0
- data/lib/punchblock/component/record.rb +216 -0
- data/lib/punchblock/component/tropo/ask.rb +197 -0
- data/lib/punchblock/component/tropo/conference.rb +328 -0
- data/lib/punchblock/component/tropo/say.rb +113 -0
- data/lib/punchblock/component/tropo/transfer.rb +178 -0
- data/lib/punchblock/component/tropo.rb +12 -0
- data/lib/punchblock/component.rb +73 -0
- data/lib/punchblock/connection.rb +209 -0
- data/lib/punchblock/core_ext/blather/stanza/presence.rb +11 -0
- data/lib/punchblock/core_ext/blather/stanza.rb +26 -0
- data/lib/punchblock/dsl.rb +46 -0
- data/lib/punchblock/event/answered.rb +7 -0
- data/lib/punchblock/event/complete.rb +65 -0
- data/lib/punchblock/event/dtmf.rb +19 -0
- data/lib/punchblock/event/end.rb +15 -0
- data/lib/punchblock/event/info.rb +15 -0
- data/lib/punchblock/event/joined.rb +50 -0
- data/lib/punchblock/event/offer.rb +29 -0
- data/lib/punchblock/event/ringing.rb +7 -0
- data/lib/punchblock/event/unjoined.rb +50 -0
- data/lib/punchblock/event.rb +16 -0
- data/lib/punchblock/generic_connection.rb +18 -0
- data/lib/punchblock/has_headers.rb +34 -0
- data/lib/punchblock/header.rb +47 -0
- data/lib/punchblock/media_container.rb +39 -0
- data/lib/punchblock/media_node.rb +17 -0
- data/lib/punchblock/protocol_error.rb +16 -0
- data/lib/punchblock/rayo_node.rb +88 -0
- data/lib/punchblock/ref.rb +26 -0
- data/lib/punchblock/version.rb +3 -0
- data/lib/punchblock.rb +42 -0
- data/log/.gitkeep +0 -0
- data/punchblock.gemspec +42 -0
- data/spec/punchblock/command/accept_spec.rb +13 -0
- data/spec/punchblock/command/answer_spec.rb +13 -0
- data/spec/punchblock/command/dial_spec.rb +54 -0
- data/spec/punchblock/command/hangup_spec.rb +13 -0
- data/spec/punchblock/command/join_spec.rb +21 -0
- data/spec/punchblock/command/mute_spec.rb +11 -0
- data/spec/punchblock/command/redirect_spec.rb +19 -0
- data/spec/punchblock/command/reject_spec.rb +43 -0
- data/spec/punchblock/command/unjoin_spec.rb +19 -0
- data/spec/punchblock/command/unmute_spec.rb +11 -0
- data/spec/punchblock/command_node_spec.rb +80 -0
- data/spec/punchblock/component/input_spec.rb +188 -0
- data/spec/punchblock/component/output_spec.rb +531 -0
- data/spec/punchblock/component/record_spec.rb +235 -0
- data/spec/punchblock/component/tropo/ask_spec.rb +183 -0
- data/spec/punchblock/component/tropo/conference_spec.rb +360 -0
- data/spec/punchblock/component/tropo/say_spec.rb +171 -0
- data/spec/punchblock/component/tropo/transfer_spec.rb +153 -0
- data/spec/punchblock/component_spec.rb +126 -0
- data/spec/punchblock/connection_spec.rb +194 -0
- data/spec/punchblock/event/answered_spec.rb +23 -0
- data/spec/punchblock/event/complete_spec.rb +80 -0
- data/spec/punchblock/event/dtmf_spec.rb +24 -0
- data/spec/punchblock/event/end_spec.rb +30 -0
- data/spec/punchblock/event/info_spec.rb +30 -0
- data/spec/punchblock/event/joined_spec.rb +32 -0
- data/spec/punchblock/event/offer_spec.rb +35 -0
- data/spec/punchblock/event/ringing_spec.rb +23 -0
- data/spec/punchblock/event/unjoined_spec.rb +32 -0
- data/spec/punchblock/header_spec.rb +44 -0
- data/spec/punchblock/protocol_error_spec.rb +9 -0
- data/spec/punchblock/ref_spec.rb +21 -0
- data/spec/spec_helper.rb +43 -0
- metadata +353 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
module Punchblock
|
2
|
+
module Component
|
3
|
+
module Tropo
|
4
|
+
class Transfer < ComponentNode
|
5
|
+
register :transfer, :transfer
|
6
|
+
|
7
|
+
include HasHeaders
|
8
|
+
|
9
|
+
##
|
10
|
+
# Creates an Rayo transfer command
|
11
|
+
#
|
12
|
+
# @param [Hash] options for transferring a call
|
13
|
+
# @option options [String, Array[String]] :to The destination(s) for the call transfer (ie - tel:+14155551212 or sip:you@sip.tropo.com). Can be an array to hunt.
|
14
|
+
# @option options [String] :from The caller ID for the call transfer (ie - tel:+14155551212 or sip:you@sip.tropo.com)
|
15
|
+
# @option options [String, Optional] :terminator The string key press required to abort the transfer.
|
16
|
+
# @option options [Integer, Optional] :timeout How long to wait - in seconds - for an answer, busy signal, or other event to occur.
|
17
|
+
# @option options [Boolean, Optional] :answer_on_media If set to true, the call will be considered "answered" and audio will begin playing as soon as media is received from the far end (ringing / busy signal / etc)
|
18
|
+
# @option options [Symbol, Optional] :media Rules for handling media. Can be :direct, where parties negotiate media directly, or :bridge where the media server will bridge audio, allowing media features like recording and ASR.
|
19
|
+
# @option options [Ring, Hash, Optional] :ring to play to the caller untill connected
|
20
|
+
#
|
21
|
+
# @return [Message::Transfer] an Rayo "transfer" message
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# Transfer.new(:to => 'sip:you@yourdomain.com', :from => 'sip:myapp@mydomain.com', :terminator => '#').to_xml
|
25
|
+
#
|
26
|
+
# returns:
|
27
|
+
# <transfer xmlns="urn:xmpp:tropo:transfer:1" from="sip:myapp@mydomain.com" terminator="#">
|
28
|
+
# <to>sip:you@yourdomain.com</to>
|
29
|
+
# </transfer>
|
30
|
+
#
|
31
|
+
def self.new(options = {})
|
32
|
+
super().tap do |new_node|
|
33
|
+
options.each_pair { |k,v| new_node.send :"#{k}=", v }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# @return [Array[String]] The destination(s) for the call transfer
|
39
|
+
#
|
40
|
+
def to
|
41
|
+
find('ns:to', :ns => self.class.registered_ns).map &:text
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# @param [String, Array[String]] :to The destination(s) for the call transfer (ie - tel:+14155551212 or sip:you@sip.tropo.com). Can be an array to hunt.
|
46
|
+
#
|
47
|
+
def to=(transfer_to)
|
48
|
+
find('//ns:to', :ns => self.class.registered_ns).each &:remove
|
49
|
+
if transfer_to
|
50
|
+
[transfer_to].flatten.each do |i|
|
51
|
+
to = RayoNode.new :to
|
52
|
+
to << i
|
53
|
+
self << to
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# @return [String] The caller ID for the call transfer
|
60
|
+
#
|
61
|
+
def from
|
62
|
+
read_attr :from
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# @param [String, Array[String]] :to The destination(s) for the call transfer (ie - tel:+14155551212 or sip:you@sip.tropo.com). Can be an array to hunt.
|
67
|
+
#
|
68
|
+
def from=(transfer_from)
|
69
|
+
write_attr :from, transfer_from
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# @return [String] The string key press required to abort the transfer.
|
74
|
+
#
|
75
|
+
def terminator
|
76
|
+
read_attr :terminator
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# @param [String] terminator The string key press required to abort the transfer.
|
81
|
+
#
|
82
|
+
def terminator=(terminator)
|
83
|
+
write_attr :terminator, terminator
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# @return [Integer] How long to wait - in seconds - for an answer, busy signal, or other event to occur.
|
88
|
+
#
|
89
|
+
def timeout
|
90
|
+
read_attr :timeout, :to_i
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# @param [Integer] timeout How long to wait - in seconds - for an answer, busy signal, or other event to occur.
|
95
|
+
#
|
96
|
+
def timeout=(timeout)
|
97
|
+
write_attr :timeout, timeout
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# @return [Boolean] If true, the call will be considered "answered" and audio will begin playing as soon as media is received from the far end (ringing / busy signal / etc)
|
102
|
+
#
|
103
|
+
def answer_on_media
|
104
|
+
read_attr('answer-on-media') == 'true'
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# @param [Boolean] aom If set to true, the call will be considered "answered" and audio will begin playing as soon as media is received from the far end (ringing / busy signal / etc)
|
109
|
+
#
|
110
|
+
def answer_on_media=(aom)
|
111
|
+
write_attr 'answer-on-media', aom.to_s
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# @return [Symbol] Rules for handling media. Can be :direct, where parties negotiate media directly, or :bridge where the media server will bridge audio, allowing media features like recording and ASR.
|
116
|
+
#
|
117
|
+
def media
|
118
|
+
read_attr 'media', :to_sym
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# @param [Symbol] media Rules for handling media. Can be :direct, where parties negotiate media directly, or :bridge where the media server will bridge audio, allowing media features like recording and ASR.
|
123
|
+
#
|
124
|
+
def media=(media)
|
125
|
+
write_attr 'media', media
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# @return [Ring] the ringer to play to the caller while transferring
|
130
|
+
#
|
131
|
+
def ring
|
132
|
+
node = find_first '//ns:ring', :ns => self.registered_ns
|
133
|
+
Ring.new node if node
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# @param [Hash] ring
|
138
|
+
# @option ring [String] :text Text to speak to the caller as an announcement
|
139
|
+
# @option ring [String] :url URL to play to the caller as an announcement
|
140
|
+
#
|
141
|
+
def ring=(ring)
|
142
|
+
ring = Ring.new(ring) unless ring.is_a?(Ring)
|
143
|
+
self << ring
|
144
|
+
end
|
145
|
+
|
146
|
+
class Ring < Say
|
147
|
+
register :ring, :transfer
|
148
|
+
end
|
149
|
+
|
150
|
+
def inspect_attributes # :nodoc:
|
151
|
+
[:to, :from, :terminator, :timeout, :answer_on_media] + super
|
152
|
+
end
|
153
|
+
|
154
|
+
class Complete
|
155
|
+
class Success < Event::Complete::Reason
|
156
|
+
register :success, :transfer_complete
|
157
|
+
end
|
158
|
+
|
159
|
+
class Timeout < Event::Complete::Reason
|
160
|
+
register :timeout, :transfer_complete
|
161
|
+
end
|
162
|
+
|
163
|
+
class Terminator < Event::Complete::Reason
|
164
|
+
register :terminator, :transfer_complete
|
165
|
+
end
|
166
|
+
|
167
|
+
class Busy < Event::Complete::Reason
|
168
|
+
register :busy, :transfer_complete
|
169
|
+
end
|
170
|
+
|
171
|
+
class Reject < Event::Complete::Reason
|
172
|
+
register :reject, :transfer_complete
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end # Transfer
|
176
|
+
end # Tropo
|
177
|
+
end # Command
|
178
|
+
end # Punchblock
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Punchblock
|
2
|
+
module Component
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
autoload :Input
|
6
|
+
autoload :Output
|
7
|
+
autoload :Record
|
8
|
+
autoload :Tropo
|
9
|
+
|
10
|
+
InvalidActionError = Class.new StandardError
|
11
|
+
|
12
|
+
class ComponentNode < CommandNode
|
13
|
+
attr_accessor :event_queue, :complete_event, :event_callback
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
super
|
17
|
+
@event_queue = Queue.new
|
18
|
+
@complete_event = FutureResource.new
|
19
|
+
@event_callback = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_event(event)
|
23
|
+
event.original_component = self
|
24
|
+
transition_state! event
|
25
|
+
complete_event.resource = event if event.is_a? Event::Complete
|
26
|
+
if event_callback.respond_to?(:call)
|
27
|
+
add_event_to_queue = event_callback.call event
|
28
|
+
end
|
29
|
+
@event_queue << event unless add_event_to_queue
|
30
|
+
end
|
31
|
+
|
32
|
+
def transition_state!(event)
|
33
|
+
complete! if event.is_a? Event::Complete
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_action(action)
|
37
|
+
connection.write call_id, action, component_id
|
38
|
+
end
|
39
|
+
|
40
|
+
def response=(other)
|
41
|
+
super
|
42
|
+
if other.is_a?(Blather::Stanza::Iq)
|
43
|
+
ref = other.rayo_node
|
44
|
+
if ref.is_a?(Ref)
|
45
|
+
@component_id = ref.id
|
46
|
+
@connection.record_command_id_for_iq_id @component_id, other.id
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Create an Rayo stop message
|
53
|
+
#
|
54
|
+
# @return [Stop] an Rayo stop message
|
55
|
+
#
|
56
|
+
def stop_action
|
57
|
+
Stop.new :component_id => component_id, :call_id => call_id
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Sends an Rayo stop message for the current component
|
62
|
+
#
|
63
|
+
def stop!(options = {})
|
64
|
+
raise InvalidActionError, "Cannot stop a #{self.class.name.split("::").last} that is not executing" unless executing?
|
65
|
+
stop_action.tap { |action| write_action action }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Stop < CommandNode # :nodoc:
|
70
|
+
register :stop, :core
|
71
|
+
end
|
72
|
+
end # Component
|
73
|
+
end # Punchblock
|
@@ -0,0 +1,209 @@
|
|
1
|
+
%w{
|
2
|
+
timeout
|
3
|
+
blather/client/dsl
|
4
|
+
punchblock/core_ext/blather/stanza
|
5
|
+
punchblock/core_ext/blather/stanza/presence
|
6
|
+
}.each { |f| require f }
|
7
|
+
|
8
|
+
module Punchblock
|
9
|
+
class Connection < GenericConnection
|
10
|
+
include Blather::DSL
|
11
|
+
|
12
|
+
##
|
13
|
+
# Initialize the required connection attributes
|
14
|
+
#
|
15
|
+
# @param [Hash] options
|
16
|
+
# @option options [String] :username client JID
|
17
|
+
# @option options [String] :password XMPP password
|
18
|
+
# @option options [String] :rayo_domain the domain on which Rayo is running
|
19
|
+
# @option options [Logger] :wire_logger to which all XMPP transactions will be logged
|
20
|
+
# @option options [Boolean, Optional] :auto_reconnect whether or not to auto reconnect
|
21
|
+
# @option options [Numeric, Optional] :write_timeout for which to wait on a command response
|
22
|
+
# @option options [Numeric, nil, Optional] :ping_period interval in seconds on which to ping the server. Nil or false to disable
|
23
|
+
#
|
24
|
+
def initialize(options = {})
|
25
|
+
super
|
26
|
+
raise ArgumentError unless @username = options.delete(:username)
|
27
|
+
raise ArgumentError unless options.has_key? :password
|
28
|
+
@rayo_domain = options[:rayo_domain] || Blather::JID.new(@username).domain
|
29
|
+
|
30
|
+
setup @username, options.delete(:password)
|
31
|
+
|
32
|
+
@callmap = {} # This hash maps call IDs to their XMPP domain.
|
33
|
+
|
34
|
+
@component_id_to_iq_id = {}
|
35
|
+
@iq_id_to_command = {}
|
36
|
+
|
37
|
+
@auto_reconnect = !!options[:auto_reconnect]
|
38
|
+
@reconnect_attempts = 0
|
39
|
+
|
40
|
+
@write_timeout = options[:write_timeout] || 3
|
41
|
+
|
42
|
+
@ping_period = options.has_key?(:ping_period) ? options[:ping_period] : 60
|
43
|
+
|
44
|
+
Blather.logger = options.delete(:wire_logger) if options.has_key?(:wire_logger)
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Write a command to the Rayo server for a particular call
|
49
|
+
#
|
50
|
+
# @param [String] call the call ID on which to act
|
51
|
+
# @param [CommandNode] cmd the command to execute on the call
|
52
|
+
# @param [String, Optional] component_id the component_id on which to execute
|
53
|
+
#
|
54
|
+
# @raise Exception if there is a server-side error
|
55
|
+
#
|
56
|
+
# @return true
|
57
|
+
#
|
58
|
+
def write(call_id, cmd, component_id = nil)
|
59
|
+
async_write call_id, cmd, component_id
|
60
|
+
cmd.response(@write_timeout).tap { |result| raise result if result.is_a? Exception }
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# @return [Queue] Pop this queue to determine result of command execution. Will be true or an exception
|
65
|
+
def async_write(call_id, cmd, component_id = nil)
|
66
|
+
iq = prep_command_for_execution call_id, cmd, component_id
|
67
|
+
write_to_stream iq
|
68
|
+
cmd.request!
|
69
|
+
end
|
70
|
+
|
71
|
+
def prep_command_for_execution(call_id, cmd, component_id = nil)
|
72
|
+
cmd.connection = self
|
73
|
+
cmd.call_id = call_id
|
74
|
+
jid = cmd.is_a?(Command::Dial) ? @rayo_domain : "#{call_id}@#{@callmap[call_id]}"
|
75
|
+
jid << "/#{component_id}" if component_id
|
76
|
+
create_iq(jid).tap do |iq|
|
77
|
+
@logger.debug "Sending IQ ID #{iq.id} #{cmd.inspect} to #{jid}" if @logger
|
78
|
+
iq << cmd
|
79
|
+
@iq_id_to_command[iq.id] = cmd
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Fire up the connection
|
85
|
+
#
|
86
|
+
def run
|
87
|
+
register_handlers
|
88
|
+
connect
|
89
|
+
end
|
90
|
+
|
91
|
+
def connect
|
92
|
+
begin
|
93
|
+
EM.run { client.run }
|
94
|
+
rescue Blather::SASLError, Blather::StreamError => e
|
95
|
+
raise ProtocolError.new(e.class.to_s, e.message)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def stop
|
100
|
+
@reconnect_attempts = nil
|
101
|
+
client.close
|
102
|
+
end
|
103
|
+
|
104
|
+
def connected?
|
105
|
+
client.connected?
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
#
|
110
|
+
# Get the original command issued by command ID
|
111
|
+
#
|
112
|
+
# @param [String] component_id
|
113
|
+
#
|
114
|
+
# @return [RayoNode]
|
115
|
+
#
|
116
|
+
def original_component_from_id(component_id)
|
117
|
+
@iq_id_to_command[@component_id_to_iq_id[component_id]]
|
118
|
+
end
|
119
|
+
|
120
|
+
def record_command_id_for_iq_id(command_id, iq_id)
|
121
|
+
@component_id_to_iq_id[command_id] = iq_id
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def handle_presence(p)
|
127
|
+
throw :pass unless p.rayo_event?
|
128
|
+
@logger.info "Receiving event for call ID #{p.call_id}" if @logger
|
129
|
+
@callmap[p.call_id] = p.from.domain
|
130
|
+
@logger.debug p.inspect if @logger
|
131
|
+
event = p.event
|
132
|
+
event.connection = self
|
133
|
+
if event.source
|
134
|
+
event.source.add_event event
|
135
|
+
else
|
136
|
+
@event_queue.push event
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def handle_iq_result(iq)
|
141
|
+
# FIXME: Do we need to raise a warning if the domain changes?
|
142
|
+
throw :pass unless command = @iq_id_to_command[iq.id]
|
143
|
+
@callmap[iq.from.node] = iq.from.domain
|
144
|
+
@logger.debug "Command #{iq.id} completed successfully" if @logger
|
145
|
+
command.response = iq
|
146
|
+
end
|
147
|
+
|
148
|
+
def handle_error(iq)
|
149
|
+
e = Blather::StanzaError.import iq
|
150
|
+
|
151
|
+
protocol_error = ProtocolError.new e.name, e.text, iq.call_id, iq.component_id
|
152
|
+
|
153
|
+
throw :pass unless command = @iq_id_to_command[iq.id]
|
154
|
+
|
155
|
+
command.response = protocol_error
|
156
|
+
end
|
157
|
+
|
158
|
+
def register_handlers
|
159
|
+
# Push a message to the queue and the log that we connected
|
160
|
+
when_ready do
|
161
|
+
@event_queue.push connected
|
162
|
+
@logger.info "Connected to XMPP as #{@username}" if @logger
|
163
|
+
@reconnect_attempts = 0
|
164
|
+
@rayo_ping = EM::PeriodicTimer.new(@ping_period) { ping_rayo } if @ping_period
|
165
|
+
end
|
166
|
+
|
167
|
+
disconnected do
|
168
|
+
@rayo_ping.cancel if @rayo_ping
|
169
|
+
if @auto_reconnect && @reconnect_attempts
|
170
|
+
timer = 30 * 2 ** @reconnect_attempts
|
171
|
+
@logger.warn "XMPP disconnected. Tried to reconnect #{@reconnect_attempts} times. Reconnecting in #{timer}s." if @logger
|
172
|
+
sleep timer
|
173
|
+
@logger.info "Trying to reconnect..." if @logger
|
174
|
+
@reconnect_attempts += 1
|
175
|
+
connect
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Read/handle call control messages. These are mostly just acknowledgement of commands
|
180
|
+
iq :result? do |msg|
|
181
|
+
handle_iq_result msg
|
182
|
+
end
|
183
|
+
|
184
|
+
# Read/handle error IQs
|
185
|
+
iq :error? do |e|
|
186
|
+
handle_error e
|
187
|
+
end
|
188
|
+
|
189
|
+
# Read/handle presence requests. This is how we get events.
|
190
|
+
presence do |msg|
|
191
|
+
handle_presence msg
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def ping_rayo
|
196
|
+
client.write_with_handler Blather::Stanza::Iq::Ping.new(:set, @rayo_domain) do |response|
|
197
|
+
begin
|
198
|
+
handle_error response if response.is_a? Blather::BlatherError
|
199
|
+
rescue ProtocolError => e
|
200
|
+
raise e unless e.name == :feature_not_implemented
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def create_iq(jid = nil)
|
206
|
+
Blather::Stanza::Iq.new :set, jid || @call_id
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Blather
|
2
|
+
class Stanza
|
3
|
+
##
|
4
|
+
# @return [Punchblock::RayoNode] a child of RayoNode
|
5
|
+
# representing the Rayo command/event contained within the stanza
|
6
|
+
#
|
7
|
+
def rayo_node
|
8
|
+
first_child = children.first
|
9
|
+
Punchblock::RayoNode.import first_child, call_id, component_id if first_child
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# @return [String] the call ID this stanza applies to
|
14
|
+
#
|
15
|
+
def call_id
|
16
|
+
from.node
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# @return [String] the command ID this stanza applies to
|
21
|
+
#
|
22
|
+
def component_id
|
23
|
+
from.resource
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
##
|
2
|
+
# DO NOT USE THIS API!
|
3
|
+
# This file is temporary, to help make testing Punchblock easier.
|
4
|
+
# THIS IS IMPERMANENT AND WILL DISAPPEAR
|
5
|
+
module Punchblock
|
6
|
+
class DSL
|
7
|
+
def initialize(protocol, call, queue) # :nodoc:
|
8
|
+
@protocol, @call, @queue = protocol, call, queue
|
9
|
+
end
|
10
|
+
|
11
|
+
def accept # :nodoc:
|
12
|
+
write @protocol.class::Command::Accept.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def answer # :nodoc:
|
16
|
+
write @protocol.class::Command::Answer.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def hangup # :nodoc:
|
20
|
+
write @protocol.class::Command::Hangup.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def reject(reason = nil) # :nodoc:
|
24
|
+
write @protocol.class::Command::Reject.new(:reason => reason)
|
25
|
+
end
|
26
|
+
|
27
|
+
def redirect(dest) # :nodoc:
|
28
|
+
write @protocol.class::Command::Redirect.new(:to => dest)
|
29
|
+
end
|
30
|
+
|
31
|
+
def record(options = {})
|
32
|
+
write @protocol.class::Component::Record.new(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def say(string, type = :text) # :nodoc:
|
36
|
+
write @protocol.class::Component::Tropo::Say.new(type => string)
|
37
|
+
puts "Waiting on the queue..."
|
38
|
+
response = @queue.pop
|
39
|
+
# TODO: Error handling
|
40
|
+
end
|
41
|
+
|
42
|
+
def write(msg) # :nodoc:
|
43
|
+
@protocol.write @call, msg
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Punchblock
|
2
|
+
class Event
|
3
|
+
class Complete < Event
|
4
|
+
# TODO: Validate response and return response type.
|
5
|
+
# -----
|
6
|
+
# <complete xmlns="urn:xmpp:rayo:ext:1"/>
|
7
|
+
|
8
|
+
register :complete, :ext
|
9
|
+
|
10
|
+
def reason
|
11
|
+
element = find_first('*')
|
12
|
+
if element
|
13
|
+
RayoNode.import(element).tap do |reason|
|
14
|
+
reason.call_id = call_id
|
15
|
+
reason.component_id = component_id
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def recording
|
21
|
+
element = find_first('//ns:recording', :ns => RAYO_NAMESPACES[:record_complete])
|
22
|
+
if element
|
23
|
+
RayoNode.import(element).tap do |recording|
|
24
|
+
recording.call_id = call_id
|
25
|
+
recording.component_id = component_id
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect_attributes # :nodoc:
|
31
|
+
[:reason, :recording] + super
|
32
|
+
end
|
33
|
+
|
34
|
+
class Reason < RayoNode
|
35
|
+
def name
|
36
|
+
super.to_sym
|
37
|
+
end
|
38
|
+
|
39
|
+
def inspect_attributes # :nodoc:
|
40
|
+
[:name] + super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Stop < Reason
|
45
|
+
register :stop, :ext_complete
|
46
|
+
end
|
47
|
+
|
48
|
+
class Hangup < Reason
|
49
|
+
register :hangup, :ext_complete
|
50
|
+
end
|
51
|
+
|
52
|
+
class Error < Reason
|
53
|
+
register :error, :ext_complete
|
54
|
+
|
55
|
+
def details
|
56
|
+
text.strip
|
57
|
+
end
|
58
|
+
|
59
|
+
def inspect_attributes # :nodoc:
|
60
|
+
[:details] + super
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end # Complete
|
64
|
+
end
|
65
|
+
end # Punchblock
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Punchblock
|
2
|
+
class Event
|
3
|
+
class DTMF < Event
|
4
|
+
register :dtmf, :core
|
5
|
+
|
6
|
+
def signal
|
7
|
+
read_attr :signal
|
8
|
+
end
|
9
|
+
|
10
|
+
def signal=(other)
|
11
|
+
write_attr :signal, other
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect_attributes # :nodoc:
|
15
|
+
[:signal] + super
|
16
|
+
end
|
17
|
+
end # End
|
18
|
+
end
|
19
|
+
end # Punchblock
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Punchblock
|
2
|
+
class Event
|
3
|
+
class End < Event
|
4
|
+
register :end, :core
|
5
|
+
|
6
|
+
def reason
|
7
|
+
children.select { |c| c.is_a? Nokogiri::XML::Element }.first.name.to_sym
|
8
|
+
end
|
9
|
+
|
10
|
+
def inspect_attributes # :nodoc:
|
11
|
+
[:reason] + super
|
12
|
+
end
|
13
|
+
end # End
|
14
|
+
end
|
15
|
+
end # Punchblock
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Punchblock
|
2
|
+
class Event
|
3
|
+
class Info < Event
|
4
|
+
register :info, :core
|
5
|
+
|
6
|
+
def event_name
|
7
|
+
children.select { |c| c.is_a? Nokogiri::XML::Element }.first.name.to_sym
|
8
|
+
end
|
9
|
+
|
10
|
+
def inspect_attributes # :nodoc:
|
11
|
+
[:event_name] + super
|
12
|
+
end
|
13
|
+
end # Info
|
14
|
+
end
|
15
|
+
end # Punchblock
|