fcm 1.0.2 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/fcm.rb CHANGED
@@ -1,128 +1,151 @@
1
- require 'faraday'
2
- require 'cgi'
3
- require 'json'
1
+ require "faraday"
2
+ require "cgi"
3
+ require "json"
4
+ require "googleauth"
4
5
 
5
6
  class FCM
6
- BASE_URI = 'https://fcm.googleapis.com'
7
+ BASE_URI = "https://fcm.googleapis.com"
8
+ BASE_URI_V1 = "https://fcm.googleapis.com/v1/projects/"
7
9
  DEFAULT_TIMEOUT = 30
8
- FORMAT = :json
9
10
 
10
- # constants
11
- GROUP_NOTIFICATION_BASE_URI = 'https://android.googleapis.com'
12
- INSTANCE_ID_API = 'https://iid.googleapis.com'
11
+ GROUP_NOTIFICATION_BASE_URI = "https://android.googleapis.com"
12
+ INSTANCE_ID_API = "https://iid.googleapis.com"
13
13
  TOPIC_REGEX = /[a-zA-Z0-9\-_.~%]+/
14
14
 
15
- attr_accessor :timeout, :api_key
16
-
17
- def initialize(api_key, client_options = {})
18
- @api_key = api_key
19
- @client_options = client_options
15
+ def initialize(json_key_path = "", project_name = "", http_options = {})
16
+ @json_key_path = json_key_path
17
+ @project_name = project_name
18
+ @http_options = http_options
20
19
  end
21
20
 
22
- # See https://developers.google.com/cloud-messaging/http for more details.
23
- # { "notification": {
24
- # "title": "Portugal vs. Denmark",
25
- # "text": "5 to 1"
26
- # },
27
- # "to" : "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1..."
21
+ # See https://firebase.google.com/docs/cloud-messaging/send-message
22
+ # {
23
+ # "token": "4sdsx",
24
+ # "notification": {
25
+ # "title": "Breaking News",
26
+ # "body": "New news story available."
27
+ # },
28
+ # "data": {
29
+ # "story_id": "story_12345"
30
+ # },
31
+ # "android": {
32
+ # "notification": {
33
+ # "click_action": "TOP_STORY_ACTIVITY",
34
+ # "body": "Check out the Top Story"
35
+ # }
36
+ # },
37
+ # "apns": {
38
+ # "payload": {
39
+ # "aps": {
40
+ # "category" : "NEW_MESSAGE_CATEGORY"
41
+ # }
42
+ # }
43
+ # }
28
44
  # }
29
- # fcm = FCM.new("API_KEY")
30
- # fcm.send(
31
- # ["4sdsx", "8sdsd"], # registration_ids
32
- # { "notification": { "title": "Portugal vs. Denmark", "text": "5 to 1" }, "to" : "bk3RNwTe3HdFQ3P1..." }
45
+ # fcm = FCM.new(json_key_path, project_name)
46
+ # fcm.send_v1(
47
+ # { "token": "4sdsx",, "to" : "notification": {}.. }
33
48
  # )
34
- def send_notification(registration_ids, options = {})
35
- post_body = build_post_body(registration_ids, options)
36
-
37
- for_uri(BASE_URI) do |connection|
38
- response = connection.post('/fcm/send', post_body.to_json)
39
- build_response(response, registration_ids)
49
+ def send_notification_v1(message)
50
+ return if @project_name.empty?
51
+
52
+ post_body = { 'message': message }
53
+ for_uri(BASE_URI_V1) do |connection|
54
+ response = connection.post(
55
+ "#{@project_name}/messages:send", post_body.to_json
56
+ )
57
+ build_response(response)
40
58
  end
41
59
  end
42
- alias send send_notification
60
+
61
+ alias send_v1 send_notification_v1
43
62
 
44
63
  def create_notification_key(key_name, project_id, registration_ids = [])
45
- post_body = build_post_body(registration_ids, operation: 'create',
46
- notification_key_name: key_name)
64
+ post_body = build_post_body(registration_ids, operation: "create",
65
+ notification_key_name: key_name)
47
66
 
