diff --git a/CHANGES.rst b/CHANGES.rst index e9a198128d..c0d3387659 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,14 @@ +Changes in Matrix iOS SDK in 0.6.2 (2016-02-09) +=============================================== + +Improvements: + * MXRoom: Add an argument to limit the pagination to the messages from the store. + * MXRoom: Support email invitation. + +Bug fixes: + * App crashes on resume if a pause is pending. + * Account creation: reCaptcha is missing in registration fallback. + Changes in Matrix iOS SDK in 0.6.1 (2016-01-29) =============================================== diff --git a/MatrixSDK.podspec b/MatrixSDK.podspec index 0076d32ed5..369b2114ec 100644 --- a/MatrixSDK.podspec +++ b/MatrixSDK.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "MatrixSDK" - s.version = "0.6.1" + s.version = "0.6.2" s.summary = "The iOS SDK to build apps compatible with Matrix (http://www.matrix.org)" s.description = <<-DESC @@ -19,7 +19,7 @@ Pod::Spec.new do |s| s.platform = :ios, "7.0" - s.source = { :git => "https://github.com/matrix-org/matrix-ios-sdk.git", :tag => "v0.6.1" } + s.source = { :git => "https://github.com/matrix-org/matrix-ios-sdk.git", :tag => "v0.6.2" } s.source_files = "MatrixSDK", "MatrixSDK/**/*.{h,m}" s.resources = "MatrixSDK/Data/Store/MXCoreDataStore/*.xcdatamodeld" diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 01fff8b721..0cd351ceb8 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -76,6 +76,8 @@ 327E37B61A974F75007F026F /* MXLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 327E37B41A974F75007F026F /* MXLogger.h */; }; 327E37B71A974F75007F026F /* MXLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 327E37B51A974F75007F026F /* MXLogger.m */; }; 327E37B91A977810007F026F /* MXLoggerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 327E37B81A977810007F026F /* MXLoggerTests.m */; }; + 327F8DB21C6112BA00581CA3 /* MXRoomThirdPartyInvite.h in Headers */ = {isa = PBXBuildFile; fileRef = 327F8DB01C6112BA00581CA3 /* MXRoomThirdPartyInvite.h */; }; + 327F8DB31C6112BA00581CA3 /* MXRoomThirdPartyInvite.m in Sources */ = {isa = PBXBuildFile; fileRef = 327F8DB11C6112BA00581CA3 /* MXRoomThirdPartyInvite.m */; }; 3281E89E19E299C000976E1A /* MXErrorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3281E89D19E299C000976E1A /* MXErrorTests.m */; }; 3281E8A019E2CC1200976E1A /* MXHTTPClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3281E89F19E2CC1200976E1A /* MXHTTPClientTests.m */; }; 3281E8A219E2DE4300976E1A /* MXSessionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3281E8A119E2DE4300976E1A /* MXSessionTests.m */; }; @@ -219,6 +221,8 @@ 327E37B41A974F75007F026F /* MXLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXLogger.h; sourceTree = ""; }; 327E37B51A974F75007F026F /* MXLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXLogger.m; sourceTree = ""; }; 327E37B81A977810007F026F /* MXLoggerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXLoggerTests.m; sourceTree = ""; }; + 327F8DB01C6112BA00581CA3 /* MXRoomThirdPartyInvite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomThirdPartyInvite.h; sourceTree = ""; }; + 327F8DB11C6112BA00581CA3 /* MXRoomThirdPartyInvite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomThirdPartyInvite.m; sourceTree = ""; }; 3281E89D19E299C000976E1A /* MXErrorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXErrorTests.m; sourceTree = ""; }; 3281E89F19E2CC1200976E1A /* MXHTTPClientTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXHTTPClientTests.m; sourceTree = ""; }; 3281E8A119E2DE4300976E1A /* MXSessionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MXSessionTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; @@ -325,6 +329,8 @@ 3220094419EFBF30008DE41D /* MXSessionEventListener.m */, 329FB1731A0A3A1600A5E88E /* MXRoomMember.h */, 329FB1741A0A3A1600A5E88E /* MXRoomMember.m */, + 327F8DB01C6112BA00581CA3 /* MXRoomThirdPartyInvite.h */, + 327F8DB11C6112BA00581CA3 /* MXRoomThirdPartyInvite.m */, 329FB17D1A0B665800A5E88E /* MXUser.h */, 329FB17E1A0B665800A5E88E /* MXUser.m */, 327137251A24D50A00DB6757 /* MXMyUser.h */, @@ -608,6 +614,7 @@ 32CE6FB81A409B1F00317F1E /* MXFileStoreMetaData.h in Headers */, 3281E8B719E42DFE00976E1A /* MXJSONModel.h in Headers */, 323E0C5B1A306D7A00A31D73 /* MXEvent.h in Headers */, + 327F8DB21C6112BA00581CA3 /* MXRoomThirdPartyInvite.h in Headers */, 322360521A8E610500A3CA81 /* MXPushRuleDisplayNameCondtionChecker.h in Headers */, 3245A7521AF7B2930001D8A7 /* MXCallManager.h in Headers */, 3264E2A21BDF8D1500F89A86 /* MXCoreDataRoomState.h in Headers */, @@ -789,6 +796,7 @@ 32DC15D51A8CF874006F9AD3 /* MXPushRuleEventMatchConditionChecker.m in Sources */, 32CAB1081A91EA34008C5BB9 /* MXPushRuleRoomMemberCountConditionChecker.m in Sources */, 3245A7511AF7B2930001D8A7 /* MXCall.m in Sources */, + 327F8DB31C6112BA00581CA3 /* MXRoomThirdPartyInvite.m in Sources */, 323B2AE21BCD4CB600B11F34 /* MXCoreDataAccount.m in Sources */, 3264E2A51BDF8D1500F89A86 /* MXCoreDataRoomState+CoreDataProperties.m in Sources */, 329FB1761A0A3A1600A5E88E /* MXRoomMember.m in Sources */, diff --git a/MatrixSDK/Data/MXRoom.h b/MatrixSDK/Data/MXRoom.h index 040b7b05b5..41233698fd 100644 --- a/MatrixSDK/Data/MXRoom.h +++ b/MatrixSDK/Data/MXRoom.h @@ -178,6 +178,7 @@ typedef void (^MXOnRoomEvent)(MXEvent *event, MXEventDirection direction, MXRoom The retrieved events will be sent to registered listeners. @param numItems the number of items to get. + @param onlyFromStore if YES, return available events from the store, do not make a pagination request to the homeserver. @param complete A block object called when the operation is complete. @param failure A block object called when the operation fails. @@ -185,8 +186,9 @@ typedef void (^MXOnRoomEvent)(MXEvent *event, MXEventDirection direction, MXRoom if no request to the home server is required. */ - (MXHTTPOperation*)paginateBackMessages:(NSUInteger)numItems - complete:(void (^)())complete - failure:(void (^)(NSError *error))failure; + onlyFromStore:(BOOL)onlyFromStore + complete:(void (^)())complete + failure:(void (^)(NSError *error))failure; /** @@ -335,6 +337,19 @@ typedef void (^MXOnRoomEvent)(MXEvent *event, MXEventDirection direction, MXRoom success:(void (^)())success failure:(void (^)(NSError *error))failure; +/** + Invite a user to a room based on their email address to this room. + + @param email the user email. + @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*)inviteUserByEmail:(NSString*)email + success:(void (^)())success + failure:(void (^)(NSError *error))failure; + /** Kick a user from this room. diff --git a/MatrixSDK/Data/MXRoom.m b/MatrixSDK/Data/MXRoom.m index a655ab539c..9eb6b6e900 100644 --- a/MatrixSDK/Data/MXRoom.m +++ b/MatrixSDK/Data/MXRoom.m @@ -72,6 +72,7 @@ - (id)initWithRoomId:(NSString *)roomId andMatrixSession:(MXSession *)mxSession2 kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomMessageFeedback, kMXEventTypeStringRoomRedaction, + kMXEventTypeStringRoomThirdPartyInvite, kMXEventTypeStringCallInvite, kMXEventTypeStringCallCandidates, kMXEventTypeStringCallAnswer, @@ -528,8 +529,9 @@ - (void)resetBackState } - (MXHTTPOperation*)paginateBackMessages:(NSUInteger)numItems - complete:(void (^)())complete - failure:(void (^)(NSError *error))failure + onlyFromStore:(BOOL)onlyFromStore + complete:(void (^)())complete + failure:(void (^)(NSError *error))failure { MXHTTPOperation *operation; @@ -561,6 +563,14 @@ - (MXHTTPOperation*)paginateBackMessages:(NSUInteger)numItems } } + if (onlyFromStore && messagesFromStoreCount) + { + complete(); + + NSLog(@"[MXRoom] paginateBackMessages : is done from the store"); + return nil; + } + if (0 < numItems && NO == [mxSession.store hasReachedHomeServerPaginationEndForRoom:_state.roomId]) { // Not enough messages: make a pagination request to the home server @@ -722,6 +732,13 @@ - (MXHTTPOperation*)inviteUser:(NSString*)userId return [mxSession.matrixRestClient inviteUser:userId toRoom:_state.roomId success:success failure:failure]; } +- (MXHTTPOperation*)inviteUserByEmail:(NSString*)email + success:(void (^)())success + failure:(void (^)(NSError *error))failure +{ + return [mxSession.matrixRestClient inviteUserByEmail:email toRoom:_state.roomId success:success failure:failure]; +} + - (MXHTTPOperation*)kickUser:(NSString*)userId reason:(NSString*)reason success:(void (^)())success diff --git a/MatrixSDK/Data/MXRoomMember.h b/MatrixSDK/Data/MXRoomMember.h index 5fc338a5fe..ce7365794f 100644 --- a/MatrixSDK/Data/MXRoomMember.h +++ b/MatrixSDK/Data/MXRoomMember.h @@ -53,9 +53,14 @@ */ @property (nonatomic, readonly) NSString *originUserId; +/** + If the m.room.member event is the successor of a m.room.third_party_invite event, + 'thirdPartyInviteToken' is the token of this event. Else, nil. + */ +@property (nonatomic, readonly) NSString *thirdPartyInviteToken; + /** The event used to build the MXRoomMember. - // @TODO: Consider MXRoomMember as a child class of MXEvent to avoid such data duplication. */ @property (nonatomic, readonly) MXEvent *originalEvent; diff --git a/MatrixSDK/Data/MXRoomMember.m b/MatrixSDK/Data/MXRoomMember.m index be2c5e67f4..2d6788389a 100644 --- a/MatrixSDK/Data/MXRoomMember.m +++ b/MatrixSDK/Data/MXRoomMember.m @@ -50,6 +50,7 @@ - (instancetype)initWithMXEvent:(MXEvent*)roomMemberEvent // We ignore non mxc avatar url _avatarUrl = ([roomMemberContent.avatarUrl hasPrefix:kMXContentUriScheme] ? roomMemberContent.avatarUrl : nil); _membership = [MXTools membership:roomMemberContent.membership]; + _thirdPartyInviteToken = roomMemberContent.thirdPartyInviteToken; _originalEvent = roomMemberEvent; // Set who is this member diff --git a/MatrixSDK/Data/MXRoomState.h b/MatrixSDK/Data/MXRoomState.h index cc7a80540e..19c8498009 100644 --- a/MatrixSDK/Data/MXRoomState.h +++ b/MatrixSDK/Data/MXRoomState.h @@ -19,6 +19,7 @@ #import "MXEvent.h" #import "MXJSONModels.h" #import "MXRoomMember.h" +#import "MXRoomThirdPartyInvite.h" #import "MXRoomPowerLevels.h" @class MXSession; @@ -52,7 +53,12 @@ /** A copy of the list of room members (actually MXRoomMember instances). */ -@property (nonatomic, readonly) NSArray *members; +@property (nonatomic, readonly) NSArray *members; + +/** +A copy of the list of third party invites (actually MXRoomThirdPartyInvite instances). +*/ +@property (nonatomic, readonly) NSArray *thirdPartyInvites; /** The power level of room members @@ -131,7 +137,7 @@ */ - (id)initWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)matrixSession - andInitialSync:(MXRoomInitialSync*)initialSync + andInitialSync:(MXRoomInitialSync*)initialSync andDirection:(BOOL)isLive; /** @@ -158,6 +164,27 @@ */ - (MXRoomMember*)memberWithUserId:(NSString*)userId; +/** + Return the member who was invited by a 3pid medium with the given token. + + When invited by a 3pid medium like email, the not-yet-registered-to-matrix user is indicated + in the room state by a m.room.third_party_invite event. + Once he registers, the homeserver adds a m.room.membership event to the room state. + This event then contains the token of the previous m.room.third_party_invite event. + + @param thirdPartyInviteToken the m.room.third_party_invite token to look for. + @return the room member. + */ +- (MXRoomMember*)memberWithThirdPartyInviteToken:(NSString*)thirdPartyInviteToken; + +/** + Return 3pid invite with the given token. + + @param thirdPartyInviteToken the m.room.third_party_invite token to look for. + @return the 3pid invite. + */ +- (MXRoomThirdPartyInvite*)thirdPartyInviteWithToken:(NSString*)thirdPartyInviteToken; + /** Return a display name for a member. It is his displayname member or, if nil, his userId diff --git a/MatrixSDK/Data/MXRoomState.m b/MatrixSDK/Data/MXRoomState.m index b801d3fc4e..c8435f5ae5 100644 --- a/MatrixSDK/Data/MXRoomState.m +++ b/MatrixSDK/Data/MXRoomState.m @@ -27,8 +27,13 @@ @interface MXRoomState () NSMutableDictionary *stateEvents; NSMutableDictionary *members; + + /** + The third party invites. The key is the token provided by the homeserver. + */ + NSMutableDictionary *thirdPartyInvites; - /* + /** Additional and optional metadata got from initialSync */ MXMembership membership; @@ -49,7 +54,13 @@ @interface MXRoomState () The key is the user id. The value, the member name to display. This cache is resetted when there is new room member event. */ - NSMutableDictionary *membersNamesCache; + NSMutableDictionary *membersNamesCache; + + /** + Cache for [self memberWithThirdPartyInviteToken]. + The key is the 3pid invite token. + */ + NSMutableDictionary *membersWithThirdPartyInviteTokenCache; } @end @@ -70,7 +81,9 @@ - (id)initWithRoomId:(NSString*)roomId stateEvents = [NSMutableDictionary dictionary]; members = [NSMutableDictionary dictionary]; + thirdPartyInvites = [NSMutableDictionary dictionary]; membersNamesCache = [NSMutableDictionary dictionary]; + membersWithThirdPartyInviteTokenCache = [NSMutableDictionary dictionary]; } return self; } @@ -146,6 +159,13 @@ - (NSArray *)stateEvents { [state addObject:roomMember.originalEvent]; } + + // Third party invites are state events too + for (MXRoomThirdPartyInvite *thirdPartyInvite in self.thirdPartyInvites) + { + [state addObject:thirdPartyInvite.originalEvent]; + } + return state; } @@ -154,6 +174,11 @@ - (NSArray *)members return [members allValues]; } +- (NSArray *)thirdPartyInvites +{ + return [thirdPartyInvites allValues]; +} + - (void)setIsPublic:(BOOL)isPublicValue { isPublic = isPublicValue; @@ -426,6 +451,12 @@ - (void)handleStateEvent:(MXEvent*)event // Force to use an identicon url roomMember.avatarUrl = [mxSession.matrixRestClient urlOfIdenticon:roomMember.userId]; } + + // Cache room member event that is successor of a third party invite event + if (roomMember.thirdPartyInviteToken) + { + membersWithThirdPartyInviteTokenCache[roomMember.thirdPartyInviteToken] = roomMember; + } } else { @@ -447,6 +478,25 @@ - (void)handleStateEvent:(MXEvent*)event 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 MXEventTypeRoomPowerLevels: { powerLevels = [MXRoomPowerLevels modelFromJSON:[self contentOfEvent:event]]; @@ -479,6 +529,16 @@ - (MXRoomMember*)memberWithUserId:(NSString *)userId return members[userId]; } +- (MXRoomMember *)memberWithThirdPartyInviteToken:(NSString *)thirdPartyInviteToken +{ + return membersWithThirdPartyInviteTokenCache[thirdPartyInviteToken]; +} + +- (MXRoomThirdPartyInvite *)thirdPartyInviteWithToken:(NSString *)thirdPartyInviteToken +{ + return thirdPartyInvites[thirdPartyInviteToken]; +} + - (NSString*)memberName:(NSString*)userId { // First, lookup in the cache @@ -591,6 +651,10 @@ - (id)copyWithZone:(NSZone *)zone // membership change (ex: "invited" -> "joined") stateCopy->members = [[NSMutableDictionary allocWithZone:zone] initWithDictionary:members]; + stateCopy->thirdPartyInvites = [[NSMutableDictionary allocWithZone:zone] initWithDictionary:thirdPartyInvites]; + + stateCopy->membersWithThirdPartyInviteTokenCache= [[NSMutableDictionary allocWithZone:zone] initWithDictionary:membersWithThirdPartyInviteTokenCache]; + if (visibility) { stateCopy->visibility = [visibility copyWithZone:zone]; diff --git a/MatrixSDK/Data/MXRoomThirdPartyInvite.h b/MatrixSDK/Data/MXRoomThirdPartyInvite.h new file mode 100644 index 0000000000..cfcee4cdce --- /dev/null +++ b/MatrixSDK/Data/MXRoomThirdPartyInvite.h @@ -0,0 +1,49 @@ +/* + Copyright 2016 OpenMarket 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" + +/** + `MXRoomThirdPartyInvite` is the information about a user invited via a third party medium. + */ +@interface MXRoomThirdPartyInvite : NSObject + +/** + The user display name as provided by the home sever. + */ +@property (nonatomic, readonly) NSString *displayname; + +/** + The token generated by the identity server. + */ +@property (nonatomic, readonly) NSString *token; + +/** + The event used to build the MXRoomThirdPartyInvite. + */ +@property (nonatomic, readonly) MXEvent *originalEvent; + +/** + Create the MXRoomThirdPartyInvite member from a Matrix m.room.third_party_invite event by + specifying the content to use.. + + @param roomThirdPartyInviteEvent The m.room.third_party_invite event. + */ +- (instancetype)initWithMXEvent:(MXEvent *)roomThirdPartyInviteEvent; + +@end diff --git a/MatrixSDK/Data/MXRoomThirdPartyInvite.m b/MatrixSDK/Data/MXRoomThirdPartyInvite.m new file mode 100644 index 0000000000..1ca2f0ca2e --- /dev/null +++ b/MatrixSDK/Data/MXRoomThirdPartyInvite.m @@ -0,0 +1,35 @@ +/* + Copyright 2016 OpenMarket 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 "MXRoomThirdPartyInvite.h" + +#import "MXJSONModels.h" + +@implementation MXRoomThirdPartyInvite + +- (instancetype)initWithMXEvent:(MXEvent *)roomThirdPartyInviteEvent +{ + self = [super init]; + if (self) + { + MXJSONModelSetString(_displayname, roomThirdPartyInviteEvent.content[@"display_name"]); + MXJSONModelSetString(_token, roomThirdPartyInviteEvent.stateKey); + _originalEvent = roomThirdPartyInviteEvent; + } + return self; +} + +@end diff --git a/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m b/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m index 52b5b9d08b..4b5a509219 100644 --- a/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m +++ b/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m @@ -20,7 +20,7 @@ #import "MXFileStoreMetaData.h" -NSUInteger const kMXFileVersion = 18; +NSUInteger const kMXFileVersion = 19; NSString *const kMXFileStoreFolder = @"MXFileStore"; NSString *const kMXFileStoreMedaDataFile = @"MXFileStore"; @@ -879,7 +879,7 @@ - (void)loadReceipts if (receiptsDict) { - NSLog(@" - %@: %@", roomId, receiptsDict); + NSLog(@" - %@: %tu", roomId, receiptsDict.count); [receiptsByRoomId setObject:receiptsDict forKey:roomId]; } diff --git a/MatrixSDK/JSONModels/MXEvent.h b/MatrixSDK/JSONModels/MXEvent.h index 9d463b0504..028309395e 100644 --- a/MatrixSDK/JSONModels/MXEvent.h +++ b/MatrixSDK/JSONModels/MXEvent.h @@ -40,6 +40,7 @@ typedef enum : NSUInteger MXEventTypeRoomMessage, MXEventTypeRoomMessageFeedback, MXEventTypeRoomRedaction, + MXEventTypeRoomThirdPartyInvite, MXEventTypeRoomTag, MXEventTypePresence, MXEventTypeTypingNotification, @@ -70,6 +71,7 @@ FOUNDATION_EXPORT NSString *const kMXEventTypeStringRoomCanonicalAlias; FOUNDATION_EXPORT NSString *const kMXEventTypeStringRoomMessage; FOUNDATION_EXPORT NSString *const kMXEventTypeStringRoomMessageFeedback; FOUNDATION_EXPORT NSString *const kMXEventTypeStringRoomRedaction; +FOUNDATION_EXPORT NSString *const kMXEventTypeStringRoomThirdPartyInvite; FOUNDATION_EXPORT NSString *const kMXEventTypeStringRoomTag; FOUNDATION_EXPORT NSString *const kMXEventTypeStringPresence; FOUNDATION_EXPORT NSString *const kMXEventTypeStringTypingNotification; diff --git a/MatrixSDK/JSONModels/MXEvent.m b/MatrixSDK/JSONModels/MXEvent.m index 5d363ff8c2..4a1309d41f 100644 --- a/MatrixSDK/JSONModels/MXEvent.m +++ b/MatrixSDK/JSONModels/MXEvent.m @@ -32,6 +32,7 @@ NSString *const kMXEventTypeStringRoomMessage = @"m.room.message"; NSString *const kMXEventTypeStringRoomMessageFeedback = @"m.room.message.feedback"; NSString *const kMXEventTypeStringRoomRedaction = @"m.room.redaction"; +NSString *const kMXEventTypeStringRoomThirdPartyInvite= @"m.room.third_party_invite"; NSString *const kMXEventTypeStringRoomTag = @"m.tag"; NSString *const kMXEventTypeStringPresence = @"m.presence"; NSString *const kMXEventTypeStringTypingNotification = @"m.typing"; diff --git a/MatrixSDK/JSONModels/MXJSONModels.h b/MatrixSDK/JSONModels/MXJSONModels.h index 294e9ef377..c7030e095f 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.h +++ b/MatrixSDK/JSONModels/MXJSONModels.h @@ -209,6 +209,12 @@ FOUNDATION_EXPORT NSString *const kMXLoginFlowTypeRecaptcha; */ @property (nonatomic) NSString *membership; + /** + If the m.room.member event is the successor of a m.room.third_party_invite event, + 'thirdPartyInviteToken' is the token of this event. Else, nil. + */ + @property (nonatomic) NSString *thirdPartyInviteToken; + @end diff --git a/MatrixSDK/JSONModels/MXJSONModels.m b/MatrixSDK/JSONModels/MXJSONModels.m index bddf895a92..d92fff5df7 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.m +++ b/MatrixSDK/JSONModels/MXJSONModels.m @@ -173,6 +173,11 @@ + (id)modelFromJSON:(NSDictionary *)JSONDictionary MXJSONModelSetString(roomMemberEventContent.displayname, JSONDictionary[@"displayname"]); MXJSONModelSetString(roomMemberEventContent.avatarUrl, JSONDictionary[@"avatar_url"]); MXJSONModelSetString(roomMemberEventContent.membership, JSONDictionary[@"membership"]); + + if (JSONDictionary[@"third_party_invite"] && JSONDictionary[@"third_party_invite"][@"signed"]) + { + MXJSONModelSetString(roomMemberEventContent.thirdPartyInviteToken, JSONDictionary[@"third_party_invite"][@"signed"][@"token"]); + } } return roomMemberEventContent; diff --git a/MatrixSDK/MXError.h b/MatrixSDK/MXError.h index 7a917912a2..f9a0bd3cdc 100644 --- a/MatrixSDK/MXError.h +++ b/MatrixSDK/MXError.h @@ -36,6 +36,11 @@ FOUNDATION_EXPORT NSString *const kMXErrCodeStringBadPagination; FOUNDATION_EXPORT NSString *const kMXErrorStringInvalidToken; +/** + Error codes generated by the Matrix SDK. + */ +FOUNDATION_EXPORT NSString *const kMXSDKErrCodeStringMissingParameters; + /** `MXError` represents an error sent by the home server. MXErrors are encapsulated in NSError. This class is an helper to create NSError or extract MXError from NSError. diff --git a/MatrixSDK/MXError.m b/MatrixSDK/MXError.m index f191ce58fc..2fd4e3c004 100644 --- a/MatrixSDK/MXError.m +++ b/MatrixSDK/MXError.m @@ -31,7 +31,9 @@ NSString *const kMXErrCodeStringRoomInUse = @"M_ROOM_IN_USE"; NSString *const kMXErrCodeStringBadPagination = @"M_BAD_PAGINATION"; -NSString *const kMXErrorStringInvalidToken = @"Invalid token"; +NSString *const kMXErrorStringInvalidToken = @"Invalid token"; + +NSString *const kMXSDKErrCodeStringMissingParameters = @"org.matrix.sdk.missing_parameters"; // Random NSError code diff --git a/MatrixSDK/MXRestClient.h b/MatrixSDK/MXRestClient.h index 56f8fe1c70..d209da149a 100644 --- a/MatrixSDK/MXRestClient.h +++ b/MatrixSDK/MXRestClient.h @@ -589,6 +589,37 @@ typedef enum : NSUInteger toRoom:(NSString*)roomId success:(void (^)())success failure:(void (^)(NSError *error))failure; +/** + Invite a user to a room based on their email address. + + @param email the user email. + @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. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)inviteUserByEmail:(NSString*)email + toRoom:(NSString*)roomId + success:(void (^)())success + failure:(void (^)(NSError *error))failure; + +/** + Invite a user to a room based on a third-party identifier. + + @param medium the medium to invite the user e.g. "email". + @param medium address the address for the specified medium. + @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. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)inviteByThreePid:(NSString*)medium + address:(NSString*)address + toRoom:(NSString*)roomId + success:(void (^)())success + failure:(void (^)(NSError *error))failure; /** Kick a user from a room. @@ -637,6 +668,7 @@ typedef enum : NSUInteger success:(void (^)())success failure:(void (^)(NSError *error))failure; + /** Create a room. diff --git a/MatrixSDK/MXRestClient.m b/MatrixSDK/MXRestClient.m index a68b19899f..38ee6a55da 100644 --- a/MatrixSDK/MXRestClient.m +++ b/MatrixSDK/MXRestClient.m @@ -18,6 +18,7 @@ #import "MXJSONModel.h" #import "MXTools.h" +#import "MXError.h" #pragma mark - Constants definitions /** @@ -192,7 +193,7 @@ - (MXHTTPOperation*)registerWithUser:(NSString*)user andPassword:(NSString*)pass - (NSString*)registerFallback; { - return [[NSURL URLWithString:@"_matrix/static/client/register" relativeToURL:[NSURL URLWithString:homeserver]] absoluteString]; + return [[NSURL URLWithString:@"_matrix/static/client/register/" relativeToURL:[NSURL URLWithString:homeserver]] absoluteString]; } #pragma mark - Login operations @@ -218,7 +219,7 @@ - (MXHTTPOperation*)loginWithUser:(NSString *)user andPassword:(NSString *)passw - (NSString*)loginFallback; { - return [[NSURL URLWithString:@"/_matrix/static/client/login" relativeToURL:[NSURL URLWithString:homeserver]] absoluteString]; + return [[NSURL URLWithString:@"/_matrix/static/client/login/" relativeToURL:[NSURL URLWithString:homeserver]] absoluteString]; } @@ -1025,6 +1026,75 @@ - (MXHTTPOperation*)inviteUser:(NSString*)userId success:success failure:failure]; } +- (MXHTTPOperation*)inviteUserByEmail:(NSString*)email + toRoom:(NSString*)roomId + success:(void (^)())success + failure:(void (^)(NSError *error))failure +{ + return [self inviteByThreePid:@"email" + address:email + toRoom:roomId + success:success failure:failure]; +} + +- (MXHTTPOperation*)inviteByThreePid:(NSString*)medium + address:(NSString*)address + toRoom:(NSString*)roomId + success:(void (^)())success + failure:(void (^)(NSError *error))failure +{ + // The identity server must be defined + if (!_identityServer) + { + if (failure) + { + MXError *error = [[MXError alloc] initWithErrorCode:kMXSDKErrCodeStringMissingParameters error:@"No supplied identity server URL"]; + failure([error createNSError]); + } + return nil; + } + + NSString *path = [NSString stringWithFormat:@"api/v1/rooms/%@/invite", roomId]; + + // This request must not have the protocol part + NSString *identityServer = _identityServer; + if ([identityServer hasPrefix:@"http://"] || [identityServer hasPrefix:@"https://"]) + { + identityServer = [identityServer substringFromIndex:[identityServer rangeOfString:@"://"].location + 3]; + } + + NSDictionary *parameters = @{ + @"id_server": identityServer, + @"medium": medium, + @"address": address + }; + + return [httpClient requestWithMethod:@"POST" + path:path + parameters:parameters + success:^(NSDictionary *JSONResponse) { + if (success) + { + // Use here the processing queue in order to keep the server response order + dispatch_async(processingQueue, ^{ + + dispatch_async(dispatch_get_main_queue(), ^{ + + success(JSONResponse); + + }); + + }); + } + } + failure:^(NSError *error) { + if (failure) + { + failure(error); + } + }]; +} + - (MXHTTPOperation*)kickUser:(NSString*)userId fromRoom:(NSString*)roomId reason:(NSString*)reason @@ -1624,7 +1694,7 @@ - (MXHTTPOperation*)avatarUrlForUser:(NSString*)userId userId = credentials.userId; } - NSString *path = [NSString stringWithFormat:@"api/v1/profile/%@/avatarUrl", userId]; + NSString *path = [NSString stringWithFormat:@"api/v1/profile/%@/avatar_url", userId]; return [httpClient requestWithMethod:@"GET" path:path parameters:nil diff --git a/MatrixSDK/MXSession.h b/MatrixSDK/MXSession.h index e1bffb3860..8c079ecf7c 100644 --- a/MatrixSDK/MXSession.h +++ b/MatrixSDK/MXSession.h @@ -250,8 +250,11 @@ FOUNDATION_EXPORT NSString *const kMXSessionNoRoomTag; /** Resume the session events stream. - @param resumeDone A block called when the SDK has been successfully resumed and - the app has received uptodate data/events. + @param resumeDone A block called when the SDK has been successfully resumed and the app + has received uptodate data/events. The live event listening + (long polling) is not launched yet. + CAUTION The session state is updated (to MXSessionStateRunning) after + calling this block. It SHOULD not be modified by this block. */ - (void)resume:(void (^)())resumeDone; diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index f09b966c72..dae6c06a2c 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -31,7 +31,7 @@ #pragma mark - Constants definitions -const NSString *MatrixSDKVersion = @"0.6.1"; +const NSString *MatrixSDKVersion = @"0.6.2"; NSString *const kMXSessionStateDidChangeNotification = @"kMXSessionStateDidChangeNotification"; NSString *const kMXSessionNewRoomNotification = @"kMXSessionNewRoomNotification"; NSString *const kMXSessionWillLeaveRoomNotification = @"kMXSessionWillLeaveRoomNotification"; @@ -342,8 +342,6 @@ - (void)startWithMessagesLimit:(NSUInteger)messagesLimit NSDate *startDate2 = [NSDate date]; [self resume:^{ NSLog(@"[MXSession] Events stream resumed in %.0fms", [[NSDate date] timeIntervalSinceDate:startDate2] * 1000); - - [self setState:MXSessionStateRunning]; onServerSyncDone(); }]; }; @@ -448,7 +446,11 @@ - (void)streamEventsFromToken:(NSString*)token withLongPoll:(BOOL)longPoll serve if (onBackgroundSyncDone) { NSLog(@"[MXSession] background Sync with %tu new events", events.count); - onBackgroundSyncDone(); + + // 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 = [onBackgroundSyncDone copy]; + onBackgroundSyncDoneCpy(); onBackgroundSyncDone = nil; // check that the application was not resumed while catching up @@ -464,24 +466,33 @@ - (void)streamEventsFromToken:(NSString*)token withLongPoll:(BOOL)longPoll serve 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 (onResumeDone) { NSLog(@"[MXSession] Events stream resumed with %tu new events", events.count); - - onResumeDone(); + + // 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 = [onResumeDone copy]; + onResumeDoneCpy(); onResumeDone = nil; - - // Check SDK user did not called [MXSession close] in onResumeDone - if (nil == _myUser) + + // Stop here if [MXSession close] or [MXSession pause] has been triggered during onResumeDone block. + if (nil == _myUser || _state == MXSessionStatePaused) { return; } } + + // 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 == _myUser || _state == MXSessionStatePaused) + { + return; + } // Go streaming from the returned token [self streamEventsFromToken:paginatedResponse.end withLongPoll:YES]; @@ -498,7 +509,10 @@ - (void)streamEventsFromToken:(NSString*)token withLongPoll:(BOOL)longPoll serve { NSLog(@"[MXSession] background Sync fails %@", error); - onBackgroundSyncFail(error); + // 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. + MXOnBackgroundSyncFail onBackgroundSyncFailCpy = [onBackgroundSyncFail copy]; + onBackgroundSyncFailCpy(error); onBackgroundSyncFail = nil; // check that the application was not resumed while catching up in background @@ -1006,6 +1020,7 @@ - (void)initialServerSync:(void (^)())onServerSyncDone [self streamEventsFromToken:_store.eventStreamToken withLongPoll:YES]; [self setState:MXSessionStateRunning]; + onServerSyncDone(); } failure:^(NSError *error) { @@ -1025,16 +1040,7 @@ - (void)serverSyncWithServerTimeout:(NSUInteger)serverTimeout NSDate *startDate = [NSDate date]; NSLog(@"[MXSession] Do a server sync"); - // *** PATCH SYNC V2 *** - NSString *inlineFilter; - if (_store.eventStreamToken) - { - // We increase the limit for catch-up syncs so that we get more of the v1 behaviour until v2 /sync performance improves - inlineFilter = @"{\"room\":{\"timeline\":{\"limit\":250}}}"; - } - // *** PATCH SYNC V2 *** - - 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:nil success:^(MXSyncResponse *syncResponse) { // Make sure [MXSession close] or [MXSession pause] has not been called before the server response if (!eventStreamRequest) @@ -1164,7 +1170,11 @@ - (void)serverSyncWithServerTimeout:(NSUInteger)serverTimeout if (onBackgroundSyncDone) { NSLog(@"[MXSession] Events stream background Sync succeeded"); - onBackgroundSyncDone(); + + // 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 = [onBackgroundSyncDone copy]; + onBackgroundSyncDoneCpy(); onBackgroundSyncDone = nil; // check that the application was not resumed while catching up in background @@ -1186,19 +1196,28 @@ - (void)serverSyncWithServerTimeout:(NSUInteger)serverTimeout { NSLog(@"[MXSession] Events stream resumed"); - onResumeDone(); + // 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 = [onResumeDone copy]; + onResumeDoneCpy(); onResumeDone = nil; - // Check SDK user did not called [MXSession close] in onResumeDone - if (nil == _myUser) + // Stop here if [MXSession close] or [MXSession pause] has been triggered during onResumeDone block. + if (nil == _myUser || _state == MXSessionStatePaused) { return; } } - // the event stream is running by now + // 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 == _myUser || _state == MXSessionStatePaused) + { + return; + } + // Pursue live events listening (long polling) [self serverSyncWithServerTimeout:SERVER_TIMEOUT_MS success:nil failure:nil clientTimeout:CLIENT_TIMEOUT_MS setPresence:nil]; @@ -1225,7 +1244,10 @@ - (void)serverSyncWithServerTimeout:(NSUInteger)serverTimeout { NSLog(@"[MXSession] background Sync fails %@", error); - onBackgroundSyncFail(error); + // 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. + MXOnBackgroundSyncFail onBackgroundSyncFailCpy = [onBackgroundSyncFail copy]; + onBackgroundSyncFailCpy(error); onBackgroundSyncFail = nil; // check that the application was not resumed while catching up in background diff --git a/MatrixSDK/Utils/MXTools.m b/MatrixSDK/Utils/MXTools.m index 503b803823..90e0841a3b 100644 --- a/MatrixSDK/Utils/MXTools.m +++ b/MatrixSDK/Utils/MXTools.m @@ -38,6 +38,7 @@ + (NSDictionary*)eventTypesMap kMXEventTypeStringRoomMessage: [NSNumber numberWithUnsignedInteger:MXEventTypeRoomMessage], kMXEventTypeStringRoomMessageFeedback: [NSNumber numberWithUnsignedInteger:MXEventTypeRoomMessageFeedback], kMXEventTypeStringRoomRedaction: [NSNumber numberWithUnsignedInteger:MXEventTypeRoomRedaction], + kMXEventTypeStringRoomThirdPartyInvite: [NSNumber numberWithUnsignedInteger:MXEventTypeRoomThirdPartyInvite], kMXEventTypeStringRoomTag: [NSNumber numberWithUnsignedInteger:MXEventTypeRoomTag], kMXEventTypeStringPresence: [NSNumber numberWithUnsignedInteger:MXEventTypePresence], kMXEventTypeStringTypingNotification: [NSNumber numberWithUnsignedInteger:MXEventTypeTypingNotification],