matrix_sdk 1.5.0 → 2.1.2
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 +4 -4
- data/CHANGELOG.md +44 -0
- data/lib/matrix_sdk.rb +3 -0
- data/lib/matrix_sdk/api.rb +108 -35
- data/lib/matrix_sdk/client.rb +261 -30
- data/lib/matrix_sdk/extensions.rb +3 -0
- data/lib/matrix_sdk/mxid.rb +25 -2
- data/lib/matrix_sdk/protocols/cs.rb +729 -53
- data/lib/matrix_sdk/protocols/msc.rb +147 -0
- data/lib/matrix_sdk/response.rb +11 -0
- data/lib/matrix_sdk/room.rb +127 -15
- data/lib/matrix_sdk/user.rb +75 -8
- data/lib/matrix_sdk/version.rb +1 -1
- metadata +4 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 5af1e7af0f473a5c44df1ede83655cdbdb120e215e42f125b6af576740b88aa6
         | 
| 4 | 
            +
              data.tar.gz: cca80fe97ca22f0ddb0c5a2b39b24b70345baa25fb20bea305fa019f46ca7d3a
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 0e4c694d8489dae1b2602d01f4aab08b20119c6697e83b41a2a463adb7ebcaf7b1afd439db04a6e54a0979c5a93be865c8afa3d2d03b4ca34b98f82afae87a5f
         | 
| 7 | 
            +
              data.tar.gz: 8f52c12d1eb8af55ac5bc011852c9d5f648e9de1cc3241ea1ed76530036b02cff373cb69b8a4c721956add3df1359377d79b2fa2562e8dab8efe5ce877596e2d
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,47 @@ | |
| 1 | 
            +
            ## 2.1.2 - 2020-09-10
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            - Adds method for reading complete member lists for rooms, improves the CS spec adherence
         | 
| 4 | 
            +
            - Adds test for state events
         | 
| 5 | 
            +
            - Fixes state event handler for rooms not actually passing events
         | 
| 6 | 
            +
            - Fixes Api#new_for_domain using a faulty URI in certain cases
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            ## 2.1.1 - 2020-08-21
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            - Fixes crash if state event content is null (#11)
         | 
| 11 | 
            +
            - Fixes an uninitialized URI constant exception when requiring only the main library file
         | 
| 12 | 
            +
            - Fixes the Api#get_pushrules method missing an ending slash in the request URI
         | 
| 13 | 
            +
            - Fixes discovery code for client/server connections based on domain
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            ## 2.1.0 - 2020-05-22
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            - Adds unique query IDs as well as duration in API debug output, to make it easier to track long requests
         | 
| 18 | 
            +
            - Finishes up MSC support, get sync over SSE working flawlessly
         | 
| 19 | 
            +
            - Exposes the #listen_forever method in the client abstraction
         | 
| 20 | 
            +
            - Fixes room access methods
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            ## 2.0.1 - 2020-03-13
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            - Adds code for handling non-final MSC's in protocols
         | 
| 25 | 
            +
              - Currently implementing clients parts of MSC2018 for Sync over Server Sent Events
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ## 2.0.0 - 2020-02-14
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            **NB**, this release includes backwards-incompatible changes;  
         | 
| 30 | 
            +
            - Changes room state lookup to separate specific state lookups from full state retrieval.
         | 
| 31 | 
            +
              This will require changes in client code where `#get_room_state` is called to retrieve
         | 
| 32 | 
            +
              all state, as it now requires a state key. For retrieving full room state,
         | 
| 33 | 
            +
              `#get_room_state_all` is now the method to use.
         | 
| 34 | 
            +
            - Changes some advanced parameters to named parameters, ensure your code is updated if it makes use of them
         | 
| 35 | 
            +
            - Fixes SSL verification to actually verify certs (#9)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            - Adds multiple CS API endpoints
         | 
| 38 | 
            +
            - Adds `:room_id` key to all room events
         | 
| 39 | 
            +
            - Adds `:self` as a valid option to the client abstraction's `#get_user` method
         | 
| 40 | 
            +
            - Separates homeserver part stringification for MXIDs
         | 
| 41 | 
            +
            - Exposes some previously private client abstraction methods (`#ensure_room`, `#next_batch`) for easier bot usage
         | 
| 42 | 
            +
            - Changes room abstraction member lookups to use `#get_room_joined_members`, reducing transferred data amounts
         | 
| 43 | 
            +
            - Fixes debug print of methods that return arrays (e.g. CS `/room/{id}/state`)
         | 
| 44 | 
            +
             | 
| 1 45 | 
             
            ## 1.5.0 - 2019-10-25
         | 
| 2 46 |  | 
| 3 47 | 
             
            - Adds error event to the client abstraction, for handling errors in the background listener
         | 
    
        data/lib/matrix_sdk.rb
    CHANGED
    
    
    
        data/lib/matrix_sdk/api.rb
    CHANGED
    
    | @@ -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, : | 
| 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,24 +92,37 @@ 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?
         | 
| 99 | 
            +
                    # If the domain is fully qualified according to Matrix (FQDN and port) then skip discovery
         | 
| 101 100 | 
             
                    target_uri = URI("https://#{domain}:#{port}")
         | 
| 102 101 | 
             
                  elsif target == :server
         | 
| 103 102 | 
             
                    # Attempt SRV record discovery
         | 
| 104 103 | 
             
                    target_uri = begin
         | 
| 105 104 | 
             
                                   require 'resolv'
         | 
| 106 105 | 
             
                                   resolver = Resolv::DNS.new
         | 
| 107 | 
            -
                                    | 
| 108 | 
            -
             | 
| 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}"
         | 
| 109 112 | 
             
                                   nil
         | 
| 110 113 | 
             
                                 end
         | 
| 111 114 |  | 
| 112 115 | 
             
                    if target_uri.nil?
         | 
| 116 | 
            +
                      # Attempt .well-known discovery for server-to-server
         | 
| 113 117 | 
             
                      well_known = begin
         | 
| 114 | 
            -
                                      | 
| 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
         | 
| 115 123 | 
             
                                     JSON.parse(data)
         | 
| 116 | 
            -
                                   rescue StandardError
         | 
| 124 | 
            +
                                   rescue StandardError => e
         | 
| 125 | 
            +
                                     logger.debug "Well-known failed with #{e.class}: #{e.message}"
         | 
| 117 126 | 
             
                                     nil
         | 
| 118 127 | 
             
                                   end
         | 
| 119 128 |  | 
| @@ -124,9 +133,14 @@ module MatrixSdk | |
| 124 133 | 
             
                  elsif %i[client identity].include? target
         | 
| 125 134 | 
             
                    # Attempt .well-known discovery
         | 
| 126 135 | 
             
                    well_known = begin
         | 
| 127 | 
            -
                                    | 
| 128 | 
            -
                                    | 
| 129 | 
            -
             | 
| 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}"
         | 
| 130 144 | 
             
                                   nil
         | 
| 131 145 | 
             
                                 end
         | 
| 132 146 |  | 
| @@ -140,6 +154,7 @@ module MatrixSdk | |
| 140 154 | 
             
                      end
         | 
| 141 155 | 
             
                    end
         | 
| 142 156 | 
             
                  end
         | 
| 157 | 
            +
                  logger.debug "Using #{target_uri.inspect}"
         | 
| 143 158 |  | 
| 144 159 | 
             
                  # Fall back to direct domain connection
         | 
| 145 160 | 
             
                  target_uri ||= URI("https://#{domain}:8448")
         | 
| @@ -153,6 +168,29 @@ module MatrixSdk | |
| 153 168 | 
             
                      ))
         | 
| 154 169 | 
             
                end
         | 
| 155 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 | 
            +
             | 
| 186 | 
            +
                # Check if a protocol is enabled on the API connection
         | 
| 187 | 
            +
                #
         | 
| 188 | 
            +
                # @example Checking for identity server API support
         | 
| 189 | 
            +
                #   api.protocol? :IS
         | 
| 190 | 
            +
                #   # => false
         | 
| 191 | 
            +
                #
         | 
| 192 | 
            +
                # @param protocol [Symbol] The protocol to check
         | 
| 193 | 
            +
                # @return [Boolean] Is the protocol enabled
         | 
| 156 194 | 
             
                def protocol?(protocol)
         | 
| 157 195 | 
             
                  protocols.include? protocol
         | 
| 158 196 | 
             
                end
         | 
| @@ -201,6 +239,29 @@ module MatrixSdk | |
| 201 239 | 
             
                  @proxy_uri = proxy_uri
         | 
| 202 240 | 
             
                end
         | 
| 203 241 |  | 
| 242 | 
            +
                # Perform a raw Matrix API request
         | 
| 243 | 
            +
                #
         | 
| 244 | 
            +
                # @example Simple API query
         | 
| 245 | 
            +
                #   api.request(:get, :client_r0, '/account/whoami')
         | 
| 246 | 
            +
                #   # => { :user_id => "@alice:matrix.org" }
         | 
| 247 | 
            +
                #
         | 
| 248 | 
            +
                # @example Advanced API request
         | 
| 249 | 
            +
                #   api.request(:post,
         | 
| 250 | 
            +
                #               :media_r0,
         | 
| 251 | 
            +
                #               '/upload',
         | 
| 252 | 
            +
                #               body_stream: open('./file'),
         | 
| 253 | 
            +
                #               headers: { 'content-type' => 'image/png' })
         | 
| 254 | 
            +
                #   # => { :content_uri => "mxc://example.com/AQwafuaFswefuhsfAFAgsw" }
         | 
| 255 | 
            +
                #
         | 
| 256 | 
            +
                # @param method [Symbol] The method to use, can be any of the ones under Net::HTTP
         | 
| 257 | 
            +
                # @param api [Symbol] The API symbol to use, :client_r0 is the current CS one
         | 
| 258 | 
            +
                # @param path [String] The API path to call, this is the part that comes after the API definition in the spec
         | 
| 259 | 
            +
                # @param options [Hash] Additional options to pass along to the request
         | 
| 260 | 
            +
                # @option options [Hash] :query Query parameters to set on the URL
         | 
| 261 | 
            +
                # @option options [Hash,String] :body The body to attach to the request, will be JSON-encoded if sent as a hash
         | 
| 262 | 
            +
                # @option options [IO] :body_stream A body stream to attach to the request
         | 
| 263 | 
            +
                # @option options [Hash] :headers Additional headers to set on the request
         | 
| 264 | 
            +
                # @option options [Boolean] :skip_auth (false) Skip authentication
         | 
| 204 265 | 
             
                def request(method, api, path, **options)
         | 
| 205 266 | 
             
                  url = homeserver.dup.tap do |u|
         | 
| 206 267 | 
             
                    u.path = api_to_path(api) + path
         | 
| @@ -218,7 +279,7 @@ module MatrixSdk | |
| 218 279 | 
             
                    request.content_length = (request.body || request.body_stream).size
         | 
| 219 280 | 
             
                  end
         | 
| 220 281 |  | 
| 221 | 
            -
                  request['authorization'] = "Bearer #{access_token}" if access_token
         | 
| 282 | 
            +
                  request['authorization'] = "Bearer #{access_token}" if access_token && !options.fetch(:skip_auth, false)
         | 
| 222 283 | 
             
                  if options.key? :headers
         | 
| 223 284 | 
             
                    options[:headers].each do |h, v|
         | 
| 224 285 | 
             
                      request[h.to_s.downcase] = v
         | 
| @@ -229,14 +290,20 @@ module MatrixSdk | |
| 229 290 | 
             
                  loop do
         | 
| 230 291 | 
             
                    raise MatrixConnectionError, "Server still too busy to handle request after #{failures} attempts, try again later" if failures >= 10
         | 
| 231 292 |  | 
| 232 | 
            -
                     | 
| 293 | 
            +
                    req_id = ('A'..'Z').to_a.sample(4).join
         | 
| 294 | 
            +
             | 
| 295 | 
            +
                    print_http(request, id: req_id)
         | 
| 233 296 | 
             
                    begin
         | 
| 297 | 
            +
                      dur_start = Time.now
         | 
| 234 298 | 
             
                      response = http.request request
         | 
| 235 | 
            -
             | 
| 299 | 
            +
                      dur_end = Time.now
         | 
| 300 | 
            +
                      duration = dur_end - dur_start
         | 
| 301 | 
            +
                    rescue EOFError
         | 
| 236 302 | 
             
                      logger.error 'Socket closed unexpectedly'
         | 
| 237 | 
            -
                      raise | 
| 303 | 
            +
                      raise
         | 
| 238 304 | 
             
                    end
         | 
| 239 | 
            -
                    print_http(response)
         | 
| 305 | 
            +
                    print_http(response, duration: duration, id: req_id)
         | 
| 306 | 
            +
             | 
| 240 307 | 
             
                    data = JSON.parse(response.body, symbolize_names: true) rescue nil
         | 
