matrix_sdk 2.0.0 → 2.1.3

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: 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