element-ios/Riot/Managers/Widgets/WidgetManager.m

825 lines
30 KiB
Objective-C

/*
Copyright 2019-2024 New Vector Ltd.
Copyright 2017 Vector Creations Ltd
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
#import "WidgetManager.h"
#import "GeneratedInterface-Swift.h"
#import "JitsiWidgetData.h"
#import "MXSession+Riot.h"
#pragma mark - Contants
NSString *const kWidgetManagerDidUpdateWidgetNotification = @"kWidgetManagerDidUpdateWidgetNotification";
NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
@interface WidgetManager ()
{
// MXSession kind of hash -> Listener for matrix events for widgets.
// There is one per matrix session
NSMutableDictionary<NSString*, id> *widgetEventListener;
// Success blocks of widgets being created
// MXSession kind of hash -> (Widget id -> `createWidget:` success block).
NSMutableDictionary<NSString*,
NSMutableDictionary<NSString*, void (^)(Widget *widget)>*> *successBlockForWidgetCreation;
// Failure blocks of widgets being created
// MXSession kind of hash -> (Widget id -> `createWidget:` failure block).
NSMutableDictionary<NSString*,
NSMutableDictionary<NSString*, void (^)(NSError *error)>*> *failureBlockForWidgetCreation;
// User id -> scalar token
NSMutableDictionary<NSString*, WidgetManagerConfig*> *configs;
// User id -> MXSession
NSMutableDictionary<NSString*, MXSession*> *matrixSessions;
}
@end
@implementation WidgetManager
+ (instancetype)sharedManager
{
static WidgetManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[WidgetManager alloc] init];
});
return sharedManager;
}
- (instancetype)init
{
self = [super init];
if (self)
{
matrixSessions = [NSMutableDictionary dictionary];
widgetEventListener = [NSMutableDictionary dictionary];
successBlockForWidgetCreation = [NSMutableDictionary dictionary];
failureBlockForWidgetCreation = [NSMutableDictionary dictionary];
[self loadConfigs];
}
return self;
}
- (NSArray<Widget *> *)widgetsInRoom:(MXRoom*)room withRoomState:(MXRoomState*)roomState
{
return [self widgetsOfTypes:nil inRoom:room withRoomState:roomState];
}
- (NSArray<Widget*> *)widgetsOfTypes:(NSArray<NSString*>*)widgetTypes inRoom:(MXRoom*)room withRoomState:(MXRoomState*)roomState
{
return [self widgetsOfTypes:widgetTypes butNotTypesOf:nil inRoom:room withRoomState:roomState];
}
- (NSArray<Widget*> *)widgetsNotOfTypes:(NSArray<NSString*>*)notWidgetTypes inRoom:(MXRoom*)room withRoomState:(MXRoomState*)roomState;
{
return [self widgetsOfTypes:nil butNotTypesOf:notWidgetTypes inRoom:room withRoomState:roomState];
}
- (NSArray<Widget*> *)widgetsOfTypes:(NSArray<NSString*>*)widgetTypes butNotTypesOf:(NSArray<NSString*>*)notWidgetTypes inRoom:(MXRoom*)room withRoomState:(MXRoomState*)roomState;
{
// Widget id -> widget
NSMutableDictionary <NSString*, Widget *> *widgets = [NSMutableDictionary dictionary];
// Get all widgets state events in the room
NSMutableArray<MXEvent*> *widgetEvents = [NSMutableArray arrayWithArray:[roomState stateEventsWithType:kWidgetMatrixEventTypeString]];
[widgetEvents addObjectsFromArray:[roomState stateEventsWithType:kWidgetModularEventTypeString]];
// There can be several widgets state events for a same widget but
// only the last one must be considered.
// Order widgetEvents with the last event first
[widgetEvents sortUsingComparator:^NSComparisonResult(MXEvent *event1, MXEvent *event2) {
NSComparisonResult result = NSOrderedAscending;
if (event2.originServerTs > event1.originServerTs)
{
result = NSOrderedDescending;
}
else if (event2.originServerTs == event1.originServerTs)
{
result = NSOrderedSame;
}
return result;
}];
// Create each widget from its lastest widgets state event
for (MXEvent *widgetEvent in widgetEvents)
{
// Filter widget types if required
if (widgetTypes || notWidgetTypes)
{
NSString *widgetType;
MXJSONModelSetString(widgetType, widgetEvent.content[@"type"]);
if (widgetType)
{
if (widgetTypes && NSNotFound == [widgetTypes indexOfObject:widgetType])
{
continue;
}
if (notWidgetTypes && NSNotFound != [notWidgetTypes indexOfObject:widgetType])
{
continue;
}
}
}
// widgetEvent.stateKey = widget id
if (!widgets[widgetEvent.stateKey])
{
Widget *widget = [[Widget alloc] initWithWidgetEvent:widgetEvent inMatrixSession:room.mxSession];
if (widget)
{
widgets[widget.widgetId] = widget;
}
}
}
// Return active widgets only
NSMutableArray<Widget *> *activeWidgets = [NSMutableArray array];
for (Widget *widget in widgets.allValues)
{
if (widget.isActive)
{
[activeWidgets addObject:widget];
}
}
return activeWidgets;
}
- (NSArray<Widget*> *)userWidgets:(MXSession*)mxSession
{
return [self userWidgets:mxSession ofTypes:nil];
}
- (NSArray<Widget*> *)userWidgets:(MXSession*)mxSession ofTypes:(NSArray<NSString*>*)widgetTypes
{
// Get all widgets in the user account data
NSMutableArray<Widget *> *userWidgets = [NSMutableArray array];
for (NSDictionary *widgetEventContent in [mxSession.accountData accountDataForEventType:kMXAccountDataTypeUserWidgets].allValues)
{
if (![widgetEventContent isKindOfClass:NSDictionary.class])
{
MXLogDebug(@"[WidgetManager] userWidgets: ERROR: invalid user widget format: %@", widgetEventContent);
continue;
}
// Patch: Modular used a malformed key: "stateKey" instead of "state_key"
// TODO: To remove once fixed server side
NSDictionary *widgetEventContentFixed = widgetEventContent;
if (!widgetEventContent[@"state_key"] && widgetEventContent[@"stateKey"])
{
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:widgetEventContent];
dict[@"state_key"] = widgetEventContent[@"stateKey"];
widgetEventContentFixed = dict;
}
MXEvent *widgetEvent = [MXEvent modelFromJSON:widgetEventContentFixed];
if (widgetEvent
&& (!widgetTypes || [widgetTypes containsObject:widgetEvent.content[@"type"]]))
{
Widget *widget = [[Widget alloc] initWithWidgetEvent:widgetEvent inMatrixSession:mxSession];
if (widget)
{
[userWidgets addObject:widget];
}
}
}
return userWidgets;
}
- (MXHTTPOperation *)createWidget:(NSString*)widgetId
withContent:(NSDictionary<NSString*, NSObject*>*)widgetContent
inRoom:(MXRoom*)room
success:(void (^)(Widget *widget))success
failure:(void (^)(NSError *error))failure
{
// Create an empty operation that will be mutated later
MXHTTPOperation *operation = [[MXHTTPOperation alloc] init];
MXWeakify(self);
[self checkWidgetPermissionInRoom:room success:^{
MXStrongifyAndReturnIfNil(self);
NSString *hash = [NSString stringWithFormat:@"%p", room.mxSession];
self->successBlockForWidgetCreation[hash][widgetId] = success;
self->failureBlockForWidgetCreation[hash][widgetId] = failure;
// Send a state event with the widget data
// TODO: This API will be shortly replaced by a pure modular API
// TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when?
MXHTTPOperation *operation2 = [room sendStateEventOfType:kWidgetModularEventTypeString
content:widgetContent
stateKey:widgetId
success:nil failure:failure];
[operation mutateTo:operation2];
} failure:^(NSError *error) {
if (failure)
{
failure(error);
}
}];
return operation;
}
- (MXHTTPOperation *)createJitsiWidgetInRoom:(MXRoom*)room
withVideo:(BOOL)video
success:(void (^)(Widget *jitsiWidget))success
failure:(void (^)(NSError *error))failure
{
MXHTTPOperation *operation = [MXHTTPOperation new];
NSString *userId = room.mxSession.myUser.userId;
WidgetManagerConfig *config = [self configForUser:userId];
if (!config.hasUrls)
{
MXLogDebug(@"[WidgetManager] createJitsiWidgetInRoom: Error: no integration manager API URL for user %@", userId);
failure(self.errorForNonConfiguredIntegrationManager);
return nil;
}
RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:room.mxSession];
if (!sharedSettings.hasIntegrationProvisioningEnabled)
{
MXLogDebug(@"[WidgetManager] createJitsiWidgetInRoom: Error: Disabled integration manager for user %@", userId);
failure(self.errorForDisabledIntegrationManager);
return nil;
}
// Build data for a jitsi widget
// Riot-Web still uses V1 type
NSString *widgetId = [NSString stringWithFormat:@"%@_%@_%@", kWidgetTypeJitsiV1, room.mxSession.myUser.userId, @((uint64_t)([[NSDate date] timeIntervalSince1970] * 1000))];
NSURL *preferredJitsiServerUrl = [room.mxSession vc_homeserverConfiguration].jitsi.serverURL;
if (!preferredJitsiServerUrl)
{
MXLogDebug(@"[WidgetManager] createJitsiWidgetInRoom: Error: No Jitsi server URL provided");
failure(self.errorForUnavailableJitsiURL);
return nil;
}
JitsiService *jitsiService = JitsiService.shared;
operation = [jitsiService createJitsiWidgetContentWithJitsiServerURL:preferredJitsiServerUrl roomID:room.roomId isAudioOnly:!video success:^(NSDictionary * _Nonnull widgetContent) {
MXHTTPOperation *operation2 = [self createWidget:widgetId
withContent:widgetContent
inRoom:room
success:success
failure:failure];
[operation mutateTo:operation2];
} failure:^(NSError * _Nonnull error) {
if (failure)
{
failure(error);
}
}];
return operation;
}
- (MXHTTPOperation *)closeWidget:(NSString *)widgetId inRoom:(MXRoom *)room success:(void (^)(void))success failure:(void (^)(NSError *))failure
{
// Create an empty operation that will be mutated later
MXHTTPOperation *operation = [[MXHTTPOperation alloc] init];
[self checkWidgetPermissionInRoom:room success:^{
// Send a state event with an empty content to disable the widget
// TODO: This API will be shortly replaced by a pure modular API
// TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when?
MXHTTPOperation *operation2 = [room sendStateEventOfType:kWidgetModularEventTypeString
content:@{}
stateKey:widgetId
success:^(NSString *eventId)
{
if (success)
{
success();
}
} failure:failure];
[operation mutateTo:operation2];
} failure:^(NSError *error) {
if (failure)
{
failure(error);
}
}];
return operation;
}
/**
Check user's power for widgets management in a room.
@param room the room to check.
*/
- (void)checkWidgetPermissionInRoom:(MXRoom *)room success:(dispatch_block_t)success failure:(void (^)(NSError *))failure
{
[room state:^(MXRoomState *roomState) {
NSError *error;
// Check user's power in the room
MXRoomPowerLevels *powerLevels = roomState.powerLevels;
NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:room.mxSession.myUser.userId];
// The user must be able to send state events to manage widgets
if (oneSelfPowerLevel < powerLevels.stateDefault)
{
error = [NSError errorWithDomain:WidgetManagerErrorDomain
code:WidgetManagerErrorCodeNotEnoughPower
userInfo:@{
NSLocalizedDescriptionKey: [VectorL10n widgetNoPowerToManage]
}];
}
if (error)
{
failure(error);
}
else
{
success();
}
}];
}
- (void)addMatrixSession:(MXSession *)mxSession
{
__weak __typeof__(self) weakSelf = self;
matrixSessions[mxSession.matrixRestClient.credentials.userId] = mxSession;
NSString *hash = [NSString stringWithFormat:@"%p", mxSession];
id listener = [mxSession listenToEventsOfTypes:@[kWidgetMatrixEventTypeString, kWidgetModularEventTypeString] onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) {
typeof(self) self = weakSelf;
if (self && direction == MXTimelineDirectionForwards)
{
// stateKey = widgetId
NSString *widgetId = event.stateKey;
if (!widgetId)
{
MXLogDebug(@"[WidgetManager] Error: New widget detected with no id in %@: %@", event.roomId, event.JSONDictionary);
return;
}
MXLogDebug(@"[WidgetManager] New widget detected: %@ in %@", widgetId, event.roomId);
Widget *widget = [[Widget alloc] initWithWidgetEvent:event inMatrixSession:mxSession];
if (widget)
{
// If it is a widget we have just created, indicate its creation is complete
if (self->successBlockForWidgetCreation[hash][widgetId])
{
self->successBlockForWidgetCreation[hash][widgetId](widget);
}
// Broadcast the generic notification
[[NSNotificationCenter defaultCenter] postNotificationName:kWidgetManagerDidUpdateWidgetNotification object:widget];
// End jitsi call if a active call exists and widget has been updated to not be active
if ([[AppDelegate theDelegate].callPresenter.jitsiVC.widget.widgetId isEqualToString: widget.widgetId] &&
[[AppDelegate theDelegate].callPresenter.jitsiVC.widget.roomId isEqualToString: event.roomId] &&
!widget.isActive)
{
[[AppDelegate theDelegate].callPresenter endActiveJitsiCall];
}
}
else
{
MXLogDebug(@"[WidgetManager] Cannot decode new widget - event: %@", event);
if (self->failureBlockForWidgetCreation[hash][widgetId])
{
// If it is a widget we have just created, indicate its creation has failed somehow
NSError *error = [NSError errorWithDomain:WidgetManagerErrorDomain
code:WidgetManagerErrorCodeCreationFailed
userInfo:@{
NSLocalizedDescriptionKey: [VectorL10n widgetCreationFailure]
}];
self->failureBlockForWidgetCreation[hash][widgetId](error);
}
}
[self->successBlockForWidgetCreation[hash] removeObjectForKey:widgetId];
[self->failureBlockForWidgetCreation[hash] removeObjectForKey:widgetId];
}
}];
widgetEventListener[hash] = listener;
successBlockForWidgetCreation[hash] = [NSMutableDictionary dictionary];
failureBlockForWidgetCreation[hash] = [NSMutableDictionary dictionary];
}
- (void)removeMatrixSession:(MXSession *)mxSession
{
// Remove by value in a dict
for (NSString *key in [matrixSessions allKeysForObject:mxSession])
{
[matrixSessions removeObjectForKey:key];
}
// mxSession.myUser.userId and mxSession.matrixRestClient.credentials.userId may be nil here
// So, use a kind of hash value instead
NSString *hash = [NSString stringWithFormat:@"%p", mxSession];
id listener = widgetEventListener[hash];
[mxSession removeListener:listener];
[widgetEventListener removeObjectForKey:hash];
[successBlockForWidgetCreation removeObjectForKey:hash];
[failureBlockForWidgetCreation removeObjectForKey:hash];
}
- (MXSession*)matrixSessionForUser:(NSString*)userId
{
return matrixSessions[userId];
}
- (void)deleteDataForUser:(NSString *)userId
{
[configs removeObjectForKey:userId];
[self saveConfigs];
}
#pragma mark - User integrations configuration
- (WidgetManagerConfig*)createWidgetManagerConfigForUser:(NSString*)userId
{
WidgetManagerConfig *config;
MXSession *session = [self matrixSessionForUser:userId];
// Find the integrations settings for the user
// First, look at matrix account
// TODO in another user story
// Then, try to the homeserver configuration
MXWellknownIntegrationsManager *integrationsManager = session.homeserverWellknown.integrations.managers.firstObject;
if (integrationsManager)
{
config = [[WidgetManagerConfig alloc] initWithApiUrl:integrationsManager.apiUrl uiUrl:integrationsManager.uiUrl];
}
else
{
// Fallback on app settings
config = [self createWidgetManagerConfigWithAppSettings];
}
return config;
}
- (WidgetManagerConfig*)createWidgetManagerConfigWithAppSettings
{
return [[WidgetManagerConfig alloc] initWithApiUrl:BuildSettings.integrationsRestApiUrlString
uiUrl:BuildSettings.integrationsUiUrlString];
}
#pragma mark - Modular interface
- (WidgetManagerConfig*)configForUser:(NSString*)userId
{
// Return a default config by default
return configs[userId] ? configs[userId] : [self createWidgetManagerConfigForUser:userId];
}
- (BOOL)hasIntegrationManagerForUser:(NSString*)userId
{
return [self configForUser:userId].hasUrls;
}
- (void)setConfig:(WidgetManagerConfig*)config forUser:(NSString*)userId
{
configs[userId] = config;
[self saveConfigs];
}
- (MXHTTPOperation *)getScalarTokenForMXSession:(MXSession*)mxSession
validate:(BOOL)validate
success:(void (^)(NSString *scalarToken))success
failure:(void (^)(NSError *error))failure;
{
MXHTTPOperation *operation;
__block NSString *scalarToken = [self scalarTokenForMXSession:mxSession];
if (scalarToken)
{
if (!validate)
{
success(scalarToken);
}
else
{
operation = [self validateScalarToken:scalarToken forMXSession:mxSession complete:^(BOOL valid) {
if (valid)
{
success(scalarToken);
}
else
{
MXLogDebug(@"[WidgetManager] getScalarTokenForMXSession: Invalid stored token. Need to register for a new token");
MXHTTPOperation *operation2 = [self registerForScalarToken:mxSession success:success failure:failure];
[operation mutateTo:operation2];
}
} failure:failure];
}
}
else
{
MXLogDebug(@"[WidgetManager] getScalarTokenForMXSession: Need to register for a token");
operation = [self registerForScalarToken:mxSession success:success failure:failure];
}
return operation;
}
- (MXHTTPOperation *)registerForScalarToken:(MXSession*)mxSession
success:(void (^)(NSString *scalarToken))success
failure:(void (^)(NSError *error))failure
{
MXHTTPOperation *operation;
NSString *userId = mxSession.myUser.userId;
MXLogDebug(@"[WidgetManager] registerForScalarToken");
WidgetManagerConfig *config = [self configForUser:userId];
if (!config.hasUrls)
{
MXLogDebug(@"[WidgetManager] registerForScalarToken: Error: no integration manager API URL for user %@", mxSession.myUser.userId);
failure(self.errorForNonConfiguredIntegrationManager);
return nil;
}
MXWeakify(self);
operation = [mxSession.matrixRestClient openIdToken:^(MXOpenIdToken *tokenObject) {
MXStrongifyAndReturnIfNil(self);
// Exchange the token for a scalar token
__block MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:config.apiUrl andOnUnrecognizedCertificateBlock:nil];
MXHTTPOperation *operation2 =
[httpClient requestWithMethod:@"POST"
path:@"register?v=1.1"
parameters:tokenObject.JSONDictionary
success:^(NSDictionary *JSONResponse)
{
httpClient = nil;
NSString *scalarToken;
MXJSONModelSetString(scalarToken, JSONResponse[@"scalar_token"])
config.scalarToken = scalarToken;
self->configs[userId] = config;
[self saveConfigs];
// Validate it (this mostly checks to see if the IM needs us to agree to some terms)
MXHTTPOperation *operation3 = [self validateScalarToken:scalarToken forMXSession:mxSession complete:^(BOOL valid) {
if (success)
{
success(scalarToken);
}
} failure:failure];
[operation mutateTo:operation3];
} failure:^(NSError *error) {
httpClient = nil;
MXLogDebug(@"[WidgetManager] registerForScalarToken: Failed to register. Error: %@", error);
if (failure)
{
// Specialise the error
NSError *error = [NSError errorWithDomain:WidgetManagerErrorDomain
code:WidgetManagerErrorCodeFailedToConnectToIntegrationsServer
userInfo:@{
NSLocalizedDescriptionKey: [VectorL10n widgetIntegrationsServerFailedToConnect]
}];
failure(error);
}
}];
[operation mutateTo:operation2];
} failure:^(NSError *error) {
MXLogDebug(@"[WidgetManager] registerForScalarToken. Error in openIdToken request");
if (failure)
{
failure(error);
}
}];
return operation;
}
- (MXHTTPOperation *)validateScalarToken:(NSString*)scalarToken forMXSession:(MXSession*)mxSession
complete:(void (^)(BOOL valid))complete
failure:(void (^)(NSError *error))failure
{
NSString *userId = mxSession.myUser.userId;
WidgetManagerConfig *config = [self configForUser:userId];
if (!config.hasUrls)
{
MXLogDebug(@"[WidgetManager] validateScalarToken: Error: no integration manager API URL for user %@", mxSession.myUser.userId);
failure(self.errorForNonConfiguredIntegrationManager);
return nil;
}
__block MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:config.apiUrl andOnUnrecognizedCertificateBlock:nil];
return [httpClient requestWithMethod:@"GET"
path:[NSString stringWithFormat:@"account?v=1.1&scalar_token=%@", scalarToken]
parameters:nil
success:^(NSDictionary *JSONResponse) {
httpClient = nil;
NSString *userId;
MXJSONModelSetString(userId, JSONResponse[@"user_id"])
if ([userId isEqualToString:mxSession.myUser.userId])
{
complete(YES);
}
else
{
MXLogDebug(@"[WidgetManager] validateScalarToken. Unexpected modular/account response: %@", JSONResponse);
complete(NO);
}
} failure:^(NSError *error) {
httpClient = nil;
NSHTTPURLResponse *urlResponse = [MXHTTPOperation urlResponseFromError:error];
MXLogDebug(@"[WidgetManager] validateScalarToken. Error in modular/account request. statusCode: %@", @(urlResponse.statusCode));
MXError *mxError = [[MXError alloc] initWithNSError:error];
if ([mxError.errcode isEqualToString:kMXErrCodeStringTermsNotSigned])
{
MXLogDebug(@"[WidgetManager] validateScalarToke. Error: Need to accept terms");
NSError *termsNotSignedError = [NSError errorWithDomain:WidgetManagerErrorDomain
code:WidgetManagerErrorCodeTermsNotSigned
userInfo:@{
NSLocalizedDescriptionKey:error.userInfo[NSLocalizedDescriptionKey]
}];
failure(termsNotSignedError);
}
else if (urlResponse && urlResponse.statusCode / 100 != 2)
{
complete(NO);
}
else if (failure)
{
failure(error);
}
}];
}
- (BOOL)isScalarUrl:(NSString *)urlString forUser:(NSString*)userId
{
BOOL isScalarUrl = NO;
// TODO: Do we need to add `integrationsWidgetsUrls` to `WidgetManagerConfig`?
NSArray<NSString*> *scalarUrlStrings = BuildSettings.integrationsScalarWidgetsPaths;
if (scalarUrlStrings.count == 0)
{
NSString *apiUrl = [self configForUser:userId].apiUrl;
if (apiUrl)
{
scalarUrlStrings = @[apiUrl];
}
}
for (NSString *scalarUrlString in scalarUrlStrings)
{
if ([urlString hasPrefix:scalarUrlString])
{
isScalarUrl = YES;
break;
}
}
return isScalarUrl;
}
#pragma mark - Private methods
- (NSString *)scalarTokenForMXSession:(MXSession *)mxSession
{
return configs[mxSession.myUser.userId].scalarToken;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
- (void)loadConfigs
{
NSUserDefaults *userDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults;
NSDictionary<NSString*, NSString*> *scalarTokens = [userDefaults objectForKey:@"scalarTokens"];
if (scalarTokens)
{
// Manage migration to WidgetManagerConfig
configs = [NSMutableDictionary dictionary];
for (NSString *userId in scalarTokens)
{
NSString *scalarToken = scalarTokens[userId];
MXLogDebug(@"[WidgetManager] migrate scalarTokens to integrationManagerConfigs for %@", userId);
WidgetManagerConfig *config = [self createWidgetManagerConfigWithAppSettings];
config.scalarToken = scalarToken;
configs[userId] = config;
}
[self saveConfigs];
[userDefaults removeObjectForKey:@"scalarTokens"];
}
else
{
NSData *configsData = [userDefaults objectForKey:@"integrationManagerConfigs"];
if (configsData)
{
// We need to map the config class name since the bundle name was updated otherwise unarchiving crashes.
[NSKeyedUnarchiver setClass:WidgetManagerConfig.class forClassName:@"Riot.WidgetManagerConfig"];
configs = [NSMutableDictionary dictionaryWithDictionary:[NSKeyedUnarchiver unarchiveObjectWithData:configsData]];
}
if (!configs)
{
configs = [NSMutableDictionary dictionary];
}
}
}
- (void)saveConfigs
{
NSUserDefaults *userDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults;
[userDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:configs]
forKey:@"integrationManagerConfigs"];
}
#pragma clang diagnostic pop
#pragma mark - Errors
- (NSError*)errorForNonConfiguredIntegrationManager
{
return [NSError errorWithDomain:WidgetManagerErrorDomain
code:WidgetManagerErrorCodeNoIntegrationsServerConfigured
userInfo:@{NSLocalizedDescriptionKey: [VectorL10n widgetNoIntegrationsServerConfigured]}];
}
- (NSError*)errorForDisabledIntegrationManager
{
return [NSError errorWithDomain:WidgetManagerErrorDomain
code:WidgetManagerErrorCodeDisabledIntegrationsServer
userInfo:@{NSLocalizedDescriptionKey: [VectorL10n widgetIntegrationManagerDisabled]}];
}
- (NSError*)errorForUnavailableJitsiURL
{
return [NSError errorWithDomain:WidgetManagerErrorDomain
code:WidgetManagerErrorCodeUnavailableJitsiURL
userInfo:@{NSLocalizedDescriptionKey: VectorL10n.callJitsiUnableToStart}];
}
@end