| 241 308 |  | 
| 242 309 | 
             
                    if response.is_a? Net::HTTPTooManyRequests
         | 
| @@ -255,35 +322,41 @@ module MatrixSdk | |
| 255 322 | 
             
                  end
         | 
| 256 323 | 
             
                end
         | 
| 257 324 |  | 
| 325 | 
            +
                # Generate a transaction ID
         | 
| 326 | 
            +
                #
         | 
| 327 | 
            +
                # @return [String] An arbitrary transaction ID
         | 
| 328 | 
            +
                def transaction_id
         | 
| 329 | 
            +
                  ret = @transaction_id ||= 0
         | 
| 330 | 
            +
                  @transaction_id = @transaction_id.succ
         | 
| 331 | 
            +
                  ret
         | 
| 332 | 
            +
                end
         | 
| 333 | 
            +
             | 
| 258 334 | 
             
                private
         | 
| 259 335 |  | 
| 260 | 
            -
                def print_http(http)
         | 
| 336 | 
            +
                def print_http(http, body: true, duration: nil, id: nil)
         | 
| 261 337 | 
             
                  return unless logger.debug?
         | 
| 262 338 |  | 
| 263 339 | 
             
                  if http.is_a? Net::HTTPRequest
         | 
| 264 | 
            -
                    dir = '> | 
| 340 | 
            +
                    dir = "#{id ? id + ' : ' : nil}>"
         | 
| 265 341 | 
             
                    logger.debug "#{dir} Sending a #{http.method} request to `#{http.path}`:"
         | 
| 266 342 | 
             
                  else
         | 
| 267 | 
            -
                    dir = '< | 
| 268 | 
            -
                    logger.debug "#{dir} Received a #{http.code} #{http.message} response:"
         | 
| 343 | 
            +
                    dir = "#{id ? id + ' : ' : nil}<"
         | 
