pling 0.1.0 → 0.2.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/Gemfile CHANGED
@@ -5,5 +5,6 @@ gemspec
5
5
 
6
6
  gem 'guard'
7
7
  gem 'guard-rspec'
8
+ gem 'guard-yard', :platform => :ruby_19
8
9
  gem 'growl_notify' if RUBY_PLATFORM =~ /darwin/
9
10
  gem 'rdiscount', ">= 1.6.8", :platform => :ruby
data/Guardfile CHANGED
@@ -2,3 +2,7 @@ guard 'rspec', :version => 2, :all_on_start => true, :all_after_pass => true do
2
2
  watch(%r{^spec/.+_spec\.rb$})
3
3
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
4
  end
5
+
6
+ guard 'yard' do
7
+ watch(%r{lib/.+\.rb})
8
+ end
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  Pling is a notification framework that supports multiple gateways. This gem implements the basic framework as well as a gateway to Google's Cloud to Device Messaging Service (C2DM) and Apple's Push Notification Service (APN).
4
4
 
5
+ See the [API Documentation](http://rubydoc.info/github/flinc/pling/master/file/README.md) for more in depth documentation.
5
6
 
6
7
  ## Requirements
7
8
 
@@ -24,8 +25,8 @@ Add this line to your `Gemfile`:
24
25
  The configuration is pretty simple. Just add a configuration block like this to your code:
25
26
 
26
27
  Pling.configure do |config|
27
- config.gateways.use Pling::Gateway::C2DM, :email => 'your-email@gmail.com', :password => 'your-password', :source => 'your-app-name'
28
- config.gateways.use Pling::Gateway::APN, :certificate => '/path/to/certificate.pem'
28
+ config.gateways.use Pling::C2DM::Gateway, :email => 'your-email@gmail.com', :password => 'your-password', :source => 'your-app-name'
29
+ config.gateways.use Pling::APN::Gateway, :certificate => '/path/to/certificate.pem'
29
30
 
30
31
  # config.middleware.use Your::Custom::Middleware, :your => :custom, :configuration => true
31
32
 
@@ -40,7 +41,7 @@ After configuring Pling you can send messages to devices by like this:
40
41
  device = Pling::Device.new(:identifier => 'XXXXXXXXXX...XXXXXX', :type => :iphone)
41
42
  device.deliver(message)
42
43
 
43
- # ... or call Pling.delver
44
+ # ... or call Pling.deliver
44
45
  Pling.deliver(message, device)
45
46
 
46
47
  Pling has three core components:
@@ -77,12 +78,12 @@ The Gateway delivers the message in the required format to the service provider.
77
78
 
78
79
  Currently there are these gateways available:
79
80
 
80
- * [Android C2DM](http://rdoc.info/github/flinc/pling/master/Pling/Gateway/C2DM)
81
- * [Apple Push Notification](http://rdoc.info/github/flinc/pling/master/Pling/Gateway/APN)
81
+ * [Android C2DM](http://rdoc.info/github/flinc/pling/master/Pling/C2DM/Gateway)
82
+ * [Apple Push Notification](http://rdoc.info/github/flinc/pling/master/Pling/APN/Gateway)
82
83
  * [SMS via Mobilant](https://github.com/flinc/pling-mobilant) (See `pling-mobilant` gem)
83
84
  * [Email](https://github.com/flinc/pling-actionmailer) (See `pling-actionmailer` gem)
84
85
 
85
- See the [API documentation](http://rdoc.info/github/flinc/pling) for details on the available gateways.
86
+ See the [API documentation](http://rubydoc.info/github/flinc/pling/master/file/README.md) for details on the available gateways.
86
87
 
87
88
 
88
89
  ### Middleware
@@ -106,7 +107,7 @@ Pling has support for middlewares. Currently pling itself does not provide any m
106
107
  You can either add middlewares for all gateways or for specific gateways:
107
108
 
108
109
  Pling.configure do |config|
109
- config.gateways.use Pling::Gateway::APN, {
110
+ config.gateways.use Pling::APN::Gateway, {
110
111
  :certificate => '/path/to/certificate.pem',
111
112
  :middlewares => [
112
113
  [Pling::Middleware::TimeFilter, { :range => 9..17 }] # Don't deliver any messages to iOS devices between 9am and 5pm
@@ -8,6 +8,8 @@ module Pling
8
8
  end
9
9
 
10
10
  def deliver(message, device)
11
+ Pling.logger.info "#{self.class} -- Delivering #{message.inspect} to #{device.inspect}"
12
+
11
13
  gateway = Pling::Gateway.discover(device)
12
14
  gateway.deliver(message, device)
13
15
  end
@@ -0,0 +1,122 @@
1
+ require 'openssl'
2
+
3
+ module Pling
4
+ module APN
5
+ class Connection
6
+ include Pling::Configurable
7
+
8
+ def initialize(config)
9
+ setup_configuration(config, :require => [:certificate])
10
+ open
11
+ end
12
+
13
+ def open
14
+ Pling.logger.info "#{self.class} -- Opening connection in #{Process.pid}"
15
+ ssl_socket.connect
16
+
17
+ self
18
+ end
19
+
20
+ def reopen
21
+ close
22
+ open
23
+ end
24
+
25
+ def open?
26
+ not closed?
27
+ end
28
+
29
+ def close
30
+ Pling.logger.info "#{self.class} -- Closing connection in #{Process.pid}"
31
+ ssl_socket.close rescue true
32
+ tcp_socket.close rescue true
33
+
34
+ @ssl_socket = @tcp_socket = nil
35
+
36
+ self
37
+ end
38
+
39
+ def closed?
40
+ return true if ssl_socket.closed?
41
+ tcp_socket.read_nonblock(1)
42
+ return false
43
+ rescue Errno::EAGAIN
44
+ return false
45
+ rescue Exception => e
46
+ return !e.message.match(/read would block/)
47
+ end
48
+
49
+ def write(*args, &block)
50
+ with_retries do
51
+ raise IOError, "Connection closed" if closed?
52
+ ssl_socket.write(*args, &block)
53
+ end
54
+ end
55
+
56
+ def puts(*args, &block)
57
+ with_retries do
58
+ raise IOError, "Connection closed" if closed?
59
+ ssl_socket.puts(*args, &block)
60
+ end
61
+ end
62
+
63
+ def read(*args, &block)
64
+ with_retries do
65
+ raise IOError, "Connection closed" if closed?
66
+ ssl_socket.read(*args, &block)
67
+ end
68
+ end
69
+
70
+ def gets(*args, &block)
71
+ with_retries do
72
+ raise IOError, "Connection closed" if closed?
73
+ ssl_socket.gets(*args, &block)
74
+ end
75
+ end
76
+
77
+ protected
78
+
79
+ def ssl_context
80
+ @ssl_context ||= OpenSSL::SSL::SSLContext.new.tap do |context|
81
+ certificate = File.read(configuration[:certificate])
82
+
83
+ context.cert = OpenSSL::X509::Certificate.new(certificate)
84
+ context.key = OpenSSL::PKey::RSA.new(certificate)
85
+ end
86
+ end
87
+
88
+ def tcp_socket
89
+ @tcp_socket ||= TCPSocket.new(configuration[:host], configuration[:port]).tap do |tcp_socket|
90
+ tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
91
+ tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
92
+ end
93
+ end
94
+
95
+ def ssl_socket
96
+ @ssl_socket ||= OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context) do |ssl_socket|
97
+ ssl_socket.sync = true
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def default_configuration
104
+ super.merge(
105
+ :host => 'gateway.push.apple.com',
106
+ :port => 2195
107
+ )
108
+ end
109
+
110
+ def with_retries(count = 3)
111
+ yield
112
+ rescue OpenSSL::SSL::SSLError, SocketError, SystemCallError, IOError
113
+ if (count -= 1) > 0
114
+ Pling.logger.info "#{self.class} -- #{$!.message}"
115
+ reopen; retry
116
+ else
117
+ raise IOError, $!.message
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,88 @@
1
+ module Pling
2
+ module APN
3
+
4
+ ##
5
+ # Instances of this class can be used to retrieve device identifiers that
6
+ # have been marked invalid. This should be done on a regular basis since
7
+ # Apple will ban you if you continue to send push notifications to
8
+ # invalid devices.
9
+ #
10
+ # The only operation supported by instances of this class is {#get}.
11
+ # The method simply returns a list of the identifieres that have been
12
+ # marked invalid since you last called this method.
13
+ #
14
+ # @example
15
+ #
16
+ # feedback = Pling::APN::Feedback.new(:certificate => '/path/to/certificate.pem')
17
+ # tokens = feedback.get
18
+ #
19
+ # tokens.each do |token|
20
+ # # process token
21
+ # end
22
+ #
23
+ class Feedback
24
+ include Pling::Configurable
25
+
26
+ ##
27
+ # Creates a new instance of this class and establishes a connection to
28
+ # Apple's Push Notification Service.
29
+ #
30
+ # The connection is only established once since Apple will ban you if
31
+ # you reconnect each time you want to retrieve the list of invalid
32
+ # device identifiers. For testing purposes, you should use Apple's
33
+ # sandbox feedback service +feedback.sandbox.push.apple.com+. In order
34
+ # to do this, you have to specify the optional +:host+ parameter when
35
+ # creating instances of this class.
36
+ #
37
+ # @param [Hash] configuration Parameters to control the connection configuration
38
+ # @option configuration [#to_s] :certificate Path to PEM certificate file (Required)
39
+ # @option configuration [String] :host Host to connect to (Default: feedback.push.apple.com)
40
+ # @option configuration [Integer] :port Port to connect to (Default: 2196)
41
+ #
42
+ # @example
43
+ #
44
+ # Pling::APN::Feedback.new(
45
+ # :certificate => '/path/to/certificate.pem',
46
+ # :host => 'feedback.push.apple.com',
47
+ # :port => 2196
48
+ # )
49
+ #
50
+ def initialize(configuration)
51
+ setup_configuration(configuration, :require => :certificate)
52
+ end
53
+
54
+ ##
55
+ # Retrieves all device identifiers that have been marked invalid since
56
+ # the method has been called last.
57
+ #
58
+ # @return [Array<String>] The list of invalid device identifiers
59
+ #
60
+ def get
61
+ tokens = []
62
+ while line = connection.gets
63
+ time, length = line.unpack("Nn")
64
+ tokens << line.unpack("x6H#{length << 1}").first
65
+ end
66
+ tokens
67
+ end
68
+
69
+ private
70
+
71
+ def connection
72
+ @connection ||= Connection.new(
73
+ :host => configuration[:host],
74
+ :port => configuration[:port],
75
+ :certificate => configuration[:certificate]
76
+ )
77
+ end
78
+
79
+ def default_configuration
80
+ super.merge(
81
+ :host => 'feedback.push.apple.com',
82
+ :port => 2196
83
+ )
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -3,21 +3,21 @@ require 'openssl'
3
3
  require 'json'
4
4
 
5
5
  module Pling
6
- module Gateway
6
+ module APN
7
7
  ##
8
8
  # Pling gateway to communicate with Apple's Push Notification service.
9
9
  #
10
- # This gateway handles these device types:
10
+ # This gateway handles these device types:
11
11
  # :apple, :apn, :ios, :ipad, :iphone, :ipod
12
12
  #
13
13
  # Configure it by providing the path to your certificate:
14
14
  #
15
- # Pling::Gateway::APN.new({
15
+ # Pling::APN::Gateway.new({
16
16
  # :certificate => '/path/to/certificate.pem', # Required
17
17
  # :host => 'gateway.sandbox.push.apple.com' # Optional
18
18
  # })
19
19
  #
20
- class APN < Base
20
+ class Gateway < Pling::Gateway
21
21
  handles :apple, :apn, :ios, :ipad, :iphone, :ipod
22
22
 
23
23
  ##
@@ -30,7 +30,13 @@ module Pling
30
30
  def initialize(configuration)
31
31
  super
32
32
  require_configuration(:certificate)
33
- connection
33
+ setup!
34
+ end
35
+
36
+ ##
37
+ # Establishes a new connection if connection is not available or closed
38
+ def setup!
39
+ connection.reopen if connection.closed?
34
40
  end
35
41
 
36
42
  ##
@@ -39,47 +45,48 @@ module Pling
39
45
  # @param [#to_pling_message] message
40
46
  # @param [#to_pling_device] device
41
47
  def deliver!(message, device)
42
- token = [device.identifier].pack('H*')
43
-
44
48
  data = {
45
49
  :aps => {
46
- :alert => message.body
47
- }
48
- }.to_json
50
+ :alert => message.body,
51
+ :badge => message.badge && message.badge.to_i,
52
+ :sound => message.sound
53
+ }.delete_if { |_, value| value.nil? }
54
+ }
49
55
 
50
- raise Pling::DeliveryFailed, "Payload size of #{data.bytesize} exceeds allowed size of 256 bytes." if data.bytesize > 256
56
+ data.merge!(message.payload) if configuration[:payload] && message.payload
57
+
58
+ data = data.to_json
59
+
60
+ if data.bytesize > 256
61
+ raise Pling::DeliveryFailed.new(
62
+ "Payload size of #{data.bytesize} exceeds allowed size of 256 bytes.",
63
+ message,
64
+ device)
65
+ end
51
66
 
52
- connection.write([0, 0, 32, token, 0, data.bytesize, data].pack('ccca*cca*'))
67
+ token = [device.identifier].pack('H*')
68
+
69
+ connection.write([0, token.bytesize, token, data.bytesize, data].pack('cna*na*'))
53
70
  end
54
71
 
55
72
  private
56
73
 
57
74
  def default_configuration
58
- super.merge({
75
+ super.merge(
59
76
  :host => 'gateway.push.apple.com',
60
- :port => 2195
61
- })
77
+ :port => 2195,
78
+ :payload => false
79
+ )
62
80
  end
63
81
 
64
82
  def connection
65
- @connection ||= OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context).tap do |socket|
66
- socket.sync = true
67
- socket.connect
68
- end
69
- end
70
-
71
- def ssl_context
72
- @ssl_context ||= OpenSSL::SSL::SSLContext.new.tap do |context|
73
- certificate = File.read(configuration[:certificate])
74
-
75
- context.cert = OpenSSL::X509::Certificate.new(certificate)
76
- context.key = OpenSSL::PKey::RSA.new(certificate)
77
- end
83
+ @connection ||= Connection.new(
84
+ :host => configuration[:host],
85
+ :port => configuration[:port],
86
+ :certificate => configuration[:certificate]
87
+ )
78
88
  end
79
89
 
80
- def tcp_socket
81
- @tcp_socket ||= TCPSocket.new(configuration[:host], configuration[:port])
82
- end
83
90
  end
84
91
  end
85
92
  end
data/lib/pling/apn.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Pling
2
+
3
+ ##
4
+ # The {APN} module wraps Apple's Push Notification Service into an easy
5
+ # to use API. You can use instances of {Gateway} to send push notifications
6
+ # and instances of {Feedback} to get device identifiers that have been
7
+ # marked invalid.
8
+ module APN
9
+ autoload :Connection, 'pling/apn/connection'
10
+ autoload :Gateway, 'pling/apn/gateway'
11
+ autoload :Feedback, 'pling/apn/feedback'
12
+ end
13
+ end
@@ -1,16 +1,16 @@
1
1
  require 'faraday'
2
2
 
3
3
  module Pling
4
- module Gateway
4
+ module C2DM
5
5
  ##
6
6
  # Pling gateway to communicate with Google's Android C2DM service.
7
7
  #
8
8
  # The gateway is implemented using Faraday. It defaults to Faraday's :net_http adapter.
9
9
  # You can customize the adapter by passing the :adapter configuration.
10
10
  #
11
- # Example:
11
+ # @example
12
12
  #
13
- # Pling::Gateway::C2DM.new({
13
+ # Pling::C2DM::Gateway.new({
14
14
  # :email => 'your-email@gmail.com', # Your google account's email address (Required)
15
15
  # :password => 'your-password', # Your google account's password (Required)
16
16
  # :source => 'your-app-name', # Your applications source identifier (Required)
@@ -20,7 +20,9 @@ module Pling
20
20
  # :adapter => :net_http, # The Faraday adapter you want to use (Optional, Default: :net_http)
21
21
  # :connection => {} # Options you want to pass to Faraday (Optional, Default: {})
22
22
  # })
23
- class C2DM < Base
23
+ class Gateway < Pling::Gateway
24
+
25
+ handles :android, :c2dm
24
26
 
25
27
  attr_reader :token
26
28
 
@@ -39,7 +41,11 @@ module Pling
39
41
  def initialize(configuration)
40
42
  super
41
43
  require_configuration([:email, :password, :source])
42
- authenticate!
44
+ setup!
45
+ end
46
+
47
+ def setup!
48
+ authenticate! unless token
43
49
  end
44
50
 
45
51
  ##
@@ -49,14 +55,26 @@ module Pling
49
55
  # @param [#to_pling_device] device
50
56
  # @raise Pling::DeliveryFailed
51
57
  def deliver!(message, device)
52
- response = connection.post(configuration[:push_url], {
58
+ data = {
53
59
  :registration_id => device.identifier,
54
- :"data.body" => message.body,
60
+ :'data.body' => message.body,
61
+ :'data.badge' => message.badge,
62
+ :'data.sound' => message.sound,
63
+ :'data.subject' => message.subject,
55
64
  :collapse_key => message.body.hash
56
- }, { :Authorization => "GoogleLogin auth=#{@token}"})
65
+ }.delete_if { |_, value| value.nil? }
66
+
67
+ if configuration[:payload] && message.payload
68
+ message.payload.each do |key, value|
69
+ data["data.#{key}".to_sym] = value
70
+ end
71
+ end
72
+
73
+ response = connection.post(configuration[:push_url], data, { :Authorization => "GoogleLogin auth=#{@token}"})
57
74
 
58
75
  if !response.success? || response.body =~ /^Error=(.+)$/
59
- raise(Pling::DeliveryFailed, "C2DM Delivery failed: [#{response.status}] #{response.body}")
76
+ error_class = Pling::C2DM.const_get($1) rescue Pling::DeliveryFailed
77
+ raise error_class.new("C2DM Delivery failed: [#{response.status}] #{response.body}", message, device)
60
78
  end
61
79
  end
62
80
 
data/lib/pling/c2dm.rb ADDED
@@ -0,0 +1,17 @@
1
+ module Pling
2
+ ##
3
+ # This module adds support for Google's Cloud to Device Messaging (C2DM) to pling.
4
+ # Please refer to {Pling::C2DM::Gateway} for documentation.
5
+ #
6
+ # @see Pling::C2DM::Gateway
7
+ module C2DM
8
+ autoload :Gateway, 'pling/c2dm/gateway'
9
+
10
+ class QuotaExceeded < Pling::DeliveryFailed; end
11
+ class DeviceQuotaExceeded < Pling::DeliveryFailed; end
12
+ class InvalidRegistration < Pling::DeliveryFailed; end
13
+ class NotRegistered < Pling::DeliveryFailed; end
14
+ class MessageTooBig < Pling::DeliveryFailed; end
15
+ class MissingCollapseKey < Pling::DeliveryFailed; end
16
+ end
17
+ end
@@ -6,7 +6,9 @@ module Pling
6
6
 
7
7
  def initialize!
8
8
  map! do |item|
9
- item.kind_of?(Array) ? item.shift.new(*item) : item
9
+ item = item.kind_of?(Array) ? item.shift.new(*item) : item
10
+ item.setup! if item.respond_to?(:setup!)
11
+ item
10
12
  end
11
13
  end
12
14
  end
data/lib/pling/gateway.rb CHANGED
@@ -1,10 +1,34 @@
1
1
  module Pling
2
- module Gateway
3
- autoload :Base, 'pling/gateway/base'
4
- autoload :C2DM, 'pling/gateway/c2dm'
5
- autoload :APN, 'pling/gateway/apn'
2
+ ##
3
+ # This is the base class of all gateways. It defines the public interface of
4
+ # all gateways and provides helper methods to configure gateways and define
5
+ # the device types a gateway is able to handle.
6
+ #
7
+ # Gateway implementations must set the types they're able to handle by calling
8
+ # the {handles} macro.
9
+ #
10
+ # Every gateway must implement a {#deliver!} method which
11
+ # does the actual delivering.
12
+ #
13
+ # @example
14
+ #
15
+ # class Pling::Example::Gateway < Pling::Gateway
16
+ # handles :example, :foo, :bar
17
+ #
18
+ # def deliver!(message, device)
19
+ # puts "Delivering #{message.body} to #{device.identifier} (#{device.type})"
20
+ # end
21
+ # end
22
+ class Gateway
23
+ include Pling::Configurable
6
24
 
7
25
  class << self
26
+ ##
27
+ # Finds a gateway that handles the given device
28
+ #
29
+ # @param device [#to_pling_device]
30
+ # @raise [Pling::NoGatewayFound] No gateway was found that is able to handle the given device
31
+ # @return [Pling::Gateway] A gateway that handles the given device
8
32
  def discover(device)
9
33
  device = Pling._convert(device, :device)
10
34
  Pling.gateways.initialize!
@@ -12,6 +36,98 @@ module Pling
12
36
  gateway.handles?(device)
13
37
  end or raise(Pling::NoGatewayFound, "Could not find a gateway for #{device.class} with type :#{device.type}")
14
38
  end
39
+
40
+ ##
41
+ # Defines the device types a gateway is able to handle
42
+ #
43
+ # @param types [Array<#to_sym>] List of types
44
+ # @return [Array<#to_sym>] List of types
45
+ def handles(*types)
46
+ @handled_types = [types].flatten.map { |t| t.to_sym }
47
+ end
48
+
49
+ ##
50
+ # Returns a list of device types that this gateway is able to handle
51
+ #
52
+ # @return [Array<#to_sym>] List of types
53
+ def handled_types
54
+ @handled_types ||= []
55
+ end
56
+ end
57
+
58
+ ##
59
+ # Initializes a new Gateway instance.
60
+ #
61
+ # @param [Hash] config Configuration for this gateway instance
62
+ # @option config [Array] :middlewares List of middlewares to execute before delivering
63
+ # @option config [#call(exception)] :on_exception Callback to execute when an exception is raised
64
+ def initialize(config = {})
65
+ setup_configuration(config)
66
+ middlewares = configuration[:middlewares]
67
+ configuration.merge!(:middlewares => Pling::DelayedInitializer.new)
68
+ middlewares.each { |middleware| configuration[:middlewares] << middleware } if middlewares
69
+ end
70
+
71
+ ##
72
+ # Sets up this gateway
73
+ def setup!
74
+ end
75
+
76
+ ##
77
+ # Tears down this gateway
78
+ def teardown!
79
+ end
80
+
81
+ ##
82
+ # Checks if this gateway is able to handle the given device
83
+ # @param device [#to_pling_device]
84
+ # @return [Boolean]
85
+ def handles?(device)
86
+ device = Pling._convert(device, :device)
87
+ self.class.handled_types.include?(device.type)
15
88
  end
89
+
90
+ ##
91
+ # Delivers the given message to the given device using the given stack.
92
+ # If the :on_exception callback is configured it'll rescue all Pling::Errors
93
+ # and pass them to the given callback.
94
+ #
95
+ # @param message [#to_pling_message]
96
+ # @param device [#to_pling_device]
97
+ # @param stack [Array] The stack to use (Default: configuration[:middlewares])
98
+ # @raise [Pling::DeliveryError] unless configuration[:on_exception] callback is set
99
+ def deliver(message, device, stack = nil)
100
+ message = Pling._convert(message, :message)
101
+ device = Pling._convert(device, :device)
102
+
103
+ stack ||= [] + configuration[:middlewares].initialize!
104
+
105
+ return deliver!(message, device) if stack.empty?
106
+
107
+ stack.shift.deliver(message, device) do |m, d|
108
+ deliver(m, d, stack)
109
+ end
110
+ rescue Pling::Error => error
111
+ callback = configuration[:on_exception]
112
+ callback && callback.respond_to?(:call) ? callback.call(error) : raise
113
+ end
114
+
115
+ ##
116
+ # Delivers the given message to the given device without using the middleware.
117
+ #
118
+ # @param message [#to_pling_message]
119
+ # @param device [#to_pling_device]
120
+ # @raise [Pling::NotImplementedError] This method must be implemented in subclasses
121
+ def deliver!(message, device)
122
+ raise NotImplementedError, "Please implement #{self.class}#deliver!(message, device)"
123
+ end
124
+
125
+ protected
126
+
127
+ def default_configuration
128
+ {
129
+ :middlewares => Pling::DelayedInitializer.new
130
+ }
131
+ end
16
132
  end
17
133
  end