From d2b452fc94a3883f76ae256279c3d10cfaeff195 Mon Sep 17 00:00:00 2001 From: grumpymonkey-agency Date: Tue, 28 Oct 2025 14:31:30 +0100 Subject: [PATCH 1/4] Implements getting Firebase project id via service credentials file instead of initialisation mechanism --- lib/fcm.rb | 34 ++++++++++++++++++++++++++-------- spec/fcm_spec.rb | 19 ++++++++++--------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/lib/fcm.rb b/lib/fcm.rb index 3622c77..9dc5acd 100644 --- a/lib/fcm.rb +++ b/lib/fcm.rb @@ -12,13 +12,12 @@ class FCM INSTANCE_ID_API = "https://iid.googleapis.com" TOPIC_REGEX = /[a-zA-Z0-9\-_.~%]+/ - def initialize(json_key_path = "", project_name = "", http_options = {}) + def initialize(json_key_path = "", http_options = {}) @json_key_path = json_key_path - @project_name = project_name @http_options = http_options end - # See https://firebase.google.com/docs/cloud-messaging/send-message + # See https://firebase.google.com/docs/cloud-messaging/send/v1-api # { # "token": "4sdsx", # "notification": { @@ -42,17 +41,17 @@ def initialize(json_key_path = "", project_name = "", http_options = {}) # } # } # } - # fcm = FCM.new(json_key_path, project_name) + # fcm = FCM.new(json_key_path, firebase_project_id) # fcm.send_v1( # { "token": "4sdsx",, "to" : "notification": {}.. } # ) def send_notification_v1(message) - return if @project_name.empty? + return if firebase_project_id.empty? post_body = { 'message': message } for_uri(BASE_URI_V1) do |connection| response = connection.post( - "#{@project_name}/messages:send", post_body.to_json + "#{firebase_project_id}/messages:send", post_body.to_json ) build_response(response) end @@ -168,7 +167,7 @@ def send_to_topic(topic, options = {}) for_uri(BASE_URI_V1) do |connection| response = connection.post( - "#{@project_name}/messages:send", body.to_json + "#{firebase_project_id}/messages:send", body.to_json ) build_response(response) end @@ -181,7 +180,7 @@ def send_to_topic_condition(condition, options = {}) for_uri(BASE_URI_V1) do |connection| response = connection.post( - "#{@project_name}/messages:send", body.to_json + "#{firebase_project_id}/messages:send", body.to_json ) build_response(response) end @@ -298,4 +297,23 @@ def json_key File.open(@json_key_path) end end + + def firebase_project_id + @firebase_project_id ||= extract_project_id + end + + def extract_project_id + return "" if @json_key_path.nil? || @json_key_path == "" + + json_content = if @json_key_path.respond_to?(:read) + @json_key_path.read.tap { @json_key_path.rewind } + else + File.read(@json_key_path) + end + + credentials = JSON.parse(json_content) + credentials["project_id"] || "" + rescue JSON::ParserError, Errno::ENOENT => e + "" + end end diff --git a/spec/fcm_spec.rb b/spec/fcm_spec.rb index a3f24e1..ff88049 100644 --- a/spec/fcm_spec.rb +++ b/spec/fcm_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe FCM do - let(:project_name) { 'test-project' } + let(:firebase_project_id) { 'test-project' } let(:json_key_path) { 'path/to/json/key.json' } let(:client) { FCM.new(json_key_path) } @@ -15,6 +15,7 @@ before do allow(client).to receive(:json_key) + allow(client).to receive(:extract_project_id).and_return(firebase_project_id) # Mock the Google::Auth::ServiceAccountCredentials allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds). @@ -38,9 +39,9 @@ end describe "#send_v1 or #send_notification_v1" do - let(:client) { FCM.new(json_key_path, project_name) } + let(:client) { FCM.new(json_key_path) } - let(:uri) { "#{FCM::BASE_URI_V1}#{project_name}/messages:send" } + let(:uri) { "#{FCM::BASE_URI_V1}#{firebase_project_id}/messages:send" } let(:status_code) { 200 } let(:stub_fcm_send_v1_request) do @@ -177,8 +178,8 @@ include_examples 'succesfuly send notification' end - context 'when project_name is empty' do - let(:project_name) { '' } + context 'when firebase_project_id is empty' do + let(:firebase_project_id) { '' } let(:send_v1_params) do { 'token' => '4sdsx', @@ -249,9 +250,9 @@ end describe '#send_to_topic' do - let(:client) { FCM.new(json_key_path, project_name) } + let(:client) { FCM.new(json_key_path) } - let(:uri) { "#{FCM::BASE_URI_V1}#{project_name}/messages:send" } + let(:uri) { "#{FCM::BASE_URI_V1}#{firebase_project_id}/messages:send" } let(:topic) { 'news' } let(:params) do @@ -300,9 +301,9 @@ end describe "#send_to_topic_condition" do - let(:client) { FCM.new(json_key_path, project_name) } + let(:client) { FCM.new(json_key_path) } - let(:uri) { "#{FCM::BASE_URI_V1}#{project_name}/messages:send" } + let(:uri) { "#{FCM::BASE_URI_V1}#{firebase_project_id}/messages:send" } let(:topic_condition) { "'foo' in topics" } let(:params) do From db06f3600fc8ad224a8efb6558c78f1e295bb1c0 Mon Sep 17 00:00:00 2001 From: grumpymonkey-agency Date: Tue, 28 Oct 2025 14:40:28 +0100 Subject: [PATCH 2/4] Updates Readme --- README.md | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index d8791c9..4ae9477 100644 --- a/README.md +++ b/README.md @@ -26,34 +26,28 @@ A version of supported Ruby, currently: To use this gem, you need to instantiate a client with your firebase credentials: ```ruby -fcm = FCM.new( - GOOGLE_APPLICATION_CREDENTIALS_PATH, - FIREBASE_PROJECT_ID -) +fcm = FCM.new(GOOGLE_APPLICATION_CREDENTIALS_PATH) ``` +**Note:** The Firebase project ID is automatically extracted from your Firebase service credentials file. + ## About the `GOOGLE_APPLICATION_CREDENTIALS_PATH` The `GOOGLE_APPLICATION_CREDENTIALS_PATH` is meant to contain your firebase credentials. The easiest way to provide them is to pass here an absolute path to a file with your credentials: ```ruby -fcm = FCM.new( - '/path/to/credentials.json', - FIREBASE_PROJECT_ID -) +fcm = FCM.new('/path/to/credentials.json') ``` As per their secret nature, you might not want to have them in your repository. In that case, another supported solution is to pass a `StringIO` that contains your credentials: ```ruby -fcm = FCM.new( - StringIO.new(ENV.fetch('FIREBASE_CREDENTIALS')), - FIREBASE_PROJECT_ID -) - +fcm = FCM.new(StringIO.new(ENV.fetch('FIREBASE_CREDENTIALS'))) ``` +The gem will automatically extract the `project_id` from your Firebase service credentials file. + ## Usage ## HTTP v1 API @@ -61,10 +55,7 @@ fcm = FCM.new( To migrate to HTTP v1 see: https://firebase.google.com/docs/cloud-messaging/migrate-v1 ```ruby -fcm = FCM.new( - GOOGLE_APPLICATION_CREDENTIALS_PATH, - FIREBASE_PROJECT_ID -) +fcm = FCM.new(GOOGLE_APPLICATION_CREDENTIALS_PATH) message = { 'token': "000iddqd", # send to a specific device # 'topic': "yourTopic", @@ -108,8 +99,7 @@ https://firebase.google.com/docs/cloud-messaging/android/device-group#managing_d 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: -`project_id` is the SENDER_ID in your cloud settings. -https://firebase.google.com/docs/cloud-messaging/concept-options#senderid +**Note:** The `project_id` parameter used in device group management is your Firebase project's sender ID, which can be found in your Firebase project settings under Cloud Messaging. ```ruby params = { key_name: "appUser-Chris", From 3e65df0fbf34be34783a59cca0c6214a37a6cca9 Mon Sep 17 00:00:00 2001 From: grumpymonkey-agency Date: Tue, 28 Oct 2025 14:48:57 +0100 Subject: [PATCH 3/4] Addresses single quote and max line linter errors --- lib/fcm.rb | 10 +++++----- spec/fcm_spec.rb | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/fcm.rb b/lib/fcm.rb index 9dc5acd..4f0eb52 100644 --- a/lib/fcm.rb +++ b/lib/fcm.rb @@ -12,7 +12,7 @@ class FCM INSTANCE_ID_API = "https://iid.googleapis.com" TOPIC_REGEX = /[a-zA-Z0-9\-_.~%]+/ - def initialize(json_key_path = "", http_options = {}) + def initialize(json_key_path = '', http_options = {}) @json_key_path = json_key_path @http_options = http_options end @@ -41,7 +41,7 @@ def initialize(json_key_path = "", http_options = {}) # } # } # } - # fcm = FCM.new(json_key_path, firebase_project_id) + # fcm = FCM.new(json_key_path) # fcm.send_v1( # { "token": "4sdsx",, "to" : "notification": {}.. } # ) @@ -303,7 +303,7 @@ def firebase_project_id end def extract_project_id - return "" if @json_key_path.nil? || @json_key_path == "" + return '' if @json_key_path.nil? || @json_key_path == '' json_content = if @json_key_path.respond_to?(:read) @json_key_path.read.tap { @json_key_path.rewind } @@ -312,8 +312,8 @@ def extract_project_id end credentials = JSON.parse(json_content) - credentials["project_id"] || "" + credentials["project_id"] || '' rescue JSON::ParserError, Errno::ENOENT => e - "" + '' end end diff --git a/spec/fcm_spec.rb b/spec/fcm_spec.rb index ff88049..d2477b2 100644 --- a/spec/fcm_spec.rb +++ b/spec/fcm_spec.rb @@ -15,7 +15,8 @@ before do allow(client).to receive(:json_key) - allow(client).to receive(:extract_project_id).and_return(firebase_project_id) + allow(client).to receive(:extract_project_id). + and_return(firebase_project_id) # Mock the Google::Auth::ServiceAccountCredentials allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds). From 1cc5245c22bc1a6bae450d6b66b76be3709b7659 Mon Sep 17 00:00:00 2001 From: grumpymonkey-agency Date: Tue, 28 Oct 2025 14:51:32 +0100 Subject: [PATCH 4/4] Addresses styling issues from linter --- lib/fcm.rb | 4 ++-- spec/fcm_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/fcm.rb b/lib/fcm.rb index 4f0eb52..a0bdbda 100644 --- a/lib/fcm.rb +++ b/lib/fcm.rb @@ -312,8 +312,8 @@ def extract_project_id end credentials = JSON.parse(json_content) - credentials["project_id"] || '' - rescue JSON::ParserError, Errno::ENOENT => e + credentials['project_id'] || '' + rescue JSON::ParserError, Errno::ENOENT '' end end diff --git a/spec/fcm_spec.rb b/spec/fcm_spec.rb index d2477b2..9bddc9e 100644 --- a/spec/fcm_spec.rb +++ b/spec/fcm_spec.rb @@ -15,8 +15,8 @@ before do allow(client).to receive(:json_key) - allow(client).to receive(:extract_project_id). - and_return(firebase_project_id) + allow(client).to receive(:extract_project_id) + .and_return(firebase_project_id) # Mock the Google::Auth::ServiceAccountCredentials allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds).