pushmeup 0.1.2 → 0.3.0
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.
- checksums.yaml +15 -0
- data/.gitignore +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +2 -6
- data/README.md +117 -20
- data/Rakefile +6 -1
- data/lib/pushmeup/amazon.rb +2 -0
- data/lib/pushmeup/apns/core.rb +60 -15
- data/lib/pushmeup/apns/notification.rb +16 -8
- data/lib/pushmeup/fire/core.rb +103 -0
- data/lib/pushmeup/fire/notification.rb +46 -0
- data/lib/pushmeup/gcm/core.rb +20 -12
- data/lib/pushmeup/gcm/notification.rb +19 -10
- data/lib/pushmeup/version.rb +1 -1
- data/lib/pushmeup.rb +1 -0
- data/spec/lib/pushmeup_spec.rb +33 -9
- metadata +10 -22
- data/.rvmrc +0 -48
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MWVmYjczNGEwYjE4ZTkzNGFhOWJlYjE5NDFhNDg2OTk1YjMyYjJhYg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OGYyNzFkMzdlZTY1MTM3NWVlMjE0YTEwYThmZWU4ZTE0NTg2ZTllNw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
M2FiNjA1MWQ2YzRiNGI5OGYzNjBlODFjM2NiYzUwY2E3ZjQwYmVlYzZiYWJj
|
10
|
+
YTI0MTU0ODU0ZWU4ODBkN2U5YmY0OWU4MmIxNDU0ZjNhNjEzMmMwMzE0ZDdk
|
11
|
+
MjA4ZmE4ZWZkODc0NmI3N2M5MTIwNmVlNDM1Y2EwZDQ2ZjYwMzc=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NTM1YjA1NzhjZWNhMmQzMjAwNmVkM2VjZmQ5MjY1YjA0MGY4MDg5YmE1NjZi
|
14
|
+
MDk0Yzk2NTAyZTI3M2U0NTk5NmI4ZDExMzAzOTAwNmEzOGNmNTE4N2QyOTE5
|
15
|
+
NTY0ZWFkMTdlZjc0ZDVhMDUyMDg0MDYyMTExYTE2NDk5NTk5MDE=
|
data/.gitignore
CHANGED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pushmeup
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-1.9.3
|
data/.travis.yml
CHANGED
@@ -1,11 +1,7 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
- 1.8.7
|
4
|
-
- 1.9.2
|
5
3
|
- 1.9.3
|
6
|
-
-
|
4
|
+
- 2.0.0
|
7
5
|
- jruby-19mode # JRuby in 1.9 mode
|
8
|
-
- rbx-18mode
|
9
|
-
- rbx-19mode
|
10
6
|
# uncomment this line if your project needs to run something other than `rake`:
|
11
|
-
# script: bundle exec rspec spec
|
7
|
+
# script: bundle exec rspec spec
|
data/README.md
CHANGED
@@ -12,7 +12,7 @@ Pushmeup is an attempt to create an push notifications center that could send pu
|
|
12
12
|
- Windows Phone
|
13
13
|
- And many others
|
14
14
|
|
15
|
-
Currently we have only support for ``iOS`` and ``
|
15
|
+
Currently we have only support for ``iOS``, ``Android`` and ``Kindle Fire`` but we are planning code for more plataforms.
|
16
16
|
|
17
17
|
## Installation
|
18
18
|
|
@@ -41,14 +41,15 @@ and install it with
|
|
41
41
|
3. After you have created your ``pem`` file. Set the host, port and certificate file location on the APNS class. You just need to set this once:
|
42
42
|
|
43
43
|
APNS.host = 'gateway.push.apple.com'
|
44
|
-
# gateway.sandbox.push.apple.com is default
|
44
|
+
# gateway.sandbox.push.apple.com is default and only for development
|
45
|
+
# gateway.push.apple.com is only for production
|
45
46
|
|
46
47
|
APNS.port = 2195
|
47
48
|
# this is also the default. Shouldn't ever have to set this, but just in case Apple goes crazy, you can.
|
48
|
-
|
49
|
+
|
49
50
|
APNS.pem = '/path/to/pem/file'
|
50
51
|
# this is the file you just created
|
51
|
-
|
52
|
+
|
52
53
|
APNS.pass = ''
|
53
54
|
# Just in case your pem need a password
|
54
55
|
|
@@ -56,25 +57,49 @@ and install it with
|
|
56
57
|
|
57
58
|
#### Sending a single notification:
|
58
59
|
|
59
|
-
|
60
|
-
|
61
|
-
|
60
|
+
device_token = '123abc456def'
|
61
|
+
APNS.send_notification(device_token, 'Hello iPhone!' )
|
62
|
+
APNS.send_notification(device_token, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default')
|
62
63
|
|
63
64
|
#### Sending multiple notifications
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
66
|
+
device_token = '123abc456def'
|
67
|
+
n1 = APNS::Notification.new(device_token, 'Hello iPhone!' )
|
68
|
+
n2 = APNS::Notification.new(device_token, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default')
|
69
|
+
APNS.send_notifications([n1, n2])
|
70
|
+
|
71
|
+
> All notifications passed as a parameter will be sent on a single connection, this is done to improve
|
72
|
+
> reliability with APNS servers.
|
73
|
+
|
74
|
+
#### Another way to send multiple notifications is to send notifications in a persistent connection (thread safe)
|
75
|
+
|
76
|
+
# Define that you want persistent connection
|
77
|
+
APNS.start_persistence
|
78
|
+
|
79
|
+
device_token = '123abc456def'
|
80
|
+
|
81
|
+
# Send single notifications
|
82
|
+
APNS.send_notification(device_token, 'Hello iPhone!' )
|
83
|
+
APNS.send_notification(device_token, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default')
|
84
|
+
|
85
|
+
# Send multiple notifications
|
86
|
+
n1 = APNS::Notification.new(device_token, 'Hello iPhone!' )
|
87
|
+
n2 = APNS::Notification.new(device_token, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default')
|
88
|
+
APNS.send_notifications([n1, n2])
|
89
|
+
|
90
|
+
...
|
91
|
+
|
92
|
+
# Stop persistence, from this point each new push will open and close connections
|
93
|
+
APNS.stop_persistence
|
69
94
|
|
70
95
|
#### Sending more information along
|
71
96
|
|
72
|
-
|
73
|
-
|
97
|
+
APNS.send_notification(device_token, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default',
|
98
|
+
:other => {:sent => 'with apns gem', :custom_param => "value"})
|
74
99
|
|
75
100
|
this will result in a payload like this:
|
76
101
|
|
77
|
-
|
102
|
+
{"aps":{"alert":"Hello iPhone!","badge":1,"sound":"default"},"sent":"with apns gem", "custom_param":"value"}
|
78
103
|
|
79
104
|
### Getting your iOS device token
|
80
105
|
|
@@ -168,29 +193,101 @@ You can use multiple keys to send notifications, to do it just do this changes i
|
|
168
193
|
# For single notification
|
169
194
|
GCM.send_notification( destination, :identity => :key1 )
|
170
195
|
# Empty notification
|
171
|
-
|
196
|
+
|
172
197
|
GCM.send_notification( destination, data, :identity => :key1 )
|
173
198
|
# Notification with custom information
|
174
|
-
|
199
|
+
|
175
200
|
GCM.send_notification( destination, data, :collapse_key => "placar_score_global", :time_to_live => 3600, :delay_while_idle => false, :identity => :key1 )
|
176
201
|
# Notification with custom information and parameters
|
177
|
-
|
202
|
+
|
178
203
|
# For multiple notifications
|
179
204
|
options1 = {}
|
180
205
|
options2 = {..., :identity => :key2}
|
181
206
|
n1 = GCM::Notification.new(destination1, data1, options1.merge({:identity => :key2}))
|
182
207
|
n2 = GCM::Notification.new(destination2, data2, :identity => :key1)
|
183
208
|
n3 = GCM::Notification.new(destination3, data3, options2)
|
184
|
-
|
209
|
+
|
185
210
|
GCM.send_notifications( [n1, n2, n3] )
|
186
211
|
# In this case, every notification has his own parameters, options and key
|
187
212
|
|
188
|
-
##
|
213
|
+
## FIRE (Amazon Messaging)
|
214
|
+
|
215
|
+
### Configure
|
216
|
+
|
217
|
+
FIRE.client_id = "amzn1.application-oa2-client.12345678sdfgsdfg"
|
218
|
+
# this is the Client ID obtained from your Security Profile Management on amazon developers
|
219
|
+
|
220
|
+
FIRE.client_secret = "fkgjsbegksklwr863485245ojowe345"
|
221
|
+
# this is the Client Secret obtained from your Security Profile Management on amazon developers
|
222
|
+
|
223
|
+
### Usage
|
224
|
+
|
225
|
+
#### Sending a single notification:
|
226
|
+
|
227
|
+
destination = "tydgfhewgnwe37586329586ejthe93053th346hrth3t"
|
228
|
+
# can be an string or an array of strings containing the regId of the device you want to send
|
229
|
+
|
230
|
+
data = {:key => "value", :key2 => "some value2"}
|
231
|
+
# must be an hash with all values you want inside you notification, strings only, no arrays
|
232
|
+
|
233
|
+
FIRE.send_notification( destination )
|
234
|
+
# Empty notification
|
235
|
+
|
236
|
+
FIRE.send_notification( destination, data )
|
237
|
+
# Notification with custom information
|
238
|
+
|
239
|
+
FIRE.send_notification( destination, data, :consolidationKey => "placar_score_global", :expiresAfter => 3600)
|
240
|
+
# Notification with custom information and parameters
|
241
|
+
|
242
|
+
for more information on parameters check documentation: [Amazon Messaging | Developers](https://developer.amazon.com/public/apis/engage/device-messaging/tech-docs/06-sending-a-message#Request Format)
|
243
|
+
|
244
|
+
#### Sending multiple notifications:
|
245
|
+
|
246
|
+
destination1 = "device1"
|
247
|
+
destination2 = ["device2"]
|
248
|
+
destination3 = ["device1", "device2", "device3"]
|
249
|
+
# can be an string or an array of strings containing the regIds of the devices you want to send
|
250
|
+
|
251
|
+
data1 = {:key => "value", :key2 => ["array", "value"]}
|
252
|
+
# must be an hash with all values you want inside you notification
|
253
|
+
|
254
|
+
options1 = {:consolidationKey => "placar_score_global", :expiresAfter => 3600}
|
255
|
+
# options for the notification
|
256
|
+
|
257
|
+
n1 = FIRE::Notification.new(destination1, data1, options1)
|
258
|
+
n2 = FIRE::Notification.new(destination2, data2)
|
259
|
+
n3 = FIRE::Notification.new(destination3, data3, options2)
|
260
|
+
|
261
|
+
FIRE.send_notifications( [n1, n2, n3] )
|
262
|
+
# In this case, every notification has his own parameters
|
263
|
+
|
264
|
+
for more information on parameters check documentation: [Amazon Messaging | Developers](https://developer.amazon.com/public/apis/engage/device-messaging/tech-docs/06-sending-a-message#Request Format)
|
265
|
+
|
266
|
+
#### Getting your Kindle Fire device token (regId)
|
267
|
+
|
268
|
+
Check this link [Amazon Messaging: Getting Started](https://developer.amazon.com/public/apis/engage/device-messaging)
|
269
|
+
|
189
270
|
|
190
|
-
##
|
271
|
+
## Status
|
272
|
+
|
273
|
+
#### Build Status
|
274
|
+
[](https://travis-ci.org/NicosKaralis/pushmeup)
|
275
|
+
[](https://codeclimate.com/github/NicosKaralis/pushmeup)
|
276
|
+
|
277
|
+
#### Dependency Status [](https://gemnasium.com/NicosKaralis/pushmeup)
|
278
|
+
|
279
|
+
## Contributing
|
280
|
+
|
281
|
+
We would be very pleased if you want to help us!
|
282
|
+
|
283
|
+
Currently we need a lot of testing so if you are good at writing tests please help us
|
191
284
|
|
192
285
|
## License
|
193
286
|
|
194
287
|
Pushmeup is released under the MIT license:
|
195
288
|
|
196
289
|
http://www.opensource.org/licenses/MIT
|
290
|
+
|
291
|
+
|
292
|
+
[](https://bitdeli.com/free "Bitdeli Badge")
|
293
|
+
|
data/Rakefile
CHANGED
data/lib/pushmeup/apns/core.rb
CHANGED
@@ -10,36 +10,52 @@ module APNS
|
|
10
10
|
@pem = nil # this should be the path of the pem file not the contentes
|
11
11
|
@pass = nil
|
12
12
|
|
13
|
+
@persistent = false
|
14
|
+
@mutex = Mutex.new
|
15
|
+
@retries = 3 # TODO: check if we really need this
|
16
|
+
|
17
|
+
@sock = nil
|
18
|
+
@ssl = nil
|
19
|
+
|
13
20
|
class << self
|
14
21
|
attr_accessor :host, :pem, :port, :pass
|
15
22
|
end
|
16
23
|
|
24
|
+
def self.start_persistence
|
25
|
+
@persistent = true
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.stop_persistence
|
29
|
+
@persistent = false
|
30
|
+
|
31
|
+
@ssl.close
|
32
|
+
@sock.close
|
33
|
+
end
|
34
|
+
|
17
35
|
def self.send_notification(device_token, message)
|
18
36
|
n = APNS::Notification.new(device_token, message)
|
19
37
|
self.send_notifications([n])
|
20
38
|
end
|
21
39
|
|
22
40
|
def self.send_notifications(notifications)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
41
|
+
@mutex.synchronize do
|
42
|
+
self.with_connection do
|
43
|
+
notifications.each do |n|
|
44
|
+
@ssl.write(n.packaged_notification)
|
45
|
+
end
|
46
|
+
end
|
27
47
|
end
|
28
|
-
|
29
|
-
ssl.close
|
30
|
-
sock.close
|
31
48
|
end
|
32
49
|
|
33
50
|
def self.feedback
|
34
|
-
raise "Not implemented yet"
|
35
51
|
sock, ssl = self.feedback_connection
|
36
52
|
|
37
53
|
apns_feedback = []
|
38
54
|
|
39
|
-
while line =
|
55
|
+
while line = ssl.read(38) # Read lines from the socket
|
40
56
|
line.strip!
|
41
57
|
f = line.unpack('N1n1H140')
|
42
|
-
apns_feedback <<
|
58
|
+
apns_feedback << { :timestamp => Time.at(f[0]), :token => f[2] }
|
43
59
|
end
|
44
60
|
|
45
61
|
ssl.close
|
@@ -48,8 +64,38 @@ module APNS
|
|
48
64
|
return apns_feedback
|
49
65
|
end
|
50
66
|
|
51
|
-
|
52
|
-
|
67
|
+
protected
|
68
|
+
|
69
|
+
def self.with_connection
|
70
|
+
attempts = 1
|
71
|
+
|
72
|
+
begin
|
73
|
+
# If no @ssl is created or if @ssl is closed we need to start it
|
74
|
+
if @ssl.nil? || @sock.nil? || @ssl.closed? || @sock.closed?
|
75
|
+
@sock, @ssl = self.open_connection
|
76
|
+
end
|
77
|
+
|
78
|
+
yield
|
79
|
+
|
80
|
+
rescue StandardError, Errno::EPIPE
|
81
|
+
raise unless attempts < @retries
|
82
|
+
|
83
|
+
@ssl.close
|
84
|
+
@sock.close
|
85
|
+
|
86
|
+
attempts += 1
|
87
|
+
retry
|
88
|
+
end
|
89
|
+
|
90
|
+
# Only force close if not persistent
|
91
|
+
unless @persistent
|
92
|
+
@ssl.close
|
93
|
+
@ssl = nil
|
94
|
+
@sock.close
|
95
|
+
@sock = nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
53
99
|
def self.open_connection
|
54
100
|
raise "The path to your pem file is not set. (APNS.pem = /path/to/cert.pem)" unless self.pem
|
55
101
|
raise "The path to your pem file does not exist!" unless File.exist?(self.pem)
|
@@ -74,13 +120,12 @@ module APNS
|
|
74
120
|
context.key = OpenSSL::PKey::RSA.new(File.read(self.pem), self.pass)
|
75
121
|
|
76
122
|
fhost = self.host.gsub('gateway','feedback')
|
77
|
-
puts fhost
|
78
123
|
|
79
124
|
sock = TCPSocket.new(fhost, 2196)
|
80
|
-
ssl = OpenSSL::SSL::SSLSocket.new(sock,context)
|
125
|
+
ssl = OpenSSL::SSL::SSLSocket.new(sock, context)
|
81
126
|
ssl.connect
|
82
127
|
|
83
128
|
return sock, ssl
|
84
129
|
end
|
85
130
|
|
86
|
-
end
|
131
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module APNS
|
2
2
|
class Notification
|
3
3
|
attr_accessor :device_token, :alert, :badge, :sound, :other
|
4
|
-
|
4
|
+
|
5
5
|
def initialize(device_token, message)
|
6
6
|
self.device_token = device_token
|
7
7
|
if message.is_a?(Hash)
|
@@ -12,28 +12,36 @@ module APNS
|
|
12
12
|
elsif message.is_a?(String)
|
13
13
|
self.alert = message
|
14
14
|
else
|
15
|
-
raise "Notification needs to have either a
|
15
|
+
raise "Notification needs to have either a Hash or String"
|
16
16
|
end
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
def packaged_notification
|
20
20
|
pt = self.packaged_token
|
21
21
|
pm = self.packaged_message
|
22
22
|
[0, 0, 32, pt, 0, pm.bytesize, pm].pack("ccca*cca*")
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def packaged_token
|
26
26
|
[device_token.gsub(/[\s|<|>]/,'')].pack('H*')
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
def packaged_message
|
30
30
|
aps = {'aps'=> {} }
|
31
31
|
aps['aps']['alert'] = self.alert if self.alert
|
32
32
|
aps['aps']['badge'] = self.badge if self.badge
|
33
33
|
aps['aps']['sound'] = self.sound if self.sound
|
34
34
|
aps.merge!(self.other) if self.other
|
35
|
-
aps.to_json
|
35
|
+
aps.to_json.gsub(/\\u([\da-fA-F]{4})/) {|m| [$1].pack("H*").unpack("n*").pack("U*")}
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
|
+
def ==(that)
|
39
|
+
device_token == that.device_token &&
|
40
|
+
alert == that.alert &&
|
41
|
+
badge == that.badge &&
|
42
|
+
sound == that.sound &&
|
43
|
+
other == that.other
|
44
|
+
end
|
45
|
+
|
38
46
|
end
|
39
|
-
end
|
47
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
# require 'cgi'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module FIRE
|
6
|
+
include HTTParty
|
7
|
+
|
8
|
+
@host = 'https://api.amazon.com/messaging/registrations/%s/messages'
|
9
|
+
@client_id = nil
|
10
|
+
@client_secret = nil
|
11
|
+
|
12
|
+
@access_token_expiration = Time.new(0)
|
13
|
+
@access_token = nil
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :host, :client_id, :client_secret, :access_token, :access_token_expiration
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.send_notification(device_token, data = {}, options = {})
|
20
|
+
n = FIRE::Notification.new(device_token, data, options)
|
21
|
+
self.send_notifications([n])
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.send_notifications(notifications)
|
25
|
+
self.prepare_token
|
26
|
+
responses = []
|
27
|
+
notifications.each do |n|
|
28
|
+
responses << self.prepare_and_send(n)
|
29
|
+
end
|
30
|
+
responses
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.prepare_token
|
34
|
+
return if Time.now < self.access_token_expiration
|
35
|
+
|
36
|
+
token = self.get_access_token
|
37
|
+
self.access_token = token['access_token']
|
38
|
+
expires_in_sec = token['expires_in']
|
39
|
+
self.access_token_expiration = Time.now + expires_in_sec - 60
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.get_access_token
|
43
|
+
headers = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
44
|
+
body = {grant_type: 'client_credentials',
|
45
|
+
scope: 'messaging:push',
|
46
|
+
client_id: self.client_id,
|
47
|
+
client_secret: self.client_secret
|
48
|
+
}
|
49
|
+
params = {headers: headers, body: body}
|
50
|
+
res = self.post('https://api.amazon.com/auth/O2/token', params)
|
51
|
+
return res.parsed_response if res.response.code.to_i == 200
|
52
|
+
raise 'Error getting access token'
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def self.prepare_and_send(n)
|
58
|
+
if !n.consolidationKey.nil? && n.expiresAfter.nil?
|
59
|
+
raise %q{If you are defining a "colapse key" you need a "time to live"}
|
60
|
+
end
|
61
|
+
self.send_push(n)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.send_push(n)
|
65
|
+
headers = {
|
66
|
+
'Authorization' => "Bearer #{self.access_token}",
|
67
|
+
'Content-Type' => 'application/json',
|
68
|
+
'Accept' => 'application/json',
|
69
|
+
'X-Amzn-Accept-Type' => 'com.amazon.device.messaging.ADMSendResult@1.0',
|
70
|
+
'X-Amzn-Type-Version' => 'com.amazon.device.messaging.ADMMessage@1.0'
|
71
|
+
}
|
72
|
+
|
73
|
+
body = {
|
74
|
+
:data => n.data
|
75
|
+
}
|
76
|
+
body.merge!({consolidationKey: n.consolidationKey}) if n.consolidationKey
|
77
|
+
body.merge!({expiresAfter: n.expiresAfter}) if n.expiresAfter
|
78
|
+
return self.send_to_server(headers, body.to_json, n.device_token)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.send_to_server(headers, body, token)
|
82
|
+
params = {:headers => headers, :body => body}
|
83
|
+
device_dest = self.host % [token]
|
84
|
+
response = self.post(device_dest, params)
|
85
|
+
return build_response(response)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.build_response(response)
|
89
|
+
case response.code
|
90
|
+
when 200
|
91
|
+
{:response => 'success', :body => JSON.parse(response.body), :headers => response.headers, :status_code => response.code}
|
92
|
+
when 400
|
93
|
+
{:response => response.parsed_response, :status_code => response.code}
|
94
|
+
when 401
|
95
|
+
{:response => 'There was an error authenticating the sender account.', :status_code => response.code}
|
96
|
+
when 500
|
97
|
+
{:response => 'There was an internal error in the Amazaon server while trying to process the request.', :status_code => response.code}
|
98
|
+
when 503
|
99
|
+
{:response => 'Server is temporarily unavailable.', :status_code => response.code}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module FIRE
|
2
|
+
class Notification
|
3
|
+
attr_accessor :device_token, :data, :consolidationKey, :expiresAfter
|
4
|
+
|
5
|
+
def initialize(token, data, options = {})
|
6
|
+
self.device_token = token
|
7
|
+
self.data = data
|
8
|
+
|
9
|
+
@consolidationKey = options[:consolidationKey]
|
10
|
+
@expiresAfter = options[:expiresAfter]
|
11
|
+
end
|
12
|
+
|
13
|
+
def device_token=(token)
|
14
|
+
|
15
|
+
if token.is_a?(String)
|
16
|
+
@device_token = token
|
17
|
+
else
|
18
|
+
raise "device_token needs to be String"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def data=(data)
|
23
|
+
if data.is_a?(Hash)
|
24
|
+
@data = data
|
25
|
+
else
|
26
|
+
raise "data parameter must be the type of Hash"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def expiresAfter=(expiresAfter)
|
31
|
+
if expiresAfter.is_a?(Integer)
|
32
|
+
@expiresAfter = expiresAfter
|
33
|
+
else
|
34
|
+
raise %q{"expiresAfter" must be seconds as an integer value, like "100"}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(that)
|
39
|
+
device_token == that.device_token &&
|
40
|
+
data == that.data &&
|
41
|
+
consolidationKey == that.consolidationKey &&
|
42
|
+
expiresAfter == that.expiresAfter
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/pushmeup/gcm/core.rb
CHANGED
@@ -4,14 +4,14 @@ require 'json'
|
|
4
4
|
|
5
5
|
module GCM
|
6
6
|
include HTTParty
|
7
|
-
|
7
|
+
|
8
8
|
@host = 'https://android.googleapis.com/gcm/send'
|
9
9
|
@format = :json
|
10
10
|
@key = nil
|
11
11
|
|
12
12
|
class << self
|
13
13
|
attr_accessor :host, :format, :key
|
14
|
-
|
14
|
+
|
15
15
|
def key(identity = nil)
|
16
16
|
if @key.is_a?(Hash)
|
17
17
|
raise %{If your key is a hash of keys you'l need to pass a identifier to the notification!} if identity.nil?
|
@@ -20,13 +20,21 @@ module GCM
|
|
20
20
|
return @key
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
def key_identities
|
25
|
+
if @key.is_a?(Hash)
|
26
|
+
return @key.keys
|
27
|
+
else
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
end
|
23
31
|
end
|
24
|
-
|
32
|
+
|
25
33
|
def self.send_notification(device_tokens, data = {}, options = {})
|
26
34
|
n = GCM::Notification.new(device_tokens, data, options)
|
27
35
|
self.send_notifications([n])
|
28
36
|
end
|
29
|
-
|
37
|
+
|
30
38
|
def self.send_notifications(notifications)
|
31
39
|
responses = []
|
32
40
|
notifications.each do |n|
|
@@ -36,7 +44,7 @@ module GCM
|
|
36
44
|
end
|
37
45
|
|
38
46
|
private
|
39
|
-
|
47
|
+
|
40
48
|
def self.prepare_and_send(n)
|
41
49
|
if n.device_tokens.count < 1 || n.device_tokens.count > 1000
|
42
50
|
raise "Number of device_tokens invalid, keep it betwen 1 and 1000"
|
@@ -47,7 +55,7 @@ module GCM
|
|
47
55
|
if @key.is_a?(Hash) && n.identity.nil?
|
48
56
|
raise %{If your key is a hash of keys you'l need to pass a identifier to the notification!}
|
49
57
|
end
|
50
|
-
|
58
|
+
|
51
59
|
if self.format == :json
|
52
60
|
self.send_push_as_json(n)
|
53
61
|
elsif self.format == :text
|
@@ -56,7 +64,7 @@ module GCM
|
|
56
64
|
raise "Invalid format"
|
57
65
|
end
|
58
66
|
end
|
59
|
-
|
67
|
+
|
60
68
|
def self.send_push_as_json(n)
|
61
69
|
headers = {
|
62
70
|
'Authorization' => "key=#{ self.key(n.identity) }",
|
@@ -71,7 +79,7 @@ module GCM
|
|
71
79
|
}
|
72
80
|
return self.send_to_server(headers, body.to_json)
|
73
81
|
end
|
74
|
-
|
82
|
+
|
75
83
|
def self.send_push_as_plain_text(n)
|
76
84
|
raise "Still has to be done: http://developer.android.com/guide/google/gcm/gcm.html"
|
77
85
|
headers = {
|
@@ -81,13 +89,13 @@ module GCM
|
|
81
89
|
}
|
82
90
|
return self.send_to_server(headers, body)
|
83
91
|
end
|
84
|
-
|
92
|
+
|
85
93
|
def self.send_to_server(headers, body)
|
86
94
|
params = {:headers => headers, :body => body}
|
87
|
-
response = self.post(
|
95
|
+
response = self.post(self.host, params)
|
88
96
|
return build_response(response)
|
89
97
|
end
|
90
|
-
|
98
|
+
|
91
99
|
def self.build_response(response)
|
92
100
|
case response.code
|
93
101
|
when 200
|
@@ -102,5 +110,5 @@ module GCM
|
|
102
110
|
{:response => 'Server is temporarily unavailable.', :status_code => response.code}
|
103
111
|
end
|
104
112
|
end
|
105
|
-
|
113
|
+
|
106
114
|
end
|
@@ -1,39 +1,39 @@
|
|
1
1
|
module GCM
|
2
2
|
class Notification
|
3
3
|
attr_accessor :device_tokens, :data, :collapse_key, :time_to_live, :delay_while_idle, :identity
|
4
|
-
|
4
|
+
|
5
5
|
def initialize(tokens, data, options = {})
|
6
6
|
self.device_tokens = tokens
|
7
7
|
self.data = data
|
8
|
-
|
8
|
+
|
9
9
|
@collapse_key = options[:collapse_key]
|
10
10
|
@time_to_live = options[:time_to_live]
|
11
11
|
@delay_while_idle = options[:delay_while_idle]
|
12
12
|
@identity = options[:identity]
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def device_tokens=(tokens)
|
16
16
|
if tokens.is_a?(Array)
|
17
17
|
@device_tokens = tokens
|
18
18
|
elsif tokens.is_a?(String)
|
19
19
|
@device_tokens = [tokens]
|
20
20
|
else
|
21
|
-
raise "device_tokens needs to be either a
|
21
|
+
raise "device_tokens needs to be either a Hash or String"
|
22
22
|
end
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def data=(data)
|
26
26
|
if data.is_a?(Hash)
|
27
27
|
@data = data
|
28
28
|
else
|
29
|
-
raise "data parameter must be the
|
29
|
+
raise "data parameter must be the type of Hash"
|
30
30
|
end
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
def delay_while_idle=(delay_while_idle)
|
34
34
|
@delay_while_idle = (delay_while_idle == true || delay_while_idle == :true)
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
def time_to_live=(time_to_live)
|
38
38
|
if time_to_live.is_a?(Integer)
|
39
39
|
@time_to_live = time_to_live
|
@@ -41,6 +41,15 @@ module GCM
|
|
41
41
|
raise %q{"time_to_live" must be seconds as an integer value, like "100"}
|
42
42
|
end
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
|
+
def ==(that)
|
46
|
+
device_tokens == that.device_tokens &&
|
47
|
+
data == that.data &&
|
48
|
+
collapse_key == that.collapse_key &&
|
49
|
+
time_to_live == that.time_to_live &&
|
50
|
+
delay_while_idle == that.delay_while_idle &&
|
51
|
+
identity == that.identity
|
52
|
+
end
|
53
|
+
|
45
54
|
end
|
46
|
-
end
|
55
|
+
end
|
data/lib/pushmeup/version.rb
CHANGED
data/lib/pushmeup.rb
CHANGED
data/spec/lib/pushmeup_spec.rb
CHANGED
@@ -5,23 +5,37 @@ describe Pushmeup do
|
|
5
5
|
it "should have a APNS object" do
|
6
6
|
defined?(APNS).should_not be_false
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
it "should not forget the APNS default parameters" do
|
10
10
|
APNS.host.should == "gateway.sandbox.push.apple.com"
|
11
11
|
APNS.port.should == 2195
|
12
12
|
APNS.pem.should be_equal(nil)
|
13
13
|
APNS.pass.should be_equal(nil)
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
|
+
describe "Notifications" do
|
17
|
+
|
18
|
+
describe "#==" do
|
19
|
+
|
20
|
+
it "should properly equate objects without caring about object identity" do
|
21
|
+
a = APNS::Notification.new("123", {:alert => "hi"})
|
22
|
+
b = APNS::Notification.new("123", {:alert => "hi"})
|
23
|
+
a.should eq(b)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
16
30
|
end
|
17
|
-
|
31
|
+
|
18
32
|
describe "GCM" do
|
19
33
|
it "should have a GCM object" do
|
20
34
|
defined?(GCM).should_not be_false
|
21
35
|
end
|
22
|
-
|
36
|
+
|
23
37
|
describe "Notifications" do
|
24
|
-
|
38
|
+
|
25
39
|
before do
|
26
40
|
@options = {:data => "dummy data"}
|
27
41
|
end
|
@@ -38,20 +52,30 @@ describe Pushmeup do
|
|
38
52
|
end
|
39
53
|
|
40
54
|
it "should allow only notifications with data as hash with :data root" do
|
41
|
-
n = GCM::Notification.new("id", {:data => "data"})
|
42
|
-
|
55
|
+
n = GCM::Notification.new("id", { :data => "data" })
|
56
|
+
|
43
57
|
n.data.is_a?(Hash).should be_true
|
44
58
|
n.data.should == {:data => "data"}
|
45
59
|
|
46
60
|
n.data = {:a => ["a", "b", "c"]}
|
47
61
|
n.data.is_a?(Hash).should be_true
|
48
62
|
n.data.should == {:a => ["a", "b", "c"]}
|
49
|
-
|
63
|
+
|
50
64
|
n.data = {:a => "a"}
|
51
65
|
n.data.is_a?(Hash).should be_true
|
52
66
|
n.data.should == {:a => "a"}
|
53
67
|
end
|
54
|
-
|
68
|
+
|
69
|
+
describe "#==" do
|
70
|
+
|
71
|
+
it "should properly equate objects without caring about object identity" do
|
72
|
+
a = GCM::Notification.new("id", { :data => "data" })
|
73
|
+
b = GCM::Notification.new("id", { :data => "data" })
|
74
|
+
a.should eq(b)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
55
79
|
end
|
56
80
|
end
|
57
81
|
end
|
metadata
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pushmeup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.3.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Nicos Karalis
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-07-26 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: httparty
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
17
|
- - ! '>='
|
20
18
|
- !ruby/object:Gem::Version
|
@@ -22,7 +20,6 @@ dependencies:
|
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
24
|
- - ! '>='
|
28
25
|
- !ruby/object:Gem::Version
|
@@ -30,7 +27,6 @@ dependencies:
|
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: json
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
31
|
- - ! '>='
|
36
32
|
- !ruby/object:Gem::Version
|
@@ -38,7 +34,6 @@ dependencies:
|
|
38
34
|
type: :runtime
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
38
|
- - ! '>='
|
44
39
|
- !ruby/object:Gem::Version
|
@@ -46,7 +41,6 @@ dependencies:
|
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: rake
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
45
|
- - ! '>='
|
52
46
|
- !ruby/object:Gem::Version
|
@@ -54,7 +48,6 @@ dependencies:
|
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
52
|
- - ! '>='
|
60
53
|
- !ruby/object:Gem::Version
|
@@ -62,7 +55,6 @@ dependencies:
|
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: rspec
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
59
|
- - ! '>='
|
68
60
|
- !ruby/object:Gem::Version
|
@@ -70,7 +62,6 @@ dependencies:
|
|
70
62
|
type: :development
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
66
|
- - ! '>='
|
76
67
|
- !ruby/object:Gem::Version
|
@@ -88,7 +79,8 @@ extensions: []
|
|
88
79
|
extra_rdoc_files: []
|
89
80
|
files:
|
90
81
|
- .gitignore
|
91
|
-
- .
|
82
|
+
- .ruby-gemset
|
83
|
+
- .ruby-version
|
92
84
|
- .travis.yml
|
93
85
|
- Gemfile
|
94
86
|
- Keychain Access.jpg
|
@@ -96,10 +88,13 @@ files:
|
|
96
88
|
- README.md
|
97
89
|
- Rakefile
|
98
90
|
- lib/pushmeup.rb
|
91
|
+
- lib/pushmeup/amazon.rb
|
99
92
|
- lib/pushmeup/android.rb
|
100
93
|
- lib/pushmeup/apns/core.rb
|
101
94
|
- lib/pushmeup/apns/notification.rb
|
102
95
|
- lib/pushmeup/apple.rb
|
96
|
+
- lib/pushmeup/fire/core.rb
|
97
|
+
- lib/pushmeup/fire/notification.rb
|
103
98
|
- lib/pushmeup/gcm/core.rb
|
104
99
|
- lib/pushmeup/gcm/notification.rb
|
105
100
|
- lib/pushmeup/version.rb
|
@@ -108,33 +103,26 @@ files:
|
|
108
103
|
- spec/spec_helper.rb
|
109
104
|
homepage: https://github.com/NicosKaralis/pushmeup
|
110
105
|
licenses: []
|
106
|
+
metadata: {}
|
111
107
|
post_install_message:
|
112
108
|
rdoc_options: []
|
113
109
|
require_paths:
|
114
110
|
- lib
|
115
111
|
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
-
none: false
|
117
112
|
requirements:
|
118
113
|
- - ! '>='
|
119
114
|
- !ruby/object:Gem::Version
|
120
115
|
version: '0'
|
121
|
-
segments:
|
122
|
-
- 0
|
123
|
-
hash: -2951185572715016947
|
124
116
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
-
none: false
|
126
117
|
requirements:
|
127
118
|
- - ! '>='
|
128
119
|
- !ruby/object:Gem::Version
|
129
120
|
version: '0'
|
130
|
-
segments:
|
131
|
-
- 0
|
132
|
-
hash: -2951185572715016947
|
133
121
|
requirements: []
|
134
122
|
rubyforge_project: pushmeup
|
135
|
-
rubygems_version:
|
123
|
+
rubygems_version: 2.2.2
|
136
124
|
signing_key:
|
137
|
-
specification_version:
|
125
|
+
specification_version: 4
|
138
126
|
summary: Send push notifications to Apple devices through ANPS and Android devices
|
139
127
|
through GCM
|
140
128
|
test_files:
|
data/.rvmrc
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
#!/usr/bin/env bash
|
2
|
-
|
3
|
-
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
-
# development environment upon cd'ing into the directory
|
5
|
-
|
6
|
-
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,
|
7
|
-
# Only full ruby name is supported here, for short names use:
|
8
|
-
# echo "rvm use 1.9.3" > .rvmrc
|
9
|
-
environment_id="ruby-1.9.3-p125@pushmeup"
|
10
|
-
|
11
|
-
# Uncomment the following lines if you want to verify rvm version per project
|
12
|
-
# rvmrc_rvm_version="1.11.5 (master)" # 1.10.1 seams as a safe start
|
13
|
-
# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
|
14
|
-
# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
|
15
|
-
# return 1
|
16
|
-
# }
|
17
|
-
|
18
|
-
# First we attempt to load the desired environment directly from the environment
|
19
|
-
# file. This is very fast and efficient compared to running through the entire
|
20
|
-
# CLI and selector. If you want feedback on which environment was used then
|
21
|
-
# insert the word 'use' after --create as this triggers verbose mode.
|
22
|
-
if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
|
23
|
-
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
24
|
-
then
|
25
|
-
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
26
|
-
[[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
|
27
|
-
\. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
|
28
|
-
else
|
29
|
-
# If the environment file has not yet been created, use the RVM CLI to select.
|
30
|
-
rvm --create "$environment_id" || {
|
31
|
-
echo "Failed to create RVM environment '${environment_id}'."
|
32
|
-
return 1
|
33
|
-
}
|
34
|
-
fi
|
35
|
-
|
36
|
-
# If you use bundler, this might be useful to you:
|
37
|
-
if [[ -s Gemfile ]] && {
|
38
|
-
! builtin command -v bundle >/dev/null ||
|
39
|
-
builtin command -v bundle | grep $rvm_path/bin/bundle >/dev/null
|
40
|
-
}
|
41
|
-
then
|
42
|
-
printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
|
43
|
-
gem install bundler
|
44
|
-
fi
|
45
|
-
if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null
|
46
|
-
then
|
47
|
-
bundle install | grep -vE '^Using|Your bundle is complete'
|
48
|
-
fi
|