cent 4.0.0 → 5.0.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.
data/lib/cent/wire.rb ADDED
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cent
4
+ # Encoding requests and decoding replies, for the generated code: each field
5
+ # is put or got by its kind - :str, :bool, :int, :float, :json (a payload),
6
+ # :bool_value and :int32_value (protobuf wrappers), or a message's class - and
7
+ # its shape - :one, :list, :map, or :presence for a message which is a
8
+ # presence wrapper, sent whenever it is set, even empty.
9
+ #
10
+ # @api private
11
+ module Wire
12
+ ZERO = { str: "", bool: false, int: 0, float: 0.0 }.freeze
13
+
14
+ module_function
15
+
16
+ # Sets +w[key]+ to the encoded field +v+, unless it is unset or at its zero
17
+ # value: nil, "", false, 0, an empty list or map, a message with nothing set
18
+ # - but a presence wrapper, sent as {} when set empty. +where+ prefixes the
19
+ # field's name in error messages.
20
+ def put(w, key, v, where, kind, shape = :one)
21
+ return if v.nil?
22
+
23
+ out =
24
+ case shape
25
+ when :list
26
+ raise InvalidRequestError, "#{where}#{key} must be an Array, not #{kind_of(v)}" unless v.is_a?(Array)
27
+ return if v.empty?
28
+
29
+ v.each_with_index.map { |item, i| value(kind, item, "#{where}#{key}[#{i}]", true) }
30
+ when :map
31
+ raise InvalidRequestError, "#{where}#{key} must be a Hash, not #{kind_of(v)}" unless v.is_a?(Hash)
32
+ return if v.empty?
33
+
34
+ v.each_with_object({}) do |(k, item), h|
35
+ name = map_key(k, "#{where}#{key}")
36
+ h[name] = value(kind, item, "#{where}#{key}[#{name.inspect}]", true)
37
+ end
38
+ else value(kind, v, "#{where}#{key}", shape == :presence)
39
+ end
40
+ w[key] = out unless out.nil?
41
+ end
42
+
43
+ # One value of a field, encoded; nil for a zero value, unless +keep+ (an
44
+ # item of a list or map, a presence wrapper).
45
+ def value(kind, v, path, keep)
46
+ case kind
47
+ when :str, :bool, :int, :float then scalar(kind, v, path, keep)
48
+ when :json then Payload.check!(v, path)
49
+ when :bool_value
50
+ raise InvalidRequestError, "#{path} must be true or false, not #{kind_of(v)}" unless [true, false].include?(v)
51
+
52
+ { "value" => v }
53
+ when :int32_value
54
+ raise InvalidRequestError, "#{path} must be an Integer, not #{kind_of(v)}" unless v.is_a?(Integer)
55
+
56
+ { "value" => v }
57
+ else
58
+ w = message(kind, v, path).to_wire("#{path}.")
59
+ w.empty? && !keep ? nil : w
60
+ end
61
+ end
62
+
63
+ def scalar(kind, v, path, keep)
64
+ case kind
65
+ when :str
66
+ s = case v
67
+ when String then v
68
+ when Symbol then v.name
69
+ else raise InvalidRequestError, "#{path} must be a String, not #{kind_of(v)}"
70
+ end
71
+ Payload.string!(s, path)
72
+ s.empty? && !keep ? nil : s
73
+ when :bool
74
+ raise InvalidRequestError, "#{path} must be true or false, not #{kind_of(v)}" unless [true, false].include?(v)
75
+
76
+ v || keep ? v : nil
77
+ when :int
78
+ raise InvalidRequestError, "#{path} must be an Integer, not #{kind_of(v)}" unless v.is_a?(Integer)
79
+
80
+ v.zero? && !keep ? nil : v
81
+ when :float
82
+ unless (v.is_a?(Float) && v.finite?) || v.is_a?(Integer)
83
+ raise InvalidRequestError, "#{path} must be a finite number, not #{kind_of(v)}"
84
+ end
85
+
86
+ v.zero? && !keep ? nil : v
87
+ end
88
+ end
89
+
90
+ # A message given as its class's instance, or as a Hash of its fields.
91
+ def message(klass, v, path)
92
+ return v if v.is_a?(klass)
93
+ unless v.is_a?(Hash)
94
+ raise InvalidRequestError, "#{path} must be a #{short(klass)} or a Hash of its fields, not #{kind_of(v)}"
95
+ end
96
+
97
+ v.each_key do |k|
98
+ next if k.is_a?(Symbol) && klass.members.include?(k)
99
+
100
+ name = k.to_s
101
+ if k.is_a?(String) && klass.members.include?(name.to_sym)
102
+ raise InvalidRequestError, "#{path}: field names are Symbols - #{name}:, not #{name.inspect}"
103
+ end
104
+
105
+ like = DidYouMean::SpellChecker.new(dictionary: klass.members.map(&:name)).correct(name).first
106
+ raise InvalidRequestError,
107
+ "#{path}.#{name} is not a field of #{short(klass)}#{": did you mean #{like}?" if like}"
108
+ end
109
+ missing = required_members(klass).find { !v.key?(it) }
110
+ raise InvalidRequestError, "#{path}.#{missing} is required" if missing
111
+
112
+ klass.new(**v)
113
+ end
114
+
115
+ # The fields a message type's initialize requires: every one when it is
116
+ # Data's own, which the generator leaves when no field has a default.
117
+ def required_members(klass)
118
+ params = klass.instance_method(:initialize).parameters
119
+ return klass.members if params == [[:rest]]
120
+
121
+ params.filter_map { |t, name| name if t == :keyreq }
122
+ end
123
+
124
+ # Raises InvalidRequestError naming the first field the proto marks
125
+ # required which the JSON +w+ of the request or message +key+ - its method
126
+ # or name in REQUIRED - leaves out: unset, or at its zero value. A nested
127
+ # message is checked when it is set, a list's each.
128
+ def check_required(w, key, path = "")
129
+ REQUIRED.fetch(key).each do |name, required, nested, list|
130
+ v = w[name]
131
+ if v.nil?
132
+ raise InvalidRequestError, "#{path}#{name} is required" if required
133
+
134
+ next
135
+ end
136
+ next unless nested
137
+
138
+ if list
139
+ v.each_with_index { |item, i| check_required(item, nested, "#{path}#{name}[#{i}].") }
140
+ else
141
+ check_required(v, nested, "#{path}#{name}.")
142
+ end
143
+ end
144
+ end
145
+
146
+ def map_key(k, path)
147
+ case k
148
+ when String
149
+ Payload.string!(k, "#{path} key #{k.inspect}")
150
+ k
151
+ when Symbol then k.name
152
+ else raise InvalidRequestError, "#{path} has a #{k.class} key, #{k.inspect}: keys are strings"
153
+ end
154
+ end
155
+
156
+ # The field +key+ of the decoded object +d+, at its zero value when the reply
157
+ # left it out; fields the client does not know are ignored.
158
+ def get(d, key, where, kind, shape = :one)
159
+ v = d[key]
160
+ case shape
161
+ when :list
162
+ return [] if v.nil?
163
+ raise DecodeError, "#{where}.#{key}: expected an array, got #{kind_of(v)}" unless v.is_a?(Array)
164
+
165
+ v.each_with_index.map { |item, i| decode(kind, item, "#{where}.#{key}[#{i}]") }
166
+ when :map
167
+ return {} if v.nil?
168
+
169
+ obj(v, "#{where}.#{key}").to_h { |k, item| [k, decode(kind, item, "#{where}.#{key}[#{k.inspect}]")] }
170
+ else
171
+ return ZERO.fetch(kind, nil) if v.nil? && kind != :json
172
+
173
+ decode(kind, v, "#{where}.#{key}")
174
+ end
175
+ end
176
+
177
+ def decode(kind, v, path)
178
+ case kind
179
+ when :str
180
+ raise DecodeError, "#{path}: expected a string, got #{kind_of(v)}" unless v.is_a?(String)
181
+ when :bool
182
+ raise DecodeError, "#{path}: expected a boolean, got #{kind_of(v)}" unless [true, false].include?(v)
183
+ when :int
184
+ raise DecodeError, "#{path}: expected an integer, got #{kind_of(v)}" unless v.is_a?(Integer)
185
+ when :float
186
+ raise DecodeError, "#{path}: expected a number, got #{kind_of(v)}" unless v.is_a?(Numeric)
187
+
188
+ return v.to_f
189
+ when :json then nil
190
+ when :bool_value, :int32_value
191
+ inner = obj(v, path)["value"]
192
+ # The value's zero, which a protobuf encoder leaves out.
193
+ return kind == :bool_value ? false : 0 if inner.nil?
194
+
195
+ return decode(kind == :bool_value ? :bool : :int, inner, "#{path}.value")
196
+ else return kind.from_wire(v, path)
197
+ end
198
+ v
199
+ end
200
+
201
+ def obj(v, where)
202
+ raise DecodeError, "#{where}: expected an object, got #{kind_of(v)}" unless v.is_a?(Hash)
203
+
204
+ v
205
+ end
206
+
207
+ def kind_of(v)
208
+ case v
209
+ when nil then "nil"
210
+ when true, false then v.to_s
211
+ when String then "a String"
212
+ when Integer then "an Integer, #{v}"
213
+ when Float then "a Float, #{v}"
214
+ when Array then "an Array"
215
+ when Hash then "a Hash"
216
+ else "a #{v.class}"
217
+ end
218
+ end
219
+
220
+ def short(klass) = klass.name.delete_prefix("Cent::")
221
+ end
222
+ end
data/lib/cent.rb CHANGED
@@ -1,10 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cent/version'
4
- require 'cent/error'
5
- require 'cent/notary'
6
- require 'cent/client'
3
+ require "json"
4
+ require "net/http"
5
+ require "openssl"
6
+ require "zlib"
7
7
 
