matrix_sdk 2.1.0 → 2.3.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: 88e56c3775bf881fb720e4e262ff6073136204d26b72c03bb1bbd2a4138b90ee
4
- data.tar.gz: c8f5223c4c0c4febda391e8320a90667b6438b92806ce0b9b7c251a506678730
3
+ metadata.gz: fb2cd8492e977a8c63d80d3a250841111f872b45ef48a06ac610dd6c871c5704
4
+ data.tar.gz: b5932dc63938f86a4bec522f2d5736871f3a5461ad5d25581c2ba1cd79b20ab2
5
5
  SHA512:
6
- metadata.gz: 7ce44dc75ea446ff9f901d8309f58df4251c2c199540d27f086fbb77359974b5950da8c960540e3d7e89d1a40b7f264dfb9f1c5ad8462c4e94176fcb19fedc60
7
- data.tar.gz: 0b2acb16c36524bac00528fe322984a587f8f0c951754d316148fbd337a5f0dafec6ce5daf3cdc5829673115bb96453b60475702633ca1e11718d5e78c8e37b0
6
+ metadata.gz: 0ed57a7d4b3296a15c244c88ca88777d6ed3f9b67a6c4cdaae017f56c6e15cf94ba0781c6831a70f7adf9c55605073c84c451b0c1b0b8bffe323015330294f19
7
+ data.tar.gz: f77aaa05a45917b80aa296b7ff73f37d60ba5fb36f29a3e753cec1a730681a428c33ccdae9f4c26648952d6a7b05f9636e7d7ffbbe8757d23d2c435378d3466b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,40 @@
1
+ ## 2.3.0 - 2021-03-26
2
+
3
+ - Adds support for Ruby 3.0 (#15)
4
+ - Adds support for requests against the Synapse admin API
5
+ - Adds helper methods for checking and changing user power levels
6
+ - Adds a proper caching system for room data
7
+ - Fixes argument error in `#get_room_messages`
8
+ - Removes unfinished and broken AS abstraction
9
+
10
+ ## 2.2.0 - 2020-11-20
11
+
12
+ - Adds direct message (1:1) room mapping to client abstraction
13
+ - Adds Api#get_room_event_context (#13)
14
+ - Improves support for JRuby
15
+
16
+ ## 2.1.3 - 2020-09-18
17
+
18
+ - Adds separate state event handler as Client#on_state_event
19
+ - Changes Client sync interval to by-default run at full speed
20
+ - Fixes state events being sent twice if included in both timeline and state of a sync
21
+ - Improves error reporting of broken 200 responses
22
+ - Improves event handlers for rooms, to not depend on a specific room object instance anymore
23
+
24
+ ## 2.1.2 - 2020-09-10
25
+
26
+ - Adds method for reading complete member lists for rooms, improves the CS spec adherence
27
+ - Adds test for state events
28
+ - Fixes state event handler for rooms not actually passing events
29
+ - Fixes Api#new_for_domain using a faulty URI in certain cases
30
+
31
+ ## 2.1.1 - 2020-08-21
32
+
33
+ - Fixes crash if state event content is null (#11)
34
+ - Fixes an uninitialized URI constant exception when requiring only the main library file
35
+ - Fixes the Api#get_pushrules method missing an ending slash in the request URI
36
+ - Fixes discovery code for client/server connections based on domain
37
+
1
38
  ## 2.1.0 - 2020-05-22
2
39
 
3
40
  - Adds unique query IDs as well as duration in API debug output, to make it easier to track long requests
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,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'matrix_sdk/extensions'
3
+ require 'matrix_sdk/util/extensions'
4
4
  require 'matrix_sdk/version'
5
5
 
6
6
  require 'json'
@@ -22,6 +22,11 @@ module MatrixSdk
22
22
  autoload :MatrixTimeoutError, 'matrix_sdk/errors'
23
23
  autoload :MatrixUnexpectedResponseError, 'matrix_sdk/errors'
24
24
 
25
+ module Util
26
+ autoload :Tinycache, 'matrix_sdk/util/tinycache'
27
+ autoload :TinycacheAdapter, 'matrix_sdk/util/tinycache_adapter'
28
+ end
29
+
25
30
  module Protocols
26
31
  autoload :AS, 'matrix_sdk/protocols/as'
27
32
  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|
@@ -92,6 +94,8 @@ module MatrixSdk
92
94
  uri = URI("http#{ssl ? 's' : ''}://#{domain}")
93
95
  well_known = nil
94
96
  target_uri = nil
97
+ logger = ::Logging.logger[self]
98
+ logger.debug "Resolving #{domain}"
95
99
 
96
100
  if !port.nil? && !port.empty?
97
101
  # If the domain is fully qualified according to Matrix (FQDN and port) then skip discovery
@@ -99,21 +103,30 @@ module MatrixSdk
99
103
  elsif target == :server
100
104
  # Attempt SRV record discovery
101
105
  target_uri = begin
102
- require 'resolv'
103
- resolver = Resolv::DNS.new
104
- resolver.getresource("_matrix._tcp.#{domain}")
105
- rescue StandardError
106
- nil
107
- 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
108
116
 
109
117
  if target_uri.nil?
110
118
  # Attempt .well-known discovery for server-to-server
111
119
  well_known = begin
112
- data = Net::HTTP.get("https://#{domain}/.well-known/matrix/server")
113
- JSON.parse(data)
114
- rescue StandardError
115
- nil
116
- 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
117
130
 
118
131
  target_uri = well_known['m.server'] if well_known&.key?('m.server')
119
132
  else
@@ -122,11 +135,16 @@ module MatrixSdk
122
135
  elsif %i[client identity].include? target
123
136
  # Attempt .well-known discovery
124
137
  well_known = begin
125
- data = Net::HTTP.get("https://#{domain}/.well-known/matrix/client")
126
- JSON.parse(data)
127
- rescue StandardError
128
- nil
129
- 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
130
148
 
131
149
  if well_known
132
150
  key = 'm.homeserver'
@@ -138,17 +156,20 @@ module MatrixSdk
138
156
  end
139
157
  end
140
158
  end
159
+ logger.debug "Using #{target_uri.inspect}"
141
160
 
142
161
  # Fall back to direct domain connection
143
162
  target_uri ||= URI("https://#{domain}:8448")
144
163
 
145
164
  params[:well_known] = well_known if keep_wellknown
146
165
 
147
- new(uri,
148
- params.merge(
149
- address: target_uri.host,
150
- port: target_uri.port
151
- ))
166
+ new(
167
+ uri,
168
+ **params.merge(
169
+ address: target_uri.host,
170
+ port: target_uri.port
171
+ )
172
+ )
152
173
  end
153
174
 
154
175
  # Get a list of enabled protocols on the API client
@@ -251,23 +272,6 @@ module MatrixSdk
251
272
  u.query = [u.query, URI.encode_www_form(options.fetch(:query))].flatten.compact.join('&') if options[:query]
252
273
  u.query = nil if u.query.nil? || u.query.empty?
253
274
  end
254
- request = Net::HTTP.const_get(method.to_s.capitalize.to_sym).new url.request_uri
255
- request.body = options[:body] if options.key? :body
256
- request.body = request.body.to_json if options.key?(:body) && !request.body.is_a?(String)
257
- request.body_stream = options[:body_stream] if options.key? :body_stream
258
-
259
- global_headers.each { |h, v| request[h] = v }
260
- if request.body || request.body_stream
261
- request.content_type = 'application/json'
262
- request.content_length = (request.body || request.body_stream).size
263
- end
264
-
265
- request['authorization'] = "Bearer #{access_token}" if access_token && !options.fetch(:skip_auth, false)
266
- if options.key? :headers
267
- options[:headers].each do |h, v|
268
- request[h.to_s.downcase] = v
269
- end
270
- end
271
275
 
272
276
  failures = 0
273
277
  loop do
@@ -275,10 +279,11 @@ module MatrixSdk
275
279
 
276
280
  req_id = ('A'..'Z').to_a.sample(4).join
277
281
 
278
- print_http(request, id: req_id)
282
+ req_obj = construct_request(url: url, method: method, **options)
283
+ print_http(req_obj, id: req_id)
279
284
  begin
280
285
  dur_start = Time.now
281
- response = http.request request
286
+ response = http.request req_obj
282
287
  dur_end = Time.now
283
288
  duration = dur_end - dur_start
284
289
  rescue EOFError
@@ -287,7 +292,12 @@ module MatrixSdk
287
292
  end
288
293
  print_http(response, duration: duration, id: req_id)
289
294
 
290
- 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
291
301
 
292
302
  if response.is_a? Net::HTTPTooManyRequests
293
303
  raise MatrixRequestError.new_by_code(data, response.code) unless autoretry
@@ -298,7 +308,13 @@ module MatrixSdk
298
308
  next
299
309
  end
300
310
 
301
- 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
302
318
  raise MatrixRequestError.new_by_code(data, response.code) if data
303
319
 
304
320
  raise MatrixConnectionError.class_by_code(response.code), response
@@ -316,14 +332,38 @@ module MatrixSdk
316
332
 
317
333
  private
318
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
+
319
359
  def print_http(http, body: true, duration: nil, id: nil)
320
360
  return unless logger.debug?
321
361
 
322
362
  if http.is_a? Net::HTTPRequest
323
- dir = "#{id ? id + ' : ' : nil}>"
363
+ dir = "#{id ? "#{id} : " : nil}>"
324
364
  logger.debug "#{dir} Sending a #{http.method} request to `#{http.path}`:"
325
365
  else
326
- dir = "#{id ? id + ' : ' : nil}<"
366
+ dir = "#{id ? "#{id} : " : nil}<"
327
367
  logger.debug "#{dir} Received a #{http.code} #{http.message} response:#{duration ? " [#{(duration * 1000).to_i}ms]" : nil}"
328
368
  end
329
369
  http.to_hash.map { |k, v| "#{k}: #{k == 'authorization' ? '[ REDACTED ]' : v.join(', ')}" }.each do |h|
@@ -332,7 +372,7 @@ module MatrixSdk
332
372
  logger.debug dir
333
373
  if body
334
374
  clean_body = JSON.parse(http.body) rescue nil if http.body
335
- 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
336
376
  clean_body = clean_body.to_s if clean_body
337
377
  logger.debug "#{dir} #{clean_body.length < 200 ? clean_body : clean_body.slice(0..200) + "... [truncated, #{clean_body.length} Bytes]"}" if clean_body
338
378
  end
@@ -341,6 +381,8 @@ module MatrixSdk
341
381
  end
342
382
 
343
383
  def api_to_path(api)
384
+ return "/_synapse/#{api.to_s.split('_').join('/')}" if @synapse && api.to_s.start_with?('admin_')
385
+
344
386
  # TODO: <api>_current / <api>_latest
345
387
  "/_matrix/#{api.to_s.split('_').join('/')}"
346
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
 
@@ -44,7 +45,7 @@ module MatrixSdk
44
45
  # @see #initialize
45
46
  def self.new_for_domain(domain, **params)
46
47
  api = MatrixSdk::Api.new_for_domain(domain, keep_wellknown: true)
47
- return new(api, params) unless api.well_known.key? 'm.identity_server'
48
+ return new(api, params) unless api.well_known&.key?('m.identity_server')
48
49
 
49
50
  identity_server = MatrixSdk::Api.new(api.well_known['m.identity_server']['base_url'], protocols: %i[IS])
50
51
  new(api, params.merge(identity_server: identity_server))
@@ -67,11 +68,9 @@ 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
77
76
 
@@ -79,7 +78,6 @@ module MatrixSdk
79
78
  @sync_thread = nil
80
79
  @sync_filter = { room: { timeline: { limit: params.fetch(:sync_filter_limit, 20) }, state: { lazy_load_members: true } } }
81
80
 
82
- @should_listen = false
83
81
  @next_batch = nil
84
82
 
85
83
  @bad_sync_timeout_limit = 60 * 60
@@ -88,6 +86,11 @@ module MatrixSdk
88
86
  instance_variable_set("@#{k}", v) if instance_variable_defined? "@#{k}"
89
87
  end
90
88
 
89
+ @rooms = {}
90
+ @room_handlers = {}
91
+ @users = {}
92
+ @should_listen = false
93
+
91
94
  raise ArgumentError, 'Cache value must be one of of [:all, :some, :none]' unless %i[all some none].include? @cache
92
95
 
93
96
  return unless params[:user_id]
@@ -154,6 +157,27 @@ 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
+ Hash[api.get_account_data(mxid, 'm.direct').map do |mxid, rooms|
165
+ [mxid.to_s, rooms]
166
+ end]
167
+ end
168
+
169
+ # Gets a direct message room for the given user if one exists
170
+ #
171
+ # @note Will return the oldest room if multiple exist
172
+ # @return [Room,nil] A direct message room if one exists
173
+ def direct_room(mxid)
174
+ mxid = MatrixSdk::MXID.new mxid.to_s unless mxid.is_a? MatrixSdk::MXID
175
+ raise ArgumentError, 'Must be a valid user ID' unless mxid.user?
176
+
177
+ room_id = direct_rooms[mxid.to_s]&.first
178
+ ensure_room room_id if room_id
179
+ end
180
+
157
181
  # Gets a list of all relevant rooms, either the ones currently handled by
158
182
  # the client, or the list of currently joined ones if no rooms are handled
159
183
  #
@@ -337,13 +361,13 @@ module MatrixSdk
337
361
  # @return [Room] The resulting room
338
362
  # @see Protocols::CS#create_room
339
363
  def create_room(room_alias = nil, **params)
340
- data = api.create_room(params.merge(room_alias: room_alias))
364
+ data = api.create_room(**params.merge(room_alias: room_alias))
341
365
  ensure_room(data.room_id)
342
366
  end
343
367
 
344
368
  # Joins an already created room
345
369
  #
346
- # @param room_id_or_alias [String,MXID] A room alias (#room:exmaple.com) or a room ID (!id:example.com)
370
+ # @param room_id_or_alias [String,MXID] A room alias (#room:example.com) or a room ID (!id:example.com)
347
371
  # @param server_name [Array[String]] A list of servers to attempt the join through, required for IDs
348
372
  # @return [Room] The resulting room
349
373
  # @see Protocols::CS#join_room
@@ -431,10 +455,11 @@ module MatrixSdk
431
455
  errors = 0
432
456
  thread, cancel_token = api.msc2108_sync_sse(params) do |data, event:, id:|
433
457
  @next_batch = id if id
434
- if event.to_sym == :sync
458
+ case event.to_sym
459
+ when :sync
435
460
  handle_sync_response(data)
436
461
  errors = 0
437
- elsif event.to_sym == :sync_error
462
+ when :sync_error
438
463
  logger.error "SSE Sync error received; #{data.type}: #{data.message}"
439
464
  errors += 1
440
465
 
@@ -445,7 +470,7 @@ module MatrixSdk
445
470
 
446
471
  @should_listen = cancel_token
447
472
  else
448
- thread = Thread.new { listen_forever(params) }
473
+ thread = Thread.new { listen_forever(**params) }
449
474
  end
450
475
  @sync_thread = thread
451
476
  thread.run
@@ -494,7 +519,7 @@ module MatrixSdk
494
519
  attempts = 0
495
520
  data = loop do
496
521
  begin
497
- break api.sync extra_params
522
+ break api.sync **extra_params
498
523
  rescue MatrixSdk::MatrixTimeoutError => e
499
524
  raise e if (attempts += 1) >= params.fetch(:allow_sync_retry, 0)
500
525
  end
@@ -524,11 +549,11 @@ module MatrixSdk
524
549
  end
525
550
  end
526
551
 
527
- def listen_forever(timeout: 30, bad_sync_timeout: 5, sync_interval: 30, **params)
552
+ def listen_forever(timeout: 30, bad_sync_timeout: 5, sync_interval: 0, **params)
528
553
  orig_bad_sync_timeout = bad_sync_timeout + 0
529
554
  while @should_listen
530
555
  begin
531
- sync(params.merge(timeout: timeout))
556
+ sync(**params.merge(timeout: timeout))
532
557
 
533
558
  bad_sync_timeout = orig_bad_sync_timeout
534
559
  sleep(sync_interval) if sync_interval.positive?
@@ -560,34 +585,10 @@ module MatrixSdk
560
585
  def handle_state(room_id, state_event)
561
586
  return unless state_event.key? :type
562
587
 
588
+ on_state_event.fire(MatrixEvent.new(self, state_event), state_event[:type])
589
+
563
590
  room = ensure_room(room_id)
564
- content = state_event[:content]
565
- case state_event[:type]
566
- when 'm.room.name'
567
- room.instance_variable_set '@name', content[:name]
568
- when 'm.room.canonical_alias'
569
- room.instance_variable_set '@canonical_alias', content[:alias]
570
- # Also add as a regular alias
571
- room.instance_variable_get('@aliases').concat [content[:alias]]
572
- when 'm.room.topic'
573
- room.instance_variable_set '@topic', content[:topic]
574
- when 'm.room.aliases'
575
- room.instance_variable_get('@aliases').concat content[:aliases]
576
- when 'm.room.join_rules'
577
- room.instance_variable_set '@join_rule', content[:join_rule].to_sym
578
- when 'm.room.guest_access'
579
- room.instance_variable_set '@guest_access', content[:guest_access].to_sym
580
- when 'm.room.member'
581
- return unless cache == :all
582
-
583
- if content[:membership] == 'join'
584
- room.send(:ensure_member, get_user(state_event[:state_key]).dup.tap do |u|
585
- u.instance_variable_set :@display_name, content[:displayname]
586
- end)
587
- elsif %w[leave kick invite].include? content[:membership]
588
- room.members.delete_if { |m| m.id == state_event[:state_key] }
589
- end
590
- end
591
+ room.send :put_state_event, state_event
591
592
  end
592
593
 
593
594
  def handle_sync_response(data)
@@ -617,7 +618,12 @@ module MatrixSdk
617
618
 
618
619
  join[:timeline][:events].each do |event|
619
620
  event[:room_id] = room_id.to_s
620
- handle_state(room_id, event) unless event[:type] == 'm.room.message'
621
+ # Avoid sending two identical state events if it's both in state and timeline
622
+ if event.key?(:state_key)
623
+ state_event = join.dig(:state, :events).find { |ev| ev[:event_id] == event[:event_id] }
624
+
625
+ handle_state(room_id, event) unless event == state_event
626
+ end
621
627
  room.send :put_event, event
622
628
 
623
629
  fire_event(MatrixEvent.new(self, event), event[:type])
@@ -631,6 +637,14 @@ module MatrixSdk
631
637
  end
632
638
  end
633
639
 
640
+ unless cache == :none
641
+ @rooms.each do |_id, room|
642
+ # Clean up old cache data after every sync
643
+ # TODO Run this in a thread?
644
+ room.tinycache_adapter.cleanup
645
+ end
646
+ end
647
+
634
648
  nil
635
649
  end
636
650
  end