Merge MatrixKit develop with commit hash: b85b736313bec0592bd1cabc68035d97f5331137

This commit is contained in:
SBiOSoftWhare
2021-12-03 11:47:24 +01:00
parent af65f71177
commit f57941177e
475 changed files with 87437 additions and 0 deletions
@@ -0,0 +1,435 @@
/*
Copyright 2015 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2019 The Matrix.org Foundation C.I.C
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 <MatrixSDK/MatrixSDK.h>
@class MXKAccount;
/**
Posted when account user information (display name, picture, presence) has been updated.
The notification object is the matrix user id of the account.
*/
extern NSString *const kMXKAccountUserInfoDidChangeNotification;
/**
Posted when the activity of the Apple Push Notification Service has been changed.
The notification object is the matrix user id of the account.
*/
extern NSString *const kMXKAccountAPNSActivityDidChangeNotification;
/**
Posted when the activity of the Push notification based on PushKit has been changed.
The notification object is the matrix user id of the account.
*/
extern NSString *const kMXKAccountPushKitActivityDidChangeNotification;
/**
MXKAccount error domain
*/
extern NSString *const kMXKAccountErrorDomain;
/**
Block called when a certificate change is observed during authentication challenge from a server.
@param mxAccount the account concerned by this certificate change.
@param certificate the server certificate to evaluate.
@return YES to accept/trust this certificate, NO to cancel/ignore it.
*/
typedef BOOL (^MXKAccountOnCertificateChange)(MXKAccount *mxAccount, NSData *certificate);
/**
`MXKAccount` object contains the credentials of a logged matrix user. It is used to handle matrix
session and presence for this user.
*/
@interface MXKAccount : NSObject <NSCoding>
/**
The account's credentials: homeserver, access token, user id.
*/
@property (nonatomic, readonly) MXCredentials *mxCredentials;
/**
The identity server URL.
*/
@property (nonatomic) NSString *identityServerURL;
/**
The antivirus server URL, if any (nil by default).
Set a non-null url to configure the antivirus scanner use.
*/
@property (nonatomic) NSString *antivirusServerURL;
/**
The Push Gateway URL used to send event notifications to (nil by default).
This URL should be over HTTPS and never over HTTP.
*/
@property (nonatomic) NSString *pushGatewayURL;
/**
The matrix REST client used to make matrix API requests.
*/
@property (nonatomic, readonly) MXRestClient *mxRestClient;
/**
The matrix session opened with the account (nil by default).
*/
@property (nonatomic, readonly) MXSession *mxSession;
/**
The account user's display name (nil by default, available if matrix session `mxSession` is opened).
The notification `kMXKAccountUserInfoDidChangeNotification` is posted in case of change of this property.
*/
@property (nonatomic, readonly) NSString *userDisplayName;
/**
The account user's avatar url (nil by default, available if matrix session `mxSession` is opened).
The notification `kMXKAccountUserInfoDidChangeNotification` is posted in case of change of this property.
*/
@property (nonatomic, readonly) NSString *userAvatarUrl;
/**
The account display name based on user id and user displayname (if any).
*/
@property (nonatomic, readonly) NSString *fullDisplayName;
/**
The 3PIDs linked to this account.
[self load3PIDs] must be called to update the property.
*/
@property (nonatomic, readonly) NSArray<MXThirdPartyIdentifier *> *threePIDs;
/**
The email addresses linked to this account.
This is a subset of self.threePIDs.
*/
@property (nonatomic, readonly) NSArray<NSString *> *linkedEmails;
/**
The phone numbers linked to this account.
This is a subset of self.threePIDs.
*/
@property (nonatomic, readonly) NSArray<NSString *> *linkedPhoneNumbers;
/**
The account user's device.
[self loadDeviceInformation] must be called to update the property.
*/
@property (nonatomic, readonly) MXDevice *device;
/**
The account user's presence (`MXPresenceUnknown` by default, available if matrix session `mxSession` is opened).
The notification `kMXKAccountUserInfoDidChangeNotification` is posted in case of change of this property.
*/
@property (nonatomic, readonly) MXPresence userPresence;
/**
The account user's tint color: a unique color fixed by the user id. This tint color may be used to highlight
rooms which belong to this account's user.
*/
@property (nonatomic, readonly) UIColor *userTintColor;
/**
The Apple Push Notification Service activity for this account. YES when APNS is turned on (locally available and synced with server).
*/
@property (nonatomic, readonly) BOOL pushNotificationServiceIsActive;
/**
Transient information storage.
*/
@property (nonatomic, strong, readonly) NSMutableDictionary<NSString *, id<NSCoding>> *others;
/**
Enable Push notification based on Apple Push Notification Service (APNS).
This method creates or removes a pusher on the homeserver.
@param enable YES to enable it.
@param success A block object called when the operation succeeds.
@param failure A block object called when the operation fails.
*/
- (void)enablePushNotifications:(BOOL)enable
success:(void (^)(void))success
failure:(void (^)(NSError *))failure;
/**
Flag to indicate that an APNS pusher has been set on the homeserver for this device.
*/
@property (nonatomic, readonly) BOOL hasPusherForPushNotifications;
/**
The Push notification activity (based on PushKit) for this account.
YES when Push is turned on (locally available and enabled homeserver side).
*/
@property (nonatomic, readonly) BOOL isPushKitNotificationActive;
/**
Enable Push notification based on PushKit.
This method creates or removes a pusher on the homeserver.
@param enable YES to enable it.
@param success A block object called when the operation succeeds.
@param failure A block object called when the operation fails.
*/
- (void)enablePushKitNotifications:(BOOL)enable
success:(void (^)(void))success
failure:(void (^)(NSError *))failure;
/**
Flag to indicate that a PushKit pusher has been set on the homeserver for this device.
*/
@property (nonatomic, readonly) BOOL hasPusherForPushKitNotifications;
/**
Enable In-App notifications based on Remote notifications rules.
NO by default.
*/
@property (nonatomic) BOOL enableInAppNotifications;
/**
Disable the account without logging out (NO by default).
A matrix session is automatically opened for the account when this property is toggled from YES to NO.
The session is closed when this property is set to YES.
*/
@property (nonatomic,getter=isDisabled) BOOL disabled;
/**
Manage the online presence event.
The presence event must not be sent if the application is launched by a push notification.
*/
@property (nonatomic) BOOL hideUserPresence;
/**
Flag indicating if the end user has been warned about encryption and its limitations.
*/
@property (nonatomic,getter=isWarnedAboutEncryption) BOOL warnedAboutEncryption;
/**
Register the MXKAccountOnCertificateChange block that will be used to handle certificate change during account use.
This block is nil by default, any new certificate is ignored/untrusted (this will abort the connection to the server).
@param onCertificateChangeBlock the block that will be used to handle certificate change.
*/
+ (void)registerOnCertificateChangeBlock:(MXKAccountOnCertificateChange)onCertificateChangeBlock;
/**
Get the color code related to a specific presence.
@param presence a user presence
@return color defined for the provided presence (nil if no color is defined).
*/
+ (UIColor*)presenceColor:(MXPresence)presence;
/**
Init `MXKAccount` instance with credentials. No matrix session is opened by default.
@param credentials user's credentials
*/
- (instancetype)initWithCredentials:(MXCredentials*)credentials;
/**
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;
/**
Close the matrix session.
@param clearStore set YES to delete all store data.
*/
- (void)closeSession:(BOOL)clearStore;
/**
Invalidate the access token, close the matrix session and delete all store data.
@note This method is equivalent to `logoutSendingServerRequest:completion:` with `sendLogoutServerRequest` parameter to YES
@param completion the block to execute at the end of the operation (independently if it succeeded or not).
*/
- (void)logout:(void (^)(void))completion;
/**
Invalidate the access token, close the matrix session and delete all store data.
@param sendLogoutServerRequest indicate to send logout request to homeserver.
@param completion the block to execute at the end of the operation (independently if it succeeded or not).
*/
- (void)logoutSendingServerRequest:(BOOL)sendLogoutServerRequest
completion:(void (^)(void))completion;
#pragma mark - Soft logout
/**
Flag to indicate if the account has been logged out by the homeserver admin.
*/
@property (nonatomic, readonly) BOOL isSoftLogout;
/**
Soft logout the account.
The matix session is stopped but the data is kept.
*/
- (void)softLogout;
/**
Hydrate the account using the credentials provided.
@param credentials the new credentials.
*/
- (void)hydrateWithCredentials:(MXCredentials*)credentials;
/**
Pause the current matrix session.
@warning: This matrix session is paused without using background task if no background mode handler
is set in the MXSDKOptions sharedInstance (see `backgroundModeHandler`).
*/
- (void)pauseInBackgroundTask;
/**
Perform a background sync by keeping the user offline.
@warning: This operation failed when no background mode handler is set in the
MXSDKOptions sharedInstance (see `backgroundModeHandler`).
@param timeout the timeout in milliseconds.
@param success A block object called when the operation succeeds.
@param failure A block object called when the operation fails.
*/
- (void)backgroundSync:(unsigned int)timeout success:(void (^)(void))success failure:(void (^)(NSError *))failure;
/**
Resume the current matrix session.
*/
- (void)resume;
/**
Close the potential matrix session and open a new one if the account is not disabled.
@param clearCache set YES to delete all store data.
*/
- (void)reload:(BOOL)clearCache;
/**
Set the display name of the account user.
@param displayname the new display name.
@param success A block object called when the operation succeeds.
@param failure A block object called when the operation fails.
*/
- (void)setUserDisplayName:(NSString*)displayname success:(void (^)(void))success failure:(void (^)(NSError *error))failure;
/**
Set the avatar url of the account user.
@param avatarUrl the new avatar url.
@param success A block object called when the operation succeeds.
@param failure A block object called when the operation fails.
*/
- (void)setUserAvatarUrl:(NSString*)avatarUrl success:(void (^)(void))success failure:(void (^)(NSError *error))failure;
/**
Update the account password.
@param oldPassword the old password.
@param newPassword the new password.
@param success A block object called when the operation succeeds.
@param failure A block object called when the operation fails.
*/
- (void)changePassword:(NSString*)oldPassword with:(NSString*)newPassword success:(void (^)(void))success failure:(void (^)(NSError *error))failure;
/**
Load the 3PIDs linked to this account.
This method must be called to refresh self.threePIDs and self.linkedEmails.
@param success A block object called when the operation succeeds.
@param failure A block object called when the operation fails.
*/
- (void)load3PIDs:(void (^)(void))success failure:(void (^)(NSError *error))failure;
/**
Load the current device information for this account.
This method must be called to refresh self.device.
@param success A block object called when the operation succeeds.
@param failure A block object called when the operation fails.
*/
- (void)loadDeviceInformation:(void (^)(void))success failure:(void (^)(NSError *error))failure;
#pragma mark - Push notification listeners
/**
Register a listener to push notifications for the account's session.
The listener will be called when a push rule matches a live event.
Note: only one listener is supported. Potential existing listener is removed.
You may use `[MXCAccount updateNotificationListenerForRoomId:]` to disable/enable all notifications from a specific room.
@param onNotification the block that will be called once a live event matches a push rule.
*/
- (void)listenToNotifications:(MXOnNotification)onNotification;
/**
Unregister the listener.
*/
- (void)removeNotificationListener;
/**
Update the listener to ignore or restore notifications from a specific room.
@param roomID the id of the concerned room.
@param isIgnored YES to disable notifications from the specified room. NO to restore them.
*/
- (void)updateNotificationListenerForRoomId:(NSString*)roomID ignore:(BOOL)isIgnored;
#pragma mark - Crypto
/**
Delete the device id.
Call this method when the current device id cannot be used anymore.
*/
- (void)resetDeviceId;
#pragma mark - Sync filter
/**
Check if the homeserver supports room members lazy loading.
@param completion the check result.
*/
- (void)supportLazyLoadOfRoomMembers:(void (^)(BOOL supportLazyLoadOfRoomMembers))completion;
/**
Call this method at an appropriate time to attempt dehydrating to a new backup device
*/
- (void)attemptDeviceDehydrationWithKeyData:(NSData *)keyData
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure;
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,218 @@
/*
Copyright 2015 OpenMarket Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "MXKAccount.h"
/**
Posted when the user logged in with a matrix account.
The notification object is the new added account.
*/
extern NSString *const kMXKAccountManagerDidAddAccountNotification;
/**
Posted when an existing account is logged out.
The notification object is the removed account.
*/
extern NSString *const kMXKAccountManagerDidRemoveAccountNotification;
/**
Posted when an existing account is soft logged out.
The notification object is the account.
*/
extern NSString *const kMXKAccountManagerDidSoftlogoutAccountNotification;
/**
Used to identify the type of data when requesting MXKeyProvider
*/
extern NSString *const MXKAccountManagerDataType;
/**
`MXKAccountManager` manages a pool of `MXKAccount` instances.
*/
@interface MXKAccountManager : NSObject
/**
The class of store used to open matrix session for the accounts. This class must be conformed to MXStore protocol.
By default this class is MXFileStore.
*/
@property (nonatomic) Class storeClass;
/**
List of all available accounts (enabled and disabled).
*/
@property (nonatomic, readonly) NSArray<MXKAccount *> *accounts;
/**
List of active accounts (only enabled accounts)
*/
@property (nonatomic, readonly) NSArray<MXKAccount *> *activeAccounts;
/**
The device token used for Apple Push Notification Service registration.
*/
@property (nonatomic, copy) NSData *apnsDeviceToken;
/**
The APNS status: YES when app is registered for remote notif, and device token is known.
*/
@property (nonatomic) BOOL isAPNSAvailable;
/**
The device token used for Push notifications registration (PushKit support).
*/
@property (nonatomic, copy, readonly) NSData *pushDeviceToken;
/**
The current options of the Push notifications based on PushKit.
*/
@property (nonatomic, copy, readonly) NSDictionary *pushOptions;
/**
Set the push token and the potential push options.
For example, for clients that want to go & fetch the body of the event themselves anyway,
the key-value `format: event_id_only` may be used in `pushOptions` dictionary to tell the
HTTP pusher to send just the event_id of the event it's notifying about, the room id and
the notification counts.
@param pushDeviceToken the push token.
@param pushOptions dictionary of the push options (may be nil).
*/
- (void)setPushDeviceToken:(NSData *)pushDeviceToken withPushOptions:(NSDictionary *)pushOptions;
/**
The PushKit status: YES when app is registered for push notif, and push token is known.
*/
@property (nonatomic) BOOL isPushAvailable;
@property (nonatomic, readonly) MXDehydrationService *dehydrationService;
/**
Retrieve the MXKAccounts manager.
@return the MXKAccounts manager.
*/
+ (MXKAccountManager *)sharedManager;
/**
Check for each enabled account if a matrix session is already opened.
Open a matrix session for each enabled account which doesn't have a session.
The developper must set 'storeClass' before the first call of this method
if the default class is not suitable.
*/
- (void)prepareSessionForActiveAccounts;
/**
Save a snapshot of the current accounts.
*/
- (void)saveAccounts;
/**
Add an account and save the new account list. Optionally a matrix session may be opened for the provided account.
@param account a matrix account.
@param openSession YES to open a matrix session (this value is ignored if the account is disabled).
*/
- (void)addAccount:(MXKAccount *)account andOpenSession:(BOOL)openSession;
/**
Remove the provided account and save the new account list. This method is used in case of logout.
@note equivalent to `removeAccount:sendLogoutRequest:completion:` method with `sendLogoutRequest` parameter to YES
@param account a matrix account.
@param completion the block to execute at the end of the operation.
*/
- (void)removeAccount:(MXKAccount *)account completion:(void (^)(void))completion;
/**
Remove the provided account and save the new account list. This method is used in case of logout or account deactivation.
@param account a matrix account.
@param sendLogoutRequest Indicate whether send logout request to homeserver.
@param completion the block to execute at the end of the operation.
*/
- (void)removeAccount:(MXKAccount*)account
sendLogoutRequest:(BOOL)sendLogoutRequest
completion:(void (^)(void))completion;
/**
Log out and remove all the existing accounts
@param completion the block to execute at the end of the operation.
*/
- (void)logoutWithCompletion:(void (^)(void))completion;
/**
Soft logout an account.
@param account a matrix account.
*/
- (void)softLogout:(MXKAccount*)account;
/**
Hydrate an existing account by using the credentials provided.
This updates account credentials and restarts the account session
If the credentials belong to a different user from the account already stored,
the old account will be cleared automatically.
@param account a matrix account.
@param credentials the new credentials.
*/
- (void)hydrateAccount:(MXKAccount*)account withCredentials:(MXCredentials*)credentials;
/**
Retrieve the account for a user id.
@param userId the user id.
@return the user's account (nil if no account exist).
*/
- (MXKAccount *)accountForUserId:(NSString *)userId;
/**
Retrieve an account that knows the room with the passed id or alias.
Note: The method is not accurate as it returns the first account that matches.
@param roomIdOrAlias the room id or alias.
@return the user's account. Nil if no account matches.
*/
- (MXKAccount *)accountKnowingRoomWithRoomIdOrAlias:(NSString *)roomIdOrAlias;
/**
Retrieve an account that knows the user with the passed id.
Note: The method is not accurate as it returns the first account that matches.
@param userId the user id.
@return the user's account. Nil if no account matches.
*/
- (MXKAccount *)accountKnowingUserWithUserId:(NSString *)userId;
/**
Force the account manager to reload existing accounts from the local storage.
The account manager is supposed to handle itself the list of the accounts.
Call this method only when an account has been changed from an other application from the same group.
*/
- (void)forceReloadAccounts;
@end
@@ -0,0 +1,726 @@
/*
Copyright 2015 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C
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 "MXKAccountManager.h"
#import "MXKAppSettings.h"
#import "MXKTools.h"
static NSString *const kMXKAccountsKeyOld = @"accounts";
static NSString *const kMXKAccountsKey = @"accountsV2";
NSString *const kMXKAccountManagerDidAddAccountNotification = @"kMXKAccountManagerDidAddAccountNotification";
NSString *const kMXKAccountManagerDidRemoveAccountNotification = @"kMXKAccountManagerDidRemoveAccountNotification";
NSString *const kMXKAccountManagerDidSoftlogoutAccountNotification = @"kMXKAccountManagerDidSoftlogoutAccountNotification";
NSString *const MXKAccountManagerDataType = @"org.matrix.kit.MXKAccountManagerDataType";
@interface MXKAccountManager()
{
/**
The list of all accounts (enabled and disabled). Each value is a `MXKAccount` instance.
*/
NSMutableArray<MXKAccount *> *mxAccounts;
}
@end
@implementation MXKAccountManager
+ (MXKAccountManager *)sharedManager
{
static MXKAccountManager *sharedAccountManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedAccountManager = [[super allocWithZone:NULL] init];
});
return sharedAccountManager;
}
- (instancetype)init
{
self = [super init];
if (self)
{
_storeClass = [MXFileStore class];
_dehydrationService = [MXDehydrationService new];
// Migrate old account file to new format
[self migrateAccounts];
// Load existing accounts from local storage
[self loadAccounts];
}
return self;
}
- (void)dealloc
{
mxAccounts = nil;
}
#pragma mark -
- (void)prepareSessionForActiveAccounts
{
for (MXKAccount *account in mxAccounts)
{
// Check whether the account is enabled. Open a new matrix session if none.
if (!account.isDisabled && !account.isSoftLogout && !account.mxSession)
{
MXLogDebug(@"[MXKAccountManager] openSession for %@ account", account.mxCredentials.userId);
id<MXStore> store = [[_storeClass alloc] init];
[account openSessionWithStore:store];
}
}
}
- (void)saveAccounts
{
NSDate *startDate = [NSDate date];
MXLogDebug(@"[MXKAccountManager] saveAccounts...");
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[encoder encodeObject:mxAccounts forKey:@"mxAccounts"];
[encoder finishEncoding];
[data setData:[self encryptData:data]];
BOOL result = [data writeToFile:[self accountFile] atomically:YES];
MXLogDebug(@"[MXKAccountManager] saveAccounts. Done (result: %@) in %.0fms", @(result), [[NSDate date] timeIntervalSinceDate:startDate] * 1000);
}
- (void)addAccount:(MXKAccount *)account andOpenSession:(BOOL)openSession
{
MXLogDebug(@"[MXKAccountManager] login (%@)", account.mxCredentials.userId);
[mxAccounts addObject:account];
[self saveAccounts];
// Check conditions to open a matrix session
if (openSession && !account.disabled)
{
// Open a new matrix session by default
MXLogDebug(@"[MXKAccountManager] openSession for %@ account (device %@)", account.mxCredentials.userId, account.mxCredentials.deviceId);
id<MXStore> store = [[_storeClass alloc] init];
[account openSessionWithStore:store];
}
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountManagerDidAddAccountNotification object:account userInfo:nil];
}
- (void)removeAccount:(MXKAccount*)theAccount completion:(void (^)(void))completion
{
[self removeAccount:theAccount sendLogoutRequest:YES completion:completion];
}
- (void)removeAccount:(MXKAccount*)theAccount
sendLogoutRequest:(BOOL)sendLogoutRequest
completion:(void (^)(void))completion
{
MXLogDebug(@"[MXKAccountManager] logout (%@), send logout request to homeserver: %d", theAccount.mxCredentials.userId, sendLogoutRequest);
// Close session and clear associated store.
[theAccount logoutSendingServerRequest:sendLogoutRequest completion:^{
// Retrieve the corresponding account in the internal array
MXKAccount* removedAccount = nil;
for (MXKAccount *account in self->mxAccounts)
{
if ([account.mxCredentials.userId isEqualToString:theAccount.mxCredentials.userId])
{
removedAccount = account;
break;
}
}
if (removedAccount)
{
[self->mxAccounts removeObject:removedAccount];
[self saveAccounts];
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountManagerDidRemoveAccountNotification object:removedAccount userInfo:nil];
}
if (completion)
{
completion();
}
}];
}
- (void)logoutWithCompletion:(void (^)(void))completion
{
// Logout one by one the existing accounts
if (mxAccounts.count)
{
[self removeAccount:mxAccounts.lastObject completion:^{
// loop: logout the next existing account (if any)
[self logoutWithCompletion:completion];
}];
return;
}
NSUserDefaults *sharedUserDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults;
// Remove APNS device token
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsDeviceToken"];
// Remove Push device token
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushDeviceToken"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
// Be sure that no account survive in local storage
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kMXKAccountsKey];
[sharedUserDefaults removeObjectForKey:kMXKAccountsKey];
[[NSFileManager defaultManager] removeItemAtPath:[self accountFile] error:nil];
if (completion)
{
completion();
}
}
- (void)softLogout:(MXKAccount*)account
{
[account softLogout];
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountManagerDidSoftlogoutAccountNotification
object:account
userInfo:nil];
}
- (void)hydrateAccount:(MXKAccount*)account withCredentials:(MXCredentials*)credentials
{
MXLogDebug(@"[MXKAccountManager] hydrateAccount: %@", account.mxCredentials.userId);
if ([account.mxCredentials.userId isEqualToString:credentials.userId])
{
// Restart the account
[account hydrateWithCredentials:credentials];
MXLogDebug(@"[MXKAccountManager] hydrateAccount: Open session");
id<MXStore> store = [[_storeClass alloc] init];
[account openSessionWithStore:store];
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountManagerDidAddAccountNotification
object:account
userInfo:nil];
}
else
{
MXLogDebug(@"[MXKAccountManager] hydrateAccount: Credentials given for another account: %@", credentials.userId);
// Logout the old account and create a new one with the new credentials
[self removeAccount:account sendLogoutRequest:YES completion:nil];
MXKAccount *newAccount = [[MXKAccount alloc] initWithCredentials:credentials];
[self addAccount:newAccount andOpenSession:YES];
}
}
- (MXKAccount *)accountForUserId:(NSString *)userId
{
for (MXKAccount *account in mxAccounts)
{
if ([account.mxCredentials.userId isEqualToString:userId])
{
return account;
}
}
return nil;
}
- (MXKAccount *)accountKnowingRoomWithRoomIdOrAlias:(NSString *)roomIdOrAlias
{
MXKAccount *theAccount = nil;
NSArray *activeAccounts = self.activeAccounts;
for (MXKAccount *account in activeAccounts)
{
if ([roomIdOrAlias hasPrefix:@"#"])
{
if ([account.mxSession roomWithAlias:roomIdOrAlias])
{
theAccount = account;
break;
}
}
else
{
if ([account.mxSession roomWithRoomId:roomIdOrAlias])
{
theAccount = account;
break;
}
}
}
return theAccount;
}
- (MXKAccount *)accountKnowingUserWithUserId:(NSString *)userId
{
MXKAccount *theAccount = nil;
NSArray *activeAccounts = self.activeAccounts;
for (MXKAccount *account in activeAccounts)
{
if ([account.mxSession userWithUserId:userId])
{
theAccount = account;
break;
}
}
return theAccount;
}
#pragma mark -
- (void)setStoreClass:(Class)storeClass
{
// Sanity check
NSAssert([storeClass conformsToProtocol:@protocol(MXStore)], @"MXKAccountManager only manages store class that conforms to MXStore protocol");
_storeClass = storeClass;
}
- (NSArray<MXKAccount *> *)accounts
{
return [mxAccounts copy];
}
- (NSArray<MXKAccount *> *)activeAccounts
{
NSMutableArray *activeAccounts = [NSMutableArray arrayWithCapacity:mxAccounts.count];
for (MXKAccount *account in mxAccounts)
{
if (!account.disabled && !account.isSoftLogout)
{
[activeAccounts addObject:account];
}
}
return activeAccounts;
}
- (NSData *)apnsDeviceToken
{
NSData *token = [[NSUserDefaults standardUserDefaults] objectForKey:@"apnsDeviceToken"];
if (!token.length)
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsDeviceToken"];
token = nil;
}
MXLogDebug(@"[MXKAccountManager][Push] apnsDeviceToken: %@", [MXKTools logForPushToken:token]);
return token;
}
- (void)setApnsDeviceToken:(NSData *)apnsDeviceToken
{
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: %@", [MXKTools logForPushToken:apnsDeviceToken]);
NSData *oldToken = self.apnsDeviceToken;
if (!apnsDeviceToken.length)
{
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: reset APNS device token");
if (oldToken)
{
// turn off the Apns flag for all accounts if any
for (MXKAccount *account in mxAccounts)
{
[account enablePushNotifications:NO success:nil failure:nil];
}
}
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsDeviceToken"];
}
else
{
NSArray *activeAccounts = self.activeAccounts;
if (!oldToken)
{
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: set APNS device token");
[[NSUserDefaults standardUserDefaults] setObject:apnsDeviceToken forKey:@"apnsDeviceToken"];
// turn on the Apns flag for all accounts, when the Apns registration succeeds for the first time
for (MXKAccount *account in activeAccounts)
{
[account enablePushNotifications:YES success:nil failure:nil];
}
}
else if (![oldToken isEqualToData:apnsDeviceToken])
{
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: update APNS device token");
NSMutableArray<MXKAccount*> *accountsWithAPNSPusher = [NSMutableArray new];
// Delete the pushers related to the old token
for (MXKAccount *account in activeAccounts)
{
if (account.hasPusherForPushNotifications)
{
[accountsWithAPNSPusher addObject:account];
}
[account enablePushNotifications:NO success:nil failure:nil];
}
// Update the token
[[NSUserDefaults standardUserDefaults] setObject:apnsDeviceToken forKey:@"apnsDeviceToken"];
// Refresh pushers with the new token.
for (MXKAccount *account in activeAccounts)
{
if ([accountsWithAPNSPusher containsObject:account])
{
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: Resync APNS for %@ account", account.mxCredentials.userId);
[account enablePushNotifications:YES success:nil failure:nil];
}
else
{
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: hasPusherForPushNotifications = NO for %@ account. Do not enable Push", account.mxCredentials.userId);
}
}
}
else
{
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: Same token. Nothing to do.");
}
}
}
- (BOOL)isAPNSAvailable
{
// [UIApplication isRegisteredForRemoteNotifications] tells whether your app can receive
// remote notifications or not. Receiving remote notifications does not guarantee it will
// display them to the user as they may have notifications set to deliver quietly.
BOOL isRemoteNotificationsAllowed = NO;
UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
if (sharedApplication)
{
isRemoteNotificationsAllowed = [sharedApplication isRegisteredForRemoteNotifications];
MXLogDebug(@"[MXKAccountManager][Push] isAPNSAvailable: The user %@ remote notification", (isRemoteNotificationsAllowed ? @"allowed" : @"denied"));
}
BOOL isAPNSAvailable = (isRemoteNotificationsAllowed && self.apnsDeviceToken);
MXLogDebug(@"[MXKAccountManager][Push] isAPNSAvailable: %@", @(isAPNSAvailable));
return isAPNSAvailable;
}
- (NSData *)pushDeviceToken
{
NSData *token = [[NSUserDefaults standardUserDefaults] objectForKey:@"pushDeviceToken"];
if (!token.length)
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushDeviceToken"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
token = nil;
}
MXLogDebug(@"[MXKAccountManager][Push] pushDeviceToken: %@", [MXKTools logForPushToken:token]);
return token;
}
- (NSDictionary *)pushOptions
{
NSDictionary *pushOptions = [[NSUserDefaults standardUserDefaults] objectForKey:@"pushOptions"];
MXLogDebug(@"[MXKAccountManager][Push] pushOptions: %@", pushOptions);
return pushOptions;
}
- (void)setPushDeviceToken:(NSData *)pushDeviceToken withPushOptions:(NSDictionary *)pushOptions
{
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: %@ withPushOptions: %@", [MXKTools logForPushToken:pushDeviceToken], pushOptions);
NSData *oldToken = self.pushDeviceToken;
if (!pushDeviceToken.length)
{
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Reset Push device token");
if (oldToken)
{
// turn off the Push flag for all accounts if any
for (MXKAccount *account in mxAccounts)
{
[account enablePushKitNotifications:NO success:^{
// make sure pusher really removed before losing token.
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushDeviceToken"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
} failure:nil];
}
}
}
else
{
NSArray *activeAccounts = self.activeAccounts;
if (!oldToken)
{
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Set Push device token");
[[NSUserDefaults standardUserDefaults] setObject:pushDeviceToken forKey:@"pushDeviceToken"];
if (pushOptions)
{
[[NSUserDefaults standardUserDefaults] setObject:pushOptions forKey:@"pushOptions"];
}
else
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
}
// turn on the Push flag for all accounts
for (MXKAccount *account in activeAccounts)
{
[account enablePushKitNotifications:YES success:nil failure:nil];
}
}
else if (![oldToken isEqualToData:pushDeviceToken])
{
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Update Push device token");
NSMutableArray<MXKAccount*> *accountsWithPushKitPusher = [NSMutableArray new];
// Delete the pushers related to the old token
for (MXKAccount *account in activeAccounts)
{
if (account.hasPusherForPushKitNotifications)
{
[accountsWithPushKitPusher addObject:account];
}
[account enablePushKitNotifications:NO success:nil failure:nil];
}
// Update the token
[[NSUserDefaults standardUserDefaults] setObject:pushDeviceToken forKey:@"pushDeviceToken"];
if (pushOptions)
{
[[NSUserDefaults standardUserDefaults] setObject:pushOptions forKey:@"pushOptions"];
}
else
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
}
// Refresh pushers with the new token.
for (MXKAccount *account in activeAccounts)
{
if ([accountsWithPushKitPusher containsObject:account])
{
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Resync Push for %@ account", account.mxCredentials.userId);
[account enablePushKitNotifications:YES success:nil failure:nil];
}
else
{
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: hasPusherForPushKitNotifications = NO for %@ account. Do not enable Push", account.mxCredentials.userId);
}
}
}
else
{
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Same token. Nothing to do.");
}
}
}
- (BOOL)isPushAvailable
{
// [UIApplication isRegisteredForRemoteNotifications] tells whether your app can receive
// remote notifications or not. Receiving remote notifications does not guarantee it will
// display them to the user as they may have notifications set to deliver quietly.
BOOL isRemoteNotificationsAllowed = NO;
UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
if (sharedApplication)
{
isRemoteNotificationsAllowed = [sharedApplication isRegisteredForRemoteNotifications];
MXLogDebug(@"[MXKAccountManager][Push] isPushAvailable: The user %@ remote notification", (isRemoteNotificationsAllowed ? @"allowed" : @"denied"));
}
BOOL isPushAvailable = (isRemoteNotificationsAllowed && self.pushDeviceToken);
MXLogDebug(@"[MXKAccountManager][Push] isPushAvailable: %@", @(isPushAvailable));
return isPushAvailable;
}
#pragma mark -
// Return the path of the file containing stored MXAccounts array
- (NSString*)accountFile
{
NSString *matrixKitCacheFolder = [MXKAppSettings cacheFolder];
return [matrixKitCacheFolder stringByAppendingPathComponent:kMXKAccountsKey];
}
- (void)loadAccounts
{
MXLogDebug(@"[MXKAccountManager] loadAccounts");
NSString *accountFile = [self accountFile];
if ([[NSFileManager defaultManager] fileExistsAtPath:accountFile])
{
NSDate *startDate = [NSDate date];
NSError *error = nil;
NSData* filecontent = [NSData dataWithContentsOfFile:accountFile options:(NSDataReadingMappedAlways | NSDataReadingUncached) error:&error];
if (!error)
{
// Decrypt data if encryption method is provided
NSData *unciphered = [self decryptData:filecontent];
NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:unciphered];
mxAccounts = [decoder decodeObjectForKey:@"mxAccounts"];
if (!mxAccounts && [[MXKeyProvider sharedInstance] isEncryptionAvailableForDataOfType:MXKAccountManagerDataType])
{
// This happens if the V2 file has not been encrypted -> read file content then save encrypted accounts
MXLogDebug(@"[MXKAccountManager] loadAccounts. Failed to read decrypted data: reading file data without encryption.");
decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:filecontent];
mxAccounts = [decoder decodeObjectForKey:@"mxAccounts"];
if (mxAccounts)
{
MXLogDebug(@"[MXKAccountManager] loadAccounts. saving encrypted accounts");
[self saveAccounts];
}
}
}
MXLogDebug(@"[MXKAccountManager] loadAccounts. %tu accounts loaded in %.0fms", mxAccounts.count, [[NSDate date] timeIntervalSinceDate:startDate] * 1000);
}
else
{
// Migration of accountData from sharedUserDefaults to a file
NSUserDefaults *sharedDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults;
NSData *accountData = [sharedDefaults objectForKey:kMXKAccountsKey];
if (!accountData)
{
// Migration of accountData from [NSUserDefaults standardUserDefaults], the first location storage
accountData = [[NSUserDefaults standardUserDefaults] objectForKey:kMXKAccountsKey];
}
if (accountData)
{
mxAccounts = [NSMutableArray arrayWithArray:[NSKeyedUnarchiver unarchiveObjectWithData:accountData]];
[self saveAccounts];
MXLogDebug(@"[MXKAccountManager] loadAccounts: performed data migration");
// Now that data has been migrated, erase old location of accountData
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kMXKAccountsKey];
[sharedDefaults removeObjectForKey:kMXKAccountsKey];
}
}
if (!mxAccounts)
{
MXLogDebug(@"[MXKAccountManager] loadAccounts. No accounts");
mxAccounts = [NSMutableArray array];
}
}
- (void)forceReloadAccounts
{
MXLogDebug(@"[MXKAccountManager] Force reload existing accounts from local storage");
[self loadAccounts];
}
- (NSData*)encryptData:(NSData*)data
{
// Exceptions are not caught as the key is always needed if the KeyProviderDelegate
// is provided.
MXKeyData *keyData = [[MXKeyProvider sharedInstance] requestKeyForDataOfType:MXKAccountManagerDataType isMandatory:YES expectedKeyType:kAes];
if (keyData && [keyData isKindOfClass:[MXAesKeyData class]])
{
MXAesKeyData *aesKey = (MXAesKeyData *) keyData;
NSData *cipher = [MXAes encrypt:data aesKey:aesKey.key iv:aesKey.iv error:nil];
return cipher;
}
MXLogDebug(@"[MXKAccountManager] encryptData: no key method provided for encryption.");
return data;
}
- (NSData*)decryptData:(NSData*)data
{
// Exceptions are not cached as the key is always needed if the KeyProviderDelegate
// is provided.
MXKeyData *keyData = [[MXKeyProvider sharedInstance] requestKeyForDataOfType:MXKAccountManagerDataType isMandatory:YES expectedKeyType:kAes];
if (keyData && [keyData isKindOfClass:[MXAesKeyData class]])
{
MXAesKeyData *aesKey = (MXAesKeyData *) keyData;
NSData *decrypt = [MXAes decrypt:data aesKey:aesKey.key iv:aesKey.iv error:nil];
return decrypt;
}
MXLogDebug(@"[MXKAccountManager] decryptData: no key method provided for decryption.");
return data;
}
- (void)migrateAccounts
{
NSString *pathOld = [[MXKAppSettings cacheFolder] stringByAppendingPathComponent:kMXKAccountsKeyOld];
NSString *pathNew = [[MXKAppSettings cacheFolder] stringByAppendingPathComponent:kMXKAccountsKey];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:pathOld])
{
if (![fileManager fileExistsAtPath:pathNew])
{
MXLogDebug(@"[MXKAccountManager] migrateAccounts: reading account");
mxAccounts = [NSKeyedUnarchiver unarchiveObjectWithFile:pathOld];
MXLogDebug(@"[MXKAccountManager] migrateAccounts: writing to accountV2");
[self saveAccounts];
}
MXLogDebug(@"[MXKAccountManager] migrateAccounts: removing account");
[fileManager removeItemAtPath:pathOld error:nil];
}
}
@end