matrix_sdk 2.1.2 → 2.4.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: 5af1e7af0f473a5c44df1ede83655cdbdb120e215e42f125b6af576740b88aa6
4
- data.tar.gz: cca80fe97ca22f0ddb0c5a2b39b24b70345baa25fb20bea305fa019f46ca7d3a
3
+ metadata.gz: b8fabc9c9f840753916f52925807e2b6282a9fd17fbeedd63f284bd60a1aab86
4
+ data.tar.gz: d0c2330e13f85036bbc0cecf1cde73fc2b960906c497b0cb7fdfe89b49499fd8
5
5
  SHA512:
6
- metadata.gz: 0e4c694d8489dae1b2602d01f4aab08b20119c6697e83b41a2a463adb7ebcaf7b1afd439db04a6e54a0979c5a93be865c8afa3d2d03b4ca34b98f82afae87a5f
7
- data.tar.gz: 8f52c12d1eb8af55ac5bc011852c9d5f648e9de1cc3241ea1ed76530036b02cff373cb69b8a4c721956add3df1359377d79b2fa2562e8dab8efe5ce877596e2d
6
+ metadata.gz: 709c402251e87cd3cb650a7081d121eda551fec86f46dde8c92f2799491810ddb16bd8e44280ced09c0a5daa343385958d8ab19640348cffc2f97eafcb14bcaf
7
+ data.tar.gz: 979fc101ef76e21023b9a6ce41649c0d144a2376429ff3ff6572b0384298d598a30f05ac64e0b2e98013180e7e4b0356a26da214df28ada4c5808e8ae02af331
data/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
1
+ ## 2.4.0 - 2021-07-19
2
+
3
+ - Adds support for matrix: URI's according to MSC2312
4
+ - Adds some basic support for detecting Spaces (MSC1772)
5
+ - Fixes sync against Synapse 1.38.0 missing empty fields
6
+
7
+ ## 2.3.0 - 2021-03-26
8
+
9
+ - Adds support for Ruby 3.0 (#15)
10
+ - Adds support for requests against the Synapse admin API
11
+ - Adds helper methods for checking and changing user power levels
12
+ - Adds a proper caching system for room data
13
+ - Fixes argument error in `#get_room_messages`
14
+ - Removes unfinished and broken AS abstraction
15
+
16
+ ## 2.2.0 - 2020-11-20
17
+
18
+ - Adds direct message (1:1) room mapping to client abstraction
19
+ - Adds Api#get_room_event_context (#13)
20
+ - Improves support for JRuby
21
+
22
+ ## 2.1.3 - 2020-09-18
23
+
24
+ - Adds separate state event handler as Client#on_state_event
25
+ - Changes Client sync interval to by-default run at full speed
26
+ - Fixes state events being sent twice if included in both timeline and state of a sync
27
+ - Improves error reporting of broken 200 responses
28
+ - Improves event handlers for rooms, to not depend on a specific room object instance anymore
29
+
1
30
  ## 2.1.2 - 2020-09-10
2
31
 
3
32
  - Adds method for reading complete member lists for rooms, improves the CS spec adherence
data/README.md CHANGED
@@ -4,7 +4,7 @@ A Ruby gem for easing the development of software that communicates with servers
4
4
 
5
5
  There is a Matrix room for the discussion about usage and development at [#ruby-matrix-sdk:kittenface.studio](https://matrix.to/#/#ruby-matrix-sdk:kittenface.studio).
6
6
 
7
- Live YARD documentation can be found at; http://aleol57.gitlab-pages.liu.se/ruby-matrix-sdk
7
+ Live YARD documentation can be found at; https://ruby-sdk.ananace.dev
8
8
 
9
9
  ## Example usage
10
10
 
data/lib/matrix_sdk.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'matrix_sdk/extensions'
3
+ require 'matrix_sdk/util/extensions'
4
+ require 'matrix_sdk/util/uri'
4
5
  require 'matrix_sdk/version'
5
6
 
6
7
  require 'json'
@@ -22,6 +23,15 @@ module MatrixSdk
22
23
  autoload :MatrixTimeoutError, 'matrix_sdk/errors'
23
24
  autoload :MatrixUnexpectedResponseError, 'matrix_sdk/errors'
24
25
 
26
+ module Rooms
27
+ autoload :Space, 'matrix_sdk/rooms/space'
28
+ end
29
+
30
+ module Util
31
+ autoload :Tinycache, 'matrix_sdk/util/tinycache'
32
+ autoload :TinycacheAdapter, 'matrix_sdk/util/tinycache_adapter'
33
+ end
34
+
25
35
  module Protocols
26
36
  autoload :AS, 'matrix_sdk/protocols/as'
27
37
  autoload :CS, 'matrix_sdk/protocols/cs'
@@ -38,6 +38,7 @@ module MatrixSdk
38
38
  # @option params [Numeric] :read_timeout (240) The timeout in seconds for reading responses
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
+ # @option params [Boolean] :synapse (true) Is the API connecting to a Synapse instance
41
42
  # @option params [Hash] :well_known The .well-known object that the server was discovered through, should not be set manually
42
43
  def initialize(homeserver, **params)
43
44
  @homeserver = homeserver
@@ -61,6 +62,7 @@ module MatrixSdk
61
62
  @well_known = params.fetch(:well_known, {})
62
63
  @global_headers = DEFAULT_HEADERS.dup
63
64
  @global_headers.merge!(params.fetch(:global_headers)) if params.key? :global_headers
65
+ @synapse = params.fetch(:synapse, true)
64
66
  @http = nil
65
67
 
66
68
  ([params.fetch(:protocols, [:CS])].flatten - protocols).each do |proto|
@@ -101,30 +103,30 @@ module MatrixSdk
101
103
  elsif target == :server
102
104
  # Attempt SRV record discovery
103
105
  target_uri = begin
104
- require 'resolv'
105
- resolver = Resolv::DNS.new
106
- srv = "_matrix._tcp.#{domain}"
107
- logger.debug "Trying DNS #{srv}..."
108
- d = resolver.getresource(srv, Resolv::DNS::Resource::IN::SRV)
109
- d
110
- rescue StandardError => e
111
- logger.debug "DNS lookup failed with #{e.class}: #{e.message}"
112
- nil
113
- end
106
+ require 'resolv'
107
+ resolver = Resolv::DNS.new
108
+ srv = "_matrix._tcp.#{domain}"
109
+ logger.debug "Trying DNS #{srv}..."
110
+ d = resolver.getresource(srv, Resolv::DNS::Resource::IN::SRV)
111
+ d
112
+ rescue StandardError => e
113
+ logger.debug "DNS lookup failed with #{e.class}: #{e.message}"
114
+ nil
115
+ end
114
116
 
115
117
  if target_uri.nil?
116
118
  # Attempt .well-known discovery for server-to-server
117
119
  well_known = begin
118
- wk_uri = URI("https://#{domain}/.well-known/matrix/server")
119
- logger.debug "Trying #{wk_uri}..."
120
- data = Net::HTTP.start(wk_uri.host, wk_uri.port, use_ssl: true, open_timeout: 5, read_timeout: 5, write_timeout: 5) do |http|
121
- http.get(wk_uri.path).body
122
- end
123
- JSON.parse(data)
124
- rescue StandardError => e
125
- logger.debug "Well-known failed with #{e.class}: #{e.message}"
126
- nil
127
- end
120
+ wk_uri = URI("https://#{domain}/.well-known/matrix/server")
121
+ logger.debug "Trying #{wk_uri}..."
122
+ data = Net::HTTP.start(wk_uri.host, wk_uri.port, use_ssl: true, open_timeout: 5, read_timeout: 5, write_timeout: 5) do |http|
123
+ http.get(wk_uri.path).body
124
+ end
125
+ JSON.parse(data)
126
+ rescue StandardError => e
127
+ logger.debug "Well-known failed with #{e.class}: #{e.message}"
128
+ nil
129
+ end
128
130
 
129
131
  target_uri = well_known['m.server'] if well_known&.key?('m.server')
130
132
  else
@@ -133,16 +135,16 @@ module MatrixSdk
133
135
  elsif %i[client identity].include? target
134
136
  # Attempt .well-known discovery
135
137
  well_known = begin
136
- wk_uri = URI("https://#{domain}/.well-known/matrix/client")
137
- logger.debug "Trying #{wk_uri}..."
138
- data = Net::HTTP.start(wk_uri.host, wk_uri.port, use_ssl: true, open_timeout: 5, read_timeout: 5, write_timeout: 5) do |http|
139
- http.get(wk_uri.path).body
140
- end
141
- data = JSON.parse(data)
142
- rescue StandardError => e
143
- logger.debug "Well-known failed with #{e.class}: #{e.message}"
144
- nil
145
- end
138
+ wk_uri = URI("https://#{domain}/.well-known/matrix/client")
139
+ logger.debug "Trying #{wk_uri}..."
140
+ data = Net::HTTP.start(wk_uri.host, wk_uri.port, use_ssl: true, open_timeout: 5, read_timeout: 5, write_timeout: 5) do |http|
141
+ http.get(wk_uri.path).body
142
+ end
143
+ JSON.parse(data)
144
+ rescue StandardError => e
145
+ logger.debug "Well-known failed with #{e.class}: #{e.message}"
146
+ nil
147
+ end
146
148
 
147
149
  if well_known
148
150
  key = 'm.homeserver'
@@ -161,11 +163,13 @@ module MatrixSdk
161
163
 
162
164
  params[:well_known] = well_known if keep_wellknown
163
165
 
164
- new(uri,
165
- params.merge(
166
- address: target_uri.host,
167
- port: target_uri.port
168
- ))
166
+ new(
167
+ uri,
168
+ **params.merge(
169
+ address: target_uri.host,
170
+ port: target_uri.port
171
+ )
172
+ )
169
173
  end
170
174
 
171
175
  # Get a list of enabled protocols on the API client
@@ -268,23 +272,6 @@ module MatrixSdk
268
272
  u.query = [u.query, URI.encode_www_form(options.fetch(:query))].flatten.compact.join('&') if options[:query]
269
273
  u.query = nil if u.query.nil? || u.query.empty?
270
274
  end
271
- request = Net::HTTP.const_get(method.to_s.capitalize.to_sym).new url.request_uri
272
- request.body = options[:body] if options.key? :body
273
- request.body = request.body.to_json if options.key?(:body) && !request.body.is_a?(String)
274
- request.body_stream = options[:body_stream] if options.key? :body_stream
275
-
276
- global_headers.each { |h, v| request[h] = v }
277
- if request.body || request.body_stream
278
- request.content_type = 'application/json'
279
- request.content_length = (request.body || request.body_stream).size
280
- end
281
-
282
- request['authorization'] = "Bearer #{access_token}" if access_token && !options.fetch(:skip_auth, false)
283
- if options.key? :headers
284
- options[:headers].each do |h, v|
285
- request[h.to_s.downcase] = v
286
- end
287
- end
288
275
 
289
276
  failures = 0
290
277
  loop do
@@ -292,10 +279,11 @@ module MatrixSdk
292
279
 
293
280
  req_id = ('A'..'Z').to_a.sample(4).join
294
281
 
295
- print_http(request, id: req_id)
282
+ req_obj = construct_request(url: url, method: method, **options)
283
+ print_http(req_obj, id: req_id)
296
284
  begin
297
285
  dur_start = Time.now
298
- response = http.request request
286
+ response = http.request req_obj
299
287
  dur_end = Time.now
300
288
  duration = dur_end - dur_start
301
289
  rescue EOFError
@@ -304,7 +292,12 @@ module MatrixSdk
304
292
  end
305
293
  print_http(response, duration: duration, id: req_id)
306
294
 
307
- data = JSON.parse(response.body, symbolize_names: true) rescue nil
295
+ begin
296
+ data = JSON.parse(response.body, symbolize_names: true)
297
+ rescue JSON::JSONError => e
298
+ logger.debug "#{e.class} error when parsing response. #{e}"
299
+ data = nil
300
+ end
308
301
 
309
302
  if response.is_a? Net::HTTPTooManyRequests
310
303
  raise MatrixRequestError.new_by_code(data, response.code) unless autoretry
@@ -315,7 +308,13 @@ module MatrixSdk
315
308
  next
316
309
  end
317
310
 
318
- return MatrixSdk::Response.new self, data if response.is_a? Net::HTTPSuccess
311
+ if response.is_a? Net::HTTPSuccess
312
+ unless data
313
+ logger.error "Received non-parsable data in 200 response; #{response.body.inspect}"
314
+ raise MatrixConnectionError, response
315
+ end
316
+ return MatrixSdk::Response.new self, data
317
+ end
319
318
  raise MatrixRequestError.new_by_code(data, response.code) if data
320
319
 
321
320
  raise MatrixConnectionError.class_by_code(response.code), response
@@ -333,14 +332,38 @@ module MatrixSdk
333
332
 
334
333
  private
335
334
 
335
+ def construct_request(method:, url:, **options)
336
+ request = Net::HTTP.const_get(method.to_s.capitalize.to_sym).new url.request_uri
337
+
338
+ # FIXME: Handle bodies better, avoid duplicating work
339
+ request.body = options[:body] if options.key? :body
340
+ request.body = request.body.to_json if options.key?(:body) && !request.body.is_a?(String)
341
+ request.body_stream = options[:body_stream] if options.key? :body_stream
342
+
343
+ global_headers.each { |h, v| request[h] = v }
344
+ if request.body || request.body_stream
345
+ request.content_type = 'application/json'
346
+ request.content_length = (request.body || request.body_stream).size
347
+ end
348
+
349
+ request['authorization'] = "Bearer #{access_token}" if access_token && !options.fetch(:skip_auth, false)
350
+ if options.key? :headers
351
+ options[:headers].each do |h, v|
352
+ request[h.to_s.downcase] = v
353
+ end
354
+ end
355
+
356
+ request
357
+ end
358
+
336
359
  def print_http(http, body: true, duration: nil, id: nil)
337
360
  return unless logger.debug?
338
361
 
339
362
  if http.is_a? Net::HTTPRequest
340
- dir = "#{id ? id + ' : ' : nil}>"
363
+ dir = "#{id ? "#{id} : " : nil}>"
341
364
  logger.debug "#{dir} Sending a #{http.method} request to `#{http.path}`:"
342
365
  else
343
- dir = "#{id ? id + ' : ' : nil}<"
366
+ dir = "#{id ? "#{id} : " : nil}<"
344
367
  logger.debug "#{dir} Received a #{http.code} #{http.message} response:#{duration ? " [#{(duration * 1000).to_i}ms]" : nil}"
345
368
  end
346
369
  http.to_hash.map { |k, v| "#{k}: #{k == 'authorization' ? '[ REDACTED ]' : v.join(', ')}" }.each do |h|
@@ -349,7 +372,7 @@ module MatrixSdk
349
372
  logger.debug dir
350
373
  if body
351
374
  clean_body = JSON.parse(http.body) rescue nil if http.body
352
- clean_body.keys.each { |k| clean_body[k] = '[ REDACTED ]' if %w[password access_token].include?(k) }.to_json if clean_body.is_a? Hash
375
+ clean_body.each_key { |k| clean_body[k] = '[ REDACTED ]' if %w[password access_token].include?(k) }.to_json if clean_body.is_a? Hash
353
376
  clean_body = clean_body.to_s if clean_body
354
377
  logger.debug "#{dir} #{clean_body.length < 200 ? clean_body : clean_body.slice(0..200) + "... [truncated, #{clean_body.length} Bytes]"}" if clean_body
355
378
  end
@@ -358,6 +381,8 @@ module MatrixSdk
358
381
  end
359
382
 
360
383
  def api_to_path(api)
384
+ return "/_synapse/#{api.to_s.split('_').join('/')}" if @synapse && api.to_s.start_with?('admin_')
385
+
361
386
  # TODO: <api>_current / <api>_latest
362
387
  "/_matrix/#{api.to_s.split('_').join('/')}"
363
388
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'matrix_sdk'
4
+ require 'matrix_sdk/util/events'
4
5
 
5
6
  require 'English'
6
7
  require 'forwardable'
@@ -23,7 +24,7 @@ module MatrixSdk
23
24
  attr_reader :api, :next_batch
24
25
  attr_accessor :cache, :sync_filter
25
26
 
26
- events :error, :event, :presence_event, :invite_event, :leave_event, :ephemeral_event
27
+ events :error, :event, :presence_event, :invite_event, :leave_event, :ephemeral_event, :state_event
27
28
  ignore_inspect :api,
28
29
  :on_event, :on_presence_event, :on_invite_event, :on_leave_event, :on_ephemeral_event
29
30
 
@@ -67,19 +68,17 @@ module MatrixSdk
67
68
  api.instance_variable_set("@#{k}", v) if api.instance_variable_defined? "@#{k}"
68
69
  end
69
70
  else
70
- @api = Api.new hs_url, params
71
+ @api = Api.new hs_url, **params
71
72
  end
72
73
 
73
- @rooms = {}
74
- @users = {}
75
74
  @cache = client_cache
76
75
  @identity_server = nil
76
+ @mxid = nil
77
77
 
78
78
  @sync_token = nil
79
79
  @sync_thread = nil
80
80
  @sync_filter = { room: { timeline: { limit: params.fetch(:sync_filter_limit, 20) }, state: { lazy_load_members: true } } }
81
81
 
82
- @should_listen = false
83
82
  @next_batch = nil
84
83
 
85
84
  @bad_sync_timeout_limit = 60 * 60
@@ -88,6 +87,11 @@ module MatrixSdk
88
87
  instance_variable_set("@#{k}", v) if instance_variable_defined? "@#{k}"
89
88
  end
90
89
 
90
+ @rooms = {}
91
+ @room_handlers = {}
92
+ @users = {}
93
+ @should_listen = false
94
+
91
95
  raise ArgumentError, 'Cache value must be one of of [:all, :some, :none]' unless %i[all some none].include? @cache
92
96
 
93
97
  return unless params[:user_id]
@@ -99,9 +103,8 @@ module MatrixSdk
99
103
  #
100
104
  # @return [MXID] The MXID of the current user
101
105
  def mxid
102
- @mxid ||= begin
103
- MXID.new api.whoami?[:user_id] if api&.access_token
104
- end
106
+ @mxid ||= MXID.new api.whoami?[:user_id] if api&.access_token
107
+ @mxid
105
108
  end
106
109
 
107
110
  alias user_id mxid
@@ -154,6 +157,25 @@ module MatrixSdk
154
157
  rooms
155
158
  end
156
159
 
160
+ # Gets a list of all direct chat rooms (1:1 chats / direct message chats) for the currenct user
161
+ #
162
+ # @return [Hash[String,Array[String]]] A mapping of MXIDs to a list of direct rooms with that user
163
+ def direct_rooms
164
+ api.get_account_data(mxid, 'm.direct').transform_keys(&:to_s)
165
+ end
166
+
167
+ # Gets a direct message room for the given user if one exists
168
+ #
169
+ # @note Will return the oldest room if multiple exist
170
+ # @return [Room,nil] A direct message room if one exists
171
+ def direct_room(mxid)
172
+ mxid = MatrixSdk::MXID.new mxid.to_s unless mxid.is_a? MatrixSdk::MXID
173
+ raise ArgumentError, 'Must be a valid user ID' unless mxid.user?
174
+
175
+ room_id = direct_rooms[mxid.to_s]&.first
176
+ ensure_room room_id if room_id
177
+ end
178
+
157
179
  # Gets a list of all relevant rooms, either the ones currently handled by
158
180
  # the client, or the list of currently joined ones if no rooms are handled
159
181
  #
@@ -170,6 +192,19 @@ module MatrixSdk
170
192
  @rooms.values
171
193
  end
172
194
 
195
+ # Get a list of all joined Matrix Spaces
196
+ #
197
+ # @return [Array[Room]] All the currently joined Spaces
198
+ def spaces
199
+ rooms = if cache == :none
200
+ api.get_joined_rooms.joined_rooms.map { |id| Room.new(self, id) }
201
+ else
202
+ self.rooms
203
+ end
204
+
205
+ rooms.select(&:space?)
206
+ end
207
+
173
208
  # Refresh the list of currently handled rooms, replacing it with the user's
174
209
  # currently joined rooms.
175
210
  #
@@ -187,6 +222,7 @@ module MatrixSdk
187
222
  true
188
223
  end
189
224
  alias refresh_rooms! reload_rooms!
225
+ alias reload_spaces! reload_rooms!
190
226
 
191
227
  # Register - and log in - on the connected HS as a guest
192
228
  #
@@ -337,7 +373,7 @@ module MatrixSdk
337
373
  # @return [Room] The resulting room
338
374
  # @see Protocols::CS#create_room
339
375
  def create_room(room_alias = nil, **params)
340
- data = api.create_room(params.merge(room_alias: room_alias))
376
+ data = api.create_room(**params.merge(room_alias: room_alias))
341
377
  ensure_room(data.room_id)
342
378
  end
343
379
 
@@ -405,13 +441,13 @@ module MatrixSdk
405
441
 
406
442
  # Upload a piece of data to the media repo
407
443
  #
408
- # @return [URI::MATRIX] A Matrix content (mxc://) URL pointing to the uploaded data
444
+ # @return [URI::MXC] A Matrix content (mxc://) URL pointing to the uploaded data
409
445
  # @param content [String] The data to upload
410
446
  # @param content_type [String] The MIME type of the data
411
447
  # @see Protocols::CS#media_upload
412
448
  def upload(content, content_type)
413
449
  data = api.media_upload(content, content_type)
414
- return data[:content_uri] if data.key? :content_uri
450
+ return URI(data[:content_uri]) if data.key? :content_uri
415
451
 
416
452
  raise MatrixUnexpectedResponseError, 'Upload succeeded, but no media URI returned'
417
453
  end
@@ -431,10 +467,11 @@ module MatrixSdk
431
467
  errors = 0
432
468
  thread, cancel_token = api.msc2108_sync_sse(params) do |data, event:, id:|
433
469
  @next_batch = id if id
434
- if event.to_sym == :sync
470
+ case event.to_sym
471
+ when :sync
435
472
  handle_sync_response(data)
436
473
  errors = 0
437
- elsif event.to_sym == :sync_error
474
+ when :sync_error
438
475
  logger.error "SSE Sync error received; #{data.type}: #{data.message}"
439
476
  errors += 1
440
477
 
@@ -445,7 +482,7 @@ module MatrixSdk
445
482
 
446
483
  @should_listen = cancel_token
447
484
  else
448
- thread = Thread.new { listen_forever(params) }
485
+ thread = Thread.new { listen_forever(**params) }
449
486
  end
450
487
  @sync_thread = thread
451
488
  thread.run
@@ -493,11 +530,9 @@ module MatrixSdk
493
530
 
494
531
  attempts = 0
495
532
  data = loop do
496
- begin
497
- break api.sync extra_params
498
- rescue MatrixSdk::MatrixTimeoutError => e
499
- raise e if (attempts += 1) >= params.fetch(:allow_sync_retry, 0)
500
- end
533
+ break api.sync(**extra_params)
534
+ rescue MatrixSdk::MatrixTimeoutError => e
535
+ raise e if (attempts += 1) >= params.fetch(:allow_sync_retry, 0)
501
536
  end
502
537
 
503
538
  @next_batch = data[:next_batch] unless skip_store_batch
@@ -524,11 +559,11 @@ module MatrixSdk
524
559
  end
525
560
  end
526
561
 
527
- def listen_forever(timeout: 30, bad_sync_timeout: 5, sync_interval: 30, **params)
562
+ def listen_forever(timeout: 30, bad_sync_timeout: 5, sync_interval: 0, **params)
528
563
  orig_bad_sync_timeout = bad_sync_timeout + 0
529
564
  while @should_listen
530
565
  begin
531
- sync(params.merge(timeout: timeout))
566
+ sync(**params.merge(timeout: timeout))
532
567
 
533
568
  bad_sync_timeout = orig_bad_sync_timeout
534
569
  sleep(sync_interval) if sync_interval.positive?
@@ -560,71 +595,51 @@ module MatrixSdk
560
595
  def handle_state(room_id, state_event)
561
596
  return unless state_event.key? :type
562
597
 
598
+ on_state_event.fire(MatrixEvent.new(self, state_event), state_event[:type])
599
+
563
600
  room = ensure_room(room_id)
564
601
  room.send :put_state_event, state_event
565
- content = state_event[:content]
566
- case state_event[:type]
567
- when 'm.room.name'
568
- room.instance_variable_set '@name', content[:name]
569
- when 'm.room.canonical_alias'
570
- room.instance_variable_set '@canonical_alias', content[:alias]
571
- # Also add as a regular alias
572
- room.instance_variable_get('@aliases').concat [content[:alias]]
573
- when 'm.room.topic'
574
- room.instance_variable_set '@topic', content[:topic]
575
- when 'm.room.aliases'
576
- room.instance_variable_get('@aliases').concat content[:aliases]
577
- when 'm.room.join_rules'
578
- room.instance_variable_set '@join_rule', content[:join_rule].nil? ? nil : content[:join_rule].to_sym
579
- when 'm.room.guest_access'
580
- room.instance_variable_set '@guest_access', content[:guest_access].nil? ? nil : content[:guest_access].to_sym
581
- when 'm.room.member'
582
- return unless cache == :all
583
-
584
- if content[:membership] == 'join'
585
- room.send(:ensure_member, get_user(state_event[:state_key]).dup.tap do |u|
586
- u.instance_variable_set :@display_name, content[:displayname]
587
- end)
588
- elsif %w[leave kick invite].include? content[:membership]
589
- room.members.delete_if { |m| m.id == state_event[:state_key] }
590
- end
591
- end
592
602
  end
593
603
 
594
604
  def handle_sync_response(data)
595
- data[:presence][:events].each do |presence_update|
605
+ data.dig(:presence, :events)&.each do |presence_update|
596
606
  fire_presence_event(MatrixEvent.new(self, presence_update))
597
607
  end
598
608
 
599
- data[:rooms][:invite].each do |room_id, invite|
609
+ data.dig(:rooms, :invite)&.each do |room_id, invite|
600
610
  invite[:room_id] = room_id.to_s
601
611
  fire_invite_event(MatrixEvent.new(self, invite), room_id.to_s)
602
612
  end
603
613
 
604
- data[:rooms][:leave].each do |room_id, left|
614
+ data.dig(:rooms, :leave)&.each do |room_id, left|
605
615
  left[:room_id] = room_id.to_s
606
616
  fire_leave_event(MatrixEvent.new(self, left), room_id.to_s)
607
617
  end
608
618
 
609
- data[:rooms][:join].each do |room_id, join|
619
+ data.dig(:rooms, :join)&.each do |room_id, join|
610
620
  room = ensure_room(room_id)
611
- room.instance_variable_set '@prev_batch', join[:timeline][:prev_batch]
621
+ room.instance_variable_set '@prev_batch', join.dig(:timeline, :prev_batch)
612
622
  room.instance_variable_set :@members_loaded, true unless sync_filter.fetch(:room, {}).fetch(:state, {}).fetch(:lazy_load_members, false)
613
623
 
614
- join[:state][:events].each do |event|
624
+ join.dig(:state, :events)&.each do |event|
615
625
  event[:room_id] = room_id.to_s
616
626
  handle_state(room_id, event)
617
627
  end
618
628
 
619
- join[:timeline][:events].each do |event|
629
+ join.dig(:timeline, :events)&.each do |event|
620
630
  event[:room_id] = room_id.to_s
621
- handle_state(room_id, event) if event.key? :state_key
631
+ # Avoid sending two identical state events if it's both in state and timeline
632
+ if event.key?(:state_key)
633
+ state_event = join.dig(:state, :events)&.find { |ev| ev[:event_id] == event[:event_id] }
634
+
635
+ handle_state(room_id, event) unless event == state_event
636
+ end
622
637
  room.send :put_event, event
623
638
 
624
639
  fire_event(MatrixEvent.new(self, event), event[:type])
625
640
  end
626
641
 
627
- join[:ephemeral][:events].each do |event|
642
+ join.dig(:ephemeral, :events)&.each do |event|
628
643
  event[:room_id] = room_id.to_s
629
644
  room.send :put_ephemeral_event, event
630
645
 
@@ -632,6 +647,14 @@ module MatrixSdk
632
647
  end
633
648
  end
634
649
 
650
+ unless cache == :none
651
+ @rooms.each do |_id, room|
652
+ # Clean up old cache data after every sync
653
+ # TODO Run this in a thread?
654
+ room.tinycache_adapter.cleanup
655
+ end
656
+ end
657
+
635
658
  nil
636
659
  end
637
660
  end