mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-15 19:45:34 -04:00
Merge remote-tracking branch 'origin/main' into gnunicorn/issue756
This commit is contained in:
5
.github/workflows/bindings_ci.yml
vendored
5
.github/workflows/bindings_ci.yml
vendored
@@ -133,6 +133,7 @@ jobs:
|
||||
test-apple:
|
||||
name: matrix-rust-components-swift
|
||||
runs-on: macos-12
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -161,7 +162,7 @@ jobs:
|
||||
args: uniffi_bindgen --version ^0.18
|
||||
|
||||
- name: Generate .xcframework
|
||||
run: sh bindings/apple/debug_build_xcframework.sh ci
|
||||
run: sh bindings/apple/debug_build_xcframework.sh x86_64 ci
|
||||
|
||||
- name: Run XCTests
|
||||
run: |
|
||||
@@ -169,4 +170,4 @@ jobs:
|
||||
-project bindings/apple/MatrixRustSDK.xcodeproj \
|
||||
-scheme MatrixRustSDK \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 13,OS=15.4'
|
||||
-destination 'platform=iOS Simulator,name=iPhone 13'
|
||||
|
||||
1
.github/workflows/coverage.yml
vendored
1
.github/workflows/coverage.yml
vendored
@@ -13,6 +13,7 @@ jobs:
|
||||
code_coverage:
|
||||
name: Code Coverage
|
||||
runs-on: "ubuntu-latest"
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
117
.github/workflows/prep-crypto-nodejs-release.yml
vendored
Normal file
117
.github/workflows/prep-crypto-nodejs-release.yml
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
name: Prepare Crypto-Node.js Release
|
||||
#
|
||||
# This is a helper workflow to craft a new Node.js release, trigger this via
|
||||
# the Github Workflow UI by dispatching it manually. Provide the version, the
|
||||
# matrix-sdk-crypto-nodejs npm package should be set to, and a optionally the
|
||||
# old version (as used in the git tag) this release should be compared to.
|
||||
#
|
||||
# This will then:
|
||||
# 1. bump the npm version to the one you specified
|
||||
# 2. commit that change together with the changelog (if it changed, see below)
|
||||
# 3. create the appropriate tag on that commit
|
||||
# 4. create the Github draft release, including the changes (if given, see below)
|
||||
# 5. push these to a new branch, including tag, triggering the `release-crypto-nodejs` workflow
|
||||
# 6. create a PR to merge these back into `main`
|
||||
#
|
||||
# Additionally, if you provide a tag to comapare this tag to, this will:
|
||||
# 1. create a changelog between the two releases, used for the github release
|
||||
# 2. update the Changelog.md and include it in the commit
|
||||
#
|
||||
# The remaining tasks are done by the release-crypto-nodejs workflow.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'New Node.js SemVer version to create'
|
||||
required: true
|
||||
type: string
|
||||
previous_version:
|
||||
description: 'Create the changelog by comparing to this old SemVer Version (as used in the tag) '
|
||||
type: string
|
||||
|
||||
env:
|
||||
PKG_PATH: "bindings/matrix-sdk-crypto-nodejs"
|
||||
TAG_PREFIX: "matrix-sdk-crypto-nodejs-v"
|
||||
|
||||
jobs:
|
||||
prepare-release:
|
||||
name: "Preparing crypto-nodejs release tag"
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag: "${{ env.TAG_PREFIX }}${{ inputs.version }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
|
||||
# Generate changelog since last tag, if given
|
||||
- name: Generate a changelog for upload
|
||||
if: inputs.previous_version
|
||||
uses: orhun/git-cliff-action@v1
|
||||
with:
|
||||
config: "${{ env.PKG_PATH }}/cliff.toml"
|
||||
args: --strip header "${{env.TAG_PREFIX}}${{ inputs.previous_version }}..HEAD"
|
||||
env:
|
||||
GIT_CLIFF_TAG: "Changes ${{ inputs.previous_version }} -> ${{ inputs.version }}"
|
||||
GIT_CLIFF_OUTPUT: "${{ env.PKG_PATH }}/CHANGES-${{ inputs.version }}.md"
|
||||
|
||||
# Update changelog since last tag, if given
|
||||
- name: Update existing Changelog
|
||||
if: inputs.previous_version
|
||||
uses: orhun/git-cliff-action@v1
|
||||
with:
|
||||
config: "${{ env.PKG_PATH }}/cliff.toml"
|
||||
args: "${{ inputs.previous_version }}..HEAD"
|
||||
env:
|
||||
GIT_CLIFF_TAG: "${{ inputs.version }}"
|
||||
GIT_CLIFF_PREPEND: "${{ env.PKG_PATH }}/CHANGELOG.md"
|
||||
|
||||
- name: Set version
|
||||
id: package_version
|
||||
working-directory: ${{ env.PKG_PATH }}
|
||||
run: npm version ${{ inputs.version }}
|
||||
|
||||
- uses: EndBug/add-and-commit@v9
|
||||
with:
|
||||
default_author: github_actions
|
||||
message: "Tagging Crypto-Node.js for release"
|
||||
tag: "${{env.TAG_PREFIX}}${{ inputs.version }}"
|
||||
new_branch: "gh-action/release-${{ env.TAG_PREFIX }}${{ inputs.version }}"
|
||||
push: true
|
||||
add: |
|
||||
${{ env.PKG_PATH }}/package.json
|
||||
${{ env.PKG_PATH }}/CHANGELOG.md
|
||||
|
||||
# if we have generated changes
|
||||
- name: Update Github Release notes
|
||||
if: inputs.previous_version
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }}
|
||||
body_path: "${{ env.PKG_PATH }}/CHANGES-${{ inputs.version }}.md"
|
||||
|
||||
# no changes, use the default changelog for the body
|
||||
- name: Update Github Release notes
|
||||
if: ${{!inputs.previous_version}}
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }}
|
||||
body_path: "${{ env.PKG_PATH }}/CHANGELOG.md"
|
||||
|
||||
# let's create a PR for all this, too
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
title: "Preparing Release ${{ env.TAG_PREFIX }}${{ inputs.version }}"
|
||||
body: |
|
||||
Automatic Pull-Request to merge release ${{ env.TAG_PREFIX }}${{ inputs.version }}
|
||||
|
||||
trigger-release:
|
||||
# and trigger the tagging release workflow
|
||||
uses: matrix-org/matrix-rust-sdk/.github/workflows/release-crypto-nodejs.yml@main
|
||||
needs: ['prepare-release']
|
||||
name: "Trigger release Workflow"
|
||||
with:
|
||||
tag: ${{needs.prepare-release.outputs.tag}}
|
||||
139
.github/workflows/release-crypto-nodejs.yml
vendored
Normal file
139
.github/workflows/release-crypto-nodejs.yml
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
name: Release Crypto-Node.js
|
||||
#
|
||||
# This workflow releases the crypto-bindings for nodejs
|
||||
#
|
||||
# It is triggered when seeing a tag prefixed matching `matrix-sdk-crypto-nodejs-v[0-9]+.*`,
|
||||
# which then build the native bindings for linux, mac and windows via the CI and uploads
|
||||
# them to the corresponding Github Release tag. Once they are finished, this workflow will
|
||||
# package the npm tar.gz and uploads that to the Github Release tag as well, before publishing
|
||||
# it to npmjs.com automatically.
|
||||
#
|
||||
# The usual way to trigger this is by manually triggering the `prep-crypto-nodejs-release`
|
||||
# workflow. See its documentation for instructions how to use it.
|
||||
|
||||
env:
|
||||
PKG_PATH: "bindings/matrix-sdk-crypto-nodejs"
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: 'aarch64-linux-gnu-gcc'
|
||||
CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER: 'i686-linux-gnu-gcc'
|
||||
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: 'arm-linux-gnueabihf-gcc'
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- matrix-sdk-crypto-nodejs-v[0-9]+.*
|
||||
workflow_call:
|
||||
inputs:
|
||||
tag:
|
||||
description: "The tag to build with"
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
upload-assets:
|
||||
name: "Upload prebuilt libraries"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# ----------------------------------- Linux
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: i686-unknown-linux-gnu
|
||||
apt_install: gcc-i686-linux-gnu g++-i686-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
apt_install: gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
|
||||
- target: arm-unknown-linux-gnueabihf
|
||||
os: ubuntu-latest
|
||||
apt_install: gcc-arm-linux-gnueabihf
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
# ----------------------------------- macOS
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
# ----------------------------------- Windows
|
||||
- target: x86_64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
- target: i686-pc-windows-msvc
|
||||
os: windows-latest
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
# use the given tag
|
||||
- uses: actions/checkout@v3
|
||||
name: "Checking out ${{ inputs.tag }}"
|
||||
if: "${{ inputs.tag }}"
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
# use the default
|
||||
- uses: actions/checkout@v3
|
||||
if: "${{ !inputs.tag }}"
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
- if: ${{ matrix.apt_install }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ${{ matrix.apt_install }}
|
||||
- name: Build lib
|
||||
working-directory: ${{env.PKG_PATH}}
|
||||
run: |
|
||||
npm install --ignore-scripts
|
||||
npx napi build --platform --release --strip --target ${{ matrix.target }}
|
||||
- name: Upload artifacts to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
files: ${{env.PKG_PATH}}/*.node
|
||||
|
||||
publish-nodejs-package:
|
||||
name: "Package nodejs package"
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- upload-assets
|
||||
steps:
|
||||
# use the given tag
|
||||
- uses: actions/checkout@v3
|
||||
name: "Checking out ${{ inputs.tag }}"
|
||||
if: "${{ inputs.tag }}"
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
# use the default
|
||||
- uses: actions/checkout@v3
|
||||
if: "${{ !inputs.tag }}"
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
- name: Build lib
|
||||
working-directory: ${{env.PKG_PATH}}
|
||||
run: |
|
||||
npm install --ignore-scripts
|
||||
npm run build
|
||||
npm pack
|
||||
- name: Upload npm package to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
files: ${{env.PKG_PATH}}/*tgz
|
||||
- name: Publish to npmjs.com
|
||||
uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
package: ${{env.PKG_PATH}}/package.json
|
||||
access: public
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -12,7 +12,7 @@ criterion = { version = "0.3.5", features = ["async", "async_tokio", "html_repor
|
||||
matrix-sdk-crypto = { path = "../crates/matrix-sdk-crypto", version = "0.5.0" }
|
||||
matrix-sdk-sled = { path = "../crates/matrix-sdk-sled", version = "0.1.0", default-features = false, features = ["crypto-store"] }
|
||||
matrix-sdk-test = { path = "../crates/matrix-sdk-test", version = "0.5.0" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b" }
|
||||
serde_json = "1.0.79"
|
||||
tempfile = "3.3.0"
|
||||
tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread"] }
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
181AA19927B52AA60005F102 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MatrixSDKFFI.xcframework; path = ../../generated/MatrixSDKFFI.xcframework; sourceTree = "<group>"; };
|
||||
189A89B927B40BBF0048B0A5 /* sdk.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = sdk.swift; path = ../../../generated/swift/sdk.swift; sourceTree = "<group>"; };
|
||||
189A89C327B417CA0048B0A5 /* MatrixRustSDK-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MatrixRustSDK-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatrixRustSDK.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixRustSDKApp.swift; sourceTree = "<group>"; };
|
||||
18CE89D927B2939900CA89E1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
@@ -117,7 +116,6 @@
|
||||
18CE89D927B2939900CA89E1 /* ContentView.swift */,
|
||||
18CE8A0127B293A900CA89E1 /* MatrixRustSDK.entitlements */,
|
||||
18CE89DB27B2939A00CA89E1 /* Assets.xcassets */,
|
||||
189A89C327B417CA0048B0A5 /* MatrixRustSDK-Bridging-Header.h */,
|
||||
);
|
||||
path = MatrixRustSDK;
|
||||
sourceTree = "<group>";
|
||||
@@ -307,7 +305,8 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
@@ -362,7 +361,8 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -397,9 +397,8 @@
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDK;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "MatrixRustSDK/MatrixRustSDK-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -431,9 +430,8 @@
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDK;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "MatrixRustSDK/MatrixRustSDK-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
version = "1.3">
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<PreActions>
|
||||
<ExecutionAction
|
||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||
<ActionContent
|
||||
title = "Run Script"
|
||||
scriptText = "#PATH=~/.cargo/bin:${PATH} echo $ARCHS #sh $PROJECT_DIR/debug_build_xcframework.sh "$ARCHS" CI ">
|
||||
<EnvironmentBuildable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
|
||||
BuildableName = "MatrixRustSDK.app"
|
||||
BlueprintName = "MatrixRustSDK"
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</EnvironmentBuildable>
|
||||
</ActionContent>
|
||||
</ExecutionAction>
|
||||
</PreActions>
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
@@ -38,16 +56,6 @@
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "18CE89ED27B2939A00CA89E1"
|
||||
BuildableName = "MatrixRustSDKUITests.xctest"
|
||||
BlueprintName = "MatrixRustSDKUITests"
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#import "sdkFFI.h"
|
||||
@@ -27,10 +27,6 @@ cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-darwin"
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim"
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios"
|
||||
|
||||
# Mac Catalyst
|
||||
cargo +nightly build -Z build-std -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-macabi"
|
||||
cargo +nightly build -Z build-std -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios-macabi"
|
||||
|
||||
# Lipo together the libraries for the same platform
|
||||
|
||||
# MacOS
|
||||
@@ -45,12 +41,6 @@ lipo -create \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
|
||||
|
||||
# Mac Catalyst
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-ios-macabi/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-macabi/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a"
|
||||
|
||||
|
||||
# Generate uniffi files
|
||||
uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR}
|
||||
@@ -59,7 +49,10 @@ uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" --lang
|
||||
HEADERS_DIR=${GENERATED_DIR}/headers
|
||||
mkdir -p ${HEADERS_DIR}
|
||||
|
||||
mv ${GENERATED_DIR}/*.h ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}
|
||||
mv ${GENERATED_DIR}/*.h ${HEADERS_DIR}
|
||||
|
||||
# Rename and move modulemap to the right place
|
||||
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
|
||||
|
||||
SWIFT_DIR="${GENERATED_DIR}/swift"
|
||||
mkdir -p ${SWIFT_DIR}
|
||||
@@ -75,8 +68,6 @@ xcodebuild -create-xcframework \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-output "${GENERATED_DIR}/MatrixSDKFFI.xcframework"
|
||||
@@ -85,5 +76,4 @@ xcodebuild -create-xcframework \
|
||||
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a"; fi
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a"; fi
|
||||
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
|
||||
@@ -4,14 +4,23 @@ set -eEu
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
IS_CI=false
|
||||
ACTIVE_ARCH="arm64"
|
||||
|
||||
if [ $# -eq 1 ]; then
|
||||
IS_CI=true
|
||||
if [ $# -eq 2 ]; then
|
||||
echo "Running CI build"
|
||||
IS_CI=true
|
||||
ARCHS=( $1 )
|
||||
ACTIVE_ARCH=${ARCHS[0]}
|
||||
elif [ $# -eq 1 ]; then
|
||||
echo "Running debug build"
|
||||
ARCHS=( $1 )
|
||||
ACTIVE_ARCH=${ARCHS[0]}
|
||||
else
|
||||
echo "Running debug build"
|
||||
fi
|
||||
|
||||
echo "Active architecture ${ACTIVE_ARCH}"
|
||||
|
||||
# Path to the repo root
|
||||
SRC_ROOT=../..
|
||||
|
||||
@@ -23,14 +32,22 @@ mkdir -p ${GENERATED_DIR}
|
||||
REL_FLAG=""
|
||||
REL_TYPE_DIR="debug"
|
||||
|
||||
# iOS Simulator
|
||||
cargo +nightly build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim"
|
||||
cargo +nightly build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios"
|
||||
# iOS Simulator arm64
|
||||
if [ "$ACTIVE_ARCH" = "arm64" ]; then
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim"
|
||||
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
|
||||
|
||||
# iOS Simulator intel
|
||||
else
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios"
|
||||
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
|
||||
fi
|
||||
|
||||
# Generate uniffi files
|
||||
uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR}
|
||||
@@ -39,7 +56,10 @@ uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" --lang
|
||||
HEADERS_DIR=${GENERATED_DIR}/headers
|
||||
mkdir -p ${HEADERS_DIR}
|
||||
|
||||
mv ${GENERATED_DIR}/*.h ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}
|
||||
mv ${GENERATED_DIR}/*.h ${HEADERS_DIR}
|
||||
|
||||
# Rename and move modulemap to the right place
|
||||
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
|
||||
|
||||
SWIFT_DIR="${GENERATED_DIR}/swift"
|
||||
mkdir -p ${SWIFT_DIR}
|
||||
@@ -57,15 +77,13 @@ xcodebuild -create-xcframework \
|
||||
|
||||
# Cleanup
|
||||
|
||||
# if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
|
||||
# if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
|
||||
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
|
||||
if [ "$IS_CI" = false ] ; then
|
||||
echo "Preparing matrix-rust-components-swift"
|
||||
|
||||
# Debug -> Copy generated files over to ../../../matrix-rust-components-swift
|
||||
echo "$(printf "import MatrixSDKFFIWrapper\n\n"; cat "${SWIFT_DIR}/sdk.swift")" > "${SWIFT_DIR}/sdk.swift"
|
||||
|
||||
rsync -a --delete "${GENERATED_DIR}/MatrixSDKFFI.xcframework" "${SRC_ROOT}/../matrix-rust-components-swift/"
|
||||
rsync -a --delete "${GENERATED_DIR}/swift/" "${SRC_ROOT}/../matrix-rust-components-swift/Sources/MatrixRustSDK"
|
||||
fi
|
||||
|
||||
@@ -20,7 +20,7 @@ hmac = "0.12.1"
|
||||
http = "0.2.6"
|
||||
pbkdf2 = "0.11.0"
|
||||
rand = "0.8.5"
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c"] }
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
sha2 = "0.10.2"
|
||||
|
||||
@@ -22,18 +22,22 @@ wasm-opt = ['-Oz']
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["tracing"]
|
||||
qrcode = ["matrix-sdk-crypto/qrcode"]
|
||||
docsrs = []
|
||||
tracing = []
|
||||
|
||||
[dependencies]
|
||||
matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" }
|
||||
matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd", features = ["js"] }
|
||||
wasm-bindgen = "0.2.80"
|
||||
wasm-bindgen-futures = "0.4.30"
|
||||
js-sys = "0.3.49"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
serde_json = "1.0.79"
|
||||
http = "0.2.6"
|
||||
anyhow = "1.0"
|
||||
anyhow = "1.0.58"
|
||||
tracing = { version = "0.1.35", default-features = false, features = ["attributes"] }
|
||||
tracing-subscriber = { version = "0.3.14", default-features = false, features = ["registry", "std"] }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "matrix-sdk-crypto-js",
|
||||
"name": "@matrix-org/matrix-sdk-crypto-js",
|
||||
"version": "0.5.0",
|
||||
"homepage": "https://github.com/matrix-org/matrix-rust-sdk",
|
||||
"description": "Matrix encryption library, for JavaScript",
|
||||
@@ -28,13 +28,14 @@
|
||||
"devDependencies": {
|
||||
"wasm-pack": "^0.10.2",
|
||||
"jest": "^28.1.0",
|
||||
"typedoc": "^0.22.17"
|
||||
"typedoc": "^0.22.17",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-name matrix_sdk_crypto --out-dir ./pkg",
|
||||
"build": "cross-env RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-name matrix_sdk_crypto --out-dir ./pkg",
|
||||
"test": "jest --verbose",
|
||||
"doc": "typedoc --tsconfig ."
|
||||
}
|
||||
|
||||
@@ -25,10 +25,21 @@ pub mod machine;
|
||||
pub mod requests;
|
||||
pub mod responses;
|
||||
pub mod sync_events;
|
||||
mod tracing;
|
||||
|
||||
use js_sys::{Object, Reflect};
|
||||
use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*};
|
||||
|
||||
/// Run some stuff when the Wasm module is instantiated.
|
||||
///
|
||||
/// Right now, it does the following:
|
||||
///
|
||||
/// * Redirect Rust panics to JavaScript console.
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn start() {
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
/// A really hacky and dirty code to downcast a `JsValue` to `T:
|
||||
/// RefFromWasmAbi`, inspired by
|
||||
/// https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-656293288.
|
||||
|
||||
290
bindings/matrix-sdk-crypto-js/src/tracing.rs
Normal file
290
bindings/matrix-sdk-crypto-js/src/tracing.rs
Normal file
@@ -0,0 +1,290 @@
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Logger level.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LoggerLevel {
|
||||
/// `TRACE` level.
|
||||
///
|
||||
/// Designate very low priority, often extremely verbose,
|
||||
/// information.
|
||||
Trace,
|
||||
|
||||
/// `DEBUG` level.
|
||||
///
|
||||
/// Designate lower priority information.
|
||||
Debug,
|
||||
|
||||
/// `INFO` level.
|
||||
///
|
||||
/// Designate useful information.
|
||||
Info,
|
||||
|
||||
/// `WARN` level.
|
||||
///
|
||||
/// Designate hazardous situations.
|
||||
Warn,
|
||||
|
||||
/// `ERROR` level.
|
||||
///
|
||||
/// Designate very serious errors.
|
||||
Error,
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
mod inner {
|
||||
use std::{
|
||||
fmt,
|
||||
fmt::Write as _,
|
||||
sync::{Arc, Once},
|
||||
};
|
||||
|
||||
use tracing::{
|
||||
field::{Field, Visit},
|
||||
metadata::LevelFilter,
|
||||
Event, Level, Metadata, Subscriber,
|
||||
};
|
||||
use tracing_subscriber::{
|
||||
layer::{Context, Layer as TracingLayer},
|
||||
prelude::*,
|
||||
reload, Registry,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
type TracingInner = Arc<reload::Handle<Layer, Registry>>;
|
||||
|
||||
/// Type to install and to manipulate the tracing layer.
|
||||
#[wasm_bindgen]
|
||||
pub struct Tracing {
|
||||
handle: TracingInner,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Tracing {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Tracing").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Tracing {
|
||||
/// Check whether the `tracing` feature has been enabled.
|
||||
#[wasm_bindgen(js_name = "isAvailable")]
|
||||
pub fn is_available() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Install the tracing layer.
|
||||
///
|
||||
/// `Tracing` is a singleton. Once it is installed,
|
||||
/// consecutive calls to the constructor will construct a new
|
||||
/// `Tracing` object but with the exact same inner
|
||||
/// state. Calling the constructor with a new `min_level` will
|
||||
/// just update the `min_level` parameter; in that regard, it
|
||||
/// is similar to calling the `min_level` method on an
|
||||
/// existing `Tracing` object.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(min_level: LoggerLevel) -> Result<Tracing, JsError> {
|
||||
static mut INSTALL: Option<TracingInner> = None;
|
||||
static INSTALLED: Once = Once::new();
|
||||
|
||||
INSTALLED.call_once(|| {
|
||||
let (filter, reload_handle) = reload::Layer::new(Layer::new(min_level.clone()));
|
||||
|
||||
tracing_subscriber::registry().with(filter).init();
|
||||
|
||||
unsafe { INSTALL = Some(Arc::new(reload_handle)) };
|
||||
});
|
||||
|
||||
let tracing = Tracing {
|
||||
handle: unsafe { INSTALL.as_ref() }
|
||||
.cloned()
|
||||
.expect("`Tracing` has not been installed correctly"),
|
||||
};
|
||||
|
||||
// If it's not the first call to `install`, the
|
||||
// `min_level` can be different. Let's update it.
|
||||
tracing.min_level(min_level);
|
||||
|
||||
Ok(tracing)
|
||||
}
|
||||
|
||||
/// Re-define the minimum logger level.
|
||||
#[wasm_bindgen(setter, js_name = "minLevel")]
|
||||
pub fn min_level(&self, min_level: LoggerLevel) {
|
||||
let _ = self.handle.modify(|layer| layer.min_level = min_level.into());
|
||||
}
|
||||
|
||||
/// Turn the logger on, i.e. it emits logs again if it was turned
|
||||
/// off.
|
||||
#[wasm_bindgen(js_name = "turnOn")]
|
||||
pub fn turn_on(&self) {
|
||||
let _ = self.handle.modify(|layer| layer.turn_on());
|
||||
}
|
||||
|
||||
/// Turn the logger off, i.e. it no long emits logs.
|
||||
#[wasm_bindgen(js_name = "turnOff")]
|
||||
pub fn turn_off(&self) {
|
||||
let _ = self.handle.modify(|layer| layer.turn_off());
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoggerLevel> for Level {
|
||||
fn from(value: LoggerLevel) -> Self {
|
||||
use LoggerLevel::*;
|
||||
|
||||
match value {
|
||||
Trace => Self::TRACE,
|
||||
Debug => Self::DEBUG,
|
||||
Info => Self::INFO,
|
||||
Warn => Self::WARN,
|
||||
Error => Self::ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "trace")]
|
||||
fn log_trace(message: String);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "debug")]
|
||||
fn log_debug(message: String);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "info")]
|
||||
fn log_info(message: String);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "warn")]
|
||||
fn log_warn(message: String);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "error")]
|
||||
fn log_error(message: String);
|
||||
}
|
||||
|
||||
struct Layer {
|
||||
min_level: Level,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
fn new<L>(min_level: L) -> Self
|
||||
where
|
||||
L: Into<Level>,
|
||||
{
|
||||
Self { min_level: min_level.into(), enabled: true }
|
||||
}
|
||||
|
||||
fn turn_on(&mut self) {
|
||||
self.enabled = true;
|
||||
}
|
||||
|
||||
fn turn_off(&mut self) {
|
||||
self.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> TracingLayer<S> for Layer
|
||||
where
|
||||
S: Subscriber,
|
||||
{
|
||||
fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, S>) -> bool {
|
||||
self.enabled && metadata.level() <= &self.min_level
|
||||
}
|
||||
|
||||
fn max_level_hint(&self) -> Option<LevelFilter> {
|
||||
if !self.enabled {
|
||||
Some(LevelFilter::OFF)
|
||||
} else {
|
||||
Some(LevelFilter::from_level(self.min_level))
|
||||
}
|
||||
}
|
||||
|
||||
fn on_event(&self, event: &Event<'_>, _: Context<'_, S>) {
|
||||
let mut recorder = StringVisitor::new();
|
||||
event.record(&mut recorder);
|
||||
let metadata = event.metadata();
|
||||
let level = metadata.level();
|
||||
|
||||
let origin = metadata
|
||||
.file()
|
||||
.and_then(|file| metadata.line().map(|ln| format!("{}:{}", file, ln)))
|
||||
.unwrap_or_default();
|
||||
|
||||
let message = format!("{level} {origin}{recorder}");
|
||||
|
||||
match *level {
|
||||
Level::TRACE => log_trace(message),
|
||||
Level::DEBUG => log_debug(message),
|
||||
Level::INFO => log_info(message),
|
||||
Level::WARN => log_warn(message),
|
||||
Level::ERROR => log_error(message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StringVisitor {
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl StringVisitor {
|
||||
fn new() -> Self {
|
||||
Self { string: String::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Visit for StringVisitor {
|
||||
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
|
||||
match field.name() {
|
||||
"message" => {
|
||||
if !self.string.is_empty() {
|
||||
self.string.push('\n');
|
||||
}
|
||||
|
||||
let _ = write!(self.string, "{:?}", value);
|
||||
}
|
||||
|
||||
field_name => {
|
||||
let _ = write!(self.string, "\n{} = {:?}", field_name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for StringVisitor {
|
||||
fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if !self.string.is_empty() {
|
||||
write!(&mut f, " {}", self.string)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
mod inner {
|
||||
use super::*;
|
||||
|
||||
/// Type to install and to manipulate the tracing layer.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct Tracing;
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Tracing {
|
||||
/// Check whether the `tracing` feature has been enabled.
|
||||
#[wasm_bindgen(js_name = "isAvailable")]
|
||||
pub fn is_available() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// The `tracing` feature is not enabled, so this constructor
|
||||
/// will raise an error.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(_min_level: LoggerLevel) -> Result<Tracing, JsError> {
|
||||
Err(JsError::new("The `tracing` feature is disabled. Check `Tracing.isAvailable` before constructing `Tracing`"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use inner::*;
|
||||
@@ -299,7 +299,8 @@ describe(OlmMachine.name, () => {
|
||||
room,
|
||||
'm.room.message',
|
||||
JSON.stringify({
|
||||
"hello": "world"
|
||||
"msgtype": "m.text",
|
||||
"body": "Hello, World!"
|
||||
}),
|
||||
));
|
||||
|
||||
@@ -328,7 +329,8 @@ describe(OlmMachine.name, () => {
|
||||
expect(decrypted).toBeInstanceOf(DecryptedRoomEvent);
|
||||
|
||||
const event = JSON.parse(decrypted.event);
|
||||
expect(event.content.hello).toStrictEqual("world");
|
||||
expect(event.content.msgtype).toStrictEqual("m.text");
|
||||
expect(event.content.body).toStrictEqual("Hello, World!");
|
||||
|
||||
expect(decrypted.sender.toString()).toStrictEqual(user.toString());
|
||||
expect(decrypted.senderDevice.toString()).toStrictEqual(device.toString());
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('RequestType', () => {
|
||||
});
|
||||
});
|
||||
|
||||
for (const [request, request_type] of [
|
||||
for (const [request, requestType] of [
|
||||
[KeysUploadRequest, RequestType.KeysUpload],
|
||||
[KeysQueryRequest, RequestType.KeysQuery],
|
||||
[KeysClaimRequest, RequestType.KeysClaim],
|
||||
@@ -28,7 +28,7 @@ for (const [request, request_type] of [
|
||||
expect(r).toBeInstanceOf(request);
|
||||
expect(r.id).toStrictEqual('foo');
|
||||
expect(r.body).toStrictEqual('{"bar": "baz"}');
|
||||
expect(r.type).toStrictEqual(request_type);
|
||||
expect(r.type).toStrictEqual(requestType);
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
84
bindings/matrix-sdk-crypto-js/tests/tracing.test.js
Normal file
84
bindings/matrix-sdk-crypto-js/tests/tracing.test.js
Normal file
@@ -0,0 +1,84 @@
|
||||
const { Tracing, LoggerLevel, OlmMachine, UserId, DeviceId } = require('../pkg/matrix_sdk_crypto');
|
||||
|
||||
describe('LoggerLevel', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(LoggerLevel.Trace).toStrictEqual(0);
|
||||
expect(LoggerLevel.Debug).toStrictEqual(1);
|
||||
expect(LoggerLevel.Info).toStrictEqual(2);
|
||||
expect(LoggerLevel.Warn).toStrictEqual(3);
|
||||
expect(LoggerLevel.Error).toStrictEqual(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe(Tracing.name, () => {
|
||||
if (Tracing.isAvailable()) {
|
||||
let tracing = new Tracing(LoggerLevel.Debug);
|
||||
|
||||
test('can installed several times', () => {
|
||||
new Tracing(LoggerLevel.Debug);
|
||||
new Tracing(LoggerLevel.Warn);
|
||||
new Tracing(LoggerLevel.Debug);
|
||||
});
|
||||
|
||||
const originalConsoleDebug = console.debug;
|
||||
|
||||
for (const [testName, testPreState, testPostState, expectedGotcha] of [
|
||||
[
|
||||
'can log something',
|
||||
() => {},
|
||||
() => {},
|
||||
true,
|
||||
],
|
||||
[
|
||||
'can change the logger level',
|
||||
() => { tracing.minLevel = LoggerLevel.Warn },
|
||||
() => { tracing.minLevel = LoggerLevel.Debug },
|
||||
false,
|
||||
],
|
||||
[
|
||||
'can be turned off',
|
||||
() => { tracing.turnOff() },
|
||||
() => {},
|
||||
false,
|
||||
],
|
||||
[
|
||||
'can be turned on',
|
||||
() => { tracing.turnOn() },
|
||||
() => {},
|
||||
true,
|
||||
],
|
||||
|
||||
// This one *must* be the last. We are turning tracing off
|
||||
// again for the other tests.
|
||||
[
|
||||
'can be turned off',
|
||||
() => { tracing.turnOff() },
|
||||
() => {},
|
||||
false,
|
||||
],
|
||||
]) {
|
||||
test(testName, async () => {
|
||||
testPreState();
|
||||
|
||||
let gotcha = false;
|
||||
|
||||
console.debug = (msg) => {
|
||||
gotcha = true;
|
||||
expect(msg).not.toHaveLength(0);
|
||||
};
|
||||
|
||||
// Do something that emits a `DEBUG` log.
|
||||
await new OlmMachine(new UserId('@alice:example.org'), new DeviceId('foo'));
|
||||
|
||||
console.debug = originalConsoleDebug;
|
||||
testPostState();
|
||||
|
||||
expect(gotcha).toStrictEqual(expectedGotcha);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
test('cannot be constructed', () => {
|
||||
expect(() => { new Tracing(LoggerLevel.Error) }).toThrow();
|
||||
});
|
||||
}
|
||||
});
|
||||
1
bindings/matrix-sdk-crypto-nodejs/.gitignore
vendored
1
bindings/matrix-sdk-crypto-nodejs/.gitignore
vendored
@@ -4,3 +4,4 @@
|
||||
/index.d.ts
|
||||
/matrix-sdk-crypto.*.node
|
||||
/docs/*
|
||||
*.tgz
|
||||
8
bindings/matrix-sdk-crypto-nodejs/.npmignore
Normal file
8
bindings/matrix-sdk-crypto-nodejs/.npmignore
Normal file
@@ -0,0 +1,8 @@
|
||||
src/
|
||||
tests/
|
||||
Cargo.toml
|
||||
build.rs
|
||||
*.node
|
||||
*.tgz
|
||||
tsconfig.json
|
||||
cliff.toml
|
||||
32
bindings/matrix-sdk-crypto-nodejs/CHANGELOG.md
Normal file
32
bindings/matrix-sdk-crypto-nodejs/CHANGELOG.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Matrix-Rust-SDK Node.js Bindings
|
||||
|
||||
## 0.1.0-beta.1 - 2022-07-14
|
||||
|
||||
- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842)
|
||||
|
||||
## 0.1.0-beta.0 - 2022-07-12
|
||||
|
||||
Welcome to the first release of `matrix-sdk-crypto-nodejs`. This is a
|
||||
Node.js binding for the Rust `matrix-sdk-crypto` library. This is a
|
||||
no-network-IO implementation of a state machine, named `OlmMachine`,
|
||||
that handles E2EE (End-to-End Encryption) for Matrix clients.
|
||||
|
||||
The goal of this binding is _not_ to cover the entirety of the
|
||||
`matrix-sdk-crypto` API, but only what's required to build Matrix bots
|
||||
or Matrix bridges (i.e. to connect different networks together via the
|
||||
Matrix protocol).
|
||||
|
||||
This project replaces and deprecates a previous project, with the same
|
||||
name and same goals, inside [the `matrix-rust-sdk-bindings`
|
||||
repository](https://github.com/matrix-org/matrix-rust-sdk-bindings),
|
||||
with the NPM package name `@turt2live/matrix-sdk-crypto-nodejs`. The
|
||||
The new official package name is
|
||||
`@matrix-org/matrix-sdk-crypto-nodejs`.
|
||||
|
||||
Note: All bindings are now part of [the `matrix-rust-sdk`
|
||||
repository](https://github.com/matrix-org/matrix-rust-sdk) (see the
|
||||
`bindings/` root directory).
|
||||
|
||||
[A documentation is available inside the new
|
||||
`matrix-sdk-crypto-nodejs`
|
||||
project](https://github.com/matrix-org/matrix-rust-sdk/tree/0bde5ccf38f8cda3865297a2d12ddcdaf4b80ca7/bindings/matrix-sdk-crypto-nodejs).
|
||||
@@ -22,16 +22,16 @@ crate-type = ["cdylib"]
|
||||
default = []
|
||||
qrcode = ["matrix-sdk-crypto/qrcode"]
|
||||
docsrs = []
|
||||
tracing = ["tracing-subscriber"]
|
||||
tracing = ["dep:tracing-subscriber"]
|
||||
|
||||
[dependencies]
|
||||
matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" }
|
||||
matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" }
|
||||
matrix-sdk-sled = { version = "0.1.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd" }
|
||||
napi = { version = "2.6.1", default-features = false, features = ["napi6", "tokio_rt"] }
|
||||
napi-derive = "2.6.0"
|
||||
napi = { git = "https://github.com/Hywan/napi-rs", branch = "fix-napi-strict-on-t-and-ref-t", default-features = false, features = ["napi6", "tokio_rt"] }
|
||||
napi-derive = { git = "https://github.com/Hywan/napi-rs", branch = "fix-napi-strict-on-t-and-ref-t" }
|
||||
serde_json = "1.0.79"
|
||||
http = "0.2.6"
|
||||
zeroize = "1.3.0"
|
||||
|
||||
@@ -11,6 +11,77 @@ Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
|
||||
|
||||
## Usage
|
||||
|
||||
Just add the latest release to your `package.json`:
|
||||
```sh
|
||||
$ npm install --save @matrix-org/matrix-sdk-crypto-nodejs
|
||||
```
|
||||
|
||||
When installing, NPM will download the corresponding prebuilt Rust library for your current host system. The following are supported:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platform</th>
|
||||
<th>Architecture</th>
|
||||
<th>Triple</th>
|
||||
<th>Prebuilt available</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="5">Linux</td>
|
||||
<td rowspan="2"><code>aarch</code></td>
|
||||
<td><code>aarch64-unknown-linux-gnu</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>arm-unknown-linux-gnueabihf</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3"><code>amd</code></td>
|
||||
<td><code>x86_64-unknown-linux-gnu</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>x86_64-unknown-linux-musl</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>i686-unknown-linux-gnu</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">macOS</td>
|
||||
<td><code>aarch</code></td>
|
||||
<td><code>arch64-apple-darwin</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>amd</code></td>
|
||||
<td><code>x86_64-apple-darwin</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3">Windows</td>
|
||||
<td><code>aarch</code></td>
|
||||
<td><code>aarch64-pc-windows-msvc</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><code>amd</code></td>
|
||||
<td><code>x86_64-pc-windows-msvc</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>i686-pc-windows-msvc</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Development
|
||||
|
||||
This Node.js binding is written in [Rust]. To build this binding, you
|
||||
need to install the Rust compiler, see [the Install Rust
|
||||
Page](https://www.rust-lang.org/tools/install). Then, the workflow is
|
||||
@@ -31,7 +102,7 @@ Once the Rust compiler, Node.js and npm are installed, you can run the
|
||||
following commands:
|
||||
|
||||
```sh
|
||||
$ npm install
|
||||
$ npm install --ignore-scripts
|
||||
$ npm run build
|
||||
$ npm run test
|
||||
```
|
||||
|
||||
59
bindings/matrix-sdk-crypto-nodejs/cliff.toml
Normal file
59
bindings/matrix-sdk-crypto-nodejs/cliff.toml
Normal file
@@ -0,0 +1,59 @@
|
||||
# configuration file for git-cliff (0.1.0)
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Matrix SDK Crypto Node.js Changelog\n
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://tera.netlify.app/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | filter(attribute="scope", value="crypto-nodejs") | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {{ commit.id | truncate(length=7, end="") }}{% if commit.breaking %} [**breaking**] {% endif %}: {{ commit.message | upper_first }}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
# changelog footer
|
||||
footer = """
|
||||
"""
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = true
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/matrix-org/matrix-rust-sdk/issues/${2}))"},
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features"},
|
||||
{ message = "^fix", group = "Bug Fixes"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^perf", group = "Performance"},
|
||||
{ message = "^test", group = "Testing"},
|
||||
{ body = ".*security", group = "Security"},
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = true
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
skip_tags = "v0.1.0-beta.1"
|
||||
# regex for ignoring tags
|
||||
ignore_tags = ""
|
||||
# sort the tags chronologically
|
||||
date_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "oldest"
|
||||
113
bindings/matrix-sdk-crypto-nodejs/download-lib.js
Normal file
113
bindings/matrix-sdk-crypto-nodejs/download-lib.js
Normal file
@@ -0,0 +1,113 @@
|
||||
const { DownloaderHelper } = require('node-downloader-helper');
|
||||
const { version } = require("./package.json");
|
||||
const { platform, arch } = process
|
||||
|
||||
const DOWNLOADS_BASE_URL = "https://github.com/matrix-org/matrix-rust-sdk/releases/download";
|
||||
const CURRENT_VERSION = `matrix-sdk-crypto-nodejs-v${version}`;
|
||||
|
||||
const byteHelper = function (value) {
|
||||
if (value === 0) {
|
||||
return '0 b';
|
||||
}
|
||||
const units = ['b', 'kB', 'MB', 'GB', 'TB'];
|
||||
const number = Math.floor(Math.log(value) / Math.log(1024));
|
||||
return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + ' ' +
|
||||
units[number];
|
||||
};
|
||||
|
||||
function download_lib(libname) {
|
||||
let startTime = new Date();
|
||||
|
||||
const url = `${DOWNLOADS_BASE_URL}/${CURRENT_VERSION}/${libname}`;
|
||||
console.info(`Downloading lib ${libname} from ${url}`);
|
||||
const dl = new DownloaderHelper(url, __dirname, {
|
||||
override: true,
|
||||
});
|
||||
|
||||
dl.on('end', () => console.info('Download Completed'));
|
||||
dl.on('error', (err) => console.info('Download Failed', err));
|
||||
dl.on('progress', stats => {
|
||||
const progress = stats.progress.toFixed(1);
|
||||
const speed = byteHelper(stats.speed);
|
||||
const downloaded = byteHelper(stats.downloaded);
|
||||
const total = byteHelper(stats.total);
|
||||
|
||||
// print every one second (`progress.throttled` can be used instead)
|
||||
const currentTime = new Date();
|
||||
const elaspsedTime = currentTime - startTime;
|
||||
if (elaspsedTime > 1000) {
|
||||
startTime = currentTime;
|
||||
console.info(`${speed}/s - ${progress}% [${downloaded}/${total}]`);
|
||||
}
|
||||
});
|
||||
dl.start().catch(err => console.error(err));
|
||||
}
|
||||
|
||||
function isMusl() {
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
||||
try {
|
||||
return readFileSync('/usr/bin/ldd', 'utf8').includes('musl')
|
||||
} catch (e) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header
|
||||
return !glibcVersionRuntime
|
||||
}
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
download_lib('matrix-sdk-crypto.win32-x64-msvc.node')
|
||||
break
|
||||
case 'ia32':
|
||||
download_lib('matrix-sdk-crypto.win32-ia32-msvc.node')
|
||||
break
|
||||
case 'arm64':
|
||||
download_lib('matrix-sdk-crypto.win32-arm64-msvc.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'darwin':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
download_lib('matrix-sdk-crypto.darwin-x64.node')
|
||||
break
|
||||
case 'arm64':
|
||||
download_lib('matrix-sdk-crypto.darwin-arm64.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'linux':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
if (isMusl()) {
|
||||
download_lib('matrix-sdk-crypto.linux-x64-musl.node')
|
||||
} else {
|
||||
download_lib('matrix-sdk-crypto.linux-x64-gnu.node')
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
if (isMusl()) {
|
||||
throw new Error('Linux for arm64 musl isn\'t support at the moment')
|
||||
} else {
|
||||
download_lib('matrix-sdk-crypto.linux-arm64-gnu.node')
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
download_lib('matrix-sdk-crypto.linux-arm-gnueabihf.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@matrix-org/matrix-sdk-crypto",
|
||||
"version": "0.5.0",
|
||||
"name": "@matrix-org/matrix-sdk-crypto-nodejs",
|
||||
"version": "0.1.0-beta.1",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
@@ -23,7 +23,11 @@
|
||||
"scripts": {
|
||||
"release-build": "napi build --platform --release --strip",
|
||||
"build": "napi build --platform",
|
||||
"postinstall": "node download-lib.js",
|
||||
"test": "jest --verbose --testTimeout 10000",
|
||||
"doc": "typedoc --tsconfig ."
|
||||
},
|
||||
"dependencies": {
|
||||
"node-downloader-helper": "^2.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ impl Attachment {
|
||||
None => {
|
||||
return Err(napi::Error::from_reason(
|
||||
"The media encryption info are absent from the given encrypted attachment"
|
||||
.to_string(),
|
||||
.to_owned(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ impl From<ruma::OwnedUserId> for UserId {
|
||||
#[napi]
|
||||
impl UserId {
|
||||
/// Parse/validate and create a new `UserId`.
|
||||
#[napi(constructor)]
|
||||
#[napi(constructor, strict)]
|
||||
pub fn new(id: String) -> napi::Result<Self> {
|
||||
Ok(Self::from(ruma::UserId::parse(id.as_str()).map_err(into_err)?))
|
||||
}
|
||||
@@ -78,7 +78,7 @@ impl From<ruma::OwnedDeviceId> for DeviceId {
|
||||
#[napi]
|
||||
impl DeviceId {
|
||||
/// Create a new `DeviceId`.
|
||||
#[napi(constructor)]
|
||||
#[napi(constructor, strict)]
|
||||
pub fn new(id: String) -> Self {
|
||||
Self::from(Into::<ruma::OwnedDeviceId>::into(id))
|
||||
}
|
||||
@@ -109,7 +109,7 @@ impl From<ruma::OwnedDeviceKeyId> for DeviceKeyId {
|
||||
#[napi]
|
||||
impl DeviceKeyId {
|
||||
/// Parse/validate and create a new `DeviceKeyId`.
|
||||
#[napi(constructor)]
|
||||
#[napi(constructor, strict)]
|
||||
pub fn new(id: String) -> napi::Result<Self> {
|
||||
Ok(Self::from(ruma::DeviceKeyId::parse(id.as_str()).map_err(into_err)?))
|
||||
}
|
||||
@@ -212,7 +212,7 @@ impl From<ruma::OwnedRoomId> for RoomId {
|
||||
#[napi]
|
||||
impl RoomId {
|
||||
/// Parse/validate and create a new `RoomId`.
|
||||
#[napi(constructor)]
|
||||
#[napi(constructor, strict)]
|
||||
pub fn new(id: String) -> napi::Result<Self> {
|
||||
Ok(Self::from(ruma::RoomId::parse(id).map_err(into_err)?))
|
||||
}
|
||||
@@ -252,7 +252,7 @@ pub struct ServerName {
|
||||
#[napi]
|
||||
impl ServerName {
|
||||
/// Parse/validate and create a new `ServerName`.
|
||||
#[napi(constructor)]
|
||||
#[napi(constructor, strict)]
|
||||
pub fn new(name: String) -> napi::Result<Self> {
|
||||
Ok(Self { inner: ruma::ServerName::parse(name).map_err(into_err)? })
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ impl OlmMachine {
|
||||
/// data at rest in the store. **Warning**, if no passphrase is given, the
|
||||
/// store and all its data will remain unencrypted. This argument is
|
||||
/// ignored if `store_path` is not set.
|
||||
#[napi]
|
||||
#[napi(strict)]
|
||||
pub async fn initialize(
|
||||
user_id: &identifiers::UserId,
|
||||
device_id: &identifiers::DeviceId,
|
||||
@@ -144,7 +144,7 @@ impl OlmMachine {
|
||||
/// response.
|
||||
/// * `one_time_keys_count`, the current one-time keys counts that the sync
|
||||
/// response returned.
|
||||
#[napi]
|
||||
#[napi(strict)]
|
||||
pub async fn receive_sync_changes(
|
||||
&self,
|
||||
to_device_events: String,
|
||||
@@ -227,7 +227,7 @@ impl OlmMachine {
|
||||
/// * `request_type`, the request type associated to the request ID.
|
||||
/// * `response`, the response that was received from the server after the
|
||||
/// outgoing request was sent out.
|
||||
#[napi]
|
||||
#[napi(strict)]
|
||||
pub async fn mark_request_as_sent(
|
||||
&self,
|
||||
request_id: String,
|
||||
@@ -272,7 +272,7 @@ impl OlmMachine {
|
||||
/// * `users`, the list of users that we should check if we lack a session
|
||||
/// with one of their devices. This can be an empty array or `null` when
|
||||
/// calling this method between sync requests.
|
||||
#[napi]
|
||||
#[napi(strict)]
|
||||
pub async fn get_missing_sessions(
|
||||
&self,
|
||||
users: Option<Vec<&identifiers::UserId>>,
|
||||
@@ -312,7 +312,7 @@ impl OlmMachine {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `users`, an array over user IDs that should be marked for tracking.
|
||||
#[napi]
|
||||
#[napi(strict)]
|
||||
pub async fn update_tracked_users(&self, users: Vec<&identifiers::UserId>) {
|
||||
let users = users.into_iter().map(|user| user.inner.clone()).collect::<Vec<_>>();
|
||||
|
||||
@@ -326,7 +326,7 @@ impl OlmMachine {
|
||||
/// * `room_id`, the room ID of the room where the room key will be used.
|
||||
/// * `users`, the list of users that should receive the room key.
|
||||
/// * `encryption_settings`, the encryption settings.
|
||||
#[napi]
|
||||
#[napi(strict)]
|
||||
pub async fn share_room_key(
|
||||
&self,
|
||||
room_id: &identifiers::RoomId,
|
||||
@@ -357,7 +357,7 @@ impl OlmMachine {
|
||||
/// * `event_type`, the plaintext type of the event.
|
||||
/// * `content`, the JSON-encoded content of the message that should be
|
||||
/// encrypted.
|
||||
#[napi]
|
||||
#[napi(strict)]
|
||||
pub async fn encrypt_room_event(
|
||||
&self,
|
||||
room_id: &identifiers::RoomId,
|
||||
@@ -383,7 +383,7 @@ impl OlmMachine {
|
||||
///
|
||||
/// * `event`, the event that should be decrypted.
|
||||
/// * `room_id`, the ID of the room where the event was sent to.
|
||||
#[napi]
|
||||
#[napi(strict)]
|
||||
pub async fn decrypt_room_event(
|
||||
&self,
|
||||
event: String,
|
||||
@@ -409,7 +409,7 @@ impl OlmMachine {
|
||||
|
||||
/// Sign the given message using our device key and if available
|
||||
/// cross-signing master key.
|
||||
#[napi]
|
||||
#[napi(strict)]
|
||||
pub async fn sign(&self, message: String) -> types::Signatures {
|
||||
self.inner.sign(message.as_str()).await.into()
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ pub struct DeviceLists {
|
||||
#[napi]
|
||||
impl DeviceLists {
|
||||
/// Create an empty `DeviceLists`.
|
||||
#[napi(constructor)]
|
||||
#[napi(constructor, strict)]
|
||||
pub fn new(
|
||||
changed: Option<Vec<&identifiers::UserId>>,
|
||||
left: Option<Vec<&identifiers::UserId>>,
|
||||
|
||||
@@ -29,7 +29,7 @@ impl Signatures {
|
||||
|
||||
/// Add the given signature from the given signer and the given key ID to
|
||||
/// the collection.
|
||||
#[napi]
|
||||
#[napi(strict)]
|
||||
pub fn add_signature(
|
||||
&mut self,
|
||||
signer: &UserId,
|
||||
@@ -43,13 +43,13 @@ impl Signatures {
|
||||
|
||||
/// Try to find an Ed25519 signature from the given signer with
|
||||
/// the given key ID.
|
||||
#[napi]
|
||||
#[napi(strict)]
|
||||
pub fn get_signature(&self, signer: &UserId, key_id: &DeviceKeyId) -> Option<Ed25519Signature> {
|
||||
self.inner.get_signature(signer.inner.as_ref(), key_id.inner.as_ref()).map(Into::into)
|
||||
}
|
||||
|
||||
/// Get the map of signatures that belong to the given user.
|
||||
#[napi]
|
||||
#[napi(strict)]
|
||||
pub fn get(&self, signer: &UserId) -> Option<HashMap<String, MaybeSignature>> {
|
||||
self.inner.get(signer.inner.as_ref()).map(|map| {
|
||||
map.iter()
|
||||
|
||||
@@ -42,7 +42,7 @@ impl From<vodozemac::Ed25519Signature> for Ed25519Signature {
|
||||
impl Ed25519Signature {
|
||||
/// Try to create an Ed25519 signature from an unpadded base64
|
||||
/// representation.
|
||||
#[napi(constructor)]
|
||||
#[napi(constructor, strict)]
|
||||
pub fn new(signature: String) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: vodozemac::Ed25519Signature::from_base64(signature.as_str())
|
||||
|
||||
@@ -31,6 +31,7 @@ thiserror = "1.0.30"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
tokio-stream = "0.1.8"
|
||||
tracing = "0.1.32"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
# keep in sync with uniffi dependency in matrix-sdk-crypto-ffi, and uniffi_bindgen in ffi CI job
|
||||
uniffi = "0.18.0"
|
||||
uniffi_macros = "0.18.0"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace sdk {
|
||||
void setup_tracing(string configuration);
|
||||
|
||||
MediaSource media_source_from_url(string url);
|
||||
MessageEventContent message_event_content_from_markdown(string md);
|
||||
string gen_transaction_id();
|
||||
@@ -156,24 +158,23 @@ interface MediaSource {
|
||||
|
||||
[Error]
|
||||
enum AuthenticationError {
|
||||
"ClientMissing",
|
||||
"Generic",
|
||||
"ClientMissing",
|
||||
"Generic",
|
||||
};
|
||||
|
||||
interface HomeserverLoginDetails {
|
||||
string url();
|
||||
string? authentication_issuer();
|
||||
boolean supports_password_login();
|
||||
};
|
||||
|
||||
interface AuthenticationService {
|
||||
constructor(string base_path);
|
||||
|
||||
[Throws=AuthenticationError]
|
||||
string homeserver();
|
||||
HomeserverLoginDetails? homeserver_details();
|
||||
|
||||
[Throws=AuthenticationError]
|
||||
string? authentication_issuer();
|
||||
|
||||
[Throws=AuthenticationError]
|
||||
boolean supports_password_login();
|
||||
|
||||
[Throws=AuthenticationError]
|
||||
void use_server(string server_name);
|
||||
void configure_homeserver(string server_name);
|
||||
|
||||
[Throws=AuthenticationError]
|
||||
Client login(string username, string password);
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures_util::future::join3;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use super::{client::Client, client_builder::ClientBuilder};
|
||||
use super::{client::Client, client_builder::ClientBuilder, RUNTIME};
|
||||
|
||||
pub struct AuthenticationService {
|
||||
base_path: String,
|
||||
client: RwLock<Option<Arc<Client>>>,
|
||||
homeserver_details: RwLock<Option<Arc<HomeserverLoginDetails>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -23,52 +25,67 @@ impl From<anyhow::Error> for AuthenticationError {
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthenticationService {
|
||||
/// Creates a new service to authenticate a user with.
|
||||
pub fn new(base_path: String) -> Self {
|
||||
AuthenticationService { base_path, client: RwLock::new(None) }
|
||||
}
|
||||
pub struct HomeserverLoginDetails {
|
||||
url: String,
|
||||
authentication_issuer: Option<String>,
|
||||
supports_password_login: bool,
|
||||
}
|
||||
|
||||
/// The currently configured homeserver.
|
||||
pub fn homeserver(&self) -> Result<String, AuthenticationError> {
|
||||
self.client
|
||||
.read()
|
||||
.as_ref()
|
||||
.ok_or(AuthenticationError::ClientMissing)
|
||||
.map(|client| client.homeserver())
|
||||
impl HomeserverLoginDetails {
|
||||
/// The URL of the currently configured homeserver.
|
||||
pub fn url(&self) -> String {
|
||||
self.url.clone()
|
||||
}
|
||||
|
||||
/// The OIDC Provider that is trusted by the homeserver. `None` when
|
||||
/// not configured.
|
||||
pub fn authentication_issuer(&self) -> Result<Option<String>, AuthenticationError> {
|
||||
self.client
|
||||
.read()
|
||||
.as_ref()
|
||||
.ok_or(AuthenticationError::ClientMissing)
|
||||
.map(|client| client.authentication_issuer())
|
||||
pub fn authentication_issuer(&self) -> Option<String> {
|
||||
self.authentication_issuer.clone()
|
||||
}
|
||||
|
||||
/// Whether the current homeserver supports the password login flow.
|
||||
pub fn supports_password_login(&self) -> Result<bool, AuthenticationError> {
|
||||
self.client
|
||||
.read()
|
||||
.as_ref()
|
||||
.ok_or(AuthenticationError::ClientMissing)
|
||||
.and_then(|client| client.supports_password_login().map_err(AuthenticationError::from))
|
||||
pub fn supports_password_login(&self) -> bool {
|
||||
self.supports_password_login
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthenticationService {
|
||||
/// Creates a new service to authenticate a user with.
|
||||
pub fn new(base_path: String) -> Self {
|
||||
AuthenticationService {
|
||||
base_path,
|
||||
client: RwLock::new(None),
|
||||
homeserver_details: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the server to authenticate with the specified homeserver.
|
||||
pub fn use_server(&self, server_name: String) -> Result<(), AuthenticationError> {
|
||||
pub fn homeserver_details(&self) -> Option<Arc<HomeserverLoginDetails>> {
|
||||
self.homeserver_details.read().clone()
|
||||
}
|
||||
|
||||
/// Updates the service to authenticate with the homeserver for the
|
||||
/// specified address.
|
||||
pub fn configure_homeserver(&self, server_name: String) -> Result<(), AuthenticationError> {
|
||||
// Construct a username as the builder currently requires one.
|
||||
let username = format!("@auth:{}", server_name);
|
||||
let client = Arc::new(ClientBuilder::new())
|
||||
.base_path(self.base_path.clone())
|
||||
.username(username)
|
||||
.build()
|
||||
.map_err(AuthenticationError::from)?;
|
||||
|
||||
*self.client.write() = Some(client);
|
||||
Ok(())
|
||||
let mut builder =
|
||||
Arc::new(ClientBuilder::new()).base_path(self.base_path.clone()).username(username);
|
||||
|
||||
if server_name.starts_with("http://") || server_name.starts_with("https://") {
|
||||
builder = builder.homeserver_url(server_name)
|
||||
}
|
||||
|
||||
let client = builder.build().map_err(AuthenticationError::from)?;
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
let details = Arc::new(self.details_from_client(&client).await?);
|
||||
|
||||
*self.client.write() = Some(client);
|
||||
*self.homeserver_details.write() = Some(details);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Performs a password login using the current homeserver.
|
||||
@@ -97,4 +114,23 @@ impl AuthenticationService {
|
||||
None => Err(AuthenticationError::ClientMissing),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the homeserver login details from a client.
|
||||
async fn details_from_client(
|
||||
&self,
|
||||
client: &Arc<Client>,
|
||||
) -> Result<HomeserverLoginDetails, AuthenticationError> {
|
||||
let login_details = join3(
|
||||
client.async_homeserver(),
|
||||
client.authentication_issuer(),
|
||||
client.supports_password_login(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let url = login_details.0;
|
||||
let authentication_issuer = login_details.1;
|
||||
let supports_password_login = login_details.2.map_err(AuthenticationError::from)?;
|
||||
|
||||
Ok(HomeserverLoginDetails { url, authentication_issuer, supports_password_login })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,26 +74,27 @@ impl Client {
|
||||
|
||||
/// The homeserver this client is configured to use.
|
||||
pub fn homeserver(&self) -> String {
|
||||
RUNTIME.block_on(async move { self.client.homeserver().await.to_string() })
|
||||
RUNTIME.block_on(async move { self.async_homeserver().await })
|
||||
}
|
||||
|
||||
pub async fn async_homeserver(&self) -> String {
|
||||
self.client.homeserver().await.to_string()
|
||||
}
|
||||
|
||||
/// The OIDC Provider that is trusted by the homeserver. `None` when
|
||||
/// not configured.
|
||||
pub fn authentication_issuer(&self) -> Option<String> {
|
||||
RUNTIME.block_on(async move {
|
||||
self.client.authentication_issuer().await.map(|server| server.to_string())
|
||||
})
|
||||
pub async fn authentication_issuer(&self) -> Option<String> {
|
||||
self.client.authentication_issuer().await.map(|server| server.to_string())
|
||||
}
|
||||
|
||||
/// Whether or not the client's homeserver supports the password login flow.
|
||||
pub fn supports_password_login(&self) -> anyhow::Result<bool> {
|
||||
RUNTIME.block_on(async move {
|
||||
let login_types = self.client.get_login_types().await?;
|
||||
let supports_password = login_types.flows.iter().any(|login_type| {
|
||||
matches!(login_type, get_login_types::v3::LoginType::Password(_))
|
||||
});
|
||||
Ok(supports_password)
|
||||
})
|
||||
pub async fn supports_password_login(&self) -> anyhow::Result<bool> {
|
||||
let login_types = self.client.get_login_types().await?;
|
||||
let supports_password = login_types
|
||||
.flows
|
||||
.iter()
|
||||
.any(|login_type| matches!(login_type, get_login_types::v3::LoginType::Password(_)));
|
||||
Ok(supports_password)
|
||||
}
|
||||
|
||||
pub fn start_sync(&self) {
|
||||
|
||||
@@ -17,6 +17,7 @@ use matrix_sdk::Session;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::runtime::Runtime;
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
pub use uniffi_api::*;
|
||||
|
||||
pub static RUNTIME: Lazy<Runtime> =
|
||||
@@ -55,3 +56,10 @@ impl From<anyhow::Error> for ClientError {
|
||||
ClientError::Generic { msg: e.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_tracing(configuration: String) {
|
||||
tracing_subscriber::registry()
|
||||
.with(EnvFilter::new(configuration))
|
||||
.with(fmt::layer().with_ansi(false))
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ http = "0.2.6"
|
||||
matrix-sdk = { version = "0.5.0", path = "../matrix-sdk", default-features = false, features = ["appservice"] }
|
||||
percent-encoding = "2.1.0"
|
||||
regex = "1.5.5"
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "appservice-api-s"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "appservice-api-s"] }
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
serde_yaml = "0.8.23"
|
||||
|
||||
@@ -27,11 +27,11 @@ pub async fn handle_room_member(
|
||||
trace!("not an appservice user: {}", event.state_key);
|
||||
} else if let MembershipState::Invite = event.content.membership {
|
||||
let user_id = UserId::parse(event.state_key.as_str())?;
|
||||
if let Err(error) = appservice.register_virtual_user(user_id.localpart()).await {
|
||||
if let Err(error) = appservice.register_virtual_user(user_id.localpart(), None).await {
|
||||
error_if_user_not_in_use(error)?;
|
||||
}
|
||||
|
||||
let client = appservice.virtual_user_client(user_id.localpart()).await?;
|
||||
let client = appservice.virtual_user(Some(user_id.localpart())).await?;
|
||||
client.join_room_by_id(room.room_id()).await?;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,9 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let appservice = AppService::new(homeserver_url, server_name, registration).await?;
|
||||
appservice.register_user_query(Box::new(|_, _| Box::pin(async { true }))).await;
|
||||
appservice
|
||||
.register_event_handler_context(appservice.clone())?
|
||||
.virtual_user(None)
|
||||
.await?
|
||||
.register_event_handler_context(appservice.clone())
|
||||
.register_event_handler(
|
||||
move |event: OriginalSyncRoomMemberEvent,
|
||||
room: Room,
|
||||
@@ -69,7 +71,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
handle_room_member(appservice, room, event)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
|
||||
let (host, port) = appservice.registration().get_host_and_port()?;
|
||||
appservice.run(host, port).await?;
|
||||
|
||||
@@ -41,41 +41,41 @@ pub enum Error {
|
||||
#[error("uri path is unknown")]
|
||||
UriPathUnknown,
|
||||
|
||||
#[error(transparent)]
|
||||
HttpRequest(#[from] ruma::api::error::FromHttpRequestError),
|
||||
#[error("HTTP request parsing error: {0}")]
|
||||
FromHttpRequest(#[from] ruma::api::error::FromHttpRequestError),
|
||||
|
||||
#[error(transparent)]
|
||||
#[error("identifier failed to parse: {0}")]
|
||||
Identifier(#[from] ruma::IdParseError),
|
||||
|
||||
#[error(transparent)]
|
||||
#[error("HTTP error: {0}")]
|
||||
Http(#[from] http::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
#[error("url parse error: {0}")]
|
||||
Url(#[from] url::ParseError),
|
||||
|
||||
#[error(transparent)]
|
||||
#[error("deserialization error: {0}")]
|
||||
Serde(#[from] serde::de::value::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
#[error("I/O error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
#[error("http uri invalid error: {0}")]
|
||||
InvalidUri(#[from] http::uri::InvalidUri),
|
||||
|
||||
#[error(transparent)]
|
||||
Matrix(#[from] matrix_sdk::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
#[error("regex error: {0}")]
|
||||
Regex(#[from] regex::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
#[error("serde yaml error: {0}")]
|
||||
SerdeYaml(#[from] serde_yaml::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
#[error("serde json error: {0}")]
|
||||
SerdeJson(#[from] serde_json::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Utf8Error(#[from] std::str::Utf8Error),
|
||||
#[error("utf8 error: {0}")]
|
||||
Utf8(#[from] std::str::Utf8Error),
|
||||
|
||||
#[error("warp rejection: {0}")]
|
||||
WarpRejection(String),
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright 2022 Famedly GmbH
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{future::Future, pin::Pin, sync::Arc};
|
||||
|
||||
use matrix_sdk::locks::Mutex;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
124
crates/matrix-sdk-appservice/src/registration.rs
Normal file
124
crates/matrix-sdk-appservice/src/registration.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2022 Famedly GmbH
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! AppService Registration.
|
||||
|
||||
use std::{convert::TryFrom, fs::File, ops::Deref, path::PathBuf};
|
||||
|
||||
use http::Uri;
|
||||
use regex::Regex;
|
||||
use ruma::api::appservice::Registration;
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
pub type Host = String;
|
||||
pub type Port = u16;
|
||||
|
||||
/// AppService Registration
|
||||
///
|
||||
/// Wrapper around [`Registration`]. See also <https://matrix.org/docs/spec/application_service/r0.1.2#registration>.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppServiceRegistration {
|
||||
inner: Registration,
|
||||
}
|
||||
|
||||
impl AppServiceRegistration {
|
||||
/// Try to load registration from yaml string
|
||||
///
|
||||
/// See the fields of [`Registration`] for the required format
|
||||
pub fn try_from_yaml_str(value: impl AsRef<str>) -> Result<Self> {
|
||||
Ok(Self { inner: serde_yaml::from_str(value.as_ref())? })
|
||||
}
|
||||
|
||||
/// Try to load registration from yaml file
|
||||
///
|
||||
/// See the fields of [`Registration`] for the required format
|
||||
pub fn try_from_yaml_file(path: impl Into<PathBuf>) -> Result<Self> {
|
||||
let file = File::open(path.into())?;
|
||||
|
||||
Ok(Self { inner: serde_yaml::from_reader(file)? })
|
||||
}
|
||||
|
||||
/// Get the host and port from the registration URL
|
||||
///
|
||||
/// If no port is found it falls back to scheme defaults: 80 for http and
|
||||
/// 443 for https
|
||||
pub fn get_host_and_port(&self) -> Result<(Host, Port)> {
|
||||
let uri = Uri::try_from(&self.inner.url)?;
|
||||
|
||||
let host = uri.host().ok_or(Error::MissingRegistrationHost)?.to_owned();
|
||||
let port = match uri.port() {
|
||||
Some(port) => Ok(port.as_u16()),
|
||||
None => match uri.scheme_str() {
|
||||
Some("http") => Ok(80),
|
||||
Some("https") => Ok(443),
|
||||
_ => Err(Error::MissingRegistrationPort),
|
||||
},
|
||||
}?;
|
||||
|
||||
Ok((host, port))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Registration> for AppServiceRegistration {
|
||||
fn from(value: Registration) -> Self {
|
||||
Self { inner: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AppServiceRegistration {
|
||||
type Target = Registration;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache data for the registration namespaces.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NamespaceCache {
|
||||
/// List of user regexes in our namespace
|
||||
pub(crate) users: Vec<Regex>,
|
||||
/// List of alias regexes in our namespace
|
||||
#[allow(dead_code)]
|
||||
aliases: Vec<Regex>,
|
||||
/// List of room id regexes in our namespace
|
||||
#[allow(dead_code)]
|
||||
rooms: Vec<Regex>,
|
||||
}
|
||||
|
||||
impl NamespaceCache {
|
||||
/// Creates a new registration cache from a [`Registration`] value
|
||||
pub fn from_registration(registration: &Registration) -> Result<Self> {
|
||||
let users = registration
|
||||
.namespaces
|
||||
.users
|
||||
.iter()
|
||||
.map(|user| Regex::new(&user.regex))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let aliases = registration
|
||||
.namespaces
|
||||
.aliases
|
||||
.iter()
|
||||
.map(|user| Regex::new(&user.regex))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let rooms = registration
|
||||
.namespaces
|
||||
.rooms
|
||||
.iter()
|
||||
.map(|user| Regex::new(&user.regex))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(NamespaceCache { users, aliases, rooms })
|
||||
}
|
||||
}
|
||||
149
crates/matrix-sdk-appservice/src/virtual_user.rs
Normal file
149
crates/matrix-sdk-appservice/src/virtual_user.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright 2022 Famedly GmbH
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Virtual users.
|
||||
|
||||
use matrix_sdk::{config::RequestConfig, Client, ClientBuildError, ClientBuilder, Session};
|
||||
use ruma::{
|
||||
api::client::{session::login, uiaa::UserIdentifier},
|
||||
assign, DeviceId, OwnedDeviceId, UserId,
|
||||
};
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{AppService, Result};
|
||||
|
||||
/// Builder for a virtual user
|
||||
#[derive(Debug)]
|
||||
pub struct VirtualUserBuilder<'a> {
|
||||
appservice: &'a AppService,
|
||||
localpart: &'a str,
|
||||
device_id: Option<OwnedDeviceId>,
|
||||
client_builder: ClientBuilder,
|
||||
log_in: bool,
|
||||
restored_session: Option<Session>,
|
||||
}
|
||||
|
||||
impl<'a> VirtualUserBuilder<'a> {
|
||||
/// Create a new virtual user builder
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `localpart` - The localpart of the virtual user
|
||||
pub fn new(appservice: &'a AppService, localpart: &'a str) -> Self {
|
||||
Self {
|
||||
appservice,
|
||||
localpart,
|
||||
device_id: None,
|
||||
client_builder: Client::builder(),
|
||||
log_in: false,
|
||||
restored_session: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the device ID of the virtual user
|
||||
pub fn device_id(mut self, device_id: Option<OwnedDeviceId>) -> Self {
|
||||
self.device_id = device_id;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the client builder to use for the virtual user
|
||||
pub fn client_builder(mut self, client_builder: ClientBuilder) -> Self {
|
||||
self.client_builder = client_builder;
|
||||
self
|
||||
}
|
||||
|
||||
/// Log in as the virtual user
|
||||
///
|
||||
/// In some cases it is necessary to log in as the virtual user, such as to
|
||||
/// upload device keys
|
||||
pub fn login(mut self) -> Self {
|
||||
self.log_in = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Restore a persisted session
|
||||
///
|
||||
/// This is primarily useful if you enable
|
||||
/// [`VirtualUserBuilder::login()`] and want to restore a session
|
||||
/// from a previous run.
|
||||
pub fn restored_session(mut self, session: Session) -> Self {
|
||||
self.restored_session = Some(session);
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the virtual user
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns an error if an invalid localpart is provided.
|
||||
pub async fn build(self) -> Result<Client> {
|
||||
if let Some(client) = self.appservice.clients.get(self.localpart) {
|
||||
return Ok(client.clone());
|
||||
}
|
||||
|
||||
let user_id = UserId::parse_with_server_name(self.localpart, &self.appservice.server_name)?;
|
||||
if !(self.appservice.user_id_is_in_namespace(&user_id)
|
||||
|| self.localpart == self.appservice.registration.sender_localpart)
|
||||
{
|
||||
warn!("Virtual client id '{user_id}' is not in the namespace")
|
||||
}
|
||||
|
||||
let mut builder = self.client_builder;
|
||||
|
||||
if !self.log_in && self.localpart != self.appservice.registration.sender_localpart {
|
||||
builder = builder.assert_identity();
|
||||
}
|
||||
|
||||
let client = builder
|
||||
.homeserver_url(self.appservice.homeserver_url.clone())
|
||||
.appservice_mode()
|
||||
.build()
|
||||
.await
|
||||
.map_err(ClientBuildError::assert_valid_builder_args)?;
|
||||
|
||||
let session = if let Some(session) = self.restored_session {
|
||||
session
|
||||
} else if self.log_in && self.localpart != self.appservice.registration.sender_localpart {
|
||||
let login_info =
|
||||
login::v3::LoginInfo::ApplicationService(login::v3::ApplicationService::new(
|
||||
UserIdentifier::UserIdOrLocalpart(self.localpart),
|
||||
));
|
||||
|
||||
let request = assign!(login::v3::Request::new(login_info), {
|
||||
device_id: self.device_id.as_ref().map(|v| v.as_ref()),
|
||||
initial_device_display_name: None,
|
||||
});
|
||||
|
||||
let response =
|
||||
client.send(request, Some(RequestConfig::short_retry().force_auth())).await?;
|
||||
|
||||
Session {
|
||||
access_token: response.access_token,
|
||||
user_id: response.user_id,
|
||||
device_id: response.device_id,
|
||||
}
|
||||
} else {
|
||||
// Don’t log in
|
||||
Session {
|
||||
access_token: self.appservice.registration.as_token.clone(),
|
||||
user_id: user_id.clone(),
|
||||
device_id: self.device_id.unwrap_or_else(DeviceId::new),
|
||||
}
|
||||
};
|
||||
|
||||
client.restore_login(session).await?;
|
||||
|
||||
self.appservice.clients.insert(self.localpart.to_owned(), client.clone());
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
}
|
||||
@@ -107,10 +107,11 @@ mod filters {
|
||||
warp::any()
|
||||
.and(valid_access_token(appservice.registration().hs_token.clone()))
|
||||
.map(move || appservice.clone())
|
||||
.and(http_request().and_then(|request| async move {
|
||||
let request = crate::transform_request_path(request).map_err(Error::from)?;
|
||||
Ok::<http::Request<Bytes>, Rejection>(request)
|
||||
}))
|
||||
.and(
|
||||
http_request().and_then(|request| async move {
|
||||
Ok::<http::Request<Bytes>, Rejection>(request)
|
||||
}),
|
||||
)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,508 +0,0 @@
|
||||
use std::{
|
||||
future,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use matrix_sdk::{
|
||||
config::RequestConfig,
|
||||
ruma::{api::appservice::Registration, events::room::member::OriginalSyncRoomMemberEvent},
|
||||
Client,
|
||||
};
|
||||
use matrix_sdk_appservice::*;
|
||||
use matrix_sdk_test::{appservice::TransactionBuilder, async_test, EventsJson};
|
||||
use ruma::{
|
||||
api::{appservice::event::push_events, MatrixVersion},
|
||||
events::AnyRoomEvent,
|
||||
room_id,
|
||||
serde::Raw,
|
||||
};
|
||||
use serde_json::json;
|
||||
use warp::{Filter, Reply};
|
||||
use wiremock::{
|
||||
matchers::{body_json, header, method, path},
|
||||
Mock, MockServer, ResponseTemplate,
|
||||
};
|
||||
|
||||
fn registration_string() -> String {
|
||||
include_str!("../tests/registration.yaml").to_owned()
|
||||
}
|
||||
|
||||
async fn appservice(
|
||||
homeserver_url: Option<String>,
|
||||
registration: Option<Registration>,
|
||||
) -> Result<AppService> {
|
||||
// env::set_var(
|
||||
// "RUST_LOG",
|
||||
// "wiremock=debug,matrix_sdk=debug,ruma=debug,warp=debug",
|
||||
// );
|
||||
let _ = tracing_subscriber::fmt::try_init();
|
||||
|
||||
let registration = match registration {
|
||||
Some(registration) => registration.into(),
|
||||
None => AppServiceRegistration::try_from_yaml_str(registration_string()).unwrap(),
|
||||
};
|
||||
|
||||
let homeserver_url = homeserver_url.unwrap_or_else(|| "http://localhost:1234".to_owned());
|
||||
let server_name = "localhost";
|
||||
|
||||
let client_builder = Client::builder()
|
||||
.request_config(RequestConfig::default().disable_retry())
|
||||
.server_versions([MatrixVersion::V1_0]);
|
||||
|
||||
AppService::with_client_builder(
|
||||
homeserver_url.as_ref(),
|
||||
server_name,
|
||||
registration,
|
||||
client_builder,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_register_virtual_user() -> Result<()> {
|
||||
let server = MockServer::start().await;
|
||||
let appservice = appservice(Some(server.uri()), None).await?;
|
||||
|
||||
let localpart = "someone";
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/_matrix/client/r0/register"))
|
||||
.and(header(
|
||||
"authorization",
|
||||
format!("Bearer {}", appservice.registration().as_token).as_str(),
|
||||
))
|
||||
.and(body_json(json!({
|
||||
"username": localpart.to_owned(),
|
||||
"type": "m.login.application_service"
|
||||
})))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"access_token": "abc123",
|
||||
"device_id": "GHTYAJCE",
|
||||
"user_id": format!("@{localpart}:localhost"),
|
||||
})))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
appservice.register_virtual_user(localpart).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_put_transaction() -> Result<()> {
|
||||
let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token";
|
||||
|
||||
let mut transaction_builder = TransactionBuilder::new();
|
||||
transaction_builder.add_room_event(EventsJson::Member);
|
||||
let transaction = transaction_builder.build_json_transaction();
|
||||
|
||||
let appservice = appservice(None, None).await?;
|
||||
|
||||
let status = warp::test::request()
|
||||
.method("PUT")
|
||||
.path(uri)
|
||||
.json(&transaction)
|
||||
.filter(&appservice.warp_filter())
|
||||
.await
|
||||
.unwrap()
|
||||
.into_response()
|
||||
.status();
|
||||
|
||||
assert_eq!(status, 200);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_put_transaction_with_repeating_txn_id() -> Result<()> {
|
||||
let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token";
|
||||
|
||||
let mut transaction_builder = TransactionBuilder::new();
|
||||
transaction_builder.add_room_event(EventsJson::Member);
|
||||
let transaction = transaction_builder.build_json_transaction();
|
||||
|
||||
let appservice = appservice(None, None).await?;
|
||||
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
let on_state_member = Arc::new(Mutex::new(false));
|
||||
appservice
|
||||
.register_event_handler({
|
||||
let on_state_member = on_state_member.clone();
|
||||
move |_ev: OriginalSyncRoomMemberEvent| {
|
||||
*on_state_member.lock().unwrap() = true;
|
||||
future::ready(())
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
let status = warp::test::request()
|
||||
.method("PUT")
|
||||
.path(uri)
|
||||
.json(&transaction)
|
||||
.filter(&appservice.warp_filter())
|
||||
.await
|
||||
.unwrap()
|
||||
.into_response()
|
||||
.status();
|
||||
|
||||
assert_eq!(status, 200);
|
||||
{
|
||||
let on_room_member_called = *on_state_member.lock().unwrap();
|
||||
assert!(on_room_member_called);
|
||||
}
|
||||
|
||||
// Reset this to check that next time it doesnt get called
|
||||
{
|
||||
let mut on_room_member_called = on_state_member.lock().unwrap();
|
||||
*on_room_member_called = false;
|
||||
}
|
||||
|
||||
let status = warp::test::request()
|
||||
.method("PUT")
|
||||
.path(uri)
|
||||
.json(&transaction)
|
||||
.filter(&appservice.warp_filter())
|
||||
.await
|
||||
.unwrap()
|
||||
.into_response()
|
||||
.status();
|
||||
|
||||
// According to https://spec.matrix.org/v1.2/application-service-api/#pushing-events
|
||||
// This should noop and return 200.
|
||||
assert_eq!(status, 200);
|
||||
{
|
||||
let on_room_member_called = *on_state_member.lock().unwrap();
|
||||
// This time we should not have called the event handler.
|
||||
assert!(!on_room_member_called);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_get_user() -> Result<()> {
|
||||
let appservice = appservice(None, None).await?;
|
||||
appservice.register_user_query(Box::new(|_, _| Box::pin(async move { true }))).await;
|
||||
|
||||
let uri = "/_matrix/app/v1/users/%40_botty_1%3Adev.famedly.local?access_token=hs_token";
|
||||
|
||||
let status = warp::test::request()
|
||||
.method("GET")
|
||||
.path(uri)
|
||||
.filter(&appservice.warp_filter())
|
||||
.await
|
||||
.unwrap()
|
||||
.into_response()
|
||||
.status();
|
||||
|
||||
assert_eq!(status, 200);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_get_room() -> Result<()> {
|
||||
let appservice = appservice(None, None).await?;
|
||||
appservice.register_room_query(Box::new(|_, _| Box::pin(async move { true }))).await;
|
||||
|
||||
let uri = "/_matrix/app/v1/rooms/%23magicforest%3Aexample.com?access_token=hs_token";
|
||||
|
||||
let status = warp::test::request()
|
||||
.method("GET")
|
||||
.path(uri)
|
||||
.filter(&appservice.warp_filter())
|
||||
.await
|
||||
.unwrap()
|
||||
.into_response()
|
||||
.status();
|
||||
|
||||
assert_eq!(status, 200);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_invalid_access_token() -> Result<()> {
|
||||
let uri = "/_matrix/app/v1/transactions/1?access_token=invalid_token";
|
||||
|
||||
let mut transaction_builder = TransactionBuilder::new();
|
||||
let transaction =
|
||||
transaction_builder.add_room_event(EventsJson::Member).build_json_transaction();
|
||||
|
||||
let appservice = appservice(None, None).await?;
|
||||
|
||||
let status = warp::test::request()
|
||||
.method("PUT")
|
||||
.path(uri)
|
||||
.json(&transaction)
|
||||
.filter(&appservice.warp_filter())
|
||||
.await
|
||||
.unwrap()
|
||||
.into_response()
|
||||
.status();
|
||||
|
||||
assert_eq!(status, 401);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_no_access_token() -> Result<()> {
|
||||
let uri = "/_matrix/app/v1/transactions/1";
|
||||
|
||||
let mut transaction_builder = TransactionBuilder::new();
|
||||
transaction_builder.add_room_event(EventsJson::Member);
|
||||
let transaction = transaction_builder.build_json_transaction();
|
||||
|
||||
let appservice = appservice(None, None).await?;
|
||||
|
||||
{
|
||||
let status = warp::test::request()
|
||||
.method("PUT")
|
||||
.path(uri)
|
||||
.json(&transaction)
|
||||
.filter(&appservice.warp_filter())
|
||||
.await
|
||||
.unwrap()
|
||||
.into_response()
|
||||
.status();
|
||||
|
||||
assert_eq!(status, 401);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_event_handler() -> Result<()> {
|
||||
let appservice = appservice(None, None).await?;
|
||||
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
let on_state_member = Arc::new(Mutex::new(false));
|
||||
appservice
|
||||
.register_event_handler({
|
||||
let on_state_member = on_state_member.clone();
|
||||
move |_ev: OriginalSyncRoomMemberEvent| {
|
||||
*on_state_member.lock().unwrap() = true;
|
||||
future::ready(())
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token";
|
||||
|
||||
let mut transaction_builder = TransactionBuilder::new();
|
||||
transaction_builder.add_room_event(EventsJson::Member);
|
||||
let transaction = transaction_builder.build_json_transaction();
|
||||
|
||||
warp::test::request()
|
||||
.method("PUT")
|
||||
.path(uri)
|
||||
.json(&transaction)
|
||||
.filter(&appservice.warp_filter())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let on_room_member_called = *on_state_member.lock().unwrap();
|
||||
assert!(on_room_member_called);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_unrelated_path() -> Result<()> {
|
||||
let appservice = appservice(None, None).await?;
|
||||
|
||||
let status = {
|
||||
let consumer_filter = warp::any()
|
||||
.and(appservice.warp_filter())
|
||||
.or(warp::get().and(warp::path("unrelated").map(warp::reply)));
|
||||
|
||||
let response = warp::test::request()
|
||||
.method("GET")
|
||||
.path("/unrelated")
|
||||
.filter(&consumer_filter)
|
||||
.await?
|
||||
.into_response();
|
||||
|
||||
response.status()
|
||||
};
|
||||
|
||||
assert_eq!(status, 200);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_appservice_on_sub_path() -> Result<()> {
|
||||
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||
let uri_1 = "/sub_path/_matrix/app/v1/transactions/1?access_token=hs_token";
|
||||
let uri_2 = "/sub_path/_matrix/app/v1/transactions/2?access_token=hs_token";
|
||||
|
||||
let mut transaction_builder = TransactionBuilder::new();
|
||||
transaction_builder.add_room_event(EventsJson::Member);
|
||||
let transaction_1 = transaction_builder.build_json_transaction();
|
||||
|
||||
let mut transaction_builder = TransactionBuilder::new();
|
||||
transaction_builder.add_room_event(EventsJson::MemberNameChange);
|
||||
let transaction_2 = transaction_builder.build_json_transaction();
|
||||
|
||||
let appservice = appservice(None, None).await?;
|
||||
|
||||
{
|
||||
warp::test::request()
|
||||
.method("PUT")
|
||||
.path(uri_1)
|
||||
.json(&transaction_1)
|
||||
.filter(&warp::path("sub_path").and(appservice.warp_filter()))
|
||||
.await?;
|
||||
|
||||
warp::test::request()
|
||||
.method("PUT")
|
||||
.path(uri_2)
|
||||
.json(&transaction_2)
|
||||
.filter(&warp::path("sub_path").and(appservice.warp_filter()))
|
||||
.await?;
|
||||
};
|
||||
|
||||
let members = appservice
|
||||
.get_cached_client(None)?
|
||||
.get_room(room_id)
|
||||
.expect("Expected room to be available")
|
||||
.members_no_sync()
|
||||
.await?;
|
||||
|
||||
assert_eq!(members[0].display_name().unwrap(), "changed");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_receive_transaction() -> Result<()> {
|
||||
tracing_subscriber::fmt().try_init().ok();
|
||||
let json = vec![
|
||||
Raw::new(&json!({
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": "Appservice",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "$151800140479rdvjg:localhost",
|
||||
"membership": "join",
|
||||
"origin_server_ts": 151800140,
|
||||
"sender": "@_appservice:localhost",
|
||||
"state_key": "@_appservice:localhost",
|
||||
"type": "m.room.member",
|
||||
"room_id": "!coolplace:localhost",
|
||||
"unsigned": {
|
||||
"age": 2970366
|
||||
}
|
||||
}))?
|
||||
.cast::<AnyRoomEvent>(),
|
||||
Raw::new(&json!({
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": "Appservice",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "$151800140491rfbja:localhost",
|
||||
"membership": "join",
|
||||
"origin_server_ts": 151800140,
|
||||
"sender": "@_appservice:localhost",
|
||||
"state_key": "@_appservice:localhost",
|
||||
"type": "m.room.member",
|
||||
"room_id": "!boringplace:localhost",
|
||||
"unsigned": {
|
||||
"age": 2970366
|
||||
}
|
||||
}))?
|
||||
.cast::<AnyRoomEvent>(),
|
||||
Raw::new(&json!({
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": "Alice",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "$151800140517rfvjc:localhost",
|
||||
"membership": "join",
|
||||
"origin_server_ts": 151800140,
|
||||
"sender": "@_appservice_alice:localhost",
|
||||
"state_key": "@_appservice_alice:localhost",
|
||||
"type": "m.room.member",
|
||||
"room_id": "!coolplace:localhost",
|
||||
"unsigned": {
|
||||
"age": 2970366
|
||||
}
|
||||
}))?
|
||||
.cast::<AnyRoomEvent>(),
|
||||
Raw::new(&json!({
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": "Bob",
|
||||
"membership": "invite"
|
||||
},
|
||||
"event_id": "$151800140594rfvjc:localhost",
|
||||
"membership": "invite",
|
||||
"origin_server_ts": 151800174,
|
||||
"sender": "@_appservice_bob:localhost",
|
||||
"state_key": "@_appservice_bob:localhost",
|
||||
"type": "m.room.member",
|
||||
"room_id": "!boringplace:localhost",
|
||||
"unsigned": {
|
||||
"age": 2970366
|
||||
}
|
||||
}))?
|
||||
.cast::<AnyRoomEvent>(),
|
||||
];
|
||||
let appservice = appservice(None, None).await?;
|
||||
|
||||
let alice = appservice.virtual_user_client("_appservice_alice").await?;
|
||||
let bob = appservice.virtual_user_client("_appservice_bob").await?;
|
||||
appservice
|
||||
.receive_transaction(push_events::v1::IncomingRequest::new("dontcare".into(), json))
|
||||
.await?;
|
||||
let coolplace = room_id!("!coolplace:localhost");
|
||||
let boringplace = room_id!("!boringplace:localhost");
|
||||
assert!(
|
||||
alice.get_joined_room(coolplace).is_some(),
|
||||
"Alice's membership in coolplace should be join"
|
||||
);
|
||||
assert!(
|
||||
bob.get_invited_room(boringplace).is_some(),
|
||||
"Bob's membership in boringplace should be invite"
|
||||
);
|
||||
assert!(alice.get_room(boringplace).is_none(), "Alice should not know about boringplace");
|
||||
assert!(bob.get_room(coolplace).is_none(), "Bob should not know about coolplace");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod registration {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_registration() -> Result<()> {
|
||||
let registration: Registration = serde_yaml::from_str(®istration_string())?;
|
||||
let registration: AppServiceRegistration = registration.into();
|
||||
|
||||
assert_eq!(registration.id, "appservice");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registration_from_yaml_file() -> Result<()> {
|
||||
let registration = AppServiceRegistration::try_from_yaml_file("./tests/registration.yaml")?;
|
||||
|
||||
assert_eq!(registration.id, "appservice");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registration_from_yaml_str() -> Result<()> {
|
||||
let registration = AppServiceRegistration::try_from_yaml_str(registration_string())?;
|
||||
|
||||
assert_eq!(registration.id, "appservice");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -17,41 +17,36 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
e2e-encryption = ["matrix-sdk-crypto"]
|
||||
qrcode = ["matrix-sdk-crypto/qrcode"]
|
||||
e2e-encryption = ["dep:matrix-sdk-crypto"]
|
||||
qrcode = ["matrix-sdk-crypto?/qrcode"]
|
||||
experimental-timeline = []
|
||||
|
||||
# helpers for testing features build upon this
|
||||
testing = ["http"]
|
||||
testing = ["dep:http"]
|
||||
|
||||
[dependencies]
|
||||
async-stream = "0.3.3"
|
||||
async-trait = "0.1.53"
|
||||
chacha20poly1305 = { version = "0.9.0", optional = true }
|
||||
dashmap = "5.2.0"
|
||||
futures-channel = "0.3.21"
|
||||
futures-core = "0.3.21"
|
||||
futures-util = { version = "0.3.21", default-features = false }
|
||||
hmac = { version = "0.12.1", optional = true }
|
||||
http = { version = "0.2.6", optional = true }
|
||||
lru = "0.7.5"
|
||||
matrix-sdk-common = { version = "0.5.0", path = "../matrix-sdk-common" }
|
||||
matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto", optional = true }
|
||||
once_cell = "1.10.0"
|
||||
pbkdf2 = { version = "0.11.0", default-features = false, optional = true }
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
serde = { version = "1.0.136", features = ["rc"] }
|
||||
serde_json = "1.0.79"
|
||||
sha2 = { version = "0.10.2", optional = true }
|
||||
thiserror = "1.0.30"
|
||||
tracing = "0.1.34"
|
||||
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "js", "canonical-json"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "js", "canonical-json"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "canonical-json"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "canonical-json"] }
|
||||
|
||||
[dev-dependencies]
|
||||
futures = { version = "0.3.21", default-features = false, features = ["executor"] }
|
||||
|
||||
@@ -66,6 +66,7 @@ use crate::{
|
||||
store::{
|
||||
ambiguity_map::AmbiguityCache, Result as StoreResult, StateChanges, Store, StoreConfig,
|
||||
},
|
||||
StateStore,
|
||||
};
|
||||
|
||||
/// A no IO Client implementation.
|
||||
@@ -135,9 +136,20 @@ impl BaseClient {
|
||||
self.store.session()
|
||||
}
|
||||
|
||||
/// Get all the rooms this client knows about.
|
||||
pub fn get_rooms(&self) -> Vec<Room> {
|
||||
self.store.get_rooms()
|
||||
}
|
||||
|
||||
/// Get all the rooms this client knows about.
|
||||
pub fn get_stripped_rooms(&self) -> Vec<Room> {
|
||||
self.store.get_stripped_rooms()
|
||||
}
|
||||
|
||||
/// Get a reference to the store.
|
||||
pub fn store(&self) -> &Store {
|
||||
&self.store
|
||||
#[allow(unknown_lints, clippy::explicit_auto_deref)]
|
||||
pub fn store(&self) -> &dyn StateStore {
|
||||
&*self.store
|
||||
}
|
||||
|
||||
/// Is the client logged in.
|
||||
@@ -1105,7 +1117,10 @@ impl Default for BaseClient {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use matrix_sdk_test::{async_test, response_from_file, EventBuilder};
|
||||
use matrix_sdk_test::{
|
||||
async_test, response_from_file, EventBuilder, InvitedRoomBuilder, LeftRoomBuilder,
|
||||
StrippedStateTestEvent, TimelineTestEvent,
|
||||
};
|
||||
use ruma::{
|
||||
api::{client as api, IncomingResponse},
|
||||
room_id, user_id,
|
||||
@@ -1133,9 +1148,8 @@ mod tests {
|
||||
let mut ev_builder = EventBuilder::new();
|
||||
|
||||
let response = ev_builder
|
||||
.add_custom_left_event(
|
||||
room_id,
|
||||
json!({
|
||||
.add_left_room(LeftRoomBuilder::new(room_id).add_timeline_event(
|
||||
TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"displayname": "Alice",
|
||||
"membership": "left",
|
||||
@@ -1145,16 +1159,15 @@ mod tests {
|
||||
"sender": user_id,
|
||||
"state_key": user_id,
|
||||
"type": "m.room.member",
|
||||
}),
|
||||
)
|
||||
})),
|
||||
))
|
||||
.build_sync_response();
|
||||
client.receive_sync_response(response).await.unwrap();
|
||||
assert_eq!(client.get_room(room_id).unwrap().room_type(), RoomType::Left);
|
||||
|
||||
let response = ev_builder
|
||||
.add_custom_invited_event(
|
||||
room_id,
|
||||
json!({
|
||||
.add_invited_room(InvitedRoomBuilder::new(room_id).add_state_event(
|
||||
StrippedStateTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"displayname": "Alice",
|
||||
"membership": "invite",
|
||||
@@ -1164,8 +1177,8 @@ mod tests {
|
||||
"sender": "@example:example.org",
|
||||
"state_key": user_id,
|
||||
"type": "m.room.member",
|
||||
}),
|
||||
)
|
||||
})),
|
||||
))
|
||||
.build_sync_response();
|
||||
client.receive_sync_response(response).await.unwrap();
|
||||
assert_eq!(client.get_room(room_id).unwrap().room_type(), RoomType::Invited);
|
||||
|
||||
@@ -43,7 +43,7 @@ pub use http;
|
||||
pub use matrix_sdk_crypto as crypto;
|
||||
pub use once_cell;
|
||||
pub use rooms::{DisplayName, Room, RoomInfo, RoomMember, RoomType};
|
||||
pub use store::{StateChanges, StateStore, Store, StoreError};
|
||||
pub use store::{StateChanges, StateStore, StoreError};
|
||||
pub use utils::{
|
||||
MinimalRoomMemberEvent, MinimalStateEvent, OriginalMinimalStateEvent, RedactedMinimalStateEvent,
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ use matrix_sdk_common::locks::Mutex;
|
||||
use ruma::{
|
||||
api::client::sync::sync_events::v3::RoomSummary as RumaSummary,
|
||||
events::{
|
||||
receipt::Receipt,
|
||||
receipt::{Receipt, ReceiptType},
|
||||
room::{
|
||||
create::RoomCreateEventContent, encryption::RoomEncryptionEventContent,
|
||||
guest_access::GuestAccess, history_visibility::HistoryVisibility, join_rules::JoinRule,
|
||||
@@ -39,7 +39,6 @@ use ruma::{
|
||||
AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent,
|
||||
RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
receipt::ReceiptType,
|
||||
room::RoomType as CreateRoomType,
|
||||
EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedUserId, RoomAliasId, RoomId,
|
||||
RoomVersionId, UserId,
|
||||
@@ -104,16 +103,7 @@ impl Room {
|
||||
room_id: &RoomId,
|
||||
room_type: RoomType,
|
||||
) -> Self {
|
||||
let room_info = RoomInfo {
|
||||
room_id: room_id.into(),
|
||||
room_type,
|
||||
notification_counts: Default::default(),
|
||||
summary: Default::default(),
|
||||
members_synced: false,
|
||||
last_prev_batch: None,
|
||||
base_info: BaseRoomInfo::new(),
|
||||
};
|
||||
|
||||
let room_info = RoomInfo::new(room_id, room_type);
|
||||
Self::restore(own_user_id, store, room_info)
|
||||
}
|
||||
|
||||
@@ -656,6 +646,19 @@ pub struct RoomInfo {
|
||||
}
|
||||
|
||||
impl RoomInfo {
|
||||
#[doc(hidden)] // used by store tests, otherwise it would be pub(crate)
|
||||
pub fn new(room_id: &RoomId, room_type: RoomType) -> Self {
|
||||
Self {
|
||||
room_id: room_id.into(),
|
||||
room_type,
|
||||
notification_counts: Default::default(),
|
||||
summary: Default::default(),
|
||||
members_synced: false,
|
||||
last_prev_batch: None,
|
||||
base_info: BaseRoomInfo::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark this Room as joined
|
||||
pub fn mark_as_joined(&mut self) {
|
||||
self.room_type = RoomType::Joined;
|
||||
|
||||
@@ -52,9 +52,10 @@ macro_rules! statestore_integration_tests {
|
||||
use matrix_sdk_test::{async_test, test_json};
|
||||
use ruma::{
|
||||
api::client::media::get_content_thumbnail::v3::Method,
|
||||
device_id, event_id,
|
||||
event_id,
|
||||
events::{
|
||||
presence::PresenceEvent,
|
||||
receipt::ReceiptType,
|
||||
room::{
|
||||
member::{
|
||||
MembershipState, OriginalSyncRoomMemberEvent, SyncRoomMemberEvent,
|
||||
@@ -69,7 +70,6 @@ macro_rules! statestore_integration_tests {
|
||||
StateEventType, StateUnsigned,
|
||||
},
|
||||
mxc_uri,
|
||||
receipt::ReceiptType,
|
||||
room_id,
|
||||
serde::Raw,
|
||||
uint, user_id, MilliSecondsSinceUnixEpoch, UserId, EventId, OwnedEventId,
|
||||
@@ -83,14 +83,13 @@ macro_rules! statestore_integration_tests {
|
||||
deserialized_responses::{ RoomEvent, SyncRoomEvent, TimelineSlice},
|
||||
};
|
||||
use $crate::{
|
||||
RoomType, Session,
|
||||
media::{MediaFormat, MediaRequest, MediaThumbnailSize},
|
||||
store::{
|
||||
Store,
|
||||
StateStore,
|
||||
Result as StoreResult,
|
||||
StateChanges
|
||||
}
|
||||
},
|
||||
RoomInfo, RoomType,
|
||||
};
|
||||
|
||||
use super::get_store;
|
||||
@@ -115,22 +114,13 @@ macro_rules! statestore_integration_tests {
|
||||
}
|
||||
|
||||
/// Populate the given `StateStore`.
|
||||
pub(crate) async fn populated_store(inner: Arc<dyn StateStore>) -> StoreResult<Store> {
|
||||
pub async fn populate_store(store: Arc<dyn StateStore>) -> StoreResult<()> {
|
||||
let mut changes = StateChanges::default();
|
||||
let store = Store::new(inner);
|
||||
|
||||
let user_id = user_id();
|
||||
let invited_user_id = invited_user_id();
|
||||
let room_id = room_id();
|
||||
let stripped_room_id = stripped_room_id();
|
||||
let device_id = device_id!("device");
|
||||
|
||||
let session = Session {
|
||||
access_token: "token".to_owned(),
|
||||
user_id: user_id.to_owned(),
|
||||
device_id: device_id.to_owned(),
|
||||
};
|
||||
store.restore_session(session).await.unwrap();
|
||||
|
||||
changes.sync_token = Some("t392-516_47314_0_7_1_1_1_11444_1".to_owned());
|
||||
|
||||
@@ -147,7 +137,7 @@ macro_rules! statestore_integration_tests {
|
||||
let pushrules_event = pushrules_raw.deserialize().unwrap();
|
||||
changes.add_account_data(pushrules_event, pushrules_raw);
|
||||
|
||||
let mut room = store.get_or_create_room(room_id, RoomType::Joined).await.clone_info();
|
||||
let mut room = RoomInfo::new(room_id, RoomType::Joined);
|
||||
room.mark_as_left();
|
||||
|
||||
let tag_json: &JsonValue = &test_json::TAG;
|
||||
@@ -220,8 +210,7 @@ macro_rules! statestore_integration_tests {
|
||||
changes.members.insert(room_id.to_owned(), room_members);
|
||||
changes.add_room(room);
|
||||
|
||||
let mut stripped_room =
|
||||
store.get_or_create_stripped_room(stripped_room_id).await.clone_info();
|
||||
let mut stripped_room = RoomInfo::new(stripped_room_id, RoomType::Invited);
|
||||
|
||||
let stripped_name_json: &JsonValue = &test_json::NAME_STRIPPED;
|
||||
let stripped_name_raw =
|
||||
@@ -249,7 +238,7 @@ macro_rules! statestore_integration_tests {
|
||||
changes.add_stripped_member(stripped_room_id, stripped_member_event);
|
||||
|
||||
store.save_changes(&changes).await?;
|
||||
Ok(store)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn power_level_event() -> Raw<AnySyncStateEvent> {
|
||||
@@ -305,11 +294,12 @@ macro_rules! statestore_integration_tests {
|
||||
let user_id = user_id();
|
||||
let inner_store = get_store().await?;
|
||||
|
||||
let store = populated_store(Arc::new(inner_store)).await?;
|
||||
let store = Arc::new(inner_store);
|
||||
populate_store(store.clone()).await?;
|
||||
|
||||
assert!(store.get_sync_token().await?.is_some());
|
||||
assert!(store.get_presence_event(user_id).await?.is_some());
|
||||
assert_eq!(store.get_room_infos().await?.len(), 1, "Expected to find 1 room info ");
|
||||
assert_eq!(store.get_room_infos().await?.len(), 1, "Expected to find 1 room info");
|
||||
assert_eq!(store.get_stripped_room_infos().await?.len(), 1, "Expected to find 1 stripped room info");
|
||||
assert!(store.get_account_data_event(GlobalAccountDataEventType::PushRules).await?.is_some());
|
||||
|
||||
@@ -580,25 +570,24 @@ macro_rules! statestore_integration_tests {
|
||||
|
||||
#[async_test]
|
||||
async fn test_persist_invited_room() -> StoreResult<()> {
|
||||
let stripped_room_id = stripped_room_id();
|
||||
let inner_store = get_store().await?;
|
||||
let store = populated_store(Arc::new(inner_store)).await?;
|
||||
let store = Arc::new(inner_store);
|
||||
populate_store(store.clone()).await?;
|
||||
|
||||
assert_eq!(store.get_stripped_room_infos().await?.len(), 1);
|
||||
assert!(store.get_stripped_room(stripped_room_id).is_some());
|
||||
|
||||
// populate rooom
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_room_removal() -> StoreResult<()> {
|
||||
async fn test_room_removal() -> StoreResult<()> {
|
||||
let room_id = room_id();
|
||||
let user_id = user_id();
|
||||
let inner_store = get_store().await?;
|
||||
let stripped_room_id = stripped_room_id();
|
||||
|
||||
let store = populated_store(Arc::new(inner_store)).await?;
|
||||
let store = Arc::new(inner_store);
|
||||
populate_store(store.clone()).await?;
|
||||
|
||||
store.remove_room(room_id).await?;
|
||||
|
||||
@@ -641,7 +630,7 @@ macro_rules! statestore_integration_tests {
|
||||
async fn test_room_timeline() {
|
||||
let store = get_store().await.unwrap();
|
||||
let mut stored_events = Vec::new();
|
||||
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||
let room_id = *test_json::DEFAULT_SYNC_ROOM_ID;
|
||||
|
||||
// Before the first sync the timeline should be empty
|
||||
assert!(store.room_timeline(room_id).await.expect("failed to read timeline").is_none(), "TL wasn't empty");
|
||||
@@ -674,7 +663,7 @@ macro_rules! statestore_integration_tests {
|
||||
// Add message response
|
||||
let messages = MessageResponse::try_from_http_response(
|
||||
Response::builder()
|
||||
.body(serde_json::to_vec(&*test_json::SYNC_ROOM_MESSAGES_BATCH_1).expect("Parsing SYNC_ROOM_MESSAGES_BATCH_1 failed"))
|
||||
.body(serde_json::to_vec(&*test_json::ROOM_MESSAGES_BATCH_1).expect("Parsing ROOM_MESSAGES_BATCH_1 failed"))
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -699,7 +688,7 @@ macro_rules! statestore_integration_tests {
|
||||
// Add second message response
|
||||
let messages = MessageResponse::try_from_http_response(
|
||||
Response::builder()
|
||||
.body(serde_json::to_vec(&*test_json::SYNC_ROOM_MESSAGES_BATCH_2).expect("Parsing SYNC_ROOM_MESSAGES_BATCH_2 failed"))
|
||||
.body(serde_json::to_vec(&*test_json::ROOM_MESSAGES_BATCH_2).expect("Parsing ROOM_MESSAGES_BATCH_2 failed"))
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -35,12 +35,11 @@ use ruma::{
|
||||
use ruma::{
|
||||
events::{
|
||||
presence::PresenceEvent,
|
||||
receipt::Receipt,
|
||||
receipt::{Receipt, ReceiptType},
|
||||
room::member::{MembershipState, StrippedRoomMemberEvent, SyncRoomMemberEvent},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
|
||||
AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
receipt::ReceiptType,
|
||||
serde::Raw,
|
||||
EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
|
||||
@@ -43,12 +43,11 @@ use ruma::{
|
||||
api::client::push::get_notifications::v3::Notification,
|
||||
events::{
|
||||
presence::PresenceEvent,
|
||||
receipt::{Receipt, ReceiptEventContent},
|
||||
receipt::{Receipt, ReceiptEventContent, ReceiptType},
|
||||
room::member::{StrippedRoomMemberEvent, SyncRoomMemberEvent},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
|
||||
AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
receipt::ReceiptType,
|
||||
serde::Raw,
|
||||
EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
@@ -408,7 +407,7 @@ where
|
||||
/// This adds additional higher level store functionality on top of a
|
||||
/// `StateStore` implementation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Store {
|
||||
pub(crate) struct Store {
|
||||
pub(super) inner: Arc<dyn StateStore>,
|
||||
session: Arc<OnceCell<Session>>,
|
||||
/// The current sync token that should be used for the next sync call.
|
||||
|
||||
@@ -16,7 +16,7 @@ default-target = "x86_64-unknown-linux-gnu"
|
||||
targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
|
||||
|
||||
[dependencies]
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c"] }
|
||||
serde = "1.0.136"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
|
||||
@@ -17,12 +17,12 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
qrcode = ["matrix-sdk-qrcode"]
|
||||
backups_v1 = ["olm-rs", "bs58"]
|
||||
qrcode = ["dep:matrix-sdk-qrcode"]
|
||||
backups_v1 = ["dep:olm-rs", "dep:bs58"]
|
||||
docsrs = []
|
||||
|
||||
# Testing helpers for implementations based upon this
|
||||
testing = ["http"]
|
||||
testing = ["dep:http"]
|
||||
|
||||
[dependencies]
|
||||
aes = "0.8.1"
|
||||
@@ -51,11 +51,11 @@ zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tokio = { version = "1.18", default-features = false, features = ["time"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "js", "rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "js", "rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd", features = ["js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1098,12 +1098,10 @@ impl RequestState<Ready> {
|
||||
// before the other side tried to do the same; ignore it if we did and
|
||||
// we're the lexicographically smaller user ID (or device ID if equal).
|
||||
use std::cmp::Ordering;
|
||||
match (sender.cmp(own_user_id), device.device_id().cmp(own_device_id)) {
|
||||
(Ordering::Greater, _) | (Ordering::Equal, Ordering::Greater) => {
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
!matches!(
|
||||
(sender.cmp(own_user_id), device.device_id().cmp(own_device_id)),
|
||||
(Ordering::Greater, _) | (Ordering::Equal, Ordering::Greater)
|
||||
)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["e2e-encryption"]
|
||||
e2e-encryption = ["matrix-sdk-base/e2e-encryption", "matrix-sdk-crypto", "dashmap"]
|
||||
e2e-encryption = ["matrix-sdk-base/e2e-encryption", "dep:matrix-sdk-crypto", "dashmap"]
|
||||
experimental-timeline = ["matrix-sdk-base/experimental-timeline"]
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ indexed_db_futures = "0.2.3"
|
||||
matrix-sdk-base = { version = "0.5.0", path = "../matrix-sdk-base" }
|
||||
matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto", optional = true }
|
||||
matrix-sdk-store-encryption = { version = "0.1.0", path = "../matrix-sdk-store-encryption" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b" }
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
thiserror = "1.0.30"
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
use base64::{encode_config as base64_encode, STANDARD_NO_PAD};
|
||||
use matrix_sdk_store_encryption::StoreCipher;
|
||||
use ruma::{
|
||||
events::{GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType},
|
||||
receipt::ReceiptType,
|
||||
events::{
|
||||
receipt::ReceiptType, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
DeviceId, EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, TransactionId,
|
||||
UserId,
|
||||
};
|
||||
|
||||
@@ -39,12 +39,11 @@ use matrix_sdk_store_encryption::{Error as EncryptionError, StoreCipher};
|
||||
use ruma::{
|
||||
events::{
|
||||
presence::PresenceEvent,
|
||||
receipt::Receipt,
|
||||
receipt::{Receipt, ReceiptType},
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent,
|
||||
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
receipt::ReceiptType,
|
||||
serde::Raw,
|
||||
EventId, MxcUri, OwnedEventId, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["decode_image"]
|
||||
decode_image = ["image", "rqrr", "qrcode/image", "qrcode/svg"]
|
||||
decode_image = ["dep:image", "dep:rqrr", "qrcode/image", "qrcode/svg"]
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.13.0"
|
||||
@@ -25,7 +25,7 @@ byteorder = "1.4.3"
|
||||
image = { version = "0.23.0", optional = true }
|
||||
qrcode = { version = "0.12.0", default-features = false }
|
||||
rqrr = { version = "0.4.0", optional = true }
|
||||
ruma-common = { git = "https://github.com/ruma/ruma", rev = "96155915f" }
|
||||
ruma-common = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b" }
|
||||
thiserror = "1.0.30"
|
||||
|
||||
[dependencies.vodozemac]
|
||||
|
||||
@@ -16,12 +16,16 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
[features]
|
||||
default = ["state-store"]
|
||||
|
||||
state-store = ["matrix-sdk-base"]
|
||||
state-store = ["dep:matrix-sdk-base"]
|
||||
crypto-store = [
|
||||
"matrix-sdk-crypto",
|
||||
"matrix-sdk-base/e2e-encryption",
|
||||
"dep:matrix-sdk-base",
|
||||
"dep:matrix-sdk-crypto",
|
||||
"matrix-sdk-base?/e2e-encryption",
|
||||
]
|
||||
experimental-timeline = [
|
||||
"dep:matrix-sdk-base",
|
||||
"matrix-sdk-base?/experimental-timeline",
|
||||
]
|
||||
experimental-timeline = ["matrix-sdk-base/experimental-timeline"]
|
||||
|
||||
[dependencies]
|
||||
async-stream = "0.3.3"
|
||||
@@ -34,7 +38,7 @@ matrix-sdk-base = { version = "0.5.0", path = "../matrix-sdk-base", optional = t
|
||||
matrix-sdk-common = { version = "0.5.0", path = "../matrix-sdk-common" }
|
||||
matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto", optional = true }
|
||||
matrix-sdk-store-encryption = { version = "0.1.0", path = "../matrix-sdk-store-encryption" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b" }
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
sled = "0.34.7"
|
||||
|
||||
@@ -3,10 +3,9 @@ use std::{borrow::Cow, ops::Deref};
|
||||
use matrix_sdk_store_encryption::StoreCipher;
|
||||
use ruma::{
|
||||
events::{
|
||||
secret::request::SecretName, GlobalAccountDataEventType, RoomAccountDataEventType,
|
||||
StateEventType,
|
||||
receipt::ReceiptType, secret::request::SecretName, GlobalAccountDataEventType,
|
||||
RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
receipt::ReceiptType,
|
||||
DeviceId, EventEncryptionAlgorithm, EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId,
|
||||
RoomId, TransactionId, UserId,
|
||||
};
|
||||
|
||||
@@ -43,12 +43,11 @@ use ruma::{
|
||||
use ruma::{
|
||||
events::{
|
||||
presence::PresenceEvent,
|
||||
receipt::Receipt,
|
||||
receipt::{Receipt, ReceiptType},
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent,
|
||||
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
receipt::ReceiptType,
|
||||
serde::Raw,
|
||||
EventId, IdParseError, MxcUri, OwnedEventId, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ rust-version = "1.60"
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
js = ["getrandom/js"]
|
||||
js = ["dep:getrandom", "getrandom?/js"]
|
||||
|
||||
[dependencies]
|
||||
blake3 = "1.3.1"
|
||||
|
||||
@@ -18,6 +18,6 @@ appservice = []
|
||||
http = "0.2.6"
|
||||
matrix-sdk-test-macros = { version = "0.2.0", path = "../matrix-sdk-test-macros" }
|
||||
once_cell = "1.10.0"
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c"] }
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use ruma::{events::AnyRoomEvent, room_id, serde::Raw};
|
||||
use ruma::{events::AnyRoomEvent, serde::Raw};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{test_json, EventsJson};
|
||||
use crate::{event_builder::TimelineTestEvent, test_json};
|
||||
|
||||
/// Clones the given [`Value`] and adds a `room_id` to it
|
||||
///
|
||||
/// Adding the `room_id` conditionally with `cfg` directly to the lazy_static
|
||||
/// test_json values is blocked by "experimental attributes on expressions, see
|
||||
/// issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information"
|
||||
pub fn value_with_room_id(value: &Value) -> Value {
|
||||
let mut val = value.clone();
|
||||
let room_id =
|
||||
Value::try_from(room_id!("!SVkFJHzfwvuaIEawgC:localhost").to_string()).expect("room_id");
|
||||
val.as_object_mut().expect("mutable test_json").insert("room_id".to_owned(), room_id);
|
||||
|
||||
val
|
||||
pub fn value_with_room_id(value: &mut Value) {
|
||||
let room_id = Value::try_from(test_json::DEFAULT_SYNC_ROOM_ID.to_string()).expect("room_id");
|
||||
value.as_object_mut().expect("mutable test_json").insert("room_id".to_owned(), room_id);
|
||||
}
|
||||
|
||||
/// The `TransactionBuilder` struct can be used to easily generate valid
|
||||
@@ -34,15 +30,9 @@ impl TransactionBuilder {
|
||||
}
|
||||
|
||||
/// Add a room event.
|
||||
pub fn add_room_event(&mut self, json: EventsJson) -> &mut Self {
|
||||
let val: &Value = match json {
|
||||
EventsJson::Member => &test_json::MEMBER,
|
||||
EventsJson::MemberNameChange => &test_json::MEMBER_NAME_CHANGE,
|
||||
EventsJson::PowerLevels => &test_json::POWER_LEVELS,
|
||||
_ => panic!("unknown event json {:?}", json),
|
||||
};
|
||||
|
||||
let val = value_with_room_id(val);
|
||||
pub fn add_room_event(&mut self, event: TimelineTestEvent) -> &mut Self {
|
||||
let mut val = event.into_json_value();
|
||||
value_with_room_id(&mut val);
|
||||
|
||||
let event = serde_json::from_value(val).unwrap();
|
||||
|
||||
|
||||
43
crates/matrix-sdk-test/src/event_builder/bulk.rs
Normal file
43
crates/matrix-sdk-test/src/event_builder/bulk.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use ruma::{
|
||||
events::{room::member::MembershipState, AnySyncStateEvent},
|
||||
serde::Raw,
|
||||
};
|
||||
use serde_json::{from_value as from_json_value, json};
|
||||
|
||||
/// Create `m.room.member` events in the given range.
|
||||
///
|
||||
/// The user IDs are generated as `@user_{idx}:{server}`, with `idx` being the
|
||||
/// current value in `range`, so providing the same range in several method
|
||||
/// calls will create events that replace the previous state.
|
||||
///
|
||||
/// The event IDs are generated as `$roommember_{batch}_{idx}` so it's important
|
||||
/// to increment `batch` between method calls to avoid having two events with
|
||||
/// the same event ID.
|
||||
///
|
||||
/// This method can be used as input for room builders with
|
||||
/// `add_timeline_state_bulk()` or `add_state_bulk()`.
|
||||
pub fn bulk_room_members<'a>(
|
||||
batch: usize,
|
||||
range: Range<usize>,
|
||||
server: &'a str,
|
||||
membership: &'a MembershipState,
|
||||
) -> impl Iterator<Item = Raw<AnySyncStateEvent>> + 'a {
|
||||
range.map(move |idx| {
|
||||
let user_id = format!("@user_{idx}:{server}");
|
||||
let event_id = format!("$roommember_{batch}_{idx}");
|
||||
let ts = 151800000 + batch * 100 + idx;
|
||||
from_json_value(json!({
|
||||
"content": {
|
||||
"membership": membership,
|
||||
},
|
||||
"event_id": event_id,
|
||||
"origin_server_ts": ts,
|
||||
"sender": user_id,
|
||||
"state_key": user_id,
|
||||
"type": "m.room.member",
|
||||
}))
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
43
crates/matrix-sdk-test/src/event_builder/invited_room.rs
Normal file
43
crates/matrix-sdk-test/src/event_builder/invited_room.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use ruma::{
|
||||
api::client::sync::sync_events::v3::InvitedRoom, events::AnyStrippedStateEvent, serde::Raw,
|
||||
OwnedRoomId,
|
||||
};
|
||||
|
||||
use super::StrippedStateTestEvent;
|
||||
use crate::test_json;
|
||||
|
||||
pub struct InvitedRoomBuilder {
|
||||
pub(super) room_id: OwnedRoomId,
|
||||
pub(super) inner: InvitedRoom,
|
||||
}
|
||||
|
||||
impl InvitedRoomBuilder {
|
||||
/// Create a new `InvitedRoomBuilder` for the given room ID.
|
||||
///
|
||||
/// If the room ID is [`test_json::DEFAULT_SYNC_ROOM_ID`],
|
||||
/// [`InvitedRoomBuilder::default()`] can be used instead.
|
||||
pub fn new(room_id: impl Into<OwnedRoomId>) -> Self {
|
||||
Self { room_id: room_id.into(), inner: Default::default() }
|
||||
}
|
||||
|
||||
/// Add an event to the state.
|
||||
pub fn add_state_event(mut self, event: StrippedStateTestEvent) -> Self {
|
||||
self.inner.invite_state.events.push(event.into_raw_event());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add events to the state in bulk.
|
||||
pub fn add_state_bulk<I>(mut self, events: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Raw<AnyStrippedStateEvent>>,
|
||||
{
|
||||
self.inner.invite_state.events.extend(events);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for InvitedRoomBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new(test_json::DEFAULT_SYNC_ROOM_ID.to_owned())
|
||||
}
|
||||
}
|
||||
129
crates/matrix-sdk-test/src/event_builder/joined_room.rs
Normal file
129
crates/matrix-sdk-test/src/event_builder/joined_room.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use ruma::{
|
||||
api::client::sync::sync_events::v3::JoinedRoom,
|
||||
events::{
|
||||
AnyRoomAccountDataEvent, AnySyncEphemeralRoomEvent, AnySyncRoomEvent, AnySyncStateEvent,
|
||||
},
|
||||
serde::Raw,
|
||||
OwnedRoomId,
|
||||
};
|
||||
use serde_json::{from_value as from_json_value, Value as JsonValue};
|
||||
|
||||
use super::{EphemeralTestEvent, RoomAccountDataTestEvent, StateTestEvent, TimelineTestEvent};
|
||||
use crate::test_json;
|
||||
|
||||
pub struct JoinedRoomBuilder {
|
||||
pub(super) room_id: OwnedRoomId,
|
||||
pub(super) inner: JoinedRoom,
|
||||
}
|
||||
|
||||
impl JoinedRoomBuilder {
|
||||
/// Create a new `JoinedRoomBuilder` for the given room ID.
|
||||
///
|
||||
/// If the room ID is [`test_json::DEFAULT_SYNC_ROOM_ID`],
|
||||
/// [`JoinedRoomBuilder::default()`] can be used instead.
|
||||
pub fn new(room_id: impl Into<OwnedRoomId>) -> Self {
|
||||
Self { room_id: room_id.into(), inner: Default::default() }
|
||||
}
|
||||
|
||||
/// Add an event to the timeline.
|
||||
pub fn add_timeline_event(mut self, event: TimelineTestEvent) -> Self {
|
||||
self.inner.timeline.events.push(event.into_raw_event());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add events in bulk to the timeline.
|
||||
pub fn add_timeline_bulk<I>(mut self, events: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Raw<AnySyncRoomEvent>>,
|
||||
{
|
||||
self.inner.timeline.events.extend(events);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add state events in bulk to the timeline.
|
||||
///
|
||||
/// This is a convenience method that casts `Raw<AnySyncStateEvent>` to
|
||||
/// `Raw<AnySyncRoomEvent>` and calls `JoinedRoom::add_timeline_bulk()`.
|
||||
pub fn add_timeline_state_bulk<I>(self, events: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Raw<AnySyncStateEvent>>,
|
||||
{
|
||||
let events = events.into_iter().map(|event| event.cast());
|
||||
self.add_timeline_bulk(events)
|
||||
}
|
||||
|
||||
/// Set the timeline as limited.
|
||||
pub fn set_timeline_limited(mut self) -> Self {
|
||||
self.inner.timeline.limited = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `prev_batch` of the timeline.
|
||||
pub fn set_timeline_prev_batch(mut self, prev_batch: String) -> Self {
|
||||
self.inner.timeline.prev_batch = Some(prev_batch);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an event to the state.
|
||||
pub fn add_state_event(mut self, event: StateTestEvent) -> Self {
|
||||
self.inner.state.events.push(event.into_raw_event());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add events in bulk to the state.
|
||||
pub fn add_state_bulk<I>(mut self, events: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Raw<AnySyncStateEvent>>,
|
||||
{
|
||||
self.inner.state.events.extend(events);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an ephemeral event.
|
||||
pub fn add_ephemeral_event(mut self, event: EphemeralTestEvent) -> Self {
|
||||
self.inner.ephemeral.events.push(event.into_raw_event());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add ephemeral events in bulk.
|
||||
pub fn add_ephemeral_bulk<I>(mut self, events: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Raw<AnySyncEphemeralRoomEvent>>,
|
||||
{
|
||||
self.inner.ephemeral.events.extend(events);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add room account data.
|
||||
pub fn add_account_data(mut self, event: RoomAccountDataTestEvent) -> Self {
|
||||
self.inner.account_data.events.push(event.into_raw_event());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add room account data in bulk.
|
||||
pub fn add_account_data_bulk<I>(mut self, events: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Raw<AnyRoomAccountDataEvent>>,
|
||||
{
|
||||
self.inner.account_data.events.extend(events);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the room summary.
|
||||
pub fn set_room_summary(mut self, summary: JsonValue) -> Self {
|
||||
self.inner.summary = from_json_value(summary).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the unread notifications count.
|
||||
pub fn set_unread_notifications_count(mut self, unread_notifications: JsonValue) -> Self {
|
||||
self.inner.unread_notifications = from_json_value(unread_notifications).unwrap();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for JoinedRoomBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new(test_json::DEFAULT_SYNC_ROOM_ID.to_owned())
|
||||
}
|
||||
}
|
||||
99
crates/matrix-sdk-test/src/event_builder/left_room.rs
Normal file
99
crates/matrix-sdk-test/src/event_builder/left_room.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use ruma::{
|
||||
api::client::sync::sync_events::v3::LeftRoom,
|
||||
events::{AnyRoomAccountDataEvent, AnySyncRoomEvent, AnySyncStateEvent},
|
||||
serde::Raw,
|
||||
OwnedRoomId,
|
||||
};
|
||||
|
||||
use super::{RoomAccountDataTestEvent, StateTestEvent, TimelineTestEvent};
|
||||
use crate::test_json;
|
||||
|
||||
pub struct LeftRoomBuilder {
|
||||
pub(super) room_id: OwnedRoomId,
|
||||
pub(super) inner: LeftRoom,
|
||||
}
|
||||
|
||||
impl LeftRoomBuilder {
|
||||
/// Create a new `LeftRoomBuilder` for the given room ID.
|
||||
///
|
||||
/// If the room ID is [`test_json::DEFAULT_SYNC_ROOM_ID`],
|
||||
/// [`LeftRoomBuilder::default()`] can be used instead.
|
||||
pub fn new(room_id: impl Into<OwnedRoomId>) -> Self {
|
||||
Self { room_id: room_id.into(), inner: Default::default() }
|
||||
}
|
||||
|
||||
/// Add an event to the timeline.
|
||||
pub fn add_timeline_event(mut self, event: TimelineTestEvent) -> Self {
|
||||
self.inner.timeline.events.push(event.into_raw_event());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add events in bulk to the timeline.
|
||||
pub fn add_timeline_bulk<I>(mut self, events: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Raw<AnySyncRoomEvent>>,
|
||||
{
|
||||
self.inner.timeline.events.extend(events);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add state events in bulk to the timeline.
|
||||
///
|
||||
/// This is a convenience method that casts `Raw<AnySyncStateEvent>` to
|
||||
/// `Raw<AnySyncRoomEvent>` and calls `LeftRoom::add_timeline_bulk()`.
|
||||
pub fn add_timeline_state_bulk<I>(self, events: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Raw<AnySyncStateEvent>>,
|
||||
{
|
||||
let events = events.into_iter().map(|event| event.cast());
|
||||
self.add_timeline_bulk(events)
|
||||
}
|
||||
|
||||
/// Set the timeline as limited.
|
||||
pub fn set_timeline_limited(mut self) -> Self {
|
||||
self.inner.timeline.limited = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `prev_batch` of the timeline.
|
||||
pub fn set_timeline_prev_batch(mut self, prev_batch: String) -> Self {
|
||||
self.inner.timeline.prev_batch = Some(prev_batch);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an event to the state.
|
||||
pub fn add_state_event(mut self, event: StateTestEvent) -> Self {
|
||||
self.inner.state.events.push(event.into_raw_event());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add events in bulk to the state.
|
||||
pub fn add_state_bulk<I>(mut self, events: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Raw<AnySyncStateEvent>>,
|
||||
{
|
||||
self.inner.state.events.extend(events);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add room account data.
|
||||
pub fn add_account_data(mut self, event: RoomAccountDataTestEvent) -> Self {
|
||||
self.inner.account_data.events.push(event.into_raw_event());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add room account data in bulk.
|
||||
pub fn add_account_data_bulk<I>(mut self, events: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Raw<AnyRoomAccountDataEvent>>,
|
||||
{
|
||||
self.inner.account_data.events.extend(events);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LeftRoomBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new(test_json::DEFAULT_SYNC_ROOM_ID.to_owned())
|
||||
}
|
||||
}
|
||||
248
crates/matrix-sdk-test/src/event_builder/mod.rs
Normal file
248
crates/matrix-sdk-test/src/event_builder/mod.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use http::Response;
|
||||
use ruma::{
|
||||
api::{
|
||||
client::sync::sync_events::v3::{
|
||||
InvitedRoom, JoinedRoom, LeftRoom, Response as SyncResponse,
|
||||
},
|
||||
IncomingResponse,
|
||||
},
|
||||
events::{presence::PresenceEvent, AnyGlobalAccountDataEvent},
|
||||
serde::Raw,
|
||||
OwnedRoomId,
|
||||
};
|
||||
use serde_json::{from_value as from_json_value, json, Value as JsonValue};
|
||||
|
||||
use super::test_json;
|
||||
|
||||
mod bulk;
|
||||
mod invited_room;
|
||||
mod joined_room;
|
||||
mod left_room;
|
||||
mod test_event;
|
||||
|
||||
pub use bulk::bulk_room_members;
|
||||
pub use invited_room::InvitedRoomBuilder;
|
||||
pub use joined_room::JoinedRoomBuilder;
|
||||
pub use left_room::LeftRoomBuilder;
|
||||
pub use test_event::{
|
||||
EphemeralTestEvent, GlobalAccountDataTestEvent, PresenceTestEvent, RoomAccountDataTestEvent,
|
||||
StateTestEvent, StrippedStateTestEvent, TimelineTestEvent,
|
||||
};
|
||||
|
||||
/// The `EventBuilder` struct can be used to easily generate valid sync
|
||||
/// responses for testing. These can be then fed into either `Client` or `Room`.
|
||||
///
|
||||
/// It supports generated a number of canned events, such as a member entering a
|
||||
/// room, his power level and display name changing and similar. It also
|
||||
/// supports insertion of custom events in the form of `EventsJson` values.
|
||||
///
|
||||
/// **Important** You *must* use the *same* builder when sending multiple sync
|
||||
/// responses to a single client. Otherwise, the subsequent responses will be
|
||||
/// *ignored* by the client because the `next_batch` sync token will not be
|
||||
/// rotated properly.
|
||||
///
|
||||
/// # Example usage
|
||||
///
|
||||
/// ```rust
|
||||
/// use matrix_sdk_test::{EventBuilder, JoinedRoomBuilder, TimelineTestEvent};
|
||||
///
|
||||
/// let mut builder = EventBuilder::new();
|
||||
///
|
||||
/// // response1 now contains events that add an example member to the room and change their power
|
||||
/// // level
|
||||
/// let response1 = builder
|
||||
/// .add_joined_room(
|
||||
/// JoinedRoomBuilder::default()
|
||||
/// .add_timeline_event(TimelineTestEvent::Member)
|
||||
/// .add_timeline_event(TimelineTestEvent::PowerLevels)
|
||||
/// )
|
||||
/// .build_sync_response();
|
||||
///
|
||||
/// // response2 is now empty (nothing changed)
|
||||
/// let response2 = builder.build_sync_response();
|
||||
///
|
||||
/// // response3 contains a display name change for member example
|
||||
/// let response3 = builder
|
||||
/// .add_joined_room(
|
||||
/// JoinedRoomBuilder::default()
|
||||
/// .add_timeline_event(TimelineTestEvent::MemberNameChange)
|
||||
/// .add_timeline_event(TimelineTestEvent::PowerLevels)
|
||||
/// )
|
||||
/// .build_sync_response();
|
||||
/// ```
|
||||
#[derive(Default)]
|
||||
pub struct EventBuilder {
|
||||
/// Updates to joined `Room`s.
|
||||
joined_rooms: HashMap<OwnedRoomId, JoinedRoom>,
|
||||
/// Updates to invited `Room`s.
|
||||
invited_rooms: HashMap<OwnedRoomId, InvitedRoom>,
|
||||
/// Updates to left `Room`s.
|
||||
left_rooms: HashMap<OwnedRoomId, LeftRoom>,
|
||||
/// Events that determine the presence state of a user.
|
||||
presence: Vec<Raw<PresenceEvent>>,
|
||||
/// Global account data events.
|
||||
account_data: Vec<Raw<AnyGlobalAccountDataEvent>>,
|
||||
/// Internal counter to enable the `prev_batch` and `next_batch` of each
|
||||
/// sync response to vary.
|
||||
batch_counter: i64,
|
||||
}
|
||||
|
||||
impl EventBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Add a joined room to the next sync response.
|
||||
///
|
||||
/// If a room with the same room ID already exists, it is replaced by this
|
||||
/// one.
|
||||
pub fn add_joined_room(&mut self, room: JoinedRoomBuilder) -> &mut Self {
|
||||
self.invited_rooms.remove(&room.room_id);
|
||||
self.left_rooms.remove(&room.room_id);
|
||||
self.joined_rooms.insert(room.room_id, room.inner);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an invited room to the next sync response.
|
||||
///
|
||||
/// If a room with the same room ID already exists, it is replaced by this
|
||||
/// one.
|
||||
pub fn add_invited_room(&mut self, room: InvitedRoomBuilder) -> &mut Self {
|
||||
self.joined_rooms.remove(&room.room_id);
|
||||
self.left_rooms.remove(&room.room_id);
|
||||
self.invited_rooms.insert(room.room_id, room.inner);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a left room to the next sync response.
|
||||
///
|
||||
/// If a room with the same room ID already exists, it is replaced by this
|
||||
/// one.
|
||||
pub fn add_left_room(&mut self, room: LeftRoomBuilder) -> &mut Self {
|
||||
self.joined_rooms.remove(&room.room_id);
|
||||
self.invited_rooms.remove(&room.room_id);
|
||||
self.left_rooms.insert(room.room_id, room.inner);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a presence event.
|
||||
pub fn add_presence_event(&mut self, event: PresenceTestEvent) -> &mut Self {
|
||||
let val = match event {
|
||||
PresenceTestEvent::Presence => test_json::PRESENCE.to_owned(),
|
||||
PresenceTestEvent::Custom(json) => json,
|
||||
};
|
||||
|
||||
self.presence.push(from_json_value(val).unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add presence in bulk.
|
||||
pub fn add_presence_bulk<I>(&mut self, events: I) -> &mut Self
|
||||
where
|
||||
I: IntoIterator<Item = Raw<PresenceEvent>>,
|
||||
{
|
||||
self.presence.extend(events);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add global account data.
|
||||
pub fn add_global_account_data_event(
|
||||
&mut self,
|
||||
event: GlobalAccountDataTestEvent,
|
||||
) -> &mut Self {
|
||||
let val = match event {
|
||||
GlobalAccountDataTestEvent::PushRules => test_json::PUSH_RULES.to_owned(),
|
||||
GlobalAccountDataTestEvent::Tags => test_json::TAG.to_owned(),
|
||||
GlobalAccountDataTestEvent::Custom(json) => json,
|
||||
};
|
||||
|
||||
self.account_data.push(from_json_value(val).unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add global account data in bulk.
|
||||
pub fn add_global_account_data_bulk<I>(&mut self, events: I) -> &mut Self
|
||||
where
|
||||
I: IntoIterator<Item = Raw<AnyGlobalAccountDataEvent>>,
|
||||
{
|
||||
self.account_data.extend(events);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds a sync response as a JSON Value containing the events we queued
|
||||
/// so far.
|
||||
///
|
||||
/// The next response returned by `build_sync_response` will then be empty
|
||||
/// if no further events were queued.
|
||||
///
|
||||
/// This method is raw JSON equivalent to
|
||||
/// [build_sync_response()](#method.build_sync_response), use
|
||||
/// [build_sync_response()](#method.build_sync_response) if you need a typed
|
||||
/// response.
|
||||
pub fn build_json_sync_response(&mut self) -> JsonValue {
|
||||
self.batch_counter += 1;
|
||||
let next_batch = self.generate_sync_token();
|
||||
|
||||
let body = json! {
|
||||
{
|
||||
"device_one_time_keys_count": {},
|
||||
"next_batch": next_batch,
|
||||
"device_lists": {
|
||||
"changed": [],
|
||||
"left": [],
|
||||
},
|
||||
"rooms": {
|
||||
"invite": self.invited_rooms,
|
||||
"join": self.joined_rooms,
|
||||
"leave": self.left_rooms,
|
||||
},
|
||||
"to_device": {
|
||||
"events": []
|
||||
},
|
||||
"presence": {
|
||||
"events": self.presence,
|
||||
},
|
||||
"account_data": {
|
||||
"events": self.account_data,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Clear state so that the next sync response will be empty if nothing
|
||||
// was added.
|
||||
self.clear();
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
/// Builds a `SyncResponse` containing the events we queued so far.
|
||||
///
|
||||
/// The next response returned by `build_sync_response` will then be empty
|
||||
/// if no further events were queued.
|
||||
///
|
||||
/// This method is high level and typed equivalent to
|
||||
/// [build_json_sync_response()](#method.build_json_sync_response), use
|
||||
/// [build_json_sync_response()](#method.build_json_sync_response) if you
|
||||
/// need an untyped response.
|
||||
pub fn build_sync_response(&mut self) -> SyncResponse {
|
||||
let body = self.build_json_sync_response();
|
||||
|
||||
let response = Response::builder().body(serde_json::to_vec(&body).unwrap()).unwrap();
|
||||
|
||||
SyncResponse::try_from_http_response(response).unwrap()
|
||||
}
|
||||
|
||||
fn generate_sync_token(&self) -> String {
|
||||
format!("t392-516_47314_0_7_1_1_1_11444_{}", self.batch_counter)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.account_data.clear();
|
||||
self.invited_rooms.clear();
|
||||
self.joined_rooms.clear();
|
||||
self.left_rooms.clear();
|
||||
self.presence.clear();
|
||||
}
|
||||
}
|
||||
235
crates/matrix-sdk-test/src/event_builder/test_event.rs
Normal file
235
crates/matrix-sdk-test/src/event_builder/test_event.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
use ruma::{
|
||||
events::{
|
||||
presence::PresenceEvent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
|
||||
AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncRoomEvent, AnySyncStateEvent,
|
||||
},
|
||||
serde::Raw,
|
||||
};
|
||||
use serde_json::{from_value as from_json_value, Value as JsonValue};
|
||||
|
||||
use crate::test_json;
|
||||
|
||||
/// Test events that can be added to the timeline.
|
||||
pub enum TimelineTestEvent {
|
||||
Alias,
|
||||
Aliases,
|
||||
Create,
|
||||
Encryption,
|
||||
HistoryVisibility,
|
||||
JoinRules,
|
||||
Member,
|
||||
MemberInvite,
|
||||
MemberNameChange,
|
||||
MessageEdit,
|
||||
MessageEmote,
|
||||
MessageNotice,
|
||||
MessageText,
|
||||
PowerLevels,
|
||||
Reaction,
|
||||
RedactedInvalid,
|
||||
RedactedMessage,
|
||||
RedactedState,
|
||||
Redaction,
|
||||
RoomAvatar,
|
||||
RoomName,
|
||||
RoomTopic,
|
||||
Custom(JsonValue),
|
||||
}
|
||||
|
||||
impl TimelineTestEvent {
|
||||
/// Get the JSON representation of this test event.
|
||||
pub fn into_json_value(self) -> JsonValue {
|
||||
match self {
|
||||
Self::Alias => test_json::sync_events::ALIAS.to_owned(),
|
||||
Self::Aliases => test_json::sync_events::ALIASES.to_owned(),
|
||||
Self::Create => test_json::sync_events::CREATE.to_owned(),
|
||||
Self::Encryption => test_json::sync_events::ENCRYPTION.to_owned(),
|
||||
Self::HistoryVisibility => test_json::sync_events::HISTORY_VISIBILITY.to_owned(),
|
||||
Self::JoinRules => test_json::sync_events::JOIN_RULES.to_owned(),
|
||||
Self::Member => test_json::sync_events::MEMBER.to_owned(),
|
||||
Self::MemberInvite => test_json::sync_events::MEMBER_INVITE.to_owned(),
|
||||
Self::MemberNameChange => test_json::sync_events::MEMBER_NAME_CHANGE.to_owned(),
|
||||
Self::MessageEdit => test_json::sync_events::MESSAGE_EDIT.to_owned(),
|
||||
Self::MessageEmote => test_json::sync_events::MESSAGE_EMOTE.to_owned(),
|
||||
Self::MessageNotice => test_json::sync_events::MESSAGE_NOTICE.to_owned(),
|
||||
Self::MessageText => test_json::sync_events::MESSAGE_TEXT.to_owned(),
|
||||
Self::PowerLevels => test_json::sync_events::POWER_LEVELS.to_owned(),
|
||||
Self::Reaction => test_json::sync_events::REACTION.to_owned(),
|
||||
Self::RedactedInvalid => test_json::sync_events::REDACTED_INVALID.to_owned(),
|
||||
Self::RedactedMessage => test_json::sync_events::REDACTED.to_owned(),
|
||||
Self::RedactedState => test_json::sync_events::REDACTED_STATE.to_owned(),
|
||||
Self::Redaction => test_json::sync_events::REDACTION.to_owned(),
|
||||
Self::RoomAvatar => test_json::sync_events::ROOM_AVATAR.to_owned(),
|
||||
Self::RoomName => test_json::sync_events::NAME.to_owned(),
|
||||
Self::RoomTopic => test_json::sync_events::TOPIC.to_owned(),
|
||||
Self::Custom(json) => json,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the typed JSON representation of this test event.
|
||||
pub fn into_raw_event(self) -> Raw<AnySyncRoomEvent> {
|
||||
from_json_value(self.into_json_value()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test events that can be added to the state.
|
||||
pub enum StateTestEvent {
|
||||
Alias,
|
||||
Aliases,
|
||||
Create,
|
||||
Encryption,
|
||||
HistoryVisibility,
|
||||
JoinRules,
|
||||
Member,
|
||||
MemberInvite,
|
||||
MemberNameChange,
|
||||
PowerLevels,
|
||||
RedactedInvalid,
|
||||
RedactedState,
|
||||
RoomAvatar,
|
||||
RoomName,
|
||||
RoomTopic,
|
||||
Custom(JsonValue),
|
||||
}
|
||||
|
||||
impl StateTestEvent {
|
||||
/// Get the JSON representation of this test event.
|
||||
pub fn into_json_value(self) -> JsonValue {
|
||||
match self {
|
||||
Self::Alias => test_json::sync_events::ALIAS.to_owned(),
|
||||
Self::Aliases => test_json::sync_events::ALIASES.to_owned(),
|
||||
Self::Create => test_json::sync_events::CREATE.to_owned(),
|
||||
Self::Encryption => test_json::sync_events::ENCRYPTION.to_owned(),
|
||||
Self::HistoryVisibility => test_json::sync_events::HISTORY_VISIBILITY.to_owned(),
|
||||
Self::JoinRules => test_json::sync_events::JOIN_RULES.to_owned(),
|
||||
Self::Member => test_json::sync_events::MEMBER.to_owned(),
|
||||
Self::MemberInvite => test_json::sync_events::MEMBER_INVITE.to_owned(),
|
||||
Self::MemberNameChange => test_json::sync_events::MEMBER_NAME_CHANGE.to_owned(),
|
||||
Self::PowerLevels => test_json::sync_events::POWER_LEVELS.to_owned(),
|
||||
Self::RedactedInvalid => test_json::sync_events::REDACTED_INVALID.to_owned(),
|
||||
Self::RedactedState => test_json::sync_events::REDACTED_STATE.to_owned(),
|
||||
Self::RoomAvatar => test_json::sync_events::ROOM_AVATAR.to_owned(),
|
||||
Self::RoomName => test_json::sync_events::NAME.to_owned(),
|
||||
Self::RoomTopic => test_json::sync_events::TOPIC.to_owned(),
|
||||
Self::Custom(json) => json,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the typed JSON representation of this test event.
|
||||
pub fn into_raw_event(self) -> Raw<AnySyncStateEvent> {
|
||||
from_json_value(self.into_json_value()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test events that can be added to the stripped state.
|
||||
pub enum StrippedStateTestEvent {
|
||||
Member,
|
||||
RoomName,
|
||||
Custom(JsonValue),
|
||||
}
|
||||
|
||||
impl StrippedStateTestEvent {
|
||||
/// Get the JSON representation of this test event.
|
||||
pub fn into_json_value(self) -> JsonValue {
|
||||
match self {
|
||||
Self::Member => test_json::sync_events::MEMBER_STRIPPED.to_owned(),
|
||||
Self::RoomName => test_json::sync_events::NAME_STRIPPED.to_owned(),
|
||||
Self::Custom(json) => json,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the typed JSON representation of this test event.
|
||||
pub fn into_raw_event(self) -> Raw<AnyStrippedStateEvent> {
|
||||
from_json_value(self.into_json_value()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test events that can be added to the room account data.
|
||||
pub enum RoomAccountDataTestEvent {
|
||||
FullyRead,
|
||||
Custom(JsonValue),
|
||||
}
|
||||
|
||||
impl RoomAccountDataTestEvent {
|
||||
/// Get the JSON representation of this test event.
|
||||
pub fn into_json_value(self) -> JsonValue {
|
||||
match self {
|
||||
Self::FullyRead => test_json::sync_events::FULLY_READ.to_owned(),
|
||||
Self::Custom(json) => json,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the typed JSON representation of this test event.
|
||||
pub fn into_raw_event(self) -> Raw<AnyRoomAccountDataEvent> {
|
||||
from_json_value(self.into_json_value()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test events that can be added to the ephemeral events.
|
||||
pub enum EphemeralTestEvent {
|
||||
ReadReceipt,
|
||||
ReadReceiptOther,
|
||||
Typing,
|
||||
Custom(JsonValue),
|
||||
}
|
||||
|
||||
impl EphemeralTestEvent {
|
||||
/// Get the JSON representation of this test event.
|
||||
pub fn into_json_value(self) -> JsonValue {
|
||||
match self {
|
||||
Self::ReadReceipt => test_json::sync_events::READ_RECEIPT.to_owned(),
|
||||
Self::ReadReceiptOther => test_json::sync_events::READ_RECEIPT_OTHER.to_owned(),
|
||||
Self::Typing => test_json::sync_events::TYPING.to_owned(),
|
||||
Self::Custom(json) => json,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the typed JSON representation of this test event.
|
||||
pub fn into_raw_event(self) -> Raw<AnySyncEphemeralRoomEvent> {
|
||||
from_json_value(self.into_json_value()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test events that can be added to the presence events.
|
||||
pub enum PresenceTestEvent {
|
||||
Presence,
|
||||
Custom(JsonValue),
|
||||
}
|
||||
|
||||
impl PresenceTestEvent {
|
||||
/// Get the JSON representation of this test event.
|
||||
pub fn into_json_value(self) -> JsonValue {
|
||||
match self {
|
||||
Self::Presence => test_json::sync_events::PRESENCE.to_owned(),
|
||||
Self::Custom(json) => json,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the typed JSON representation of this test event.
|
||||
pub fn into_raw_event(self) -> Raw<PresenceEvent> {
|
||||
from_json_value(self.into_json_value()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test events that can be added to the global account data.
|
||||
pub enum GlobalAccountDataTestEvent {
|
||||
PushRules,
|
||||
Tags,
|
||||
Custom(JsonValue),
|
||||
}
|
||||
|
||||
impl GlobalAccountDataTestEvent {
|
||||
/// Get the JSON representation of this test event.
|
||||
pub fn into_json_value(self) -> JsonValue {
|
||||
match self {
|
||||
Self::PushRules => test_json::sync_events::PUSH_RULES.to_owned(),
|
||||
Self::Tags => test_json::sync_events::TAG.to_owned(),
|
||||
Self::Custom(json) => json,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the typed JSON representation of this test event.
|
||||
pub fn into_raw_event(self) -> Raw<AnyGlobalAccountDataEvent> {
|
||||
from_json_value(self.into_json_value()).unwrap()
|
||||
}
|
||||
}
|
||||
@@ -1,381 +1,18 @@
|
||||
use std::{collections::HashMap, panic};
|
||||
|
||||
use http::Response;
|
||||
pub use matrix_sdk_test_macros::async_test;
|
||||
use ruma::{
|
||||
api::{client::sync::sync_events::v3::Response as SyncResponse, IncomingResponse},
|
||||
events::{
|
||||
presence::PresenceEvent, AnyGlobalAccountDataEvent, AnySyncEphemeralRoomEvent,
|
||||
AnySyncRoomEvent, AnySyncStateEvent,
|
||||
},
|
||||
room_id,
|
||||
serde::Raw,
|
||||
OwnedRoomId, RoomId,
|
||||
};
|
||||
use ruma::api::{client::sync::sync_events::v3::Response as SyncResponse, IncomingResponse};
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
#[cfg(feature = "appservice")]
|
||||
pub mod appservice;
|
||||
mod event_builder;
|
||||
pub mod test_json;
|
||||
|
||||
/// Embedded event files
|
||||
#[derive(Debug)]
|
||||
pub enum EventsJson {
|
||||
Alias,
|
||||
Aliases,
|
||||
Create,
|
||||
Encryption,
|
||||
FullyRead,
|
||||
HistoryVisibility,
|
||||
JoinRules,
|
||||
Member,
|
||||
MemberInvite,
|
||||
MemberNameChange,
|
||||
MessageEmote,
|
||||
MessageNotice,
|
||||
MessageText,
|
||||
Name,
|
||||
PowerLevels,
|
||||
PushRules,
|
||||
Presence,
|
||||
ReadReceipt,
|
||||
ReadReceiptOther,
|
||||
RedactedInvalid,
|
||||
RedactedState,
|
||||
Redacted,
|
||||
Redaction,
|
||||
RoomAvatar,
|
||||
Tag,
|
||||
Topic,
|
||||
Typing,
|
||||
}
|
||||
|
||||
/// The `EventBuilder` struct can be used to easily generate valid sync
|
||||
/// responses for testing. These can be then fed into either `Client` or `Room`.
|
||||
///
|
||||
/// It supports generated a number of canned events, such as a member entering a
|
||||
/// room, his power level and display name changing and similar. It also
|
||||
/// supports insertion of custom events in the form of `EventsJson` values.
|
||||
///
|
||||
/// **Important** You *must* use the *same* builder when sending multiple sync
|
||||
/// responses to a single client. Otherwise, the subsequent responses will be
|
||||
/// *ignored* by the client because the `next_batch` sync token will not be
|
||||
/// rotated properly.
|
||||
///
|
||||
/// # Example usage
|
||||
///
|
||||
/// ```rust
|
||||
/// use matrix_sdk_test::{EventBuilder, EventsJson};
|
||||
///
|
||||
/// let mut builder = EventBuilder::new();
|
||||
///
|
||||
/// // response1 now contains events that add an example member to the room and change their power
|
||||
/// // level
|
||||
/// let response1 = builder
|
||||
/// .add_room_event(EventsJson::Member)
|
||||
/// .add_room_event(EventsJson::PowerLevels)
|
||||
/// .build_sync_response();
|
||||
///
|
||||
/// // response2 is now empty (nothing changed)
|
||||
/// let response2 = builder.build_sync_response();
|
||||
///
|
||||
/// // response3 contains a display name change for member example
|
||||
/// let response3 = builder
|
||||
/// .add_room_event(EventsJson::MemberNameChange)
|
||||
/// .build_sync_response();
|
||||
/// ```
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EventBuilder {
|
||||
/// The events that determine the state of a `Room`.
|
||||
joined_room_events: HashMap<OwnedRoomId, Vec<Raw<AnySyncRoomEvent>>>,
|
||||
/// The events that determine the state of a `Room`.
|
||||
invited_room_events: HashMap<OwnedRoomId, Vec<Raw<AnySyncStateEvent>>>,
|
||||
/// The events that determine the state of a `Room`.
|
||||
left_room_events: HashMap<OwnedRoomId, Vec<Raw<AnySyncRoomEvent>>>,
|
||||
/// The presence events that determine the presence state of a `RoomMember`.
|
||||
presence_events: Vec<PresenceEvent>,
|
||||
/// The state events that determine the state of a `Room`.
|
||||
state_events: Vec<Raw<AnySyncStateEvent>>,
|
||||
/// The ephemeral room events that determine the state of a `Room`.
|
||||
ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
|
||||
/// The account data events that determine the state of a `Room`.
|
||||
account_data: Vec<Raw<AnyGlobalAccountDataEvent>>,
|
||||
/// Internal counter to enable the `prev_batch` and `next_batch` of each
|
||||
/// sync response to vary.
|
||||
batch_counter: i64,
|
||||
}
|
||||
|
||||
impl EventBuilder {
|
||||
pub fn new() -> Self {
|
||||
let builder: EventBuilder = Default::default();
|
||||
builder
|
||||
}
|
||||
|
||||
/// Add an event to the room events `Vec`.
|
||||
pub fn add_ephemeral(&mut self, json: EventsJson) -> &mut Self {
|
||||
let val: &JsonValue = match json {
|
||||
EventsJson::ReadReceipt => &test_json::READ_RECEIPT,
|
||||
EventsJson::ReadReceiptOther => &test_json::READ_RECEIPT_OTHER,
|
||||
EventsJson::Typing => &test_json::TYPING,
|
||||
_ => panic!("unknown ephemeral event {:?}", json),
|
||||
};
|
||||
|
||||
let event = serde_json::from_value(val.clone()).unwrap();
|
||||
self.ephemeral.push(event);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an event to the room events `Vec`.
|
||||
#[allow(unused)]
|
||||
pub fn add_account(&mut self, json: EventsJson) -> &mut Self {
|
||||
let val: &JsonValue = match json {
|
||||
EventsJson::PushRules => &test_json::PUSH_RULES,
|
||||
_ => panic!("unknown account event {:?}", json),
|
||||
};
|
||||
|
||||
let event = serde_json::from_value(val.clone()).unwrap();
|
||||
self.account_data.push(event);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an event to the room events `Vec`.
|
||||
pub fn add_room_event(&mut self, json: EventsJson) -> &mut Self {
|
||||
let val: &JsonValue = match json {
|
||||
EventsJson::Member => &test_json::MEMBER,
|
||||
EventsJson::MemberInvite => &test_json::MEMBER_INVITE,
|
||||
EventsJson::MemberNameChange => &test_json::MEMBER_NAME_CHANGE,
|
||||
EventsJson::PowerLevels => &test_json::POWER_LEVELS,
|
||||
_ => panic!("unknown room event json {:?}", json),
|
||||
};
|
||||
|
||||
let event = serde_json::from_value(val.clone()).unwrap();
|
||||
|
||||
self.add_joined_event(room_id!("!SVkFJHzfwvuaIEawgC:localhost"), event);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_custom_joined_event(
|
||||
&mut self,
|
||||
room_id: &RoomId,
|
||||
event: serde_json::Value,
|
||||
) -> &mut Self {
|
||||
let event = serde_json::from_value(event).unwrap();
|
||||
self.add_joined_event(room_id, event);
|
||||
self
|
||||
}
|
||||
|
||||
fn add_joined_event(&mut self, room_id: &RoomId, event: Raw<AnySyncRoomEvent>) {
|
||||
self.joined_room_events.entry(room_id.to_owned()).or_default().push(event);
|
||||
}
|
||||
|
||||
pub fn add_custom_invited_event(
|
||||
&mut self,
|
||||
room_id: &RoomId,
|
||||
event: serde_json::Value,
|
||||
) -> &mut Self {
|
||||
let event = serde_json::from_value(event).unwrap();
|
||||
self.invited_room_events.entry(room_id.to_owned()).or_default().push(event);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_custom_left_event(
|
||||
&mut self,
|
||||
room_id: &RoomId,
|
||||
event: serde_json::Value,
|
||||
) -> &mut Self {
|
||||
let event = serde_json::from_value(event).unwrap();
|
||||
self.left_room_events.entry(room_id.to_owned()).or_default().push(event);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a state event to the state events `Vec`.
|
||||
pub fn add_state_event(&mut self, json: EventsJson) -> &mut Self {
|
||||
let val: &JsonValue = match json {
|
||||
EventsJson::Alias => &test_json::ALIAS,
|
||||
EventsJson::Aliases => &test_json::ALIASES,
|
||||
EventsJson::Name => &test_json::NAME,
|
||||
EventsJson::Member => &test_json::MEMBER,
|
||||
EventsJson::PowerLevels => &test_json::POWER_LEVELS,
|
||||
EventsJson::Encryption => &test_json::ENCRYPTION,
|
||||
_ => panic!("unknown state event {:?}", json),
|
||||
};
|
||||
|
||||
let event = serde_json::from_value(val.clone()).unwrap();
|
||||
self.state_events.push(event);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an presence event to the presence events `Vec`.
|
||||
pub fn add_presence_event(&mut self, json: EventsJson) -> &mut Self {
|
||||
let val: &JsonValue = match json {
|
||||
EventsJson::Presence => &test_json::PRESENCE,
|
||||
_ => panic!("unknown presence event {:?}", json),
|
||||
};
|
||||
|
||||
let event = serde_json::from_value::<PresenceEvent>(val.clone()).unwrap();
|
||||
self.presence_events.push(event);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds a sync response as a JSON Value containing the events we queued
|
||||
/// so far.
|
||||
///
|
||||
/// The next response returned by `build_sync_response` will then be empty
|
||||
/// if no further events were queued.
|
||||
///
|
||||
/// This method is raw JSON equivalent to
|
||||
/// [build_sync_response()](#method.build_sync_response), use
|
||||
/// [build_sync_response()](#method.build_sync_response) if you need a typed
|
||||
/// response.
|
||||
pub fn build_json_sync_response(&mut self) -> JsonValue {
|
||||
let main_room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||
|
||||
// First time building a sync response, so initialize the `prev_batch` to a
|
||||
// default one.
|
||||
let prev_batch = self.generate_sync_token();
|
||||
self.batch_counter += 1;
|
||||
let next_batch = self.generate_sync_token();
|
||||
|
||||
// TODO generalize this.
|
||||
let joined_room = serde_json::json!({
|
||||
"summary": {},
|
||||
"account_data": {
|
||||
"events": self.account_data
|
||||
},
|
||||
"ephemeral": {
|
||||
"events": self.ephemeral
|
||||
},
|
||||
"state": {
|
||||
"events": self.state_events
|
||||
},
|
||||
"timeline": {
|
||||
"events": self.joined_room_events.remove(main_room_id).unwrap_or_default(),
|
||||
"limited": true,
|
||||
"prev_batch": prev_batch
|
||||
},
|
||||
"unread_notifications": {
|
||||
"highlight_count": 0,
|
||||
"notification_count": 11
|
||||
}
|
||||
});
|
||||
|
||||
let mut joined_rooms = HashMap::new();
|
||||
|
||||
joined_rooms.insert(main_room_id.to_owned(), joined_room);
|
||||
|
||||
for (room_id, events) in self.joined_room_events.drain() {
|
||||
let joined_room = serde_json::json!({
|
||||
"summary": {},
|
||||
"account_data": {
|
||||
"events": [],
|
||||
},
|
||||
"ephemeral": {
|
||||
"events": [],
|
||||
},
|
||||
"state": {
|
||||
"events": [],
|
||||
},
|
||||
"timeline": {
|
||||
"events": events,
|
||||
"limited": true,
|
||||
"prev_batch": prev_batch
|
||||
},
|
||||
"unread_notifications": {
|
||||
"highlight_count": 0,
|
||||
"notification_count": 11
|
||||
}
|
||||
});
|
||||
joined_rooms.insert(room_id, joined_room);
|
||||
}
|
||||
|
||||
let mut left_rooms = HashMap::new();
|
||||
|
||||
for (room_id, events) in self.left_room_events.drain() {
|
||||
let room = serde_json::json!({
|
||||
"state": {
|
||||
"events": [],
|
||||
},
|
||||
"timeline": {
|
||||
"events": events,
|
||||
"limited": false,
|
||||
"prev_batch": prev_batch
|
||||
},
|
||||
});
|
||||
left_rooms.insert(room_id, room);
|
||||
}
|
||||
|
||||
let mut invited_rooms = HashMap::new();
|
||||
|
||||
for (room_id, events) in self.invited_room_events.drain() {
|
||||
let room = serde_json::json!({
|
||||
"invite_state": {
|
||||
"events": events,
|
||||
},
|
||||
});
|
||||
invited_rooms.insert(room_id, room);
|
||||
}
|
||||
|
||||
let body = serde_json::json! {
|
||||
{
|
||||
"device_one_time_keys_count": {},
|
||||
"next_batch": next_batch,
|
||||
"device_lists": {
|
||||
"changed": [],
|
||||
"left": []
|
||||
},
|
||||
"rooms": {
|
||||
"invite": invited_rooms,
|
||||
"join": joined_rooms,
|
||||
"leave": left_rooms,
|
||||
},
|
||||
"to_device": {
|
||||
"events": []
|
||||
},
|
||||
"presence": {
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Clear state so that the next sync response will be empty if nothing
|
||||
// was added.
|
||||
self.clear();
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
/// Builds a `SyncResponse` containing the events we queued so far.
|
||||
///
|
||||
/// The next response returned by `build_sync_response` will then be empty
|
||||
/// if no further events were queued.
|
||||
///
|
||||
/// This method is high level and typed equivalent to
|
||||
/// [build_json_sync_response()](#method.build_json_sync_response), use
|
||||
/// [build_json_sync_response()](#method.build_json_sync_response) if you
|
||||
/// need an untyped response.
|
||||
pub fn build_sync_response(&mut self) -> SyncResponse {
|
||||
let body = self.build_json_sync_response();
|
||||
|
||||
let response = Response::builder().body(serde_json::to_vec(&body).unwrap()).unwrap();
|
||||
|
||||
SyncResponse::try_from_http_response(response).unwrap()
|
||||
}
|
||||
|
||||
fn generate_sync_token(&self) -> String {
|
||||
format!("t392-516_47314_0_7_1_1_1_11444_{}", self.batch_counter)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.account_data.clear();
|
||||
self.ephemeral.clear();
|
||||
self.invited_room_events.clear();
|
||||
self.joined_room_events.clear();
|
||||
self.left_room_events.clear();
|
||||
self.presence_events.clear();
|
||||
self.state_events.clear();
|
||||
}
|
||||
}
|
||||
pub use event_builder::{
|
||||
bulk_room_members, EphemeralTestEvent, EventBuilder, GlobalAccountDataTestEvent,
|
||||
InvitedRoomBuilder, JoinedRoomBuilder, LeftRoomBuilder, PresenceTestEvent,
|
||||
RoomAccountDataTestEvent, StateTestEvent, StrippedStateTestEvent, TimelineTestEvent,
|
||||
};
|
||||
|
||||
/// Embedded sync response files
|
||||
pub enum SyncResponseFile {
|
||||
@@ -402,6 +39,6 @@ pub fn sync_response(kind: SyncResponseFile) -> SyncResponse {
|
||||
SyncResponse::try_from_http_response(response).unwrap()
|
||||
}
|
||||
|
||||
pub fn response_from_file(json: &serde_json::Value) -> Response<Vec<u8>> {
|
||||
pub fn response_from_file(json: &JsonValue) -> Response<Vec<u8>> {
|
||||
Response::builder().status(200).body(json.to_string().as_bytes().to_vec()).unwrap()
|
||||
}
|
||||
|
||||
213
crates/matrix-sdk-test/src/test_json/api_responses.rs
Normal file
213
crates/matrix-sdk-test/src/test_json/api_responses.rs
Normal file
@@ -0,0 +1,213 @@
|
||||
//! Responses to client API calls.
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
|
||||
/// `GET /_matrix/client/v3/devices`
|
||||
pub static DEVICES: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"devices": [
|
||||
{
|
||||
"device_id": "BNYQQWUMXO",
|
||||
"display_name": "Client 1",
|
||||
"last_seen_ip": "-",
|
||||
"last_seen_ts": 1596117733037u64,
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"device_id": "LEBKSEUSNR",
|
||||
"display_name": "Client 2",
|
||||
"last_seen_ip": "-",
|
||||
"last_seen_ts": 1599057006985u64,
|
||||
"user_id": "@example:localhost"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
/// `GET /_matrix/client/v3/directory/room/{roomAlias}`
|
||||
pub static GET_ALIAS: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"room_id": "!lUbmUPdxdXxEQurqOs:example.net",
|
||||
"servers": [
|
||||
"example.org",
|
||||
"example.net",
|
||||
"matrix.org",
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
/// `POST /_matrix/client/v3/keys/query`
|
||||
pub static KEYS_QUERY: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"device_keys": {
|
||||
"@alice:example.org": {
|
||||
"JLAFKJWSCS": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "JLAFKJWSCS",
|
||||
"user_id": "@alice:example.org",
|
||||
"keys": {
|
||||
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM"
|
||||
},
|
||||
"signatures": {
|
||||
"@alice:example.org": {
|
||||
"ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA"
|
||||
}
|
||||
},
|
||||
"unsigned": {
|
||||
"device_display_name": "Alice's mobile phone"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"failures": {}
|
||||
})
|
||||
});
|
||||
|
||||
/// ``
|
||||
pub static KEYS_UPLOAD: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"one_time_key_counts": {
|
||||
"curve25519": 10,
|
||||
"signed_curve25519": 20
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
/// Successful call to `POST /_matrix/client/v3/login` without auto-discovery.
|
||||
pub static LOGIN: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"access_token": "abc123",
|
||||
"device_id": "GHTYAJCE",
|
||||
"home_server": "matrix.org",
|
||||
"user_id": "@cheeky_monkey:matrix.org"
|
||||
})
|
||||
});
|
||||
|
||||
/// Successful call to `POST /_matrix/client/v3/login` with auto-discovery.
|
||||
pub static LOGIN_WITH_DISCOVERY: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"access_token": "abc123",
|
||||
"device_id": "GHTYAJCE",
|
||||
"home_server": "matrix.org",
|
||||
"user_id": "@cheeky_monkey:matrix.org",
|
||||
"well_known": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https://example.org"
|
||||
},
|
||||
"m.identity_server": {
|
||||
"base_url": "https://id.example.org"
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
/// Failed call to `POST /_matrix/client/v3/login`
|
||||
pub static LOGIN_RESPONSE_ERR: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Invalid password"
|
||||
})
|
||||
});
|
||||
|
||||
/// `GET /_matrix/client/v3/login`
|
||||
pub static LOGIN_TYPES: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"flows": [
|
||||
{
|
||||
"type": "m.login.password"
|
||||
},
|
||||
{
|
||||
"type": "m.login.sso"
|
||||
},
|
||||
{
|
||||
"type": "m.login.token"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
/// `GET /_matrix/client/v3/publicRooms`
|
||||
pub static PUBLIC_ROOMS: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"chunk": [
|
||||
{
|
||||
"aliases": [
|
||||
"#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
|
||||
})
|
||||
});
|
||||
|
||||
/// Failed call to `POST /_matrix/client/v3/register`
|
||||
pub static REGISTRATION_RESPONSE_ERR: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Invalid password",
|
||||
"completed": ["example.type.foo"],
|
||||
"flows": [
|
||||
{
|
||||
"stages": ["example.type.foo", "example.type.bar"]
|
||||
},
|
||||
{
|
||||
"stages": ["example.type.foo", "example.type.baz"]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"example.type.baz": {
|
||||
"example_key": "foobar"
|
||||
}
|
||||
},
|
||||
"session": "xxxxxx"
|
||||
})
|
||||
});
|
||||
|
||||
/// `GET /_matrix/client/versions`
|
||||
pub static VERSIONS: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"versions": [
|
||||
"r0.0.1",
|
||||
"r0.1.0",
|
||||
"r0.2.0",
|
||||
"r0.3.0",
|
||||
"r0.4.0",
|
||||
"r0.5.0",
|
||||
"r0.6.0"
|
||||
],
|
||||
"unstable_features": {
|
||||
"org.matrix.label_based_filtering":true,
|
||||
"org.matrix.e2e_cross_signing":true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
/// `GET /.well-known/matrix/client`
|
||||
pub static WELL_KNOWN: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"m.homeserver": {
|
||||
"base_url": "HOMESERVER_URL"
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
/// `GET /_matrix/client/v3/account/whoami`
|
||||
pub static WHOAMI: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"user_id": "@joe:example.org"
|
||||
})
|
||||
});
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Example responses to `GET /_matrix/client/v3/rooms/{roomId}/members`
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
|
||||
|
||||
149
crates/matrix-sdk-test/src/test_json/messages.rs
Normal file
149
crates/matrix-sdk-test/src/test_json/messages.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
//! Example responses from calls to `GET
|
||||
//! /_matrix/client/v3/rooms/{roomId}/messages`.
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
|
||||
pub static ROOM_MESSAGES: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"chunk": [
|
||||
{
|
||||
"age": 1042,
|
||||
"content": {
|
||||
"body": "hello world",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1444812213350496Caaaa:example.com",
|
||||
"origin_server_ts": 1444812213737i64,
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@alice:example.com",
|
||||
"type": "m.room.message"
|
||||
},
|
||||
{
|
||||
"age": 20123,
|
||||
"content": {
|
||||
"body": "the world is big",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1444812213350496Cbbbb:example.com",
|
||||
"origin_server_ts": 1444812194656i64,
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@bob:example.com",
|
||||
"type": "m.room.message"
|
||||
},
|
||||
{
|
||||
"age": 50789,
|
||||
"content": {
|
||||
"name": "New room name"
|
||||
},
|
||||
"event_id": "$1444812213350496Ccccc:example.com",
|
||||
"origin_server_ts": 1444812163990i64,
|
||||
"prev_content": {
|
||||
"name": "Old room name"
|
||||
},
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@bob:example.com",
|
||||
"state_key": "",
|
||||
"type": "m.room.name"
|
||||
}
|
||||
],
|
||||
"end": "t47409-4357353_219380_26003_2265",
|
||||
"start": "t47429-4392820_219380_26003_2265"
|
||||
})
|
||||
});
|
||||
|
||||
pub static ROOM_MESSAGES_BATCH_1: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"chunk": [
|
||||
{
|
||||
"age": 1042,
|
||||
"content": {
|
||||
"body": "hello world",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1444812213350496Caaaf:example.com",
|
||||
"origin_server_ts": 1444812213737i64,
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@alice:example.com",
|
||||
"type": "m.room.message"
|
||||
},
|
||||
{
|
||||
"age": 20123,
|
||||
"content": {
|
||||
"body": "the world is big",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1444812213350496Cbbbf:example.com",
|
||||
"origin_server_ts": 1444812194656i64,
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@bob:example.com",
|
||||
"type": "m.room.message"
|
||||
},
|
||||
{
|
||||
"age": 50789,
|
||||
"content": {
|
||||
"name": "New room name"
|
||||
},
|
||||
"event_id": "$1444812213350496Ccccf:example.com",
|
||||
"origin_server_ts": 1444812163990i64,
|
||||
"prev_content": {
|
||||
"name": "Old room name"
|
||||
},
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@bob:example.com",
|
||||
"state_key": "",
|
||||
"type": "m.room.name"
|
||||
}
|
||||
],
|
||||
"end": "t47409-4357353_219380_26003_2269",
|
||||
"start": "t392-516_47314_0_7_1_1_1_11444_1"
|
||||
})
|
||||
});
|
||||
|
||||
pub static ROOM_MESSAGES_BATCH_2: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"chunk": [
|
||||
{
|
||||
"age": 1042,
|
||||
"content": {
|
||||
"body": "hello world",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1444812213350496Caaak:example.com",
|
||||
"origin_server_ts": 1444812213737i64,
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@alice:example.com",
|
||||
"type": "m.room.message"
|
||||
},
|
||||
{
|
||||
"age": 20123,
|
||||
"content": {
|
||||
"body": "the world is big",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1444812213350496Cbbbk:example.com",
|
||||
"origin_server_ts": 1444812194656i64,
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@bob:example.com",
|
||||
"type": "m.room.message"
|
||||
},
|
||||
{
|
||||
"age": 50789,
|
||||
"content": {
|
||||
"name": "New room name"
|
||||
},
|
||||
"event_id": "$1444812213350496Cccck:example.com",
|
||||
"origin_server_ts": 1444812163990i64,
|
||||
"prev_content": {
|
||||
"name": "Old room name"
|
||||
},
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@bob:example.com",
|
||||
"state_key": "",
|
||||
"type": "m.room.name"
|
||||
}
|
||||
],
|
||||
"end": "t47409-4357353_219380_26003_2270",
|
||||
"start": "t47409-4357353_219380_26003_2269"
|
||||
})
|
||||
});
|
||||
@@ -7,84 +7,42 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
|
||||
pub mod events;
|
||||
pub mod api_responses;
|
||||
pub mod members;
|
||||
pub mod messages;
|
||||
pub mod sync;
|
||||
pub mod sync_events;
|
||||
|
||||
pub use events::{
|
||||
ALIAS, ALIASES, EMPTY, ENCRYPTION, EVENT_ID, KEYS_QUERY, KEYS_UPLOAD, LOGIN,
|
||||
LOGIN_RESPONSE_ERR, LOGIN_TYPES, LOGIN_WITH_DISCOVERY, MEMBER, MEMBER_INVITE,
|
||||
MEMBER_NAME_CHANGE, MEMBER_STRIPPED, MESSAGE_EDIT, MESSAGE_TEXT, NAME, NAME_STRIPPED,
|
||||
POWER_LEVELS, PRESENCE, PUBLIC_ROOMS, PUSH_RULES, REACTION, READ_RECEIPT, READ_RECEIPT_OTHER,
|
||||
REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, REGISTRATION_RESPONSE_ERR, ROOM_ID,
|
||||
ROOM_MESSAGES, SYNC_ROOM_MESSAGES_BATCH_1, SYNC_ROOM_MESSAGES_BATCH_2, TAG, TOPIC, TYPING,
|
||||
pub use api_responses::{
|
||||
DEVICES, GET_ALIAS, KEYS_QUERY, KEYS_UPLOAD, LOGIN, LOGIN_RESPONSE_ERR, LOGIN_TYPES,
|
||||
LOGIN_WITH_DISCOVERY, PUBLIC_ROOMS, REGISTRATION_RESPONSE_ERR, VERSIONS, WELL_KNOWN, WHOAMI,
|
||||
};
|
||||
pub use members::MEMBERS;
|
||||
pub use messages::{ROOM_MESSAGES, ROOM_MESSAGES_BATCH_1, ROOM_MESSAGES_BATCH_2};
|
||||
pub use sync::{
|
||||
DEFAULT_SYNC_SUMMARY, INVITE_SYNC, LEAVE_SYNC, LEAVE_SYNC_EVENT, MORE_SYNC, MORE_SYNC_2, SYNC,
|
||||
VOIP_SYNC,
|
||||
DEFAULT_SYNC_ROOM_ID, DEFAULT_SYNC_SUMMARY, INVITE_SYNC, LEAVE_SYNC, LEAVE_SYNC_EVENT,
|
||||
MORE_SYNC, MORE_SYNC_2, SYNC, VOIP_SYNC,
|
||||
};
|
||||
pub use sync_events::{
|
||||
ALIAS, ALIASES, ENCRYPTION, MEMBER, MEMBER_INVITE, MEMBER_NAME_CHANGE, MEMBER_STRIPPED,
|
||||
MESSAGE_EDIT, MESSAGE_TEXT, NAME, NAME_STRIPPED, POWER_LEVELS, PRESENCE, PUSH_RULES, REACTION,
|
||||
READ_RECEIPT, READ_RECEIPT_OTHER, REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, TAG,
|
||||
TOPIC, TYPING,
|
||||
};
|
||||
|
||||
pub static DEVICES: Lazy<JsonValue> = Lazy::new(|| {
|
||||
/// An empty response.
|
||||
pub static EMPTY: Lazy<JsonValue> = Lazy::new(|| json!({}));
|
||||
|
||||
/// A response with only an event ID.
|
||||
pub static EVENT_ID: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"devices": [
|
||||
{
|
||||
"device_id": "BNYQQWUMXO",
|
||||
"display_name": "Client 1",
|
||||
"last_seen_ip": "-",
|
||||
"last_seen_ts": 1596117733037u64,
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"device_id": "LEBKSEUSNR",
|
||||
"display_name": "Client 2",
|
||||
"last_seen_ip": "-",
|
||||
"last_seen_ts": 1599057006985u64,
|
||||
"user_id": "@example:localhost"
|
||||
}
|
||||
]
|
||||
"event_id": "$h29iv0s8:example.com"
|
||||
})
|
||||
});
|
||||
|
||||
pub static GET_ALIAS: Lazy<JsonValue> = Lazy::new(|| {
|
||||
/// A response with only a room ID.
|
||||
pub static ROOM_ID: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"room_id": "!lUbmUPdxdXxEQurqOs:example.net",
|
||||
"servers": [
|
||||
"example.org",
|
||||
"example.net",
|
||||
"matrix.org",
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
pub static WELL_KNOWN: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"m.homeserver": {
|
||||
"base_url": "HOMESERVER_URL"
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
pub static VERSIONS: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"versions": [
|
||||
"r0.0.1",
|
||||
"r0.1.0",
|
||||
"r0.2.0",
|
||||
"r0.3.0",
|
||||
"r0.4.0",
|
||||
"r0.5.0",
|
||||
"r0.6.0"
|
||||
],
|
||||
"unstable_features": {
|
||||
"org.matrix.label_based_filtering":true,
|
||||
"org.matrix.e2e_cross_signing":true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
pub static WHOAMI: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"user_id": "@joe:example.org"
|
||||
"room_id": "!testroom:example.org"
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
//! Complete sync responses.
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use ruma::{room_id, RoomId};
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
|
||||
/// The default room ID where sync events are added.
|
||||
pub static DEFAULT_SYNC_ROOM_ID: Lazy<&RoomId> =
|
||||
Lazy::new(|| room_id!("!SVkFJHzfwvuaIEawgC:localhost"));
|
||||
|
||||
pub static SYNC: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"device_one_time_keys_count": {},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Discrete events found in a sync response.
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
|
||||
@@ -95,246 +97,6 @@ pub static JOIN_RULES: Lazy<JsonValue> = Lazy::new(|| {
|
||||
})
|
||||
});
|
||||
|
||||
pub static ROOM_MESSAGES: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"chunk": [
|
||||
{
|
||||
"age": 1042,
|
||||
"content": {
|
||||
"body": "hello world",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1444812213350496Caaaa:example.com",
|
||||
"origin_server_ts": 1444812213737i64,
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@alice:example.com",
|
||||
"type": "m.room.message"
|
||||
},
|
||||
{
|
||||
"age": 20123,
|
||||
"content": {
|
||||
"body": "the world is big",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1444812213350496Cbbbb:example.com",
|
||||
"origin_server_ts": 1444812194656i64,
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@bob:example.com",
|
||||
"type": "m.room.message"
|
||||
},
|
||||
{
|
||||
"age": 50789,
|
||||
"content": {
|
||||
"name": "New room name"
|
||||
},
|
||||
"event_id": "$1444812213350496Ccccc:example.com",
|
||||
"origin_server_ts": 1444812163990i64,
|
||||
"prev_content": {
|
||||
"name": "Old room name"
|
||||
},
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@bob:example.com",
|
||||
"state_key": "",
|
||||
"type": "m.room.name"
|
||||
}
|
||||
],
|
||||
"end": "t47409-4357353_219380_26003_2265",
|
||||
"start": "t47429-4392820_219380_26003_2265"
|
||||
})
|
||||
});
|
||||
|
||||
pub static SYNC_ROOM_MESSAGES_BATCH_1: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"chunk": [
|
||||
{
|
||||
"age": 1042,
|
||||
"content": {
|
||||
"body": "hello world",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1444812213350496Caaaf:example.com",
|
||||
"origin_server_ts": 1444812213737i64,
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@alice:example.com",
|
||||
"type": "m.room.message"
|
||||
},
|
||||
{
|
||||
"age": 20123,
|
||||
"content": {
|
||||
"body": "the world is big",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1444812213350496Cbbbf:example.com",
|
||||
"origin_server_ts": 1444812194656i64,
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@bob:example.com",
|
||||
"type": "m.room.message"
|
||||
},
|
||||
{
|
||||
"age": 50789,
|
||||
"content": {
|
||||
"name": "New room name"
|
||||
},
|
||||
"event_id": "$1444812213350496Ccccf:example.com",
|
||||
"origin_server_ts": 1444812163990i64,
|
||||
"prev_content": {
|
||||
"name": "Old room name"
|
||||
},
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@bob:example.com",
|
||||
"state_key": "",
|
||||
"type": "m.room.name"
|
||||
}
|
||||
],
|
||||
"end": "t47409-4357353_219380_26003_2269",
|
||||
"start": "t392-516_47314_0_7_1_1_1_11444_1"
|
||||
})
|
||||
});
|
||||
|
||||
pub static SYNC_ROOM_MESSAGES_BATCH_2: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"chunk": [
|
||||
{
|
||||
"age": 1042,
|
||||
"content": {
|
||||
"body": "hello world",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1444812213350496Caaak:example.com",
|
||||
"origin_server_ts": 1444812213737i64,
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@alice:example.com",
|
||||
"type": "m.room.message"
|
||||
},
|
||||
{
|
||||
"age": 20123,
|
||||
"content": {
|
||||
"body": "the world is big",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1444812213350496Cbbbk:example.com",
|
||||
"origin_server_ts": 1444812194656i64,
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@bob:example.com",
|
||||
"type": "m.room.message"
|
||||
},
|
||||
{
|
||||
"age": 50789,
|
||||
"content": {
|
||||
"name": "New room name"
|
||||
},
|
||||
"event_id": "$1444812213350496Cccck:example.com",
|
||||
"origin_server_ts": 1444812163990i64,
|
||||
"prev_content": {
|
||||
"name": "Old room name"
|
||||
},
|
||||
"room_id": "!Xq3620DUiqCaoxq:example.com",
|
||||
"sender": "@bob:example.com",
|
||||
"state_key": "",
|
||||
"type": "m.room.name"
|
||||
}
|
||||
],
|
||||
"end": "t47409-4357353_219380_26003_2270",
|
||||
"start": "t47409-4357353_219380_26003_2269"
|
||||
})
|
||||
});
|
||||
|
||||
pub static KEYS_QUERY: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"device_keys": {
|
||||
"@alice:example.org": {
|
||||
"JLAFKJWSCS": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "JLAFKJWSCS",
|
||||
"user_id": "@alice:example.org",
|
||||
"keys": {
|
||||
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM"
|
||||
},
|
||||
"signatures": {
|
||||
"@alice:example.org": {
|
||||
"ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA"
|
||||
}
|
||||
},
|
||||
"unsigned": {
|
||||
"device_display_name": "Alice's mobile phone"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"failures": {}
|
||||
})
|
||||
});
|
||||
|
||||
pub static KEYS_UPLOAD: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"one_time_key_counts": {
|
||||
"curve25519": 10,
|
||||
"signed_curve25519": 20
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
pub static LOGIN: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"access_token": "abc123",
|
||||
"device_id": "GHTYAJCE",
|
||||
"home_server": "matrix.org",
|
||||
"user_id": "@cheeky_monkey:matrix.org"
|
||||
})
|
||||
});
|
||||
|
||||
pub static LOGIN_WITH_DISCOVERY: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"access_token": "abc123",
|
||||
"device_id": "GHTYAJCE",
|
||||
"home_server": "matrix.org",
|
||||
"user_id": "@cheeky_monkey:matrix.org",
|
||||
"well_known": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https://example.org"
|
||||
},
|
||||
"m.identity_server": {
|
||||
"base_url": "https://id.example.org"
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
pub static LOGIN_RESPONSE_ERR: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Invalid password"
|
||||
})
|
||||
});
|
||||
|
||||
pub static LOGIN_TYPES: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"flows": [
|
||||
{
|
||||
"type": "m.login.password"
|
||||
},
|
||||
{
|
||||
"type": "m.login.sso"
|
||||
},
|
||||
{
|
||||
"type": "m.login.token"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
pub static EMPTY: Lazy<JsonValue> = Lazy::new(|| json!({}));
|
||||
|
||||
pub static EVENT_ID: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"event_id": "$h29iv0s8:example.com"
|
||||
})
|
||||
});
|
||||
|
||||
pub static ENCRYPTION: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"content": {
|
||||
@@ -609,28 +371,6 @@ pub static PRESENCE: Lazy<JsonValue> = Lazy::new(|| {
|
||||
})
|
||||
});
|
||||
|
||||
pub static PUBLIC_ROOMS: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"chunk": [
|
||||
{
|
||||
"aliases": [
|
||||
"#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
|
||||
})
|
||||
});
|
||||
|
||||
pub static PUSH_RULES: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"content": {
|
||||
@@ -830,28 +570,6 @@ pub static PUSH_RULES: Lazy<JsonValue> = Lazy::new(|| {
|
||||
})
|
||||
});
|
||||
|
||||
pub static REGISTRATION_RESPONSE_ERR: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Invalid password",
|
||||
"completed": ["example.type.foo"],
|
||||
"flows": [
|
||||
{
|
||||
"stages": ["example.type.foo", "example.type.bar"]
|
||||
},
|
||||
{
|
||||
"stages": ["example.type.foo", "example.type.baz"]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"example.type.baz": {
|
||||
"example_key": "foobar"
|
||||
}
|
||||
},
|
||||
"session": "xxxxxx"
|
||||
})
|
||||
});
|
||||
|
||||
pub static REACTION: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"content": {
|
||||
@@ -998,12 +716,6 @@ pub static ROOM_AVATAR: Lazy<JsonValue> = Lazy::new(|| {
|
||||
})
|
||||
});
|
||||
|
||||
pub static ROOM_ID: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"room_id": "!testroom:example.org"
|
||||
})
|
||||
});
|
||||
|
||||
pub static TAG: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"content": {
|
||||
@@ -28,18 +28,18 @@ e2e-encryption = [
|
||||
"matrix-sdk-indexeddb?/e2e-encryption", # activate on indexeddb if given
|
||||
]
|
||||
|
||||
sled = ["matrix-sdk-sled/state-store"]
|
||||
indexeddb = ["matrix-sdk-indexeddb"]
|
||||
sled = ["dep:matrix-sdk-sled", "matrix-sdk-sled?/state-store"]
|
||||
indexeddb = ["dep:matrix-sdk-indexeddb"]
|
||||
|
||||
qrcode = ["e2e-encryption", "matrix-sdk-base/qrcode"]
|
||||
markdown = ["ruma/markdown"]
|
||||
native-tls = ["reqwest/native-tls"]
|
||||
rustls-tls = ["reqwest/rustls-tls"]
|
||||
socks = ["reqwest/socks"]
|
||||
sso-login = ["warp", "rand", "tokio-stream"]
|
||||
sso-login = ["warp", "dep:rand", "dep:tokio-stream"]
|
||||
appservice = ["ruma/appservice-api-s"]
|
||||
image-proc = ["image"]
|
||||
image-rayon = ["image-proc", "image/jpeg_rayon"]
|
||||
image-proc = ["dep:image"]
|
||||
image-rayon = ["image-proc", "image?/jpeg_rayon"]
|
||||
|
||||
experimental-timeline = [
|
||||
"matrix-sdk-base/experimental-timeline",
|
||||
@@ -110,13 +110,8 @@ default_features = false
|
||||
|
||||
[dependencies.ruma]
|
||||
git = "https://github.com/ruma/ruma"
|
||||
rev = "96155915f"
|
||||
features = ["client-api-c", "compat", "rand", "unstable-msc2448"]
|
||||
|
||||
[dependencies.ruma-client-api]
|
||||
git = "https://github.com/ruma/ruma"
|
||||
rev = "96155915f"
|
||||
features = ["unstable-msc2965"]
|
||||
rev = "ca8c66c885241a7ba3805399604eda4a38979f6b"
|
||||
features = ["client-api-c", "compat", "rand", "unstable-msc2448", "unstable-msc2965"]
|
||||
|
||||
[dependencies.tokio-stream]
|
||||
version = "0.1.8"
|
||||
|
||||
@@ -239,8 +239,11 @@ impl ClientBuilder {
|
||||
|
||||
/// All outgoing http requests will have a GET query key-value appended with
|
||||
/// `user_id` being the key and the `user_id` from the `Session` being
|
||||
/// the value. Will error if there's no `Session`. This is called
|
||||
/// [identity assertion] in the Matrix Application Service Spec
|
||||
/// the value. This is called [identity assertion] in the
|
||||
/// Matrix Application Service Spec.
|
||||
///
|
||||
/// Requests that don't require authentication might not do identity
|
||||
/// assertion.
|
||||
///
|
||||
/// [identity assertion]: https://spec.matrix.org/unstable/application-service-api/#identity-assertion
|
||||
#[doc(hidden)]
|
||||
|
||||
@@ -30,7 +30,7 @@ use futures_core::stream::Stream;
|
||||
use matrix_sdk_base::{
|
||||
deserialized_responses::SyncResponse,
|
||||
media::{MediaEventContent, MediaFormat, MediaRequest, MediaThumbnailSize},
|
||||
BaseClient, Session, Store,
|
||||
BaseClient, Session, StateStore,
|
||||
};
|
||||
use matrix_sdk_common::{
|
||||
instant::{Duration, Instant},
|
||||
@@ -320,12 +320,12 @@ impl Client {
|
||||
/// Can be used with [`Client::restore_login`] to restore a previously
|
||||
/// logged-in session.
|
||||
pub fn session(&self) -> Option<&Session> {
|
||||
self.store().session()
|
||||
self.base_client().session()
|
||||
}
|
||||
|
||||
/// Get a reference to the store.
|
||||
pub fn store(&self) -> &Store {
|
||||
self.inner.base_client.store()
|
||||
/// Get a reference to the state store.
|
||||
pub fn store(&self) -> &dyn StateStore {
|
||||
self.base_client().store()
|
||||
}
|
||||
|
||||
/// Get the account of the current owner of the client.
|
||||
@@ -564,7 +564,7 @@ impl Client {
|
||||
///
|
||||
/// This will return the list of joined, invited, and left rooms.
|
||||
pub fn rooms(&self) -> Vec<room::Room> {
|
||||
self.store()
|
||||
self.base_client()
|
||||
.get_rooms()
|
||||
.into_iter()
|
||||
.map(|room| room::Common::new(self.clone(), room).into())
|
||||
@@ -573,7 +573,7 @@ impl Client {
|
||||
|
||||
/// Returns the joined rooms this client knows about.
|
||||
pub fn joined_rooms(&self) -> Vec<room::Joined> {
|
||||
self.store()
|
||||
self.base_client()
|
||||
.get_rooms()
|
||||
.into_iter()
|
||||
.filter_map(|room| room::Joined::new(self.clone(), room))
|
||||
@@ -582,7 +582,7 @@ impl Client {
|
||||
|
||||
/// Returns the invited rooms this client knows about.
|
||||
pub fn invited_rooms(&self) -> Vec<room::Invited> {
|
||||
self.store()
|
||||
self.base_client()
|
||||
.get_stripped_rooms()
|
||||
.into_iter()
|
||||
.filter_map(|room| room::Invited::new(self.clone(), room))
|
||||
@@ -591,7 +591,7 @@ impl Client {
|
||||
|
||||
/// Returns the left rooms this client knows about.
|
||||
pub fn left_rooms(&self) -> Vec<room::Left> {
|
||||
self.store()
|
||||
self.base_client()
|
||||
.get_rooms()
|
||||
.into_iter()
|
||||
.filter_map(|room| room::Left::new(self.clone(), room))
|
||||
@@ -604,7 +604,9 @@ impl Client {
|
||||
///
|
||||
/// `room_id` - The unique id of the room that should be fetched.
|
||||
pub fn get_room(&self, room_id: &RoomId) -> Option<room::Room> {
|
||||
self.store().get_room(room_id).map(|room| room::Common::new(self.clone(), room).into())
|
||||
self.base_client()
|
||||
.get_room(room_id)
|
||||
.map(|room| room::Common::new(self.clone(), room).into())
|
||||
}
|
||||
|
||||
/// Get a joined room with the given room id.
|
||||
@@ -613,7 +615,7 @@ impl Client {
|
||||
///
|
||||
/// `room_id` - The unique id of the room that should be fetched.
|
||||
pub fn get_joined_room(&self, room_id: &RoomId) -> Option<room::Joined> {
|
||||
self.store().get_room(room_id).and_then(|room| room::Joined::new(self.clone(), room))
|
||||
self.base_client().get_room(room_id).and_then(|room| room::Joined::new(self.clone(), room))
|
||||
}
|
||||
|
||||
/// Get an invited room with the given room id.
|
||||
@@ -622,7 +624,7 @@ impl Client {
|
||||
///
|
||||
/// `room_id` - The unique id of the room that should be fetched.
|
||||
pub fn get_invited_room(&self, room_id: &RoomId) -> Option<room::Invited> {
|
||||
self.store().get_room(room_id).and_then(|room| room::Invited::new(self.clone(), room))
|
||||
self.base_client().get_room(room_id).and_then(|room| room::Invited::new(self.clone(), room))
|
||||
}
|
||||
|
||||
/// Get a left room with the given room id.
|
||||
@@ -631,7 +633,7 @@ impl Client {
|
||||
///
|
||||
/// `room_id` - The unique id of the room that should be fetched.
|
||||
pub fn get_left_room(&self, room_id: &RoomId) -> Option<room::Left> {
|
||||
self.store().get_room(room_id).and_then(|room| room::Left::new(self.clone(), room))
|
||||
self.base_client().get_room(room_id).and_then(|room| room::Left::new(self.clone(), room))
|
||||
}
|
||||
|
||||
/// Resolve a room alias to a room id and a list of servers which know
|
||||
@@ -2206,11 +2208,11 @@ impl Client {
|
||||
pub(crate) mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use matrix_sdk_test::{async_test, test_json, EventBuilder, EventsJson};
|
||||
use matrix_sdk_test::{async_test, test_json, EventBuilder, JoinedRoomBuilder, StateTestEvent};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
use ruma::{api::MatrixVersion, device_id, room_id, user_id, UserId};
|
||||
use ruma::{api::MatrixVersion, device_id, user_id, UserId};
|
||||
use url::Url;
|
||||
use wiremock::{
|
||||
matchers::{header, method, path},
|
||||
@@ -2316,12 +2318,15 @@ pub(crate) mod tests {
|
||||
let client = logged_in_client(Some(server.uri())).await;
|
||||
|
||||
let response = EventBuilder::default()
|
||||
.add_state_event(EventsJson::Member)
|
||||
.add_state_event(EventsJson::PowerLevels)
|
||||
.add_joined_room(
|
||||
JoinedRoomBuilder::default()
|
||||
.add_state_event(StateTestEvent::Member)
|
||||
.add_state_event(StateTestEvent::PowerLevels),
|
||||
)
|
||||
.build_sync_response();
|
||||
|
||||
client.inner.base_client.receive_sync_response(response).await.unwrap();
|
||||
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||
let room_id = &test_json::DEFAULT_SYNC_ROOM_ID;
|
||||
|
||||
assert_eq!(client.homeserver().await, Url::parse(&server.uri()).unwrap());
|
||||
|
||||
|
||||
@@ -872,11 +872,10 @@ impl Encryption {
|
||||
|
||||
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||
mod tests {
|
||||
use matrix_sdk_test::{async_test, EventBuilder, EventsJson};
|
||||
use matrix_sdk_test::{async_test, test_json, EventBuilder, JoinedRoomBuilder, StateTestEvent};
|
||||
use ruma::{
|
||||
event_id,
|
||||
events::reaction::{ReactionEventContent, Relation},
|
||||
room_id,
|
||||
};
|
||||
use serde_json::json;
|
||||
use wiremock::{
|
||||
@@ -892,7 +891,7 @@ mod tests {
|
||||
let client = logged_in_client(Some(server.uri())).await;
|
||||
|
||||
let event_id = event_id!("$2:example.org");
|
||||
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||
let room_id = &test_json::DEFAULT_SYNC_ROOM_ID;
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m%2Ereaction/.*".to_owned()))
|
||||
@@ -903,9 +902,12 @@ mod tests {
|
||||
.await;
|
||||
|
||||
let response = EventBuilder::default()
|
||||
.add_state_event(EventsJson::Member)
|
||||
.add_state_event(EventsJson::PowerLevels)
|
||||
.add_state_event(EventsJson::Encryption)
|
||||
.add_joined_room(
|
||||
JoinedRoomBuilder::default()
|
||||
.add_state_event(StateTestEvent::Member)
|
||||
.add_state_event(StateTestEvent::PowerLevels)
|
||||
.add_state_event(StateTestEvent::Encryption),
|
||||
)
|
||||
.build_sync_response();
|
||||
|
||||
client.inner.base_client.receive_sync_response(response).await.unwrap();
|
||||
|
||||
@@ -558,12 +558,14 @@ mod static_events {
|
||||
|
||||
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||
mod tests {
|
||||
use matrix_sdk_test::async_test;
|
||||
use matrix_sdk_test::{async_test, InvitedRoomBuilder, JoinedRoomBuilder};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
use std::{future, sync::Arc};
|
||||
|
||||
use matrix_sdk_test::{EventBuilder, EventsJson};
|
||||
use matrix_sdk_test::{
|
||||
EphemeralTestEvent, EventBuilder, StateTestEvent, StrippedStateTestEvent, TimelineTestEvent,
|
||||
};
|
||||
use ruma::{
|
||||
events::room::member::{OriginalSyncRoomMemberEvent, StrippedRoomMemberEvent},
|
||||
room_id,
|
||||
@@ -618,45 +620,49 @@ mod tests {
|
||||
.await;
|
||||
|
||||
let response = EventBuilder::default()
|
||||
.add_room_event(EventsJson::Member)
|
||||
.add_ephemeral(EventsJson::Typing)
|
||||
.add_state_event(EventsJson::PowerLevels)
|
||||
.add_custom_invited_event(
|
||||
room_id!("!test_invited:example.org"),
|
||||
json!({
|
||||
"content": {
|
||||
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
|
||||
"displayname": "Alice",
|
||||
"membership": "invite",
|
||||
},
|
||||
"event_id": "$143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824653u64,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "@alice:example.org",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {
|
||||
"age": 1234,
|
||||
"invite_room_state": [
|
||||
{
|
||||
"content": {
|
||||
"name": "Example Room"
|
||||
.add_joined_room(
|
||||
JoinedRoomBuilder::default()
|
||||
.add_timeline_event(TimelineTestEvent::Member)
|
||||
.add_ephemeral_event(EphemeralTestEvent::Typing)
|
||||
.add_state_event(StateTestEvent::PowerLevels),
|
||||
)
|
||||
.add_invited_room(
|
||||
InvitedRoomBuilder::new(room_id!("!test_invited:example.org")).add_state_event(
|
||||
StrippedStateTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
|
||||
"displayname": "Alice",
|
||||
"membership": "invite",
|
||||
},
|
||||
"event_id": "$143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824653u64,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "@alice:example.org",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {
|
||||
"age": 1234,
|
||||
"invite_room_state": [
|
||||
{
|
||||
"content": {
|
||||
"name": "Example Room"
|
||||
},
|
||||
"sender": "@bob:example.org",
|
||||
"state_key": "",
|
||||
"type": "m.room.name"
|
||||
},
|
||||
"sender": "@bob:example.org",
|
||||
"state_key": "",
|
||||
"type": "m.room.name"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"join_rule": "invite"
|
||||
},
|
||||
"sender": "@bob:example.org",
|
||||
"state_key": "",
|
||||
"type": "m.room.join_rules"
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
{
|
||||
"content": {
|
||||
"join_rule": "invite"
|
||||
},
|
||||
"sender": "@bob:example.org",
|
||||
"state_key": "",
|
||||
"type": "m.room.join_rules"
|
||||
}
|
||||
]
|
||||
}
|
||||
})),
|
||||
),
|
||||
)
|
||||
.build_sync_response();
|
||||
client.process_sync(response).await?;
|
||||
|
||||
@@ -125,7 +125,8 @@ impl HttpClient {
|
||||
}
|
||||
|
||||
trace!("Serializing request");
|
||||
let request = if !config.assert_identity {
|
||||
// We can't assert the identity without a session.
|
||||
let request = if !config.assert_identity || session.is_none() {
|
||||
let send_access_token = if auth_scheme == AuthScheme::None && !config.force_auth {
|
||||
// Small optimization: Don't take the session lock if we know the auth token
|
||||
// isn't going to be used anyways.
|
||||
|
||||
@@ -176,13 +176,13 @@ impl Common {
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// let request = MessagesOptions::backward("t47429-4392820_219380_26003_2265");
|
||||
/// let options = MessagesOptions::backward().from("t47429-4392820_219380_26003_2265");
|
||||
///
|
||||
/// let mut client = Client::new(homeserver).await.unwrap();
|
||||
/// let room = client
|
||||
/// .get_joined_room(room_id!("!roomid:example.com"))
|
||||
/// .unwrap();
|
||||
/// assert!(room.messages(request).await.is_ok());
|
||||
/// assert!(room.messages(options).await.is_ok());
|
||||
/// # });
|
||||
/// ```
|
||||
pub async fn messages(&self, options: MessagesOptions<'_>) -> Result<Messages> {
|
||||
@@ -264,7 +264,7 @@ impl Common {
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// use matrix_sdk::{room::MessagesOptions, Client};
|
||||
/// use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::ruma::{
|
||||
/// # api::client::filter::RoomEventFilter,
|
||||
/// # room_id,
|
||||
@@ -344,7 +344,7 @@ impl Common {
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// use matrix_sdk::{room::MessagesOptions, Client};
|
||||
/// use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::ruma::{
|
||||
/// # api::client::filter::RoomEventFilter,
|
||||
/// # room_id,
|
||||
@@ -403,7 +403,7 @@ impl Common {
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// use matrix_sdk::{room::MessagesOptions, Client};
|
||||
/// use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::ruma::{
|
||||
/// # api::client::filter::RoomEventFilter,
|
||||
/// # room_id,
|
||||
@@ -461,7 +461,8 @@ impl Common {
|
||||
let filter = assign!(RoomEventFilter::default(), {
|
||||
lazy_load_options: LazyLoadOptions::Enabled { include_redundant_members: false },
|
||||
});
|
||||
let options = assign!(MessagesOptions::backward(token), {
|
||||
let options = assign!(MessagesOptions::backward(), {
|
||||
from: Some(token),
|
||||
limit: uint!(10),
|
||||
filter,
|
||||
});
|
||||
@@ -1003,7 +1004,7 @@ impl Common {
|
||||
}
|
||||
|
||||
let via = self.route().await?;
|
||||
Ok(self.room_id().matrix_to_uri(via))
|
||||
Ok(self.room_id().matrix_to_uri_via(via))
|
||||
}
|
||||
|
||||
/// Get a `matrix:` permalink to this room.
|
||||
@@ -1022,7 +1023,7 @@ impl Common {
|
||||
}
|
||||
|
||||
let via = self.route().await?;
|
||||
Ok(self.room_id().matrix_uri(via, join))
|
||||
Ok(self.room_id().matrix_uri_via(via, join))
|
||||
}
|
||||
|
||||
/// Get a `matrix.to` permalink to an event in this room.
|
||||
@@ -1045,7 +1046,7 @@ impl Common {
|
||||
// Don't use the alias because an event is tied to a room ID, but an
|
||||
// alias might point to another room, e.g. after a room upgrade.
|
||||
let via = self.route().await?;
|
||||
Ok(self.room_id().matrix_to_event_uri(event_id, via))
|
||||
Ok(self.room_id().matrix_to_event_uri_via(event_id, via))
|
||||
}
|
||||
|
||||
/// Get a `matrix:` permalink to an event in this room.
|
||||
@@ -1068,13 +1069,13 @@ impl Common {
|
||||
// Don't use the alias because an event is tied to a room ID, but an
|
||||
// alias might point to another room, e.g. after a room upgrade.
|
||||
let via = self.route().await?;
|
||||
Ok(self.room_id().matrix_event_uri(event_id, via))
|
||||
Ok(self.room_id().matrix_event_uri_via(event_id, via))
|
||||
}
|
||||
}
|
||||
|
||||
/// Options for [`messages`][Common::messages].
|
||||
///
|
||||
/// See that method for details.
|
||||
/// See that method and <https://spec.matrix.org/v1.3/client-server-api/#get_matrixclientv3roomsroomidmessages> for details.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct MessagesOptions<'a> {
|
||||
@@ -1083,7 +1084,11 @@ pub struct MessagesOptions<'a> {
|
||||
/// This token can be obtained from a `prev_batch` token returned for each
|
||||
/// room from the sync API, or from a start or end token returned by a
|
||||
/// previous `messages` call.
|
||||
pub from: &'a str,
|
||||
///
|
||||
/// If `from` isn't provided the homeserver shall return a list of messages
|
||||
/// from the first or last (per the value of the dir parameter) visible
|
||||
/// event in the room history for the requesting user.
|
||||
pub from: Option<&'a str>,
|
||||
|
||||
/// The token to stop returning events at.
|
||||
///
|
||||
@@ -1105,27 +1110,42 @@ pub struct MessagesOptions<'a> {
|
||||
}
|
||||
|
||||
impl<'a> MessagesOptions<'a> {
|
||||
/// Creates `MessagesOptions` with the given start token and direction.
|
||||
/// Creates `MessagesOptions` with the given direction.
|
||||
///
|
||||
/// All other parameters will be defaulted.
|
||||
pub fn new(from: &'a str, dir: Direction) -> Self {
|
||||
Self { from, to: None, dir, limit: uint!(10), filter: RoomEventFilter::default() }
|
||||
pub fn new(dir: Direction) -> Self {
|
||||
Self { from: None, to: None, dir, limit: uint!(10), filter: RoomEventFilter::default() }
|
||||
}
|
||||
|
||||
/// Creates `MessagesOptions` with the given start token, and `dir` set to
|
||||
/// `Backward`.
|
||||
pub fn backward(from: &'a str) -> Self {
|
||||
Self::new(from, Direction::Backward)
|
||||
/// Creates `MessagesOptions` with `dir` set to `Backward`.
|
||||
///
|
||||
/// If no `from` token is set afterwards, pagination will start at the
|
||||
/// end of (the accessible part of) the room timeline.
|
||||
pub fn backward() -> Self {
|
||||
Self::new(Direction::Backward)
|
||||
}
|
||||
|
||||
/// Creates `MessagesOptions` with the given start token, and `dir` set to
|
||||
/// `Forward`.
|
||||
pub fn forward(from: &'a str) -> Self {
|
||||
Self::new(from, Direction::Forward)
|
||||
/// Creates `MessagesOptions` with `dir` set to `Forward`.
|
||||
///
|
||||
/// If no `from` token is set afterwards, pagination will start at the
|
||||
/// beginning of (the accessible part of) the room timeline.
|
||||
pub fn forward() -> Self {
|
||||
Self::new(Direction::Forward)
|
||||
}
|
||||
|
||||
/// Creates a new `MessagesOptions` from `self` with the `from` field set to
|
||||
/// the given value.
|
||||
///
|
||||
/// Since the field is public, you can also assign to it directly. This
|
||||
/// method merely acts as a shorthand for that, because it is very
|
||||
/// common to set this field.
|
||||
pub fn from(self, from: impl Into<Option<&'a str>>) -> Self {
|
||||
Self { from: from.into(), ..self }
|
||||
}
|
||||
|
||||
fn into_request(self, room_id: &'a RoomId) -> get_message_events::v3::Request<'_> {
|
||||
assign!(get_message_events::v3::Request::new(room_id, self.from, self.dir), {
|
||||
assign!(get_message_events::v3::Request::new(room_id, self.dir), {
|
||||
from: self.from,
|
||||
to: self.to,
|
||||
limit: self.limit,
|
||||
filter: self.filter,
|
||||
|
||||
@@ -20,14 +20,13 @@ use ruma::{
|
||||
},
|
||||
message::send_message_event,
|
||||
read_marker::set_read_marker,
|
||||
receipt::create_receipt,
|
||||
receipt::create_receipt::{self, v3::ReceiptType},
|
||||
redact::redact_event,
|
||||
state::send_state_event,
|
||||
typing::create_typing_event::v3::{Request as TypingRequest, Typing},
|
||||
},
|
||||
assign,
|
||||
events::{room::message::RoomMessageEventContent, MessageLikeEventContent, StateEventContent},
|
||||
receipt::ReceiptType,
|
||||
serde::Raw,
|
||||
EventId, OwnedTransactionId, TransactionId, UserId,
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ impl Client {
|
||||
rooms,
|
||||
presence,
|
||||
account_data,
|
||||
to_device: _,
|
||||
to_device,
|
||||
device_lists: _,
|
||||
device_one_time_keys_count: _,
|
||||
ambiguity_changes: _,
|
||||
@@ -31,6 +31,7 @@ impl Client {
|
||||
|
||||
self.handle_sync_events(EventKind::GlobalAccountData, &None, &account_data.events).await?;
|
||||
self.handle_sync_events(EventKind::Presence, &None, &presence.events).await?;
|
||||
self.handle_sync_events(EventKind::ToDevice, &None, &to_device.events).await?;
|
||||
|
||||
for (room_id, room_info) in &rooms.join {
|
||||
let room = self.get_room(room_id);
|
||||
|
||||
@@ -365,7 +365,7 @@ async fn resolve_room_alias() {
|
||||
|
||||
#[async_test]
|
||||
async fn join_leave_room() {
|
||||
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||
let room_id = &test_json::DEFAULT_SYNC_ROOM_ID;
|
||||
let (client, server) = logged_in_client().await;
|
||||
|
||||
mock_sync(&server, &*test_json::SYNC, None).await;
|
||||
@@ -501,7 +501,7 @@ async fn left_rooms() {
|
||||
assert!(!client.left_rooms().is_empty());
|
||||
assert!(client.invited_rooms().is_empty());
|
||||
|
||||
assert!(client.get_left_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).is_some())
|
||||
assert!(client.get_left_room(&test_json::DEFAULT_SYNC_ROOM_ID).is_some())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use matrix_sdk::{config::SyncSettings, DisplayName, RoomMember};
|
||||
use matrix_sdk_test::{async_test, test_json};
|
||||
use matrix_sdk_test::{
|
||||
async_test, bulk_room_members, test_json, EventBuilder, JoinedRoomBuilder, TimelineTestEvent,
|
||||
};
|
||||
use ruma::{
|
||||
event_id,
|
||||
events::{AnySyncStateEvent, StateEventType},
|
||||
events::{room::member::MembershipState, AnySyncStateEvent, StateEventType},
|
||||
room_id,
|
||||
};
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use serde_json::json;
|
||||
use wiremock::{
|
||||
matchers::{header, method, path_regex},
|
||||
Mock, ResponseTemplate,
|
||||
@@ -32,7 +34,7 @@ async fn user_presence() {
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
let members: Vec<RoomMember> = room.active_members().await.unwrap();
|
||||
|
||||
assert_eq!(2, members.len());
|
||||
@@ -47,7 +49,7 @@ async fn calculate_room_names_from_summary() {
|
||||
|
||||
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
assert_eq!(DisplayName::Calculated("example2".to_owned()), room.display_name().await.unwrap());
|
||||
}
|
||||
@@ -63,7 +65,7 @@ async fn room_names() {
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
assert_eq!(client.rooms().len(), 1);
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
assert_eq!(DisplayName::Aliased("tutorial".to_owned()), room.display_name().await.unwrap());
|
||||
|
||||
@@ -83,7 +85,7 @@ async fn room_names() {
|
||||
|
||||
#[async_test]
|
||||
async fn test_state_event_getting() {
|
||||
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||
let room_id = &test_json::DEFAULT_SYNC_ROOM_ID;
|
||||
|
||||
let (client, server) = logged_in_client().await;
|
||||
|
||||
@@ -187,7 +189,7 @@ async fn room_timeline_with_remove() {
|
||||
|
||||
let _ = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
let (forward_stream, backward_stream) = room.timeline().await.unwrap();
|
||||
|
||||
// these two syncs lead to the store removing its existing timeline
|
||||
@@ -201,9 +203,7 @@ async fn room_timeline_with_remove() {
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/messages$"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.and(query_param("from", "t392-516_47314_0_7_1_1_1_11444_1"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_json(&*test_json::SYNC_ROOM_MESSAGES_BATCH_1),
|
||||
)
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::ROOM_MESSAGES_BATCH_1))
|
||||
.expect(1)
|
||||
.named("messages_batch_1")
|
||||
.mount(&server)
|
||||
@@ -213,9 +213,7 @@ async fn room_timeline_with_remove() {
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/messages$"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.and(query_param("from", "t47409-4357353_219380_26003_2269"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_json(&*test_json::SYNC_ROOM_MESSAGES_BATCH_2),
|
||||
)
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::ROOM_MESSAGES_BATCH_2))
|
||||
.expect(1)
|
||||
.named("messages_batch_2")
|
||||
.mount(&server)
|
||||
@@ -288,7 +286,7 @@ async fn room_timeline() {
|
||||
|
||||
let _ = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
let (forward_stream, backward_stream) = room.timeline().await.unwrap();
|
||||
|
||||
let sync_token = client.sync_token().await.unwrap();
|
||||
@@ -299,9 +297,7 @@ async fn room_timeline() {
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/messages$"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.and(query_param("from", "t392-516_47314_0_7_1_1_1_11444_1"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_json(&*test_json::SYNC_ROOM_MESSAGES_BATCH_1),
|
||||
)
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::ROOM_MESSAGES_BATCH_1))
|
||||
.expect(1)
|
||||
.named("messages_batch_1")
|
||||
.mount(&server)
|
||||
@@ -311,9 +307,7 @@ async fn room_timeline() {
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/messages$"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.and(query_param("from", "t47409-4357353_219380_26003_2269"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_json(&*test_json::SYNC_ROOM_MESSAGES_BATCH_2),
|
||||
)
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::ROOM_MESSAGES_BATCH_2))
|
||||
.expect(1)
|
||||
.named("messages_batch_2")
|
||||
.mount(&server)
|
||||
@@ -369,92 +363,26 @@ async fn room_timeline() {
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn room_permalink() {
|
||||
fn sync_response(index: u8, room_timeline_events: &[JsonValue]) -> JsonValue {
|
||||
json!({
|
||||
"device_one_time_keys_count": {},
|
||||
"next_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index + 1),
|
||||
"device_lists": {
|
||||
"changed": [],
|
||||
"left": []
|
||||
},
|
||||
"account_data": {
|
||||
"events": []
|
||||
},
|
||||
"rooms": {
|
||||
"invite": {},
|
||||
"join": {
|
||||
"!test_room:127.0.0.1": {
|
||||
"summary": {},
|
||||
"account_data": {
|
||||
"events": []
|
||||
},
|
||||
"ephemeral": {
|
||||
"events": []
|
||||
},
|
||||
"state": {
|
||||
"events": []
|
||||
},
|
||||
"timeline": {
|
||||
"events": room_timeline_events,
|
||||
"limited": false,
|
||||
"prev_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index - 1),
|
||||
},
|
||||
"unread_notifications": {
|
||||
"highlight_count": 0,
|
||||
"notification_count": 0,
|
||||
}
|
||||
}
|
||||
},
|
||||
"leave": {}
|
||||
},
|
||||
"to_device": {
|
||||
"events": []
|
||||
},
|
||||
"presence": {
|
||||
"events": []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn room_member_events(nb: usize, server: &str) -> Vec<JsonValue> {
|
||||
let mut events = Vec::with_capacity(nb);
|
||||
for i in 0..nb {
|
||||
let id = format!("${server}{i}");
|
||||
let user = format!("@user{i}:{server}");
|
||||
events.push(json!({
|
||||
"content": {
|
||||
"membership": "join",
|
||||
},
|
||||
"event_id": id,
|
||||
"origin_server_ts": 151800140,
|
||||
"sender": user,
|
||||
"state_key": user,
|
||||
"type": "m.room.member",
|
||||
}))
|
||||
}
|
||||
events
|
||||
}
|
||||
|
||||
async fn room_route() {
|
||||
let (client, server) = logged_in_client().await;
|
||||
let mut ev_builder = EventBuilder::new();
|
||||
let room_id = room_id!("!test_room:127.0.0.1");
|
||||
|
||||
// Without elligible server
|
||||
let mut sync_index = 1;
|
||||
let res = sync_response(
|
||||
sync_index,
|
||||
&[
|
||||
json!({
|
||||
ev_builder.add_joined_room(
|
||||
JoinedRoomBuilder::new(room_id)
|
||||
.add_timeline_event(TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"creator": "@creator:127.0.0.1",
|
||||
"room_version": "6",
|
||||
},
|
||||
"event_id": "$151957878228ekrDs",
|
||||
"origin_server_ts": 15195787,
|
||||
"sender": "@creator:localhost",
|
||||
"sender": "@creator:127.0.0.1",
|
||||
"state_key": "",
|
||||
"type": "m.room.create",
|
||||
}),
|
||||
json!({
|
||||
})))
|
||||
.add_timeline_event(TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"membership": "join",
|
||||
},
|
||||
@@ -463,110 +391,79 @@ async fn room_permalink() {
|
||||
"sender": "@creator:127.0.0.1",
|
||||
"state_key": "@creator:127.0.0.1",
|
||||
"type": "m.room.member",
|
||||
}),
|
||||
],
|
||||
}))),
|
||||
);
|
||||
mock_sync(&server, res, None).await;
|
||||
client.sync_once(SyncSettings::new()).await.unwrap();
|
||||
let room = client.get_room(room_id!("!test_room:127.0.0.1")).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
room.matrix_to_permalink().await.unwrap().to_string(),
|
||||
"https://matrix.to/#/%21test_room%3A127.0.0.1"
|
||||
);
|
||||
assert_eq!(
|
||||
room.matrix_permalink(false).await.unwrap().to_string(),
|
||||
"matrix:roomid/test_room:127.0.0.1"
|
||||
);
|
||||
assert_eq!(
|
||||
room.matrix_permalink(true).await.unwrap().to_string(),
|
||||
"matrix:roomid/test_room:127.0.0.1?action=join"
|
||||
);
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
|
||||
client.sync_once(SyncSettings::new()).await.unwrap();
|
||||
let room = client.get_room(room_id).unwrap();
|
||||
|
||||
let route = room.route().await.unwrap();
|
||||
assert_eq!(route.len(), 0);
|
||||
|
||||
// With a single elligible server
|
||||
sync_index += 1;
|
||||
let res = sync_response(
|
||||
sync_index,
|
||||
&[json!({
|
||||
"content": {
|
||||
"membership": "join",
|
||||
},
|
||||
"event_id": "$151800140517rfvjc",
|
||||
"origin_server_ts": 151800140,
|
||||
"sender": "@example:localhost",
|
||||
"state_key": "@example:localhost",
|
||||
"type": "m.room.member",
|
||||
})],
|
||||
);
|
||||
let mut batch = 0;
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_state_bulk(
|
||||
bulk_room_members(batch, 0..1, "localhost", &MembershipState::Join),
|
||||
));
|
||||
let sync_token = client.sync_token().await.unwrap();
|
||||
mock_sync(&server, res, Some(sync_token.clone())).await;
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await;
|
||||
client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
room.matrix_to_permalink().await.unwrap().to_string(),
|
||||
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost"
|
||||
);
|
||||
assert_eq!(
|
||||
room.matrix_permalink(false).await.unwrap().to_string(),
|
||||
"matrix:roomid/test_room:127.0.0.1?via=localhost"
|
||||
);
|
||||
let route = room.route().await.unwrap();
|
||||
assert_eq!(route.len(), 1);
|
||||
assert_eq!(route[0], "localhost");
|
||||
|
||||
// With two elligible servers
|
||||
sync_index += 1;
|
||||
let res = sync_response(sync_index, &room_member_events(15, "notarealhs"));
|
||||
batch += 1;
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_state_bulk(
|
||||
bulk_room_members(batch, 0..15, "notarealhs", &MembershipState::Join),
|
||||
));
|
||||
let sync_token = client.sync_token().await.unwrap();
|
||||
mock_sync(&server, res, Some(sync_token.clone())).await;
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await;
|
||||
client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
room.matrix_to_permalink().await.unwrap().to_string(),
|
||||
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=localhost"
|
||||
);
|
||||
assert_eq!(
|
||||
room.matrix_permalink(false).await.unwrap().to_string(),
|
||||
"matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=localhost"
|
||||
);
|
||||
let route = room.route().await.unwrap();
|
||||
assert_eq!(route.len(), 2);
|
||||
assert_eq!(route[0], "notarealhs");
|
||||
assert_eq!(route[1], "localhost");
|
||||
|
||||
// With three elligible servers
|
||||
sync_index += 1;
|
||||
let res = sync_response(sync_index, &room_member_events(5, "mymatrix"));
|
||||
batch += 1;
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_state_bulk(
|
||||
bulk_room_members(batch, 0..5, "mymatrix", &MembershipState::Join),
|
||||
));
|
||||
let sync_token = client.sync_token().await.unwrap();
|
||||
mock_sync(&server, res, Some(sync_token.clone())).await;
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await;
|
||||
client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
room.matrix_to_permalink().await.unwrap().to_string(),
|
||||
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=mymatrix&via=localhost"
|
||||
);
|
||||
assert_eq!(
|
||||
room.matrix_permalink(false).await.unwrap().to_string(),
|
||||
"matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=mymatrix&via=localhost"
|
||||
);
|
||||
let route = room.route().await.unwrap();
|
||||
assert_eq!(route.len(), 3);
|
||||
assert_eq!(route[0], "notarealhs");
|
||||
assert_eq!(route[1], "mymatrix");
|
||||
assert_eq!(route[2], "localhost");
|
||||
|
||||
// With four elligible servers
|
||||
sync_index += 1;
|
||||
let res = sync_response(sync_index, &room_member_events(10, "yourmatrix"));
|
||||
batch += 1;
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_state_bulk(
|
||||
bulk_room_members(batch, 0..10, "yourmatrix", &MembershipState::Join),
|
||||
));
|
||||
let sync_token = client.sync_token().await.unwrap();
|
||||
mock_sync(&server, res, Some(sync_token.clone())).await;
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await;
|
||||
client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
room.matrix_to_permalink().await.unwrap().to_string(),
|
||||
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix"
|
||||
);
|
||||
assert_eq!(
|
||||
room.matrix_permalink(false).await.unwrap().to_string(),
|
||||
"matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix"
|
||||
);
|
||||
let route = room.route().await.unwrap();
|
||||
assert_eq!(route.len(), 3);
|
||||
assert_eq!(route[0], "notarealhs");
|
||||
assert_eq!(route[1], "yourmatrix");
|
||||
assert_eq!(route[2], "mymatrix");
|
||||
|
||||
// With power levels
|
||||
sync_index += 1;
|
||||
let res = sync_response(
|
||||
sync_index,
|
||||
&[json!({
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event(
|
||||
TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"users": {
|
||||
"@example:localhost": 50,
|
||||
"@user_0:localhost": 50,
|
||||
},
|
||||
},
|
||||
"event_id": "$15139375512JaHAW",
|
||||
@@ -574,30 +471,25 @@ async fn room_permalink() {
|
||||
"sender": "@creator:127.0.0.1",
|
||||
"state_key": "",
|
||||
"type": "m.room.power_levels",
|
||||
})],
|
||||
);
|
||||
})),
|
||||
));
|
||||
let sync_token = client.sync_token().await.unwrap();
|
||||
mock_sync(&server, res, Some(sync_token.clone())).await;
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await;
|
||||
client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
room.matrix_to_permalink().await.unwrap().to_string(),
|
||||
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix"
|
||||
);
|
||||
assert_eq!(
|
||||
room.matrix_permalink(false).await.unwrap().to_string(),
|
||||
"matrix:roomid/test_room:127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix"
|
||||
);
|
||||
let route = room.route().await.unwrap();
|
||||
assert_eq!(route.len(), 3);
|
||||
assert_eq!(route[0], "localhost");
|
||||
assert_eq!(route[1], "notarealhs");
|
||||
assert_eq!(route[2], "yourmatrix");
|
||||
|
||||
// With higher power levels
|
||||
sync_index += 1;
|
||||
let res = sync_response(
|
||||
sync_index,
|
||||
&[json!({
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event(
|
||||
TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"users": {
|
||||
"@example:localhost": 50,
|
||||
"@user0:mymatrix": 70,
|
||||
"@user_0:localhost": 50,
|
||||
"@user_2:mymatrix": 70,
|
||||
},
|
||||
},
|
||||
"event_id": "$15139375512JaHAZ",
|
||||
@@ -605,26 +497,21 @@ async fn room_permalink() {
|
||||
"sender": "@creator:127.0.0.1",
|
||||
"state_key": "",
|
||||
"type": "m.room.power_levels",
|
||||
})],
|
||||
);
|
||||
})),
|
||||
));
|
||||
let sync_token = client.sync_token().await.unwrap();
|
||||
mock_sync(&server, res, Some(sync_token.clone())).await;
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await;
|
||||
client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
room.matrix_to_permalink().await.unwrap().to_string(),
|
||||
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix"
|
||||
);
|
||||
assert_eq!(
|
||||
room.matrix_permalink(false).await.unwrap().to_string(),
|
||||
"matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix"
|
||||
);
|
||||
let route = room.route().await.unwrap();
|
||||
assert_eq!(route.len(), 3);
|
||||
assert_eq!(route[0], "mymatrix");
|
||||
assert_eq!(route[1], "notarealhs");
|
||||
assert_eq!(route[2], "yourmatrix");
|
||||
|
||||
// With server ACLs
|
||||
sync_index += 1;
|
||||
let res = sync_response(
|
||||
sync_index,
|
||||
&[json!({
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event(
|
||||
TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"allow": ["*"],
|
||||
"allow_ip_literals": true,
|
||||
@@ -635,38 +522,69 @@ async fn room_permalink() {
|
||||
"sender": "@creator:127.0.0.1",
|
||||
"state_key": "",
|
||||
"type": "m.room.server_acl",
|
||||
})],
|
||||
);
|
||||
})),
|
||||
));
|
||||
let sync_token = client.sync_token().await.unwrap();
|
||||
mock_sync(&server, res, Some(sync_token.clone())).await;
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await;
|
||||
client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap();
|
||||
|
||||
let route = room.route().await.unwrap();
|
||||
assert_eq!(route.len(), 3);
|
||||
assert_eq!(route[0], "mymatrix");
|
||||
assert_eq!(route[1], "yourmatrix");
|
||||
assert_eq!(route[2], "localhost");
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn room_permalink() {
|
||||
let (client, server) = logged_in_client().await;
|
||||
let mut ev_builder = EventBuilder::new();
|
||||
let room_id = room_id!("!test_room:127.0.0.1");
|
||||
|
||||
// Without aliases
|
||||
ev_builder.add_joined_room(
|
||||
JoinedRoomBuilder::new(room_id)
|
||||
.add_timeline_state_bulk(bulk_room_members(
|
||||
0,
|
||||
0..1,
|
||||
"localhost",
|
||||
&MembershipState::Join,
|
||||
))
|
||||
.add_timeline_state_bulk(bulk_room_members(
|
||||
1,
|
||||
0..5,
|
||||
"notarealhs",
|
||||
&MembershipState::Join,
|
||||
)),
|
||||
);
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
|
||||
client.sync_once(SyncSettings::new()).await.unwrap();
|
||||
let room = client.get_room(room_id).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
room.matrix_to_permalink().await.unwrap().to_string(),
|
||||
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost"
|
||||
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=localhost"
|
||||
);
|
||||
assert_eq!(
|
||||
room.matrix_permalink(false).await.unwrap().to_string(),
|
||||
"matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost"
|
||||
"matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=localhost"
|
||||
);
|
||||
|
||||
// With an alternative alias
|
||||
sync_index += 1;
|
||||
let res = sync_response(
|
||||
sync_index,
|
||||
&[json!({
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event(
|
||||
TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"alt_aliases": ["#alias:localhost"],
|
||||
},
|
||||
"event_id": "$15139375513VdeRF",
|
||||
"origin_server_ts": 151393755,
|
||||
"sender": "@example:localhost",
|
||||
"sender": "@user_0:localhost",
|
||||
"state_key": "",
|
||||
"type": "m.room.canonical_alias",
|
||||
})],
|
||||
);
|
||||
})),
|
||||
));
|
||||
let sync_token = client.sync_token().await.unwrap();
|
||||
mock_sync(&server, res, Some(sync_token.clone())).await;
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await;
|
||||
client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
@@ -676,23 +594,21 @@ async fn room_permalink() {
|
||||
assert_eq!(room.matrix_permalink(false).await.unwrap().to_string(), "matrix:r/alias:localhost");
|
||||
|
||||
// With a canonical alias
|
||||
sync_index += 1;
|
||||
let res = sync_response(
|
||||
sync_index,
|
||||
&[json!({
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event(
|
||||
TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"alias": "#canonical:localhost",
|
||||
"alt_aliases": ["#alias:localhost"],
|
||||
},
|
||||
"event_id": "$15139375513VdeRF",
|
||||
"origin_server_ts": 151393755,
|
||||
"sender": "@example:localhost",
|
||||
"sender": "@user_0:localhost",
|
||||
"state_key": "",
|
||||
"type": "m.room.canonical_alias",
|
||||
})],
|
||||
);
|
||||
})),
|
||||
));
|
||||
let sync_token = client.sync_token().await.unwrap();
|
||||
mock_sync(&server, res, Some(sync_token.clone())).await;
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await;
|
||||
client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
@@ -707,14 +623,68 @@ async fn room_permalink() {
|
||||
room.matrix_permalink(true).await.unwrap().to_string(),
|
||||
"matrix:r/canonical:localhost?action=join"
|
||||
);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn room_event_permalink() {
|
||||
let (client, server) = logged_in_client().await;
|
||||
let mut ev_builder = EventBuilder::new();
|
||||
let room_id = room_id!("!test_room:127.0.0.1");
|
||||
let event_id = event_id!("$15139375512JaHAW");
|
||||
|
||||
// Without aliases
|
||||
ev_builder.add_joined_room(
|
||||
JoinedRoomBuilder::new(room_id)
|
||||
.add_timeline_state_bulk(bulk_room_members(
|
||||
0,
|
||||
0..1,
|
||||
"localhost",
|
||||
&MembershipState::Join,
|
||||
))
|
||||
.add_timeline_state_bulk(bulk_room_members(
|
||||
1,
|
||||
0..5,
|
||||
"notarealhs",
|
||||
&MembershipState::Join,
|
||||
)),
|
||||
);
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
|
||||
client.sync_once(SyncSettings::new()).await.unwrap();
|
||||
let room = client.get_room(room_id).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
room.matrix_to_event_permalink(event_id).await.unwrap().to_string(),
|
||||
"https://matrix.to/#/%21test_room%3A127.0.0.1/%2415139375512JaHAW?via=mymatrix&via=yourmatrix&via=localhost"
|
||||
"https://matrix.to/#/%21test_room%3A127.0.0.1/%2415139375512JaHAW?via=notarealhs&via=localhost"
|
||||
);
|
||||
assert_eq!(
|
||||
room.matrix_event_permalink(event_id).await.unwrap().to_string(),
|
||||
"matrix:roomid/test_room:127.0.0.1/e/15139375512JaHAW?via=mymatrix&via=yourmatrix&via=localhost"
|
||||
"matrix:roomid/test_room:127.0.0.1/e/15139375512JaHAW?via=notarealhs&via=localhost"
|
||||
);
|
||||
|
||||
// Adding an alias doesn't change anything
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event(
|
||||
TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"alias": "#canonical:localhost",
|
||||
"alt_aliases": ["#alias:localhost"],
|
||||
},
|
||||
"event_id": "$15139375513VdeRF",
|
||||
"origin_server_ts": 151393755,
|
||||
"sender": "@user_0:localhost",
|
||||
"state_key": "",
|
||||
"type": "m.room.canonical_alias",
|
||||
})),
|
||||
));
|
||||
let sync_token = client.sync_token().await.unwrap();
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await;
|
||||
client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
room.matrix_to_event_permalink(event_id).await.unwrap().to_string(),
|
||||
"https://matrix.to/#/%21test_room%3A127.0.0.1/%2415139375512JaHAW?via=notarealhs&via=localhost"
|
||||
);
|
||||
assert_eq!(
|
||||
room.matrix_event_permalink(event_id).await.unwrap().to_string(),
|
||||
"matrix:roomid/test_room:127.0.0.1/e/15139375512JaHAW?via=notarealhs&via=localhost"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use matrix_sdk::{
|
||||
use matrix_sdk_test::{async_test, test_json};
|
||||
use ruma::{
|
||||
api::client::membership::Invite3pidInit, assign, event_id,
|
||||
events::room::message::RoomMessageEventContent, mxc_uri, room_id, thirdparty, uint, user_id,
|
||||
events::room::message::RoomMessageEventContent, mxc_uri, thirdparty, uint, user_id,
|
||||
TransactionId,
|
||||
};
|
||||
use serde_json::json;
|
||||
@@ -39,7 +39,7 @@ async fn invite_user_by_id() {
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let user = user_id!("@example:localhost");
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
room.invite_user_by_id(user).await.unwrap();
|
||||
}
|
||||
@@ -61,7 +61,7 @@ async fn invite_user_by_3pid() {
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
room.invite_user_by_3pid(
|
||||
Invite3pidInit {
|
||||
@@ -93,7 +93,7 @@ async fn leave_room() {
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
room.leave().await.unwrap();
|
||||
}
|
||||
@@ -116,7 +116,7 @@ async fn ban_user() {
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let user = user_id!("@example:localhost");
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
room.ban_user(user, None).await.unwrap();
|
||||
}
|
||||
@@ -139,7 +139,7 @@ async fn kick_user() {
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let user = user_id!("@example:localhost");
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
room.kick_user(user, None).await.unwrap();
|
||||
}
|
||||
@@ -162,7 +162,7 @@ async fn read_receipt() {
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let event_id = event_id!("$xxxxxx:example.org");
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
room.read_receipt(event_id).await.unwrap();
|
||||
}
|
||||
@@ -185,7 +185,7 @@ async fn read_marker() {
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let event_id = event_id!("$xxxxxx:example.org");
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
room.read_marker(event_id, None).await.unwrap();
|
||||
}
|
||||
@@ -207,7 +207,7 @@ async fn typing_notice() {
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
room.typing_notice(true).await.unwrap();
|
||||
}
|
||||
@@ -231,7 +231,7 @@ async fn room_state_event_send() {
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||
let room_id = &test_json::DEFAULT_SYNC_ROOM_ID;
|
||||
|
||||
let room = client.get_joined_room(room_id).unwrap();
|
||||
|
||||
@@ -260,7 +260,7 @@ async fn room_message_send() {
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
let content = RoomMessageEventContent::text_plain("Hello world");
|
||||
let txn_id = TransactionId::new();
|
||||
@@ -301,7 +301,7 @@ async fn room_attachment_send() {
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
let mut media = Cursor::new("Hello world");
|
||||
|
||||
@@ -347,7 +347,7 @@ async fn room_attachment_send_info() {
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
let mut media = Cursor::new("Hello world");
|
||||
|
||||
@@ -398,7 +398,7 @@ async fn room_attachment_send_wrong_info() {
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
let mut media = Cursor::new("Hello world");
|
||||
|
||||
@@ -457,7 +457,7 @@ async fn room_attachment_send_info_thumbnail() {
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
let mut media = Cursor::new("Hello world");
|
||||
|
||||
@@ -502,7 +502,7 @@ async fn room_redact() {
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
let event_id = event_id!("$xxxxxxxx:example.com");
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::time::Duration;
|
||||
|
||||
use matrix_sdk::config::SyncSettings;
|
||||
use matrix_sdk_test::{async_test, test_json};
|
||||
use ruma::room_id;
|
||||
use wiremock::{
|
||||
matchers::{header, method, path_regex},
|
||||
Mock, ResponseTemplate,
|
||||
@@ -27,7 +26,7 @@ async fn forget_room() {
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_left_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
let room = client.get_left_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
|
||||
room.forget().await.unwrap();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ clap = "3.2.4"
|
||||
futures = { version = "0.3.21", default-features = false, features = ["executor"] }
|
||||
matrix-sdk-base = { path = "../../crates/matrix-sdk-base", version = "0.5.0" }
|
||||
matrix-sdk-sled = { path = "../../crates/matrix-sdk-sled", version = "0.1.0" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b" }
|
||||
rustyline = "9.1.2"
|
||||
rustyline-derive = "0.6.0"
|
||||
serde = "1.0.136"
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{convert::TryFrom, fmt::Debug, sync::Arc};
|
||||
use atty::Stream;
|
||||
use clap::{Arg, ArgMatches, Command as Argparse};
|
||||
use futures::executor::block_on;
|
||||
use matrix_sdk_base::{RoomInfo, Store};
|
||||
use matrix_sdk_base::RoomInfo;
|
||||
use matrix_sdk_sled::StateStore;
|
||||
use ruma::{events::StateEventType, OwnedRoomId, OwnedUserId, RoomId};
|
||||
use rustyline::{
|
||||
@@ -26,13 +26,13 @@ use syntect::{
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Inspector {
|
||||
store: Store,
|
||||
store: Arc<StateStore>,
|
||||
printer: Printer,
|
||||
}
|
||||
|
||||
#[derive(Helper)]
|
||||
struct InspectorHelper {
|
||||
store: Store,
|
||||
store: Arc<StateStore>,
|
||||
_highlighter: MatchingBracketHighlighter,
|
||||
_validator: MatchingBracketValidator,
|
||||
_hinter: HistoryHinter,
|
||||
@@ -54,7 +54,7 @@ impl InspectorHelper {
|
||||
"m.room.topic",
|
||||
];
|
||||
|
||||
fn new(store: Store) -> Self {
|
||||
fn new(store: Arc<StateStore>) -> Self {
|
||||
Self {
|
||||
store,
|
||||
_highlighter: MatchingBracketHighlighter::new(),
|
||||
@@ -72,7 +72,9 @@ impl InspectorHelper {
|
||||
}
|
||||
|
||||
fn complete_rooms(&self, arg: Option<&&str>) -> Vec<Pair> {
|
||||
let rooms: Vec<RoomInfo> = block_on(async { self.store.get_room_infos().await.unwrap() });
|
||||
let rooms: Vec<RoomInfo> = block_on(async {
|
||||
matrix_sdk_base::StateStore::get_room_infos(&*self.store).await.unwrap()
|
||||
});
|
||||
|
||||
rooms
|
||||
.into_iter()
|
||||
@@ -200,9 +202,8 @@ impl Printer {
|
||||
impl Inspector {
|
||||
fn new(database_path: &str, json: bool, color: bool) -> Self {
|
||||
let printer = Printer::new(json, color);
|
||||
let store = Store::new(Arc::new(
|
||||
StateStore::open_with_path(database_path).expect("Can't open sled database"),
|
||||
));
|
||||
let store =
|
||||
Arc::new(StateStore::open_with_path(database_path).expect("Can't open sled database"));
|
||||
|
||||
Self { store, printer }
|
||||
}
|
||||
@@ -237,7 +238,8 @@ impl Inspector {
|
||||
}
|
||||
|
||||
async fn list_rooms(&self) {
|
||||
let rooms: Vec<RoomInfo> = self.store.get_room_infos().await.unwrap();
|
||||
let rooms: Vec<RoomInfo> =
|
||||
matrix_sdk_base::StateStore::get_room_infos(&*self.store).await.unwrap();
|
||||
self.printer.pretty_print_struct(&rooms);
|
||||
}
|
||||
|
||||
@@ -247,7 +249,8 @@ impl Inspector {
|
||||
}
|
||||
|
||||
async fn get_profiles(&self, room_id: OwnedRoomId) {
|
||||
let joined: Vec<OwnedUserId> = self.store.get_joined_user_ids(&room_id).await.unwrap();
|
||||
let joined: Vec<OwnedUserId> =
|
||||
matrix_sdk_base::StateStore::get_joined_user_ids(&*self.store, &room_id).await.unwrap();
|
||||
|
||||
for member in joined {
|
||||
let event = self.store.get_profile(&room_id, &member).await.unwrap();
|
||||
@@ -256,7 +259,8 @@ impl Inspector {
|
||||
}
|
||||
|
||||
async fn get_members(&self, room_id: OwnedRoomId) {
|
||||
let joined: Vec<OwnedUserId> = self.store.get_joined_user_ids(&room_id).await.unwrap();
|
||||
let joined: Vec<OwnedUserId> =
|
||||
matrix_sdk_base::StateStore::get_joined_user_ids(&*self.store, &room_id).await.unwrap();
|
||||
|
||||
for member in joined {
|
||||
let event = self.store.get_member_event(&room_id, &member).await.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user