Skip to content

Commit

Permalink
format and update readme with send_v1 HTTP v1
Browse files Browse the repository at this point in the history
  • Loading branch information
sabman committed Nov 25, 2021
1 parent 7691238 commit b31378f
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 175 deletions.
48 changes: 46 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,56 @@ gem 'fcm'

For Android you will need a device running 2.3 (or newer) that also have the Google Play Store app installed, or an emulator running Android 2.3 with Google APIs. iOS devices are also supported.


A version of supported Ruby, currently:
`ruby >= 2.4`


## Usage

## HTTP v1 API

To migrate to HTTP v1 see: https://firebase.google.com/docs/cloud-messaging/migrate-v1

```ruby
fcm = FCM.new(
API_TOKEN,
GOOGLE_APPLICATION_CREDENTIALS_PATH,
FIREBASE_PROJECT_ID
)
message = {
'topic': "89023", # OR token if you want to send to a specific device
# 'token': "000iddqd",
'data': {
payload: {
data: {
id: 1
}
}.to_json
},
'notification': {
title: notification.title_th,
body: notification.body_th,
},
'android': {},
'apns': {
payload: {
aps: {
sound: "default",
category: "#{Time.zone.now.to_i}"
}
}
},
'fcm_options': {
analytics_label: 'Label'
}
}

fcm.send_v1(message)
```

## HTTP Legacy Version

To migrate to HTTP v1 see: https://firebase.google.com/docs/cloud-messaging/migrate-v1

For your server to send a message to one or more devices, you must first initialise a new `FCM` class with your Firebase Cloud Messaging server 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://firebase.google.com/docs/cloud-messaging/http-server-ref) like `data` or `time_to_live` etc. as a hash via the second optional argument to `send`.

Example sending notifications:
Expand Down Expand Up @@ -166,6 +209,7 @@ You can find a guide to implement an Android Client app to receive notifications
The guide to set up an iOS app to get notifications is here: [Setting up a FCM Client App on iOS](https://firebase.google.com/docs/cloud-messaging/ios/client).

## ChangeLog

### 1.0.3

- Fix overly strict faraday depenecy
Expand Down
2 changes: 1 addition & 1 deletion fcm.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ Gem::Specification.new do |s|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]

# s.add_runtime_dependency('faraday', '~> 1')
s.add_runtime_dependency('faraday', '~> 1')
end
125 changes: 65 additions & 60 deletions lib/fcm.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
require 'faraday'
require 'cgi'
require 'json'
require 'googleauth'
require "faraday"
require "cgi"
require "json"
require "googleauth"

class FCM
BASE_URI = 'https://fcm.googleapis.com'
BASE_URI_V1 = 'https://fcm.googleapis.com/v1/projects/'
BASE_URI = "https://fcm.googleapis.com"
BASE_URI_V1 = "https://fcm.googleapis.com/v1/projects/"
DEFAULT_TIMEOUT = 30
FORMAT = :json

# constants
GROUP_NOTIFICATION_BASE_URI = 'https://android.googleapis.com'
INSTANCE_ID_API = 'https://iid.googleapis.com'
GROUP_NOTIFICATION_BASE_URI = "https://android.googleapis.com"
INSTANCE_ID_API = "https://iid.googleapis.com"
TOPIC_REGEX = /[a-zA-Z0-9\-_.~%]+/

attr_accessor :timeout, :api_key, :json_key_path, :project_base_uri

def initialize(api_key, json_key_path = '', project_name = '', client_options = {})
def initialize(api_key, json_key_path = "", project_name = "", client_options = {})
@api_key = api_key
@client_options = client_options
@json_key_path = json_key_path
Expand Down Expand Up @@ -57,12 +57,13 @@ def send_notification_v1(message)
post_body = { 'message': message }

response = Faraday.post("#{@project_base_uri}/messages:send") do |req|
req.headers['Content-Type'] = 'application/json'
req.headers['Authorization'] = "Bearer #{jwt_token}"
req.headers["Content-Type"] = "application/json"
req.headers["Authorization"] = "Bearer #{jwt_token}"
req.body = post_body.to_json
end
build_response(response)
end

