matrix_sdk 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24a67aeb6af66f75480104c5c0dc44987e2fe54163ed96047da58bf091476752
4
- data.tar.gz: e5935a94b430a1b195b55d748415cc0ad95ea649e32f5e97045cc82726dd2bd3
3
+ metadata.gz: 50821866efab2cbd39daf23bc9ff3bd7864bca5638f76c766a8ba26ab91c7423
4
+ data.tar.gz: 2601cf5141e824b322f5310e09b72d42059162e1ac87da11243167d2604f1801
5
5
  SHA512:
6
- metadata.gz: 1ea88ea4c76b573dad7b03aeba49e91b8f7c01040118c38032b49ca00cfbe687bd931c4e17387545e27df529f6ae06fc835ee964b5e79489fb0c5cf84186b35b
7
- data.tar.gz: e2a6a5cd78e7629ffb2cc4af5ec9c9154f2f63db570479ebb2c6fa9aff13b7b231bea877d56694ae799019698af8d4a87394f88fa162dcb172dbbd1ede09fc8f
6
+ metadata.gz: 7bb45dfcb6696b75ed0be95b92fbb3f569b258d4d0456a3cd8ea16fd22d5e3d5050607e6229bfa0e32b84fc0248e554db8f93d48cd970b2ff95475cf0bcfba85
7
+ data.tar.gz: 58d8524e9d83ef570ca19a41f28758fc129c1a3988ea3522a994e58eaa93c4807850b63a2171423dd69a948738e69468542d56d882ecf98c60e6f8607928b6d1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 2.6.0 - 2022-07-15
2
+
3
+ - Adds some multi-thread usage support to the API (create your API/client with `threadsafe: :multithread/true/false`)
4
+ The API will currently default to running with multi-threaded requests. In case your application is single-threaded - or never performs requests from multiple threads - then you can set `threadsafe: true/false` to support connection reuse.
5
+ - Changes room finding to ignore non-canonical aliases by default
6
+ - Improves room alias handling
7
+ - Improves Ruby 3.0+ support
8
+ - Improves debug output by supporting PP (Ruby pretty print) on all MatrixSdk objects
9
+
1
10
  ## 2.5.0 - 2022-01-14
2
11
 
3
12
  - Adds preliminary support for the Matrix v1.1 `client/v3` API
data/README.md CHANGED
@@ -8,6 +8,8 @@ Live YARD documentation can be found at; https://ruby-sdk.ananace.dev
8
8
 
9
9
  ## Example usage
10
10
 
