bpoweski-apnserver 0.0.2 → 0.0.6

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/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