pling 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Gemfile +4 -1
- data/Guardfile +4 -0
- data/README.md +82 -21
- data/Rakefile +6 -1
- data/lib/pling.rb +86 -1
- data/lib/pling/adapter.rb +5 -0
- data/lib/pling/adapter/base.rb +17 -0
- data/lib/pling/configurable.rb +29 -0
- data/lib/pling/delayed_initializer.rb +13 -0
- data/lib/pling/device.rb +68 -0
- data/lib/pling/gateway.rb +17 -0
- data/lib/pling/gateway/apn.rb +85 -0
- data/lib/pling/gateway/base.rb +64 -0
- data/lib/pling/gateway/c2dm.rb +102 -0
- data/lib/pling/message.rb +56 -0
- data/lib/pling/middleware.rb +5 -0
- data/lib/pling/middleware/base.rb +15 -0
- data/lib/pling/version.rb +1 -1
- data/pling.gemspec +4 -4
- data/spec/pling/adapter/base_spec.rb +22 -0
- data/spec/pling/delayed_initializer_spec.rb +31 -0
- data/spec/pling/device_spec.rb +75 -0
- data/spec/pling/gateway/apn_spec.rb +103 -0
- data/spec/pling/gateway/base_spec.rb +61 -0
- data/spec/pling/gateway/c2dm_spec.rb +182 -0
- data/spec/pling/gateway_spec.rb +50 -0
- data/spec/pling/message_spec.rb +44 -0
- data/spec/pling_spec.rb +117 -0
- metadata +64 -11
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Guardfile
ADDED
data/README.md
CHANGED
@@ -1,10 +1,16 @@
|
|
1
|
-
# Pling ![Travis build status of pling](http://travis-ci.org/flinc/pling.png)
|
1
|
+
# Pling [![Travis build status of pling](http://travis-ci.org/flinc/pling.png)](http://travis-ci.org/flinc/pling)
|
2
2
|
|
3
|
-
Pling is a notification framework that supports multiple gateways.
|
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
5
|
|
6
6
|
## Requirements
|
7
7
|
|
8
|
+
This gem has two runtime dependencies
|
9
|
+
|
10
|
+
- faraday ~> 0.7
|
11
|
+
- json ~> 1.4
|
12
|
+
|
13
|
+
On JRuby it also requires the jruby-openssl gem.
|
8
14
|
|
9
15
|
|
10
16
|
## Install
|
@@ -15,27 +21,39 @@ Add this line to your `Gemfile`:
|
|
15
21
|
|
16
22
|
## Configuration
|
17
23
|
|
24
|
+
The configuration is pretty simple. Just add a configuration block like this to your code:
|
25
|
+
|
18
26
|
Pling.configure do |config|
|
19
|
-
config.gateways
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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'
|
29
|
+
|
30
|
+
# config.middleware.use Your::Custom::Middleware, :your => :custom, :configuration => true
|
31
|
+
|
32
|
+
# config.adapter = Your::Custom::Adapter.new
|
24
33
|
end
|
25
34
|
|
26
35
|
## Usage
|
27
36
|
|
37
|
+
After configuring Pling you can send messages to devices by like this:
|
38
|
+
|
39
|
+
message = Pling::Message.new("Hello from pling!")
|
40
|
+
device = Pling::Device.new(:identifier => 'XXXXXXXXXX...XXXXXX', :type => :iphone)
|
41
|
+
device.deliver(message)
|
42
|
+
|
43
|
+
# ... or call Pling.delver
|
44
|
+
Pling.deliver(message, device)
|
45
|
+
|
28
46
|
Pling has three core components:
|
29
47
|
|
30
|
-
* A
|
31
|
-
* A
|
32
|
-
* A
|
48
|
+
* A _device_ describes a concrete receiver such as a smartphone or an email address.
|
49
|
+
* A _message_ wraps the content delivered to a device.
|
50
|
+
* A _gateway_ handles the communication with the service provider used to deliver the message.
|
33
51
|
|
34
|
-
|
52
|
+
You can easily integrate pling into your existing application by implementing `#to_pling_device` on your device models and `#to_pling_message` on your message models. Use these methods to either convert your models into `Pling::Device` and `Pling::Message` objects or return `self` and make sure your models implement the basic `Pling::Device` and `Pling::Message` interfaces.
|
35
53
|
|
36
54
|
### Devices
|
37
55
|
|
38
|
-
Devices store an identifier and a type.
|
56
|
+
Devices store an identifier and a type.
|
39
57
|
|
40
58
|
Example:
|
41
59
|
|
@@ -59,31 +77,74 @@ The Gateway delivers the message in the required format to the service provider.
|
|
59
77
|
|
60
78
|
Currently there are these gateways available:
|
61
79
|
|
62
|
-
* Android C2DM
|
63
|
-
*
|
64
|
-
* SMS via Mobilant (See `pling-mobilant` gem
|
65
|
-
*
|
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)
|
82
|
+
* [SMS via Mobilant](https://github.com/flinc/pling-mobilant) (See `pling-mobilant` gem)
|
83
|
+
* [Email](https://github.com/flinc/pling-actionmailer) (See `pling-actionmailer` gem)
|
84
|
+
|
85
|
+
See the [API documentation](http://rdoc.info/github/flinc/pling) for details on the available gateways.
|
86
|
+
|
87
|
+
|
88
|
+
### Middleware
|
89
|
+
|
90
|
+
Pling has support for middlewares. Currently pling itself does not provide any middlewares but you can easily implement your own. All you need is a class that responds to `#deliver(message, device)` which yields to call the next middleware on the stack. You might just want to subclass `Pling::Middleware::Base` to get a simple configuration management.
|
91
|
+
|
92
|
+
class Pling::Middleware::TimeFilter < Pling::Middleware::Base
|
93
|
+
def deliver(message, device)
|
94
|
+
yield(message, device) if configuration[:range].include? Time.now.hour
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
def default_configuration
|
100
|
+
super.merge({
|
101
|
+
:range => 8..22
|
102
|
+
})
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
You can either add middlewares for all gateways or for specific gateways:
|
107
|
+
|
108
|
+
Pling.configure do |config|
|
109
|
+
config.gateways.use Pling::Gateway::APN, {
|
110
|
+
:certificate => '/path/to/certificate.pem',
|
111
|
+
:middlewares => [
|
112
|
+
[Pling::Middleware::TimeFilter, { :range => 9..17 }] # Don't deliver any messages to iOS devices between 9am and 5pm
|
113
|
+
]
|
114
|
+
}
|
115
|
+
|
116
|
+
# Don't deliver any messages between 8am and 10pm
|
117
|
+
config.middleware.use Pling::Middleware::TimeFilter
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
### Adapters
|
122
|
+
|
123
|
+
Pling supports different adapters. A adapter is in a way similar to a middleware but is responsible for dispatching a device and a message to a gateway.
|
124
|
+
The default adapter simply looks up the first matching gateway for the given device and calls its `#deliver(message, device)` method. Adapters are handy
|
125
|
+
when you want to add support for background queues. Have a look at [this example](https://gist.github.com/1308846) of an adapter for Resque.
|
126
|
+
|
66
127
|
|
67
128
|
## Build Status
|
68
129
|
|
69
|
-
Pling is on [Travis](http://travis-ci.org/flinc/pling) running the specs on Ruby 1.8.7, Ruby 1.9.2 and
|
130
|
+
Pling is on [Travis](http://travis-ci.org/flinc/pling) running the specs on Ruby 1.8.7, Ruby Enterprise Edition, Ruby 1.9.2, Ruby HEAD, JRuby, Rubinius and Rubinius 2.
|
70
131
|
|
71
132
|
|
72
133
|
## Known issues
|
73
134
|
|
74
|
-
See https://github.com/flinc/pling/issues
|
135
|
+
See [the issue tracker on GitHub](https://github.com/flinc/pling/issues).
|
75
136
|
|
76
137
|
|
77
138
|
## Repository
|
78
139
|
|
79
|
-
See https://github.com/flinc/pling and feel free to fork it!
|
140
|
+
See [the repository on GitHub](https://github.com/flinc/pling) and feel free to fork it!
|
80
141
|
|
81
142
|
|
82
143
|
## Contributors
|
83
144
|
|
84
|
-
See a list of all contributors
|
145
|
+
See a list of all contributors on [GitHub](https://github.com/flinc/pling/contributors). Thanks a lot everyone!
|
85
146
|
|
86
147
|
|
87
148
|
## Copyright
|
88
149
|
|
89
|
-
Copyright (c) 2010-2011 flinc AG. See LICENSE for details.
|
150
|
+
Copyright (c) 2010-2011 [flinc AG](https://flinc.org/). See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
|
3
3
|
require 'rspec/core/rake_task'
|
4
|
-
require 'yard
|
4
|
+
require 'yard'
|
5
5
|
|
6
6
|
RSpec::Core::RakeTask.new(:spec)
|
7
7
|
|
8
8
|
YARD::Rake::YardocTask.new(:doc)
|
9
9
|
|
10
10
|
task :default => :spec
|
11
|
+
|
12
|
+
desc "Open an irb session"
|
13
|
+
task :console do
|
14
|
+
sh "irb -rubygems -I lib -r pling.rb"
|
15
|
+
end
|
data/lib/pling.rb
CHANGED
@@ -1,5 +1,90 @@
|
|
1
1
|
require "pling/version"
|
2
2
|
|
3
3
|
module Pling
|
4
|
-
|
4
|
+
|
5
|
+
autoload :Device, 'pling/device'
|
6
|
+
autoload :Message, 'pling/message'
|
7
|
+
autoload :Gateway, 'pling/gateway'
|
8
|
+
autoload :Middleware, 'pling/middleware'
|
9
|
+
autoload :Adapter, 'pling/adapter'
|
10
|
+
autoload :Configurable, 'pling/configurable'
|
11
|
+
autoload :DelayedInitializer, 'pling/delayed_initializer'
|
12
|
+
|
13
|
+
@gateways = Pling::DelayedInitializer.new
|
14
|
+
@middlewares = Pling::DelayedInitializer.new
|
15
|
+
@adapter = Pling::Adapter::Base.new
|
16
|
+
|
17
|
+
class Error < StandardError; end
|
18
|
+
class AuthenticationFailed < Error; end
|
19
|
+
class DeliveryFailed < Error; end
|
20
|
+
class NoGatewayFound < Error; end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
##
|
24
|
+
# Stores the list of available gateway instances
|
25
|
+
#
|
26
|
+
# @return [Array] list of available gateways
|
27
|
+
attr_reader :gateways
|
28
|
+
|
29
|
+
def gateways=(gateways)
|
30
|
+
gateways.each { |gateway| @gateways << gateway }
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Stores the list of avaiable middleware instances
|
35
|
+
#
|
36
|
+
# @return [Array] list of available middleware
|
37
|
+
attr_reader :middlewares
|
38
|
+
|
39
|
+
def middlewares=(middlewares)
|
40
|
+
middlewares.each { |middleware| @middlewares << middleware }
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Stores the adapter
|
45
|
+
#
|
46
|
+
# @return [Pling::Adapter]
|
47
|
+
attr_accessor :adapter
|
48
|
+
|
49
|
+
##
|
50
|
+
# Allows configuration of Pling by passing a config object to the given block
|
51
|
+
#
|
52
|
+
# @yield [config]
|
53
|
+
# @raise [ArgumentError] Raised when no block is given
|
54
|
+
def configure
|
55
|
+
raise ArgumentError, 'No block given for Pling.configure' unless block_given?
|
56
|
+
yield self
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Delivers the given message to the given device using the given stack.
|
61
|
+
#
|
62
|
+
# @param message [#to_pling_message]
|
63
|
+
# @param device [#to_pling_device]
|
64
|
+
# @param stack [Array] The stack to use (Default: middlewares + [adapter])
|
65
|
+
def deliver(message, device, stack = nil)
|
66
|
+
message = Pling._convert(message, :message)
|
67
|
+
device = Pling._convert(device, :device)
|
68
|
+
|
69
|
+
stack ||= middlewares.initialize! + [adapter]
|
70
|
+
|
71
|
+
stack.shift.deliver(message, device) do |m, d|
|
72
|
+
deliver(m, d, stack)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# [INTERNAL METHOD] Converts the given object to the given pling type
|
78
|
+
#
|
79
|
+
# @private
|
80
|
+
# @param object [Object] The object that needs to be converted
|
81
|
+
# @param type [Symbol, String] #to_pling_ method suffix
|
82
|
+
# @raise [ArgumentError] The object does not implement a #to_pling_ + type method
|
83
|
+
def _convert(object, type)
|
84
|
+
method = :"to_pling_#{type}"
|
85
|
+
raise ArgumentError, "Instances of #{object.class} do not implement ##{method}" unless object.respond_to?(method)
|
86
|
+
object && object.send(method)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
5
90
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Pling
|
2
|
+
module Adapter
|
3
|
+
class Base
|
4
|
+
include Pling::Configurable
|
5
|
+
|
6
|
+
def initialize(configuration = {})
|
7
|
+
setup_configuration(configuration)
|
8
|
+
end
|
9
|
+
|
10
|
+
def deliver(message, device)
|
11
|
+
gateway = Pling::Gateway.discover(device)
|
12
|
+
gateway.deliver(message, device)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Pling
|
2
|
+
module Configurable
|
3
|
+
|
4
|
+
protected
|
5
|
+
|
6
|
+
def configuration
|
7
|
+
@configuration ||= default_configuration
|
8
|
+
end
|
9
|
+
|
10
|
+
def default_configuration
|
11
|
+
{}
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup_configuration(config = {}, opts = {})
|
15
|
+
config.each_pair do |key, value|
|
16
|
+
configuration[key.to_sym] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
require_configuration(opts[:require] || [])
|
20
|
+
end
|
21
|
+
|
22
|
+
def require_configuration(keys, message = nil)
|
23
|
+
[keys].flatten.each do |key|
|
24
|
+
raise(ArgumentError, message || "Option :#{key} is missing") unless configuration.key?(key.to_sym)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/pling/device.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module Pling
|
2
|
+
class Device
|
3
|
+
|
4
|
+
##
|
5
|
+
# The device identifier
|
6
|
+
#
|
7
|
+
# @overload identifier
|
8
|
+
# @overload identifier=(identifier)
|
9
|
+
# @param [#to_s] identifier
|
10
|
+
attr_reader :identifier
|
11
|
+
|
12
|
+
def identifier=(identifier)
|
13
|
+
identifier &&= identifier.to_s
|
14
|
+
@identifier = identifier
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# The device type
|
19
|
+
#
|
20
|
+
# @overload type
|
21
|
+
# @overload type=(type)
|
22
|
+
# @param [#to_sym] type
|
23
|
+
attr_reader :type
|
24
|
+
|
25
|
+
def type=(type)
|
26
|
+
type &&= type.to_sym
|
27
|
+
@type = type
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Creates a new Pling::Device instance with the given identifier and type
|
32
|
+
#
|
33
|
+
# @param [Hash] attributes
|
34
|
+
# @option attributes [#to_s] :identifier
|
35
|
+
# @option attributes [#to_sym] :type
|
36
|
+
def initialize(attributes = {})
|
37
|
+
attributes.each_pair do |key, value|
|
38
|
+
method = "#{key}="
|
39
|
+
send(method, value) if respond_to?(method)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# A device is valid if it has a type and an identifier.
|
45
|
+
#
|
46
|
+
# @return [Boolean]
|
47
|
+
def valid?
|
48
|
+
!!(type && identifier)
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Delivers the given message using an appropriate gateway.
|
53
|
+
#
|
54
|
+
# @param [#to_pling_message] message
|
55
|
+
def deliver(message)
|
56
|
+
Pling.deliver(message, self)
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Returns the object itself as it is already a Pling::Device.
|
61
|
+
#
|
62
|
+
# @return [Pling::Device]
|
63
|
+
def to_pling_device
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Pling
|
2
|
+
module Gateway
|
3
|
+
autoload :Base, 'pling/gateway/base'
|
4
|
+
autoload :C2DM, 'pling/gateway/c2dm'
|
5
|
+
autoload :APN, 'pling/gateway/apn'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def discover(device)
|
9
|
+
device = Pling._convert(device, :device)
|
10
|
+
Pling.gateways.initialize!
|
11
|
+
Pling.gateways.detect do |gateway|
|
12
|
+
gateway.handles?(device)
|
13
|
+
end or raise(Pling::NoGatewayFound, "Could not find a gateway for #{device.class} with type :#{device.type}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|