matrix_sdk 2.0.0 → 2.1.3

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: 2e0e5819346e56fd4eac1564434e88399500adefe0def16bc706a25ec7a254b2
4
- data.tar.gz: 2d74cd2d5203f5431396b03717c4302f9a0c7816c47eaa969e2395ab2eb46586
3
+ metadata.gz: 550080056a38ee354ff8e633069d71f207e1927a567fcaed4f7ed9792bcbb79a
4
+ data.tar.gz: ae8909f5a1092873453c4b7a876315efd29882cee353252d9bd92f069ce3c4ac
5
5
  SHA512:
6
- metadata.gz: 7eb8ef8277b0bda1191915e5be4dcf93856cd876f611b77974b8dc88e5e230ba5c3300995157f9717a4b00d1a7cba7f7a618844cc436ad0b6a3ea5865367049d
7
- data.tar.gz: bd888c36cf3a082360dbecc408c0a67e16ddb337a721105be7abf3a9662d81bfa564a1093a64d1f934d484af6a22a36e5b91bafde70f02a882e6d092e81d5459
6
+ metadata.gz: 5838b47c8ea7df7becdb7dadcb07e57c31526c8c3cdd025a18cc2add7db9e9da9f0aff62a16ed4a3a5306566785f941e7cdba90e664f2b872959c7774b4ec4bc
7
+ data.tar.gz: 3fcad5ea834b2dd0e6d68e55a8e88e5a88b444d07491f381420c4749ca1ff5375030f3a7c4b1b535ff947422caf7c4b86f81633d425614a05e5577aa9a4963ff
@@ -1,3 +1,37 @@
1
+ ## 2.1.3 - 2020-09-18
2
+
3
+ - Adds separate state event handler as Client#on_state_event
4
+ - Changes Client sync interval to by-default run at full speed
5
+ - Fixes state events being sent twice if included in both timeline and state of a sync
6
+ - Improves error reporting of broken 200 responses
7
+ - Improves event handlers for rooms, to not depend on a specific room object instance anymore
8
+
9
+ ## 2.1.2 - 2020-09-10
10
+
11
+ - Adds method for reading complete member lists for rooms, improves the CS spec adherence
12
+ - Adds test for state events
13
+ - Fixes state event handler for rooms not actually passing events
14
+ - Fixes Api#new_for_domain using a faulty URI in certain cases
15
+
16
+ ## 2.1.1 - 2020-08-21
17
+
18
+ - Fixes crash if state event content is null (#11)
19
+ - Fixes an uninitialized URI constant exception when requiring only the main library file
20
+ - Fixes the Api#get_pushrules method missing an ending slash in the request URI
21
+ - Fixes discovery code for client/server connections based on domain
22
+
23
+ ## 2.1.0 - 2020-05-22
24
+
25
+ - Adds unique query IDs as well as duration in API debug output, to make it easier to track long requests
26
+ - Finishes up MSC support, get sync over SSE working flawlessly
27
+ - Exposes the #listen_forever method in the client abstraction
28
+ - Fixes room access methods
29
+
30
+ ## 2.0.1 - 2020-03-13
31
+
32
+ - Adds code for handling non-final MSC's in protocols
33
+ - Currently implementing clients parts of MSC2018 for Sync over Server Sent Events
34
+
1
35
  ## 2.0.0 - 2020-02-14
2
36
 
3
37
  **NB**, this release includes backwards-incompatible changes;
@@ -27,6 +27,9 @@ module MatrixSdk
27
27
  autoload :CS, 'matrix_sdk/protocols/cs'
28
28
  autoload :IS, 'matrix_sdk/protocols/is'
29
29
  autoload :SS, 'matrix_sdk/protocols/ss'
30
+
31
+ # Non-final protocol extensions
32
+ autoload :MSC, 'matrix_sdk/protocols/msc'
30
33
  end
31
34
 
32
35
  def self.debug!
@@ -11,10 +11,6 @@ module MatrixSdk
11
11
  class Api
12
12
  extend MatrixSdk::Extensions
13
13
  include MatrixSdk::Logging
14
- include MatrixSdk::Protocols::AS
15
- include MatrixSdk::Protocols::CS
16
- include MatrixSdk::Protocols::IS
17
- include MatrixSdk::Protocols::SS
18
14
 
19
15
  USER_AGENT = "Ruby Matrix SDK v#{MatrixSdk::VERSION}"
20
16
  DEFAULT_HEADERS = {
@@ -23,7 +19,7 @@ module MatrixSdk
23
19
  }.freeze
24
20
 
25
21
  attr_accessor :access_token, :connection_address, :connection_port, :device_id, :autoretry, :global_headers
26
- attr_reader :homeserver, :validate_certificate, :open_timeout, :read_timeout, :protocols, :well_known, :proxy_uri
22
+ attr_reader :homeserver, :validate_certificate, :open_timeout, :read_timeout, :well_known, :proxy_uri
27
23
 
28
24
  ignore_inspect :access_token, :logger
29
25
 
@@ -51,10 +47,6 @@ module MatrixSdk
51
47
  @homeserver.path.gsub!(/\/?_matrix\/?/, '') if @homeserver.path =~ /_matrix\/?$/
52
48
  raise ArgumentError, 'Please use the base URL for your HS (without /_matrix/)' if @homeserver.path.include? '/_matrix/'
53
49
 
54
- @protocols = params.fetch(:protocols, %i[CS])
55
- @protocols = [@protocols] unless @protocols.is_a? Array
56
- @protocols << :CS if @protocols.include?(:AS) && !@protocols.include?(:CS)
57
-
58
50
  @proxy_uri = params.fetch(:proxy_uri, nil)
59
51
  @connection_address = params.fetch(:address, nil)
60
52
  @connection_port = params.fetch(:port, nil)
@@ -71,6 +63,10 @@ module MatrixSdk
71
63
  @global_headers.merge!(params.fetch(:global_headers)) if params.key? :global_headers
72
64
  @http = nil
73
65
 
66
+ ([params.fetch(:protocols, [:CS])].flatten - protocols).each do |proto|
67
+ self.class.include MatrixSdk::Protocols.const_get(proto)
68
+ end
69
+
74
70
  login(user: @homeserver.user, password: @homeserver.password) if @homeserver.user && @homeserver.password && !@access_token && !params[:skip_login] && protocol?(:CS)
75
71
  @homeserver.userinfo = '' unless params[:skip_login]
76
72
  end
@@ -96,6 +92,8 @@ module MatrixSdk
96
92
  uri = URI("http#{ssl ? 's' : ''}://#{domain}")
97
93
  well_known = nil
98
94
  target_uri = nil
95
+ logger = ::Logging.logger[self]
96
+ logger.debug "Resolving #{domain}"
99
97
 
100
98
  if !port.nil? && !port.empty?
101
99
  # If the domain is fully qualified according to Matrix (FQDN and port) then skip discovery
@@ -103,21 +101,30 @@ module MatrixSdk
103
101
  elsif target == :server
104
102
  # Attempt SRV record discovery
105
103
  target_uri = begin
106
- require 'resolv'
107
- resolver = Resolv::DNS.new
108
- resolver.getresource("_matrix._tcp.#{domain}")
109
- rescue StandardError
110
- nil
111
- end
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
112
114
 
113
115
  if target_uri.nil?
114
116
  # Attempt .well-known discovery for server-to-server
115
117
  well_known = begin
116
- data = Net::HTTP.get("https://#{domain}/.well-known/matrix/server")
117
- JSON.parse(data)
118
- rescue StandardError
119
- nil
120
- end
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
121
128
 
122
129
  target_uri = well_known['m.server'] if well_known&.key?('m.server')
123
130
  else
@@ -126,11 +133,16 @@ module MatrixSdk
126
133
  elsif %i[client identity].include? target
127
134
  # Attempt .well-known discovery
128
135
  well_known = begin
129
- data = Net::HTTP.get("https://#{domain}/.well-known/matrix/client")
130
- JSON.parse(data)
131
- rescue StandardError
132
- nil
133
- end
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
+ JSON.parse(data)
142
+ rescue StandardError => e
143
+ logger.debug "Well-known failed with #{e.class}: #{e.message}"
144
+ nil
145
+ end
134
146
 
135
147
  if well_known
136
148
  key = 'm.homeserver'
@@ -142,6 +154,7 @@ module MatrixSdk
142
154
  end
143
155
  end
144
156
  end
157
+ logger.debug "Using #{target_uri.inspect}"
145
158
 
146
159
  # Fall back to direct domain connection
147
160
  target_uri ||= URI("https://#{domain}:8448")
@@ -155,6 +168,21 @@ module MatrixSdk
155
168
  ))
