Skip to content

Commit b434a9b

Browse files
author
Carlos Alonso
committed
added docs
1 parent 164ff20 commit b434a9b

14 files changed

+221
-28
lines changed

Gemfile.lock

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
PATH
22
remote: .
33
specs:
4-
ruby-push-notifications (0.0.1)
4+
ruby-push-notifications (0.1.0)
55

66
GEM
77
remote: https://rubygems.org/
88
specs:
9-
activesupport (4.2.0)
9+
activesupport (4.2.1)
1010
i18n (~> 0.7)
1111
json (~> 1.7, >= 1.7.7)
1212
minitest (~> 5.1)
1313
thread_safe (~> 0.3, >= 0.3.4)
1414
tzinfo (~> 1.1)
15-
addressable (2.3.7)
15+
addressable (2.3.8)
1616
codeclimate-test-reporter (0.4.7)
1717
simplecov (>= 0.7.1, < 1.0.0)
1818
crack (0.4.2)
@@ -30,25 +30,25 @@ GEM
3030
rspec-core (~> 3.2.0)
3131
rspec-expectations (~> 3.2.0)
3232
rspec-mocks (~> 3.2.0)
33-
rspec-core (3.2.0)
33+
rspec-core (3.2.2)
3434
rspec-support (~> 3.2.0)
3535
rspec-expectations (3.2.0)
3636
diff-lcs (>= 1.2.0, < 2.0)
3737
rspec-support (~> 3.2.0)
38-
rspec-mocks (3.2.0)
38+
rspec-mocks (3.2.1)
3939
diff-lcs (>= 1.2.0, < 2.0)
4040
rspec-support (~> 3.2.0)
41-
rspec-support (3.2.1)
41+
rspec-support (3.2.2)
4242
safe_yaml (1.0.4)
4343
simplecov (0.9.2)
4444
docile (~> 1.1.0)
4545
multi_json (~> 1.0)
4646
simplecov-html (~> 0.9.0)
4747
simplecov-html (0.9.0)
48-
thread_safe (0.3.4)
48+
thread_safe (0.3.5)
4949
tzinfo (1.2.2)
5050
thread_safe (~> 0.1)
51-
webmock (1.20.4)
51+
webmock (1.21.0)
5252
addressable (>= 2.3.6)
5353
crack (>= 0.3.2)
5454

lib/ruby-push-notifications.rb

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
require "ruby-push-notifications/version"
2-
3-
module RubyPushNotifications
4-
# Your code goes here...
5-
end
6-
1+
require 'ruby-push-notifications/version'
72
require 'ruby-push-notifications/apns'
83
require 'ruby-push-notifications/gcm'

lib/ruby-push-notifications/apns.rb

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,40 @@
1-
1+
#
2+
# @author Carlos Alonso
3+
#
24
module RubyPushNotifications::APNS
5+
6+
# No Status Error Code. Represents a successfully sent notification
37
NO_ERROR_STATUS_CODE = 0
8+
9+
# An error occurred while processing the notification
410
PROCESSING_ERROR_STATUS_CODE = 1
11+
12+
# The notification contains no device token
513
MISSING_DEVICE_TOKEN_STATUS_CODE = 2
6-
MISSING_TOPIC_STATUS_CODE = 3 # You're writing to the TCPSocket rather than the SSL one
14+
15+
# The notification is being sent through the plain TCPSocket,
16+
# rather than the SSL one
17+
MISSING_TOPIC_STATUS_CODE = 3
18+
19+
# The notification contains no payload
720
MISSING_PAYLOAD_STATUS_CODE = 4
21+
22+
# The given token sice is invalid (!= 32)
823
INVALID_TOKEN_SIZE_STATUS_CODE = 5
24+
25+
# The given topic size is invalid
926
INVALID_TOPIC_SIZE_STATUS_CODE = 6
27+
28+
# Payload size is invalid (256 bytes if iOS < 8, 2Kb otherwise)
1029
INVALID_PAYLOAD_SIZE_STATUS_CODE = 7
11-
INVALID_TOKEN_STATUS_CODE = 8 # The token is for dev and the env is prod or viceversa, or simply wrong
30+
31+
# The token is for dev and the env is prod or viceversa, or simply wrong
32+
INVALID_TOKEN_STATUS_CODE = 8
33+
34+
# Connection closed at Apple's end
1235
SHUTDOWN_STATUS_CODE = 10
36+
37+
# Unknown error
1338
UNKNOWN_ERROR_STATUS_CODE = 255
1439
end
1540

lib/ruby-push-notifications/apns/apns_connection.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,28 @@
55

66
module RubyPushNotifications
77
module APNS
8+
# This class encapsulates a connection with APNS.
9+
#
10+
# @author Carlos Alonso
811
class APNSConnection
912
extend Forwardable
1013

14+
# @private URL of the APNS Sandbox environment
1115
APNS_SANDBOX_URL = 'gateway.sandbox.push.apple.com'
16+
17+
# @private URL of APNS production environment
1218
APNS_PRODUCTION_URL = 'gateway.push.apple.com'
19+
20+
# @private Port to connect to
1321
APNS_PORT = 2195
1422

1523
def_delegators :@sslsock, :write, :flush, :to_io, :read
1624

25+
# Opens a connection with APNS
26+
#
27+
# @param cert [String]. Contents of the PEM encoded certificate
28+
# @param sandbox [Boolean]. Whether to use the sandbox environment or not.
29+
# @return [APNSConnection]. The recently stablished connection.
1730
def self.open(cert, sandbox)
1831
ctx = OpenSSL::SSL::SSLContext.new
1932
ctx.key = OpenSSL::PKey::RSA.new cert
@@ -27,15 +40,25 @@ def self.open(cert, sandbox)
2740
new socket, ssl
2841
end
2942

43+
# Returns the URL to connect to.
44+
#
45+
# @param sandbox [Boolean]. Whether it is the sandbox or the production
46+
# environment we're looking for.
47+
# @return [String]. The URL for the APNS service.
3048
def self.host(sandbox)
3149
sandbox ? APNS_SANDBOX_URL : APNS_PRODUCTION_URL
3250
end
3351

52+
# Initializes the APNSConnection
53+
#
54+
# @param tcpsock [TCPSocket]. The used TCP Socket.
55+
# @param sslsock [SSLSocket]. The connected SSL Socket.
3456
def initialize(tcpsock, sslsock)
3557
@tcpsock = tcpsock
3658
@sslsock = sslsock
3759
end
3860

61+
# Closes the APNSConnection
3962
def close
4063
@sslsock.close
4164
@tcpsock.close

lib/ruby-push-notifications/apns/apns_notification.rb

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,89 @@
33

44
module RubyPushNotifications
55
module APNS
6+
# Represents a APNS Notification.
7+
# Manages the conversion of the notification to APNS binary format for
8+
# each of the destinations.
9+
# By default sets maximum expiration date (4 weeks).
10+
#
11+
# @author Carlos Alonso
612
class APNSNotification
713

8-
WEEKS_4 = 2419200 # 4 weeks
14+
# @private. 4 weeks in seconds
15+
WEEKS_4 = 2419200
916

17+
# @return [Array]. Array with the results from sending this notification
1018
attr_accessor :results
1119

20+
# Initializes the APNS Notification
21+
#
22+
# @param [Array]. Array containing all destinations for the notification
23+
# @param [Hash]. Hash with the data to use as payload.
1224
def initialize(tokens, data)
1325
@tokens = tokens
1426
@data = data
1527
end
1628

29+
# Method that yields the notification's binary for each of the receivers.
30+
#
31+
# @param starting_id [Integer]. Every notification encodes a unique ID for
32+
# further reference. This parameter represents the first id the first
33+
# notification of this group should use.
34+
# @yieldparam [String]. APNS binary's representation of this notification.
35+
# Consisting of:
36+
# Notification = 2(1), FrameLength(4), items(FrameLength)
37+
# Item = ItemID(1), ItemLength(2), data(ItemLength)
38+
# Items:
39+
# Device Token => Id: 1, length: 32, data: binary device token
40+
# Payload => Id: 2, length: ??, data: json formatted payload
41+
# Notification ID => Id: 3, length: 4, data: notif id as int
42+
# Expiration Date => Id: 4, length: 4, data: Unix timestamp as int
43+
# Priority => Id: 5, length: 1, data: 10 as 1 byte int
44+
# (https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4)
1745
def each_message(starting_id)
1846
@tokens.each_with_index do |token, i|
19-
# Notification = 2(1), FrameLength(4), items(FrameLength)
20-
# Item = ItemID(1), ItemLength(2), data(ItemLength)
21-
# Items:
22-
# Device Token => Id: 1, length: 32, data: binary device token
23-
# Payload => Id: 2, length: ??, data: json formatted payload
24-
# Notification ID => Id: 3, length: 4, data: notif id as int
25-
# Expiration Date => Id: 4, length: 4, data: Unix timestamp as int
26-
# Priority => Id: 5, length: 1, data: 10 as 1 byte int
2747
bytes = device_token(token) + payload + notification_id(starting_id + i) + expiration_date + priority
2848
yield [2, bytes.bytesize, bytes].pack 'cNa*'
2949
end
3050
end
3151

52+
# @return [Integer]. The number of binaries this notification will send.
53+
# One for each receiver.
3254
def count
3355
@tokens.count
3456
end
3557

3658
private
3759

60+
# @param [String]. The device token to encode.
61+
# @return [String]. Binary representation of the device token field.
3862
def device_token(token)
3963
[1, 32, token].pack 'cnH64'
4064
end
4165

66+
# Generates the APNS's binary representation of the notification's payload.
67+
# Caches the value in an instance variable.
68+
#
69+
# @return [String]. Binary representation of the notification's payload.
4270
def payload
4371
@encoded_payload ||= -> {
4472
json = JSON.dump(@data).force_encoding 'ascii-8bit'
4573
[2, json.bytesize, json].pack 'cna*'
4674
}.call
4775
end
4876

77+
# @param [Integer]. The unique ID for this notification.
78+
# @return [String]. Binary representation of the notification id field.
4979
def notification_id(id)
5080
[3, 4, id].pack 'cnN'
5181
end
5282

83+
# @return [String]. Binary representation of the expiration date field.
5384
def expiration_date
5485
[4, 4, (Time.now + WEEKS_4).to_i].pack 'cnN'
5586
end
5687

88+
# @return [String]. Binary representation of the priority field.
5789
def priority
5890
[5, 1, 10].pack 'cnc'
5991
end

lib/ruby-push-notifications/apns/apns_pusher.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
11

22
module RubyPushNotifications
33
module APNS
4+
# This class coordinates the process of sending notifications.
5+
# It takes care of reopening closed APNSConnections and seeking back to
6+
# the failed notification to keep writing.
7+
#
8+
# Remember that APNS doesn't confirm successful notification, it just
9+
# notifies when one went wrong and closes the connection. Therefore, this
10+
# APNSPusher reconnects and rewinds the array until the notification that
11+
# Apple rejected.
12+
#
13+
# @author Carlos Alonso
414
class APNSPusher
515

16+
# @param certificate [String]. The PEM encoded APNS certificate.
17+
# @param sandbox [Boolean]. Whether the certificate is an APNS sandbox or not.
618
def initialize(certificate, sandbox)
719
@certificate = certificate
820
@sandbox = sandbox
921
end
1022

23+
# Pushes the notifications.
24+
# Builds an array with all the binaries (one for each notification and receiver)
25+
# and pushes them sequentially to APNS monitoring the response.
26+
# If an error is received, the connection is reopened and the process
27+
# continues at the next notification after the failed one (pointed by the response error)
28+
#
29+
# For each notification assigns an array with the results of each submission.
30+
#
31+
# @param notifications [Array]. All the APNSNotifications to be sent.
1132
def push(notifications)
1233
conn = APNSConnection.open @certificate, @sandbox
1334

lib/ruby-push-notifications/gcm/gcm_connection.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,31 @@
33

44
module RubyPushNotifications
55
module GCM
6+
# Encapsulates a connection to the GCM service
7+
# Responsible for final connection with the service.
8+
#
9+
# @author Carlos Alonso
610
class GCMConnection
711

12+
# @private The URL of the Android GCM endpoint
813
GCM_URL = 'https://android.googleapis.com/gcm/send'
914

15+
# @private Content-Type HTTP Header string
1016
CONTENT_TYPE_HEADER = 'Content-Type'
17+
18+
# @private Application/JSON content type
1119
JSON_CONTENT_TYPE = 'application/json'
20+
21+
# @private Authorization HTTP Header String
1222
AUTHORIZATION_HEADER = 'Authorization'
1323

24+
# Issues a POST request to the GCM send endpoint to
25+
# submit the given notifications.
26+
#
27+
# @param notification [String]. The text to POST
28+
# @param key [String]. The GCM sender id to use
29+
# (https://developer.android.com/google/gcm/gcm.html#senderid)
30+
# @return [GCMResponse]. The GCMResponse that encapsulates the received response
1431
def self.post(notification, key)
1532
headers = {
1633
CONTENT_TYPE_HEADER => JSON_CONTENT_TYPE,

lib/ruby-push-notifications/gcm/gcm_error.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11

22
module RubyPushNotifications
33
module GCM
4+
# Base class for all GCM related errors
5+
#
6+
# @author Carlos Alonso
47
class GCMError < StandardError ; end
58

69
class MalformedGCMJSONError < GCMError ; end

lib/ruby-push-notifications/gcm/gcm_notification.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11

22
module RubyPushNotifications
33
module GCM
4+
# Encapsulates a GCM Notification.
5+
# By default only Required fields are set.
6+
# (https://developer.android.com/google/gcm/server-ref.html#send-downstream)
7+
#
8+
# @author Carlos Alonso
49
class GCMNotification
510

11+
# @return [Array]. Array with the results from sending this notification
612
attr_accessor :results
713

14+
# Initializes the notification
15+
#
16+
# @param [Array]. Array with the receiver's GCM registration ids.
17+
# @param [Hash]. Payload to send.
818
def initialize(registration_ids, data)
919
@registration_ids = registration_ids
1020
@data = data
1121
end
1222

23+
# @return [String]. The GCM's JSON format for the payload to send.
24+
# (https://developer.android.com/google/gcm/server-ref.html#send-downstream)
1325
def as_gcm_json
1426
JSON.dump(
1527
registration_ids: @registration_ids,

lib/ruby-push-notifications/gcm/gcm_pusher.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11

22
module RubyPushNotifications
33
module GCM
4+
5+
# This class is responsible for sending notifications to the GCM service.
6+
#
7+
# @author Carlos Alonso
48
class GCMPusher
59

10+
# Initializes the GCMPusher
11+
#
12+
# @param key [String]. GCM sender id to use
13+
# ((https://developer.android.com/google/gcm/gcm.html#senderid))
614
def initialize(key)
715
@key = key
816
end
917

18+
# Actually pushes the given notifications.
19+
# Assigns every notification an array with the result of each
20+
# individual notification.
21+
#
22+
# @param notifications [Array]. Array of GCMNotification to send.
1023
def push(notifications)
1124
notifications.each do |notif|
1225
notif.results = GCMConnection.post notif.as_gcm_json, @key

0 commit comments

Comments
 (0)