8
- # Centrifugo Ruby Client
8
+ # Ruby SDK for Centrifugo's HTTP server API:
9
+ #
10
+ # client = Cent::Client.new(api_endpoint: "http://localhost:8000/api", api_key: ENV.fetch("CENTRIFUGO_API_KEY"))
11
+ # client.publish(channel: "news", data: { text: "hello" })
12
+ #
13
+ # See https://github.com/centrifugal/rubycent for the documentation.
9
14
  module Cent
10
15
  end
16
+
17
+ require_relative "cent/version"
18
+ require_relative "cent/message"
19
+ require_relative "cent/errors"
20
+ require_relative "cent/payload"
21
+ require_relative "cent/wire"
22
+ require_relative "cent/http"
23
+ require_relative "cent/generated/types"
24
+ require_relative "cent/generated/methods"
25
+ require_relative "cent/reply"
26
+ require_relative "cent/pages"
27
+ require_relative "cent/broadcast"
28
+ require_relative "cent/retry"
29
+ require_relative "cent/batch"
30
+ require_relative "cent/command"
31
+ require_relative "cent/auto_batch"
32
+ require_relative "cent/client"
33
+ require_relative "cent/handler"
34
+ require_relative "cent/token"
35
+ require_relative "cent/shared_poll"
36
+ require_relative "cent/endpoints"
37
+ require_relative "cent/generated/proxy_types"
38
+ require_relative "cent/proxy"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cent
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Prikhodko
@@ -11,49 +11,24 @@ cert_chain: []
11
11
  date: 1980-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: faraday