11
+ For more fully-featured examples, check the [examples](examples/) folder.
12
+
11
13
  ```ruby
12
14
  # Raw API usage
13
15
  require 'matrix_sdk'
@@ -19,7 +19,7 @@ module MatrixSdk
19
19
  }.freeze
20
20
 
21
21
  attr_accessor :access_token, :connection_address, :connection_port, :device_id, :autoretry, :global_headers
22
- attr_reader :homeserver, :validate_certificate, :open_timeout, :read_timeout, :well_known, :proxy_uri
22
+ attr_reader :homeserver, :validate_certificate, :open_timeout, :read_timeout, :well_known, :proxy_uri, :threadsafe
23
23
 
24
24
  ignore_inspect :access_token, :logger
25
25
 
@@ -39,7 +39,10 @@ module MatrixSdk
39
39
  # @option params [Hash] :global_headers Additional headers to set for all requests
40
40
  # @option params [Boolean] :skip_login Should the API skip logging in if the HS URL contains user information
41
41
  # @option params [Boolean] :synapse (true) Is the API connecting to a Synapse instance
42
- # @option params [Hash] :well_known The .well-known object that the server was discovered through, should not be set manually
42
+ # @option params [Boolean,:multithread] :threadsafe (:multithread) Should the connection be threadsafe/mutexed - or
43
+ # safe for simultaneous multi-thread usage. Will default to +:multithread+ - a.k.a. per-thread HTTP connections
44
+ # and requests
45
+ # @note Using threadsafe +:multithread+ currently doesn't support connection re-use
43
46
  def initialize(homeserver, **params)
44
47
  @homeserver = homeserver
45
48
  raise ArgumentError, 'Homeserver URL must be String or URI' unless @homeserver.is_a?(String) || @homeserver.is_a?(URI)
@@ -64,7 +67,8 @@ module MatrixSdk
64
67
  @global_headers.merge!(params.fetch(:global_headers)) if params.key? :global_headers
65
68
  @synapse = params.fetch(:synapse, true)
66
69
  @http = nil
67
- @http_lock = Mutex.new
70
+
71
+ self.threadsafe = params.fetch(:threadsafe, :multithread)
68
72
 
69
73
  ([params.fetch(:protocols, [:CS])].flatten - protocols).each do |proto|
70
74
  self.class.include MatrixSdk::Protocols.const_get(proto)
@@ -244,6 +248,17 @@ module MatrixSdk
244
248
  @proxy_uri = proxy_uri
245
249
  end
246
250
 
251
+ # @param [Boolean,:multithread] threadsafe What level of thread-safety the API should use
252
+ # @return [Boolean,:multithread]
253
+ def threadsafe=(threadsafe)
254
+ raise ArgumentError, 'Threadsafe must be either a boolean or :multithread' unless [true, false, :multithread].include? threadsafe
255
+ raise ArugmentError, 'JRuby only support :multithread/false for threadsafe' if RUBY_ENGINE == 'jruby' && threadsafe == true
256
+
257
+ @threadsafe = threadsafe
258
+ @http_lock = nil unless threadsafe == true
259
+ @threadsafe
260
+ end
261
+
247
262
  # Perform a raw Matrix API request
248
263
  #
249
264
  # @example Simple API query
@@ -284,15 +299,23 @@ module MatrixSdk
284
299
  print_http(req_obj, id: req_id)
285
300
  response = duration = nil
286
301
 
287
- @http_lock.synchronize do
302
+ loc_http = http
303
+ perform_request = proc do
288
304
  dur_start = Time.now
289
- response = http.request req_obj
305
+ response = loc_http.request req_obj
290
306
  dur_end = Time.now
291
307
  duration = dur_end - dur_start
292
308
  rescue EOFError
293
309
  logger.error 'Socket closed unexpectedly'
294
310
  raise
295
311
  end
312
+
313
+ if @threadsafe == true
314
+ http_lock.synchronize { perform_request.call }
315
+ else
316
+ perform_request.call
317
+ loc_http.finish if @threadsafe == :multithread
318
+ end
296
319
  print_http(response, duration: duration, id: req_id)
297
320
 
298
321
  begin
@@ -395,18 +418,26 @@ module MatrixSdk
395
418
 
396
419
  host = (@connection_address || homeserver.host)
397
420
  port = (@connection_port || homeserver.port)
398
- @http ||= if proxy_uri
399
- Net::HTTP.new(host, port, proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
400
- else
401
- Net::HTTP.new(host, port)
402
- end
403
-
404
- @http.open_timeout = open_timeout if open_timeout
405
- @http.read_timeout = read_timeout if read_timeout
406
- @http.use_ssl = homeserver.scheme == 'https'
407
- @http.verify_mode = validate_certificate ? ::OpenSSL::SSL::VERIFY_PEER : ::OpenSSL::SSL::VERIFY_NONE
408
- @http.start
409
- @http
421
+
422
+ connection = @http unless @threadsafe == :multithread
423
+ connection ||= if proxy_uri
424
+ Net::HTTP.new(host, port, proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
425
+ else
426
+ Net::HTTP.new(host, port)
427
+ end
428
+
429
+ connection.open_timeout = open_timeout if open_timeout
430
+ connection.read_timeout = read_timeout if read_timeout
431
+ connection.use_ssl = homeserver.scheme == 'https'
432
+ connection.verify_mode = validate_certificate ? ::OpenSSL::SSL::VERIFY_PEER : ::OpenSSL::SSL::VERIFY_NONE
433
+ connection.start
434
+ @http = connection unless @threadsafe == :multithread
435
+
436
+ connection
437
+ end
438
+
439
+ def http_lock
440
+ @http_lock ||= Mutex.new if @threadsafe == true
410
441
  end
411
442
  end
412
443
  end
@@ -395,15 +395,16 @@ module MatrixSdk
395
395
  # @param only_canonical [Boolean] Only match alias against the canonical alias
396
396
  # @return [Room] The found room
397
397
  # @return [nil] If no room was found
398
- def find_room(room_id_or_alias, only_canonical: false)
398
+ def find_room(room_id_or_alias, only_canonical: true)
399
399
  room_id_or_alias = MXID.new(room_id_or_alias.to_s) unless room_id_or_alias.is_a? MXID
400
400
  raise ArgumentError, 'Must be a room id or alias' unless room_id_or_alias.room?
401
401
 
402
402
  return @rooms.fetch(room_id_or_alias.to_s, nil) if room_id_or_alias.room_id?
403
403
 
404
- return @rooms.values.find { |r| r.canonical_alias == room_id_or_alias.to_s } if only_canonical
404
+ room = @rooms.values.find { |r| r.aliases.include? room_id_or_alias.to_s }
405
+ return room if only_canonical
405
406
 
406
- @rooms.values.find { |r| r.aliases.include? room_id_or_alias.to_s }
407
+ room || @rooms.values.find { |r| r.aliases(canonical_only: false).include? room_id_or_alias.to_s }
407
408
  end
408
409
 
409
410
  # Get a User instance from a MXID
@@ -12,7 +12,7 @@ module MatrixSdk
12
12
 
13
13
  # TODO: Community-as-a-Room / Profile-as-a-Room, in case they're going for room aliases
14
14
  @sigil = identifier[0]
15
- @localpart, @domain, @port = identifier[1..].split(':')
15
+ @localpart, @domain, @port = identifier[1..-1].split(':') # rubocop:disable Style/SlicingWithRange # Keep support for slightly older Rubies
16
16
  @port = @port.to_i if @port
17
17
 
18
18
  raise ArgumentError, 'Identifier is not a valid MXID' unless valid?
@@ -203,7 +203,7 @@ module MatrixSdk::Protocols::CS
203
203
  data[:device_id] = device_id if device_id
204
204
 
205
205
  request(:post, client_api_latest, '/login', body: data, query: query).tap do |resp|
206
- @access_token = resp.token if resp.key?(:token) && options[:store_token]
206
+ @access_token = resp.access_token if resp.key?(:access_token) && options[:store_token]
207
207
  @device_id = resp.device_id if resp.key?(:device_id) && options[:store_device_id]
208
208
  end
209
209
  end
@@ -912,31 +912,23 @@ module MatrixSdk::Protocols::CS
912
912
  send_state_event(room_id, 'm.room.avatar', content, **params)
913
913
  end
914
914
 
915
- # Gets a list of current aliases of a room
915
+ # Gets a list of currently known aliases of a room
916
916
  #
917
917
  # @param [MXID,String] room_id The room ID to look up
918
- # @param [Hash] params Extra options to provide to the request, see #get_room_state
919
918
  # @return [Response] A response hash with the array :aliases
920
- # @raise [MatrixNotFoundError] Raised if no aliases has been set on the room by the specified HS
921
- # @see get_room_state
922
- # @see https://matrix.org/docs/spec/client_server/latest.html#m-room-avatar
919
+ # @raise [MatrixForbiddenError] Raised if the user doesn't have the right to read aliases
920
+ # @see https://spec.matrix.org/v1.1/client-server-api/#get_matrixclientv3roomsroomidaliases
923
921
  # The Matrix Spec, for more information about the event and data
924
922
  # @example Looking up aliases for a room
925
923
  # api.get_room_aliases('!QtykxKocfZaZOUrTwp:matrix.org')
926
- # # MatrixSdk::MatrixNotFoundError: HTTP 404 (M_NOT_FOUND): Event not found.
927
- # api.get_room_aliases('!QtykxKocfZaZOUrTwp:matrix.org', key: 'matrix.org')
928
924
  # # => {:aliases=>["#matrix:matrix.org"]}
929
- # api.get_room_aliases('!QtykxKocfZaZOUrTwp:matrix.org', key: 'kittenface.studio')
930
- # # => {:aliases=>["#worlddominationhq:kittenface.studio"]}
931
- # @example A way to find all aliases for a room
932
- # api.get_room_state('!mjbDjyNsRXndKLkHIe:matrix.org')
933
- # .select { |ch| ch[:type] == 'm.room.aliases' }
934
- # .map { |ch| ch[:content][:aliases] }
935
- # .flatten
936
- # .compact
937
- # # => ["#synapse:im.kabi.tk", "#synapse:matrix.org", "#synapse-community:matrix.org", "#synapse-ops:matrix.org", "#synops:matrix.org", ...
938
925
  def get_room_aliases(room_id, **params)
939
- get_room_state(room_id, 'm.room.aliases', **params)
926
+ query = {}
927
+ query[:user_id] = params.delete(:user_id) if protocol?(:AS) && params.key?(:user_id)
928
+
929
+ room_id = ERB::Util.url_encode room_id.to_s
930
+
931
+ request(:get, client_api_latest, "/rooms/#{room_id}/aliases", query: query)
940
932
  end
941
933
 
942
934
  # Gets a list of pinned events in a room
@@ -60,9 +60,9 @@ module MatrixSdk::Protocols::MSC
60
60
  # rubocop:disable Metrics/BlockLength
61
61
  thread = Thread.new(cancellation_token) do |ctx|
62
62
  print_http(req)
63
- @http_lock.lock
63
+ @http_lock&.lock
64
64
  http.request req do |response|
65
- @http_lock.unlock
65
+ @http_lock&.unlock
66
66
  break unless ctx[:run]
67
67
 
68
68
  print_http(response, body: false)
@@ -136,10 +136,11 @@ module MatrixSdk::Protocols::MSC
136
136
  break
137
137
  end
138
138
  end
139
+
139
140
  break unless ctx[:run]
140
141
  end
141
- rescue StandardError
142
- @http_lock.unlock if @http_lock.owned?
142
+ ensure
143
+ @http_lock.unlock if @http_lock&.owned?
143
144
  end
144
145
  # rubocop:enable Metrics/BlockLength
145
146
 
@@ -31,9 +31,11 @@ module MatrixSdk
31
31
  ignore_inspect :client, :events, :prev_batch, :logger, :tinycache_adapter
32
32
 
33
33
  # Requires heavy lookups, so they're cached for an hour
34
- cached :joined_members, :aliases, cache_level: :all, expires_in: 60 * 60
35
- # Only cache unfiltered requests for all members
36
- cached :all_members, unless: proc { |args| args.any? }, cache_level: :all, expires_in: 3600
34
+ cached :joined_members, cache_level: :all, expires_in: 60 * 60
35
+
36
+ # Only cache unfiltered requests for aliases and members
37
+ cached :aliases, unless: proc { |args| args.any? }, cache_level: :all, expires_in: 60 * 60
38
+ cached :all_members, unless: proc { |args| args.any? }, cache_level: :all, expires_in: 60 * 60
37
39
 
38
40
  # Much simpler to look up, and lighter data-wise, so the cache is wider
39
41
  cached :canonical_alias, :name, :avatar_url, :topic, :guest_access, :join_rule, :power_levels, cache_level: :some, expires_in: 15 * 60
@@ -272,15 +274,14 @@ module MatrixSdk
272
274
 
273
275
  # Gets the room aliases
274
276
  #
277
+ # @param canonical_only [Boolean] Should the list of aliases only contain the canonical ones
275
278
  # @return [Array[String]] The assigned room aliases
276
- def aliases
277
- client.api.get_room_aliases(id).aliases
278
- rescue MatrixNotFoundError
279
- data = client.api.get_room_state_all(id)
280
- data.select { |chunk| chunk[:type] == 'm.room.aliases' && !chunk.dig(*%i[content aliases]).nil? }
281
- .map { |chunk| chunk.dig(*%i[content aliases]) }
282
- .flatten
283
- .compact
279
+ def aliases(canonical_only: true)
280
+ canonical = client.api.get_room_state(id, 'm.room.canonical_alias') rescue {}
281
+ aliases = ([canonical[:alias]].compact + (canonical[:alt_aliases] || [])).uniq.sort
282
+ return aliases if canonical_only
283
+
284
+ (aliases + client.api.get_room_aliases(id).aliases).uniq.sort
284
285
  end
285
286
 
286
287
  #
@@ -904,16 +905,8 @@ module MatrixSdk
904
905
 
905
906
  data = tinycache_adapter.read(:aliases) || []
906
907
  data << canonical_alias
907
- tinycache_adapter.write(:aliases, data)
908
- end
909
-
910
- def handle_room_aliases(event)
911
- tinycache_adapter.write(:aliases, []) unless tinycache_adapter.exist? :aliases
912
-
913
- aliases = tinycache_adapter.read(:aliases) || []
914
- aliases.concat(event.dig(*%i[content aliases]))
915
-
916
- tinycache_adapter.write(:aliases, aliases)
908
+ data += event.dig(*%i[content alt_aliases]) || []
909
+ tinycache_adapter.write(:aliases, data.uniq.sort)
917
910
  end
918
911
 
919
912
  def room_handlers?
@@ -929,7 +922,6 @@ module MatrixSdk
929
922
  end
930
923
 
931
924
  INTERNAL_HANDLERS = {
932
- 'm.room.aliases' => :handle_room_aliases,
933
925
  'm.room.canonical_alias' => :handle_room_canonical_alias,
934
926
  'm.room.guest_access' => :handle_room_guest_access,
935
927
  'm.room.join_rules' => :handle_room_join_rules,
@@ -6,12 +6,12 @@ module MatrixSdk::Rooms
6
6
 
7
7
  def tree(suggested_only: nil, max_rooms: nil)
8
8
  begin
9
- data = client.api.request :get, :client_unstable, "/org.matrix.msc2946/rooms/#{id}/spaces", query: {
9
+ data = client.api.request :get, :client_r0, "/rooms/#{id}/spaces", query: {
10
10
  suggested_only: suggested_only,
11
11
  max_rooms_per_space: max_rooms
12
12
  }.compact
13
13
  rescue MatrixRequestError
14
- data = client.api.request :get, :client_r0, "/rooms/#{id}/spaces", query: {
14
+ data = client.api.request :get, :client_unstable, "/org.matrix.msc2946/rooms/#{id}/spaces", query: {
15
15
  suggested_only: suggested_only,
16
16
  max_rooms_per_space: max_rooms
17
17
  }.compact
@@ -132,7 +132,7 @@ module MatrixSdk
132
132
 
133
133
  # Returns all the current device keys for the user, retrieving them if necessary
134
134
  def device_keys
135
- @device_keys ||= client.api.keys_query(device_keys: { id => [] }).yield_self do |resp|
135
+ @device_keys ||= client.api.keys_query(device_keys: { id => [] }).yield_self do |resp| # rubocop:disable Style/ObjectThen # Keep Ruby 2.5 support a little longer
136
136
  resp.dig(:device_keys, id.to_sym)
137
137
  end
138
138
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pp'
4
+
3
5
  unless Object.respond_to? :yield_self
4
6
  class Object
5
7
  def yield_self
@@ -51,13 +53,20 @@ module MatrixSdk
51
53
 
52
54
  def ignore_inspect(*symbols)
53
55
  class_eval %*
54
- def inspect
55
- reentrant = caller_locations.any? { |l| l.absolute_path == __FILE__ && l.label == 'inspect' }
56
- "\\\#<\#{self.class} \#{instance_variables
56
+ include PP::ObjectMixin
57
+
58
+ def pretty_print_instance_variables
59
+ instance_variables
57
60
  .reject { |f| %i[#{symbols.map { |s| "@#{s}" }.join ' '}].include? f }
58
- .map { |f| "\#{f}=\#{reentrant ? instance_variable_get(f) : instance_variable_get(f).inspect}" }.join " " }}>"
61
+ .sort
59
62
  end
60
- *, __FILE__, __LINE__ - 7
63
+
64
+ def pretty_print(pp)
65
+ pp.pp_object(self)
66
+ end
67
+
68
+ alias inspect pretty_print_inspect
69
+ *, __FILE__, __LINE__ - 14
61
70
  end
62
71
  end
63
72
 
@@ -75,13 +75,25 @@ module MatrixSdk::Util
75
75
 
76
76
  define_method(method_names[:with_cache]) do |*args|
77
77
  tinycache_adapter.fetch(__send__(method_names[:cache_key], *args), expires_in: expires_in) do
78
- __send__(method_names[:without_cache], *args)
78
+ named = args.delete_at(-1) if args.last.is_a? Hash
79
+
80
+ if named
81
+ __send__(method_names[:without_cache], *args, **named)
82
+ else
83
+ __send__(method_names[:without_cache], *args)
84
+ end
79
85
  end
80
86
  end
81
87
 
82
88
  define_method(method_names[:without_cache]) do |*args|
83
89
  orig = method(method_name).super_method
84
- orig.call(*args)
90
+ named = args.delete_at(-1) if args.last.is_a? Hash
91
+
92
+ if named
93
+ orig.call(*args, **named)
94
+ else
95
+ orig.call(*args)
96
+ end
85
97
  end
86
98
 
87
99
  define_method(method_names[:clear_cache]) do |*args|
@@ -7,14 +7,20 @@ module URI
7
7
  class MXC < Generic
8
8
  def full_path
9
9
  select(:host, :port, :path, :query, :fragment)
10
- .reject(&:nil?)
10
+ .compact
11
11
  .join
12
12
  end
13
13
  end
14
14
 
15
- @@schemes['MXC'] = MXC
15
+ # TODO: Use +register_scheme+ on Ruby >=3.1 and fall back to old behavior
16
+ # for older Rubies. May be removed at EOL of Ruby 3.0.
17
+ if respond_to? :register_scheme
18
+ register_scheme 'MXC', MXC
19
+ else
20
+ @@schemes['MXC'] = MXC
21
+ end
16
22
 
17
- unless @@schemes.key? 'MATRIX'
23
+ unless scheme_list.key? 'MATRIX'
18
24
  # A matrix: URI according to MSC2312
19
25
  class MATRIX < Generic
20
26
  attr_reader :authority, :action, :mxid, :mxid2, :via
@@ -84,6 +90,12 @@ module URI
84
90
  end
85
91
  end
86
92
 
87
- @@schemes['MATRIX'] = MATRIX
93
+ # TODO: Use +register_scheme+ on Ruby >=3.1 and fall back to old behavior
94
+ # for older Rubies. May be removed at EOL of Ruby 3.0.
95
+ if respond_to? :register_scheme
96
+ register_scheme 'MATRIX', MATRIX
97
+ else
98
+ @@schemes['MATRIX'] = MATRIX
99
+ end
88
100
  end
89
101
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MatrixSdk
4
- VERSION = '2.5.0'
4
+ VERSION = '2.6.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: matrix_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Olofsson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-14 00:00:00.000000000 Z
11
+ date: 2022-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mocha
@@ -132,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
132
  - !ruby/object:Gem::Version
133
133
  version: '0'
134
134
  requirements: []
135
- rubygems_version: 3.2.22
135
+ rubygems_version: 3.3.8
136
136
  signing_key:
137
137
  specification_version: 4
138
138
  summary: SDK for applications using the Matrix protocol