connfu-client 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/.rspec +3 -0
  2. data/.rvmrc +2 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +120 -0
  5. data/LICENSE.txt +661 -0
  6. data/README.rdoc +398 -0
  7. data/Rakefile +38 -0
  8. data/bin/.DS_Store +0 -0
  9. data/bin/connfu-client +94 -0
  10. data/connfu-client.gemspec +41 -0
  11. data/examples/conference/application.rb +68 -0
  12. data/examples/conference/conference.rb +33 -0
  13. data/examples/conference/conference_app.rb +25 -0
  14. data/examples/conference/connfu.log +5 -0
  15. data/examples/conference/wall.rb +11 -0
  16. data/examples/provisioning/app/get.rb +29 -0
  17. data/examples/provisioning/channels/get.rb +34 -0
  18. data/examples/provisioning/rss/create.rb +28 -0
  19. data/examples/provisioning/rss/delete.rb +27 -0
  20. data/examples/provisioning/rss/get.rb +32 -0
  21. data/examples/provisioning/rss/put.rb +29 -0
  22. data/examples/provisioning/setup.rb +2 -0
  23. data/examples/provisioning/twitter/create.rb +31 -0
  24. data/examples/provisioning/twitter/delete.rb +27 -0
  25. data/examples/provisioning/twitter/get.rb +32 -0
  26. data/examples/provisioning/twitter/put.rb +29 -0
  27. data/examples/provisioning/voice/create.rb +26 -0
  28. data/examples/provisioning/voice/delete.rb +27 -0
  29. data/examples/provisioning/voice/get.rb +36 -0
  30. data/examples/provisioning/voice/phones/create.rb +26 -0
  31. data/examples/provisioning/voice/phones/delete.rb +28 -0
  32. data/examples/provisioning/voice/phones/get.rb +38 -0
  33. data/examples/provisioning/voice/put.rb +38 -0
  34. data/examples/provisioning/voice/whitelist/create.rb +26 -0
  35. data/examples/provisioning/voice/whitelist/delete.rb +27 -0
  36. data/examples/provisioning/voice/whitelist/get.rb +36 -0
  37. data/examples/provisioning/voice/whitelist/put.rb +27 -0
  38. data/lib/connfu.rb +134 -0
  39. data/lib/connfu/cli/generator.rb +71 -0
  40. data/lib/connfu/connfu_logger.rb +88 -0
  41. data/lib/connfu/connfu_message_formatter.rb +134 -0
  42. data/lib/connfu/connfu_stream.rb +182 -0
  43. data/lib/connfu/dispatcher.rb +164 -0
  44. data/lib/connfu/dsl.rb +84 -0
  45. data/lib/connfu/events.rb +32 -0
  46. data/lib/connfu/listener.rb +85 -0
  47. data/lib/connfu/listener_channel.rb +100 -0
  48. data/lib/connfu/message.rb +74 -0
  49. data/lib/connfu/provisioning.rb +12 -0
  50. data/lib/connfu/provisioning/application.rb +374 -0
  51. data/lib/connfu/provisioning/base.rb +95 -0
  52. data/lib/connfu/provisioning/channel.rb +79 -0
  53. data/lib/connfu/provisioning/phone.rb +55 -0
  54. data/lib/connfu/provisioning/rss.rb +21 -0
  55. data/lib/connfu/provisioning/twitter.rb +28 -0
  56. data/lib/connfu/provisioning/voice.rb +89 -0
  57. data/lib/connfu/provisioning/whitelist.rb +62 -0
  58. data/lib/connfu/version.rb +6 -0
  59. data/lib/rdoc/generator/template/connfu/_context.rhtml +209 -0
  60. data/lib/rdoc/generator/template/connfu/_head.rhtml +7 -0
  61. data/lib/rdoc/generator/template/connfu/class.rhtml +38 -0
  62. data/lib/rdoc/generator/template/connfu/file.rhtml +36 -0
  63. data/lib/rdoc/generator/template/connfu/index.rhtml +13 -0
  64. data/lib/rdoc/generator/template/connfu/resources/apple-touch-icon.png +0 -0
  65. data/lib/rdoc/generator/template/connfu/resources/css/github.css +129 -0
  66. data/lib/rdoc/generator/template/connfu/resources/css/main.css +339 -0
  67. data/lib/rdoc/generator/template/connfu/resources/css/panel.css +389 -0
  68. data/lib/rdoc/generator/template/connfu/resources/css/reset.css +53 -0
  69. data/lib/rdoc/generator/template/connfu/resources/favicon.ico +0 -0
  70. data/lib/rdoc/generator/template/connfu/resources/i/arrows.png +0 -0
  71. data/lib/rdoc/generator/template/connfu/resources/i/results_bg.png +0 -0
  72. data/lib/rdoc/generator/template/connfu/resources/i/tree_bg.png +0 -0
  73. data/lib/rdoc/generator/template/connfu/resources/js/highlight.pack.js +1 -0
  74. data/lib/rdoc/generator/template/connfu/resources/js/jquery-1.3.2.min.js +19 -0
  75. data/lib/rdoc/generator/template/connfu/resources/js/jquery-effect.js +593 -0
  76. data/lib/rdoc/generator/template/connfu/resources/js/main.js +20 -0
  77. data/lib/rdoc/generator/template/connfu/resources/js/searchdoc.js +628 -0
  78. data/lib/rdoc/generator/template/connfu/resources/panel/index.html +72 -0
  79. data/lib/rdoc/generator/template/connfu/se_index.rhtml +8 -0
  80. data/spec/connfu_message_formatter_spec.rb +88 -0
  81. data/spec/connfu_spec.rb +51 -0
  82. data/spec/connfu_stream_spec.rb +84 -0
  83. data/spec/dispatcher_spec.rb +227 -0
  84. data/spec/dsl_spec.rb +159 -0
  85. data/spec/listener_channel_spec.rb +130 -0
  86. data/spec/listener_spec.rb +67 -0
  87. data/spec/provisioning/application_spec.rb +47 -0
  88. data/spec/provisioning/channel_shared_examples.rb +52 -0
  89. data/spec/provisioning/channel_spec.rb +13 -0
  90. data/spec/provisioning/phone_spec.rb +88 -0
  91. data/spec/provisioning/voice_spec.rb +138 -0
  92. data/spec/provisioning_spec.rb +500 -0
  93. data/spec/spec_helper.rb +51 -0
  94. metadata +298 -0
