matrix_sdk 2.1.2 → 2.4.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: 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