mwotton-apnd 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,65 @@
1
+ module APND
2
+ #
3
+ # APND::Feedback receives feedback from Apple when notifications are
4
+ # being rejected for a specific token. This is usually due to the user
5
+ # uninstalling your application.
6
+ #
7
+ class Feedback
8
+
9
+ class << self
10
+ #
11
+ # The host to receive feedback from, usually apple
12
+ #
13
+ attr_accessor :upstream_host
14
+
15
+ #
16
+ # The port to connect to upstream_host on
17
+ #
18
+ attr_accessor :upstream_port
19
+ end
20
+
21
+ #
22
+ # Set upstream host/port to default values
23
+ #
24
+ self.upstream_host = APND.settings.feedback.host
25
+ self.upstream_port = APND.settings.feedback.port.to_i
26
+
27
+ #
28
+ # Connect to Apple's Feedback Service and return an array of device
29
+ # tokens that should no longer receive push notifications
30
+ #
31
+ def self.find_stale_devices(&block)
32
+ feedback = self.new
33
+ devices = feedback.receive
34
+ devices.each { |device| yield *device } if block_given?
35
+ devices
36
+ end
37
+
38
+ #
39
+ # Create a connection to Apple's Feedback Service
40
+ #
41
+ def initialize
42
+ @apple = APND::Daemon::AppleConnection.new({
43
+ :cert => APND.settings.apple.cert,
44
+ :cert_pass => APND.settings.apple.cert_pass,
45
+ :host => self.class.upstream_host,
46
+ :port => self.class.upstream_port.to_i
47
+ })
48
+ end
49
+
50
+ #
51
+ # Receive feedback from Apple and return an array of device tokens
52
+ #
53
+ def receive
54
+ tokens = []
55
+ @apple.open do |sock|
56
+ while line = sock.gets
57
+ payload = line.strip.unpack('N1n1H140')
58
+ tokens << [payload[2].strip, Time.at(payload[0])]
59
+ end
60
+ end
61
+ tokens
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,189 @@
1
+ require 'socket'
2
+
3
+ module APND
4
+ #
5
+ # APND::Notification is the base class for creating new push notifications.
6
+ #
7
+ class Notification
8
+
9
+ class << self
10
+ #
11
+ # The host notifications will be written to, usually one
12
+ # running APND
13
+ #
14
+ attr_accessor :upstream_host
15
+
16
+ #
17
+ # The port to connect to upstream_host on
18
+ #
19
+ attr_accessor :upstream_port
20
+ end
21
+
22
+ #
23
+ # Set upstream host/port to default values
24
+ #
25
+ self.upstream_host = APND.settings.notification.host
26
+ self.upstream_port = APND.settings.notification.port.to_i
27
+
28
+ #
29
+ # The device token from Apple
30
+ #
31
+ attr_accessor :token
32
+
33
+ #
34
+ # The alert to send
35
+ #
36
+ attr_accessor :alert
37
+
38
+ #
39
+ # The badge number to set
40
+ #
41
+ attr_accessor :badge
42
+
43
+ #
44
+ # The sound to play
45
+ #
46
+ attr_accessor :sound
47
+
48
+ #
49
+ # Custom data to send
50
+ #
51
+ attr_accessor :custom
52
+
53
+ #
54
+ # Creates a new socket to upstream_host:upstream_port
55
+ #
56
+ def self.upstream_socket
57
+ @socket = TCPSocket.new(upstream_host, upstream_port)
58
+ end
59
+
60
+ #
61
+ # Opens a new socket upstream, yields it, and closes it
62
+ #
63
+ def self.open_upstream_socket(&block)
64
+ socket = upstream_socket
65
+ yield socket
66
+ socket.close
67
+ end
68
+
69
+ #
70
+ # Create a new APN
71
+ #
72
+ def self.create(params = {}, push = true)
73
+ notification = Notification.new(params)
74
+ notification.push! if push
75
+ notification
76
+ end
77
+
78
+ #
79
+ # Try to create a new Notification from raw data
80
+ # Used by Daemon::Protocol to validate incoming data
81
+ #
82
+ def self.valid?(data)
83
+ parse(data)
84
+ rescue => e
85
+ puts e.inspect
86
+ puts e.backtrace
87
+ false
88
+ end
89
+
90
+ #
91
+ # Parse raw data into a new Notification
92
+ #
93
+ def self.parse(data)
94
+ buffer = data.dup
95
+ notification = Notification.new
96
+
97
+ header = buffer.slice!(0, 3).unpack('ccc')
98
+
99
+ if header[0] != 0
100
+ raise APND::Errors::InvalidNotificationHeader.new(header)
101
+ end
102
+
103
+ notification.token = buffer.slice!(0, 32).unpack('H*').first
104
+
105
+ json_length = buffer.slice!(0, 2).unpack('CC')
106
+
107
+ json = buffer.slice!(0, json_length.last)
108
+
109
+ payload = JSON.parse(json)
110
+
111
+ %w[alert sound badge].each do |key|
112
+ if payload['aps'] && payload['aps'][key]
113
+ notification.send("#{key}=", payload['aps'][key])
114
+ end
115
+ end
116
+
117
+ payload.delete('aps')
118
+
119
+ unless payload.empty?
120
+ notification.custom = payload
121
+ end
122
+
123
+ notification
124
+ end
125
+
126
+ #
127
+ # Create a new Notification object from a hash
128
+ #
129
+ def initialize(params = {})
130
+ @token = params[:token]
131
+ @alert = params[:alert]
132
+ @badge = params[:badge]
133
+ @sound = params[:sound]
134
+ @custom = params[:custom]
135
+ end
136
+
137
+ #
138
+ # Token in hex format
139
+ #
140
+ def hex_token
141
+ [self.token.delete(' ')].pack('H*')
142
+ end
143
+
144
+ #
145
+ # aps hash sent to Apple
146
+ #
147
+ def aps
148
+ aps = {}
149
+ aps['alert'] = self.alert if self.alert
150
+ aps['badge'] = self.badge.to_i if self.badge
151
+ aps['sound'] = self.sound if self.sound
152
+
153
+ output = { 'aps' => aps }
154
+
155
+ if self.custom
156
+ self.custom.each do |key, value|
157
+ output[key.to_s] = value
158
+ end
159
+ end
160
+ output
161
+ end
162
+
163
+ #
164
+ # Pushes notification to upstream host:port (default is localhost:22195)
165
+ #
166
+ def push!
167
+ self.class.open_upstream_socket { |sock| sock.write(to_bytes) }
168
+ end
169
+
170
+ #
171
+ # Returns the Notification's aps hash as json
172
+ #
173
+ def aps_json
174
+ return @aps_json if @aps_json
175
+ json = aps.to_json
176
+ raise APND::Errors::InvalidPayload.new(json) if json.size > 256
177
+ @aps_json = json
178
+ end
179
+
180
+ #
181
+ # Format the notification as a string for submission
182
+ # to Apple
183
+ #
184
+ def to_bytes
185
+ @bytes ||= "\0\0 %s\0%s%s" % [hex_token, aps_json.length.chr, aps_json]
186
+ end
187
+
188
+ end
189
+ end
@@ -0,0 +1,205 @@
1
+ module APND
2
+ #
3
+ # Settings for APND
4
+ #
5
+ class Settings
6
+
7
+ #
8
+ # Settings for APND::Daemon::AppleConnection
9
+ #
10
+ class AppleConnection
11
+
12
+ #
13
+ # Host used to connect to Apple
14
+ #
15
+ # Development: gateway.sandbox.push.apple.com
16
+ # Production: gateway.push.apple.com
17
+ #
18
+ attr_accessor :host
19
+
20
+ #
21
+ # Port used to connect to Apple
22
+ #
23
+ attr_accessor :port
24
+
25
+ #
26
+ # Path to APN cert for your application
27
+ #
28
+ attr_accessor :cert
29
+
30
+ #
31
+ # Password for APN cert, optional
32
+ #
33
+ attr_accessor :cert_pass
34
+
35
+ def initialize
36
+ @host = 'gateway.sandbox.push.apple.com'
37
+ @port = 2195
38
+ end
39
+ end
40
+
41
+ #
42
+ # Settings for APND::Daemon
43
+ #
44
+ class Daemon
45
+
46
+ #
47
+ # IP to bind APND::Daemon to
48
+ #
49
+ # Default: '0.0.0.0'
50
+ #
51
+ attr_accessor :bind
52
+
53
+ #
54
+ # Port APND::Daemon will run on
55
+ #
56
+ # Default: 22195
57
+ #
58
+ attr_accessor :port
59
+
60
+ #
61
+ # Path to APND::Daemon log
62
+ #
63
+ # Default: /var/log/apnd.log
64
+ #
65
+ attr_accessor :log_file
66
+
67
+ #
68
+ # Interval (in seconds) the queue will be processed
69
+ #
70
+ # Default: 30
71
+ #
72
+ attr_accessor :timer
73
+
74
+ def initialize
75
+ @timer = 30
76
+ @bind = '0.0.0.0'
77
+ @port = 22195
78
+ @log_file = '/var/log/apnd.log'
79
+ end
80
+ end
81
+
82
+ #
83
+ # Settings for APND::Notification
84
+ #
85
+ class Notification
86
+
87
+ #
88
+ # Host to send notification to, usually the one running APND::Daemon
89
+ #
90
+ # Default: localhost
91
+ #
92
+ attr_accessor :host
93
+
94
+ #
95
+ # Port to send notifications to
96
+ #
97
+ # Default: 22195
98
+ #
99
+ attr_accessor :port
100
+
101
+ def initialize
102
+ @host = 'localhost'
103
+ @port = 22195
104
+ end
105
+ end
106
+
107
+ #
108
+ # Settings for APND::Feedback
109
+ #
110
+ class Feedback
111
+
112
+ #
113
+ # Host used to connect to Apple
114
+ #
115
+ # Development: feedback.sandbox.push.apple.com
116
+ # Production: feedback.push.apple.com
117
+ #
118
+ attr_accessor :host
119
+
120
+ #
121
+ # Port used to connect to Apple
122
+ #
123
+ # Default: 2196
124
+ #
125
+ attr_accessor :port
126
+
127
+ def initialize
128
+ @host = 'feedback.sandbox.push.apple.com'
129
+ @port = 2196
130
+ end
131
+ end
132
+
133
+ #
134
+ # Returns the AppleConnection settings
135
+ #
136
+ def apple
137
+ @apple ||= APND::Settings::AppleConnection.new
138
+ end
139
+
140
+ #
141
+ # Mass assign AppleConnection settings
142
+ #
143
+ def apple=(options = {})
144
+ if options.respond_to?(:keys)
145
+ apple.cert = options[:cert] if options[:cert]
146
+ apple.cert_pass = options[:cert_pass] if options[:cert_pass]
147
+ apple.host = options[:host] if options[:host]
148
+ apple.port = options[:port] if options[:port]
149
+ end
150
+ end
151
+
152
+ #
153
+ # Returns the Daemon settings
154
+ #
155
+ def daemon
156
+ @daemon ||= APND::Settings::Daemon.new
157
+ end
158
+
159
+ #
160
+ # Mass assign Daemon settings
161
+ #
162
+ def daemon=(options = {})
163
+ if options.respond_to?(:keys)
164
+ daemon.bind = options[:bind] if options[:bind]
165
+ daemon.port = options[:port] if options[:port]
166
+ daemon.log_file = options[:log_file] if options[:log_file]
167
+ daemon.timer = options[:timer] if options[:timer]
168
+ end
169
+ end
170
+
171
+ #
172
+ # Returns the Notification settings
173
+ #
174
+ def notification
175
+ @notification ||= APND::Settings::Notification.new
176
+ end
177
+
178
+ #
179
+ # Mass assign Notification settings
180
+ #
181
+ def notification=(options = {})
182
+ if options.respond_to?(:keys)
183
+ notification.port = options[:port] if options[:port]
184
+ notification.host = options[:host] if options[:host]
185
+ end
186
+ end
187
+
188
+ #
189
+ # Returns the Feedback settings
190
+ #
191
+ def feedback
192
+ @feedback ||= APND::Settings::Feedback.new
193
+ end
194
+
195
+ #
196
+ # Mass assign Feedback settings
197
+ #
198
+ def feedback=(options = {})
199
+ if options.respond_to?(:keys)
200
+ feedback.port = options[:port] if options[:port]
201
+ feedback.host = options[:host] if options[:host]
202
+ end
203
+ end
204
+ end
205
+ end