| 344 | 
            +
                    logger.debug "#{dir} Received a #{http.code} #{http.message} response:#{duration ? " [#{(duration * 1000).to_i}ms]" : nil}"
         | 
| 269 345 | 
             
                  end
         | 
| 270 346 | 
             
                  http.to_hash.map { |k, v| "#{k}: #{k == 'authorization' ? '[ REDACTED ]' : v.join(', ')}" }.each do |h|
         | 
| 271 347 | 
             
                    logger.debug "#{dir} #{h}"
         | 
| 272 348 | 
             
                  end
         | 
| 273 349 | 
             
                  logger.debug dir
         | 
| 274 | 
            -
                   | 
| 275 | 
            -
             | 
| 276 | 
            -
             | 
| 350 | 
            +
                  if body
         | 
| 351 | 
            +
                    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
         | 
| 353 | 
            +
                    clean_body = clean_body.to_s if clean_body
         | 
| 354 | 
            +
                    logger.debug "#{dir} #{clean_body.length < 200 ? clean_body : clean_body.slice(0..200) + "... [truncated, #{clean_body.length} Bytes]"}" if clean_body
         | 
| 355 | 
            +
                  end
         | 
| 277 356 | 
             
                rescue StandardError => e
         | 
| 278 357 | 
             
                  logger.warn "#{e.class} occured while printing request debug; #{e.message}\n#{e.backtrace.join "\n"}"
         | 
| 279 358 | 
             
                end
         | 
| 280 359 |  | 
| 281 | 
            -
                def transaction_id
         | 
| 282 | 
            -
                  ret = @transaction_id ||= 0
         | 
| 283 | 
            -
                  @transaction_id = @transaction_id.succ
         | 
| 284 | 
            -
                  ret
         | 
| 285 | 
            -
                end
         | 
| 286 | 
            -
             | 
| 287 360 | 
             
                def api_to_path(api)
         | 
| 288 361 | 
             
                  # TODO: <api>_current / <api>_latest
         | 
| 289 362 | 
             
                  "/_matrix/#{api.to_s.split('_').join('/')}"
         | 
| @@ -303,7 +376,7 @@ module MatrixSdk | |
| 303 376 | 
             
                  @http.open_timeout = open_timeout
         | 
| 304 377 | 
             
                  @http.read_timeout = read_timeout
         | 
| 305 378 | 
             
                  @http.use_ssl = homeserver.scheme == 'https'
         | 
| 306 | 
            -
                  @http.verify_mode = validate_certificate ? ::OpenSSL::SSL:: | 
| 379 | 
            +
                  @http.verify_mode = validate_certificate ? ::OpenSSL::SSL::VERIFY_PEER : ::OpenSSL::SSL::VERIFY_NONE
         | 
| 307 380 | 
             
                  @http.start
         | 
| 308 381 | 
             
                  @http
         | 
| 309 382 | 
             
                end
         | 
    
        data/lib/matrix_sdk/client.rb
    CHANGED
    
    | @@ -11,7 +11,16 @@ module MatrixSdk | |
| 11 11 | 
             
                include MatrixSdk::Logging
         | 
| 12 12 | 
             
                extend Forwardable
         | 
| 13 13 |  | 
| 14 | 
            -
                 | 
| 14 | 
            +
                # @!attribute api [r] The underlying API connection
         | 
| 15 | 
            +
                #   @return [Api] The underlying API connection
         | 
| 16 | 
            +
                # @!attribute next_batch [r] The batch token for a running sync
         | 
| 17 | 
            +
                #   @return [String] The opaque batch token
         | 
| 18 | 
            +
                # @!attribute cache [rw] The cache level
         | 
| 19 | 
            +
                #   @return [:all,:some,:none] The level of caching to do
         | 
| 20 | 
            +
                # @!attribute sync_filter [rw] The global sync filter
         | 
| 21 | 
            +
                #   @return [Hash,String] A filter definition, either as defined by the
         | 
| 22 | 
            +
                #           Matrix spec, or as an identifier returned by a filter creation request
         | 
| 23 | 
            +
                attr_reader :api, :next_batch
         | 
| 15 24 | 
             
                attr_accessor :cache, :sync_filter
         | 
| 16 25 |  | 
| 17 26 | 
             
                events :error, :event, :presence_event, :invite_event, :leave_event, :ephemeral_event
         | 
| @@ -22,9 +31,20 @@ module MatrixSdk | |
| 22 31 | 
             
                               :access_token, :access_token=, :device_id, :device_id=, :homeserver, :homeserver=,
         | 
| 23 32 | 
             
                               :validate_certificate, :validate_certificate=
         | 
| 24 33 |  | 
| 34 | 
            +
                # Create a new client instance from only a Matrix HS domain
         | 
| 35 | 
            +
                #
         | 
| 36 | 
            +
                # This will use the well-known delegation lookup to find the correct client URL
         | 
| 37 | 
            +
                #
         | 
| 38 | 
            +
                # @note This method will not verify that the created client has a valid connection,
         | 
| 39 | 
            +
                #       it will only perform the necessary lookups to build a connection URL.
         | 
| 40 | 
            +
                # @return [Client] The new client instance
         | 
| 41 | 
            +
                # @param domain [String] The domain name to look up
         | 
| 42 | 
            +
                # @param params [Hash] Additional parameters to pass along to {Api.new_for_domain} as well as {initialize}
         | 
| 43 | 
            +
                # @see Api.new_for_domain
         | 
| 44 | 
            +
                # @see #initialize
         | 
| 25 45 | 
             
                def self.new_for_domain(domain, **params)
         | 
| 26 46 | 
             
                  api = MatrixSdk::Api.new_for_domain(domain, keep_wellknown: true)
         | 
| 27 | 
            -
                  return new(api, params) unless api.well_known | 
| 47 | 
            +
                  return new(api, params) unless api.well_known&.key?('m.identity_server')
         | 
| 28 48 |  | 
| 29 49 | 
             
                  identity_server = MatrixSdk::Api.new(api.well_known['m.identity_server']['base_url'], protocols: %i[IS])
         | 
| 30 50 | 
             
                  new(api, params.merge(identity_server: identity_server))
         | 
| @@ -53,7 +73,7 @@ module MatrixSdk | |
| 53 73 | 
             
                  @rooms = {}
         | 
| 54 74 | 
             
                  @users = {}
         | 
| 55 75 | 
             
                  @cache = client_cache
         | 
| 56 | 
            -
                  @identity_server =  | 
| 76 | 
            +
                  @identity_server = nil
         | 
| 57 77 |  | 
| 58 78 | 
             
                  @sync_token = nil
         | 
| 59 79 | 
             
                  @sync_thread = nil
         | 
| @@ -75,22 +95,42 @@ module MatrixSdk | |
| 75 95 | 
             
                  @mxid = params[:user_id]
         | 
| 76 96 | 
             
                end
         | 
| 77 97 |  | 
| 98 | 
            +
                # Gets the currently logged in user's MXID
         | 
| 99 | 
            +
                #
         | 
| 100 | 
            +
                # @return [MXID] The MXID of the current user
         | 
| 78 101 | 
             
                def mxid
         | 
| 79 102 | 
             
                  @mxid ||= begin
         | 
| 80 | 
            -
                    api.whoami?[:user_id] if api&.access_token
         | 
| 103 | 
            +
                    MXID.new api.whoami?[:user_id] if api&.access_token
         | 
| 81 104 | 
             
                  end
         | 
| 82 105 | 
             
                end
         | 
| 83 106 |  | 
| 84 | 
            -
                 | 
| 85 | 
            -
                  id = MXID.new id.to_s unless id.is_a? MXID
         | 
| 86 | 
            -
                  raise ArgumentError, 'Must be a User ID' unless id.user?
         | 
| 107 | 
            +
                alias user_id mxid
         | 
| 87 108 |  | 
| 88 | 
            -
             | 
| 109 | 
            +
                # Gets the current user presence status object
         | 
| 110 | 
            +
                #
         | 
| 111 | 
            +
                # @return [Response] The user presence
         | 
| 112 | 
            +
                # @see User#presence
         | 
| 113 | 
            +
                # @see Protocols::CS#get_presence_status
         | 
| 114 | 
            +
                def presence
         | 
| 115 | 
            +
                  api.get_presence_status(mxid).tap { |h| h.delete :user_id }
         | 
| 89 116 | 
             
                end
         | 
| 90 117 |  | 
| 91 | 
            -
                 | 
| 92 | 
            -
                 | 
| 118 | 
            +
                # Sets the current user's presence status
         | 
| 119 | 
            +
                #
         | 
| 120 | 
            +
                # @param status [:online,:offline,:unavailable] The new status to use
         | 
| 121 | 
            +
                # @param message [String] A custom status message to set
         | 
| 122 | 
            +
                # @see User#presence=
         | 
| 123 | 
            +
                # @see Protocols::CS#set_presence_status
         | 
| 124 | 
            +
                def set_presence(status, message: nil)
         | 
| 125 | 
            +
                  raise ArgumentError, 'Presence must be one of :online, :offline, :unavailable' unless %i[online offline unavailable].include?(status)
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                  api.set_presence_status(mxid, status, message: message)
         | 
| 128 | 
            +
                end
         | 
| 93 129 |  | 
| 130 | 
            +
                # Gets a list of all the public rooms on the connected HS
         | 
| 131 | 
            +
                #
         | 
| 132 | 
            +
                # @note This will try to list all public rooms on the HS, and may take a while on larger instances
         | 
| 133 | 
            +
                # @return [Array[Room]] The public rooms
         | 
| 94 134 | 
             
                def public_rooms
         | 
| 95 135 | 
             
                  rooms = []
         | 
| 96 136 | 
             
                  since = nil
         | 
| @@ -114,6 +154,12 @@ module MatrixSdk | |
| 114 154 | 
             
                  rooms
         | 
| 115 155 | 
             
                end
         | 
| 116 156 |  | 
| 157 | 
            +
                # Gets a list of all relevant rooms, either the ones currently handled by
         | 
| 158 | 
            +
                # the client, or the list of currently joined ones if no rooms are handled
         | 
| 159 | 
            +
                #
         | 
| 160 | 
            +
                # @return [Array[Room]] All the currently handled rooms
         | 
| 161 | 
            +
                # @note This will always return the empty array if the cache level is set
         | 
| 162 | 
            +
                #       to :none
         | 
| 117 163 | 
             
                def rooms
         | 
| 118 164 | 
             
                  if @rooms.empty? && cache != :none
         | 
| 119 165 | 
             
                    api.get_joined_rooms.joined_rooms.each do |id|
         | 
| @@ -124,6 +170,11 @@ module MatrixSdk | |
| 124 170 | 
             
                  @rooms.values
         | 
| 125 171 | 
             
                end
         | 
| 126 172 |  | 
| 173 | 
            +
                # Refresh the list of currently handled rooms, replacing it with the user's
         | 
| 174 | 
            +
                # currently joined rooms.
         | 
| 175 | 
            +
                #
         | 
| 176 | 
            +
                # @note This will be a no-op if the cache level is set to :none
         | 
| 177 | 
            +
                # @return [Boolean] If the refresh succeeds
         | 
| 127 178 | 
             
                def reload_rooms!
         | 
| 128 179 | 
             
                  return true if cache == :none
         | 
| 129 180 |  | 
| @@ -137,12 +188,25 @@ module MatrixSdk | |
| 137 188 | 
             
                end
         | 
| 138 189 | 
             
                alias refresh_rooms! reload_rooms!
         | 
| 139 190 |  | 
| 191 | 
            +
                # Register - and log in - on the connected HS as a guest
         | 
| 192 | 
            +
                #
         | 
| 193 | 
            +
                # @note This feature is not commonly supported by many HSes
         | 
| 140 194 | 
             
                def register_as_guest
         | 
| 141 195 | 
             
                  data = api.register(kind: :guest)
         | 
| 142 196 | 
             
                  post_authentication(data)
         | 
| 143 197 | 
             
                end
         | 
| 144 198 |  | 
| 145 | 
            -
                 | 
| 199 | 
            +
                # Register a new user account on the connected HS
         | 
| 200 | 
            +
                #
         | 
| 201 | 
            +
                # This will also trigger an initial sync unless no_sync is set
         | 
| 202 | 
            +
                #
         | 
| 203 | 
            +
                # @note This method will currently always use auth type 'm.login.dummy'
         | 
| 204 | 
            +
                # @param username [String] The new user's name
         | 
| 205 | 
            +
                # @param password [String] The new user's password
         | 
| 206 | 
            +
                # @param params [Hash] Additional options
         | 
| 207 | 
            +
                # @option params [Boolean] :no_sync Skip the initial sync on registering
         | 
| 208 | 
            +
                # @option params [Boolean] :allow_sync_retry Allow sync to retry on failure
         | 
| 209 | 
            +
                def register_with_password(username, password, **params)
         | 
| 146 210 | 
             
                  username = username.to_s unless username.is_a?(String)
         | 
| 147 211 | 
             
                  password = password.to_s unless password.is_a?(String)
         | 
| 148 212 |  | 
| @@ -154,10 +218,21 @@ module MatrixSdk | |
| 154 218 |  | 
| 155 219 | 
             
                  return if params[:no_sync]
         | 
| 156 220 |  | 
| 157 | 
            -
                  sync full_state:  | 
| 221 | 
            +
                  sync full_state: true,
         | 
| 158 222 | 
             
                       allow_sync_retry: params.fetch(:allow_sync_retry, nil)
         | 
| 159 223 | 
             
                end
         | 
| 160 224 |  | 
| 225 | 
            +
                # Logs in as a user on the connected HS
         | 
| 226 | 
            +
                #
         | 
| 227 | 
            +
                # This will also trigger an initial sync unless no_sync is set
         | 
| 228 | 
            +
                #
         | 
| 229 | 
            +
                # @param username [String] The username of the user
         | 
| 230 | 
            +
                # @param password [String] The password of the user
         | 
| 231 | 
            +
                # @param sync_timeout [Numeric] The timeout of the initial sync on login
         | 
| 232 | 
            +
                # @param full_state [Boolean] Should the initial sync retrieve full state
         | 
| 233 | 
            +
                # @param params [Hash] Additional options
         | 
| 234 | 
            +
                # @option params [Boolean] :no_sync Skip the initial sync on registering
         | 
| 235 | 
            +
                # @option params [Boolean] :allow_sync_retry Allow sync to retry on failure
         | 
| 161 236 | 
             
                def login(username, password, sync_timeout: 15, full_state: false, **params)
         | 
| 162 237 | 
             
                  username = username.to_s unless username.is_a?(String)
         | 
| 163 238 | 
             
                  password = password.to_s unless password.is_a?(String)
         | 
| @@ -175,6 +250,17 @@ module MatrixSdk | |
| 175 250 | 
             
                       allow_sync_retry: params.fetch(:allow_sync_retry, nil)
         | 
| 176 251 | 
             
                end
         | 
| 177 252 |  | 
| 253 | 
            +
                # Logs in as a user on the connected HS
         | 
| 254 | 
            +
                #
         | 
| 255 | 
            +
                # This will also trigger an initial sync unless no_sync is set
         | 
| 256 | 
            +
                #
         | 
| 257 | 
            +
                # @param username [String] The username of the user
         | 
| 258 | 
            +
                # @param token [String] The token to log in with
         | 
| 259 | 
            +
                # @param sync_timeout [Numeric] The timeout of the initial sync on login
         | 
| 260 | 
            +
                # @param full_state [Boolean] Should the initial sync retrieve full state
         | 
| 261 | 
            +
                # @param params [Hash] Additional options
         | 
| 262 | 
            +
                # @option params [Boolean] :no_sync Skip the initial sync on registering
         | 
| 263 | 
            +
                # @option params [Boolean] :allow_sync_retry Allow sync to retry on failure
         | 
| 178 264 | 
             
                def login_with_token(username, token, sync_timeout: 15, full_state: false, **params)
         | 
| 179 265 | 
             
                  username = username.to_s unless username.is_a?(String)
         | 
| 180 266 | 
             
                  token = token.to_s unless token.is_a?(String)
         | 
| @@ -192,30 +278,90 @@ module MatrixSdk | |
| 192 278 | 
             
                       allow_sync_retry: params.fetch(:allow_sync_retry, nil)
         | 
| 193 279 | 
             
                end
         | 
| 194 280 |  | 
| 281 | 
            +
                # Logs out of the current session
         | 
| 195 282 | 
             
                def logout
         | 
| 196 283 | 
             
                  api.logout
         | 
| 197 284 | 
             
                  @api.access_token = nil
         | 
| 198 285 | 
             
                  @mxid = nil
         | 
| 199 286 | 
             
                end
         | 
| 200 287 |  | 
| 288 | 
            +
                # Check if there's a currently logged in session
         | 
| 289 | 
            +
                #
         | 
| 290 | 
            +
                # @note This will not check if the session is valid, only if it exists
         | 
| 291 | 
            +
                # @return [Boolean] If there's a current session
         | 
| 201 292 | 
             
                def logged_in?
         | 
| 202 | 
            -
                   | 
| 293 | 
            +
                  !@api.access_token.nil?
         | 
| 294 | 
            +
                end
         | 
| 295 | 
            +
             | 
| 296 | 
            +
                # Retrieve a list of all registered third-party IDs for the current user
         | 
| 297 | 
            +
                #
         | 
| 298 | 
            +
                # @return [Response] A response hash containing the key :threepids
         | 
| 299 | 
            +
                # @see Protocols::CS#get_3pids
         | 
| 300 | 
            +
                def registered_3pids
         | 
| 301 | 
            +
                  data = api.get_3pids
         | 
| 302 | 
            +
                  data.threepids.each do |obj|
         | 
| 303 | 
            +
                    obj.instance_eval do
         | 
| 304 | 
            +
                      def added_at
         | 
| 305 | 
            +
                        Time.at(self[:added_at] / 1000)
         | 
| 306 | 
            +
                      end
         | 
| 307 | 
            +
             | 
| 308 | 
            +
                      def validated_at
         | 
| 309 | 
            +
                        return unless validated?
         | 
| 310 | 
            +
             | 
| 311 | 
            +
                        Time.at(self[:validated_at] / 1000)
         | 
| 312 | 
            +
                      end
         | 
| 313 | 
            +
             | 
| 314 | 
            +
                      def validated?
         | 
| 315 | 
            +
                        key? :validated_at
         | 
| 316 | 
            +
                      end
         | 
| 317 | 
            +
             | 
| 318 | 
            +
                      def to_s
         | 
| 319 | 
            +
                        "#{self[:medium]}:#{self[:address]}"
         | 
| 320 | 
            +
                      end
         | 
| 321 | 
            +
             | 
| 322 | 
            +
                      def inspect
         | 
| 323 | 
            +
                        "#<MatrixSdk::Response 3pid=#{to_s.inspect} added_at=\"#{added_at}\"#{validated? ? " validated_at=\"#{validated_at}\"" : ''}>"
         | 
| 324 | 
            +
                      end
         | 
| 325 | 
            +
                    end
         | 
| 326 | 
            +
                  end
         | 
| 327 | 
            +
                  data
         | 
| 203 328 | 
             
                end
         | 
| 204 329 |  | 
| 330 | 
            +
                # Creates a new room
         | 
| 331 | 
            +
                #
         | 
| 332 | 
            +
                # @example Creating a room with an alias
         | 
| 333 | 
            +
                #   client.create_room('myroom')
         | 
| 334 | 
            +
                #   #<MatrixSdk::Room ... >
         | 
| 335 | 
            +
                #
         | 
| 336 | 
            +
                # @param room_alias [String] A default alias to set on the room, should only be the localpart
         | 
| 337 | 
            +
                # @return [Room] The resulting room
         | 
| 338 | 
            +
                # @see Protocols::CS#create_room
         | 
| 205 339 | 
             
                def create_room(room_alias = nil, **params)
         | 
| 206 340 | 
             
                  data = api.create_room(params.merge(room_alias: room_alias))
         | 
| 207 341 | 
             
                  ensure_room(data.room_id)
         | 
| 208 342 | 
             
                end
         | 
| 209 343 |  | 
| 344 | 
            +
                # Joins an already created room
         | 
| 345 | 
            +
                #
         | 
| 346 | 
            +
                # @param room_id_or_alias [String,MXID] A room alias (#room:example.com) or a room ID (!id:example.com)
         | 
| 347 | 
            +
                # @param server_name [Array[String]] A list of servers to attempt the join through, required for IDs
         | 
| 348 | 
            +
                # @return [Room] The resulting room
         | 
| 349 | 
            +
                # @see Protocols::CS#join_room
         | 
| 210 350 | 
             
                def join_room(room_id_or_alias, server_name: [])
         | 
| 211 351 | 
             
                  server_name = [server_name] unless server_name.is_a? Array
         | 
| 212 352 | 
             
                  data = api.join_room(room_id_or_alias, server_name: server_name)
         | 
| 213 353 | 
             
                  ensure_room(data.fetch(:room_id, room_id_or_alias))
         | 
| 214 354 | 
             
                end
         | 
| 215 355 |  | 
| 356 | 
            +
                # Find a room in the locally cached list of rooms that the current user is part of
         | 
| 357 | 
            +
                #
         | 
| 358 | 
            +
                # @param room_id_or_alias [String,MXID] A room ID or alias
         | 
| 359 | 
            +
                # @param only_canonical [Boolean] Only match alias against the canonical alias
         | 
| 360 | 
            +
                # @return [Room] The found room
         | 
| 361 | 
            +
                # @return [nil] If no room was found
         | 
| 216 362 | 
             
                def find_room(room_id_or_alias, only_canonical: false)
         | 
| 217 363 | 
             
                  room_id_or_alias = MXID.new(room_id_or_alias.to_s) unless room_id_or_alias.is_a? MXID
         | 
| 218 | 
            -
                  raise ArgumentError, 'Must be a room id or alias' unless  | 
| 364 | 
            +
                  raise ArgumentError, 'Must be a room id or alias' unless room_id_or_alias.room?
         | 
| 219 365 |  | 
| 220 366 | 
             
                  return @rooms.fetch(room_id_or_alias.to_s, nil) if room_id_or_alias.room_id?
         | 
| 221 367 |  | 
| @@ -224,7 +370,21 @@ module MatrixSdk | |
| 224 370 | 
             
                  @rooms.values.find { |r| r.aliases.include? room_id_or_alias.to_s }
         | 
| 225 371 | 
             
                end
         | 
| 226 372 |  | 
| 373 | 
            +
                # Get a User instance from a MXID
         | 
| 374 | 
            +
                #
         | 
| 375 | 
            +
                # @param user_id [String,MXID,:self] The MXID to look up, will also accept :self in order to get the currently logged-in user
         | 
| 376 | 
            +
                # @return [User] The User instance for the specified user
         | 
| 377 | 
            +
                # @raise [ArgumentError] If the input isn't a valid user ID
         | 
| 378 | 
            +
                # @note The method doesn't perform any existence checking, so the returned User object may point to a non-existent user
         | 
| 227 379 | 
             
                def get_user(user_id)
         | 
| 380 | 
            +
                  user_id = mxid if user_id == :self
         | 
| 381 | 
            +
             | 
| 382 | 
            +
                  user_id = MXID.new user_id.to_s unless user_id.is_a? MXID
         | 
| 383 | 
            +
                  raise ArgumentError, 'Must be a User ID' unless user_id.user?
         | 
| 384 | 
            +
             | 
| 385 | 
            +
                  # To still use regular string storage in the hash itself
         | 
| 386 | 
            +
                  user_id = user_id.to_s
         | 
| 387 | 
            +
             | 
| 228 388 | 
             
                  if cache == :all
         | 
| 229 389 | 
             
                    @users[user_id] ||= User.new(self, user_id)
         | 
| 230 390 | 
             
                  else
         | 
| @@ -232,10 +392,23 @@ module MatrixSdk | |
| 232 392 | 
             
                  end
         | 
| 233 393 | 
             
                end
         | 
| 234 394 |  | 
| 395 | 
            +
                # Remove a room alias
         | 
| 396 | 
            +
                #
         | 
| 397 | 
            +
                # @param room_alias [String,MXID] The room alias to remove
         | 
| 398 | 
            +
                # @see Protocols::CS#remove_room_alias
         | 
| 235 399 | 
             
                def remove_room_alias(room_alias)
         | 
| 400 | 
            +
                  room_alias = MXID.new room_alias.to_s unless room_alias.is_a? MXID
         | 
| 401 | 
            +
                  raise ArgumentError, 'Must be a room alias' unless room_alias.room_alias?
         | 
| 402 | 
            +
             | 
| 236 403 | 
             
                  api.remove_room_alias(room_alias)
         | 
| 237 404 | 
             
                end
         | 
| 238 405 |  | 
| 406 | 
            +
                # Upload a piece of data to the media repo
         | 
| 407 | 
            +
                #
         | 
| 408 | 
            +
                # @return [URI::MATRIX] A Matrix content (mxc://) URL pointing to the uploaded data
         | 
| 409 | 
            +
                # @param content [String] The data to upload
         | 
| 410 | 
            +
                # @param content_type [String] The MIME type of the data
         | 
| 411 | 
            +
                # @see Protocols::CS#media_upload
         | 
| 239 412 | 
             
                def upload(content, content_type)
         | 
| 240 413 | 
             
                  data = api.media_upload(content, content_type)
         | 
| 241 414 | 
             
                  return data[:content_uri] if data.key? :content_uri
         | 
| @@ -243,25 +416,72 @@ module MatrixSdk | |
| 243 416 | 
             
                  raise MatrixUnexpectedResponseError, 'Upload succeeded, but no media URI returned'
         | 
| 244 417 | 
             
                end
         | 
| 245 418 |  | 
| 419 | 
            +
                # Starts a background thread that will listen to new events
         | 
| 420 | 
            +
                #
         | 
| 421 | 
            +
                # @see sync For What parameters are accepted
         | 
| 246 422 | 
             
                def start_listener_thread(**params)
         | 
| 423 | 
            +
                  return if listening?
         | 
| 424 | 
            +
             | 
| 247 425 | 
             
                  @should_listen = true
         | 
| 248 | 
            -
                   | 
| 426 | 
            +
                  if api.protocol?(:MSC) && api.msc2108?
         | 
| 427 | 
            +
                    params[:filter] = sync_filter unless params.key? :filter
         | 
| 428 | 
            +
                    params[:filter] = params[:filter].to_json unless params[:filter].nil? || params[:filter].is_a?(String)
         | 
| 429 | 
            +
                    params[:since] = @next_batch if @next_batch
         | 
| 430 | 
            +
             | 
| 431 | 
            +
                    errors = 0
         | 
| 432 | 
            +
                    thread, cancel_token = api.msc2108_sync_sse(params) do |data, event:, id:|
         | 
| 433 | 
            +
                      @next_batch = id if id
         | 
| 434 | 
            +
                      if event.to_sym == :sync
         | 
| 435 | 
            +
                        handle_sync_response(data)
         | 
| 436 | 
            +
                        errors = 0
         | 
| 437 | 
            +
                      elsif event.to_sym == :sync_error
         | 
| 438 | 
            +
                        logger.error "SSE Sync error received; #{data.type}: #{data.message}"
         | 
| 439 | 
            +
                        errors += 1
         | 
| 440 | 
            +
             | 
| 441 | 
            +
                        # TODO: Allow configuring
         | 
| 442 | 
            +
                        raise 'Aborting due to excessive errors' if errors >= 5
         | 
| 443 | 
            +
                      end
         | 
| 444 | 
            +
                    end
         | 
| 445 | 
            +
             | 
| 446 | 
            +
                    @should_listen = cancel_token
         | 
| 447 | 
            +
                  else
         | 
| 448 | 
            +
                    thread = Thread.new { listen_forever(params) }
         | 
| 449 | 
            +
                  end
         | 
| 249 450 | 
             
                  @sync_thread = thread
         | 
| 250 451 | 
             
                  thread.run
         | 
| 251 452 | 
             
                end
         | 
| 252 453 |  | 
| 454 | 
            +
                # Stops the running background thread if one is active
         | 
| 253 455 | 
             
                def stop_listener_thread
         | 
| 254 456 | 
             
                  return unless @sync_thread
         | 
| 255 457 |  | 
| 256 | 
            -
                  @should_listen  | 
| 257 | 
            -
             | 
| 458 | 
            +
                  if @should_listen.is_a? Hash
         | 
| 459 | 
            +
                    @should_listen[:run] = false
         | 
| 460 | 
            +
                  else
         | 
| 461 | 
            +
                    @should_listen = false
         | 
| 462 | 
            +
                  end
         | 
| 463 | 
            +
                  if @sync_thread.alive?
         | 
| 464 | 
            +
                    ret = @sync_thread.join(2)
         | 
| 465 | 
            +
                    @sync_thread.kill unless ret
         | 
| 466 | 
            +
                  end
         | 
| 258 467 | 
             
                  @sync_thread = nil
         | 
| 259 468 | 
             
                end
         | 
| 260 469 |  | 
| 470 | 
            +
                # Check if there's a thread listening for events
         | 
| 261 471 | 
             
                def listening?
         | 
| 262 472 | 
             
                  @sync_thread&.alive? == true
         | 
| 263 473 | 
             
                end
         | 
| 264 474 |  | 
| 475 | 
            +
                # Run a message sync round, triggering events as necessary
         | 
| 476 | 
            +
                #
         | 
| 477 | 
            +
                # @param skip_store_batch [Boolean] Should this sync skip storing the returned next_batch token,
         | 
| 478 | 
            +
                #        doing this would mean the next sync re-runs from the same point. Useful with use of filters.
         | 
| 479 | 
            +
                # @param params [Hash] Additional options
         | 
| 480 | 
            +
                # @option params [String,Hash] :filter (#sync_filter) A filter to use for this sync
         | 
| 481 | 
            +
                # @option params [Numeric] :timeout (30) A timeout value in seconds for the sync request
         | 
| 482 | 
            +
                # @option params [Numeric] :allow_sync_retry (0) The number of retries allowed for this sync request
         | 
| 483 | 
            +
                # @option params [String] :since An override of the "since" token to provide to the sync request
         | 
| 484 | 
            +
                # @see Protocols::CS#sync
         | 
| 265 485 | 
             
                def sync(skip_store_batch: false, **params)
         | 
| 266 486 | 
             
                  extra_params = {
         | 
| 267 487 | 
             
                    filter: sync_filter,
         | 
| @@ -283,11 +503,26 @@ module MatrixSdk | |
| 283 503 | 
             
                  @next_batch = data[:next_batch] unless skip_store_batch
         | 
| 284 504 |  | 
| 285 505 | 
             
                  handle_sync_response(data)
         | 
| 506 | 
            +
                  true
         | 
| 286 507 | 
             
                end
         | 
| 287 508 |  | 
| 288 509 | 
             
                alias listen_for_events sync
         | 
| 289 510 |  | 
| 290 | 
            -
                 | 
| 511 | 
            +
                # Ensures that a room exists in the cache
         | 
| 512 | 
            +
                #
         | 
| 513 | 
            +
                # @param room_id [String,MXID] The room ID to ensure
         | 
| 514 | 
            +
                # @return [Room] The room object for the requested room
         | 
| 515 | 
            +
                def ensure_room(room_id)
         | 
| 516 | 
            +
                  room_id = MXID.new room_id.to_s unless room_id.is_a? MXID
         | 
| 517 | 
            +
                  raise ArgumentError, 'Must be a room ID' unless room_id.room_id?
         | 
| 518 | 
            +
             | 
| 519 | 
            +
                  room_id = room_id.to_s
         | 
| 520 | 
            +
                  @rooms.fetch(room_id) do
         | 
| 521 | 
            +
                    room = Room.new(self, room_id)
         | 
| 522 | 
            +
                    @rooms[room_id] = room unless cache == :none
         | 
| 523 | 
            +
                    room
         | 
| 524 | 
            +
                  end
         | 
| 525 | 
            +
                end
         | 
| 291 526 |  | 
| 292 527 | 
             
                def listen_forever(timeout: 30, bad_sync_timeout: 5, sync_interval: 30, **params)
         | 
| 293 528 | 
             
                  orig_bad_sync_timeout = bad_sync_timeout + 0
         | 
| @@ -312,6 +547,8 @@ module MatrixSdk | |
| 312 547 | 
             
                  fire_error(ErrorEvent.new(e, :listener_thread))
         | 
| 313 548 | 
             
                end
         | 
| 314 549 |  | 
| 550 | 
            +
                private
         | 
| 551 | 
            +
             | 
| 315 552 | 
             
                def post_authentication(data)
         | 
| 316 553 | 
             
                  @mxid = data[:user_id]
         | 
| 317 554 | 
             
                  @api.access_token = data[:access_token]
         | 
| @@ -320,19 +557,11 @@ module MatrixSdk | |
| 320 557 | 
             
                  access_token
         | 
| 321 558 | 
             
                end
         | 
| 322 559 |  | 
| 323 | 
            -
                def ensure_room(room_id)
         | 
| 324 | 
            -
                  room_id = room_id.to_s unless room_id.is_a? String
         | 
| 325 | 
            -
                  @rooms.fetch(room_id) do
         | 
| 326 | 
            -
                    room = Room.new(self, room_id)
         | 
| 327 | 
            -
                    @rooms[room_id] = room unless cache == :none
         | 
| 328 | 
            -
                    room
         | 
| 329 | 
            -
                  end
         | 
| 330 | 
            -
                end
         | 
| 331 | 
            -
             | 
| 332 560 | 
             
                def handle_state(room_id, state_event)
         | 
| 333 561 | 
             
                  return unless state_event.key? :type
         | 
| 334 562 |  | 
| 335 563 | 
             
                  room = ensure_room(room_id)
         | 
| 564 | 
            +
                  room.send :put_state_event, state_event
         | 
| 336 565 | 
             
                  content = state_event[:content]
         | 
| 337 566 | 
             
                  case state_event[:type]
         | 
| 338 567 | 
             
                  when 'm.room.name'
         | 
| @@ -346,9 +575,9 @@ module MatrixSdk | |
| 346 575 | 
             
                  when 'm.room.aliases'
         | 
| 347 576 | 
             
                    room.instance_variable_get('@aliases').concat content[:aliases]
         | 
| 348 577 | 
             
                  when 'm.room.join_rules'
         | 
| 349 | 
            -
                    room.instance_variable_set '@join_rule', content[:join_rule].to_sym
         | 
| 578 | 
            +
                    room.instance_variable_set '@join_rule', content[:join_rule].nil? ? nil : content[:join_rule].to_sym
         | 
| 350 579 | 
             
                  when 'm.room.guest_access'
         | 
| 351 | 
            -
                    room.instance_variable_set '@guest_access', content[:guest_access].to_sym
         | 
| 580 | 
            +
                    room.instance_variable_set '@guest_access', content[:guest_access].nil? ? nil : content[:guest_access].to_sym
         | 
| 352 581 | 
             
                  when 'm.room.member'
         | 
| 353 582 | 
             
                    return unless cache == :all
         | 
| 354 583 |  | 
| @@ -368,10 +597,12 @@ module MatrixSdk | |
| 368 597 | 
             
                  end
         | 
| 369 598 |  | 
| 370 599 | 
             
                  data[:rooms][:invite].each do |room_id, invite|
         | 
| 600 | 
            +
                    invite[:room_id] = room_id.to_s
         | 
| 371 601 | 
             
                    fire_invite_event(MatrixEvent.new(self, invite), room_id.to_s)
         | 
| 372 602 | 
             
                  end
         | 
| 373 603 |  | 
| 374 604 | 
             
                  data[:rooms][:leave].each do |room_id, left|
         | 
| 605 | 
            +
                    left[:room_id] = room_id.to_s
         | 
| 375 606 | 
             
                    fire_leave_event(MatrixEvent.new(self, left), room_id.to_s)
         | 
| 376 607 | 
             
                  end
         | 
| 377 608 |  | 
| @@ -387,7 +618,7 @@ module MatrixSdk | |
| 387 618 |  | 
| 388 619 | 
             
                    join[:timeline][:events].each do |event|
         | 
| 389 620 | 
             
                      event[:room_id] = room_id.to_s
         | 
| 390 | 
            -
                      handle_state(room_id, event)  | 
| 621 | 
            +
                      handle_state(room_id, event) if event.key? :state_key
         | 
| 391 622 | 
             
                      room.send :put_event, event
         | 
| 392 623 |  | 
| 393 624 | 
             
                      fire_event(MatrixEvent.new(self, event), event[:type])
         |