bpoweski-apnserver 0.0.2 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ === 0.0.1 2009-09-10
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,23 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README
5
+ README.rdoc
6
+ Rakefile
7
+ bin/apns
8
+ bin/apnserverd
9
+ lib/apnserver.rb
10
+ lib/apnserver/client.rb
11
+ lib/apnserver/notification.rb
12
+ lib/apnserver/payload.rb
13
+ lib/apnserver/server.rb
14
+ script/console
15
+ script/console.cmd
16
+ script/destroy
17
+ script/destroy.cmd
18
+ script/generate
19
+ script/generate.cmd
20
+ test/notification_test.rb
21
+ test/payload_test.rb
22
+ test/test_apnserver.rb
23
+ test/test_helper.rb
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = "apnserver"
7
+ gemspec.summary = "Apple Push Notification Server"
8
+ gemspec.description = "A toolkit for proxying and sending Apple Push Notifications"
9
+ gemspec.email = "bpoweski@3factors.com"
10
+ gemspec.homepage = "http://github.com/bpoweski/apnserver"
11
+ gemspec.authors = ["Ben Poweski"]
12
+ gemspec.add_dependency 'eventmachine'
13
+ gemspec.add_dependency 'daemons'
14
+ gemspec.files = FileList['lib/**/*.rb', 'bin/*', '[A-Z]*', 'test/**/*'].to_a
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.test_files = FileList.new('test/**/test_*.rb') do |list|
23
+ list.exclude 'test/test_helper.rb'
24
+ end
25
+ test.libs << 'test'
26
+ test.verbose = true
27
+ end
28
+
29
+ task :default => [:test]
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.6
data/apnserver.gemspec ADDED
@@ -0,0 +1,70 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{apnserver}
8
+ s.version = "0.0.6"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ben Poweski"]
12
+ s.date = %q{2009-09-14}
13
+ s.description = %q{A toolkit for proxying and sending Apple Push Notifications}
14
+ s.email = %q{bpoweski@3factors.com}
15
+ s.executables = ["apns", "apnsend", "apnserverd"]
16
+ s.extra_rdoc_files = [
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ "History.txt",
21
+ "Manifest.txt",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "apnserver.gemspec",
26
+ "bin/apns",
27
+ "bin/apnsend",
28
+ "bin/apnserverd",
29
+ "lib/apnserver.rb",
30
+ "lib/apnserver/client.rb",
31
+ "lib/apnserver/notification.rb",
32
+ "lib/apnserver/payload.rb",
33
+ "lib/apnserver/protocol.rb",
34
+ "lib/apnserver/server.rb",
35
+ "lib/apnserver/server_connection.rb",
36
+ "test/test_client.rb",
37
+ "test/test_helper.rb",
38
+ "test/test_notification.rb",
39
+ "test/test_payload.rb",
40
+ "test/test_protocol.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/bpoweski/apnserver}
43
+ s.rdoc_options = ["--charset=UTF-8"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.5}
46
+ s.summary = %q{Apple Push Notification Server}
47
+ s.test_files = [
48
+ "test/test_client.rb",
49
+ "test/test_helper.rb",
50
+ "test/test_notification.rb",
51
+ "test/test_payload.rb",
52
+ "test/test_protocol.rb"
53
+ ]
54
+
55
+ if s.respond_to? :specification_version then
56
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
57
+ s.specification_version = 3
58
+
59
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
60
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
61
+ s.add_runtime_dependency(%q<daemons>, [">= 0"])
62
+ else
63
+ s.add_dependency(%q<eventmachine>, [">= 0"])
64
+ s.add_dependency(%q<daemons>, [">= 0"])
65
+ end
66
+ else
67
+ s.add_dependency(%q<eventmachine>, [">= 0"])
68
+ s.add_dependency(%q<daemons>, [">= 0"])
69
+ end
70
+ end
data/bin/apns CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
2
4
  require 'socket'
3
5
  require 'base64'
4
6
  require 'rubygems'
data/bin/apnsend CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
2
4
  require 'getoptlong'
3
5
  require 'rubygems'
4
6
  require 'apnserver'
data/bin/apnserverd CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
2
4
  require 'getoptlong'
3
5
  require 'rubygems'
4
- require 'apnserver'
5
6
  require 'daemons'
7
+ require 'apnserver'
6
8
 
7
9
  def usage
8
10
  puts "Usage: apnserverd [switches] --pem <path>"
@@ -47,10 +49,15 @@ end
47
49
 
48
50
  if pem.nil?
49
51
  usage
52
+ exit 1
50
53
  else
51
- # Become a daemon
52
- Daemons.daemonize
53
-
54
+ Daemonize.daemonize('apnserverd', 'apnserverd')
55
+ @pid_file = '/var/run/apnserverd.pid'
56
+ open(@pid_file,"w") {|f| f.write(Process.pid) } # copied from mongrel
57
+ open(@pid_file,"w") do |f|
58
+ f.write(Process.pid)
59
+ File.chmod(0644, @pid_file)
60
+ end
54
61
  server = ApnServer::Server.new(pem, bind_address, proxy_port)
55
62
  server.start!
56
63
  end
data/lib/apnserver.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'eventmachine'
2
+ require 'apnserver/payload'
3
+ require 'apnserver/notification'
4
+ require 'apnserver/protocol'
5
+ require 'apnserver/client'
6
+ require 'apnserver/server_connection'
7
+ require 'apnserver/server'
@@ -0,0 +1,42 @@
1
+ require 'openssl'
2
+ require 'socket'
3
+
4
+ module ApnServer
5
+ class Client
6
+
7
+ attr_accessor :pem, :host, :port, :password
8
+
9
+ def initialize(pem, host = 'gateway.push.apple.com', port = 2195, pass = nil)
10
+ @pem, @host, @port, @password = pem, host, port, pass
11
+ end
12
+
13
+ def connect!
14
+ raise "The path to your pem file is not set." unless self.pem
15
+ raise "The path to your pem file does not exist!" unless File.exist?(self.pem)
16
+
17
+ @context = OpenSSL::SSL::SSLContext.new
18
+ @context.cert = OpenSSL::X509::Certificate.new(File.read(self.pem))
19
+ @context.key = OpenSSL::PKey::RSA.new(File.read(self.pem), self.password)
20
+
21
+ @sock = TCPSocket.new(self.host, self.port)
22
+ @ssl = OpenSSL::SSL::SSLSocket.new(@sock, @context)
23
+ @ssl.connect
24
+
25
+ return @sock, @ssl
26
+ end
27
+
28
+ def disconnect!
29
+ @ssl.close
30
+ @sock.close
31
+ end
32
+
33
+ def write(notification)
34
+ puts "#{Time.now} [#{host}:#{port}] sending #{notification.alert}"
35
+ @ssl.write(notification.to_bytes)
36
+ end
37
+
38
+ def connected?
39
+ @ssl
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,63 @@
1
+ require 'apnserver/payload'
2
+ require 'json'
3
+
4
+ module ApnServer
5
+
6
+ class Notification
7
+ include ApnServer::Payload
8
+
9
+ attr_accessor :device_token, :alert, :badge, :sound, :custom
10
+
11
+ def payload
12
+ p = Hash.new
13
+ [:badge, :alert, :sound, :custom].each do |k|
14
+ p[k] = send(k) if send(k)
15
+ end
16
+ create_payload(p)
17
+ end
18
+
19
+ def json_payload
20
+ p = JSON.generate(payload)
21
+ raise PayloadInvalid.new("Payload of #{p.size} is longer than 256") if p.size > 256
22
+ p
23
+ end
24
+
25
+ def to_bytes
26
+ json = json_payload
27
+ [0, 0, device_token.size, device_token, 0, json.size, json].pack("ccca*cca*")
28
+ end
29
+
30
+ def self.valid?(payload)
31
+ begin
32
+ Notification.parse(payload)
33
+ rescue RuntimeError
34
+ false
35
+ end
36
+ end
37
+
38
+ def self.parse(p)
39
+ buffer = p.dup
40
+ notification = Notification.new
41
+
42
+ header = buffer.slice!(0, 3).unpack('ccc')
43
+ if header[0] != 0 || header[1] != 0 || header[2] != 32
44
+ raise RuntimeError.new("Header of notification is invalid: #{header.inspect}")
45
+ end
46
+
47
+ # parse token
48
+ notification.device_token = buffer.slice!(0, 32).unpack('a*').first
49
+
50
+ # parse json payload
51
+ payload_len = buffer.slice!(0, 2).unpack('cc')
52
+ result = JSON.parse(buffer.slice!(0, payload_len.last))
53
+
54
+ ['alert', 'badge', 'sound'].each do |k|
55
+ notification.send("#{k}=", result['aps'][k]) if result['aps'] && result['aps'][k]
56
+ end
57
+ result.delete('aps')
58
+ notification.custom = result
59
+
60
+ notification
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ module ApnServer
2
+
3
+ module Payload
4
+
5
+ class PayloadInvalid < RuntimeError
6
+ end
7
+
8
+ def create_payload(payload)
9
+ case payload
10
+ when String then { :aps => { :alert => payload } }
11
+ when Hash then create_payload_from_hash(payload)
12
+ end
13
+ end
14
+
15
+ def create_payload_from_hash(payload)
16
+ custom = payload.delete(:custom)
17
+ aps = {:aps => payload }
18
+ aps.merge!(custom) if custom
19
+ aps
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,21 @@
1
+ module ApnServer
2
+ module Protocol
3
+
4
+ def post_init
5
+ @address = Socket.unpack_sockaddr_in(self.get_peername)
6
+ puts "#{Time.now} [#{address.last}:#{address.first}] CONNECT"
7
+ end
8
+
9
+ def unbind
10
+ puts "#{Time.now} [#{address.last}:#{address.first}] DISCONNECT"
11
+ end
12
+
13
+ def receive_data(data)
14
+ (@buf ||= "") << data
15
+ if notification = ApnServer::Notification.valid?(@buf)
16
+ queue.push(notification)
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ module ApnServer
2
+
3
+ class Server
4
+ attr_accessor :client, :bind_address, :port
5
+
6
+ def initialize(pem, bind_address = '0.0.0.0', port = 22195)
7
+ @queue = EM::Queue.new
8
+ @client = ApnServer::Client.new(pem)
9
+ @bind_address, @port = bind_address, port
10
+ end
11
+
12
+ def start!
13
+ EventMachine::run do
14
+ puts "#{Time.now} Starting APN Server on #{bind_address}:#{port}"
15
+
16
+ EM.start_server(bind_address, port, ApnServer::ServerConnection) do |s|
17
+ s.queue = @queue
18
+ end
19
+
20
+ EventMachine::PeriodicTimer.new(1) do
21
+ unless @queue.empty?
22
+ size = @queue.size
23
+ size.times do
24
+ @queue.pop do |notification|
25
+ @client.connect! unless @client.connected?
26
+ @client.write(notification)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ require 'socket'
2
+ require 'apnserver/protocol'
3
+
4
+ module ApnServer
5
+ class ServerConnection < EventMachine::Connection
6
+ include Protocol
7
+ attr_accessor :queue, :address
8
+ end
9
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bpoweski-apnserver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Poweski
@@ -43,10 +43,29 @@ extensions: []
43
43
  extra_rdoc_files:
44
44
  - README.rdoc
45
45
  files:
46
+ - History.txt
47
+ - Manifest.txt
46
48
  - README.rdoc
47
- has_rdoc: true
49
+ - Rakefile
50
+ - VERSION
51
+ - apnserver.gemspec
52
+ - bin/apns
53
+ - bin/apnsend
54
+ - bin/apnserverd
55
+ - lib/apnserver.rb
56
+ - lib/apnserver/client.rb
57
+ - lib/apnserver/notification.rb
58
+ - lib/apnserver/payload.rb
59
+ - lib/apnserver/protocol.rb
60
+ - lib/apnserver/server.rb
61
+ - lib/apnserver/server_connection.rb
62
+ - test/test_client.rb
63
+ - test/test_helper.rb
64
+ - test/test_notification.rb
65
+ - test/test_payload.rb
66
+ - test/test_protocol.rb
67
+ has_rdoc: false
48
68
  homepage: http://github.com/bpoweski/apnserver
49
- licenses:
50
69
  post_install_message:
51
70
  rdoc_options:
52
71
  - --charset=UTF-8
@@ -67,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
86
  requirements: []
68
87
 
69
88
  rubyforge_project:
70
- rubygems_version: 1.3.5
89
+ rubygems_version: 1.2.0
71
90
  signing_key:
72
91
  specification_version: 3
73
92
  summary: Apple Push Notification Server