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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eff608513cb7687d9cca8c68084dcfd5be1ca90c4037d62988bbd641bf988387
4
- data.tar.gz: 6a48e86e369b7aa8864be4106ea0914a59d6d10c72d0d3c7399061686f7fdad2
3
+ metadata.gz: a58a38f4ef75e8d95b3806d811c72901a8c36b6deb339d0add0733fec54a6712
4
+ data.tar.gz: f97502ea7319cda995cde93901dc392e3530d13aff30d6385c9f76302b751722
5
5
  SHA512:
6
- metadata.gz: 71a1906f8a7abe5f8d84144ff4bbc37d635224448563aac8b619383751f2a9fed4bdb71fedcc909e63fe4f590b2cdc6d42c12b641f6d33492b3885485f5ce286
7
- data.tar.gz: 0f451566f5ca7174c72f3434f15b541b8ae44136202d7e211f684a869aa6e2d979a55dbb26339adfd2ba05e019c00ecb1565feaba4c4962a3dfa6d10c1d523be
6
+ metadata.gz: 23b20f5aef8feede77ccbc519a6abfb70932055b0a761b4ea2d29418d54dd8820d0b34b6e1292ac5c4f72f6c29de92cc7dd5f6bc1672970692f7bdcb7bf5a10a
7
+ data.tar.gz: e17f9cba6b6053229f6fff77678e20fbe132dfe837b684b732656a4701db98a9a5381bf44733b8f096ce72b9af587f35b6b4a8139bb16d2f635937445bb57e05
@@ -6,3 +6,6 @@ Metrics/LineLength:
6
6
 
7
7
  Style/Documentation:
8
8
  Enabled: false
9
+
10
+ Layout/IndentationConsistency:
11
+ EnforcedStyle: indented_internal_methods
data/Gemfile CHANGED
@@ -3,4 +3,5 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in fcmpush.gemspec
4
4
  gemspec
5
5
 
6
+ gem 'activesupport', '< 6.0'
6
7
  gem 'pry-byebug'
@@ -1,17 +1,23 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fcmpush (0.1.2)
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)**, because both authentication method is different.
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
@@ -3,14 +3,11 @@ require 'googleauth'
3
3
 
4
4
  require 'fcmpush/configuration'
5
5
  require 'fcmpush/version'
6
- require 'fcmpush/exceptions'
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
@@ -1,3 +1,3 @@
1
1
  module Fcmpush
2
- VERSION = '0.1.2'.freeze
2
+ VERSION = '0.9.0'.freeze
3
3
  end
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.1.2
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-15 00:00:00.000000000 Z
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