pling 0.1.0 → 0.2.0

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