Class: MatrixSdk::Api
Constant Summary collapse
- USER_AGENT =
"Ruby Matrix SDK v#{MatrixSdk::VERSION}"
- DEFAULT_HEADERS =
{ 'accept' => 'application/json', 'user-agent' => USER_AGENT }.freeze
Instance Attribute Summary collapse
-
#access_token ⇒ Object
Returns the value of attribute access_token.
-
#autoretry ⇒ Object
Returns the value of attribute autoretry.
-
#connection_address ⇒ Object
Returns the value of attribute connection_address.
-
#connection_port ⇒ Object
Returns the value of attribute connection_port.
-
#device_id ⇒ Object
Returns the value of attribute device_id.
-
#global_headers ⇒ Object
Returns the value of attribute global_headers.
-
#homeserver ⇒ Object
Returns the value of attribute homeserver.
-
#open_timeout ⇒ Object
Returns the value of attribute open_timeout.
-
#proxy_uri ⇒ Object
Returns the value of attribute proxy_uri.
-
#read_timeout ⇒ Object
Returns the value of attribute read_timeout.
-
#validate_certificate ⇒ Object
Returns the value of attribute validate_certificate.
-
#well_known ⇒ Object
readonly
Returns the value of attribute well_known.
Class Method Summary collapse
-
.new_for_domain(domain, target: :client, keep_wellknown: false, ssl: true, **params) ⇒ API
Create an API connection to a domain entry.
Instance Method Summary collapse
-
#initialize(homeserver, **params) ⇒ Api
constructor
A new instance of Api.
-
#protocol?(protocol) ⇒ Boolean
Check if a protocol is enabled on the API connection.
-
#protocols ⇒ Symbol[]
Get a list of enabled protocols on the API client.
-
#request(method, api, path, **options) ⇒ Object
Perform a raw Matrix API request.
-
#transaction_id ⇒ String
Generate a transaction ID.
Methods included from Extensions
Methods included from Logging
Constructor Details
#initialize(homeserver, **params) ⇒ Api
Returns a new instance of Api.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/matrix_sdk/api.rb', line 42 def initialize(homeserver, **params) @homeserver = homeserver raise ArgumentError, 'Homeserver URL must be String or URI' unless @homeserver.is_a?(String) || @homeserver.is_a?(URI) @homeserver = URI.parse("#{'https://' unless @homeserver.start_with? 'http'}#{@homeserver}") unless @homeserver.is_a? URI @homeserver.path.gsub!(/\/?_matrix\/?/, '') if @homeserver.path =~ /_matrix\/?$/ raise ArgumentError, 'Please use the base URL for your HS (without /_matrix/)' if @homeserver.path.include? '/_matrix/' @proxy_uri = params.fetch(:proxy_uri, nil) @connection_address = params.fetch(:address, nil) @connection_port = params.fetch(:port, nil) @access_token = params.fetch(:access_token, nil) @device_id = params.fetch(:device_id, nil) @autoretry = params.fetch(:autoretry, true) @validate_certificate = params.fetch(:validate_certificate, false) @transaction_id = params.fetch(:transaction_id, 0) @backoff_time = params.fetch(:backoff_time, 5000) @open_timeout = params.fetch(:open_timeout, 60) @read_timeout = params.fetch(:read_timeout, 240) @well_known = params.fetch(:well_known, {}) @global_headers = DEFAULT_HEADERS.dup @global_headers.merge!(params.fetch(:global_headers)) if params.key? :global_headers @http = nil ([params.fetch(:protocols, [:CS])].flatten - protocols).each do |proto| self.class.include MatrixSdk::Protocols.const_get(proto) end login(user: @homeserver.user, password: @homeserver.password) if @homeserver.user && @homeserver.password && !@access_token && !params[:skip_login] && protocol?(:CS) @homeserver.userinfo = '' unless params[:skip_login] end |
Instance Attribute Details
#access_token ⇒ Object
Returns the value of attribute access_token.
21 22 23 |
# File 'lib/matrix_sdk/api.rb', line 21 def access_token @access_token end |
#autoretry ⇒ Object
Returns the value of attribute autoretry.
21 22 23 |
# File 'lib/matrix_sdk/api.rb', line 21 def autoretry @autoretry end |
#connection_address ⇒ Object
Returns the value of attribute connection_address.
21 22 23 |
# File 'lib/matrix_sdk/api.rb', line 21 def connection_address @connection_address end |
#connection_port ⇒ Object
Returns the value of attribute connection_port.
21 22 23 |
# File 'lib/matrix_sdk/api.rb', line 21 def connection_port @connection_port end |
#device_id ⇒ Object
Returns the value of attribute device_id.
21 22 23 |
# File 'lib/matrix_sdk/api.rb', line 21 def device_id @device_id end |
#global_headers ⇒ Object
Returns the value of attribute global_headers.
21 22 23 |
# File 'lib/matrix_sdk/api.rb', line 21 def global_headers @global_headers end |
#homeserver ⇒ Object
Returns the value of attribute homeserver.
22 23 24 |
# File 'lib/matrix_sdk/api.rb', line 22 def homeserver @homeserver end |
#open_timeout ⇒ Object
Returns the value of attribute open_timeout.
22 23 24 |
# File 'lib/matrix_sdk/api.rb', line 22 def open_timeout @open_timeout end |
#proxy_uri ⇒ Object
Returns the value of attribute proxy_uri.
22 23 24 |
# File 'lib/matrix_sdk/api.rb', line 22 def proxy_uri @proxy_uri end |
#read_timeout ⇒ Object
Returns the value of attribute read_timeout.
22 23 24 |
# File 'lib/matrix_sdk/api.rb', line 22 def read_timeout @read_timeout end |
#validate_certificate ⇒ Object
Returns the value of attribute validate_certificate.
22 23 24 |
# File 'lib/matrix_sdk/api.rb', line 22 def validate_certificate @validate_certificate end |
#well_known ⇒ Object (readonly)
Returns the value of attribute well_known.
22 23 24 |
# File 'lib/matrix_sdk/api.rb', line 22 def well_known @well_known end |
Class Method Details
.new_for_domain(domain, target: :client, keep_wellknown: false, ssl: true, **params) ⇒ API
Create an API connection to a domain entry
This will follow the server discovery spec for client-server and federation
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/matrix_sdk/api.rb', line 90 def self.new_for_domain(domain, target: :client, keep_wellknown: false, ssl: true, **params) domain, port = domain.split(':') uri = URI("http#{ssl ? 's' : ''}://#{domain}") well_known = nil target_uri = nil logger = ::Logging.logger[self] logger.debug "Resolving #{domain}" if !port.nil? && !port.empty? # If the domain is fully qualified according to Matrix (FQDN and port) then skip discovery target_uri = URI("https://#{domain}:#{port}") elsif target == :server # Attempt SRV record discovery target_uri = begin require 'resolv' resolver = Resolv::DNS.new srv = "_matrix._tcp.#{domain}" logger.debug "Trying DNS #{srv}..." d = resolver.getresource(srv, Resolv::DNS::Resource::IN::SRV) d rescue StandardError => e logger.debug "DNS lookup failed with #{e.class}: #{e.}" nil end if target_uri.nil? # Attempt .well-known discovery for server-to-server well_known = begin wk_uri = URI("https://#{domain}/.well-known/matrix/server") logger.debug "Trying #{wk_uri}..." data = Net::HTTP.start(wk_uri.host, wk_uri.port, use_ssl: true, open_timeout: 5, read_timeout: 5, write_timeout: 5) do |http| http.get(wk_uri.path).body end JSON.parse(data) rescue StandardError => e logger.debug "Well-known failed with #{e.class}: #{e.}" nil end target_uri = well_known['m.server'] if well_known&.key?('m.server') else target_uri = URI("https://#{target_uri.target}:#{target_uri.port}") end elsif %i[client identity].include? target # Attempt .well-known discovery well_known = begin wk_uri = URI("https://#{domain}/.well-known/matrix/client") logger.debug "Trying #{wk_uri}..." data = Net::HTTP.start(wk_uri.host, wk_uri.port, use_ssl: true, open_timeout: 5, read_timeout: 5, write_timeout: 5) do |http| http.get(wk_uri.path).body end data = JSON.parse(data) rescue StandardError => e logger.debug "Well-known failed with #{e.class}: #{e.}" nil end if well_known key = 'm.homeserver' key = 'm.identity_server' if target == :identity if well_known.key?(key) && well_known[key].key?('base_url') uri = URI(well_known[key]['base_url']) target_uri = uri end end end logger.debug "Using #{target_uri.inspect}" # Fall back to direct domain connection target_uri ||= URI("https://#{domain}:8448") params[:well_known] = well_known if keep_wellknown new(uri, params.merge( address: target_uri.host, port: target_uri.port )) end |
Instance Method Details
#protocol?(protocol) ⇒ Boolean
Check if a protocol is enabled on the API connection
194 195 196 |
# File 'lib/matrix_sdk/api.rb', line 194 def protocol?(protocol) protocols.include? protocol end |
#protocols ⇒ Symbol[]
Get a list of enabled protocols on the API client
178 179 180 181 182 183 184 |
# File 'lib/matrix_sdk/api.rb', line 178 def protocols self .class.included_modules .reject { |m| m&.name.nil? } .select { |m| m.name.start_with? 'MatrixSdk::Protocols::' } .map { |m| m.name.split('::').last.to_sym } end |
#request(method, api, path, **options) ⇒ Object
Perform a raw Matrix API request
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/matrix_sdk/api.rb', line 265 def request(method, api, path, **) url = homeserver.dup.tap do |u| u.path = api_to_path(api) + path u.query = [u.query, URI.encode_www_form(.fetch(:query))].flatten.compact.join('&') if [:query] u.query = nil if u.query.nil? || u.query.empty? end request = Net::HTTP.const_get(method.to_s.capitalize.to_sym).new url.request_uri request.body = [:body] if .key? :body request.body = request.body.to_json if .key?(:body) && !request.body.is_a?(String) request.body_stream = [:body_stream] if .key? :body_stream global_headers.each { |h, v| request[h] = v } if request.body || request.body_stream request.content_type = 'application/json' request.content_length = (request.body || request.body_stream).size end request['authorization'] = "Bearer #{access_token}" if access_token && !.fetch(:skip_auth, false) if .key? :headers [:headers].each do |h, v| request[h.to_s.downcase] = v end end failures = 0 loop do raise MatrixConnectionError, "Server still too busy to handle request after #{failures} attempts, try again later" if failures >= 10 req_id = ('A'..'Z').to_a.sample(4).join print_http(request, id: req_id) begin dur_start = Time.now response = http.request request dur_end = Time.now duration = dur_end - dur_start rescue EOFError logger.error 'Socket closed unexpectedly' raise end print_http(response, duration: duration, id: req_id) data = JSON.parse(response.body, symbolize_names: true) rescue nil if response.is_a? Net::HTTPTooManyRequests raise MatrixRequestError.new_by_code(data, response.code) unless autoretry failures += 1 waittime = data[:retry_after_ms] || data[:error][:retry_after_ms] || @backoff_time sleep(waittime.to_f / 1000.0) next end return MatrixSdk::Response.new self, data if response.is_a? Net::HTTPSuccess raise MatrixRequestError.new_by_code(data, response.code) if data raise MatrixConnectionError.class_by_code(response.code), response end end |
#transaction_id ⇒ String
Generate a transaction ID
328 329 330 331 332 |
# File 'lib/matrix_sdk/api.rb', line 328 def transaction_id ret = @transaction_id ||= 0 @transaction_id = @transaction_id.succ ret end |