From bb10adef79b14d2c99d1491f5844629376cc5f59 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Mon, 28 Dec 2020 12:01:03 +0100 Subject: [PATCH] Initial commit --- .gitignore | 76 + .gitlab-ci.yml | 51 + CHANGELOG.md | 3 + CONTRIBUTING.md | 54 + LICENSE | 661 +++++ README.md | 22 + analysis_options.yaml | 14 + example/matrix_api_lite_example.dart | 9 + lib/matrix_api_lite.dart | 83 + lib/src/README.md | 0 lib/src/matrix_api.dart | 2126 ++++++++++++++++ lib/src/model/algorithm_types.dart | 27 + lib/src/model/auth/authentication_data.dart | 36 + .../model/auth/authentication_identifier.dart | 33 + .../model/auth/authentication_password.dart | 84 + .../auth/authentication_phone_identifier.dart | 42 + .../model/auth/authentication_recaptcha.dart | 42 + ...authentication_third_party_identifier.dart | 42 + .../auth/authentication_three_pid_creds.dart | 85 + lib/src/model/auth/authentication_token.dart | 45 + lib/src/model/auth/authentication_types.dart | 34 + .../auth/authentication_user_identifier.dart | 39 + lib/src/model/basic_event.dart | 38 + lib/src/model/basic_event_with_sender.dart | 39 + lib/src/model/basic_room_event.dart | 46 + lib/src/model/device.dart | 46 + lib/src/model/event_context.dart | 73 + lib/src/model/event_types.dart | 63 + .../secret_storage_default_key_content.dart | 22 + .../events/secret_storage_key_content.dart | 57 + lib/src/model/events/tombstone_content.dart | 23 + lib/src/model/events_sync_update.dart | 47 + lib/src/model/filter.dart | 222 ++ lib/src/model/keys_query_response.dart | 117 + lib/src/model/login_response.dart | 47 + lib/src/model/login_types.dart | 52 + lib/src/model/marked_unread.dart | 43 + .../model/matrix_connection_exception.dart | 26 + lib/src/model/matrix_event.dart | 72 + lib/src/model/matrix_exception.dart | 110 + lib/src/model/matrix_keys.dart | 115 + lib/src/model/message_types.dart | 31 + .../model/notifications_query_response.dart | 72 + .../model/one_time_keys_claim_response.dart | 38 + lib/src/model/open_graph_data.dart | 63 + lib/src/model/open_id_credentials.dart | 40 + lib/src/model/presence.dart | 32 + lib/src/model/presence_content.dart | 49 + lib/src/model/profile.dart | 44 + lib/src/model/public_rooms_response.dart | 97 + lib/src/model/push_rule_set.dart | 143 ++ lib/src/model/pusher.dart | 93 + lib/src/model/request_token_response.dart | 34 + lib/src/model/room_alias_informations.dart | 34 + lib/src/model/room_keys_info.dart | 69 + lib/src/model/room_keys_keys.dart | 101 + lib/src/model/room_summary.dart | 42 + lib/src/model/server_capabilities.dart | 89 + lib/src/model/stripped_state_event.dart | 39 + lib/src/model/supported_protocol.dart | 92 + lib/src/model/supported_versions.dart | 36 + lib/src/model/sync_update.dart | 333 +++ lib/src/model/tag.dart | 42 + lib/src/model/third_party_identifier.dart | 43 + lib/src/model/third_party_location.dart | 37 + lib/src/model/third_party_user.dart | 37 + lib/src/model/timeline_history_response.dart | 46 + lib/src/model/turn_server_credentials.dart | 40 + .../model/upload_key_signatures_response.dart | 55 + lib/src/model/user_search_result.dart | 41 + lib/src/model/well_known_informations.dart | 54 + lib/src/model/who_is_info.dart | 107 + lib/src/utils/logs.dart | 51 + lib/src/utils/try_get_map_extension.dart | 18 + pubspec.yaml | 15 + test/fake_matrix_api.dart | 2159 +++++++++++++++++ test/matrix_api_test.dart | 1915 +++++++++++++++ 77 files changed, 11097 insertions(+) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 example/matrix_api_lite_example.dart create mode 100644 lib/matrix_api_lite.dart create mode 100644 lib/src/README.md create mode 100644 lib/src/matrix_api.dart create mode 100644 lib/src/model/algorithm_types.dart create mode 100644 lib/src/model/auth/authentication_data.dart create mode 100644 lib/src/model/auth/authentication_identifier.dart create mode 100644 lib/src/model/auth/authentication_password.dart create mode 100644 lib/src/model/auth/authentication_phone_identifier.dart create mode 100644 lib/src/model/auth/authentication_recaptcha.dart create mode 100644 lib/src/model/auth/authentication_third_party_identifier.dart create mode 100644 lib/src/model/auth/authentication_three_pid_creds.dart create mode 100644 lib/src/model/auth/authentication_token.dart create mode 100644 lib/src/model/auth/authentication_types.dart create mode 100644 lib/src/model/auth/authentication_user_identifier.dart create mode 100644 lib/src/model/basic_event.dart create mode 100644 lib/src/model/basic_event_with_sender.dart create mode 100644 lib/src/model/basic_room_event.dart create mode 100644 lib/src/model/device.dart create mode 100644 lib/src/model/event_context.dart create mode 100644 lib/src/model/event_types.dart create mode 100644 lib/src/model/events/secret_storage_default_key_content.dart create mode 100644 lib/src/model/events/secret_storage_key_content.dart create mode 100644 lib/src/model/events/tombstone_content.dart create mode 100644 lib/src/model/events_sync_update.dart create mode 100644 lib/src/model/filter.dart create mode 100644 lib/src/model/keys_query_response.dart create mode 100644 lib/src/model/login_response.dart create mode 100644 lib/src/model/login_types.dart create mode 100644 lib/src/model/marked_unread.dart create mode 100644 lib/src/model/matrix_connection_exception.dart create mode 100644 lib/src/model/matrix_event.dart create mode 100644 lib/src/model/matrix_exception.dart create mode 100644 lib/src/model/matrix_keys.dart create mode 100644 lib/src/model/message_types.dart create mode 100644 lib/src/model/notifications_query_response.dart create mode 100644 lib/src/model/one_time_keys_claim_response.dart create mode 100644 lib/src/model/open_graph_data.dart create mode 100644 lib/src/model/open_id_credentials.dart create mode 100644 lib/src/model/presence.dart create mode 100644 lib/src/model/presence_content.dart create mode 100644 lib/src/model/profile.dart create mode 100644 lib/src/model/public_rooms_response.dart create mode 100644 lib/src/model/push_rule_set.dart create mode 100644 lib/src/model/pusher.dart create mode 100644 lib/src/model/request_token_response.dart create mode 100644 lib/src/model/room_alias_informations.dart create mode 100644 lib/src/model/room_keys_info.dart create mode 100644 lib/src/model/room_keys_keys.dart create mode 100644 lib/src/model/room_summary.dart create mode 100644 lib/src/model/server_capabilities.dart create mode 100644 lib/src/model/stripped_state_event.dart create mode 100644 lib/src/model/supported_protocol.dart create mode 100644 lib/src/model/supported_versions.dart create mode 100644 lib/src/model/sync_update.dart create mode 100644 lib/src/model/tag.dart create mode 100644 lib/src/model/third_party_identifier.dart create mode 100644 lib/src/model/third_party_location.dart create mode 100644 lib/src/model/third_party_user.dart create mode 100644 lib/src/model/timeline_history_response.dart create mode 100644 lib/src/model/turn_server_credentials.dart create mode 100644 lib/src/model/upload_key_signatures_response.dart create mode 100644 lib/src/model/user_search_result.dart create mode 100644 lib/src/model/well_known_informations.dart create mode 100644 lib/src/model/who_is_info.dart create mode 100644 lib/src/utils/logs.dart create mode 100644 lib/src/utils/try_get_map_extension.dart create mode 100644 pubspec.yaml create mode 100644 test/fake_matrix_api.dart create mode 100644 test/matrix_api_test.dart diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0ab205ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,76 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +native/ +test/.test_coverage.dart +coverage/ +coverage_badge.svg + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# Visual Studio Code related +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ +pubspec.lock + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..b324a26a --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,51 @@ +stages: + - coverage + - builddocs + - deploy + +test: + tags: + - linux + stage: coverage + image: google/dart + script: + - pub get + - pub run test + +code_analyze: + tags: + - docker + stage: coverage + image: cirrusci/flutter + dependencies: [] + script: + - flutter format lib/ test/ test_driver/ --set-exit-if-changed + - flutter analyze + +build_api_doc: + tags: + - docker + stage: builddocs + image: cirrusci/flutter + script: + - dartdoc --exclude "dart:async,dart:collection,dart:convert,dart:core,dart:developer,dart:io,dart:isolate,dart:math,dart:typed_data,dart:ui" + artifacts: + paths: + - doc/api/ + only: + - main + +pages: + tags: + - linux + stage: deploy + image: alpine:latest + script: + - mv doc/api/ public + dependencies: + - build_api_doc + artifacts: + paths: + - public + only: + - main \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..687440ba --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version, created by Stagehand diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a5c23f7a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,54 @@ +# Contributing code to famedly talk + +Everyone is welcome to contribute code to FamedlySDK, provided that they are willing to license their contributions under the same license as the project itself. +Please follow these rules when contributing code to famedly talk: + +## Merge Requests: +- Never ever just push something directly to the master branch! +- Create a new branch or fork this project and send a Merge Request. +- Only Merge Requests with a working CI can be merged. +- Only Merge Requests with at least one code reviewer can be merged. +- Merge Requests may be refused if they don't follow the rules below. +- A new Merge Request SHOULD never decrease the test coverage. + +## Branches +### Naming + +Branches should get named by this pattern: `[Module Name]-[Type]-[Detail]`. + +That means for example: "users-fix-attach-roles-issue#765". + +Modules are various parts of the App. This can for example be the directory list or the chat room. + +Types can be one of these: +- **feature** +- **enhance** +- **cleanup** +- **refactor** +- **fix** +- **hotfix** (should rarely get used) + +The Detail part of the pattern should be a short description of what the branch delivers. + +## Commit Messages + +Commit Messages should get written in this pattern: `[[Module Name]] [Commit Message]`. +That means for example: "[users] add fetch users endpoint". + + +## File structure: +- Every file must be named by the class and must be capitalized in the beginning. +- Directories need to be lowercase. + +## Code style: +Please use code formatting. You can use VSCode or Android Studio. On other editors you need to run: +``` +flutter format lib/**/*/*.dart +``` + +## Code quality: +- Don't repeat yourself! Use local variables, functions, classes. +- Don't mix UI and business logic in the same environment. +- Write tests for new classes, functions and widgets. +- Keep it simple stupid: https://en.wikipedia.org/wiki/KISS_principle +- Describe all of your classes, methods and attributes using **dartdoc** comments. Read this for more informations: https://dart.dev/guides/language/effective-dart/documentation \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..096ad991 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + famedlySDK + Copyright (C) 2019 famedly + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..1b0a69b6 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +A library for Dart developers. + +Created from templates made available by Stagehand under a BSD-style +[license](https://github.com/dart-lang/stagehand/blob/master/LICENSE). + +## Usage + +A simple usage example: + +```dart +import 'package:matrix_api_lite/matrix_api_lite.dart'; + +main() { + var awesome = new Awesome(); +} +``` + +## Features and bugs + +Please file feature requests and bugs at the [issue tracker][tracker]. + +[tracker]: http://example.com/issues/replaceme diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..a686c1b4 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,14 @@ +# Defines a default set of lint rules enforced for +# projects at Google. For details and rationale, +# see https://github.com/dart-lang/pedantic#enabled-lints. +include: package:pedantic/analysis_options.yaml + +# For lint rules and documentation, see http://dart-lang.github.io/linter/lints. +# Uncomment to specify additional rules. +# linter: +# rules: +# - camel_case_types + +analyzer: +# exclude: +# - path/to/excluded/files/** diff --git a/example/matrix_api_lite_example.dart b/example/matrix_api_lite_example.dart new file mode 100644 index 00000000..c588cf51 --- /dev/null +++ b/example/matrix_api_lite_example.dart @@ -0,0 +1,9 @@ +//import 'package:matrix_api_lite/matrix_api_lite.dart'; + +import 'package:matrix_api_lite/src/matrix_api.dart'; + +void main() async { + final api = MatrixApi(homeserver: Uri.parse('https://matrix.org')); + final capabilities = await api.requestServerCapabilities(); + print(capabilities.toJson()); +} diff --git a/lib/matrix_api_lite.dart b/lib/matrix_api_lite.dart new file mode 100644 index 00000000..d90c0a1b --- /dev/null +++ b/lib/matrix_api_lite.dart @@ -0,0 +1,83 @@ +/* + * Matrix API Lite + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +library matrix_api_lite; + +export 'src/matrix_api.dart'; +export 'src/utils/logs.dart'; +export 'src/utils/try_get_map_extension.dart'; +export 'src/model/algorithm_types.dart'; +export 'src/model/basic_event.dart'; +export 'src/model/basic_event_with_sender.dart'; +export 'src/model/basic_room_event.dart'; +export 'src/model/device.dart'; +export 'src/model/event_context.dart'; +export 'src/model/event_types.dart'; +export 'src/model/events_sync_update.dart'; +export 'src/model/filter.dart'; +export 'src/model/keys_query_response.dart'; +export 'src/model/login_response.dart'; +export 'src/model/login_types.dart'; +export 'src/model/matrix_connection_exception.dart'; +export 'src/model/matrix_event.dart'; +export 'src/model/matrix_exception.dart'; +export 'src/model/matrix_keys.dart'; +export 'src/model/message_types.dart'; +export 'src/model/notifications_query_response.dart'; +export 'src/model/one_time_keys_claim_response.dart'; +export 'src/model/open_graph_data.dart'; +export 'src/model/open_id_credentials.dart'; +export 'src/model/presence.dart'; +export 'src/model/presence_content.dart'; +export 'src/model/profile.dart'; +export 'src/model/public_rooms_response.dart'; +export 'src/model/push_rule_set.dart'; +export 'src/model/pusher.dart'; +export 'src/model/request_token_response.dart'; +export 'src/model/room_alias_informations.dart'; +export 'src/model/room_keys_info.dart'; +export 'src/model/room_keys_keys.dart'; +export 'src/model/room_summary.dart'; +export 'src/model/server_capabilities.dart'; +export 'src/model/stripped_state_event.dart'; +export 'src/model/supported_protocol.dart'; +export 'src/model/supported_versions.dart'; +export 'src/model/sync_update.dart'; +export 'src/model/tag.dart'; +export 'src/model/third_party_identifier.dart'; +export 'src/model/third_party_location.dart'; +export 'src/model/third_party_user.dart'; +export 'src/model/timeline_history_response.dart'; +export 'src/model/turn_server_credentials.dart'; +export 'src/model/upload_key_signatures_response.dart'; +export 'src/model/user_search_result.dart'; +export 'src/model/well_known_informations.dart'; +export 'src/model/who_is_info.dart'; +export 'src/model/auth/authentication_data.dart'; +export 'src/model/auth/authentication_identifier.dart'; +export 'src/model/auth/authentication_password.dart'; +export 'src/model/auth/authentication_phone_identifier.dart'; +export 'src/model/auth/authentication_recaptcha.dart'; +export 'src/model/auth/authentication_third_party_identifier.dart'; +export 'src/model/auth/authentication_three_pid_creds.dart'; +export 'src/model/auth/authentication_token.dart'; +export 'src/model/auth/authentication_types.dart'; +export 'src/model/auth/authentication_user_identifier.dart'; +export 'src/model/events/secret_storage_default_key_content.dart'; +export 'src/model/events/secret_storage_key_content.dart'; +export 'src/model/events/tombstone_content.dart'; diff --git a/lib/src/README.md b/lib/src/README.md new file mode 100644 index 00000000..e69de29b diff --git a/lib/src/matrix_api.dart b/lib/src/matrix_api.dart new file mode 100644 index 00000000..a1c055c4 --- /dev/null +++ b/lib/src/matrix_api.dart @@ -0,0 +1,2126 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:http/http.dart' as http; +import 'package:mime/mime.dart'; + +import 'model/auth/authentication_data.dart'; +import 'model/auth/authentication_types.dart'; +import 'model/device.dart'; +import 'model/event_context.dart'; +import 'model/events_sync_update.dart'; +import 'model/filter.dart'; +import 'model/keys_query_response.dart'; +import 'model/login_response.dart'; +import 'model/login_types.dart'; +import 'model/matrix_connection_exception.dart'; +import 'model/matrix_event.dart'; +import 'model/matrix_exception.dart'; +import 'model/matrix_keys.dart'; +import 'model/notifications_query_response.dart'; +import 'model/one_time_keys_claim_response.dart'; +import 'model/open_graph_data.dart'; +import 'model/open_id_credentials.dart'; +import 'model/presence_content.dart'; +import 'model/profile.dart'; +import 'model/public_rooms_response.dart'; +import 'model/push_rule_set.dart'; +import 'model/pusher.dart'; +import 'model/request_token_response.dart'; +import 'model/room_alias_informations.dart'; +import 'model/room_keys_info.dart'; +import 'model/room_keys_keys.dart'; +import 'model/server_capabilities.dart'; +import 'model/supported_protocol.dart'; +import 'model/supported_versions.dart'; +import 'model/sync_update.dart'; +import 'model/tag.dart'; +import 'model/third_party_identifier.dart'; +import 'model/third_party_location.dart'; +import 'model/third_party_user.dart'; +import 'model/timeline_history_response.dart'; +import 'model/turn_server_credentials.dart'; +import 'model/upload_key_signatures_response.dart'; +import 'model/user_search_result.dart'; +import 'model/well_known_informations.dart'; +import 'model/who_is_info.dart'; + +enum RequestType { GET, POST, PUT, DELETE } +enum IdServerUnbindResult { success, no_success } +enum ThirdPartyIdentifierMedium { email, msisdn } +enum Membership { join, invite, leave, ban } +enum Direction { b, f } +enum Visibility { public, private } +enum CreateRoomPreset { private_chat, public_chat, trusted_private_chat } + +String describeEnum(Object enumEntry) { + final description = enumEntry.toString(); + final indexOfDot = description.indexOf('.'); + assert(indexOfDot != -1 && indexOfDot < description.length - 1); + return description.substring(indexOfDot + 1); +} + +class MatrixApi { + /// The homeserver this client is communicating with. + Uri homeserver; + + /// This is the access token for the matrix client. When it is undefined, then + /// the user needs to sign in first. + String accessToken; + + /// Matrix synchronisation is done with https long polling. This needs a + /// timeout which is usually 30 seconds. + int syncTimeoutSec; + + http.Client httpClient = http.Client(); + + bool get _testMode => + homeserver.toString() == 'https://fakeserver.notexisting'; + + int _timeoutFactor = 1; + + MatrixApi({ + this.homeserver, + this.accessToken, + http.Client httpClient, + this.syncTimeoutSec = 30, + }) { + if (httpClient != null) { + this.httpClient = httpClient; + } + } + + /// Used for all Matrix json requests using the [c2s API](https://matrix.org/docs/spec/client_server/r0.6.0.html). + /// + /// Throws: TimeoutException, FormatException, MatrixException + /// + /// You must first set [this.homeserver] and for some endpoints also + /// [this.accessToken] before you can use this! For example to send a + /// message to a Matrix room with the id '!fjd823j:example.com' you call: + /// ``` + /// final resp = await request( + /// RequestType.PUT, + /// '/r0/rooms/!fjd823j:example.com/send/m.room.message/$txnId', + /// data: { + /// 'msgtype': 'm.text', + /// 'body': 'hello' + /// } + /// ); + /// ``` + /// + Future> request( + RequestType type, + String action, { + dynamic data = '', + int timeout, + String contentType = 'application/json', + Map query, + }) async { + if (homeserver == null) { + throw ('No homeserver specified.'); + } + timeout ??= (_timeoutFactor * syncTimeoutSec) + 5; + dynamic json; + (!(data is String)) ? json = jsonEncode(data) : json = data; + if (data is List || action.startsWith('/media/r0/upload')) json = data; + + final queryPart = query?.entries + ?.where((x) => x.value != null) + ?.map((x) => [x.key, x.value].map(Uri.encodeQueryComponent).join('=')) + ?.join('&'); + final url = ['${homeserver.toString()}/_matrix${action}', queryPart] + .where((x) => x != null && x != '') + .join('?'); + + var headers = {}; + if (type == RequestType.PUT || type == RequestType.POST) { + headers['Content-Type'] = contentType; + } + if (accessToken != null) { + headers['Authorization'] = 'Bearer ${accessToken}'; + } + + http.Response resp; + var jsonResp = {}; + try { + switch (describeEnum(type)) { + case 'GET': + resp = await httpClient.get(url, headers: headers).timeout( + Duration(seconds: timeout), + ); + break; + case 'POST': + resp = + await httpClient.post(url, body: json, headers: headers).timeout( + Duration(seconds: timeout), + ); + break; + case 'PUT': + resp = + await httpClient.put(url, body: json, headers: headers).timeout( + Duration(seconds: timeout), + ); + break; + case 'DELETE': + resp = await httpClient.delete(url, headers: headers).timeout( + Duration(seconds: timeout), + ); + break; + } + var respBody = resp.body; + try { + respBody = utf8.decode(resp.bodyBytes); + } catch (_) { + // No-OP + } + if (resp.statusCode >= 500 && resp.statusCode < 600) { + throw Exception(respBody); + } + var jsonString = String.fromCharCodes(respBody.runes); + if (jsonString.startsWith('[') && jsonString.endsWith(']')) { + jsonString = '\{"chunk":$jsonString\}'; + } + jsonResp = jsonDecode(jsonString) + as Map; // May throw FormatException + + _timeoutFactor = 1; + } on TimeoutException catch (e, s) { + _timeoutFactor *= 2; + throw MatrixConnectionException(e, s); + } catch (e, s) { + throw MatrixConnectionException(e, s); + } + if (resp.statusCode >= 400 && resp.statusCode < 500) { + throw MatrixException(resp); + } + + return jsonResp; + } + + /// Gets the versions of the specification supported by the server. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-versions + Future requestSupportedVersions() async { + final response = await request( + RequestType.GET, + '/client/versions', + ); + return SupportedVersions.fromJson(response); + } + + /// Gets discovery information about the domain. The file may include additional keys. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-well-known-matrix-client + Future requestWellKnownInformations() async { + var baseUrl = homeserver.toString(); + if (baseUrl.endsWith('/')) { + baseUrl = baseUrl.substring(0, baseUrl.length - 1); + } + final response = await httpClient.get('$baseUrl/.well-known/matrix/client'); + final rawJson = json.decode(response.body); + return WellKnownInformations.fromJson(rawJson); + } + + Future requestLoginTypes() async { + final response = await request( + RequestType.GET, + '/client/r0/login', + ); + return LoginTypes.fromJson(response); + } + + /// Authenticates the user, and issues an access token they can use to authorize themself in subsequent requests. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-login + Future login({ + String type = AuthenticationTypes.password, + String userIdentifierType, + String user, + String medium, + String address, + String password, + String token, + String deviceId, + String initialDeviceDisplayName, + AuthenticationData auth, + }) async { + final response = await request(RequestType.POST, '/client/r0/login', data: { + 'type': type, + if (userIdentifierType != null) + 'identifier': { + 'type': userIdentifierType, + if (user != null) 'user': user, + }, + if (user != null) 'user': user, + if (medium != null) 'medium': medium, + if (address != null) 'address': address, + if (password != null) 'password': password, + if (token != null) 'token': token, + if (deviceId != null) 'device_id': deviceId, + if (initialDeviceDisplayName != null) + 'initial_device_display_name': initialDeviceDisplayName, + if (auth != null) 'auth': auth.toJson(), + }); + return LoginResponse.fromJson(response); + } + + /// Invalidates an existing access token, so that it can no longer be used for authorization. + /// The device associated with the access token is also deleted. Device keys for the device + /// are deleted alongside the device. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-logout + Future logout() async { + await request( + RequestType.POST, + '/client/r0/logout', + ); + return; + } + + /// Invalidates all access tokens for a user, so that they can no longer be used + /// for authorization. This includes the access token that made this request. All + /// devices for the user are also deleted. Device keys for the device are + /// deleted alongside the device. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-logout-all + Future logoutAll() async { + await request( + RequestType.POST, + '/client/r0/logout/all', + ); + return; + } + + /// Register for an account on this homeserver. + /// + /// There are two kinds of user account: + /// + /// user accounts. These accounts may use the full API described in this + /// specification. + /// guest accounts. These accounts may have limited permissions and may not + /// be supported by all servers. + /// + /// If registration is successful, this endpoint will issue an access token + /// the client can use to authorize itself in subsequent requests. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-register + Future register({ + String username, + String password, + String deviceId, + String initialDeviceDisplayName, + bool inhibitLogin, + AuthenticationData auth, + String kind, + }) async { + var action = '/client/r0/register'; + if (kind != null) action += '?kind=${Uri.encodeQueryComponent(kind)}'; + final response = await request(RequestType.POST, action, data: { + if (username != null) 'username': username, + if (password != null) 'password': password, + if (deviceId != null) 'device_id': deviceId, + if (initialDeviceDisplayName != null) + 'initial_device_display_name': initialDeviceDisplayName, + if (inhibitLogin != null) 'inhibit_login': inhibitLogin, + if (auth != null) 'auth': auth.toJson(), + }); + return LoginResponse.fromJson(response); + } + + /// The homeserver must check that the given email address is not already associated + /// with an account on this homeserver. The homeserver should validate the email + /// itself, either by sending a validation email itself or by using a service it + /// has control over. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-register-email-requesttoken + Future requestEmailToken( + String email, + String clientSecret, + int sendAttempt, { + String nextLink, + String idServer, + String idAccessToken, + }) async { + final response = await request( + RequestType.POST, '/client/r0/register/email/requestToken', + data: { + 'email': email, + 'send_attempt': sendAttempt, + 'client_secret': clientSecret, + if (nextLink != null) 'next_link': nextLink, + if (idServer != null) 'id_server': idServer, + if (idAccessToken != null) 'id_access_token': idAccessToken, + }); + return RequestTokenResponse.fromJson(response); + } + + /// The homeserver must check that the given phone number is not already associated with an + /// account on this homeserver. The homeserver should validate the phone number itself, + /// either by sending a validation message itself or by using a service it has control over. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-register-msisdn-requesttoken + Future requestMsisdnToken( + String country, + String phoneNumber, + String clientSecret, + int sendAttempt, { + String nextLink, + String idServer, + String idAccessToken, + }) async { + final response = await request( + RequestType.POST, '/client/r0/register/msisdn/requestToken', + data: { + 'country': country, + 'phone_number': phoneNumber, + 'send_attempt': sendAttempt, + 'client_secret': clientSecret, + if (nextLink != null) 'next_link': nextLink, + if (idServer != null) 'id_server': idServer, + if (idAccessToken != null) 'id_access_token': idAccessToken, + }); + return RequestTokenResponse.fromJson(response); + } + + /// Changes the password for an account on this homeserver. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-password + Future changePassword( + String newPassword, { + AuthenticationData auth, + }) async { + await request(RequestType.POST, '/client/r0/account/password', data: { + 'new_password': newPassword, + if (auth != null) 'auth': auth.toJson(), + }); + return; + } + + /// The homeserver must check that the given email address is associated with + /// an account on this homeserver. This API should be used to request + /// validation tokens when authenticating for the /account/password endpoint. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-password-email-requesttoken + Future resetPasswordUsingEmail( + String email, + String clientSecret, + int sendAttempt, { + String nextLink, + String idServer, + String idAccessToken, + }) async { + final response = await request( + RequestType.POST, '/client/r0/account/password/email/requestToken', + data: { + 'email': email, + 'send_attempt': sendAttempt, + 'client_secret': clientSecret, + if (nextLink != null) 'next_link': nextLink, + if (idServer != null) 'id_server': idServer, + if (idAccessToken != null) 'id_access_token': idAccessToken, + }); + return RequestTokenResponse.fromJson(response); + } + + /// The homeserver must check that the given phone number is associated with + /// an account on this homeserver. This API should be used to request validation + /// tokens when authenticating for the /account/password endpoint. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-password-msisdn-requesttoken + Future resetPasswordUsingMsisdn( + String country, + String phoneNumber, + String clientSecret, + int sendAttempt, { + String nextLink, + String idServer, + String idAccessToken, + }) async { + final response = await request( + RequestType.POST, '/client/r0/account/password/msisdn/requestToken', + data: { + 'country': country, + 'phone_number': phoneNumber, + 'send_attempt': sendAttempt, + 'client_secret': clientSecret, + if (nextLink != null) 'next_link': nextLink, + if (idServer != null) 'id_server': idServer, + if (idAccessToken != null) 'id_access_token': idAccessToken, + }); + return RequestTokenResponse.fromJson(response); + } + + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-deactivate + Future deactivateAccount({ + String idServer, + AuthenticationData auth, + }) async { + final response = + await request(RequestType.POST, '/client/r0/account/deactivate', data: { + if (idServer != null) 'id_server': idServer, + if (auth != null) 'auth': auth.toJson(), + }); + + return IdServerUnbindResult.values.firstWhere( + (i) => describeEnum(i) == response['id_server_unbind_result'], + ); + } + + Future usernameAvailable(String username) async { + final response = await request( + RequestType.GET, + '/client/r0/register/available?username=$username', + ); + return response['available']; + } + + /// Gets a list of the third party identifiers that the homeserver has + /// associated with the user's account. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-register-available + Future> requestThirdPartyIdentifiers() async { + final response = await request( + RequestType.GET, + '/client/r0/account/3pid', + ); + return (response['threepids'] as List) + .map((item) => ThirdPartyIdentifier.fromJson(item)) + .toList(); + } + + /// Adds contact information to the user's account. Homeservers + /// should use 3PIDs added through this endpoint for password resets + /// instead of relying on the identity server. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-3pid-add + Future addThirdPartyIdentifier( + String clientSecret, + String sid, { + AuthenticationData auth, + }) async { + await request(RequestType.POST, '/client/r0/account/3pid/add', data: { + 'sid': sid, + 'client_secret': clientSecret, + if (auth != null) 'auth': auth.toJson(), + }); + return; + } + + /// Binds a 3PID to the user's account through the specified identity server. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-3pid-bind + Future bindThirdPartyIdentifier( + String clientSecret, + String sid, + String idServer, + String idAccessToken, + ) async { + await request(RequestType.POST, '/client/r0/account/3pid/bind', data: { + 'sid': sid, + 'client_secret': clientSecret, + 'id_server': idServer, + 'id_access_token': idAccessToken, + }); + return; + } + + /// Removes a third party identifier from the user's account. This might not cause an unbind of the identifier from the identity server. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-3pid-delete + Future deleteThirdPartyIdentifier( + String address, + ThirdPartyIdentifierMedium medium, { + String idServer, + }) async { + final response = await request( + RequestType.POST, '/client/r0/account/3pid/delete', + data: { + 'address': address, + 'medium': describeEnum(medium), + if (idServer != null) 'id_server': idServer, + }); + return IdServerUnbindResult.values.firstWhere( + (i) => describeEnum(i) == response['id_server_unbind_result'], + ); + } + + /// Removes a user's third party identifier from the provided identity server without removing it from the homeserver. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-3pid-unbind + Future unbindThirdPartyIdentifier( + String address, + ThirdPartyIdentifierMedium medium, + String idServer, + ) async { + final response = await request( + RequestType.POST, '/client/r0/account/3pid/unbind', + data: { + 'address': address, + 'medium': describeEnum(medium), + 'id_server': idServer, + }); + return IdServerUnbindResult.values.firstWhere( + (i) => describeEnum(i) == response['id_server_unbind_result'], + ); + } + + /// This API should be used to request validation tokens when adding an email address to an account. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-3pid-email-requesttoken + Future requestEmailValidationToken( + String email, + String clientSecret, + int sendAttempt, { + String nextLink, + String idServer, + String idAccessToken, + }) async { + final response = await request( + RequestType.POST, '/client/r0/account/3pid/email/requestToken', + data: { + 'email': email, + 'send_attempt': sendAttempt, + 'client_secret': clientSecret, + if (nextLink != null) 'next_link': nextLink, + if (idServer != null) 'id_server': idServer, + if (idAccessToken != null) 'id_access_token': idAccessToken, + }); + return RequestTokenResponse.fromJson(response); + } + + /// This API should be used to request validation tokens when adding a phone number to an account. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-3pid-msisdn-requesttoken + Future requestMsisdnValidationToken( + String country, + String phoneNumber, + String clientSecret, + int sendAttempt, { + String nextLink, + String idServer, + String idAccessToken, + }) async { + final response = await request( + RequestType.POST, '/client/r0/account/3pid/msisdn/requestToken', + data: { + 'country': country, + 'phone_number': phoneNumber, + 'send_attempt': sendAttempt, + 'client_secret': clientSecret, + if (nextLink != null) 'next_link': nextLink, + if (idServer != null) 'id_server': idServer, + if (idAccessToken != null) 'id_access_token': idAccessToken, + }); + return RequestTokenResponse.fromJson(response); + } + + /// Gets information about the owner of a given access token. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-account-whoami + Future whoAmI() async { + final response = await request( + RequestType.GET, + '/client/r0/account/whoami', + ); + return response['user_id']; + } + + /// Gets information about the server's supported feature set and other relevant capabilities. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-capabilities + Future requestServerCapabilities() async { + final response = await request( + RequestType.GET, + '/client/r0/capabilities', + ); + return ServerCapabilities.fromJson(response['capabilities']); + } + + /// Uploads a new filter definition to the homeserver. Returns a filter ID that may be used + /// in future requests to restrict which events are returned to the client. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-user-userid-filter + Future uploadFilter( + String userId, + Filter filter, + ) async { + final response = await request( + RequestType.POST, + '/client/r0/user/${Uri.encodeComponent(userId)}/filter', + data: filter.toJson(), + ); + return response['filter_id']; + } + + /// Download a filter + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-user-userid-filter + Future downloadFilter(String userId, String filterId) async { + final response = await request( + RequestType.GET, + '/client/r0/user/${Uri.encodeComponent(userId)}/filter/${Uri.encodeComponent(filterId)}', + ); + return Filter.fromJson(response); + } + + /// Synchronise the client's state with the latest state on the server. Clients use this API when + /// they first log in to get an initial snapshot of the state on the server, and then continue to + /// call this API to get incremental deltas to the state, and to receive new messages. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-sync + Future sync({ + String filter, + String since, + bool fullState, + PresenceType setPresence, + int timeout, + }) async { + final response = await request( + RequestType.GET, + '/client/r0/sync', + query: { + if (filter != null) 'filter': filter, + if (since != null) 'since': since, + if (fullState != null) 'full_state': fullState.toString(), + if (setPresence != null) 'set_presence': describeEnum(setPresence), + if (timeout != null) 'timeout': timeout.toString(), + }, + ); + return SyncUpdate.fromJson(response); + } + + /// Get a single event based on roomId/eventId. You must have permission to + /// retrieve this event e.g. by being a member in the room for this event. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-event-eventid + Future requestEvent(String roomId, String eventId) async { + final response = await request( + RequestType.GET, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/event/${Uri.encodeComponent(eventId)}', + ); + return MatrixEvent.fromJson(response); + } + + /// Looks up the contents of a state event in a room. If the user is joined to the room then the + /// state is taken from the current state of the room. If the user has left the room then the + /// state is taken from the state of the room when they left. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey + Future> requestStateContent( + String roomId, String eventType, + [String stateKey]) async { + var url = + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/state/${Uri.encodeComponent(eventType)}/'; + if (stateKey != null) { + url += Uri.encodeComponent(stateKey); + } + final response = await request( + RequestType.GET, + url, + ); + return response; + } + + /// Get the state events for the current state of a room. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-state + Future> requestStates(String roomId) async { + final response = await request( + RequestType.GET, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/state', + ); + return (response['chunk'] as List) + .map((i) => MatrixEvent.fromJson(i)) + .toList(); + } + + /// Get the list of members for this room. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-members + Future> requestMembers( + String roomId, { + String at, + Membership membership, + Membership notMembership, + }) async { + final response = await request( + RequestType.GET, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/members', + query: { + if (at != null) 'at': at, + if (membership != null) 'membership': describeEnum(membership), + if (notMembership != null) + 'not_membership': describeEnum(notMembership), + }, + ); + return (response['chunk'] as List) + .map((i) => MatrixEvent.fromJson(i)) + .toList(); + } + + /// This API returns a map of MXIDs to member info objects for members of the room. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-joined-members + Future> requestJoinedMembers(String roomId) async { + final response = await request( + RequestType.GET, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/joined_members', + ); + return (response['joined'] as Map).map( + (k, v) => MapEntry(k, Profile.fromJson(v)), + ); + } + + /// This API returns a list of message and state events for a room. It uses pagination query + /// parameters to paginate history in the room. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-messages + Future requestMessages( + String roomId, + String from, + Direction dir, { + String to, + int limit, + String filter, + }) async { + final response = await request(RequestType.GET, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/messages', + query: { + 'from': from, + 'dir': describeEnum(dir), + if (to != null) 'to': to, + if (limit != null) 'limit': limit.toString(), + if (filter != null) 'filter': filter, + }); + return TimelineHistoryResponse.fromJson(response); + } + + /// State events can be sent using this endpoint. + /// https://matrix.org/docs/spec/client_server/r0.6.0#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey + Future sendState( + String roomId, + String eventType, + Map content, [ + String stateKey = '', + ]) async { + final response = await request(RequestType.PUT, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/state/${Uri.encodeComponent(eventType)}/${Uri.encodeComponent(stateKey)}', + data: content); + return response['event_id']; + } + + /// This endpoint is used to send a message event to a room. + /// Message events allow access to historical events and pagination, + /// making them suited for "once-off" activity in a room. + /// https://matrix.org/docs/spec/client_server/r0.6.0#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid + Future sendMessage( + String roomId, + String eventType, + String txnId, + Map content, + ) async { + final response = await request(RequestType.PUT, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/send/${Uri.encodeComponent(eventType)}/${Uri.encodeComponent(txnId)}', + data: content); + return response['event_id']; + } + + /// Strips all information out of an event which isn't critical to the integrity of + /// the server-side representation of the room. + /// https://matrix.org/docs/spec/client_server/r0.6.0#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid + Future redact( + String roomId, + String eventId, + String txnId, { + String reason, + }) async { + final response = await request(RequestType.PUT, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/redact/${Uri.encodeComponent(eventId)}/${Uri.encodeComponent(txnId)}', + data: { + if (reason != null) 'reason': reason, + }); + return response['event_id']; + } + + Future createRoom({ + Visibility visibility, + String roomAliasName, + String name, + String topic, + List invite, + List> invite3pid, + String roomVersion, + Map creationContent, + List> initialState, + CreateRoomPreset preset, + bool isDirect, + Map powerLevelContentOverride, + }) async { + final response = + await request(RequestType.POST, '/client/r0/createRoom', data: { + if (visibility != null) 'visibility': describeEnum(visibility), + if (roomAliasName != null) 'room_alias_name': roomAliasName, + if (name != null) 'name': name, + if (topic != null) 'topic': topic, + if (invite != null) 'invite': invite, + if (invite3pid != null) 'invite_3pid': invite3pid, + if (roomVersion != null) 'room_version': roomVersion, + if (creationContent != null) 'creation_content': creationContent, + if (initialState != null) 'initial_state': initialState, + if (preset != null) 'preset': describeEnum(preset), + if (isDirect != null) 'is_direct': isDirect, + if (powerLevelContentOverride != null) + 'power_level_content_override': powerLevelContentOverride, + }); + return response['room_id']; + } + + /// Create a new mapping from room alias to room ID. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-directory-room-roomalias + Future createRoomAlias(String alias, String roomId) async { + await request( + RequestType.PUT, + '/client/r0/directory/room/${Uri.encodeComponent(alias)}', + data: {'room_id': roomId}, + ); + return; + } + + /// Requests that the server resolve a room alias to a room ID. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-directory-room-roomalias + Future requestRoomAliasInformations( + String alias) async { + final response = await request( + RequestType.GET, + '/client/r0/directory/room/${Uri.encodeComponent(alias)}', + ); + return RoomAliasInformations.fromJson(response); + } + + /// Remove a mapping of room alias to room ID. + /// https://matrix.org/docs/spec/client_server/r0.6.1#delete-matrix-client-r0-directory-room-roomalias + Future removeRoomAlias(String alias) async { + await request( + RequestType.DELETE, + '/client/r0/directory/room/${Uri.encodeComponent(alias)}', + ); + return; + } + + /// Get a list of aliases maintained by the local server for the given room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-aliases + Future> requestRoomAliases(String roomId) async { + final response = await request( + RequestType.GET, + '/client/r0/room/${Uri.encodeComponent(roomId)}/aliases', + ); + return List.from(response['aliases']); + } + + /// This API returns a list of the user's current rooms. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-joined-rooms + Future> requestJoinedRooms() async { + final response = await request( + RequestType.GET, + '/client/r0/joined_rooms', + ); + return List.from(response['joined_rooms']); + } + + /// This API invites a user to participate in a particular room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-invite + Future inviteToRoom(String roomId, String userId) async { + await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/invite', + data: { + 'user_id': userId, + }, + ); + return; + } + + /// This API starts a user participating in a particular room, if that user is allowed to participate in that room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-invite + Future joinRoom( + String roomId, { + String thirdPidSignedSender, + String thirdPidSignedmxid, + String thirdPidSignedToken, + Map thirdPidSignedSiganture, + }) async { + final response = await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/join', + data: { + if (thirdPidSignedSiganture != null) + 'third_party_signed': { + 'sender': thirdPidSignedSender, + 'mxid': thirdPidSignedmxid, + 'token': thirdPidSignedToken, + 'signatures': thirdPidSignedSiganture, + } + }, + ); + return response['room_id']; + } + + /// This API starts a user participating in a particular room, if that user is allowed to participate in that room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-join-roomidoralias + Future joinRoomOrAlias( + String roomIdOrAlias, { + List servers, + String thirdPidSignedSender, + String thirdPidSignedmxid, + String thirdPidSignedToken, + Map thirdPidSignedSiganture, + }) async { + var action = '/client/r0/join/${Uri.encodeComponent(roomIdOrAlias)}'; + final queryPart = servers + ?.map((x) => 'server_name=${Uri.encodeQueryComponent(x)}') + ?.join('&'); + if (queryPart != null && queryPart != '') { + action += '?' + queryPart; + } + final response = await request( + RequestType.POST, + action, + data: { + if (thirdPidSignedSiganture != null) + 'third_party_signed': { + 'sender': thirdPidSignedSender, + 'mxid': thirdPidSignedmxid, + 'token': thirdPidSignedToken, + 'signatures': thirdPidSignedSiganture, + } + }, + ); + return response['room_id']; + } + + /// This API stops a user participating in a particular room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-leave + Future leaveRoom(String roomId) async { + await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/leave', + ); + return; + } + + /// This API stops a user remembering about a particular room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-forget + Future forgetRoom(String roomId) async { + await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/forget', + ); + return; + } + + /// Kick a user from the room. + /// The caller must have the required power level in order to perform this operation. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-kick + Future kickFromRoom(String roomId, String userId, + {String reason}) async { + await request(RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/kick', + data: { + 'user_id': userId, + if (reason != null) 'reason': reason, + }); + return; + } + + /// Ban a user in the room. If the user is currently in the room, also kick them. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-ban + Future banFromRoom(String roomId, String userId, + {String reason}) async { + await request( + RequestType.POST, '/client/r0/rooms/${Uri.encodeComponent(roomId)}/ban', + data: { + 'user_id': userId, + if (reason != null) 'reason': reason, + }); + return; + } + + /// Unban a user from the room. This allows them to be invited to the room, and join if they + /// would otherwise be allowed to join according to its join rules. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-unban + Future unbanInRoom(String roomId, String userId) async { + await request(RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/unban', + data: { + 'user_id': userId, + }); + return; + } + + /// Gets the visibility of a given room on the server's public room directory. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-directory-list-room-roomid + Future requestRoomVisibility(String roomId) async { + final response = await request( + RequestType.GET, + '/client/r0/directory/list/room/${Uri.encodeComponent(roomId)}', + ); + return Visibility.values + .firstWhere((v) => describeEnum(v) == response['visibility']); + } + + /// Sets the visibility of a given room in the server's public room directory. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-directory-list-room-roomid + Future setRoomVisibility(String roomId, Visibility visibility) async { + await request( + RequestType.PUT, + '/client/r0/directory/list/room/${Uri.encodeComponent(roomId)}', + data: { + 'visibility': describeEnum(visibility), + }, + ); + return; + } + + /// Lists the public rooms on the server. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-publicrooms + Future requestPublicRooms({ + int limit, + String since, + String server, + }) async { + final response = await request( + RequestType.GET, + '/client/r0/publicRooms', + query: { + if (limit != null) 'limit': limit.toString(), + if (since != null) 'since': since, + if (server != null) 'server': server, + }, + ); + return PublicRoomsResponse.fromJson(response); + } + + /// Lists the public rooms on the server, with optional filter. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-publicrooms + Future searchPublicRooms({ + String genericSearchTerm, + int limit, + String since, + String server, + bool includeAllNetworks, + String thirdPartyInstanceId, + }) async { + final response = await request( + RequestType.POST, + '/client/r0/publicRooms', + query: { + if (server != null) 'server': server, + }, + data: { + if (limit != null) 'limit': limit, + if (since != null) 'since': since, + if (includeAllNetworks != null) + 'include_all_networks': includeAllNetworks, + if (thirdPartyInstanceId != null) + 'third_party_instance_id': thirdPartyInstanceId, + if (genericSearchTerm != null) + 'filter': { + 'generic_search_term': genericSearchTerm, + }, + }, + ); + return PublicRoomsResponse.fromJson(response); + } + + /// Performs a search for users. The homeserver may determine which subset of users are searched, + /// however the homeserver MUST at a minimum consider the users the requesting user shares a + /// room with and those who reside in public rooms (known to the homeserver). The search MUST + /// consider local users to the homeserver, and SHOULD query remote users as part of the search. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-user-directory-search + Future searchUser( + String searchTerm, { + int limit, + }) async { + final response = await request( + RequestType.POST, + '/client/r0/user_directory/search', + data: { + 'search_term': searchTerm, + if (limit != null) 'limit': limit, + }, + ); + return UserSearchResult.fromJson(response); + } + + /// This API sets the given user's display name. You must have permission to + /// set this user's display name, e.g. you need to have their access_token. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-profile-userid-displayname + Future setDisplayname(String userId, String displayname) async { + await request( + RequestType.PUT, + '/client/r0/profile/${Uri.encodeComponent(userId)}/displayname', + data: { + 'displayname': displayname, + }, + ); + return; + } + + /// Get the user's display name. This API may be used to fetch the user's own + /// displayname or to query the name of other users; either locally or on remote homeservers. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-profile-userid-displayname + Future requestDisplayname(String userId) async { + final response = await request( + RequestType.GET, + '/client/r0/profile/${Uri.encodeComponent(userId)}/displayname', + ); + return response['displayname']; + } + + /// This API sets the given user's avatar URL. You must have permission to set + /// this user's avatar URL, e.g. you need to have their access_token. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-profile-userid-avatar-url + Future setAvatarUrl(String userId, Uri avatarUrl) async { + await request( + RequestType.PUT, + '/client/r0/profile/${Uri.encodeComponent(userId)}/avatar_url', + data: { + 'avatar_url': avatarUrl.toString(), + }, + ); + return; + } + + /// Get the user's avatar URL. This API may be used to fetch the user's own avatar URL or to + /// query the URL of other users; either locally or on remote homeservers. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-profile-userid-avatar-url + Future requestAvatarUrl(String userId) async { + final response = await request( + RequestType.GET, + '/client/r0/profile/${Uri.encodeComponent(userId)}/avatar_url', + ); + return Uri.parse(response['avatar_url']); + } + + /// Get the combined profile information for this user. This API may be used to fetch the user's + /// own profile information or other users; either locally or on remote homeservers. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-profile-userid-avatar-url + Future requestProfile(String userId) async { + final response = await request( + RequestType.GET, + '/client/r0/profile/${Uri.encodeComponent(userId)}', + ); + return Profile.fromJson(response); + } + + /// This API provides credentials for the client to use when initiating calls. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-voip-turnserver + Future requestTurnServerCredentials() async { + final response = await request( + RequestType.GET, + '/client/r0/voip/turnServer', + ); + return TurnServerCredentials.fromJson(response); + } + + /// This tells the server that the user is typing for the next N milliseconds + /// where N is the value specified in the timeout key. Alternatively, if typing is false, + /// it tells the server that the user has stopped typing. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-typing-userid + Future sendTypingNotification( + String userId, + String roomId, + bool typing, { + int timeout, + }) async { + await request(RequestType.PUT, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/typing/${Uri.encodeComponent(userId)}', + data: { + 'typing': typing, + if (timeout != null) 'timeout': timeout, + }); + return; + } + + /// This API updates the marker for the given receipt type to the event ID specified. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-receipt-receipttype-eventid + /// + Future sendReceiptMarker(String roomId, String eventId) async { + await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/receipt/m.read/${Uri.encodeComponent(eventId)}', + ); + return; + } + + /// Sets the position of the read marker for a given room, and optionally the read receipt's location. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-read-markers + Future sendReadMarker(String roomId, String eventId, + {String readReceiptLocationEventId}) async { + await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/read_markers', + data: { + 'm.fully_read': eventId, + if (readReceiptLocationEventId != null) + 'm.read': readReceiptLocationEventId, + }, + ); + return; + } + + /// This API sets the given user's presence state. When setting the status, + /// the activity time is updated to reflect that activity; the client does not need + /// to specify the last_active_ago field. You cannot set the presence state of another user. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-presence-userid-status + Future sendPresence( + String userId, + PresenceType presenceType, { + String statusMsg, + }) async { + await request( + RequestType.PUT, + '/client/r0/presence/${Uri.encodeComponent(userId)}/status', + data: { + 'presence': describeEnum(presenceType), + if (statusMsg != null) 'status_msg': statusMsg, + }, + ); + return; + } + + /// Get the given user's presence state. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-presence-userid-status + Future requestPresence(String userId) async { + final response = await request( + RequestType.GET, + '/client/r0/presence/${Uri.encodeComponent(userId)}/status', + ); + return PresenceContent.fromJson(response); + } + + /// Uploads a file with the name [fileName] as base64 encoded to the server + /// and returns the mxc url as a string. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-media-r0-upload + Future upload(Uint8List file, String fileName, + {String contentType}) async { + fileName = fileName.split('/').last; + var headers = {}; + headers['Authorization'] = 'Bearer $accessToken'; + headers['Content-Type'] = + contentType ?? lookupMimeType(fileName, headerBytes: file); + fileName = Uri.encodeQueryComponent(fileName); + final url = + '${homeserver.toString()}/_matrix/media/r0/upload?filename=$fileName'; + final streamedRequest = http.StreamedRequest('POST', Uri.parse(url)) + ..headers.addAll(headers); + streamedRequest.contentLength = await file.length; + streamedRequest.sink.add(file); + streamedRequest.sink.close(); + var streamedResponse = _testMode ? null : await streamedRequest.send(); + Map jsonResponse = json.decode( + String.fromCharCodes(_testMode + ? ((fileName == 'file.jpeg') + ? '{"content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"}' + : '{"errcode":"M_FORBIDDEN","error":"Cannot upload this content"}') + .codeUnits + : await streamedResponse.stream.first), + ); + if (!(jsonResponse['content_uri'] is String)) { + throw MatrixException.fromJson(jsonResponse); + } + return jsonResponse['content_uri']; + } + + /// Get information about a URL for the client. Typically this is called when a client sees a + /// URL in a message and wants to render a preview for the user. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-media-r0-preview-url + Future requestOpenGraphDataForUrl(Uri url, {int ts}) async { + var action = + '${homeserver.toString()}/_matrix/media/r0/preview_url?url=${Uri.encodeQueryComponent(url.toString())}'; + if (ts != null) { + action += '&ts=${Uri.encodeQueryComponent(ts.toString())}'; + } + final response = await httpClient.get(action); + final rawJson = json.decode(response.body.isEmpty ? '{}' : response.body); + return OpenGraphData.fromJson(rawJson); + } + + /// This endpoint allows clients to retrieve the configuration of the content repository, such as upload limitations. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-media-r0-config + Future requestMaxUploadSize() async { + var action = '${homeserver.toString()}/_matrix/media/r0/config'; + final response = await httpClient.get(action); + final rawJson = json.decode(response.body.isEmpty ? '{}' : response.body); + return rawJson['m.upload.size']; + } + + /// This endpoint is used to send send-to-device events to a set of client devices. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-sendtodevice-eventtype-txnid + Future sendToDevice( + String eventType, + String txnId, + Map>> messages, + ) async { + await request( + RequestType.PUT, + '/client/r0/sendToDevice/${Uri.encodeComponent(eventType)}/${Uri.encodeComponent(txnId)}', + data: { + 'messages': messages, + }, + ); + return; + } + + /// Gets information about all devices for the current user. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-devices + Future> requestDevices() async { + final response = await request( + RequestType.GET, + '/client/r0/devices', + ); + return (response['devices'] as List) + .map((i) => Device.fromJson(i)) + .toList(); + } + + /// Gets information on a single device, by device id. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-devices-deviceid + Future requestDevice(String deviceId) async { + final response = await request( + RequestType.GET, + '/client/r0/devices/${Uri.encodeComponent(deviceId)}', + ); + return Device.fromJson(response); + } + + /// Updates the metadata on the given device. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-devices-deviceid + Future setDeviceMetadata(String deviceId, {String displayName}) async { + await request( + RequestType.PUT, '/client/r0/devices/${Uri.encodeComponent(deviceId)}', + data: { + if (displayName != null) 'display_name': displayName, + }); + return; + } + + /// Deletes the given device, and invalidates any access token associated with it. + /// https://matrix.org/docs/spec/client_server/r0.6.1#delete-matrix-client-r0-devices-deviceid + Future deleteDevice(String deviceId, {AuthenticationData auth}) async { + await request(RequestType.DELETE, + '/client/r0/devices/${Uri.encodeComponent(deviceId)}', + data: { + if (auth != null) 'auth': auth.toJson(), + }); + return; + } + + /// Deletes the given devices, and invalidates any access token associated with them. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-delete-devices + Future deleteDevices(List deviceIds, + {AuthenticationData auth}) async { + await request(RequestType.POST, '/client/r0/delete_devices', data: { + 'devices': deviceIds, + if (auth != null) 'auth': auth.toJson(), + }); + return; + } + + /// Publishes end-to-end encryption keys for the device. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-query + Future> uploadDeviceKeys( + {MatrixDeviceKeys deviceKeys, Map oneTimeKeys}) async { + final response = await request( + RequestType.POST, + '/client/r0/keys/upload', + data: { + if (deviceKeys != null) 'device_keys': deviceKeys.toJson(), + if (oneTimeKeys != null) 'one_time_keys': oneTimeKeys, + }, + ); + return Map.from(response['one_time_key_counts']); + } + + /// Returns the current devices and identity keys for the given users. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-query + Future requestDeviceKeys( + Map deviceKeys, { + int timeout, + String token, + }) async { + final response = await request( + RequestType.POST, + '/client/r0/keys/query', + data: { + 'device_keys': deviceKeys, + if (timeout != null) 'timeout': timeout, + if (token != null) 'token': token, + }, + ); + return KeysQueryResponse.fromJson(response); + } + + /// Claims one-time keys for use in pre-key messages. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-claim + Future requestOneTimeKeys( + Map> oneTimeKeys, { + int timeout, + }) async { + final response = await request( + RequestType.POST, + '/client/r0/keys/claim', + data: { + 'one_time_keys': oneTimeKeys, + if (timeout != null) 'timeout': timeout, + }, + ); + return OneTimeKeysClaimResponse.fromJson(response); + } + + /// Gets a list of users who have updated their device identity keys since a previous sync token. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-upload + Future requestDeviceListsUpdate( + String from, String to) async { + final response = await request( + RequestType.GET, + '/client/r0/keys/changes?from=${Uri.encodeQueryComponent(from)}&to=${Uri.encodeQueryComponent(to)}', + ); + return DeviceListsUpdate.fromJson(response); + } + + /// Uploads your own cross-signing keys. + /// https://github.com/matrix-org/matrix-doc/pull/2536 + Future uploadDeviceSigningKeys({ + MatrixCrossSigningKey masterKey, + MatrixCrossSigningKey selfSigningKey, + MatrixCrossSigningKey userSigningKey, + AuthenticationData auth, + }) async { + await request( + RequestType.POST, + '/client/unstable/keys/device_signing/upload', + data: { + if (masterKey != null) 'master_key': masterKey.toJson(), + if (selfSigningKey != null) 'self_signing_key': selfSigningKey.toJson(), + if (userSigningKey != null) 'user_signing_key': userSigningKey.toJson(), + if (auth != null) 'auth': auth.toJson(), + }, + ); + } + + /// Uploads new signatures of keys + /// https://github.com/matrix-org/matrix-doc/pull/2536 + Future uploadKeySignatures( + List keys) async { + final payload = {}; + for (final key in keys) { + if (key.identifier == null || + key.signatures == null || + key.signatures.isEmpty) { + continue; + } + if (!payload.containsKey(key.userId)) { + payload[key.userId] = {}; + } + if (payload[key.userId].containsKey(key.identifier)) { + // we need to merge signature objects + payload[key.userId][key.identifier]['signatures'] + .addAll(key.signatures); + } else { + // we can just add signatures + payload[key.userId][key.identifier] = key.toJson(); + } + } + final response = await request( + RequestType.POST, + '/client/r0/keys/signatures/upload', + data: payload, + ); + return UploadKeySignaturesResponse.fromJson(response); + } + + /// Gets all currently active pushers for the authenticated user. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-pushers + Future> requestPushers() async { + final response = await request( + RequestType.GET, + '/client/r0/pushers', + ); + return (response['pushers'] as List) + .map((i) => Pusher.fromJson(i)) + .toList(); + } + + /// This endpoint allows the creation, modification and deletion of pushers + /// for this user ID. The behaviour of this endpoint varies depending on the + /// values in the JSON body. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-pushers-set + Future setPusher(Pusher pusher, {bool append}) async { + var data = pusher.toJson(); + if (append != null) { + data['append'] = append; + } + await request( + RequestType.POST, + '/client/r0/pushers/set', + data: data, + ); + return; + } + + /// This API is used to paginate through the list of events that the user has + /// been, or would have been notified about. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-notifications + Future requestNotifications({ + String from, + int limit, + String only, + }) async { + final response = await request( + RequestType.GET, + '/client/r0/notifications', + query: { + if (from != null) 'from': from, + if (limit != null) 'limit': limit.toString(), + if (only != null) 'only': only, + }, + ); + return NotificationsQueryResponse.fromJson(response); + } + + /// Retrieve all push rulesets for this user. Clients can "drill-down" + /// on the rulesets by suffixing a scope to this path e.g. /pushrules/global/. + /// This will return a subset of this data under the specified key e.g. the global key. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-pushrules + Future requestPushRules() async { + final response = await request( + RequestType.GET, + '/client/r0/pushrules', + ); + return PushRuleSet.fromJson(response['global']); + } + + /// Retrieve a single specified push rule. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-pushrules-scope-kind-ruleid + Future requestPushRule( + String scope, + PushRuleKind kind, + String ruleId, + ) async { + final response = await request( + RequestType.GET, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(describeEnum(kind))}/${Uri.encodeComponent(ruleId)}', + ); + return PushRule.fromJson(response); + } + + /// This endpoint removes the push rule defined in the path. + /// https://matrix.org/docs/spec/client_server/r0.6.1#delete-matrix-client-r0-pushrules-scope-kind-ruleid + Future deletePushRule( + String scope, + PushRuleKind kind, + String ruleId, + ) async { + await request( + RequestType.DELETE, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(describeEnum(kind))}/${Uri.encodeComponent(ruleId)}', + ); + return; + } + + /// This endpoint allows the creation, modification and deletion of pushers for this user ID. + /// The behaviour of this endpoint varies depending on the values in the JSON body. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-pushrules-scope-kind-ruleid + Future setPushRule( + String scope, + PushRuleKind kind, + String ruleId, + List actions, { + String before, + String after, + List conditions, + String pattern, + }) async { + await request(RequestType.PUT, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(describeEnum(kind))}/${Uri.encodeComponent(ruleId)}', + query: { + if (before != null) 'before': before, + if (after != null) 'after': after, + }, + data: { + 'actions': actions.map(describeEnum).toList(), + if (conditions != null) + 'conditions': conditions.map((c) => c.toJson()).toList(), + if (pattern != null) 'pattern': pattern, + }); + return; + } + + /// This endpoint gets whether the specified push rule is enabled. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-pushrules-scope-kind-ruleid-enabled + Future requestPushRuleEnabled( + String scope, + PushRuleKind kind, + String ruleId, + ) async { + final response = await request( + RequestType.GET, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(describeEnum(kind))}/${Uri.encodeComponent(ruleId)}/enabled', + ); + return response['enabled']; + } + + /// This endpoint allows clients to enable or disable the specified push rule. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-pushrules-scope-kind-ruleid-enabled + Future enablePushRule( + String scope, + PushRuleKind kind, + String ruleId, + bool enabled, + ) async { + await request( + RequestType.PUT, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(describeEnum(kind))}/${Uri.encodeComponent(ruleId)}/enabled', + data: {'enabled': enabled}, + ); + return; + } + + /// This endpoint get the actions for the specified push rule. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-pushrules-scope-kind-ruleid-actions + Future> requestPushRuleActions( + String scope, + PushRuleKind kind, + String ruleId, + ) async { + final response = await request( + RequestType.GET, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(describeEnum(kind))}/${Uri.encodeComponent(ruleId)}/actions', + ); + return (response['actions'] as List) + .map((i) => + PushRuleAction.values.firstWhere((a) => describeEnum(a) == i)) + .toList(); + } + + /// This endpoint allows clients to change the actions of a push rule. This can be used to change the actions of builtin rules. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-pushrules-scope-kind-ruleid-actions + Future setPushRuleActions( + String scope, + PushRuleKind kind, + String ruleId, + List actions, + ) async { + await request( + RequestType.PUT, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(describeEnum(kind))}/${Uri.encodeComponent(ruleId)}/actions', + data: {'actions': actions.map((a) => describeEnum(a)).toList()}, + ); + return; + } + + /// Performs a full text search across different categories. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-search + /// Please note: The specification is not 100% clear what it is expecting and sending here. + /// So we stick with pure json until we have more informations. + Future> globalSearch(Map query) async { + return await request( + RequestType.POST, + '/client/r0/search', + data: query, + ); + } + + /// This will listen for new events related to a particular room and return them to the + /// caller. This will block until an event is received, or until the timeout is reached. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-events + Future requestEvents({ + String from, + int timeout, + String roomId, + }) async { + final response = + await request(RequestType.GET, '/client/r0/events', query: { + if (from != null) 'from': from, + if (timeout != null) 'timeout': timeout.toString(), + if (roomId != null) 'roomId': roomId, + }); + return EventsSyncUpdate.fromJson(response); + } + + /// List the tags set by a user on a room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-user-userid-rooms-roomid-tags + Future> requestRoomTags(String userId, String roomId) async { + final response = await request( + RequestType.GET, + '/client/r0/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/tags', + ); + return (response['tags'] as Map).map( + (k, v) => MapEntry(k, Tag.fromJson(v)), + ); + } + + /// Add a tag to the room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-user-userid-rooms-roomid-tags-tag + Future addRoomTag( + String userId, + String roomId, + String tag, { + double order, + }) async { + await request(RequestType.PUT, + '/client/r0/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/tags/${Uri.encodeComponent(tag)}', + data: { + if (order != null) 'order': order, + }); + return; + } + + /// Remove a tag from the room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-user-userid-rooms-roomid-tags-tag + Future removeRoomTag(String userId, String roomId, String tag) async { + await request( + RequestType.DELETE, + '/client/r0/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/tags/${Uri.encodeComponent(tag)}', + ); + return; + } + + /// Set some account_data for the client. This config is only visible to the user that set the account_data. + /// The config will be synced to clients in the top-level account_data. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-user-userid-account-data-type + Future setAccountData( + String userId, + String type, + Map content, + ) async { + await request( + RequestType.PUT, + '/client/r0/user/${Uri.encodeComponent(userId)}/account_data/${Uri.encodeComponent(type)}', + data: content, + ); + return; + } + + /// Get some account_data for the client. This config is only visible to the user that set the account_data. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-user-userid-account-data-type + Future> requestAccountData( + String userId, + String type, + ) async { + return await request( + RequestType.GET, + '/client/r0/user/${Uri.encodeComponent(userId)}/account_data/${Uri.encodeComponent(type)}', + ); + } + + /// Set some account_data for the client on a given room. This config is only visible to the user that set + /// the account_data. The config will be synced to clients in the per-room account_data. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-user-userid-rooms-roomid-account-data-type + Future setRoomAccountData( + String userId, + String roomId, + String type, + Map content, + ) async { + await request( + RequestType.PUT, + '/client/r0/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/account_data/${Uri.encodeComponent(type)}', + data: content, + ); + return; + } + + /// Get some account_data for the client on a given room. This config is only visible to the user that set the account_data. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-user-userid-rooms-roomid-account-data-type + Future> requestRoomAccountData( + String userId, + String roomId, + String type, + ) async { + return await request( + RequestType.GET, + '/client/r0/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/account_data/${Uri.encodeComponent(type)}', + ); + } + + /// Gets information about a particular user. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid + Future requestWhoIsInfo(String userId) async { + final response = await request( + RequestType.GET, + '/client/r0/admin/whois/${Uri.encodeComponent(userId)}', + ); + return WhoIsInfo.fromJson(response); + } + + /// This API returns a number of events that happened just before and after the specified event. + /// This allows clients to get the context surrounding an event. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-context-eventid + Future requestEventContext( + String roomId, + String eventId, { + int limit, + String filter, + }) async { + final response = await request(RequestType.GET, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/context/${Uri.encodeComponent(eventId)}', + query: { + if (filter != null) 'filter': filter, + if (limit != null) 'limit': limit.toString(), + }); + return EventContext.fromJson(response); + } + + /// Reports an event as inappropriate to the server, which may then notify the appropriate people. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-report-eventid + Future reportEvent( + String roomId, + String eventId, + String reason, + int score, + ) async { + await request(RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/report/${Uri.encodeComponent(eventId)}', + data: { + 'reason': reason, + 'score': score, + }); + return; + } + + /// Fetches the overall metadata about protocols supported by the homeserver. Includes + /// both the available protocols and all fields required for queries against each protocol. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-protocols + Future> requestSupportedProtocols() async { + final response = await request( + RequestType.GET, + '/client/r0/thirdparty/protocols', + ); + return response.map((k, v) => MapEntry(k, SupportedProtocol.fromJson(v))); + } + + /// Fetches the metadata from the homeserver about a particular third party protocol. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-protocol-protocol + Future requestSupportedProtocol(String protocol) async { + final response = await request( + RequestType.GET, + '/client/r0/thirdparty/protocol/${Uri.encodeComponent(protocol)}', + ); + return SupportedProtocol.fromJson(response); + } + + /// Requesting this endpoint with a valid protocol name results in a list of successful + /// mapping results in a JSON array. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-location-protocol + Future> requestThirdPartyLocations( + String protocol) async { + final response = await request( + RequestType.GET, + '/client/r0/thirdparty/location/${Uri.encodeComponent(protocol)}', + ); + return (response['chunk'] as List) + .map((i) => ThirdPartyLocation.fromJson(i)) + .toList(); + } + + /// Retrieve a Matrix User ID linked to a user on the third party service, given a set of + /// user parameters. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-user-protocol + Future> requestThirdPartyUsers(String protocol) async { + final response = await request( + RequestType.GET, + '/client/r0/thirdparty/user/${Uri.encodeComponent(protocol)}', + ); + return (response['chunk'] as List) + .map((i) => ThirdPartyUser.fromJson(i)) + .toList(); + } + + /// Retrieve an array of third party network locations from a Matrix room alias. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-location + Future> requestThirdPartyLocationsByAlias( + String alias) async { + final response = await request( + RequestType.GET, + '/client/r0/thirdparty/location?alias=${Uri.encodeComponent(alias)}', + ); + return (response['chunk'] as List) + .map((i) => ThirdPartyLocation.fromJson(i)) + .toList(); + } + + /// Retrieve an array of third party users from a Matrix User ID. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-user + Future> requestThirdPartyUsersByUserId( + String userId) async { + final response = await request( + RequestType.GET, + '/client/r0/thirdparty/user?userid=${Uri.encodeComponent(userId)}', + ); + return (response['chunk'] as List) + .map((i) => ThirdPartyUser.fromJson(i)) + .toList(); + } + + Future requestOpenIdCredentials(String userId) async { + final response = await request( + RequestType.POST, + '/client/r0/user/${Uri.encodeComponent(userId)}/openid/request_token', + data: {}, + ); + return OpenIdCredentials.fromJson(response); + } + + Future upgradeRoom(String roomId, String version) async { + await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/upgrade', + data: {'new_version': version}, + ); + return; + } + + /// Create room keys backup + /// https://matrix.org/docs/spec/client_server/unstable#post-matrix-client-r0-room-keys-version + Future createRoomKeysBackup( + RoomKeysAlgorithmType algorithm, Map authData) async { + final ret = await request( + RequestType.POST, + '/client/unstable/room_keys/version', + data: { + 'algorithm': algorithm.algorithmString, + 'auth_data': authData, + }, + ); + return ret['version']; + } + + /// Gets a room key backup + /// https://matrix.org/docs/spec/client_server/unstable#get-matrix-client-r0-room-keys-version + Future getRoomKeysBackup([String version]) async { + var url = '/client/unstable/room_keys/version'; + if (version != null) { + url += '/${Uri.encodeComponent(version)}'; + } + final ret = await request( + RequestType.GET, + url, + ); + return RoomKeysVersionResponse.fromJson(ret); + } + + /// Updates a room key backup + /// https://matrix.org/docs/spec/client_server/unstable#put-matrix-client-r0-room-keys-version-version + Future updateRoomKeysBackup(String version, + RoomKeysAlgorithmType algorithm, Map authData) async { + await request( + RequestType.PUT, + '/client/unstable/room_keys/version/${Uri.encodeComponent(version)}', + data: { + 'algorithm': algorithm.algorithmString, + 'auth_data': authData, + 'version': version, + }, + ); + } + + /// Deletes a room key backup + /// https://matrix.org/docs/spec/client_server/unstable#delete-matrix-client-r0-room-keys-version-version + Future deleteRoomKeysBackup(String version) async { + await request( + RequestType.DELETE, + '/client/unstable/room_keys/version/${Uri.encodeComponent(version)}', + ); + } + + /// Stores a single room key + /// https://matrix.org/docs/spec/client_server/unstable#put-matrix-client-r0-room-keys-keys-roomid-sessionid + Future storeRoomKeysSingleKey(String roomId, + String sessionId, String version, RoomKeysSingleKey session) async { + final ret = await request( + RequestType.PUT, + '/client/unstable/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${Uri.encodeComponent(version)}', + data: session.toJson(), + ); + return RoomKeysUpdateResponse.fromJson(ret); + } + + /// Gets a single room key + /// https://matrix.org/docs/spec/client_server/unstable#get-matrix-client-r0-room-keys-keys-roomid-sessionid + Future getRoomKeysSingleKey( + String roomId, String sessionId, String version) async { + final ret = await request( + RequestType.GET, + '/client/unstable/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${Uri.encodeComponent(version)}', + ); + return RoomKeysSingleKey.fromJson(ret); + } + + /// Deletes a single room key + /// https://matrix.org/docs/spec/client_server/unstable#delete-matrix-client-r0-room-keys-keys-roomid-sessionid + Future deleteRoomKeysSingleKey( + String roomId, String sessionId, String version) async { + final ret = await request( + RequestType.DELETE, + '/client/unstable/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${Uri.encodeComponent(version)}', + ); + return RoomKeysUpdateResponse.fromJson(ret); + } + + /// Stores room keys for a room + /// https://matrix.org/docs/spec/client_server/unstable#put-matrix-client-r0-room-keys-keys-roomid + Future storeRoomKeysRoom( + String roomId, String version, RoomKeysRoom keys) async { + final ret = await request( + RequestType.PUT, + '/client/unstable/room_keys/keys/${Uri.encodeComponent(roomId)}?version=${Uri.encodeComponent(version)}', + data: keys.toJson(), + ); + return RoomKeysUpdateResponse.fromJson(ret); + } + + /// Gets room keys for a room + /// https://matrix.org/docs/spec/client_server/unstable#get-matrix-client-r0-room-keys-keys-roomid + Future getRoomKeysRoom(String roomId, String version) async { + final ret = await request( + RequestType.GET, + '/client/unstable/room_keys/keys/${Uri.encodeComponent(roomId)}?version=${Uri.encodeComponent(version)}', + ); + return RoomKeysRoom.fromJson(ret); + } + + /// Deletes room keys for a room + /// https://matrix.org/docs/spec/client_server/unstable#delete-matrix-client-r0-room-keys-keys-roomid + Future deleteRoomKeysRoom( + String roomId, String version) async { + final ret = await request( + RequestType.DELETE, + '/client/unstable/room_keys/keys/${Uri.encodeComponent(roomId)}?version=${Uri.encodeComponent(version)}', + ); + return RoomKeysUpdateResponse.fromJson(ret); + } + + /// Store multiple room keys + /// https://matrix.org/docs/spec/client_server/unstable#put-matrix-client-r0-room-keys-keys + Future storeRoomKeys( + String version, RoomKeys keys) async { + final ret = await request( + RequestType.PUT, + '/client/unstable/room_keys/keys?version=${Uri.encodeComponent(version)}', + data: keys.toJson(), + ); + return RoomKeysUpdateResponse.fromJson(ret); + } + + /// get all room keys + /// https://matrix.org/docs/spec/client_server/unstable#get-matrix-client-r0-room-keys-keys + Future getRoomKeys(String version) async { + final ret = await request( + RequestType.GET, + '/client/unstable/room_keys/keys?version=${Uri.encodeComponent(version)}', + ); + return RoomKeys.fromJson(ret); + } + + /// delete all room keys + /// https://matrix.org/docs/spec/client_server/unstable#delete-matrix-client-r0-room-keys-keys + Future deleteRoomKeys(String version) async { + final ret = await request( + RequestType.DELETE, + '/client/unstable/room_keys/keys?version=${Uri.encodeComponent(version)}', + ); + return RoomKeysUpdateResponse.fromJson(ret); + } +} diff --git a/lib/src/model/algorithm_types.dart b/lib/src/model/algorithm_types.dart new file mode 100644 index 00000000..cc657d7b --- /dev/null +++ b/lib/src/model/algorithm_types.dart @@ -0,0 +1,27 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +abstract class AlgorithmTypes { + static const String olmV1Curve25519AesSha2 = 'm.olm.v1.curve25519-aes-sha2'; + static const String megolmV1AesSha2 = 'm.megolm.v1.aes-sha2'; + static const String secretStorageV1AesHmcSha2 = + 'm.secret_storage.v1.aes-hmac-sha2'; + static const String megolmBackupV1Curve25519AesSha2 = + 'm.megolm_backup.v1.curve25519-aes-sha2'; + static const String pbkdf2 = 'm.pbkdf2'; +} diff --git a/lib/src/model/auth/authentication_data.dart b/lib/src/model/auth/authentication_data.dart new file mode 100644 index 00000000..ad340400 --- /dev/null +++ b/lib/src/model/auth/authentication_data.dart @@ -0,0 +1,36 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class AuthenticationData { + String type; + String session; + + AuthenticationData({this.type, this.session}); + + AuthenticationData.fromJson(Map json) { + type = json['type']; + session = json['session']; + } + + Map toJson() { + final data = {}; + if (type != null) data['type'] = type; + if (session != null) data['session'] = session; + return data; + } +} diff --git a/lib/src/model/auth/authentication_identifier.dart b/lib/src/model/auth/authentication_identifier.dart new file mode 100644 index 00000000..be5a4bfa --- /dev/null +++ b/lib/src/model/auth/authentication_identifier.dart @@ -0,0 +1,33 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class AuthenticationIdentifier { + String type; + + AuthenticationIdentifier({this.type}); + + AuthenticationIdentifier.fromJson(Map json) { + type = json['type']; + } + + Map toJson() { + final data = {}; + data['type'] = type; + return data; + } +} diff --git a/lib/src/model/auth/authentication_password.dart b/lib/src/model/auth/authentication_password.dart new file mode 100644 index 00000000..fe28d1c8 --- /dev/null +++ b/lib/src/model/auth/authentication_password.dart @@ -0,0 +1,84 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'authentication_user_identifier.dart'; +import 'authentication_data.dart'; +import 'authentication_identifier.dart'; +import 'authentication_phone_identifier.dart'; +import 'authentication_third_party_identifier.dart'; +import 'authentication_types.dart'; + +class AuthenticationPassword extends AuthenticationData { + String user; + String password; + + /// You may want to cast this as [AuthenticationUserIdentifier] or other + /// Identifier classes extending AuthenticationIdentifier. + AuthenticationIdentifier identifier; + + AuthenticationPassword( + {String session, this.password, this.user, this.identifier}) + : super( + type: AuthenticationTypes.password, + session: session, + ); + + AuthenticationPassword.fromJson(Map json) + : super.fromJson(json) { + user = json['user']; + password = json['password']; + identifier = AuthenticationIdentifier.fromJson(json['identifier']); + switch (identifier.type) { + case AuthenticationIdentifierTypes.userId: + identifier = AuthenticationUserIdentifier.fromJson(json['identifier']); + break; + case AuthenticationIdentifierTypes.phone: + identifier = AuthenticationPhoneIdentifier.fromJson(json['identifier']); + break; + case AuthenticationIdentifierTypes.thirdParty: + identifier = + AuthenticationThirdPartyIdentifier.fromJson(json['identifier']); + break; + } + } + + @override + Map toJson() { + final data = super.toJson(); + if (user != null) data['user'] = user; + data['password'] = password; + switch (identifier.type) { + case AuthenticationIdentifierTypes.userId: + data['identifier'] = + (identifier as AuthenticationUserIdentifier).toJson(); + break; + case AuthenticationIdentifierTypes.phone: + data['identifier'] = + (identifier as AuthenticationPhoneIdentifier).toJson(); + break; + case AuthenticationIdentifierTypes.thirdParty: + data['identifier'] = + (identifier as AuthenticationThirdPartyIdentifier).toJson(); + break; + default: + data['identifier'] = identifier.toJson(); + break; + } + return data; + } +} diff --git a/lib/src/model/auth/authentication_phone_identifier.dart b/lib/src/model/auth/authentication_phone_identifier.dart new file mode 100644 index 00000000..452f01c2 --- /dev/null +++ b/lib/src/model/auth/authentication_phone_identifier.dart @@ -0,0 +1,42 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'authentication_identifier.dart'; +import 'authentication_types.dart'; + +class AuthenticationPhoneIdentifier extends AuthenticationIdentifier { + String country; + String phone; + + AuthenticationPhoneIdentifier({this.country, this.phone}) + : super(type: AuthenticationIdentifierTypes.phone); + + AuthenticationPhoneIdentifier.fromJson(Map json) + : super.fromJson(json) { + country = json['country']; + phone = json['phone']; + } + + @override + Map toJson() { + final data = super.toJson(); + data['country'] = country; + data['phone'] = phone; + return data; + } +} diff --git a/lib/src/model/auth/authentication_recaptcha.dart b/lib/src/model/auth/authentication_recaptcha.dart new file mode 100644 index 00000000..3d0816ad --- /dev/null +++ b/lib/src/model/auth/authentication_recaptcha.dart @@ -0,0 +1,42 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'authentication_data.dart'; +import 'authentication_types.dart'; + +class AuthenticationRecaptcha extends AuthenticationData { + String response; + + AuthenticationRecaptcha({String session, this.response}) + : super( + type: AuthenticationTypes.recaptcha, + session: session, + ); + + AuthenticationRecaptcha.fromJson(Map json) + : super.fromJson(json) { + response = json['response']; + } + + @override + Map toJson() { + final data = super.toJson(); + data['response'] = response; + return data; + } +} diff --git a/lib/src/model/auth/authentication_third_party_identifier.dart b/lib/src/model/auth/authentication_third_party_identifier.dart new file mode 100644 index 00000000..5a4ab496 --- /dev/null +++ b/lib/src/model/auth/authentication_third_party_identifier.dart @@ -0,0 +1,42 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'authentication_identifier.dart'; +import 'authentication_types.dart'; + +class AuthenticationThirdPartyIdentifier extends AuthenticationIdentifier { + String medium; + String address; + + AuthenticationThirdPartyIdentifier({this.medium, this.address}) + : super(type: AuthenticationIdentifierTypes.thirdParty); + + AuthenticationThirdPartyIdentifier.fromJson(Map json) + : super.fromJson(json) { + medium = json['medium']; + address = json['address']; + } + + @override + Map toJson() { + final data = super.toJson(); + data['medium'] = medium; + data['address'] = address; + return data; + } +} diff --git a/lib/src/model/auth/authentication_three_pid_creds.dart b/lib/src/model/auth/authentication_three_pid_creds.dart new file mode 100644 index 00000000..1ddb45e7 --- /dev/null +++ b/lib/src/model/auth/authentication_three_pid_creds.dart @@ -0,0 +1,85 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'authentication_data.dart'; + +/// For email based identity: +/// https://matrix.org/docs/spec/client_server/r0.6.1#email-based-identity-homeserver +/// Or phone number based identity: +/// https://matrix.org/docs/spec/client_server/r0.6.1#phone-number-msisdn-based-identity-homeserver +class AuthenticationThreePidCreds extends AuthenticationData { + List threepidCreds; + + AuthenticationThreePidCreds({String session, String type, this.threepidCreds}) + : super( + type: type, + session: session, + ); + + AuthenticationThreePidCreds.fromJson(Map json) + : super.fromJson(json) { + if (json['threepidCreds'] != null) { + threepidCreds = (json['threepidCreds'] as List) + .map((item) => ThreepidCreds.fromJson(item)) + .toList(); + } + + // This is so extremly stupid... kill it with fire! + if (json['threepid_creds'] != null) { + threepidCreds = (json['threepid_creds'] as List) + .map((item) => ThreepidCreds.fromJson(item)) + .toList(); + } + } + + @override + Map toJson() { + final data = super.toJson(); + data['threepidCreds'] = threepidCreds.map((t) => t.toJson()).toList(); + // Help me! I'm prisoned in a developer factory against my will, + // where we are forced to work with json like this!! + data['threepid_creds'] = threepidCreds.map((t) => t.toJson()).toList(); + return data; + } +} + +class ThreepidCreds { + String sid; + String clientSecret; + String idServer; + String idAccessToken; + + ThreepidCreds( + {this.sid, this.clientSecret, this.idServer, this.idAccessToken}); + + ThreepidCreds.fromJson(Map json) { + sid = json['sid']; + clientSecret = json['client_secret']; + idServer = json['id_server']; + idAccessToken = json['id_access_token']; + } + + Map toJson() { + final data = {}; + data['sid'] = sid; + data['client_secret'] = clientSecret; + data['id_server'] = idServer; + data['id_access_token'] = idAccessToken; + return data; + } +} diff --git a/lib/src/model/auth/authentication_token.dart b/lib/src/model/auth/authentication_token.dart new file mode 100644 index 00000000..23db3755 --- /dev/null +++ b/lib/src/model/auth/authentication_token.dart @@ -0,0 +1,45 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'authentication_data.dart'; +import 'authentication_types.dart'; + +class AuthenticationToken extends AuthenticationData { + String token; + String txnId; + + AuthenticationToken({String session, this.token, this.txnId}) + : super( + type: AuthenticationTypes.token, + session: session, + ); + + AuthenticationToken.fromJson(Map json) + : super.fromJson(json) { + token = json['token']; + txnId = json['txn_id']; + } + + @override + Map toJson() { + final data = super.toJson(); + data['token'] = token; + data['txn_id'] = txnId; + return data; + } +} diff --git a/lib/src/model/auth/authentication_types.dart b/lib/src/model/auth/authentication_types.dart new file mode 100644 index 00000000..34e357f3 --- /dev/null +++ b/lib/src/model/auth/authentication_types.dart @@ -0,0 +1,34 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +abstract class AuthenticationTypes { + static const String password = 'm.login.password'; + static const String recaptcha = 'm.login.recaptcha'; + static const String token = 'm.login.token'; + static const String oauth2 = 'm.login.oauth2'; + static const String sso = 'm.login.sso'; + static const String emailIdentity = 'm.login.email.identity'; + static const String msisdn = 'm.login.msisdn'; + static const String dummy = 'm.login.dummy'; +} + +abstract class AuthenticationIdentifierTypes { + static const String userId = 'm.id.user'; + static const String thirdParty = 'm.id.thirdparty'; + static const String phone = 'm.id.phone'; +} diff --git a/lib/src/model/auth/authentication_user_identifier.dart b/lib/src/model/auth/authentication_user_identifier.dart new file mode 100644 index 00000000..66e6091e --- /dev/null +++ b/lib/src/model/auth/authentication_user_identifier.dart @@ -0,0 +1,39 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'authentication_identifier.dart'; +import 'authentication_types.dart'; + +class AuthenticationUserIdentifier extends AuthenticationIdentifier { + String user; + + AuthenticationUserIdentifier({this.user}) + : super(type: AuthenticationIdentifierTypes.userId); + + AuthenticationUserIdentifier.fromJson(Map json) + : super.fromJson(json) { + user = json['user']; + } + + @override + Map toJson() { + final data = super.toJson(); + data['user'] = user; + return data; + } +} diff --git a/lib/src/model/basic_event.dart b/lib/src/model/basic_event.dart new file mode 100644 index 00000000..095b2b21 --- /dev/null +++ b/lib/src/model/basic_event.dart @@ -0,0 +1,38 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class BasicEvent { + String type; + Map content; + + BasicEvent({ + this.type, + this.content, + }); + + BasicEvent.fromJson(Map json) { + type = json['type']; + content = Map.from(json['content']); + } + Map toJson() { + final data = {}; + data['type'] = type; + data['content'] = content; + return data; + } +} diff --git a/lib/src/model/basic_event_with_sender.dart b/lib/src/model/basic_event_with_sender.dart new file mode 100644 index 00000000..0e50bd86 --- /dev/null +++ b/lib/src/model/basic_event_with_sender.dart @@ -0,0 +1,39 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'basic_event.dart'; + +class BasicEventWithSender extends BasicEvent { + String senderId; + + BasicEventWithSender(); + + BasicEventWithSender.fromJson(Map json) { + final basicEvent = BasicEvent.fromJson(json); + type = basicEvent.type; + content = basicEvent.content; + senderId = json['sender']; + } + + @override + Map toJson() { + final data = super.toJson(); + data['sender'] = senderId; + return data; + } +} diff --git a/lib/src/model/basic_room_event.dart b/lib/src/model/basic_room_event.dart new file mode 100644 index 00000000..de8ee75c --- /dev/null +++ b/lib/src/model/basic_room_event.dart @@ -0,0 +1,46 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'basic_event.dart'; + +class BasicRoomEvent extends BasicEvent { + String roomId; + + BasicRoomEvent({ + this.roomId, + Map content, + String type, + }) : super( + content: content, + type: type, + ); + + BasicRoomEvent.fromJson(Map json) { + final basicEvent = BasicEvent.fromJson(json); + content = basicEvent.content; + type = basicEvent.type; + roomId = json['room_id']; + } + + @override + Map toJson() { + final data = super.toJson(); + if (roomId != null) data['room_id'] = roomId; + return data; + } +} diff --git a/lib/src/model/device.dart b/lib/src/model/device.dart new file mode 100644 index 00000000..aa089a4b --- /dev/null +++ b/lib/src/model/device.dart @@ -0,0 +1,46 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class Device { + String deviceId; + String displayName; + String lastSeenIp; + DateTime lastSeenTs; + + Device.fromJson(Map json) { + deviceId = json['device_id']; + displayName = json['display_name']; + lastSeenIp = json['last_seen_ip']; + lastSeenTs = DateTime.fromMillisecondsSinceEpoch(json['last_seen_ts'] ?? 0); + } + + Map toJson() { + final data = {}; + data['device_id'] = deviceId; + if (displayName != null) { + data['display_name'] = displayName; + } + if (lastSeenIp != null) { + data['last_seen_ip'] = lastSeenIp; + } + if (lastSeenTs != null) { + data['last_seen_ts'] = lastSeenTs.millisecondsSinceEpoch; + } + return data; + } +} diff --git a/lib/src/model/event_context.dart b/lib/src/model/event_context.dart new file mode 100644 index 00000000..9d23961c --- /dev/null +++ b/lib/src/model/event_context.dart @@ -0,0 +1,73 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'matrix_event.dart'; + +class EventContext { + String end; + List eventsAfter; + MatrixEvent event; + List eventsBefore; + String start; + List state; + + EventContext.fromJson(Map json) { + end = json['end']; + if (json['events_after'] != null) { + eventsAfter = []; + json['events_after'].forEach((v) { + eventsAfter.add(MatrixEvent.fromJson(v)); + }); + } + event = json['event'] != null ? MatrixEvent.fromJson(json['event']) : null; + if (json['events_before'] != null) { + eventsBefore = []; + json['events_before'].forEach((v) { + eventsBefore.add(MatrixEvent.fromJson(v)); + }); + } + start = json['start']; + if (json['state'] != null) { + state = []; + json['state'].forEach((v) { + state.add(MatrixEvent.fromJson(v)); + }); + } + } + + Map toJson() { + final data = {}; + if (end != null) { + data['end'] = end; + } + if (eventsAfter != null) { + data['events_after'] = eventsAfter.map((v) => v.toJson()).toList(); + } + if (event != null) { + data['event'] = event.toJson(); + } + if (eventsBefore != null) { + data['events_before'] = eventsBefore.map((v) => v.toJson()).toList(); + } + data['start'] = start; + if (state != null) { + data['state'] = state.map((v) => v.toJson()).toList(); + } + return data; + } +} diff --git a/lib/src/model/event_types.dart b/lib/src/model/event_types.dart new file mode 100644 index 00000000..36b63a7d --- /dev/null +++ b/lib/src/model/event_types.dart @@ -0,0 +1,63 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +abstract class EventTypes { + // Room timeline and state event types + static const String Message = 'm.room.message'; + static const String Sticker = 'm.sticker'; + static const String Reaction = 'm.reaction'; + static const String Redaction = 'm.room.redaction'; + static const String RoomAliases = 'm.room.aliases'; + static const String RoomCanonicalAlias = 'm.room.canonical_alias'; + static const String RoomCreate = 'm.room.create'; + static const String RoomJoinRules = 'm.room.join_rules'; + static const String RoomMember = 'm.room.member'; + static const String RoomPowerLevels = 'm.room.power_levels'; + static const String RoomName = 'm.room.name'; + static const String RoomPinnedEvents = 'm.room.pinned_events'; + static const String RoomTopic = 'm.room.topic'; + static const String RoomAvatar = 'm.room.avatar'; + static const String RoomTombstone = 'm.room.tombstone'; + static const String GuestAccess = 'm.room.guest_access'; + static const String HistoryVisibility = 'm.room.history_visibility'; + static const String Encryption = 'm.room.encryption'; + static const String Encrypted = 'm.room.encrypted'; + static const String CallInvite = 'm.call.invite'; + static const String CallAnswer = 'm.call.answer'; + static const String CallCandidates = 'm.call.candidates'; + static const String CallHangup = 'm.call.hangup'; + static const String Unknown = 'm.unknown'; + + // To device event types + static const String RoomKey = 'm.room_key'; + static const String ForwardedRoomKey = 'm.forwarded_room_key'; + static const String RoomKeyRequest = 'm.room_key_request'; + static const String KeyVerificationRequest = 'm.key.verification.request'; + static const String KeyVerificationStart = 'm.key.verification.start'; + static const String KeyVerificationDone = 'm.key.verification.done'; + static const String KeyVerificationCancel = 'm.key.verification.cancel'; + static const String KeyVerificationAccept = 'm.key.verification.accept'; + static const String SecretRequest = 'm.secret.request'; + static const String SecretSend = 'm.secret.send'; + static const String CrossSigningSelfSigning = 'm.cross_signing.self_signing'; + static const String CrossSigningUserSigning = 'm.cross_signing.user_signing'; + static const String CrossSigningMasterKey = 'm.cross_signing.master'; + static const String MegolmBackup = 'm.megolm_backup.v1'; + static const String SecretStorageDefaultKey = 'm.secret_storage.default_key'; + static String secretStorageKey(String keyId) => 'm.secret_storage.key.$keyId'; +} diff --git a/lib/src/model/events/secret_storage_default_key_content.dart b/lib/src/model/events/secret_storage_default_key_content.dart new file mode 100644 index 00000000..2e40ddf5 --- /dev/null +++ b/lib/src/model/events/secret_storage_default_key_content.dart @@ -0,0 +1,22 @@ +import '../basic_event.dart'; +import '../../utils/try_get_map_extension.dart'; + +extension SecretStorageDefaultKeyContentBasicEventExtension on BasicEvent { + SecretStorageDefaultKeyContent get parsedSecretStorageDefaultKeyContent => + SecretStorageDefaultKeyContent.fromJson(content); +} + +class SecretStorageDefaultKeyContent { + String key; + + SecretStorageDefaultKeyContent(); + + SecretStorageDefaultKeyContent.fromJson(Map json) + : key = json.tryGet('key'); + + Map toJson() { + final data = {}; + if (key != null) data['key'] = key; + return data; + } +} diff --git a/lib/src/model/events/secret_storage_key_content.dart b/lib/src/model/events/secret_storage_key_content.dart new file mode 100644 index 00000000..29050834 --- /dev/null +++ b/lib/src/model/events/secret_storage_key_content.dart @@ -0,0 +1,57 @@ +import '../basic_event.dart'; +import '../../utils/try_get_map_extension.dart'; + +extension SecretStorageKeyContentBasicEventExtension on BasicEvent { + SecretStorageKeyContent get parsedSecretStorageKeyContent => + SecretStorageKeyContent.fromJson(content); +} + +class SecretStorageKeyContent { + PassphraseInfo passphrase; + String iv; + String mac; + String algorithm; + + SecretStorageKeyContent(); + + SecretStorageKeyContent.fromJson(Map json) + : passphrase = json['passphrase'] is Map + ? PassphraseInfo.fromJson(json['passphrase']) + : null, + iv = json.tryGet('iv'), + mac = json.tryGet('mac'), + algorithm = json.tryGet('algorithm'); + + Map toJson() { + final data = {}; + if (passphrase != null) data['passphrase'] = passphrase.toJson(); + if (iv != null) data['iv'] = iv; + if (mac != null) data['mac'] = mac; + if (algorithm != null) data['algorithm'] = algorithm; + return data; + } +} + +class PassphraseInfo { + String algorithm; + String salt; + int iterations; + int bits; + + PassphraseInfo(); + + PassphraseInfo.fromJson(Map json) + : algorithm = json.tryGet('algorithm'), + salt = json.tryGet('salt'), + iterations = json.tryGet('iterations'), + bits = json.tryGet('bits'); + + Map toJson() { + final data = {}; + if (algorithm != null) data['algorithm'] = algorithm; + if (salt != null) data['salt'] = salt; + if (iterations != null) data['iterations'] = iterations; + if (bits != null) data['bits'] = bits; + return data; + } +} diff --git a/lib/src/model/events/tombstone_content.dart b/lib/src/model/events/tombstone_content.dart new file mode 100644 index 00000000..efddba3c --- /dev/null +++ b/lib/src/model/events/tombstone_content.dart @@ -0,0 +1,23 @@ +import '../basic_event.dart'; +import '../../utils/try_get_map_extension.dart'; + +extension TombstoneContentBasicEventExtension on BasicEvent { + TombstoneContent get parsedTombstoneContent => + TombstoneContent.fromJson(content); +} + +class TombstoneContent { + String body; + String replacementRoom; + + TombstoneContent.fromJson(Map json) + : body = json.tryGet('body', ''), + replacementRoom = json.tryGet('replacement_room', ''); + + Map toJson() { + final data = {}; + data['body'] = body; + data['replacement_room'] = replacementRoom; + return data; + } +} diff --git a/lib/src/model/events_sync_update.dart b/lib/src/model/events_sync_update.dart new file mode 100644 index 00000000..46b0fb3f --- /dev/null +++ b/lib/src/model/events_sync_update.dart @@ -0,0 +1,47 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'matrix_event.dart'; + +class EventsSyncUpdate { + String start; + String end; + List chunk; + + EventsSyncUpdate.fromJson(Map json) { + start = json['start']; + end = json['end']; + chunk = json['chunk'] != null + ? (json['chunk'] as List).map((i) => MatrixEvent.fromJson(i)).toList() + : null; + } + + Map toJson() { + final data = {}; + if (start != null) { + data['start'] = start; + } + if (end != null) { + data['end'] = end; + } + if (chunk != null) { + data['chunk'] = chunk.map((i) => i.toJson()).toList(); + } + return data; + } +} diff --git a/lib/src/model/filter.dart b/lib/src/model/filter.dart new file mode 100644 index 00000000..b1e18a7d --- /dev/null +++ b/lib/src/model/filter.dart @@ -0,0 +1,222 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +enum EventFormat { client, federation } + +class Filter { + RoomFilter room; + EventFilter presence; + EventFilter accountData; + EventFormat eventFormat; + List eventFields; + + Filter({ + this.room, + this.presence, + this.accountData, + this.eventFormat, + this.eventFields, + }); + + Filter.fromJson(Map json) { + room = json['room'] != null ? RoomFilter.fromJson(json['room']) : null; + presence = json['presence'] != null + ? EventFilter.fromJson(json['presence']) + : null; + accountData = json['account_data'] != null + ? EventFilter.fromJson(json['account_data']) + : null; + eventFormat = json['event_format'] != null + ? EventFormat.values.firstWhere( + (e) => e.toString().split('.').last == json['event_format']) + : null; + eventFields = json['event_fields'] != null + ? json['event_fields'].cast() + : null; + } + + Map toJson() { + final data = {}; + if (room != null) { + data['room'] = room.toJson(); + } + if (presence != null) { + data['presence'] = presence.toJson(); + } + if (eventFormat != null) { + data['event_format'] = eventFormat.toString().split('.').last; + } + if (eventFields != null) { + data['event_fields'] = eventFields; + } + if (accountData != null) { + data['account_data'] = accountData.toJson(); + } + return data; + } +} + +class RoomFilter { + List notRooms; + List rooms; + StateFilter ephemeral; + bool includeLeave; + StateFilter state; + StateFilter timeline; + StateFilter accountData; + + RoomFilter({ + this.notRooms, + this.rooms, + this.ephemeral, + this.includeLeave, + this.state, + this.timeline, + this.accountData, + }); + + RoomFilter.fromJson(Map json) { + notRooms = json['not_rooms']?.cast(); + rooms = json['rooms']?.cast(); + state = json['state'] != null ? StateFilter.fromJson(json['state']) : null; + includeLeave = json['include_leave']; + timeline = json['timeline'] != null + ? StateFilter.fromJson(json['timeline']) + : null; + ephemeral = json['ephemeral'] != null + ? StateFilter.fromJson(json['ephemeral']) + : null; + accountData = json['account_data'] != null + ? StateFilter.fromJson(json['account_data']) + : null; + } + + Map toJson() { + final data = {}; + if (notRooms != null) { + data['not_rooms'] = notRooms; + } + if (rooms != null) { + data['rooms'] = rooms; + } + if (ephemeral != null) { + data['ephemeral'] = ephemeral.toJson(); + } + if (includeLeave != null) { + data['include_leave'] = includeLeave; + } + if (state != null) { + data['state'] = state.toJson(); + } + if (timeline != null) { + data['timeline'] = timeline.toJson(); + } + if (accountData != null) { + data['account_data'] = accountData.toJson(); + } + return data; + } +} + +class EventFilter { + int limit; + List senders; + List types; + List notRooms; + List notSenders; + + EventFilter( + {this.limit, this.senders, this.types, this.notRooms, this.notSenders}); + + EventFilter.fromJson(Map json) { + limit = json['limit']; + types = json['senders']?.cast(); + types = json['types']?.cast(); + notRooms = json['not_rooms']?.cast(); + notSenders = json['not_senders']?.cast(); + } + + Map toJson() { + final data = {}; + if (limit != null) data['limit'] = limit; + if (types != null) data['types'] = types; + if (notRooms != null) data['not_rooms'] = notRooms; + if (notSenders != null) data['not_senders'] = notSenders; + return data; + } +} + +class StateFilter extends EventFilter { + List notTypes; + bool lazyLoadMembers; + bool includeRedundantMembers; + bool containsUrl; + + StateFilter({ + this.notTypes, + this.lazyLoadMembers, + this.includeRedundantMembers, + this.containsUrl, + int limit, + List senders, + List types, + List notRooms, + List notSenders, + }) : super( + limit: limit, + senders: senders, + types: types, + notRooms: notRooms, + notSenders: notSenders, + ); + + StateFilter.fromJson(Map json) { + final eventFilter = EventFilter.fromJson(json); + limit = eventFilter.limit; + senders = eventFilter.senders; + types = eventFilter.types; + notRooms = eventFilter.notRooms; + notSenders = eventFilter.notSenders; + + notTypes = json['not_types']?.cast(); + lazyLoadMembers = json['lazy_load_members']; + includeRedundantMembers = json['include_redundant_members']; + containsUrl = json['contains_url']; + } + + @override + Map toJson() { + final data = super.toJson(); + if (limit != null) { + data['limit'] = limit; + } + if (notTypes != null) { + data['not_types'] = notTypes; + } + if (lazyLoadMembers != null) { + data['lazy_load_members'] = notTypes; + } + if (includeRedundantMembers != null) { + data['include_redundant_members'] = notTypes; + } + if (containsUrl != null) { + data['contains_url'] = notTypes; + } + return data; + } +} diff --git a/lib/src/model/keys_query_response.dart b/lib/src/model/keys_query_response.dart new file mode 100644 index 00000000..b368b4f1 --- /dev/null +++ b/lib/src/model/keys_query_response.dart @@ -0,0 +1,117 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'matrix_keys.dart'; + +class KeysQueryResponse { + Map failures; + Map> deviceKeys; + Map masterKeys; + Map selfSigningKeys; + Map userSigningKeys; + + KeysQueryResponse.fromJson(Map json) { + failures = json['failures'] != null + ? Map.from(json['failures']) + : null; + deviceKeys = json['device_keys'] != null + ? (json['device_keys'] as Map).map( + (k, v) => MapEntry( + k, + (v as Map).map( + (k, v) => MapEntry( + k, + MatrixDeviceKeys.fromJson(v), + ), + ), + ), + ) + : null; + masterKeys = json['master_keys'] != null + ? (json['master_keys'] as Map).map( + (k, v) => MapEntry( + k, + MatrixCrossSigningKey.fromJson(v), + ), + ) + : null; + + selfSigningKeys = json['self_signing_keys'] != null + ? (json['self_signing_keys'] as Map).map( + (k, v) => MapEntry( + k, + MatrixCrossSigningKey.fromJson(v), + ), + ) + : null; + + userSigningKeys = json['user_signing_keys'] != null + ? (json['user_signing_keys'] as Map).map( + (k, v) => MapEntry( + k, + MatrixCrossSigningKey.fromJson(v), + ), + ) + : null; + } + + Map toJson() { + final data = {}; + if (failures != null) { + data['failures'] = failures; + } + if (deviceKeys != null) { + data['device_keys'] = deviceKeys.map( + (k, v) => MapEntry( + k, + v.map( + (k, v) => MapEntry( + k, + v.toJson(), + ), + ), + ), + ); + } + if (masterKeys != null) { + data['master_keys'] = masterKeys.map( + (k, v) => MapEntry( + k, + v.toJson(), + ), + ); + } + if (selfSigningKeys != null) { + data['self_signing_keys'] = selfSigningKeys.map( + (k, v) => MapEntry( + k, + v.toJson(), + ), + ); + } + if (userSigningKeys != null) { + data['user_signing_keys'] = userSigningKeys.map( + (k, v) => MapEntry( + k, + v.toJson(), + ), + ); + } + return data; + } +} diff --git a/lib/src/model/login_response.dart b/lib/src/model/login_response.dart new file mode 100644 index 00000000..a29627ad --- /dev/null +++ b/lib/src/model/login_response.dart @@ -0,0 +1,47 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'well_known_informations.dart'; + +class LoginResponse { + String userId; + String accessToken; + String deviceId; + WellKnownInformations wellKnownInformations; + + LoginResponse.fromJson(Map json) { + userId = json['user_id']; + accessToken = json['access_token']; + deviceId = json['device_id']; + if (json['well_known'] is Map) { + wellKnownInformations = + WellKnownInformations.fromJson(json['well_known']); + } + } + + Map toJson() { + final data = {}; + if (userId != null) data['user_id'] = userId; + if (accessToken != null) data['access_token'] = accessToken; + if (deviceId != null) data['device_id'] = deviceId; + if (wellKnownInformations != null) { + data['well_known'] = wellKnownInformations.toJson(); + } + return data; + } +} diff --git a/lib/src/model/login_types.dart b/lib/src/model/login_types.dart new file mode 100644 index 00000000..5cb67d22 --- /dev/null +++ b/lib/src/model/login_types.dart @@ -0,0 +1,52 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class LoginTypes { + List flows; + + LoginTypes.fromJson(Map json) { + if (json['flows'] != null) { + flows = []; + json['flows'].forEach((v) { + flows.add(Flows.fromJson(v)); + }); + } + } + + Map toJson() { + final data = {}; + if (flows != null) { + data['flows'] = flows.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Flows { + String type; + + Flows.fromJson(Map json) { + type = json['type']; + } + + Map toJson() { + final data = {}; + data['type'] = type; + return data; + } +} diff --git a/lib/src/model/marked_unread.dart b/lib/src/model/marked_unread.dart new file mode 100644 index 00000000..c3223c78 --- /dev/null +++ b/lib/src/model/marked_unread.dart @@ -0,0 +1,43 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +mixin EventType { + static const String MarkedUnread = 'com.famedly.marked_unread'; +} + +class MarkedUnread { + bool unread; + + MarkedUnread(this.unread); + + MarkedUnread.fromJson(Map json) { + if (!(json['unread'] is bool)) { + unread = false; + } else { + unread = json['unread']; + } + } + + Map toJson() { + final data = {}; + if (unread != null) { + data['unread'] = unread; + } + return data; + } +} diff --git a/lib/src/model/matrix_connection_exception.dart b/lib/src/model/matrix_connection_exception.dart new file mode 100644 index 00000000..b45192e0 --- /dev/null +++ b/lib/src/model/matrix_connection_exception.dart @@ -0,0 +1,26 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class MatrixConnectionException implements Exception { + final dynamic original; + final StackTrace stackTrace; + MatrixConnectionException(this.original, this.stackTrace); + + @override + String toString() => original.toString(); +} diff --git a/lib/src/model/matrix_event.dart b/lib/src/model/matrix_event.dart new file mode 100644 index 00000000..2f5f35f0 --- /dev/null +++ b/lib/src/model/matrix_event.dart @@ -0,0 +1,72 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'stripped_state_event.dart'; + +class MatrixEvent extends StrippedStateEvent { + String eventId; + String roomId; + DateTime originServerTs; + Map unsigned; + Map prevContent; + String redacts; + + MatrixEvent(); + + MatrixEvent.fromJson(Map json) { + final strippedStateEvent = StrippedStateEvent.fromJson(json); + content = strippedStateEvent.content; + type = strippedStateEvent.type; + senderId = strippedStateEvent.senderId; + stateKey = strippedStateEvent.stateKey; + eventId = json['event_id']; + roomId = json['room_id']; + originServerTs = + DateTime.fromMillisecondsSinceEpoch(json['origin_server_ts']); + unsigned = json['unsigned'] != null + ? Map.from(json['unsigned']) + : null; + prevContent = json['prev_content'] != null + ? Map.from(json['prev_content']) + : null; + redacts = json['redacts']; + } + + @override + Map toJson() { + final data = super.toJson(); + data['event_id'] = eventId; + data['origin_server_ts'] = originServerTs.millisecondsSinceEpoch; + if (unsigned != null) { + data['unsigned'] = unsigned; + } + if (prevContent != null) { + data['prev_content'] = prevContent; + } + if (roomId != null) { + data['room_id'] = roomId; + } + if (data['state_key'] == null) { + data.remove('state_key'); + } + if (redacts != null) { + data['redacts'] = redacts; + } + return data; + } +} diff --git a/lib/src/model/matrix_exception.dart b/lib/src/model/matrix_exception.dart new file mode 100644 index 00000000..fa292c9c --- /dev/null +++ b/lib/src/model/matrix_exception.dart @@ -0,0 +1,110 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +enum MatrixError { + M_UNKNOWN, + M_UNKNOWN_TOKEN, + M_NOT_FOUND, + M_FORBIDDEN, + M_LIMIT_EXCEEDED, + M_USER_IN_USE, + M_THREEPID_IN_USE, + M_THREEPID_DENIED, + M_THREEPID_NOT_FOUND, + M_THREEPID_AUTH_FAILED, + M_TOO_LARGE, + M_MISSING_PARAM, + M_UNSUPPORTED_ROOM_VERSION, + M_UNRECOGNIZED, +} + +/// Represents a special response from the Homeserver for errors. +class MatrixException implements Exception { + final Map raw; + + /// The unique identifier for this error. + String get errcode => + raw['errcode'] ?? + (requireAdditionalAuthentication ? 'M_FORBIDDEN' : 'M_UNKNOWN'); + + /// A human readable error description. + String get errorMessage => + raw['error'] ?? + (requireAdditionalAuthentication + ? 'Require additional authentication' + : 'Unknown error'); + + /// The frozen request which triggered this Error + http.Response response; + + MatrixException(this.response) : raw = json.decode(response.body); + MatrixException.fromJson(Map content) : raw = content; + + @override + String toString() => '$errcode: $errorMessage'; + + /// Returns the [ResponseError]. Is ResponseError.NONE if there wasn't an error. + MatrixError get error => MatrixError.values.firstWhere( + (e) => e.toString() == 'MatrixError.${(raw["errcode"] ?? "")}', + orElse: () => MatrixError.M_UNKNOWN); + + int get retryAfterMs => raw['retry_after_ms']; + + /// This is a session identifier that the client must pass back to the homeserver, if one is provided, + /// in subsequent attempts to authenticate in the same API call. + String get session => raw['session']; + + /// Returns true if the server requires additional authentication. + bool get requireAdditionalAuthentication => response != null + ? response.statusCode == 401 + : authenticationFlows != null; + + /// For each endpoint, a server offers one or more 'flows' that the client can use + /// to authenticate itself. Each flow comprises a series of stages. If this request + /// doesn't need additional authentication, then this is null. + List get authenticationFlows { + if (!raw.containsKey('flows') || !(raw['flows'] is List)) return null; + var flows = []; + for (Map flow in raw['flows']) { + if (flow['stages'] is List) { + flows.add(AuthenticationFlow(List.from(flow['stages']))); + } + } + return flows; + } + + /// This section contains any information that the client will need to know in order to use a given type + /// of authentication. For each authentication type presented, that type may be present as a key in this + /// dictionary. For example, the public part of an OAuth client ID could be given here. + Map get authenticationParams => raw['params']; + + /// Returns the list of already completed authentication flows from previous requests. + List get completedAuthenticationFlows => + List.from(raw['completed'] ?? []); +} + +/// For each endpoint, a server offers one or more 'flows' that the client can use +/// to authenticate itself. Each flow comprises a series of stages +class AuthenticationFlow { + final List stages; + const AuthenticationFlow(this.stages); +} diff --git a/lib/src/model/matrix_keys.dart b/lib/src/model/matrix_keys.dart new file mode 100644 index 00000000..c0f00386 --- /dev/null +++ b/lib/src/model/matrix_keys.dart @@ -0,0 +1,115 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class MatrixSignableKey { + String userId; + String identifier; + Map keys; + Map> signatures; + Map unsigned; + + MatrixSignableKey(this.userId, this.identifier, this.keys, this.signatures, + {this.unsigned}); + + // This object is used for signing so we need the raw json too + Map _json; + + MatrixSignableKey.fromJson(Map json) { + _json = json; + userId = json['user_id']; + keys = Map.from(json['keys']); + signatures = json['signatures'] is Map + ? Map>.from((json['signatures'] as Map) + .map((k, v) => MapEntry(k, Map.from(v)))) + : null; + unsigned = json['unsigned'] is Map + ? Map.from(json['unsigned']) + : null; + } + + Map toJson() { + final data = _json ?? {}; + data['user_id'] = userId; + data['keys'] = keys; + + if (signatures != null) { + data['signatures'] = signatures; + } + if (unsigned != null) { + data['unsigned'] = unsigned; + } + return data; + } +} + +class MatrixCrossSigningKey extends MatrixSignableKey { + List usage; + String get publicKey => identifier; + + MatrixCrossSigningKey( + String userId, + this.usage, + Map keys, + Map> signatures, { + Map unsigned, + }) : super(userId, keys?.values?.first, keys, signatures, unsigned: unsigned); + + @override + MatrixCrossSigningKey.fromJson(Map json) + : super.fromJson(json) { + usage = List.from(json['usage']); + identifier = keys?.values?.first; + } + + @override + Map toJson() { + final data = super.toJson(); + data['usage'] = usage; + return data; + } +} + +class MatrixDeviceKeys extends MatrixSignableKey { + String get deviceId => identifier; + List algorithms; + String get deviceDisplayName => + unsigned != null ? unsigned['device_display_name'] : null; + + MatrixDeviceKeys( + String userId, + String deviceId, + this.algorithms, + Map keys, + Map> signatures, { + Map unsigned, + }) : super(userId, deviceId, keys, signatures, unsigned: unsigned); + + @override + MatrixDeviceKeys.fromJson(Map json) : super.fromJson(json) { + identifier = json['device_id']; + algorithms = json['algorithms'].cast(); + } + + @override + Map toJson() { + final data = super.toJson(); + data['device_id'] = deviceId; + data['algorithms'] = algorithms; + return data; + } +} diff --git a/lib/src/model/message_types.dart b/lib/src/model/message_types.dart new file mode 100644 index 00000000..90fe2d08 --- /dev/null +++ b/lib/src/model/message_types.dart @@ -0,0 +1,31 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +abstract class MessageTypes { + static const String Text = 'm.text'; + static const String Emote = 'm.emote'; + static const String Notice = 'm.notice'; + static const String Image = 'm.image'; + static const String Video = 'm.video'; + static const String Audio = 'm.audio'; + static const String File = 'm.file'; + static const String Location = 'm.location'; + static const String Sticker = 'm.sticker'; + static const String BadEncrypted = 'm.bad.encrypted'; + static const String None = 'm.none'; +} diff --git a/lib/src/model/notifications_query_response.dart b/lib/src/model/notifications_query_response.dart new file mode 100644 index 00000000..f9eff6b9 --- /dev/null +++ b/lib/src/model/notifications_query_response.dart @@ -0,0 +1,72 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'matrix_event.dart'; + +class NotificationsQueryResponse { + String nextToken; + List notifications; + + NotificationsQueryResponse.fromJson(Map json) { + nextToken = json['next_token']; + notifications = []; + json['notifications'].forEach((v) { + notifications.add(Notification.fromJson(v)); + }); + } + + Map toJson() { + final data = {}; + if (nextToken != null) { + data['next_token'] = nextToken; + } + data['notifications'] = notifications.map((v) => v.toJson()).toList(); + return data; + } +} + +class Notification { + List actions; + String profileTag; + bool read; + String roomId; + int ts; + MatrixEvent event; + + Notification.fromJson(Map json) { + actions = json['actions'].cast(); + profileTag = json['profile_tag']; + read = json['read']; + roomId = json['room_id']; + ts = json['ts']; + event = MatrixEvent.fromJson(json['event']); + } + + Map toJson() { + final data = {}; + data['actions'] = actions; + if (profileTag != null) { + data['profile_tag'] = profileTag; + } + data['read'] = read; + data['room_id'] = roomId; + data['ts'] = ts; + data['event'] = event.toJson(); + return data; + } +} diff --git a/lib/src/model/one_time_keys_claim_response.dart b/lib/src/model/one_time_keys_claim_response.dart new file mode 100644 index 00000000..7834211c --- /dev/null +++ b/lib/src/model/one_time_keys_claim_response.dart @@ -0,0 +1,38 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class OneTimeKeysClaimResponse { + Map failures; + Map> oneTimeKeys; + + OneTimeKeysClaimResponse.fromJson(Map json) { + failures = Map.from(json['failures'] ?? {}); + oneTimeKeys = Map>.from(json['one_time_keys']); + } + + Map toJson() { + final data = {}; + if (failures != null) { + data['failures'] = failures; + } + if (oneTimeKeys != null) { + data['one_time_keys'] = oneTimeKeys; + } + return data; + } +} diff --git a/lib/src/model/open_graph_data.dart b/lib/src/model/open_graph_data.dart new file mode 100644 index 00000000..d2d8eb60 --- /dev/null +++ b/lib/src/model/open_graph_data.dart @@ -0,0 +1,63 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class OpenGraphData { + String ogTitle; + String ogDescription; + String ogImage; + String ogImageType; + int ogImageHeight; + int ogImageWidth; + int matrixImageSize; + + OpenGraphData.fromJson(Map json) { + ogTitle = json['og:title']; + ogDescription = json['og:description']; + ogImage = json['og:image']; + ogImageType = json['og:image:type']; + ogImageHeight = json['og:image:height']; + ogImageWidth = json['og:image:width']; + matrixImageSize = json['matrix:image:size']; + } + + Map toJson() { + final data = {}; + if (ogTitle != null) { + data['og:title'] = ogTitle; + } + if (ogDescription != null) { + data['og:description'] = ogDescription; + } + if (ogImage != null) { + data['og:image'] = ogImage; + } + if (ogImageType != null) { + data['og:image:type'] = ogImageType; + } + if (ogImageHeight != null) { + data['og:image:height'] = ogImageHeight; + } + if (ogImageWidth != null) { + data['og:image:width'] = ogImageWidth; + } + if (matrixImageSize != null) { + data['matrix:image:size'] = matrixImageSize; + } + return data; + } +} diff --git a/lib/src/model/open_id_credentials.dart b/lib/src/model/open_id_credentials.dart new file mode 100644 index 00000000..1d2902bd --- /dev/null +++ b/lib/src/model/open_id_credentials.dart @@ -0,0 +1,40 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class OpenIdCredentials { + String accessToken; + String tokenType; + String matrixServerName; + double expiresIn; + + OpenIdCredentials.fromJson(Map json) { + accessToken = json['access_token']; + tokenType = json['token_type']; + matrixServerName = json['matrix_server_name']; + expiresIn = json['expires_in']; + } + + Map toJson() { + final data = {}; + data['access_token'] = accessToken; + data['token_type'] = tokenType; + data['matrix_server_name'] = matrixServerName; + data['expires_in'] = expiresIn; + return data; + } +} diff --git a/lib/src/model/presence.dart b/lib/src/model/presence.dart new file mode 100644 index 00000000..c8044063 --- /dev/null +++ b/lib/src/model/presence.dart @@ -0,0 +1,32 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'basic_event_with_sender.dart'; +import 'presence_content.dart'; + +class Presence extends BasicEventWithSender { + PresenceContent presence; + + Presence.fromJson(Map json) { + final basicEvent = BasicEventWithSender.fromJson(json); + type = basicEvent.type; + content = basicEvent.content; + senderId = basicEvent.senderId; + presence = PresenceContent.fromJson(content); + } +} diff --git a/lib/src/model/presence_content.dart b/lib/src/model/presence_content.dart new file mode 100644 index 00000000..df9d2f08 --- /dev/null +++ b/lib/src/model/presence_content.dart @@ -0,0 +1,49 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +enum PresenceType { online, offline, unavailable } + +class PresenceContent { + PresenceType presence; + int lastActiveAgo; + String statusMsg; + bool currentlyActive; + + PresenceContent.fromJson(Map json) { + presence = PresenceType.values + .firstWhere((p) => p.toString().split('.').last == json['presence']); + lastActiveAgo = json['last_active_ago']; + statusMsg = json['status_msg']; + currentlyActive = json['currently_active']; + } + + Map toJson() { + final data = {}; + data['presence'] = presence.toString().split('.').last; + if (lastActiveAgo != null) { + data['last_active_ago'] = lastActiveAgo; + } + if (statusMsg != null) { + data['status_msg'] = statusMsg; + } + if (currentlyActive != null) { + data['currently_active'] = currentlyActive; + } + return data; + } +} diff --git a/lib/src/model/profile.dart b/lib/src/model/profile.dart new file mode 100644 index 00000000..37eace57 --- /dev/null +++ b/lib/src/model/profile.dart @@ -0,0 +1,44 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class Profile { + /// The user's avatar URL if they have set one, otherwise null. + Uri avatarUrl; + + /// The user's display name if they have set one, otherwise null. + String displayname; + + /// The matrix ID of this user. May be omitted. + String userId; + + Map additionalContent; + + Profile(this.displayname, this.avatarUrl, + {this.additionalContent = const {}}); + + Profile.fromJson(Map json) + : avatarUrl = + json['avatar_url'] != null ? Uri.parse(json['avatar_url']) : null, + displayname = json['display_name'] ?? json['displayname'], + userId = json['user_id'], + additionalContent = json; + + Map toJson() { + return additionalContent; + } +} diff --git a/lib/src/model/public_rooms_response.dart b/lib/src/model/public_rooms_response.dart new file mode 100644 index 00000000..f227dfb7 --- /dev/null +++ b/lib/src/model/public_rooms_response.dart @@ -0,0 +1,97 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class PublicRoomsResponse { + List chunk; + String nextBatch; + String prevBatch; + int totalRoomCountEstimate; + + PublicRoomsResponse.fromJson(Map json) { + chunk = []; + json['chunk'].forEach((v) { + chunk.add(PublicRoom.fromJson(v)); + }); + nextBatch = json['next_batch']; + prevBatch = json['prev_batch']; + totalRoomCountEstimate = json['total_room_count_estimate']; + } + + Map toJson() { + final data = {}; + data['chunk'] = chunk.map((v) => v.toJson()).toList(); + if (nextBatch != null) { + data['next_batch'] = nextBatch; + } + if (prevBatch != null) { + data['prev_batch'] = prevBatch; + } + if (totalRoomCountEstimate != null) { + data['total_room_count_estimate'] = totalRoomCountEstimate; + } + return data; + } +} + +class PublicRoom { + List aliases; + String avatarUrl; + bool guestCanJoin; + String name; + int numJoinedMembers; + String roomId; + String topic; + bool worldReadable; + String canonicalAlias; + + PublicRoom.fromJson(Map json) { + aliases = json['aliases']?.cast(); + avatarUrl = json['avatar_url']; + guestCanJoin = json['guest_can_join']; + canonicalAlias = json['canonical_alias']; + name = json['name']; + numJoinedMembers = json['num_joined_members']; + roomId = json['room_id']; + topic = json['topic']; + worldReadable = json['world_readable']; + } + + Map toJson() { + final data = {}; + if (aliases != null) { + data['aliases'] = aliases; + } + if (canonicalAlias != null) { + data['canonical_alias'] = canonicalAlias; + } + if (avatarUrl != null) { + data['avatar_url'] = avatarUrl; + } + data['guest_can_join'] = guestCanJoin; + if (name != null) { + data['name'] = name; + } + data['num_joined_members'] = numJoinedMembers; + data['room_id'] = roomId; + if (topic != null) { + data['topic'] = topic; + } + data['world_readable'] = worldReadable; + return data; + } +} diff --git a/lib/src/model/push_rule_set.dart b/lib/src/model/push_rule_set.dart new file mode 100644 index 00000000..2c9682b5 --- /dev/null +++ b/lib/src/model/push_rule_set.dart @@ -0,0 +1,143 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +enum PushRuleKind { content, override, room, sender, underride } +enum PushRuleAction { notify, dont_notify, coalesce, set_tweak } + +class PushRuleSet { + List content; + List override; + List room; + List sender; + List underride; + + PushRuleSet.fromJson(Map json) { + if (json['content'] != null) { + content = + (json['content'] as List).map((i) => PushRule.fromJson(i)).toList(); + } + if (json['override'] != null) { + override = + (json['override'] as List).map((i) => PushRule.fromJson(i)).toList(); + } + if (json['room'] != null) { + room = (json['room'] as List).map((i) => PushRule.fromJson(i)).toList(); + } + if (json['sender'] != null) { + sender = + (json['sender'] as List).map((i) => PushRule.fromJson(i)).toList(); + } + if (json['underride'] != null) { + underride = + (json['underride'] as List).map((i) => PushRule.fromJson(i)).toList(); + } + } + + Map toJson() { + final data = {}; + if (content != null) { + data['content'] = content.map((v) => v.toJson()).toList(); + } + if (override != null) { + data['override'] = override.map((v) => v.toJson()).toList(); + } + if (room != null) { + data['room'] = room.map((v) => v.toJson()).toList(); + } + if (sender != null) { + data['sender'] = sender.map((v) => v.toJson()).toList(); + } + if (underride != null) { + data['underride'] = underride.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class PushRule { + List actions; + List conditions; + bool isDefault; + bool enabled; + String pattern; + String ruleId; + + PushRule.fromJson(Map json) { + actions = json['actions']; + isDefault = json['default']; + enabled = json['enabled']; + pattern = json['pattern']; + ruleId = json['rule_id']; + conditions = json['conditions'] != null + ? (json['conditions'] as List) + .map((i) => PushConditions.fromJson(i)) + .toList() + : null; + } + + Map toJson() { + final data = {}; + data['actions'] = actions; + data['default'] = isDefault; + data['enabled'] = enabled; + if (pattern != null) { + data['pattern'] = pattern; + } + if (conditions != null) { + data['conditions'] = conditions.map((i) => i.toJson()).toList(); + } + data['rule_id'] = ruleId; + return data; + } +} + +class PushConditions { + String key; + String kind; + String pattern; + String isOperator; + + PushConditions( + this.kind, { + this.key, + this.pattern, + this.isOperator, + }); + + PushConditions.fromJson(Map json) { + key = json['key']; + kind = json['kind']; + pattern = json['pattern']; + isOperator = json['is']; + } + + Map toJson() { + final data = {}; + if (key != null) { + data['key'] = key; + } + data['kind'] = kind; + if (pattern != null) { + data['pattern'] = pattern; + } + if (isOperator != null) { + data['is'] = isOperator; + } + return data; + } +} diff --git a/lib/src/model/pusher.dart b/lib/src/model/pusher.dart new file mode 100644 index 00000000..00bc2803 --- /dev/null +++ b/lib/src/model/pusher.dart @@ -0,0 +1,93 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class Pusher { + String pushkey; + String kind; + String appId; + String appDisplayName; + String deviceDisplayName; + String profileTag; + String lang; + PusherData data; + + Pusher( + this.pushkey, + this.appId, + this.appDisplayName, + this.deviceDisplayName, + this.lang, + this.data, { + this.profileTag, + this.kind, + }); + + Pusher.fromJson(Map json) { + pushkey = json['pushkey']; + kind = json['kind']; + appId = json['app_id']; + appDisplayName = json['app_display_name']; + deviceDisplayName = json['device_display_name']; + profileTag = json['profile_tag']; + lang = json['lang']; + data = PusherData.fromJson(json['data']); + } + + Map toJson() { + final data = {}; + data['pushkey'] = pushkey; + data['kind'] = kind; + data['app_id'] = appId; + data['app_display_name'] = appDisplayName; + data['device_display_name'] = deviceDisplayName; + if (profileTag != null) { + data['profile_tag'] = profileTag; + } + data['lang'] = lang; + data['data'] = this.data.toJson(); + return data; + } +} + +class PusherData { + Uri url; + String format; + + PusherData({ + this.url, + this.format, + }); + + PusherData.fromJson(Map json) { + if (json.containsKey('url')) { + url = Uri.parse(json['url']); + } + format = json['format']; + } + + Map toJson() { + final data = {}; + if (url != null) { + data['url'] = url.toString(); + } + if (format != null) { + data['format'] = format; + } + return data; + } +} diff --git a/lib/src/model/request_token_response.dart b/lib/src/model/request_token_response.dart new file mode 100644 index 00000000..ee264be6 --- /dev/null +++ b/lib/src/model/request_token_response.dart @@ -0,0 +1,34 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class RequestTokenResponse { + String sid; + String submitUrl; + + RequestTokenResponse.fromJson(Map json) { + sid = json['sid']; + submitUrl = json['submit_url']; + } + + Map toJson() { + final data = {}; + data['sid'] = sid; + data['submit_url'] = submitUrl; + return data; + } +} diff --git a/lib/src/model/room_alias_informations.dart b/lib/src/model/room_alias_informations.dart new file mode 100644 index 00000000..d6b81640 --- /dev/null +++ b/lib/src/model/room_alias_informations.dart @@ -0,0 +1,34 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class RoomAliasInformations { + String roomId; + List servers; + + RoomAliasInformations.fromJson(Map json) { + roomId = json['room_id']; + servers = json['servers'].cast(); + } + + Map toJson() { + final data = {}; + data['room_id'] = roomId; + data['servers'] = servers; + return data; + } +} diff --git a/lib/src/model/room_keys_info.dart b/lib/src/model/room_keys_info.dart new file mode 100644 index 00000000..2ac7ac3c --- /dev/null +++ b/lib/src/model/room_keys_info.dart @@ -0,0 +1,69 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'algorithm_types.dart'; + +enum RoomKeysAlgorithmType { v1Curve25519AesSha2 } + +extension RoomKeysAlgorithmTypeExtension on RoomKeysAlgorithmType { + String get algorithmString { + switch (this) { + case RoomKeysAlgorithmType.v1Curve25519AesSha2: + return AlgorithmTypes.megolmBackupV1Curve25519AesSha2; + default: + return null; + } + } + + static RoomKeysAlgorithmType fromAlgorithmString(String s) { + switch (s) { + case AlgorithmTypes.megolmBackupV1Curve25519AesSha2: + return RoomKeysAlgorithmType.v1Curve25519AesSha2; + default: + return null; + } + } +} + +class RoomKeysVersionResponse { + RoomKeysAlgorithmType algorithm; + Map authData; + int count; + String etag; + String version; + + RoomKeysVersionResponse.fromJson(Map json) { + algorithm = + RoomKeysAlgorithmTypeExtension.fromAlgorithmString(json['algorithm']); + authData = json['auth_data']; + count = json['count']; + etag = + json['etag'].toString(); // synapse replies an int but docs say string? + version = json['version']; + } + + Map toJson() { + final data = {}; + data['algorithm'] = algorithm?.algorithmString; + data['auth_data'] = authData; + data['count'] = count; + data['etag'] = etag; + data['version'] = version; + return data; + } +} diff --git a/lib/src/model/room_keys_keys.dart b/lib/src/model/room_keys_keys.dart new file mode 100644 index 00000000..69870665 --- /dev/null +++ b/lib/src/model/room_keys_keys.dart @@ -0,0 +1,101 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class RoomKeysSingleKey { + int firstMessageIndex; + int forwardedCount; + bool isVerified; + Map sessionData; + + RoomKeysSingleKey( + {this.firstMessageIndex, + this.forwardedCount, + this.isVerified, + this.sessionData}); + + RoomKeysSingleKey.fromJson(Map json) { + firstMessageIndex = json['first_message_index']; + forwardedCount = json['forwarded_count']; + isVerified = json['is_verified']; + sessionData = json['session_data']; + } + + Map toJson() { + final data = {}; + data['first_message_index'] = firstMessageIndex; + data['forwarded_count'] = forwardedCount; + data['is_verified'] = isVerified; + data['session_data'] = sessionData; + return data; + } +} + +class RoomKeysRoom { + Map sessions; + + RoomKeysRoom({this.sessions}) { + sessions ??= {}; + } + + RoomKeysRoom.fromJson(Map json) { + sessions = (json['sessions'] as Map) + .map((k, v) => MapEntry(k, RoomKeysSingleKey.fromJson(v))); + } + + Map toJson() { + final data = {}; + data['sessions'] = sessions.map((k, v) => MapEntry(k, v.toJson())); + return data; + } +} + +class RoomKeys { + Map rooms; + + RoomKeys({this.rooms}) { + rooms ??= {}; + } + + RoomKeys.fromJson(Map json) { + rooms = (json['rooms'] as Map) + .map((k, v) => MapEntry(k, RoomKeysRoom.fromJson(v))); + } + + Map toJson() { + final data = {}; + data['rooms'] = rooms.map((k, v) => MapEntry(k, v.toJson())); + return data; + } +} + +class RoomKeysUpdateResponse { + String etag; + int count; + + RoomKeysUpdateResponse.fromJson(Map json) { + etag = json['etag']; // synapse replies an int but docs say string? + count = json['count']; + } + + Map toJson() { + final data = {}; + data['etag'] = etag; + data['count'] = count; + return data; + } +} diff --git a/lib/src/model/room_summary.dart b/lib/src/model/room_summary.dart new file mode 100644 index 00000000..67b6a8f3 --- /dev/null +++ b/lib/src/model/room_summary.dart @@ -0,0 +1,42 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class RoomSummary { + List mHeroes; + int mJoinedMemberCount; + int mInvitedMemberCount; + RoomSummary.fromJson(Map json) { + mHeroes = + json['m.heroes'] != null ? List.from(json['m.heroes']) : null; + mJoinedMemberCount = json['m.joined_member_count']; + mInvitedMemberCount = json['m.invited_member_count']; + } + Map toJson() { + final data = {}; + if (mHeroes != null) { + data['m.heroes'] = mHeroes; + } + if (mJoinedMemberCount != null) { + data['m.joined_member_count'] = mJoinedMemberCount; + } + if (mInvitedMemberCount != null) { + data['m.invited_member_count'] = mInvitedMemberCount; + } + return data; + } +} diff --git a/lib/src/model/server_capabilities.dart b/lib/src/model/server_capabilities.dart new file mode 100644 index 00000000..0e449e9d --- /dev/null +++ b/lib/src/model/server_capabilities.dart @@ -0,0 +1,89 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +enum RoomVersionStability { stable, unstable } + +class ServerCapabilities { + MChangePassword mChangePassword; + MRoomVersions mRoomVersions; + Map customCapabilities; + + ServerCapabilities.fromJson(Map json) { + mChangePassword = json['m.change_password'] != null + ? MChangePassword.fromJson(json['m.change_password']) + : null; + mRoomVersions = json['m.room_versions'] != null + ? MRoomVersions.fromJson(json['m.room_versions']) + : null; + customCapabilities = Map.from(json); + customCapabilities.remove('m.change_password'); + customCapabilities.remove('m.room_versions'); + } + + Map toJson() { + final data = {}; + if (mChangePassword != null) { + data['m.change_password'] = mChangePassword.toJson(); + } + if (mRoomVersions != null) { + data['m.room_versions'] = mRoomVersions.toJson(); + } + for (final entry in customCapabilities.entries) { + data[entry.key] = entry.value; + } + return data; + } +} + +class MChangePassword { + bool enabled; + + MChangePassword.fromJson(Map json) { + enabled = json['enabled']; + } + + Map toJson() { + final data = {}; + data['enabled'] = enabled; + return data; + } +} + +class MRoomVersions { + String defaultVersion; + Map available; + + MRoomVersions.fromJson(Map json) { + defaultVersion = json['default']; + available = (json['available'] as Map).map( + (k, v) => MapEntry( + k, + RoomVersionStability.values + .firstWhere((r) => r.toString().split('.').last == v), + ), + ); + } + + Map toJson() { + final data = {}; + data['default'] = defaultVersion; + data['available'] = available.map( + (k, v) => MapEntry(k, v.toString().split('.').last)); + return data; + } +} diff --git a/lib/src/model/stripped_state_event.dart b/lib/src/model/stripped_state_event.dart new file mode 100644 index 00000000..29c0740a --- /dev/null +++ b/lib/src/model/stripped_state_event.dart @@ -0,0 +1,39 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'basic_event_with_sender.dart'; + +class StrippedStateEvent extends BasicEventWithSender { + String stateKey; + + StrippedStateEvent(); + StrippedStateEvent.fromJson(Map json) { + final basicEvent = BasicEventWithSender.fromJson(json); + content = basicEvent.content; + type = basicEvent.type; + senderId = basicEvent.senderId; + stateKey = json['state_key']; + } + + @override + Map toJson() { + final data = super.toJson(); + data['state_key'] = stateKey; + return data; + } +} diff --git a/lib/src/model/supported_protocol.dart b/lib/src/model/supported_protocol.dart new file mode 100644 index 00000000..736b7d6d --- /dev/null +++ b/lib/src/model/supported_protocol.dart @@ -0,0 +1,92 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class SupportedProtocol { + List userFields; + List locationFields; + String icon; + Map fieldTypes; + List instances; + + SupportedProtocol.fromJson(Map json) { + userFields = json['user_fields'].cast(); + locationFields = json['location_fields'].cast(); + icon = json['icon']; + fieldTypes = (json['field_types'] as Map) + .map((k, v) => MapEntry(k, ProtocolFieldType.fromJson(v))); + instances = []; + json['instances'].forEach((v) { + instances.add(ProtocolInstance.fromJson(v)); + }); + } + + Map toJson() { + final data = {}; + data['user_fields'] = userFields; + data['location_fields'] = locationFields; + data['icon'] = icon; + data['field_types'] = fieldTypes.map((k, v) => MapEntry(k, v.toJson())); + + data['instances'] = instances.map((v) => v.toJson()).toList(); + + return data; + } +} + +class ProtocolFieldType { + String regexp; + String placeholder; + + ProtocolFieldType.fromJson(Map json) { + regexp = json['regexp']; + placeholder = json['placeholder']; + } + + Map toJson() { + final data = {}; + data['regexp'] = regexp; + data['placeholder'] = placeholder; + return data; + } +} + +class ProtocolInstance { + String networkId; + String desc; + String icon; + dynamic fields; + + ProtocolInstance.fromJson(Map json) { + networkId = json['network_id']; + desc = json['desc']; + icon = json['icon']; + fields = json['fields']; + } + + Map toJson() { + final data = {}; + data['network_id'] = networkId; + data['desc'] = desc; + if (icon != null) { + data['icon'] = icon; + } + data['fields'] = fields; + + return data; + } +} diff --git a/lib/src/model/supported_versions.dart b/lib/src/model/supported_versions.dart new file mode 100644 index 00000000..33ab8c49 --- /dev/null +++ b/lib/src/model/supported_versions.dart @@ -0,0 +1,36 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class SupportedVersions { + List versions; + Map unstableFeatures; + + SupportedVersions.fromJson(Map json) { + versions = json['versions'].cast(); + unstableFeatures = Map.from(json['unstable_features'] ?? {}); + } + + Map toJson() { + final data = {}; + data['versions'] = versions; + if (unstableFeatures != null) { + data['unstable_features'] = unstableFeatures; + } + return data; + } +} diff --git a/lib/src/model/sync_update.dart b/lib/src/model/sync_update.dart new file mode 100644 index 00000000..c36c09e1 --- /dev/null +++ b/lib/src/model/sync_update.dart @@ -0,0 +1,333 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'basic_event.dart'; +import 'basic_event_with_sender.dart'; +import 'basic_room_event.dart'; +import 'matrix_event.dart'; +import 'presence.dart'; +import 'room_summary.dart'; +import 'stripped_state_event.dart'; + +class SyncUpdate { + String nextBatch; + RoomsUpdate rooms; + List presence; + List accountData; + List toDevice; + DeviceListsUpdate deviceLists; + Map deviceOneTimeKeysCount; + + SyncUpdate(); + + SyncUpdate.fromJson(Map json) { + nextBatch = json['next_batch']; + rooms = json['rooms'] != null ? RoomsUpdate.fromJson(json['rooms']) : null; + presence = (json['presence'] != null && json['presence']['events'] != null) + ? (json['presence']['events'] as List) + .map((i) => Presence.fromJson(i)) + .toList() + : null; + accountData = + (json['account_data'] != null && json['account_data']['events'] != null) + ? (json['account_data']['events'] as List) + .map((i) => BasicEvent.fromJson(i)) + .toList() + : null; + toDevice = + (json['to_device'] != null && json['to_device']['events'] != null) + ? (json['to_device']['events'] as List) + .map((i) => BasicEventWithSender.fromJson(i)) + .toList() + : null; + deviceLists = json['device_lists'] != null + ? DeviceListsUpdate.fromJson(json['device_lists']) + : null; + deviceOneTimeKeysCount = json['device_one_time_keys_count'] != null + ? Map.from(json['device_one_time_keys_count']) + : null; + } + + Map toJson() { + final data = {}; + data['next_batch'] = nextBatch; + if (rooms != null) { + data['rooms'] = rooms.toJson(); + } + if (presence != null) { + data['presence'] = { + 'events': presence.map((i) => i.toJson()).toList(), + }; + } + if (accountData != null) { + data['account_data'] = { + 'events': accountData.map((i) => i.toJson()).toList(), + }; + } + if (toDevice != null) { + data['to_device'] = { + 'events': toDevice.map((i) => i.toJson()).toList(), + }; + } + if (deviceLists != null) { + data['device_lists'] = deviceLists.toJson(); + } + if (deviceOneTimeKeysCount != null) { + data['device_one_time_keys_count'] = deviceOneTimeKeysCount; + } + return data; + } +} + +class RoomsUpdate { + Map join; + Map invite; + Map leave; + + RoomsUpdate(); + + RoomsUpdate.fromJson(Map json) { + join = json['join'] != null + ? (json['join'] as Map) + .map((k, v) => MapEntry(k, JoinedRoomUpdate.fromJson(v))) + : null; + invite = json['invite'] != null + ? (json['invite'] as Map) + .map((k, v) => MapEntry(k, InvitedRoomUpdate.fromJson(v))) + : null; + leave = json['leave'] != null + ? (json['leave'] as Map) + .map((k, v) => MapEntry(k, LeftRoomUpdate.fromJson(v))) + : null; + } + Map toJson() { + final data = {}; + if (join != null) { + data['join'] = join.map((k, v) => MapEntry(k, v.toJson())); + } + if (invite != null) { + data['invite'] = invite.map((k, v) => MapEntry(k, v.toJson())); + } + if (leave != null) { + data['leave'] = leave.map((k, v) => MapEntry(k, v.toJson())); + } + return data; + } +} + +abstract class SyncRoomUpdate {} + +class JoinedRoomUpdate extends SyncRoomUpdate { + RoomSummary summary; + List state; + TimelineUpdate timeline; + List ephemeral; + List accountData; + UnreadNotificationCounts unreadNotifications; + + JoinedRoomUpdate(); + + JoinedRoomUpdate.fromJson(Map json) { + summary = + json['summary'] != null ? RoomSummary.fromJson(json['summary']) : null; + state = (json['state'] != null && json['state']['events'] != null) + ? (json['state']['events'] as List) + .map((i) => MatrixEvent.fromJson(i)) + .toList() + : null; + timeline = json['timeline'] != null + ? TimelineUpdate.fromJson(json['timeline']) + : null; + + ephemeral = + (json['ephemeral'] != null && json['ephemeral']['events'] != null) + ? (json['ephemeral']['events'] as List) + .map((i) => BasicRoomEvent.fromJson(i)) + .toList() + : null; + accountData = + (json['account_data'] != null && json['account_data']['events'] != null) + ? (json['account_data']['events'] as List) + .map((i) => BasicRoomEvent.fromJson(i)) + .toList() + : null; + unreadNotifications = json['unread_notifications'] != null + ? UnreadNotificationCounts.fromJson(json['unread_notifications']) + : null; + } + + Map toJson() { + final data = {}; + if (summary != null) { + data['summary'] = summary.toJson(); + } + if (state != null) { + data['state'] = { + 'events': state.map((i) => i.toJson()).toList(), + }; + } + if (timeline != null) { + data['timeline'] = timeline.toJson(); + } + if (ephemeral != null) { + data['ephemeral'] = { + 'events': ephemeral.map((i) => i.toJson()).toList(), + }; + } + if (accountData != null) { + data['account_data'] = { + 'events': accountData.map((i) => i.toJson()).toList(), + }; + } + if (unreadNotifications != null) { + data['unread_notifications'] = unreadNotifications.toJson(); + } + return data; + } +} + +class InvitedRoomUpdate extends SyncRoomUpdate { + List inviteState; + InvitedRoomUpdate.fromJson(Map json) { + inviteState = + (json['invite_state'] != null && json['invite_state']['events'] != null) + ? (json['invite_state']['events'] as List) + .map((i) => StrippedStateEvent.fromJson(i)) + .toList() + : null; + } + Map toJson() { + final data = {}; + if (inviteState != null) { + data['invite_state'] = { + 'events': inviteState.map((i) => i.toJson()).toList(), + }; + } + return data; + } +} + +class LeftRoomUpdate extends SyncRoomUpdate { + List state; + TimelineUpdate timeline; + List accountData; + + LeftRoomUpdate(); + + LeftRoomUpdate.fromJson(Map json) { + state = (json['state'] != null && json['state']['events'] != null) + ? (json['state']['events'] as List) + .map((i) => MatrixEvent.fromJson(i)) + .toList() + : null; + timeline = json['timeline'] != null + ? TimelineUpdate.fromJson(json['timeline']) + : null; + accountData = + (json['account_data'] != null && json['account_data']['events'] != null) + ? (json['account_data']['events'] as List) + .map((i) => BasicRoomEvent.fromJson(i)) + .toList() + : null; + } + Map toJson() { + final data = {}; + if (state != null) { + data['state'] = { + 'events': state.map((i) => i.toJson()).toList(), + }; + } + if (timeline != null) { + data['timeline'] = timeline.toJson(); + } + if (accountData != null) { + data['account_data'] = { + 'events': accountData.map((i) => i.toJson()).toList(), + }; + } + return data; + } +} + +class TimelineUpdate { + List events; + bool limited; + String prevBatch; + + TimelineUpdate(); + + TimelineUpdate.fromJson(Map json) { + events = json['events'] != null + ? (json['events'] as List).map((i) => MatrixEvent.fromJson(i)).toList() + : null; + limited = json['limited']; + prevBatch = json['prev_batch']; + } + + Map toJson() { + final data = {}; + if (events != null) { + data['events'] = events.map((i) => i.toJson()).toList(); + } + if (limited != null) { + data['limited'] = limited; + } + if (prevBatch != null) { + data['prev_batch'] = prevBatch; + } + return data; + } +} + +class UnreadNotificationCounts { + int highlightCount; + int notificationCount; + UnreadNotificationCounts.fromJson(Map json) { + highlightCount = json['highlight_count']; + notificationCount = json['notification_count']; + } + Map toJson() { + final data = {}; + if (highlightCount != null) { + data['highlight_count'] = highlightCount; + } + if (notificationCount != null) { + data['notification_count'] = notificationCount; + } + return data; + } +} + +class DeviceListsUpdate { + List changed; + List left; + DeviceListsUpdate.fromJson(Map json) { + changed = List.from(json['changed'] ?? []); + left = List.from(json['left'] ?? []); + } + Map toJson() { + final data = {}; + if (changed != null) { + data['changed'] = changed; + } + if (left != null) { + data['left'] = left; + } + return data; + } +} diff --git a/lib/src/model/tag.dart b/lib/src/model/tag.dart new file mode 100644 index 00000000..f63d6e57 --- /dev/null +++ b/lib/src/model/tag.dart @@ -0,0 +1,42 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class Tag { + double order; + + Tag.fromJson(Map json) { + order = double.tryParse(json['order'].toString()); + } + + Map toJson() { + final data = {}; + if (order != null) { + data['order'] = order; + } + return data; + } +} + +abstract class TagType { + static const String Favourite = 'm.favourite'; + static const String LowPriority = 'm.lowpriority'; + static const String ServerNotice = 'm.server_notice'; + static bool isValid(String tag) => tag.startsWith('m.') + ? [Favourite, LowPriority, ServerNotice].contains(tag) + : true; +} diff --git a/lib/src/model/third_party_identifier.dart b/lib/src/model/third_party_identifier.dart new file mode 100644 index 00000000..14fe31d8 --- /dev/null +++ b/lib/src/model/third_party_identifier.dart @@ -0,0 +1,43 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import '../../matrix_api_lite.dart'; + +class ThirdPartyIdentifier { + ThirdPartyIdentifierMedium medium; + String address; + int validatedAt; + int addedAt; + + ThirdPartyIdentifier.fromJson(Map json) { + medium = ThirdPartyIdentifierMedium.values + .firstWhere((medium) => describeEnum(medium) == json['medium']); + address = json['address']; + validatedAt = json['validated_at']; + addedAt = json['added_at']; + } + + Map toJson() { + final data = {}; + data['medium'] = describeEnum(medium); + data['address'] = address; + data['validated_at'] = validatedAt; + data['added_at'] = addedAt; + return data; + } +} diff --git a/lib/src/model/third_party_location.dart b/lib/src/model/third_party_location.dart new file mode 100644 index 00000000..68083805 --- /dev/null +++ b/lib/src/model/third_party_location.dart @@ -0,0 +1,37 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class ThirdPartyLocation { + String alias; + String protocol; + Map fields; + + ThirdPartyLocation.fromJson(Map json) { + alias = json['alias']; + protocol = json['protocol']; + fields = Map.from(json['fields']); + } + + Map toJson() { + final data = {}; + data['alias'] = alias; + data['protocol'] = protocol; + data['fields'] = fields; + return data; + } +} diff --git a/lib/src/model/third_party_user.dart b/lib/src/model/third_party_user.dart new file mode 100644 index 00000000..fd84f907 --- /dev/null +++ b/lib/src/model/third_party_user.dart @@ -0,0 +1,37 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class ThirdPartyUser { + String userId; + String protocol; + Map fields; + + ThirdPartyUser.fromJson(Map json) { + userId = json['userid']; + protocol = json['protocol']; + fields = Map.from(json['fields']); + } + + Map toJson() { + final data = {}; + data['userid'] = userId; + data['protocol'] = protocol; + data['fields'] = fields; + return data; + } +} diff --git a/lib/src/model/timeline_history_response.dart b/lib/src/model/timeline_history_response.dart new file mode 100644 index 00000000..e5752762 --- /dev/null +++ b/lib/src/model/timeline_history_response.dart @@ -0,0 +1,46 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'matrix_event.dart'; + +class TimelineHistoryResponse { + String start; + String end; + List chunk; + List state; + + TimelineHistoryResponse.fromJson(Map json) { + start = json['start']; + end = json['end']; + chunk = json['chunk'] != null + ? (json['chunk'] as List).map((i) => MatrixEvent.fromJson(i)).toList() + : null; + state = json['state'] != null + ? (json['state'] as List).map((i) => MatrixEvent.fromJson(i)).toList() + : null; + } + + Map toJson() { + final data = {}; + if (start != null) data['start'] = start; + if (end != null) data['end'] = end; + if (chunk != null) data['chunk'] = chunk.map((i) => i.toJson()); + if (state != null) data['state'] = state.map((i) => i.toJson()); + return data; + } +} diff --git a/lib/src/model/turn_server_credentials.dart b/lib/src/model/turn_server_credentials.dart new file mode 100644 index 00000000..bf350f50 --- /dev/null +++ b/lib/src/model/turn_server_credentials.dart @@ -0,0 +1,40 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class TurnServerCredentials { + String username; + String password; + List uris; + num ttl; + + TurnServerCredentials.fromJson(Map json) { + username = json['username']; + password = json['password']; + uris = json['uris'].cast(); + ttl = json['ttl']; + } + + Map toJson() { + final data = {}; + data['username'] = username; + data['password'] = password; + data['uris'] = uris; + data['ttl'] = ttl; + return data; + } +} diff --git a/lib/src/model/upload_key_signatures_response.dart b/lib/src/model/upload_key_signatures_response.dart new file mode 100644 index 00000000..325aa5e7 --- /dev/null +++ b/lib/src/model/upload_key_signatures_response.dart @@ -0,0 +1,55 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'matrix_exception.dart'; + +class UploadKeySignaturesResponse { + Map> failures; + + UploadKeySignaturesResponse.fromJson(Map json) { + failures = json['failures'] != null + ? (json['failures'] as Map).map( + (k, v) => MapEntry( + k, + (v as Map).map((k, v) => MapEntry( + k, + MatrixException.fromJson(v), + )), + ), + ) + : null; + } + + Map toJson() { + final data = {}; + if (failures != null) { + data['failures'] = failures.map( + (k, v) => MapEntry( + k, + v.map( + (k, v) => MapEntry( + k, + v.raw, + ), + ), + ), + ); + } + return data; + } +} diff --git a/lib/src/model/user_search_result.dart b/lib/src/model/user_search_result.dart new file mode 100644 index 00000000..1d498083 --- /dev/null +++ b/lib/src/model/user_search_result.dart @@ -0,0 +1,41 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'profile.dart'; + +class UserSearchResult { + List results; + bool limited; + + UserSearchResult.fromJson(Map json) { + results = []; + json['results'].forEach((v) { + results.add(Profile.fromJson(v)); + }); + + limited = json['limited']; + } + + Map toJson() { + final data = {}; + data['results'] = results.map((v) => v.toJson()).toList(); + + data['limited'] = limited; + return data; + } +} diff --git a/lib/src/model/well_known_informations.dart b/lib/src/model/well_known_informations.dart new file mode 100644 index 00000000..feba960c --- /dev/null +++ b/lib/src/model/well_known_informations.dart @@ -0,0 +1,54 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class WellKnownInformations { + MHomeserver mHomeserver; + MHomeserver mIdentityServer; + Map content; + + WellKnownInformations.fromJson(Map json) { + content = json; + mHomeserver = json['m.homeserver'] != null + ? MHomeserver.fromJson(json['m.homeserver']) + : null; + mIdentityServer = json['m.identity_server'] != null + ? MHomeserver.fromJson(json['m.identity_server']) + : null; + } + + Map toJson() { + final data = content; + data['m.homeserver'] = mHomeserver.toJson(); + data['m.identity_server'] = mIdentityServer.toJson(); + return data; + } +} + +class MHomeserver { + String baseUrl; + + MHomeserver.fromJson(Map json) { + baseUrl = json['base_url']; + } + + Map toJson() { + final data = {}; + data['base_url'] = baseUrl; + return data; + } +} diff --git a/lib/src/model/who_is_info.dart b/lib/src/model/who_is_info.dart new file mode 100644 index 00000000..6a266852 --- /dev/null +++ b/lib/src/model/who_is_info.dart @@ -0,0 +1,107 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class WhoIsInfo { + String userId; + Map devices; + + WhoIsInfo.fromJson(Map json) { + userId = json['user_id']; + devices = json['devices'] != null + ? (json['devices'] as Map) + .map((k, v) => MapEntry(k, DeviceInfo.fromJson(v))) + : null; + } + + Map toJson() { + final data = {}; + data['user_id'] = userId; + if (devices != null) { + data['devices'] = devices.map((k, v) => MapEntry(k, v.toJson())); + } + return data; + } +} + +class DeviceInfo { + List sessions; + + DeviceInfo.fromJson(Map json) { + if (json['sessions'] != null) { + sessions = []; + json['sessions'].forEach((v) { + sessions.add(Sessions.fromJson(v)); + }); + } + } + + Map toJson() { + final data = {}; + if (sessions != null) { + data['sessions'] = sessions.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Sessions { + List connections; + + Sessions.fromJson(Map json) { + if (json['connections'] != null) { + connections = []; + json['connections'].forEach((v) { + connections.add(Connections.fromJson(v)); + }); + } + } + + Map toJson() { + final data = {}; + if (connections != null) { + data['connections'] = connections.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Connections { + String ip; + int lastSeen; + String userAgent; + + Connections.fromJson(Map json) { + ip = json['ip']; + lastSeen = json['last_seen']; + userAgent = json['user_agent']; + } + + Map toJson() { + final data = {}; + if (ip != null) { + data['ip'] = ip; + } + if (lastSeen != null) { + data['last_seen'] = lastSeen; + } + if (userAgent != null) { + data['user_agent'] = userAgent; + } + return data; + } +} diff --git a/lib/src/utils/logs.dart b/lib/src/utils/logs.dart new file mode 100644 index 00000000..7e15f379 --- /dev/null +++ b/lib/src/utils/logs.dart @@ -0,0 +1,51 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:logger/logger.dart'; + +class Logs extends Logger { + static final Logs _singleton = Logs._internal(); + + factory Logs() { + return _singleton; + } + + set level(Level newLevel) => Logger.level = newLevel; + + final List outputEvents = []; + + Logs._internal() + : super( + printer: PrettyPrinter(methodCount: 0, lineLength: 100), + filter: _MatrixSdkFilter(), + output: _CacheOutput(), + ); +} + +class _MatrixSdkFilter extends LogFilter { + @override + bool shouldLog(LogEvent event) => event.level.index >= Logger.level.index; +} + +class _CacheOutput extends ConsoleOutput { + @override + void output(OutputEvent event) { + Logs().outputEvents.add(event); + super.output(event); + } +} diff --git a/lib/src/utils/try_get_map_extension.dart b/lib/src/utils/try_get_map_extension.dart new file mode 100644 index 00000000..fe8f306c --- /dev/null +++ b/lib/src/utils/try_get_map_extension.dart @@ -0,0 +1,18 @@ +import 'logs.dart'; + +extension TryGetMapExtension on Map { + T tryGet(String key, [T fallbackValue]) { + final value = this[key]; + if (value != null && !(value is T)) { + Logs().w( + 'Expected "${T.runtimeType}" in event content for the Key "$key" but got "${value.runtimeType}".'); + return fallbackValue; + } + if (value == null && fallbackValue != null) { + Logs().w( + 'Required field in event content for the Key "$key" is null. Set to "$fallbackValue".'); + return fallbackValue; + } + return value; + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..f796df2b --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,15 @@ +name: matrix_api_lite +description: A starting point for Dart libraries or applications. +# version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.10.0 <3.0.0' + +dependencies: + http: ^0.12.2 + logger: ^0.9.4 + +dev_dependencies: + pedantic: ^1.9.0 + test: ^1.14.4 diff --git a/test/fake_matrix_api.dart b/test/fake_matrix_api.dart new file mode 100644 index 00000000..8c980a4f --- /dev/null +++ b/test/fake_matrix_api.dart @@ -0,0 +1,2159 @@ +/* + * Ansible inventory script used at Famedly GmbH for managing many hosts + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'dart:convert'; +import 'dart:core'; +import 'dart:math'; + +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:matrix_api_lite/matrix_api_lite.dart'; + +Map decodeJson(dynamic data) { + if (data is String) { + return json.decode(data); + } + if (data.isEmpty) { + return {}; + } + return data; +} + +class FakeMatrixApi extends MockClient { + static final calledEndpoints = >{}; + static int eventCounter = 0; + + FakeMatrixApi() + : super((request) async { + // Collect data from Request + var action = request.url.path; + if (request.url.path.contains('/_matrix')) { + action = request.url.path.split('/_matrix').last + + '?' + + request.url.query; + } + + if (action.endsWith('?')) { + action = action.substring(0, action.length - 1); + } + if (action.endsWith('/')) { + action = action.substring(0, action.length - 1); + } + final method = request.method; + final dynamic data = + method == 'GET' ? request.url.queryParameters : request.body; + dynamic res = {}; + var statusCode = 200; + + //print('\$method request to $action with Data: $data'); + + // Sync requests with timeout + if (data is Map && data['timeout'] is String) { + await Future.delayed(Duration(seconds: 5)); + } + + if (request.url.origin != 'https://fakeserver.notexisting') { + return Response( + 'Not found...', 404); + } + + // Call API + if (!calledEndpoints.containsKey(action)) { + calledEndpoints[action] = []; + } + calledEndpoints[action].add(data); + if (api.containsKey(method) && api[method].containsKey(action)) { + res = api[method][action](data); + if (res is Map && res.containsKey('errcode')) { + statusCode = 405; + } + } else if (method == 'PUT' && + action.contains('/client/r0/sendToDevice/')) { + res = {}; + } else if (method == 'GET' && + action.contains('/client/r0/rooms/') && + action.contains('/state/m.room.member/')) { + res = {'displayname': ''}; + } else if (method == 'PUT' && + action.contains( + '/client/r0/rooms/!1234%3AfakeServer.notExisting/send/')) { + res = {'event_id': '\$event${FakeMatrixApi.eventCounter++}'}; + } else if (action.contains('/client/r0/sync')) { + res = { + 'next_batch': DateTime.now().millisecondsSinceEpoch.toString + }; + } else { + res = { + 'errcode': 'M_UNRECOGNIZED', + 'error': 'Unrecognized request' + }; + statusCode = 405; + } + + return Response.bytes(utf8.encode(json.encode(res)), statusCode); + }); + + static Map messagesResponse = { + 'start': 't47429-4392820_219380_26003_2265', + 'end': 't47409-4357353_219380_26003_2265', + 'chunk': [ + { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '3143273582443PhrSn:example.org', + 'room_id': '!1234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + { + 'content': {'name': 'The room name'}, + 'type': 'm.room.name', + 'event_id': '2143273582443PhrSn:example.org', + 'room_id': '!1234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + { + 'content': { + 'body': 'Gangnam Style', + 'url': 'mxc://example.org/a526eYUSFFxlgbQYZmo442', + 'info': { + 'thumbnail_url': 'mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe', + 'thumbnail_info': { + 'mimetype': 'image/jpeg', + 'size': 46144, + 'w': 300, + 'h': 300 + }, + 'w': 480, + 'h': 320, + 'duration': 2140786, + 'size': 1563685, + 'mimetype': 'video/mp4' + }, + 'msgtype': 'm.video' + }, + 'type': 'm.room.message', + 'event_id': '1143273582443PhrSn:example.org', + 'room_id': '!1234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + ], + 'state': [], + }; + + static Map syncResponse = { + 'next_batch': Random().nextDouble().toString(), + 'rooms': { + 'join': { + '!726s6s6q:example.com': { + 'summary': { + 'm.heroes': ['@alice:example.com', '@bob:example.com'], + 'm.joined_member_count': 2, + 'm.invited_member_count': 0 + }, + 'unread_notifications': { + 'highlight_count': 2, + 'notification_count': 2, + }, + 'state': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.room.member', + 'state_key': '@alice:example.com', + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid', + }, + 'origin_server_ts': 1417731086795, + 'event_id': '66697273743031:example.com' + }, + { + 'sender': '@alice:example.com', + 'type': 'm.room.canonical_alias', + 'content': { + 'alias': '#famedlyContactDiscovery:fakeServer.notExisting' + }, + 'state_key': '', + 'origin_server_ts': 1417731086796, + 'event_id': '66697273743032:example.com' + }, + { + 'sender': '@alice:example.com', + 'type': 'm.room.encryption', + 'state_key': '', + 'content': {'algorithm': AlgorithmTypes.megolmV1AesSha2}, + 'origin_server_ts': 1417731086795, + 'event_id': '666972737430353:example.com' + }, + { + 'content': { + 'pinned': ['1234:bla'] + }, + 'type': 'm.room.pinned_events', + 'event_id': '21432735824443PhrSn:example.org', + 'room_id': '!1234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + ] + }, + 'timeline': { + 'events': [ + { + 'sender': '@bob:example.com', + 'type': 'm.room.member', + 'state_key': '@bob:example.com', + 'content': {'membership': 'join'}, + 'prev_content': {'membership': 'invite'}, + 'origin_server_ts': 1417731086795, + 'event_id': '7365636s6r6432:example.com', + 'unsigned': {'foo': 'bar'} + }, + { + 'sender': '@alice:example.com', + 'type': 'm.room.message', + 'content': {'body': 'I am a fish', 'msgtype': 'm.text'}, + 'origin_server_ts': 1417731086797, + 'event_id': '74686972643033:example.com' + } + ], + 'limited': true, + 'prev_batch': 't34-23535_0_0' + }, + 'ephemeral': { + 'events': [ + { + 'type': 'm.typing', + 'content': { + 'user_ids': ['@alice:example.com'] + } + }, + { + 'content': { + '7365636s6r6432:example.com': { + 'm.read': { + '@alice:example.com': {'ts': 1436451550453} + } + } + }, + 'room_id': '!726s6s6q:example.com', + 'type': 'm.receipt' + } + ] + }, + 'account_data': { + 'events': [ + { + 'type': 'm.tag', + 'content': { + 'tags': { + 'work': {'order': 1} + } + } + }, + { + 'type': 'org.example.custom.room.config', + 'content': {'custom_config_key': 'custom_config_value'} + } + ] + } + } + }, + 'invite': { + '!696r7674:example.com': { + 'invite_state': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.room.name', + 'state_key': '', + 'content': {'name': 'My Room Name'} + }, + { + 'sender': '@alice:example.com', + 'type': 'm.room.member', + 'state_key': '@bob:example.com', + 'content': {'membership': 'invite'} + } + ] + } + } + }, + 'leave': { + '!726s6s6f:example.com': { + 'state': { + 'events': [ + { + 'sender': '@charley:example.com', + 'type': 'm.room.name', + 'state_key': '', + 'content': {'name': 'left room'}, + 'origin_server_ts': 1417731086795, + 'event_id': '66697273743031:example.com' + }, + ] + }, + 'timeline': { + 'events': [ + { + 'sender': '@bob:example.com', + 'type': 'm.room.message', + 'content': {'text': 'Hallo'}, + 'origin_server_ts': 1417731086795, + 'event_id': '7365636s6r64300:example.com', + 'unsigned': {'foo': 'bar'} + }, + ], + 'limited': true, + 'prev_batch': 't34-23535_0_0' + }, + 'account_data': { + 'events': [ + { + 'type': 'm.tag', + 'content': { + 'tags': { + 'work': {'order': 1} + } + } + }, + { + 'type': 'org.example.custom.room.config', + 'content': {'custom_config_key': 'custom_config_value'} + } + ] + } + } + }, + }, + 'presence': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.presence', + 'content': {'presence': 'online'} + } + ] + }, + 'account_data': { + 'events': [ + { + 'content': { + 'global': { + 'content': [ + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight'} + ], + 'default': true, + 'enabled': true, + 'pattern': 'alice', + 'rule_id': '.m.rule.contains_user_name' + } + ], + 'override': [ + { + 'actions': ['dont_notify'], + 'conditions': [], + 'default': true, + 'enabled': false, + 'rule_id': '.m.rule.master' + }, + { + 'actions': ['dont_notify'], + 'conditions': [ + { + 'key': 'content.msgtype', + 'kind': 'event_match', + 'pattern': 'm.notice' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.suppress_notices' + } + ], + 'room': [ + { + 'actions': ['dont_notify'], + 'conditions': [ + { + 'key': 'room_id', + 'kind': 'event_match', + 'pattern': '!localpart:server.abc', + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '!localpart:server.abc' + } + ], + 'sender': [], + 'underride': [ + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'ring'}, + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.call.invite' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.call' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight'} + ], + 'conditions': [ + {'kind': 'contains_display_name'} + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.contains_display_name' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + {'is': '2', 'kind': 'room_member_count'}, + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.message' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.room_one_to_one' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.member' + }, + { + 'key': 'content.membership', + 'kind': 'event_match', + 'pattern': 'invite' + }, + { + 'key': 'state_key', + 'kind': 'event_match', + 'pattern': '@alice:example.com' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.invite_for_me' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.member' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.member_event' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.message' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.message' + } + ] + } + }, + 'type': 'm.push_rules' + }, + { + 'type': 'org.example.custom.config', + 'content': {'custom_config_key': 'custom_config_value'} + }, + { + 'content': { + '@bob:example.com': [ + '!726s6s6q:example.com', + '!hgfedcba:example.com' + ] + }, + 'type': 'm.direct' + }, + { + 'type': EventTypes.SecretStorageDefaultKey, + 'content': {'key': '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3'} + }, + { + 'type': 'm.secret_storage.key.0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3', + 'content': { + 'algorithm': AlgorithmTypes.secretStorageV1AesHmcSha2, + 'passphrase': { + 'algorithm': AlgorithmTypes.pbkdf2, + 'iterations': 500000, + 'salt': 'F4jJ80mr0Fc8mRwU9JgA3lQDyjPuZXQL' + }, + 'iv': 'HjbTgIoQH2pI7jQo19NUzA==', + 'mac': 'QbJjQzDnAggU0cM4RBnDxw2XyarRGjdahcKukP9xVlk=' + } + }, + { + 'type': 'm.cross_signing.master', + 'content': { + 'encrypted': { + '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3': { + 'iv': 'eIb2IITxtmcq+1TrT8D5eQ==', + 'ciphertext': + 'lWRTPo5qxf4LAVwVPzGHOyMcP181n7bb9/B0lvkLDC2Oy4DvAL0eLx2x3bY=', + 'mac': 'Ynx89tIxPkx0o6ljMgxszww17JOgB4tg4etmNnMC9XI=' + } + } + } + }, + { + 'type': EventTypes.CrossSigningSelfSigning, + 'content': { + 'encrypted': { + '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3': { + 'iv': 'YqU2XIjYulYZl+bkZtGgVw==', + 'ciphertext': + 'kM2TSoy/jR/4d357ZoRPbpPypxQl6XRLo3FsEXz+f7vIOp82GeRp28RYb3k=', + 'mac': 'F+DZa5tAFmWsYSryw5EuEpzTmmABRab4GETkM85bGGo=' + } + } + } + }, + { + 'type': EventTypes.CrossSigningUserSigning, + 'content': { + 'encrypted': { + '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3': { + 'iv': 'D7AM3LXFu7ZlyGOkR+OeqQ==', + 'ciphertext': + 'bYA2+OMgsO6QB1E31aY+ESAWrT0fUBTXqajy4qmL7bVDSZY4Uj64EXNbHuA=', + 'mac': 'j2UtyPo/UBSoiaQCWfzCiRZXp3IRt0ZZujuXgUMjnw4=' + } + } + } + }, + { + 'type': EventTypes.MegolmBackup, + 'content': { + 'encrypted': { + '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3': { + 'iv': 'cL/0MJZaiEd3fNU+I9oJrw==', + 'ciphertext': + 'WL73Pzdk5wZdaaSpaeRH0uZYKcxkuV8IS6Qa2FEfA1+vMeRLuHcWlXbMX0w=', + 'mac': '+xozp909S6oDX8KRV8D8ZFVRyh7eEYQpPP76f+DOsnw=' + } + } + } + } + ] + }, + 'to_device': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.new_device', + 'content': { + 'device_id': 'XYZABCDE', + 'rooms': ['!726s6s6q:example.com'] + } + }, +// { +// 'sender': '@othertest:fakeServer.notExisting', +// 'content': { +// 'algorithm': AlgorithmTypes.megolmV1AesSha2, +// 'room_id': '!726s6s6q:example.com', +// 'session_id': 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU', +// 'session_key': +// 'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw' +// }, +// 'type': 'm.room_key' +// }, + { + // this is the commented out m.room_key event - only encrypted + 'sender': '@othertest:fakeServer.notExisting', + 'content': { + 'algorithm': AlgorithmTypes.olmV1Curve25519AesSha2, + 'sender_key': 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg', + 'ciphertext': { + '7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk': { + 'type': 0, + 'body': + 'Awogyh7K4iLUQjcOxIfi7q7LhBBqv9w0mQ6JI9+U9tv7iF4SIHC6xb5YFWf9voRnmDBbd+0vxD/xDlVNRDlPIKliLGkYGiAkEbtlo+fng4ELtO4gSLKVbcFn7tZwZCEUE8H2miBsCCKABgMKIFrKDJwB7gM3lXPt9yVoh6gQksafKt7VFCNRN5KLKqsDEAAi0AX5EfTV7jJ1ZWAbxftjoSN6kCVIxzGclbyg1HjchmNCX7nxNCHWl+q5ZgqHYZVu2n2mCVmIaKD0kvoEZeY3tV1Itb6zf67BLaU0qgW/QzHCHg5a44tNLjucvL2mumHjIG8k0BY2uh+52HeiMCvSOvtDwHg7nzCASGdqPVCj9Kzw6z7F6nL4e3mYim8zvJd7f+mD9z3ARrypUOLGkTGYbB2PQOovf0Do8WzcaRzfaUCnuu/YVZWKK7DPgG8uhw/TjR6XtraAKZysF+4DJYMG9SQWx558r6s7Z5EUOF5CU2M35w1t1Xxllb3vrS83dtf9LPCrBhLsEBeYEUBE2+bTBfl0BDKqLiB0Cc0N0ixOcHIt6e40wAvW622/gMgHlpNSx8xG12u0s6h6EMWdCXXLWd9fy2q6glFUHvA67A35q7O+M8DVml7Y9xG55Y3DHkMDc9cwgwFkBDCAYQe6pQF1nlKytcVCGREpBs/gq69gHAStMQ8WEg38Lf8u8eBr2DFexrN4U+QAk+S//P3fJgf0bQx/Eosx4fvWSz9En41iC+ADCsWQpMbwHn4JWvtAbn3oW0XmL/OgThTkJMLiCymduYAa1Hnt7a3tP0KTL2/x11F02ggQHL28cCjq5W4zUGjWjl5wo2PsKB6t8aAvMg2ujGD2rCjb4yrv5VIzAKMOZLyj7K0vSK9gwDLQ/4vq+QnKUBG5zrcOze0hX+kz2909/tmAdeCH61Ypw7gbPUJAKnmKYUiB/UgwkJvzMJSsk/SEs5SXosHDI+HsJHJp4Mp4iKD0xRMst+8f9aTjaWwh8ZvELE1ZOhhCbF3RXhxi3x2Nu8ORIz+vhEQ1NOlMc7UIo98Fk/96T36vL/fviowT4C/0AlaapZDJBmKwhmwqisMjY2n1vY29oM2p5BzY1iwP7q9BYdRFst6xwo57TNSuRwQw7IhFsf0k+ABuPEZy5xB5nPHyIRTf/pr3Hw', + }, + }, + }, + 'type': 'm.room.encrypted', + }, + ] + }, + 'device_lists': { + 'changed': [ + '@alice:example.com', + ], + 'left': [ + '@bob:example.com', + ], + }, + 'device_one_time_keys_count': {'curve25519': 10, 'signed_curve25519': 20}, + }; + + static Map archiveSyncResponse = { + 'next_batch': Random().nextDouble().toString(), + 'presence': {'events': []}, + 'account_data': {'events': []}, + 'to_device': {'events': []}, + 'rooms': { + 'join': {}, + 'invite': {}, + 'leave': { + '!5345234234:example.com': { + 'timeline': { + 'events': [ + { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '143273582443PhrSn:example.org', + 'room_id': '!5345234234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + ] + }, + 'state': { + 'events': [ + { + 'content': {'name': 'The room name'}, + 'type': 'm.room.name', + 'event_id': '2143273582443PhrSn:example.org', + 'room_id': '!5345234234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + ] + }, + 'account_data': { + 'events': [ + { + 'type': 'test.type.data', + 'content': {'foo': 'bar'}, + }, + ], + }, + }, + '!5345234235:example.com': { + 'timeline': {'events': []}, + 'state': { + 'events': [ + { + 'content': {'name': 'The room name 2'}, + 'type': 'm.room.name', + 'event_id': '2143273582443PhrSn:example.org', + 'room_id': '!5345234235:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + ] + } + }, + }, + } + }; + + static final Map> api = { + 'GET': { + '/path/to/auth/error': (var req) => { + 'errcode': 'M_FORBIDDEN', + 'error': 'Blabla', + }, + '/media/r0/preview_url?url=https%3A%2F%2Fmatrix.org&ts=10': (var req) => { + 'og:title': 'Matrix Blog Post', + 'og:description': 'This is a really cool blog post from matrix.org', + 'og:image': 'mxc://example.com/ascERGshawAWawugaAcauga', + 'og:image:type': 'image/png', + 'og:image:height': 48, + 'og:image:width': 48, + 'matrix:image:size': 102400 + }, + '/media/r0/config': (var req) => {'m.upload.size': 50000000}, + '/.well-known/matrix/client': (var req) => { + 'm.homeserver': {'base_url': 'https://matrix.example.com'}, + 'm.identity_server': {'base_url': 'https://identity.example.com'}, + 'org.example.custom.property': { + 'app_url': 'https://custom.app.example.org' + } + }, + '/client/r0/user/%40alice%3Aexample.com/rooms/!localpart%3Aexample.com/tags': + (var req) => { + 'tags': { + 'm.favourite': {'order': 0.1}, + 'u.Work': {'order': 0.7}, + 'u.Customers': {} + } + }, + '/client/r0/events?from=1234&timeout=10&roomId=%211234': (var req) => { + 'start': 's3456_9_0', + 'end': 's3457_9_0', + 'chunk': [ + { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!somewhere:over.the.rainbow', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + ] + }, + '/client/r0/thirdparty/location?alias=1234': (var req) => [ + { + 'alias': '#freenode_#matrix:matrix.org', + 'protocol': 'irc', + 'fields': {'network': 'freenode', 'channel': '#matrix'} + } + ], + '/client/r0/thirdparty/location/irc': (var req) => [ + { + 'alias': '#freenode_#matrix:matrix.org', + 'protocol': 'irc', + 'fields': {'network': 'freenode', 'channel': '#matrix'} + } + ], + '/client/r0/thirdparty/user/irc': (var req) => [ + { + 'userid': '@_gitter_jim:matrix.org', + 'protocol': 'gitter', + 'fields': {'user': 'jim'} + } + ], + '/client/r0/thirdparty/user?userid=1234': (var req) => [ + { + 'userid': '@_gitter_jim:matrix.org', + 'protocol': 'gitter', + 'fields': {'user': 'jim'} + } + ], + '/client/r0/thirdparty/protocol/irc': (var req) => { + 'user_fields': ['network', 'nickname'], + 'location_fields': ['network', 'channel'], + 'icon': 'mxc://example.org/aBcDeFgH', + 'field_types': { + 'network': { + 'regexp': '([a-z0-9]+\\.)*[a-z0-9]+', + 'placeholder': 'irc.example.org' + }, + 'nickname': {'regexp': '[^\\s#]+', 'placeholder': 'username'}, + 'channel': {'regexp': '#[^\\s]+', 'placeholder': '#foobar'} + }, + 'instances': [ + { + 'desc': 'Freenode', + 'icon': 'mxc://example.org/JkLmNoPq', + 'fields': {'network': 'freenode'}, + 'network_id': 'freenode' + } + ] + }, + '/client/r0/thirdparty/protocols': (var req) => { + 'irc': { + 'user_fields': ['network', 'nickname'], + 'location_fields': ['network', 'channel'], + 'icon': 'mxc://example.org/aBcDeFgH', + 'field_types': { + 'network': { + 'regexp': '([a-z0-9]+\\.)*[a-z0-9]+', + 'placeholder': 'irc.example.org' + }, + 'nickname': {'regexp': '[^\\s]+', 'placeholder': 'username'}, + 'channel': {'regexp': '#[^\\s]+', 'placeholder': '#foobar'} + }, + 'instances': [ + { + 'network_id': 'freenode', + 'desc': 'Freenode', + 'icon': 'mxc://example.org/JkLmNoPq', + 'fields': {'network': 'freenode.net'} + } + ] + }, + 'gitter': { + 'user_fields': ['username'], + 'location_fields': ['room'], + 'icon': 'mxc://example.org/aBcDeFgH', + 'field_types': { + 'username': {'regexp': '@[^\\s]+', 'placeholder': '@username'}, + 'room': { + 'regexp': '[^\\s]+\\/[^\\s]+', + 'placeholder': 'matrix-org/matrix-doc' + } + }, + 'instances': [ + { + 'network_id': 'gitter', + 'desc': 'Gitter', + 'icon': 'mxc://example.org/zXyWvUt', + 'fields': {} + } + ] + } + }, + '/client/r0/account/whoami': (var req) => + {'user_id': 'alice@example.com'}, + '/client/r0/capabilities': (var req) => { + 'capabilities': { + 'm.change_password': {'enabled': false}, + 'm.room_versions': { + 'default': '1', + 'available': { + '1': 'stable', + '2': 'stable', + '3': 'unstable', + 'test-version': 'unstable' + } + }, + 'com.example.custom.ratelimit': {'max_requests_per_hour': 600} + } + }, + '/client/r0/rooms/1234/context/1234?filter=%7B%7D&limit=10': (var req) => + { + 'end': 't29-57_2_0_2', + 'events_after': [ + { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + ], + 'event': { + 'content': { + 'body': 'filename.jpg', + 'info': { + 'h': 398, + 'w': 394, + 'mimetype': 'image/jpeg', + 'size': 31037 + }, + 'url': 'mxc://example.org/JWEIFJgwEIhweiWJE', + 'msgtype': 'm.image' + }, + 'type': 'm.room.message', + 'event_id': '\$f3h4d129462ha:example.com', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + 'events_before': [ + { + 'content': { + 'body': 'something-important.doc', + 'filename': 'something-important.doc', + 'info': {'mimetype': 'application/msword', 'size': 46144}, + 'msgtype': 'm.file', + 'url': 'mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe' + }, + 'type': 'm.room.message', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + ], + 'start': 't27-54_2_0_2', + 'state': [ + { + 'content': { + 'creator': '@example:example.org', + 'room_version': '1', + 'm.federate': true, + 'predecessor': { + 'event_id': '\$something:example.org', + 'room_id': '!oldroom:example.org' + } + }, + 'type': 'm.room.create', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.org' + } + ] + }, + '/client/r0/admin/whois/%40alice%3Aexample.com': (var req) => { + 'user_id': '@peter:rabbit.rocks', + 'devices': { + 'teapot': { + 'sessions': [ + { + 'connections': [ + { + 'ip': '127.0.0.1', + 'last_seen': 1411996332123, + 'user_agent': 'curl/7.31.0-DEV' + }, + { + 'ip': '10.0.0.2', + 'last_seen': 1411996332123, + 'user_agent': + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36' + } + ] + } + ] + } + } + }, + '/client/r0/user/%40alice%3Aexample.com/account_data/test.account.data': + (var req) => {'foo': 'bar'}, + '/client/r0/user/%40alice%3Aexample.com/rooms/1234/account_data/test.account.data': + (var req) => {'foo': 'bar'}, + '/client/r0/directory/room/%23testalias%3Aexample.com': (var reqI) => { + 'room_id': '!abnjk1jdasj98:capuchins.com', + 'servers': ['capuchins.com', 'matrix.org', 'another.com'] + }, + '/client/r0/account/3pid': (var req) => { + 'threepids': [ + { + 'medium': 'email', + 'address': 'monkey@banana.island', + 'validated_at': 1535176800000, + 'added_at': 1535336848756 + } + ] + }, + '/client/r0/devices': (var req) => { + 'devices': [ + { + 'device_id': 'QBUAZIFURK', + 'display_name': 'android', + 'last_seen_ip': '1.2.3.4', + 'last_seen_ts': 1474491775024 + } + ] + }, + '/client/r0/notifications?from=1234&limit=10&only=1234': (var req) => { + 'next_token': 'abcdef', + 'notifications': [ + { + 'actions': ['notify'], + 'profile_tag': 'hcbvkzxhcvb', + 'read': true, + 'room_id': '!abcdefg:example.com', + 'ts': 1475508881945, + 'event': { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!jEsUZKDJdhlrceRyVU:example.org', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + } + ] + }, + '/client/r0/devices/QBUAZIFURK': (var req) => { + 'device_id': 'QBUAZIFURK', + 'display_name': 'android', + 'last_seen_ip': '1.2.3.4', + 'last_seen_ts': 1474491775024 + }, + '/client/r0/profile/%40alice%3Aexample.com/displayname': (var reqI) => + {'displayname': 'Alice M'}, + '/client/r0/profile/%40alice%3Aexample.com/avatar_url': (var reqI) => + {'avatar_url': 'mxc://test'}, + '/client/r0/profile/%40alice%3Aexample.com': (var reqI) => { + 'avatar_url': 'mxc://test', + 'displayname': 'Alice M', + }, + '/client/r0/voip/turnServer': (var req) => { + 'username': '1443779631:@user:example.com', + 'password': 'JlKfBy1QwLrO20385QyAtEyIv0=', + 'uris': [ + 'turn:turn.example.com:3478?transport=udp', + 'turn:10.20.30.40:3478?transport=tcp', + 'turns:10.20.30.40:443?transport=tcp' + ], + 'ttl': 86400 + }, + '/client/r0/presence/${Uri.encodeComponent('@alice:example.com')}/status': + (var req) => { + 'presence': 'unavailable', + 'last_active_ago': 420845, + 'status_msg': 'test', + 'currently_active': false + }, + '/client/r0/keys/changes?from=1234&to=1234': (var req) => { + 'changed': ['@alice:example.com', '@bob:example.org'], + 'left': ['@clara:example.com', '@doug:example.org'] + }, + '/client/r0/pushers': (var req) => { + 'pushers': [ + { + 'pushkey': 'Xp/MzCt8/9DcSNE9cuiaoT5Ac55job3TdLSSmtmYl4A=', + 'kind': 'http', + 'app_id': 'face.mcapp.appy.prod', + 'app_display_name': 'Appy McAppface', + 'device_display_name': 'Alices Phone', + 'profile_tag': 'xyz', + 'lang': 'en-US', + 'data': { + 'url': 'https://example.com/_matrix/push/v1/notify', + 'format': 'event_id_only', + } + } + ] + }, + '/client/r0/publicRooms?limit=10&since=1234&server=example.com': + (var req) => { + 'chunk': [ + { + 'aliases': ['#murrays:cheese.bar'], + 'canonical_alias': '#murrays:cheese.bar', + 'avatar_url': 'mxc://bleeker.street/CHEDDARandBRIE', + 'guest_can_join': false, + 'name': 'CHEESE', + 'num_joined_members': 37, + 'room_id': '!ol19s:bleecker.street', + 'topic': 'Tasty tasty cheese', + 'world_readable': true + } + ], + 'next_batch': 'p190q', + 'prev_batch': 'p1902', + 'total_room_count_estimate': 115 + }, + '/client/r0/room/!localpart%3Aexample.com/aliases': (var req) => { + 'aliases': [ + '#somewhere:example.com', + '#another:example.com', + '#hat_trick:example.com' + ] + }, + '/client/r0/joined_rooms': (var req) => { + 'joined_rooms': ['!foo:example.com'] + }, + '/client/r0/directory/list/room/!localpart%3Aexample.com': (var req) => + {'visibility': 'public'}, + '/client/r0/rooms/1/state/m.room.member/@alice:example.com': (var req) => + {'displayname': 'Alice'}, + '/client/r0/profile/%40getme%3Aexample.com': (var req) => { + 'avatar_url': 'mxc://test', + 'displayname': 'You got me', + }, + '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.member/@getme%3Aexample.com': + (var req) => { + 'avatar_url': 'mxc://test', + 'displayname': 'You got me', + }, + '/client/r0/rooms/!localpart%3Aserver.abc/state': (var req) => [ + { + 'content': {'join_rule': 'public'}, + 'type': 'm.room.join_rules', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.org' + }, + { + 'content': { + 'creator': '@example:example.org', + 'room_version': '1', + 'm.federate': true, + 'predecessor': { + 'event_id': '\$something:example.org', + 'room_id': '!oldroom:example.org' + } + }, + 'type': 'm.room.create', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + { + 'content': { + 'ban': 50, + 'events': {'m.room.name': 100, 'm.room.power_levels': 100}, + 'events_default': 0, + 'invite': 50, + 'kick': 50, + 'redact': 50, + 'state_default': 50, + 'users': {'@example:localhost': 100}, + 'users_default': 0, + 'notifications': {'room': 20} + }, + 'type': 'm.room.power_levels', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + } + ], + '/client/r0/rooms/!localpart:server.abc/state/m.room.member/@getme:example.com': + (var req) => { + 'avatar_url': 'mxc://test', + 'displayname': 'You got me', + }, + '/client/r0/rooms/!localpart:server.abc/event/1234': (var req) => { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '143273582443PhrSn:example.org', + 'room_id': '!localpart:server.abc', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + '/client/r0/rooms/!localpart%3Aserver.abc/event/1234': (var req) => { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '143273582443PhrSn:example.org', + 'room_id': '!localpart:server.abc', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + '/client/r0/rooms/!localpart%3Aserver.abc/messages?from=1234&dir=b&to=1234&limit=10&filter=%7B%22lazy_load_members%22%3Atrue%7D': + (var req) => messagesResponse, + '/client/r0/rooms/!localpart%3Aserver.abc/messages?from=&dir=b&limit=10&filter=%7B%22lazy_load_members%22%3Atrue%7D': + (var req) => messagesResponse, + '/client/r0/rooms/!1234%3Aexample.com/messages?from=1234&dir=b&limit=100&filter=%7B%22lazy_load_members%22%3Atrue%7D': + (var req) => messagesResponse, + '/client/versions': (var req) => { + 'versions': [ + 'r0.0.1', + 'r0.1.0', + 'r0.2.0', + 'r0.3.0', + 'r0.4.0', + 'r0.5.0' + ], + 'unstable_features': {'m.lazy_load_members': true}, + }, + '/client/r0/login': (var req) => { + 'flows': [ + {'type': 'm.login.password'} + ] + }, + '/client/r0/rooms/!localpart%3Aserver.abc/joined_members': (var req) => { + 'joined': { + '@bar:example.com': { + 'display_name': 'Bar', + 'avatar_url': 'mxc://riot.ovh/printErCATzZijQsSDWorRaK' + } + } + }, + '/client/r0/rooms/!localpart%3Aserver.abc/members?at=1234&membership=join¬_membership=leave': + (var req) => { + 'chunk': [ + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '§143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@alice:example.com', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.com' + } + ] + }, + '/client/r0/rooms/!696r7674:example.com/members': (var req) => { + 'chunk': [ + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '§143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@alice:example.com', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.com' + } + ] + }, + '/client/r0/rooms/!726s6s6q:example.com/members': (var req) => { + 'chunk': [ + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '§143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@alice:example.com', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.com' + } + ] + }, + '/client/r0/rooms/!localpart%3Aserver.abc/members': (var req) => { + 'chunk': [ + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '§143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.org' + } + ] + }, + '/client/r0/pushrules/global/content/nocake': (var req) => { + 'actions': ['dont_notify'], + 'pattern': 'cake*lie', + 'rule_id': 'nocake', + 'enabled': true, + 'default': false + }, + '/client/r0/pushrules/global/content/nocake/enabled': (var req) => { + 'enabled': true, + }, + '/client/r0/pushrules/global/content/nocake/actions': (var req) => { + 'actions': ['notify'] + }, + '/client/r0/pushrules': (var req) => { + 'global': { + 'content': [ + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight'} + ], + 'default': true, + 'enabled': true, + 'pattern': 'alice', + 'rule_id': '.m.rule.contains_user_name' + } + ], + 'override': [ + { + 'actions': ['dont_notify'], + 'conditions': [], + 'default': true, + 'enabled': false, + 'rule_id': '.m.rule.master' + }, + { + 'actions': ['dont_notify'], + 'conditions': [ + { + 'key': 'content.msgtype', + 'kind': 'event_match', + 'pattern': 'm.notice' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.suppress_notices' + } + ], + 'room': [], + 'sender': [], + 'underride': [ + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'ring'}, + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.call.invite' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.call' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight'} + ], + 'conditions': [ + {'kind': 'contains_display_name'} + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.contains_display_name' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + {'is': '2', 'kind': 'room_member_count'} + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.room_one_to_one' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.member' + }, + { + 'key': 'content.membership', + 'kind': 'event_match', + 'pattern': 'invite' + }, + { + 'key': 'state_key', + 'kind': 'event_match', + 'pattern': '@alice:example.com' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.invite_for_me' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.member' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.member_event' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.message' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.message' + } + ] + } + }, + '/client/r0/sync?filter=%7B%22room%22%3A%7B%22include_leave%22%3Atrue%2C%22timeline%22%3A%7B%22limit%22%3A10%7D%7D%7D&timeout=0': + (var req) => archiveSyncResponse, + '/client/r0/sync?filter=%7B%22room%22%3A%7B%22state%22%3A%7B%22lazy_load_members%22%3Atrue%7D%7D%7D': + (var req) => syncResponse, + '/client/r0/sync?filter=%7B%7D&since=1234&full_state=false&set_presence=unavailable&timeout=15': + (var req) => syncResponse, + '/client/r0/register/available?username=testuser': (var req) => + {'available': true}, + '/client/r0/user/${Uri.encodeComponent('alice@example.com')}/filter/1234': + (var req) => { + 'room': { + 'state': { + 'types': ['m.room.*'], + 'not_rooms': ['!726s6s6q:example.com'] + }, + 'timeline': { + 'limit': 10, + 'types': ['m.room.message'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + }, + 'ephemeral': { + 'types': ['m.receipt', 'm.typing'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + }, + 'account_data': { + 'types': ['m.receipt', 'm.typing'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + } + }, + 'presence': { + 'types': ['m.presence'], + 'not_senders': ['@alice:example.com'] + }, + 'event_format': 'client', + 'event_fields': ['type', 'content', 'sender'] + }, + '/client/unstable/room_keys/version': (var req) => { + 'algorithm': AlgorithmTypes.megolmBackupV1Curve25519AesSha2, + 'auth_data': { + 'public_key': 'GXYaxqhNhUK28zUdxOmEsFRguz+PzBsDlTLlF0O0RkM', + 'signatures': {}, + }, + 'count': 0, + 'etag': '0', + 'version': '5', + }, + '/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}/${Uri.encodeComponent('ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU')}?version=5': + (var req) => { + 'first_message_index': 0, + 'forwarded_count': 0, + 'is_verified': true, + 'session_data': { + 'ephemeral': 'fwRxYh+seqLykz5mQCLypJ4/59URdcFJ2s69OU1dGRc', + 'ciphertext': + '19jkQYlbgdP+VL9DH3qY/Dvpk6onJZgf+6frZFl1TinPCm9OMK9AZZLuM1haS9XLAUK1YsREgjBqfl6T+Tq8JlJ5ONZGg2Wttt24sGYc0iTMZJ8rXcNDeKMZhM96ETyjufJSeYoXLqifiVLDw9rrVBmNStF7PskYp040em+0OZ4pF85Cwsdf7l9V7MMynzh9BoXqVUCBiwT03PNYH9AEmNUxXX+6ZwCpe/saONv8MgGt5uGXMZIK29phA3D8jD6uV/WOHsB8NjHNq9FrfSEAsl+dAcS4uiYie4BKSSeQN+zGAQqu1MMW4OAdxGOuf8WpIINx7n+7cKQfxlmc/Cgg5+MmIm2H0oDwQ+Xu7aSxp1OCUzbxQRdjz6+tnbYmZBuH0Ov2RbEvC5tDb261LRqKXpub0llg5fqKHl01D0ahv4OAQgRs5oU+4mq+H2QGTwIFGFqP9tCRo0I+aICawpxYOfoLJpFW6KvEPnM2Lr3sl6Nq2fmkz6RL5F7nUtzxN8OKazLQpv8DOYzXbi7+ayEsqS0/EINetq7RfCqgjrEUgfNWYuFXWqvUT8lnxLdNu+8cyrJqh1UquFjXWTw1kWcJ0pkokVeBtK9YysCnF1UYh/Iv3rl2ZoYSSLNtuvMSYlYHggZ8xV8bz9S3X2/NwBycBiWIy5Ou/OuSX7trIKgkkmda0xjBWEM1a2acVuqu2OFbMn2zFxm2a3YwKP//OlIgMg', + 'mac': 'QzKV/fgAs4U', + }, + }, + '/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}?version=5': + (var req) => { + 'sessions': { + 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU': { + 'first_message_index': 0, + 'forwarded_count': 0, + 'is_verified': true, + 'session_data': { + 'ephemeral': + 'fwRxYh+seqLykz5mQCLypJ4/59URdcFJ2s69OU1dGRc', + 'ciphertext': + '19jkQYlbgdP+VL9DH3qY/Dvpk6onJZgf+6frZFl1TinPCm9OMK9AZZLuM1haS9XLAUK1YsREgjBqfl6T+Tq8JlJ5ONZGg2Wttt24sGYc0iTMZJ8rXcNDeKMZhM96ETyjufJSeYoXLqifiVLDw9rrVBmNStF7PskYp040em+0OZ4pF85Cwsdf7l9V7MMynzh9BoXqVUCBiwT03PNYH9AEmNUxXX+6ZwCpe/saONv8MgGt5uGXMZIK29phA3D8jD6uV/WOHsB8NjHNq9FrfSEAsl+dAcS4uiYie4BKSSeQN+zGAQqu1MMW4OAdxGOuf8WpIINx7n+7cKQfxlmc/Cgg5+MmIm2H0oDwQ+Xu7aSxp1OCUzbxQRdjz6+tnbYmZBuH0Ov2RbEvC5tDb261LRqKXpub0llg5fqKHl01D0ahv4OAQgRs5oU+4mq+H2QGTwIFGFqP9tCRo0I+aICawpxYOfoLJpFW6KvEPnM2Lr3sl6Nq2fmkz6RL5F7nUtzxN8OKazLQpv8DOYzXbi7+ayEsqS0/EINetq7RfCqgjrEUgfNWYuFXWqvUT8lnxLdNu+8cyrJqh1UquFjXWTw1kWcJ0pkokVeBtK9YysCnF1UYh/Iv3rl2ZoYSSLNtuvMSYlYHggZ8xV8bz9S3X2/NwBycBiWIy5Ou/OuSX7trIKgkkmda0xjBWEM1a2acVuqu2OFbMn2zFxm2a3YwKP//OlIgMg', + 'mac': 'QzKV/fgAs4U', + }, + }, + }, + }, + '/client/unstable/room_keys/keys?version=5': (var req) => { + 'rooms': { + '!726s6s6q:example.com': { + 'sessions': { + 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU': { + 'first_message_index': 0, + 'forwarded_count': 0, + 'is_verified': true, + 'session_data': { + 'ephemeral': + 'fwRxYh+seqLykz5mQCLypJ4/59URdcFJ2s69OU1dGRc', + 'ciphertext': + '19jkQYlbgdP+VL9DH3qY/Dvpk6onJZgf+6frZFl1TinPCm9OMK9AZZLuM1haS9XLAUK1YsREgjBqfl6T+Tq8JlJ5ONZGg2Wttt24sGYc0iTMZJ8rXcNDeKMZhM96ETyjufJSeYoXLqifiVLDw9rrVBmNStF7PskYp040em+0OZ4pF85Cwsdf7l9V7MMynzh9BoXqVUCBiwT03PNYH9AEmNUxXX+6ZwCpe/saONv8MgGt5uGXMZIK29phA3D8jD6uV/WOHsB8NjHNq9FrfSEAsl+dAcS4uiYie4BKSSeQN+zGAQqu1MMW4OAdxGOuf8WpIINx7n+7cKQfxlmc/Cgg5+MmIm2H0oDwQ+Xu7aSxp1OCUzbxQRdjz6+tnbYmZBuH0Ov2RbEvC5tDb261LRqKXpub0llg5fqKHl01D0ahv4OAQgRs5oU+4mq+H2QGTwIFGFqP9tCRo0I+aICawpxYOfoLJpFW6KvEPnM2Lr3sl6Nq2fmkz6RL5F7nUtzxN8OKazLQpv8DOYzXbi7+ayEsqS0/EINetq7RfCqgjrEUgfNWYuFXWqvUT8lnxLdNu+8cyrJqh1UquFjXWTw1kWcJ0pkokVeBtK9YysCnF1UYh/Iv3rl2ZoYSSLNtuvMSYlYHggZ8xV8bz9S3X2/NwBycBiWIy5Ou/OuSX7trIKgkkmda0xjBWEM1a2acVuqu2OFbMn2zFxm2a3YwKP//OlIgMg', + 'mac': 'QzKV/fgAs4U', + }, + }, + }, + }, + }, + }, + }, + 'POST': { + '/client/r0/delete_devices': (var req) => {}, + '/client/r0/account/3pid/add': (var req) => {}, + '/client/r0/account/3pid/bind': (var req) => {}, + '/client/r0/account/3pid/delete': (var req) => + {'id_server_unbind_result': 'success'}, + '/client/r0/account/3pid/unbind': (var req) => + {'id_server_unbind_result': 'success'}, + '/client/r0/account/password': (var req) => {}, + '/client/r0/rooms/1234/report/1234': (var req) => {}, + '/client/r0/search': (var req) => { + 'search_categories': { + 'room_events': { + 'groups': { + 'room_id': { + '!qPewotXpIctQySfjSy:localhost': { + 'order': 1, + 'next_batch': 'BdgFsdfHSf-dsFD', + 'results': ['\$144429830826TWwbB:localhost'] + } + } + }, + 'highlights': ['martians', 'men'], + 'next_batch': '5FdgFsd234dfgsdfFD', + 'count': 1224, + 'results': [ + { + 'rank': 0.00424866, + 'result': { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': + 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '\$144429830826TWwbB:localhost', + 'room_id': '!qPewotXpIctQySfjSy:localhost', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + } + ] + } + } + }, + '/client/r0/account/deactivate': (var req) => + {'id_server_unbind_result': 'success'}, + '/client/r0/user_directory/search': (var req) => { + 'results': [ + { + 'user_id': '@foo:bar.com', + 'display_name': 'Foo', + 'avatar_url': 'mxc://bar.com/foo' + } + ], + 'limited': false + }, + '/client/r0/register/email/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/r0/register/msisdn/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/r0/account/password/email/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/r0/account/password/msisdn/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/r0/account/3pid/email/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/r0/account/3pid/msisdn/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/r0/rooms/!localpart%3Aexample.com/receipt/m.read/%241234%3Aexample.com': + (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/read_markers': (var req) => {}, + '/client/r0/user/${Uri.encodeComponent('alice@example.com')}/filter': + (var req) => {'filter_id': '1234'}, + '/client/r0/publicRooms?server=example.com': (var req) => { + 'chunk': [ + { + 'aliases': ['#murrays:cheese.bar'], + 'canonical_alias': '#murrays:cheese.bar', + 'avatar_url': 'mxc://bleeker.street/CHEDDARandBRIE', + 'guest_can_join': false, + 'name': 'CHEESE', + 'num_joined_members': 37, + 'room_id': '!ol19s:bleecker.street', + 'topic': 'Tasty tasty cheese', + 'world_readable': true + } + ], + 'next_batch': 'p190q', + 'prev_batch': 'p1902', + 'total_room_count_estimate': 115 + }, + '/client/r0/keys/claim': (var req) => { + 'failures': {}, + 'one_time_keys': { + if (decodeJson(req)['one_time_keys']['@alice:example.com'] != + null) + '@alice:example.com': { + 'JLAFKJWSCS': { + 'signed_curve25519:AAAAAQ': { + 'key': 'ikMXajRlkS7Xi9CROrAh3jXnbygk8mLBdSaY9/al0X0', + 'signatures': { + '@alice:example.com': { + 'ed25519:JLAFKJWSCS': + 'XdboCa0Ljoh0Y0i/IVnmMqy/+T1hJyu8BA/nRYniJMQ7QWh/pGS5AsWswdARD+MAX+r4u98Qzk0y27HUddZXDA' + } + } + } + } + }, + if (decodeJson(req)['one_time_keys'] + ['@test:fakeServer.notExisting'] != + null) + '@test:fakeServer.notExisting': { + 'GHTYAJCE': { + 'signed_curve25519:AAAAAQ': { + 'key': 'qc72ve94cA28iuE0fXa98QO3uls39DHWdQlYyvvhGh0', + 'signatures': { + '@test:fakeServer.notExisting': { + 'ed25519:GHTYAJCE': + 'dFwffr5kTKefO7sjnWLMhTzw7oV31nkPIDRxFy5OQT2OP5++Ao0KRbaBZ6qfuT7lW1owKK0Xk3s7QTBvc/eNDA', + }, + }, + }, + }, + }, + } + }, + '/client/r0/rooms/!localpart%3Aexample.com/invite': (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/leave': (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/forget': (var req) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/kick': (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/kick': (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/ban': (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/unban': (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/join': (var req) => + {'room_id': '!localpart:example.com'}, + '/client/r0/join/!localpart%3Aexample.com?server_name=example.com&server_name=example.abc': + (var req) => {'room_id': '!localpart:example.com'}, + '/client/r0/keys/upload': (var req) => { + 'one_time_key_counts': { + 'curve25519': 10, + 'signed_curve25519': + decodeJson(req)['one_time_keys']?.keys?.length ?? 0, + } + }, + '/client/r0/keys/query': (var req) => { + 'failures': {}, + 'device_keys': { + '@alice:example.com': { + 'JLAFKJWSCS': { + 'user_id': '@alice:example.com', + 'device_id': 'JLAFKJWSCS', + 'algorithms': [ + AlgorithmTypes.olmV1Curve25519AesSha2, + AlgorithmTypes.megolmV1AesSha2 + ], + 'keys': { + 'curve25519:JLAFKJWSCS': + 'L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8', + 'ed25519:JLAFKJWSCS': + 'rUFJftIWpFF/jqqz3bexGGYiG8UobKhzkeabqw1v0zM' + }, + 'signatures': { + '@alice:example.com': { + 'ed25519:JLAFKJWSCS': + 'go3mi5o3Ile+Ik+lCEpHmBmyJmKWfnRDCBBvfaVlKsMyha5IORuYcxwEUrAeLyAeeeHvkWDFX+No5eY1jYeKBw' + } + }, + 'unsigned': {'device_display_name': 'Alices mobile phone'} + }, + 'OTHERDEVICE': { + 'user_id': '@alice:example.com', + 'device_id': 'OTHERDEVICE', + 'algorithms': [ + AlgorithmTypes.olmV1Curve25519AesSha2, + AlgorithmTypes.megolmV1AesSha2 + ], + 'keys': { + 'curve25519:OTHERDEVICE': + 'wMIDhiQl5jEXQrTB03ePOSQfR8sA/KMrW0CIfFfXKEE', + 'ed25519:OTHERDEVICE': + '2Lyaj5NB7HPqKZMjZpA/pECXuQ+9wi8AGFdw33y3DuQ' + }, + 'signatures': { + '@alice:example.com': { + 'ed25519:OTHERDEVICE': + 'bwHd6ylISP13AICdDPd0HQd4V6dvvd4vno8/OwUNdm9UAprr3YjkDqVw425I74u2UQAarq9bytBqVqFyD6trAw', + } + }, + }, + }, + '@test:fakeServer.notExisting': { + 'GHTYAJCE': { + 'user_id': '@test:fakeServer.notExisting', + 'device_id': 'GHTYAJCE', + 'algorithms': [ + AlgorithmTypes.olmV1Curve25519AesSha2, + AlgorithmTypes.megolmV1AesSha2 + ], + 'keys': { + 'curve25519:GHTYAJCE': + '7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk', + 'ed25519:GHTYAJCE': + 'gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo' + }, + 'signatures': { + '@test:fakeServer.notExisting': { + 'ed25519:GHTYAJCE': + 'NEQeTgv7ew1IZSLQphWd0y60EdHdcNfHgvoaMQco5XKeIYyiUZIWd7F4x/mkPDjUizv6yWMbTDCWdSg5XcgNBA', + 'ed25519:F9ypFzgbISXCzxQhhSnXMkc1vq12Luna3Nw5rqViOJY': + 'Q4/55vZjEJD7M2EC40bgZqd9Zuy/4C75UPVopJdXeioQVaKtFf6EF0nUUuql0yD+r3hinsZcock0wO6Q2xcoAQ', + }, + }, + }, + 'OTHERDEVICE': { + 'user_id': '@test:fakeServer.notExisting', + 'device_id': 'OTHERDEVICE', + 'algorithms': [ + AlgorithmTypes.olmV1Curve25519AesSha2, + AlgorithmTypes.megolmV1AesSha2 + ], + 'keys': { + 'curve25519:OTHERDEVICE': + 'R96BA0qE1+QAWLp7E1jyWSTJ1VXMLpEdiM2SZHlKMXM', + 'ed25519:OTHERDEVICE': + 'EQo9eYbSygIbOR+tVJziqAY1NI6Gga+JQOVIqJe4mr4' + }, + 'signatures': { + '@test:fakeServer.notExisting': { + 'ed25519:OTHERDEVICE': + '/rT6pVRypJWxGos1QcI7jHL9HwcA83nkHLHqMcRPeLSxXHh4oHWvC0/tl0Xg06ogyiGw4NuB7TpOISvJBdt7BA', + 'ed25519:F9ypFzgbISXCzxQhhSnXMkc1vq12Luna3Nw5rqViOJY': + 'qnjiLl36h/1jlLvcAgt46Igaod2T9lOSnoSVkV0KC+c7vYIjG4QBzXpH+hycfufOT/y+a/kl52dUTLQWctMKCA', + }, + }, + }, + }, + '@othertest:fakeServer.notExisting': { + 'FOXDEVICE': { + 'user_id': '@othertest:fakeServer.notExisting', + 'device_id': 'FOXDEVICE', + 'algorithms': [ + AlgorithmTypes.olmV1Curve25519AesSha2, + AlgorithmTypes.megolmV1AesSha2 + ], + 'keys': { + 'curve25519:FOXDEVICE': + 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg', + 'ed25519:FOXDEVICE': + 'R5/p04tticvdlNIxiiBIP0j9OQWv8ep6eEU6/lWKDxw', + }, + 'signatures': { + '@othertest:fakeServer.notExisting': { + 'ed25519:FOXDEVICE': + '2lJ3atmRIWgkyQNC9gvWEpxwuozsBQsg33M2IMDJqLhx/+g3Ds1vQ683dJsYIu04ORa4U0L9TqieHVpV/7qqDA', + }, + }, + }, + }, + }, + 'master_keys': { + '@test:fakeServer.notExisting': { + 'user_id': '@test:fakeServer.notExisting', + 'usage': ['master'], + 'keys': { + 'ed25519:82mAXjsmbTbrE6zyShpR869jnrANO75H8nYY0nDLoJ8': + '82mAXjsmbTbrE6zyShpR869jnrANO75H8nYY0nDLoJ8', + }, + 'signatures': {}, + }, + '@othertest:fakeServer.notExisting': { + 'user_id': '@othertest:fakeServer.notExisting', + 'usage': ['master'], + 'keys': { + 'ed25519:master': 'master', + }, + 'signatures': {}, + }, + }, + 'self_signing_keys': { + '@test:fakeServer.notExisting': { + 'user_id': '@test:fakeServer.notExisting', + 'usage': ['self_signing'], + 'keys': { + 'ed25519:F9ypFzgbISXCzxQhhSnXMkc1vq12Luna3Nw5rqViOJY': + 'F9ypFzgbISXCzxQhhSnXMkc1vq12Luna3Nw5rqViOJY', + }, + 'signatures': { + '@test:fakeServer.notExisting': { + 'ed25519:82mAXjsmbTbrE6zyShpR869jnrANO75H8nYY0nDLoJ8': + 'afkrbGvPn5Zb5zc7Lk9cz2skI3QrzI/L0st1GS+/GATxNjMzc6vKmGu7r9cMb1GJxy4RdeUpfH3L7Fs/fNL1Dw', + }, + }, + }, + '@othertest:fakeServer.notExisting': { + 'user_id': '@othertest:fakeServer.notExisting', + 'usage': ['self_signing'], + 'keys': { + 'ed25519:self_signing': 'self_signing', + }, + 'signatures': {}, + }, + }, + 'user_signing_keys': { + '@test:fakeServer.notExisting': { + 'user_id': '@test:fakeServer.notExisting', + 'usage': ['user_signing'], + 'keys': { + 'ed25519:0PiwulzJ/RU86LlzSSZ8St80HUMN3dqjKa/orIJoA0g': + '0PiwulzJ/RU86LlzSSZ8St80HUMN3dqjKa/orIJoA0g', + }, + 'signatures': { + '@test:fakeServer.notExisting': { + 'ed25519:82mAXjsmbTbrE6zyShpR869jnrANO75H8nYY0nDLoJ8': + 'pvgbZxEbllaElhpiRnb7/uOIUhrglvHCFnpoxr3/5ZrWa0EK/uaefhex9eEV4uBLrHjHg2ymwdNaM7ap9+sBBg', + }, + }, + }, + '@othertest:fakeServer.notExisting': { + 'user_id': '@othertest:fakeServer.notExisting', + 'usage': ['user_signing'], + 'keys': { + 'ed25519:user_signing': 'user_signing', + }, + 'signatures': {}, + }, + }, + }, + '/client/r0/register': (var req) => { + 'user_id': '@testuser:example.com', + 'access_token': '1234', + 'device_id': 'ABCD', + }, + '/client/r0/register?kind=user': (var req) => + {'user_id': '@testuser:example.com'}, + '/client/r0/register?kind=guest': (var req) => + {'user_id': '@testuser:example.com'}, + '/client/r0/rooms/1234/upgrade': (var req) => {}, + '/client/r0/user/1234/openid/request_token': (var req) => { + 'access_token': 'SomeT0kenHere', + 'token_type': 'Bearer', + 'matrix_server_name': 'example.com', + 'expires_in': 3600.0 + }, + '/client/r0/user/@test:fakeServer.notExisting/openid/request_token': + (var req) => { + 'access_token': 'SomeT0kenHere', + 'token_type': 'Bearer', + 'matrix_server_name': 'example.com', + 'expires_in': 3600 + }, + '/client/r0/login': (var req) => { + 'user_id': '@test:fakeServer.notExisting', + 'access_token': 'abc123', + 'device_id': 'GHTYAJCE', + 'well_known': { + 'm.homeserver': {'base_url': 'https://example.org'}, + 'm.identity_server': {'base_url': 'https://id.example.org'} + } + }, + '/media/r0/upload?filename=file.jpeg': (var req) => + {'content_uri': 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'}, + '/client/r0/logout': (var reqI) => {}, + '/client/r0/pushers/set': (var reqI) => {}, + '/client/r0/join/1234': (var reqI) => {'room_id': '1234'}, + '/client/r0/logout/all': (var reqI) => {}, + '/client/r0/createRoom': (var reqI) => { + 'room_id': '!1234:fakeServer.notExisting', + }, + '/client/r0/rooms/!localpart%3Aserver.abc/read_markers': (var reqI) => {}, + '/client/r0/rooms/!localpart:server.abc/kick': (var reqI) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/ban': (var reqI) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/unban': (var reqI) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/invite': (var reqI) => {}, + '/client/unstable/keys/device_signing/upload': (var reqI) { + return {}; + }, + '/client/r0/keys/signatures/upload': (var reqI) => {'failures': {}}, + '/client/unstable/room_keys/version': (var reqI) => {'version': '5'}, + }, + 'PUT': { + '/client/r0/user/%40test%3AfakeServer.notExisting/account_data/m.ignored_user_list': + (var req) => {}, + '/client/r0/presence/${Uri.encodeComponent('@alice:example.com')}/status': + (var req) => {}, + '/client/r0/pushrules/global/content/nocake/enabled': (var req) => {}, + '/client/r0/pushrules/global/content/nocake/actions': (var req) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.history_visibility': + (var req) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.join_rules': + (var req) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.guest_access': + (var req) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.invite/1234': + (var req) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.answer/1234': + (var req) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.candidates/1234': + (var req) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.hangup/1234': + (var req) => {}, + '/client/r0/rooms/!1234%3Aexample.com/redact/1143273582443PhrSn%3Aexample.org/1234': + (var req) => {'event_id': '1234'}, + '/client/r0/pushrules/global/room/!localpart%3Aserver.abc': (var req) => + {}, + '/client/r0/pushrules/global/override/.m.rule.master/enabled': + (var req) => {}, + '/client/r0/pushrules/global/content/nocake?before=1&after=2': + (var req) => {}, + '/client/r0/devices/QBUAZIFURK': (var req) => {}, + '/client/r0/directory/room/%23testalias%3Aexample.com': (var reqI) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/send/m.room.message/testtxid': + (var reqI) => { + 'event_id': '\$event${FakeMatrixApi.eventCounter++}', + }, + '/client/r0/rooms/!localpart%3Aserver.abc/send/m.reaction/testtxid': + (var reqI) => { + 'event_id': '\$event${FakeMatrixApi.eventCounter++}', + }, + '/client/r0/rooms/!localpart%3Aexample.com/typing/%40alice%3Aexample.com': + (var req) => {}, + '/client/r0/rooms/!1234%3Aexample.com/send/m.room.message/1234': + (var reqI) => { + 'event_id': '\$event${FakeMatrixApi.eventCounter++}', + }, + '/client/r0/rooms/!1234%3Aexample.com/send/m.room.message/newresend': + (var reqI) => { + 'event_id': '\$event${FakeMatrixApi.eventCounter++}', + }, + '/client/r0/user/%40test%3AfakeServer.notExisting/rooms/!localpart%3Aserver.abc/tags/m.favourite': + (var req) => {}, + '/client/r0/user/%40alice%3Aexample.com/rooms/!localpart%3Aexample.com/tags/testtag': + (var req) => {}, + '/client/r0/user/%40alice%3Aexample.com/account_data/test.account.data': + (var req) => {}, + '/client/r0/user/%40test%3AfakeServer.notExisting/account_data/best%20animal': + (var req) => {}, + '/client/r0/user/%40alice%3Aexample.com/rooms/1234/account_data/test.account.data': + (var req) => {}, + '/client/r0/user/%40test%3AfakeServer.notExisting/rooms/!localpart%3Aserver.abc/account_data/com.famedly.marked_unread': + (var req) => {}, + '/client/r0/user/%40test%3AfakeServer.notExisting/account_data/m.direct': + (var req) => {}, + '/client/r0/user/%40othertest%3AfakeServer.notExisting/account_data/m.direct': + (var req) => {}, + '/client/r0/profile/%40alice%3Aexample.com/displayname': (var reqI) => {}, + '/client/r0/profile/%40alice%3Aexample.com/avatar_url': (var reqI) => {}, + '/client/r0/profile/%40test%3AfakeServer.notExisting/avatar_url': + (var reqI) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.encryption': + (var reqI) => {'event_id': 'YUwRidLecu:example.com'}, + '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.avatar': + (var reqI) => {'event_id': 'YUwRidLecu:example.com'}, + '/client/r0/rooms/!localpart%3Aserver.abc/send/m.room.message/1234': + (var reqI) => {'event_id': 'YUwRidLecu:example.com'}, + '/client/r0/rooms/!localpart%3Aserver.abc/redact/1234/1234': (var reqI) => + {'event_id': 'YUwRidLecu:example.com'}, + '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.name': + (var reqI) => { + 'event_id': '42', + }, + '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.topic': + (var reqI) => { + 'event_id': '42', + }, + '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.pinned_events': + (var reqI) => { + 'event_id': '42', + }, + '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.power_levels': + (var reqI) => { + 'event_id': '42', + }, + '/client/r0/directory/list/room/!localpart%3Aexample.com': (var req) => + {}, + '/client/unstable/room_keys/version/5': (var req) => {}, + '/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}/${Uri.encodeComponent('ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU')}?version=5': + (var req) => { + 'etag': 'asdf', + 'count': 1, + }, + '/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}?version=5': + (var req) => { + 'etag': 'asdf', + 'count': 1, + }, + '/client/unstable/room_keys/keys?version=5': (var req) => { + 'etag': 'asdf', + 'count': 1, + }, + }, + 'DELETE': { + '/unknown/token': (var req) => {'errcode': 'M_UNKNOWN_TOKEN'}, + '/client/r0/devices/QBUAZIFURK': (var req) => {}, + '/client/r0/directory/room/%23testalias%3Aexample.com': (var reqI) => {}, + '/client/r0/pushrules/global/content/nocake': (var req) => {}, + '/client/r0/pushrules/global/override/!localpart%3Aserver.abc': + (var req) => {}, + '/client/r0/user/%40test%3AfakeServer.notExisting/rooms/!localpart%3Aserver.abc/tags/m.favourite': + (var req) => {}, + '/client/r0/user/%40alice%3Aexample.com/rooms/!localpart%3Aexample.com/tags/testtag': + (var req) => {}, + '/client/unstable/room_keys/version/5': (var req) => {}, + '/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}/${Uri.encodeComponent('ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU')}?version=5': + (var req) => { + 'etag': 'asdf', + 'count': 1, + }, + '/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}?version=5': + (var req) => { + 'etag': 'asdf', + 'count': 1, + }, + '/client/unstable/room_keys/keys?version=5': (var req) => { + 'etag': 'asdf', + 'count': 1, + }, + }, + }; +} diff --git a/test/matrix_api_test.dart b/test/matrix_api_test.dart new file mode 100644 index 00000000..d545bf11 --- /dev/null +++ b/test/matrix_api_test.dart @@ -0,0 +1,1915 @@ +/* + * Ansible inventory script used at Famedly GmbH for managing many hosts + * Copyright (C) 2019, 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'dart:typed_data'; +import 'package:logger/logger.dart'; +import 'package:matrix_api_lite/matrix_api_lite.dart'; +import 'package:test/test.dart'; + +import 'fake_matrix_api.dart'; + +void main() { + /// All Tests related to device keys + group('Matrix API', () { + Logs().level = Level.error; + final matrixApi = MatrixApi( + httpClient: FakeMatrixApi(), + ); + test('MatrixException test', () async { + final exception = MatrixException.fromJson({ + 'flows': [ + { + 'stages': ['example.type.foo'] + } + ], + 'params': { + 'example.type.baz': {'example_key': 'foobar'} + }, + 'session': 'xxxxxxyz', + 'completed': ['example.type.foo'] + }); + expect( + exception.authenticationFlows.first.stages.first, 'example.type.foo'); + expect(exception.authenticationParams['example.type.baz'], + {'example_key': 'foobar'}); + expect(exception.session, 'xxxxxxyz'); + expect(exception.completedAuthenticationFlows, ['example.type.foo']); + expect(exception.requireAdditionalAuthentication, true); + expect(exception.retryAfterMs, null); + expect(exception.error, MatrixError.M_UNKNOWN); + expect(exception.errcode, 'M_FORBIDDEN'); + expect(exception.errorMessage, 'Require additional authentication'); + }); + test('triggerNotFoundError', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + bool error; + error = false; + try { + await matrixApi.request(RequestType.GET, '/fake/path'); + } catch (_) { + error = true; + } + expect(error, true); + error = false; + try { + await matrixApi.request(RequestType.POST, '/fake/path'); + } catch (_) { + error = true; + } + expect(error, true); + error = false; + try { + await matrixApi.request(RequestType.PUT, '/fake/path'); + } catch (_) { + error = true; + } + expect(error, true); + error = false; + try { + await matrixApi.request(RequestType.DELETE, '/fake/path'); + } catch (_) { + error = true; + } + expect(error, true); + error = false; + try { + await matrixApi.request(RequestType.GET, '/path/to/auth/error/'); + } catch (exception) { + expect(exception is MatrixException, true); + expect((exception as MatrixException).errcode, 'M_FORBIDDEN'); + expect((exception as MatrixException).error, MatrixError.M_FORBIDDEN); + expect((exception as MatrixException).errorMessage, 'Blabla'); + expect((exception as MatrixException).requireAdditionalAuthentication, + false); + expect( + (exception as MatrixException).toString(), 'M_FORBIDDEN: Blabla'); + error = true; + } + expect(error, true); + matrixApi.homeserver = null; + }); + test('getSupportedVersions', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final supportedVersions = await matrixApi.requestSupportedVersions(); + expect(supportedVersions.versions.contains('r0.5.0'), true); + expect(supportedVersions.unstableFeatures['m.lazy_load_members'], true); + expect(FakeMatrixApi.api['GET']['/client/versions']({}), + supportedVersions.toJson()); + matrixApi.homeserver = null; + }); + test('getWellKnownInformations', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final wellKnownInformations = + await matrixApi.requestWellKnownInformations(); + expect(wellKnownInformations.mHomeserver.baseUrl, + 'https://matrix.example.com'); + expect(wellKnownInformations.toJson(), { + 'm.homeserver': {'base_url': 'https://matrix.example.com'}, + 'm.identity_server': {'base_url': 'https://identity.example.com'}, + 'org.example.custom.property': { + 'app_url': 'https://custom.app.example.org' + } + }); + }); + test('getLoginTypes', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final loginTypes = await matrixApi.requestLoginTypes(); + expect(loginTypes.flows.first.type, 'm.login.password'); + expect(FakeMatrixApi.api['GET']['/client/r0/login']({}), + loginTypes.toJson()); + matrixApi.homeserver = null; + }); + test('login', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final loginResponse = + await matrixApi.login(userIdentifierType: 'username'); + expect(FakeMatrixApi.api['POST']['/client/r0/login']({}), + loginResponse.toJson()); + matrixApi.homeserver = null; + }); + test('logout', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.logout(); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('logoutAll', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.logoutAll(); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('register', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final registerResponse = + await matrixApi.register(kind: 'guest', username: 'test'); + expect(FakeMatrixApi.api['POST']['/client/r0/register?kind=guest']({}), + registerResponse.toJson()); + matrixApi.homeserver = null; + }); + test('requestEmailToken', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.requestEmailToken( + 'alice@example.com', + '1234', + 1, + nextLink: 'https://example.com', + idServer: 'https://example.com', + idAccessToken: '1234', + ); + expect( + FakeMatrixApi.api['POST'] + ['/client/r0/register/email/requestToken']({}), + response.toJson()); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestMsisdnToken', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.requestMsisdnToken( + 'en', + '1234', + '1234', + 1, + nextLink: 'https://example.com', + idServer: 'https://example.com', + idAccessToken: '1234', + ); + expect( + FakeMatrixApi.api['POST'] + ['/client/r0/register/email/requestToken']({}), + response.toJson()); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('changePassword', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.changePassword( + '1234', + auth: AuthenticationData.fromJson({ + 'type': 'example.type.foo', + 'session': 'xxxxx', + 'example_credential': 'verypoorsharedsecret' + }), + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('resetPasswordUsingEmail', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.resetPasswordUsingEmail( + 'alice@example.com', + '1234', + 1, + nextLink: 'https://example.com', + idServer: 'https://example.com', + idAccessToken: '1234', + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('resetPasswordUsingMsisdn', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.resetPasswordUsingMsisdn( + 'en', + '1234', + '1234', + 1, + nextLink: 'https://example.com', + idServer: 'https://example.com', + idAccessToken: '1234', + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('deactivateAccount', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.deactivateAccount( + idServer: 'https://example.com', + auth: AuthenticationData.fromJson({ + 'type': 'example.type.foo', + 'session': 'xxxxx', + 'example_credential': 'verypoorsharedsecret' + }), + ); + expect(response, IdServerUnbindResult.success); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('usernameAvailable', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final loginResponse = await matrixApi.usernameAvailable('testuser'); + expect(loginResponse, true); + matrixApi.homeserver = null; + }); + test('getThirdPartyIdentifiers', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.requestThirdPartyIdentifiers(); + expect(FakeMatrixApi.api['GET']['/client/r0/account/3pid']({}), + {'threepids': response.map((t) => t.toJson()).toList()}); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('addThirdPartyIdentifier', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.addThirdPartyIdentifier('1234', '1234', + auth: AuthenticationData.fromJson({'type': 'm.login.dummy'})); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('bindThirdPartyIdentifier', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.bindThirdPartyIdentifier( + '1234', + '1234', + 'https://example.com', + '1234', + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('deleteThirdPartyIdentifier', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.deleteThirdPartyIdentifier( + 'alice@example.com', + ThirdPartyIdentifierMedium.email, + idServer: 'https://example.com', + ); + expect(response, IdServerUnbindResult.success); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('unbindThirdPartyIdentifier', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.unbindThirdPartyIdentifier( + 'alice@example.com', + ThirdPartyIdentifierMedium.email, + 'https://example.com', + ); + expect(response, IdServerUnbindResult.success); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestEmailValidationToken', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.requestEmailValidationToken( + 'alice@example.com', + '1234', + 1, + nextLink: 'https://example.com', + idServer: 'https://example.com', + idAccessToken: '1234', + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestMsisdnValidationToken', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.requestMsisdnValidationToken( + 'en', + '1234', + '1234', + 1, + nextLink: 'https://example.com', + idServer: 'https://example.com', + idAccessToken: '1234', + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestMsisdnValidationToken', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.whoAmI(); + expect(response, 'alice@example.com'); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('getServerCapabilities', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.requestServerCapabilities(); + expect(FakeMatrixApi.api['GET']['/client/r0/capabilities']({}), + {'capabilities': response.toJson()}); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('uploadFilter', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = + await matrixApi.uploadFilter('alice@example.com', Filter()); + expect(response, '1234'); + final filter = Filter( + room: RoomFilter( + notRooms: ['!1234'], + rooms: ['!1234'], + ephemeral: StateFilter( + limit: 10, + senders: ['@alice:example.com'], + types: ['type1'], + notTypes: ['type2'], + notRooms: ['!1234'], + notSenders: ['@bob:example.com'], + lazyLoadMembers: true, + includeRedundantMembers: false, + containsUrl: true, + ), + includeLeave: true, + state: StateFilter(), + timeline: StateFilter(), + accountData: StateFilter(limit: 10, types: ['type1']), + ), + presence: EventFilter( + limit: 10, + senders: ['@alice:example.com'], + types: ['type1'], + notRooms: ['!1234'], + notSenders: ['@bob:example.com'], + ), + eventFormat: EventFormat.client, + eventFields: ['type', 'content', 'sender'], + accountData: EventFilter( + types: ['m.accountdatatest'], + notSenders: ['@alice:example.com'], + ), + ); + expect(filter.toJson(), { + 'room': { + 'not_rooms': ['!1234'], + 'rooms': ['!1234'], + 'ephemeral': { + 'limit': 10, + 'types': ['type1'], + 'not_rooms': ['!1234'], + 'not_senders': ['@bob:example.com'], + 'not_types': ['type2'], + 'lazy_load_members': ['type2'], + 'include_redundant_members': ['type2'], + 'contains_url': ['type2'] + }, + 'account_data': { + 'limit': 10, + 'types': ['type1'], + }, + 'include_leave': true, + 'state': {}, + 'timeline': {}, + }, + 'presence': { + 'limit': 10, + 'types': ['type1'], + 'not_rooms': ['!1234'], + 'not_senders': ['@bob:example.com'] + }, + 'event_format': 'client', + 'event_fields': ['type', 'content', 'sender'], + 'account_data': { + 'types': ['m.accountdatatest'], + 'not_senders': ['@alice:example.com'] + }, + }); + await matrixApi.uploadFilter( + 'alice@example.com', + filter, + ); + final filterMap = { + 'room': { + 'state': { + 'types': ['m.room.*'], + 'not_rooms': ['!726s6s6q:example.com'] + }, + 'timeline': { + 'limit': 10, + 'types': ['m.room.message'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + }, + 'ephemeral': { + 'types': ['m.receipt', 'm.typing'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + } + }, + 'presence': { + 'types': ['m.presence'], + 'not_senders': ['@alice:example.com'] + }, + 'account_data': { + 'types': ['m.accountdatatest'], + 'not_senders': ['@alice:example.com'] + }, + 'event_format': 'client', + 'event_fields': ['type', 'content', 'sender'] + }; + expect(filterMap, Filter.fromJson(filterMap).toJson()); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('downloadFilter', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.downloadFilter('alice@example.com', '1234'); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sync', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.sync( + filter: '{}', + since: '1234', + fullState: false, + setPresence: PresenceType.unavailable, + timeout: 15, + ); + expect( + FakeMatrixApi.api['GET'][ + '/client/r0/sync?filter=%7B%7D&since=1234&full_state=false&set_presence=unavailable&timeout=15']( + {}) as Map, + response.toJson()); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestEvent', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final event = + await matrixApi.requestEvent('!localpart:server.abc', '1234'); + expect(event.eventId, '143273582443PhrSn:example.org'); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestStateContent', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.requestStateContent( + '!localpart:server.abc', + 'm.room.member', + '@getme:example.com', + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestStates', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final states = await matrixApi.requestStates('!localpart:server.abc'); + expect(states.length, 4); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestMembers', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final states = await matrixApi.requestMembers( + '!localpart:server.abc', + at: '1234', + membership: Membership.join, + notMembership: Membership.leave, + ); + expect(states.length, 1); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestJoinedMembers', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final states = await matrixApi.requestJoinedMembers( + '!localpart:server.abc', + ); + expect(states.length, 1); + expect(states['@bar:example.com'].toJson(), { + 'display_name': 'Bar', + 'avatar_url': 'mxc://riot.ovh/printErCATzZijQsSDWorRaK' + }); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestMessages', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final timelineHistoryResponse = await matrixApi.requestMessages( + '!localpart:server.abc', + '1234', + Direction.b, + limit: 10, + filter: '{"lazy_load_members":true}', + to: '1234', + ); + + expect( + FakeMatrixApi.api['GET'][ + '/client/r0/rooms/!localpart%3Aserver.abc/messages?from=1234&dir=b&to=1234&limit=10&filter=%7B%22lazy_load_members%22%3Atrue%7D']( + {}) as Map, + timelineHistoryResponse.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendState', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final eventId = await matrixApi.sendState( + '!localpart:server.abc', 'm.room.avatar', {'url': 'mxc://1234'}); + + expect(eventId, 'YUwRidLecu:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendMessage', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final eventId = await matrixApi.sendMessage( + '!localpart:server.abc', + 'm.room.message', + '1234', + {'body': 'hello world', 'msgtype': 'm.text'}, + ); + + expect(eventId, 'YUwRidLecu:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('redact', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final eventId = await matrixApi.redact( + '!localpart:server.abc', + '1234', + '1234', + reason: 'hello world', + ); + + expect(eventId, 'YUwRidLecu:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('createRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = await matrixApi.createRoom( + visibility: Visibility.public, + roomAliasName: '#testroom:example.com', + name: 'testroom', + topic: 'just for testing', + invite: ['@bob:example.com'], + invite3pid: [], + roomVersion: '2', + creationContent: {}, + initialState: [], + preset: CreateRoomPreset.public_chat, + isDirect: false, + powerLevelContentOverride: {}, + ); + + expect(roomId, '!1234:fakeServer.notExisting'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('createRoomAlias', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.createRoomAlias( + '#testalias:example.com', + '!1234:example.com', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestRoomAliasInformations', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomAliasInformations = + await matrixApi.requestRoomAliasInformations( + '#testalias:example.com', + ); + + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/directory/room/%23testalias%3Aexample.com']({}), + roomAliasInformations.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('removeRoomAlias', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.removeRoomAlias('#testalias:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestRoomAliases', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final list = await matrixApi.requestRoomAliases('!localpart:example.com'); + expect(list.length, 3); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestJoinedRooms', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final list = await matrixApi.requestJoinedRooms(); + expect(list.length, 1); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('inviteToRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.inviteToRoom( + '!localpart:example.com', '@bob:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('joinRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = '!localpart:example.com'; + final response = await matrixApi.joinRoom( + roomId, + thirdPidSignedSender: '@bob:example.com', + thirdPidSignedmxid: '@alice:example.com', + thirdPidSignedToken: '1234', + thirdPidSignedSiganture: { + 'example.org': {'ed25519:0': 'some9signature'} + }, + ); + expect(response, roomId); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('joinRoomOrAlias', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = '!localpart:example.com'; + final response = await matrixApi.joinRoomOrAlias( + roomId, + servers: ['example.com', 'example.abc'], + thirdPidSignedSender: '@bob:example.com', + thirdPidSignedmxid: '@alice:example.com', + thirdPidSignedToken: '1234', + thirdPidSignedSiganture: { + 'example.org': {'ed25519:0': 'some9signature'} + }, + ); + expect(response, roomId); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('leave', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.leaveRoom('!localpart:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('forget', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.forgetRoom('!localpart:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('kickFromRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.kickFromRoom( + '!localpart:example.com', + '@bob:example.com', + reason: 'test', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('banFromRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.banFromRoom( + '!localpart:example.com', + '@bob:example.com', + reason: 'test', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('unbanInRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.unbanInRoom( + '!localpart:example.com', + '@bob:example.com', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestRoomVisibility', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final visibility = + await matrixApi.requestRoomVisibility('!localpart:example.com'); + expect(visibility, Visibility.public); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setRoomVisibility', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setRoomVisibility( + '!localpart:example.com', Visibility.private); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPublicRooms', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestPublicRooms( + limit: 10, + since: '1234', + server: 'example.com', + ); + + expect( + FakeMatrixApi.api['GET'][ + '/client/r0/publicRooms?limit=10&since=1234&server=example.com']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('searchPublicRooms', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.searchPublicRooms( + limit: 10, + since: '1234', + server: 'example.com', + genericSearchTerm: 'test', + includeAllNetworks: false, + thirdPartyInstanceId: 'id', + ); + + expect( + FakeMatrixApi.api['POST'] + ['/client/r0/publicRooms?server=example.com']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('searchUser', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.searchUser( + 'test', + limit: 10, + ); + + expect(FakeMatrixApi.api['POST']['/client/r0/user_directory/search']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setDisplayname', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setDisplayname('@alice:example.com', 'Alice M'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestDisplayname', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.requestDisplayname('@alice:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setAvatarUrl', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setAvatarUrl( + '@alice:example.com', + Uri.parse('mxc://test'), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestAvatarUrl', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestAvatarUrl('@alice:example.com'); + expect(response, Uri.parse('mxc://test')); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestProfile', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestProfile('@alice:example.com'); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/profile/%40alice%3Aexample.com']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestTurnServerCredentials', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestTurnServerCredentials(); + expect(FakeMatrixApi.api['GET']['/client/r0/voip/turnServer']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendTypingNotification', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.sendTypingNotification( + '@alice:example.com', + '!localpart:example.com', + true, + timeout: 10, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendReceiptMarker', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.sendReceiptMarker( + '!localpart:example.com', + '\$1234:example.com', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendReadMarker', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.sendReadMarker( + '!localpart:example.com', + '\$1234:example.com', + readReceiptLocationEventId: '\$1234:example.com', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendPresence', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.sendPresence( + '@alice:example.com', + PresenceType.offline, + statusMsg: 'test', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPresence', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestPresence( + '@alice:example.com', + ); + expect( + FakeMatrixApi.api['GET'][ + '/client/r0/presence/${Uri.encodeComponent('@alice:example.com')}/status']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('upload', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final response = await matrixApi.upload(Uint8List(0), 'file.jpeg'); + expect(response, 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'); + var throwsException = false; + try { + await matrixApi.upload(Uint8List(0), 'file.jpg'); + } on MatrixException catch (_) { + throwsException = true; + } + expect(throwsException, true); + matrixApi.homeserver = null; + }); + test('requestOpenGraphDataForUrl', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final openGraphData = await matrixApi.requestOpenGraphDataForUrl( + Uri.parse('https://matrix.org'), + ts: 10, + ); + expect( + FakeMatrixApi.api['GET'] + ['/media/r0/preview_url?url=https%3A%2F%2Fmatrix.org&ts=10']({}), + openGraphData.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestMaxUploadSize', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestMaxUploadSize(); + expect(response, 50000000); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendToDevice', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.sendToDevice('m.test', '1234', { + '@alice:example.com': { + 'TLLBEANAAG': {'example_content_key': 'value'} + } + }); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestDevices', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final devices = await matrixApi.requestDevices(); + expect(FakeMatrixApi.api['GET']['/client/r0/devices']({})['devices'], + devices.map((i) => i.toJson()).toList()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestDevice', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.requestDevice('QBUAZIFURK'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setDeviceMetadata', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setDeviceMetadata('QBUAZIFURK', displayName: 'test'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('deleteDevice', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.deleteDevice('QBUAZIFURK', + auth: AuthenticationData.fromJson({})); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('deleteDevices', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi + .deleteDevices(['QBUAZIFURK'], auth: AuthenticationData.fromJson({})); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('uploadDeviceKeys', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.uploadDeviceKeys( + deviceKeys: MatrixDeviceKeys( + '@alice:example.com', + 'ABCD', + ['caesar-chiffre'], + {}, + {}, + unsigned: {}, + ), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestDeviceKeys', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestDeviceKeys( + { + '@alice:example.com': [], + }, + timeout: 10, + token: '1234', + ); + expect( + response + .deviceKeys['@alice:example.com']['JLAFKJWSCS'].deviceDisplayName, + 'Alices mobile phone'); + expect( + FakeMatrixApi.api['POST'] + ['/client/r0/keys/query']({'device_keys': {}}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestOneTimeKeys', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestOneTimeKeys( + { + '@alice:example.com': {'JLAFKJWSCS': 'signed_curve25519'} + }, + timeout: 10, + ); + expect( + FakeMatrixApi.api['POST']['/client/r0/keys/claim']({ + 'one_time_keys': { + '@alice:example.com': {'JLAFKJWSCS': 'signed_curve25519'} + } + }), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestDeviceListsUpdate', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.requestDeviceListsUpdate('1234', '1234'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('uploadDeviceSigningKeys', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final masterKey = MatrixCrossSigningKey.fromJson({ + 'user_id': '@test:fakeServer.notExisting', + 'usage': ['master'], + 'keys': { + 'ed25519:82mAXjsmbTbrE6zyShpR869jnrANO75H8nYY0nDLoJ8': + '82mAXjsmbTbrE6zyShpR869jnrANO75H8nYY0nDLoJ8', + }, + 'signatures': {}, + }); + final selfSigningKey = MatrixCrossSigningKey.fromJson({ + 'user_id': '@test:fakeServer.notExisting', + 'usage': ['self_signing'], + 'keys': { + 'ed25519:F9ypFzgbISXCzxQhhSnXMkc1vq12Luna3Nw5rqViOJY': + 'F9ypFzgbISXCzxQhhSnXMkc1vq12Luna3Nw5rqViOJY', + }, + 'signatures': {}, + }); + final userSigningKey = MatrixCrossSigningKey.fromJson({ + 'user_id': '@test:fakeServer.notExisting', + 'usage': ['user_signing'], + 'keys': { + 'ed25519:0PiwulzJ/RU86LlzSSZ8St80HUMN3dqjKa/orIJoA0g': + '0PiwulzJ/RU86LlzSSZ8St80HUMN3dqjKa/orIJoA0g', + }, + 'signatures': {}, + }); + await matrixApi.uploadDeviceSigningKeys( + masterKey: masterKey, + selfSigningKey: selfSigningKey, + userSigningKey: userSigningKey); + }); + test('uploadKeySignatures', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final key1 = MatrixDeviceKeys.fromJson({ + 'user_id': '@alice:example.com', + 'device_id': 'JLAFKJWSCS', + 'algorithms': [ + AlgorithmTypes.olmV1Curve25519AesSha2, + AlgorithmTypes.megolmV1AesSha2 + ], + 'keys': { + 'curve25519:JLAFKJWSCS': + '3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI', + 'ed25519:JLAFKJWSCS': 'lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI' + }, + 'signatures': { + '@alice:example.com': { + 'ed25519:JLAFKJWSCS': + 'dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA' + } + }, + 'unsigned': {'device_display_name': 'Alices mobile phone'}, + }); + final key2 = MatrixDeviceKeys.fromJson({ + 'user_id': '@alice:example.com', + 'device_id': 'JLAFKJWSCS', + 'algorithms': [ + AlgorithmTypes.olmV1Curve25519AesSha2, + AlgorithmTypes.megolmV1AesSha2 + ], + 'keys': { + 'curve25519:JLAFKJWSCS': + '3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI', + 'ed25519:JLAFKJWSCS': 'lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI' + }, + 'signatures': { + '@alice:example.com': {'ed25519:OTHERDEVICE': 'OTHERSIG'} + }, + 'unsigned': {'device_display_name': 'Alices mobile phone'}, + }); + final ret = await matrixApi.uploadKeySignatures([key1, key2]); + expect( + FakeMatrixApi.api['POST']['/client/r0/keys/signatures/upload']({}), + ret.toJson(), + ); + }); + test('requestPushers', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestPushers(); + expect( + FakeMatrixApi.api['GET']['/client/r0/pushers']({}), + {'pushers': response.map((i) => i.toJson()).toList()}, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setPusher', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setPusher( + Pusher( + '1234', + 'app.id', + 'appDisplayName', + 'deviceDisplayName', + 'en', + PusherData( + format: 'event_id_only', url: Uri.parse('https://matrix.org')), + profileTag: 'tag', + kind: 'http', + ), + append: true, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestNotifications', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestNotifications( + from: '1234', + limit: 10, + only: '1234', + ); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/notifications?from=1234&limit=10&only=1234']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPushRules', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestPushRules(); + expect( + FakeMatrixApi.api['GET']['/client/r0/pushrules']({}), + {'global': response.toJson()}, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPushRule', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestPushRule( + 'global', PushRuleKind.content, 'nocake'); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/pushrules/global/content/nocake']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('deletePushRule', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.deletePushRule('global', PushRuleKind.content, 'nocake'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setPushRule', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setPushRule( + 'global', + PushRuleKind.content, + 'nocake', + [PushRuleAction.notify], + before: '1', + after: '2', + conditions: [ + PushConditions( + 'event_match', + key: 'key', + pattern: 'pattern', + isOperator: '+', + ) + ], + pattern: 'pattern', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPushRuleEnabled', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final enabled = await matrixApi.requestPushRuleEnabled( + 'global', PushRuleKind.content, 'nocake'); + expect(enabled, true); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('enablePushRule', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.enablePushRule( + 'global', + PushRuleKind.content, + 'nocake', + true, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPushRuleActions', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final actions = await matrixApi.requestPushRuleActions( + 'global', PushRuleKind.content, 'nocake'); + expect(actions.first, PushRuleAction.notify); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setPushRuleActions', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setPushRuleActions( + 'global', + PushRuleKind.content, + 'nocake', + [PushRuleAction.dont_notify], + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('globalSearch', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.globalSearch({}); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('globalSearch', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestEvents( + from: '1234', roomId: '!1234', timeout: 10); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/events?from=1234&timeout=10&roomId=%211234']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestRoomTags', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestRoomTags( + '@alice:example.com', '!localpart:example.com'); + expect( + FakeMatrixApi.api['GET'][ + '/client/r0/user/%40alice%3Aexample.com/rooms/!localpart%3Aexample.com/tags']({}), + {'tags': response.map((k, v) => MapEntry(k, v.toJson()))}, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('addRoomTag', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.addRoomTag( + '@alice:example.com', + '!localpart:example.com', + 'testtag', + order: 0.5, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('addRoomTag', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.removeRoomTag( + '@alice:example.com', + '!localpart:example.com', + 'testtag', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setAccountData', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setAccountData( + '@alice:example.com', + 'test.account.data', + {'foo': 'bar'}, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestAccountData', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.requestAccountData( + '@alice:example.com', + 'test.account.data', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setRoomAccountData', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setRoomAccountData( + '@alice:example.com', + '1234', + 'test.account.data', + {'foo': 'bar'}, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestRoomAccountData', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.requestRoomAccountData( + '@alice:example.com', + '1234', + 'test.account.data', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestWhoIsInfo', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestWhoIsInfo('@alice:example.com'); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/admin/whois/%40alice%3Aexample.com']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestEventContext', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestEventContext('1234', '1234', + limit: 10, filter: '{}'); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/rooms/1234/context/1234?filter=%7B%7D&limit=10']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('reportEvent', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.reportEvent( + '1234', + '1234', + 'test', + -100, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestSupportedProtocols', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestSupportedProtocols(); + expect( + FakeMatrixApi.api['GET']['/client/r0/thirdparty/protocols']({}), + response.map((k, v) => MapEntry(k, v.toJson())), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestSupportedProtocol', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestSupportedProtocol('irc'); + expect( + FakeMatrixApi.api['GET']['/client/r0/thirdparty/protocol/irc']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestThirdPartyLocations', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestThirdPartyLocations('irc'); + expect( + FakeMatrixApi.api['GET']['/client/r0/thirdparty/location/irc']({}), + response.map((i) => i.toJson()).toList(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestThirdPartyUsers', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestThirdPartyUsers('irc'); + expect( + FakeMatrixApi.api['GET']['/client/r0/thirdparty/user/irc']({}), + response.map((i) => i.toJson()).toList(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestThirdPartyLocationsByAlias', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = + await matrixApi.requestThirdPartyLocationsByAlias('1234'); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/thirdparty/location?alias=1234']({}), + response.map((i) => i.toJson()).toList(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestThirdPartyUsersByUserId', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestThirdPartyUsersByUserId('1234'); + expect( + FakeMatrixApi.api['GET']['/client/r0/thirdparty/user?userid=1234']({}), + response.map((i) => i.toJson()).toList(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestOpenIdCredentials', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestOpenIdCredentials('1234'); + expect( + FakeMatrixApi.api['POST'] + ['/client/r0/user/1234/openid/request_token']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('upgradeRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.upgradeRoom('1234', '2'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('createRoomKeysBackup', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final algorithm = RoomKeysAlgorithmType.v1Curve25519AesSha2; + final authData = { + 'public_key': 'GXYaxqhNhUK28zUdxOmEsFRguz+PzBsDlTLlF0O0RkM', + 'signatures': {}, + }; + final ret = await matrixApi.createRoomKeysBackup(algorithm, authData); + expect( + FakeMatrixApi.api['POST'] + ['/client/unstable/room_keys/version']({})['version'], + ret); + }); + test('getRoomKeysBackup', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final ret = await matrixApi.getRoomKeysBackup(); + expect(FakeMatrixApi.api['GET']['/client/unstable/room_keys/version']({}), + ret.toJson()); + }); + test('updateRoomKeysBackup', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final algorithm = RoomKeysAlgorithmType.v1Curve25519AesSha2; + final authData = { + 'public_key': 'GXYaxqhNhUK28zUdxOmEsFRguz+PzBsDlTLlF0O0RkM', + 'signatures': {}, + }; + await matrixApi.updateRoomKeysBackup('5', algorithm, authData); + }); + test('deleteRoomKeysBackup', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.deleteRoomKeysBackup('5'); + }); + test('storeRoomKeysSingleKey', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = '!726s6s6q:example.com'; + final sessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU'; + final session = RoomKeysSingleKey.fromJson({ + 'first_message_index': 0, + 'forwarded_count': 0, + 'is_verified': true, + 'session_data': { + 'ephemeral': 'fwRxYh+seqLykz5mQCLypJ4/59URdcFJ2s69OU1dGRc', + 'ciphertext': + '19jkQYlbgdP+VL9DH3qY/Dvpk6onJZgf+6frZFl1TinPCm9OMK9AZZLuM1haS9XLAUK1YsREgjBqfl6T+Tq8JlJ5ONZGg2Wttt24sGYc0iTMZJ8rXcNDeKMZhM96ETyjufJSeYoXLqifiVLDw9rrVBmNStF7PskYp040em+0OZ4pF85Cwsdf7l9V7MMynzh9BoXqVUCBiwT03PNYH9AEmNUxXX+6ZwCpe/saONv8MgGt5uGXMZIK29phA3D8jD6uV/WOHsB8NjHNq9FrfSEAsl+dAcS4uiYie4BKSSeQN+zGAQqu1MMW4OAdxGOuf8WpIINx7n+7cKQfxlmc/Cgg5+MmIm2H0oDwQ+Xu7aSxp1OCUzbxQRdjz6+tnbYmZBuH0Ov2RbEvC5tDb261LRqKXpub0llg5fqKHl01D0ahv4OAQgRs5oU+4mq+H2QGTwIFGFqP9tCRo0I+aICawpxYOfoLJpFW6KvEPnM2Lr3sl6Nq2fmkz6RL5F7nUtzxN8OKazLQpv8DOYzXbi7+ayEsqS0/EINetq7RfCqgjrEUgfNWYuFXWqvUT8lnxLdNu+8cyrJqh1UquFjXWTw1kWcJ0pkokVeBtK9YysCnF1UYh/Iv3rl2ZoYSSLNtuvMSYlYHggZ8xV8bz9S3X2/NwBycBiWIy5Ou/OuSX7trIKgkkmda0xjBWEM1a2acVuqu2OFbMn2zFxm2a3YwKP//OlIgMg', + 'mac': 'QzKV/fgAs4U', + }, + }); + final ret = await matrixApi.storeRoomKeysSingleKey( + roomId, sessionId, '5', session); + expect( + FakeMatrixApi.api['PUT'][ + '/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}/${Uri.encodeComponent('ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU')}?version=5']({}), + ret.toJson()); + }); + test('getRoomKeysSingleKey', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = '!726s6s6q:example.com'; + final sessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU'; + final ret = await matrixApi.getRoomKeysSingleKey(roomId, sessionId, '5'); + expect( + FakeMatrixApi.api['GET'][ + '/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}/${Uri.encodeComponent('ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU')}?version=5']({}), + ret.toJson()); + }); + test('deleteRoomKeysSingleKey', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = '!726s6s6q:example.com'; + final sessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU'; + final ret = + await matrixApi.deleteRoomKeysSingleKey(roomId, sessionId, '5'); + expect( + FakeMatrixApi.api['DELETE'][ + '/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}/${Uri.encodeComponent('ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU')}?version=5']({}), + ret.toJson()); + }); + test('storeRoomKeysRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = '!726s6s6q:example.com'; + final sessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU'; + final session = RoomKeysRoom.fromJson({ + 'sessions': { + sessionId: { + 'first_message_index': 0, + 'forwarded_count': 0, + 'is_verified': true, + 'session_data': { + 'ephemeral': 'fwRxYh+seqLykz5mQCLypJ4/59URdcFJ2s69OU1dGRc', + 'ciphertext': + '19jkQYlbgdP+VL9DH3qY/Dvpk6onJZgf+6frZFl1TinPCm9OMK9AZZLuM1haS9XLAUK1YsREgjBqfl6T+Tq8JlJ5ONZGg2Wttt24sGYc0iTMZJ8rXcNDeKMZhM96ETyjufJSeYoXLqifiVLDw9rrVBmNStF7PskYp040em+0OZ4pF85Cwsdf7l9V7MMynzh9BoXqVUCBiwT03PNYH9AEmNUxXX+6ZwCpe/saONv8MgGt5uGXMZIK29phA3D8jD6uV/WOHsB8NjHNq9FrfSEAsl+dAcS4uiYie4BKSSeQN+zGAQqu1MMW4OAdxGOuf8WpIINx7n+7cKQfxlmc/Cgg5+MmIm2H0oDwQ+Xu7aSxp1OCUzbxQRdjz6+tnbYmZBuH0Ov2RbEvC5tDb261LRqKXpub0llg5fqKHl01D0ahv4OAQgRs5oU+4mq+H2QGTwIFGFqP9tCRo0I+aICawpxYOfoLJpFW6KvEPnM2Lr3sl6Nq2fmkz6RL5F7nUtzxN8OKazLQpv8DOYzXbi7+ayEsqS0/EINetq7RfCqgjrEUgfNWYuFXWqvUT8lnxLdNu+8cyrJqh1UquFjXWTw1kWcJ0pkokVeBtK9YysCnF1UYh/Iv3rl2ZoYSSLNtuvMSYlYHggZ8xV8bz9S3X2/NwBycBiWIy5Ou/OuSX7trIKgkkmda0xjBWEM1a2acVuqu2OFbMn2zFxm2a3YwKP//OlIgMg', + 'mac': 'QzKV/fgAs4U', + }, + }, + }, + }); + final ret = await matrixApi.storeRoomKeysRoom(roomId, '5', session); + expect( + FakeMatrixApi.api['PUT'][ + '/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}?version=5']({}), + ret.toJson()); + }); + test('getRoomKeysRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = '!726s6s6q:example.com'; + final ret = await matrixApi.getRoomKeysRoom(roomId, '5'); + expect( + FakeMatrixApi.api['GET'][ + '/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}?version=5']({}), + ret.toJson()); + }); + test('deleteRoomKeysRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = '!726s6s6q:example.com'; + final ret = await matrixApi.deleteRoomKeysRoom(roomId, '5'); + expect( + FakeMatrixApi.api['DELETE'][ + '/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}?version=5']({}), + ret.toJson()); + }); + test('storeRoomKeys', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = '!726s6s6q:example.com'; + final sessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU'; + final session = RoomKeys.fromJson({ + 'rooms': { + roomId: { + 'sessions': { + sessionId: { + 'first_message_index': 0, + 'forwarded_count': 0, + 'is_verified': true, + 'session_data': { + 'ephemeral': 'fwRxYh+seqLykz5mQCLypJ4/59URdcFJ2s69OU1dGRc', + 'ciphertext': + '19jkQYlbgdP+VL9DH3qY/Dvpk6onJZgf+6frZFl1TinPCm9OMK9AZZLuM1haS9XLAUK1YsREgjBqfl6T+Tq8JlJ5ONZGg2Wttt24sGYc0iTMZJ8rXcNDeKMZhM96ETyjufJSeYoXLqifiVLDw9rrVBmNStF7PskYp040em+0OZ4pF85Cwsdf7l9V7MMynzh9BoXqVUCBiwT03PNYH9AEmNUxXX+6ZwCpe/saONv8MgGt5uGXMZIK29phA3D8jD6uV/WOHsB8NjHNq9FrfSEAsl+dAcS4uiYie4BKSSeQN+zGAQqu1MMW4OAdxGOuf8WpIINx7n+7cKQfxlmc/Cgg5+MmIm2H0oDwQ+Xu7aSxp1OCUzbxQRdjz6+tnbYmZBuH0Ov2RbEvC5tDb261LRqKXpub0llg5fqKHl01D0ahv4OAQgRs5oU+4mq+H2QGTwIFGFqP9tCRo0I+aICawpxYOfoLJpFW6KvEPnM2Lr3sl6Nq2fmkz6RL5F7nUtzxN8OKazLQpv8DOYzXbi7+ayEsqS0/EINetq7RfCqgjrEUgfNWYuFXWqvUT8lnxLdNu+8cyrJqh1UquFjXWTw1kWcJ0pkokVeBtK9YysCnF1UYh/Iv3rl2ZoYSSLNtuvMSYlYHggZ8xV8bz9S3X2/NwBycBiWIy5Ou/OuSX7trIKgkkmda0xjBWEM1a2acVuqu2OFbMn2zFxm2a3YwKP//OlIgMg', + 'mac': 'QzKV/fgAs4U', + }, + }, + }, + }, + }, + }); + final ret = await matrixApi.storeRoomKeys('5', session); + expect( + FakeMatrixApi.api['PUT'] + ['/client/unstable/room_keys/keys?version=5']({}), + ret.toJson()); + }); + test('getRoomKeys', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final ret = await matrixApi.getRoomKeys('5'); + expect( + FakeMatrixApi.api['GET'] + ['/client/unstable/room_keys/keys?version=5']({}), + ret.toJson()); + }); + test('deleteRoomKeys', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final ret = await matrixApi.deleteRoomKeys('5'); + expect( + FakeMatrixApi.api['DELETE'] + ['/client/unstable/room_keys/keys?version=5']({}), + ret.toJson()); + }); + test('AuthenticationData', () { + final json = {'session': '1234', 'type': 'm.login.dummy'}; + expect(AuthenticationData.fromJson(json).toJson(), json); + expect( + AuthenticationData(session: '1234', type: 'm.login.dummy').toJson(), + json); + }); + test('AuthenticationRecaptcha', () { + final json = { + 'session': '1234', + 'type': 'm.login.recaptcha', + 'response': 'a', + }; + expect(AuthenticationRecaptcha.fromJson(json).toJson(), json); + expect(AuthenticationRecaptcha(session: '1234', response: 'a').toJson(), + json); + }); + test('AuthenticationToken', () { + final json = { + 'session': '1234', + 'type': 'm.login.token', + 'token': 'a', + 'txn_id': '1' + }; + expect(AuthenticationToken.fromJson(json).toJson(), json); + expect( + AuthenticationToken(session: '1234', token: 'a', txnId: '1').toJson(), + json); + }); + test('AuthenticationThreePidCreds', () { + final json = { + 'type': 'm.login.email.identity', + 'threepidCreds': [ + { + 'sid': '1', + 'client_secret': 'a', + 'id_server': 'matrix.org', + 'id_access_token': 'a', + }, + ], + 'threepid_creds': [ + { + 'sid': '1', + 'client_secret': 'a', + 'id_server': 'matrix.org', + 'id_access_token': 'a', + }, + ], + 'session': '1', + }; + expect(AuthenticationThreePidCreds.fromJson(json).toJson(), json); + expect( + AuthenticationThreePidCreds( + session: '1', + type: AuthenticationTypes.emailIdentity, + threepidCreds: [ + ThreepidCreds( + sid: '1', + clientSecret: 'a', + idServer: 'matrix.org', + idAccessToken: 'a', + ), + ]).toJson(), + json); + }); + test('AuthenticationIdentifier', () { + final json = {'type': 'm.id.user'}; + expect(AuthenticationIdentifier.fromJson(json).toJson(), json); + expect(AuthenticationIdentifier(type: 'm.id.user').toJson(), json); + }); + test('AuthenticationPassword', () { + final json = { + 'type': 'm.login.password', + 'identifier': {'type': 'm.id.user', 'user': 'a'}, + 'password': 'a', + 'session': '1', + 'user': 'a', + }; + expect(AuthenticationPassword.fromJson(json).toJson(), json); + expect( + AuthenticationPassword( + session: '1', + password: 'a', + user: 'a', + identifier: AuthenticationUserIdentifier(user: 'a'), + ).toJson(), + json); + json['identifier'] = { + 'type': 'm.id.thirdparty', + 'medium': 'a', + 'address': 'a', + }; + expect(AuthenticationPassword.fromJson(json).toJson(), json); + expect( + AuthenticationPassword( + session: '1', + password: 'a', + user: 'a', + identifier: + AuthenticationThirdPartyIdentifier(medium: 'a', address: 'a'), + ).toJson(), + json); + json['identifier'] = { + 'type': 'm.id.phone', + 'country': 'a', + 'phone': 'a', + }; + expect(AuthenticationPassword.fromJson(json).toJson(), json); + expect( + AuthenticationPassword( + session: '1', + password: 'a', + user: 'a', + identifier: AuthenticationPhoneIdentifier(country: 'a', phone: 'a'), + ).toJson(), + json); + }); + }); +}