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.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +30 -0
- data/.gitignore +1 -1
- data/Gemfile +1 -0
- data/README.md +95 -22
- data/fcm.gemspec +14 -15
- data/lib/fcm.rb +180 -141
- data/spec/fcm_spec.rb +179 -110
- metadata +33 -20
- data/.travis.yml +0 -8
data/lib/fcm.rb
CHANGED
@@ -1,25 +1,71 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "faraday"
|
2
|
+
require "cgi"
|
3
|
+
require "json"
|
4
|
+
require "googleauth"
|
4
5
|
|
5
6
|
class FCM
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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 =
|
13
|
-
INSTANCE_ID_API =
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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:
|
52
|
-
|
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
|
-
|
96
|
+
extra_headers = {
|
97
|
+
"project_id" => project_id,
|
98
|
+
}
|
64
99
|
|
65
|
-
for_uri(GROUP_NOTIFICATION_BASE_URI) do
|
66
|
-
response =
|
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:
|
75
|
-
|
76
|
-
|
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
|
-
|
113
|
+
extra_headers = {
|
114
|
+
"project_id" => project_id,
|
115
|
+
}
|
88
116
|
|
89
|
-
for_uri(GROUP_NOTIFICATION_BASE_URI) do
|
90
|
-
response =
|
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:
|
98
|
-
|
99
|
-
|
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
|
-
|
130
|
+
extra_headers = {
|
131
|
+
"project_id" => project_id,
|
132
|
+
}
|
111
133
|
|
112
|
-
for_uri(GROUP_NOTIFICATION_BASE_URI) do
|
113
|
-
response =
|
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
|
-
|
145
|
+
extra_headers = {
|
146
|
+
"project_id" => project_id,
|
147
|
+
}
|
132
148
|
|
133
|
-
for_uri(GROUP_NOTIFICATION_BASE_URI) do
|
134
|
-
response =
|
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
|
-
|
146
|
-
|
147
|
-
|
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,
|
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,
|
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
|
-
|
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
|
-
|
182
|
-
|
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
|
-
|
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
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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.
|
220
|
-
case response.
|
246
|
+
response_hash = { body: body, headers: response.headers, status_code: response.status }
|
247
|
+
case response.status
|
221
248
|
when 200
|
222
|
-
response_hash[:response] =
|
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] =
|
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] =
|
256
|
+
response_hash[:response] = "There was an error authenticating the sender account."
|
230
257
|
when 503
|
231
|
-
response_hash[:response] =
|
258
|
+
response_hash[:response] = "Server is temporarily unavailable."
|
232
259
|
when 500..599
|
233
|
-
response_hash[:response] =
|
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[
|
242
|
-
body[
|
243
|
-
canonical_ids << { old: registration_ids[index], new: 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[
|
254
|
-
body[
|
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
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
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[
|
297
|
+
!result["registration_id"].nil?
|
277
298
|
end
|
278
299
|
|
279
300
|
def is_not_registered?(result)
|
280
|
-
result[
|
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
|