maia 4.0.0 → 5.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +35 -59
- data/app/controllers/concerns/maia/controller.rb +1 -6
- data/app/models/maia/device.rb +4 -0
- data/lib/maia.rb +23 -11
- data/lib/maia/devices.rb +21 -0
- data/lib/maia/error/generic.rb +7 -0
- data/lib/maia/error/no_credentials.rb +9 -0
- data/lib/maia/error/unregistered.rb +12 -0
- data/lib/maia/fcm/connection.rb +18 -18
- data/lib/maia/fcm/credentials.rb +44 -0
- data/lib/maia/fcm/gateway.rb +30 -0
- data/lib/maia/fcm/notification.rb +14 -12
- data/lib/maia/fcm/platform/android.rb +37 -0
- data/lib/maia/fcm/platform/apns.rb +42 -0
- data/lib/maia/fcm/response.rb +17 -31
- data/lib/maia/fcm/serializer.rb +38 -0
- data/lib/maia/message.rb +24 -65
- data/lib/maia/messengers/activejob.rb +19 -0
- data/lib/maia/messengers/array.rb +21 -0
- data/lib/maia/messengers/inline.rb +13 -0
- data/lib/maia/token.rb +21 -0
- data/lib/maia/topic.rb +21 -0
- data/lib/maia/version.rb +1 -1
- metadata +25 -61
- data/lib/maia/dry_run.rb +0 -15
- data/lib/maia/error.rb +0 -4
- data/lib/maia/fcm.rb +0 -7
- data/lib/maia/fcm/response_collection.rb +0 -34
- data/lib/maia/fcm/result.rb +0 -31
- data/lib/maia/fcm/result_collection.rb +0 -35
- data/lib/maia/fcm/service.rb +0 -48
- data/lib/maia/messenger.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d3efb00ad0b208362f022cc62718c3e7acf26f850007dfa899123f5b1de33250
|
4
|
+
data.tar.gz: 77b3a882f44ee407c1bc00075ba591646097adbb284f8a7429de575b43ca28a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8ff83629565645b0b6ea9c8802974d0d924f08c596f61364956b73e92bf4610f801ba41b8a9e9b486cc8c19a0db0e1f3445d1217dda66e205554c2c28e962d3
|
7
|
+
data.tar.gz: 31da736742c1b67abbdea6863f2cd090576f7f08bde0466dabee5f1cee6e088ff6428439731886d14c060207498a673b4f5171c62f7354a1212e670f9772ca2a
|
data/README.md
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
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
4
|
|
5
|
+
As of Maia 5, only the FCM HTTP v1 is supported. Use an older version of Maia if
|
6
|
+
you need to use the FCM legacy API.
|
7
|
+
|
5
8
|
## Installation
|
6
9
|
|
7
10
|
```
|
@@ -13,27 +16,39 @@ bin/rake db:migrate
|
|
13
16
|
|
14
17
|
This will copy the `maia_devices` table into your project.
|
15
18
|
|
16
|
-
|
19
|
+
### Messenger configuration
|
17
20
|
|
18
|
-
Maia
|
21
|
+
Maia is setup to use [ActiveJob](https://github.com/rails/rails/tree/master/activejob)
|
22
|
+
as it's default messenger. If you want to send messages inline instead, use the inline adapter:
|
19
23
|
|
20
|
-
|
24
|
+
```
|
25
|
+
Maia.messenger = Maia::Messengers::Inline.new
|
26
|
+
```
|
27
|
+
|
28
|
+
or set it to anything that responds to `#deliver(payload)`.
|
29
|
+
|
30
|
+
### Gateway configuration
|
31
|
+
|
32
|
+
Maia uses the FCM HTTP v1 gateway by default. This assumes you are using `['GOOGLE_APPLICATION_CREDENTIALS']`
|
33
|
+
for authentication, so you should be good to go if this environment variable is set. If not, you can pass a custom
|
34
|
+
object to the FCM gateway as long as it responds to `#project` and `#token`.
|
21
35
|
|
22
|
-
```
|
23
|
-
Maia
|
36
|
+
```
|
37
|
+
Maia.gateway = Maia::FCM::Gateway.new CustomFCMCredentials.new
|
24
38
|
```
|
25
39
|
|
26
|
-
|
40
|
+
### Rails configuration
|
27
41
|
|
28
42
|
Include `Maia::Model` into your User model. This will attach the `has_many` relationship you need with `Maia::Device`:
|
29
43
|
|
30
44
|
```ruby
|
31
45
|
class User
|
32
46
|
include Maia::Model
|
33
|
-
# ...
|
34
47
|
end
|
35
48
|
```
|
36
49
|
|
50
|
+
## Device Registration
|
51
|
+
|
37
52
|
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
53
|
|
39
54
|
Here's an example of getting setup with an API Devices controller that mobile apps can register with:
|
@@ -50,25 +65,17 @@ class API::DevicesController
|
|
50
65
|
|
51
66
|
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
67
|
|
53
|
-
## Device Registration
|
54
|
-
|
55
68
|
Devices can register with your application by submitting a POST to your devices controller with these params:
|
56
69
|
|
57
70
|
```
|
58
71
|
{ "device": { "token": "<TOKEN>" } }
|
59
72
|
```
|
60
73
|
|
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.
|
74
|
+
Where `<TOKEN>` is the token from FCM registration. Maia will automatically destroy devices when FCM responds with an `UNREGISTERED` error.
|
68
75
|
|
69
76
|
## Device Expiration
|
70
77
|
|
71
|
-
Devices will expire after 14 days. This is to ensure
|
78
|
+
Devices will expire after 14 days. This is to ensure users 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
79
|
|
73
80
|
## Defining Messages
|
74
81
|
|
@@ -87,16 +94,16 @@ class ExampleMessage < Maia::Message
|
|
87
94
|
end
|
88
95
|
|
89
96
|
# Determines the icon to load on Android phones
|
90
|
-
def
|
97
|
+
def image
|
91
98
|
'icn_maia'
|
92
99
|
end
|
93
100
|
|
94
|
-
#
|
101
|
+
# Sound to play on arrival (nil by default)
|
95
102
|
def sound
|
96
103
|
'default'
|
97
104
|
end
|
98
105
|
|
99
|
-
# Badge to use on iOS
|
106
|
+
# Badge to use on iOS (nil by default)
|
100
107
|
def badge
|
101
108
|
1
|
102
109
|
end
|
@@ -106,11 +113,6 @@ class ExampleMessage < Maia::Message
|
|
106
113
|
'#ffffff'
|
107
114
|
end
|
108
115
|
|
109
|
-
# click_action on Android, category on iOS
|
110
|
-
def on_click
|
111
|
-
'SOMETHING_HAPPENED'
|
112
|
-
end
|
113
|
-
|
114
116
|
# Any additional data to send with the payload
|
115
117
|
def data
|
116
118
|
{ foo: :bar }
|
@@ -122,45 +124,17 @@ class ExampleMessage < Maia::Message
|
|
122
124
|
end
|
123
125
|
|
124
126
|
# Override to true in order to send the iOS content-available flag
|
125
|
-
def
|
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?
|
127
|
+
def background?
|
131
128
|
false
|
132
129
|
end
|
133
130
|
end
|
134
131
|
```
|
135
132
|
|
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
133
|
`Maia::Message` does not define a constructor so you can construct your message however you want.
|
160
134
|
|
161
135
|
## Sending messages
|
162
136
|
|
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.
|
137
|
+
`Maia::Message` provides a `send_to` method 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
138
|
|
165
139
|
For example:
|
166
140
|
|
@@ -169,14 +143,16 @@ ExampleMessage.new(...).send_to User.first
|
|
169
143
|
ExampleMessage.new(...).send_to User.where(beta: true)
|
170
144
|
```
|
171
145
|
|
172
|
-
|
146
|
+
You can also send a message directly to a raw token:
|
173
147
|
|
174
|
-
|
148
|
+
```ruby
|
149
|
+
ExampleMessage.new(...).send_to token: 'token123'
|
150
|
+
```
|
175
151
|
|
176
|
-
|
152
|
+
or to a topic:
|
177
153
|
|
178
154
|
```ruby
|
179
|
-
ExampleMessage.new(...).send_to
|
155
|
+
ExampleMessage.new(...).send_to topic: 'my-topic'
|
180
156
|
```
|
181
157
|
|
182
158
|
## Sending a test push
|
@@ -8,7 +8,6 @@ module Maia
|
|
8
8
|
update_device @device
|
9
9
|
else
|
10
10
|
@device = create_device_token
|
11
|
-
send_dry_run_to current_user
|
12
11
|
end
|
13
12
|
respond_with @device
|
14
13
|
end
|
@@ -25,7 +24,7 @@ module Maia
|
|
25
24
|
end
|
26
25
|
|
27
26
|
def find_device(token = params[:device][:token])
|
28
|
-
current_user.devices.find_by token: token
|
27
|
+
current_user.devices.find_by! token: token
|
29
28
|
end
|
30
29
|
|
31
30
|
def update_device(device)
|
@@ -38,10 +37,6 @@ module Maia
|
|
38
37
|
current_user.devices.create permitted_params
|
39
38
|
end
|
40
39
|
|
41
|
-
def send_dry_run_to(user)
|
42
|
-
Maia::DryRun.new.send_to user
|
43
|
-
end
|
44
|
-
|
45
40
|
def permitted_params
|
46
41
|
params.require(:device).permit :token, :platform
|
47
42
|
end
|
data/app/models/maia/device.rb
CHANGED
data/lib/maia.rb
CHANGED
@@ -1,22 +1,34 @@
|
|
1
|
-
require 'rails'
|
2
|
-
require 'active_support/core_ext/enumerable'
|
1
|
+
require 'rails/all'
|
3
2
|
|
4
3
|
require 'maia/engine'
|
5
4
|
require 'maia/message'
|
6
|
-
require 'maia/messenger'
|
7
5
|
require 'maia/poke'
|
8
|
-
require 'maia/
|
9
|
-
require 'maia/
|
6
|
+
require 'maia/token'
|
7
|
+
require 'maia/topic'
|
8
|
+
require 'maia/devices'
|
9
|
+
|
10
|
+
require 'maia/messengers/array'
|
11
|
+
require 'maia/messengers/inline'
|
12
|
+
require 'maia/messengers/activejob'
|
13
|
+
|
14
|
+
require 'maia/error/generic'
|
15
|
+
require 'maia/error/unregistered'
|
16
|
+
require 'maia/error/no_credentials'
|
10
17
|
|
11
|
-
require 'maia/fcm'
|
12
18
|
require 'maia/fcm/connection'
|
19
|
+
require 'maia/fcm/credentials'
|
20
|
+
require 'maia/fcm/gateway'
|
13
21
|
require 'maia/fcm/notification'
|
14
|
-
require 'maia/fcm/response_collection'
|
15
22
|
require 'maia/fcm/response'
|
16
|
-
require 'maia/fcm/
|
17
|
-
require 'maia/fcm/
|
18
|
-
require 'maia/fcm/
|
23
|
+
require 'maia/fcm/serializer'
|
24
|
+
require 'maia/fcm/platform/android'
|
25
|
+
require 'maia/fcm/platform/apns'
|
19
26
|
|
20
27
|
module Maia
|
21
|
-
|
28
|
+
class << self
|
29
|
+
attr_accessor :gateway, :messenger
|
30
|
+
end
|
22
31
|
end
|
32
|
+
|
33
|
+
Maia.gateway = Maia::FCM::Gateway.new
|
34
|
+
Maia.messenger = Maia::Messengers::ActiveJob.new
|
data/lib/maia/devices.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Maia
|
2
|
+
class Devices
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(models)
|
6
|
+
@models = models
|
7
|
+
end
|
8
|
+
|
9
|
+
def each(&block)
|
10
|
+
tokens.each(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def tokens
|
14
|
+
return [] if @models.empty?
|
15
|
+
|
16
|
+
Maia::Device.owned_by(@models).tokens.map do |token|
|
17
|
+
Maia::Token.new token
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/maia/fcm/connection.rb
CHANGED
@@ -1,34 +1,34 @@
|
|
1
1
|
module Maia
|
2
2
|
module FCM
|
3
3
|
class Connection
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
@key = key
|
4
|
+
def initialize(project, token)
|
5
|
+
@project = project
|
6
|
+
@token = token
|
8
7
|
end
|
9
8
|
|
10
9
|
def write(payload = {})
|
11
10
|
request = Net::HTTP::Post.new uri, headers
|
12
|
-
request.body = payload
|
11
|
+
request.body = payload
|
13
12
|
http.request request
|
14
13
|
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
private
|
16
|
+
def uri
|
17
|
+
URI("https://fcm.googleapis.com/v1/projects/#{@project}/messages:send")
|
18
|
+
end
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
def headers
|
21
|
+
{
|
22
|
+
'Content-Type' => 'application/json',
|
23
|
+
'Authorization' => "Bearer #{@token}"
|
24
|
+
}
|
25
|
+
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
def http
|
28
|
+
@_http ||= Net::HTTP.new(uri.host, uri.port).tap do |h|
|
29
|
+
h.use_ssl = true
|
30
|
+
end
|
30
31
|
end
|
31
|
-
end
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'googleauth'
|
2
|
+
|
3
|
+
module Maia
|
4
|
+
module FCM
|
5
|
+
class Credentials
|
6
|
+
SCOPE = 'https://www.googleapis.com/auth/firebase.messaging'.freeze
|
7
|
+
|
8
|
+
def initialize(path = ENV['GOOGLE_APPLICATION_CREDENTIALS'], cache: Rails.cache)
|
9
|
+
@path = path
|
10
|
+
@cache = cache
|
11
|
+
end
|
12
|
+
|
13
|
+
def project
|
14
|
+
@project ||= to_h['project_id']
|
15
|
+
end
|
16
|
+
|
17
|
+
def token
|
18
|
+
@cache.fetch('maia-fcm-token', expires_in: 1.hour) do
|
19
|
+
credentials.fetch_access_token!['access_token']
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
@to_h ||= JSON.parse file.read
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def file
|
29
|
+
if @path && File.exist?(@path)
|
30
|
+
File.new @path
|
31
|
+
else
|
32
|
+
raise Maia::Error::NoCredentials
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def credentials
|
37
|
+
@credentials ||= Google::Auth::ServiceAccountCredentials.make_creds(
|
38
|
+
json_key_io: file,
|
39
|
+
scope: SCOPE
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Maia
|
2
|
+
module FCM
|
3
|
+
class Gateway
|
4
|
+
def initialize(auth = Maia::FCM::Credentials.new)
|
5
|
+
@auth = auth
|
6
|
+
end
|
7
|
+
|
8
|
+
def deliver(payload)
|
9
|
+
response = Maia::FCM::Response.new connection.write(payload)
|
10
|
+
|
11
|
+
if response.fail?
|
12
|
+
error = response.error
|
13
|
+
error.payload = payload
|
14
|
+
raise error
|
15
|
+
end
|
16
|
+
|
17
|
+
response
|
18
|
+
end
|
19
|
+
|
20
|
+
def serialize(message, target)
|
21
|
+
Maia::FCM::Serializer.new(message, target).to_json
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def connection
|
26
|
+
Maia::FCM::Connection.new(@auth.project, @auth.token)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|