apple_push 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ *.tmproj
5
+ *~
6
+ .DS_Store
7
+ .\#*
8
+ .bundle
9
+ .config
10
+ .yardoc
11
+ Gemfile.lock
12
+ InstalledFiles
13
+ \#*
14
+ _yardoc
15
+ coverage
16
+ doc/
17
+ lib/bundler/man
18
+ pkg
19
+ rdoc
20
+ spec/reports
21
+ test/tmp
22
+ test/version_tmp
23
+ tmp
24
+ tmtags
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # ApplePush
2
+
3
+ Sinatra-based API service to send Apple Push Notifications (APN)
4
+
5
+ ## Why?
6
+
7
+ The difference between most of other APN daemons/libraries is that
8
+ ApplePush provides a simple HTTP API and also could be configured to use
9
+ both live and sandbox environments with configurable connections pools.
10
+
11
+ ## Installation
12
+
13
+ Install via rubybems:
14
+
15
+ ```
16
+ gem install apple_push
17
+ ```
18
+
19
+ Or clone the repo and run:
20
+
21
+ ```
22
+ bundle install
23
+ rake install
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ Create a YAML configuration file:
29
+
30
+ ```yml
31
+ host: 127.0.0.1
32
+ port: 27000
33
+
34
+ sandbox:
35
+ cert: path/to/your/certificate.pem
36
+ key: path/to/your/key.pem
37
+ pool: 1
38
+
39
+ live:
40
+ cert: path/to/your/certificate.pem
41
+ key: path/to/your/key.pem
42
+ pool: 1
43
+ ```
44
+
45
+ To start server run:
46
+
47
+ ```
48
+ apple_push path/to/your/config.yml
49
+ ```
50
+
51
+ ### Using with foreman
52
+
53
+ If you want to use apple_push server with foreman, you'll need to create a ```Procfile```:
54
+
55
+ ```
56
+ apn: apple_push config/apn.yml
57
+ ```
58
+
59
+ ## API
60
+
61
+ ApplePush server provides the following routes to send notifications:
62
+
63
+ ```
64
+ POST /live
65
+ POST /sandbox
66
+ ```
67
+
68
+ Test with curl:
69
+
70
+ ```bash
71
+ curl -X POST -d '{"alert":"Test Message"}' "http://localhost:27000/live?token=TOKEN"
72
+ ```
73
+
74
+ Also, check out an example with ruby and faraday at ```examples/apple_push_client.rb```
75
+
76
+ ## License
77
+
78
+ Copyright (c) 2012 Dan Sosedoff.
79
+
80
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
81
+
82
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
83
+
84
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
@@ -0,0 +1,26 @@
1
+ require File.expand_path('../lib/apple_push/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "apple_push"
5
+ s.version = ApplePush::VERSION.dup
6
+ s.summary = "Simple API to send APN notifications"
7
+ s.description = "Sinatra-based server to deliver Apple Push Notifications"
8
+ s.homepage = "http://github.com/doejo/apple_push"
9
+ s.authors = ["Dan Sosedoff"]
10
+ s.email = ["dan.sosedoff@gmail.com"]
11
+
12
+ s.add_runtime_dependency 'eventmachine', '>= 0'
13
+ s.add_runtime_dependency 'sinatra', '>= 0'
14
+ s.add_runtime_dependency 'thin', '>= 0'
15
+ s.add_runtime_dependency 'connection_pool', '>= 0'
16
+ s.add_runtime_dependency 'multi_json', '>= 0'
17
+ s.add_runtime_dependency 'hashr', '>= 0'
18
+ s.add_runtime_dependency 'em-apn', '>= 0'
19
+
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
24
+ s.require_paths = ["lib"]
25
+ s.default_executable = 'apple_push'
26
+ end
data/bin/apple_push ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'eventmachine'
7
+ require 'thin'
8
+ require 'apple_push'
9
+
10
+ config_path = ARGV.shift.to_s.strip
11
+
12
+ if config_path.empty?
13
+ STDERR.puts "Error: Configuration file required!"
14
+ exit(1)
15
+ end
16
+
17
+ config = nil
18
+ begin
19
+ config = ApplePush::Configuration.load_file(config_path)
20
+ rescue ArgumentError => ex
21
+ STDERR.puts "Error: #{ex.message}"
22
+ puts ex.backtrace
23
+ exit(1)
24
+ end
25
+
26
+ EM.run do
27
+ begin
28
+ ApplePush.configure(config)
29
+ rescue Exception => ex
30
+ STDERR.puts "Error: #{ex.message}"
31
+ exit(1)
32
+ end
33
+
34
+ host = ApplePush.configuration[:host]
35
+ port = ApplePush.configuration[:port]
36
+
37
+ Thin::Server.start(ApplePush::Server, host, port)
38
+ end
@@ -0,0 +1,34 @@
1
+ require 'faraday'
2
+ require 'multi_json'
3
+
4
+ class PushClient
5
+ attr_reader :host
6
+
7
+ def initialize(host)
8
+ @host = host
9
+ end
10
+
11
+ def connection(host)
12
+ conn = Faraday::Connection.new(host) do |c|
13
+ c.use(Faraday::Request::UrlEncoded)
14
+ c.adapter(Faraday.default_adapter)
15
+ end
16
+ end
17
+
18
+ def notify(env, token, data)
19
+ params = {:token => token}
20
+ body = MultiJson.encode(data)
21
+ path = env == 'live' ? '/live' : '/sandbox'
22
+
23
+ response = connection(host).send(:post) do |request|
24
+ request.path = path
25
+ request.params = params
26
+ request.body = body
27
+ end
28
+
29
+ response.body
30
+ end
31
+ end
32
+
33
+ client = PushClient.new('http://localhost:27000')
34
+ client.notify('live', 'TOKEN', {:alert => "Test Message"})
@@ -0,0 +1,19 @@
1
+ require 'hashr'
2
+ require 'yaml'
3
+
4
+ module ApplePush
5
+ class Configuration < Hashr
6
+ # Load a configuration options from file
7
+ #
8
+ # path - Path yo YAML file
9
+ #
10
+ def self.load_file(path)
11
+ path = File.expand_path(path)
12
+ if !File.exists?(path)
13
+ raise ArgumentError, "File \"#{path}\" does not exist."
14
+ end
15
+ data = YAML.load_file(path)
16
+ Configuration.new(data)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,68 @@
1
+ require 'sinatra/base'
2
+ require 'multi_json'
3
+ require 'apple_push/version'
4
+
5
+ module ApplePush
6
+ class Server < Sinatra::Base
7
+ set :environment, ENV['RACK_ENV'] || 'production'
8
+
9
+ helpers do
10
+ def json_response(data)
11
+ MultiJson.encode(data)
12
+ end
13
+
14
+ def error_response(error, status=400)
15
+ halt(400, json_response(:error => error))
16
+ end
17
+
18
+ def process_payload
19
+ @token = params[:token].to_s.strip
20
+ @payload = request.body.read
21
+
22
+ error_response("APN token required") if @token.empty?
23
+ error_response("Payload required") if @payload.empty?
24
+
25
+ begin
26
+ @payload = MultiJson.decode(@payload)
27
+ rescue MultiJson::DecodeError
28
+ error_response("Invalid payload. Should be JSON", 422)
29
+ end
30
+ end
31
+
32
+ def deliver(env, token, payload={})
33
+ unless ApplePush.pool?(env)
34
+ error_response("#{env.capitalize} is not configured.")
35
+ end
36
+
37
+ notification = EM::APN::Notification.new(token, payload)
38
+ ApplePush.pool(env).with_connection do |apn|
39
+ apn.deliver(notification)
40
+ end
41
+ end
42
+ end
43
+
44
+ before do
45
+ content_type :json, :encoding => :utf8
46
+ end
47
+
48
+ error do
49
+ err = env['sinatra.error']
50
+ content_type :json, :encoding => :utf8
51
+ json_response(:error => {:message => err.message, :type => err.class.to_s})
52
+ end
53
+
54
+ not_found do
55
+ json_response(:error => "Invalid request path")
56
+ end
57
+
58
+ get '/' do
59
+ "{\"version\":\"#{ApplePush::VERSION}\"}"
60
+ end
61
+
62
+ post %r{/(sandbox|live)} do |mode|
63
+ process_payload
64
+ deliver(mode, @token, @payload)
65
+ '{"delivered":true}'
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,3 @@
1
+ module ApplePush
2
+ VERSION = '0.1.1'
3
+ end
data/lib/apple_push.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'connection_pool'
2
+ require 'em-apn'
3
+
4
+ require 'apple_push/configuration'
5
+ require 'apple_push/server'
6
+
7
+ module ApplePush
8
+ LIVE_URL = 'gateway.push.apple.com'
9
+ SANDBOX_URL = 'gateway.sandbox.push.apple.com'
10
+
11
+ @@options = {}
12
+ @@pools = {}
13
+
14
+ def self.configure(options={})
15
+ config = ApplePush::Configuration.new(options)
16
+
17
+ @@options.merge!(
18
+ :host => config.host || '127.0.0.1',
19
+ :port => config.port || '27000'
20
+ )
21
+
22
+ if !config.sandbox? && !config.live?
23
+ raise ArgumentError, "At least one environment should be defined."
24
+ end
25
+
26
+ self.setup_environment('sandbox', config.sandbox) if config.sandbox?
27
+ self.setup_environment('live', config.live) if config.live?
28
+ end
29
+
30
+ # Get current configuration options
31
+ #
32
+ def self.configuration
33
+ @@options
34
+ end
35
+
36
+ # Get a connection from pool
37
+ #
38
+ def self.pool(env='sandbox')
39
+ @@pools[env]
40
+ end
41
+
42
+ # Returns true if pools is configured
43
+ #
44
+ def self.pool?(env)
45
+ @@pools.key?(env)
46
+ end
47
+
48
+ private
49
+
50
+ # Configure an environment
51
+ #
52
+ def self.setup_environment(env, config)
53
+ raise ArgumentError, "Path to certificate file required." if !config.cert?
54
+ raise ArgumentError, "Path to key file required." if !config.cert_key?
55
+
56
+ pool_size = Integer(config.pool || 1)
57
+
58
+ @@pools[env] = ConnectionPool.new(:size => pool_size, :timeout => 5) do
59
+ EM::APN::Client.connect(
60
+ :gateway => env == 'live' ? LIVE_URL : SANDBOX_URL,
61
+ :cert => File.expand_path(config.cert),
62
+ :key => File.expand_path(config.cert_key)
63
+ )
64
+ end
65
+ end
66
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apple_push
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dan Sosedoff
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-22 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: &70349632289120 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70349632289120
25
+ - !ruby/object:Gem::Dependency
26
+ name: sinatra
27
+ requirement: &70349632287660 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70349632287660
36
+ - !ruby/object:Gem::Dependency
37
+ name: thin
38
+ requirement: &70349632284240 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70349632284240
47
+ - !ruby/object:Gem::Dependency
48
+ name: connection_pool
49
+ requirement: &70349632283260 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70349632283260
58
+ - !ruby/object:Gem::Dependency
59
+ name: multi_json
60
+ requirement: &70349632226680 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70349632226680
69
+ - !ruby/object:Gem::Dependency
70
+ name: hashr
71
+ requirement: &70349632225640 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *70349632225640
80
+ - !ruby/object:Gem::Dependency
81
+ name: em-apn
82
+ requirement: &70349632224840 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: *70349632224840
91
+ description: Sinatra-based server to deliver Apple Push Notifications
92
+ email:
93
+ - dan.sosedoff@gmail.com
94
+ executables:
95
+ - apple_push
96
+ extensions: []
97
+ extra_rdoc_files: []
98
+ files:
99
+ - .gitignore
100
+ - Gemfile
101
+ - README.md
102
+ - Rakefile
103
+ - apple_push.gemspec
104
+ - bin/apple_push
105
+ - examples/apple_push_client.rb
106
+ - lib/apple_push.rb
107
+ - lib/apple_push/configuration.rb
108
+ - lib/apple_push/server.rb
109
+ - lib/apple_push/version.rb
110
+ homepage: http://github.com/doejo/apple_push
111
+ licenses: []
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ! '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 1.8.10
131
+ signing_key:
132
+ specification_version: 3
133
+ summary: Simple API to send APN notifications
134
+ test_files: []
135
+ has_rdoc: