speedy_c2dm 0.1.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.markdown +34 -19
- data/lib/speedy_c2dm/version.rb +1 -1
- data/lib/speedy_c2dm.rb +86 -83
- data/speedy_c2dm.gemspec +0 -2
- data/test/test_speedy_c2dm.rb +8 -9
- metadata +3 -14
data/.gitignore
CHANGED
data/README.markdown
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
#
|
1
|
+
# Speedy C2DM
|
2
2
|
|
3
|
-
|
3
|
+
Speedy C2DM sends push notifications to Android devices via Google's [c2dm](http://code.google.com/android/c2dm/index.html) (Cloud To Device Messaging).
|
4
|
+
|
5
|
+
Pull requests are welcome!
|
4
6
|
|
5
7
|
# How is this GEM different than other C2DM gems?
|
6
8
|
|
7
|
-
To use C2DM
|
9
|
+
To use C2DM, your application server needs to fetch and store an authentication token from Google. This token will periodically expire and the recommendation from Google is that "the server should store the token and have a policy to refresh it periodically." Other C2DM gems take a brute force approach around this issue by requesting a new authenticaion token from Google for *each* notification request they send. This effectively doubles the number of HTTP calls being made for each C2DM notification sent.
|
8
10
|
|
9
|
-
This GEM will request the token when the SpeedyC2DM::API class is first
|
11
|
+
This GEM will request the token when the SpeedyC2DM::API.send_notification() class method is first called. From then on, calls to SpeedyC2DM::API.send_notification() will use the auth token stored in the class instance variable. On subsequent notification calls, the object will check for 'Update-Client-Auth' or check for status 401 (auth failed). If it detects either the 401 or an 'Update-Client-Auth' header, speedy_c2dm will immediately request new tokens from Google. In the case of status 503 (service unavailable), a return message indicating 503 is returned from the send_notification() call. It is suggested (by Google) that you retry after exponential back-off in the case of 503. Using something like [resque-retry](https://github.com/lantins/resque-retry) would work well in this case.
|
10
12
|
|
11
13
|
##Installation
|
12
14
|
|
@@ -14,33 +16,46 @@ This GEM will request the token when the SpeedyC2DM::API class is first initiali
|
|
14
16
|
|
15
17
|
##Requirements
|
16
18
|
|
17
|
-
An Android device running 2.2 or newer, its registration token, and a
|
19
|
+
An Android device running 2.2 or newer, its registration token, and a Google account registered for c2dm.
|
20
|
+
|
21
|
+
##Compatibility
|
22
|
+
|
23
|
+
Speedy_C2DM will work with Rails 3.x & Ruby 1.9x. It has not been tested on previous versions or Rails or Ruby, and may or may not work with those versions.
|
18
24
|
|
19
25
|
##Usage
|
20
26
|
|
21
|
-
|
27
|
+
For a Rails app, a good place to put the following would be in config/initializers/speedy_c2dm.rb :
|
28
|
+
|
29
|
+
C2DM_API_EMAIL = "myemail@gmail.com"
|
30
|
+
C2DM_API_PASSWORD = "mypassword"
|
22
31
|
|
23
|
-
|
24
|
-
:registration_id => TEST_REGISTRATION_ID,
|
25
|
-
:message => "Hi!",
|
26
|
-
:extra_data => 42,
|
27
|
-
:collapse_key => "some-collapse-key"
|
28
|
-
}
|
32
|
+
SpeedyC2DM::API.set_account(C2DM_API_EMAIL, C2DM_API_PASSWORD)
|
29
33
|
|
30
|
-
|
34
|
+
Then, where you want to make a C2DM call in your code, create an options hash and pass it to send_notification():
|
31
35
|
|
32
|
-
|
36
|
+
options = {
|
37
|
+
:registration_id => SOME_REGISTRATION_ID,
|
38
|
+
:message => "Hi!",
|
39
|
+
:extra_data => 42,
|
40
|
+
:collapse_key => "some-collapse-key"
|
41
|
+
}
|
42
|
+
|
43
|
+
response = SpeedyC2DM::API.send_notification(options)
|
44
|
+
|
45
|
+
Note: there are blocking calls in both .new() and .send_notification(). You should use an async queue like [Resque](https://github.com/defunkt/resque) to ensure a non-blocking code path in your application code, particularly for the .send_notification() call.
|
33
46
|
|
34
47
|
|
35
48
|
##Testing
|
36
49
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
50
|
+
To test, first fill out these variables in test/test_speedy_c2dm.rb:
|
51
|
+
|
52
|
+
API_ACCOUNT_EMAIL = "TODO - Your C2DM account email"
|
53
|
+
API_ACCOUNT_PASSWORD = "TODO - Your C2DM account password"
|
54
|
+
TEST_PHONE_C2DM_REGISTRATION_ID = "TODO - Some C2DM registration id you want to test push notification to"
|
41
55
|
|
42
56
|
then run:
|
43
|
-
|
57
|
+
|
58
|
+
$ ruby test/test_speedy_c2dm.rb
|
44
59
|
|
45
60
|
##Copyrights
|
46
61
|
|
data/lib/speedy_c2dm/version.rb
CHANGED
data/lib/speedy_c2dm.rb
CHANGED
@@ -1,103 +1,106 @@
|
|
1
1
|
require "speedy_c2dm/version"
|
2
|
-
require 'typhoeus'
|
3
2
|
|
4
3
|
module SpeedyC2DM
|
5
|
-
|
4
|
+
|
6
5
|
class API
|
7
6
|
AUTH_URL = 'https://www.google.com/accounts/ClientLogin'
|
8
7
|
PUSH_URL = 'https://android.apis.google.com/c2dm/send'
|
9
|
-
|
10
|
-
# Initialize with an API key and config options
|
11
|
-
def initialize(email, password)
|
12
|
-
@email = email
|
13
|
-
@password = password
|
14
8
|
|
15
|
-
|
16
|
-
end
|
9
|
+
class << self
|
17
10
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
11
|
+
def set_account(api_email, api_password)
|
12
|
+
@email = api_email
|
13
|
+
@password = api_password
|
14
|
+
end
|
15
|
+
|
16
|
+
# Send a notification
|
17
|
+
#
|
18
|
+
# :registration_id is required.
|
19
|
+
# :collapse_key is optional.
|
20
|
+
#
|
21
|
+
# Other +options+ will be sent as "data.<key>=<value>"
|
22
|
+
#
|
23
|
+
# +options+ = {
|
24
|
+
# :registration_id => "...",
|
25
|
+
# :message => "Hi!",
|
26
|
+
# :extra_data => 42,
|
27
|
+
# :collapse_key => "some-collapse-key"
|
28
|
+
# }
|
29
|
+
def send_notification(options)
|
30
|
+
get_auth_token(@email, @password) unless @auth_token
|
31
|
+
|
32
|
+
response = notificationRequest(options)
|
33
|
+
|
34
|
+
# the response can be one of three codes:
|
35
|
+
# 200 (success)
|
36
|
+
# 401 (auth failed)
|
37
|
+
# 503 (retry later with exponential backoff)
|
38
|
+
# see more documentation here: http://code.google.com/android/c2dm/#testing
|
39
|
+
if response.code.eql? "200"
|
40
|
+
|
41
|
+
# look for the header 'Update-Client-Auth' in the response you get after sending
|
42
|
+
# a message. It indicates that this is the token to be used for the next message to send.
|
43
|
+
response.each_header do |key, value|
|
44
|
+
if key == "Update-Client-Auth"
|
45
|
+
@auth_token = value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
return response
|
50
|
+
|
51
|
+
elsif response.code.eql? "401"
|
52
|
+
|
53
|
+
# auth failed. Refresh auth key and requeue
|
54
|
+
@auth_token = get_auth_token(@email, @password)
|
55
|
+
|
56
|
+
response = notificationRequest(options)
|
57
|
+
|
58
|
+
return response
|
59
|
+
|
60
|
+
elsif response.code.eql? "503"
|
61
|
+
|
62
|
+
# service un-available.
|
63
|
+
return response
|
30
64
|
|
31
|
-
# Send a notification
|
32
|
-
#
|
33
|
-
# :registration_id is required.
|
34
|
-
# :collapse_key is optional.
|
35
|
-
#
|
36
|
-
# Other +options+ will be sent as "data.<key>=<value>"
|
37
|
-
#
|
38
|
-
# +options+ = {
|
39
|
-
# :registration_id => "...",
|
40
|
-
# :message => "Hi!",
|
41
|
-
# :extra_data => 42,
|
42
|
-
# :collapse_key => "some-collapse-key"
|
43
|
-
# }
|
44
|
-
def send_notification(options)
|
45
|
-
request = requestObject(options)
|
46
|
-
|
47
|
-
hydra = Typhoeus::Hydra.new
|
48
|
-
hydra.queue request
|
49
|
-
hydra.run # this is a blocking call that returns once all requests are complete
|
50
|
-
|
51
|
-
# the response object will be set after the request is run
|
52
|
-
response = request.response
|
53
|
-
|
54
|
-
# the response can be one of three codes:
|
55
|
-
# 200 (success)
|
56
|
-
# 401 (auth failed)
|
57
|
-
# 503 (retry later with exponential backoff)
|
58
|
-
# see more documentation here: http://code.google.com/android/c2dm/#testing
|
59
|
-
if response.code.eql? 200
|
60
|
-
|
61
|
-
# look for the header 'Update-Client-Auth' in the response you get after sending
|
62
|
-
# a message. It indicates that this is the token to be used for the next message to send.
|
63
|
-
if response.headers_hash['Update-Client-Auth']
|
64
|
-
@auth_token = response.headers_hash['Update-Client-Auth']
|
65
65
|
end
|
66
|
-
|
66
|
+
end
|
67
67
|
|
68
|
-
|
68
|
+
def get_auth_token(email, password)
|
69
|
+
data = "accountType=HOSTED_OR_GOOGLE&Email=#{email}&Passwd=#{password}&service=ac2dm"
|
70
|
+
headers = { "Content-type" => "application/x-www-form-urlencoded",
|
71
|
+
"Content-length" => "#{data.length}"}
|
69
72
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
73
|
+
uri = URI.parse(AUTH_URL)
|
74
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
75
|
+
http.use_ssl = true
|
76
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
74
77
|
|
75
|
-
|
76
|
-
return
|
78
|
+
response, body = http.post(uri.path, data, headers)
|
79
|
+
return body.split("\n")[2].gsub("Auth=", "")
|
80
|
+
end
|
77
81
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
+
def notificationRequest(options)
|
83
|
+
data = {}
|
84
|
+
options.each do |key, value|
|
85
|
+
if [:registration_id, :collapse_key].include? key
|
86
|
+
data[key] = value
|
87
|
+
else
|
88
|
+
data["data.#{key}"] = value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
data = data.map{|k, v| "&#{k}=#{URI.escape(v.to_s)}"}.reduce{|k, v| k + v}
|
93
|
+
headers = { "Authorization" => "GoogleLogin auth=#{@auth_token}",
|
94
|
+
"Content-length" => "#{data.length}" }
|
95
|
+
uri = URI.parse(PUSH_URL)
|
96
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
97
|
+
http.use_ssl = true
|
98
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
82
99
|
|
100
|
+
http.post(uri.path, data, headers)
|
83
101
|
end
|
102
|
+
|
84
103
|
end
|
85
104
|
|
86
|
-
def requestObject(options)
|
87
|
-
payload = {}
|
88
|
-
payload[:registration_id] = options.delete(:registration_id)
|
89
|
-
payload[:collapse_key] = options.delete(:collapse_key)
|
90
|
-
options.each {|key, value| payload["data.#{key}"] = value}
|
91
|
-
|
92
|
-
Typhoeus::Request.new(PUSH_URL, {
|
93
|
-
:method => :post,
|
94
|
-
:params => payload,
|
95
|
-
:headers => {
|
96
|
-
'Authorization' => "GoogleLogin auth=#{@auth_token}"
|
97
|
-
}
|
98
|
-
})
|
99
|
-
end
|
100
|
-
|
101
105
|
end
|
102
|
-
|
103
106
|
end
|
data/speedy_c2dm.gemspec
CHANGED
data/test/test_speedy_c2dm.rb
CHANGED
@@ -3,35 +3,34 @@ require File.join(current_dir, 'helper')
|
|
3
3
|
|
4
4
|
class TestSpeedyC2DM < Test::Unit::TestCase
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
TEST_REGISTRATION_ID = "TODO - Fill me"
|
6
|
+
API_ACCOUNT_EMAIL = "TODO - Fill me"
|
7
|
+
API_ACCOUNT_PASSWORD = "TODO - Fill me"
|
8
|
+
TEST_PHONE_C2DM_REGISTRATION_ID = "TODO - Fill me"
|
10
9
|
|
11
10
|
should "not raise an error if the API key is valid" do
|
12
11
|
assert_nothing_raised do
|
13
|
-
SpeedyC2DM::API.
|
12
|
+
SpeedyC2DM::API.set_account(API_ACCOUNT_EMAIL, API_ACCOUNT_PASSWORD)
|
14
13
|
end
|
15
14
|
end
|
16
15
|
|
17
16
|
should "raise an error if the email/password is not provided" do
|
18
17
|
assert_raise(ArgumentError) do
|
19
|
-
SpeedyC2DM::API.
|
18
|
+
SpeedyC2DM::API.set_account()
|
20
19
|
end
|
21
20
|
end
|
22
21
|
|
23
22
|
should "not raise an error if a send notification call succeeds" do
|
24
23
|
assert_nothing_raised do
|
25
|
-
|
24
|
+
SpeedyC2DM::API.set_account(API_ACCOUNT_EMAIL, API_ACCOUNT_PASSWORD)
|
26
25
|
|
27
26
|
options = {
|
28
|
-
:registration_id =>
|
27
|
+
:registration_id => TEST_PHONE_C2DM_REGISTRATION_ID,
|
29
28
|
:message => "Hi!",
|
30
29
|
:extra_data => 42,
|
31
30
|
:collapse_key => "some-collapse-key"
|
32
31
|
}
|
33
32
|
|
34
|
-
response =
|
33
|
+
response = SpeedyC2DM::API.send_notification(options)
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: speedy_c2dm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,20 +9,9 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-11-03 00:00:00.000000000 -04:00
|
13
13
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
- !ruby/object:Gem::Dependency
|
16
|
-
name: typhoeus
|
17
|
-
requirement: &2153057240 !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
|
-
requirements:
|
20
|
-
- - ! '>='
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: '0'
|
23
|
-
type: :runtime
|
24
|
-
prerelease: false
|
25
|
-
version_requirements: *2153057240
|
14
|
+
dependencies: []
|
26
15
|
description: Speedy C2DM efficiently sends push notifications to Android devices via
|
27
16
|
google c2dm.
|
28
17
|
email:
|