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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +93 -0
- data/MIGRATING.md +305 -0
- data/README.md +702 -155
- data/lib/cent/auto_batch.rb +305 -0
- data/lib/cent/batch.rb +209 -0
- data/lib/cent/broadcast.rb +117 -0
- data/lib/cent/client.rb +602 -216
- data/lib/cent/command.rb +64 -0
- data/lib/cent/endpoints.rb +191 -0
- data/lib/cent/errors.rb +392 -0
- data/lib/cent/generated/methods.rb +4226 -0
- data/lib/cent/generated/proxy_types.rb +1834 -0
- data/lib/cent/generated/types.rb +2844 -0
- data/lib/cent/handler.rb +179 -0
- data/lib/cent/http.rb +251 -0
- data/lib/cent/message.rb +52 -0
- data/lib/cent/pages.rb +165 -0
- data/lib/cent/payload.rb +93 -0
- data/lib/cent/proxy.rb +254 -0
- data/lib/cent/reply.rb +109 -0
- data/lib/cent/retry.rb +53 -0
- data/lib/cent/shared_poll.rb +61 -0
- data/lib/cent/token.rb +560 -0
- data/lib/cent/version.rb +2 -1
- data/lib/cent/wire.rb +222 -0
- data/lib/cent.rb +33 -5
- metadata +34 -44
- data/Gemfile +0 -15
- data/Rakefile +0 -12
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/cent.gemspec +0 -36
- data/lib/cent/error.rb +0 -46
- data/lib/cent/notary.rb +0 -105
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
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "openssl"
|
|
6
|
+
require "zlib"
|
|
7
7
|
|
|
8
|
-
# Centrifugo
|
|
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
|
+
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:
|
|
14
|
+
name: json
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '2.
|
|
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.
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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/
|
|
74
|
-
- lib/cent/
|
|
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/
|
|
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.
|
|
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:
|
|
87
|
+
rubygems_version: 4.0.20
|
|
98
88
|
specification_version: 4
|
|
99
|
-
summary: Centrifugo server API
|
|
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
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
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
|