14
+ name: json
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: '4'
19
+ version: '2.10'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
- version: '2.0'
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: '4'
33
- - !ruby/object:Gem::Dependency
34
- name: jwt
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '2.2'
40
- - - "<"
41
- - !ruby/object:Gem::Version
42
- version: '4'
43
- type: :runtime
44
- prerelease: false
45
- version_requirements: !ruby/object:Gem::Requirement
46
- requirements:
47
- - - ">="
48
- - !ruby/object:Gem::Version
49
- version: '2.2'
50
- - - "<"
51
- - !ruby/object:Gem::Version
52
- version: '4'
26
+ version: '2.10'
53
27
  description: |
54
- Ruby client for Centrifugo server HTTP API. Provides Cent::Client to call
55
- Centrifugo server methods (publish, broadcast, subscribe, presence, history, ...)
56
- and Cent::Notary to issue JWT connection and subscription tokens.
28
+ Every method of Centrifugo's HTTP server API, with typed results, batches,
29
+ automatic batching and retries; connection, subscription and API tokens,
30
+ JWKS; endpoints for client SDKs and Rack handlers for Centrifugo's proxy.
31
+ No dependencies but json.
57
32
  email:
58
33
  - prikha@gmail.com
59
34
  executables: []
@@ -61,25 +36,40 @@ extensions: []
61
36
  extra_rdoc_files: []
