maia 2.0.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +188 -0
- data/lib/maia.rb +12 -0
- data/lib/maia/engine.rb +0 -1
- data/lib/maia/fcm.rb +7 -0
- data/lib/maia/fcm/connection.rb +34 -0
- data/lib/maia/fcm/notification.rb +27 -0
- data/lib/maia/fcm/response.rb +55 -0
- data/lib/maia/fcm/response_collection.rb +34 -0
- data/lib/maia/fcm/result.rb +31 -0
- data/lib/maia/fcm/result_collection.rb +35 -0
- data/lib/maia/fcm/service.rb +48 -0
- data/lib/maia/message.rb +23 -62
- data/lib/maia/messenger.rb +25 -31
- data/lib/maia/poke.rb +2 -2
- data/lib/maia/version.rb +1 -1
- metadata +44 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f16e5e9cba3546a3653e1fd2e3f87d19fa46784
|
4
|
+
data.tar.gz: aa5f6350dd17689e8dd01c999c3f509680b73e80
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51ae47908f1ae0e3a1e0b04eb3812023a20db60276b07fa2156583b325191ea7c3231057d0f461c36832dcfd9db6d37c831b10f1a58f5e2cd304431197f4ad71
|
7
|
+
data.tar.gz: 1bbdf160cdeaefebd160966ad92c39ec18d795af55a2e0e4ec291b91e8af91dd199e0d1374cda1bea091f29f150a8f523584221c505274d12cd1ad72f9c82a9c
|
data/README.md
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
# Maia
|
2
|
+
|
3
|
+
This project maintains a `Maia::Device` model and facilitates the delivery of push notifications for iOS and Android through FCM (Firebase Cloud Messaging).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```
|
8
|
+
gem 'maia'
|
9
|
+
bundle
|
10
|
+
bin/rake railties:install:migrations
|
11
|
+
bin/rake db:migrate
|
12
|
+
```
|
13
|
+
|
14
|
+
This will copy the `maia_devices` table into your project.
|
15
|
+
|
16
|
+
## Setup
|
17
|
+
|
18
|
+
Maia relies on [ActiveJob](https://github.com/rails/rails/tree/master/activejob) to enqueue messages. Ensure your application is properly setup with ActiveJob!
|
19
|
+
|
20
|
+
Set your FCM key in an initializer, such as `config/initializers/maia.rb`:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
Maia::FCM.key = Rails.application.secrets[:fcm][:key]
|
24
|
+
```
|
25
|
+
|
26
|
+
Alternatively, you can set `ENV['FCM_KEY']`, which Maia will check first.
|
27
|
+
|
28
|
+
Include `Maia::Model` into your User model. This will attach the `has_many` relationship you need with `Maia::Device`:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
class User
|
32
|
+
include Maia::Model
|
33
|
+
# ...
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
Create a Devices controller where you need it, which is most likely an API. The controller itself will be generated within your application so that Maia does not make any assumptions about your method of authentication, `respond_with` mimetypes, etc. The only requirement is that `current_user` exists and returns whatever model included `Maia::Model`.
|
38
|
+
|
39
|
+
Here's an example of getting setup with an API Devices controller that mobile apps can register with:
|
40
|
+
|
41
|
+
`bin/rails g controller api/devices`
|
42
|
+
|
43
|
+
After the controller is generated, include `Maia::Controller`:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
class API::DevicesController
|
47
|
+
include Maia::Controller
|
48
|
+
# ...
|
49
|
+
```
|
50
|
+
|
51
|
+
Maia provides the `create` method for you, so devices can now register themselves by POSTing to that controller. If you'd like to add any other actions, feel free.
|
52
|
+
|
53
|
+
## Device Registration
|
54
|
+
|
55
|
+
Devices can register with your application by submitting a POST to your devices controller with these params:
|
56
|
+
|
57
|
+
```
|
58
|
+
{ "device": { "token": "<TOKEN>" } }
|
59
|
+
```
|
60
|
+
|
61
|
+
Where `<TOKEN>` is the token from FCM registration.
|
62
|
+
|
63
|
+
## Device Management
|
64
|
+
|
65
|
+
When FCM responds with an invalid or unregistered device token, the device record will be destroyed from the database.
|
66
|
+
|
67
|
+
When FCM responds with a canonical ID, the device record will be updated so that it's `token` field will be equal to the canonical ID given by FCM.
|
68
|
+
|
69
|
+
## Device Expiration
|
70
|
+
|
71
|
+
Devices will expire after 14 days. This is to ensure user's who sell or otherwise give away their device will not be tied to that device forever. Each time a POST to Devices is received, the token expiration will be refreshed.
|
72
|
+
|
73
|
+
## Defining Messages
|
74
|
+
|
75
|
+
Maia provides a `Maia::Message` class that provides an interface for defining push messages and sending them. To define a message, inherit from `Maia::Message` and override whatever you need to:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
class ExampleMessage < Maia::Message
|
79
|
+
# Required
|
80
|
+
def title
|
81
|
+
'Something happened!'
|
82
|
+
end
|
83
|
+
|
84
|
+
# Required, the body of the message on Android, alert on iOS
|
85
|
+
def body
|
86
|
+
'Something very important has happened, check it out!'
|
87
|
+
end
|
88
|
+
|
89
|
+
# Determines the icon to load on Android phones
|
90
|
+
def icon
|
91
|
+
'icn_maia'
|
92
|
+
end
|
93
|
+
|
94
|
+
# Will use 'default' by default. Overriding to nil will prevent sound
|
95
|
+
def sound
|
96
|
+
'default'
|
97
|
+
end
|
98
|
+
|
99
|
+
# Badge to use on iOS
|
100
|
+
def badge
|
101
|
+
1
|
102
|
+
end
|
103
|
+
|
104
|
+
# #RRGGBB formatted color to use for the Android notification icon
|
105
|
+
def color
|
106
|
+
'#ffffff'
|
107
|
+
end
|
108
|
+
|
109
|
+
# click_action on Android, category on iOS
|
110
|
+
def on_click
|
111
|
+
'SOMETHING_HAPPENED'
|
112
|
+
end
|
113
|
+
|
114
|
+
# Any additional data to send with the payload
|
115
|
+
def data
|
116
|
+
{ foo: :bar }
|
117
|
+
end
|
118
|
+
|
119
|
+
# :normal or :high (:normal by default)
|
120
|
+
def priority
|
121
|
+
:normal
|
122
|
+
end
|
123
|
+
|
124
|
+
# Override to true in order to send the iOS content-available flag
|
125
|
+
def content_available?
|
126
|
+
false
|
127
|
+
end
|
128
|
+
|
129
|
+
# Override to true in order to send a dry run push. This can help debug any device errors without actually sending a push message
|
130
|
+
def dry_run?
|
131
|
+
false
|
132
|
+
end
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
This message will generate the following FCM payload:
|
137
|
+
|
138
|
+
```json
|
139
|
+
{
|
140
|
+
"priority": "normal",
|
141
|
+
"dry_run": false,
|
142
|
+
"content_available": false,
|
143
|
+
"data": {
|
144
|
+
"foo": "bar"
|
145
|
+
},
|
146
|
+
"notification": {
|
147
|
+
"title": "Something happened!",
|
148
|
+
"body": "'Something very important has happened, check it out!'",
|
149
|
+
"icon": "icn_maia",
|
150
|
+
"sound": "default",
|
151
|
+
"badge": 1,
|
152
|
+
"color": "#ffffff",
|
153
|
+
"click_action": "SOMETHING_HAPPENED",
|
154
|
+
},
|
155
|
+
"registration_ids": ["<TOKEN1>", "<TOKEN2>"]
|
156
|
+
}
|
157
|
+
```
|
158
|
+
|
159
|
+
`Maia::Message` does not define a constructor so you can construct your message however you want.
|
160
|
+
|
161
|
+
## Sending messages
|
162
|
+
|
163
|
+
`Maia::Message` provides a `send_to` that pushes the message out to a user (or collection of users). The argument to `send_to` should be a single record or relation of records.
|
164
|
+
|
165
|
+
For example:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
ExampleMessage.new(...).send_to User.first
|
169
|
+
ExampleMessage.new(...).send_to User.where(beta: true)
|
170
|
+
```
|
171
|
+
|
172
|
+
`send_to` will batch users in groups of 999 tokens (FCM limitation) and send them via ActiveJob.
|
173
|
+
|
174
|
+
## Specifying job options (`wait`, `queue`, etc)
|
175
|
+
|
176
|
+
The `send_to` method passes it's last argument into [ActiveJob's `set` method](http://apidock.com/rails/ActiveJob/Core/ClassMethods/set), for example:
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
ExampleMessage.new(...).send_to User.first, wait: 10.seconds, queue: :maia
|
180
|
+
```
|
181
|
+
|
182
|
+
## Sending a test push
|
183
|
+
|
184
|
+
Maia comes with a built-in message to use to test your configuration out:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
Maia::Poke.new.send_to user
|
188
|
+
```
|
data/lib/maia.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'rails'
|
2
|
+
require 'active_support/core_ext/enumerable'
|
3
|
+
|
2
4
|
require 'maia/engine'
|
3
5
|
require 'maia/message'
|
4
6
|
require 'maia/messenger'
|
@@ -6,5 +8,15 @@ require 'maia/poke'
|
|
6
8
|
require 'maia/dry_run'
|
7
9
|
require 'maia/error'
|
8
10
|
|
11
|
+
require 'maia/fcm'
|
12
|
+
require 'maia/fcm/connection'
|
13
|
+
require 'maia/fcm/notification'
|
14
|
+
require 'maia/fcm/response_collection'
|
15
|
+
require 'maia/fcm/response'
|
16
|
+
require 'maia/fcm/result_collection'
|
17
|
+
require 'maia/fcm/result'
|
18
|
+
require 'maia/fcm/service'
|
19
|
+
|
9
20
|
module Maia
|
21
|
+
BATCH_SIZE = 999
|
10
22
|
end
|
data/lib/maia/engine.rb
CHANGED
data/lib/maia/fcm.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Maia
|
2
|
+
module FCM
|
3
|
+
class Connection
|
4
|
+
URL = 'https://fcm.googleapis.com/fcm/send'.freeze
|
5
|
+
|
6
|
+
def initialize(key)
|
7
|
+
@key = key
|
8
|
+
end
|
9
|
+
|
10
|
+
def write(payload = {})
|
11
|
+
request = Net::HTTP::Post.new uri, headers
|
12
|
+
request.body = payload.to_json
|
13
|
+
http.request request
|
14
|
+
end
|
15
|
+
|
16
|
+
def uri
|
17
|
+
URI(URL)
|
18
|
+
end
|
19
|
+
|
20
|
+
def headers
|
21
|
+
{
|
22
|
+
'Content-Type' => 'application/json',
|
23
|
+
'Authorization' => "key=#{@key}"
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def http
|
28
|
+
@_http ||= Net::HTTP.new(uri.host, uri.port).tap do |h|
|
29
|
+
h.use_ssl = true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Maia
|
2
|
+
module FCM
|
3
|
+
class Notification
|
4
|
+
attr_accessor :attributes
|
5
|
+
|
6
|
+
def initialize(attributes = {})
|
7
|
+
@attributes = attributes
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_h
|
11
|
+
@attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
attributes == other.attributes
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(method_name, *args, &block)
|
19
|
+
@attributes.fetch(method_name) { super }
|
20
|
+
end
|
21
|
+
|
22
|
+
def respond_to_missing?(method_name)
|
23
|
+
@attributes.include? method_name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Maia
|
2
|
+
module FCM
|
3
|
+
class Response
|
4
|
+
attr_reader :http_response, :tokens
|
5
|
+
|
6
|
+
def initialize(http_response, tokens = [])
|
7
|
+
@http_response = http_response
|
8
|
+
@tokens = tokens
|
9
|
+
end
|
10
|
+
|
11
|
+
def status
|
12
|
+
http_response.code
|
13
|
+
end
|
14
|
+
|
15
|
+
def success?
|
16
|
+
(200..399).cover? status
|
17
|
+
end
|
18
|
+
|
19
|
+
def fail?
|
20
|
+
!success?
|
21
|
+
end
|
22
|
+
|
23
|
+
def results
|
24
|
+
@_results ||= begin
|
25
|
+
results = to_h.fetch 'results', []
|
26
|
+
results.map!.with_index do |attributes, i|
|
27
|
+
Result.new attributes, tokens[i]
|
28
|
+
end
|
29
|
+
ResultCollection.new(results)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def error
|
34
|
+
case status
|
35
|
+
when 400
|
36
|
+
'Invalid JSON was sent to FCM.'
|
37
|
+
when 401
|
38
|
+
'Authentication error with FCM. Check the server whitelist and the validity of your project key.'
|
39
|
+
when 500..599
|
40
|
+
'FCM Internal server error.'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def retry_after
|
45
|
+
http_response.headers['Retry-After']
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_h
|
49
|
+
JSON.parse http_response.body
|
50
|
+
rescue JSON::ParserError
|
51
|
+
{}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Maia
|
2
|
+
module FCM
|
3
|
+
class ResponseCollection
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize(notification, responses = [])
|
7
|
+
@notification = notification
|
8
|
+
@responses = responses
|
9
|
+
end
|
10
|
+
|
11
|
+
def results
|
12
|
+
collection = ResultCollection.new
|
13
|
+
@responses.each do |response|
|
14
|
+
response.results.each do |result|
|
15
|
+
collection << result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
collection
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](index)
|
22
|
+
@responses[index]
|
23
|
+
end
|
24
|
+
|
25
|
+
def <<(response)
|
26
|
+
@responses.concat Array(response).flatten
|
27
|
+
end
|
28
|
+
|
29
|
+
def each(&block)
|
30
|
+
@responses.each(&block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Maia
|
2
|
+
module FCM
|
3
|
+
class Result
|
4
|
+
include ActiveModel::Model
|
5
|
+
|
6
|
+
attr_accessor :message_id, :registration_id, :error
|
7
|
+
attr_reader :token
|
8
|
+
|
9
|
+
def initialize(attributes, token)
|
10
|
+
super attributes
|
11
|
+
@token = token
|
12
|
+
end
|
13
|
+
|
14
|
+
def success?
|
15
|
+
message_id.present?
|
16
|
+
end
|
17
|
+
|
18
|
+
def fail?
|
19
|
+
!success?
|
20
|
+
end
|
21
|
+
|
22
|
+
def canonical_id
|
23
|
+
registration_id
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_canonical_id?
|
27
|
+
canonical_id.present?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Maia
|
2
|
+
module FCM
|
3
|
+
class ResultCollection
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize(results = [])
|
7
|
+
@results = results
|
8
|
+
end
|
9
|
+
|
10
|
+
def succeeded
|
11
|
+
@results.select(&:success?)
|
12
|
+
end
|
13
|
+
|
14
|
+
def failed
|
15
|
+
@results.select(&:fail?)
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_canonical_ids
|
19
|
+
@results.select(&:has_canonical_id?)
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](index)
|
23
|
+
@results[index]
|
24
|
+
end
|
25
|
+
|
26
|
+
def <<(result)
|
27
|
+
@results << result
|
28
|
+
end
|
29
|
+
|
30
|
+
def each(&block)
|
31
|
+
@results.each(&block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Maia
|
2
|
+
module FCM
|
3
|
+
class Service
|
4
|
+
def initialize
|
5
|
+
@connection ||= FCM::Connection.new key
|
6
|
+
end
|
7
|
+
|
8
|
+
def key
|
9
|
+
ENV.fetch 'FCM_KEY', Maia::FCM.key
|
10
|
+
end
|
11
|
+
|
12
|
+
def deliver(notification, *tokens, topic: nil)
|
13
|
+
responses = ResponseCollection.new notification
|
14
|
+
responses << deliver_all(notification, tokens)
|
15
|
+
responses << deliver_all(notification, topic) if topic
|
16
|
+
responses
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def deliver_all(notification, recipients)
|
21
|
+
batch(recipients).map do |batch|
|
22
|
+
if batch.many?
|
23
|
+
multicast notification, batch
|
24
|
+
elsif batch.one?
|
25
|
+
unicast notification, batch.first
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def unicast(notification, recipient)
|
31
|
+
deliver! notification, recipient, to: recipient
|
32
|
+
end
|
33
|
+
|
34
|
+
def multicast(notification, recipients)
|
35
|
+
deliver! notification, recipients, registration_ids: recipients
|
36
|
+
end
|
37
|
+
|
38
|
+
def deliver!(notification, recipients, params = {})
|
39
|
+
payload = notification.to_h.merge params
|
40
|
+
Response.new @connection.write(payload), Array(recipients)
|
41
|
+
end
|
42
|
+
|
43
|
+
def batch(recipients, batch_size: Maia::BATCH_SIZE)
|
44
|
+
Array(recipients).flatten.compact.each_slice batch_size
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/maia/message.rb
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
module Maia
|
2
2
|
class Message
|
3
|
-
MAX_TOKENS_AT_ONCE = 999
|
4
|
-
|
5
3
|
def send_to(pushable, job_options = {})
|
6
4
|
devices = Device.owned_by pushable
|
7
|
-
worker
|
5
|
+
worker = Messenger.set job_options
|
8
6
|
|
9
|
-
enqueue worker, devices.android
|
10
|
-
enqueue worker, devices.ios
|
11
|
-
enqueue worker, devices.unknown
|
7
|
+
enqueue worker, devices.android
|
8
|
+
enqueue worker, devices.ios
|
9
|
+
enqueue worker, devices.unknown
|
12
10
|
end
|
13
11
|
|
14
|
-
def enqueue(worker, devices
|
15
|
-
devices.
|
16
|
-
worker.perform_later
|
12
|
+
def enqueue(worker, devices)
|
13
|
+
devices.in_batches(of: Maia::BATCH_SIZE) do |devices|
|
14
|
+
worker.perform_later devices.pluck(:token), to_h.deep_stringify_keys
|
17
15
|
end
|
18
16
|
end
|
19
17
|
|
@@ -23,11 +21,14 @@ module Maia
|
|
23
21
|
def body
|
24
22
|
end
|
25
23
|
|
24
|
+
def on_click
|
25
|
+
end
|
26
|
+
|
26
27
|
def icon
|
27
28
|
end
|
28
29
|
|
29
30
|
def sound
|
30
|
-
|
31
|
+
:default
|
31
32
|
end
|
32
33
|
|
33
34
|
def badge
|
@@ -36,17 +37,6 @@ module Maia
|
|
36
37
|
def color
|
37
38
|
end
|
38
39
|
|
39
|
-
def action
|
40
|
-
end
|
41
|
-
|
42
|
-
def title_i18n
|
43
|
-
[]
|
44
|
-
end
|
45
|
-
|
46
|
-
def body_i18n
|
47
|
-
[]
|
48
|
-
end
|
49
|
-
|
50
40
|
def data
|
51
41
|
end
|
52
42
|
|
@@ -58,12 +48,12 @@ module Maia
|
|
58
48
|
false
|
59
49
|
end
|
60
50
|
|
61
|
-
def
|
51
|
+
def content_mutable?
|
62
52
|
false
|
63
53
|
end
|
64
54
|
|
65
|
-
def
|
66
|
-
|
55
|
+
def dry_run?
|
56
|
+
false
|
67
57
|
end
|
68
58
|
|
69
59
|
def notification
|
@@ -71,51 +61,22 @@ module Maia
|
|
71
61
|
title: title,
|
72
62
|
body: body,
|
73
63
|
icon: icon,
|
74
|
-
sound: sound,
|
64
|
+
sound: sound.to_s,
|
75
65
|
badge: badge,
|
76
66
|
color: color,
|
77
|
-
click_action:
|
78
|
-
}.
|
67
|
+
click_action: on_click
|
68
|
+
}.compact
|
79
69
|
end
|
80
70
|
|
81
|
-
def to_h
|
82
|
-
|
71
|
+
def to_h
|
72
|
+
{
|
83
73
|
priority: priority.to_s,
|
84
74
|
dry_run: dry_run?,
|
85
75
|
content_available: content_available?,
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
payload.compact
|
76
|
+
mutable_content: content_mutable?,
|
77
|
+
data: data,
|
78
|
+
notification: notification
|
79
|
+
}.compact
|
91
80
|
end
|
92
|
-
|
93
|
-
private
|
94
|
-
def i18n
|
95
|
-
{}.tap do |hash|
|
96
|
-
hash[:body_loc_key] = body_i18n.first
|
97
|
-
hash[:body_loc_args] = body_i18n_args
|
98
|
-
hash[:title_loc_key] = title_i18n_key
|
99
|
-
hash[:title_loc_args] = title_i18n_args
|
100
|
-
end.compact
|
101
|
-
end
|
102
|
-
|
103
|
-
def body_i18n_key
|
104
|
-
body_i18n.first
|
105
|
-
end
|
106
|
-
|
107
|
-
def body_i18n_args
|
108
|
-
args = body_i18n.drop 1
|
109
|
-
args if args.any?
|
110
|
-
end
|
111
|
-
|
112
|
-
def title_i18n_key
|
113
|
-
title_i18n.first
|
114
|
-
end
|
115
|
-
|
116
|
-
def title_i18n_args
|
117
|
-
args = title_i18n.drop 1
|
118
|
-
args if args.any?
|
119
|
-
end
|
120
81
|
end
|
121
82
|
end
|
data/lib/maia/messenger.rb
CHANGED
@@ -4,46 +4,48 @@ module Maia
|
|
4
4
|
logger.info "Pushing to #{tokens.size} token(s)..."
|
5
5
|
logger.info "Payload: #{payload}"
|
6
6
|
|
7
|
-
notification =
|
8
|
-
responses
|
7
|
+
notification = FCM::Notification.new payload
|
8
|
+
responses = fcm.deliver notification, tokens
|
9
9
|
|
10
10
|
responses.each do |response|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
handle_failed_tokens response.results.failed
|
15
|
-
update_devices_to_use_canonical_ids response.results.with_canonical_ids
|
16
|
-
end
|
11
|
+
raise Maia::Error, response.error if response.error
|
12
|
+
handle_errors response.results.failed
|
13
|
+
update_devices_to_use_canonical_ids response.results.with_canonical_ids
|
17
14
|
end
|
18
15
|
end
|
19
16
|
|
20
17
|
private
|
21
|
-
def
|
22
|
-
@_service ||=
|
23
|
-
end
|
24
|
-
|
25
|
-
def connection
|
26
|
-
nil # use the default
|
18
|
+
def fcm
|
19
|
+
@_service ||= FCM::Service.new
|
27
20
|
end
|
28
21
|
|
29
|
-
def
|
22
|
+
def handle_errors(results)
|
30
23
|
results.each do |result|
|
31
|
-
device = Maia::Device.find_by
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
24
|
+
device = Maia::Device.find_by token: result.token
|
25
|
+
next unless device.present?
|
26
|
+
|
27
|
+
if device_unrecoverable? result.error
|
28
|
+
log_error "Destroying device #{device.id}", result, device
|
29
|
+
device.destroy
|
30
|
+
else
|
31
|
+
log_error "Push to device #{device.id} failed", result, device
|
39
32
|
end
|
40
33
|
end
|
41
34
|
end
|
42
35
|
|
36
|
+
def device_unrecoverable?(error)
|
37
|
+
error =~ /InvalidRegistration|NotRegistered|MismatchSenderId/
|
38
|
+
end
|
39
|
+
|
40
|
+
def log_error(message, result, device)
|
41
|
+
logger.info "#{message} (error: #{result.error}, token: #{device.token})"
|
42
|
+
end
|
43
|
+
|
43
44
|
def update_devices_to_use_canonical_ids(results)
|
44
45
|
results.each do |result|
|
45
46
|
device = Maia::Device.find_by token: result.token
|
46
47
|
next if device.nil?
|
48
|
+
|
47
49
|
if user_already_has_token_registered?(device.pushable, result.canonical_id)
|
48
50
|
device.destroy
|
49
51
|
else
|
@@ -52,16 +54,8 @@ module Maia
|
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
55
|
-
def device_unrecoverable?(error)
|
56
|
-
error =~ /InvalidRegistration|NotRegistered|MismatchSenderId/
|
57
|
-
end
|
58
|
-
|
59
57
|
def user_already_has_token_registered?(user, token)
|
60
58
|
user.devices.exists? token: token
|
61
59
|
end
|
62
|
-
|
63
|
-
def log_error(message, result, device)
|
64
|
-
logger.info "#{message} (error: #{result.error}, token: #{device.token})"
|
65
|
-
end
|
66
60
|
end
|
67
61
|
end
|
data/lib/maia/poke.rb
CHANGED
data/lib/maia/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: maia
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Logan Serman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -16,58 +16,70 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 5
|
19
|
+
version: '5'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '6'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: 5
|
29
|
+
version: '5'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '6'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: activejob
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
37
|
- - ">="
|
32
38
|
- !ruby/object:Gem::Version
|
33
|
-
version: 5
|
39
|
+
version: '5'
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '6'
|
34
43
|
type: :runtime
|
35
44
|
prerelease: false
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
37
46
|
requirements:
|
38
47
|
- - ">="
|
39
48
|
- !ruby/object:Gem::Version
|
40
|
-
version: 5
|
49
|
+
version: '5'
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '6'
|
41
53
|
- !ruby/object:Gem::Dependency
|
42
54
|
name: responders
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
44
56
|
requirements:
|
45
|
-
- - "
|
57
|
+
- - ">="
|
46
58
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
59
|
+
version: '0'
|
48
60
|
type: :runtime
|
49
61
|
prerelease: false
|
50
62
|
version_requirements: !ruby/object:Gem::Requirement
|
51
63
|
requirements:
|
52
|
-
- - "
|
64
|
+
- - ">="
|
53
65
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
66
|
+
version: '0'
|
55
67
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
68
|
+
name: sqlite3
|
57
69
|
requirement: !ruby/object:Gem::Requirement
|
58
70
|
requirements:
|
59
71
|
- - ">="
|
60
72
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0
|
62
|
-
type: :
|
73
|
+
version: '0'
|
74
|
+
type: :development
|
63
75
|
prerelease: false
|
64
76
|
version_requirements: !ruby/object:Gem::Requirement
|
65
77
|
requirements:
|
66
78
|
- - ">="
|
67
79
|
- !ruby/object:Gem::Version
|
68
|
-
version: 0
|
80
|
+
version: '0'
|
69
81
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
82
|
+
name: rspec-rails
|
71
83
|
requirement: !ruby/object:Gem::Requirement
|
72
84
|
requirements:
|
73
85
|
- - ">="
|
@@ -81,7 +93,7 @@ dependencies:
|
|
81
93
|
- !ruby/object:Gem::Version
|
82
94
|
version: '0'
|
83
95
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
96
|
+
name: capybara
|
85
97
|
requirement: !ruby/object:Gem::Requirement
|
86
98
|
requirements:
|
87
99
|
- - ">="
|
@@ -95,7 +107,7 @@ dependencies:
|
|
95
107
|
- !ruby/object:Gem::Version
|
96
108
|
version: '0'
|
97
109
|
- !ruby/object:Gem::Dependency
|
98
|
-
name: capybara
|
110
|
+
name: capybara-webkit
|
99
111
|
requirement: !ruby/object:Gem::Requirement
|
100
112
|
requirements:
|
101
113
|
- - ">="
|
@@ -109,7 +121,7 @@ dependencies:
|
|
109
121
|
- !ruby/object:Gem::Version
|
110
122
|
version: '0'
|
111
123
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
124
|
+
name: launchy
|
113
125
|
requirement: !ruby/object:Gem::Requirement
|
114
126
|
requirements:
|
115
127
|
- - ">="
|
@@ -123,7 +135,7 @@ dependencies:
|
|
123
135
|
- !ruby/object:Gem::Version
|
124
136
|
version: '0'
|
125
137
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
138
|
+
name: webmock
|
127
139
|
requirement: !ruby/object:Gem::Requirement
|
128
140
|
requirements:
|
129
141
|
- - ">="
|
@@ -137,7 +149,7 @@ dependencies:
|
|
137
149
|
- !ruby/object:Gem::Version
|
138
150
|
version: '0'
|
139
151
|
- !ruby/object:Gem::Dependency
|
140
|
-
name: webmock
|
152
|
+
name: webmock-rspec-helper
|
141
153
|
requirement: !ruby/object:Gem::Requirement
|
142
154
|
requirements:
|
143
155
|
- - ">="
|
@@ -150,7 +162,7 @@ dependencies:
|
|
150
162
|
- - ">="
|
151
163
|
- !ruby/object:Gem::Version
|
152
164
|
version: '0'
|
153
|
-
description: Manage device tokens and push messaging with Rails and
|
165
|
+
description: Manage device tokens and push messaging with Rails and FCM.
|
154
166
|
email:
|
155
167
|
- loganserman@gmail.com
|
156
168
|
executables: []
|
@@ -158,6 +170,7 @@ extensions: []
|
|
158
170
|
extra_rdoc_files: []
|
159
171
|
files:
|
160
172
|
- MIT-LICENSE
|
173
|
+
- README.md
|
161
174
|
- Rakefile
|
162
175
|
- app/controllers/concerns/maia/controller.rb
|
163
176
|
- app/models/concerns/maia/model.rb
|
@@ -171,6 +184,14 @@ files:
|
|
171
184
|
- lib/maia/dry_run.rb
|
172
185
|
- lib/maia/engine.rb
|
173
186
|
- lib/maia/error.rb
|
187
|
+
- lib/maia/fcm.rb
|
188
|
+
- lib/maia/fcm/connection.rb
|
189
|
+
- lib/maia/fcm/notification.rb
|
190
|
+
- lib/maia/fcm/response.rb
|
191
|
+
- lib/maia/fcm/response_collection.rb
|
192
|
+
- lib/maia/fcm/result.rb
|
193
|
+
- lib/maia/fcm/result_collection.rb
|
194
|
+
- lib/maia/fcm/service.rb
|
174
195
|
- lib/maia/message.rb
|
175
196
|
- lib/maia/messenger.rb
|
176
197
|
- lib/maia/poke.rb
|
@@ -195,8 +216,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
195
216
|
version: '0'
|
196
217
|
requirements: []
|
197
218
|
rubyforge_project:
|
198
|
-
rubygems_version: 2.6.
|
219
|
+
rubygems_version: 2.6.11
|
199
220
|
signing_key:
|
200
221
|
specification_version: 4
|
201
|
-
summary: Manage device tokens and push messaging with Rails and
|
222
|
+
summary: Manage device tokens and push messaging with Rails and FCM.
|
202
223
|
test_files: []
|