LCOV - code coverage report
Current view: top level - lib/matrix_api_lite - matrix_api.dart (source / functions) Hit Total Coverage
Test: merged.info Lines: 52 72 72.2 %
Date: 2024-09-04 20:26:16 Functions: 0 0 -

          Line data    Source code
       1             : /* MIT License
       2             : *
       3             : * Copyright (C) 2019, 2020, 2021 Famedly GmbH
       4             : *
       5             : * Permission is hereby granted, free of charge, to any person obtaining a copy
       6             : * of this software and associated documentation files (the "Software"), to deal
       7             : * in the Software without restriction, including without limitation the rights
       8             : * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       9             : * copies of the Software, and to permit persons to whom the Software is
      10             : * furnished to do so, subject to the following conditions:
      11             : *
      12             : * The above copyright notice and this permission notice shall be included in all
      13             : * copies or substantial portions of the Software.
      14             : *
      15             : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      16             : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      17             : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      18             : * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      19             : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      20             : * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      21             : * SOFTWARE.
      22             : */
      23             : 
      24             : import 'dart:async';
      25             : import 'dart:convert';
      26             : import 'dart:typed_data';
      27             : 
      28             : import 'package:http/http.dart' as http;
      29             : 
      30             : import 'package:matrix/matrix_api_lite.dart';
      31             : import 'package:matrix/matrix_api_lite/generated/api.dart';
      32             : 
      33             : // ignore: constant_identifier_names
      34             : enum RequestType { GET, POST, PUT, DELETE }
      35             : 
      36             : class MatrixApi extends Api {
      37             :   /// The homeserver this client is communicating with.
      38          68 :   Uri? get homeserver => baseUri;
      39             : 
      40          68 :   set homeserver(Uri? uri) => baseUri = uri;
      41             : 
      42             :   /// This is the access token for the matrix client. When it is undefined, then
      43             :   /// the user needs to sign in first.
      44          64 :   String? get accessToken => bearerToken;
      45             : 
      46          64 :   set accessToken(String? token) => bearerToken = token;
      47             : 
      48           5 :   @override
      49             :   Never unexpectedResponse(http.BaseResponse response, Uint8List body) {
      50          20 :     if (response.statusCode >= 400 && response.statusCode < 500) {
      51          10 :       final resp = json.decode(utf8.decode(body));
      52           5 :       if (resp is Map<String, Object?>) {
      53           5 :         throw MatrixException.fromJson(resp);
      54             :       }
      55             :     }
      56           1 :     super.unexpectedResponse(response, body);
      57             :   }
      58             : 
      59           2 :   @override
      60             :   Never bodySizeExceeded(int expected, int actual) {
      61           2 :     throw EventTooLarge(expected, actual);
      62             :   }
      63             : 
      64          39 :   MatrixApi({
      65             :     Uri? homeserver,
      66             :     String? accessToken,
      67             :     super.httpClient,
      68          39 :   }) : super(baseUri: homeserver, bearerToken: accessToken);
      69             : 
      70             :   /// Used for all Matrix json requests using the [c2s API](https://matrix.org/docs/spec/client_server/r0.6.0.html).
      71             :   ///
      72             :   /// Throws: FormatException, MatrixException
      73             :   ///
      74             :   /// You must first set [this.homeserver] and for some endpoints also
      75             :   /// [this.accessToken] before you can use this! For example to send a
      76             :   /// message to a Matrix room with the id '!fjd823j:example.com' you call:
      77             :   /// ```
      78             :   /// final resp = await request(
      79             :   ///   RequestType.PUT,
      80             :   ///   '/r0/rooms/!fjd823j:example.com/send/m.room.message/$txnId',
      81             :   ///   data: {
      82             :   ///     'msgtype': 'm.text',
      83             :   ///     'body': 'hello'
      84             :   ///   }
      85             :   ///  );
      86             :   /// ```
      87             :   ///
      88          26 :   Future<Map<String, Object?>> request(
      89             :     RequestType type,
      90             :     String action, {
      91             :     dynamic data = '',
      92             :     String contentType = 'application/json',
      93             :     Map<String, Object?>? query,
      94             :   }) async {
      95          26 :     if (homeserver == null) {
      96             :       throw ('No homeserver specified.');
      97             :     }
      98             :     dynamic json;
      99          51 :     (data is! String) ? json = jsonEncode(data) : json = data;
     100          52 :     if (data is List<int> || action.startsWith('/media/v3/upload')) json = data;
     101             : 
     102          26 :     final url = homeserver!
     103          78 :         .resolveUri(Uri(path: '_matrix$action', queryParameters: query));
     104             : 
     105          26 :     final headers = <String, String>{};
     106          52 :     if (type == RequestType.PUT || type == RequestType.POST) {
     107          25 :       headers['Content-Type'] = contentType;
     108             :     }
     109          26 :     if (accessToken != null) {
     110          78 :       headers['Authorization'] = 'Bearer $accessToken';
     111             :     }
     112             : 
     113             :     late http.Response resp;
     114          26 :     Map<String, Object?>? jsonResp = <String, Object?>{};
     115             : 
     116             :     switch (type) {
     117          26 :       case RequestType.GET:
     118           8 :         resp = await httpClient.get(url, headers: headers);
     119             :         break;
     120          25 :       case RequestType.POST:
     121          50 :         resp = await httpClient.post(url, body: json, headers: headers);
     122             :         break;
     123           2 :       case RequestType.PUT:
     124           4 :         resp = await httpClient.put(url, body: json, headers: headers);
     125             :         break;
     126           0 :       case RequestType.DELETE:
     127           0 :         resp = await httpClient.delete(url, headers: headers);
     128             :         break;
     129             :     }
     130          26 :     var respBody = resp.body;
     131             :     try {
     132          52 :       respBody = utf8.decode(resp.bodyBytes);
     133             :     } catch (_) {
     134             :       // No-OP
     135             :     }
     136          52 :     if (resp.statusCode >= 500 && resp.statusCode < 600) {
     137           0 :       throw Exception(respBody);
     138             :     }
     139          52 :     var jsonString = String.fromCharCodes(respBody.runes);
     140          26 :     if (jsonString.startsWith('[') && jsonString.endsWith(']')) {
     141           0 :       jsonString = '{"chunk":$jsonString}';
     142             :     }
     143          26 :     jsonResp = jsonDecode(jsonString)
     144             :         as Map<String, Object?>?; // May throw FormatException
     145             : 
     146          52 :     if (resp.statusCode >= 400 && resp.statusCode < 500) {
     147           0 :       throw MatrixException(resp);
     148             :     }
     149             : 
     150             :     return jsonResp!;
     151             :   }
     152             : 
     153             :   /// Publishes end-to-end encryption keys for the device.
     154             :   /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-query
     155          24 :   Future<Map<String, int>> uploadKeys(
     156             :       {MatrixDeviceKeys? deviceKeys,
     157             :       Map<String, Object?>? oneTimeKeys,
     158             :       Map<String, Object?>? fallbackKeys}) async {
     159          24 :     final response = await request(
     160             :       RequestType.POST,
     161             :       '/client/v3/keys/upload',
     162          24 :       data: {
     163          10 :         if (deviceKeys != null) 'device_keys': deviceKeys.toJson(),
     164          24 :         if (oneTimeKeys != null) 'one_time_keys': oneTimeKeys,
     165          24 :         if (fallbackKeys != null) ...{
     166             :           'fallback_keys': fallbackKeys,
     167             :           'org.matrix.msc2732.fallback_keys': fallbackKeys,
     168             :         },
     169             :       },
     170             :     );
     171          48 :     return Map<String, int>.from(response['one_time_key_counts'] as Map);
     172             :   }
     173             : 
     174             :   /// This endpoint allows the creation, modification and deletion of pushers
     175             :   /// for this user ID. The behaviour of this endpoint varies depending on the
     176             :   /// values in the JSON body.
     177             :   ///
     178             :   /// See [deletePusher] to issue requests with `kind: null`.
     179             :   ///
     180             :   /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-pushers-set
     181           0 :   Future<void> postPusher(Pusher pusher, {bool? append}) async {
     182           0 :     final data = pusher.toJson();
     183             :     if (append != null) {
     184           0 :       data['append'] = append;
     185             :     }
     186           0 :     await request(
     187             :       RequestType.POST,
     188             :       '/client/v3/pushers/set',
     189             :       data: data,
     190             :     );
     191             :     return;
     192             :   }
     193             : 
     194             :   /// Variant of postPusher operation that deletes pushers by setting `kind: null`.
     195             :   ///
     196             :   /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-pushers-set
     197           0 :   Future<void> deletePusher(PusherId pusher) async {
     198           0 :     final data = PusherData.fromJson(pusher.toJson()).toJson();
     199           0 :     data['kind'] = null;
     200           0 :     await request(
     201             :       RequestType.POST,
     202             :       '/client/v3/pushers/set',
     203             :       data: data,
     204             :     );
     205             :     return;
     206             :   }
     207             : 
     208             :   /// This API provides credentials for the client to use when initiating
     209             :   /// calls.
     210           2 :   @override
     211             :   Future<TurnServerCredentials> getTurnServer() async {
     212           2 :     final json = await request(RequestType.GET, '/client/v3/voip/turnServer');
     213             : 
     214             :     // fix invalid responses from synapse
     215             :     // https://github.com/matrix-org/synapse/pull/10922
     216           2 :     final ttl = json['ttl'];
     217           2 :     if (ttl is double) {
     218           0 :       json['ttl'] = ttl.toInt();
     219             :     }
     220             : 
     221           2 :     return TurnServerCredentials.fromJson(json);
     222             :   }
     223             : 
     224           0 :   @Deprecated('Use [deleteRoomKeyBySessionId] instead')
     225             :   Future<RoomKeysUpdateResponse> deleteRoomKeysBySessionId(
     226             :       String roomId, String sessionId, String version) async {
     227           0 :     return deleteRoomKeyBySessionId(roomId, sessionId, version);
     228             :   }
     229             : 
     230           0 :   @Deprecated('Use [deleteRoomKeyBySessionId] instead')
     231             :   Future<RoomKeysUpdateResponse> putRoomKeysBySessionId(String roomId,
     232             :       String sessionId, String version, KeyBackupData data) async {
     233           0 :     return putRoomKeyBySessionId(roomId, sessionId, version, data);
     234             :   }
     235             : 
     236           0 :   @Deprecated('Use [getRoomKeyBySessionId] instead')
     237             :   Future<KeyBackupData> getRoomKeysBySessionId(
     238             :       String roomId, String sessionId, String version) async {
     239           0 :     return getRoomKeyBySessionId(roomId, sessionId, version);
     240             :   }
     241             : }
     242             : 
     243             : class EventTooLarge implements Exception {
     244             :   int maxSize, actualSize;
     245           2 :   EventTooLarge(this.maxSize, this.actualSize);
     246             : }

Generated by: LCOV version 1.14