fcm 0.0.6 → 1.0.8

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