sms_broker 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0b28741c2cf164576894b6b876444ae070f7303f
4
+ data.tar.gz: f0549e93ac7bbe3b87fff76d101a6f06152c192a
5
+ SHA512:
6
+ metadata.gz: fcd96dffdafb77800af5a3798be97beff7ec3a921fcdbc061c0ad1f069f5ea127a976e0dace58624742c5ad510b400fadd866a3d6eba884d22a179d7577ae3c2
7
+ data.tar.gz: c6b9c850c13c0c43e2cc324b12fb6bf884bda46a3f0ab709a6ca13a06df0412a82341fd7e4e87d07aa0d47dfd100352906f9a685f6bf9a464c342a1c0cdccf10
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rvmrc
7
+ .ruby-version
8
+ .ruby-gemset
9
+ Gemfile.lock
10
+ InstalledFiles
11
+ _yardoc
12
+ coverage
13
+ doc/
14
+ lib/bundler/man
15
+ pkg
16
+ rdoc
17
+ spec/reports
18
+ spec/support/services_keys.yml
19
+ test/tmp
20
+ test/version_tmp
21
+ tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'codeclimate-test-reporter'
7
+ gem 'simplecov', require: false
8
+ gem 'rack-test'
9
+ end
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ Sms broker
2
+ ==========================
3
+
4
+ ### Usage
5
+
6
+ ```ruby
7
+ SmsBroker.setup do |config|
8
+ config.services ['nexmo', 'twilio']
9
+
10
+ config.default_service 'nexmo'
11
+
12
+ config.nexmo_setup \
13
+ key: 'NEXMO_API_KEY',
14
+ secret: 'NEXMO_API_SECRET',
15
+ sender_id: 'NEXMO_SENDER_ID',
16
+ phone_number: 'NEXMO_PHONE_NUMBER'
17
+
18
+ config.twilio_setup \
19
+ auth_token: 'TWILIO_AUTH_TOKEN',
20
+ account_sid: 'TWILIO_ACCOUNT_SID',
21
+ sender_id: 'TWILIO_SENDER_ID',
22
+ phone_number: 'TWILIO_PHONE_NUMBER'
23
+ end
24
+ ```
25
+
26
+ #### Basic usage
27
+ ```ruby
28
+ message = SmsBroker.message('Get paid doing small tasks!').to('441234567890')
29
+
30
+ if message.valid?
31
+ response = message.deliver
32
+
33
+ # response.success?
34
+ # response.serialized
35
+ end
36
+ ```
37
+
38
+ #### Specifying the provider
39
+ ```ruby
40
+ SmsBroker.service(:twilio).message('Get paid doing small tasks!').to('441234567890')
41
+ ```
42
+
43
+ ###Installation
44
+
45
+ Add this line to your application's Gemfile:
46
+
47
+ gem 'sms_broker'
48
+
49
+ And then execute:
50
+
51
+ $ bundle
52
+
53
+ ### Get in touch
54
+
55
+ If you have any questions, write an issue or get in touch dev@streetbees.com
56
+
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ task :default => :test
5
+ RSpec::Core::RakeTask.new(:test)
@@ -0,0 +1,24 @@
1
+ module SmsBroker
2
+ module Client
3
+
4
+ class Base
5
+
6
+ attr_reader :name,
7
+ :client,
8
+ :options,
9
+ :sender_id,
10
+ :phone_number
11
+
12
+ def initialize(name, client)
13
+ @name = name
14
+ @client = client
15
+ end
16
+
17
+ def serialize_number(number)
18
+ "#{number}".delete("+")
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,44 @@
1
+ module SmsBroker
2
+ module Client
3
+
4
+ class Nexmo < Base
5
+
6
+ def initialize(options)
7
+ nexmo_options = options.dup
8
+
9
+ auth_options = {
10
+ key: nexmo_options.delete(:key),
11
+ secret: nexmo_options.delete(:secret)
12
+ }
13
+
14
+ @sender_id = nexmo_options.delete(:sender_id)
15
+ @phone_number = nexmo_options.delete(:phone_number)
16
+
17
+ super(:nexmo, ::Nexmo::Client.new(auth_options))
18
+ end
19
+
20
+ def send_message(message)
21
+ response = client.send_message \
22
+ text: message[:text],
23
+ from: serialize_number(message[:from]),
24
+ to: serialize_number(message[:to])
25
+
26
+ if success_response?(response)
27
+ Response::NexmoSuccess.new(response)
28
+ else
29
+ Response::NexmoError.new(response)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def success_response?(response)
36
+ # just looking for the first message,
37
+ # right now only one message per call
38
+ response['messages'].length > 0 && response['messages'][0]['status'] == '0'
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ module SmsBroker
2
+ module Client
3
+ module Response
4
+
5
+ class Error
6
+
7
+ attr_reader :service,
8
+ :response,
9
+ :serialized
10
+
11
+ def initialize(service, response, serialized = {})
12
+ @service = service
13
+ @response = response
14
+ @serialized = { errors: serialized }
15
+ end
16
+
17
+ def success?
18
+ false
19
+ end
20
+
21
+ def invalid_sender_id?
22
+ (@serialized[:errors]['sender_id'] || {}).include?('is invalid')
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+
31
+ require 'sms_broker/client/response/nexmo_error'
32
+ require 'sms_broker/client/response/twilio_error'
@@ -0,0 +1,35 @@
1
+ module SmsBroker
2
+ module Client
3
+ module Response
4
+
5
+ class NexmoError < Error
6
+
7
+ SENDER_ID_NOT_SUPPORTED = '15'
8
+
9
+ def initialize(nexmo_response)
10
+ super :nexmo, nexmo_response, serialize_error_response(nexmo_response)
11
+ end
12
+
13
+ private
14
+
15
+ def serialize_error_response(nexmo_response)
16
+ errors = {}.tap do |hash|
17
+ nexmo_response['messages'].each do |message|
18
+ hash[message['status']] = [message['error-text']]
19
+ end
20
+
21
+ hash
22
+ end
23
+
24
+ if errors.keys.include?(SENDER_ID_NOT_SUPPORTED)
25
+ errors['sender_id'] = ['is invalid']
26
+ end
27
+
28
+ errors
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ module SmsBroker
2
+ module Client
3
+ module Response
4
+
5
+ class NexmoSuccess < Success
6
+
7
+ def initialize(nexmo_response)
8
+ super :nexmo, nexmo_response, serialize(nexmo_response)
9
+ end
10
+
11
+ private
12
+
13
+ def serialize(response)
14
+ single_response = response['messages'][0]
15
+
16
+ {
17
+ to: single_response['to'],
18
+ from: single_response['from'],
19
+ message_id: single_response['message-id'],
20
+ raw: {
21
+ to: single_response['to'],
22
+ from: single_response['from'],
23
+ status: single_response['status'],
24
+ network: single_response['network'],
25
+ message_id: single_response['message-id'],
26
+ client_ref: single_response['client-ref'],
27
+ remaining_balance: single_response['remaining-balance'],
28
+ message_price: single_response['message-price']
29
+ }
30
+ }
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,40 @@
1
+ module SmsBroker
2
+ module Client
3
+ module Response
4
+
5
+ class Success
6
+
7
+ attr_reader :raw,
8
+ :service,
9
+ :serialized
10
+
11
+ def initialize(service, response, serialized)
12
+ @raw = response
13
+ @service = service
14
+ @serialized = serialized
15
+ end
16
+
17
+ def success?
18
+ true
19
+ end
20
+
21
+ def to
22
+ serialized[:to]
23
+ end
24
+
25
+ def from
26
+ serialized[:from]
27
+ end
28
+
29
+ def message_id
30
+ serialized[:message_id]
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+
39
+ require 'sms_broker/client/response/nexmo_success'
40
+ require 'sms_broker/client/response/twilio_success'
@@ -0,0 +1,51 @@
1
+ module SmsBroker
2
+ module Client
3
+ module Response
4
+
5
+ class TwilioError < Error
6
+
7
+ SENDER_ID_NOT_SUPPORTED = '21212'
8
+
9
+ def initialize(twilio_response)
10
+ super :twilio, twilio_response, serialize(twilio_response)
11
+ end
12
+
13
+ private
14
+
15
+ def serialize(twilio_response)
16
+ if twilio_response.is_a?(::Twilio::REST::RequestError)
17
+ serialize_exeception_errors(twilio_response)
18
+ else
19
+ serialize_response_error(twilio_response)
20
+ end
21
+ end
22
+
23
+ def serialize_response_error(response)
24
+ errors = {
25
+ "#{response.error_code}" => [response.error_message]
26
+ }
27
+
28
+ if "#{response.error_code}" == SENDER_ID_NOT_SUPPORTED
29
+ errors['sender_id'] = ['is invalid']
30
+ end
31
+
32
+ errors
33
+ end
34
+
35
+ def serialize_exeception_errors(exception)
36
+ errors = {
37
+ "#{exception.code}" => [exception.message]
38
+ }
39
+
40
+ if "#{exception.code}" == SENDER_ID_NOT_SUPPORTED
41
+ errors['sender_id'] = ['is invalid']
42
+ end
43
+
44
+ errors
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,41 @@
1
+ module SmsBroker
2
+ module Client
3
+ module Response
4
+
5
+ class TwilioSuccess < Success
6
+
7
+ def initialize(twilio_response)
8
+ super :twilio, twilio_response, serialize(twilio_response)
9
+ end
10
+
11
+ private
12
+
13
+ def serialize(response)
14
+ {
15
+ to: response.to,
16
+ from: response.from,
17
+ message_id: response.sid,
18
+ raw: {
19
+ to: response.to,
20
+ sid: response.sid,
21
+ uri: response.uri,
22
+ from: response.from,
23
+ body: response.body,
24
+ price: response.price,
25
+ status: response.status,
26
+ price_unit: response.price_unit,
27
+ error_code: response.error_code,
28
+ account_sid: response.account_sid,
29
+ api_version: response.api_version,
30
+ date_created: response.date_created,
31
+ error_message: response.error_message,
32
+ messaging_service_sid: response.messaging_service_sid
33
+ }
34
+ }
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,49 @@
1
+ module SmsBroker
2
+ module Client
3
+
4
+ class Twilio < Base
5
+
6
+ def initialize(options)
7
+ twilio_options = options.dup
8
+
9
+ auth_options = {
10
+ account_sid: twilio_options.delete(:account_sid),
11
+ auth_token: twilio_options.delete(:auth_token)
12
+ }
13
+
14
+ @sender_id = twilio_options.delete(:sender_id)
15
+ @phone_number = twilio_options.delete(:phone_number)
16
+
17
+ super \
18
+ :twilio,
19
+ ::Twilio::REST::Client.new(auth_options[:account_sid], auth_options[:auth_token])
20
+ end
21
+
22
+ def send_message(message)
23
+ begin
24
+ response = client.messages.create \
25
+ body: message[:text],
26
+ from: serialize_number(message[:from]),
27
+ to: serialize_number(message[:to])
28
+
29
+ if failed_response?(response)
30
+ Response::TwilioError.new(response)
31
+ else
32
+ Response::TwilioSuccess.new(response)
33
+ end
34
+
35
+ rescue ::Twilio::REST::RequestError => exception
36
+ Response::TwilioError.new(exception)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def failed_response?(response)
43
+ ['undelivered', 'failed'].include?(response.status)
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,56 @@
1
+ require 'sms_broker/setup'
2
+ require 'sms_broker/exceptions/invalid_setup'
3
+
4
+ module SmsBroker
5
+
6
+ module Configuration
7
+
8
+ @@configuration = nil
9
+
10
+ def default_service
11
+ configuration[:default_service]
12
+ end
13
+
14
+ def clear_setup
15
+ @@configuration = nil
16
+ end
17
+
18
+ def configuration
19
+ exception = \
20
+ Exceptions::InvalidSetup.new('setup does not exists')
21
+
22
+ @@configuration || (raise exception)
23
+ end
24
+
25
+ def setup(&block)
26
+ setup = Setup.new
27
+ yield setup if block_given?
28
+
29
+ @@configuration = setup.options
30
+
31
+ setup
32
+ end
33
+
34
+ def setup!(&block)
35
+ setup = Setup.new
36
+ yield setup if block_given?
37
+
38
+ unless setup.valid?
39
+ exception = \
40
+ Exceptions::InvalidSetup.new('setup is invalid, check exception.errors')
41
+
42
+ exception.errors = setup.errors
43
+
44
+ raise exception
45
+ end
46
+
47
+ @@configuration = setup.options
48
+
49
+ setup
50
+ end
51
+
52
+ extend self
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,9 @@
1
+ module SmsBroker
2
+ module Exceptions
3
+
4
+ class InvalidService < StandardError
5
+
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module SmsBroker
2
+ module Exceptions
3
+
4
+ class InvalidSetup < StandardError
5
+
6
+ attr_accessor :errors
7
+
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,81 @@
1
+ module SmsBroker
2
+
3
+ class MessageSender
4
+
5
+ attr_reader :client,
6
+ :errors
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def to(number)
13
+ @message_to = number
14
+
15
+ self
16
+ end
17
+
18
+ def message(text)
19
+ @message_text = text
20
+
21
+ self
22
+ end
23
+
24
+ def deliver
25
+ unless valid?
26
+ return Client::Response::Error.new(client.name, errors, errors)
27
+ end
28
+
29
+ response = client.send_message(build_message)
30
+
31
+ if should_try_again_with_phone_number?(response)
32
+ return client.send_message(build_message(:phone_number))
33
+ end
34
+
35
+ response
36
+ end
37
+
38
+ def valid?
39
+ schema = {
40
+ message: Compel.string.required.max_length(140),
41
+ to: Compel.string.required
42
+ }
43
+
44
+ object = {
45
+ message: @message_text,
46
+ to: @message_to
47
+ }
48
+
49
+ result = Compel.hash.keys(schema).validate(object)
50
+
51
+ @errors = result.errors
52
+
53
+ result.valid?
54
+ end
55
+
56
+ private
57
+
58
+ def build_message(from = :sender_id)
59
+ sender = \
60
+ if client.sender_id && from == :sender_id
61
+ client.sender_id
62
+ else
63
+ client.phone_number
64
+ end
65
+
66
+ {
67
+ text: @message_text,
68
+ from: sender,
69
+ to: @message_to
70
+ }
71
+ end
72
+
73
+ def should_try_again_with_phone_number?(response)
74
+ response.is_a?(Client::Response::Error) &&
75
+ response.invalid_sender_id? &&
76
+ !!client.sender_id
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,50 @@
1
+ require 'sms_broker/message_sender'
2
+
3
+ require 'sms_broker/client/base'
4
+ require 'sms_broker/client/nexmo'
5
+ require 'sms_broker/client/twilio'
6
+
7
+ require 'sms_broker/exceptions/invalid_service'
8
+
9
+ module SmsBroker
10
+
11
+ CLIENTS = {
12
+ nexmo: Client::Nexmo,
13
+ twilio: Client::Twilio
14
+ }
15
+
16
+ class Service
17
+
18
+ def self.get(name)
19
+ options = service_configuration(name)
20
+
21
+ result = Service.validate(name, options)
22
+
23
+ unless result.valid?
24
+ raise Exceptions::InvalidService, { name.to_sym => result.errors }
25
+ end
26
+
27
+ new CLIENTS[name.to_sym].new(options)
28
+ end
29
+
30
+ def self.service_configuration(name)
31
+ SmsBroker.configuration[:services_setups][name.to_sym]
32
+ end
33
+
34
+ def self.validate(name, options)
35
+ Setup.service_validation_schemas[name.to_sym].validate(options)
36
+ end
37
+
38
+ attr_reader :client
39
+
40
+ def initialize(client)
41
+ @client = client
42
+ end
43
+
44
+ def message(message)
45
+ MessageSender.new(client).message(message)
46
+ end
47
+
48
+ end
49
+
50
+ end