fcmpush 0.1.2 → 0.9.0
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/.rubocop.yml +3 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +14 -1
- data/README.md +28 -5
- data/lib/fcmpush.rb +1 -60
- data/lib/fcmpush/client.rb +125 -0
- data/lib/fcmpush/configuration.rb +4 -1
- data/lib/fcmpush/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a58a38f4ef75e8d95b3806d811c72901a8c36b6deb339d0add0733fec54a6712
|
4
|
+
data.tar.gz: f97502ea7319cda995cde93901dc392e3530d13aff30d6385c9f76302b751722
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23b20f5aef8feede77ccbc519a6abfb70932055b0a761b4ea2d29418d54dd8820d0b34b6e1292ac5c4f72f6c29de92cc7dd5f6bc1672970692f7bdcb7bf5a10a
|
7
|
+
data.tar.gz: e17f9cba6b6053229f6fff77678e20fbe132dfe837b684b732656a4701db98a9a5381bf44733b8f096ce72b9af587f35b6b4a8139bb16d2f635937445bb57e05
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,17 +1,23 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fcmpush (0.
|
4
|
+
fcmpush (0.9.0)
|
5
5
|
googleauth (>= 0.10.0)
|
6
6
|
net-http-persistent (>= 3.1.0)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
+
activesupport (5.2.3)
|
12
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
13
|
+
i18n (>= 0.7, < 2)
|
14
|
+
minitest (~> 5.1)
|
15
|
+
tzinfo (~> 1.1)
|
11
16
|
addressable (2.7.0)
|
12
17
|
public_suffix (>= 2.0.2, < 5.0)
|
13
18
|
byebug (11.0.1)
|
14
19
|
coderay (1.1.2)
|
20
|
+
concurrent-ruby (1.1.5)
|
15
21
|
connection_pool (2.2.2)
|
16
22
|
diff-lcs (1.3)
|
17
23
|
faraday (0.17.0)
|
@@ -23,9 +29,12 @@ GEM
|
|
23
29
|
multi_json (~> 1.11)
|
24
30
|
os (>= 0.9, < 2.0)
|
25
31
|
signet (~> 0.12)
|
32
|
+
i18n (1.7.0)
|
33
|
+
concurrent-ruby (~> 1.0)
|
26
34
|
jwt (2.2.1)
|
27
35
|
memoist (0.16.0)
|
28
36
|
method_source (0.9.2)
|
37
|
+
minitest (5.12.2)
|
29
38
|
multi_json (1.13.1)
|
30
39
|
multipart-post (2.1.1)
|
31
40
|
net-http-persistent (3.1.0)
|
@@ -57,11 +66,15 @@ GEM
|
|
57
66
|
faraday (~> 0.9)
|
58
67
|
jwt (>= 1.5, < 3.0)
|
59
68
|
multi_json (~> 1.10)
|
69
|
+
thread_safe (0.3.6)
|
70
|
+
tzinfo (1.2.5)
|
71
|
+
thread_safe (~> 0.1)
|
60
72
|
|
61
73
|
PLATFORMS
|
62
74
|
ruby
|
63
75
|
|
64
76
|
DEPENDENCIES
|
77
|
+
activesupport (< 6.0)
|
65
78
|
bundler (~> 2.0)
|
66
79
|
fcmpush!
|
67
80
|
pry-byebug
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Fcmpush [](https://travis-ci.org/miyataka/fcmpush)
|
2
2
|
|
3
|
-
Fcmpush is an Firebase Cloud Messaging(FCM) Client. It implements [FCM HTTP v1 API](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages)
|
4
|
-
This gem supports HTTP v1 API only, **NOT supported [legacy HTTP protocol](https://firebase.google.com/docs/cloud-messaging/http-server-ref)
|
3
|
+
Fcmpush is an Firebase Cloud Messaging(FCM) Client. It implements [FCM HTTP v1 API](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages), including **Auto Refresh** access_token feature!!
|
4
|
+
This gem supports HTTP v1 API only, **NOT supported [legacy HTTP protocol](https://firebase.google.com/docs/cloud-messaging/http-server-ref)**.
|
5
5
|
|
6
6
|
fcmpush is highly inspired by [andpush gem](https://github.com/yuki24/andpush).
|
7
7
|
|
@@ -26,6 +26,7 @@ Or install it yourself as:
|
|
26
26
|
on Rails, config/initializers/fcmpush.rb
|
27
27
|
```ruby
|
28
28
|
Fcmpush.configure do |config|
|
29
|
+
## for message push
|
29
30
|
# firebase web console => project settings => service account => firebase admin sdk => generate new private key
|
30
31
|
config.json_key_io = "#{Rails.root}/path/to/service_account_credentials.json"
|
31
32
|
|
@@ -33,12 +34,19 @@ Fcmpush.configure do |config|
|
|
33
34
|
# ENV['GOOGLE_ACCOUNT_TYPE'] = 'service_account'
|
34
35
|
# ENV['GOOGLE_CLIENT_ID'] = '000000000000000000000'
|
35
36
|
# ENV['GOOGLE_CLIENT_EMAIL'] = 'xxxx@xxxx.iam.gserviceaccount.com'
|
36
|
-
# ENV['GOOGLE_PRIVATE_KEY'] = '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n\
|
37
|
+
# ENV['GOOGLE_PRIVATE_KEY'] = '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n\'
|
38
|
+
|
39
|
+
## for topic subscribe/unsubscribe because they use regacy auth
|
40
|
+
# firebase web console => project settings => cloud messaging => Project credentials => Server key
|
41
|
+
config.server_key = 'your firebase server key'
|
42
|
+
# Or set environment variables
|
43
|
+
# ENV['FCM_SERVER_KEY'] = 'your firebase server key'
|
37
44
|
end
|
38
45
|
```
|
39
46
|
|
40
47
|
for more detail. see [here](https://github.com/googleapis/google-auth-library-ruby#example-service-account).
|
41
48
|
|
49
|
+
### push message
|
42
50
|
```ruby
|
43
51
|
require 'fcmpush'
|
44
52
|
|
@@ -62,9 +70,24 @@ json = response.json
|
|
62
70
|
json[:name] # => "projects/[your_project_id]/messages/0:1571037134532751%31bd1c9631bd1c96"
|
63
71
|
```
|
64
72
|
|
73
|
+
### topic subscribe/unsubscribe
|
74
|
+
```ruby
|
75
|
+
require 'fcmpush'
|
76
|
+
|
77
|
+
project_id = "..." # Your project_id
|
78
|
+
topic = "your_topic_name"
|
79
|
+
device_tokens = ["device_tokenA", "device_tokenB", ...] # The device tokens of the device you'd like to subscribe
|
80
|
+
|
81
|
+
client = Fcmpush.new(project_id)
|
82
|
+
|
83
|
+
response = client.subscribe(topic, device_tokens)
|
84
|
+
# response = client.unsubscribe(topic, device_tokens)
|
85
|
+
|
86
|
+
json = response.json
|
87
|
+
json[:results] # => [{}, {"error":"NOT_FOUND"}, ...] ref. https://developers.google.com/instance-id/reference/server#example_result_3
|
88
|
+
```
|
89
|
+
|
65
90
|
## Future Work
|
66
|
-
- topic subscribe/unsubscribe
|
67
|
-
- auto refresh access_token before expiry
|
68
91
|
- [DEV] compare other gems
|
69
92
|
|
70
93
|
## Contributing
|
data/lib/fcmpush.rb
CHANGED
@@ -3,14 +3,11 @@ require 'googleauth'
|
|
3
3
|
|
4
4
|
require 'fcmpush/configuration'
|
5
5
|
require 'fcmpush/version'
|
6
|
-
require 'fcmpush/
|
7
|
-
require 'fcmpush/json_response'
|
6
|
+
require 'fcmpush/client'
|
8
7
|
|
9
8
|
module Fcmpush
|
10
9
|
class Error < StandardError; end
|
11
10
|
DOMAIN = 'https://fcm.googleapis.com'.freeze
|
12
|
-
V1_ENDPOINT_PREFIX = '/v1/projects/'.freeze
|
13
|
-
V1_ENDPOINT_SUFFIX = '/messages:send'.freeze
|
14
11
|
|
15
12
|
class << self
|
16
13
|
def build(project_id, domain: nil)
|
@@ -30,60 +27,4 @@ module Fcmpush
|
|
30
27
|
def self.configure(&block)
|
31
28
|
yield(configuration(&block))
|
32
29
|
end
|
33
|
-
|
34
|
-
class Client
|
35
|
-
attr_reader :domain, :path, :connection, :access_token, :configuration
|
36
|
-
|
37
|
-
def initialize(domain, project_id, configuration, **options)
|
38
|
-
@domain = domain
|
39
|
-
@project_id = project_id
|
40
|
-
@path = V1_ENDPOINT_PREFIX + project_id.to_s + V1_ENDPOINT_SUFFIX
|
41
|
-
@options = {}.merge(options)
|
42
|
-
@configuration = configuration
|
43
|
-
@access_token = authorize
|
44
|
-
@connection = Net::HTTP::Persistent.new
|
45
|
-
end
|
46
|
-
|
47
|
-
def authorize
|
48
|
-
@auth ||= if configuration.json_key_io
|
49
|
-
Google::Auth::ServiceAccountCredentials.make_creds(
|
50
|
-
json_key_io: File.open(configuration.json_key_io),
|
51
|
-
scope: configuration.scope
|
52
|
-
)
|
53
|
-
else
|
54
|
-
# from ENV
|
55
|
-
Google::Auth::ServiceAccountCredentials.make_creds(
|
56
|
-
scope: configuration.scope
|
57
|
-
)
|
58
|
-
end
|
59
|
-
@auth.fetch_access_token!['access_token']
|
60
|
-
end
|
61
|
-
|
62
|
-
def push(body, query: {}, headers: {})
|
63
|
-
uri = URI.join(domain, path)
|
64
|
-
uri.query = URI.encode_www_form(query) unless query.empty?
|
65
|
-
|
66
|
-
headers = authorized_header(headers)
|
67
|
-
post = Net::HTTP::Post.new(uri, headers)
|
68
|
-
post.body = body.is_a?(String) ? body : body.to_json
|
69
|
-
|
70
|
-
response = exception_handler(connection.request(uri, post))
|
71
|
-
JsonResponse.new(response)
|
72
|
-
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
|
73
|
-
raise NetworkError, "A network error occurred: #{e.class} (#{e.message})"
|
74
|
-
end
|
75
|
-
|
76
|
-
def authorized_header(headers)
|
77
|
-
headers.merge('Content-Type' => 'application/json',
|
78
|
-
'Accept' => 'application/json',
|
79
|
-
'Authorization' => "Bearer #{access_token}")
|
80
|
-
end
|
81
|
-
|
82
|
-
def exception_handler(response)
|
83
|
-
error = STATUS_TO_EXCEPTION_MAPPING[response.code]
|
84
|
-
raise error.new("Receieved an error response #{response.code} #{error.to_s.split('::').last}: #{response.body}", response) if error
|
85
|
-
|
86
|
-
response
|
87
|
-
end
|
88
|
-
end
|
89
30
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'fcmpush/exceptions'
|
2
|
+
require 'fcmpush/json_response'
|
3
|
+
|
4
|
+
module Fcmpush
|
5
|
+
V1_ENDPOINT_PREFIX = '/v1/projects/'.freeze
|
6
|
+
V1_ENDPOINT_SUFFIX = '/messages:send'.freeze
|
7
|
+
TOPIC_DOMAIN = 'https://iid.googleapis.com'.freeze
|
8
|
+
TOPIC_ENDPOINT_PREFIX = '/iid/v1'.freeze
|
9
|
+
|
10
|
+
class Client
|
11
|
+
attr_reader :domain, :path, :connection, :configuration, :server_key, :access_token, :access_token_expiry
|
12
|
+
|
13
|
+
def initialize(domain, project_id, configuration, **options)
|
14
|
+
@domain = domain
|
15
|
+
@project_id = project_id
|
16
|
+
@path = V1_ENDPOINT_PREFIX + project_id.to_s + V1_ENDPOINT_SUFFIX
|
17
|
+
@options = {}.merge(options)
|
18
|
+
@configuration = configuration
|
19
|
+
access_token_response = v1_authorize
|
20
|
+
@access_token = access_token_response['access_token']
|
21
|
+
@access_token_expiry = Time.now.utc + access_token_response['expires_in']
|
22
|
+
@server_key = configuration.server_key
|
23
|
+
@connection = Net::HTTP::Persistent.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def v1_authorize
|
27
|
+
@auth ||= if configuration.json_key_io
|
28
|
+
Google::Auth::ServiceAccountCredentials.make_creds(
|
29
|
+
json_key_io: File.open(configuration.json_key_io),
|
30
|
+
scope: configuration.scope
|
31
|
+
)
|
32
|
+
else
|
33
|
+
# from ENV
|
34
|
+
Google::Auth::ServiceAccountCredentials.make_creds(scope: configuration.scope)
|
35
|
+
end
|
36
|
+
@auth.fetch_access_token
|
37
|
+
end
|
38
|
+
|
39
|
+
def push(body, query: {}, headers: {})
|
40
|
+
uri, request = make_push_request(body, query, headers)
|
41
|
+
response = exception_handler(connection.request(uri, request))
|
42
|
+
JsonResponse.new(response)
|
43
|
+
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
|
44
|
+
raise NetworkError, "A network error occurred: #{e.class} (#{e.message})"
|
45
|
+
end
|
46
|
+
|
47
|
+
def subscribe(topic, *instance_ids, query: {}, headers: {})
|
48
|
+
uri, request = make_subscription_request(topic, *instance_ids, :subscribe, query, headers)
|
49
|
+
response = exception_handler(connection.request(uri, request))
|
50
|
+
JsonResponse.new(response)
|
51
|
+
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
|
52
|
+
raise NetworkError, "A network error occurred: #{e.class} (#{e.message})"
|
53
|
+
end
|
54
|
+
|
55
|
+
def unsubscribe(topic, *instance_ids, query: {}, headers: {})
|
56
|
+
uri, request = make_subscription_request(topic, *instance_ids, :unsubscribe, query, headers)
|
57
|
+
response = exception_handler(connection.request(uri, request))
|
58
|
+
JsonResponse.new(response)
|
59
|
+
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
|
60
|
+
raise NetworkError, "A network error occurred: #{e.class} (#{e.message})"
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def make_push_request(body, query, headers)
|
66
|
+
uri = URI.join(domain, path)
|
67
|
+
uri.query = URI.encode_www_form(query) unless query.empty?
|
68
|
+
|
69
|
+
access_token_refresh
|
70
|
+
headers = v1_authorized_header(headers)
|
71
|
+
post = Net::HTTP::Post.new(uri, headers)
|
72
|
+
post.body = body.is_a?(String) ? body : body.to_json
|
73
|
+
|
74
|
+
[uri, post]
|
75
|
+
end
|
76
|
+
|
77
|
+
def make_subscription_request(topic, instance_ids, type, query, headers)
|
78
|
+
suffix = type == :subscribe ? ':batchAdd' : ':batchRemove'
|
79
|
+
|
80
|
+
uri = URI.join(TOPIC_DOMAIN, TOPIC_ENDPOINT_PREFIX + suffix)
|
81
|
+
uri.query = URI.encode_www_form(query) unless query.empty?
|
82
|
+
|
83
|
+
headers = legacy_authorized_header(headers)
|
84
|
+
post = Net::HTTP::Post.new(uri, headers)
|
85
|
+
post.body = make_subscription_body(topic, *instance_ids)
|
86
|
+
|
87
|
+
[uri, post]
|
88
|
+
end
|
89
|
+
|
90
|
+
def access_token_refresh
|
91
|
+
return if access_token_expiry > Time.now.utc + 300
|
92
|
+
|
93
|
+
access_token_response = v1_authorize
|
94
|
+
@access_token = access_token_response['access_token']
|
95
|
+
@access_token_expiry = Time.now.utc + access_token_response['expires_in']
|
96
|
+
end
|
97
|
+
|
98
|
+
def v1_authorized_header(headers)
|
99
|
+
headers.merge('Content-Type' => 'application/json',
|
100
|
+
'Accept' => 'application/json',
|
101
|
+
'Authorization' => "Bearer #{access_token}")
|
102
|
+
end
|
103
|
+
|
104
|
+
def legacy_authorized_header(headers)
|
105
|
+
headers.merge('Content-Type' => 'application/json',
|
106
|
+
'Accept' => 'application/json',
|
107
|
+
'Authorization' => "Bearer key=#{server_key}")
|
108
|
+
end
|
109
|
+
|
110
|
+
def exception_handler(response)
|
111
|
+
error = STATUS_TO_EXCEPTION_MAPPING[response.code]
|
112
|
+
raise error.new("Receieved an error response #{response.code} #{error.to_s.split('::').last}: #{response.body}", response) if error
|
113
|
+
|
114
|
+
response
|
115
|
+
end
|
116
|
+
|
117
|
+
def make_subscription_body(topic, *instance_ids)
|
118
|
+
topic = topic.match?(%r{^/topics/}) ? topic : '/topics/' + topic
|
119
|
+
{
|
120
|
+
to: topic,
|
121
|
+
registration_tokens: instance_ids
|
122
|
+
}.to_json
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Fcmpush
|
2
2
|
class Configuration
|
3
|
-
attr_accessor :scope, :json_key_io
|
3
|
+
attr_accessor :scope, :json_key_io, :server_key
|
4
4
|
|
5
5
|
def initialize
|
6
6
|
@scope = ['https://www.googleapis.com/auth/firebase.messaging']
|
@@ -13,6 +13,9 @@ module Fcmpush
|
|
13
13
|
# ENV['GOOGLE_CLIENT_ID'] = '000000000000000000000'
|
14
14
|
# ENV['GOOGLE_CLIENT_EMAIL'] = 'xxxx@xxxx.iam.gserviceaccount.com'
|
15
15
|
# ENV['GOOGLE_PRIVATE_KEY'] = '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n'
|
16
|
+
|
17
|
+
# regacy auth
|
18
|
+
@server_key = ENV['FCM_SERVER_KEY']
|
16
19
|
end
|
17
20
|
end
|
18
21
|
end
|
data/lib/fcmpush/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fcmpush
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Takayuki Miyahara
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-10-
|
11
|
+
date: 2019-10-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: googleauth
|
@@ -100,6 +100,7 @@ files:
|
|
100
100
|
- bin/setup
|
101
101
|
- fcmpush.gemspec
|
102
102
|
- lib/fcmpush.rb
|
103
|
+
- lib/fcmpush/client.rb
|
103
104
|
- lib/fcmpush/configuration.rb
|
104
105
|
- lib/fcmpush/exceptions.rb
|
105
106
|
- lib/fcmpush/json_response.rb
|