62
37
  files:
63
38
  - CHANGELOG.md
64
- - Gemfile
65
39
  - LICENSE.txt
40
+ - MIGRATING.md
66
41
  - README.md
67
- - Rakefile
68
- - bin/console
69
- - bin/setup
70
- - cent.gemspec
71
42
  - lib/cent.rb
43
+ - lib/cent/auto_batch.rb
44
+ - lib/cent/batch.rb
45
+ - lib/cent/broadcast.rb
72
46
  - lib/cent/client.rb
73
- - lib/cent/error.rb
74
- - lib/cent/notary.rb
47
+ - lib/cent/command.rb
48
+ - lib/cent/endpoints.rb
49
+ - lib/cent/errors.rb
50
+ - lib/cent/generated/methods.rb
51
+ - lib/cent/generated/proxy_types.rb
52
+ - lib/cent/generated/types.rb
53
+ - lib/cent/handler.rb
54
+ - lib/cent/http.rb
55
+ - lib/cent/message.rb
56
+ - lib/cent/pages.rb
57
+ - lib/cent/payload.rb
58
+ - lib/cent/proxy.rb
59
+ - lib/cent/reply.rb
60
+ - lib/cent/retry.rb
61
+ - lib/cent/shared_poll.rb
62
+ - lib/cent/token.rb
75
63
  - lib/cent/version.rb
64
+ - lib/cent/wire.rb
76
65
  homepage: https://github.com/centrifugal/rubycent
77
66
  licenses:
78
67
  - MIT
79
68
  metadata:
80
69
  source_code_uri: https://github.com/centrifugal/rubycent
81
- changelog_uri: https://github.com/centrifugal/rubycent/releases
70
+ changelog_uri: https://github.com/centrifugal/rubycent/blob/master/CHANGELOG.md
82
71
  bug_tracker_uri: https://github.com/centrifugal/rubycent/issues
72
+ documentation_uri: https://rubydoc.info/gems/cent
83
73
  rdoc_options: []
84
74
  require_paths:
85
75
  - lib
@@ -87,14 +77,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
87
77
  requirements:
88
78
  - - ">="
89
79
  - !ruby/object:Gem::Version
90
- version: '3.0'
80
+ version: '3.4'
91
81
  required_rubygems_version: !ruby/object:Gem::Requirement
92
82
  requirements:
93
83
  - - ">="
94
84
  - !ruby/object:Gem::Version
95
85
  version: '0'
96
86
  requirements: []
97
- rubygems_version: 3.6.9
87
+ rubygems_version: 4.0.20
98
88
  specification_version: 4
99
- summary: Centrifugo server API client for Ruby
89
+ summary: Ruby SDK for Centrifugo's HTTP server API
100
90
  test_files: []