156
169
  end
157
170
 
171
+ # Get a list of enabled protocols on the API client
172
+ #
173
+ # @example
174
+ # MatrixSdk::Api.new_for_domain('matrix.org').protocols
175
+ # # => [:IS, :CS]
176
+ #
177
+ # @return [Symbol[]] An array of enabled APIs
178
+ def protocols
179
+ self
180
+ .class.included_modules
181
+ .reject { |m| m&.name.nil? }
182
+ .select { |m| m.name.start_with? 'MatrixSdk::Protocols::' }
183
+ .map { |m| m.name.split('::').last.to_sym }
184
+ end
185
+
158
186
  # Check if a protocol is enabled on the API connection
159
187
  #
160
188
  # @example Checking for identity server API support
@@ -233,6 +261,7 @@ module MatrixSdk
233
261
  # @option options [Hash,String] :body The body to attach to the request, will be JSON-encoded if sent as a hash
234
262
  # @option options [IO] :body_stream A body stream to attach to the request
235
263
  # @option options [Hash] :headers Additional headers to set on the request
264
+ # @option options [Boolean] :skip_auth (false) Skip authentication
236
265
  def request(method, api, path, **options)
237
266
  url = homeserver.dup.tap do |u|
238
267
  u.path = api_to_path(api) + path
