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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +30 -0
- data/.gitignore +1 -0
- data/Gemfile +1 -0
- data/README.md +180 -42
- data/fcm.gemspec +14 -13
- data/lib/fcm.rb +143 -110
- data/spec/fcm_spec.rb +335 -394
- metadata +32 -12
- data/.travis.yml +0 -7
data/lib/fcm.rb
CHANGED
@@ -1,128 +1,151 @@
|
|
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
|
-
BASE_URI =
|
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
|
-
|
11
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@
|
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://
|
23
|
-
# {
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
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(
|
30
|
-
# fcm.
|
31
|
-
#
|
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
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
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:
|
46
|
-
|
64
|
+
post_body = build_post_body(registration_ids, operation: "create",
|
65
|
+
notification_key_name: key_name)
|
47
66
|
|
48
67
|
extra_headers = {
|
49
|
-
|
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(
|
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:
|
61
|
-
|
62
|
-
|
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
|
-
|
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(
|
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:
|
77
|
-
|
78
|
-
|
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
|
-
|
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(
|
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
|
-
|
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(
|
121
|
+
response = connection.get("/gcm/notification", params)
|
100
122
|
build_response(response)
|
101
123
|
end
|
102
124
|
end
|
103
125
|
|
104
|
-
def
|
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(
|
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
|
117
|
-
|
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,
|
121
|
-
manage_topics_relationship(topic,
|
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,
|
125
|
-
body = { to: "/topics/#{topic}", registration_tokens:
|
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
|
134
|
-
|
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(
|
160
|
+
response = connection.get("/iid/info/#{iid_token}", params)
|
146
161
|
build_response(response)
|
147
162
|
end
|
148
163
|
end
|
149
164
|
|
150
|
-
def
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
-
|
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(
|
177
|
-
|
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"] = "
|
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] =
|
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] =
|
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] =
|
226
|
+
response_hash[:response] = "There was an error authenticating the sender account."
|
205
227
|
when 503
|
206
|
-
response_hash[:response] =
|
228
|
+
response_hash[:response] = "Server is temporarily unavailable."
|
207
229
|
when 500..599
|
208
|
-
response_hash[:response] =
|
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[
|
217
|
-
body[
|
218
|
-
canonical_ids << { old: registration_ids[index], new: 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[
|
229
|
-
body[
|
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[
|
260
|
+
!result["registration_id"].nil?
|
246
261
|
end
|
247
262
|
|
248
263
|
def is_not_registered?(result)
|
249
|
-
result[
|
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
|