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 CHANGED
@@ -2,3 +2,4 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ .rvmrc
data/README.markdown CHANGED
@@ -1,12 +1,14 @@
1
- # c2dm
1
+ # Speedy C2DM
2
2
 
3
- c2dm sends push notifications to Android devices via google [c2dm](http://code.google.com/android/c2dm/index.html).
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 the server needs to fetch and store authenticaion tokens 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 method 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 notification.
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 initialized. 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 of these, it will immediately request new tokens from Google servers. In the case of status 503 (service unavailable), a return message indicating 503 is returned. It is suggested that you retry after exponential back-off in the case of 503 (using something like [resque-retry](https://github.com/lantins/resque-retry)).
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 google account registered for c2dm.
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
- c2dm = SpeedyC2DM::API.new(TEST_EMAIL, TEST_PASSWORD)
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
- options = {
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
- response = c2dm.send_notification(options)
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
- Note: there are blocking calls in both .new() and .send_notification(). You should use an async queue like Resque to ensure non-blocking behavior in your application code.
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
- to test, first fill out these variables in test/test_speedy_c2dm.rb:
38
- TEST_EMAIL = "TODO - Fill me"
39
- TEST_PASSWORD = "TODO - Fill me"
40
- TEST_REGISTRATION_ID = "TODO - Fill me"
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
- $ ruby test/test_speedy_c2dm.rb
57
+
58
+ $ ruby test/test_speedy_c2dm.rb
44
59
 
45
60
  ##Copyrights
46
61
 
@@ -1,3 +1,3 @@
1
1
  module SpeedyC2dm
2
- VERSION = "0.1.1"
2
+ VERSION = "1.0.0"
3
3
  end
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
- @auth_token = get_auth_token(@email, @password)
16
- end
9
+ class << self
17
10
 
18
- def get_auth_token(email, password)
19
- post_body = "accountType=HOSTED_OR_GOOGLE&Email=#{email}&Passwd=#{password}&service=ac2dm"
20
- params = {
21
- :body => post_body,
22
- :headers => {
23
- 'Content-type' => 'application/x-www-form-urlencoded',
24
- 'Content-length' => "#{post_body.length}"
25
- }
26
- }
27
- response = Typhoeus::Request.post(AUTH_URL, params)
28
- return response.body.split("\n")[2].gsub("Auth=", "")
29
- end
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
- return response.inspect
66
+ end
67
67
 
68
- elsif response.code.eql? 401
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
- # auth failed. Refresh auth key and requeue
71
- @auth_token = get_auth_token(@email, @password)
72
- hydra.queue request(options)
73
- hydra.run # this is a blocking call that returns once all requests are complete
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
- response_inner = request.response
76
- return response.inspect
78
+ response, body = http.post(uri.path, data, headers)
79
+ return body.split("\n")[2].gsub("Auth=", "")
80
+ end
77
81
 
78
- elsif response.code.eql? 503
79
-
80
- # service un-available.
81
- return response.inspect
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
@@ -17,6 +17,4 @@ Gem::Specification.new do |s|
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
-
21
- s.add_dependency "typhoeus"
22
20
  end
@@ -3,35 +3,34 @@ require File.join(current_dir, 'helper')
3
3
 
4
4
  class TestSpeedyC2DM < Test::Unit::TestCase
5
5
 
6
- TEST_EMAIL = "TODO - Fill me"
7
- TEST_PASSWORD = "TODO - Fill me"
8
- INVALID_TEST_EMAIL = "foo-bar.com"
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.new(TEST_EMAIL, TEST_PASSWORD)
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.new()
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
- speedyC2DM = SpeedyC2DM::API.new(TEST_EMAIL, TEST_PASSWORD)
24
+ SpeedyC2DM::API.set_account(API_ACCOUNT_EMAIL, API_ACCOUNT_PASSWORD)
26
25
 
27
26
  options = {
28
- :registration_id => TEST_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 = speedyC2DM.send_notification(options)
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.1.1
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-08-01 00:00:00.000000000 -04:00
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: