mobile_notify 0.1
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/Rakefile +54 -0
- data/bin/apns_ping +29 -0
- data/lib/mobile_notify.rb +8 -0
- data/lib/mobile_notify/apns/connection.rb +124 -0
- data/lib/mobile_notify/apns/notification.rb +75 -0
- data/lib/mobile_notify/version.rb +3 -0
- metadata +84 -0
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "pathname"
|
3
|
+
require "rake"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
# task :default => [:test]
|
7
|
+
# Rake::TestTask.new do |t|
|
8
|
+
# t.libs << "test"
|
9
|
+
# t.test_files = FileList['test/**/*_test.rb']
|
10
|
+
# t.verbose = true
|
11
|
+
# end
|
12
|
+
|
13
|
+
# Gem
|
14
|
+
require "rake/gempackagetask"
|
15
|
+
require "lib/mobile_notify/version"
|
16
|
+
|
17
|
+
NAME = "mobile_notify"
|
18
|
+
SUMMARY = "Mobile Notification Services w/ support for the Apple Push Notification Service (APNS)"
|
19
|
+
GEM_VERSION = MobileNotify::VERSION
|
20
|
+
|
21
|
+
spec = Gem::Specification.new do |s|
|
22
|
+
s.name = NAME
|
23
|
+
s.summary = s.description = SUMMARY
|
24
|
+
s.author = "Scott Bauer"
|
25
|
+
s.homepage = "http://rdoc.info/projects/Bauerpauer/mobile_notify"
|
26
|
+
s.version = GEM_VERSION
|
27
|
+
s.platform = Gem::Platform::RUBY
|
28
|
+
s.require_path = 'lib'
|
29
|
+
s.files = %w(Rakefile) + Dir.glob("lib/**/*")
|
30
|
+
s.executables = ['apns_ping']
|
31
|
+
|
32
|
+
s.add_dependency "json"
|
33
|
+
end
|
34
|
+
|
35
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
36
|
+
pkg.gem_spec = spec
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "Install #{NAME} as a gem"
|
40
|
+
task :install => [:repackage] do
|
41
|
+
sh %{gem install pkg/#{NAME}-#{GEM_VERSION}}
|
42
|
+
end
|
43
|
+
|
44
|
+
task :version do
|
45
|
+
puts GEM_VERSION
|
46
|
+
end
|
47
|
+
|
48
|
+
spec_file = ".gemspec"
|
49
|
+
desc "Create #{spec_file}"
|
50
|
+
task :gemspec do
|
51
|
+
File.open(spec_file, "w") do |file|
|
52
|
+
file.puts spec.to_ruby
|
53
|
+
end
|
54
|
+
end
|
data/bin/apns_ping
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
|
5
|
+
gem "mobile_notify"
|
6
|
+
require "mobile_notify"
|
7
|
+
|
8
|
+
gateway_uri = if ARGV[0].to_s.downcase == '-p'
|
9
|
+
ARGV.shift
|
10
|
+
MobileNotify::Apns::PRODUCTION_GATEWAY_URI
|
11
|
+
else
|
12
|
+
MobileNotify::Apns::SANDBOX_GATEWAY_URI
|
13
|
+
end
|
14
|
+
|
15
|
+
device_token, cert, key, badge, alert, sound = if File.file?(ARGV[2])
|
16
|
+
[ARGV[0], ARGV[1], ARGV[2], ARGV[3].to_i, ARGV[4], ARGV[5]]
|
17
|
+
else
|
18
|
+
[ARGV[0], ARGV[1], nil, ARGV[2].to_i, ARGV[3], ARGV[4]]
|
19
|
+
end
|
20
|
+
|
21
|
+
puts "Gateway: #{gateway_uri}"
|
22
|
+
puts "Device Token: #{device_token}"
|
23
|
+
puts "Badge: #{badge}"
|
24
|
+
puts "Alert: #{alert}"
|
25
|
+
puts "Sound: #{sound}"
|
26
|
+
|
27
|
+
MobileNotify::Apns::Connection.open(gateway_uri, cert, key) do |connection|
|
28
|
+
connection.send(MobileNotify::Apns::SimpleNotification.new(device_token, badge, alert, sound))
|
29
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module MobileNotify
|
2
|
+
|
3
|
+
module Apns
|
4
|
+
|
5
|
+
SANDBOX_GATEWAY_URI = URI.parse("apns://gateway.sandbox.push.apple.com:2195")
|
6
|
+
PRODUCTION_GATEWAY_URI = URI.parse("apns://gateway.push.apple.com:2195")
|
7
|
+
|
8
|
+
class Connection
|
9
|
+
|
10
|
+
class NotConnectedError < StandardError
|
11
|
+
def initialize(uri)
|
12
|
+
super("A connection to #{uri} has not yet been established.")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class AlreadyConnectedError < StandardError
|
17
|
+
def initialize(uri)
|
18
|
+
super("A connection to #{uri} has already been established.")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class ConnectionTimeoutError < StandardError
|
23
|
+
def initialize(uri, timeout, original_error)
|
24
|
+
super("Unable to establish connection to #{uri.host}:#{uri.port} within #{timeout} seconds. SSL Reported: #{original_error.inspect}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class TransmissionTimeoutError < StandardError
|
29
|
+
def initialize(uri, timeout, original_error)
|
30
|
+
super("Unable to send data to #{uri.host}:#{uri.port} within #{timeout} seconds. SSL Reported: #{original_error.inspect}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
DEFAULT_CONNECTION_TIMEOUT = 30
|
35
|
+
DEFAULT_CONNECTION_RETRY_DELAY = 2
|
36
|
+
DEFAULT_TRANSMISSION_TIMEOUT = 10
|
37
|
+
DEFAULT_TRANSMISSION_RETRY_DELAY = 2
|
38
|
+
|
39
|
+
attr_accessor :connection_timeout, :connection_retry_delay
|
40
|
+
attr_accessor :transmission_timeout, :transmission_retry_delay
|
41
|
+
|
42
|
+
def self.open(uri, certificate_file, key_file = certificate_file)
|
43
|
+
connection = new(uri, certificate_file, key_file = certificate_file)
|
44
|
+
connection.open
|
45
|
+
yield connection
|
46
|
+
ensure
|
47
|
+
connection.close if connection
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(uri, certificate_file, key_file = certificate_file)
|
51
|
+
@uri = uri
|
52
|
+
@certificate_file = certificate_file
|
53
|
+
@key_file = key_file.nil? ? certificate_file : key_file
|
54
|
+
|
55
|
+
@connection_timeout = DEFAULT_CONNECTION_TIMEOUT
|
56
|
+
@connection_retry_delay = DEFAULT_CONNECTION_RETRY_DELAY
|
57
|
+
@transmission_timeout = DEFAULT_TRANSMISSION_TIMEOUT
|
58
|
+
@transmission_retry_delay = DEFAULT_TRANSMISSION_RETRY_DELAY
|
59
|
+
|
60
|
+
@ssl_context = OpenSSL::SSL::SSLContext.new()
|
61
|
+
@ssl_context.cert = OpenSSL::X509::Certificate.new(File::read(@certificate_file))
|
62
|
+
@ssl_context.key = OpenSSL::PKey::RSA.new(File::read(@key_file))
|
63
|
+
@tcp_socket = TCPSocket.new(@uri.host, @uri.port)
|
64
|
+
@ssl_socket = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def open
|
68
|
+
raise AlreadyConnectedError.new(@uri) if @ssl_socket
|
69
|
+
|
70
|
+
@ssl_socket = establish_connection
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def send(notification)
|
75
|
+
raise NotConnectedError.new(@uri) unless @ssl_socket
|
76
|
+
|
77
|
+
unless defined?(retry_timer)
|
78
|
+
retry_timer = 0
|
79
|
+
end
|
80
|
+
|
81
|
+
@ssl_socket.write(notification.to_data)
|
82
|
+
self
|
83
|
+
rescue OpenSSL::SSL::SSLError, Errno::EPIPE
|
84
|
+
sleep(self.transmission_retry_delay)
|
85
|
+
retry_timer += self.transmission_retry_delay
|
86
|
+
retry if retry_timer < self.transmission_timeout
|
87
|
+
|
88
|
+
raise TransmissionTimeoutError.new(@uri, self.transmission_timeout, $!) if retry_timer >= self.transmission_timeout
|
89
|
+
end
|
90
|
+
|
91
|
+
def close
|
92
|
+
@ssl_socket.close if @ssl_socket
|
93
|
+
@ssl_socket = nil
|
94
|
+
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
def establish_connection
|
101
|
+
unless defined?(retry_timer)
|
102
|
+
retry_timer = 0
|
103
|
+
end
|
104
|
+
|
105
|
+
# Wrap the TCP socket w/ SSL
|
106
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(@tcp_socket, @ssl_context)
|
107
|
+
ssl_socket.connect
|
108
|
+
ssl_socket.sync_close = true
|
109
|
+
|
110
|
+
ssl_socket
|
111
|
+
rescue OpenSSL::SSL::SSLError, Errno::EPIPE
|
112
|
+
sleep(self.connection_retry_delay)
|
113
|
+
retry_timer += self.connection_retry_delay
|
114
|
+
retry if retry_timer < self.connection_timeout
|
115
|
+
|
116
|
+
raise ConnectionTimeoutError.new(@uri, self.connection_timeout, $!) if retry_timer >= self.connection_timeout
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module MobileNotify
|
2
|
+
|
3
|
+
module Apns
|
4
|
+
|
5
|
+
class Notification
|
6
|
+
|
7
|
+
MAX_PAYLOAD_LENGTH = 256
|
8
|
+
|
9
|
+
# Counterpart of {Notification#to_s} - parses from binary string
|
10
|
+
# @param [String] bitstring string to parse
|
11
|
+
# @return [Notification] parsed Notification object
|
12
|
+
def self.parse(bitstring)
|
13
|
+
command, tokenlen, device_token, payloadlen, payload = bitstring.unpack("CnH64na*")
|
14
|
+
new(device_token, payload)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates new notification with given token and payload
|
18
|
+
# @param [String, Fixnum] token APNs token of device to notify
|
19
|
+
# @param [Hash, String] payload attached payload
|
20
|
+
# @example
|
21
|
+
# APNs4r::Notification.new 'e754dXXXX...', { :aps => {:alert => "Hey, dude!", :badge => 1}, :custom_data => "asd" }
|
22
|
+
def initialize(device_token, payload)
|
23
|
+
@device_token = device_token.delete(' ')
|
24
|
+
@payload = payload.kind_of?(Hash) ? payload.to_json : payload
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid?
|
28
|
+
@payload.length <= MAX_PAYLOAD_LENGTH
|
29
|
+
end
|
30
|
+
|
31
|
+
# Converts to binary string wich can be writen directly into socket
|
32
|
+
# @return [String] binary string representation
|
33
|
+
def to_data
|
34
|
+
[0, 32, @device_token, @payload.length, @payload ].pack("CnH*na*")
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class AlertNotification < Notification
|
40
|
+
def initialize(device_token, alert, sound = nil)
|
41
|
+
payload = { "aps" => {} }
|
42
|
+
payload["aps"]["alert"] = alert
|
43
|
+
payload["aps"]["sound"] = sound if sound
|
44
|
+
super(device_token, payload)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class BadgeNotification < Notification
|
49
|
+
def initialize(device_token, badge, sound = nil)
|
50
|
+
payload = { "aps" => {} }
|
51
|
+
payload["aps"]["badge"] = badge_value.to_i
|
52
|
+
payload["aps"]["sound"] = sound if sound
|
53
|
+
super(device_token, payload)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class SimpleNotification < Notification
|
58
|
+
|
59
|
+
EMPTY_BADGE = 0
|
60
|
+
|
61
|
+
def initialize(device_token, badge_value = EMPTY_BADGE, alert = nil, sound = nil, extra = nil)
|
62
|
+
payload = { "aps" => {} }
|
63
|
+
payload["aps"]["badge"] = badge_value.to_i
|
64
|
+
payload["aps"]["alert"] = alert if alert
|
65
|
+
payload["aps"]["sound"] = sound if sound
|
66
|
+
payload.update(extra) if extra.is_a?(Hash)
|
67
|
+
|
68
|
+
super(device_token, payload)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mobile_notify
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 9
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: "0.1"
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Scott Bauer
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-05-27 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: json
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
description: Mobile Notification Services w/ support for the Apple Push Notification Service (APNS)
|
35
|
+
email:
|
36
|
+
executables:
|
37
|
+
- apns_ping
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files: []
|
41
|
+
|
42
|
+
files:
|
43
|
+
- Rakefile
|
44
|
+
- lib/mobile_notify/apns/connection.rb
|
45
|
+
- lib/mobile_notify/apns/notification.rb
|
46
|
+
- lib/mobile_notify/version.rb
|
47
|
+
- lib/mobile_notify.rb
|
48
|
+
- bin/apns_ping
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://rdoc.info/projects/Bauerpauer/mobile_notify
|
51
|
+
licenses: []
|
52
|
+
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.3.7
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: Mobile Notification Services w/ support for the Apple Push Notification Service (APNS)
|
83
|
+
test_files: []
|
84
|
+
|