diff --git a/lib/fcm.rb b/lib/fcm.rb index 87a3cfd..bcf421e 100644 --- a/lib/fcm.rb +++ b/lib/fcm.rb @@ -8,7 +8,6 @@ class FCM FORMAT = :json # constants - GROUP_NOTIFICATION_BASE_URI = 'https://android.googleapis.com' INSTANCE_ID_API = 'https://iid.googleapis.com' TOPIC_REGEX = /[a-zA-Z0-9\-_.~%]+/ @@ -34,6 +33,7 @@ def initialize(api_key, client_options = {}) def send_notification(registration_ids, options = {}) post_body = build_post_body(registration_ids, options) + for_uri(BASE_URI) do |connection| response = connection.post('/fcm/send', post_body.to_json) build_response(response, registration_ids) @@ -45,14 +45,7 @@ def create_notification_key(key_name, project_id, registration_ids = []) post_body = build_post_body(registration_ids, operation: 'create', notification_key_name: key_name) - extra_headers = { - 'project_id' => project_id - } - - for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection| - response = connection.post('/gcm/notification', post_body.to_json) - build_response(response) - end + update_group_messaging_setting(post_body, project_id) end alias create create_notification_key @@ -61,14 +54,7 @@ def add_registration_ids(key_name, project_id, notification_key, registration_id notification_key_name: key_name, notification_key: notification_key) - extra_headers = { - 'project_id' => project_id - } - - for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection| - response = connection.post('/gcm/notification', post_body.to_json) - build_response(response) - end + update_group_messaging_setting(post_body, project_id) end alias add add_registration_ids @@ -77,14 +63,7 @@ def remove_registration_ids(key_name, project_id, notification_key, registration notification_key_name: key_name, notification_key: notification_key) - extra_headers = { - 'project_id' => project_id - } - - for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection| - response = connection.post('/gcm/notification', post_body.to_json) - build_response(response) - end + update_group_messaging_setting(post_body, project_id) end alias remove remove_registration_ids @@ -94,13 +73,13 @@ def recover_notification_key(key_name, project_id) notification_key_name: key_name } } - + extra_headers = { 'project_id' => project_id } - for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection| - response = connection.get('/gcm/notification', params) + for_uri(BASE_URI, extra_headers) do |connection| + response = connection.get('/fcm/notification', params) build_response(response) end end @@ -144,7 +123,7 @@ def get_instance_id_info iid_token, options={} params = { query: options } - + for_uri(INSTANCE_ID_API) do |connection| response = connection.get('/iid/info/'+iid_token, params) build_response(response) @@ -177,7 +156,22 @@ def send_to_topic_condition(condition, options = {}) private def for_uri(uri, extra_headers = {}) + retry_if_func = lambda do |env, exception| + retryable_errors = [ + "Unavailable", "InternalServerError", + "DeviceMessageRateExceeded", "TopicsMessageRateExceeded"] + if defined?(exception.response) && defined?(exception.response.status) && exception.response.status == 200 + body = JSON.parse(exception.response.body) + body["results"] != nil && body["results"].any? { |result| retryable_errors.include? result["error"]} + else + true + end + end + retryable_exceptions = Faraday::Request::Retry::DEFAULT_EXCEPTIONS connection = ::Faraday.new(:url => uri) do |faraday| + faraday.request :retry, max: 5, interval: 0.1, interval_randomness: 0.5, backoff_factor: 2, + exceptions: retryable_exceptions, retry_statuses: [200, *(500..599)], methods: [], + retry_if: retry_if_func faraday.adapter Faraday.default_adapter faraday.headers["Content-Type"] = "application/json" faraday.headers["Authorization"] = "key=#{api_key}" @@ -238,6 +232,17 @@ def build_not_registered_ids(body, registration_id) not_registered_ids end + def update_group_messaging_setting(body, project_id) + extra_headers = { + 'project_id' => project_id + } + + for_uri(BASE_URI, extra_headers) do |connection| + response = connection.post('/fcm/notification', body.to_json) + build_response(response) + end + end + def execute_notification(body) for_uri(BASE_URI) do |connection| response = connection.post('/fcm/send', body.to_json) diff --git a/spec/fcm_spec.rb b/spec/fcm_spec.rb index d0341b6..60c9527 100644 --- a/spec/fcm_spec.rb +++ b/spec/fcm_spec.rb @@ -2,7 +2,7 @@ describe FCM do let(:send_url) { "#{FCM::BASE_URI}/fcm/send" } - let(:group_notification_base_uri) { "#{FCM::GROUP_NOTIFICATION_BASE_URI}/gcm/notification" } + let(:group_notification_base_uri) { "#{FCM::BASE_URI}/fcm/notification" } let(:api_key) { 'AIzaSyB-1uEai2WiUapxCs2Q0GZYzPu7Udno5aA' } let(:registration_id) { '42' } let(:registration_ids) { ['42'] } @@ -90,7 +90,7 @@ stub_request(:post, send_url) .with(body: '{"registration_ids":["42"],"data":{"score":"5x1","time":"15:10"}}', headers: valid_request_headers) - .to_return(status: 200, body: '', headers: {}) + .to_return(status: 200, body: '{}', headers: {}) end before do end @@ -106,13 +106,13 @@ stub_request(:post, send_url) .with(body: '{"to":"/topics/TopicA","data":{"score":"5x1","time":"15:10"}}', headers: valid_request_headers) - .to_return(status: 200, body: '', headers: {}) + .to_return(status: 200, body: '{}', headers: {}) end let!(:stub_with_invalid_topic) do stub_request(:post, send_url) .with(body: '{"condition":"/topics/TopicA$","data":{"score":"5x1","time":"15:10"}}', headers: valid_request_headers) - .to_return(status: 200, body: '', headers: {}) + .to_return(status: 200, body: '{}', headers: {}) end describe "#send_to_topic" do @@ -135,19 +135,19 @@ stub_request(:post, send_url) .with(body: '{"condition":"\'TopicA\' in topics && (\'TopicB\' in topics || \'TopicC\' in topics)","data":{"score":"5x1","time":"15:10"}}', headers: valid_request_headers) - .to_return(status: 200, body: '', headers: {}) + .to_return(status: 200, body: '{}', headers: {}) end let!(:stub_with_invalid_condition) do stub_request(:post, send_url) .with(body: '{"condition":"\'TopicA\' in topics and some other text (\'TopicB\' in topics || \'TopicC\' in topics)","data":{"score":"5x1","time":"15:10"}}', headers: valid_request_headers) - .to_return(status: 200, body: '', headers: {}) + .to_return(status: 200, body: '{}', headers: {}) end let!(:stub_with_invalid_condition_topic) do stub_request(:post, send_url) .with(body: '{"condition":"\'TopicA$\' in topics","data":{"score":"5x1","time":"15:10"}}', headers: valid_request_headers) - .to_return(status: 200, body: '', headers: {}) + .to_return(status: 200, body: '{}', headers: {}) end describe "#send_to_topic_condition" do @@ -338,6 +338,50 @@ ) end end + + context 'when send_notification responds with 5XX or 200+error:unavailable' do + subject { FCM.new(api_key) } + + let(:valid_response_body) do + { + failure: 0, canonical_ids: 0, results: [{ message_id: '0:1385025861956342%572c22801bb3' }] + } + end + + let(:error_response_body) do + { + failure: 1, canonical_ids: 0, results: [{ error: 'Unavailable' }] + } + end + + + let(:stub_fcm_send_request_unavaible_server) do + i = 0 + stub_request(:post, send_url).with( + body: "{\"registration_ids\":[\"42\"]}", + headers: { + 'Accept'=>'*/*', + 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', + 'User-Agent'=>'Faraday v0.15.4' + }).to_return do |stub| + body = {} + status = ( i == 0) ? 501 : 200 + body = error_response_body if i == 1 + body = valid_response_body if i == 0 + i += 1 + {status: status, headers: { :"Retry-After" => "1"}, body: body.to_json} + end + end + + before(:each) do + stub_fcm_send_request_unavaible_server + end + + it 'should retry 2 times' do + subject.send( registration_ids) + stub_fcm_send_request_unavaible_server.should have_been_made.times(3) + end + end end describe 'sending group notifications' do