esendex 0.2.3 → 0.3.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.
- data/app/controllers/esendex/application_controller.rb +4 -0
- data/app/controllers/esendex/inbound_messages_controller.rb +12 -0
- data/app/controllers/esendex/message_delivered_events_controller.rb +12 -0
- data/app/controllers/esendex/message_failed_events_controller.rb +12 -0
- data/app/controllers/esendex/push_notification_handler.rb +42 -0
- data/config/routes.rb +5 -0
- data/lib/esendex.rb +26 -10
- data/lib/esendex/engine.rb +5 -0
- data/lib/esendex/hash_serialisation.rb +24 -0
- data/lib/esendex/inbound_message.rb +31 -0
- data/lib/esendex/message_delivered_event.rb +30 -0
- data/lib/esendex/message_failed_event.rb +30 -0
- data/lib/esendex/version.rb +1 -1
- data/licence.txt +24 -0
- data/readme.md +101 -9
- data/spec/controllers/message_delivered_events_controller_spec.rb +30 -0
- data/spec/controllers/push_notification_handler_spec.rb +97 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +66 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/log/test.log +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/hash_serialisation_spec.rb +53 -0
- data/spec/inbound_message_spec.rb +43 -0
- data/spec/message_delivered_event_spec.rb +33 -0
- data/spec/message_failed_event_spec.rb +33 -0
- data/spec/spec_helper.rb +14 -3
- metadata +85 -6
- data/LICENSE.txt +0 -20
@@ -0,0 +1,12 @@
|
|
1
|
+
module Esendex
|
2
|
+
class MessageDeliveredEventsController < ApplicationController
|
3
|
+
include PushNotificationHandler
|
4
|
+
|
5
|
+
def create
|
6
|
+
process_notification :message_delivered_event, request.body
|
7
|
+
render text: "OK"
|
8
|
+
rescue => e
|
9
|
+
render_error e
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Common behaviour for all push notification controllers
|
2
|
+
#
|
3
|
+
module Esendex
|
4
|
+
module PushNotificationHandler
|
5
|
+
|
6
|
+
# Used by controllers to handle incoming push notification
|
7
|
+
#
|
8
|
+
# type - Symbol indicating type
|
9
|
+
# source - String request body of the notification
|
10
|
+
#
|
11
|
+
# Returns nothing
|
12
|
+
def process_notification(type, source)
|
13
|
+
|
14
|
+
if handler = Esendex.send("#{type}_handler")
|
15
|
+
notification_class = Esendex.const_get(type.to_s.camelize)
|
16
|
+
notification = notification_class.from_xml source
|
17
|
+
handler.call notification
|
18
|
+
else
|
19
|
+
logger.info "Received #{type} push notification but no handler configured" if defined?(logger)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def render_error(error)
|
25
|
+
lines = []
|
26
|
+
lines << "Path: #{request.path}"
|
27
|
+
request.body.rewind
|
28
|
+
lines << "Notification XML:\r\n#{request.body.read}"
|
29
|
+
lines << "Error: #{error.class.name} #{error.message}"
|
30
|
+
if Esendex.suppress_error_backtrace
|
31
|
+
lines << "[backtrace suppressed]"
|
32
|
+
else
|
33
|
+
lines << error.backtrace.join("\r\n")
|
34
|
+
end
|
35
|
+
lines << "---"
|
36
|
+
lines << "Ruby #{RUBY_VERSION}"
|
37
|
+
lines << "Rails #{Rails::VERSION::STRING}"
|
38
|
+
lines << Esendex.user_agent
|
39
|
+
render text: lines.join("\r\n"), status: 500
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/config/routes.rb
ADDED
data/lib/esendex.rb
CHANGED
@@ -1,32 +1,48 @@
|
|
1
1
|
module Esendex
|
2
2
|
require_relative 'esendex/version'
|
3
|
+
require_relative 'esendex/exceptions'
|
3
4
|
require_relative 'esendex/api_connection'
|
5
|
+
require_relative 'esendex/hash_serialisation'
|
6
|
+
|
4
7
|
require_relative 'esendex/account'
|
8
|
+
require_relative 'esendex/inbound_message'
|
5
9
|
require_relative 'esendex/message'
|
6
|
-
require_relative 'esendex/exceptions'
|
7
10
|
require_relative 'esendex/message_batch_submission'
|
11
|
+
require_relative 'esendex/message_delivered_event'
|
12
|
+
require_relative 'esendex/message_failed_event'
|
8
13
|
|
9
|
-
|
14
|
+
# Load Rails extensions if Rails present
|
15
|
+
if defined?(Rails)
|
16
|
+
require_relative 'esendex/railtie'
|
17
|
+
require_relative 'esendex/engine'
|
18
|
+
end
|
10
19
|
|
11
20
|
API_NAMESPACE = 'http://api.esendex.com/ns/'
|
12
21
|
API_HOST = 'https://api.esendex.com'
|
13
22
|
API_VERSION = 'v1.0'
|
14
23
|
|
24
|
+
# Public - used to configure the gem prior to use
|
25
|
+
#
|
26
|
+
# Esendex.configure do |config|
|
27
|
+
# config.username = 'username'
|
28
|
+
# config.password = 'password'
|
29
|
+
# config.account_reference = 'account reference'
|
30
|
+
# end
|
31
|
+
#
|
15
32
|
def self.configure
|
16
33
|
yield self if block_given?
|
17
|
-
|
18
|
-
unless Esendex.username
|
19
|
-
raise StandardError.new("username required. Either set Esendex.username or set environment variable ESENDEX_USERNAME")
|
20
|
-
end
|
21
|
-
|
22
|
-
unless Esendex.password
|
23
|
-
raise StandardError.new("password required. Either set Esendex.password or set environment variable ESENDEX_PASSWORD")
|
24
|
-
end
|
25
34
|
end
|
26
35
|
|
27
36
|
class << self
|
37
|
+
# credentials for authentication
|
28
38
|
attr_writer :account_reference, :username, :password
|
29
39
|
|
40
|
+
# lambdas for handling push notifications
|
41
|
+
attr_accessor :message_delivered_event_handler, :message_failed_event_handler, :inbound_message_handler
|
42
|
+
|
43
|
+
# behaviour config
|
44
|
+
attr_accessor :suppress_error_backtrace
|
45
|
+
|
30
46
|
def account_reference
|
31
47
|
@account_reference ||= ENV['ESENDEX_ACCOUNT']
|
32
48
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Handles serialisation and deserialisation of object as a hash
|
2
|
+
#
|
3
|
+
module Esendex
|
4
|
+
module HashSerialisation
|
5
|
+
def initialize(attrs={})
|
6
|
+
attrs.each_pair do |k, v|
|
7
|
+
raise ArgumentError.new("#{k} not an attribute of #{self.class.name}") unless respond_to?("#{k}=")
|
8
|
+
send("#{k}=", v)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_hash
|
13
|
+
attrs = {}
|
14
|
+
public_methods
|
15
|
+
.select { |m| m =~ /\w\=$/ }
|
16
|
+
.select { |m| respond_to?(m.to_s.chop) }
|
17
|
+
.collect { |m| m.to_s.chop.to_sym }
|
18
|
+
.each do |method|
|
19
|
+
attrs[method] = send(method)
|
20
|
+
end
|
21
|
+
attrs
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
# <InboundMessage>
|
4
|
+
# <Id>{guid-of-push-notification}</Id>
|
5
|
+
# <MessageId>{guid-of-inbound-message}</MessageId>
|
6
|
+
# <AccountId>{guid-of-esendex-account-for-message}</AccountId>
|
7
|
+
# <MessageText>{Message text of inbound message}</MessageText>
|
8
|
+
# <From>{phone number of sender of the message}</From>
|
9
|
+
# <To>{phone number for the Virtual Mobile Number of your Account}</To>
|
10
|
+
# </InboundMessage>
|
11
|
+
|
12
|
+
module Esendex
|
13
|
+
class InboundMessage
|
14
|
+
include HashSerialisation
|
15
|
+
|
16
|
+
attr_accessor :id, :message_id, :account_id, :message_text, :from, :to
|
17
|
+
|
18
|
+
def self.from_xml(source)
|
19
|
+
doc = Nokogiri::XML source
|
20
|
+
event = InboundMessage.new
|
21
|
+
event.id = doc.at_xpath("/InboundMessage/Id").content
|
22
|
+
event.message_id = doc.at_xpath("/InboundMessage/MessageId").content
|
23
|
+
event.account_id = doc.at_xpath("/InboundMessage/AccountId").content
|
24
|
+
event.message_text = doc.at_xpath("/InboundMessage/MessageText").content
|
25
|
+
event.from = doc.at_xpath("/InboundMessage/From").content
|
26
|
+
event.to = doc.at_xpath("/InboundMessage/To").content
|
27
|
+
event
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
# <MessageDelivered>
|
4
|
+
# <Id>{guid-of-push-notification}</Id>
|
5
|
+
# <MessageId>{guid-of-inbound-message}</MessageId>
|
6
|
+
# <AccountId>{guid-of-esendex-account-for-message}</AccountId>
|
7
|
+
# <OccurredAt>
|
8
|
+
# {the UTC DateTime (yyyy-MM-ddThh:mm:ss) the message was delivered}
|
9
|
+
# </OccurredAt>
|
10
|
+
# </MessageDelivered>
|
11
|
+
|
12
|
+
module Esendex
|
13
|
+
class MessageDeliveredEvent
|
14
|
+
include HashSerialisation
|
15
|
+
|
16
|
+
attr_accessor :id, :message_id, :account_id, :occurred_at
|
17
|
+
|
18
|
+
def self.from_xml(source)
|
19
|
+
doc = Nokogiri::XML source
|
20
|
+
event = MessageDeliveredEvent.new
|
21
|
+
event.id = doc.at_xpath("/MessageDelivered/Id").content
|
22
|
+
event.message_id = doc.at_xpath("/MessageDelivered/MessageId").content
|
23
|
+
event.account_id = doc.at_xpath("/MessageDelivered/AccountId").content
|
24
|
+
occurred_at_s = doc.at_xpath("/MessageDelivered/OccurredAt").content
|
25
|
+
event.occurred_at = DateTime.strptime(occurred_at_s, "%Y-%m-%dT%H:%M:%S").to_time
|
26
|
+
event
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
# <MessageFailed>
|
4
|
+
# <Id>{guid-of-push-notification}</Id>
|
5
|
+
# <MessageId>{guid-of-inbound-message}</MessageId>
|
6
|
+
# <AccountId>{guid-of-esendex-account-for-message}</AccountId>
|
7
|
+
# <OccurredAt>
|
8
|
+
# {the UTC DateTime (yyyy-MM-ddThh:mm:ss) the message failed}
|
9
|
+
# </OccurredAt>
|
10
|
+
# </MessageDelivered>
|
11
|
+
|
12
|
+
module Esendex
|
13
|
+
class MessageFailedEvent
|
14
|
+
include HashSerialisation
|
15
|
+
|
16
|
+
attr_accessor :id, :message_id, :account_id, :occurred_at
|
17
|
+
|
18
|
+
def self.from_xml(source)
|
19
|
+
doc = Nokogiri::XML source
|
20
|
+
event = MessageFailedEvent.new
|
21
|
+
event.id = doc.at_xpath("/MessageFailed/Id").content
|
22
|
+
event.message_id = doc.at_xpath("/MessageFailed/MessageId").content
|
23
|
+
event.account_id = doc.at_xpath("/MessageFailed/AccountId").content
|
24
|
+
occurred_at_s = doc.at_xpath("/MessageFailed/OccurredAt").content
|
25
|
+
event.occurred_at = DateTime.strptime(occurred_at_s, "%Y-%m-%dT%H:%M:%S").to_time
|
26
|
+
event
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/esendex/version.rb
CHANGED
data/licence.txt
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2013, Esendex Ltd.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
* Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
* Neither the name of Esendex nor the
|
12
|
+
names of its contributors may be used to endorse or promote products
|
13
|
+
derived from this software without specific prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL ESENDEX LTD BE LIABLE FOR ANY
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/readme.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
# Esendex
|
1
|
+
# Esendex Ruby SDK
|
2
2
|
|
3
3
|
Ruby Gem for interacting with the Esendex API
|
4
4
|
|
5
|
-
This is in
|
5
|
+
This is in early stages of development but supports the primary requirements around sending and receiving messages.
|
6
6
|
|
7
7
|
## Usage
|
8
8
|
|
@@ -45,7 +45,7 @@ You can specify a different account to the default by passing the reference in a
|
|
45
45
|
account = Account.new('EX23847')
|
46
46
|
```
|
47
47
|
|
48
|
-
Multiple messages are sent by passing an array of `Messages` to the send_messages method
|
48
|
+
Multiple messages are sent by passing an array of `Messages` to the `send_messages` method
|
49
49
|
|
50
50
|
```ruby
|
51
51
|
batch_id = account.send_messages([Message.new("07777111222", "Hello"), Message.new("07777111333", "Hi")])
|
@@ -68,17 +68,109 @@ You will need to set the credentials as enviroment variables which can also be d
|
|
68
68
|
rake esendex:send_message["<mobile_number>","<message_body>"] ESENDEX_USERNAME=<username> ESENDEX_PASSWORD=<password> ESENDEX_ACCOUNT=<account_reference>
|
69
69
|
|
70
70
|
|
71
|
-
|
71
|
+
## Push Notifications
|
72
|
+
|
73
|
+
You can configure your Esendex account to send Push Notifications when the following occur
|
74
|
+
|
75
|
+
+ MessageDeliveredEvent
|
76
|
+
+ MessageFailedEvent
|
77
|
+
+ InboundMessage
|
78
|
+
|
79
|
+
While it is possible to poll our API to check the status of outbound messages and to check for new inbound messages, the recommended approach is to allow the Esendex Platform to notify your system when these events occur.
|
80
|
+
|
81
|
+
To do this you need to setup web accessible end points to accept the notifications. These end points are configured in [Esendex Developer Tools](https://www.esendex.com/developertools).
|
82
|
+
|
83
|
+
Classes are provided in the gem for deserialising the notifications: `MessageDeliveredEvent`, `MessageFailedEvent` and `InboundMessage`. They all expose a `.from_xml` method to support this.
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
message = InboundMessage.from_xml request.body
|
87
|
+
```
|
88
|
+
|
89
|
+
### Rails 3
|
90
|
+
|
91
|
+
In order to simplify receipt of push notifications for Rails 3 users, the gem ships with mountable Rails controllers to handle the receipt of these notifications.
|
92
|
+
|
93
|
+
To mount the end points, add this to `routes.rb`
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
RailsApp::Application.routes.draw do
|
97
|
+
...
|
98
|
+
mount Esendex::Engine => "/esendex"
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
To configure the behaviour in response to a notification, you need to configure a lambda to use.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
Esendex.message_delivered_event_handler = lambda { |notification|
|
106
|
+
# process the notification
|
107
|
+
}
|
108
|
+
```
|
109
|
+
|
110
|
+
Please be kind to us and don't perform anything potentially long running in this call back as our notifier may timeout the request and pass the notififation to the retry queue.
|
111
|
+
|
112
|
+
If you need to perform processing that may run for a longer time then using an async task system like [Resque](https://github.com/defunkt/resque) is recommended.
|
113
|
+
|
114
|
+
All notification classes expose a `.to_hash` method and can be initialised from the hash for serialisatiion and deserialisation.
|
115
|
+
|
116
|
+
For example:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
|
120
|
+
Esendex.inbound_message_handler = lambda { |notification|
|
121
|
+
Resque.enqueue InboundMessageProcessor, notification.to_hash
|
122
|
+
}
|
123
|
+
|
124
|
+
class InboundMessageProcessor
|
125
|
+
|
126
|
+
def self.perform(notification)
|
127
|
+
message = Esendex::InboundMessage.new notification
|
128
|
+
# do some processing of the message here
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
The handlers are defined as follows:
|
135
|
+
|
136
|
+
| End Point| Config Setting | Notification Class | Developer Tools |
|
137
|
+
| -------- | -------------- | ------------------ | --------------- |
|
138
|
+
| /esendex/inbound_messages | Esendex.inbound_message_handler | InboundMessage | SMS received |
|
139
|
+
| /esendex/message_delivered_events | Esendex.message_delivered_event_handler | MessageDeliveredEvent | SMS delivered |
|
140
|
+
| /esendex/message_failed_events | Esendex.message_failed_event_handler | MessageFailedEvent | SMS failed |
|
141
|
+
|
142
|
+
#### Errors
|
143
|
+
|
144
|
+
When an error occur in the handler lambdas then the controller returns a `500` status along with some error information back to the the Esendex Platform. This information is emailed to the technical notifications contact for the account.
|
145
|
+
|
146
|
+
The notification then enters the retry cycle.
|
147
|
+
|
148
|
+
Included by default is the backtrace for the error to assist you in identifying the issue. You can suppress backtrace with the following config option.
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
Esendex.configure do |config|
|
152
|
+
config.suppress_error_backtrace = true
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
## Testing
|
157
|
+
|
158
|
+
bundle exec rspec
|
159
|
+
|
160
|
+
Will run specs. The spec folder contains a dummy Rails project for testing the Rails Engine.
|
161
|
+
|
162
|
+
This project also ships with a `Guardfile` so you can run tests continuously.
|
163
|
+
|
164
|
+
bundle exec guard
|
72
165
|
|
73
|
-
bundle exec rspec
|
74
|
-
|
75
|
-
will run specs, ie those in the root of the test folder
|
76
166
|
|
77
167
|
## Contributing
|
78
168
|
|
79
|
-
|
169
|
+
We really welcome any thoughts or guidance on how this gem could best provide the hooks you need to consume our services in your applicaiton. Please fork and raise pull requests for any features you would like us to add or raise an [issue]((https://github.com/esendex/esendex.gem/issues)
|
170
|
+
|
171
|
+
Customers with more pressing issues should contact our support teams via the usual local phone number or by email: [support@esendex.com](mailto:support@esendex.com).
|
80
172
|
|
81
173
|
## Copyright
|
82
174
|
|
83
|
-
Copyright (c) 2011-13 Esendex Ltd. See
|
175
|
+
Copyright (c) 2011-13 Esendex Ltd. See licence.txt for further details.
|
84
176
|
|