48
67
  extra_headers = {
49
- 'project_id' => project_id
68
+ "project_id" => project_id,
50
69
  }
51
70
 
52
71
  for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection|
53
- response = connection.post('/gcm/notification', post_body.to_json)
72
+ response = connection.post("/gcm/notification", post_body.to_json)
54
73
  build_response(response)
55
74
  end
56
75
  end
76
+
57
77
  alias create create_notification_key
58
78
 
59
79
  def add_registration_ids(key_name, project_id, notification_key, registration_ids)
60
- post_body = build_post_body(registration_ids, operation: 'add',
61
- notification_key_name: key_name,
62
- notification_key: notification_key)
80
+ post_body = build_post_body(registration_ids, operation: "add",
81
+ notification_key_name: key_name,
82
+ notification_key: notification_key)
63
83
 
64
84
  extra_headers = {
65
- 'project_id' => project_id
85
+ "project_id" => project_id,
66
86
  }
67
87
 
68
88
  for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection|
69
- response = connection.post('/gcm/notification', post_body.to_json)
89
+ response = connection.post("/gcm/notification", post_body.to_json)
70
90
  build_response(response)
71
91
  end
72
92
  end
93
+
73
94
  alias add add_registration_ids
74
95
 
75
96
  def remove_registration_ids(key_name, project_id, notification_key, registration_ids)
76
- post_body = build_post_body(registration_ids, operation: 'remove',
77
- notification_key_name: key_name,
78
- notification_key: notification_key)
97
+ post_body = build_post_body(registration_ids, operation: "remove",
98
+ notification_key_name: key_name,
99
+ notification_key: notification_key)
79
100
 
80
101
  extra_headers = {
81
- 'project_id' => project_id
102
+ "project_id" => project_id,
82
103
  }
83
104
 
84
105
  for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection|
85
- response = connection.post('/gcm/notification', post_body.to_json)
106
+ response = connection.post("/gcm/notification", post_body.to_json)
86
107
  build_response(response)
87
108
  end
88
109
  end
110
+
89
111
  alias remove remove_registration_ids
90
112
 
91
113
  def recover_notification_key(key_name, project_id)
92
- params = {notification_key_name: key_name}
93
-
114
+ params = { notification_key_name: key_name }
115
+
94
116
  extra_headers = {
95
- 'project_id' => project_id
117
+ "project_id" => project_id,
96
118
  }
97
119
 
98
120
  for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection|
99
- response = connection.get('/gcm/notification', params)
121
+ response = connection.get("/gcm/notification", params)
100
122
  build_response(response)
101
123
  end
102
124
  end
103
125
 
104
- def send_with_notification_key(notification_key, options = {})
105
- body = { to: notification_key }.merge(options)
106
- execute_notification(body)
107
- end
108
-
109
- def topic_subscription(topic, registration_id)
126
+ def topic_subscription(topic, registration_token)
110
127
  for_uri(INSTANCE_ID_API) do |connection|
111
- response = connection.post("/iid/v1/#{registration_id}/rel/topics/#{topic}")
128
+ response = connection.post(
129
+ "/iid/v1/#{registration_token}/rel/topics/#{topic}"
130
+ )
112
131
  build_response(response)
113
132
  end
114
133
  end
115
134
 
116
- def batch_topic_subscription(topic, registration_ids)
117
- manage_topics_relationship(topic, registration_ids, 'Add')
135
+ def topic_unsubscription(topic, registration_token)
136
+ batch_topic_unsubscription(topic, [registration_token])
137
+ end
138
+
139
+ def batch_topic_subscription(topic, registration_tokens)
140
+ manage_topics_relationship(topic, registration_tokens, 'Add')
118
141
  end
119
142
 
120
- def batch_topic_unsubscription(topic, registration_ids)
121
- manage_topics_relationship(topic, registration_ids, 'Remove')
143
+ def batch_topic_unsubscription(topic, registration_tokens)
144
+ manage_topics_relationship(topic, registration_tokens, 'Remove')
122
145
  end