@@ -250,7 +279,7 @@ module MatrixSdk
250
279
  request.content_length = (request.body || request.body_stream).size
251
280
  end
252
281
 
253
- request['authorization'] = "Bearer #{access_token}" if access_token
282
+ request['authorization'] = "Bearer #{access_token}" if access_token && !options.fetch(:skip_auth, false)
254
283
  if options.key? :headers
255
284
  options[:headers].each do |h, v|
256
285
  request[h.to_s.downcase] = v
@@ -261,15 +290,26 @@ module MatrixSdk
261
290
  loop do
262
291
  raise MatrixConnectionError, "Server still too busy to handle request after #{failures} attempts, try again later" if failures >= 10
263
292
 
264
- print_http(request)
293
+ req_id = ('A'..'Z').to_a.sample(4).join
294
+
295
+ print_http(request, id: req_id)
265
296
  begin
297
+ dur_start = Time.now
266
298
  response = http.request request
267
- rescue EOFError => e
299
+ dur_end = Time.now
300
+ duration = dur_end - dur_start
301
+ rescue EOFError
268
302
  logger.error 'Socket closed unexpectedly'
269
- raise e
303
+ raise
304
+ end
305
+ print_http(response, duration: duration, id: req_id)
306
+
307
+ begin
308
+ data = JSON.parse(response.body, symbolize_names: true)
309
+ rescue JSON::JSONError => e
310
+ logger.debug "#{e.class} error when parsing response. #{e}"
311
+ data = nil
270
312
  end
271
- print_http(response)
272
- data = JSON.parse(response.body, symbolize_names: true) rescue nil
273
313
 
274
314
  if response.is_a? Net::HTTPTooManyRequests
275
315
  raise MatrixRequestError.new_by_code(data, response.code) unless autoretry
@@ -280,43 +320,54 @@ module MatrixSdk
280
320
  next
281
321
  end
282
322
 
283
- return MatrixSdk::Response.new self, data if response.is_a? Net::HTTPSuccess
323
+ if response.is_a? Net::HTTPSuccess
324
+ unless data
325
+ logger.error "Received non-parsable data in 200 response; #{response.body.inspect}"
326
+ raise MatrixConnectionError, response
327
+ end
328
+ return MatrixSdk::Response.new self, data
329
+ end
284
330
  raise MatrixRequestError.new_by_code(data, response.code) if data
285
331
 
286
332
  raise MatrixConnectionError.class_by_code(response.code), response
287
333
  end
288
334
  end
289
335
 
336
+ # Generate a transaction ID
337
+ #
338
+ # @return [String] An arbitrary transaction ID
339
+ def transaction_id
340
+ ret = @transaction_id ||= 0
341
+ @transaction_id = @transaction_id.succ
342
+ ret
343
+ end
344
+
290
345
  private
291
346
 
292
- def print_http(http)
347
+ def print_http(http, body: true, duration: nil, id: nil)
293
348
  return unless logger.debug?
294
349
 
295
350
  if http.is_a? Net::HTTPRequest
296
- dir = '>'
351
+ dir = "#{id ? id + ' : ' : nil}>"
297
352
  logger.debug "#{dir} Sending a #{http.method} request to `#{http.path}`:"
298
353
  else
299
- dir = '<'
300
- logger.debug "#{dir} Received a #{http.code} #{http.message} response:"
354
+ dir = "#{id ? id + ' : ' : nil}<"
355
+ logger.debug "#{dir} Received a #{http.code} #{http.message} response:#{duration ? " [#{(duration * 1000).to_i}ms]" : nil}"
301
356
  end
302
357
  http.to_hash.map { |k, v| "#{k}: #{k == 'authorization' ? '[ REDACTED ]' : v.join(', ')}" }.each do |h|
303
358
  logger.debug "#{dir} #{h}"
304
359
  end
305
360
  logger.debug dir
306
- clean_body = JSON.parse(http.body) rescue nil if http.body
307
- clean_body.keys.each { |k| clean_body[k] = '[ REDACTED ]' if %w[password access_token].include?(k) }.to_json if clean_body.is_a? Hash
308
- clean_body = clean_body.to_s if clean_body
309
- logger.debug "#{dir} #{clean_body.length < 200 ? clean_body : clean_body.slice(0..200) + "... [truncated, #{clean_body.length} Bytes]"}" if clean_body
361
+ if body
362
+ clean_body = JSON.parse(http.body) rescue nil if http.body
363
+ clean_body.keys.each { |k| clean_body[k] = '[ REDACTED ]' if %w[password access_token].include?(k) }.to_json if clean_body.is_a? Hash
364
+ clean_body = clean_body.to_s if clean_body
365
+ logger.debug "#{dir} #{clean_body.length < 200 ? clean_body : clean_body.slice(0..200) + "... [truncated, #{clean_body.length} Bytes]"}" if clean_body
366
+ end
310
367
  rescue StandardError => e
311
368
  logger.warn "#{e.class} occured while printing request debug; #{e.message}\n#{e.backtrace.join "\n"}"
312
369
  end
313
370
 
314
- def transaction_id
315
- ret = @transaction_id ||= 0
316
- @transaction_id = @transaction_id.succ
317
- ret
318
- end
319
-
320
371
  def api_to_path(api)
321
372
  # TODO: <api>_current / <api>_latest
322
373
  "/_matrix/#{api.to_s.split('_').join('/')}"
@@ -23,7 +23,7 @@ module MatrixSdk
23
23
  attr_reader :api, :next_batch
24
24
  attr_accessor :cache, :sync_filter
25
25
 
26
- events :error, :event, :presence_event, :invite_event, :leave_event, :ephemeral_event
26
+ events :error, :event, :presence_event, :invite_event, :leave_event, :ephemeral_event, :state_event
27
27
  ignore_inspect :api,
28
28
  :on_event, :on_presence_event, :on_invite_event, :on_leave_event, :on_ephemeral_event
29
29
 
@@ -44,7 +44,7 @@ module MatrixSdk
44
44
  # @see #initialize
45
45
  def self.new_for_domain(domain, **params)
46
46
  api = MatrixSdk::Api.new_for_domain(domain, keep_wellknown: true)
47
- return new(api, params) unless api.well_known.key? 'm.identity_server'
47
+ return new(api, params) unless api.well_known&.key?('m.identity_server')
48
48
 
49
49
  identity_server = MatrixSdk::Api.new(api.well_known['m.identity_server']['base_url'], protocols: %i[IS])
50
50
  new(api, params.merge(identity_server: identity_server))
@@ -70,8 +70,6 @@ module MatrixSdk
70
70
  @api = Api.new hs_url, params
71
71
  end
72
72
 
73
- @rooms = {}
74
- @users = {}
75
73
  @cache = client_cache
76
74
  @identity_server = nil
77
75
 
@@ -79,7 +77,6 @@ module MatrixSdk
79
77
  @sync_thread = nil
80
78
  @sync_filter = { room: { timeline: { limit: params.fetch(:sync_filter_limit, 20) }, state: { lazy_load_members: true } } }
81
79
 
82
- @should_listen = false
83
80
  @next_batch = nil
84
81
 
85
82
  @bad_sync_timeout_limit = 60 * 60
@@ -88,6 +85,11 @@ module MatrixSdk
88
85
  instance_variable_set("@#{k}", v) if instance_variable_defined? "@#{k}"
89
86
  end
90
87
 
88
+ @rooms = {}
89
+ @room_handlers = {}
90
+ @users = {}
91
+ @should_listen = false
92
+
91
93
  raise ArgumentError, 'Cache value must be one of of [:all, :some, :none]' unless %i[all some none].include? @cache
92
94
 
93
95
  return unless params[:user_id]
@@ -343,7 +345,7 @@ module MatrixSdk
343
345
 
344
346
  # Joins an already created room
345
347
  #
346
- # @param room_id_or_alias [String,MXID] A room alias (#room:exmaple.com) or a room ID (!id:example.com)
348
+ # @param room_id_or_alias [String,MXID] A room alias (#room:example.com) or a room ID (!id:example.com)
347
349
  # @param server_name [Array[String]] A list of servers to attempt the join through, required for IDs
348
350
  # @return [Room] The resulting room
349
351
  # @see Protocols::CS#join_room
@@ -423,7 +425,30 @@ module MatrixSdk
423
425
  return if listening?
424
426
 
425
427
  @should_listen = true
426
- thread = Thread.new { listen_forever(params) }
428
+ if api.protocol?(:MSC) && api.msc2108?
429
+ params[:filter] = sync_filter unless params.key? :filter
430
+ params[:filter] = params[:filter].to_json unless params[:filter].nil? || params[:filter].is_a?(String)
431
+ params[:since] = @next_batch if @next_batch
432
+
433
+ errors = 0
434
+ thread, cancel_token = api.msc2108_sync_sse(params) do |data, event:, id:|
435
+ @next_batch = id if id
436
+ if event.to_sym == :sync
437
+ handle_sync_response(data)
438
+ errors = 0
439
+ elsif event.to_sym == :sync_error
440
+ logger.error "SSE Sync error received; #{data.type}: #{data.message}"
441
+ errors += 1
442
+
443
+ # TODO: Allow configuring
444
+ raise 'Aborting due to excessive errors' if errors >= 5
445
+ end
446
+ end
447
+
448
+ @should_listen = cancel_token
449
+ else
450
+ thread = Thread.new { listen_forever(params) }
451
+ end
427
452
  @sync_thread = thread
428
453
  thread.run
429
454
  end
@@ -432,8 +457,15 @@ module MatrixSdk
432
457
  def stop_listener_thread
433
458
  return unless @sync_thread
434
459
 
435
- @should_listen = false
436
- @sync_thread.join if @sync_thread.alive?
460
+ if @should_listen.is_a? Hash
461
+ @should_listen[:run] = false
462
+ else
463
+ @should_listen = false
464
+ end
465
+ if @sync_thread.alive?
466
+ ret = @sync_thread.join(2)
467
+ @sync_thread.kill unless ret
468
+ end
437
469
  @sync_thread = nil
438
470
  end
439
471
 
@@ -494,9 +526,7 @@ module MatrixSdk
494
526
  end
495
527
  end
496
528
 
497
- private
498
-
499
- def listen_forever(timeout: 30, bad_sync_timeout: 5, sync_interval: 30, **params)
529
+ def listen_forever(timeout: 30, bad_sync_timeout: 5, sync_interval: 0, **params)
500
530
  orig_bad_sync_timeout = bad_sync_timeout + 0
501
531
  while @should_listen
502
532
  begin
@@ -519,6 +549,8 @@ module MatrixSdk
519
549
  fire_error(ErrorEvent.new(e, :listener_thread))
520
550
  end
521
551
 
552
+ private
553
+
522
554
  def post_authentication(data)
523
555
  @mxid = data[:user_id]
524
556
  @api.access_token = data[:access_token]
@@ -530,7 +562,10 @@ module MatrixSdk
530
562
  def handle_state(room_id, state_event)
531
563
  return unless state_event.key? :type
532
564
 
565
+ on_state_event.fire(MatrixEvent.new(self, state_event), state_event[:type])
566
+
533
567
  room = ensure_room(room_id)
568
+ room.send :put_state_event, state_event
534
569
  content = state_event[:content]
535
570
  case state_event[:type]
536
571
  when 'm.room.name'
@@ -544,9 +579,9 @@ module MatrixSdk
544
579
  when 'm.room.aliases'
545
580
  room.instance_variable_get('@aliases').concat content[:aliases]
546
581
  when 'm.room.join_rules'
547
- room.instance_variable_set '@join_rule', content[:join_rule].to_sym
582
+ room.instance_variable_set '@join_rule', content[:join_rule].nil? ? nil : content[:join_rule].to_sym
548
583
  when 'm.room.guest_access'
549
- room.instance_variable_set '@guest_access', content[:guest_access].to_sym
584
+ room.instance_variable_set '@guest_access', content[:guest_access].nil? ? nil : content[:guest_access].to_sym
550
585
  when 'm.room.member'
551
586
  return unless cache == :all
552
587
 
@@ -587,7 +622,12 @@ module MatrixSdk
587
622
 
588
623
  join[:timeline][:events].each do |event|
589
624
  event[:room_id] = room_id.to_s
590
- handle_state(room_id, event) unless event[:type] == 'm.room.message'
625
+ # Avoid sending two identical state events if it's both in state and timeline
626
+ if event.key?(:state_key)
627
+ state_event = join.dig(:state, :events).find { |ev| ev[:event_id] == event[:event_id] }
628
+
629
+ handle_state(room_id, event) unless event == state_event
630
+ end
591
631
  room.send :put_event, event
592
632
 
593
633
  fire_event(MatrixEvent.new(self, event), event[:type])
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'uri'
4
+
3
5
  module URI
4
6
  # A mxc:// Matrix content URL
5
7
  class MATRIX < Generic
@@ -10,6 +10,7 @@ module MatrixSdk
10
10
  raise ArgumentError, 'Identifier is too long' if identifier.size > 255
11
11
  raise ArgumentError, 'Identifier lacks required data' unless identifier =~ %r{^([@!$+#][^:]+:[^:]+(?::\d+)?)|(\$[A-Za-z0-9+/]+)$}
12
12
 
13
+ # TODO: Community-as-a-Room / Profile-as-a-Room, in case they're going for room aliases
13
14
  @sigil = identifier[0]
14
15
  @localpart, @domain, @port = identifier[1..-1].split(':')
15
16
  @port = @port.to_i if @port
@@ -1637,7 +1637,7 @@ module MatrixSdk::Protocols::CS
1637
1637
 
1638
1638
  room_id = ERB::Util.url_encode room_id.to_s
1639
1639
 
1640
- request(:get, :client_r0, "/rooms/#{room_id}/members", query: query)
1640
+ request(:get, :client_r0, "/rooms/#{room_id}/members", query: query.merge(params))
1641
1641
  end
1642
1642
 
1643
1643
  # Gets a list of the joined members in a room
@@ -1823,7 +1823,7 @@ module MatrixSdk::Protocols::CS
1823
1823
  # @see https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
1824
1824
  # The Matrix Spec, for more information about the parameters and data
1825
1825
  def get_pushrules
1826
- request(:get, :client_r0, '/pushrules')
1826
+ request(:get, :client_r0, '/pushrules/')
1827
1827
  end
1828
1828
 
1829
1829
  # Retrieves a single registered push rule for the current user
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Preliminary support for unmerged MSCs (Matrix Spec Changes)
4
+ module MatrixSdk::Protocols::MSC
5
+ def refresh_mscs
6
+ @msc = {}
7
+ end
8
+
9
+ # Check if there's support for MSC2108 - Sync over Server Sent Events
10
+ def msc2108?
11
+ @msc ||= {}
12
+ @msc[2108] ||= \
13
+ begin
14
+ request(:get, :client_r0, '/sync/sse', skip_auth: true, headers: { accept: 'text/event-stream' })
15
+ rescue MatrixSdk::MatrixNotAuthorizedError # Returns 401 if implemented
16
+ true
17
+ rescue MatrixSdk::MatrixRequestError
18
+ false
19
+ end
20
+ rescue StandardError => e
21
+ logger.debug "Failed to check MSC2108 status;\n#{e.inspect}"
22
+ false
23
+ end
24
+
25
+ # Sync over Server Sent Events - MSC2108
26
+ #
27
+ # @example Syncing over SSE
28
+ # @since = 'some token'
29
+ # api.msc2108_sync_sse(since: @since) do |data, event:, id:|
30
+ # if event == 'sync'
31
+ # handle(data) # data is the same as a normal sync response
32
+ # @since = id
33
+ # end
34
+ # end
35
+ #
36
+ # @see Protocols::CS#sync
37
+ # @see https://github.com/matrix-org/matrix-doc/pull/2108/
38
+ # rubocop:disable Metrics/MethodLength
39
+ def msc2108_sync_sse(since: nil, **params, &on_data)
40
+ raise ArgumentError, 'Must be given a block accepting two args - data and { event:, id: }' \
41
+ unless on_data.is_a?(Proc) && on_data.arity == 2
42
+ raise 'Needs to be logged in' unless access_token # TODO: Better error
43
+
44
+ query = params.select do |k, _v|
45
+ %i[filter full_state set_presence].include? k
46
+ end
47
+ query[:user_id] = params.delete(:user_id) if protocol?(:AS) && params.key?(:user_id)
48
+
49
+ req = Net::HTTP::Get.new(homeserver.dup.tap do |u|
50
+ u.path = api_to_path(:client_r0) + '/sync/sse'
51
+ u.query = URI.encode_www_form(query)
52
+ end)
53
+ req['accept'] = 'text/event-stream'
54
+ req['accept-encoding'] = 'identity' # Disable compression on the SSE stream
55
+ req['authorization'] = "Bearer #{access_token}"
56
+ req['last-event-id'] = since if since
57
+
58
+ cancellation_token = { run: true }
59
+
60
+ # rubocop:disable Metrics/BlockLength
61
+ thread = Thread.new(cancellation_token) do |ctx|
62
+ print_http(req)
63
+ http.request req do |response|
64
+ break unless ctx[:run]
65
+
66
+ print_http(response, body: false)
67
+ raise MatrixRequestError.new_by_code(JSON.parse(response.body, symbolize_names: true), response.code) unless response.is_a? Net::HTTPSuccess
68
+
69
+ # Override buffer size for BufferedIO
70
+ socket = response.instance_variable_get :@socket
71
+ if socket.is_a? Net::BufferedIO
72
+ socket.instance_eval do
73
+ def rbuf_fill
74
+ bufsize_override = 1024
75
+ loop do
76
+ case rv = @io.read_nonblock(bufsize_override, exception: false)
77
+ when String
78
+ @rbuf << rv
79
+ rv.clear
80
+ return
81
+ when :wait_readable
82
+ @io.to_io.wait_readable(@read_timeout) || raise(Net::ReadTimeout)
83
+ when :wait_writable
84
+ @io.to_io.wait_writable(@read_timeout) || raise(Net::ReadTimeout)
85
+ when nil
86
+ raise EOFError, 'end of file reached'
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ stream_id = ('A'..'Z').to_a.sample(4).join
94
+
95
+ logger.debug "MSC2108 : #{stream_id} : Starting SSE stream."
96
+
97
+ buffer = ''
98
+ response.read_body do |chunk|
99
+ buffer += chunk
100
+
101
+ while (index = buffer.index(/\r\n\r\n|\n\n/))
102
+ stream = buffer.slice!(0..index)
103
+
104
+ data = ''
105
+ event = nil
106
+ id = nil
107
+
108
+ stream.split(/\r?\n/).each do |part|
109
+ /^data:(.+)$/.match(part) do |m_data|
110
+ data += "\n" unless data.empty?
111
+ data += m_data[1].strip
112
+ end
113
+ /^event:(.+)$/.match(part) do |m_event|
114
+ event = m_event[1].strip
115
+ end
116
+ /^id:(.+)$/.match(part) do |m_id|
117
+ id = m_id[1].strip
118
+ end
119
+ /^:(.+)$/.match(part) do |m_comment|
120
+ logger.debug "MSC2108 : #{stream_id} : Received comment '#{m_comment[1].strip}'"
121
+ end
122
+ end
123
+
124
+ if %w[sync sync_error].include? event
125
+ data = JSON.parse(data, symbolize_names: true)
126
+ yield((MatrixSdk::Response.new self, data), event: event, id: id)
127
+ elsif event
128
+ logger.info "MSC2108 : #{stream_id} : Received unknown event '#{event}'; #{data}"
129
+ end
130
+ end
131
+
132
+ unless ctx[:run]
133
+ socket.close
134
+ break
135
+ end
136
+ end
137
+ break unless ctx[:run]
138
+ end
139
+ end
140
+ # rubocop:enable Metrics/BlockLength
141
+
142
+ thread.run
143
+
144
+ [thread, cancellation_token]
145
+ end
146
+ # rubocop:enable Metrics/MethodLength
147
+ end
@@ -42,19 +42,11 @@ module MatrixSdk
42
42
  # The timeline events are what will end up in here
43
43
  attr_reader :id, :client, :topic, :aliases, :members, :events
44
44
 
45
- # @!attribute [r] on_event
46
- # @return [EventHandlerArray] The list of event handlers for all events
47
- # @!attribute [r] on_state_event
48
- # @return [EventHandlerArray] The list of event handlers for only state events
49
- # @!attribute [r] on_ephemeral_event
50
- # @return [EventHandlerArray] The list of event handlers for only ephemeral events
51
- events :event, :state_event, :ephemeral_event
52
45
  # @!method inspect
53
46
  # An inspect method that skips a handful of instance variables to avoid
54
47
  # flooding the terminal with debug data.
55
48
  # @return [String] a regular inspect string without the data for some variables
56
- ignore_inspect :client, :members, :events, :prev_batch, :logger,
57
- :on_event, :on_state_event, :on_ephemeral_event
49
+ ignore_inspect :client, :members, :events, :prev_batch, :logger
58
50
 
59
51
  alias room_id id
60
52
 
@@ -85,8 +77,6 @@ module MatrixSdk
85
77
  room_id = MXID.new room_id unless room_id.is_a?(MXID)
86
78
  raise ArgumentError, 'room_id must be a valid Room ID' unless room_id.room_id?
87
79
 
88
- event_initialize
89
-
90
80
  @name = nil
91
81
  @topic = nil
92
82
  @canonical_alias = nil
@@ -114,6 +104,30 @@ module MatrixSdk
114
104
  logger.debug "Created room #{room_id}"
115
105
  end
116
106
 
107
+
108
+ #
109
+ # Event handlers
110
+ #
111
+
112
+ # @!attribute [r] on_event
113
+ # @return [EventHandlerArray] The list of event handlers for all events
114
+ def on_event
115
+ ensure_room_handlers[:event]
116
+ end
117
+
118
+ # @!attribute [r] on_state_event
119
+ # @return [EventHandlerArray] The list of event handlers for only state events
120
+ def on_state_event
121
+ ensure_room_handlers[:state_event]
122
+ end
123
+
124
+ # @!attribute [r] on_ephemeral_event
125
+ # @return [EventHandlerArray] The list of event handlers for only ephemeral events
126
+ def on_ephemeral_event
127
+ ensure_room_handlers[:ephemeral_event]
128
+ end
129
+
130
+
117
131
  #
118
132
  # State readers
119
133
  #
@@ -157,6 +171,17 @@ module MatrixSdk
157
171
  members
158
172
  end
159
173
 
174
+ # Get all members (member events) in the room
175
+ #
176
+ # @note This will also count members who've knocked, been invited, have left, or have been banned.
177
+ #
178
+ # @param params [Hash] Additional query parameters to pass to the room member listing - e.g. for filtering purposes.
179
+ #
180
+ # @return [Array(User)] The complete list of members in the room, regardless of membership state
181
+ def all_members(**params)
182
+ client.api.get_room_members(id, **params)[:chunk].map { |ch| client.get_user(ch[:state_key]) }
183
+ end
184
+
160
185
  # Gets the current name of the room, querying the API if necessary
161
186
  #
162
187
  # @note Will cache the current name for 15 minutes
@@ -512,10 +537,10 @@ module MatrixSdk
512
537
  # @return [Boolean] if the name was changed or not
513
538
  def reload_name!
514
539
  data = begin
515
- client.api.get_room_name(id)
516
- rescue MatrixNotFoundError
517
- nil
518
- end
540
+ client.api.get_room_name(id)
541
+ rescue MatrixNotFoundError
542
+ nil
543
+ end
519
544
  changed = data[:name] != @name
520
545
  @name = data[:name] if changed
521
546
  changed
@@ -535,10 +560,10 @@ module MatrixSdk
535
560
  # @return [Boolean] if the topic was changed or not
536
561
  def reload_topic!
537
562
  data = begin
538
- client.api.get_room_topic(id)
539
- rescue MatrixNotFoundError
540
- nil
541
- end
563
+ client.api.get_room_topic(id)
564
+ rescue MatrixNotFoundError
565
+ nil
566
+ end
542
567
  changed = data[:topic] != @topic
543
568
  @topic = data[:topic] if changed
544
569
  changed
@@ -590,7 +615,7 @@ module MatrixSdk
590
615
  #
591
616
  # @param join_rule [:invite,:public] The join rule of the room
592
617
  def join_rule=(join_rule)
593
- client.api.set_join_rule(id, join_rule)
618
+ client.api.set_room_join_rules(id, join_rule)
594
619
  @join_rule = join_rule
595
620
  end
596
621
 
@@ -606,7 +631,7 @@ module MatrixSdk
606
631
  #
607
632
  # @param guest_access [:can_join,:forbidden] The new guest access status of the room
608
633
  def guest_access=(guest_access)
609
- client.api.set_guest_access(id, guest_access)
634
+ client.api.set_room_guest_access(id, guest_access)
610
635
  @guest_access = guest_access
611
636
  end
612
637
 
@@ -670,15 +695,35 @@ module MatrixSdk
670
695
  members << member unless members.any? { |m| m.id == member.id }
671
696
  end
672
697
 
698
+ def room_handlers?
699
+ client.instance_variable_get(:@room_handlers).key? id
700
+ end
701
+
702
+ def ensure_room_handlers
703
+ client.instance_variable_get(:@room_handlers)[id] ||= {
704
+ event: MatrixSdk::EventHandlerArray.new,
705
+ state_event: MatrixSdk::EventHandlerArray.new,
706
+ ephemeral_event: MatrixSdk::EventHandlerArray.new
707
+ }
708
+ end
709
+
673
710
  def put_event(event)
711
+ ensure_room_handlers[:event].fire(MatrixEvent.new(self, event), event[:type]) if room_handlers?
712
+
674
713
  @events.push event
675
714
  @events.shift if @events.length > @event_history_limit
676
-
677
- fire_event MatrixEvent.new(self, event)
678
715
  end
679
716
 
680
717
  def put_ephemeral_event(event)
681
- fire_ephemeral_event MatrixEvent.new(self, event)
718
+ return unless room_handlers?
719
+
720
+ ensure_room_handlers[:ephemeral_event].fire(MatrixEvent.new(self, event), event[:type])
721
+ end
722
+
723
+ def put_state_event(event)
724
+ return unless room_handlers?
725
+
726
+ ensure_room_handlers[:state_event].fire(MatrixEvent.new(self, event), event[:type])
682
727
  end
683
728
  end
684
729
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MatrixSdk
4
- VERSION = '2.0.0'
4
+ VERSION = '2.1.3'
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.0.0
4
+ version: 2.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Olofsson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-14 00:00:00.000000000 Z
11
+ date: 2020-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mocha
@@ -103,6 +103,7 @@ files:
103
103
  - lib/matrix_sdk/protocols/as.rb
104
104
  - lib/matrix_sdk/protocols/cs.rb
105
105
  - lib/matrix_sdk/protocols/is.rb
106
+ - lib/matrix_sdk/protocols/msc.rb
106
107
  - lib/matrix_sdk/protocols/ss.rb
107
108
  - lib/matrix_sdk/response.rb
108
109
  - lib/matrix_sdk/room.rb