Merge remote-tracking branch 'origin/main' into gnunicorn/issue756

This commit is contained in:
Benjamin Kampmann
2022-07-21 11:38:10 +02:00
94 changed files with 3754 additions and 2359 deletions

View File

@@ -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'

View File

@@ -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

View 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}}

View 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 }}

View File

@@ -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"] }

View File

@@ -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";
};

View File

@@ -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}&#10;echo $ARCHS&#10;#sh $PROJECT_DIR/debug_build_xcframework.sh &quot;$ARCHS&quot; CI&#10;">
<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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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"] }

View File

@@ -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 ."
}

View File

@@ -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.

View 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::*;

View File

@@ -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());

View File

@@ -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);
});
})

View 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();
});
}
});

View File

@@ -4,3 +4,4 @@
/index.d.ts
/matrix-sdk-crypto.*.node
/docs/*
*.tgz

View File

@@ -0,0 +1,8 @@
src/
tests/
Cargo.toml
build.rs
*.node
*.tgz
tsconfig.json
cliff.toml

View 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).

View File

@@ -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"

View File

@@ -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
```

View 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"

View 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}`)
}

View File

@@ -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"
}
}

View File

@@ -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(),
))
}
};

View File

@@ -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)? })
}

View File

@@ -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()
}

View File

@@ -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>>,

View File

@@ -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()

View File

@@ -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())

View File

@@ -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"

View File

@@ -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);

View File

@@ -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 })
}
}

View File

@@ -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) {

View File

@@ -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();
}

View File

@@ -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"

View File

@@ -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?;

View File

@@ -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),

View File

@@ -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;

View File

File diff suppressed because it is too large Load Diff

View 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 })
}
}

View 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 {
// Dont 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)
}
}

View File

@@ -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()
}

View File

@@ -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(&registration_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(())
}
}

View File

@@ -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"] }

View File

@@ -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);

View File

@@ -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,
};

View File

@@ -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;

View File

@@ -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();

View File

@@ -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,
};

View File

@@ -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.

View File

@@ -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]

View File

@@ -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]

View File

@@ -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
};

View File

@@ -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"

View File

@@ -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,
};

View File

@@ -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,
};

View File

@@ -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]

View File

@@ -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"

View File

@@ -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,
};

View File

@@ -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,
};

View File

@@ -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"

View File

@@ -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"

View File

@@ -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();

View 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()
})
}

View 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())
}
}

View 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())
}
}

View 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())
}
}

View 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();
}
}

View 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()
}
}

View File

@@ -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()
}

View 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"
})
});

View File

@@ -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};

View 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"
})
});

View File

@@ -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"
})
});

View File

@@ -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": {},

View File

@@ -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": {

View File

@@ -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"

View File

@@ -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)]

View File

@@ -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());

View File

@@ -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();

View File

@@ -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?;

View File

@@ -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.

View File

@@ -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,

View File

@@ -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,
};

View File

@@ -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);

View File

@@ -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]

View File

@@ -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"
);
}

View File

@@ -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");

View File

@@ -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();
}

View File

@@ -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"

View File

@@ -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();