123
146
 
124
- def manage_topics_relationship(topic, registration_ids, action)
125
- body = { to: "/topics/#{topic}", registration_tokens: registration_ids }
147
+ def manage_topics_relationship(topic, registration_tokens, action)
148
+ body = { to: "/topics/#{topic}", registration_tokens: registration_tokens }
126
149
 
127
150
  for_uri(INSTANCE_ID_API) do |connection|
128
151
  response = connection.post("/iid/v1:batch#{action}", body.to_json)
@@ -130,53 +153,52 @@ class FCM
130
153
  end
131
154
  end
132
155
 
133
- def send_to_topic(topic, options = {})
134
- if topic.gsub(TOPIC_REGEX, "").length == 0
135
- send_with_notification_key('/topics/' + topic, options)
136
- end
137
- end
156
+ def get_instance_id_info(iid_token, options = {})
157
+ params = options
138
158
 
139
- def get_instance_id_info iid_token, options={}
140
- params = {
141
- query: options
142
- }
143
-
144
159
  for_uri(INSTANCE_ID_API) do |connection|
145
- response = connection.get('/iid/info/'+iid_token, params)
160
+ response = connection.get("/iid/info/#{iid_token}", params)
146
161
  build_response(response)
147
162
  end
148
163
  end
149
164
 
150
- def subscribe_instance_id_to_topic iid_token, topic_name
151
- batch_subscribe_instance_ids_to_topic([iid_token], topic_name)
152
- end
153
-
154
- def unsubscribe_instance_id_from_topic iid_token, topic_name
155
- batch_unsubscribe_instance_ids_from_topic([iid_token], topic_name)
156
- end
157
-
158
- def batch_subscribe_instance_ids_to_topic instance_ids, topic_name
159
- manage_topics_relationship(topic_name, instance_ids, 'Add')
160
- end
161
-
162
- def batch_unsubscribe_instance_ids_from_topic instance_ids, topic_name
163
- manage_topics_relationship(topic_name, instance_ids, 'Remove')
165
+ def send_to_topic(topic, options = {})
166
+ if topic.gsub(TOPIC_REGEX, '').length.zero?
167
+ body = { 'message': { 'topic': topic }.merge(options) }
168
+
169
+ for_uri(BASE_URI_V1) do |connection|
170
+ response = connection.post(
171
+ "#{@project_name}/messages:send", body.to_json
172
+ )
173
+ build_response(response)
174
+ end
175
+ end
164
176
  end
165
177
 
166
178
  def send_to_topic_condition(condition, options = {})
167
179
  if validate_condition?(condition)
168
- body = { condition: condition }.merge(options)
169
- execute_notification(body)
180
+ body = { 'message': { 'condition': condition }.merge(options) }
181
+
182
+ for_uri(BASE_URI_V1) do |connection|
183
+ response = connection.post(
184
+ "#{@project_name}/messages:send", body.to_json
185
+ )
186
+ build_response(response)
187
+ end
170
188
  end
171
189
  end
172
190
 
173
191
  private
174
192
 
175
193
  def for_uri(uri, extra_headers = {})
176
- connection = ::Faraday.new(:url => uri) do |faraday|
177
- faraday.adapter Faraday.default_adapter
194
+ connection = ::Faraday.new(
195
+ url: uri,
196
+ request: { timeout: @http_options.fetch(:timeout, DEFAULT_TIMEOUT) }
197
+ ) do |faraday|
198
+ faraday.adapter Faraday.default_adapter
178
199
  faraday.headers["Content-Type"] = "application/json"
179
- faraday.headers["Authorization"] = "key=#{api_key}"
200
+ faraday.headers["Authorization"] = "Bearer #{jwt_token}"
201
+ faraday.headers["access_token_auth"]= "true"
180
202
  extra_headers.each do |key, value|
181
203
  faraday.headers[key] = value
182
204
  end
@@ -194,18 +216,18 @@ class FCM
194
216
  response_hash = { body: body, headers: response.headers, status_code: response.status }
195
217
  case response.status
196
218
  when 200