@@ -0,0 +1,71 @@
1
+ module Connfu
2
+ ##
3
+ # This module helps a client to manage connFu applications
4
+ module Cli
5
+ module Generator
6
+
7
+ APPLICATION_TEMPLATE=<<END
8
+
9
+ require 'connfu'
10
+
11
+ ##
12
+ # This application is an example of how to create a connFu application
13
+
14
+ token = "YOUR-VALID-CONNFU-TOKEN"
15
+
16
+ Connfu.logger = STDOUT
17
+ Connfu.log_level = Logger::DEBUG
18
+
19
+ Connfu.application(token) {
20
+
21
+ listen(:voice) do |conference|
22
+ conference.on(:join) do |call|
23
+ puts "New inbound call from \#{call[:from]} on number \#{call[:to]}"
24
+ end
25
+
26
+ conference.on(:leave) do |call|
27
+ puts "\#{call[:from]} has left the conference \#{call[:channel_name]}"
28
+ end
29
+
30
+ conference.on(:new_topic) do |topic|
31
+ puts "New topic in the conference \#{topic[:channel_name]}: \#{topic[:content]}"
32
+ end
33
+ end
34
+
35
+ listen(:twitter) do |twitter|
36
+ twitter.on(:new) do |tweet|
37
+ puts "\#{tweet[:channel_name]} just posted a new tweet in the conference room: \#{tweet.content}"
38
+ end
39
+ end
40
+
41
+ listen(:sms) do |sms|
42
+ sms.on(:new) do |message|
43
+ puts "New inbound sms from \#{message[:from]}: \#{message[:content]}"
44
+ end
45
+ end
46
+
47
+ }
48
+
49
+ END
50
+
51
+ class << self
52
+ ##
53
+ #
54
+ # ==== Parameters
55
+ # * **name** application name
56
+ # * **channels** channels the application should listen to
57
+ # * **file_name** main file that will hold the application logic
58
+ #
59
+ # ==== Return
60
+ def run(name, channels = nil, file_name = "application.rb")
61
+ Dir.mkdir(name)
62
+ Dir.chdir(name) do
63
+ File.open(file_name, 'w') do |f|
64
+ f.write(APPLICATION_TEMPLATE)
65
+ end
66
+ end
67
+ end # end:run
68
+ end # end class level
69
+ end # end module Generator
70
+ end # end module Cli
71
+ end
@@ -0,0 +1,88 @@
1
+ require 'logger'
2
+
3
+ module Connfu
4
+ ##
5
+ # This module defines a mixin to be used in any class that needs to log messages.
6
+ #
7
+ module ConnfuLogger
8
+
9
+ ##
10
+ # Nice way to include the Module methods when including it in a class/module
11
+ #
12
+ # ==== Parameters
13
+ # * +base+ class that includes the mixin
14
+ def self.included(base)
15
+ base.extend(ClassMethods)
16
+ end
17
+
18
+ ##
19
+ # This internal module acts as a wrapper to include the class/module level methods
20
+ module ClassMethods
21
+
22
+ ##
23
+ # logger setter
24
+ #
25
+ # ==== Parameters
26
+ # * +value+ should be:
27
+ # * a valid IO object (STDOUT, string representing a valid filename, File object)
28
+ # * a ::Logger instance
29
+ #
30
+ # ==== Return
31
+ # new ::Logger object created
32
+ def logger=(value)
33
+ # _logger must be static var and not class var to be shared between objects/classes
34
+ if value.is_a?(String) or value.is_a?(IO)
35
+ @@_logger = Logger.new(value)
36
+ else
37
+ @@_logger = value
38
+ end
39
+ end
40
+
41
+ ##
42
+ # logger getter
43
+ #
44
+ # ==== Return
45
+ # ::Logger object
46
+ def logger
47
+ @@_logger ||= create_logger
48
+ end
49
+
50
+ ##
51
+ # Change logger level
52
+ #
53
+ # ==== Parameters
54
+ # * +level+ valid Logger level constant (::Logger::DEBUG, etc)
55
+ def log_level=(level)
56
+ logger.level = level
57
+ end
58
+
59
+ ##
60
+ # Creates a new Logger object and defines the level and format
61
+ #
62
+ # ==== Parameters
63
+ # * +output+ valid IO object
64
+ # ==== Return
65
+ # Logger object
66
+ def create_logger(output = nil)
67
+ output.nil? and output = STDOUT
68
+ logger = Logger.new(output)
69
+ logger.level = Logger::ERROR
70
+ #logger.formatter = proc { |severity, datetime, progname, msg|
71
+ # "#{severity} on #{datetime} at #{progname}: #{msg}\n"
72
+ #}
73
+ logger.datetime_format = "%Y-%m-%d %H:%M:%S"
74
+ logger
75
+ end
76
+ end
77
+
78
+ ##
79
+ # Instance method that wraps the class method
80
+ #
81
+ # ==== Return
82
+ # * see ClassMethods.logger
83
+ def logger
84
+ self.class.logger
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,134 @@
1
+ require 'active_support'
2
+ require 'connfu/connfu_logger'
3
+ require 'connfu/message'
4
+ require 'uri'
5
+
6
+ module Connfu
7
+
8
+ ##
9
+ # This class is in charge of formatting the incoming messages
10
+ # It's used by any connFu listener to format a raw message got from the streaming API to a
11
+ # Connfu::Message instance
12
+ #
13
+ class ConnfuMessageFormatter
14
+ include Connfu::ConnfuLogger # application logger
15
+
16
+ class << self
17
+
18
+ ##
19
+ # Convert an inbound twitter/RSS event to a Message instance.
20
+ #
21
+ # ==== Parameters
22
+ #
23
+ # * +message+ raw JSON decoded message
24
+ # ==== Return
25
+ # Array of Connfu::Message instances
26
+ def format_message(message)
27
+ message.is_a?(String) and message = ActiveSupport::JSON.decode(message)
28
+ values = []
29
+
30
+ # internal method that process the message
31
+ fetch_values = lambda { |msg|
32
+ begin
33
+ if msg.is_a?(Hash)
34
+ sender = msg["actor"]["id"]
35
+ recipients = nil
36
+ user_mentions = msg["object"]["entities"]["user_mentions"]
37
+ logger.debug(user_mentions)
38
+ if user_mentions.is_a?(Array) && user_mentions.length > 0
39
+ recipients = []
40
+ user_mentions.each { |recipient|
41
+ recipients << recipient
42
+ }
43
+ if recipients.length.eql?(1)
44
+ recipients = recipients[0]
45
+ end
46
+ end
47
+ channel_info = msg["backchat"]["bare_uri"].split(":")
48
+ logger.debug(channel_info)
49
+ params = {
50
+ :id => msg["object"]["id"],
51
+ :content => msg["object"]["content"],
52
+ :message_type => "new",
53
+ :channel_type => channel_info[0],
54
+ :channel_name => channel_info[1][2..-2],
55
+ :from => sender,
56
+ :to => recipients
57
+ }
58
+ params
59
+ else
60
+ logger.error("Invalid message format: #{msg}")
61
+ nil
62
+ end
63
+ rescue => ex
64
+ logger.error("Unable to fetch values from message #{msg}. Caught exception #{ex}")
65
+ nil
66
+ end
67
+ }
68
+
69
+ if message.is_a?(Array)
70
+ message.each { |msg|
71
+ params = fetch_values.call(msg)
72
+ # include the message if valid params
73
+ params.nil? or values << Connfu::Message.new(params)
74
+ }
75
+ else
76
+ params = fetch_values.call(message)
77
+ # include the message if valid params
78
+ params.nil? or values << Connfu::Message.new(params)
79
+ end
80
+ values
81
+ end
82
+
83
+ ##
84
+ # Convert the inbound voice/sms event to a Message instance
85
+ # ==== Parameters
86
+ # * +message+ raw JSON decoded message
87
+ #
88
+ # ==== Return
89
+ # * Array of one Connfu::Message instance
90
+ # * Empty array if the message is invalid
91
+ def format_voice_sms(message)
92
+ if message.is_a?(Array) and message.length.eql?(2)
93
+ if Connfu::Message.is_voice?(message[0])
94
+ logger.debug("Format the new voice message")
95
+ conference_id = URI.parse(message[1]["conferenceId"]).host
96
+ params = {
97
+ :id => 1234,
98
+ :content => message[1]["newTopic"],
99
+ :from => message[1]["from"],
100
+ :to => message[1]["to"],
101
+ :message_type => message[0],
102
+ :channel_type => "voice",
103
+ :channel_name => conference_id
104
+ }
105
+ logger.debug(params)
106
+
107
+ [Connfu::Message.new(params)]
108
+ elsif Connfu::Message.is_sms?(message[0])
109
+ logger.debug("Format the new sms message")
110
+ params = {
111
+ :id => 1234,
112
+ :content => message[1]["message"],
113
+ :from => message[1]["from"],
114
+ :to => message[1]["to"],
115
+ :message_type => "new",
116
+ :channel_type => "sms",
117
+ :channel_name => message[1]["appId"]
118
+ }
119
+ [Connfu::Message.new(params)]
120
+ else
121
+ logger.error("Unexpected message type")
122
+ logger.error(message)
123
+ []
124
+ end
125
+ else
126
+ logger.error("Unexpected message format")
127
+ logger.error(message)
128
+ []
129
+ end
130
+ end
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,182 @@
1
+ require 'active_support'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'uri'
5
+ require 'thread'
6
+ require 'connfu/connfu_logger'
7
+ require 'connfu/message'
8
+ require 'connfu/connfu_message_formatter'
9
+
10
+ module Connfu
11
+
12
+ ##
13
+ # Open an HTTP connection to connFu and start listening to any incoming event
14
+ # This is the entry point to execute any proc in a connFu application
15
+ # based on external events.
16
+ class ConnfuStream
17
+ include Connfu::ConnfuLogger # application logger
18
+
19
+ ##
20
+ # ConnfuStream initializer.
21
+ # ==== Parameters
22
+ #
23
+ # * +app_stream+ valid HTTP stream url to connect and listen events
24
+ # * +api_key+ valid Token to get access to a Connfu Stream
25
+ # * +uri+ HTTP endpoint to open the connection
26
+ def initialize(app_stream, api_key, uri)
27
+
28
+ @app_stream = app_stream
29
+ @api_key = api_key
30
+
31
+ _uri = URI.parse(uri)
32
+ @port = _uri.port
33
+ @host = _uri.host
34
+ @path = _uri.path.concat(app_stream)
35
+ @scheme = _uri.scheme
36
+
37
+ @formatter = Connfu::ConnfuMessageFormatter
38
+ end
39
+
40
+ ##
41
+ # Open a HTTP connection to connFu and start listening new events
42
+ def start_listening
43
+
44
+ begin
45
+ # http client instantiation and configuration
46
+ http_client = Net::HTTP.new(@host, @port)
47
+ logger.debug("#{self.class} opening connection to #{@host}:#{@port}")
48
+ http_client.use_ssl = @scheme.eql?("https")
49
+ if @scheme.eql?("https")
50
+ http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE
51
+ end
52
+
53
+ logger.debug("#{self.class} start listening to stream #{@path}")
54
+
55
+ http_client.read_timeout = 60*6 # double the timeout connFu is configured
56
+
57
+ # open connection
58
+ http_client.start do |http|
59
+ req = Net::HTTP::Get.new(
60
+ @path,
61
+ headers)
62
+
63
+ # send GET request
64
+ http.request(req) do |res|
65
+ logger.debug "#{self.class} Request to the endpoint...."
66
+ # read chunk data
67
+ logger.debug "#{self.class} Waiting for a new event...."
68
+ res.read_body do |chunk|
69
+ unless chunk.chomp.strip.empty?
70
+ # format data retrieved
71
+ events = handle_data(chunk)
72
+ # Insert message(s) in the queue
73
+ events.nil? or message(events)
74
+ else
75
+ logger.debug "#{self.class} got an empty data"
76
+ end
77
+ logger.debug "#{self.class} Waiting for a new event...."
78
+ end
79
+ end
80
+ end
81
+ rescue Exception => ex
82
+ logger.error "[#{Time.now} | #{ex.class}] #{ex.message}\n#{ex.backtrace.join("\n")}"
83
+ # loop again
84
+ start_listening
85
+ end
86
+ end
87
+
88
+ ##
89
+ # This method should be called by the class that instantiates the ConnfuStream to fetch inbound events.
90
+ # It stops the execution waiting for an inbound event.
91
+ #
92
+ # ==== Return
93
+ # Connfu::Message instance
94
+ def get
95
+ queue.pop
96
+ end
97
+
98
+ private
99
+
100
+ ##
101
+ # Internal Queue to send Message instances to the listener
102
+ #
103
+ # ==== Return
104
+ # Queue instance
105
+ def queue
106
+ @queue ||= Queue.new
107
+ end
108
+
109
+ ##
110
+ # Process the data retrieved, formatting the raw data into one or more Connfu::Message instances
111
+ #
112
+ # ==== Parameters
113
+ # * +chunk+ data got from the HTTP stream
114
+ #
115
+ # ==== Return
116
+ # * Array of Connfu::Message instances
117
+ # * nil if invalid data
118
+ def handle_data(chunk)
119
+ if chunk.nil?
120
+ logger.info("Unable to process nil data")
121
+ return nil
122
+ end
123
+ logger.debug("raw data #{chunk}")
124
+ chunk = chunk.split("\n") # more than one event can be retrieved separated by '\n'
125
+ if chunk.is_a?(Array)
126
+ events = []
127
+ temp_events = []
128
+ chunk.each { |value|
129
+ json = ActiveSupport::JSON.decode(value)
130
+ if json
131
+ unless json.is_a?(Array) # Twitter - RSS message
132
+ unless json.nil?
133
+ logger.debug("#{self.class} Got a twitter message")
134
+ temp_events << @formatter.format_message(json)
135
+ temp_events.nil? or events << temp_events.flatten
136
+ else
137
+ logger.debug("#{self.class} Invalid data received")
138
+ events = nil
139
+ end
140
+ else # Voice - SMS message
141
+ logger.debug("#{self.class} Got a voice/sms message")
142
+ logger.debug(json)
143
+ temp_events = @formatter.format_voice_sms(json)
144
+ temp_events.nil? or events << temp_events.flatten
145
+ end
146
+ end
147
+ }
148
+ events.flatten
149
+ else
150
+ logger.info("#{self.class} Invalid data received #{chunk}")
151
+ nil
152
+ end
153
+ end
154
+
155
+ ##
156
+ # Insert an array of messages in the queue
157
+ # ==== Parameters
158
+ # * +events+ array of incoming events
159
+ def message(events)
160
+ if events.is_a?(Array)
161
+ events.each { |msg|
162
+ if msg.is_a?(Connfu::Message)
163
+ logger.debug("#{self.class} Inserting message in the queue")
164
+ logger.debug msg.to_s
165
+ queue << msg
166
+ else
167
+ logger.info("Invalid message type #{msg.class}")
168
+ end
169
+ }
170
+ end
171
+ end
172
+
173
+ ##
174
+ # Headers to send to authenticate
175
+ def headers
176
+ {"accept" => "application/json",
177
+ "authorization" => "Backchat #{@api_key}",
178
+ "Connection" => "Keep-Alive"}
179
+ end
180
+
181
+ end
182
+ end