data/Gemfile DELETED
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- # Specify your gem's dependencies in cent.gemspec
6
- gemspec
7
-
8
- gem 'appraisal', '~> 2.5'
9
- gem 'pry'
10
- gem 'rake', '~> 13.0'
11
- gem 'rspec', '~> 3.12'
12
- gem 'rubocop', '~> 1.60'
13
- gem 'rubocop-rake', '~> 0.6'
14
- gem 'rubocop-rspec', '~> 3.0'
15
- gem 'webmock', '~> 3.19'
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- require 'rubocop/rake_task'
9
-
10
- RuboCop::RakeTask.new
11
-
12
- task default: %i[spec rubocop]
data/bin/console DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'bundler/setup'
5
- require 'cent'
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require 'pry'
15
- Pry.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/cent.gemspec DELETED
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'lib/cent/version'
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = 'cent'
7
- spec.version = Cent::VERSION
8
- spec.authors = ['Sergey Prikhodko', 'Centrifugal Labs']
9
- spec.email = ['prikha@gmail.com']
10
-
11
- spec.summary = 'Centrifugo server API client for Ruby'
12
- spec.description = <<~DESC
13
- Ruby client for Centrifugo server HTTP API. Provides Cent::Client to call
14
- Centrifugo server methods (publish, broadcast, subscribe, presence, history, ...)
15
- and Cent::Notary to issue JWT connection and subscription tokens.
16
- DESC
17
- spec.homepage = 'https://github.com/centrifugal/rubycent'
18
- spec.license = 'MIT'
19
- spec.required_ruby_version = Gem::Requirement.new('>= 3.0')
20
-
21
- spec.metadata['source_code_uri'] = 'https://github.com/centrifugal/rubycent'
22
- spec.metadata['changelog_uri'] = 'https://github.com/centrifugal/rubycent/releases'
23
- spec.metadata['bug_tracker_uri'] = 'https://github.com/centrifugal/rubycent/issues'
24
-
25
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
- `git ls-files -z`.split("\x0").reject do |f|
27
- f.match(%r{\A(?:spec|benchmarks|gemfiles|\.github)/}) ||
28
- f.match(/\A(?:docker-compose\.yml|Appraisals|\.rubocop\.yml|\.rspec|\.gitignore)\z/)
29
- end
30
- end
31
-
32
- spec.require_paths = ['lib']
33
-
34
- spec.add_dependency 'faraday', '>= 2.0', '< 4'
35
- spec.add_dependency 'jwt', '>= 2.2', '< 4'
36
- end
data/lib/cent/error.rb DELETED
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Cent
4
- # Base class for all errors raised by this library.
5
- class Error < StandardError; end
6
-
7
- # Raised when Centrifugo is unreachable (DNS failure, connection refused, ...).
8
- class NetworkError < Error; end
9
-
10
- # Raised when the HTTP request times out.
11
- class TimeoutError < Error; end
12
-
13
- # Raised when Centrifugo returns a non-2xx HTTP status.
14
- class TransportError < Error
15
- attr_reader :status
16
-
17
- def initialize(status:, message: nil)
18
- @status = status
19
- super(message || "HTTP #{status}")
20
- end
21
- end
22
-
23
- # Raised when Centrifugo returns HTTP 401 (invalid API key).
24
- class UnauthorizedError < TransportError; end
25
-
26
- # Raised when response from Centrifugo cannot be decoded (not valid JSON).
27
- class DecodeError < Error; end
28
-
29
- # Raised when Centrifugo returns a top-level `error` in the response body
30
- # (API-level failure, e.g., unknown channel, namespace not found). Exposes
31
- # Centrifugo's numeric `code` and human-readable `message`. See
32
- # https://centrifugal.dev/docs/server/server_api#error for the full list
33
- # of codes.
34
- #
35
- # Note: for `batch` and `broadcast`, individual sub-reply errors are NOT
36
- # raised — those responses contain an array of independent replies, and
37
- # each entry should be inspected by the caller for its own `error` key.
38
- class ResponseError < Error
39
- attr_reader :code
40
-
41
- def initialize(code:, message:)
42
- @code = code
43
- super(message)
44
- end
45
- end
46
- end
data/lib/cent/notary.rb DELETED
@@ -1,105 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'jwt'
4
- require 'cent/error'
5
-
6
- module Cent
7
- # Cent::Notary
8
- #
9
- # Issues JWT tokens for Centrifugo client connections and channel subscriptions.
10
- # Supports HMAC, RSA and ECDSA families of algorithms (HS256/384/512,
11
- # RS256/384/512, ES256/384/512).
12
- #
13
- # @see https://centrifugal.dev/docs/server/authentication
14
- # @see https://centrifugal.dev/docs/server/channel_token_auth
15
- class Notary
16
- # @param secret [String, OpenSSL::PKey::RSA, OpenSSL::PKey::EC] Secret key
17
- # for the chosen algorithm. For HMAC pass the raw secret as a String. For
18
- # RSA/ECDSA pass a PEM-loaded {OpenSSL::PKey::RSA} / {OpenSSL::PKey::EC}.
19
- # @param algorithm [String] JWT algorithm, defaults to `HS256`.
20
- def initialize(secret:, algorithm: 'HS256')
21
- raise Error, 'Secret can not be nil' if secret.nil?
22
-
23
- @secret = secret
24
- @algorithm = algorithm
25
- end
26
-
27
- # Issue a connection JWT used by clients when establishing a real-time
28
- # connection to Centrifugo.
29
- #
30
- # @param sub [String] Standard JWT claim with the application user ID.
31
- # Use an empty string for anonymous connections.
32
- # @param exp [Integer] UNIX timestamp (seconds) when the token expires.
33
- # @param iat [Integer] UNIX timestamp (seconds) when the token was issued.
34
- # @param jti [String] Unique token identifier.
35
- # @param aud [String] Token audience (matches `client.token.audience`).
36
- # @param iss [String] Token issuer (matches `client.token.issuer`).
37
- # @param info [Hash] Arbitrary public info attached to the connection.
38
- # @param b64info [String] Base64-encoded `info` (for binary payloads).
39
- # @param channels [Array<String>] Server-side subscription channel list.
40
- # @param subs [Hash] Server-side subscriptions with per-channel options.
41
- # @param meta [Hash] Server-only metadata attached to the connection.
42
- # @param expire_at [Integer] Override connection expiration timestamp.
43
- #
44
- # @return [String] Encoded JWT.
45
- def issue_connection_token(sub:, exp: nil, iat: nil, jti: nil, aud: nil, iss: nil,
46
- info: nil, b64info: nil, channels: nil, subs: nil,
47
- meta: nil, expire_at: nil)
48
- payload = {
49
- 'sub' => sub,
50
- 'exp' => exp,
51
- 'iat' => iat,
52
- 'jti' => jti,
53
- 'aud' => aud,
54
- 'iss' => iss,
55
- 'info' => info,
56
- 'b64info' => b64info,
57
- 'channels' => channels,
58
- 'subs' => subs,
59
- 'meta' => meta,
60
- 'expire_at' => expire_at
61
- }.compact
62
-
63
- JWT.encode(payload, secret, algorithm)
64
- end
65
-
66
- # Issue a subscription JWT used by clients to authorize subscription to a
67
- # channel that requires token authorization.
68
- #
69
- # @param sub [String] Application user ID (same meaning as in connection token).
70
- # @param channel [String] Channel this subscription token is valid for.
71
- # @param exp [Integer] UNIX timestamp (seconds) when the token expires.
72
- # @param iat [Integer] UNIX timestamp (seconds) when the token was issued.
73
- # @param jti [String] Unique token identifier.
74
- # @param aud [String] Token audience.
75
- # @param iss [String] Token issuer.
76
- # @param info [Hash] Arbitrary channel info.
77
- # @param b64info [String] Base64-encoded `info`.
78
- # @param override [Hash] Per-subscription channel option overrides.
79
- # @param expire_at [Integer] Override subscription expiration timestamp.
80
- #
81
- # @return [String] Encoded JWT.
82
- def issue_channel_token(sub:, channel:, exp: nil, iat: nil, jti: nil, aud: nil, iss: nil,
83
- info: nil, b64info: nil, override: nil, expire_at: nil)
84
- payload = {
85
- 'sub' => sub,
86
- 'channel' => channel,
87
- 'exp' => exp,
88
- 'iat' => iat,
89
- 'jti' => jti,
90
- 'aud' => aud,
91
- 'iss' => iss,
92
- 'info' => info,
93
- 'b64info' => b64info,
94
- 'override' => override,
95
- 'expire_at' => expire_at
96
- }.compact
97
-
98
- JWT.encode(payload, secret, algorithm)
99
- end
100
-
101
- private
102
-
103
- attr_reader :secret, :algorithm
104
- end
105
- end