alias send_v1 send_notification_v1

# See https://developers.google.com/cloud-messaging/http for more details.
Expand All @@ -81,68 +82,72 @@ 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)
response = connection.post("/fcm/send", post_body.to_json)
build_response(response, registration_ids)
end
end

alias send send_notification

def create_notification_key(key_name, project_id, registration_ids = [])
post_body = build_post_body(registration_ids, operation: 'create',
notification_key_name: key_name)
post_body = build_post_body(registration_ids, operation: "create",
notification_key_name: key_name)

extra_headers = {
'project_id' => project_id
"project_id" => project_id,
}

for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection|
response = connection.post('/gcm/notification', post_body.to_json)
response = connection.post("/gcm/notification", post_body.to_json)
build_response(response)
end
end

alias create create_notification_key

def add_registration_ids(key_name, project_id, notification_key, registration_ids)
post_body = build_post_body(registration_ids, operation: 'add',
notification_key_name: key_name,
notification_key: notification_key)
post_body = build_post_body(registration_ids, operation: "add",
notification_key_name: key_name,
notification_key: notification_key)

extra_headers = {
'project_id' => project_id
"project_id" => project_id,
}

for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection|
response = connection.post('/gcm/notification', post_body.to_json)
response = connection.post("/gcm/notification", post_body.to_json)
build_response(response)
end
end

alias add add_registration_ids

def remove_registration_ids(key_name, project_id, notification_key, registration_ids)
post_body = build_post_body(registration_ids, operation: 'remove',
notification_key_name: key_name,
notification_key: notification_key)
post_body = build_post_body(registration_ids, operation: "remove",
notification_key_name: key_name,
notification_key: notification_key)

extra_headers = {
'project_id' => project_id
"project_id" => project_id,
}

for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection|
response = connection.post('/gcm/notification', post_body.to_json)
response = connection.post("/gcm/notification", post_body.to_json)
build_response(response)
end
end

alias remove remove_registration_ids

def recover_notification_key(key_name, project_id)
params = {notification_key_name: key_name}
params = { notification_key_name: key_name }

extra_headers = {
'project_id' => project_id
"project_id" => project_id,
}

for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection|
response = connection.get('/gcm/notification', params)
response = connection.get("/gcm/notification", params)
build_response(response)
end
end
Expand All @@ -160,11 +165,11 @@ def topic_subscription(topic, registration_id)
end

def batch_topic_subscription(topic, registration_ids)
manage_topics_relationship(topic, registration_ids, 'Add')
manage_topics_relationship(topic, registration_ids, "Add")
end

def batch_topic_unsubscription(topic, registration_ids)
manage_topics_relationship(topic, registration_ids, 'Remove')
manage_topics_relationship(topic, registration_ids, "Remove")
end

def manage_topics_relationship(topic, registration_ids, action)
Expand All @@ -178,35 +183,35 @@ def manage_topics_relationship(topic, registration_ids, action)

def send_to_topic(topic, options = {})
if topic.gsub(TOPIC_REGEX, "").length == 0
send_with_notification_key('/topics/' + topic, options)
send_with_notification_key("/topics/" + topic, options)
end
end

def get_instance_id_info iid_token, options={}
def get_instance_id_info(iid_token, options = {})
params = {
query: options
query: options,
}

for_uri(INSTANCE_ID_API) do |connection|
response = connection.get('/iid/info/'+iid_token, params)
response = connection.get("/iid/info/" + iid_token, params)
build_response(response)
end
end

def subscribe_instance_id_to_topic iid_token, topic_name
def subscribe_instance_id_to_topic(iid_token, topic_name)
batch_subscribe_instance_ids_to_topic([iid_token], topic_name)
end

def unsubscribe_instance_id_from_topic iid_token, topic_name
def unsubscribe_instance_id_from_topic(iid_token, topic_name)
batch_unsubscribe_instance_ids_from_topic([iid_token], topic_name)
end

def batch_subscribe_instance_ids_to_topic instance_ids, topic_name
manage_topics_relationship(topic_name, instance_ids, 'Add')
def batch_subscribe_instance_ids_to_topic(instance_ids, topic_name)
manage_topics_relationship(topic_name, instance_ids, "Add")
end

def batch_unsubscribe_instance_ids_from_topic instance_ids, topic_name
manage_topics_relationship(topic_name, instance_ids, 'Remove')
def batch_unsubscribe_instance_ids_from_topic(instance_ids, topic_name)
manage_topics_relationship(topic_name, instance_ids, "Remove")
end

def send_to_topic_condition(condition, options = {})
Expand All @@ -220,7 +225,7 @@ def send_to_topic_condition(condition, options = {})

def for_uri(uri, extra_headers = {})
connection = ::Faraday.new(:url => uri) do |faraday|
faraday.adapter Faraday.default_adapter
faraday.adapter Faraday.default_adapter
faraday.headers["Content-Type"] = "application/json"
faraday.headers["Authorization"] = "key=#{api_key}"
extra_headers.each do |key, value|
Expand All @@ -240,28 +245,28 @@ def build_response(response, registration_ids = [])
response_hash = { body: body, headers: response.headers, status_code: response.status }
case response.status
when 200
response_hash[:response] = 'success'
response_hash[:response] = "success"
body = JSON.parse(body) unless body.empty?
response_hash[:canonical_ids] = build_canonical_ids(body, registration_ids) unless registration_ids.empty?
response_hash[:not_registered_ids] = build_not_registered_ids(body, registration_ids) unless registration_ids.empty?
when 400
response_hash[:response] = 'Only applies for JSON requests. Indicates that the request could not be parsed as JSON, or it contained invalid fields.'
response_hash[:response] = "Only applies for JSON requests. Indicates that the request could not be parsed as JSON, or it contained invalid fields."
when 401
response_hash[:response] = 'There was an error authenticating the sender account.'
response_hash[:response] = "There was an error authenticating the sender account."
when 503
response_hash[:response] = 'Server is temporarily unavailable.'
response_hash[:response] = "Server is temporarily unavailable."
when 500..599
response_hash[:response] = 'There was an internal error in the FCM server while trying to process the request.'
response_hash[:response] = "There was an internal error in the FCM server while trying to process the request."
end
response_hash
end

def build_canonical_ids(body, registration_ids)
canonical_ids = []
unless body.empty?
if body['canonical_ids'] > 0
body['results'].each_with_index do |result, index|
canonical_ids << { old: registration_ids[index], new: result['registration_id'] } if has_canonical_id?(result)
if body["canonical_ids"] > 0
body["results"].each_with_index do |result, index|
canonical_ids << { old: registration_ids[index], new: result["registration_id"] } if has_canonical_id?(result)
end
end
end
Expand All @@ -271,8 +276,8 @@ def build_canonical_ids(body, registration_ids)
def build_not_registered_ids(body, registration_id)
not_registered_ids = []
unless body.empty?
if body['failure'] > 0
body['results'].each_with_index do |result, index|
if body["failure"] > 0
body["results"].each_with_index do |result, index|
not_registered_ids << registration_id[index] if is_not_registered?(result)
end
end
Expand All @@ -282,17 +287,17 @@ def build_not_registered_ids(body, registration_id)

def execute_notification(body)
for_uri(BASE_URI) do |connection|
response = connection.post('/fcm/send', body.to_json)
response = connection.post("/fcm/send", body.to_json)
build_response(response)
end
end

def has_canonical_id?(result)
!result['registration_id'].nil?
!result["registration_id"].nil?
end

def is_not_registered?(result)
result['error'] == 'NotRegistered'
result["error"] == "NotRegistered"
end

def validate_condition?(condition)
Expand All @@ -313,12 +318,12 @@ def validate_condition_topics?(condition)
end

def jwt_token
scope = 'https://www.googleapis.com/auth/firebase.messaging'
scope = "https://www.googleapis.com/auth/firebase.messaging"
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: File.open(@json_key_path),
scope: scope
scope: scope,
)
token = authorizer.fetch_access_token!
token['access_token']
token["access_token"]
end
end
Loading

0 comments on commit b31378f

Please sign in to comment.