197
- response_hash[:response] = 'success'
219
+ response_hash[:response] = "success"
198
220
  body = JSON.parse(body) unless body.empty?
199
221
  response_hash[:canonical_ids] = build_canonical_ids(body, registration_ids) unless registration_ids.empty?
200
222
  response_hash[:not_registered_ids] = build_not_registered_ids(body, registration_ids) unless registration_ids.empty?
201
223
  when 400
202
- response_hash[:response] = 'Only applies for JSON requests. Indicates that the request could not be parsed as JSON, or it contained invalid fields.'
224
+ response_hash[:response] = "Only applies for JSON requests. Indicates that the request could not be parsed as JSON, or it contained invalid fields."
203
225
  when 401
204
- response_hash[:response] = 'There was an error authenticating the sender account.'
226
+ response_hash[:response] = "There was an error authenticating the sender account."
205
227
  when 503
206
- response_hash[:response] = 'Server is temporarily unavailable.'
228
+ response_hash[:response] = "Server is temporarily unavailable."
207
229
  when 500..599
208
- response_hash[:response] = 'There was an internal error in the FCM server while trying to process the request.'
230
+ response_hash[:response] = "There was an internal error in the FCM server while trying to process the request."
209
231
  end
210
232
  response_hash
211
233
  end
@@ -213,9 +235,9 @@ class FCM
213
235
  def build_canonical_ids(body, registration_ids)
214
236
  canonical_ids = []
215
237
  unless body.empty?
216
- if body['canonical_ids'] > 0
217
- body['results'].each_with_index do |result, index|
218
- canonical_ids << { old: registration_ids[index], new: result['registration_id'] } if has_canonical_id?(result)
238
+ if body["canonical_ids"] > 0
239
+ body["results"].each_with_index do |result, index|
240
+ canonical_ids << { old: registration_ids[index], new: result["registration_id"] } if has_canonical_id?(result)
219
241
  end
220
242
  end
221
243
  end
@@ -225,8 +247,8 @@ class FCM
225
247
  def build_not_registered_ids(body, registration_id)
226
248
  not_registered_ids = []
227
249
  unless body.empty?
228
- if body['failure'] > 0
229
- body['results'].each_with_index do |result, index|
250
+ if body["failure"] > 0
251
+ body["results"].each_with_index do |result, index|
230
252
  not_registered_ids << registration_id[index] if is_not_registered?(result)
231
253
  end
232
254
  end
@@ -234,19 +256,12 @@ class FCM
234
256
  not_registered_ids
235
257
  end
236
258
 
237
- def execute_notification(body)
238
- for_uri(BASE_URI) do |connection|
239
- response = connection.post('/fcm/send', body.to_json)
240
- build_response(response)
241
- end
242
- end
243
-
244
259
  def has_canonical_id?(result)
245
- !result['registration_id'].nil?
260
+ !result["registration_id"].nil?
246
261
  end
247
262
 
248
263
  def is_not_registered?(result)
249
- result['error'] == 'NotRegistered'
264
+ result["error"] == "NotRegistered"
250
265
  end
251
266
 
252
267
  def validate_condition?(condition)
@@ -265,4 +280,22 @@ class FCM
265
280
  topics = condition.scan(/(?:^|\S|\s)'([^']*?)'(?:$|\S|\s)/).flatten
266
281
  topics.all? { |topic| topic.gsub(TOPIC_REGEX, "").length == 0 }
267
282
  end
283
+
284
+ def jwt_token
285
+ scope = "https://www.googleapis.com/auth/firebase.messaging"
286
+ @authorizer ||= Google::Auth::ServiceAccountCredentials.make_creds(
287
+ json_key_io: json_key,
288
+ scope: scope,
289
+ )
290
+ token = @authorizer.fetch_access_token!
291
+ token["access_token"]
292
+ end
293
+
294
+ def json_key
295
+ @json_key ||= if @json_key_path.respond_to?(:read)
296
+ @json_key_path
297
+ else
298
+ File.open(@json_key_path)
299
+ end
300
+ end
268
301
  end