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 +1 -0
- data/Guardfile +4 -0
- data/README.md +8 -7
- data/lib/pling/adapter/base.rb +2 -0
- data/lib/pling/apn/connection.rb +122 -0
- data/lib/pling/apn/feedback.rb +88 -0
- data/lib/pling/{gateway/apn.rb → apn/gateway.rb} +38 -31
- data/lib/pling/apn.rb +13 -0
- data/lib/pling/{gateway/c2dm.rb → c2dm/gateway.rb} +27 -9
- data/lib/pling/c2dm.rb +17 -0
- data/lib/pling/delayed_initializer.rb +3 -1
- data/lib/pling/gateway.rb +120 -4
- data/lib/pling/message.rb +52 -0
- data/lib/pling/middleware/base.rb +31 -0
- data/lib/pling/version.rb +1 -1
- data/lib/pling.rb +24 -2
- data/pling.gemspec +1 -1
- data/spec/pling/.DS_Store +0 -0
- data/spec/pling/adapter/.DS_Store +0 -0
- data/spec/pling/apn/connection_spec.rb +124 -0
- data/spec/pling/apn/feedback_spec.rb +44 -0
- data/spec/pling/apn/gateway_spec.rb +141 -0
- data/spec/pling/{gateway/c2dm_spec.rb → c2dm/gateway_spec.rb} +79 -20
- data/spec/pling/gateway_spec.rb +65 -3
- data/spec/pling/message_spec.rb +25 -0
- data/spec/pling_spec.rb +9 -0
- metadata +31 -22
- data/lib/pling/gateway/base.rb +0 -64
- data/spec/pling/gateway/apn_spec.rb +0 -103
- data/spec/pling/gateway/base_spec.rb +0 -61
data/Gemfile
CHANGED
data/Guardfile
CHANGED
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
|
28
|
-
config.gateways.use Pling::Gateway
|
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.
|
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
|
81
|
-
* [Apple Push Notification](http://rdoc.info/github/flinc/pling/master/Pling/Gateway
|
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://
|
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
|
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
|
data/lib/pling/adapter/base.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 ||=
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
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
|
-
#
|
11
|
+
# @example
|
12
12
|
#
|
13
|
-
# Pling::Gateway
|
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
|
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
|
-
|
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
|
-
|
58
|
+
data = {
|
53
59
|
:registration_id => device.identifier,
|
54
|
-
:
|
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
|
-
}
|
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
|
-
|
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
|
data/lib/pling/gateway.rb
CHANGED
@@ -1,10 +1,34 @@
|
|
1
1
|
module Pling
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|