pling 0.0.1 → 0.1.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/.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 
|
1
|
+
# Pling [](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
|