2291 lines
79 KiB
Objective-C
2291 lines
79 KiB
Objective-C
/*
|
|
Copyright 2024 New Vector Ltd.
|
|
Copyright 2019 The Matrix.org Foundation C.I.C
|
|
Copyright 2018 New Vector Ltd
|
|
Copyright 2017 Vector Creations Ltd
|
|
Copyright 2015 OpenMarket Ltd
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
#import "MXKAccount.h"
|
|
|
|
#import "MXKAccountManager.h"
|
|
#import "MXKRoomDataSourceManager.h"
|
|
#import "MXKEventFormatter.h"
|
|
|
|
#import "MXKTools.h"
|
|
#import "MXKContactManager.h"
|
|
|
|
#import "MXKConstants.h"
|
|
|
|
#import "NSBundle+MatrixKit.h"
|
|
|
|
#import <AFNetworking/AFNetworking.h>
|
|
|
|
#import <MatrixSDK/MXBackgroundModeHandler.h>
|
|
|
|
#import "MXKSwiftHeader.h"
|
|
|
|
#import "GeneratedInterface-Swift.h"
|
|
|
|
NSString *const kMXKAccountUserInfoDidChangeNotification = @"kMXKAccountUserInfoDidChangeNotification";
|
|
NSString *const kMXKAccountAPNSActivityDidChangeNotification = @"kMXKAccountAPNSActivityDidChangeNotification";
|
|
NSString *const kMXKAccountPushKitActivityDidChangeNotification = @"kMXKAccountPushKitActivityDidChangeNotification";
|
|
|
|
NSString *const kMXKAccountErrorDomain = @"kMXKAccountErrorDomain";
|
|
|
|
static MXKAccountOnCertificateChange _onCertificateChangeBlock;
|
|
/**
|
|
HTTP status codes for error cases on initial sync requests, for which errors will not be propagated to the client.
|
|
*/
|
|
static NSArray<NSNumber*> *initialSyncSilentErrorsHTTPStatusCodes;
|
|
|
|
@interface MXKAccount ()
|
|
{
|
|
// We will notify user only once on session failure
|
|
BOOL notifyOpenSessionFailure;
|
|
|
|
// Reachability observer
|
|
id reachabilityObserver;
|
|
|
|
// Session state observer
|
|
id sessionStateObserver;
|
|
|
|
// Handle user's settings change
|
|
id userUpdateListener;
|
|
|
|
// Used for logging application start up
|
|
NSDate *openSessionStartDate;
|
|
|
|
// Event notifications listener
|
|
id notificationCenterListener;
|
|
|
|
// Internal list of ignored rooms
|
|
NSMutableArray* ignoredRooms;
|
|
|
|
// Background sync management
|
|
MXOnBackgroundSyncDone backgroundSyncDone;
|
|
MXOnBackgroundSyncFail backgroundSyncFails;
|
|
NSTimer* backgroundSyncTimer;
|
|
|
|
// Observe UIApplicationSignificantTimeChangeNotification to refresh MXRoomSummaries on time formatting change.
|
|
id UIApplicationSignificantTimeChangeNotificationObserver;
|
|
|
|
// Observe NSCurrentLocaleDidChangeNotification to refresh MXRoomSummaries on time formatting change.
|
|
id NSCurrentLocaleDidChangeNotificationObserver;
|
|
|
|
MXPusher *currentPusher;
|
|
}
|
|
|
|
/// Will be true if the session is not in a pauseable state or we requested for the session to pause but not finished yet. Will be reverted to false again after `resume` called.
|
|
@property (nonatomic, assign, getter=isPauseRequested) BOOL pauseRequested;
|
|
@property (nonatomic, strong) id<MXBackgroundTask> pauseBackgroundTask;
|
|
@property (nonatomic, strong) id<MXBackgroundTask> backgroundSyncBgTask;
|
|
|
|
@end
|
|
|
|
@implementation MXKAccount
|
|
@synthesize mxSession, mxRestClient;
|
|
@synthesize userPresence;
|
|
@synthesize userTintColor;
|
|
@synthesize hideUserPresence;
|
|
|
|
+ (void)load
|
|
{
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
initialSyncSilentErrorsHTTPStatusCodes = @[
|
|
@(504),
|
|
@(522),
|
|
@(524),
|
|
@(599)
|
|
];
|
|
});
|
|
}
|
|
|
|
+ (void)registerOnCertificateChangeBlock:(MXKAccountOnCertificateChange)onCertificateChangeBlock
|
|
{
|
|
_onCertificateChangeBlock = onCertificateChangeBlock;
|
|
}
|
|
|
|
+ (UIColor*)presenceColor:(MXPresence)presence
|
|
{
|
|
switch (presence)
|
|
{
|
|
case MXPresenceOnline:
|
|
return [[MXKAppSettings standardAppSettings] presenceColorForOnlineUser];
|
|
case MXPresenceUnavailable:
|
|
return [[MXKAppSettings standardAppSettings] presenceColorForUnavailableUser];
|
|
case MXPresenceOffline:
|
|
return [[MXKAppSettings standardAppSettings] presenceColorForOfflineUser];
|
|
case MXPresenceUnknown:
|
|
default:
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
- (nonnull instancetype)initWithCredentials:(MXCredentials*)credentials
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
notifyOpenSessionFailure = YES;
|
|
|
|
// Report credentials and alloc REST client.
|
|
_mxCredentials = credentials;
|
|
[self prepareRESTClient];
|
|
|
|
userPresence = MXPresenceUnknown;
|
|
|
|
// Refresh device information
|
|
[self loadDeviceInformation:nil failure:nil];
|
|
[self loadCurrentPusher:nil failure:nil];
|
|
|
|
[self registerAccountDataDidChangeIdentityServerNotification];
|
|
[self registerIdentityServiceDidChangeAccessTokenNotification];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[self closeSession:NO];
|
|
mxSession = nil;
|
|
|
|
[mxRestClient close];
|
|
mxRestClient = nil;
|
|
}
|
|
|
|
#pragma mark - NSCoding
|
|
|
|
- (id)initWithCoder:(NSCoder *)coder
|
|
{
|
|
self = [super initWithCoder:coder];
|
|
|
|
if (self)
|
|
{
|
|
notifyOpenSessionFailure = YES;
|
|
|
|
[self prepareRESTClient];
|
|
|
|
[self registerAccountDataDidChangeIdentityServerNotification];
|
|
[self registerIdentityServiceDidChangeAccessTokenNotification];
|
|
|
|
userPresence = MXPresenceUnknown;
|
|
|
|
// Refresh device information
|
|
[self loadDeviceInformation:nil failure:nil];
|
|
[self loadCurrentPusher:nil failure:nil];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - Properties
|
|
|
|
- (void)setIdentityServerURL:(NSString *)identityServerURL
|
|
{
|
|
if (identityServerURL.length)
|
|
{
|
|
_identityServerURL = identityServerURL;
|
|
self.mxCredentials.identityServer = identityServerURL;
|
|
|
|
// Update services used in MXSession
|
|
[mxSession setIdentityServer:self.mxCredentials.identityServer andAccessToken:self.mxCredentials.identityServerAccessToken];
|
|
}
|
|
else
|
|
{
|
|
_identityServerURL = nil;
|
|
[mxSession setIdentityServer:nil andAccessToken:nil];
|
|
}
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
|
|
- (void)setAntivirusServerURL:(NSString *)antivirusServerURL
|
|
{
|
|
_antivirusServerURL = antivirusServerURL;
|
|
// Update the current session if any
|
|
[mxSession setAntivirusServerURL:antivirusServerURL];
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
|
|
- (void)setPushGatewayURL:(NSString *)pushGatewayURL
|
|
{
|
|
_pushGatewayURL = pushGatewayURL.length ? pushGatewayURL : nil;
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] setPushGatewayURL: %@", _pushGatewayURL);
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
|
|
- (NSString*)userDisplayName
|
|
{
|
|
if (mxSession)
|
|
{
|
|
return mxSession.myUser.displayname;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (NSString*)userAvatarUrl
|
|
{
|
|
if (mxSession)
|
|
{
|
|
return mxSession.myUser.avatarUrl;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (NSString*)fullDisplayName
|
|
{
|
|
if (self.userDisplayName.length)
|
|
{
|
|
return [NSString stringWithFormat:@"%@ (%@)", self.userDisplayName, self.mxCredentials.userId];
|
|
}
|
|
else
|
|
{
|
|
return self.mxCredentials.userId;
|
|
}
|
|
}
|
|
|
|
- (NSArray<NSString *> *)linkedEmails
|
|
{
|
|
NSMutableArray<NSString *> *linkedEmails = [NSMutableArray array];
|
|
|
|
for (MXThirdPartyIdentifier *threePID in self.threePIDs)
|
|
{
|
|
if ([threePID.medium isEqualToString:kMX3PIDMediumEmail])
|
|
{
|
|
[linkedEmails addObject:threePID.address];
|
|
}
|
|
}
|
|
|
|
return linkedEmails;
|
|
}
|
|
|
|
- (NSArray<NSString *> *)linkedPhoneNumbers
|
|
{
|
|
NSMutableArray<NSString *> *linkedPhoneNumbers = [NSMutableArray array];
|
|
|
|
for (MXThirdPartyIdentifier *threePID in self.threePIDs)
|
|
{
|
|
if ([threePID.medium isEqualToString:kMX3PIDMediumMSISDN])
|
|
{
|
|
[linkedPhoneNumbers addObject:threePID.address];
|
|
}
|
|
}
|
|
|
|
return linkedPhoneNumbers;
|
|
}
|
|
|
|
- (UIColor*)userTintColor
|
|
{
|
|
if (!userTintColor)
|
|
{
|
|
userTintColor = [MXKTools colorWithRGBValue:[self.mxCredentials.userId hash]];
|
|
}
|
|
|
|
return userTintColor;
|
|
}
|
|
|
|
- (BOOL)pushNotificationServiceIsActive
|
|
{
|
|
if (currentPusher && currentPusher.enabled)
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] pushNotificationServiceIsActive: currentPusher.enabled %@", currentPusher.enabled);
|
|
return currentPusher.enabled.boolValue;
|
|
}
|
|
|
|
BOOL pushNotificationServiceIsActive = ([[MXKAccountManager sharedManager] isAPNSAvailable] && self.hasPusherForPushNotifications && mxSession);
|
|
MXLogDebug(@"[MXKAccount][Push] pushNotificationServiceIsActive: %@", @(pushNotificationServiceIsActive));
|
|
|
|
return pushNotificationServiceIsActive;
|
|
}
|
|
|
|
- (void)enablePushNotifications:(BOOL)enable
|
|
success:(void (^)(void))success
|
|
failure:(void (^)(NSError *))failure
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushNotifications: %@", @(enable));
|
|
|
|
if (enable)
|
|
{
|
|
if (currentPusher && currentPusher.enabled && !currentPusher.enabled.boolValue)
|
|
{
|
|
[self.mxSession.matrixRestClient setPusherWithPushkey:currentPusher.pushkey
|
|
kind:currentPusher.kind
|
|
appId:currentPusher.appId
|
|
appDisplayName:currentPusher.appDisplayName
|
|
deviceDisplayName:currentPusher.deviceDisplayName
|
|
profileTag:currentPusher.profileTag
|
|
lang:currentPusher.lang
|
|
data:currentPusher.data.JSONDictionary
|
|
append:NO
|
|
enabled:enable
|
|
success:^{
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushNotifications: remotely enabled Push: Success");
|
|
[self loadCurrentPusher:^{
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
} failure:^(NSError *error) {
|
|
|
|
MXLogWarning(@"[MXKAccount][Push] enablePushNotifications: load current pusher failed with error: %@", error);
|
|
if (failure)
|
|
{
|
|
failure(error);
|
|
}
|
|
}];
|
|
} failure:^(NSError *error) {
|
|
|
|
MXLogWarning(@"[MXKAccount][Push] enablePushNotifications: remotely enable push failed with error: %@", error);
|
|
if (failure)
|
|
{
|
|
failure(error);
|
|
}
|
|
}];
|
|
}
|
|
else if ([[MXKAccountManager sharedManager] isAPNSAvailable])
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushNotifications: Enable Push for %@ account", self.mxCredentials.userId);
|
|
|
|
// Create/restore the pusher
|
|
[self enableAPNSPusher:YES success:^{
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushNotifications: Enable Push: Success");
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
} failure:^(NSError *error) {
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushNotifications: Enable Push: Error: %@", error);
|
|
if (failure)
|
|
{
|
|
failure(error);
|
|
}
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushNotifications: Error: Cannot enable Push");
|
|
|
|
NSError *error = [NSError errorWithDomain:kMXKAccountErrorDomain
|
|
code:0
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey:
|
|
[VectorL10n accountErrorPushNotAllowed]
|
|
}];
|
|
if (failure)
|
|
{
|
|
failure (error);
|
|
}
|
|
}
|
|
}
|
|
else if (self.hasPusherForPushNotifications || currentPusher)
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushNotifications: Disable APNS for %@ account", self.mxCredentials.userId);
|
|
|
|
// Delete the pusher, report the new value only on success.
|
|
[self enableAPNSPusher:NO
|
|
success:^{
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushNotifications: Disable Push: Success");
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
} failure:^(NSError *error) {
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushNotifications: Disable Push: Error: %@", error);
|
|
if (failure)
|
|
{
|
|
failure(error);
|
|
}
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (BOOL)isPushKitNotificationActive
|
|
{
|
|
BOOL isPushKitNotificationActive = ([[MXKAccountManager sharedManager] isPushAvailable] && self.hasPusherForPushKitNotifications && mxSession);
|
|
MXLogDebug(@"[MXKAccount][Push] isPushKitNotificationActive: %@", @(isPushKitNotificationActive));
|
|
|
|
return isPushKitNotificationActive;
|
|
}
|
|
|
|
- (void)enablePushKitNotifications:(BOOL)enable
|
|
success:(void (^)(void))success
|
|
failure:(void (^)(NSError *))failure
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitNotifications: %@", @(enable));
|
|
|
|
if (enable)
|
|
{
|
|
if ([[MXKAccountManager sharedManager] isPushAvailable])
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitNotifications: Enable Push for %@ account", self.mxCredentials.userId);
|
|
|
|
// Create/restore the pusher
|
|
[self enablePushKitPusher:YES success:^{
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitNotifications: Enable Push: Success");
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
} failure:^(NSError *error) {
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitNotifications: Enable Push: Error: %@", error);
|
|
if (failure)
|
|
{
|
|
failure(error);
|
|
}
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitNotifications: Error: Cannot enable Push");
|
|
|
|
NSError *error = [NSError errorWithDomain:kMXKAccountErrorDomain
|
|
code:0
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey:
|
|
[VectorL10n accountErrorPushNotAllowed]
|
|
}];
|
|
failure (error);
|
|
}
|
|
}
|
|
else if (self.hasPusherForPushKitNotifications)
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitNotifications: Disable Push for %@ account", self.mxCredentials.userId);
|
|
|
|
// Delete the pusher, report the new value only on success.
|
|
[self enablePushKitPusher:NO success:^{
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitNotifications: Disable Push: Success");
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
} failure:^(NSError *error) {
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitNotifications: Disable Push: Error: %@", error);
|
|
if (failure)
|
|
{
|
|
failure(error);
|
|
}
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitNotifications: PushKit is already disabled for %@", self.mxCredentials.userId);
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)setEnableInAppNotifications:(BOOL)enableInAppNotifications
|
|
{
|
|
MXLogDebug(@"[MXKAccount] setEnableInAppNotifications: %@", @(enableInAppNotifications));
|
|
|
|
_enableInAppNotifications = enableInAppNotifications;
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
|
|
- (void)setDisabled:(BOOL)disabled
|
|
{
|
|
if (_disabled != disabled)
|
|
{
|
|
_disabled = disabled;
|
|
|
|
if (_disabled)
|
|
{
|
|
[self deletePusher];
|
|
[self enablePushKitNotifications:NO success:nil failure:nil];
|
|
|
|
// Close session (keep the storage).
|
|
[self closeSession:NO];
|
|
}
|
|
else if (!mxSession)
|
|
{
|
|
// Open a new matrix session
|
|
id<MXStore> store = [[[MXKAccountManager sharedManager].storeClass alloc] init];
|
|
|
|
[self openSessionWithStore:store];
|
|
}
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
}
|
|
|
|
- (void)setWarnedAboutEncryption:(BOOL)warnedAboutEncryption
|
|
{
|
|
_warnedAboutEncryption = warnedAboutEncryption;
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
|
|
- (NSMutableDictionary<NSString *, id<NSCoding>> *)others
|
|
{
|
|
if(_others == nil)
|
|
{
|
|
_others = [NSMutableDictionary dictionary];
|
|
}
|
|
|
|
return _others;
|
|
}
|
|
|
|
- (void)setPauseRequested:(BOOL)pauseRequested
|
|
{
|
|
if (_pauseRequested != pauseRequested)
|
|
{
|
|
_pauseRequested = pauseRequested;
|
|
|
|
if (_pauseRequested)
|
|
{
|
|
// Make sure the SDK finish its work before the app goes sleeping in background
|
|
id<MXBackgroundModeHandler> handler = [MXSDKOptions sharedInstance].backgroundModeHandler;
|
|
if (handler)
|
|
{
|
|
if (!self.pauseBackgroundTask.isRunning)
|
|
{
|
|
self.pauseBackgroundTask = [handler startBackgroundTaskWithName:@"[MXKAccount] pauseInBackgroundTask"
|
|
expirationHandler:nil];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[self cancelPauseBackgroundTask];
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - Matrix user's profile
|
|
|
|
- (void)setUserDisplayName:(NSString*)displayname success:(void (^)(void))success failure:(void (^)(NSError *error))failure
|
|
{
|
|
if (mxSession && mxSession.myUser)
|
|
{
|
|
[mxSession.myUser setDisplayName:displayname
|
|
success:^{
|
|
if (success) {
|
|
success();
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountUserInfoDidChangeNotification object:self.mxCredentials.userId];
|
|
}
|
|
failure:failure];
|
|
}
|
|
else if (failure)
|
|
{
|
|
failure ([NSError errorWithDomain:kMXKAccountErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: [VectorL10n accountErrorMatrixSessionIsNotOpened]}]);
|
|
}
|
|
}
|
|
|
|
- (void)setUserAvatarUrl:(NSString*)avatarUrl success:(void (^)(void))success failure:(void (^)(NSError *error))failure
|
|
{
|
|
if (mxSession && mxSession.myUser)
|
|
{
|
|
[mxSession.myUser setAvatarUrl:avatarUrl
|
|
success:^{
|
|
if (success) {
|
|
success();
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountUserInfoDidChangeNotification object:self.mxCredentials.userId];
|
|
}
|
|
failure:failure];
|
|
}
|
|
else if (failure)
|
|
{
|
|
failure ([NSError errorWithDomain:kMXKAccountErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: [VectorL10n accountErrorMatrixSessionIsNotOpened]}]);
|
|
}
|
|
}
|
|
|
|
- (void)changePassword:(NSString*)oldPassword with:(NSString*)newPassword logoutDevices:(BOOL)logoutDevices success:(void (^)(void))success failure:(void (^)(NSError *error))failure
|
|
{
|
|
if (mxSession)
|
|
{
|
|
[mxRestClient changePassword:oldPassword
|
|
with:newPassword
|
|
logoutDevices:logoutDevices
|
|
success:^{
|
|
|
|
if (success) {
|
|
success();
|
|
}
|
|
|
|
}
|
|
failure:failure];
|
|
}
|
|
else if (failure)
|
|
{
|
|
failure ([NSError errorWithDomain:kMXKAccountErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: [VectorL10n accountErrorMatrixSessionIsNotOpened]}]);
|
|
}
|
|
}
|
|
|
|
- (void)load3PIDs:(void (^)(void))success failure:(void (^)(NSError *))failure
|
|
{
|
|
|
|
[mxRestClient threePIDs:^(NSArray<MXThirdPartyIdentifier *> *threePIDs2) {
|
|
self->_threePIDs = threePIDs2;
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
|
|
} failure:^(NSError *error) {
|
|
if (failure)
|
|
{
|
|
failure(error);
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)loadCurrentPusher:(void (^)(void))success failure:(void (^)(NSError *error))failure
|
|
{
|
|
if (!self.mxSession.myDeviceId)
|
|
{
|
|
MXLogWarning(@"[MXKAccount] loadPusher: device ID not found");
|
|
if (failure)
|
|
{
|
|
failure([NSError errorWithDomain:kMXKAccountErrorDomain code:0 userInfo:nil]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
[self.mxSession supportedMatrixVersions:^(MXMatrixVersions *matrixVersions) {
|
|
if (!matrixVersions.supportsRemotelyTogglingPushNotifications)
|
|
{
|
|
MXLogDebug(@"[MXKAccount] loadPusher: remotely toggling push notifications not supported");
|
|
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
[self.mxSession.matrixRestClient pushers:^(NSArray<MXPusher *> *pushers) {
|
|
MXPusher *ownPusher;
|
|
for (MXPusher *pusher in pushers)
|
|
{
|
|
if ([pusher.deviceId isEqualToString:self.mxSession.myDeviceId])
|
|
{
|
|
ownPusher = pusher;
|
|
}
|
|
}
|
|
|
|
self->currentPusher = ownPusher;
|
|
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
} failure:^(NSError *error) {
|
|
MXLogWarning(@"[MXKAccount] loadPusher: get pushers failed due to error %@", error);
|
|
|
|
if (failure)
|
|
{
|
|
failure(error);
|
|
}
|
|
}];
|
|
} failure:^(NSError *error) {
|
|
MXLogWarning(@"[MXKAccount] loadPusher: supportedMatrixVersions failed due to error %@", error);
|
|
|
|
if (failure)
|
|
{
|
|
failure(error);
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)loadDeviceInformation:(void (^)(void))success failure:(void (^)(NSError *error))failure
|
|
{
|
|
if (self.mxCredentials.deviceId)
|
|
{
|
|
[mxRestClient deviceByDeviceId:self.mxCredentials.deviceId success:^(MXDevice *device) {
|
|
|
|
self->_device = device;
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
if (failure)
|
|
{
|
|
failure(error);
|
|
}
|
|
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
_device = nil;
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)setUserPresence:(MXPresence)presence andStatusMessage:(NSString *)statusMessage completion:(void (^)(void))completion
|
|
{
|
|
userPresence = presence;
|
|
|
|
if (mxSession && !hideUserPresence)
|
|
{
|
|
// Update user presence on server side
|
|
[mxSession.myUser setPresence:userPresence
|
|
andStatusMessage:statusMessage
|
|
success:^{
|
|
MXLogDebug(@"[MXKAccount] %@: set user presence (%lu) succeeded", self.mxCredentials.userId, (unsigned long)self->userPresence);
|
|
if (completion)
|
|
{
|
|
completion();
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountUserInfoDidChangeNotification object:self.mxCredentials.userId];
|
|
}
|
|
failure:^(NSError *error) {
|
|
MXLogDebug(@"[MXKAccount] %@: set user presence (%lu) failed", self.mxCredentials.userId, (unsigned long)self->userPresence);
|
|
}];
|
|
}
|
|
else if (hideUserPresence)
|
|
{
|
|
MXLogDebug(@"[MXKAccount] %@: set user presence is disabled.", self.mxCredentials.userId);
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
/**
|
|
Create a matrix session based on the provided store.
|
|
When store data is ready, the live stream is automatically launched by synchronising the session with the server.
|
|
|
|
In case of failure during server sync, the method is reiterated until the data is up-to-date with the server.
|
|
This loop is stopped if you call [MXCAccount closeSession:], it is suspended if you call [MXCAccount pauseInBackgroundTask].
|
|
|
|
@param store the store to use for the session.
|
|
*/
|
|
-(void)openSessionWithStore:(id<MXStore>)store
|
|
{
|
|
// Sanity check
|
|
if (!self.mxCredentials || !mxRestClient)
|
|
{
|
|
MXLogDebug(@"[MXKAccount] Matrix session cannot be created without credentials");
|
|
return;
|
|
}
|
|
|
|
// Close potential session (keep associated store).
|
|
[self closeSession:NO];
|
|
|
|
openSessionStartDate = [NSDate date];
|
|
|
|
// Instantiate new session
|
|
mxSession = [[MXSession alloc] initWithMatrixRestClient:mxRestClient];
|
|
mxSession.preferredSyncPresence = self.preferredSyncPresence;
|
|
|
|
// Check whether an antivirus url is defined.
|
|
if (_antivirusServerURL)
|
|
{
|
|
// Enable the antivirus scanner in the current session.
|
|
[mxSession setAntivirusServerURL:_antivirusServerURL];
|
|
}
|
|
|
|
// Set default MXEvent -> NSString formatter
|
|
MXKEventFormatter *eventFormatter = [[MXKEventFormatter alloc] initWithMatrixSession:self.mxSession];
|
|
eventFormatter.isForSubtitle = YES;
|
|
|
|
// Apply the event types filter to display only the wanted event types.
|
|
eventFormatter.eventTypesFilterForMessages = [MXKAppSettings standardAppSettings].eventsFilterForMessages;
|
|
|
|
mxSession.roomSummaryUpdateDelegate = eventFormatter;
|
|
|
|
// Observe UIApplicationSignificantTimeChangeNotification to refresh to MXRoomSummaries if date/time are shown.
|
|
// UIApplicationSignificantTimeChangeNotification is posted if DST is updated, carrier time is updated
|
|
UIApplicationSignificantTimeChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationSignificantTimeChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
[self onDateTimeFormatUpdate];
|
|
}];
|
|
|
|
|
|
// Observe NSCurrentLocaleDidChangeNotification to refresh MXRoomSummaries if date/time are shown.
|
|
// NSCurrentLocaleDidChangeNotification is triggered when the time swicthes to AM/PM to 24h time format
|
|
NSCurrentLocaleDidChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSCurrentLocaleDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
[self onDateTimeFormatUpdate];
|
|
}];
|
|
|
|
// Force a date refresh for all the last messages.
|
|
[self onDateTimeFormatUpdate];
|
|
|
|
// Register session state observer
|
|
sessionStateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionStateDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
|
|
// Check whether the concerned session is the associated one
|
|
if (notif.object == self->mxSession)
|
|
{
|
|
[self onMatrixSessionStateChange];
|
|
}
|
|
}];
|
|
|
|
MXWeakify(self);
|
|
|
|
[mxSession setStore:store success:^{
|
|
|
|
// Complete session registration by launching live stream
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
// Validate the availability of local contact sync for any changes to the
|
|
// authorization of contacts access that may have occurred since the last launch.
|
|
// The session is passed in as the contacts manager may not have had a session added yet.
|
|
[MXKContactManager.sharedManager validateSyncLocalContactsStateForSession:self.mxSession];
|
|
|
|
// Refresh pusher state
|
|
[self loadCurrentPusher:^{
|
|
[self refreshAPNSPusher];
|
|
} failure:nil];
|
|
[self refreshPushKitPusher];
|
|
|
|
// Launch server sync
|
|
[self launchInitialServerSync];
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
// This cannot happen. Loading of MXFileStore cannot fail.
|
|
MXStrongifyAndReturnIfNil(self);
|
|
self->mxSession = nil;
|
|
|
|
NSString *myUserId = self.mxSession.myUser.userId;
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self->sessionStateObserver];
|
|
self->sessionStateObserver = nil;
|
|
|
|
}];
|
|
}
|
|
|
|
/**
|
|
Close the matrix session.
|
|
|
|
@param clearStore set YES to delete all store data.
|
|
*/
|
|
- (void)closeSession:(BOOL)clearStore
|
|
{
|
|
MXLogDebug(@"[MXKAccount] closeSession (%u)", clearStore);
|
|
|
|
if (NSCurrentLocaleDidChangeNotificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:NSCurrentLocaleDidChangeNotificationObserver];
|
|
NSCurrentLocaleDidChangeNotificationObserver = nil;
|
|
}
|
|
|
|
if (UIApplicationSignificantTimeChangeNotificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:UIApplicationSignificantTimeChangeNotificationObserver];
|
|
UIApplicationSignificantTimeChangeNotificationObserver = nil;
|
|
}
|
|
|
|
[self removeNotificationListener];
|
|
|
|
if (reachabilityObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:reachabilityObserver];
|
|
reachabilityObserver = nil;
|
|
}
|
|
|
|
if (sessionStateObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:sessionStateObserver];
|
|
sessionStateObserver = nil;
|
|
}
|
|
|
|
if (userUpdateListener)
|
|
{
|
|
[mxSession.myUser removeListener:userUpdateListener];
|
|
userUpdateListener = nil;
|
|
}
|
|
|
|
if (mxSession)
|
|
{
|
|
// Reset room data stored in memory
|
|
[MXKRoomDataSourceManager removeSharedManagerForMatrixSession:mxSession];
|
|
|
|
if (clearStore)
|
|
{
|
|
// Clean other stores
|
|
[mxSession.scanManager deleteAllAntivirusScans];
|
|
[mxSession.aggregations resetData];
|
|
}
|
|
else
|
|
{
|
|
// For recomputing of room summaries as they are a cache of computed data
|
|
[mxSession resetRoomsSummariesLastMessage];
|
|
}
|
|
|
|
// Close session
|
|
[mxSession close];
|
|
|
|
if (clearStore)
|
|
{
|
|
[mxSession.store deleteAllData];
|
|
}
|
|
|
|
mxSession = nil;
|
|
}
|
|
|
|
notifyOpenSessionFailure = YES;
|
|
}
|
|
|
|
- (void)logout:(void (^)(void))completion
|
|
{
|
|
if (!mxSession || !mxSession.matrixRestClient)
|
|
{
|
|
MXLogDebug(@"[MXKAccount] logout: Need to open the closed session to make a logout request");
|
|
id<MXStore> store = [[[MXKAccountManager sharedManager].storeClass alloc] init];
|
|
mxSession = [[MXSession alloc] initWithMatrixRestClient:mxRestClient];
|
|
|
|
MXWeakify(self);
|
|
[mxSession setStore:store success:^{
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
[self logout:completion];
|
|
|
|
} failure:^(NSError *error) {
|
|
completion();
|
|
}];
|
|
return;
|
|
}
|
|
|
|
[self deletePusher];
|
|
[self enablePushKitNotifications:NO success:nil failure:nil];
|
|
|
|
MXHTTPOperation *operation = [mxSession logout:^{
|
|
|
|
[self closeSession:YES];
|
|
if (completion)
|
|
{
|
|
completion();
|
|
}
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
// Close the session even if the logout request failed
|
|
[self closeSession:YES];
|
|
if (completion)
|
|
{
|
|
completion();
|
|
}
|
|
|
|
}];
|
|
|
|
// Do not retry on failure.
|
|
operation.maxNumberOfTries = 1;
|
|
}
|
|
|
|
// Logout locally, do not send server request
|
|
- (void)logoutLocally:(void (^)(void))completion
|
|
{
|
|
[self deletePusher];
|
|
[self enablePushKitNotifications:NO success:nil failure:nil];
|
|
|
|
[mxSession enableCrypto:NO success:^{
|
|
[self closeSession:YES];
|
|
if (completion)
|
|
{
|
|
completion();
|
|
}
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
// Close the session even if the logout request failed
|
|
[self closeSession:YES];
|
|
if (completion)
|
|
{
|
|
completion();
|
|
}
|
|
|
|
}];
|
|
}
|
|
|
|
- (void)logoutSendingServerRequest:(BOOL)sendLogoutServerRequest
|
|
completion:(void (^)(void))completion
|
|
{
|
|
if (sendLogoutServerRequest)
|
|
{
|
|
[self logout:completion];
|
|
}
|
|
else
|
|
{
|
|
[self logoutLocally:completion];
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark - Soft logout
|
|
|
|
- (void)softLogout
|
|
{
|
|
if (_isSoftLogout)
|
|
{
|
|
// do not close the session if already soft logged out
|
|
// it may break the current logout request and resetting session credentials can cause crashes
|
|
return;
|
|
}
|
|
_isSoftLogout = YES;
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
|
|
// Stop SDK making requests to the homeserver
|
|
[mxSession close];
|
|
}
|
|
|
|
- (void)hydrateWithCredentials:(MXCredentials*)credentials
|
|
{
|
|
// Sanity check
|
|
if ([self.mxCredentials.userId isEqualToString:credentials.userId])
|
|
{
|
|
_mxCredentials = credentials;
|
|
_isSoftLogout = NO;
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
|
|
[self prepareRESTClient];
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccount] hydrateWithCredentials: Error: users ids mismatch: %@ vs %@", credentials.userId, self.mxCredentials.userId);
|
|
}
|
|
}
|
|
|
|
- (void)deletePusher
|
|
{
|
|
if (self.pushNotificationServiceIsActive)
|
|
{
|
|
[self enableAPNSPusher:NO success:nil failure:nil];
|
|
}
|
|
}
|
|
|
|
- (void)pauseInBackgroundTask
|
|
{
|
|
if (mxSession == nil)
|
|
{
|
|
// no session to pause
|
|
return;
|
|
}
|
|
|
|
// mark that we want to pause when possible
|
|
self.pauseRequested = YES;
|
|
|
|
if (mxSession.isPauseable)
|
|
{
|
|
// Pause SDK
|
|
[mxSession pause];
|
|
|
|
// Update user presence
|
|
MXWeakify(self);
|
|
[self setUserPresence:MXPresenceOffline andStatusMessage:nil completion:^{
|
|
MXStrongifyAndReturnIfNil(self);
|
|
[self cancelPauseBackgroundTask];
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
// Cancel pending actions
|
|
[[NSNotificationCenter defaultCenter] removeObserver:reachabilityObserver];
|
|
reachabilityObserver = nil;
|
|
|
|
MXLogDebug(@"[MXKAccount] Pause is delayed due to the session state: %@", [MXTools readableSessionState: mxSession.state]);
|
|
}
|
|
}
|
|
|
|
- (void)resume
|
|
{
|
|
if (mxSession == nil)
|
|
{
|
|
// no session to resume
|
|
return;
|
|
}
|
|
|
|
// mark that we don't want to pause anymore
|
|
self.pauseRequested = NO;
|
|
|
|
MXLogVerbose(@"[MXKAccount] resume: with session state: %@", [MXTools readableSessionState:mxSession.state]);
|
|
|
|
[self cancelBackgroundSync];
|
|
|
|
switch (mxSession.state)
|
|
{
|
|
case MXSessionStatePaused:
|
|
case MXSessionStatePauseRequested:
|
|
{
|
|
// Resume SDK and update user presence
|
|
MXWeakify(self);
|
|
[mxSession resume:^{
|
|
MXStrongifyAndReturnIfNil(self);
|
|
[self setUserPresence:self.preferredSyncPresence andStatusMessage:nil completion:nil];
|
|
|
|
[self refreshAPNSPusher];
|
|
[self refreshPushKitPusher];
|
|
}];
|
|
|
|
break;
|
|
}
|
|
case MXSessionStateStoreDataReady:
|
|
case MXSessionStateInitialSyncFailed:
|
|
{
|
|
// The session initialisation was uncompleted, we try to complete it here.
|
|
[self launchInitialServerSync];
|
|
|
|
[self refreshAPNSPusher];
|
|
[self refreshPushKitPusher];
|
|
|
|
break;
|
|
}
|
|
case MXSessionStateSyncInProgress:
|
|
{
|
|
[self refreshAPNSPusher];
|
|
[self refreshPushKitPusher];
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (void)reload:(BOOL)clearCache
|
|
{
|
|
// close potential session
|
|
[self closeSession:clearCache];
|
|
|
|
if (!_disabled)
|
|
{
|
|
// Open a new matrix session
|
|
id<MXStore> store = [[[MXKAccountManager sharedManager].storeClass alloc] init];
|
|
[self openSessionWithStore:store];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Push notifications
|
|
|
|
// Refresh the APNS pusher state for this account on this device.
|
|
- (void)refreshAPNSPusher
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] refreshAPNSPusher");
|
|
|
|
if (currentPusher)
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] refreshAPNSPusher aborted as a pusher has been found");
|
|
return;
|
|
}
|
|
|
|
// Check the conditions required to run the pusher
|
|
if (self.pushNotificationServiceIsActive)
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] refreshAPNSPusher: Refresh APNS pusher for %@ account", self.mxCredentials.userId);
|
|
|
|
// Create/restore the pusher
|
|
[self enableAPNSPusher:YES
|
|
success:nil
|
|
failure:^(NSError *error) {
|
|
MXLogDebug(@"[MXKAccount][Push] ;: Error: %@", error);
|
|
}];
|
|
}
|
|
else if (_hasPusherForPushNotifications)
|
|
{
|
|
if ([MXKAccountManager sharedManager].apnsDeviceToken)
|
|
{
|
|
if (mxSession)
|
|
{
|
|
// Turn off pusher if user denied remote notification.
|
|
MXLogDebug(@"[MXKAccount][Push] refreshAPNSPusher: Disable APNS pusher for %@ account (notifications are denied)", self.mxCredentials.userId);
|
|
[self enableAPNSPusher:NO success:nil failure:nil];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] refreshAPNSPusher: APNS pusher for %@ account is already disabled. Reset _hasPusherForPushNotifications", self.mxCredentials.userId);
|
|
_hasPusherForPushNotifications = NO;
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enable/Disable the APNS pusher for this account on this device on the homeserver.
|
|
- (void)enableAPNSPusher:(BOOL)enabled success:(void (^)(void))success failure:(void (^)(NSError *))failure
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enableAPNSPusher: %@", @(enabled));
|
|
|
|
#ifdef DEBUG
|
|
NSString *appId = [[NSUserDefaults standardUserDefaults] objectForKey:@"pusherAppIdDev"];
|
|
#else
|
|
NSString *appId = [[NSUserDefaults standardUserDefaults] objectForKey:@"pusherAppIdProd"];
|
|
#endif
|
|
|
|
NSString *locKey = MXKAppSettings.standardAppSettings.notificationBodyLocalizationKey;
|
|
|
|
NSDictionary *pushData = @{
|
|
@"url": self.pushGatewayURL,
|
|
@"format": @"event_id_only",
|
|
@"default_payload": @{@"aps": @{@"mutable-content": @(1), @"alert": @{@"loc-key": locKey, @"loc-args": @[]}}}
|
|
};
|
|
|
|
[self enablePusher:enabled appId:appId token:[MXKAccountManager sharedManager].apnsDeviceToken pushData:pushData success:^{
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enableAPNSPusher: Succeeded to update APNS pusher for %@ (%d)", self.mxCredentials.userId, enabled);
|
|
|
|
self->_hasPusherForPushNotifications = enabled;
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
|
|
if (enabled)
|
|
{
|
|
[self loadCurrentPusher:^{
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountAPNSActivityDidChangeNotification object:self.mxCredentials.userId];
|
|
} failure:^(NSError *error) {
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountAPNSActivityDidChangeNotification object:self.mxCredentials.userId];
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
self->currentPusher = nil;
|
|
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountAPNSActivityDidChangeNotification object:self.mxCredentials.userId];
|
|
}
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
// Ignore error if the client try to disable an unknown token
|
|
if (!enabled)
|
|
{
|
|
// Check whether the token was unknown
|
|
MXError *mxError = [[MXError alloc] initWithNSError:error];
|
|
if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringUnknown])
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enableAPNSPusher: APNS was already disabled for %@!", self.mxCredentials.userId);
|
|
|
|
// Ignore the error
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountAPNSActivityDidChangeNotification object:self.mxCredentials.userId];
|
|
|
|
return;
|
|
}
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enableAPNSPusher: Failed to disable APNS %@! (%@)", self.mxCredentials.userId, error);
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enableAPNSPusher: Failed to send APNS token for %@! (%@)", self.mxCredentials.userId, error);
|
|
}
|
|
|
|
if (failure)
|
|
{
|
|
failure(error);
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountAPNSActivityDidChangeNotification object:self.mxCredentials.userId];
|
|
}];
|
|
}
|
|
|
|
// Refresh the PushKit pusher state for this account on this device.
|
|
- (void)refreshPushKitPusher
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] refreshPushKitPusher");
|
|
|
|
// Check the conditions required to run the pusher
|
|
if (![MXKAppSettings standardAppSettings].allowPushKitPushers)
|
|
{
|
|
// Turn off pusher if PushKit pushers are not allowed
|
|
MXLogDebug(@"[MXKAccount][Push] refreshPushKitPusher: Disable PushKit pusher for %@ account (pushers are not allowed)", self.mxCredentials.userId);
|
|
[self enablePushKitPusher:NO success:nil failure:nil];
|
|
}
|
|
else if (self.isPushKitNotificationActive)
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] refreshPushKitPusher: Refresh PushKit pusher for %@ account", self.mxCredentials.userId);
|
|
|
|
// Create/restore the pusher
|
|
[self enablePushKitPusher:YES
|
|
success:nil
|
|
failure:^(NSError *error) {
|
|
MXLogDebug(@"[MXKAccount][Push] refreshPushKitPusher: Error: %@", error);
|
|
}];
|
|
}
|
|
else if (self.hasPusherForPushKitNotifications)
|
|
{
|
|
if ([MXKAccountManager sharedManager].pushDeviceToken)
|
|
{
|
|
if (mxSession)
|
|
{
|
|
// Turn off pusher if user denied remote notification.
|
|
MXLogDebug(@"[MXKAccount][Push] refreshPushKitPusher: Disable PushKit pusher for %@ account (notifications are denied)", self.mxCredentials.userId);
|
|
[self enablePushKitPusher:NO success:nil failure:nil];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] refreshPushKitPusher: PushKit pusher for %@ account is already disabled. Reset _hasPusherForPushKitNotifications", self.mxCredentials.userId);
|
|
self->_hasPusherForPushKitNotifications = NO;
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enable/Disable the pusher based on PushKit for this account on this device on the homeserver.
|
|
- (void)enablePushKitPusher:(BOOL)enabled success:(void (^)(void))success failure:(void (^)(NSError *))failure
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitPusher: %@", @(enabled));
|
|
|
|
if (enabled && ![MXKAppSettings standardAppSettings].allowPushKitPushers)
|
|
{
|
|
// sanity check, if accidently try to enable the pusher
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitPusher: Do not enable it because PushKit pushers not allowed");
|
|
if (failure)
|
|
{
|
|
failure([NSError errorWithDomain:kMXKAccountErrorDomain code:0 userInfo:nil]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
NSString *appIdKey;
|
|
#ifdef DEBUG
|
|
appIdKey = @"pushKitAppIdDev";
|
|
#else
|
|
appIdKey = @"pushKitAppIdProd";
|
|
#endif
|
|
|
|
NSString *appId = [[NSUserDefaults standardUserDefaults] objectForKey:appIdKey];
|
|
|
|
NSMutableDictionary *pushData = [NSMutableDictionary dictionaryWithDictionary:@{@"url": self.pushGatewayURL}];
|
|
|
|
NSDictionary *options = [MXKAccountManager sharedManager].pushOptions;
|
|
if (options.count)
|
|
{
|
|
[pushData addEntriesFromDictionary:options];
|
|
}
|
|
|
|
NSData *token = [MXKAccountManager sharedManager].pushDeviceToken;
|
|
if (!token)
|
|
{
|
|
// sanity check, if no token there is no point of calling the endpoint
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitPusher: Failed to update PushKit pusher to %@ for %@. (token is missing)", @(enabled), self.mxCredentials.userId);
|
|
if (failure)
|
|
{
|
|
failure([NSError errorWithDomain:kMXKAccountErrorDomain code:0 userInfo:nil]);
|
|
}
|
|
return;
|
|
}
|
|
[self enablePusher:enabled appId:appId token:token pushData:pushData success:^{
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitPusher: Succeeded to update PushKit pusher for %@. Enabled: %@. Token: %@", self.mxCredentials.userId, @(enabled), [MXKTools logForPushToken:token]);
|
|
|
|
self->_hasPusherForPushKitNotifications = enabled;
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountPushKitActivityDidChangeNotification object:self.mxCredentials.userId];
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
// Ignore error if the client try to disable an unknown token
|
|
if (!enabled)
|
|
{
|
|
// Check whether the token was unknown
|
|
MXError *mxError = [[MXError alloc] initWithNSError:error];
|
|
if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringUnknown])
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitPusher: Push was already disabled for %@!", self.mxCredentials.userId);
|
|
|
|
// Ignore the error
|
|
if (success)
|
|
{
|
|
success();
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountPushKitActivityDidChangeNotification object:self.mxCredentials.userId];
|
|
|
|
return;
|
|
}
|
|
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitPusher: Failed to disable Push %@! (%@)", self.mxCredentials.userId, error);
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePushKitPusher: Failed to send Push token for %@! (%@)", self.mxCredentials.userId, error);
|
|
}
|
|
|
|
if (failure)
|
|
{
|
|
failure(error);
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountPushKitActivityDidChangeNotification object:self.mxCredentials.userId];
|
|
}];
|
|
}
|
|
|
|
- (void)enablePusher:(BOOL)enabled appId:(NSString*)appId token:(NSData*)token pushData:(NSDictionary*)pushData success:(void (^)(void))success failure:(void (^)(NSError *))failure
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePusher: %@", @(enabled));
|
|
|
|
// Refuse to try & turn push on if we're not logged in, it's nonsensical.
|
|
if (!self.mxCredentials)
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePusher: Not setting push token because we're not logged in");
|
|
return;
|
|
}
|
|
|
|
// Check whether the Push Gateway URL has been configured.
|
|
if (!self.pushGatewayURL)
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePusher: Not setting pusher because the Push Gateway URL is undefined");
|
|
return;
|
|
}
|
|
|
|
if (!appId)
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePusher: Not setting pusher because pusher app id is undefined");
|
|
return;
|
|
}
|
|
|
|
NSString *appDisplayName = [NSString stringWithFormat:@"%@ (iOS)", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]];
|
|
|
|
NSString *b64Token = [token base64EncodedStringWithOptions:0];
|
|
|
|
NSString *deviceLang = [NSLocale preferredLanguages][0];
|
|
|
|
NSString * profileTag = [[NSUserDefaults standardUserDefaults] valueForKey:@"pusherProfileTag"];
|
|
if (!profileTag)
|
|
{
|
|
profileTag = @"";
|
|
NSString *alphabet = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
for (int i = 0; i < 16; ++i)
|
|
{
|
|
unsigned char c = [alphabet characterAtIndex:arc4random() % alphabet.length];
|
|
profileTag = [profileTag stringByAppendingFormat:@"%c", c];
|
|
}
|
|
MXLogDebug(@"[MXKAccount][Push] enablePusher: Generated fresh profile tag: %@", profileTag);
|
|
[[NSUserDefaults standardUserDefaults] setValue:profileTag forKey:@"pusherProfileTag"];
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccount][Push] enablePusher: Using existing profile tag: %@", profileTag);
|
|
}
|
|
|
|
NSObject *kind = enabled ? @"http" : [NSNull null];
|
|
|
|
// Use the append flag to handle multiple accounts registration.
|
|
BOOL append = NO;
|
|
// Check whether a pusher is running for another account
|
|
NSArray *activeAccounts = [MXKAccountManager sharedManager].activeAccounts;
|
|
for (MXKAccount *account in activeAccounts)
|
|
{
|
|
if (![account.mxCredentials.userId isEqualToString:self.mxCredentials.userId] && account.pushNotificationServiceIsActive)
|
|
{
|
|
append = YES;
|
|
break;
|
|
}
|
|
}
|
|
MXLogDebug(@"[MXKAccount][Push] enablePusher: append flag: %d", append);
|
|
|
|
MXRestClient *restCli = self.mxRestClient;
|
|
|
|
[restCli setPusherWithPushkey:b64Token kind:kind appId:appId appDisplayName:appDisplayName deviceDisplayName:[[UIDevice currentDevice] name] profileTag:profileTag lang:deviceLang data:pushData append:append enabled:enabled success:success failure:failure];
|
|
}
|
|
|
|
#pragma mark - InApp notifications
|
|
|
|
- (void)listenToNotifications:(MXOnNotification)onNotification
|
|
{
|
|
// Check conditions required to add notification listener
|
|
if (!mxSession || !onNotification)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove existing listener (if any)
|
|
[self removeNotificationListener];
|
|
|
|
// Register on notification center
|
|
notificationCenterListener = [self.mxSession.notificationCenter listenToNotifications:^(MXEvent *event, MXRoomState *roomState, MXPushRule *rule)
|
|
{
|
|
// Apply first the event filter defined in the related room data source
|
|
MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self->mxSession];
|
|
[roomDataSourceManager roomDataSourceForRoom:event.roomId create:NO onComplete:^(MXKRoomDataSource *roomDataSource) {
|
|
if (roomDataSource)
|
|
{
|
|
if (!roomDataSource.eventFormatter.eventTypesFilterForMessages || [roomDataSource.eventFormatter.eventTypesFilterForMessages indexOfObject:event.type] != NSNotFound)
|
|
{
|
|
// Check conditions to report this notification
|
|
if (nil == self->ignoredRooms || [self->ignoredRooms indexOfObject:event.roomId] == NSNotFound)
|
|
{
|
|
onNotification(event, roomState, rule);
|
|
}
|
|
}
|
|
}
|
|
}];
|
|
}];
|
|
}
|
|
|
|
- (void)removeNotificationListener
|
|
{
|
|
if (notificationCenterListener)
|
|
{
|
|
[self.mxSession.notificationCenter removeListener:notificationCenterListener];
|
|
notificationCenterListener = nil;
|
|
}
|
|
ignoredRooms = nil;
|
|
}
|
|
|
|
- (void)updateNotificationListenerForRoomId:(NSString*)roomID ignore:(BOOL)isIgnored
|
|
{
|
|
if (isIgnored)
|
|
{
|
|
if (!ignoredRooms)
|
|
{
|
|
ignoredRooms = [[NSMutableArray alloc] init];
|
|
}
|
|
[ignoredRooms addObject:roomID];
|
|
}
|
|
else if (ignoredRooms)
|
|
{
|
|
[ignoredRooms removeObject:roomID];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Internals
|
|
|
|
- (void)launchInitialServerSync
|
|
{
|
|
// Complete the session registration when store data is ready.
|
|
|
|
// Cancel potential reachability observer and pending action
|
|
[[NSNotificationCenter defaultCenter] removeObserver:reachabilityObserver];
|
|
reachabilityObserver = nil;
|
|
|
|
// Sanity check
|
|
if (!mxSession || (mxSession.state != MXSessionStateStoreDataReady && mxSession.state != MXSessionStateInitialSyncFailed))
|
|
{
|
|
MXLogDebug(@"[MXKAccount] Initial server sync is applicable only when store data is ready to complete session initialisation");
|
|
return;
|
|
}
|
|
|
|
// Use /sync filter corresponding to current settings and homeserver capabilities
|
|
MXWeakify(self);
|
|
[self buildSyncFilter:^(MXFilterJSONModel *syncFilter) {
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
// Make sure the filter is compatible with the previously used one
|
|
MXWeakify(self);
|
|
[self checkSyncFilterCompatibility:syncFilter completion:^(BOOL compatible) {
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
if (!compatible)
|
|
{
|
|
// Else clear the cache
|
|
MXLogDebug(@"[MXKAccount] New /sync filter not compatible with previous one. Clear cache");
|
|
|
|
[self reload:YES];
|
|
return;
|
|
}
|
|
|
|
// Launch mxSession
|
|
MXWeakify(self);
|
|
[self.mxSession startWithSyncFilter:syncFilter onServerSyncDone:^{
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
MXLogDebug(@"[MXKAccount] %@: The session is ready. Matrix SDK session has been started in %0.fms.", self.mxCredentials.userId, [[NSDate date] timeIntervalSinceDate:self->openSessionStartDate] * 1000);
|
|
|
|
[self setUserPresence:self.preferredSyncPresence andStatusMessage:nil completion:nil];
|
|
|
|
} failure:^(NSError *error) {
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
MXLogDebug(@"[MXKAccount] Initial Sync failed. Error: %@", error);
|
|
|
|
BOOL isClientTimeout = [error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorTimedOut;
|
|
NSHTTPURLResponse *httpResponse = [MXHTTPOperation urlResponseFromError:error];
|
|
BOOL isServerTimeout = httpResponse && [initialSyncSilentErrorsHTTPStatusCodes containsObject:@(httpResponse.statusCode)];
|
|
|
|
if (isClientTimeout || isServerTimeout)
|
|
{
|
|
// do not propagate this error to the client
|
|
// the request will be retried or postponed according to the reachability status
|
|
MXLogDebug(@"[MXKAccount] Initial sync failure did not propagated");
|
|
}
|
|
else if (self->notifyOpenSessionFailure && error)
|
|
{
|
|
// Notify MatrixKit user only once
|
|
self->notifyOpenSessionFailure = NO;
|
|
NSString *myUserId = self.mxSession.myUser.userId;
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
|
|
}
|
|
|
|
// If we cannot resolve this error by retrying, exit early
|
|
BOOL isRetryableError = [error.domain isEqualToString:NSURLErrorDomain] || [MXHTTPOperation urlResponseFromError:error] != nil;
|
|
if (!isRetryableError)
|
|
{
|
|
MXLogDebug(@"[MXKAccount] Initial sync will not be retried");
|
|
return;
|
|
}
|
|
|
|
// Check if it is a network connectivity issue
|
|
AFNetworkReachabilityManager *networkReachabilityManager = [AFNetworkReachabilityManager sharedManager];
|
|
MXLogDebug(@"[MXKAccount] Network reachability: %d", networkReachabilityManager.isReachable);
|
|
|
|
if (networkReachabilityManager.isReachable)
|
|
{
|
|
// If we have network, we retry immediately, otherwise the server may clear any cache it has computed thus far
|
|
[self launchInitialServerSync];
|
|
}
|
|
else
|
|
{
|
|
// The device is not connected to the internet, wait for the connection to be up again before retrying
|
|
// Add observer to launch a new attempt according to reachability.
|
|
self->reachabilityObserver = [[NSNotificationCenter defaultCenter] addObserverForName:AFNetworkingReachabilityDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
|
|
NSNumber *statusItem = note.userInfo[AFNetworkingReachabilityNotificationStatusItem];
|
|
if (statusItem)
|
|
{
|
|
AFNetworkReachabilityStatus reachabilityStatus = statusItem.integerValue;
|
|
if (reachabilityStatus == AFNetworkReachabilityStatusReachableViaWiFi || reachabilityStatus == AFNetworkReachabilityStatusReachableViaWWAN)
|
|
{
|
|
// New attempt
|
|
[self launchInitialServerSync];
|
|
}
|
|
}
|
|
|
|
}];
|
|
}
|
|
}];
|
|
}];
|
|
}];
|
|
}
|
|
|
|
- (void)onMatrixSessionStateChange
|
|
{
|
|
// Check if pause has been requested
|
|
if (self.isPauseRequested && mxSession.isPauseable)
|
|
{
|
|
MXLogDebug(@"[MXKAccount] Apply the pending pause.");
|
|
[self pauseInBackgroundTask];
|
|
return;
|
|
}
|
|
|
|
if (mxSession.state == MXSessionStateRunning)
|
|
{
|
|
// Check whether the session was not already running
|
|
if (!userUpdateListener)
|
|
{
|
|
// Register listener to user's information change
|
|
userUpdateListener = [mxSession.myUser listenToUserUpdate:^(MXEvent *event) {
|
|
// Consider events related to user's presence
|
|
if (event.eventType == MXEventTypePresence)
|
|
{
|
|
self->userPresence = [MXTools presence:event.content[@"presence"]];
|
|
}
|
|
|
|
// Here displayname or other information have been updated, post update notification.
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountUserInfoDidChangeNotification object:self.mxCredentials.userId];
|
|
}];
|
|
|
|
// User information are just up-to-date (`mxSession` is running), post update notification.
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountUserInfoDidChangeNotification object:self.mxCredentials.userId];
|
|
}
|
|
}
|
|
else if (mxSession.state == MXSessionStateStoreDataReady || mxSession.state == MXSessionStateSyncInProgress)
|
|
{
|
|
// Remove listener (if any), this action is required to handle correctly matrix sdk handler reload (see clear cache)
|
|
if (userUpdateListener)
|
|
{
|
|
[mxSession.myUser removeListener:userUpdateListener];
|
|
userUpdateListener = nil;
|
|
}
|
|
else
|
|
{
|
|
// Here the initial server sync is in progress. The session is not running yet, but some user's information are available (from local storage).
|
|
// We post update notification to let observer take into account this user's information even if they may not be up-to-date.
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountUserInfoDidChangeNotification object:self.mxCredentials.userId];
|
|
}
|
|
}
|
|
else if (mxSession.state == MXSessionStatePaused)
|
|
{
|
|
self.pauseRequested = NO;
|
|
}
|
|
}
|
|
|
|
- (void)prepareRESTClient
|
|
{
|
|
if (!self.mxCredentials)
|
|
{
|
|
return;
|
|
}
|
|
MXWeakify(self);
|
|
mxRestClient = [[MXRestClient alloc] initWithCredentials:self.mxCredentials andOnUnrecognizedCertificateBlock:^BOOL(NSData *certificate) {
|
|
MXStrongifyAndReturnValueIfNil(self, NO);
|
|
if (_onCertificateChangeBlock)
|
|
{
|
|
if (_onCertificateChangeBlock (self, certificate))
|
|
{
|
|
// Update the certificate in credentials
|
|
self.mxCredentials.allowedCertificate = certificate;
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
|
|
return YES;
|
|
}
|
|
|
|
self.mxCredentials.ignoredCertificate = certificate;
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
return NO;
|
|
|
|
} andPersistentTokenDataHandler:^(void (^handler)(NSArray<MXCredentials *> *credentials, void (^completion)(BOOL didUpdateCredentials))) {
|
|
[MXKAccountManager.sharedManager readAndWriteCredentials:handler];
|
|
} andUnauthenticatedHandler:^(MXError *error, BOOL isSoftLogout, BOOL isRefreshTokenAuth, void (^completion)(void)) {
|
|
MXStrongifyAndReturnIfNil(self);
|
|
[self handleUnauthenticatedWithError:error isSoftLogout:isSoftLogout isRefreshTokenAuth:isRefreshTokenAuth andCompletion:completion];
|
|
}];
|
|
}
|
|
|
|
- (void)handleUnauthenticatedWithError:(MXError *)error isSoftLogout:(BOOL)isSoftLogout isRefreshTokenAuth:(BOOL)isRefreshTokenAuth andCompletion:(void (^)(void))completion
|
|
{
|
|
|
|
[Analytics.shared trackAuthUnauthenticatedErrorWithSoftLogout:isSoftLogout refreshTokenAuth:isRefreshTokenAuth errorCode:error.errcode errorReason:error.error];
|
|
MXLogDebug(@"[MXKAccountManager] handleUnauthenticated: trackAuthUnauthenticatedErrorWithSoftLogout sent");
|
|
if (isSoftLogout)
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] handleUnauthenticated: soft logout.");
|
|
[[MXKAccountManager sharedManager] softLogout:self];
|
|
completion();
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] handleUnauthenticated: hard logout.");
|
|
[[MXKAccountManager sharedManager] removeAccount:self sendLogoutRequest:NO completion:completion];
|
|
}
|
|
}
|
|
|
|
- (void)onDateTimeFormatUpdate
|
|
{
|
|
if ([mxSession.roomSummaryUpdateDelegate isKindOfClass:MXKEventFormatter.class])
|
|
{
|
|
MXKEventFormatter *eventFormatter = (MXKEventFormatter*)mxSession.roomSummaryUpdateDelegate;
|
|
|
|
// Update the date and time formatters
|
|
[eventFormatter initDateTimeFormatters];
|
|
|
|
dispatch_group_t dispatchGroup = dispatch_group_create();
|
|
|
|
for (MXRoom *room in mxSession.rooms)
|
|
{
|
|
MXRoomSummary *summary = room.summary;
|
|
if (summary)
|
|
{
|
|
NSString *eventId = summary.lastMessage.eventId;
|
|
if (!eventId)
|
|
{
|
|
MXLogFailure(@"[MXKAccount] onDateTimeFormatUpdate: Missing event id");
|
|
continue;
|
|
}
|
|
|
|
dispatch_group_enter(dispatchGroup);
|
|
[summary.mxSession eventWithEventId:eventId
|
|
inRoom:summary.roomId
|
|
success:^(MXEvent *event) {
|
|
|
|
if (event)
|
|
{
|
|
if (summary.lastMessage.others == nil)
|
|
{
|
|
summary.lastMessage.others = [NSMutableDictionary dictionary];
|
|
}
|
|
summary.lastMessage.others[@"lastEventDate"] = [eventFormatter dateStringFromEvent:event withTime:YES];
|
|
[self->mxSession.store.roomSummaryStore storeSummary:summary];
|
|
}
|
|
|
|
dispatch_group_leave(dispatchGroup);
|
|
} failure:^(NSError *error) {
|
|
MXLogErrorDetails(@"[MXKAccount] onDateTimeFormatUpdate: event fetch failed", @{
|
|
@"error": error ?: @"unknown"
|
|
});
|
|
dispatch_group_leave(dispatchGroup);
|
|
}];
|
|
}
|
|
}
|
|
|
|
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
|
|
|
|
// Commit store changes done
|
|
if ([self->mxSession.store respondsToSelector:@selector(commit)])
|
|
{
|
|
[self->mxSession.store commit];
|
|
}
|
|
|
|
// Broadcast the change which concerns all the room summaries.
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXRoomSummaryDidChangeNotification object:nil userInfo:nil];
|
|
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)cancelPauseBackgroundTask
|
|
{
|
|
// Cancel background task
|
|
if (self.pauseBackgroundTask.isRunning)
|
|
{
|
|
[self.pauseBackgroundTask stop];
|
|
self.pauseBackgroundTask = nil;
|
|
}
|
|
}
|
|
|
|
#pragma mark - Crypto
|
|
- (void)resetDeviceId
|
|
{
|
|
self.mxCredentials.deviceId = nil;
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
|
|
#pragma mark - backgroundSync management
|
|
|
|
- (void)cancelBackgroundSync
|
|
{
|
|
if (self.backgroundSyncBgTask.isRunning)
|
|
{
|
|
MXLogDebug(@"[MXKAccount] The background Sync is cancelled.");
|
|
|
|
if (mxSession)
|
|
{
|
|
if (mxSession.state == MXSessionStateBackgroundSyncInProgress)
|
|
{
|
|
[mxSession pause];
|
|
}
|
|
}
|
|
|
|
[self onBackgroundSyncDone:[NSError errorWithDomain:kMXKAccountErrorDomain code:0 userInfo:nil]];
|
|
}
|
|
}
|
|
|
|
- (void)onBackgroundSyncDone:(NSError*)error
|
|
{
|
|
if (backgroundSyncTimer)
|
|
{
|
|
[backgroundSyncTimer invalidate];
|
|
backgroundSyncTimer = nil;
|
|
}
|
|
|
|
if (backgroundSyncFails && error)
|
|
{
|
|
backgroundSyncFails(error);
|
|
}
|
|
|
|
if (backgroundSyncDone && !error)
|
|
{
|
|
backgroundSyncDone();
|
|
}
|
|
|
|
backgroundSyncDone = nil;
|
|
backgroundSyncFails = nil;
|
|
|
|
// End background task
|
|
if (self.backgroundSyncBgTask.isRunning)
|
|
{
|
|
[self.backgroundSyncBgTask stop];
|
|
self.backgroundSyncBgTask = nil;
|
|
}
|
|
}
|
|
|
|
- (void)onBackgroundSyncTimerOut
|
|
{
|
|
[self cancelBackgroundSync];
|
|
}
|
|
|
|
- (void)backgroundSync:(unsigned int)timeout success:(void (^)(void))success failure:(void (^)(NSError *))failure
|
|
{
|
|
// Check whether a background mode handler has been set.
|
|
id<MXBackgroundModeHandler> handler = [MXSDKOptions sharedInstance].backgroundModeHandler;
|
|
if (handler)
|
|
{
|
|
// Only work when the application is suspended.
|
|
// Check conditions before launching background sync
|
|
if (mxSession && mxSession.state == MXSessionStatePaused)
|
|
{
|
|
MXLogDebug(@"[MXKAccount] starts a background Sync");
|
|
|
|
backgroundSyncDone = success;
|
|
backgroundSyncFails = failure;
|
|
|
|
MXWeakify(self);
|
|
|
|
self.backgroundSyncBgTask = [handler startBackgroundTaskWithName:@"[MXKAccount] backgroundSync:success:failure:" expirationHandler:^{
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
MXLogDebug(@"[MXKAccount] the background Sync fails because of the bg task timeout");
|
|
[self cancelBackgroundSync];
|
|
}];
|
|
|
|
// ensure that the backgroundSync will be really done in the expected time
|
|
// the request could be done but the treatment could be long so add a timer to cancel it
|
|
// if it takes too much time
|
|
backgroundSyncTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:(timeout - 1) / 1000]
|
|
interval:0
|
|
target:self
|
|
selector:@selector(onBackgroundSyncTimerOut)
|
|
userInfo:nil
|
|
repeats:NO];
|
|
|
|
[[NSRunLoop mainRunLoop] addTimer:backgroundSyncTimer forMode:NSDefaultRunLoopMode];
|
|
|
|
[mxSession backgroundSync:timeout success:^{
|
|
MXLogDebug(@"[MXKAccount] the background Sync succeeds");
|
|
[self onBackgroundSyncDone:nil];
|
|
|
|
}
|
|
failure:^(NSError* error) {
|
|
|
|
MXLogDebug(@"[MXKAccount] the background Sync fails");
|
|
[self onBackgroundSyncDone:error];
|
|
|
|
}
|
|
|
|
];
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccount] cannot start background Sync (invalid state %@)", [MXTools readableSessionState:mxSession.state]);
|
|
failure([NSError errorWithDomain:kMXKAccountErrorDomain code:0 userInfo:nil]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccount] cannot start background Sync");
|
|
failure([NSError errorWithDomain:kMXKAccountErrorDomain code:0 userInfo:nil]);
|
|
}
|
|
}
|
|
|
|
#pragma mark - Sync filter
|
|
|
|
- (void)supportLazyLoadOfRoomMembersWithMatrixVersion:(MXMatrixVersions *)matrixVersions
|
|
completion:(void (^)(BOOL supportLazyLoadOfRoomMembers))completion
|
|
{
|
|
void(^onUnsupportedLazyLoadOfRoomMembers)(NSError *) = ^(NSError *error) {
|
|
completion(NO);
|
|
};
|
|
|
|
// Check if the server supports LL sync filter
|
|
MXFilterJSONModel *filter = [self syncFilterWithLazyLoadOfRoomMembers:YES supportsNotificationsForThreads:NO];
|
|
[mxSession.store filterIdForFilter:filter success:^(NSString * _Nullable filterId) {
|
|
|
|
if (filterId)
|
|
{
|
|
// The LL filter is already in the store. The HS supports LL
|
|
completion(YES);
|
|
}
|
|
else
|
|
{
|
|
// Check the Matrix versions supported by the HS
|
|
if (matrixVersions)
|
|
{
|
|
if (matrixVersions.supportLazyLoadMembers)
|
|
{
|
|
// The HS supports LL
|
|
completion(YES);
|
|
}
|
|
else
|
|
{
|
|
onUnsupportedLazyLoadOfRoomMembers(nil);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
completion(NO);
|
|
}
|
|
}
|
|
} failure:onUnsupportedLazyLoadOfRoomMembers];
|
|
}
|
|
|
|
/**
|
|
Build the sync filter according to application settings and HS capability.
|
|
|
|
@param completion the block providing the sync filter to use.
|
|
*/
|
|
- (void)buildSyncFilter:(void (^)(MXFilterJSONModel *syncFilter))completion
|
|
{
|
|
// Check settings
|
|
BOOL syncWithLazyLoadOfRoomMembersSetting = [MXKAppSettings standardAppSettings].syncWithLazyLoadOfRoomMembers;
|
|
|
|
void(^buildSyncFilter)(MXMatrixVersions *) = ^(MXMatrixVersions *matrixVersions) {
|
|
BOOL supportsNotificationsForThreads = matrixVersions ? matrixVersions.supportsNotificationsForThreads : NO;
|
|
|
|
if (syncWithLazyLoadOfRoomMembersSetting)
|
|
{
|
|
// Check if the server supports LL sync filter before enabling it
|
|
[self supportLazyLoadOfRoomMembersWithMatrixVersion:matrixVersions completion:^(BOOL supportLazyLoadOfRoomMembers) {
|
|
|
|
|
|
if (supportLazyLoadOfRoomMembers)
|
|
{
|
|
completion([self syncFilterWithLazyLoadOfRoomMembers:YES
|
|
supportsNotificationsForThreads:supportsNotificationsForThreads]);
|
|
}
|
|
else
|
|
{
|
|
// No support from the HS
|
|
// Disable the setting. That will avoid to make a request at every startup
|
|
[MXKAppSettings standardAppSettings].syncWithLazyLoadOfRoomMembers = NO;
|
|
completion([self syncFilterWithLazyLoadOfRoomMembers:NO
|
|
supportsNotificationsForThreads:supportsNotificationsForThreads]);
|
|
}
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
completion([self syncFilterWithLazyLoadOfRoomMembers:NO supportsNotificationsForThreads:supportsNotificationsForThreads]);
|
|
}
|
|
};
|
|
|
|
[mxSession supportedMatrixVersions:^(MXMatrixVersions *matrixVersions) {
|
|
buildSyncFilter(matrixVersions);
|
|
} failure:^(NSError *error) {
|
|
MXLogWarning(@"[MXAccount] buildSyncFilter: failed to get supported versions: %@", error);
|
|
buildSyncFilter(nil);
|
|
}];
|
|
}
|
|
|
|
/**
|
|
Compute the sync filter to use according to the device screen size.
|
|
|
|
@param syncWithLazyLoadOfRoomMembers enable LL support.
|
|
@return the sync filter to use.
|
|
*/
|
|
- (MXFilterJSONModel *)syncFilterWithLazyLoadOfRoomMembers:(BOOL)syncWithLazyLoadOfRoomMembers supportsNotificationsForThreads:(BOOL)supportsNotificationsForThreads
|
|
{
|
|
MXFilterJSONModel *syncFilter;
|
|
NSUInteger limit = 10;
|
|
|
|
// Define a message limit for /sync requests that is high enough so that
|
|
// a full page of room messages can be displayed without an additional
|
|
// server request.
|
|
|
|
// This limit value depends on the device screen size. So, the rough rule is:
|
|
// - use 10 for small phones (5S/SE)
|
|
// - use 15 for phones (6/6S/7/8)
|
|
// - use 20 for phablets (.Plus/X/XR/XS/XSMax)
|
|
// - use 30 for iPads
|
|
UIUserInterfaceIdiom userInterfaceIdiom = [[UIDevice currentDevice] userInterfaceIdiom];
|
|
if (userInterfaceIdiom == UIUserInterfaceIdiomPhone)
|
|
{
|
|
CGFloat screenHeight = [[UIScreen mainScreen] nativeBounds].size.height;
|
|
if (screenHeight == 1334) // 6/6S/7/8 screen height
|
|
{
|
|
limit = 15;
|
|
}
|
|
else if (screenHeight > 1334)
|
|
{
|
|
limit = 20;
|
|
}
|
|
}
|
|
else if (userInterfaceIdiom == UIUserInterfaceIdiomPad)
|
|
{
|
|
limit = 30;
|
|
}
|
|
|
|
// Set that limit in the filter
|
|
if (syncWithLazyLoadOfRoomMembers)
|
|
{
|
|
syncFilter = [MXFilterJSONModel syncFilterForLazyLoadingWithMessageLimit:limit unreadThreadNotifications:supportsNotificationsForThreads];
|
|
}
|
|
else
|
|
{
|
|
syncFilter = [MXFilterJSONModel syncFilterWithMessageLimit:limit unreadThreadNotifications:supportsNotificationsForThreads];
|
|
}
|
|
|
|
// TODO: We could extend the filter to match other settings (self.showAllEventsInRoomHistory,
|
|
// self.eventsFilterForMessages, etc).
|
|
|
|
return syncFilter;
|
|
}
|
|
|
|
|
|
/**
|
|
Check the sync filter we want to use is compatible with the one previously used.
|
|
|
|
@param syncFilter the sync filter to use.
|
|
@param completion the block called to indicated the compatibility.
|
|
*/
|
|
- (void)checkSyncFilterCompatibility:(MXFilterJSONModel*)syncFilter completion:(void (^)(BOOL compatible))completion
|
|
{
|
|
// There is no compatibility issue if no /sync was done before
|
|
if (!mxSession.store.eventStreamToken)
|
|
{
|
|
completion(YES);
|
|
}
|
|
|
|
// Check the filter we want to use is compatible with the one previously used
|
|
else if (!syncFilter && !mxSession.syncFilterId)
|
|
{
|
|
// A nil filter implies a nil mxSession.syncFilterId. So, there is no filter change
|
|
completion(YES);
|
|
}
|
|
else if (!syncFilter || !mxSession.syncFilterId)
|
|
{
|
|
// Change from no filter with using a filter or vice-versa. So, there is a filter change
|
|
MXLogDebug(@"[MXKAccount] checkSyncFilterCompatibility: Incompatible filter. New or old is nil. mxSession.syncFilterId: %@ - syncFilter: %@",
|
|
mxSession.syncFilterId, syncFilter.JSONDictionary);
|
|
completion(NO);
|
|
}
|
|
else if (!mxSession.store.allFilterIds.count)
|
|
{
|
|
MXLogDebug(@"[MXKAccount] There are no filters stored in this session, proceed as if no /sync was done before");
|
|
completion(YES);
|
|
}
|
|
else
|
|
{
|
|
// Check the filter is the one previously set
|
|
// It must be already in the store
|
|
MXWeakify(self);
|
|
[mxSession.store filterIdForFilter:syncFilter success:^(NSString * _Nullable filterId) {
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
// Note: We could be more tolerant here
|
|
// We could accept filter hot change if the change is limited to the `limit` filter value
|
|
// But we do not have this requirement yet
|
|
BOOL compatible = [filterId isEqualToString:self.mxSession.syncFilterId];
|
|
if (!compatible)
|
|
{
|
|
MXLogDebug(@"[MXKAccount] checkSyncFilterCompatibility: Incompatible filter ids. mxSession.syncFilterId: %@ - store.filterId: %@ - syncFilter: %@",
|
|
self.mxSession.syncFilterId, filterId, syncFilter.JSONDictionary);
|
|
}
|
|
completion(compatible);
|
|
|
|
} failure:^(NSError * _Nullable error) {
|
|
// Should never happen
|
|
completion(NO);
|
|
}];
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark - Identity server updates
|
|
|
|
- (void)registerAccountDataDidChangeIdentityServerNotification
|
|
{
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAccountDataDidChangeIdentityServerNotification:) name:kMXSessionAccountDataDidChangeIdentityServerNotification object:nil];
|
|
}
|
|
|
|
- (void)handleAccountDataDidChangeIdentityServerNotification:(NSNotification*)notification
|
|
{
|
|
MXSession *mxSession = notification.object;
|
|
if (mxSession == self.mxSession)
|
|
{
|
|
if (![self.mxCredentials.identityServer isEqualToString:self.mxSession.accountDataIdentityServer])
|
|
{
|
|
_identityServerURL = self.mxSession.accountDataIdentityServer;
|
|
self.mxCredentials.identityServer = _identityServerURL;
|
|
self.mxCredentials.identityServerAccessToken = nil;
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark - Identity server Access Token updates
|
|
|
|
- (void)identityService:(MXIdentityService *)identityService didUpdateAccessToken:(NSString *)accessToken
|
|
{
|
|
self.mxCredentials.identityServerAccessToken = accessToken;
|
|
}
|
|
|
|
- (void)registerIdentityServiceDidChangeAccessTokenNotification
|
|
{
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleIdentityServiceDidChangeAccessTokenNotification:) name:MXIdentityServiceDidChangeAccessTokenNotification object:nil];
|
|
}
|
|
|
|
- (void)handleIdentityServiceDidChangeAccessTokenNotification:(NSNotification*)notification
|
|
{
|
|
NSDictionary *userInfo = notification.userInfo;
|
|
|
|
NSString *userId = userInfo[MXIdentityServiceNotificationUserIdKey];
|
|
NSString *identityServer = userInfo[MXIdentityServiceNotificationIdentityServerKey];
|
|
NSString *accessToken = userInfo[MXIdentityServiceNotificationAccessTokenKey];
|
|
|
|
if (userId && identityServer && accessToken && [self.mxCredentials.identityServer isEqualToString:identityServer])
|
|
{
|
|
self.mxCredentials.identityServerAccessToken = accessToken;
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Presence
|
|
|
|
- (void)setPreferredSyncPresence:(MXPresence)preferredSyncPresence
|
|
{
|
|
[super setPreferredSyncPresence:preferredSyncPresence];
|
|
|
|
if (self.mxSession)
|
|
{
|
|
self.mxSession.preferredSyncPresence = preferredSyncPresence;
|
|
[self setUserPresence:preferredSyncPresence andStatusMessage:nil completion:nil];
|
|
}
|
|
|
|
// Archive updated field
|
|
[[MXKAccountManager sharedManager] saveAccounts];
|
|
}
|
|
|
|
@end
|