Merge remote-tracking branch 'origin/main' into user_once_cell_for_session

This commit is contained in:
Julian Sparber
2022-05-27 15:53:22 +02:00
79 changed files with 2419 additions and 290 deletions

13
.github/workflows/audit.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: Security audit
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -40,3 +40,4 @@ jobs:
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./target/doc/
force_orphan: true

58
.github/workflows/ffi.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: FFI
on:
workflow_dispatch:
push:
branches: [main]
pull_request:
branches: [main]
types:
- opened
- reopened
- synchronize
- ready_for_review
env:
CARGO_TERM_COLOR: always
jobs:
test:
name: Run Apple platform tests
runs-on: macos-12
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Install targets
run: |
rustup target add aarch64-apple-ios-sim
rustup target add x86_64-apple-ios
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install Uniffi
uses: actions-rs/cargo@v1
with:
command: install
args: uniffi_bindgen
- name: Generate .xcframework
run: sh bindings/apple/debug_build_xcframework.sh ci
- name: Run XCTests
run: |
xcodebuild test \
-project bindings/apple/MatrixRustSDK.xcodeproj \
-scheme MatrixRustSDK \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 13,OS=15.4'

4
.gitignore vendored
View File

@@ -1,6 +1,10 @@
Cargo.lock
target
generated
master.zip
emsdk-*
.idea/
## User settings
xcuserdata/
.vscode/

View File

@@ -0,0 +1,513 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
181AA19B27B52AB40005F102 /* MatrixSDKFFI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */; };
181AA19C27B52AB40005F102 /* MatrixSDKFFI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
189A89BA27B40BBF0048B0A5 /* sdk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189A89B927B40BBF0048B0A5 /* sdk.swift */; };
18CE89D827B2939900CA89E1 /* MatrixRustSDKApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */; };
18CE89DA27B2939900CA89E1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89D927B2939900CA89E1 /* ContentView.swift */; };
18CE89DC27B2939A00CA89E1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 18CE89DB27B2939A00CA89E1 /* Assets.xcassets */; };
18CE89E927B2939A00CA89E1 /* MatrixRustSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
18CE89E527B2939A00CA89E1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 18CE89CC27B2939900CA89E1 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 18CE89D327B2939900CA89E1;
remoteInfo = MatrixRustSDK;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
18CE8A1F27B2941600CA89E1 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
181AA19C27B52AB40005F102 /* MatrixSDKFFI.xcframework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
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>"; };
18CE89DB27B2939A00CA89E1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatrixRustSDKTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixRustSDKTests.swift; sourceTree = "<group>"; };
18CE8A0127B293A900CA89E1 /* MatrixRustSDK.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MatrixRustSDK.entitlements; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
18CE89D127B2939900CA89E1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
181AA19B27B52AB40005F102 /* MatrixSDKFFI.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
18CE89E127B2939A00CA89E1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
189A89AB27B2E16B0048B0A5 /* Frameworks */ = {
isa = PBXGroup;
children = (
181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */,
);
name = Frameworks;
sourceTree = "<group>";
};
189A89B827B40BB10048B0A5 /* Generated */ = {
isa = PBXGroup;
children = (
189A89B927B40BBF0048B0A5 /* sdk.swift */,
);
name = Generated;
sourceTree = "<group>";
};
18CE89CB27B2939900CA89E1 = {
isa = PBXGroup;
children = (
18CE89D627B2939900CA89E1 /* MatrixRustSDK */,
18CE89E727B2939A00CA89E1 /* MatrixRustSDKTests */,
18CE89D527B2939900CA89E1 /* Products */,
189A89AB27B2E16B0048B0A5 /* Frameworks */,
);
sourceTree = "<group>";
};
18CE89D527B2939900CA89E1 /* Products */ = {
isa = PBXGroup;
children = (
18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */,
18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
18CE89D627B2939900CA89E1 /* MatrixRustSDK */ = {
isa = PBXGroup;
children = (
181AA19927B52AA60005F102 /* Info.plist */,
189A89B827B40BB10048B0A5 /* Generated */,
18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */,
18CE89D927B2939900CA89E1 /* ContentView.swift */,
18CE8A0127B293A900CA89E1 /* MatrixRustSDK.entitlements */,
18CE89DB27B2939A00CA89E1 /* Assets.xcassets */,
189A89C327B417CA0048B0A5 /* MatrixRustSDK-Bridging-Header.h */,
);
path = MatrixRustSDK;
sourceTree = "<group>";
};
18CE89E727B2939A00CA89E1 /* MatrixRustSDKTests */ = {
isa = PBXGroup;
children = (
18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */,
);
path = MatrixRustSDKTests;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
18CE89D327B2939900CA89E1 /* MatrixRustSDK */ = {
isa = PBXNativeTarget;
buildConfigurationList = 18CE89F827B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDK" */;
buildPhases = (
18CE89D027B2939900CA89E1 /* Sources */,
18CE89D127B2939900CA89E1 /* Frameworks */,
18CE89D227B2939900CA89E1 /* Resources */,
18CE8A1F27B2941600CA89E1 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = MatrixRustSDK;
productName = MatrixRustSDK;
productReference = 18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */;
productType = "com.apple.product-type.application";
};
18CE89E327B2939A00CA89E1 /* MatrixRustSDKTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 18CE89FB27B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDKTests" */;
buildPhases = (
18CE89E027B2939A00CA89E1 /* Sources */,
18CE89E127B2939A00CA89E1 /* Frameworks */,
18CE89E227B2939A00CA89E1 /* Resources */,
);
buildRules = (
);
dependencies = (
18CE89E627B2939A00CA89E1 /* PBXTargetDependency */,
);
name = MatrixRustSDKTests;
productName = MatrixRustSDKTests;
productReference = 18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
18CE89CC27B2939900CA89E1 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1320;
LastUpgradeCheck = 1320;
TargetAttributes = {
18CE89D327B2939900CA89E1 = {
CreatedOnToolsVersion = 13.2.1;
LastSwiftMigration = 1320;
};
18CE89E327B2939A00CA89E1 = {
CreatedOnToolsVersion = 13.2.1;
TestTargetID = 18CE89D327B2939900CA89E1;
};
};
};
buildConfigurationList = 18CE89CF27B2939900CA89E1 /* Build configuration list for PBXProject "MatrixRustSDK" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 18CE89CB27B2939900CA89E1;
productRefGroup = 18CE89D527B2939900CA89E1 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
18CE89D327B2939900CA89E1 /* MatrixRustSDK */,
18CE89E327B2939A00CA89E1 /* MatrixRustSDKTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
18CE89D227B2939900CA89E1 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
18CE89DC27B2939A00CA89E1 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
18CE89E227B2939A00CA89E1 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
18CE89D027B2939900CA89E1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
18CE89DA27B2939900CA89E1 /* ContentView.swift in Sources */,
189A89BA27B40BBF0048B0A5 /* sdk.swift in Sources */,
18CE89D827B2939900CA89E1 /* MatrixRustSDKApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
18CE89E027B2939A00CA89E1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
18CE89E927B2939A00CA89E1 /* MatrixRustSDKTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
18CE89E627B2939A00CA89E1 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 18CE89D327B2939900CA89E1 /* MatrixRustSDK */;
targetProxy = 18CE89E527B2939A00CA89E1 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
18CE89F627B2939A00CA89E1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
18CE89F727B2939A00CA89E1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
18CE89F927B2939A00CA89E1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MatrixRustSDK/MatrixRustSDK.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MatrixRustSDK/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDK;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
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";
};
name = Debug;
};
18CE89FA27B2939A00CA89E1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MatrixRustSDK/MatrixRustSDK.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MatrixRustSDK/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDK;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MatrixRustSDK/MatrixRustSDK-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
18CE89FC27B2939A00CA89E1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDKTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatrixRustSDK.app/MatrixRustSDK";
};
name = Debug;
};
18CE89FD27B2939A00CA89E1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDKTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatrixRustSDK.app/MatrixRustSDK";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
18CE89CF27B2939900CA89E1 /* Build configuration list for PBXProject "MatrixRustSDK" */ = {
isa = XCConfigurationList;
buildConfigurations = (
18CE89F627B2939A00CA89E1 /* Debug */,
18CE89F727B2939A00CA89E1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
18CE89F827B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDK" */ = {
isa = XCConfigurationList;
buildConfigurations = (
18CE89F927B2939A00CA89E1 /* Debug */,
18CE89FA27B2939A00CA89E1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
18CE89FB27B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDKTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
18CE89FC27B2939A00CA89E1 /* Debug */,
18CE89FD27B2939A00CA89E1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 18CE89CC27B2939900CA89E1 /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
BuildableName = "MatrixRustSDK.app"
BlueprintName = "MatrixRustSDK"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89E327B2939A00CA89E1"
BuildableName = "MatrixRustSDKTests.xctest"
BlueprintName = "MatrixRustSDKTests"
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
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
BuildableName = "MatrixRustSDK.app"
BlueprintName = "MatrixRustSDK"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
BuildableName = "MatrixRustSDK.app"
BlueprintName = "MatrixRustSDK"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,21 @@
//
// ContentView.swift
// MatrixRustSDK
//
// Created by Stefan Ceriu on 08.02.2022.
//
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, Rust!")
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

View File

@@ -0,0 +1,5 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "sdkFFI.h"

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,17 @@
//
// MatrixRustSDKApp.swift
// MatrixRustSDK
//
// Created by Stefan Ceriu on 08.02.2022.
//
import SwiftUI
@main
struct MatrixRustSDKApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

View File

@@ -0,0 +1,48 @@
//
// MatrixRustSDKTests.swift
// MatrixRustSDKTests
//
// Created by Stefan Ceriu on 08.02.2022.
//
import XCTest
@testable import MatrixRustSDK
class MatrixRustSDKTests: XCTestCase {
static var client: Client!
override class func setUp() {
client = try! guestClient(basePath: basePath, homeserver: "https://matrix.org")
}
func testClientProperties() {
XCTAssertTrue(Self.client.isGuest())
XCTAssertNotNil(try? Self.client.restoreToken())
XCTAssertNotNil(try? Self.client.deviceId())
XCTAssertNotNil(try? Self.client.displayName())
}
func testReadOnlyFileSystemError() {
do {
let _ = try loginNewClient(basePath: "", username: "test", password: "test")
} catch ClientError.Generic(let message) {
XCTAssertNotNil(message.range(of: "Read-only file system"))
} catch {
XCTFail("Not expecting any other kind of exception")
}
}
// MARK: - Private
static private var basePath: String {
guard let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
fatalError("Should always be able to retrieve the caches directory")
}
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
return url.path
}
}

39
bindings/apple/README.md Normal file
View File

@@ -0,0 +1,39 @@
# Apple platforms support
This project and build script demonstrate how to create an XCFramework that can be imported into an Xcode project and run on Apple platforms.
## Building the universal framework
```
sh build_xcframework.sh
```
**Prerequisites**
* the Rust toolchain
* UniFFI - `cargo install uniffi_bindge`
* Apple targets (e.g. `rustup target add aarch64-apple-ios`)
* `xcodebuild` command line tool from [Apple](https://developer.apple.com/library/archive/technotes/tn2339/_index.html)
* `lipo` for creating the fat static libs
The `build_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`:
1. compile `matrix-sdk-ffi` libraries for iOS, the iOS simulator, MacOS, and Mac Catalyst under `/target`. Some targets are not part of the standard library and they will be built using the nightly toolchain.
* `lipo` together the libraries for the same platform under `/generated`
* run `uniffi` and generate the C header, module map and swift files
* `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKFFI.xcframework`
* cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework)
## Running the Xcode project
The Xcode project is meant to provide a simple example on how to integrate everything together but also a place to run unit and integration tests from.
It's pre-configured to link to the generated .xcframework and .swift files so successfully running the script first is necessary for it to compile.
It makes the compiled code available to swift by importing the C header through its bridging header.
Once all the generated components are available running it should be as easy as choosing a platform and clicking run.
## Distribution
The generated framework and Swift code can be distributed and integrated directly but in order to make things simpler we bundle them together as a Swift package available [TBD](here)

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env bash
set -eEu
cd "$(dirname "$0")"
# Path to the repo root
SRC_ROOT=../..
TARGET_DIR="${SRC_ROOT}/target"
GENERATED_DIR="${SRC_ROOT}/generated"
mkdir -p ${GENERATED_DIR}
REL_FLAG="--release"
REL_TYPE_DIR="release"
# Build static libs for all the different architectures
# iOS
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios"
# MacOS
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-darwin"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-darwin"
# iOS Simulator
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
lipo -create \
"${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
"${TARGET_DIR}/aarch64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a"
# iOS Simulator
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"
# 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}/crates/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR}
# Move them to the right place
HEADERS_DIR=${GENERATED_DIR}/headers
mkdir -p ${HEADERS_DIR}
mv ${GENERATED_DIR}/*.h ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}
SWIFT_DIR="${GENERATED_DIR}/swift"
mkdir -p ${SWIFT_DIR}
mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
# Build the xcframework
if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi
xcodebuild -create-xcframework \
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" \
-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"
# Cleanup
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

@@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -eEu
cd "$(dirname "$0")"
IS_CI=false
if [ $# -eq 1 ]; then
IS_CI=true
echo "Running CI build"
else
echo "Running debug build"
fi
# Path to the repo root
SRC_ROOT=../..
TARGET_DIR="${SRC_ROOT}/target"
GENERATED_DIR="${SRC_ROOT}/generated"
mkdir -p ${GENERATED_DIR}
# Release for now. Debug builds cause crashes deep inside the Tokio runtime.
REL_FLAG="--release"
REL_TYPE_DIR="release"
# iOS Simulator
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"
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"
# Generate uniffi files
uniffi-bindgen generate "${SRC_ROOT}/crates/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR}
# Move them to the right place
HEADERS_DIR=${GENERATED_DIR}/headers
mkdir -p ${HEADERS_DIR}
mv ${GENERATED_DIR}/*.h ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}
SWIFT_DIR="${GENERATED_DIR}/swift"
mkdir -p ${SWIFT_DIR}
mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
# Build the xcframework
if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi
xcodebuild -create-xcframework \
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \
-headers ${HEADERS_DIR} \
-output "${GENERATED_DIR}/MatrixSDKFFI.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 [ "$IS_CI" = false ] ; then
echo "Preparing matrix-rust-components-swift"
# Debug -> Copy generated files over to ../../../matrix-rust-components-swift
echo "$(echo "import MatrixSDKFFIWrapper\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

@@ -2,15 +2,34 @@ coverage:
status:
project:
default:
# Commits pushed to master should not make the overall
# project coverage decrease by more than 1%:
target: auto
threshold: 1%
patch:
default:
# Be tolerant on slight code coverage diff on PRs to limit
# noisy red coverage status on github PRs.
# Note: The coverage stats are still uploaded
# to codecov so that PR reviewers can see uncovered lines
# by default, we only care about test coverage of the main
# rust crates
target: auto
threshold: 1%
paths:
- "crates/matrix-sdk/"
- "crates/matrix-sdk-appservice/"
- "crates/matrix-sdk-base/"
- "crates/matrix-sdk-common/"
- "crates/matrix-sdk-crypto/"
- "crates/matrix-sdk-qrcode/"
- "crates/matrix-sdk-sled/"
- "crates/matrix-sdk-store-encryption/"
# Coverage of wasm tests isn't supported at the moment,
# see rustwasm/wasm-bindgen#2276
# - "crates/matrix-sdk-indexeddb"
bindings:
# Coverage of binding tests is recorded but for informational
# purposes only
informational: true
paths:
- "bindings/"
- "crates/matrix-sdk-crypto-ffi/"
- "crates/matrix-sdk-ffi/"
labs:
# Coverage of lab tests is recorded but for informational
# purposes only
informational: true
paths:
- "labs/"
patch: off

View File

@@ -8,9 +8,14 @@ use matrix_sdk_appservice::{
events::room::member::{MembershipState, OriginalSyncRoomMemberEvent},
UserId,
},
HttpError,
},
AppService, AppServiceRegistration, Result,
};
use ruma::api::{
client::{error::ErrorKind, uiaa::UiaaResponse},
error::{FromHttpResponseError, ServerError},
};
use tracing::trace;
pub async fn handle_room_member(
@@ -22,7 +27,9 @@ 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())?;
appservice.register_virtual_user(user_id.localpart()).await?;
if let Err(error) = appservice.register_virtual_user(user_id.localpart()).await {
error_if_user_not_in_use(error)?;
}
let client = appservice.virtual_user_client(user_id.localpart()).await?;
client.join_room_by_id(room.room_id()).await?;
@@ -31,6 +38,17 @@ pub async fn handle_room_member(
Ok(())
}
pub fn error_if_user_not_in_use(error: matrix_sdk_appservice::Error) -> Result<()> {
match error {
// If user is already in use that's OK.
matrix_sdk_appservice::Error::Matrix(matrix_sdk::Error::Http(HttpError::UiaaError(
FromHttpResponseError::Server(ServerError::Known(UiaaResponse::MatrixError(error))),
))) if matches!(error.kind, ErrorKind::UserInUse) => Ok(()),
// In all other cases return with an error.
error => Err(error),
}
}
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
env::set_var("RUST_LOG", "matrix_sdk=debug,matrix_sdk_appservice=debug");
@@ -41,6 +59,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let registration = AppServiceRegistration::try_from_yaml_file("./tests/registration.yaml")?;
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())?
.register_event_handler(

View File

@@ -1043,7 +1043,7 @@ impl BaseClient {
content: impl MessageLikeEventContent,
) -> Result<RoomEncryptedEventContent> {
match self.olm_machine() {
Some(o) => Ok(o.encrypt(room_id, content).await?),
Some(o) => Ok(o.encrypt_room_event(room_id, content).await?),
None => panic!("Olm machine wasn't started"),
}
}

View File

@@ -1,7 +1,7 @@
mod members;
mod normal;
use std::{cmp::max, collections::HashSet, fmt};
use std::{collections::HashSet, fmt};
pub use members::RoomMember;
pub use normal::{Room, RoomInfo, RoomType};
@@ -143,11 +143,7 @@ impl BaseRoomInfo {
self.tombstone = Some(t.into());
}
AnySyncStateEvent::RoomPowerLevels(p) => {
self.max_power_level = p
.power_levels()
.users
.values()
.fold(self.max_power_level, |acc, &p| max(acc, p.into()));
self.max_power_level = p.power_levels().max().into();
}
_ => return false,
}
@@ -192,11 +188,7 @@ impl BaseRoomInfo {
self.tombstone = Some(t.into());
}
AnyStrippedStateEvent::RoomPowerLevels(p) => {
self.max_power_level = p
.content
.users
.values()
.fold(self.max_power_level, |acc, &p| max(acc, p.into()));
self.max_power_level = p.power_levels().max().into();
}
_ => return false,
}

View File

@@ -177,7 +177,13 @@ impl Room {
/// Get the avatar url of this room.
pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
self.inner.read().unwrap().base_info.avatar.as_ref()?.as_original()?.content.url.clone()
self.inner
.read()
.unwrap()
.base_info
.avatar
.as_ref()
.and_then(|e| e.as_original().and_then(|e| e.content.url.clone()))
}
/// Get the canonical alias of this room.
@@ -194,7 +200,13 @@ impl Room {
/// It can also be redacted in current room versions, leaving only the
/// `creator` field.
pub fn create_content(&self) -> Option<RoomCreateEventContent> {
Some(self.inner.read().unwrap().base_info.create.as_ref()?.as_original()?.content.clone())
self.inner
.read()
.unwrap()
.base_info
.create
.as_ref()
.and_then(|e| e.as_original().map(|e| e.content.clone()))
}
/// Is this room considered a direct message.

View File

@@ -10,8 +10,8 @@ license = "Apache-2.0"
publish = false
[lib]
crate-type = ["cdylib", "lib"]
name = "matrix_crypto"
crate-type = ["cdylib", "staticlib"]
name = "matrix_crypto_ffi"
[dependencies]
anyhow = "1.0.57"
@@ -55,7 +55,8 @@ default_features = false
features = ["rt-multi-thread"]
[dependencies.vodozemac]
version = "0.2.0"
git = "https://github.com/matrix-org/vodozemac/"
rev = "d0e744287a14319c2a9148fef3747548c740fc36"
[build-dependencies]
uniffi_build = { version = "0.17.0", features = ["builtin-bindgen"] }

View File

@@ -48,7 +48,7 @@ pub struct MigrationData {
/// The list of Megolm inbound group sessions.
inbound_group_sessions: Vec<PickledInboundGroupSession>,
/// The Olm pickle key that was used to pickle all the Olm objects.
pickle_key: String,
pickle_key: Vec<u8>,
/// The backup version that is currently active.
backup_version: Option<String>,
// The backup recovery key, as a base58 encoded string.
@@ -521,7 +521,9 @@ mod test {
"backed_up":true
}
],
"pickle_key":"\u{0011}$xJ_N8$>{\u{0005}iJoF03eBVt\u{000e}rUU\\,GYc7J",
"pickle_key": [17, 36, 120, 74, 95, 78, 56, 36, 62, 123, 5, 105, 74,
111, 70, 48, 51, 101, 66, 86, 116, 14, 114, 85, 85,
92, 44, 71, 89, 99, 55, 74],
"backup_version":"3",
"backup_recovery_key":"EsTHScmRV5oT1WBhe2mj2Gn3odeYantZ4NEk7L51p6L8hrmB",
"cross_signing":{

View File

@@ -33,7 +33,7 @@ use ruma::{
},
events::{
key::verification::VerificationMethod, room::encrypted::OriginalSyncRoomEncryptedEvent,
AnyMessageLikeEventContent, AnySyncMessageLikeEvent, EventContent,
AnySyncMessageLikeEvent,
},
DeviceKeyAlgorithm, EventId, OwnedTransactionId, OwnedUserId, RoomId, UserId,
};
@@ -519,12 +519,11 @@ impl OlmMachine {
content: &str,
) -> Result<String, CryptoStoreError> {
let room_id = RoomId::parse(room_id)?;
let content: Box<RawValue> = serde_json::from_str(content)?;
let content: Value = serde_json::from_str(content)?;
let content = AnyMessageLikeEventContent::from_parts(event_type, &content)?;
let encrypted_content = self
.runtime
.block_on(self.inner.encrypt(&room_id, content))
.block_on(self.inner.encrypt_room_event_raw(&room_id, content, event_type))
.expect("Encrypting an event produced an error");
Ok(serde_json::to_string(&encrypted_content)?)
@@ -1331,7 +1330,7 @@ impl OlmMachine {
Ok(self.runtime.block_on(self.inner.backup_machine().room_key_counts())?.into())
}
/// Store the recovery key in the cryptostore.
/// Store the recovery key in the crypto store.
///
/// This is useful if the client wants to support gossiping of the backup
/// key.

View File

@@ -451,7 +451,7 @@ dictionary MigrationData {
sequence<PickledInboundGroupSession> inbound_group_sessions;
string? backup_version;
string? backup_recovery_key;
string pickle_key;
sequence<u8> pickle_key;
CrossSigningKeyExport cross_signing;
sequence<string> tracked_users;
};

View File

@@ -53,7 +53,8 @@ version = "0.6.1"
features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"]
[target.'cfg(target_arch = "wasm32")'.dependencies.vodozemac]
version = "0.2.0"
git = "https://github.com/matrix-org/vodozemac/"
rev = "d0e744287a14319c2a9148fef3747548c740fc36"
features = ["js"]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.ruma]
@@ -61,7 +62,8 @@ version = "0.6.1"
features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.vodozemac]
version = "0.2.0"
git = "https://github.com/matrix-org/vodozemac/"
rev = "d0e744287a14319c2a9148fef3747548c740fc36"
[dev-dependencies]
futures = { version = "0.3.21", default-features = false, features = ["executor"] }

View File

@@ -220,7 +220,7 @@ impl BackupMachine {
Ok(())
}
/// Store the recovery key in the cryptostore.
/// Store the recovery key in the crypto store.
///
/// This is useful if the client wants to support gossiping of the backup
/// key.

View File

@@ -43,7 +43,7 @@ use crate::{
};
/// An error describing why a key share request won't be honored.
#[derive(Debug, Clone, Error, PartialEq)]
#[derive(Debug, Clone, Error, PartialEq, Eq)]
pub enum KeyForwardDecision {
/// The key request is from a device that we don't own, we're only sharing
/// sessions that we know the requesting device already was supposed to get.

View File

@@ -348,7 +348,7 @@ impl UserDevices {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
/// The local trust state of a device.
pub enum LocalTrust {
/// The device has been verified and is trusted.
@@ -456,8 +456,8 @@ impl ReadOnlyDevice {
/// Set the trust state of the device to the given state.
///
/// Note: This should only done in the cryptostore where the trust state can
/// be stored.
/// Note: This should only done in the crypto store where the trust state
/// can be stored.
pub(crate) fn set_trust_state(&self, state: LocalTrust) {
self.trust_state.store(state, Ordering::Relaxed)
}

View File

@@ -45,7 +45,7 @@ use std::collections::{BTreeMap, BTreeSet};
use ruma::OwnedRoomId;
/// Return type for the room key importing.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RoomKeyImportResult {
/// The number of room keys that were imported.
pub imported_count: usize,

View File

@@ -648,21 +648,23 @@ impl OlmMachine {
/// # Panics
///
/// Panics if a group session for the given room wasn't shared beforehand.
pub async fn encrypt(
///
/// [`share_group_session`]: Self::share_group_session
pub async fn encrypt_room_event(
&self,
room_id: &RoomId,
content: impl MessageLikeEventContent,
) -> MegolmResult<RoomEncryptedEventContent> {
let event_type = content.event_type().to_string();
let content = serde_json::to_value(&content)?;
self.group_session_manager.encrypt(room_id, content, &event_type).await
self.encrypt_room_event_raw(room_id, content, &event_type).await
}
/// Encrypt a json [`Value`] content for the given room.
///
/// This method is equivalent to the [`encrypt()`] method but operates on an
/// arbitrary JSON value instead of strongly-typed event content struct.
/// This method is equivalent to the [`OlmMachine::encrypt_room_event()`]
/// method but operates on an arbitrary JSON value instead of strongly-typed
/// event content struct.
///
/// # Arguments
///
@@ -677,9 +679,7 @@ impl OlmMachine {
/// # Panics
///
/// Panics if a group session for the given room wasn't shared beforehand.
///
/// [`encrypt()`]: #method.encrypt
pub async fn encrypt_raw(
pub async fn encrypt_room_event_raw(
&self,
room_id: &RoomId,
content: Value,
@@ -1954,7 +1954,7 @@ pub(crate) mod tests {
let content = RoomMessageEventContent::text_plain(plaintext);
let encrypted_content = alice
.encrypt(room_id, AnyMessageLikeEventContent::RoomMessage(content.clone()))
.encrypt_room_event(room_id, AnyMessageLikeEventContent::RoomMessage(content.clone()))
.await
.unwrap();

View File

@@ -35,7 +35,7 @@ use ruma::{
},
AnyToDeviceEvent, OlmV1Keys,
},
serde::{CanonicalJsonValue, Raw},
serde::Raw,
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, OwnedDeviceId,
OwnedDeviceKeyId, OwnedUserId, RoomId, SecondsSinceUnixEpoch, UInt, UserId,
};
@@ -49,8 +49,8 @@ use vodozemac::{
};
use super::{
EncryptionSettings, InboundGroupSession, OutboundGroupSession, PrivateCrossSigningIdentity,
Session,
utility::SignJson, EncryptionSettings, InboundGroupSession, OutboundGroupSession,
PrivateCrossSigningIdentity, Session,
};
use crate::{
error::{EventError, OlmResult, SessionCreationError},
@@ -752,10 +752,14 @@ impl ReadOnlyAccount {
// get signed.
let json_device_keys =
serde_json::to_value(&device_keys).expect("device key is always safe to serialize");
let signature = self
.sign_json(json_device_keys)
.await
.expect("Newly created device keys can always be signed");
device_keys.signatures.entry(self.user_id().to_owned()).or_default().insert(
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &self.device_id),
self.sign_json(json_device_keys).await.to_base64(),
signature.to_base64(),
);
device_keys
@@ -773,7 +777,7 @@ impl ReadOnlyAccount {
&self,
cross_signing_key: &mut CrossSigningKey,
) -> Result<(), SignatureError> {
let signature = self.sign_json(serde_json::to_value(&cross_signing_key)?).await;
let signature = self.sign_json(serde_json::to_value(&cross_signing_key)?).await?;
cross_signing_key.signatures.entry(self.user_id().to_owned()).or_default().insert(
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id()),
@@ -809,19 +813,8 @@ impl ReadOnlyAccount {
///
/// * `json` - The value that should be converted into a canonical JSON
/// string.
///
/// # Panic
///
/// Panics if the json value can't be serialized.
pub async fn sign_json(&self, mut json: Value) -> Ed25519Signature {
let object = json.as_object_mut().expect("Canonical json value isn't an object");
object.remove("unsigned");
object.remove("signatures");
let canonical_json: CanonicalJsonValue =
json.try_into().expect("Can't canonicalize the json value");
self.sign(&canonical_json.to_string()).await
pub async fn sign_json(&self, json: Value) -> Result<Ed25519Signature, SignatureError> {
self.inner.lock().await.sign_json(json)
}
/// Generate, sign and prepare one-time keys to be uploaded.
@@ -885,8 +878,10 @@ impl ReadOnlyAccount {
SignedKey::new(key.to_owned())
};
let signature =
self.sign_json(serde_json::to_value(&key).expect("Can't serialize a signed key")).await;
let signature = self
.sign_json(serde_json::to_value(&key).expect("Can't serialize a signed key"))
.await
.expect("Newly created one-time keys can always be signed");
let signatures = BTreeMap::from([(
self.user_id().to_owned(),
@@ -1012,8 +1007,7 @@ impl ReadOnlyAccount {
message: &PreKeyMessage,
) -> Result<InboundCreationResult, SessionCreationError> {
let their_identity_key = Curve25519PublicKey::from_base64(their_identity_key)?;
let result =
self.inner.lock().await.create_inbound_session(&their_identity_key, message)?;
let result = self.inner.lock().await.create_inbound_session(their_identity_key, message)?;
let now = SecondsSinceUnixEpoch::now();
let session_id = result.session.session_id();

View File

@@ -651,13 +651,16 @@ impl PrivateCrossSigningIdentity {
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use matrix_sdk_test::async_test;
use ruma::{device_id, user_id, UserId};
use ruma::{device_id, user_id, DeviceKeyAlgorithm, DeviceKeyId, UserId};
use serde_json::json;
use super::{PrivateCrossSigningIdentity, Signing};
use crate::{
identities::{ReadOnlyDevice, ReadOnlyUserIdentity},
olm::ReadOnlyAccount,
olm::{utility::SignJson, ReadOnlyAccount},
};
fn user_id() -> &'static UserId {
@@ -667,11 +670,25 @@ mod tests {
#[test]
fn signature_verification() {
let signing = Signing::new();
let user_id = user_id();
let key_id = DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, "DEVICEID".into());
let message = "Hello world";
let json = json!({
"hello": "world"
});
let signature = signing.sign(message);
assert!(signing.verify(message, &signature).is_ok());
let signature =
signing.sign_json(json).expect("We should be able to sign a simple json object");
let signatures =
BTreeMap::from([(user_id, BTreeMap::from([(key_id.clone(), signature.to_base64())]))]);
let mut json = json!({
"hello": "world",
"signatures": signatures,
});
assert!(signing.verify_json(user_id, &key_id, &mut json).is_ok());
}
#[test]

View File

@@ -12,11 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{collections::BTreeMap, convert::TryInto};
use std::collections::BTreeMap;
use ruma::{
encryption::KeyUsage, serde::CanonicalJsonValue, DeviceKeyAlgorithm, DeviceKeyId, OwnedUserId,
};
use ruma::{encryption::KeyUsage, DeviceKeyAlgorithm, DeviceKeyId, OwnedUserId};
use serde::{Deserialize, Serialize};
use serde_json::{Error as JsonError, Value};
use thiserror::Error;
@@ -25,6 +23,7 @@ use vodozemac::{Ed25519PublicKey, Ed25519SecretKey, Ed25519Signature, KeyError};
use crate::{
error::SignatureError,
identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey},
olm::utility::SignJson,
types::{CrossSigningKey, CrossSigningKeySignatures, DeviceKeys},
utilities::{encode, DecodeError},
ReadOnlyUserIdentity,
@@ -64,6 +63,12 @@ impl PartialEq for Signing {
}
}
impl SignJson for Signing {
fn sign_json(&self, value: Value) -> Result<Ed25519Signature, SignatureError> {
self.inner.sign_json(value)
}
}
#[derive(PartialEq, Debug)]
pub struct MasterSigning {
pub inner: Signing,
@@ -215,12 +220,9 @@ impl SelfSigning {
Ok(Self { inner, public_key })
}
pub fn sign_device_helper(&self, value: Value) -> Result<Ed25519Signature, SignatureError> {
self.inner.sign_json(value)
}
pub fn sign_device(&self, device_keys: &mut DeviceKeys) -> Result<(), SignatureError> {
let signature = self.sign_device_helper(serde_json::to_value(&device_keys)?)?;
let serialized = serde_json::to_value(&device_keys)?;
let signature = self.inner.sign_json(serialized)?;
device_keys.signatures.entry(self.public_key.user_id().to_owned()).or_default().insert(
DeviceKeyId::from_parts(
@@ -311,27 +313,18 @@ impl Signing {
CrossSigningKey::new(user_id, vec![usage], keys, BTreeMap::new())
}
#[cfg(test)]
pub fn verify(
&self,
message: &str,
signature: &Ed25519Signature,
) -> Result<(), SignatureError> {
Ok(self.public_key.verify(message.as_bytes(), signature)?)
}
pub fn sign_json(&self, mut json: Value) -> Result<Ed25519Signature, SignatureError> {
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
let _ = json_object.remove("signatures");
let _ = json_object.remove("unsigned");
let canonical_json: CanonicalJsonValue =
json.try_into().expect("Can't canonicalize the json value");
Ok(self.sign(&canonical_json.to_string()))
}
pub fn sign(&self, message: &str) -> Ed25519Signature {
self.inner.sign(message.as_bytes())
}
#[cfg(test)]
pub fn verify_json(
&self,
user_id: &ruma::UserId,
key_id: &DeviceKeyId,
message: &mut Value,
) -> Result<(), SignatureError> {
use crate::olm::VerifyJson;
self.public_key.verify_json(user_id, key_id, message)
}
}

View File

@@ -16,9 +16,39 @@ use std::convert::TryInto;
use ruma::{serde::CanonicalJsonValue, DeviceKeyAlgorithm, DeviceKeyId, UserId};
use serde_json::Value;
use vodozemac::{olm::Account, Ed25519SecretKey, Ed25519Signature};
use crate::error::SignatureError;
pub trait SignJson {
fn sign_json(&self, value: Value) -> Result<Ed25519Signature, SignatureError>;
fn to_signable_json(mut value: Value) -> Result<String, SignatureError> {
let json_object = value.as_object_mut().ok_or(SignatureError::NotAnObject)?;
let _ = json_object.remove("signatures");
let _ = json_object.remove("unsigned");
let canonical_json: CanonicalJsonValue = value.try_into().unwrap();
Ok(canonical_json.to_string())
}
}
impl SignJson for Account {
fn sign_json(&self, value: Value) -> Result<Ed25519Signature, SignatureError> {
let serialized = Self::to_signable_json(value)?;
Ok(self.sign(serialized.as_ref()))
}
}
impl SignJson for Ed25519SecretKey {
fn sign_json(&self, value: Value) -> Result<Ed25519Signature, SignatureError> {
let serialized = Self::to_signable_json(value)?;
Ok(self.sign(serialized.as_ref()))
}
}
pub trait VerifyJson {
/// Verify a signed JSON object.
///

View File

@@ -640,8 +640,8 @@ impl CryptoStoreError {
}
}
/// Trait abstracting a store that the `OlmMachine` uses to store cryptographic
/// keys.
/// Represents a store that the `OlmMachine` uses to store E2EE data (such as
/// cryptographic keys).
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait CryptoStore: AsyncTraitDeps {

View File

@@ -77,7 +77,7 @@ impl CrossSigningKey {
/// Currently cross signing keys support an ed25519 keypair. The keys transport
/// format is a base64 encoded string, any unknown key type will be left as such
/// a string.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SigningKey {
/// The ed25519 cross-signing key.
Ed25519(Ed25519PublicKey),

View File

@@ -115,7 +115,7 @@ impl UnsignedDeviceInfo {
/// Currently devices have a curve25519 and ed25519 keypair. The keys transport
/// format is a base64 encoded string, any unknown key type will be left as such
/// a string.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DeviceKey {
/// The curve25519 device key.
Curve25519(Curve25519PublicKey),

View File

@@ -29,7 +29,7 @@ use vodozemac::{Curve25519PublicKey, Ed25519Signature};
pub type SignedKeySignatures = BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, Ed25519Signature>>;
/// A key for the SignedCurve25519 algorithm
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SignedKey {
// /// The Curve25519 key that can be used to establish Olm sessions.
#[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
@@ -151,7 +151,7 @@ impl SignedKey {
}
/// A one-time public key for "pre-key" messages.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum OneTimeKey {
/// A signed Curve25519 one-time key.

View File

@@ -376,7 +376,7 @@ impl Cancelled {
}
}
#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)]
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd)]
pub enum FlowId {
ToDevice(OwnedTransactionId),
InRoom(OwnedRoomId, OwnedEventId),

View File

@@ -0,0 +1,35 @@
[package]
name = "matrix-sdk-ffi"
version = "0.1.0"
edition = "2021"
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
keywords = ["matrix", "chat", "messaging", "ffi"]
license = "Apache-2.0"
readme = "README.md"
rust-version = "1.56"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
[lib]
crate-type = ["cdylib", "staticlib"]
[build-dependencies]
uniffi_build = { version = "0.18.0", features = ["builtin-bindgen"] }
[dependencies]
anyhow = "1.0.51"
extension-trait = "1.0.1"
futures-core = "0.3.17"
futures-util = { version = "0.3.17", default-features = false }
matrix-sdk = { path = "../matrix-sdk", features = ["experimental-timeline", "markdown"] }
once_cell = "1.10.0"
parking_lot = "0.12.0"
sanitize-filename-reader-friendly = "2.2.1"
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1" }
thiserror = "1.0.30"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
tokio-stream = "0.1.8"
tracing = "0.1.32"
uniffi = "0.18.0"
uniffi_macros = "0.18.0"

View File

@@ -0,0 +1,13 @@
# FFI bindings for the rust matrix SDK
This uses [´uniffi´](https://mozilla.github.io/uniffi-rs/Overview.html) to build the matrix bindings for native support and wasm-bindgen for web-browser assembly support. Please refer to the specific section to figure out how to build and use the bindings for your platform.
## Platforms
### Swift/iOS sync
### Swift/iOS async
TBD

View File

@@ -0,0 +1,3 @@
fn main() {
uniffi_build::generate_scaffolding("./src/api.udl").expect("Building the UDL file failed");
}

View File

@@ -0,0 +1,137 @@
namespace sdk {
[Throws=ClientError]
Client login_new_client(string base_path, string username, string password);
[Throws=ClientError]
Client guest_client(string base_path, string homeserver);
[Throws=ClientError]
Client login_with_token(string base_path, string restore_token);
MediaSource media_source_from_url(string url);
MessageEventContent message_event_content_from_markdown(string md);
string gen_transaction_id();
};
[Error]
interface ClientError {
Generic(string msg);
};
callback interface ClientDelegate {
void did_receive_sync_update();
};
interface Client {
void set_delegate(ClientDelegate? delegate);
void start_sync();
[Throws=ClientError]
string restore_token();
boolean is_guest();
boolean has_first_synced();
boolean is_syncing();
[Throws=ClientError]
string user_id();
[Throws=ClientError]
string display_name();
[Throws=ClientError]
string avatar_url();
[Throws=ClientError]
string device_id();
sequence<Room> rooms();
[Throws=ClientError]
sequence<u8> get_media_content(MediaSource source);
};
callback interface RoomDelegate {
void did_receive_message(AnyMessage message);
};
interface Room {
void set_delegate(RoomDelegate? delegate);
string id();
string? name();
string? topic();
string? avatar_url();
boolean is_direct();
boolean is_public();
boolean is_space();
boolean is_encrypted();
boolean is_tombstoned();
[Throws=ClientError]
string display_name();
[Throws=ClientError]
string? member_avatar_url(string user_id);
[Throws=ClientError]
string? member_display_name(string user_id);
BackwardsStream? start_live_event_listener();
void stop_live_event_listener();
[Throws=ClientError]
void send(MessageEventContent msg, string? txn_id);
};
interface BackwardsStream {
sequence<AnyMessage> paginate_backwards(u64 count);
};
interface MessageEventContent {};
interface AnyMessage {
TextMessage? text_message();
ImageMessage? image_message();
NoticeMessage? notice_message();
EmoteMessage? emote_message();
};
interface BaseMessage {
string id();
string body();
string sender();
u64 origin_server_ts();
string? transaction_id();
};
interface TextMessage {
BaseMessage base_message();
string? html_body();
};
interface ImageMessage {
BaseMessage base_message();
MediaSource source();
u64? width();
u64? height();
string? blurhash();
};
interface NoticeMessage {
BaseMessage base_message();
string? html_body();
};
interface EmoteMessage {
BaseMessage base_message();
string? html_body();
};
interface MediaSource {
string url();
};

View File

@@ -0,0 +1,42 @@
use core::pin::Pin;
use std::sync::Arc;
use futures_core::Stream;
use matrix_sdk::{deserialized_responses::SyncRoomEvent, locks::Mutex, Result};
use tokio_stream::StreamExt;
use tracing::error;
use super::{
messages::{sync_event_to_message, AnyMessage},
RUNTIME,
};
type MsgStream = Pin<Box<dyn Stream<Item = Result<SyncRoomEvent>> + Send>>;
pub struct BackwardsStream {
stream: Arc<Mutex<MsgStream>>,
}
impl BackwardsStream {
pub fn new(stream: MsgStream) -> Self {
BackwardsStream { stream: Arc::new(Mutex::new(Box::pin(stream))) }
}
pub fn paginate_backwards(&self, count: u64) -> Vec<Arc<AnyMessage>> {
let stream = self.stream.clone();
RUNTIME.block_on(async move {
let mut stream = stream.lock().await;
(&mut *stream)
.take(count as usize)
.filter_map(|r| match r {
Ok(ev) => sync_event_to_message(ev),
Err(e) => {
error!("Pagniation error: {e}");
None
}
})
.collect()
.await
})
}
}

View File

@@ -0,0 +1,162 @@
use std::sync::Arc;
use matrix_sdk::{
config::SyncSettings,
media::{MediaFormat, MediaRequest},
ruma::{
api::client::{
filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
sync::sync_events::v3::Filter,
},
events::room::MediaSource,
TransactionId,
},
Client as MatrixClient, LoopCtrl,
};
use parking_lot::RwLock;
use super::{room::Room, ClientState, RestoreToken, RUNTIME};
impl std::ops::Deref for Client {
type Target = MatrixClient;
fn deref(&self) -> &MatrixClient {
&self.client
}
}
pub trait ClientDelegate: Sync + Send {
fn did_receive_sync_update(&self);
}
#[derive(Clone)]
pub struct Client {
client: MatrixClient,
state: Arc<RwLock<ClientState>>,
delegate: Arc<RwLock<Option<Box<dyn ClientDelegate>>>>,
}
impl Client {
pub fn new(client: MatrixClient, state: ClientState) -> Self {
Client {
client,
state: Arc::new(RwLock::new(state)),
delegate: Arc::new(RwLock::new(None)),
}
}
pub fn set_delegate(&self, delegate: Option<Box<dyn ClientDelegate>>) {
*self.delegate.write() = delegate;
}
pub fn start_sync(&self) {
let client = self.client.clone();
let state = self.state.clone();
let delegate = self.delegate.clone();
RUNTIME.spawn(async move {
let mut filter = FilterDefinition::default();
let mut room_filter = RoomFilter::default();
let mut event_filter = RoomEventFilter::default();
event_filter.lazy_load_options =
LazyLoadOptions::Enabled { include_redundant_members: false };
room_filter.state = event_filter;
filter.room = room_filter;
let filter_id = client.get_or_upload_filter("sync", filter).await.unwrap();
let sync_settings = SyncSettings::new().filter(Filter::FilterId(&filter_id));
client
.sync_with_callback(sync_settings, |_| async {
if !state.read().has_first_synced {
state.write().has_first_synced = true
}
if state.read().should_stop_syncing {
state.write().is_syncing = false;
return LoopCtrl::Break;
} else if !state.read().is_syncing {
state.write().is_syncing = true;
}
if let Some(ref delegate) = *delegate.read() {
delegate.did_receive_sync_update()
}
LoopCtrl::Continue
})
.await;
});
}
/// Indication whether we've received a first sync response since
/// establishing the client (in memory)
pub fn has_first_synced(&self) -> bool {
self.state.read().has_first_synced
}
/// Indication whether we are currently syncing
pub fn is_syncing(&self) -> bool {
self.state.read().has_first_synced
}
/// Is this a guest account?
pub fn is_guest(&self) -> bool {
self.state.read().is_guest
}
pub fn restore_token(&self) -> anyhow::Result<String> {
RUNTIME.block_on(async move {
let session = self.client.session().expect("Missing session").clone();
let homeurl = self.client.homeserver().await.into();
Ok(serde_json::to_string(&RestoreToken {
session,
homeurl,
is_guest: self.state.read().is_guest,
})?)
})
}
pub fn rooms(&self) -> Vec<Arc<Room>> {
self.client.rooms().into_iter().map(|room| Arc::new(Room::new(room))).collect()
}
pub fn user_id(&self) -> anyhow::Result<String> {
let user_id = self.client.user_id().expect("No User ID found");
Ok(user_id.to_string())
}
pub fn display_name(&self) -> anyhow::Result<String> {
let l = self.client.clone();
RUNTIME.block_on(async move {
let display_name = l.account().get_display_name().await?.expect("No User ID found");
Ok(display_name)
})
}
pub fn avatar_url(&self) -> anyhow::Result<String> {
let l = self.client.clone();
RUNTIME.block_on(async move {
let avatar_url = l.account().get_avatar_url().await?.expect("No User ID found");
Ok(avatar_url.to_string())
})
}
pub fn device_id(&self) -> anyhow::Result<String> {
let device_id = self.client.device_id().expect("No Device ID found");
Ok(device_id.to_string())
}
pub fn get_media_content(&self, media_source: Arc<MediaSource>) -> anyhow::Result<Vec<u8>> {
let l = self.client.clone();
let source = (*media_source).clone();
RUNTIME.block_on(async move {
Ok(l.get_media_content(&MediaRequest { source, format: MediaFormat::File }, true)
.await?)
})
}
}
pub fn gen_transaction_id() -> String {
TransactionId::new().to_string()
}

View File

@@ -0,0 +1,110 @@
// TODO: target-os conditional would be good.
#![allow(unused_qualifications)]
pub mod backward_stream;
pub mod client;
pub mod messages;
pub mod room;
mod uniffi_api;
use std::{fs, path, sync::Arc};
use client::Client;
use matrix_sdk::{store::make_store_config, Client as MatrixClient, ClientBuilder, Session};
use once_cell::sync::Lazy;
use sanitize_filename_reader_friendly::sanitize;
use serde::{Deserialize, Serialize};
use tokio::runtime::Runtime;
pub use uniffi_api::*;
pub static RUNTIME: Lazy<Runtime> =
Lazy::new(|| Runtime::new().expect("Can't start Tokio runtime"));
pub use matrix_sdk::ruma::{api::client::account::register, UserId};
pub use self::{backward_stream::*, client::*, messages::*, room::*};
pub fn guest_client(base_path: String, homeurl: String) -> anyhow::Result<Arc<Client>> {
let builder = new_client_builder(base_path, homeurl.clone())?.homeserver_url(&homeurl);
let mut guest_registration = register::v3::Request::new();
guest_registration.kind = register::RegistrationKind::Guest;
RUNTIME.block_on(async move {
let client = builder.build().await?;
let register = client.register(guest_registration).await?;
let session = Session {
access_token: register.access_token.expect("no access token given"),
user_id: register.user_id,
device_id: register.device_id.clone().expect("device id is given by server"),
};
client.restore_login(session).await?;
let c = Client::new(client, ClientState { is_guest: true, ..ClientState::default() });
Ok(Arc::new(c))
})
}
pub fn login_with_token(base_path: String, restore_token: String) -> anyhow::Result<Arc<Client>> {
let RestoreToken { session, homeurl, is_guest } = serde_json::from_str(&restore_token)?;
let builder = new_client_builder(base_path, session.user_id.to_string())?
.homeserver_url(&homeurl)
.user_id(&session.user_id);
// First we need to log in.
RUNTIME.block_on(async move {
let client = builder.build().await?;
client.restore_login(session).await?;
let c = Client::new(client, ClientState { is_guest, ..ClientState::default() });
Ok(Arc::new(c))
})
}
pub fn login_new_client(
base_path: String,
username: String,
password: String,
) -> anyhow::Result<Arc<Client>> {
let builder = new_client_builder(base_path, username.clone())?;
let user = Box::<UserId>::try_from(username)?;
// First we need to log in.
RUNTIME.block_on(async move {
let client = builder.user_id(&user).build().await?;
client.login(user, &password, None, None).await?;
let c = Client::new(client, ClientState { is_guest: false, ..ClientState::default() });
Ok(Arc::new(c))
})
}
fn new_client_builder(base_path: String, home: String) -> anyhow::Result<ClientBuilder> {
let data_path = path::PathBuf::from(base_path).join(sanitize(&home));
fs::create_dir_all(&data_path)?;
let store_config = make_store_config(&data_path, None)?;
Ok(MatrixClient::builder().user_agent("rust-sdk-ios").store_config(store_config))
}
#[derive(Default, Debug)]
pub struct ClientState {
is_guest: bool,
has_first_synced: bool,
is_syncing: bool,
should_stop_syncing: bool,
}
#[derive(Serialize, Deserialize)]
struct RestoreToken {
is_guest: bool,
homeurl: String,
session: Session,
}
#[derive(thiserror::Error, Debug)]
pub enum ClientError {
#[error("client error: {msg}")]
Generic { msg: String },
}
impl From<anyhow::Error> for ClientError {
fn from(e: anyhow::Error) -> ClientError {
ClientError::Generic { msg: e.to_string() }
}
}

View File

@@ -0,0 +1,254 @@
use std::sync::Arc;
use extension_trait::extension_trait;
pub use matrix_sdk::ruma::events::room::{
message::RoomMessageEventContent as MessageEventContent, MediaSource,
};
use matrix_sdk::{
deserialized_responses::SyncRoomEvent,
ruma::events::{
room::{
message::{ImageMessageEventContent, MessageFormat, MessageType},
ImageInfo,
},
AnySyncMessageLikeEvent, AnySyncRoomEvent, SyncMessageLikeEvent,
},
};
#[derive(Clone)]
pub struct BaseMessage {
id: String,
body: String,
sender: String,
origin_server_ts: u64,
transaction_id: Option<String>,
}
impl BaseMessage {
pub fn id(&self) -> String {
self.id.clone()
}
pub fn body(&self) -> String {
self.body.clone()
}
pub fn sender(&self) -> String {
self.sender.clone()
}
pub fn origin_server_ts(&self) -> u64 {
self.origin_server_ts
}
pub fn transaction_id(&self) -> Option<String> {
self.transaction_id.clone()
}
}
pub struct TextMessage {
base_message: Arc<BaseMessage>,
html_body: Option<String>,
}
impl TextMessage {
pub fn base_message(&self) -> Arc<BaseMessage> {
self.base_message.clone()
}
pub fn html_body(&self) -> Option<String> {
self.html_body.clone()
}
}
pub struct ImageMessage {
base_message: Arc<BaseMessage>,
source: Arc<MediaSource>,
info: Option<Box<ImageInfo>>,
}
impl ImageMessage {
pub fn base_message(&self) -> Arc<BaseMessage> {
self.base_message.clone()
}
pub fn source(&self) -> Arc<MediaSource> {
self.source.clone()
}
pub fn width(&self) -> Option<u64> {
self.info.clone()?.width?.try_into().ok()
}
pub fn height(&self) -> Option<u64> {
self.info.clone()?.height?.try_into().ok()
}
pub fn blurhash(&self) -> Option<String> {
self.info.clone()?.blurhash
}
}
pub struct NoticeMessage {
base_message: Arc<BaseMessage>,
html_body: Option<String>,
}
impl NoticeMessage {
pub fn base_message(&self) -> Arc<BaseMessage> {
self.base_message.clone()
}
pub fn html_body(&self) -> Option<String> {
self.html_body.clone()
}
}
pub struct EmoteMessage {
base_message: Arc<BaseMessage>,
html_body: Option<String>,
}
impl EmoteMessage {
pub fn base_message(&self) -> Arc<BaseMessage> {
self.base_message.clone()
}
pub fn html_body(&self) -> Option<String> {
self.html_body.clone()
}
}
pub struct AnyMessage {
text: Option<Arc<TextMessage>>,
image: Option<Arc<ImageMessage>>,
notice: Option<Arc<NoticeMessage>>,
emote: Option<Arc<EmoteMessage>>,
}
impl AnyMessage {
pub fn text_message(&self) -> Option<Arc<TextMessage>> {
self.text.clone()
}
pub fn image_message(&self) -> Option<Arc<ImageMessage>> {
self.image.clone()
}
pub fn notice_message(&self) -> Option<Arc<NoticeMessage>> {
self.notice.clone()
}
pub fn emote_message(&self) -> Option<Arc<EmoteMessage>> {
self.emote.clone()
}
}
pub fn sync_event_to_message(sync_event: SyncRoomEvent) -> Option<Arc<AnyMessage>> {
match sync_event.event.deserialize() {
Ok(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
SyncMessageLikeEvent::Original(m),
))) => {
let base_message = Arc::new(BaseMessage {
id: m.event_id.to_string(),
body: m.content.body().to_owned(),
sender: m.sender.to_string(),
origin_server_ts: m.origin_server_ts.as_secs().into(),
transaction_id: m.unsigned.transaction_id.map(|txn_id| txn_id.to_string()),
});
match m.content.msgtype {
MessageType::Image(ImageMessageEventContent { source, info, .. }) => {
let any_message = AnyMessage {
text: None,
image: Some(Arc::new(ImageMessage {
base_message,
source: Arc::new(source),
info,
})),
notice: None,
emote: None,
};
Some(Arc::new(any_message))
}
MessageType::Text(content) => {
let mut html_body: Option<String> = None;
if let Some(formatted_body) = content.formatted {
if formatted_body.format == MessageFormat::Html {
html_body = Some(formatted_body.body);
}
}
let any_message = AnyMessage {
text: Some(Arc::new(TextMessage { base_message, html_body })),
image: None,
notice: None,
emote: None,
};
Some(Arc::new(any_message))
}
MessageType::Notice(content) => {
let mut html_body: Option<String> = None;
if let Some(formatted_body) = content.formatted {
if formatted_body.format == MessageFormat::Html {
html_body = Some(formatted_body.body);
}
}
let any_message = AnyMessage {
text: None,
image: None,
notice: Some(Arc::new(NoticeMessage { base_message, html_body })),
emote: None,
};
Some(Arc::new(any_message))
}
MessageType::Emote(content) => {
let mut html_body: Option<String> = None;
if let Some(formatted_body) = content.formatted {
if formatted_body.format == MessageFormat::Html {
html_body = Some(formatted_body.body);
}
}
let any_message = AnyMessage {
text: None,
image: None,
notice: None,
emote: Some(Arc::new(EmoteMessage { base_message, html_body })),
};
Some(Arc::new(any_message))
}
_ => {
let any_message = AnyMessage {
text: Some(Arc::new(TextMessage { base_message, html_body: None })),
image: None,
notice: None,
emote: None,
};
Some(Arc::new(any_message))
}
}
}
_ => None,
}
}
pub fn media_source_from_url(url: String) -> Arc<MediaSource> {
Arc::new(MediaSource::Plain(url.into()))
}
pub fn message_event_content_from_markdown(md: String) -> Arc<MessageEventContent> {
Arc::new(MessageEventContent::text_markdown(md))
}
#[extension_trait]
pub impl MediaSourceExt for MediaSource {
fn url(&self) -> String {
match self {
MediaSource::Plain(url) => url.to_string(),
MediaSource::Encrypted(file) => file.url.to_string(),
}
}
}

View File

@@ -0,0 +1,159 @@
use std::sync::Arc;
use anyhow::{bail, Result};
use futures_util::{pin_mut, StreamExt};
use matrix_sdk::{
room::Room as MatrixRoom,
ruma::{events::room::message::RoomMessageEventContent, UserId},
};
use parking_lot::RwLock;
use super::{
backward_stream::BackwardsStream,
messages::{sync_event_to_message, AnyMessage},
RUNTIME,
};
pub trait RoomDelegate: Sync + Send {
fn did_receive_message(&self, messages: Arc<AnyMessage>);
}
pub struct Room {
room: MatrixRoom,
delegate: Arc<RwLock<Option<Box<dyn RoomDelegate>>>>,
is_listening_to_live_events: Arc<RwLock<bool>>,
}
impl Room {
pub fn new(room: MatrixRoom) -> Self {
Room {
room,
delegate: Arc::new(RwLock::new(None)),
is_listening_to_live_events: Arc::new(RwLock::new(false)),
}
}
pub fn set_delegate(&self, delegate: Option<Box<dyn RoomDelegate>>) {
*self.delegate.write() = delegate;
}
pub fn id(&self) -> String {
self.room.room_id().to_string()
}
pub fn name(&self) -> Option<String> {
self.room.name()
}
pub fn display_name(&self) -> Result<String> {
let r = self.room.clone();
RUNTIME.block_on(async move { Ok(r.display_name().await?.to_string()) })
}
pub fn topic(&self) -> Option<String> {
self.room.topic()
}
pub fn avatar_url(&self) -> Option<String> {
self.room.avatar_url().map(|m| m.to_string())
}
pub fn member_avatar_url(&self, user_id: String) -> Result<Option<String>> {
let room = self.room.clone();
let user_id = user_id;
RUNTIME.block_on(async move {
let user_id = <&UserId>::try_from(&*user_id).expect("Invalid user id.");
let member = room.get_member(user_id).await?.expect("No user found");
let avatar_url_string = member.avatar_url().map(|m| m.to_string());
Ok(avatar_url_string)
})
}
pub fn member_display_name(&self, user_id: String) -> Result<Option<String>> {
let room = self.room.clone();
let user_id = user_id;
RUNTIME.block_on(async move {
let user_id = <&UserId>::try_from(&*user_id).expect("Invalid user id.");
let member = room.get_member(user_id).await?.expect("No user found");
let avatar_url_string = member.display_name().map(|m| m.to_owned());
Ok(avatar_url_string)
})
}
pub fn is_direct(&self) -> bool {
self.room.is_direct()
}
pub fn is_public(&self) -> bool {
self.room.is_public()
}
pub fn is_encrypted(&self) -> bool {
self.room.is_encrypted()
}
pub fn is_space(&self) -> bool {
self.room.is_space()
}
pub fn is_tombstoned(&self) -> bool {
self.room.is_tombstoned()
}
pub fn start_live_event_listener(&self) -> Option<Arc<BackwardsStream>> {
if *self.is_listening_to_live_events.read() {
return None;
}
*self.is_listening_to_live_events.write() = true;
let room = self.room.clone();
let delegate = self.delegate.clone();
let is_listening_to_live_events = self.is_listening_to_live_events.clone();
let (forward_stream, backwards) = RUNTIME.block_on(async move {
room.timeline().await.expect("Failed acquiring timeline streams")
});
RUNTIME.spawn(async move {
pin_mut!(forward_stream);
while let Some(sync_event) = forward_stream.next().await {
if !(*is_listening_to_live_events.read()) {
return;
}
if let Some(delegate) = &*delegate.read() {
if let Some(message) = sync_event_to_message(sync_event) {
delegate.did_receive_message(message)
}
}
}
});
Some(Arc::new(BackwardsStream::new(Box::pin(backwards))))
}
pub fn stop_live_event_listener(&self) {
*self.is_listening_to_live_events.write() = false;
}
pub fn send(&self, msg: Arc<RoomMessageEventContent>, txn_id: Option<String>) -> Result<()> {
let room = match &self.room {
MatrixRoom::Joined(j) => j.clone(),
_ => bail!("Can't send to a room that isn't in joined state"),
};
RUNTIME.block_on(async move {
room.send((*msg).to_owned(), txn_id.as_deref().map(Into::into)).await
})?;
Ok(())
}
}
impl std::ops::Deref for Room {
type Target = MatrixRoom;
fn deref(&self) -> &MatrixRoom {
&self.room
}
}

View File

@@ -0,0 +1,5 @@
#![allow(clippy::all)]
use crate::*;
uniffi_macros::include_scaffolding!("api");

View File

@@ -33,7 +33,7 @@ use matrix_sdk_crypto::{
};
use matrix_sdk_store_encryption::StoreCipher;
use ruma::{DeviceId, OwnedDeviceId, OwnedUserId, RoomId, TransactionId, UserId};
use serde::{Deserialize, Serialize};
use serde::{de::DeserializeOwned, Serialize};
use wasm_bindgen::JsValue;
use web_sys::IdbKeyRange;
@@ -284,7 +284,7 @@ impl IndexeddbStore {
}
}
fn deserialize_value<T: for<'b> Deserialize<'b>>(
fn deserialize_value<T: DeserializeOwned>(
&self,
value: JsValue,
) -> Result<T, CryptoStoreError> {

View File

@@ -46,7 +46,7 @@ use ruma::{
signatures::{redact_in_place, CanonicalJsonObject},
RoomVersionId,
};
use serde::{Deserialize, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use wasm_bindgen::JsValue;
use web_sys::IdbKeyRange;
@@ -275,7 +275,7 @@ impl IndexeddbStore {
})
}
fn deserialize_event<T: for<'b> Deserialize<'b>>(
fn deserialize_event<T: DeserializeOwned>(
&self,
event: JsValue,
) -> std::result::Result<T, SerializationError> {

View File

@@ -29,4 +29,5 @@ ruma-common = "0.9.0"
thiserror = "1.0.30"
[dependencies.vodozemac]
version = "0.2.0"
git = "https://github.com/matrix-org/vodozemac/"
rev = "d0e744287a14319c2a9148fef3747548c740fc36"

View File

@@ -32,7 +32,7 @@ use crate::{
};
/// An enum representing the different modes a QR verification can be in.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum QrVerificationData {
/// The QR verification is verifying another user
Verification(VerificationData),
@@ -375,7 +375,7 @@ impl QrVerificationData {
///
/// This mode is used for verification between two users using their master
/// cross signing keys.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VerificationData {
event_id: OwnedEventId,
first_master_key: Ed25519PublicKey,
@@ -474,7 +474,7 @@ impl From<VerificationData> for QrVerificationData {
/// This mode is used for verification between two devices of the same user
/// where this device, that is creating this QR code, is trusting or owning
/// the cross signing master key.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SelfVerificationData {
transaction_id: String,
master_key: Ed25519PublicKey,
@@ -577,7 +577,7 @@ impl From<SelfVerificationData> for QrVerificationData {
/// This mode is used for verification between two devices of the same user
/// where this device, that is creating this QR code, is not trusting the
/// cross signing master key.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SelfVerificationNoMasterKey {
transaction_id: String,
device_key: Ed25519PublicKey,

View File

@@ -38,7 +38,7 @@ use ruma::{
events::room_key_request::RequestedKeyInfo, DeviceId, OwnedDeviceId, OwnedUserId, RoomId,
TransactionId, UserId,
};
use serde::{Deserialize, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
pub use sled::Error;
use sled::{
transaction::{ConflictableTransactionError, TransactionError},
@@ -205,7 +205,7 @@ impl std::fmt::Debug for SledStore {
}
impl SledStore {
/// Open the sled based cryptostore at the given path using the given
/// Open the sled-based crypto store at the given path using the given
/// passphrase to encrypt private data.
pub fn open_with_passphrase(
path: impl AsRef<Path>,
@@ -224,7 +224,7 @@ impl SledStore {
SledStore::open_helper(db, Some(path), store_cipher)
}
/// Create a sled based cryptostore using the given sled database.
/// Create a sled-based crypto store using the given sled database.
/// The given passphrase will be used to encrypt private data.
pub fn open_with_database(
db: Db,
@@ -245,10 +245,7 @@ impl SledStore {
}
}
fn deserialize_value<T: for<'b> Deserialize<'b>>(
&self,
event: &[u8],
) -> Result<T, CryptoStoreError> {
fn deserialize_value<T: DeserializeOwned>(&self, event: &[u8]) -> Result<T, CryptoStoreError> {
if let Some(key) = &self.store_cipher {
key.decrypt_value(event).map_err(CryptoStoreError::backend)
} else {

View File

@@ -51,7 +51,9 @@ use ruma::{
signatures::{redact_in_place, CanonicalJsonObject},
RoomVersionId,
};
use serde::{Deserialize, Serialize};
#[cfg(feature = "experimental-timeline")]
use serde::Deserialize;
use serde::{de::DeserializeOwned, Serialize};
use sled::{
transaction::{ConflictableTransactionError, TransactionError},
Config, Db, Transactional, Tree,
@@ -368,10 +370,7 @@ impl SledStore {
}
}
fn deserialize_event<T: for<'b> Deserialize<'b>>(
&self,
event: &[u8],
) -> Result<T, SledStoreError> {
fn deserialize_event<T: DeserializeOwned>(&self, event: &[u8]) -> Result<T, SledStoreError> {
if let Some(key) = &self.store_cipher {
Ok(key.decrypt_value(event)?)
} else {

View File

@@ -25,7 +25,7 @@ use displaydoc::Display;
use hmac::Hmac;
use pbkdf2::pbkdf2;
use rand::{thread_rng, Error as RandomError, Fill};
use serde::{Deserialize, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use sha2::Sha256;
use zeroize::Zeroize;
@@ -76,7 +76,7 @@ pub enum Error {
/// let decrypted: Value = store_cipher.decrypt_value(&encrypted)?;
///
/// assert_eq!(value, decrypted);
/// # Result::<_, anyhow::Error>::Ok(()) };
/// # anyhow::Ok(()) };
/// ```
#[allow(missing_debug_implementations)]
pub struct StoreCipher {
@@ -115,7 +115,7 @@ impl StoreCipher {
/// let export = store_cipher.export("secret-passphrase");
///
/// // Save the export in your key/value store.
/// # Result::<_, anyhow::Error>::Ok(()) };
/// # anyhow::Ok(()) };
/// ```
pub fn export(&self, passphrase: &str) -> Result<Vec<u8>, Error> {
let mut rng = thread_rng();
@@ -171,7 +171,7 @@ impl StoreCipher {
/// let imported = StoreCipher::import("secret-passphrase", &export)?;
///
/// // Save the export in your key/value store.
/// # Result::<_, anyhow::Error>::Ok(()) };
/// # anyhow::Ok(()) };
/// ```
pub fn import(passphrase: &str, encrypted: &[u8]) -> Result<Self, Error> {
let encrypted: EncryptedStoreCipher = serde_json::from_slice(encrypted)?;
@@ -245,7 +245,7 @@ impl StoreCipher {
/// let hashed_key = store_cipher.hash_key("list-of-pokemon", key.as_ref());
///
/// // It's now safe to insert the key into our key/value store.
/// # Result::<_, anyhow::Error>::Ok(()) };
/// # anyhow::Ok(()) };
/// ```
pub fn hash_key(&self, table_name: &str, key: &[u8]) -> [u8; 32] {
let mac_key = self.inner.get_mac_key_for_table(table_name);
@@ -281,7 +281,7 @@ impl StoreCipher {
/// let decrypted: Value = store_cipher.decrypt_value(&encrypted)?;
///
/// assert_eq!(value, decrypted);
/// # Result::<_, anyhow::Error>::Ok(()) };
/// # anyhow::Ok(()) };
/// ```
pub fn encrypt_value(&self, value: &impl Serialize) -> Result<Vec<u8>, Error> {
Ok(serde_json::to_vec(&self.encrypt_value_typed(value)?)?)
@@ -351,7 +351,7 @@ impl StoreCipher {
/// let decrypted = store_cipher.decrypt_value_data(encrypted)?;
///
/// assert_eq!(value, decrypted);
/// # Result::<_, anyhow::Error>::Ok(()) };
/// # anyhow::Ok(()) };
/// ```
pub fn encrypt_value_data(&self, mut data: Vec<u8>) -> Result<EncryptedValue, Error> {
let nonce = Keys::get_nonce()?;
@@ -391,9 +391,9 @@ impl StoreCipher {
/// let decrypted: Value = store_cipher.decrypt_value(&encrypted)?;
///
/// assert_eq!(value, decrypted);
/// # Result::<_, anyhow::Error>::Ok(()) };
/// # anyhow::Ok(()) };
/// ```
pub fn decrypt_value<T: for<'b> Deserialize<'b>>(&self, value: &[u8]) -> Result<T, Error> {
pub fn decrypt_value<T: DeserializeOwned>(&self, value: &[u8]) -> Result<T, Error> {
let value: EncryptedValue = serde_json::from_slice(value)?;
self.decrypt_value_typed(value)
}
@@ -427,9 +427,9 @@ impl StoreCipher {
/// let decrypted: Value = store_cipher.decrypt_value_typed(encrypted)?;
///
/// assert_eq!(value, decrypted);
/// # Result::<_, anyhow::Error>::Ok(()) };
/// # anyhow::Ok(()) };
/// ```
pub fn decrypt_value_typed<T: for<'b> Deserialize<'b>>(
pub fn decrypt_value_typed<T: DeserializeOwned>(
&self,
value: EncryptedValue,
) -> Result<T, Error> {
@@ -467,7 +467,7 @@ impl StoreCipher {
/// let decrypted = store_cipher.decrypt_value_data(encrypted)?;
///
/// assert_eq!(value, decrypted);
/// # Result::<_, anyhow::Error>::Ok(()) };
/// # anyhow::Ok(()) };
/// ```
pub fn decrypt_value_data(&self, value: EncryptedValue) -> Result<Vec<u8>, Error> {
if value.version != VERSION {
@@ -500,7 +500,7 @@ impl MacKey {
/// Encrypted value, ready for storage, as created by the
/// [`StoreCipher::encrypt_value_data()`]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct EncryptedValue {
version: u8,
ciphertext: Vec<u8>,
@@ -557,7 +557,7 @@ impl Keys {
}
/// Version specific info for the key derivation method that is used.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
enum KdfInfo {
/// The PBKDF2 to Chacha key derivation variant.
Pbkdf2ToChaCha20Poly1305 {
@@ -572,7 +572,7 @@ enum KdfInfo {
/// Version specific info for encryption method that is used to encrypt our
/// store cipher.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
enum CipherTextInfo {
/// A store cipher encrypted using the ChaCha20Poly1305 AEAD.
ChaCha20Poly1305 {
@@ -585,7 +585,7 @@ enum CipherTextInfo {
/// An encrypted version of our store cipher, this can be safely stored in a
/// database.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
struct EncryptedStoreCipher {
/// Info about the key derivation method that was used to expand the
/// passphrase into an encryption key.

View File

@@ -46,6 +46,17 @@ pub static DEVICES: Lazy<JsonValue> = Lazy::new(|| {
})
});
pub static GET_ALIAS: 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": {

View File

@@ -210,30 +210,6 @@ pub static SYNC: Lazy<JsonValue> = Lazy::new(|| {
"replaces_state": "$152034819067QWJxM:localhost"
}
},
{
"content": {
"membership": "leave",
"reason": "offline",
"avatar_url": "mxc://avatar.com/d0dV9jLpe",
"displayname": "example"
},
"event_id": "$1585345508297748AIUBh:matrix.org",
"origin_server_ts": 158534550,
"sender": "@example:localhost",
"state_key": "@example:localhost",
"type": "m.room.member",
"unsigned": {
"replaces_state": "$1585345354296486IGZfp:localhost",
"prev_content": {
"avatar_url": "mxc://avatar.com/d0dV9jLpe",
"displayname": "example",
"membership": "join"
},
"prev_sender": "@example2:localhost",
"age": 6992
},
"room_id": "!roomid:room.com"
}
]
},
"timeline": {

View File

@@ -123,7 +123,7 @@ default-features = false
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies]
async-once-cell = "0.3.0"
async-once-cell = "0.3.1"
wasm-timer = "0.2.5"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]

View File

@@ -59,19 +59,19 @@ More examples can be found in the [examples] directory.
The following crate feature flags are available:
| Feature | Default | Description |
| ------------------- | :-----: | --------------------------------------------------------------------- |
| `anyhow` | No | Better logging for event handlers that return `anyhow::Result` |
| `e2e-encryption` | Yes | Enable End-to-end encryption support |
| `eyre` | No | Better logging for event handlers that return `eyre::Result` |
| `image-proc` | No | Enables image processing to generate thumbnails |
| `image-rayon` | No | Enables faster image processing |
| `markdown` | No | Support to send Markdown-formatted messages |
| `qrcode` | Yes | QR code verification support |
| `sled` | Yes | Persistent storage of state and E2EE-Data using sled (if `e2e-encryption` is activated)
| `indexeddb` | No | Persistent storage of state and E2EE-Data for browsers using indexeddb (if `e2e-encryption` is activated)
| `socks` | No | Enables SOCKS support in the default HTTP client, [`reqwest`] |
| `sso-login` | No | Enables SSO login with a local HTTP server |
| Feature | Default | Description |
| ------------------- | :-----: | -------------------------------------------------------------------------------------------------------------------------- |
| `anyhow` | No | Better logging for event handlers that return `anyhow::Result` |
| `e2e-encryption` | Yes | End-to-end encryption (E2EE) support |
| `eyre` | No | Better logging for event handlers that return `eyre::Result` |
| `image-proc` | No | Image processing for generating thumbnails |
| `image-rayon` | No | Enables faster image processing |
| `markdown` | No | Support for sending Markdown-formatted messages |
| `qrcode` | Yes | QR code verification support |
| `sled` | Yes | Persistent storage of state and E2EE data (optionally, if feature `e2e-encryption` is enabled), via Sled |
| `indexeddb` | No | Persistent storage of state and E2EE data (optionally, if feature `e2e-encryption` is enabled) for browsers, via IndexedDB |
| `socks` | No | SOCKS support in the default HTTP client, [`reqwest`] |
| `sso-login` | No | Support for SSO login with a local HTTP server |
[`reqwest`]: https://docs.rs/reqwest/0.11.5/reqwest/index.html

View File

@@ -37,11 +37,7 @@ async fn bootstrap(client: Client, user_id: OwnedUserId, password: String) {
}
}
async fn login(
homeserver_url: String,
username: &str,
password: &str,
) -> Result<(), matrix_sdk::Error> {
async fn login(homeserver_url: String, username: &str, password: &str) -> matrix_sdk::Result<()> {
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
let client = Client::new(homeserver_url).await.unwrap();
@@ -72,7 +68,7 @@ async fn login(
}
#[tokio::main]
async fn main() -> Result<(), matrix_sdk::Error> {
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let (homeserver_url, username, password) =
@@ -87,5 +83,7 @@ async fn main() -> Result<(), matrix_sdk::Error> {
}
};
login(homeserver_url, &username, &password).await
login(homeserver_url, &username, &password).await?;
Ok(())
}

View File

@@ -65,11 +65,7 @@ async fn print_devices(user_id: &UserId, client: &Client) {
}
}
async fn login(
homeserver_url: String,
username: &str,
password: &str,
) -> Result<(), matrix_sdk::Error> {
async fn login(homeserver_url: String, username: &str, password: &str) -> matrix_sdk::Result<()> {
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
let client = Client::new(homeserver_url).await.unwrap();
@@ -201,7 +197,7 @@ async fn login(
}
#[tokio::main]
async fn main() -> Result<(), matrix_sdk::Error> {
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let (homeserver_url, username, password) =
@@ -216,5 +212,7 @@ async fn main() -> Result<(), matrix_sdk::Error> {
}
};
login(homeserver_url, &username, &password).await
login(homeserver_url, &username, &password).await?;
Ok(())
}

View File

@@ -35,7 +35,7 @@ async fn login(
homeserver_url: String,
username: &str,
password: &str,
) -> Result<Client, matrix_sdk::Error> {
) -> matrix_sdk::Result<Client> {
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
let client = Client::new(homeserver_url).await.unwrap();
@@ -45,7 +45,7 @@ async fn login(
}
#[tokio::main]
async fn main() -> Result<(), matrix_sdk::Error> {
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let (homeserver_url, username, password) =

View File

@@ -56,7 +56,7 @@ async fn login_and_sync(
username: String,
password: String,
image: File,
) -> Result<(), matrix_sdk::Error> {
) -> matrix_sdk::Result<()> {
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
let client = Client::new(homeserver_url).await.unwrap();
@@ -74,7 +74,7 @@ async fn login_and_sync(
}
#[tokio::main]
async fn main() -> Result<(), matrix_sdk::Error> {
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let (homeserver_url, username, password, image_path) =
match (env::args().nth(1), env::args().nth(2), env::args().nth(3), env::args().nth(4)) {

View File

@@ -30,11 +30,7 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) {
}
}
async fn login(
homeserver_url: String,
username: &str,
password: &str,
) -> Result<(), matrix_sdk::Error> {
async fn login(homeserver_url: String, username: &str, password: &str) -> matrix_sdk::Result<()> {
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
let client = Client::new(homeserver_url).await.unwrap();
@@ -47,7 +43,7 @@ async fn login(
}
#[tokio::main]
async fn main() -> Result<(), matrix_sdk::Error> {
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let (homeserver_url, username, password) =
@@ -62,5 +58,7 @@ async fn main() -> Result<(), matrix_sdk::Error> {
}
};
login(homeserver_url, &username, &password).await
login(homeserver_url, &username, &password).await?;
Ok(())
}

View File

@@ -56,7 +56,7 @@ async fn print_timeline(room: Room) {
}
#[tokio::main]
async fn main() -> Result<(), matrix_sdk::Error> {
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let (homeserver_url, username, password, room_id) =

View File

@@ -67,7 +67,7 @@ impl Account {
/// if let Some(name) = client.account().get_display_name().await? {
/// println!("Logged in as user '{}' with display name '{}'", user, name);
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn get_display_name(&self) -> Result<Option<String>> {
let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
@@ -90,7 +90,7 @@ impl Account {
/// client.login(user, "password", None, None).await?;
///
/// client.account().set_display_name(Some("Alice")).await?;
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn set_display_name(&self, name: Option<&str>) -> Result<()> {
let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
@@ -115,7 +115,7 @@ impl Account {
/// if let Some(url) = client.account().get_avatar_url().await? {
/// println!("Your avatar's mxc url is {}", url);
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn get_avatar_url(&self) -> Result<Option<OwnedMxcUri>> {
let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
@@ -164,7 +164,7 @@ impl Account {
/// if let Some(avatar) = client.account().get_avatar(MediaFormat::File).await? {
/// std::fs::write("avatar.png", avatar);
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn get_avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
if let Some(url) = self.get_avatar_url().await? {
@@ -199,7 +199,7 @@ impl Account {
/// let mut image = File::open(&path)?;
///
/// client.account().upload_avatar(&mime::IMAGE_JPEG, &mut image).await?;
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn upload_avatar<R: Read>(
&self,
@@ -230,7 +230,7 @@ impl Account {
/// profile.avatar_url
/// );
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn get_profile(&self) -> Result<get_profile::v3::Response> {
let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
@@ -276,7 +276,7 @@ impl Account {
/// "myverysecretpassword",
/// Some(AuthData::Dummy(Dummy::new())),
/// ).await?;
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
/// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
/// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
@@ -326,7 +326,7 @@ impl Account {
///
/// // Proceed with UIAA.
///
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
/// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
/// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
@@ -362,7 +362,7 @@ impl Account {
/// for threepid in threepids {
/// println!("Found 3PID '{}' of type '{}'", threepid.address, threepid.medium);
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
/// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
pub async fn get_3pids(&self) -> Result<get_3pids::v3::Response> {
@@ -429,7 +429,7 @@ impl Account {
///
/// // Proceed with UIAA.
///
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
/// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
/// [`ErrorKind::ThreepidInUse`]: ruma::api::client::error::ErrorKind::ThreepidInUse
@@ -511,7 +511,7 @@ impl Account {
///
/// // Proceed with UIAA.
///
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
/// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
/// [`ErrorKind::ThreepidInUse`]: ruma::api::client::error::ErrorKind::ThreepidInUse
@@ -617,7 +617,7 @@ impl Account {
/// _ => println!("Could not unbind 3PID from the Identity Server"),
/// }
///
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
/// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
/// [`ThirdPartyIdRemovalStatus::Success`]: ruma::api::client::account::ThirdPartyIdRemovalStatus::Success

View File

@@ -336,7 +336,7 @@ impl<'a, R: Read> AttachmentConfig<'a, R> {
/// config,
/// ).await?;
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
#[cfg(feature = "image-proc")]
pub fn generate_image_thumbnail<R: BufRead + Seek>(

View File

@@ -1,11 +1,15 @@
use std::sync::Arc;
#[cfg(target_arch = "wasm32")]
use async_once_cell::OnceCell;
use matrix_sdk_base::{locks::RwLock, store::StoreConfig, BaseClient, StateStore};
use ruma::{
api::{client::discovery::discover_homeserver, error::FromHttpResponseError, MatrixVersion},
OwnedServerName, ServerName, UserId,
};
use thiserror::Error;
#[cfg(not(target_arch = "wasm32"))]
use tokio::sync::OnceCell;
use url::Url;
use super::{Client, ClientInner};
@@ -109,7 +113,7 @@ impl ClientBuilder {
self
}
/// Create a new `ClientConfig` with the given [`StoreConfig`].
/// Create a new `ClientBuilder` with the given [`StoreConfig`].
///
/// The easiest way to get a [`StoreConfig`] is to use the
/// [`make_store_config`] method from the [`store`] module or directly from
@@ -186,7 +190,7 @@ impl ClientBuilder {
/// let client_config = Client::builder()
/// .proxy("http://localhost:8080");
///
/// # Result::<_, matrix_sdk::Error>::Ok(())
/// # anyhow::Ok(())
/// # });
/// ```
#[cfg(not(target_arch = "wasm32"))]
@@ -318,23 +322,11 @@ impl ClientBuilder {
let homeserver = Arc::new(RwLock::new(Url::parse(&homeserver)?));
let http_client = mk_http_client(homeserver.clone());
#[cfg(target_arch = "wasm32")]
let server_versions = {
let cell = async_once_cell::OnceCell::new();
if let Some(server_versions) = self.server_versions {
cell.get_or_init(async move { server_versions }).await;
}
cell
};
#[cfg(not(target_arch = "wasm32"))]
let server_versions = tokio::sync::OnceCell::new_with(self.server_versions);
let inner = Arc::new(ClientInner {
homeserver,
http_client,
base_client,
server_versions,
server_versions: OnceCell::new_with(self.server_versions),
#[cfg(feature = "e2e-encryption")]
group_session_locks: Default::default(),
#[cfg(feature = "e2e-encryption")]

View File

@@ -43,6 +43,7 @@ use ruma::{
api::{
client::{
account::{register, whoami},
alias::get_alias,
device::{delete_devices, get_devices},
directory::{get_public_rooms, get_public_rooms_filtered},
discovery::{
@@ -64,8 +65,8 @@ use ruma::{
assign,
events::room::MediaSource,
presence::PresenceState,
DeviceId, MxcUri, OwnedDeviceId, OwnedRoomId, OwnedServerName, RoomId, RoomOrAliasId,
ServerName, UInt, UserId,
DeviceId, MxcUri, OwnedDeviceId, OwnedRoomId, OwnedServerName, RoomAliasId, RoomId,
RoomOrAliasId, ServerName, UInt, UserId,
};
use serde::de::DeserializeOwned;
#[cfg(not(target_arch = "wasm32"))]
@@ -238,7 +239,7 @@ impl Client {
/// // Change password
/// }
///
/// # Result::<_, anyhow::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn get_capabilities(&self) -> HttpResult<Capabilities> {
let res = self.send(get_capabilities::v3::Request::new(), None).await?;
@@ -606,6 +607,20 @@ impl Client {
self.store().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
/// about it.
///
/// # Arguments
///
/// `room_alias` - The room alias to be resolved.
pub async fn resolve_room_alias(
&self,
room_alias: &RoomAliasId,
) -> HttpResult<get_alias::v3::Response> {
let request = get_alias::v3::Request::new(room_alias);
self.send(request, None).await
}
/// Gets the homeservers supported login types.
///
/// This should be the first step when trying to login so you can call the
@@ -701,7 +716,7 @@ impl Client {
/// "Logged in as {}, got device_id {} and access_token {}",
/// user, response.device_id, response.access_token
/// );
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
///
/// [`restore_login`]: #method.restore_login
@@ -1371,7 +1386,7 @@ impl Client {
/// for room in response.chunk {
/// println!("Found room {:?}", room);
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn public_rooms_filtered(
&self,
@@ -1475,7 +1490,7 @@ impl Client {
///
/// // Check the corresponding Response struct to find out what types are
/// // returned
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn send<Request>(
&self,
@@ -1543,7 +1558,7 @@ impl Client {
/// device.display_name.as_deref().unwrap_or("")
/// );
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn devices(&self) -> HttpResult<get_devices::v3::Response> {
let request = get_devices::v3::Request::new();
@@ -1600,7 +1615,7 @@ impl Client {
/// .await?;
/// }
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
pub async fn delete_devices(
&self,
devices: &[OwnedDeviceId],
@@ -1689,7 +1704,7 @@ impl Client {
/// // Now keep on syncing forever. `sync()` will use the stored sync token
/// // from our `sync_once()` call automatically.
/// client.sync(SyncSettings::default()).await;
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
///
/// [`sync`]: #method.sync
@@ -1791,7 +1806,7 @@ impl Client {
/// // Now keep on syncing forever. `sync()` will use the latest sync token
/// // automatically.
/// client.sync(SyncSettings::default()).await;
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
///
/// [argument docs]: #method.sync_once
@@ -1920,7 +1935,7 @@ impl Client {
/// }
/// }
///
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
#[instrument(skip(self))]
pub async fn sync_stream<'a>(
@@ -2532,6 +2547,19 @@ pub(crate) mod tests {
assert!(client.devices().await.is_ok());
}
#[async_test]
async fn resolve_room_alias() {
let client = no_retry_test_client().await;
let _m = mock("GET", "/_matrix/client/r0/directory/room/%23alias%3Aexample%2Eorg")
.with_status(200)
.with_body(test_json::GET_ALIAS.to_string())
.create();
let alias = ruma::room_alias_id!("#alias:example.org");
assert!(client.resolve_room_alias(alias).await.is_ok());
}
#[async_test]
async fn test_join_leave_room() {
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
@@ -3361,7 +3389,7 @@ pub(crate) mod tests {
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
let members: Vec<RoomMember> = room.active_members().await.unwrap();
assert_eq!(1, members.len());
assert_eq!(2, members.len());
// assert!(room.power_levels.is_some())
}

View File

@@ -198,9 +198,9 @@ pub enum RoomKeyImportError {
#[error(transparent)]
SerdeJson(#[from] JsonError),
/// The cryptostore isn't yet open, logging in is required to open the
/// cryptostore.
#[error("The cryptostore hasn't been yet opened, can't import yet.")]
/// The crypto store isn't yet open. Logging in is required to open the
/// crypto store.
#[error("The crypto store hasn't been yet opened, can't import yet.")]
StoreClosed,
/// An IO error happened.

View File

@@ -205,15 +205,20 @@ impl Common {
#[cfg(feature = "e2e-encryption")]
if let Some(machine) = self.client.olm_machine() {
for event in http_response.chunk {
let decrypted_event =
if let AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
SyncMessageLikeEvent::Original(encrypted_event),
)) = event.deserialize_as::<AnySyncRoomEvent>()?
{
machine.decrypt_room_event(&encrypted_event, room_id).await?
let decrypted_event = if let Ok(AnySyncRoomEvent::MessageLike(
AnySyncMessageLikeEvent::RoomEncrypted(SyncMessageLikeEvent::Original(
encrypted_event,
)),
)) = event.deserialize_as::<AnySyncRoomEvent>()
{
if let Ok(event) = machine.decrypt_room_event(&encrypted_event, room_id).await {
event
} else {
RoomEvent { event, encryption_info: None }
};
}
} else {
RoomEvent { event, encryption_info: None }
};
response.chunk.push(decrypted_event);
}
@@ -292,7 +297,7 @@ impl Common {
/// }
/// }
///
/// # Result::<_, matrix_sdk::Error>::Ok(())
/// # anyhow::Ok(())
/// # });
/// ```
#[cfg(feature = "experimental-timeline")]
@@ -361,7 +366,7 @@ impl Common {
/// }
/// }
///
/// # Result::<_, matrix_sdk::Error>::Ok(())
/// # anyhow::Ok(())
/// # });
/// ```
#[cfg(feature = "experimental-timeline")]
@@ -425,7 +430,7 @@ impl Common {
/// });
/// }
///
/// # Result::<_, matrix_sdk::Error>::Ok(())
/// # anyhow::Ok(())
/// # });
/// ```
#[cfg(feature = "experimental-timeline")]
@@ -478,12 +483,15 @@ impl Common {
let event = self.client.send(request, None).await?.event;
#[cfg(feature = "e2e-encryption")]
if let AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
SyncMessageLikeEvent::Original(encrypted_event),
)) = event.deserialize_as::<AnySyncRoomEvent>()?
{
Ok(self.decrypt_event(&encrypted_event).await?)
} else {
if let Ok(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
SyncMessageLikeEvent::Original(encrypted_event),
))) = event.deserialize_as::<AnySyncRoomEvent>()
{
if let Ok(event) = self.decrypt_event(&encrypted_event).await {
return Ok(event);
}
}
Ok(RoomEvent { event, encryption_info: None })
}
@@ -837,7 +845,7 @@ impl Common {
///
/// room.set_tag(TagName::User(user_tag), tag_info ).await?;
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn set_tag(
&self,

View File

@@ -177,7 +177,7 @@ impl Joined {
/// if let Some(room) = client.get_joined_room(&room_id) {
/// room.typing_notice(true).await?
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn typing_notice(&self, typing: bool) -> Result<()> {
// Only send a request to the homeserver if the old timeout has elapsed
@@ -288,7 +288,7 @@ impl Joined {
/// if let Some(room) = client.get_joined_room(&room_id) {
/// room.enable_encryption().await?
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn enable_encryption(&self) -> Result<()> {
use ruma::{
@@ -458,7 +458,7 @@ impl Joined {
/// if let Some(room) = client.get_joined_room(&room_id) {
/// room.send(content, Some(&txn_id)).await?;
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
///
/// [`SyncMessageLikeEvent`]: ruma::events::SyncMessageLikeEvent
@@ -526,7 +526,7 @@ impl Joined {
/// if let Some(room) = client.get_joined_room(&room_id) {
/// room.send_raw(content, "m.room.message", None).await?;
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
///
/// [`SyncMessageLikeEvent`]: ruma::events::SyncMessageLikeEvent
@@ -575,7 +575,7 @@ impl Joined {
let olm = self.client.olm_machine().expect("Olm machine wasn't started");
let encrypted_content =
olm.encrypt_raw(self.inner.room_id(), content, event_type).await?;
olm.encrypt_room_event_raw(self.inner.room_id(), content, event_type).await?;
let raw_content = Raw::new(&encrypted_content)
.expect("Failed to serialize encrypted event")
.cast();
@@ -648,7 +648,7 @@ impl Joined {
/// AttachmentConfig::new(),
/// ).await?;
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn send_attachment<R: Read + Seek, T: Read>(
&self,
@@ -813,7 +813,7 @@ impl Joined {
/// if let Some(room) = client.get_joined_room(&room_id) {
/// room.send_state_event(content, "").await?;
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn send_state_event(
&self,
@@ -907,7 +907,7 @@ impl Joined {
/// let reason = Some("Indecent material");
/// room.redact(&event_id, reason, None).await?;
/// }
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
/// # anyhow::Ok(()) });
/// ```
pub async fn redact(
&self,

View File

@@ -16,12 +16,12 @@
//! The re-exports present here depend on the store-related features that are
//! enabled:
//!
//! 1. `sled` provides a `StateStore`, and a `CryptoStore` for encryption data
//! if `e2e-encryption` is enabled. This is the default persistent store
//! implementation for non-WebAssembly targets.
//! 2. `indexeddb`, too, provides a `StateStore` and a `CryptoStore` if
//! `encryption` is also enabled. This is the default persistent store
//! implementation for WebAssembly targets.
//! 1. The `sled` feature provides a `StateStore` for storing state and
//! a `CryptoStore` for E2EE data (if `e2e-encryption` is enabled). This is
//! the default persistent store implementation for non-WebAssembly targets.
//! 2. The `indexeddb` feature also provides a `StateStore` for storing state
//! and a `CryptoStore` (if `e2e-encryption` is enabled). This is the default
//! persistent store implementation for WebAssembly targets.
//!
//! Both options provide a `make_store_config` convenience method to create a
//! [`StoreConfig`] for [`ClientBuilder::store_config()`].