fcmpush 0.1.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/miyataka/fcmpush.svg?branch=master)](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
|