sms_broker 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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