├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── gcm.gemspec ├── lib └── gcm.rb └── spec ├── gcm_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | 4 | # rdoc generated 5 | rdoc 6 | 7 | # yard generated 8 | doc 9 | .yardoc 10 | 11 | # bundler 12 | .bundle 13 | 14 | # jeweler generated 15 | pkg 16 | 17 | # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 18 | # 19 | # * Create a file at ~/.gitignore 20 | # * Include files you want ignored 21 | # * Run: git config --global core.excludesfile ~/.gitignore 22 | # 23 | # After doing this, these files will be ignored in all your git projects, 24 | # saving you from having to 'pollute' every project you touch with them 25 | # 26 | # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) 27 | # 28 | # For MacOS: 29 | # 30 | .DS_Store 31 | # 32 | # For TextMate 33 | #*.tmproj 34 | #tmtags 35 | # 36 | # For emacs: 37 | #*~ 38 | #\#* 39 | #.\#* 40 | # 41 | # For vim: 42 | #*.swp 43 | 44 | bin 45 | cache 46 | gems 47 | specifications 48 | Gemfile.lock 49 | .rvmrc 50 | spec/reports 51 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.0.0 4 | - 2.1.8 5 | - 2.2.4 6 | - 2.3.0 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | 4 | gem 'rake' 5 | gem 'rspec' 6 | gem 'webmock' 7 | gem 'ci_reporter_rspec' 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Kashif Rasul and Shoaib Burq 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google Cloud Messaging is now deprecated. 2 | 3 | [Google Cloud Messaging has been deprecated in favor of Firebase Cloud Messaging](https://developers.google.com/cloud-messaging/android/android-migrate-fcm). Please use the [`fcm` gem](https://github.com/spacialdb/fcm) moving forward. 4 | 5 | > As of April 10, 2018, Google has deprecated GCM. The GCM server and client APIs are deprecated and will be removed as soon as April 11, 2019. Migrate GCM apps to [Firebase Cloud Messaging](http://firebase.google.com/docs/cloud-messaging/) (FCM), which inherits the reliable and scalable GCM infrastructure, plus many new features. See the [migration guide](https://developers.google.com/cloud-messaging/android/android-migrate-fcm) to learn more. 6 | 7 | ----- 8 | 9 | # Google Cloud Messaging (GCM) for Android and iOS 10 | [![Gem Version](https://badge.fury.io/rb/gcm.svg)](http://badge.fury.io/rb/gcm) [![Build Status](https://secure.travis-ci.org/spacialdb/gcm.png?branch=master)](http://travis-ci.org/spacialdb/gcm) 11 | 12 | The GCM gem lets your ruby backend send notifications to Android and iOS devices via [ 13 | Google Cloud Messaging](https://developers.google.com/cloud-messaging/gcm). 14 | 15 | ## Installation 16 | 17 | $ gem install gcm 18 | 19 | or in your `Gemfile` just include it: 20 | 21 | ```ruby 22 | gem 'gcm' 23 | ``` 24 | 25 | ## Requirements 26 | 27 | An Android device running 2.3 (or newer) or an iOS device and an API key as per [GCM getting started guide](https://developers.google.com/cloud-messaging/android/start). 28 | 29 | One of the following, tested Ruby versions: 30 | 31 | * `2.0.0` 32 | * `2.1.8` 33 | * `2.2.4` 34 | * `2.3.0` 35 | 36 | ## Usage 37 | 38 | For your server to send a message to one or more devices, you must first initialise a new `GCM` class with your Api key, and then call the `send` method on this and give it 1 or more (up to 1000) registration tokens as an array of strings. You can also optionally send further [HTTP message parameters](https://developers.google.com/cloud-messaging/http-server-ref) like `data` or `time_to_live` etc. as a hash via the second optional argument to `send`. 39 | 40 | Example sending notifications: 41 | 42 | ```ruby 43 | require 'gcm' 44 | 45 | gcm = GCM.new("my_api_key") 46 | # you can set option parameters in here 47 | # - all options are pass to HTTParty method arguments 48 | # - ref: https://github.com/jnunemaker/httparty/blob/master/lib/httparty.rb#L29-L60 49 | # gcm = GCM.new("my_api_key", timeout: 3) 50 | 51 | registration_ids= ["12", "13"] # an array of one or more client registration tokens 52 | options = {data: {score: "123"}, collapse_key: "updated_score"} 53 | response = gcm.send(registration_ids, options) 54 | ``` 55 | 56 | Currently `response` is just a hash containing the response `body`, `headers` and `status`. Check [here](https://developers.google.com/cloud-messaging/http#response) to see how to interpret the responses. 57 | 58 | ## Device Group Messaging 59 | 60 | With [device group messaging](https://developers.google.com/cloud-messaging/notifications), you can send a single message to multiple instance of an app running on devices belonging to a group. Typically, "group" refers a set of different devices that belong to a single user. However, a group could also represent a set of devices where the app instance functions in a highly correlated manner. To use this feature, you will first need an initialised `GCM` class. 61 | 62 | ### Generate a Notification Key for device group 63 | Then you will need a notification key which you can create for a particular `key_name` which needs to be uniquely named per app in case you have multiple apps for the same `project_id`. This ensures that notifications only go to the intended target app. The `create` method will do this and return the token `notification_key`, that represents the device group, in the response: 64 | 65 | ```ruby 66 | response = gcm.create(key_name: "appUser-Chris", 67 | project_id: "my_project_id", # https://developers.google.com/cloud-messaging/gcm#senderid 68 | registration_ids:["4", "8", "15", "16", "23", "42"]) 69 | ``` 70 | 71 | ### Send to Notification Key 72 | Now you can send a message to a particular `notification_key` via the `send_with_notification_key` method. This allows the server to send a single data to multiple app instances (typically on multiple devices) owned by a single user (instead of sending to some registration tokens). Note: the maximum number of members allowed for a `notification_key` is 20. 73 | 74 | ```ruby 75 | response = gcm.send_with_notification_key("notification_key", { 76 | data: {score: "3x1"}, 77 | collapse_key: "updated_score"}) 78 | ``` 79 | 80 | ### Add/Remove Registration Tokens 81 | 82 | You can also add/remove registration Tokens to/from a particular `notification_key` of some `project_id`. For example: 83 | 84 | ```ruby 85 | response = gcm.add(key_name: "appUser-Chris", 86 | project_id: "my_project_id", 87 | notification_key:"appUser-Chris-key", 88 | registration_ids:["7", "3"]) 89 | 90 | response = gcm.remove(key_name: "appUser-Chris", 91 | project_id: "my_project_id", 92 | notification_key:"appUser-Chris-key", 93 | registration_ids:["8", "15"]) 94 | ``` 95 | 96 | ## Send Messages to Topics 97 | 98 | GCM topic messaging allows your app server to send a message to multiple devices that have opted in to a particular topic. Based on the publish/subscribe model, topic messaging supports unlimited subscriptions per app. Sending to a topic is very similar to sending to an individual device or to a user group, in the sense that you can use the `gcm.send_with_notification_key()` method where the `noticiation_key` matches the regular expression `"/topics/[a-zA-Z0-9-_.~%]+"`: 99 | 100 | ```ruby 101 | response = gcm.send_with_notification_key("/topics/yourTopic", { 102 | data: {message: "This is a GCM Topic Message!"}) 103 | ``` 104 | 105 | Or you can use the helper: 106 | 107 | ```ruby 108 | response = gcm.send_to_topic("yourTopic", { 109 | data: {message: "This is a GCM Topic Message!"}) 110 | ``` 111 | 112 | ## Blog Posts 113 | 114 | * [How to send iOS and Android notifications from your Rails backend](http://juretriglav.si/how-to-send-ios-and-android-notifications-from-your-rails-backend/) 115 | * [Как отправлять push уведомления из Вашего Rails приложения](http://habrahabr.ru/post/214607/) 116 | * [GCM – 서버 만들기](http://susemi99.kr/1023) 117 | * [ruby から gcm を使って android 端末へメッセージを送信する](http://qiita.com/ma2saka/items/5852308b7c2855eef552) 118 | * [titanium alloy android push通知 by ruby](http://shoprev.hatenablog.com/entry/2014/08/30/202531) 119 | * [Android Push Notifications via Rails](http://azukiweb.com/blog/2015/android-push-nots/) 120 | 121 | ## Mobile Clients 122 | 123 | You can find a guide to implement an Android Client app to receive notifications here: [Set up a GCM Client App on Android](https://developers.google.com/cloud-messaging/android/client). 124 | 125 | The guide to set up an iOS app to get notifications is here: [Setting up a GCM Client App on iOS](https://developers.google.com/cloud-messaging/ios/client). 126 | 127 | ## ChangeLog 128 | 129 | ### 0.1.1 130 | 131 | * Added helper `send_to_topic` to send messages to [topics](https://developers.google.com/cloud-messaging/topic-messaging). 132 | * Fixed documentation and updated base uri to `https://gcm-http.googleapis.com/gcm` 133 | 134 | ### 0.1.0 135 | * Added `send_with_notification_key` to send message to a notification key since the documented API for it is [wrong]( http://stackoverflow.com/questions/19720767/gcm-user-notifications-missing-registration-ids-field/25183892#25183892). 136 | 137 | ### 0.0.9 138 | * Check for [NotRegistered](http://developer.android.com/google/gcm/adv.html#unreg) error and return unregistered ids if this occurs 139 | 140 | ### 0.0.8 141 | * Added support for User Notifications API 142 | * Added alias method `send` for `send_notification` 143 | 144 | ### 0.0.7 145 | * All responses now have a body and header hashes 146 | 147 | ### 0.0.6 148 | * You can initialise GCM class with [HTTParty Options](https://github.com/jnunemaker/httparty/blob/master/lib/httparty.rb#L41-L69) 149 | 150 | ### 0.0.5 151 | * Added support for [canonical registration ID](http://developer.android.com/google/gcm/adv.html#canonical) 152 | * Only support Ruby versions [>= 1.9.3](https://www.ruby-lang.org/en/news/2014/01/10/ruby-1-9-3-will-end-on-2015/) 153 | * Fixed Rspec deprecation warnings for Rspec 3.0.0.beta 154 | 155 | ##MIT License 156 | 157 | * Copyright (c) 2016 Kashif Rasul and Shoaib Burq. See LICENSE.txt for details. 158 | 159 | ##Many thanks to all the contributors 160 | 161 | * [Contributors](https://github.com/spacialdb/gcm/contributors) 162 | 163 | ## Donations 164 | We accept tips through [Gratipay](https://gratipay.com/spacialdb/). 165 | 166 | [![Gratipay](https://img.shields.io/gratipay/spacialdb.svg)](https://www.gittip.com/spacialdb/) 167 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | require "bundler/gem_tasks" 3 | require "rake/tasklib" 4 | require 'ci/reporter/rake/rspec' 5 | 6 | RSpec::Core::RakeTask.new(:spec => ["ci:setup:rspec"]) do |t| 7 | t.pattern = 'spec/**/*_spec.rb' 8 | end 9 | 10 | RSpec::Core::RakeTask.new(:spec) do |spec| 11 | spec.pattern = 'spec/**/*_spec.rb' 12 | spec.rspec_opts = ['--format documentation'] 13 | end 14 | 15 | task :default => :spec 16 | -------------------------------------------------------------------------------- /gcm.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "gcm" 6 | s.version = "0.1.1" 7 | s.platform = Gem::Platform::RUBY 8 | s.authors = ["Kashif Rasul", "Shoaib Burq"] 9 | s.email = ["kashif@spacialdb.com", "shoaib@spacialdb.com"] 10 | s.homepage = "https://github.com/spacialdb/gcm" 11 | s.summary = %q{send data to Android applications on Android devices} 12 | s.description = %q{gcm is a service that helps developers send data from servers to their Android applications on Android devices.} 13 | s.license = "MIT" 14 | 15 | s.required_ruby_version = '>= 1.9.3' 16 | 17 | s.rubyforge_project = "gcm" 18 | 19 | s.files = `git ls-files`.split("\n") 20 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 21 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 22 | s.require_paths = ["lib"] 23 | 24 | s.add_dependency('httparty') 25 | s.add_dependency('json') 26 | end 27 | -------------------------------------------------------------------------------- /lib/gcm.rb: -------------------------------------------------------------------------------- 1 | require 'httparty' 2 | require 'cgi' 3 | require 'json' 4 | 5 | class GCM 6 | include HTTParty 7 | base_uri 'https://gcm-http.googleapis.com/gcm' 8 | default_timeout 30 9 | format :json 10 | 11 | attr_accessor :timeout, :api_key 12 | 13 | def initialize(api_key, client_options = {}) 14 | @api_key = api_key 15 | @client_options = client_options 16 | end 17 | 18 | # { 19 | # "collapse_key": "score_update", 20 | # "time_to_live": 108, 21 | # "delay_while_idle": true, 22 | # "registration_ids": ["4", "8", "15", "16", "23", "42"], 23 | # "data" : { 24 | # "score": "5x1", 25 | # "time": "15:10" 26 | # } 27 | # } 28 | # gcm = GCM.new("API_KEY") 29 | # gcm.send(registration_ids: ["4sdsx", "8sdsd"], {data: {score: "5x1"}}) 30 | def send_notification(registration_ids, options = {}) 31 | post_body = build_post_body(registration_ids, options) 32 | 33 | params = { 34 | body: post_body.to_json, 35 | headers: { 36 | 'Authorization' => "key=#{@api_key}", 37 | 'Content-Type' => 'application/json' 38 | } 39 | } 40 | response = self.class.post('/send', params.merge(@client_options)) 41 | build_response(response, registration_ids) 42 | end 43 | alias send send_notification 44 | 45 | def create_notification_key(key_name, project_id, registration_ids = []) 46 | post_body = build_post_body(registration_ids, operation: 'create', 47 | notification_key_name: key_name) 48 | 49 | params = { 50 | body: post_body.to_json, 51 | headers: { 52 | 'Content-Type' => 'application/json', 53 | 'project_id' => project_id, 54 | 'Authorization' => "key=#{@api_key}" 55 | } 56 | } 57 | 58 | response = self.class.post('/notification', params.merge(@client_options)) 59 | build_response(response) 60 | end 61 | alias create create_notification_key 62 | 63 | def add_registration_ids(key_name, project_id, notification_key, registration_ids) 64 | post_body = build_post_body(registration_ids, operation: 'add', 65 | notification_key_name: key_name, 66 | notification_key: notification_key) 67 | 68 | params = { 69 | body: post_body.to_json, 70 | headers: { 71 | 'Content-Type' => 'application/json', 72 | 'project_id' => project_id, 73 | 'Authorization' => "key=#{@api_key}" 74 | } 75 | } 76 | 77 | response = self.class.post('/notification', params.merge(@client_options)) 78 | build_response(response) 79 | end 80 | alias add add_registration_ids 81 | 82 | def remove_registration_ids(key_name, project_id, notification_key, registration_ids) 83 | post_body = build_post_body(registration_ids, operation: 'remove', 84 | notification_key_name: key_name, 85 | notification_key: notification_key) 86 | 87 | params = { 88 | body: post_body.to_json, 89 | headers: { 90 | 'Content-Type' => 'application/json', 91 | 'project_id' => project_id, 92 | 'Authorization' => "key=#{@api_key}" 93 | } 94 | } 95 | 96 | response = self.class.post('/notification', params.merge(@client_options)) 97 | build_response(response) 98 | end 99 | alias remove remove_registration_ids 100 | 101 | def send_with_notification_key(notification_key, options = {}) 102 | body = { to: notification_key }.merge(options) 103 | 104 | params = { 105 | body: body.to_json, 106 | headers: { 107 | 'Authorization' => "key=#{@api_key}", 108 | 'Content-Type' => 'application/json' 109 | } 110 | } 111 | response = self.class.post('/send', params.merge(@client_options)) 112 | build_response(response) 113 | end 114 | 115 | def send_to_topic(topic, options = {}) 116 | if topic =~ /[a-zA-Z0-9\-_.~%]+/ 117 | send_with_notification_key('/topics/' + topic, options) 118 | end 119 | end 120 | 121 | private 122 | 123 | def build_post_body(registration_ids, options = {}) 124 | { registration_ids: registration_ids }.merge(options) 125 | end 126 | 127 | def build_response(response, registration_ids = []) 128 | body = response.body || {} 129 | response_hash = { body: body, headers: response.headers, status_code: response.code } 130 | case response.code 131 | when 200 132 | response_hash[:response] = 'success' 133 | body = JSON.parse(body) unless body.empty? 134 | response_hash[:canonical_ids] = build_canonical_ids(body, registration_ids) unless registration_ids.empty? 135 | response_hash[:not_registered_ids] = build_not_registered_ids(body, registration_ids) unless registration_ids.empty? 136 | when 400 137 | response_hash[:response] = 'Only applies for JSON requests. Indicates that the request could not be parsed as JSON, or it contained invalid fields.' 138 | when 401 139 | response_hash[:response] = 'There was an error authenticating the sender account.' 140 | when 503 141 | response_hash[:response] = 'Server is temporarily unavailable.' 142 | when 500..599 143 | response_hash[:response] = 'There was an internal error in the GCM server while trying to process the request.' 144 | end 145 | response_hash 146 | end 147 | 148 | def build_canonical_ids(body, registration_ids) 149 | canonical_ids = [] 150 | unless body.empty? 151 | if body['canonical_ids'] > 0 152 | body['results'].each_with_index do |result, index| 153 | canonical_ids << { old: registration_ids[index], new: result['registration_id'] } if has_canonical_id?(result) 154 | end 155 | end 156 | end 157 | canonical_ids 158 | end 159 | 160 | def build_not_registered_ids(body, registration_id) 161 | not_registered_ids = [] 162 | unless body.empty? 163 | if body['failure'] > 0 164 | body['results'].each_with_index do |result, index| 165 | not_registered_ids << registration_id[index] if is_not_registered?(result) 166 | end 167 | end 168 | end 169 | not_registered_ids 170 | end 171 | 172 | def has_canonical_id?(result) 173 | !result['registration_id'].nil? 174 | end 175 | 176 | def is_not_registered?(result) 177 | result['error'] == 'NotRegistered' 178 | end 179 | end 180 | -------------------------------------------------------------------------------- /spec/gcm_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GCM do 4 | let(:send_url) { "#{GCM.base_uri}/send" } 5 | 6 | it 'should raise an error if the api key is not provided' do 7 | expect { GCM.new }.to raise_error 8 | end 9 | 10 | it 'should raise error if time_to_live is given' do 11 | # ref: http://developer.android.com/guide/google/gcm/gcm.html#send-msg 12 | end 13 | 14 | describe 'sending notification' do 15 | let(:api_key) { 'AIzaSyB-1uEai2WiUapxCs2Q0GZYzPu7Udno5aA' } 16 | let(:registration_ids) { ['42'] } 17 | let(:valid_request_body) do 18 | { registration_ids: registration_ids } 19 | end 20 | let(:valid_request_headers) do 21 | { 22 | 'Content-Type' => 'application/json', 23 | 'Authorization' => "key=#{api_key}" 24 | } 25 | end 26 | 27 | let(:stub_gcm_send_request) do 28 | stub_request(:post, send_url).with( 29 | body: valid_request_body.to_json, 30 | headers: valid_request_headers 31 | ).to_return( 32 | # ref: http://developer.android.com/guide/google/gcm/gcm.html#success 33 | body: '{}', 34 | headers: {}, 35 | status: 200 36 | ) 37 | end 38 | 39 | let(:stub_gcm_send_request_with_basic_auth) do 40 | uri = URI.parse(send_url) 41 | uri.user = 'a' 42 | uri.password = 'b' 43 | stub_request(:post, uri.to_s).to_return(body: '{}', headers: {}, status: 200) 44 | end 45 | 46 | before(:each) do 47 | stub_gcm_send_request 48 | stub_gcm_send_request_with_basic_auth 49 | end 50 | 51 | it 'should send notification using POST to GCM server' do 52 | gcm = GCM.new(api_key) 53 | gcm.send(registration_ids).should eq(response: 'success', body: '{}', headers: {}, status_code: 200, canonical_ids: [], not_registered_ids: []) 54 | stub_gcm_send_request.should have_been_made.times(1) 55 | end 56 | 57 | it 'should use basic authentication provided by options' do 58 | gcm = GCM.new(api_key, basic_auth: { username: 'a', password: 'b' }) 59 | gcm.send(registration_ids).should eq(response: 'success', body: '{}', headers: {}, status_code: 200, canonical_ids: [], not_registered_ids: []) 60 | stub_gcm_send_request_with_basic_auth.should have_been_made.times(1) 61 | end 62 | 63 | context 'send notification with data' do 64 | let!(:stub_with_data) do 65 | stub_request(:post, send_url) 66 | .with(body: '{"registration_ids":["42"],"data":{"score":"5x1","time":"15:10"}}', 67 | headers: valid_request_headers) 68 | .to_return(status: 200, body: '', headers: {}) 69 | end 70 | before do 71 | end 72 | it 'should send the data in a post request to gcm' do 73 | gcm = GCM.new(api_key) 74 | gcm.send(registration_ids, data: { score: '5x1', time: '15:10' }) 75 | stub_with_data.should have_been_requested 76 | end 77 | end 78 | 79 | context 'when send_notification responds with failure' do 80 | let(:mock_request_attributes) do 81 | { 82 | body: valid_request_body.to_json, 83 | headers: valid_request_headers 84 | } 85 | end 86 | 87 | subject { GCM.new(api_key) } 88 | 89 | context 'on failure code 400' do 90 | before do 91 | stub_request(:post, send_url).with( 92 | mock_request_attributes 93 | ).to_return( 94 | # ref: http://developer.android.com/guide/google/gcm/gcm.html#success 95 | body: '{}', 96 | headers: {}, 97 | status: 400 98 | ) 99 | end 100 | it 'should not send notification due to 400' do 101 | subject.send(registration_ids).should eq(body: '{}', 102 | headers: {}, 103 | response: 'Only applies for JSON requests. Indicates that the request could not be parsed as JSON, or it contained invalid fields.', 104 | status_code: 400) 105 | end 106 | end 107 | 108 | context 'on failure code 401' do 109 | before do 110 | stub_request(:post, send_url).with( 111 | mock_request_attributes 112 | ).to_return( 113 | # ref: http://developer.android.com/guide/google/gcm/gcm.html#success 114 | body: '{}', 115 | headers: {}, 116 | status: 401 117 | ) 118 | end 119 | 120 | it 'should not send notification due to 401' do 121 | subject.send(registration_ids).should eq(body: '{}', 122 | headers: {}, 123 | response: 'There was an error authenticating the sender account.', 124 | status_code: 401) 125 | end 126 | end 127 | 128 | context 'on failure code 503' do 129 | before do 130 | stub_request(:post, send_url).with( 131 | mock_request_attributes 132 | ).to_return( 133 | # ref: http://developer.android.com/guide/google/gcm/gcm.html#success 134 | body: '{}', 135 | headers: {}, 136 | status: 503 137 | ) 138 | end 139 | 140 | it 'should not send notification due to 503' do 141 | subject.send(registration_ids).should eq(body: '{}', 142 | headers: {}, 143 | response: 'Server is temporarily unavailable.', 144 | status_code: 503) 145 | end 146 | end 147 | 148 | context 'on failure code 5xx' do 149 | before do 150 | stub_request(:post, send_url).with( 151 | mock_request_attributes 152 | ).to_return( 153 | # ref: http://developer.android.com/guide/google/gcm/gcm.html#success 154 | body: '{"body-key" => "Body value"}', 155 | headers: { 'header-key' => 'Header value' }, 156 | status: 599 157 | ) 158 | end 159 | 160 | it 'should not send notification due to 599' do 161 | subject.send(registration_ids).should eq(body: '{"body-key" => "Body value"}', 162 | headers: { 'header-key' => ['Header value'] }, 163 | response: 'There was an internal error in the GCM server while trying to process the request.', 164 | status_code: 599) 165 | end 166 | end 167 | end 168 | 169 | context 'when send_notification responds canonical_ids' do 170 | let(:mock_request_attributes) do 171 | { 172 | body: valid_request_body.to_json, 173 | headers: valid_request_headers 174 | } 175 | end 176 | 177 | let(:valid_response_body_with_canonical_ids) do 178 | { 179 | failure: 0, canonical_ids: 1, results: [{ registration_id: '43', message_id: '0:1385025861956342%572c22801bb3' }] 180 | } 181 | end 182 | 183 | subject { GCM.new(api_key) } 184 | 185 | before do 186 | stub_request(:post, send_url).with( 187 | mock_request_attributes 188 | ).to_return( 189 | # ref: http://developer.android.com/guide/google/gcm/gcm.html#success 190 | body: valid_response_body_with_canonical_ids.to_json, 191 | headers: {}, 192 | status: 200 193 | ) 194 | end 195 | 196 | it 'should contain canonical_ids' do 197 | response = subject.send(registration_ids) 198 | 199 | response.should eq(headers: {}, 200 | canonical_ids: [{ old: '42', new: '43' }], 201 | not_registered_ids: [], 202 | status_code: 200, 203 | response: 'success', 204 | body: '{"failure":0,"canonical_ids":1,"results":[{"registration_id":"43","message_id":"0:1385025861956342%572c22801bb3"}]}') 205 | end 206 | end 207 | 208 | context 'when send_notification responds with NotRegistered' do 209 | subject { GCM.new(api_key) } 210 | 211 | let(:mock_request_attributes) do 212 | { 213 | body: valid_request_body.to_json, 214 | headers: valid_request_headers 215 | } 216 | end 217 | 218 | let(:valid_response_body_with_not_registered_ids) do 219 | { 220 | canonical_ids: 0, failure: 1, results: [{ error: 'NotRegistered' }] 221 | } 222 | end 223 | 224 | before do 225 | stub_request(:post, send_url).with( 226 | mock_request_attributes 227 | ).to_return( 228 | body: valid_response_body_with_not_registered_ids.to_json, 229 | headers: {}, 230 | status: 200 231 | ) 232 | end 233 | 234 | it 'should contain not_registered_ids' do 235 | response = subject.send(registration_ids) 236 | response.should eq( 237 | headers: {}, 238 | canonical_ids: [], 239 | not_registered_ids: registration_ids, 240 | status_code: 200, 241 | response: 'success', 242 | body: '{"canonical_ids":0,"failure":1,"results":[{"error":"NotRegistered"}]}' 243 | ) 244 | end 245 | end 246 | end 247 | end 248 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | require 'webmock/rspec' 4 | 5 | require 'gcm' 6 | 7 | RSpec.configure do |config| 8 | config.run_all_when_everything_filtered = true 9 | config.expect_with :rspec do |c| 10 | c.syntax = [:should, :expect] 11 | end 12 | # config.filter_run :focus 13 | end 14 | --------------------------------------------------------------------------------