diff --git a/CHANGES.rst b/CHANGES.rst index f08bcc7dbb..75bf4992de 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,37 @@ +Changes in Matrix iOS SDK in 0.11.0 (2018-08-10) +=============================================== + +Improvements: +* MXSession: Add the option to use a Matrix filter in /sync requests ([MXSession startWithSyncFilter:]). +* MXSession: Add API to manage Matrix filters. +* MXRestClient: Add Matrix filter API. +* MXRoom: Add send reply with text message (vector-im/riot-ios#1911). +* MXRoom: Add an asynchronous methods for liveTimeline, state and members. +* MXRoom: Add methods to manage the room liveTimeline listeners synchronously. +* MXRoomState: Add a membersCount property to store members stats independently from MXRoomMember objects. +* MXRoomSummary: Add a membersCount property to cache MXRoomState one. +* MXRoomSummary: Add a membership property to cache MXRoomState one. +* MXRoomSummary: add isConferenceUserRoom. +* MXStore: Add Obj-C annotations. +* MXFileStore: Add a setting to set which data to preload ([MXFileStore setPreloadOptions:]). +* Manage the new summary API from the homeserver( MSC: https://docs.google.com/document/d/11i14UI1cUz-OJ0knD5BFu7fmT6Fo327zvMYqfSAR7xs/edit#). +* MXRoom: Add send reply with text message (vector-im/riot-ios#1911). +* Support room versioning (vector-im/riot-ios#1938). +* Add SwiftSupport subspec to MatrixSDK in order to be able to expose Swift refinements to a project using both Swift and Objective-C. + +Bug fix: +* MXRestClient: Fix filter parameter in messagesForRoom. It must be sent as an inline JSON string. +* Sends read receipts on login (vector-im/riot-ios/issues/1918). + +API break: +* MXSession: [MXSession startWithMessagesLimit] has been removed. Use the more generic [MXSession startWithSyncFilter:]. +* MXRoom: liveTimeline and state accesses are now asynchronous. +* MXCall: callee access is now asynchronous. +* MXRoomState: Remove displayName property. Use MXRoomSummary.displayName instead. +* MXRoomState: Create a MXRoomMembers property. All members getter methods has been to the new class. +* MXStore: Make the stateOfRoom method asynchronous. +* MXRestClient: contextOfEvent: Add a filter parameter. + Changes in Matrix iOS SDK in 0.10.12 (2018-05-31) =============================================== diff --git a/MatrixSDK.podspec b/MatrixSDK.podspec index 989bd61328..2bcd9a5d58 100644 --- a/MatrixSDK.podspec +++ b/MatrixSDK.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "MatrixSDK" - s.version = "0.10.12" + s.version = "0.11.0" s.summary = "The iOS SDK to build apps compatible with Matrix (https://www.matrix.org)" s.description = <<-DESC @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.author = { "matrix.org" => "support@matrix.org" } s.social_media_url = "http://twitter.com/matrixdotorg" - s.source = { :git => "https://github.com/matrix-org/matrix-ios-sdk.git", :tag => "v0.10.12" } + s.source = { :git => "https://github.com/matrix-org/matrix-ios-sdk.git", :tag => "v#{s.version}" } s.requires_arc = true @@ -35,8 +35,8 @@ Pod::Spec.new do |s| ss.dependency 'GZIP', '~> 1.2.1' # Requirements for e2e encryption - ss.dependency 'OLMKit', '~> 2.2.2' - ss.dependency 'Realm', '~> 3.6.0' + ss.dependency 'OLMKit', '~> 2.3.0' + ss.dependency 'Realm', '~> 3.7.4' end s.subspec 'JingleCallStack' do |ss| @@ -65,4 +65,10 @@ Pod::Spec.new do |s| ss.ios.dependency 'GoogleAnalytics' end + s.subspec 'SwiftSupport' do |ss| + ss.source_files = "MatrixSDK", "MatrixSDK/**/*.{swift}" + + ss.dependency 'MatrixSDK/Core' + end + end diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 8759ac8a44..82325aed3e 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -54,8 +54,6 @@ 3233606F1A403A0D0071A488 /* MXFileStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 3233606D1A403A0D0071A488 /* MXFileStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; 323360701A403A0D0071A488 /* MXFileStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 3233606E1A403A0D0071A488 /* MXFileStore.m */; }; 323C5A081A70E53500FB0549 /* MXToolsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 323C5A071A70E53500FB0549 /* MXToolsTests.m */; }; - 323E0C571A2F6E7D00A31D73 /* MXRoomPowerLevels.h in Headers */ = {isa = PBXBuildFile; fileRef = 323E0C551A2F6E7D00A31D73 /* MXRoomPowerLevels.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 323E0C581A2F6E7D00A31D73 /* MXRoomPowerLevels.m in Sources */ = {isa = PBXBuildFile; fileRef = 323E0C561A2F6E7D00A31D73 /* MXRoomPowerLevels.m */; }; 323E0C5B1A306D7A00A31D73 /* MXEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 323E0C591A306D7A00A31D73 /* MXEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 323E0C5C1A306D7A00A31D73 /* MXEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 323E0C5A1A306D7A00A31D73 /* MXEvent.m */; }; 323EF7471C7CB4C7000DC98C /* MXEventTimelineTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 323EF7461C7CB4C7000DC98C /* MXEventTimelineTests.m */; }; @@ -84,6 +82,10 @@ 325D1C261DFECE0D0070B8BF /* MXCrypto_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 325D1C251DFECE0D0070B8BF /* MXCrypto_Private.h */; }; 326056851C76FDF2009D44AD /* MXEventTimeline.h in Headers */ = {isa = PBXBuildFile; fileRef = 326056831C76FDF1009D44AD /* MXEventTimeline.h */; settings = {ATTRIBUTES = (Public, ); }; }; 326056861C76FDF2009D44AD /* MXEventTimeline.m in Sources */ = {isa = PBXBuildFile; fileRef = 326056841C76FDF1009D44AD /* MXEventTimeline.m */; }; + 32618E7120ED2DF500E1D2EA /* MXFilterJSONModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 32618E6F20ED2DF500E1D2EA /* MXFilterJSONModel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32618E7220ED2DF500E1D2EA /* MXFilterJSONModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 32618E7020ED2DF500E1D2EA /* MXFilterJSONModel.m */; }; + 32618E7B20EFA45B00E1D2EA /* MXRoomMembers.h in Headers */ = {isa = PBXBuildFile; fileRef = 32618E7920EFA45B00E1D2EA /* MXRoomMembers.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32618E7C20EFA45B00E1D2EA /* MXRoomMembers.m in Sources */ = {isa = PBXBuildFile; fileRef = 32618E7A20EFA45B00E1D2EA /* MXRoomMembers.m */; }; 32637ED41E5B00400011E20D /* MXDeviceList.h in Headers */ = {isa = PBXBuildFile; fileRef = 32637ED21E5B00400011E20D /* MXDeviceList.h */; }; 32637ED51E5B00400011E20D /* MXDeviceList.m in Sources */ = {isa = PBXBuildFile; fileRef = 32637ED31E5B00400011E20D /* MXDeviceList.m */; }; 3264DB911CEC528D00B99881 /* MXAccountData.h in Headers */ = {isa = PBXBuildFile; fileRef = 3264DB8F1CEC528D00B99881 /* MXAccountData.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -92,6 +94,7 @@ 3265CB381A14C43E00E24B2F /* MXRoomState.h in Headers */ = {isa = PBXBuildFile; fileRef = 3265CB361A14C43E00E24B2F /* MXRoomState.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3265CB391A14C43E00E24B2F /* MXRoomState.m in Sources */ = {isa = PBXBuildFile; fileRef = 3265CB371A14C43E00E24B2F /* MXRoomState.m */; }; 3265CB3B1A151C3800E24B2F /* MXRoomStateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3265CB3A1A151C3800E24B2F /* MXRoomStateTests.m */; }; + 32684CB821085F770046D2F9 /* MXLazyLoadingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32684CB721085F770046D2F9 /* MXLazyLoadingTests.m */; }; 326D1EF51BFC79300030947B /* MXPushRuleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 326D1EF41BFC79300030947B /* MXPushRuleTests.m */; }; 327137241A24BDDE00DB6757 /* MXUserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 327137231A24BDDE00DB6757 /* MXUserTests.m */; }; 327137271A24D50A00DB6757 /* MXMyUser.h in Headers */ = {isa = PBXBuildFile; fileRef = 327137251A24D50A00DB6757 /* MXMyUser.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -160,13 +163,15 @@ 32A31BBE20D3F2EC005916C7 /* MXFilterObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 32A31BBC20D3F2EC005916C7 /* MXFilterObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32A31BBF20D3F2EC005916C7 /* MXFilterObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 32A31BBD20D3F2EC005916C7 /* MXFilterObject.m */; }; 32A31BC120D3F4C4005916C7 /* MXFilterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32A31BC020D3F4C4005916C7 /* MXFilterTests.m */; }; - 32A31BC420D3FFB0005916C7 /* MXFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 32A31BC220D3FFB0005916C7 /* MXFilter.h */; }; + 32A31BC420D3FFB0005916C7 /* MXFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 32A31BC220D3FFB0005916C7 /* MXFilter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32A31BC520D3FFB0005916C7 /* MXFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 32A31BC320D3FFB0005916C7 /* MXFilter.m */; }; - 32A31BC820D401FC005916C7 /* MXRoomFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 32A31BC620D401FC005916C7 /* MXRoomFilter.h */; }; + 32A31BC820D401FC005916C7 /* MXRoomFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 32A31BC620D401FC005916C7 /* MXRoomFilter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32A31BC920D401FC005916C7 /* MXRoomFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 32A31BC720D401FC005916C7 /* MXRoomFilter.m */; }; 32A9E8241EF4026E0081358A /* MXBackgroundModeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 32A9E8211EF4026E0081358A /* MXBackgroundModeHandler.h */; }; 32A9E8251EF4026E0081358A /* MXUIKitBackgroundModeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 32A9E8221EF4026E0081358A /* MXUIKitBackgroundModeHandler.h */; }; 32A9E8261EF4026E0081358A /* MXUIKitBackgroundModeHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 32A9E8231EF4026E0081358A /* MXUIKitBackgroundModeHandler.m */; }; + 32B76EA320FDE2BE00B095F6 /* MXRoomMembersCount.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B76EA220FDE2BE00B095F6 /* MXRoomMembersCount.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32B76EA520FDE85100B095F6 /* MXRoomMembersCount.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B76EA420FDE85100B095F6 /* MXRoomMembersCount.m */; }; 32BD34BE1E84134A006EDC0D /* MatrixSDKTestsE2EData.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BD34BD1E84134A006EDC0D /* MatrixSDKTestsE2EData.m */; }; 32BED28F1B00A23F00E668FE /* MXCallStack.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BED28E1B00A23F00E668FE /* MXCallStack.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32C235721F827F3800E38FC5 /* MXRoomOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 32C235701F827F3800E38FC5 /* MXRoomOperation.h */; }; @@ -215,6 +220,18 @@ 9274AFE81EE580240009BEB6 /* MXCallKitAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = 9274AFE61EE580240009BEB6 /* MXCallKitAdapter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9274AFE91EE580240009BEB6 /* MXCallKitAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 9274AFE71EE580240009BEB6 /* MXCallKitAdapter.m */; }; A23A8594855481FEFA0E9A22 /* libPods-MatrixSDK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E1674C6FF8BBF074E7F76059 /* libPods-MatrixSDK.a */; }; + B17285792100C8EA0052C51E /* MXSendReplyEventStringsLocalizable.h in Headers */ = {isa = PBXBuildFile; fileRef = B17285782100C88A0052C51E /* MXSendReplyEventStringsLocalizable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B172857C2100D4F60052C51E /* MXSendReplyEventDefaultStringLocalizations.h in Headers */ = {isa = PBXBuildFile; fileRef = B172857A2100D4F60052C51E /* MXSendReplyEventDefaultStringLocalizations.h */; }; + B172857D2100D4F60052C51E /* MXSendReplyEventDefaultStringLocalizations.m in Sources */ = {isa = PBXBuildFile; fileRef = B172857B2100D4F60052C51E /* MXSendReplyEventDefaultStringLocalizations.m */; }; + B17982F52119E4A2001FD722 /* MXRoomCreateContent.h in Headers */ = {isa = PBXBuildFile; fileRef = B17982ED2119E49E001FD722 /* MXRoomCreateContent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B17982F62119E4A2001FD722 /* MXRoomTombStoneContent.m in Sources */ = {isa = PBXBuildFile; fileRef = B17982EE2119E49F001FD722 /* MXRoomTombStoneContent.m */; }; + B17982F72119E4A2001FD722 /* MXRoomPredecessorInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = B17982EF2119E49F001FD722 /* MXRoomPredecessorInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B17982F82119E4A2001FD722 /* MXRoomPowerLevels.h in Headers */ = {isa = PBXBuildFile; fileRef = B17982F02119E4A0001FD722 /* MXRoomPowerLevels.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B17982F92119E4A2001FD722 /* MXRoomTombStoneContent.h in Headers */ = {isa = PBXBuildFile; fileRef = B17982F12119E4A0001FD722 /* MXRoomTombStoneContent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B17982FA2119E4A2001FD722 /* MXRoomCreateContent.m in Sources */ = {isa = PBXBuildFile; fileRef = B17982F22119E4A1001FD722 /* MXRoomCreateContent.m */; }; + B17982FB2119E4A2001FD722 /* MXRoomPredecessorInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = B17982F32119E4A1001FD722 /* MXRoomPredecessorInfo.m */; }; + B17982FC2119E4A2001FD722 /* MXRoomPowerLevels.m in Sources */ = {isa = PBXBuildFile; fileRef = B17982F42119E4A2001FD722 /* MXRoomPowerLevels.m */; }; + B1798304211B3649001FD722 /* MXRoomSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1798303211B3649001FD722 /* MXRoomSummary.swift */; }; C60165381E3AA57900B92CFA /* MXSDKOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = F0C34CB91C18C80000C36F09 /* MXSDKOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; C602B58C1F2268F700B67D87 /* MXRoom.swift in Sources */ = {isa = PBXBuildFile; fileRef = C602B58B1F2268F700B67D87 /* MXRoom.swift */; }; C602B58E1F22A8D700B67D87 /* MXImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C602B58D1F22A8D700B67D87 /* MXImage.swift */; }; @@ -321,8 +338,6 @@ 3233606D1A403A0D0071A488 /* MXFileStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXFileStore.h; sourceTree = ""; }; 3233606E1A403A0D0071A488 /* MXFileStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXFileStore.m; sourceTree = ""; }; 323C5A071A70E53500FB0549 /* MXToolsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXToolsTests.m; sourceTree = ""; }; - 323E0C551A2F6E7D00A31D73 /* MXRoomPowerLevels.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomPowerLevels.h; sourceTree = ""; }; - 323E0C561A2F6E7D00A31D73 /* MXRoomPowerLevels.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomPowerLevels.m; sourceTree = ""; }; 323E0C591A306D7A00A31D73 /* MXEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXEvent.h; sourceTree = ""; }; 323E0C5A1A306D7A00A31D73 /* MXEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXEvent.m; sourceTree = ""; }; 323EF7461C7CB4C7000DC98C /* MXEventTimelineTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXEventTimelineTests.m; sourceTree = ""; }; @@ -351,6 +366,10 @@ 325D1C251DFECE0D0070B8BF /* MXCrypto_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXCrypto_Private.h; sourceTree = ""; }; 326056831C76FDF1009D44AD /* MXEventTimeline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXEventTimeline.h; sourceTree = ""; }; 326056841C76FDF1009D44AD /* MXEventTimeline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXEventTimeline.m; sourceTree = ""; }; + 32618E6F20ED2DF500E1D2EA /* MXFilterJSONModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXFilterJSONModel.h; sourceTree = ""; }; + 32618E7020ED2DF500E1D2EA /* MXFilterJSONModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXFilterJSONModel.m; sourceTree = ""; }; + 32618E7920EFA45B00E1D2EA /* MXRoomMembers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXRoomMembers.h; sourceTree = ""; }; + 32618E7A20EFA45B00E1D2EA /* MXRoomMembers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRoomMembers.m; sourceTree = ""; }; 32637ED21E5B00400011E20D /* MXDeviceList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXDeviceList.h; sourceTree = ""; }; 32637ED31E5B00400011E20D /* MXDeviceList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXDeviceList.m; sourceTree = ""; }; 3264DB8F1CEC528D00B99881 /* MXAccountData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXAccountData.h; sourceTree = ""; }; @@ -359,6 +378,7 @@ 3265CB361A14C43E00E24B2F /* MXRoomState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomState.h; sourceTree = ""; }; 3265CB371A14C43E00E24B2F /* MXRoomState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomState.m; sourceTree = ""; }; 3265CB3A1A151C3800E24B2F /* MXRoomStateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomStateTests.m; sourceTree = ""; }; + 32684CB721085F770046D2F9 /* MXLazyLoadingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLazyLoadingTests.m; sourceTree = ""; }; 326D1EF41BFC79300030947B /* MXPushRuleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXPushRuleTests.m; sourceTree = ""; }; 327137231A24BDDE00DB6757 /* MXUserTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXUserTests.m; sourceTree = ""; }; 327137251A24D50A00DB6757 /* MXMyUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXMyUser.h; sourceTree = ""; }; @@ -438,6 +458,8 @@ 32A9E8211EF4026E0081358A /* MXBackgroundModeHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXBackgroundModeHandler.h; sourceTree = ""; }; 32A9E8221EF4026E0081358A /* MXUIKitBackgroundModeHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXUIKitBackgroundModeHandler.h; sourceTree = ""; }; 32A9E8231EF4026E0081358A /* MXUIKitBackgroundModeHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXUIKitBackgroundModeHandler.m; sourceTree = ""; }; + 32B76EA220FDE2BE00B095F6 /* MXRoomMembersCount.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXRoomMembersCount.h; sourceTree = ""; }; + 32B76EA420FDE85100B095F6 /* MXRoomMembersCount.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRoomMembersCount.m; sourceTree = ""; }; 32BD34BC1E84134A006EDC0D /* MatrixSDKTestsE2EData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MatrixSDKTestsE2EData.h; sourceTree = ""; }; 32BD34BD1E84134A006EDC0D /* MatrixSDKTestsE2EData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MatrixSDKTestsE2EData.m; sourceTree = ""; }; 32BED28E1B00A23F00E668FE /* MXCallStack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXCallStack.h; sourceTree = ""; }; @@ -492,6 +514,18 @@ 92634B811EF2E3C400DB9F60 /* MXCallKitConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXCallKitConfiguration.m; sourceTree = ""; }; 9274AFE61EE580240009BEB6 /* MXCallKitAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXCallKitAdapter.h; sourceTree = ""; }; 9274AFE71EE580240009BEB6 /* MXCallKitAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXCallKitAdapter.m; sourceTree = ""; }; + B17285782100C88A0052C51E /* MXSendReplyEventStringsLocalizable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXSendReplyEventStringsLocalizable.h; sourceTree = ""; }; + B172857A2100D4F60052C51E /* MXSendReplyEventDefaultStringLocalizations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXSendReplyEventDefaultStringLocalizations.h; sourceTree = ""; }; + B172857B2100D4F60052C51E /* MXSendReplyEventDefaultStringLocalizations.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXSendReplyEventDefaultStringLocalizations.m; sourceTree = ""; }; + B17982ED2119E49E001FD722 /* MXRoomCreateContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomCreateContent.h; sourceTree = ""; }; + B17982EE2119E49F001FD722 /* MXRoomTombStoneContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomTombStoneContent.m; sourceTree = ""; }; + B17982EF2119E49F001FD722 /* MXRoomPredecessorInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomPredecessorInfo.h; sourceTree = ""; }; + B17982F02119E4A0001FD722 /* MXRoomPowerLevels.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomPowerLevels.h; sourceTree = ""; }; + B17982F12119E4A0001FD722 /* MXRoomTombStoneContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomTombStoneContent.h; sourceTree = ""; }; + B17982F22119E4A1001FD722 /* MXRoomCreateContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomCreateContent.m; sourceTree = ""; }; + B17982F32119E4A1001FD722 /* MXRoomPredecessorInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomPredecessorInfo.m; sourceTree = ""; }; + B17982F42119E4A2001FD722 /* MXRoomPowerLevels.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomPowerLevels.m; sourceTree = ""; }; + B1798303211B3649001FD722 /* MXRoomSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomSummary.swift; sourceTree = ""; }; B1F1AE550CF4C15B653DDE6E /* Pods-MatrixSDKTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MatrixSDKTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MatrixSDKTests/Pods-MatrixSDKTests.debug.xcconfig"; sourceTree = ""; }; C602B58B1F2268F700B67D87 /* MXRoom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXRoom.swift; sourceTree = ""; }; C602B58D1F22A8D700B67D87 /* MXImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXImage.swift; sourceTree = ""; }; @@ -593,6 +627,10 @@ 32E226A51D06AC9F00E6CA54 /* MXPeekingRoom.m */, 3265CB361A14C43E00E24B2F /* MXRoomState.h */, 3265CB371A14C43E00E24B2F /* MXRoomState.m */, + 32618E7920EFA45B00E1D2EA /* MXRoomMembers.h */, + 32618E7A20EFA45B00E1D2EA /* MXRoomMembers.m */, + 32B76EA220FDE2BE00B095F6 /* MXRoomMembersCount.h */, + 32B76EA420FDE85100B095F6 /* MXRoomMembersCount.m */, 32481A821C03572900782AD3 /* MXRoomAccountData.h */, 32481A831C03572900782AD3 /* MXRoomAccountData.m */, 3220093619EFA4C9008DE41D /* MXEventListener.h */, @@ -613,6 +651,9 @@ 329FB17E1A0B665800A5E88E /* MXUser.m */, 327137251A24D50A00DB6757 /* MXMyUser.h */, 327137261A24D50A00DB6757 /* MXMyUser.m */, + B17285782100C88A0052C51E /* MXSendReplyEventStringsLocalizable.h */, + B172857A2100D4F60052C51E /* MXSendReplyEventDefaultStringLocalizations.h */, + B172857B2100D4F60052C51E /* MXSendReplyEventDefaultStringLocalizations.m */, ); path = Data; sourceTree = ""; @@ -721,6 +762,8 @@ 32A31BC720D401FC005916C7 /* MXRoomFilter.m */, 323F3F9220D3F0C700D26D6A /* MXRoomEventFilter.h */, 323F3F9120D3F0C700D26D6A /* MXRoomEventFilter.m */, + 32618E6F20ED2DF500E1D2EA /* MXFilterJSONModel.h */, + 32618E7020ED2DF500E1D2EA /* MXFilterJSONModel.m */, ); path = Filters; sourceTree = ""; @@ -786,8 +829,14 @@ 3281E8B619E42DFE00976E1A /* MXJSONModels.m */, 323E0C591A306D7A00A31D73 /* MXEvent.h */, 323E0C5A1A306D7A00A31D73 /* MXEvent.m */, - 323E0C551A2F6E7D00A31D73 /* MXRoomPowerLevels.h */, - 323E0C561A2F6E7D00A31D73 /* MXRoomPowerLevels.m */, + B17982F02119E4A0001FD722 /* MXRoomPowerLevels.h */, + B17982F42119E4A2001FD722 /* MXRoomPowerLevels.m */, + B17982F12119E4A0001FD722 /* MXRoomTombStoneContent.h */, + B17982EE2119E49F001FD722 /* MXRoomTombStoneContent.m */, + B17982ED2119E49E001FD722 /* MXRoomCreateContent.h */, + B17982F22119E4A1001FD722 /* MXRoomCreateContent.m */, + B17982EF2119E49F001FD722 /* MXRoomPredecessorInfo.h */, + B17982F32119E4A1001FD722 /* MXRoomPredecessorInfo.m */, ); path = JSONModels; sourceTree = ""; @@ -962,6 +1011,7 @@ 324BE45A1E3FA7A8008D99D4 /* MXMegolmExportEncryptionTest.m */, C6CC43261E5CB0FD00DB9C34 /* MatrixSDKTests-Bridging-Header.h */, C61A4AF31E5DD88400442158 /* Dummy.swift */, + 32684CB721085F770046D2F9 /* MXLazyLoadingTests.m */, ); path = MatrixSDKTests; sourceTree = ""; @@ -1088,6 +1138,7 @@ C6F935831E5B3BE600FC34BF /* MX3PID.swift */, C6F935841E5B3BE600FC34BF /* MXEventTimeline.swift */, C6481AF11F1678A9000DB8A0 /* MXSessionEventListener.swift */, + B1798303211B3649001FD722 /* MXRoomSummary.swift */, ); path = Data; sourceTree = ""; @@ -1137,6 +1188,7 @@ 32A1515B1DB525DA00400192 /* NSObject+sortedKeys.h in Headers */, 329D3E621E251027002E2F1E /* MXRoomSummaryUpdater.h in Headers */, 321B413F1E09937E009EEEC7 /* MXRoomSummary.h in Headers */, + B17982F52119E4A2001FD722 /* MXRoomCreateContent.h in Headers */, 32DC15CF1A8CF7AE006F9AD3 /* MXPushRuleConditionChecker.h in Headers */, 327187851DA7D0220071C818 /* MXOlmDecryption.h in Headers */, 32D7767D1A27860600FC4AA2 /* MXMemoryStore.h in Headers */, @@ -1150,6 +1202,7 @@ 323E0C5B1A306D7A00A31D73 /* MXEvent.h in Headers */, 327F8DB21C6112BA00581CA3 /* MXRoomThirdPartyInvite.h in Headers */, 92634B821EF2E3C400DB9F60 /* MXCallKitConfiguration.h in Headers */, + 32618E7120ED2DF500E1D2EA /* MXFilterJSONModel.h in Headers */, 322360521A8E610500A3CA81 /* MXPushRuleDisplayNameCondtionChecker.h in Headers */, F03EF5001DF014D9009DF592 /* MXMediaManager.h in Headers */, 3245A7521AF7B2930001D8A7 /* MXCallManager.h in Headers */, @@ -1157,6 +1210,7 @@ 92634B7F1EF2A37A00DB9F60 /* MXCallAudioSessionConfigurator.h in Headers */, 3291D4D41A68FFEB00C3BA41 /* MXFileRoomStore.h in Headers */, 320BBF431D6C81550079890E /* MXEventsEnumeratorOnArray.h in Headers */, + 32B76EA320FDE2BE00B095F6 /* MXRoomMembersCount.h in Headers */, 32C6F93319DD814400EA4E9C /* MatrixSDK.h in Headers */, 324BE46C1E422766008D99D4 /* MXMegolmSessionData.h in Headers */, 327187891DA7DCE50071C818 /* MXOlmEncryption.h in Headers */, @@ -1164,12 +1218,12 @@ 320BBF3C1D6C7D9D0079890E /* MXEventsEnumerator.h in Headers */, 32637ED41E5B00400011E20D /* MXDeviceList.h in Headers */, 32F9FA7D1DBA0CF0009D98A6 /* MXDecryptionResult.h in Headers */, - 323E0C571A2F6E7D00A31D73 /* MXRoomPowerLevels.h in Headers */, 3245A7501AF7B2930001D8A7 /* MXCall.h in Headers */, 3271877D1DA7CB2F0071C818 /* MXDecrypting.h in Headers */, 32114A851A262CE000FF2EC4 /* MXStore.h in Headers */, 32A151391DAD292400400192 /* MXMegolmEncryption.h in Headers */, F03EF5041DF01596009DF592 /* MXLRUCache.h in Headers */, + B172857C2100D4F60052C51E /* MXSendReplyEventDefaultStringLocalizations.h in Headers */, 329FB1791A0A74B100A5E88E /* MXTools.h in Headers */, 322691321E5EF77D00966A6E /* MXDeviceListOperation.h in Headers */, 32481A841C03572900782AD3 /* MXRoomAccountData.h in Headers */, @@ -1179,6 +1233,7 @@ 323F3F9420D3F0C700D26D6A /* MXRoomEventFilter.h in Headers */, 32A9E8251EF4026E0081358A /* MXUIKitBackgroundModeHandler.h in Headers */, 325D1C261DFECE0D0070B8BF /* MXCrypto_Private.h in Headers */, + B17285792100C8EA0052C51E /* MXSendReplyEventStringsLocalizable.h in Headers */, 32A151521DAF8A7200400192 /* MXQueuedEncryption.h in Headers */, 32A30B181FB4813400C8309E /* MXIncomingRoomKeyRequestManager.h in Headers */, F03EF5081DF071D5009DF592 /* MXEncryptedAttachments.h in Headers */, @@ -1192,16 +1247,19 @@ 324BE4681E3FADB1008D99D4 /* MXMegolmExportEncryption.h in Headers */, 32CAB10B1A925B41008C5BB9 /* MXHTTPOperation.h in Headers */, 32F945F81FAB83D900622468 /* MXIncomingRoomKeyRequestCancellation.h in Headers */, + 32618E7B20EFA45B00E1D2EA /* MXRoomMembers.h in Headers */, 3240969D1F9F751600DBA607 /* MXPushRuleSenderNotificationPermissionConditionChecker.h in Headers */, 32F634AB1FC5E3480054EF49 /* MXEventDecryptionResult.h in Headers */, 322A51C71D9BBD3C00C8536D /* MXOlmDevice.h in Headers */, 320DFDE419DD99B60068622A /* MXRestClient.h in Headers */, 3265CB381A14C43E00E24B2F /* MXRoomState.h in Headers */, 3283F7781EAF30F700C1688C /* MXBugReportRestClient.h in Headers */, + B17982F82119E4A2001FD722 /* MXRoomPowerLevels.h in Headers */, 32DC15D41A8CF874006F9AD3 /* MXPushRuleEventMatchConditionChecker.h in Headers */, 32A151261DABB0CB00400192 /* MXMegolmDecryption.h in Headers */, 3220094519EFBF30008DE41D /* MXSessionEventListener.h in Headers */, 32A9E8241EF4026E0081358A /* MXBackgroundModeHandler.h in Headers */, + B17982F72119E4A2001FD722 /* MXRoomPredecessorInfo.h in Headers */, 32BED28F1B00A23F00E668FE /* MXCallStack.h in Headers */, F03EF4FE1DF014D9009DF592 /* MXMediaLoader.h in Headers */, 3264DB911CEC528D00B99881 /* MXAccountData.h in Headers */, @@ -1210,6 +1268,7 @@ 329FB1751A0A3A1600A5E88E /* MXRoomMember.h in Headers */, 320DFDE019DD99B60068622A /* MXSession.h in Headers */, 324095221AFA432F00D81C97 /* MXCallStackCall.h in Headers */, + B17982F92119E4A2001FD722 /* MXRoomTombStoneContent.h in Headers */, 32A31BBE20D3F2EC005916C7 /* MXFilterObject.h in Headers */, F082946D1DB66C3D00CEAB63 /* MXInvite3PID.h in Headers */, 3233606F1A403A0D0071A488 /* MXFileStore.h in Headers */, @@ -1379,7 +1438,7 @@ F082946E1DB66C3D00CEAB63 /* MXInvite3PID.m in Sources */, 322360531A8E610500A3CA81 /* MXPushRuleDisplayNameCondtionChecker.m in Sources */, 32A31BC520D3FFB0005916C7 /* MXFilter.m in Sources */, - 323E0C581A2F6E7D00A31D73 /* MXRoomPowerLevels.m in Sources */, + 32B76EA520FDE85100B095F6 /* MXRoomMembersCount.m in Sources */, F03EF4FF1DF014D9009DF592 /* MXMediaLoader.m in Sources */, 32DC15D51A8CF874006F9AD3 /* MXPushRuleEventMatchConditionChecker.m in Sources */, 320BBF411D6C81550079890E /* MXEventsByTypesEnumeratorOnArray.m in Sources */, @@ -1388,6 +1447,7 @@ 32CAB1081A91EA34008C5BB9 /* MXPushRuleRoomMemberCountConditionChecker.m in Sources */, 3245A7511AF7B2930001D8A7 /* MXCall.m in Sources */, 327F8DB31C6112BA00581CA3 /* MXRoomThirdPartyInvite.m in Sources */, + B17982FC2119E4A2001FD722 /* MXRoomPowerLevels.m in Sources */, 32C235731F827F3800E38FC5 /* MXRoomOperation.m in Sources */, 322A51C41D9AC8FE00C8536D /* MXCryptoAlgorithms.m in Sources */, 329FB1761A0A3A1600A5E88E /* MXRoomMember.m in Sources */, @@ -1422,6 +1482,7 @@ 3264DB921CEC528D00B99881 /* MXAccountData.m in Sources */, 322691331E5EF77D00966A6E /* MXDeviceListOperation.m in Sources */, 3283F7791EAF30F700C1688C /* MXBugReportRestClient.m in Sources */, + B1798304211B3649001FD722 /* MXRoomSummary.swift in Sources */, 9274AFE91EE580240009BEB6 /* MXCallKitAdapter.m in Sources */, C63E78B01F26588000AC692F /* MXRoomPowerLevels.swift in Sources */, 323E0C5C1A306D7A00A31D73 /* MXEvent.m in Sources */, @@ -1434,7 +1495,9 @@ 32A31BC920D401FC005916C7 /* MXRoomFilter.m in Sources */, 32A151471DAF7C0C00400192 /* MXDeviceInfo.m in Sources */, 32A1515C1DB525DA00400192 /* NSObject+sortedKeys.m in Sources */, + 32618E7C20EFA45B00E1D2EA /* MXRoomMembers.m in Sources */, F03EF5091DF071D5009DF592 /* MXEncryptedAttachments.m in Sources */, + B172857D2100D4F60052C51E /* MXSendReplyEventDefaultStringLocalizations.m in Sources */, 32FA10CF1FA1C9F700E54233 /* MXOutgoingRoomKeyRequest.m in Sources */, 32322A4C1E575F65005DD155 /* MXAllowedCertificates.m in Sources */, F03EF5051DF01596009DF592 /* MXLRUCache.m in Sources */, @@ -1448,10 +1511,12 @@ 320DFDE719DD99B60068622A /* MXHTTPClient.m in Sources */, 32FE41371D0AB7070060835E /* MXEnumConstants.m in Sources */, 320DFDE119DD99B60068622A /* MXSession.m in Sources */, + B17982F62119E4A2001FD722 /* MXRoomTombStoneContent.m in Sources */, C602B58E1F22A8D700B67D87 /* MXImage.swift in Sources */, 32A151491DAF7C0C00400192 /* MXKey.m in Sources */, F0C34CBB1C18C93700C36F09 /* MXSDKOptions.m in Sources */, 320BBF441D6C81550079890E /* MXEventsEnumeratorOnArray.m in Sources */, + 32618E7220ED2DF500E1D2EA /* MXFilterJSONModel.m in Sources */, 320DFDDC19DD99B60068622A /* MXRoom.m in Sources */, F08B8D5D1E014711006171A8 /* NSData+MatrixSDK.m in Sources */, 3291D4D51A68FFEB00C3BA41 /* MXFileRoomStore.m in Sources */, @@ -1464,8 +1529,10 @@ 32A1514F1DAF897600400192 /* MXOlmSessionResult.m in Sources */, C6F9357C1E5B39CA00FC34BF /* MXRestClient.swift in Sources */, 322A51B71D9AB15900C8536D /* MXCrypto.m in Sources */, + B17982FB2119E4A2001FD722 /* MXRoomPredecessorInfo.m in Sources */, 32DC15D11A8CF7AE006F9AD3 /* MXNotificationCenter.m in Sources */, 32637ED51E5B00400011E20D /* MXDeviceList.m in Sources */, + B17982FA2119E4A2001FD722 /* MXRoomCreateContent.m in Sources */, 32A9E8261EF4026E0081358A /* MXUIKitBackgroundModeHandler.m in Sources */, 322A51C81D9BBD3C00C8536D /* MXOlmDevice.m in Sources */, 329FB17A1A0A74B100A5E88E /* MXTools.m in Sources */, @@ -1487,6 +1554,7 @@ 3281E8A819E41A2000976E1A /* MatrixSDKTestsData.m in Sources */, 322A51D81D9E846800C8536D /* MXCryptoTests.m in Sources */, 32BD34BE1E84134A006EDC0D /* MatrixSDKTestsE2EData.m in Sources */, + 32684CB821085F770046D2F9 /* MXLazyLoadingTests.m in Sources */, 32832B5D1BCC048300241108 /* MXStoreMemoryStoreTests.m in Sources */, 32114A7F1A24E15500FF2EC4 /* MXMyUserTests.m in Sources */, 32832B5E1BCC048300241108 /* MXStoreNoStoreTests.m in Sources */, @@ -1700,6 +1768,7 @@ baseConfigurationReference = B1F1AE550CF4C15B653DDE6E /* Pods-MatrixSDKTests.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", @@ -1729,6 +1798,7 @@ baseConfigurationReference = 0BCFBADF157F3C8C43112BD6 /* Pods-MatrixSDKTests.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", diff --git a/MatrixSDK/Contrib/Swift/Data/MXRoom.swift b/MatrixSDK/Contrib/Swift/Data/MXRoom.swift index 7b17223419..d887b6fa7a 100644 --- a/MatrixSDK/Contrib/Swift/Data/MXRoom.swift +++ b/MatrixSDK/Contrib/Swift/Data/MXRoom.swift @@ -604,6 +604,39 @@ public extension MXRoom { return __reportEvent(eventId, score: score, reason: reason, success: currySuccess(completion), failure: curryFailure(completion)) } + /** + Send a reply to an event with text message to the room. + + It's only supported to reply to event with 'm.room.message' event type and following message types: 'm.text', 'm.text', 'm.emote', 'm.notice', 'm.image', 'm.file', 'm.video', 'm.audio'. + + - parameters: + - eventToReply: The event to reply. + - textMessage: The text to send. + - formattedTextMessage: The optional HTML formatted string of the text to send. + - stringLocalizations: String localizations used when building reply message. + - localEcho: a pointer to an MXEvent object. + + When the event type is `MXEventType.roomMessage`, this pointer is set to an actual + MXEvent object containing the local created event which should be used to echo the + message in the messages list until the resulting event comes through the server sync. + For information, the identifier of the created local event has the prefix: + `kMXEventLocalEventIdPrefix`. + + You may specify nil for this parameter if you do not want this information. + + You may provide your own MXEvent object, in this case only its send state is updated. + + When the event type is `kMXEventTypeStringRoomEncrypted`, no local event is created. + + - completion: A block object called when the operation completes. + - response: Provides the event id of the event generated on the home server on success + + - returns: a `MXHTTPOperation` instance. + */ + @nonobjc @discardableResult func sendReply(to eventToReply: MXEvent, textMessage: String, formattedTextMessage: String?, stringLocalizations: MXSendReplyEventStringsLocalizable?, localEcho: inout MXEvent?, completion: @escaping (_ response: MXResponse) -> Void) -> MXHTTPOperation { + return __sendReply(to: eventToReply, withTextMessage: textMessage, formattedTextMessage: formattedTextMessage, stringLocalizations: stringLocalizations, localEcho: &localEcho, success: currySuccess(completion), failure: curryFailure(completion)) + } + // MARK: - Room Tags Operations diff --git a/MatrixSDK/Contrib/Swift/Data/MXRoomSummary.swift b/MatrixSDK/Contrib/Swift/Data/MXRoomSummary.swift new file mode 100644 index 0000000000..4ba16aa913 --- /dev/null +++ b/MatrixSDK/Contrib/Swift/Data/MXRoomSummary.swift @@ -0,0 +1,28 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +extension MXRoomSummary { + + /// The membership state of the logged in user for this room + /// + /// If the membership is `invite`, the room state contains few information. + /// Join the room with [MXRoom join] to get full information about the room. + public var membership: MXMembership { + return MXMembership(identifier: self.__membership) + } +} diff --git a/MatrixSDK/Contrib/Swift/JSONModels/MXEvent.swift b/MatrixSDK/Contrib/Swift/JSONModels/MXEvent.swift index e472820981..e9d5650338 100644 --- a/MatrixSDK/Contrib/Swift/JSONModels/MXEvent.swift +++ b/MatrixSDK/Contrib/Swift/JSONModels/MXEvent.swift @@ -56,6 +56,7 @@ public enum MXEventType { case callAnswer case callHangup case receipt + case roomTombStone case custom(String) @@ -88,6 +89,7 @@ public enum MXEventType { case .callAnswer: return kMXEventTypeStringCallAnswer case .callHangup: return kMXEventTypeStringCallHangup case .receipt: return kMXEventTypeStringReceipt + case .roomTombStone: return kMXEventTypeStringRoomTombStone // Swift converts any constant with the suffix "Notification" as the type `Notification.Name` // The original value can be reached using the `rawValue` property. @@ -98,7 +100,7 @@ public enum MXEventType { } public init(identifier: String) { - let events: [MXEventType] = [.roomName, .roomTopic, .roomAvatar, .roomMember, .roomCreate, .roomJoinRules, .roomPowerLevels, .roomAliases, .roomCanonicalAlias, .roomEncrypted, .roomEncryption, .roomGuestAccess, .roomHistoryVisibility, .roomKey, .roomForwardedKey, .roomKeyRequest, .roomMessage, .roomMessageFeedback, .roomRedaction, .roomThirdPartyInvite, .roomTag, .presence, .typing, .callInvite, .callCandidates, .callAnswer, .callHangup, .receipt] + let events: [MXEventType] = [.roomName, .roomTopic, .roomAvatar, .roomMember, .roomCreate, .roomJoinRules, .roomPowerLevels, .roomAliases, .roomCanonicalAlias, .roomEncrypted, .roomEncryption, .roomGuestAccess, .roomHistoryVisibility, .roomKey, .roomForwardedKey, .roomKeyRequest, .roomMessage, .roomMessageFeedback, .roomRedaction, .roomThirdPartyInvite, .roomTag, .presence, .typing, .callInvite, .callCandidates, .callAnswer, .callHangup, .receipt, .roomTombStone] self = events.first(where: { $0.identifier == identifier }) ?? .custom(identifier) } } diff --git a/MatrixSDK/Contrib/Swift/MXRestClient.swift b/MatrixSDK/Contrib/Swift/MXRestClient.swift index c10647940f..29403e3afa 100644 --- a/MatrixSDK/Contrib/Swift/MXRestClient.swift +++ b/MatrixSDK/Contrib/Swift/MXRestClient.swift @@ -1208,13 +1208,14 @@ public extension MXRestClient { - eventId: the id of the event to get context around. - roomId: the id of the room to get events from. - limit: the maximum number of messages to return. + - filter the filter to pass in the request. Can be nil. - completion: A block object called when the operation completes. - response: Provides the model created from the homeserver JSON response on success. - returns: a `MXHTTPOperation` instance. */ - @nonobjc @discardableResult func context(ofEvent eventId: String, inRoom roomId: String, limit: UInt, completion: @escaping (_ response: MXResponse) -> Void) -> MXHTTPOperation { - return __context(ofEvent: eventId, inRoom: roomId, limit: limit, success: currySuccess(completion), failure: curryFailure(completion)) + @nonobjc @discardableResult func context(ofEvent eventId: String, inRoom roomId: String, limit: UInt, filter: MXRoomEventFilter? = nil, completion: @escaping (_ response: MXResponse) -> Void) -> MXHTTPOperation { + return __context(ofEvent: eventId, inRoom: roomId, limit: limit, filter:filter, success: currySuccess(completion), failure: curryFailure(completion)) } diff --git a/MatrixSDK/Contrib/Swift/MXSession.swift b/MatrixSDK/Contrib/Swift/MXSession.swift index 54ad16994d..eead7445d1 100644 --- a/MatrixSDK/Contrib/Swift/MXSession.swift +++ b/MatrixSDK/Contrib/Swift/MXSession.swift @@ -25,21 +25,16 @@ public extension MXSession { will resume the events streaming from where it had been stopped the time before. - parameters: - - limit: The number of messages to retrieve in each room. If `nil`, this preloads 10 messages. - Use this argument to use a custom limit. + - filterId: the id of the filter to use. - completion: A block object called when the operation completes. In case of failure during the initial sync, the session state is `MXSessionStateInitialSyncFailed`. - response: Indicates whether the operation was successful. */ - @nonobjc func start(withMessagesLimit limit: UInt? = nil, completion: @escaping (_ response: MXResponse) -> Void) { - if let limit = limit { - __start(withMessagesLimit: limit, onServerSyncDone: currySuccess(completion), failure: curryFailure(completion)) - } else { - __start(currySuccess(completion), failure: curryFailure(completion)) - } + @nonobjc func start(withSyncFilterId filterId: String? = nil, completion: @escaping (_ response: MXResponse) -> Void) { + __start(withSyncFilterId:filterId, onServerSyncDone: currySuccess(completion), failure: curryFailure(completion)) } - - + + /** Perform an events stream catchup in background (by keeping user offline). diff --git a/MatrixSDK/Crypto/MXCrypto.m b/MatrixSDK/Crypto/MXCrypto.m index e2dcaa43a8..3b3822c957 100644 --- a/MatrixSDK/Crypto/MXCrypto.m +++ b/MatrixSDK/Crypto/MXCrypto.m @@ -338,75 +338,86 @@ - (MXHTTPOperation *)encryptEventContent:(NSDictionary *)eventContent withType:( // just as you are sending a secret message? // XXX what about rooms where invitees can see the content? - NSMutableArray *roomMembers = [NSMutableArray array]; - for (MXRoomMember *roomMember in room.state.joinedMembers) - { - [roomMembers addObject:roomMember.userId]; - } - MXWeakify(self); - dispatch_async(_cryptoQueue, ^{ + [room state:^(MXRoomState *roomState) { MXStrongifyAndReturnIfNil(self); - NSString *algorithm; - id alg = self->roomEncryptors[room.roomId]; + MXWeakify(self); + [room members:^(MXRoomMembers *roomMembers) { + MXStrongifyAndReturnIfNil(self); - if (!alg) - { - // If the crypto has been enabled after the initialSync (the global one or the one for this room), - // the algorithm has not been initialised yet. So, do it now from room state information - algorithm = room.state.encryptionAlgorithm; - if (algorithm) + NSMutableArray *userIds = [NSMutableArray array]; + for (MXRoomMember *roomMember in roomMembers.joinedMembers) { - [self setEncryptionInRoom:room.roomId withAlgorithm:algorithm inhibitDeviceQuery:NO]; - alg = self->roomEncryptors[room.roomId]; + [userIds addObject:roomMember.userId]; } - } - else - { - // For log purpose - algorithm = NSStringFromClass(alg.class); - } - // Sanity check (we don't expect an encrypted content here). - if (alg && [eventType isEqualToString:kMXEventTypeStringRoomEncrypted] == NO) - { + MXWeakify(self); + dispatch_async(self.cryptoQueue, ^{ + MXStrongifyAndReturnIfNil(self); + + NSString *algorithm; + id alg = self->roomEncryptors[room.roomId]; + + if (!alg) + { + // If the crypto has been enabled after the initialSync (the global one or the one for this room), + // the algorithm has not been initialised yet. So, do it now from room state information + algorithm = roomState.encryptionAlgorithm; + if (algorithm) + { + [self setEncryptionInRoom:room.roomId withMembers:userIds algorithm:algorithm inhibitDeviceQuery:NO]; + alg = self->roomEncryptors[room.roomId]; + } + } + else + { + // For log purpose + algorithm = NSStringFromClass(alg.class); + } + + // Sanity check (we don't expect an encrypted content here). + if (alg && [eventType isEqualToString:kMXEventTypeStringRoomEncrypted] == NO) + { #ifdef DEBUG - NSLog(@"[MXCrypto] encryptEventContent with %@: %@", algorithm, eventContent); + NSLog(@"[MXCrypto] encryptEventContent with %@: %@", algorithm, eventContent); #endif - MXHTTPOperation *operation2 = [alg encryptEventContent:eventContent eventType:eventType forUsers:roomMembers success:^(NSDictionary *encryptedContent) { + MXHTTPOperation *operation2 = [alg encryptEventContent:eventContent eventType:eventType forUsers:userIds success:^(NSDictionary *encryptedContent) { - dispatch_async(dispatch_get_main_queue(), ^{ - success(encryptedContent, kMXEventTypeStringRoomEncrypted); - }); + dispatch_async(dispatch_get_main_queue(), ^{ + success(encryptedContent, kMXEventTypeStringRoomEncrypted); + }); - } failure:^(NSError *error) { - dispatch_async(dispatch_get_main_queue(), ^{ - failure(error); - }); - }]; + } failure:^(NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + }]; - // Mutate the HTTP operation if an HTTP is required for the encryption - if (operation2) - { - [operation mutateTo:operation2]; - } - } - else - { - NSError *error = [NSError errorWithDomain:MXDecryptingErrorDomain - code:MXDecryptingErrorUnableToEncryptCode - userInfo:@{ - NSLocalizedDescriptionKey: MXDecryptingErrorUnableToEncrypt, - NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:MXDecryptingErrorUnableToEncryptReason, algorithm] - }]; - - dispatch_async(dispatch_get_main_queue(), ^{ - failure(error); + // Mutate the HTTP operation if an HTTP is required for the encryption + if (operation2) + { + [operation mutateTo:operation2]; + } + } + else + { + NSError *error = [NSError errorWithDomain:MXDecryptingErrorDomain + code:MXDecryptingErrorUnableToEncryptCode + userInfo:@{ + NSLocalizedDescriptionKey: MXDecryptingErrorUnableToEncrypt, + NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:MXDecryptingErrorUnableToEncryptReason, algorithm] + }]; + + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } }); - } - }); + + } failure:failure]; + }]; return operation; @@ -467,73 +478,85 @@ - (MXHTTPOperation*)ensureEncryptionInRoom:(NSString*)roomId #ifdef MX_CRYPTO MXRoom *room = [_mxSession roomWithRoomId:roomId]; - if (room.state.isEncrypted) + if (room.summary.isEncrypted) { - // Get user ids in this room - NSMutableArray *userIds = [NSMutableArray array]; - for (MXRoomMember *member in room.state.joinedMembers) - { - [userIds addObject:member.userId]; - } - MXWeakify(self); - dispatch_async(_cryptoQueue, ^{ + [room state:^(MXRoomState *roomState) { MXStrongifyAndReturnIfNil(self); - NSString *algorithm; - id alg = self->roomEncryptors[room.roomId]; + MXWeakify(self); + [room members:^(MXRoomMembers *roomMembers) { + MXStrongifyAndReturnIfNil(self); - if (!alg) - { - // The algorithm has not been initialised yet. So, do it now from room state information - algorithm = room.state.encryptionAlgorithm; - if (algorithm) + // Get user ids in this room + NSMutableArray *userIds = [NSMutableArray array]; + for (MXRoomMember *member in roomMembers.joinedMembers) { - [self setEncryptionInRoom:room.roomId withAlgorithm:algorithm inhibitDeviceQuery:NO]; - alg = self->roomEncryptors[room.roomId]; + [userIds addObject:member.userId]; } - } - if (alg) - { - // Check we have everything to encrypt events - MXHTTPOperation *operation2 = [alg ensureSessionForUsers:userIds success:^(NSObject *sessionInfo) { + MXWeakify(self); + dispatch_async(self.cryptoQueue, ^{ + MXStrongifyAndReturnIfNil(self); - if (success) + NSString *algorithm; + id alg = self->roomEncryptors[room.roomId]; + + if (!alg) { - dispatch_async(dispatch_get_main_queue(), ^{ - success(); - }); + // The algorithm has not been initialised yet. So, do it now from room state information + algorithm = roomState.encryptionAlgorithm; + if (algorithm) + { + [self setEncryptionInRoom:room.roomId withMembers:userIds algorithm:algorithm inhibitDeviceQuery:NO]; + alg = self->roomEncryptors[room.roomId]; + } } - } failure:^(NSError *error) { - if (failure) + if (alg) + { + // Check we have everything to encrypt events + MXHTTPOperation *operation2 = [alg ensureSessionForUsers:userIds success:^(NSObject *sessionInfo) { + + if (success) + { + dispatch_async(dispatch_get_main_queue(), ^{ + success(); + }); + } + + } failure:^(NSError *error) { + if (failure) + { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + }]; + + if (operation2) + { + [operation mutateTo:operation2]; + } + } + else if (failure) { + NSError *error = [NSError errorWithDomain:MXDecryptingErrorDomain + code:MXDecryptingErrorUnableToEncryptCode + userInfo:@{ + NSLocalizedDescriptionKey: MXDecryptingErrorUnableToEncrypt, + NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:MXDecryptingErrorUnableToEncryptReason, algorithm] + }]; + dispatch_async(dispatch_get_main_queue(), ^{ failure(error); }); } - }]; + }); - if (operation2) - { - [operation mutateTo:operation2]; - } - } - else if (failure) - { - NSError *error = [NSError errorWithDomain:MXDecryptingErrorDomain - code:MXDecryptingErrorUnableToEncryptCode - userInfo:@{ - NSLocalizedDescriptionKey: MXDecryptingErrorUnableToEncrypt, - NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:MXDecryptingErrorUnableToEncryptReason, algorithm] - }]; - dispatch_async(dispatch_get_main_queue(), ^{ - failure(error); - }); - } - }); + } failure:failure]; + }]; } else #endif @@ -705,21 +728,7 @@ - (void)setDeviceVerification:(MXDeviceVerification)verificationStatus forDevice failure:(void (^)(NSError *error))failure { #ifdef MX_CRYPTO - - // Get all rooms with this user - NSMutableArray *userRooms = [NSMutableArray array]; - for (MXRoom *room in _mxSession.rooms) - { - if (room.state.isEncrypted) - { - MXRoomMember *member = [room.state memberWithUserId:userId]; - if (member && member.membership == MXMembershipJoin) - { - [userRooms addObject:room.roomId]; - } - } - } - + // Note: failure is not currently used but it would make sense the day device // verification will be sync'ed with the hs. MXWeakify(self); @@ -1419,7 +1428,7 @@ - (MXDeviceInfo *)eventSenderDeviceOfEvent:(MXEvent *)event return device; } -- (BOOL)setEncryptionInRoom:(NSString*)roomId withAlgorithm:(NSString*)algorithm inhibitDeviceQuery:(BOOL)inhibitDeviceQuery +- (BOOL)setEncryptionInRoom:(NSString*)roomId withMembers:(NSArray*)members algorithm:(NSString*)algorithm inhibitDeviceQuery:(BOOL)inhibitDeviceQuery { // If we already have encryption in this room, we should ignore this event // (for now at least. Maybe we should alert the user somehow?) @@ -1449,10 +1458,9 @@ - (BOOL)setEncryptionInRoom:(NSString*)roomId withAlgorithm:(NSString*)algorithm // make sure we are tracking the device lists for all users in this room. NSLog(@"[MXCrypto] setEncryptionInRoom: Enabling encryption in %@; starting to track device lists for all users therein", roomId); - MXRoom *room = [_mxSession roomWithRoomId:roomId]; - for (MXRoomMember *member in room.state.joinedMembers) + for (NSString *userId in members) { - [_deviceList startTrackingDeviceList:member.userId]; + [_deviceList startTrackingDeviceList:userId]; } if (!inhibitDeviceQuery) @@ -1816,7 +1824,7 @@ - (void)registerEventHandlers } else if (event.eventType == MXEventTypeRoomMember) { - [self onRoomMembership:event]; + [self onRoomMembership:event roomState:customObject]; } } }]; @@ -1898,20 +1906,45 @@ - (void)onRoomKeyEvent:(MXEvent*)event */ - (void)onCryptoEvent:(MXEvent*)event { - if (_cryptoQueue) + MXRoom *room = [_mxSession roomWithRoomId:event.roomId]; + + MXWeakify(self); + void (^success)(MXRoomMembers *roomMembers) = ^void(MXRoomMembers *roomMembers) { - dispatch_async(_cryptoQueue, ^{ - [self setEncryptionInRoom:event.roomId withAlgorithm:event.content[@"algorithm"] inhibitDeviceQuery:YES]; - }); - } + MXStrongifyAndReturnIfNil(self); + + NSMutableArray *members = [NSMutableArray array]; + for (MXRoomMember *roomMember in roomMembers.joinedMembers) + { + [members addObject:roomMember.userId]; + } + + if (self.cryptoQueue) + { + dispatch_async(self.cryptoQueue, ^{ + [self setEncryptionInRoom:event.roomId withMembers:members algorithm:event.content[@"algorithm"] inhibitDeviceQuery:YES]; + }); + } + }; + + [room members:^(MXRoomMembers *roomMembers) { + success(roomMembers); + } failure:^(NSError *error) { + NSLog(@"[MXCrypto] onCryptoEvent: Warning: Unable to get all members from the HS. Fallback by using lazy-loaded members"); + + [room state:^(MXRoomState *roomState) { + success(roomState.members); + }]; + }]; } /** Handle a change in the membership state of a member of a room. - @param event the membership event causing the change + @param event the membership event causing the change. + @param roomState the know state of the room when the event occurs. */ -- (void)onRoomMembership:(MXEvent*)event +- (void)onRoomMembership:(MXEvent*)event roomState:(MXRoomState*)roomState { id alg = roomEncryptors[event.roomId]; if (!alg) @@ -1921,16 +1954,16 @@ - (void)onRoomMembership:(MXEvent*)event } NSString *userId = event.stateKey; - MXRoomMember *member = [[_mxSession roomWithRoomId:event.roomId].state memberWithUserId:userId]; + MXRoomMember *member = [roomState.members memberWithUserId:userId]; if (member && member.membership == MXMembershipJoin) { NSLog(@"[MXCrypto] onRoomMembership: Join event for %@ in %@", member.userId, event.roomId); - if (_cryptoQueue) + if (self.cryptoQueue) { MXWeakify(self); - dispatch_async(_cryptoQueue, ^{ + dispatch_async(self.cryptoQueue, ^{ MXStrongifyAndReturnIfNil(self); // make sure we are tracking the deviceList for this user diff --git a/MatrixSDK/Crypto/MXCrypto_Private.h b/MatrixSDK/Crypto/MXCrypto_Private.h index 1b8aa6f4c5..c1f4118d2b 100644 --- a/MatrixSDK/Crypto/MXCrypto_Private.h +++ b/MatrixSDK/Crypto/MXCrypto_Private.h @@ -95,11 +95,12 @@ Configure a room to use encryption. @param roomId the room id to enable encryption in. + @param members a list of user ids. @param algorithm the encryption config for the room. @param inhibitDeviceQuery YES to suppress device list query for users in the room (for now) @return YES if the operation succeeds. */ -- (BOOL)setEncryptionInRoom:(NSString*)roomId withAlgorithm:(NSString*)algorithm inhibitDeviceQuery:(BOOL)inhibitDeviceQuery; +- (BOOL)setEncryptionInRoom:(NSString*)roomId withMembers:(NSArray*)members algorithm:(NSString*)algorithm inhibitDeviceQuery:(BOOL)inhibitDeviceQuery; /** Try to make sure we have established olm sessions for the given users. diff --git a/MatrixSDK/Data/Filters/MXFilterJSONModel.h b/MatrixSDK/Data/Filters/MXFilterJSONModel.h new file mode 100644 index 0000000000..050c932ca9 --- /dev/null +++ b/MatrixSDK/Data/Filters/MXFilterJSONModel.h @@ -0,0 +1,87 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXJSONModel.h" + +#import "MXFilter.h" +#import "MXRoomFilter.h" + +/** + `MXFilterJSONModel` represents a matrix filter model as exchanged through the Matrix + Client-Server API. + */ +@interface MXFilterJSONModel : MXJSONModel + +/** + List of event fields to include. If this list is absent then all fields are included. + The entries may include '.' charaters to indicate sub-fields. So ['content.body'] + will include the 'body' field of the 'content' object. + A literal '.' character in a field name may be escaped using a '\'. + A server may include more fields than were requested. + */ +@property (nonatomic) NSArray *eventFields; + +/** + The format to use for events. + 'client' will return the events in a format suitable for clients. + 'federation' will return the raw event as receieved over federation. + The default is 'client'. One of: ["client", "federation"]. + */ +@property (nonatomic) NSString *eventFormat; + +/** + The presence updates to include. + */ +@property (nonatomic) MXFilter *presence; + +/** + The user account data that isn't associated with rooms to include. + */ +@property (nonatomic) MXFilter *accountData; + +/** + Filters to be applied to room data. + */ +@property (nonatomic) MXRoomFilter *room; + + +#pragma mark - Factory + +/** + Build a Matrix filter to retrieve a max given number of messages per room in /sync requests. + + @param messageLimit messageLimit the messages count limit. + @return the Matrix filter. + */ ++ (MXFilterJSONModel*)syncFilterWithMessageLimit:(NSUInteger)messageLimit; + +/** + Build a Matrix filter to enable room members lazy loading in /sync requests. + + @return the Matrix filter. + */ ++ (MXFilterJSONModel*)syncFilterForLazyLoading; + +/** + Build a Matrix filter to enable room members lazy loading in /sync requests + with a message count limit per room. + + @param messageLimit messageLimit the messages count limit. + @return the Matrix filter. + */ ++ (MXFilterJSONModel*)syncFilterForLazyLoadingWithMessageLimit:(NSUInteger)messageLimit; + +@end diff --git a/MatrixSDK/Data/Filters/MXFilterJSONModel.m b/MatrixSDK/Data/Filters/MXFilterJSONModel.m new file mode 100644 index 0000000000..04d8d136cf --- /dev/null +++ b/MatrixSDK/Data/Filters/MXFilterJSONModel.m @@ -0,0 +1,143 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXFilterJSONModel.h" + +@implementation MXFilterJSONModel + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + MXFilterJSONModel *filter = [[MXFilterJSONModel alloc] init]; + if (filter) + { + NSDictionary *presenceFilter, *accountDataFilter, *roomFilter; + + MXJSONModelSetArray(filter.eventFields, JSONDictionary[@"event_fields"]); + MXJSONModelSetString(filter.eventFormat, JSONDictionary[@"event_format"]); + + MXJSONModelSetDictionary(presenceFilter, JSONDictionary[@"presence"]); + if (presenceFilter) + { + filter.presence = [[MXFilter alloc] initWithDictionary:presenceFilter]; + } + + MXJSONModelSetDictionary(accountDataFilter, JSONDictionary[@"account_data"]); + if (accountDataFilter) + { + filter.accountData = [[MXFilter alloc] initWithDictionary:accountDataFilter]; + } + + MXJSONModelSetDictionary(roomFilter, JSONDictionary[@"room"]); + if (roomFilter) + { + filter.room = [[MXRoomFilter alloc] initWithDictionary:roomFilter]; + } + } + + return filter; +} + +- (NSDictionary *)JSONDictionary +{ + NSMutableDictionary *JSONDictionary = [NSMutableDictionary dictionary]; + + if (_eventFields) + { + JSONDictionary[@"event_fields"] = _eventFields; + } + if (_eventFormat) + { + JSONDictionary[@"event_format"] = _eventFormat; + } + if (_presence) + { + JSONDictionary[@"presence"] = _presence.dictionary; + } + if (_accountData) + { + JSONDictionary[@"account_data"] = _accountData.dictionary; + } + if (_room) + { + JSONDictionary[@"room"] = _room.dictionary; + } + + return JSONDictionary; +} + +- (BOOL)isEqual:(id)object +{ + if (self == object) + { + return YES; + } + + BOOL isEqual = NO; + + if ([object isKindOfClass:MXFilterJSONModel.class]) + { + MXFilterJSONModel *other = object; + isEqual = [self.JSONDictionary isEqualToDictionary:other.JSONDictionary]; + } + + return isEqual; +} + + +#pragma mark - Factory + ++ (MXFilterJSONModel*)syncFilterWithMessageLimit:(NSUInteger)messageLimit +{ + MXFilterJSONModel *filter = [[MXFilterJSONModel alloc] init]; + + filter.room = [[MXRoomFilter alloc] init]; + filter.room.timeline = [[MXRoomEventFilter alloc] init]; + filter.room.timeline.limit = messageLimit; + + return filter; +} + ++ (MXFilterJSONModel*)syncFilterForLazyLoading +{ + MXFilterJSONModel *filter = [[MXFilterJSONModel alloc] init]; + + filter.room = [[MXRoomFilter alloc] init]; + filter.room.state = [[MXRoomEventFilter alloc] init]; + filter.room.state.lazyLoadMembers = YES; + + return filter; +} + ++ (MXFilterJSONModel*)syncFilterForLazyLoadingWithMessageLimit:(NSUInteger)messageLimit +{ + MXFilterJSONModel *filter = [[MXFilterJSONModel alloc] init]; + + filter.room = [[MXRoomFilter alloc] init]; + filter.room.timeline = [[MXRoomEventFilter alloc] init]; + filter.room.timeline.limit = messageLimit; + filter.room.state = [[MXRoomEventFilter alloc] init]; + filter.room.state.lazyLoadMembers = YES; + + return filter; +} + + +- (NSString *)description +{ + return self.JSONDictionary.description; +} + +@end diff --git a/MatrixSDK/Data/Filters/MXFilterObject.h b/MatrixSDK/Data/Filters/MXFilterObject.h index ed2b8114e7..74fd929e95 100644 --- a/MatrixSDK/Data/Filters/MXFilterObject.h +++ b/MatrixSDK/Data/Filters/MXFilterObject.h @@ -33,7 +33,7 @@ @property (nonatomic, readonly) NSDictionary *dictionary; /** - The JSON object representating the filter. + The JSON string representating the filter. */ @property (nonatomic, readonly) NSString *jsonString; diff --git a/MatrixSDK/Data/Filters/MXFilterObject.m b/MatrixSDK/Data/Filters/MXFilterObject.m index a1b642b214..af4f525213 100644 --- a/MatrixSDK/Data/Filters/MXFilterObject.m +++ b/MatrixSDK/Data/Filters/MXFilterObject.m @@ -47,7 +47,7 @@ - (instancetype)initWithDictionary:(NSDictionary *)theDictionary - (NSString *)jsonString { - return [MXTools serialiseJSONObject:dictionary]; + return [MXTools serialiseJSONObject:self.dictionary]; } - (NSString *)description diff --git a/MatrixSDK/Data/Filters/MXRoomEventFilter.h b/MatrixSDK/Data/Filters/MXRoomEventFilter.h index 070da5446c..4d06a7154a 100644 --- a/MatrixSDK/Data/Filters/MXRoomEventFilter.h +++ b/MatrixSDK/Data/Filters/MXRoomEventFilter.h @@ -69,4 +69,9 @@ */ @property (nonatomic) NSUInteger limit; +/** + Enable lazy loading of members + */ +@property (nonatomic) BOOL lazyLoadMembers; + @end diff --git a/MatrixSDK/Data/Filters/MXRoomEventFilter.m b/MatrixSDK/Data/Filters/MXRoomEventFilter.m index 69c1de75b1..fa273c2b4b 100644 --- a/MatrixSDK/Data/Filters/MXRoomEventFilter.m +++ b/MatrixSDK/Data/Filters/MXRoomEventFilter.m @@ -114,7 +114,7 @@ - (void)setNotSenders:(NSArray *)notSenders - (void)setLimit:(NSUInteger)limit { - dictionary[@"limit"] = [NSNumber numberWithUnsignedInteger:limit]; + dictionary[@"limit"] = @(limit); } - (NSUInteger)limit @@ -124,4 +124,17 @@ - (NSUInteger)limit return limit; } + +- (void)setLazyLoadMembers:(BOOL)lazyLoadMembers +{ + dictionary[@"lazy_load_members"] = @(lazyLoadMembers); +} + +- (BOOL)lazyLoadMembers +{ + BOOL lazyLoadMembers = NO; // Basic default value used by homeservers + MXJSONModelSetBoolean(lazyLoadMembers, dictionary[@"lazy_load_members"]); + return lazyLoadMembers; +} + @end diff --git a/MatrixSDK/Data/Filters/MXRoomFilter.m b/MatrixSDK/Data/Filters/MXRoomFilter.m index 9a2f15e924..80edfb532b 100644 --- a/MatrixSDK/Data/Filters/MXRoomFilter.m +++ b/MatrixSDK/Data/Filters/MXRoomFilter.m @@ -46,17 +46,6 @@ - (void)setNotRooms:(NSArray *)notRooms } -- (void)setEphemeral:(MXRoomEventFilter *)ephemeral -{ - dictionary[@"ephemeral"] = ephemeral.dictionary; -} - -- (MXRoomEventFilter *)ephemeral -{ - return [self roomEventFilterFor:@"ephemeral"]; -} - - - (void)setIncludeLeave:(BOOL)includeLeave { dictionary[@"include_leave"] = @(includeLeave); @@ -70,53 +59,59 @@ - (BOOL)includeLeave } -- (void)setState:(MXRoomEventFilter *)state -{ - dictionary[@"state"] = state.dictionary; -} - -- (MXRoomEventFilter *)state -{ - return [self roomEventFilterFor:@"state"]; -} - - -- (void)setTimeline:(MXRoomEventFilter *)timeline -{ - dictionary[@"timeline"] = timeline.dictionary; -} - -- (MXRoomEventFilter *)timeline -{ - return [self roomEventFilterFor:@"timeline"]; -} - +#pragma mark - MXFilterObject override -- (void)setAccountData:(MXRoomEventFilter *)accountData +- (instancetype)initWithDictionary:(NSDictionary *)dictionary { - dictionary[@"account_data"] = accountData.dictionary; -} + NSMutableDictionary *flatDictionary = [dictionary mutableCopy]; + [flatDictionary removeObjectsForKeys:@[@"ephemeral", @"state", @"timeline", @"account_data"]]; -- (MXRoomEventFilter *)accountData -{ - return [self roomEventFilterFor:@"account_data"]; + self = [super initWithDictionary:flatDictionary]; + if (self) + { + if (dictionary[@"ephemeral"]) + { + _ephemeral = [[MXRoomEventFilter alloc] initWithDictionary:dictionary[@"ephemeral"]]; + } + if (dictionary[@"state"]) + { + _state = [[MXRoomEventFilter alloc] initWithDictionary:dictionary[@"state"]]; + } + if (dictionary[@"timeline"]) + { + _timeline = [[MXRoomEventFilter alloc] initWithDictionary:dictionary[@"timeline"]]; + } + if (dictionary[@"account_data"]) + { + _accountData = [[MXRoomEventFilter alloc] initWithDictionary:dictionary[@"account_data"]]; + } + } + return self; } - -#pragma mark - Private methods - -- (MXRoomEventFilter *)roomEventFilterFor:(NSString *)key +- (NSDictionary *)dictionary { - MXRoomEventFilter *roomEventFilter; + NSMutableDictionary *flatDictionary = [super.dictionary mutableCopy]; - NSDictionary *roomEventFilterDict; - MXJSONModelSetDictionary(roomEventFilterDict, dictionary[key]); - if (roomEventFilterDict) + // And JSONify exposed models + if (_ephemeral) + { + flatDictionary[@"ephemeral"] = _ephemeral.dictionary; + } + if (_state) + { + flatDictionary[@"state"] = _state.dictionary; + } + if (_timeline) + { + flatDictionary[@"timeline"] = _timeline.dictionary; + } + if (_accountData) { - roomEventFilter = [[MXRoomEventFilter alloc] initWithDictionary:roomEventFilterDict]; + flatDictionary[@"account_data"] = _accountData.dictionary; } - return roomEventFilter; + return flatDictionary; } @end diff --git a/MatrixSDK/Data/MXEventTimeline.h b/MatrixSDK/Data/MXEventTimeline.h index d1c49789ff..411cb58784 100644 --- a/MatrixSDK/Data/MXEventTimeline.h +++ b/MatrixSDK/Data/MXEventTimeline.h @@ -113,6 +113,13 @@ typedef void (^MXOnRoomEvent)(MXEvent *event, MXTimelineDirection direction, MXR */ - (void)initialiseState:(NSArray *)stateEvents; +/** + Reset the room evenTimeline state. + + @param roomState the new state to use. + */ +- (void)setState:(MXRoomState *)roomState; + /** Release RAM memory used by the timeline. */ @@ -203,6 +210,12 @@ typedef void (^MXOnRoomEvent)(MXEvent *event, MXTimelineDirection direction, MXR */ - (void)handleInvitedRoomSync:(MXInvitedRoomSync *)invitedRoomSync; +/** + For live timeline, enrich lazy loaded state events with more state events. + + @param stateEvents state events to append. + */ +- (void)handleLazyLoadedStateEvents:(NSArray *)stateEvents; #pragma mark - Events listeners /** diff --git a/MatrixSDK/Data/MXEventTimeline.m b/MatrixSDK/Data/MXEventTimeline.m index 3032e8a5d9..7e078a38b0 100644 --- a/MatrixSDK/Data/MXEventTimeline.m +++ b/MatrixSDK/Data/MXEventTimeline.m @@ -110,10 +110,12 @@ - (id)initWithRoom:(MXRoom*)room2 initialEventId:(NSString*)initialEventId andSt - (void)initialiseState:(NSArray *)stateEvents { - for (MXEvent *event in stateEvents) - { - [self handleStateEvent:event direction:MXTimelineDirectionForwards]; - } + [self handleStateEvents:stateEvents direction:MXTimelineDirectionForwards]; +} + +- (void)setState:(MXRoomState *)roomState +{ + _state = roomState; } - (void)destroy @@ -188,9 +190,16 @@ - (MXHTTPOperation *)resetPaginationAroundInitialEventWithLimit:(NSUInteger)limi forwardsPaginationToken = nil; hasReachedHomeServerForwardsPaginationEnd = NO; + MXRoomEventFilter *filter; + if (room.mxSession.syncWithLazyLoadOfRoomMembers) + { + filter = [MXRoomEventFilter new]; + filter.lazyLoadMembers = YES; + } + // Get the context around the initial event MXWeakify(self); - return [room.mxSession.matrixRestClient contextOfEvent:_initialEventId inRoom:room.roomId limit:limit success:^(MXEventContext *eventContext) { + return [room.mxSession.matrixRestClient contextOfEvent:_initialEventId inRoom:room.roomId limit:limit filter:filter success:^(MXEventContext *eventContext) { MXStrongifyAndReturnIfNil(self); // And fill the timelime with received data @@ -302,6 +311,18 @@ - (MXHTTPOperation *)paginate:(NSUInteger)numItems direction:(MXTimelineDirectio paginationToken = forwardsPaginationToken; } + + // If the event stream runs with lazy loading, the timeline must do the same + if (room.mxSession.syncWithLazyLoadOfRoomMembers) + { + if (!_roomEventFilter) + { + _roomEventFilter = [MXRoomEventFilter new]; + } + + _roomEventFilter.lazyLoadMembers = YES; + } + NSLog(@"[MXEventTimeline] paginate : request %tu messages from the server", numItems); MXWeakify(self); @@ -365,31 +386,32 @@ - (NSUInteger)remainingMessagesForBackPaginationInStore - (void)handleJoinedRoomSync:(MXRoomSync *)roomSync { // Is it an initial sync for this room? - BOOL isRoomInitialSync = (self.state.membership == MXMembershipUnknown || self.state.membership == MXMembershipInvite); + BOOL isRoomInitialSync = (room.summary.membership == MXMembershipUnknown || room.summary.membership == MXMembershipInvite); // Check whether the room was pending on an invitation. - if (self.state.membership == MXMembershipInvite) + if (room.summary.membership == MXMembershipInvite) { // Reset the storage of this room. An initial sync of the room will be done with the provided 'roomSync'. NSLog(@"[MXEventTimeline] handleJoinedRoomSync: clean invited room from the store (%@).", self.state.roomId); [store deleteRoom:self.state.roomId]; } + // In case of lazy-loading, we may not have the membership event for our user. + // If handleJoinedRoomSync is called, the user is a joined member. + if (room.mxSession.syncWithLazyLoadOfRoomMembers && room.summary.membership != MXMembershipJoin) + { + room.summary.membership = MXMembershipJoin; + } + // Build/Update first the room state corresponding to the 'start' of the timeline. // Note: We consider it is not required to clone the existing room state here, because no notification is posted for these events. for (MXEvent *event in roomSync.state.events) { // Report the room id in the event as it is skipped in /sync response event.roomId = _state.roomId; - - [self handleStateEvent:event direction:MXTimelineDirectionForwards]; } - // Update store with new room state when all state event have been processed - if ([store respondsToSelector:@selector(storeStateForRoom:stateEvents:)]) - { - [store storeStateForRoom:_state.roomId stateEvents:_state.stateEvents]; - } + [self handleStateEvents:roomSync.state.events direction:MXTimelineDirectionForwards]; // Handle now timeline.events, the room state is updated during this step too (Note: timeline events are in chronological order) if (isRoomInitialSync) @@ -437,9 +459,6 @@ - (void)handleJoinedRoomSync:(MXRoomSync *)roomSync // Finalize initial sync if (isRoomInitialSync) { - // Initialise notification counters homeserver side - [room markAllAsRead]; - // Notify that room has been sync'ed // Delay it so that MXRoom.summary is computed before sending it dispatch_async(dispatch_get_main_queue(), ^{ @@ -460,6 +479,13 @@ - (void)handleJoinedRoomSync:(MXRoomSync *)roomSync - (void)handleInvitedRoomSync:(MXInvitedRoomSync *)invitedRoomSync { + // In case of lazy-loading, we may not have the membership event for our user. + // If handleInvitedRoomSync is called, the user is an invited member. + if (room.mxSession.syncWithLazyLoadOfRoomMembers && room.summary.membership != MXMembershipInvite) + { + room.summary.membership = MXMembershipInvite; + } + // Handle the state events forwardly (the room state will be updated, and the listeners (if any) will be notified). for (MXEvent *event in invitedRoomSync.inviteState.events) { @@ -476,6 +502,11 @@ - (void)handleInvitedRoomSync:(MXInvitedRoomSync *)invitedRoomSync } } +- (void)handleLazyLoadedStateEvents:(NSArray *)stateEvents +{ + [self handleStateEvents:stateEvents direction:MXTimelineDirectionForwards]; +} + - (void)handlePaginationResponse:(MXPaginationResponse*)paginatedResponse direction:(MXTimelineDirection)direction { // Check pagination end - @see SPEC-319 ticket @@ -492,6 +523,19 @@ - (void)handlePaginationResponse:(MXPaginationResponse*)paginatedResponse direct } } + // Process additional state events (this happens in case of lazy loading) + if (paginatedResponse.state.count) + { + if (direction == MXTimelineDirectionBackwards) + { + // Enrich the timeline root state with the additional state events observed during back pagination + [self handleStateEvents:paginatedResponse.state direction:MXTimelineDirectionForwards]; + } + + // Enrich intermediate room state while paginating + [self handleStateEvents:paginatedResponse.state direction:direction]; + } + // Process received events for (MXEvent *event in paginatedResponse.chunk) { @@ -540,13 +584,7 @@ - (void)addEvent:(MXEvent*)event direction:(MXTimelineDirection)direction fromSt { [self cloneState:direction]; - [self handleStateEvent:event direction:direction]; - - // The store keeps only the most recent state of the room - if (direction == MXTimelineDirectionForwards && [store respondsToSelector:@selector(storeStateForRoom:stateEvents:)]) - { - [store storeStateForRoom:_state.roomId stateEvents:_state.stateEvents]; - } + [self handleStateEvents:@[event] direction:direction]; } // Decrypt event if necessary @@ -624,12 +662,6 @@ - (void)handleRedaction:(MXEvent*)redactionEvent _state = [[MXRoomState alloc] initWithRoomId:room.roomId andMatrixSession:room.mxSession andDirection:YES]; [self initialiseState:stateEvents]; - // Update store with new room state when all state event have been processed - if ([store respondsToSelector:@selector(storeStateForRoom:stateEvents:)]) - { - [store storeStateForRoom:_state.roomId stateEvents:_state.stateEvents]; - } - // Reset the current pagination [self resetPagination]; @@ -711,31 +743,26 @@ - (void)cloneState:(MXTimelineDirection)direction } } -- (void)handleStateEvent:(MXEvent*)event direction:(MXTimelineDirection)direction +- (void)handleStateEvents:(NSArray *)stateEvents direction:(MXTimelineDirection)direction { // Update the room state if (MXTimelineDirectionBackwards == direction) { - [backState handleStateEvent:event]; + [backState handleStateEvents:stateEvents]; } else { // Forwards events update the current state of the room - [_state handleStateEvent:event]; + [_state handleStateEvents:stateEvents]; - // Special handling for presence: update MXUser data in case of membership event. - // CAUTION: ignore here redacted state event, the redaction concerns only the context of the event room. - if (_isLiveTimeline && MXEventTypeRoomMember == event.eventType && !event.isRedactedEvent) - { - MXUser *user = [room.mxSession getOrCreateUser:event.sender]; - - MXRoomMember *roomMember = [_state memberWithUserId:event.sender]; - if (roomMember && MXMembershipJoin == roomMember.membership) - { - [user updateWithRoomMemberEvent:event roomMember:roomMember inMatrixSession:room.mxSession]; + // Update summary with this state events update + [room.summary handleStateEvents:stateEvents]; - [room.mxSession.store storeUser:user]; - } + if (!room.mxSession.syncWithLazyLoadOfRoomMembers && ![store hasLoadedAllRoomMembersForRoom:room.roomId]) + { + // If there is no lazy loading of room members, consider we have fetched + // all of them + [store storeHasLoadedAllRoomMembersForRoom:room.roomId andValue:YES]; } } } diff --git a/MatrixSDK/Data/MXPeekingRoom.m b/MatrixSDK/Data/MXPeekingRoom.m index 0e5283e1f1..dbda906df2 100644 --- a/MatrixSDK/Data/MXPeekingRoom.m +++ b/MatrixSDK/Data/MXPeekingRoom.m @@ -61,23 +61,30 @@ - (void)start:(void (^)(void))onServerSyncDone failure:(void (^)(NSError *error) } self->httpOperation = nil; - [self.liveTimeline initialiseState:roomInitialSync.state]; + [self liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline initialiseState:roomInitialSync.state]; - // @TODO: start the events stream + // @TODO: start the events stream - onServerSyncDone(); + onServerSyncDone(); + }]; } failure:failure]; } +- (MXRoomSummary *)summary +{ + NSLog(@"[MXPeekingRoom] Warning: MXPeekingRoom has no summary"); + return nil; +} + - (void)close { // Cancel the current server request (if any) [httpOperation cancel]; httpOperation = nil; - // Clean MXRoom - [self.liveTimeline removeAllListeners]; + [super close]; } - (void)pause diff --git a/MatrixSDK/Data/MXRoom.h b/MatrixSDK/Data/MXRoom.h index 9a6daca8f3..ff35b7e436 100644 --- a/MatrixSDK/Data/MXRoom.h +++ b/MatrixSDK/Data/MXRoom.h @@ -1,6 +1,7 @@ /* Copyright 2014 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -36,6 +37,7 @@ #import "MXEventTimeline.h" #import "MXEventsEnumerator.h" #import "MXCryptoConstants.h" +#import "MXSendReplyEventStringsLocalizable.h" @class MXRoom; @class MXSession; @@ -81,12 +83,45 @@ FOUNDATION_EXPORT NSString *const kMXRoomDidFlushDataNotification; /** The live events timeline. */ -@property (nonatomic, readonly) MXEventTimeline *liveTimeline; +- (void)liveTimeline:(void (^)(MXEventTimeline *liveTimeline))onComplete; /** - The up-to-date state of the room. + The current state of the room. + + This getter method is a shortcut to `liveTimeline.state`. + */ +- (void)state:(void (^)(MXRoomState *roomState))onComplete; + +/** + The current list of members of the room. + + It may require a request to the homeserver if the client has not fetched yet all + data like in case of members lazy loading. + + @param success A block object called when the operation succeeds. It returns + the MXRoomMembers object. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)members:(void (^)(MXRoomMembers *roomMembers))success + failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; + +/** + Same as `[MXRoom members:]` but it returns already lazy-loaded members if an + HTTP request to the homeserver is requested. + + @param success A block object called when the operation succeeds. It returns + all members of the room. + @param lazyLoadedMembers A block object called when an HTTP request is required. It returns + already lazy-loaded members. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. */ -@property (nonatomic, readonly) MXRoomState *state; +- (MXHTTPOperation*)members:(void (^)(MXRoomMembers *members))success + lazyLoadedMembers:(void (^)(MXRoomMembers *lazyLoadedMembers))lazyLoadedMembers + failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; /** The private user data for this room. @@ -145,25 +180,26 @@ FOUNDATION_EXPORT NSString *const kMXRoomDidFlushDataNotification; - (id)initWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)mxSession; /** - Create a `MXRoom` instance from room state and account data already available. + Create a `MXRoom` instance by specifying the store the live timeline must use. @param roomId the id of the room. @param mxSession the session to use. - @param stateEvents the state events of the room. - @param accountData the account data for the room. + @param store the store to use to store live timeline events. @return the new instance. */ -- (id)initWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)mxSession andStateEvents:(NSArray *)stateEvents andAccountData:(MXRoomAccountData*)accountData; +- (id)initWithRoomId:(NSString *)roomId matrixSession:(MXSession *)mxSession andStore:(id)store; /** - Create a `MXRoom` instance by specifying the store the live timeline must use. + Load a `MXRoom` instance from the store. + @param store the store to mount data from and to store live data to. @param roomId the id of the room. - @param mxSession the session to use. - @param store the store to use to store live timeline events. + @param matrixSession the session to use. @return the new instance. */ -- (id)initWithRoomId:(NSString *)roomId matrixSession:(MXSession *)mxSession andStore:(id)store; ++ (id)loadRoomFromStore:(id)store withRoomId:(NSString *)roomId matrixSession:(MXSession *)matrixSession; + +- (void)close; #pragma mark - Server sync @@ -743,6 +779,70 @@ FOUNDATION_EXPORT NSString *const kMXRoomDidFlushDataNotification; success:(void (^)(void))success failure:(void (^)(NSError *error))failure; +/** + Indicate if replying to the provided event is supported. + Only event of type 'MXEventTypeRoomMessage' are supported for the moment, and for certain msgtype. + + @param eventToReply the event to reply to + @return YES if it is possible to reply to this event + */ +- (BOOL)canReplyToEvent:(MXEvent *)eventToReply; + +/** + Send a reply to an event with text message to the room. + + It's only supported to reply to event with 'm.room.message' event type and following message types: 'm.text', 'm.text', 'm.emote', 'm.notice', 'm.image', 'm.file', 'm.video', 'm.audio'. + + @param eventToReply The event to reply. + @param textMessage the text to send. + @param formattedTextMessage the optional HTML formatted string of the text to send. + @param stringLocalizations string localizations used when building reply message. + @param localEcho a pointer to a MXEvent object (@see sendMessageWithContent: for details). + @param success A block object called when the operation succeeds. It returns + the event id of the event generated on the home server + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)sendReplyToEvent:(MXEvent*)eventToReply + withTextMessage:(NSString*)textMessage + formattedTextMessage:(NSString*)formattedTextMessage + stringLocalizations:(id)stringLocalizations + localEcho:(MXEvent**)localEcho + success:(void (^)(NSString *eventId))success + failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; + + +#pragma mark - Events listeners on the live timeline +/** + Register a listener to events of the room live timeline. + + @param onEvent the block that will called once a new event has been handled. + @return a reference to use to unregister the listener + */ +- (id)listenToEvents:(MXOnRoomEvent)onEvent; + +/** + Register a listener for some types of events on the room live timeline. + + @param types an array of event types strings (MXEventTypeString) to listen to. + @param onEvent the block that will called once a new event has been handled. + @return a reference to use to unregister the listener + */ +- (id)listenToEventsOfTypes:(NSArray *)types onEvent:(MXOnRoomEvent)onEvent; + +/** + Unregister a listener from the room live timeline. + + @param listener the reference of the listener to remove. + */ +- (void)removeListener:(id)listener; + +/** + Unregister all listeners from the room live timeline. + */ +- (void)removeAllListeners; + #pragma mark - Events timeline /** @@ -753,6 +853,7 @@ FOUNDATION_EXPORT NSString *const kMXRoomDidFlushDataNotification; */ - (MXEventTimeline*)timelineOnEvent:(NSString*)eventId; + #pragma mark - Fake event objects creation /** Create a temporary event for the room. diff --git a/MatrixSDK/Data/MXRoom.m b/MatrixSDK/Data/MXRoom.m index 8f025bf9f8..94a0283143 100644 --- a/MatrixSDK/Data/MXRoom.m +++ b/MatrixSDK/Data/MXRoom.m @@ -27,6 +27,7 @@ #import "MXMediaManager.h" #import "MXRoomOperation.h" +#import "MXSendReplyEventDefaultStringLocalizations.h" #import "MXError.h" @@ -41,6 +42,27 @@ The list of room operations (sending of text, images...) that must be sent These operations are stored in a FIFO and executed one after the other. */ NSMutableArray *orderedOperations; + + /** + The liveTimeline instance. + Its data is loaded only when [self liveTimeline:] is called. + */ + MXEventTimeline *liveTimeline; + + /** + Flag to indicate that the data for `_liveTimeline` must be loaded before use. + */ + BOOL needToLoadLiveTimeline; + + /** + FIFO queue of objects waiting for [self liveTimeline:]. + */ + NSMutableArray *pendingLiveTimelineRequesters; + + /** + FIFO queue of objects waiting for [self members:]. + */ + NSMutableArray *pendingMembersRequesters; } @end @@ -59,6 +81,8 @@ - (instancetype)init orderedOperations = [NSMutableArray array]; _directUserId = nil; + + needToLoadLiveTimeline = NO; } return self; @@ -70,30 +94,6 @@ - (id)initWithRoomId:(NSString *)roomId andMatrixSession:(MXSession *)mxSession2 return [self initWithRoomId:roomId matrixSession:mxSession2 andStore:nil]; } -- (id)initWithRoomId:(NSString *)roomId andMatrixSession:(MXSession *)mxSession2 andStateEvents:(NSArray *)stateEvents andAccountData:(MXRoomAccountData*)accountData -{ - self = [self initWithRoomId:roomId andMatrixSession:mxSession2]; - if (self) - { - @autoreleasepool - { - [_liveTimeline initialiseState:stateEvents]; - - // Report the provided accountData. - // Allocate a new instance if none, in order to handle room tag events for this room. - _accountData = accountData ? accountData : [[MXRoomAccountData alloc] init]; - - // Check whether the room is pending on an invitation. - if (self.state.membership == MXMembershipInvite) - { - // Handle direct flag to decide if it is direct or not - [self handleInviteDirectFlag]; - } - } - } - return self; -} - - (id)initWithRoomId:(NSString *)roomId matrixSession:(MXSession *)mxSession2 andStore:(id)store { self = [self init]; @@ -104,12 +104,12 @@ - (id)initWithRoomId:(NSString *)roomId matrixSession:(MXSession *)mxSession2 an if (store) { - _liveTimeline = [[MXEventTimeline alloc] initWithRoom:self initialEventId:nil andStore:store]; + liveTimeline = [[MXEventTimeline alloc] initWithRoom:self initialEventId:nil andStore:store]; } else { // Let the timeline use the session store - _liveTimeline = [[MXEventTimeline alloc] initWithRoom:self andInitialEventId:nil]; + liveTimeline = [[MXEventTimeline alloc] initWithRoom:self andInitialEventId:nil]; } // Update the stored outgoing messages, by removing the sent messages and tagging as failed the others. @@ -118,6 +118,35 @@ - (id)initWithRoomId:(NSString *)roomId matrixSession:(MXSession *)mxSession2 an return self; } ++ (id)loadRoomFromStore:(id)store withRoomId:(NSString *)roomId matrixSession:(MXSession *)matrixSession +{ + MXRoom *room = [[MXRoom alloc] initWithRoomId:roomId andMatrixSession:matrixSession]; + if (room) + { + MXRoomAccountData *accountData = [store accountDataOfRoom:roomId]; + + room->needToLoadLiveTimeline = YES; + + // Report the provided accountData. + // Allocate a new instance if none, in order to handle room tag events for this room. + room->_accountData = accountData ? accountData : [[MXRoomAccountData alloc] init]; + + // Check whether the room is pending on an invitation. + if (room.summary.membership == MXMembershipInvite) + { + // Handle direct flag to decide if it is direct or not + [room handleInviteDirectFlag]; + } + } + return room; +} + +- (void)close +{ + // Clean MXRoom + [liveTimeline removeAllListeners]; +} + #pragma mark - Properties implementation - (MXRoomSummary *)summary { @@ -125,11 +154,128 @@ - (MXRoomSummary *)summary return [mxSession roomSummaryWithRoomId:_roomId]; } -- (MXRoomState *)state +- (void)liveTimeline:(void (^)(MXEventTimeline *))onComplete +{ + // Is timelime ready? + if (needToLoadLiveTimeline || pendingLiveTimelineRequesters) + { + // Queue the requester + if (!pendingLiveTimelineRequesters) + { + pendingLiveTimelineRequesters = [NSMutableArray array]; + + MXWeakify(self); + [MXRoomState loadRoomStateFromStore:self.mxSession.store withRoomId:self.roomId matrixSession:self.mxSession onComplete:^(MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); + + [self->liveTimeline setState:roomState]; + + // Provide the timelime to pending requesters + NSArray *liveTimelineRequesters = [self->pendingLiveTimelineRequesters copy]; + self->pendingLiveTimelineRequesters = nil; + + for (void (^onRequesterComplete)(MXEventTimeline *) in liveTimelineRequesters) + { + onRequesterComplete(self->liveTimeline); + } + NSLog(@"[MXRoom] liveTimeline loaded. Pending requesters: %@", @(liveTimelineRequesters.count)); + }]; + } + + [pendingLiveTimelineRequesters addObject:onComplete]; + + self->needToLoadLiveTimeline = NO; + } + else + { + onComplete(liveTimeline); + } +} + +- (void)state:(void (^)(MXRoomState *))onComplete +{ + [self liveTimeline:^(MXEventTimeline *theLiveTimeline) { + onComplete(theLiveTimeline.state); + }]; +} + +- (MXHTTPOperation *)members:(void (^)(MXRoomMembers *roomMembers))success + failure:(void (^)(NSError *error))failure { - return _liveTimeline.state; + return [self members:success lazyLoadedMembers:nil failure:failure]; +} + +- (MXHTTPOperation*)members:(void (^)(MXRoomMembers *members))success + lazyLoadedMembers:(void (^)(MXRoomMembers *lazyLoadedMembers))lazyLoadedMembers + failure:(void (^)(NSError *error))failure +{ + // Create an empty operation that will be mutated later + MXHTTPOperation *operation = [[MXHTTPOperation alloc] init]; + + MXWeakify(self); + [self liveTimeline:^(MXEventTimeline *liveTimeline) { + MXStrongifyAndReturnIfNil(self); + + // Return directly liveTimeline.state.members if we have already all of them + if ([self.mxSession.store hasLoadedAllRoomMembersForRoom:self.roomId]) + { + success(liveTimeline.state.members); + } + else + { + // Return already lazy-loaded room members if requested + if (lazyLoadedMembers) + { + lazyLoadedMembers(liveTimeline.state.members); + } + + // Queue the requester + if (!self->pendingMembersRequesters) + { + self->pendingMembersRequesters = [NSMutableArray array]; + + // Else get them from the homeserver + MXWeakify(self); + MXHTTPOperation *operation2 = [self.mxSession.matrixRestClient membersOfRoom:self.roomId success:^(NSArray *roomMemberEvents) { + MXStrongifyAndReturnIfNil(self); + + [liveTimeline handleLazyLoadedStateEvents:roomMemberEvents]; + + [self.mxSession.store storeHasLoadedAllRoomMembersForRoom:self.roomId andValue:YES]; + if ([self.mxSession.store respondsToSelector:@selector(commit)]) + { + [self.mxSession.store commit]; + } + + // Provide the timelime to pending requesters + NSArray *pendingMembersRequesters = [self->pendingMembersRequesters copy]; + self->pendingMembersRequesters = nil; + + for (void (^onRequesterComplete)(MXRoomMembers *) in pendingMembersRequesters) + { + onRequesterComplete(liveTimeline.state.members); + } + NSLog(@"[MXRoom] members loaded. Pending requesters: %@", @(pendingMembersRequesters.count)); + + } failure:failure]; + + if (operation2) + { + [operation mutateTo:operation2]; + } + } + + if (success) + { + [self->pendingMembersRequesters addObject:success]; + } + } + }]; + + return operation; } + - (void)setPartialTextMessage:(NSString *)partialTextMessage { [mxSession.store storePartialTextMessageForRoom:self.roomId partialTextMessage:partialTextMessage]; @@ -148,62 +294,78 @@ - (NSString *)partialTextMessage #pragma mark - Sync - (void)handleJoinedRoomSync:(MXRoomSync *)roomSync { - // Let the live timeline handle live events - [_liveTimeline handleJoinedRoomSync:roomSync]; + MXWeakify(self); + [self liveTimeline:^(MXEventTimeline *theLiveTimeline) { + MXStrongifyAndReturnIfNil(self); - // Handle here ephemeral events (if any) - for (MXEvent *event in roomSync.ephemeral.events) - { - // Report the room id in the event as it is skipped in /sync response - event.roomId = self.roomId; + // Let the live timeline handle live events + [theLiveTimeline handleJoinedRoomSync:roomSync]; - // Handle first typing notifications - if (event.eventType == MXEventTypeTypingNotification) + // Handle here ephemeral events (if any) + for (MXEvent *event in roomSync.ephemeral.events) { - // Typing notifications events are not room messages nor room state events - // They are just volatile information - MXJSONModelSetArray(_typingUsers, event.content[@"user_ids"]); + // Report the room id in the event as it is skipped in /sync response + event.roomId = self.roomId; - // Notify listeners - [_liveTimeline notifyListeners:event direction:MXTimelineDirectionForwards]; - } - else if (event.eventType == MXEventTypeReceipt) - { - [self handleReceiptEvent:event direction:MXTimelineDirectionForwards]; + // Handle first typing notifications + if (event.eventType == MXEventTypeTypingNotification) + { + // Typing notifications events are not room messages nor room state events + // They are just volatile information + MXJSONModelSetArray(self->_typingUsers, event.content[@"user_ids"]); + + // Notify listeners + [theLiveTimeline notifyListeners:event direction:MXTimelineDirectionForwards]; + } + else if (event.eventType == MXEventTypeReceipt) + { + [self handleReceiptEvent:event direction:MXTimelineDirectionForwards]; + } } - } - // Handle account data events (if any) - [self handleAccounDataEvents:roomSync.accountData.events direction:MXTimelineDirectionForwards]; + // Handle account data events (if any) + [self handleAccounDataEvents:roomSync.accountData.events liveTimeline:theLiveTimeline direction:MXTimelineDirectionForwards]; + }]; } - (void)handleInvitedRoomSync:(MXInvitedRoomSync *)invitedRoomSync { - // Let the live timeline handle live events - [_liveTimeline handleInvitedRoomSync:invitedRoomSync]; - - // Handle direct flag to decide if it is direct or not - [self handleInviteDirectFlag]; + [self liveTimeline:^(MXEventTimeline *theLiveTimeline) { + + // Let the live timeline handle live events + [theLiveTimeline handleInvitedRoomSync:invitedRoomSync]; + + // Handle direct flag to decide if it is direct or not + [self handleInviteDirectFlag]; + }]; } - (void)handleInviteDirectFlag { // Handle here invite data to decide if it is direct or not - MXRoomMember *myUser = [self.state memberWithUserId:mxSession.myUser.userId]; - BOOL isDirect = NO; - - if (myUser.originalEvent.content[@"is_direct"]) - { - isDirect = [((NSNumber*)myUser.originalEvent.content[@"is_direct"]) boolValue]; - } - - if (isDirect) - { - // Mark as direct this room with the invite sender. - [self setIsDirect:YES withUserId:myUser.originalEvent.sender success:nil failure:^(NSError *error) { - NSLog(@"[MXRoom] Failed to tag an invite as a direct chat"); - }]; - } + MXWeakify(self); + [self state:^(MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); + + // We can use roomState.members because, even in case of lazy loading of room members, + // my user must be in roomState.members + MXRoomMembers *roomMembers = roomState.members; + MXRoomMember *myUser = [roomMembers memberWithUserId:self.mxSession.myUser.userId]; + BOOL isDirect = NO; + + if (myUser.originalEvent.content[@"is_direct"]) + { + isDirect = [((NSNumber*)myUser.originalEvent.content[@"is_direct"]) boolValue]; + } + + if (isDirect) + { + // Mark as direct this room with the invite sender. + [self setIsDirect:YES withUserId:myUser.originalEvent.sender success:nil failure:^(NSError *error) { + NSLog(@"[MXRoom] Failed to tag an invite as a direct chat"); + }]; + } + }]; } #pragma mark - Room private account data handling @@ -213,7 +375,7 @@ - (void)handleInviteDirectFlag @param accounDataEvents the events to handle. @param direction the process direction: MXTimelineDirectionSync or MXTimelineDirectionForwards. MXTimelineDirectionBackwards is not applicable here. */ -- (void)handleAccounDataEvents:(NSArray*)accounDataEvents direction:(MXTimelineDirection)direction +- (void)handleAccounDataEvents:(NSArray*)accounDataEvents liveTimeline:(MXEventTimeline*)theLiveTimeline direction:(MXTimelineDirection)direction { for (MXEvent *event in accounDataEvents) { @@ -226,7 +388,7 @@ - (void)handleAccounDataEvents:(NSArray*)accounDataEvents direction:(M } // And notify listeners - [_liveTimeline notifyListeners:event direction:direction]; + [theLiveTimeline notifyListeners:event direction:direction]; } } @@ -325,7 +487,7 @@ - (MXHTTPOperation*)sendEventOfType:(MXEventTypeString)eventTypeString }; // Check whether the content must be encrypted before sending - if (mxSession.crypto && self.state.isEncrypted) + if (mxSession.crypto && self.summary.isEncrypted) { // Check whether the provided content is already encrypted if ([eventTypeString isEqualToString:kMXEventTypeStringRoomEncrypted]) @@ -347,6 +509,23 @@ - (MXHTTPOperation*)sendEventOfType:(MXEventTypeString)eventTypeString } else { + NSDictionary *relatesToJSON = nil; + + NSDictionary *contentCopyToEncrypt = nil; + + // Store the "m.relates_to" data and remove them from event clear content before encrypting the event content + if (contentCopy[@"m.relates_to"]) + { + relatesToJSON = contentCopy[@"m.relates_to"]; + NSMutableDictionary *updatedContent = [contentCopy mutableCopy]; + updatedContent[@"m.relates_to"] = nil; + contentCopyToEncrypt = [updatedContent copy]; + } + else + { + contentCopyToEncrypt = contentCopy; + } + // Check whether a local echo is required if ([eventTypeString isEqualToString:kMXEventTypeStringRoomMessage] || [eventTypeString isEqualToString:kMXEventTypeStringSticker]) @@ -377,16 +556,30 @@ - (MXHTTPOperation*)sendEventOfType:(MXEventTypeString)eventTypeString MXStrongifyAndReturnIfNil(self); MXWeakify(self); - MXHTTPOperation *operation = [self->mxSession.crypto encryptEventContent:contentCopy withType:eventTypeString inRoom:self success:^(NSDictionary *encryptedContent, NSString *encryptedEventType) { + MXHTTPOperation *operation = [self->mxSession.crypto encryptEventContent:contentCopyToEncrypt withType:eventTypeString inRoom:self success:^(NSDictionary *encryptedContent, NSString *encryptedEventType) { MXStrongifyAndReturnIfNil(self); + NSDictionary *finalEncryptedContent; + + // Add "m.relates_to" to encrypted event content if any + if (relatesToJSON) + { + NSMutableDictionary *updatedEncryptedContent = [encryptedContent mutableCopy]; + updatedEncryptedContent[@"m.relates_to"] = relatesToJSON; + finalEncryptedContent = [updatedEncryptedContent copy]; + } + else + { + finalEncryptedContent = encryptedContent; + } + if (event) { // Encapsulate the resulting event in a fake encrypted event MXEvent *clearEvent = [self fakeEventWithEventId:event.eventId eventType:eventTypeString andContent:event.content]; event.wireType = encryptedEventType; - event.wireContent = encryptedContent; + event.wireContent = finalEncryptedContent; MXEventDecryptionResult *decryptionResult = [[MXEventDecryptionResult alloc] init]; decryptionResult.clearEvent = clearEvent.JSONDictionary; @@ -403,7 +596,7 @@ - (MXHTTPOperation*)sendEventOfType:(MXEventTypeString)eventTypeString } // Send the encrypted content - MXHTTPOperation *operation2 = [self _sendEventOfType:encryptedEventType content:encryptedContent txnId:event.eventId success:onSuccess failure:onFailure]; + MXHTTPOperation *operation2 = [self _sendEventOfType:encryptedEventType content:finalEncryptedContent txnId:event.eventId success:onSuccess failure:onFailure]; if (operation2) { // Mutate MXHTTPOperation so that the user can cancel this new operation @@ -573,7 +766,7 @@ - (MXHTTPOperation*)sendImage:(NSData*)imageData double endRange = 1.0; // Check whether the content must be encrypted before sending - if (mxSession.crypto && self.state.isEncrypted) endRange = 0.9; + if (mxSession.crypto && self.summary.isEncrypted) endRange = 0.9; // Use the uploader id as fake URL for this image data // The URL does not need to be valid as the MediaManager will get the data @@ -653,7 +846,7 @@ - (MXHTTPOperation*)sendImage:(NSData*)imageData }; // Add a local echo for this message during the sending process. - MXEventSentState initialSentState = (mxSession.crypto && self.state.isEncrypted) ? MXEventSentStateEncrypting : MXEventSentStateUploading; + MXEventSentState initialSentState = (mxSession.crypto && self.summary.isEncrypted) ? MXEventSentStateEncrypting : MXEventSentStateUploading; event = [self addLocalEchoForMessageContent:msgContent eventType:kMXEventTypeStringRoomMessage withState:initialSentState]; if (localEcho) @@ -667,7 +860,7 @@ - (MXHTTPOperation*)sendImage:(NSData*)imageData MXStrongifyAndReturnIfNil(self); // Check whether the content must be encrypted before sending - if (self.mxSession.crypto && self.state.isEncrypted) + if (self.mxSession.crypto && self.summary.isEncrypted) { // Register uploader observer uploaderObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXMediaUploadProgressNotification object:uploader queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -896,7 +1089,7 @@ - (MXHTTPOperation*)sendVideo:(NSURL*)videoLocalURL msgContent[@"info"][@"h"] = @(size.height); msgContent[@"info"][@"duration"] = @(durationInMs); - if (self.mxSession.crypto && self.state.isEncrypted) + if (self.mxSession.crypto && self.summary.isEncrypted) { [MXEncryptedAttachments encryptAttachment:thumbUploader mimeType:@"image/jpeg" data:videoThumbnailData success:^(NSDictionary *result) { @@ -1146,7 +1339,7 @@ - (MXHTTPOperation*)sendFile:(NSURL*)fileLocalURL }; // Add a local echo for this message during the sending process. - MXEventSentState initialSentState = (mxSession.crypto && self.state.isEncrypted) ? MXEventSentStateEncrypting : MXEventSentStateUploading; + MXEventSentState initialSentState = (mxSession.crypto && self.summary.isEncrypted) ? MXEventSentStateEncrypting : MXEventSentStateUploading; event = [self addLocalEchoForMessageContent:msgContent eventType:kMXEventTypeStringRoomMessage withState:initialSentState]; if (localEcho) @@ -1157,7 +1350,7 @@ - (MXHTTPOperation*)sendFile:(NSURL*)fileLocalURL roomOperation = [self preserveOperationOrder:event block:^{ - if (self.mxSession.crypto && self.state.isEncrypted) + if (self.mxSession.crypto && self.summary.isEncrypted) { // Register uploader observer uploaderObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXMediaUploadProgressNotification object:uploader queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -1378,19 +1571,34 @@ - (MXHTTPOperation*)setPowerLevelOfUserWithUserID:(NSString *)userId powerLevel: success:(void (^)(void))success failure:(void (^)(NSError *))failure { - // To set this new value, we have to take the current powerLevels content, - // Update it with expected values and send it to the home server. - NSMutableDictionary *newPowerLevelsEventContent = [NSMutableDictionary dictionaryWithDictionary:self.state.powerLevels.JSONDictionary]; + // Create an empty operation that will be mutated later + MXHTTPOperation *operation = [[MXHTTPOperation alloc] init]; - NSMutableDictionary *newPowerLevelsEventContentUsers = [NSMutableDictionary dictionaryWithDictionary:newPowerLevelsEventContent[@"users"]]; - newPowerLevelsEventContentUsers[userId] = [NSNumber numberWithInteger:powerLevel]; + MXWeakify(self); + [self state:^(MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); - newPowerLevelsEventContent[@"users"] = newPowerLevelsEventContentUsers; + // To set this new value, we have to take the current powerLevels content, + // Update it with expected values and send it to the home server. + NSMutableDictionary *newPowerLevelsEventContent = [NSMutableDictionary dictionaryWithDictionary:roomState.powerLevels.JSONDictionary]; - // Make the request to the HS - return [self sendStateEventOfType:kMXEventTypeStringRoomPowerLevels content:newPowerLevelsEventContent stateKey:nil success:^(NSString *eventId) { - success(); - } failure:failure]; + NSMutableDictionary *newPowerLevelsEventContentUsers = [NSMutableDictionary dictionaryWithDictionary:newPowerLevelsEventContent[@"users"]]; + newPowerLevelsEventContentUsers[userId] = [NSNumber numberWithInteger:powerLevel]; + + newPowerLevelsEventContent[@"users"] = newPowerLevelsEventContentUsers; + + // Make the request to the HS + MXHTTPOperation *operation2 = [self sendStateEventOfType:kMXEventTypeStringRoomPowerLevels content:newPowerLevelsEventContent stateKey:nil success:^(NSString *eventId) { + success(); + } failure:failure]; + + if (operation2) + { + [operation mutateTo:operation2]; + } + }]; + + return operation; } - (MXHTTPOperation*)sendTypingNotification:(BOOL)typing @@ -1425,6 +1633,357 @@ - (MXHTTPOperation*)setRelatedGroups:(NSArray*)relatedGroups return [mxSession.matrixRestClient setRoomRelatedGroups:self.roomId relatedGroups:relatedGroups success:success failure:failure]; } +- (MXHTTPOperation*)sendReplyToEvent:(MXEvent*)eventToReply + withTextMessage:(NSString*)textMessage + formattedTextMessage:(NSString*)formattedTextMessage + stringLocalizations:(id)stringLocalizations + localEcho:(MXEvent**)localEcho + success:(void (^)(NSString *eventId))success + failure:(void (^)(NSError *error))failure +{ + if (![self canReplyToEvent:eventToReply]) + { + NSLog(@"[MXRoom] Send reply to this event is not supported"); + return nil; + } + + id finalStringLocalizations; + + if (stringLocalizations) + { + finalStringLocalizations = stringLocalizations; + } + else + { + finalStringLocalizations = [MXSendReplyEventDefaultStringLocalizations new]; + } + + MXHTTPOperation* operation = nil; + + NSString *replyToBody; + NSString *replyToFormattedBody; + + [self getReplyContentBodiesWithEventToReply:eventToReply + textMessage:textMessage + formattedTextMessage:formattedTextMessage + replyContentBody:&replyToBody + replyContentFormattedBody:&replyToFormattedBody + stringLocalizations:finalStringLocalizations]; + + if (replyToBody && replyToFormattedBody) + { + NSString *eventId = eventToReply.eventId; + + NSDictionary *relatesToDict = @{ @"m.in_reply_to" : + @{ + @"event_id" : eventId + } + }; + + NSMutableDictionary *msgContent = [NSMutableDictionary dictionary]; + + msgContent[@"format"] = kMXRoomMessageFormatHTML; + msgContent[@"msgtype"] = kMXMessageTypeText; + msgContent[@"body"] = replyToBody; + msgContent[@"formatted_body"] = replyToFormattedBody; + msgContent[@"m.relates_to"] = relatesToDict; + + operation = [self sendMessageWithContent:msgContent + localEcho:localEcho + success:success + failure:failure]; + } + else + { + NSLog(@"[MXRoom] Fail to generate reply body and formatted body"); + } + + return operation; +} + +/** + Build reply to body and formatted body. + + @param eventToReply the event to reply. Should be 'm.room.message' event type. + @param textMessage the text to send. + @param formattedTextMessage the optional HTML formatted string of the text to send. + @param replyContentBody reply string of the text to send. + @param replyContentFormattedBody reply HTML formatted string of the text to send. + @param stringLocalizations string localizations used when building reply content bodies. + + */ +- (void)getReplyContentBodiesWithEventToReply:(MXEvent*)eventToReply + textMessage:(NSString*)textMessage + formattedTextMessage:(NSString*)formattedTextMessage + replyContentBody:(NSString**)replyContentBody + replyContentFormattedBody:(NSString**)replyContentFormattedBody + stringLocalizations:(id)stringLocalizations +{ + NSString *msgtype; + MXJSONModelSetString(msgtype, eventToReply.content[@"msgtype"]); + + if (!msgtype) + { + return; + } + + BOOL eventToReplyIsAlreadyAReply = eventToReply.content[@"m.relates_to"][@"m.in_reply_to"][@"event_id"] != nil; + BOOL isSenderMessageAnEmote = [msgtype isEqualToString:kMXMessageTypeEmote]; + + NSString *senderMessageBody; + NSString *senderMessageFormattedBody; + + if ([msgtype isEqualToString:kMXMessageTypeText] + || [msgtype isEqualToString:kMXMessageTypeNotice] + || [msgtype isEqualToString:kMXMessageTypeEmote]) + { + NSString *eventToReplyMessageBody = eventToReply.content[@"body"]; + NSString *eventToReplyMessageFormattedBody = eventToReply.content[@"formatted_body"]; + + senderMessageBody = eventToReplyMessageBody; + senderMessageFormattedBody = eventToReplyMessageFormattedBody ?: eventToReplyMessageBody; + } + else if ([msgtype isEqualToString:kMXMessageTypeImage]) + { + senderMessageBody = stringLocalizations.senderSentAnImage; + senderMessageFormattedBody = senderMessageBody; + } + else if ([msgtype isEqualToString:kMXMessageTypeVideo]) + { + senderMessageBody = stringLocalizations.senderSentAVideo; + senderMessageFormattedBody = senderMessageBody; + } + else if ([msgtype isEqualToString:kMXMessageTypeAudio]) + { + senderMessageBody = stringLocalizations.senderSentAnAudioFile; + senderMessageFormattedBody = senderMessageBody; + } + else if ([msgtype isEqualToString:kMXMessageTypeFile]) + { + senderMessageBody = stringLocalizations.senderSentAFile; + senderMessageFormattedBody = senderMessageBody; + } + else + { + // Other message types are not supported + NSLog(@"[MXRoom] Reply to message type %@ is not supported", msgtype); + } + + if (senderMessageBody && senderMessageFormattedBody) + { + *replyContentBody = [self replyMessageBodyFromSender:eventToReply.sender + senderMessageBody:senderMessageBody + isSenderMessageAnEmote:isSenderMessageAnEmote + isSenderMessageAReplyTo:eventToReplyIsAlreadyAReply + replyMessage:textMessage]; + + // As formatted body is mandatory for a reply message, use non formatted to build it + NSString *finalFormattedTextMessage = formattedTextMessage ?: textMessage; + + *replyContentFormattedBody = [self replyMessageFormattedBodyFromEventToReply:eventToReply + senderMessageFormattedBody:senderMessageFormattedBody + isSenderMessageAnEmote:isSenderMessageAnEmote + isSenderMessageAReplyTo:eventToReplyIsAlreadyAReply + replyFormattedMessage:finalFormattedTextMessage + stringLocalizations:stringLocalizations]; + } +} + +/** + Build reply body. + + Example of reply body: + `> <@sender:matrix.org> sent an image.\n\nReply message` + + @param sender The sender of the message. + @param senderMessageBody The message body of the sender. + @param isSenderMessageAnEmote Indicate if the sender message is an emote (/me). + @param isSenderMessageAReplyTo Indicate if the sender message is already a reply to message. + @param replyMessage The response for the sender message. + + @return Reply message body. + */ +- (NSString*)replyMessageBodyFromSender:(NSString*)sender + senderMessageBody:(NSString*)senderMessageBody + isSenderMessageAnEmote:(BOOL)isSenderMessageAnEmote + isSenderMessageAReplyTo:(BOOL)isSenderMessageAReplyTo + replyMessage:(NSString*)replyMessage +{ + // Sender reply body split by lines + NSMutableArray *senderReplyBodyLines = [[senderMessageBody componentsSeparatedByString:@"\n"] mutableCopy]; + + // Strip previous reply to, if the event was already a reply + if (isSenderMessageAReplyTo) + { + // Removes lines beginning with `> ` until you reach one that doesn't. + while (senderReplyBodyLines.count && [senderReplyBodyLines.firstObject hasPrefix:@"> "]) + { + [senderReplyBodyLines removeObjectAtIndex:0]; + } + + // Reply fallback has a blank line after it, so remove it to prevent leading newline + if (senderReplyBodyLines.firstObject.length == 0) + { + [senderReplyBodyLines removeObjectAtIndex:0]; + } + } + + // Build sender message reply body part + + // Add user id on first line + NSString *firstLine = senderReplyBodyLines.firstObject; + if (firstLine) + { + NSString *newFirstLine; + + if (isSenderMessageAnEmote) + { + newFirstLine = [NSString stringWithFormat:@"* <%@> %@", sender, firstLine]; + } + else + { + newFirstLine = [NSString stringWithFormat:@"<%@> %@", sender, firstLine]; + } + senderReplyBodyLines[0] = newFirstLine; + } + + NSUInteger messageToReplyBodyLineIndex = 0; + + // Add reply `> ` sequence at begining of each line + for (NSString *messageToReplyBodyLine in [senderReplyBodyLines copy]) + { + senderReplyBodyLines[messageToReplyBodyLineIndex] = [NSString stringWithFormat:@"> %@", messageToReplyBodyLine]; + messageToReplyBodyLineIndex++; + } + + // Build final message body with sender message and reply message + NSMutableString *messageBody = [NSMutableString string]; + [messageBody appendString:[senderReplyBodyLines componentsJoinedByString:@"\n"]]; + [messageBody appendString:@"\n\n"]; // Add separator between sender message and reply message + [messageBody appendString:replyMessage]; + + return [messageBody copy]; +} + +/** + Build reply formatted body. + + Example of reply formatted body: + `
In reply to @sender:matrix.org
sent an image.
Reply message` + + @param eventToReply The sender event to reply. + @param senderMessageFormattedBody The message body of the sender. + @param isSenderMessageAnEmote Indicate if the sender message is an emote (/me). + @param isSenderMessageAReplyTo Indicate if the sender message is already a reply to message. + @param replyFormattedMessage The response for the sender message. HTML formatted string if any otherwise non formatted string as reply formatted body is mandatory. + @param stringLocalizations string localizations used when building formatted body. + + @return reply message body. + */ +- (NSString*)replyMessageFormattedBodyFromEventToReply:(MXEvent*)eventToReply + senderMessageFormattedBody:(NSString*)senderMessageFormattedBody + isSenderMessageAnEmote:(BOOL)isSenderMessageAnEmote + isSenderMessageAReplyTo:(BOOL)isSenderMessageAReplyTo + replyFormattedMessage:(NSString*)replyFormattedMessage + stringLocalizations:(id)stringLocalizations +{ + NSString *eventId = eventToReply.eventId; + NSString *roomId = eventToReply.roomId; + NSString *sender = eventToReply.sender; + + if (!eventId || !roomId || !sender) + { + NSLog(@"[MXRoom] roomId, eventId and sender cound not be nil"); + return nil; + } + + NSString *replySenderMessageFormattedBody; + + // Strip previous reply to, if the event was already a reply + if (isSenderMessageAReplyTo) + { + NSError *error = nil; + NSRegularExpression *replyRegex = [NSRegularExpression regularExpressionWithPattern:@"^.*" options:NSRegularExpressionCaseInsensitive error:&error]; + NSString *senderMessageFormattedBodyWithoutReply = [replyRegex stringByReplacingMatchesInString:senderMessageFormattedBody options:0 range:NSMakeRange(0, senderMessageFormattedBody.length) withTemplate:@""]; + + if (error) + { + NSLog(@"[MXRoom] Fail to strip previous reply to message"); + } + + if (senderMessageFormattedBodyWithoutReply) + { + replySenderMessageFormattedBody = senderMessageFormattedBodyWithoutReply; + } + } + else + { + replySenderMessageFormattedBody = senderMessageFormattedBody; + } + + // Build reply formatted body + + NSString *eventPermalink = [MXTools permalinkToEvent:eventId inRoom:roomId]; + NSString *userPermalink = [MXTools permalinkToUserWithUserId:sender]; + + NSMutableString *replyMessageFormattedBody = [NSMutableString string]; + + // Start reply quote + [replyMessageFormattedBody appendString:@"
"]; + + // Add event link + [replyMessageFormattedBody appendFormat:@"%@ ", eventPermalink, stringLocalizations.messageToReplyToPrefix]; + + if (isSenderMessageAnEmote) + { + [replyMessageFormattedBody appendString:@"* "]; + } + + // Add user link + [replyMessageFormattedBody appendFormat:@"%@", userPermalink, sender]; + + [replyMessageFormattedBody appendString:@"
"]; + + // Add sender message + [replyMessageFormattedBody appendString:replySenderMessageFormattedBody]; + + // End reply quote + [replyMessageFormattedBody appendString:@"
"]; + + // Add reply message + [replyMessageFormattedBody appendString:replyFormattedMessage]; + + return replyMessageFormattedBody; +} + +- (BOOL)canReplyToEvent:(MXEvent *)eventToReply +{ + if (eventToReply.eventType != MXEventTypeRoomMessage) + { + return NO; + } + + BOOL canReplyToEvent = NO; + + NSString *messageType = eventToReply.content[@"msgtype"]; + + if (messageType) + { + NSArray *supportedMessageTypes = @[ + kMXMessageTypeText, + kMXMessageTypeNotice, + kMXMessageTypeEmote, + kMXMessageTypeImage, + kMXMessageTypeVideo, + kMXMessageTypeAudio, + kMXMessageTypeFile + ]; + + canReplyToEvent = [supportedMessageTypes containsObject:messageType]; + } + + return canReplyToEvent; +} #pragma mark - Message order preserving /** @@ -1556,6 +2115,29 @@ - (void)mutateRoomOperation:(MXRoomOperation*)roomOperation to:(MXRoomOperation* } +#pragma mark - Events listeners on the live timeline +- (id)listenToEvents:(MXOnRoomEvent)onEvent +{ + // We do not need the live timeline data to be loaded to set a listener + return [liveTimeline listenToEvents:onEvent]; +} + +- (id)listenToEventsOfTypes:(NSArray *)types onEvent:(MXOnRoomEvent)onEvent +{ + return [liveTimeline listenToEventsOfTypes:types onEvent:onEvent]; +} + +- (void)removeListener:(id)listener +{ + [liveTimeline removeListener:listener]; +} + +- (void)removeAllListeners +{ + [liveTimeline removeAllListeners]; +} + + #pragma mark - Events timeline - (MXEventTimeline*)timelineOnEvent:(NSString*)eventId; { @@ -1625,7 +2207,7 @@ - (void)removeOutgoingMessage:(NSString*)outgoingMessageEventId } // If required, update the last message - if ([self.summary.lastMessageEvent.eventId isEqualToString:outgoingMessageEventId]) + if ([self.summary.lastMessageEventId isEqualToString:outgoingMessageEventId]) { [self.summary resetLastMessage:nil failure:nil commit:YES]; } @@ -1917,7 +2499,9 @@ - (BOOL)handleReceiptEvent:(MXEvent *)event direction:(MXTimelineDirection)direc if (managedEvents) { // Notify listeners - [_liveTimeline notifyListeners:event direction:direction]; + [self liveTimeline:^(MXEventTimeline *theLiveTimeline) { + [theLiveTimeline notifyListeners:event direction:direction]; + }]; } return managedEvents; @@ -2179,7 +2763,9 @@ - (BOOL)storeLocalReceipt:(NSString *)receiptType eventId:(NSString *)eventId us } }]; - [_liveTimeline notifyListeners:receiptEvent direction:MXTimelineDirectionForwards]; + [self liveTimeline:^(MXEventTimeline *theLiveTimeline) { + [theLiveTimeline notifyListeners:receiptEvent direction:MXTimelineDirectionForwards]; + }]; } return YES; @@ -2239,182 +2825,204 @@ - (MXHTTPOperation*)setIsDirect:(BOOL)isDirect success:(void (^)(void))success failure:(void (^)(NSError *error))failure { - if (isDirect == NO) - { - if (_directUserId) + // Create an empty operation that will be mutated later + MXHTTPOperation *operation = [[MXHTTPOperation alloc] init]; + + MXWeakify(self); + [self members:^(MXRoomMembers *roomMembers) { + MXStrongifyAndReturnIfNil(self); + + NSString *myUserId = self.mxSession.myUser.userId; + NSMutableDictionary*> *directRooms = self.mxSession.directRooms; + + if (isDirect == NO) { - NSArray *savedRoomLists = mxSession.directRooms[_directUserId]; - NSString *savedDirectUserId = _directUserId; - NSMutableArray *roomLists = [NSMutableArray arrayWithArray:savedRoomLists]; - - [roomLists removeObject:self.roomId]; - - if (roomLists.count) + if (self.directUserId) { - [mxSession.directRooms setObject:roomLists forKey:_directUserId]; - } - else - { - [mxSession.directRooms removeObjectForKey:_directUserId]; - } - - // Update - _directUserId = nil; - - // Upload the updated direct rooms directory. - // mxSession will post the 'kMXSessionDirectRoomsDidChangeNotification' notification on success. - MXWeakify(self); - return [mxSession uploadDirectRooms:success failure:^(NSError *error) { - MXStrongifyAndReturnIfNil(self); - - // Restore the previous configuration - if (savedRoomLists) + NSArray *savedRoomLists = directRooms[self.directUserId]; + NSString *savedDirectUserId = self.directUserId; + NSMutableArray *roomLists = [NSMutableArray arrayWithArray:savedRoomLists]; + + [roomLists removeObject:self.roomId]; + + if (roomLists.count) { - self.directUserId = savedDirectUserId; - [self.mxSession.directRooms setObject:savedRoomLists forKey:self.directUserId]; + [directRooms setObject:roomLists forKey:self.directUserId]; } - - if (failure) + else { - failure(error); + [directRooms removeObjectForKey:self.directUserId]; } - - }]; - } - } - else if (!_directUserId || (userId && ![userId isEqualToString:_directUserId])) - { - // Here the room is not direct yet, or it is direct with the wrong user - NSString *newDirectUserId = userId; - - if (!newDirectUserId) - { - // By default mark as direct this room for the oldest joined member. - NSArray *members = self.state.joinedMembers; - MXRoomMember *oldestJoinedMember; - - for (MXRoomMember *member in members) - { - if (![member.userId isEqualToString:mxSession.myUser.userId]) - { - if (!oldestJoinedMember) + + // Update + self.directUserId = nil; + + // Upload the updated direct rooms directory. + // mxSession will post the 'kMXSessionDirectRoomsDidChangeNotification' notification on success. + MXWeakify(self); + MXHTTPOperation *operation2 = [self.mxSession uploadDirectRooms:success failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + + // Restore the previous configuration + if (savedRoomLists) { - oldestJoinedMember = member; + self.directUserId = savedDirectUserId; + [directRooms setObject:savedRoomLists forKey:self.directUserId]; } - else if (member.originalEvent.originServerTs < oldestJoinedMember.originalEvent.originServerTs) + + if (failure) { - oldestJoinedMember = member; + failure(error); } + + }]; + + if (operation2) + { + [operation mutateTo:operation2]; } + return; } - - newDirectUserId = oldestJoinedMember.userId; + } + else if (!self.directUserId || (userId && ![userId isEqualToString:self.directUserId])) + { + // Here the room is not direct yet, or it is direct with the wrong user + NSString *newDirectUserId = userId; + if (!newDirectUserId) { - // Consider the first invited member if none has joined - members = [self.state membersWithMembership:MXMembershipInvite]; - - MXRoomMember *oldestInvitedMember; + // By default mark as direct this room for the oldest joined member. + NSArray *members = roomMembers.joinedMembers; + MXRoomMember *oldestJoinedMember; + for (MXRoomMember *member in members) { - if (![member.userId isEqualToString:mxSession.myUser.userId]) + if (![member.userId isEqualToString:myUserId]) { - if (!oldestInvitedMember) + if (!oldestJoinedMember) { - oldestInvitedMember = member; + oldestJoinedMember = member; } - else if (member.originalEvent.originServerTs < oldestInvitedMember.originalEvent.originServerTs) + else if (member.originalEvent.originServerTs < oldestJoinedMember.originalEvent.originServerTs) { - oldestInvitedMember = member; + oldestJoinedMember = member; } } } - - newDirectUserId = oldestInvitedMember.userId; - } - - if (!newDirectUserId) - { - // Use the current user by default - newDirectUserId = mxSession.myUser.userId; - } - } - - // Add the room id in the direct chats listed for this user. - // Check whether the room id is not already present (in this case `_directUserId` was not updated yet), - // this may happen during invite handling. - NSArray *savedNewDirectUserIdRoomLists = mxSession.directRooms[newDirectUserId]; - if (!savedNewDirectUserIdRoomLists || [savedNewDirectUserIdRoomLists indexOfObject:self.roomId] == NSNotFound) - { - NSArray *savedDirectUserIdRoomLists = nil; - NSString *savedDirectUserId = _directUserId; - - NSMutableArray *roomLists = (savedNewDirectUserIdRoomLists ? [NSMutableArray arrayWithArray:savedNewDirectUserIdRoomLists] : [NSMutableArray array]); - [roomLists addObject:self.roomId]; - [mxSession.directRooms setObject:roomLists forKey:newDirectUserId]; - - // Remove the room id for the current direct user if any - if (_directUserId) - { - savedDirectUserIdRoomLists = mxSession.directRooms[_directUserId]; - roomLists = [NSMutableArray arrayWithArray:savedDirectUserIdRoomLists]; - [roomLists removeObject:self.roomId]; - if (roomLists.count) + + newDirectUserId = oldestJoinedMember.userId; + if (!newDirectUserId) { - [mxSession.directRooms setObject:roomLists forKey:_directUserId]; + // Consider the first invited member if none has joined + members = [roomMembers membersWithMembership:MXMembershipInvite]; + + MXRoomMember *oldestInvitedMember; + for (MXRoomMember *member in members) + { + if (![member.userId isEqualToString:myUserId]) + { + if (!oldestInvitedMember) + { + oldestInvitedMember = member; + } + else if (member.originalEvent.originServerTs < oldestInvitedMember.originalEvent.originServerTs) + { + oldestInvitedMember = member; + } + } + } + + newDirectUserId = oldestInvitedMember.userId; } - else + + if (!newDirectUserId) { - [mxSession.directRooms removeObjectForKey:_directUserId]; + // Use the current user by default + newDirectUserId = myUserId; } } - - // Update - _directUserId = newDirectUserId; - - // Upload the updated direct rooms directory. - // mxSession will post the 'kMXSessionDirectRoomsDidChangeNotification' notification on success. - MXWeakify(self); - return [mxSession uploadDirectRooms:success failure:^(NSError *error) { - MXStrongifyAndReturnIfNil(self); - - // Restore the previous configuration - self.directUserId = savedDirectUserId; - if (savedDirectUserIdRoomLists) - { - [self.mxSession.directRooms setObject:savedDirectUserIdRoomLists forKey:self.directUserId]; - } - - if (savedNewDirectUserIdRoomLists) - { - [self.mxSession.directRooms setObject:savedNewDirectUserIdRoomLists forKey:newDirectUserId]; - } - else + + // Add the room id in the direct chats listed for this user. + // Check whether the room id is not already present (in this case `_directUserId` was not updated yet), + // this may happen during invite handling. + NSArray *savedNewDirectUserIdRoomLists = directRooms[newDirectUserId]; + if (!savedNewDirectUserIdRoomLists || [savedNewDirectUserIdRoomLists indexOfObject:self.roomId] == NSNotFound) + { + NSArray *savedDirectUserIdRoomLists = nil; + NSString *savedDirectUserId = self.directUserId; + + NSMutableArray *roomLists = (savedNewDirectUserIdRoomLists ? [NSMutableArray arrayWithArray:savedNewDirectUserIdRoomLists] : [NSMutableArray array]); + [roomLists addObject:self.roomId]; + [directRooms setObject:roomLists forKey:newDirectUserId]; + + // Remove the room id for the current direct user if any + if (self.directUserId) { - [self.mxSession.directRooms removeObjectForKey:newDirectUserId]; + savedDirectUserIdRoomLists = directRooms[self.directUserId]; + roomLists = [NSMutableArray arrayWithArray:savedDirectUserIdRoomLists]; + [roomLists removeObject:self.roomId]; + if (roomLists.count) + { + [directRooms setObject:roomLists forKey:self.directUserId]; + } + else + { + [directRooms removeObjectForKey:self.directUserId]; + } } - - if (failure) + + // Update + self.directUserId = newDirectUserId; + + // Upload the updated direct rooms directory. + // mxSession will post the 'kMXSessionDirectRoomsDidChangeNotification' notification on success. + MXWeakify(self); + MXHTTPOperation *operation2 = [self.mxSession uploadDirectRooms:success failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + + // Restore the previous configuration + self.directUserId = savedDirectUserId; + if (savedDirectUserIdRoomLists) + { + [directRooms setObject:savedDirectUserIdRoomLists forKey:self.directUserId]; + } + + if (savedNewDirectUserIdRoomLists) + { + [directRooms setObject:savedNewDirectUserIdRoomLists forKey:newDirectUserId]; + } + else + { + [directRooms removeObjectForKey:newDirectUserId]; + } + + if (failure) + { + failure(error); + } + }]; + + if (operation2) { - failure(error); + [operation mutateTo:operation2]; } - - }]; + return; + } + else + { + // Update directUserId field. + self.directUserId = newDirectUserId; + } } - else + + // Here the room has already the right value for the direct tag + if (success) { - // Update directUserId field. - _directUserId = newDirectUserId; + success(); } - } - - // Here the room has already the right value for the direct tag - if (success) - { - success(); - } - - return nil; + } failure:failure]; + + return operation; } #pragma mark - Crypto @@ -2427,7 +3035,6 @@ - (MXHTTPOperation *)enableEncryptionWithAlgorithm:(NSString *)algorithm if (mxSession.crypto) { // Send the information to the homeserver - MXWeakify(self); operation = [self sendStateEventOfType:kMXEventTypeStringRoomEncryption content:@{ @"algorithm": algorithm @@ -2438,14 +3045,16 @@ - (MXHTTPOperation *)enableEncryptionWithAlgorithm:(NSString *)algorithm // Wait for the event coming back from the hs id eventBackListener; - eventBackListener = [_liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomEncryption] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - MXStrongifyAndReturnIfNil(self); + eventBackListener = [self listenToEventsOfTypes:@[kMXEventTypeStringRoomEncryption] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [self.liveTimeline removeListener:eventBackListener]; + [self removeListener:eventBackListener]; // Dispatch to let time to MXCrypto to digest the m.room.encryption event dispatch_async(dispatch_get_main_queue(), ^{ - success(); + if (success) + { + success(); + } }); }]; } @@ -2466,7 +3075,7 @@ - (MXHTTPOperation *)enableEncryptionWithAlgorithm:(NSString *)algorithm - (NSString *)description { - return [NSString stringWithFormat:@" %@: %@ - %@", self, self.roomId, self.state.name, self.state.topic]; + return [NSString stringWithFormat:@" %@: %@ - %@", self, self.roomId, self.summary.displayname, self.summary.topic]; } - (NSComparisonResult)compareLastMessageEventOriginServerTs:(MXRoom *)otherRoom diff --git a/MatrixSDK/Data/MXRoomMembers.h b/MatrixSDK/Data/MXRoomMembers.h new file mode 100644 index 0000000000..4f8063a580 --- /dev/null +++ b/MatrixSDK/Data/MXRoomMembers.h @@ -0,0 +1,105 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXEvent.h" +#import "MXRoomMember.h" + +@class MXRoomState, MXSession; + +/** + `MXRoomMembers` holds room members of a given room. + + Room members are part of room state events but, for performance reason (they can + be very very numerous), they are store aside other room state events. + */ +@interface MXRoomMembers : NSObject + +/** + Create a `MXRoomMembers` instance. + + @paran state the room state it depends on. + @param matrixSession the session to the home server. + + @return The newly-initialized MXRoomMembers. + */ +- (instancetype)initWithRoomState:(MXRoomState*)state andMatrixSession:(MXSession*)matrixSession; + +/** + A copy of the list of room members. + */ +@property (nonatomic, readonly) NSArray *members; + +/** + A copy of the list of joined room members. + */ +@property (nonatomic, readonly) NSArray *joinedMembers; + +/** + Return the member with the given user id. + + @param userId the id of the member to retrieve. + @return the room member. + */ +- (MXRoomMember*)memberWithUserId:(NSString*)userId; + +/** + Return a display name for a member. + It is his displayname member or, if nil, his userId. + Disambiguate members who have the same displayname in the room by adding his userId. + */ +- (NSString*)memberName:(NSString*)userId; + +/** + Return a display name for a member suitable to compare and sort members list + */ +- (NSString*)memberSortedName:(NSString*)userId; + +/** + Return the list of members with a given membership. + + @param membership the membership to look for. + @return an array of MXRoomMember objects. + */ +- (NSArray*)membersWithMembership:(MXMembership)membership; + +/** + A copy of the list of room members excluding the conference user. + */ +- (NSArray*)membersWithoutConferenceUser; + +/** + Return the list of members with a given membership with or without the conference user. + + @param membership the membership to look for. + @param includeConferenceUser NO to filter the conference user. + @return an array of MXRoomMember objects. + */ +- (NSArray*)membersWithMembership:(MXMembership)membership includeConferenceUser:(BOOL)includeConferenceUser; + + +#pragma mark - State events handling + +/** + Process state events in order to update room members. + + @param stateEvents an array of state events. + @return YES if there was a change in MXRoomMembers. + */ +- (BOOL)handleStateEvents:(NSArray *)stateEvents; + +@end diff --git a/MatrixSDK/Data/MXRoomMembers.m b/MatrixSDK/Data/MXRoomMembers.m new file mode 100644 index 0000000000..c7db604d39 --- /dev/null +++ b/MatrixSDK/Data/MXRoomMembers.m @@ -0,0 +1,315 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXRoomMembers.h" + +#import "MXRoomState.h" +#import "MXSession.h" +#import "MXSDKOptions.h" + +@interface MXRoomMembers () +{ + MXSession *mxSession; + MXRoomState *state; + + /** + Members ordered by userId. + */ + NSMutableDictionary *members; + + /** + Track the usage of members displaynames in order to disambiguate them if necessary, + ie if the same displayname is used by several users, we have to update their displaynames. + displayname -> count (= how many members of the room uses this displayname) + */ + NSMutableDictionary *membersNamesInUse; +} +@end + +@implementation MXRoomMembers + +- (instancetype)initWithRoomState:(MXRoomState *)roomState andMatrixSession:(MXSession*)matrixSession +{ + self = [super init]; + if (self) + { + mxSession = matrixSession; + state = roomState; + + members = [NSMutableDictionary dictionary]; + membersNamesInUse = [NSMutableDictionary dictionary]; + } + return self; +} + +- (NSArray *)members +{ + return [members allValues]; +} + +- (NSArray *)joinedMembers +{ + return [self membersWithMembership:MXMembershipJoin]; +} + +#pragma mark - Memberships +- (MXRoomMember*)memberWithUserId:(NSString *)userId +{ + return members[userId]; +} + +- (NSString*)memberName:(NSString*)userId +{ + // Sanity check (ignore the request if the room state is not initialized yet) + if (!userId.length || !membersNamesInUse) + { + return nil; + } + + NSString *displayName; + + // Get the user display name from the member list of the room + MXRoomMember *member = [self memberWithUserId:userId]; + if (member && member.displayname.length) + { + displayName = member.displayname; + } + + if (displayName) + { + // Do we need to disambiguate it? + NSNumber *memberNameCount = membersNamesInUse[displayName]; + if (memberNameCount && [memberNameCount unsignedIntegerValue] > 1) + { + // There are more than one member that uses the same displayname, so, yes, disambiguate it + displayName = [NSString stringWithFormat:@"%@ (%@)", displayName, userId]; + } + } + else + { + // By default, use the user ID + displayName = userId; + } + + return displayName; +} + +- (NSString*)memberSortedName:(NSString*)userId +{ + // Get the user display name from the member list of the room + MXRoomMember *member = [self memberWithUserId:userId]; + NSString *displayName = member.displayname; + + // Do not disambiguate here members who have the same displayname in the room (see memberName:). + + if (!displayName) + { + // By default, use the user ID + displayName = userId; + } + + return displayName; +} + +- (NSArray*)membersWithMembership:(MXMembership)theMembership +{ + NSMutableArray *membersWithMembership = [NSMutableArray array]; + for (MXRoomMember *roomMember in members.allValues) + { + if (roomMember.membership == theMembership) + { + [membersWithMembership addObject:roomMember]; + } + } + return membersWithMembership; +} + +- (NSArray *)membersWithoutConferenceUser +{ + NSArray *membersWithoutConferenceUser; + + if (state.isConferenceUserRoom) + { + // Show everyone in a 1:1 room with a conference user + membersWithoutConferenceUser = self.members; + } + else if (![self memberWithUserId:state.conferenceUserId]) + { + // There is no conference user. No need to filter + membersWithoutConferenceUser = self.members; + } + else + { + // Filter the conference user from the list + NSMutableDictionary *membersWithoutConferenceUserDict = [NSMutableDictionary dictionaryWithDictionary:members]; + [membersWithoutConferenceUserDict removeObjectForKey:state.conferenceUserId]; + membersWithoutConferenceUser = membersWithoutConferenceUserDict.allValues; + } + + return membersWithoutConferenceUser; +} + +- (NSArray *)membersWithMembership:(MXMembership)theMembership includeConferenceUser:(BOOL)includeConferenceUser +{ + NSArray *membersWithMembership; + + if (includeConferenceUser || state.isConferenceUserRoom) + { + // Show everyone in a 1:1 room with a conference user + membersWithMembership = [self membersWithMembership:theMembership]; + } + else + { + MXRoomMember *conferenceUserMember = [self memberWithUserId:state.conferenceUserId]; + if (!conferenceUserMember || conferenceUserMember.membership != theMembership) + { + // The conference user is not in list of members with the passed membership + membersWithMembership = [self membersWithMembership:theMembership]; + } + else + { + NSMutableDictionary *membersWithMembershipDict = [NSMutableDictionary dictionaryWithCapacity:members.count]; + for (MXRoomMember *roomMember in members.allValues) + { + if (roomMember.membership == theMembership) + { + membersWithMembershipDict[roomMember.userId] = roomMember; + } + } + + [membersWithMembershipDict removeObjectForKey:state.conferenceUserId]; + membersWithMembership = membersWithMembershipDict.allValues; + } + } + + return membersWithMembership; +} + +#pragma mark - State events handling +- (BOOL)handleStateEvents:(NSArray *)stateEvents; +{ + BOOL hasRoomMemberEvent = NO; + + @autoreleasepool + { + for (MXEvent *event in stateEvents) + { + switch (event.eventType) + { + case MXEventTypeRoomMember: + { + hasRoomMemberEvent = YES; + + // Remove the previous MXRoomMember of this user from membersNamesInUse + NSString *userId = event.stateKey; + MXRoomMember *oldRoomMember = members[userId]; + if (oldRoomMember && oldRoomMember.displayname) + { + NSNumber *memberNameCount = membersNamesInUse[oldRoomMember.displayname]; + if (memberNameCount) + { + NSUInteger count = [memberNameCount unsignedIntegerValue]; + if (count) + { + count--; + } + + if (count) + { + membersNamesInUse[oldRoomMember.displayname] = @(count); + } + else + { + [membersNamesInUse removeObjectForKey:oldRoomMember.displayname]; + } + } + } + + MXRoomMember *roomMember = [[MXRoomMember alloc] initWithMXEvent:event andEventContent:[state contentOfEvent:event]]; + if (roomMember) + { + /// Update membersNamesInUse + if (roomMember.displayname) + { + NSUInteger count = 1; + + NSNumber *memberNameCount = membersNamesInUse[roomMember.displayname]; + if (memberNameCount) + { + // We have several users using the same displayname + count = [memberNameCount unsignedIntegerValue]; + count++; + } + + membersNamesInUse[roomMember.displayname] = @(count); + } + + members[roomMember.userId] = roomMember; + + // Handle here the case where the member has no defined avatar. + if (nil == roomMember.avatarUrl && ![MXSDKOptions sharedInstance].disableIdenticonUseForUserAvatar) + { + // Force to use an identicon url + roomMember.avatarUrl = [mxSession.matrixRestClient urlOfIdenticon:roomMember.userId]; + } + } + else + { + // The user is no more part of the room. Remove him. + // This case happens during back pagination: we remove here users when they are not in the room yet. + [members removeObjectForKey:event.stateKey]; + } + + // Special handling for presence: update MXUser data in case of membership event. + // CAUTION: ignore here redacted state event, the redaction concerns only the context of the event room. + if (state.isLive && !event.isRedactedEvent && roomMember.membership == MXMembershipJoin) + { + MXUser *user = [mxSession getOrCreateUser:event.sender]; + [user updateWithRoomMemberEvent:event roomMember:roomMember inMatrixSession:mxSession]; + + [mxSession.store storeUser:user]; + } + + break; + } + + default: + break; + } + } + } + + return hasRoomMemberEvent; +} + +#pragma mark - NSCopying +- (id)copyWithZone:(NSZone *)zone +{ + MXRoomMembers *membersCopy = [[MXRoomMembers allocWithZone:zone] init]; + + membersCopy->mxSession = mxSession; + membersCopy->state = state; + + // MXRoomMember objects in members are immutable. A new instance of it is created each time + // the sdk receives room member event, even if it is an update of an existing member like a + // membership change (ex: "invited" -> "joined") + membersCopy->members = [members mutableCopyWithZone:zone]; + + membersCopy->membersNamesInUse = [membersNamesInUse mutableCopyWithZone:zone]; + + return membersCopy; +} +@end diff --git a/MatrixSDK/Data/MXRoomMembersCount.h b/MatrixSDK/Data/MXRoomMembersCount.h new file mode 100644 index 0000000000..3efd24cde3 --- /dev/null +++ b/MatrixSDK/Data/MXRoomMembersCount.h @@ -0,0 +1,28 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +/** + Room members counts. + */ +@interface MXRoomMembersCount : NSObject + +@property (nonatomic) NSUInteger members; +@property (nonatomic) NSUInteger joined; +@property (nonatomic) NSUInteger invited; + +@end diff --git a/MatrixSDK/Data/MXRoomMembersCount.m b/MatrixSDK/Data/MXRoomMembersCount.m new file mode 100644 index 0000000000..83a2e7d11b --- /dev/null +++ b/MatrixSDK/Data/MXRoomMembersCount.m @@ -0,0 +1,72 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXRoomMembersCount.h" + + +@implementation MXRoomMembersCount + + +- (BOOL)isEqual:(id)object +{ + BOOL isEqual = NO; + + if ([object isKindOfClass:MXRoomMembersCount.class]) + { + MXRoomMembersCount *other = object; + isEqual = _members == other.members + && _joined == other.joined + && _invited == other.invited; + } + + return isEqual; +} + + +#pragma mark - NSCopying +- (id)copyWithZone:(NSZone *)zone +{ + MXRoomMembersCount *roomMembersCount = [[MXRoomMembersCount allocWithZone:zone] init]; + + roomMembersCount.members = self.members; + roomMembersCount.joined = self.joined; + roomMembersCount.invited = self.invited; + + return roomMembersCount; +} + + +#pragma mark - NSCoding +- (instancetype)initWithCoder:(NSCoder *)aDecoder +{ + self = [self init]; + if (self) + { + _members = (NSUInteger)[aDecoder decodeIntegerForKey:@"members"]; + _joined = (NSUInteger)[aDecoder decodeIntegerForKey:@"joined"]; + _invited = (NSUInteger)[aDecoder decodeIntegerForKey:@"invited"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder +{ + [aCoder encodeInteger:(NSInteger)_members forKey:@"members"]; + [aCoder encodeInteger:(NSInteger)_joined forKey:@"joined"]; + [aCoder encodeInteger:(NSInteger)_invited forKey:@"invited"]; +} + +@end diff --git a/MatrixSDK/Data/MXRoomState.h b/MatrixSDK/Data/MXRoomState.h index 69779f5ed6..9e3240ba46 100644 --- a/MatrixSDK/Data/MXRoomState.h +++ b/MatrixSDK/Data/MXRoomState.h @@ -19,10 +19,14 @@ #import "MXEvent.h" #import "MXJSONModels.h" -#import "MXRoomMember.h" +#import "MXRoomMembers.h" #import "MXRoomThirdPartyInvite.h" #import "MXRoomPowerLevels.h" #import "MXEnumConstants.h" +#import "MXRoomMembersCount.h" +#import "MXStore.h" +#import "MXRoomTombStoneContent.h" +#import "MXRoomCreateContent.h" @class MXSession; @@ -37,7 +41,7 @@ @interface MXRoomState : NSObject /** - The room ID + The room id. */ @property (nonatomic, readonly) NSString *roomId; @@ -53,14 +57,20 @@ @property (nonatomic, readonly) NSArray *stateEvents; /** - A copy of the list of room members. + Room members of the room. + + In case of lazy-loading of room members (@see MXSession.syncWithLazyLoadOfRoomMembers), + `MXRoomState.members` contains only a subset of all actual room members. This subset + is enough to render the events timeline owning the `MXRoomState` instance. + + Use [MXRoom members:] to get the full list of room members. */ -@property (nonatomic, readonly) NSArray *members; +@property (nonatomic, readonly) MXRoomMembers *members; /** - A copy of the list of joined room members. + Cache counts for MXRoomState.members`. */ -@property (nonatomic, readonly) NSArray *joinedMembers; +@property (nonatomic, readonly) MXRoomMembersCount *membersCount; /** A copy of the list of third party invites (actually MXRoomThirdPartyInvite instances). @@ -89,6 +99,8 @@ A copy of the list of third party invites (actually MXRoomThirdPartyInvite insta /** The name of the room as provided by the home server. + +Use MXRoomSummary.displayname to get a computed room display name. */ @property (nonatomic, readonly) NSString *name; @@ -122,12 +134,6 @@ A copy of the list of third party invites (actually MXRoomThirdPartyInvite insta */ @property (nonatomic, readonly) MXRoomGuestAccess guestAccess NS_REFINED_FOR_SWIFT; -/** - The display name of the room. - It is computed from information retrieved so far. - */ -@property (nonatomic, readonly) NSString *displayname; - /** The membership state of the logged in user for this room @@ -146,6 +152,15 @@ A copy of the list of third party invites (actually MXRoomThirdPartyInvite insta */ @property (nonatomic, readonly) NSString *encryptionAlgorithm; +/** + Indicate whether this room is obsolete (had a `m.room.tombstone` state event type). + */ +@property (nonatomic, readonly) BOOL isObsolete; + +/** + If any the state event content for event type `m.room.tombstone` + */ +@property (nonatomic, strong, readonly) MXRoomTombStoneContent *tombStoneContent; /** Create a `MXRoomState` instance. @@ -178,6 +193,19 @@ A copy of the list of third party invites (actually MXRoomThirdPartyInvite insta andInitialSync:(MXRoomInitialSync*)initialSync andDirection:(BOOL)isLive; +/** + Load a `MXRoomState` instance from the store. + + @param store the store to mount data from and to store live data to. + @param roomId the id of the room. + @param matrixSession the session to use. + @param onComplete the block providing the new instance. + */ ++ (void)loadRoomStateFromStore:(id)store + withRoomId:(NSString *)roomId + matrixSession:(MXSession *)matrixSession + onComplete:(void (^)(MXRoomState *roomState))onComplete; + /** Create a `MXRoomState` instance used as a back state of a room. Such instance holds the state of a room at a given time in the room history. @@ -188,11 +216,11 @@ A copy of the list of third party invites (actually MXRoomThirdPartyInvite insta - (id)initBackStateWith:(MXRoomState*)state; /** - Process a state event in order to update the room state. + Process state events in order to update the room state. - @param event the state event. + @param stateEvents an array of state events. */ -- (void)handleStateEvent:(MXEvent*)event; +- (void)handleStateEvents:(NSArray *)stateEvents; /** Return the state events with the given type. @@ -203,12 +231,14 @@ A copy of the list of third party invites (actually MXRoomThirdPartyInvite insta - (NSArray *)stateEventsWithType:(MXEventTypeString)eventType NS_REFINED_FOR_SWIFT; /** - Return the member with the given user id. - - @param userId the id of the member to retrieve. - @return the room member. + According to the direction of the instance, we are interested either by + the content of the event or its prev_content. + + @param event the event to get the content from. + + @return content or prev_content dictionary. */ -- (MXRoomMember*)memberWithUserId:(NSString*)userId; +- (NSDictionary *)contentOfEvent:(MXEvent*)event; /** Return the member who was invited by a 3pid medium with the given token. @@ -231,18 +261,6 @@ A copy of the list of third party invites (actually MXRoomThirdPartyInvite insta */ - (MXRoomThirdPartyInvite*)thirdPartyInviteWithToken:(NSString*)thirdPartyInviteToken; -/** - Return a display name for a member. - It is his displayname member or, if nil, his userId. - Disambiguate members who have the same displayname in the room by adding his userId. - */ -- (NSString*)memberName:(NSString*)userId; - -/** - Return a display name for a member suitable to compare and sort members list - */ -- (NSString*)memberSortedName:(NSString*)userId; - /** Normalize (between 0 and 1) the power level of a member compared to other members. @@ -251,14 +269,6 @@ A copy of the list of third party invites (actually MXRoomThirdPartyInvite insta */ - (float)memberNormalizedPowerLevel:(NSString*)userId; -/** - Return the list of members with a given membership. - - @param membership the membership to look for. - @return an array of MXRoomMember objects. - */ -- (NSArray*)membersWithMembership:(MXMembership)membership; - # pragma mark - Conference call /** @@ -277,17 +287,4 @@ A copy of the list of third party invites (actually MXRoomThirdPartyInvite insta */ @property (nonatomic, readonly) NSString *conferenceUserId; -/** - A copy of the list of room members excluding the conference user. - */ -- (NSArray*)membersWithoutConferenceUser; - -/** - Return the list of members with a given membership with or without the conference user. - - @param membership the membership to look for. - @param includeConferenceUser NO to filter the conference user. - @return an array of MXRoomMember objects. - */ -- (NSArray*)membersWithMembership:(MXMembership)membership includeConferenceUser:(BOOL)includeConferenceUser; @end diff --git a/MatrixSDK/Data/MXRoomState.m b/MatrixSDK/Data/MXRoomState.m index cb065d39e4..e6cb3c8e5d 100644 --- a/MatrixSDK/Data/MXRoomState.m +++ b/MatrixSDK/Data/MXRoomState.m @@ -32,11 +32,6 @@ @interface MXRoomState () */ NSMutableDictionary*> *stateEvents; - /** - Members ordered by userId. - */ - NSMutableDictionary *members; - /** The room aliases. The key is the domain. */ @@ -47,23 +42,11 @@ @interface MXRoomState () */ NSMutableDictionary *thirdPartyInvites; - /** - Additional and optional metadata got from initialSync - */ - MXMembership membership; - /** Maximum power level observed in power level list */ NSInteger maxPowerLevel; - /** - Track the usage of members displaynames in order to disambiguate them if necessary, - ie if the same displayname is used by several users, we have to update their displaynames. - displayname -> count (= how many members of the room uses this displayname) - */ - NSMutableDictionary *membersNamesInUse; - /** Cache for [self memberWithThirdPartyInviteToken]. The key is the 3pid invite token. @@ -93,10 +76,10 @@ - (id)initWithRoomId:(NSString*)roomId _isLive = isLive; stateEvents = [NSMutableDictionary dictionary]; - members = [NSMutableDictionary dictionary]; + _members = [[MXRoomMembers alloc] initWithRoomState:self andMatrixSession:mxSession]; + _membersCount = [MXRoomMembersCount new]; roomAliases = [NSMutableDictionary dictionary]; thirdPartyInvites = [NSMutableDictionary dictionary]; - membersNamesInUse = [NSMutableDictionary dictionary]; membersWithThirdPartyInviteTokenCache = [NSMutableDictionary dictionary]; } return self; @@ -115,13 +98,29 @@ - (id)initWithRoomId:(NSString*)roomId { if (initialSync.membership) { - membership = [MXTools membership:initialSync.membership]; + _membership = [MXTools membership:initialSync.membership]; } } } return self; } ++ (void)loadRoomStateFromStore:(id)store + withRoomId:(NSString *)roomId + matrixSession:(MXSession *)matrixSession + onComplete:(void (^)(MXRoomState *roomState))onComplete +{ + MXRoomState *roomState = [[MXRoomState alloc] initWithRoomId:roomId andMatrixSession:matrixSession andDirection:YES]; + if (roomState) + { + [store stateOfRoom:roomId success:^(NSArray * _Nonnull stateEvents) { + [roomState handleStateEvents:stateEvents]; + + onComplete(roomState); + } failure:nil]; + } +} + - (id)initBackStateWith:(MXRoomState*)state { self = [state copy]; @@ -172,7 +171,7 @@ - (id)initBackStateWith:(MXRoomState*)state } // Members are also state events - for (MXRoomMember *roomMember in self.members) + for (MXRoomMember *roomMember in self.members.members) { [state addObject:roomMember.originalEvent]; } @@ -192,16 +191,6 @@ - (id)initBackStateWith:(MXRoomState*)state return state; } -- (NSArray *)members -{ - return [members allValues]; -} - -- (NSArray *)joinedMembers -{ - return [self membersWithMembership:MXMembershipJoin]; -} - - (NSArray *)thirdPartyInvites { return [thirdPartyInvites allValues]; @@ -343,300 +332,155 @@ - (MXRoomGuestAccess)guestAccess return guestAccess; } -- (NSString *)displayname +- (BOOL)isEncrypted { - // Reuse the Synapse web client algo - - NSString *displayname = self.name; - - // Check for alias (consider first canonical alias). - NSString *alias = self.canonicalAlias; - if (!alias) - { - // For rooms where canonical alias is not defined, we use the 1st alias as a workaround - NSArray *aliases = self.aliases; - - if (aliases.count) - { - alias = [aliases[0] copy]; - } - } - - // Compute a name if none - if (!displayname) - { - // use alias (if any) - if (alias) - { - displayname = alias; - } - // use members - else if (members.count > 0) - { - if (members.count >= 3) - { - // this is a group chat and should have the names of participants - // according to "( , , ..." - NSMutableString* roomName = [[NSMutableString alloc] init]; - int count = 0; - - for (NSString *memberUserId in members.allKeys) - { - if (NO == [memberUserId isEqualToString:mxSession.matrixRestClient.credentials.userId]) - { - MXRoomMember *member = [self memberWithUserId:memberUserId]; - - // only manage the invited an joined users - if ((member.membership == MXMembershipInvite) || (member.membership == MXMembershipJoin)) - { - // some participants are already added - if (roomName.length != 0) - { - // add a separator - [roomName appendString:@", "]; - } - - NSString* username = [self memberSortedName:memberUserId]; - - if (username.length == 0) - { - [roomName appendString:memberUserId]; - } - else - { - [roomName appendString:username]; - } - count++; - } - } - } - - displayname = [NSString stringWithFormat:@"(%d) %@",count, roomName]; - } - else if (members.count == 2) - { - // this is a "one to one" room and should have the name of other user - - for (NSString *memberUserId in members.allKeys) - { - if (NO == [memberUserId isEqualToString:mxSession.matrixRestClient.credentials.userId]) - { - displayname = [self memberName:memberUserId]; - break; - } - } - } - else if (members.count == 1) - { - // this could be just us (self-chat) or could be the other person - // in a room if they have invited us to the room. Find out which - - NSString *otherUserId; - - MXRoomMember *member = members.allValues[0]; - - if ([mxSession.matrixRestClient.credentials.userId isEqualToString:member.userId]) - { - // It is an invite or a self chat - otherUserId = member.originUserId; - } - else - { - // XXX: Not sure how it can happen - // The logged-in user should be always in the list of the room members - otherUserId = member.userId; - } - displayname = [self memberName:otherUserId]; - } - } - } - else if (([displayname hasPrefix:@"#"] == NO) && alias) - { - // Always show the alias in the room displayed name - displayname = [NSString stringWithFormat:@"%@ (%@)", displayname, alias]; - } - - if (!displayname) - { - displayname = [_roomId copy]; - } - - return displayname; + return (0 != self.encryptionAlgorithm.length); } -- (MXMembership)membership +- (NSString *)encryptionAlgorithm { - MXMembership result; - - // Find the current value in room state events - MXRoomMember *user = [self memberWithUserId:mxSession.matrixRestClient.credentials.userId]; - if (user) - { - result = user.membership; - } - else - { - result = membership; - } - - return result; + return stateEvents[kMXEventTypeStringRoomEncryption].lastObject.content[@"algorithm"]; } -- (BOOL)isEncrypted +- (BOOL)isObsolete { - return (0 != self.encryptionAlgorithm.length); + return self.tombStoneContent != nil; } -- (NSString *)encryptionAlgorithm +- (MXRoomTombStoneContent*)tombStoneContent { - return stateEvents[kMXEventTypeStringRoomEncryption].lastObject.content[@"algorithm"]; + MXRoomTombStoneContent *roomTombStoneContent = nil; + + // Check it from the state events + MXEvent *event = stateEvents[kMXEventTypeStringRoomTombStone].lastObject; + NSDictionary *eventContent = [self contentOfEvent:event]; + if (eventContent) + { + roomTombStoneContent = [MXRoomTombStoneContent modelFromJSON:eventContent]; + } + + return roomTombStoneContent; } - #pragma mark - State events handling -- (void)handleStateEvent:(MXEvent*)event +- (void)handleStateEvents:(NSArray *)events; { - switch (event.eventType) + // Process the update on room members + if ([_members handleStateEvents:events]) + { + // Update counters for currently known room members + _membersCount.members = _members.members.count; + _membersCount.joined = _members.joinedMembers.count; + _membersCount.invited = [_members membersWithMembership:MXMembershipInvite].count; + } + + @autoreleasepool { - case MXEventTypeRoomMember: + for (MXEvent *event in events) { - // Remove the previous MXRoomMember of this user from membersNamesInUse - NSString *userId = event.stateKey; - MXRoomMember *oldRoomMember = members[userId]; - if (oldRoomMember && oldRoomMember.displayname) + switch (event.eventType) { - NSNumber *memberNameCount = membersNamesInUse[oldRoomMember.displayname]; - if (memberNameCount) + case MXEventTypeRoomMember: { - NSUInteger count = [memberNameCount unsignedIntegerValue]; - if (count) + // User in this membership event + NSString *userId = event.stateKey ? event.stateKey : event.sender; + + NSDictionary *content = [self contentOfEvent:event]; + + // Compute my user membership indepently from MXRoomMembers + if ([userId isEqualToString:mxSession.myUser.userId]) { - count--; + MXRoomMember *roomMember = [[MXRoomMember alloc] initWithMXEvent:event andEventContent:content]; + _membership = roomMember.membership; } - if (count) + if (content[@"third_party_invite"][@"signed"][@"token"]) { - membersNamesInUse[oldRoomMember.displayname] = @(count); + // Cache room member event that is successor of a third party invite event + MXRoomMember *roomMember = [[MXRoomMember alloc] initWithMXEvent:event andEventContent:content]; + membersWithThirdPartyInviteTokenCache[roomMember.thirdPartyInviteToken] = roomMember; } - else + + // In case of invite, process the provided but incomplete room state + if (self.membership == MXMembershipInvite && event.inviteRoomState) { - [membersNamesInUse removeObjectForKey:oldRoomMember.displayname]; + [self handleStateEvents:event.inviteRoomState]; } - } - } - - MXRoomMember *roomMember = [[MXRoomMember alloc] initWithMXEvent:event andEventContent:[self contentOfEvent:event]]; - if (roomMember) - { - /// Update membersNamesInUse - if (roomMember.displayname) - { - NSUInteger count = 1; - - NSNumber *memberNameCount = membersNamesInUse[roomMember.displayname]; - if (memberNameCount) + else if (_isLive && self.membership == MXMembershipJoin && _membersCount.members > 2) { - // We have several users using the same displayname - count = [memberNameCount unsignedIntegerValue]; - count++; + if ([userId isEqualToString:self.conferenceUserId]) + { + // Forward the change of the conference user membership to the call manager + MXRoomMember *roomMember = [[MXRoomMember alloc] initWithMXEvent:event andEventContent:content]; + [mxSession.callManager handleConferenceUserUpdate:roomMember inRoom:_roomId]; + } } - membersNamesInUse[roomMember.displayname] = @(count); + break; } - - members[roomMember.userId] = roomMember; - - // Handle here the case where the member has no defined avatar. - if (nil == roomMember.avatarUrl && ![MXSDKOptions sharedInstance].disableIdenticonUseForUserAvatar) + case MXEventTypeRoomThirdPartyInvite: { - // Force to use an identicon url - roomMember.avatarUrl = [mxSession.matrixRestClient urlOfIdenticon:roomMember.userId]; + // The content and the prev_content of a m.room.third_party_invite event are the same. + // So, use isLive to know if the invite must be added or removed (case of back state). + if (_isLive) + { + MXRoomThirdPartyInvite *thirdPartyInvite = [[MXRoomThirdPartyInvite alloc] initWithMXEvent:event]; + if (thirdPartyInvite) + { + thirdPartyInvites[thirdPartyInvite.token] = thirdPartyInvite; + } + } + else + { + // Note: the 3pid invite token is stored in the event state key + [thirdPartyInvites removeObjectForKey:event.stateKey]; + } + break; } - - // Cache room member event that is successor of a third party invite event - if (roomMember.thirdPartyInviteToken) + case MXEventTypeRoomAliases: { - membersWithThirdPartyInviteTokenCache[roomMember.thirdPartyInviteToken] = roomMember; + // Sanity check + if (event.stateKey.length) + { + // Store the bunch of aliases for the domain (which is the state_key) + roomAliases[event.stateKey] = event; + } + break; } - } - else - { - // The user is no more part of the room. Remove him. - // This case happens during back pagination: we remove here users when they are not in the room yet. - [members removeObjectForKey:event.stateKey]; - } - - // In case of invite, process the provided but incomplete room state - if (self.membership == MXMembershipInvite && event.inviteRoomState) - { - for (MXEvent *inviteRoomStateEvent in event.inviteRoomState) + case MXEventTypeRoomPowerLevels: { - [self handleStateEvent:inviteRoomStateEvent]; - } - } - else if (_isLive && self.membership == MXMembershipJoin && members.count > 2 && [roomMember.userId isEqualToString:self.conferenceUserId]) - { - // Forward the change of the conference user membership to the call manager - [mxSession.callManager handleConferenceUserUpdate:roomMember inRoom:_roomId]; - } + powerLevels = [MXRoomPowerLevels modelFromJSON:[self contentOfEvent:event]]; + // Compute max power level + maxPowerLevel = powerLevels.usersDefault; + NSArray *array = powerLevels.users.allValues; + for (NSNumber *powerLevel in array) + { + NSInteger level = 0; + MXJSONModelSetInteger(level, powerLevel); + if (level > maxPowerLevel) + { + maxPowerLevel = level; + } + } - break; - } - case MXEventTypeRoomThirdPartyInvite: - { - // The content and the prev_content of a m.room.third_party_invite event are the same. - // So, use isLive to know if the invite must be added or removed (case of back state). - if (_isLive) - { - MXRoomThirdPartyInvite *thirdPartyInvite = [[MXRoomThirdPartyInvite alloc] initWithMXEvent:event]; - if (thirdPartyInvite) - { - thirdPartyInvites[thirdPartyInvite.token] = thirdPartyInvite; - } - } - else - { - // Note: the 3pid invite token is stored in the event state key - [thirdPartyInvites removeObjectForKey:event.stateKey]; - } - break; - } - case MXEventTypeRoomAliases: - { - // Sanity check - if (event.stateKey.length) - { - // Store the bunch of aliases for the domain (which is the state_key) - roomAliases[event.stateKey] = event; - } - break; - } - case MXEventTypeRoomPowerLevels: - { - powerLevels = [MXRoomPowerLevels modelFromJSON:[self contentOfEvent:event]]; - // Compute max power level - maxPowerLevel = powerLevels.usersDefault; - NSArray *array = powerLevels.users.allValues; - for (NSNumber *powerLevel in array) - { - NSInteger level = 0; - MXJSONModelSetInteger(level, powerLevel); - if (level > maxPowerLevel) - { - maxPowerLevel = level; + // Do not break here to store the event into the stateEvents dictionary. } + default: + // Store other states into the stateEvents dictionary. + if (!stateEvents[event.type]) + { + stateEvents[event.type] = [NSMutableArray array]; + } + [stateEvents[event.type] addObject:event]; + break; } - - // Do not break here to store the event into the stateEvents dictionary. } - default: - // Store other states into the stateEvents dictionary. - if (!stateEvents[event.type]) - { - stateEvents[event.type] = [NSMutableArray array]; - } - [stateEvents[event.type] addObject:event]; - break; + } + + // Update store with new room state when all state event have been processed + if (_isLive && [mxSession.store respondsToSelector:@selector(storeStateForRoom:stateEvents:)]) + { + [mxSession.store storeStateForRoom:_roomId stateEvents:self.stateEvents]; } } @@ -645,13 +489,6 @@ - (void)handleStateEvent:(MXEvent*)event return stateEvents[eventType]; } - -#pragma mark - Memberships -- (MXRoomMember*)memberWithUserId:(NSString *)userId -{ - return members[userId]; -} - - (MXRoomMember *)memberWithThirdPartyInviteToken:(NSString *)thirdPartyInviteToken { return membersWithThirdPartyInviteTokenCache[thirdPartyInviteToken]; @@ -662,65 +499,14 @@ - (MXRoomThirdPartyInvite *)thirdPartyInviteWithToken:(NSString *)thirdPartyInvi return thirdPartyInvites[thirdPartyInviteToken]; } -- (NSString*)memberName:(NSString*)userId -{ - // Sanity check (ignore the request if the room state is not initialized yet) - if (!userId.length || !membersNamesInUse) - { - return nil; - } - - NSString *displayName; - - // Get the user display name from the member list of the room - MXRoomMember *member = [self memberWithUserId:userId]; - if (member && member.displayname.length) - { - displayName = member.displayname; - } - - if (displayName) - { - // Do we need to disambiguate it? - NSNumber *memberNameCount = membersNamesInUse[displayName]; - if (memberNameCount && [memberNameCount unsignedIntegerValue] > 1) - { - // There are more than one member that uses the same displayname, so, yes, disambiguate it - displayName = [NSString stringWithFormat:@"%@ (%@)", displayName, userId]; - } - } - else - { - // By default, use the user ID - displayName = userId; - } - - return displayName; -} - -- (NSString*)memberSortedName:(NSString*)userId -{ - // Get the user display name from the member list of the room - MXRoomMember *member = [self memberWithUserId:userId]; - NSString *displayName = member.displayname; - - // Do not disambiguate here members who have the same displayname in the room (see memberName:). - - if (!displayName) - { - // By default, use the user ID - displayName = userId; - } - - return displayName; -} - - (float)memberNormalizedPowerLevel:(NSString*)userId { float powerLevel = 0; - // Get the user display name from the member list of the room - MXRoomMember *member = [self memberWithUserId:userId]; + // Get the user from the member list of the room + // If the app asks for information about a user id, it means that we already + // have the MXRoomMember data + MXRoomMember *member = [self.members memberWithUserId:userId]; // Ignore banned and left (kicked) members if (member.membership != MXMembershipLeave && member.membership != MXMembershipBan) @@ -732,26 +518,12 @@ - (float)memberNormalizedPowerLevel:(NSString*)userId return powerLevel; } -- (NSArray*)membersWithMembership:(MXMembership)theMembership -{ - NSMutableArray *membersWithMembership = [NSMutableArray array]; - for (MXRoomMember *roomMember in members.allValues) - { - if (roomMember.membership == theMembership) - { - [membersWithMembership addObject:roomMember]; - } - } - return membersWithMembership; -} - - # pragma mark - Conference call - (BOOL)isOngoingConferenceCall { BOOL isOngoingConferenceCall = NO; - MXRoomMember *conferenceUserMember = [self memberWithUserId:self.conferenceUserId]; + MXRoomMember *conferenceUserMember = [self.members memberWithUserId:self.conferenceUserId]; if (conferenceUserMember) { isOngoingConferenceCall = (conferenceUserMember.membership == MXMembershipJoin); @@ -765,16 +537,9 @@ - (BOOL)isConferenceUserRoom BOOL isConferenceUserRoom = NO; // A conference user room is a 1:1 room with a conference user - if (members.count == 2) + if (_membersCount.members == 2 && [self.members memberWithUserId:self.conferenceUserId]) { - for (NSString *memberUserId in members) - { - if ([MXCallManager isConferenceUser:memberUserId]) - { - isConferenceUserRoom = YES; - break; - } - } + isConferenceUserRoom = YES; } return isConferenceUserRoom; @@ -789,68 +554,6 @@ - (NSString *)conferenceUserId return conferenceUserId; } -- (NSArray *)membersWithoutConferenceUser -{ - NSArray *membersWithoutConferenceUser; - - if (self.isConferenceUserRoom) - { - // Show everyone in a 1:1 room with a conference user - membersWithoutConferenceUser = self.members; - } - else if (![self memberWithUserId:self.conferenceUserId]) - { - // There is no conference user. No need to filter - membersWithoutConferenceUser = self.members; - } - else - { - // Filter the conference user from the list - NSMutableDictionary *membersWithoutConferenceUserDict = [NSMutableDictionary dictionaryWithDictionary:members]; - [membersWithoutConferenceUserDict removeObjectForKey:self.conferenceUserId]; - membersWithoutConferenceUser = membersWithoutConferenceUserDict.allValues; - } - - return membersWithoutConferenceUser; -} - -- (NSArray *)membersWithMembership:(MXMembership)theMembership includeConferenceUser:(BOOL)includeConferenceUser -{ - NSArray *membersWithMembership; - - if (includeConferenceUser || self.isConferenceUserRoom) - { - // Show everyone in a 1:1 room with a conference user - membersWithMembership = [self membersWithMembership:theMembership]; - } - else - { - MXRoomMember *conferenceUserMember = [self memberWithUserId:self.conferenceUserId]; - if (!conferenceUserMember || conferenceUserMember.membership != theMembership) - { - // The conference user is not in list of members with the passed membership - membersWithMembership = [self membersWithMembership:theMembership]; - } - else - { - NSMutableDictionary *membersWithMembershipDict = [NSMutableDictionary dictionaryWithCapacity:members.count]; - for (MXRoomMember *roomMember in members.allValues) - { - if (roomMember.membership == theMembership) - { - membersWithMembershipDict[roomMember.userId] = roomMember; - } - } - - [membersWithMembershipDict removeObjectForKey:self.conferenceUserId]; - membersWithMembership = membersWithMembershipDict.allValues; - } - } - - return membersWithMembership; -} - - #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { @@ -869,10 +572,9 @@ - (id)copyWithZone:(NSZone *)zone stateCopy->stateEvents[key] = [[NSMutableArray allocWithZone:zone] initWithArray:stateEvents[key]]; } - // Same thing here. MXRoomMembers are also immutable. A new instance of it is created each time - // the sdk receives room member event, even if it is an update of an existing member like a - // membership change (ex: "invited" -> "joined") - stateCopy->members = [[NSMutableDictionary allocWithZone:zone] initWithDictionary:members]; + stateCopy->_members = [_members copyWithZone:zone]; + + stateCopy->_membersCount = _membersCount; stateCopy->roomAliases = [[NSMutableDictionary allocWithZone:zone] initWithDictionary:roomAliases]; @@ -880,10 +582,8 @@ - (id)copyWithZone:(NSZone *)zone stateCopy->membersWithThirdPartyInviteTokenCache= [[NSMutableDictionary allocWithZone:zone] initWithDictionary:membersWithThirdPartyInviteTokenCache]; - stateCopy->membership = membership; + stateCopy->_membership = _membership; - stateCopy->membersNamesInUse = [[NSMutableDictionary allocWithZone:zone] initWithDictionary:membersNamesInUse]; - stateCopy->powerLevels = [powerLevels copy]; stateCopy->maxPowerLevel = maxPowerLevel; diff --git a/MatrixSDK/Data/MXRoomSummary.h b/MatrixSDK/Data/MXRoomSummary.h index 2c609b256f..5abe21adbc 100644 --- a/MatrixSDK/Data/MXRoomSummary.h +++ b/MatrixSDK/Data/MXRoomSummary.h @@ -1,6 +1,7 @@ /* Copyright 2017 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,6 +20,8 @@ #import "MXJSONModels.h" #import "MXHTTPOperation.h" +#import "MXRoomMembersCount.h" +#import "MXEnumConstants.h" @class MXSession, MXRoom, MXRoomState, MXEvent; @@ -136,6 +139,33 @@ FOUNDATION_EXPORT NSString *const kMXRoomSummaryDidChangeNotification; */ @property (nonatomic) NSString *topic; +/** + The aliases of this room. + */ +@property (nonatomic) NSArray *aliases; + +/** + The membership state of the logged in user for this room. + */ +@property (nonatomic) MXMembership membership NS_REFINED_FOR_SWIFT; + +/** + Room members counts. + */ +@property (nonatomic) MXRoomMembersCount *membersCount; + +/** + Flag indicating if the room is a 1:1 room with a call conference user. + In this case, the room is used as a call signaling room and does not need to be + displayed to the end user. + */ +@property (nonatomic) BOOL isConferenceUserRoom; + +/** + Indicate whether this room should be hidden from the user. + */ +@property (nonatomic) BOOL hiddenFromUser; + /** Reset data related to room state. @@ -251,9 +281,18 @@ FOUNDATION_EXPORT NSString *const kMXRoomSummaryDidChangeNotification; #pragma mark - Server sync +/** + Process state events in order to update the room state. + + @param stateEvents an array of state events. + */ +- (void)handleStateEvents:(NSArray *)stateEvents; + /** Update room summary data according to the provided sync response. + Note: state events have been previously sent to `handleStateEvents`. + @param roomSync information to sync the room with the home server data. */ - (void)handleJoinedRoomSync:(MXRoomSync*)roomSync; @@ -261,6 +300,8 @@ FOUNDATION_EXPORT NSString *const kMXRoomSummaryDidChangeNotification; /** Update the invited room state according to the provided data. + Note: state events have been previously sent to `handleStateEvents`. + @param invitedRoomSync information to update the room state. */ - (void)handleInvitedRoomSync:(MXInvitedRoomSync*)invitedRoomSync; @@ -297,13 +338,25 @@ FOUNDATION_EXPORT NSString *const kMXRoomSummaryDidChangeNotification; - (BOOL)session:(MXSession*)session updateRoomSummary:(MXRoomSummary*)summary withLastEvent:(MXEvent*)event eventState:(MXRoomState*)eventState roomState:(MXRoomState*)roomState; /** - Called to update the room summary on a received state event. + Called to update the room summary on received state events. @param session the session the room belongs to. @param summary the room summary. @param stateEvents state events that may change the room summary. + @param roomState the current state of the room. + @return YES if the room summary has changed. + */ +- (BOOL)session:(MXSession*)session updateRoomSummary:(MXRoomSummary*)summary withStateEvents:(NSArray*)stateEvents roomState:(MXRoomState*)roomState; + +/** + Called to update the room summary on received summary update. + + @param session the session the room belongs to. + @param summary the room summary. + @param serverRoomSummary the homeserver side room summary. + @param roomState the current state of the room. @return YES if the room summary has changed. */ -- (BOOL)session:(MXSession*)session updateRoomSummary:(MXRoomSummary*)summary withStateEvents:(NSArray*)stateEvents; +- (BOOL)session:(MXSession*)session updateRoomSummary:(MXRoomSummary*)summary withServerRoomSummary:(MXRoomSyncSummary*)serverRoomSummary roomState:(MXRoomState*)roomState; @end diff --git a/MatrixSDK/Data/MXRoomSummary.m b/MatrixSDK/Data/MXRoomSummary.m index 00d4bcc953..af41c9f6da 100644 --- a/MatrixSDK/Data/MXRoomSummary.m +++ b/MatrixSDK/Data/MXRoomSummary.m @@ -1,6 +1,7 @@ /* Copyright 2017 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,6 +21,7 @@ #import "MXRoom.h" #import "MXRoomState.h" #import "MXSession.h" +#import "MXTools.h" #import #import @@ -30,16 +32,29 @@ @interface MXRoomSummary () { // Cache for the last event to avoid to read it from the store everytime MXEvent *lastMessageEvent; + + // Flag to avoid to notify several updates + BOOL updatedWithStateEvents; } @end @implementation MXRoomSummary -- (instancetype)initWithRoomId:(NSString *)theRoomId andMatrixSession:(MXSession *)matrixSession +- (instancetype)init { self = [super init]; if (self) + { + updatedWithStateEvents = NO; + } + return self; +} + +- (instancetype)initWithRoomId:(NSString *)theRoomId andMatrixSession:(MXSession *)matrixSession +{ + self = [self init]; + if (self) { _roomId = theRoomId; _lastMessageOthers = [NSMutableDictionary dictionary]; @@ -109,11 +124,17 @@ - (void)resetRoomStateData _avatar = nil; _displayname = nil; _topic = nil; + _aliases = nil; - if ([_mxSession.roomSummaryUpdateDelegate session:_mxSession updateRoomSummary:self withStateEvents:room.state.stateEvents]) - { - [self save:YES]; - } + MXWeakify(self); + [room state:^(MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); + + if ([self.mxSession.roomSummaryUpdateDelegate session:self.mxSession updateRoomSummary:self withStateEvents:roomState.stateEvents roomState:roomState]) + { + [self save:YES]; + } + }]; } @@ -121,24 +142,22 @@ - (void)resetRoomStateData - (MXEvent *)lastMessageEvent { - if (lastMessageEvent) + if (!lastMessageEvent) { - return lastMessageEvent; - } - - // The storage of the event depends if it is a true matrix event or a local echo - if (![_lastMessageEventId hasPrefix:kMXEventLocalEventIdPrefix]) - { - lastMessageEvent = [_mxSession.store eventWithEventId:_lastMessageEventId inRoom:_roomId]; - } - else - { - for (MXEvent *event in [_mxSession.store outgoingMessagesInRoom:_roomId]) + // The storage of the event depends if it is a true matrix event or a local echo + if (![_lastMessageEventId hasPrefix:kMXEventLocalEventIdPrefix]) + { + lastMessageEvent = [_mxSession.store eventWithEventId:_lastMessageEventId inRoom:_roomId]; + } + else { - if ([event.eventId isEqualToString:_lastMessageEventId]) + for (MXEvent *event in [_mxSession.store outgoingMessagesInRoom:_roomId]) { - lastMessageEvent = event; - break; + if ([event.eventId isEqualToString:_lastMessageEventId]) + { + lastMessageEvent = event; + break; + } } } } @@ -201,114 +220,127 @@ - (MXHTTPOperation *)fetchLastMessage:(void (^)(void))complete failure:(void (^) return nil; } - MXHTTPOperation *newOperation; + if (!operation) + { + // Create an empty operation that will be mutated later + operation = [[MXHTTPOperation alloc] init]; + } - // Start by checking events we have in the store - MXRoomState *state = self.room.state; - id messagesEnumerator = room.enumeratorForStoredMessages; - NSUInteger messagesInStore = messagesEnumerator.remaining; - MXEvent *event = messagesEnumerator.nextEvent; + MXWeakify(self); + [self.room state:^(MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); - // 1.1 Find where we stopped at the previous call in the fetchLastMessage calls loop - BOOL firstIteration = YES; - if (lastEventIdChecked) - { - firstIteration = NO; - while (event) + // Start by checking events we have in the store + MXRoomState *state = roomState; + id messagesEnumerator = room.enumeratorForStoredMessages; + NSUInteger messagesInStore = messagesEnumerator.remaining; + MXEvent *event = messagesEnumerator.nextEvent; + NSString *lastEventIdCheckedInBlock = lastEventIdChecked; + + // 1.1 Find where we stopped at the previous call in the fetchLastMessage calls loop + BOOL firstIteration = YES; + if (lastEventIdCheckedInBlock) { - NSString *eventId = event.eventId; + firstIteration = NO; + while (event) + { + NSString *eventId = event.eventId; - event = messagesEnumerator.nextEvent; + event = messagesEnumerator.nextEvent; - if ([eventId isEqualToString:lastEventIdChecked]) - { - break; + if ([eventId isEqualToString:lastEventIdCheckedInBlock]) + { + break; + } } } - } - // 1.2 Check events one by one until finding the right last message for the room - BOOL lastMessageUpdated = NO; - while (event) - { - // Decrypt the event if necessary - if (event.eventType == MXEventTypeRoomEncrypted) + // 1.2 Check events one by one until finding the right last message for the room + BOOL lastMessageUpdated = NO; + while (event) { - if (![_mxSession decryptEvent:event inTimeline:nil]) + // Decrypt the event if necessary + if (event.eventType == MXEventTypeRoomEncrypted) { - NSLog(@"[MXRoomSummary] fetchLastMessage: Warning: Unable to decrypt event: %@\nError: %@", event.content[@"body"], event.decryptionError); + if (![self.mxSession decryptEvent:event inTimeline:nil]) + { + NSLog(@"[MXRoomSummary] fetchLastMessage: Warning: Unable to decrypt event: %@\nError: %@", event.content[@"body"], event.decryptionError); + } } - } - if (event.isState) - { - // Need to go backward in the state to provide it as it was when the event occured - if (state.isLive) + if (event.isState) { - state = [state copy]; - state.isLive = NO; + // Need to go backward in the state to provide it as it was when the event occured + if (state.isLive) + { + state = [state copy]; + state.isLive = NO; + } + + [state handleStateEvents:@[event]]; } - [state handleStateEvent:event]; - } + lastEventIdCheckedInBlock = event.eventId; - lastEventIdChecked = event.eventId; + // Propose the event as last message + lastMessageUpdated = [self.mxSession.roomSummaryUpdateDelegate session:self.mxSession updateRoomSummary:self withLastEvent:event eventState:state roomState:roomState]; + if (lastMessageUpdated) + { + // The event is accepted. We have our last message + // The roomSummaryUpdateDelegate has stored the _lastMessageEventId + break; + } - // Propose the event as last message - lastMessageUpdated = [_mxSession.roomSummaryUpdateDelegate session:_mxSession updateRoomSummary:self withLastEvent:event eventState:state roomState:self.room.state]; - if (lastMessageUpdated) - { - // The event is accepted. We have our last message - // The roomSummaryUpdateDelegate has stored the _lastMessageEventId - break; + event = messagesEnumerator.nextEvent; } - event = messagesEnumerator.nextEvent; - } + // 2.1 If lastMessageEventId is still nil, fetch events from the homeserver + MXWeakify(self); + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + MXStrongifyAndReturnIfNil(self); - // 2.1 If lastMessageEventId is still nil, fetch events from the homeserver - if (!_lastMessageEventId && [room.liveTimeline canPaginate:MXTimelineDirectionBackwards]) - { - NSUInteger messagesToPaginate = 30; + if (!self->_lastMessageEventId && [liveTimeline canPaginate:MXTimelineDirectionBackwards]) + { + NSUInteger messagesToPaginate = 30; - // Reset pagination the first time - if (firstIteration) - { - [room.liveTimeline resetPagination]; + // Reset pagination the first time + if (firstIteration) + { + [liveTimeline resetPagination]; - // Make sure we paginate more than the events we have already in the store - messagesToPaginate += messagesInStore; - } + // Make sure we paginate more than the events we have already in the store + messagesToPaginate += messagesInStore; + } - // Paginate events from the homeserver - // XXX: Pagination on the timeline may conflict with request from the app - newOperation = [room.liveTimeline paginate:messagesToPaginate direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + // Paginate events from the homeserver + // XXX: Pagination on the timeline may conflict with request from the app + __block MXHTTPOperation *newOperation; + newOperation = [liveTimeline paginate:messagesToPaginate direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - // Received messages have been stored in the store. We can make a new loop - [self fetchLastMessage:complete failure:failure - lastEventIdChecked:lastEventIdChecked - operation:(operation ? operation : newOperation) - commit:commit]; + // Received messages have been stored in the store. We can make a new loop + [self fetchLastMessage:complete failure:failure + lastEventIdChecked:lastEventIdCheckedInBlock + operation:(operation ? operation : newOperation) + commit:commit]; - } failure:failure]; + } failure:failure]; - // Update the current HTTP operation - if (operation) - { - [operation mutateTo:newOperation]; - } - } - else - { - if (complete) - { - complete(); - } + // Update the current HTTP operation + [operation mutateTo:newOperation]; + } + else + { + if (complete) + { + complete(); + } - [self save:commit]; - } + [self save:commit]; + } + }]; + }]; - return operation ? operation : newOperation; + return operation; } - (void)eventDidChangeSentState:(NSNotification *)notif @@ -327,7 +359,7 @@ - (void)eventDidChangeSentState:(NSNotification *)notif - (void)roomDidFlushData:(NSNotification *)notif { MXRoom *room = notif.object; - if (_mxSession == room.mxSession && [_roomId isEqualToString:room.state.roomId]) + if (_mxSession == room.mxSession && [_roomId isEqualToString:room.roomId]) { NSLog(@"[MXRoomSummary] roomDidFlushData: %@", _roomId); @@ -359,95 +391,109 @@ - (void)markAllAsRead } #pragma mark - Server sync +- (void)handleStateEvents:(NSArray *)stateEvents +{ + if (stateEvents.count) + { + MXWeakify(self); + [self.room state:^(MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); + + self->updatedWithStateEvents |= [self.mxSession.roomSummaryUpdateDelegate session:self.mxSession updateRoomSummary:self withStateEvents:stateEvents roomState:roomState]; + }]; + } +} + - (void)handleJoinedRoomSync:(MXRoomSync*)roomSync { - // Handle first changes due to state events - BOOL updated = NO; + MXWeakify(self); + [self.room state:^(MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); - NSMutableArray *stateEvents = [NSMutableArray arrayWithArray:roomSync.state.events]; + // Changes due to state events have been processed previously + BOOL updated = self->updatedWithStateEvents; + self->updatedWithStateEvents = NO; - // There may be state events in the timeline too - for (MXEvent *event in roomSync.timeline.events) - { - if (event.isState) + // Handle room summary sent by the home server + if (roomSync.summary) { - [stateEvents addObject:event]; + updated |= [self.mxSession.roomSummaryUpdateDelegate session:self.mxSession updateRoomSummary:self withServerRoomSummary:roomSync.summary roomState:roomState]; } - } - if (stateEvents.count) - { - updated = [_mxSession.roomSummaryUpdateDelegate session:_mxSession updateRoomSummary:self withStateEvents:stateEvents]; - } - - // Handle the last message starting by the most recent event. - // Then, if the delegate refuses it as last message, pass the previous event. - BOOL lastMessageUpdated = NO; - MXRoomState *state = self.room.state; - for (MXEvent *event in roomSync.timeline.events.reverseObjectEnumerator) - { - if (event.isState) + // Handle the last message starting by the most recent event. + // Then, if the delegate refuses it as last message, pass the previous event. + BOOL lastMessageUpdated = NO; + MXRoomState *state = roomState; + for (MXEvent *event in roomSync.timeline.events.reverseObjectEnumerator) { - // Need to go backward in the state to provide it as it was when the event occured - if (state.isLive) + if (event.isState) { - state = [state copy]; - state.isLive = NO; + // Need to go backward in the state to provide it as it was when the event occured + if (state.isLive) + { + state = [state copy]; + state.isLive = NO; + } + + [state handleStateEvents:@[event]]; } - [state handleStateEvent:event]; - } - - lastMessageUpdated = [_mxSession.roomSummaryUpdateDelegate session:_mxSession updateRoomSummary:self withLastEvent:event eventState:state roomState:self.room.state]; - if (lastMessageUpdated) - { - break; + lastMessageUpdated = [self.mxSession.roomSummaryUpdateDelegate session:self.mxSession updateRoomSummary:self withLastEvent:event eventState:state roomState:roomState]; + if (lastMessageUpdated) + { + break; + } } - } - // Store notification counts from unreadNotifications field in /sync response - if (roomSync.unreadNotifications) - { - // Caution: the server may provide a not null count whereas we know locally the user has read all room messages - // (see for example this issue https://github.com/matrix-org/synapse/issues/2193). - // Patch: Ignore the server information when the user has read all messages. - if (roomSync.unreadNotifications.notificationCount && self.localUnreadEventCount == 0) + // Store notification counts from unreadNotifications field in /sync response + if (roomSync.unreadNotifications) { - if (_notificationCount != 0) + // Caution: the server may provide a not null count whereas we know locally the user has read all room messages + // (see for example this issue https://github.com/matrix-org/synapse/issues/2193). + // Patch: Ignore the server information when the user has read all messages. + if (roomSync.unreadNotifications.notificationCount && self.localUnreadEventCount == 0) + { + if (self.notificationCount != 0) + { + self->_notificationCount = 0; + self->_highlightCount = 0; + updated = YES; + } + } + else if (self.notificationCount != roomSync.unreadNotifications.notificationCount + || self.highlightCount != roomSync.unreadNotifications.highlightCount) { - _notificationCount = 0; - _highlightCount = 0; + self->_notificationCount = roomSync.unreadNotifications.notificationCount; + self->_highlightCount = roomSync.unreadNotifications.highlightCount; updated = YES; } } - else if (_notificationCount != roomSync.unreadNotifications.notificationCount - || _highlightCount != roomSync.unreadNotifications.highlightCount) + + if (updated || lastMessageUpdated) { - _notificationCount = roomSync.unreadNotifications.notificationCount; - _highlightCount = roomSync.unreadNotifications.highlightCount; - updated = YES; + [self save:NO]; } - } - if (updated || lastMessageUpdated) - { - [self save:NO]; - } + }]; } - (void)handleInvitedRoomSync:(MXInvitedRoomSync*)invitedRoomSync { - BOOL updated = [_mxSession.roomSummaryUpdateDelegate session:_mxSession updateRoomSummary:self withStateEvents:invitedRoomSync.inviteState.events]; + MXWeakify(self); + [self.room state:^(MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); - MXRoom *room = self.room; + BOOL updated = self->updatedWithStateEvents; + self->updatedWithStateEvents = NO; - // Fake the last message with the invitation event contained in invitedRoomSync.inviteState - updated |= [_mxSession.roomSummaryUpdateDelegate session:_mxSession updateRoomSummary:self withLastEvent:invitedRoomSync.inviteState.events.lastObject eventState:nil roomState:room.state]; + // Fake the last message with the invitation event contained in invitedRoomSync.inviteState + updated |= [self.mxSession.roomSummaryUpdateDelegate session:self.mxSession updateRoomSummary:self withLastEvent:invitedRoomSync.inviteState.events.lastObject eventState:nil roomState:roomState]; - if (updated) - { - [self save:NO]; - } + if (updated) + { + [self save:NO]; + } + }]; } @@ -458,12 +504,18 @@ - (void)handleEvent:(MXEvent*)event if (room) { - BOOL updated = [_mxSession.roomSummaryUpdateDelegate session:_mxSession updateRoomSummary:self withLastEvent:event eventState:nil roomState:room.state]; + MXWeakify(self); + [self.room state:^(MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); + + BOOL updated = [self.mxSession.roomSummaryUpdateDelegate session:self.mxSession updateRoomSummary:self withLastEvent:event eventState:nil roomState:roomState]; + + if (updated) + { + [self save:YES]; + } + }]; - if (updated) - { - [self save:YES]; - } } } @@ -479,6 +531,10 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder _avatar = [aDecoder decodeObjectForKey:@"avatar"]; _displayname = [aDecoder decodeObjectForKey:@"displayname"]; _topic = [aDecoder decodeObjectForKey:@"topic"]; + _aliases = [aDecoder decodeObjectForKey:@"aliases"]; + _membership = (MXMembership)[aDecoder decodeIntegerForKey:@"membership"]; + _membersCount = [aDecoder decodeObjectForKey:@"membersCount"]; + _isConferenceUserRoom = [(NSNumber*)[aDecoder decodeObjectForKey:@"isConferenceUserRoom"] boolValue]; _others = [aDecoder decodeObjectForKey:@"others"]; _isEncrypted = [aDecoder decodeBoolForKey:@"isEncrypted"]; @@ -504,6 +560,8 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder _lastMessageString = lastMessageData[@"lastMessageString"]; _lastMessageAttributedString = lastMessageData[@"lastMessageAttributedString"]; _lastMessageOthers = lastMessageData[@"lastMessageOthers"]; + + _hiddenFromUser = [aDecoder decodeBoolForKey:@"hiddenFromUser"]; } return self; } @@ -515,6 +573,10 @@ - (void)encodeWithCoder:(NSCoder *)aCoder [aCoder encodeObject:_avatar forKey:@"avatar"]; [aCoder encodeObject:_displayname forKey:@"displayname"]; [aCoder encodeObject:_topic forKey:@"topic"]; + [aCoder encodeObject:_aliases forKey:@"aliases"]; + [aCoder encodeInteger:(NSInteger)_membership forKey:@"membership"]; + [aCoder encodeObject:_membersCount forKey:@"membersCount"]; + [aCoder encodeObject:@(_isConferenceUserRoom) forKey:@"isConferenceUserRoom"]; [aCoder encodeObject:_others forKey:@"others"]; [aCoder encodeBool:_isEncrypted forKey:@"isEncrypted"]; @@ -557,6 +619,8 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:lastMessageData forKey:@"lastMessageData"]; } + + [aCoder encodeBool:_hiddenFromUser forKey:@"hiddenFromUser"]; } diff --git a/MatrixSDK/Data/MXRoomSummaryUpdater.m b/MatrixSDK/Data/MXRoomSummaryUpdater.m index 118175c52a..f97b60349c 100644 --- a/MatrixSDK/Data/MXRoomSummaryUpdater.m +++ b/MatrixSDK/Data/MXRoomSummaryUpdater.m @@ -1,6 +1,7 @@ /* Copyright 2017 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,7 +18,9 @@ #import "MXRoomSummaryUpdater.h" +#import "MXSession.h" #import "MXRoom.h" +#import "MXSession.h" @implementation MXRoomSummaryUpdater @@ -79,16 +82,9 @@ - (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary return updated; } -- (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary withStateEvents:(NSArray *)stateEvents +- (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary withStateEvents:(NSArray *)stateEvents roomState:(MXRoomState*)roomState { - MXRoom *room = summary.room; - if (!room.state) - { - // Should not happen - NSLog(@"[MXRoomSummaryUpdater] updateRoomSummary withStateEvents: room.state not ready"); - return NO; - } - + BOOL hasRoomMembersChange = NO; BOOL updated = NO; for (MXEvent *event in stateEvents) @@ -96,30 +92,228 @@ - (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary switch (event.eventType) { case MXEventTypeRoomName: - summary.displayname = room.state.displayname; + summary.displayname = roomState.name; updated = YES; break; case MXEventTypeRoomAvatar: - summary.avatar = room.state.avatar; + summary.avatar = roomState.avatar; updated = YES; break; case MXEventTypeRoomTopic: - summary.topic = room.state.topic; + summary.topic = roomState.topic; updated = YES; break; - case MXEventTypeRoomEncryption: - summary.isEncrypted = room.state.isEncrypted; + case MXEventTypeRoomAliases: + summary.aliases = roomState.aliases; updated = YES; break; + case MXEventTypeRoomCanonicalAlias: + // If m.room.canonical_alias is set, use it if there is no m.room.name + if (!roomState.name && roomState.canonicalAlias) + { + summary.displayname = roomState.canonicalAlias; + updated = YES; + } + break; + + case MXEventTypeRoomMember: + hasRoomMembersChange = YES; + break; + + case MXEventTypeRoomEncryption: + summary.isEncrypted = roomState.isEncrypted; + updated = YES; + break; + + case MXEventTypeRoomTombStone: + { + if ([self checkForTombStoneStateEventAndUpdateRoomSummaryIfNeeded:summary session:session roomState:roomState]) + { + updated = YES; + } + break; + } + + case MXEventTypeRoomCreate: + [self checkRoomCreateStateEventPredecessorAndUpdateObsoleteRoomSummaryIfNeededWithCreateEvent:event summary:summary session:session roomState:roomState]; + break; + default: break; } } + if (hasRoomMembersChange) + { + // Check if there was a change on room state cached data + + // In case of lazy-loaded room members, roomState.membersCount is a partial count. + // The actual count will come with [updateRoomSummary:withServerRoomSummary:...]. + if (!session.syncWithLazyLoadOfRoomMembers && ![summary.membersCount isEqual:roomState.membersCount]) + { + summary.membersCount = [roomState.membersCount copy]; + updated = YES; + } + + if (summary.membership != roomState.membership && roomState.membership != MXMembershipUnknown) + { + summary.membership = roomState.membership; + updated = YES; + } + + if (summary.isConferenceUserRoom != roomState.isConferenceUserRoom) + { + summary.isConferenceUserRoom = roomState.isConferenceUserRoom; + updated = YES; + } + } + + return updated; +} + +#pragma mark - Private + +// Hide tombstoned room from user only if the user joined the replacement room +// Important: Room replacement summary could not be present in memory when making this process even if the user joined it, +// in this case it should be processed when checking the room replacement in `checkRoomCreateStateEventPredecessorAndUpdateObsoleteRoomSummaryIfNeeded:session:room:`. +- (BOOL)checkForTombStoneStateEventAndUpdateRoomSummaryIfNeeded:(MXRoomSummary*)summary session:(MXSession*)session roomState:(MXRoomState*)roomState +{ + BOOL updated = NO; + + MXRoomTombStoneContent *roomTombStoneContent = roomState.tombStoneContent; + + if (roomTombStoneContent) + { + MXRoomSummary *replacementRoomSummary = [session roomSummaryWithRoomId:roomTombStoneContent.replacementRoomId]; + + if (replacementRoomSummary) + { + summary.hiddenFromUser = replacementRoomSummary.membership == MXMembershipJoin; + } + } + + return updated; +} + +// Hide tombstoned room predecessor from user only if the user joined the current room +// Important: Room predecessor summary could not be present in memory when making this process, +// in this case it should be processed when checking the room predecessor in `checkForTombStoneStateEventAndUpdateRoomSummaryIfNeeded:session:room:`. +- (void)checkRoomCreateStateEventPredecessorAndUpdateObsoleteRoomSummaryIfNeededWithCreateEvent:(MXEvent*)createEvent summary:(MXRoomSummary*)summary session:(MXSession*)session roomState:(MXRoomState*)roomState +{ + MXRoomCreateContent *createContent = [MXRoomCreateContent modelFromJSON:createEvent.content]; + + if (createContent.roomPredecessorInfo) + { + MXRoomSummary *obsoleteRoomSummary = [session roomSummaryWithRoomId:createContent.roomPredecessorInfo.roomId]; + obsoleteRoomSummary.hiddenFromUser = summary.membership == MXMembershipJoin; // Hide room predecessor if user joined the new one + } +} + +- (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary withServerRoomSummary:(MXRoomSyncSummary *)serverRoomSummary roomState:(MXRoomState *)roomState +{ + BOOL updated = NO; + + // Update room members count + if (-1 != serverRoomSummary.joinedMemberCount || -1 != serverRoomSummary.invitedMemberCount) + { + updated |= [self updateSummaryMemberCount:summary session:session withServerRoomSummary:serverRoomSummary roomState:roomState]; + } + + // Compute display name from summary heroes if there was no name nor canonical alias + if (!roomState.name && !roomState.canonicalAlias) + { + updated |= [self updateSummaryDisplayname:summary session:session withServerRoomSummary:serverRoomSummary roomState:roomState]; + } + + return updated; +} + +- (BOOL)updateSummaryDisplayname:(MXRoomSummary *)summary session:(MXSession *)session withServerRoomSummary:(MXRoomSyncSummary *)serverRoomSummary roomState:(MXRoomState *)roomState +{ + BOOL updated = NO; + + // Compute a non internationalised display name based on + // https://docs.google.com/document/d/11i14UI1cUz-OJ0knD5BFu7fmT6Fo327zvMYqfSAR7xs/edit# + if (serverRoomSummary.heroes.count == 0 || roomState.membersCount.members <= 1) + { + summary.displayname = @"Empty Room"; + updated = YES; + } + else if (1 <= serverRoomSummary.heroes.count) + { + NSMutableArray *memberNames = [NSMutableArray arrayWithCapacity:serverRoomSummary.heroes.count]; + for (NSString *hero in serverRoomSummary.heroes) + { + NSString *memberName = [roomState.members memberName:hero]; + if (!memberName) + { + memberName = hero; + } + + [memberNames addObject:memberName]; + } + + if (memberNames.count == 1) + { + summary.displayname = memberNames.firstObject; + } + else + { + if (serverRoomSummary.heroes.count == summary.membersCount.members - 1) + { + NSString *lastMemberName = memberNames.lastObject; + [memberNames removeLastObject]; + + summary.displayname = [NSString stringWithFormat:@"%@ & %@", + [memberNames componentsJoinedByString:@", "], + lastMemberName]; + } + else + { + NSUInteger otherCount = summary.membersCount.members - 1 - serverRoomSummary.heroes.count; + summary.displayname = [NSString stringWithFormat:@"%@ & %@ %@", + [memberNames componentsJoinedByString:@", "], + @(otherCount), + (1 < otherCount) ? @"others" : @"other"]; + } + } + + updated = YES; + } + + return updated; +} + +- (BOOL)updateSummaryMemberCount:(MXRoomSummary *)summary session:(MXSession *)session withServerRoomSummary:(MXRoomSyncSummary *)serverRoomSummary roomState:(MXRoomState *)roomState +{ + BOOL updated = NO; + + MXRoomMembersCount *memberCount = [summary.membersCount copy]; + if (!memberCount) + { + memberCount = [MXRoomMembersCount new]; + } + + if (-1 != serverRoomSummary.joinedMemberCount) + { + memberCount.joined = serverRoomSummary.joinedMemberCount; + } + if (-1 != serverRoomSummary.invitedMemberCount) + { + memberCount.invited = serverRoomSummary.invitedMemberCount; + } + memberCount.members = memberCount.joined + memberCount.invited; + + if (![summary.membersCount isEqual:memberCount]) + { + summary.membersCount = memberCount; + updated = YES; + } + return updated; } diff --git a/MatrixSDK/Data/MXSendReplyEventDefaultStringLocalizations.h b/MatrixSDK/Data/MXSendReplyEventDefaultStringLocalizations.h new file mode 100644 index 0000000000..ef42655849 --- /dev/null +++ b/MatrixSDK/Data/MXSendReplyEventDefaultStringLocalizations.h @@ -0,0 +1,31 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +@import Foundation; +#import "MXSendReplyEventStringsLocalizable.h" + +/** + A `MXSendReplyEventDefaultStringLocalizations` instance represents default localization strings used when send reply event to a message in a room. + */ +@interface MXSendReplyEventDefaultStringLocalizations : NSObject + +@property (copy, readonly, nonnull) NSString *senderSentAnImage; +@property (copy, readonly, nonnull) NSString *senderSentAVideo; +@property (copy, readonly, nonnull) NSString *senderSentAnAudioFile; +@property (copy, readonly, nonnull) NSString *senderSentAFile; +@property (copy, readonly, nonnull) NSString *messageToReplyToPrefix; + +@end diff --git a/MatrixSDK/Data/MXSendReplyEventDefaultStringLocalizations.m b/MatrixSDK/Data/MXSendReplyEventDefaultStringLocalizations.m new file mode 100644 index 0000000000..ffc7475e4e --- /dev/null +++ b/MatrixSDK/Data/MXSendReplyEventDefaultStringLocalizations.m @@ -0,0 +1,34 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXSendReplyEventDefaultStringLocalizations.h" + +@implementation MXSendReplyEventDefaultStringLocalizations + +- (instancetype)init +{ + self = [super init]; + if (self) { + _senderSentAnImage = @"sent an image."; + _senderSentAVideo = @"sent a video."; + _senderSentAnAudioFile = @"sent an audio file."; + _senderSentAFile = @"sent a file."; + _messageToReplyToPrefix = @"In reply to"; + } + return self; +} + +@end diff --git a/MatrixSDK/Data/MXSendReplyEventStringsLocalizable.h b/MatrixSDK/Data/MXSendReplyEventStringsLocalizable.h new file mode 100644 index 0000000000..6d5efc7b2d --- /dev/null +++ b/MatrixSDK/Data/MXSendReplyEventStringsLocalizable.h @@ -0,0 +1,33 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +@import Foundation; + +/** + The `MXSendReplyEventStringsLocalizable` protocol defines an interface that must be implemented + to provide string localizations when send reply event to a message in a room. + */ +@protocol MXSendReplyEventStringsLocalizable + +@required + +@property (copy, readonly, nonnull) NSString *senderSentAnImage; +@property (copy, readonly, nonnull) NSString *senderSentAVideo; +@property (copy, readonly, nonnull) NSString *senderSentAnAudioFile; +@property (copy, readonly, nonnull) NSString *senderSentAFile; +@property (copy, readonly, nonnull) NSString *messageToReplyToPrefix; + +@end diff --git a/MatrixSDK/Data/MXSessionEventListener.m b/MatrixSDK/Data/MXSessionEventListener.m index 40a4bcd689..fafb5a86cf 100644 --- a/MatrixSDK/Data/MXSessionEventListener.m +++ b/MatrixSDK/Data/MXSessionEventListener.m @@ -1,6 +1,7 @@ /* Copyright 2014 OpenMarket Ltd - + Copyright 2018 New Vector Ltd + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -18,6 +19,7 @@ #import "MXSession.h" #import "MXRoom.h" +#import "MXTools.h" @interface MXSessionEventListener() { @@ -42,22 +44,21 @@ - (instancetype)initWithSender:(id)sender andEventTypes:(NSArrayroomEventListeners[room.roomId] = + [room listenToEventsOfTypes:self.eventTypes onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { self.listenerBlock(event, direction, roomState); }]; } - } - (void)removeSpiedRoom:(MXRoom*)room { - if ([roomEventListeners objectForKey:room.state.roomId]) + if ([roomEventListeners objectForKey:room.roomId]) { - [room.liveTimeline removeListener:roomEventListeners[room.state.roomId]]; - [roomEventListeners removeObjectForKey:room.state.roomId]; + [room removeListener:self->roomEventListeners[room.roomId]]; + [roomEventListeners removeObjectForKey:room.roomId]; } } @@ -69,7 +70,7 @@ - (void)removeAllSpiedRooms for (NSString *roomId in roomEventListeners) { MXRoom *room = [mxSession roomWithRoomId:roomId]; - [room.liveTimeline removeListener:roomEventListeners[room.state.roomId]]; + [room removeListener:self->roomEventListeners[room.roomId]]; } [roomEventListeners removeAllObjects]; } diff --git a/MatrixSDK/Data/Store/MXFileStore/MXFileRoomStore.m b/MatrixSDK/Data/Store/MXFileStore/MXFileRoomStore.m index d0961cbf4f..c4eff0457b 100644 --- a/MatrixSDK/Data/Store/MXFileStore/MXFileRoomStore.m +++ b/MatrixSDK/Data/Store/MXFileStore/MXFileRoomStore.m @@ -30,6 +30,7 @@ - (id)initWithCoder:(NSCoder *)aDecoder self.paginationToken = [aDecoder decodeObjectForKey:@"paginationToken"]; self.hasReachedHomeServerPaginationEnd = [aDecoder decodeBoolForKey:@"hasReachedHomeServerPaginationEnd"]; + self.hasLoadedAllRoomMembersForRoom = [aDecoder decodeBoolForKey:@"hasLoadedAllRoomMembersForRoom"]; self.partialTextMessage = [aDecoder decodeObjectForKey:@"partialTextMessage"]; @@ -64,6 +65,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder } [aCoder encodeBool:self.hasReachedHomeServerPaginationEnd forKey:@"hasReachedHomeServerPaginationEnd"]; + [aCoder encodeBool:self.hasLoadedAllRoomMembersForRoom forKey:@"hasLoadedAllRoomMembersForRoom"]; if (self.partialTextMessage) { diff --git a/MatrixSDK/Data/Store/MXFileStore/MXFileStore.h b/MatrixSDK/Data/Store/MXFileStore/MXFileStore.h index 556dc4867a..c2ee244ee3 100644 --- a/MatrixSDK/Data/Store/MXFileStore/MXFileStore.h +++ b/MatrixSDK/Data/Store/MXFileStore/MXFileStore.h @@ -1,6 +1,7 @@ /* Copyright 2014 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,6 +20,21 @@ NS_ASSUME_NONNULL_BEGIN +/** + Options for preloading data during the `[MXStore openWithCredentials:]` operation. + */ +typedef NS_OPTIONS(NSInteger, MXFileStorePreloadOptions) +{ + // Preload rooms summaries + MXFileStorePreloadOptionRoomSummary = 0x1, + + // Preload rooms states + MXFileStorePreloadOptionRoomState = 0x2, + + // Preload rooms account data + MXFileStorePreloadOptionRoomAccountData = 0x4 +}; + /** `MXFileStore` extends MXMemoryStore by adding permanent storage. @@ -51,6 +67,7 @@ NS_ASSUME_NONNULL_BEGIN L groupA L groupB L ... + L filters: Matrix filters L MXFileStore : Information about the stored data + backup : This folder contains backup of files that are modified during the commit process. It is flushed when the commit completes. @@ -86,6 +103,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)diskUsageWithBlock:(void(^)(NSUInteger diskUsage))block; +/** + Set the preload options for the file store. + + These options are used in the `[MXStore openWithCredentials:]`. + + @param preloadOptions bit flags of `MXFileStorePreloadOptions`. + */ ++ (void)setPreloadOptions:(MXFileStorePreloadOptions)preloadOptions; + #pragma mark - Async API /** @@ -126,16 +152,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)asyncRoomsSummaries:(void (^)(NSArray *roomsSummaries))success failure:(nullable void (^)(NSError *error))failure; -/** - Get the stored room state for a specific room. - @param roomId the Id of the room - @param success A block object called when the operation succeeds. - @param failure A block object called when the operation fails. - */ -- (void)asyncStateEventsOfRoom:(NSString *)roomId - success:(void (^)(NSArray * _Nonnull stateEvents))success - failure:(nullable void (^)(NSError * _Nonnull error))failure; - /** Get the stored account data for a specific room. diff --git a/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m b/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m index d9f048dd36..de4dd88dd1 100644 --- a/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m +++ b/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m @@ -1,6 +1,7 @@ /* Copyright 2014 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,10 +25,11 @@ #import "MXSDKOptions.h" #import "MXTools.h" -static NSUInteger const kMXFileVersion = 52; +static NSUInteger const kMXFileVersion = 56; static NSString *const kMXFileStoreFolder = @"MXFileStore"; static NSString *const kMXFileStoreMedaDataFile = @"MXFileStore"; +static NSString *const kMXFileStoreFiltersFile = @"filters"; static NSString *const kMXFileStoreUsersFolder = @"users"; static NSString *const kMXFileStoreGroupsFolder = @"groups"; static NSString *const kMXFileStoreBackupFolder = @"backup"; @@ -41,6 +43,8 @@ static NSString *const kMXFileStoreRoomAccountDataFile = @"accountData"; static NSString *const kMXFileStoreRoomReadReceiptsFile = @"readReceipts"; +static NSUInteger preloadOptions; + @interface MXFileStore () { // Meta data about the store. It is defined only if the passed MXCredentials contains all information. @@ -83,6 +87,9 @@ @interface MXFileStore () // Flag to indicate metaData needs to be stored BOOL metaDataHasChanged; + // Flag to indicate Matrix filters need to be stored + BOOL filtersHasChanged; + // Cache used to preload room states while the store is opening. // It is filled on the separate thread so that the UI thread will not be blocked // when it will read rooms states. @@ -112,6 +119,16 @@ @interface MXFileStore () @implementation MXFileStore ++ (void)initialize +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + + // By default, we do not need to preload rooms states now + preloadOptions = MXFileStorePreloadOptionRoomSummary | MXFileStorePreloadOptionRoomAccountData; + }); +} + - (instancetype)init; { self = [super init]; @@ -211,9 +228,18 @@ - (void)openWithCredentials:(MXCredentials*)someCredentials onComplete:(void (^) NSLog(@"[MXFileStore] Start data loading from files"); [self loadRoomsMessages]; - [self preloadRoomsStates]; - [self preloadRoomsSummaries]; - [self preloadRoomsAccountData]; + if (preloadOptions & MXFileStorePreloadOptionRoomState) + { + [self preloadRoomsStates]; + } + if (preloadOptions & MXFileStorePreloadOptionRoomSummary) + { + [self preloadRoomsSummaries]; + } + if (preloadOptions & MXFileStorePreloadOptionRoomAccountData) + { + [self preloadRoomsAccountData]; + } [self loadReceipts]; [self loadUsers]; [self loadGroups]; @@ -276,6 +302,11 @@ - (void)diskUsageWithBlock:(void (^)(NSUInteger))block } ++ (void)setPreloadOptions:(MXFileStorePreloadOptions)thePreloadOptions +{ + preloadOptions = thePreloadOptions; +} + #pragma mark - MXStore - (void)storeEventForRoom:(NSString*)roomId event:(MXEvent*)event direction:(MXTimelineDirection)direction { @@ -376,6 +407,17 @@ - (void)storePartialTextMessageForRoom:(NSString *)roomId partialTextMessage:(NS } } +- (void)storeHasLoadedAllRoomMembersForRoom:(NSString *)roomId andValue:(BOOL)value +{ + [super storeHasLoadedAllRoomMembersForRoom:roomId andValue:value]; + + if (NSNotFound == [roomsToCommitForMessages indexOfObject:roomId]) + { + [roomsToCommitForMessages addObject:roomId]; + } +} + + - (BOOL)isPermanent { return YES; @@ -401,6 +443,18 @@ - (void)storeStateForRoom:(NSString*)roomId stateEvents:(NSArray*)stateEvents roomsToCommitForState[roomId] = stateEvents; } +- (void)stateOfRoom:(NSString *)roomId success:(void (^)(NSArray * _Nonnull))success failure:(void (^)(NSError * _Nonnull))failure +{ + dispatch_async(dispatchQueue, ^{ + + NSArray *stateEvents = [self stateOfRoom:roomId]; + + dispatch_async(dispatch_get_main_queue(), ^{ + success(stateEvents); + }); + }); +} + - (NSArray*)stateOfRoom:(NSString *)roomId { // First, try to get the state from the cache @@ -527,6 +581,73 @@ - (NSDictionary *)userAccountData return metaData.userAccountData; } +#pragma mark - Matrix filters +- (void)setSyncFilterId:(NSString *)syncFilterId +{ + [super setSyncFilterId:syncFilterId]; + if (metaData) + { + metaData.syncFilterId = syncFilterId; + metaDataHasChanged = YES; + } +} + +- (NSString *)syncFilterId +{ + return metaData.syncFilterId; +} + +- (void)storeFilter:(nonnull MXFilterJSONModel*)filter withFilterId:(nonnull NSString*)filterId +{ + [super storeFilter:filter withFilterId:filterId]; + filtersHasChanged = YES; +} + +- (void)filterWithFilterId:(nonnull NSString*)filterId + success:(nonnull void (^)(MXFilterJSONModel * _Nullable filter))success + failure:(nullable void (^)(NSError * _Nullable error))failure +{ + if (filters) + { + [super filterWithFilterId:filterId success:success failure:failure]; + } + else + { + // Load filters from the store + dispatch_async(dispatchQueue, ^{ + + [self loadFilters]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [super filterWithFilterId:filterId success:success failure:failure]; + }); + }); + } +} + +- (void)filterIdForFilter:(nonnull MXFilterJSONModel*)filter + success:(nonnull void (^)(NSString * _Nullable filterId))success + failure:(nullable void (^)(NSError * _Nullable error))failure +{ + if (filters) + { + [super filterIdForFilter:filter success:success failure:failure]; + } + else + { + // Load filters from the store + dispatch_async(dispatchQueue, ^{ + + [self loadFilters]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [super filterIdForFilter:filter success:success failure:failure]; + }); + }); + } +} + + - (void)commit { // Save data only if metaData exists @@ -621,6 +742,7 @@ - (void)saveDataToFiles [self saveUsers]; [self saveGroupsDeletion]; [self saveGroups]; + [self saveFilters]; [self saveMetaData]; } @@ -815,6 +937,26 @@ - (NSString*)groupFileForGroup:(NSString*)groupId forBackup:(BOOL)backup } } +- (NSString*)filtersFileForBackup:(BOOL)backup +{ + if (!backup) + { + return [storePath stringByAppendingPathComponent:kMXFileStoreFiltersFile]; + } + else + { + if (backupEventStreamToken) + { + return [[storeBackupPath stringByAppendingPathComponent:backupEventStreamToken] stringByAppendingPathComponent:kMXFileStoreFiltersFile]; + } + else + { + return nil; + } + } +} + + #pragma mark - Storage validity - (BOOL)checkStorageValidity { @@ -1308,6 +1450,52 @@ - (void)saveMetaData } } +#pragma mark - Matrix filters +- (void)loadFilters +{ + NSString *file = [storePath stringByAppendingPathComponent:kMXFileStoreFiltersFile]; + filters = [NSKeyedUnarchiver unarchiveObjectWithFile:file]; + + if (!filters) + { + // This is used as flag to indicate it has been mounted from the file + filters = [NSMutableDictionary dictionary]; + } +} + +- (void)saveFilters +{ + // Save only in case of change + if (filtersHasChanged) + { + filtersHasChanged = NO; + + MXWeakify(self); + dispatch_async(dispatchQueue, ^(void){ + MXStrongifyAndReturnIfNil(self); + + NSString *file = [self filtersFileForBackup:NO]; + NSString *backupFile = [self filtersFileForBackup:YES]; + + // Backup the file + if (backupFile && [[NSFileManager defaultManager] fileExistsAtPath:file]) + { + // Make sure the backup folder exists + NSString *storeBackupMetaDataPath = [self->storeBackupPath stringByAppendingPathComponent:self->backupEventStreamToken]; + if (![NSFileManager.defaultManager fileExistsAtPath:storeBackupMetaDataPath]) + { + [[NSFileManager defaultManager] createDirectoryAtPath:storeBackupMetaDataPath withIntermediateDirectories:YES attributes:nil error:nil]; + } + + [[NSFileManager defaultManager] moveItemAtPath:file toPath:backupFile error:nil]; + } + + // Store new data + [NSKeyedArchiver archiveRootObject:self->filters toFile:file]; + }); + } +} + #pragma mark - Matrix users /** Preload all users. @@ -1752,18 +1940,6 @@ - (void)asyncRoomsSummaries:(void (^)(NSArray * _Nonnull))succe }); } -- (void)asyncStateEventsOfRoom:(NSString *)roomId success:(void (^)(NSArray * _Nonnull))success failure:(nullable void (^)(NSError * _Nonnull))failure -{ - dispatch_async(dispatchQueue, ^{ - - NSArray *stateEvents = [self stateOfRoom:roomId]; - - dispatch_async(dispatch_get_main_queue(), ^{ - success(stateEvents); - }); - }); -} - - (void)asyncAccountDataOfRoom:(NSString *)roomId success:(void (^)(MXRoomAccountData * _Nonnull))success failure:(nullable void (^)(NSError * _Nonnull))failure { dispatch_async(dispatchQueue, ^{ diff --git a/MatrixSDK/Data/Store/MXFileStore/MXFileStoreMetaData.h b/MatrixSDK/Data/Store/MXFileStore/MXFileStoreMetaData.h index 87f3c2e5a1..3638b61d7f 100644 --- a/MatrixSDK/Data/Store/MXFileStore/MXFileStoreMetaData.h +++ b/MatrixSDK/Data/Store/MXFileStore/MXFileStoreMetaData.h @@ -34,6 +34,11 @@ */ @property (nonatomic) NSString *eventStreamToken; +/** + The id of the filter being used in /sync requests. + */ +@property (nonatomic) NSString *syncFilterId; + /** The current version of the store. */ diff --git a/MatrixSDK/Data/Store/MXFileStore/MXFileStoreMetaData.m b/MatrixSDK/Data/Store/MXFileStore/MXFileStoreMetaData.m index d324791dfb..3a31399b6e 100644 --- a/MatrixSDK/Data/Store/MXFileStore/MXFileStoreMetaData.m +++ b/MatrixSDK/Data/Store/MXFileStore/MXFileStoreMetaData.m @@ -27,6 +27,7 @@ - (id)initWithCoder:(NSCoder *)aDecoder _homeServer = dict[@"homeServer"]; _userId = dict[@"userId"]; _eventStreamToken = dict[@"eventStreamToken"]; + _syncFilterId = dict[@"syncFilterId"]; _userAccountData = dict[@"userAccountData"]; NSNumber *version = dict[@"version"]; @@ -37,7 +38,7 @@ - (id)initWithCoder:(NSCoder *)aDecoder -(void)encodeWithCoder:(NSCoder *)aCoder { - // All properties are mandatory except eventStreamToken + // Mandatory, non-null, properties NSMutableDictionary *dict =[NSMutableDictionary dictionaryWithDictionary: @{ @"homeServer": _homeServer, @@ -45,10 +46,15 @@ -(void)encodeWithCoder:(NSCoder *)aCoder @"version": @(_version), }]; + // Nullable properties if (_eventStreamToken) { dict[@"eventStreamToken"] = _eventStreamToken; } + if (_syncFilterId) + { + dict[@"syncFilterId"] = _syncFilterId; + } if (_userAccountData) { dict[@"userAccountData"] = _userAccountData; diff --git a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryRoomStore.h b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryRoomStore.h index 1b759e214a..ea52ea5045 100644 --- a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryRoomStore.h +++ b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryRoomStore.h @@ -69,6 +69,11 @@ */ @property (nonatomic) BOOL hasReachedHomeServerPaginationEnd; +/** + The flag indicating that the SDK has retrieved all room members. + */ +@property (nonatomic) BOOL hasLoadedAllRoomMembersForRoom; + /** Reset the current messages array. */ diff --git a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryRoomStore.m b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryRoomStore.m index d322b77aa7..72eee29cfe 100644 --- a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryRoomStore.m +++ b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryRoomStore.m @@ -36,7 +36,9 @@ - (instancetype)init { messages = [NSMutableArray array]; messagesByEventIds = [NSMutableDictionary dictionary]; - outgoingMessages = [NSMutableArray array];; + outgoingMessages = [NSMutableArray array]; + _hasReachedHomeServerPaginationEnd = NO; + _hasLoadedAllRoomMembersForRoom = NO; } return self; } @@ -154,7 +156,7 @@ - (void)removeOutgoingMessage:(NSString*)outgoingMessageEventId - (NSString *)description { - return [NSString stringWithFormat:@"%tu messages - paginationToken: %@ - hasReachedHomeServerPaginationEnd: %d", messages.count, _paginationToken, _hasReachedHomeServerPaginationEnd]; + return [NSString stringWithFormat:@"%tu messages - paginationToken: %@ - hasReachedHomeServerPaginationEnd: %@ - hasLoadedAllRoomMembersForRoom: %@", messages.count, _paginationToken, @(_hasReachedHomeServerPaginationEnd), @(_hasLoadedAllRoomMembersForRoom)]; } @end diff --git a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.h b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.h index 8656b0b6d6..63d010283a 100644 --- a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.h +++ b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.h @@ -37,6 +37,10 @@ // Dict of dict of MXReceiptData indexed by userId NSMutableDictionary *receiptsByRoomId; + // Matrix filters + // FilterId -> Filter JSON string + NSMutableDictionary *filters; + // The user credentials MXCredentials *credentials; } diff --git a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.m b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.m index 43edbce165..62868b83fe 100644 --- a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.m +++ b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.m @@ -19,6 +19,8 @@ #import "MXMemoryRoomStore.h" +#import "MXTools.h" + @interface MXMemoryStore() { NSString *eventStreamToken; @@ -28,7 +30,7 @@ @interface MXMemoryStore() @implementation MXMemoryStore -@synthesize eventStreamToken, userAccountData; +@synthesize eventStreamToken, userAccountData, syncFilterId; - (instancetype)init { @@ -126,6 +128,18 @@ - (BOOL)hasReachedHomeServerPaginationEndForRoom:(NSString*)roomId return roomStore.hasReachedHomeServerPaginationEnd; } +- (void)storeHasLoadedAllRoomMembersForRoom:(NSString *)roomId andValue:(BOOL)value +{ + MXMemoryRoomStore *roomStore = [self getOrCreateRoomStore:roomId]; + roomStore.hasLoadedAllRoomMembersForRoom = value; +} + +- (BOOL)hasLoadedAllRoomMembersForRoom:(NSString *)roomId +{ + MXMemoryRoomStore *roomStore = [self getOrCreateRoomStore:roomId]; + return roomStore.hasLoadedAllRoomMembersForRoom; +} + - (id)messagesEnumeratorForRoom:(NSString *)roomId { @@ -342,6 +356,55 @@ - (void)removeOutgoingMessageFromRoom:(NSString*)roomId outgoingMessage:(NSStrin } +#pragma mark - Matrix filters +- (void)storeFilter:(nonnull MXFilterJSONModel*)filter withFilterId:(nonnull NSString*)filterId +{ + if (!filters) + { + filters = [NSMutableDictionary dictionary]; + } + + filters[filterId] = filter.jsonString; +} + +- (void)filterWithFilterId:(nonnull NSString*)filterId + success:(nonnull void (^)(MXFilterJSONModel * _Nullable filter))success + failure:(nullable void (^)(NSError * _Nullable error))failure +{ + MXFilterJSONModel *filter; + + NSString *jsonString = filters[filterId]; + if (jsonString) + { + NSDictionary *json = [MXTools deserialiseJSONString:jsonString]; + filter = [MXFilterJSONModel modelFromJSON:json]; + } + + success(filter); +} + +- (void)filterIdForFilter:(nonnull MXFilterJSONModel*)filter + success:(nonnull void (^)(NSString * _Nullable filterId))success + failure:(nullable void (^)(NSError * _Nullable error))failure +{ + NSString *theFilterId; + + for (NSString *filterId in filters) + { + NSDictionary *json = [MXTools deserialiseJSONString:filters[filterId]]; + MXFilterJSONModel *cachedFilter = [MXFilterJSONModel modelFromJSON:json]; + + if ([cachedFilter isEqual:filter]) + { + theFilterId = filterId; + break; + } + } + + success(theFilterId); +} + + #pragma mark - Protected operations - (MXMemoryRoomStore*)getOrCreateRoomStore:(NSString*)roomId { diff --git a/MatrixSDK/Data/Store/MXNoStore/MXNoStore.m b/MatrixSDK/Data/Store/MXNoStore/MXNoStore.m index 86c5072f2a..4c6d17b44f 100644 --- a/MatrixSDK/Data/Store/MXNoStore/MXNoStore.m +++ b/MatrixSDK/Data/Store/MXNoStore/MXNoStore.m @@ -32,6 +32,9 @@ @interface MXNoStore () // key: roomId, value: the bool value NSMutableDictionary *hasReachedHomeServerPaginations; + // key: roomId, value: the bool value + NSMutableDictionary *hasLoadedAllRoomMembersForRooms; + // key: roomId, value: the last message of this room NSMutableDictionary *lastMessages; @@ -52,7 +55,7 @@ @interface MXNoStore () @implementation MXNoStore -@synthesize eventStreamToken, userAccountData; +@synthesize eventStreamToken, userAccountData, syncFilterId; - (instancetype)init { @@ -63,6 +66,7 @@ - (instancetype)init notificationCounts = [NSMutableDictionary dictionary]; highlightCounts = [NSMutableDictionary dictionary]; hasReachedHomeServerPaginations = [NSMutableDictionary dictionary]; + hasLoadedAllRoomMembersForRooms = [NSMutableDictionary dictionary]; lastMessages = [NSMutableDictionary dictionary]; partialTextMessages = [NSMutableDictionary dictionary]; users = [NSMutableDictionary dictionary]; @@ -139,6 +143,10 @@ - (void)deleteRoom:(NSString *)roomId { [hasReachedHomeServerPaginations removeObjectForKey:roomId]; } + if (hasLoadedAllRoomMembersForRooms[roomId]) + { + [hasLoadedAllRoomMembersForRooms removeObjectForKey:roomId]; + } if (lastMessages[roomId]) { [lastMessages removeObjectForKey:roomId]; @@ -155,6 +163,7 @@ - (void)deleteAllData [notificationCounts removeAllObjects]; [highlightCounts removeAllObjects]; [hasReachedHomeServerPaginations removeAllObjects]; + [hasLoadedAllRoomMembersForRooms removeAllObjects]; [lastMessages removeAllObjects]; [partialTextMessages removeAllObjects]; } @@ -186,6 +195,25 @@ - (BOOL)hasReachedHomeServerPaginationEndForRoom:(NSString*)roomId return hasReachedHomeServerPaginationEnd; } +- (void)storeHasLoadedAllRoomMembersForRoom:(NSString *)roomId andValue:(BOOL)value +{ + hasLoadedAllRoomMembersForRooms[roomId] = @(value); +} + +- (BOOL)hasLoadedAllRoomMembersForRoom:(NSString *)roomId +{ + BOOL hasLoadedAllRoomMembers = NO; + + NSNumber *hasLoadedAllRoomMembersNumber = hasLoadedAllRoomMembersForRooms[roomId]; + if (hasLoadedAllRoomMembersNumber) + { + hasLoadedAllRoomMembers = [hasLoadedAllRoomMembersNumber boolValue]; + } + + return hasLoadedAllRoomMembers; +} + + - (id)messagesEnumeratorForRoom:(NSString *)roomId { // As the back pagination is based on the HS back pagination API, reset data about it @@ -255,6 +283,25 @@ - (void)deleteGroup:(NSString *)groupId } } +#pragma mark - Matrix filters +- (void)storeFilter:(nonnull MXFilterJSONModel*)filter withFilterId:(nonnull NSString*)filterId +{ +} + +- (void)filterWithFilterId:(nonnull NSString*)filterId + success:(nonnull void (^)(MXFilterJSONModel * _Nullable filter))success + failure:(nullable void (^)(NSError * _Nullable error))failure +{ + success(nil); +} + +- (void)filterIdForFilter:(nonnull MXFilterJSONModel*)filter + success:(nonnull void (^)(NSString * _Nullable filterId))success + failure:(nullable void (^)(NSError * _Nullable error))failure +{ + success(nil); +} + - (void)storePartialTextMessageForRoom:(NSString *)roomId partialTextMessage:(NSString *)partialTextMessage { if (partialTextMessage) diff --git a/MatrixSDK/Data/Store/MXStore.h b/MatrixSDK/Data/Store/MXStore.h index 6cfd10017b..5cc6ef6376 100644 --- a/MatrixSDK/Data/Store/MXStore.h +++ b/MatrixSDK/Data/Store/MXStore.h @@ -1,6 +1,7 @@ /* Copyright 2014 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,6 +24,7 @@ #import "MXRoomSummary.h" #import "MXRoomAccountData.h" #import "MXGroup.h" +#import "MXFilterJSONModel.h" #import "MXEventsEnumerator.h" @@ -44,7 +46,7 @@ @param onComplete the callback called once the data has been loaded @param failure the callback called in case of error. */ -- (void)openWithCredentials:(MXCredentials*)credentials onComplete:(void (^)(void))onComplete failure:(void (^)(NSError *error))failure; +- (void)openWithCredentials:(nonnull MXCredentials*)credentials onComplete:(nullable void (^)(void))onComplete failure:(nullable void (^)(NSError * _Nullable error))failure; /** Store a room event received from the home server. @@ -56,7 +58,7 @@ @param event the MXEvent object to store. @param direction the origin of the event. Live or past events. */ -- (void)storeEventForRoom:(NSString*)roomId event:(MXEvent*)event direction:(MXTimelineDirection)direction; +- (void)storeEventForRoom:(nonnull NSString*)roomId event:(nonnull MXEvent*)event direction:(MXTimelineDirection)direction; /** Replace a room event (in case of redaction for example). @@ -65,7 +67,7 @@ @param event the MXEvent object to store. @param roomId the id of the room. */ -- (void)replaceEvent:(MXEvent*)event inRoom:(NSString*)roomId; +- (void)replaceEvent:(nonnull MXEvent*)event inRoom:(nonnull NSString*)roomId; /** Returns a Boolean value that indicates whether an event is already stored. @@ -75,7 +77,7 @@ @return YES if the event exists in the store. */ -- (BOOL)eventExistsWithEventId:(NSString*)eventId inRoom:(NSString*)roomId; +- (BOOL)eventExistsWithEventId:(nonnull NSString*)eventId inRoom:(nonnull NSString*)roomId; /** Get an event in a room from the store. @@ -85,7 +87,7 @@ @return the MXEvent object or nil if not found. */ -- (MXEvent*)eventWithEventId:(NSString*)eventId inRoom:(NSString*)roomId; +- (MXEvent* _Nullable)eventWithEventId:(nonnull NSString*)eventId inRoom:(nonnull NSString*)roomId; /** Remove all existing messages in a room. @@ -93,14 +95,14 @@ @param roomId the id of the room. */ -- (void)deleteAllMessagesInRoom:(NSString *)roomId; +- (void)deleteAllMessagesInRoom:(nonnull NSString *)roomId; /** Erase a room and all related data. @param roomId the id of the room. */ -- (void)deleteRoom:(NSString*)roomId; +- (void)deleteRoom:(nonnull NSString*)roomId; /** Erase all data from the store. @@ -111,16 +113,23 @@ Store/retrieve the current pagination token of a room. */ // @TODO(summary): Move to MXRoomSummary -- (void)storePaginationTokenOfRoom:(NSString*)roomId andToken:(NSString*)token; -- (NSString*)paginationTokenOfRoom:(NSString*)roomId; +- (void)storePaginationTokenOfRoom:(nonnull NSString*)roomId andToken:(nonnull NSString*)token; +- (NSString * _Nullable)paginationTokenOfRoom:(nonnull NSString*)roomId; /** Store/retrieve the flag indicating that the SDK has reached the end of pagination in its pagination requests to the home server. */ // @TODO(summary): Move to MXRoomSummary -- (void)storeHasReachedHomeServerPaginationEndForRoom:(NSString*)roomId andValue:(BOOL)value; -- (BOOL)hasReachedHomeServerPaginationEndForRoom:(NSString*)roomId; +- (void)storeHasReachedHomeServerPaginationEndForRoom:(nonnull NSString*)roomId andValue:(BOOL)value; +- (BOOL)hasReachedHomeServerPaginationEndForRoom:(nonnull NSString*)roomId; + +/** + Store/retrieve the flag indicating that the SDK has retrieved all room members + of a room. + */ +- (void)storeHasLoadedAllRoomMembersForRoom:(nonnull NSString*)roomId andValue:(BOOL)value; +- (BOOL)hasLoadedAllRoomMembersForRoom:(nonnull NSString*)roomId; /** Get an events enumerator on all messages of a room. @@ -128,7 +137,7 @@ @param roomId the id of the room. @return the events enumerator. */ -- (id)messagesEnumeratorForRoom:(NSString*)roomId; +- (id _Nonnull)messagesEnumeratorForRoom:(nonnull NSString*)roomId; /** Get an events enumerator on messages of a room with a filter on the events types. @@ -137,21 +146,21 @@ @param types an array of event types strings (MXEventTypeString). @return the events enumerator. */ -- (id)messagesEnumeratorForRoom:(NSString*)roomId withTypeIn:(NSArray*)types; +- (id _Nonnull)messagesEnumeratorForRoom:(nonnull NSString*)roomId withTypeIn:(nullable NSArray*)types; #pragma mark - Matrix users /** Store a matrix user. */ -- (void)storeUser:(MXUser*)user; +- (void)storeUser:(nonnull MXUser*)user; /** Get the list of all stored matrix users. @return an array of MXUser. */ -- (NSArray*)users; +- (NSArray* _Nullable)users; /** Get a matrix user. @@ -159,20 +168,20 @@ @param userId The id to the user. @return the MXUser instance or nil if not found. */ -- (MXUser*)userWithUserId:(NSString*)userId; +- (MXUser* _Nullable)userWithUserId:(nonnull NSString*)userId; #pragma mark - groups /** Store a matrix group. */ -- (void)storeGroup:(MXGroup*)group; +- (void)storeGroup:(nonnull MXGroup*)group; /** Get the list of all stored matrix groups. @return an array of MXGroup. */ -- (NSArray*)groups; +- (NSArray* _Nullable)groups; /** Get a matrix group. @@ -180,14 +189,14 @@ @param groupId The id to the group. @return the MXGroup instance or nil if not found. */ -- (MXGroup*)groupWithGroupId:(NSString*)groupId; +- (MXGroup* _Nullable)groupWithGroupId:(nonnull NSString*)groupId; /** Erase a group and all related data. @param groupId the id of the group. */ -- (void)deleteGroup:(NSString*)groupId; +- (void)deleteGroup:(nonnull NSString*)groupId; #pragma mark - /** @@ -197,7 +206,7 @@ @param partialTextMessage the text to store. Nil to reset it. */ // @TODO(summary): Move to MXRoomSummary -- (void)storePartialTextMessageForRoom:(NSString*)roomId partialTextMessage:(NSString*)partialTextMessage; +- (void)storePartialTextMessageForRoom:(nonnull NSString*)roomId partialTextMessage:(nonnull NSString*)partialTextMessage; /** The text message typed by the user but not yet sent. @@ -205,7 +214,7 @@ @param roomId the id of the room. @return the text message. Can be nil. */ -- (NSString*)partialTextMessageOfRoom:(NSString*)roomId; +- (NSString* _Nullable)partialTextMessageOfRoom:(nonnull NSString*)roomId; /** @@ -217,7 +226,7 @@ @param sort to sort them from the latest to the oldest @return the receipts for an event in a dedicated room. */ -- (NSArray *)getEventReceipts:(NSString*)roomId eventId:(NSString*)eventId sorted:(BOOL)sort; +- (NSArray * _Nullable)getEventReceipts:(nonnull NSString*)roomId eventId:(nonnull NSString*)eventId sorted:(BOOL)sort; /** Store the receipt for a user in a room @@ -226,7 +235,7 @@ @param roomId The roomId @return true if the receipt has been stored */ -- (BOOL)storeReceipt:(MXReceiptData*)receipt inRoom:(NSString*)roomId; +- (BOOL)storeReceipt:(nonnull MXReceiptData*)receipt inRoom:(nonnull NSString*)roomId; /** Retrieve the receipt for a user in a room @@ -235,7 +244,7 @@ @param userId The user identifier @return the current stored receipt (nil by default). */ -- (MXReceiptData *)getReceiptInRoom:(NSString*)roomId forUserId:(NSString*)userId; +- (MXReceiptData * _Nullable)getReceiptInRoom:(nonnull NSString*)roomId forUserId:(nonnull NSString*)userId; /** Count the unread events wrote in the store. @@ -247,7 +256,7 @@ @param types an array of event types strings (MXEventTypeString). @return The number of unread events which have their type listed in the provided array. */ -- (NSUInteger)localUnreadEventCount:(NSString*)roomId withTypeIn:(NSArray*)types; +- (NSUInteger)localUnreadEventCount:(nonnull NSString*)roomId withTypeIn:(nullable NSArray*)types; /** Indicate if the MXStore implementation stores data permanently. @@ -259,7 +268,7 @@ The token indicating from where to start listening event stream to get live events. */ -@property (nonatomic) NSString *eventStreamToken; +@property (nonatomic) NSString * _Nullable eventStreamToken; @optional @@ -280,15 +289,18 @@ - (void)close; -#pragma mark - Permanent storage +#pragma mark - Permanent storage - + /** Return the ids of the rooms currently stored. Note: this method is required in permanent storage implementation. - + @return the array of room ids. */ -- (NSArray*)rooms; +- (NSArray* _Nullable)rooms; + +#pragma mark - Room state /** Store the state of a room. @@ -298,7 +310,7 @@ @param roomId the id of the room. @param stateEvents the state events that define the room state. */ -- (void)storeStateForRoom:(NSString*)roomId stateEvents:(NSArray*)stateEvents; +- (void)storeStateForRoom:(nonnull NSString*)roomId stateEvents:(nonnull NSArray *)stateEvents; /** Get the state of a room. @@ -306,11 +318,15 @@ Note: this method is required in permanent storage implementation. @param roomId the id of the room. - - @return the stored state events that define the room state. + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. */ -- (NSArray*)stateOfRoom:(NSString*)roomId; +- (void)stateOfRoom:(nonnull NSString *)roomId + success:(nonnull void (^)(NSArray * _Nonnull stateEvents))success + failure:(nullable void (^)(NSError * _Nonnull error))failure; + +#pragma mark - Room summary /** Store the summary for a room. @@ -320,7 +336,7 @@ @param roomId the id of the room. @param summary the room summary. */ -- (void)storeSummaryForRoom:(NSString*)roomId summary:(MXRoomSummary*)summary; +- (void)storeSummaryForRoom:(nonnull NSString*)roomId summary:(nonnull MXRoomSummary*)summary; /** Get the summary a room. @@ -330,9 +346,11 @@ @param roomId the id of the room. @return the user private data for this room. */ -- (MXRoomSummary*)summaryOfRoom:(NSString*)roomId; +- (MXRoomSummary* _Nullable)summaryOfRoom:(nonnull NSString*)roomId; +#pragma mark - Room user data + /** Store the user data for a room. @@ -341,7 +359,7 @@ @param roomId the id of the room. @param accountData the private data the user defined for this room. */ -- (void)storeAccountDataForRoom:(NSString*)roomId userData:(MXRoomAccountData*)accountData; +- (void)storeAccountDataForRoom:(nonnull NSString*)roomId userData:(nonnull MXRoomAccountData*)accountData; /** Get the user data for a room. @@ -351,7 +369,7 @@ @param roomId the id of the room. @return the user private data for this room. */ -- (MXRoomAccountData*)accountDataOfRoom:(NSString*)roomId; +- (MXRoomAccountData* _Nullable)accountDataOfRoom:(nonnull NSString*)roomId; #pragma mark - Outgoing events @@ -361,14 +379,14 @@ @param roomId the id of the room. @param outgoingMessage the MXEvent object of the message. */ -- (void)storeOutgoingMessageForRoom:(NSString*)roomId outgoingMessage:(MXEvent*)outgoingMessage; +- (void)storeOutgoingMessageForRoom:(nonnull NSString*)roomId outgoingMessage:(nonnull MXEvent*)outgoingMessage; /** Remove all outgoing messages from a room. @param roomId the id of the room. */ -- (void)removeAllOutgoingMessagesFromRoom:(NSString*)roomId; +- (void)removeAllOutgoingMessagesFromRoom:(nonnull NSString*)roomId; /** Remove an outgoing message from a room. @@ -376,7 +394,7 @@ @param roomId the id of the room. @param outgoingMessageEventId the id of the message to remove. */ -- (void)removeOutgoingMessageFromRoom:(NSString*)roomId outgoingMessage:(NSString*)outgoingMessageEventId; +- (void)removeOutgoingMessageFromRoom:(nonnull NSString*)roomId outgoingMessage:(nonnull NSString*)outgoingMessageEventId; /** Get all outgoing messages pending in a room. @@ -384,11 +402,50 @@ @param roomId the id of the room. @return the list of messages that have not been sent yet */ -- (NSArray*)outgoingMessagesInRoom:(NSString*)roomId; +- (NSArray* _Nullable)outgoingMessagesInRoom:(nonnull NSString*)roomId; + +#pragma mark - User Account data /** Store/retrieve the user account data. */ -@property (nonatomic) NSDictionary *userAccountData; +@property (nonatomic) NSDictionary * _Nullable userAccountData; + + +#pragma mark - Matrix filters +/** + Store/retrieve the id of the Matrix filter used in /sync requests. + */ +@property (nonatomic) NSString * _Nullable syncFilterId; + +/** + Store a created filter. + + @param filter the filter to store. + @param filterId the id of this filter on the homeserver. + */ +- (void)storeFilter:(nonnull MXFilterJSONModel*)filter withFilterId:(nonnull NSString*)filterId; + +/** + Retrieve a filter with a given id. + + @param filterId the id of the filter. + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + */ +- (void)filterWithFilterId:(nonnull NSString*)filterId + success:(nonnull void (^)(MXFilterJSONModel * _Nullable filter))success + failure:(nullable void (^)(NSError * _Nullable error))failure; + +/** + Check if a filter already exists and return its filter id. + + @param filter the filter to check the existence. + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + */ +- (void)filterIdForFilter:(nonnull MXFilterJSONModel*)filter + success:(nonnull void (^)(NSString * _Nullable filterId))success + failure:(nullable void (^)(NSError * _Nullable error))failure; @end diff --git a/MatrixSDK/JSONModels/MXEvent.h b/MatrixSDK/JSONModels/MXEvent.h index 61a4d4c7d9..a3072ead04 100644 --- a/MatrixSDK/JSONModels/MXEvent.h +++ b/MatrixSDK/JSONModels/MXEvent.h @@ -65,6 +65,7 @@ typedef enum : NSUInteger MXEventTypeCallAnswer, MXEventTypeCallHangup, MXEventTypeSticker, + MXEventTypeRoomTombStone, // The event is a custom event. Refer to its `MXEventTypeString` version MXEventTypeCustom = 1000 @@ -109,6 +110,7 @@ FOUNDATION_EXPORT NSString *const kMXEventTypeStringCallCandidates; FOUNDATION_EXPORT NSString *const kMXEventTypeStringCallAnswer; FOUNDATION_EXPORT NSString *const kMXEventTypeStringCallHangup; FOUNDATION_EXPORT NSString *const kMXEventTypeStringSticker; +FOUNDATION_EXPORT NSString *const kMXEventTypeStringRoomTombStone; /** Types of room messages diff --git a/MatrixSDK/JSONModels/MXEvent.m b/MatrixSDK/JSONModels/MXEvent.m index aec652873d..2351dc8488 100644 --- a/MatrixSDK/JSONModels/MXEvent.m +++ b/MatrixSDK/JSONModels/MXEvent.m @@ -56,6 +56,7 @@ NSString *const kMXEventTypeStringCallAnswer = @"m.call.answer"; NSString *const kMXEventTypeStringCallHangup = @"m.call.hangup"; NSString *const kMXEventTypeStringSticker = @"m.sticker"; +NSString *const kMXEventTypeStringRoomTombStone = @"m.room.tombstone"; NSString *const kMXMessageTypeText = @"m.text"; NSString *const kMXMessageTypeEmote = @"m.emote"; @@ -530,7 +531,25 @@ - (void)setClearData:(MXEventDecryptionResult *)decryptionResult _clearEvent = nil; if (decryptionResult.clearEvent) { - _clearEvent = [MXEvent modelFromJSON:decryptionResult.clearEvent]; + NSDictionary *decryptionClearEventJSON; + NSDictionary *encryptedContentRelatesToJSON = _wireContent[@"m.relates_to"]; + + // Add "m.relates_to" data from e2e event to the unencrypted content event + if (encryptedContentRelatesToJSON) + { + NSMutableDictionary *decryptionClearEventUpdatedJSON = [decryptionResult.clearEvent mutableCopy]; + NSMutableDictionary *clearEventContentUpdatedJSON = [decryptionClearEventUpdatedJSON[@"content"] mutableCopy]; + + clearEventContentUpdatedJSON[@"m.relates_to"] = encryptedContentRelatesToJSON; + decryptionClearEventUpdatedJSON[@"content"] = [clearEventContentUpdatedJSON copy]; + decryptionClearEventJSON = [decryptionClearEventUpdatedJSON copy]; + } + else + { + decryptionClearEventJSON = decryptionResult.clearEvent; + } + + _clearEvent = [MXEvent modelFromJSON:decryptionClearEventJSON]; } if (_clearEvent) diff --git a/MatrixSDK/JSONModels/MXJSONModel.h b/MatrixSDK/JSONModels/MXJSONModel.h index 6965db9aa9..471d6003c6 100644 --- a/MatrixSDK/JSONModels/MXJSONModel.h +++ b/MatrixSDK/JSONModels/MXJSONModel.h @@ -69,6 +69,11 @@ */ - (NSDictionary *)others; +/** + The JSON string representating the filter. + */ +- (NSString *)jsonString; + @end diff --git a/MatrixSDK/JSONModels/MXJSONModel.m b/MatrixSDK/JSONModels/MXJSONModel.m index fe2729d490..79369a81e3 100644 --- a/MatrixSDK/JSONModels/MXJSONModel.m +++ b/MatrixSDK/JSONModels/MXJSONModel.m @@ -16,6 +16,8 @@ #import "MXJSONModel.h" +#import "MXTools.h" + @implementation MXJSONModel @@ -140,6 +142,11 @@ - (NSDictionary *)others return nil; } +- (NSString *)jsonString +{ + return [MXTools serialiseJSONObject:self.JSONDictionary]; +} + #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone diff --git a/MatrixSDK/JSONModels/MXJSONModels.h b/MatrixSDK/JSONModels/MXJSONModels.h index 853efd8cd9..6c568bbf3c 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.h +++ b/MatrixSDK/JSONModels/MXJSONModels.h @@ -361,9 +361,14 @@ FOUNDATION_EXPORT NSString *const kMXLoginIdentifierTypePhone; @interface MXPaginationResponse : MXJSONModel /** - An array of MXEvents. + An array of timeline MXEvents. */ - @property (nonatomic) NSArray *chunk; + @property (nonatomic) NSArray *chunk; + + /** + In case of lazy loading, more state MXEvents. + */ + @property (nonatomic) NSArray *state; /** The opaque token for the start. @@ -1211,6 +1216,33 @@ FOUNDATION_EXPORT NSString *const kMXPushRuleScopeStringDevice; @end +/** + `MXRoomSyncSummary` represents the summary of a room. + */ +@interface MXRoomSyncSummary : MXJSONModel + +/** + Present only if the room has no m.room.name or m.room.canonical_alias. + Lists the mxids of the first 5 members in the room who are currently joined or + invited (ordered by stream ordering as seen on the server). + */ +@property (nonatomic) NSArray *heroes; + +/** + The number of m.room.members in state ‘joined’ (including the syncing user). + -1 means the information was not sent by the server. + */ +@property (nonatomic) NSUInteger joinedMemberCount; + +/** + The number of m.room.members in state ‘invited’. + -1 means the information was not sent by the server. + */ +@property (nonatomic) NSUInteger invitedMemberCount; + +@end + + /** `MXRoomSync` represents the response for a room during server sync. */ @@ -1241,6 +1273,11 @@ FOUNDATION_EXPORT NSString *const kMXPushRuleScopeStringDevice; */ @property (nonatomic) MXRoomSyncUnreadNotifications *unreadNotifications; + /** + The room summary. Sent in case of lazy-loading of members. + */ + @property (nonatomic) MXRoomSyncSummary *summary; + @end /** diff --git a/MatrixSDK/JSONModels/MXJSONModels.m b/MatrixSDK/JSONModels/MXJSONModels.m index f9937d8bcd..dab65c073b 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.m +++ b/MatrixSDK/JSONModels/MXJSONModels.m @@ -298,6 +298,7 @@ + (id)modelFromJSON:(NSDictionary *)JSONDictionary if (paginationResponse) { MXJSONModelSetMXJSONModelArray(paginationResponse.chunk, MXEvent, JSONDictionary[@"chunk"]); + MXJSONModelSetMXJSONModelArray(paginationResponse.state, MXEvent, JSONDictionary[@"state"]); MXJSONModelSetString(paginationResponse.start, JSONDictionary[@"start"]); MXJSONModelSetString(paginationResponse.end, JSONDictionary[@"end"]); @@ -1109,6 +1110,39 @@ + (id)modelFromJSON:(NSDictionary *)JSONDictionary @end +@implementation MXRoomSyncSummary + +- (instancetype)init +{ + self = [super init]; + if (self) + { + _joinedMemberCount = -1; + _invitedMemberCount = -1; + } + return self; +} + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + MXRoomSyncSummary *roomSyncSummary; + + if (JSONDictionary.count) + { + roomSyncSummary = [MXRoomSyncSummary new]; + if (roomSyncSummary) + { + MXJSONModelSetArray(roomSyncSummary.heroes, JSONDictionary[@"m.heroes"]); + MXJSONModelSetUInteger(roomSyncSummary.joinedMemberCount, JSONDictionary[@"m.joined_member_count"]); + MXJSONModelSetUInteger(roomSyncSummary.invitedMemberCount, JSONDictionary[@"m.invited_member_count"]); + } + } + return roomSyncSummary; +} + +@end + + @implementation MXRoomSync + (id)modelFromJSON:(NSDictionary *)JSONDictionary @@ -1121,6 +1155,7 @@ + (id)modelFromJSON:(NSDictionary *)JSONDictionary MXJSONModelSetMXJSONModel(roomSync.ephemeral, MXRoomSyncEphemeral, JSONDictionary[@"ephemeral"]); MXJSONModelSetMXJSONModel(roomSync.accountData, MXRoomSyncAccountData, JSONDictionary[@"account_data"]); MXJSONModelSetMXJSONModel(roomSync.unreadNotifications, MXRoomSyncUnreadNotifications, JSONDictionary[@"unread_notifications"]); + MXJSONModelSetMXJSONModel(roomSync.summary, MXRoomSyncSummary, JSONDictionary[@"summary"]); } return roomSync; } diff --git a/MatrixSDK/JSONModels/MXRoomCreateContent.h b/MatrixSDK/JSONModels/MXRoomCreateContent.h new file mode 100644 index 0000000000..bc7e1df6f8 --- /dev/null +++ b/MatrixSDK/JSONModels/MXRoomCreateContent.h @@ -0,0 +1,34 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXRoomPredecessorInfo.h" + +/** + A `MXRoomCreateContent` instance represents the content of a `m.room.create` event type. + */ +@interface MXRoomCreateContent : MXJSONModel + +/** + The `user_id` of the room creator. This is set by the homeserver. + */ +@property (nonatomic, copy, readonly, nonnull) NSString *creatorUserId; + +/** + Room predecessor information if the current room is a new version of an old room (that has a state event `m.room.tombstone`). + */ +@property (nonatomic, strong, readonly, nullable) MXRoomPredecessorInfo *roomPredecessorInfo; + +@end diff --git a/MatrixSDK/JSONModels/MXRoomCreateContent.m b/MatrixSDK/JSONModels/MXRoomCreateContent.m new file mode 100644 index 0000000000..4f67eb40b2 --- /dev/null +++ b/MatrixSDK/JSONModels/MXRoomCreateContent.m @@ -0,0 +1,76 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXRoomCreateContent.h" + +#pragma mark - Defines & Constants + +static NSString* const kRoomCreateContentUserIdJSONKey = @"creator"; +static NSString* const kRoomCreateContentPredecessorInfoJSONKey = @"predecessor"; + +#pragma mark - Private Interface + +@interface MXRoomCreateContent() + +@property (nonatomic, copy, readwrite, nonnull) NSString *creatorUserId; +@property (nonatomic, strong, readwrite, nullable) MXRoomPredecessorInfo *roomPredecessorInfo; + +@end + +@implementation MXRoomCreateContent + ++ (id)modelFromJSON:(NSDictionary *)jsonDictionary +{ + MXRoomCreateContent *roomCreateContent = nil; + + NSString *roomCreatorUserId; + MXJSONModelSetString(roomCreatorUserId, jsonDictionary[kRoomCreateContentUserIdJSONKey]); + + if (roomCreatorUserId) + { + roomCreateContent = [MXRoomCreateContent new]; + + MXRoomPredecessorInfo *roomPredecessorInfo = nil; + + NSDictionary *roomPredecessorJSON = jsonDictionary[kRoomCreateContentPredecessorInfoJSONKey]; + + if (roomPredecessorJSON) + { + roomPredecessorInfo = [MXRoomPredecessorInfo modelFromJSON:roomPredecessorJSON]; + } + + roomCreateContent.creatorUserId = roomCreatorUserId; + roomCreateContent.roomPredecessorInfo = roomPredecessorInfo; + } + + return roomCreateContent; +} + +- (NSDictionary *)JSONDictionary +{ + NSMutableDictionary *jsonDictionary = [NSMutableDictionary dictionary]; + + jsonDictionary[kRoomCreateContentUserIdJSONKey] = self.creatorUserId; + + if (self.roomPredecessorInfo) + { + jsonDictionary[kRoomCreateContentPredecessorInfoJSONKey] = [self.roomPredecessorInfo JSONDictionary]; + } + + return jsonDictionary; +} + +@end diff --git a/MatrixSDK/JSONModels/MXRoomPredecessorInfo.h b/MatrixSDK/JSONModels/MXRoomPredecessorInfo.h new file mode 100644 index 0000000000..842c07b943 --- /dev/null +++ b/MatrixSDK/JSONModels/MXRoomPredecessorInfo.h @@ -0,0 +1,36 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + +#import +#import "MXJSONModel.h" + +/** + A `MXRoomPredecessorInfo` instance represents the predecessor informations from the content of a `m.room.create` event type. + */ +@interface MXRoomPredecessorInfo : MXJSONModel + +/** + Room identifier of the previous version of the room. + */ +@property (nonatomic, copy, readonly, nonnull) NSString *roomId; + +/** + Event id of the `m.room.tombstone` event type in the previous version of the room. + */ +@property (nonatomic, copy, readonly, nonnull) NSString *tombStoneEventId; + +@end diff --git a/MatrixSDK/JSONModels/MXRoomPredecessorInfo.m b/MatrixSDK/JSONModels/MXRoomPredecessorInfo.m new file mode 100644 index 0000000000..fee5762ace --- /dev/null +++ b/MatrixSDK/JSONModels/MXRoomPredecessorInfo.m @@ -0,0 +1,67 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXRoomPredecessorInfo.h" + +#pragma mark - Defines & Constants + +static NSString* const kRoomPredecessorRoomIdJSONKey = @"room_id"; +static NSString* const kRoomPredecessorTombstoneEventIdJSONKey = @"event_id"; + +#pragma mark - Private Interface + +@interface MXRoomPredecessorInfo() + +@property (nonatomic, copy, readwrite, nonnull) NSString *roomId; +@property (nonatomic, copy, readwrite, nonnull) NSString *tombStoneEventId; + +@end + +@implementation MXRoomPredecessorInfo + +#pragma mark - MXJSONModel + ++ (id)modelFromJSON:(NSDictionary *)jsonDictionary +{ + MXRoomPredecessorInfo *tombStoneContent = nil; + + NSString *roomId; + NSString *tombStoneEventId; + + MXJSONModelSetString(roomId, jsonDictionary[kRoomPredecessorRoomIdJSONKey]); + MXJSONModelSetString(tombStoneEventId, jsonDictionary[kRoomPredecessorTombstoneEventIdJSONKey]); + + if (roomId && tombStoneEventId) + { + tombStoneContent = [MXRoomPredecessorInfo new]; + tombStoneContent.roomId = roomId; + tombStoneContent.tombStoneEventId = tombStoneEventId; + } + + return tombStoneContent; +} + +- (NSDictionary *)JSONDictionary +{ + NSMutableDictionary *jsonDictionary = [NSMutableDictionary dictionary]; + + jsonDictionary[kRoomPredecessorRoomIdJSONKey] = self.roomId; + jsonDictionary[kRoomPredecessorTombstoneEventIdJSONKey] = self.tombStoneEventId; + + return jsonDictionary; +} + +@end diff --git a/MatrixSDK/JSONModels/MXRoomTombStoneContent.h b/MatrixSDK/JSONModels/MXRoomTombStoneContent.h new file mode 100644 index 0000000000..0d3e4f3401 --- /dev/null +++ b/MatrixSDK/JSONModels/MXRoomTombStoneContent.h @@ -0,0 +1,35 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import +#import "MXJSONModel.h" + +/** + A `MXRoomTombStoneContent` instance represents the content of a `m.room.tombstone` event type. + */ +@interface MXRoomTombStoneContent : MXJSONModel + +/** + The reason message for the obsolence of the room. + */ +@property (nonatomic, copy, readonly, nonnull) NSString *body; + +/** + The identifier of the room that comes in replacement. + */ +@property (nonatomic, copy, readonly, nonnull) NSString *replacementRoomId; + +@end diff --git a/MatrixSDK/JSONModels/MXRoomTombStoneContent.m b/MatrixSDK/JSONModels/MXRoomTombStoneContent.m new file mode 100644 index 0000000000..48009b9836 --- /dev/null +++ b/MatrixSDK/JSONModels/MXRoomTombStoneContent.m @@ -0,0 +1,69 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXRoomTombStoneContent.h" + +#pragma mark - Defines & Constants + +static NSString* const kTombStoneContentBodyJSONKey = @"body"; +static NSString* const kTombStoneContentReplacementRoomIdJSONKey = @"replacement_room"; + +#pragma mark - Private Interface + +@interface MXRoomTombStoneContent() + +@property (nonatomic, copy, readwrite, nonnull) NSString *body; +@property (nonatomic, copy, readwrite, nonnull) NSString *replacementRoomId; + +@end + +#pragma mark - Implementation + +@implementation MXRoomTombStoneContent + +#pragma mark - MXJSONModel + ++ (id)modelFromJSON:(NSDictionary *)jsonDictionary +{ + MXRoomTombStoneContent *tombStoneContent = nil; + + NSString *body; + NSString *replacementRoomId; + + MXJSONModelSetString(body, jsonDictionary[kTombStoneContentBodyJSONKey]); + MXJSONModelSetString(replacementRoomId, jsonDictionary[kTombStoneContentReplacementRoomIdJSONKey]); + + if (body && replacementRoomId) + { + tombStoneContent = [MXRoomTombStoneContent new]; + tombStoneContent.body = body; + tombStoneContent.replacementRoomId = replacementRoomId; + } + + return tombStoneContent; +} + +- (NSDictionary *)JSONDictionary +{ + NSMutableDictionary *jsonDictionary = [NSMutableDictionary dictionary]; + + jsonDictionary[kTombStoneContentBodyJSONKey] = self.body; + jsonDictionary[kTombStoneContentReplacementRoomIdJSONKey] = self.replacementRoomId; + + return jsonDictionary; +} + +@end diff --git a/MatrixSDK/MXRestClient.h b/MatrixSDK/MXRestClient.h index 81ea32c4c9..4f2793f682 100644 --- a/MatrixSDK/MXRestClient.h +++ b/MatrixSDK/MXRestClient.h @@ -1,6 +1,7 @@ /* Copyright 2014 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -29,6 +30,7 @@ #import "MXInvite3PID.h" #import "MXEventTimeline.h" #import "MXJSONModels.h" +#import "MXFilterJSONModel.h" #pragma mark - Constants definitions @@ -410,6 +412,39 @@ typedef enum : NSUInteger success:(void (^)(void))success failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; + +#pragma mark - Filtering +/** + Uploads a new filter definition to the homeserver. + + @param filter the filter to set. + + @param success A block object called when the operation succeeds. It provides the + id of the created filter. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)setFilter:(MXFilterJSONModel*)filter + success:(void (^)(NSString *filterId))success + failure:(void (^)(NSError *error))failure; + +/** + Download a filter. + + @param filterId The filter id to download. + + @param success A block object called when the operation succeeds. It provides the + filter object. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)getFilterWithFilterId:(NSString*)filterId + success:(void (^)(MXFilterJSONModel *filter))success + failure:(void (^)(NSError *error))failure; + + /** Gets a bearer token from the homeserver that the user can present to a third party in order to prove their ownership @@ -423,6 +458,7 @@ typedef enum : NSUInteger - (MXHTTPOperation*)openIdToken:(void (^)(MXOpenIdToken *tokenObject))success failure:(void (^)(NSError *error))failure; + #pragma mark - 3pid token request /** Request the validation of an email address (like `requestEmailValidation` in identity server API), @@ -1118,7 +1154,7 @@ typedef enum : NSUInteger @param from the token to start getting results from. @param direction `MXTimelineDirectionForwards` or `MXTimelineDirectionBackwards` @param limit (optional, use -1 to not defined this value) the maximum number of messages to return. - @param roomEventFilter to filter returned events with. + @param roomEventFilter the filter to pass in the request. Can be nil. @param success A block object called when the operation succeeds. It provides a `MXPaginationResponse` object. @param failure A block object called when the operation fails. @@ -1278,6 +1314,7 @@ typedef enum : NSUInteger @param eventId the id of the event to get context around. @param roomId the id of the room to get events from. @param limit the maximum number of messages to return. + @param filter the filter to pass in the request. Can be nil. @param success A block object called when the operation succeeds. It provides the model created from the homeserver JSON response. @@ -1288,6 +1325,7 @@ typedef enum : NSUInteger - (MXHTTPOperation*)contextOfEvent:(NSString*)eventId inRoom:(NSString*)roomId limit:(NSUInteger)limit + filter:(MXRoomEventFilter*)filter success:(void (^)(MXEventContext *eventContext))success failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; @@ -1832,7 +1870,7 @@ typedef enum : NSUInteger Search a text in room messages. @param textPattern the text to search for in message body. - @param roomEventFilter a nullable dictionary which defines the room event filtering during the search request. + @param roomEventFilter the filter to pass in the request. Can be nil. @param beforeLimit the number of events to get before the matching results. @param afterLimit the number of events to get after the matching results. @param nextBatch the token to pass for doing pagination from a previous response. diff --git a/MatrixSDK/MXRestClient.m b/MatrixSDK/MXRestClient.m index fdc99670c7..ad05cc3ac0 100644 --- a/MatrixSDK/MXRestClient.m +++ b/MatrixSDK/MXRestClient.m @@ -751,6 +751,67 @@ - (MXHTTPOperation*)setAccountData:(NSDictionary*)data }]; } + +#pragma mark - Filtering + +- (MXHTTPOperation*)setFilter:(MXFilterJSONModel*)filter + success:(void (^)(NSString *filterId))success + failure:(void (^)(NSError *error))failure +{ + NSString *path = [NSString stringWithFormat:@"%@/user/%@/filter", apiPathPrefix, credentials.userId]; + + MXWeakify(self); + return [httpClient requestWithMethod:@"POST" + path:path + parameters:filter.JSONDictionary + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + + if (success) + { + __block NSString *filterId; + [self dispatchProcessing:^{ + MXJSONModelSetString(filterId, JSONResponse[@"filter_id"]); + } andCompletion:^{ + success(filterId); + }]; + } + } + failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (MXHTTPOperation*)getFilterWithFilterId:(NSString*)filterId + success:(void (^)(MXFilterJSONModel *filter))success + failure:(void (^)(NSError *error))failure +{ + NSString *path = [NSString stringWithFormat:@"%@/user/%@/filter/%@", apiPathPrefix, credentials.userId, filterId]; + + MXWeakify(self); + return [httpClient requestWithMethod:@"GET" + path:path + parameters:nil + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + + if (success) + { + __block MXFilterJSONModel *filter; + [self dispatchProcessing:^{ + MXJSONModelSetMXJSONModel(filter, MXFilterJSONModel, JSONResponse); + } andCompletion:^{ + success(filter); + }]; + } + } + failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + - (MXHTTPOperation *)openIdToken:(void (^)(MXOpenIdToken *))success failure:(void (^)(NSError *))failure { NSString *path = [NSString stringWithFormat:@"%@/user/%@/openid/request_token", kMXAPIPrefixPathUnstable, credentials.userId]; @@ -2018,7 +2079,7 @@ - (MXHTTPOperation*)messagesForRoom:(NSString*)roomId if (roomEventFilter.dictionary.count) { - parameters[@"filter"] = roomEventFilter.dictionary; + parameters[@"filter"] = roomEventFilter.jsonString; } MXWeakify(self); @@ -2296,7 +2357,7 @@ - (MXHTTPOperation*)eventWithEventId:(NSString*)eventId { NSLog(@"[MXRestClient] eventWithEventId: The homeserver does not support `/rooms/{roomId}/event/{eventId}` API. Try with `/context`"); - MXHTTPOperation *operation2 = [self contextOfEvent:eventId inRoom:roomId limit:1 success:^(MXEventContext *eventContext) { + MXHTTPOperation *operation2 = [self contextOfEvent:eventId inRoom:roomId limit:1 filter:nil success:^(MXEventContext *eventContext) { if (success) { @@ -2317,17 +2378,24 @@ - (MXHTTPOperation*)eventWithEventId:(NSString*)eventId - (MXHTTPOperation*)contextOfEvent:(NSString*)eventId inRoom:(NSString*)roomId limit:(NSUInteger)limit + filter:(MXRoomEventFilter*)filter success:(void (^)(MXEventContext *eventContext))success failure:(void (^)(NSError *error))failure { NSString *path = [NSString stringWithFormat:@"%@/rooms/%@/context/%@", apiPathPrefix, roomId, eventId]; + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"limit"] = @(limit); + + if (filter) + { + parameters[@"filter"] = filter.jsonString; + } + MXWeakify(self); return [httpClient requestWithMethod:@"GET" path:path - parameters:@{ - @"limit": [NSNumber numberWithInteger:limit] - } + parameters:parameters success:^(NSDictionary *JSONResponse) { MXStrongifyAndReturnIfNil(self); diff --git a/MatrixSDK/MXSession.h b/MatrixSDK/MXSession.h index 5331c77ee4..fb1f53316c 100644 --- a/MatrixSDK/MXSession.h +++ b/MatrixSDK/MXSession.h @@ -21,6 +21,7 @@ #import "MXRestClient.h" #import "MXRoom.h" #import "MXPeekingRoom.h" +#import "MXFilterJSONModel.h" #import "MXMyUser.h" #import "MXAccountData.h" #import "MXSessionEventListener.h" @@ -346,6 +347,16 @@ FOUNDATION_EXPORT NSString *const kMXSessionNoRoomTag; */ @property (nonatomic, readonly) BOOL catchingUp; +/** + The id of the filter being used in /sync requests. + */ +@property (nonatomic, readonly) NSString *syncFilterId; + +/** + True when the filter used for /sync requests enables lazy loading of room members. + */ +@property (nonatomic, readonly) BOOL syncWithLazyLoadOfRoomMembers; + /** The profile of the current user. It is available only after the `onStoreDataReady` callback of `start` is called. @@ -413,18 +424,26 @@ FOUNDATION_EXPORT NSString *const kMXSessionNoRoomTag; failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; /** - Start the session like `[MXSession start]` but preload the requested number of messages - for each user's rooms. + Start the session like `[MXSession start]` but with using a filter in /sync requests. + + @param syncFilter the filter to use. + @param onServerSyncDone A block object called when the data is up-to-date with the server. + @param failure A block object called when the operation fails. + */ +- (void)startWithSyncFilter:(MXFilterJSONModel*)syncFilter + onServerSyncDone:(void (^)(void))onServerSyncDone + failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; - By default, [MXSession start] preloads 10 messages. Use this method to use a custom limit. +/** + Start the session like `[MXSession startWithSyncFilter]` but with a filter id. - @param messagesLimit the number of messages to retrieve in each room. + @param syncFilterId the id of the filter to use. @param onServerSyncDone A block object called when the data is up-to-date with the server. @param failure A block object called when the operation fails. */ -- (void)startWithMessagesLimit:(NSUInteger)messagesLimit - onServerSyncDone:(void (^)(void))onServerSyncDone - failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; +- (void)startWithSyncFilterId:(NSString*)syncFilterId + onServerSyncDone:(void (^)(void))onServerSyncDone + failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; /** Pause the session events stream. @@ -757,6 +776,17 @@ typedef void (^MXOnBackgroundSyncFail)(NSError *error); - (MXHTTPOperation*)uploadDirectRooms:(void (^)(void))success failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; +/** + Make sure that the `MXRoom` internal data for a list of rooms is preloaded. + + Thus, next async calls to their internal data (like [MXRoom liveTimeline:]) + will behave synchronously. + + @param roomIds ids of rooms to preload data. + @param onComplete block called once done. + */ +- (void)preloadRoomsData:(NSArray *)roomIds onComplete:(dispatch_block_t)onComplete; + #pragma mark - Rooms summaries /** @@ -1107,6 +1137,38 @@ typedef void (^MXOnBackgroundSyncFail)(NSError *error); failure:(void (^)(NSError *error))failure; +#pragma mark - Matrix filters +/** + Set a Matrix filter. + + If the filter has been already created, this will return the stored filter id. + + @param filter the Matrix filter. + + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)setFilter:(MXFilterJSONModel*)filter + success:(void (^)(NSString *filterId))success + failure:(void (^)(NSError *error))failure; + +/** + Retrieve a Matrix filter either from the store of from the homeserver. + + @param filterId the id of the Matrix filter to retrieve. + + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)filterWithFilterId:(NSString*)filterId + success:(void (^)(MXFilterJSONModel *filter))success + failure:(void (^)(NSError *error))failure; + + #pragma mark - Crypto /** Decrypt an event and update its data. diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index 27dcf2548b..a2dd7c9f6b 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -42,7 +42,7 @@ #pragma mark - Constants definitions -const NSString *MatrixSDKVersion = @"0.10.12"; +const NSString *MatrixSDKVersion = @"0.11.0"; NSString *const kMXSessionStateDidChangeNotification = @"kMXSessionStateDidChangeNotification"; NSString *const kMXSessionNewRoomNotification = @"kMXSessionNewRoomNotification"; NSString *const kMXSessionWillLeaveRoomNotification = @"kMXSessionWillLeaveRoomNotification"; @@ -106,12 +106,6 @@ The list of global events listeners (`MXSessionEventListener`). */ NSMutableArray *globalEventListeners; - /** - The limit value to use when doing /sync requests. - -1, the default value, let the homeserver use its default value. - */ - NSInteger syncMessagesLimit; - /** The block to call when MSSession resume is complete. */ @@ -186,7 +180,6 @@ - (id)initWithMatrixRestClient:(MXRestClient*)mxRestClient _roomSummaryUpdateDelegate = [MXRoomSummaryUpdater roomSummaryUpdaterForSession:self]; _directRooms = [NSMutableDictionary dictionary]; globalEventListeners = [NSMutableArray array]; - syncMessagesLimit = -1; _notificationCenter = [[MXNotificationCenter alloc] initWithMatrixSession:self]; _accountData = [[MXAccountData alloc] init]; peekingRooms = [NSMutableArray array]; @@ -267,7 +260,7 @@ -(void)setStore:(id)store success:(void (^)(void))onStoreDataReady fail // A permanent MXStore must implement these methods: NSParameterAssert([_store respondsToSelector:@selector(rooms)]); NSParameterAssert([_store respondsToSelector:@selector(storeStateForRoom:stateEvents:)]); - NSParameterAssert([_store respondsToSelector:@selector(stateOfRoom:)]); + NSParameterAssert([_store respondsToSelector:@selector(stateOfRoom:success:failure:)]); NSParameterAssert([_store respondsToSelector:@selector(summaryOfRoom:)]); } @@ -330,12 +323,7 @@ -(void)setStore:(id)store success:(void (^)(void))onStoreDataReady fail NSDate *startDate3 = [NSDate date]; for (NSString *roomId in self.store.rooms) { - @autoreleasepool - { - NSArray *stateEvents = [self.store stateOfRoom:roomId]; - MXRoomAccountData *roomAccountData = [self.store accountDataOfRoom:roomId]; - [self createRoom:roomId withStateEvents:stateEvents andAccountData:roomAccountData notify:NO]; - } + [self loadRoom:roomId]; } NSLog(@"[MXSession] Built %lu MXRooms in %.0fms", (unsigned long)self->rooms.allKeys.count, [[NSDate date] timeIntervalSinceDate:startDate3] * 1000); @@ -380,12 +368,29 @@ -(void)setStore:(id)store success:(void (^)(void))onStoreDataReady fail - (void)start:(void (^)(void))onServerSyncDone failure:(void (^)(NSError *error))failure { - [self startWithMessagesLimit:-1 onServerSyncDone:onServerSyncDone failure:failure]; + [self startWithSyncFilter:nil onServerSyncDone:onServerSyncDone failure:failure]; } -- (void)startWithMessagesLimit:(NSUInteger)messagesLimit - onServerSyncDone:(void (^)(void))onServerSyncDone - failure:(void (^)(NSError *error))failure +- (void)startWithSyncFilter:(MXFilterJSONModel*)syncFilter + onServerSyncDone:(void (^)(void))onServerSyncDone + failure:(void (^)(NSError *error))failure; +{ + // Build or retrieve the filter before launching the event stream + MXWeakify(self); + [self setFilter:syncFilter success:^(NSString *filterId) { + MXStrongifyAndReturnIfNil(self); + + [self startWithSyncFilterId:filterId onServerSyncDone:onServerSyncDone failure:failure]; + + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + + NSLog(@"[MXSesssion] startWithSyncFilter: WARNING: Impossible to create the filter. Use no filter in /sync"); + [self startWithSyncFilterId:nil onServerSyncDone:onServerSyncDone failure:failure]; + }]; +} + +- (void)startWithSyncFilterId:(NSString *)syncFilterId onServerSyncDone:(void (^)(void))onServerSyncDone failure:(void (^)(NSError *))failure { if (nil == _store) { @@ -398,7 +403,7 @@ - (void)startWithMessagesLimit:(NSUInteger)messagesLimit MXStrongifyAndReturnIfNil(self); // Then, start again - [self startWithMessagesLimit:messagesLimit onServerSyncDone:onServerSyncDone failure:failure]; + [self startWithSyncFilterId:syncFilterId onServerSyncDone:onServerSyncDone failure:failure]; } failure:^(NSError *error) { MXStrongifyAndReturnIfNil(self); @@ -412,8 +417,32 @@ - (void)startWithMessagesLimit:(NSUInteger)messagesLimit [self setState:MXSessionStateSyncInProgress]; - // Store the passed limit to reuse it when initialSyncing per room - syncMessagesLimit = messagesLimit; + // Check update of the filter used for /sync requests + if (_store.syncFilterId != syncFilterId + && ![_store.syncFilterId isEqualToString:syncFilterId]) + { + if (_store.eventStreamToken) + { + NSLog(@"[MXSesssion] startWithSyncFilterId: WARNING: Changing the sync filter while there is existing data in the store is not recommended"); + } + + // Store the passed filter id + _store.syncFilterId = syncFilterId; + } + + // Determine if this filter implies lazy loading of room members + if (syncFilterId) + { + MXWeakify(self); + [self filterWithFilterId:syncFilterId success:^(MXFilterJSONModel *filter) { + MXStrongifyAndReturnIfNil(self); + + if (filter.room.state.lazyLoadMembers) + { + self->_syncWithLazyLoadOfRoomMembers = YES; + } + } failure:nil]; + } // Can we resume from data available in the cache if (_store.isPermanent && self.isEventStreamInitialised && 0 < _store.rooms.count) @@ -513,6 +542,11 @@ - (void)startWithMessagesLimit:(NSUInteger)messagesLimit } } +- (NSString *)syncFilterId +{ + return _store.syncFilterId; +} + - (void)pause { NSLog(@"[MXSession] pause the event stream in state %tu", _state); @@ -672,7 +706,7 @@ - (void)close // Clean MXRooms for (MXRoom *room in rooms.allValues) { - [room.liveTimeline removeAllListeners]; + [room close]; } [rooms removeAllObjects]; @@ -844,27 +878,10 @@ - (void)serverSyncWithServerTimeout:(NSUInteger)serverTimeout NSLog(@"[MXSession] Do a server sync%@", _catchingUp ? @" (catching up)" : @""); - // If required, define a filter - MXRoomFilter *roomFilter = [[MXRoomFilter alloc] init]; - if (-1 != syncMessagesLimit) - { - // If requested by the app, use a limit for the timeline in /sync - MXRoomEventFilter *timelineFilter = [[MXRoomEventFilter alloc] init]; - timelineFilter.limit = syncMessagesLimit; - roomFilter.timeline = timelineFilter; - } - - NSString *inlineFilter; - if (roomFilter.dictionary.count) - { - // Serialise the filter to pass it inline in the request - inlineFilter = [NSString stringWithFormat:@"{\"room\":%@}", roomFilter.jsonString]; - } - MXWeakify(self); - eventStreamRequest = [matrixRestClient syncFromToken:_store.eventStreamToken serverTimeout:serverTimeout clientTimeout:clientTimeout setPresence:setPresence filter:inlineFilter success:^(MXSyncResponse *syncResponse) { + eventStreamRequest = [matrixRestClient syncFromToken:_store.eventStreamToken serverTimeout:serverTimeout clientTimeout:clientTimeout setPresence:setPresence filter:self.syncFilterId success:^(MXSyncResponse *syncResponse) { MXStrongifyAndReturnIfNil(self); - + // Make sure [MXSession close] or [MXSession pause] has not been called before the server response if (!self->eventStreamRequest) { @@ -888,13 +905,6 @@ - (void)serverSyncWithServerTimeout:(NSUInteger)serverTimeout [[MXSDKOptions sharedInstance].analyticsDelegate trackStartupSyncDuration:duration isInitial:isInitialSync]; } - // Handle top-level account data - self->didDirectRoomsChange = NO; - if (syncResponse.accountData) - { - [self handleAccountData:syncResponse.accountData]; - } - // Handle the to device events before the room ones // to ensure to decrypt them properly for (MXEvent *toDeviceEvent in syncResponse.toDevice.events) @@ -909,51 +919,54 @@ - (void)serverSyncWithServerTimeout:(NSUInteger)serverTimeout NSLog(@"[MXSession] Continue /sync with short timeout to get all to-device events (%@)", self.myUser.userId); nextServerTimeout = 0; } - + // Handle first joined rooms for (NSString *roomId in syncResponse.rooms.join) { MXRoomSync *roomSync = syncResponse.rooms.join[roomId]; - + @autoreleasepool { - + // Retrieve existing room or create a new one MXRoom *room = [self getOrCreateRoom:roomId notify:!isInitialSync]; - - // Sync room - [room handleJoinedRoomSync:roomSync]; - [room.summary handleJoinedRoomSync:roomSync]; + // Sync room + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [room handleJoinedRoomSync:roomSync]; + [room.summary handleJoinedRoomSync:roomSync]; + }]; } } - + // Handle invited rooms for (NSString *roomId in syncResponse.rooms.invite) { MXInvitedRoomSync *invitedRoomSync = syncResponse.rooms.invite[roomId]; - + @autoreleasepool { - + // Retrieve existing room or create a new one MXRoom *room = [self getOrCreateRoom:roomId notify:!isInitialSync]; - + // Prepare invited room - [room handleInvitedRoomSync:invitedRoomSync]; - [room.summary handleInvitedRoomSync:invitedRoomSync]; + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [room handleInvitedRoomSync:invitedRoomSync]; + [room.summary handleInvitedRoomSync:invitedRoomSync]; + }]; } } - + // Handle archived rooms for (NSString *roomId in syncResponse.rooms.leave) { MXRoomSync *leftRoomSync = syncResponse.rooms.leave[roomId]; - + @autoreleasepool { - + // Presently we remove the existing room from the rooms list. // FIXME SYNCV2 Archive/Display the left rooms! // For that create 'handleArchivedRoomSync' method - + // Retrieve existing room MXRoom *room = [self roomWithRoomId:roomId]; if (room) @@ -961,38 +974,40 @@ - (void)serverSyncWithServerTimeout:(NSUInteger)serverTimeout // FIXME SYNCV2: While 'handleArchivedRoomSync' is not available, // use 'handleJoinedRoomSync' to pass the last events to the room before leaving it. // The room will then able to notify its listeners. - [room handleJoinedRoomSync:leftRoomSync]; - [room.summary handleJoinedRoomSync:leftRoomSync]; + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [room handleJoinedRoomSync:leftRoomSync]; + [room.summary handleJoinedRoomSync:leftRoomSync]; + + // Look for the last room member event + MXEvent *roomMemberEvent; + NSInteger index = leftRoomSync.timeline.events.count; + while (index--) + { + MXEvent *event = leftRoomSync.timeline.events[index]; - // Look for the last room member event - MXEvent *roomMemberEvent; - NSInteger index = leftRoomSync.timeline.events.count; - while (index--) - { - MXEvent *event = leftRoomSync.timeline.events[index]; - - if ([event.type isEqualToString:kMXEventTypeStringRoomMember]) + if ([event.type isEqualToString:kMXEventTypeStringRoomMember]) + { + roomMemberEvent = event; + break; + } + } + + // Notify the room is going to disappear + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:room.roomId forKey:kMXSessionNotificationRoomIdKey]; + if (roomMemberEvent) { - roomMemberEvent = event; - break; + userInfo[kMXSessionNotificationEventKey] = roomMemberEvent; } - } - - // Notify the room is going to disappear - NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:room.state.roomId forKey:kMXSessionNotificationRoomIdKey]; - if (roomMemberEvent) - { - userInfo[kMXSessionNotificationEventKey] = roomMemberEvent; - } - [[NSNotificationCenter defaultCenter] postNotificationName:kMXSessionWillLeaveRoomNotification - object:self - userInfo:userInfo]; - // Remove the room from the rooms list - [self removeRoom:room.state.roomId]; + [[NSNotificationCenter defaultCenter] postNotificationName:kMXSessionWillLeaveRoomNotification + object:self + userInfo:userInfo]; + // Remove the room from the rooms list + [self removeRoom:room.roomId]; + }]; } } } - + // Handle invited groups for (NSString *groupId in syncResponse.groups.invite) { @@ -1000,159 +1015,171 @@ - (void)serverSyncWithServerTimeout:(NSUInteger)serverTimeout MXInvitedGroupSync *invitedGroupSync = syncResponse.groups.invite[groupId]; [self createGroupInviteWithId:groupId profile:invitedGroupSync.profile andInviter:invitedGroupSync.inviter notify:!isInitialSync]; } - + // Handle joined groups for (NSString *groupId in syncResponse.groups.join) { // Join an existing group or create a new one [self didJoinGroupWithId:groupId notify:!isInitialSync]; } - + // Handle left groups for (NSString *groupId in syncResponse.groups.leave) { // Remove the group from the group list [self removeGroup:groupId]; } - + // Handle presence of other users for (MXEvent *presenceEvent in syncResponse.presence.events) { [self handlePresenceEvent:presenceEvent direction:MXTimelineDirectionForwards]; } - - if (self->didDirectRoomsChange) - { - [self updateDirectRoomsData]; - + + // Sync point: wait that all rooms in the /sync response have been loaded + // and their /sync response has been processed + [self preloadRoomsData:[self roomsInSyncResponse:syncResponse] onComplete:^{ + + // Handle top-level account data self->didDirectRoomsChange = NO; - - [[NSNotificationCenter defaultCenter] postNotificationName:kMXSessionDirectRoomsDidChangeNotification - object:self - userInfo:nil]; - } + if (syncResponse.accountData) + { + [self handleAccountData:syncResponse.accountData]; + } - if (self.crypto) - { - // Handle device list updates - if (syncResponse.deviceLists) + if (self->didDirectRoomsChange) { - [self.crypto handleDeviceListsChanges:syncResponse.deviceLists]; + [self updateDirectRoomsData]; + + self->didDirectRoomsChange = NO; + + [[NSNotificationCenter defaultCenter] postNotificationName:kMXSessionDirectRoomsDidChangeNotification + object:self + userInfo:nil]; } - // Handle one_time_keys_count - if (syncResponse.deviceOneTimeKeysCount) + if (self.crypto) { - [self.crypto handleDeviceOneTimeKeysCount:syncResponse.deviceOneTimeKeysCount]; + // Handle device list updates + if (syncResponse.deviceLists) + { + [self.crypto handleDeviceListsChanges:syncResponse.deviceLists]; + } + + // Handle one_time_keys_count + if (syncResponse.deviceOneTimeKeysCount) + { + [self.crypto handleDeviceOneTimeKeysCount:syncResponse.deviceOneTimeKeysCount]; + } + + // Tell the crypto module to do its processing + [self.crypto onSyncCompleted:self.store.eventStreamToken + nextSyncToken:syncResponse.nextBatch + catchingUp:self.catchingUp]; } - // Tell the crypto module to do its processing - [self.crypto onSyncCompleted:self.store.eventStreamToken - nextSyncToken:syncResponse.nextBatch - catchingUp:self.catchingUp]; - } + // Update live event stream token + self.store.eventStreamToken = syncResponse.nextBatch; - // Update live event stream token - self.store.eventStreamToken = syncResponse.nextBatch; - - // Commit store changes done in [room handleMessages] - if ([self.store respondsToSelector:@selector(commit)]) - { - [self.store commit]; - } + // Commit store changes done in [room handleMessages] + if ([self.store respondsToSelector:@selector(commit)]) + { + [self.store commit]; + } - // Do a loop of /syncs until catching up is done - if (nextServerTimeout == 0) - { - [self serverSyncWithServerTimeout:nextServerTimeout success:success failure:failure clientTimeout:CLIENT_TIMEOUT_MS setPresence:nil]; - return; - } - - // there is a pending backgroundSync - if (self->onBackgroundSyncDone) - { - NSLog(@"[MXSession] Events stream background Sync succeeded"); - - // Operations on session may occur during this block. For example, [MXSession close] may be triggered. - // We run a copy of the block to prevent app from crashing if the block is released by one of these operations. - MXOnBackgroundSyncDone onBackgroundSyncDoneCpy = [self->onBackgroundSyncDone copy]; - onBackgroundSyncDoneCpy(); - self->onBackgroundSyncDone = nil; - - // check that the application was not resumed while catching up in background - if (self.state == MXSessionStateBackgroundSyncInProgress) + // Do a loop of /syncs until catching up is done + if (nextServerTimeout == 0) { - // Check that none required the session to keep running - if (self.preventPauseCount) + [self serverSyncWithServerTimeout:nextServerTimeout success:success failure:failure clientTimeout:CLIENT_TIMEOUT_MS setPresence:nil]; + return; + } + + // there is a pending backgroundSync + if (self->onBackgroundSyncDone) + { + NSLog(@"[MXSession] Events stream background Sync succeeded"); + + // Operations on session may occur during this block. For example, [MXSession close] may be triggered. + // We run a copy of the block to prevent app from crashing if the block is released by one of these operations. + MXOnBackgroundSyncDone onBackgroundSyncDoneCpy = [self->onBackgroundSyncDone copy]; + onBackgroundSyncDoneCpy(); + self->onBackgroundSyncDone = nil; + + // check that the application was not resumed while catching up in background + if (self.state == MXSessionStateBackgroundSyncInProgress) { - // Delay the pause by calling the reliable `pause` method. - [self pause]; + // Check that none required the session to keep running + if (self.preventPauseCount) + { + // Delay the pause by calling the reliable `pause` method. + [self pause]; + } + else + { + NSLog(@"[MXSession] go to paused "); + self->eventStreamRequest = nil; + [self setState:MXSessionStatePaused]; + return; + } } else { - NSLog(@"[MXSession] go to paused "); - self->eventStreamRequest = nil; - [self setState:MXSessionStatePaused]; + NSLog(@"[MXSession] resume after a background Sync"); + } + } + + // If we are resuming inform the app that it received the last uptodate data + if (self->onResumeDone) + { + NSLog(@"[MXSession] Events stream resumed"); + + // Operations on session may occur during this block. For example, [MXSession close] or [MXSession pause] may be triggered. + // We run a copy of the block to prevent app from crashing if the block is released by one of these operations. + MXOnResumeDone onResumeDoneCpy = [self->onResumeDone copy]; + onResumeDoneCpy(); + self->onResumeDone = nil; + + // Stop here if [MXSession close] or [MXSession pause] has been triggered during onResumeDone block. + if (nil == self.myUser || self.state == MXSessionStatePaused) + { return; } } - else + + if (self.state != MXSessionStatePauseRequested) { - NSLog(@"[MXSession] resume after a background Sync"); + // The event stream is running by now + [self setState:MXSessionStateRunning]; } - } - - // If we are resuming inform the app that it received the last uptodate data - if (self->onResumeDone) - { - NSLog(@"[MXSession] Events stream resumed"); - - // Operations on session may occur during this block. For example, [MXSession close] or [MXSession pause] may be triggered. - // We run a copy of the block to prevent app from crashing if the block is released by one of these operations. - MXOnResumeDone onResumeDoneCpy = [self->onResumeDone copy]; - onResumeDoneCpy(); - self->onResumeDone = nil; - - // Stop here if [MXSession close] or [MXSession pause] has been triggered during onResumeDone block. + + // Check SDK user did not called [MXSession close] or [MXSession pause] during the session state change notification handling. if (nil == self.myUser || self.state == MXSessionStatePaused) { return; } - } - - if (self.state != MXSessionStatePauseRequested) - { - // The event stream is running by now - [self setState:MXSessionStateRunning]; - } - // Check SDK user did not called [MXSession close] or [MXSession pause] during the session state change notification handling. - if (nil == self.myUser || self.state == MXSessionStatePaused) - { - return; - } - - // Pursue live events listening - [self serverSyncWithServerTimeout:nextServerTimeout success:nil failure:nil clientTimeout:CLIENT_TIMEOUT_MS setPresence:nil]; + // Pursue live events listening + [self serverSyncWithServerTimeout:nextServerTimeout success:nil failure:nil clientTimeout:CLIENT_TIMEOUT_MS setPresence:nil]; - if (wasfirstSync) - { - [[MXSDKOptions sharedInstance].analyticsDelegate trackRoomCount:self->rooms.count]; - } + if (wasfirstSync) + { + [[MXSDKOptions sharedInstance].analyticsDelegate trackRoomCount:self->rooms.count]; + } + + // Broadcast that a server sync has been processed. + [[NSNotificationCenter defaultCenter] postNotificationName:kMXSessionDidSyncNotification + object:self + userInfo:@{ + kMXSessionNotificationSyncResponseKey: syncResponse + }]; + + if (success) + { + success(); + } + }]; - // Broadcast that a server sync has been processed. - [[NSNotificationCenter defaultCenter] postNotificationName:kMXSessionDidSyncNotification - object:self - userInfo:@{ - kMXSessionNotificationSyncResponseKey: syncResponse - }]; - - if (success) - { - success(); - } - } failure:^(NSError *error) { MXStrongifyAndReturnIfNil(self); @@ -1419,6 +1446,23 @@ - (void)updateDirectRoomsData } } +/** + Get rooms implied in a /sync response. + + @param syncResponse the /sync response to parse. + @return ids of rooms found in the /sync response. + */ +- (NSArray *)roomsInSyncResponse:(MXSyncResponse *)syncResponse +{ + NSMutableArray *roomsInSyncResponse = [NSMutableArray array]; + [roomsInSyncResponse addObjectsFromArray:syncResponse.rooms.join.allKeys]; + [roomsInSyncResponse addObjectsFromArray:syncResponse.rooms.invite.allKeys]; + [roomsInSyncResponse addObjectsFromArray:syncResponse.rooms.leave.allKeys]; + + return roomsInSyncResponse; +} + + #pragma mark - Options - (void)enableVoIPWithCallStack:(id)callStack { @@ -1496,7 +1540,7 @@ - (void)onCreatedRoom:(MXCreateRoomResponse*)response success:(void (^)(MXRoom * MXRoom *room = note.object; - if ([room.state.roomId isEqualToString:response.roomId]) + if ([room.roomId isEqualToString:response.roomId]) { // Initialise notification counters homeserver side [room markAllAsRead]; @@ -1543,7 +1587,7 @@ - (void)onCreatedDirectChat:(MXCreateRoomResponse*)response withUserId:(NSString MXRoom *room = note.object; - if ([room.state.roomId isEqualToString:response.roomId]) + if ([room.roomId isEqualToString:response.roomId]) { // Initialise notification counters homeserver side [room markAllAsRead]; @@ -1652,14 +1696,14 @@ - (void)onJoinedRoom:(NSString*)roomId success:(void (^)(MXRoom *room))success [[NSNotificationCenter defaultCenter] postNotificationName:kMXSessionInvitedRoomsDidChangeNotification object:self userInfo:@{ - kMXSessionNotificationRoomIdKey: room.state.roomId, + kMXSessionNotificationRoomIdKey: room.roomId, }]; } // Wait to receive data from /sync about this room before returning if (success) { - if (room.state.membership == MXMembershipJoin) + if (room.summary.membership == MXMembershipJoin) { // The /sync corresponding to this join may have happened before the // homeserver answer to the joinRoom request. @@ -1779,11 +1823,11 @@ - (MXRoom *)roomWithAlias:(NSString *)alias if (alias) { - for (MXRoom *room in rooms.allValues) + for (MXRoomSummary *summary in roomsSummaries.allValues) { - if (room.state.aliases && NSNotFound != [room.state.aliases indexOfObject:alias]) + if (summary.aliases && NSNotFound != [summary.aliases indexOfObject:alias]) { - theRoom = room; + theRoom = [self roomWithRoomId:summary.roomId]; break; } } @@ -1808,7 +1852,7 @@ - (MXRoom *)directJoinedRoomWithUserId:(NSString*)userId if (directRoom) { // Check whether the user membership is joined - if (directRoom.state.membership == MXMembershipJoin) + if (directRoom.summary.membership == MXMembershipJoin) { return directRoom; } @@ -1857,14 +1901,6 @@ - (MXRoom *)createRoom:(NSString *)roomId notify:(BOOL)notify return room; } -- (MXRoom *)createRoom:(NSString *)roomId withStateEvents:(NSArray*)stateEvents andAccountData:(MXRoomAccountData*)theAccountData notify:(BOOL)notify -{ - MXRoom *room = [[MXRoom alloc] initWithRoomId:roomId andMatrixSession:self andStateEvents:stateEvents andAccountData:theAccountData]; - - [self addRoom:room notify:notify]; - return room; -} - - (void)addRoom:(MXRoom*)room notify:(BOOL)notify { // Register global listeners for this room @@ -1873,7 +1909,7 @@ - (void)addRoom:(MXRoom*)room notify:(BOOL)notify [listener addRoomToSpy:room]; } - [rooms setObject:room forKey:room.state.roomId]; + [rooms setObject:room forKey:room.roomId]; // Create the room summary if does not exist yet MXRoomSummary *summary = roomsSummaries[room.roomId]; @@ -1889,7 +1925,7 @@ - (void)addRoom:(MXRoom*)room notify:(BOOL)notify [[NSNotificationCenter defaultCenter] postNotificationName:kMXSessionNewRoomNotification object:self userInfo:@{ - kMXSessionNotificationRoomIdKey: room.state.roomId + kMXSessionNotificationRoomIdKey: room.roomId }]; } } @@ -1923,6 +1959,51 @@ - (void)removeRoom:(NSString *)roomId } +#pragma mark - Rooms loading +/** + Load a `MXRoom` object from the store. + + @param roomId the id of the room to load. + @return the loaded `MXRoom` object. + */ +- (MXRoom *)loadRoom:(NSString *)roomId +{ + MXRoom *room = [MXRoom loadRoomFromStore:_store withRoomId:roomId matrixSession:self]; + + if (room) + { + [self addRoom:room notify:NO]; + } + return room; +} + +- (void)preloadRoomsData:(NSArray *)roomIds onComplete:(dispatch_block_t)onComplete +{ + NSLog(@"[MXSession] preloadRooms: %@ rooms", @(roomIds.count)); + + dispatch_group_t group = dispatch_group_create(); + for (NSString *roomId in roomIds) + { + MXRoom *room = [self roomWithRoomId:roomId]; + if (room) + { + dispatch_group_enter(group); + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + dispatch_group_leave(group); + }]; + } + else + { + NSLog(@"[MXSession] preloadRoomsData: Unkown room id: %@", roomId); + } + } + + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + NSLog(@"[MXSession] preloadRoomsForSyncResponse: DONE"); + onComplete(); + }); +} + #pragma mark - Rooms summaries - (MXRoomSummary *)roomSummaryWithRoomId:(NSString*)roomId { @@ -2676,7 +2757,7 @@ - (BOOL)removeInvitedRoom:(MXRoom*)roomToRemove { for(MXRoom* room in invitedRooms) { - if ([room.state.roomId isEqualToString:roomToRemove.state.roomId]) + if ([room.roomId isEqualToString:roomToRemove.roomId]) { roomToRemove = room; hasBeenFound = YES; @@ -2704,7 +2785,7 @@ - (BOOL)removeInvitedRoom:(MXRoom*)roomToRemove // Compute the current invitation list for (MXRoom *room in rooms.allValues) { - if (room.state.membership == MXMembershipInvite) + if (room.summary.membership == MXMembershipInvite) { [invitedRooms addObject:room]; } @@ -2736,7 +2817,7 @@ - (BOOL)removeInvitedRoom:(MXRoom*)roomToRemove MXRoomState *roomPrevState = (MXRoomState *)customObject; MXRoom *room = [self roomWithRoomId:event.roomId]; - if (room.state.membership == MXMembershipInvite) + if (room.summary.membership == MXMembershipInvite) { // check if the room is not yet in the list // must be done in forward and sync direction @@ -2959,6 +3040,112 @@ - (MXHTTPOperation*)setAccountData:(NSDictionary*)data return [matrixRestClient setAccountData:data forType:type success:success failure:failure]; } + +#pragma mark - Matrix filters +- (MXHTTPOperation*)setFilter:(MXFilterJSONModel*)filter + success:(void (^)(NSString *filterId))success + failure:(void (^)(NSError *error))failure +{ + MXHTTPOperation *operation; + + if (_store) + { + // Create an empty operation that will be mutated later + operation = [[MXHTTPOperation alloc] init]; + + // Check if the filter has been already created and cached + MXWeakify(self); + [_store filterIdForFilter:filter success:^(NSString * _Nullable filterId) { + MXStrongifyAndReturnIfNil(self); + + if (filterId) + { + success(filterId); + } + else + { + // Else, create homeserver side + MXWeakify(self); + MXHTTPOperation *operation2 = [self.matrixRestClient setFilter:filter success:^(NSString *filterId) { + MXStrongifyAndReturnIfNil(self); + + // And store it + [self.store storeFilter:filter withFilterId:filterId]; + + success(filterId); + + } failure:failure]; + + if (operation2) + { + [operation mutateTo:operation2]; + } + } + + } failure:failure]; + } + else + { + operation = [self.matrixRestClient setFilter:filter success:success failure:failure]; + } + + return operation; +} + +- (MXHTTPOperation*)filterWithFilterId:(NSString*)filterId + success:(void (^)(MXFilterJSONModel *filter))success + failure:(void (^)(NSError *error))failure +{ + MXHTTPOperation *operation; + + if (_store) + { + // Create an empty operation that will be mutated later + operation = [[MXHTTPOperation alloc] init]; + + // Check in the store + MXWeakify(self); + [_store filterWithFilterId:filterId success:^(MXFilterJSONModel * _Nullable filter) { + MXStrongifyAndReturnIfNil(self); + + if (filter) + { + success(filter); + } + else + { + // Check on the homeserver + MXWeakify(self); + MXHTTPOperation *operation2 = [self.matrixRestClient getFilterWithFilterId:filterId success:^(MXFilterJSONModel *filter) { + MXStrongifyAndReturnIfNil(self); + + if (filter) + { + // Cache it locally + [self.store storeFilter:filter withFilterId:filterId]; + } + + success(filter); + + } failure:failure]; + + if (operation2) + { + [operation mutateTo:operation2]; + } + } + + } failure:failure]; + } + else + { + operation = [matrixRestClient getFilterWithFilterId:filterId success:success failure:failure]; + } + + return operation; +} + + #pragma mark - Crypto /** diff --git a/MatrixSDK/NotificationCenter/Checker/MXPushRuleConditionChecker.h b/MatrixSDK/NotificationCenter/Checker/MXPushRuleConditionChecker.h index bc8f34dc55..d0b6e1bf67 100644 --- a/MatrixSDK/NotificationCenter/Checker/MXPushRuleConditionChecker.h +++ b/MatrixSDK/NotificationCenter/Checker/MXPushRuleConditionChecker.h @@ -19,6 +19,8 @@ #import "MXJSONModels.h" #import "MXEvent.h" +@class MXRoomState; + /** `MXNotificationCenter` delegates push rule condition check to class that implements `MXPushRuleConditionChecker`. @@ -33,6 +35,6 @@ @param contentAsJsonDict the content as a dictionnary. @return YES if the event satisties the condition. */ -- (BOOL)isCondition:(MXPushRuleCondition*)condition satisfiedBy:(MXEvent*)event withJsonDict:(NSDictionary*)contentAsJsonDict; +- (BOOL)isCondition:(MXPushRuleCondition*)condition satisfiedBy:(MXEvent*)event roomState:(MXRoomState*)roomState withJsonDict:(NSDictionary*)contentAsJsonDict; -@end \ No newline at end of file +@end diff --git a/MatrixSDK/NotificationCenter/Checker/MXPushRuleDisplayNameCondtionChecker.m b/MatrixSDK/NotificationCenter/Checker/MXPushRuleDisplayNameCondtionChecker.m index 2a4cc281a3..85b9d867b1 100644 --- a/MatrixSDK/NotificationCenter/Checker/MXPushRuleDisplayNameCondtionChecker.m +++ b/MatrixSDK/NotificationCenter/Checker/MXPushRuleDisplayNameCondtionChecker.m @@ -49,7 +49,7 @@ - (instancetype)initWithMatrixSession:(MXSession *)mxSession2 return self; } -- (BOOL)isCondition:(MXPushRuleCondition*)condition satisfiedBy:(MXEvent*)event withJsonDict:(NSDictionary*)contentAsJsonDict +- (BOOL)isCondition:(MXPushRuleCondition*)condition satisfiedBy:(MXEvent*)event roomState:(MXRoomState*)roomState withJsonDict:(NSDictionary*)contentAsJsonDict { BOOL isSatisfied = NO; diff --git a/MatrixSDK/NotificationCenter/Checker/MXPushRuleEventMatchConditionChecker.m b/MatrixSDK/NotificationCenter/Checker/MXPushRuleEventMatchConditionChecker.m index 6eaf6cc8a3..a45f8cc8d5 100644 --- a/MatrixSDK/NotificationCenter/Checker/MXPushRuleEventMatchConditionChecker.m +++ b/MatrixSDK/NotificationCenter/Checker/MXPushRuleEventMatchConditionChecker.m @@ -24,7 +24,7 @@ @interface MXPushRuleEventMatchConditionChecker () @implementation MXPushRuleEventMatchConditionChecker -- (BOOL)isCondition:(MXPushRuleCondition*)condition satisfiedBy:(MXEvent*)event withJsonDict:(NSDictionary*)contentAsJsonDict +- (BOOL)isCondition:(MXPushRuleCondition*)condition satisfiedBy:(MXEvent*)event roomState:(MXRoomState*)roomState withJsonDict:(NSDictionary*)contentAsJsonDict { BOOL isSatisfied = NO; diff --git a/MatrixSDK/NotificationCenter/Checker/MXPushRuleRoomMemberCountConditionChecker.m b/MatrixSDK/NotificationCenter/Checker/MXPushRuleRoomMemberCountConditionChecker.m index a607979b1f..820e6cc350 100644 --- a/MatrixSDK/NotificationCenter/Checker/MXPushRuleRoomMemberCountConditionChecker.m +++ b/MatrixSDK/NotificationCenter/Checker/MXPushRuleRoomMemberCountConditionChecker.m @@ -1,5 +1,6 @@ /* Copyright 2015 OpenMarket Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -47,7 +48,7 @@ - (instancetype)initWithMatrixSession:(MXSession *)mxSession2 return self; } -- (BOOL)isCondition:(MXPushRuleCondition*)condition satisfiedBy:(MXEvent*)event withJsonDict:(NSDictionary*)contentAsJsonDict +- (BOOL)isCondition:(MXPushRuleCondition*)condition satisfiedBy:(MXEvent*)event roomState:(MXRoomState*)roomState withJsonDict:(NSDictionary*)contentAsJsonDict { if (!event.eventId) { @@ -84,27 +85,27 @@ - (BOOL)isCondition:(MXPushRuleCondition*)condition satisfiedBy:(MXEvent*)event MXRoom *room = [mxSession roomWithRoomId:event.roomId]; // sanity checks - if (room && room.state && room.state.members) + if (room && room.summary) { if (nil == op || [op isEqualToString:@"=="]) { - isSatisfied = (value == room.state.members.count); + isSatisfied = (value == room.summary.membersCount.members); } else if ([op isEqualToString:@"<"]) { - isSatisfied = (value < room.state.members.count); + isSatisfied = (value < room.summary.membersCount.members); } else if ([op isEqualToString:@">"]) { - isSatisfied = (value > room.state.members.count); + isSatisfied = (value > room.summary.membersCount.members); } else if ([op isEqualToString:@">="]) { - isSatisfied = (value >= room.state.members.count); + isSatisfied = (value >= room.summary.membersCount.members); } else if ([op isEqualToString:@"<="]) { - isSatisfied = (value <= room.state.members.count); + isSatisfied = (value <= room.summary.membersCount.members); } } } diff --git a/MatrixSDK/NotificationCenter/Checker/MXPushRuleSenderNotificationPermissionConditionChecker.m b/MatrixSDK/NotificationCenter/Checker/MXPushRuleSenderNotificationPermissionConditionChecker.m index 65811a080c..a4113b0697 100644 --- a/MatrixSDK/NotificationCenter/Checker/MXPushRuleSenderNotificationPermissionConditionChecker.m +++ b/MatrixSDK/NotificationCenter/Checker/MXPushRuleSenderNotificationPermissionConditionChecker.m @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -36,7 +37,7 @@ - (instancetype)initWithMatrixSession:(MXSession *)mxSession2 return self; } -- (BOOL)isCondition:(MXPushRuleCondition*)condition satisfiedBy:(MXEvent*)event withJsonDict:(NSDictionary*)contentAsJsonDict +- (BOOL)isCondition:(MXPushRuleCondition*)condition satisfiedBy:(MXEvent*)event roomState:(MXRoomState*)roomState withJsonDict:(NSDictionary*)contentAsJsonDict { if (!event.eventId) { @@ -53,7 +54,7 @@ - (BOOL)isCondition:(MXPushRuleCondition*)condition satisfiedBy:(MXEvent*)event MXRoom *room = [mxSession roomWithRoomId:event.roomId]; if (room) { - MXRoomPowerLevels *roomPowerLevels = room.state.powerLevels; + MXRoomPowerLevels *roomPowerLevels = roomState.powerLevels; NSInteger notifLevel = [roomPowerLevels minimumPowerLevelForNotifications:notifLevelKey defaultPower:50]; NSInteger senderPowerLevel = [roomPowerLevels powerLevelOfUserWithUserID:event.sender]; diff --git a/MatrixSDK/NotificationCenter/MXNotificationCenter.h b/MatrixSDK/NotificationCenter/MXNotificationCenter.h index 424e388638..985f4d7328 100644 --- a/MatrixSDK/NotificationCenter/MXNotificationCenter.h +++ b/MatrixSDK/NotificationCenter/MXNotificationCenter.h @@ -146,10 +146,11 @@ extern NSString *const kMXNotificationCenterAllOtherRoomMessagesRuleID; /** Find a push rule that is satisfied by an event. - @param event the event to test + @param event the event to test. + @parm roomState the room state when the event occurs @return the push rule that matches the event. Nil if no match. */ -- (MXPushRule*)ruleMatchingEvent:(MXEvent*)event; +- (MXPushRule*)ruleMatchingEvent:(MXEvent*)event roomState:(MXRoomState*)roomState; /** Get a push rule by using its id. diff --git a/MatrixSDK/NotificationCenter/MXNotificationCenter.m b/MatrixSDK/NotificationCenter/MXNotificationCenter.m index 5d1ee94439..be3dfb6da3 100644 --- a/MatrixSDK/NotificationCenter/MXNotificationCenter.m +++ b/MatrixSDK/NotificationCenter/MXNotificationCenter.m @@ -154,7 +154,7 @@ - (void)setChecker:(id)checker forConditionKind:(MXP [conditionCheckers setObject:checker forKey:conditionKind]; } -- (MXPushRule *)ruleMatchingEvent:(MXEvent *)event +- (MXPushRule *)ruleMatchingEvent:(MXEvent *)event roomState:(MXRoomState*)roomState { MXPushRule *theRule = nil; @@ -196,7 +196,7 @@ - (MXPushRule *)ruleMatchingEvent:(MXEvent *)event id checker = [conditionCheckers valueForKey:condition.kind]; if (checker) { - conditionsOk = [checker isCondition:condition satisfiedBy:event withJsonDict:JSONDictionary]; + conditionsOk = [checker isCondition:condition satisfiedBy:event roomState:roomState withJsonDict:JSONDictionary]; if (NO == conditionsOk) { // Do not need to go further @@ -223,7 +223,7 @@ - (MXPushRule *)ruleMatchingEvent:(MXEvent *)event @"pattern": rule.pattern }; - conditionsOk = [eventMatchConditionChecker isCondition:equivalentCondition satisfiedBy:event withJsonDict:JSONDictionary]; + conditionsOk = [eventMatchConditionChecker isCondition:equivalentCondition satisfiedBy:event roomState:roomState withJsonDict:JSONDictionary]; break; } @@ -238,7 +238,7 @@ - (MXPushRule *)ruleMatchingEvent:(MXEvent *)event @"pattern": rule.ruleId }; - conditionsOk = [eventMatchConditionChecker isCondition:equivalentCondition satisfiedBy:event withJsonDict:JSONDictionary]; + conditionsOk = [eventMatchConditionChecker isCondition:equivalentCondition satisfiedBy:event roomState:roomState withJsonDict:JSONDictionary]; break; } @@ -253,7 +253,7 @@ - (MXPushRule *)ruleMatchingEvent:(MXEvent *)event @"pattern": rule.ruleId }; - conditionsOk = [eventMatchConditionChecker isCondition:equivalentCondition satisfiedBy:event withJsonDict:JSONDictionary]; + conditionsOk = [eventMatchConditionChecker isCondition:equivalentCondition satisfiedBy:event roomState:roomState withJsonDict:JSONDictionary]; break; } } @@ -539,7 +539,7 @@ - (void)shouldNotify:(MXEvent*)event roomState:(MXRoomState*)roomState // Check for notifications only if we have listeners if (notificationListeners.count) { - MXPushRule *rule = [self ruleMatchingEvent:event]; + MXPushRule *rule = [self ruleMatchingEvent:event roomState:roomState]; if (rule) { // Make sure this is not a rule to prevent from generating a notification diff --git a/MatrixSDK/Utils/MXTools.h b/MatrixSDK/Utils/MXTools.h index e3de01b029..35ac769d9e 100644 --- a/MatrixSDK/Utils/MXTools.h +++ b/MatrixSDK/Utils/MXTools.h @@ -134,6 +134,14 @@ FOUNDATION_EXPORT NSString *const kMXToolsRegexStringForMatrixGroupIdentifier; */ + (NSString*)permalinkToEvent:(NSString*)eventId inRoom:(NSString*)roomIdOrAlias; +/* + Return a matrix.to permalink to a user. + + @param userId the id of the user to link to. + @return the matrix.to permalink. + */ ++ (NSString*)permalinkToUserWithUserId:(NSString*)userId; + #pragma mark - File /** diff --git a/MatrixSDK/Utils/MXTools.m b/MatrixSDK/Utils/MXTools.m index 694aef5711..3257184083 100644 --- a/MatrixSDK/Utils/MXTools.m +++ b/MatrixSDK/Utils/MXTools.m @@ -94,7 +94,8 @@ + (void)initialize kMXEventTypeStringCallCandidates, kMXEventTypeStringCallAnswer, kMXEventTypeStringCallHangup, - kMXEventTypeStringSticker + kMXEventTypeStringSticker, + kMXEventTypeStringRoomTombStone ]; NSMutableDictionary *map = [NSMutableDictionary dictionaryWithCapacity:eventTypeMapEnumToString.count]; @@ -339,6 +340,11 @@ + (NSString *)permalinkToEvent:(NSString *)eventId inRoom:(NSString *)roomIdOrAl } ++ (NSString*)permalinkToUserWithUserId:(NSString*)userId +{ + return [NSString stringWithFormat:@"%@/#/%@", kMXMatrixDotToUrl, userId]; +} + #pragma mark - File // return an array of files attributes diff --git a/MatrixSDK/VoIP/CallKit/MXCallKitAdapter.m b/MatrixSDK/VoIP/CallKit/MXCallKitAdapter.m index 5d11c70a4e..987f95ac32 100644 --- a/MatrixSDK/VoIP/CallKit/MXCallKitAdapter.m +++ b/MatrixSDK/VoIP/CallKit/MXCallKitAdapter.m @@ -85,38 +85,29 @@ - (void)dealloc - (void)startCall:(MXCall *)call { - MXSession *mxSession = call.room.mxSession; NSUUID *callUUID = call.callUUID; - - NSString *contactIdentifier; - if (call.isConferenceCall) - { - contactIdentifier = call.room.state.displayname; - } - else - { - MXUser *callee = [mxSession userWithUserId:call.calleeId]; - contactIdentifier = callee.displayname; - } - - CXHandle *handle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:call.room.roomId]; - CXStartCallAction *action = [[CXStartCallAction alloc] initWithCallUUID:callUUID handle:handle]; - action.contactIdentifier = contactIdentifier; - - CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action]; - [self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) { - CXCallUpdate *update = [[CXCallUpdate alloc] init]; - update.remoteHandle = handle; - update.localizedCallerName = contactIdentifier; - update.hasVideo = call.isVideoCall; - update.supportsHolding = NO; - update.supportsGrouping = NO; - update.supportsUngrouping = NO; - update.supportsDTMF = NO; - - [self.provider reportCallWithUUID:callUUID updated:update]; - - [self.calls setObject:call forKey:callUUID]; + + [self contactIdentifierForCall:call onComplete:^(NSString *contactIdentifier) { + + CXHandle *handle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:call.room.roomId]; + CXStartCallAction *action = [[CXStartCallAction alloc] initWithCallUUID:callUUID handle:handle]; + action.contactIdentifier = contactIdentifier; + + CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action]; + [self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) { + CXCallUpdate *update = [[CXCallUpdate alloc] init]; + update.remoteHandle = handle; + update.localizedCallerName = contactIdentifier; + update.hasVideo = call.isVideoCall; + update.supportsHolding = NO; + update.supportsGrouping = NO; + update.supportsUngrouping = NO; + update.supportsDTMF = NO; + + [self.provider reportCallWithUUID:callUUID updated:update]; + + [self.calls setObject:call forKey:callUUID]; + }]; }]; } @@ -280,6 +271,24 @@ - (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCal [action fulfill]; } + +#pragma mark - Private methods + +- (void)contactIdentifierForCall:(MXCall *)call onComplete:(void (^)(NSString *contactIdentifier))onComplete +{ + if (call.isConferenceCall) + { + onComplete(call.room.summary.displayname); + } + else + { + [call calleeId:^(NSString *calleeId) { + MXUser *callee = [call.room.mxSession userWithUserId:calleeId]; + onComplete(callee.displayname); + }]; + } +} + @end #endif diff --git a/MatrixSDK/VoIP/MXCall.h b/MatrixSDK/VoIP/MXCall.h index 4a0a51c0d8..cae47a3cc2 100644 --- a/MatrixSDK/VoIP/MXCall.h +++ b/MatrixSDK/VoIP/MXCall.h @@ -186,7 +186,7 @@ extern NSString *const kMXCallStateDidChange; /** The user id of the callee. Nil for conference calls */ -@property (readonly, nullable, nonatomic) NSString *calleeId; +- (void)calleeId:(void (^)(NSString *calleeId))onComplete; /** The UIView that receives frames from the user's camera. diff --git a/MatrixSDK/VoIP/MXCall.m b/MatrixSDK/VoIP/MXCall.m index e5f8b48ec7..05d4bc27af 100644 --- a/MatrixSDK/VoIP/MXCall.m +++ b/MatrixSDK/VoIP/MXCall.m @@ -1,5 +1,6 @@ /* Copyright 2015 OpenMarket Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -66,6 +67,11 @@ @interface MXCall () Timer for sending local ICE candidates. */ NSTimer *localIceGatheringTimer; + + /** + Cache for self.calleeId. + */ + NSString *calleeId; } @end @@ -96,20 +102,7 @@ - (instancetype)initWithRoomId:(NSString *)roomId callSignalingRoomId:(NSString _endReason = MXCallEndReasonUnknown; // Consider we are using a conference call when there are more than 2 users - _isConferenceCall = (2 < _room.state.joinedMembers.count); - - // Set caleeId only for regular calls - if (!_isConferenceCall) - { - for (MXRoomMember *roomMember in _room.state.joinedMembers) - { - if (![roomMember.userId isEqualToString:_callerId]) - { - _calleeId = roomMember.userId; - break; - } - } - } + _isConferenceCall = (2 < _room.summary.membersCount.joined); localICECandidates = [NSMutableArray array]; @@ -144,6 +137,37 @@ - (instancetype)initWithRoomId:(NSString *)roomId callSignalingRoomId:(NSString return self; } +- (void)calleeId:(void (^)(NSString * _Nonnull))onComplete +{ + if (calleeId) + { + onComplete(calleeId); + } + else + { + // Set caleeId only for regular calls + if (!_isConferenceCall) + { + MXWeakify(self); + [_room state:^(MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); + + MXRoomMembers *roomMembers = roomState.members; + for (MXRoomMember *roomMember in roomMembers.joinedMembers) + { + if (![roomMember.userId isEqualToString:self.callerId]) + { + self->calleeId = roomMember.userId; + break; + } + } + + onComplete(self->calleeId); + }]; + } + } +} + - (void)handleCallEvent:(MXEvent *)event { switch (event.eventType) @@ -158,7 +182,7 @@ - (void)handleCallEvent:(MXEvent *)event _callId = callInviteEventContent.callId; _callerId = event.sender; - _calleeId = callManager.mxSession.myUser.userId; + calleeId = callManager.mxSession.myUser.userId; _isIncoming = YES; // Store if it is voice or video call @@ -335,7 +359,7 @@ - (void)answer // Sanity check on the call state // Note that e2e rooms requires several attempts of [MXCall answer] in case of unknown devices if (self.state == MXCallStateRinging - || (_callSignalingRoom.state.isEncrypted && self.state == MXCallStateCreateAnswer)) + || (_callSignalingRoom.summary.isEncrypted && self.state == MXCallStateCreateAnswer)) { [self setState:MXCallStateCreateAnswer reason:nil]; @@ -387,7 +411,7 @@ - (void)answer // in the room before actually answering. // That will allow MXCall to send ICE candidates events without encryption errors like // MXEncryptingErrorUnknownDeviceReason. - if (_callSignalingRoom.state.isEncrypted) + if (_callSignalingRoom.summary.isEncrypted) { NSLog(@"[MXCall] answer: ensuring encryption is ready to use ..."); [callManager.mxSession.crypto ensureEncryptionInRoom:_callSignalingRoom.roomId success:answer failure:^(NSError *error) { diff --git a/MatrixSDK/VoIP/MXCallManager.h b/MatrixSDK/VoIP/MXCallManager.h index 53b2d360cb..d96abeaf25 100644 --- a/MatrixSDK/VoIP/MXCallManager.h +++ b/MatrixSDK/VoIP/MXCallManager.h @@ -1,5 +1,6 @@ /* Copyright 2015 OpenMarket Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @class MXCall; @class MXCallKitAdapter; -@class MXRoom; +@class MXRoom, MXRoomState; @class MXRoomMember; @class MXSession; @class MXTurnServerResponse; @@ -174,9 +175,10 @@ extern NSString *const kMXCallManagerConferenceFinished; invite power level can create a conference call. @param room the room to check. + @param roomState the state of the room. @return YES if the user can. */ -+ (BOOL)canPlaceConferenceCallInRoom:(MXRoom *)room; ++ (BOOL)canPlaceConferenceCallInRoom:(MXRoom *)room roomState:(MXRoomState *)roomState; @end diff --git a/MatrixSDK/VoIP/MXCallManager.m b/MatrixSDK/VoIP/MXCallManager.m index 6f9335184b..36a842869f 100644 --- a/MatrixSDK/VoIP/MXCallManager.m +++ b/MatrixSDK/VoIP/MXCallManager.m @@ -1,5 +1,6 @@ /* Copyright 2015 OpenMarket Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -161,7 +162,7 @@ - (MXCall *)callInRoom:(NSString *)roomId MXCall *theCall; for (MXCall *call in calls) { - if ([call.room.state.roomId isEqualToString:roomId]) + if ([call.room.roomId isEqualToString:roomId]) { theCall = call; break; @@ -203,9 +204,9 @@ - (void)placeCallInRoom:(NSString *)roomId withVideo:(BOOL)video MXRoom *room = [_mxSession roomWithRoomId:roomId]; - if (room && 1 < room.state.joinedMembers.count) + if (room && 1 < room.summary.membersCount.joined) { - if (2 == room.state.joinedMembers.count) + if (2 == room.summary.membersCount.joined) { // Do a peer to peer, one to one call MXCall *call = [[MXCall alloc] initWithRoomId:roomId andCallManager:self]; @@ -269,7 +270,7 @@ - (void)placeCallInRoom:(NSString *)roomId withVideo:(BOOL)video } else { - NSLog(@"[MXCallManager] placeCallInRoom: ERROR: Cannot place call in %@. Members count: %tu", roomId, room.state.joinedMembers.count); + NSLog(@"[MXCallManager] placeCallInRoom: ERROR: Cannot place call in %@. Members count: %tu", roomId, room.summary.membersCount.joined); if (failure) { @@ -522,18 +523,18 @@ + (BOOL)isConferenceUser:(NSString *)userId return isConferenceUser; } -+ (BOOL)canPlaceConferenceCallInRoom:(MXRoom *)room ++ (BOOL)canPlaceConferenceCallInRoom:(MXRoom *)room roomState:(MXRoomState *)roomState { BOOL canPlaceConferenceCallInRoom = NO; - if (room.state.isOngoingConferenceCall) + if (roomState.isOngoingConferenceCall) { // All room members can join an existing conference call canPlaceConferenceCallInRoom = YES; } else { - MXRoomPowerLevels *powerLevels = room.state.powerLevels; + MXRoomPowerLevels *powerLevels = roomState.powerLevels; NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:room.mxSession.myUser.userId]; // Only member with invite power level can create a conference call @@ -561,15 +562,17 @@ - (void)inviteConferenceUserToRoom:(MXRoom *)room { NSString *conferenceUserId = [MXCallManager conferenceUserIdForRoom:room.roomId]; - MXRoomMember *conferenceUserMember = [room.state memberWithUserId:conferenceUserId]; - if (conferenceUserMember && conferenceUserMember.membership == MXMembershipJoin) - { - success(); - } - else - { - [room inviteUser:conferenceUserId success:success failure:failure]; - } + [room members:^(MXRoomMembers *roomMembers) { + MXRoomMember *conferenceUserMember = [roomMembers memberWithUserId:conferenceUserId]; + if (conferenceUserMember && conferenceUserMember.membership == MXMembershipJoin) + { + success(); + } + else + { + [room inviteUser:conferenceUserId success:success failure:failure]; + } + } failure:failure]; } /** @@ -587,30 +590,47 @@ - (void)conferenceUserRoomForRoom:(NSString*)roomId NSString *conferenceUserId = [MXCallManager conferenceUserIdForRoom:roomId]; // Use an existing 1:1 with the conference user; else make one - MXRoom *conferenceUserRoom; - for (MXRoom *room in _mxSession.rooms) + __block MXRoom *conferenceUserRoom; + + dispatch_group_t group = dispatch_group_create(); + for (MXRoomSummary *roomSummary in _mxSession.roomsSummaries) { - if (room.state.members.count == 2 && [room.state memberWithUserId:conferenceUserId]) + if (roomSummary.isConferenceUserRoom) { - conferenceUserRoom = room; + dispatch_group_enter(group); + MXRoom *room = [_mxSession roomWithRoomId:roomSummary.roomId]; + + [room state:^(MXRoomState *roomState) { + if ([roomState.members memberWithUserId:conferenceUserId]) + { + conferenceUserRoom = room; + } + + dispatch_group_leave(group); + }]; } } - if (conferenceUserRoom) - { - success(conferenceUserRoom); - } - else - { - [_mxSession createRoom:@{ - @"preset": @"private_chat", - @"invite": @[conferenceUserId] - } success:^(MXRoom *room) { + MXWeakify(self); + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + MXStrongifyAndReturnIfNil(self); - success(room); + if (conferenceUserRoom) + { + success(conferenceUserRoom); + } + else + { + [self.mxSession createRoom:@{ + @"preset": @"private_chat", + @"invite": @[conferenceUserId] + } success:^(MXRoom *room) { - } failure:failure]; - } + success(room); + + } failure:failure]; + } + }); } @end diff --git a/MatrixSDKTests/MXCryptoTests.m b/MatrixSDKTests/MXCryptoTests.m index 7d8742069f..58d034943a 100644 --- a/MatrixSDKTests/MXCryptoTests.m +++ b/MatrixSDKTests/MXCryptoTests.m @@ -27,6 +27,8 @@ #import "MXFileStore.h" #import "MXSDKOptions.h" +#import "MXTools.h" +#import "MXSendReplyEventDefaultStringLocalizations.h" #if 1 // MX_CRYPTO autamatic definiton does not work well for tests so force it //#ifdef MX_CRYPTO @@ -251,7 +253,7 @@ - (void)testMultipleDownloadKeys [matrixSDKTestsE2EData doE2ETestWithBobAndAlice:self readyToTest:^(MXSession *bobSession, MXSession *aliceSession, XCTestExpectation *expectation) { __block NSUInteger count = 0; - void(^onSuccess)() = ^() { + void(^onSuccess)(void) = ^(void) { if (++count == 2) { @@ -381,11 +383,11 @@ - (void)testRoomIsEncrypted [mxSession createRoom:@{} success:^(MXRoom *room) { - XCTAssertFalse(room.state.isEncrypted); + XCTAssertFalse(room.summary.isEncrypted); [room enableEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm success:^{ - XCTAssert(room.state.isEncrypted); + XCTAssert(room.summary.isEncrypted); // mxSession.crypto.store is a private member // and should be used only from the cryptoQueue. Particularly for this test @@ -416,14 +418,17 @@ - (void)testAliceInACryptedRoom MXRoom *roomFromAlicePOV = [aliceSession roomWithRoomId:roomId]; - XCTAssert(roomFromAlicePOV.state.isEncrypted); + XCTAssert(roomFromAlicePOV.summary.isEncrypted); // Check the echo from hs of a post message is correct - [roomFromAlicePOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromAlicePOV liveTimeline:^(MXEventTimeline *liveTimeline) { - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:message senderSession:aliceSession]); + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [expectation fulfill]; + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:message senderSession:aliceSession]); + + [expectation fulfill]; + }]; }]; [roomFromAlicePOV sendTextMessage:message success:nil failure:^(NSError *error) { @@ -456,21 +461,25 @@ - (void)testAliceInACryptedRoomAfterInitialSync MXRoom *roomFromAlicePOV = [aliceSession2 roomWithRoomId:roomId]; - XCTAssert(roomFromAlicePOV.state.isEncrypted); + XCTAssert(roomFromAlicePOV.summary.isEncrypted); // Check the echo from hs of a post message is correct - [roomFromAlicePOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromAlicePOV liveTimeline:^(MXEventTimeline *liveTimeline) { - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:message senderSession:aliceSession2]); + XCTAssert(liveTimeline.state.isEncrypted); - [expectation fulfill]; - }]; + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [roomFromAlicePOV sendTextMessage:message success:nil failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:message senderSession:aliceSession2]); + + [expectation fulfill]; + }]; + [roomFromAlicePOV sendTextMessage:message success:nil failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + }]; } failure:^(NSError *error) { XCTFail(@"Cannot set up intial test conditions - error: %@", error); @@ -497,14 +506,17 @@ - (void)testAliceAndBobInACryptedRoom MXRoom *roomFromBobPOV = [bobSession roomWithRoomId:roomId]; MXRoom *roomFromAlicePOV = [aliceSession roomWithRoomId:roomId]; - XCTAssert(roomFromBobPOV.state.isEncrypted); - XCTAssert(roomFromAlicePOV.state.isEncrypted); + XCTAssert(roomFromBobPOV.summary.isEncrypted); + XCTAssert(roomFromAlicePOV.summary.isEncrypted); - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [expectation fulfill]; + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); + + [expectation fulfill]; + }]; }]; [roomFromAlicePOV sendTextMessage:messageFromAlice success:nil failure:^(NSError *error) { @@ -528,53 +540,57 @@ - (void)testAliceAndBobInACryptedRoom2 MXRoom *roomFromBobPOV = [bobSession roomWithRoomId:roomId]; MXRoom *roomFromAlicePOV = [aliceSession roomWithRoomId:roomId]; - XCTAssert(roomFromBobPOV.state.isEncrypted); - XCTAssert(roomFromAlicePOV.state.isEncrypted); + XCTAssert(roomFromBobPOV.summary.isEncrypted); + XCTAssert(roomFromAlicePOV.summary.isEncrypted); - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - if ([event.sender isEqualToString:bobSession.myUser.userId]) - { - return; - } + if ([event.sender isEqualToString:bobSession.myUser.userId]) + { + return; + } - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromAlice[receivedMessagesFromAlice++] senderSession:aliceSession]); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromAlice[receivedMessagesFromAlice++] senderSession:aliceSession]); - switch (receivedMessagesFromAlice) - { - case 1: + switch (receivedMessagesFromAlice) { - // Send messages in expected order - [roomFromBobPOV sendTextMessage:matrixSDKTestsE2EData.messagesFromBob[0] success:^(NSString *eventId) { - [roomFromBobPOV sendTextMessage:matrixSDKTestsE2EData.messagesFromBob[1] success:^(NSString *eventId) { - [roomFromBobPOV sendTextMessage:matrixSDKTestsE2EData.messagesFromBob[2] success:nil failure:nil]; + case 1: + { + // Send messages in expected order + [roomFromBobPOV sendTextMessage:matrixSDKTestsE2EData.messagesFromBob[0] success:^(NSString *eventId) { + [roomFromBobPOV sendTextMessage:matrixSDKTestsE2EData.messagesFromBob[1] success:^(NSString *eventId) { + [roomFromBobPOV sendTextMessage:matrixSDKTestsE2EData.messagesFromBob[2] success:nil failure:nil]; + } failure:nil]; } failure:nil]; - } failure:nil]; - break; - } + break; + } - case 2: - [expectation fulfill]; - break; - default: - break; - } + case 2: + [expectation fulfill]; + break; + default: + break; + } + }]; }]; - [roomFromAlicePOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromAlicePOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - if ([event.sender isEqualToString:aliceSession.myUser.userId]) - { - return; - } + if ([event.sender isEqualToString:aliceSession.myUser.userId]) + { + return; + } - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[receivedMessagesFromBob++] senderSession:bobSession]); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[receivedMessagesFromBob++] senderSession:bobSession]); - if (receivedMessagesFromBob == 3) - { - [roomFromAlicePOV sendTextMessage:matrixSDKTestsE2EData.messagesFromAlice[1] success:nil failure:nil]; - } + if (receivedMessagesFromBob == 3) + { + [roomFromAlicePOV sendTextMessage:matrixSDKTestsE2EData.messagesFromAlice[1] success:nil failure:nil]; + } + }]; }]; [roomFromAlicePOV sendTextMessage:matrixSDKTestsE2EData.messagesFromAlice[0] success:nil failure:^(NSError *error) { @@ -608,52 +624,55 @@ - (void)testAliceAndBobInACryptedRoomFromInitialSync MXRoom *roomFromBobPOV = [bobSession roomWithRoomId:roomId]; - [roomFromBobPOV.liveTimeline resetPagination]; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { - XCTAssertEqual(direction, MXTimelineDirectionBackwards); + [liveTimeline resetPagination]; + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - switch (paginatedMessagesCount++) - { - case 0: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromAlice[1] senderSession:aliceSession]); - break; + XCTAssertEqual(direction, MXTimelineDirectionBackwards); - case 1: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[2] senderSession:bobSession]); - break; + switch (paginatedMessagesCount++) + { + case 0: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromAlice[1] senderSession:aliceSession]); + break; - case 2: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[1] senderSession:bobSession]); - break; + case 1: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[2] senderSession:bobSession]); + break; - case 3: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[0] senderSession:bobSession]); - break; + case 2: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[1] senderSession:bobSession]); + break; - case 4: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromAlice[0] senderSession:aliceSession]); - break; + case 3: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[0] senderSession:bobSession]); + break; - default: - break; - } + case 4: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromAlice[0] senderSession:aliceSession]); + break; - }]; + default: + break; + } - XCTAssert([roomFromBobPOV.liveTimeline canPaginate:MXTimelineDirectionBackwards]); + }]; - [roomFromBobPOV.liveTimeline paginate:10 direction:MXTimelineDirectionBackwards onlyFromStore:YES complete:^{ + XCTAssert([liveTimeline canPaginate:MXTimelineDirectionBackwards]); - XCTAssertEqual(paginatedMessagesCount, 5); + [liveTimeline paginate:10 direction:MXTimelineDirectionBackwards onlyFromStore:YES complete:^{ - [expectation fulfill]; + XCTAssertEqual(paginatedMessagesCount, 5); - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; + [expectation fulfill]; + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + + }]; } failure:^(NSError *error) { NSAssert(NO, @"Cannot set up intial test conditions - error: %@", error); @@ -677,50 +696,53 @@ - (void)testAliceAndBobInACryptedRoomBackPaginationFromMemoryStore MXRoom *roomFromBobPOV = [bobSession roomWithRoomId:roomId]; - [roomFromBobPOV.liveTimeline resetPagination]; + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [liveTimeline resetPagination]; - XCTAssertEqual(direction, MXTimelineDirectionBackwards); + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - switch (paginatedMessagesCount++) { - case 0: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromAlice[1] senderSession:aliceSession]); - break; + XCTAssertEqual(direction, MXTimelineDirectionBackwards); - case 1: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[2] senderSession:bobSession]); - break; + switch (paginatedMessagesCount++) { + case 0: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromAlice[1] senderSession:aliceSession]); + break; - case 2: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[1] senderSession:bobSession]); - break; + case 1: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[2] senderSession:bobSession]); + break; - case 3: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[0] senderSession:bobSession]); - break; + case 2: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[1] senderSession:bobSession]); + break; - case 4: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromAlice[0] senderSession:aliceSession]); - break; + case 3: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromBob[0] senderSession:bobSession]); + break; - default: - break; - } + case 4: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:matrixSDKTestsE2EData.messagesFromAlice[0] senderSession:aliceSession]); + break; - }]; + default: + break; + } - XCTAssert([roomFromBobPOV.liveTimeline canPaginate:MXTimelineDirectionBackwards]); + }]; - [roomFromBobPOV.liveTimeline paginate:10 direction:MXTimelineDirectionBackwards onlyFromStore:YES complete:^{ + XCTAssert([liveTimeline canPaginate:MXTimelineDirectionBackwards]); - XCTAssertEqual(paginatedMessagesCount, 5); + [liveTimeline paginate:10 direction:MXTimelineDirectionBackwards onlyFromStore:YES complete:^{ - [expectation fulfill]; + XCTAssertEqual(paginatedMessagesCount, 5); - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; }]; }]; @@ -804,41 +826,45 @@ - (void)testAliceAndNotCryptedBobInACryptedRoom MXRoom *roomFromBobPOV = [bobSession roomWithRoomId:roomId]; MXRoom *roomFromAlicePOV = [aliceSession roomWithRoomId:roomId]; - XCTAssert(roomFromBobPOV.state.isEncrypted, "Even if his crypto is disabled, Bob should know that a room is encrypted"); - XCTAssert(roomFromAlicePOV.state.isEncrypted); + XCTAssert(roomFromBobPOV.summary.isEncrypted, "Even if his crypto is disabled, Bob should know that a room is encrypted"); + XCTAssert(roomFromAlicePOV.summary.isEncrypted); __block NSUInteger messageCount = 0; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomEncrypted, kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { - switch (messageCount++) - { - case 0: + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomEncrypted, kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + switch (messageCount++) { - XCTAssert(event.isEncrypted); - XCTAssertEqual(event.eventType, MXEventTypeRoomEncrypted); - XCTAssertNil(event.content[@"body"]); + case 0: + { + XCTAssert(event.isEncrypted); + XCTAssertEqual(event.eventType, MXEventTypeRoomEncrypted); + XCTAssertNil(event.content[@"body"]); - XCTAssert(event.decryptionError); - XCTAssertEqualObjects(event.decryptionError.domain, MXDecryptingErrorDomain); - XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorEncryptionNotEnabledCode); - XCTAssertEqualObjects(event.decryptionError.localizedDescription, MXDecryptingErrorEncryptionNotEnabledReason); + XCTAssert(event.decryptionError); + XCTAssertEqualObjects(event.decryptionError.domain, MXDecryptingErrorDomain); + XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorEncryptionNotEnabledCode); + XCTAssertEqualObjects(event.decryptionError.localizedDescription, MXDecryptingErrorEncryptionNotEnabledReason); - [roomFromBobPOV sendTextMessage:@"Hello I'm Bob!" success:nil failure:nil]; - break; - } + [roomFromBobPOV sendTextMessage:@"Hello I'm Bob!" success:nil failure:nil]; + break; + } - case 1: - { - XCTAssertFalse(event.isEncrypted); + case 1: + { + XCTAssertFalse(event.isEncrypted); - [expectation fulfill]; - break; + [expectation fulfill]; + break; + } + + default: + break; } - default: - break; - } + }]; }]; @@ -873,7 +899,7 @@ - (void)testAliceDecryptOldMessageWithANewDeviceInACryptedRoom MXRoom *roomFromAlicePOV2 = [aliceSession2 roomWithRoomId:roomId]; - XCTAssert(roomFromAlicePOV2.state.isEncrypted, @"The room must still appear as encrypted"); + XCTAssert(roomFromAlicePOV2.summary.isEncrypted, @"The room must still appear as encrypted"); MXEvent *event = roomFromAlicePOV2.summary.lastMessageEvent; @@ -914,12 +940,14 @@ - (void)testAliceWithNewDeviceAndBob NSString *messageFromAlice = @"Hello I'm still Alice!"; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession2]); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession2]); - [expectation fulfill]; + [expectation fulfill]; + }]; }]; [roomFromAlice2POV sendTextMessage:messageFromAlice success:nil failure:^(NSError *error) { @@ -939,40 +967,43 @@ - (void)testAliceAndBobWithNewDevice MXRoom *roomFromAlicePOV = [aliceSession roomWithRoomId:roomId]; MXRoom *roomFromBobPOV = [bobSession roomWithRoomId:roomId]; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - NSString *messageFromAlice = @"Hello I'm still Alice!"; + NSString *messageFromAlice = @"Hello I'm still Alice!"; - // Relog bob to simulate a new device - [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; - [matrixSDKTestsData relogUserSession:bobSession withPassword:MXTESTS_BOB_PWD onComplete:^(MXSession *bobSession2) { - [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = NO; + // Relog bob to simulate a new device + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; + [matrixSDKTestsData relogUserSession:bobSession withPassword:MXTESTS_BOB_PWD onComplete:^(MXSession *bobSession2) { + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = NO; - aliceSessionToClose = aliceSession; - bobSessionToClose = bobSession2; + aliceSessionToClose = aliceSession; + bobSessionToClose = bobSession2; - MXRoom *roomFromBob2POV = [bobSession2 roomWithRoomId:roomId]; + MXRoom *roomFromBob2POV = [bobSession2 roomWithRoomId:roomId]; - [roomFromBob2POV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBob2POV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); - [expectation fulfill]; + [expectation fulfill]; + }]; + }]; }]; - }]; - - // Wait a bit before sending the 2nd message to Bob with his 2 devices. - // We wait until Alice receives the new device information event. This cannot be more accurate. - observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionOnToDeviceEventNotification object:aliceSession queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + // Wait a bit before sending the 2nd message to Bob with his 2 devices. + // We wait until Alice receives the new device information event. This cannot be more accurate. + observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionOnToDeviceEventNotification object:aliceSession queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - [roomFromAlicePOV sendTextMessage:messageFromAlice success:nil failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; + [roomFromAlicePOV sendTextMessage:messageFromAlice success:nil failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; }]; - }]; + }]; }]; // 1st message to Bob and his single device @@ -1007,7 +1038,7 @@ - (void)testAliceWithNewDeviceAndBobWithNewDevice MXRoom *roomFromBob2POV = [bobSession2 roomWithRoomId:roomId]; MXRoom *roomFromAlice2POV = [aliceSession2 roomWithRoomId:roomId]; - XCTAssert(roomFromBob2POV.state.isEncrypted, @"The room must still appear as encrypted"); + XCTAssert(roomFromBob2POV.summary.isEncrypted, @"The room must still appear as encrypted"); MXEvent *event = roomFromBob2POV.summary.lastMessageEvent; @@ -1020,12 +1051,14 @@ - (void)testAliceWithNewDeviceAndBobWithNewDevice NSString *messageFromAlice = @"Hello I'm still Alice!"; - [roomFromBob2POV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBob2POV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession2]); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession2]); - [expectation fulfill]; + [expectation fulfill]; + }]; }]; [roomFromAlice2POV sendTextMessage:messageFromAlice success:nil failure:^(NSError *error) { @@ -1058,69 +1091,72 @@ - (void)testAliceAndBlockedBob __block NSUInteger messageCount = 0; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - switch (messageCount++) - { - case 0: + switch (messageCount++) { - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[0] senderSession:aliceSession]); + case 0: + { + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[0] senderSession:aliceSession]); + + // Make Alice block Bob + [aliceSession.crypto setDeviceVerification:MXDeviceBlocked + forDevice:bobSession.matrixRestClient.credentials.deviceId + ofUser:bobSession.myUser.userId + success: + ^{ - // Make Alice block Bob - [aliceSession.crypto setDeviceVerification:MXDeviceBlocked - forDevice:bobSession.matrixRestClient.credentials.deviceId - ofUser:bobSession.myUser.userId - success: - ^{ + [roomFromAlicePOV sendTextMessage:aliceMessages[1] success:nil failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; - [roomFromAlicePOV sendTextMessage:aliceMessages[1] success:nil failure:^(NSError *error) { + } failure:^(NSError *error) { XCTFail(@"Cannot set up intial test conditions - error: %@", error); [expectation fulfill]; }]; - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - - break; - } + break; + } - case 1: - { - // Bob must be not able to decrypt the 2nd message - XCTAssertEqual(event.eventType, MXEventTypeRoomEncrypted); - XCTAssertNil(event.clearEvent); - XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorUnknownInboundSessionIdCode); - - // Make Alice unblock Bob - [aliceSession.crypto setDeviceVerification:MXDeviceUnverified - forDevice:bobSession.matrixRestClient.credentials.deviceId - ofUser:bobSession.myUser.userId success: - ^{ - [roomFromAlicePOV sendTextMessage:aliceMessages[2] success:nil failure:^(NSError *error) { + case 1: + { + // Bob must be not able to decrypt the 2nd message + XCTAssertEqual(event.eventType, MXEventTypeRoomEncrypted); + XCTAssertNil(event.clearEvent); + XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorUnknownInboundSessionIdCode); + + // Make Alice unblock Bob + [aliceSession.crypto setDeviceVerification:MXDeviceUnverified + forDevice:bobSession.matrixRestClient.credentials.deviceId + ofUser:bobSession.myUser.userId success: + ^{ + [roomFromAlicePOV sendTextMessage:aliceMessages[2] success:nil failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { XCTFail(@"Cannot set up intial test conditions - error: %@", error); [expectation fulfill]; }]; - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; + break; + } - break; - } + case 2: + { + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[2] senderSession:aliceSession]); + [expectation fulfill]; + break; + } - case 2: - { - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[2] senderSession:aliceSession]); - [expectation fulfill]; - break; + default: + break; } - default: - break; - } + }]; }]; @@ -1181,81 +1217,85 @@ - (void)testBlackListUnverifiedDevices __block NSUInteger bobMessageCount = 1; __block NSUInteger samMessageCount = 1; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - switch (bobMessageCount++) - { - case 1: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[1] senderSession:aliceSession]); - break; + switch (bobMessageCount++) + { + case 1: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[1] senderSession:aliceSession]); + break; - case 2: - XCTAssertEqual(event.eventType, MXEventTypeRoomEncrypted); - XCTAssertNil(event.clearEvent); - XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorUnknownInboundSessionIdCode); - break; + case 2: + XCTAssertEqual(event.eventType, MXEventTypeRoomEncrypted); + XCTAssertNil(event.clearEvent); + XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorUnknownInboundSessionIdCode); + break; - case 3: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[3] senderSession:aliceSession]); - break; + case 3: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[3] senderSession:aliceSession]); + break; - case 4: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[4] senderSession:aliceSession]); - break; + case 4: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[4] senderSession:aliceSession]); + break; - case 5: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[5] senderSession:aliceSession]); + case 5: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[5] senderSession:aliceSession]); - if (samMessageCount > 5) - { - [expectation fulfill]; - } - break; + if (samMessageCount > 5) + { + [expectation fulfill]; + } + break; - default: - break; - } + default: + break; + } + }]; }]; - [roomFromSamPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromSamPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - switch (samMessageCount++) - { - case 1: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[1] senderSession:aliceSession]); - break; + switch (samMessageCount++) + { + case 1: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[1] senderSession:aliceSession]); + break; - case 2: - XCTAssertEqual(event.eventType, MXEventTypeRoomEncrypted); - XCTAssertNil(event.clearEvent); - XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorUnknownInboundSessionIdCode); - break; + case 2: + XCTAssertEqual(event.eventType, MXEventTypeRoomEncrypted); + XCTAssertNil(event.clearEvent); + XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorUnknownInboundSessionIdCode); + break; - case 3: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[3] senderSession:aliceSession]); - break; + case 3: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[3] senderSession:aliceSession]); + break; - case 4: - XCTAssertEqual(event.eventType, MXEventTypeRoomEncrypted); - XCTAssertNil(event.clearEvent); - XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorUnknownInboundSessionIdCode); - break; + case 4: + XCTAssertEqual(event.eventType, MXEventTypeRoomEncrypted); + XCTAssertNil(event.clearEvent); + XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorUnknownInboundSessionIdCode); + break; - case 5: - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[5] senderSession:aliceSession]); + case 5: + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:aliceMessages[5] senderSession:aliceSession]); - if (bobMessageCount > 5) - { - [expectation fulfill]; - } - break; + if (bobMessageCount > 5) + { + [expectation fulfill]; + } + break; - default: - break; - } - }]; + default: + break; + } + }]; + }]; // Let alice sends messages and control this test flow [roomFromAlicePOV sendTextMessage:aliceMessages[0] success:^(NSString *eventId) { @@ -1274,77 +1314,80 @@ - (void)testBlackListUnverifiedDevices __block NSUInteger aliceMessageCount = 1; - [roomFromAlicePOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromAlicePOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - switch (aliceMessageCount++) - { - case 1: + switch (aliceMessageCount++) { - // Alice blacklists the unverified devices - aliceSession.crypto.globalBlacklistUnverifiedDevices = YES; + case 1: + { + // Alice blacklists the unverified devices + aliceSession.crypto.globalBlacklistUnverifiedDevices = YES; - [roomFromAlicePOV sendTextMessage:aliceMessages[2] success:nil failure:^(NSError *error) { - XCTFail(@"Alice should be able to send message #2 - error: %@", error); - [expectation fulfill]; - }]; + [roomFromAlicePOV sendTextMessage:aliceMessages[2] success:nil failure:^(NSError *error) { + XCTFail(@"Alice should be able to send message #2 - error: %@", error); + [expectation fulfill]; + }]; - break; - } + break; + } - case 2: - { - // Alice unblacklists the unverified devices - aliceSession.crypto.globalBlacklistUnverifiedDevices = NO; + case 2: + { + // Alice unblacklists the unverified devices + aliceSession.crypto.globalBlacklistUnverifiedDevices = NO; - [roomFromAlicePOV sendTextMessage:aliceMessages[3] success:nil failure:^(NSError *error) { - XCTFail(@"Alice should be able to send message #3 - error: %@", error); - [expectation fulfill]; - }]; + [roomFromAlicePOV sendTextMessage:aliceMessages[3] success:nil failure:^(NSError *error) { + XCTFail(@"Alice should be able to send message #3 - error: %@", error); + [expectation fulfill]; + }]; - break; - } + break; + } - case 3: - { - // Alice verifies the Bob device and blacklists the unverified devices in the current room - XCTAssertFalse([aliceSession.crypto isBlacklistUnverifiedDevicesInRoom:roomId]); - [aliceSession.crypto setBlacklistUnverifiedDevicesInRoom:roomId blacklist:YES]; - XCTAssert([aliceSession.crypto isBlacklistUnverifiedDevicesInRoom:roomId]); + case 3: + { + // Alice verifies the Bob device and blacklists the unverified devices in the current room + XCTAssertFalse([aliceSession.crypto isBlacklistUnverifiedDevicesInRoom:roomId]); + [aliceSession.crypto setBlacklistUnverifiedDevicesInRoom:roomId blacklist:YES]; + XCTAssert([aliceSession.crypto isBlacklistUnverifiedDevicesInRoom:roomId]); - NSString *bobDeviceId = [unknownDevices deviceIdsForUser:bobSession.myUser.userId][0]; - [aliceSession.crypto setDeviceVerification:MXDeviceVerified forDevice:bobDeviceId ofUser:bobSession.myUser.userId success:^{ + NSString *bobDeviceId = [unknownDevices deviceIdsForUser:bobSession.myUser.userId][0]; + [aliceSession.crypto setDeviceVerification:MXDeviceVerified forDevice:bobDeviceId ofUser:bobSession.myUser.userId success:^{ - [roomFromAlicePOV sendTextMessage:aliceMessages[4] success:nil failure:^(NSError *error) { - XCTFail(@"Alice should be able to send message #4 - error: %@", error); + [roomFromAlicePOV sendTextMessage:aliceMessages[4] success:nil failure:^(NSError *error) { + XCTFail(@"Alice should be able to send message #4 - error: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); [expectation fulfill]; }]; - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - - break; - } + break; + } - case 4: - { - // Alice unblacklists the unverified devices - XCTAssert([aliceSession.crypto isBlacklistUnverifiedDevicesInRoom:roomId]); - [aliceSession.crypto setBlacklistUnverifiedDevicesInRoom:roomId blacklist:NO]; - XCTAssertFalse([aliceSession.crypto isBlacklistUnverifiedDevicesInRoom:roomId]); + case 4: + { + // Alice unblacklists the unverified devices + XCTAssert([aliceSession.crypto isBlacklistUnverifiedDevicesInRoom:roomId]); + [aliceSession.crypto setBlacklistUnverifiedDevicesInRoom:roomId blacklist:NO]; + XCTAssertFalse([aliceSession.crypto isBlacklistUnverifiedDevicesInRoom:roomId]); + + [roomFromAlicePOV sendTextMessage:aliceMessages[5] success:nil failure:^(NSError *error) { + XCTFail(@"Alice should be able to send message #5 - error: %@", error); + [expectation fulfill]; + }]; - [roomFromAlicePOV sendTextMessage:aliceMessages[5] success:nil failure:^(NSError *error) { - XCTFail(@"Alice should be able to send message #5 - error: %@", error); - [expectation fulfill]; - }]; + break; + } - break; + default: + break; } - default: - break; - } + }]; }]; @@ -1363,6 +1406,160 @@ - (void)testBlackListUnverifiedDevices } +// Test method copy from MXRoomTests -testSendReplyToTextMessage +- (void)testSendReplyToTextMessage +{ + NSString *firstMessage = @"**First message!**"; + NSString *firstFormattedMessage = @"

First message!

"; + + NSString *secondMessageReplyToFirst = @"**Reply to first message**"; + NSString *secondMessageFormattedReplyToFirst = @"

Reply to first message

"; + + NSString *expectedSecondEventBodyStringFormat = @"> <%@> **First message!**\n\n**Reply to first message**"; + NSString *expectedSecondEventFormattedBodyStringFormat = @"
In reply to %@

First message!

Reply to first message

"; + + NSString *thirdMessageReplyToSecond = @"**Reply to second message**"; + NSString *thirdMessageFormattedReplyToSecond = @"

Reply to second message

"; + + NSString *expectedThirdEventBodyStringFormat = @"> <%@> **Reply to first message**\n\n**Reply to second message**"; + NSString *expectedThirdEventFormattedBodyStringFormat = @"
In reply to %@

Reply to first message

Reply to second message

"; + + MXSendReplyEventDefaultStringLocalizations *defaultStringLocalizations = [MXSendReplyEventDefaultStringLocalizations new]; + + __block NSUInteger successFullfillCount = 0; + NSUInteger expectedSuccessFulfillCount = 2; // Bob and Alice have finished their tests + + [matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoom:self cryptedBob:YES warnOnUnknowDevices:NO readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { + + void (^testExpectationFullfillIfComplete)(void) = ^() { + successFullfillCount++; + if (successFullfillCount == expectedSuccessFulfillCount) + { + [expectation fulfill]; + } + }; + + __block NSUInteger messageCount = 0; + __block NSUInteger messageCountFromAlice = 0; + + MXRoom *roomFromBobPOV = [bobSession roomWithRoomId:roomId]; + MXRoom *roomFromAlicePOV = [aliceSession roomWithRoomId:roomId]; + + // Listen to messages from Bob POV + [roomFromBobPOV listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + messageCount++; + + if (messageCount == 1) + { + __block MXEvent *localEchoEvent = nil; + + // Reply to first message + [roomFromBobPOV sendReplyToEvent:event withTextMessage:secondMessageReplyToFirst formattedTextMessage:secondMessageFormattedReplyToFirst stringLocalizations:defaultStringLocalizations localEcho:&localEchoEvent success:^(NSString *eventId) { + NSLog(@"Send reply to first message with success"); + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + XCTAssertNotNil(localEchoEvent); + + NSString *firstEventId = event.eventId; + NSString *firstEventSender = event.sender; + + NSString *secondEventBody = localEchoEvent.content[@"body"]; + NSString *secondEventFormattedBody = localEchoEvent.content[@"formatted_body"]; + NSString *secondEventRelatesToEventId = localEchoEvent.content[@"m.relates_to"][@"m.in_reply_to"][@"event_id"]; + NSString *secondWiredEventRelatesToEventId = localEchoEvent.wireContent[@"m.relates_to"][@"m.in_reply_to"][@"event_id"]; + + NSString *permalinkToUser = [MXTools permalinkToUserWithUserId:firstEventSender]; + NSString *permalinkToEvent = [MXTools permalinkToEvent:firstEventId inRoom:roomId]; + + NSString *expectedSecondEventBody = [NSString stringWithFormat:expectedSecondEventBodyStringFormat, firstEventSender]; + NSString *expectedSecondEventFormattedBody = [NSString stringWithFormat:expectedSecondEventFormattedBodyStringFormat, permalinkToEvent, permalinkToUser, firstEventSender]; + + XCTAssertEqualObjects(secondEventBody, expectedSecondEventBody); + XCTAssertEqualObjects(secondEventFormattedBody, expectedSecondEventFormattedBody); + XCTAssertEqualObjects(secondEventRelatesToEventId, firstEventId); + XCTAssertEqualObjects(secondWiredEventRelatesToEventId, firstEventId); + } + else if (messageCount == 2) + { + __block MXEvent *localEchoEvent = nil; + + // Reply to second message, which was also a reply + [roomFromBobPOV sendReplyToEvent:event withTextMessage:thirdMessageReplyToSecond formattedTextMessage:thirdMessageFormattedReplyToSecond stringLocalizations:defaultStringLocalizations localEcho:&localEchoEvent success:^(NSString *eventId) { + NSLog(@"Send reply to second message with success"); + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + XCTAssertNotNil(localEchoEvent); + + NSString *secondEventId = event.eventId; + NSString *secondEventSender = event.sender; + + NSString *thirdEventBody = localEchoEvent.content[@"body"]; + NSString *thirdEventFormattedBody = localEchoEvent.content[@"formatted_body"]; + NSString *thirdEventRelatesToEventId = localEchoEvent.content[@"m.relates_to"][@"m.in_reply_to"][@"event_id"]; + NSString *thirdWiredEventRelatesToEventId = localEchoEvent.wireContent[@"m.relates_to"][@"m.in_reply_to"][@"event_id"]; + + NSString *permalinkToUser = [MXTools permalinkToUserWithUserId:secondEventSender]; + NSString *permalinkToEvent = [MXTools permalinkToEvent:secondEventId inRoom:roomId]; + + NSString *expectedThirdEventBody = [NSString stringWithFormat:expectedThirdEventBodyStringFormat, secondEventSender]; + NSString *expectedThirdEventFormattedBody = [NSString stringWithFormat:expectedThirdEventFormattedBodyStringFormat, permalinkToEvent, permalinkToUser, secondEventSender]; + + + XCTAssertEqualObjects(thirdEventBody, expectedThirdEventBody); + XCTAssertEqualObjects(thirdEventFormattedBody, expectedThirdEventFormattedBody); + XCTAssertEqualObjects(thirdEventRelatesToEventId, secondEventId); + XCTAssertEqualObjects(thirdWiredEventRelatesToEventId, secondEventId); + } + else + { + testExpectationFullfillIfComplete(); + } + }]; + + __block NSString *firstEventId; + __block NSString *secondEventId; + + // Listen to messages from Alice POV + [roomFromAlicePOV listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + messageCountFromAlice++; + + if (messageCountFromAlice == 1) + { + firstEventId = event.eventId; + } + else if (messageCountFromAlice == 2) + { + secondEventId = event.eventId; + NSString *secondWiredEventRelatesToEventId = event.wireContent[@"m.relates_to"][@"m.in_reply_to"][@"event_id"]; + + XCTAssertEqualObjects(secondWiredEventRelatesToEventId, firstEventId); + } + else + { + NSString *thirdWiredEventRelatesToEventId = event.wireContent[@"m.relates_to"][@"m.in_reply_to"][@"event_id"]; + + XCTAssertEqualObjects(thirdWiredEventRelatesToEventId, secondEventId); + + testExpectationFullfillIfComplete(); + } + }]; + + // Send first message + [roomFromBobPOV sendTextMessage:firstMessage formattedText:firstFormattedMessage localEcho:nil success:^(NSString *eventId) { + NSLog(@"Send first message with success"); + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + #pragma mark - Edge cases @@ -1378,27 +1575,30 @@ - (void)testReplayAttack MXRoom *roomFromBobPOV = [bobSession roomWithRoomId:roomId]; MXRoom *roomFromAlicePOV = [aliceSession roomWithRoomId:roomId]; - XCTAssert(roomFromBobPOV.state.isEncrypted); - XCTAssert(roomFromAlicePOV.state.isEncrypted); + XCTAssert(roomFromBobPOV.summary.isEncrypted); + XCTAssert(roomFromAlicePOV.summary.isEncrypted); - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - // Try to decrypt the event again - [event setClearData:nil]; - BOOL b = [bobSession decryptEvent:event inTimeline:roomFromBobPOV.liveTimeline.timelineId]; + // Try to decrypt the event again + [event setClearData:nil]; + BOOL b = [bobSession decryptEvent:event inTimeline:liveTimeline.timelineId]; - // It must fail - XCTAssertFalse(b); - XCTAssert(event.decryptionError); - XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorDuplicateMessageIndexCode); - XCTAssertNil(event.clearEvent); + // It must fail + XCTAssertFalse(b); + XCTAssert(event.decryptionError); + XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorDuplicateMessageIndexCode); + XCTAssertNil(event.clearEvent); - // Decrypting it with no replay attack mitigation must still work - b = [bobSession decryptEvent:event inTimeline:nil]; - XCTAssert(b); - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); + // Decrypting it with no replay attack mitigation must still work + b = [bobSession decryptEvent:event inTimeline:nil]; + XCTAssert(b); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); + + [expectation fulfill]; + }]; - [expectation fulfill]; }]; [roomFromAlicePOV sendTextMessage:messageFromAlice success:nil failure:^(NSError *error) { @@ -1428,36 +1628,38 @@ - (void)testRoomKeyReshare }]; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); - // Reinject a modified version of the received room_key event from Alice. - // From Bob pov, that mimics Alice resharing her keys but with an advanced outbound group session. - XCTAssert(toDeviceEvent); - NSString *sessionId = toDeviceEvent.content[@"session_id"]; + // Reinject a modified version of the received room_key event from Alice. + // From Bob pov, that mimics Alice resharing her keys but with an advanced outbound group session. + XCTAssert(toDeviceEvent); + NSString *sessionId = toDeviceEvent.content[@"session_id"]; - NSMutableDictionary *newContent = [NSMutableDictionary dictionaryWithDictionary:toDeviceEvent.content]; - newContent[@"session_key"] = [aliceSession.crypto.olmDevice sessionKeyForOutboundGroupSession:sessionId]; - toDeviceEvent.clearEvent.wireContent = newContent; + NSMutableDictionary *newContent = [NSMutableDictionary dictionaryWithDictionary:toDeviceEvent.content]; + newContent[@"session_key"] = [aliceSession.crypto.olmDevice sessionKeyForOutboundGroupSession:sessionId]; + toDeviceEvent.clearEvent.wireContent = newContent; - [[NSNotificationCenter defaultCenter] postNotificationName:kMXSessionOnToDeviceEventNotification - object:bobSession - userInfo:@{ - kMXSessionNotificationEventKey: toDeviceEvent - }]; + [[NSNotificationCenter defaultCenter] postNotificationName:kMXSessionOnToDeviceEventNotification + object:bobSession + userInfo:@{ + kMXSessionNotificationEventKey: toDeviceEvent + }]; - // We still must be able to decrypt the event - // ie, the implementation must have ignored the new room key with the advanced outbound group - // session key - [event setClearData:nil]; - BOOL b = [bobSession decryptEvent:event inTimeline:nil]; + // We still must be able to decrypt the event + // ie, the implementation must have ignored the new room key with the advanced outbound group + // session key + [event setClearData:nil]; + BOOL b = [bobSession decryptEvent:event inTimeline:nil]; - XCTAssert(b); - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); + XCTAssert(b); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); - [expectation fulfill]; + [expectation fulfill]; + }]; }]; [roomFromAlicePOV sendTextMessage:messageFromAlice success:nil failure:^(NSError *error) { @@ -1486,41 +1688,43 @@ - (void)testLateRoomKey toDeviceEvent = notif.userInfo[kMXSessionNotificationEventKey]; }]; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); - // Make crypto forget the inbound group session - XCTAssert(toDeviceEvent); - NSString *sessionId = toDeviceEvent.content[@"session_id"]; + // Make crypto forget the inbound group session + XCTAssert(toDeviceEvent); + NSString *sessionId = toDeviceEvent.content[@"session_id"]; - id bobCryptoStore = (id)[bobSession.crypto.olmDevice valueForKey:@"store"]; - [bobCryptoStore removeInboundGroupSessionWithId:sessionId andSenderKey:toDeviceEvent.senderKey]; + id bobCryptoStore = (id)[bobSession.crypto.olmDevice valueForKey:@"store"]; + [bobCryptoStore removeInboundGroupSessionWithId:sessionId andSenderKey:toDeviceEvent.senderKey]; - // So that we cannot decrypt it anymore right now - [event setClearData:nil]; - BOOL b = [bobSession decryptEvent:event inTimeline:nil]; + // So that we cannot decrypt it anymore right now + [event setClearData:nil]; + BOOL b = [bobSession decryptEvent:event inTimeline:nil]; - XCTAssertFalse(b); - XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorUnknownInboundSessionIdCode); + XCTAssertFalse(b); + XCTAssertEqual(event.decryptionError.code, MXDecryptingErrorUnknownInboundSessionIdCode); - // The event must be decrypted once we reinject the m.room_key event - __block __weak id observer2 = [[NSNotificationCenter defaultCenter] addObserverForName:kMXEventDidDecryptNotification object:event queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + // The event must be decrypted once we reinject the m.room_key event + __block __weak id observer2 = [[NSNotificationCenter defaultCenter] addObserverForName:kMXEventDidDecryptNotification object:event queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { - XCTAssert([NSThread currentThread].isMainThread); + XCTAssert([NSThread currentThread].isMainThread); - [[NSNotificationCenter defaultCenter] removeObserver:observer2]; + [[NSNotificationCenter defaultCenter] removeObserver:observer2]; - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); - [expectation fulfill]; - }]; + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); + [expectation fulfill]; + }]; - // Reinject the m.room_key event. This mimics a room_key event that arrives after message events. - [[NSNotificationCenter defaultCenter] postNotificationName:kMXSessionOnToDeviceEventNotification - object:bobSession - userInfo:@{ - kMXSessionNotificationEventKey: toDeviceEvent - }]; + // Reinject the m.room_key event. This mimics a room_key event that arrives after message events. + [[NSNotificationCenter defaultCenter] postNotificationName:kMXSessionOnToDeviceEventNotification + object:bobSession + userInfo:@{ + kMXSessionNotificationEventKey: toDeviceEvent + }]; + }]; }]; [roomFromAlicePOV sendTextMessage:messageFromAlice success:nil failure:^(NSError *error) { @@ -1554,11 +1758,13 @@ - (void)testFirstMessageSentWhileSessionWasPaused __block BOOL testDone = NO; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); - testDone = YES; + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession]); + testDone = YES; + }]; }]; [bobSession resume:^{ @@ -1594,47 +1800,55 @@ - (void)testLeftAndJoinedBob [bobSession joinRoom:roomFromAlicePOV.roomId success:^(MXRoom *roomFromBobPOV) { - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [roomFromBobPOV.liveTimeline removeAllListeners]; + [liveTimeline removeAllListeners]; - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomFromBobPOV.roomId clearMessage:messageFromAlice senderSession:aliceSession]); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomFromBobPOV.roomId clearMessage:messageFromAlice senderSession:aliceSession]); - [roomFromBobPOV leave:^{ + [roomFromBobPOV leave:^{ - // Make Bob come back to the room with a new device - // Clear his crypto store - [bobSession enableCrypto:NO success:^{ + // Make Bob come back to the room with a new device + // Clear his crypto store + [bobSession enableCrypto:NO success:^{ - // Relog bob to simulate a new device - [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; - [matrixSDKTestsData relogUserSession:bobSession withPassword:MXTESTS_BOB_PWD onComplete:^(MXSession *bobSession2) { + // Relog bob to simulate a new device + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; + [matrixSDKTestsData relogUserSession:bobSession withPassword:MXTESTS_BOB_PWD onComplete:^(MXSession *bobSession2) { - [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = NO; + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = NO; - [bobSession2 joinRoom:roomFromAlicePOV.roomId success:^(MXRoom *roomFromBobPOV2) { + [bobSession2 joinRoom:roomFromAlicePOV.roomId success:^(MXRoom *roomFromBobPOV2) { - // Bob should be able to receive the message from Alice - [roomFromBobPOV2.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + // Bob should be able to receive the message from Alice + [roomFromBobPOV2 liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssert(event.clearEvent, @"Bob must be able to decrypt this new message on his new device"); + XCTAssert(event.clearEvent, @"Bob must be able to decrypt this new message on his new device"); - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomFromBobPOV2.roomId clearMessage:message2FromAlice senderSession:aliceSession]); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomFromBobPOV2.roomId clearMessage:message2FromAlice senderSession:aliceSession]); - [expectation fulfill]; + [expectation fulfill]; - }]; + }]; + }]; + + [roomFromAlicePOV sendTextMessage:message2FromAlice success:nil failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; - [roomFromAlicePOV sendTextMessage:message2FromAlice success:nil failure:^(NSError *error) { + } failure:^(NSError *error) { XCTFail(@"Cannot set up intial test conditions - error: %@", error); [expectation fulfill]; }]; - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; }]; - + + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; }]; } failure:^(NSError *error) { @@ -1642,11 +1856,7 @@ - (void)testLeftAndJoinedBob [expectation fulfill]; }]; - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; }]; - }]; [roomFromAlicePOV sendTextMessage:messageFromAlice success:nil failure:^(NSError *error) { @@ -1712,12 +1922,14 @@ - (void)testLeftBobAndAliceWithNewDevice NSString *messageFromBob = @"Hello Alice with new device!"; - [roomFromAlice2POV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromAlice2POV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:newRoomId clearMessage:messageFromBob senderSession:bobSession]); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:newRoomId clearMessage:messageFromBob senderSession:bobSession]); - [expectation fulfill]; + [expectation fulfill]; + }]; }]; [roomFromBobPOV sendTextMessage:messageFromBob success:nil failure:^(NSError *error) { @@ -1800,17 +2012,19 @@ - (void)testEnableEncryptionAfterNonCryptedMessages // Turn the crypto ON in the room [roomFromAlicePOV enableEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm success:^{ - [roomFromNewBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromNewBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssert(event.clearEvent, @"Bob must be able to decrypt message from his new device after the crypto is ON"); + XCTAssert(event.clearEvent, @"Bob must be able to decrypt message from his new device after the crypto is ON"); - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomFromNewBobPOV.roomId clearMessage:encryptedMessageFromAlice senderSession:aliceSession]); + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomFromNewBobPOV.roomId clearMessage:encryptedMessageFromAlice senderSession:aliceSession]); - NSDictionary *bobDevices = [aliceSession.crypto.store devicesForUser:newBobSession.myUser.userId]; - XCTAssertEqual(bobDevices.count, 1, @"Alice must now know Bob's device keys"); + NSDictionary *bobDevices = [aliceSession.crypto.store devicesForUser:newBobSession.myUser.userId]; + XCTAssertEqual(bobDevices.count, 1, @"Alice must now know Bob's device keys"); - [expectation fulfill]; + [expectation fulfill]; + }]; }]; // Post an encrypted message @@ -2013,40 +2227,42 @@ - (void)testImportRoomKeys NSMutableArray *encryptedEvents = [NSMutableArray array]; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [encryptedEvents addObject:event]; - }]; + [encryptedEvents addObject:event]; + }]; - [roomFromBobPOV.liveTimeline resetPagination]; - [roomFromBobPOV.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + [liveTimeline resetPagination]; + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - XCTAssertEqual(encryptedEvents.count, 5, @"There are 5 encrypted messages in the room. They cannot be decrypted at this step in the test"); + XCTAssertEqual(encryptedEvents.count, 5, @"There are 5 encrypted messages in the room. They cannot be decrypted at this step in the test"); - // All these events must be decrypted once we import the keys - observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXEventDidDecryptNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + // All these events must be decrypted once we import the keys + observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXEventDidDecryptNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { - [encryptedEvents removeObject:note.object]; - }]; + [encryptedEvents removeObject:note.object]; + }]; - // Import the exported keys - [bobSession.crypto importRoomKeys:keys success:^{ + // Import the exported keys + [bobSession.crypto importRoomKeys:keys success:^{ - XCTAssertEqual(encryptedEvents.count, 0, @"All events should have been decrypted after the keys import"); + XCTAssertEqual(encryptedEvents.count, 0, @"All events should have been decrypted after the keys import"); - [expectation fulfill]; + [expectation fulfill]; - } failure:^(NSError *error) { + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { XCTFail(@"The operation should not fail - NSError: %@", error); [expectation fulfill]; }]; - - } failure:^(NSError *error) { - XCTFail(@"The operation should not fail - NSError: %@", error); - [expectation fulfill]; }]; } failure:^(NSError *error) { @@ -2092,40 +2308,42 @@ - (void)testExportImportRoomKeysWithPassword NSMutableArray *encryptedEvents = [NSMutableArray array]; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [encryptedEvents addObject:event]; - }]; + [encryptedEvents addObject:event]; + }]; - [roomFromBobPOV.liveTimeline resetPagination]; - [roomFromBobPOV.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + [liveTimeline resetPagination]; + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - XCTAssertEqual(encryptedEvents.count, 5, @"There are 5 encrypted messages in the room. They cannot be decrypted at this step in the test"); + XCTAssertEqual(encryptedEvents.count, 5, @"There are 5 encrypted messages in the room. They cannot be decrypted at this step in the test"); - // All these events must be decrypted once we import the keys - observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXEventDidDecryptNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + // All these events must be decrypted once we import the keys + observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXEventDidDecryptNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { - [encryptedEvents removeObject:note.object]; - }]; + [encryptedEvents removeObject:note.object]; + }]; - // Import the exported keys - [bobSession.crypto importRoomKeys:keyFile withPassword:password success:^{ + // Import the exported keys + [bobSession.crypto importRoomKeys:keyFile withPassword:password success:^{ - XCTAssertEqual(encryptedEvents.count, 0, @"All events should have been decrypted after the keys import"); + XCTAssertEqual(encryptedEvents.count, 0, @"All events should have been decrypted after the keys import"); - [expectation fulfill]; + [expectation fulfill]; - } failure:^(NSError *error) { + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { XCTFail(@"The operation should not fail - NSError: %@", error); [expectation fulfill]; }]; - - } failure:^(NSError *error) { - XCTFail(@"The operation should not fail - NSError: %@", error); - [expectation fulfill]; }]; } failure:^(NSError *error) { @@ -2220,86 +2438,89 @@ - (void)testIncomingRoomKeyRequest [expectation fulfill]; }]; - [roomFromAlice2POV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession2]); - - // 5 - Instantiante a MXRestclient, alice1MatrixRestClient - MXRestClient *alice1MatrixRestClient = [[MXRestClient alloc] initWithCredentials:alice1Credentials andOnUnrecognizedCertificateBlock:nil]; - [matrixSDKTestsData retain:alice1MatrixRestClient]; - - // 6 - Make alice1MatrixRestClient make a fake room key request for the message sent at step #4 - NSDictionary *requestMessage = @{ - @"action": @"request", - @"body": @{ - @"algorithm": event.wireContent[@"algorithm"], - @"room_id": roomId, - @"sender_key": event.wireContent[@"sender_key"], - @"session_id": event.wireContent[@"session_id"] - }, - @"request_id": @"my_request_id", - @"requesting_device_id": alice1Credentials.deviceId - }; + [roomFromAlice2POV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - MXUsersDevicesMap *contentMap = [[MXUsersDevicesMap alloc] init]; - [contentMap setObject:requestMessage forUser:alice1Credentials.userId andDevice:@"*"]; + XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession2]); - [alice1MatrixRestClient sendToDevice:kMXEventTypeStringRoomKeyRequest contentMap:contentMap txnId:requestMessage[@"request_id"] success:nil failure:^(NSError *error) { - XCTFail(@"The operation should not fail - NSError: %@", error); - [expectation fulfill]; - }]; + // 5 - Instantiante a MXRestclient, alice1MatrixRestClient + MXRestClient *alice1MatrixRestClient = [[MXRestClient alloc] initWithCredentials:alice1Credentials andOnUnrecognizedCertificateBlock:nil]; + [matrixSDKTestsData retain:alice1MatrixRestClient]; + + // 6 - Make alice1MatrixRestClient make a fake room key request for the message sent at step #4 + NSDictionary *requestMessage = @{ + @"action": @"request", + @"body": @{ + @"algorithm": event.wireContent[@"algorithm"], + @"room_id": roomId, + @"sender_key": event.wireContent[@"sender_key"], + @"session_id": event.wireContent[@"session_id"] + }, + @"request_id": @"my_request_id", + @"requesting_device_id": alice1Credentials.deviceId + }; + + MXUsersDevicesMap *contentMap = [[MXUsersDevicesMap alloc] init]; + [contentMap setObject:requestMessage forUser:alice1Credentials.userId andDevice:@"*"]; + + [alice1MatrixRestClient sendToDevice:kMXEventTypeStringRoomKeyRequest contentMap:contentMap txnId:requestMessage[@"request_id"] success:nil failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; - // 7 - aliceSession2 must receive kMXCryptoRoomKeyRequestNotification - observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXCryptoRoomKeyRequestNotification - object:aliceSession2.crypto - queue:[NSOperationQueue mainQueue] - usingBlock:^(NSNotification *notif) - { - // 8 - Do checks - MXIncomingRoomKeyRequest *incomingKeyRequest = notif.userInfo[kMXCryptoRoomKeyRequestNotificationRequestKey]; - XCTAssert(incomingKeyRequest); - XCTAssert([incomingKeyRequest isKindOfClass:MXIncomingRoomKeyRequest.class], @"Notified object must be indeed a MXIncomingRoomKeyRequest object. Not %@", incomingKeyRequest); + // 7 - aliceSession2 must receive kMXCryptoRoomKeyRequestNotification + observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXCryptoRoomKeyRequestNotification + object:aliceSession2.crypto + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *notif) + { + // 8 - Do checks + MXIncomingRoomKeyRequest *incomingKeyRequest = notif.userInfo[kMXCryptoRoomKeyRequestNotificationRequestKey]; + XCTAssert(incomingKeyRequest); + XCTAssert([incomingKeyRequest isKindOfClass:MXIncomingRoomKeyRequest.class], @"Notified object must be indeed a MXIncomingRoomKeyRequest object. Not %@", incomingKeyRequest); - XCTAssertEqualObjects(incomingKeyRequest.requestId, requestMessage[@"request_id"]); - XCTAssertEqualObjects(incomingKeyRequest.userId, alice1Credentials.userId); - XCTAssertEqualObjects(incomingKeyRequest.deviceId, alice1Credentials.deviceId); - XCTAssert(incomingKeyRequest.requestBody); + XCTAssertEqualObjects(incomingKeyRequest.requestId, requestMessage[@"request_id"]); + XCTAssertEqualObjects(incomingKeyRequest.userId, alice1Credentials.userId); + XCTAssertEqualObjects(incomingKeyRequest.deviceId, alice1Credentials.deviceId); + XCTAssert(incomingKeyRequest.requestBody); - //9 - Check [MXSession.crypto pendingKeyRequests:] result - [aliceSession2.crypto pendingKeyRequests:^(MXUsersDevicesMap *> *pendingKeyRequests) { + //9 - Check [MXSession.crypto pendingKeyRequests:] result + [aliceSession2.crypto pendingKeyRequests:^(MXUsersDevicesMap *> *pendingKeyRequests) { - XCTAssertEqual(pendingKeyRequests.count, 1); + XCTAssertEqual(pendingKeyRequests.count, 1); - MXIncomingRoomKeyRequest *keyRequest = [pendingKeyRequests objectForDevice:alice1Credentials.deviceId forUser:alice1Credentials.userId][0]; + MXIncomingRoomKeyRequest *keyRequest = [pendingKeyRequests objectForDevice:alice1Credentials.deviceId forUser:alice1Credentials.userId][0]; - // Should be the same request - XCTAssertEqualObjects(keyRequest.requestId, incomingKeyRequest.requestId); - XCTAssertEqualObjects(keyRequest.userId, incomingKeyRequest.userId); - XCTAssertEqualObjects(keyRequest.deviceId, incomingKeyRequest.deviceId); - XCTAssertEqualObjects(keyRequest.requestBody, incomingKeyRequest.requestBody); + // Should be the same request + XCTAssertEqualObjects(keyRequest.requestId, incomingKeyRequest.requestId); + XCTAssertEqualObjects(keyRequest.userId, incomingKeyRequest.userId); + XCTAssertEqualObjects(keyRequest.deviceId, incomingKeyRequest.deviceId); + XCTAssertEqualObjects(keyRequest.requestBody, incomingKeyRequest.requestBody); - // 10 - Check [MXSession.crypto acceptAllPendingKeyRequestsFromUser:] with a wrong userId:deviceId pair - [aliceSession2.crypto acceptAllPendingKeyRequestsFromUser:alice1Credentials.userId andDevice:@"DEADBEEF" onComplete:^{ + // 10 - Check [MXSession.crypto acceptAllPendingKeyRequestsFromUser:] with a wrong userId:deviceId pair + [aliceSession2.crypto acceptAllPendingKeyRequestsFromUser:alice1Credentials.userId andDevice:@"DEADBEEF" onComplete:^{ - [aliceSession2.crypto pendingKeyRequests:^(MXUsersDevicesMap *> *pendingKeyRequests2) { + [aliceSession2.crypto pendingKeyRequests:^(MXUsersDevicesMap *> *pendingKeyRequests2) { - XCTAssertEqual(pendingKeyRequests2.count, 1, @"The pending request should be still here"); + XCTAssertEqual(pendingKeyRequests2.count, 1, @"The pending request should be still here"); - // 11 - Check [MXSession.crypto acceptAllPendingKeyRequestsFromUser:] with a valid userId:deviceId pair - [aliceSession2.crypto acceptAllPendingKeyRequestsFromUser:alice1Credentials.userId andDevice:alice1Credentials.deviceId onComplete:^{ + // 11 - Check [MXSession.crypto acceptAllPendingKeyRequestsFromUser:] with a valid userId:deviceId pair + [aliceSession2.crypto acceptAllPendingKeyRequestsFromUser:alice1Credentials.userId andDevice:alice1Credentials.deviceId onComplete:^{ - [aliceSession2.crypto pendingKeyRequests:^(MXUsersDevicesMap *> *pendingKeyRequests3) { + [aliceSession2.crypto pendingKeyRequests:^(MXUsersDevicesMap *> *pendingKeyRequests3) { - XCTAssertEqual(pendingKeyRequests3.count, 0, @"There should be no more pending request"); + XCTAssertEqual(pendingKeyRequests3.count, 0, @"There should be no more pending request"); - [expectation fulfill]; + [expectation fulfill]; + }]; }]; }]; }]; }]; }]; - }]; + }]; }]; + }]; }]; } diff --git a/MatrixSDKTests/MXEventTests.m b/MatrixSDKTests/MXEventTests.m index 1a774120a5..30f53a8b25 100644 --- a/MatrixSDKTests/MXEventTests.m +++ b/MatrixSDKTests/MXEventTests.m @@ -106,33 +106,39 @@ - (void)testIsState [mxSession start:^{ MXRoom *room = [mxSession roomWithRoomId:roomId]; - - for (MXEvent *stateEvent in room.state.stateEvents) - { - XCTAssertTrue(stateEvent.isState, "All events in room.stateEvents must be states. stateEvent: %@", stateEvent); - } - - - __block NSUInteger eventCount = 0; - [room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - eventCount++; - XCTAssertFalse(event.isState, "Room messages are not states. message: %@", event); - - }]; - - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - - XCTAssertGreaterThan(eventCount, 0, "We should have received events in registerEventListenerForTypes"); - - [expectation fulfill]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + + [room state:^(MXRoomState *roomState) { + + for (MXEvent *stateEvent in roomState.stateEvents) + { + XCTAssertTrue(stateEvent.isState, "All events in room.stateEvents must be states. stateEvent: %@", stateEvent); + } + + + __block NSUInteger eventCount = 0; + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + eventCount++; + XCTAssertFalse(event.isState, "Room messages are not states. message: %@", event); + + }]; + + [liveTimeline resetPagination]; + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + + XCTAssertGreaterThan(eventCount, 0, "We should have received events in registerEventListenerForTypes"); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; }]; - + } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); [expectation fulfill]; diff --git a/MatrixSDKTests/MXFilterTests.m b/MatrixSDKTests/MXFilterTests.m index 53476051c1..e3bdecca4e 100644 --- a/MatrixSDKTests/MXFilterTests.m +++ b/MatrixSDKTests/MXFilterTests.m @@ -16,11 +16,24 @@ #import +#import "MatrixSDKTestsData.h" + #import "MXFilter.h" #import "MXRoomEventFilter.h" #import "MXRoomFilter.h" +#import "MXNoStore.h" +#import "MXFileStore.h" + + +// Do not bother with retain cycles warnings in tests +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-retain-cycles" + @interface MXFilterTests : XCTestCase +{ + MatrixSDKTestsData *matrixSDKTestsData; +} @end @@ -29,10 +42,14 @@ @implementation MXFilterTests - (void)setUp { [super setUp]; + + matrixSDKTestsData = [[MatrixSDKTestsData alloc] init]; } - (void)tearDown { + matrixSDKTestsData = nil; + [super tearDown]; } @@ -214,4 +231,175 @@ - (void)testEventFilterInit XCTAssertTrue([filter.dictionary isEqualToDictionary:dictionary], @"%@/%@", filter.dictionary, dictionary); } +// - Create a session with MXNoStore +// - Create a filter +// - Get it back +// - Compare them +- (void)testFilterAPI +{ + [matrixSDKTestsData doMXSessionTestWithBob:self andStore:[MXNoStore new] readyToTest:^(MXSession *mxSession, XCTestExpectation *expectation) { + + MXFilterJSONModel *filter = [MXFilterJSONModel syncFilterWithMessageLimit:0]; + + MXHTTPOperation *operation = [mxSession setFilter:filter success:^(NSString *filterId) { + + XCTAssertNotNil(filterId); + + MXHTTPOperation *operation2 = [mxSession filterWithFilterId:filterId success:^(MXFilterJSONModel *filter2) { + + XCTAssertEqualObjects(filter, filter2); + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + XCTAssertNotNil(operation2.operation, @"An HTTP request should have been made"); + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + XCTAssert(operation.operation, @"An HTTP request should have been made"); + }]; +} + +// - Create a session with MXNFileStore +// - Create a filter +// - Get it back (no HTTP should have been done) +// - Create it again (no HTTP should have been done) +// - Compare them +- (void)testFilterCache +{ + MXFileStore *store = [MXFileStore new]; + + [matrixSDKTestsData doMXSessionTestWithBob:self andStore:store readyToTest:^(MXSession *mxSession, XCTestExpectation *expectation) { + + MXFilterJSONModel *filter = [MXFilterJSONModel syncFilterWithMessageLimit:0]; + + MXHTTPOperation *operation = [mxSession setFilter:filter success:^(NSString *filterId) { + + XCTAssertNotNil(filterId); + + MXHTTPOperation *operation2 = [mxSession setFilter:filter success:^(NSString *filterId2) { + + XCTAssertNotNil(filterId); + XCTAssertEqualObjects(filterId, filterId2); + + MXHTTPOperation *operation3 = [mxSession filterWithFilterId:filterId success:^(MXFilterJSONModel *filter2) { + + XCTAssertEqualObjects(filter, filter2); + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + XCTAssertNil(operation3.operation, @"No HTTP request is required for filter already created"); + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + XCTAssertNil(operation2.operation, @"No HTTP request is required for filter already created"); + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + XCTAssert(operation.operation, @"An HTTP request should have been made"); + }]; +} + +// Check that filter data is permanent +- (void)testFilterPermanentStorage +{ + [matrixSDKTestsData doMXRestClientTestWithBob:self readyToTest:^(MXRestClient *bobRestClient, XCTestExpectation *expectation) { + + MXSession *mxSession = [[MXSession alloc] initWithMatrixRestClient:bobRestClient]; + [matrixSDKTestsData retain:mxSession]; + + [mxSession setStore:[MXFileStore new] success:^{ + + MXFilterJSONModel *filter = [MXFilterJSONModel syncFilterWithMessageLimit:12]; + + [mxSession startWithSyncFilter:filter onServerSyncDone:^{ + + NSString *syncFilterId = mxSession.syncFilterId; + XCTAssertNotNil(syncFilterId); + + [mxSession close]; + + // Check data directly in the store + MXFileStore *fileStore = [MXFileStore new]; + [fileStore openWithCredentials:bobRestClient.credentials onComplete:^{ + + NSString *filterId = fileStore.syncFilterId; + XCTAssertNotNil(filterId); + XCTAssertEqualObjects(syncFilterId, filterId); + + [fileStore filterWithFilterId:syncFilterId success:^(MXFilterJSONModel * _Nullable filter2) { + + XCTAssertNotNil(filter2); + XCTAssertEqualObjects(filter, filter2); + + [expectation fulfill]; + + } failure:^(NSError * _Nullable error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError * _Nullable error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + }]; +} + +// Check MXSession start if the passed filter is not supported by the homeserver +- (void)testUnsupportedSyncFilter +{ + [matrixSDKTestsData doMXRestClientTestWithBob:self readyToTest:^(MXRestClient *bobRestClient, XCTestExpectation *expectation) { + + MXSession *mxSession = [[MXSession alloc] initWithMatrixRestClient:bobRestClient]; + [matrixSDKTestsData retain:mxSession]; + + MXFilterJSONModel *badFilter = [MXFilterJSONModel modelFromJSON:@{ + @"room": @{ + @"say": @"hello" + } + }]; + + [mxSession startWithSyncFilter:badFilter onServerSyncDone:^{ + + XCTAssertNil(mxSession.syncFilterId); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + @end + +#pragma clang diagnostic pop + diff --git a/MatrixSDKTests/MXLazyLoadingTests.m b/MatrixSDKTests/MXLazyLoadingTests.m new file mode 100644 index 0000000000..13055a2178 --- /dev/null +++ b/MatrixSDKTests/MXLazyLoadingTests.m @@ -0,0 +1,1077 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MatrixSDKTestsData.h" +#import "MXSDKOptions.h" + +// Do not bother with retain cycles warnings in tests +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-retain-cycles" + +NSString * const bobMessage = @"I am Bob"; + +@interface MXLazyLoadingTests : XCTestCase +{ + MatrixSDKTestsData *matrixSDKTestsData; + + // The id of the message by Bob in the scenario + NSString *bobMessageEventId; +} + +@end + +@implementation MXLazyLoadingTests + +- (void)setUp +{ + [super setUp]; + + matrixSDKTestsData = [[MatrixSDKTestsData alloc] init]; +} + +- (void)tearDown +{ + [super tearDown]; + + matrixSDKTestsData = nil; +} + +/** +Common initial conditions: + - Alice, Bob in a room + - Charlie joins the room + - Dave is invited + - Alice sends 50 messages + - Bob sends one message + - Alice sends 50 messages + - Alice makes an initial /sync with lazy-loading enabled or not +*/ +- (void)createScenarioWithLazyLoading:(BOOL)lazyLoading + readyToTest:(void (^)(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString* roomId, XCTestExpectation *expectation))readyToTest +{ + [self createScenarioWithLazyLoading:lazyLoading inARoomWithName:YES readyToTest:readyToTest]; +} + +- (void)createScenarioWithLazyLoading:(BOOL)lazyLoading + inARoomWithName:(BOOL)inARoomWithName + readyToTest:(void (^)(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString* roomId, XCTestExpectation *expectation))readyToTest +{ + // - Alice, Bob in a room + [matrixSDKTestsData doMXSessionTestWithBobAndAliceInARoom:self readyToTest:^(MXSession *bobSession, MXRestClient *aliceRestClient, NSString *roomId, XCTestExpectation *expectation) { + + MXRoom *roomFromBobPOV = [bobSession roomWithRoomId:roomId]; + + if (inARoomWithName) + { + // Set a room name to prevent the HS from sending us heroes through the summary API. + // When the HS sends heroes, it also sends m.room.membership events for them. This breaks + // how this tests suite was written. + [roomFromBobPOV setName:@"A name" success:nil failure:nil]; + } + + [roomFromBobPOV setJoinRule:kMXRoomJoinRulePublic success:^{ + + // - Charlie joins the room + [matrixSDKTestsData doMXSessionTestWithAUser:nil readyToTest:^(MXSession *charlieSession, XCTestExpectation *expectation2) { + [charlieSession joinRoom:roomId success:^(MXRoom *room) { + + // - Dave is invited + [roomFromBobPOV inviteUser:@"@dave:localhost:8480" success:^{ + + // - Alice sends 50 messages + [matrixSDKTestsData for:aliceRestClient andRoom:roomId sendMessages:50 success:^{ + + // - Bob sends a message + [roomFromBobPOV sendTextMessage:bobMessage success:^(NSString *eventId) { + + bobMessageEventId = eventId; + + // - Alice sends 50 messages + [matrixSDKTestsData for:aliceRestClient andRoom:roomId sendMessages:50 success:^{ + + // - Alice makes an initial /sync + MXSession *aliceSession = [[MXSession alloc] initWithMatrixRestClient:aliceRestClient]; + [matrixSDKTestsData retain:aliceSession]; + + + // Alice makes an initial /sync with lazy-loading enabled or not + MXFilterJSONModel *filter; + if (lazyLoading) + { + filter = [MXFilterJSONModel syncFilterForLazyLoading]; + } + + [aliceSession startWithSyncFilter:filter onServerSyncDone:^{ + + // We are done + readyToTest(aliceSession, bobSession, charlieSession, roomId, expectation); + + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + }]; + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + }]; +} + + +- (void)testLazyLoadingFilterSupportHSSide +{ + [matrixSDKTestsData doMXRestClientTestWithBob:self readyToTest:^(MXRestClient *bobRestClient, XCTestExpectation *expectation) { + + MXSession *mxSession = [[MXSession alloc] initWithMatrixRestClient:bobRestClient]; + [matrixSDKTestsData retain:mxSession]; + + MXFilterJSONModel *lazyLoadingFilter = [MXFilterJSONModel syncFilterForLazyLoading]; + + [mxSession startWithSyncFilter:lazyLoadingFilter onServerSyncDone:^{ + + XCTAssertNotNil(mxSession.syncFilterId); + XCTAssertTrue(mxSession.syncWithLazyLoadOfRoomMembers); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + + +// After the test scenario, room state should be lazy loaded and partial. +// There should be only Alice and state.members.count = 1 +- (void)checkRoomStateWithLazyLoading:(BOOL)lazyLoading +{ + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + [room state:^(MXRoomState *roomState) { + + MXRoomMembers *lazyloadedRoomMembers = roomState.members; + + XCTAssert([lazyloadedRoomMembers memberWithUserId:aliceSession.myUser.userId]); + + if (lazyLoading) + { + XCTAssertEqual(lazyloadedRoomMembers.members.count, 1, @"There should be only Alice in the lazy loaded room state"); + + XCTAssertEqual(roomState.membersCount.members, 1); + XCTAssertEqual(roomState.membersCount.joined, 1); + XCTAssertEqual(roomState.membersCount.invited, 0); + } + else + { + // The room members list in the room state is full known + XCTAssertEqual(lazyloadedRoomMembers.members.count, 4); + + XCTAssertEqual(roomState.membersCount.members, 4); + XCTAssertEqual(roomState.membersCount.joined, 3); + XCTAssertEqual(roomState.membersCount.invited, 1); + } + + [expectation fulfill]; + }]; + }]; +} + +- (void)testRoomState +{ + [self checkRoomStateWithLazyLoading:YES]; +} + +- (void)testRoomStateWithLazyLoadingOFF +{ + [self checkRoomStateWithLazyLoading:NO]; +} + +// Check lazy loaded romm state when Charlie sends a new message +- (void)checkRoomStateOnIncomingMessage:(BOOL)lazyLoading +{ + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + NSString *messageFromCharlie = @"A new message from Charlie"; + + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + XCTAssertEqualObjects(event.content[@"body"], messageFromCharlie); + + XCTAssert([roomState.members memberWithUserId:aliceSession.myUser.userId]); + XCTAssert([roomState.members memberWithUserId:charlieSession.myUser.userId]); + + if (lazyLoading) + { + XCTAssertEqual(roomState.members.members.count, 2, @"There should only Alice and Charlie now in the lazy loaded room state"); + + XCTAssertEqual(roomState.membersCount.members, 2); + XCTAssertEqual(roomState.membersCount.joined, 2); + XCTAssertEqual(roomState.membersCount.invited, 0); + } + else + { + // The room members list in the room state is full known + XCTAssertEqual(roomState.members.members.count, 4); + + XCTAssertEqual(roomState.membersCount.members, 4); + XCTAssertEqual(roomState.membersCount.joined, 3); + XCTAssertEqual(roomState.membersCount.invited, 1); + } + + [expectation fulfill]; + }]; + + MXRoom *roomFromCharliePOV = [charlieSession roomWithRoomId:roomId]; + [roomFromCharliePOV sendTextMessage:messageFromCharlie success:nil failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + }]; + }]; +} + +- (void)testRoomStateOnIncomingMessage +{ + [self checkRoomStateOnIncomingMessage:YES]; +} + +- (void)testRoomStateOnIncomingMessageLazyLoadingOFF +{ + [self checkRoomStateOnIncomingMessage:NO]; +} + + +// When paginating back to the beginning, lazy loaded room state passed in pagination callback must be updated with Bob, then Charlie and Dave. +- (void)checkRoomStateWhilePaginatingWithLazyLoading:(BOOL)lazyLoading +{ + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + + __block NSUInteger messageCount = 0; + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + switch (++messageCount) { + case 50: + if (lazyLoading) + { + XCTAssertNil([roomState.members memberWithUserId:bobSession.myUser.userId]); + XCTAssertNil([liveTimeline.state.members memberWithUserId:bobSession.myUser.userId]); + XCTAssertNil([liveTimeline.state.members memberWithUserId:charlieSession.myUser.userId]); + } + else + { + XCTAssertNotNil([roomState.members memberWithUserId:bobSession.myUser.userId]); + } + + XCTAssertNotNil([liveTimeline.state.members memberWithUserId:aliceSession.myUser.userId]); + break; + case 51: + XCTAssert([roomState.members memberWithUserId:bobSession.myUser.userId]); + + if (lazyLoading) + { + XCTAssertNil([roomState.members memberWithUserId:charlieSession.myUser.userId]); + XCTAssertNil([liveTimeline.state.members memberWithUserId:charlieSession.myUser.userId]); + } + else + { + XCTAssert([roomState.members memberWithUserId:charlieSession.myUser.userId]); + } + + // The room state of the room should have been enriched + XCTAssertNotNil([liveTimeline.state.members memberWithUserId:bobSession.myUser.userId]); + + break; + + case 110: + XCTAssertNotNil([liveTimeline.state.members memberWithUserId:aliceSession.myUser.userId]); + XCTAssertNotNil([liveTimeline.state.members memberWithUserId:bobSession.myUser.userId]); + XCTAssertNotNil([liveTimeline.state.members memberWithUserId:charlieSession.myUser.userId]); + break; + + default: + break; + } + }]; + + // Paginate the 50 last message where there is only Alice talking + [liveTimeline resetPagination]; + [liveTimeline paginate:50 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + + // Pagine a bit more to get Bob's message + [liveTimeline paginate:25 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + + // Paginate all to get a full room state + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + + XCTAssertGreaterThan(messageCount, 110, @"The test has not fully run"); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; + }]; +} + +- (void)testRoomStateWhilePaginating +{ + [self checkRoomStateWhilePaginatingWithLazyLoading:YES]; +} + +- (void)testRoomStateWhilePaginatingWithLazyLoadingOFF +{ + [self checkRoomStateWhilePaginatingWithLazyLoading:NO]; +} + + +// As members is only partial, [room members:] should trigger an HTTP request +// and returns the 4 members. +- (void)checkRoomMembersWithLazyLoading:(BOOL)lazyLoading +{ + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + __block BOOL firstRequestComplete = NO; + + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + + MXHTTPOperation *operation = [room members:^(MXRoomMembers *roomMembers) { + + // The room members list in the room state is full known + XCTAssertEqual(roomMembers.members.count, 4); + XCTAssertEqual(roomMembers.joinedMembers.count, 3); + XCTAssertEqual([roomMembers membersWithMembership:MXMembershipInvite].count, 1); + + firstRequestComplete = YES; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + if (lazyLoading) + { + XCTAssert(operation.operation, @"As members is only partial, [room members:] should trigger an HTTP request"); + } + else + { + XCTAssertNil(operation.operation); + } + + MXHTTPOperation *secondOperation = [room members:^(MXRoomMembers *roomMembers) { + + XCTAssertTrue(firstRequestComplete); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + XCTAssertNil(secondOperation.operation, @"There must be no second request to /members"); + }]; +} + +- (void)testRoomMembers +{ + [self checkRoomMembersWithLazyLoading:YES]; +} + +- (void)testRoomMembersWithLazyLoadingOFF +{ + [self checkRoomMembersWithLazyLoading:NO]; +} + + +// [MXRoom members:] should make an HTTP request to fetch members only once +- (void)checkSingleRoomMembersRequestWithLazyLoading:(BOOL)lazyLoading +{ + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + + [room members:^(MXRoomMembers *members) { + + MXHTTPOperation *operation = [room members:^(MXRoomMembers *roomMembers) { + + // The room members list in the room state is full known + XCTAssertEqual(roomMembers.members.count, 4); + XCTAssertEqual(roomMembers.joinedMembers.count, 3); + XCTAssertEqual([roomMembers membersWithMembership:MXMembershipInvite].count, 1); + + [expectation fulfill]; + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + XCTAssertNil(operation.operation); + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +- (void)testSingleRoomMembersRequest +{ + [self checkSingleRoomMembersRequestWithLazyLoading:YES]; +} + +- (void)testSingleRoomMembersRequestWithLazyLoadingOFF +{ + [self checkSingleRoomMembersRequestWithLazyLoading:NO]; +} + + +// Check [room members:lazyLoadedMembers:] callbacks call +- (void)checkRoomMembersAndLazyLoadedMembersWithLazyLoading:(BOOL)lazyLoading +{ + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + + [room members:^(MXRoomMembers *members) { + + // The room members list in the room state is full known + XCTAssertEqual(members.members.count, 4); + XCTAssertEqual(members.joinedMembers.count, 3); + XCTAssertEqual([members membersWithMembership:MXMembershipInvite].count, 1); + + [expectation fulfill]; + + } lazyLoadedMembers:^(MXRoomMembers *lazyLoadedMembers) { + + if (lazyLoading) + { + XCTAssertEqual(lazyLoadedMembers.members.count, 1, @"There should be only Alice in the lazy loaded room state"); + XCTAssertEqual(lazyLoadedMembers.joinedMembers.count, 1); + XCTAssertEqual([lazyLoadedMembers membersWithMembership:MXMembershipInvite].count, 0); + } + else + { + XCTFail(@"This block should not be called when we already know all room members"); + } + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +- (void)testRoomMembersAndLazyLoadedMembers +{ + [self checkRoomMembersAndLazyLoadedMembersWithLazyLoading:YES]; +} + +- (void)testRoomMembersAndLazyLoadedMembersWithLazyLoadingOFF +{ + [self checkRoomMembersAndLazyLoadedMembersWithLazyLoading:NO]; +} + + +// Test MXRoomSummary.membership +// With the scenario, if Alice and Charlie do an /initialSync, they must see them as joined +// in the room. +- (void)checkSummaryMembershipWithLazyLoading:(BOOL)lazyLoading +{ + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + [room state:^(MXRoomState *roomState) { + + // Check Alice membership + MXRoomSummary *roomSummary = [aliceSession roomSummaryWithRoomId:roomId]; + XCTAssertEqual(roomSummary.membership, MXMembershipJoin); + + + // Check Charlie POV + // - Charlie makes an initial /sync + MXSession *charlieSession2 = [[MXSession alloc] initWithMatrixRestClient:charlieSession.matrixRestClient]; + [charlieSession close]; + [matrixSDKTestsData retain:charlieSession2]; + + MXFilterJSONModel *filter; + if (lazyLoading) + { + filter = [MXFilterJSONModel syncFilterForLazyLoading]; + } + + [charlieSession2 startWithSyncFilter:filter onServerSyncDone:^{ + + // Check Charlie membership + MXRoomSummary *roomFromCharliePOVSummary = [charlieSession2 roomSummaryWithRoomId:roomId]; + XCTAssertEqual(roomFromCharliePOVSummary.membership, MXMembershipJoin); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + }]; + }]; +} + +- (void)testSummaryMembership +{ + [self checkSummaryMembershipWithLazyLoading:YES]; +} + +- (void)testSummaryMembershipWithLazyLoadingOFF +{ + [self checkSummaryMembershipWithLazyLoading:NO]; +} + +// Complementary test to testSummaryMembership +// - Run the scenario +// - Pause Alice MXSession +// - Make her leave the room outside her MXSession +// - Bob sends 50 messages +// - Resume Alice MXSession +// -> Alice must not know the room anymore +- (void)checkRoomAfterLeavingFromAnotherDeviceWithLazyLoading:(BOOL)lazyLoading +{ + // - Run the scenario + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + MXRoomSummary *summary = [aliceSession roomSummaryWithRoomId:roomId]; + + XCTAssertNotNil(room); + XCTAssertNotNil(summary); + + // - Pause Alice MXSession + [aliceSession pause]; + + // - Make her leave the room outside her MXSession + [aliceSession.matrixRestClient leaveRoom:roomId success:^{ + + // - Bob sends 50 messages + [matrixSDKTestsData for:bobSession.matrixRestClient andRoom:roomId sendMessages:50 success:^{ + + + + // - Resume Alice MXSession + [aliceSession resume:^{ + + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + MXRoomSummary *summary = [aliceSession roomSummaryWithRoomId:roomId]; + + XCTAssertNil(room); + XCTAssertNil(summary); + + [expectation fulfill]; + }]; + }]; + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + }]; +} + +- (void)testRoomAfterLeavingFromAnotherDevice +{ + [self checkRoomAfterLeavingFromAnotherDeviceWithLazyLoading:YES]; +} + +- (void)testRoomAfterLeavingFromAnotherDeviceWithLazyLoadingOFF +{ + [self checkRoomAfterLeavingFromAnotherDeviceWithLazyLoading:NO]; +} + + +// roomSummary.membersCount must be right in both cases +- (void)checkRoomSummaryMembersCountWithLazyLoading:(BOOL)lazyLoading +{ + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + MXRoomSummary *roomSummary = [aliceSession roomSummaryWithRoomId:roomId]; + + XCTAssertEqual(roomSummary.membersCount.members, 4); + XCTAssertEqual(roomSummary.membersCount.joined, 3); + XCTAssertEqual(roomSummary.membersCount.invited, 1); + + [expectation fulfill]; + }]; +} + +- (void)testRoomSummaryMembersCount +{ + [self checkRoomSummaryMembersCountWithLazyLoading:YES]; +} + +- (void)testRoomSummaryMembersCountWithLazyLoadingOFF +{ + [self checkRoomSummaryMembersCountWithLazyLoading:NO]; +} + + +// Check room display name computed from heroes provided in the room summary +- (void)checkRoomSummaryDisplayNameFromHeroesWithLazyLoading:(BOOL)lazyLoading +{ + // Do not set a room name for this test + [self createScenarioWithLazyLoading:lazyLoading inARoomWithName:NO readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + [room state:^(MXRoomState *roomState) { + + // Membership events for heroes must have been lazy-loaded + // There are used to compute roomSummary.displayname + XCTAssertNotNil([roomState.members memberWithUserId:aliceSession.myUser.userId]); + XCTAssertNotNil([roomState.members memberWithUserId:bobSession.myUser.userId]); + XCTAssertNotNil([roomState.members memberWithUserId:charlieSession.myUser.userId]); + + MXRoomSummary *roomSummary = [aliceSession roomSummaryWithRoomId:roomId]; + + if (lazyLoading) + { + XCTAssertNotNil(roomSummary.displayname, @"Thanks to the summary api, the SDK can build a room display name"); + } + else + { + XCTAssertNil(roomSummary.displayname); + } + + [expectation fulfill]; + }]; + }]; +} + +- (void)testRoomSummaryDisplayNameFromHeroes +{ + [self checkRoomSummaryDisplayNameFromHeroesWithLazyLoading:YES]; +} + +- (void)testRoomSummaryDisplayNameFromHeroesWithLazyLoadingOFF +{ + [self checkRoomSummaryDisplayNameFromHeroesWithLazyLoading:NO]; +} + + +// Check encryption from a lazy loaded room state +// - Alice sends a message from its lazy loaded room state where there is no Charlie +// - Charlie must be able to decrypt it +- (void)checkEncryptedMessageWithLazyLoading:(BOOL)lazyLoading +{ + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = NO; + + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + [room listenToEventsOfTypes:@[kMXEventTypeStringRoomEncryption] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + aliceSession.crypto.warnOnUnknowDevices = NO; + + NSString *messageFromAlice = @"An encrypted message"; + + [charlieSession listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) { + + XCTAssertTrue(event.isEncrypted); + XCTAssert(event.clearEvent); + XCTAssertEqualObjects(event.content[@"body"], messageFromAlice); + + [expectation fulfill]; + }]; + + MXRoomSummary *summary = [aliceSession roomSummaryWithRoomId:roomId]; + XCTAssertTrue(summary.isEncrypted); + + [room sendTextMessage:messageFromAlice success:nil failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; + + MXRoom *roomFromBobPOV = [bobSession roomWithRoomId:roomId]; + [roomFromBobPOV enableEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm success:nil failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +- (void)testEncryptedMessage +{ + [self checkEncryptedMessageWithLazyLoading:YES]; +} + +- (void)testEncryptedMessageWithLazyLoadingOFF +{ + [self checkEncryptedMessageWithLazyLoading:NO]; +} + + +// After the test scenario, create a temporary timeline on the last event. +// The timeline state should be lazy loaded and partial. +// There should be only Alice and state.members.count = 1 +- (void)checkPermalinkRoomStateWithLazyLoading:(BOOL)lazyLoading +{ + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + MXRoomSummary *summary = [aliceSession roomSummaryWithRoomId:roomId]; + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + + MXEventTimeline *eventTimeline = [room timelineOnEvent:summary.lastMessageEventId]; + + [eventTimeline resetPaginationAroundInitialEventWithLimit:10 success:^{ + + MXRoomState *roomState = eventTimeline.state; + + MXRoomMembers *lazyloadedRoomMembers = roomState.members; + + XCTAssert([lazyloadedRoomMembers memberWithUserId:aliceSession.myUser.userId]); + + if (lazyLoading) + { + XCTAssertEqual(lazyloadedRoomMembers.members.count, 1, @"There should be only Alice in the lazy loaded room state"); + + XCTAssertEqual(roomState.membersCount.members, 1); + XCTAssertEqual(roomState.membersCount.joined, 1); + XCTAssertEqual(roomState.membersCount.invited, 0); + } + else + { + // The room members list in the room state is full known + XCTAssertEqual(lazyloadedRoomMembers.members.count, 4); + + XCTAssertEqual(roomState.membersCount.members, 4); + XCTAssertEqual(roomState.membersCount.joined, 3); + XCTAssertEqual(roomState.membersCount.invited, 1); + } + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +- (void)testPermalinkRoomState +{ + [self checkPermalinkRoomStateWithLazyLoading:YES]; +} + +- (void)testPermalinkRoomStateWithLazyLoadingOFF +{ + [self checkPermalinkRoomStateWithLazyLoading:NO]; +} + + +// Test lazy loaded members sent by the HS when paginating backward from a permalink +// When paginating back to the beginning, lazy loaded room state passed in pagination callback must be updated with Bob, then Charlie and Dave. +// Almost the same test as `checkRoomStateWhilePaginatingWithLazyLoading`. +- (void)checkPermalinkRoomStateWhilePaginatingBackwardWithLazyLoading:(BOOL)lazyLoading +{ + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + MXRoomSummary *summary = [aliceSession roomSummaryWithRoomId:roomId]; + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + + MXEventTimeline *eventTimeline = [room timelineOnEvent:summary.lastMessageEventId]; + + __block NSUInteger messageCount = 0; + [eventTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + switch (++messageCount) + { + case 1: + XCTAssertEqualObjects(event.sender, aliceSession.myUser.userId); + + if (lazyLoading) + { + XCTAssertNil([roomState.members memberWithUserId:bobSession.myUser.userId]); + XCTAssertNil([eventTimeline.state.members memberWithUserId:bobSession.myUser.userId]); + XCTAssertNil([eventTimeline.state.members memberWithUserId:charlieSession.myUser.userId]); + } + else + { + XCTAssertNotNil([roomState.members memberWithUserId:bobSession.myUser.userId]); + } + + XCTAssertNotNil([eventTimeline.state.members memberWithUserId:aliceSession.myUser.userId]); + break; + + case 50: + if (lazyLoading) + { + // Disabled because the HS sends too early the Bob membership event (but not Charlie nor Dave) + //XCTAssertNil([roomState.members memberWithUserId:bobSession.myUser.userId]); + //XCTAssertNil([eventTimeline.state.members memberWithUserId:bobSession.myUser.userId]); + XCTAssertNil([eventTimeline.state.members memberWithUserId:charlieSession.myUser.userId]); + } + else + { + XCTAssertNotNil([roomState.members memberWithUserId:bobSession.myUser.userId]); + } + + XCTAssertNotNil([eventTimeline.state.members memberWithUserId:aliceSession.myUser.userId]); + break; + case 51: + XCTAssert([roomState.members memberWithUserId:bobSession.myUser.userId]); + + if (lazyLoading) + { + XCTAssertNil([roomState.members memberWithUserId:charlieSession.myUser.userId]); + XCTAssertNil([eventTimeline.state.members memberWithUserId:charlieSession.myUser.userId]); + } + else + { + XCTAssert([roomState.members memberWithUserId:charlieSession.myUser.userId]); + } + + // The room state of the room should have been enriched + XCTAssertNotNil([eventTimeline.state.members memberWithUserId:bobSession.myUser.userId]); + + break; + + case 110: + XCTAssertNotNil([eventTimeline.state.members memberWithUserId:aliceSession.myUser.userId]); + XCTAssertNotNil([eventTimeline.state.members memberWithUserId:bobSession.myUser.userId]); + XCTAssertNotNil([eventTimeline.state.members memberWithUserId:charlieSession.myUser.userId]); + break; + + default: + break; + } + }]; + + [eventTimeline resetPaginationAroundInitialEventWithLimit:0 success:^{ + + // Paginate the 50 last message where there is only Alice talking + [eventTimeline paginate:50 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + + // Pagine a bit more to get Bob's message + [eventTimeline paginate:25 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + + // Paginate all to get a full room state + [eventTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + + XCTAssertGreaterThan(messageCount, 110, @"The test has not fully run"); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } + failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +- (void)testPermalinkRoomStateWhilePaginatingBackward +{ + [self checkPermalinkRoomStateWhilePaginatingBackwardWithLazyLoading:YES]; +} + +- (void)testPermalinkRoomStateWhilePaginatingWithLazyLoadingOFF +{ + [self checkPermalinkRoomStateWhilePaginatingBackwardWithLazyLoading:NO]; +} + + +// Test lazy loaded members sent by the HS when paginating forward +// - Come back to Bob message +// - We should only know Bob membership +// - Paginate forward to get Alice next message +// - We should know Alice membership now +- (void)checkPermalinkRoomStateWhilePaginatingForwardWithLazyLoading:(BOOL)lazyLoading +{ + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + + MXEventTimeline *eventTimeline = [room timelineOnEvent:bobMessageEventId]; + + __block NSUInteger messageCount = 0; + [eventTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + switch (++messageCount) + { + case 1: + // Bob message + // - We should only know Bob membership + XCTAssertEqualObjects(event.sender, bobSession.myUser.userId); + + if (lazyLoading) + { + XCTAssertNil([eventTimeline.state.members memberWithUserId:aliceSession.myUser.userId]); + XCTAssertNil([eventTimeline.state.members memberWithUserId:charlieSession.myUser.userId]); + } + else + { + XCTAssertNotNil([roomState.members memberWithUserId:aliceSession.myUser.userId]); + } + + XCTAssertNotNil([eventTimeline.state.members memberWithUserId:bobSession.myUser.userId]); + break; + + case 2: + // First next message from Alice + // - We should know Alice membership now + XCTAssertEqualObjects(event.sender, aliceSession.myUser.userId); + + if (lazyLoading) + { + XCTAssertNil([eventTimeline.state.members memberWithUserId:charlieSession.myUser.userId]); + } + + XCTAssertNotNil([eventTimeline.state.members memberWithUserId:aliceSession.myUser.userId]); + XCTAssertNotNil([eventTimeline.state.members memberWithUserId:bobSession.myUser.userId]); + break; + + default: + break; + } + }]; + + // - Come back to Bob message + [eventTimeline resetPaginationAroundInitialEventWithLimit:0 success:^{ + + // - Paginate forward to get Alice next message + [eventTimeline paginate:1 direction:MXTimelineDirectionForwards onlyFromStore:NO complete:^{ + + XCTAssertGreaterThanOrEqual(messageCount, 2, @"The test has not fully run"); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +- (void)testPermalinkRoomStateWhilePaginatingForward +{ + [self checkPermalinkRoomStateWhilePaginatingForwardWithLazyLoading:YES]; +} + +- (void)testPermalinkRoomStateWhilePaginatingForwardWithLazyLoadingOFF +{ + [self checkPermalinkRoomStateWhilePaginatingForwardWithLazyLoading:NO]; +} + + +// After the test scenario, search for the message sent by Bob. +// We should be able to display +- (void)checkSearchWithLazyLoading:(BOOL)lazyLoading +{ + [self createScenarioWithLazyLoading:lazyLoading readyToTest:^(MXSession *aliceSession, MXSession *bobSession, MXSession *charlieSession, NSString *roomId, XCTestExpectation *expectation) { + + [aliceSession.matrixRestClient searchMessagesWithText:bobMessage roomEventFilter:nil beforeLimit:0 afterLimit:0 nextBatch:nil success:^(MXSearchRoomEventResults *roomEventResults) { + + XCTAssertEqual(roomEventResults.results.count, 1); + + if (roomEventResults.results.count) + { + XCTAssertNotNil(roomEventResults.results.firstObject.context.profileInfo[bobSession.myUser.userId].displayName); + } + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The operation should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +- (void)testSearch +{ + [self checkSearchWithLazyLoading:YES]; +} + +- (void)testSearchWithLazyLoadingOFF +{ + [self checkSearchWithLazyLoading:NO]; +} + + +/* + @TODO(lazy-loading): + - test read receipts + */ +@end + +#pragma clang diagnostic pop + diff --git a/MatrixSDKTests/MXNotificationCenterTests.m b/MatrixSDKTests/MXNotificationCenterTests.m index d9bfdc2054..01d063ec84 100644 --- a/MatrixSDKTests/MXNotificationCenterTests.m +++ b/MatrixSDKTests/MXNotificationCenterTests.m @@ -158,24 +158,27 @@ - (void)testDefaultPushOnAllNonYouMessagesRule mxSession = bobSession; MXRoom *room = [mxSession roomWithRoomId:roomId]; - [room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMember] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - [bobSession.notificationCenter listenToNotifications:^(MXEvent *event, MXRoomState *roomState, MXPushRule *rule) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMember] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - // We must be alerted by the default content HS rule on any message - XCTAssertEqual(rule.kind, MXPushRuleKindUnderride); - XCTAssert(rule.isDefault, @"The rule must be the server default rule. Rule: %@", rule); + [bobSession.notificationCenter listenToNotifications:^(MXEvent *event, MXRoomState *roomState, MXPushRule *rule) { - [expectation fulfill]; - }]; + // We must be alerted by the default content HS rule on any message + XCTAssertEqual(rule.kind, MXPushRuleKindUnderride); + XCTAssert(rule.isDefault, @"The rule must be the server default rule. Rule: %@", rule); - [aliceRestClient sendTextMessageToRoom:roomId text:@"a message" success:^(NSString *eventId) { + [expectation fulfill]; + }]; - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; + [aliceRestClient sendTextMessageToRoom:roomId text:@"a message" success:^(NSString *eventId) { + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + + }]; }]; // Make sure there 3 are peoples in the room to avoid to fire the default "room_member_count == 2" rule @@ -371,20 +374,23 @@ - (void)testRuleMatchingEvent if (MXTimelineDirectionForwards == direction) { - MXPushRule *rule = [aliceSession.notificationCenter ruleMatchingEvent:event]; + [[aliceSession roomWithRoomId:event.roomId] state:^(MXRoomState *roomState) { + + MXPushRule *rule = [aliceSession.notificationCenter ruleMatchingEvent:event roomState:roomState]; + + XCTAssert(rule, @"A push rule must be found for this event: %@", event); - XCTAssert(rule, @"A push rule must be found for this event: %@", event); + // Do the same test as in testDefaultDisplayNameCondition + XCTAssertEqual(rule.kind, MXPushRuleKindOverride); - // Do the same test as in testDefaultDisplayNameCondition - XCTAssertEqual(rule.kind, MXPushRuleKindOverride); + MXPushRuleCondition *condition = rule.conditions[0]; - MXPushRuleCondition *condition = rule.conditions[0]; + XCTAssertEqualObjects(condition.kind, kMXPushRuleConditionStringContainsDisplayName, @"The default content rule with contains_display_name condition must fire first"); + XCTAssertEqual(condition.kindType, MXPushRuleConditionTypeContainsDisplayName); - XCTAssertEqualObjects(condition.kind, kMXPushRuleConditionStringContainsDisplayName, @"The default content rule with contains_display_name condition must fire first"); - XCTAssertEqual(condition.kindType, MXPushRuleConditionTypeContainsDisplayName); - - [aliceSession close]; - [expectation fulfill]; + [aliceSession close]; + [expectation fulfill]; + }]; } }]; diff --git a/MatrixSDKTests/MXPeekingRoomTests.m b/MatrixSDKTests/MXPeekingRoomTests.m index 210ab9b60c..4917d8ffb8 100644 --- a/MatrixSDKTests/MXPeekingRoomTests.m +++ b/MatrixSDKTests/MXPeekingRoomTests.m @@ -73,11 +73,13 @@ - (void)testPeeking XCTAssertEqual(mxSession.rooms.count, 0, @"MXPeekingRoom must not be listed by mxSession.rooms"); XCTAssertEqual(peekingRoom.roomId, room.roomId); - XCTAssertEqual(peekingRoom.state.members.count, 1, @"The MXPeekingRoom state must be known now"); + [peekingRoom state:^(MXRoomState *roomState) { + XCTAssertEqual(roomState.membersCount.members, 1, @"The MXPeekingRoom state must be known now"); - [mxSession stopPeeking:peekingRoom]; + [mxSession stopPeeking:peekingRoom]; - [expectation fulfill]; + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The operation should not fail - NSError: %@", error); @@ -127,18 +129,20 @@ - (void)testPeekingWithMemberAlreadyInRoom mxSession = mxSession2; XCTAssertEqual(mxSession.rooms.count, 1); - XCTAssertEqual(room.state.members.count, 1); + XCTAssertEqual(room.summary.membersCount.members, 1); [mxSession peekInRoomWithRoomId:room.roomId success:^(MXPeekingRoom *peekingRoom) { XCTAssertEqual(mxSession.rooms.count, 1, @"MXPeekingRoom must not be listed by mxSession.rooms"); XCTAssertEqual(peekingRoom.roomId, room.roomId); - XCTAssertEqual(peekingRoom.state.members.count, 1, @"The MXPeekingRoom state must be known now"); + [peekingRoom state:^(MXRoomState *roomState) { + XCTAssertEqual(roomState.membersCount.members, 1, @"The MXPeekingRoom state must be known now"); - [mxSession stopPeeking:peekingRoom]; + [mxSession stopPeeking:peekingRoom]; - [expectation fulfill]; + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The operation should not fail - NSError: %@", error); diff --git a/MatrixSDKTests/MXPushRuleTests.m b/MatrixSDKTests/MXPushRuleTests.m index f275bc0ffa..4c3438211d 100644 --- a/MatrixSDKTests/MXPushRuleTests.m +++ b/MatrixSDKTests/MXPushRuleTests.m @@ -108,31 +108,31 @@ - (void)testEventContentMatchFoo notificationCenter.flatRules = @[rule]; event = [self messageTextEventWithContent:@"foo bar"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); event = [self messageTextEventWithContent:@"foo,bar"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); event = [self messageTextEventWithContent:@"bar.foo"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); event = [self messageTextEventWithContent:@"bar.foo!bar"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); event = [self messageTextEventWithContent:@"foobar"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNil(matchingRule, @"'In this test, foo must be surrounded by word delimiters (e.g. punctuation and whitespace or start/end of line)"); event = [self messageTextEventWithContent:@"barfoo"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNil(matchingRule, @"'In this test, foo must be surrounded by word delimiters (e.g. punctuation and whitespace or start/end of line)"); } @@ -149,32 +149,32 @@ - (void)testEventContentMatchFooStar notificationCenter.flatRules = @[rule]; event = [self messageTextEventWithContent:@"foo bar"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); event = [self messageTextEventWithContent:@"foo,bar"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); event = [self messageTextEventWithContent:@"bar.foo"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); event = [self messageTextEventWithContent:@"bar.foo!bar"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); event = [self messageTextEventWithContent:@"foobar"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); event = [self messageTextEventWithContent:@"barfoo"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNil(matchingRule, @"'In this test, only words starting with foo must match"); } @@ -190,22 +190,22 @@ - (void)testEventContentMatchStarFooStar notificationCenter.flatRules = @[rule]; event = [self messageTextEventWithContent:@"foo bar"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); event = [self messageTextEventWithContent:@"foobar"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); event = [self messageTextEventWithContent:@"barfoo"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); event = [self messageTextEventWithContent:@"foobarfoo"]; - matchingRule = [notificationCenter ruleMatchingEvent:event]; + matchingRule = [notificationCenter ruleMatchingEvent:event roomState:nil]; XCTAssertNotNil(matchingRule); XCTAssertEqual(matchingRule, rule); } diff --git a/MatrixSDKTests/MXRestClientTests.m b/MatrixSDKTests/MXRestClientTests.m index 0404cf29c4..dec2111e31 100644 --- a/MatrixSDKTests/MXRestClientTests.m +++ b/MatrixSDKTests/MXRestClientTests.m @@ -1,6 +1,7 @@ /* Copyright 2014 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -944,7 +945,7 @@ - (void)testContextOfEvent MXEvent *eventAfter = roomInitialSync.messages.chunk[6]; // Get the context around it - [bobRestClient contextOfEvent:event.eventId inRoom:roomId limit:10 success:^(MXEventContext *eventContext) { + [bobRestClient contextOfEvent:event.eventId inRoom:roomId limit:10 filter:nil success:^(MXEventContext *eventContext) { XCTAssertNotNil(eventContext); XCTAssertNotNil(eventContext.start); @@ -1007,7 +1008,7 @@ - (void)testMXRoomMemberEventContent } -#pragma mark - #pragma mark - Room tags operations +#pragma mark - Room tags operations - (void)testAddAndRemoveTag { [matrixSDKTestsData doMXRestClientTestWithBobAndARoom:self readyToTest:^(MXRestClient *bobRestClient, NSString *roomId, XCTestExpectation *expectation) { @@ -1050,6 +1051,67 @@ - (void)testAddAndRemoveTag } +#pragma mark - Filter operations +- (void)testFilter +{ + [matrixSDKTestsData doMXRestClientTestWithAlice:self readyToTest:^(MXRestClient *aliceRestClient, XCTestExpectation *expectation) { + + MXFilterJSONModel *filter = [[MXFilterJSONModel alloc] init]; + + filter.eventFields = @[@"content.body"]; + filter.eventFormat = @"federation"; + + filter.room = [[MXRoomFilter alloc] init]; + filter.room.rooms = @[@"!aroom:matrix:org"]; + filter.room.notRooms = @[@"!notaroom:matrix:org"]; + + filter.room.ephemeral = [[MXRoomEventFilter alloc] init]; + filter.room.ephemeral.containsURL = NO; + filter.room.ephemeral.types = @[@"atype"]; + filter.room.ephemeral.notTypes = @[@"notatype"]; + filter.room.ephemeral.rooms = @[@"!aroom_ephemeral:matrix:org"]; + filter.room.ephemeral.notRooms = @[@"!notaroom_ephemeral:matrix:org"]; + filter.room.ephemeral.senders = @[@"@asender:matrix.org"]; + filter.room.ephemeral.notSenders = @[@"@notasender:matrix.org"]; + + // This is the basic filter we use + filter.room.timeline = [[MXRoomEventFilter alloc] init]; + filter.room.timeline.limit = 10; + + filter.room.includeLeave = YES; + filter.room.state = filter.room.ephemeral; + filter.room.accountData = filter.room.timeline; + + filter.presence = [[MXFilter alloc] init]; + filter.presence.types = @[@"atype"]; + filter.presence.notTypes = @[@"notatype"]; + filter.presence.senders = @[@"@asender:matrix.org"]; + filter.presence.notSenders = @[@"@notasender:matrix.org"]; + filter.presence.limit = 11; + + [aliceRestClient setFilter:filter success:^(NSString *filterId) { + + XCTAssertNotNil(filterId); + XCTAssert([filterId isKindOfClass:NSString.class]); + + [aliceRestClient getFilterWithFilterId:filterId success:^(MXFilterJSONModel *receivedFilter) { + + XCTAssert([receivedFilter.JSONDictionary isEqualToDictionary:filter.JSONDictionary], + @"Filters are different: receivedFilter: %@\nfilter:%@", receivedFilter, filter); + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + #pragma mark - Profile operations - (void)testUserDisplayName { @@ -1128,60 +1190,62 @@ - (void)testOtherUserDisplayName //} -- (void)testUserAvatarUrl -{ - [matrixSDKTestsData doMXRestClientTestWithAlice:self readyToTest:^(MXRestClient *aliceRestClient, XCTestExpectation *expectation) { - - // Set the avatar url - __block NSString *newAvatarUrl = @"http://matrix.org/matrix2.png"; - [aliceRestClient setAvatarUrl:newAvatarUrl success:^{ - - // Then retrieve it - [aliceRestClient avatarUrlForUser:nil success:^(NSString *avatarUrl) { - - XCTAssertEqual(avatarUrl, newAvatarUrl); - [expectation fulfill]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - }]; -} - -- (void)testOtherUserAvatarUrl -{ - [matrixSDKTestsData doMXRestClientTestWithAlice:self readyToTest:^(MXRestClient *aliceRestClient, XCTestExpectation *expectation) { - - // Set the avatar url - __block NSString *newAvatarUrl = @"http://matrix.org/matrix2.png"; - [aliceRestClient setAvatarUrl:newAvatarUrl success:^{ - - [matrixSDKTestsData doMXRestClientTestWithBob:nil readyToTest:^(MXRestClient *bobRestClient, XCTestExpectation *expectation2) { - - // Then retrieve it from a Bob restClient - [bobRestClient avatarUrlForUser:matrixSDKTestsData.aliceCredentials.userId success:^(NSString *avatarUrl) { +// Disabled because setting avatar does not work anymore with local test homeserver +//- (void)testUserAvatarUrl +//{ +// [matrixSDKTestsData doMXRestClientTestWithAlice:self readyToTest:^(MXRestClient *aliceRestClient, XCTestExpectation *expectation) { +// +// // Set the avatar url +// __block NSString *newAvatarUrl = @"http://matrix.org/matrix2.png"; +// [aliceRestClient setAvatarUrl:newAvatarUrl success:^{ +// +// // Then retrieve it +// [aliceRestClient avatarUrlForUser:nil success:^(NSString *avatarUrl) { +// +// XCTAssertEqual(avatarUrl, newAvatarUrl); +// [expectation fulfill]; +// +// } failure:^(NSError *error) { +// XCTFail(@"The request should not fail - NSError: %@", error); +// [expectation fulfill]; +// }]; +// +// } failure:^(NSError *error) { +// XCTFail(@"The request should not fail - NSError: %@", error); +// [expectation fulfill]; +// }]; +// }]; +//} - XCTAssertEqual(avatarUrl, newAvatarUrl); - [expectation fulfill]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - }]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - }]; -} +// Disabled because setting avatar does not work anymore with local test homeserver +//- (void)testOtherUserAvatarUrl +//{ +// [matrixSDKTestsData doMXRestClientTestWithAlice:self readyToTest:^(MXRestClient *aliceRestClient, XCTestExpectation *expectation) { +// +// // Set the avatar url +// __block NSString *newAvatarUrl = @"http://matrix.org/matrix2.png"; +// [aliceRestClient setAvatarUrl:newAvatarUrl success:^{ +// +// [matrixSDKTestsData doMXRestClientTestWithBob:nil readyToTest:^(MXRestClient *bobRestClient, XCTestExpectation *expectation2) { +// +// // Then retrieve it from a Bob restClient +// [bobRestClient avatarUrlForUser:matrixSDKTestsData.aliceCredentials.userId success:^(NSString *avatarUrl) { +// +// XCTAssertEqual(avatarUrl, newAvatarUrl); +// [expectation fulfill]; +// +// } failure:^(NSError *error) { +// XCTFail(@"The request should not fail - NSError: %@", error); +// [expectation fulfill]; +// }]; +// }]; +// +// } failure:^(NSError *error) { +// XCTFail(@"The request should not fail - NSError: %@", error); +// [expectation fulfill]; +// }]; +// }]; +//} #pragma mark - Presence operations diff --git a/MatrixSDKTests/MXRoomStateDynamicTests.m b/MatrixSDKTests/MXRoomStateDynamicTests.m index 571b4f2bf3..c67e773516 100644 --- a/MatrixSDKTests/MXRoomStateDynamicTests.m +++ b/MatrixSDKTests/MXRoomStateDynamicTests.m @@ -119,67 +119,69 @@ - (void)testBackPaginationForScenario1 MXRoom *room = [mxSession roomWithRoomId:roomId]; __block NSUInteger eventCount = 0; - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - // Check each expected event and their roomState contect - // Events are received in the reverse order - switch (eventCount++) { - case 0: - // 6 - Bob: "Bonjour" - XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); - - XCTAssert([roomState.topic isEqualToString:@"Topic #2"], @"roomState.topic is wrong. Found: %@", roomState.topic); - XCTAssert([room.state.topic isEqualToString:@"Topic #2"]); - break; - - case 1: - // 5 - Bob changes the room topic to "Topic #2" - XCTAssertEqual(event.eventType, MXEventTypeRoomTopic); - - XCTAssert([roomState.topic isEqualToString:@"Topic #1"], @"roomState.topic is wrong. Found: %@", roomState.topic); - XCTAssert([room.state.topic isEqualToString:@"Topic #2"]); - break; - - case 2: - // 4 - Bob: "Hola" - XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); - - XCTAssert([roomState.topic isEqualToString:@"Topic #1"], @"roomState.topic is wrong. Found: %@", roomState.topic); - XCTAssert([room.state.topic isEqualToString:@"Topic #2"]); - break; - - case 3: - // 3 - Bob changes the room topic to "Topic #1" - XCTAssertEqual(event.eventType, MXEventTypeRoomTopic); - - XCTAssertNil(roomState.topic, @"The room topic was undefined before getting this event. Found: %@", roomState.topic); - XCTAssert([room.state.topic isEqualToString:@"Topic #2"]); - break; - - case 4: - // 2 - Bob: "Hello World" - XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); - - XCTAssertNil(roomState.topic, @"The room topic was undefined before getting this event. Found: %@", roomState.topic); - XCTAssert([room.state.topic isEqualToString:@"Topic #2"]); - break; - - default: - break; - } - - }]; - - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:10 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - - XCTAssertGreaterThan(eventCount, 4, @"We must have received events"); - - [expectation fulfill]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + // Check each expected event and their roomState contect + // Events are received in the reverse order + switch (eventCount++) { + case 0: + // 6 - Bob: "Bonjour" + XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); + + XCTAssertEqualObjects(roomState.topic, @"Topic #2"); + XCTAssertEqualObjects(room.summary.topic, @"Topic #2"); + break; + + case 1: + // 5 - Bob changes the room topic to "Topic #2" + XCTAssertEqual(event.eventType, MXEventTypeRoomTopic); + + XCTAssertEqualObjects(roomState.topic, @"Topic #1"); + XCTAssertEqualObjects(room.summary.topic, @"Topic #2"); + break; + + case 2: + // 4 - Bob: "Hola" + XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); + + XCTAssertEqualObjects(roomState.topic, @"Topic #1"); + XCTAssertEqualObjects(room.summary.topic, @"Topic #2"); + break; + + case 3: + // 3 - Bob changes the room topic to "Topic #1" + XCTAssertEqual(event.eventType, MXEventTypeRoomTopic); + + XCTAssertNil(roomState.topic, @"The room topic was undefined before getting this event. Found: %@", roomState.topic); + XCTAssertEqualObjects(room.summary.topic, @"Topic #2"); + break; + + case 4: + // 2 - Bob: "Hello World" + XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); + + XCTAssertNil(roomState.topic, @"The room topic was undefined before getting this event. Found: %@", roomState.topic); + XCTAssertEqualObjects(room.summary.topic, @"Topic #2"); + break; + + default: + break; + } + + }]; + + [liveTimeline resetPagination]; + [liveTimeline paginate:10 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + + XCTAssertGreaterThan(eventCount, 4, @"We must have received events"); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; }]; } failure:^(NSError *error) { @@ -203,73 +205,76 @@ - (void)testLiveEventsForScenario1 MXRoom *room = [mxSession roomWithRoomId:roomId]; __block NSUInteger eventCount = 0; - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - // Check each expected event and their roomState contect - // Events are live. Then comes in order - switch (eventCount++) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - case 0: - // 2 - Bob: "Hello World" - XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); - - XCTAssertNotNil(roomState); - - XCTAssertNil(roomState.topic, @"The room topic is not yet defined. Found: %@", roomState.topic); - XCTAssertNil(room.state.topic, @"The room topic is not yet defined. Found: %@", roomState.topic); - break; - - case 1: - // 3 - Bob changes the room topic to "Topic #1" - XCTAssertEqual(event.eventType, MXEventTypeRoomTopic); + // Check each expected event and their roomState contect + // Events are live. Then comes in order + switch (eventCount++) { - XCTAssertNotNil(roomState); + case 0: + // 2 - Bob: "Hello World" + XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); - XCTAssertNil(roomState.topic, @"The room topic was not yet defined before this event. Found: %@", roomState.topic); - XCTAssert([room.state.topic isEqualToString:@"Topic #1"]); - break; - - case 2: - // 4 - Bob: "Hola" - XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); + XCTAssertNotNil(roomState); - XCTAssertNotNil(roomState); + XCTAssertNil(roomState.topic, @"The room topic is not yet defined. Found: %@", roomState.topic); + XCTAssertNil(room.summary.topic, @"The room topic is not yet defined. Found: %@", roomState.topic); + break; - XCTAssert([roomState.topic isEqualToString:@"Topic #1"], @"roomState.topic is wrong. Found: %@", roomState.topic); - XCTAssert([room.state.topic isEqualToString:@"Topic #1"]); - break; - - case 3: - // 5 - Bob changes the room topic to "Topic #2" - XCTAssertEqual(event.eventType, MXEventTypeRoomTopic); + case 1: + // 3 - Bob changes the room topic to "Topic #1" + XCTAssertEqual(event.eventType, MXEventTypeRoomTopic); - XCTAssertNotNil(roomState); + XCTAssertNotNil(roomState); - XCTAssertEqualObjects(roomState.topic, @"Topic #1", @"roomState.topic is wrong. Found: %@", roomState.topic); - XCTAssert([room.state.topic isEqualToString:@"Topic #2"]); - break; - - case 4: - // 6 - Bob: "Bonjour" - XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); + XCTAssertNil(roomState.topic, @"The room topic was not yet defined before this event. Found: %@", roomState.topic); + XCTAssertEqualObjects(room.summary.topic, @"Topic #1"); + break; - XCTAssertNotNil(roomState); + case 2: + // 4 - Bob: "Hola" + XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); - XCTAssert([roomState.topic isEqualToString:@"Topic #2"], @"roomState.topic is wrong. Found: %@", roomState.topic); - XCTAssert([room.state.topic isEqualToString:@"Topic #2"]); - - // No more events. This is the end of the test - [expectation fulfill]; - break; + XCTAssertNotNil(roomState); + + XCTAssertEqualObjects(roomState.topic, @"Topic #1"); + XCTAssertEqualObjects(room.summary.topic, @"Topic #1"); + break; + + case 3: + // 5 - Bob changes the room topic to "Topic #2" + XCTAssertEqual(event.eventType, MXEventTypeRoomTopic); + + XCTAssertNotNil(roomState); + + XCTAssertEqualObjects(roomState.topic, @"Topic #1"); + XCTAssertEqualObjects(room.summary.topic, @"Topic #2"); + break; + + case 4: + // 6 - Bob: "Bonjour" + XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); + + XCTAssertNotNil(roomState); + + XCTAssertEqualObjects(roomState.topic, @"Topic #2"); + XCTAssertEqualObjects(room.summary.topic, @"Topic #2"); + + // No more events. This is the end of the test + [expectation fulfill]; + break; + + default: + XCTFail(@"No more events are expected"); + [expectation fulfill]; + break; + } + + }]; - default: - XCTFail(@"No more events are expected"); - [expectation fulfill]; - break; - } - }]; - + // Send events of the scenario [self createScenario1:bobRestClient inRoom:roomId expectation:expectation onComplete:^{ @@ -393,8 +398,8 @@ - (void)testBackPaginationForScenario2 NSLog(@"eventCount: %tu - %@", eventCount, event); - MXRoomMember *beforeEventAliceMember = [roomState memberWithUserId:aliceRestClient.credentials.userId]; - MXRoomMember *aliceMember = [room.state memberWithUserId:aliceRestClient.credentials.userId]; + MXRoomMember *beforeEventAliceMember = [roomstate.members memberWithUserId:aliceRestClient.credentials.userId]; + MXRoomMember *aliceMember = [room.state.members memberWithUserId:aliceRestClient.credentials.userId]; // Check each expected event and their roomState contect // Events are received in the reverse order @@ -405,7 +410,7 @@ - (void)testBackPaginationForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipLeave); XCTAssertEqual(aliceMember.membership, MXMembershipLeave); @@ -420,7 +425,7 @@ - (void)testBackPaginationForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMember); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipJoin); XCTAssertEqual(aliceMember.membership, MXMembershipLeave); @@ -434,7 +439,7 @@ - (void)testBackPaginationForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipJoin); XCTAssertEqual(aliceMember.membership, MXMembershipLeave); @@ -448,7 +453,7 @@ - (void)testBackPaginationForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMember); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipJoin); XCTAssertEqual(aliceMember.membership, MXMembershipLeave); @@ -462,7 +467,7 @@ - (void)testBackPaginationForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipJoin); XCTAssertEqual(aliceMember.membership, MXMembershipLeave); @@ -476,7 +481,7 @@ - (void)testBackPaginationForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMember); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipInvite); XCTAssertEqual(aliceMember.membership, MXMembershipLeave); @@ -491,7 +496,7 @@ - (void)testBackPaginationForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipInvite); XCTAssertEqual(aliceMember.membership, MXMembershipLeave); @@ -505,7 +510,7 @@ - (void)testBackPaginationForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMember); XCTAssertEqual(roomState.members.count, 1); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(aliceMember.membership, MXMembershipLeave); XCTAssertNil(aliceMember.displayname); @@ -519,7 +524,7 @@ - (void)testBackPaginationForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); XCTAssertEqual(roomState.members.count, 1); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(aliceMember.membership, MXMembershipLeave); XCTAssertNil(aliceMember.displayname); @@ -569,8 +574,8 @@ - (void)testLiveEventsForScenario2 __block NSUInteger eventCount = 0; [room listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - MXRoomMember *beforeEventAliceMember = [roomState memberWithUserId:matrixSDKTestsData.aliceCredentials.userId]; - MXRoomMember *aliceMember = [room.state memberWithUserId:matrixSDKTestsData.aliceCredentials.userId]; + MXRoomMember *beforeEventAliceMember = [roomstate.members memberWithUserId:matrixSDKTestsData.aliceCredentials.userId]; + MXRoomMember *aliceMember = [room.state.members memberWithUserId:matrixSDKTestsData.aliceCredentials.userId]; // Check each expected event and their roomState contect // Events are live. Then comes in order @@ -581,7 +586,7 @@ - (void)testLiveEventsForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); XCTAssertEqual(roomState.members.count, 1); - XCTAssertEqual(room.state.members.count, 1); + XCTAssertEqual(room.summary.membersCount.members, 1); // Alice member doest not exist at that time XCTAssertNil(beforeEventAliceMember); @@ -593,7 +598,7 @@ - (void)testLiveEventsForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMember); XCTAssertEqual(roomState.members.count, 1); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(aliceMember.membership, MXMembershipInvite); XCTAssertNil(aliceMember.displayname); @@ -607,7 +612,7 @@ - (void)testLiveEventsForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipInvite); XCTAssertEqual(aliceMember.membership, MXMembershipInvite); @@ -621,7 +626,7 @@ - (void)testLiveEventsForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMember); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipInvite); XCTAssertEqual(aliceMember.membership, MXMembershipJoin); @@ -636,7 +641,7 @@ - (void)testLiveEventsForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipJoin); XCTAssertEqual(aliceMember.membership, MXMembershipJoin); @@ -650,7 +655,7 @@ - (void)testLiveEventsForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMember); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipJoin); XCTAssertEqual(aliceMember.membership, MXMembershipJoin); @@ -664,7 +669,7 @@ - (void)testLiveEventsForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipJoin); XCTAssertEqual(aliceMember.membership, MXMembershipJoin); @@ -678,7 +683,7 @@ - (void)testLiveEventsForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMember); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipJoin); XCTAssertEqual(aliceMember.membership, MXMembershipLeave); @@ -692,7 +697,7 @@ - (void)testLiveEventsForScenario2 XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); XCTAssertEqual(roomState.members.count, 2); - XCTAssertEqual(room.state.members.count, 2); + XCTAssertEqual(room.summary.membersCount.members, 2); XCTAssertEqual(beforeEventAliceMember.membership, MXMembershipLeave); XCTAssertEqual(aliceMember.membership, MXMembershipLeave); diff --git a/MatrixSDKTests/MXRoomStateTests.m b/MatrixSDKTests/MXRoomStateTests.m index a2858566b5..73d3c99ad0 100644 --- a/MatrixSDKTests/MXRoomStateTests.m +++ b/MatrixSDKTests/MXRoomStateTests.m @@ -64,9 +64,12 @@ - (void)testIsJoinRulePublic mxSession = mxSession2; - XCTAssertTrue(room.state.isJoinRulePublic, @"The room join rule must be public"); - - [expectation fulfill]; + [room state:^(MXRoomState *roomState) { + + XCTAssertTrue(roomState.isJoinRulePublic, @"The room join rule must be public"); + + [expectation fulfill]; + }]; }]; } @@ -76,9 +79,11 @@ - (void)testIsJoinRulePublicForAPrivateRoom mxSession = mxSession2; - XCTAssertFalse(room.state.isJoinRulePublic, @"This room join rule must be private"); - - [expectation fulfill]; + [room state:^(MXRoomState *roomState) { + XCTAssertFalse(roomState.isJoinRulePublic, @"This room join rule must be private"); + + [expectation fulfill]; + }]; }]; } @@ -94,11 +99,13 @@ - (void)testRoomTopicProvidedByInitialSync [mxSession start:^{ MXRoom *room = [mxSession roomWithRoomId:roomId]; - - XCTAssertNotNil(room.state.topic); - XCTAssert([room.state.topic isEqualToString:@"My topic"], @"The room topic shoud be \"My topic\". Found: %@", room.state.topic); - - [expectation fulfill]; + + [room state:^(MXRoomState *roomState) { + XCTAssertNotNil(roomState.topic); + XCTAssertEqualObjects(roomState.topic, @"My topic"); + + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -123,27 +130,31 @@ - (void)testRoomTopicLive [mxSession start:^{ MXRoom *room = [mxSession roomWithRoomId:roomId]; - - XCTAssertNil(room.state.topic, @"There must be no room topic yet. Found: %@", room.state.topic); - + // Listen to live event. We should receive only one: a m.room.topic event - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - XCTAssertEqual(event.eventType, MXEventTypeRoomTopic); - - XCTAssertNotNil(room.state.topic); - XCTAssert([room.state.topic isEqualToString:@"My topic"], @"The room topic shoud be \"My topic\". Found: %@", room.state.topic); - - [expectation fulfill]; - - }]; - - // Change the topic - [bobRestClient2 setRoomTopic:roomId topic:@"My topic" success:^{ - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + + XCTAssertNil(liveTimeline.state.topic, @"There must be no room topic yet. Found: %@", liveTimeline.state.topic); + + + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + XCTAssertEqual(event.eventType, MXEventTypeRoomTopic); + + XCTAssertNotNil(liveTimeline.state.topic); + XCTAssertEqualObjects(liveTimeline.state.topic, @"My topic"); + + [expectation fulfill]; + + }]; + + // Change the topic + [bobRestClient2 setRoomTopic:roomId topic:@"My topic" success:^{ + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; }]; } failure:^(NSError *error) { @@ -168,10 +179,12 @@ - (void)testRoomAvatarProvidedByInitialSync MXRoom *room = [mxSession roomWithRoomId:roomId]; - XCTAssertNotNil(room.state.avatar); - XCTAssertEqualObjects(room.state.avatar, @"http://matrix.org/matrix.png"); + [room state:^(MXRoomState *roomState) { + XCTAssertNotNil(roomState.avatar); + XCTAssertEqualObjects(roomState.avatar, @"http://matrix.org/matrix.png"); - [expectation fulfill]; + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -197,26 +210,29 @@ - (void)testRoomAvatarLive MXRoom *room = [mxSession roomWithRoomId:roomId]; - XCTAssertNil(room.state.avatar, @"There must be no room avatar yet. Found: %@", room.state.avatar); - // Listen to live event. We should receive only one: a m.room.avatar event - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - XCTAssertEqual(event.eventType, MXEventTypeRoomAvatar); + XCTAssertNil(liveTimeline.state.avatar, @"There must be no room avatar yet. Found: %@", liveTimeline.state.avatar); - XCTAssertNotNil(room.state.avatar); - XCTAssertEqualObjects(room.state.avatar, @"http://matrix.org/matrix.png"); + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [expectation fulfill]; + XCTAssertEqual(event.eventType, MXEventTypeRoomAvatar); - }]; + XCTAssertNotNil(liveTimeline.state.avatar); + XCTAssertEqualObjects(liveTimeline.state.avatar, @"http://matrix.org/matrix.png"); - // Change the avatar - [bobRestClient2 setRoomAvatar:roomId avatar:@"http://matrix.org/matrix.png" success:^{ + [expectation fulfill]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + }]; + + // Change the avatar + [bobRestClient2 setRoomAvatar:roomId avatar:@"http://matrix.org/matrix.png" success:^{ + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; }]; } failure:^(NSError *error) { @@ -239,11 +255,14 @@ - (void)testRoomNameProvidedByInitialSync [mxSession start:^{ MXRoom *room = [mxSession roomWithRoomId:roomId]; - - XCTAssertNotNil(room.state.name); - XCTAssert([room.state.name isEqualToString:@"My room name"], @"The room name shoud be \"My room name\". Found: %@", room.state.name); - - [expectation fulfill]; + + [room state:^(MXRoomState *roomState) { + + XCTAssertNotNil(roomState.name); + XCTAssertEqualObjects(roomState.name, @"My room name"); + + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -268,27 +287,30 @@ - (void)testRoomNameLive [mxSession start:^{ MXRoom *room = [mxSession roomWithRoomId:roomId]; - - XCTAssertNil(room.state.name, @"There must be no room name yet. Found: %@", room.state.name); - + // Listen to live event. We should receive only one: a m.room.name event - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - XCTAssertEqual(event.eventType, MXEventTypeRoomName); - - XCTAssertNotNil(room.state.name); - XCTAssert([room.state.name isEqualToString:@"My room name"], @"The room topic shoud be \"My room name\". Found: %@", room.state.name); - - [expectation fulfill]; - - }]; - - // Change the topic - [bobRestClient2 setRoomName:roomId name:@"My room name" success:^{ - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + + XCTAssertNil(liveTimeline.state.name, @"There must be no room name yet. Found: %@", liveTimeline.state.name); + + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + XCTAssertEqual(event.eventType, MXEventTypeRoomName); + + XCTAssertNotNil(liveTimeline.state.name); + XCTAssertEqualObjects(liveTimeline.state.name, @"My room name"); + + [expectation fulfill]; + + }]; + + // Change the topic + [bobRestClient2 setRoomName:roomId name:@"My room name" success:^{ + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; }]; } failure:^(NSError *error) { @@ -312,10 +334,13 @@ - (void)testRoomHistoryVisibilityProvidedByInitialSync MXRoom *room = [mxSession roomWithRoomId:roomId]; - XCTAssertNotNil(room.state.historyVisibility); - XCTAssertEqualObjects(room.state.historyVisibility, kMXRoomHistoryVisibilityWorldReadable, @"The room history visibility is wrong"); + [room state:^(MXRoomState *roomState) { - [expectation fulfill]; + XCTAssertNotNil(roomState.historyVisibility); + XCTAssertEqualObjects(roomState.historyVisibility, kMXRoomHistoryVisibilityWorldReadable, @"The room history visibility is wrong"); + + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -341,27 +366,30 @@ - (void)testRoomHistoryVisibilityLive MXRoom *room = [mxSession roomWithRoomId:roomId]; - XCTAssertEqualObjects(room.state.historyVisibility, kMXRoomHistoryVisibilityShared, @"The default room history visibility should be shared"); - // Listen to live event. We should receive only one: a m.room.name event - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - XCTAssertEqual(event.eventType, MXEventTypeRoomHistoryVisibility); + XCTAssertEqualObjects(liveTimeline.state.historyVisibility, kMXRoomHistoryVisibilityShared, @"The default room history visibility should be shared"); - XCTAssertNotNil(room.state.historyVisibility); - XCTAssertEqualObjects(room.state.historyVisibility, kMXRoomHistoryVisibilityInvited, @"The room history visibility is wrong"); -; + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [expectation fulfill]; + XCTAssertEqual(event.eventType, MXEventTypeRoomHistoryVisibility); - }]; + XCTAssertNotNil(liveTimeline.state.historyVisibility); + XCTAssertEqualObjects(liveTimeline.state.historyVisibility, kMXRoomHistoryVisibilityInvited, @"The room history visibility is wrong"); + ; - // Change the history visibility - [room setHistoryVisibility:kMXRoomHistoryVisibilityInvited success:^{ + [expectation fulfill]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + }]; + + // Change the history visibility + [room setHistoryVisibility:kMXRoomHistoryVisibilityInvited success:^{ + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; }]; } failure:^(NSError *error) { @@ -385,10 +413,12 @@ - (void)testRoomJoinRuleProvidedByInitialSync MXRoom *room = [mxSession roomWithRoomId:roomId]; - XCTAssertNotNil(room.state.joinRule); - XCTAssertEqualObjects(room.state.joinRule, kMXRoomJoinRulePublic, @"The room join rule is wrong"); + [room state:^(MXRoomState *roomState) { + XCTAssertNotNil(roomState.joinRule); + XCTAssertEqualObjects(roomState.joinRule, kMXRoomJoinRulePublic, @"The room join rule is wrong"); - [expectation fulfill]; + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -414,28 +444,32 @@ - (void)testRoomJoinRuleLive MXRoom *room = [mxSession roomWithRoomId:roomId]; - XCTAssertEqualObjects(room.state.joinRule, kMXRoomJoinRuleInvite, @"The default room join rule should be invite"); - // Listen to live event. We should receive only one: a m.room.name event - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - XCTAssertEqual(event.eventType, MXEventTypeRoomJoinRules); + XCTAssertEqualObjects(liveTimeline.state.joinRule, kMXRoomJoinRuleInvite, @"The default room join rule should be invite"); - XCTAssertNotNil(room.state.joinRule); - XCTAssertEqualObjects(room.state.joinRule, kMXRoomJoinRulePublic, @"The room join rule is wrong"); + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [expectation fulfill]; + XCTAssertEqual(event.eventType, MXEventTypeRoomJoinRules); - }]; + XCTAssertNotNil(liveTimeline.state.joinRule); + XCTAssertEqualObjects(liveTimeline.state.joinRule, kMXRoomJoinRulePublic, @"The room join rule is wrong"); - // Change the join rule - [room setJoinRule:kMXRoomJoinRulePublic success:^{ + [expectation fulfill]; + + }]; + + // Change the join rule + [room setJoinRule:kMXRoomJoinRulePublic success:^{ + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; }]; - + } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); [expectation fulfill]; @@ -457,10 +491,12 @@ - (void)testRoomGuestAccessProvidedByInitialSync MXRoom *room = [mxSession roomWithRoomId:roomId]; - XCTAssertNotNil(room.state.joinRule); - XCTAssertEqualObjects(room.state.guestAccess, kMXRoomGuestAccessCanJoin, @"The room guest access is wrong"); + [room state:^(MXRoomState *roomState) { + XCTAssertNotNil(roomState.joinRule); + XCTAssertEqualObjects(roomState.guestAccess, kMXRoomGuestAccessCanJoin, @"The room guest access is wrong"); - [expectation fulfill]; + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -486,26 +522,28 @@ - (void)testRoomGuestAccessLive MXRoom *room = [mxSession roomWithRoomId:roomId]; - XCTAssertEqualObjects(room.state.guestAccess, kMXRoomGuestAccessCanJoin, @"The default room guest access should be forbidden"); - // Listen to live event. We should receive only one: a m.room.name event - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - XCTAssertEqual(event.eventType, MXEventTypeRoomGuestAccess); + XCTAssertEqualObjects(liveTimeline.state.guestAccess, kMXRoomGuestAccessCanJoin, @"The default room guest access should be forbidden"); - XCTAssertNotNil(room.state.guestAccess); - XCTAssertEqualObjects(room.state.guestAccess, kMXRoomGuestAccessForbidden, @"The room guest access is wrong"); + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [expectation fulfill]; + XCTAssertEqual(event.eventType, MXEventTypeRoomGuestAccess); - }]; + XCTAssertNotNil(liveTimeline.state.guestAccess); + XCTAssertEqualObjects(liveTimeline.state.guestAccess, kMXRoomGuestAccessForbidden, @"The room guest access is wrong"); - // Change the guest access - [room setGuestAccess:kMXRoomGuestAccessForbidden success:^{ + [expectation fulfill]; + }]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + // Change the guest access + [room setGuestAccess:kMXRoomGuestAccessForbidden success:^{ + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; }]; } failure:^(NSError *error) { @@ -535,16 +573,19 @@ - (void)testRoomCanonicalAliasProvidedByInitialSync [mxSession start:^{ MXRoom *room = [mxSession roomWithRoomId:roomId]; - - XCTAssertNotNil(room.state.aliases); - XCTAssertEqual(room.state.aliases.count, 1); - XCTAssertEqualObjects(room.state.aliases.firstObject, roomAlias, @"The room alias is wrong"); - - XCTAssertNotNil(room.state.canonicalAlias); - XCTAssertNotEqual(room.state.canonicalAlias.length, 0); - XCTAssertEqualObjects(room.state.canonicalAlias, roomAlias, @"The room canonical alias is wrong"); - - [expectation fulfill]; + + [room state:^(MXRoomState *roomState) { + + XCTAssertNotNil(roomState.aliases); + XCTAssertEqual(roomState.aliases.count, 1); + XCTAssertEqualObjects(roomState.aliases.firstObject, roomAlias, @"The room alias is wrong"); + + XCTAssertNotNil(roomState.canonicalAlias); + XCTAssertNotEqual(roomState.canonicalAlias.length, 0); + XCTAssertEqualObjects(roomState.canonicalAlias, roomAlias, @"The room canonical alias is wrong"); + + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -578,46 +619,49 @@ - (void)testRoomCanonicalAliasLive NSString *globallyUniqueString = [[NSProcessInfo processInfo] globallyUniqueString]; NSString *roomAlias = [NSString stringWithFormat:@"#%@%@", globallyUniqueString, bobRestClient.homeserverSuffix]; - XCTAssertNil(room.state.aliases); - XCTAssertNil(room.state.canonicalAlias); - // Listen to live event. We should receive only: a m.room.aliases and m.room.canonical_alias events - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - if(event.eventType == MXEventTypeRoomAliases) - { - XCTAssertNotNil(room.state.aliases); - XCTAssertEqual(room.state.aliases.count, 1); - XCTAssertEqualObjects(room.state.aliases.firstObject, roomAlias, @"The room alias is wrong"); - } - else if (event.eventType == MXEventTypeRoomCanonicalAlias) - { - XCTAssertNotNil(room.state.canonicalAlias); - XCTAssertNotEqual(room.state.canonicalAlias.length, 0); - XCTAssertEqualObjects(room.state.canonicalAlias, roomAlias, @"The room canonical alias is wrong"); - - [expectation fulfill]; - } - else - { - XCTFail(@"The event type is unexpected - type: %@", event.type); - } - - }]; - - // Set room alias - [room addAlias:roomAlias success:^{ - - [room setCanonicalAlias:roomAlias success:^{ - + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + + XCTAssertNil(liveTimeline.state.aliases); + XCTAssertNil(liveTimeline.state.canonicalAlias); + + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + if(event.eventType == MXEventTypeRoomAliases) + { + XCTAssertNotNil(liveTimeline.state.aliases); + XCTAssertEqual(liveTimeline.state.aliases.count, 1); + XCTAssertEqualObjects(liveTimeline.state.aliases.firstObject, roomAlias, @"The room alias is wrong"); + } + else if (event.eventType == MXEventTypeRoomCanonicalAlias) + { + XCTAssertNotNil(liveTimeline.state.canonicalAlias); + XCTAssertNotEqual(liveTimeline.state.canonicalAlias.length, 0); + XCTAssertEqualObjects(liveTimeline.state.canonicalAlias, roomAlias, @"The room canonical alias is wrong"); + + [expectation fulfill]; + } + else + { + XCTFail(@"The event type is unexpected - type: %@", event.type); + } + + }]; + + // Set room alias + [room addAlias:roomAlias success:^{ + + [room setCanonicalAlias:roomAlias success:^{ + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); [expectation fulfill]; }]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; }]; } failure:^(NSError *error) { @@ -637,20 +681,27 @@ - (void)testMembers MXRoom *room = [mxSession roomWithRoomId:roomId]; XCTAssertNotNil(room); - - NSArray *members = room.state.members; - XCTAssertEqual(members.count, 1, "There must be only one member: mxBob, the creator"); - - for (MXRoomMember *member in room.state.members) - { - XCTAssertTrue([member.userId isEqualToString:bobRestClient.credentials.userId], "This must be mxBob"); - } - - XCTAssertNotNil([room.state memberWithUserId:bobRestClient.credentials.userId], @"Bob must be retrieved"); - - XCTAssertNil([room.state memberWithUserId:@"NonExistingUserId"], @"getMember must return nil if the user does not exist"); - - [expectation fulfill]; + + [room members:^(MXRoomMembers *roomMembers) { + + NSArray *members = roomMembers.members; + XCTAssertEqual(members.count, 1, "There must be only one member: mxBob, the creator"); + + for (MXRoomMember *member in roomMembers.members) + { + XCTAssertTrue([member.userId isEqualToString:bobRestClient.credentials.userId], "This must be mxBob"); + } + + XCTAssertNotNil([roomMembers memberWithUserId:bobRestClient.credentials.userId], @"Bob must be retrieved"); + + XCTAssertNil([roomMembers memberWithUserId:@"NonExistingUserId"], @"getMember must return nil if the user does not exist"); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -666,14 +717,21 @@ - (void)testMemberName mxSession = mxSession2; NSString *bobUserId = matrixSDKTestsData.bobCredentials.userId; - NSString *bobMemberName = [room.state memberName:bobUserId]; - - XCTAssertNotNil(bobMemberName); - XCTAssertFalse([bobMemberName isEqualToString:@""], @"bobMemberName must not be an empty string"); - - XCTAssert([[room.state memberName:@"NonExistingUserId"] isEqualToString:@"NonExistingUserId"], @"memberName must return his id if the user does not exist"); - - [expectation fulfill]; + + [room members:^(MXRoomMembers *roomMembers) { + NSString *bobMemberName = [roomMembers memberName:bobUserId]; + + XCTAssertNotNil(bobMemberName); + XCTAssertFalse([bobMemberName isEqualToString:@""], @"bobMemberName must not be an empty string"); + + XCTAssert([[roomMembers memberName:@"NonExistingUserId"] isEqualToString:@"NonExistingUserId"], @"memberName must return his id if the user does not exist"); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; }]; } @@ -683,10 +741,12 @@ - (void)testStateEvents mxSession = mxSession2; - XCTAssertNotNil(room.state.stateEvents); - XCTAssertGreaterThan(room.state.stateEvents.count, 0); - - [expectation fulfill]; + [room state:^(MXRoomState *roomState) { + XCTAssertNotNil(roomState.stateEvents); + XCTAssertGreaterThan(roomState.stateEvents.count, 0); + + [expectation fulfill]; + }]; }]; } @@ -696,49 +756,19 @@ - (void)testAliases mxSession = mxSession2; - XCTAssertNotNil(room.state.aliases); - XCTAssertGreaterThanOrEqual(room.state.aliases.count, 1); - - NSString *alias = room.state.aliases[0]; - - XCTAssertTrue([alias hasPrefix:@"#mxPublic"]); - - [expectation fulfill]; - }]; -} + [room state:^(MXRoomState *roomState) { + XCTAssertNotNil(roomState.aliases); + XCTAssertGreaterThanOrEqual(roomState.aliases.count, 1); -// Test the room display name formatting: "roomName (roomAlias)" -- (void)testDisplayName1 -{ - [matrixSDKTestsData doMXSessionTestWithBobAndThePublicRoom:self readyToTest:^(MXSession *mxSession2, MXRoom *room, XCTestExpectation *expectation) { - - mxSession = mxSession2; + NSString *alias = roomState.aliases[0]; - XCTAssertNotNil(room.state.displayname); - XCTAssertTrue([room.state.displayname hasPrefix:@"MX Public Room test (#mxPublic"], @"We must retrieve the #mxPublic room settings"); - - [expectation fulfill]; + XCTAssertTrue([alias hasPrefix:@"#mxPublic"]); + + [expectation fulfill]; + }]; }]; } -// Test the room display name formatting: "userID" (self chat) -// Disabled as it seems that the registration method we use in tests now uses the -// local part of the user id as the default displayname -//- (void)testDisplayName2 -//{ -// [matrixSDKTestsData doMXSessionTestWithBobAndARoomWithMessages:self readyToTest:^(MXSession *mxSession2, MXRoom *room, XCTestExpectation *expectation) { -// -// mxSession = mxSession2; -// -// // Test room the display formatting: "roomName (roomAlias)" -// XCTAssertNotNil(room.state.displayname); -// XCTAssertTrue([room.state.displayname isEqualToString:mxSession.matrixRestClient.credentials.userId], @"The room name must be Bob's userID as he has no displayname: %@ - %@", room.state.displayname, mxSession.matrixRestClient.credentials.userId); -// -// [expectation fulfill]; -// }]; -//} - - /* Creates a room with the following historic. This scenario tests the "invite by other" behavior. @@ -819,22 +849,29 @@ - (void)testInviteByOtherInInitialSync XCTAssertNotNil(newRoom); - XCTAssertEqual(newRoom.state.membership, MXMembershipInvite); + XCTAssertEqual(newRoom.summary.membership, MXMembershipInvite); // The room has 2 members (Alice & Bob) - XCTAssertEqual(newRoom.state.members.count, 2); + XCTAssertEqual(newRoom.summary.membersCount.members, 2); - MXRoomMember *alice = [newRoom.state memberWithUserId:aliceRestClient.credentials.userId]; - XCTAssertNotNil(alice); - XCTAssertEqual(alice.membership, MXMembershipInvite); - XCTAssert([alice.originUserId isEqualToString:bobRestClient.credentials.userId], @"Wrong inviter: %@", alice.originUserId); - - // The last message should be an invite m.room.member - MXEvent *lastMessage = newRoom.summary.lastMessageEvent; - XCTAssertEqual(lastMessage.eventType, MXEventTypeRoomMember, @"The last message should be an invite m.room.member"); - XCTAssertLessThan([[NSDate date] timeIntervalSince1970] * 1000 - lastMessage.originServerTs, 3000); - - [expectation fulfill]; + [newRoom members:^(MXRoomMembers *roomMembers) { + + MXRoomMember *alice = [roomMembers memberWithUserId:aliceRestClient.credentials.userId]; + XCTAssertNotNil(alice); + XCTAssertEqual(alice.membership, MXMembershipInvite); + XCTAssert([alice.originUserId isEqualToString:bobRestClient.credentials.userId], @"Wrong inviter: %@", alice.originUserId); + + // The last message should be an invite m.room.member + MXEvent *lastMessage = newRoom.summary.lastMessageEvent; + XCTAssertEqual(lastMessage.eventType, MXEventTypeRoomMember, @"The last message should be an invite m.room.member"); + XCTAssertLessThan([[NSDate date] timeIntervalSince1970] * 1000 - lastMessage.originServerTs, 3000); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -867,27 +904,34 @@ - (void)testInviteByOtherInLive XCTAssertNotNil(newRoom); - if (newRoom.state.membership != MXMembershipUnknown) + if (newRoom.summary.membership != MXMembershipUnknown) { - XCTAssertEqual(newRoom.state.membership, MXMembershipInvite); + XCTAssertEqual(newRoom.summary.membership, MXMembershipInvite); // The room has 2 members (Alice & Bob) - XCTAssertEqual(newRoom.state.members.count, 2); + XCTAssertEqual(newRoom.summary.membersCount.members, 2); + + [newRoom members:^(MXRoomMembers *roomMembers) { - MXRoomMember *alice = [newRoom.state memberWithUserId:aliceRestClient.credentials.userId]; - XCTAssertNotNil(alice); - XCTAssertEqual(alice.membership, MXMembershipInvite); - XCTAssert([alice.originUserId isEqualToString:bobRestClient.credentials.userId], @"Wrong inviter: %@", alice.originUserId); + MXRoomMember *alice = [roomMembers memberWithUserId:aliceRestClient.credentials.userId]; + XCTAssertNotNil(alice); + XCTAssertEqual(alice.membership, MXMembershipInvite); + XCTAssert([alice.originUserId isEqualToString:bobRestClient.credentials.userId], @"Wrong inviter: %@", alice.originUserId); - // The last message should be an invite m.room.member - dispatch_async(dispatch_get_main_queue(), ^{ // We could also wait for kMXRoomSummaryDidChangeNotification + // The last message should be an invite m.room.member + dispatch_async(dispatch_get_main_queue(), ^{ // We could also wait for kMXRoomSummaryDidChangeNotification - MXEvent *lastMessage = newRoom.summary.lastMessageEvent; - XCTAssertNotNil(lastMessage); - XCTAssertEqual(lastMessage.eventType, MXEventTypeRoomMember, @"The last message should be an invite m.room.member"); - XCTAssertLessThan([[NSDate date] timeIntervalSince1970] * 1000 - lastMessage.originServerTs, 3000); + MXEvent *lastMessage = newRoom.summary.lastMessageEvent; + XCTAssertNotNil(lastMessage); + XCTAssertEqual(lastMessage.eventType, MXEventTypeRoomMember, @"The last message should be an invite m.room.member"); + XCTAssertLessThan([[NSDate date] timeIntervalSince1970] * 1000 - lastMessage.originServerTs, 3000); - }); + }); + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; } } @@ -925,16 +969,18 @@ - (void)testMXRoomJoin [mxSession start:^{ MXRoom *newRoom = [mxSession roomWithRoomId:roomId]; - - [newRoom.liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - if (MXTimelineDirectionForwards == event) - { - // We should receive only join events in live - XCTAssertEqual(event.eventType, MXEventTypeRoomMember); - MXRoomMemberEventContent *roomMemberEventContent = [MXRoomMemberEventContent modelFromJSON:event.content]; - XCTAssert([roomMemberEventContent.membership isEqualToString:kMXMembershipStringJoin]); - } + [newRoom liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + if (MXTimelineDirectionForwards == event) + { + // We should receive only join events in live + XCTAssertEqual(event.eventType, MXEventTypeRoomMember); + + MXRoomMemberEventContent *roomMemberEventContent = [MXRoomMemberEventContent modelFromJSON:event.content]; + XCTAssert([roomMemberEventContent.membership isEqualToString:kMXMembershipStringJoin]); + } + }]; }]; [mxSession listenToEvents:^(MXEvent *event, MXTimelineDirection direction, id customObject) { @@ -952,10 +998,10 @@ - (void)testMXRoomJoin // Now, we must have more information about the room // Check its new state - XCTAssertEqual(newRoom.state.members.count, 2); - XCTAssert([newRoom.state.topic isEqualToString:@"We test room invitation here"], @"Wrong topic. Found: %@", newRoom.state.topic); + XCTAssertEqual(newRoom.summary.membersCount.members, 2); + XCTAssertEqualObjects(newRoom.summary.topic, @"We test room invitation here"); - XCTAssertEqual(newRoom.state.membership, MXMembershipJoin); + XCTAssertEqual(newRoom.summary.membership, MXMembershipJoin); XCTAssertNotNil(newRoom.summary.lastMessageEventId); XCTAssertNotNil(newRoom.summary.lastMessageEvent); @@ -999,22 +1045,25 @@ - (void)testMXSessionJoinOnPublicRoom [mxSession joinRoom:roomId success:^(MXRoom *room) { - XCTAssert([room.state.roomId isEqualToString:roomId]); + XCTAssert([room.roomId isEqualToString:roomId]); MXRoom *newRoom = [mxSession roomWithRoomId:roomId]; XCTAssert(newRoom, @"The room must be known now by the user"); - - // Now, we must have more information about the room - // Check its new state - XCTAssertEqual(newRoom.state.isJoinRulePublic, YES); - XCTAssertEqual(newRoom.state.members.count, 2); - XCTAssert([newRoom.state.topic isEqualToString:@"We test room invitation here"], @"Wrong topic. Found: %@", newRoom.state.topic); - - XCTAssertEqual(newRoom.state.membership, MXMembershipJoin); - XCTAssertNotNil(newRoom.summary.lastMessageEvent); - XCTAssertEqual(newRoom.summary.lastMessageEvent.eventType, MXEventTypeRoomMember, @"The last should be a m.room.member event indicating Alice joining the room"); - - [expectation fulfill]; + + [newRoom state:^(MXRoomState *roomState) { + + // Now, we must have more information about the room + // Check its new state + XCTAssertEqual(roomState.isJoinRulePublic, YES); + XCTAssertEqual(newRoom.summary.membersCount.members, 2); + XCTAssertEqualObjects(roomState.topic, @"We test room invitation here"); + + XCTAssertEqual(newRoom.summary.membership, MXMembershipJoin); + XCTAssertNotNil(newRoom.summary.lastMessageEvent); + XCTAssertEqual(newRoom.summary.lastMessageEvent.eventType, MXEventTypeRoomMember, @"The last should be a m.room.member event indicating Alice joining the room"); + + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -1042,47 +1091,50 @@ - (void)testPowerLevels MXRoom *room = [mxSession roomWithRoomId:roomId]; - MXRoomPowerLevels *roomPowerLevels = room.state.powerLevels; + [room state:^(MXRoomState *roomState) { - XCTAssertNotNil(roomPowerLevels); + MXRoomPowerLevels *roomPowerLevels = roomState.powerLevels; - // Check the user power level - XCTAssertNotNil(roomPowerLevels.users); - XCTAssertEqual(roomPowerLevels.users.count, 1); - XCTAssertEqualObjects(roomPowerLevels.users[bobRestClient.credentials.userId], [NSNumber numberWithUnsignedInteger: 100], @"By default power level of room creator is 100"); + XCTAssertNotNil(roomPowerLevels); - NSUInteger powerlLevel = [roomPowerLevels powerLevelOfUserWithUserID:bobRestClient.credentials.userId]; - XCTAssertEqual(powerlLevel, 100, @"By default power level of room creator is 100"); + // Check the user power level + XCTAssertNotNil(roomPowerLevels.users); + XCTAssertEqual(roomPowerLevels.users.count, 1); + XCTAssertEqualObjects(roomPowerLevels.users[bobRestClient.credentials.userId], [NSNumber numberWithUnsignedInteger: 100], @"By default power level of room creator is 100"); - powerlLevel = [roomPowerLevels powerLevelOfUserWithUserID:@"randomUserId"]; - XCTAssertEqual(powerlLevel, roomPowerLevels.usersDefault, @"Power level of user with no attributed power level must default to usersDefault"); + NSUInteger powerlLevel = [roomPowerLevels powerLevelOfUserWithUserID:bobRestClient.credentials.userId]; + XCTAssertEqual(powerlLevel, 100, @"By default power level of room creator is 100"); - // Check minimum power level for actions - // Hope the HS will not change these values - XCTAssertEqual(roomPowerLevels.ban, 50); - XCTAssertEqual(roomPowerLevels.kick, 50); - XCTAssertEqual(roomPowerLevels.redact, 50); + powerlLevel = [roomPowerLevels powerLevelOfUserWithUserID:@"randomUserId"]; + XCTAssertEqual(powerlLevel, roomPowerLevels.usersDefault, @"Power level of user with no attributed power level must default to usersDefault"); - // Check power level to send events - XCTAssertNotNil(roomPowerLevels.events); - XCTAssertGreaterThan(roomPowerLevels.events.allKeys.count, 0); + // Check minimum power level for actions + // Hope the HS will not change these values + XCTAssertEqual(roomPowerLevels.ban, 50); + XCTAssertEqual(roomPowerLevels.kick, 50); + XCTAssertEqual(roomPowerLevels.redact, 50); - NSUInteger minimumPowerLevelForEvent; - for (MXEventTypeString eventTypeString in roomPowerLevels.events.allKeys) - { - minimumPowerLevelForEvent = [roomPowerLevels minimumPowerLevelForSendingEventAsStateEvent:eventTypeString]; + // Check power level to send events + XCTAssertNotNil(roomPowerLevels.events); + XCTAssertGreaterThan(roomPowerLevels.events.allKeys.count, 0); - XCTAssertEqualObjects(roomPowerLevels.events[eventTypeString], [NSNumber numberWithUnsignedInteger:minimumPowerLevelForEvent]); - } + NSUInteger minimumPowerLevelForEvent; + for (MXEventTypeString eventTypeString in roomPowerLevels.events.allKeys) + { + minimumPowerLevelForEvent = [roomPowerLevels minimumPowerLevelForSendingEventAsStateEvent:eventTypeString]; - minimumPowerLevelForEvent = [roomPowerLevels minimumPowerLevelForSendingEventAsMessage:kMXEventTypeStringRoomMessage]; - XCTAssertEqual(minimumPowerLevelForEvent, roomPowerLevels.eventsDefault); + XCTAssertEqualObjects(roomPowerLevels.events[eventTypeString], [NSNumber numberWithUnsignedInteger:minimumPowerLevelForEvent]); + } + minimumPowerLevelForEvent = [roomPowerLevels minimumPowerLevelForSendingEventAsMessage:kMXEventTypeStringRoomMessage]; + XCTAssertEqual(minimumPowerLevelForEvent, roomPowerLevels.eventsDefault); - minimumPowerLevelForEvent = [roomPowerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomTopic]; - XCTAssertEqual(minimumPowerLevelForEvent, roomPowerLevels.stateDefault); - [expectation fulfill]; + minimumPowerLevelForEvent = [roomPowerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomTopic]; + XCTAssertEqual(minimumPowerLevelForEvent, roomPowerLevels.stateDefault); + + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -1115,7 +1167,7 @@ - (void)testRoomStateWhenARoomHasBeenJoinedOnAnotherMatrixClient MXRoom *room = [mxSession roomWithRoomId:event.roomId]; XCTAssert(room); - XCTAssertEqual(room.state.members.count, 2, @"If this count is wrong, the room state is invalid"); + XCTAssertEqual(room.summary.membersCount.members, 2, @"If this count is wrong, the room state is invalid"); [expectation fulfill]; } @@ -1164,7 +1216,7 @@ - (void)testRoomStateWhenARoomHasBeenJoinedOnAnotherMatrixClientAndNotifications MXRoom *room = [mxSession roomWithRoomId:newRoomId]; XCTAssertNotNil(room); - BOOL isSync = (room.state.membership != MXMembershipInvite && room.state.membership != MXMembershipUnknown); + BOOL isSync = (room.summary.membership != MXMembershipInvite && room.summary.membership != MXMembershipUnknown); XCTAssertFalse(isSync, @"The room is not yet sync'ed"); [[NSNotificationCenter defaultCenter] removeObserver:newRoomObserver]; @@ -1177,9 +1229,9 @@ - (void)testRoomStateWhenARoomHasBeenJoinedOnAnotherMatrixClientAndNotifications MXRoom *room = note.object; - XCTAssertEqualObjects(newRoomId, room.state.roomId); + XCTAssertEqualObjects(newRoomId, room.roomId); - BOOL isSync = (room.state.membership != MXMembershipInvite && room.state.membership != MXMembershipUnknown); + BOOL isSync = (room.summary.membership != MXMembershipInvite && room.summary.membership != MXMembershipUnknown); XCTAssert(isSync, @"The room must be sync'ed now"); [[NSNotificationCenter defaultCenter] removeObserver:initialSyncObserver]; diff --git a/MatrixSDKTests/MXRoomSummaryTests.m b/MatrixSDKTests/MXRoomSummaryTests.m index 8c6d8f386f..03cdc8dc07 100644 --- a/MatrixSDKTests/MXRoomSummaryTests.m +++ b/MatrixSDKTests/MXRoomSummaryTests.m @@ -122,7 +122,7 @@ - (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary } else if ([self.description containsString:@"testStatePassedToMXRoomSummaryUpdating"]) { - XCTAssertNotEqualObjects(eventState.displayname, @"A room", @"The passed state must be the state of room when the event occured, not the current room state"); + XCTAssertNotEqualObjects(eventState.name, @"A room", @"The passed state must be the state of room when the event occured, not the current room state"); // Do a classic update MXRoomSummaryUpdater *updater = [MXRoomSummaryUpdater roomSummaryUpdaterForSession:session]; @@ -161,11 +161,11 @@ - (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary return updated; } -- (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary withStateEvents:(NSArray *)stateEvents +- (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary withStateEvents:(NSArray *)stateEvents roomState:(MXRoomState *)roomState { // Do a classic update MXRoomSummaryUpdater *updater = [MXRoomSummaryUpdater roomSummaryUpdaterForSession:session]; - return [updater session:session updateRoomSummary:summary withStateEvents:stateEvents]; + return [updater session:session updateRoomSummary:summary withStateEvents:stateEvents roomState:roomState]; } - (void)test @@ -253,7 +253,7 @@ - (void)testGetLastMessageFromPagination [mxSession2 setStore:[[MXMemoryStore alloc] init] success:^{ // Start a new session by loading no message - [mxSession2 startWithMessagesLimit:0 onServerSyncDone:^{ + [mxSession2 startWithSyncFilter:[MXFilterJSONModel syncFilterWithMessageLimit:0] onServerSyncDone:^{ MXRoomSummary *summary2 = [mxSession2 roomSummaryWithRoomId:roomId]; @@ -320,7 +320,7 @@ - (void)testGetLastMessageFromSeveralPaginations [mxSession2 setStore:[[MXMemoryStore alloc] init] success:^{ // Start a new session by loading no message - [mxSession2 startWithMessagesLimit:0 onServerSyncDone:^{ + [mxSession2 startWithSyncFilter:[MXFilterJSONModel syncFilterWithMessageLimit:0] onServerSyncDone:^{ MXRoomSummary *summary2 = [mxSession2 roomSummaryWithRoomId:roomId]; @@ -385,7 +385,7 @@ - (void)testFixRoomsSummariesLastMessage [mxSession2 setStore:[[MXMemoryStore alloc] init] success:^{ // Start a new session by loading no message - [mxSession2 startWithMessagesLimit:0 onServerSyncDone:^{ + [mxSession2 startWithSyncFilter:[MXFilterJSONModel syncFilterWithMessageLimit:0] onServerSyncDone:^{ MXRoomSummary *summary2 = [mxSession2 roomSummaryWithRoomId:roomId]; @@ -441,6 +441,70 @@ - (void)testDisplaynameUpdate }]; } +// Check membership when: +// - the user is in the room +// - he has left it +- (void)testMembership +{ + [matrixSDKTestsData doMXSessionTestWithBobAndARoom:self andStore:[[MXFileStore alloc] init] readyToTest:^(MXSession *mxSession, MXRoom *room, XCTestExpectation *expectation) { + + XCTAssertEqual(room.summary.membership, MXMembershipJoin); + + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMember] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + if (direction == MXTimelineDirectionForwards) + { + XCTAssertEqual(room.summary.membership, MXMembershipLeave); + + [expectation fulfill]; + } + }]; + }]; + + [room leave:nil failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + }]; +} + +// Check members count when: +// - Bob is the only one in a room +// - Bob has invited Alice +- (void)testMembersCount +{ + [matrixSDKTestsData doMXSessionTestWithBobAndARoom:self andStore:[[MXFileStore alloc] init] readyToTest:^(MXSession *mxSession, MXRoom *room, XCTestExpectation *expectation) { + + XCTAssertEqual(room.summary.membersCount.members, 1); + XCTAssertEqual(room.summary.membersCount.joined, 1); + XCTAssertEqual(room.summary.membersCount.invited, 0); + + [matrixSDKTestsData doMXSessionTestWithAlice:nil readyToTest:^(MXSession *aliceSession, XCTestExpectation *expectation2) { + + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMember] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + if (direction == MXTimelineDirectionForwards) + { + XCTAssertEqual(room.summary.membersCount.members, 2); + XCTAssertEqual(room.summary.membersCount.joined, 1); + XCTAssertEqual(room.summary.membersCount.invited, 1); + + [expectation fulfill]; + } + }]; + }]; + + [room inviteUser:aliceSession.myUser.userId success:nil failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + }]; + + }]; +} + // @TODO(summary): It breaks the tests suite :/ //- (void)testResetRoomStateData //{ @@ -707,11 +771,10 @@ - (void)testRedaction switch (notifCount++) { case 0: - case 1: - // Do not care about the local echo update for MXEventSentStateSending and then MXEventSentStateSent + // Do not care about the local echo update for MXEventSentStateSending break; - case 2: + case 1: { XCTAssertEqualObjects(summary.lastMessageEventId, newEventId); @@ -724,7 +787,7 @@ - (void)testRedaction break; } - case 3: + case 2: { XCTAssertEqualObjects(summary.lastMessageEventId, lastMessageEventId, @"We must come back to the previous event"); @@ -780,7 +843,7 @@ - (void)testStateEventRedaction break; } - case 1: + case 2: { XCTAssertEqualObjects(summary.lastMessageEventId, lastMessageEventId, @"We must come back to the previous event"); @@ -852,7 +915,7 @@ - (void)testClearCacheAfterStateEventRedaction defaultUpdater.ignoreRedactedEvent = YES; // Start a new session by loading no message - [mxSession2 startWithMessagesLimit:10 onServerSyncDone:^{ + [mxSession2 startWithSyncFilter:[MXFilterJSONModel syncFilterWithMessageLimit:10] onServerSyncDone:^{ MXRoomSummary *summary2 = [mxSession2 roomSummaryWithRoomId:roomId]; @@ -899,10 +962,13 @@ - (void)testStatePassedToMXRoomSummaryUpdating observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomSummaryDidChangeNotification object:summary queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { - XCTAssertEqualObjects(room.state.displayname, displayName); - XCTAssertEqualObjects(summary.displayname, displayName, @"Room summary must be updated"); + [room state:^(MXRoomState *roomState) { - [expectation fulfill]; + XCTAssertEqualObjects(roomState.name, displayName); + XCTAssertEqualObjects(summary.displayname, displayName, @"Room summary must be updated"); + + [expectation fulfill]; + }]; }]; [room setName:displayName success:nil failure:^(NSError *error) { @@ -1102,8 +1168,8 @@ - (void)testLateRoomKey warnOnUnknowDevices:NO aliceStore:[[MXMemoryStore alloc] init] bobStore:[[MXMemoryStore alloc] init] - readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { - + readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) + { bobSession.roomSummaryUpdateDelegate = self; NSString *messageFromAlice = @"Hello I'm Alice!"; @@ -1119,24 +1185,26 @@ - (void)testLateRoomKey toDeviceEvent = notif.userInfo[kMXSessionNotificationEventKey]; }]; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - // Make crypto forget the inbound group session now - // MXRoomSummary will not be able to decrypt it - XCTAssert(toDeviceEvent); - NSString *sessionId = toDeviceEvent.content[@"session_id"]; + // Make crypto forget the inbound group session now + // MXRoomSummary will not be able to decrypt it + XCTAssert(toDeviceEvent); + NSString *sessionId = toDeviceEvent.content[@"session_id"]; - id bobCryptoStore = (id)[bobSession.crypto.olmDevice valueForKey:@"store"]; - [bobCryptoStore removeInboundGroupSessionWithId:sessionId andSenderKey:toDeviceEvent.senderKey]; + id bobCryptoStore = (id)[bobSession.crypto.olmDevice valueForKey:@"store"]; + [bobCryptoStore removeInboundGroupSessionWithId:sessionId andSenderKey:toDeviceEvent.senderKey]; - // So that we cannot decrypt it anymore right now - [event setClearData:nil]; - BOOL b = [bobSession decryptEvent:event inTimeline:nil]; + // So that we cannot decrypt it anymore right now + [event setClearData:nil]; + BOOL b = [bobSession decryptEvent:event inTimeline:nil]; - XCTAssertFalse(b, @"Failed to set up test condition"); + XCTAssertFalse(b, @"Failed to set up test condition"); + }]; }]; - MXRoomSummary *roomSummaryFromBobPOV = roomFromBobPOV.summary; __block NSString *lastMessageEventId; @@ -1203,6 +1271,10 @@ - (void)testNotificationCountUpdate bobSession.roomSummaryUpdateDelegate = self; MXRoom *room = [bobSession roomWithRoomId:roomId]; + + // Set a RR position to get notifications for new incoming messsages + [room markAllAsRead]; + MXRoomSummary *summary = room.summary; NSUInteger notificationCount = room.summary.notificationCount; diff --git a/MatrixSDKTests/MXRoomTests.m b/MatrixSDKTests/MXRoomTests.m index 554ff95f0d..f5ca325eff 100644 --- a/MatrixSDKTests/MXRoomTests.m +++ b/MatrixSDKTests/MXRoomTests.m @@ -20,6 +20,8 @@ #import "MatrixSDKTestsData.h" #import "MXSession.h" +#import "MXTools.h" +#import "MXSendReplyEventDefaultStringLocalizations.h" // Do not bother with retain cycles warnings in tests #pragma clang diagnostic push @@ -66,7 +68,7 @@ - (void)testListenerForAllLiveEvents __block NSString *sentMessageEventID; __block NSString *receivedMessageEventID; - void (^checkEventIDs)() = ^ void () + void (^checkEventIDs)(void) = ^ void () { if (sentMessageEventID && receivedMessageEventID) { @@ -77,20 +79,22 @@ - (void)testListenerForAllLiveEvents }; // Register the listener - [room.liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - XCTAssertEqual(direction, MXTimelineDirectionForwards); - - XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); - - XCTAssertNotNil(event.eventId); - - receivedMessageEventID = event.eventId; - - checkEventIDs(); + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + XCTAssertEqual(direction, MXTimelineDirectionForwards); + + XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); + + XCTAssertNotNil(event.eventId); + + receivedMessageEventID = event.eventId; + + checkEventIDs(); + }]; }]; - // Populate a text message in parallel [matrixSDKTestsData doMXRestClientTestWithBobAndThePublicRoom:nil readyToTest:^(MXRestClient *bobRestClient, NSString *roomId, XCTestExpectation *expectation2) { @@ -121,7 +125,7 @@ - (void)testListenerForRoomMessageLiveEvents __block NSString *sentMessageEventID; __block NSString *receivedMessageEventID; - void (^checkEventIDs)() = ^ void () + void (^checkEventIDs)(void) = ^ void (void) { if (sentMessageEventID && receivedMessageEventID) { @@ -132,18 +136,21 @@ - (void)testListenerForRoomMessageLiveEvents }; // Register the listener for m.room.message.only - [room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] - onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - XCTAssertEqual(direction, MXTimelineDirectionForwards); - - XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); - - XCTAssertNotNil(event.eventId); - - receivedMessageEventID = event.eventId; - - checkEventIDs(); + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] + onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + XCTAssertEqual(direction, MXTimelineDirectionForwards); + + XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); + + XCTAssertNotNil(event.eventId); + + receivedMessageEventID = event.eventId; + + checkEventIDs(); + }]; }]; // Populate a text message in parallel @@ -172,12 +179,14 @@ - (void)testLeave mxSession = mxSession2; - NSString *roomId = room.state.roomId; + NSString *roomId = room.roomId; __block MXMembership lastKnownMembership = MXMembershipUnknown; - [room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMember] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMember] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - lastKnownMembership = room.state.membership; + lastKnownMembership = liveTimeline.state.membership; + }]; }]; // This implicitly tests MXSession leaveRoom @@ -207,17 +216,19 @@ - (void)testJoin [bobRestClient inviteUser:aliceRestClient.credentials.userId toRoom:roomId success:^{ - [mxSession startWithMessagesLimit:0 onServerSyncDone:^{ + + [mxSession startWithSyncFilter:[MXFilterJSONModel syncFilterWithMessageLimit:0] + onServerSyncDone:^{ MXRoom *room = [mxSession roomWithRoomId:roomId]; - XCTAssertEqual(room.state.membership, MXMembershipInvite); - XCTAssertEqual(room.state.members.count, 2, @"The room state information is limited while the room is joined"); + XCTAssertEqual(room.summary.membership, MXMembershipInvite); + XCTAssertEqual(room.summary.membersCount.members, 2, @"The room state information is limited while the room is joined"); [room join:^{ - XCTAssertEqual(room.state.membership, MXMembershipJoin); - XCTAssertEqual(room.state.members.count, 2, @"The room state must be fully known (after an initialSync on the room"); + XCTAssertEqual(room.summary.membership, MXMembershipJoin); + XCTAssertEqual(room.summary.membersCount.members, 2, @"The room state must be fully known (after an initialSync on the room"); [expectation fulfill]; @@ -249,11 +260,13 @@ - (void)testSetPowerLevelOfUser MXRoom *room = [mxSession roomWithRoomId:roomId]; - [room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomPowerLevels] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomPowerLevels] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssertEqual([room.state.powerLevels powerLevelOfUserWithUserID:aliceRestClient.credentials.userId], 36); + XCTAssertEqual([liveTimeline.state.powerLevels powerLevelOfUserWithUserID:aliceRestClient.credentials.userId], 36); - [expectation fulfill]; + [expectation fulfill]; + }]; }]; [room setPowerLevelOfUserWithUserID:aliceRestClient.credentials.userId powerLevel:36 success:^{ @@ -276,33 +289,35 @@ - (void)testPaginateBackMessagesCancel mxSession = [[MXSession alloc] initWithMatrixRestClient:bobRestClient]; - [mxSession startWithMessagesLimit:0 onServerSyncDone:^{ + [mxSession startWithSyncFilter:[MXFilterJSONModel syncFilterWithMessageLimit:0] onServerSyncDone:^{ MXRoom *room = [mxSession roomWithRoomId:roomId]; __block NSUInteger eventCount = 0; - [room.liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - eventCount++; - XCTFail(@"We should not receive events. Received: %@", event); - [expectation fulfill]; + eventCount++; + XCTFail(@"We should not receive events. Received: %@", event); + [expectation fulfill]; - }]; + }]; - [room.liveTimeline resetPagination]; - MXHTTPOperation *pagination = [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + [liveTimeline resetPagination]; + MXHTTPOperation *pagination = [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - XCTFail(@"The cancelled operation must not complete"); - [expectation fulfill]; + XCTFail(@"The cancelled operation must not complete"); + [expectation fulfill]; - } failure:^(NSError *error) { - XCTAssertEqual(eventCount, 0, "We should have received events in registerEventListenerForTypes"); - [expectation fulfill]; - }]; + } failure:^(NSError *error) { + XCTAssertEqual(eventCount, 0, "We should have received events in registerEventListenerForTypes"); + [expectation fulfill]; + }]; - XCTAssertNotNil(pagination); + XCTAssertNotNil(pagination); - [pagination cancel]; + [pagination cancel]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -322,12 +337,14 @@ - (void)testTypingUsersNotifications XCTAssertEqual(room.typingUsers.count, 0); - [room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringTypingNotification] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringTypingNotification] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssertEqual(room.typingUsers.count, 1); - XCTAssertEqualObjects(room.typingUsers[0], bobRestClient.credentials.userId); + XCTAssertEqual(room.typingUsers.count, 1); + XCTAssertEqualObjects(room.typingUsers[0], bobRestClient.credentials.userId); - [expectation fulfill]; + [expectation fulfill]; + }]; }]; [room sendTypingNotification:YES timeout:30000 success:^{ @@ -355,30 +372,33 @@ - (void)testAddAndRemoveTag __block NSUInteger tagEventUpdata = 0; // Wait for the m.tag event to get the room tags update - [room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomTag] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomTag] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - if (++tagEventUpdata == 1) - { - // This event is fired after the [room addTag:] request - MXRoomTag *roomTag = room.accountData.tags[tag]; + if (++tagEventUpdata == 1) + { + // This event is fired after the [room addTag:] request + MXRoomTag *roomTag = room.accountData.tags[tag]; - XCTAssertNotNil(roomTag); - XCTAssertEqualObjects(roomTag.name, tag); - XCTAssertEqualObjects(roomTag.order, order); + XCTAssertNotNil(roomTag); + XCTAssertEqualObjects(roomTag.name, tag); + XCTAssertEqualObjects(roomTag.order, order); + + [room removeTag:tag success:nil failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } + else if (tagEventUpdata == 2) + { + // This event is fired after the [room removeTag:] request + XCTAssertNotNil(room.accountData.tags); + XCTAssertEqual(room.accountData.tags.count, 0); - [room removeTag:tag success:nil failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); [expectation fulfill]; - }]; - } - else if (tagEventUpdata == 2) - { - // This event is fired after the [room removeTag:] request - XCTAssertNotNil(room.accountData.tags); - XCTAssertEqual(room.accountData.tags.count, 0); + } + }]; - [expectation fulfill]; - } }]; // Do the test @@ -400,17 +420,19 @@ - (void)testReplaceTag NSString *newTagOrder = nil; // Wait for the m.tag event that corresponds to "newTag" - [room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomTag] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomTag] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - MXRoomTag *newRoomTag = room.accountData.tags[newTag]; - if (newRoomTag) - { - XCTAssertNotNil(newRoomTag); - XCTAssertEqualObjects(newRoomTag.name, newTag); - XCTAssertEqualObjects(newRoomTag.order, newTagOrder); + MXRoomTag *newRoomTag = room.accountData.tags[newTag]; + if (newRoomTag) + { + XCTAssertNotNil(newRoomTag); + XCTAssertEqualObjects(newRoomTag.name, newTag); + XCTAssertEqualObjects(newRoomTag.order, newTagOrder); - [expectation fulfill]; - } + [expectation fulfill]; + } + }]; }]; // Prepare initial condition: have a tag @@ -516,6 +538,113 @@ - (void)tesRoomDirectoryVisibilityLive }]; } +- (void)testSendReplyToTextMessage +{ + NSString *firstMessage = @"**First message!**"; + NSString *firstFormattedMessage = @"

First message!

"; + + NSString *secondMessageReplyToFirst = @"**Reply to first message**"; + NSString *secondMessageFormattedReplyToFirst = @"

Reply to first message

"; + + NSString *expectedSecondEventBodyStringFormat = @"> <%@> **First message!**\n\n**Reply to first message**"; + NSString *expectedSecondEventFormattedBodyStringFormat = @"
In reply to %@

First message!

Reply to first message

"; + + NSString *thirdMessageReplyToSecond = @"**Reply to second message**"; + NSString *thirdMessageFormattedReplyToSecond = @"

Reply to second message

"; + + NSString *expectedThirdEventBodyStringFormat = @"> <%@> **Reply to first message**\n\n**Reply to second message**"; + NSString *expectedThirdEventFormattedBodyStringFormat = @"
In reply to %@

Reply to first message

Reply to second message

"; + + MXSendReplyEventDefaultStringLocalizations *defaultStringLocalizations = [MXSendReplyEventDefaultStringLocalizations new]; + + [matrixSDKTestsData doMXSessionTestWithBobAndARoomWithMessages:self readyToTest:^(MXSession *mxSession, MXRoom *room, XCTestExpectation *expectation) { + + __block NSUInteger messageCount = 0; + + // Listen to messages + [room listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + messageCount++; + + if (messageCount == 1) + { + __block MXEvent *localEchoEvent = nil; + + // Reply to first message + [room sendReplyToEvent:event withTextMessage:secondMessageReplyToFirst formattedTextMessage:secondMessageFormattedReplyToFirst stringLocalizations:defaultStringLocalizations localEcho:&localEchoEvent success:^(NSString *eventId) { + NSLog(@"Send reply to first message with success"); + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + XCTAssertNotNil(localEchoEvent); + + NSString *roomId = room.roomId; + NSString *firstEventId = event.eventId; + NSString *firstEventSender = event.sender; + + NSString *secondEventBody = localEchoEvent.content[@"body"]; + NSString *secondEventFormattedBody = localEchoEvent.content[@"formatted_body"]; + NSString *secondEventRelatesToEventId = localEchoEvent.content[@"m.relates_to"][@"m.in_reply_to"][@"event_id"]; + + NSString *permalinkToUser = [MXTools permalinkToUserWithUserId:firstEventSender]; + NSString *permalinkToEvent = [MXTools permalinkToEvent:firstEventId inRoom:roomId]; + + NSString *expectedSecondEventBody = [NSString stringWithFormat:expectedSecondEventBodyStringFormat, firstEventSender]; + NSString *expectedSecondEventFormattedBody = [NSString stringWithFormat:expectedSecondEventFormattedBodyStringFormat, permalinkToEvent, permalinkToUser, firstEventSender]; + + XCTAssertEqualObjects(secondEventBody, expectedSecondEventBody); + XCTAssertEqualObjects(secondEventFormattedBody, expectedSecondEventFormattedBody); + XCTAssertEqualObjects(firstEventId, secondEventRelatesToEventId); + } + else if (messageCount == 2) + { + __block MXEvent *localEchoEvent = nil; + + // Reply to second message, which was also a reply + [room sendReplyToEvent:event withTextMessage:thirdMessageReplyToSecond formattedTextMessage:thirdMessageFormattedReplyToSecond stringLocalizations:defaultStringLocalizations localEcho:&localEchoEvent success:^(NSString *eventId) { + NSLog(@"Send reply to second message with success"); + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + XCTAssertNotNil(localEchoEvent); + + NSString *roomId = room.roomId; + NSString *secondEventId = event.eventId; + NSString *secondEventSender = event.sender; + + NSString *thirdEventBody = localEchoEvent.content[@"body"]; + NSString *thirdEventFormattedBody = localEchoEvent.content[@"formatted_body"]; + NSString *thirdEventRelatesToEventId = localEchoEvent.content[@"m.relates_to"][@"m.in_reply_to"][@"event_id"]; + + NSString *permalinkToUser = [MXTools permalinkToUserWithUserId:secondEventSender]; + NSString *permalinkToEvent = [MXTools permalinkToEvent:secondEventId inRoom:roomId]; + + NSString *expectedThirdEventBody = [NSString stringWithFormat:expectedThirdEventBodyStringFormat, secondEventSender]; + NSString *expectedThirdEventFormattedBody = [NSString stringWithFormat:expectedThirdEventFormattedBodyStringFormat, permalinkToEvent, permalinkToUser, secondEventSender]; + + XCTAssertEqualObjects(thirdEventBody, expectedThirdEventBody); + XCTAssertEqualObjects(thirdEventFormattedBody, expectedThirdEventFormattedBody); + XCTAssertEqualObjects(secondEventId, thirdEventRelatesToEventId); + } + else + { + [expectation fulfill]; + } + }]; + + // Send first message + [room sendTextMessage:firstMessage formattedText:firstFormattedMessage localEcho:nil success:^(NSString *eventId) { + NSLog(@"Send first message with success"); + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + @end #pragma clang diagnostic pop diff --git a/MatrixSDKTests/MXSelfSignedHomeserverTests.m b/MatrixSDKTests/MXSelfSignedHomeserverTests.m index 63bb7fb108..0a929cf943 100644 --- a/MatrixSDKTests/MXSelfSignedHomeserverTests.m +++ b/MatrixSDKTests/MXSelfSignedHomeserverTests.m @@ -126,12 +126,14 @@ - (void)testRoomAndMessages XCTAssertNotNil(room); - [room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssertEqual(direction, MXTimelineDirectionForwards); - XCTAssert([event.description containsString:@"Hello"]); + XCTAssertEqual(direction, MXTimelineDirectionForwards); + XCTAssert([event.description containsString:@"Hello"]); - [expectation fulfill]; + [expectation fulfill]; + }]; }]; [room sendTextMessage:@"Hello" success:nil failure:^(NSError *error) { @@ -161,13 +163,16 @@ - (void)testE2ERoomAndMessages [room enableEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm success:^{ - [room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - XCTAssertEqual(direction, MXTimelineDirectionForwards); - XCTAssert(event.clearEvent); - XCTAssert([event.clearEvent.description containsString:@"Hello"]); + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [expectation fulfill]; + XCTAssertEqual(direction, MXTimelineDirectionForwards); + XCTAssert(event.clearEvent); + XCTAssert([event.clearEvent.description containsString:@"Hello"]); + + [expectation fulfill]; + }]; }]; [room sendTextMessage:@"Hello" success:nil failure:^(NSError *error) { @@ -196,25 +201,28 @@ - (void)testRoomAndMedia XCTAssertNotNil(room); - [room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - XCTAssertEqual(direction, MXTimelineDirectionForwards); - XCTAssertEqualObjects(event.content[@"msgtype"], kMXMessageTypeImage); + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - NSString *contentURL = event.content[@"url"]; - XCTAssert(contentURL); + XCTAssertEqual(direction, MXTimelineDirectionForwards); + XCTAssertEqualObjects(event.content[@"msgtype"], kMXMessageTypeImage); - NSString *actualURL = [mxSession.matrixRestClient urlOfContent:contentURL]; - XCTAssert(actualURL); + NSString *contentURL = event.content[@"url"]; + XCTAssert(contentURL); - // Download back the image - [MXMediaManager downloadMediaFromURL:actualURL andSaveAtFilePath:nil success:^() { + NSString *actualURL = [mxSession.matrixRestClient urlOfContent:contentURL]; + XCTAssert(actualURL); - [expectation fulfill]; + // Download back the image + [MXMediaManager downloadMediaFromURL:actualURL andSaveAtFilePath:nil success:^() { - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; }]; }]; @@ -280,30 +288,33 @@ - (void)testMediaWithNotTrustedCertificate XCTAssertNotNil(room); - [room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - XCTAssertEqual(direction, MXTimelineDirectionForwards); - XCTAssertEqualObjects(event.content[@"msgtype"], kMXMessageTypeImage); + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - NSString *contentURL = event.content[@"url"]; - XCTAssert(contentURL); + XCTAssertEqual(direction, MXTimelineDirectionForwards); + XCTAssertEqualObjects(event.content[@"msgtype"], kMXMessageTypeImage); - NSString *actualURL = [mxSession.matrixRestClient urlOfContent:contentURL]; - XCTAssert(actualURL); + NSString *contentURL = event.content[@"url"]; + XCTAssert(contentURL); - [mxSession close]; + NSString *actualURL = [mxSession.matrixRestClient urlOfContent:contentURL]; + XCTAssert(actualURL); - // Fake the case where our server was never not trusted - [[MXAllowedCertificates sharedInstance] reset]; + [mxSession close]; - // Then, try to download back the image - [MXMediaManager downloadMediaFromURL:actualURL andSaveAtFilePath:nil success:^() { + // Fake the case where our server was never not trusted + [[MXAllowedCertificates sharedInstance] reset]; - XCTFail(@"The operation must fail because the self-signed certficate was not trusted (anymore)"); - [expectation fulfill]; + // Then, try to download back the image + [MXMediaManager downloadMediaFromURL:actualURL andSaveAtFilePath:nil success:^() { - } failure:^(NSError *error) { - [expectation fulfill]; + XCTFail(@"The operation must fail because the self-signed certficate was not trusted (anymore)"); + [expectation fulfill]; + + } failure:^(NSError *error) { + [expectation fulfill]; + }]; }]; }]; diff --git a/MatrixSDKTests/MXSessionTests.m b/MatrixSDKTests/MXSessionTests.m index 958da3f96b..f5c530296c 100644 --- a/MatrixSDKTests/MXSessionTests.m +++ b/MatrixSDKTests/MXSessionTests.m @@ -82,10 +82,13 @@ - (void)testRoomWithAlias MXRoom *room = [mxSession roomWithAlias:response.roomAlias]; XCTAssertNotNil(room); - XCTAssertEqual(room.state.aliases.count, 1); - XCTAssertEqualObjects(room.state.aliases[0], response.roomAlias); - [expectation fulfill]; + [room state:^(MXRoomState *roomState) { + XCTAssertEqual(roomState.aliases.count, 1); + XCTAssertEqualObjects(roomState.aliases[0], response.roomAlias); + + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); @@ -153,7 +156,8 @@ - (void)testListenerForAllLiveEvents // Create a room with messages in parallel // Use a 0 limit to avoid to get older messages from /sync - [mxSession startWithMessagesLimit:0 onServerSyncDone:^{ + [mxSession startWithSyncFilter:[MXFilterJSONModel syncFilterWithMessageLimit:0] + onServerSyncDone:^{ [matrixSDKTestsData doMXRestClientTestWithBobAndARoomWithMessages:nil readyToTest:^(MXRestClient *bobRestClient, NSString *roomId, XCTestExpectation *expectation2) { @@ -289,9 +293,11 @@ - (void)testClose MXRoom *room = [mxSession roomWithRoomId:roomId]; XCTAssert(room); - [room.liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTFail(@"We should not receive events after closing the session. Received: %@", event); - [expectation fulfill]; + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + XCTFail(@"We should not receive events after closing the session. Received: %@", event); + [expectation fulfill]; + }]; }]; MXUser *bob = [mxSession userWithUserId:bobRestClient.credentials.userId]; @@ -397,9 +403,11 @@ - (void)testPauseResume }]; MXRoom *room = [mxSession roomWithRoomId:roomId]; - [room.liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - eventCount++; - XCTAssertFalse(paused, @"We should not receive events when paused. Received: %@", event); + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + eventCount++; + XCTAssertFalse(paused, @"We should not receive events when paused. Received: %@", event); + }]; }]; MXUser *bob = [mxSession userWithUserId:bobRestClient.credentials.userId]; @@ -466,9 +474,11 @@ - (void)testPauseResumeOnNothingNew }]; MXRoom *room = [mxSession roomWithRoomId:roomId]; - [room.liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - eventCount++; - XCTAssertFalse(paused, @"We should not receive events when paused. Received: %@", event); + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + eventCount++; + XCTAssertFalse(paused, @"We should not receive events when paused. Received: %@", event); + }]; }]; MXUser *bob = [mxSession userWithUserId:bobRestClient.credentials.userId]; @@ -575,10 +585,10 @@ - (void)testCreateRoom XCTAssertNotNil(room); - BOOL isSync = (room.state.membership != MXMembershipInvite && room.state.membership != MXMembershipUnknown); + BOOL isSync = (room.summary.membership != MXMembershipInvite && room.summary.membership != MXMembershipUnknown); XCTAssertTrue(isSync, @"The callback must be called once the room has been initialSynced"); - XCTAssertEqual(1, room.state.members.count, @"Bob must be the only one. members: %@", room.state.members); + XCTAssertEqual(1, room.summary.membersCount.members, @"Bob must be the only one"); [expectation fulfill]; @@ -626,7 +636,7 @@ - (void)testCreateRoomWithInvite XCTAssertNotNil(room); - BOOL isSync = (room.state.membership != MXMembershipInvite && room.state.membership != MXMembershipUnknown); + BOOL isSync = (room.summary.membership != MXMembershipInvite && room.summary.membership != MXMembershipUnknown); XCTAssertTrue(isSync, @"The callback must be called once the room has been initialSynced"); [mxSession.matrixRestClient membersOfRoom:room.roomId success:^(NSArray *roomMemberEvents) { @@ -678,7 +688,7 @@ - (void)testCreateDirectRoom XCTAssertNotNil(room); - BOOL isSync = (room.state.membership != MXMembershipInvite && room.state.membership != MXMembershipUnknown); + BOOL isSync = (room.summary.membership != MXMembershipInvite && room.summary.membership != MXMembershipUnknown); XCTAssertTrue(isSync, @"The callback must be called once the room has been initialSynced"); [mxSession.matrixRestClient membersOfRoom:room.roomId success:^(NSArray *roomMemberEvents) { @@ -701,19 +711,23 @@ - (void)testCreateDirectRoom XCTAssertTrue(succeed); // Force sync to get direct rooms list - [mxSession startWithMessagesLimit:0 onServerSyncDone:^{ + [mxSession startWithSyncFilter:[MXFilterJSONModel syncFilterWithMessageLimit:0] + onServerSyncDone:^{ XCTAssertTrue(room.isDirect); - - // Check whether both members have the same power level (trusted_private_chat preset) - MXRoomPowerLevels *roomPowerLevels = room.state.powerLevels; - - XCTAssertNotNil(roomPowerLevels); - NSUInteger powerlLevel1 = [roomPowerLevels powerLevelOfUserWithUserID:mxSession.myUser.userId]; - NSUInteger powerlLevel2 = [roomPowerLevels powerLevelOfUserWithUserID:matrixSDKTestsData.aliceCredentials.userId]; - XCTAssertEqual(powerlLevel1, powerlLevel2, @"The members must have the same power level"); - - [expectation fulfill]; + + [room state:^(MXRoomState *roomState) { + + // Check whether both members have the same power level (trusted_private_chat preset) + MXRoomPowerLevels *roomPowerLevels = roomState.powerLevels; + + XCTAssertNotNil(roomPowerLevels); + NSUInteger powerlLevel1 = [roomPowerLevels powerLevelOfUserWithUserID:mxSession.myUser.userId]; + NSUInteger powerlLevel2 = [roomPowerLevels powerLevelOfUserWithUserID:matrixSDKTestsData.aliceCredentials.userId]; + XCTAssertEqual(powerlLevel1, powerlLevel2, @"The members must have the same power level"); + + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"Cannot sync direct rooms - error: %@", error); @@ -740,21 +754,30 @@ - (void)testPrivateDirectRoomWithUserId [matrixSDKTestsData doMXSessionTestWithBobAndAliceInARoom:self readyToTest:^(MXSession *bobSession, MXRestClient *aliceRestClient, NSString *roomId, XCTestExpectation *expectation) { mxSession = bobSession; - - MXRoom *mxRoom1 = [mxSession directJoinedRoomWithUserId:aliceRestClient.credentials.userId]; - XCTAssertEqualObjects(mxRoom1.state.roomId, roomId, @"We should retrieve the last created room"); - - [mxSession leaveRoom:roomId success:^{ - MXRoom *mxRoom2 = [mxSession directJoinedRoomWithUserId:aliceRestClient.credentials.userId]; - if (mxRoom2) { - XCTAssertNotEqualObjects(mxRoom2.state.roomId, roomId, @"We should not retrieve the left room"); - } - - [expectation fulfill]; - + + MXRoom *room = [mxSession roomWithRoomId:roomId]; + [room setIsDirect:YES withUserId:aliceRestClient.credentials.userId success:^{ + + MXRoom *mxRoom1 = [mxSession directJoinedRoomWithUserId:aliceRestClient.credentials.userId]; + XCTAssertEqualObjects(mxRoom1.roomId, roomId, @"We should retrieve the last created room"); + + [mxSession leaveRoom:roomId success:^{ + MXRoom *mxRoom2 = [mxSession directJoinedRoomWithUserId:aliceRestClient.credentials.userId]; + if (mxRoom2) + { + XCTAssertNotEqualObjects(mxRoom2.roomId, roomId, @"We should not retrieve the left room"); + } + + [expectation fulfill]; + } failure:^(NSError *error) { XCTFail(@"Cannot set up intial test conditions - error: %@", error); [expectation fulfill]; + }]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; }]; }]; } @@ -873,7 +896,7 @@ - (void)testMXRoomInitialSyncNotificationOnJoiningPublicRoom MXRoom *publicRoom = (MXRoom*)note.object; XCTAssertNotNil(publicRoom); - BOOL isSync = (publicRoom.state.membership != MXMembershipInvite && publicRoom.state.membership != MXMembershipUnknown); + BOOL isSync = (publicRoom.summary.membership != MXMembershipInvite && publicRoom.summary.membership != MXMembershipUnknown); XCTAssert(isSync, @"kMXRoomInitialSyncNotification must inform when the room state is fully known"); XCTAssertEqual(mxSession, publicRoom.mxSession, @"The session of the sent MXRoom must be the right one"); @@ -936,9 +959,9 @@ - (void)doRoomByTagsOrderTest:(XCTestCase*)testCase withOrder1:(NSString*)order1 // Room ordering: a tagged room with no order value must have higher priority // than the tagged rooms with order value. - XCTAssertEqualObjects(room1.state.topic, @"1", "The order is wrong"); - XCTAssertEqualObjects(room2.state.topic, @"2", "The order is wrong"); - XCTAssertEqualObjects(room3.state.topic, @"3", "The order is wrong"); + XCTAssertEqualObjects(room1.summary.topic, @"1", "The order is wrong"); + XCTAssertEqualObjects(room2.summary.topic, @"2", "The order is wrong"); + XCTAssertEqualObjects(room3.summary.topic, @"3", "The order is wrong"); // By the way, check roomsWithTag @@ -1016,8 +1039,8 @@ - (void)testTagRoomsWithSameOrder NSArray *roomsWithTag = [mxSession roomsWithTag:tag]; // If the order is the same, the room must be sorted by their last event - XCTAssertEqualObjects(roomsWithTag[0].state.topic, @"newest"); - XCTAssertEqualObjects(roomsWithTag[1].state.topic, @"oldest"); + XCTAssertEqualObjects(roomsWithTag[0].summary.topic, @"newest"); + XCTAssertEqualObjects(roomsWithTag[1].summary.topic, @"oldest"); [expectation fulfill]; diff --git a/MatrixSDKTests/MXStoreMemoryStoreTests.m b/MatrixSDKTests/MXStoreMemoryStoreTests.m index 9685e9e105..c83cc7d527 100644 --- a/MatrixSDKTests/MXStoreMemoryStoreTests.m +++ b/MatrixSDKTests/MXStoreMemoryStoreTests.m @@ -206,49 +206,52 @@ - (void)testMXMemoryStorePaginate __block NSUInteger eventCount = 0; __block MXEvent *firstEventInTheRoom; - [room.liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - eventCount++; + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - firstEventInTheRoom = event; - }]; + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - // First make a call to paginateBackMessages that will make a request to the server - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + eventCount++; - XCTAssertEqual(firstEventInTheRoom.eventType, MXEventTypeRoomCreate, @"First event in a room is always m.room.create"); + firstEventInTheRoom = event; + }]; - [room.liveTimeline removeAllListeners]; + // First make a call to paginateBackMessages that will make a request to the server + [liveTimeline resetPagination]; + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - __block NSUInteger eventCount2 = 0; - __block MXEvent *firstEventInTheRoom2; - [room.liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + XCTAssertEqual(firstEventInTheRoom.eventType, MXEventTypeRoomCreate, @"First event in a room is always m.room.create"); - eventCount2++; + [liveTimeline removeAllListeners]; - firstEventInTheRoom2 = event; - }]; + __block NSUInteger eventCount2 = 0; + __block MXEvent *firstEventInTheRoom2; + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + eventCount2++; - XCTAssertEqual(eventCount, eventCount2); - XCTAssertEqual(firstEventInTheRoom2.eventType, MXEventTypeRoomCreate, @"First event in a room is always m.room.create"); - XCTAssertEqualObjects(firstEventInTheRoom, firstEventInTheRoom2); + firstEventInTheRoom2 = event; + }]; - [expectation fulfill]; + [liveTimeline resetPagination]; + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + + XCTAssertEqual(eventCount, eventCount2); + XCTAssertEqual(firstEventInTheRoom2.eventType, MXEventTypeRoomCreate, @"First event in a room is always m.room.create"); + XCTAssertEqualObjects(firstEventInTheRoom, firstEventInTheRoom2); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); [expectation fulfill]; }]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; }]; - }]; } @@ -260,81 +263,91 @@ - (void)testMXMemoryStorePaginateAgain __block NSInteger paginateBackMessagesCallCount = 0; __block NSMutableArray *roomEvents = [NSMutableArray array]; - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssertEqual(paginateBackMessagesCallCount, 1, @"Messages must asynchronously come"); + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - [roomEvents addObject:event]; - }]; + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:8 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - - [room.liveTimeline removeAllListeners]; - - __block NSMutableArray *room2Events = [NSMutableArray array]; - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - [room2Events addObject:event]; - - if (room2Events.count <=2) - { - XCTAssertEqual(paginateBackMessagesCallCount, 1, @"Messages for 'paginate:2 direction:MXTimelineDirectionBackwards' must synchronously come"); - } - else if (room2Events.count <=7) - { - XCTAssertEqual(paginateBackMessagesCallCount, 1, @"Messages for 'paginate:5 direction:MXTimelineDirectionBackwards' must synchronously come"); - } - else if (room2Events.count <=8) - { - XCTAssertEqual(paginateBackMessagesCallCount, 1, @"The first messages for 'paginate:100 direction:MXTimelineDirectionBackwards' must synchronously come"); - } - else - { - XCTAssertEqual(paginateBackMessagesCallCount, 4, @"Other Messages for 'paginate:100 direction:MXTimelineDirectionBackwards' must ssynchronously come"); - } + XCTAssertEqual(paginateBackMessagesCallCount, 1, @"Messages must asynchronously come"); + + [roomEvents addObject:event]; }]; - XCTAssertTrue([room.liveTimeline canPaginate:MXTimelineDirectionBackwards], @"There is still at least one event to retrieve from the server"); + [liveTimeline resetPagination]; + [liveTimeline paginate:8 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + + [liveTimeline removeAllListeners]; + + __block NSMutableArray *room2Events = [NSMutableArray array]; + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + [room2Events addObject:event]; + + if (room2Events.count <=2) + { + XCTAssertEqual(paginateBackMessagesCallCount, 1, @"Messages for 'paginate:2 direction:MXTimelineDirectionBackwards' must synchronously come"); + } + else if (room2Events.count <=7) + { + XCTAssertEqual(paginateBackMessagesCallCount, 1, @"Messages for 'paginate:5 direction:MXTimelineDirectionBackwards' must synchronously come"); + } + else if (room2Events.count <=8) + { + XCTAssertEqual(paginateBackMessagesCallCount, 1, @"The first messages for 'paginate:100 direction:MXTimelineDirectionBackwards' must synchronously come"); + } + else + { + XCTAssertEqual(paginateBackMessagesCallCount, 4, @"Other Messages for 'paginate:100 direction:MXTimelineDirectionBackwards' must ssynchronously come"); + } + }]; - // The several paginations - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:2 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + XCTAssertTrue([liveTimeline canPaginate:MXTimelineDirectionBackwards], @"There is still at least one event to retrieve from the server"); - [room.liveTimeline paginate:5 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + // The several paginations + [liveTimeline resetPagination]; + [liveTimeline paginate:2 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + [liveTimeline paginate:5 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - // Now, compare the result with the reference - XCTAssertEqual(roomEvents.count, 8); - XCTAssertGreaterThan(room2Events.count, roomEvents.count); + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - // Compare events one by one - for (NSUInteger i = 0; i < roomEvents.count; i++) - { - MXEvent *event = roomEvents[i]; - MXEvent *event2 = room2Events[i]; + // Now, compare the result with the reference + XCTAssertEqual(roomEvents.count, 8); + XCTAssertGreaterThan(room2Events.count, roomEvents.count); - XCTAssertTrue([event2.eventId isEqualToString:event.eventId], @"Events mismatch: %@ - %@", event, event2); - } + // Compare events one by one + for (NSUInteger i = 0; i < roomEvents.count; i++) + { + MXEvent *event = roomEvents[i]; + MXEvent *event2 = room2Events[i]; - // Do one more round trip so that SDK detect the limit - [room.liveTimeline paginate:1 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + XCTAssertTrue([event2.eventId isEqualToString:event.eventId], @"Events mismatch: %@ - %@", event, event2); + } - XCTAssertEqual(roomEvents.count, 8, @"We should have not received more events"); + // Do one more round trip so that SDK detect the limit + [liveTimeline paginate:1 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - XCTAssertFalse([room.liveTimeline canPaginate:MXTimelineDirectionBackwards], @"We reach the beginning of the history"); + XCTAssertEqual(roomEvents.count, 8, @"We should have not received more events"); - [room.liveTimeline resetPagination]; - XCTAssertTrue([room.liveTimeline canPaginate:MXTimelineDirectionBackwards], @"We must be able to paginate again"); + XCTAssertFalse([liveTimeline canPaginate:MXTimelineDirectionBackwards], @"We reach the beginning of the history"); - [expectation fulfill]; + [liveTimeline resetPagination]; + XCTAssertTrue([liveTimeline canPaginate:MXTimelineDirectionBackwards], @"We must be able to paginate again"); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); [expectation fulfill]; }]; + paginateBackMessagesCallCount++; + } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); [expectation fulfill]; @@ -353,13 +366,8 @@ - (void)testMXMemoryStorePaginateAgain XCTFail(@"The request should not fail - NSError: %@", error); [expectation fulfill]; }]; - - paginateBackMessagesCallCount++; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; }]; + paginateBackMessagesCallCount++; diff --git a/MatrixSDKTests/MXStoreTests.m b/MatrixSDKTests/MXStoreTests.m index 1357819089..aead95b690 100644 --- a/MatrixSDKTests/MXStoreTests.m +++ b/MatrixSDKTests/MXStoreTests.m @@ -167,7 +167,7 @@ - (void)doTestWithStore:(id)store [mxSession setStore:store success:^{ - [mxSession startWithMessagesLimit:messagesLimit onServerSyncDone:^{ + [mxSession startWithSyncFilter:[MXFilterJSONModel syncFilterWithMessageLimit:messagesLimit] onServerSyncDone:^{ MXRoom *room = [mxSession roomWithRoomId:roomId]; readyToTest(room); @@ -271,21 +271,26 @@ - (void)checkPaginateBack:(MXRoom*)room ]; __block NSUInteger eventCount = 0; - [room.liveTimeline listenToEventsOfTypes:eventsFilterForMessages onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - eventCount++; - }]; + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:5 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + [liveTimeline listenToEventsOfTypes:eventsFilterForMessages onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssertEqual(eventCount, 5, @"We should get as many messages as requested"); + eventCount++; + }]; - [expectation fulfill]; + [liveTimeline resetPagination]; + [liveTimeline paginate:5 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + + XCTAssertEqual(eventCount, 5, @"We should get as many messages as requested"); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; }]; } @@ -299,26 +304,30 @@ - (void)checkPaginateBackFilter:(MXRoom*)room ]; __block NSUInteger eventCount = 0; - [room.liveTimeline listenToEventsOfTypes:eventsFilterForMessages onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - eventCount++; + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - // Only events with a type declared in `eventsFilterForMessages` - // must appear in messages - XCTAssertNotEqual([eventsFilterForMessages indexOfObject:event.type], NSNotFound, "Event of this type must not be in messages. Event: %@", event); + [liveTimeline listenToEventsOfTypes:eventsFilterForMessages onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - }]; + eventCount++; - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + // Only events with a type declared in `eventsFilterForMessages` + // must appear in messages + XCTAssertNotEqual([eventsFilterForMessages indexOfObject:event.type], NSNotFound, "Event of this type must not be in messages. Event: %@", event); - XCTAssert(eventCount, "We should have received events in registerEventListenerForTypes"); + }]; - [expectation fulfill]; + [liveTimeline resetPagination]; + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + XCTAssert(eventCount, "We should have received events in registerEventListenerForTypes"); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; }]; } @@ -332,25 +341,29 @@ - (void)checkPaginateBackOrder:(MXRoom*)room ]; __block uint64_t prev_ts = -1; - [room.liveTimeline listenToEventsOfTypes:eventsFilterForMessages onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - XCTAssert(event.originServerTs, @"The event should have an attempt: %@", event); + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - XCTAssertLessThanOrEqual(event.originServerTs, prev_ts, @"Events in messages must be listed one by one in antichronological order"); - prev_ts = event.originServerTs; + [liveTimeline listenToEventsOfTypes:eventsFilterForMessages onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - }]; + XCTAssert(event.originServerTs, @"The event should have an attempt: %@", event); - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + XCTAssertLessThanOrEqual(event.originServerTs, prev_ts, @"Events in messages must be listed one by one in antichronological order"); + prev_ts = event.originServerTs; - XCTAssertNotEqual(prev_ts, -1, "We should have received events in registerEventListenerForTypes"); + }]; - [expectation fulfill]; + [liveTimeline resetPagination]; + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + XCTAssertNotEqual(prev_ts, -1, "We should have received events in registerEventListenerForTypes"); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; }]; } @@ -358,25 +371,28 @@ - (void)checkPaginateBackDuplicates:(MXRoom*)room { __block NSUInteger eventCount = 0; __block NSMutableArray *events = [NSMutableArray array]; - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - eventCount++; + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [events addObject:event]; - }]; + eventCount++; - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + [events addObject:event]; + }]; - XCTAssert(eventCount, "We should have received events in registerEventListenerForTypes"); + [liveTimeline resetPagination]; + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - [self assertNoDuplicate:events text:@"events got one by one with paginateBackMessages"]; + XCTAssert(eventCount, "We should have received events in registerEventListenerForTypes"); - [expectation fulfill]; + [self assertNoDuplicate:events text:@"events got one by one with paginateBackMessages"]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; }]; } @@ -384,77 +400,86 @@ - (void)checkPaginateBackDuplicates:(MXRoom*)room - (void)checkSeveralPaginateBacks:(MXRoom*)room { __block NSMutableArray *roomEvents = [NSMutableArray array]; - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - [roomEvents addObject:event]; - }]; + [roomEvents addObject:event]; + }]; - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + [liveTimeline resetPagination]; + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - // Use another MXRoom instance to do pagination in several times - MXRoom *room2 = [[MXRoom alloc] initWithRoomId:room.state.roomId andMatrixSession:mxSession]; + // Use another MXRoom instance to do pagination in several times + MXRoom *room2 = [[MXRoom alloc] initWithRoomId:room.roomId andMatrixSession:mxSession]; - __block NSMutableArray *room2Events = [NSMutableArray array]; - [room2.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + __block NSMutableArray *room2Events = [NSMutableArray array]; - [room2Events addObject:event]; - }]; + [room2 liveTimeline:^(MXEventTimeline *room2LiveTimeline) { - // The several paginations - [room2.liveTimeline resetPagination]; + [room2LiveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - if (mxSession.store.isPermanent) - { - XCTAssertGreaterThanOrEqual(room2.liveTimeline.remainingMessagesForBackPaginationInStore, 7); - } + [room2Events addObject:event]; + }]; - [room2.liveTimeline paginate:2 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + // The several paginations + [room2LiveTimeline resetPagination]; - if (mxSession.store.isPermanent) - { - XCTAssertGreaterThanOrEqual(room2.liveTimeline.remainingMessagesForBackPaginationInStore, 5); - } + if (mxSession.store.isPermanent) + { + XCTAssertGreaterThanOrEqual(room2LiveTimeline.remainingMessagesForBackPaginationInStore, 7); + } - [room2.liveTimeline paginate:5 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + [room2LiveTimeline paginate:2 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - [room2.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + if (mxSession.store.isPermanent) + { + XCTAssertGreaterThanOrEqual(room2LiveTimeline.remainingMessagesForBackPaginationInStore, 5); + } - [self assertNoDuplicate:room2Events text:@"events got one by one with testSeveralPaginateBacks"]; + [room2LiveTimeline paginate:5 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - // Now, compare the result with the reference - XCTAssertEqual(roomEvents.count, room2Events.count); + // Log room2 to keep a ref on it up to here + NSLog(@"%@", room2); - if (roomEvents.count == room2Events.count) - { - // Compare events one by one - for (NSUInteger i = 0; i < room2Events.count; i++) - { - MXEvent *event = roomEvents[i]; - MXEvent *event2 = room2Events[i]; + [room2LiveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - XCTAssertTrue([event2.eventId isEqualToString:event.eventId], @"Events mismatch: %@ - %@", event, event2); - } - } + [self assertNoDuplicate:room2Events text:@"events got one by one with testSeveralPaginateBacks"]; - [expectation fulfill]; + // Now, compare the result with the reference + XCTAssertEqual(roomEvents.count, room2Events.count); + + if (roomEvents.count == room2Events.count) + { + // Compare events one by one + for (NSUInteger i = 0; i < room2Events.count; i++) + { + MXEvent *event = roomEvents[i]; + MXEvent *event2 = room2Events[i]; + + XCTAssertTrue([event2.eventId isEqualToString:event.eventId], @"Events mismatch: %@ - %@", event, event2); + } + } + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); [expectation fulfill]; }]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; }]; + } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); [expectation fulfill]; }]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; }]; } @@ -463,148 +488,164 @@ - (void)checkPaginateWithLiveEvents:(MXRoom*)room __block NSMutableArray *roomEvents = [NSMutableArray array]; // Use another MXRoom instance to paginate while receiving live events - MXRoom *room2 = [[MXRoom alloc] initWithRoomId:room.state.roomId andMatrixSession:mxSession]; + MXRoom *room2 = [[MXRoom alloc] initWithRoomId:room.roomId andMatrixSession:mxSession]; __block NSMutableArray *room2Events = [NSMutableArray array]; - [room2.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - if (MXTimelineDirectionForwards != direction) - { - [room2Events addObject:event]; - } - }]; + [room2 liveTimeline:^(MXEventTimeline *room2liveTimeline) { - __block NSUInteger liveEvents = 0; - [room.liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room2liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - if (MXTimelineDirectionForwards == direction) - { - // Do some paginations after receiving live events - liveEvents++; - if (1 == liveEvents) + if (MXTimelineDirectionForwards != direction) { - if (mxSession.store.isPermanent) - { - XCTAssertGreaterThanOrEqual(room2.liveTimeline.remainingMessagesForBackPaginationInStore, 7); - } + [room2Events addObject:event]; + } + }]; - [room2.liveTimeline paginate:2 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + __block NSUInteger liveEvents = 0; - if (mxSession.store.isPermanent) + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + + [liveTimeline listenToEventsOfTypes:nil onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + + if (MXTimelineDirectionForwards == direction) + { + // Do some paginations after receiving live events + liveEvents++; + if (1 == liveEvents) { - XCTAssertGreaterThanOrEqual(room2.liveTimeline.remainingMessagesForBackPaginationInStore, 5); - } + if (mxSession.store.isPermanent) + { + XCTAssertGreaterThanOrEqual(room2liveTimeline.remainingMessagesForBackPaginationInStore, 7); + } - // Try with 2 more live events - [room sendTextMessage:@"How is the pagination #2?" success:nil failure:nil]; - [room sendTextMessage:@"How is the pagination #3?" success:nil failure:nil]; + [room2liveTimeline paginate:2 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - } - else if (3 == liveEvents) + if (mxSession.store.isPermanent) + { + XCTAssertGreaterThanOrEqual(room2liveTimeline.remainingMessagesForBackPaginationInStore, 5); + } - [room2.liveTimeline paginate:5 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + // Try with 2 more live events + [room sendTextMessage:@"How is the pagination #2?" success:nil failure:nil]; + [room sendTextMessage:@"How is the pagination #3?" success:nil failure:nil]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } + else if (3 == liveEvents) + { + [room2liveTimeline paginate:5 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - [room2.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + // Log room2 to keep a ref on it up to here + NSLog(@"%@", room2); + + [room2liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - [self assertNoDuplicate:room2Events text:@"events got one by one with testSeveralPaginateBacks"]; + [self assertNoDuplicate:room2Events text:@"events got one by one with testSeveralPaginateBacks"]; - // Now, compare the result with the reference - XCTAssertEqual(roomEvents.count, room2Events.count); + // Now, compare the result with the reference + XCTAssertEqual(roomEvents.count, room2Events.count); - // Compare events one by one - for (NSUInteger i = 0; i < room2Events.count; i++) - { - MXEvent *event = roomEvents[i]; - MXEvent *event2 = room2Events[i]; + // Compare events one by one + for (NSUInteger i = 0; i < room2Events.count; i++) + { + MXEvent *event = roomEvents[i]; + MXEvent *event2 = room2Events[i]; - XCTAssertTrue([event2.eventId isEqualToString:event.eventId], @"Events mismatch: %@ - %@", event, event2); - } + XCTAssertTrue([event2.eventId isEqualToString:event.eventId], @"Events mismatch: %@ - %@", event, event2); + } - [expectation fulfill]; + [expectation fulfill]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } + } + else + { + [roomEvents addObject:event]; + } }]; - } - else - { - [roomEvents addObject:event]; - } - }]; - // Take a snapshot of all room history - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + // Take a snapshot of all room history + [liveTimeline resetPagination]; + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - // Messages are now in the cache - // Start checking pagination from the cache - [room2.liveTimeline resetPagination]; + // Messages are now in the cache + // Start checking pagination from the cache + [room2liveTimeline resetPagination]; - [room sendTextMessage:@"How is the pagination #1?" success:nil failure:nil]; + [room sendTextMessage:@"How is the pagination #1?" success:nil failure:nil]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; }]; } - (void)checkCanPaginateFromHomeServer:(MXRoom*)room { - [room.liveTimeline resetPagination]; - XCTAssertTrue([room.liveTimeline canPaginate:MXTimelineDirectionBackwards], @"We can always paginate at the beginning"); + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline resetPagination]; + XCTAssertTrue([liveTimeline canPaginate:MXTimelineDirectionBackwards], @"We can always paginate at the beginning"); - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - // Due to SPEC-319, we need to paginate twice to be sure to hit the limit - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + // Due to SPEC-319, we need to paginate twice to be sure to hit the limit + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - XCTAssertFalse([room.liveTimeline canPaginate:MXTimelineDirectionBackwards], @"We must have reached the end of the pagination"); + XCTAssertFalse([liveTimeline canPaginate:MXTimelineDirectionBackwards], @"We must have reached the end of the pagination"); - [expectation fulfill]; + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); [expectation fulfill]; }]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; }]; } - (void)checkCanPaginateFromMXStore:(MXRoom*)room { - [room.liveTimeline resetPagination]; - XCTAssertTrue([room.liveTimeline canPaginate:MXTimelineDirectionBackwards], @"We can always paginate at the beginning"); + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + [liveTimeline resetPagination]; + XCTAssertTrue([liveTimeline canPaginate:MXTimelineDirectionBackwards], @"We can always paginate at the beginning"); - // Do one more round trip so that SDK detect the limit - [room.liveTimeline paginate:1 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - XCTAssertFalse([room.liveTimeline canPaginate:MXTimelineDirectionBackwards], @"We must have reached the end of the pagination"); + // Do one more round trip so that SDK detect the limit + [liveTimeline paginate:1 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - [expectation fulfill]; + XCTAssertFalse([liveTimeline canPaginate:MXTimelineDirectionBackwards], @"We must have reached the end of the pagination"); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"The request should not fail - NSError: %@", error); [expectation fulfill]; }]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; }]; } @@ -613,20 +654,23 @@ - (void)checkLastMessageAfterPaginate:(MXRoom*)room MXEvent *lastMessage = room.summary.lastMessageEvent; XCTAssertEqual(lastMessage.eventType, MXEventTypeRoomMessage); - [room.liveTimeline resetPagination]; - MXEvent *lastMessage2 = room.summary.lastMessageEvent; - XCTAssertEqualObjects(lastMessage2.eventId, lastMessage.eventId, @"The last message should stay the same"); + [room liveTimeline:^(MXEventTimeline *liveTimeline) { - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + [liveTimeline resetPagination]; + MXEvent *lastMessage2 = room.summary.lastMessageEvent; + XCTAssertEqualObjects(lastMessage2.eventId, lastMessage.eventId, @"The last message should stay the same"); - MXEvent *lastMessage3 = room.summary.lastMessageEvent; - XCTAssertEqualObjects(lastMessage3.eventId, lastMessage.eventId, @"The last message should stay the same"); + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - [expectation fulfill]; + MXEvent *lastMessage3 = room.summary.lastMessageEvent; + XCTAssertEqualObjects(lastMessage3.eventId, lastMessage.eventId, @"The last message should stay the same"); - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; }]; } @@ -635,21 +679,23 @@ - (void)checkLastMessageProfileChange:(MXRoom*)room MXEvent *lastMessage = room.summary.lastMessageEvent; XCTAssertEqual(lastMessage.eventType, MXEventTypeRoomMessage); - [room.liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - // The room summary is handled afterwards - if (!observer) - { - observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomSummaryDidChangeNotification object:room.summary queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + // The room summary is handled afterwards + if (!observer) + { + observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomSummaryDidChangeNotification object:room.summary queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { - MXEvent *lastMessage2 = room.summary.lastMessageEvent; - XCTAssertNotNil(lastMessage2); - XCTAssertEqual(lastMessage2.eventType, MXEventTypeRoomMember); - XCTAssertNotEqualObjects(lastMessage2.eventId, lastMessage.eventId); + MXEvent *lastMessage2 = room.summary.lastMessageEvent; + XCTAssertNotNil(lastMessage2); + XCTAssertEqual(lastMessage2.eventType, MXEventTypeRoomMember); + XCTAssertNotEqualObjects(lastMessage2.eventId, lastMessage.eventId); - [expectation fulfill]; - }]; - } + [expectation fulfill]; + }]; + } + }]; }]; [room.mxSession.myUser setDisplayName:@"Toto" success:nil failure:^(NSError *error) { @@ -667,21 +713,23 @@ - (void)checkLastMessageIgnoreProfileChange:(MXRoom*)room MXRoomSummaryUpdater *updater = [MXRoomSummaryUpdater roomSummaryUpdaterForSession:room.mxSession]; updater.ignoreMemberProfileChanges = YES; - [room.liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMember] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - MXEvent *lastMessage2 = room.summary.lastMessageEvent; - XCTAssertNotNil(lastMessage2); - XCTAssertEqual(lastMessage2.eventType, MXEventTypeRoomMessage); - XCTAssertEqualObjects(lastMessage2.eventId, lastMessage.eventId); + MXEvent *lastMessage2 = room.summary.lastMessageEvent; + XCTAssertNotNil(lastMessage2); + XCTAssertEqual(lastMessage2.eventType, MXEventTypeRoomMessage); + XCTAssertEqualObjects(lastMessage2.eventId, lastMessage.eventId); - // The room.summary.lastMessageEvent must no be updated in this case - [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomSummaryDidChangeNotification object:room.summary queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + // The room.summary.lastMessageEvent must no be updated in this case + [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomSummaryDidChangeNotification object:room.summary queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { - XCTFail(@"The room.summary.lastMessageEvent must no be updated in this case"); - - }]; + XCTFail(@"The room.summary.lastMessageEvent must no be updated in this case"); - [expectation fulfill]; + }]; + + [expectation fulfill]; + }]; }]; [room.mxSession.myUser setDisplayName:@"Toto" success:nil failure:^(NSError *error) { @@ -694,9 +742,9 @@ - (void)checkPaginateWhenJoiningAgainAfterLeft:(MXRoom*)room { [matrixSDKTestsData doMXRestClientTestWithAlice:nil readyToTest:^(MXRestClient *aliceRestClient, XCTestExpectation *expectation2) { - [mxSession.matrixRestClient inviteUser:aliceRestClient.credentials.userId toRoom:room.state.roomId success:^{ + [mxSession.matrixRestClient inviteUser:aliceRestClient.credentials.userId toRoom:room.roomId success:^{ - NSString *roomId = room.state.roomId; + NSString *roomId = room.roomId; __block NSString *aliceTextEventId; @@ -711,7 +759,7 @@ - (void)checkPaginateWhenJoiningAgainAfterLeft:(MXRoom*)room XCTAssertNotNil(room2); - if (direction == MXTimelineDirectionForwards && MXMembershipInvite == room2.state.membership && !joinedRequestMade) + if (direction == MXTimelineDirectionForwards && MXMembershipInvite == room2.summary.membership && !joinedRequestMade) { // Join the room on the invitation and check we can paginate all expected text messages // By default the last Alice's message (sent while Bob is not in the room) must be visible. @@ -719,33 +767,35 @@ - (void)checkPaginateWhenJoiningAgainAfterLeft:(MXRoom*)room [room2 join:^{ NSMutableArray *events = [NSMutableArray array]; - [room2.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room2 liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - if (direction == MXTimelineDirectionBackwards) - { - if (0 == events.count) + if (direction == MXTimelineDirectionBackwards) { - // The most recent message must be "Hi bob" sent by Alice - XCTAssertEqualObjects(aliceTextEventId, event.eventId); - } + if (0 == events.count) + { + // The most recent message must be "Hi bob" sent by Alice + XCTAssertEqualObjects(aliceTextEventId, event.eventId); + } - [events addObject:event]; - } + [events addObject:event]; + } - }]; + }]; - [room2.liveTimeline resetPagination]; - [room2.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + [liveTimeline resetPagination]; + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - XCTAssertEqual(events.count, 6, "The room should contain only 6 messages (the last message sent while the user is not in the room must be visible)"); + XCTAssertEqual(events.count, 6, "The room should contain only 6 messages (the last message sent while the user is not in the room must be visible)"); - [mxSession close]; - [expectation fulfill]; + [mxSession close]; + [expectation fulfill]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [mxSession close]; - [expectation fulfill]; + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [mxSession close]; + [expectation fulfill]; + }]; }]; } failure:^(NSError *error) { @@ -800,41 +850,47 @@ - (void)checkPaginateWhenJoiningAgainAfterLeft:(MXRoom*)room - (void)checkPaginateWhenReachingTheExactBeginningOfTheRoom:(MXRoom*)room { __block NSUInteger eventCount = 0; - [room.liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - if (direction == MXTimelineDirectionBackwards) - { - eventCount++; - } - }]; + if (direction == MXTimelineDirectionBackwards) + { + eventCount++; + } + }]; - // First count how many messages to retrieve - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { + // First count how many messages to retrieve + [liveTimeline resetPagination]; + [liveTimeline paginate:100 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^() { - // Paginate for the exact number of events in the room - NSUInteger pagEnd = eventCount; - eventCount = 0; - [mxSession.store deleteRoom:room.state.roomId]; - [room.liveTimeline resetPagination]; + // Paginate for the exact number of events in the room + NSUInteger pagEnd = eventCount; + eventCount = 0; + [mxSession.store deleteRoom:room.roomId]; + [liveTimeline resetPagination]; - [room.liveTimeline paginate:pagEnd direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + [liveTimeline paginate:pagEnd direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - XCTAssertEqual(eventCount, pagEnd, @"We should get as many messages as requested"); + XCTAssertEqual(eventCount, pagEnd, @"We should get as many messages as requested"); - XCTAssert([room.liveTimeline canPaginate:MXTimelineDirectionBackwards], @"At this point the SDK cannot know it reaches the beginning of the history"); + XCTAssert([liveTimeline canPaginate:MXTimelineDirectionBackwards], @"At this point the SDK cannot know it reaches the beginning of the history"); - // Try to load more messages - eventCount = 0; - [room.liveTimeline paginate:1 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + // Try to load more messages + eventCount = 0; + [liveTimeline paginate:1 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - XCTAssertEqual(eventCount, 0, @"There must be no more event"); - XCTAssertFalse([room.liveTimeline canPaginate:MXTimelineDirectionBackwards], @"SDK must now indicate there is no more event to paginate"); + XCTAssertEqual(eventCount, 0, @"There must be no more event"); + XCTAssertFalse([liveTimeline canPaginate:MXTimelineDirectionBackwards], @"SDK must now indicate there is no more event to paginate"); - [expectation fulfill]; + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - see SYN-162 - NSError: %@", error); + [expectation fulfill]; + }]; } failure:^(NSError *error) { - XCTFail(@"The request should not fail - see SYN-162 - NSError: %@", error); + XCTFail(@"Cannot set up intial test conditions - error: %@", error); [expectation fulfill]; }]; @@ -842,10 +898,6 @@ - (void)checkPaginateWhenReachingTheExactBeginningOfTheRoom:(MXRoom*)room XCTFail(@"Cannot set up intial test conditions - error: %@", error); [expectation fulfill]; }]; - - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; }]; } @@ -853,44 +905,46 @@ - (void)checkRedactEvent:(MXRoom*)room { __block NSString *messageEventId; - [room.liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - if (MXEventTypeRoomMessage == event.eventType) - { - // Manage the case where message comes down the stream before the call of the success - // callback of [room sendTextMessage:...] - if (nil == messageEventId) + if (MXEventTypeRoomMessage == event.eventType) { - messageEventId = event.eventId; - } + // Manage the case where message comes down the stream before the call of the success + // callback of [room sendTextMessage:...] + if (nil == messageEventId) + { + messageEventId = event.eventId; + } - MXEvent *notYetRedactedEvent = [mxSession.store eventWithEventId:messageEventId inRoom:room.state.roomId]; + MXEvent *notYetRedactedEvent = [mxSession.store eventWithEventId:messageEventId inRoom:room.roomId]; - XCTAssertGreaterThan(notYetRedactedEvent.content.count, 0); - XCTAssertNil(notYetRedactedEvent.redacts); - XCTAssertNil(notYetRedactedEvent.redactedBecause); + XCTAssertGreaterThan(notYetRedactedEvent.content.count, 0); + XCTAssertNil(notYetRedactedEvent.redacts); + XCTAssertNil(notYetRedactedEvent.redactedBecause); - // Redact this event - [room redactEvent:messageEventId reason:@"No reason" success:^{ + // Redact this event + [room redactEvent:messageEventId reason:@"No reason" success:^{ - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - } - else if (MXEventTypeRoomRedaction == event.eventType) - { - MXEvent *redactedEvent = [mxSession.store eventWithEventId:messageEventId inRoom:room.state.roomId]; + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; + } + else if (MXEventTypeRoomRedaction == event.eventType) + { + MXEvent *redactedEvent = [mxSession.store eventWithEventId:messageEventId inRoom:room.roomId]; - XCTAssertEqual(redactedEvent.content.count, 0, @"Redacted event content must be now empty"); - XCTAssertEqualObjects(event.eventId, redactedEvent.redactedBecause[@"event_id"], @"It must contain the event that redacted it"); + XCTAssertEqual(redactedEvent.content.count, 0, @"Redacted event content must be now empty"); + XCTAssertEqualObjects(event.eventId, redactedEvent.redactedBecause[@"event_id"], @"It must contain the event that redacted it"); - // Tests more related to redaction (could be moved to a dedicated section somewhere else) - XCTAssertEqualObjects(event.redacts, messageEventId, @""); + // Tests more related to redaction (could be moved to a dedicated section somewhere else) + XCTAssertEqualObjects(event.redacts, messageEventId, @""); - [expectation fulfill]; - } + [expectation fulfill]; + } + }]; }]; [room sendTextMessage:@"This is text message" success:^(NSString *eventId) { @@ -935,7 +989,7 @@ - (void)checkUserDisplaynameAndAvatarUrl:(Class)mxStoreClass MXUser *myUser = [mxSession userWithUserId:aliceRestClient.credentials.userId]; XCTAssertEqual(myUser, mxSession.myUser); XCTAssertEqualObjects(myUser.displayname, kMXTestsAliceDisplayName); - XCTAssertEqualObjects(myUser.avatarUrl, kMXTestsAliceAvatarURL); + //XCTAssertEqualObjects(myUser.avatarUrl, kMXTestsAliceAvatarURL); // Disabled because setting avatar does not work anymore with local test homeserver [mxSession close]; mxSession = nil; @@ -947,7 +1001,7 @@ - (void)checkUserDisplaynameAndAvatarUrl:(Class)mxStoreClass MXUser *myUser = [store2 userWithUserId:aliceRestClient.credentials.userId]; XCTAssert([myUser isKindOfClass:MXMyUser.class]); XCTAssertEqualObjects(myUser.displayname, kMXTestsAliceDisplayName); - XCTAssertEqualObjects(myUser.avatarUrl, kMXTestsAliceAvatarURL); + //XCTAssertEqualObjects(myUser.avatarUrl, kMXTestsAliceAvatarURL); // Disabled because setting avatar does not work anymore with local test homeserver if ([store2 respondsToSelector:@selector(close)]) { @@ -1006,7 +1060,7 @@ - (void)checkUpdateUserDisplaynameAndAvatarUrl:(Class)mxStoreClass MXUser *myUser = [mxSession userWithUserId:aliceRestClient.credentials.userId]; XCTAssertEqual(myUser, mxSession.myUser); XCTAssertEqualObjects(myUser.displayname, kMXTestsAliceDisplayName); - XCTAssertEqualObjects(myUser.avatarUrl, kMXTestsAliceAvatarURL); + //XCTAssertEqualObjects(myUser.avatarUrl, kMXTestsAliceAvatarURL); // Disabled because setting avatar does not work anymore with local test homeserver [mxSession.myUser setDisplayName:@"Alicia" success:^{ @@ -1290,32 +1344,38 @@ - (void)checkMXRoomPaginationToken:(Class)mxStoreClass // Do a 1st [mxSession start] to fill the store mxSession = [[MXSession alloc] initWithMatrixRestClient:bobRestClient]; [mxSession setStore:store success:^{ - [mxSession startWithMessagesLimit:5 onServerSyncDone:^{ + [mxSession startWithSyncFilter:[MXFilterJSONModel syncFilterWithMessageLimit:5] onServerSyncDone:^{ MXRoom *room = [mxSession roomWithRoomId:roomId]; - [room.liveTimeline resetPagination]; - [room.liveTimeline paginate:10 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - NSString *roomPaginationToken = [store paginationTokenOfRoom:roomId]; - XCTAssert(roomPaginationToken, @"The room must have a pagination after a pagination"); + [room liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline resetPagination]; + [liveTimeline paginate:10 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ - [mxSession close]; - mxSession = nil; + NSString *roomPaginationToken = [store paginationTokenOfRoom:roomId]; + XCTAssert(roomPaginationToken, @"The room must have a pagination after a pagination"); - // Reopen a session and check roomPaginationToken - id store2 = [[mxStoreClass alloc] init]; + [mxSession close]; + mxSession = nil; + + // Reopen a session and check roomPaginationToken + id store2 = [[mxStoreClass alloc] init]; - mxSession = [[MXSession alloc] initWithMatrixRestClient:bobRestClient]; - [mxSession setStore:store2 success:^{ + mxSession = [[MXSession alloc] initWithMatrixRestClient:bobRestClient]; + [mxSession setStore:store2 success:^{ - XCTAssertEqualObjects(roomPaginationToken, [store2 paginationTokenOfRoom:roomId], @"The store must keep the pagination token"); + XCTAssertEqualObjects(roomPaginationToken, [store2 paginationTokenOfRoom:roomId], @"The store must keep the pagination token"); - [mxSession start:^{ + [mxSession start:^{ - XCTAssertEqualObjects(roomPaginationToken, [store2 paginationTokenOfRoom:roomId], @"The store must keep the pagination token even after [MXSession start]"); + XCTAssertEqualObjects(roomPaginationToken, [store2 paginationTokenOfRoom:roomId], @"The store must keep the pagination token even after [MXSession start]"); - [expectation fulfill]; + [expectation fulfill]; + } failure:^(NSError *error) { + XCTFail(@"Cannot set up intial test conditions - error: %@", error); + [expectation fulfill]; + }]; } failure:^(NSError *error) { XCTFail(@"Cannot set up intial test conditions - error: %@", error); [expectation fulfill]; @@ -1324,10 +1384,8 @@ - (void)checkMXRoomPaginationToken:(Class)mxStoreClass XCTFail(@"Cannot set up intial test conditions - error: %@", error); [expectation fulfill]; }]; - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; }]; + } failure:^(NSError *error) { XCTFail(@"Cannot set up intial test conditions - error: %@", error); [expectation fulfill]; diff --git a/MatrixSDKTests/MXUserTests.m b/MatrixSDKTests/MXUserTests.m index 1e7153cb1f..16351760e9 100644 --- a/MatrixSDKTests/MXUserTests.m +++ b/MatrixSDKTests/MXUserTests.m @@ -133,6 +133,7 @@ - (void)testOtherUserLastActiveUpdate [self doTestWithBobAndAliceActiveInARoom:^(MXRestClient *bobRestClient, MXRestClient *aliceRestClient, NSString *roomId, XCTestExpectation *expectation) { MXUser *mxAlice = [mxSession userWithUserId:aliceRestClient.credentials.userId]; + XCTAssert(mxAlice); [mxAlice listenToUserUpdate:^(MXEvent *event) { @@ -155,6 +156,7 @@ - (void)testOtherUserProfileUpdate [self doTestWithBobAndAliceActiveInARoom:^(MXRestClient *bobRestClient, MXRestClient *aliceRestClient, NSString *roomId, XCTestExpectation *expectation) { MXUser *mxAlice = [mxSession userWithUserId:aliceRestClient.credentials.userId]; + XCTAssert(mxAlice); [mxAlice listenToUserUpdate:^(MXEvent *event) { @@ -185,6 +187,7 @@ - (void)testOtherUserPresenceUpdate [self doTestWithBobAndAliceActiveInARoom:^(MXRestClient *bobRestClient, MXRestClient *aliceRestClient, NSString *roomId, XCTestExpectation *expectation) { MXUser *mxAlice = [mxSession userWithUserId:aliceRestClient.credentials.userId]; + XCTAssert(mxAlice); [mxAlice listenToUserUpdate:^(MXEvent *event) { @@ -222,7 +225,7 @@ - (void)testMyUserAvailability XCTAssertNotNil(mxSession.myUser); XCTAssertEqualObjects(mxSession.myUser.displayname, kMXTestsAliceDisplayName); - XCTAssertEqualObjects(mxSession.myUser.avatarUrl, kMXTestsAliceAvatarURL); + //XCTAssertEqualObjects(mxSession.myUser.avatarUrl, kMXTestsAliceAvatarURL); // Disabled because setting avatar does not work anymore with local test homeserver [expectation fulfill]; diff --git a/MatrixSDKTests/MatrixSDKTestsData.h b/MatrixSDKTests/MatrixSDKTestsData.h index 389b0ec9f2..469ced2104 100644 --- a/MatrixSDKTests/MatrixSDKTestsData.h +++ b/MatrixSDKTests/MatrixSDKTestsData.h @@ -117,6 +117,11 @@ FOUNDATION_EXPORT NSString * const kMXTestsAliceAvatarURL; readyToTest:(void (^)(MXSession *bobSession, MXRestClient *aliceRestClient, NSString* roomId, XCTestExpectation *expectation))readyToTest; +#pragma mark - random user +- (void)doMXSessionTestWithAUser:(XCTestCase*)testCase + readyToTest:(void (^)(MXSession *aUserSession, XCTestExpectation *expectation))readyToTest; + + #pragma mark - HTTPS mxBob - (void)getHttpsBobCredentials:(void (^)(void))success; - (void)getHttpsBobCredentials:(void (^)(void))success onUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock; diff --git a/MatrixSDKTests/MatrixSDKTestsData.m b/MatrixSDKTests/MatrixSDKTestsData.m index 174f4972d6..0748b8f78d 100644 --- a/MatrixSDKTests/MatrixSDKTestsData.m +++ b/MatrixSDKTests/MatrixSDKTestsData.m @@ -654,6 +654,46 @@ - (void)doMXSessionTestWithBobAndAliceInARoom:(XCTestCase*)testCase } +#pragma mark - random user +- (void)doMXSessionTestWithAUser:(XCTestCase*)testCase + readyToTest:(void (^)(MXSession *aUserSession, XCTestExpectation *expectation))readyToTest +{ + XCTestExpectation *expectation; + if (testCase) + { + expectation = [testCase expectationWithDescription:@"asyncTest"]; + } + + __block MXRestClient *aUserRestClient = [[MXRestClient alloc] initWithHomeServer:kMXTestsHomeServerURL + andOnUnrecognizedCertificateBlock:^BOOL(NSData *certificate) { + return YES; + }]; + [self retain:aUserRestClient]; + + // First, register a new random user + NSString *anUniqueUser = [NSString stringWithFormat:@"%@", [[NSUUID UUID] UUIDString]]; + [aUserRestClient registerWithLoginType:kMXLoginFlowTypeDummy username:anUniqueUser password:@"123456" success:^(MXCredentials *credentials) { + + aUserRestClient = [[MXRestClient alloc] initWithCredentials:credentials andOnUnrecognizedCertificateBlock:nil]; + [self retain:aUserRestClient]; + + MXSession *aUserSession = [[MXSession alloc] initWithMatrixRestClient:aUserRestClient]; + [self retain:aUserSession]; + + [aUserSession start:^{ + + readyToTest(aUserSession, expectation); + + } failure:^(NSError *error) { + NSAssert(NO, @"Cannot set up intial test conditions - error: %@", error); + }]; + + } failure:^(NSError *error) { + NSAssert(NO, @"Cannot set up intial test conditions - error: %@", error); + }]; +} + + #pragma mark - HTTPS mxBob - (void)getHttpsBobCredentials:(void (^)(void))success { diff --git a/MatrixSDKTests/MatrixSDKTestsE2EData.m b/MatrixSDKTests/MatrixSDKTestsE2EData.m index 25352f5f03..d0725f7f49 100644 --- a/MatrixSDKTests/MatrixSDKTestsE2EData.m +++ b/MatrixSDKTests/MatrixSDKTestsE2EData.m @@ -170,13 +170,16 @@ - (void)doE2ETestWithAliceAndBobInARoomWithCryptedMessages:(XCTestCase*)testCase __block NSUInteger messagesCount = 0; - [roomFromBobPOV.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - if (++messagesCount == 5) - { - readyToTest(aliceSession, bobSession, roomId, expectation); - } + [roomFromBobPOV liveTimeline:^(MXEventTimeline *liveTimeline) { + [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + if (++messagesCount == 5) + { + readyToTest(aliceSession, bobSession, roomId, expectation); + } + }]; }]; + // Send messages in expected order [roomFromAlicePOV sendTextMessage:messagesFromAlice[0] success:^(NSString *eventId) { diff --git a/Podfile b/Podfile index 22092047ce..7636b89cab 100644 --- a/Podfile +++ b/Podfile @@ -7,10 +7,10 @@ target "MatrixSDK" do pod 'AFNetworking', '~> 3.2.0' pod 'GZIP', '~> 1.2.1' -pod 'OLMKit', :inhibit_warnings => true +pod 'OLMKit', '~> 2.3.0', :inhibit_warnings => true #pod 'OLMKit', :path => '../olm/OLMKit.podspec' -pod 'Realm', '~> 3.6.0' +pod 'Realm', '~> 3.7.4' end diff --git a/Podfile.lock b/Podfile.lock index 9b957d2b7e..12f50f03f7 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -15,20 +15,20 @@ PODS: - AFNetworking/UIKit (3.2.1): - AFNetworking/NSURLSession - GZIP (1.2.1) - - OLMKit (2.2.2): - - OLMKit/olmc (= 2.2.2) - - OLMKit/olmcpp (= 2.2.2) - - OLMKit/olmc (2.2.2) - - OLMKit/olmcpp (2.2.2) - - Realm (3.6.0): - - Realm/Headers (= 3.6.0) - - Realm/Headers (3.6.0) + - OLMKit (2.3.0): + - OLMKit/olmc (= 2.3.0) + - OLMKit/olmcpp (= 2.3.0) + - OLMKit/olmc (2.3.0) + - OLMKit/olmcpp (2.3.0) + - Realm (3.7.4): + - Realm/Headers (= 3.7.4) + - Realm/Headers (3.7.4) DEPENDENCIES: - AFNetworking (~> 3.2.0) - GZIP (~> 1.2.1) - - OLMKit - - Realm (~> 3.6.0) + - OLMKit (~> 2.3.0) + - Realm (~> 3.7.4) SPEC REPOS: https://github.com/cocoapods/specs.git: @@ -40,9 +40,9 @@ SPEC REPOS: SPEC CHECKSUMS: AFNetworking: b6f891fdfaed196b46c7a83cf209e09697b94057 GZIP: 7ee835f989fb3c6ea79005fc90b8fa6af710a70d - OLMKit: b9d8c0ffee9ea8c45bc0aaa9afb47f93fba7efbd - Realm: 08b464b462d4f31bbd4ba5f5a1c8722ef0a700b7 + OLMKit: dd79cdc5fab9ec04c940a901e025195b7801f306 + Realm: a469bb59e33f9926102ccaea4349822c53b9117e -PODFILE CHECKSUM: cb6d030096e2a4d1185a7ada228e77cf95f979bf +PODFILE CHECKSUM: ac8a8af85a45d601e5a4451ccc4a997501d45364 COCOAPODS: 1.5.3 diff --git a/SwiftMatrixSDK.podspec b/SwiftMatrixSDK.podspec index 98096a25f3..c2a2f4c30c 100644 --- a/SwiftMatrixSDK.podspec +++ b/SwiftMatrixSDK.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SwiftMatrixSDK" - s.version = "0.10.12" + s.version = "0.11.0" s.summary = "The iOS SDK to build apps compatible with Matrix (https://www.matrix.org)" s.description = <<-DESC @@ -20,7 +20,7 @@ Pod::Spec.new do |s| s.ios.deployment_target = "8.0" s.osx.deployment_target = "10.10" - s.source = { :git => "https://github.com/matrix-org/matrix-ios-sdk.git", :tag => "v0.10.12" } + s.source = { :git => "https://github.com/matrix-org/matrix-ios-sdk.git", :tag => "v#{s.version}" } s.source_files = "MatrixSDK", "MatrixSDK/**/*.{h,m,swift}" s.requires_arc = true